github.com/slackhq/nebula@v1.9.0/overlay/tun_water_windows.go (about)

     1  package overlay
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net"
     7  	"os/exec"
     8  	"strconv"
     9  	"sync/atomic"
    10  
    11  	"github.com/sirupsen/logrus"
    12  	"github.com/slackhq/nebula/cidr"
    13  	"github.com/slackhq/nebula/config"
    14  	"github.com/slackhq/nebula/iputil"
    15  	"github.com/slackhq/nebula/util"
    16  	"github.com/songgao/water"
    17  )
    18  
    19  type waterTun struct {
    20  	Device    string
    21  	cidr      *net.IPNet
    22  	MTU       int
    23  	Routes    atomic.Pointer[[]Route]
    24  	routeTree atomic.Pointer[cidr.Tree4[iputil.VpnIp]]
    25  	l         *logrus.Logger
    26  	f         *net.Interface
    27  	*water.Interface
    28  }
    29  
    30  func newWaterTun(c *config.C, l *logrus.Logger, cidr *net.IPNet, _ bool) (*waterTun, error) {
    31  	// NOTE: You cannot set the deviceName under Windows, so you must check tun.Device after calling .Activate()
    32  	t := &waterTun{
    33  		cidr: cidr,
    34  		MTU:  c.GetInt("tun.mtu", DefaultMTU),
    35  		l:    l,
    36  	}
    37  
    38  	err := t.reload(c, true)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  
    43  	c.RegisterReloadCallback(func(c *config.C) {
    44  		err := t.reload(c, false)
    45  		if err != nil {
    46  			util.LogWithContextIfNeeded("failed to reload tun device", err, t.l)
    47  		}
    48  	})
    49  
    50  	return t, nil
    51  }
    52  
    53  func (t *waterTun) Activate() error {
    54  	var err error
    55  	t.Interface, err = water.New(water.Config{
    56  		DeviceType: water.TUN,
    57  		PlatformSpecificParams: water.PlatformSpecificParams{
    58  			ComponentID: "tap0901",
    59  			Network:     t.cidr.String(),
    60  		},
    61  	})
    62  	if err != nil {
    63  		return fmt.Errorf("activate failed: %v", err)
    64  	}
    65  
    66  	t.Device = t.Interface.Name()
    67  
    68  	// TODO use syscalls instead of exec.Command
    69  	err = exec.Command(
    70  		`C:\Windows\System32\netsh.exe`, "interface", "ipv4", "set", "address",
    71  		fmt.Sprintf("name=%s", t.Device),
    72  		"source=static",
    73  		fmt.Sprintf("addr=%s", t.cidr.IP),
    74  		fmt.Sprintf("mask=%s", net.IP(t.cidr.Mask)),
    75  		"gateway=none",
    76  	).Run()
    77  	if err != nil {
    78  		return fmt.Errorf("failed to run 'netsh' to set address: %s", err)
    79  	}
    80  	err = exec.Command(
    81  		`C:\Windows\System32\netsh.exe`, "interface", "ipv4", "set", "interface",
    82  		t.Device,
    83  		fmt.Sprintf("mtu=%d", t.MTU),
    84  	).Run()
    85  	if err != nil {
    86  		return fmt.Errorf("failed to run 'netsh' to set MTU: %s", err)
    87  	}
    88  
    89  	t.f, err = net.InterfaceByName(t.Device)
    90  	if err != nil {
    91  		return fmt.Errorf("failed to find interface named %s: %v", t.Device, err)
    92  	}
    93  
    94  	err = t.addRoutes(false)
    95  	if err != nil {
    96  		return err
    97  	}
    98  
    99  	return nil
   100  }
   101  
   102  func (t *waterTun) reload(c *config.C, initial bool) error {
   103  	change, routes, err := getAllRoutesFromConfig(c, t.cidr, initial)
   104  	if err != nil {
   105  		return err
   106  	}
   107  
   108  	if !initial && !change {
   109  		return nil
   110  	}
   111  
   112  	routeTree, err := makeRouteTree(t.l, routes, false)
   113  	if err != nil {
   114  		return err
   115  	}
   116  
   117  	// Teach nebula how to handle the routes before establishing them in the system table
   118  	oldRoutes := t.Routes.Swap(&routes)
   119  	t.routeTree.Store(routeTree)
   120  
   121  	if !initial {
   122  		// Remove first, if the system removes a wanted route hopefully it will be re-added next
   123  		t.removeRoutes(findRemovedRoutes(routes, *oldRoutes))
   124  
   125  		// Ensure any routes we actually want are installed
   126  		err = t.addRoutes(true)
   127  		if err != nil {
   128  			// Catch any stray logs
   129  			util.LogWithContextIfNeeded("Failed to set routes", err, t.l)
   130  		} else {
   131  			for _, r := range findRemovedRoutes(routes, *oldRoutes) {
   132  				t.l.WithField("route", r).Info("Removed route")
   133  			}
   134  		}
   135  	}
   136  
   137  	return nil
   138  }
   139  
   140  func (t *waterTun) addRoutes(logErrors bool) error {
   141  	// Path routes
   142  	routes := *t.Routes.Load()
   143  	for _, r := range routes {
   144  		if r.Via == nil || !r.Install {
   145  			// We don't allow route MTUs so only install routes with a via
   146  			continue
   147  		}
   148  
   149  		err := exec.Command(
   150  			"C:\\Windows\\System32\\route.exe", "add", r.Cidr.String(), r.Via.String(), "IF", strconv.Itoa(t.f.Index), "METRIC", strconv.Itoa(r.Metric),
   151  		).Run()
   152  
   153  		if err != nil {
   154  			retErr := util.NewContextualError("Failed to add route", map[string]interface{}{"route": r}, err)
   155  			if logErrors {
   156  				retErr.Log(t.l)
   157  			} else {
   158  				return retErr
   159  			}
   160  		} else {
   161  			t.l.WithField("route", r).Info("Added route")
   162  		}
   163  	}
   164  
   165  	return nil
   166  }
   167  
   168  func (t *waterTun) removeRoutes(routes []Route) {
   169  	for _, r := range routes {
   170  		if !r.Install {
   171  			continue
   172  		}
   173  
   174  		err := exec.Command(
   175  			"C:\\Windows\\System32\\route.exe", "delete", r.Cidr.String(), r.Via.String(), "IF", strconv.Itoa(t.f.Index), "METRIC", strconv.Itoa(r.Metric),
   176  		).Run()
   177  		if err != nil {
   178  			t.l.WithError(err).WithField("route", r).Error("Failed to remove route")
   179  		} else {
   180  			t.l.WithField("route", r).Info("Removed route")
   181  		}
   182  	}
   183  }
   184  
   185  func (t *waterTun) RouteFor(ip iputil.VpnIp) iputil.VpnIp {
   186  	_, r := t.routeTree.Load().MostSpecificContains(ip)
   187  	return r
   188  }
   189  
   190  func (t *waterTun) Cidr() *net.IPNet {
   191  	return t.cidr
   192  }
   193  
   194  func (t *waterTun) Name() string {
   195  	return t.Device
   196  }
   197  
   198  func (t *waterTun) Close() error {
   199  	if t.Interface == nil {
   200  		return nil
   201  	}
   202  
   203  	return t.Interface.Close()
   204  }
   205  
   206  func (t *waterTun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
   207  	return nil, fmt.Errorf("TODO: multiqueue not implemented for windows")
   208  }