5
0
mirror of https://github.com/cwinfo/yggdrasil-go.git synced 2024-11-15 16:40:27 +00:00
yggdrasil-go/src/yggdrasil/ckr.go

244 lines
6.2 KiB
Go
Raw Normal View History

package yggdrasil
import (
2018-11-06 14:28:57 +00:00
"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
}
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)
}
2018-11-06 12:32:16 +00:00
func (c *cryptokey) setEnabled(enabled bool) {
c.enabled = enabled
}
func (c *cryptokey) isEnabled() bool {
return c.enabled
}
2018-11-06 20:49:19 +00:00
func (c *cryptokey) isValidSource(addr address, addrlen int) bool {
ip := net.IP(addr[:addrlen])
2018-11-06 20:49:19 +00:00
if addrlen == net.IPv6len {
// Does this match our node's address?
if bytes.Equal(addr[:16], c.core.router.addr[:16]) {
return true
}
2018-11-06 20:49:19 +00:00
// 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() {
2018-11-06 20:49:19 +00:00
// 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
}
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
}
2018-11-06 20:04:49 +00:00
// 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
2018-11-06 20:04:49 +00:00
for _, subnet := range *routingsources {
if subnet.String() == ipnet.String() {
return errors.New("Source subnet already configured")
}
}
// Add the source subnet
2018-11-06 20:04:49 +00:00
*routingsources = append(*routingsources, *ipnet)
2018-11-06 11:56:32 +00:00
c.core.log.Println("Added CKR source subnet", cidr)
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)
if err != nil {
return err
}
// Get the prefix length and size
_, prefixsize := ipnet.Mask.Size()
2018-11-06 20:04:49 +00:00
// 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 {
2018-11-06 20:04:49 +00:00
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))
}
2018-11-06 20:04:49 +00:00
}
// 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)
}
2018-11-06 20:04:49 +00:00
c.core.log.Println("Added CKR destination subnet", cidr)
return nil
}
2018-11-06 20:04:49 +00:00
return errors.New("Unspecified error")
}
2018-11-06 20:04:49 +00:00
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() {
2018-11-05 23:22:45 +00:00
return boxPubKey{}, errors.New("Cannot look up CKR for Yggdrasil addresses")
}
2018-11-06 20:04:49 +00:00
// 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
2018-11-06 20:04:49 +00:00
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
2018-11-06 20:04:49 +00:00
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()))
}