mirror of
https://github.com/yggdrasil-network/yggdrasil-go.git
synced 2024-11-27 12:05:23 +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 {
|
||||
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,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. 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."`
|
||||
}
|
||||
|
||||
@ -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,12 @@ 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 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
|
||||
}
|
||||
|
||||
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 {
|
||||
c.log.Println("Failed to start admin socket")
|
||||
return err
|
||||
|
@ -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"
|
||||
@ -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'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
|
||||
subnet subnet
|
||||
in <-chan []byte // packets we received from the network, link to peer's "out"
|
||||
out func([]byte) // packets we're sending to the network, link to peer's "in"
|
||||
recv chan<- []byte // place where the tun pulls received packets from
|
||||
send <-chan []byte // place where the tun puts outgoing packets
|
||||
reset chan struct{} // signal that coords changed (re-init sessions/dht)
|
||||
admin chan func() // pass a lambda for the admin socket to query stuff
|
||||
cryptokey cryptokey
|
||||
}
|
||||
|
||||
// Initializes the router struct, which includes setting up channels to/from the tun/tap.
|
||||
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) {
|
||||
@ -67,6 +71,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()
|
||||
}
|
||||
|
||||
@ -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.
|
||||
// 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 sourceSubnet subnet
|
||||
copy(sourceAddr[:], bs[8:])
|
||||
copy(sourceSubnet[:], bs[8:])
|
||||
if !sourceAddr.isValid() && !sourceSubnet.isValid() {
|
||||
var destAddr address
|
||||
var destSnet subnet
|
||||
var destPubKey *boxPubKey
|
||||
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
|
||||
}
|
||||
var dest address
|
||||
copy(dest[:], bs[24:])
|
||||
var snet subnet
|
||||
copy(snet[:], bs[24:])
|
||||
if !dest.isValid() && !snet.isValid() {
|
||||
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[:])
|
||||
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) {
|
||||
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 {
|
||||
@ -153,11 +210,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:
|
||||
@ -186,6 +243,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 {
|
||||
@ -236,29 +301,55 @@ func (r *router) sendPacket(bs []byte) {
|
||||
// Don't continue - drop the packet
|
||||
return
|
||||
}
|
||||
|
||||
sinfo.send <- bs
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
return
|
||||
}
|
||||
var source address
|
||||
copy(source[:], bs[8:])
|
||||
var sourceAddr address
|
||||
var dest address
|
||||
var snet subnet
|
||||
copy(snet[:], bs[8:])
|
||||
switch {
|
||||
case source.isValid() && source == *theirAddr:
|
||||
case snet.isValid() && snet == *theirSubnet:
|
||||
default:
|
||||
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 {
|
||||
// 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:
|
||||
default:
|
||||
key, err := r.cryptokey.getPublicKeyForAddress(sourceAddr, addrlen)
|
||||
if err != nil || key != sinfo.theirPermPub {
|
||||
util_putBytes(bs)
|
||||
return
|
||||
}
|
||||
}
|
||||
//go func() { r.recv<-bs }()
|
||||
r.recv <- bs
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -101,10 +101,10 @@ 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")
|
||||
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 {
|
||||
|
Loading…
Reference in New Issue
Block a user