Skip to content

Commit 383c81d

Browse files
committed
feat(discovery): add IVNP relay overlay
1 parent 7378ff7 commit 383c81d

23 files changed

Lines changed: 854 additions & 135 deletions

File tree

.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ IDENTITY_PATH=/portal-certs
4949
# Compose publishes this port.
5050
WIREGUARD_PORT=51820
5151

52+
# Optional embedded IVNP/I2P relay overlay. It carries relay discovery and
53+
# authenticated relay-to-relay hop streams without requiring inbound reachability.
54+
# The generated config and encrypted router state live under IDENTITY_PATH by default.
55+
IVNP_ENABLED=false
56+
# IVNP_CONFIG=/data/portal/ivnp.conf
57+
5258
# Inclusive lease port range shared by the UDP and raw TCP transports.
5359
# 0 disables both. Enabling a transport without a range does nothing; the relay
5460
# reports that at startup. Publish the same range in docker-compose.yml when set.

cmd/relay-server/config.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ func (f feature) needsAttention() bool {
5353
func evaluateFeatures(cfg relayServerConfig) []feature {
5454
return []feature{
5555
discoveryFeature(cfg),
56+
ivnpFeature(cfg),
5657
acmeFeature(cfg),
5758
ensGaslessFeature(cfg),
5859
leaseTransportFeature("udp-transport", "UDP_ENABLED", cfg.UDPEnabled, cfg),
@@ -66,6 +67,26 @@ func evaluateFeatures(cfg relayServerConfig) []feature {
6667
}
6768
}
6869

70+
func ivnpFeature(cfg relayServerConfig) feature {
71+
f := feature{Name: "ivnp-overlay"}
72+
if !cfg.IVNPEnabled {
73+
f.State, f.By = stateDisabled, "IVNP_ENABLED=false"
74+
return f
75+
}
76+
if !cfg.DiscoveryEnabled {
77+
f.State, f.By = stateBlocked, "IVNP_ENABLED=true"
78+
f.Missing = "DISCOVERY=true is required because IVNP carries the relay discovery and hop protocols"
79+
return f
80+
}
81+
f.State, f.By = stateEnabled, "IVNP_ENABLED=true"
82+
if path := strings.TrimSpace(cfg.IVNPConfigPath); path != "" {
83+
f.Detail = "config=" + path
84+
} else {
85+
f.Detail = "config=IDENTITY_PATH/ivnp.conf"
86+
}
87+
return f
88+
}
89+
6990
func frontendFeature(cfg relayServerConfig) feature {
7091
f := feature{Name: "frontend"}
7192
dir := strings.TrimSpace(cfg.FrontendDir)

cmd/relay-server/main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ type relayServerConfig struct {
4040
IdentityPath string
4141
Bootstraps string
4242
DiscoveryEnabled bool
43+
IVNPEnabled bool
44+
IVNPConfigPath string
4345
WireGuardPort int
4446
APIPort int
4547
SNIPort int
@@ -104,6 +106,8 @@ func registerRelayServerFlags(fs *flag.FlagSet, cfg *relayServerConfig) {
104106
utils.StringFlagEnv(fs, &cfg.IdentityPath, "identity-path", "./.portal-certs", "directory path for relay identity, policy state, and keyless materials", "IDENTITY_PATH")
105107
utils.StringFlagEnv(fs, &cfg.Bootstraps, "bootstraps", "", "bootstrap relay API URLs; merged with bootstrap relays when discovery is enabled", "BOOTSTRAPS")
106108
utils.BoolFlagEnv(fs, &cfg.DiscoveryEnabled, "discovery", false, "serve relay discovery endpoints and poll discovery peers", "DISCOVERY")
109+
utils.BoolFlagEnv(fs, &cfg.IVNPEnabled, "ivnp-enabled", false, "enable the embedded IVNP relay overlay", "IVNP_ENABLED")
110+
utils.StringFlagEnv(fs, &cfg.IVNPConfigPath, "ivnp-config", "", "IVNP configuration path; defaults to IDENTITY_PATH/ivnp.conf", "IVNP_CONFIG")
107111
utils.IntFlagEnv(fs, &cfg.WireGuardPort, "wireguard-port", overlay.DefaultListenPort, utils.ParsePortNumber, "public and listen UDP port for relay overlay", "WIREGUARD_PORT")
108112

109113
utils.IntFlagEnv(fs, &cfg.APIPort, "api-port", 4017, utils.ParsePortNumber, "Admin/API server port", "API_PORT")
@@ -179,6 +183,8 @@ func runServer(ctx context.Context, cfg relayServerConfig) error {
179183
IdentityPath: cfg.IdentityPath,
180184
Bootstraps: utils.SplitCSV(cfg.Bootstraps),
181185
DiscoveryEnabled: cfg.DiscoveryEnabled,
186+
IVNPEnabled: cfg.IVNPEnabled,
187+
IVNPConfigPath: cfg.IVNPConfigPath,
182188
WireGuardPort: cfg.WireGuardPort,
183189
APIPort: cfg.APIPort,
184190
SNIPort: cfg.SNIPort,

config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ base_url = "https://github.com/gosuda/portal-tunnel/releases"
77

88
[protocol]
99
tunnel = "8"
10-
discovery = "8"
10+
discovery = "9"

docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ services:
2626
PORTAL_FRONTEND_DIR: ${PORTAL_FRONTEND_DIR:-}
2727
BOOTSTRAPS: ${BOOTSTRAPS:-}
2828
DISCOVERY: ${DISCOVERY:-false}
29+
IVNP_ENABLED: ${IVNP_ENABLED:-false}
30+
IVNP_CONFIG: ${IVNP_CONFIG:-}
2931
IDENTITY_PATH: ${IDENTITY_PATH:-/portal-certs}
3032

3133
API_PORT: 4017

docs/src/routes/architecture/+page.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ UDP client
174174
- `/sdk/register` is authenticated by a SIWE challenge/response flow using the SDK identity secp256k1 key. On success, the relay issues a lease-scoped ES256K JWT access token signed by the relay identity key and used for the rest of the lease lifecycle.
175175
- Relay URLs must use `https://`.
176176
- HTTP/2 stays disabled on the admin/API TLS listener. Keyless TLS certificate sharing and `/sdk/connect` both depend on the current HTTP/1.1-only transport contract.
177-
- WireGuard, when enabled, is relay-to-relay overlay transport only. It carries multi-hop relay forwarding and overlay discovery, but it is not used for direct tenant TLS termination, public UDP ingress, or `/sdk/*` control-plane traffic.
177+
- IVNP, when enabled, is the preferred relay-to-relay overlay. It owns I2P peer reachability and internal path construction while Portal authenticates discovery descriptors and hop route tokens. WireGuard remains a direct relay fallback during migration. Neither overlay is used for direct tenant TLS termination, public UDP ingress, or tunnel-client reverse backhaul.
178178

179179
### Reverse Session Protocol
180180

@@ -291,14 +291,16 @@ Result: raw public UDP exposure with an internal QUIC datagram backhaul. UDP and
291291

292292
<Mermaid code={udpQuicDiagram} />
293293

294-
## WireGuard Overlay and Discovery
294+
## Relay Overlay and Discovery
295295

296296
- Discovery bootstraps from public HTTPS relay URLs, then expands through relay-to-relay `/discovery` polling and periodic self-announces to bootstrap relays through `/discovery/announce`.
297297
- SDK exposures consume relay discovery results to choose relays, but they do not announce themselves and do not serve `/discovery`.
298-
- Discovery descriptors are signed relay self-descriptions. They bind relay routing metadata such as `api_https_addr`, `supports_overlay`, `wireguard_public_key`, and `wireguard_port` to the relay identity. Lease access tokens remain separate and authorize tenant lease operations only.
298+
- Discovery descriptors are signed relay self-descriptions. They bind relay routing metadata such as `api_https_addr`, `supports_overlay`, `ivnp_destination`, and optional WireGuard metadata to the relay identity. Lease access tokens remain separate and authorize tenant lease operations only.
299299
- `/discovery/announce` accepts only signed relay descriptors. Loopback or localhost relay descriptors are rejected because they cannot join the public discovery mesh.
300-
- The overlay peer API is plain HTTP on the WireGuard network, not public Internet HTTP. It serves the same discovery payload shape used by public `/discovery`.
301-
- Overlay failure affects inter-relay discovery, mesh synchronization, and multi-hop relay forwarding. Direct tenant TLS routing, keyless TLS, register/renew/connect, and public UDP ingress do not depend on the WireGuard transport path.
300+
- The overlay peer API serves the same Portal-owned discovery payload as public `/discovery`. With IVNP it is carried on the relay's persistent I2P application destination; Portal does not put relay descriptors into I2P NetDB records.
301+
- IVNP discovery runs at a slower cadence than public HTTPS polling, and its latency is not recorded as public relay ingress RTT for MOLS ranking.
302+
- Authenticated hop streams prefer IVNP when both relays advertise destinations and fall back to the direct WireGuard path while migration is in progress.
303+
- Overlay failure affects inter-relay discovery and multi-hop relay forwarding. Direct tenant TLS routing, keyless TLS, register/renew/connect, and public UDP ingress do not depend on the relay overlay.
302304

303305
## Control Plane Flow
304306

@@ -361,7 +363,7 @@ The relay signs handshake digests via `/v1/sign` but never receives tenant TLS t
361363
- One canonical raw TCP reverse transport
362364
- Dedicated TCP port allocation for non-TLS services with raw TCP bridging
363365
- Raw public UDP exposure with an internal QUIC datagram backhaul
364-
- Optional WireGuard relay overlay for relay discovery, peer synchronization, and multi-hop relay forwarding
366+
- Optional IVNP relay overlay for NAT-independent relay discovery and authenticated relay-to-relay forwarding, with WireGuard direct fallback during migration
365367
- SNI-based routing with root-host fallback
366368
- End-to-end tenant TLS with relay-backed keyless signing
367369
- Traffic-triggered detect-only MITM self-probing for probable relay-side TLS termination

docs/src/routes/configuration/+page.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ A value that cannot be parsed is a startup error rather than a silent fallback:
5555
| `API_PORT` | `4017` | int | Admin/API server listen port |
5656
| `SNI_PORT` | `443` | int | TCP SNI router listen port; non-standard values are intended for local testing, while the bundled public deployment requires `443` |
5757
| `WIREGUARD_PORT` | `51820` | int | Public and listen UDP port for relay discovery overlay |
58+
| `IVNP_CONFIG` | `IDENTITY_PATH/ivnp.conf` | string | Embedded IVNP router configuration path when the IVNP overlay is enabled |
5859

5960
### Transport
6061

@@ -70,6 +71,7 @@ A value that cannot be parsed is a startup error rather than a silent fallback:
7071
| Variable | Default | Type | Description |
7172
|----------|---------|------|-------------|
7273
| `DISCOVERY` | `false` | bool | Serve relay discovery endpoints and poll discovery peers |
74+
| `IVNP_ENABLED` | `false` | bool | Carry relay discovery and authenticated relay-to-relay hop streams over embedded IVNP/I2P; requires `DISCOVERY=true` |
7375
| `BOOTSTRAPS` | `""` | string | Additional bootstrap relay API URLs used for discovery expansion (comma-separated) |
7476
| `LANDING_PAGE_ENABLED` | `false` | bool | Initial dashboard landing-page visibility; admin changes are persisted in the relay policy state |
7577

go.mod

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,21 @@ require (
3232
github.com/spruceid/siwe-go v0.2.1
3333
github.com/tyler-smith/go-bip39 v1.1.0
3434
github.com/vultr/govultr/v3 v3.30.0
35-
golang.org/x/crypto v0.53.0
36-
golang.org/x/mod v0.37.0
37-
golang.org/x/net v0.56.0
35+
golang.org/x/crypto v0.55.0
36+
golang.org/x/mod v0.38.0
37+
golang.org/x/net v0.57.0
3838
golang.org/x/oauth2 v0.36.0
39-
golang.org/x/sync v0.21.0
40-
golang.org/x/sys v0.46.0
39+
golang.org/x/sync v0.22.0
40+
golang.org/x/sys v0.47.0
4141
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
4242
google.golang.org/api v0.275.0
43+
gosuda.org/ivnp v0.0.0-20260831152821-ff6b4ad3e203
4344
)
4445

4546
require (
4647
cloud.google.com/go/auth v0.20.0 // indirect
4748
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
48-
filippo.io/edwards25519 v1.0.0-rc.1 // indirect
49+
filippo.io/edwards25519 v1.2.0 // indirect
4950
github.com/KyleBanks/depth v1.2.1 // indirect
5051
github.com/Microsoft/go-winio v0.6.2 // indirect
5152
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 // indirect
@@ -150,9 +151,9 @@ require (
150151
go.opentelemetry.io/otel/metric v1.43.0 // indirect
151152
go.opentelemetry.io/otel/trace v1.43.0 // indirect
152153
go.yaml.in/yaml/v2 v2.4.2 // indirect
153-
golang.org/x/text v0.39.0 // indirect
154+
golang.org/x/text v0.41.0 // indirect
154155
golang.org/x/time v0.15.0 // indirect
155-
golang.org/x/tools v0.47.0 // indirect
156+
golang.org/x/tools v0.48.0 // indirect
156157
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
157158
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
158159
google.golang.org/grpc v1.80.0 // indirect

go.sum

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIi
44
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
55
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
66
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
7-
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
8-
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
7+
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
8+
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
99
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
1010
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
1111
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
@@ -452,22 +452,22 @@ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnf
452452
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
453453
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
454454
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
455-
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
456-
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
455+
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
456+
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
457457
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
458458
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
459-
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
460-
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
459+
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
460+
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
461461
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
462462
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
463463
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
464-
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
465-
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
464+
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
465+
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
466466
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
467467
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
468468
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
469-
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
470-
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
469+
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
470+
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
471471
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
472472
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
473473
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -481,19 +481,19 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
481481
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
482482
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
483483
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
484-
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
485-
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
484+
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
485+
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
486486
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
487487
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
488488
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
489489
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
490-
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
491-
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
490+
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
491+
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
492492
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
493493
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
494494
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
495-
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
496-
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
495+
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
496+
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
497497
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
498498
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
499499
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
@@ -529,5 +529,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
529529
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
530530
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
531531
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
532+
gosuda.org/ivnp v0.0.0-20260831152821-ff6b4ad3e203 h1:d1GZREjq3rxUEiEMTJdnUonV5iX2REoFgvS17ZKBw6g=
533+
gosuda.org/ivnp v0.0.0-20260831152821-ff6b4ad3e203/go.mod h1:0sh2RIj/K0RuIoDLYRuCHvpzKUzkCc8W3jj5gJKJPdU=
532534
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgIMBW8ZZhT4gLnUyDIhzI=
533535
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=

portal/api_server.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ func (s *Server) handleHop(w http.ResponseWriter, r *http.Request) {
433433
utils.WriteAPIError(w, http.StatusTooManyRequests, types.APIErrorCodeRateLimited, "hop route rate limit exceeded")
434434
return
435435
}
436-
if s.overlay == nil || s.relaySet == nil {
436+
if (s.overlay == nil && s.ivnpOverlay == nil) || s.relaySet == nil {
437437
utils.WriteAPIError(w, http.StatusServiceUnavailable, types.APIErrorCodeFeatureUnavailable, errFeatureUnavailable.Error())
438438
return
439439
}
@@ -480,17 +480,19 @@ func (s *Server) handleHop(w http.ResponseWriter, r *http.Request) {
480480
return
481481
}
482482
if !forwardRelay.HasOverlayPeer() {
483-
utils.InvalidRequestError(errors.New("forward relay wireguard overlay metadata is required")).Write(w)
483+
utils.InvalidRequestError(errors.New("forward relay overlay metadata is required")).Write(w)
484484
return
485485
}
486486
route.ForwardRelay = forwardRelay
487487
if err := s.relaySet.InsertCandidate(forwardRelay, now); err != nil {
488488
utils.InvalidRequestError(fmt.Errorf("forward relay: %w", err)).Write(w)
489489
return
490490
}
491-
if err := s.overlay.Sync(s.relaySet.OverlayPeerDescriptor()); err != nil {
492-
utils.WriteAPIError(w, http.StatusInternalServerError, types.APIErrorCodeInternal, err.Error())
493-
return
491+
if s.overlay != nil {
492+
if err := s.overlay.Sync(s.relaySet.OverlayPeerDescriptor()); err != nil {
493+
utils.WriteAPIError(w, http.StatusInternalServerError, types.APIErrorCodeInternal, err.Error())
494+
return
495+
}
494496
}
495497
record, err := s.registry.RegisterHopRoute(&route, now)
496498
if err != nil {

0 commit comments

Comments
 (0)