mirror of
https://github.com/aramperes/onetun.git
synced 2025-09-09 12:18:31 -04:00
HTTP Proxy Server
This commit is contained in:
parent
b78cab58ee
commit
7ea7b0e683
7 changed files with 238 additions and 37 deletions
92
Cargo.lock
generated
92
Cargo.lock
generated
|
@ -146,6 +146,12 @@ dependencies = [
|
|||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.21"
|
||||
|
@ -267,6 +273,40 @@ version = "0.4.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "1.3.0"
|
||||
|
@ -276,6 +316,29 @@ dependencies = [
|
|||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.1"
|
||||
|
@ -308,6 +371,12 @@ version = "0.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.19.0"
|
||||
|
@ -433,6 +502,7 @@ dependencies = [
|
|||
"boringtun",
|
||||
"clap",
|
||||
"futures",
|
||||
"hyper",
|
||||
"log",
|
||||
"nom",
|
||||
"pretty_env_logger",
|
||||
|
@ -737,6 +807,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.35"
|
||||
|
@ -769,6 +845,12 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.1"
|
||||
|
@ -804,6 +886,16 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
|
|
@ -21,6 +21,7 @@ nom = "7"
|
|||
async-trait = "0.1.51"
|
||||
priority-queue = "1.2.0"
|
||||
smoltcp = { version = "0.8.0", default-features = false, features = ["std", "log", "medium-ip", "proto-ipv4", "proto-ipv6", "socket-udp", "socket-tcp"] }
|
||||
hyper = { version = "0.14", features = ["server", "http1", "tcp"] }
|
||||
|
||||
# bin-only dependencies
|
||||
clap = { version = "2.33", default-features = false, features = ["suggestions"], optional = true }
|
||||
|
|
|
@ -15,6 +15,7 @@ pub struct Config {
|
|||
pub port_forwards: Vec<PortForwardConfig>,
|
||||
#[allow(dead_code)]
|
||||
pub remote_port_forwards: Vec<PortForwardConfig>,
|
||||
pub proxy_listen_addr: Option<SocketAddr>,
|
||||
pub private_key: Arc<X25519SecretKey>,
|
||||
pub endpoint_public_key: Arc<X25519PublicKey>,
|
||||
pub endpoint_addr: SocketAddr,
|
||||
|
@ -133,6 +134,13 @@ impl Config {
|
|||
\t--remote 8080:[::1]:8081:TCP\n\
|
||||
\t--remote 8080:google.com:80\
|
||||
"),
|
||||
Arg::with_name("proxy-listen-addr")
|
||||
.required(false)
|
||||
.takes_value(true)
|
||||
.long("http-proxy")
|
||||
.env("ONETUN_PROXY_LISTEN_ADDR")
|
||||
.help("Create an HTTP proxy server that listens on this address. All connections through the proxy will be forwarded through the WireGuard tunnel. \
|
||||
Currently, only the CONNECT method is supported, and only IP addresses are supported (the server will not do any DNS lookups)."),
|
||||
]).get_matches();
|
||||
|
||||
// Combine `PORT_FORWARD` arg and `ONETUN_PORT_FORWARD_#` envs
|
||||
|
@ -203,8 +211,13 @@ impl Config {
|
|||
port_forward.remote = true;
|
||||
}
|
||||
|
||||
if port_forwards.is_empty() && remote_port_forwards.is_empty() {
|
||||
return Err(anyhow::anyhow!("No port forward configurations given."));
|
||||
let proxy_listen_addr = match matches.value_of("proxy-listen-addr") {
|
||||
Some(proxy_listen_addr) => Some(parse_addr(Some(proxy_listen_addr)).with_context(|| "Invalid proxy listen address")?),
|
||||
None => None
|
||||
};
|
||||
|
||||
if port_forwards.is_empty() && remote_port_forwards.is_empty() && proxy_listen_addr.is_none() {
|
||||
return Err(anyhow::anyhow!("No port forward or http proxy configurations given."));
|
||||
}
|
||||
|
||||
// Read private key from file or CLI argument
|
||||
|
@ -257,6 +270,7 @@ impl Config {
|
|||
Ok(Self {
|
||||
port_forwards,
|
||||
remote_port_forwards,
|
||||
proxy_listen_addr,
|
||||
private_key: Arc::new(
|
||||
parse_private_key(&private_key).with_context(|| "Invalid private key")?,
|
||||
),
|
||||
|
|
10
src/lib.rs
10
src/lib.rs
|
@ -66,6 +66,7 @@ pub async fn start_tunnels(config: Config, bus: Bus) -> anyhow::Result<()> {
|
|||
.port_forwards
|
||||
.iter()
|
||||
.any(|pf| pf.protocol == PortProtocol::Tcp)
|
||||
|| config.proxy_listen_addr.is_some()
|
||||
{
|
||||
// TCP device
|
||||
let bus = bus.clone();
|
||||
|
@ -118,5 +119,14 @@ pub async fn start_tunnels(config: Config, bus: Bus) -> anyhow::Result<()> {
|
|||
});
|
||||
}
|
||||
|
||||
if let Some(proxy_listen_addr) = config.proxy_listen_addr {
|
||||
let source_peer_ip = config.source_peer_ip;
|
||||
tokio::spawn(async move {
|
||||
tunnel::http_proxy_listen(proxy_listen_addr, source_peer_ip, tcp_port_pool.clone(), wg.clone(), bus.clone())
|
||||
.await
|
||||
.unwrap_or_else(|e| error!("HTTP Proxy Listen failed: {}", e));
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
75
src/tunnel/http_proxy.rs
Normal file
75
src/tunnel/http_proxy.rs
Normal file
|
@ -0,0 +1,75 @@
|
|||
use std::convert::Infallible;
|
||||
use std::net::SocketAddr;
|
||||
use crate::{PortProtocol, TcpPortPool};
|
||||
use crate::Bus;
|
||||
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Server, Method, Request, Body, Response, http};
|
||||
use hyper::server::conn::AddrStream;
|
||||
use crate::config::PortForwardConfig;
|
||||
use crate::tunnel::tcp::handle_tcp_proxy_connection;
|
||||
|
||||
pub async fn http_proxy_server(
|
||||
listen_addr: SocketAddr,
|
||||
port_pool: TcpPortPool,
|
||||
bus: Bus,
|
||||
) -> anyhow::Result<()> {
|
||||
|
||||
let make_service = make_service_fn(move |conn: &AddrStream| {
|
||||
let bus = bus.clone();
|
||||
let port_pool = port_pool.clone();
|
||||
let addr = conn.remote_addr();
|
||||
async move {
|
||||
let addr = addr.clone();
|
||||
Ok::<_, Infallible>(service_fn(move |req| proxy(addr, req, port_pool.clone(), bus.clone())))
|
||||
}
|
||||
});
|
||||
|
||||
let server = Server::bind(&listen_addr).serve(make_service);
|
||||
|
||||
Ok(server.await?)
|
||||
|
||||
}
|
||||
|
||||
async fn proxy(remote_addr: SocketAddr, req: Request<Body>, port_pool: TcpPortPool, bus: Bus) -> Result<Response<Body>, hyper::Error> {
|
||||
debug!("http request from {}: {:?}", remote_addr, req);
|
||||
|
||||
if Method::CONNECT == req.method() {
|
||||
if let Some(addr) = host_addr(req.uri()) {
|
||||
tokio::task::spawn(async move {
|
||||
match hyper::upgrade::on(req).await {
|
||||
Ok(upgraded) => {
|
||||
let config = PortForwardConfig {
|
||||
destination: addr.parse().expect("failed to parse destination SocketAddr"),
|
||||
source: remote_addr, // doesn't matter, won't be used
|
||||
protocol: PortProtocol::Tcp,
|
||||
remote: false
|
||||
};
|
||||
info!("proxy {} -> {}", remote_addr, config.destination);
|
||||
if let Err(e) = handle_tcp_proxy_connection(upgraded, port_pool.next().await.unwrap(), config, bus.clone()).await {
|
||||
eprintln!("server io error: {}", e);
|
||||
};
|
||||
}
|
||||
Err(e) => eprintln!("upgrade error: {}", e),
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Response::new(Body::empty()))
|
||||
} else {
|
||||
eprintln!("CONNECT host is not socket addr: {:?}", req.uri());
|
||||
let mut resp = Response::new(Body::from("CONNECT must be to a socket address"));
|
||||
*resp.status_mut() = http::StatusCode::BAD_REQUEST;
|
||||
|
||||
Ok(resp)
|
||||
}
|
||||
} else {
|
||||
let mut resp = Response::new(Body::from("Only CONNECT method supported"));
|
||||
*resp.status_mut() = http::StatusCode::METHOD_NOT_ALLOWED;
|
||||
|
||||
Ok(resp)
|
||||
}
|
||||
}
|
||||
|
||||
fn host_addr(uri: &http::Uri) -> Option<String> {
|
||||
uri.authority().and_then(|auth| Some(auth.to_string()))
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use std::net::IpAddr;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::config::{PortForwardConfig, PortProtocol};
|
||||
|
@ -9,6 +9,7 @@ use crate::wg::WireGuardTunnel;
|
|||
|
||||
pub mod tcp;
|
||||
pub mod udp;
|
||||
pub mod http_proxy;
|
||||
|
||||
pub async fn port_forward(
|
||||
port_forward: PortForwardConfig,
|
||||
|
@ -32,3 +33,20 @@ pub async fn port_forward(
|
|||
PortProtocol::Udp => udp::udp_proxy_server(port_forward, udp_port_pool, bus).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn http_proxy_listen(
|
||||
listen_addr: SocketAddr,
|
||||
source_peer_ip: IpAddr,
|
||||
tcp_port_pool: TcpPortPool,
|
||||
wg: Arc<WireGuardTunnel>,
|
||||
bus: Bus,
|
||||
) -> anyhow::Result<()> {
|
||||
info!(
|
||||
"HTTP proxy server listening on {} (forwarding through {} as peer {})",
|
||||
listen_addr,
|
||||
&wg.endpoint,
|
||||
source_peer_ip
|
||||
);
|
||||
|
||||
http_proxy::http_proxy_server(listen_addr, tcp_port_pool, bus).await
|
||||
}
|
|
@ -3,7 +3,7 @@ use crate::virtual_iface::VirtualPort;
|
|||
use anyhow::Context;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use std::ops::Range;
|
||||
use std::time::Duration;
|
||||
|
@ -11,7 +11,7 @@ use std::time::Duration;
|
|||
use crate::events::{Bus, Event};
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
const MAX_PACKET: usize = 65536;
|
||||
const MIN_PORT: u16 = 1000;
|
||||
|
@ -72,45 +72,39 @@ pub async fn tcp_proxy_server(
|
|||
}
|
||||
|
||||
/// Handles a new TCP connection with its assigned virtual port.
|
||||
async fn handle_tcp_proxy_connection(
|
||||
mut socket: TcpStream,
|
||||
pub async fn handle_tcp_proxy_connection<A>(
|
||||
mut socket: A,
|
||||
virtual_port: VirtualPort,
|
||||
port_forward: PortForwardConfig,
|
||||
bus: Bus,
|
||||
) -> anyhow::Result<()> {
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
A: AsyncReadExt + AsyncWriteExt + Unpin
|
||||
{
|
||||
let mut endpoint = bus.new_endpoint();
|
||||
endpoint.send(Event::ClientConnectionInitiated(port_forward, virtual_port));
|
||||
|
||||
let mut buffer = Vec::with_capacity(MAX_PACKET);
|
||||
let mut buffer = vec![0; MAX_PACKET];
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
readable_result = socket.readable() => {
|
||||
match readable_result {
|
||||
Ok(_) => {
|
||||
match socket.try_read_buf(&mut buffer) {
|
||||
Ok(size) if size > 0 => {
|
||||
let data = Vec::from(&buffer[..size]);
|
||||
endpoint.send(Event::LocalData(port_forward, virtual_port, data));
|
||||
// Reset buffer
|
||||
buffer.clear();
|
||||
}
|
||||
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
"[{}] Failed to read from client TCP socket: {:?}",
|
||||
virtual_port, e
|
||||
);
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
read_result = socket.read(&mut buffer) => {
|
||||
match read_result {
|
||||
Ok(size) if size > 0 => {
|
||||
let data = Vec::from(&buffer[..size]);
|
||||
endpoint.send(Event::LocalData(port_forward, virtual_port, data));
|
||||
}
|
||||
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
error!("[{}] Failed to check if readable: {:?}", virtual_port, e);
|
||||
error!(
|
||||
"[{}] Failed to read from client TCP socket: {:?}",
|
||||
virtual_port, e
|
||||
);
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -123,9 +117,6 @@ async fn handle_tcp_proxy_connection(
|
|||
}
|
||||
Event::RemoteData(e_vp, data) if e_vp == virtual_port => {
|
||||
// Have remote data to send to the local client
|
||||
if let Err(e) = socket.writable().await {
|
||||
error!("[{}] Failed to check if writable: {:?}", virtual_port, e);
|
||||
}
|
||||
let expected = data.len();
|
||||
let mut sent = 0;
|
||||
loop {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue