Removed peer editing, cleanup
This commit is contained in:
102
hub/api/api.go
102
hub/api/api.go
@@ -40,7 +40,13 @@ func New(dbPath string) (*API, error) {
|
|||||||
sessions: make(map[string]*Session),
|
sessions: make(map[string]*Session),
|
||||||
}
|
}
|
||||||
|
|
||||||
return a, a.ensurePassword()
|
if err := a.ensurePassword(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go a.sweepSessions()
|
||||||
|
|
||||||
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *API) ensurePassword() error {
|
func (a *API) ensurePassword() error {
|
||||||
@@ -80,58 +86,72 @@ func (a *API) Session_Delete(sessionID string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session_Get returns a snapshot copy of the session for sessionID (creating a
|
const (
|
||||||
// fresh one if absent/expired). Returning a value rather than the stored
|
sessionTTLSecs = 86400 * 21 // sessions expire 21 days after last use
|
||||||
// pointer prevents callers from racing on the shared struct; mutations go
|
sessionSweepEvery = time.Hour // cadence of expired-session eviction
|
||||||
// through Session_SignIn / Session_Delete under the lock.
|
)
|
||||||
|
|
||||||
|
// Session_Get returns a snapshot copy of the signed-in session for sessionID,
|
||||||
|
// or the zero Session if the cookie is missing/unknown/expired. It never
|
||||||
|
// creates a session, so anonymous requests cost no memory — a session is minted
|
||||||
|
// only by Session_SignIn. Returning a value (not the stored pointer) keeps
|
||||||
|
// callers from racing on the shared struct.
|
||||||
func (a *API) Session_Get(sessionID string) (Session, error) {
|
func (a *API) Session_Get(sessionID string) (Session, error) {
|
||||||
a.sessionsMu.Lock()
|
a.sessionsMu.Lock()
|
||||||
defer a.sessionsMu.Unlock()
|
defer a.sessionsMu.Unlock()
|
||||||
|
|
||||||
if sessionID != "" {
|
s, ok := a.sessions[sessionID]
|
||||||
s, ok := a.sessions[sessionID]
|
|
||||||
if ok {
|
if sessionID == "" || !ok {
|
||||||
if timeSince(s.LastSeenAt) <= 86400*21 {
|
return Session{}, nil
|
||||||
if timeSince(s.LastSeenAt) > 86400*7 {
|
|
||||||
s.LastSeenAt = time.Now().Unix()
|
|
||||||
}
|
|
||||||
return *s, nil
|
|
||||||
}
|
|
||||||
delete(a.sessions, sessionID)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return *a.session_Create(), nil
|
if timeSince(s.LastSeenAt) > sessionTTLSecs {
|
||||||
|
delete(a.sessions, sessionID)
|
||||||
|
return Session{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s.LastSeenAt = time.Now().Unix()
|
||||||
|
return *s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// caller must hold sessionsMu
|
// Session_SignIn verifies pwd and, on success, mints a fresh signed-in session,
|
||||||
func (a *API) session_Create() *Session {
|
// returning it so the caller can set the cookie. A new ID per sign-in rotates
|
||||||
|
// the session at the privilege boundary (session-fixation resistance).
|
||||||
|
func (a *API) Session_SignIn(pwd string) (Session, error) {
|
||||||
|
conf, err := a.Config_Get()
|
||||||
|
if err != nil {
|
||||||
|
return Session{}, err
|
||||||
|
}
|
||||||
|
if err := bcrypt.CompareHashAndPassword(conf.Password, []byte(pwd)); err != nil {
|
||||||
|
return Session{}, ErrNotAuthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
a.sessionsMu.Lock()
|
||||||
|
defer a.sessionsMu.Unlock()
|
||||||
s := &Session{
|
s := &Session{
|
||||||
SessionID: idgen.NewToken(),
|
SessionID: idgen.NewToken(),
|
||||||
|
SignedIn: true,
|
||||||
CreatedAt: time.Now().Unix(),
|
CreatedAt: time.Now().Unix(),
|
||||||
LastSeenAt: time.Now().Unix(),
|
LastSeenAt: time.Now().Unix(),
|
||||||
}
|
}
|
||||||
a.sessions[s.SessionID] = s
|
a.sessions[s.SessionID] = s
|
||||||
return s
|
return *s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *API) Session_SignIn(sessionID, pwd string) error {
|
// sweepSessions periodically evicts sessions past their TTL. Without it, a
|
||||||
conf, err := a.Config_Get()
|
// signed-in session whose ID is never presented again would linger forever
|
||||||
if err != nil {
|
// (Session_Get only evicts on a lookup of that same ID).
|
||||||
return err
|
func (a *API) sweepSessions() {
|
||||||
|
for range time.Tick(sessionSweepEvery) {
|
||||||
|
a.sessionsMu.Lock()
|
||||||
|
for id, s := range a.sessions {
|
||||||
|
if timeSince(s.LastSeenAt) > sessionTTLSecs {
|
||||||
|
delete(a.sessions, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.sessionsMu.Unlock()
|
||||||
}
|
}
|
||||||
if err := bcrypt.CompareHashAndPassword(conf.Password, []byte(pwd)); err != nil {
|
|
||||||
return ErrNotAuthorized
|
|
||||||
}
|
|
||||||
a.sessionsMu.Lock()
|
|
||||||
defer a.sessionsMu.Unlock()
|
|
||||||
s, ok := a.sessions[sessionID]
|
|
||||||
if !ok {
|
|
||||||
// Session expired or was evicted between fetch and sign-in.
|
|
||||||
return ErrNotAuthorized
|
|
||||||
}
|
|
||||||
s.SignedIn = true
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *API) Network_Create(n *Network) error {
|
func (a *API) Network_Create(n *Network) error {
|
||||||
@@ -153,7 +173,6 @@ func (a *API) Network_List() ([]*Network, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *API) Peer_CreateNew(p *Peer) error {
|
func (a *API) Peer_CreateNew(p *Peer) error {
|
||||||
p.Version = idgen.NextID(0)
|
|
||||||
p.WGPubKey = []byte{}
|
p.WGPubKey = []byte{}
|
||||||
p.SignPubKey = []byte{}
|
p.SignPubKey = []byte{}
|
||||||
p.APIKey = idgen.NewToken()
|
p.APIKey = idgen.NewToken()
|
||||||
@@ -175,21 +194,12 @@ func (a *API) Peer_Init(peer *Peer, args m.PeerInitArgs) error {
|
|||||||
return errors.New("peer already initialized")
|
return errors.New("peer already initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
peer.Version = idgen.NextID(0)
|
|
||||||
peer.WGPubKey = args.WGPubKey
|
peer.WGPubKey = args.WGPubKey
|
||||||
peer.SignPubKey = args.SignPubKey
|
peer.SignPubKey = args.SignPubKey
|
||||||
|
|
||||||
return db.Peer_UpdateFull(a.db, peer)
|
return db.Peer_UpdateFull(a.db, peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *API) Peer_Update(p *Peer) error {
|
|
||||||
a.lock.Lock()
|
|
||||||
defer a.lock.Unlock()
|
|
||||||
|
|
||||||
p.Version = idgen.NextID(0)
|
|
||||||
return db.Peer_Update(a.db, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *API) Peer_Delete(networkID int64, peerIP byte) error {
|
func (a *API) Peer_Delete(networkID int64, peerIP byte) error {
|
||||||
return db.Peer_Delete(a.db, networkID, peerIP)
|
return db.Peer_Delete(a.db, networkID, peerIP)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -341,7 +341,6 @@ func Network_List(
|
|||||||
type Peer struct {
|
type Peer struct {
|
||||||
NetworkID int64
|
NetworkID int64
|
||||||
PeerIP byte
|
PeerIP byte
|
||||||
Version int64
|
|
||||||
APIKey string
|
APIKey string
|
||||||
Name string
|
Name string
|
||||||
Addr4 []byte
|
Addr4 []byte
|
||||||
@@ -352,7 +351,7 @@ type Peer struct {
|
|||||||
SignPubKey []byte
|
SignPubKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
const Peer_SelectQuery = "SELECT NetworkID,PeerIP,Version,APIKey,Name,Addr4,Addr6,Port,Relay,WGPubKey,SignPubKey FROM peers"
|
const Peer_SelectQuery = "SELECT NetworkID,PeerIP,APIKey,Name,Addr4,Addr6,Port,Relay,WGPubKey,SignPubKey FROM peers"
|
||||||
|
|
||||||
func Peer_Insert(
|
func Peer_Insert(
|
||||||
tx TX,
|
tx TX,
|
||||||
@@ -363,7 +362,7 @@ func Peer_Insert(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.Exec("INSERT INTO peers(NetworkID,PeerIP,Version,APIKey,Name,Addr4,Addr6,Port,Relay,WGPubKey,SignPubKey) VALUES(?,?,?,?,?,?,?,?,?,?,?)", row.NetworkID, row.PeerIP, row.Version, row.APIKey, row.Name, row.Addr4, row.Addr6, row.Port, row.Relay, row.WGPubKey, row.SignPubKey)
|
_, err = tx.Exec("INSERT INTO peers(NetworkID,PeerIP,APIKey,Name,Addr4,Addr6,Port,Relay,WGPubKey,SignPubKey) VALUES(?,?,?,?,?,?,?,?,?,?)", row.NetworkID, row.PeerIP, row.APIKey, row.Name, row.Addr4, row.Addr6, row.Port, row.Relay, row.WGPubKey, row.SignPubKey)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,7 +375,7 @@ func Peer_Update(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := tx.Exec("UPDATE peers SET Version=?,Name=?,Addr4=?,Addr6=?,Port=?,Relay=? WHERE NetworkID=? AND PeerIP=?", row.Version, row.Name, row.Addr4, row.Addr6, row.Port, row.Relay, row.NetworkID, row.PeerIP)
|
result, err := tx.Exec("UPDATE peers SET Name=?,Addr4=?,Addr6=?,Port=?,Relay=? WHERE NetworkID=? AND PeerIP=?", row.Name, row.Addr4, row.Addr6, row.Port, row.Relay, row.NetworkID, row.PeerIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -404,7 +403,7 @@ func Peer_UpdateFull(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := tx.Exec("UPDATE peers SET Version=?,APIKey=?,Name=?,Addr4=?,Addr6=?,Port=?,Relay=?,WGPubKey=?,SignPubKey=? WHERE NetworkID=? AND PeerIP=?", row.Version, row.APIKey, row.Name, row.Addr4, row.Addr6, row.Port, row.Relay, row.WGPubKey, row.SignPubKey, row.NetworkID, row.PeerIP)
|
result, err := tx.Exec("UPDATE peers SET APIKey=?,Name=?,Addr4=?,Addr6=?,Port=?,Relay=?,WGPubKey=?,SignPubKey=? WHERE NetworkID=? AND PeerIP=?", row.APIKey, row.Name, row.Addr4, row.Addr6, row.Port, row.Relay, row.WGPubKey, row.SignPubKey, row.NetworkID, row.PeerIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -456,8 +455,8 @@ func Peer_Get(
|
|||||||
err error,
|
err error,
|
||||||
) {
|
) {
|
||||||
row = &Peer{}
|
row = &Peer{}
|
||||||
r := tx.QueryRow("SELECT NetworkID,PeerIP,Version,APIKey,Name,Addr4,Addr6,Port,Relay,WGPubKey,SignPubKey FROM peers WHERE NetworkID=? AND PeerIP=?", NetworkID, PeerIP)
|
r := tx.QueryRow("SELECT NetworkID,PeerIP,APIKey,Name,Addr4,Addr6,Port,Relay,WGPubKey,SignPubKey FROM peers WHERE NetworkID=? AND PeerIP=?", NetworkID, PeerIP)
|
||||||
if err = r.Scan(&row.NetworkID, &row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.Addr4, &row.Addr6, &row.Port, &row.Relay, &row.WGPubKey, &row.SignPubKey); err != nil {
|
if err = r.Scan(&row.NetworkID, &row.PeerIP, &row.APIKey, &row.Name, &row.Addr4, &row.Addr6, &row.Port, &row.Relay, &row.WGPubKey, &row.SignPubKey); err != nil {
|
||||||
row = nil
|
row = nil
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -473,7 +472,7 @@ func Peer_GetWhere(
|
|||||||
) {
|
) {
|
||||||
row = &Peer{}
|
row = &Peer{}
|
||||||
r := tx.QueryRow(query, args...)
|
r := tx.QueryRow(query, args...)
|
||||||
if err = r.Scan(&row.NetworkID, &row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.Addr4, &row.Addr6, &row.Port, &row.Relay, &row.WGPubKey, &row.SignPubKey); err != nil {
|
if err = r.Scan(&row.NetworkID, &row.PeerIP, &row.APIKey, &row.Name, &row.Addr4, &row.Addr6, &row.Port, &row.Relay, &row.WGPubKey, &row.SignPubKey); err != nil {
|
||||||
row = nil
|
row = nil
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -495,7 +494,7 @@ func Peer_Iterate(
|
|||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
row := &Peer{}
|
row := &Peer{}
|
||||||
err := rows.Scan(&row.NetworkID, &row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.Addr4, &row.Addr6, &row.Port, &row.Relay, &row.WGPubKey, &row.SignPubKey)
|
err := rows.Scan(&row.NetworkID, &row.PeerIP, &row.APIKey, &row.Name, &row.Addr4, &row.Addr6, &row.Port, &row.Relay, &row.WGPubKey, &row.SignPubKey)
|
||||||
if !yield(row, err) {
|
if !yield(row, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,13 +12,12 @@ TABLE networks OF Network (
|
|||||||
TABLE peers OF Peer (
|
TABLE peers OF Peer (
|
||||||
NetworkID int64 PK,
|
NetworkID int64 PK,
|
||||||
PeerIP byte PK,
|
PeerIP byte PK,
|
||||||
Version int64,
|
|
||||||
APIKey string NoUpdate,
|
APIKey string NoUpdate,
|
||||||
Name string,
|
Name string NoUpdate,
|
||||||
Addr4 []byte,
|
Addr4 []byte NoUpdate,
|
||||||
Addr6 []byte,
|
Addr6 []byte NoUpdate,
|
||||||
Port uint16,
|
Port uint16 NoUpdate,
|
||||||
Relay bool,
|
Relay bool NoUpdate,
|
||||||
WGPubKey []byte NoUpdate,
|
WGPubKey []byte NoUpdate,
|
||||||
SignPubKey []byte NoUpdate
|
SignPubKey []byte NoUpdate
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ CREATE TABLE networks (
|
|||||||
CREATE TABLE peers (
|
CREATE TABLE peers (
|
||||||
NetworkID INTEGER NOT NULL,
|
NetworkID INTEGER NOT NULL,
|
||||||
PeerIP INTEGER NOT NULL, -- Final byte of IP.
|
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.
|
APIKey TEXT NOT NULL UNIQUE, -- Peer's secret API key.
|
||||||
Name TEXT NOT NULL, -- For humans.
|
Name TEXT NOT NULL, -- For humans.
|
||||||
Addr4 BLOB NOT NULL,
|
Addr4 BLOB NOT NULL,
|
||||||
|
|||||||
@@ -20,10 +20,6 @@ func (app *App) handlePub(pattern string, fn handlerFunc) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.SessionID != sessionID {
|
|
||||||
app.setCookie(w, SESSION_ID_COOKIE_NAME, s.SessionID)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Method == http.MethodPost {
|
if r.Method == http.MethodPost {
|
||||||
r.ParseMultipartForm(64 * 1024)
|
r.ParseMultipartForm(64 * 1024)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -34,9 +34,11 @@ func (a *App) _signinSubmit(s *api.Session, w http.ResponseWriter, r *http.Reque
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := a.api.Session_SignIn(s.SessionID, pwd); err != nil {
|
sess, err := a.api.Session_SignIn(pwd)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
a.setCookie(w, SESSION_ID_COOKIE_NAME, sess.SessionID)
|
||||||
|
|
||||||
return a.redirect(w, r, "/")
|
return a.redirect(w, r, "/")
|
||||||
}
|
}
|
||||||
@@ -185,52 +187,6 @@ func (a *App) _adminPeerView(s *api.Session, w http.ResponseWriter, r *http.Requ
|
|||||||
}{s, net, 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 addr4Str, addr6Str string
|
|
||||||
|
|
||||||
err = webutil.NewFormScanner(r.Form).
|
|
||||||
Scan("Name", &peer.Name).
|
|
||||||
Scan("Addr4", &addr4Str).
|
|
||||||
Scan("Addr6", &addr6Str).
|
|
||||||
Scan("Port", &peer.Port).
|
|
||||||
Scan("Relay", &peer.Relay).
|
|
||||||
Error()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if peer.Addr4, err = stringToIP(addr4Str); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if peer.Addr6, err = stringToIP(addr6Str); 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 {
|
func (a *App) _adminPeerDelete(s *api.Session, w http.ResponseWriter, r *http.Request) error {
|
||||||
n, peer, err := a.formGetPeer(r.Form)
|
n, peer, err := a.formGetPeer(r.Form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -378,7 +334,6 @@ func (a *App) peersList(networkID int64) (peers []m.Peer, err error) {
|
|||||||
copy(signKey[:], p.SignPubKey)
|
copy(signKey[:], p.SignPubKey)
|
||||||
peers = append(peers, m.Peer{
|
peers = append(peers, m.Peer{
|
||||||
PeerIP: p.PeerIP,
|
PeerIP: p.PeerIP,
|
||||||
Version: p.Version,
|
|
||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
Addr4: addrFromBytes(p.Addr4),
|
Addr4: addrFromBytes(p.Addr4),
|
||||||
Addr6: addrFromBytes(p.Addr6),
|
Addr6: addrFromBytes(p.Addr6),
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ func (a *App) registerRoutes() {
|
|||||||
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/view/", a._adminPeerView)
|
a.handleSignedIn("GET /admin/peer/view/", a._adminPeerView)
|
||||||
a.handleSignedIn("GET /admin/peer/edit/", a._adminPeerEdit)
|
|
||||||
a.handleSignedIn("POST /admin/peer/edit/", a._adminPeerEditSubmit)
|
|
||||||
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)
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
{{with .Peer -}}
|
{{with .Peer -}}
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<input type="hidden" name="NetworkID" value="{{.NetworkID}}">
|
<input type="hidden" name="NetworkID" value="{{.NetworkID}}">
|
||||||
<input type="hidden" name="NetworkID" value="{{.PeerIP}}">
|
<input type="hidden" name="PeerIP" value="{{.PeerIP}}">
|
||||||
<p>
|
<p>
|
||||||
<button type="submit">Delete</button>
|
<button type="submit">Delete</button>
|
||||||
<a href="/admin/peer/view/?PeerIP={{.PeerIP}}&NetworkID={{.NetworkID}}">Cancel</a>
|
<a href="/admin/peer/view/?PeerIP={{.PeerIP}}&NetworkID={{.NetworkID}}">Cancel</a>
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
{{define "body" -}}
|
|
||||||
<h2>Edit Peer</h2>
|
|
||||||
|
|
||||||
{{with .Peer -}}
|
|
||||||
<form method="POST">
|
|
||||||
<p>
|
|
||||||
<label>Peer IP</label><br>
|
|
||||||
<input type="text" value="{{.PeerIP}}" disabled>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<label>Name</label><br>
|
|
||||||
<input type="text" name="Name" value="{{.Name}}">
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<label>IPv4 Address (optional)</label><br>
|
|
||||||
<input type="text" name="Addr4" value="{{ipToString .Addr4}}">
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<label>IPv6 Address (optional)</label><br>
|
|
||||||
<input type="text" name="Addr6" value="{{ipToString .Addr6}}">
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<label>WireGuard Port</label><br>
|
|
||||||
<input type="number" name="Port" value="{{.Port}}">
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" name="Relay" {{if .Relay}}checked{{end}}>
|
|
||||||
Relay
|
|
||||||
</label>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<button type="submit">Save</button>
|
|
||||||
<a href="/admin/peer/view/?NetworkID={{$.Network.NetworkID}}&PeerIP={{.PeerIP}}">Cancel</a>
|
|
||||||
</p>
|
|
||||||
</form>
|
|
||||||
{{- end}}
|
|
||||||
{{- end}}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
{{define "body" -}}
|
{{define "body" -}}
|
||||||
<h3>{{.Peer.Name}}</h3>
|
<h3>{{.Peer.Name}}</h3>
|
||||||
<p>
|
<p>
|
||||||
<a href="/admin/peer/edit/?NetworkID={{.Network.NetworkID}}&PeerIP={{.Peer.PeerIP}}">Edit</a> /
|
|
||||||
<a href="/admin/peer/delete/?NetworkID={{.Network.NetworkID}}&PeerIP={{.Peer.PeerIP}}">Delete</a>
|
<a href="/admin/peer/delete/?NetworkID={{.Network.NetworkID}}&PeerIP={{.Peer.PeerIP}}">Delete</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ type PeerInitResp struct {
|
|||||||
// non-public peer (it is the peer's own bind/beacon port).
|
// non-public peer (it is the peer's own bind/beacon port).
|
||||||
type Peer struct {
|
type Peer struct {
|
||||||
PeerIP byte
|
PeerIP byte
|
||||||
Version int64
|
|
||||||
Name string
|
Name string
|
||||||
Addr4 netip.Addr // zero if none
|
Addr4 netip.Addr // zero if none
|
||||||
Addr6 netip.Addr // zero if none
|
Addr6 netip.Addr // zero if none
|
||||||
@@ -74,7 +73,6 @@ func (p Peer) PreferredEndpoint() netip.AddrPort {
|
|||||||
// encoding/json would emit them as arrays of numbers).
|
// encoding/json would emit them as arrays of numbers).
|
||||||
type peerJSON struct {
|
type peerJSON struct {
|
||||||
PeerIP byte
|
PeerIP byte
|
||||||
Version int64
|
|
||||||
Name string
|
Name string
|
||||||
Addr4 netip.Addr
|
Addr4 netip.Addr
|
||||||
Addr6 netip.Addr
|
Addr6 netip.Addr
|
||||||
@@ -87,7 +85,6 @@ type peerJSON struct {
|
|||||||
func (p Peer) MarshalJSON() ([]byte, error) {
|
func (p Peer) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(peerJSON{
|
return json.Marshal(peerJSON{
|
||||||
PeerIP: p.PeerIP,
|
PeerIP: p.PeerIP,
|
||||||
Version: p.Version,
|
|
||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
Addr4: p.Addr4,
|
Addr4: p.Addr4,
|
||||||
Addr6: p.Addr6,
|
Addr6: p.Addr6,
|
||||||
@@ -120,7 +117,6 @@ func (p *Peer) UnmarshalJSON(data []byte) error {
|
|||||||
}
|
}
|
||||||
*p = Peer{
|
*p = Peer{
|
||||||
PeerIP: j.PeerIP,
|
PeerIP: j.PeerIP,
|
||||||
Version: j.Version,
|
|
||||||
Name: j.Name,
|
Name: j.Name,
|
||||||
Addr4: j.Addr4,
|
Addr4: j.Addr4,
|
||||||
Addr6: j.Addr6,
|
Addr6: j.Addr6,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ type HubPoller struct {
|
|||||||
statePath string // where the network state cache is persisted
|
statePath string // where the network state cache is persisted
|
||||||
addCh chan<- m.Peer
|
addCh chan<- m.Peer
|
||||||
removeCh chan<- wgtypes.Key
|
removeCh chan<- wgtypes.Key
|
||||||
known map[wgtypes.Key]int64 // pubKey → last seen version
|
known map[wgtypes.Key]struct{} // pubKeys currently configured
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHubPoller(
|
func NewHubPoller(
|
||||||
@@ -49,15 +49,15 @@ func NewHubPoller(
|
|||||||
statePath: statePath,
|
statePath: statePath,
|
||||||
addCh: addCh,
|
addCh: addCh,
|
||||||
removeCh: removeCh,
|
removeCh: removeCh,
|
||||||
known: make(map[wgtypes.Key]int64),
|
known: make(map[wgtypes.Key]struct{}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hp *HubPoller) Run() {
|
func (hp *HubPoller) Run() {
|
||||||
// Prime from the on-disk cache before reaching the hub, so the peer
|
// Prime from the on-disk cache before reaching the hub, so the peer
|
||||||
// configures WireGuard from its last known state even if the hub is down.
|
// configures WireGuard from its last known state even if the hub is down.
|
||||||
// known starts empty, so this emits every cached peer as an add and seeds
|
// known starts empty, so this emits every cached peer as an add; the first
|
||||||
// the version map; the first real poll then emits only deltas.
|
// real poll then emits only deltas (adds for new peers, removes for gone).
|
||||||
if state, err := loadNetworkState(hp.statePath); err == nil {
|
if state, err := loadNetworkState(hp.statePath); err == nil {
|
||||||
hp.apply(state)
|
hp.apply(state)
|
||||||
}
|
}
|
||||||
@@ -110,9 +110,10 @@ func (hp *HubPoller) poll() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply diffs state against the known versions, emitting add events for new or
|
// apply diffs state against the set of known peers, emitting an add for each
|
||||||
// changed peers and remove events for peers that disappeared. It returns true
|
// newly-seen peer and a remove for each that disappeared. It returns true if
|
||||||
// if anything changed.
|
// anything changed. A peer's config is immutable under a stable WG key (the hub
|
||||||
|
// has no peer-edit path), so a key already in known needs no re-emit.
|
||||||
func (hp *HubPoller) apply(state m.NetworkState) (changed bool) {
|
func (hp *HubPoller) apply(state m.NetworkState) (changed bool) {
|
||||||
seen := make(map[wgtypes.Key]struct{}, len(hp.known))
|
seen := make(map[wgtypes.Key]struct{}, len(hp.known))
|
||||||
|
|
||||||
@@ -132,10 +133,10 @@ func (hp *HubPoller) apply(state m.NetworkState) (changed bool) {
|
|||||||
|
|
||||||
seen[p.WGPubKey] = struct{}{}
|
seen[p.WGPubKey] = struct{}{}
|
||||||
|
|
||||||
if v, ok := hp.known[p.WGPubKey]; ok && v == p.Version {
|
if _, ok := hp.known[p.WGPubKey]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
hp.known[p.WGPubKey] = p.Version
|
hp.known[p.WGPubKey] = struct{}{}
|
||||||
hp.addCh <- p
|
hp.addCh <- p
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,15 +18,14 @@ func testPoller(t *testing.T) (*HubPoller, chan m.Peer, chan wgtypes.Key) {
|
|||||||
vpnNet: netip.MustParsePrefix("10.0.0.0/24"),
|
vpnNet: netip.MustParsePrefix("10.0.0.0/24"),
|
||||||
addCh: addCh,
|
addCh: addCh,
|
||||||
removeCh: removeCh,
|
removeCh: removeCh,
|
||||||
known: make(map[wgtypes.Key]int64),
|
known: make(map[wgtypes.Key]struct{}),
|
||||||
}
|
}
|
||||||
return hp, addCh, removeCh
|
return hp, addCh, removeCh
|
||||||
}
|
}
|
||||||
|
|
||||||
func stateWith(key wgtypes.Key, peerIP byte, version int64) m.NetworkState {
|
func stateWith(key wgtypes.Key, peerIP byte) m.NetworkState {
|
||||||
return m.NetworkState{Peers: []m.Peer{{
|
return m.NetworkState{Peers: []m.Peer{{
|
||||||
PeerIP: peerIP,
|
PeerIP: peerIP,
|
||||||
Version: version,
|
|
||||||
WGPubKey: key,
|
WGPubKey: key,
|
||||||
}}}
|
}}}
|
||||||
}
|
}
|
||||||
@@ -35,7 +34,7 @@ func TestApply_EmitsAddsAndReportsChange(t *testing.T) {
|
|||||||
hp, addCh, _ := testPoller(t)
|
hp, addCh, _ := testPoller(t)
|
||||||
key := mustKey(t)
|
key := mustKey(t)
|
||||||
|
|
||||||
if changed := hp.apply(stateWith(key, 2, 1)); !changed {
|
if changed := hp.apply(stateWith(key, 2)); !changed {
|
||||||
t.Fatal("expected changed=true on first apply")
|
t.Fatal("expected changed=true on first apply")
|
||||||
}
|
}
|
||||||
if len(addCh) != 1 {
|
if len(addCh) != 1 {
|
||||||
@@ -46,41 +45,26 @@ func TestApply_EmitsAddsAndReportsChange(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApply_NoChangeWhenVersionSame(t *testing.T) {
|
func TestApply_NoChangeWhenKnown(t *testing.T) {
|
||||||
hp, addCh, _ := testPoller(t)
|
hp, addCh, _ := testPoller(t)
|
||||||
key := mustKey(t)
|
key := mustKey(t)
|
||||||
|
|
||||||
hp.apply(stateWith(key, 2, 1))
|
hp.apply(stateWith(key, 2))
|
||||||
<-addCh // drain initial add
|
<-addCh // drain initial add
|
||||||
|
|
||||||
if changed := hp.apply(stateWith(key, 2, 1)); changed {
|
if changed := hp.apply(stateWith(key, 2)); changed {
|
||||||
t.Fatal("expected changed=false when version unchanged")
|
t.Fatal("expected changed=false when peer already known")
|
||||||
}
|
}
|
||||||
if len(addCh) != 0 {
|
if len(addCh) != 0 {
|
||||||
t.Fatalf("expected no re-emit, got %d adds", len(addCh))
|
t.Fatalf("expected no re-emit, got %d adds", len(addCh))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApply_ReEmitsOnVersionBump(t *testing.T) {
|
|
||||||
hp, addCh, _ := testPoller(t)
|
|
||||||
key := mustKey(t)
|
|
||||||
|
|
||||||
hp.apply(stateWith(key, 2, 1))
|
|
||||||
<-addCh
|
|
||||||
|
|
||||||
if changed := hp.apply(stateWith(key, 2, 2)); !changed {
|
|
||||||
t.Fatal("expected changed=true on version bump")
|
|
||||||
}
|
|
||||||
if len(addCh) != 1 {
|
|
||||||
t.Fatalf("expected 1 re-emit, got %d", len(addCh))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestApply_RemovesVanishedPeer(t *testing.T) {
|
func TestApply_RemovesVanishedPeer(t *testing.T) {
|
||||||
hp, addCh, removeCh := testPoller(t)
|
hp, addCh, removeCh := testPoller(t)
|
||||||
key := mustKey(t)
|
key := mustKey(t)
|
||||||
|
|
||||||
hp.apply(stateWith(key, 2, 1))
|
hp.apply(stateWith(key, 2))
|
||||||
<-addCh
|
<-addCh
|
||||||
|
|
||||||
// Empty state: the peer is gone.
|
// Empty state: the peer is gone.
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ func TestNetworkState_RoundTrip(t *testing.T) {
|
|||||||
state := m.NetworkState{Peers: []m.Peer{
|
state := m.NetworkState{Peers: []m.Peer{
|
||||||
{
|
{
|
||||||
PeerIP: 1,
|
PeerIP: 1,
|
||||||
Version: 7,
|
|
||||||
Name: "hub",
|
Name: "hub",
|
||||||
Addr4: netip.MustParseAddr("10.11.12.1"),
|
Addr4: netip.MustParseAddr("10.11.12.1"),
|
||||||
Port: 51820,
|
Port: 51820,
|
||||||
@@ -28,7 +27,6 @@ func TestNetworkState_RoundTrip(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
PeerIP: 10,
|
PeerIP: 10,
|
||||||
Version: 3,
|
|
||||||
Name: "laptop",
|
Name: "laptop",
|
||||||
Addr4: netip.MustParseAddr("10.11.12.10"),
|
Addr4: netip.MustParseAddr("10.11.12.10"),
|
||||||
Port: 51820,
|
Port: 51820,
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ func (a *App) onAddPeer(p m.Peer) {
|
|||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
IsRelay: p.Relay,
|
IsRelay: p.Relay,
|
||||||
IsPublic: p.IsPublic(),
|
IsPublic: p.IsPublic(),
|
||||||
Endpoint4: p.Endpoint4(),
|
EndpointV4: p.Endpoint4(),
|
||||||
Endpoint6: p.Endpoint6(),
|
EndpointV6: p.Endpoint6(),
|
||||||
RTT: time.Duration(math.MaxInt64) * time.Nanosecond,
|
RTT: time.Duration(math.MaxInt64) * time.Nanosecond,
|
||||||
Role: roleFor(a.isPublic, a.vpnIP, p.IsPublic(), vpnIP),
|
Role: roleFor(a.isPublic, a.vpnIP, p.IsPublic(), vpnIP),
|
||||||
SignPubKey: p.SignPubKey,
|
SignPubKey: p.SignPubKey,
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ func TestSwitchActiveRelay(t *testing.T) {
|
|||||||
t.Errorf("call[0]: got %v, want AddDirect with ep1", dev.Calls[0])
|
t.Errorf("call[0]: got %v, want AddDirect with ep1", dev.Calls[0])
|
||||||
}
|
}
|
||||||
dev.AssertSetRelay(t, 1, dev.Calls[1].PubKey, ep2, a.vpnNet)
|
dev.AssertSetRelay(t, 1, dev.Calls[1].PubKey, ep2, a.vpnNet)
|
||||||
if a.relay == nil || a.relay.Endpoint4 != ep2 {
|
if a.relay == nil || a.relay.EndpointV4 != ep2 {
|
||||||
t.Error("relay should be the backup peer")
|
t.Error("relay should be the backup peer")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ type Peer struct {
|
|||||||
Name string // Human-readable DNS label.
|
Name string // Human-readable DNS label.
|
||||||
IsRelay bool // Peer is a relay.
|
IsRelay bool // Peer is a relay.
|
||||||
IsPublic bool // Peer has a public IP.
|
IsPublic bool // Peer has a public IP.
|
||||||
Endpoint4 netip.AddrPort // Reported IPv4 endpoint.
|
EndpointV4 netip.AddrPort // Reported IPv4 endpoint.
|
||||||
Endpoint6 netip.AddrPort // Reported IPv6 endpoint.
|
EndpointV6 netip.AddrPort // Reported IPv6 endpoint.
|
||||||
RTT time.Duration // Round-trip time.
|
RTT time.Duration // Round-trip time.
|
||||||
State PeerState // Current routing state; updated on each devXxx call.
|
State PeerState // Current routing state; updated on each devXxx call.
|
||||||
Role control.Role // Client initiates pings; server responds.
|
Role control.Role // Client initiates pings; server responds.
|
||||||
@@ -62,14 +62,14 @@ func (p *Peer) CanRelay() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Peer) PreferredEndpoint() netip.AddrPort {
|
func (p *Peer) PreferredEndpoint() netip.AddrPort {
|
||||||
return preferredEndpoint(p.Endpoint4, p.Endpoint6)
|
return preferredEndpoint(p.EndpointV4, p.EndpointV6)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Peer) UpdateEndpoints(v4, v6 netip.AddrPort) {
|
func (p *Peer) UpdateEndpoints(v4, v6 netip.AddrPort) {
|
||||||
if v4.IsValid() {
|
if v4.IsValid() {
|
||||||
p.Endpoint4 = v4
|
p.EndpointV4 = v4
|
||||||
}
|
}
|
||||||
if v6.IsValid() {
|
if v6.IsValid() {
|
||||||
p.Endpoint6 = v6
|
p.EndpointV6 = v6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user