Skip to main content

powhttp_sdk/
proxy_server.rs

1use std::net::SocketAddr;
2use serde::{Deserialize, Serialize};
3use ulid::Ulid;
4use url::Url;
5use crate::sessions::Headers;
6
7/// Context passed to a connect handler when a client connects to the powhttp proxy server.
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct ConnectContext {
11    pub session_id: Ulid,
12    pub target_addr: TargetAddress,
13    pub client_addr: SocketAddr,
14    pub process: Option<ProcessInfo>,
15    pub request: ConnectRequest,
16}
17
18/// A host–port pair identifying the target server.
19#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
20#[serde(rename_all = "camelCase")]
21pub struct TargetAddress {
22    pub host: String,
23    pub port: u16,
24}
25
26impl TargetAddress {
27    pub fn new(host: impl Into<String>, port: u16) -> Self {
28        Self { host: host.into(), port }
29    }
30}
31
32/// Information about the process that initiated the connection.
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub struct ProcessInfo {
36    pub pid: u32,
37    pub name: Option<String>,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41#[serde(rename_all = "camelCase")]
42pub struct Socks4Request {
43    pub user_id: String,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
47#[serde(rename_all = "camelCase")]
48pub struct HttpConnectRequest {
49    pub target: String,
50    pub http_version: String,
51    pub headers: Headers,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55#[serde(rename_all = "camelCase")]
56pub struct HttpDirectRequest {
57    pub method: String,
58    pub path: String,
59    pub http_version: String,
60    pub headers: Headers,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64#[serde(rename_all = "camelCase")]
65pub struct HttpProxyRequest {
66    pub method: String,
67    pub url: Url,
68    pub http_version: String,
69    pub headers: Headers,
70}
71
72/// The protocol-specific request that initiated the proxy connection.
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
74#[serde(tag = "type", rename_all = "snake_case")]
75pub enum ConnectRequest {
76    #[serde(rename = "socks4")]
77    Socks4(Socks4Request),
78    #[serde(rename = "socks5")]
79    Socks5,
80    HttpConnect(HttpConnectRequest),
81    TlsSni,
82    HttpDirect(HttpDirectRequest),
83    HttpProxy(HttpProxyRequest),
84}
85
86/// Options returned when accepting a connection.
87///
88/// All fields default to `None`, which tells the proxy to use its own defaults.
89#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
90#[serde(rename_all = "camelCase")]
91pub struct AcceptOptions {
92    pub target_addr: Option<TargetAddress>,
93    pub proxy: Option<Proxy>,
94    pub record: Option<bool>,
95    pub intercept_tls: Option<bool>,
96}
97
98/// The result returned by a connect handler to accept or reject the connection.
99///
100/// ```
101/// use powhttp_sdk::proxy_server::ConnectResult;
102///
103/// let result = ConnectResult::accept();
104/// ```
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
106#[serde(tag = "type", rename_all = "snake_case")]
107pub enum ConnectResult {
108    Accept(AcceptOptions),
109    Reject,
110}
111
112impl ConnectResult {
113    /// Accept the connection with default settings.
114    pub fn accept() -> Self {
115        ConnectResult::Accept(AcceptOptions::default())
116    }
117
118    /// Accept the connection and route it through an upstream proxy.
119    pub fn use_proxy(proxy: Option<Proxy>) -> Self {
120        ConnectResult::Accept(
121            AcceptOptions { proxy, ..Default::default() }
122        )
123    }
124    
125    /// Reject the connection.
126    pub fn reject() -> Self {
127        ConnectResult::Reject
128    }
129}
130
131impl From<AcceptOptions> for ConnectResult {
132    fn from(accept: AcceptOptions) -> Self {
133        ConnectResult::Accept(accept)
134    }
135}
136
137/// Upstream proxy protocol.
138#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
139#[serde(rename_all = "lowercase")]
140pub enum ProxyType {
141    #[default]
142    Http,
143    Https,
144    Socks4,
145    Socks4a,
146    Socks5,
147    Socks5h,
148}
149
150/// An upstream proxy to route connections through.
151///
152/// Use one of the constructor methods ([`http`](Proxy::http), [`socks5`](Proxy::socks5), etc.)
153/// to create an instance.
154#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
155#[serde(rename_all = "camelCase")]
156pub struct Proxy {
157    proxy_type: ProxyType,
158    host: String,
159    port: u16,
160    username: Option<String>,
161    password: Option<String>,
162}
163
164impl Proxy {
165    pub fn http(host: String, port: u16, username: Option<String>, password: Option<String>) -> Self {
166        Self { 
167            proxy_type: ProxyType::Http, 
168            host: host.into(), 
169            port, 
170            username, 
171            password 
172        }
173    }
174
175    pub fn https(host: String, port: u16, username: Option<String>, password: Option<String>) -> Self {
176        Self { 
177            proxy_type: ProxyType::Https, 
178            host: host.into(), 
179            port, 
180            username, 
181            password 
182        }
183    }
184
185    pub fn socks4(host: String, port: u16, user_id: Option<String>) -> Self {
186        Self { 
187            proxy_type: ProxyType::Socks4, 
188            host: host.into(), 
189            port, 
190            username: user_id, 
191            password: None 
192        }
193    }
194
195    pub fn socks4a(host: String, port: u16, user_id: Option<String>) -> Self {
196        Self { 
197            proxy_type: ProxyType::Socks4a, 
198            host: host.into(), 
199            port, 
200            username: user_id, 
201            password: None 
202        }
203    }
204
205    pub fn socks5(host: String, port: u16, username: Option<String>, password: Option<String>) -> Self {
206        Self { 
207            proxy_type: ProxyType::Socks5, 
208            host: host.into(), 
209            port, 
210            username, 
211            password 
212        }
213    }
214
215    pub fn socks5h(host: String, port: u16, username: Option<String>, password: Option<String>) -> Self {
216        Self { 
217            proxy_type: ProxyType::Socks5h,
218            host: host.into(), 
219            port, 
220            username, 
221            password
222        }
223    }
224}