Я хочу реализовать прокси-сервер для пересылки трафика на вышестоящий прокси-сервер.
client <-> proxy server <-> upstream proxy server <-> ..... <-> target server
После долгих поисков и обращения за помощью к GTP ниже приведен туннельный прокси http/https.
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);
}
});
}
}
Эта работа для http-трафика
$ 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
Не работает с https-трафиком
$ 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
Не знаю, где я ошибся, но когда я несколько раз спрашивал gtp, он давал одинаковые и неверные ответы :neutral_face:
Я очень благодарен за любые предложения.
[править] Извините, я также задаю этот вопрос на форуме ржавчины. https://users.rust-lang.org/t/tunnel-proxy-for-https-traffic/116955
Кстати, 127.0.0.1:12345
работает для https-трафика.
$ curl -v 'https://httpbin.org/anything' -x http://localhost:12345
* Trying 127.0.0.1:12345...
* Connected to (nil) (127.0.0.1) port 12345 (#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):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS header, Finished (20):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS header, Finished (20):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
* subject: CN=httpbin.org
* start date: Aug 20 00:00:00 2024 GMT
* expire date: Sep 17 23:59:59 2025 GMT
* subjectAltName: host "httpbin.org" matched cert's "httpbin.org"
* issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M02
* SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* Using Stream ID: 1 (easy handle 0x562af2195eb0)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET /anything HTTP/2
> Host: httpbin.org
> user-agent: curl/7.81.0
> accept: */*
>
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 200
< date: Tue, 03 Sep 2024 00:21:55 GMT
< content-type: application/json
< content-length: 342
< server: gunicorn/19.9.0
< access-control-allow-origin: *
< access-control-allow-credentials: true
<
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "curl/7.81.0",
"X-Amzn-Trace-Id": "Root=1-66d656a3-3b632abc5c2a7cff6f66e16d"
},
"json": null,
"method": "GET",
"origin": "a.b.e.f",
"url": "https://httpbin.org/anything"
}
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Connection #0 to host (nil) left intact
@moy2010 Это тот же вопрос, конечно. Но я не думаю, что это можно квалифицировать как дубликат. Есть ли в stackoverflow политика, запрещающая задавать одни и те же вопросы на других, несвязанных форумах?
Насколько я понимаю, ваш код ваш прокси всегда подключается к 127.0.0.1:12345, т. е. он не получает цель из запроса клиентов. Так что же делает 127.0.0.1:12345? Если это восходящий HTTP-прокси, как предложено в первых строках вашего вопроса, вы также должны отправить ему прокси-запросы, т. е. отправить CONNECT и дождаться ответа, аналогичного тому, как Curl делает с вашим прокси.
@AleksanderKrauze Мы не закрываем вопрос как дубликат чего-то стороннего. Если ответ доступен где-то кроме самого Stack Overflow, кто-то должен опубликовать здесь ответ с кратким изложением решения и ссылкой для указания авторства, если это необходимо (хотя имейте в виду, что ответ должен быть самостоятельным, без переходов по ссылкам).
@moy2010. Извините, я задаю этот вопрос обеим сторонам.
@SteffenUllrich, 127.0.0.1:12345
— это http(s)-сервер. Я редактирую свой вопрос более подробно, чтобы доказать, что этот прокси-сервер работает.
TL;DR: ваш прокси-сервер должен пересылать запрос CONNECT от клиента восходящему прокси-серверу, а не только внутренний трафик.
Ваша реализация HTTP-прокси по адресу 127.0.0.1:8181 имеет фиксированный восходящий прокси-сервер 127.0.0.1:12345. Оба этих прокси ожидают запроса CONNECT для HTTPS.
Но хотя ваш прокси-сервер по адресу 127.0.0.18181 обрабатывает CONNECT от клиента, он не отправляет CONNECT восходящему прокси-серверу по адресу 127.0.0.1:12345. Вместо этого он будет напрямую пытаться пройти через все после запроса CONNECT, т. е. он будет рассматривать восходящий поток как HTTPS-сервер (ожидающий прямой TLS), а не HTTP-прокси (ожидающий запрос CONNECT).
Поскольку восходящий прокси-сервер получает не правильный HTTP-запрос (то есть ожидаемый CONNECT), а вместо этого, по-видимому, тарабарщину (начало внутреннего рукопожатия TLS от клиента - в частности, TLS ClientHello), он ответит ошибкой HTTP. Эта ошибка HTTP передается через клиент, который будет рассматривать ее как часть внутреннего подтверждения TLS, поскольку именно это (в частности, TLS ServerHello) отправляется в ожиданиях клиента.
На первый взгляд странная ошибка «неправильный номер версии» возникает из-за того, что клиент пытается интерпретировать ошибку HTTP от восходящего прокси-сервера как запись TLS, в частности, пытается интерпретировать второй и третий байт как версию TLS — см. структуру уровня записи TLS.. Но вместо того, чтобы эти байты были чем-то вроде 0x0303 (TLS 1.2), они будут больше похожи на 0x5454, то есть начало HTTP-ответа (который начинается с HTTP/1.
.) и, таким образом, совершенно неожиданно.
Чтобы решить эту проблему, ваш прокси-сервер должен не только обрабатывать запрос CONNECT от клиента, как вы уже это делаете, но и пересылать запрос CONNECT вышестоящему прокси-серверу, чего вы не делаете.
Это дубликат users.rust-lang.org/t/tunel-proxy-for-https-traffic/116955