//go:build !android && !e2e_testing
// +build !android,!e2e_testing

package overlay

import (
	"fmt"
	"log/slog"
	"net"
	"net/netip"
	"os"
	"strings"
	"sync"
	"sync/atomic"
	"time"
	"unsafe"

	"github.com/gaissmai/bart"
	"github.com/slackhq/nebula/config"
	"github.com/slackhq/nebula/overlay/tio"
	"github.com/slackhq/nebula/routing"
	"github.com/slackhq/nebula/util"
	"github.com/vishvananda/netlink"
	"golang.org/x/sys/unix"
)

type tun struct {
	readers     tio.QueueSet
	closeLock   sync.Mutex
	Device      string
	vpnNetworks []netip.Prefix
	MaxMTU      int
	DefaultMTU  int
	TXQueueLen  int
	deviceIndex int
	ioctlFd     uintptr
	vnetHdr     bool
	// offloadFlags is the exact TUN_F_* offload mask newTun negotiated with
	// the kernel: usoOffloadFlags when USO was accepted, tsoOffloadFlags on
	// the TSO-only fallback, or 0 when vnetHdr is off. TUNSETOFFLOAD is
	// device-wide (drivers/net/tun.c set_offload updates tun->set_features
	// for the whole netdev), so NewMultiQueueReader must replay this exact
	// mask on every added queue — issuing a narrower mask there would
	// silently downgrade offloads (e.g. disable USO) for all queues while
	// they still advertise the stale capability.
	offloadFlags uint
	// routeFeatureECN, when true, sets RTAX_FEATURE_ECN on every route we
	// install for the tun. The kernel then actively negotiates ECN for
	// connections destined to those prefixes (equivalent to `ip route
	// change ... features ecn`) regardless of net.ipv4.tcp_ecn, so flows
	// across the nebula mesh use ECN even when the host default is the
	// passive setting (=2). Disable via tunnels.ecn=false.
	routeFeatureECN bool

	Routes                    atomic.Pointer[[]Route]
	routeTree                 atomic.Pointer[bart.Table[routing.Gateways]]
	routeChan                 chan struct{}
	useSystemRoutes           bool
	useSystemRoutesBufferSize int

	// These are routes learned from `tun.use_system_route_table`
	// stored here to make it easier to restore them after a reload
	routesFromSystem     map[netip.Prefix]routing.Gateways
	routesFromSystemLock sync.Mutex

	l *slog.Logger
}

func (t *tun) Networks() []netip.Prefix {
	return t.vpnNetworks
}

type ifReq struct {
	Name  [16]byte
	Flags uint16
	pad   [8]byte
}

type ifreqMTU struct {
	Name [16]byte
	MTU  int32
	pad  [8]byte
}

type ifreqQLEN struct {
	Name  [16]byte
	Value int32
	pad   [8]byte
}

func newTunFromFd(c *config.C, l *slog.Logger, deviceFd int, vpnNetworks []netip.Prefix) (*tun, error) {
	// We don't know what flags the caller opened this fd with and can't turn
	// on IFF_VNET_HDR after TUNSETIFF, so skip offload on inherited fds.
	t, err := newTunGeneric(c, l, deviceFd, false, 0, vpnNetworks)
	if err != nil {
		return nil, err
	}

	t.Device = "tun0"

	return t, nil
}

// openTunDev opens /dev/net/tun, creating the device node first if it's
// missing (docker containers occasionally omit it).
func openTunDev() (int, error) {
	fd, err := unix.Open("/dev/net/tun", os.O_RDWR, 0)
	if err == nil {
		return fd, nil
	}
	if !os.IsNotExist(err) {
		return -1, err
	}
	if err = os.MkdirAll("/dev/net", 0755); err != nil {
		return -1, fmt.Errorf("/dev/net/tun doesn't exist, failed to mkdir -p /dev/net: %w", err)
	}
	if err = unix.Mknod("/dev/net/tun", unix.S_IFCHR|0600, int(unix.Mkdev(10, 200))); err != nil {
		return -1, 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 -1, fmt.Errorf("created /dev/net/tun, but still failed: %w", err)
	}
	return fd, nil
}

// tunSetIff runs TUNSETIFF with the given flags and returns the kernel-chosen
// device name on success.
func tunSetIff(fd int, name string, flags uint16) (string, error) {
	var req ifReq
	req.Flags = flags
	copy(req.Name[:], name)
	if err := ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
		return "", err
	}
	return strings.Trim(string(req.Name[:]), "\x00"), nil
}

// tsoOffloadFlags are the TUN_F_* bits we ask the kernel to enable when a
// TSO-capable TUN is available. CSUM is required as a prerequisite for TSO.
// TSO_ECN tells the kernel we propagate ECN correctly through coalesce and
// segmentation, so it can deliver superpackets whose seed has CWR/ECE set
// or whose IP-level codepoint is CE.
const tsoOffloadFlags = unix.TUN_F_CSUM | unix.TUN_F_TSO4 | unix.TUN_F_TSO6 | unix.TUN_F_TSO_ECN

// usoOffloadFlags adds UDP Segmentation Offload to tsoOffloadFlags. Requires
// Linux ≥ 6.2; older kernels reject it and we fall back to TCP-only TSO via
// tsoOffloadFlags.
const usoOffloadFlags = tsoOffloadFlags | unix.TUN_F_USO4 | unix.TUN_F_USO6

// offloadUSOEnabled reports whether the negotiated offload mask includes UDP
// Segmentation Offload. It is the single source of truth for the usoEnabled
// capability surfaced by each queue, so the mask stored on the tun and the USO
// bit reported to coalescers can never drift apart.
func offloadUSOEnabled(offloadFlags uint) bool {
	return offloadFlags&(unix.TUN_F_USO4|unix.TUN_F_USO6) != 0
}

func newTun(c *config.C, l *slog.Logger, vpnNetworks []netip.Prefix, multiqueue bool) (*tun, error) {
	baseFlags := uint16(unix.IFF_TUN | unix.IFF_NO_PI)
	if multiqueue {
		baseFlags |= unix.IFF_MULTI_QUEUE
	}
	nameStr := c.GetString("tun.dev", "")

	// First try to enable IFF_VNET_HDR via TUNSETIFF and negotiate TUN_F_*
	// offloads via TUNSETOFFLOAD so we can receive TSO/USO superpackets.
	// We try TSO+USO first, fall back to TSO-only on kernels without USO
	// (Linux < 6.2), and finally give up on virtio headers entirely and
	// reopen as a plain TUN if neither offload mask is accepted.
	fd, err := openTunDev()
	if err != nil {
		return nil, err
	}
	vnetHdr := true
	// offloadFlags is the exact TUN_F_* mask the kernel accepted. We remember
	// it (rather than a plain bool) so NewMultiQueueReader can replay the
	// identical device-wide mask on added queues instead of downgrading them.
	var offloadFlags uint
	name, err := tunSetIff(fd, nameStr, baseFlags|unix.IFF_VNET_HDR)
	if err != nil {
		_ = unix.Close(fd)
		vnetHdr = false
	} else {
		// Try TSO+USO first. On kernels without USO support (Linux < 6.2)
		// the ioctl returns EINVAL; fall back to the TCP-only mask before
		// giving up on VNET_HDR entirely.
		if err = ioctl(uintptr(fd), unix.TUNSETOFFLOAD, uintptr(usoOffloadFlags)); err == nil {
			offloadFlags = usoOffloadFlags
		} else if err = ioctl(uintptr(fd), unix.TUNSETOFFLOAD, uintptr(tsoOffloadFlags)); err == nil {
			offloadFlags = tsoOffloadFlags
		} else {
			l.Warn("Failed to enable TUN offload (TSO); proceeding without virtio headers", "error", err)
			_ = unix.Close(fd)
			vnetHdr = false
		}
	}

	if !vnetHdr {
		fd, err = openTunDev()
		if err != nil {
			return nil, err
		}
		name, err = tunSetIff(fd, nameStr, baseFlags)
		if err != nil {
			_ = unix.Close(fd)
			return nil, &NameError{Name: nameStr, Underlying: err}
		}
	}

	if vnetHdr {
		l.Info("TUN offload enabled", "tso", true, "uso", offloadUSOEnabled(offloadFlags))
	}

	t, err := newTunGeneric(c, l, fd, vnetHdr, offloadFlags, vpnNetworks)
	if err != nil {
		return nil, err
	}

	t.Device = name

	return t, nil
}

// newTunGeneric does all the stuff common to different tun initialization
// paths. It will close your files on error. offloadFlags is the TUN_F_* mask
// newTun negotiated (0 when vnetHdr is off); the queues' USO capability is
// derived from it so it can never disagree with the mask we replay on added
// multiqueue readers.
func newTunGeneric(c *config.C, l *slog.Logger, fd int, vnetHdr bool, offloadFlags uint, vpnNetworks []netip.Prefix) (*tun, error) {
	var qs tio.QueueSet
	var err error
	if vnetHdr {
		qs, err = tio.NewOffloadQueueSet(offloadUSOEnabled(offloadFlags))
	} else {
		qs, err = tio.NewPollQueueSet()
	}

	if err != nil {
		_ = unix.Close(fd)
		return nil, err
	}
	err = qs.Add(fd)
	if err != nil {
		_ = unix.Close(fd)
		return nil, err
	}

	t := &tun{
		readers:                   qs,
		closeLock:                 sync.Mutex{},
		vnetHdr:                   vnetHdr,
		offloadFlags:              offloadFlags,
		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),
		routeFeatureECN:           c.GetBool("tunnels.ecn", true),
		routesFromSystem:          map[netip.Prefix]routing.Gateways{},
		l:                         l,
	}

	if err = t.reload(c, true); err != nil {
		_ = t.Close()
		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)
		}
	})

	return t, nil
}

func (t *tun) 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
	}

	// Bring along any routes learned from the system route table on reload
	t.routesFromSystemLock.Lock()
	for dst, gw := range t.routesFromSystem {
		routeTree.Insert(dst, gw)
	}
	t.routesFromSystemLock.Unlock()

	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.Info("Set max MTU", "mtu", t.MaxMTU, "oldMTU", oldMaxMTU)
		}

		if oldDefaultMTU != newDefaultMTU {
			for i := range t.vpnNetworks {
				err := t.setDefaultRoute(t.vpnNetworks[i])
				if err != nil {
					t.l.Warn(err.Error())
				} else {
					t.l.Info("Set default MTU", "mtu", t.DefaultMTU, "oldMTU", oldDefaultMTU)
				}
			}
		}

		// 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
}

func (t *tun) SupportsMultiqueue() bool {
	return true
}

func (t *tun) NewMultiQueueReader() error {
	t.closeLock.Lock()
	defer t.closeLock.Unlock()

	fd, err := unix.Open("/dev/net/tun", os.O_RDWR, 0)
	if err != nil {
		return err
	}

	flags := uint16(unix.IFF_TUN | unix.IFF_NO_PI | unix.IFF_MULTI_QUEUE)
	if t.vnetHdr {
		flags |= unix.IFF_VNET_HDR
	}
	if _, err = tunSetIff(fd, t.Device, flags); err != nil {
		_ = unix.Close(fd)
		return err
	}

	if t.vnetHdr {
		// Replay the exact mask newTun negotiated. TUNSETOFFLOAD is
		// device-wide, so issuing the TSO-only mask here would disable USO
		// for every queue (including queue 0) on kernels where newTun
		// successfully enabled it, while the queues keep advertising USO.
		if err = ioctl(uintptr(fd), unix.TUNSETOFFLOAD, uintptr(t.offloadFlags)); err != nil {
			_ = unix.Close(fd)
			return fmt.Errorf("failed to enable offload on multiqueue tun fd: %w", err)
		}
	}

	err = t.readers.Add(fd)
	if err != nil {
		_ = unix.Close(fd)
		return err
	}

	return nil
}

func (t *tun) RoutesFor(ip netip.Addr) routing.Gateways {
	r, _ := t.routeTree.Load().Lookup(ip)
	return r
}

func (t *tun) deviceBytes() (o [16]byte) {
	for i, c := range t.Device {
		o[i] = byte(c)
	}
	return
}

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.Error("failed to remove address from tun address list", "error", err)
		} else {
			t.l.Info("removed address not listed in cert(s)", "removed", al[i].String())
		}
	}

	return nil
}

func (t *tun) Activate() error {
	devName := t.deviceBytes()

	if t.useSystemRoutes {
		t.watchRoutes()
	}

	s, err := unix.Socket(
		unix.AF_INET, //because everything we use t.ioctlFd for is address family independent, this is fine
		unix.SOCK_DGRAM,
		unix.IPPROTO_IP,
	)
	if err != nil {
		return err
	}
	t.ioctlFd = uintptr(s)

	// Set the device name
	ifrf := ifReq{Name: devName}
	if err = ioctl(t.ioctlFd, unix.SIOCGIFFLAGS, uintptr(unsafe.Pointer(&ifrf))); err != nil {
		return fmt.Errorf("failed to set tun device name: %s", err)
	}

	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

	// Setup our default MTU
	t.setMTU()

	// Set the transmit queue length
	ifrq := ifreqQLEN{Name: devName, Value: int32(t.TXQueueLen)}
	if err = ioctl(t.ioctlFd, unix.SIOCSIFTXQLEN, uintptr(unsafe.Pointer(&ifrq))); err != nil {
		// If we can't set the queue length nebula will still work but it may lead to packet loss
		t.l.Error("Failed to set tun tx queue length", "error", err)
	}

	const modeNone = 1
	if err = netlink.LinkSetIP6AddrGenMode(link, modeNone); err != nil {
		t.l.Warn("Failed to disable link local address generation", "error", err)
	}

	if err = t.addIPs(link); err != nil {
		return err
	}

	// Bring up the interface
	ifrf.Flags = ifrf.Flags | unix.IFF_UP
	if err = ioctl(t.ioctlFd, unix.SIOCSIFFLAGS, uintptr(unsafe.Pointer(&ifrf))); err != nil {
		return fmt.Errorf("failed to bring the tun device up: %s", err)
	}

	//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)
		}
	}

	// Set the routes
	if err = t.addRoutes(false); err != nil {
		return err
	}

	// 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.Error("Failed to set tun mtu", "error", err)
	}
}

func (t *tun) setDefaultRoute(cidr netip.Prefix) error {
	dr := &net.IPNet{
		IP:   cidr.Masked().Addr().AsSlice(),
		Mask: net.CIDRMask(cidr.Bits(), cidr.Addr().BitLen()),
	}

	nr := netlink.Route{
		LinkIndex: t.deviceIndex,
		Dst:       dr,
		MTU:       t.DefaultMTU,
		AdvMSS:    t.advMSS(Route{}),
		Scope:     unix.RT_SCOPE_LINK,
		Src:       net.IP(cidr.Addr().AsSlice()),
		Protocol:  unix.RTPROT_KERNEL,
		Table:     unix.RT_TABLE_MAIN,
		Type:      unix.RTN_UNICAST,
	}
	// Match the metric the kernel uses for its auto-installed connected
	// route, so RouteReplace overwrites it in place instead of adding a
	// second route at a worse metric. IPv6 connected routes are installed
	// at metric 256 (IP6_RT_PRIO_KERN); IPv4 uses 0. Without this, the
	// kernel route wins lookups and our MTU / AdvMSS / Features never
	// apply on v6.
	if cidr.Addr().Is6() {
		nr.Priority = 256
	}
	if t.routeFeatureECN {
		nr.Features |= unix.RTAX_FEATURE_ECN
	}
	err := netlink.RouteReplace(&nr)
	if err != nil {
		t.l.Warn("Failed to set default route MTU, retrying", "error", err, "cidr", cidr)
		//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.Warn("Failed to set default route MTU, retrying",
					"error", err,
					"cidr", cidr,
					"mtu", t.DefaultMTU,
				)
			}
		}
		if err != nil {
			return fmt.Errorf("failed to set mtu %v on the default route %v; %v", t.DefaultMTU, dr, err)
		}
	}

	return nil
}

func (t *tun) addRoutes(logErrors bool) error {
	// Path routes
	routes := *t.Routes.Load()
	for _, r := range routes {
		if !r.Install {
			continue
		}

		dr := &net.IPNet{
			IP:   r.Cidr.Masked().Addr().AsSlice(),
			Mask: net.CIDRMask(r.Cidr.Bits(), r.Cidr.Addr().BitLen()),
		}

		nr := netlink.Route{
			LinkIndex: t.deviceIndex,
			Dst:       dr,
			MTU:       r.MTU,
			AdvMSS:    t.advMSS(r),
			Scope:     unix.RT_SCOPE_LINK,
		}

		if r.Metric > 0 {
			nr.Priority = r.Metric
		}
		if t.routeFeatureECN {
			nr.Features |= unix.RTAX_FEATURE_ECN
		}

		err := netlink.RouteReplace(&nr)
		if err != nil {
			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.Info("Added route", "route", r)
		}
	}

	return nil
}

func (t *tun) removeRoutes(routes []Route) {
	for _, r := range routes {
		if !r.Install {
			continue
		}

		dr := &net.IPNet{
			IP:   r.Cidr.Masked().Addr().AsSlice(),
			Mask: net.CIDRMask(r.Cidr.Bits(), r.Cidr.Addr().BitLen()),
		}

		nr := netlink.Route{
			LinkIndex: t.deviceIndex,
			Dst:       dr,
			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.Error("Failed to remove route", "error", err, "route", r)
		} else {
			t.l.Info("Removed route", "route", r)
		}
	}
}

func (t *tun) Name() string {
	return t.Device
}

func (t *tun) advMSS(r Route) int {
	mtu := r.MTU
	if r.MTU == 0 {
		mtu = t.DefaultMTU
	}

	// We only need to set advmss if the route MTU does not match the device MTU
	if mtu != t.MaxMTU {
		return mtu - 40
	}
	return 0
}

func (t *tun) watchRoutes() {
	rch := make(chan netlink.RouteUpdate)
	doneChan := make(chan struct{})

	netlinkOptions := netlink.RouteSubscribeOptions{
		ReceiveBufferSize:      t.useSystemRoutesBufferSize,
		ReceiveBufferForceSize: t.useSystemRoutesBufferSize != 0,
		ErrorCallback:          func(e error) { t.l.Error("netlink error", "error", e) },
	}

	if err := netlink.RouteSubscribeWithOptions(rch, doneChan, netlinkOptions); err != nil {
		t.l.Error("failed to subscribe to system route changes", "error", err)
		return
	}

	t.routeChan = doneChan

	go func() {
		for {
			select {
			case r, ok := <-rch:
				if ok {
					t.updateRoutes(r)
				} else {
					// may be should do something here as
					// netlink stops sending updates
					return
				}
			case <-doneChan:
				// netlink.RouteSubscriber will close the rch for us
				return
			}
		}
	}()
}

func (t *tun) isGatewayInVpnNetworks(gwAddr netip.Addr) bool {
	withinNetworks := false
	for i := range t.vpnNetworks {
		if t.vpnNetworks[i].Contains(gwAddr) {
			withinNetworks = true
			break
		}
	}

	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.Error("Ignoring route update: failed to get link by name", "deviceName", t.Device)
		return gateways
	}

	// If this route is relevant to our interface and there is a gateway then add it
	if r.LinkIndex == link.Attrs().Index {
		gwAddr, ok := getGatewayAddr(r.Gw, r.Via)
		if ok {
			if t.isGatewayInVpnNetworks(gwAddr) {
				gateways = append(gateways, routing.NewGateway(gwAddr, 1))
			} else {
				// Gateway isn't in our overlay network, ignore
				t.l.Debug("Ignoring route update, gateway is not in our network", "route", r)
			}
		} else {
			t.l.Debug("Ignoring route update, invalid gateway or via address", "route", r)
		}
	}

	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 {
			gwAddr, ok := getGatewayAddr(p.Gw, p.Via)
			if ok {
				if t.isGatewayInVpnNetworks(gwAddr) {
					gateways = append(gateways, routing.NewGateway(gwAddr, p.Hops+1))
				} else {
					// Gateway isn't in our overlay network, ignore
					t.l.Debug("Ignoring route update, gateway is not in our network", "route", r)
				}
			} else {
				t.l.Debug("Ignoring route update, invalid gateway or via address", "route", r)
			}
		}
	}

	routing.CalculateBucketsForGateways(gateways)
	return gateways
}

func getGatewayAddr(gw net.IP, via netlink.Destination) (netip.Addr, bool) {
	// Try to use the old RTA_GATEWAY first
	gwAddr, ok := netip.AddrFromSlice(gw)
	if !ok {
		// Fallback to the new RTA_VIA
		rVia, ok := via.(*netlink.Via)
		if ok {
			gwAddr, ok = netip.AddrFromSlice(rVia.Addr)
		}
	}

	if gwAddr.IsValid() {
		gwAddr = gwAddr.Unmap()
		return gwAddr, true
	}

	return netip.Addr{}, false
}

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.Debug("Ignoring route update, no gateways", "route", r)
		return
	}

	if r.Dst == nil {
		t.l.Debug("Ignoring route update, no destination address", "route", r)
		return
	}

	dstAddr, ok := netip.AddrFromSlice(r.Dst.IP)
	if !ok {
		t.l.Debug("Ignoring route update, invalid destination address", "route", r)
		return
	}

	ones, _ := r.Dst.Mask.Size()
	dst := netip.PrefixFrom(dstAddr, ones)

	newTree := t.routeTree.Load().Clone()

	t.routesFromSystemLock.Lock()
	if r.Type == unix.RTM_NEWROUTE {
		t.l.Info("Adding route", "destination", dst, "via", gateways)
		t.routesFromSystem[dst] = gateways
		newTree.Insert(dst, gateways)

	} else {
		t.l.Info("Removing route", "destination", dst, "via", gateways)
		delete(t.routesFromSystem, dst)
		newTree.Delete(dst)
	}
	t.routesFromSystemLock.Unlock()
	t.routeTree.Store(newTree)
}

func (t *tun) Readers() []tio.Queue {
	return t.readers.Queues()
}

func (t *tun) Close() error {
	t.closeLock.Lock()
	defer t.closeLock.Unlock()

	if t.routeChan != nil {
		close(t.routeChan)
		t.routeChan = nil
	}

	if t.ioctlFd > 0 {
		_ = unix.Close(int(t.ioctlFd))
		t.ioctlFd = 0
	}

	return t.readers.Close()
}
