mirror of
https://github.com/slackhq/nebula.git
synced 2025-11-22 08:24:25 +01:00
209 lines
4.7 KiB
Go
209 lines
4.7 KiB
Go
//go:build windows && !e2e_testing
|
|
// +build windows,!e2e_testing
|
|
|
|
package overlay
|
|
|
|
import (
|
|
"crypto"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"net/netip"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/slackhq/nebula/config"
|
|
"github.com/slackhq/nebula/util"
|
|
"golang.org/x/sys/windows"
|
|
wgtun "golang.zx2c4.com/wireguard/tun"
|
|
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
|
)
|
|
|
|
const tunGUIDLabel = "Fixed Nebula Windows GUID v1"
|
|
|
|
type tun struct {
|
|
luid windows.LUID
|
|
}
|
|
|
|
func newTunFromFd(_ *config.C, _ *logrus.Logger, _ int, _ []netip.Prefix) (*wgTun, error) {
|
|
return nil, fmt.Errorf("newTunFromFd not supported in Windows")
|
|
}
|
|
|
|
func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (*wgTun, error) {
|
|
deviceName := c.GetString("tun.dev", "Nebula")
|
|
mtu := c.GetInt("tun.mtu", DefaultMTU)
|
|
|
|
// 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
|
|
actualName, err := tunDevice.Name()
|
|
if err != nil {
|
|
tunDevice.Close()
|
|
return nil, fmt.Errorf("failed to get TUN device name: %w", err)
|
|
}
|
|
|
|
t := &wgTun{
|
|
tunDevice: tunDevice,
|
|
vpnNetworks: vpnNetworks,
|
|
MaxMTU: mtu,
|
|
DefaultMTU: mtu,
|
|
l: l,
|
|
}
|
|
|
|
// Create Windows-specific route manager
|
|
rm := &tun{}
|
|
|
|
// Get LUID from the device name
|
|
luid, err := winipcfg.LUIDFromAlias(actualName)
|
|
if err != nil {
|
|
tunDevice.Close()
|
|
return nil, fmt.Errorf("failed to get LUID: %w", err)
|
|
}
|
|
rm.luid = luid
|
|
t.routeManager = rm
|
|
|
|
err = t.reload(c, true)
|
|
if err != nil {
|
|
tunDevice.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)
|
|
}
|
|
})
|
|
|
|
l.WithField("name", actualName).Info("Created WireGuard TUN device")
|
|
|
|
return t, nil
|
|
}
|
|
|
|
func (rm *tun) Activate(t *wgTun) error {
|
|
// Set MTU
|
|
err := rm.setMTU(t, t.MaxMTU)
|
|
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
|
|
}
|
|
}
|
|
|
|
// Add routes
|
|
if err := rm.AddRoutes(t, false); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (rm *tun) SetMTU(t *wgTun, mtu int) {
|
|
if err := rm.setMTU(t, mtu); err != nil {
|
|
t.l.WithError(err).Error("Failed to set MTU")
|
|
}
|
|
}
|
|
|
|
func (rm *tun) setMTU(t *wgTun, mtu int) error {
|
|
// Set MTU using winipcfg
|
|
return rm.luid.SetIPInterfaceMTU(uint32(mtu))
|
|
}
|
|
|
|
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()
|
|
for _, r := range routes {
|
|
if !r.Install {
|
|
continue
|
|
}
|
|
|
|
route := winipcfg.RouteData{
|
|
Destination: r.Cidr,
|
|
Metric: uint32(r.Metric),
|
|
}
|
|
|
|
if r.MTU > 0 {
|
|
// Windows route MTU is not directly supported
|
|
t.l.WithField("route", r).Debug("Route MTU is not supported on Windows")
|
|
}
|
|
|
|
err := rm.luid.AddRoute(route.Destination, route.Destination.Addr(), route.Metric)
|
|
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.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.luid.DeleteRoute(r.Cidr, r.Cidr.Addr())
|
|
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) {
|
|
// 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
|
|
err := rm.luid.AddIPAddress(network)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to add IP address %s: %w", network, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// generateGUIDByDeviceName generates a GUID based on the device name
|
|
func generateGUIDByDeviceName(deviceName string) (*windows.GUID, error) {
|
|
// Hash the device name to create a deterministic GUID
|
|
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
|
|
}
|