Skip to main content

eggress_server/
reply.rs

1use tokio::io::AsyncWriteExt;
2
3use crate::accept::{PendingTunnel, ReplyContext, TunnelProtocol};
4use crate::error::SessionOpenError;
5use eggress_protocol_socks::socks5::server::SocksAddr;
6
7fn unspecified_ipv4() -> std::net::SocketAddr {
8    std::net::SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED), 0)
9}
10
11/// Send a tunnel success reply after route is established.
12pub async fn send_tunnel_success(
13    pending: &mut PendingTunnel,
14    _bound_addr: Option<std::net::SocketAddr>,
15) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
16    match (&pending.protocol, &pending.reply_context) {
17        (TunnelProtocol::HttpConnect, ReplyContext::Http) => {
18            pending
19                .client
20                .write_all(b"HTTP/1.1 200 Connection Established\r\n\r\n")
21                .await?;
22        }
23        (TunnelProtocol::Http2, ReplyContext::Http2)
24        | (TunnelProtocol::Http3, ReplyContext::Http3)
25        | (TunnelProtocol::WebSocket, ReplyContext::WebSocket) => {}
26        (TunnelProtocol::Socks4, ReplyContext::Socks4) => {
27            eggress_protocol_socks::socks4::server::write_socks4_reply(
28                &mut pending.client,
29                eggress_protocol_socks::socks4::server::Socks4Status::Granted,
30                unspecified_ipv4(),
31            )
32            .await?;
33        }
34        (TunnelProtocol::Socks5, ReplyContext::Socks5) => {
35            let bind_addr = SocksAddr::IPv4([0, 0, 0, 0], 0);
36            eggress_protocol_socks::socks5::server::send_connect_reply(
37                &mut pending.client,
38                0x00,
39                &bind_addr,
40            )
41            .await?;
42        }
43        (TunnelProtocol::Shadowsocks, ReplyContext::Shadowsocks) => {
44            // Shadowsocks has no success reply - the server starts relaying immediately
45        }
46        (TunnelProtocol::Trojan, ReplyContext::Trojan) => {
47            // Trojan has no success reply - the server starts relaying immediately
48        }
49        (TunnelProtocol::Raw, ReplyContext::Raw) => {}
50        _ => {
51            return Err("mismatched protocol and reply context".into());
52        }
53    }
54    Ok(())
55}
56
57/// Send a tunnel failure reply when route opening fails.
58pub async fn send_tunnel_failure(
59    pending: &mut PendingTunnel,
60    error: &SessionOpenError,
61) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
62    match (&pending.protocol, &pending.reply_context) {
63        (TunnelProtocol::HttpConnect, ReplyContext::Http) => {
64            let status = http_failure_status(error);
65            pending.client.write_all(status).await?;
66        }
67        (TunnelProtocol::Http2, ReplyContext::Http2)
68        | (TunnelProtocol::Http3, ReplyContext::Http3)
69        | (TunnelProtocol::WebSocket, ReplyContext::WebSocket) => {
70            pending.client.shutdown().await.ok();
71        }
72        (TunnelProtocol::Socks4, ReplyContext::Socks4) => {
73            eggress_protocol_socks::socks4::server::write_socks4_reply(
74                &mut pending.client,
75                eggress_protocol_socks::socks4::server::Socks4Status::Failed,
76                unspecified_ipv4(),
77            )
78            .await?;
79        }
80        (TunnelProtocol::Socks5, ReplyContext::Socks5) => {
81            let rep = socks5_failure_rep(error);
82            let bind_addr = SocksAddr::IPv4([0, 0, 0, 0], 0);
83            eggress_protocol_socks::socks5::server::send_connect_reply(
84                &mut pending.client,
85                rep,
86                &bind_addr,
87            )
88            .await?;
89        }
90        (TunnelProtocol::Shadowsocks, ReplyContext::Shadowsocks) => {
91            // Shadowsocks has no failure reply - just close the connection
92            pending.client.shutdown().await.ok();
93        }
94        (TunnelProtocol::Trojan, ReplyContext::Trojan) => {
95            // Trojan has no failure reply - just close the connection
96            pending.client.shutdown().await.ok();
97        }
98        (TunnelProtocol::Raw, ReplyContext::Raw) => {
99            pending.client.shutdown().await.ok();
100        }
101        _ => {
102            return Err("mismatched protocol and reply context".into());
103        }
104    }
105    Ok(())
106}
107
108/// Send an HTTP forward-proxy failure reply.
109pub async fn send_http_forward_failure(
110    client: &mut eggress_core::BoxStream,
111    error: &SessionOpenError,
112) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
113    let status = http_failure_status(error);
114    client.write_all(status).await?;
115    Ok(())
116}
117
118/// Reject request expectations because the forwarder does not implement the
119/// 100-continue negotiation or a concurrent request/response pump.
120pub async fn send_http_expectation_failed(
121    client: &mut eggress_core::BoxStream,
122) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
123    client
124        .write_all(b"HTTP/1.1 417 Expectation Failed\r\nConnection: close\r\n\r\n")
125        .await?;
126    client.shutdown().await?;
127    Ok(())
128}
129
130/// Reject an upstream protocol upgrade rather than accidentally turning the
131/// forward-proxy session into an unframed tunnel.
132pub async fn send_http_upgrade_unsupported(
133    client: &mut eggress_core::BoxStream,
134) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
135    client
136        .write_all(b"HTTP/1.1 501 Not Implemented\r\nConnection: close\r\n\r\n")
137        .await?;
138    client.shutdown().await?;
139    Ok(())
140}
141
142fn http_failure_status(error: &SessionOpenError) -> &'static [u8] {
143    match error {
144        SessionOpenError::Timeout => b"HTTP/1.1 504 Gateway Timeout\r\nConnection: close\r\n\r\n",
145        SessionOpenError::PolicyDenied => b"HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n",
146        _ => b"HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\n\r\n",
147    }
148}
149
150fn socks5_failure_rep(error: &SessionOpenError) -> u8 {
151    match error {
152        SessionOpenError::Timeout => 0x06,
153        SessionOpenError::PolicyDenied => 0x02,
154        SessionOpenError::NetworkUnreachable => 0x03,
155        SessionOpenError::HostUnreachable | SessionOpenError::Dns => 0x04,
156        SessionOpenError::Refused => 0x05,
157        _ => 0x01,
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164    use crate::accept::{PendingTunnel, ReplyContext, TunnelProtocol};
165    use eggress_core::{TargetAddr, TargetHost};
166    use tokio::io::AsyncReadExt;
167
168    fn make_pending(
169        protocol: TunnelProtocol,
170        reply_context: ReplyContext,
171    ) -> (PendingTunnel, tokio::io::DuplexStream) {
172        let (client_stream, server_stream) = tokio::io::duplex(1024);
173        let pending = PendingTunnel {
174            target: TargetAddr {
175                host: TargetHost::Domain("example.com".into()),
176                port: 443,
177            },
178            client: Box::new(client_stream),
179            protocol,
180            reply_context,
181            identity: eggress_core::ClientIdentity::Anonymous,
182        };
183        (pending, server_stream)
184    }
185
186    #[tokio::test]
187    async fn test_send_tunnel_success_http() {
188        let (mut pending, mut server) =
189            make_pending(TunnelProtocol::HttpConnect, ReplyContext::Http);
190        send_tunnel_success(&mut pending, None).await.unwrap();
191
192        let mut response = vec![0u8; 1024];
193        let n = server.read(&mut response).await.unwrap();
194        let s = String::from_utf8_lossy(&response[..n]);
195        assert!(s.contains("200"));
196    }
197
198    #[tokio::test]
199    async fn test_send_tunnel_success_socks4() {
200        let (mut pending, mut server) = make_pending(TunnelProtocol::Socks4, ReplyContext::Socks4);
201        send_tunnel_success(&mut pending, None).await.unwrap();
202
203        let mut response = [0u8; 8];
204        server.read_exact(&mut response).await.unwrap();
205        assert_eq!(response[0], 0x00);
206        assert_eq!(response[1], 90); // granted
207    }
208
209    #[tokio::test]
210    async fn test_send_tunnel_success_socks5() {
211        let (mut pending, mut server) = make_pending(TunnelProtocol::Socks5, ReplyContext::Socks5);
212        send_tunnel_success(&mut pending, None).await.unwrap();
213
214        let mut response = [0u8; 10];
215        server.read_exact(&mut response).await.unwrap();
216        assert_eq!(response[0], 0x05);
217        assert_eq!(response[1], 0x00); // success
218    }
219
220    #[tokio::test]
221    async fn test_send_tunnel_failure_http_timeout() {
222        let (mut pending, mut server) =
223            make_pending(TunnelProtocol::HttpConnect, ReplyContext::Http);
224        send_tunnel_failure(&mut pending, &SessionOpenError::Timeout)
225            .await
226            .unwrap();
227
228        let mut response = vec![0u8; 1024];
229        let n = server.read(&mut response).await.unwrap();
230        let s = String::from_utf8_lossy(&response[..n]);
231        assert!(s.contains("504"));
232    }
233
234    #[tokio::test]
235    async fn test_send_tunnel_failure_socks5_refused() {
236        let (mut pending, mut server) = make_pending(TunnelProtocol::Socks5, ReplyContext::Socks5);
237        send_tunnel_failure(&mut pending, &SessionOpenError::Refused)
238            .await
239            .unwrap();
240
241        let mut response = [0u8; 10];
242        server.read_exact(&mut response).await.unwrap();
243        assert_eq!(response[0], 0x05);
244        assert_eq!(response[1], 0x05); // connection refused
245    }
246
247    #[tokio::test]
248    async fn test_send_http_forward_failure() {
249        let (client_stream, mut server) = tokio::io::duplex(1024);
250        let mut client: eggress_core::BoxStream = Box::new(client_stream);
251        send_http_forward_failure(&mut client, &SessionOpenError::Refused)
252            .await
253            .unwrap();
254
255        let mut response = vec![0u8; 1024];
256        let n = server.read(&mut response).await.unwrap();
257        let s = String::from_utf8_lossy(&response[..n]);
258        assert!(s.contains("502"));
259    }
260}