Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(iroh-net)!: Implement http proxy support #2298

Merged
merged 14 commits into from
May 22, 2024
630 changes: 379 additions & 251 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion iroh-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ postcard = "1.0.8"
quic-rpc = { version = "0.9.0", features = ["flume-transport", "quinn-transport"] }
rand = "0.8.5"
ratatui = "0.26.2"
reqwest = { version = "0.11.19", default-features = false, features = ["json", "rustls-tls"] }
reqwest = { version = "0.12.4", default-features = false, features = ["json", "rustls-tls"] }
rustyline = "12.0.0"
serde = { version = "1.0.197", features = ["derive"] }
serde_with = "3.7.0"
Expand Down
2 changes: 1 addition & 1 deletion iroh-metrics/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ hyper = { version = "1", features = ["server", "http1"] }
hyper-util = { version = "0.1.1", features = ["tokio"] }
once_cell = "1.17.0"
prometheus-client = { version = "0.22.0", optional = true }
reqwest = { version = "0.11.19", default-features = false, features = ["json", "rustls-tls"] }
reqwest = { version = "0.12.4", default-features = false, features = ["json", "rustls-tls"] }
serde = { version = "1.0", features = ["derive"] }
struct_iterable = "0.1"
time = { version = "0.3.21", features = ["serde-well-known"] }
Expand Down
4 changes: 3 additions & 1 deletion iroh-net/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ quinn-udp = { package = "iroh-quinn-udp", version = "0.4" }
rand = "0.8"
rand_core = "0.6.4"
rcgen = "0.11"
reqwest = { version = "0.11.19", default-features = false, features = ["rustls-tls"] }
reqwest = { version = "0.12.4", default-features = false, features = ["rustls-tls"] }
ring = "0.17"
rustls = { version = "0.21.11", default-features = false, features = ["dangerous_configuration"] }
serde = { version = "1", features = ["derive", "rc"] }
Expand Down Expand Up @@ -87,6 +87,8 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"], optional = tr
# metrics
iroh-metrics = { version = "0.16.0", path = "../iroh-metrics", default-features = false }
strum = { version = "0.26.2", features = ["derive"] }
pin-project-lite = "0.2.14"
base64 = "0.22.1"
dignifiedquire marked this conversation as resolved.
Show resolved Hide resolved

[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
netlink-packet-core = "0.7.0"
Expand Down
68 changes: 68 additions & 0 deletions iroh-net/src/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use derive_more::Debug;
use futures_lite::{Stream, StreamExt};
use tokio_util::sync::{CancellationToken, WaitForCancellationFuture};
use tracing::{debug, info_span, trace, warn};
use url::Url;

use crate::{
config,
Expand Down Expand Up @@ -58,6 +59,7 @@ pub struct Builder {
concurrent_connections: Option<u32>,
keylog: bool,
discovery: Option<Box<dyn Discovery>>,
proxy_url: Option<Url>,
/// Path for known peers. See [`Builder::peers_data_path`].
peers_path: Option<PathBuf>,
dns_resolver: Option<DnsResolver>,
Expand All @@ -75,6 +77,7 @@ impl Default for Builder {
concurrent_connections: Default::default(),
keylog: Default::default(),
discovery: Default::default(),
proxy_url: None,
peers_path: None,
dns_resolver: None,
#[cfg(any(test, feature = "test-utils"))]
Expand All @@ -100,6 +103,23 @@ impl Builder {
self
}

/// Set an explicit proxy url to proxy all HTTP(S) traffic through.
pub fn proxy_url(mut self, url: Url) -> Self {
self.proxy_url.replace(url);
self
}

/// Set the proxy url from the environment, in this order:
///
/// - `HTTP_PROXY`
/// - `http_proxy`
/// - `HTTPS_PROXY`
/// - `https_proxy`
pub fn proxy_from_env(mut self) -> Self {
self.proxy_url = proxy_url_from_env();
self
}

/// If *keylog* is `true` and the KEYLOGFILE environment variable is present it will be
/// considered a filename to which the TLS pre-master keys are logged. This can be useful
/// to be able to decrypt captured traffic for debugging purposes.
Expand Down Expand Up @@ -225,6 +245,7 @@ impl Builder {
relay_map,
nodes_path: self.peers_path,
discovery: self.discovery,
proxy_url: self.proxy_url,
dns_resolver,
#[cfg(any(test, feature = "test-utils"))]
insecure_skip_relay_cert_verify: self.insecure_skip_relay_cert_verify,
Expand Down Expand Up @@ -831,6 +852,53 @@ fn try_send_rtt_msg(conn: &quinn::Connection, magic_ep: &Endpoint) {
}
}

/// Read a proxy url from the environemnt, in this order
///
/// - `HTTP_PROXY`
/// - `http_proxy`
/// - `HTTPS_PROXY`
/// - `https_proxy`
fn proxy_url_from_env() -> Option<Url> {
if let Some(url) = std::env::var("HTTP_PROXY")
.ok()
.and_then(|s| s.parse::<Url>().ok())
{
if is_cgi() {
warn!("HTTP_PROXY environment variable ignored in CGI");
} else {
return Some(url);
}
}
if let Some(url) = std::env::var("http_proxy")
.ok()
.and_then(|s| s.parse::<Url>().ok())
{
return Some(url);
}
if let Some(url) = std::env::var("HTTPS_PROXY")
.ok()
.and_then(|s| s.parse::<Url>().ok())
{
return Some(url);
}
if let Some(url) = std::env::var("https_proxy")
.ok()
.and_then(|s| s.parse::<Url>().ok())
{
return Some(url);
}

None
}

/// Check if we are being executed in a CGI context.
///
/// If so, a malicious client can send the `Proxy:` header, and it will
/// be in the `HTTP_PROXY` env var. So we don't use it :)
fn is_cgi() -> bool {
std::env::var_os("REQUEST_METHOD").is_some()
}

// TODO: These tests could still be flaky, lets fix that:
// https://github.com/n0-computer/iroh/issues/1183
#[cfg(test)]
Expand Down
15 changes: 15 additions & 0 deletions iroh-net/src/magicsock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ use tokio_util::sync::CancellationToken;
use tracing::{
debug, error, error_span, info, info_span, instrument, trace, trace_span, warn, Instrument,
};
use url::Url;
use watchable::Watchable;

use crate::{
Expand Down Expand Up @@ -117,6 +118,9 @@ pub(super) struct Options {
/// configuration.
pub dns_resolver: DnsResolver,

/// Proxy configuration.
pub proxy_url: Option<Url>,

/// Skip verification of SSL certificates from relay servers
///
/// May only be used in tests.
Expand All @@ -132,6 +136,7 @@ impl Default for Options {
relay_map: RelayMap::empty(),
nodes_path: None,
discovery: None,
proxy_url: None,
dns_resolver: crate::dns::default_resolver().clone(),
#[cfg(any(test, feature = "test-utils"))]
insecure_skip_relay_cert_verify: false,
Expand Down Expand Up @@ -170,6 +175,9 @@ pub(super) struct MagicSock {
relay_actor_sender: mpsc::Sender<RelayActorMessage>,
/// String representation of the node_id of this node.
me: String,
/// Proxy
proxy_url: Option<Url>,

/// Used for receiving relay messages.
relay_recv_receiver: flume::Receiver<RelayRecvResult>,
/// Stores wakers, to be called when relay_recv_ch receives new data.
Expand Down Expand Up @@ -249,6 +257,11 @@ impl MagicSock {
self.my_relay.get()
}

/// Get the current proxy configuration.
pub fn proxy_url(&self) -> Option<&Url> {
self.proxy_url.as_ref()
}

/// Sets the relay node with the best latency.
///
/// If we are not connected to any relay nodes, set this to `None`.
Expand Down Expand Up @@ -1283,6 +1296,7 @@ impl Handle {
discovery,
nodes_path,
dns_resolver,
proxy_url,
#[cfg(any(test, feature = "test-utils"))]
insecure_skip_relay_cert_verify,
} = opts;
Expand Down Expand Up @@ -1341,6 +1355,7 @@ impl Handle {
me,
port: AtomicU16::new(port),
secret_key,
proxy_url,
local_addrs: std::sync::RwLock::new((ipv4_addr, ipv6_addr)),
closing: AtomicBool::new(false),
closed: AtomicBool::new(false),
Expand Down
6 changes: 5 additions & 1 deletion iroh-net/src/magicsock/relay_actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,11 @@ impl RelayActor {
let url1 = url.clone();

// building a client dials the relay
let builder = relay::http::ClientBuilder::new(url1.clone())
let mut builder = relay::http::ClientBuilder::new(url1.clone());
if let Some(url) = self.msock.proxy_url() {
builder = builder.proxy_url(url.clone());
}
let builder = builder
.address_family_selector(move || {
let ipv6_reported = ipv6_reported.clone();
Box::pin(async move { ipv6_reported.load(Ordering::Relaxed) })
Expand Down
11 changes: 6 additions & 5 deletions iroh-net/src/relay/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ use bytes::Bytes;
use futures_lite::StreamExt;
use futures_sink::Sink;
use futures_util::sink::SinkExt;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::io::AsyncWrite;
use tokio::sync::mpsc;
use tokio_util::codec::{FramedRead, FramedWrite};
use tracing::{debug, info_span, trace, Instrument};

use super::codec::PER_CLIENT_READ_QUEUE_DEPTH;
use super::http::streams::{MaybeTlsStreamReader, MaybeTlsStreamWriter};
use super::{
codec::{
write_frame, DerpCodec, Frame, MAX_PACKET_SIZE, PER_CLIENT_SEND_QUEUE_DEPTH,
Expand Down Expand Up @@ -63,7 +64,7 @@ impl ClientReceiver {
}
}

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

#[derive(derive_more::Debug)]
pub struct InnerClient {
Expand Down Expand Up @@ -247,16 +248,16 @@ impl<W: AsyncWrite + Unpin + Send + 'static> ClientWriter<W> {
pub struct ClientBuilder {
secret_key: SecretKey,
reader: RelayReader,
writer: FramedWrite<Box<dyn AsyncWrite + Unpin + Send + Sync + 'static>, DerpCodec>,
writer: FramedWrite<MaybeTlsStreamWriter, DerpCodec>,
local_addr: SocketAddr,
}

impl ClientBuilder {
pub fn new(
secret_key: SecretKey,
local_addr: SocketAddr,
reader: Box<dyn AsyncRead + Unpin + Send + Sync + 'static>,
writer: Box<dyn AsyncWrite + Unpin + Send + Sync + 'static>,
reader: MaybeTlsStreamReader,
writer: MaybeTlsStreamWriter,
) -> Self {
Self {
secret_key,
Expand Down
1 change: 1 addition & 0 deletions iroh-net/src/relay/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//!
mod client;
mod server;
pub(crate) mod streams;

pub use self::client::{Client, ClientBuilder, ClientError, ClientReceiver};
pub use self::server::{Server, ServerBuilder, TlsAcceptor, TlsConfig};
Expand Down
Loading
Loading