Removed peer editing, cleanup
This commit is contained in:
@@ -40,7 +40,13 @@ func New(dbPath string) (*API, error) {
|
||||
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 {
|
||||
@@ -80,58 +86,72 @@ func (a *API) Session_Delete(sessionID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Session_Get returns a snapshot copy of the session for sessionID (creating a
|
||||
// fresh one if absent/expired). Returning a value rather than the stored
|
||||
// pointer prevents callers from racing on the shared struct; mutations go
|
||||
// through Session_SignIn / Session_Delete under the lock.
|
||||
const (
|
||||
sessionTTLSecs = 86400 * 21 // sessions expire 21 days after last use
|
||||
sessionSweepEvery = time.Hour // cadence of expired-session eviction
|
||||
)
|
||||
|
||||
// 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) {
|
||||
a.sessionsMu.Lock()
|
||||
defer a.sessionsMu.Unlock()
|
||||
|
||||
if sessionID != "" {
|
||||
s, ok := a.sessions[sessionID]
|
||||
if ok {
|
||||
if timeSince(s.LastSeenAt) <= 86400*21 {
|
||||
if timeSince(s.LastSeenAt) > 86400*7 {
|
||||
s.LastSeenAt = time.Now().Unix()
|
||||
|
||||
if sessionID == "" || !ok {
|
||||
return Session{}, nil
|
||||
}
|
||||
|
||||
if timeSince(s.LastSeenAt) > sessionTTLSecs {
|
||||
delete(a.sessions, sessionID)
|
||||
return Session{}, nil
|
||||
}
|
||||
|
||||
s.LastSeenAt = time.Now().Unix()
|
||||
return *s, nil
|
||||
}
|
||||
delete(a.sessions, sessionID)
|
||||
|
||||
// Session_SignIn verifies pwd and, on success, mints a fresh signed-in 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
|
||||
}
|
||||
|
||||
return *a.session_Create(), nil
|
||||
}
|
||||
|
||||
// caller must hold sessionsMu
|
||||
func (a *API) session_Create() *Session {
|
||||
a.sessionsMu.Lock()
|
||||
defer a.sessionsMu.Unlock()
|
||||
s := &Session{
|
||||
SessionID: idgen.NewToken(),
|
||||
SignedIn: true,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
LastSeenAt: time.Now().Unix(),
|
||||
}
|
||||
a.sessions[s.SessionID] = s
|
||||
return s
|
||||
return *s, nil
|
||||
}
|
||||
|
||||
func (a *API) Session_SignIn(sessionID, pwd string) error {
|
||||
conf, err := a.Config_Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := bcrypt.CompareHashAndPassword(conf.Password, []byte(pwd)); err != nil {
|
||||
return ErrNotAuthorized
|
||||
}
|
||||
// sweepSessions periodically evicts sessions past their TTL. Without it, a
|
||||
// signed-in session whose ID is never presented again would linger forever
|
||||
// (Session_Get only evicts on a lookup of that same ID).
|
||||
func (a *API) sweepSessions() {
|
||||
for range time.Tick(sessionSweepEvery) {
|
||||
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
|
||||
for id, s := range a.sessions {
|
||||
if timeSince(s.LastSeenAt) > sessionTTLSecs {
|
||||
delete(a.sessions, id)
|
||||
}
|
||||
}
|
||||
a.sessionsMu.Unlock()
|
||||
}
|
||||
s.SignedIn = true
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
p.Version = idgen.NextID(0)
|
||||
p.WGPubKey = []byte{}
|
||||
p.SignPubKey = []byte{}
|
||||
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")
|
||||
}
|
||||
|
||||
peer.Version = idgen.NextID(0)
|
||||
peer.WGPubKey = args.WGPubKey
|
||||
peer.SignPubKey = args.SignPubKey
|
||||
|
||||
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 {
|
||||
return db.Peer_Delete(a.db, networkID, peerIP)
|
||||
}
|
||||
|
||||
@@ -341,7 +341,6 @@ func Network_List(
|
||||
type Peer struct {
|
||||
NetworkID int64
|
||||
PeerIP byte
|
||||
Version int64
|
||||
APIKey string
|
||||
Name string
|
||||
Addr4 []byte
|
||||
@@ -352,7 +351,7 @@ type Peer struct {
|
||||
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(
|
||||
tx TX,
|
||||
@@ -363,7 +362,7 @@ func Peer_Insert(
|
||||
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
|
||||
}
|
||||
|
||||
@@ -376,7 +375,7 @@ func Peer_Update(
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@@ -404,7 +403,7 @@ func Peer_UpdateFull(
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@@ -456,8 +455,8 @@ func Peer_Get(
|
||||
err error,
|
||||
) {
|
||||
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)
|
||||
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 {
|
||||
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.APIKey, &row.Name, &row.Addr4, &row.Addr6, &row.Port, &row.Relay, &row.WGPubKey, &row.SignPubKey); err != nil {
|
||||
row = nil
|
||||
}
|
||||
return
|
||||
@@ -473,7 +472,7 @@ func Peer_GetWhere(
|
||||
) {
|
||||
row = &Peer{}
|
||||
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
|
||||
}
|
||||
return
|
||||
@@ -495,7 +494,7 @@ func Peer_Iterate(
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
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) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -12,13 +12,12 @@ TABLE networks OF Network (
|
||||
TABLE peers OF Peer (
|
||||
NetworkID int64 PK,
|
||||
PeerIP byte PK,
|
||||
Version int64,
|
||||
APIKey string NoUpdate,
|
||||
Name string,
|
||||
Addr4 []byte,
|
||||
Addr6 []byte,
|
||||
Port uint16,
|
||||
Relay bool,
|
||||
Name string NoUpdate,
|
||||
Addr4 []byte NoUpdate,
|
||||
Addr6 []byte NoUpdate,
|
||||
Port uint16 NoUpdate,
|
||||
Relay bool NoUpdate,
|
||||
WGPubKey []byte NoUpdate,
|
||||
SignPubKey []byte NoUpdate
|
||||
);
|
||||
|
||||
@@ -12,7 +12,6 @@ CREATE TABLE networks (
|
||||
CREATE TABLE peers (
|
||||
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, -- For humans.
|
||||
Addr4 BLOB NOT NULL,
|
||||
|
||||
@@ -20,10 +20,6 @@ func (app *App) handlePub(pattern string, fn handlerFunc) {
|
||||
return
|
||||
}
|
||||
|
||||
if s.SessionID != sessionID {
|
||||
app.setCookie(w, SESSION_ID_COOKIE_NAME, s.SessionID)
|
||||
}
|
||||
|
||||
if r.Method == http.MethodPost {
|
||||
r.ParseMultipartForm(64 * 1024)
|
||||
} else {
|
||||
|
||||
@@ -34,9 +34,11 @@ func (a *App) _signinSubmit(s *api.Session, w http.ResponseWriter, r *http.Reque
|
||||
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
|
||||
}
|
||||
a.setCookie(w, SESSION_ID_COOKIE_NAME, sess.SessionID)
|
||||
|
||||
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})
|
||||
}
|
||||
|
||||
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 {
|
||||
n, peer, err := a.formGetPeer(r.Form)
|
||||
if err != nil {
|
||||
@@ -378,7 +334,6 @@ func (a *App) peersList(networkID int64) (peers []m.Peer, err error) {
|
||||
copy(signKey[:], p.SignPubKey)
|
||||
peers = append(peers, m.Peer{
|
||||
PeerIP: p.PeerIP,
|
||||
Version: p.Version,
|
||||
Name: p.Name,
|
||||
Addr4: addrFromBytes(p.Addr4),
|
||||
Addr6: addrFromBytes(p.Addr6),
|
||||
|
||||
@@ -22,8 +22,6 @@ func (a *App) registerRoutes() {
|
||||
a.handleSignedIn("GET /admin/peer/create/", a._adminPeerCreate)
|
||||
a.handleSignedIn("POST /admin/peer/create/", a._adminPeerCreateSubmit)
|
||||
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("POST /admin/peer/delete/", a._adminPeerDeleteSubmit)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{{with .Peer -}}
|
||||
<form method="POST">
|
||||
<input type="hidden" name="NetworkID" value="{{.NetworkID}}">
|
||||
<input type="hidden" name="NetworkID" value="{{.PeerIP}}">
|
||||
<input type="hidden" name="PeerIP" value="{{.PeerIP}}">
|
||||
<p>
|
||||
<button type="submit">Delete</button>
|
||||
<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" -}}
|
||||
<h3>{{.Peer.Name}}</h3>
|
||||
<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>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ type PeerInitResp struct {
|
||||
// non-public peer (it is the peer's own bind/beacon port).
|
||||
type Peer struct {
|
||||
PeerIP byte
|
||||
Version int64
|
||||
Name string
|
||||
Addr4 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).
|
||||
type peerJSON struct {
|
||||
PeerIP byte
|
||||
Version int64
|
||||
Name string
|
||||
Addr4 netip.Addr
|
||||
Addr6 netip.Addr
|
||||
@@ -87,7 +85,6 @@ type peerJSON struct {
|
||||
func (p Peer) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(peerJSON{
|
||||
PeerIP: p.PeerIP,
|
||||
Version: p.Version,
|
||||
Name: p.Name,
|
||||
Addr4: p.Addr4,
|
||||
Addr6: p.Addr6,
|
||||
@@ -120,7 +117,6 @@ func (p *Peer) UnmarshalJSON(data []byte) error {
|
||||
}
|
||||
*p = Peer{
|
||||
PeerIP: j.PeerIP,
|
||||
Version: j.Version,
|
||||
Name: j.Name,
|
||||
Addr4: j.Addr4,
|
||||
Addr6: j.Addr6,
|
||||
|
||||
@@ -24,7 +24,7 @@ type HubPoller struct {
|
||||
statePath string // where the network state cache is persisted
|
||||
addCh chan<- m.Peer
|
||||
removeCh chan<- wgtypes.Key
|
||||
known map[wgtypes.Key]int64 // pubKey → last seen version
|
||||
known map[wgtypes.Key]struct{} // pubKeys currently configured
|
||||
}
|
||||
|
||||
func NewHubPoller(
|
||||
@@ -49,15 +49,15 @@ func NewHubPoller(
|
||||
statePath: statePath,
|
||||
addCh: addCh,
|
||||
removeCh: removeCh,
|
||||
known: make(map[wgtypes.Key]int64),
|
||||
known: make(map[wgtypes.Key]struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (hp *HubPoller) Run() {
|
||||
// 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.
|
||||
// known starts empty, so this emits every cached peer as an add and seeds
|
||||
// the version map; the first real poll then emits only deltas.
|
||||
// known starts empty, so this emits every cached peer as an add; the first
|
||||
// real poll then emits only deltas (adds for new peers, removes for gone).
|
||||
if state, err := loadNetworkState(hp.statePath); err == nil {
|
||||
hp.apply(state)
|
||||
}
|
||||
@@ -110,9 +110,10 @@ func (hp *HubPoller) poll() {
|
||||
}
|
||||
}
|
||||
|
||||
// apply diffs state against the known versions, emitting add events for new or
|
||||
// changed peers and remove events for peers that disappeared. It returns true
|
||||
// if anything changed.
|
||||
// apply diffs state against the set of known peers, emitting an add for each
|
||||
// newly-seen peer and a remove for each that disappeared. It returns true if
|
||||
// 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) {
|
||||
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{}{}
|
||||
|
||||
if v, ok := hp.known[p.WGPubKey]; ok && v == p.Version {
|
||||
if _, ok := hp.known[p.WGPubKey]; ok {
|
||||
continue
|
||||
}
|
||||
hp.known[p.WGPubKey] = p.Version
|
||||
hp.known[p.WGPubKey] = struct{}{}
|
||||
hp.addCh <- p
|
||||
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"),
|
||||
addCh: addCh,
|
||||
removeCh: removeCh,
|
||||
known: make(map[wgtypes.Key]int64),
|
||||
known: make(map[wgtypes.Key]struct{}),
|
||||
}
|
||||
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{{
|
||||
PeerIP: peerIP,
|
||||
Version: version,
|
||||
WGPubKey: key,
|
||||
}}}
|
||||
}
|
||||
@@ -35,7 +34,7 @@ func TestApply_EmitsAddsAndReportsChange(t *testing.T) {
|
||||
hp, addCh, _ := testPoller(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")
|
||||
}
|
||||
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)
|
||||
key := mustKey(t)
|
||||
|
||||
hp.apply(stateWith(key, 2, 1))
|
||||
hp.apply(stateWith(key, 2))
|
||||
<-addCh // drain initial add
|
||||
|
||||
if changed := hp.apply(stateWith(key, 2, 1)); changed {
|
||||
t.Fatal("expected changed=false when version unchanged")
|
||||
if changed := hp.apply(stateWith(key, 2)); changed {
|
||||
t.Fatal("expected changed=false when peer already known")
|
||||
}
|
||||
if len(addCh) != 0 {
|
||||
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) {
|
||||
hp, addCh, removeCh := testPoller(t)
|
||||
key := mustKey(t)
|
||||
|
||||
hp.apply(stateWith(key, 2, 1))
|
||||
hp.apply(stateWith(key, 2))
|
||||
<-addCh
|
||||
|
||||
// Empty state: the peer is gone.
|
||||
|
||||
@@ -18,7 +18,6 @@ func TestNetworkState_RoundTrip(t *testing.T) {
|
||||
state := m.NetworkState{Peers: []m.Peer{
|
||||
{
|
||||
PeerIP: 1,
|
||||
Version: 7,
|
||||
Name: "hub",
|
||||
Addr4: netip.MustParseAddr("10.11.12.1"),
|
||||
Port: 51820,
|
||||
@@ -28,7 +27,6 @@ func TestNetworkState_RoundTrip(t *testing.T) {
|
||||
},
|
||||
{
|
||||
PeerIP: 10,
|
||||
Version: 3,
|
||||
Name: "laptop",
|
||||
Addr4: netip.MustParseAddr("10.11.12.10"),
|
||||
Port: 51820,
|
||||
|
||||
@@ -25,8 +25,8 @@ func (a *App) onAddPeer(p m.Peer) {
|
||||
Name: p.Name,
|
||||
IsRelay: p.Relay,
|
||||
IsPublic: p.IsPublic(),
|
||||
Endpoint4: p.Endpoint4(),
|
||||
Endpoint6: p.Endpoint6(),
|
||||
EndpointV4: p.Endpoint4(),
|
||||
EndpointV6: p.Endpoint6(),
|
||||
RTT: time.Duration(math.MaxInt64) * time.Nanosecond,
|
||||
Role: roleFor(a.isPublic, a.vpnIP, p.IsPublic(), vpnIP),
|
||||
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])
|
||||
}
|
||||
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")
|
||||
}
|
||||
},
|
||||
|
||||
@@ -24,8 +24,8 @@ type Peer struct {
|
||||
Name string // Human-readable DNS label.
|
||||
IsRelay bool // Peer is a relay.
|
||||
IsPublic bool // Peer has a public IP.
|
||||
Endpoint4 netip.AddrPort // Reported IPv4 endpoint.
|
||||
Endpoint6 netip.AddrPort // Reported IPv6 endpoint.
|
||||
EndpointV4 netip.AddrPort // Reported IPv4 endpoint.
|
||||
EndpointV6 netip.AddrPort // Reported IPv6 endpoint.
|
||||
RTT time.Duration // Round-trip time.
|
||||
State PeerState // Current routing state; updated on each devXxx call.
|
||||
Role control.Role // Client initiates pings; server responds.
|
||||
@@ -62,14 +62,14 @@ func (p *Peer) CanRelay() bool {
|
||||
}
|
||||
|
||||
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) {
|
||||
if v4.IsValid() {
|
||||
p.Endpoint4 = v4
|
||||
p.EndpointV4 = v4
|
||||
}
|
||||
if v6.IsValid() {
|
||||
p.Endpoint6 = v6
|
||||
p.EndpointV6 = v6
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user