Expand description
A hyper-compatible connector that routes outbound HTTP requests over the tailnet.
This is the analog of Go tsnet.Server.HTTPClient, whose entire mechanism is
&http.Client{Transport: &http.Transport{DialContext: s.Dial}} — a bare dialer injection with no
extra client defaults. TailnetConnector is that injection for the Rust hyper ecosystem:
given an http:// request Uri, it resolves the host as a MagicDNS name (or IPv4 literal) and
dials it into the overlay (default port 80), so the request egresses over the tailnet rather than
the host’s network. Redirects, pooling, and timeouts are the hyper client’s concern — the
connector only supplies the transport, exactly like Go’s DialContext.
Obtain one from Device::http_connector and hand it to
hyper_util::client::legacy::Client::builder(...).build(connector).
Available only with the hyper crate feature.
§TLS — this is a PLAINTEXT connector
TailnetConnector yields a plain overlay TCP stream and performs no TLS. Unlike Go’s
http.Transport (which wraps the DialContext conn in TLS for https:// itself), hyper’s legacy
Client does no TLS — it speaks HTTP directly over whatever stream the connector returns. So an
https:// request through a bare TailnetConnector would be sent cleartext onto port 443;
this connector therefore rejects https/wss URIs (with BadRequest) rather than dial them
into a silent plaintext-on-TLS-port failure. Traffic over the tailnet is still WireGuard-encrypted
hop-to-hop (the host’s origin IP never leaks), but there is no end-to-end TLS / peer-certificate
validation.
For real HTTPS over the tailnet, wrap this connector in a TLS connector — e.g.
hyper_rustls::HttpsConnectorBuilder::new().with_native_roots()?.https_or_http().enable_http1().wrap_connector(connector)
— which performs the TLS handshake over the tailnet stream this connector supplies.
§IPv4-only
Like the rest of this fork’s tailnet surface, the connector is IPv4-only: hosts resolve to a
tailnet IPv4 (or are dialed as an IPv4 literal). An IPv6-only destination is not reachable even
with Config::enable_ipv6, unlike Device::dial.
§Example
use hyper_util::{client::legacy::Client, rt::TokioExecutor};
let dev = Device::new(
&Config::default_with_key_file("tsrs_keys.json").await?,
Some("YOUR_AUTH_KEY".to_owned()),
).await?;
// A hyper client that dials every (http://) request over the tailnet — the analog of Go
// `tsnet.Server.HTTPClient`. (Body type `String` here just to name the generic; use whatever
// `http_body::Body` your requests carry. For https, wrap `connector` in a TLS connector first.)
let connector = dev.http_connector().await?;
let client: Client<_, String> = Client::builder(TokioExecutor::new()).build(connector);
let resp = client.get("http://my-peer:8080/".parse()?).await?;
println!("status: {}", resp.status());Structs§
- Tailnet
Connector - A
hyperconnector that dials over the tailnet (the analog of Gohttp.Transport.DialContext = tsnet.Server.Dial). Build one withDevice::http_connectorand pass it tohyper_util::client::legacy::Client::builder(...).build(connector). - Tailnet
Stream - The connection
TailnetConnectoryields: a tailnetnetstack::TcpStreamwrapped so it satisfies hyper’s IO +Connectionrequirements.TokioIoadapts the stream’s tokioAsyncRead/AsyncWriteto hyper’srt::{Read,Write}, and theConnectionimpl reports the (unremarkable) connection metadata hyper needs.