From 52206dc381b2724a594f2212c6811d30373a408f Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 5 Nov 2018 16:40:47 +0000 Subject: [PATCH 01/22] Add initial crypto-key routing handlers --- src/yggdrasil/ckr.go | 104 +++++++++++++++++++++++++++++++++ src/yggdrasil/config/config.go | 8 +++ src/yggdrasil/core.go | 8 +++ src/yggdrasil/router.go | 18 +++--- 4 files changed, 130 insertions(+), 8 deletions(-) create mode 100644 src/yggdrasil/ckr.go diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go new file mode 100644 index 00000000..15f8828d --- /dev/null +++ b/src/yggdrasil/ckr.go @@ -0,0 +1,104 @@ +package yggdrasil + +import ( + "encoding/hex" + "errors" + "net" + "sort" +) + +// This module implements crypto-key routing, similar to Wireguard, where we +// allow traffic for non-Yggdrasil ranges to be routed over Yggdrasil. + +type cryptokey struct { + core *Core + enabled bool + ipv4routes []cryptokey_route + ipv6routes []cryptokey_route +} + +type cryptokey_route struct { + subnet net.IPNet + destination []byte +} + +func (c *cryptokey) init(core *Core) { + c.core = core + c.ipv4routes = make([]cryptokey_route, 0) + c.ipv6routes = make([]cryptokey_route, 0) +} + +func (c *cryptokey) isEnabled() bool { + return c.enabled +} + +func (c *cryptokey) addRoute(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 + prefixlen, prefixsize := ipnet.Mask.Size() + + // Check if the prefix is IPv4 or IPv6 + if prefixsize == net.IPv6len*8 { + // IPv6 + for _, route := range c.ipv6routes { + // Do we already have a route for this subnet? + routeprefixlen, _ := route.subnet.Mask.Size() + if route.subnet.IP.Equal(ipnet.IP) && routeprefixlen == prefixlen { + return errors.New("IPv6 route already exists") + } + } + // Decode the public key + if boxPubKey, err := hex.DecodeString(dest); err != nil { + return err + } else { + // Add the new crypto-key route + c.ipv6routes = append(c.ipv6routes, cryptokey_route{ + subnet: *ipnet, + destination: boxPubKey, + }) + // Sort so most specific routes are first + sort.Slice(c.ipv6routes, func(i, j int) bool { + im, _ := c.ipv6routes[i].subnet.Mask.Size() + jm, _ := c.ipv6routes[j].subnet.Mask.Size() + return im > jm + }) + return nil + } + } else if prefixsize == net.IPv4len*8 { + // IPv4 + return errors.New("IPv4 not supported at this time") + } + return errors.New("Unspecified error") +} + +func (c *cryptokey) getPublicKeyForAddress(addr string) (boxPubKey, error) { + ipaddr := net.ParseIP(addr) + + if ipaddr.To4() == nil { + // IPv6 + for _, route := range c.ipv6routes { + if route.subnet.Contains(ipaddr) { + var box boxPubKey + copy(box[:boxPubKeyLen], route.destination) + return box, nil + } + } + } else { + // IPv4 + return boxPubKey{}, errors.New("IPv4 not supported at this time") + /* + for _, route := range c.ipv4routes { + if route.subnet.Contains(ipaddr) { + return route.destination, nil + } + } + */ + } + + return boxPubKey{}, errors.New("No route") +} diff --git a/src/yggdrasil/config/config.go b/src/yggdrasil/config/config.go index bcf4f322..6f1871fc 100644 --- a/src/yggdrasil/config/config.go +++ b/src/yggdrasil/config/config.go @@ -17,6 +17,7 @@ type NodeConfig struct { IfTAPMode bool `comment:"Set local network interface to TAP mode rather than TUN mode if\nsupported by your platform - option will be ignored if not."` IfMTU int `comment:"Maximux Transmission Unit (MTU) size for your local TUN/TAP interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` SessionFirewall SessionFirewall `comment:"The session firewall controls who can send/receive network traffic\nto/from. This is useful if you want to protect this node without\nresorting to using a real firewall. This does not affect traffic\nbeing routed via this node to somewhere else. Rules are prioritised as\nfollows: blacklist, whitelist, always allow outgoing, direct, remote."` + TunnelRouting TunnelRouting `comment:"Allow tunneling non-Yggdrasil traffic over Yggdrasil."` //Net NetConfig `comment:"Extended options for connecting to peers over other networks."` } @@ -26,6 +27,7 @@ type NetConfig struct { I2P I2PConfig `comment:"Experimental options for configuring peerings over I2P."` } +// SessionFirewall controls the session firewall configuration type SessionFirewall struct { Enable bool `comment:"Enable or disable the session firewall. If disabled, network traffic\nfrom any node will be allowed. If enabled, the below rules apply."` AllowFromDirect bool `comment:"Allow network traffic from directly connected peers."` @@ -34,3 +36,9 @@ type SessionFirewall struct { WhitelistEncryptionPublicKeys []string `comment:"List of public keys from which network traffic is always accepted,\nregardless of AllowFromDirect or AllowFromRemote."` BlacklistEncryptionPublicKeys []string `comment:"List of public keys from which network traffic is always rejected,\nregardless of the whitelist, AllowFromDirect or AllowFromRemote."` } + +// TunnelRouting contains the crypto-key routing tables for tunneling +type TunnelRouting struct { + Enable bool `comment:"Enable or disable tunneling."` + IPv6Routes map[string]string `comment:"IPv6 subnets, mapped to the public keys to which they should be routed."` +} diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 015147c4..f4fb3d78 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -121,6 +121,14 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { return err } + if nc.TunnelRouting.Enable { + for ipv6, pubkey := range nc.TunnelRouting.IPv6Routes { + if err := c.router.cryptokey.addRoute(ipv6, pubkey); err != nil { + panic(err) + } + } + } + if err := c.admin.start(); err != nil { c.log.Println("Failed to start admin socket") return err diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 86eb193c..e83bb114 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -32,14 +32,15 @@ import ( // The router struct has channels to/from the tun/tap device and a self peer (0), which is how messages are passed between this node and the peers/switch layer. // The router's mainLoop goroutine is responsible for managing all information related to the dht, searches, and crypto sessions. type router struct { - core *Core - addr address - 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 + core *Core + addr address + 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 + cryptokey cryptokey } // Initializes the router struct, which includes setting up channels to/from the tun/tap. @@ -67,6 +68,7 @@ func (r *router) init(core *Core) { r.core.tun.send = send r.reset = make(chan struct{}, 1) r.admin = make(chan func()) + r.cryptokey.init(r.core) // go r.mainLoop() } From ec751e8cc71683537ea5790e73e00b2de237035c Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 5 Nov 2018 17:03:58 +0000 Subject: [PATCH 02/22] Don't allow Yggdrasil ranges as crypto-key routes --- src/yggdrasil/ckr.go | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index 15f8828d..c4f6d697 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -3,6 +3,7 @@ package yggdrasil import ( "encoding/hex" "errors" + "fmt" "net" "sort" ) @@ -34,22 +35,28 @@ func (c *cryptokey) isEnabled() bool { func (c *cryptokey) addRoute(cidr string, dest string) error { // Is the CIDR we've been given valid? - _, ipnet, err := net.ParseCIDR(cidr) + ipaddr, ipnet, err := net.ParseCIDR(cidr) if err != nil { return err } // Get the prefix length and size - prefixlen, prefixsize := ipnet.Mask.Size() + _, prefixsize := ipnet.Mask.Size() // Check if the prefix is IPv4 or IPv6 if prefixsize == net.IPv6len*8 { - // IPv6 + // Is the route an Yggdrasil destination? + var addr address + var snet subnet + copy(addr[:], ipaddr) + copy(snet[:], ipnet.IP) + if addr.isValid() || snet.isValid() { + return errors.New("Can't specify Yggdrasil destination as crypto-key route") + } + // Do we already have a route for this subnet? for _, route := range c.ipv6routes { - // Do we already have a route for this subnet? - routeprefixlen, _ := route.subnet.Mask.Size() - if route.subnet.IP.Equal(ipnet.IP) && routeprefixlen == prefixlen { - return errors.New("IPv6 route already exists") + if route.subnet.String() == ipnet.String() { + return errors.New(fmt.Sprintf("Route already exists for %s", cidr)) } } // Decode the public key @@ -99,6 +106,5 @@ func (c *cryptokey) getPublicKeyForAddress(addr string) (boxPubKey, error) { } */ } - - return boxPubKey{}, errors.New("No route") + return boxPubKey{}, errors.New(fmt.Sprintf("No route to %s", addr)) } From 295e9c9a1020eafd6cad1320e0219bf0124762ce Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 5 Nov 2018 17:31:10 +0000 Subject: [PATCH 03/22] Cache crypto-key routes (until routing table changes) --- src/yggdrasil/ckr.go | 51 ++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index c4f6d697..f24cd9d5 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -16,6 +16,8 @@ type cryptokey struct { enabled bool ipv4routes []cryptokey_route ipv6routes []cryptokey_route + ipv4cache map[address]cryptokey_route + ipv6cache map[address]cryptokey_route } type cryptokey_route struct { @@ -27,6 +29,8 @@ func (c *cryptokey) init(core *Core) { c.core = core c.ipv4routes = make([]cryptokey_route, 0) c.ipv6routes = make([]cryptokey_route, 0) + c.ipv4cache = make(map[address]cryptokey_route, 0) + c.ipv6cache = make(map[address]cryptokey_route, 0) } func (c *cryptokey) isEnabled() bool { @@ -68,12 +72,20 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { subnet: *ipnet, destination: boxPubKey, }) + // Sort so most specific routes are first sort.Slice(c.ipv6routes, func(i, j int) bool { im, _ := c.ipv6routes[i].subnet.Mask.Size() jm, _ := c.ipv6routes[j].subnet.Mask.Size() return im > jm }) + + // Clear the cache as this route might change future routing + // Setting an empty slice keeps the memory whereas nil invokes GC + for k := range c.ipv6cache { + delete(c.ipv6cache, k) + } + return nil } } else if prefixsize == net.IPv4len*8 { @@ -83,28 +95,39 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { return errors.New("Unspecified error") } -func (c *cryptokey) getPublicKeyForAddress(addr string) (boxPubKey, error) { - ipaddr := net.ParseIP(addr) +func (c *cryptokey) getPublicKeyForAddress(addr address) (boxPubKey, error) { + // Check if there's a cache entry for this addr + if route, ok := c.ipv6cache[addr]; ok { + var box boxPubKey + copy(box[:boxPubKeyLen], route.destination) + return box, nil + } - if ipaddr.To4() == nil { - // IPv6 + // No cache was found - start by converting the address into a net.IP + ip := make(net.IP, 16) + copy(ip[:16], addr[:]) + + // Check whether it's an IPv4 or an IPv6 address + if ip.To4() == nil { + // Check if we have a route. At this point c.ipv6routes should be + // pre-sorted so that the most specific routes are first for _, route := range c.ipv6routes { - if route.subnet.Contains(ipaddr) { + // Does this subnet match the given IP? + if route.subnet.Contains(ip) { + // Cache the entry for future packets to get a faster lookup + c.ipv6cache[addr] = route + + // Return the boxPubKey var box boxPubKey copy(box[:boxPubKeyLen], route.destination) return box, nil } } } else { - // IPv4 + // IPv4 isn't supported yet return boxPubKey{}, errors.New("IPv4 not supported at this time") - /* - for _, route := range c.ipv4routes { - if route.subnet.Contains(ipaddr) { - return route.destination, nil - } - } - */ } - return boxPubKey{}, errors.New(fmt.Sprintf("No route to %s", addr)) + + // No route was found if we got to this point + return boxPubKey{}, errors.New(fmt.Sprintf("No route to %s", ip.String())) } From 87b0f5fe24212678c500407a808db3c65b4deadf Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 5 Nov 2018 22:39:30 +0000 Subject: [PATCH 04/22] Use CKR in router when sending packets --- src/yggdrasil/router.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index e83bb114..0faf850b 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -134,7 +134,16 @@ func (r *router) sendPacket(bs []byte) { var snet subnet copy(snet[:], bs[24:]) if !dest.isValid() && !snet.isValid() { - return + if key, err := r.cryptokey.getPublicKeyForAddress(dest); err == nil { + addr := *address_addrForNodeID(getNodeID(&key)) + copy(dest[:], addr[:]) + copy(snet[:], addr[:]) + if !dest.isValid() && !snet.isValid() { + return + } + } else { + return + } } doSearch := func(packet []byte) { var nodeID, mask *NodeID From c7f2427de11abc986a68237f8725934fe8ac8bb6 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 5 Nov 2018 22:58:58 +0000 Subject: [PATCH 05/22] Check CKR routes when receiving packets in router --- src/yggdrasil/router.go | 13 ++++++++----- src/yggdrasil/session.go | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 0faf850b..e369d836 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -253,7 +253,7 @@ func (r *router) sendPacket(bs []byte) { // Called for incoming traffic by the session worker for that connection. // Checks that the IP address is correct (matches the session) and passes the packet to the tun/tap. -func (r *router) recvPacket(bs []byte, theirAddr *address, theirSubnet *subnet) { +func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) { // Note: called directly by the session worker, not the router goroutine if len(bs) < 24 { util_putBytes(bs) @@ -264,11 +264,14 @@ func (r *router) recvPacket(bs []byte, theirAddr *address, theirSubnet *subnet) var snet subnet copy(snet[:], bs[8:]) switch { - case source.isValid() && source == *theirAddr: - case snet.isValid() && snet == *theirSubnet: + case source.isValid() && source == sinfo.theirAddr: + case snet.isValid() && snet == sinfo.theirSubnet: default: - util_putBytes(bs) - return + key, err := r.cryptokey.getPublicKeyForAddress(source) + if err != nil || key != sinfo.theirPermPub { + util_putBytes(bs) + return + } } //go func() { r.recv<-bs }() r.recv <- bs diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 0bc27a12..0e587d52 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.theirAddr, &sinfo.theirSubnet) + sinfo.core.router.recvPacket(bs, sinfo) } From 7218b5a56c3ec735807b7dccd2ddfa766f6500f8 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 5 Nov 2018 23:12:26 +0000 Subject: [PATCH 06/22] Don't look up public keys for Yggdrasil native addresses --- src/yggdrasil/ckr.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index f24cd9d5..0e195fd3 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -96,6 +96,12 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { } func (c *cryptokey) getPublicKeyForAddress(addr address) (boxPubKey, error) { + // Check if the address is a valid Yggdrasil address - if so it + // is exempt from all CKR checking + if addr.isValid() { + return + } + // Check if there's a cache entry for this addr if route, ok := c.ipv6cache[addr]; ok { var box boxPubKey From cfdbc481a5502c9cbc5aa23b6a8e5c1b77f50dd2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 5 Nov 2018 23:22:45 +0000 Subject: [PATCH 07/22] Modify source address check for CKR --- src/yggdrasil/ckr.go | 2 +- src/yggdrasil/router.go | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index 0e195fd3..2b6c0d1f 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -99,7 +99,7 @@ func (c *cryptokey) getPublicKeyForAddress(addr address) (boxPubKey, error) { // Check if the address is a valid Yggdrasil address - if so it // is exempt from all CKR checking if addr.isValid() { - return + return boxPubKey{}, errors.New("Cannot look up CKR for Yggdrasil addresses") } // Check if there's a cache entry for this addr diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index e369d836..dcce7271 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -124,14 +124,11 @@ func (r *router) sendPacket(bs []byte) { } var sourceAddr address var sourceSubnet subnet + var dest address + var snet subnet copy(sourceAddr[:], bs[8:]) copy(sourceSubnet[:], bs[8:]) - if !sourceAddr.isValid() && !sourceSubnet.isValid() { - return - } - var dest address copy(dest[:], bs[24:]) - var snet subnet copy(snet[:], bs[24:]) if !dest.isValid() && !snet.isValid() { if key, err := r.cryptokey.getPublicKeyForAddress(dest); err == nil { @@ -144,6 +141,10 @@ func (r *router) sendPacket(bs []byte) { } else { return } + } else { + if !sourceAddr.isValid() && !sourceSubnet.isValid() { + return + } } doSearch := func(packet []byte) { var nodeID, mask *NodeID From 8c2327a2bf15e6388b03957983023b704b4266a1 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 5 Nov 2018 23:59:41 +0000 Subject: [PATCH 08/22] Add source addresses option and more intelligent source checking --- src/yggdrasil/ckr.go | 62 ++++++++++++++++++++++++++++++---- src/yggdrasil/config/config.go | 5 +-- src/yggdrasil/router.go | 9 ++--- 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index 2b6c0d1f..96fde27e 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -12,12 +12,14 @@ import ( // allow traffic for non-Yggdrasil ranges to be routed over Yggdrasil. type cryptokey struct { - core *Core - enabled bool - ipv4routes []cryptokey_route - ipv6routes []cryptokey_route - ipv4cache map[address]cryptokey_route - ipv6cache map[address]cryptokey_route + core *Core + enabled bool + ipv4routes []cryptokey_route + ipv6routes []cryptokey_route + ipv4cache map[address]cryptokey_route + ipv6cache map[address]cryptokey_route + ipv4sources []net.IPNet + ipv6sources []net.IPNet } type cryptokey_route struct { @@ -31,12 +33,60 @@ func (c *cryptokey) init(core *Core) { c.ipv6routes = make([]cryptokey_route, 0) c.ipv4cache = make(map[address]cryptokey_route, 0) c.ipv6cache = make(map[address]cryptokey_route, 0) + c.ipv4sources = make([]net.IPNet, 0) + c.ipv6sources = make([]net.IPNet, 0) } func (c *cryptokey) isEnabled() bool { return c.enabled } +func (c *cryptokey) isValidSource(addr address) bool { + ip := net.IP(addr[:]) + + // Does this match our node's address? + if addr == c.core.router.addr { + return true + } + + // Does this match our node's subnet? + var subnet net.IPNet + copy(subnet.IP, c.core.router.subnet[:]) + copy(subnet.Mask, net.CIDRMask(64, 128)) + if subnet.Contains(ip) { + return true + } + + // Does it match a configured CKR source? + for _, subnet := range c.ipv6sources { + if subnet.Contains(ip) { + return true + } + } + + // Doesn't match any of the above + return false +} + +func (c *cryptokey) addSourceSubnet(cidr string) error { + // Is the CIDR we've been given valid? + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + return err + } + + // Check if we already have this CIDR + for _, subnet := range c.ipv6sources { + if subnet.String() == ipnet.String() { + return errors.New("Source subnet already configured") + } + } + + // Add the source subnet + c.ipv6sources = append(c.ipv6sources, *ipnet) + return nil +} + func (c *cryptokey) addRoute(cidr string, dest string) error { // Is the CIDR we've been given valid? ipaddr, ipnet, err := net.ParseCIDR(cidr) diff --git a/src/yggdrasil/config/config.go b/src/yggdrasil/config/config.go index 6f1871fc..95947454 100644 --- a/src/yggdrasil/config/config.go +++ b/src/yggdrasil/config/config.go @@ -39,6 +39,7 @@ type SessionFirewall struct { // TunnelRouting contains the crypto-key routing tables for tunneling type TunnelRouting struct { - Enable bool `comment:"Enable or disable tunneling."` - IPv6Routes map[string]string `comment:"IPv6 subnets, mapped to the public keys to which they should be routed."` + Enable bool `comment:"Enable or disable tunneling."` + IPv6Routes map[string]string `comment:"IPv6 subnets, mapped to the public keys to which they should be routed."` + IPv6Sources []string `comment:"Allow source addresses in these subnets."` } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index dcce7271..e3634640 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -34,6 +34,7 @@ import ( 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 @@ -47,6 +48,7 @@ type router struct { func (r *router) init(core *Core) { r.core = core r.addr = *address_addrForNodeID(&r.core.dht.nodeID) + r.subnet = *address_subnetForNodeID(&r.core.dht.nodeID) in := make(chan []byte, 32) // TODO something better than this... p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &boxSharedKey{}, "(self)") p.out = func(packet []byte) { @@ -128,6 +130,9 @@ func (r *router) sendPacket(bs []byte) { var snet subnet copy(sourceAddr[:], bs[8:]) copy(sourceSubnet[:], bs[8:]) + if !r.cryptokey.isValidSource(sourceAddr) { + return + } copy(dest[:], bs[24:]) copy(snet[:], bs[24:]) if !dest.isValid() && !snet.isValid() { @@ -141,10 +146,6 @@ func (r *router) sendPacket(bs []byte) { } else { return } - } else { - if !sourceAddr.isValid() && !sourceSubnet.isValid() { - return - } } doSearch := func(packet []byte) { var nodeID, mask *NodeID From e3d4aed44adc1dfa1d089ae0d0f1b012b0c71692 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 00:05:01 +0000 Subject: [PATCH 09/22] Configure IPv6Sources --- src/yggdrasil/core.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index f4fb3d78..31142180 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -127,6 +127,11 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { panic(err) } } + for _, source := range nc.TunnelRouting.IPv6Sources { + if c.router.cryptokey.addSourceSubnet(source); err != nil { + panic(err) + } + } } if err := c.admin.start(); err != nil { From 19e6aaf9f54ddf00066e39c27ab56b0a89dd81f2 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 00:06:37 +0000 Subject: [PATCH 10/22] Remove sourceSubnet from router --- src/yggdrasil/router.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index e3634640..09058e43 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -125,11 +125,9 @@ func (r *router) sendPacket(bs []byte) { panic("Tried to send a packet shorter than a header...") } var sourceAddr address - var sourceSubnet subnet var dest address var snet subnet copy(sourceAddr[:], bs[8:]) - copy(sourceSubnet[:], bs[8:]) if !r.cryptokey.isValidSource(sourceAddr) { return } From f0947223bb552eded37f7433eb9c54a30063e702 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 11:11:57 +0000 Subject: [PATCH 11/22] Only validate CKR routes if CKR enabled --- src/yggdrasil/ckr.go | 8 +++++--- src/yggdrasil/config/config.go | 6 +++--- src/yggdrasil/core.go | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index 96fde27e..ffe9206c 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -58,9 +58,11 @@ func (c *cryptokey) isValidSource(addr address) bool { } // Does it match a configured CKR source? - for _, subnet := range c.ipv6sources { - if subnet.Contains(ip) { - return true + if c.isEnabled() { + for _, subnet := range c.ipv6sources { + if subnet.Contains(ip) { + return true + } } } diff --git a/src/yggdrasil/config/config.go b/src/yggdrasil/config/config.go index 95947454..f6694662 100644 --- a/src/yggdrasil/config/config.go +++ b/src/yggdrasil/config/config.go @@ -39,7 +39,7 @@ type SessionFirewall struct { // TunnelRouting contains the crypto-key routing tables for tunneling type TunnelRouting struct { - Enable bool `comment:"Enable or disable tunneling."` - IPv6Routes map[string]string `comment:"IPv6 subnets, mapped to the public keys to which they should be routed."` - IPv6Sources []string `comment:"Allow source addresses in these subnets."` + Enable bool `comment:"Enable or disable tunneling."` + IPv6Destinations map[string]string `comment:"IPv6 subnets, mapped to the EncryptionPublicKey to which they should\nbe routed to."` + IPv6Sources []string `comment:"Optional IPv6 subnets which are allowed to be used as source addresses\nin addition to this node's Yggdrasil address/subnet."` } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 31142180..03e8a263 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -122,7 +122,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { } if nc.TunnelRouting.Enable { - for ipv6, pubkey := range nc.TunnelRouting.IPv6Routes { + for ipv6, pubkey := range nc.TunnelRouting.IPv6Destinations { if err := c.router.cryptokey.addRoute(ipv6, pubkey); err != nil { panic(err) } From bc578f571c2f01d5b8943a0754dbf7ff91b57176 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 11:56:32 +0000 Subject: [PATCH 12/22] Some output at startup --- src/yggdrasil/ckr.go | 2 ++ src/yggdrasil/core.go | 1 + 2 files changed, 3 insertions(+) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index ffe9206c..c1558663 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -86,6 +86,7 @@ func (c *cryptokey) addSourceSubnet(cidr string) error { // Add the source subnet c.ipv6sources = append(c.ipv6sources, *ipnet) + c.core.log.Println("Added CKR source subnet", cidr) return nil } @@ -138,6 +139,7 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { delete(c.ipv6cache, k) } + c.core.log.Println("Added CKR destination subnet", cidr) return nil } } else if prefixsize == net.IPv4len*8 { diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 03e8a263..10cf272d 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -122,6 +122,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { } if nc.TunnelRouting.Enable { + c.log.Println("Crypto-key routing enabled") for ipv6, pubkey := range nc.TunnelRouting.IPv6Destinations { if err := c.router.cryptokey.addRoute(ipv6, pubkey); err != nil { panic(err) From bc62af7f7dbfcd66faa30a4e9031d936bdf7a0bc Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 12:32:16 +0000 Subject: [PATCH 13/22] Enable CKR properly from config --- src/yggdrasil/ckr.go | 4 ++++ src/yggdrasil/core.go | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index c1558663..baa0058c 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -37,6 +37,10 @@ func (c *cryptokey) init(core *Core) { c.ipv6sources = make([]net.IPNet, 0) } +func (c *cryptokey) setEnabled(enabled bool) { + c.enabled = enabled +} + func (c *cryptokey) isEnabled() bool { return c.enabled } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 10cf272d..3a3531f6 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -121,7 +121,8 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { return err } - if nc.TunnelRouting.Enable { + c.router.cryptokey.setEnabled(nc.TunnelRouting.Enable) + if c.router.cryptokey.isEnabled() { c.log.Println("Crypto-key routing enabled") for ipv6, pubkey := range nc.TunnelRouting.IPv6Destinations { if err := c.router.cryptokey.addRoute(ipv6, pubkey); err != nil { From 2f75075da3812ed5273df34758535fcf8fc4355d Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 14:28:57 +0000 Subject: [PATCH 14/22] Fix Yggdrasil subnet routing --- src/yggdrasil/ckr.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index baa0058c..ccedc649 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -1,6 +1,7 @@ package yggdrasil import ( + "bytes" "encoding/hex" "errors" "fmt" @@ -49,15 +50,12 @@ func (c *cryptokey) isValidSource(addr address) bool { ip := net.IP(addr[:]) // Does this match our node's address? - if addr == c.core.router.addr { + if bytes.Equal(addr[:16], c.core.router.addr[:16]) { return true } // Does this match our node's subnet? - var subnet net.IPNet - copy(subnet.IP, c.core.router.subnet[:]) - copy(subnet.Mask, net.CIDRMask(64, 128)) - if subnet.Contains(ip) { + if bytes.Equal(addr[:8], c.core.router.subnet[:8]) { return true } From cb7a5f17d9b5b5c47bb454b0474ef07ea98bdae1 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 19:23:20 +0000 Subject: [PATCH 15/22] Check destination address upon receive in router --- src/yggdrasil/router.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 09058e43..8b5d17e4 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -259,6 +259,12 @@ func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) { util_putBytes(bs) return } + var dest address + copy(dest[:], bs[24:]) + if !r.cryptokey.isValidSource(dest) { + util_putBytes(bs) + return + } var source address copy(source[:], bs[8:]) var snet subnet From 424faa1c516e7e487925a7cd096a75fe19099309 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 20:04:49 +0000 Subject: [PATCH 16/22] Support IPv4 in ckr.go --- src/yggdrasil/ckr.go | 164 ++++++++++++++++++++------------- src/yggdrasil/config/config.go | 2 + src/yggdrasil/core.go | 10 ++ src/yggdrasil/router.go | 4 +- 4 files changed, 113 insertions(+), 67 deletions(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index ccedc649..8036486b 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -79,15 +79,30 @@ func (c *cryptokey) addSourceSubnet(cidr string) error { 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 _, subnet := range c.ipv6sources { + for _, subnet := range *routingsources { if subnet.String() == ipnet.String() { return errors.New("Source subnet already configured") } } // Add the source subnet - c.ipv6sources = append(c.ipv6sources, *ipnet) + *routingsources = append(*routingsources, *ipnet) c.core.log.Println("Added CKR source subnet", cidr) return nil } @@ -102,92 +117,111 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { // 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 { - // Is the route an Yggdrasil destination? - var addr address - var snet subnet - copy(addr[:], ipaddr) - copy(snet[:], ipnet.IP) - if addr.isValid() || snet.isValid() { - return errors.New("Can't specify Yggdrasil destination as crypto-key route") - } - // Do we already have a route for this subnet? - for _, route := range c.ipv6routes { - if route.subnet.String() == ipnet.String() { - return errors.New(fmt.Sprintf("Route already exists for %s", cidr)) - } - } - // Decode the public key - if boxPubKey, err := hex.DecodeString(dest); err != nil { - return err - } else { - // Add the new crypto-key route - c.ipv6routes = append(c.ipv6routes, cryptokey_route{ - subnet: *ipnet, - destination: boxPubKey, - }) - - // Sort so most specific routes are first - sort.Slice(c.ipv6routes, func(i, j int) bool { - im, _ := c.ipv6routes[i].subnet.Mask.Size() - jm, _ := c.ipv6routes[j].subnet.Mask.Size() - return im > jm - }) - - // Clear the cache as this route might change future routing - // Setting an empty slice keeps the memory whereas nil invokes GC - for k := range c.ipv6cache { - delete(c.ipv6cache, k) - } - - c.core.log.Println("Added CKR destination subnet", cidr) - return nil - } + routingtable = &c.ipv6routes + routingcache = &c.ipv6cache } else if prefixsize == net.IPv4len*8 { - // IPv4 - return errors.New("IPv4 not supported at this time") + routingtable = &c.ipv4routes + routingcache = &c.ipv4cache + } else { + return errors.New("Unexpected prefix size") } + + // Is the route an Yggdrasil destination? + var addr address + var snet subnet + copy(addr[:], ipaddr) + copy(snet[:], ipnet.IP) + if addr.isValid() || snet.isValid() { + return errors.New("Can't specify Yggdrasil destination as crypto-key route") + } + // Do we already have a route for this subnet? + for _, route := range *routingtable { + if route.subnet.String() == ipnet.String() { + return errors.New(fmt.Sprintf("Route already exists for %s", cidr)) + } + } + // Decode the public key + if boxPubKey, err := hex.DecodeString(dest); err != nil { + return err + } else { + // Add the new crypto-key route + *routingtable = append(*routingtable, cryptokey_route{ + subnet: *ipnet, + destination: boxPubKey, + }) + + // Sort so most specific routes are first + sort.Slice(*routingtable, func(i, j int) bool { + im, _ := (*routingtable)[i].subnet.Mask.Size() + jm, _ := (*routingtable)[j].subnet.Mask.Size() + return im > jm + }) + + // Clear the cache as this route might change future routing + // Setting an empty slice keeps the memory whereas nil invokes GC + for k := range *routingcache { + delete(*routingcache, k) + } + + c.core.log.Println("Added CKR destination subnet", cidr) + return nil + } + return errors.New("Unspecified error") } -func (c *cryptokey) getPublicKeyForAddress(addr address) (boxPubKey, error) { +func (c *cryptokey) getPublicKeyForAddress(addr address, addrlen int) (boxPubKey, error) { // Check if the address is a valid Yggdrasil address - if so it // is exempt from all CKR checking if addr.isValid() { return boxPubKey{}, errors.New("Cannot look up CKR for Yggdrasil addresses") } + // 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 addrlen == net.IPv6len { + routingtable = &c.ipv6routes + routingcache = &c.ipv6cache + } else if addrlen == net.IPv4len { + routingtable = &c.ipv4routes + routingcache = &c.ipv4cache + } else { + return boxPubKey{}, errors.New("Unexpected prefix size") + } + // Check if there's a cache entry for this addr - if route, ok := c.ipv6cache[addr]; ok { + if route, ok := (*routingcache)[addr]; ok { var box boxPubKey copy(box[:boxPubKeyLen], route.destination) return box, nil } // No cache was found - start by converting the address into a net.IP - ip := make(net.IP, 16) - copy(ip[:16], addr[:]) + ip := make(net.IP, addrlen) + copy(ip[:addrlen], addr[:]) - // Check whether it's an IPv4 or an IPv6 address - if ip.To4() == nil { - // Check if we have a route. At this point c.ipv6routes should be - // pre-sorted so that the most specific routes are first - for _, route := range c.ipv6routes { - // Does this subnet match the given IP? - if route.subnet.Contains(ip) { - // Cache the entry for future packets to get a faster lookup - c.ipv6cache[addr] = route + // Check if we have a route. At this point c.ipv6routes should be + // pre-sorted so that the most specific routes are first + for _, route := range *routingtable { + // Does this subnet match the given IP? + if route.subnet.Contains(ip) { + // Cache the entry for future packets to get a faster lookup + (*routingcache)[addr] = route - // Return the boxPubKey - var box boxPubKey - copy(box[:boxPubKeyLen], route.destination) - return box, nil - } + // Return the boxPubKey + var box boxPubKey + copy(box[:boxPubKeyLen], route.destination) + return box, nil } - } else { - // IPv4 isn't supported yet - return boxPubKey{}, errors.New("IPv4 not supported at this time") } // No route was found if we got to this point diff --git a/src/yggdrasil/config/config.go b/src/yggdrasil/config/config.go index f6694662..38f93404 100644 --- a/src/yggdrasil/config/config.go +++ b/src/yggdrasil/config/config.go @@ -42,4 +42,6 @@ type TunnelRouting struct { Enable bool `comment:"Enable or disable tunneling."` IPv6Destinations map[string]string `comment:"IPv6 subnets, mapped to the EncryptionPublicKey to which they should\nbe routed to."` IPv6Sources []string `comment:"Optional IPv6 subnets which are allowed to be used as source addresses\nin addition to this node's Yggdrasil address/subnet."` + IPv4Destinations map[string]string `comment:"IPv4 subnets, mapped to the EncryptionPublicKey to which they should\nbe routed to."` + IPv4Sources []string `comment:"Optional IPv4 subnets which are allowed to be used as source addresses."` } diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index 3a3531f6..2f60a1ba 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -134,6 +134,16 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { panic(err) } } + for ipv4, pubkey := range nc.TunnelRouting.IPv4Destinations { + if err := c.router.cryptokey.addRoute(ipv4, pubkey); err != nil { + panic(err) + } + } + for _, source := range nc.TunnelRouting.IPv4Sources { + if c.router.cryptokey.addSourceSubnet(source); err != nil { + panic(err) + } + } } if err := c.admin.start(); err != nil { diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 8b5d17e4..cd922a9c 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -134,7 +134,7 @@ func (r *router) sendPacket(bs []byte) { copy(dest[:], bs[24:]) copy(snet[:], bs[24:]) if !dest.isValid() && !snet.isValid() { - if key, err := r.cryptokey.getPublicKeyForAddress(dest); err == nil { + if key, err := r.cryptokey.getPublicKeyForAddress(dest, 16); err == nil { addr := *address_addrForNodeID(getNodeID(&key)) copy(dest[:], addr[:]) copy(snet[:], addr[:]) @@ -273,7 +273,7 @@ func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) { case source.isValid() && source == sinfo.theirAddr: case snet.isValid() && snet == sinfo.theirSubnet: default: - key, err := r.cryptokey.getPublicKeyForAddress(source) + key, err := r.cryptokey.getPublicKeyForAddress(source, 16) if err != nil || key != sinfo.theirPermPub { util_putBytes(bs) return From 024037541741696e22527f7c93cead4a94ea6e70 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 20:49:19 +0000 Subject: [PATCH 17/22] IPv4 CKR support in router --- src/yggdrasil/ckr.go | 34 +++++++++++++++++++--------- src/yggdrasil/router.go | 50 ++++++++++++++++++++++++++++++----------- src/yggdrasil/tun.go | 2 +- 3 files changed, 62 insertions(+), 24 deletions(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index 8036486b..f22840c3 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -46,22 +46,36 @@ func (c *cryptokey) isEnabled() bool { return c.enabled } -func (c *cryptokey) isValidSource(addr address) bool { - ip := net.IP(addr[:]) +func (c *cryptokey) isValidSource(addr address, addrlen int) bool { + ip := net.IP(addr[:addrlen]) - // Does this match our node's address? - if bytes.Equal(addr[:16], c.core.router.addr[:16]) { - return true - } + if addrlen == net.IPv6len { + // Does this match our node's address? + if bytes.Equal(addr[:16], c.core.router.addr[:16]) { + return true + } - // Does this match our node's subnet? - if bytes.Equal(addr[:8], c.core.router.subnet[:8]) { - return true + // Does this match our node's subnet? + if bytes.Equal(addr[:8], c.core.router.subnet[:8]) { + return true + } } // Does it match a configured CKR source? if c.isEnabled() { - for _, subnet := range c.ipv6sources { + // Build our references to the routing sources + var routingsources *[]net.IPNet + + // Check if the prefix is IPv4 or IPv6 + if addrlen == net.IPv6len { + routingsources = &c.ipv6sources + } else if addrlen == net.IPv4len { + routingsources = &c.ipv4sources + } else { + return false + } + + for _, subnet := range *routingsources { if subnet.Contains(ip) { return true } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index cd922a9c..0c633acc 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -127,14 +127,26 @@ func (r *router) sendPacket(bs []byte) { var sourceAddr address var dest address var snet subnet - copy(sourceAddr[:], bs[8:]) - if !r.cryptokey.isValidSource(sourceAddr) { + var addrlen int + if bs[0]&0xf0 == 0x60 { + // IPv6 address + addrlen = 16 + copy(sourceAddr[:addrlen], bs[8:]) + copy(dest[:addrlen], bs[24:]) + copy(snet[:addrlen/2], bs[24:]) + } else if bs[0]&0xf0 == 0x40 { + // IPv4 address + addrlen = 4 + copy(sourceAddr[:addrlen], bs[12:]) + copy(dest[:addrlen], bs[16:]) + } else { + return + } + if !r.cryptokey.isValidSource(sourceAddr, addrlen) { return } - copy(dest[:], bs[24:]) - copy(snet[:], bs[24:]) if !dest.isValid() && !snet.isValid() { - if key, err := r.cryptokey.getPublicKeyForAddress(dest, 16); err == nil { + if key, err := r.cryptokey.getPublicKeyForAddress(dest, addrlen); err == nil { addr := *address_addrForNodeID(getNodeID(&key)) copy(dest[:], addr[:]) copy(snet[:], addr[:]) @@ -259,21 +271,33 @@ func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) { util_putBytes(bs) return } + var sourceAddr address var dest address - copy(dest[:], bs[24:]) - if !r.cryptokey.isValidSource(dest) { + var snet subnet + var addrlen int + if bs[0]&0xf0 == 0x60 { + // IPv6 address + addrlen = 16 + copy(sourceAddr[:addrlen], bs[8:]) + copy(dest[:addrlen], bs[24:]) + copy(snet[:addrlen/2], bs[24:]) + } else if bs[0]&0xf0 == 0x40 { + // IPv4 address + addrlen = 4 + copy(sourceAddr[:addrlen], bs[12:]) + copy(dest[:addrlen], bs[16:]) + } else { + return + } + if !r.cryptokey.isValidSource(dest, addrlen) { util_putBytes(bs) return } - var source address - copy(source[:], bs[8:]) - var snet subnet - copy(snet[:], bs[8:]) switch { - case source.isValid() && source == sinfo.theirAddr: + case sourceAddr.isValid() && sourceAddr == sinfo.theirAddr: case snet.isValid() && snet == sinfo.theirSubnet: default: - key, err := r.cryptokey.getPublicKeyForAddress(source, 16) + key, err := r.cryptokey.getPublicKeyForAddress(sourceAddr, addrlen) if err != nil || key != sinfo.theirPermPub { util_putBytes(bs) return diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index cbbcdea7..ac703985 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -105,7 +105,7 @@ func (tun *tunDevice) read() error { n != 256*int(buf[o+4])+int(buf[o+5])+tun_IPv6_HEADER_LENGTH+o { // Either not an IPv6 packet or not the complete packet for some reason //panic("Should not happen in testing") - continue + //continue } if buf[o+6] == 58 { // Found an ICMPv6 packet From a3a53f92c3891d531df9b9cc2c0a05412950e744 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 22:35:28 +0000 Subject: [PATCH 18/22] Reinstate length/bounds check in tun.go --- src/yggdrasil/tun.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/yggdrasil/tun.go b/src/yggdrasil/tun.go index ac703985..1987c2d2 100644 --- a/src/yggdrasil/tun.go +++ b/src/yggdrasil/tun.go @@ -101,11 +101,11 @@ func (tun *tunDevice) read() error { if tun.iface.IsTAP() { o = tun_ETHER_HEADER_LENGTH } - if buf[o]&0xf0 != 0x60 || - n != 256*int(buf[o+4])+int(buf[o+5])+tun_IPv6_HEADER_LENGTH+o { - // Either not an IPv6 packet or not the complete packet for some reason - //panic("Should not happen in testing") - //continue + switch { + case buf[o]&0xf0 == 0x60 && n == 256*int(buf[o+4])+int(buf[o+5])+tun_IPv6_HEADER_LENGTH+o: + case buf[o]&0xf0 == 0x40 && n == 256*int(buf[o+2])+int(buf[o+3])+o: + default: + continue } if buf[o+6] == 58 { // Found an ICMPv6 packet From 39dab53ac709eb294491fa65627b806d3d03525b Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 6 Nov 2018 22:57:53 +0000 Subject: [PATCH 19/22] Update comments in configuration and some godoc descriptions --- src/yggdrasil/ckr.go | 13 +++++++++++++ src/yggdrasil/config/config.go | 16 ++++++++-------- src/yggdrasil/router.go | 2 ++ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/yggdrasil/ckr.go b/src/yggdrasil/ckr.go index f22840c3..2a054711 100644 --- a/src/yggdrasil/ckr.go +++ b/src/yggdrasil/ckr.go @@ -28,6 +28,7 @@ type cryptokey_route struct { destination []byte } +// Initialise crypto-key routing. This must be done before any other CKR calls. func (c *cryptokey) init(core *Core) { c.core = core c.ipv4routes = make([]cryptokey_route, 0) @@ -38,14 +39,19 @@ func (c *cryptokey) init(core *Core) { c.ipv6sources = make([]net.IPNet, 0) } +// Enable or disable crypto-key routing. func (c *cryptokey) setEnabled(enabled bool) { c.enabled = enabled } +// Check if crypto-key routing is enabled. func (c *cryptokey) isEnabled() bool { return c.enabled } +// 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, addrlen int) bool { ip := net.IP(addr[:addrlen]) @@ -86,6 +92,8 @@ func (c *cryptokey) isValidSource(addr address, addrlen int) bool { return false } +// 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 { // Is the CIDR we've been given valid? _, ipnet, err := net.ParseCIDR(cidr) @@ -121,6 +129,8 @@ func (c *cryptokey) addSourceSubnet(cidr string) error { return nil } +// 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 { // Is the CIDR we've been given valid? ipaddr, ipnet, err := net.ParseCIDR(cidr) @@ -190,6 +200,9 @@ func (c *cryptokey) addRoute(cidr string, dest string) error { return errors.New("Unspecified error") } +// Looks up the most specific route for the given address (with the address +// 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, addrlen int) (boxPubKey, error) { // Check if the address is a valid Yggdrasil address - if so it // is exempt from all CKR checking diff --git a/src/yggdrasil/config/config.go b/src/yggdrasil/config/config.go index 38f93404..a14ece9d 100644 --- a/src/yggdrasil/config/config.go +++ b/src/yggdrasil/config/config.go @@ -4,8 +4,8 @@ package config type NodeConfig struct { Listen string `comment:"Listen address for peer connections. Default is to listen for all\nTCP connections over IPv4 and IPv6 with a random port."` AdminListen string `comment:"Listen address for admin connections Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X."` - Peers []string `comment:"List of connection strings for static peers in URI format, i.e.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j."` - InterfacePeers map[string][]string `comment:"List of connection strings for static peers in URI format, arranged\nby source interface, i.e. { \"eth0\": [ tcp://a.b.c.d:e ] }. Note that\nSOCKS peerings will NOT be affected by this option and should go in\nthe \"Peers\" section instead."` + Peers []string `comment:"List of connection strings for static peers in URI format, e.g.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j."` + InterfacePeers map[string][]string `comment:"List of connection strings for static peers in URI format, arranged\nby source interface, e.g. { \"eth0\": [ tcp://a.b.c.d:e ] }. Note that\nSOCKS peerings will NOT be affected by this option and should go in\nthe \"Peers\" section instead."` ReadTimeout int32 `comment:"Read timeout for connections, specified in milliseconds. If less\nthan 6000 and not negative, 6000 (the default) is used. If negative,\nreads won't time out."` AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow or incoming TCP\nconnections from. If left empty/undefined then all connections\nwill be allowed by default."` EncryptionPublicKey string `comment:"Your public encryption key. Your peers may ask you for this to put\ninto their AllowedEncryptionPublicKeys configuration."` @@ -17,7 +17,7 @@ type NodeConfig struct { IfTAPMode bool `comment:"Set local network interface to TAP mode rather than TUN mode if\nsupported by your platform - option will be ignored if not."` IfMTU int `comment:"Maximux Transmission Unit (MTU) size for your local TUN/TAP interface.\nDefault is the largest supported size for your platform. The lowest\npossible value is 1280."` SessionFirewall SessionFirewall `comment:"The session firewall controls who can send/receive network traffic\nto/from. This is useful if you want to protect this node without\nresorting to using a real firewall. This does not affect traffic\nbeing routed via this node to somewhere else. Rules are prioritised as\nfollows: blacklist, whitelist, always allow outgoing, direct, remote."` - TunnelRouting TunnelRouting `comment:"Allow tunneling non-Yggdrasil traffic over Yggdrasil."` + TunnelRouting TunnelRouting `comment:"Allow tunneling non-Yggdrasil traffic over Yggdrasil. This effectively\nallows you to use Yggdrasil to route to, or to bridge other networks,\nsimilar to a VPN tunnel. Tunnelling works between any two nodes and\ndoes not require them to be directly peered."` //Net NetConfig `comment:"Extended options for connecting to peers over other networks."` } @@ -39,9 +39,9 @@ type SessionFirewall struct { // TunnelRouting contains the crypto-key routing tables for tunneling type TunnelRouting struct { - Enable bool `comment:"Enable or disable tunneling."` - IPv6Destinations map[string]string `comment:"IPv6 subnets, mapped to the EncryptionPublicKey to which they should\nbe routed to."` - IPv6Sources []string `comment:"Optional IPv6 subnets which are allowed to be used as source addresses\nin addition to this node's Yggdrasil address/subnet."` - IPv4Destinations map[string]string `comment:"IPv4 subnets, mapped to the EncryptionPublicKey to which they should\nbe routed to."` - IPv4Sources []string `comment:"Optional IPv4 subnets which are allowed to be used as source addresses."` + Enable bool `comment:"Enable or disable tunnel routing."` + IPv6Destinations map[string]string `comment:"IPv6 CIDR subnets, mapped to the EncryptionPublicKey to which they\nshould be routed, e.g. { \"aaaa:bbbb:cccc::/e\": \"boxpubkey\", ... }"` + IPv6Sources []string `comment:"Optional IPv6 source subnets which are allowed to be tunnelled in\naddition to this node's Yggdrasil address/subnet. If not\nspecified, only traffic originating from this node's Yggdrasil\naddress or subnet will be tunnelled."` + IPv4Destinations map[string]string `comment:"IPv4 CIDR subnets, mapped to the EncryptionPublicKey to which they\nshould be routed, e.g. { \"a.b.c.d/e\": \"boxpubkey\", ... }"` + IPv4Sources []string `comment:"IPv4 source subnets which are allowed to be tunnelled. Unlike for\nIPv6, this option is required for bridging IPv4 traffic. Only\ntraffic with a source matching these subnets will be tunnelled."` } diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 0c633acc..f57f80f0 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -140,6 +140,7 @@ func (r *router) sendPacket(bs []byte) { copy(sourceAddr[:addrlen], bs[12:]) copy(dest[:addrlen], bs[16:]) } else { + // Unknown address length return } if !r.cryptokey.isValidSource(sourceAddr, addrlen) { @@ -287,6 +288,7 @@ func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) { copy(sourceAddr[:addrlen], bs[12:]) copy(dest[:addrlen], bs[16:]) } else { + // Unknown address length return } if !r.cryptokey.isValidSource(dest, addrlen) { From fbfae473d47ded8bbb4b16822e242a1711d51e19 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 7 Nov 2018 10:04:31 +0000 Subject: [PATCH 20/22] Use full node ID for CKR routes instead of truncated node IDs from the address/subnet --- src/yggdrasil/router.go | 57 ++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index f57f80f0..a2f9cc83 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -125,20 +125,21 @@ func (r *router) sendPacket(bs []byte) { panic("Tried to send a packet shorter than a header...") } var sourceAddr address - var dest address - var snet subnet + var destAddr address + var destSnet subnet + var destNodeID *NodeID var addrlen int if bs[0]&0xf0 == 0x60 { // IPv6 address addrlen = 16 copy(sourceAddr[:addrlen], bs[8:]) - copy(dest[:addrlen], bs[24:]) - copy(snet[:addrlen/2], bs[24:]) + copy(destAddr[:addrlen], bs[24:]) + copy(destSnet[:addrlen/2], bs[24:]) } else if bs[0]&0xf0 == 0x40 { // IPv4 address addrlen = 4 copy(sourceAddr[:addrlen], bs[12:]) - copy(dest[:addrlen], bs[16:]) + copy(destAddr[:addrlen], bs[16:]) } else { // Unknown address length return @@ -146,12 +147,13 @@ func (r *router) sendPacket(bs []byte) { if !r.cryptokey.isValidSource(sourceAddr, addrlen) { return } - if !dest.isValid() && !snet.isValid() { - if key, err := r.cryptokey.getPublicKeyForAddress(dest, addrlen); err == nil { - addr := *address_addrForNodeID(getNodeID(&key)) - copy(dest[:], addr[:]) - copy(snet[:], addr[:]) - if !dest.isValid() && !snet.isValid() { + if !destAddr.isValid() && !destSnet.isValid() { + if key, err := r.cryptokey.getPublicKeyForAddress(destAddr, addrlen); err == nil { + destNodeID = getNodeID(&key) + addr := *address_addrForNodeID(destNodeID) + copy(destAddr[:], addr[:]) + copy(destSnet[:], addr[:]) + if !destAddr.isValid() && !destSnet.isValid() { return } } else { @@ -160,11 +162,26 @@ func (r *router) sendPacket(bs []byte) { } doSearch := func(packet []byte) { var nodeID, mask *NodeID - if dest.isValid() { - nodeID, mask = dest.getNodeIDandMask() - } - if snet.isValid() { - nodeID, mask = snet.getNodeIDandMask() + switch { + case destNodeID != nil: + // We already know the full node ID, probably because it's from a CKR + // route in which the public key is known ahead of time + nodeID = destNodeID + var m NodeID + for i := range m { + m[i] = 0xFF + } + mask = &m + case destAddr.isValid(): + // We don't know the full node ID - try and use the address to generate + // a truncated node ID + nodeID, mask = destAddr.getNodeIDandMask() + case destSnet.isValid(): + // We don't know the full node ID - try and use the subnet to generate + // a truncated node ID + nodeID, mask = destSnet.getNodeIDandMask() + default: + return } sinfo, isIn := r.core.searches.searches[*nodeID] if !isIn { @@ -177,11 +194,11 @@ func (r *router) sendPacket(bs []byte) { } var sinfo *sessionInfo var isIn bool - if dest.isValid() { - sinfo, isIn = r.core.sessions.getByTheirAddr(&dest) + if destAddr.isValid() { + sinfo, isIn = r.core.sessions.getByTheirAddr(&destAddr) } - if snet.isValid() { - sinfo, isIn = r.core.sessions.getByTheirSubnet(&snet) + if destSnet.isValid() { + sinfo, isIn = r.core.sessions.getByTheirSubnet(&destSnet) } switch { case !isIn || !sinfo.init: From 9542bfa902c20f36fe0bc341c59f6cc4bd89381e Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 7 Nov 2018 10:16:46 +0000 Subject: [PATCH 21/22] Check the session perm pub key against the CKR key --- src/yggdrasil/router.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index a2f9cc83..72a8cfba 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -23,6 +23,7 @@ package yggdrasil // The router then runs some sanity checks before passing it to the tun import ( + "bytes" "time" "golang.org/x/net/icmp" @@ -127,6 +128,7 @@ func (r *router) sendPacket(bs []byte) { var sourceAddr address var destAddr address var destSnet subnet + var destPubKey *boxPubKey var destNodeID *NodeID var addrlen int if bs[0]&0xf0 == 0x60 { @@ -149,7 +151,8 @@ func (r *router) sendPacket(bs []byte) { } if !destAddr.isValid() && !destSnet.isValid() { if key, err := r.cryptokey.getPublicKeyForAddress(destAddr, addrlen); err == nil { - destNodeID = getNodeID(&key) + destPubKey = &key + destNodeID = getNodeID(destPubKey) addr := *address_addrForNodeID(destNodeID) copy(destAddr[:], addr[:]) copy(destSnet[:], addr[:]) @@ -227,6 +230,14 @@ func (r *router) sendPacket(bs []byte) { } fallthrough // Also send the packet default: + // If we know the public key ahead of time (i.e. a CKR route) then check + // if the session perm pub key matches before we send the packet to it + if destPubKey != nil { + if !bytes.Equal((*destPubKey)[:], sinfo.theirPermPub[:]) { + return + } + } + // Drop packets if the session MTU is 0 - this means that one or other // side probably has their TUN adapter disabled if sinfo.getMTU() == 0 { @@ -277,6 +288,7 @@ func (r *router) sendPacket(bs []byte) { // Don't continue - drop the packet return } + sinfo.send <- bs } } From 685b565512381da980278a9514c8fff1a42d31b4 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Wed, 7 Nov 2018 10:29:08 +0000 Subject: [PATCH 22/22] Check IP header lengths correctly per protocol --- src/yggdrasil/router.go | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/router.go b/src/yggdrasil/router.go index 72a8cfba..fdcbb97f 100644 --- a/src/yggdrasil/router.go +++ b/src/yggdrasil/router.go @@ -122,9 +122,6 @@ func (r *router) mainLoop() { // If the session hasn't responded recently, it triggers a ping or search to keep things alive or deal with broken coords *relatively* quickly. // It also deals with oversized packets if there are MTU issues by calling into icmpv6.go to spoof PacketTooBig traffic, or DestinationUnreachable if the other side has their tun/tap disabled. func (r *router) sendPacket(bs []byte) { - if len(bs) < 40 { - panic("Tried to send a packet shorter than a header...") - } var sourceAddr address var destAddr address var destSnet subnet @@ -132,12 +129,20 @@ func (r *router) sendPacket(bs []byte) { var destNodeID *NodeID var addrlen int if bs[0]&0xf0 == 0x60 { + // Check if we have a fully-sized header + if len(bs) < 40 { + panic("Tried to send a packet shorter than an IPv6 header...") + } // IPv6 address addrlen = 16 copy(sourceAddr[:addrlen], bs[8:]) copy(destAddr[:addrlen], bs[24:]) copy(destSnet[:addrlen/2], bs[24:]) } else if bs[0]&0xf0 == 0x40 { + // Check if we have a fully-sized header + if len(bs) < 20 { + panic("Tried to send a packet shorter than an IPv4 header...") + } // IPv4 address addrlen = 4 copy(sourceAddr[:addrlen], bs[12:]) @@ -147,12 +152,19 @@ func (r *router) sendPacket(bs []byte) { return } if !r.cryptokey.isValidSource(sourceAddr, addrlen) { + // The packet had a source address that doesn't belong to us or our + // configured crypto-key routing source subnets return } if !destAddr.isValid() && !destSnet.isValid() { + // The addresses didn't match valid Yggdrasil node addresses so let's see + // whether it matches a crypto-key routing range instead if key, err := r.cryptokey.getPublicKeyForAddress(destAddr, addrlen); err == nil { + // A public key was found, get the node ID for the search destPubKey = &key destNodeID = getNodeID(destPubKey) + // Do a quick check to ensure that the node ID refers to a vaild Yggdrasil + // address or subnet - this might be superfluous addr := *address_addrForNodeID(destNodeID) copy(destAddr[:], addr[:]) copy(destSnet[:], addr[:]) @@ -160,6 +172,7 @@ func (r *router) sendPacket(bs []byte) { return } } else { + // No public key was found in the CKR table so we've exhausted our options return } } @@ -320,10 +333,13 @@ func (r *router) recvPacket(bs []byte, sinfo *sessionInfo) { // Unknown address length return } + // Check that the packet is destined for either our Yggdrasil address or + // subnet, or that it matches one of the crypto-key routing source routes if !r.cryptokey.isValidSource(dest, addrlen) { util_putBytes(bs) return } + // See whether the packet they sent should have originated from this session switch { case sourceAddr.isValid() && sourceAddr == sinfo.theirAddr: case snet.isValid() && snet == sinfo.theirSubnet: