rama_proxy/proxydb/
internal.rs

1use super::{ProxyFilter, StringFilter};
2use rama_net::{
3    address::ProxyAddress,
4    asn::Asn,
5    transport::{TransportContext, TransportProtocol},
6};
7use rama_utils::str::NonEmptyString;
8use serde::{Deserialize, Serialize};
9
10#[cfg(feature = "memory-db")]
11use venndb::VennDB;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
14#[cfg_attr(feature = "memory-db", derive(VennDB))]
15#[cfg_attr(feature = "memory-db", venndb(validator = proxydb_insert_validator))]
16/// The selected proxy to use to connect to the proxy.
17pub struct Proxy {
18    #[cfg_attr(feature = "memory-db", venndb(key))]
19    /// Unique identifier of the proxy.
20    pub id: NonEmptyString,
21
22    /// The address to be used to connect to the proxy, including credentials if needed.
23    pub address: ProxyAddress,
24
25    /// True if the proxy supports TCP connections.
26    pub tcp: bool,
27
28    /// True if the proxy supports UDP connections.
29    pub udp: bool,
30
31    /// http-proxy enabled
32    pub http: bool,
33
34    /// https-proxy enabled
35    pub https: bool,
36
37    /// socks5-proxy enabled
38    pub socks5: bool,
39
40    /// socks5h-proxy enabled
41    pub socks5h: bool,
42
43    /// Proxy is located in a datacenter.
44    pub datacenter: bool,
45
46    /// Proxy's IP is labeled as residential.
47    pub residential: bool,
48
49    /// Proxy's IP originates from a mobile network.
50    pub mobile: bool,
51
52    #[cfg_attr(feature = "memory-db", venndb(filter, any))]
53    /// Pool ID of the proxy.
54    pub pool_id: Option<StringFilter>,
55
56    #[cfg_attr(feature = "memory-db", venndb(filter, any))]
57    /// Continent of the proxy.
58    pub continent: Option<StringFilter>,
59
60    #[cfg_attr(feature = "memory-db", venndb(filter, any))]
61    /// Country of the proxy.
62    pub country: Option<StringFilter>,
63
64    #[cfg_attr(feature = "memory-db", venndb(filter, any))]
65    /// State of the proxy.
66    pub state: Option<StringFilter>,
67
68    #[cfg_attr(feature = "memory-db", venndb(filter, any))]
69    /// City of the proxy.
70    pub city: Option<StringFilter>,
71
72    #[cfg_attr(feature = "memory-db", venndb(filter, any))]
73    /// Mobile carrier of the proxy.
74    pub carrier: Option<StringFilter>,
75
76    #[cfg_attr(feature = "memory-db", venndb(filter, any))]
77    ///  Autonomous System Number (ASN).
78    pub asn: Option<Asn>,
79}
80
81#[cfg(feature = "memory-db")]
82/// Validate the proxy is valid according to rules that are not enforced by the type system.
83fn proxydb_insert_validator(proxy: &Proxy) -> bool {
84    (proxy.datacenter || proxy.residential || proxy.mobile)
85        && (((proxy.http || proxy.https) && proxy.tcp)
86            || ((proxy.socks5 || proxy.socks5h) && (proxy.tcp || proxy.udp)))
87}
88
89impl Proxy {
90    /// Check if the proxy is a match for the given[`TransportContext`] and [`ProxyFilter`].
91    pub fn is_match(&self, ctx: &TransportContext, filter: &ProxyFilter) -> bool {
92        if let Some(id) = &filter.id {
93            if id != &self.id {
94                return false;
95            }
96        }
97
98        match ctx.protocol {
99            TransportProtocol::Udp => {
100                if !(self.socks5 || self.socks5h) || !self.udp {
101                    return false;
102                }
103            }
104            TransportProtocol::Tcp => {
105                if !self.tcp || !(self.http || self.https || self.socks5 || self.socks5h) {
106                    return false;
107                }
108            }
109        }
110
111        filter
112            .continent
113            .as_ref()
114            .map(|c| {
115                let continent = self.continent.as_ref();
116                c.iter().any(|c| Some(c) == continent)
117            })
118            .unwrap_or(true)
119            && filter
120                .country
121                .as_ref()
122                .map(|c| {
123                    let country = self.country.as_ref();
124                    c.iter().any(|c| Some(c) == country)
125                })
126                .unwrap_or(true)
127            && filter
128                .state
129                .as_ref()
130                .map(|s| {
131                    let state = self.state.as_ref();
132                    s.iter().any(|s| Some(s) == state)
133                })
134                .unwrap_or(true)
135            && filter
136                .city
137                .as_ref()
138                .map(|c| {
139                    let city = self.city.as_ref();
140                    c.iter().any(|c| Some(c) == city)
141                })
142                .unwrap_or(true)
143            && filter
144                .pool_id
145                .as_ref()
146                .map(|p| {
147                    let pool_id = self.pool_id.as_ref();
148                    p.iter().any(|p| Some(p) == pool_id)
149                })
150                .unwrap_or(true)
151            && filter
152                .carrier
153                .as_ref()
154                .map(|c| {
155                    let carrier = self.carrier.as_ref();
156                    c.iter().any(|c| Some(c) == carrier)
157                })
158                .unwrap_or(true)
159            && filter
160                .asn
161                .as_ref()
162                .map(|a| {
163                    let asn = self.asn.as_ref();
164                    a.iter().any(|a| Some(a) == asn)
165                })
166                .unwrap_or(true)
167            && filter
168                .datacenter
169                .map(|d| d == self.datacenter)
170                .unwrap_or(true)
171            && filter
172                .residential
173                .map(|r| r == self.residential)
174                .unwrap_or(true)
175            && filter.mobile.map(|m| m == self.mobile).unwrap_or(true)
176    }
177}
178
179#[cfg(all(feature = "csv", feature = "memory-db"))]
180#[cfg(test)]
181mod tests {
182    use super::*;
183    use crate::proxydb::csv::{parse_csv_row, ProxyCsvRowReader};
184    use crate::proxydb::internal::{ProxyDB, ProxyDBErrorKind};
185    use itertools::Itertools;
186
187    #[test]
188    fn test_proxy_db_happy_path_basic() {
189        let mut db = ProxyDB::new();
190        let proxy = parse_csv_row("id,1,,1,,,,1,,,authority,,,,,,,,").unwrap();
191        db.append(proxy).unwrap();
192
193        let mut query = db.query();
194        query.tcp(true).http(true);
195
196        let proxy = query.execute().unwrap().any();
197        assert_eq!(proxy.id, "id");
198    }
199
200    #[tokio::test]
201    async fn test_proxy_db_happy_path_any_country() {
202        let mut db = ProxyDB::new();
203        let mut reader = ProxyCsvRowReader::raw(
204            "1,1,,1,,,,1,,,authority,,,US,,,,,\n2,1,,1,,,,1,,,authority,,,*,,,,,",
205        );
206        while let Some(proxy) = reader.next().await.unwrap() {
207            db.append(proxy).unwrap();
208        }
209
210        let mut query = db.query();
211        query.tcp(true).http(true).country("US");
212
213        let proxies: Vec<_> = query
214            .execute()
215            .unwrap()
216            .iter()
217            .sorted_by(|a, b| a.id.cmp(&b.id))
218            .collect();
219        assert_eq!(proxies.len(), 2);
220        assert_eq!(proxies[0].id, "1");
221        assert_eq!(proxies[1].id, "2");
222
223        query.reset().country("BE");
224        let proxies: Vec<_> = query
225            .execute()
226            .unwrap()
227            .iter()
228            .sorted_by(|a, b| a.id.cmp(&b.id))
229            .collect();
230        assert_eq!(proxies.len(), 1);
231        assert_eq!(proxies[0].id, "2");
232    }
233
234    #[tokio::test]
235    async fn test_proxy_db_happy_path_any_country_city() {
236        let mut db = ProxyDB::new();
237        let mut reader = ProxyCsvRowReader::raw(
238            "1,1,,1,,,,1,,,authority,,,US,,New York,,,\n2,1,,1,,,,1,,,authority,,,*,,*,,,",
239        );
240        while let Some(proxy) = reader.next().await.unwrap() {
241            db.append(proxy).unwrap();
242        }
243
244        let mut query = db.query();
245        query.tcp(true).http(true).country("US").city("new york");
246
247        let proxies: Vec<_> = query
248            .execute()
249            .unwrap()
250            .iter()
251            .sorted_by(|a, b| a.id.cmp(&b.id))
252            .collect();
253        assert_eq!(proxies.len(), 2);
254        assert_eq!(proxies[0].id, "1");
255        assert_eq!(proxies[1].id, "2");
256
257        query.reset().country("US").city("Los Angeles");
258        let proxies: Vec<_> = query
259            .execute()
260            .unwrap()
261            .iter()
262            .sorted_by(|a, b| a.id.cmp(&b.id))
263            .collect();
264        assert_eq!(proxies.len(), 1);
265        assert_eq!(proxies[0].id, "2");
266
267        query.reset().city("Ghent");
268        let proxies: Vec<_> = query
269            .execute()
270            .unwrap()
271            .iter()
272            .sorted_by(|a, b| a.id.cmp(&b.id))
273            .collect();
274        assert_eq!(proxies.len(), 1);
275        assert_eq!(proxies[0].id, "2");
276    }
277
278    #[tokio::test]
279    async fn test_proxy_db_happy_path_specific_asn_within_continents() {
280        let mut db = ProxyDB::new();
281        let mut reader = ProxyCsvRowReader::raw(
282            "1,1,,1,,,,1,,,authority,,europe,BE,,Brussels,,1348,\n2,1,,1,,,,1,,,authority,,asia,CN,,Shenzen,,1348,\n3,1,,1,,,,1,,,authority,,asia,CN,,Peking,,42,",
283        );
284        while let Some(proxy) = reader.next().await.unwrap() {
285            db.append(proxy).unwrap();
286        }
287
288        let mut query = db.query();
289        query
290            .tcp(true)
291            .http(true)
292            .continent("europe")
293            .continent("asia")
294            .asn(Asn::from_static(1348));
295
296        let proxies: Vec<_> = query
297            .execute()
298            .unwrap()
299            .iter()
300            .sorted_by(|a, b| a.id.cmp(&b.id))
301            .collect();
302        assert_eq!(proxies.len(), 2);
303        assert_eq!(proxies[0].id, "1");
304        assert_eq!(proxies[1].id, "2");
305
306        query.reset().asn(Asn::from_static(42));
307        let proxies: Vec<_> = query
308            .execute()
309            .unwrap()
310            .iter()
311            .sorted_by(|a, b| a.id.cmp(&b.id))
312            .collect();
313        assert_eq!(proxies.len(), 1);
314        assert_eq!(proxies[0].id, "3");
315    }
316
317    #[tokio::test]
318    async fn test_proxy_db_happy_path_states() {
319        let mut db = ProxyDB::new();
320        let mut reader = ProxyCsvRowReader::raw(
321            "1,1,,1,,,,1,,,authority,,,US,Texas,,,,\n2,1,,1,,,,1,,,authority,,,US,New York,,,,\n3,1,,1,,,,1,,,authority,,,US,California,,,,",
322        );
323        while let Some(proxy) = reader.next().await.unwrap() {
324            db.append(proxy).unwrap();
325        }
326
327        let mut query = db.query();
328        query.tcp(true).http(true).state("texas").state("new york");
329
330        let proxies: Vec<_> = query
331            .execute()
332            .unwrap()
333            .iter()
334            .sorted_by(|a, b| a.id.cmp(&b.id))
335            .collect();
336        assert_eq!(proxies.len(), 2);
337        assert_eq!(proxies[0].id, "1");
338        assert_eq!(proxies[1].id, "2");
339
340        query.reset().state("california");
341        let proxies: Vec<_> = query
342            .execute()
343            .unwrap()
344            .iter()
345            .sorted_by(|a, b| a.id.cmp(&b.id))
346            .collect();
347        assert_eq!(proxies.len(), 1);
348        assert_eq!(proxies[0].id, "3");
349    }
350
351    #[tokio::test]
352    async fn test_proxy_db_invalid_row_cases() {
353        let mut db = ProxyDB::new();
354        let mut reader = ProxyCsvRowReader::raw("id1,1,,,,,,,,,authority,,,,,,,\nid2,,1,,,,,,,,authority,,,,,,,\nid3,,1,1,,,,,,,authority,,,,,,,\nid4,,1,1,,,,,1,,authority,,,,,,,\nid5,,1,1,,,,,1,,authority,,,,,,,");
355        while let Some(proxy) = reader.next().await.unwrap() {
356            assert_eq!(
357                ProxyDBErrorKind::InvalidRow,
358                db.append(proxy).unwrap_err().kind
359            );
360        }
361    }
362}