diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 42ea8a5..1e85907 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -232,6 +232,76 @@ func (a *admin) init(c *Core, listenaddr string) { }, errors.New("Failed to remove allowed key") } }) + a.addHandler("addSourceSubnet", []string{"subnet"}, func(in admin_info) (admin_info, error) { + var err error + a.core.router.doAdmin(func() { + err = a.core.router.cryptokey.addSourceSubnet(in["subnet"].(string)) + }) + if 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", "destPubKey"}, func(in admin_info) (admin_info, error) { + var err error + a.core.router.doAdmin(func() { + err = a.core.router.cryptokey.addRoute(in["subnet"].(string), in["destPubKey"].(string)) + }) + if err == nil { + return admin_info{"added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["destPubKey"].(string))}}, nil + } else { + return admin_info{"not_added": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["destPubKey"].(string))}}, errors.New("Failed to add route") + } + }) + a.addHandler("getSourceSubnets", []string{}, func(in admin_info) (admin_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 admin_info{"source_subnets": subnets}, nil + }) + a.addHandler("getRoutes", []string{}, func(in admin_info) (admin_info, error) { + var routes []string + a.core.router.doAdmin(func() { + getRoutes := func(ckrs []cryptokey_route) { + for _, ckr := range ckrs { + routes = append(routes, fmt.Sprintf("%s via %s", ckr.subnet.String(), hex.EncodeToString(ckr.destination[:]))) + } + } + getRoutes(a.core.router.cryptokey.ipv4routes) + getRoutes(a.core.router.cryptokey.ipv6routes) + }) + return admin_info{"routes": routes}, nil + }) + a.addHandler("removeSourceSubnet", []string{"subnet"}, func(in admin_info) (admin_info, error) { + var err error + a.core.router.doAdmin(func() { + err = a.core.router.cryptokey.removeSourceSubnet(in["subnet"].(string)) + }) + if 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", "destPubKey"}, func(in admin_info) (admin_info, error) { + var err error + a.core.router.doAdmin(func() { + err = a.core.router.cryptokey.removeRoute(in["subnet"].(string), in["destPubKey"].(string)) + }) + if err == nil { + return admin_info{"removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["destPubKey"].(string))}}, nil + } else { + return admin_info{"not_removed": []string{fmt.Sprintf("%s via %s", in["subnet"].(string), in["destPubKey"].(string))}}, errors.New("Failed to remove route") + } + }) } // start runs the admin API socket to listen for / respond to admin API calls. @@ -386,7 +456,6 @@ func (n *admin_nodeInfo) toString() string { out = append(out, fmt.Sprintf("%v: %v", p.key, p.val)) } return strings.Join(out, ", ") - return fmt.Sprint(*n) } // printInfos returns a newline separated list of strings from admin_nodeInfos, e.g. a printable string of info about all peers. @@ -527,7 +596,7 @@ func (a *admin) getData_getSwitchPeers() []admin_nodeInfo { // getData_getSwitchQueues returns info from Core.switchTable for an queue data. func (a *admin) getData_getSwitchQueues() admin_nodeInfo { var peerInfos admin_nodeInfo - switchTable := a.core.switchTable + switchTable := &a.core.switchTable getSwitchQueues := func() { queues := make([]map[string]interface{}, 0) for k, v := range switchTable.queues.bufs { diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index 2a05471..b73946c 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -25,7 +25,7 @@ type cryptokey struct { type cryptokey_route struct { subnet net.IPNet - destination []byte + destination boxPubKey } // Initialise crypto-key routing. This must be done before any other CKR calls. @@ -171,13 +171,17 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { } } // Decode the public key - if boxPubKey, err := hex.DecodeString(dest); err != nil { + if bpk, err := hex.DecodeString(dest); err != nil { return err + } else if len(bpk) != boxPubKeyLen { + return errors.New(fmt.Sprintf("Incorrect key length for %s", dest)) } else { // Add the new crypto-key route + var key boxPubKey + copy(key[:], bpk) *routingtable = append(*routingtable, cryptokey_route{ subnet: *ipnet, - destination: boxPubKey, + destination: key, }) // Sort so most specific routes are first @@ -196,8 +200,6 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { c.core.log.Println("Added CKR destination subnet", cidr) return nil } - - return errors.New("Unspecified error") } // Looks up the most specific route for the given address (with the address @@ -227,9 +229,7 @@ func (c *cryptokey) getPublicKeyForAddress(addr address, addrlen int) (boxPubKey // Check if there's a cache entry for this addr if route, ok := (*routingcache)[addr]; ok { - var box boxPubKey - copy(box[:boxPubKeyLen], route.destination) - return box, nil + return route.destination, nil } // No cache was found - start by converting the address into a net.IP @@ -245,12 +245,94 @@ func (c *cryptokey) getPublicKeyForAddress(addr address, addrlen int) (boxPubKey (*routingcache)[addr] = route // Return the boxPubKey - var box boxPubKey - copy(box[:boxPubKeyLen], route.destination) - return box, nil + return route.destination, nil } } // No route was found if we got to this point return boxPubKey{}, errors.New(fmt.Sprintf("No route to %s", ip.String())) } + +// 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 { + // Is the CIDR we've been given valid? + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return err + } + + // Get the prefix length and size + _, prefixsize := ipnet.Mask.Size() + + // Build our references to the routing sources + var routingsources *[]net.IPNet + + // Check if the prefix is IPv4 or IPv6 + if prefixsize == net.IPv6len*8 { + routingsources = &c.ipv6sources + } else if prefixsize == net.IPv4len*8 { + routingsources = &c.ipv4sources + } else { + return errors.New("Unexpected prefix size") + } + + // Check if we already have this CIDR + for idx, subnet := range *routingsources { + if subnet.String() == ipnet.String() { + *routingsources = append((*routingsources)[:idx], (*routingsources)[idx+1:]...) + c.core.log.Println("Removed CKR source subnet", cidr) + return nil + } + } + return errors.New("Source subnet not found") +} + +// 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 { + // Is the CIDR we've been given valid? + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return err + } + + // Get the prefix length and size + _, prefixsize := ipnet.Mask.Size() + + // Build our references to the routing table and cache + var routingtable *[]cryptokey_route + var routingcache *map[address]cryptokey_route + + // Check if the prefix is IPv4 or IPv6 + if prefixsize == net.IPv6len*8 { + routingtable = &c.ipv6routes + routingcache = &c.ipv6cache + } else if prefixsize == net.IPv4len*8 { + routingtable = &c.ipv4routes + routingcache = &c.ipv4cache + } else { + return errors.New("Unexpected prefix size") + } + + // Decode the public key + bpk, err := hex.DecodeString(dest) + if err != nil { + return err + } else if len(bpk) != boxPubKeyLen { + return errors.New(fmt.Sprintf("Incorrect key length for %s", dest)) + } + netStr := ipnet.String() + + for idx, route := range *routingtable { + if bytes.Equal(route.destination[:], bpk) && route.subnet.String() == netStr { + *routingtable = append((*routingtable)[:idx], (*routingtable)[idx+1:]...) + for k := range *routingcache { + delete(*routingcache, k) + } + c.core.log.Printf("Removed CKR destination subnet %s via %s\n", cidr, dest) + return nil + } + } + return errors.New(fmt.Sprintf("Route does not exists for %s", cidr)) +} diff --git a/src/yggdrasil/dht.go b/src/yggdrasil/dht.go index 220e291..31acd8a 100644 --- a/src/yggdrasil/dht.go +++ b/src/yggdrasil/dht.go @@ -102,7 +102,6 @@ func (t *dht) insert(info *dhtInfo) { if *info.getNodeID() == t.nodeID { // This shouldn't happen, but don't add it if it does return - panic("FIXME") } info.recv = time.Now() if oldInfo, isIn := t.table[*info.getNodeID()]; isIn { diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 0723d73..41e2863 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -36,15 +36,22 @@ type router struct { core *Core addr address subnet subnet - in <-chan []byte // packets we received from the network, link to peer's "out" - out func([]byte) // packets we're sending to the network, link to peer's "in" - recv chan<- []byte // place where the tun pulls received packets from - send <-chan []byte // place where the tun puts outgoing packets - reset chan struct{} // signal that coords changed (re-init sessions/dht) - admin chan func() // pass a lambda for the admin socket to query stuff + in <-chan []byte // packets we received from the network, link to peer's "out" + out func([]byte) // packets we're sending to the network, link to peer's "in" + toRecv chan router_recvPacket // packets to handle via recvPacket() + recv chan<- []byte // place where the tun pulls received packets from + send <-chan []byte // place where the tun puts outgoing packets + reset chan struct{} // signal that coords changed (re-init sessions/dht) + admin chan func() // pass a lambda for the admin socket to query stuff cryptokey cryptokey } +// Packet and session info, used to check that the packet matches a valid IP range or CKR prefix before sending to the tun. +type router_recvPacket struct { + bs []byte + sinfo *sessionInfo +} + // Initializes the router struct, which includes setting up channels to/from the tun/tap. func (r *router) init(core *Core) { r.core = core @@ -63,6 +70,7 @@ func (r *router) init(core *Core) { } r.in = in r.out = func(packet []byte) { p.handlePacket(packet) } // The caller is responsible for go-ing if it needs to not block + r.toRecv = make(chan router_recvPacket, 32) recv := make(chan []byte, 32) send := make(chan []byte, 32) r.recv = recv @@ -70,7 +78,7 @@ func (r *router) init(core *Core) { r.core.tun.recv = recv r.core.tun.send = send r.reset = make(chan struct{}, 1) - r.admin = make(chan func()) + r.admin = make(chan func(), 32) r.cryptokey.init(r.core) // go r.mainLoop() } @@ -91,6 +99,8 @@ func (r *router) mainLoop() { defer ticker.Stop() for { select { + case rp := <-r.toRecv: + r.recvPacket(rp.bs, rp.sinfo) case p := <-r.in: r.handleIn(p) case p := <-r.send: diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index a7b1d43..b0022d7 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -589,5 +589,5 @@ func (sinfo *sessionInfo) doRecv(p *wire_trafficPacket) { sinfo.updateNonce(&p.Nonce) sinfo.time = time.Now() sinfo.bytesRecvd += uint64(len(bs)) - sinfo.core.router.recvPacket(bs, sinfo) + sinfo.core.router.toRecv <- router_recvPacket{bs, sinfo} } diff --git a/yggdrasilctl.go b/yggdrasilctl.go index d98386b..a3e2409 100644 --- a/yggdrasilctl.go +++ b/yggdrasilctl.go @@ -231,7 +231,7 @@ func main() { uint(k), uint(v), uint(queuesizepercent), uint(portqueuepackets[k])) } } - case "addpeer", "removepeer", "addallowedencryptionpublickey", "removeallowedencryptionpublickey": + case "addpeer", "removepeer", "addallowedencryptionpublickey", "removeallowedencryptionpublickey", "addsourcesubnet", "addroute", "removesourcesubnet", "removeroute": if _, ok := res["added"]; ok { for _, v := range res["added"].([]interface{}) { fmt.Println("Added:", fmt.Sprint(v)) @@ -274,6 +274,28 @@ func main() { fmt.Println("-", v) } } + case "getsourcesubnets": + if _, ok := res["source_subnets"]; !ok { + fmt.Println("No source subnets found") + } else if res["source_subnets"] == nil { + fmt.Println("No source subnets found") + } else { + fmt.Println("Source subnets:") + for _, v := range res["source_subnets"].([]interface{}) { + fmt.Println("-", v) + } + } + case "getroutes": + if _, ok := res["routes"]; !ok { + fmt.Println("No routes found") + } else if res["routes"] == nil { + fmt.Println("No routes found") + } else { + fmt.Println("Routes:") + for _, v := range res["routes"].([]interface{}) { + fmt.Println("-", v) + } + } default: if json, err := json.MarshalIndent(recv["response"], "", " "); err == nil { fmt.Println(string(json))