Files
nebula/overlay/tun_disabled.go
2026-05-11 11:14:25 -05:00

168 lines
3.7 KiB
Go

package overlay
import (
"context"
"fmt"
"io"
"log/slog"
"net/netip"
"strings"
"github.com/rcrowley/go-metrics"
"github.com/slackhq/nebula/iputil"
"github.com/slackhq/nebula/overlay/tio"
"github.com/slackhq/nebula/routing"
)
type disabledTun struct {
read chan []byte
vpnNetworks []netip.Prefix
// Track these metrics since we don't have the tun device to do it for us
tx metrics.Counter
rx metrics.Counter
l *slog.Logger
numReaders int
}
// disabledQueue is one tio.Queue view onto a shared disabledTun. Each queue
// owns a private batchRet so concurrent Read calls from different reader
// goroutines do not race on the returned slice.
type disabledQueue struct {
parent *disabledTun
batchRet [1]tio.Packet
}
func (q *disabledQueue) Read() ([]tio.Packet, error) {
r, ok := <-q.parent.read
if !ok {
return nil, io.EOF
}
q.parent.tx.Inc(1)
if q.parent.l.Enabled(context.Background(), slog.LevelDebug) {
q.parent.l.Debug("Write payload", "raw", prettyPacket(r))
}
q.batchRet[0] = tio.Packet{Bytes: r}
return q.batchRet[:], nil
}
// Write on a queue forwards to the underlying disabledTun. All queues share
// one ICMP-handling/log path so this is a thin pass-through.
func (q *disabledQueue) Write(b []byte) (int, error) {
return q.parent.Write(b)
}
// Close on a queue is a no-op. The shared channel and metrics are owned by
// the disabledTun; Close on the device tears them down once for everybody.
func (q *disabledQueue) Close() error {
return nil
}
func newDisabledTun(vpnNetworks []netip.Prefix, queueLen int, metricsEnabled bool, l *slog.Logger) *disabledTun {
tun := &disabledTun{
vpnNetworks: vpnNetworks,
read: make(chan []byte, queueLen),
l: l,
numReaders: 1,
}
if metricsEnabled {
tun.tx = metrics.GetOrRegisterCounter("messages.tx.message", nil)
tun.rx = metrics.GetOrRegisterCounter("messages.rx.message", nil)
} else {
tun.tx = &metrics.NilCounter{}
tun.rx = &metrics.NilCounter{}
}
return tun
}
func (*disabledTun) Activate() error {
return nil
}
func (*disabledTun) RoutesFor(addr netip.Addr) routing.Gateways {
return routing.Gateways{}
}
func (t *disabledTun) Networks() []netip.Prefix {
return t.vpnNetworks
}
func (*disabledTun) Name() string {
return "disabled"
}
func (t *disabledTun) handleICMPEchoRequest(b []byte) bool {
out := make([]byte, len(b))
out = iputil.CreateICMPEchoResponse(b, out)
if out == nil {
return false
}
// attempt to write it, but don't block
select {
case t.read <- out:
default:
t.l.Debug("tun_disabled: dropped ICMP Echo Reply response")
}
return true
}
func (t *disabledTun) Write(b []byte) (int, error) {
t.rx.Inc(1)
// Check for ICMP Echo Request before spending time doing the full parsing
if t.handleICMPEchoRequest(b) {
if t.l.Enabled(context.Background(), slog.LevelDebug) {
t.l.Debug("Disabled tun responded to ICMP Echo Request", "raw", prettyPacket(b))
}
} else if t.l.Enabled(context.Background(), slog.LevelDebug) {
t.l.Debug("Disabled tun received unexpected payload", "raw", prettyPacket(b))
}
return len(b), nil
}
func (t *disabledTun) SupportsMultiqueue() bool {
return true
}
func (t *disabledTun) NewMultiQueueReader() error {
t.numReaders++
return nil
}
func (t *disabledTun) Readers() []tio.Queue {
out := make([]tio.Queue, t.numReaders)
for i := range t.numReaders {
out[i] = &disabledQueue{parent: t}
}
return out
}
func (t *disabledTun) Close() error {
if t.read != nil {
close(t.read)
t.read = nil
}
return nil
}
type prettyPacket []byte
func (p prettyPacket) String() string {
var s strings.Builder
for i, b := range p {
if i > 0 && i%8 == 0 {
s.WriteString(" ")
}
s.WriteString(fmt.Sprintf("%02x ", b))
}
return s.String()
}