package overlay import ( "crypto" "fmt" "io" "net" "net/netip" "sync/atomic" "unsafe" "github.com/sirupsen/logrus" "github.com/slackhq/nebula/cidr" "github.com/slackhq/nebula/config" "github.com/slackhq/nebula/iputil" "github.com/slackhq/nebula/util" "github.com/slackhq/nebula/wintun" "golang.org/x/sys/windows" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" ) const tunGUIDLabel = "Fixed Nebula Windows GUID v1" type winTun struct { Device string cidr *net.IPNet prefix netip.Prefix MTU int Routes atomic.Pointer[[]Route] routeTree atomic.Pointer[cidr.Tree4[iputil.VpnIp]] l *logrus.Logger tun *wintun.NativeTun } 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 newWinTun(c *config.C, l *logrus.Logger, cidr *net.IPNet, _ bool) (*winTun, error) { deviceName := c.GetString("tun.dev", "") guid, err := generateGUIDByDeviceName(deviceName) if err != nil { return nil, fmt.Errorf("generate GUID failed: %w", err) } prefix, err := iputil.ToNetIpPrefix(*cidr) if err != nil { return nil, err } t := &winTun{ Device: deviceName, cidr: cidr, prefix: prefix, MTU: c.GetInt("tun.mtu", DefaultMTU), l: l, } err = t.reload(c, true) if err != nil { 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) { err := t.reload(c, false) if err != nil { util.LogWithContextIfNeeded("failed to reload tun device", err, t.l) } }) return t, nil } func (t *winTun) reload(c *config.C, initial bool) error { change, routes, err := getAllRoutesFromConfig(c, t.cidr, 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 *winTun) Activate() error { luid := winipcfg.LUID(t.tun.LUID()) err := luid.SetIPAddresses([]netip.Prefix{t.prefix}) if err != nil { return fmt.Errorf("failed to set address: %w", err) } err = t.addRoutes(false) if err != nil { return err } return nil } func (t *winTun) addRoutes(logErrors bool) error { luid := winipcfg.LUID(t.tun.LUID()) routes := *t.Routes.Load() foundDefault4 := false for _, r := range routes { if r.Via == nil || !r.Install { // We don't allow route MTUs so only install routes with a via continue } prefix, err := iputil.ToNetIpPrefix(*r.Cidr) if err != nil { retErr := util.NewContextualError("Failed to parse cidr to netip prefix, ignoring route", map[string]interface{}{"route": r}, err) if logErrors { retErr.Log(t.l) continue } else { return retErr } } // Add our unsafe route err = luid.AddRoute(prefix, r.Via.ToNetIpAddr(), uint32(r.Metric)) if err != nil { retErr := util.NewContextualError("Failed to add route", map[string]interface{}{"route": r}, err) if logErrors { retErr.Log(t.l) continue } else { return retErr } } else { t.l.WithField("route", r).Info("Added route") } if !foundDefault4 { if ones, bits := r.Cidr.Mask.Size(); ones == 0 && bits != 0 { 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 } func (t *winTun) removeRoutes(routes []Route) error { luid := winipcfg.LUID(t.tun.LUID()) for _, r := range routes { if !r.Install { continue } prefix, err := iputil.ToNetIpPrefix(*r.Cidr) if err != nil { t.l.WithError(err).WithField("route", r).Info("Failed to convert cidr to netip prefix") continue } err = luid.DeleteRoute(prefix, r.Via.ToNetIpAddr()) 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 (t *winTun) RouteFor(ip iputil.VpnIp) iputil.VpnIp { _, r := t.routeTree.Load().MostSpecificContains(ip) return r } func (t *winTun) Cidr() *net.IPNet { return t.cidr } 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) /* We don't support IPV6 yet _ = luid.FlushRoutes(windows.AF_INET6) _ = luid.FlushIPAddresses(windows.AF_INET6) */ _ = luid.FlushDNS(windows.AF_INET) return t.tun.Close() }