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() }