diff --git a/hub/api/api.go b/hub/api/api.go index 975149d..8870a85 100644 --- a/hub/api/api.go +++ b/hub/api/api.go @@ -15,6 +15,7 @@ import ( "git.crumpington.com/lib/go/sqliteutil" "golang.org/x/crypto/bcrypt" "golang.org/x/crypto/nacl/box" + "golang.org/x/crypto/nacl/sign" ) //go:embed migrations @@ -24,6 +25,7 @@ type API struct { db *sql.DB lock sync.Mutex peerIntents map[string]PeerCreateArgs + initIntents map[string]byte // Map from intent key to peer IP } func New(dbPath string) (*API, error) { @@ -39,6 +41,7 @@ func New(dbPath string) (*API, error) { a := &API{ db: sqlDB, peerIntents: map[string]PeerCreateArgs{}, + initIntents: map[string]byte{}, } return a, a.ensurePassword() @@ -141,6 +144,16 @@ func (a *API) Session_SignIn(s *Session, pwd string) error { return db.Session_SetSignedIn(a.db, s.SessionID) } +func (a *API) Peer_CreateNew(p *Peer) error { + p.Version = idgen.NextID(0) + p.PubKey = []byte{} + p.PubSignKey = []byte{} + p.APIKey = idgen.NewToken() + + return db.Peer_Insert(a.db, p) +} + +// TODO: Remove type PeerCreateArgs struct { Name string PublicIP []byte @@ -148,6 +161,7 @@ type PeerCreateArgs struct { Relay bool } +// TODO: Remove // Create the intention to add a peer. The returned code is used to complete // the peer creation. The code is valid for 5 minutes. func (a *API) Peer_CreateIntent(args PeerCreateArgs) string { @@ -167,6 +181,78 @@ func (a *API) Peer_CreateIntent(args PeerCreateArgs) string { return code } +// Create the intention to initialize a peer. The returned code is used to +// complete the peer initialization. The code is valid for 5 minutes. +func (a *API) Peer_CreateInitIntent(peerIP byte) string { + a.lock.Lock() + defer a.lock.Unlock() + + code := idgen.NewToken() + a.initIntents[code] = peerIP + + go func() { + time.Sleep(5 * time.Minute) + a.lock.Lock() + defer a.lock.Unlock() + delete(a.initIntents, code) + }() + + return code +} + +func (a *API) Peer_Init(initCode string) (*m.PeerConfig, error) { + a.lock.Lock() + defer a.lock.Unlock() + + ip, ok := a.initIntents[initCode] + if !ok { + return nil, ErrNotAuthorized + } + + peer, err := a.Peer_Get(ip) + if err != nil { + return nil, err + } + + delete(a.initIntents, initCode) + + encPubKey, encPrivKey, err := box.GenerateKey(rand.Reader) + if err != nil { + return nil, err + } + + signPubKey, signPrivKey, err := sign.GenerateKey(rand.Reader) + if err != nil { + return nil, err + } + + peer.Version = idgen.NextID(0) + peer.APIKey = idgen.NewToken() + peer.PubKey = encPubKey[:] + peer.PubSignKey = signPubKey[:] + + if err := db.Peer_UpdateFull(a.db, peer); err != nil { + return nil, err + } + + conf := a.Config_Get() + + return &m.PeerConfig{ + PeerIP: peer.PeerIP, + HubAddress: conf.HubAddress, + APIKey: peer.APIKey, + Network: conf.VPNNetwork, + PublicIP: peer.PublicIP, + Port: peer.Port, + Relay: peer.Relay, + PubKey: encPubKey[:], + PrivKey: encPrivKey[:], + PubSignKey: signPubKey[:], + PrivSignKey: signPrivKey[:], + }, nil +} + +// TODO: Remove func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) { a.lock.Lock() defer a.lock.Unlock() @@ -183,6 +269,11 @@ func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) { return nil, err } + signPubKey, signPrivKey, err := sign.GenerateKey(rand.Reader) + if err != nil { + return nil, err + } + // Get peer IP. peerIP := byte(0) @@ -202,14 +293,15 @@ func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) { } peer := &Peer{ - PeerIP: peerIP, - Version: idgen.NextID(0), - APIKey: idgen.NewToken(), - Name: args.Name, - PublicIP: args.PublicIP, - Port: args.Port, - Relay: args.Relay, - PubKey: encPubKey[:], + PeerIP: peerIP, + Version: idgen.NextID(0), + APIKey: idgen.NewToken(), + Name: args.Name, + PublicIP: args.PublicIP, + Port: args.Port, + Relay: args.Relay, + PubKey: encPubKey[:], + PubSignKey: signPubKey[:], } if err := db.Peer_Insert(a.db, peer); err != nil { @@ -219,15 +311,17 @@ func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) { conf := a.Config_Get() return &m.PeerConfig{ - PeerIP: peer.PeerIP, - HubAddress: conf.HubAddress, - APIKey: peer.APIKey, - Network: conf.VPNNetwork, - PublicIP: peer.PublicIP, - Port: peer.Port, - Relay: peer.Relay, - PubKey: encPubKey[:], - PrivKey: encPrivKey[:], + PeerIP: peer.PeerIP, + HubAddress: conf.HubAddress, + APIKey: peer.APIKey, + Network: conf.VPNNetwork, + PublicIP: peer.PublicIP, + Port: peer.Port, + Relay: peer.Relay, + PubKey: encPubKey[:], + PrivKey: encPrivKey[:], + PubSignKey: signPubKey[:], + PrivSignKey: signPrivKey[:], }, nil } diff --git a/hub/api/db/generated.go b/hub/api/db/generated.go index 1957b6f..1548afd 100644 --- a/hub/api/db/generated.go +++ b/hub/api/db/generated.go @@ -307,17 +307,18 @@ func Session_List( // ---------------------------------------------------------------------------- type Peer struct { - PeerIP byte - Version int64 - APIKey string - Name string - PublicIP []byte - Port uint16 - Relay bool - PubKey []byte + PeerIP byte + Version int64 + APIKey string + Name string + PublicIP []byte + Port uint16 + Relay bool + PubKey []byte + PubSignKey []byte } -const Peer_SelectQuery = "SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey FROM peers" +const Peer_SelectQuery = "SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey FROM peers" func Peer_Insert( tx TX, @@ -328,7 +329,7 @@ func Peer_Insert( return err } - _, err = tx.Exec("INSERT INTO peers(PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey) VALUES(?,?,?,?,?,?,?,?)", row.PeerIP, row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey) + _, err = tx.Exec("INSERT INTO peers(PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey) VALUES(?,?,?,?,?,?,?,?,?)", row.PeerIP, row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey, row.PubSignKey) return err } @@ -369,7 +370,7 @@ func Peer_UpdateFull( return err } - result, err := tx.Exec("UPDATE peers SET Version=?,APIKey=?,Name=?,PublicIP=?,Port=?,Relay=?,PubKey=? WHERE PeerIP=?", row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey, row.PeerIP) + result, err := tx.Exec("UPDATE peers SET Version=?,APIKey=?,Name=?,PublicIP=?,Port=?,Relay=?,PubKey=?,PubSignKey=? WHERE PeerIP=?", row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey, row.PubSignKey, row.PeerIP) if err != nil { return err } @@ -419,8 +420,8 @@ func Peer_Get( err error, ) { row = &Peer{} - r := tx.QueryRow("SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey FROM peers WHERE PeerIP=?", PeerIP) - err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey) + r := tx.QueryRow("SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey FROM peers WHERE PeerIP=?", PeerIP) + err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey) return } @@ -434,7 +435,7 @@ func Peer_GetWhere( ) { row = &Peer{} r := tx.QueryRow(query, args...) - err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey) + err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey) return } @@ -454,7 +455,7 @@ func Peer_Iterate( defer rows.Close() for rows.Next() { row := &Peer{} - err := rows.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey) + err := rows.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey) if !yield(row, err) { return } diff --git a/hub/api/db/tables.defs b/hub/api/db/tables.defs index 6df286f..08244e4 100644 --- a/hub/api/db/tables.defs +++ b/hub/api/db/tables.defs @@ -21,5 +21,6 @@ TABLE peers OF Peer ( PublicIP []byte, Port uint16, Relay bool, - PubKey []byte NoUpdate + PubKey []byte NoUpdate, + PubSignKey []byte NoUpdate ); diff --git a/hub/api/migrations/2024-12-27-signing-keys.sql b/hub/api/migrations/2024-12-27-signing-keys.sql new file mode 100644 index 0000000..8731917 --- /dev/null +++ b/hub/api/migrations/2024-12-27-signing-keys.sql @@ -0,0 +1 @@ +ALTER TABLE peers ADD COLUMN PubSignKey BLOB NOT NULL DEFAULT ''; diff --git a/hub/handlers.go b/hub/handlers.go index aabf3c7..238a4c5 100644 --- a/hub/handlers.go +++ b/hub/handlers.go @@ -54,11 +54,18 @@ func (a *App) _adminSignOutSubmit(s *api.Session, w http.ResponseWriter, r *http } func (a *App) _adminConfig(s *api.Session, w http.ResponseWriter, r *http.Request) error { + peers, err := a.api.Peer_List() + if err != nil { + return err + } + return a.render("/admin-config.html", w, struct { Session *api.Session + Peers []*api.Peer Config *api.Config }{ s, + peers, a.api.Config_Get(), }) } @@ -142,21 +149,6 @@ func (a *App) _adminPasswordSubmit(s *api.Session, w http.ResponseWriter, r *htt return a.redirect(w, r, "/admin/config/") } -func (a *App) _adminPeerList(s *api.Session, w http.ResponseWriter, r *http.Request) error { - peers, err := a.api.Peer_List() - if err != nil { - return err - } - - return a.render("/admin-peer-list.html", w, struct { - Session *api.Session - Peers []*api.Peer - }{ - s, - peers, - }) -} - func (a *App) _adminHosts(s *api.Session, w http.ResponseWriter, r *http.Request) error { conf := a.api.Config_Get() @@ -187,25 +179,45 @@ func (a *App) _adminPeerCreate(s *api.Session, w http.ResponseWriter, r *http.Re func (a *App) _adminPeerCreateSubmit(s *api.Session, w http.ResponseWriter, r *http.Request) error { var ipStr string - args := api.PeerCreateArgs{} + p := &api.Peer{} err := webutil.NewFormScanner(r.Form). - Scan("Name", &args.Name). + Scan("IP", &p.PeerIP). + Scan("Name", &p.Name). Scan("PublicIP", &ipStr). - Scan("Port", &args.Port). - Scan("Relay", &args.Relay). + Scan("Port", &p.Port). + Scan("Relay", &p.Relay). Error() if err != nil { return err } - if args.PublicIP, err = stringToIP(ipStr); err != nil { + if p.PublicIP, err = stringToIP(ipStr); err != nil { return err } - code := a.api.Peer_CreateIntent(args) - return a.redirect(w, r, "/admin/peer/intent-created/?Code=%s", code) + if err := a.api.Peer_CreateNew(p); err != nil { + return err + } + return a.redirect(w, r, "/admin/peer/view/?PeerIP=%d", p.PeerIP) } +func (a *App) _adminPeerInit(s *api.Session, w http.ResponseWriter, r *http.Request) error { + var peerIP byte + err := webutil.NewFormScanner(r.Form).Scan("PeerIP", &peerIP).Error() + if err != nil { + return err + } + code := a.api.Peer_CreateInitIntent(peerIP) + log.Printf("Got code: %v / %v", peerIP, cod) + + return a.render("/admin-peer-init.html", w, struct { + Session *api.Session + HubAddress string + Code string + }{s, a.api.Config_Get().HubAddress, code}) +} + +// TODO: Remove func (a *App) _adminPeerIntentCreated(s *api.Session, w http.ResponseWriter, r *http.Request) error { code := r.FormValue("Code") if code == "" { @@ -323,6 +335,17 @@ func (a *App) _adminPeerDeleteSubmit(s *api.Session, w http.ResponseWriter, r *h return a.redirect(w, r, "/admin/peer/list/") } +func (a *App) _peerInit(w http.ResponseWriter, r *http.Request) error { + code := r.FormValue("Code") + conf, err := a.api.Peer_Init(code) + if err != nil { + return err + } + + return a.sendJSON(w, conf) +} + +// TODO: Remove func (a *App) _peerCreate(w http.ResponseWriter, r *http.Request) error { code := r.FormValue("Code") conf, err := a.api.Peer_Create(code) @@ -360,14 +383,16 @@ func (a *App) _peerFetchState(w http.ResponseWriter, r *http.Request) error { } for _, p := range peers { - state.Peers[p.PeerIP] = &m.Peer{ - PeerIP: p.PeerIP, - Version: p.Version, - Name: p.Name, - PublicIP: p.PublicIP, - Port: p.Port, - Relay: p.Relay, - PubKey: p.PubKey, + if len(p.PubKey) != 0 { + state.Peers[p.PeerIP] = &m.Peer{ + PeerIP: p.PeerIP, + Version: p.Version, + Name: p.Name, + PublicIP: p.PublicIP, + Port: p.Port, + Relay: p.Relay, + PubKey: p.PubKey, + } } } diff --git a/hub/routes.go b/hub/routes.go index a29736f..a86619e 100644 --- a/hub/routes.go +++ b/hub/routes.go @@ -16,10 +16,11 @@ func (a *App) registerRoutes() { a.handleSignedIn("POST /admin/sign-out/", a._adminSignOutSubmit) a.handleSignedIn("GET /admin/password/edit/", a._adminPasswordEdit) a.handleSignedIn("POST /admin/password/edit/", a._adminPasswordSubmit) - a.handleSignedIn("GET /admin/peer/list/", a._adminPeerList) a.handleSignedIn("GET /admin/peer/hosts/", a._adminHosts) a.handleSignedIn("GET /admin/peer/create/", a._adminPeerCreate) a.handleSignedIn("POST /admin/peer/create/", a._adminPeerCreateSubmit) + a.handleSignedIn("GET /admin/peer/init/", a._adminPeerInit) + // TODO: Remove a.handleSignedIn("GET /admin/peer/intent-created/", a._adminPeerIntentCreated) a.handleSignedIn("GET /admin/peer/view/", a._adminPeerView) a.handleSignedIn("GET /admin/peer/edit/", a._adminPeerEdit) @@ -27,6 +28,7 @@ func (a *App) registerRoutes() { a.handleSignedIn("GET /admin/peer/delete/", a._adminPeerDelete) a.handleSignedIn("POST /admin/peer/delete/", a._adminPeerDeleteSubmit) - a.handlePeer("GET /peer/create/", a._peerCreate) + a.handlePeer("GET /peer/create/", a._peerCreate) // TODO: Remove + a.handlePeer("GET /peer/init/", a._peerInit) a.handlePeer("GET /peer/fetch-state/", a._peerFetchState) } diff --git a/hub/templates/admin-config.html b/hub/templates/admin-config.html index da0f8ae..2c9cb82 100644 --- a/hub/templates/admin-config.html +++ b/hub/templates/admin-config.html @@ -16,4 +16,42 @@ {{ipToString .Config.VPNNetwork}} + +

Peers

+ +

+ Add Peer / + Hosts +

+ +{{if .Peers -}} + + + + + + + + + + + + {{range .Peers -}} + + + + + + + + + {{- end}} +
PeerIPNamePublic IPPortRelay
+ + {{.PeerIP}} + + {{.Name}}{{ipToString .PublicIP}}{{.Port}}{{if .Relay}}T{{else}}F{{end}}
+{{- else}} +

No peers.

+{{- end}} {{- end}} diff --git a/hub/templates/admin-peer-create.html b/hub/templates/admin-peer-create.html index 8225fc8..1a7089b 100644 --- a/hub/templates/admin-peer-create.html +++ b/hub/templates/admin-peer-create.html @@ -3,6 +3,10 @@
+

+
+ +


diff --git a/hub/templates/admin-peer-init.html b/hub/templates/admin-peer-init.html new file mode 100644 index 0000000..a00b8d9 --- /dev/null +++ b/hub/templates/admin-peer-init.html @@ -0,0 +1,13 @@ +{{define "body" -}} +

Initialize Peer

+ +

+ Configure the peer with the following URL: +

+
+  {{.HubAddress}}/peer/init/?Code={{.Code}}
+
+

+ Done +

+{{- end}} diff --git a/hub/templates/admin-peer-intent.html b/hub/templates/admin-peer-intent.html index 0cb5686..9a1d05f 100644 --- a/hub/templates/admin-peer-intent.html +++ b/hub/templates/admin-peer-intent.html @@ -8,6 +8,6 @@ {{.HubAddress}}/peer/create/?Code={{.Code}}

- Done + Done

{{- end}} diff --git a/hub/templates/admin-peer-list.html b/hub/templates/admin-peer-list.html deleted file mode 100644 index cb7c72c..0000000 --- a/hub/templates/admin-peer-list.html +++ /dev/null @@ -1,40 +0,0 @@ -{{define "body" -}} -

Peers

- -

- Add Peer / - Hosts -

- -{{if .Peers -}} - - - - - - - - - - - - {{range .Peers -}} - - - - - - - - - {{- end}} -
PeerIPNamePublic IPPortRelay
- - {{.PeerIP}} - - {{.Name}}{{ipToString .PublicIP}}{{.Port}}{{if .Relay}}T{{else}}F{{end}}
-{{- else}} -

No peers.

-{{- end}} - -{{- end}} diff --git a/hub/templates/admin-peer-view.html b/hub/templates/admin-peer-view.html index e8d6f6e..b5ef574 100644 --- a/hub/templates/admin-peer-view.html +++ b/hub/templates/admin-peer-view.html @@ -3,6 +3,7 @@

Edit / + Initialize / Delete

diff --git a/hub/templates/base.html b/hub/templates/base.html index e582e83..f984646 100644 --- a/hub/templates/base.html +++ b/hub/templates/base.html @@ -10,8 +10,6 @@

VPPN

diff --git a/m/models.go b/m/models.go index 345bf5d..d00ecd6 100644 --- a/m/models.go +++ b/m/models.go @@ -2,25 +2,28 @@ package m type PeerConfig struct { + PeerIP byte + HubAddress string + Network []byte + APIKey string + PublicIP []byte + Port uint16 + Relay bool + PubKey []byte + PrivKey []byte + PubSignKey []byte + PrivSignKey []byte +} + +type Peer struct { PeerIP byte - HubAddress string - Network []byte - APIKey string + Version int64 + Name string PublicIP []byte Port uint16 Relay bool PubKey []byte - PrivKey []byte -} - -type Peer struct { - PeerIP byte - Version int64 - Name string - PublicIP []byte - Port uint16 - Relay bool - PubKey []byte + PubSignKey []byte } type NetworkState struct { diff --git a/node/cipher-data.go b/node/cipher-data.go index 7cdc0d5..9151870 100644 --- a/node/cipher-data.go +++ b/node/cipher-data.go @@ -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 { diff --git a/node/cipher-discovery.go b/node/cipher-discovery.go new file mode 100644 index 0000000..85e1381 --- /dev/null +++ b/node/cipher-discovery.go @@ -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) +} +*/ diff --git a/node/conn.go b/node/conn.go index b055ff8..2a1e762 100644 --- a/node/conn.go +++ b/node/conn.go @@ -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() } diff --git a/node/globals.go b/node/globals.go index 3b8edea..5438a9c 100644 --- a/node/globals.go +++ b/node/globals.go @@ -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]{} ) diff --git a/node/localbroadcaster.go b/node/localbroadcaster.go new file mode 100644 index 0000000..0b2cd17 --- /dev/null +++ b/node/localbroadcaster.go @@ -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) +} diff --git a/node/localdiscovery.go b/node/localdiscovery.go new file mode 100644 index 0000000..acc9f14 --- /dev/null +++ b/node/localdiscovery.go @@ -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 +} diff --git a/node/localdiscovery_test.go b/node/localdiscovery_test.go new file mode 100644 index 0000000..7f4eaa3 --- /dev/null +++ b/node/localdiscovery_test.go @@ -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) + } +} diff --git a/node/main.go b/node/main.go index ee2e7a7..e1dfe5f 100644 --- a/node/main.go +++ b/node/main.go @@ -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 } diff --git a/node/messages.go b/node/messages.go new file mode 100644 index 0000000..2b4023a --- /dev/null +++ b/node/messages.go @@ -0,0 +1 @@ +package node diff --git a/node/packets.go b/node/packets.go index 267fed0..b173887 100644 --- a/node/packets.go +++ b/node/packets.go @@ -138,3 +138,7 @@ func parseProbePacket(buf []byte) (p probePacket, err error) { Error() return } + +// ---------------------------------------------------------------------------- + +type localDiscoveryPacket struct{} diff --git a/node/packets_test.go b/node/packets_test.go index 60295ec..675bb3e 100644 --- a/node/packets_test.go +++ b/node/packets_test.go @@ -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[:]) diff --git a/node/peer-supervisor.go b/node/peer-supervisor.go index 9a587e8..b31ae84 100644 --- a/node/peer-supervisor.go +++ b/node/peer-supervisor.go @@ -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 } diff --git a/node/signing.go b/node/signing.go new file mode 100644 index 0000000..2b4023a --- /dev/null +++ b/node/signing.go @@ -0,0 +1 @@ +package node