Skip to content

fix(dgw): connect to ngrok through its current agent endpoint - #2176

Merged
Benoît Cortier (CBenoit) merged 2 commits into
masterfrom
claude/sweet-hofstadter-d16852
Oct 28, 2026
Merged

Benoît Cortier (CBenoit) merged 2 commits into
masterfrom
claude/sweet-hofstadter-d16852

Conversation

@CBenoit

Copy link
Copy Markdown
Member

Gateway now connects to ngrok through connect.ngrok-agent.com, the endpoint ngrok documents for firewall allowlists, instead of the legacy tunnel.ngrok.com. Networks that only allow the documented hostname no longer block the tunnel. ServerAddr can still point to another endpoint, such as a regional one.

Gateway now connects to ngrok through `connect.ngrok-agent.com`, the
endpoint ngrok documents for firewall allowlists, instead of the legacy
`tunnel.ngrok.com`. Networks that only allow the documented hostname no
longer block the tunnel. `ServerAddr` can still point to another
endpoint, such as a regional one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@CBenoit

Copy link
Copy Markdown
Member Author

Implementation notes.
This supersedes the draft #1395: the ngrok-rust changes it waited on (ngrok/ngrok-rust#176 and ngrok/ngrok-rust#178) shipped before 0.19.

API changes (ngrok 0.13.1 → 0.19.0)

  • Builder methods take &mut self, so the session and tunnel builders are now configured in place.
  • SessionBuilder::server_addr returns a Result. An invalid Ngrok.ServerAddr fails startup with invalid Ngrok.ServerAddr, expected host[:port].
  • The SDK parses ServerAddr as http://{value}, so https://host becomes the host https and only fails at name resolution. The Gateway now rejects values containing :// with invalid Ngrok.ServerAddr <value>, expected host[:port] without a URL scheme. The README previously called this field a URL, but 0.13 rejected URLs too.
  • SDK quirk: host:80 is read as port 443, because url drops the default port of the http scheme. 0.13 honored port 80.
  • heartbeat_interval and heartbeat_tolerance return a Result when the value overflows i64 nanoseconds. Such values now fail startup with invalid Ngrok.HeartbeatInterval or invalid Ngrok.HeartbeatTolerance.
  • UrlTunnel is replaced by EndpointInfo for url(), and remote_addr() now comes from the ConnInfo trait.
  • tcp_endpoint, http_endpoint, forwards_to with TunnelBuilder::listen, RpcError::Response and ErrResp::error_code are unchanged. None of the APIs used are deprecated; in 0.19 only the tunnel_ext helpers are.
  • Plan probe in configure_endpoint: the 0.19 sources still send CIDRs as the bind's ip_restriction, still decode RPC errors into RpcError::Response with ERR_NGROK_xxxx parsed into error_code the same way, and still close a dropped tunnel through close_tunnel. The probe behaves as before.

Crypto backend

  • default-features = false, features = ["ring"]. With the default aws-lc-rs feature, the CI job that runs ci/check-crate-is-not-in-the-tree.ps1 against devolutions-gateway would fail. The Gateway tree has no aws-lc-rs for x86_64-pc-windows-msvc or x86_64-unknown-linux-gnu.
  • ngrok builds its rustls::ClientConfig from the process-default provider, which tls::install_default_crypto_provider sets to ring at startup.
  • axum 0.7 stays out either way, because it sits behind the non-default axum feature. axum-core 0.4 is a non-optional dependency of ngrok.

Dependency tree (devolutions-gateway, all targets)

  • Removed: rustls 0.20.9, ring 0.16.20, webpki 0.22.4, sct 0.7.1, async-rustls 0.3.0, rustls-pemfile 1.0.4, base64 0.13.1, untrusted 0.7.1, spin 0.5.2. The Gateway now builds a single rustls and a single ring.
  • Added, all non-optional in ngrok 0.19: futures-rustls 0.26.0, hyper-http-proxy 1.2.0, tokio-socks 0.5.3, proxy-protocol 0.5.0 (with snafu 0.6.10 and doc-comment 0.3.4), axum-core 0.4.5, and rustls-native-certs 0.7.3 (with openssl-probe 0.1.6 and security-framework 2.11.1 outside Windows).
  • Upgraded: muxado 0.4.0 → 0.5.4.
  • Crate count goes from 632 to 635. Crates with several versions go from 85 to 84: ring, rustls, untrusted, spin and base64 lose their duplicates, while axum-core, rustls-native-certs, openssl-probe and security-framework gain one.
  • Licenses of the new crates: MIT, Apache-2.0 or ISC.
  • cargo audit and cargo deny were not run.

Testing

  • cargo +nightly fmt --all and cargo clippy --workspace --tests -- -D warnings: clean.
  • cargo test --workspace: passes. Under load, one timing-sensitive agent test (slowly_reading_child_is_not_killed) and 17 jetsocat and agent-tunnel integration tests failed once. All of them pass on rerun, and none depends on ngrok.
  • Live run against ngrok with a paid account, Gateway listeners on loopback, one HTTP tunnel:
    • Default endpoint: the session connects and the tunnel binds. With the DNS cache cleared first, connect.ngrok-agent.com is the only ngrok name resolved, and the agent socket connects to one of its addresses. The same run on master (0.13.1) resolves only tunnel.ngrok.com.
    • ServerAddr: connect.eu.ngrok-agent.com:443: the session and tunnel come up, only that name is resolved, and the peer is one of its EU addresses.
    • ServerAddr: https://connect.ngrok-agent.com and connect.ngrok-agent.com:notaport: startup fails with the errors above.
    • AllowCidrs set: the probe takes the paid-plan path (no ERR_NGROK_9017) and the tunnel starts with the rule. The free-plan path was not exercised.
    • /jet/health through the tunnel was not verified end to end. The ngrok edge answers 403 ERR_NGROK_3205 ("Request blocked by Endpoints IP Restriction") even with no CIDRs configured, over IPv4 and IPv6. Master (0.13.1) with the same account gets the same response, so the block comes from the account's IP policy, not from this change. Locally, /jet/health returns 200.

Follow-up (out of scope)

  • Route the ngrok connection through the Gateway's Proxy setting. 0.19 adds SessionBuilder::proxy_url, which accepts http, https and socks5 URLs.

Note

LLM-assisted content (no human feedback).

…ateway into claude/sweet-hofstadter-d16852

# Conflicts:
#	Cargo.lock
@CBenoit
Benoît Cortier (CBenoit) merged commit c8ebb9f into master Oct 28, 2026
53 checks passed
@CBenoit
Benoît Cortier (CBenoit) deleted the claude/sweet-hofstadter-d16852 branch October 28, 2026 17:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant