Removed peer editing, cleanup

This commit is contained in:
jdl
2026-06-12 14:49:40 +02:00
parent 4d2aa5c8c7
commit 3e7d34a92a
17 changed files with 99 additions and 203 deletions

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
);

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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),

View File

@@ -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)

View File

@@ -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>

View File

@@ -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}}

View File

@@ -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>

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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,

View File

@@ -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,

View File

@@ -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")
}
},

View File

@@ -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
}
}