Skip to content

Commit 6d1a6dd

Browse files
feat(iroh-net)!: Implement http proxy support (#2298)
Headers are based on how `curl` and env variables on`reqwest`. Setting any of - `HTTP_PROXY` - `http_proxy` - `HTTPS_PROXY` - `https_proxy` will make all relay code use these to proxy outgoing connections. Closes #2295 ## Breaking Changes - Added `iroh_net::endpoint::Builder::proxy_url` - Added `iroh_net::endpoint::Builder::proxy_from_env` - Added `iroh_net::relay::http::ClientError::Proxy` enum variant ## TODOs - [x] config & parsing env variables - [x] the todos in the code - [x] https proxy - [x] testing: tested manually on two machines using `squid`
1 parent b412927 commit 6d1a6dd

File tree

15 files changed

+1059
-314
lines changed

15 files changed

+1059
-314
lines changed

Cargo.lock

Lines changed: 378 additions & 251 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

iroh-cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ postcard = "1.0.8"
4848
quic-rpc = { version = "0.9.0", features = ["flume-transport", "quinn-transport"] }
4949
rand = "0.8.5"
5050
ratatui = "0.26.2"
51-
reqwest = { version = "0.11.19", default-features = false, features = ["json", "rustls-tls"] }
51+
reqwest = { version = "0.12.4", default-features = false, features = ["json", "rustls-tls"] }
5252
rustyline = "12.0.0"
5353
serde = { version = "1.0.197", features = ["derive"] }
5454
serde_with = "3.7.0"

iroh-metrics/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ hyper = { version = "1", features = ["server", "http1"] }
2222
hyper-util = { version = "0.1.1", features = ["tokio"] }
2323
once_cell = "1.17.0"
2424
prometheus-client = { version = "0.22.0", optional = true }
25-
reqwest = { version = "0.11.19", default-features = false, features = ["json", "rustls-tls"] }
25+
reqwest = { version = "0.12.4", default-features = false, features = ["json", "rustls-tls"] }
2626
serde = { version = "1.0", features = ["derive"] }
2727
struct_iterable = "0.1"
2828
time = { version = "0.3.21", features = ["serde-well-known"] }

iroh-net/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ workspace = true
1919
axum = { version = "0.7.4", optional = true }
2020
aead = { version = "0.5.2", features = ["bytes"] }
2121
anyhow = { version = "1" }
22+
base64 = "0.22.1"
2223
backoff = "0.4.0"
2324
bytes = "1"
2425
netdev = "0.25"
@@ -54,7 +55,7 @@ quinn-udp = { package = "iroh-quinn-udp", version = "0.4" }
5455
rand = "0.8"
5556
rand_core = "0.6.4"
5657
rcgen = "0.11"
57-
reqwest = { version = "0.11.19", default-features = false, features = ["rustls-tls"] }
58+
reqwest = { version = "0.12.4", default-features = false, features = ["rustls-tls"] }
5859
ring = "0.17"
5960
rustls = { version = "0.21.11", default-features = false, features = ["dangerous_configuration"] }
6061
serde = { version = "1", features = ["derive", "rc"] }

iroh-net/src/endpoint.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use derive_more::Debug;
1616
use futures_lite::{Stream, StreamExt};
1717
use tokio_util::sync::{CancellationToken, WaitForCancellationFuture};
1818
use tracing::{debug, info_span, trace, warn};
19+
use url::Url;
1920

2021
use crate::{
2122
config,
@@ -58,6 +59,7 @@ pub struct Builder {
5859
concurrent_connections: Option<u32>,
5960
keylog: bool,
6061
discovery: Option<Box<dyn Discovery>>,
62+
proxy_url: Option<Url>,
6163
/// Path for known peers. See [`Builder::peers_data_path`].
6264
peers_path: Option<PathBuf>,
6365
dns_resolver: Option<DnsResolver>,
@@ -75,6 +77,7 @@ impl Default for Builder {
7577
concurrent_connections: Default::default(),
7678
keylog: Default::default(),
7779
discovery: Default::default(),
80+
proxy_url: None,
7881
peers_path: None,
7982
dns_resolver: None,
8083
#[cfg(any(test, feature = "test-utils"))]
@@ -100,6 +103,23 @@ impl Builder {
100103
self
101104
}
102105

106+
/// Set an explicit proxy url to proxy all HTTP(S) traffic through.
107+
pub fn proxy_url(mut self, url: Url) -> Self {
108+
self.proxy_url.replace(url);
109+
self
110+
}
111+
112+
/// Set the proxy url from the environment, in this order:
113+
///
114+
/// - `HTTP_PROXY`
115+
/// - `http_proxy`
116+
/// - `HTTPS_PROXY`
117+
/// - `https_proxy`
118+
pub fn proxy_from_env(mut self) -> Self {
119+
self.proxy_url = proxy_url_from_env();
120+
self
121+
}
122+
103123
/// If *keylog* is `true` and the KEYLOGFILE environment variable is present it will be
104124
/// considered a filename to which the TLS pre-master keys are logged. This can be useful
105125
/// to be able to decrypt captured traffic for debugging purposes.
@@ -225,6 +245,7 @@ impl Builder {
225245
relay_map,
226246
nodes_path: self.peers_path,
227247
discovery: self.discovery,
248+
proxy_url: self.proxy_url,
228249
dns_resolver,
229250
#[cfg(any(test, feature = "test-utils"))]
230251
insecure_skip_relay_cert_verify: self.insecure_skip_relay_cert_verify,
@@ -831,6 +852,53 @@ fn try_send_rtt_msg(conn: &quinn::Connection, magic_ep: &Endpoint) {
831852
}
832853
}
833854

855+
/// Read a proxy url from the environemnt, in this order
856+
///
857+
/// - `HTTP_PROXY`
858+
/// - `http_proxy`
859+
/// - `HTTPS_PROXY`
860+
/// - `https_proxy`
861+
fn proxy_url_from_env() -> Option<Url> {
862+
if let Some(url) = std::env::var("HTTP_PROXY")
863+
.ok()
864+
.and_then(|s| s.parse::<Url>().ok())
865+
{
866+
if is_cgi() {
867+
warn!("HTTP_PROXY environment variable ignored in CGI");
868+
} else {
869+
return Some(url);
870+
}
871+
}
872+
if let Some(url) = std::env::var("http_proxy")
873+
.ok()
874+
.and_then(|s| s.parse::<Url>().ok())
875+
{
876+
return Some(url);
877+
}
878+
if let Some(url) = std::env::var("HTTPS_PROXY")
879+
.ok()
880+
.and_then(|s| s.parse::<Url>().ok())
881+
{
882+
return Some(url);
883+
}
884+
if let Some(url) = std::env::var("https_proxy")
885+
.ok()
886+
.and_then(|s| s.parse::<Url>().ok())
887+
{
888+
return Some(url);
889+
}
890+
891+
None
892+
}
893+
894+
/// Check if we are being executed in a CGI context.
895+
///
896+
/// If so, a malicious client can send the `Proxy:` header, and it will
897+
/// be in the `HTTP_PROXY` env var. So we don't use it :)
898+
fn is_cgi() -> bool {
899+
std::env::var_os("REQUEST_METHOD").is_some()
900+
}
901+
834902
// TODO: These tests could still be flaky, lets fix that:
835903
// https://github.com/n0-computer/iroh/issues/1183
836904
#[cfg(test)]

iroh-net/src/magicsock.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ use tokio_util::sync::CancellationToken;
4646
use tracing::{
4747
debug, error, error_span, info, info_span, instrument, trace, trace_span, warn, Instrument,
4848
};
49+
use url::Url;
4950
use watchable::Watchable;
5051

5152
use crate::{
@@ -117,6 +118,9 @@ pub(super) struct Options {
117118
/// configuration.
118119
pub dns_resolver: DnsResolver,
119120

121+
/// Proxy configuration.
122+
pub proxy_url: Option<Url>,
123+
120124
/// Skip verification of SSL certificates from relay servers
121125
///
122126
/// May only be used in tests.
@@ -132,6 +136,7 @@ impl Default for Options {
132136
relay_map: RelayMap::empty(),
133137
nodes_path: None,
134138
discovery: None,
139+
proxy_url: None,
135140
dns_resolver: crate::dns::default_resolver().clone(),
136141
#[cfg(any(test, feature = "test-utils"))]
137142
insecure_skip_relay_cert_verify: false,
@@ -170,6 +175,9 @@ pub(super) struct MagicSock {
170175
relay_actor_sender: mpsc::Sender<RelayActorMessage>,
171176
/// String representation of the node_id of this node.
172177
me: String,
178+
/// Proxy
179+
proxy_url: Option<Url>,
180+
173181
/// Used for receiving relay messages.
174182
relay_recv_receiver: flume::Receiver<RelayRecvResult>,
175183
/// Stores wakers, to be called when relay_recv_ch receives new data.
@@ -249,6 +257,11 @@ impl MagicSock {
249257
self.my_relay.get()
250258
}
251259

260+
/// Get the current proxy configuration.
261+
pub fn proxy_url(&self) -> Option<&Url> {
262+
self.proxy_url.as_ref()
263+
}
264+
252265
/// Sets the relay node with the best latency.
253266
///
254267
/// If we are not connected to any relay nodes, set this to `None`.
@@ -1283,6 +1296,7 @@ impl Handle {
12831296
discovery,
12841297
nodes_path,
12851298
dns_resolver,
1299+
proxy_url,
12861300
#[cfg(any(test, feature = "test-utils"))]
12871301
insecure_skip_relay_cert_verify,
12881302
} = opts;
@@ -1341,6 +1355,7 @@ impl Handle {
13411355
me,
13421356
port: AtomicU16::new(port),
13431357
secret_key,
1358+
proxy_url,
13441359
local_addrs: std::sync::RwLock::new((ipv4_addr, ipv6_addr)),
13451360
closing: AtomicBool::new(false),
13461361
closed: AtomicBool::new(false),

iroh-net/src/magicsock/relay_actor.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,11 @@ impl RelayActor {
479479
let url1 = url.clone();
480480

481481
// building a client dials the relay
482-
let builder = relay::http::ClientBuilder::new(url1.clone())
482+
let mut builder = relay::http::ClientBuilder::new(url1.clone());
483+
if let Some(url) = self.msock.proxy_url() {
484+
builder = builder.proxy_url(url.clone());
485+
}
486+
let builder = builder
483487
.address_family_selector(move || {
484488
let ipv6_reported = ipv6_reported.clone();
485489
Box::pin(async move { ipv6_reported.load(Ordering::Relaxed) })

iroh-net/src/relay/client.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ use bytes::Bytes;
88
use futures_lite::StreamExt;
99
use futures_sink::Sink;
1010
use futures_util::sink::SinkExt;
11-
use tokio::io::{AsyncRead, AsyncWrite};
11+
use tokio::io::AsyncWrite;
1212
use tokio::sync::mpsc;
1313
use tokio_util::codec::{FramedRead, FramedWrite};
1414
use tracing::{debug, info_span, trace, Instrument};
1515

1616
use super::codec::PER_CLIENT_READ_QUEUE_DEPTH;
17+
use super::http::streams::{MaybeTlsStreamReader, MaybeTlsStreamWriter};
1718
use super::{
1819
codec::{
1920
write_frame, DerpCodec, Frame, MAX_PACKET_SIZE, PER_CLIENT_SEND_QUEUE_DEPTH,
@@ -63,7 +64,7 @@ impl ClientReceiver {
6364
}
6465
}
6566

66-
type RelayReader = FramedRead<Box<dyn AsyncRead + Unpin + Send + Sync + 'static>, DerpCodec>;
67+
type RelayReader = FramedRead<MaybeTlsStreamReader, DerpCodec>;
6768

6869
#[derive(derive_more::Debug)]
6970
pub struct InnerClient {
@@ -247,16 +248,16 @@ impl<W: AsyncWrite + Unpin + Send + 'static> ClientWriter<W> {
247248
pub struct ClientBuilder {
248249
secret_key: SecretKey,
249250
reader: RelayReader,
250-
writer: FramedWrite<Box<dyn AsyncWrite + Unpin + Send + Sync + 'static>, DerpCodec>,
251+
writer: FramedWrite<MaybeTlsStreamWriter, DerpCodec>,
251252
local_addr: SocketAddr,
252253
}
253254

254255
impl ClientBuilder {
255256
pub fn new(
256257
secret_key: SecretKey,
257258
local_addr: SocketAddr,
258-
reader: Box<dyn AsyncRead + Unpin + Send + Sync + 'static>,
259-
writer: Box<dyn AsyncWrite + Unpin + Send + Sync + 'static>,
259+
reader: MaybeTlsStreamReader,
260+
writer: MaybeTlsStreamWriter,
260261
) -> Self {
261262
Self {
262263
secret_key,

iroh-net/src/relay/http.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//!
44
mod client;
55
mod server;
6+
pub(crate) mod streams;
67

78
pub use self::client::{Client, ClientBuilder, ClientError, ClientReceiver};
89
pub use self::server::{Server, ServerBuilder, TlsAcceptor, TlsConfig};

0 commit comments

Comments
 (0)