From d558ebbd14c7e876bee2967e6edcce4e27251b77 Mon Sep 17 00:00:00 2001 From: jdl Date: Sun, 6 Apr 2025 07:51:47 +0200 Subject: [PATCH] WIP --- README.md | 14 +- go.mod | 10 +- go.sum | 20 +- hub/api/api.go | 58 ++- hub/api/db/generated | 0 hub/api/db/generated.go | 187 +++++++- hub/api/db/sanitize-validate.go | 75 +++- hub/api/db/tables.defs | 11 +- hub/api/db/written.go | 18 +- hub/api/migrations/2024-11-30-init.sql | 19 +- .../migrations/2024-12-27-signing-keys.sql | 1 - hub/api/types.go | 1 + hub/form.go | 42 ++ hub/handlers.go | 422 ++++++++++-------- hub/routes.go | 18 +- hub/templates/admin-config-edit.html | 20 - hub/templates/admin-network-create.html | 19 + hub/templates/admin-network-list.html | 38 ++ hub/templates/admin-peer-delete.html | 36 -- hub/templates/admin-peer-init.html | 13 - hub/templates/admin-peer-intent.html | 13 - hub/templates/admin-sign-out.html | 2 +- hub/templates/base.html | 2 +- hub/templates/network/base.html | 25 ++ hub/templates/network/network-delete.html | 16 + .../network-view.html} | 21 +- .../peer-create.html} | 5 +- hub/templates/network/peer-delete.html | 15 + .../peer-edit.html} | 5 +- .../peer-view.html} | 17 +- hub/templates/share/common.html | 0 m/models.go | 10 +- peer/files.go | 7 +- peer/files_test.go | 4 +- peer/main.go | 2 +- peer/mcreader.go | 1 + peer/mcwriter.go | 1 + peer/peer.go | 60 ++- 38 files changed, 773 insertions(+), 455 deletions(-) delete mode 100644 hub/api/db/generated delete mode 100644 hub/api/migrations/2024-12-27-signing-keys.sql create mode 100644 hub/form.go delete mode 100644 hub/templates/admin-config-edit.html create mode 100644 hub/templates/admin-network-create.html create mode 100644 hub/templates/admin-network-list.html delete mode 100644 hub/templates/admin-peer-delete.html delete mode 100644 hub/templates/admin-peer-init.html delete mode 100644 hub/templates/admin-peer-intent.html create mode 100644 hub/templates/network/base.html create mode 100644 hub/templates/network/network-delete.html rename hub/templates/{admin-config.html => network/network-view.html} (57%) rename hub/templates/{admin-peer-create.html => network/peer-create.html} (78%) create mode 100644 hub/templates/network/peer-delete.html rename hub/templates/{admin-peer-edit.html => network/peer-edit.html} (82%) rename hub/templates/{admin-peer-view.html => network/peer-view.html} (51%) delete mode 100644 hub/templates/share/common.html diff --git a/README.md b/README.md index 4567196..29ae92c 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Add and start the hub server: ``` systemctl daemon-reload +systemctl enable hub systemctl start hub ``` @@ -62,10 +63,19 @@ AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_ADMIN Type=simple User=user WorkingDirectory=/home/user/ -ExecStart=/home/user/vppn -name vppn -hub-address https://my.hub -api-key 1234567890 +ExecStart=/home/user/vppn -hub-address https://my.hub -api-key 1234567890 Restart=always RestartSec=8 [Install] -WantedBy=default.target +WantedBy=multi-user.target +``` + +Add and start the service: + + +``` +systemctl daemon-reload +systemctl enable vppn +systemctl start vppn ``` diff --git a/go.mod b/go.mod index 82d87c4..e55e1f6 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,13 @@ module vppn go 1.24.1 require ( - git.crumpington.com/lib/go v0.8.1 - golang.org/x/crypto v0.29.0 - golang.org/x/sys v0.27.0 + git.crumpington.com/lib/go v0.9.0 + golang.org/x/crypto v0.36.0 + golang.org/x/sys v0.31.0 ) require ( github.com/mattn/go-sqlite3 v1.14.24 // indirect - golang.org/x/net v0.31.0 // indirect - golang.org/x/text v0.20.0 // indirect + golang.org/x/net v0.37.0 // indirect + golang.org/x/text v0.23.0 // indirect ) diff --git a/go.sum b/go.sum index fde1083..a173169 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,12 @@ -git.crumpington.com/lib/go v0.8.1 h1:rWjddllSxQ4yReraqDaGZAod4NpRD9LtGx1yV71ytcU= -git.crumpington.com/lib/go v0.8.1/go.mod h1:XjQaf2NFlje9BJ1EevZL8NNioPrAe7WwHpKUhcDw2Lk= +git.crumpington.com/lib/go v0.9.0 h1:QXoMhsycSgEUWNiiPZWl0jgBls+NI9TNR5Z6nNXslCM= +git.crumpington.com/lib/go v0.9.0/go.mod h1:i3DXiPDo/pgPMHAxUTpyo1Xj2spcvXwXcBef3aSYlnQ= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= diff --git a/hub/api/api.go b/hub/api/api.go index 801f689..7a534ca 100644 --- a/hub/api/api.go +++ b/hub/api/api.go @@ -54,17 +54,11 @@ func (a *API) ensurePassword() error { log.Printf("Setting password: %s", pwd) hashed, err := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.DefaultCost) - if err != nil { return err } - conf := &Config{ - ConfigID: 1, - VPNNetwork: []byte{10, 1, 1, 0}, - Password: hashed, - } - + conf := &Config{ConfigID: 1, Password: hashed} return db.Config_Insert(a.db, conf) } @@ -80,10 +74,6 @@ func (a *API) Config_Update(conf *Config) error { return db.Config_Update(a.db, conf) } -func (a *API) Config_UpdatePassword(pwdHash []byte) error { - return db.Config_UpdatePassword(a.db, pwdHash) -} - func (a *API) Session_Delete(sessionID string) error { return db.Session_Delete(a.db, sessionID) } @@ -137,6 +127,24 @@ func (a *API) Session_SignIn(s *Session, pwd string) error { return db.Session_SetSignedIn(a.db, s.SessionID) } +func (a *API) Network_Create(n *Network) error { + n.NetworkID = idgen.NextID(0) + return db.Network_Insert(a.db, n) +} + +func (a *API) Network_Delete(n *Network) error { + return db.Network_Delete(a.db, n.NetworkID) +} + +func (a *API) Network_Get(id int64) (*Network, error) { + return db.Network_Get(a.db, id) +} + +func (a *API) Network_List() ([]*Network, error) { + const query = db.Network_SelectQuery + ` ORDER BY Name ASC` + return db.Network_List(a.db, query) +} + func (a *API) Peer_CreateNew(p *Peer) error { p.Version = idgen.NextID(0) p.PubKey = []byte{} @@ -146,7 +154,7 @@ func (a *API) Peer_CreateNew(p *Peer) error { return db.Peer_Insert(a.db, p) } -func (a *API) Peer_Init(peer *Peer, args m.PeerInitArgs) (*m.PeerConfig, error) { +func (a *API) Peer_Init(peer *Peer, args m.PeerInitArgs) error { a.lock.Lock() defer a.lock.Unlock() @@ -154,19 +162,7 @@ func (a *API) Peer_Init(peer *Peer, args m.PeerInitArgs) (*m.PeerConfig, error) peer.PubKey = args.EncPubKey peer.PubSignKey = args.PubSignKey - if err := db.Peer_UpdateFull(a.db, peer); err != nil { - return nil, err - } - - conf := a.Config_Get() - - return &m.PeerConfig{ - PeerIP: peer.PeerIP, - Network: conf.VPNNetwork, - PublicIP: peer.PublicIP, - Port: peer.Port, - Relay: peer.Relay, - }, nil + return db.Peer_UpdateFull(a.db, peer) } func (a *API) Peer_Update(p *Peer) error { @@ -177,16 +173,16 @@ func (a *API) Peer_Update(p *Peer) error { return db.Peer_Update(a.db, p) } -func (a *API) Peer_Delete(ip byte) error { - return db.Peer_Delete(a.db, ip) +func (a *API) Peer_Delete(networkID int64, peerIP byte) error { + return db.Peer_Delete(a.db, networkID, peerIP) } -func (a *API) Peer_List() ([]*Peer, error) { - return db.Peer_ListAll(a.db) +func (a *API) Peer_List(networkID int64) ([]*Peer, error) { + return db.Peer_ListAll(a.db, networkID) } -func (a *API) Peer_Get(ip byte) (*Peer, error) { - return db.Peer_Get(a.db, ip) +func (a *API) Peer_Get(networkID int64, ip byte) (*Peer, error) { + return db.Peer_Get(a.db, networkID, ip) } func (a *API) Peer_GetByAPIKey(key string) (*Peer, error) { diff --git a/hub/api/db/generated b/hub/api/db/generated deleted file mode 100644 index e69de29..0000000 diff --git a/hub/api/db/generated.go b/hub/api/db/generated.go index 1548afd..88aec6c 100644 --- a/hub/api/db/generated.go +++ b/hub/api/db/generated.go @@ -16,13 +16,11 @@ type TX interface { // ---------------------------------------------------------------------------- type Config struct { - ConfigID int64 - HubAddress string - VPNNetwork []byte - Password []byte + ConfigID int64 + Password []byte } -const Config_SelectQuery = "SELECT ConfigID,HubAddress,VPNNetwork,Password FROM config" +const Config_SelectQuery = "SELECT ConfigID,Password FROM config" func Config_Insert( tx TX, @@ -33,7 +31,7 @@ func Config_Insert( return err } - _, err = tx.Exec("INSERT INTO config(ConfigID,HubAddress,VPNNetwork,Password) VALUES(?,?,?,?)", row.ConfigID, row.HubAddress, row.VPNNetwork, row.Password) + _, err = tx.Exec("INSERT INTO config(ConfigID,Password) VALUES(?,?)", row.ConfigID, row.Password) return err } @@ -46,7 +44,7 @@ func Config_Update( return err } - result, err := tx.Exec("UPDATE config SET HubAddress=?,VPNNetwork=? WHERE ConfigID=?", row.HubAddress, row.VPNNetwork, row.ConfigID) + result, err := tx.Exec("UPDATE config SET Password=? WHERE ConfigID=?", row.Password, row.ConfigID) if err != nil { return err } @@ -74,7 +72,7 @@ func Config_UpdateFull( return err } - result, err := tx.Exec("UPDATE config SET HubAddress=?,VPNNetwork=?,Password=? WHERE ConfigID=?", row.HubAddress, row.VPNNetwork, row.Password, row.ConfigID) + result, err := tx.Exec("UPDATE config SET Password=? WHERE ConfigID=?", row.Password, row.ConfigID) if err != nil { return err } @@ -124,8 +122,8 @@ func Config_Get( err error, ) { row = &Config{} - r := tx.QueryRow("SELECT ConfigID,HubAddress,VPNNetwork,Password FROM config WHERE ConfigID=?", ConfigID) - err = r.Scan(&row.ConfigID, &row.HubAddress, &row.VPNNetwork, &row.Password) + r := tx.QueryRow("SELECT ConfigID,Password FROM config WHERE ConfigID=?", ConfigID) + err = r.Scan(&row.ConfigID, &row.Password) return } @@ -139,7 +137,7 @@ func Config_GetWhere( ) { row = &Config{} r := tx.QueryRow(query, args...) - err = r.Scan(&row.ConfigID, &row.HubAddress, &row.VPNNetwork, &row.Password) + err = r.Scan(&row.ConfigID, &row.Password) return } @@ -159,7 +157,7 @@ func Config_Iterate( defer rows.Close() for rows.Next() { row := &Config{} - err := rows.Scan(&row.ConfigID, &row.HubAddress, &row.VPNNetwork, &row.Password) + err := rows.Scan(&row.ConfigID, &row.Password) if !yield(row, err) { return } @@ -302,11 +300,156 @@ func Session_List( return l, nil } +// ---------------------------------------------------------------------------- +// Table: networks +// ---------------------------------------------------------------------------- + +type Network struct { + NetworkID int64 + Name string + Network []byte +} + +const Network_SelectQuery = "SELECT NetworkID,Name,Network FROM networks" + +func Network_Insert( + tx TX, + row *Network, +) (err error) { + Network_Sanitize(row) + if err = Network_Validate(row); err != nil { + return err + } + + _, err = tx.Exec("INSERT INTO networks(NetworkID,Name,Network) VALUES(?,?,?)", row.NetworkID, row.Name, row.Network) + return err +} + +func Network_UpdateFull( + tx TX, + row *Network, +) (err error) { + Network_Sanitize(row) + if err = Network_Validate(row); err != nil { + return err + } + + result, err := tx.Exec("UPDATE networks SET Name=?,Network=? WHERE NetworkID=?", row.Name, row.Network, row.NetworkID) + if err != nil { + return err + } + + n, err := result.RowsAffected() + if err != nil { + panic(err) + } + switch n { + case 0: + return sql.ErrNoRows + case 1: + return nil + default: + panic("multiple rows updated") + } +} + +func Network_Delete( + tx TX, + NetworkID int64, +) (err error) { + result, err := tx.Exec("DELETE FROM networks WHERE NetworkID=?", NetworkID) + if err != nil { + return err + } + + n, err := result.RowsAffected() + if err != nil { + panic(err) + } + switch n { + case 0: + return sql.ErrNoRows + case 1: + return nil + default: + panic("multiple rows deleted") + } +} + +func Network_Get( + tx TX, + NetworkID int64, +) ( + row *Network, + err error, +) { + row = &Network{} + r := tx.QueryRow("SELECT NetworkID,Name,Network FROM networks WHERE NetworkID=?", NetworkID) + err = r.Scan(&row.NetworkID, &row.Name, &row.Network) + return +} + +func Network_GetWhere( + tx TX, + query string, + args ...any, +) ( + row *Network, + err error, +) { + row = &Network{} + r := tx.QueryRow(query, args...) + err = r.Scan(&row.NetworkID, &row.Name, &row.Network) + return +} + +func Network_Iterate( + tx TX, + query string, + args ...any, +) iter.Seq2[*Network, error] { + rows, err := tx.Query(query, args...) + if err != nil { + return func(yield func(*Network, error) bool) { + yield(nil, err) + } + } + + return func(yield func(*Network, error) bool) { + defer rows.Close() + for rows.Next() { + row := &Network{} + err := rows.Scan(&row.NetworkID, &row.Name, &row.Network) + if !yield(row, err) { + return + } + } + } +} + +func Network_List( + tx TX, + query string, + args ...any, +) ( + l []*Network, + err error, +) { + for row, err := range Network_Iterate(tx, query, args...) { + if err != nil { + return nil, err + } + l = append(l, row) + } + return l, nil +} + // ---------------------------------------------------------------------------- // Table: peers // ---------------------------------------------------------------------------- type Peer struct { + NetworkID int64 PeerIP byte Version int64 APIKey string @@ -318,7 +461,7 @@ type Peer struct { PubSignKey []byte } -const Peer_SelectQuery = "SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey FROM peers" +const Peer_SelectQuery = "SELECT NetworkID,PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey FROM peers" func Peer_Insert( tx TX, @@ -329,7 +472,7 @@ func Peer_Insert( return err } - _, 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) + _, err = tx.Exec("INSERT INTO peers(NetworkID,PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey) VALUES(?,?,?,?,?,?,?,?,?,?)", row.NetworkID, row.PeerIP, row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey, row.PubSignKey) return err } @@ -342,7 +485,7 @@ func Peer_Update( return err } - result, err := tx.Exec("UPDATE peers SET Version=?,Name=?,PublicIP=?,Port=?,Relay=? WHERE PeerIP=?", row.Version, row.Name, row.PublicIP, row.Port, row.Relay, row.PeerIP) + result, err := tx.Exec("UPDATE peers SET Version=?,Name=?,PublicIP=?,Port=?,Relay=? WHERE NetworkID=? AND PeerIP=?", row.Version, row.Name, row.PublicIP, row.Port, row.Relay, row.NetworkID, row.PeerIP) if err != nil { return err } @@ -370,7 +513,7 @@ func Peer_UpdateFull( return err } - 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) + result, err := tx.Exec("UPDATE peers SET Version=?,APIKey=?,Name=?,PublicIP=?,Port=?,Relay=?,PubKey=?,PubSignKey=? WHERE NetworkID=? AND PeerIP=?", row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey, row.PubSignKey, row.NetworkID, row.PeerIP) if err != nil { return err } @@ -391,9 +534,10 @@ func Peer_UpdateFull( func Peer_Delete( tx TX, + NetworkID int64, PeerIP byte, ) (err error) { - result, err := tx.Exec("DELETE FROM peers WHERE PeerIP=?", PeerIP) + result, err := tx.Exec("DELETE FROM peers WHERE NetworkID=? AND PeerIP=?", NetworkID, PeerIP) if err != nil { return err } @@ -414,14 +558,15 @@ func Peer_Delete( func Peer_Get( tx TX, + NetworkID int64, PeerIP byte, ) ( row *Peer, err error, ) { row = &Peer{} - 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) + r := tx.QueryRow("SELECT NetworkID,PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey FROM peers WHERE NetworkID=? AND PeerIP=?", NetworkID, PeerIP) + err = r.Scan(&row.NetworkID, &row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey) return } @@ -435,7 +580,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, &row.PubSignKey) + err = r.Scan(&row.NetworkID, &row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey) return } @@ -455,7 +600,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, &row.PubSignKey) + err := rows.Scan(&row.NetworkID, &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/sanitize-validate.go b/hub/api/db/sanitize-validate.go index e06ad94..71785e9 100644 --- a/hub/api/db/sanitize-validate.go +++ b/hub/api/db/sanitize-validate.go @@ -3,35 +3,21 @@ package db import ( "errors" "net/netip" - "net/url" "strings" ) var ( - ErrInvalidIP = errors.New("invalid IP") - ErrInvalidPort = errors.New("invalid port") + ErrInvalidIP = errors.New("invalid IP") + ErrNonPrivateIP = errors.New("non-private IP") + ErrInvalidPort = errors.New("invalid port") + ErrInvalidNetName = errors.New("invalid network name") + ErrInvalidPeerName = errors.New("invalid peer name") ) func Config_Sanitize(c *Config) { - if u, err := url.Parse(c.HubAddress); err == nil { - c.HubAddress = u.String() - } - - if addr, ok := netip.AddrFromSlice(c.VPNNetwork); ok { - c.VPNNetwork = addr.AsSlice() - } } func Config_Validate(c *Config) error { - if _, err := url.Parse(c.HubAddress); err != nil { - return err - } - - addr, ok := netip.AddrFromSlice(c.VPNNetwork) - if !ok || !addr.Is4() || addr.As4()[3] != 0 || addr.As4()[0] == 0 { - return ErrInvalidIP - } - return nil } @@ -42,6 +28,42 @@ func Session_Validate(s *Session) error { return nil } +func Network_Sanitize(n *Network) { + n.Name = strings.TrimSpace(n.Name) + + if addr, ok := netip.AddrFromSlice(n.Network); ok { + n.Network = addr.AsSlice() + } +} + +func Network_Validate(c *Network) error { + // 16 bytes is linux limit for network interface names. + if len(c.Name) == 0 || len(c.Name) > 16 { + return ErrInvalidNetName + } + + for _, c := range c.Name { + if c >= 'a' && c <= 'z' { + continue + } + if c >= '0' && c <= '9' { + continue + } + return ErrInvalidNetName + } + + addr, ok := netip.AddrFromSlice(c.Network) + if !ok || !addr.Is4() || addr.As4()[3] != 0 || addr.As4()[0] == 0 { + return ErrInvalidIP + } + + if !addr.IsPrivate() { + return ErrNonPrivateIP + } + + return nil +} + func Peer_Sanitize(p *Peer) { p.Name = strings.TrimSpace(p.Name) if len(p.PublicIP) != 0 { @@ -65,5 +87,20 @@ func Peer_Validate(p *Peer) error { if p.Port == 0 { return ErrInvalidPort } + + for _, c := range p.Name { + if c >= 'a' && c <= 'z' { + continue + } + if c >= '0' && c <= '9' { + continue + } + if c == '.' || c == '-' || c == '_' { + continue + } + + return ErrInvalidPeerName + } + return nil } diff --git a/hub/api/db/tables.defs b/hub/api/db/tables.defs index 08244e4..d6dc338 100644 --- a/hub/api/db/tables.defs +++ b/hub/api/db/tables.defs @@ -1,8 +1,6 @@ TABLE config OF Config ( ConfigID int64 PK, - HubAddress string, - VPNNetwork []byte, - Password []byte NoUpdate + Password []byte ); TABLE sessions OF Session NoUpdate ( @@ -13,7 +11,14 @@ TABLE sessions OF Session NoUpdate ( LastSeenAt int64 ); +TABLE networks OF Network ( + NetworkID int64 PK, + Name string NoUpdate, + Network []byte NoUpdate +); + TABLE peers OF Peer ( + NetworkID int64 PK, PeerIP byte PK, Version int64, APIKey string NoUpdate, diff --git a/hub/api/db/written.go b/hub/api/db/written.go index 5b8bb15..6d61bb5 100644 --- a/hub/api/db/written.go +++ b/hub/api/db/written.go @@ -26,16 +26,9 @@ func Session_DeleteBefore( return err } -func Config_UpdatePassword( - tx TX, - pwdHash []byte, -) (err error) { - _, err = tx.Exec("UPDATE config SET Password=? WHERE ConfigID=1", pwdHash) - return err -} - -func Peer_ListAll(tx TX) ([]*Peer, error) { - return Peer_List(tx, Peer_SelectQuery) +func Peer_ListAll(tx TX, networkID int64) ([]*Peer, error) { + const query = Peer_SelectQuery + ` WHERE NetworkID=? ORDER BY PeerIP ASC` + return Peer_List(tx, query, networkID) } func Peer_GetByAPIKey(tx TX, apiKey string) (*Peer, error) { @@ -45,7 +38,8 @@ func Peer_GetByAPIKey(tx TX, apiKey string) (*Peer, error) { apiKey) } -func Peer_Exists(tx TX, ip byte) (exists bool, err error) { - err = tx.QueryRow(`SELECT EXISTS(SELECT 1 FROM peers WHERE PeerIP=?)`, ip).Scan(&exists) +func Peer_Exists(tx TX, networkID int64, ip byte) (exists bool, err error) { + const query = `SELECT EXISTS(SELECT 1 FROM peers WHERE NetworkID=? AND PeerIP=?)` + err = tx.QueryRow(query, networkID, ip).Scan(&exists) return } diff --git a/hub/api/migrations/2024-11-30-init.sql b/hub/api/migrations/2024-11-30-init.sql index ee37ddc..f60aa77 100644 --- a/hub/api/migrations/2024-11-30-init.sql +++ b/hub/api/migrations/2024-11-30-init.sql @@ -1,7 +1,5 @@ CREATE TABLE config ( ConfigID INTEGER NOT NULL PRIMARY KEY, -- Always 1. - HubAddress TEXT NOT NULL, -- https://for.example.com - VPNNetwork BLOB NOT NULL, -- Network (/24), example 10.51.50.0 Password BLOB NOT NULL -- bcrypt password for web interface ) WITHOUT ROWID; @@ -15,13 +13,22 @@ CREATE TABLE sessions ( CREATE INDEX sessions_last_seen_index ON sessions(LastSeenAt); +CREATE TABLE networks ( + NetworkID INTEGER NOT NULL PRIMARY KEY, + Name TEXT NOT NULL UNIQUE, -- Network/interface name. + Network BLOB NOT NULL UNIQUE -- Network (/24), example 10.51.50.0 +) WITHOUT ROWID; + CREATE TABLE peers ( - PeerIP INTEGER NOT NULL PRIMARY KEY, -- Final byte. - Version INTEGER NOT NULL, - APIKey TEXT NOT NULL UNIQUE, + NetworkID INTEGER NOT NULL, + PeerIP INTEGER NOT NULL, -- Final byte of IP. + Version INTEGER NOT NULL, -- Changes when updated. + APIKey TEXT NOT NULL UNIQUE, -- Peer's secret API key. Name TEXT NOT NULL UNIQUE, -- For humans. PublicIP BLOB NOT NULL, Port INTEGER NOT NULL, Relay INTEGER NOT NULL DEFAULT 0, -- Boolean if peer will forward packets. Must also have public address. - PubKey BLOB NOT NULL + PubKey BLOB NOT NULL, + PubSignKey BLOB NOT NULL, + PRIMARY KEY(NetworkID, PeerIP) ) WITHOUT ROWID; diff --git a/hub/api/migrations/2024-12-27-signing-keys.sql b/hub/api/migrations/2024-12-27-signing-keys.sql deleted file mode 100644 index 8731917..0000000 --- a/hub/api/migrations/2024-12-27-signing-keys.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE peers ADD COLUMN PubSignKey BLOB NOT NULL DEFAULT ''; diff --git a/hub/api/types.go b/hub/api/types.go index 92d227c..bfcfc04 100644 --- a/hub/api/types.go +++ b/hub/api/types.go @@ -4,4 +4,5 @@ import "vppn/hub/api/db" type Config = db.Config type Session = db.Session +type Network = db.Network type Peer = db.Peer diff --git a/hub/form.go b/hub/form.go new file mode 100644 index 0000000..645223d --- /dev/null +++ b/hub/form.go @@ -0,0 +1,42 @@ +package hub + +import ( + "net/url" + "vppn/hub/api" + + "git.crumpington.com/lib/go/webutil" +) + +func (app *App) formGetNetwork(form url.Values) (*api.Network, error) { + var id int64 + if err := webutil.NewFormScanner(form).Scan("NetworkID", &id).Error(); err != nil { + return nil, err + } + + return app.api.Network_Get(id) +} + +func (app *App) formGetNetworkPeers(form url.Values) (*api.Network, []*api.Peer, error) { + n, err := app.formGetNetwork(form) + if err != nil { + return nil, nil, err + } + + peers, err := app.api.Peer_List(n.NetworkID) + return n, peers, err +} + +func (app *App) formGetPeer(form url.Values) (*api.Network, *api.Peer, error) { + net, err := app.formGetNetwork(form) + if err != nil { + return nil, nil, err + } + + var ip byte + if err := webutil.NewFormScanner(form).Scan("PeerIP", &ip).Error(); err != nil { + return nil, nil, err + } + + peer, err := app.api.Peer_Get(net.NetworkID, ip) + return net, peer, err +} diff --git a/hub/handlers.go b/hub/handlers.go index c81c9ad..ab3c625 100644 --- a/hub/handlers.go +++ b/hub/handlers.go @@ -16,7 +16,7 @@ import ( func (a *App) _root(s *api.Session, w http.ResponseWriter, r *http.Request) error { if s.SignedIn { - return a.redirect(w, r, "/admin/config/") + return a.redirect(w, r, "/admin/network/list/") } else { return a.redirect(w, r, "/sign-in/") } @@ -54,54 +54,219 @@ func (a *App) _adminSignOutSubmit(s *api.Session, w http.ResponseWriter, r *http return a.redirect(w, r, "/") } -func (a *App) _adminConfig(s *api.Session, w http.ResponseWriter, r *http.Request) error { - peers, err := a.api.Peer_List() +func (a *App) _adminNetworkList(s *api.Session, w http.ResponseWriter, r *http.Request) error { + l, err := a.api.Network_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(), - }) + return a.render("/admin-network-list.html", w, struct { + Session *api.Session + Networks []*api.Network + }{s, l}) } -func (a *App) _adminConfigEdit(s *api.Session, w http.ResponseWriter, r *http.Request) error { - return a.render("/admin-config-edit.html", w, struct { - Session *api.Session - Config *api.Config - }{ - s, - a.api.Config_Get(), - }) +func (a *App) _adminNetworkCreate(s *api.Session, w http.ResponseWriter, r *http.Request) error { + return a.render("/admin-network-create.html", w, struct{ Session *api.Session }{s}) } -func (a *App) _adminConfigEditSubmit(s *api.Session, w http.ResponseWriter, r *http.Request) error { - var ( - conf = a.api.Config_Get() - ipStr string - ) +func (a *App) _adminNetworkCreateSubmit(s *api.Session, w http.ResponseWriter, r *http.Request) error { + n := &api.Network{} + var netStr string err := webutil.NewFormScanner(r.Form). - Scan("HubAddress", &conf.HubAddress). - Scan("VPNNetwork", &ipStr). + Scan("Name", &n.Name). + Scan("Network", &netStr). Error() if err != nil { return err } - if conf.VPNNetwork, err = stringToIP(ipStr); err != nil { + n.Network, err = stringToIP(netStr) + if err != nil { return err } - if err := a.api.Config_Update(conf); err != nil { + + if err := a.api.Network_Create(n); err != nil { return err } - return a.redirect(w, r, "/admin/config/") + + return a.redirect(w, r, "/admin/network/view/?NetworkID=%d", n.NetworkID) +} + +func (a *App) _adminNetworkView(s *api.Session, w http.ResponseWriter, r *http.Request) error { + n, peers, err := a.formGetNetworkPeers(r.Form) + if err != nil { + return err + } + + return a.render("/network/network-view.html", w, struct { + Session *api.Session + Network *api.Network + Peers []*api.Peer + }{s, n, peers}) +} + +func (a *App) _adminNetworkDelete(s *api.Session, w http.ResponseWriter, r *http.Request) error { + n, peers, err := a.formGetNetworkPeers(r.Form) + if err != nil { + return err + } + + return a.render("/network/network-delete.html", w, struct { + Session *api.Session + Network *api.Network + Peers []*api.Peer + }{s, n, peers}) +} + +func (a *App) _adminNetworkDeleteSubmit(s *api.Session, w http.ResponseWriter, r *http.Request) error { + n, err := a.formGetNetwork(r.Form) + if err != nil { + return err + } + + if err = a.api.Network_Delete(n); err != nil { + return err + } + return a.redirect(w, r, "/admin/network/list/") +} + +func (a *App) _adminPeerCreate(s *api.Session, w http.ResponseWriter, r *http.Request) error { + n, err := a.formGetNetwork(r.Form) + if err != nil { + return err + } + + return a.render("/network/peer-create.html", w, struct { + Session *api.Session + Network *api.Network + }{s, n}) +} + +func (a *App) _adminPeerCreateSubmit(s *api.Session, w http.ResponseWriter, r *http.Request) error { + var ipStr string + + p := &api.Peer{} + err := webutil.NewFormScanner(r.Form). + Scan("NetworkID", &p.NetworkID). + Scan("IP", &p.PeerIP). + Scan("Name", &p.Name). + Scan("PublicIP", &ipStr). + Scan("Port", &p.Port). + Scan("Relay", &p.Relay). + Error() + if err != nil { + return err + } + + if p.PublicIP, err = stringToIP(ipStr); err != nil { + return err + } + + if err := a.api.Peer_CreateNew(p); err != nil { + return err + } + return a.redirect(w, r, "/admin/peer/view/?NetworkID=%d&PeerIP=%d", p.NetworkID, p.PeerIP) +} + +func (a *App) _adminPeerView(s *api.Session, w http.ResponseWriter, r *http.Request) error { + net, peer, err := a.formGetPeer(r.Form) + if err != nil { + return err + } + + return a.render("/network/peer-view.html", w, struct { + Session *api.Session + Network *api.Network + Peer *api.Peer + }{s, net, peer}) +} + +func (a *App) _adminPeerEdit(s *api.Session, w http.ResponseWriter, r *http.Request) error { + net, peer, err := a.formGetPeer(r.Form) + if err != nil { + return err + } + + return a.render("/network/peer-edit.html", w, struct { + Session *api.Session + Network *api.Network + Peer *api.Peer + }{s, net, peer}) +} + +func (a *App) _adminPeerEditSubmit(s *api.Session, w http.ResponseWriter, r *http.Request) error { + _, peer, err := a.formGetPeer(r.Form) + if err != nil { + return err + } + + var ipStr string + + err = webutil.NewFormScanner(r.Form). + Scan("Name", &peer.Name). + Scan("PublicIP", &ipStr). + Scan("Port", &peer.Port). + Scan("Relay", &peer.Relay). + Error() + if err != nil { + return err + } + + if peer.PublicIP, err = stringToIP(ipStr); err != nil { + return err + } + + if err = a.api.Peer_Update(peer); err != nil { + return err + } + + return a.redirect(w, r, "/admin/peer/view/?NetworkID=%d&PeerIP=%d", peer.NetworkID, peer.PeerIP) +} + +func (a *App) _adminPeerDelete(s *api.Session, w http.ResponseWriter, r *http.Request) error { + n, peer, err := a.formGetPeer(r.Form) + if err != nil { + return err + } + + return a.render("/network/peer-delete.html", w, struct { + Session *api.Session + Network *api.Network + Peer *api.Peer + }{s, n, peer}) +} + +func (a *App) _adminPeerDeleteSubmit(s *api.Session, w http.ResponseWriter, r *http.Request) error { + n, peer, err := a.formGetPeer(r.Form) + if err != nil { + return err + } + if err := a.api.Peer_Delete(n.NetworkID, peer.PeerIP); err != nil { + return err + } + return a.redirect(w, r, "/admin/network/view/?NetworkID=%d", n.NetworkID) +} + +func (a *App) _adminNetworkHosts(s *api.Session, w http.ResponseWriter, r *http.Request) error { + n, peers, err := a.formGetNetworkPeers(r.Form) + if err != nil { + return err + } + + b := strings.Builder{} + + for _, peer := range peers { + ip := n.Network + ip[3] = peer.PeerIP + b.WriteString(netip.AddrFrom4([4]byte(ip)).String()) + b.WriteString(" ") + b.WriteString(peer.Name) + b.WriteString("\n") + } + + w.Write([]byte(b.String())) + return nil } func (a *App) _adminPasswordEdit(s *api.Session, w http.ResponseWriter, r *http.Request) error { @@ -143,194 +308,61 @@ func (a *App) _adminPasswordSubmit(s *api.Session, w http.ResponseWriter, r *htt return err } - if err := a.api.Config_UpdatePassword(hash); err != nil { + conf.Password = hash + + if err := a.api.Config_Update(conf); err != nil { return err } return a.redirect(w, r, "/admin/config/") } -func (a *App) _adminHosts(s *api.Session, w http.ResponseWriter, r *http.Request) error { - conf := a.api.Config_Get() - - peers, err := a.api.Peer_List() - if err != nil { - return err - } - - b := strings.Builder{} - - for _, peer := range peers { - ip := conf.VPNNetwork - ip[3] = peer.PeerIP - b.WriteString(netip.AddrFrom4([4]byte(ip)).String()) - b.WriteString(" ") - b.WriteString(peer.Name) - b.WriteString("\n") - } - - w.Write([]byte(b.String())) - return nil -} - -func (a *App) _adminPeerCreate(s *api.Session, w http.ResponseWriter, r *http.Request) error { - return a.render("/admin-peer-create.html", w, struct{ Session *api.Session }{s}) -} - -func (a *App) _adminPeerCreateSubmit(s *api.Session, w http.ResponseWriter, r *http.Request) error { - var ipStr string - - p := &api.Peer{} - err := webutil.NewFormScanner(r.Form). - Scan("IP", &p.PeerIP). - Scan("Name", &p.Name). - Scan("PublicIP", &ipStr). - Scan("Port", &p.Port). - Scan("Relay", &p.Relay). - Error() - if err != nil { - return err - } - - if p.PublicIP, err = stringToIP(ipStr); err != nil { - return err - } - - 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) _adminPeerView(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 - } - - peer, err := a.api.Peer_Get(peerIP) - if err != nil { - return err - } - - return a.render("/admin-peer-view.html", w, struct { - Session *api.Session - Peer *api.Peer - }{s, peer}) -} - -func (a *App) _adminPeerEdit(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 - } - - peer, err := a.api.Peer_Get(peerIP) - if err != nil { - return err - } - - return a.render("/admin-peer-edit.html", w, struct { - Session *api.Session - Peer *api.Peer - }{s, peer}) -} - -func (a *App) _adminPeerEditSubmit(s *api.Session, w http.ResponseWriter, r *http.Request) error { - var ( - peerIP byte - ipStr string - ) - - err := webutil.NewFormScanner(r.Form).Scan("PeerIP", &peerIP).Error() - if err != nil { - return err - } - - peer, err := a.api.Peer_Get(peerIP) - if err != nil { - return err - } - err = webutil.NewFormScanner(r.Form). - Scan("Name", &peer.Name). - Scan("PublicIP", &ipStr). - Scan("Port", &peer.Port). - Scan("Relay", &peer.Relay). - Error() - if err != nil { - return err - } - - if peer.PublicIP, err = stringToIP(ipStr); err != nil { - return err - } - - if err = a.api.Peer_Update(peer); err != nil { - return err - } - - return a.redirect(w, r, "/admin/peer/view/?PeerIP=%d", peer.PeerIP) -} - -func (a *App) _adminPeerDelete(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 - } - - peer, err := a.api.Peer_Get(peerIP) - if err != nil { - return err - } - - return a.render("/admin-peer-delete.html", w, struct { - Session *api.Session - Peer *api.Peer - }{s, peer}) -} - -func (a *App) _adminPeerDeleteSubmit(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 - } - - if err := a.api.Peer_Delete(peerIP); err != nil { - return err - } - - return a.redirect(w, r, "/admin/peer/list/") -} - func (a *App) _peerInit(peer *api.Peer, w http.ResponseWriter, r *http.Request) error { args := m.PeerInitArgs{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { return err } - conf, err := a.api.Peer_Init(peer, args) + net, err := a.api.Network_Get(peer.NetworkID) if err != nil { return err } - return a.sendJSON(w, conf) + if err := a.api.Peer_Init(peer, args); err != nil { + return err + } + + resp := m.PeerInitResp{ + PeerIP: peer.PeerIP, + Network: net.Network, + } + + resp.NetworkState.Peers, err = a.peersArray(net.NetworkID) + if err != nil { + return err + } + + return a.sendJSON(w, resp) } func (a *App) _peerFetchState(peer *api.Peer, w http.ResponseWriter, r *http.Request) error { - peers, err := a.api.Peer_List() + + peers, err := a.peersArray(peer.NetworkID) if err != nil { return err } + return a.sendJSON(w, m.NetworkState{Peers: peers}) +} - state := m.NetworkState{} +func (a *App) peersArray(networkID int64) (peers [256]*m.Peer, err error) { + l, err := a.api.Peer_List(networkID) + if err != nil { + return peers, err + } - for _, p := range peers { + for _, p := range l { if len(p.PubKey) != 0 { - state.Peers[p.PeerIP] = &m.Peer{ + peers[p.PeerIP] = &m.Peer{ PeerIP: p.PeerIP, Version: p.Version, Name: p.Name, @@ -343,5 +375,5 @@ func (a *App) _peerFetchState(peer *api.Peer, w http.ResponseWriter, r *http.Req } } - return a.sendJSON(w, state) + return } diff --git a/hub/routes.go b/hub/routes.go index 7d505c5..f94e271 100644 --- a/hub/routes.go +++ b/hub/routes.go @@ -9,14 +9,17 @@ func (a *App) registerRoutes() { a.handleNotSignedIn("GET /sign-in/", a._signin) a.handleNotSignedIn("POST /sign-in/", a._signinSubmit) - a.handleSignedIn("GET /admin/config/", a._adminConfig) - a.handleSignedIn("GET /admin/config/edit/", a._adminConfigEdit) - a.handleSignedIn("POST /admin/config/edit/", a._adminConfigEditSubmit) a.handleSignedIn("GET /admin/sign-out/", a._adminSignOut) 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/hosts/", a._adminHosts) + + a.handleSignedIn("GET /admin/network/list/", a._adminNetworkList) + a.handleSignedIn("GET /admin/network/create/", a._adminNetworkCreate) + a.handleSignedIn("POST /admin/network/create/", a._adminNetworkCreateSubmit) + a.handleSignedIn("GET /admin/network/delete/", a._adminNetworkDelete) + a.handleSignedIn("POST /admin/network/delete/", a._adminNetworkDeleteSubmit) + + a.handleSignedIn("GET /admin/network/view/", a._adminNetworkView) + a.handleSignedIn("GET /admin/network/hosts/", a._adminNetworkHosts) a.handleSignedIn("GET /admin/peer/create/", a._adminPeerCreate) a.handleSignedIn("POST /admin/peer/create/", a._adminPeerCreateSubmit) a.handleSignedIn("GET /admin/peer/view/", a._adminPeerView) @@ -25,6 +28,9 @@ func (a *App) registerRoutes() { a.handleSignedIn("GET /admin/peer/delete/", a._adminPeerDelete) a.handleSignedIn("POST /admin/peer/delete/", a._adminPeerDeleteSubmit) + a.handleSignedIn("GET /admin/password/edit/", a._adminPasswordEdit) + a.handleSignedIn("POST /admin/password/edit/", a._adminPasswordSubmit) + a.handlePeer("POST /peer/init/", a._peerInit) a.handlePeer("GET /peer/fetch-state/", a._peerFetchState) } diff --git a/hub/templates/admin-config-edit.html b/hub/templates/admin-config-edit.html deleted file mode 100644 index 0dcb1b3..0000000 --- a/hub/templates/admin-config-edit.html +++ /dev/null @@ -1,20 +0,0 @@ -{{define "body" -}} -

Config

- -
- -

-
- -

-

-
- -

-

- - Cancel -

-
- -{{- end}} diff --git a/hub/templates/admin-network-create.html b/hub/templates/admin-network-create.html new file mode 100644 index 0000000..786ae8b --- /dev/null +++ b/hub/templates/admin-network-create.html @@ -0,0 +1,19 @@ +{{define "body" -}} +

Create Network

+ +
+ +

+
+ +

+

+
+ +

+

+ + Cancel +

+
+{{- end}} diff --git a/hub/templates/admin-network-list.html b/hub/templates/admin-network-list.html new file mode 100644 index 0000000..3626d6f --- /dev/null +++ b/hub/templates/admin-network-list.html @@ -0,0 +1,38 @@ +{{define "body" -}} +

Networks

+ +

+ Create +

+ +{{if .Networks -}} + + + + + + + + + {{range .Networks -}} + + + + + + {{- end}} +
NameNetwork
+ + {{.Name}} + + {{ipToString .Network}}
+{{- else}} +

No networks.

+{{- end}} + +

Settings

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

Delete Peer

- -{{with .Peer -}} -
- -

-
- -

-

-
- -

-

-
- -

-

-
- -

-

- -

-

- - Cancel -

-
-{{- end}} - -{{- end}} diff --git a/hub/templates/admin-peer-init.html b/hub/templates/admin-peer-init.html deleted file mode 100644 index a00b8d9..0000000 --- a/hub/templates/admin-peer-init.html +++ /dev/null @@ -1,13 +0,0 @@ -{{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 deleted file mode 100644 index 9a1d05f..0000000 --- a/hub/templates/admin-peer-intent.html +++ /dev/null @@ -1,13 +0,0 @@ -{{define "body" -}} -

Create Peer

- -

- Configure the peer with the following URL: -

-
-  {{.HubAddress}}/peer/create/?Code={{.Code}}
-
-

- Done -

-{{- end}} diff --git a/hub/templates/admin-sign-out.html b/hub/templates/admin-sign-out.html index 812d103..7141fb8 100644 --- a/hub/templates/admin-sign-out.html +++ b/hub/templates/admin-sign-out.html @@ -5,7 +5,7 @@

- Cancel + Cancel

{{- end}} diff --git a/hub/templates/base.html b/hub/templates/base.html index 5179441..8d73aaf 100644 --- a/hub/templates/base.html +++ b/hub/templates/base.html @@ -10,7 +10,7 @@

VPPN

diff --git a/hub/templates/network/base.html b/hub/templates/network/base.html new file mode 100644 index 0000000..b773baf --- /dev/null +++ b/hub/templates/network/base.html @@ -0,0 +1,25 @@ + + + + VPPN Hub + + + + +
+

VPPN

+ +
+

+ Network: + {{.Network.Name}} +

+ + {{block "body" .}}There's nothing here.{{end}} + + diff --git a/hub/templates/network/network-delete.html b/hub/templates/network/network-delete.html new file mode 100644 index 0000000..8b61116 --- /dev/null +++ b/hub/templates/network/network-delete.html @@ -0,0 +1,16 @@ +{{define "body" -}} +

Delete

+ +{{if .Peers -}} +

You must first delete all peers.

+{{- else -}} +
+ + +

+ + Cancel +

+
+{{- end}} +{{- end}} diff --git a/hub/templates/admin-config.html b/hub/templates/network/network-view.html similarity index 57% rename from hub/templates/admin-config.html rename to hub/templates/network/network-view.html index 2c9cb82..3e97698 100644 --- a/hub/templates/admin-config.html +++ b/hub/templates/network/network-view.html @@ -1,27 +1,20 @@ {{define "body" -}} -

Config

-

- Edit / - Change Password + Delete / + Hosts

- - - - - - + +
Hub Address{{.Config.HubAddress}}
VPN Network{{ipToString .Config.VPNNetwork}}Network{{ipToString .Network.Network}}/24
-

Peers

+

Peers

- Add Peer / - Hosts + Create

{{if .Peers -}} @@ -39,7 +32,7 @@ {{range .Peers -}} - + {{.PeerIP}} diff --git a/hub/templates/admin-peer-create.html b/hub/templates/network/peer-create.html similarity index 78% rename from hub/templates/admin-peer-create.html rename to hub/templates/network/peer-create.html index 1a7089b..cc6b92f 100644 --- a/hub/templates/admin-peer-create.html +++ b/hub/templates/network/peer-create.html @@ -1,8 +1,9 @@ {{define "body" -}} -

New Peer

+

New Peer

+


@@ -27,7 +28,7 @@

- Cancel + Cancel

diff --git a/hub/templates/network/peer-delete.html b/hub/templates/network/peer-delete.html new file mode 100644 index 0000000..9da4ae4 --- /dev/null +++ b/hub/templates/network/peer-delete.html @@ -0,0 +1,15 @@ +{{define "body" -}} +

Delete {{.Peer.Name}}

+ +{{with .Peer -}} +
+ + + +

+ + Cancel +

+
+{{- end}} +{{- end}} diff --git a/hub/templates/admin-peer-edit.html b/hub/templates/network/peer-edit.html similarity index 82% rename from hub/templates/admin-peer-edit.html rename to hub/templates/network/peer-edit.html index da40de8..a27f674 100644 --- a/hub/templates/admin-peer-edit.html +++ b/hub/templates/network/peer-edit.html @@ -6,7 +6,7 @@


- +


@@ -28,9 +28,8 @@

- Cancel + Cancel

{{- end}} - {{- end}} diff --git a/hub/templates/admin-peer-view.html b/hub/templates/network/peer-view.html similarity index 51% rename from hub/templates/admin-peer-view.html rename to hub/templates/network/peer-view.html index e8d6f6e..546f69b 100644 --- a/hub/templates/admin-peer-view.html +++ b/hub/templates/network/peer-view.html @@ -1,20 +1,25 @@ {{define "body" -}} -

Peer

- +

{{.Peer.Name}}

- Edit / - Delete + Edit / + Delete

{{with .Peer -}} - - +
Peer IP{{.PeerIP}}
Name{{.Name}}
Public IP{{ipToString .PublicIP}}
Port{{.Port}}
Relay{{if .Relay}}T{{else}}F{{end}}
API Key{{.APIKey}}
+ +
+ API Key +

{{.APIKey}}

+
+ + {{- end}} {{- end}} diff --git a/hub/templates/share/common.html b/hub/templates/share/common.html deleted file mode 100644 index e69de29..0000000 diff --git a/m/models.go b/m/models.go index bf9b73e..0bac684 100644 --- a/m/models.go +++ b/m/models.go @@ -6,12 +6,10 @@ type PeerInitArgs struct { PubSignKey []byte } -type PeerConfig struct { - PeerIP byte - Network []byte - PublicIP []byte - Port uint16 - Relay bool +type PeerInitResp struct { + PeerIP byte + Network []byte + NetworkState NetworkState } type Peer struct { diff --git a/peer/files.go b/peer/files.go index b0eade5..a7d9566 100644 --- a/peer/files.go +++ b/peer/files.go @@ -9,7 +9,8 @@ import ( ) type localConfig struct { - m.PeerConfig + PeerIP byte + Network []byte PubKey []byte PrivKey []byte PubSignKey []byte @@ -25,11 +26,11 @@ func configDir(netName string) string { } func peerConfigPath(netName string) string { - return filepath.Join(configDir(netName), "peer-config.json") + return filepath.Join(configDir(netName), "config.json") } func peerStatePath(netName string) string { - return filepath.Join(configDir(netName), "peer-state.json") + return filepath.Join(configDir(netName), "state.json") } func storeJson(x any, outPath string) error { diff --git a/peer/files_test.go b/peer/files_test.go index 5e32ced..5a7f334 100644 --- a/peer/files_test.go +++ b/peer/files_test.go @@ -16,12 +16,12 @@ func TestFilePaths(t *testing.T) { } path := peerConfigPath("netName") - if path != filepath.Join(confDir, "peer-config.json") { + if path != filepath.Join(confDir, "config.json") { t.Fatal(path) } path = peerStatePath("netName") - if path != filepath.Join(confDir, "peer-state.json") { + if path != filepath.Join(confDir, "state.json") { t.Fatal(path) } } diff --git a/peer/main.go b/peer/main.go index 9ab9ab7..819effb 100644 --- a/peer/main.go +++ b/peer/main.go @@ -6,7 +6,7 @@ import ( ) func Main() { - conf := peerConfig{} + conf := mainArgs{} flag.StringVar(&conf.NetName, "name", "", "[REQUIRED] The network name.") flag.StringVar(&conf.HubAddress, "hub-address", "", "[REQUIRED] The hub address.") diff --git a/peer/mcreader.go b/peer/mcreader.go index 7b8af27..3c1086d 100644 --- a/peer/mcreader.go +++ b/peer/mcreader.go @@ -65,6 +65,7 @@ func runMCReaderInner( SrcIP: h.SourceIP, SrcAddr: remoteAddr, } + logf("Got discovery packet from peer %d.", h.SourceIP) handleControlMsg(h.SourceIP, msg) } } diff --git a/peer/mcwriter.go b/peer/mcwriter.go index eb53af4..c20ed61 100644 --- a/peer/mcwriter.go +++ b/peer/mcwriter.go @@ -45,6 +45,7 @@ func runMCWriter(localIP byte, signingKey []byte) { } for range time.Tick(broadcastInterval) { + log.Printf("[MCWriter] Broadcasting on %v...", multicastAddr) _, err := conn.WriteToUDP(discoveryPacket, multicastAddr) if err != nil { log.Printf("[MCWriter] Failed to write multicast: %v", err) diff --git a/peer/peer.go b/peer/peer.go index c210af4..ade03ce 100644 --- a/peer/peer.go +++ b/peer/peer.go @@ -25,35 +25,42 @@ type peerMain struct { super *supervisor } -type peerConfig struct { +type mainArgs struct { NetName string HubAddress string APIKey string } -func newPeerMain(conf peerConfig) *peerMain { +func newPeerMain(args mainArgs) *peerMain { logf := func(s string, args ...any) { log.Printf("[Main] "+s, args...) } - config, err := loadPeerConfig(conf.NetName) + config, err := loadPeerConfig(args.NetName) if err != nil { logf("Failed to load configuration: %v", err) logf("Initializing...") - initPeerWithHub(conf) + initPeerWithHub(args) - config, err = loadPeerConfig(conf.NetName) + config, err = loadPeerConfig(args.NetName) if err != nil { log.Fatalf("Failed to load configuration: %v", err) } } - iface, err := openInterface(config.Network, config.PeerIP, conf.NetName) + state, err := loadNetworkState(args.NetName) + if err != nil { + log.Fatalf("Failed to load network state: %v", err) + } + + iface, err := openInterface(config.Network, config.PeerIP, args.NetName) if err != nil { log.Fatalf("Failed to open interface: %v", err) } - myAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", config.Port)) + localPeer := state.Peers[config.PeerIP] + + myAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", localPeer.Port)) if err != nil { log.Fatalf("Failed to resolve UDP address: %v", err) } @@ -80,19 +87,19 @@ func newPeerMain(conf peerConfig) *peerMain { } var localAddr netip.AddrPort - ip, localAddrValid := netip.AddrFromSlice(config.PublicIP) + ip, localAddrValid := netip.AddrFromSlice(localPeer.PublicIP) if localAddrValid { - localAddr = netip.AddrPortFrom(ip, config.Port) + localAddr = netip.AddrPortFrom(ip, localPeer.Port) } - rt := newRoutingTable(config.PeerIP, localAddr) + rt := newRoutingTable(localPeer.PeerIP, localAddr) rtPtr := &atomic.Pointer[routingTable]{} rtPtr.Store(&rt) ifReader := newIFReader(iface, writeToUDPAddrPort, rtPtr) super := newSupervisor(writeToUDPAddrPort, rtPtr, config.PrivKey) connReader := newConnReader(conn.ReadFromUDPAddrPort, writeToUDPAddrPort, iface, super.HandleControlMsg, rtPtr) - hubPoller, err := newHubPoller(config.PeerIP, conf.NetName, conf.HubAddress, conf.APIKey, super.HandleControlMsg) + hubPoller, err := newHubPoller(config.PeerIP, args.NetName, args.HubAddress, args.APIKey, super.HandleControlMsg) if err != nil { log.Fatalf("Failed to create hub poller: %v", err) } @@ -123,22 +130,22 @@ func (p *peerMain) Run() { select {} } -func initPeerWithHub(conf peerConfig) { +func initPeerWithHub(args mainArgs) { keys := generateKeys() - initURL, err := url.Parse(conf.HubAddress) + initURL, err := url.Parse(args.HubAddress) if err != nil { log.Fatalf("Failed to parse hub URL: %v", err) } initURL.Path = "/peer/init/" - args := m.PeerInitArgs{ + initArgs := m.PeerInitArgs{ EncPubKey: keys.PubKey, PubSignKey: keys.PubSignKey, } buf := &bytes.Buffer{} - if err := json.NewEncoder(buf).Encode(args); err != nil { + if err := json.NewEncoder(buf).Encode(initArgs); err != nil { log.Fatalf("Failed to encode init args: %v", err) } @@ -146,7 +153,7 @@ func initPeerWithHub(conf peerConfig) { if err != nil { log.Fatalf("Failed to construct request: %v", err) } - req.SetBasicAuth("", conf.APIKey) + req.SetBasicAuth("", args.APIKey) resp, err := http.DefaultClient.Do(req) if err != nil { @@ -159,17 +166,24 @@ func initPeerWithHub(conf peerConfig) { log.Fatalf("Failed to read response body: %v", err) } - peerConfig := localConfig{} - if err := json.Unmarshal(data, &peerConfig.PeerConfig); err != nil { + initResp := m.PeerInitResp{} + if err := json.Unmarshal(data, &initResp); err != nil { log.Fatalf("Failed to parse configuration: %v\n%s", err, data) } - peerConfig.PubKey = keys.PubKey - peerConfig.PrivKey = keys.PrivKey - peerConfig.PubSignKey = keys.PubSignKey - peerConfig.PrivSignKey = keys.PrivSignKey + config := localConfig{} + config.PeerIP = initResp.PeerIP + config.Network = initResp.Network + config.PubKey = keys.PubKey + config.PrivKey = keys.PrivKey + config.PubSignKey = keys.PubSignKey + config.PrivSignKey = keys.PrivSignKey - if err := storePeerConfig(conf.NetName, peerConfig); err != nil { + if err := storeNetworkState(args.NetName, initResp.NetworkState); err != nil { + log.Fatalf("Failed to store network state: %v", err) + } + + if err := storePeerConfig(args.NetName, config); err != nil { log.Fatalf("Failed to store configuration: %v", err) } -- 2.39.5