Forráskód Böngészése

bolt more stuff onto tun to help auto-assign snat addresses

JackDoan 8 hónapja
szülő
commit
1cc257f997

+ 15 - 3
firewall.go

@@ -215,7 +215,6 @@ func NewFirewall(l *logrus.Logger, tcpTimeout, UDPTimeout, defaultTimeout time.D
 		routableNetworks:  routableNetworks,
 		routableNetworks:  routableNetworks,
 		assignedNetworks:  assignedNetworks,
 		assignedNetworks:  assignedNetworks,
 		hasUnsafeNetworks: hasUnsafeNetworks,
 		hasUnsafeNetworks: hasUnsafeNetworks,
-		snatAddr:          snatAddr,
 		l:                 l,
 		l:                 l,
 
 
 		incomingMetrics: firewallMetrics{
 		incomingMetrics: firewallMetrics{
@@ -231,7 +230,7 @@ func NewFirewall(l *logrus.Logger, tcpTimeout, UDPTimeout, defaultTimeout time.D
 	}
 	}
 }
 }
 
 
-func NewFirewallFromConfig(l *logrus.Logger, cs *CertState, c *config.C, snatAddr netip.Addr) (*Firewall, error) {
+func NewFirewallFromConfig(l *logrus.Logger, cs *CertState, c *config.C) (*Firewall, error) {
 	certificate := cs.getCertificate(cert.Version2)
 	certificate := cs.getCertificate(cert.Version2)
 	if certificate == nil {
 	if certificate == nil {
 		certificate = cs.getCertificate(cert.Version1)
 		certificate = cs.getCertificate(cert.Version1)
@@ -241,7 +240,14 @@ func NewFirewallFromConfig(l *logrus.Logger, cs *CertState, c *config.C, snatAdd
 		panic("No certificate available to reconfigure the firewall")
 		panic("No certificate available to reconfigure the firewall")
 	}
 	}
 
 
-	fw := NewFirewall(l, c.GetDuration("firewall.conntrack.tcp_timeout", time.Minute*12), c.GetDuration("firewall.conntrack.udp_timeout", time.Minute*3), c.GetDuration("firewall.conntrack.default_timeout", time.Minute*10), certificate, snatAddr)
+	fw := NewFirewall(
+		l,
+		c.GetDuration("firewall.conntrack.tcp_timeout", time.Minute*12),
+		c.GetDuration("firewall.conntrack.udp_timeout", time.Minute*3),
+		c.GetDuration("firewall.conntrack.default_timeout", time.Minute*10),
+		certificate,
+		netip.Addr{},
+	)
 
 
 	fw.defaultLocalCIDRAny = c.GetBool("firewall.default_local_cidr_any", false)
 	fw.defaultLocalCIDRAny = c.GetBool("firewall.default_local_cidr_any", false)
 
 
@@ -347,6 +353,12 @@ func (f *Firewall) GetRuleHashes() string {
 	return "SHA:" + f.GetRuleHash() + ",FNV:" + strconv.FormatUint(uint64(f.GetRuleHashFNV()), 10)
 	return "SHA:" + f.GetRuleHash() + ",FNV:" + strconv.FormatUint(uint64(f.GetRuleHashFNV()), 10)
 }
 }
 
 
+func (f *Firewall) SetSNATAddressFromInterface(i *Interface) {
+	//address-mutation-avoidance is done inside Interface, the firewall doesn't need to care
+	//todo should snatted conntracks get expired out? Probably not needed until if/when we allow reload
+	f.snatAddr = i.inside.SNATAddress().Addr()
+}
+
 func (f *Firewall) ShouldUnSNAT(fp *firewall.Packet) bool {
 func (f *Firewall) ShouldUnSNAT(fp *firewall.Packet) bool {
 	// f.snatAddr is only valid if we're a snat-capable router
 	// f.snatAddr is only valid if we're a snat-capable router
 	return f.snatAddr.IsValid() && fp.RemoteAddr == f.snatAddr
 	return f.snatAddr.IsValid() && fp.RemoteAddr == f.snatAddr

+ 3 - 1
interface.go

@@ -249,6 +249,7 @@ func (f *Interface) activate() {
 		f.inside.Close()
 		f.inside.Close()
 		f.l.Fatal(err)
 		f.l.Fatal(err)
 	}
 	}
+	f.firewall.SetSNATAddressFromInterface(f)
 }
 }
 
 
 func (f *Interface) run() {
 func (f *Interface) run() {
@@ -340,11 +341,12 @@ func (f *Interface) reloadFirewall(c *config.C) {
 		return
 		return
 	}
 	}
 
 
-	fw, err := NewFirewallFromConfig(f.l, f.pki.getCertState(), c, f.firewall.snatAddr)
+	fw, err := NewFirewallFromConfig(f.l, f.pki.getCertState(), c)
 	if err != nil {
 	if err != nil {
 		f.l.WithError(err).Error("Error while creating firewall during reload")
 		f.l.WithError(err).Error("Error while creating firewall during reload")
 		return
 		return
 	}
 	}
+	fw.SetSNATAddressFromInterface(f)
 
 
 	oldFw := f.firewall
 	oldFw := f.firewall
 	conntrack := oldFw.Conntrack
 	conntrack := oldFw.Conntrack

+ 1 - 2
main.go

@@ -66,8 +66,7 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
 		return nil, util.ContextualizeIfNeeded("Failed to load PKI from config", err)
 		return nil, util.ContextualizeIfNeeded("Failed to load PKI from config", err)
 	}
 	}
 
 
-	snatAddr := netip.MustParseAddr("169.254.55.96") //todo get this from tun!
-	fw, err := NewFirewallFromConfig(l, pki.getCertState(), c, snatAddr)
+	fw, err := NewFirewallFromConfig(l, pki.getCertState(), c)
 	if err != nil {
 	if err != nil {
 		return nil, util.ContextualizeIfNeeded("Error while loading firewall rules", err)
 		return nil, util.ContextualizeIfNeeded("Error while loading firewall rules", err)
 	}
 	}

+ 2 - 0
overlay/device.go

@@ -11,6 +11,8 @@ type Device interface {
 	io.ReadWriteCloser
 	io.ReadWriteCloser
 	Activate() error
 	Activate() error
 	Networks() []netip.Prefix
 	Networks() []netip.Prefix
+	UnsafeNetworks() []netip.Prefix
+	SNATAddress() netip.Prefix
 	Name() string
 	Name() string
 	RoutesFor(netip.Addr) routing.Gateways
 	RoutesFor(netip.Addr) routing.Gateways
 	SupportsMultiqueue() bool
 	SupportsMultiqueue() bool

+ 51 - 0
overlay/tun.go

@@ -1,6 +1,7 @@
 package overlay
 package overlay
 
 
 import (
 import (
+	"crypto/rand"
 	"fmt"
 	"fmt"
 	"net"
 	"net"
 	"net/netip"
 	"net/netip"
@@ -129,3 +130,53 @@ func selectGateway(dest netip.Prefix, gateways []netip.Prefix) (netip.Prefix, er
 
 
 	return netip.Prefix{}, fmt.Errorf("no gateway found for %v in the list of vpn networks", dest)
 	return netip.Prefix{}, fmt.Errorf("no gateway found for %v in the list of vpn networks", dest)
 }
 }
+
+func prepareSnatAddr(d Device, l *logrus.Logger, c *config.C, routes []Route) netip.Prefix {
+	if !d.Networks()[0].Addr().Is6() {
+		return netip.Prefix{} //if we have an IPv4 assignment within the overlay, we don't need a snat address
+	}
+
+	addSnatAddr := false
+	for _, un := range d.UnsafeNetworks() { //if we are an unsafe router for an IPv4 range
+		if un.Addr().Is4() {
+			addSnatAddr = true
+			break
+		}
+	}
+	for _, route := range routes { //or if we have a route defined into an IPv4 range
+		if route.Cidr.Addr().Is4() {
+			addSnatAddr = true //todo should this only apply to unsafe routes?
+			break
+		}
+	}
+	if !addSnatAddr {
+		return netip.Prefix{}
+	}
+
+	var err error
+	out := netip.Addr{}
+	if a := c.GetString("tun.snat_address_for_4over6", ""); a != "" {
+		out, err = netip.ParseAddr(a)
+		if err != nil {
+			l.WithField("value", a).WithError(err).Warn("failed to parse tun.snat_address_for_4over6, will use a random value")
+		} else if !out.Is4() || !out.IsLinkLocalUnicast() {
+			l.WithField("value", out).Warn("tun.snat_address_for_4over6 must be an IPv4 address")
+		}
+	}
+	if !out.IsValid() {
+		octets := []byte{169, 254, 0, 0}
+		_, _ = rand.Read(octets[2:4])
+		if octets[3] == 0 {
+			octets[3] = 1 //please no .0 addresses
+		} else if octets[2] == 255 && octets[3] == 255 {
+			octets[3] = 254 //please no broadcast addresses
+		}
+		ok := false
+		out, ok = netip.AddrFromSlice(octets)
+		if !ok {
+			l.Error("failed to produce a valid IPv4 address for tun.snat_address_for_4over6")
+			return netip.Prefix{}
+		}
+	}
+	return netip.PrefixFrom(out, 32)
+}

+ 12 - 6
overlay/tun_android.go

@@ -19,14 +19,15 @@ import (
 
 
 type tun struct {
 type tun struct {
 	io.ReadWriteCloser
 	io.ReadWriteCloser
-	fd          int
-	vpnNetworks []netip.Prefix
-	Routes      atomic.Pointer[[]Route]
-	routeTree   atomic.Pointer[bart.Table[routing.Gateways]]
-	l           *logrus.Logger
+	fd             int
+	vpnNetworks    []netip.Prefix
+	unsafeNetworks []netip.Prefix
+	Routes         atomic.Pointer[[]Route]
+	routeTree      atomic.Pointer[bart.Table[routing.Gateways]]
+	l              *logrus.Logger
 }
 }
 
 
-func newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, vpnNetworks []netip.Prefix, _ []netip.Prefix) (*tun, error) {
+func newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, vpnNetworks []netip.Prefix, unsafeNetworks []netip.Prefix) (*tun, error) {
 	// XXX Android returns an fd in non-blocking mode which is necessary for shutdown to work properly.
 	// XXX Android returns an fd in non-blocking mode which is necessary for shutdown to work properly.
 	// Be sure not to call file.Fd() as it will set the fd to blocking mode.
 	// Be sure not to call file.Fd() as it will set the fd to blocking mode.
 	file := os.NewFile(uintptr(deviceFd), "/dev/net/tun")
 	file := os.NewFile(uintptr(deviceFd), "/dev/net/tun")
@@ -35,6 +36,7 @@ func newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, vpnNetworks []net
 		ReadWriteCloser: file,
 		ReadWriteCloser: file,
 		fd:              deviceFd,
 		fd:              deviceFd,
 		vpnNetworks:     vpnNetworks,
 		vpnNetworks:     vpnNetworks,
+		unsafeNetworks:  unsafeNetworks,
 		l:               l,
 		l:               l,
 	}
 	}
 
 
@@ -91,6 +93,10 @@ func (t *tun) Networks() []netip.Prefix {
 	return t.vpnNetworks
 	return t.vpnNetworks
 }
 }
 
 
+func (t *tun) UnsafeNetworks() []netip.Prefix {
+	return t.UnsafeNetworks()
+}
+
 func (t *tun) Name() string {
 func (t *tun) Name() string {
 	return "android"
 	return "android"
 }
 }

+ 18 - 7
overlay/tun_darwin.go

@@ -24,13 +24,15 @@ import (
 
 
 type tun struct {
 type tun struct {
 	io.ReadWriteCloser
 	io.ReadWriteCloser
-	Device      string
-	vpnNetworks []netip.Prefix
-	DefaultMTU  int
-	Routes      atomic.Pointer[[]Route]
-	routeTree   atomic.Pointer[bart.Table[routing.Gateways]]
-	linkAddr    *netroute.LinkAddr
-	l           *logrus.Logger
+	Device         string
+	vpnNetworks    []netip.Prefix
+	unsafeNetworks []netip.Prefix
+	snatAddr       netip.Prefix
+	DefaultMTU     int
+	Routes         atomic.Pointer[[]Route]
+	routeTree      atomic.Pointer[bart.Table[routing.Gateways]]
+	linkAddr       *netroute.LinkAddr
+	l              *logrus.Logger
 
 
 	// cache out buffer since we need to prepend 4 bytes for tun metadata
 	// cache out buffer since we need to prepend 4 bytes for tun metadata
 	out []byte
 	out []byte
@@ -127,6 +129,7 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, unsafeNet
 		ReadWriteCloser: os.NewFile(uintptr(fd), ""),
 		ReadWriteCloser: os.NewFile(uintptr(fd), ""),
 		Device:          name,
 		Device:          name,
 		vpnNetworks:     vpnNetworks,
 		vpnNetworks:     vpnNetworks,
+		unsafeNetworks:  unsafeNetworks,
 		DefaultMTU:      c.GetInt("tun.mtu", DefaultMTU),
 		DefaultMTU:      c.GetInt("tun.mtu", DefaultMTU),
 		l:               l,
 		l:               l,
 	}
 	}
@@ -545,6 +548,14 @@ func (t *tun) Networks() []netip.Prefix {
 	return t.vpnNetworks
 	return t.vpnNetworks
 }
 }
 
 
+func (t *tun) UnsafeNetworks() []netip.Prefix {
+	return t.unsafeNetworks
+}
+
+func (t *tun) SNATAddress() netip.Prefix {
+	return t.snatAddr
+}
+
 func (t *tun) Name() string {
 func (t *tun) Name() string {
 	return t.Device
 	return t.Device
 }
 }

+ 7 - 0
overlay/tun_disabled.go

@@ -22,6 +22,13 @@ type disabledTun struct {
 	l  *logrus.Logger
 	l  *logrus.Logger
 }
 }
 
 
+func (*disabledTun) UnsafeNetworks() []netip.Prefix {
+	return nil
+}
+func (*disabledTun) SNATAddress() netip.Prefix {
+	return netip.Prefix{}
+}
+
 func newDisabledTun(vpnNetworks []netip.Prefix, queueLen int, metricsEnabled bool, l *logrus.Logger) *disabledTun {
 func newDisabledTun(vpnNetworks []netip.Prefix, queueLen int, metricsEnabled bool, l *logrus.Logger) *disabledTun {
 	tun := &disabledTun{
 	tun := &disabledTun{
 		vpnNetworks: vpnNetworks,
 		vpnNetworks: vpnNetworks,

+ 24 - 13
overlay/tun_freebsd.go

@@ -86,14 +86,16 @@ type ifreqAlias6 struct {
 }
 }
 
 
 type tun struct {
 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
+	Device         string
+	vpnNetworks    []netip.Prefix
+	unsafeNetworks []netip.Prefix
+	snatAddr       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) {
 func (t *tun) Read(to []byte) (int, error) {
@@ -270,11 +272,12 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, unsafeNet
 	}
 	}
 
 
 	t := &tun{
 	t := &tun{
-		Device:      deviceName,
-		vpnNetworks: vpnNetworks,
-		MTU:         c.GetInt("tun.mtu", DefaultMTU),
-		l:           l,
-		devFd:       fd,
+		Device:         deviceName,
+		vpnNetworks:    vpnNetworks,
+		unsafeNetworks: unsafeNetworks,
+		MTU:            c.GetInt("tun.mtu", DefaultMTU),
+		l:              l,
+		devFd:          fd,
 	}
 	}
 
 
 	err = t.reload(c, true)
 	err = t.reload(c, true)
@@ -446,6 +449,14 @@ func (t *tun) Networks() []netip.Prefix {
 	return t.vpnNetworks
 	return t.vpnNetworks
 }
 }
 
 
+func (t *tun) UnsafeNetworks() []netip.Prefix {
+	return t.unsafeNetworks
+}
+
+func (t *tun) SNATAddress() netip.Prefix {
+	return t.snatAddr
+}
+
 func (t *tun) Name() string {
 func (t *tun) Name() string {
 	return t.Device
 	return t.Device
 }
 }

+ 15 - 5
overlay/tun_ios.go

@@ -22,20 +22,22 @@ import (
 
 
 type tun struct {
 type tun struct {
 	io.ReadWriteCloser
 	io.ReadWriteCloser
-	vpnNetworks []netip.Prefix
-	Routes      atomic.Pointer[[]Route]
-	routeTree   atomic.Pointer[bart.Table[routing.Gateways]]
-	l           *logrus.Logger
+	vpnNetworks    []netip.Prefix
+	unsafeNetworks []netip.Prefix
+	Routes         atomic.Pointer[[]Route]
+	routeTree      atomic.Pointer[bart.Table[routing.Gateways]]
+	l              *logrus.Logger
 }
 }
 
 
 func newTun(_ *config.C, _ *logrus.Logger, _ []netip.Prefix, _ []netip.Prefix, _ bool) (*tun, error) {
 func newTun(_ *config.C, _ *logrus.Logger, _ []netip.Prefix, _ []netip.Prefix, _ bool) (*tun, error) {
 	return nil, fmt.Errorf("newTun not supported in iOS")
 	return nil, fmt.Errorf("newTun not supported in iOS")
 }
 }
 
 
-func newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, vpnNetworks []netip.Prefix, _ []netip.Prefix) (*tun, error) {
+func newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, vpnNetworks []netip.Prefix, unsafeNetworks []netip.Prefix) (*tun, error) {
 	file := os.NewFile(uintptr(deviceFd), "/dev/tun")
 	file := os.NewFile(uintptr(deviceFd), "/dev/tun")
 	t := &tun{
 	t := &tun{
 		vpnNetworks:     vpnNetworks,
 		vpnNetworks:     vpnNetworks,
+		unsafeNetworks:  unsafeNetworks,
 		ReadWriteCloser: &tunReadCloser{f: file},
 		ReadWriteCloser: &tunReadCloser{f: file},
 		l:               l,
 		l:               l,
 	}
 	}
@@ -147,6 +149,14 @@ func (t *tun) Networks() []netip.Prefix {
 	return t.vpnNetworks
 	return t.vpnNetworks
 }
 }
 
 
+func (t *tun) UnsafeNetworks() []netip.Prefix {
+	return t.unsafeNetworks
+}
+
+func (t *tun) SNATAddress() netip.Prefix {
+	return t.snatAddr
+}
+
 func (t *tun) Name() string {
 func (t *tun) Name() string {
 	return "iOS"
 	return "iOS"
 }
 }

+ 11 - 55
overlay/tun_linux.go

@@ -4,7 +4,6 @@
 package overlay
 package overlay
 
 
 import (
 import (
-	"crypto/rand"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"net"
 	"net"
@@ -57,6 +56,14 @@ func (t *tun) Networks() []netip.Prefix {
 	return t.vpnNetworks
 	return t.vpnNetworks
 }
 }
 
 
+func (t *tun) UnsafeNetworks() []netip.Prefix {
+	return t.unsafeNetworks
+}
+
+func (t *tun) SNATAddress() netip.Prefix {
+	return t.snatAddr
+}
+
 type ifReq struct {
 type ifReq struct {
 	Name  [16]byte
 	Name  [16]byte
 	Flags uint16
 	Flags uint16
@@ -165,59 +172,6 @@ func newTunGeneric(c *config.C, l *logrus.Logger, file *os.File, vpnNetworks []n
 	return t, nil
 	return t, nil
 }
 }
 
 
-func (t *tun) prepareSnatAddr(c *config.C, initial bool, routes []Route) netip.Prefix {
-	if !initial {
-		return netip.Prefix{} //I don't wanna think about reloading this yet
-	}
-	if !t.vpnNetworks[0].Addr().Is6() {
-		return netip.Prefix{} //if we have an IPv4 assignment within the overlay, we don't need a snat address
-	}
-
-	addSnatAddr := false
-	for _, un := range t.unsafeNetworks { //if we are an unsafe router for an IPv4 range
-		if un.Addr().Is4() {
-			addSnatAddr = true
-			break
-		}
-	}
-	for _, route := range routes { //or if we have a route defined into an IPv4 range
-		if route.Cidr.Addr().Is4() {
-			addSnatAddr = true //todo should this only apply to unsafe routes?
-			break
-		}
-	}
-	if !addSnatAddr {
-		return netip.Prefix{}
-	}
-
-	var err error
-	out := netip.Addr{}
-	if a := c.GetString("tun.snat_address_for_4over6", ""); a != "" {
-		out, err = netip.ParseAddr(a)
-		if err != nil {
-			t.l.WithField("value", a).WithError(err).Warn("failed to parse tun.snat_address_for_4over6, will use a random value")
-		} else if !out.Is4() || !out.IsLinkLocalUnicast() {
-			t.l.WithField("value", t.snatAddr).Warn("tun.snat_address_for_4over6 must be an IPv4 address")
-		}
-	}
-	if !out.IsValid() {
-		octets := []byte{169, 254, 0, 0}
-		_, _ = rand.Read(octets[2:4])
-		if octets[3] == 0 {
-			octets[3] = 1 //please no .0 addresses
-		} else if octets[2] == 255 && octets[3] == 255 {
-			octets[3] = 254 //please no broadcast addresses
-		}
-		ok := false
-		out, ok = netip.AddrFromSlice(octets)
-		if !ok {
-			t.l.Error("failed to produce a valid IPv4 address for tun.snat_address_for_4over6")
-			return netip.Prefix{}
-		}
-	}
-	return netip.PrefixFrom(out, 32)
-}
-
 func (t *tun) reload(c *config.C, initial bool) error {
 func (t *tun) reload(c *config.C, initial bool) error {
 	routeChange, routes, err := getAllRoutesFromConfig(c, t.vpnNetworks, initial)
 	routeChange, routes, err := getAllRoutesFromConfig(c, t.vpnNetworks, initial)
 	if err != nil {
 	if err != nil {
@@ -228,7 +182,9 @@ func (t *tun) reload(c *config.C, initial bool) error {
 		return nil
 		return nil
 	}
 	}
 
 
-	t.snatAddr = t.prepareSnatAddr(c, initial, routes)
+	if !initial {
+		t.snatAddr = prepareSnatAddr(t, t.l, c, routes)
+	}
 
 
 	routeTree, err := makeRouteTree(t.l, routes, true)
 	routeTree, err := makeRouteTree(t.l, routes, true)
 	if err != nil {
 	if err != nil {

+ 18 - 8
overlay/tun_netbsd.go

@@ -58,14 +58,16 @@ type addrLifetime struct {
 }
 }
 
 
 type tun struct {
 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
+	Device         string
+	vpnNetworks    []netip.Prefix
+	unsafeNetworks []netip.Prefix
+	snatAddr       netip.Prefix
+	MTU            int
+	Routes         atomic.Pointer[[]Route]
+	routeTree      atomic.Pointer[bart.Table[routing.Gateways]]
+	l              *logrus.Logger
+	f              *os.File
+	fd             int
 }
 }
 
 
 var deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`)
 var deviceNameRE = regexp.MustCompile(`^tun[0-9]+$`)
@@ -386,6 +388,14 @@ func (t *tun) Networks() []netip.Prefix {
 	return t.vpnNetworks
 	return t.vpnNetworks
 }
 }
 
 
+func (t *tun) UnsafeNetworks() []netip.Prefix {
+	return t.unsafeNetworks
+}
+
+func (t *tun) SNATAddress() netip.Prefix {
+	return t.snatAddr
+}
+
 func (t *tun) Name() string {
 func (t *tun) Name() string {
 	return t.Device
 	return t.Device
 }
 }

+ 25 - 14
overlay/tun_openbsd.go

@@ -49,14 +49,16 @@ type ifreq struct {
 }
 }
 
 
 type tun struct {
 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
+	Device         string
+	vpnNetworks    []netip.Prefix
+	unsafeNetworks []netip.Prefix
+	snatAddr       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
 	// cache out buffer since we need to prepend 4 bytes for tun metadata
 	out []byte
 	out []byte
 }
 }
@@ -89,12 +91,13 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, unsafeNet
 	}
 	}
 
 
 	t := &tun{
 	t := &tun{
-		f:           os.NewFile(uintptr(fd), ""),
-		fd:          fd,
-		Device:      deviceName,
-		vpnNetworks: vpnNetworks,
-		MTU:         c.GetInt("tun.mtu", DefaultMTU),
-		l:           l,
+		f:              os.NewFile(uintptr(fd), ""),
+		fd:             fd,
+		Device:         deviceName,
+		vpnNetworks:    vpnNetworks,
+		unsafeNetworks: unsafeNetworks,
+		MTU:            c.GetInt("tun.mtu", DefaultMTU),
+		l:              l,
 	}
 	}
 
 
 	err = t.reload(c, true)
 	err = t.reload(c, true)
@@ -306,6 +309,14 @@ func (t *tun) Networks() []netip.Prefix {
 	return t.vpnNetworks
 	return t.vpnNetworks
 }
 }
 
 
+func (t *tun) UnsafeNetworks() []netip.Prefix {
+	return t.unsafeNetworks
+}
+
+func (t *tun) SNATAddress() netip.Prefix {
+	return t.snatAddr
+}
+
 func (t *tun) Name() string {
 func (t *tun) Name() string {
 	return t.Device
 	return t.Device
 }
 }

+ 22 - 12
overlay/tun_tester.go

@@ -17,11 +17,12 @@ import (
 )
 )
 
 
 type TestTun struct {
 type TestTun struct {
-	Device      string
-	vpnNetworks []netip.Prefix
-	Routes      []Route
-	routeTree   *bart.Table[routing.Gateways]
-	l           *logrus.Logger
+	Device         string
+	vpnNetworks    []netip.Prefix
+	unsafeNetworks []netip.Prefix
+	Routes         []Route
+	routeTree      *bart.Table[routing.Gateways]
+	l              *logrus.Logger
 
 
 	closed    atomic.Bool
 	closed    atomic.Bool
 	rxPackets chan []byte // Packets to receive into nebula
 	rxPackets chan []byte // Packets to receive into nebula
@@ -39,13 +40,14 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, unsafeNet
 	}
 	}
 
 
 	return &TestTun{
 	return &TestTun{
-		Device:      c.GetString("tun.dev", ""),
-		vpnNetworks: vpnNetworks,
-		Routes:      routes,
-		routeTree:   routeTree,
-		l:           l,
-		rxPackets:   make(chan []byte, 10),
-		TxPackets:   make(chan []byte, 10),
+		Device:         c.GetString("tun.dev", ""),
+		vpnNetworks:    vpnNetworks,
+		unsafeNetworks: unsafeNetworks,
+		Routes:         routes,
+		routeTree:      routeTree,
+		l:              l,
+		rxPackets:      make(chan []byte, 10),
+		TxPackets:      make(chan []byte, 10),
 	}, nil
 	}, nil
 }
 }
 
 
@@ -139,3 +141,11 @@ func (t *TestTun) SupportsMultiqueue() bool {
 func (t *TestTun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
 func (t *TestTun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
 	return nil, fmt.Errorf("TODO: multiqueue not implemented")
 	return nil, fmt.Errorf("TODO: multiqueue not implemented")
 }
 }
+
+func (t *tun) UnsafeNetworks() []netip.Prefix {
+	return t.UnsafeNetworks()
+}
+
+func (t *tun) SNATAddress() netip.Prefix {
+	return netip.Prefix{}
+}

+ 25 - 10
overlay/tun_windows.go

@@ -28,12 +28,14 @@ import (
 const tunGUIDLabel = "Fixed Nebula Windows GUID v1"
 const tunGUIDLabel = "Fixed Nebula Windows GUID v1"
 
 
 type winTun struct {
 type winTun struct {
-	Device      string
-	vpnNetworks []netip.Prefix
-	MTU         int
-	Routes      atomic.Pointer[[]Route]
-	routeTree   atomic.Pointer[bart.Table[routing.Gateways]]
-	l           *logrus.Logger
+	Device         string
+	vpnNetworks    []netip.Prefix
+	unsafeNetworks []netip.Prefix
+	snatAddr       netip.Prefix
+	MTU            int
+	Routes         atomic.Pointer[[]Route]
+	routeTree      atomic.Pointer[bart.Table[routing.Gateways]]
+	l              *logrus.Logger
 
 
 	tun *wintun.NativeTun
 	tun *wintun.NativeTun
 }
 }
@@ -55,10 +57,11 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, unsafeNet
 	}
 	}
 
 
 	t := &winTun{
 	t := &winTun{
-		Device:      deviceName,
-		vpnNetworks: vpnNetworks,
-		MTU:         c.GetInt("tun.mtu", DefaultMTU),
-		l:           l,
+		Device:         deviceName,
+		vpnNetworks:    vpnNetworks,
+		unsafeNetworks: unsafeNetworks,
+		MTU:            c.GetInt("tun.mtu", DefaultMTU),
+		l:              l,
 	}
 	}
 
 
 	err = t.reload(c, true)
 	err = t.reload(c, true)
@@ -102,6 +105,10 @@ func (t *winTun) reload(c *config.C, initial bool) error {
 		return nil
 		return nil
 	}
 	}
 
 
+	if !initial {
+		t.snatAddr = prepareSnatAddr(t, t.l, c, routes)
+	}
+
 	routeTree, err := makeRouteTree(t.l, routes, false)
 	routeTree, err := makeRouteTree(t.l, routes, false)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -225,6 +232,14 @@ func (t *winTun) Networks() []netip.Prefix {
 	return t.vpnNetworks
 	return t.vpnNetworks
 }
 }
 
 
+func (t *winTun) UnsafeNetworks() []netip.Prefix {
+	return t.unsafeNetworks
+}
+
+func (t *winTun) SNATAddress() netip.Prefix {
+	return t.snatAddr
+}
+
 func (t *winTun) Name() string {
 func (t *winTun) Name() string {
 	return t.Device
 	return t.Device
 }
 }

+ 8 - 0
overlay/user.go

@@ -36,6 +36,14 @@ type UserDevice struct {
 	inboundWriter *io.PipeWriter
 	inboundWriter *io.PipeWriter
 }
 }
 
 
+func (d *UserDevice) UnsafeNetworks() []netip.Prefix {
+	return nil
+}
+
+func (d *UserDevice) SNATAddress() netip.Prefix {
+	return netip.Prefix{}
+}
+
 func (d *UserDevice) Activate() error {
 func (d *UserDevice) Activate() error {
 	return nil
 	return nil
 }
 }

+ 10 - 0
test/tun.go

@@ -10,6 +10,16 @@ import (
 
 
 type NoopTun struct{}
 type NoopTun struct{}
 
 
+func (NoopTun) Routes() []Route {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (NoopTun) UnsafeNetworks() []netip.Prefix {
+	//TODO implement me
+	panic("implement me")
+}
+
 func (NoopTun) RoutesFor(addr netip.Addr) routing.Gateways {
 func (NoopTun) RoutesFor(addr netip.Addr) routing.Gateways {
 	return routing.Gateways{}
 	return routing.Gateways{}
 }
 }