lux/net/lux_router.go

305 lines
6.9 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
}
type LuxRouter struct {
thisKey crypto.LuxKey
keyStore crypto.LuxKeyStore
routes []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([]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 = append(r.routes, LuxRoute{
Key: key,
Destination: channel.Address,
Associated: r.addOutboundChannel(channel),
})
return nil
}
func (r *LuxRouter) CreateInboundChannel(chType LuxChannelType, udpAddr string) error {
channel, err := NewLuxInboundChannel(udpAddr, chType)
if err != nil {
return err
}
r.routes = append(r.routes, LuxRoute{
Key: r.thisKey,
Destination: channel.Address,
Associated: r.addInboundChannel(channel),
})
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 = r.routes[0:]
}
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) getRouteIndex(udpAddr *net.UDPAddr) (LuxRoute, int) {
for idx, route := range r.routes {
if udpAddrEqual(route.Destination, udpAddr) {
return route, idx
}
}
return LuxRoute{}, -1
}
func (r *LuxRouter) getRouteIndexByID(id proto.LuxID) (LuxRoute, int) {
for idx, route := range r.routes {
if bytes.Equal(route.Key.Id.UUID[:], id.UUID[:]) {
return route, idx
}
}
return LuxRoute{}, -1
}
func (r *LuxRouter) GetRoutes() []LuxRoute {
return r.routes
}
func (r *LuxRouter) GetRoute(udpAddr *net.UDPAddr) (LuxRoute, bool) {
route, idx := r.getRouteIndex(udpAddr)
return route, idx != -1
}
func (r *LuxRouter) DeleteRoute(route *LuxRoute) {
_, idx := r.getRouteIndex(route.Destination)
if idx == -1 {
return
}
r.CloseChannel(route.Associated, false)
r.routes = append(r.routes[idx:], r.routes[:idx+1]...)
}
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 {
packet, err = DecryptLuxPacket(dgram, route.Key)
if err != nil {
// do we really fail here?
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
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
if _, idx := r.getRouteIndexByID(packet.Target); idx != -1 {
route := &r.routes[idx]
route.Destination = dgram.Target
route.Associated = dgram.Channel
} else {
r.routes = append(r.routes, LuxRoute{
Key: key,
Destination: dgram.Target,
Associated: dgram.Channel,
})
}
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, idx := r.getRouteIndexByID(packet.Target)
if idx == -1 {
return errors.New("no route to peer")
}
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) error {
for _, route := range r.routes {
if bytes.Equal(route.Key.Id.UUID[:], packet.Target.UUID[:]) {
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
}