package net import ( "bytes" "errors" "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) 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 } 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) 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) 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 { 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 new route and return packet // TODO: inject lux channel into dgram } } } return packet, nil }