Skip to content

Commit 1f1aaff

Browse files
committed
WIP 11 vpn: add gateway support
1 parent 13b3f3e commit 1f1aaff

8 files changed

Lines changed: 148 additions & 160 deletions

File tree

api/peers.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -119,22 +119,21 @@ func (h *Handler) UpdatePeerSettings(c echo.Context) (err error) {
119119
return c.JSON(http.StatusBadRequest, ErrorMessage("invalid domain name"))
120120
}
121121

122-
knownPeer, exists := h.conf.GetPeer(req.PeerID)
122+
req.Alias = strings.TrimSpace(req.Alias)
123+
124+
// Read, validate and write under a single critical section so the peer can't
125+
// change between the lookup and the write, and so the uniqueness checks stay
126+
// consistent with the write.
127+
h.conf.Lock()
128+
defer h.conf.Unlock()
129+
knownPeer, exists := h.conf.GetPeerUnlocked(req.PeerID)
123130
if !exists {
124131
return c.JSON(http.StatusNotFound, ErrorMessage("peer not found"))
125132
}
126-
peerID := knownPeer.PeerId()
127-
128-
req.Alias = strings.TrimSpace(req.Alias)
129-
if !h.conf.IsUniqPeerAlias(req.PeerID, req.Alias) {
133+
if !h.conf.IsUniqPeerAliasUnlocked(req.PeerID, req.Alias) {
130134
return c.JSON(http.StatusBadRequest, ErrorMessage(ErrorPeerAliasIsNotUniq))
131135
}
132-
133-
h.conf.Lock()
134-
defer h.conf.Unlock()
135-
136-
checkIPErr := h.conf.CheckIPUnique(req.IPAddr, knownPeer.PeerID)
137-
if checkIPErr != nil {
136+
if checkIPErr := h.conf.CheckIPUnique(req.IPAddr, knownPeer.PeerID); checkIPErr != nil {
138137
return c.JSON(http.StatusBadRequest, ErrorMessage(checkIPErr.Error()))
139138
}
140139

@@ -145,6 +144,7 @@ func (h *Handler) UpdatePeerSettings(c echo.Context) (err error) {
145144

146145
h.conf.UpsertPeerUnlocked(knownPeer)
147146

147+
peerID := knownPeer.PeerId()
148148
go func() {
149149
_ = h.authStatus.ExchangeNewStatusInfo(h.ctx, peerID, knownPeer)
150150
}()

api/settings.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,17 @@ func (h *Handler) GetMyPeerInfo(c echo.Context) (err error) {
1919
totalBootstraps, connectedBootstraps := h.p2p.BootstrapPeersStats()
2020
netStats := h.p2p.NetworkStats()
2121

22+
// Snapshot the runtime-mutable scalar config fields under the read lock.
23+
// P2pNode.Name is mutated by UpdateMySettings; VPNConfig fields may change
24+
// once runtime reconfiguration lands — read them under the lock too.
25+
h.conf.RLock()
26+
p2pNode := h.conf.P2pNode
27+
vpnConfig := h.conf.VPNConfig
28+
h.conf.RUnlock()
29+
2230
peerInfo := entity.PeerInfo{
23-
PeerID: h.conf.P2pNode.PeerID,
24-
Name: h.conf.P2pNode.Name,
31+
PeerID: p2pNode.PeerID,
32+
Name: p2pNode.Name,
2533
Uptime: h.p2p.Uptime(),
2634
ServerVersion: config.Version,
2735
NetworkStats: netStats,
@@ -33,8 +41,8 @@ func (h *Handler) GetMyPeerInfo(c echo.Context) (err error) {
3341
IsAwlDNSSetAsSystem: h.dns.IsAwlDNSSetAsSystem(),
3442
VPN: entity.VPNInfo{
3543
VPNInterfaceEnabled: h.tunnel != nil,
36-
InterfaceName: h.conf.VPNConfig.InterfaceName,
37-
IPNet: h.conf.VPNConfig.IPNet,
44+
InterfaceName: vpnConfig.InterfaceName,
45+
IPNet: vpnConfig.IPNet,
3846
},
3947
SOCKS5: func() entity.SOCKS5Info {
4048
h.conf.RLock()
@@ -143,7 +151,6 @@ func (h *Handler) ListAvailableProxies(c echo.Context) (err error) {
143151

144152
response := entity.ListAvailableProxiesResponse{
145153
Proxies: proxies,
146-
//Proxies: []entity.AvailableProxy{},
147154
}
148155

149156
return c.JSON(http.StatusOK, response)

application_gateway_test.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,31 @@ package awl
22

33
import (
44
"context"
5-
"runtime"
65
"testing"
76
"time"
87

98
"github.com/anywherelan/awl/config"
109
"github.com/anywherelan/awl/entity"
10+
"github.com/anywherelan/awl/service"
1111
)
1212

1313
const (
1414
gatewayTestPacketSize = 500
1515
internetIP = "8.8.8.8"
1616
)
1717

18+
// skipIfVPNGatewayUnsupported skips tests that drive the VPN gateway runtime API
19+
// (client/server enable) or its startup wiring. The feature is implemented only
20+
// on Linux; on Windows and macOS the API returns an error and startup wiring is
21+
// a no-op, so these tests don't apply there. service.VPNGatewaySupported is the
22+
// single source of truth for platform support.
23+
func skipIfVPNGatewayUnsupported(t *testing.T) {
24+
t.Helper()
25+
if err := service.VPNGatewaySupported(); err != nil {
26+
t.Skipf("VPN gateway is only supported on Linux: %v", err)
27+
}
28+
}
29+
1830
// setupGatewayPeers creates two peers that are friends and configures them for gateway mode.
1931
// peer1 = gateway client, peer2 = exit node.
2032
// Returns the peers and peer1's assigned IP in peer2's config.
@@ -642,6 +654,7 @@ func TestGatewayAPIEnableNotAllowed(t *testing.T) {
642654
// /settings/peer_info (the canonical status surface — there is no dedicated
643655
// /gateway/status endpoint).
644656
func TestGatewayPeerInfoStatus(t *testing.T) {
657+
skipIfVPNGatewayUnsupported(t)
645658
ts := NewTestSuite(t)
646659
client, exitNode, _ := setupGatewayPeers(ts)
647660

@@ -675,6 +688,7 @@ func TestGatewayAPIListAvailableGateways(t *testing.T) {
675688
// atomic-switch case: an enable with a different exit-node ID while already
676689
// enabled must rebind the tunnel without tearing routes down and back up.
677690
func TestGatewayAPIRuntimeToggle(t *testing.T) {
691+
skipIfVPNGatewayUnsupported(t)
678692
ts := NewTestSuite(t)
679693
client, exitNode, _ := setupGatewayPeers(ts)
680694

@@ -734,6 +748,7 @@ func TestGatewayAPIRuntimeToggle(t *testing.T) {
734748
// applies and tears down the server-side state at runtime, persisting the
735749
// flag and propagating it to peers via the next status exchange.
736750
func TestGatewayAPIExitNodeMode(t *testing.T) {
751+
skipIfVPNGatewayUnsupported(t)
737752
ts := NewTestSuite(t)
738753
peer1 := ts.NewTestPeer(true)
739754
peer2 := ts.NewTestPeer(true)
@@ -1034,9 +1049,7 @@ func TestGatewayListAvailableGatewaysFiltersToVPNGatewayOnly(t *testing.T) {
10341049
// KnownPeers, OS-level apply failure, etc.) propagates from EnableClient and
10351050
// fails Init — see TestGatewayUnknownPeerIDAtStartupFailsBoot.
10361051
func TestGatewayInvalidPeerIDAtStartupClearsGracefully(t *testing.T) {
1037-
if runtime.GOOS == "windows" {
1038-
t.Skip("VPN gateway mode is gated off on Windows; setupGateway returns early")
1039-
}
1052+
skipIfVPNGatewayUnsupported(t)
10401053
ts := NewTestSuite(t)
10411054

10421055
// Malformed peer ID: not a valid multihash, so peer.Decode returns an
@@ -1066,9 +1079,7 @@ func TestGatewayInvalidPeerIDAtStartupClearsGracefully(t *testing.T) {
10661079
// SetupAtStartup wraps it, and Init fails. The persisted config is left
10671080
// untouched so a human can investigate why the peer disappeared.
10681081
func TestGatewayUnknownPeerIDAtStartupFailsBoot(t *testing.T) {
1069-
if runtime.GOOS == "windows" {
1070-
t.Skip("VPN gateway mode is gated off on Windows; setupGateway returns early")
1071-
}
1082+
skipIfVPNGatewayUnsupported(t)
10721083
ts := NewTestSuite(t)
10731084

10741085
// Valid-format peer ID that won't be in KnownPeers.

cli_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ func TestCLI_Proxy(t *testing.T) {
381381
// state. Subtests are ordered so DisableGateway runs before re-enabling, and
382382
// the exit_node toggle runs on the exitNode peer where it makes sense.
383383
func TestCLI_Gateway(t *testing.T) {
384+
skipIfVPNGatewayUnsupported(t)
384385
ts := NewTestSuite(t)
385386
client, exitNode, _ := setupGatewayPeers(ts)
386387

config/config.go

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,11 @@ func (c *Config) SaveLocked() {
171171
func (c *Config) IsUniqPeerAlias(excludePeerID, alias string) bool {
172172
c.RLock()
173173
defer c.RUnlock()
174+
return c.IsUniqPeerAliasUnlocked(excludePeerID, alias)
175+
}
176+
177+
// IsUniqPeerAliasUnlocked is IsUniqPeerAlias without locking; the caller must hold the lock.
178+
func (c *Config) IsUniqPeerAliasUnlocked(excludePeerID, alias string) bool {
174179
for _, kPeer := range c.KnownPeers {
175180
if kPeer.PeerID == excludePeerID {
176181
continue
@@ -189,6 +194,11 @@ func (c *Config) GenUniqPeerAlias(name, alias string) string {
189194
return alias
190195
}
191196

197+
// GenUniqPeerAliasUnlocked is GenUniqPeerAlias without locking; the caller must hold the lock.
198+
func (c *Config) GenUniqPeerAliasUnlocked(name, alias string) string {
199+
return c.genUniqPeerAlias(name, alias, nil)
200+
}
201+
192202
func (c *Config) KnownPeersIds() []peer.ID {
193203
c.RLock()
194204
ids := make([]peer.ID, 0, len(c.KnownPeers))
@@ -201,11 +211,25 @@ func (c *Config) KnownPeersIds() []peer.ID {
201211

202212
func (c *Config) GetPeer(peerID string) (KnownPeer, bool) {
203213
c.RLock()
204-
knownPeer, ok := c.KnownPeers[peerID]
214+
knownPeer, ok := c.GetPeerUnlocked(peerID)
205215
c.RUnlock()
206216
return knownPeer, ok
207217
}
208218

219+
// GetPeerUnlocked is GetPeer without locking; the caller must hold the lock.
220+
func (c *Config) GetPeerUnlocked(peerID string) (KnownPeer, bool) {
221+
knownPeer, ok := c.KnownPeers[peerID]
222+
return knownPeer, ok
223+
}
224+
225+
// NodeName returns this node's name under the read lock. P2pNode.Name is mutated
226+
// at runtime (UpdateMySettings), so it must not be read directly without the lock.
227+
func (c *Config) NodeName() string {
228+
c.RLock()
229+
defer c.RUnlock()
230+
return c.P2pNode.Name
231+
}
232+
209233
func (c *Config) RemovePeer(peerID string) (KnownPeer, bool) {
210234
c.Lock()
211235
knownPeer, exists := c.KnownPeers[peerID]
@@ -238,6 +262,29 @@ func (c *Config) UpsertPeerUnlocked(peer KnownPeer) {
238262
_ = c.emitter.Emit(awlevent.KnownPeerChanged{})
239263
}
240264

265+
// UpdatePeerFields atomically applies mutate to the stored KnownPeer under the
266+
// write lock and persists the change. It returns false if the peer is unknown.
267+
//
268+
// mutate must only change the fields it owns and must NOT replace the struct
269+
// wholesale, so that fields updated concurrently by other callers are not
270+
// clobbered. mutate runs while the lock is held, so it must not call other
271+
// Config methods that take the lock (use the *Unlocked variants instead).
272+
func (c *Config) UpdatePeerFields(peerID string, mutate func(*KnownPeer)) bool {
273+
c.Lock()
274+
knownPeer, ok := c.KnownPeers[peerID]
275+
if ok {
276+
mutate(&knownPeer)
277+
c.KnownPeers[peerID] = knownPeer
278+
c.save()
279+
}
280+
c.Unlock()
281+
282+
if ok {
283+
_ = c.emitter.Emit(awlevent.KnownPeerChanged{})
284+
}
285+
return ok
286+
}
287+
241288
func (c *Config) UpdatePeerLastSeen(peerID string) {
242289
c.Lock()
243290
knownPeer, ok := c.KnownPeers[peerID]

0 commit comments

Comments
 (0)