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

@ -15,6 +15,7 @@ import (
"git.crumpington.com/lib/go/sqliteutil" "git.crumpington.com/lib/go/sqliteutil"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/nacl/box" "golang.org/x/crypto/nacl/box"
"golang.org/x/crypto/nacl/sign"
) )
//go:embed migrations //go:embed migrations
@ -24,6 +25,7 @@ type API struct {
db *sql.DB db *sql.DB
lock sync.Mutex lock sync.Mutex
peerIntents map[string]PeerCreateArgs peerIntents map[string]PeerCreateArgs
initIntents map[string]byte // Map from intent key to peer IP
} }
func New(dbPath string) (*API, error) { func New(dbPath string) (*API, error) {
@ -39,6 +41,7 @@ func New(dbPath string) (*API, error) {
a := &API{ a := &API{
db: sqlDB, db: sqlDB,
peerIntents: map[string]PeerCreateArgs{}, peerIntents: map[string]PeerCreateArgs{},
initIntents: map[string]byte{},
} }
return a, a.ensurePassword() 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) 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 { type PeerCreateArgs struct {
Name string Name string
PublicIP []byte PublicIP []byte
@ -148,6 +161,7 @@ type PeerCreateArgs struct {
Relay bool Relay bool
} }
// TODO: Remove
// Create the intention to add a peer. The returned code is used to complete // Create the intention to add a peer. The returned code is used to complete
// the peer creation. The code is valid for 5 minutes. // the peer creation. The code is valid for 5 minutes.
func (a *API) Peer_CreateIntent(args PeerCreateArgs) string { func (a *API) Peer_CreateIntent(args PeerCreateArgs) string {
@ -167,6 +181,78 @@ func (a *API) Peer_CreateIntent(args PeerCreateArgs) string {
return code 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) { func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) {
a.lock.Lock() a.lock.Lock()
defer a.lock.Unlock() defer a.lock.Unlock()
@ -183,6 +269,11 @@ func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) {
return nil, err return nil, err
} }
signPubKey, signPrivKey, err := sign.GenerateKey(rand.Reader)
if err != nil {
return nil, err
}
// Get peer IP. // Get peer IP.
peerIP := byte(0) peerIP := byte(0)
@ -210,6 +301,7 @@ func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) {
Port: args.Port, Port: args.Port,
Relay: args.Relay, Relay: args.Relay,
PubKey: encPubKey[:], PubKey: encPubKey[:],
PubSignKey: signPubKey[:],
} }
if err := db.Peer_Insert(a.db, peer); err != nil { if err := db.Peer_Insert(a.db, peer); err != nil {
@ -228,6 +320,8 @@ func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) {
Relay: peer.Relay, Relay: peer.Relay,
PubKey: encPubKey[:], PubKey: encPubKey[:],
PrivKey: encPrivKey[:], PrivKey: encPrivKey[:],
PubSignKey: signPubKey[:],
PrivSignKey: signPrivKey[:],
}, nil }, nil
} }

View File

@ -315,9 +315,10 @@ type Peer struct {
Port uint16 Port uint16
Relay bool Relay bool
PubKey []byte 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( func Peer_Insert(
tx TX, tx TX,
@ -328,7 +329,7 @@ func Peer_Insert(
return err 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 return err
} }
@ -369,7 +370,7 @@ func Peer_UpdateFull(
return err 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 { if err != nil {
return err return err
} }
@ -419,8 +420,8 @@ func Peer_Get(
err error, err error,
) { ) {
row = &Peer{} row = &Peer{}
r := tx.QueryRow("SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey FROM peers WHERE PeerIP=?", PeerIP) 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) err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey)
return return
} }
@ -434,7 +435,7 @@ func Peer_GetWhere(
) { ) {
row = &Peer{} row = &Peer{}
r := tx.QueryRow(query, args...) 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 return
} }
@ -454,7 +455,7 @@ func Peer_Iterate(
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
row := &Peer{} 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) { if !yield(row, err) {
return return
} }

View File

@ -21,5 +21,6 @@ TABLE peers OF Peer (
PublicIP []byte, PublicIP []byte,
Port uint16, Port uint16,
Relay bool, Relay bool,
PubKey []byte NoUpdate PubKey []byte NoUpdate,
PubSignKey []byte NoUpdate
); );

View File

@ -0,0 +1 @@
ALTER TABLE peers ADD COLUMN PubSignKey BLOB NOT NULL DEFAULT '';

View File

@ -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 { 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 { return a.render("/admin-config.html", w, struct {
Session *api.Session Session *api.Session
Peers []*api.Peer
Config *api.Config Config *api.Config
}{ }{
s, s,
peers,
a.api.Config_Get(), 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/") 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 { func (a *App) _adminHosts(s *api.Session, w http.ResponseWriter, r *http.Request) error {
conf := a.api.Config_Get() 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 { func (a *App) _adminPeerCreateSubmit(s *api.Session, w http.ResponseWriter, r *http.Request) error {
var ipStr string var ipStr string
args := api.PeerCreateArgs{} p := &api.Peer{}
err := webutil.NewFormScanner(r.Form). err := webutil.NewFormScanner(r.Form).
Scan("Name", &args.Name). Scan("IP", &p.PeerIP).
Scan("Name", &p.Name).
Scan("PublicIP", &ipStr). Scan("PublicIP", &ipStr).
Scan("Port", &args.Port). Scan("Port", &p.Port).
Scan("Relay", &args.Relay). Scan("Relay", &p.Relay).
Error() Error()
if err != nil { if err != nil {
return err return err
} }
if args.PublicIP, err = stringToIP(ipStr); err != nil { if p.PublicIP, err = stringToIP(ipStr); err != nil {
return err return err
} }
code := a.api.Peer_CreateIntent(args) if err := a.api.Peer_CreateNew(p); err != nil {
return a.redirect(w, r, "/admin/peer/intent-created/?Code=%s", code) 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 { func (a *App) _adminPeerIntentCreated(s *api.Session, w http.ResponseWriter, r *http.Request) error {
code := r.FormValue("Code") code := r.FormValue("Code")
if 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/") 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 { func (a *App) _peerCreate(w http.ResponseWriter, r *http.Request) error {
code := r.FormValue("Code") code := r.FormValue("Code")
conf, err := a.api.Peer_Create(code) conf, err := a.api.Peer_Create(code)
@ -360,6 +383,7 @@ func (a *App) _peerFetchState(w http.ResponseWriter, r *http.Request) error {
} }
for _, p := range peers { for _, p := range peers {
if len(p.PubKey) != 0 {
state.Peers[p.PeerIP] = &m.Peer{ state.Peers[p.PeerIP] = &m.Peer{
PeerIP: p.PeerIP, PeerIP: p.PeerIP,
Version: p.Version, Version: p.Version,
@ -370,6 +394,7 @@ func (a *App) _peerFetchState(w http.ResponseWriter, r *http.Request) error {
PubKey: p.PubKey, PubKey: p.PubKey,
} }
} }
}
return a.sendJSON(w, state) return a.sendJSON(w, state)
} }

View File

@ -16,10 +16,11 @@ func (a *App) registerRoutes() {
a.handleSignedIn("POST /admin/sign-out/", a._adminSignOutSubmit) a.handleSignedIn("POST /admin/sign-out/", a._adminSignOutSubmit)
a.handleSignedIn("GET /admin/password/edit/", a._adminPasswordEdit) a.handleSignedIn("GET /admin/password/edit/", a._adminPasswordEdit)
a.handleSignedIn("POST /admin/password/edit/", a._adminPasswordSubmit) 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/hosts/", a._adminHosts)
a.handleSignedIn("GET /admin/peer/create/", a._adminPeerCreate) a.handleSignedIn("GET /admin/peer/create/", a._adminPeerCreate)
a.handleSignedIn("POST /admin/peer/create/", a._adminPeerCreateSubmit) 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/intent-created/", a._adminPeerIntentCreated)
a.handleSignedIn("GET /admin/peer/view/", a._adminPeerView) a.handleSignedIn("GET /admin/peer/view/", a._adminPeerView)
a.handleSignedIn("GET /admin/peer/edit/", a._adminPeerEdit) 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("GET /admin/peer/delete/", a._adminPeerDelete)
a.handleSignedIn("POST /admin/peer/delete/", a._adminPeerDeleteSubmit) 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) a.handlePeer("GET /peer/fetch-state/", a._peerFetchState)
} }

View File

@ -16,4 +16,42 @@
<td>{{ipToString .Config.VPNNetwork}}</td> <td>{{ipToString .Config.VPNNetwork}}</td>
</tr> </tr>
</table> </table>
<h2>Peers</h2>
<p>
<a href="/admin/peer/create/">Add Peer</a> /
<a href="/admin/peer/hosts/">Hosts</a>
</p>
{{if .Peers -}}
<table>
<thead>
<tr>
<th>PeerIP</th>
<th>Name</th>
<th>Public IP</th>
<th>Port</th>
<th>Relay</th>
</tr>
</thead>
<tbody>
{{range .Peers -}}
<tr>
<td>
<a href="/admin/peer/view/?PeerIP={{.PeerIP}}">
{{.PeerIP}}
</a>
</td>
<td>{{.Name}}</td>
<td>{{ipToString .PublicIP}}</td>
<td>{{.Port}}</td>
<td>{{if .Relay}}T{{else}}F{{end}}</td>
</tr>
</tbody>
{{- end}}
</table>
{{- else}}
<p>No peers.</p>
{{- end}}
{{- end}} {{- end}}

View File

@ -3,6 +3,10 @@
<form method="POST"> <form method="POST">
<input type="hidden" name="CSRF" value="{{.Session.CSRF}}"> <input type="hidden" name="CSRF" value="{{.Session.CSRF}}">
<p>
<label>IP</label><br>
<input type="number" name="IP" min="1" max="255" value="0">
</p>
<p> <p>
<label>Name</label><br> <label>Name</label><br>
<input type="text" name="Name"> <input type="text" name="Name">

View File

@ -0,0 +1,13 @@
{{define "body" -}}
<h2>Initialize Peer</h2>
<p>
Configure the peer with the following URL:
</p>
<pre>
{{.HubAddress}}/peer/init/?Code={{.Code}}
</pre>
<p>
<a href="/admin/config/">Done</a>
</p>
{{- end}}

View File

@ -8,6 +8,6 @@
{{.HubAddress}}/peer/create/?Code={{.Code}} {{.HubAddress}}/peer/create/?Code={{.Code}}
</pre> </pre>
<p> <p>
<a href="/admin/peer/list/">Done</a> <a href="/admin/config/">Done</a>
</p> </p>
{{- end}} {{- end}}

View File

@ -1,40 +0,0 @@
{{define "body" -}}
<h2>Peers</h2>
<p>
<a href="/admin/peer/create/">Add Peer</a> /
<a href="/admin/peer/hosts/">Hosts</a>
</p>
{{if .Peers -}}
<table>
<thead>
<tr>
<th>PeerIP</th>
<th>Name</th>
<th>Public IP</th>
<th>Port</th>
<th>Relay</th>
</tr>
</thead>
<tbody>
{{range .Peers -}}
<tr>
<td>
<a href="/admin/peer/view/?PeerIP={{.PeerIP}}">
{{.PeerIP}}
</a>
</td>
<td>{{.Name}}</td>
<td>{{ipToString .PublicIP}}</td>
<td>{{.Port}}</td>
<td>{{if .Relay}}T{{else}}F{{end}}</td>
</tr>
</tbody>
{{- end}}
</table>
{{- else}}
<p>No peers.</p>
{{- end}}
{{- end}}

View File

@ -3,6 +3,7 @@
<p> <p>
<a href="/admin/peer/edit/?PeerIP={{.Peer.PeerIP}}">Edit</a> / <a href="/admin/peer/edit/?PeerIP={{.Peer.PeerIP}}">Edit</a> /
<a href="/admin/peer/init/?PeerIP={{.Peer.PeerIP}}">Initialize</a> /
<a href="/admin/peer/delete/?PeerIP={{.Peer.PeerIP}}">Delete</a> <a href="/admin/peer/delete/?PeerIP={{.Peer.PeerIP}}">Delete</a>
</p> </p>

View File

@ -10,8 +10,6 @@
<h1>VPPN</h1> <h1>VPPN</h1>
<nav> <nav>
{{if .Session.SignedIn -}} {{if .Session.SignedIn -}}
<a href="/admin/config/">Config</a> /
<a href="/admin/peer/list/">Peers</a> /
<a href="/admin/sign-out/">Sign out</a> <a href="/admin/sign-out/">Sign out</a>
{{- end}} {{- end}}
</nav> </nav>

View File

@ -11,6 +11,8 @@ type PeerConfig struct {
Relay bool Relay bool
PubKey []byte PubKey []byte
PrivKey []byte PrivKey []byte
PubSignKey []byte
PrivSignKey []byte
} }
type Peer struct { type Peer struct {
@ -21,6 +23,7 @@ type Peer struct {
Port uint16 Port uint16
Relay bool Relay bool
PubKey []byte PubKey []byte
PubSignKey []byte
} }
type NetworkState struct { type NetworkState struct {

View File

@ -6,7 +6,6 @@ import (
"crypto/rand" "crypto/rand"
) )
// TODO: Use [32]byte for simplicity everywhere.
type dataCipher struct { type dataCipher struct {
key [32]byte key [32]byte
aead cipher.AEAD aead cipher.AEAD
@ -20,7 +19,6 @@ func newDataCipher() *dataCipher {
return newDataCipherFromKey(key) return newDataCipherFromKey(key)
} }
// key must be 32 bytes.
func newDataCipherFromKey(key [32]byte) *dataCipher { func newDataCipherFromKey(key [32]byte) *dataCipher {
block, err := aes.NewCipher(key[:]) block, err := aes.NewCipher(key[:])
if err != nil { 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. // packets may fail to be sent in a timely manner causing timeouts.
w.lock.Lock() w.lock.Lock()
if _, err := w.conn.WriteToUDPAddrPort(packet, addr); err != nil { 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() w.lock.Unlock()
} }

View File

@ -20,6 +20,7 @@ type peerRoute struct {
Up bool // True if data can be sent on the route. Up bool // True if data can be sent on the route.
Relay bool // True if the peer is a relay. Relay bool // True if the peer is a relay.
Direct bool // True if this is a direct connection. Direct bool // True if this is a direct connection.
PubSignKey []byte
ControlCipher *controlCipher ControlCipher *controlCipher
DataCipher *dataCipher DataCipher *dataCipher
RemoteAddr netip.AddrPort // Remote address if directly connected. RemoteAddr netip.AddrPort // Remote address if directly connected.
@ -30,7 +31,8 @@ var (
netName string netName string
localIP byte localIP byte
localPub bool localPub bool
privateKey []byte privKey []byte
privSignKey []byte
// Shared interface for writing. // Shared interface for writing.
_iface *ifWriter _iface *ifWriter
@ -80,7 +82,7 @@ var (
}() }()
// Managed by the relayManager. // Managed by the relayManager.
discoveryPackets chan controlPacket discoveryPackets = make(chan controlPacket, 256)
localAddr *atomic.Pointer[netip.AddrPort] // May be nil. localAddr = &atomic.Pointer[netip.AddrPort]{}
relayIP *atomic.Pointer[byte] // May be nil. 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" "net/netip"
"os" "os"
"runtime/debug" "runtime/debug"
"sync/atomic"
"vppn/m" "vppn/m"
) )
@ -50,10 +49,6 @@ func Main() {
} }
func mainInit(initURL string) { func mainInit(initURL string) {
if _, err := loadPeerConfig(netName); err == nil {
log.Fatalf("Network is already initialized.")
}
resp, err := http.Get(initURL) resp, err := http.Get(initURL)
if err != nil { if err != nil {
log.Fatalf("Failed to fetch data from hub: %v", err) 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) log.Fatalf("Failed to open UDP port: %v", err)
} }
conn.SetReadBuffer(1024 * 1024 * 8)
conn.SetWriteBuffer(1024 * 1024 * 8)
// Intialize globals. // Intialize globals.
_iface = newIFWriter(iface) _iface = newIFWriter(iface)
_conn = newConnWriter(conn) _conn = newConnWriter(conn)
localIP = config.PeerIP localIP = config.PeerIP
discoveryPackets = make(chan controlPacket, 256)
localAddr = &atomic.Pointer[netip.AddrPort]{}
relayIP = &atomic.Pointer[byte]{}
ip, ok := netip.AddrFromSlice(config.PublicIP) ip, ok := netip.AddrFromSlice(config.PublicIP)
if ok { if ok {
@ -118,7 +113,8 @@ func main(listenIP string, port uint16) {
localAddr.Store(&addr) localAddr.Store(&addr)
} }
privateKey = config.PrivKey privKey = config.PrivKey
privSignKey = config.PrivSignKey
// Start supervisors. // Start supervisors.
for i := range 256 { for i := range 256 {
@ -130,7 +126,9 @@ func main(listenIP string, port uint16) {
} else { } else {
go addrDiscoveryClient() go addrDiscoveryClient()
go relayManager() go relayManager()
go localDiscovery()
} }
go newHubPoller(config).Run() go newHubPoller(config).Run()
go readFromConn(conn) go readFromConn(conn)
readFromIFace(iface) readFromIFace(iface)
@ -206,17 +204,17 @@ func handleControlPacket(addr netip.AddrPort, h header, data, decBuf []byte) {
out, ok := route.ControlCipher.Decrypt(data, decBuf) out, ok := route.ControlCipher.Decrypt(data, decBuf)
if !ok { if !ok {
//log.Printf("Failed to decrypt control packet.") log.Printf("Failed to decrypt control packet.")
return return
} }
if len(out) == 0 { if len(out) == 0 {
//log.Printf("Empty control packet from: %d", h.SourceIP) log.Printf("Empty control packet from: %d", h.SourceIP)
return return
} }
if dupChecks[h.SourceIP].IsDup(h.Counter) { 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 return
} }
@ -252,7 +250,7 @@ func handleControlPacket(addr netip.AddrPort, h header, data, decBuf []byte) {
func handleDataPacket(h header, data []byte, decBuf []byte) { func handleDataPacket(h header, data []byte, decBuf []byte) {
route := routingTable[h.SourceIP].Load() route := routingTable[h.SourceIP].Load()
if !route.Up { if !route.Up {
//log.Printf("Not connected (recv).") log.Printf("Not connected (recv).")
return return
} }
@ -263,7 +261,7 @@ func handleDataPacket(h header, data []byte, decBuf []byte) {
} }
if dupChecks[h.SourceIP].IsDup(h.Counter) { 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 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() Error()
return return
} }
// ----------------------------------------------------------------------------
type localDiscoveryPacket struct{}

View File

@ -10,7 +10,6 @@ import (
func TestPacketSyn(t *testing.T) { func TestPacketSyn(t *testing.T) {
in := synPacket{ in := synPacket{
TraceID: newTraceID(), TraceID: newTraceID(),
RelayIP: 4,
FromAddr: netip.AddrPortFrom(netip.AddrFrom4([4]byte{4, 5, 6, 7}), 22), FromAddr: netip.AddrPortFrom(netip.AddrFrom4([4]byte{4, 5, 6, 7}), 22),
} }
rand.Read(in.SharedKey[:]) 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.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() s.staged.DataCipher = newDataCipher()
if ip, isValid := netip.AddrFromSlice(peer.PublicIP); isValid { if ip, isValid := netip.AddrFromSlice(peer.PublicIP); isValid {
@ -241,7 +242,7 @@ func (s *peerSupervisor) client() stateFunc {
probe probePacket probe probePacket
probeAddr netip.AddrPort probeAddr netip.AddrPort
lAddr netip.AddrPort remoteAddr netip.AddrPort
timeoutTimer = time.NewTimer(timeoutInterval) timeoutTimer = time.NewTimer(timeoutInterval)
pingTimer = time.NewTimer(pingInterval) pingTimer = time.NewTimer(pingInterval)
@ -306,9 +307,9 @@ func (s *peerSupervisor) client() stateFunc {
// Send syn. // Send syn.
syn.FromAddr = getLocalAddr() syn.FromAddr = getLocalAddr()
if syn.FromAddr != lAddr { if syn.FromAddr != remoteAddr {
syn.TraceID = newTraceID() syn.TraceID = newTraceID()
lAddr = syn.FromAddr remoteAddr = syn.FromAddr
} }
s.sendControlPacket(syn) s.sendControlPacket(syn)
@ -319,6 +320,9 @@ func (s *peerSupervisor) client() stateFunc {
continue continue
} }
// TODO: Check if we have local address.
// TODO: Send local probe
if !ack.FromAddr.IsValid() { if !ack.FromAddr.IsValid() {
continue continue
} }

1
node/signing.go Normal file
View File

@ -0,0 +1 @@
package node