This commit is contained in:
jdl
2026-06-11 17:03:57 +02:00
parent 82a6d47bc9
commit ab2837817d
6 changed files with 68 additions and 14 deletions

View File

@@ -80,7 +80,11 @@ func (a *API) Session_Delete(sessionID string) error {
return nil
}
func (a *API) Session_Get(sessionID string) (*Session, error) {
// 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.
func (a *API) Session_Get(sessionID string) (Session, error) {
a.sessionsMu.Lock()
defer a.sessionsMu.Unlock()
@@ -91,13 +95,13 @@ func (a *API) Session_Get(sessionID string) (*Session, error) {
if timeSince(s.LastSeenAt) > 86400*7 {
s.LastSeenAt = time.Now().Unix()
}
return s, nil
return *s, nil
}
delete(a.sessions, sessionID)
}
}
return a.session_Create(), nil
return *a.session_Create(), nil
}
// caller must hold sessionsMu
@@ -111,7 +115,7 @@ func (a *API) session_Create() *Session {
return s
}
func (a *API) Session_SignIn(s *Session, pwd string) error {
func (a *API) Session_SignIn(sessionID, pwd string) error {
conf, err := a.Config_Get()
if err != nil {
return err
@@ -120,8 +124,13 @@ func (a *API) Session_SignIn(s *Session, pwd string) error {
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
a.sessionsMu.Unlock()
return nil
}

View File

@@ -30,7 +30,7 @@ func (app *App) handlePub(pattern string, fn handlerFunc) {
r.ParseForm()
}
if err := fn(s, w, r); err != nil {
if err := fn(&s, w, r); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}

View File

@@ -33,7 +33,7 @@ func (a *App) _signinSubmit(s *api.Session, w http.ResponseWriter, r *http.Reque
return err
}
if err := a.api.Session_SignIn(s, pwd); err != nil {
if err := a.api.Session_SignIn(s.SessionID, pwd); err != nil {
return err
}

View File

@@ -53,7 +53,7 @@ func (a *App) devAddDirect(p *Peer, endpoint netip.AddrPort) {
func (a *App) devSetRelay(p *Peer, endpoint netip.AddrPort) {
log.Printf("RELAY: %s - %s @ %s", p.Name, p.VPNIP.String(), endpoint.String())
devRetry(p.VPNIP, "SetRelay", func() error { return a.dev.SetRelay(p.PubKey(), endpoint, a.vpnNet) })
p.State = StateDirect
p.State = StateDirect // Dirrect connection. The app marks peer as relay.
}
func (a *App) devPromote(p *Peer) {

View File

@@ -18,6 +18,14 @@ const (
hostsEnd = "# END vppn"
)
// hostMarkers returns the begin/end marker lines that delimit the managed
// section for localDomain. The domain is wrapped in parentheses so one domain's
// marker can never be a prefix of another's (e.g. "net" vs "net2") when
// multiple vppn instances share /etc/hosts.
func hostMarkers(localDomain string) (begin, end string) {
return hostsBegin + "(" + localDomain + ")", hostsEnd + "(" + localDomain + ")"
}
// updateHosts rewrites the managed vppn section in /etc/hosts using the
// current peersByIP map. Peers without a Name are skipped.
func (a *App) updateHosts() {
@@ -36,8 +44,7 @@ func updateHosts(hostsPath, localDomain string, peers map[netip.Addr]*Peer) erro
}
defer lockFile.Close()
begin := hostsBegin + localDomain
end := hostsEnd + localDomain
begin, end := hostMarkers(localDomain)
info, err := os.Stat(hostsPath)
if err != nil {

View File

@@ -28,8 +28,7 @@ func readManagedSection(t *testing.T, path, localDomain string) (inside, outside
if err != nil {
t.Fatal(err)
}
begin := hostsBegin + localDomain
end := hostsEnd + localDomain
begin, end := hostMarkers(localDomain)
inSection := false
for _, line := range strings.Split(string(raw), "\n") {
@@ -157,6 +156,45 @@ func TestUpdateHosts_Idempotent(t *testing.T) {
}
}
// TestUpdateHosts_PrefixDomainsCoexist guards finding 4.4: two domains where
// one label is a prefix of the other ("net" vs "net2") must each manage their
// own section without clobbering the other's, even sharing one hosts file.
func TestUpdateHosts_PrefixDomainsCoexist(t *testing.T) {
path := writeTempHosts(t, "127.0.0.1 localhost\n")
if err := updateHosts(path, "net2.local", map[netip.Addr]*Peer{
netip.MustParseAddr("10.0.2.1"): peer("a"),
}); err != nil {
t.Fatal(err)
}
if err := updateHosts(path, "net.local", map[netip.Addr]*Peer{
netip.MustParseAddr("10.0.1.1"): peer("b"),
}); err != nil {
t.Fatal(err)
}
// Both sections coexist after writing the prefix domain.
if in, _ := readManagedSection(t, path, "net2.local"); len(in) != 1 || in[0] != "10.0.2.1 a.net2.local" {
t.Errorf("net2 section clobbered: %v", in)
}
if in, _ := readManagedSection(t, path, "net.local"); len(in) != 1 || in[0] != "10.0.1.1 b.net.local" {
t.Errorf("net section wrong: %v", in)
}
// Re-updating net2 must not disturb the net section.
if err := updateHosts(path, "net2.local", map[netip.Addr]*Peer{
netip.MustParseAddr("10.0.2.2"): peer("c"),
}); err != nil {
t.Fatal(err)
}
if in, _ := readManagedSection(t, path, "net.local"); len(in) != 1 || in[0] != "10.0.1.1 b.net.local" {
t.Errorf("net section disturbed by net2 update: %v", in)
}
if in, _ := readManagedSection(t, path, "net2.local"); len(in) != 1 || in[0] != "10.0.2.2 c.net2.local" {
t.Errorf("net2 section not updated: %v", in)
}
}
func contains(ss []string, s string) bool {
for _, x := range ss {
if x == s {