Compare commits

..

4 Commits

Author SHA1 Message Date
Ryan Huber
29157f413c tell stupid ai not to meddle with the paths on other platforms by adding a copy 2025-10-31 14:16:02 -04:00
Ryan Huber
68746bd907 fix hang on exit 2025-10-31 13:50:52 -04:00
Ryan Huber
51b383f680 forgot these little guys 2025-10-31 13:38:43 -04:00
Ryan Huber
71c849e63e extra stinky slop that works kinda 2025-10-31 13:34:39 -04:00
33 changed files with 3760 additions and 1382 deletions

View File

@@ -7,12 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added
- Experimental Linux UDP offload support: enable `listen.enable_gso` and
`listen.enable_gro` to activate UDP_SEGMENT batching and GRO receive
splitting. Includes automatic capability probing, per-packet fallbacks, and
runtime metrics/logs for visibility.
- Optional Linux TUN `virtio_net_hdr` support: set `tun.enable_vnet_hdr` to
have Nebula negotiate VNET headers and offload flags so future batches can
be delivered to the kernel with metadata instead of per-packet writes.
- Linux UDP send sharding can now be tuned with `listen.send_shards`; defaults
to `GOMAXPROCS` but can be increased to stripe heavy peers across more
goroutines.
### Changed ### Changed
- `default_local_cidr_any` now defaults to false, meaning that any firewall rule - `default_local_cidr_any` now defaults to false, meaning that any firewall rule
intended to target an `unsafe_routes` entry must explicitly declare it via the intended to target an `unsafe_routes` entry must explicitly declare it via the
`local_cidr` field. This is almost always the intended behavior. This flag is `local_cidr` field. This is almost always the intended behavior. This flag is
deprecated and will be removed in a future release. deprecated and will be removed in a future release.
- UDP receive path now enqueues into per-worker lock-free rings, restoring the
`listen.decrypt_workers`/`listen.decrypt_queue_depth` tuning knobs while
eliminating the mutex contention from the old shared channel.
- Increased replay protection window to 32k packets so high-throughput links
tolerate larger bursts of reordering without tripping the anti-replay logic.
## [1.9.4] - 2024-09-09 ## [1.9.4] - 2024-09-09

View File

@@ -13,7 +13,10 @@ import (
"github.com/slackhq/nebula/noiseutil" "github.com/slackhq/nebula/noiseutil"
) )
const ReplayWindow = 1024 // ReplayWindow controls the size of the sliding window used to detect replays.
// High-bandwidth links with GRO/GSO can reorder more than a thousand packets in
// flight, so keep this comfortably above the largest expected burst.
const ReplayWindow = 32768
type ConnectionState struct { type ConnectionState struct {
eKey *NebulaCipherState eKey *NebulaCipherState

6
go.mod
View File

@@ -29,11 +29,11 @@ require (
golang.org/x/sys v0.37.0 golang.org/x/sys v0.37.0
golang.org/x/term v0.36.0 golang.org/x/term v0.36.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b
golang.zx2c4.com/wireguard/windows v0.5.3 golang.zx2c4.com/wireguard/windows v0.5.3
google.golang.org/protobuf v1.36.8 google.golang.org/protobuf v1.36.8
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c gvisor.dev/gvisor v0.0.0-20240423190808-9d7a357edefe
) )
require ( require (
@@ -49,6 +49,6 @@ require (
github.com/vishvananda/netns v0.0.5 // indirect github.com/vishvananda/netns v0.0.5 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/mod v0.24.0 // indirect golang.org/x/mod v0.24.0 // indirect
golang.org/x/time v0.7.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.33.0 // indirect golang.org/x/tools v0.33.0 // indirect
) )

12
go.sum
View File

@@ -215,8 +215,8 @@ golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@@ -230,8 +230,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A= golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b h1:J1CaxgLerRR5lgx3wnr6L04cJFbWoceSK9JWBdglINo=
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= golang.zx2c4.com/wireguard v0.0.0-20230325221338-052af4a8072b/go.mod h1:tqur9LnfstdR9ep2LaJT4lFUl0EjlHtge+gAjmsHUG4=
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -257,5 +257,5 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgIMBW8ZZhT4gLnUyDIhzI= gvisor.dev/gvisor v0.0.0-20240423190808-9d7a357edefe h1:fre4i6mv4iBuz5lCMOzHD1rH1ljqHWSICFmZRbbgp3g=
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g= gvisor.dev/gvisor v0.0.0-20240423190808-9d7a357edefe/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU=

View File

@@ -33,7 +33,8 @@ func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet
// routes packets from the Nebula addr to the Nebula addr through the Nebula // routes packets from the Nebula addr to the Nebula addr through the Nebula
// TUN device. // TUN device.
if immediatelyForwardToSelf { if immediatelyForwardToSelf {
if err := f.writeTun(q, packet); err != nil { _, err := f.readers[q].Write(packet)
if err != nil {
f.l.WithError(err).Error("Failed to forward to tun") f.l.WithError(err).Error("Failed to forward to tun")
} }
} }
@@ -90,7 +91,8 @@ func (f *Interface) rejectInside(packet []byte, out []byte, q int) {
return return
} }
if err := f.writeTun(q, out); err != nil { _, err := f.readers[q].Write(out)
if err != nil {
f.l.WithError(err).Error("Failed to write to tun") f.l.WithError(err).Error("Failed to write to tun")
} }
} }

View File

@@ -5,9 +5,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math/bits"
"net/netip" "net/netip"
"os" "os"
"runtime" "runtime"
"sync"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -21,7 +23,12 @@ import (
"github.com/slackhq/nebula/udp" "github.com/slackhq/nebula/udp"
) )
const mtu = 9001 const (
mtu = 9001
tunReadBufferSize = mtu * 8
defaultDecryptWorkerFactor = 2
defaultInboundQueueDepth = 1024
)
type InterfaceConfig struct { type InterfaceConfig struct {
HostMap *HostMap HostMap *HostMap
@@ -47,8 +54,9 @@ type InterfaceConfig struct {
reQueryWait time.Duration reQueryWait time.Duration
ConntrackCacheTimeout time.Duration ConntrackCacheTimeout time.Duration
batchSize int
l *logrus.Logger l *logrus.Logger
DecryptWorkers int
DecryptQueueDepth int
} }
type Interface struct { type Interface struct {
@@ -85,7 +93,6 @@ type Interface struct {
version string version string
conntrackCacheTimeout time.Duration conntrackCacheTimeout time.Duration
batchSize int
writers []udp.Conn writers []udp.Conn
readers []io.ReadWriteCloser readers []io.ReadWriteCloser
@@ -95,6 +102,166 @@ type Interface struct {
cachedPacketMetrics *cachedPacketMetrics cachedPacketMetrics *cachedPacketMetrics
l *logrus.Logger l *logrus.Logger
ctx context.Context
udpListenWG sync.WaitGroup
inboundPool sync.Pool
decryptWG sync.WaitGroup
decryptQueues []*inboundRing
decryptWorkers int
decryptStates []decryptWorkerState
decryptCounter atomic.Uint32
}
type inboundPacket struct {
addr netip.AddrPort
payload []byte
release func()
queue int
}
type decryptWorkerState struct {
queue *inboundRing
notify chan struct{}
}
type decryptContext struct {
ctTicker *firewall.ConntrackCacheTicker
plain []byte
head header.H
fwPacket firewall.Packet
light *LightHouseHandler
nebula []byte
}
type inboundCell struct {
seq atomic.Uint64
pkt *inboundPacket
}
type inboundRing struct {
mask uint64
cells []inboundCell
enqueuePos atomic.Uint64
dequeuePos atomic.Uint64
}
func newInboundRing(capacity int) *inboundRing {
if capacity < 2 {
capacity = 2
}
size := nextPowerOfTwo(uint32(capacity))
if size < 2 {
size = 2
}
ring := &inboundRing{
mask: uint64(size - 1),
cells: make([]inboundCell, size),
}
for i := range ring.cells {
ring.cells[i].seq.Store(uint64(i))
}
return ring
}
func nextPowerOfTwo(v uint32) uint32 {
if v == 0 {
return 1
}
return 1 << (32 - bits.LeadingZeros32(v-1))
}
func (r *inboundRing) Enqueue(pkt *inboundPacket) bool {
var cell *inboundCell
pos := r.enqueuePos.Load()
for {
cell = &r.cells[pos&r.mask]
seq := cell.seq.Load()
diff := int64(seq) - int64(pos)
if diff == 0 {
if r.enqueuePos.CompareAndSwap(pos, pos+1) {
break
}
} else if diff < 0 {
return false
} else {
pos = r.enqueuePos.Load()
}
}
cell.pkt = pkt
cell.seq.Store(pos + 1)
return true
}
func (r *inboundRing) Dequeue() (*inboundPacket, bool) {
var cell *inboundCell
pos := r.dequeuePos.Load()
for {
cell = &r.cells[pos&r.mask]
seq := cell.seq.Load()
diff := int64(seq) - int64(pos+1)
if diff == 0 {
if r.dequeuePos.CompareAndSwap(pos, pos+1) {
break
}
} else if diff < 0 {
return nil, false
} else {
pos = r.dequeuePos.Load()
}
}
pkt := cell.pkt
cell.pkt = nil
cell.seq.Store(pos + r.mask + 1)
return pkt, true
}
func (f *Interface) getInboundPacket() *inboundPacket {
if pkt, ok := f.inboundPool.Get().(*inboundPacket); ok && pkt != nil {
return pkt
}
return &inboundPacket{}
}
func (f *Interface) putInboundPacket(pkt *inboundPacket) {
if pkt == nil {
return
}
pkt.addr = netip.AddrPort{}
pkt.payload = nil
pkt.release = nil
pkt.queue = 0
f.inboundPool.Put(pkt)
}
func newDecryptContext(f *Interface) *decryptContext {
return &decryptContext{
ctTicker: firewall.NewConntrackCacheTicker(f.conntrackCacheTimeout),
plain: make([]byte, udp.MTU),
head: header.H{},
fwPacket: firewall.Packet{},
light: f.lightHouse.NewRequestHandler(),
nebula: make([]byte, 12, 12),
}
}
func (f *Interface) processInboundPacket(pkt *inboundPacket, ctx *decryptContext) {
if pkt == nil {
return
}
defer func() {
if pkt.release != nil {
pkt.release()
}
f.putInboundPacket(pkt)
}()
ctx.head = header.H{}
ctx.fwPacket = firewall.Packet{}
var cache firewall.ConntrackCache
if ctx.ctTicker != nil {
cache = ctx.ctTicker.Get(f.l)
}
f.readOutsidePackets(pkt.addr, nil, ctx.plain[:0], pkt.payload, &ctx.head, &ctx.fwPacket, ctx.light, ctx.nebula, pkt.queue, cache)
} }
type EncWriter interface { type EncWriter interface {
@@ -112,16 +279,6 @@ type EncWriter interface {
GetCertState() *CertState GetCertState() *CertState
} }
// BatchReader is an interface for readers that support vectorized packet reading
type BatchReader interface {
BatchRead(buffers [][]byte, sizes []int) (int, error)
}
// BatchWriter is an interface for writers that support vectorized packet writing
type BatchWriter interface {
BatchWrite([][]byte) (int, error)
}
type sendRecvErrorConfig uint8 type sendRecvErrorConfig uint8
const ( const (
@@ -174,6 +331,35 @@ func NewInterface(ctx context.Context, c *InterfaceConfig) (*Interface, error) {
} }
cs := c.pki.getCertState() cs := c.pki.getCertState()
decryptWorkers := c.DecryptWorkers
if decryptWorkers < 0 {
decryptWorkers = 0
}
if decryptWorkers == 0 {
decryptWorkers = c.routines * defaultDecryptWorkerFactor
if decryptWorkers < c.routines {
decryptWorkers = c.routines
}
}
if decryptWorkers < 0 {
decryptWorkers = 0
}
if runtime.GOOS != "linux" {
decryptWorkers = 0
}
queueDepth := c.DecryptQueueDepth
if queueDepth <= 0 {
queueDepth = defaultInboundQueueDepth
}
minDepth := c.routines * 64
if minDepth <= 0 {
minDepth = 64
}
if queueDepth < minDepth {
queueDepth = minDepth
}
ifce := &Interface{ ifce := &Interface{
pki: c.pki, pki: c.pki,
hostMap: c.HostMap, hostMap: c.HostMap,
@@ -198,7 +384,6 @@ func NewInterface(ctx context.Context, c *InterfaceConfig) (*Interface, error) {
relayManager: c.relayManager, relayManager: c.relayManager,
connectionManager: c.connectionManager, connectionManager: c.connectionManager,
conntrackCacheTimeout: c.ConntrackCacheTimeout, conntrackCacheTimeout: c.ConntrackCacheTimeout,
batchSize: c.batchSize,
metricHandshakes: metrics.GetOrRegisterHistogram("handshakes", nil, metrics.NewExpDecaySample(1028, 0.015)), metricHandshakes: metrics.GetOrRegisterHistogram("handshakes", nil, metrics.NewExpDecaySample(1028, 0.015)),
messageMetrics: c.MessageMetrics, messageMetrics: c.MessageMetrics,
@@ -208,6 +393,9 @@ func NewInterface(ctx context.Context, c *InterfaceConfig) (*Interface, error) {
}, },
l: c.l, l: c.l,
ctx: ctx,
inboundPool: sync.Pool{New: func() any { return &inboundPacket{} }},
decryptWorkers: decryptWorkers,
} }
ifce.tryPromoteEvery.Store(c.tryPromoteEvery) ifce.tryPromoteEvery.Store(c.tryPromoteEvery)
@@ -216,6 +404,19 @@ func NewInterface(ctx context.Context, c *InterfaceConfig) (*Interface, error) {
ifce.connectionManager.intf = ifce ifce.connectionManager.intf = ifce
if decryptWorkers > 0 {
ifce.decryptQueues = make([]*inboundRing, decryptWorkers)
ifce.decryptStates = make([]decryptWorkerState, decryptWorkers)
for i := 0; i < decryptWorkers; i++ {
queue := newInboundRing(queueDepth)
ifce.decryptQueues[i] = queue
ifce.decryptStates[i] = decryptWorkerState{
queue: queue,
notify: make(chan struct{}, 1),
}
}
}
return ifce, nil return ifce, nil
} }
@@ -255,8 +456,68 @@ func (f *Interface) activate() {
} }
} }
func (f *Interface) startDecryptWorkers() {
if f.decryptWorkers <= 0 || len(f.decryptQueues) == 0 {
return
}
f.decryptWG.Add(f.decryptWorkers)
for i := 0; i < f.decryptWorkers; i++ {
go f.decryptWorker(i)
}
}
func (f *Interface) decryptWorker(id int) {
defer f.decryptWG.Done()
if id < 0 || id >= len(f.decryptStates) {
return
}
state := f.decryptStates[id]
if state.queue == nil {
return
}
ctx := newDecryptContext(f)
for {
for {
pkt, ok := state.queue.Dequeue()
if !ok {
break
}
f.processInboundPacket(pkt, ctx)
}
if f.closed.Load() || f.ctx.Err() != nil {
for {
pkt, ok := state.queue.Dequeue()
if !ok {
return
}
f.processInboundPacket(pkt, ctx)
}
}
select {
case <-f.ctx.Done():
case <-state.notify:
}
}
}
func (f *Interface) notifyDecryptWorker(idx int) {
if idx < 0 || idx >= len(f.decryptStates) {
return
}
state := f.decryptStates[idx]
if state.notify == nil {
return
}
select {
case state.notify <- struct{}{}:
default:
}
}
func (f *Interface) run() { func (f *Interface) run() {
f.startDecryptWorkers()
// Launch n queues to read packets from udp // Launch n queues to read packets from udp
f.udpListenWG.Add(f.routines)
for i := 0; i < f.routines; i++ { for i := 0; i < f.routines; i++ {
go f.listenOut(i) go f.listenOut(i)
} }
@@ -269,6 +530,7 @@ func (f *Interface) run() {
func (f *Interface) listenOut(i int) { func (f *Interface) listenOut(i int) {
runtime.LockOSThread() runtime.LockOSThread()
defer f.udpListenWG.Done()
var li udp.Conn var li udp.Conn
if i > 0 { if i > 0 {
@@ -277,33 +539,78 @@ func (f *Interface) listenOut(i int) {
li = f.outside li = f.outside
} }
ctCache := firewall.NewConntrackCacheTicker(f.conntrackCacheTimeout) useWorkers := f.decryptWorkers > 0 && len(f.decryptQueues) > 0
lhh := f.lightHouse.NewRequestHandler() var (
plaintext := make([]byte, udp.MTU) inlineTicker *firewall.ConntrackCacheTicker
h := &header.H{} inlineHandler *LightHouseHandler
fwPacket := &firewall.Packet{} inlinePlain []byte
nb := make([]byte, 12) inlineHeader header.H
inlinePacket firewall.Packet
inlineNB []byte
inlineCtx *decryptContext
)
li.ListenOut(func(fromUdpAddr netip.AddrPort, payload []byte) { if useWorkers {
f.readOutsidePackets(fromUdpAddr, nil, plaintext[:0], payload, h, fwPacket, lhh, nb, i, ctCache.Get(f.l)) inlineCtx = newDecryptContext(f)
} else {
inlineTicker = firewall.NewConntrackCacheTicker(f.conntrackCacheTimeout)
inlineHandler = f.lightHouse.NewRequestHandler()
inlinePlain = make([]byte, udp.MTU)
inlineNB = make([]byte, 12, 12)
}
li.ListenOut(func(fromUdpAddr netip.AddrPort, payload []byte, release func()) {
if !useWorkers {
if release != nil {
defer release()
}
select {
case <-f.ctx.Done():
return
default:
}
inlineHeader = header.H{}
inlinePacket = firewall.Packet{}
var cache firewall.ConntrackCache
if inlineTicker != nil {
cache = inlineTicker.Get(f.l)
}
f.readOutsidePackets(fromUdpAddr, nil, inlinePlain[:0], payload, &inlineHeader, &inlinePacket, inlineHandler, inlineNB, i, cache)
return
}
if f.ctx.Err() != nil {
if release != nil {
release()
}
return
}
pkt := f.getInboundPacket()
pkt.addr = fromUdpAddr
pkt.payload = payload
pkt.release = release
pkt.queue = i
queueCount := len(f.decryptQueues)
if queueCount == 0 {
f.processInboundPacket(pkt, inlineCtx)
return
}
w := int(f.decryptCounter.Add(1)-1) % queueCount
if w < 0 || w >= queueCount || !f.decryptQueues[w].Enqueue(pkt) {
f.processInboundPacket(pkt, inlineCtx)
return
}
f.notifyDecryptWorker(w)
}) })
} }
func (f *Interface) listenIn(reader io.ReadWriteCloser, i int) { func (f *Interface) listenIn(reader io.ReadWriteCloser, i int) {
runtime.LockOSThread() runtime.LockOSThread()
// Check if reader supports batch operations packet := make([]byte, tunReadBufferSize)
if batchReader, ok := reader.(BatchReader); ok { out := make([]byte, tunReadBufferSize)
err := f.listenInBatch(batchReader, i)
if err != nil {
f.l.WithError(err).Error("Fatal error in batch packet reader, exiting goroutine")
}
return
}
// Fall back to single-packet mode
packet := make([]byte, mtu)
out := make([]byte, mtu)
fwPacket := &firewall.Packet{} fwPacket := &firewall.Packet{}
nb := make([]byte, 12, 12) nb := make([]byte, 12, 12)
@@ -316,85 +623,15 @@ func (f *Interface) listenIn(reader io.ReadWriteCloser, i int) {
return return
} }
f.l.WithError(err).Error("Fatal error while reading outbound packet, exiting goroutine") f.l.WithError(err).Error("Error while reading outbound packet")
return // This only seems to happen when something fatal happens to the fd, so exit.
os.Exit(2)
} }
f.consumeInsidePacket(packet[:n], fwPacket, nb, out, i, conntrackCache.Get(f.l)) f.consumeInsidePacket(packet[:n], fwPacket, nb, out, i, conntrackCache.Get(f.l))
} }
} }
// listenInBatch handles vectorized packet reading for improved performance
func (f *Interface) listenInBatch(reader BatchReader, i int) error {
// Allocate per-packet state and buffers for batch reading
batchSize := f.batchSize
if batchSize <= 0 {
batchSize = 64 // Fallback to default if not configured
}
fwPackets := make([]*firewall.Packet, batchSize)
outBuffers := make([][]byte, batchSize)
nbBuffers := make([][]byte, batchSize)
packets := make([][]byte, batchSize)
sizes := make([]int, batchSize)
for j := 0; j < batchSize; j++ {
fwPackets[j] = &firewall.Packet{}
outBuffers[j] = make([]byte, mtu)
nbBuffers[j] = make([]byte, 12)
packets[j] = make([]byte, mtu)
}
conntrackCache := firewall.NewConntrackCacheTicker(f.conntrackCacheTimeout)
for {
n, err := reader.BatchRead(packets, sizes)
if err != nil {
if errors.Is(err, os.ErrClosed) && f.closed.Load() {
return nil
}
return fmt.Errorf("error while batch reading outbound packets: %w", err)
}
// Process each packet in the batch
cache := conntrackCache.Get(f.l)
for idx := 0; idx < n; idx++ {
if sizes[idx] > 0 {
// Use modulo to reuse fw packet state if batch is larger than our pre-allocated state
stateIdx := idx % len(fwPackets)
f.consumeInsidePacket(packets[idx][:sizes[idx]], fwPackets[stateIdx], nbBuffers[stateIdx], outBuffers[stateIdx], i, cache)
}
}
}
}
// writeTunBatch attempts to write multiple packets to the TUN device using batch operations if supported
func (f *Interface) writeTunBatch(q int, packets [][]byte) error {
if len(packets) == 0 {
return nil
}
// Check if the reader/writer supports batch operations
if batchWriter, ok := f.readers[q].(BatchWriter); ok {
_, err := batchWriter.BatchWrite(packets)
return err
}
// Fall back to writing packets individually
for _, packet := range packets {
if _, err := f.readers[q].Write(packet); err != nil {
return err
}
}
return nil
}
// writeTun writes a single packet to the TUN device
func (f *Interface) writeTun(q int, packet []byte) error {
_, err := f.readers[q].Write(packet)
return err
}
func (f *Interface) RegisterConfigChangeCallbacks(c *config.C) { func (f *Interface) RegisterConfigChangeCallbacks(c *config.C) {
c.RegisterReloadCallback(f.reloadFirewall) c.RegisterReloadCallback(f.reloadFirewall)
c.RegisterReloadCallback(f.reloadSendRecvError) c.RegisterReloadCallback(f.reloadSendRecvError)
@@ -551,6 +788,19 @@ func (f *Interface) Close() error {
} }
} }
f.udpListenWG.Wait()
if f.decryptWorkers > 0 {
for _, state := range f.decryptStates {
if state.notify != nil {
select {
case state.notify <- struct{}{}:
default:
}
}
}
f.decryptWG.Wait()
}
// Release the tun device // Release the tun device
return f.inside.Close() return f.inside.Close()
} }

View File

@@ -120,6 +120,8 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
l.WithField("duration", conntrackCacheTimeout).Info("Using routine-local conntrack cache") l.WithField("duration", conntrackCacheTimeout).Info("Using routine-local conntrack cache")
} }
udp.SetDisableUDPCsum(c.GetBool("listen.disable_udp_checksum", false))
var tun overlay.Device var tun overlay.Device
if !configTest { if !configTest {
c.CatchHUP(ctx) c.CatchHUP(ctx)
@@ -221,6 +223,9 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
} }
} }
decryptWorkers := c.GetInt("listen.decrypt_workers", 0)
decryptQueueDepth := c.GetInt("listen.decrypt_queue_depth", 0)
ifConfig := &InterfaceConfig{ ifConfig := &InterfaceConfig{
HostMap: hostMap, HostMap: hostMap,
Inside: tun, Inside: tun,
@@ -242,8 +247,9 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
relayManager: NewRelayManager(ctx, l, hostMap, c), relayManager: NewRelayManager(ctx, l, hostMap, c),
punchy: punchy, punchy: punchy,
ConntrackCacheTimeout: conntrackCacheTimeout, ConntrackCacheTimeout: conntrackCacheTimeout,
batchSize: c.GetInt("tun.batch_size", 64),
l: l, l: l,
DecryptWorkers: decryptWorkers,
DecryptQueueDepth: decryptQueueDepth,
} }
var ifce *Interface var ifce *Interface

View File

@@ -333,13 +333,12 @@ func parseV6(data []byte, incoming bool, fp *firewall.Packet) error {
} }
fp.Protocol = uint8(proto) fp.Protocol = uint8(proto)
ports := data[offset : offset+4]
if incoming { if incoming {
fp.RemotePort = binary.BigEndian.Uint16(ports[0:2]) fp.RemotePort = binary.BigEndian.Uint16(data[offset : offset+2])
fp.LocalPort = binary.BigEndian.Uint16(ports[2:4]) fp.LocalPort = binary.BigEndian.Uint16(data[offset+2 : offset+4])
} else { } else {
fp.LocalPort = binary.BigEndian.Uint16(ports[0:2]) fp.LocalPort = binary.BigEndian.Uint16(data[offset : offset+2])
fp.RemotePort = binary.BigEndian.Uint16(ports[2:4]) fp.RemotePort = binary.BigEndian.Uint16(data[offset+2 : offset+4])
} }
fp.Fragment = false fp.Fragment = false
@@ -471,7 +470,13 @@ func (f *Interface) decryptToTun(hostinfo *HostInfo, messageCounter uint64, out
out, err = hostinfo.ConnectionState.dKey.DecryptDanger(out, packet[:header.Len], packet[header.Len:], messageCounter, nb) out, err = hostinfo.ConnectionState.dKey.DecryptDanger(out, packet[:header.Len], packet[header.Len:], messageCounter, nb)
if err != nil { if err != nil {
hostinfo.logger(f.l).WithError(err).Error("Failed to decrypt packet") hostinfo.logger(f.l).
WithError(err).
WithField("tag", "decrypt-debug").
WithField("remoteIndexLocal", hostinfo.localIndexId).
WithField("messageCounter", messageCounter).
WithField("packet_len", len(packet)).
Error("Failed to decrypt packet")
return false return false
} }

View File

@@ -3,6 +3,7 @@ package overlay
import ( import (
"fmt" "fmt"
"math" "math"
"net"
"net/netip" "net/netip"
"runtime" "runtime"
"strconv" "strconv"
@@ -304,3 +305,29 @@ func parseUnsafeRoutes(c *config.C, networks []netip.Prefix) ([]Route, error) {
return routes, nil return routes, nil
} }
func ipWithin(o *net.IPNet, i *net.IPNet) bool {
// Make sure o contains the lowest form of i
if !o.Contains(i.IP.Mask(i.Mask)) {
return false
}
// Find the max ip in i
ip4 := i.IP.To4()
if ip4 == nil {
return false
}
last := make(net.IP, len(ip4))
copy(last, ip4)
for x := range ip4 {
last[x] |= ^i.Mask[x]
}
// Make sure o contains the max
if !o.Contains(last) {
return false
}
return true
}

View File

@@ -225,7 +225,6 @@ func Test_parseUnsafeRoutes(t *testing.T) {
// no mtu // no mtu
c.Settings["tun"] = map[string]any{"unsafe_routes": []any{map[string]any{"via": "127.0.0.1", "route": "1.0.0.0/8"}}} c.Settings["tun"] = map[string]any{"unsafe_routes": []any{map[string]any{"via": "127.0.0.1", "route": "1.0.0.0/8"}}}
routes, err = parseUnsafeRoutes(c, []netip.Prefix{n}) routes, err = parseUnsafeRoutes(c, []netip.Prefix{n})
require.NoError(t, err)
assert.Len(t, routes, 1) assert.Len(t, routes, 1)
assert.Equal(t, 0, routes[0].MTU) assert.Equal(t, 0, routes[0].MTU)
@@ -319,7 +318,7 @@ func Test_makeRouteTree(t *testing.T) {
ip, err = netip.ParseAddr("1.1.0.1") ip, err = netip.ParseAddr("1.1.0.1")
require.NoError(t, err) require.NoError(t, err)
_, ok = routeTree.Lookup(ip) r, ok = routeTree.Lookup(ip)
assert.False(t, ok) assert.False(t, ok)
} }

View File

@@ -1,6 +1,8 @@
package overlay package overlay
import ( import (
"fmt"
"net"
"net/netip" "net/netip"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@@ -70,3 +72,51 @@ func findRemovedRoutes(newRoutes, oldRoutes []Route) []Route {
return removed return removed
} }
func prefixToMask(prefix netip.Prefix) netip.Addr {
pLen := 128
if prefix.Addr().Is4() {
pLen = 32
}
addr, _ := netip.AddrFromSlice(net.CIDRMask(prefix.Bits(), pLen))
return addr
}
func flipBytes(b []byte) []byte {
for i := 0; i < len(b); i++ {
b[i] ^= 0xFF
}
return b
}
func orBytes(a []byte, b []byte) []byte {
ret := make([]byte, len(a))
for i := 0; i < len(a); i++ {
ret[i] = a[i] | b[i]
}
return ret
}
func getBroadcast(cidr netip.Prefix) netip.Addr {
broadcast, _ := netip.AddrFromSlice(
orBytes(
cidr.Addr().AsSlice(),
flipBytes(prefixToMask(cidr).AsSlice()),
),
)
return broadcast
}
func selectGateway(dest netip.Prefix, gateways []netip.Prefix) (netip.Prefix, error) {
for _, gateway := range gateways {
if dest.Addr().Is4() && gateway.Addr().Is4() {
return gateway, nil
}
if dest.Addr().Is6() && gateway.Addr().Is6() {
return gateway, nil
}
}
return netip.Prefix{}, fmt.Errorf("no gateway found for %v in the list of vpn networks", dest)
}

View File

@@ -1,5 +1,5 @@
//go:build darwin && !ios && !e2e_testing //go:build !ios && !e2e_testing
// +build darwin,!ios,!e2e_testing // +build !ios,!e2e_testing
package overlay package overlay
@@ -8,27 +8,48 @@ import (
"fmt" "fmt"
"io" "io"
"net/netip" "net/netip"
"os"
"sync/atomic"
"syscall"
"unsafe" "unsafe"
"github.com/gaissmai/bart"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/slackhq/nebula/config" "github.com/slackhq/nebula/config"
"github.com/slackhq/nebula/routing"
"github.com/slackhq/nebula/util" "github.com/slackhq/nebula/util"
netroute "golang.org/x/net/route" netroute "golang.org/x/net/route"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
wgtun "golang.zx2c4.com/wireguard/tun"
) )
type tun struct { type tun struct {
io.ReadWriteCloser
Device string
vpnNetworks []netip.Prefix
DefaultMTU int
Routes atomic.Pointer[[]Route]
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
linkAddr *netroute.LinkAddr linkAddr *netroute.LinkAddr
l *logrus.Logger
// cache out buffer since we need to prepend 4 bytes for tun metadata
out []byte
} }
// ioctl structures for Darwin network configuration
type ifReq struct { type ifReq struct {
Name [unix.IFNAMSIZ]byte Name [unix.IFNAMSIZ]byte
Flags uint16 Flags uint16
pad [8]byte pad [8]byte
} }
const (
_SIOCAIFADDR_IN6 = 2155899162
_UTUN_OPT_IFNAME = 2
_IN6_IFF_NODAD = 0x0020
_IN6_IFF_SECURED = 0x0400
utunControlName = "com.apple.net.utun_control"
)
type ifreqMTU struct { type ifreqMTU struct {
Name [16]byte Name [16]byte
MTU int32 MTU int32
@@ -58,61 +79,60 @@ type ifreqAlias6 struct {
Lifetime addrLifetime Lifetime addrLifetime
} }
const ( func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*tun, error) {
_SIOCAIFADDR_IN6 = 2155899162
_IN6_IFF_NODAD = 0x0020
)
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*wgTun, error) {
return nil, fmt.Errorf("newTunFromFd not supported on Darwin")
}
func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*wgTun, error) {
name := c.GetString("tun.dev", "") name := c.GetString("tun.dev", "")
deviceName := "utun"
// Parse device name to handle utun[0-9]+ format
if name != "" && name != "utun" {
ifIndex := -1 ifIndex := -1
if name != "" && name != "utun" {
_, err := fmt.Sscanf(name, "utun%d", &ifIndex) _, err := fmt.Sscanf(name, "utun%d", &ifIndex)
if err != nil || ifIndex < 0 { if err != nil || ifIndex < 0 {
// NOTE: we don't make this error so we don't break existing // NOTE: we don't make this error so we don't break existing
// configs that set a name before it was used. // configs that set a name before it was used.
l.Warn("interface name must be utun[0-9]+ on Darwin, ignoring") l.Warn("interface name must be utun[0-9]+ on Darwin, ignoring")
} else { ifIndex = -1
deviceName = name
} }
} }
mtu := c.GetInt("tun.mtu", DefaultMTU) fd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, unix.AF_SYS_CONTROL)
// Create WireGuard TUN device
tunDevice, err := wgtun.CreateTUN(deviceName, mtu)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create TUN device: %w", err) return nil, fmt.Errorf("system socket: %v", err)
} }
// Get the actual device name var ctlInfo = &unix.CtlInfo{}
actualName, err := tunDevice.Name() copy(ctlInfo.Name[:], utunControlName)
err = unix.IoctlCtlInfo(fd, ctlInfo)
if err != nil { if err != nil {
tunDevice.Close() return nil, fmt.Errorf("CTLIOCGINFO: %v", err)
return nil, fmt.Errorf("failed to get TUN device name: %w", err)
} }
t := &wgTun{ err = unix.Connect(fd, &unix.SockaddrCtl{
tunDevice: tunDevice, ID: ctlInfo.Id,
Unit: uint32(ifIndex) + 1,
})
if err != nil {
return nil, fmt.Errorf("SYS_CONNECT: %v", err)
}
name, err = unix.GetsockoptString(fd, unix.AF_SYS_CONTROL, _UTUN_OPT_IFNAME)
if err != nil {
return nil, fmt.Errorf("failed to retrieve tun name: %w", err)
}
err = unix.SetNonblock(fd, true)
if err != nil {
return nil, fmt.Errorf("SetNonblock: %v", err)
}
t := &tun{
ReadWriteCloser: os.NewFile(uintptr(fd), ""),
Device: name,
vpnNetworks: vpnNetworks, vpnNetworks: vpnNetworks,
MaxMTU: mtu, DefaultMTU: c.GetInt("tun.mtu", DefaultMTU),
DefaultMTU: mtu,
l: l, l: l,
} }
// Create Darwin-specific route manager
t.routeManager = &tun{}
err = t.reload(c, true) err = t.reload(c, true)
if err != nil { if err != nil {
tunDevice.Close()
return nil, err return nil, err
} }
@@ -123,251 +143,215 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (
} }
}) })
l.WithField("name", actualName).Info("Created WireGuard TUN device")
return t, nil return t, nil
} }
func (rm *tun) Activate(t *wgTun) error { func (t *tun) deviceBytes() (o [16]byte) {
name, err := t.tunDevice.Name() for i, c := range t.Device {
if err != nil { o[i] = byte(c)
return fmt.Errorf("failed to get device name: %w", err) }
return
} }
// Set the MTU func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*tun, error) {
rm.SetMTU(t, t.MaxMTU) return nil, fmt.Errorf("newTunFromFd not supported in Darwin")
}
// Add IP addresses func (t *tun) Close() error {
for _, network := range t.vpnNetworks { if t.ReadWriteCloser != nil {
if err := rm.addIP(t, name, network); err != nil { return t.ReadWriteCloser.Close()
}
return nil
}
func (t *tun) Activate() error {
devName := t.deviceBytes()
s, err := unix.Socket(
unix.AF_INET,
unix.SOCK_DGRAM,
unix.IPPROTO_IP,
)
if err != nil {
return err return err
} }
defer unix.Close(s)
fd := uintptr(s)
// Set the MTU on the device
ifm := ifreqMTU{Name: devName, MTU: int32(t.DefaultMTU)}
if err = ioctl(fd, unix.SIOCSIFMTU, uintptr(unsafe.Pointer(&ifm))); err != nil {
return fmt.Errorf("failed to set tun mtu: %v", err)
} }
// Bring up the interface using ioctl // Get the device flags
if err := rm.bringUpInterface(name); err != nil { ifrf := ifReq{Name: devName}
return fmt.Errorf("failed to bring up interface: %w", err) if err = ioctl(fd, unix.SIOCGIFFLAGS, uintptr(unsafe.Pointer(&ifrf))); err != nil {
return fmt.Errorf("failed to get tun flags: %s", err)
} }
// Get the link address for routing linkAddr, err := getLinkAddr(t.Device)
linkAddr, err := getLinkAddr(name)
if err != nil { if err != nil {
return fmt.Errorf("failed to get link address: %w", err) return err
} }
if linkAddr == nil { if linkAddr == nil {
return fmt.Errorf("unable to discover link_addr for tun interface") return fmt.Errorf("unable to discover link_addr for tun interface")
} }
rm.linkAddr = linkAddr t.linkAddr = linkAddr
// Set the routes for _, network := range t.vpnNetworks {
if err := rm.AddRoutes(t, false); err != nil { if network.Addr().Is4() {
err = t.activate4(network)
if err != nil {
return err
}
} else {
err = t.activate6(network)
if err != nil {
return err
}
}
}
// Run the interface
ifrf.Flags = ifrf.Flags | unix.IFF_UP | unix.IFF_RUNNING
if err = ioctl(fd, unix.SIOCSIFFLAGS, uintptr(unsafe.Pointer(&ifrf))); err != nil {
return fmt.Errorf("failed to run tun device: %s", err)
}
// Unsafe path routes
return t.addRoutes(false)
}
func (t *tun) activate4(network netip.Prefix) error {
s, err := unix.Socket(
unix.AF_INET,
unix.SOCK_DGRAM,
unix.IPPROTO_IP,
)
if err != nil {
return err
}
defer unix.Close(s)
ifr := ifreqAlias4{
Name: t.deviceBytes(),
Addr: unix.RawSockaddrInet4{
Len: unix.SizeofSockaddrInet4,
Family: unix.AF_INET,
Addr: network.Addr().As4(),
},
DstAddr: unix.RawSockaddrInet4{
Len: unix.SizeofSockaddrInet4,
Family: unix.AF_INET,
Addr: network.Addr().As4(),
},
MaskAddr: unix.RawSockaddrInet4{
Len: unix.SizeofSockaddrInet4,
Family: unix.AF_INET,
Addr: prefixToMask(network).As4(),
},
}
if err := ioctl(uintptr(s), unix.SIOCAIFADDR, uintptr(unsafe.Pointer(&ifr))); err != nil {
return fmt.Errorf("failed to set tun v4 address: %s", err)
}
err = addRoute(network, t.linkAddr)
if err != nil {
return err return err
} }
return nil return nil
} }
func (rm *tun) bringUpInterface(name string) error { func (t *tun) activate6(network netip.Prefix) error {
// Open a socket for ioctl s, err := unix.Socket(
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0) unix.AF_INET6,
unix.SOCK_DGRAM,
unix.IPPROTO_IP,
)
if err != nil { if err != nil {
return fmt.Errorf("failed to create socket: %w", err) return err
}
defer unix.Close(fd)
// Get current flags
var ifrf ifReq
copy(ifrf.Name[:], name)
if err := ioctl(uintptr(fd), unix.SIOCGIFFLAGS, uintptr(unsafe.Pointer(&ifrf))); err != nil {
return fmt.Errorf("failed to get interface flags: %w", err)
}
// Set IFF_UP and IFF_RUNNING flags
ifrf.Flags = ifrf.Flags | unix.IFF_UP | unix.IFF_RUNNING
if err := ioctl(uintptr(fd), unix.SIOCSIFFLAGS, uintptr(unsafe.Pointer(&ifrf))); err != nil {
return fmt.Errorf("failed to set interface flags: %w", err)
}
return nil
}
func (rm *tun) SetMTU(t *wgTun, mtu int) {
name, err := t.tunDevice.Name()
if err != nil {
t.l.WithError(err).Error("Failed to get device name for MTU set")
return
}
// Open a socket for ioctl
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
if err != nil {
t.l.WithError(err).Error("Failed to create socket for MTU set")
return
}
defer unix.Close(fd)
// Prepare the ioctl request
var ifr ifreqMTU
copy(ifr.Name[:], name)
ifr.MTU = int32(mtu)
// Set the MTU using ioctl
if err := ioctl(uintptr(fd), unix.SIOCSIFMTU, uintptr(unsafe.Pointer(&ifr))); err != nil {
t.l.WithError(err).Error("Failed to set tun mtu via ioctl")
}
}
func (rm *tun) SetDefaultRoute(t *wgTun, cidr netip.Prefix) error {
// On Darwin, routes are set via ifconfig and route commands
return nil
}
func (rm *tun) AddRoutes(t *wgTun, logErrors bool) error {
routes := *t.Routes.Load()
for _, r := range routes {
if !r.Install {
continue
}
err := rm.addRoute(r.Cidr)
if err != nil {
if errors.Is(err, unix.EEXIST) {
t.l.WithField("route", r.Cidr).
Warnf("unable to add unsafe_route, identical route already exists")
} else {
retErr := util.NewContextualError("Failed to add route", map[string]any{"route": r}, err)
if logErrors {
retErr.Log(t.l)
} else {
return retErr
}
}
} else {
t.l.WithField("route", r).Info("Added route")
}
}
return nil
}
func (rm *tun) RemoveRoutes(t *wgTun, routes []Route) {
for _, r := range routes {
if !r.Install {
continue
}
err := rm.delRoute(r.Cidr)
if err != nil {
t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
} else {
t.l.WithField("route", r).Info("Removed route")
}
}
}
func (rm *tun) NewMultiQueueReader(t *wgTun) (io.ReadWriteCloser, error) {
// Darwin doesn't support multi-queue TUN devices in the same way as Linux
// Return a reader that wraps the same device
return &wgTunReader{
parent: t,
tunDevice: t.tunDevice,
offset: 0,
l: t.l,
}, nil
}
func (rm *tun) addIP(t *wgTun, name string, network netip.Prefix) error {
addr := network.Addr()
if addr.Is4() {
return rm.addIPv4(name, network)
} else {
return rm.addIPv6(name, network)
}
}
func (rm *tun) addIPv4(name string, network netip.Prefix) error {
// Open an IPv4 socket for ioctl
s, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
if err != nil {
return fmt.Errorf("failed to create IPv4 socket: %w", err)
} }
defer unix.Close(s) defer unix.Close(s)
var ifr ifreqAlias4 ifr := ifreqAlias6{
copy(ifr.Name[:], name) Name: t.deviceBytes(),
Addr: unix.RawSockaddrInet6{
// Set the address
ifr.Addr = unix.RawSockaddrInet4{
Len: unix.SizeofSockaddrInet4,
Family: unix.AF_INET,
Addr: network.Addr().As4(),
}
// Set the destination address (same as address for point-to-point)
ifr.DstAddr = unix.RawSockaddrInet4{
Len: unix.SizeofSockaddrInet4,
Family: unix.AF_INET,
Addr: network.Addr().As4(),
}
// Set the netmask
ifr.MaskAddr = unix.RawSockaddrInet4{
Len: unix.SizeofSockaddrInet4,
Family: unix.AF_INET,
Addr: prefixToMask(network).As4(),
}
if err := ioctl(uintptr(s), unix.SIOCAIFADDR, uintptr(unsafe.Pointer(&ifr))); err != nil {
return fmt.Errorf("failed to set IPv4 address via ioctl: %w", err)
}
return nil
}
func (rm *tun) addIPv6(name string, network netip.Prefix) error {
// Open an IPv6 socket for ioctl
s, err := unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_IP)
if err != nil {
return fmt.Errorf("failed to create IPv6 socket: %w", err)
}
defer unix.Close(s)
var ifr ifreqAlias6
copy(ifr.Name[:], name)
// Set the address
ifr.Addr = unix.RawSockaddrInet6{
Len: unix.SizeofSockaddrInet6, Len: unix.SizeofSockaddrInet6,
Family: unix.AF_INET6, Family: unix.AF_INET6,
Addr: network.Addr().As16(), Addr: network.Addr().As16(),
} },
PrefixMask: unix.RawSockaddrInet6{
// Set the prefix mask
ifr.PrefixMask = unix.RawSockaddrInet6{
Len: unix.SizeofSockaddrInet6, Len: unix.SizeofSockaddrInet6,
Family: unix.AF_INET6, Family: unix.AF_INET6,
Addr: prefixToMask(network).As16(), Addr: prefixToMask(network).As16(),
} },
Lifetime: addrLifetime{
// Set lifetime (never expires) // never expires
ifr.Lifetime = addrLifetime{
Vltime: 0xffffffff, Vltime: 0xffffffff,
Pltime: 0xffffffff, Pltime: 0xffffffff,
},
Flags: _IN6_IFF_NODAD,
} }
// Set flags (no DAD - Duplicate Address Detection)
ifr.Flags = _IN6_IFF_NODAD
if err := ioctl(uintptr(s), _SIOCAIFADDR_IN6, uintptr(unsafe.Pointer(&ifr))); err != nil { if err := ioctl(uintptr(s), _SIOCAIFADDR_IN6, uintptr(unsafe.Pointer(&ifr))); err != nil {
return fmt.Errorf("failed to set IPv6 address via ioctl: %w", err) return fmt.Errorf("failed to set tun address: %s", err)
} }
return nil return nil
} }
func (t *tun) reload(c *config.C, initial bool) error {
change, routes, err := getAllRoutesFromConfig(c, t.vpnNetworks, initial)
if err != nil {
return err
}
if !initial && !change {
return nil
}
routeTree, err := makeRouteTree(t.l, routes, false)
if err != nil {
return err
}
// Teach nebula how to handle the routes before establishing them in the system table
oldRoutes := t.Routes.Swap(&routes)
t.routeTree.Store(routeTree)
if !initial {
// Remove first, if the system removes a wanted route hopefully it will be re-added next
err := t.removeRoutes(findRemovedRoutes(routes, *oldRoutes))
if err != nil {
util.LogWithContextIfNeeded("Failed to remove routes", err, t.l)
}
// Ensure any routes we actually want are installed
err = t.addRoutes(true)
if err != nil {
// Catch any stray logs
util.LogWithContextIfNeeded("Failed to add routes", err, t.l)
}
}
return nil
}
func (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {
r, ok := t.routeTree.Load().Lookup(ip)
if ok {
return r
}
return routing.Gateways{}
}
// Get the LinkAddr for the interface of the given name
// Is there an easier way to fetch this when we create the interface?
// Maybe SIOCGIFINDEX? but this doesn't appear to exist in the darwin headers.
func getLinkAddr(name string) (*netroute.LinkAddr, error) { func getLinkAddr(name string) (*netroute.LinkAddr, error) {
rib, err := netroute.FetchRIB(unix.AF_UNSPEC, unix.NET_RT_IFLIST, 0) rib, err := netroute.FetchRIB(unix.AF_UNSPEC, unix.NET_RT_IFLIST, 0)
if err != nil { if err != nil {
@@ -393,7 +377,53 @@ func getLinkAddr(name string) (*netroute.LinkAddr, error) {
return nil, nil return nil, nil
} }
func (rm *tun) addRoute(prefix netip.Prefix) error { func (t *tun) addRoutes(logErrors bool) error {
routes := *t.Routes.Load()
for _, r := range routes {
if len(r.Via) == 0 || !r.Install {
// We don't allow route MTUs so only install routes with a via
continue
}
err := addRoute(r.Cidr, t.linkAddr)
if err != nil {
if errors.Is(err, unix.EEXIST) {
t.l.WithField("route", r.Cidr).
Warnf("unable to add unsafe_route, identical route already exists")
} else {
retErr := util.NewContextualError("Failed to add route", map[string]any{"route": r}, err)
if logErrors {
retErr.Log(t.l)
} else {
return retErr
}
}
} else {
t.l.WithField("route", r).Info("Added route")
}
}
return nil
}
func (t *tun) removeRoutes(routes []Route) error {
for _, r := range routes {
if !r.Install {
continue
}
err := delRoute(r.Cidr, t.linkAddr)
if err != nil {
t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
} else {
t.l.WithField("route", r).Info("Removed route")
}
}
return nil
}
func addRoute(prefix netip.Prefix, gateway netroute.Addr) error {
sock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC) sock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
if err != nil { if err != nil {
return fmt.Errorf("unable to create AF_ROUTE socket: %v", err) return fmt.Errorf("unable to create AF_ROUTE socket: %v", err)
@@ -411,13 +441,13 @@ func (rm *tun) addRoute(prefix netip.Prefix) error {
route.Addrs = []netroute.Addr{ route.Addrs = []netroute.Addr{
unix.RTAX_DST: &netroute.Inet4Addr{IP: prefix.Masked().Addr().As4()}, unix.RTAX_DST: &netroute.Inet4Addr{IP: prefix.Masked().Addr().As4()},
unix.RTAX_NETMASK: &netroute.Inet4Addr{IP: prefixToMask(prefix).As4()}, unix.RTAX_NETMASK: &netroute.Inet4Addr{IP: prefixToMask(prefix).As4()},
unix.RTAX_GATEWAY: rm.linkAddr, unix.RTAX_GATEWAY: gateway,
} }
} else { } else {
route.Addrs = []netroute.Addr{ route.Addrs = []netroute.Addr{
unix.RTAX_DST: &netroute.Inet6Addr{IP: prefix.Masked().Addr().As16()}, unix.RTAX_DST: &netroute.Inet6Addr{IP: prefix.Masked().Addr().As16()},
unix.RTAX_NETMASK: &netroute.Inet6Addr{IP: prefixToMask(prefix).As16()}, unix.RTAX_NETMASK: &netroute.Inet6Addr{IP: prefixToMask(prefix).As16()},
unix.RTAX_GATEWAY: rm.linkAddr, unix.RTAX_GATEWAY: gateway,
} }
} }
@@ -434,7 +464,7 @@ func (rm *tun) addRoute(prefix netip.Prefix) error {
return nil return nil
} }
func (rm *tun) delRoute(prefix netip.Prefix) error { func delRoute(prefix netip.Prefix, gateway netroute.Addr) error {
sock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC) sock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
if err != nil { if err != nil {
return fmt.Errorf("unable to create AF_ROUTE socket: %v", err) return fmt.Errorf("unable to create AF_ROUTE socket: %v", err)
@@ -451,13 +481,13 @@ func (rm *tun) delRoute(prefix netip.Prefix) error {
route.Addrs = []netroute.Addr{ route.Addrs = []netroute.Addr{
unix.RTAX_DST: &netroute.Inet4Addr{IP: prefix.Masked().Addr().As4()}, unix.RTAX_DST: &netroute.Inet4Addr{IP: prefix.Masked().Addr().As4()},
unix.RTAX_NETMASK: &netroute.Inet4Addr{IP: prefixToMask(prefix).As4()}, unix.RTAX_NETMASK: &netroute.Inet4Addr{IP: prefixToMask(prefix).As4()},
unix.RTAX_GATEWAY: rm.linkAddr, unix.RTAX_GATEWAY: gateway,
} }
} else { } else {
route.Addrs = []netroute.Addr{ route.Addrs = []netroute.Addr{
unix.RTAX_DST: &netroute.Inet6Addr{IP: prefix.Masked().Addr().As16()}, unix.RTAX_DST: &netroute.Inet6Addr{IP: prefix.Masked().Addr().As16()},
unix.RTAX_NETMASK: &netroute.Inet6Addr{IP: prefixToMask(prefix).As16()}, unix.RTAX_NETMASK: &netroute.Inet6Addr{IP: prefixToMask(prefix).As16()},
unix.RTAX_GATEWAY: rm.linkAddr, unix.RTAX_GATEWAY: gateway,
} }
} }
@@ -465,7 +495,6 @@ func (rm *tun) delRoute(prefix netip.Prefix) error {
if err != nil { if err != nil {
return fmt.Errorf("failed to create route.RouteMessage: %w", err) return fmt.Errorf("failed to create route.RouteMessage: %w", err)
} }
_, err = unix.Write(sock, data[:]) _, err = unix.Write(sock, data[:])
if err != nil { if err != nil {
return fmt.Errorf("failed to write route.RouteMessage to socket: %w", err) return fmt.Errorf("failed to write route.RouteMessage to socket: %w", err)
@@ -474,34 +503,52 @@ func (rm *tun) delRoute(prefix netip.Prefix) error {
return nil return nil
} }
func ioctl(a1, a2, a3 uintptr) error { func (t *tun) Read(to []byte) (int, error) {
_, _, errno := unix.Syscall(unix.SYS_IOCTL, a1, a2, a3) buf := make([]byte, len(to)+4)
if errno != 0 {
return errno n, err := t.ReadWriteCloser.Read(buf)
}
return nil copy(to, buf[4:])
return n - 4, err
} }
func prefixToMask(prefix netip.Prefix) netip.Addr { // Write is only valid for single threaded use
bits := prefix.Bits() func (t *tun) Write(from []byte) (int, error) {
if prefix.Addr().Is4() { buf := t.out
// Create IPv4 netmask from prefix length if cap(buf) < len(from)+4 {
mask := ^uint32(0) << (32 - bits) buf = make([]byte, len(from)+4)
return netip.AddrFrom4([4]byte{ t.out = buf
byte(mask >> 24), }
byte(mask >> 16), buf = buf[:len(from)+4]
byte(mask >> 8),
byte(mask), if len(from) == 0 {
}) return 0, syscall.EIO
}
// Determine the IP Family for the NULL L2 Header
ipVer := from[0] >> 4
if ipVer == 4 {
buf[3] = syscall.AF_INET
} else if ipVer == 6 {
buf[3] = syscall.AF_INET6
} else { } else {
// Create IPv6 netmask from prefix length return 0, fmt.Errorf("unable to determine IP version from packet")
var mask [16]byte
for i := 0; i < bits/8; i++ {
mask[i] = 0xff
} }
if bits%8 != 0 {
mask[bits/8] = ^byte(0) << (8 - bits%8) copy(buf[4:], from)
n, err := t.ReadWriteCloser.Write(buf)
return n - 4, err
} }
return netip.AddrFrom16(mask)
func (t *tun) Networks() []netip.Prefix {
return t.vpnNetworks
} }
func (t *tun) Name() string {
return t.Device
}
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
return nil, fmt.Errorf("TODO: multiqueue not implemented for darwin")
} }

View File

@@ -1,77 +1,284 @@
//go:build freebsd && !e2e_testing //go:build !e2e_testing
// +build freebsd,!e2e_testing // +build !e2e_testing
package overlay package overlay
import ( import (
"bytes"
"errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"net/netip" "net/netip"
"os/exec" "sync/atomic"
"strconv"
"strings"
"syscall" "syscall"
"time"
"unsafe" "unsafe"
"github.com/gaissmai/bart"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/slackhq/nebula/config" "github.com/slackhq/nebula/config"
"github.com/slackhq/nebula/routing"
"github.com/slackhq/nebula/util" "github.com/slackhq/nebula/util"
netroute "golang.org/x/net/route"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
wgtun "golang.zx2c4.com/wireguard/tun"
) )
type tun struct{} const (
// FIODGNAME is defined in sys/sys/filio.h on FreeBSD
// For 32-bit systems, use FIODGNAME_32 (not defined in this file: 0x80086678)
FIODGNAME = 0x80106678
TUNSIFMODE = 0x8004745e
TUNSIFHEAD = 0x80047460
OSIOCAIFADDR_IN6 = 0x8088691b
IN6_IFF_NODAD = 0x0020
)
type fiodgnameArg struct {
length int32
pad [4]byte
buf unsafe.Pointer
}
// ifreqRename is used for renaming network interfaces on FreeBSD
type ifreqRename struct { type ifreqRename struct {
Name [unix.IFNAMSIZ]byte Name [unix.IFNAMSIZ]byte
Data uintptr Data uintptr
} }
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*wgTun, error) { type ifreqDestroy struct {
return nil, fmt.Errorf("newTunFromFd not supported on FreeBSD") Name [unix.IFNAMSIZ]byte
pad [16]byte
} }
func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*wgTun, error) { type ifReq struct {
deviceName := c.GetString("tun.dev", "tun") Name [unix.IFNAMSIZ]byte
mtu := c.GetInt("tun.mtu", DefaultMTU) Flags uint16
// Create WireGuard TUN device
tunDevice, err := wgtun.CreateTUN(deviceName, mtu)
if err != nil {
return nil, fmt.Errorf("failed to create TUN device: %w", err)
} }
// Get the actual device name type ifreqMTU struct {
actualName, err := tunDevice.Name() Name [unix.IFNAMSIZ]byte
MTU int32
}
type addrLifetime struct {
Expire uint64
Preferred uint64
Vltime uint32
Pltime uint32
}
type ifreqAlias4 struct {
Name [unix.IFNAMSIZ]byte
Addr unix.RawSockaddrInet4
DstAddr unix.RawSockaddrInet4
MaskAddr unix.RawSockaddrInet4
VHid uint32
}
type ifreqAlias6 struct {
Name [unix.IFNAMSIZ]byte
Addr unix.RawSockaddrInet6
DstAddr unix.RawSockaddrInet6
PrefixMask unix.RawSockaddrInet6
Flags uint32
Lifetime addrLifetime
VHid uint32
}
type tun struct {
Device string
vpnNetworks []netip.Prefix
MTU int
Routes atomic.Pointer[[]Route]
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
linkAddr *netroute.LinkAddr
l *logrus.Logger
devFd int
}
func (t *tun) Read(to []byte) (int, error) {
// use readv() to read from the tunnel device, to eliminate the need for copying the buffer
if t.devFd < 0 {
return -1, syscall.EINVAL
}
// first 4 bytes is protocol family, in network byte order
head := make([]byte, 4)
iovecs := []syscall.Iovec{
{&head[0], 4},
{&to[0], uint64(len(to))},
}
n, _, errno := syscall.Syscall(syscall.SYS_READV, uintptr(t.devFd), uintptr(unsafe.Pointer(&iovecs[0])), uintptr(2))
var err error
if errno != 0 {
err = syscall.Errno(errno)
} else {
err = nil
}
// fix bytes read number to exclude header
bytesRead := int(n)
if bytesRead < 0 {
return bytesRead, err
} else if bytesRead < 4 {
return 0, err
} else {
return bytesRead - 4, err
}
}
// Write is only valid for single threaded use
func (t *tun) Write(from []byte) (int, error) {
// use writev() to write to the tunnel device, to eliminate the need for copying the buffer
if t.devFd < 0 {
return -1, syscall.EINVAL
}
if len(from) <= 1 {
return 0, syscall.EIO
}
ipVer := from[0] >> 4
var head []byte
// first 4 bytes is protocol family, in network byte order
if ipVer == 4 {
head = []byte{0, 0, 0, syscall.AF_INET}
} else if ipVer == 6 {
head = []byte{0, 0, 0, syscall.AF_INET6}
} else {
return 0, fmt.Errorf("unable to determine IP version from packet")
}
iovecs := []syscall.Iovec{
{&head[0], 4},
{&from[0], uint64(len(from))},
}
n, _, errno := syscall.Syscall(syscall.SYS_WRITEV, uintptr(t.devFd), uintptr(unsafe.Pointer(&iovecs[0])), uintptr(2))
var err error
if errno != 0 {
err = syscall.Errno(errno)
} else {
err = nil
}
return int(n) - 4, err
}
func (t *tun) Close() error {
if t.devFd >= 0 {
err := syscall.Close(t.devFd)
if err != nil { if err != nil {
tunDevice.Close() t.l.WithError(err).Error("Error closing device")
return nil, fmt.Errorf("failed to get TUN device name: %w", err) }
t.devFd = -1
c := make(chan struct{})
go func() {
// destroying the interface can block if a read() is still pending. Do this asynchronously.
defer close(c)
s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)
if err == nil {
defer syscall.Close(s)
ifreq := ifreqDestroy{Name: t.deviceBytes()}
err = ioctl(uintptr(s), syscall.SIOCIFDESTROY, uintptr(unsafe.Pointer(&ifreq)))
}
if err != nil {
t.l.WithError(err).Error("Error destroying tunnel")
}
}()
// wait up to 1 second so we start blocking at the ioctl
select {
case <-c:
case <-time.After(1 * time.Second):
}
}
return nil
}
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*tun, error) {
return nil, fmt.Errorf("newTunFromFd not supported in FreeBSD")
}
func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*tun, error) {
// Try to open existing tun device
var fd int
var err error
deviceName := c.GetString("tun.dev", "")
if deviceName != "" {
fd, err = syscall.Open("/dev/"+deviceName, syscall.O_RDWR, 0)
}
if errors.Is(err, fs.ErrNotExist) || deviceName == "" {
// If the device doesn't already exist, request a new one and rename it
fd, err = syscall.Open("/dev/tun", syscall.O_RDWR, 0)
}
if err != nil {
return nil, err
}
// Read the name of the interface
var name [16]byte
arg := fiodgnameArg{length: 16, buf: unsafe.Pointer(&name)}
ctrlErr := ioctl(uintptr(fd), FIODGNAME, uintptr(unsafe.Pointer(&arg)))
if ctrlErr == nil {
// set broadcast mode and multicast
ifmode := uint32(unix.IFF_BROADCAST | unix.IFF_MULTICAST)
ctrlErr = ioctl(uintptr(fd), TUNSIFMODE, uintptr(unsafe.Pointer(&ifmode)))
}
if ctrlErr == nil {
// turn on link-layer mode, to support ipv6
ifhead := uint32(1)
ctrlErr = ioctl(uintptr(fd), TUNSIFHEAD, uintptr(unsafe.Pointer(&ifhead)))
}
if ctrlErr != nil {
return nil, err
}
ifName := string(bytes.TrimRight(name[:], "\x00"))
if deviceName == "" {
deviceName = ifName
} }
// If the name doesn't match the desired interface name, rename it now // If the name doesn't match the desired interface name, rename it now
if actualName != deviceName && deviceName != "" && deviceName != "tun" { if ifName != deviceName {
if err := renameInterface(actualName, deviceName); err != nil { s, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
tunDevice.Close() if err != nil {
return nil, fmt.Errorf("failed to rename interface from %s to %s: %w", actualName, deviceName, err) return nil, err
} }
actualName = deviceName defer syscall.Close(s)
fd := uintptr(s)
var fromName [16]byte
var toName [16]byte
copy(fromName[:], ifName)
copy(toName[:], deviceName)
ifrr := ifreqRename{
Name: fromName,
Data: uintptr(unsafe.Pointer(&toName)),
} }
t := &wgTun{ // Set the device name
tunDevice: tunDevice, ioctl(fd, syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&ifrr)))
}
t := &tun{
Device: deviceName,
vpnNetworks: vpnNetworks, vpnNetworks: vpnNetworks,
MaxMTU: mtu, MTU: c.GetInt("tun.mtu", DefaultMTU),
DefaultMTU: mtu,
l: l, l: l,
devFd: fd,
} }
// Create FreeBSD-specific route manager
t.routeManager = &tun{}
err = t.reload(c, true) err = t.reload(c, true)
if err != nil { if err != nil {
tunDevice.Close()
return nil, err return nil, err
} }
@@ -82,86 +289,180 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (
} }
}) })
l.WithField("name", actualName).Info("Created WireGuard TUN device")
return t, nil return t, nil
} }
func (rm *tun) Activate(t *wgTun) error { func (t *tun) addIp(cidr netip.Prefix) error {
name, err := t.tunDevice.Name() if cidr.Addr().Is4() {
ifr := ifreqAlias4{
Name: t.deviceBytes(),
Addr: unix.RawSockaddrInet4{
Len: unix.SizeofSockaddrInet4,
Family: unix.AF_INET,
Addr: cidr.Addr().As4(),
},
DstAddr: unix.RawSockaddrInet4{
Len: unix.SizeofSockaddrInet4,
Family: unix.AF_INET,
Addr: getBroadcast(cidr).As4(),
},
MaskAddr: unix.RawSockaddrInet4{
Len: unix.SizeofSockaddrInet4,
Family: unix.AF_INET,
Addr: prefixToMask(cidr).As4(),
},
VHid: 0,
}
s, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
if err != nil { if err != nil {
return fmt.Errorf("failed to get device name: %w", err) return err
}
defer syscall.Close(s)
// Note: unix.SIOCAIFADDR corresponds to FreeBSD's OSIOCAIFADDR
if err := ioctl(uintptr(s), unix.SIOCAIFADDR, uintptr(unsafe.Pointer(&ifr))); err != nil {
return fmt.Errorf("failed to set tun address %s: %s", cidr.Addr().String(), err)
}
return nil
} }
// Set the MTU if cidr.Addr().Is6() {
rm.SetMTU(t, t.MaxMTU) ifr := ifreqAlias6{
Name: t.deviceBytes(),
Addr: unix.RawSockaddrInet6{
Len: unix.SizeofSockaddrInet6,
Family: unix.AF_INET6,
Addr: cidr.Addr().As16(),
},
PrefixMask: unix.RawSockaddrInet6{
Len: unix.SizeofSockaddrInet6,
Family: unix.AF_INET6,
Addr: prefixToMask(cidr).As16(),
},
Lifetime: addrLifetime{
Expire: 0,
Preferred: 0,
Vltime: 0xffffffff,
Pltime: 0xffffffff,
},
Flags: IN6_IFF_NODAD,
}
s, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_DGRAM, syscall.IPPROTO_IP)
if err != nil {
return err
}
defer syscall.Close(s)
// Add IP addresses if err := ioctl(uintptr(s), OSIOCAIFADDR_IN6, uintptr(unsafe.Pointer(&ifr))); err != nil {
for _, network := range t.vpnNetworks { return fmt.Errorf("failed to set tun address %s: %s", cidr.Addr().String(), err)
if err := rm.addIP(t, name, network); err != nil { }
return nil
}
return fmt.Errorf("unknown address type %v", cidr)
}
func (t *tun) Activate() error {
// Setup our default MTU
err := t.setMTU()
if err != nil {
return err
}
linkAddr, err := getLinkAddr(t.Device)
if err != nil {
return err
}
if linkAddr == nil {
return fmt.Errorf("unable to discover link_addr for tun interface")
}
t.linkAddr = linkAddr
for i := range t.vpnNetworks {
err := t.addIp(t.vpnNetworks[i])
if err != nil {
return err return err
} }
} }
// Bring up the interface return t.addRoutes(false)
if err := runCommandBSD("ifconfig", name, "up"); err != nil {
return fmt.Errorf("failed to bring up interface: %w", err)
} }
// Set the routes func (t *tun) setMTU() error {
if err := rm.AddRoutes(t, false); err != nil { // Set the MTU on the device
s, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
if err != nil {
return err return err
} }
defer syscall.Close(s)
ifm := ifreqMTU{Name: t.deviceBytes(), MTU: int32(t.MTU)}
err = ioctl(uintptr(s), unix.SIOCSIFMTU, uintptr(unsafe.Pointer(&ifm)))
return err
}
func (t *tun) reload(c *config.C, initial bool) error {
change, routes, err := getAllRoutesFromConfig(c, t.vpnNetworks, initial)
if err != nil {
return err
}
if !initial && !change {
return nil
}
routeTree, err := makeRouteTree(t.l, routes, false)
if err != nil {
return err
}
// Teach nebula how to handle the routes before establishing them in the system table
oldRoutes := t.Routes.Swap(&routes)
t.routeTree.Store(routeTree)
if !initial {
// Remove first, if the system removes a wanted route hopefully it will be re-added next
err := t.removeRoutes(findRemovedRoutes(routes, *oldRoutes))
if err != nil {
util.LogWithContextIfNeeded("Failed to remove routes", err, t.l)
}
// Ensure any routes we actually want are installed
err = t.addRoutes(true)
if err != nil {
// Catch any stray logs
util.LogWithContextIfNeeded("Failed to add routes", err, t.l)
}
}
return nil return nil
} }
func (rm *tun) SetMTU(t *wgTun, mtu int) { func (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {
name, err := t.tunDevice.Name() r, _ := t.routeTree.Load().Lookup(ip)
if err != nil { return r
t.l.WithError(err).Error("Failed to get device name for MTU set")
return
} }
if err := runCommandBSD("ifconfig", name, "mtu", strconv.Itoa(mtu)); err != nil { func (t *tun) Networks() []netip.Prefix {
t.l.WithError(err).Error("Failed to set tun mtu") return t.vpnNetworks
}
} }
func (rm *tun) SetDefaultRoute(t *wgTun, cidr netip.Prefix) error { func (t *tun) Name() string {
// On FreeBSD, routes are set via ifconfig and route commands return t.Device
return nil
} }
func (rm *tun) AddRoutes(t *wgTun, logErrors bool) error { func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
name, err := t.tunDevice.Name() return nil, fmt.Errorf("TODO: multiqueue not implemented for freebsd")
if err != nil {
return fmt.Errorf("failed to get device name: %w", err)
} }
func (t *tun) addRoutes(logErrors bool) error {
routes := *t.Routes.Load() routes := *t.Routes.Load()
for _, r := range routes { for _, r := range routes {
if !r.Install { if len(r.Via) == 0 || !r.Install {
// We don't allow route MTUs so only install routes with a via
continue continue
} }
// Add route using route command err := addRoute(r.Cidr, t.linkAddr)
args := []string{"add"}
if r.Cidr.Addr().Is6() {
args = append(args, "-inet6")
} else {
args = append(args, "-inet")
}
args = append(args, r.Cidr.String(), "-interface", name)
if r.Metric > 0 {
// FreeBSD doesn't support route metrics directly like Linux
t.l.WithField("route", r).Warn("Route metrics are not fully supported on FreeBSD")
}
err := runCommandBSD("route", args...)
if err != nil { if err != nil {
retErr := util.NewContextualError("Failed to add route", map[string]any{"route": r}, err) retErr := util.NewContextualError("Failed to add route", map[string]any{"route": r}, err)
if logErrors { if logErrors {
@@ -177,99 +478,142 @@ func (rm *tun) AddRoutes(t *wgTun, logErrors bool) error {
return nil return nil
} }
func (rm *tun) RemoveRoutes(t *wgTun, routes []Route) { func (t *tun) removeRoutes(routes []Route) error {
name, err := t.tunDevice.Name()
if err != nil {
t.l.WithError(err).Error("Failed to get device name for route removal")
return
}
for _, r := range routes { for _, r := range routes {
if !r.Install { if !r.Install {
continue continue
} }
args := []string{"delete"} err := delRoute(r.Cidr, t.linkAddr)
if r.Cidr.Addr().Is6() {
args = append(args, "-inet6")
} else {
args = append(args, "-inet")
}
args = append(args, r.Cidr.String(), "-interface", name)
err := runCommandBSD("route", args...)
if err != nil { if err != nil {
t.l.WithError(err).WithField("route", r).Error("Failed to remove route") t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
} else { } else {
t.l.WithField("route", r).Info("Removed route") t.l.WithField("route", r).Info("Removed route")
} }
} }
return nil
} }
func (rm *tun) NewMultiQueueReader(t *wgTun) (io.ReadWriteCloser, error) { func (t *tun) deviceBytes() (o [16]byte) {
// FreeBSD doesn't support multi-queue TUN devices in the same way as Linux for i, c := range t.Device {
// Return a reader that wraps the same device o[i] = byte(c)
return &wgTunReader{ }
parent: t, return
tunDevice: t.tunDevice,
offset: 0,
l: t.l,
}, nil
} }
func (rm *tun) addIP(t *wgTun, name string, network netip.Prefix) error { func addRoute(prefix netip.Prefix, gateway netroute.Addr) error {
addr := network.Addr() sock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
if err != nil {
return fmt.Errorf("unable to create AF_ROUTE socket: %v", err)
}
defer unix.Close(sock)
if addr.Is4() { route := &netroute.RouteMessage{
// For IPv4: ifconfig tun0 10.0.0.1/24 Version: unix.RTM_VERSION,
if err := runCommandBSD("ifconfig", name, network.String()); err != nil { Type: unix.RTM_ADD,
return fmt.Errorf("failed to add IPv4 address: %w", err) Flags: unix.RTF_UP,
Seq: 1,
}
if prefix.Addr().Is4() {
route.Addrs = []netroute.Addr{
unix.RTAX_DST: &netroute.Inet4Addr{IP: prefix.Masked().Addr().As4()},
unix.RTAX_NETMASK: &netroute.Inet4Addr{IP: prefixToMask(prefix).As4()},
unix.RTAX_GATEWAY: gateway,
} }
} else { } else {
// For IPv6: ifconfig tun0 inet6 add 2001:db8::1/64 route.Addrs = []netroute.Addr{
if err := runCommandBSD("ifconfig", name, "inet6", "add", network.String()); err != nil { unix.RTAX_DST: &netroute.Inet6Addr{IP: prefix.Masked().Addr().As16()},
return fmt.Errorf("failed to add IPv6 address: %w", err) unix.RTAX_NETMASK: &netroute.Inet6Addr{IP: prefixToMask(prefix).As16()},
unix.RTAX_GATEWAY: gateway,
} }
} }
return nil data, err := route.Marshal()
}
func runCommandBSD(name string, args ...string) error {
cmd := exec.Command(name, args...)
output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
return fmt.Errorf("%s %s failed: %w\nOutput: %s", name, strings.Join(args, " "), err, string(output)) return fmt.Errorf("failed to create route.RouteMessage: %w", err)
}
return nil
} }
func renameInterface(fromName, toName string) error { _, err = unix.Write(sock, data[:])
s, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
if err != nil { if err != nil {
return fmt.Errorf("failed to create socket: %w", err) if errors.Is(err, unix.EEXIST) {
// Try to do a change
route.Type = unix.RTM_CHANGE
data, err = route.Marshal()
if err != nil {
return fmt.Errorf("failed to create route.RouteMessage for change: %w", err)
} }
defer syscall.Close(s) _, err = unix.Write(sock, data[:])
fmt.Println("DOING CHANGE")
fd := uintptr(s) return err
var fromNameBytes [unix.IFNAMSIZ]byte
var toNameBytes [unix.IFNAMSIZ]byte
copy(fromNameBytes[:], fromName)
copy(toNameBytes[:], toName)
ifrr := ifreqRename{
Name: fromNameBytes,
Data: uintptr(unsafe.Pointer(&toNameBytes)),
} }
return fmt.Errorf("failed to write route.RouteMessage to socket: %w", err)
// Set the device name using SIOCSIFNAME ioctl
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&ifrr)))
if errno != 0 {
return fmt.Errorf("SIOCSIFNAME ioctl failed: %w", errno)
} }
return nil return nil
} }
func delRoute(prefix netip.Prefix, gateway netroute.Addr) error {
sock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
if err != nil {
return fmt.Errorf("unable to create AF_ROUTE socket: %v", err)
}
defer unix.Close(sock)
route := netroute.RouteMessage{
Version: unix.RTM_VERSION,
Type: unix.RTM_DELETE,
Seq: 1,
}
if prefix.Addr().Is4() {
route.Addrs = []netroute.Addr{
unix.RTAX_DST: &netroute.Inet4Addr{IP: prefix.Masked().Addr().As4()},
unix.RTAX_NETMASK: &netroute.Inet4Addr{IP: prefixToMask(prefix).As4()},
unix.RTAX_GATEWAY: gateway,
}
} else {
route.Addrs = []netroute.Addr{
unix.RTAX_DST: &netroute.Inet6Addr{IP: prefix.Masked().Addr().As16()},
unix.RTAX_NETMASK: &netroute.Inet6Addr{IP: prefixToMask(prefix).As16()},
unix.RTAX_GATEWAY: gateway,
}
}
data, err := route.Marshal()
if err != nil {
return fmt.Errorf("failed to create route.RouteMessage: %w", err)
}
_, err = unix.Write(sock, data[:])
if err != nil {
return fmt.Errorf("failed to write route.RouteMessage to socket: %w", err)
}
return nil
}
// getLinkAddr Gets the link address for the interface of the given name
func getLinkAddr(name string) (*netroute.LinkAddr, error) {
rib, err := netroute.FetchRIB(unix.AF_UNSPEC, unix.NET_RT_IFLIST, 0)
if err != nil {
return nil, err
}
msgs, err := netroute.ParseRIB(unix.NET_RT_IFLIST, rib)
if err != nil {
return nil, err
}
for _, m := range msgs {
switch m := m.(type) {
case *netroute.InterfaceMessage:
if m.Name == name {
sa, ok := m.Addrs[unix.RTAX_IFP].(*netroute.LinkAddr)
if ok {
return sa, nil
}
}
}
}
return nil, nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -7,26 +7,25 @@ import "testing"
var runAdvMSSTests = []struct { var runAdvMSSTests = []struct {
name string name string
defaultMTU int tun *tun
maxMTU int
r Route r Route
expected int expected int
}{ }{
// Standard case, default MTU is the device max MTU // Standard case, default MTU is the device max MTU
{"default", 1440, 1440, Route{}, 0}, {"default", &tun{DefaultMTU: 1440, MaxMTU: 1440}, Route{}, 0},
{"default-min", 1440, 1440, Route{MTU: 1440}, 0}, {"default-min", &tun{DefaultMTU: 1440, MaxMTU: 1440}, Route{MTU: 1440}, 0},
{"default-low", 1440, 1440, Route{MTU: 1200}, 1160}, {"default-low", &tun{DefaultMTU: 1440, MaxMTU: 1440}, Route{MTU: 1200}, 1160},
// Case where we have a route MTU set higher than the default // Case where we have a route MTU set higher than the default
{"route", 1440, 8941, Route{}, 1400}, {"route", &tun{DefaultMTU: 1440, MaxMTU: 8941}, Route{}, 1400},
{"route-min", 1440, 8941, Route{MTU: 1440}, 1400}, {"route-min", &tun{DefaultMTU: 1440, MaxMTU: 8941}, Route{MTU: 1440}, 1400},
{"route-high", 1440, 8941, Route{MTU: 8941}, 0}, {"route-high", &tun{DefaultMTU: 1440, MaxMTU: 8941}, Route{MTU: 8941}, 0},
} }
func TestTunAdvMSS(t *testing.T) { func TestTunAdvMSS(t *testing.T) {
for _, tt := range runAdvMSSTests { for _, tt := range runAdvMSSTests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
o := advMSS(tt.r, tt.defaultMTU, tt.maxMTU) o := tt.tun.advMSS(tt.r)
if o != tt.expected { if o != tt.expected {
t.Errorf("got %d, want %d", o, tt.expected) t.Errorf("got %d, want %d", o, tt.expected)
} }

View File

@@ -547,41 +547,3 @@ func delRoute(prefix netip.Prefix, gateways []netip.Prefix) error {
return nil return nil
} }
func ioctl(a1, a2, a3 uintptr) error {
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, a1, a2, a3)
if errno != 0 {
return errno
}
return nil
}
func prefixToMask(prefix netip.Prefix) netip.Addr {
bits := prefix.Bits()
if prefix.Addr().Is4() {
mask := ^uint32(0) << (32 - bits)
return netip.AddrFrom4([4]byte{
byte(mask >> 24),
byte(mask >> 16),
byte(mask >> 8),
byte(mask),
})
}
var mask [16]byte
for i := 0; i < bits/8; i++ {
mask[i] = 0xff
}
if bits%8 != 0 {
mask[bits/8] = ^byte(0) << (8 - bits%8)
}
return netip.AddrFrom16(mask)
}
func selectGateway(prefix netip.Prefix, gateways []netip.Prefix) (netip.Prefix, error) {
for _, gw := range gateways {
if prefix.Addr().Is4() == gw.Addr().Is4() {
return gw, nil
}
}
return netip.Prefix{}, fmt.Errorf("no suitable gateway found for prefix %v", prefix)
}

14
overlay/tun_notwin.go Normal file
View File

@@ -0,0 +1,14 @@
//go:build !windows
// +build !windows
package overlay
import "syscall"
func ioctl(a1, a2, a3 uintptr) error {
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, a1, a2, a3)
if errno != 0 {
return errno
}
return nil
}

View File

@@ -1,59 +1,104 @@
//go:build openbsd && !e2e_testing //go:build !e2e_testing
// +build openbsd,!e2e_testing // +build !e2e_testing
package overlay package overlay
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"net/netip" "net/netip"
"os/exec" "os"
"strconv" "regexp"
"strings" "sync/atomic"
"syscall"
"unsafe"
"github.com/gaissmai/bart"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/slackhq/nebula/config" "github.com/slackhq/nebula/config"
"github.com/slackhq/nebula/routing"
"github.com/slackhq/nebula/util" "github.com/slackhq/nebula/util"
wgtun "golang.zx2c4.com/wireguard/tun" netroute "golang.org/x/net/route"
"golang.org/x/sys/unix"
) )
type tun struct{} const (
SIOCAIFADDR_IN6 = 0x8080691a
)
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*wgTun, error) { type ifreqAlias4 struct {
return nil, fmt.Errorf("newTunFromFd not supported on OpenBSD") Name [unix.IFNAMSIZ]byte
Addr unix.RawSockaddrInet4
DstAddr unix.RawSockaddrInet4
MaskAddr unix.RawSockaddrInet4
} }
func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*wgTun, error) { type ifreqAlias6 struct {
deviceName := c.GetString("tun.dev", "tun") Name [unix.IFNAMSIZ]byte
mtu := c.GetInt("tun.mtu", DefaultMTU) Addr unix.RawSockaddrInet6
DstAddr unix.RawSockaddrInet6
PrefixMask unix.RawSockaddrInet6
Flags uint32
Lifetime [2]uint32
}
// Create WireGuard TUN device type ifreq struct {
tunDevice, err := wgtun.CreateTUN(deviceName, mtu) Name [unix.IFNAMSIZ]byte
data int
}
type tun struct {
Device string
vpnNetworks []netip.Prefix
MTU int
Routes atomic.Pointer[[]Route]
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
l *logrus.Logger
f *os.File
fd int
// cache out buffer since we need to prepend 4 bytes for tun metadata
out []byte
}
var deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`)
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*tun, error) {
return nil, fmt.Errorf("newTunFromFd not supported in openbsd")
}
func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*tun, error) {
// Try to open tun device
var err error
deviceName := c.GetString("tun.dev", "")
if deviceName == "" {
return nil, fmt.Errorf("a device name in the format of /dev/tunN must be specified")
}
if !deviceNameRE.MatchString(deviceName) {
return nil, fmt.Errorf("a device name in the format of /dev/tunN must be specified")
}
fd, err := unix.Open("/dev/"+deviceName, os.O_RDWR, 0)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create TUN device: %w", err) return nil, err
} }
// Get the actual device name err = unix.SetNonblock(fd, true)
actualName, err := tunDevice.Name()
if err != nil { if err != nil {
tunDevice.Close() l.WithError(err).Warn("Failed to set the tun device as nonblocking")
return nil, fmt.Errorf("failed to get TUN device name: %w", err)
} }
t := &wgTun{ t := &tun{
tunDevice: tunDevice, f: os.NewFile(uintptr(fd), ""),
fd: fd,
Device: deviceName,
vpnNetworks: vpnNetworks, vpnNetworks: vpnNetworks,
MaxMTU: mtu, MTU: c.GetInt("tun.mtu", DefaultMTU),
DefaultMTU: mtu,
l: l, l: l,
} }
// Create OpenBSD-specific route manager
t.routeManager = &tun{}
err = t.reload(c, true) err = t.reload(c, true)
if err != nil { if err != nil {
tunDevice.Close()
return nil, err return nil, err
} }
@@ -64,86 +109,221 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (
} }
}) })
l.WithField("name", actualName).Info("Created WireGuard TUN device")
return t, nil return t, nil
} }
func (rm *tun) Activate(t *wgTun) error { func (t *tun) Close() error {
name, err := t.tunDevice.Name() if t.f != nil {
if err := t.f.Close(); err != nil {
return fmt.Errorf("error closing tun file: %w", err)
}
// t.f.Close should have handled it for us but let's be extra sure
_ = unix.Close(t.fd)
}
return nil
}
func (t *tun) Read(to []byte) (int, error) {
buf := make([]byte, len(to)+4)
n, err := t.f.Read(buf)
copy(to, buf[4:])
return n - 4, err
}
// Write is only valid for single threaded use
func (t *tun) Write(from []byte) (int, error) {
buf := t.out
if cap(buf) < len(from)+4 {
buf = make([]byte, len(from)+4)
t.out = buf
}
buf = buf[:len(from)+4]
if len(from) == 0 {
return 0, syscall.EIO
}
// Determine the IP Family for the NULL L2 Header
ipVer := from[0] >> 4
if ipVer == 4 {
buf[3] = syscall.AF_INET
} else if ipVer == 6 {
buf[3] = syscall.AF_INET6
} else {
return 0, fmt.Errorf("unable to determine IP version from packet")
}
copy(buf[4:], from)
n, err := t.f.Write(buf)
return n - 4, err
}
func (t *tun) addIp(cidr netip.Prefix) error {
if cidr.Addr().Is4() {
var req ifreqAlias4
req.Name = t.deviceBytes()
req.Addr = unix.RawSockaddrInet4{
Len: unix.SizeofSockaddrInet4,
Family: unix.AF_INET,
Addr: cidr.Addr().As4(),
}
req.DstAddr = unix.RawSockaddrInet4{
Len: unix.SizeofSockaddrInet4,
Family: unix.AF_INET,
Addr: cidr.Addr().As4(),
}
req.MaskAddr = unix.RawSockaddrInet4{
Len: unix.SizeofSockaddrInet4,
Family: unix.AF_INET,
Addr: prefixToMask(cidr).As4(),
}
s, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
if err != nil { if err != nil {
return fmt.Errorf("failed to get device name: %w", err)
}
// Set the MTU
rm.SetMTU(t, t.MaxMTU)
// Add IP addresses
for _, network := range t.vpnNetworks {
if err := rm.addIP(t, name, network); err != nil {
return err return err
} }
defer syscall.Close(s)
if err := ioctl(uintptr(s), unix.SIOCAIFADDR, uintptr(unsafe.Pointer(&req))); err != nil {
return fmt.Errorf("failed to set tun address %s: %s", cidr.Addr(), err)
} }
// Bring up the interface err = addRoute(cidr, t.vpnNetworks)
if err := runCommandBSD("ifconfig", name, "up"); err != nil { if err != nil {
return fmt.Errorf("failed to bring up interface: %w", err) return fmt.Errorf("failed to set route for vpn network %v: %w", cidr, err)
}
// Set the routes
if err := rm.AddRoutes(t, false); err != nil {
return err
} }
return nil return nil
} }
func (rm *tun) SetMTU(t *wgTun, mtu int) { if cidr.Addr().Is6() {
name, err := t.tunDevice.Name() var req ifreqAlias6
req.Name = t.deviceBytes()
req.Addr = unix.RawSockaddrInet6{
Len: unix.SizeofSockaddrInet6,
Family: unix.AF_INET6,
Addr: cidr.Addr().As16(),
}
req.PrefixMask = unix.RawSockaddrInet6{
Len: unix.SizeofSockaddrInet6,
Family: unix.AF_INET6,
Addr: prefixToMask(cidr).As16(),
}
req.Lifetime[0] = 0xffffffff
req.Lifetime[1] = 0xffffffff
s, err := unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_IP)
if err != nil { if err != nil {
t.l.WithError(err).Error("Failed to get device name for MTU set") return err
return }
defer syscall.Close(s)
if err := ioctl(uintptr(s), SIOCAIFADDR_IN6, uintptr(unsafe.Pointer(&req))); err != nil {
return fmt.Errorf("failed to set tun address %s: %s", cidr.Addr().String(), err)
} }
if err := runCommandBSD("ifconfig", name, "mtu", strconv.Itoa(mtu)); err != nil {
t.l.WithError(err).Error("Failed to set tun mtu")
}
}
func (rm *tun) SetDefaultRoute(t *wgTun, cidr netip.Prefix) error {
// On OpenBSD, routes are set via ifconfig and route commands
return nil return nil
} }
func (rm *tun) AddRoutes(t *wgTun, logErrors bool) error { return fmt.Errorf("unknown address type %v", cidr)
name, err := t.tunDevice.Name()
if err != nil {
return fmt.Errorf("failed to get device name: %w", err)
} }
func (t *tun) Activate() error {
err := t.doIoctlByName(unix.SIOCSIFMTU, uint32(t.MTU))
if err != nil {
return fmt.Errorf("failed to set tun mtu: %w", err)
}
for i := range t.vpnNetworks {
err = t.addIp(t.vpnNetworks[i])
if err != nil {
return err
}
}
return t.addRoutes(false)
}
func (t *tun) doIoctlByName(ctl uintptr, value uint32) error {
s, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
if err != nil {
return err
}
defer syscall.Close(s)
ir := ifreq{Name: t.deviceBytes(), data: int(value)}
err = ioctl(uintptr(s), ctl, uintptr(unsafe.Pointer(&ir)))
return err
}
func (t *tun) reload(c *config.C, initial bool) error {
change, routes, err := getAllRoutesFromConfig(c, t.vpnNetworks, initial)
if err != nil {
return err
}
if !initial && !change {
return nil
}
routeTree, err := makeRouteTree(t.l, routes, false)
if err != nil {
return err
}
// Teach nebula how to handle the routes before establishing them in the system table
oldRoutes := t.Routes.Swap(&routes)
t.routeTree.Store(routeTree)
if !initial {
// Remove first, if the system removes a wanted route hopefully it will be re-added next
err := t.removeRoutes(findRemovedRoutes(routes, *oldRoutes))
if err != nil {
util.LogWithContextIfNeeded("Failed to remove routes", err, t.l)
}
// Ensure any routes we actually want are installed
err = t.addRoutes(true)
if err != nil {
// Catch any stray logs
util.LogWithContextIfNeeded("Failed to add routes", err, t.l)
}
}
return nil
}
func (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {
r, _ := t.routeTree.Load().Lookup(ip)
return r
}
func (t *tun) Networks() []netip.Prefix {
return t.vpnNetworks
}
func (t *tun) Name() string {
return t.Device
}
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
return nil, fmt.Errorf("TODO: multiqueue not implemented for openbsd")
}
func (t *tun) addRoutes(logErrors bool) error {
routes := *t.Routes.Load() routes := *t.Routes.Load()
for _, r := range routes { for _, r := range routes {
if !r.Install { if len(r.Via) == 0 || !r.Install {
// We don't allow route MTUs so only install routes with a via
continue continue
} }
// Add route using route command err := addRoute(r.Cidr, t.vpnNetworks)
args := []string{"add"}
if r.Cidr.Addr().Is6() {
args = append(args, "-inet6")
} else {
args = append(args, "-inet")
}
args = append(args, r.Cidr.String(), "-interface", name)
if r.Metric > 0 {
// OpenBSD doesn't support route metrics directly like Linux
t.l.WithField("route", r).Warn("Route metrics are not fully supported on OpenBSD")
}
err := runCommandBSD("route", args...)
if err != nil { if err != nil {
retErr := util.NewContextualError("Failed to add route", map[string]any{"route": r}, err) retErr := util.NewContextualError("Failed to add route", map[string]any{"route": r}, err)
if logErrors { if logErrors {
@@ -159,71 +339,131 @@ func (rm *tun) AddRoutes(t *wgTun, logErrors bool) error {
return nil return nil
} }
func (rm *tun) RemoveRoutes(t *wgTun, routes []Route) { func (t *tun) removeRoutes(routes []Route) error {
name, err := t.tunDevice.Name()
if err != nil {
t.l.WithError(err).Error("Failed to get device name for route removal")
return
}
for _, r := range routes { for _, r := range routes {
if !r.Install { if !r.Install {
continue continue
} }
args := []string{"delete"} err := delRoute(r.Cidr, t.vpnNetworks)
if r.Cidr.Addr().Is6() {
args = append(args, "-inet6")
} else {
args = append(args, "-inet")
}
args = append(args, r.Cidr.String(), "-interface", name)
err := runCommandBSD("route", args...)
if err != nil { if err != nil {
t.l.WithError(err).WithField("route", r).Error("Failed to remove route") t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
} else { } else {
t.l.WithField("route", r).Info("Removed route") t.l.WithField("route", r).Info("Removed route")
} }
} }
return nil
} }
func (rm *tun) NewMultiQueueReader(t *wgTun) (io.ReadWriteCloser, error) { func (t *tun) deviceBytes() (o [16]byte) {
// OpenBSD doesn't support multi-queue TUN devices in the same way as Linux for i, c := range t.Device {
// Return a reader that wraps the same device o[i] = byte(c)
return &wgTunReader{ }
parent: t, return
tunDevice: t.tunDevice,
offset: 0,
l: t.l,
}, nil
} }
func (rm *tun) addIP(t *wgTun, name string, network netip.Prefix) error { func addRoute(prefix netip.Prefix, gateways []netip.Prefix) error {
addr := network.Addr() sock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
if err != nil {
return fmt.Errorf("unable to create AF_ROUTE socket: %v", err)
}
defer unix.Close(sock)
if addr.Is4() { route := &netroute.RouteMessage{
// For IPv4: ifconfig tun0 10.0.0.1/24 Version: unix.RTM_VERSION,
if err := runCommandBSD("ifconfig", name, network.String()); err != nil { Type: unix.RTM_ADD,
return fmt.Errorf("failed to add IPv4 address: %w", err) Flags: unix.RTF_UP | unix.RTF_GATEWAY,
Seq: 1,
}
if prefix.Addr().Is4() {
gw, err := selectGateway(prefix, gateways)
if err != nil {
return err
}
route.Addrs = []netroute.Addr{
unix.RTAX_DST: &netroute.Inet4Addr{IP: prefix.Masked().Addr().As4()},
unix.RTAX_NETMASK: &netroute.Inet4Addr{IP: prefixToMask(prefix).As4()},
unix.RTAX_GATEWAY: &netroute.Inet4Addr{IP: gw.Addr().As4()},
} }
} else { } else {
// For IPv6: ifconfig tun0 inet6 add 2001:db8::1/64 gw, err := selectGateway(prefix, gateways)
if err := runCommandBSD("ifconfig", name, "inet6", "add", network.String()); err != nil {
return fmt.Errorf("failed to add IPv6 address: %w", err)
}
}
return nil
}
func runCommandBSD(name string, args ...string) error {
cmd := exec.Command(name, args...)
output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
return fmt.Errorf("%s %s failed: %w\nOutput: %s", name, strings.Join(args, " "), err, string(output)) return err
} }
route.Addrs = []netroute.Addr{
unix.RTAX_DST: &netroute.Inet6Addr{IP: prefix.Masked().Addr().As16()},
unix.RTAX_NETMASK: &netroute.Inet6Addr{IP: prefixToMask(prefix).As16()},
unix.RTAX_GATEWAY: &netroute.Inet6Addr{IP: gw.Addr().As16()},
}
}
data, err := route.Marshal()
if err != nil {
return fmt.Errorf("failed to create route.RouteMessage: %w", err)
}
_, err = unix.Write(sock, data[:])
if err != nil {
if errors.Is(err, unix.EEXIST) {
// Try to do a change
route.Type = unix.RTM_CHANGE
data, err = route.Marshal()
if err != nil {
return fmt.Errorf("failed to create route.RouteMessage for change: %w", err)
}
_, err = unix.Write(sock, data[:])
return err
}
return fmt.Errorf("failed to write route.RouteMessage to socket: %w", err)
}
return nil
}
func delRoute(prefix netip.Prefix, gateways []netip.Prefix) error {
sock, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
if err != nil {
return fmt.Errorf("unable to create AF_ROUTE socket: %v", err)
}
defer unix.Close(sock)
route := netroute.RouteMessage{
Version: unix.RTM_VERSION,
Type: unix.RTM_DELETE,
Seq: 1,
}
if prefix.Addr().Is4() {
gw, err := selectGateway(prefix, gateways)
if err != nil {
return err
}
route.Addrs = []netroute.Addr{
unix.RTAX_DST: &netroute.Inet4Addr{IP: prefix.Masked().Addr().As4()},
unix.RTAX_NETMASK: &netroute.Inet4Addr{IP: prefixToMask(prefix).As4()},
unix.RTAX_GATEWAY: &netroute.Inet4Addr{IP: gw.Addr().As4()},
}
} else {
gw, err := selectGateway(prefix, gateways)
if err != nil {
return err
}
route.Addrs = []netroute.Addr{
unix.RTAX_DST: &netroute.Inet6Addr{IP: prefix.Masked().Addr().As16()},
unix.RTAX_NETMASK: &netroute.Inet6Addr{IP: prefixToMask(prefix).As16()},
unix.RTAX_GATEWAY: &netroute.Inet6Addr{IP: gw.Addr().As16()},
}
}
data, err := route.Marshal()
if err != nil {
return fmt.Errorf("failed to create route.RouteMessage: %w", err)
}
_, err = unix.Write(sock, data[:])
if err != nil {
return fmt.Errorf("failed to write route.RouteMessage to socket: %w", err)
}
return nil return nil
} }

View File

@@ -1,242 +0,0 @@
//go:build !android && !netbsd && !e2e_testing
// +build !android,!netbsd,!e2e_testing
package overlay
import (
"fmt"
"io"
"net/netip"
"sync/atomic"
"github.com/gaissmai/bart"
"github.com/sirupsen/logrus"
"github.com/slackhq/nebula/config"
"github.com/slackhq/nebula/routing"
"github.com/slackhq/nebula/util"
wgtun "golang.zx2c4.com/wireguard/tun"
)
// wgTun wraps a WireGuard TUN device and implements the overlay.Device interface
type wgTun struct {
tunDevice wgtun.Device
vpnNetworks []netip.Prefix
MaxMTU int
DefaultMTU int
Routes atomic.Pointer[[]Route]
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
routeChan chan struct{}
// Platform-specific route management
routeManager *tun
l *logrus.Logger
}
// BatchReader interface for readers that support vectorized I/O
type BatchReader interface {
BatchRead(buffers [][]byte, sizes []int) (int, error)
}
// BatchWriter interface for writers that support vectorized I/O
type BatchWriter interface {
BatchWrite(packets [][]byte) (int, error)
}
// wgTunReader wraps a single TUN queue for multi-queue support
type wgTunReader struct {
parent *wgTun
tunDevice wgtun.Device
offset int
l *logrus.Logger
}
func (t *wgTun) Networks() []netip.Prefix {
return t.vpnNetworks
}
func (t *wgTun) Name() string {
name, err := t.tunDevice.Name()
if err != nil {
t.l.WithError(err).Error("Failed to get TUN device name")
return "unknown"
}
return name
}
func (t *wgTun) RoutesFor(ip netip.Addr) routing.Gateways {
r, _ := t.routeTree.Load().Lookup(ip)
return r
}
func (t *wgTun) Activate() error {
if t.routeManager == nil {
return fmt.Errorf("route manager not initialized")
}
return t.routeManager.Activate(t)
}
// Read implements single-packet read for backward compatibility
func (t *wgTun) Read(b []byte) (int, error) {
bufs := [][]byte{b}
sizes := []int{0}
n, err := t.tunDevice.Read(bufs, sizes, 0)
if err != nil {
return 0, err
}
if n == 0 {
return 0, io.ErrNoProgress
}
return sizes[0], nil
}
// Write implements single-packet write for backward compatibility
func (t *wgTun) Write(b []byte) (int, error) {
bufs := [][]byte{b}
offset := 0
// WireGuard TUN expects the packet data to start at offset 0
n, err := t.tunDevice.Write(bufs, offset)
if err != nil {
return 0, err
}
if n == 0 {
return 0, io.ErrShortWrite
}
return len(b), nil
}
func (t *wgTun) Close() error {
if t.routeChan != nil {
close(t.routeChan)
}
if t.tunDevice != nil {
return t.tunDevice.Close()
}
return nil
}
func (t *wgTun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
// For WireGuard TUN, we need to create separate TUN device instances for multi-queue
// The platform-specific implementation will handle this
if t.routeManager == nil {
return nil, fmt.Errorf("route manager not initialized for multi-queue reader")
}
return t.routeManager.NewMultiQueueReader(t)
}
func (t *wgTun) reload(c *config.C, initial bool) error {
routeChange, routes, err := getAllRoutesFromConfig(c, t.vpnNetworks, initial)
if err != nil {
return err
}
if !initial && !routeChange && !c.HasChanged("tun.mtu") {
return nil
}
routeTree, err := makeRouteTree(t.l, routes, true)
if err != nil {
return err
}
oldDefaultMTU := t.DefaultMTU
oldMaxMTU := t.MaxMTU
newDefaultMTU := c.GetInt("tun.mtu", DefaultMTU)
newMaxMTU := newDefaultMTU
for i, r := range routes {
if r.MTU == 0 {
routes[i].MTU = newDefaultMTU
}
if r.MTU > t.MaxMTU {
newMaxMTU = r.MTU
}
}
t.MaxMTU = newMaxMTU
t.DefaultMTU = newDefaultMTU
// Teach nebula how to handle the routes before establishing them in the system table
oldRoutes := t.Routes.Swap(&routes)
t.routeTree.Store(routeTree)
if !initial && t.routeManager != nil {
if oldMaxMTU != newMaxMTU {
t.routeManager.SetMTU(t, t.MaxMTU)
t.l.Infof("Set max MTU to %v was %v", t.MaxMTU, oldMaxMTU)
}
if oldDefaultMTU != newDefaultMTU {
for i := range t.vpnNetworks {
err := t.routeManager.SetDefaultRoute(t, t.vpnNetworks[i])
if err != nil {
t.l.Warn(err)
} else {
t.l.Infof("Set default MTU to %v was %v", t.DefaultMTU, oldDefaultMTU)
}
}
}
// Remove first, if the system removes a wanted route hopefully it will be re-added next
t.routeManager.RemoveRoutes(t, findRemovedRoutes(routes, *oldRoutes))
// Ensure any routes we actually want are installed
err = t.routeManager.AddRoutes(t, true)
if err != nil {
// This should never be called since AddRoutes should log its own errors in a reload condition
util.LogWithContextIfNeeded("Failed to refresh routes", err, t.l)
}
}
return nil
}
// BatchRead reads multiple packets from the TUN device using vectorized I/O
// The caller provides buffers and sizes slices, and this function returns the number of packets read.
func (r *wgTunReader) BatchRead(buffers [][]byte, sizes []int) (int, error) {
return r.tunDevice.Read(buffers, sizes, r.offset)
}
// Read implements io.Reader for wgTunReader (single packet for compatibility)
func (r *wgTunReader) Read(b []byte) (int, error) {
bufs := [][]byte{b}
sizes := []int{0}
n, err := r.tunDevice.Read(bufs, sizes, r.offset)
if err != nil {
return 0, err
}
if n == 0 {
return 0, io.ErrNoProgress
}
return sizes[0], nil
}
// Write implements io.Writer for wgTunReader
func (r *wgTunReader) Write(b []byte) (int, error) {
bufs := [][]byte{b}
n, err := r.tunDevice.Write(bufs, r.offset)
if err != nil {
return 0, err
}
if n == 0 {
return 0, io.ErrShortWrite
}
return len(b), nil
}
// BatchWrite writes multiple packets to the TUN device using vectorized I/O
func (r *wgTunReader) BatchWrite(packets [][]byte) (int, error) {
return r.tunDevice.Write(packets, r.offset)
}
func (r *wgTunReader) Close() error {
if r.tunDevice != nil {
return r.tunDevice.Close()
}
return nil
}

View File

@@ -1,77 +1,84 @@
//go:build windows && !e2e_testing //go:build !e2e_testing
// +build windows,!e2e_testing // +build !e2e_testing
package overlay package overlay
import ( import (
"crypto" "crypto"
"encoding/binary"
"fmt" "fmt"
"io" "io"
"net/netip" "net/netip"
"os"
"path/filepath"
"runtime"
"sync/atomic"
"syscall"
"unsafe"
"github.com/gaissmai/bart"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/slackhq/nebula/config" "github.com/slackhq/nebula/config"
"github.com/slackhq/nebula/routing"
"github.com/slackhq/nebula/util" "github.com/slackhq/nebula/util"
"github.com/slackhq/nebula/wintun"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
wgtun "golang.zx2c4.com/wireguard/tun"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
) )
const tunGUIDLabel = "Fixed Nebula Windows GUID v1" const tunGUIDLabel = "Fixed Nebula Windows GUID v1"
type tun struct { type winTun struct {
luid winipcfg.LUID Device string
vpnNetworks []netip.Prefix
MTU int
Routes atomic.Pointer[[]Route]
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
l *logrus.Logger
tun *wintun.NativeTun
} }
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*wgTun, error) { func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (Device, error) {
return nil, fmt.Errorf("newTunFromFd not supported in Windows") return nil, fmt.Errorf("newTunFromFd not supported in Windows")
} }
func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*wgTun, error) { func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*winTun, error) {
deviceName := c.GetString("tun.dev", "Nebula") err := checkWinTunExists()
mtu := c.GetInt("tun.mtu", DefaultMTU)
// Create WireGuard TUN device
tunDevice, err := wgtun.CreateTUN(deviceName, mtu)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create TUN device: %w", err) return nil, fmt.Errorf("can not load the wintun driver: %w", err)
} }
// Get the actual device name deviceName := c.GetString("tun.dev", "")
actualName, err := tunDevice.Name() guid, err := generateGUIDByDeviceName(deviceName)
if err != nil { if err != nil {
tunDevice.Close() return nil, fmt.Errorf("generate GUID failed: %w", err)
return nil, fmt.Errorf("failed to get TUN device name: %w", err)
} }
t := &wgTun{ t := &winTun{
tunDevice: tunDevice, Device: deviceName,
vpnNetworks: vpnNetworks, vpnNetworks: vpnNetworks,
MaxMTU: mtu, MTU: c.GetInt("tun.mtu", DefaultMTU),
DefaultMTU: mtu,
l: l, l: l,
} }
// Create Windows-specific route manager
rm := &tun{}
// Get LUID from the TUN device
// The WireGuard TUN device on Windows should provide a LUID() method
if nativeTun, ok := tunDevice.(interface{ LUID() uint64 }); ok {
rm.luid = winipcfg.LUID(nativeTun.LUID())
} else {
tunDevice.Close()
return nil, fmt.Errorf("failed to get LUID from TUN device")
}
t.routeManager = rm
err = t.reload(c, true) err = t.reload(c, true)
if err != nil { if err != nil {
tunDevice.Close()
return nil, err return nil, err
} }
var tunDevice wintun.Device
tunDevice, err = wintun.CreateTUNWithRequestedGUID(deviceName, guid, t.MTU)
if err != nil {
// Windows 10 has an issue with unclean shutdowns not fully cleaning up the wintun device.
// Trying a second time resolves the issue.
l.WithError(err).Debug("Failed to create wintun device, retrying")
tunDevice, err = wintun.CreateTUNWithRequestedGUID(deviceName, guid, t.MTU)
if err != nil {
return nil, fmt.Errorf("create TUN device failed: %w", err)
}
}
t.tun = tunDevice.(*wintun.NativeTun)
c.RegisterReloadCallback(func(c *config.C) { c.RegisterReloadCallback(func(c *config.C) {
err := t.reload(c, false) err := t.reload(c, false)
if err != nil { if err != nil {
@@ -79,140 +86,206 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (
} }
}) })
l.WithField("name", actualName).Info("Created WireGuard TUN device")
return t, nil return t, nil
} }
func (rm *tun) Activate(t *wgTun) error { func (t *winTun) reload(c *config.C, initial bool) error {
// Set MTU change, routes, err := getAllRoutesFromConfig(c, t.vpnNetworks, initial)
err := rm.setMTU(t, t.MaxMTU)
if err != nil { if err != nil {
return fmt.Errorf("failed to set MTU: %w", err)
}
// Add IP addresses
for _, network := range t.vpnNetworks {
if err := rm.addIP(t, network); err != nil {
return err return err
} }
if !initial && !change {
return nil
} }
// Add routes routeTree, err := makeRouteTree(t.l, routes, false)
if err := rm.AddRoutes(t, false); err != nil { if err != nil {
return err
}
// Teach nebula how to handle the routes before establishing them in the system table
oldRoutes := t.Routes.Swap(&routes)
t.routeTree.Store(routeTree)
if !initial {
// Remove first, if the system removes a wanted route hopefully it will be re-added next
err := t.removeRoutes(findRemovedRoutes(routes, *oldRoutes))
if err != nil {
util.LogWithContextIfNeeded("Failed to remove routes", err, t.l)
}
// Ensure any routes we actually want are installed
err = t.addRoutes(true)
if err != nil {
// Catch any stray logs
util.LogWithContextIfNeeded("Failed to add routes", err, t.l)
}
}
return nil
}
func (t *winTun) Activate() error {
luid := winipcfg.LUID(t.tun.LUID())
err := luid.SetIPAddresses(t.vpnNetworks)
if err != nil {
return fmt.Errorf("failed to set address: %w", err)
}
err = t.addRoutes(false)
if err != nil {
return err return err
} }
return nil return nil
} }
func (rm *tun) SetMTU(t *wgTun, mtu int) { func (t *winTun) addRoutes(logErrors bool) error {
if err := rm.setMTU(t, mtu); err != nil { luid := winipcfg.LUID(t.tun.LUID())
t.l.WithError(err).Error("Failed to set MTU")
}
}
func (rm *tun) setMTU(t *wgTun, mtu int) error {
// Set MTU using winipcfg
// Note: MTU setting on Windows TUN devices may be handled by the driver
// For now, we'll skip explicit MTU setting as the WireGuard TUN handles it
return nil
}
func (rm *tun) SetDefaultRoute(t *wgTun, cidr netip.Prefix) error {
// On Windows, routes are managed differently
return nil
}
func (rm *tun) AddRoutes(t *wgTun, logErrors bool) error {
routes := *t.Routes.Load() routes := *t.Routes.Load()
foundDefault4 := false
for _, r := range routes { for _, r := range routes {
if !r.Install { if len(r.Via) == 0 || !r.Install {
// We don't allow route MTUs so only install routes with a via
continue continue
} }
if r.MTU > 0 { // Add our unsafe route
// Windows route MTU is not directly supported // Windows does not support multipath routes natively, so we install only a single route.
t.l.WithField("route", r).Debug("Route MTU is not supported on Windows") // This is not a problem as traffic will always be sent to Nebula which handles the multipath routing internally.
} // In effect this provides multipath routing support to windows supporting loadbalancing and redundancy.
err := luid.AddRoute(r.Cidr, r.Via[0].Addr(), uint32(r.Metric))
// Use winipcfg to add the route
// The rm.luid should have the AddRoute method from winipcfg
if len(r.Via) == 0 {
t.l.WithField("route", r).Warn("Route has no via address, skipping")
continue
}
err := rm.luid.AddRoute(r.Cidr, r.Via[0].Addr(), uint32(r.Metric))
if err != nil { if err != nil {
retErr := util.NewContextualError("Failed to add route", map[string]any{"route": r}, err) retErr := util.NewContextualError("Failed to add route", map[string]any{"route": r}, err)
if logErrors { if logErrors {
retErr.Log(t.l) retErr.Log(t.l)
continue
} else { } else {
return retErr return retErr
} }
} else { } else {
t.l.WithField("route", r).Info("Added route") t.l.WithField("route", r).Info("Added route")
} }
if !foundDefault4 {
if r.Cidr.Bits() == 0 && r.Cidr.Addr().BitLen() == 32 {
foundDefault4 = true
}
}
} }
ipif, err := luid.IPInterface(windows.AF_INET)
if err != nil {
return fmt.Errorf("failed to get ip interface: %w", err)
}
ipif.NLMTU = uint32(t.MTU)
if foundDefault4 {
ipif.UseAutomaticMetric = false
ipif.Metric = 0
}
if err := ipif.Set(); err != nil {
return fmt.Errorf("failed to set ip interface: %w", err)
}
return nil return nil
} }
func (rm *tun) RemoveRoutes(t *wgTun, routes []Route) { func (t *winTun) removeRoutes(routes []Route) error {
luid := winipcfg.LUID(t.tun.LUID())
for _, r := range routes { for _, r := range routes {
if !r.Install { if !r.Install {
continue continue
} }
if len(r.Via) == 0 { // See comment on luid.AddRoute
continue err := luid.DeleteRoute(r.Cidr, r.Via[0].Addr())
}
err := rm.luid.DeleteRoute(r.Cidr, r.Via[0].Addr())
if err != nil { if err != nil {
t.l.WithError(err).WithField("route", r).Error("Failed to remove route") t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
} else { } else {
t.l.WithField("route", r).Info("Removed route") t.l.WithField("route", r).Info("Removed route")
} }
} }
}
func (rm *tun) NewMultiQueueReader(t *wgTun) (io.ReadWriteCloser, error) {
// Windows doesn't support multi-queue TUN devices
// Return a reader that wraps the same device
return &wgTunReader{
parent: t,
tunDevice: t.tunDevice,
offset: 0,
l: t.l,
}, nil
}
func (rm *tun) addIP(t *wgTun, network netip.Prefix) error {
// Add IP address using winipcfg
// SetIPAddresses expects a slice of prefixes
err := rm.luid.SetIPAddresses([]netip.Prefix{network})
if err != nil {
return fmt.Errorf("failed to add IP address %s: %w", network, err)
}
return nil return nil
} }
// generateGUIDByDeviceName generates a GUID based on the device name func (t *winTun) RoutesFor(ip netip.Addr) routing.Gateways {
func generateGUIDByDeviceName(deviceName string) (*windows.GUID, error) { r, _ := t.routeTree.Load().Lookup(ip)
// Hash the device name to create a deterministic GUID return r
h := crypto.SHA256.New()
h.Write([]byte(tunGUIDLabel))
h.Write([]byte(deviceName))
sum := h.Sum(nil)
guid := &windows.GUID{
Data1: binary.LittleEndian.Uint32(sum[0:4]),
Data2: binary.LittleEndian.Uint16(sum[4:6]),
Data3: binary.LittleEndian.Uint16(sum[6:8]),
} }
copy(guid.Data4[:], sum[8:16])
return guid, nil func (t *winTun) Networks() []netip.Prefix {
return t.vpnNetworks
}
func (t *winTun) Name() string {
return t.Device
}
func (t *winTun) Read(b []byte) (int, error) {
return t.tun.Read(b, 0)
}
func (t *winTun) Write(b []byte) (int, error) {
return t.tun.Write(b, 0)
}
func (t *winTun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
return nil, fmt.Errorf("TODO: multiqueue not implemented for windows")
}
func (t *winTun) Close() error {
// It seems that the Windows networking stack doesn't like it when we destroy interfaces that have active routes,
// so to be certain, just remove everything before destroying.
luid := winipcfg.LUID(t.tun.LUID())
_ = luid.FlushRoutes(windows.AF_INET)
_ = luid.FlushIPAddresses(windows.AF_INET)
_ = luid.FlushRoutes(windows.AF_INET6)
_ = luid.FlushIPAddresses(windows.AF_INET6)
_ = luid.FlushDNS(windows.AF_INET)
_ = luid.FlushDNS(windows.AF_INET6)
return t.tun.Close()
}
func generateGUIDByDeviceName(name string) (*windows.GUID, error) {
// GUID is 128 bit
hash := crypto.MD5.New()
_, err := hash.Write([]byte(tunGUIDLabel))
if err != nil {
return nil, err
}
_, err = hash.Write([]byte(name))
if err != nil {
return nil, err
}
sum := hash.Sum(nil)
return (*windows.GUID)(unsafe.Pointer(&sum[0])), nil
}
func checkWinTunExists() error {
myPath, err := os.Executable()
if err != nil {
return err
}
arch := runtime.GOARCH
switch arch {
case "386":
//NOTE: wintun bundles 386 as x86
arch = "x86"
}
_, err = syscall.LoadDLL(filepath.Join(filepath.Dir(myPath), "dist", "windows", "wintun", "bin", arch, "wintun.dll"))
return err
} }

16
udp/config.go Normal file
View File

@@ -0,0 +1,16 @@
package udp
import "sync/atomic"
var disableUDPCsum atomic.Bool
// SetDisableUDPCsum controls whether IPv4 UDP sockets opt out of kernel
// checksum calculation via SO_NO_CHECK. Only applicable on platforms that
// support the option (Linux). IPv6 always keeps the checksum enabled.
func SetDisableUDPCsum(disable bool) {
disableUDPCsum.Store(disable)
}
func udpChecksumDisabled() bool {
return disableUDPCsum.Load()
}

View File

@@ -11,6 +11,7 @@ const MTU = 9001
type EncReader func( type EncReader func(
addr netip.AddrPort, addr netip.AddrPort,
payload []byte, payload []byte,
release func(),
) )
type Conn interface { type Conn interface {

View File

@@ -0,0 +1,25 @@
//go:build linux && (386 || amd64p32 || arm || mips || mipsle) && !android && !e2e_testing
// +build linux
// +build 386 amd64p32 arm mips mipsle
// +build !android
// +build !e2e_testing
package udp
import "golang.org/x/sys/unix"
func controllen(n int) uint32 {
return uint32(n)
}
func setCmsgLen(h *unix.Cmsghdr, n int) {
h.Len = uint32(unix.CmsgLen(n))
}
func setIovecLen(v *unix.Iovec, n int) {
v.Len = uint32(n)
}
func setMsghdrIovlen(m *unix.Msghdr, n int) {
m.Iovlen = uint32(n)
}

View File

@@ -0,0 +1,25 @@
//go:build linux && (amd64 || arm64 || ppc64 || ppc64le || mips64 || mips64le || s390x || riscv64 || loong64) && !android && !e2e_testing
// +build linux
// +build amd64 arm64 ppc64 ppc64le mips64 mips64le s390x riscv64 loong64
// +build !android
// +build !e2e_testing
package udp
import "golang.org/x/sys/unix"
func controllen(n int) uint64 {
return uint64(n)
}
func setCmsgLen(h *unix.Cmsghdr, n int) {
h.Len = uint64(unix.CmsgLen(n))
}
func setIovecLen(v *unix.Iovec, n int) {
v.Len = uint64(n)
}
func setMsghdrIovlen(m *unix.Msghdr, n int) {
m.Iovlen = uint64(n)
}

25
udp/sendmmsg_linux_32.go Normal file
View File

@@ -0,0 +1,25 @@
//go:build linux && (386 || amd64p32 || arm || mips || mipsle) && !android && !e2e_testing
package udp
import (
"unsafe"
"golang.org/x/sys/unix"
)
type linuxMmsgHdr struct {
Hdr unix.Msghdr
Len uint32
}
func sendmmsg(fd int, hdrs []linuxMmsgHdr, flags int) (int, error) {
if len(hdrs) == 0 {
return 0, nil
}
n, _, errno := unix.Syscall6(unix.SYS_SENDMMSG, uintptr(fd), uintptr(unsafe.Pointer(&hdrs[0])), uintptr(len(hdrs)), uintptr(flags), 0, 0)
if errno != 0 {
return int(n), errno
}
return int(n), nil
}

26
udp/sendmmsg_linux_64.go Normal file
View File

@@ -0,0 +1,26 @@
//go:build linux && (amd64 || arm64 || ppc64 || ppc64le || mips64 || mips64le || s390x || riscv64 || loong64) && !android && !e2e_testing
package udp
import (
"unsafe"
"golang.org/x/sys/unix"
)
type linuxMmsgHdr struct {
Hdr unix.Msghdr
Len uint32
_ uint32
}
func sendmmsg(fd int, hdrs []linuxMmsgHdr, flags int) (int, error) {
if len(hdrs) == 0 {
return 0, nil
}
n, _, errno := unix.Syscall6(unix.SYS_SENDMMSG, uintptr(fd), uintptr(unsafe.Pointer(&hdrs[0])), uintptr(len(hdrs)), uintptr(flags), 0, 0)
if errno != 0 {
return int(n), errno
}
return int(n), nil
}

View File

@@ -180,7 +180,7 @@ func (u *StdConn) ListenOut(r EncReader) {
u.l.WithError(err).Error("unexpected udp socket receive error") u.l.WithError(err).Error("unexpected udp socket receive error")
} }
r(netip.AddrPortFrom(rua.Addr().Unmap(), rua.Port()), buffer[:n]) r(netip.AddrPortFrom(rua.Addr().Unmap(), rua.Port()), buffer[:n], nil)
} }
} }

View File

@@ -82,6 +82,6 @@ func (u *GenericConn) ListenOut(r EncReader) {
return return
} }
r(netip.AddrPortFrom(rua.Addr().Unmap(), rua.Port()), buffer[:n]) r(netip.AddrPortFrom(rua.Addr().Unmap(), rua.Port()), buffer[:n], nil)
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -30,17 +30,29 @@ type rawMessage struct {
Len uint32 Len uint32
} }
func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte) { func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte, [][]byte) {
controlLen := int(u.controlLen.Load())
msgs := make([]rawMessage, n) msgs := make([]rawMessage, n)
buffers := make([][]byte, n) buffers := make([][]byte, n)
names := make([][]byte, n) names := make([][]byte, n)
var controls [][]byte
if controlLen > 0 {
controls = make([][]byte, n)
}
for i := range msgs { for i := range msgs {
buffers[i] = make([]byte, MTU) size := int(u.groBufSize.Load())
if size < MTU {
size = MTU
}
buf := u.borrowRxBuffer(size)
buffers[i] = buf
names[i] = make([]byte, unix.SizeofSockaddrInet6) names[i] = make([]byte, unix.SizeofSockaddrInet6)
vs := []iovec{ vs := []iovec{
{Base: &buffers[i][0], Len: uint32(len(buffers[i]))}, {Base: &buf[0], Len: uint32(len(buf))},
} }
msgs[i].Hdr.Iov = &vs[0] msgs[i].Hdr.Iov = &vs[0]
@@ -48,7 +60,22 @@ func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte) {
msgs[i].Hdr.Name = &names[i][0] msgs[i].Hdr.Name = &names[i][0]
msgs[i].Hdr.Namelen = uint32(len(names[i])) msgs[i].Hdr.Namelen = uint32(len(names[i]))
if controlLen > 0 {
controls[i] = make([]byte, controlLen)
msgs[i].Hdr.Control = &controls[i][0]
msgs[i].Hdr.Controllen = controllen(len(controls[i]))
} else {
msgs[i].Hdr.Control = nil
msgs[i].Hdr.Controllen = controllen(0)
}
} }
return msgs, buffers, names return msgs, buffers, names, controls
}
func setIovecBase(msg *rawMessage, buf []byte) {
iov := (*iovec)(msg.Hdr.Iov)
iov.Base = &buf[0]
iov.Len = uint32(len(buf))
} }

View File

@@ -33,25 +33,50 @@ type rawMessage struct {
Pad0 [4]byte Pad0 [4]byte
} }
func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte) { func (u *StdConn) PrepareRawMessages(n int) ([]rawMessage, [][]byte, [][]byte, [][]byte) {
controlLen := int(u.controlLen.Load())
msgs := make([]rawMessage, n) msgs := make([]rawMessage, n)
buffers := make([][]byte, n) buffers := make([][]byte, n)
names := make([][]byte, n) names := make([][]byte, n)
var controls [][]byte
if controlLen > 0 {
controls = make([][]byte, n)
}
for i := range msgs { for i := range msgs {
buffers[i] = make([]byte, MTU) size := int(u.groBufSize.Load())
if size < MTU {
size = MTU
}
buf := u.borrowRxBuffer(size)
buffers[i] = buf
names[i] = make([]byte, unix.SizeofSockaddrInet6) names[i] = make([]byte, unix.SizeofSockaddrInet6)
vs := []iovec{ vs := []iovec{{Base: &buf[0], Len: uint64(len(buf))}}
{Base: &buffers[i][0], Len: uint64(len(buffers[i]))},
}
msgs[i].Hdr.Iov = &vs[0] msgs[i].Hdr.Iov = &vs[0]
msgs[i].Hdr.Iovlen = uint64(len(vs)) msgs[i].Hdr.Iovlen = uint64(len(vs))
msgs[i].Hdr.Name = &names[i][0] msgs[i].Hdr.Name = &names[i][0]
msgs[i].Hdr.Namelen = uint32(len(names[i])) msgs[i].Hdr.Namelen = uint32(len(names[i]))
if controlLen > 0 {
controls[i] = make([]byte, controlLen)
msgs[i].Hdr.Control = &controls[i][0]
msgs[i].Hdr.Controllen = controllen(len(controls[i]))
} else {
msgs[i].Hdr.Control = nil
msgs[i].Hdr.Controllen = controllen(0)
}
} }
return msgs, buffers, names return msgs, buffers, names, controls
}
func setIovecBase(msg *rawMessage, buf []byte) {
iov := (*iovec)(msg.Hdr.Iov)
iov.Base = &buf[0]
iov.Len = uint64(len(buf))
} }

View File

@@ -149,7 +149,7 @@ func (u *RIOConn) ListenOut(r EncReader) {
continue continue
} }
r(netip.AddrPortFrom(netip.AddrFrom16(rua.Addr).Unmap(), (rua.Port>>8)|((rua.Port&0xff)<<8)), buffer[:n]) r(netip.AddrPortFrom(netip.AddrFrom16(rua.Addr).Unmap(), (rua.Port>>8)|((rua.Port&0xff)<<8)), buffer[:n], nil)
} }
} }

View File

@@ -112,7 +112,7 @@ func (u *TesterConn) ListenOut(r EncReader) {
if !ok { if !ok {
return return
} }
r(p.From, p.Data) r(p.From, p.Data, func() {})
} }
} }