2021-10-21 16:24:11 -05:00
|
|
|
//go:build !android && !e2e_testing
|
|
|
|
|
// +build !android,!e2e_testing
|
2020-07-01 10:20:52 -05:00
|
|
|
|
2021-11-11 16:37:29 -06:00
|
|
|
package overlay
|
2019-11-19 17:00:20 +00:00
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"net"
|
2024-07-31 10:18:56 -05:00
|
|
|
"net/netip"
|
2019-11-19 17:00:20 +00:00
|
|
|
"os"
|
|
|
|
|
"strings"
|
2023-05-09 10:36:55 -05:00
|
|
|
"sync/atomic"
|
2025-03-06 11:28:26 -06:00
|
|
|
"time"
|
2019-11-19 17:00:20 +00:00
|
|
|
"unsafe"
|
|
|
|
|
|
2024-07-31 10:18:56 -05:00
|
|
|
"github.com/gaissmai/bart"
|
2021-03-26 09:46:30 -05:00
|
|
|
"github.com/sirupsen/logrus"
|
2024-03-28 15:17:28 -05:00
|
|
|
"github.com/slackhq/nebula/config"
|
2025-03-24 23:15:59 +01:00
|
|
|
"github.com/slackhq/nebula/routing"
|
2024-03-28 15:17:28 -05:00
|
|
|
"github.com/slackhq/nebula/util"
|
2019-11-19 17:00:20 +00:00
|
|
|
"github.com/vishvananda/netlink"
|
|
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
|
)
|
|
|
|
|
|
2021-11-12 10:47:36 -06:00
|
|
|
type tun struct {
|
2019-11-19 17:00:20 +00:00
|
|
|
io.ReadWriteCloser
|
2024-03-28 15:17:28 -05:00
|
|
|
fd int
|
|
|
|
|
Device string
|
2025-03-06 11:28:26 -06:00
|
|
|
vpnNetworks []netip.Prefix
|
2024-03-28 15:17:28 -05:00
|
|
|
MaxMTU int
|
|
|
|
|
DefaultMTU int
|
|
|
|
|
TXQueueLen int
|
|
|
|
|
deviceIndex int
|
|
|
|
|
ioctlFd uintptr
|
|
|
|
|
|
2025-04-21 20:44:33 +03:00
|
|
|
Routes atomic.Pointer[[]Route]
|
|
|
|
|
routeTree atomic.Pointer[bart.Table[routing.Gateways]]
|
|
|
|
|
routeChan chan struct{}
|
|
|
|
|
useSystemRoutes bool
|
|
|
|
|
useSystemRoutesBufferSize int
|
2023-05-09 10:36:55 -05:00
|
|
|
|
|
|
|
|
l *logrus.Logger
|
2019-11-19 17:00:20 +00:00
|
|
|
}
|
|
|
|
|
|
2025-03-06 11:28:26 -06:00
|
|
|
func (t *tun) Networks() []netip.Prefix {
|
|
|
|
|
return t.vpnNetworks
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-19 17:00:20 +00:00
|
|
|
type ifReq struct {
|
|
|
|
|
Name [16]byte
|
|
|
|
|
Flags uint16
|
|
|
|
|
pad [8]byte
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ifreqMTU struct {
|
|
|
|
|
Name [16]byte
|
2019-11-25 11:21:48 -08:00
|
|
|
MTU int32
|
2019-11-19 17:00:20 +00:00
|
|
|
pad [8]byte
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ifreqQLEN struct {
|
|
|
|
|
Name [16]byte
|
2019-12-11 12:02:57 -08:00
|
|
|
Value int32
|
2019-11-19 17:00:20 +00:00
|
|
|
pad [8]byte
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-06 11:28:26 -06:00
|
|
|
func newTunFromFd(c *config.C, l *logrus.Logger, deviceFd int, vpnNetworks []netip.Prefix) (*tun, error) {
|
2024-03-28 15:17:28 -05:00
|
|
|
file := os.NewFile(uintptr(deviceFd), "/dev/net/tun")
|
|
|
|
|
|
2025-03-06 11:28:26 -06:00
|
|
|
t, err := newTunGeneric(c, l, file, vpnNetworks)
|
2021-11-12 11:19:28 -06:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2020-06-30 13:48:58 -05:00
|
|
|
|
2024-03-28 15:17:28 -05:00
|
|
|
t.Device = "tun0"
|
2020-06-30 13:48:58 -05:00
|
|
|
|
2023-05-09 10:36:55 -05:00
|
|
|
return t, nil
|
2020-06-30 13:48:58 -05:00
|
|
|
}
|
|
|
|
|
|
2025-03-06 11:28:26 -06:00
|
|
|
func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, multiqueue bool) (*tun, error) {
|
2019-11-19 17:00:20 +00:00
|
|
|
fd, err := unix.Open("/dev/net/tun", os.O_RDWR, 0)
|
|
|
|
|
if err != nil {
|
2024-05-02 09:37:11 -04:00
|
|
|
// If /dev/net/tun doesn't exist, try to create it (will happen in docker)
|
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
|
err = os.MkdirAll("/dev/net", 0755)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("/dev/net/tun doesn't exist, failed to mkdir -p /dev/net: %w", err)
|
|
|
|
|
}
|
|
|
|
|
err = unix.Mknod("/dev/net/tun", unix.S_IFCHR|0600, int(unix.Mkdev(10, 200)))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to create /dev/net/tun: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fd, err = unix.Open("/dev/net/tun", os.O_RDWR, 0)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("created /dev/net/tun, but still failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2019-11-19 17:00:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var req ifReq
|
2021-11-12 10:47:36 -06:00
|
|
|
req.Flags = uint16(unix.IFF_TUN | unix.IFF_NO_PI)
|
2021-02-25 15:01:14 -05:00
|
|
|
if multiqueue {
|
2021-11-12 10:47:36 -06:00
|
|
|
req.Flags |= unix.IFF_MULTI_QUEUE
|
2021-02-25 15:01:14 -05:00
|
|
|
}
|
2024-03-28 15:17:28 -05:00
|
|
|
copy(req.Name[:], c.GetString("tun.dev", ""))
|
Add linux-386, linux-ppc64le targets (#56)
* Use golang.org/x/sys/unix for _linux.go sources
To support builds on GOARCH=386 and possibly elsewhere, it's necessary
to use the x/sys/unix package instead of the syscall package. This is
because the syscall package is frozen and does not support
SYS_GETSOCKNAME, SYS_RECVFROM, nor SYS_SENDTO for GOARCH=386.
This commit alone doesn't add support for 386 builds, just gets things
onto x/sys/unix so that it's possible.
The remaining uses of the syscall package relate to signals, which
cannot be switched to the x/sys/unix package at this time. Windows
support breaks, so they can either continue using the syscall package
(it's frozen, this is safe for Go 1.x at minimum), or something can be
written to just use both windows- and unix-compatible signals.
* Add linux-386, ppc64le targets to Makefile
Because 'linux' is linux-amd64 already, just add linux-386 and
linux-ppc64le targets to distinguish them. Would rename the linux
target but that might break existing uses.
2019-12-11 17:51:55 -08:00
|
|
|
if err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
|
2021-02-25 15:01:14 -05:00
|
|
|
return nil, err
|
2019-11-19 17:00:20 +00:00
|
|
|
}
|
|
|
|
|
name := strings.Trim(string(req.Name[:]), "\x00")
|
|
|
|
|
|
|
|
|
|
file := os.NewFile(uintptr(fd), "/dev/net/tun")
|
2025-03-06 11:28:26 -06:00
|
|
|
t, err := newTunGeneric(c, l, file, vpnNetworks)
|
2021-11-12 11:19:28 -06:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-28 15:17:28 -05:00
|
|
|
t.Device = name
|
|
|
|
|
|
|
|
|
|
return t, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-06 11:28:26 -06:00
|
|
|
func newTunGeneric(c *config.C, l *logrus.Logger, file *os.File, vpnNetworks []netip.Prefix) (*tun, error) {
|
2023-05-09 10:36:55 -05:00
|
|
|
t := &tun{
|
2025-04-21 20:44:33 +03:00
|
|
|
ReadWriteCloser: file,
|
|
|
|
|
fd: int(file.Fd()),
|
|
|
|
|
vpnNetworks: vpnNetworks,
|
|
|
|
|
TXQueueLen: c.GetInt("tun.tx_queue", 500),
|
|
|
|
|
useSystemRoutes: c.GetBool("tun.use_system_route_table", false),
|
|
|
|
|
useSystemRoutesBufferSize: c.GetInt("tun.use_system_route_table_buffer_size", 0),
|
|
|
|
|
l: l,
|
2023-05-09 10:36:55 -05:00
|
|
|
}
|
2024-03-28 15:17:28 -05:00
|
|
|
|
|
|
|
|
err := t.reload(c, true)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.RegisterReloadCallback(func(c *config.C) {
|
|
|
|
|
err := t.reload(c, false)
|
|
|
|
|
if err != nil {
|
|
|
|
|
util.LogWithContextIfNeeded("failed to reload tun device", err, t.l)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2023-05-09 10:36:55 -05:00
|
|
|
return t, nil
|
2019-11-19 17:00:20 +00:00
|
|
|
}
|
|
|
|
|
|
2024-03-28 15:17:28 -05:00
|
|
|
func (t *tun) reload(c *config.C, initial bool) error {
|
2025-03-06 11:28:26 -06:00
|
|
|
routeChange, routes, err := getAllRoutesFromConfig(c, t.vpnNetworks, initial)
|
2024-03-28 15:17:28 -05:00
|
|
|
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 {
|
|
|
|
|
if oldMaxMTU != newMaxMTU {
|
|
|
|
|
t.setMTU()
|
|
|
|
|
t.l.Infof("Set max MTU to %v was %v", t.MaxMTU, oldMaxMTU)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if oldDefaultMTU != newDefaultMTU {
|
2025-03-06 11:28:26 -06:00
|
|
|
for i := range t.vpnNetworks {
|
|
|
|
|
err := t.setDefaultRoute(t.vpnNetworks[i])
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.l.Warn(err)
|
|
|
|
|
} else {
|
|
|
|
|
t.l.Infof("Set default MTU to %v was %v", t.DefaultMTU, oldDefaultMTU)
|
|
|
|
|
}
|
2024-03-28 15:17:28 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove first, if the system removes a wanted route hopefully it will be re-added next
|
|
|
|
|
t.removeRoutes(findRemovedRoutes(routes, *oldRoutes))
|
|
|
|
|
|
|
|
|
|
// Ensure any routes we actually want are installed
|
|
|
|
|
err = t.addRoutes(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
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-19 14:10:11 -05:00
|
|
|
func (t *tun) SupportsMultiqueue() bool {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-12 10:47:36 -06:00
|
|
|
func (t *tun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
|
2021-02-25 15:01:14 -05:00
|
|
|
fd, err := unix.Open("/dev/net/tun", os.O_RDWR, 0)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var req ifReq
|
2021-11-12 10:47:36 -06:00
|
|
|
req.Flags = uint16(unix.IFF_TUN | unix.IFF_NO_PI | unix.IFF_MULTI_QUEUE)
|
|
|
|
|
copy(req.Name[:], t.Device)
|
2021-02-25 15:01:14 -05:00
|
|
|
if err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
file := os.NewFile(uintptr(fd), "/dev/net/tun")
|
|
|
|
|
|
|
|
|
|
return file, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-24 23:15:59 +01:00
|
|
|
func (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {
|
2024-07-31 10:18:56 -05:00
|
|
|
r, _ := t.routeTree.Load().Lookup(ip)
|
2023-11-02 17:05:08 -05:00
|
|
|
return r
|
2021-11-12 11:19:28 -06:00
|
|
|
}
|
|
|
|
|
|
2021-11-12 12:47:09 -06:00
|
|
|
func (t *tun) Write(b []byte) (int, error) {
|
2019-11-19 17:00:20 +00:00
|
|
|
var nn int
|
2025-03-06 11:28:26 -06:00
|
|
|
maximum := len(b)
|
2021-11-12 12:47:09 -06:00
|
|
|
|
2019-11-19 17:00:20 +00:00
|
|
|
for {
|
2025-03-06 11:28:26 -06:00
|
|
|
n, err := unix.Write(t.fd, b[nn:maximum])
|
2019-11-19 17:00:20 +00:00
|
|
|
if n > 0 {
|
|
|
|
|
nn += n
|
|
|
|
|
}
|
|
|
|
|
if nn == len(b) {
|
2021-11-12 12:47:09 -06:00
|
|
|
return nn, err
|
2019-11-19 17:00:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
2021-11-12 12:47:09 -06:00
|
|
|
return nn, err
|
2019-11-19 17:00:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if n == 0 {
|
2021-11-12 12:47:09 -06:00
|
|
|
return nn, io.ErrUnexpectedEOF
|
2019-11-19 17:00:20 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-09 10:36:55 -05:00
|
|
|
func (t *tun) deviceBytes() (o [16]byte) {
|
2021-11-12 10:47:36 -06:00
|
|
|
for i, c := range t.Device {
|
2019-11-19 17:00:20 +00:00
|
|
|
o[i] = byte(c)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-06 11:28:26 -06:00
|
|
|
func hasNetlinkAddr(al []*netlink.Addr, x netlink.Addr) bool {
|
|
|
|
|
for i := range al {
|
|
|
|
|
if al[i].Equal(x) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// addIPs uses netlink to add all addresses that don't exist, then it removes ones that should not be there
|
|
|
|
|
func (t *tun) addIPs(link netlink.Link) error {
|
|
|
|
|
newAddrs := make([]*netlink.Addr, len(t.vpnNetworks))
|
|
|
|
|
for i := range t.vpnNetworks {
|
|
|
|
|
newAddrs[i] = &netlink.Addr{
|
|
|
|
|
IPNet: &net.IPNet{
|
|
|
|
|
IP: t.vpnNetworks[i].Addr().AsSlice(),
|
|
|
|
|
Mask: net.CIDRMask(t.vpnNetworks[i].Bits(), t.vpnNetworks[i].Addr().BitLen()),
|
|
|
|
|
},
|
|
|
|
|
Label: t.vpnNetworks[i].Addr().Zone(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//add all new addresses
|
|
|
|
|
for i := range newAddrs {
|
|
|
|
|
//AddrReplace still adds new IPs, but if their properties change it will change them as well
|
|
|
|
|
if err := netlink.AddrReplace(link, newAddrs[i]); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//iterate over remainder, remove whoever shouldn't be there
|
|
|
|
|
al, err := netlink.AddrList(link, netlink.FAMILY_ALL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to get tun address list: %s", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i := range al {
|
|
|
|
|
if hasNetlinkAddr(newAddrs, al[i]) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
err = netlink.AddrDel(link, &al[i])
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.l.WithError(err).Error("failed to remove address from tun address list")
|
|
|
|
|
} else {
|
|
|
|
|
t.l.WithField("removed", al[i].String()).Info("removed address not listed in cert(s)")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-09 10:36:55 -05:00
|
|
|
func (t *tun) Activate() error {
|
2021-11-12 10:47:36 -06:00
|
|
|
devName := t.deviceBytes()
|
2019-11-19 17:00:20 +00:00
|
|
|
|
2023-05-09 10:36:55 -05:00
|
|
|
if t.useSystemRoutes {
|
|
|
|
|
t.watchRoutes()
|
|
|
|
|
}
|
|
|
|
|
|
Add linux-386, linux-ppc64le targets (#56)
* Use golang.org/x/sys/unix for _linux.go sources
To support builds on GOARCH=386 and possibly elsewhere, it's necessary
to use the x/sys/unix package instead of the syscall package. This is
because the syscall package is frozen and does not support
SYS_GETSOCKNAME, SYS_RECVFROM, nor SYS_SENDTO for GOARCH=386.
This commit alone doesn't add support for 386 builds, just gets things
onto x/sys/unix so that it's possible.
The remaining uses of the syscall package relate to signals, which
cannot be switched to the x/sys/unix package at this time. Windows
support breaks, so they can either continue using the syscall package
(it's frozen, this is safe for Go 1.x at minimum), or something can be
written to just use both windows- and unix-compatible signals.
* Add linux-386, ppc64le targets to Makefile
Because 'linux' is linux-amd64 already, just add linux-386 and
linux-ppc64le targets to distinguish them. Would rename the linux
target but that might break existing uses.
2019-12-11 17:51:55 -08:00
|
|
|
s, err := unix.Socket(
|
2025-03-06 11:28:26 -06:00
|
|
|
unix.AF_INET, //because everything we use t.ioctlFd for is address family independent, this is fine
|
Add linux-386, linux-ppc64le targets (#56)
* Use golang.org/x/sys/unix for _linux.go sources
To support builds on GOARCH=386 and possibly elsewhere, it's necessary
to use the x/sys/unix package instead of the syscall package. This is
because the syscall package is frozen and does not support
SYS_GETSOCKNAME, SYS_RECVFROM, nor SYS_SENDTO for GOARCH=386.
This commit alone doesn't add support for 386 builds, just gets things
onto x/sys/unix so that it's possible.
The remaining uses of the syscall package relate to signals, which
cannot be switched to the x/sys/unix package at this time. Windows
support breaks, so they can either continue using the syscall package
(it's frozen, this is safe for Go 1.x at minimum), or something can be
written to just use both windows- and unix-compatible signals.
* Add linux-386, ppc64le targets to Makefile
Because 'linux' is linux-amd64 already, just add linux-386 and
linux-ppc64le targets to distinguish them. Would rename the linux
target but that might break existing uses.
2019-12-11 17:51:55 -08:00
|
|
|
unix.SOCK_DGRAM,
|
|
|
|
|
unix.IPPROTO_IP,
|
2019-11-19 17:00:20 +00:00
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2024-03-28 15:17:28 -05:00
|
|
|
t.ioctlFd = uintptr(s)
|
2019-11-19 17:00:20 +00:00
|
|
|
|
|
|
|
|
// Set the device name
|
|
|
|
|
ifrf := ifReq{Name: devName}
|
2024-03-28 15:17:28 -05:00
|
|
|
if err = ioctl(t.ioctlFd, unix.SIOCGIFFLAGS, uintptr(unsafe.Pointer(&ifrf))); err != nil {
|
2019-11-23 23:18:03 -08:00
|
|
|
return fmt.Errorf("failed to set tun device name: %s", err)
|
2019-11-19 17:00:20 +00:00
|
|
|
}
|
|
|
|
|
|
2025-03-06 11:28:26 -06:00
|
|
|
link, err := netlink.LinkByName(t.Device)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to get tun device link: %s", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t.deviceIndex = link.Attrs().Index
|
|
|
|
|
|
2024-03-28 15:17:28 -05:00
|
|
|
// Setup our default MTU
|
|
|
|
|
t.setMTU()
|
2019-11-19 17:00:20 +00:00
|
|
|
|
|
|
|
|
// Set the transmit queue length
|
2021-11-12 10:47:36 -06:00
|
|
|
ifrq := ifreqQLEN{Name: devName, Value: int32(t.TXQueueLen)}
|
2024-03-28 15:17:28 -05:00
|
|
|
if err = ioctl(t.ioctlFd, unix.SIOCSIFTXQLEN, uintptr(unsafe.Pointer(&ifrq))); err != nil {
|
2019-12-20 09:10:26 -08:00
|
|
|
// If we can't set the queue length nebula will still work but it may lead to packet loss
|
2021-11-12 10:47:36 -06:00
|
|
|
t.l.WithError(err).Error("Failed to set tun tx queue length")
|
2019-11-19 17:00:20 +00:00
|
|
|
}
|
|
|
|
|
|
2025-09-09 13:25:23 -05:00
|
|
|
const modeNone = 1
|
|
|
|
|
if err = netlink.LinkSetIP6AddrGenMode(link, modeNone); err != nil {
|
|
|
|
|
t.l.WithError(err).Warn("Failed to disable link local address generation")
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-06 11:28:26 -06:00
|
|
|
if err = t.addIPs(link); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-19 17:00:20 +00:00
|
|
|
// Bring up the interface
|
Add linux-386, linux-ppc64le targets (#56)
* Use golang.org/x/sys/unix for _linux.go sources
To support builds on GOARCH=386 and possibly elsewhere, it's necessary
to use the x/sys/unix package instead of the syscall package. This is
because the syscall package is frozen and does not support
SYS_GETSOCKNAME, SYS_RECVFROM, nor SYS_SENDTO for GOARCH=386.
This commit alone doesn't add support for 386 builds, just gets things
onto x/sys/unix so that it's possible.
The remaining uses of the syscall package relate to signals, which
cannot be switched to the x/sys/unix package at this time. Windows
support breaks, so they can either continue using the syscall package
(it's frozen, this is safe for Go 1.x at minimum), or something can be
written to just use both windows- and unix-compatible signals.
* Add linux-386, ppc64le targets to Makefile
Because 'linux' is linux-amd64 already, just add linux-386 and
linux-ppc64le targets to distinguish them. Would rename the linux
target but that might break existing uses.
2019-12-11 17:51:55 -08:00
|
|
|
ifrf.Flags = ifrf.Flags | unix.IFF_UP
|
2024-03-28 15:17:28 -05:00
|
|
|
if err = ioctl(t.ioctlFd, unix.SIOCSIFFLAGS, uintptr(unsafe.Pointer(&ifrf))); err != nil {
|
2019-11-23 23:18:03 -08:00
|
|
|
return fmt.Errorf("failed to bring the tun device up: %s", err)
|
2019-11-19 17:00:20 +00:00
|
|
|
}
|
|
|
|
|
|
2025-03-06 11:28:26 -06:00
|
|
|
//set route MTU
|
|
|
|
|
for i := range t.vpnNetworks {
|
|
|
|
|
if err = t.setDefaultRoute(t.vpnNetworks[i]); err != nil {
|
|
|
|
|
return fmt.Errorf("failed to set default route MTU: %w", err)
|
|
|
|
|
}
|
2024-03-28 15:17:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set the routes
|
|
|
|
|
if err = t.addRoutes(false); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2019-11-19 17:00:20 +00:00
|
|
|
|
2024-03-28 15:17:28 -05:00
|
|
|
// Run the interface
|
|
|
|
|
ifrf.Flags = ifrf.Flags | unix.IFF_UP | unix.IFF_RUNNING
|
|
|
|
|
if err = ioctl(t.ioctlFd, unix.SIOCSIFFLAGS, uintptr(unsafe.Pointer(&ifrf))); err != nil {
|
|
|
|
|
return fmt.Errorf("failed to run tun device: %s", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *tun) setMTU() {
|
|
|
|
|
// Set the MTU on the device
|
|
|
|
|
ifm := ifreqMTU{Name: t.deviceBytes(), MTU: int32(t.MaxMTU)}
|
|
|
|
|
if err := ioctl(t.ioctlFd, unix.SIOCSIFMTU, uintptr(unsafe.Pointer(&ifm))); err != nil {
|
|
|
|
|
// This is currently a non fatal condition because the route table must have the MTU set appropriately as well
|
|
|
|
|
t.l.WithError(err).Error("Failed to set tun mtu")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-06 11:28:26 -06:00
|
|
|
func (t *tun) setDefaultRoute(cidr netip.Prefix) error {
|
2024-07-31 10:18:56 -05:00
|
|
|
dr := &net.IPNet{
|
2025-03-06 11:28:26 -06:00
|
|
|
IP: cidr.Masked().Addr().AsSlice(),
|
|
|
|
|
Mask: net.CIDRMask(cidr.Bits(), cidr.Addr().BitLen()),
|
2024-07-31 10:18:56 -05:00
|
|
|
}
|
|
|
|
|
|
2019-11-19 17:00:20 +00:00
|
|
|
nr := netlink.Route{
|
2024-03-28 15:17:28 -05:00
|
|
|
LinkIndex: t.deviceIndex,
|
2019-11-19 17:00:20 +00:00
|
|
|
Dst: dr,
|
2021-11-12 10:47:36 -06:00
|
|
|
MTU: t.DefaultMTU,
|
|
|
|
|
AdvMSS: t.advMSS(Route{}),
|
2019-11-19 17:00:20 +00:00
|
|
|
Scope: unix.RT_SCOPE_LINK,
|
2025-03-06 11:28:26 -06:00
|
|
|
Src: net.IP(cidr.Addr().AsSlice()),
|
2019-11-19 17:00:20 +00:00
|
|
|
Protocol: unix.RTPROT_KERNEL,
|
|
|
|
|
Table: unix.RT_TABLE_MAIN,
|
|
|
|
|
Type: unix.RTN_UNICAST,
|
|
|
|
|
}
|
2024-03-28 15:17:28 -05:00
|
|
|
err := netlink.RouteReplace(&nr)
|
2019-11-19 17:00:20 +00:00
|
|
|
if err != nil {
|
2025-03-06 11:28:26 -06:00
|
|
|
t.l.WithError(err).WithField("cidr", cidr).Warn("Failed to set default route MTU, retrying")
|
|
|
|
|
//retry twice more -- on some systems there appears to be a race condition where if we set routes too soon, netlink says `invalid argument`
|
|
|
|
|
for i := 0; i < 2; i++ {
|
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
|
err = netlink.RouteReplace(&nr)
|
|
|
|
|
if err == nil {
|
|
|
|
|
break
|
|
|
|
|
} else {
|
|
|
|
|
t.l.WithError(err).WithField("cidr", cidr).WithField("mtu", t.DefaultMTU).Warn("Failed to set default route MTU, retrying")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to set mtu %v on the default route %v; %v", t.DefaultMTU, dr, err)
|
|
|
|
|
}
|
2019-11-19 17:00:20 +00:00
|
|
|
}
|
|
|
|
|
|
2024-03-28 15:17:28 -05:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *tun) addRoutes(logErrors bool) error {
|
2019-11-19 17:00:20 +00:00
|
|
|
// Path routes
|
2024-03-28 15:17:28 -05:00
|
|
|
routes := *t.Routes.Load()
|
|
|
|
|
for _, r := range routes {
|
2023-04-10 12:32:37 -05:00
|
|
|
if !r.Install {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-31 10:18:56 -05:00
|
|
|
dr := &net.IPNet{
|
|
|
|
|
IP: r.Cidr.Masked().Addr().AsSlice(),
|
|
|
|
|
Mask: net.CIDRMask(r.Cidr.Bits(), r.Cidr.Addr().BitLen()),
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-19 17:00:20 +00:00
|
|
|
nr := netlink.Route{
|
2024-03-28 15:17:28 -05:00
|
|
|
LinkIndex: t.deviceIndex,
|
2024-07-31 10:18:56 -05:00
|
|
|
Dst: dr,
|
2021-11-11 16:37:29 -06:00
|
|
|
MTU: r.MTU,
|
2021-11-12 10:47:36 -06:00
|
|
|
AdvMSS: t.advMSS(r),
|
2019-11-19 17:00:20 +00:00
|
|
|
Scope: unix.RT_SCOPE_LINK,
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-12 11:19:28 -06:00
|
|
|
if r.Metric > 0 {
|
|
|
|
|
nr.Priority = r.Metric
|
2019-12-12 16:34:17 +00:00
|
|
|
}
|
|
|
|
|
|
2024-03-28 15:17:28 -05:00
|
|
|
err := netlink.RouteReplace(&nr)
|
2019-12-12 16:34:17 +00:00
|
|
|
if err != nil {
|
2025-03-31 16:08:34 -04:00
|
|
|
retErr := util.NewContextualError("Failed to add route", map[string]any{"route": r}, err)
|
2024-03-28 15:17:28 -05:00
|
|
|
if logErrors {
|
|
|
|
|
retErr.Log(t.l)
|
|
|
|
|
} else {
|
|
|
|
|
return retErr
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
t.l.WithField("route", r).Info("Added route")
|
2019-12-12 16:34:17 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-19 17:00:20 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
2020-06-26 13:47:21 -04:00
|
|
|
|
2024-03-28 15:17:28 -05:00
|
|
|
func (t *tun) removeRoutes(routes []Route) {
|
|
|
|
|
for _, r := range routes {
|
|
|
|
|
if !r.Install {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-31 10:18:56 -05:00
|
|
|
dr := &net.IPNet{
|
|
|
|
|
IP: r.Cidr.Masked().Addr().AsSlice(),
|
|
|
|
|
Mask: net.CIDRMask(r.Cidr.Bits(), r.Cidr.Addr().BitLen()),
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-28 15:17:28 -05:00
|
|
|
nr := netlink.Route{
|
|
|
|
|
LinkIndex: t.deviceIndex,
|
2024-07-31 10:18:56 -05:00
|
|
|
Dst: dr,
|
2024-03-28 15:17:28 -05:00
|
|
|
MTU: r.MTU,
|
|
|
|
|
AdvMSS: t.advMSS(r),
|
|
|
|
|
Scope: unix.RT_SCOPE_LINK,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r.Metric > 0 {
|
|
|
|
|
nr.Priority = r.Metric
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err := netlink.RouteDel(&nr)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
|
|
|
|
|
} else {
|
|
|
|
|
t.l.WithField("route", r).Info("Removed route")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-12 12:47:09 -06:00
|
|
|
func (t *tun) Name() string {
|
2021-11-12 10:47:36 -06:00
|
|
|
return t.Device
|
2020-07-28 22:53:16 +10:00
|
|
|
}
|
|
|
|
|
|
2023-05-09 10:36:55 -05:00
|
|
|
func (t *tun) advMSS(r Route) int {
|
2021-11-11 16:37:29 -06:00
|
|
|
mtu := r.MTU
|
|
|
|
|
if r.MTU == 0 {
|
2021-11-12 10:47:36 -06:00
|
|
|
mtu = t.DefaultMTU
|
2020-06-26 13:47:21 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We only need to set advmss if the route MTU does not match the device MTU
|
2021-11-12 10:47:36 -06:00
|
|
|
if mtu != t.MaxMTU {
|
2020-06-26 13:47:21 -04:00
|
|
|
return mtu - 40
|
|
|
|
|
}
|
|
|
|
|
return 0
|
|
|
|
|
}
|
2023-05-09 10:36:55 -05:00
|
|
|
|
|
|
|
|
func (t *tun) watchRoutes() {
|
|
|
|
|
rch := make(chan netlink.RouteUpdate)
|
|
|
|
|
doneChan := make(chan struct{})
|
|
|
|
|
|
2025-04-21 20:44:33 +03:00
|
|
|
netlinkOptions := netlink.RouteSubscribeOptions{
|
|
|
|
|
ReceiveBufferSize: t.useSystemRoutesBufferSize,
|
|
|
|
|
ReceiveBufferForceSize: t.useSystemRoutesBufferSize != 0,
|
|
|
|
|
ErrorCallback: func(e error) { t.l.WithError(e).Errorf("netlink error") },
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := netlink.RouteSubscribeWithOptions(rch, doneChan, netlinkOptions); err != nil {
|
2023-05-09 10:36:55 -05:00
|
|
|
t.l.WithError(err).Errorf("failed to subscribe to system route changes")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t.routeChan = doneChan
|
|
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
for {
|
|
|
|
|
select {
|
2025-04-21 20:44:33 +03:00
|
|
|
case r, ok := <-rch:
|
|
|
|
|
if ok {
|
|
|
|
|
t.updateRoutes(r)
|
|
|
|
|
} else {
|
|
|
|
|
// may be should do something here as
|
|
|
|
|
// netlink stops sending updates
|
|
|
|
|
return
|
|
|
|
|
}
|
2023-05-09 10:36:55 -05:00
|
|
|
case <-doneChan:
|
|
|
|
|
// netlink.RouteSubscriber will close the rch for us
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-24 23:15:59 +01:00
|
|
|
func (t *tun) isGatewayInVpnNetworks(gwAddr netip.Addr) bool {
|
2025-03-06 11:28:26 -06:00
|
|
|
withinNetworks := false
|
|
|
|
|
for i := range t.vpnNetworks {
|
|
|
|
|
if t.vpnNetworks[i].Contains(gwAddr) {
|
|
|
|
|
withinNetworks = true
|
|
|
|
|
break
|
|
|
|
|
}
|
2023-05-09 10:36:55 -05:00
|
|
|
}
|
2025-03-24 23:15:59 +01:00
|
|
|
|
|
|
|
|
return withinNetworks
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *tun) getGatewaysFromRoute(r *netlink.Route) routing.Gateways {
|
|
|
|
|
|
|
|
|
|
var gateways routing.Gateways
|
|
|
|
|
|
|
|
|
|
link, err := netlink.LinkByName(t.Device)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.l.WithField("Devicename", t.Device).Error("Ignoring route update: failed to get link by name")
|
|
|
|
|
return gateways
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If this route is relevant to our interface and there is a gateway then add it
|
|
|
|
|
if r.LinkIndex == link.Attrs().Index && len(r.Gw) > 0 {
|
|
|
|
|
gwAddr, ok := netip.AddrFromSlice(r.Gw)
|
|
|
|
|
if !ok {
|
|
|
|
|
t.l.WithField("route", r).Debug("Ignoring route update, invalid gateway address")
|
|
|
|
|
} else {
|
|
|
|
|
gwAddr = gwAddr.Unmap()
|
|
|
|
|
|
|
|
|
|
if !t.isGatewayInVpnNetworks(gwAddr) {
|
|
|
|
|
// Gateway isn't in our overlay network, ignore
|
|
|
|
|
t.l.WithField("route", r).Debug("Ignoring route update, not in our network")
|
|
|
|
|
} else {
|
|
|
|
|
gateways = append(gateways, routing.NewGateway(gwAddr, 1))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, p := range r.MultiPath {
|
|
|
|
|
// If this route is relevant to our interface and there is a gateway then add it
|
|
|
|
|
if p.LinkIndex == link.Attrs().Index && len(p.Gw) > 0 {
|
|
|
|
|
gwAddr, ok := netip.AddrFromSlice(p.Gw)
|
|
|
|
|
if !ok {
|
|
|
|
|
t.l.WithField("route", r).Debug("Ignoring multipath route update, invalid gateway address")
|
|
|
|
|
} else {
|
|
|
|
|
gwAddr = gwAddr.Unmap()
|
|
|
|
|
|
|
|
|
|
if !t.isGatewayInVpnNetworks(gwAddr) {
|
|
|
|
|
// Gateway isn't in our overlay network, ignore
|
|
|
|
|
t.l.WithField("route", r).Debug("Ignoring route update, not in our network")
|
|
|
|
|
} else {
|
|
|
|
|
// p.Hops+1 = weight of the route
|
|
|
|
|
gateways = append(gateways, routing.NewGateway(gwAddr, p.Hops+1))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
routing.CalculateBucketsForGateways(gateways)
|
|
|
|
|
return gateways
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *tun) updateRoutes(r netlink.RouteUpdate) {
|
|
|
|
|
|
|
|
|
|
gateways := t.getGatewaysFromRoute(&r.Route)
|
|
|
|
|
|
|
|
|
|
if len(gateways) == 0 {
|
|
|
|
|
// No gateways relevant to our network, no routing changes required.
|
|
|
|
|
t.l.WithField("route", r).Debug("Ignoring route update, no gateways")
|
2023-05-09 10:36:55 -05:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-25 14:05:35 -04:00
|
|
|
if r.Dst == nil {
|
|
|
|
|
t.l.WithField("route", r).Debug("Ignoring route update, no destination address")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-31 10:18:56 -05:00
|
|
|
dstAddr, ok := netip.AddrFromSlice(r.Dst.IP)
|
|
|
|
|
if !ok {
|
|
|
|
|
t.l.WithField("route", r).Debug("Ignoring route update, invalid destination address")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ones, _ := r.Dst.Mask.Size()
|
|
|
|
|
dst := netip.PrefixFrom(dstAddr, ones)
|
|
|
|
|
|
|
|
|
|
newTree := t.routeTree.Load().Clone()
|
2023-05-09 10:36:55 -05:00
|
|
|
|
2024-07-31 10:18:56 -05:00
|
|
|
if r.Type == unix.RTM_NEWROUTE {
|
2025-03-24 23:15:59 +01:00
|
|
|
t.l.WithField("destination", dst).WithField("via", gateways).Info("Adding route")
|
|
|
|
|
newTree.Insert(dst, gateways)
|
2023-05-09 10:36:55 -05:00
|
|
|
|
|
|
|
|
} else {
|
2025-03-24 23:15:59 +01:00
|
|
|
t.l.WithField("destination", dst).WithField("via", gateways).Info("Removing route")
|
2024-07-31 10:18:56 -05:00
|
|
|
newTree.Delete(dst)
|
2023-05-09 10:36:55 -05:00
|
|
|
}
|
|
|
|
|
t.routeTree.Store(newTree)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *tun) Close() error {
|
|
|
|
|
if t.routeChan != nil {
|
|
|
|
|
close(t.routeChan)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if t.ReadWriteCloser != nil {
|
2025-03-06 11:28:26 -06:00
|
|
|
_ = t.ReadWriteCloser.Close()
|
2023-05-09 10:36:55 -05:00
|
|
|
}
|
|
|
|
|
|
2024-03-28 15:17:28 -05:00
|
|
|
if t.ioctlFd > 0 {
|
2025-03-06 11:28:26 -06:00
|
|
|
_ = os.NewFile(t.ioctlFd, "ioctlFd").Close()
|
2024-03-28 15:17:28 -05:00
|
|
|
}
|
|
|
|
|
|
2023-05-09 10:36:55 -05:00
|
|
|
return nil
|
|
|
|
|
}
|