├── .gitignore ├── src ├── lib.rs ├── error.rs ├── framed_ext.rs ├── bin │ └── yimu.rs ├── dns.rs ├── auth.rs ├── server.rs └── socks5.rs ├── README.md ├── .github └── workflows │ └── rust.yml ├── Cargo.toml └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | 4 | *.swp 5 | .idea/ 6 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod auth; 2 | pub mod dns; 3 | pub mod error; 4 | pub mod framed_ext; 5 | pub mod server; 6 | pub mod socks5; 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | yimu-rs 2 | ======= 3 | 4 | `yimu` is a socks5 server implemented with Rust and `tokio`. 5 | 6 | References: 7 | * [SOCKS Protocol Version 5](https://tools.ietf.org/html/rfc1928) 8 | * [Username/Password Authentication for SOCKS V5](https://tools.ietf.org/html/rfc1929) 9 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yimu" 3 | version = "0.1.0" 4 | authors = ["yfaming "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | anyhow = "1.0.38" 11 | async-trait = "0.1" 12 | bytes = "1" 13 | clap = { version = "3", features = ["derive", "env"] } 14 | dyn-clone = "1.0.1" 15 | env_logger = "0.7.1" 16 | futures = "0.3" 17 | log = "0.4.8" 18 | num_enum = "0.5.1" 19 | tokio = { version = "1", features = ["full"] } 20 | tokio-util = { version = "0.7", features = ["codec"] } 21 | trust-dns-resolver = "0.21" 22 | thiserror = "1.0" 23 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use thiserror::Error; 3 | use trust_dns_resolver::error::ResolveError; 4 | 5 | pub type YimuError = anyhow::Error; 6 | 7 | #[derive(Debug, Error)] 8 | pub enum Socks5Error { 9 | #[error("auth failed")] 10 | AuthFailed, 11 | #[error("invalid cmd: {0}")] 12 | InvalidCmd(u8), 13 | #[error("invalid address type: {0}")] 14 | InvalidAddrType(u8), 15 | #[error("invalid domain name")] 16 | InvalidDomainName, 17 | #[error("resolve error: {0}")] 18 | ResolveError(#[from] ResolveError), 19 | #[error("io error: {0}")] 20 | Io(#[from] io::Error), 21 | } 22 | 23 | impl Socks5Error { 24 | pub fn invalid_data() -> Socks5Error { 25 | Socks5Error::Io(io::Error::from(io::ErrorKind::InvalidData)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/framed_ext.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use futures::sink::SinkExt; 3 | use futures::stream::StreamExt; 4 | use std::convert::From; 5 | use std::io; 6 | use tokio::io::{AsyncRead, AsyncWrite}; 7 | use tokio_util::codec::{Decoder, Encoder, Framed, FramedParts}; 8 | 9 | fn unexpected_eof() -> io::Error { 10 | io::Error::from(io::ErrorKind::UnexpectedEof) 11 | } 12 | 13 | /// Framed impls Stream/Sink, so it's suitably used in a loop. 14 | /// FramedExt provides easy to use methods for us to read/write a single item. 15 | #[async_trait] 16 | pub trait FramedExt { 17 | type Decoder: Decoder; 18 | type Encoder: Encoder; 19 | 20 | async fn framed_read( 21 | &mut self, 22 | ) -> Result<::Item, ::Error>; 23 | 24 | async fn framed_write( 25 | &mut self, 26 | item: Item, 27 | ) -> Result<(), >::Error>; 28 | } 29 | 30 | #[async_trait] 31 | impl FramedExt for Framed 32 | where 33 | T: AsyncRead + AsyncWrite + Unpin + Send, 34 | U: Decoder + Encoder + Unpin + Send, 35 | I: Send + 'static, 36 | { 37 | type Decoder = U; 38 | type Encoder = U; 39 | 40 | async fn framed_read(&mut self) -> Result<::Item, ::Error> { 41 | self.next() 42 | .await 43 | .unwrap_or(Err(From::from(unexpected_eof()))) 44 | } 45 | 46 | async fn framed_write(&mut self, item: I) -> Result<(), >::Error> { 47 | self.send(item).await 48 | } 49 | } 50 | 51 | /// FramedExt2 let us replace codec with a new one. 52 | pub trait FramedExt2 { 53 | fn replace_codec(self, new_codec: V) -> Framed 54 | where 55 | V: Encoder; 56 | } 57 | 58 | impl FramedExt2 for Framed { 59 | fn replace_codec(self, new_codec: V) -> Framed 60 | where 61 | V: Encoder, 62 | { 63 | let parts = self.into_parts(); 64 | let mut new_parts = FramedParts::new(parts.io, new_codec); 65 | new_parts.read_buf = parts.read_buf; 66 | new_parts.write_buf = parts.write_buf; 67 | Framed::from_parts(new_parts) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/bin/yimu.rs: -------------------------------------------------------------------------------- 1 | use std::net::{IpAddr, Ipv4Addr}; 2 | use std::process; 3 | use yimu::auth::{NoAuth, UsernamePasswordAuth}; 4 | use yimu::dns::Dns; 5 | use yimu::error::YimuError; 6 | use yimu::server::Server; 7 | 8 | #[derive(Debug, clap::Parser)] 9 | #[clap(name = "yimu", about = "yimu is a socks5 server")] 10 | pub struct Opt { 11 | #[clap( 12 | long = "port", 13 | default_value = "9011", 14 | help = "the port yimu listens to" 15 | )] 16 | pub port: u16, 17 | 18 | #[clap( 19 | long = "dns", 20 | default_value = "google", 21 | help = "the dns resolver to use. allowed values are: system, google, cloudflare, quad9, and ." 22 | )] 23 | pub dns: Dns, 24 | 25 | #[clap( 26 | long = "no_auth", 27 | help = "if specified, it's allowed to connect to yimu without password. \ 28 | Note: can be used together with auth_username and auth_password" 29 | )] 30 | pub no_auth: bool, 31 | 32 | #[clap( 33 | long = "auth_username", 34 | help = "specify username for authentication, should be used along with auth_password" 35 | )] 36 | pub auth_username: Option, 37 | 38 | #[clap( 39 | long = "auth_password", 40 | help = "specify password for authentication, should be used along with auth_username" 41 | )] 42 | pub auth_password: Option, 43 | } 44 | 45 | #[tokio::main] 46 | async fn main() -> Result<(), YimuError> { 47 | env_logger::init(); 48 | 49 | let opt: Opt = clap::Parser::parse(); 50 | if (opt.auth_username.is_some() && opt.auth_password.is_none()) 51 | || (opt.auth_username.is_none() && opt.auth_password.is_some()) 52 | { 53 | println!("auth_username and auth_password should both be specified."); 54 | process::exit(1); 55 | } 56 | 57 | let mut builder = Server::builder() 58 | .ip(IpAddr::V4(Ipv4Addr::UNSPECIFIED)) 59 | .port(opt.port) 60 | .dns(opt.dns); 61 | 62 | if opt.no_auth { 63 | builder = builder.add_authenticator(NoAuth); 64 | } 65 | if opt.auth_username.is_some() { 66 | let username = opt.auth_username.clone().unwrap(); 67 | let password = opt.auth_password.clone().unwrap(); 68 | builder = builder.add_authenticator(UsernamePasswordAuth::new(username, password)); 69 | } 70 | 71 | let server = builder.build().await?; 72 | server.run().await?; 73 | Ok(()) 74 | } 75 | -------------------------------------------------------------------------------- /src/dns.rs: -------------------------------------------------------------------------------- 1 | use crate::error::YimuError; 2 | use crate::socks5::Addr; 3 | use async_trait::async_trait; 4 | use std::net::{IpAddr, SocketAddr}; 5 | use std::str::FromStr; 6 | use thiserror::Error; 7 | use trust_dns_resolver::config::{NameServerConfig, Protocol, ResolverConfig, ResolverOpts}; 8 | use trust_dns_resolver::error::ResolveError; 9 | use trust_dns_resolver::TokioAsyncResolver; 10 | 11 | #[async_trait] 12 | pub trait AsyncResolverExt { 13 | async fn dns_resolve(&self, addr: &Addr) -> Result, ResolveError>; 14 | } 15 | 16 | #[async_trait] 17 | impl AsyncResolverExt for TokioAsyncResolver { 18 | async fn dns_resolve(&self, addr: &Addr) -> Result, ResolveError> { 19 | match addr { 20 | Addr::Ip(ip) => Ok(vec![*ip]), 21 | Addr::Domain(domain) => Ok(self 22 | .lookup_ip(domain.as_str()) 23 | .await? 24 | .iter() 25 | .collect::>()), 26 | } 27 | } 28 | } 29 | 30 | #[derive(Debug, Clone)] 31 | pub enum Dns { 32 | System, 33 | Google, 34 | Cloudflare, 35 | Quad9, 36 | NameServer(SocketAddr), 37 | } 38 | 39 | impl Dns { 40 | pub fn new() -> Dns { 41 | Dns::System 42 | } 43 | 44 | pub async fn create_resolver(&self) -> Result { 45 | let opts = ResolverOpts::default(); 46 | let resolver = match self { 47 | Dns::System => TokioAsyncResolver::tokio_from_system_conf()?, 48 | Dns::Google => TokioAsyncResolver::tokio(ResolverConfig::google(), opts)?, 49 | Dns::Cloudflare => TokioAsyncResolver::tokio(ResolverConfig::cloudflare(), opts)?, 50 | Dns::Quad9 => TokioAsyncResolver::tokio(ResolverConfig::quad9(), opts)?, 51 | Dns::NameServer(socket_addr) => { 52 | let mut config = ResolverConfig::new(); 53 | config.add_name_server(NameServerConfig { 54 | socket_addr: *socket_addr, 55 | protocol: Protocol::Udp, 56 | tls_dns_name: None, 57 | trust_nx_responses: true, 58 | bind_addr: None, 59 | }); 60 | TokioAsyncResolver::tokio(config, opts)? 61 | } 62 | }; 63 | Ok(resolver) 64 | } 65 | } 66 | 67 | #[derive(Debug, Error)] 68 | #[error("parse dns error")] 69 | pub struct ParseDnsError; 70 | 71 | impl FromStr for Dns { 72 | type Err = ParseDnsError; 73 | fn from_str(s: &str) -> Result { 74 | match s { 75 | "system" => { 76 | return Ok(Dns::System); 77 | } 78 | "google" => { 79 | return Ok(Dns::Google); 80 | } 81 | "cloudflare" => { 82 | return Ok(Dns::Cloudflare); 83 | } 84 | "quad9" => { 85 | return Ok(Dns::Quad9); 86 | } 87 | _ => {} 88 | } 89 | 90 | if let Ok(socket_addr) = SocketAddr::from_str(s) { 91 | return Ok(Dns::NameServer(socket_addr)); 92 | } 93 | if let Ok(ip) = IpAddr::from_str(s) { 94 | let socket_addr = SocketAddr::from((ip, 53)); 95 | return Ok(Dns::NameServer(socket_addr)); 96 | } 97 | return Err(ParseDnsError); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/auth.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Socks5Error, YimuError}; 2 | use crate::framed_ext::FramedExt; 3 | use crate::socks5; 4 | use async_trait::async_trait; 5 | use bytes::{BufMut, BytesMut}; 6 | use dyn_clone::DynClone; 7 | use std::str; 8 | use tokio::net::TcpStream; 9 | use tokio_util::codec::{Decoder, Encoder, Framed}; 10 | 11 | #[async_trait] 12 | pub trait Authenticate: DynClone + Sync + Send { 13 | fn method(&self) -> u8; 14 | 15 | /// authenticate the stream. 16 | async fn auth(&self, stream: TcpStream) -> Result; 17 | } 18 | 19 | #[derive(Debug, Clone, Copy)] 20 | pub struct NoAuth; 21 | 22 | #[async_trait] 23 | impl Authenticate for NoAuth { 24 | fn method(&self) -> u8 { 25 | socks5::S5AUTH_NO_AUTHENTICATION_REQUIRED 26 | } 27 | 28 | async fn auth(&self, stream: TcpStream) -> Result { 29 | Ok(stream) 30 | } 31 | } 32 | 33 | /// [RFC1929: Username/Password Authentication for SOCKS V5](https://tools.ietf.org/html/rfc1929) 34 | #[derive(Clone)] 35 | pub struct UsernamePasswordAuth { 36 | username: String, 37 | password: String, 38 | } 39 | 40 | impl UsernamePasswordAuth { 41 | pub fn new(username: String, password: String) -> UsernamePasswordAuth { 42 | UsernamePasswordAuth { username, password } 43 | } 44 | } 45 | 46 | #[async_trait] 47 | impl Authenticate for UsernamePasswordAuth { 48 | fn method(&self) -> u8 { 49 | socks5::S5AUTH_USERNAME_PASSWORD 50 | } 51 | 52 | async fn auth(&self, stream: TcpStream) -> Result { 53 | let mut transport = Framed::new(stream, UsernamePasswordAuthCodec); 54 | let req = transport.framed_read().await?; 55 | 56 | if req.username == self.username && req.password == self.password { 57 | let res = UsernamePasswordAuthResponse::success(); 58 | transport.framed_write(res).await?; 59 | Ok(transport.into_inner()) 60 | } else { 61 | let res = UsernamePasswordAuthResponse::fail(); 62 | transport.framed_write(res).await?; 63 | Err(Socks5Error::AuthFailed.into()) 64 | } 65 | } 66 | } 67 | 68 | #[derive(Debug, Clone, PartialEq)] 69 | pub struct UsernamePasswordAuthRequest { 70 | pub username: String, 71 | pub password: String, 72 | } 73 | 74 | impl UsernamePasswordAuthRequest { 75 | pub fn new(username: &str, password: &str) -> UsernamePasswordAuthRequest { 76 | UsernamePasswordAuthRequest { 77 | username: username.to_string(), 78 | password: password.to_string(), 79 | } 80 | } 81 | } 82 | 83 | #[derive(Debug, Clone, PartialEq)] 84 | pub struct UsernamePasswordAuthResponse { 85 | pub version: u8, 86 | pub status: u8, 87 | } 88 | 89 | impl UsernamePasswordAuthResponse { 90 | const STATUS_SUCCESS: u8 = 0; 91 | const STATUS_FAIL: u8 = 1; 92 | 93 | pub fn success() -> UsernamePasswordAuthResponse { 94 | UsernamePasswordAuthResponse { 95 | version: socks5::SOCKSV5, 96 | status: Self::STATUS_SUCCESS, 97 | } 98 | } 99 | pub fn fail() -> UsernamePasswordAuthResponse { 100 | UsernamePasswordAuthResponse { 101 | version: socks5::SOCKSV5, 102 | status: Self::STATUS_FAIL, 103 | } 104 | } 105 | } 106 | 107 | #[derive(Debug, Clone, Copy)] 108 | pub struct UsernamePasswordAuthCodec; 109 | 110 | impl Decoder for UsernamePasswordAuthCodec { 111 | type Item = UsernamePasswordAuthRequest; 112 | type Error = Socks5Error; 113 | 114 | // +----+------+----------+------+----------+ 115 | // |VER | ULEN | UNAME | PLEN | PASSWD | 116 | // +----+------+----------+------+----------+ 117 | // | 1 | 1 | 1 to 255 | 1 | 1 to 255 | 118 | // +----+------+----------+------+----------+ 119 | fn decode( 120 | &mut self, 121 | src: &mut BytesMut, 122 | ) -> Result, Socks5Error> { 123 | // VER + ULEN 124 | if src.len() <= 2 { 125 | return Ok(None); 126 | } 127 | 128 | let ulen = src[1] as usize; 129 | // VER + ULEN + UNAME + PLEN 130 | if src.len() < 3 + ulen { 131 | return Ok(None); 132 | } 133 | 134 | let plen = src[2 + ulen] as usize; 135 | // VER + ULEN + UNAME + PLEN + PASSWD 136 | if src.len() < 3 + ulen + plen { 137 | return Ok(None); 138 | } 139 | 140 | let uname = str::from_utf8(&src[2..2 + ulen]).map_err(|_| Socks5Error::invalid_data())?; 141 | let passwd_start = 3 + ulen; 142 | let passwd = str::from_utf8(&src[passwd_start..passwd_start + plen]) 143 | .map_err(|_| Socks5Error::invalid_data())?; 144 | Ok(Some(UsernamePasswordAuthRequest::new(uname, passwd))) 145 | } 146 | } 147 | 148 | impl Encoder for UsernamePasswordAuthCodec { 149 | type Error = Socks5Error; 150 | 151 | fn encode( 152 | &mut self, 153 | item: UsernamePasswordAuthResponse, 154 | dst: &mut BytesMut, 155 | ) -> Result<(), Socks5Error> { 156 | dst.reserve(2); 157 | dst.put_u8(item.version); 158 | dst.put_u8(item.status); 159 | Ok(()) 160 | } 161 | } 162 | 163 | #[cfg(test)] 164 | mod tests { 165 | use super::*; 166 | 167 | #[test] 168 | fn test_decode_username_password_auth_request() -> Result<(), Socks5Error> { 169 | let raw: &[u8] = b"\x05\x08username\x08password"; 170 | let mut buf = BytesMut::from(raw); 171 | let req = UsernamePasswordAuthCodec.decode(&mut buf)?; 172 | assert_eq!( 173 | req, 174 | Some(UsernamePasswordAuthRequest::new("username", "password")) 175 | ); 176 | Ok(()) 177 | } 178 | 179 | #[test] 180 | fn test_encode_username_password_auth_response() -> Result<(), Socks5Error> { 181 | let reply = UsernamePasswordAuthResponse::success(); 182 | let mut buf = BytesMut::new(); 183 | UsernamePasswordAuthCodec.encode(reply, &mut buf)?; 184 | assert_eq!(buf.as_ref(), b"\x05\x00"); 185 | Ok(()) 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | use crate::auth::Authenticate; 2 | use crate::dns::{AsyncResolverExt, Dns}; 3 | use crate::error::{Socks5Error, YimuError}; 4 | use crate::framed_ext::FramedExt; 5 | use crate::socks5::{AuthNegoReply, AuthNegoRequest, NegotiateCodec, REP_SUCCEEDED}; 6 | use crate::socks5::{Cmd, Reply, Request, Socks5Codec, S5AUTH_NO_ACCEPTABLE_METHODS}; 7 | use dyn_clone::clone_box; 8 | use futures::future::try_join; 9 | use futures::future::FutureExt; 10 | use log::{error, info}; 11 | use std::io; 12 | use std::net::{IpAddr, Ipv4Addr, SocketAddr}; 13 | use std::sync::Arc; 14 | use tokio::io::{copy, split}; 15 | use tokio::net::{TcpListener, TcpStream}; 16 | use tokio_util::codec::Framed; 17 | use trust_dns_resolver::error::ResolveError; 18 | use trust_dns_resolver::TokioAsyncResolver; 19 | 20 | pub struct Server { 21 | state: Arc, 22 | } 23 | 24 | impl Server { 25 | pub fn builder() -> Builder { 26 | Builder::new() 27 | } 28 | } 29 | 30 | pub struct State { 31 | listen_addr: SocketAddr, 32 | authenticators: Vec>, 33 | resolver: TokioAsyncResolver, 34 | } 35 | 36 | impl State { 37 | pub fn resolver(&self) -> TokioAsyncResolver { 38 | self.resolver.clone() 39 | } 40 | 41 | pub fn authenticator(&self, auth_nego_req: &AuthNegoRequest) -> Option> { 42 | for auth in &self.authenticators { 43 | for method in &auth_nego_req.methods { 44 | if auth.method() == *method { 45 | return Some(clone_box(auth.as_ref())); 46 | } 47 | } 48 | } 49 | None 50 | } 51 | } 52 | 53 | pub struct Builder { 54 | ip: IpAddr, 55 | port: u16, 56 | authenticators: Vec>, 57 | dns: Dns, 58 | } 59 | 60 | impl Builder { 61 | pub fn new() -> Builder { 62 | Builder { 63 | ip: IpAddr::V4(Ipv4Addr::UNSPECIFIED), 64 | port: 0, 65 | authenticators: vec![], 66 | dns: Dns::new(), 67 | } 68 | } 69 | 70 | pub async fn build(self) -> Result { 71 | let state = State { 72 | listen_addr: SocketAddr::new(self.ip, self.port), 73 | authenticators: self.authenticators, 74 | resolver: self.dns.create_resolver().await?, 75 | }; 76 | 77 | Ok(Server { 78 | state: Arc::new(state), 79 | }) 80 | } 81 | 82 | pub fn ip(mut self, ip: IpAddr) -> Builder { 83 | self.ip = ip; 84 | self 85 | } 86 | 87 | pub fn port(mut self, port: u16) -> Builder { 88 | self.port = port; 89 | self 90 | } 91 | 92 | pub fn add_authenticator(mut self, authenticator: T) -> Builder 93 | where 94 | T: Authenticate + Send + Sync + 'static, 95 | { 96 | self.authenticators.push(Box::new(authenticator)); 97 | self 98 | } 99 | 100 | pub fn dns(mut self, dns: Dns) -> Builder { 101 | self.dns = dns; 102 | self 103 | } 104 | } 105 | 106 | impl Server { 107 | pub async fn run(&self) -> Result<(), io::Error> { 108 | let listen_addr = self.state.listen_addr; 109 | let listener = TcpListener::bind(listen_addr).await?; 110 | info!("listening on: {}", listen_addr); 111 | 112 | loop { 113 | if let Ok((stream, client_addr)) = listener.accept().await { 114 | info!("new client: {}", client_addr); 115 | tokio::spawn(handle(stream, client_addr, self.state.clone()).map(|_| {})); 116 | } 117 | } 118 | } 119 | } 120 | 121 | async fn handle( 122 | stream: TcpStream, 123 | client_addr: SocketAddr, 124 | state: Arc, 125 | ) -> Result<(), YimuError> { 126 | // negotiation and authentication 127 | let mut transport = Framed::new(stream, NegotiateCodec); 128 | let auth_nego_req = transport.framed_read().await?; 129 | let stream = match state.authenticator(&auth_nego_req) { 130 | Some(authenticator) => { 131 | let auth_nego_res = AuthNegoReply::new(authenticator.method()); 132 | transport.framed_write(auth_nego_res).await?; 133 | authenticator.auth(transport.into_inner()).await? 134 | } 135 | None => { 136 | info!("no acceptable auth method for client {}", client_addr); 137 | let auth_nego_res = AuthNegoReply::new(S5AUTH_NO_ACCEPTABLE_METHODS); 138 | transport.framed_write(auth_nego_res).await?; 139 | return Ok(()); 140 | } 141 | }; 142 | 143 | // request process 144 | let mut transport = Framed::new(stream, Socks5Codec); 145 | let req = transport.framed_read().await?; 146 | info!("client {}, request: {}", client_addr, req); 147 | let (remote_stream, reply) = match handle_request(client_addr, &req, &state.resolver()).await { 148 | Ok((remote_stream, reply)) => (remote_stream, reply), 149 | Err(e) => { 150 | transport.framed_write(Reply::from(e)).await?; 151 | return Ok(()); 152 | } 153 | }; 154 | transport.framed_write(reply).await?; 155 | 156 | // now do proxy 157 | let mut stream = transport.into_inner(); 158 | let (mut ri, mut wi) = stream.split(); 159 | let (mut ro, mut wo) = split(remote_stream); 160 | 161 | let client_to_server = copy(&mut ri, &mut wo); 162 | let server_to_client = copy(&mut ro, &mut wi); 163 | 164 | try_join(client_to_server, server_to_client).await?; 165 | Ok(()) 166 | } 167 | 168 | pub async fn handle_request( 169 | client_addr: SocketAddr, 170 | req: &Request, 171 | resolver: &TokioAsyncResolver, 172 | ) -> Result<(TcpStream, Reply), Socks5Error> { 173 | if req.cmd != Cmd::Connect { 174 | // only cmd CONNECT is supported for now. 175 | error!("client {}, request not supported: {}", client_addr, req); 176 | return Err(Socks5Error::InvalidCmd(req.cmd as u8)); 177 | } 178 | 179 | let target_ips = resolver.dns_resolve(&req.dest_addr).await?; 180 | if target_ips.is_empty() { 181 | error!( 182 | "client {}, no DNS records found for: {}", 183 | client_addr, req.dest_addr 184 | ); 185 | return Err(ResolveError::from("no DNS records found").into()); 186 | } 187 | 188 | let target_sockaddr = SocketAddr::new(target_ips[0], req.dest_port); 189 | info!("client {}, connecting to: {}", client_addr, target_sockaddr); 190 | let remote_stream = TcpStream::connect(target_sockaddr) 191 | .await 192 | .map_err(move |err| { 193 | error!( 194 | "client {}, connecting to {} failed, error: {}", 195 | client_addr, target_sockaddr, err 196 | ); 197 | err 198 | })?; 199 | let reply = Reply::new(REP_SUCCEEDED, remote_stream.local_addr()?); 200 | Ok((remote_stream, reply)) 201 | } 202 | -------------------------------------------------------------------------------- /src/socks5.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Socks5Error; 2 | use bytes::{Buf, BufMut, BytesMut}; 3 | use num_enum::TryFromPrimitive; 4 | use std::convert::TryFrom; 5 | use std::fmt; 6 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; 7 | use std::str; 8 | use tokio_util::codec::{Decoder, Encoder}; 9 | 10 | pub const SOCKSV5: u8 = 5; 11 | 12 | pub const S5AUTH_NO_AUTHENTICATION_REQUIRED: u8 = 0; 13 | pub const S5AUTH_GSSAPI: u8 = 1; 14 | pub const S5AUTH_USERNAME_PASSWORD: u8 = 2; 15 | pub const S5AUTH_NO_ACCEPTABLE_METHODS: u8 = 255; 16 | 17 | #[derive(Debug, Clone, PartialEq)] 18 | pub struct AuthNegoRequest { 19 | pub version: u8, 20 | pub nmethods: u8, 21 | pub methods: Vec, 22 | } 23 | 24 | #[derive(Debug, Copy, Clone, PartialEq)] 25 | pub struct AuthNegoReply { 26 | pub version: u8, 27 | pub method: u8, 28 | } 29 | impl AuthNegoReply { 30 | pub fn new(method: u8) -> AuthNegoReply { 31 | AuthNegoReply { 32 | version: SOCKSV5, 33 | method, 34 | } 35 | } 36 | } 37 | 38 | #[derive(Debug, Clone, Copy)] 39 | pub struct NegotiateCodec; 40 | 41 | impl Decoder for NegotiateCodec { 42 | type Item = AuthNegoRequest; 43 | type Error = Socks5Error; 44 | 45 | // +----+----------+----------+ 46 | // |VER | NMETHODS | METHODS | 47 | // +----+----------+----------+ 48 | // | 1 | 1 | 1 to 255 | 49 | // +----+----------+----------+ 50 | fn decode(&mut self, src: &mut BytesMut) -> Result, Socks5Error> { 51 | if src.len() >= 2 && src.len() >= src[1] as usize + 2 { 52 | let nmethods = src[1]; 53 | let req = AuthNegoRequest { 54 | version: src[0], 55 | nmethods: nmethods, 56 | methods: Vec::from(&src[2..2 + nmethods as usize]), 57 | }; 58 | src.advance(2 + nmethods as usize); 59 | return Ok(Some(req)); 60 | } 61 | Ok(None) 62 | } 63 | } 64 | 65 | impl Encoder for NegotiateCodec { 66 | type Error = Socks5Error; 67 | 68 | // +----+--------+ 69 | // |VER | METHOD | 70 | // +----+--------+ 71 | // | 1 | 1 | 72 | // +----+--------+ 73 | fn encode(&mut self, item: AuthNegoReply, dst: &mut BytesMut) -> Result<(), Socks5Error> { 74 | dst.reserve(2); 75 | dst.put_u8(item.version); 76 | dst.put_u8(item.method); 77 | Ok(()) 78 | } 79 | } 80 | 81 | #[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)] 82 | #[repr(u8)] 83 | pub enum Cmd { 84 | Connect = 1, 85 | Bind = 2, 86 | UdpAssociate = 3, 87 | } 88 | 89 | #[derive(Debug, Clone, Copy, PartialEq, TryFromPrimitive)] 90 | #[repr(u8)] 91 | pub enum AddrType { 92 | V4 = 1, 93 | Domain = 3, 94 | V6 = 4, 95 | } 96 | 97 | #[derive(Debug, Clone, PartialEq)] 98 | pub enum Addr { 99 | Ip(IpAddr), 100 | Domain(String), 101 | } 102 | 103 | impl Addr { 104 | pub fn addr_type(&self) -> AddrType { 105 | use Addr::*; 106 | match self { 107 | Ip(IpAddr::V4(_)) => AddrType::V4, 108 | Ip(IpAddr::V6(_)) => AddrType::V6, 109 | Domain(_) => AddrType::Domain, 110 | } 111 | } 112 | 113 | pub fn wire_len(&self) -> usize { 114 | use Addr::*; 115 | match self { 116 | Ip(IpAddr::V4(_)) => 4, 117 | Ip(IpAddr::V6(_)) => 16, 118 | Domain(domain) => domain.len() + 1, 119 | } 120 | } 121 | } 122 | 123 | impl fmt::Display for Addr { 124 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 125 | match self { 126 | Addr::Ip(ipaddr) => ipaddr.fmt(f), 127 | Addr::Domain(domain) => domain.fmt(f), 128 | } 129 | } 130 | } 131 | 132 | #[derive(Debug, Clone, PartialEq)] 133 | pub struct Request { 134 | pub version: u8, 135 | pub cmd: Cmd, 136 | pub rsv: u8, 137 | pub dest_addr: Addr, 138 | pub dest_port: u16, 139 | } 140 | 141 | #[derive(Debug, Clone, PartialEq)] 142 | pub struct Reply { 143 | pub version: u8, 144 | pub reply_code: u8, 145 | pub rsv: u8, 146 | pub bind_addr: Addr, 147 | pub bind_port: u16, 148 | } 149 | 150 | impl fmt::Display for Request { 151 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 152 | match self.cmd { 153 | Cmd::Connect => write!(f, "CONNECT {}:{}", self.dest_addr, self.dest_port), 154 | Cmd::Bind => write!(f, "BIND"), 155 | Cmd::UdpAssociate => write!(f, "UDP ASSOCIATE"), 156 | } 157 | } 158 | } 159 | 160 | // reply code 161 | pub const REP_SUCCEEDED: u8 = 0; 162 | pub const REP_GENERAL_FAILURE: u8 = 1; 163 | pub const REP_CONNECTION_NOT_ALLOWED: u8 = 2; 164 | pub const REP_NETWORK_UNREACHABLE: u8 = 3; 165 | pub const REP_HOST_UNREACHABLE: u8 = 4; 166 | pub const REP_CONNECTION_REFUSED: u8 = 5; 167 | pub const REP_TTL_EXPIRED: u8 = 6; 168 | pub const REP_COMMAND_NOT_SUPPORTED: u8 = 7; 169 | pub const REP_ADDRESS_TYPE_NOT_SUPPORTED: u8 = 8; 170 | // 9 - 255 not assigned 171 | 172 | pub fn socks5_error_to_reply_code(e: &Socks5Error) -> u8 { 173 | use Socks5Error::*; 174 | match e { 175 | InvalidCmd(_) => REP_COMMAND_NOT_SUPPORTED, 176 | InvalidAddrType(_) => REP_ADDRESS_TYPE_NOT_SUPPORTED, 177 | _ => REP_GENERAL_FAILURE, 178 | } 179 | } 180 | 181 | impl Reply { 182 | pub fn new(code: u8, sockaddr: SocketAddr) -> Reply { 183 | Reply { 184 | version: SOCKSV5, 185 | reply_code: code, 186 | rsv: 0, 187 | bind_addr: Addr::Ip(sockaddr.ip()), 188 | bind_port: sockaddr.port(), 189 | } 190 | } 191 | } 192 | 193 | impl From for Reply { 194 | fn from(e: Socks5Error) -> Reply { 195 | let code = socks5_error_to_reply_code(&e); 196 | let sockaddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0); 197 | Reply::new(code, sockaddr) 198 | } 199 | } 200 | 201 | #[derive(Debug, Clone, Copy)] 202 | pub struct Socks5Codec; 203 | 204 | impl Decoder for Socks5Codec { 205 | type Item = Request; 206 | type Error = Socks5Error; 207 | 208 | // +----+-----+-------+------+----------+----------+ 209 | // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | 210 | // +----+-----+-------+------+----------+----------+ 211 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 212 | // +----+-----+-------+------+----------+----------+ 213 | fn decode(&mut self, src: &mut BytesMut) -> Result, Socks5Error> { 214 | // ver(1) + cmd(1) + rsv(1) + atyp(1) = 4 215 | // If atyp=3(domain name), we have to inspect extra byte to get domain name length. 216 | if src.len() <= 5 { 217 | return Ok(None); 218 | } 219 | 220 | let addr_type = 221 | AddrType::try_from(src[3]).map_err(|e| Socks5Error::InvalidAddrType(e.number))?; 222 | // req_len: ver(1) + cmd(1) + rsv(1) + atyp(1) + len_of_addr + dst.port(2) 223 | // for AddrType::Domain, the 1st byte of DST.ADDR is length of domain 224 | let (addr_start, addr_len, req_len): (usize, usize, usize) = match addr_type { 225 | AddrType::V4 => (4, 4, 10), 226 | AddrType::V6 => (4, 16, 22), 227 | AddrType::Domain => (5, src[4] as usize, 6 + src[4] as usize + 1), 228 | }; 229 | if src.len() < req_len { 230 | return Ok(None); 231 | } 232 | 233 | let addr_raw: &[u8] = &src[addr_start..addr_start + addr_len]; 234 | let addr = match addr_type { 235 | AddrType::V4 => { 236 | let octets = <[u8; 4]>::try_from(addr_raw).expect("unreachable"); 237 | Addr::Ip(IpAddr::V4(Ipv4Addr::from(octets))) 238 | } 239 | AddrType::V6 => { 240 | let octets = <[u8; 16]>::try_from(addr_raw).expect("unreachable"); 241 | Addr::Ip(IpAddr::V6(Ipv6Addr::from(octets))) 242 | } 243 | AddrType::Domain => { 244 | let domain = str::from_utf8(addr_raw) 245 | .map_err(|_| Socks5Error::invalid_data())? 246 | .to_string(); 247 | Addr::Domain(domain) 248 | } 249 | }; 250 | 251 | let port_start = addr_start + addr_len; 252 | let port_raw: [u8; 2] = [src[port_start], src[port_start + 1]]; 253 | let port: u16 = u16::from_be_bytes(port_raw); 254 | 255 | let cmd = Cmd::try_from(src[1]).map_err(|e| Socks5Error::InvalidCmd(e.number))?; 256 | let req = Request { 257 | version: src[0], 258 | cmd: cmd, 259 | rsv: src[2], 260 | dest_addr: addr, 261 | dest_port: port, 262 | }; 263 | src.advance(req_len); 264 | 265 | Ok(Some(req)) 266 | } 267 | } 268 | 269 | impl Encoder for Socks5Codec { 270 | type Error = Socks5Error; 271 | 272 | // +----+-----+-------+------+----------+----------+ 273 | // |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | 274 | // +----+-----+-------+------+----------+----------+ 275 | // | 1 | 1 | X'00' | 1 | Variable | 2 | 276 | // +----+-----+-------+------+----------+----------+ 277 | fn encode(&mut self, item: Reply, dst: &mut BytesMut) -> Result<(), Socks5Error> { 278 | // ver(1) + rep(1) + rsv(1) + atyp(1) + BIND.ADDR.wire_len + port(2) 279 | dst.reserve(6 + item.bind_addr.wire_len()); 280 | dst.put_u8(item.version); 281 | dst.put_u8(item.reply_code); 282 | dst.put_u8(item.rsv); 283 | dst.put_u8(item.bind_addr.addr_type() as u8); 284 | 285 | match item.bind_addr { 286 | Addr::Ip(IpAddr::V4(ipv4)) => dst.put_slice(&ipv4.octets()), 287 | Addr::Ip(IpAddr::V6(ipv6)) => dst.put_slice(&ipv6.octets()), 288 | Addr::Domain(domain) => { 289 | if domain.len() > u8::max_value() as usize { 290 | return Err(Socks5Error::InvalidDomainName); 291 | } 292 | // extra 1 byte to indicate domain length 293 | dst.put_u8(domain.len() as u8); 294 | dst.put_slice(domain.as_bytes()); 295 | } 296 | } 297 | dst.put_u16(item.bind_port); 298 | Ok(()) 299 | } 300 | } 301 | 302 | #[cfg(test)] 303 | mod tests { 304 | use super::*; 305 | 306 | #[test] 307 | fn test_decode_auth_negotiation_request() -> Result<(), Socks5Error> { 308 | let raw: &[u8] = b"\x05\x01\x00"; 309 | let mut buf = BytesMut::from(raw); 310 | let req = NegotiateCodec.decode(&mut buf)?; 311 | assert_eq!( 312 | req, 313 | Some(AuthNegoRequest { 314 | version: 5, 315 | nmethods: 1, 316 | methods: vec![S5AUTH_NO_AUTHENTICATION_REQUIRED], 317 | }) 318 | ); 319 | assert!(buf.is_empty()); 320 | Ok(()) 321 | } 322 | 323 | #[test] 324 | fn test_encode_auth_negotiation_reply() -> Result<(), Socks5Error> { 325 | let reply = AuthNegoReply { 326 | version: 5, 327 | method: S5AUTH_NO_AUTHENTICATION_REQUIRED, 328 | }; 329 | let mut buf = BytesMut::new(); 330 | NegotiateCodec.encode(reply, &mut buf)?; 331 | assert_eq!(buf.as_ref(), b"\x05\x00"); 332 | Ok(()) 333 | } 334 | 335 | #[test] 336 | fn test_decode_request() -> Result<(), Socks5Error> { 337 | let raw: &[u8] = b"\x05\x01\x00\x03\x07\x61\x62\x63\x2e\x63\x6f\x6d\x04\x00"; 338 | let mut buf = BytesMut::from(raw); 339 | let req = Socks5Codec.decode(&mut buf)?; 340 | assert_eq!( 341 | req, 342 | Some(Request { 343 | version: 5, 344 | cmd: Cmd::Connect, 345 | rsv: 0, 346 | dest_addr: Addr::Domain("abc.com".to_string()), 347 | dest_port: 1024, 348 | }) 349 | ); 350 | assert!(buf.is_empty()); 351 | Ok(()) 352 | } 353 | 354 | #[test] 355 | fn test_encode_reply() -> Result<(), Socks5Error> { 356 | let reply = Reply { 357 | version: 5, 358 | reply_code: REP_SUCCEEDED, 359 | rsv: 0, 360 | bind_addr: Addr::Ip(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 8))), 361 | bind_port: 3306, 362 | }; 363 | let mut buf = BytesMut::new(); 364 | Socks5Codec.encode(reply, &mut buf)?; 365 | assert_eq!(buf.as_ref(), b"\x05\x00\x00\x01\xc0\xa8\x00\x08\x0c\xea"); 366 | Ok(()) 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.55" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd" 19 | 20 | [[package]] 21 | name = "async-trait" 22 | version = "0.1.52" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" 25 | dependencies = [ 26 | "proc-macro2", 27 | "quote", 28 | "syn", 29 | ] 30 | 31 | [[package]] 32 | name = "atty" 33 | version = "0.2.14" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 36 | dependencies = [ 37 | "hermit-abi", 38 | "libc", 39 | "winapi", 40 | ] 41 | 42 | [[package]] 43 | name = "autocfg" 44 | version = "1.1.0" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 47 | 48 | [[package]] 49 | name = "bitflags" 50 | version = "1.3.2" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 53 | 54 | [[package]] 55 | name = "bytes" 56 | version = "1.1.0" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 59 | 60 | [[package]] 61 | name = "cfg-if" 62 | version = "1.0.0" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 65 | 66 | [[package]] 67 | name = "clap" 68 | version = "3.1.5" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "ced1892c55c910c1219e98d6fc8d71f6bddba7905866ce740066d8bfea859312" 71 | dependencies = [ 72 | "atty", 73 | "bitflags", 74 | "clap_derive", 75 | "indexmap", 76 | "lazy_static", 77 | "os_str_bytes", 78 | "strsim", 79 | "termcolor", 80 | "textwrap", 81 | ] 82 | 83 | [[package]] 84 | name = "clap_derive" 85 | version = "3.1.4" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16" 88 | dependencies = [ 89 | "heck", 90 | "proc-macro-error", 91 | "proc-macro2", 92 | "quote", 93 | "syn", 94 | ] 95 | 96 | [[package]] 97 | name = "data-encoding" 98 | version = "2.3.2" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" 101 | 102 | [[package]] 103 | name = "dyn-clone" 104 | version = "1.0.4" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" 107 | 108 | [[package]] 109 | name = "enum-as-inner" 110 | version = "0.4.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73" 113 | dependencies = [ 114 | "heck", 115 | "proc-macro2", 116 | "quote", 117 | "syn", 118 | ] 119 | 120 | [[package]] 121 | name = "env_logger" 122 | version = "0.7.1" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 125 | dependencies = [ 126 | "atty", 127 | "humantime", 128 | "log", 129 | "regex", 130 | "termcolor", 131 | ] 132 | 133 | [[package]] 134 | name = "form_urlencoded" 135 | version = "1.0.1" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 138 | dependencies = [ 139 | "matches", 140 | "percent-encoding", 141 | ] 142 | 143 | [[package]] 144 | name = "futures" 145 | version = "0.3.21" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" 148 | dependencies = [ 149 | "futures-channel", 150 | "futures-core", 151 | "futures-executor", 152 | "futures-io", 153 | "futures-sink", 154 | "futures-task", 155 | "futures-util", 156 | ] 157 | 158 | [[package]] 159 | name = "futures-channel" 160 | version = "0.3.21" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" 163 | dependencies = [ 164 | "futures-core", 165 | "futures-sink", 166 | ] 167 | 168 | [[package]] 169 | name = "futures-core" 170 | version = "0.3.21" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" 173 | 174 | [[package]] 175 | name = "futures-executor" 176 | version = "0.3.21" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" 179 | dependencies = [ 180 | "futures-core", 181 | "futures-task", 182 | "futures-util", 183 | ] 184 | 185 | [[package]] 186 | name = "futures-io" 187 | version = "0.3.21" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" 190 | 191 | [[package]] 192 | name = "futures-macro" 193 | version = "0.3.21" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" 196 | dependencies = [ 197 | "proc-macro2", 198 | "quote", 199 | "syn", 200 | ] 201 | 202 | [[package]] 203 | name = "futures-sink" 204 | version = "0.3.21" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" 207 | 208 | [[package]] 209 | name = "futures-task" 210 | version = "0.3.21" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" 213 | 214 | [[package]] 215 | name = "futures-util" 216 | version = "0.3.21" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" 219 | dependencies = [ 220 | "futures-channel", 221 | "futures-core", 222 | "futures-io", 223 | "futures-macro", 224 | "futures-sink", 225 | "futures-task", 226 | "memchr", 227 | "pin-project-lite", 228 | "pin-utils", 229 | "slab", 230 | ] 231 | 232 | [[package]] 233 | name = "getrandom" 234 | version = "0.2.5" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" 237 | dependencies = [ 238 | "cfg-if", 239 | "libc", 240 | "wasi", 241 | ] 242 | 243 | [[package]] 244 | name = "hashbrown" 245 | version = "0.11.2" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 248 | 249 | [[package]] 250 | name = "heck" 251 | version = "0.4.0" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 254 | 255 | [[package]] 256 | name = "hermit-abi" 257 | version = "0.1.19" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 260 | dependencies = [ 261 | "libc", 262 | ] 263 | 264 | [[package]] 265 | name = "hostname" 266 | version = "0.3.1" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" 269 | dependencies = [ 270 | "libc", 271 | "match_cfg", 272 | "winapi", 273 | ] 274 | 275 | [[package]] 276 | name = "humantime" 277 | version = "1.3.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 280 | dependencies = [ 281 | "quick-error", 282 | ] 283 | 284 | [[package]] 285 | name = "idna" 286 | version = "0.2.3" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 289 | dependencies = [ 290 | "matches", 291 | "unicode-bidi", 292 | "unicode-normalization", 293 | ] 294 | 295 | [[package]] 296 | name = "indexmap" 297 | version = "1.8.0" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" 300 | dependencies = [ 301 | "autocfg", 302 | "hashbrown", 303 | ] 304 | 305 | [[package]] 306 | name = "ipconfig" 307 | version = "0.3.0" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "723519edce41262b05d4143ceb95050e4c614f483e78e9fd9e39a8275a84ad98" 310 | dependencies = [ 311 | "socket2", 312 | "widestring", 313 | "winapi", 314 | "winreg", 315 | ] 316 | 317 | [[package]] 318 | name = "ipnet" 319 | version = "2.3.1" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" 322 | 323 | [[package]] 324 | name = "lazy_static" 325 | version = "1.4.0" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 328 | 329 | [[package]] 330 | name = "libc" 331 | version = "0.2.119" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" 334 | 335 | [[package]] 336 | name = "linked-hash-map" 337 | version = "0.5.4" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" 340 | 341 | [[package]] 342 | name = "lock_api" 343 | version = "0.4.6" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" 346 | dependencies = [ 347 | "scopeguard", 348 | ] 349 | 350 | [[package]] 351 | name = "log" 352 | version = "0.4.14" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 355 | dependencies = [ 356 | "cfg-if", 357 | ] 358 | 359 | [[package]] 360 | name = "lru-cache" 361 | version = "0.1.2" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" 364 | dependencies = [ 365 | "linked-hash-map", 366 | ] 367 | 368 | [[package]] 369 | name = "match_cfg" 370 | version = "0.1.0" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" 373 | 374 | [[package]] 375 | name = "matches" 376 | version = "0.1.9" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 379 | 380 | [[package]] 381 | name = "memchr" 382 | version = "2.4.1" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 385 | 386 | [[package]] 387 | name = "mio" 388 | version = "0.8.0" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" 391 | dependencies = [ 392 | "libc", 393 | "log", 394 | "miow", 395 | "ntapi", 396 | "winapi", 397 | ] 398 | 399 | [[package]] 400 | name = "miow" 401 | version = "0.3.7" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 404 | dependencies = [ 405 | "winapi", 406 | ] 407 | 408 | [[package]] 409 | name = "ntapi" 410 | version = "0.3.7" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" 413 | dependencies = [ 414 | "winapi", 415 | ] 416 | 417 | [[package]] 418 | name = "num_cpus" 419 | version = "1.13.1" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 422 | dependencies = [ 423 | "hermit-abi", 424 | "libc", 425 | ] 426 | 427 | [[package]] 428 | name = "num_enum" 429 | version = "0.5.6" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "720d3ea1055e4e4574c0c0b0f8c3fd4f24c4cdaf465948206dea090b57b526ad" 432 | dependencies = [ 433 | "num_enum_derive", 434 | ] 435 | 436 | [[package]] 437 | name = "num_enum_derive" 438 | version = "0.5.6" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "0d992b768490d7fe0d8586d9b5745f6c49f557da6d81dc982b1d167ad4edbb21" 441 | dependencies = [ 442 | "proc-macro-crate", 443 | "proc-macro2", 444 | "quote", 445 | "syn", 446 | ] 447 | 448 | [[package]] 449 | name = "once_cell" 450 | version = "1.9.0" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" 453 | 454 | [[package]] 455 | name = "os_str_bytes" 456 | version = "6.0.0" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" 459 | dependencies = [ 460 | "memchr", 461 | ] 462 | 463 | [[package]] 464 | name = "parking_lot" 465 | version = "0.12.0" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" 468 | dependencies = [ 469 | "lock_api", 470 | "parking_lot_core", 471 | ] 472 | 473 | [[package]] 474 | name = "parking_lot_core" 475 | version = "0.9.1" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" 478 | dependencies = [ 479 | "cfg-if", 480 | "libc", 481 | "redox_syscall", 482 | "smallvec", 483 | "windows-sys", 484 | ] 485 | 486 | [[package]] 487 | name = "percent-encoding" 488 | version = "2.1.0" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 491 | 492 | [[package]] 493 | name = "pin-project-lite" 494 | version = "0.2.8" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" 497 | 498 | [[package]] 499 | name = "pin-utils" 500 | version = "0.1.0" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 503 | 504 | [[package]] 505 | name = "ppv-lite86" 506 | version = "0.2.16" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 509 | 510 | [[package]] 511 | name = "proc-macro-crate" 512 | version = "1.1.3" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" 515 | dependencies = [ 516 | "thiserror", 517 | "toml", 518 | ] 519 | 520 | [[package]] 521 | name = "proc-macro-error" 522 | version = "1.0.4" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 525 | dependencies = [ 526 | "proc-macro-error-attr", 527 | "proc-macro2", 528 | "quote", 529 | "syn", 530 | "version_check", 531 | ] 532 | 533 | [[package]] 534 | name = "proc-macro-error-attr" 535 | version = "1.0.4" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 538 | dependencies = [ 539 | "proc-macro2", 540 | "quote", 541 | "version_check", 542 | ] 543 | 544 | [[package]] 545 | name = "proc-macro2" 546 | version = "1.0.36" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 549 | dependencies = [ 550 | "unicode-xid", 551 | ] 552 | 553 | [[package]] 554 | name = "quick-error" 555 | version = "1.2.3" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 558 | 559 | [[package]] 560 | name = "quote" 561 | version = "1.0.15" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" 564 | dependencies = [ 565 | "proc-macro2", 566 | ] 567 | 568 | [[package]] 569 | name = "rand" 570 | version = "0.8.5" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 573 | dependencies = [ 574 | "libc", 575 | "rand_chacha", 576 | "rand_core", 577 | ] 578 | 579 | [[package]] 580 | name = "rand_chacha" 581 | version = "0.3.1" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 584 | dependencies = [ 585 | "ppv-lite86", 586 | "rand_core", 587 | ] 588 | 589 | [[package]] 590 | name = "rand_core" 591 | version = "0.6.3" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 594 | dependencies = [ 595 | "getrandom", 596 | ] 597 | 598 | [[package]] 599 | name = "redox_syscall" 600 | version = "0.2.11" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c" 603 | dependencies = [ 604 | "bitflags", 605 | ] 606 | 607 | [[package]] 608 | name = "regex" 609 | version = "1.5.4" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 612 | dependencies = [ 613 | "aho-corasick", 614 | "memchr", 615 | "regex-syntax", 616 | ] 617 | 618 | [[package]] 619 | name = "regex-syntax" 620 | version = "0.6.25" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 623 | 624 | [[package]] 625 | name = "resolv-conf" 626 | version = "0.7.0" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" 629 | dependencies = [ 630 | "hostname", 631 | "quick-error", 632 | ] 633 | 634 | [[package]] 635 | name = "scopeguard" 636 | version = "1.1.0" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 639 | 640 | [[package]] 641 | name = "serde" 642 | version = "1.0.136" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" 645 | 646 | [[package]] 647 | name = "signal-hook-registry" 648 | version = "1.4.0" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 651 | dependencies = [ 652 | "libc", 653 | ] 654 | 655 | [[package]] 656 | name = "slab" 657 | version = "0.4.5" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" 660 | 661 | [[package]] 662 | name = "smallvec" 663 | version = "1.8.0" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" 666 | 667 | [[package]] 668 | name = "socket2" 669 | version = "0.4.4" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" 672 | dependencies = [ 673 | "libc", 674 | "winapi", 675 | ] 676 | 677 | [[package]] 678 | name = "strsim" 679 | version = "0.10.0" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 682 | 683 | [[package]] 684 | name = "syn" 685 | version = "1.0.86" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" 688 | dependencies = [ 689 | "proc-macro2", 690 | "quote", 691 | "unicode-xid", 692 | ] 693 | 694 | [[package]] 695 | name = "termcolor" 696 | version = "1.1.3" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 699 | dependencies = [ 700 | "winapi-util", 701 | ] 702 | 703 | [[package]] 704 | name = "textwrap" 705 | version = "0.15.0" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 708 | 709 | [[package]] 710 | name = "thiserror" 711 | version = "1.0.30" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 714 | dependencies = [ 715 | "thiserror-impl", 716 | ] 717 | 718 | [[package]] 719 | name = "thiserror-impl" 720 | version = "1.0.30" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 723 | dependencies = [ 724 | "proc-macro2", 725 | "quote", 726 | "syn", 727 | ] 728 | 729 | [[package]] 730 | name = "tinyvec" 731 | version = "1.5.1" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" 734 | dependencies = [ 735 | "tinyvec_macros", 736 | ] 737 | 738 | [[package]] 739 | name = "tinyvec_macros" 740 | version = "0.1.0" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 743 | 744 | [[package]] 745 | name = "tokio" 746 | version = "1.17.0" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" 749 | dependencies = [ 750 | "bytes", 751 | "libc", 752 | "memchr", 753 | "mio", 754 | "num_cpus", 755 | "once_cell", 756 | "parking_lot", 757 | "pin-project-lite", 758 | "signal-hook-registry", 759 | "socket2", 760 | "tokio-macros", 761 | "winapi", 762 | ] 763 | 764 | [[package]] 765 | name = "tokio-macros" 766 | version = "1.7.0" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" 769 | dependencies = [ 770 | "proc-macro2", 771 | "quote", 772 | "syn", 773 | ] 774 | 775 | [[package]] 776 | name = "tokio-util" 777 | version = "0.7.0" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1" 780 | dependencies = [ 781 | "bytes", 782 | "futures-core", 783 | "futures-sink", 784 | "log", 785 | "pin-project-lite", 786 | "tokio", 787 | ] 788 | 789 | [[package]] 790 | name = "toml" 791 | version = "0.5.8" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" 794 | dependencies = [ 795 | "serde", 796 | ] 797 | 798 | [[package]] 799 | name = "trust-dns-proto" 800 | version = "0.21.1" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "2861b3ed517888174d13909e675c4e94b3291867512068be59d76533e4d1270c" 803 | dependencies = [ 804 | "async-trait", 805 | "cfg-if", 806 | "data-encoding", 807 | "enum-as-inner", 808 | "futures-channel", 809 | "futures-io", 810 | "futures-util", 811 | "idna", 812 | "ipnet", 813 | "lazy_static", 814 | "log", 815 | "rand", 816 | "smallvec", 817 | "thiserror", 818 | "tinyvec", 819 | "tokio", 820 | "url", 821 | ] 822 | 823 | [[package]] 824 | name = "trust-dns-resolver" 825 | version = "0.21.1" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "d9e737a252a617bd4774649e245dbf705e207275db0893b9fa824d49f074fc1c" 828 | dependencies = [ 829 | "cfg-if", 830 | "futures-util", 831 | "ipconfig", 832 | "lazy_static", 833 | "log", 834 | "lru-cache", 835 | "parking_lot", 836 | "resolv-conf", 837 | "smallvec", 838 | "thiserror", 839 | "tokio", 840 | "trust-dns-proto", 841 | ] 842 | 843 | [[package]] 844 | name = "unicode-bidi" 845 | version = "0.3.7" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" 848 | 849 | [[package]] 850 | name = "unicode-normalization" 851 | version = "0.1.19" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 854 | dependencies = [ 855 | "tinyvec", 856 | ] 857 | 858 | [[package]] 859 | name = "unicode-xid" 860 | version = "0.2.2" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 863 | 864 | [[package]] 865 | name = "url" 866 | version = "2.2.2" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 869 | dependencies = [ 870 | "form_urlencoded", 871 | "idna", 872 | "matches", 873 | "percent-encoding", 874 | ] 875 | 876 | [[package]] 877 | name = "version_check" 878 | version = "0.9.4" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 881 | 882 | [[package]] 883 | name = "wasi" 884 | version = "0.10.2+wasi-snapshot-preview1" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 887 | 888 | [[package]] 889 | name = "widestring" 890 | version = "0.5.1" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" 893 | 894 | [[package]] 895 | name = "winapi" 896 | version = "0.3.9" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 899 | dependencies = [ 900 | "winapi-i686-pc-windows-gnu", 901 | "winapi-x86_64-pc-windows-gnu", 902 | ] 903 | 904 | [[package]] 905 | name = "winapi-i686-pc-windows-gnu" 906 | version = "0.4.0" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 909 | 910 | [[package]] 911 | name = "winapi-util" 912 | version = "0.1.5" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 915 | dependencies = [ 916 | "winapi", 917 | ] 918 | 919 | [[package]] 920 | name = "winapi-x86_64-pc-windows-gnu" 921 | version = "0.4.0" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 924 | 925 | [[package]] 926 | name = "windows-sys" 927 | version = "0.32.0" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" 930 | dependencies = [ 931 | "windows_aarch64_msvc", 932 | "windows_i686_gnu", 933 | "windows_i686_msvc", 934 | "windows_x86_64_gnu", 935 | "windows_x86_64_msvc", 936 | ] 937 | 938 | [[package]] 939 | name = "windows_aarch64_msvc" 940 | version = "0.32.0" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" 943 | 944 | [[package]] 945 | name = "windows_i686_gnu" 946 | version = "0.32.0" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" 949 | 950 | [[package]] 951 | name = "windows_i686_msvc" 952 | version = "0.32.0" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" 955 | 956 | [[package]] 957 | name = "windows_x86_64_gnu" 958 | version = "0.32.0" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" 961 | 962 | [[package]] 963 | name = "windows_x86_64_msvc" 964 | version = "0.32.0" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" 967 | 968 | [[package]] 969 | name = "winreg" 970 | version = "0.7.0" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" 973 | dependencies = [ 974 | "winapi", 975 | ] 976 | 977 | [[package]] 978 | name = "yimu" 979 | version = "0.1.0" 980 | dependencies = [ 981 | "anyhow", 982 | "async-trait", 983 | "bytes", 984 | "clap", 985 | "dyn-clone", 986 | "env_logger", 987 | "futures", 988 | "log", 989 | "num_enum", 990 | "thiserror", 991 | "tokio", 992 | "tokio-util", 993 | "trust-dns-resolver", 994 | ] 995 | --------------------------------------------------------------------------------