mirror of
https://github.com/cwinfo/yggdrasil-go.git
synced 2024-11-22 21:10:29 +00:00
Merge pull request #201 from neilalexander/ckr
Tunnel traffic using crypto-key routing
This commit is contained in:
commit
7af85c7d70
256
src/yggdrasil/ckr.go
Normal file
256
src/yggdrasil/ckr.go
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
package yggdrasil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"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
|
||||||
|
ipv4cache map[address]cryptokey_route
|
||||||
|
ipv6cache map[address]cryptokey_route
|
||||||
|
ipv4sources []net.IPNet
|
||||||
|
ipv6sources []net.IPNet
|
||||||
|
}
|
||||||
|
|
||||||
|
type cryptokey_route struct {
|
||||||
|
subnet net.IPNet
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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])
|
||||||
|
|
||||||
|
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 it match a configured CKR source?
|
||||||
|
if c.isEnabled() {
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Doesn't match any of the above
|
||||||
|
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)
|
||||||
|
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 _, subnet := range *routingsources {
|
||||||
|
if subnet.String() == ipnet.String() {
|
||||||
|
return errors.New("Source subnet already configured")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the source subnet
|
||||||
|
*routingsources = append(*routingsources, *ipnet)
|
||||||
|
c.core.log.Println("Added CKR source subnet", cidr)
|
||||||
|
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)
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
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 := (*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, addrlen)
|
||||||
|
copy(ip[:addrlen], addr[:])
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No route was found if we got to this point
|
||||||
|
return boxPubKey{}, errors.New(fmt.Sprintf("No route to %s", ip.String()))
|
||||||
|
}
|
@ -4,8 +4,8 @@ package config
|
|||||||
type NodeConfig struct {
|
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."`
|
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."`
|
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."`
|
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, 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."`
|
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."`
|
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."`
|
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."`
|
EncryptionPublicKey string `comment:"Your public encryption key. Your peers may ask you for this to put\ninto their AllowedEncryptionPublicKeys configuration."`
|
||||||
@ -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."`
|
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."`
|
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."`
|
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. 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."`
|
//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."`
|
I2P I2PConfig `comment:"Experimental options for configuring peerings over I2P."`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SessionFirewall controls the session firewall configuration
|
||||||
type SessionFirewall struct {
|
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."`
|
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."`
|
AllowFromDirect bool `comment:"Allow network traffic from directly connected peers."`
|
||||||
@ -34,3 +36,12 @@ type SessionFirewall struct {
|
|||||||
WhitelistEncryptionPublicKeys []string `comment:"List of public keys from which network traffic is always accepted,\nregardless of AllowFromDirect or AllowFromRemote."`
|
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."`
|
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 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."`
|
||||||
|
}
|
||||||
|
@ -121,6 +121,31 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, source := range nc.TunnelRouting.IPv6Sources {
|
||||||
|
if c.router.cryptokey.addSourceSubnet(source); err != nil {
|
||||||
|
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 {
|
if err := c.admin.start(); err != nil {
|
||||||
c.log.Println("Failed to start admin socket")
|
c.log.Println("Failed to start admin socket")
|
||||||
return err
|
return err
|
||||||
|
@ -23,6 +23,7 @@ package yggdrasil
|
|||||||
// The router then runs some sanity checks before passing it to the tun
|
// The router then runs some sanity checks before passing it to the tun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/icmp"
|
"golang.org/x/net/icmp"
|
||||||
@ -32,20 +33,23 @@ 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 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.
|
// The router's mainLoop goroutine is responsible for managing all information related to the dht, searches, and crypto sessions.
|
||||||
type router struct {
|
type router struct {
|
||||||
core *Core
|
core *Core
|
||||||
addr address
|
addr address
|
||||||
in <-chan []byte // packets we received from the network, link to peer's "out"
|
subnet subnet
|
||||||
out func([]byte) // packets we're sending to the network, link to peer's "in"
|
in <-chan []byte // packets we received from the network, link to peer's "out"
|
||||||
recv chan<- []byte // place where the tun pulls received packets from
|
out func([]byte) // packets we're sending to the network, link to peer's "in"
|
||||||
send <-chan []byte // place where the tun puts outgoing packets
|
recv chan<- []byte // place where the tun pulls received packets from
|
||||||
reset chan struct{} // signal that coords changed (re-init sessions/dht)
|
send <-chan []byte // place where the tun puts outgoing packets
|
||||||
admin chan func() // pass a lambda for the admin socket to query stuff
|
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.
|
// Initializes the router struct, which includes setting up channels to/from the tun/tap.
|
||||||
func (r *router) init(core *Core) {
|
func (r *router) init(core *Core) {
|
||||||
r.core = core
|
r.core = core
|
||||||
r.addr = *address_addrForNodeID(&r.core.dht.nodeID)
|
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...
|
in := make(chan []byte, 32) // TODO something better than this...
|
||||||
p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &boxSharedKey{}, "(self)")
|
p := r.core.peers.newPeer(&r.core.boxPub, &r.core.sigPub, &boxSharedKey{}, "(self)")
|
||||||
p.out = func(packet []byte) {
|
p.out = func(packet []byte) {
|
||||||
@ -67,6 +71,7 @@ func (r *router) init(core *Core) {
|
|||||||
r.core.tun.send = send
|
r.core.tun.send = send
|
||||||
r.reset = make(chan struct{}, 1)
|
r.reset = make(chan struct{}, 1)
|
||||||
r.admin = make(chan func())
|
r.admin = make(chan func())
|
||||||
|
r.cryptokey.init(r.core)
|
||||||
// go r.mainLoop()
|
// go r.mainLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,30 +122,82 @@ 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.
|
// 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.
|
// 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) {
|
func (r *router) sendPacket(bs []byte) {
|
||||||
if len(bs) < 40 {
|
|
||||||
panic("Tried to send a packet shorter than a header...")
|
|
||||||
}
|
|
||||||
var sourceAddr address
|
var sourceAddr address
|
||||||
var sourceSubnet subnet
|
var destAddr address
|
||||||
copy(sourceAddr[:], bs[8:])
|
var destSnet subnet
|
||||||
copy(sourceSubnet[:], bs[8:])
|
var destPubKey *boxPubKey
|
||||||
if !sourceAddr.isValid() && !sourceSubnet.isValid() {
|
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:])
|
||||||
|
copy(destAddr[:addrlen], bs[16:])
|
||||||
|
} else {
|
||||||
|
// Unknown address length
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var dest address
|
if !r.cryptokey.isValidSource(sourceAddr, addrlen) {
|
||||||
copy(dest[:], bs[24:])
|
// The packet had a source address that doesn't belong to us or our
|
||||||
var snet subnet
|
// configured crypto-key routing source subnets
|
||||||
copy(snet[:], bs[24:])
|
|
||||||
if !dest.isValid() && !snet.isValid() {
|
|
||||||
return
|
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[:])
|
||||||
|
if !destAddr.isValid() && !destSnet.isValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No public key was found in the CKR table so we've exhausted our options
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
doSearch := func(packet []byte) {
|
doSearch := func(packet []byte) {
|
||||||
var nodeID, mask *NodeID
|
var nodeID, mask *NodeID
|
||||||
if dest.isValid() {
|
switch {
|
||||||
nodeID, mask = dest.getNodeIDandMask()
|
case destNodeID != nil:
|
||||||
}
|
// We already know the full node ID, probably because it's from a CKR
|
||||||
if snet.isValid() {
|
// route in which the public key is known ahead of time
|
||||||
nodeID, mask = snet.getNodeIDandMask()
|
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]
|
sinfo, isIn := r.core.searches.searches[*nodeID]
|
||||||
if !isIn {
|
if !isIn {
|
||||||
@ -153,11 +210,11 @@ func (r *router) sendPacket(bs []byte) {
|
|||||||
}
|
}
|
||||||
var sinfo *sessionInfo
|
var sinfo *sessionInfo
|
||||||
var isIn bool
|
var isIn bool
|
||||||
if dest.isValid() {
|
if destAddr.isValid() {
|
||||||
sinfo, isIn = r.core.sessions.getByTheirAddr(&dest)
|
sinfo, isIn = r.core.sessions.getByTheirAddr(&destAddr)
|
||||||
}
|
}
|
||||||
if snet.isValid() {
|
if destSnet.isValid() {
|
||||||
sinfo, isIn = r.core.sessions.getByTheirSubnet(&snet)
|
sinfo, isIn = r.core.sessions.getByTheirSubnet(&destSnet)
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case !isIn || !sinfo.init:
|
case !isIn || !sinfo.init:
|
||||||
@ -186,6 +243,14 @@ func (r *router) sendPacket(bs []byte) {
|
|||||||
}
|
}
|
||||||
fallthrough // Also send the packet
|
fallthrough // Also send the packet
|
||||||
default:
|
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
|
// Drop packets if the session MTU is 0 - this means that one or other
|
||||||
// side probably has their TUN adapter disabled
|
// side probably has their TUN adapter disabled
|
||||||
if sinfo.getMTU() == 0 {
|
if sinfo.getMTU() == 0 {
|
||||||
@ -236,29 +301,55 @@ func (r *router) sendPacket(bs []byte) {
|
|||||||
// Don't continue - drop the packet
|
// Don't continue - drop the packet
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sinfo.send <- bs
|
sinfo.send <- bs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called for incoming traffic by the session worker for that connection.
|
// 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.
|
// 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
|
// Note: called directly by the session worker, not the router goroutine
|
||||||
if len(bs) < 24 {
|
if len(bs) < 24 {
|
||||||
util_putBytes(bs)
|
util_putBytes(bs)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var source address
|
var sourceAddr address
|
||||||
copy(source[:], bs[8:])
|
var dest address
|
||||||
var snet subnet
|
var snet subnet
|
||||||
copy(snet[:], bs[8:])
|
var addrlen int
|
||||||
switch {
|
if bs[0]&0xf0 == 0x60 {
|
||||||
case source.isValid() && source == *theirAddr:
|
// IPv6 address
|
||||||
case snet.isValid() && snet == *theirSubnet:
|
addrlen = 16
|
||||||
default:
|
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 {
|
||||||
|
// 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)
|
util_putBytes(bs)
|
||||||
return
|
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:
|
||||||
|
default:
|
||||||
|
key, err := r.cryptokey.getPublicKeyForAddress(sourceAddr, addrlen)
|
||||||
|
if err != nil || key != sinfo.theirPermPub {
|
||||||
|
util_putBytes(bs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
//go func() { r.recv<-bs }()
|
//go func() { r.recv<-bs }()
|
||||||
r.recv <- bs
|
r.recv <- bs
|
||||||
}
|
}
|
||||||
|
@ -589,5 +589,5 @@ func (sinfo *sessionInfo) doRecv(p *wire_trafficPacket) {
|
|||||||
sinfo.updateNonce(&p.Nonce)
|
sinfo.updateNonce(&p.Nonce)
|
||||||
sinfo.time = time.Now()
|
sinfo.time = time.Now()
|
||||||
sinfo.bytesRecvd += uint64(len(bs))
|
sinfo.bytesRecvd += uint64(len(bs))
|
||||||
sinfo.core.router.recvPacket(bs, &sinfo.theirAddr, &sinfo.theirSubnet)
|
sinfo.core.router.recvPacket(bs, sinfo)
|
||||||
}
|
}
|
||||||
|
@ -101,10 +101,10 @@ func (tun *tunDevice) read() error {
|
|||||||
if tun.iface.IsTAP() {
|
if tun.iface.IsTAP() {
|
||||||
o = tun_ETHER_HEADER_LENGTH
|
o = tun_ETHER_HEADER_LENGTH
|
||||||
}
|
}
|
||||||
if buf[o]&0xf0 != 0x60 ||
|
switch {
|
||||||
n != 256*int(buf[o+4])+int(buf[o+5])+tun_IPv6_HEADER_LENGTH+o {
|
case 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
|
case buf[o]&0xf0 == 0x40 && n == 256*int(buf[o+2])+int(buf[o+3])+o:
|
||||||
//panic("Should not happen in testing")
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if buf[o+6] == 58 {
|
if buf[o+6] == 58 {
|
||||||
|
Loading…
Reference in New Issue
Block a user