lux/net/lux_router.go
2025-01-29 06:54:03 +02:00

494 lines
12 KiB
Go

package net
import (
"bytes"
"errors"
"fmt"
"lux/crypto"
"lux/proto"
"lux/rpc"
"net"
"sync"
)
type LuxRouteType int
const (
LuxRouteFromSource = 0
LuxRouteToTarget = 1
)
type LuxRoute struct {
Type LuxRouteType
Target proto.LuxID
Source proto.LuxID
Destination *net.UDPAddr
Associated *LuxChannel
Nonces LuxNonceList
}
func (route *LuxRoute) String() string {
var dir string
switch route.Type {
case LuxRouteFromSource:
dir = " <-> *"
case LuxRouteToTarget:
dir = "* <-> "
default:
dir = " <?> "
}
return fmt.Sprintf("%s%s%s %s %s",
route.Target.String(), dir, route.Source.String(),
route.Associated.Type.String(), route.Destination.IP.String())
}
/*
Routing Map Table
key TargetID <-> value Route (SourceID, SourceKey, Transport..)
Host routing:
node 1111 <-> source 0001 host A
node 1112 <-> source 0001 host A
node 1113 <-> source 0001 host A
node 1114 <-> source 0001 host A
Node routing:
host 0001 <-> source 1111 node A
host 0002 <-> source 1111 node A
host 0003 <-> source 1111 node A
host 0004 <-> source 1111 node A
node 1112 <-> source 1111 node A
Node and host:
Receives and decrypts with host key, which is TargetID (as well as LuxPacket Target)
Sends to host with host key, which is TargetID
Node 1111 and node 1112:
Node 1111 receives and decrypts with node 1112 key, which is TargetID
Node 1111 sends to node 1112 with node 1112 key, which is TargetID
But host uses host key when communicating to node, so target ID in host routing table cannot
be used. To overcome this, a "direction" field must be introduced to routing entry, that
decides whether use target or source ID-key.
*/
type LuxRouter struct {
thisKey crypto.LuxKey
keyStore crypto.LuxKeyStore
routes map[proto.LuxID]*LuxRoute
channelLock sync.RWMutex
outbound []LuxChannel
inbound []LuxChannel
dgramChan chan LuxDatagram
}
func NewLuxRouter(key crypto.LuxKey, ks crypto.LuxKeyStore) LuxRouter {
return LuxRouter{
thisKey: key,
keyStore: ks,
routes: make(map[proto.LuxID]*LuxRoute, 0),
outbound: make([]LuxChannel, 0),
inbound: make([]LuxChannel, 0),
dgramChan: make(chan LuxDatagram),
}
}
func (r *LuxRouter) GetThisKey() crypto.LuxKey {
return r.thisKey
}
func (r *LuxRouter) GetKeyStore() *crypto.LuxKeyStore {
return &r.keyStore
}
func (r *LuxRouter) GetRouterType() proto.LuxType {
return r.thisKey.Type
}
func (r *LuxRouter) addOutboundChannel(ch LuxChannel) *LuxChannel {
r.channelLock.Lock()
defer r.channelLock.Unlock()
r.outbound = append(r.outbound, ch)
channel := &r.outbound[len(r.outbound)-1]
return channel
}
func (r *LuxRouter) addInboundChannel(ch LuxChannel) *LuxChannel {
r.channelLock.Lock()
defer r.channelLock.Unlock()
r.inbound = append(r.inbound, ch)
channel := &r.inbound[len(r.inbound)-1]
return channel
}
// the ID is not destination, but rather peer associated for this route, like source ID.
// Destination router always know who is he, therefore we dont need target ID.
func (r *LuxRouter) CreateOutboundRoute(id proto.LuxID, chType LuxChannelType, udpAddr string) error {
// we gonna look up key by id from key store
key, ok := r.keyStore.Get(id)
if !ok {
return errors.New("key not found")
}
// create outbound channel
channel, err := NewLuxOutboundChannel(udpAddr, chType)
if err != nil {
return err
}
var routeType LuxRouteType
switch r.GetRouterType() {
case proto.LuxTypeHost:
if key.Type == proto.LuxTypeNode {
routeType = LuxRouteFromSource
} else {
routeType = LuxRouteToTarget
}
case proto.LuxTypeNode:
routeType = LuxRouteToTarget
}
r.routes[key.Id] = &LuxRoute{
Type: routeType,
Target: id,
Source: r.thisKey.Id,
Destination: channel.Address,
Associated: r.addOutboundChannel(channel),
Nonces: NewLuxNonceList(),
}
log.Debugf("outbound route: %s", r.routes[key.Id].String())
return nil
}
func (r *LuxRouter) CreateInboundChannel(chType LuxChannelType, udpAddr string) error {
channel, err := NewLuxInboundChannel(udpAddr, chType)
if err != nil {
return err
}
r.addInboundChannel(channel)
return nil
}
// close channel when error happened
func (r *LuxRouter) CloseChannel(channel *LuxChannel, closeInbound bool) {
r.channelLock.Lock()
defer r.channelLock.Unlock()
for i, ch := range r.outbound {
if &ch == channel {
r.outbound = append(r.outbound[:i], r.outbound[i+1:]...)
}
}
if closeInbound {
for i, ch := range r.inbound {
if &ch == channel {
ch.control <- true
r.inbound = append(r.inbound[:i], r.inbound[:i+1]...)
}
}
}
}
func (r *LuxRouter) GetDgramChannel() chan<- LuxDatagram {
return r.dgramChan
}
// goroutine to receive datagrams and send them to router over channel
func channelReceiver(r *LuxRouter, channel *LuxChannel) {
dgramChan := r.GetDgramChannel()
var dgram LuxDatagram
var err error
for {
select {
case <-channel.control:
return
default:
dgram, err = channel.Recv()
if err == nil {
dgramChan <- dgram
} else {
return
}
}
}
}
func (r *LuxRouter) Start() {
r.channelLock.RLock()
defer r.channelLock.RUnlock()
for _, inbound := range r.inbound {
go channelReceiver(r, &inbound)
}
for _, outbound := range r.outbound {
go channelReceiver(r, &outbound)
}
}
func (r *LuxRouter) Stop() {
// close all channel
for _, inbound := range r.inbound {
//inbound.Close()
r.CloseChannel(&inbound, true)
}
for _, outbound := range r.outbound {
//outbound.Close()
r.CloseChannel(&outbound, false)
}
r.routes = make(map[proto.LuxID]*LuxRoute)
}
func (r *LuxRouter) RecvDgram() LuxDatagram {
return <-r.dgramChan
}
func udpAddrEqual(addr *net.UDPAddr, other *net.UDPAddr) bool {
return addr.IP.Equal(other.IP) && addr.Port == other.Port
}
func (r *LuxRouter) GetRoutes() map[proto.LuxID]*LuxRoute {
return r.routes
}
func (r *LuxRouter) GetRoute(udpAddr *net.UDPAddr) (*LuxRoute, bool) {
for _, route := range r.routes {
if udpAddrEqual(route.Destination, udpAddr) {
return route, true
}
}
return nil, false
}
func (r *LuxRouter) DeleteRoute(route *LuxRoute) {
if _, ok := r.routes[route.Target]; !ok {
return
}
r.CloseChannel(route.Associated, false)
delete(r.routes, route.Target)
}
func (r *LuxRouter) GetRouteKey(route *LuxRoute) (crypto.LuxKey, bool) {
switch route.Type {
case LuxRouteFromSource:
return r.keyStore.Get(route.Source)
case LuxRouteToTarget:
return r.keyStore.Get(route.Target)
}
return crypto.LuxKey{}, false
}
func (r *LuxRouter) HasKeyFor(id proto.LuxID) bool {
_, ok := r.keyStore.Get(id)
return ok
}
func (r *LuxRouter) Recv() (LuxPacket, error) {
dgram := r.RecvDgram()
var err error
var packet LuxPacket
// first we look key from routes
if route, ok := r.GetRoute(dgram.Target); ok {
key, _ := r.GetRouteKey(route)
packet, err = DecryptLuxPacket(dgram, key)
if err != nil {
log.Debugf("DecryptLuxPacket err %v for route %v", err, route)
return packet, err
}
// check if LuxID matches
// Node replies to host shall be matched by source ID on the host
var routeId *proto.LuxID
targetKey, _ := r.keyStore.Get(route.Target)
if r.GetRouterType() == proto.LuxTypeHost {
if targetKey.Type == proto.LuxTypeNode {
routeId = &route.Source
} else {
routeId = &route.Target
}
} else {
// on the node, we always operate by target ID
routeId = &route.Target
}
if !bytes.Equal(packet.Target.UUID[:], routeId.UUID[:]) {
// not matches.. we discard route and throw away packet
log.Infof("packet from %s received at route %v has targetID %s that mismatches associated target UUID %s",
dgram.Target.String(), route, packet.Target.String(), route.Target.String())
r.DeleteRoute(route)
return packet, errors.New("bogus packet from established route")
/* NOTE:
* there may be rare situation where multiple clients behind NAT/CGNAT
* reusing same IP:Port of established route, confusing routing table.
* This is not critical, since hosts and nodes are expected to send
* data again after interval of time, and packet loss is expected and
* tolerated at protocol design level.
*/
}
// packet arrived to right route and successfully decrypted
packet.ChannelType = dgram.Channel.Type
return packet, nil
} else {
// first time seeing peer - bruteforce keys from keystore
for _, key := range r.keyStore.Keys() {
packet, err = DecryptLuxPacket(dgram, key)
if err != nil {
continue
}
if bytes.Equal(packet.Target.UUID[:], key.Id.UUID[:]) {
// key UUID and decrypted UUID matching - create OR update route and return packet
var ok bool
var route *LuxRoute
if route, ok = r.routes[packet.Target]; ok {
log.Debugf("updating route %s: %s -> %s", route.Target.String,
route.Destination.String(), dgram.Target.String())
route.Destination = dgram.Target
route.Associated = dgram.Channel
// since packet arrived from different transport, we flush nonces
route.Nonces.Flush()
} else {
// If we're host sending to node, then we must encypt with source (host) key,
// if we're node or host sending to host, then as usual - target key
var routeType LuxRouteType
if r.GetRouterType() == proto.LuxTypeHost {
if key.Type == proto.LuxTypeNode {
routeType = LuxRouteFromSource
} else {
routeType = LuxRouteToTarget
}
} else {
routeType = LuxRouteToTarget
}
r.routes[key.Id] = &LuxRoute{
Type: routeType,
Target: key.Id,
Source: r.thisKey.Id,
Destination: dgram.Target,
Associated: dgram.Channel,
Nonces: NewLuxNonceList(),
}
route = r.routes[key.Id]
log.Debugf("established route: %s", route.String())
}
// rotate nonce
if !route.Nonces.RotateOrFail(packet.Nonce) {
// failed nonce, discard packet
log.Debug("failed nonce")
return packet, fmt.Errorf("packet failed nonce check")
}
packet.ChannelType = dgram.Channel.Type
return packet, nil
}
}
}
return packet, fmt.Errorf("non-peer packet from %s", dgram.Target.String())
}
func (r *LuxRouter) Send(packet LuxPacket) error {
route, ok := r.routes[packet.Target]
if !ok {
return errors.New("no route to peer")
}
key, _ := r.GetRouteKey(route)
packet.Nonce = GenerateLuxNonce()
dgram, err := EncryptLuxPacket(packet, key, route.Destination)
if err != nil {
return err
}
if err := route.Associated.Send(dgram); err != nil {
log.Warningf("multicast to %s failed: %v", dgram.Target, err)
return err
}
return nil
}
// In multicast we send packet to all routes by group. Group is lux peer type
// which is always determined by target ID-key, even in LuxRouteFromSource,
// since target is node, but we encrypt with source ID-key.
func (r *LuxRouter) Multicast(packet LuxPacket, group proto.LuxType) error {
for _, route := range r.routes {
key, _ := r.GetRouteKey(route)
targetKey, _ := r.keyStore.Get(route.Target)
if targetKey.Type == group {
packet.Nonce = GenerateLuxNonce()
dgram, err := EncryptLuxPacket(packet, key, route.Destination)
if err != nil {
return err
}
if err = route.Associated.Send(dgram); err != nil {
log.Warningf("multicast to %s failed: %v", dgram.Target, err)
continue
}
}
}
return nil
}
// RPC
func (route *LuxRoute) IntoRpc() rpc.LuxRpcRoute {
return rpc.LuxRpcRoute{
Type: int(route.Type),
ChannelType: route.Associated.Type.String(),
Target: route.Target.String(),
Source: route.Source.String(),
Destination: route.Destination.String(),
}
}
func (r *LuxRouter) GetRpcName() string {
return "router"
}
func (r *LuxRouter) Register(sv *rpc.LuxRpcServer) {
sv.RegisterController(&r.keyStore)
}
func (r *LuxRouter) Handle(request rpc.LuxRpcRequest, rpcType rpc.LuxRpcType) (rpc.LuxRpcResponse, rpc.LuxRpcError, bool) {
if request.Command == "get" {
rpcRoutes := make([]rpc.LuxRpcRoute, 0)
for _, route := range r.routes {
rpcRoutes = append(rpcRoutes, route.IntoRpc())
}
return rpc.LuxRpcResponse{Routes: rpcRoutes}, rpc.LuxRpcError{}, true
}
return rpc.LuxRpcResponse{}, rpc.LUX_RPC_ERROR_UNKNOWN_COMMAND, false
}