lux/net/lux_router.go
2025-01-14 10:11:24 +02:00

323 lines
7.7 KiB
Go

package net
import (
"bytes"
"errors"
"fmt"
"lux/crypto"
"lux/proto"
"net"
"sync"
)
type LuxRoute struct {
Key crypto.LuxKey
Destination *net.UDPAddr
Associated *LuxChannel
Nonces LuxNonceList
}
// !!!!!
// TODO: map key can be destination ID, while Key (+ ID) in route struct would be SOURCE
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) addOutboundChannel(ch LuxChannel) *LuxChannel {
r.channelLock.Lock()
r.outbound = append(r.outbound, ch)
channel := &r.outbound[len(r.outbound)-1]
r.channelLock.Unlock()
return channel
}
func (r *LuxRouter) addInboundChannel(ch LuxChannel) *LuxChannel {
r.channelLock.Lock()
r.inbound = append(r.inbound, ch)
channel := &r.inbound[len(r.inbound)-1]
r.channelLock.Unlock()
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
}
r.routes[key.Id] = &LuxRoute{
Key: key,
Destination: channel.Address,
Associated: r.addOutboundChannel(channel),
Nonces: NewLuxNonceList(),
}
return nil
}
func (r *LuxRouter) CreateInboundChannel(chType LuxChannelType, udpAddr string) error {
channel, err := NewLuxInboundChannel(udpAddr, chType)
if err != nil {
return err
}
r.routes[r.thisKey.Id] = &LuxRoute{
Key: r.thisKey,
Destination: channel.Address,
Associated: r.addInboundChannel(channel),
Nonces: NewLuxNonceList(),
}
return nil
}
// close channel when error happened
func (r *LuxRouter) CloseChannel(channel *LuxChannel, closeInbound bool) {
r.channelLock.Lock()
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 {
r.inbound = append(r.inbound[:i], r.inbound[:i+1]...)
}
}
}
r.channelLock.Unlock()
}
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 err == nil {
dgram, err = channel.Recv()
dgramChan <- dgram
}
r.CloseChannel(channel, true)
}
func (r *LuxRouter) Start() {
r.channelLock.RLock()
for _, inbound := range r.inbound {
go channelReceiver(r, &inbound)
}
r.channelLock.RUnlock()
}
func (r *LuxRouter) Stop() {
// close all channels
r.channelLock.Lock()
for _, inbound := range r.inbound {
inbound.Close()
}
r.inbound = r.inbound[0:]
for _, outbound := range r.outbound {
outbound.Close()
}
r.outbound = r.outbound[0:]
r.channelLock.Unlock()
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.Key.Id]; !ok {
return
}
r.CloseChannel(route.Associated, false)
delete(r.routes, route.Key.Id)
}
func (r *LuxRouter) Recv() (LuxPacket, error) {
dgram := r.RecvDgram()
var err error
var packet LuxPacket
/* BUG:
* nodes share same UUID. When updating routes by UUID, it will overwrite others' nodes routes.
* this is current limitation of protocol
*/
// first we look key from routes
if route, ok := r.GetRoute(dgram.Target); ok {
packet, err = DecryptLuxPacket(dgram, route.Key)
if err != nil {
// do we really fail here?
log.Debugf("DecryptLuxPacket err %v for route %v", err, route)
return packet, err
}
// check if LuxID matches
if !bytes.Equal(packet.Target.UUID[:], route.Key.Id.UUID[:]) {
// not matches.. we discard route and throw away packet
log.Infof("packet from %s received at route %v mismatches associated target UUID %s",
dgram.Target.String(), route, route.Key.Id.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
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.Key.Id.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 {
r.routes[key.Id] = &LuxRoute{
Key: key,
Destination: dgram.Target,
Associated: dgram.Channel,
Nonces: NewLuxNonceList(),
}
route = r.routes[key.Id]
log.Debugf("established route %s <-> %s", route.Key.Id.String(), route.Destination.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")
}
packet.Nonce = GenerateLuxNonce()
dgram, err := EncryptLuxPacket(packet, route.Key, route.Destination)
if err != nil {
return err
}
// TODO: close route if it fails?
return route.Associated.Send(dgram)
}
func (r *LuxRouter) Multicast(packet LuxPacket, group proto.LuxType) error {
for _, route := range r.routes {
if route.Key.Type == group {
packet.Nonce = GenerateLuxNonce()
dgram, err := EncryptLuxPacket(packet, route.Key, route.Destination)
if err != nil {
return err
}
if err = route.Associated.Send(dgram); err != nil {
// TODO: close route if it fails?
return err
}
}
}
return nil
}