├── .gitignore ├── examples └── proxy │ ├── .gitignore │ ├── Cargo.toml │ ├── src │ └── main.rs │ └── Cargo.lock ├── .github └── workflows │ └── ci.yml ├── Cargo.toml ├── README.md ├── LICENSE-MIT ├── src ├── v4 │ ├── types.rs │ └── mod.rs ├── lib.rs └── v5 │ ├── mod.rs │ ├── types.rs │ ├── hosts.rs │ ├── handshake.rs │ └── client_commands.rs └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /examples/proxy/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /examples/proxy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proxy" 3 | version = "0.1.0" 4 | authors = ["Alexandre Kirszenberg "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | anyhow = "1.0.38" 9 | futures = "0.3.12" 10 | async-std = "1.9.0" 11 | socksv5 = { path = "../.." } 12 | log = "0.4.13" 13 | env_logger = "0.8.2" 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push 5 | 6 | env: 7 | CARGO_TERM_COLOR: always 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Build 17 | run: cargo build --verbose 18 | - name: Run tests 19 | run: cargo test --verbose 20 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "socksv5" 3 | version = "0.3.1" 4 | authors = ["Alexandre Kirszenberg "] 5 | edition = "2018" 6 | description = "SOCKS v4a and v5 basic building blocks to build your own async SOCKS application" 7 | license = "MIT OR Apache-2.0" 8 | keywords = ["SOCKS", "SOCKS4a", "SOCKS5", "proxy", "async"] 9 | repository = "https://github.com/alexkirsz/socksv5" 10 | 11 | [features] 12 | default = ["futures"] 13 | tokio = ["tokio_compat"] 14 | 15 | [dependencies] 16 | thiserror = "1.0.22" 17 | byteorder = "1.3.4" 18 | futures = { version = "0.3.8", optional = true } 19 | tokio_compat = { package = "tokio", version = "1.0.2", optional = true, features = [ 20 | "macros", 21 | "io-util" 22 | ] } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # socksv5 2 | 3 | SOCKS v4a and v5 basic building blocks to build your own async SOCKS 4 | application. See [examples/proxy](examples/proxy) for an example use case. 5 | 6 | ## Futures 7 | 8 | This library supports futures 0.3 async traits by default. 9 | 10 | Tokio 1 async traits support can be enabled with the `tokio` 11 | feature. In that case, set `default-features = false` to avoid pulling in 12 | the `futures` crate. 13 | 14 | ## TODO 15 | 16 | - [ ] Client-side message parsing. 17 | - [ ] Username/password and GSSAPI authentication methods. 18 | - [ ] Documentation. 19 | - [ ] Tests. 20 | - [ ] Sync API. 21 | 22 | #### License 23 | 24 | 25 | Licensed under either of Apache License, Version 26 | 2.0 or MIT license at your option. 27 | 28 | 29 |
30 | 31 | 32 | Unless you explicitly state otherwise, any contribution intentionally submitted 33 | for inclusion in this crate by you, as defined in the Apache-2.0 license, shall 34 | be dual licensed as above, without any additional terms or conditions. 35 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /src/v4/types.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 2 | pub enum SocksV4Command { 3 | Connect, 4 | Bind, 5 | } 6 | 7 | impl SocksV4Command { 8 | pub fn from_u8(n: u8) -> Option { 9 | match n { 10 | 0x01 => Some(SocksV4Command::Connect), 11 | 0x02 => Some(SocksV4Command::Bind), 12 | _ => None, 13 | } 14 | } 15 | 16 | pub fn to_u8(&self) -> u8 { 17 | match self { 18 | SocksV4Command::Connect => 0x01, 19 | SocksV4Command::Bind => 0x02, 20 | } 21 | } 22 | } 23 | 24 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 25 | pub enum SocksV4RequestStatus { 26 | Granted, 27 | /// Also known as Refused 28 | Failed, 29 | IdentdFailed, 30 | WrongUserid, 31 | } 32 | 33 | impl SocksV4RequestStatus { 34 | pub fn from_u8(n: u8) -> Option { 35 | match n { 36 | 0x5a => Some(SocksV4RequestStatus::Granted), 37 | 0x5b => Some(SocksV4RequestStatus::Failed), 38 | 0x5c => Some(SocksV4RequestStatus::IdentdFailed), 39 | 0x5d => Some(SocksV4RequestStatus::WrongUserid), 40 | _ => None, 41 | } 42 | } 43 | 44 | pub fn to_u8(&self) -> u8 { 45 | match self { 46 | SocksV4RequestStatus::Granted => 0x5a, 47 | SocksV4RequestStatus::Failed => 0x5b, 48 | SocksV4RequestStatus::IdentdFailed => 0x5c, 49 | SocksV4RequestStatus::WrongUserid => 0x5d, 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use crate::io::*; 4 | 5 | pub mod v4; 6 | pub mod v5; 7 | 8 | pub(crate) mod io { 9 | #[cfg(not(feature = "tokio"))] 10 | pub use futures::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 11 | #[cfg(feature = "tokio")] 12 | pub use tokio_compat::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 13 | } 14 | 15 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 16 | pub enum SocksVersion { 17 | V4 = 0x04, 18 | V5 = 0x05, 19 | } 20 | 21 | impl SocksVersion { 22 | pub fn from_u8(n: u8) -> Option { 23 | match n { 24 | 0x04 => Some(SocksVersion::V4), 25 | 0x05 => Some(SocksVersion::V5), 26 | _ => None, 27 | } 28 | } 29 | 30 | pub fn to_u8(&self) -> u8 { 31 | match self { 32 | SocksVersion::V4 => 0x04, 33 | SocksVersion::V5 => 0x05, 34 | } 35 | } 36 | } 37 | 38 | #[derive(Debug, Error)] 39 | pub enum SocksVersionError { 40 | #[error("invalid version {0:x}, expected {:x} or {:x}", SocksVersion::V4.to_u8(), SocksVersion::V5 as u8)] 41 | InvalidVersion(u8), 42 | #[error("{0}")] 43 | Io( 44 | #[from] 45 | #[source] 46 | std::io::Error, 47 | ), 48 | } 49 | 50 | pub type SocksVersionResult = Result; 51 | 52 | pub async fn read_version(mut stream: Stream) -> SocksVersionResult 53 | where 54 | Stream: AsyncRead + Unpin, 55 | { 56 | let mut version = [0u8]; 57 | stream.read_exact(&mut version).await?; 58 | SocksVersion::from_u8(version[0]).ok_or_else(|| SocksVersionError::InvalidVersion(version[0])) 59 | } 60 | -------------------------------------------------------------------------------- /src/v4/mod.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ByteOrder}; 2 | use thiserror::Error; 3 | 4 | pub use types::*; 5 | 6 | use crate::io::*; 7 | use crate::SocksVersion; 8 | 9 | mod types; 10 | 11 | #[derive(Debug, Error)] 12 | pub enum SocksV4RequestError { 13 | #[error("invalid SOCKS version {0:02X}, expected {:02X}", SocksVersion::V4.to_u8())] 14 | InvalidVersion(u8), 15 | #[error("invalid request: {0}")] 16 | InvalidRequest(String), 17 | #[error("{0}")] 18 | Io( 19 | #[from] 20 | #[source] 21 | std::io::Error, 22 | ), 23 | } 24 | 25 | #[derive(Debug)] 26 | pub enum SocksV4Host { 27 | Domain(Vec), 28 | Ip([u8; 4]), 29 | } 30 | 31 | #[derive(Debug)] 32 | pub struct SocksV4Request { 33 | pub command: SocksV4Command, 34 | pub port: u16, 35 | pub host: SocksV4Host, 36 | pub userid: Vec, 37 | } 38 | 39 | pub type SocksV4RequestResult = Result; 40 | 41 | pub async fn read_request(mut reader: Reader) -> SocksV4RequestResult 42 | where 43 | Reader: AsyncRead + Unpin, 44 | { 45 | let mut version = [0u8]; 46 | reader.read_exact(&mut version).await?; 47 | let version = version[0]; 48 | 49 | if version != SocksVersion::V4.to_u8() { 50 | return Err(SocksV4RequestError::InvalidVersion(version)); 51 | } 52 | 53 | read_request_skip_version(reader).await 54 | } 55 | 56 | pub async fn read_request_skip_version(mut reader: Reader) -> SocksV4RequestResult 57 | where 58 | Reader: AsyncRead + Unpin, 59 | { 60 | let mut command = [0u8]; 61 | reader.read_exact(&mut command).await?; 62 | 63 | let command = SocksV4Command::from_u8(command[0]).ok_or_else(|| { 64 | SocksV4RequestError::InvalidRequest(format!( 65 | "invalid command {:x}, expected {:x} (CONNECT) or {:x} (BIND)", 66 | command[0], 67 | SocksV4Command::Bind.to_u8(), 68 | SocksV4Command::Connect.to_u8(), 69 | )) 70 | })?; 71 | 72 | let mut dstport = [0u8; 2]; 73 | reader.read_exact(&mut dstport).await?; 74 | let port = BigEndian::read_u16(&dstport); 75 | 76 | let mut dstip = [0u8; 4]; 77 | reader.read_exact(&mut dstip).await?; 78 | 79 | let mut userid = vec![]; 80 | let mut byte = [0u8]; 81 | loop { 82 | reader.read_exact(&mut byte).await?; 83 | if byte[0] == 0 { 84 | break; 85 | } 86 | userid.push(byte[0]); 87 | } 88 | 89 | // V4a 90 | let host = if dstip[..3] == [0, 0, 0] && dstip[3] != 0 { 91 | let mut domain = vec![]; 92 | let mut byte = [0u8]; 93 | loop { 94 | reader.read_exact(&mut byte).await?; 95 | if byte[0] == 0 { 96 | break; 97 | } 98 | domain.push(byte[0]); 99 | } 100 | SocksV4Host::Domain(domain) 101 | } else { 102 | SocksV4Host::Ip(dstip) 103 | }; 104 | 105 | Ok(SocksV4Request { 106 | command, 107 | port, 108 | host, 109 | userid, 110 | }) 111 | } 112 | 113 | pub async fn write_request_status( 114 | mut writer: Writer, 115 | status: SocksV4RequestStatus, 116 | host: [u8; 4], 117 | port: u16, 118 | ) -> std::io::Result<()> 119 | where 120 | Writer: AsyncWrite + Unpin, 121 | { 122 | let mut buf = [0u8; 8]; 123 | buf[1] = status.to_u8(); 124 | BigEndian::write_u16(&mut buf[2..4], port); 125 | buf[4..8].copy_from_slice(&host); 126 | writer.write_all(&buf).await?; 127 | Ok(()) 128 | } 129 | -------------------------------------------------------------------------------- /src/v5/mod.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{ByteOrder, NetworkEndian}; 2 | use thiserror::Error; 3 | 4 | pub use client_commands::*; 5 | pub use handshake::*; 6 | pub use hosts::*; 7 | pub use types::*; 8 | 9 | use crate::io::*; 10 | use crate::SocksVersion; 11 | 12 | mod client_commands; 13 | mod handshake; 14 | mod hosts; 15 | mod types; 16 | 17 | #[derive(Debug, Error)] 18 | pub enum SocksV5RequestError { 19 | #[error("invalid SOCKS version {0:02X}, expected {:02X}", SocksVersion::V5.to_u8())] 20 | InvalidVersion(u8), 21 | #[error("invalid request: {0}")] 22 | InvalidRequest(String), 23 | #[error("{0}")] 24 | Io( 25 | #[from] 26 | #[source] 27 | std::io::Error, 28 | ), 29 | } 30 | 31 | #[derive(Debug)] 32 | pub struct SocksV5Request { 33 | pub command: SocksV5Command, 34 | pub host: SocksV5Host, 35 | pub port: u16, 36 | } 37 | 38 | pub type SocksV5RequestResult = Result; 39 | 40 | pub async fn read_request(mut reader: Reader) -> SocksV5RequestResult 41 | where 42 | Reader: AsyncRead + Unpin, 43 | { 44 | let mut version = [0u8]; 45 | reader.read_exact(&mut version).await?; 46 | let version = version[0]; 47 | 48 | if version != SocksVersion::V5.to_u8() { 49 | return Err(SocksV5RequestError::InvalidVersion(version)); 50 | } 51 | 52 | let mut command = [0u8]; 53 | reader.read_exact(&mut command).await?; 54 | let command = SocksV5Command::from_u8(command[0]).ok_or_else(|| { 55 | SocksV5RequestError::InvalidRequest(format!( 56 | "invalid command {:02X}, expected {:02X} (CONNECT), {:02X} (BIND), or {:02X} (UDP ASSOCIATE)", 57 | command[0], 58 | SocksV5Command::Bind.to_u8(), 59 | SocksV5Command::Connect.to_u8(), 60 | SocksV5Command::UdpAssociate.to_u8(), 61 | )) 62 | })?; 63 | 64 | // Skip RSV 65 | reader.read_exact(&mut [0]).await?; 66 | 67 | let mut atyp = [0u8]; 68 | reader.read_exact(&mut atyp).await?; 69 | let atyp = SocksV5AddressType::from_u8(atyp[0]).ok_or_else(|| { 70 | SocksV5RequestError::InvalidRequest(format!( 71 | "invalid address type {:02X}, expected {:02X} (IP V4), {:02X} (DOMAINNAME), or {:02X} (IP V6)", 72 | atyp[0], 73 | SocksV5AddressType::Ipv4.to_u8(), 74 | SocksV5AddressType::Domain.to_u8(), 75 | SocksV5AddressType::Ipv6.to_u8(), 76 | )) 77 | })?; 78 | 79 | let host = SocksV5Host::read(&mut reader, atyp).await?; 80 | 81 | let mut port = [0u8; 2]; 82 | reader.read_exact(&mut port).await?; 83 | let port = NetworkEndian::read_u16(&port); 84 | 85 | Ok(SocksV5Request { 86 | command, 87 | port, 88 | host, 89 | }) 90 | } 91 | 92 | pub async fn write_request_status( 93 | mut writer: Writer, 94 | status: SocksV5RequestStatus, 95 | host: SocksV5Host, 96 | port: u16, 97 | ) -> std::io::Result<()> 98 | where 99 | Writer: AsyncWrite + Unpin, 100 | { 101 | let mut buf = vec![0u8; 6 + host.repr_len()]; 102 | buf[0] = SocksVersion::V5.to_u8(); 103 | buf[1] = status.to_u8(); 104 | let idx = match &host { 105 | SocksV5Host::Ipv4(ip) => { 106 | buf[3] = SocksV5AddressType::Ipv4.to_u8(); 107 | buf[4..8].copy_from_slice(ip); 108 | 8 109 | } 110 | SocksV5Host::Ipv6(ip) => { 111 | buf[3] = SocksV5AddressType::Ipv6.to_u8(); 112 | buf[4..20].copy_from_slice(ip); 113 | 20 114 | } 115 | SocksV5Host::Domain(d) => { 116 | buf[3] = SocksV5AddressType::Domain.to_u8(); 117 | buf[4] = d.len() as u8; 118 | buf[5..5 + d.len()].copy_from_slice(d); 119 | 5 + d.len() 120 | } 121 | }; 122 | NetworkEndian::write_u16(&mut buf[idx..idx + 2], port); 123 | writer.write_all(&buf).await 124 | } 125 | 126 | #[cfg(test)] 127 | mod tests { 128 | use futures::executor::block_on; 129 | 130 | use super::*; 131 | 132 | #[test] 133 | fn write_request_domain() { 134 | let mut buf = Vec::::new(); 135 | block_on(write_request( 136 | &mut buf, 137 | SocksV5Command::Connect, 138 | SocksV5Host::Domain("A".into()), 139 | 1080, 140 | )) 141 | .unwrap(); 142 | assert_eq!(buf, &[5, 1, 0, 3, 1, 65, 4, 56]); 143 | } 144 | 145 | #[test] 146 | fn read_request_status_good() { 147 | let data = [5, 0, 0, 1, 127, 0, 0, 1, 4, 56]; 148 | let response = block_on(read_request_status(data.as_slice())).unwrap(); 149 | assert_eq!(response.status, SocksV5RequestStatus::Success); 150 | match response.host { 151 | SocksV5Host::Ipv4(ip) => assert_eq!(ip, [127, 0, 0, 1]), 152 | _ => panic!("parsed host was not IPv4 as expected"), 153 | } 154 | assert_eq!(response.port, 1080); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/v5/types.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 2 | pub enum SocksV5AuthMethod { 3 | Noauth, 4 | Gssapi, 5 | UsernamePassword, 6 | NoAcceptableMethod, 7 | Other(u8), 8 | } 9 | 10 | impl SocksV5AuthMethod { 11 | pub(crate) fn from_u8(n: u8) -> SocksV5AuthMethod { 12 | match n { 13 | 0x00 => SocksV5AuthMethod::Noauth, 14 | 0x01 => SocksV5AuthMethod::Gssapi, 15 | 0x02 => SocksV5AuthMethod::UsernamePassword, 16 | 0xff => SocksV5AuthMethod::NoAcceptableMethod, 17 | other => SocksV5AuthMethod::Other(other), 18 | } 19 | } 20 | 21 | pub(crate) fn to_u8(&self) -> u8 { 22 | match self { 23 | SocksV5AuthMethod::Noauth => 0x00, 24 | SocksV5AuthMethod::Gssapi => 0x01, 25 | SocksV5AuthMethod::UsernamePassword => 0x02, 26 | SocksV5AuthMethod::NoAcceptableMethod => 0xff, 27 | SocksV5AuthMethod::Other(other) => *other, 28 | } 29 | } 30 | } 31 | 32 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 33 | pub enum SocksV5Command { 34 | Connect, 35 | Bind, 36 | UdpAssociate, 37 | } 38 | 39 | impl SocksV5Command { 40 | pub fn from_u8(n: u8) -> Option { 41 | match n { 42 | 0x01 => Some(SocksV5Command::Connect), 43 | 0x02 => Some(SocksV5Command::Bind), 44 | 0x03 => Some(SocksV5Command::UdpAssociate), 45 | _ => None, 46 | } 47 | } 48 | 49 | pub fn to_u8(&self) -> u8 { 50 | match self { 51 | SocksV5Command::Connect => 0x01, 52 | SocksV5Command::Bind => 0x02, 53 | SocksV5Command::UdpAssociate => 0x03, 54 | } 55 | } 56 | } 57 | 58 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 59 | pub enum SocksV5AddressType { 60 | Ipv4, 61 | Domain, 62 | Ipv6, 63 | } 64 | 65 | impl SocksV5AddressType { 66 | pub fn from_u8(n: u8) -> Option { 67 | match n { 68 | 0x01 => Some(SocksV5AddressType::Ipv4), 69 | 0x03 => Some(SocksV5AddressType::Domain), 70 | 0x04 => Some(SocksV5AddressType::Ipv6), 71 | _ => None, 72 | } 73 | } 74 | 75 | pub fn to_u8(&self) -> u8 { 76 | match self { 77 | SocksV5AddressType::Ipv4 => 0x01, 78 | SocksV5AddressType::Domain => 0x03, 79 | SocksV5AddressType::Ipv6 => 0x04, 80 | } 81 | } 82 | } 83 | 84 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 85 | pub enum SocksV5RequestStatus { 86 | Success, 87 | ServerFailure, 88 | ConnectionNotAllowed, 89 | NetworkUnreachable, 90 | HostUnreachable, 91 | ConnectionRefused, 92 | TtlExpired, 93 | CommandNotSupported, 94 | AddrtypeNotSupported, 95 | } 96 | 97 | impl SocksV5RequestStatus { 98 | pub fn from_u8(n: u8) -> Option { 99 | match n { 100 | 0x00 => Some(SocksV5RequestStatus::Success), 101 | 0x01 => Some(SocksV5RequestStatus::ServerFailure), 102 | 0x02 => Some(SocksV5RequestStatus::ConnectionNotAllowed), 103 | 0x03 => Some(SocksV5RequestStatus::NetworkUnreachable), 104 | 0x04 => Some(SocksV5RequestStatus::HostUnreachable), 105 | 0x05 => Some(SocksV5RequestStatus::ConnectionRefused), 106 | 0x06 => Some(SocksV5RequestStatus::TtlExpired), 107 | 0x07 => Some(SocksV5RequestStatus::CommandNotSupported), 108 | 0x08 => Some(SocksV5RequestStatus::AddrtypeNotSupported), 109 | _ => None, 110 | } 111 | } 112 | 113 | pub fn to_u8(&self) -> u8 { 114 | match self { 115 | SocksV5RequestStatus::Success => 0x00, 116 | SocksV5RequestStatus::ServerFailure => 0x01, 117 | SocksV5RequestStatus::ConnectionNotAllowed => 0x02, 118 | SocksV5RequestStatus::NetworkUnreachable => 0x03, 119 | SocksV5RequestStatus::HostUnreachable => 0x04, 120 | SocksV5RequestStatus::ConnectionRefused => 0x05, 121 | SocksV5RequestStatus::TtlExpired => 0x06, 122 | SocksV5RequestStatus::CommandNotSupported => 0x07, 123 | SocksV5RequestStatus::AddrtypeNotSupported => 0x08, 124 | } 125 | } 126 | 127 | #[cfg(unix)] 128 | pub fn from_io_error(e: std::io::Error) -> SocksV5RequestStatus { 129 | match e.raw_os_error() { 130 | // ENETUNREACH 131 | Some(101) => SocksV5RequestStatus::NetworkUnreachable, 132 | // ETIMEDOUT 133 | Some(110) => SocksV5RequestStatus::TtlExpired, 134 | // ECONNREFUSED 135 | Some(111) => SocksV5RequestStatus::ConnectionRefused, 136 | // EHOSTUNREACH 137 | Some(113) => SocksV5RequestStatus::HostUnreachable, 138 | // Unhandled error code 139 | _ => SocksV5RequestStatus::ServerFailure, 140 | } 141 | } 142 | 143 | #[cfg(not(unix))] 144 | pub fn from_io_error(e: std::io::Error) -> SocksV5RequestStatus { 145 | SocksV5RequestStatus::ServerFailure 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/v5/hosts.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; 3 | 4 | use crate::io::*; 5 | use crate::v5::SocksV5AddressType; 6 | 7 | #[derive(Clone, Debug)] 8 | pub enum SocksV5Host { 9 | Domain(Vec), 10 | Ipv4([u8; 4]), 11 | Ipv6([u8; 16]), 12 | } 13 | 14 | impl SocksV5Host { 15 | pub fn is_ip_addr(&self) -> bool { 16 | !matches!(self, SocksV5Host::Domain(_)) 17 | } 18 | 19 | pub fn is_ip_v4(&self) -> bool { 20 | matches!(self, SocksV5Host::Ipv4(_)) 21 | } 22 | 23 | pub fn is_ip_v6(&self) -> bool { 24 | matches!(self, SocksV5Host::Ipv6(_)) 25 | } 26 | 27 | pub fn is_domain(&self) -> bool { 28 | matches!(self, SocksV5Host::Domain(_)) 29 | } 30 | 31 | pub(crate) fn repr_len(&self) -> usize { 32 | match self { 33 | SocksV5Host::Domain(domain) => 1 + domain.len(), 34 | SocksV5Host::Ipv4(_) => 4, 35 | SocksV5Host::Ipv6(_) => 16, 36 | } 37 | } 38 | 39 | pub(crate) async fn read( 40 | mut reader: Reader, 41 | addr_type: SocksV5AddressType, 42 | ) -> std::io::Result 43 | where 44 | Reader: AsyncRead + Unpin, 45 | { 46 | match addr_type { 47 | SocksV5AddressType::Ipv4 => { 48 | let mut host = [0u8; 4]; 49 | reader.read_exact(&mut host).await?; 50 | Ok(host.into()) 51 | } 52 | SocksV5AddressType::Ipv6 => { 53 | let mut host = [0u8; 16]; 54 | reader.read_exact(&mut host).await?; 55 | Ok(host.into()) 56 | } 57 | SocksV5AddressType::Domain => { 58 | let mut buf = [0u8]; 59 | reader.read_exact(&mut buf).await?; 60 | let mut domain = vec![0u8; buf[0] as usize]; 61 | reader.read_exact(&mut domain).await?; 62 | Ok(domain.into()) 63 | } 64 | } 65 | } 66 | } 67 | 68 | impl From<[u8; 4]> for SocksV5Host { 69 | fn from(octets: [u8; 4]) -> Self { 70 | SocksV5Host::Ipv4(octets) 71 | } 72 | } 73 | 74 | impl From<[u8; 16]> for SocksV5Host { 75 | fn from(octets: [u8; 16]) -> Self { 76 | SocksV5Host::Ipv6(octets) 77 | } 78 | } 79 | 80 | impl From> for SocksV5Host { 81 | fn from(domain_bytes: Vec) -> Self { 82 | SocksV5Host::Domain(domain_bytes) 83 | } 84 | } 85 | 86 | impl From for SocksV5Host { 87 | fn from(addr: Ipv4Addr) -> Self { 88 | SocksV5Host::Ipv4(addr.octets()) 89 | } 90 | } 91 | 92 | impl From for SocksV5Host { 93 | fn from(addr: Ipv6Addr) -> Self { 94 | SocksV5Host::Ipv6(addr.octets()) 95 | } 96 | } 97 | 98 | impl From for SocksV5Host { 99 | fn from(addr: IpAddr) -> Self { 100 | match addr { 101 | IpAddr::V4(addr) => addr.into(), 102 | IpAddr::V6(addr) => addr.into(), 103 | } 104 | } 105 | } 106 | 107 | impl<'a> From<&'a str> for SocksV5Host { 108 | fn from(addr: &'a str) -> Self { 109 | SocksV5Host::Domain(addr.into()) 110 | } 111 | } 112 | 113 | impl From for SocksV5Host { 114 | fn from(addr: String) -> Self { 115 | SocksV5Host::Domain(addr.into()) 116 | } 117 | } 118 | 119 | impl TryFrom for Ipv4Addr { 120 | type Error = SocksV5Host; 121 | 122 | fn try_from(host: SocksV5Host) -> Result { 123 | match host { 124 | SocksV5Host::Ipv4(octets) => Ok(octets.into()), 125 | other => Err(other), 126 | } 127 | } 128 | } 129 | 130 | impl<'a> TryFrom<&'a SocksV5Host> for Ipv4Addr { 131 | type Error = (); 132 | 133 | fn try_from(host: &'a SocksV5Host) -> Result { 134 | match host { 135 | SocksV5Host::Ipv4(octets) => Ok((*octets).into()), 136 | _ => Err(()), 137 | } 138 | } 139 | } 140 | 141 | impl TryFrom for Ipv6Addr { 142 | type Error = SocksV5Host; 143 | 144 | fn try_from(host: SocksV5Host) -> Result { 145 | match host { 146 | SocksV5Host::Ipv6(octets) => Ok(octets.into()), 147 | other => Err(other), 148 | } 149 | } 150 | } 151 | 152 | impl<'a> TryFrom<&'a SocksV5Host> for Ipv6Addr { 153 | type Error = (); 154 | 155 | fn try_from(host: &'a SocksV5Host) -> Result { 156 | match host { 157 | SocksV5Host::Ipv6(octets) => Ok((*octets).into()), 158 | _ => Err(()), 159 | } 160 | } 161 | } 162 | 163 | impl TryFrom for IpAddr { 164 | type Error = SocksV5Host; 165 | 166 | fn try_from(host: SocksV5Host) -> Result { 167 | match host { 168 | SocksV5Host::Ipv4(octets) => Ok(octets.into()), 169 | SocksV5Host::Ipv6(octets) => Ok(octets.into()), 170 | other => Err(other), 171 | } 172 | } 173 | } 174 | 175 | impl<'a> TryFrom<&'a SocksV5Host> for IpAddr { 176 | type Error = (); 177 | 178 | fn try_from(host: &'a SocksV5Host) -> Result { 179 | match host { 180 | SocksV5Host::Ipv4(octets) => Ok((*octets).into()), 181 | SocksV5Host::Ipv6(octets) => Ok((*octets).into()), 182 | _ => Err(()), 183 | } 184 | } 185 | } 186 | 187 | impl TryFrom for Vec { 188 | type Error = SocksV5Host; 189 | 190 | fn try_from(host: SocksV5Host) -> Result { 191 | match host { 192 | SocksV5Host::Domain(bytes) => Ok(bytes), 193 | other => Err(other), 194 | } 195 | } 196 | } 197 | 198 | impl<'a> TryFrom<&'a SocksV5Host> for &'a [u8] { 199 | type Error = (); 200 | 201 | fn try_from(host: &'a SocksV5Host) -> Result { 202 | match host { 203 | SocksV5Host::Domain(bytes) => Ok(bytes), 204 | _ => Err(()), 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/v5/handshake.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use crate::io::*; 4 | use crate::v5::SocksV5AuthMethod; 5 | use crate::SocksVersion; 6 | 7 | #[derive(Debug, Error)] 8 | pub enum SocksV5HandshakeError { 9 | #[error("invalid SOCKS version {0:02X}, expected {:02X}", SocksVersion::V5.to_u8())] 10 | InvalidVersion(u8), 11 | #[error("invalid request: {0}")] 12 | InvalidHandshake(String), 13 | #[error("{0}")] 14 | Io( 15 | #[from] 16 | #[source] 17 | std::io::Error, 18 | ), 19 | } 20 | 21 | #[derive(Debug)] 22 | pub struct SocksV5Handshake { 23 | pub methods: Vec, 24 | } 25 | 26 | pub type SocksV5HandshakeResult = Result; 27 | 28 | pub async fn read_handshake(mut reader: Reader) -> SocksV5HandshakeResult 29 | where 30 | Reader: AsyncRead + Unpin, 31 | { 32 | let mut version = [0u8]; 33 | reader.read_exact(&mut version).await?; 34 | let version = version[0]; 35 | 36 | if version != SocksVersion::V5.to_u8() { 37 | return Err(SocksV5HandshakeError::InvalidVersion(version)); 38 | } 39 | 40 | read_handshake_skip_version(reader).await 41 | } 42 | 43 | pub async fn read_handshake_skip_version(mut reader: Reader) -> SocksV5HandshakeResult 44 | where 45 | Reader: AsyncRead + Unpin, 46 | { 47 | let mut nmethods = [0u8]; 48 | reader.read_exact(&mut nmethods).await?; 49 | let nmethods = nmethods[0]; 50 | 51 | if nmethods == 0 { 52 | return Err(SocksV5HandshakeError::InvalidHandshake( 53 | "handshake must provide at least one authentication method".to_owned(), 54 | )); 55 | } 56 | 57 | let mut methods = vec![0u8; nmethods as usize]; 58 | reader.read_exact(&mut methods).await?; 59 | let methods: Vec = methods 60 | .into_iter() 61 | .map(SocksV5AuthMethod::from_u8) 62 | .collect(); 63 | 64 | Ok(SocksV5Handshake { methods }) 65 | } 66 | 67 | /// Writes a SOCKSv5 "version identifier/method selection message", 68 | /// requesting the specified authentication methods. 69 | /// 70 | /// The methods are added to the request as they are returned by the supplied iterator; 71 | /// no ordering or deduplication is performed. However, if `methods` returns more than 255 values, 72 | /// this function will silently truncate the list to 255 elements (the maximum allowed by the spec). 73 | /// 74 | /// # Errors 75 | /// 76 | /// If writing to `writer` fails, this function will return the I/O error. 77 | /// 78 | /// # Panics 79 | /// 80 | /// If the list of auth methods is empty. The SOCKSv5 specification requires at least one method to be specified. 81 | pub async fn write_handshake( 82 | mut writer: Writer, 83 | methods: Methods, 84 | ) -> std::io::Result<()> 85 | where 86 | Writer: AsyncWrite + Unpin, 87 | Methods: IntoIterator, 88 | { 89 | let mut data = vec![SocksVersion::V5.to_u8(), 0u8]; 90 | data.extend(methods.into_iter().take(255).map(|m| m.to_u8())); 91 | let method_count = (data.len() - 2) as u8; 92 | assert!(method_count > 0, "must specify at least one auth method"); 93 | data[1] = method_count; 94 | writer.write_all(&data).await 95 | } 96 | 97 | pub type SocksV5AuthMethodResult = Result; 98 | 99 | /// Reads a SOCKSv5 "METHOD selection message", verifying the protocol version 100 | /// and returning the authentication method selected by the server. 101 | /// 102 | /// This function consumes from 0 to 2 bytes from `reader`, depending on the data and errors. 103 | /// When the result is successful, it will have consumed exactly 2 bytes. 104 | /// 105 | /// # Errors 106 | /// 107 | /// If reading from `reader` fails, including if a premature EOF is encountered, 108 | /// this function will return the I/O error (wrapped in `SocksV5HandshakeError::Io`). 109 | /// 110 | /// If the first byte read from `reader` is not `05`, as required by the SOCKSv5 specification, 111 | /// then this function will return `SocksV5HandshakeError::InvalidVersion` with the actual "version number". 112 | pub async fn read_auth_method(mut reader: Reader) -> SocksV5AuthMethodResult 113 | where 114 | Reader: AsyncRead + Unpin, 115 | { 116 | let mut data = [0u8]; 117 | // read protocol version 118 | reader.read_exact(&mut data).await?; 119 | if data[0] != SocksVersion::V5.to_u8() { 120 | return Err(SocksV5HandshakeError::InvalidVersion(data[0])); 121 | } 122 | // read selected auth method 123 | reader.read_exact(&mut data).await?; 124 | Ok(SocksV5AuthMethod::from_u8(data[0])) 125 | } 126 | 127 | pub async fn write_auth_method( 128 | mut writer: Writer, 129 | status: SocksV5AuthMethod, 130 | ) -> std::io::Result<()> 131 | where 132 | Writer: AsyncWrite + Unpin, 133 | { 134 | writer 135 | .write_all(&[SocksVersion::V5.to_u8(), status.to_u8()]) 136 | .await?; 137 | Ok(()) 138 | } 139 | 140 | #[derive(Debug, Error)] 141 | pub enum SocksV5AuthError { 142 | #[error("invalid SOCKS version {0:02X}, expected {:02X}", SocksVersion::V5.to_u8())] 143 | InvalidVersion(u8), 144 | #[error("handshake protocol violation: {0}")] 145 | InvalidHandshake(String), 146 | #[error("could not agree on auth methods")] 147 | NoAcceptableMethods, 148 | #[error("{0}")] 149 | Io( 150 | #[from] 151 | #[source] 152 | std::io::Error, 153 | ), 154 | } 155 | 156 | impl From for SocksV5AuthError { 157 | fn from(he: SocksV5HandshakeError) -> Self { 158 | use SocksV5AuthError::*; 159 | match he { 160 | SocksV5HandshakeError::InvalidVersion(v) => InvalidVersion(v), 161 | SocksV5HandshakeError::InvalidHandshake(msg) => InvalidHandshake(msg), 162 | SocksV5HandshakeError::Io(e) => Io(e), 163 | } 164 | } 165 | } 166 | 167 | /// Send a handshake request (offering no-op authentication) and process the response. 168 | /// 169 | /// # Returns 170 | /// 171 | /// On successful (no-op) authentication, this function returns the same stream that it received as an argument. 172 | /// 173 | /// # Errors 174 | /// 175 | /// - `Io` if either sending the request or receiving the response fails due to I/O error, including a premature EOF. 176 | /// - `InvalidVersion` if the server returns an unexpected version number. 177 | /// - `NoAcceptableMethods` if the server does not agree to skip authentication. 178 | /// - `InvalidHandshake` is never returned by this function. 179 | 180 | // The choice to receive the stream by value and return it anticipates probable future implementations 181 | // of other authentication methods that may want to encrypt the data passed within the stream. 182 | // For such cases, the negotiation function would return a "wrapper" stream. 183 | pub async fn negotiate_noauth_with_server( 184 | mut stream: Stream, 185 | ) -> Result 186 | where 187 | Stream: AsyncRead + AsyncWrite + Unpin, 188 | { 189 | use SocksV5AuthError::*; 190 | 191 | write_handshake(&mut stream, [SocksV5AuthMethod::Noauth]) 192 | .await 193 | .map_err(Io)?; 194 | 195 | let server_auth_method = read_auth_method(&mut stream).await?; 196 | if server_auth_method != SocksV5AuthMethod::Noauth { 197 | return Err(NoAcceptableMethods); 198 | } 199 | Ok(stream) 200 | } 201 | 202 | /// Receive a handshake request and reply to it, supporting only NOAUTH. 203 | /// 204 | /// # Returns 205 | /// 206 | /// On successful (no-op) authentication, this function returns the same stream that it received as an argument. 207 | /// A successful response will have been sent to the client. 208 | /// 209 | /// # Errors 210 | /// 211 | /// - `Io` if either reading the request or writing the response fails due to I/O error, including a premature EOF. 212 | /// - `InvalidVersion` if the client sends an unexpected version number. 213 | /// - `InvalidHandshake` if the client violates the SOCKSv5 protocol (e.g. does not offer any auth methods). 214 | /// - `NoAcceptableMethods` if the client does not offer to use NOAUTH. 215 | /// 216 | /// In case of `NoAcceptableMethods`, the corresponding response is sent to the client. 217 | /// In other error cases no reply is sent. 218 | pub async fn negotiate_noauth_with_client( 219 | mut stream: Stream, 220 | ) -> Result 221 | where 222 | Stream: AsyncRead + AsyncWrite + Unpin, 223 | { 224 | use SocksV5AuthError::*; 225 | 226 | let auth_methods = read_handshake(&mut stream).await?.methods; 227 | if !auth_methods.contains(&SocksV5AuthMethod::Noauth) { 228 | write_auth_method(&mut stream, SocksV5AuthMethod::NoAcceptableMethod).await?; 229 | return Err(NoAcceptableMethods); 230 | } 231 | write_auth_method(&mut stream, SocksV5AuthMethod::Noauth).await?; 232 | Ok(stream) 233 | } 234 | 235 | #[cfg(test)] 236 | mod tests { 237 | use futures::executor::block_on; 238 | 239 | use super::*; 240 | 241 | #[test] 242 | fn write_handshake_good() { 243 | let mut buf = Vec::::new(); 244 | block_on(write_handshake(&mut buf, [SocksV5AuthMethod::Noauth])).unwrap(); 245 | assert_eq!(buf, &[0x05, 0x01, 0x00]); 246 | } 247 | 248 | #[test] 249 | fn read_auth_method_good() { 250 | assert_eq!( 251 | block_on(read_auth_method([0x05u8, 0x00].as_slice())).unwrap(), 252 | SocksV5AuthMethod::Noauth 253 | ); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /examples/proxy/src/main.rs: -------------------------------------------------------------------------------- 1 | use async_std::net::{TcpListener, TcpStream, ToSocketAddrs}; 2 | use async_std::prelude::*; 3 | use async_std::task; 4 | use futures::{ 5 | future::FutureExt, 6 | io::{AsyncRead, AsyncWrite}, 7 | }; 8 | use std::net::{IpAddr, SocketAddr}; 9 | 10 | async fn handshake( 11 | mut reader: Reader, 12 | mut writer: Writer, 13 | ) -> anyhow::Result 14 | where 15 | Reader: AsyncRead + Unpin, 16 | Writer: AsyncWrite + Unpin, 17 | { 18 | match socksv5::read_version(&mut reader).await? { 19 | socksv5::SocksVersion::V5 => { 20 | let handshake = socksv5::v5::read_handshake_skip_version(&mut reader).await?; 21 | 22 | if let None = handshake 23 | .methods 24 | .into_iter() 25 | .find(|m| *m == socksv5::v5::SocksV5AuthMethod::Noauth) 26 | { 27 | return Err(anyhow::anyhow!("this proxy only supports NOAUTH")); 28 | } 29 | 30 | socksv5::v5::write_auth_method(&mut writer, socksv5::v5::SocksV5AuthMethod::Noauth) 31 | .await?; 32 | 33 | let request = socksv5::v5::read_request(&mut reader).await?; 34 | 35 | match request.command { 36 | socksv5::v5::SocksV5Command::Connect => { 37 | let host = match request.host { 38 | socksv5::v5::SocksV5Host::Ipv4(ip) => { 39 | SocketAddr::new(IpAddr::V4(ip.into()), request.port) 40 | } 41 | socksv5::v5::SocksV5Host::Ipv6(ip) => { 42 | SocketAddr::new(IpAddr::V6(ip.into()), request.port) 43 | } 44 | socksv5::v5::SocksV5Host::Domain(domain) => { 45 | let domain = String::from_utf8(domain)?; 46 | let mut addr = (&domain as &str, request.port) 47 | .to_socket_addrs() 48 | .await? 49 | .next() 50 | .ok_or_else(|| anyhow::anyhow!("failed to resolve domain"))?; 51 | addr.set_port(request.port); 52 | addr 53 | } 54 | }; 55 | 56 | let server_socket = TcpStream::connect(host).await; 57 | 58 | match server_socket { 59 | Ok(server_socket) => { 60 | socksv5::v5::write_request_status( 61 | &mut writer, 62 | socksv5::v5::SocksV5RequestStatus::Success, 63 | socksv5::v5::SocksV5Host::Ipv4([0, 0, 0, 0]), 64 | 0, 65 | ) 66 | .await?; 67 | return Ok(server_socket); 68 | } 69 | Err(e) => { 70 | socksv5::v5::write_request_status( 71 | &mut writer, 72 | socksv5::v5::SocksV5RequestStatus::from_io_error(e), 73 | socksv5::v5::SocksV5Host::Ipv4([0, 0, 0, 0]), 74 | 0, 75 | ) 76 | .await?; 77 | return Err(anyhow::anyhow!("failed to connect")); 78 | } 79 | } 80 | } 81 | cmd => { 82 | socksv5::v5::write_request_status( 83 | &mut writer, 84 | socksv5::v5::SocksV5RequestStatus::CommandNotSupported, 85 | socksv5::v5::SocksV5Host::Ipv4([0, 0, 0, 0]), 86 | 0, 87 | ) 88 | .await?; 89 | return Err(anyhow::anyhow!("unsupported command {:?}", cmd)); 90 | } 91 | } 92 | } 93 | socksv5::SocksVersion::V4 => { 94 | let request = socksv5::v4::read_request(reader).await?; 95 | 96 | match request.command { 97 | socksv5::v4::SocksV4Command::Connect => { 98 | let host = match request.host { 99 | socksv5::v4::SocksV4Host::Ip(ip) => { 100 | SocketAddr::new(IpAddr::V4(ip.into()), request.port) 101 | } 102 | socksv5::v4::SocksV4Host::Domain(domain) => { 103 | let domain = String::from_utf8(domain)?; 104 | domain 105 | .to_socket_addrs() 106 | .await? 107 | .next() 108 | .ok_or_else(|| anyhow::anyhow!("failed to resolve domain"))? 109 | } 110 | }; 111 | 112 | let server_socket = TcpStream::connect(host).await; 113 | 114 | match server_socket { 115 | Ok(server_socket) => { 116 | socksv5::v4::write_request_status( 117 | &mut writer, 118 | socksv5::v4::SocksV4RequestStatus::Granted, 119 | [0, 0, 0, 0], 120 | 0, 121 | ) 122 | .await?; 123 | return Ok(server_socket); 124 | } 125 | Err(_) => { 126 | socksv5::v4::write_request_status( 127 | &mut writer, 128 | socksv5::v4::SocksV4RequestStatus::Failed, 129 | [0, 0, 0, 0], 130 | 0, 131 | ) 132 | .await?; 133 | return Err(anyhow::anyhow!("failed to connect")); 134 | } 135 | } 136 | } 137 | cmd => { 138 | socksv5::v4::write_request_status( 139 | &mut writer, 140 | socksv5::v4::SocksV4RequestStatus::Failed, 141 | [0, 0, 0, 0], 142 | 0, 143 | ) 144 | .await?; 145 | return Err(anyhow::anyhow!("unsupported command {:?}", cmd)); 146 | } 147 | } 148 | } 149 | } 150 | } 151 | 152 | async fn pipe(mut reader: Reader, mut writer: Writer) -> std::io::Result<()> 153 | where 154 | Reader: AsyncRead + Unpin, 155 | Writer: AsyncWrite + Unpin, 156 | { 157 | let mut buf = vec![0u8; 1024 * 1024]; 158 | loop { 159 | let n = reader.read(&mut buf).await?; 160 | if n > 0 { 161 | writer.write_all(&buf[..n]).await?; 162 | } else { 163 | break Ok(()); 164 | } 165 | } 166 | } 167 | 168 | async fn pipe_multiple( 169 | reader1: Reader1, 170 | writer1: Writer1, 171 | reader2: Reader2, 172 | writer2: Writer2, 173 | ) -> std::io::Result<()> 174 | where 175 | Reader1: AsyncRead + Unpin, 176 | Writer1: AsyncWrite + Unpin, 177 | Reader2: AsyncRead + Unpin, 178 | Writer2: AsyncWrite + Unpin, 179 | { 180 | let pipe1 = pipe(reader1, writer2).fuse(); 181 | let pipe2 = pipe(reader2, writer1).fuse(); 182 | 183 | futures::pin_mut!(pipe1, pipe2); 184 | 185 | futures::select! { 186 | res = pipe1 => res, 187 | res = pipe2 => res 188 | } 189 | } 190 | 191 | async fn server() -> std::io::Result<()> { 192 | let listener = TcpListener::bind("127.0.0.1:1987").await?; 193 | let mut incoming = listener.incoming(); 194 | 195 | while let Some(client_stream) = incoming.next().await { 196 | let client_stream = client_stream?; 197 | log::debug!("incoming request from {}", client_stream.peer_addr().unwrap()); 198 | 199 | task::spawn(async move { 200 | let (client_reader, client_writer) = &mut (&client_stream, &client_stream); 201 | 202 | match handshake(client_reader, client_writer).await { 203 | Err(err) => log::error!("handshake error: {}", err), 204 | Ok(server_socket) => { 205 | log::debug!("{} initiated connection with {}", client_stream.peer_addr().unwrap(), server_socket.peer_addr().unwrap()); 206 | 207 | let (client_reader, client_writer) = &mut (&client_stream, &client_stream); 208 | let (server_reader, server_writer) = &mut (&server_socket, &server_socket); 209 | 210 | match pipe_multiple(client_reader, client_writer, server_reader, server_writer) 211 | .await 212 | { 213 | Err(err) => log::error!("pipe error: {}", err), 214 | Ok(_) => log::debug!{"connection terminated between {} and {}", client_stream.peer_addr().unwrap(), server_socket.peer_addr().unwrap()} 215 | } 216 | } 217 | } 218 | }); 219 | } 220 | 221 | Ok(()) 222 | } 223 | 224 | fn main() -> std::io::Result<()> { 225 | env_logger::init(); 226 | 227 | task::block_on(server()) 228 | } 229 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /src/v5/client_commands.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{ByteOrder, NetworkEndian}; 2 | use std::future::Future; 3 | use thiserror::Error; 4 | 5 | use crate::io::*; 6 | use crate::v5::{ 7 | SocksV5AddressType, SocksV5Command, SocksV5Host, SocksV5RequestError, SocksV5RequestStatus, 8 | }; 9 | use crate::SocksVersion; 10 | 11 | /// Writes a SOCKSv5 request with the specified command, host and port. 12 | /// 13 | /// # Errors 14 | /// 15 | /// If writing to `writer` fails, this function will return the I/O error. 16 | /// 17 | /// # Panics 18 | /// 19 | /// If `host` is a domain name, and its length is greater than 255 bytes. 20 | /// The SOCKSv5 specification leaves only a single octet for encoding the domain name length, 21 | /// so a target longer than 255 bytes cannot be properly encoded. 22 | pub async fn write_request( 23 | mut writer: Writer, 24 | command: SocksV5Command, 25 | host: SocksV5Host, 26 | port: u16, 27 | ) -> std::io::Result<()> 28 | where 29 | Writer: AsyncWrite + Unpin, 30 | { 31 | let mut data = Vec::::with_capacity(6 + host.repr_len()); 32 | data.push(SocksVersion::V5.to_u8()); 33 | data.push(command.to_u8()); 34 | data.push(0u8); // reserved bits in SOCKSv5 35 | match &host { 36 | SocksV5Host::Domain(domain) => { 37 | assert!( 38 | domain.len() < 256, 39 | "domain name must be shorter than 256 bytes" 40 | ); 41 | data.push(SocksV5AddressType::Domain.to_u8()); 42 | data.push(domain.len() as u8); 43 | data.extend_from_slice(domain); 44 | } 45 | SocksV5Host::Ipv4(octets) => { 46 | data.push(SocksV5AddressType::Ipv4.to_u8()); 47 | data.extend_from_slice(octets); 48 | } 49 | SocksV5Host::Ipv6(octets) => { 50 | data.push(SocksV5AddressType::Ipv6.to_u8()); 51 | data.extend_from_slice(octets); 52 | } 53 | } 54 | let port_start = data.len(); 55 | data.extend_from_slice(b"\0\0"); 56 | NetworkEndian::write_u16(&mut data[port_start..], port); 57 | 58 | writer.write_all(&data).await 59 | } 60 | 61 | #[derive(Debug)] 62 | pub struct SocksV5Response { 63 | pub status: SocksV5RequestStatus, 64 | pub host: SocksV5Host, 65 | pub port: u16, 66 | } 67 | 68 | pub type SocksV5ResponseResult = Result; 69 | 70 | /// Reads and parses a SOCKSv5 command response message. 71 | /// 72 | /// Depending on the data (in case of parsing errors), 73 | /// this function may not consume the whole response from the server. 74 | /// 75 | /// # Errors 76 | /// 77 | /// If reading from `reader` fails, including if a premature EOF is encountered, 78 | /// this function will return the I/O error (wrapped in `SocksV5RequestError::Io`). 79 | /// 80 | /// If the first byte read from `reader` is not `05`, as required by the SOCKSv5 specification, 81 | /// then this function will return `SocksV5RequestError::InvalidVersion` with the actual "version number". 82 | /// 83 | /// If the status byte or the address type byte are not from the respective lists in the specification, 84 | /// then this function will return `SocksV5RequestError::InvalidRequest` 85 | /// with a human-readable description of the error. 86 | pub async fn read_request_status(mut reader: Reader) -> SocksV5ResponseResult 87 | where 88 | Reader: AsyncRead + Unpin, 89 | { 90 | let mut buf = [0u8; 2]; 91 | 92 | reader.read_exact(&mut buf[0..1]).await?; 93 | if buf[0] != SocksVersion::V5.to_u8() { 94 | return Err(SocksV5RequestError::InvalidVersion(buf[0])); 95 | } 96 | 97 | reader.read_exact(&mut buf[0..1]).await?; 98 | let status = SocksV5RequestStatus::from_u8(buf[0]).ok_or_else(|| { 99 | SocksV5RequestError::InvalidRequest(format!("invalid status {:02X}", buf[0])) 100 | })?; 101 | 102 | reader.read_exact(&mut buf).await?; 103 | // ignore a reserved octet, use the following one 104 | let atyp = SocksV5AddressType::from_u8(buf[1]).ok_or_else(|| { 105 | SocksV5RequestError::InvalidRequest(format!( 106 | "invalid address type {:02X}, expected {:02X} (IP V4), {:02X} (DOMAINNAME), or {:02X} (IP V6)", 107 | buf[1], 108 | SocksV5AddressType::Ipv4.to_u8(), 109 | SocksV5AddressType::Domain.to_u8(), 110 | SocksV5AddressType::Ipv6.to_u8(), 111 | )) 112 | })?; 113 | 114 | let host = SocksV5Host::read(&mut reader, atyp).await?; 115 | 116 | reader.read_exact(&mut buf).await?; 117 | let port = NetworkEndian::read_u16(&buf); 118 | 119 | Ok(SocksV5Response { status, port, host }) 120 | } 121 | 122 | #[derive(Debug, Error)] 123 | pub enum SocksV5ConnectError { 124 | #[error("invalid SOCKS version {0:02X}, expected {:02X}", SocksVersion::V5.to_u8())] 125 | InvalidVersion(u8), 126 | #[error("invalid server response: {0}")] 127 | InvalidResponse(String), 128 | #[error("server returned an error: {0:?}")] 129 | ServerError(SocksV5RequestStatus), 130 | #[error("{0}")] 131 | Io( 132 | #[from] 133 | #[source] 134 | std::io::Error, 135 | ), 136 | } 137 | 138 | impl From for SocksV5ConnectError { 139 | fn from(he: SocksV5RequestError) -> Self { 140 | use SocksV5ConnectError::*; 141 | match he { 142 | SocksV5RequestError::InvalidVersion(v) => InvalidVersion(v), 143 | SocksV5RequestError::InvalidRequest(msg) => InvalidResponse(msg), 144 | SocksV5RequestError::Io(e) => Io(e), 145 | } 146 | } 147 | } 148 | 149 | /// As a client, send a CONNECT request to a stream and process the response. 150 | /// 151 | /// # Returns 152 | /// 153 | /// If the server accepts the command, this function returns the stream (that can now be used 154 | /// to communicate with the target through the proxy), as well as the host and port that the proxy 155 | /// server used to connect to the target socket. 156 | /// 157 | /// # Errors 158 | /// 159 | /// - `Io` if either sending the request or receiving the response fails due to I/O error, including a premature EOF. 160 | /// - `InvalidVersion` if the server returns an unexpected version number. 161 | /// - `InvalidResponse` if the server's reply cannot be interpreted (because, for example, it uses 162 | /// an unsupported status code or address type). 163 | /// - `ServerError` if the server returns a non-success status. 164 | pub async fn request_connect( 165 | mut stream: Stream, 166 | target_host: Host, 167 | target_port: u16, 168 | ) -> Result<(Stream, SocksV5Host, u16), SocksV5ConnectError> 169 | where 170 | Stream: AsyncRead + AsyncWrite + Unpin, 171 | Host: Into, 172 | { 173 | write_request( 174 | &mut stream, 175 | SocksV5Command::Connect, 176 | target_host.into(), 177 | target_port, 178 | ) 179 | .await?; 180 | 181 | let response = read_request_status(&mut stream).await?; 182 | if response.status != SocksV5RequestStatus::Success { 183 | return Err(SocksV5ConnectError::ServerError(response.status)); 184 | } 185 | 186 | Ok((stream, response.host, response.port)) 187 | } 188 | 189 | /// As a client, send a BIND request to a stream and process the response. 190 | /// 191 | /// # Returns 192 | /// 193 | /// If the server accepts the command, this function returns a triple consisting of: 194 | /// - a future (that can be used to accept an incoming connection through the proxy); 195 | /// - as well as the host and port that the proxy is listening on. 196 | /// 197 | /// Once an incoming connection to the proxy is made, that "accept" future will resolve into another triple: 198 | /// - a read-write stream, 199 | /// - as well as the host and port of the client that connected to the proxy. 200 | /// 201 | /// The stream can then be used to communicate with the client. 202 | /// 203 | /// # Errors 204 | /// 205 | /// Errors can be returned from both this function and the "accept" future: 206 | /// - `Io` if either sending the request or receiving the response fails due to I/O error, including a premature EOF. 207 | /// - `InvalidVersion` if the server returns an unexpected version number. 208 | /// - `InvalidResponse` if the server's reply cannot be interpreted (because, for example, it uses 209 | /// an unsupported status code or address type). 210 | /// - `ServerError` if the server returns a non-success status. 211 | pub async fn request_bind( 212 | mut stream: Stream, 213 | host: Host, 214 | port: u16, 215 | ) -> Result< 216 | ( 217 | impl Future>, 218 | SocksV5Host, 219 | u16, 220 | ), 221 | SocksV5ConnectError, 222 | > 223 | where 224 | Stream: AsyncRead + AsyncWrite + Unpin, 225 | Host: Into, 226 | { 227 | write_request(&mut stream, SocksV5Command::Bind, host.into(), port).await?; 228 | 229 | let response1 = read_request_status(&mut stream).await?; 230 | if response1.status != SocksV5RequestStatus::Success { 231 | return Err(SocksV5ConnectError::ServerError(response1.status)); 232 | } 233 | 234 | let accepted_fut = async move { 235 | let response2 = read_request_status(&mut stream).await?; 236 | if response2.status != SocksV5RequestStatus::Success { 237 | return Err(SocksV5ConnectError::ServerError(response2.status)); 238 | } 239 | Ok((stream, response2.host, response2.port)) 240 | }; 241 | 242 | Ok((accepted_fut, response1.host, response1.port)) 243 | } 244 | 245 | #[cfg(test)] 246 | mod tests { 247 | use futures::executor::block_on; 248 | 249 | use super::*; 250 | 251 | #[test] 252 | fn write_request_ipv4() { 253 | let mut buf = Vec::::new(); 254 | block_on(write_request( 255 | &mut buf, 256 | SocksV5Command::Connect, 257 | SocksV5Host::Ipv4([127, 0, 0, 1]), 258 | 1080, 259 | )) 260 | .unwrap(); 261 | assert_eq!(buf, &[5, 1, 0, 1, 127, 0, 0, 1, 4, 56]); 262 | } 263 | 264 | #[test] 265 | fn write_request_ipv6() { 266 | let mut buf = Vec::::new(); 267 | block_on(write_request( 268 | &mut buf, 269 | SocksV5Command::Connect, 270 | SocksV5Host::Ipv6([1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6]), 271 | 1080, 272 | )) 273 | .unwrap(); 274 | assert_eq!( 275 | buf, 276 | &[5, 1, 0, 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 4, 56] 277 | ); 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /examples/proxy/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aho-corasick" 5 | version = "0.7.10" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" 8 | dependencies = [ 9 | "memchr", 10 | ] 11 | 12 | [[package]] 13 | name = "anyhow" 14 | version = "1.0.31" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" 17 | 18 | [[package]] 19 | name = "async-std" 20 | version = "1.6.0" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "a45cee2749d880d7066e328a7e161c7470ced883b2fd000ca4643e9f1dd5083a" 23 | dependencies = [ 24 | "async-task", 25 | "crossbeam-utils", 26 | "futures-channel", 27 | "futures-core", 28 | "futures-io", 29 | "futures-timer", 30 | "kv-log-macro", 31 | "log", 32 | "memchr", 33 | "num_cpus", 34 | "once_cell", 35 | "pin-project-lite", 36 | "pin-utils", 37 | "slab", 38 | "smol", 39 | "wasm-bindgen-futures", 40 | ] 41 | 42 | [[package]] 43 | name = "async-task" 44 | version = "3.0.0" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3" 47 | 48 | [[package]] 49 | name = "atty" 50 | version = "0.2.14" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 53 | dependencies = [ 54 | "hermit-abi", 55 | "libc", 56 | "winapi", 57 | ] 58 | 59 | [[package]] 60 | name = "autocfg" 61 | version = "1.0.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 64 | 65 | [[package]] 66 | name = "bitflags" 67 | version = "1.2.1" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 70 | 71 | [[package]] 72 | name = "bumpalo" 73 | version = "3.3.0" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "5356f1d23ee24a1f785a56d1d1a5f0fd5b0f6a0c0fb2412ce11da71649ab78f6" 76 | 77 | [[package]] 78 | name = "byteorder" 79 | version = "1.3.4" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 82 | 83 | [[package]] 84 | name = "cc" 85 | version = "1.0.54" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" 88 | 89 | [[package]] 90 | name = "cfg-if" 91 | version = "0.1.10" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 94 | 95 | [[package]] 96 | name = "crossbeam" 97 | version = "0.7.3" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" 100 | dependencies = [ 101 | "cfg-if", 102 | "crossbeam-channel", 103 | "crossbeam-deque", 104 | "crossbeam-epoch", 105 | "crossbeam-queue", 106 | "crossbeam-utils", 107 | ] 108 | 109 | [[package]] 110 | name = "crossbeam-channel" 111 | version = "0.4.2" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" 114 | dependencies = [ 115 | "crossbeam-utils", 116 | "maybe-uninit", 117 | ] 118 | 119 | [[package]] 120 | name = "crossbeam-deque" 121 | version = "0.7.3" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" 124 | dependencies = [ 125 | "crossbeam-epoch", 126 | "crossbeam-utils", 127 | "maybe-uninit", 128 | ] 129 | 130 | [[package]] 131 | name = "crossbeam-epoch" 132 | version = "0.8.2" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" 135 | dependencies = [ 136 | "autocfg", 137 | "cfg-if", 138 | "crossbeam-utils", 139 | "lazy_static", 140 | "maybe-uninit", 141 | "memoffset", 142 | "scopeguard", 143 | ] 144 | 145 | [[package]] 146 | name = "crossbeam-queue" 147 | version = "0.2.1" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" 150 | dependencies = [ 151 | "cfg-if", 152 | "crossbeam-utils", 153 | ] 154 | 155 | [[package]] 156 | name = "crossbeam-utils" 157 | version = "0.7.2" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 160 | dependencies = [ 161 | "autocfg", 162 | "cfg-if", 163 | "lazy_static", 164 | ] 165 | 166 | [[package]] 167 | name = "env_logger" 168 | version = "0.7.1" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 171 | dependencies = [ 172 | "atty", 173 | "humantime", 174 | "log", 175 | "regex", 176 | "termcolor", 177 | ] 178 | 179 | [[package]] 180 | name = "futures" 181 | version = "0.3.5" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" 184 | dependencies = [ 185 | "futures-channel", 186 | "futures-core", 187 | "futures-executor", 188 | "futures-io", 189 | "futures-sink", 190 | "futures-task", 191 | "futures-util", 192 | ] 193 | 194 | [[package]] 195 | name = "futures-channel" 196 | version = "0.3.5" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" 199 | dependencies = [ 200 | "futures-core", 201 | "futures-sink", 202 | ] 203 | 204 | [[package]] 205 | name = "futures-core" 206 | version = "0.3.5" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" 209 | 210 | [[package]] 211 | name = "futures-executor" 212 | version = "0.3.5" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314" 215 | dependencies = [ 216 | "futures-core", 217 | "futures-task", 218 | "futures-util", 219 | ] 220 | 221 | [[package]] 222 | name = "futures-io" 223 | version = "0.3.5" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" 226 | 227 | [[package]] 228 | name = "futures-macro" 229 | version = "0.3.5" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" 232 | dependencies = [ 233 | "proc-macro-hack", 234 | "proc-macro2", 235 | "quote", 236 | "syn", 237 | ] 238 | 239 | [[package]] 240 | name = "futures-sink" 241 | version = "0.3.5" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" 244 | 245 | [[package]] 246 | name = "futures-task" 247 | version = "0.3.5" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" 250 | dependencies = [ 251 | "once_cell", 252 | ] 253 | 254 | [[package]] 255 | name = "futures-timer" 256 | version = "3.0.2" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" 259 | dependencies = [ 260 | "gloo-timers", 261 | "send_wrapper", 262 | ] 263 | 264 | [[package]] 265 | name = "futures-util" 266 | version = "0.3.5" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" 269 | dependencies = [ 270 | "futures-channel", 271 | "futures-core", 272 | "futures-io", 273 | "futures-macro", 274 | "futures-sink", 275 | "futures-task", 276 | "memchr", 277 | "pin-project", 278 | "pin-utils", 279 | "proc-macro-hack", 280 | "proc-macro-nested", 281 | "slab", 282 | ] 283 | 284 | [[package]] 285 | name = "gloo-timers" 286 | version = "0.2.1" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" 289 | dependencies = [ 290 | "futures-channel", 291 | "futures-core", 292 | "js-sys", 293 | "wasm-bindgen", 294 | "web-sys", 295 | ] 296 | 297 | [[package]] 298 | name = "hermit-abi" 299 | version = "0.1.13" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" 302 | dependencies = [ 303 | "libc", 304 | ] 305 | 306 | [[package]] 307 | name = "humantime" 308 | version = "1.3.0" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 311 | dependencies = [ 312 | "quick-error", 313 | ] 314 | 315 | [[package]] 316 | name = "js-sys" 317 | version = "0.3.39" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "fa5a448de267e7358beaf4a5d849518fe9a0c13fce7afd44b06e68550e5562a7" 320 | dependencies = [ 321 | "wasm-bindgen", 322 | ] 323 | 324 | [[package]] 325 | name = "kv-log-macro" 326 | version = "1.0.6" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "4ff57d6d215f7ca7eb35a9a64d656ba4d9d2bef114d741dc08048e75e2f5d418" 329 | dependencies = [ 330 | "log", 331 | ] 332 | 333 | [[package]] 334 | name = "lazy_static" 335 | version = "1.4.0" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 338 | 339 | [[package]] 340 | name = "libc" 341 | version = "0.2.70" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" 344 | 345 | [[package]] 346 | name = "log" 347 | version = "0.4.8" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 350 | dependencies = [ 351 | "cfg-if", 352 | ] 353 | 354 | [[package]] 355 | name = "maybe-uninit" 356 | version = "2.0.0" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 359 | 360 | [[package]] 361 | name = "memchr" 362 | version = "2.3.3" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 365 | 366 | [[package]] 367 | name = "memoffset" 368 | version = "0.5.4" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" 371 | dependencies = [ 372 | "autocfg", 373 | ] 374 | 375 | [[package]] 376 | name = "nix" 377 | version = "0.17.0" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" 380 | dependencies = [ 381 | "bitflags", 382 | "cc", 383 | "cfg-if", 384 | "libc", 385 | "void", 386 | ] 387 | 388 | [[package]] 389 | name = "num_cpus" 390 | version = "1.13.0" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 393 | dependencies = [ 394 | "hermit-abi", 395 | "libc", 396 | ] 397 | 398 | [[package]] 399 | name = "once_cell" 400 | version = "1.4.0" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" 403 | 404 | [[package]] 405 | name = "pin-project" 406 | version = "0.4.17" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791" 409 | dependencies = [ 410 | "pin-project-internal", 411 | ] 412 | 413 | [[package]] 414 | name = "pin-project-internal" 415 | version = "0.4.17" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" 418 | dependencies = [ 419 | "proc-macro2", 420 | "quote", 421 | "syn", 422 | ] 423 | 424 | [[package]] 425 | name = "pin-project-lite" 426 | version = "0.1.5" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "f7505eeebd78492e0f6108f7171c4948dbb120ee8119d9d77d0afa5469bef67f" 429 | 430 | [[package]] 431 | name = "pin-utils" 432 | version = "0.1.0" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 435 | 436 | [[package]] 437 | name = "piper" 438 | version = "0.1.2" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "6b0deb65f46e873ba8aa7c6a8dbe3f23cb1bf59c339a81a1d56361dde4d66ac8" 441 | dependencies = [ 442 | "crossbeam-utils", 443 | "futures-io", 444 | "futures-sink", 445 | "futures-util", 446 | ] 447 | 448 | [[package]] 449 | name = "proc-macro-hack" 450 | version = "0.5.16" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" 453 | 454 | [[package]] 455 | name = "proc-macro-nested" 456 | version = "0.1.4" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" 459 | 460 | [[package]] 461 | name = "proc-macro2" 462 | version = "1.0.17" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101" 465 | dependencies = [ 466 | "unicode-xid", 467 | ] 468 | 469 | [[package]] 470 | name = "proxy" 471 | version = "0.1.0" 472 | dependencies = [ 473 | "anyhow", 474 | "async-std", 475 | "env_logger", 476 | "futures", 477 | "log", 478 | "socksv5", 479 | ] 480 | 481 | [[package]] 482 | name = "quick-error" 483 | version = "1.2.3" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 486 | 487 | [[package]] 488 | name = "quote" 489 | version = "1.0.6" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" 492 | dependencies = [ 493 | "proc-macro2", 494 | ] 495 | 496 | [[package]] 497 | name = "redox_syscall" 498 | version = "0.1.56" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 501 | 502 | [[package]] 503 | name = "regex" 504 | version = "1.3.7" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" 507 | dependencies = [ 508 | "aho-corasick", 509 | "memchr", 510 | "regex-syntax", 511 | "thread_local", 512 | ] 513 | 514 | [[package]] 515 | name = "regex-syntax" 516 | version = "0.6.17" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" 519 | 520 | [[package]] 521 | name = "scoped-tls-hkt" 522 | version = "0.1.2" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "c2e9d7eaddb227e8fbaaa71136ae0e1e913ca159b86c7da82f3e8f0044ad3a63" 525 | 526 | [[package]] 527 | name = "scopeguard" 528 | version = "1.1.0" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 531 | 532 | [[package]] 533 | name = "send_wrapper" 534 | version = "0.4.0" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" 537 | 538 | [[package]] 539 | name = "slab" 540 | version = "0.4.2" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 543 | 544 | [[package]] 545 | name = "smol" 546 | version = "0.1.10" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "686c634ad1873fffef6aed20f180eede424fbf3bb31802394c90fd7335a661b7" 549 | dependencies = [ 550 | "async-task", 551 | "crossbeam", 552 | "futures-io", 553 | "futures-util", 554 | "nix", 555 | "once_cell", 556 | "piper", 557 | "scoped-tls-hkt", 558 | "slab", 559 | "socket2", 560 | "wepoll-binding", 561 | ] 562 | 563 | [[package]] 564 | name = "socket2" 565 | version = "0.3.12" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" 568 | dependencies = [ 569 | "cfg-if", 570 | "libc", 571 | "redox_syscall", 572 | "winapi", 573 | ] 574 | 575 | [[package]] 576 | name = "socksv5" 577 | version = "0.1.0" 578 | dependencies = [ 579 | "byteorder", 580 | "futures", 581 | "thiserror", 582 | ] 583 | 584 | [[package]] 585 | name = "syn" 586 | version = "1.0.24" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "f87bc5b2815ebb664de0392fdf1b95b6d10e160f86d9f64ff65e5679841ca06a" 589 | dependencies = [ 590 | "proc-macro2", 591 | "quote", 592 | "unicode-xid", 593 | ] 594 | 595 | [[package]] 596 | name = "termcolor" 597 | version = "1.1.0" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" 600 | dependencies = [ 601 | "winapi-util", 602 | ] 603 | 604 | [[package]] 605 | name = "thiserror" 606 | version = "1.0.19" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "b13f926965ad00595dd129fa12823b04bbf866e9085ab0a5f2b05b850fbfc344" 609 | dependencies = [ 610 | "thiserror-impl", 611 | ] 612 | 613 | [[package]] 614 | name = "thiserror-impl" 615 | version = "1.0.19" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479" 618 | dependencies = [ 619 | "proc-macro2", 620 | "quote", 621 | "syn", 622 | ] 623 | 624 | [[package]] 625 | name = "thread_local" 626 | version = "1.0.1" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 629 | dependencies = [ 630 | "lazy_static", 631 | ] 632 | 633 | [[package]] 634 | name = "unicode-xid" 635 | version = "0.2.0" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 638 | 639 | [[package]] 640 | name = "void" 641 | version = "1.0.2" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 644 | 645 | [[package]] 646 | name = "wasm-bindgen" 647 | version = "0.2.62" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "e3c7d40d09cdbf0f4895ae58cf57d92e1e57a9dd8ed2e8390514b54a47cc5551" 650 | dependencies = [ 651 | "cfg-if", 652 | "wasm-bindgen-macro", 653 | ] 654 | 655 | [[package]] 656 | name = "wasm-bindgen-backend" 657 | version = "0.2.62" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "c3972e137ebf830900db522d6c8fd74d1900dcfc733462e9a12e942b00b4ac94" 660 | dependencies = [ 661 | "bumpalo", 662 | "lazy_static", 663 | "log", 664 | "proc-macro2", 665 | "quote", 666 | "syn", 667 | "wasm-bindgen-shared", 668 | ] 669 | 670 | [[package]] 671 | name = "wasm-bindgen-futures" 672 | version = "0.4.12" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "8a369c5e1dfb7569e14d62af4da642a3cbc2f9a3652fe586e26ac22222aa4b04" 675 | dependencies = [ 676 | "cfg-if", 677 | "js-sys", 678 | "wasm-bindgen", 679 | "web-sys", 680 | ] 681 | 682 | [[package]] 683 | name = "wasm-bindgen-macro" 684 | version = "0.2.62" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "2cd85aa2c579e8892442954685f0d801f9129de24fa2136b2c6a539c76b65776" 687 | dependencies = [ 688 | "quote", 689 | "wasm-bindgen-macro-support", 690 | ] 691 | 692 | [[package]] 693 | name = "wasm-bindgen-macro-support" 694 | version = "0.2.62" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "8eb197bd3a47553334907ffd2f16507b4f4f01bbec3ac921a7719e0decdfe72a" 697 | dependencies = [ 698 | "proc-macro2", 699 | "quote", 700 | "syn", 701 | "wasm-bindgen-backend", 702 | "wasm-bindgen-shared", 703 | ] 704 | 705 | [[package]] 706 | name = "wasm-bindgen-shared" 707 | version = "0.2.62" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "a91c2916119c17a8e316507afaaa2dd94b47646048014bbdf6bef098c1bb58ad" 710 | 711 | [[package]] 712 | name = "web-sys" 713 | version = "0.3.39" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "8bc359e5dd3b46cb9687a051d50a2fdd228e4ba7cf6fcf861a5365c3d671a642" 716 | dependencies = [ 717 | "js-sys", 718 | "wasm-bindgen", 719 | ] 720 | 721 | [[package]] 722 | name = "wepoll-binding" 723 | version = "2.0.2" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "374fff4ff9701ff8b6ad0d14bacd3156c44063632d8c136186ff5967d48999a7" 726 | dependencies = [ 727 | "bitflags", 728 | "wepoll-sys", 729 | ] 730 | 731 | [[package]] 732 | name = "wepoll-sys" 733 | version = "2.0.0" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "9082a777aed991f6769e2b654aa0cb29f1c3d615daf009829b07b66c7aff6a24" 736 | dependencies = [ 737 | "cc", 738 | ] 739 | 740 | [[package]] 741 | name = "winapi" 742 | version = "0.3.8" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 745 | dependencies = [ 746 | "winapi-i686-pc-windows-gnu", 747 | "winapi-x86_64-pc-windows-gnu", 748 | ] 749 | 750 | [[package]] 751 | name = "winapi-i686-pc-windows-gnu" 752 | version = "0.4.0" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 755 | 756 | [[package]] 757 | name = "winapi-util" 758 | version = "0.1.5" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 761 | dependencies = [ 762 | "winapi", 763 | ] 764 | 765 | [[package]] 766 | name = "winapi-x86_64-pc-windows-gnu" 767 | version = "0.4.0" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 770 | --------------------------------------------------------------------------------