mirror of https://github.com/slackhq/nebula.git
279 lines
6.4 KiB
Go
279 lines
6.4 KiB
Go
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()
|
|
}
|