2023-10-01 15:16:23 +00:00
|
|
|
use anyhow::{anyhow, Context};
|
|
|
|
use std::{io, vec};
|
|
|
|
|
2023-12-19 21:41:11 +00:00
|
|
|
use crate::dns::DnsResolver;
|
2023-10-21 10:04:10 +00:00
|
|
|
use base64::Engine;
|
2023-10-27 07:15:15 +00:00
|
|
|
use bytes::BytesMut;
|
2023-11-26 14:47:49 +00:00
|
|
|
use log::warn;
|
2023-10-01 15:16:23 +00:00
|
|
|
use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
|
|
|
|
use std::time::Duration;
|
2023-10-21 10:04:10 +00:00
|
|
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
2023-10-01 15:16:23 +00:00
|
|
|
use tokio::net::{TcpListener, TcpSocket, TcpStream};
|
|
|
|
use tokio::time::timeout;
|
|
|
|
use tokio_stream::wrappers::TcpListenerStream;
|
|
|
|
use tracing::debug;
|
|
|
|
use tracing::log::info;
|
2023-10-21 10:04:10 +00:00
|
|
|
use url::{Host, Url};
|
2023-10-01 15:16:23 +00:00
|
|
|
|
2023-12-04 17:21:55 +00:00
|
|
|
fn configure_socket(socket: &mut TcpSocket, so_mark: &Option<u32>) -> Result<(), anyhow::Error> {
|
2023-10-30 07:13:38 +00:00
|
|
|
socket
|
|
|
|
.set_nodelay(true)
|
|
|
|
.with_context(|| format!("cannot set no_delay on socket: {}", io::Error::last_os_error()))?;
|
2023-10-01 15:16:23 +00:00
|
|
|
|
2023-10-15 18:27:23 +00:00
|
|
|
#[cfg(target_os = "linux")]
|
2023-10-01 15:16:23 +00:00
|
|
|
if let Some(so_mark) = so_mark {
|
2023-12-04 17:21:55 +00:00
|
|
|
use std::os::fd::AsFd;
|
2023-10-01 15:16:23 +00:00
|
|
|
|
2023-12-04 17:21:55 +00:00
|
|
|
let ret = nix::sys::socket::setsockopt(&socket.as_fd(), nix::sys::socket::sockopt::Mark, so_mark);
|
|
|
|
if let Err(err) = ret {
|
|
|
|
return Err(anyhow!(
|
|
|
|
"Cannot set SO_MARK on the connection {:?} {:?}",
|
|
|
|
err,
|
|
|
|
io::Error::last_os_error()
|
|
|
|
));
|
2023-10-01 15:16:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2023-10-21 10:04:10 +00:00
|
|
|
|
2023-10-01 15:16:23 +00:00
|
|
|
pub async fn connect(
|
|
|
|
host: &Host<String>,
|
|
|
|
port: u16,
|
2023-12-04 17:21:55 +00:00
|
|
|
so_mark: Option<u32>,
|
2023-10-01 15:16:23 +00:00
|
|
|
connect_timeout: Duration,
|
2023-12-19 21:41:11 +00:00
|
|
|
dns_resolver: &DnsResolver,
|
2023-10-01 15:16:23 +00:00
|
|
|
) -> Result<TcpStream, anyhow::Error> {
|
|
|
|
info!("Opening TCP connection to {}:{}", host, port);
|
|
|
|
|
|
|
|
let socket_addrs: Vec<SocketAddr> = match host {
|
2023-12-19 21:41:11 +00:00
|
|
|
Host::Domain(domain) => dns_resolver
|
|
|
|
.lookup_host(domain.as_str(), port)
|
2023-10-01 15:16:23 +00:00
|
|
|
.await
|
2023-12-19 21:41:11 +00:00
|
|
|
.with_context(|| format!("cannot resolve domain: {}", domain))?,
|
2023-10-01 15:16:23 +00:00
|
|
|
Host::Ipv4(ip) => vec![SocketAddr::V4(SocketAddrV4::new(*ip, port))],
|
|
|
|
Host::Ipv6(ip) => vec![SocketAddr::V6(SocketAddrV6::new(*ip, port, 0, 0))],
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut cnx = None;
|
|
|
|
let mut last_err = None;
|
|
|
|
for addr in socket_addrs {
|
|
|
|
debug!("connecting to {}", addr);
|
|
|
|
|
|
|
|
let mut socket = match &addr {
|
|
|
|
SocketAddr::V4(_) => TcpSocket::new_v4()?,
|
|
|
|
SocketAddr::V6(_) => TcpSocket::new_v6()?,
|
|
|
|
};
|
|
|
|
|
2023-11-26 17:22:28 +00:00
|
|
|
configure_socket(&mut socket, &so_mark)?;
|
2023-10-01 15:16:23 +00:00
|
|
|
match timeout(connect_timeout, socket.connect(addr)).await {
|
|
|
|
Ok(Ok(stream)) => {
|
|
|
|
cnx = Some(stream);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Ok(Err(err)) => {
|
2023-11-26 14:47:49 +00:00
|
|
|
warn!("Cannot connect to tcp endpoint {addr} reason {err}");
|
2023-10-01 15:16:23 +00:00
|
|
|
last_err = Some(err);
|
|
|
|
}
|
|
|
|
Err(_) => {
|
2023-11-26 14:47:49 +00:00
|
|
|
warn!(
|
2023-10-01 15:16:23 +00:00
|
|
|
"Cannot connect to tcp endpoint {addr} due to timeout of {}s elapsed",
|
|
|
|
connect_timeout.as_secs()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(cnx) = cnx {
|
|
|
|
Ok(cnx)
|
|
|
|
} else {
|
|
|
|
Err(anyhow!(
|
|
|
|
"Cannot connect to tcp endpoint {}:{} reason {:?}",
|
|
|
|
host,
|
|
|
|
port,
|
|
|
|
last_err
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-21 10:04:10 +00:00
|
|
|
pub async fn connect_with_http_proxy(
|
|
|
|
proxy: &Url,
|
|
|
|
host: &Host<String>,
|
|
|
|
port: u16,
|
2023-12-04 17:21:55 +00:00
|
|
|
so_mark: Option<u32>,
|
2023-10-21 10:04:10 +00:00
|
|
|
connect_timeout: Duration,
|
2024-01-02 18:32:47 +00:00
|
|
|
dns_resolver: &DnsResolver,
|
2023-10-21 10:04:10 +00:00
|
|
|
) -> Result<TcpStream, anyhow::Error> {
|
2023-10-21 16:16:03 +00:00
|
|
|
let proxy_host = proxy.host().context("Cannot parse proxy host")?.to_owned();
|
2023-10-21 10:04:10 +00:00
|
|
|
let proxy_port = proxy.port_or_known_default().unwrap_or(80);
|
|
|
|
|
2024-01-02 18:32:47 +00:00
|
|
|
let mut socket = connect(&proxy_host, proxy_port, so_mark, connect_timeout, dns_resolver).await?;
|
2023-10-21 10:04:10 +00:00
|
|
|
info!("Connected to http proxy {}:{}", proxy_host, proxy_port);
|
|
|
|
|
2023-10-30 07:13:38 +00:00
|
|
|
let authorization = if let Some((user, password)) = proxy.password().map(|p| (proxy.username(), p)) {
|
2023-11-09 16:19:08 +00:00
|
|
|
let user = urlencoding::decode(user).with_context(|| format!("Cannot urldecode proxy user: {}", user))?;
|
2023-11-09 16:18:38 +00:00
|
|
|
let password =
|
|
|
|
urlencoding::decode(password).with_context(|| format!("Cannot urldecode proxy password: {}", password))?;
|
2023-10-30 07:13:38 +00:00
|
|
|
let creds = base64::engine::general_purpose::STANDARD.encode(format!("{}:{}", user, password));
|
|
|
|
format!("Proxy-Authorization: Basic {}\r\n", creds)
|
|
|
|
} else {
|
|
|
|
"".to_string()
|
|
|
|
};
|
2023-10-21 10:04:10 +00:00
|
|
|
|
2023-10-30 07:13:38 +00:00
|
|
|
let connect_request = format!("CONNECT {host}:{port} HTTP/1.0\r\nHost: {host}:{port}\r\n{authorization}\r\n");
|
2023-10-27 07:15:15 +00:00
|
|
|
socket.write_all(connect_request.as_bytes()).await?;
|
2023-10-21 10:04:10 +00:00
|
|
|
|
2023-10-27 07:15:15 +00:00
|
|
|
let mut buf = BytesMut::with_capacity(1024);
|
2023-10-21 10:04:10 +00:00
|
|
|
loop {
|
2023-10-27 07:15:15 +00:00
|
|
|
let nb_bytes = tokio::time::timeout(connect_timeout, socket.read_buf(&mut buf)).await;
|
|
|
|
match nb_bytes {
|
|
|
|
Ok(Ok(0)) => {
|
|
|
|
return Err(anyhow!(
|
2023-10-30 07:13:38 +00:00
|
|
|
"Cannot connect to http proxy. Proxy closed the connection without returning any response"
|
|
|
|
));
|
2023-10-21 10:04:10 +00:00
|
|
|
}
|
2023-10-27 07:15:15 +00:00
|
|
|
Ok(Ok(_)) => {}
|
2023-10-21 10:04:10 +00:00
|
|
|
Ok(Err(err)) => {
|
|
|
|
return Err(anyhow!("Cannot connect to http proxy. {err}"));
|
|
|
|
}
|
|
|
|
Err(_) => {
|
2023-10-30 07:13:38 +00:00
|
|
|
return Err(anyhow!("Cannot connect to http proxy. Proxy took too long to connect"));
|
2023-10-21 10:04:10 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-10-27 07:15:15 +00:00
|
|
|
static END_HTTP_RESPONSE: &[u8; 4] = b"\r\n\r\n"; // It is reversed from \r\n\r\n as we reverse scan the buffer
|
|
|
|
if buf.len() > 50 * 1024
|
|
|
|
|| buf
|
|
|
|
.windows(END_HTTP_RESPONSE.len())
|
|
|
|
.any(|window| window == END_HTTP_RESPONSE)
|
|
|
|
{
|
2023-10-21 10:04:10 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-27 19:02:12 +00:00
|
|
|
static OK_RESPONSE_10: &[u8] = b"HTTP/1.0 200 ";
|
|
|
|
static OK_RESPONSE_11: &[u8] = b"HTTP/1.1 200 ";
|
2023-10-21 10:04:10 +00:00
|
|
|
if !buf
|
2023-10-27 19:02:12 +00:00
|
|
|
.windows(OK_RESPONSE_10.len())
|
|
|
|
.any(|window| window == OK_RESPONSE_10 || window == OK_RESPONSE_11)
|
2023-10-21 10:04:10 +00:00
|
|
|
{
|
|
|
|
return Err(anyhow!(
|
|
|
|
"Cannot connect to http proxy. Proxy returned an invalid response: {}",
|
2023-10-27 07:15:15 +00:00
|
|
|
String::from_utf8_lossy(&buf)
|
2023-10-21 10:04:10 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
info!("http proxy connected to remote host {}:{}", host, port);
|
|
|
|
Ok(socket)
|
|
|
|
}
|
|
|
|
|
2023-12-01 19:20:33 +00:00
|
|
|
pub async fn run_server(bind: SocketAddr, ip_transparent: bool) -> Result<TcpListenerStream, anyhow::Error> {
|
2023-10-01 15:16:23 +00:00
|
|
|
info!("Starting TCP server listening cnx on {}", bind);
|
|
|
|
|
|
|
|
let listener = TcpListener::bind(bind)
|
|
|
|
.await
|
|
|
|
.with_context(|| format!("Cannot create TCP server {:?}", bind))?;
|
2023-12-01 19:20:33 +00:00
|
|
|
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
if ip_transparent {
|
|
|
|
info!("TCP server listening in TProxy mode");
|
|
|
|
socket2::SockRef::from(&listener).set_ip_transparent(ip_transparent)?;
|
|
|
|
}
|
|
|
|
|
2023-10-01 15:16:23 +00:00
|
|
|
Ok(TcpListenerStream::new(listener))
|
|
|
|
}
|
2023-10-27 19:02:12 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use futures_util::pin_mut;
|
|
|
|
use std::net::SocketAddr;
|
|
|
|
use testcontainers::core::WaitFor;
|
|
|
|
use testcontainers::{Image, ImageArgs, RunnableImage};
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Default)]
|
|
|
|
pub struct MitmProxy {}
|
|
|
|
|
|
|
|
impl ImageArgs for MitmProxy {
|
|
|
|
fn into_iterator(self) -> Box<dyn Iterator<Item = String>> {
|
|
|
|
Box::new(vec!["mitmdump".to_string()].into_iter())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Image for MitmProxy {
|
|
|
|
type Args = Self;
|
|
|
|
|
|
|
|
fn name(&self) -> String {
|
|
|
|
"mitmproxy/mitmproxy".to_string()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn tag(&self) -> String {
|
|
|
|
"10.1.1".to_string()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn ready_conditions(&self) -> Vec<WaitFor> {
|
|
|
|
vec![WaitFor::Duration {
|
|
|
|
length: Duration::from_secs(5),
|
|
|
|
}]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn test_proxy_connection() {
|
|
|
|
let server_addr: SocketAddr = "[::1]:1236".parse().unwrap();
|
|
|
|
let server = TcpListener::bind(server_addr).await.unwrap();
|
|
|
|
|
|
|
|
let docker = testcontainers::clients::Cli::default();
|
2023-10-30 07:13:38 +00:00
|
|
|
let mitm_proxy: RunnableImage<MitmProxy> = RunnableImage::from(MitmProxy {}).with_network("host".to_string());
|
2023-10-27 19:02:12 +00:00
|
|
|
let _node = docker.run(mitm_proxy);
|
|
|
|
|
|
|
|
let mut client = connect_with_http_proxy(
|
|
|
|
&"http://localhost:8080".parse().unwrap(),
|
|
|
|
&Host::Domain("[::1]".to_string()),
|
|
|
|
1236,
|
2023-11-26 17:22:28 +00:00
|
|
|
None,
|
2023-10-27 19:02:12 +00:00
|
|
|
Duration::from_secs(1),
|
2024-01-02 18:42:13 +00:00
|
|
|
&DnsResolver::System,
|
2023-10-27 19:02:12 +00:00
|
|
|
)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
2023-10-30 07:13:38 +00:00
|
|
|
client.write_all(b"GET / HTTP/1.1\r\n\r\n".as_slice()).await.unwrap();
|
2023-10-27 19:02:12 +00:00
|
|
|
let client_srv = server.accept().await.unwrap().0;
|
|
|
|
pin_mut!(client_srv);
|
|
|
|
|
|
|
|
let mut buf = [0u8; 25];
|
|
|
|
let ret = client_srv.read(&mut buf).await;
|
|
|
|
assert!(matches!(ret, Ok(18)));
|
|
|
|
client_srv
|
|
|
|
.write_all("HTTP/1.1 200 OK\r\n\r\n".as_bytes())
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
client_srv.get_mut().shutdown().await.unwrap();
|
|
|
|
let _ = client.read(&mut buf).await.unwrap();
|
|
|
|
assert!(buf.starts_with(b"HTTP/1.1 200 OK\r\n\r\n"));
|
|
|
|
}
|
|
|
|
}
|