Skip to main content

nurtex_proxy/
proxy.rs

1use std::time::Duration;
2
3use bytes::{BufMut, BytesMut};
4use tokio::io::{AsyncReadExt, AsyncWriteExt};
5use tokio::net::TcpStream;
6use tokio::time::timeout;
7
8use crate::ProxyAuth;
9use crate::error::{ErrorName, ProxyError};
10use crate::result::ProxyResult;
11
12/// Структура SOCKS5 / SOCKS4 прокси.
13///
14/// ## Примеры
15///
16/// ```rust, ignore
17/// use nurtex_proxy::{Proxy, ProxyAuth, ProxyType};
18///
19/// // Пример SOCKS5 прокси без авторизации
20/// let proxy = Proxy::new("PROXY_IP:PROXY_PORT", ProxyType::Socks5);
21///
22/// // Пример SOCKS5 с авторизацией
23/// let auth = ProxyAuth::new("USERNAME", "PASSWORD");
24/// let proxy = Proxy::new_with_auth("PROXY_IP:PROXY_PORT", ProxyType::Socks5, auth);
25///
26/// // Пример SOCKS4 с авторизацией
27/// let auth = ProxyAuth::new("USER_ID", ""); // В SOCKS4 не используется пароль
28/// let proxy = Proxy::new_with_auth("PROXY_IP:PROXY_PORT", ProxyType::Socks4, auth);
29/// ```
30#[derive(Debug, Clone)]
31pub struct Proxy {
32  proxy_type: ProxyType,
33  proxy_address: String,
34  timeout: u64,
35  auth: Option<ProxyAuth>,
36}
37
38/// Тип прокси
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum ProxyType {
41  Socks5,
42  Socks4,
43}
44
45/// Вспомогательная функция записи данных в поток
46async fn write_all_to(stream: &mut TcpStream, buffer: Vec<u8>) -> ProxyResult<()> {
47  match timeout(Duration::from_secs(10), stream.write_all(&buffer)).await {
48    Ok(result) => match result {
49      Ok(_) => ProxyResult::Ok(()),
50      Err(e) => ProxyResult::Err(ProxyError::new(ErrorName::StreamError, e.to_string())),
51    },
52    Err(_) => ProxyResult::Err(ProxyError::new(ErrorName::StreamError, "failed to write buffer to stream")),
53  }
54}
55
56/// Вспомогательная функция чтения данных из потока
57async fn read_exact_from<'a>(stream: &mut TcpStream, buffer: &'a mut [u8]) -> ProxyResult<()> {
58  match timeout(Duration::from_secs(10), stream.read_exact(buffer)).await {
59    Ok(result) => match result {
60      Ok(_) => ProxyResult::Ok(()),
61      Err(e) => ProxyResult::Err(ProxyError::new(ErrorName::StreamError, e.to_string())),
62    },
63    Err(_) => ProxyResult::Err(ProxyError::new(ErrorName::StreamError, "failed to read buffer from stream")),
64  }
65}
66
67impl From<String> for Proxy {
68  fn from(value: String) -> Self {
69    let split = value.split("://").collect::<Vec<&str>>();
70    let (protocol, proxy) = (split.get(0).unwrap_or(&"socks5"), split.get(1).unwrap_or(&"127.0.0.1"));
71
72    Self {
73      proxy_address: (*proxy).to_string(),
74      proxy_type: match *protocol {
75        "socks5" => ProxyType::Socks5,
76        "socks4" => ProxyType::Socks4,
77        _ => ProxyType::Socks5,
78      },
79      timeout: 20000,
80      auth: None,
81    }
82  }
83}
84
85impl From<&str> for Proxy {
86  fn from(value: &str) -> Self {
87    let split = value.split("://").collect::<Vec<&str>>();
88    let (protocol, proxy) = (split.get(0).unwrap_or(&"socks5"), split.get(1).unwrap_or(&"127.0.0.1"));
89
90    Self {
91      proxy_address: (*proxy).to_string(),
92      proxy_type: match *protocol {
93        "socks5" => ProxyType::Socks5,
94        "socks4" => ProxyType::Socks4,
95        _ => ProxyType::Socks5,
96      },
97      timeout: 20000,
98      auth: None,
99    }
100  }
101}
102
103impl Proxy {
104  /// Метод создания нового прокси
105  pub fn new(proxy_address: impl Into<String>, proxy_type: ProxyType) -> Self {
106    Self {
107      proxy_address: proxy_address.into(),
108      proxy_type: proxy_type,
109      timeout: 20000,
110      auth: None,
111    }
112  }
113
114  /// Метод создания нового прокси с авторизацией
115  pub fn new_with_auth(proxy_address: impl Into<String>, proxy_type: ProxyType, auth: ProxyAuth) -> Self {
116    Self {
117      proxy_address: proxy_address.into(),
118      proxy_type: proxy_type,
119      timeout: 20000,
120      auth: Some(auth),
121    }
122  }
123
124  /// Метод установки таймаута подключения к прокси
125  pub fn with_timeout(mut self, timeout: u64) -> Self {
126    self.timeout = timeout;
127    self
128  }
129
130  /// Метод установки типа прокси
131  pub fn with_proxy_type(mut self, proxy_type: ProxyType) -> Self {
132    self.proxy_type = proxy_type;
133    self
134  }
135
136  /// Метод установки авторизации
137  pub fn with_auth(mut self, auth: ProxyAuth) -> Self {
138    self.auth = Some(auth);
139    self
140  }
141
142  /// Метод попытки создания соединения с прокси
143  pub async fn is_available(&self) -> bool {
144    match timeout(Duration::from_millis(self.timeout), TcpStream::connect(&self.proxy_address)).await {
145      Ok(result) => match result {
146        Ok(_) => return true,
147        Err(_) => return false,
148      },
149      Err(_) => return false,
150    }
151  }
152
153  /// Метод получения IP прокси
154  pub fn get_ip(&self) -> Option<String> {
155    if let Some(ip) = self.proxy_address.split(":").collect::<Vec<&str>>().get(0) {
156      Some(ip.to_string())
157    } else {
158      None
159    }
160  }
161
162  /// Метод подключения к прокси
163  pub async fn connect(&self, target_host: impl Into<String>, target_port: u16) -> ProxyResult<TcpStream> {
164    let mut stream = match timeout(Duration::from_millis(self.timeout), TcpStream::connect(&self.proxy_address)).await {
165      Ok(result) => match result {
166        Ok(s) => s,
167        Err(_) => return ProxyResult::Err(ProxyError::new(ErrorName::NotConnected, "could not connect to specified server")),
168      },
169      Err(_) => return ProxyResult::Err(ProxyError::new(ErrorName::Timeout, "failed to connect to server within specified time")),
170    };
171
172    match self.proxy_type {
173      ProxyType::Socks5 => self.connect_socks5(&mut stream, target_host.into(), target_port).await?,
174      ProxyType::Socks4 => self.connect_socks4(&mut stream, target_host.into(), target_port).await?,
175    }
176
177    ProxyResult::Ok(stream)
178  }
179
180  /// Метод создания подключения с SOCKS5 прокси
181  async fn connect_socks5(&self, stream: &mut TcpStream, target_host: String, target_port: u16) -> ProxyResult<()> {
182    let greet = if self.auth.is_some() { vec![0x05, 0x02, 0x00, 0x02] } else { vec![0x05, 0x01, 0x00] };
183
184    write_all_to(stream, greet).await?;
185
186    let mut response = [0u8; 2];
187
188    read_exact_from(stream, &mut response).await?;
189
190    if response[0] != 0x05 {
191      return ProxyResult::Err(ProxyError::new(ErrorName::InvalidVersion, "invalid response version"));
192    }
193
194    match response[1] {
195      0x00 => {}
196      0x02 => {
197        if let Some(auth) = &self.auth {
198          let username = auth.username();
199          let password = auth.password();
200
201          if username.len() > 255 || password.len() > 255 {
202            return Err(ProxyError::new(ErrorName::InvalidData, "username or password is too long"));
203          }
204
205          let mut buffer = BytesMut::with_capacity(2 + username.len() + password.len());
206          buffer.put_u8(0x01);
207          buffer.put_u8(username.len() as u8);
208          buffer.put_slice(username.as_bytes());
209          buffer.put_u8(password.len() as u8);
210          buffer.put_slice(password.as_bytes());
211
212          write_all_to(stream, buffer.into()).await?;
213
214          let mut resp = [0u8; 2];
215
216          read_exact_from(stream, &mut resp).await?;
217
218          if resp[0] != 0x01 {
219            return Err(ProxyError::new(ErrorName::AuthFailed, "invalid authorization version"));
220          }
221
222          if resp[1] != 0x00 {
223            return Err(ProxyError::new(ErrorName::AuthFailed, "authorization failed (possibly incorrect password or username)"));
224          }
225        } else {
226          return ProxyResult::Err(ProxyError::new(ErrorName::AuthFailed, "proxy requires authorization (username, password)"));
227        }
228      }
229      _ => return ProxyResult::Err(ProxyError::new(ErrorName::Unsupported, "unsupported authorization method")),
230    }
231
232    let mut request = BytesMut::with_capacity(512);
233    request.put_u8(0x05);
234    request.put_u8(0x01);
235    request.put_u8(0x00);
236
237    if let Ok(ipv4) = target_host.parse::<std::net::Ipv4Addr>() {
238      request.put_u8(0x01);
239      request.put_slice(&ipv4.octets());
240    } else if let Ok(ipv6) = target_host.parse::<std::net::Ipv6Addr>() {
241      request.put_u8(0x04);
242      request.put_slice(&ipv6.octets());
243    } else {
244      request.put_u8(0x03);
245      let host_bytes = target_host.as_bytes();
246
247      if host_bytes.len() > 255 {
248        return ProxyResult::Err(ProxyError::new(ErrorName::InvalidData, "target host is too long"));
249      }
250
251      request.put_u8(host_bytes.len() as u8);
252      request.put_slice(host_bytes);
253    }
254
255    request.put_u16(target_port);
256
257    write_all_to(stream, request.into()).await?;
258
259    let mut header = [0u8; 4];
260
261    read_exact_from(stream, &mut header).await?;
262
263    if header[0] != 0x05 {
264      return ProxyResult::Err(ProxyError::new(ErrorName::InvalidVersion, "invalid response version"));
265    }
266
267    let rep = header[1];
268
269    if rep != 0x00 {
270      return ProxyResult::Err(ProxyError::new(ErrorName::NotConnected, format!("proxy connection error (rep: 0x{:02x})", rep)));
271    }
272
273    let atyp = header[3];
274
275    match atyp {
276      0x01 => {
277        let mut addr = [0u8; 4 + 2];
278        read_exact_from(stream, &mut addr).await?;
279      }
280      0x04 => {
281        let mut addr = [0u8; 16 + 2];
282        read_exact_from(stream, &mut addr).await?;
283      }
284      0x03 => {
285        let mut len = [0u8; 1];
286        read_exact_from(stream, &mut len).await?;
287        let mut rest = vec![0u8; len[0] as usize + 2];
288        read_exact_from(stream, &mut rest).await?;
289      }
290      _ => return ProxyResult::Err(ProxyError::new(ErrorName::InvalidData, format!("unknown address type in reply: 0x{:02x}", atyp))),
291    }
292
293    ProxyResult::Ok(())
294  }
295
296  /// Метод создания подключения с SOCKS4 прокси
297  async fn connect_socks4(&self, stream: &mut TcpStream, target_host: String, target_port: u16) -> ProxyResult<()> {
298    let mut request = BytesMut::with_capacity(512);
299    request.put_u8(0x04);
300    request.put_u8(0x01);
301    request.put_u16(target_port);
302
303    if let Ok(ipv4) = target_host.parse::<std::net::Ipv4Addr>() {
304      request.put_slice(&ipv4.octets());
305
306      if let Some(auth) = &self.auth {
307        request.put_slice(auth.username().as_bytes());
308      } else {
309        request.put_u8(0x00);
310      }
311    } else {
312      request.put_slice(&[0x00, 0x00, 0x00, 0x01]);
313
314      if let Some(auth) = &self.auth {
315        request.put_slice(auth.username().as_bytes());
316      } else {
317        request.put_u8(0x00);
318      }
319
320      if target_host.len() > 255 {
321        return Err(ProxyError::new(ErrorName::InvalidData, "target host is too long"));
322      }
323
324      request.put_slice(target_host.as_bytes());
325      request.put_u8(0x00);
326    }
327
328    write_all_to(stream, request.into()).await?;
329
330    let mut response = [0u8; 8];
331    read_exact_from(stream, &mut response).await?;
332
333    if response[0] != 0x00 {
334      return Err(ProxyError::new(ErrorName::InvalidVersion, "invalid response version"));
335    }
336
337    match response[1] {
338      0x5a => Ok(()),
339      0x5b => Err(ProxyError::new(ErrorName::NotConnected, "request rejected or failed")),
340      0x5c => Err(ProxyError::new(ErrorName::AuthFailed, "client not identd-authenticated")),
341      0x5d => Err(ProxyError::new(ErrorName::AuthFailed, "client identd-user mismatch")),
342      _ => Err(ProxyError::new(ErrorName::Unsupported, format!("unknown response code 0x{:02x}", response[1]))),
343    }
344  }
345}
346
347#[cfg(test)]
348mod tests {
349  use std::io::{Error, ErrorKind};
350
351  use tokio::io::{AsyncReadExt, AsyncWriteExt};
352
353  use crate::result::ProxyResult;
354  use crate::{Proxy, ProxyType};
355
356  #[tokio::test]
357  async fn test_socks5_proxy() -> std::io::Result<()> {
358    let proxy = Proxy::new("212.58.132.5:1080", ProxyType::Socks5);
359
360    let mut conn = match proxy.connect("ipinfo.io".to_string(), 80).await {
361      ProxyResult::Ok(s) => s,
362      ProxyResult::Err(e) => return Err(Error::new(ErrorKind::NotConnected, e.text())),
363    };
364
365    conn.write_all(b"GET / HTTP/1.0\r\nHost: ipinfo.io\r\n\r\n").await?;
366
367    let mut buf = Vec::new();
368    conn.read_to_end(&mut buf).await?;
369
370    println!("{}", String::from_utf8_lossy(&buf));
371
372    Ok(())
373  }
374
375  #[tokio::test]
376  async fn test_socks4_proxy() -> std::io::Result<()> {
377    let proxy = Proxy::new("68.71.242.118:4145", ProxyType::Socks4);
378
379    let mut conn = match proxy.connect("ipinfo.io".to_string(), 80).await {
380      ProxyResult::Ok(s) => s,
381      ProxyResult::Err(e) => return Err(Error::new(ErrorKind::NotConnected, e.text())),
382    };
383
384    conn.write_all(b"GET / HTTP/1.0\r\nHost: ipinfo.io\r\n\r\n").await?;
385
386    let mut buf = Vec::new();
387    conn.read_to_end(&mut buf).await?;
388
389    println!("{}", String::from_utf8_lossy(&buf));
390
391    Ok(())
392  }
393}