From c6efa3b2ce6f5a15dc5a6732b5e25fff517ea87e Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Tue, 7 Apr 2020 17:56:01 -0400 Subject: [PATCH] add other executable that starts to emulate wireguard running as a service --- wintun-testing/wireguard-wintun/main.go | 218 +++++++++- wintun-testing/wireguard/enable-firewall.go | 30 ++ wintun-testing/wireguard/simple.conf | 9 + wintun-testing/wireguard/watchInterface.go | 437 ++++++++++++++++++++ 4 files changed, 686 insertions(+), 8 deletions(-) create mode 100644 wintun-testing/wireguard/enable-firewall.go create mode 100644 wintun-testing/wireguard/simple.conf create mode 100644 wintun-testing/wireguard/watchInterface.go diff --git a/wintun-testing/wireguard-wintun/main.go b/wintun-testing/wireguard-wintun/main.go index 2a189743..87bcd6e3 100644 --- a/wintun-testing/wireguard-wintun/main.go +++ b/wintun-testing/wireguard-wintun/main.go @@ -1,17 +1,24 @@ package main import ( + "bufio" "fmt" + "log" + "strings" + + "wintun-testing/wireguard" + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/svc" + "golang.org/x/sys/windows/svc/mgr" "golang.zx2c4.com/wireguard/device" "golang.zx2c4.com/wireguard/ipc" "golang.zx2c4.com/wireguard/tun" - "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" - "net" - "os" - "os/signal" - user2 "os/user" - "syscall" + "golang.zx2c4.com/wireguard/windows/conf" + "golang.zx2c4.com/wireguard/windows/elevate" + "golang.zx2c4.com/wireguard/windows/ringlogger" + "golang.zx2c4.com/wireguard/windows/services" + "golang.zx2c4.com/wireguard/windows/version" ) const ( @@ -20,8 +27,176 @@ const ( ) func main(){ + var r <-chan svc.ChangeRequest + var changes chan<- svc.Status + confPath := "c:\\git\\github\\ziti-windows-uwp\\wintun-testing\\simple.conf" + interfaceName := "ZitiTUN" + + serviceError := services.ErrorSuccess + conf, err := conf.LoadFromPath(confPath) + if err != nil { + serviceError = services.ErrorLoadConfiguration + return + } + conf.DeduplicateNetworkEntries() + + logPrefix := fmt.Sprintf("[%s] ", conf.Name) + log.SetPrefix(logPrefix) + + log.Println("Starting", version.UserAgent()) + if m, err := mgr.Connect(); err == nil { + if lockStatus, err := m.LockStatus(); err == nil && lockStatus.IsLocked { + /* If we don't do this, then the Wintun installation will block forever, because + * installing a Wintun device starts a service too. Apparently at boot time, Windows + * 8.1 locks the SCM for each service start, creating a deadlock if we don't announce + * that we're running before starting additional services. + */ + log.Printf("SCM locked for %v by %s, marking service as started", lockStatus.Age, lockStatus.Owner) + changes <- svc.Status{State: svc.Running} + } + m.Disconnect() + } + + + if serviceError != services.ErrorSuccess { + panic(serviceError) + } + + + var watcher *wireguard.InterfaceWatcher + log.Println("Watching network interfaces") + watcher, err = wireguard.WatchInterface() + if err != nil { + serviceError = services.ErrorSetNetConfig + return + } + + log.Println("Resolving DNS names") + uapiConf, err := conf.ToUAPI() + if err != nil { + serviceError = services.ErrorDNSLookup + return + } + + log.Println("Creating Wintun interface") + tunDevice, err := tun.CreateTUN(interfaceName, 0) + defer tunDevice.Close() + if err != nil { + serviceError = services.ErrorCreateWintun + return + } + nativeTun := tunDevice.(*tun.NativeTun) + wintunVersion, ndisVersion, err := nativeTun.Version() + if err != nil { + log.Printf("Warning: unable to determine Wintun version: %v", err) + } else { + log.Printf("Using Wintun/%s (NDIS %s)", wintunVersion, ndisVersion) + } + + log.Println("Enabling firewall rules") + err = wireguard.EnableFirewall(conf, nativeTun) + if err != nil { + log.Printf("ERROR: %v", err) + serviceError = services.ErrorFirewall + return + } + + log.Println("Enabling firewall rules") + err = wireguard.EnableFirewall(conf, nativeTun) + if err != nil { + serviceError = services.ErrorFirewall + return + } + + log.Println("Dropping privileges") + err = elevate.DropAllPrivileges(true) + if err != nil { + serviceError = services.ErrorDropPrivileges + return + } + + log.Println("Creating interface instance") + logOutput := log.New(ringlogger.Global, logPrefix, 0) + logger := &device.Logger{logOutput, logOutput, logOutput} + dev := device.NewDevice(tunDevice, logger) + + log.Println("Setting interface configuration") + uapi, err := ipc.UAPIListen(conf.Name) + if err != nil { + serviceError = services.ErrorUAPIListen + return + } + ipcErr := dev.IpcSetOperation(bufio.NewReader(strings.NewReader(uapiConf))) + if ipcErr != nil { + err = ipcErr + serviceError = services.ErrorDeviceSetConfig + return + } + + log.Println("Bringing peers up") + dev.Up() + + watcher.Configure(dev, conf, nativeTun) + + log.Println("Listening for UAPI requests") + go func() { + for { + conn, err := uapi.Accept() + if err != nil { + continue + } + go dev.IpcHandle(conn) + } + }() + + changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown} + log.Println("Startup complete") + + for { + select { + case c := <-r: + switch c.Cmd { + case svc.Stop, svc.Shutdown: + return + case svc.Interrogate: + changes <- c.CurrentStatus + default: + log.Printf("Unexpected service control request #%d\n", c) + } + case <-dev.Wait(): + return + case e := <-watcher.Errors: + serviceError, err = e.ServiceError, e.Err + return + } + } + + +/* + + + "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" + "golang.zx2c4.com/wireguard/windows/version" + "log" + "net" + "os" + "os/signal" + user2 "os/user" + "strings" + "sync" + "syscall" + + + + + + + + + + fmt.Printf("bootstrapped conf: %v", serviceName) user,err := user2.Current() fmt.Printf("Hi there: %v", user) @@ -54,6 +229,33 @@ func main(){ nativeTunDevice := tunDevice.(*tun.NativeTun) + wintunVersion, ndisVersion, err := nativeTunDevice.Version() + if err != nil { + log.Printf("Warning: unable to determine Wintun version: %v", err) + } else { + log.Printf("Using Wintun/%s (NDIS %s)", wintunVersion, ndisVersion) + } + + log.Println("Dropping privileges") + err = elevate.DropAllPrivileges(true) + if err != nil { + serviceError = services.ErrorDropPrivileges + return + } + + + log.Println("Creating interface instance") + logOutput := log.New(ringlogger.Global, logPrefix, 0) + logger := &device.Logger{logOutput, logOutput, logOutput} + dev := device.NewDevice(wintun, logger) + + + + + + + + luid := winipcfg.LUID(nativeTunDevice.LUID()) //xxx ip, ipnet, _ := net.ParseCIDR("10.82.31.4/24") ip, ipnet, _ := net.ParseCIDR("169.254.100.200/32") @@ -113,10 +315,10 @@ func main(){ case <-errs: case <-device.Wait(): } - +*/ // clean up - logger.Info.Println("Shutting down") +// logger.Info.Println("Shutting down") } diff --git a/wintun-testing/wireguard/enable-firewall.go b/wintun-testing/wireguard/enable-firewall.go new file mode 100644 index 00000000..c1aca30f --- /dev/null +++ b/wintun-testing/wireguard/enable-firewall.go @@ -0,0 +1,30 @@ +package wireguard + +import ( + "golang.zx2c4.com/wireguard/tun" + "golang.zx2c4.com/wireguard/windows/conf" + "golang.zx2c4.com/wireguard/windows/tunnel/firewall" + "log" +) + +func EnableFirewall(conf *conf.Config, tun *tun.NativeTun) error { + restrictAll := false + if len(conf.Peers) == 1 { + nextallowedip: + for _, allowedip := range conf.Peers[0].AllowedIPs { + if allowedip.Cidr == 0 { + for _, b := range allowedip.IP { + if b != 0 { + continue nextallowedip + } + } + restrictAll = true + break + } + } + } + if restrictAll && len(conf.Interface.DNS) == 0 { + log.Println("Warning: no DNS server specified, despite having an allowed IPs of 0.0.0.0/0 or ::/0. There may be connectivity issues.") + } + return firewall.EnableFirewall(tun.LUID(), conf.Interface.DNS, restrictAll) +} diff --git a/wintun-testing/wireguard/simple.conf b/wintun-testing/wireguard/simple.conf new file mode 100644 index 00000000..20e9027f --- /dev/null +++ b/wintun-testing/wireguard/simple.conf @@ -0,0 +1,9 @@ +[Interface] +PrivateKey = 4GZLLHriLdsToVzCloLuQzK2zp3MWyYB5s/6MLB91Ew= +Address = 169.254.100.200/32 + +[Peer] +PublicKey = dPaSKJOyE5aVAof9hJh/NoPLCnbhG2APiGNS918HgRI= +AllowedIPs = 169.254.100.100 +Endpoint = 192.168.1.115:51820 +PersistentKeepalive = 30 diff --git a/wintun-testing/wireguard/watchInterface.go b/wintun-testing/wireguard/watchInterface.go new file mode 100644 index 00000000..d6055bdc --- /dev/null +++ b/wintun-testing/wireguard/watchInterface.go @@ -0,0 +1,437 @@ +package wireguard + +import ( + "bytes" + "golang.org/x/sys/windows" + "golang.zx2c4.com/wireguard/device" + "golang.zx2c4.com/wireguard/tun" + "golang.zx2c4.com/wireguard/windows/conf" + "golang.zx2c4.com/wireguard/windows/services" + "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" + "log" + "net" + "sort" + "sync" + "time" + +) + + +type InterfaceWatcherError struct { + ServiceError services.Error + Err error +} +type InterfaceWatcherEvent struct { + luid winipcfg.LUID + family winipcfg.AddressFamily +} +type InterfaceWatcher struct { + Errors chan InterfaceWatcherError + + device *device.Device + conf *conf.Config + tun *tun.NativeTun + + setupMutex sync.Mutex + interfaceChangeCallback winipcfg.ChangeCallback + changeCallbacks4 []winipcfg.ChangeCallback + changeCallbacks6 []winipcfg.ChangeCallback + storedEvents []InterfaceWatcherEvent +} + +func WatchInterface() (*InterfaceWatcher, error) { + iw := &InterfaceWatcher{ + Errors: make(chan InterfaceWatcherError, 2), + } + var err error + iw.interfaceChangeCallback, err = winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) { + iw.setupMutex.Lock() + defer iw.setupMutex.Unlock() + + if notificationType != winipcfg.MibAddInstance { + return + } + if iw.tun == nil { + iw.storedEvents = append(iw.storedEvents, InterfaceWatcherEvent{iface.InterfaceLUID, iface.Family}) + return + } + if iface.InterfaceLUID != winipcfg.LUID(iw.tun.LUID()) { + return + } + iw.setup(iface.Family) + }) + if err != nil { + return nil, err + } + return iw, nil +} + +func (iw *InterfaceWatcher) setup(family winipcfg.AddressFamily) { + var changeCallbacks *[]winipcfg.ChangeCallback + var ipversion string + if family == windows.AF_INET { + changeCallbacks = &iw.changeCallbacks4 + ipversion = "v4" + } else if family == windows.AF_INET6 { + changeCallbacks = &iw.changeCallbacks6 + ipversion = "v6" + } else { + return + } + if len(*changeCallbacks) != 0 { + for _, cb := range *changeCallbacks { + cb.Unregister() + } + *changeCallbacks = nil + } + var err error + + log.Printf("Monitoring default %s routes", ipversion) + *changeCallbacks, err = monitorDefaultRoutes(family, iw.device, iw.conf.Interface.MTU == 0, hasDefaultRoute(family, iw.conf.Peers), iw.tun) + if err != nil { + iw.Errors <- InterfaceWatcherError{services.ErrorBindSocketsToDefaultRoutes, err} + return + } + + log.Printf("Setting device %s addresses", ipversion) + err = configureInterface(family, iw.conf, iw.tun) + if err != nil { + iw.Errors <- InterfaceWatcherError{services.ErrorSetNetConfig, err} + return + } +} + +func monitorDefaultRoutes(family winipcfg.AddressFamily, device *device.Device, autoMTU bool, blackholeWhenLoop bool, tun *tun.NativeTun) ([]winipcfg.ChangeCallback, error) { + var minMTU uint32 + if family == windows.AF_INET { + minMTU = 576 + } else if family == windows.AF_INET6 { + minMTU = 1280 + } + ourLUID := winipcfg.LUID(tun.LUID()) + lastLUID := winipcfg.LUID(0) + lastIndex := ^uint32(0) + lastMTU := uint32(0) + doIt := func() error { + err := bindSocketRoute(family, device, ourLUID, &lastLUID, &lastIndex, blackholeWhenLoop) + if err != nil { + return err + } + if !autoMTU { + return nil + } + mtu := uint32(0) + if lastLUID != 0 { + iface, err := lastLUID.Interface() + if err != nil { + return err + } + if iface.MTU > 0 { + mtu = iface.MTU + } + } + if mtu > 0 && lastMTU != mtu { + iface, err := ourLUID.IPInterface(family) + if err != nil { + return err + } + iface.NLMTU = mtu - 80 + if iface.NLMTU < minMTU { + iface.NLMTU = minMTU + } + err = iface.Set() + if err != nil { + return err + } + tun.ForceMTU(int(iface.NLMTU)) // TODO: having one MTU for both v4 and v6 kind of breaks the windows model, so right now this just gets the second one which is... bad. + lastMTU = mtu + } + return nil + } + err := doIt() + if err != nil { + return nil, err + } + + firstBurst := time.Time{} + burstMutex := sync.Mutex{} + burstTimer := time.AfterFunc(time.Hour*200, func() { + burstMutex.Lock() + firstBurst = time.Time{} + doIt() + burstMutex.Unlock() + }) + burstTimer.Stop() + bump := func() { + burstMutex.Lock() + burstTimer.Reset(time.Millisecond * 150) + if firstBurst.IsZero() { + firstBurst = time.Now() + } else if time.Since(firstBurst) > time.Second*2 { + firstBurst = time.Time{} + burstTimer.Stop() + doIt() + } + burstMutex.Unlock() + } + + cbr, err := winipcfg.RegisterRouteChangeCallback(func(notificationType winipcfg.MibNotificationType, route *winipcfg.MibIPforwardRow2) { + if route != nil && route.DestinationPrefix.PrefixLength == 0 { + bump() + } + }) + if err != nil { + return nil, err + } + cbi, err := winipcfg.RegisterInterfaceChangeCallback(func(notificationType winipcfg.MibNotificationType, iface *winipcfg.MibIPInterfaceRow) { + if notificationType == winipcfg.MibParameterNotification { + bump() + } + }) + if err != nil { + cbr.Unregister() + return nil, err + } + return []winipcfg.ChangeCallback{cbr, cbi}, nil +} + +func bindSocketRoute(family winipcfg.AddressFamily, device *device.Device, ourLUID winipcfg.LUID, lastLUID *winipcfg.LUID, lastIndex *uint32, blackholeWhenLoop bool) error { + r, err := winipcfg.GetIPForwardTable2(family) + if err != nil { + return err + } + lowestMetric := ^uint32(0) + index := uint32(0) // Zero is "unspecified", which for IP_UNICAST_IF resets the value, which is what we want. + luid := winipcfg.LUID(0) // Hopefully luid zero is unspecified, but hard to find docs saying so. + for i := range r { + if r[i].DestinationPrefix.PrefixLength != 0 || r[i].InterfaceLUID == ourLUID { + continue + } + ifrow, err := r[i].InterfaceLUID.Interface() + if err != nil || ifrow.OperStatus != winipcfg.IfOperStatusUp { + continue + } + if r[i].Metric < lowestMetric { + lowestMetric = r[i].Metric + index = r[i].InterfaceIndex + luid = r[i].InterfaceLUID + } + } + if luid == *lastLUID && index == *lastIndex { + return nil + } + *lastLUID = luid + *lastIndex = index + blackhole := blackholeWhenLoop && index == 0 + if family == windows.AF_INET { + log.Printf("Binding v4 socket to interface %d (blackhole=%v)", index, blackhole) + return device.BindSocketToInterface4(index, blackhole) + } else if family == windows.AF_INET6 { + log.Printf("Binding v6 socket to interface %d (blackhole=%v)", index, blackhole) + return device.BindSocketToInterface6(index, blackhole) + } + return nil +} + + + +func configureInterface(family winipcfg.AddressFamily, conf *conf.Config, tun *tun.NativeTun) error { + luid := winipcfg.LUID(tun.LUID()) + + estimatedRouteCount := 0 + for _, peer := range conf.Peers { + estimatedRouteCount += len(peer.AllowedIPs) + } + routes := make([]winipcfg.RouteData, 0, estimatedRouteCount) + addresses := make([]net.IPNet, len(conf.Interface.Addresses)) + var haveV4Address, haveV6Address bool + for i, addr := range conf.Interface.Addresses { + addresses[i] = addr.IPNet() + if addr.Bits() == 32 { + haveV4Address = true + } else if addr.Bits() == 128 { + haveV6Address = true + } + } + + foundDefault4 := false + foundDefault6 := false + for _, peer := range conf.Peers { + for _, allowedip := range peer.AllowedIPs { + if (allowedip.Bits() == 32 && !haveV4Address) || (allowedip.Bits() == 128 && !haveV6Address) { + continue + } + route := winipcfg.RouteData{ + Destination: allowedip.IPNet(), + Metric: 0, + } + if allowedip.Bits() == 32 { + if allowedip.Cidr == 0 { + foundDefault4 = true + } + route.NextHop = net.IPv4zero + } else if allowedip.Bits() == 128 { + if allowedip.Cidr == 0 { + foundDefault6 = true + } + route.NextHop = net.IPv6zero + } + routes = append(routes, route) + } + } + + err := luid.SetIPAddressesForFamily(family, addresses) + if err == windows.ERROR_OBJECT_ALREADY_EXISTS { + cleanupAddressesOnDisconnectedInterfaces(family, addresses) + err = luid.SetIPAddressesForFamily(family, addresses) + } + if err != nil { + return err + } + + deduplicatedRoutes := make([]*winipcfg.RouteData, 0, len(routes)) + sort.Slice(routes, func(i, j int) bool { + return routes[i].Metric < routes[j].Metric || + bytes.Compare(routes[i].NextHop, routes[j].NextHop) == -1 || + bytes.Compare(routes[i].Destination.IP, routes[j].Destination.IP) == -1 || + bytes.Compare(routes[i].Destination.Mask, routes[j].Destination.Mask) == -1 + }) + for i := 0; i < len(routes); i++ { + if i > 0 && routes[i].Metric == routes[i-1].Metric && + bytes.Equal(routes[i].NextHop, routes[i-1].NextHop) && + bytes.Equal(routes[i].Destination.IP, routes[i-1].Destination.IP) && + bytes.Equal(routes[i].Destination.Mask, routes[i-1].Destination.Mask) { + continue + } + deduplicatedRoutes = append(deduplicatedRoutes, &routes[i]) + } + + err = luid.SetRoutesForFamily(family, deduplicatedRoutes) + if err != nil { + return nil + } + + ipif, err := luid.IPInterface(family) + if err != nil { + return err + } + if conf.Interface.MTU > 0 { + ipif.NLMTU = uint32(conf.Interface.MTU) + tun.ForceMTU(int(ipif.NLMTU)) + } + if family == windows.AF_INET { + if foundDefault4 { + ipif.UseAutomaticMetric = false + ipif.Metric = 0 + } + } else if family == windows.AF_INET6 { + if foundDefault6 { + ipif.UseAutomaticMetric = false + ipif.Metric = 0 + } + ipif.DadTransmits = 0 + ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled + } + err = ipif.Set() + if err != nil { + return err + } + + err = luid.SetDNSForFamily(family, conf.Interface.DNS) + if err != nil { + return err + } + + return nil +} + +func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, addresses []net.IPNet) { + if len(addresses) == 0 { + return + } + includedInAddresses := func(a net.IPNet) bool { + // TODO: this makes the whole algorithm O(n^2). But we can't stick net.IPNet in a Go hashmap. Bummer! + for _, addr := range addresses { + ip := addr.IP + if ip4 := ip.To4(); ip4 != nil { + ip = ip4 + } + mA, _ := addr.Mask.Size() + mB, _ := a.Mask.Size() + if bytes.Equal(ip, a.IP) && mA == mB { + return true + } + } + return false + } + interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagDefault) + if err != nil { + return + } + for _, iface := range interfaces { + if iface.OperStatus == winipcfg.IfOperStatusUp { + continue + } + for address := iface.FirstUnicastAddress; address != nil; address = address.Next { + ip := address.Address.IP() + ipnet := net.IPNet{IP: ip, Mask: net.CIDRMask(int(address.OnLinkPrefixLength), 8*len(ip))} + if includedInAddresses(ipnet) { + log.Printf("Cleaning up stale address %s from interface ā€˜%s’", ipnet.String(), iface.FriendlyName()) + iface.LUID.DeleteIPAddress(ipnet) + } + } + } +} + + +func hasDefaultRoute(family winipcfg.AddressFamily, peers []conf.Peer) bool { + var ( + foundV401 bool + foundV41281 bool + foundV600001 bool + foundV680001 bool + foundV400 bool + foundV600 bool + v40 = [4]byte{} + v60 = [16]byte{} + v48 = [4]byte{0x80} + v68 = [16]byte{0x80} + ) + for _, peer := range peers { + for _, allowedip := range peer.AllowedIPs { + if allowedip.Cidr == 1 && len(allowedip.IP) == 16 && allowedip.IP.Equal(v60[:]) { + foundV600001 = true + } else if allowedip.Cidr == 1 && len(allowedip.IP) == 16 && allowedip.IP.Equal(v68[:]) { + foundV680001 = true + } else if allowedip.Cidr == 1 && len(allowedip.IP) == 4 && allowedip.IP.Equal(v40[:]) { + foundV401 = true + } else if allowedip.Cidr == 1 && len(allowedip.IP) == 4 && allowedip.IP.Equal(v48[:]) { + foundV41281 = true + } else if allowedip.Cidr == 0 && len(allowedip.IP) == 16 && allowedip.IP.Equal(v60[:]) { + foundV600 = true + } else if allowedip.Cidr == 0 && len(allowedip.IP) == 4 && allowedip.IP.Equal(v40[:]) { + foundV400 = true + } + } + } + if family == windows.AF_INET { + return foundV400 || (foundV401 && foundV41281) + } else if family == windows.AF_INET6 { + return foundV600 || (foundV600001 && foundV680001) + } + return false +} + + +func (iw *InterfaceWatcher) Configure(device *device.Device, conf *conf.Config, tun *tun.NativeTun) { + iw.setupMutex.Lock() + defer iw.setupMutex.Unlock() + + iw.device, iw.conf, iw.tun = device, conf, tun + for _, event := range iw.storedEvents { + if event.luid == winipcfg.LUID(iw.tun.LUID()) { + iw.setup(event.family) + } + } + iw.storedEvents = nil +} \ No newline at end of file