use tokio::io::AsyncWriteExt;
use crate::accept::{PendingTunnel, ReplyContext, TunnelProtocol};
use crate::error::SessionOpenError;
use eggress_protocol_socks::socks5::server::SocksAddr;
fn unspecified_ipv4() -> std::net::SocketAddr {
std::net::SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED), 0)
}
pub async fn send_tunnel_success(
pending: &mut PendingTunnel,
_bound_addr: Option<std::net::SocketAddr>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
match (&pending.protocol, &pending.reply_context) {
(TunnelProtocol::HttpConnect, ReplyContext::Http) => {
pending
.client
.write_all(b"HTTP/1.1 200 Connection Established\r\n\r\n")
.await?;
}
(TunnelProtocol::Http2, ReplyContext::Http2)
| (TunnelProtocol::Http3, ReplyContext::Http3)
| (TunnelProtocol::WebSocket, ReplyContext::WebSocket) => {}
(TunnelProtocol::Socks4, ReplyContext::Socks4) => {
eggress_protocol_socks::socks4::server::write_socks4_reply(
&mut pending.client,
eggress_protocol_socks::socks4::server::Socks4Status::Granted,
unspecified_ipv4(),
)
.await?;
}
(TunnelProtocol::Socks5, ReplyContext::Socks5) => {
let bind_addr = SocksAddr::IPv4([0, 0, 0, 0], 0);
eggress_protocol_socks::socks5::server::send_connect_reply(
&mut pending.client,
0x00,
&bind_addr,
)
.await?;
}
(TunnelProtocol::Shadowsocks, ReplyContext::Shadowsocks) => {
}
(TunnelProtocol::Trojan, ReplyContext::Trojan) => {
}
(TunnelProtocol::Raw, ReplyContext::Raw) => {}
_ => {
return Err("mismatched protocol and reply context".into());
}
}
Ok(())
}
pub async fn send_tunnel_failure(
pending: &mut PendingTunnel,
error: &SessionOpenError,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
match (&pending.protocol, &pending.reply_context) {
(TunnelProtocol::HttpConnect, ReplyContext::Http) => {
let status = http_failure_status(error);
pending.client.write_all(status).await?;
}
(TunnelProtocol::Http2, ReplyContext::Http2)
| (TunnelProtocol::Http3, ReplyContext::Http3)
| (TunnelProtocol::WebSocket, ReplyContext::WebSocket) => {
pending.client.shutdown().await.ok();
}
(TunnelProtocol::Socks4, ReplyContext::Socks4) => {
eggress_protocol_socks::socks4::server::write_socks4_reply(
&mut pending.client,
eggress_protocol_socks::socks4::server::Socks4Status::Failed,
unspecified_ipv4(),
)
.await?;
}
(TunnelProtocol::Socks5, ReplyContext::Socks5) => {
let rep = socks5_failure_rep(error);
let bind_addr = SocksAddr::IPv4([0, 0, 0, 0], 0);
eggress_protocol_socks::socks5::server::send_connect_reply(
&mut pending.client,
rep,
&bind_addr,
)
.await?;
}
(TunnelProtocol::Shadowsocks, ReplyContext::Shadowsocks) => {
pending.client.shutdown().await.ok();
}
(TunnelProtocol::Trojan, ReplyContext::Trojan) => {
pending.client.shutdown().await.ok();
}
(TunnelProtocol::Raw, ReplyContext::Raw) => {
pending.client.shutdown().await.ok();
}
_ => {
return Err("mismatched protocol and reply context".into());
}
}
Ok(())
}
pub async fn send_http_forward_failure(
client: &mut eggress_core::BoxStream,
error: &SessionOpenError,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let status = http_failure_status(error);
client.write_all(status).await?;
Ok(())
}
pub async fn send_http_expectation_failed(
client: &mut eggress_core::BoxStream,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
client
.write_all(b"HTTP/1.1 417 Expectation Failed\r\nConnection: close\r\n\r\n")
.await?;
client.shutdown().await?;
Ok(())
}
pub async fn send_http_upgrade_unsupported(
client: &mut eggress_core::BoxStream,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
client
.write_all(b"HTTP/1.1 501 Not Implemented\r\nConnection: close\r\n\r\n")
.await?;
client.shutdown().await?;
Ok(())
}
fn http_failure_status(error: &SessionOpenError) -> &'static [u8] {
match error {
SessionOpenError::Timeout => b"HTTP/1.1 504 Gateway Timeout\r\nConnection: close\r\n\r\n",
SessionOpenError::PolicyDenied => b"HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n",
_ => b"HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\n\r\n",
}
}
fn socks5_failure_rep(error: &SessionOpenError) -> u8 {
match error {
SessionOpenError::Timeout => 0x06,
SessionOpenError::PolicyDenied => 0x02,
SessionOpenError::NetworkUnreachable => 0x03,
SessionOpenError::HostUnreachable | SessionOpenError::Dns => 0x04,
SessionOpenError::Refused => 0x05,
_ => 0x01,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::accept::{PendingTunnel, ReplyContext, TunnelProtocol};
use eggress_core::{TargetAddr, TargetHost};
use tokio::io::AsyncReadExt;
fn make_pending(
protocol: TunnelProtocol,
reply_context: ReplyContext,
) -> (PendingTunnel, tokio::io::DuplexStream) {
let (client_stream, server_stream) = tokio::io::duplex(1024);
let pending = PendingTunnel {
target: TargetAddr {
host: TargetHost::Domain("example.com".into()),
port: 443,
},
client: Box::new(client_stream),
protocol,
reply_context,
identity: eggress_core::ClientIdentity::Anonymous,
};
(pending, server_stream)
}
#[tokio::test]
async fn test_send_tunnel_success_http() {
let (mut pending, mut server) =
make_pending(TunnelProtocol::HttpConnect, ReplyContext::Http);
send_tunnel_success(&mut pending, None).await.unwrap();
let mut response = vec![0u8; 1024];
let n = server.read(&mut response).await.unwrap();
let s = String::from_utf8_lossy(&response[..n]);
assert!(s.contains("200"));
}
#[tokio::test]
async fn test_send_tunnel_success_socks4() {
let (mut pending, mut server) = make_pending(TunnelProtocol::Socks4, ReplyContext::Socks4);
send_tunnel_success(&mut pending, None).await.unwrap();
let mut response = [0u8; 8];
server.read_exact(&mut response).await.unwrap();
assert_eq!(response[0], 0x00);
assert_eq!(response[1], 90); }
#[tokio::test]
async fn test_send_tunnel_success_socks5() {
let (mut pending, mut server) = make_pending(TunnelProtocol::Socks5, ReplyContext::Socks5);
send_tunnel_success(&mut pending, None).await.unwrap();
let mut response = [0u8; 10];
server.read_exact(&mut response).await.unwrap();
assert_eq!(response[0], 0x05);
assert_eq!(response[1], 0x00); }
#[tokio::test]
async fn test_send_tunnel_failure_http_timeout() {
let (mut pending, mut server) =
make_pending(TunnelProtocol::HttpConnect, ReplyContext::Http);
send_tunnel_failure(&mut pending, &SessionOpenError::Timeout)
.await
.unwrap();
let mut response = vec![0u8; 1024];
let n = server.read(&mut response).await.unwrap();
let s = String::from_utf8_lossy(&response[..n]);
assert!(s.contains("504"));
}
#[tokio::test]
async fn test_send_tunnel_failure_socks5_refused() {
let (mut pending, mut server) = make_pending(TunnelProtocol::Socks5, ReplyContext::Socks5);
send_tunnel_failure(&mut pending, &SessionOpenError::Refused)
.await
.unwrap();
let mut response = [0u8; 10];
server.read_exact(&mut response).await.unwrap();
assert_eq!(response[0], 0x05);
assert_eq!(response[1], 0x05); }
#[tokio::test]
async fn test_send_http_forward_failure() {
let (client_stream, mut server) = tokio::io::duplex(1024);
let mut client: eggress_core::BoxStream = Box::new(client_stream);
send_http_forward_failure(&mut client, &SessionOpenError::Refused)
.await
.unwrap();
let mut response = vec![0u8; 1024];
let n = server.read(&mut response).await.unwrap();
let s = String::from_utf8_lossy(&response[..n]);
assert!(s.contains("502"));
}
}