1*cf78ab8cSAndroid Build Coastguard Worker // Copyright 2024 Google LLC
2*cf78ab8cSAndroid Build Coastguard Worker //
3*cf78ab8cSAndroid Build Coastguard Worker // Licensed under the Apache License, Version 2.0 (the "License");
4*cf78ab8cSAndroid Build Coastguard Worker // you may not use this file except in compliance with the License.
5*cf78ab8cSAndroid Build Coastguard Worker // You may obtain a copy of the License at
6*cf78ab8cSAndroid Build Coastguard Worker //
7*cf78ab8cSAndroid Build Coastguard Worker // https://www.apache.org/licenses/LICENSE-2.0
8*cf78ab8cSAndroid Build Coastguard Worker //
9*cf78ab8cSAndroid Build Coastguard Worker // Unless required by applicable law or agreed to in writing, software
10*cf78ab8cSAndroid Build Coastguard Worker // distributed under the License is distributed on an "AS IS" BASIS,
11*cf78ab8cSAndroid Build Coastguard Worker // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*cf78ab8cSAndroid Build Coastguard Worker // See the License for the specific language governing permissions and
13*cf78ab8cSAndroid Build Coastguard Worker // limitations under the License.
14*cf78ab8cSAndroid Build Coastguard Worker
15*cf78ab8cSAndroid Build Coastguard Worker // # Http Proxy Utils
16*cf78ab8cSAndroid Build Coastguard Worker //
17*cf78ab8cSAndroid Build Coastguard Worker // This module provides functionality for parsing proxy configuration
18*cf78ab8cSAndroid Build Coastguard Worker // strings and converting `TcpStream` objects to raw file
19*cf78ab8cSAndroid Build Coastguard Worker // descriptors.
20*cf78ab8cSAndroid Build Coastguard Worker //
21*cf78ab8cSAndroid Build Coastguard Worker // The `ProxyConfig` struct holds the parsed proxy configuration,
22*cf78ab8cSAndroid Build Coastguard Worker // including protocol, address, username, and password. The
23*cf78ab8cSAndroid Build Coastguard Worker // `from_string` function parses a proxy configuration string in the
24*cf78ab8cSAndroid Build Coastguard Worker // format `[protocol://][username:password@]host:port` or
25*cf78ab8cSAndroid Build Coastguard Worker // `[protocol://][username:password@]/[host/]:port` and returns a
26*cf78ab8cSAndroid Build Coastguard Worker // `ProxyConfig` struct.
27*cf78ab8cSAndroid Build Coastguard Worker //
28*cf78ab8cSAndroid Build Coastguard Worker // The `into_raw_descriptor` function converts a `TcpStream` object
29*cf78ab8cSAndroid Build Coastguard Worker // to a raw file descriptor (`RawDescriptor`), which is an `i32`
30*cf78ab8cSAndroid Build Coastguard Worker // representing the underlying socket. This is used for compatibility
31*cf78ab8cSAndroid Build Coastguard Worker // with libraries that require raw file descriptors, such as
32*cf78ab8cSAndroid Build Coastguard Worker // `libslirp_rs`.
33*cf78ab8cSAndroid Build Coastguard Worker
34*cf78ab8cSAndroid Build Coastguard Worker use crate::Error;
35*cf78ab8cSAndroid Build Coastguard Worker use regex::Regex;
36*cf78ab8cSAndroid Build Coastguard Worker use std::net::{SocketAddr, ToSocketAddrs};
37*cf78ab8cSAndroid Build Coastguard Worker #[cfg(unix)]
38*cf78ab8cSAndroid Build Coastguard Worker use std::os::fd::IntoRawFd;
39*cf78ab8cSAndroid Build Coastguard Worker #[cfg(windows)]
40*cf78ab8cSAndroid Build Coastguard Worker use std::os::windows::io::IntoRawSocket;
41*cf78ab8cSAndroid Build Coastguard Worker use tokio::net::TcpStream;
42*cf78ab8cSAndroid Build Coastguard Worker
43*cf78ab8cSAndroid Build Coastguard Worker pub type RawDescriptor = i32;
44*cf78ab8cSAndroid Build Coastguard Worker
45*cf78ab8cSAndroid Build Coastguard Worker /// Proxy configuration
46*cf78ab8cSAndroid Build Coastguard Worker pub struct ProxyConfig {
47*cf78ab8cSAndroid Build Coastguard Worker pub protocol: String,
48*cf78ab8cSAndroid Build Coastguard Worker pub addr: SocketAddr,
49*cf78ab8cSAndroid Build Coastguard Worker pub username: Option<String>,
50*cf78ab8cSAndroid Build Coastguard Worker pub password: Option<String>,
51*cf78ab8cSAndroid Build Coastguard Worker }
52*cf78ab8cSAndroid Build Coastguard Worker
53*cf78ab8cSAndroid Build Coastguard Worker impl ProxyConfig {
54*cf78ab8cSAndroid Build Coastguard Worker /// Parses a proxy configuration string and returns a `ProxyConfig` struct.
55*cf78ab8cSAndroid Build Coastguard Worker ///
56*cf78ab8cSAndroid Build Coastguard Worker /// The function expects the proxy configuration string to be in the following format:
57*cf78ab8cSAndroid Build Coastguard Worker ///
58*cf78ab8cSAndroid Build Coastguard Worker /// [protocol://][username:password@]host:port
59*cf78ab8cSAndroid Build Coastguard Worker /// [protocol://][username:password@]/[host/]:port
60*cf78ab8cSAndroid Build Coastguard Worker ///
61*cf78ab8cSAndroid Build Coastguard Worker /// where:
62*cf78ab8cSAndroid Build Coastguard Worker ///
63*cf78ab8cSAndroid Build Coastguard Worker /// * `protocol`: The network protocol (e.g., `http`, `https`,
64*cf78ab8cSAndroid Build Coastguard Worker /// `socks5`). If not provided, defaults to `http`.
65*cf78ab8cSAndroid Build Coastguard Worker /// * `username`: and `password` are optional credentials for authentication.
66*cf78ab8cSAndroid Build Coastguard Worker /// * `host`: The hostname or IP address of the proxy server. If
67*cf78ab8cSAndroid Build Coastguard Worker /// it's an IPv6 address, it should be enclosed in square brackets
68*cf78ab8cSAndroid Build Coastguard Worker /// (e.g., "[::1]").
69*cf78ab8cSAndroid Build Coastguard Worker /// * `port`: The port number on which the proxy server is listening.
70*cf78ab8cSAndroid Build Coastguard Worker ///
71*cf78ab8cSAndroid Build Coastguard Worker /// # Errors
72*cf78ab8cSAndroid Build Coastguard Worker /// Returns a `Error` if the input string is not in a
73*cf78ab8cSAndroid Build Coastguard Worker /// valid format or if the hostname/port resolution fails.
74*cf78ab8cSAndroid Build Coastguard Worker ///
75*cf78ab8cSAndroid Build Coastguard Worker /// # Limitations
76*cf78ab8cSAndroid Build Coastguard Worker /// * Usernames and passwords cannot contain `@` or `:`.
from_string(config_string: &str) -> Result<ProxyConfig, Error>77*cf78ab8cSAndroid Build Coastguard Worker pub fn from_string(config_string: &str) -> Result<ProxyConfig, Error> {
78*cf78ab8cSAndroid Build Coastguard Worker let re = Regex::new(r"^(?:(?P<protocol>\w+)://)?(?:(?P<user>\w+):(?P<pass>\w+)@)?(?P<host>(?:[\w\.-]+|\[[^\]]+\])):(?P<port>\d+)$").unwrap();
79*cf78ab8cSAndroid Build Coastguard Worker let caps = re.captures(config_string).ok_or(Error::MalformedConfigString)?;
80*cf78ab8cSAndroid Build Coastguard Worker
81*cf78ab8cSAndroid Build Coastguard Worker let protocol =
82*cf78ab8cSAndroid Build Coastguard Worker caps.name("protocol").map_or_else(|| "http".to_string(), |m| m.as_str().to_string());
83*cf78ab8cSAndroid Build Coastguard Worker let username = caps.name("user").map(|m| m.as_str().to_string());
84*cf78ab8cSAndroid Build Coastguard Worker let password = caps.name("pass").map(|m| m.as_str().to_string());
85*cf78ab8cSAndroid Build Coastguard Worker
86*cf78ab8cSAndroid Build Coastguard Worker // Extract host, removing surrounding brackets if present
87*cf78ab8cSAndroid Build Coastguard Worker let hostname = caps
88*cf78ab8cSAndroid Build Coastguard Worker .name("host")
89*cf78ab8cSAndroid Build Coastguard Worker .ok_or(Error::MalformedConfigString)?
90*cf78ab8cSAndroid Build Coastguard Worker .as_str()
91*cf78ab8cSAndroid Build Coastguard Worker .trim_matches(|c| c == '[' || c == ']')
92*cf78ab8cSAndroid Build Coastguard Worker .to_string();
93*cf78ab8cSAndroid Build Coastguard Worker
94*cf78ab8cSAndroid Build Coastguard Worker let port = caps
95*cf78ab8cSAndroid Build Coastguard Worker .name("port")
96*cf78ab8cSAndroid Build Coastguard Worker .ok_or(Error::MalformedConfigString)?
97*cf78ab8cSAndroid Build Coastguard Worker .as_str()
98*cf78ab8cSAndroid Build Coastguard Worker .parse::<u16>()
99*cf78ab8cSAndroid Build Coastguard Worker .map_err(|_| Error::InvalidPortNumber)?;
100*cf78ab8cSAndroid Build Coastguard Worker
101*cf78ab8cSAndroid Build Coastguard Worker let host = (hostname, port)
102*cf78ab8cSAndroid Build Coastguard Worker .to_socket_addrs()
103*cf78ab8cSAndroid Build Coastguard Worker .map_err(|_| Error::InvalidHost)?
104*cf78ab8cSAndroid Build Coastguard Worker .next() // Take the first resolved address
105*cf78ab8cSAndroid Build Coastguard Worker .ok_or(Error::InvalidHost)?
106*cf78ab8cSAndroid Build Coastguard Worker .ip();
107*cf78ab8cSAndroid Build Coastguard Worker
108*cf78ab8cSAndroid Build Coastguard Worker Ok(ProxyConfig { protocol, username, password, addr: SocketAddr::from((host, port)) })
109*cf78ab8cSAndroid Build Coastguard Worker }
110*cf78ab8cSAndroid Build Coastguard Worker }
111*cf78ab8cSAndroid Build Coastguard Worker
112*cf78ab8cSAndroid Build Coastguard Worker /// Convert TcpStream to RawDescriptor (i32)
into_raw_descriptor(stream: TcpStream) -> RawDescriptor113*cf78ab8cSAndroid Build Coastguard Worker pub fn into_raw_descriptor(stream: TcpStream) -> RawDescriptor {
114*cf78ab8cSAndroid Build Coastguard Worker let std_stream = stream.into_std().expect("into_raw_descriptor's into_std() failed");
115*cf78ab8cSAndroid Build Coastguard Worker
116*cf78ab8cSAndroid Build Coastguard Worker std_stream.set_nonblocking(false).expect("non-blocking");
117*cf78ab8cSAndroid Build Coastguard Worker
118*cf78ab8cSAndroid Build Coastguard Worker // Use into_raw_fd for Unix to pass raw file descriptor to C
119*cf78ab8cSAndroid Build Coastguard Worker #[cfg(unix)]
120*cf78ab8cSAndroid Build Coastguard Worker return std_stream.into_raw_fd();
121*cf78ab8cSAndroid Build Coastguard Worker
122*cf78ab8cSAndroid Build Coastguard Worker // Use into_raw_socket for Windows to pass raw socket to C
123*cf78ab8cSAndroid Build Coastguard Worker #[cfg(windows)]
124*cf78ab8cSAndroid Build Coastguard Worker std_stream.into_raw_socket().try_into().expect("Failed to convert Raw Socket value into i32")
125*cf78ab8cSAndroid Build Coastguard Worker }
126*cf78ab8cSAndroid Build Coastguard Worker
127*cf78ab8cSAndroid Build Coastguard Worker #[cfg(test)]
128*cf78ab8cSAndroid Build Coastguard Worker mod tests {
129*cf78ab8cSAndroid Build Coastguard Worker use super::*;
130*cf78ab8cSAndroid Build Coastguard Worker use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
131*cf78ab8cSAndroid Build Coastguard Worker
132*cf78ab8cSAndroid Build Coastguard Worker #[test]
parse_configuration_string_success()133*cf78ab8cSAndroid Build Coastguard Worker fn parse_configuration_string_success() {
134*cf78ab8cSAndroid Build Coastguard Worker // Test data
135*cf78ab8cSAndroid Build Coastguard Worker let data = [
136*cf78ab8cSAndroid Build Coastguard Worker (
137*cf78ab8cSAndroid Build Coastguard Worker "127.0.0.1:8080",
138*cf78ab8cSAndroid Build Coastguard Worker ProxyConfig {
139*cf78ab8cSAndroid Build Coastguard Worker protocol: "http".to_owned(),
140*cf78ab8cSAndroid Build Coastguard Worker addr: SocketAddr::from((IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080)),
141*cf78ab8cSAndroid Build Coastguard Worker username: None,
142*cf78ab8cSAndroid Build Coastguard Worker password: None,
143*cf78ab8cSAndroid Build Coastguard Worker },
144*cf78ab8cSAndroid Build Coastguard Worker ),
145*cf78ab8cSAndroid Build Coastguard Worker (
146*cf78ab8cSAndroid Build Coastguard Worker "http://127.0.0.1:8080",
147*cf78ab8cSAndroid Build Coastguard Worker ProxyConfig {
148*cf78ab8cSAndroid Build Coastguard Worker protocol: "http".to_owned(),
149*cf78ab8cSAndroid Build Coastguard Worker addr: SocketAddr::from((IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080)),
150*cf78ab8cSAndroid Build Coastguard Worker username: None,
151*cf78ab8cSAndroid Build Coastguard Worker password: None,
152*cf78ab8cSAndroid Build Coastguard Worker },
153*cf78ab8cSAndroid Build Coastguard Worker ),
154*cf78ab8cSAndroid Build Coastguard Worker (
155*cf78ab8cSAndroid Build Coastguard Worker "https://127.0.0.1:8080",
156*cf78ab8cSAndroid Build Coastguard Worker ProxyConfig {
157*cf78ab8cSAndroid Build Coastguard Worker protocol: "https".to_owned(),
158*cf78ab8cSAndroid Build Coastguard Worker addr: SocketAddr::from((IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080)),
159*cf78ab8cSAndroid Build Coastguard Worker username: None,
160*cf78ab8cSAndroid Build Coastguard Worker password: None,
161*cf78ab8cSAndroid Build Coastguard Worker },
162*cf78ab8cSAndroid Build Coastguard Worker ),
163*cf78ab8cSAndroid Build Coastguard Worker (
164*cf78ab8cSAndroid Build Coastguard Worker "sock5://127.0.0.1:8080",
165*cf78ab8cSAndroid Build Coastguard Worker ProxyConfig {
166*cf78ab8cSAndroid Build Coastguard Worker protocol: "sock5".to_owned(),
167*cf78ab8cSAndroid Build Coastguard Worker addr: SocketAddr::from((IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080)),
168*cf78ab8cSAndroid Build Coastguard Worker username: None,
169*cf78ab8cSAndroid Build Coastguard Worker password: None,
170*cf78ab8cSAndroid Build Coastguard Worker },
171*cf78ab8cSAndroid Build Coastguard Worker ),
172*cf78ab8cSAndroid Build Coastguard Worker (
173*cf78ab8cSAndroid Build Coastguard Worker "user:[email protected]:3128",
174*cf78ab8cSAndroid Build Coastguard Worker ProxyConfig {
175*cf78ab8cSAndroid Build Coastguard Worker protocol: "http".to_owned(),
176*cf78ab8cSAndroid Build Coastguard Worker addr: SocketAddr::from((IpAddr::V4(Ipv4Addr::new(192, 168, 0, 18)), 3128)),
177*cf78ab8cSAndroid Build Coastguard Worker username: Some("user".to_string()),
178*cf78ab8cSAndroid Build Coastguard Worker password: Some("pass".to_string()),
179*cf78ab8cSAndroid Build Coastguard Worker },
180*cf78ab8cSAndroid Build Coastguard Worker ),
181*cf78ab8cSAndroid Build Coastguard Worker (
182*cf78ab8cSAndroid Build Coastguard Worker "https://[::1]:7000",
183*cf78ab8cSAndroid Build Coastguard Worker ProxyConfig {
184*cf78ab8cSAndroid Build Coastguard Worker protocol: "https".to_owned(),
185*cf78ab8cSAndroid Build Coastguard Worker addr: SocketAddr::from((
186*cf78ab8cSAndroid Build Coastguard Worker IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
187*cf78ab8cSAndroid Build Coastguard Worker 7000,
188*cf78ab8cSAndroid Build Coastguard Worker )),
189*cf78ab8cSAndroid Build Coastguard Worker username: None,
190*cf78ab8cSAndroid Build Coastguard Worker password: None,
191*cf78ab8cSAndroid Build Coastguard Worker },
192*cf78ab8cSAndroid Build Coastguard Worker ),
193*cf78ab8cSAndroid Build Coastguard Worker (
194*cf78ab8cSAndroid Build Coastguard Worker "[::1]:7000",
195*cf78ab8cSAndroid Build Coastguard Worker ProxyConfig {
196*cf78ab8cSAndroid Build Coastguard Worker protocol: "http".to_owned(),
197*cf78ab8cSAndroid Build Coastguard Worker addr: SocketAddr::from((
198*cf78ab8cSAndroid Build Coastguard Worker IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
199*cf78ab8cSAndroid Build Coastguard Worker 7000,
200*cf78ab8cSAndroid Build Coastguard Worker )),
201*cf78ab8cSAndroid Build Coastguard Worker username: None,
202*cf78ab8cSAndroid Build Coastguard Worker password: None,
203*cf78ab8cSAndroid Build Coastguard Worker },
204*cf78ab8cSAndroid Build Coastguard Worker ),
205*cf78ab8cSAndroid Build Coastguard Worker ];
206*cf78ab8cSAndroid Build Coastguard Worker
207*cf78ab8cSAndroid Build Coastguard Worker // TODO: Mock DNS server to to test hostname. e.g. "proxy.example.com:3000".
208*cf78ab8cSAndroid Build Coastguard Worker for (input, expected) in data {
209*cf78ab8cSAndroid Build Coastguard Worker let result = ProxyConfig::from_string(input);
210*cf78ab8cSAndroid Build Coastguard Worker assert!(
211*cf78ab8cSAndroid Build Coastguard Worker result.is_ok(),
212*cf78ab8cSAndroid Build Coastguard Worker "Unexpected error {} for input: {}",
213*cf78ab8cSAndroid Build Coastguard Worker result.err().unwrap(),
214*cf78ab8cSAndroid Build Coastguard Worker input
215*cf78ab8cSAndroid Build Coastguard Worker );
216*cf78ab8cSAndroid Build Coastguard Worker let result = result.ok().unwrap();
217*cf78ab8cSAndroid Build Coastguard Worker assert_eq!(result.addr, expected.addr, "For input: {}", input);
218*cf78ab8cSAndroid Build Coastguard Worker assert_eq!(result.username, expected.username, "For input: {}", input);
219*cf78ab8cSAndroid Build Coastguard Worker assert_eq!(result.password, expected.password, "For input: {}", input);
220*cf78ab8cSAndroid Build Coastguard Worker }
221*cf78ab8cSAndroid Build Coastguard Worker }
222*cf78ab8cSAndroid Build Coastguard Worker
223*cf78ab8cSAndroid Build Coastguard Worker #[test]
parse_configuration_string_with_errors()224*cf78ab8cSAndroid Build Coastguard Worker fn parse_configuration_string_with_errors() {
225*cf78ab8cSAndroid Build Coastguard Worker let data = [
226*cf78ab8cSAndroid Build Coastguard Worker ("http://", Error::MalformedConfigString),
227*cf78ab8cSAndroid Build Coastguard Worker ("", Error::MalformedConfigString),
228*cf78ab8cSAndroid Build Coastguard Worker ("256.0.0.1:8080", Error::InvalidHost),
229*cf78ab8cSAndroid Build Coastguard Worker ("127.0.0.1:foo", Error::MalformedConfigString),
230*cf78ab8cSAndroid Build Coastguard Worker ("127.0.0.1:-2", Error::MalformedConfigString),
231*cf78ab8cSAndroid Build Coastguard Worker ("127.0.0.1:100000", Error::InvalidPortNumber),
232*cf78ab8cSAndroid Build Coastguard Worker ("127.0.0.1", Error::MalformedConfigString),
233*cf78ab8cSAndroid Build Coastguard Worker ("http:127.0.0.1:8080", Error::MalformedConfigString),
234*cf78ab8cSAndroid Build Coastguard Worker ("::1:8080", Error::MalformedConfigString),
235*cf78ab8cSAndroid Build Coastguard Worker ("user@pass:127.0.0.1:8080", Error::MalformedConfigString),
236*cf78ab8cSAndroid Build Coastguard Worker ("[email protected]:8080", Error::MalformedConfigString),
237*cf78ab8cSAndroid Build Coastguard Worker ("proxy.example.com:7000", Error::InvalidHost),
238*cf78ab8cSAndroid Build Coastguard Worker ("[::1}:7000", Error::MalformedConfigString),
239*cf78ab8cSAndroid Build Coastguard Worker ];
240*cf78ab8cSAndroid Build Coastguard Worker
241*cf78ab8cSAndroid Build Coastguard Worker for (input, expected_error) in data {
242*cf78ab8cSAndroid Build Coastguard Worker let result = ProxyConfig::from_string(input);
243*cf78ab8cSAndroid Build Coastguard Worker assert_eq!(
244*cf78ab8cSAndroid Build Coastguard Worker result.err().unwrap().to_string(),
245*cf78ab8cSAndroid Build Coastguard Worker expected_error.to_string(),
246*cf78ab8cSAndroid Build Coastguard Worker "Expected an error for input: {}",
247*cf78ab8cSAndroid Build Coastguard Worker input
248*cf78ab8cSAndroid Build Coastguard Worker );
249*cf78ab8cSAndroid Build Coastguard Worker }
250*cf78ab8cSAndroid Build Coastguard Worker }
251*cf78ab8cSAndroid Build Coastguard Worker }
252