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 }