Tunnel proxy for https traffic

I want to implement a proxy server to forward traffic to upstream proxy server.

client <-> proxy server <-> upstream proxy server <-> ..... <-> target server

After searching for a long time, and ask GTP for help, below is http/https tunnel proxy

use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};

async fn handle_connection(mut inbound: TcpStream, target_addr: &str) -> io::Result<()> {
    let mut buffer = [0; 1024];
    let n = inbound.read(&mut buffer).await?;
    let request = String::from_utf8_lossy(&buffer[..n]);
    println!("request {:?}", request);

    let mut outbound = TcpStream::connect(target_addr).await?;
    println!("outbound {:?}", outbound);

    if request.starts_with("CONNECT") {
        inbound
            .write_all(b"HTTP/1.1 200 Connection Established\r\n\r\n")
            .await?;
    }
    let (mut ri, mut wi) = inbound.split();
    let (mut ro, mut wo) = outbound.split();
    let initial_data = &buffer[..n];
    let write_initial = async {
        if !initial_data.is_empty() {
            wo.write_all(initial_data).await?;
        }
        io::copy(&mut ri, &mut wo).await
    };
    tokio::try_join!(write_initial, io::copy(&mut ro, &mut wi))?;

    // tokio::try_join!(io::copy(&mut ri, &mut wo), io::copy(&mut ro, &mut wi))?;
    Ok(())
}

#[tokio::main]
async fn main() -> io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:8181").await?;
    loop {
        let (inbound, _) = listener.accept().await?;

        tokio::spawn(async move {
            let target_addr = "127.0.0.1:12345";
            if let Err(e) = handle_connection(inbound, target_addr).await {
                eprintln!("Failed to forward connection: {}", e);
            }
        });
    }
}

This work for http traffic

$ curl -v 'http://httpbin.org/anything' -x http://localhost:8181
*   Trying 127.0.0.1:8181...
* Connected to (nil) (127.0.0.1) port 8181 (#0)
> GET http://httpbin.org/anything HTTP/1.1
> Host: httpbin.org
> User-Agent: curl/7.81.0
> Accept: */*
> Proxy-Connection: Keep-Alive
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Length: 373
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Origin: *
< Connection: keep-alive
< Content-Type: application/json
< Date: Mon, 02 Sep 2024 12:39:53 GMT
< Keep-Alive: timeout=4
< Proxy-Connection: keep-alive
< Server: gunicorn/19.9.0
< 
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip", 
    "Host": "httpbin.org", 
    "User-Agent": "curl/7.81.0", 
    "X-Amzn-Trace-Id": "Root=1-66d5b219-3fc519364e7b7fc100db86cc"
  }, 
  "json": null, 
  "method": "GET", 
  "origin": "x.y.z.k", 
  "url": "http://httpbin.org/anything"
}
* Connection #0 to host (nil) left intact

Not work on https traffic

$ curl -v 'https://httpbin.org/anything' -x http://localhost:8181
*   Trying 127.0.0.1:8181...
* Connected to (nil) (127.0.0.1) port 8181 (#0)
* allocate connect buffer!
* Establish HTTP proxy tunnel to httpbin.org:443
> CONNECT httpbin.org:443 HTTP/1.1
> Host: httpbin.org:443
> User-Agent: curl/7.81.0
> Proxy-Connection: Keep-Alive
> 
< HTTP/1.1 200 Connection Established
< 
* Proxy replied 200 to CONNECT request
* CONNECT phase completed!
* ALPN, offering h2
* ALPN, offering http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* (5454) (IN), , Unknown (72):
* error:0A00010B:SSL routines::wrong version number
* Closing connection 0
curl: (35) error:0A00010B:SSL routines::wrong version number

I don't know where I went wrong, but when I asked gtp multiple times, it gave thesame and incorrect answers :neutral_face:

I am very grateful for any suggestions.

I had a similar request and created tls-proxy-tunnel.

It looks to me that you don't make the upstream Proxy handshake or at least isn't shown in the code above, something like in this comment is missing in the code above.

Btw. The sequence do not match the code from my point of view.

1 Like

your project is helpful for me. My tunnel proxy server job should only forward traffic without any modification.
Finally, I found out the problem is read the initial_data to judge whether is CONNECT request.

use tokio::io::{self, AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};

async fn handle_connection(mut inbound: TcpStream, target_addr: &str) -> io::Result<()> {
    let mut buffer = [0; 1024];
    // let n = inbound.read(&mut buffer).await?;
    // Problem: read from inbound tarffic!
    // let request = String::from_utf8_lossy(&buffer[..n]);
    // println!("request {:?}", request);


    let mut outbound = TcpStream::connect(target_addr).await?;
    println!("outbound {:?}", outbound);

    // Not need to send CONNECT request to upstream proxy
    
    // if request.starts_with("CONNECT") {
    //     inbound
    //         .write_all(b"HTTP/1.1 200 Connection Established\r\n\r\n")
    //         .await?;
    // }


    let (mut ri, mut wi) = inbound.split();
    let (mut ro, mut wo) = outbound.split();

    // let initial_data = &buffer[..n];
    let write_initial = async {
        // Problem: refoward the initial inbound trafic to upstream proxy is not working
        // if !initial_data.is_empty() {
        //     wo.write_all(initial_data).await?;
        // }
        io::copy(&mut ri, &mut wo).await
    };
    tokio::try_join!(write_initial, io::copy(&mut ro, &mut wi))?;

    // tokio::try_join!(io::copy(&mut ri, &mut wo), io::copy(&mut ro, &mut wi))?;
    Ok(())

}


#[tokio::main]
async fn main() -> io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:8181").await?;
    loop {
        let (inbound, _) = listener.accept().await?;


        tokio::spawn(async move {
            let target_addr = "127.0.0.1:12345";
            if let Err(e) = handle_connection(inbound, target_addr).await {
                eprintln!("Failed to forward connection: {}", e);
            }
        });
    }
}

I don't know why the previous code modified the request.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.