WIP - cleanup / local discovery

This commit is contained in:
jdl
2024-12-30 09:26:48 +01:00
parent f47a8245b4
commit 8407fd5b48
27 changed files with 523 additions and 151 deletions

View File

@@ -6,7 +6,6 @@ import (
"crypto/rand"
)
// TODO: Use [32]byte for simplicity everywhere.
type dataCipher struct {
key [32]byte
aead cipher.AEAD
@@ -20,7 +19,6 @@ func newDataCipher() *dataCipher {
return newDataCipherFromKey(key)
}
// key must be 32 bytes.
func newDataCipherFromKey(key [32]byte) *dataCipher {
block, err := aes.NewCipher(key[:])
if err != nil {

13
node/cipher-discovery.go Normal file
View File

@@ -0,0 +1,13 @@
package node
/*
func signData(privKey *[64]byte, h header, data, out []byte) []byte {
out = out[:headerSize]
h.Marshal(out)
return sign.Sign(out, data, privKey)
}
func openData(pubKey *[32]byte, signed, out []byte) (data []byte, ok bool) {
return sign.Open(out[:0], signed[headerSize:], pubKey)
}
*/

View File

@@ -25,7 +25,7 @@ func (w *connWriter) WriteTo(packet []byte, addr netip.AddrPort) {
// packets may fail to be sent in a timely manner causing timeouts.
w.lock.Lock()
if _, err := w.conn.WriteToUDPAddrPort(packet, addr); err != nil {
log.Fatalf("Failed to write to UDP port: %v", err)
log.Printf("Failed to write to UDP port: %v", err)
}
w.lock.Unlock()
}

View File

@@ -20,6 +20,7 @@ type peerRoute struct {
Up bool // True if data can be sent on the route.
Relay bool // True if the peer is a relay.
Direct bool // True if this is a direct connection.
PubSignKey []byte
ControlCipher *controlCipher
DataCipher *dataCipher
RemoteAddr netip.AddrPort // Remote address if directly connected.
@@ -27,10 +28,11 @@ type peerRoute struct {
var (
// Configuration for this peer.
netName string
localIP byte
localPub bool
privateKey []byte
netName string
localIP byte
localPub bool
privKey []byte
privSignKey []byte
// Shared interface for writing.
_iface *ifWriter
@@ -80,7 +82,7 @@ var (
}()
// Managed by the relayManager.
discoveryPackets chan controlPacket
localAddr *atomic.Pointer[netip.AddrPort] // May be nil.
relayIP *atomic.Pointer[byte] // May be nil.
discoveryPackets = make(chan controlPacket, 256)
localAddr = &atomic.Pointer[netip.AddrPort]{}
relayIP = &atomic.Pointer[byte]{}
)

75
node/localbroadcaster.go Normal file
View File

@@ -0,0 +1,75 @@
package node
import (
"encoding/binary"
"log"
"net"
"net/netip"
"time"
)
func localBroadcaster() {
var (
buf1 = make([]byte, bufferSize)
buf2 = make([]byte, bufferSize)
)
time.Sleep(4 * time.Second)
doBroadcast(buf1, buf2)
for range time.Tick(32 * time.Second) {
doBroadcast(buf1, buf2)
}
}
func doBroadcast(buf1, buf2 []byte) {
ifaces, err := net.Interfaces()
if err != nil {
log.Printf("Failed to list interfaces: %v", err)
return
}
for _, iface := range ifaces {
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagRunning == 0 {
continue
}
if iface.Flags&net.FlagPointToPoint != 0 {
continue
}
if iface.Flags&net.FlagBroadcast == 0 {
continue
}
addrs, err := iface.Addrs()
if err != nil {
log.Printf("Failed to get interface addresses: %v", err)
continue
}
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok {
continue
}
ip4 := ipNet.IP.To4()
if ip4 == nil {
continue
}
ip, ok := lastAddr(ipNet)
if !ok {
log.Printf("Failed to find broadcast address: %v", ipNet)
continue
}
log.Printf("Broadcasting on address: %v", ip)
//addr := netip.AddrPortFrom(ip, 456)
}
}
}
// works when the n is a prefix, otherwise...
func lastAddr(n *net.IPNet) (netip.Addr, bool) {
ip := make(net.IP, len(n.IP.To4()))
binary.BigEndian.PutUint32(ip, binary.BigEndian.Uint32(n.IP.To4())|
^binary.BigEndian.Uint32(net.IP(n.Mask).To4()))
return netip.AddrFromSlice(ip)
}

101
node/localdiscovery.go Normal file
View File

@@ -0,0 +1,101 @@
package node
import (
"log"
"net"
"net/netip"
"time"
"golang.org/x/crypto/nacl/sign"
)
var (
signOverhead = 64
multicastIP = netip.AddrFrom4([4]byte{224, 0, 0, 157})
multicastAddr = net.UDPAddrFromAddrPort(netip.AddrPortFrom(multicastIP, 4560))
)
func localDiscovery() {
conn, err := net.ListenMulticastUDP("udp", nil, multicastAddr)
if err != nil {
log.Printf("Failed to bind to multicast address: %v", err)
return
}
go sendLocalDiscovery(conn)
go recvLocalDiscovery(conn)
}
func sendLocalDiscovery(conn *net.UDPConn) {
var (
buf1 = make([]byte, bufferSize)
buf2 = make([]byte, bufferSize)
)
for range time.Tick(16 * time.Second) {
signed := buildLocalDiscoveryPacket(buf1, buf2)
if _, err := conn.WriteToUDP(signed, multicastAddr); err != nil {
log.Printf("Failed to write multicast UDP packet: %v", err)
}
}
}
func recvLocalDiscovery(conn *net.UDPConn) {
var (
raw = make([]byte, bufferSize)
buf = make([]byte, bufferSize)
)
for {
n, remoteAddr, err := conn.ReadFromUDPAddrPort(raw[:bufferSize])
if err != nil {
log.Fatalf("Failed to read from UDP port (multicast): %v", err)
}
raw = raw[:n]
h, ok := openLocalDiscoveryPacket(raw, buf)
if !ok {
continue
}
pkt := controlPacket{
SrcIP: h.SourceIP,
SrcAddr: remoteAddr,
Payload: localDiscoveryPacket{},
}
select {
case controlPackets[h.SourceIP] <- pkt:
default:
}
}
}
func buildLocalDiscoveryPacket(buf1, buf2 []byte) []byte {
h := header{
StreamID: controlStreamID,
Counter: 0,
SourceIP: localIP,
DestIP: 255,
}
out := buf1[:headerSize]
h.Marshal(out)
return sign.Sign(buf2[:0], out, (*[64]byte)(privSignKey))
}
func openLocalDiscoveryPacket(raw, buf []byte) (h header, ok bool) {
if len(raw) != headerSize+signOverhead {
ok = false
return
}
h.Parse(raw[signOverhead:])
route := routingTable[h.SourceIP].Load()
if route == nil || route.PubSignKey == nil {
ok = false
return
}
_, ok = sign.Open(buf[:0], raw, (*[32]byte)(route.PubSignKey))
return
}

View File

@@ -0,0 +1,35 @@
package node
import (
"bytes"
"crypto/rand"
"testing"
"golang.org/x/crypto/nacl/sign"
)
func TestLocalDiscoveryPacketSigning(t *testing.T) {
localIP = 32
var (
buf1 = make([]byte, bufferSize)
buf2 = make([]byte, bufferSize)
pubSignKey, privSigKey, _ = sign.GenerateKey(rand.Reader)
)
privSignKey = privSigKey[:]
route := routingTable[localIP].Load()
route.IP = byte(localIP)
route.PubSignKey = pubSignKey[0:32]
routingTable[localIP].Store(route)
out := buildLocalDiscoveryPacket(buf1, buf2)
h, ok := openLocalDiscoveryPacket(bytes.Clone(out), buf1)
if !ok {
t.Fatal(h, ok)
}
if h.StreamID != controlStreamID || h.SourceIP != localIP || h.DestIP != 255 {
t.Fatal(h)
}
}

View File

@@ -11,7 +11,6 @@ import (
"net/netip"
"os"
"runtime/debug"
"sync/atomic"
"vppn/m"
)
@@ -50,10 +49,6 @@ func Main() {
}
func mainInit(initURL string) {
if _, err := loadPeerConfig(netName); err == nil {
log.Fatalf("Network is already initialized.")
}
resp, err := http.Get(initURL)
if err != nil {
log.Fatalf("Failed to fetch data from hub: %v", err)
@@ -102,14 +97,14 @@ func main(listenIP string, port uint16) {
log.Fatalf("Failed to open UDP port: %v", err)
}
conn.SetReadBuffer(1024 * 1024 * 8)
conn.SetWriteBuffer(1024 * 1024 * 8)
// Intialize globals.
_iface = newIFWriter(iface)
_conn = newConnWriter(conn)
localIP = config.PeerIP
discoveryPackets = make(chan controlPacket, 256)
localAddr = &atomic.Pointer[netip.AddrPort]{}
relayIP = &atomic.Pointer[byte]{}
ip, ok := netip.AddrFromSlice(config.PublicIP)
if ok {
@@ -118,7 +113,8 @@ func main(listenIP string, port uint16) {
localAddr.Store(&addr)
}
privateKey = config.PrivKey
privKey = config.PrivKey
privSignKey = config.PrivSignKey
// Start supervisors.
for i := range 256 {
@@ -130,7 +126,9 @@ func main(listenIP string, port uint16) {
} else {
go addrDiscoveryClient()
go relayManager()
go localDiscovery()
}
go newHubPoller(config).Run()
go readFromConn(conn)
readFromIFace(iface)
@@ -206,17 +204,17 @@ func handleControlPacket(addr netip.AddrPort, h header, data, decBuf []byte) {
out, ok := route.ControlCipher.Decrypt(data, decBuf)
if !ok {
//log.Printf("Failed to decrypt control packet.")
log.Printf("Failed to decrypt control packet.")
return
}
if len(out) == 0 {
//log.Printf("Empty control packet from: %d", h.SourceIP)
log.Printf("Empty control packet from: %d", h.SourceIP)
return
}
if dupChecks[h.SourceIP].IsDup(h.Counter) {
//log.Printf("[%03d] Duplicate control packet: %d", h.SourceIP, h.Counter)
log.Printf("[%03d] Duplicate control packet: %d", h.SourceIP, h.Counter)
return
}
@@ -252,7 +250,7 @@ func handleControlPacket(addr netip.AddrPort, h header, data, decBuf []byte) {
func handleDataPacket(h header, data []byte, decBuf []byte) {
route := routingTable[h.SourceIP].Load()
if !route.Up {
//log.Printf("Not connected (recv).")
log.Printf("Not connected (recv).")
return
}
@@ -263,7 +261,7 @@ func handleDataPacket(h header, data []byte, decBuf []byte) {
}
if dupChecks[h.SourceIP].IsDup(h.Counter) {
//log.Printf("[%03d] Duplicate data packet: %d", h.SourceIP, h.Counter)
log.Printf("[%03d] Duplicate data packet: %d", h.SourceIP, h.Counter)
return
}

1
node/messages.go Normal file
View File

@@ -0,0 +1 @@
package node

View File

@@ -138,3 +138,7 @@ func parseProbePacket(buf []byte) (p probePacket, err error) {
Error()
return
}
// ----------------------------------------------------------------------------
type localDiscoveryPacket struct{}

View File

@@ -10,7 +10,6 @@ import (
func TestPacketSyn(t *testing.T) {
in := synPacket{
TraceID: newTraceID(),
RelayIP: 4,
FromAddr: netip.AddrPortFrom(netip.AddrFrom4([4]byte{4, 5, 6, 7}), 22),
}
rand.Read(in.SharedKey[:])

View File

@@ -115,7 +115,8 @@ func (s *peerSupervisor) _peerUpdate(peer *m.Peer) stateFunc {
}
s.staged.IP = s.remoteIP
s.staged.ControlCipher = newControlCipher(privateKey, peer.PubKey)
s.staged.ControlCipher = newControlCipher(privKey, peer.PubKey)
s.staged.PubSignKey = peer.PubSignKey
s.staged.DataCipher = newDataCipher()
if ip, isValid := netip.AddrFromSlice(peer.PublicIP); isValid {
@@ -241,7 +242,7 @@ func (s *peerSupervisor) client() stateFunc {
probe probePacket
probeAddr netip.AddrPort
lAddr netip.AddrPort
remoteAddr netip.AddrPort
timeoutTimer = time.NewTimer(timeoutInterval)
pingTimer = time.NewTimer(pingInterval)
@@ -306,9 +307,9 @@ func (s *peerSupervisor) client() stateFunc {
// Send syn.
syn.FromAddr = getLocalAddr()
if syn.FromAddr != lAddr {
if syn.FromAddr != remoteAddr {
syn.TraceID = newTraceID()
lAddr = syn.FromAddr
remoteAddr = syn.FromAddr
}
s.sendControlPacket(syn)
@@ -319,6 +320,9 @@ func (s *peerSupervisor) client() stateFunc {
continue
}
// TODO: Check if we have local address.
// TODO: Send local probe
if !ack.FromAddr.IsValid() {
continue
}

1
node/signing.go Normal file
View File

@@ -0,0 +1 @@
package node