From 70774fc3de52b2f72ba6b771a92ed9ff419ba536 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 20 May 2019 21:45:33 +0100 Subject: [PATCH] Reimplement get/setTunnelRouting, add/removeSourceSubnet, add/removeRoute, getRoutes, getSourceSubnets, make CKR threadsafe --- src/tuntap/admin.go | 209 ++++++++++++++++++++------------------------ src/tuntap/ckr.go | 76 +++++++++++++--- 2 files changed, 158 insertions(+), 127 deletions(-) diff --git a/src/tuntap/admin.go b/src/tuntap/admin.go index 1d0b5876..21c7048d 100644 --- a/src/tuntap/admin.go +++ b/src/tuntap/admin.go @@ -1,6 +1,13 @@ package tuntap -import "github.com/yggdrasil-network/yggdrasil-go/src/admin" +import ( + "encoding/hex" + "errors" + "fmt" + "net" + + "github.com/yggdrasil-network/yggdrasil-go/src/admin" +) func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { a.AddHandler("getTunTap", []string{}, func(in admin.Info) (r admin.Info, e error) { @@ -19,118 +26,94 @@ func (t *TunAdapter) SetupAdminHandlers(a *admin.AdminSocket) { }, nil }) /* - a.AddHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in Info) (Info, error) { - // Set sane defaults - iftapmode := defaults.GetDefaults().DefaultIfTAPMode - ifmtu := defaults.GetDefaults().DefaultIfMTU - // Has TAP mode been specified? - if tap, ok := in["tap_mode"]; ok { - iftapmode = tap.(bool) - } - // Check we have enough params for MTU - if mtu, ok := in["mtu"]; ok { - if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU { - ifmtu = int(in["mtu"].(float64)) - } - } - // Start the TUN adapter - if err := a.startTunWithMTU(in["name"].(string), iftapmode, ifmtu); err != nil { - return Info{}, errors.New("Failed to configure adapter") - } else { - return Info{ - a.core.router.tun.iface.Name(): Info{ - "tap_mode": a.core.router.tun.iface.IsTAP(), - "mtu": ifmtu, - }, - }, nil - } - }) - a.AddHandler("getTunnelRouting", []string{}, func(in Info) (Info, error) { - enabled := false - a.core.router.doAdmin(func() { - enabled = a.core.router.cryptokey.isEnabled() - }) - return Info{"enabled": enabled}, nil - }) - a.AddHandler("setTunnelRouting", []string{"enabled"}, func(in Info) (Info, error) { - enabled := false - if e, ok := in["enabled"].(bool); ok { - enabled = e + // TODO: rewrite this as I'm fairly sure it doesn't work right on many + // platforms anyway, but it may require changes to Water + a.AddHandler("setTunTap", []string{"name", "[tap_mode]", "[mtu]"}, func(in Info) (Info, error) { + // Set sane defaults + iftapmode := defaults.GetDefaults().DefaultIfTAPMode + ifmtu := defaults.GetDefaults().DefaultIfMTU + // Has TAP mode been specified? + if tap, ok := in["tap_mode"]; ok { + iftapmode = tap.(bool) + } + // Check we have enough params for MTU + if mtu, ok := in["mtu"]; ok { + if mtu.(float64) >= 1280 && ifmtu <= defaults.GetDefaults().MaximumIfMTU { + ifmtu = int(in["mtu"].(float64)) } - a.core.router.doAdmin(func() { - a.core.router.cryptokey.setEnabled(enabled) - }) - return Info{"enabled": enabled}, nil - }) - a.AddHandler("addSourceSubnet", []string{"subnet"}, func(in Info) (Info, error) { - var err error - a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.addSourceSubnet(in["subnet"].(string)) - }) - if err == nil { - return Info{"added": []string{in["subnet"].(string)}}, nil - } else { - return Info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet") - } - }) - a.AddHandler("addRoute", []string{"subnet", "box_pub_key"}, func(in Info) (Info, error) { - var err error - a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.addRoute(in["subnet"].(string), in["box_pub_key"].(string)) - }) - if err == nil { - return Info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil - } else { - return Info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route") - } - }) - a.AddHandler("getSourceSubnets", []string{}, func(in Info) (Info, error) { - var subnets []string - a.core.router.doAdmin(func() { - getSourceSubnets := func(snets []net.IPNet) { - for _, subnet := range snets { - subnets = append(subnets, subnet.String()) - } - } - getSourceSubnets(a.core.router.cryptokey.ipv4sources) - getSourceSubnets(a.core.router.cryptokey.ipv6sources) - }) - return Info{"source_subnets": subnets}, nil - }) - a.AddHandler("getRoutes", []string{}, func(in Info) (Info, error) { - routes := make(Info) - a.core.router.doAdmin(func() { - getRoutes := func(ckrs []cryptokey_route) { - for _, ckr := range ckrs { - routes[ckr.subnet.String()] = hex.EncodeToString(ckr.destination[:]) - } - } - getRoutes(a.core.router.cryptokey.ipv4routes) - getRoutes(a.core.router.cryptokey.ipv6routes) - }) - return Info{"routes": routes}, nil - }) - a.AddHandler("removeSourceSubnet", []string{"subnet"}, func(in Info) (Info, error) { - var err error - a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.removeSourceSubnet(in["subnet"].(string)) - }) - if err == nil { - return Info{"removed": []string{in["subnet"].(string)}}, nil - } else { - return Info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet") - } - }) - a.AddHandler("removeRoute", []string{"subnet", "box_pub_key"}, func(in Info) (Info, error) { - var err error - a.core.router.doAdmin(func() { - err = a.core.router.cryptokey.removeRoute(in["subnet"].(string), in["box_pub_key"].(string)) - }) - if err == nil { - return Info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil - } else { - return Info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") - } - }) + } + // Start the TUN adapter + if err := a.startTunWithMTU(in["name"].(string), iftapmode, ifmtu); err != nil { + return Info{}, errors.New("Failed to configure adapter") + } else { + return Info{ + a.core.router.tun.iface.Name(): Info{ + "tap_mode": a.core.router.tun.iface.IsTAP(), + "mtu": ifmtu, + }, + }, nil + } + }) */ + a.AddHandler("getTunnelRouting", []string{}, func(in admin.Info) (admin.Info, error) { + return admin.Info{"enabled": t.ckr.isEnabled()}, nil + }) + a.AddHandler("setTunnelRouting", []string{"enabled"}, func(in admin.Info) (admin.Info, error) { + enabled := false + if e, ok := in["enabled"].(bool); ok { + enabled = e + } + t.ckr.setEnabled(enabled) + return admin.Info{"enabled": enabled}, nil + }) + a.AddHandler("addSourceSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) { + if err := t.ckr.addSourceSubnet(in["subnet"].(string)); err == nil { + return admin.Info{"added": []string{in["subnet"].(string)}}, nil + } else { + return admin.Info{"not_added": []string{in["subnet"].(string)}}, errors.New("Failed to add source subnet") + } + }) + a.AddHandler("addRoute", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) { + if err := t.ckr.addRoute(in["subnet"].(string), in["box_pub_key"].(string)); err == nil { + return admin.Info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil + } else { + return admin.Info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to add route") + } + }) + a.AddHandler("getSourceSubnets", []string{}, func(in admin.Info) (admin.Info, error) { + var subnets []string + getSourceSubnets := func(snets []net.IPNet) { + for _, subnet := range snets { + subnets = append(subnets, subnet.String()) + } + } + getSourceSubnets(t.ckr.ipv4sources) + getSourceSubnets(t.ckr.ipv6sources) + return admin.Info{"source_subnets": subnets}, nil + }) + a.AddHandler("getRoutes", []string{}, func(in admin.Info) (admin.Info, error) { + routes := make(admin.Info) + getRoutes := func(ckrs []cryptokey_route) { + for _, ckr := range ckrs { + routes[ckr.subnet.String()] = hex.EncodeToString(ckr.destination[:]) + } + } + getRoutes(t.ckr.ipv4routes) + getRoutes(t.ckr.ipv6routes) + return admin.Info{"routes": routes}, nil + }) + a.AddHandler("removeSourceSubnet", []string{"subnet"}, func(in admin.Info) (admin.Info, error) { + if err := t.ckr.removeSourceSubnet(in["subnet"].(string)); err == nil { + return admin.Info{"removed": []string{in["subnet"].(string)}}, nil + } else { + return admin.Info{"not_removed": []string{in["subnet"].(string)}}, errors.New("Failed to remove source subnet") + } + }) + a.AddHandler("removeRoute", []string{"subnet", "box_pub_key"}, func(in admin.Info) (admin.Info, error) { + if err := t.ckr.removeRoute(in["subnet"].(string), in["box_pub_key"].(string)); err == nil { + return admin.Info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, nil + } else { + return admin.Info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["box_pub_key"].(string))}}, errors.New("Failed to remove route") + } + }) } diff --git a/src/tuntap/ckr.go b/src/tuntap/ckr.go index 971f0a35..c996c397 100644 --- a/src/tuntap/ckr.go +++ b/src/tuntap/ckr.go @@ -7,6 +7,8 @@ import ( "fmt" "net" "sort" + "sync" + "sync/atomic" "github.com/yggdrasil-network/yggdrasil-go/src/address" "github.com/yggdrasil-network/yggdrasil-go/src/crypto" @@ -16,15 +18,18 @@ import ( // allow traffic for non-Yggdrasil ranges to be routed over Yggdrasil. type cryptokey struct { - tun *TunAdapter - enabled bool - reconfigure chan chan error - ipv4routes []cryptokey_route - ipv6routes []cryptokey_route - ipv4cache map[address.Address]cryptokey_route - ipv6cache map[address.Address]cryptokey_route - ipv4sources []net.IPNet - ipv6sources []net.IPNet + tun *TunAdapter + enabled atomic.Value // bool + reconfigure chan chan error + ipv4routes []cryptokey_route + ipv6routes []cryptokey_route + ipv4cache map[address.Address]cryptokey_route + ipv6cache map[address.Address]cryptokey_route + ipv4sources []net.IPNet + ipv6sources []net.IPNet + mutexroutes sync.RWMutex + mutexcaches sync.RWMutex + mutexsources sync.RWMutex } type cryptokey_route struct { @@ -58,8 +63,10 @@ func (c *cryptokey) configure() error { c.setEnabled(c.tun.config.Current.TunnelRouting.Enable) // Clear out existing routes + c.mutexroutes.Lock() c.ipv6routes = make([]cryptokey_route, 0) c.ipv4routes = make([]cryptokey_route, 0) + c.mutexroutes.Unlock() // Add IPv6 routes for ipv6, pubkey := range c.tun.config.Current.TunnelRouting.IPv6Destinations { @@ -76,8 +83,10 @@ func (c *cryptokey) configure() error { } // Clear out existing sources + c.mutexsources.Lock() c.ipv6sources = make([]net.IPNet, 0) c.ipv4sources = make([]net.IPNet, 0) + c.mutexsources.Unlock() // Add IPv6 sources c.ipv6sources = make([]net.IPNet, 0) @@ -96,26 +105,31 @@ func (c *cryptokey) configure() error { } // Wipe the caches + c.mutexcaches.Lock() c.ipv4cache = make(map[address.Address]cryptokey_route, 0) c.ipv6cache = make(map[address.Address]cryptokey_route, 0) + c.mutexcaches.Unlock() return nil } // Enable or disable crypto-key routing. func (c *cryptokey) setEnabled(enabled bool) { - c.enabled = enabled + c.enabled.Store(true) } // Check if crypto-key routing is enabled. func (c *cryptokey) isEnabled() bool { - return c.enabled + return c.enabled.Load().(bool) } // Check whether the given address (with the address length specified in bytes) // matches either the current node's address, the node's routed subnet or the // list of subnets specified in IPv4Sources/IPv6Sources. func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool { + c.mutexsources.RLock() + defer c.mutexsources.RUnlock() + ip := net.IP(addr[:addrlen]) if addrlen == net.IPv6len { @@ -158,6 +172,9 @@ func (c *cryptokey) isValidSource(addr address.Address, addrlen int) bool { // Adds a source subnet, which allows traffic with these source addresses to // be tunnelled using crypto-key routing. func (c *cryptokey) addSourceSubnet(cidr string) error { + c.mutexsources.Lock() + defer c.mutexsources.Unlock() + // Is the CIDR we've been given valid? _, ipnet, err := net.ParseCIDR(cidr) if err != nil { @@ -195,6 +212,11 @@ func (c *cryptokey) addSourceSubnet(cidr string) error { // Adds a destination route for the given CIDR to be tunnelled to the node // with the given BoxPubKey. func (c *cryptokey) addRoute(cidr string, dest string) error { + c.mutexroutes.Lock() + c.mutexcaches.Lock() + defer c.mutexroutes.Unlock() + defer c.mutexcaches.Unlock() + // Is the CIDR we've been given valid? ipaddr, ipnet, err := net.ParseCIDR(cidr) if err != nil { @@ -269,6 +291,8 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { // length specified in bytes) from the crypto-key routing table. An error is // returned if the address is not suitable or no route was found. func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (crypto.BoxPubKey, error) { + c.mutexcaches.RLock() + // Check if the address is a valid Yggdrasil address - if so it // is exempt from all CKR checking if addr.IsValid() { @@ -281,10 +305,8 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c // Check if the prefix is IPv4 or IPv6 if addrlen == net.IPv6len { - routingtable = &c.ipv6routes routingcache = &c.ipv6cache } else if addrlen == net.IPv4len { - routingtable = &c.ipv4routes routingcache = &c.ipv4cache } else { return crypto.BoxPubKey{}, errors.New("Unexpected prefix size") @@ -292,9 +314,24 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c // Check if there's a cache entry for this addr if route, ok := (*routingcache)[addr]; ok { + c.mutexcaches.RUnlock() return route.destination, nil } + c.mutexcaches.RUnlock() + + c.mutexroutes.RLock() + defer c.mutexroutes.RUnlock() + + // Check if the prefix is IPv4 or IPv6 + if addrlen == net.IPv6len { + routingtable = &c.ipv6routes + } else if addrlen == net.IPv4len { + routingtable = &c.ipv4routes + } else { + return crypto.BoxPubKey{}, errors.New("Unexpected prefix size") + } + // No cache was found - start by converting the address into a net.IP ip := make(net.IP, addrlen) copy(ip[:addrlen], addr[:]) @@ -304,6 +341,9 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c for _, route := range *routingtable { // Does this subnet match the given IP? if route.subnet.Contains(ip) { + c.mutexcaches.Lock() + defer c.mutexcaches.Unlock() + // Check if the routing cache is above a certain size, if it is evict // a random entry so we can make room for this one. We take advantage // of the fact that the iteration order is random here @@ -329,6 +369,9 @@ func (c *cryptokey) getPublicKeyForAddress(addr address.Address, addrlen int) (c // Removes a source subnet, which allows traffic with these source addresses to // be tunnelled using crypto-key routing. func (c *cryptokey) removeSourceSubnet(cidr string) error { + c.mutexsources.Lock() + defer c.mutexsources.Unlock() + // Is the CIDR we've been given valid? _, ipnet, err := net.ParseCIDR(cidr) if err != nil { @@ -364,6 +407,11 @@ func (c *cryptokey) removeSourceSubnet(cidr string) error { // Removes a destination route for the given CIDR to be tunnelled to the node // with the given BoxPubKey. func (c *cryptokey) removeRoute(cidr string, dest string) error { + c.mutexroutes.Lock() + c.mutexcaches.Lock() + defer c.mutexroutes.Unlock() + defer c.mutexcaches.Unlock() + // Is the CIDR we've been given valid? _, ipnet, err := net.ParseCIDR(cidr) if err != nil { @@ -403,7 +451,7 @@ func (c *cryptokey) removeRoute(cidr string, dest string) error { for k := range *routingcache { delete(*routingcache, k) } - c.tun.log.Infoln("Removed CKR destination subnet %s via %s\n", cidr, dest) + c.tun.log.Infof("Removed CKR destination subnet %s via %s\n", cidr, dest) return nil } }