├── .gitignore ├── src ├── lib.rs ├── io.rs ├── resolver.rs ├── bin │ └── rabbit.rs ├── args.rs ├── listener.rs ├── config.rs ├── util.rs ├── write_all.rs ├── read_exact.rs ├── local.rs ├── socks5.rs ├── cipher.rs ├── copy.rs ├── redir.rs └── server.rs ├── README.md ├── config.toml ├── Cargo.toml ├── LICENSE ├── .github └── workflows │ └── build.yaml └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | .vscode/** 4 | rls/* 5 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod args; 2 | pub mod cipher; 3 | pub mod config; 4 | mod copy; 5 | pub mod io; 6 | pub mod listener; 7 | pub mod local; 8 | mod read_exact; 9 | pub mod redir; 10 | pub mod resolver; 11 | pub mod server; 12 | pub mod socks5; 13 | pub mod util; 14 | mod write_all; 15 | -------------------------------------------------------------------------------- /src/io.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | pub use crate::copy::copy_bidirectional; 4 | pub use crate::read_exact::read_exact; 5 | pub use crate::write_all::write_all; 6 | 7 | pub const DEFAULT_IDLE_TIMEOUT: Duration = Duration::from_secs(5 * 60); 8 | pub const DEFAULT_CHECK_INTERVAL: Duration = Duration::from_secs(3); 9 | -------------------------------------------------------------------------------- /src/resolver.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::net::IpAddr; 3 | use std::str::FromStr; 4 | use std::sync::OnceLock; 5 | 6 | use dns_resolver::Resolver; 7 | use rand::{thread_rng, Rng}; 8 | 9 | use crate::util::other; 10 | 11 | static GLOBAL_RESOLVER: OnceLock = OnceLock::new(); 12 | 13 | pub async fn resolve(host: &str) -> io::Result { 14 | if let Ok(addr) = IpAddr::from_str(host) { 15 | return Ok(addr); 16 | } 17 | let resolver = GLOBAL_RESOLVER.get_or_init(Resolver::new); 18 | let results = resolver.lookup_host(host).await?; 19 | if !results.is_empty() { 20 | return Ok(results[thread_rng().gen_range(0..results.len())]); 21 | } 22 | Err(other("resolve fail")) 23 | } 24 | -------------------------------------------------------------------------------- /src/bin/rabbit.rs: -------------------------------------------------------------------------------- 1 | use std::future::pending; 2 | 3 | use rabbit::args::parse_args; 4 | use rabbit::{local, redir, server}; 5 | 6 | fn main() { 7 | env_logger::init(); 8 | 9 | let config = parse_args("rabbit").unwrap(); 10 | log::info!( 11 | "config: \n{}", 12 | toml::ser::to_string_pretty(&config).unwrap() 13 | ); 14 | 15 | awak::block_on(async { 16 | if let Some(c) = config.client { 17 | local::Server::new(c).serve(); 18 | } 19 | if let Some(c) = config.server { 20 | server::Server::new(c).serve(); 21 | } 22 | if let Some(c) = config.redir { 23 | redir::Server::new(c).serve(); 24 | } 25 | pending().await 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rabbit 2 | 3 | [![Build](https://github.com/cssivision/rabbit/workflows/build/badge.svg)]( 4 | https://github.com/cssivision/rabbit/actions) 5 | [![License](http://img.shields.io/badge/license-mit-blue.svg)](https://github.com/cssivision/rabbit/blob/master/LICENSE) 6 | 7 | personal tunnel in rust. 8 | 9 | # Configuration 10 | config.toml 11 | ```toml 12 | [[server]] 13 | local_addr = "127.0.0.1:9006" 14 | password = "password" 15 | method = "aes-128-cfb" 16 | 17 | [[server]] 18 | local_addr = "temp.sock" 19 | password = "password" 20 | method = "aes-128-cfb" 21 | ``` 22 | 23 | # Usage 24 | #### server 25 | ```sh 26 | RUST_LOG=info ./rabbit -c config.toml 27 | ``` 28 | 29 | # Licenses 30 | 31 | All source code is licensed under the [MIT License](https://github.com/cssivision/rabbit/blob/master/LICENSE). 32 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | [[server]] 2 | local_addr = "127.0.0.1:9006" 3 | password = "password" 4 | method = "aes-128-cfb" 5 | 6 | [[server]] 7 | local_addr = "temp.sock" 8 | password = "password" 9 | method = "aes-128-cfb" 10 | 11 | [[server]] 12 | local_addr = "127.0.0.1:9006" 13 | password = "password" 14 | method = "aes-128-cfb" 15 | mode = "Udp" 16 | 17 | [[client]] 18 | local_addr = "127.0.0.1:6009" 19 | server_addr = "127.0.0.1:9006" 20 | password = "password" 21 | method = "aes-128-cfb" 22 | 23 | [[client]] 24 | local_addr = "temp.sock" 25 | server_addr = "127.0.0.1:9006" 26 | password = "password" 27 | method = "aes-128-cfb" 28 | 29 | [[redir]] 30 | local_addr = "127.0.0.1:6010" 31 | server_addr = "127.0.0.1:9006" 32 | password = "password" 33 | method = "aes-128-cfb" 34 | mode = "Udp" 35 | redir_addr = "127.0.0.1:8080" 36 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rabbit" 3 | version = "0.9.0" 4 | authors = ["cssivision "] 5 | license = "MIT" 6 | repository = "https://github.com/cssivision/rabbit" 7 | homepage = "https://github.com/cssivision/rabbit" 8 | description = "personal VPN in rust." 9 | readme = "README.md" 10 | edition = "2018" 11 | 12 | [dependencies] 13 | serde = "1.0" 14 | serde_derive = "1.0" 15 | toml = "0.8" 16 | log = "0.4" 17 | libc = "0.2" 18 | env_logger = "0.11" 19 | getopts = "0.2" 20 | rand = "0.8" 21 | md5 = "0.7" 22 | awak = "0.2" 23 | dns-resolver = { version = "0.2", default-features = false, features = ["awak-runtime"] } 24 | aes = "0.7" 25 | socket2 = { version = "0.5", features = ["all"] } 26 | cfb-mode = "0.7" 27 | chacha20 = "0.7" 28 | ctr = "0.7" 29 | cipher = "0.3" 30 | futures-util = { version = "0.3", default-features = false, features = ["io"] } 31 | pin-project-lite = "0.2" 32 | futures-channel = "0.3" 33 | -------------------------------------------------------------------------------- /src/args.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::process; 3 | 4 | use crate::config::Config; 5 | 6 | use getopts::Options; 7 | 8 | pub fn parse_args(name: &str) -> Option { 9 | let args: Vec = env::args().collect(); 10 | 11 | let mut opts = Options::new(); 12 | opts.optopt("c", "", "configuration path", "config"); 13 | opts.optflag("h", "help", "print this help"); 14 | 15 | let matches = match opts.parse(&args[1..]) { 16 | Ok(m) => m, 17 | Err(_) => { 18 | print!("{}", opts.usage(&format!("Usage: {name} -c PATH"))); 19 | return None; 20 | } 21 | }; 22 | 23 | if matches.opt_present("h") { 24 | print!("{}", opts.usage(&format!("Usage: {name} -c PATH"))); 25 | process::exit(0); 26 | } 27 | 28 | let path = matches.opt_str("c").unwrap_or_default(); 29 | match Config::new(path) { 30 | Ok(c) => Some(c), 31 | Err(e) => { 32 | log::error!("{}", e); 33 | None 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017~present cssivision 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/listener.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::io; 3 | 4 | use awak::net::{TcpListener, TcpStream, UnixListener, UnixStream}; 5 | use futures_util::{AsyncRead, AsyncWrite}; 6 | 7 | use crate::config::Addr; 8 | 9 | pub enum Listener { 10 | Tcp(TcpListener), 11 | Unix(UnixListener), 12 | } 13 | 14 | pub trait AsyncReadWrite: AsyncRead + AsyncWrite + Unpin + Send {} 15 | 16 | impl AsyncReadWrite for TcpStream {} 17 | 18 | impl AsyncReadWrite for UnixStream {} 19 | 20 | impl Listener { 21 | pub async fn bind(addr: Addr) -> io::Result { 22 | match addr { 23 | Addr::Path(addr) => { 24 | let _ = fs::remove_file(&addr); 25 | Ok(Listener::Unix(UnixListener::bind(addr)?)) 26 | } 27 | Addr::Socket(addr) => Ok(Listener::Tcp(TcpListener::bind(addr).await?)), 28 | } 29 | } 30 | 31 | pub async fn accept(&self) -> io::Result> { 32 | match self { 33 | Listener::Unix(lis) => { 34 | let (stream, addr) = lis.accept().await?; 35 | log::debug!("accept stream from addr {:?}", addr); 36 | Ok(Box::new(stream)) 37 | } 38 | Listener::Tcp(lis) => { 39 | let (stream, addr) = lis.accept().await?; 40 | log::debug!("accept stream from addr {:?}", addr); 41 | Ok(Box::new(stream)) 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | use std::path::{Path, PathBuf}; 3 | use std::{fs, io}; 4 | 5 | use serde_derive::{Deserialize, Serialize}; 6 | 7 | #[derive(Serialize, Deserialize, Debug)] 8 | pub struct Config { 9 | pub server: Option>, 10 | pub client: Option>, 11 | pub redir: Option>, 12 | } 13 | 14 | #[derive(Default, Serialize, Deserialize, Debug, Clone)] 15 | pub enum Mode { 16 | #[default] 17 | Tcp, 18 | Udp, 19 | Both, 20 | } 21 | 22 | #[derive(Serialize, Deserialize, Debug)] 23 | pub struct Server { 24 | pub local_addr: Addr, 25 | pub password: String, 26 | pub method: String, 27 | #[serde(default)] 28 | pub mode: Mode, 29 | } 30 | 31 | #[derive(Serialize, Deserialize, Debug)] 32 | pub struct Client { 33 | pub local_addr: Addr, 34 | pub server_addr: SocketAddr, 35 | pub password: String, 36 | pub method: String, 37 | } 38 | 39 | #[derive(Serialize, Deserialize, Debug)] 40 | pub struct Redir { 41 | pub local_addr: SocketAddr, 42 | pub server_addr: SocketAddr, 43 | pub password: String, 44 | pub method: String, 45 | #[serde(default)] 46 | pub mode: Mode, 47 | pub redir_addr: Option, 48 | } 49 | 50 | #[derive(Serialize, Deserialize, Debug, Clone)] 51 | #[serde(untagged)] 52 | pub enum Addr { 53 | Socket(SocketAddr), 54 | Path(PathBuf), 55 | } 56 | 57 | impl Config { 58 | pub fn new>(path: P) -> io::Result { 59 | let contents = fs::read_to_string(path)?; 60 | toml::from_str(&contents).map_err(|e| io::Error::new(io::ErrorKind::Other, e)) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 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: ${{ matrix.os }} 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | os: [ubuntu-latest] 20 | rust: [stable] 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - name: Install latest ${{ matrix.rust }} 26 | uses: actions-rs/toolchain@v1 27 | with: 28 | toolchain: ${{ matrix.rust }} 29 | profile: minimal 30 | override: true 31 | 32 | - name: Build 33 | uses: actions-rs/cargo@v1 34 | with: 35 | command: build 36 | args: --verbose 37 | 38 | - name: Test 39 | uses: actions-rs/cargo@v1 40 | with: 41 | command: test 42 | args: --verbose 43 | 44 | - run: rustup component add clippy 45 | 46 | - name: Clippy 47 | uses: actions-rs/cargo@v1 48 | with: 49 | command: clippy 50 | args: -- -D warnings 51 | 52 | fmt: 53 | name: fmt 54 | runs-on: ubuntu-latest 55 | steps: 56 | - uses: actions/checkout@v2 57 | - name: Install Rust 58 | run: rustup update stable 59 | - name: Install rustfmt 60 | run: rustup component add rustfmt 61 | 62 | - name: "rustfmt --check" 63 | run: | 64 | if ! rustfmt --check --edition 2018 $(find . -name '*.rs' -print); then 65 | printf "Please run \`rustfmt --edition 2018 \$(find . -name '*.rs' -print)\` to fix rustfmt errors.\n" >&2 66 | exit 1 67 | fi 68 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use md5::compute; 2 | use std::io; 3 | use std::net::IpAddr; 4 | use std::str::FromStr; 5 | 6 | use crate::socks5::v5::{TYPE_DOMAIN, TYPE_IPV4, TYPE_IPV6}; 7 | 8 | static MD5_LENGTH: u32 = 16; 9 | 10 | pub fn other(desc: &str) -> io::Error { 11 | io::Error::new(io::ErrorKind::Other, desc) 12 | } 13 | 14 | pub fn eof() -> io::Error { 15 | io::Error::new(io::ErrorKind::UnexpectedEof, "early eof") 16 | } 17 | 18 | pub fn generate_key(data: &[u8], key_len: usize) -> Vec { 19 | let count = (key_len as f32 / MD5_LENGTH as f32).ceil() as u32; 20 | let mut key = Vec::from(&compute(data)[..]); 21 | let mut start = 0; 22 | for _ in 1..count { 23 | start += MD5_LENGTH; 24 | let mut d = Vec::from(&key[(start - MD5_LENGTH) as usize..start as usize]); 25 | d.extend_from_slice(data); 26 | let d = compute(d); 27 | key.extend_from_slice(&*d); 28 | } 29 | key 30 | } 31 | 32 | pub fn generate_raw_addr(host: &str, port: u16) -> Vec { 33 | match IpAddr::from_str(host) { 34 | Ok(IpAddr::V4(host)) => { 35 | let mut rawaddr = vec![TYPE_IPV4]; 36 | rawaddr.extend_from_slice(&host.octets()); 37 | rawaddr.extend_from_slice(&[((port >> 8) & 0xff) as u8, (port & 0xff) as u8]); 38 | rawaddr 39 | } 40 | Ok(IpAddr::V6(host)) => { 41 | let mut rawaddr = vec![TYPE_IPV6]; 42 | rawaddr.extend_from_slice(&host.octets()); 43 | rawaddr.extend_from_slice(&[((port >> 8) & 0xff) as u8, (port & 0xff) as u8]); 44 | rawaddr 45 | } 46 | _ => { 47 | let dm_len = host.as_bytes().len(); 48 | let mut rawaddr = vec![TYPE_DOMAIN, dm_len as u8]; 49 | rawaddr.extend_from_slice(host.as_bytes()); 50 | rawaddr.extend_from_slice(&[((port >> 8) & 0xff) as u8, (port & 0xff) as u8]); 51 | rawaddr 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/write_all.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::io; 3 | use std::pin::Pin; 4 | use std::sync::{Arc, Mutex}; 5 | use std::task::{ready, Context, Poll}; 6 | 7 | use futures_util::AsyncWrite; 8 | 9 | use crate::cipher::Cipher; 10 | 11 | enum State { 12 | Iv, 13 | Write(Vec), 14 | Done, 15 | } 16 | 17 | pub struct EncryptWriteAll<'a, W: ?Sized> { 18 | cipher: Arc>, 19 | writer: &'a mut W, 20 | buf: &'a [u8], 21 | state: State, 22 | } 23 | 24 | pub fn write_all<'a, W>( 25 | cipher: Arc>, 26 | writer: &'a mut W, 27 | buf: &'a [u8], 28 | ) -> EncryptWriteAll<'a, W> 29 | where 30 | W: AsyncWrite + Unpin + ?Sized, 31 | { 32 | EncryptWriteAll { 33 | cipher, 34 | writer, 35 | buf, 36 | state: State::Iv, 37 | } 38 | } 39 | 40 | impl Future for EncryptWriteAll<'_, W> 41 | where 42 | W: AsyncWrite + Unpin + ?Sized, 43 | { 44 | type Output = io::Result<()>; 45 | 46 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 47 | let me = &mut *self; 48 | loop { 49 | match &mut me.state { 50 | State::Iv => { 51 | let mut cipher = me.cipher.lock().unwrap(); 52 | if cipher.is_encrypt_inited() { 53 | me.state = State::Write(vec![]); 54 | continue; 55 | } 56 | cipher.init_encrypt(); 57 | let mut data = cipher.iv().to_vec(); 58 | data.extend_from_slice(me.buf); 59 | let iv_len = cipher.iv_len(); 60 | cipher.encrypt(&mut data[iv_len..]); 61 | me.state = State::Write(data); 62 | } 63 | State::Write(data) => { 64 | while !data.is_empty() { 65 | let n = ready!(Pin::new(&mut me.writer).poll_write(cx, data))?; 66 | if n == 0 { 67 | return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); 68 | } 69 | data.drain(0..n); 70 | } 71 | me.state = State::Done; 72 | } 73 | State::Done => { 74 | return Poll::Ready(Ok(())); 75 | } 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/read_exact.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::io; 3 | use std::pin::Pin; 4 | use std::sync::{Arc, Mutex}; 5 | use std::task::{ready, Context, Poll}; 6 | 7 | use crate::cipher::Cipher; 8 | use crate::util::eof; 9 | 10 | use futures_util::AsyncRead; 11 | 12 | enum State { 13 | Iv, 14 | Read, 15 | Done, 16 | } 17 | 18 | pub struct DecryptReadExact<'a, A: ?Sized> { 19 | cipher: Arc>, 20 | reader: &'a mut A, 21 | buf: &'a mut [u8], 22 | pos: usize, 23 | state: State, 24 | } 25 | 26 | pub fn read_exact<'a, A>( 27 | cipher: Arc>, 28 | reader: &'a mut A, 29 | buf: &'a mut [u8], 30 | ) -> DecryptReadExact<'a, A> 31 | where 32 | A: AsyncRead + Unpin + ?Sized, 33 | { 34 | DecryptReadExact { 35 | cipher, 36 | reader, 37 | buf, 38 | pos: 0, 39 | state: State::Iv, 40 | } 41 | } 42 | 43 | impl Future for DecryptReadExact<'_, A> 44 | where 45 | A: AsyncRead + Unpin + ?Sized, 46 | { 47 | type Output = io::Result; 48 | 49 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 50 | let me = &mut *self; 51 | loop { 52 | match &mut me.state { 53 | State::Iv => { 54 | let mut cipher = me.cipher.lock().unwrap(); 55 | if cipher.is_decrypt_inited() { 56 | me.state = State::Read; 57 | continue; 58 | } 59 | while me.pos < cipher.iv_len() { 60 | let n = 61 | ready!(Pin::new(&mut *me.reader) 62 | .poll_read(cx, &mut cipher.iv_mut()[me.pos..]))?; 63 | me.pos += n; 64 | if n == 0 { 65 | return Err(eof()).into(); 66 | } 67 | } 68 | me.pos = 0; 69 | cipher.init_decrypt(); 70 | me.state = State::Read; 71 | } 72 | State::Read => { 73 | // if our buffer is empty, then we need to read some data to continue. 74 | while me.pos < me.buf.len() { 75 | let n = 76 | ready!(Pin::new(&mut *me.reader).poll_read(cx, &mut me.buf[me.pos..]))?; 77 | me.pos += n; 78 | if n == 0 { 79 | return Err(eof()).into(); 80 | } 81 | } 82 | let copy_len = me.buf.len(); 83 | let mut cipher = me.cipher.lock().unwrap(); 84 | cipher.decrypt(&mut me.buf[..copy_len]); 85 | me.state = State::Done; 86 | } 87 | State::Done => { 88 | return Poll::Ready(Ok(me.pos)); 89 | } 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/local.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::net::SocketAddr; 3 | use std::sync::{Arc, Mutex}; 4 | use std::time::Duration; 5 | 6 | use awak::net::TcpStream; 7 | use awak::time::timeout; 8 | use awak::util::IdleTimeout; 9 | use futures_util::{AsyncRead, AsyncWrite}; 10 | 11 | use crate::cipher::Cipher; 12 | use crate::config; 13 | use crate::io::{copy_bidirectional, write_all, DEFAULT_CHECK_INTERVAL, DEFAULT_IDLE_TIMEOUT}; 14 | use crate::listener::Listener; 15 | use crate::socks5; 16 | use crate::util::generate_raw_addr; 17 | 18 | const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(1); 19 | 20 | pub struct Server { 21 | services: Vec, 22 | } 23 | 24 | impl Server { 25 | pub fn new(configs: Vec) -> Server { 26 | let services = configs.into_iter().map(Service::new).collect(); 27 | Server { services } 28 | } 29 | 30 | pub fn serve(self) { 31 | for s in self.services { 32 | awak::spawn(async move { 33 | if let Err(e) = s.serve().await { 34 | log::error!("server fail: {:?}", e); 35 | } 36 | }) 37 | .detach(); 38 | } 39 | } 40 | } 41 | 42 | pub struct Service { 43 | config: config::Client, 44 | } 45 | 46 | impl Service { 47 | pub fn new(config: config::Client) -> Service { 48 | Service { config } 49 | } 50 | 51 | pub async fn serve(&self) -> io::Result<()> { 52 | let cipher = Cipher::new(&self.config.method, &self.config.password); 53 | let local_addr = self.config.local_addr.clone(); 54 | let listener = Listener::bind(local_addr).await?; 55 | log::info!("listening connections on {:?}", self.config.local_addr); 56 | loop { 57 | let mut socket = listener.accept().await?; 58 | let cipher = cipher.reset(); 59 | let server_addr = self.config.server_addr; 60 | let proxy = async move { 61 | if let Err(e) = proxy(server_addr, cipher, &mut socket).await { 62 | log::error!("failed to proxy; error={}", e); 63 | } 64 | }; 65 | awak::spawn(proxy).detach(); 66 | } 67 | } 68 | } 69 | 70 | async fn proxy( 71 | server_addr: SocketAddr, 72 | cipher: Cipher, 73 | socket1: &mut A, 74 | ) -> io::Result<(u64, u64)> 75 | where 76 | A: AsyncRead + AsyncWrite + Unpin + ?Sized, 77 | { 78 | let cipher = Arc::new(Mutex::new(cipher)); 79 | 80 | let (host, port) = socks5::handshake(socket1, Duration::from_secs(3)).await?; 81 | log::debug!("proxy to address: {}:{}", host, port); 82 | 83 | let mut socket2 = timeout(DEFAULT_CONNECT_TIMEOUT, TcpStream::connect(&server_addr)).await??; 84 | log::debug!("connected to server {}", server_addr); 85 | 86 | let rawaddr = generate_raw_addr(&host, port); 87 | write_all(cipher.clone(), &mut socket2, &rawaddr).await?; 88 | 89 | let (n1, n2) = IdleTimeout::new( 90 | copy_bidirectional(socket1, &mut socket2, cipher), 91 | DEFAULT_IDLE_TIMEOUT, 92 | DEFAULT_CHECK_INTERVAL, 93 | ) 94 | .await??; 95 | log::debug!("proxy local => remote: {}, remote => local: {:?}", n1, n2); 96 | Ok((n1, n2)) 97 | } 98 | -------------------------------------------------------------------------------- /src/socks5.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::many_single_char_names)] 2 | use std::io; 3 | use std::net::{Ipv4Addr, Ipv6Addr}; 4 | use std::str; 5 | use std::time::Duration; 6 | 7 | use awak::time::timeout; 8 | use futures_util::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 9 | 10 | use crate::util::other; 11 | 12 | pub mod v5 { 13 | pub const VERSION: u8 = 5; 14 | pub const METH_NO_AUTH: u8 = 0; 15 | pub const CMD_CONNECT: u8 = 1; 16 | pub const TYPE_IPV4: u8 = 1; 17 | pub const TYPE_IPV6: u8 = 4; 18 | pub const TYPE_DOMAIN: u8 = 3; 19 | pub const REPLY_SUCESS: u8 = 0; 20 | } 21 | 22 | /// Creates a future which will handle socks5 connection. 23 | /// 24 | /// if success The returned future will the handled `TcpStream` and address. if handle shake fail will 25 | /// return `io::Error`. 26 | pub async fn handshake(conn: &mut A, dur: Duration) -> io::Result<(String, u16)> 27 | where 28 | A: AsyncRead + AsyncWrite + Unpin + ?Sized, 29 | { 30 | let fut = async move { 31 | // socks version, only support version 5. 32 | let buf1 = &mut [0u8; 2]; 33 | conn.read_exact(buf1).await?; 34 | 35 | if buf1[0] != v5::VERSION { 36 | return Err(other("unknown version")); 37 | } 38 | 39 | let buf2 = &mut vec![0u8; buf1[1] as usize]; 40 | conn.read_exact(buf2).await?; 41 | 42 | let buf3 = &mut [v5::VERSION, v5::METH_NO_AUTH]; 43 | conn.write_all(buf3).await?; 44 | 45 | // check version 46 | let buf4 = &mut [0u8; 3]; 47 | conn.read_exact(buf4).await?; 48 | if buf4[0] != v5::VERSION { 49 | return Err(other("didn't confirm with v5 version")); 50 | } 51 | // checkout cmd 52 | if buf4[1] != v5::CMD_CONNECT { 53 | return Err(other("unsupported command")); 54 | } 55 | // there's one byte which is reserved for future use, so we read it and discard it. 56 | 57 | let address_type = &mut [0u8]; 58 | conn.read_exact(address_type).await?; 59 | 60 | // Sending connection established message immediately to client. 61 | // This some round trip time for creating socks connection with the client. 62 | // But if connection failed, the client will get connection reset error. 63 | 64 | let result = match address_type.first() { 65 | // For IPv4 addresses, we read the 4 bytes for the address as 66 | // well as 2 bytes for the port. 67 | Some(&v5::TYPE_IPV4) => { 68 | let buf = &mut [0u8; 6]; 69 | conn.read_exact(buf).await?; 70 | let addr = Ipv4Addr::new(buf[0], buf[1], buf[2], buf[3]); 71 | let port = ((buf[4] as u16) << 8) | (buf[5] as u16); 72 | 73 | let mut resp = vec![v5::VERSION, v5::REPLY_SUCESS, 0x00, v5::TYPE_IPV4]; 74 | resp.extend_from_slice(buf); 75 | conn.write_all(&resp).await?; 76 | 77 | Ok((format!("{addr}"), port)) 78 | } 79 | 80 | // For IPv6 addresses there's 16 bytes of an address plus two 81 | // bytes for a port, so we read that off and then keep going. 82 | Some(&v5::TYPE_IPV6) => { 83 | let buf = &mut [0u8; 18]; 84 | conn.read_exact(buf).await?; 85 | let a = ((buf[0] as u16) << 8) | (buf[1] as u16); 86 | let b = ((buf[2] as u16) << 8) | (buf[3] as u16); 87 | let c = ((buf[4] as u16) << 8) | (buf[5] as u16); 88 | let d = ((buf[6] as u16) << 8) | (buf[7] as u16); 89 | let e = ((buf[8] as u16) << 8) | (buf[9] as u16); 90 | let f = ((buf[10] as u16) << 8) | (buf[11] as u16); 91 | let g = ((buf[12] as u16) << 8) | (buf[13] as u16); 92 | let h = ((buf[14] as u16) << 8) | (buf[15] as u16); 93 | let addr = Ipv6Addr::new(a, b, c, d, e, f, g, h); 94 | let port = ((buf[16] as u16) << 8) | (buf[17] as u16); 95 | 96 | let mut resp = vec![v5::VERSION, v5::REPLY_SUCESS, 0x00, v5::TYPE_IPV6]; 97 | resp.extend_from_slice(buf); 98 | conn.write_all(&resp).await?; 99 | 100 | Ok((format!("{addr}"), port)) 101 | } 102 | 103 | // The SOCKSv5 protocol not only supports proxying to specific 104 | // IP addresses, but also arbitrary hostnames. 105 | Some(&v5::TYPE_DOMAIN) => { 106 | let buf1 = &mut [0u8]; 107 | conn.read_exact(buf1).await?; 108 | let buf2 = &mut vec![0u8; buf1[0] as usize + 2]; 109 | conn.read_exact(buf2).await?; 110 | 111 | let hostname = &buf2[..buf2.len() - 2]; 112 | let hostname = if let Ok(hostname) = str::from_utf8(hostname) { 113 | hostname 114 | } else { 115 | return Err(other("hostname include invalid utf8")); 116 | }; 117 | 118 | let pos = buf2.len() - 2; 119 | let port = ((buf2[pos] as u16) << 8) | (buf2[pos + 1] as u16); 120 | 121 | let mut resp = vec![v5::VERSION, v5::REPLY_SUCESS, 0x00, v5::TYPE_DOMAIN]; 122 | resp.extend_from_slice(buf1); 123 | resp.extend_from_slice(buf2); 124 | conn.write_all(&resp).await?; 125 | 126 | Ok((hostname.to_string(), port)) 127 | } 128 | n => { 129 | let msg = format!("unknown address type, received: {n:?}"); 130 | Err(other(&msg)) 131 | } 132 | }; 133 | result 134 | }; 135 | timeout(dur, fut).await? 136 | } 137 | -------------------------------------------------------------------------------- /src/cipher.rs: -------------------------------------------------------------------------------- 1 | use aes::{Aes128, Aes192, Aes256}; 2 | use cfb_mode::Cfb; 3 | use chacha20::ChaCha20; 4 | use cipher::{consts::U16, AsyncStreamCipher, BlockCipher, BlockEncrypt, NewCipher, StreamCipher}; 5 | use ctr::Ctr128BE; 6 | use rand::distributions::Standard; 7 | use rand::{thread_rng, Rng}; 8 | 9 | use crate::util::generate_key; 10 | 11 | pub trait SymmetricCipher { 12 | /// Encrypt data in place. 13 | fn encrypt(&mut self, data: &mut [u8]); 14 | 15 | /// Decrypt data in place. 16 | fn decrypt(&mut self, data: &mut [u8]); 17 | } 18 | 19 | impl SymmetricCipher for Cfb { 20 | /// Encrypt data in place. 21 | fn encrypt(&mut self, data: &mut [u8]) { 22 | AsyncStreamCipher::encrypt(self, data) 23 | } 24 | 25 | /// Decrypt data in place. 26 | fn decrypt(&mut self, data: &mut [u8]) { 27 | AsyncStreamCipher::decrypt(self, data) 28 | } 29 | } 30 | 31 | impl> SymmetricCipher for Ctr128BE { 32 | /// Encrypt data in place. 33 | fn encrypt(&mut self, data: &mut [u8]) { 34 | StreamCipher::apply_keystream(self, data) 35 | } 36 | 37 | /// Decrypt data in place. 38 | fn decrypt(&mut self, data: &mut [u8]) { 39 | StreamCipher::apply_keystream(self, data) 40 | } 41 | } 42 | 43 | impl SymmetricCipher for ChaCha20 { 44 | /// Encrypt data in place. 45 | fn encrypt(&mut self, data: &mut [u8]) { 46 | StreamCipher::apply_keystream(self, data) 47 | } 48 | 49 | /// Decrypt data in place. 50 | fn decrypt(&mut self, data: &mut [u8]) { 51 | StreamCipher::apply_keystream(self, data) 52 | } 53 | } 54 | 55 | type Aes128Cfb = Cfb; 56 | type Aes192Cfb = Cfb; 57 | type Aes256Cfb = Cfb; 58 | type Aes128Ctr = Ctr128BE; 59 | type Aes192Ctr = Ctr128BE; 60 | type Aes256Ctr = Ctr128BE; 61 | 62 | pub struct Cipher { 63 | key: Vec, 64 | key_len: usize, 65 | iv: Vec, 66 | iv_len: usize, 67 | enc: Option>, 68 | dec: Option>, 69 | cipher_method: CipherMethod, 70 | } 71 | 72 | #[derive(Clone, Copy, Debug)] 73 | enum CipherMethod { 74 | Aes128Cfb, 75 | Aes192Cfb, 76 | Aes256Cfb, 77 | Aes128Ctr, 78 | Aes192Ctr, 79 | Aes256Ctr, 80 | ChaCha20, 81 | } 82 | 83 | impl Cipher { 84 | pub fn new(method: &str, password: &str) -> Cipher { 85 | let (key_len, cipher_method, iv_len) = match method { 86 | "aes-128-cfb" => (16, CipherMethod::Aes128Cfb, 16), 87 | "aes-192-cfb" => (24, CipherMethod::Aes192Cfb, 16), 88 | "aes-256-cfb" => (32, CipherMethod::Aes256Cfb, 16), 89 | "aes-128-ctr" => (16, CipherMethod::Aes128Ctr, 16), 90 | "aes-192-ctr" => (24, CipherMethod::Aes192Ctr, 16), 91 | "aes-256-ctr" => (32, CipherMethod::Aes256Ctr, 16), 92 | "chacha20" => (32, CipherMethod::ChaCha20, 12), 93 | _ => panic!("method not supported"), 94 | }; 95 | 96 | let key = generate_key(password.as_bytes(), key_len); 97 | Cipher { 98 | key: Vec::from(&key[..]), 99 | key_len, 100 | iv_len, 101 | iv: vec![0u8; iv_len], 102 | enc: None, 103 | dec: None, 104 | cipher_method, 105 | } 106 | } 107 | 108 | pub fn init_encrypt(&mut self) { 109 | if self.iv.is_empty() { 110 | let rng = thread_rng(); 111 | self.iv = rng.sample_iter(&Standard).take(self.iv_len).collect(); 112 | } 113 | self.enc = Some(self.new_cipher(&self.iv)); 114 | } 115 | 116 | pub fn iv(&self) -> &[u8] { 117 | &self.iv 118 | } 119 | 120 | pub fn iv_len(&self) -> usize { 121 | self.iv_len 122 | } 123 | 124 | pub fn iv_mut(&mut self) -> &mut [u8] { 125 | &mut self.iv[..] 126 | } 127 | 128 | pub fn is_encrypt_inited(&self) -> bool { 129 | self.enc.is_some() 130 | } 131 | 132 | pub fn is_decrypt_inited(&self) -> bool { 133 | self.dec.is_some() 134 | } 135 | 136 | fn new_cipher(&self, iv: &[u8]) -> Box { 137 | let key: &[u8] = &self.key; 138 | match self.cipher_method { 139 | CipherMethod::Aes128Cfb => { 140 | Box::new(Aes128Cfb::new_from_slices(key, iv).expect("init cipher error")) 141 | } 142 | CipherMethod::Aes192Cfb => { 143 | Box::new(Aes192Cfb::new_from_slices(key, iv).expect("init cipher error")) 144 | } 145 | CipherMethod::Aes256Cfb => { 146 | Box::new(Aes256Cfb::new_from_slices(key, iv).expect("init cipher error")) 147 | } 148 | CipherMethod::Aes128Ctr => Box::new(Aes128Ctr::new(key.into(), iv.into())), 149 | CipherMethod::Aes192Ctr => Box::new(Aes192Ctr::new(key.into(), iv.into())), 150 | CipherMethod::Aes256Ctr => Box::new(Aes256Ctr::new(key.into(), iv.into())), 151 | CipherMethod::ChaCha20 => Box::new(ChaCha20::new(key.into(), iv.into())), 152 | } 153 | } 154 | 155 | pub fn init_decrypt(&mut self) { 156 | self.dec = Some(self.new_cipher(&self.iv)); 157 | } 158 | 159 | pub fn encrypt(&mut self, input: &mut [u8]) { 160 | if let Some(enc) = &mut self.enc { 161 | enc.encrypt(input); 162 | } 163 | } 164 | 165 | pub fn decrypt(&mut self, input: &mut [u8]) { 166 | if let Some(dec) = &mut self.dec { 167 | dec.decrypt(input) 168 | } 169 | } 170 | 171 | #[must_use] 172 | pub fn reset(&self) -> Cipher { 173 | Cipher { 174 | key: self.key.clone(), 175 | iv: vec![0u8; self.iv_len], 176 | iv_len: self.iv_len, 177 | key_len: self.key_len, 178 | enc: None, 179 | dec: None, 180 | cipher_method: self.cipher_method, 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/copy.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::io; 3 | use std::pin::Pin; 4 | use std::sync::{Arc, Mutex}; 5 | use std::task::{ready, Context, Poll}; 6 | 7 | use futures_util::{AsyncRead, AsyncWrite}; 8 | 9 | use crate::cipher::Cipher; 10 | use crate::util::eof; 11 | 12 | pub async fn copy_bidirectional( 13 | a: &mut A, 14 | b: &mut B, 15 | cipher: Arc>, 16 | ) -> io::Result<(u64, u64)> 17 | where 18 | A: AsyncRead + AsyncWrite + Unpin + ?Sized, 19 | B: AsyncRead + AsyncWrite + Unpin + ?Sized, 20 | { 21 | CopyBidirectional { 22 | a, 23 | b, 24 | a_to_b: TransferState::Running, 25 | b_to_a: TransferState::Running, 26 | decrypter: CopyBuffer::new(cipher.clone()), 27 | encrypter: CopyBuffer::new(cipher), 28 | } 29 | .await 30 | } 31 | 32 | impl<'a, A, B> Future for CopyBidirectional<'a, A, B> 33 | where 34 | A: AsyncRead + AsyncWrite + Unpin + ?Sized, 35 | B: AsyncRead + AsyncWrite + Unpin + ?Sized, 36 | { 37 | type Output = io::Result<(u64, u64)>; 38 | 39 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 40 | let a_to_b = self.transfer_one_direction(cx, Direction::Encrypt)?; 41 | let b_to_a = self.transfer_one_direction(cx, Direction::Decrypt)?; 42 | 43 | // It is not a problem if ready! returns early because transfer_one_direction for the 44 | // other direction will keep returning TransferState::Done(count) in future calls to poll 45 | let a_to_b = ready!(a_to_b); 46 | let b_to_a = ready!(b_to_a); 47 | 48 | Poll::Ready(Ok((a_to_b, b_to_a))) 49 | } 50 | } 51 | 52 | struct CopyBidirectional<'a, A: ?Sized, B: ?Sized> { 53 | a: &'a mut A, 54 | b: &'a mut B, 55 | a_to_b: TransferState, 56 | b_to_a: TransferState, 57 | decrypter: CopyBuffer, 58 | encrypter: CopyBuffer, 59 | } 60 | 61 | enum TransferState { 62 | Running, 63 | ShuttingDown(u64), 64 | Done(u64), 65 | } 66 | 67 | #[derive(Debug)] 68 | enum Direction { 69 | Encrypt, 70 | Decrypt, 71 | } 72 | 73 | impl<'a, A, B> CopyBidirectional<'a, A, B> 74 | where 75 | A: AsyncRead + AsyncWrite + Unpin + ?Sized, 76 | B: AsyncRead + AsyncWrite + Unpin + ?Sized, 77 | { 78 | fn transfer_one_direction( 79 | &mut self, 80 | cx: &mut Context<'_>, 81 | direction: Direction, 82 | ) -> Poll> { 83 | let state = match direction { 84 | Direction::Encrypt => &mut self.a_to_b, 85 | Direction::Decrypt => &mut self.b_to_a, 86 | }; 87 | loop { 88 | match state { 89 | TransferState::Running => { 90 | let count = match direction { 91 | Direction::Encrypt => { 92 | ready!(self.encrypter.poll_encrypt(cx, self.a, self.b))? 93 | } 94 | Direction::Decrypt => { 95 | ready!(self.decrypter.poll_decrypt(cx, self.b, self.a))? 96 | } 97 | }; 98 | 99 | *state = TransferState::ShuttingDown(count); 100 | } 101 | TransferState::ShuttingDown(count) => { 102 | match direction { 103 | Direction::Encrypt => ready!(Pin::new(&mut self.b).poll_close(cx))?, 104 | Direction::Decrypt => ready!(Pin::new(&mut self.a).poll_close(cx))?, 105 | }; 106 | 107 | *state = TransferState::Done(*count); 108 | } 109 | TransferState::Done(count) => return Poll::Ready(Ok(*count)), 110 | } 111 | } 112 | } 113 | } 114 | 115 | struct CopyBuffer { 116 | cipher: Arc>, 117 | read_done: bool, 118 | pos: usize, 119 | cap: usize, 120 | amt: u64, 121 | buf: Box<[u8]>, 122 | need_flush: bool, 123 | } 124 | 125 | impl CopyBuffer { 126 | fn new(cipher: Arc>) -> CopyBuffer { 127 | CopyBuffer { 128 | cipher, 129 | read_done: false, 130 | amt: 0, 131 | pos: 0, 132 | cap: 0, 133 | buf: Box::new([0; 1024 * 2]), 134 | need_flush: false, 135 | } 136 | } 137 | 138 | fn poll_decrypt<'a, R, W>( 139 | &mut self, 140 | cx: &mut Context, 141 | mut reader: &'a mut R, 142 | writer: &'a mut W, 143 | ) -> Poll> 144 | where 145 | R: AsyncRead + Unpin + ?Sized, 146 | W: AsyncWrite + Unpin + ?Sized, 147 | { 148 | { 149 | let mut cipher = self.cipher.lock().unwrap(); 150 | if !cipher.is_decrypt_inited() { 151 | while self.pos < cipher.iv_len() { 152 | let n = ready!( 153 | Pin::new(&mut reader).poll_read(cx, &mut cipher.iv_mut()[self.pos..]) 154 | )?; 155 | self.pos += n; 156 | if n == 0 { 157 | return Err(eof()).into(); 158 | } 159 | } 160 | self.pos = 0; 161 | cipher.init_decrypt(); 162 | } 163 | } 164 | self.poll_copy(cx, reader, writer, Direction::Decrypt) 165 | } 166 | 167 | fn poll_encrypt<'a, R, W>( 168 | &mut self, 169 | cx: &mut Context, 170 | reader: &'a mut R, 171 | writer: &'a mut W, 172 | ) -> Poll> 173 | where 174 | R: AsyncRead + Unpin + ?Sized, 175 | W: AsyncWrite + Unpin + ?Sized, 176 | { 177 | { 178 | let mut cipher = self.cipher.lock().unwrap(); 179 | if !cipher.is_encrypt_inited() { 180 | cipher.init_encrypt(); 181 | self.pos = 0; 182 | let n = cipher.iv_len(); 183 | self.cap = n; 184 | self.buf[..n].copy_from_slice(cipher.iv()); 185 | } 186 | } 187 | self.poll_copy(cx, reader, writer, Direction::Encrypt) 188 | } 189 | 190 | fn poll_copy<'a, R, W>( 191 | &mut self, 192 | cx: &mut Context, 193 | mut reader: &'a mut R, 194 | mut writer: &'a mut W, 195 | direction: Direction, 196 | ) -> Poll> 197 | where 198 | R: AsyncRead + Unpin + ?Sized, 199 | W: AsyncWrite + Unpin + ?Sized, 200 | { 201 | loop { 202 | // If our buffer is empty, then we need to read some data to 203 | // continue. 204 | if self.pos == self.cap && !self.read_done { 205 | let n = match Pin::new(&mut reader).poll_read(cx, &mut self.buf) { 206 | Poll::Ready(Ok(n)) => n, 207 | Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), 208 | Poll::Pending => { 209 | // Try flushing when the reader has no progress to avoid deadlock 210 | // when the reader depends on buffered writer. 211 | if self.need_flush { 212 | ready!(Pin::new(&mut writer).poll_flush(cx))?; 213 | self.need_flush = false; 214 | } 215 | return Poll::Pending; 216 | } 217 | }; 218 | if n == 0 { 219 | self.read_done = true; 220 | } else { 221 | let mut cipher = self.cipher.lock().unwrap(); 222 | match direction { 223 | Direction::Decrypt => { 224 | cipher.decrypt(&mut self.buf[..n]); 225 | } 226 | Direction::Encrypt => { 227 | cipher.encrypt(&mut self.buf[..n]); 228 | } 229 | } 230 | self.pos = 0; 231 | self.cap = n; 232 | } 233 | } 234 | 235 | // If our buffer has some data, let's write it out! 236 | while self.pos < self.cap { 237 | let i = 238 | ready!(Pin::new(&mut writer).poll_write(cx, &self.buf[self.pos..self.cap]))?; 239 | if i == 0 { 240 | return Poll::Ready(Err(io::Error::new( 241 | io::ErrorKind::WriteZero, 242 | "write zero byte into writer", 243 | ))); 244 | } else { 245 | self.pos += i; 246 | self.amt += i as u64; 247 | self.need_flush = true; 248 | } 249 | } 250 | 251 | // If we've written all the data and we've seen EOF, flush out the 252 | // data and finish the transfer. 253 | if self.pos == self.cap && self.read_done { 254 | ready!(Pin::new(&mut writer).poll_flush(cx))?; 255 | return Poll::Ready(Ok(self.amt)); 256 | } 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/redir.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::io; 3 | use std::net::SocketAddr; 4 | use std::os::unix::io::AsRawFd; 5 | use std::pin::Pin; 6 | use std::sync::{Arc, Mutex}; 7 | use std::task::{ready, Context, Poll}; 8 | use std::time::Duration; 9 | 10 | use awak::net::{TcpListener, TcpStream, UdpSocket}; 11 | use awak::time::timeout; 12 | use awak::util::IdleTimeout; 13 | use futures_channel::mpsc::{channel, Receiver, Sender}; 14 | use futures_util::{future::join, AsyncRead, AsyncWrite, Stream}; 15 | use socket2::SockAddr; 16 | 17 | use crate::cipher::Cipher; 18 | use crate::config::{self, Mode}; 19 | use crate::io::{copy_bidirectional, write_all, DEFAULT_CHECK_INTERVAL, DEFAULT_IDLE_TIMEOUT}; 20 | use crate::util::{generate_raw_addr, other}; 21 | 22 | const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(1); 23 | const MAX_UDP_BUFFER_SIZE: usize = 65536; 24 | 25 | pub struct Server { 26 | services: Vec, 27 | } 28 | 29 | impl Server { 30 | pub fn new(configs: Vec) -> Server { 31 | let services = configs.into_iter().map(Service::new).collect(); 32 | Server { services } 33 | } 34 | 35 | pub fn serve(self) { 36 | for s in self.services { 37 | awak::spawn(async move { 38 | if let Err(e) = s.serve().await { 39 | log::error!("server fail: {:?}", e); 40 | } 41 | }) 42 | .detach(); 43 | } 44 | } 45 | } 46 | 47 | pub struct Service { 48 | config: config::Redir, 49 | } 50 | 51 | impl Service { 52 | pub fn new(config: config::Redir) -> Service { 53 | Service { config } 54 | } 55 | 56 | pub async fn serve(&self) -> io::Result<()> { 57 | match self.config.mode { 58 | Mode::Tcp => self.stream_relay().await, 59 | Mode::Udp => self.packet_relay().await, 60 | Mode::Both => { 61 | let fut1 = self.stream_relay(); 62 | let fut2 = self.packet_relay(); 63 | let _ = join(fut1, fut2).await; 64 | Ok(()) 65 | } 66 | } 67 | } 68 | 69 | pub async fn stream_relay(&self) -> io::Result<()> { 70 | let cipher = Cipher::new(&self.config.method, &self.config.password); 71 | let local_addr = self.config.local_addr; 72 | let listener = TcpListener::bind(local_addr).await?; 73 | log::info!("listening connections on {:?}", self.config.local_addr); 74 | loop { 75 | let (mut socket, addr) = listener.accept().await?; 76 | log::debug!("accept stream from addr {:?}", addr); 77 | let cipher = cipher.reset(); 78 | let server_addr = self.config.server_addr; 79 | let original_dst_addr = if let Some(addr) = self.config.redir_addr { 80 | addr 81 | } else { 82 | get_original_destination_addr(&socket)? 83 | }; 84 | let proxy = async move { 85 | if let Err(e) = proxy(server_addr, cipher, &mut socket, original_dst_addr).await { 86 | log::error!("failed to proxy; error={}", e); 87 | } 88 | }; 89 | awak::spawn(proxy).detach(); 90 | } 91 | } 92 | 93 | pub async fn packet_relay(&self) -> io::Result<()> { 94 | let cipher = Cipher::new(&self.config.method, &self.config.password); 95 | let socket = UdpSocket::bind(self.config.local_addr)?; 96 | log::info!("listening udp on {:?}", self.config.local_addr); 97 | let (sender, receiver) = channel(1024); 98 | let udp_relay = UdpRelay { 99 | buf: [0u8; MAX_UDP_BUFFER_SIZE], 100 | sender, 101 | receiver, 102 | socket, 103 | recv: None, 104 | cipher, 105 | server_addr: self.config.server_addr, 106 | redir_addr: self.config.redir_addr, 107 | }; 108 | udp_relay.await 109 | } 110 | } 111 | 112 | fn get_original_destination_addr(s: &TcpStream) -> io::Result { 113 | let fd = s.as_raw_fd(); 114 | 115 | unsafe { 116 | let (_, addr) = SockAddr::try_init(|addr, addr_len| { 117 | match s.local_addr()? { 118 | SocketAddr::V4(..) => { 119 | let ret = libc::getsockopt( 120 | fd, 121 | libc::SOL_IP, 122 | libc::SO_ORIGINAL_DST, 123 | addr as *mut _, 124 | addr_len, // libc::socklen_t 125 | ); 126 | if ret != 0 { 127 | let err = io::Error::last_os_error(); 128 | return Err(err); 129 | } 130 | } 131 | SocketAddr::V6(..) => { 132 | let ret = libc::getsockopt( 133 | fd, 134 | libc::SOL_IPV6, 135 | libc::IP6T_SO_ORIGINAL_DST, 136 | addr as *mut _, 137 | addr_len, // libc::socklen_t 138 | ); 139 | 140 | if ret != 0 { 141 | let err = io::Error::last_os_error(); 142 | return Err(err); 143 | } 144 | } 145 | } 146 | Ok(()) 147 | })?; 148 | addr.as_socket().ok_or_else(|| other("invalid SocketAddr")) 149 | } 150 | } 151 | 152 | struct UdpRelay { 153 | buf: [u8; MAX_UDP_BUFFER_SIZE], 154 | sender: Sender<(Vec, SocketAddr)>, 155 | receiver: Receiver<(Vec, SocketAddr)>, 156 | socket: UdpSocket, 157 | recv: Option<(Vec, SocketAddr)>, 158 | cipher: Cipher, 159 | server_addr: SocketAddr, 160 | redir_addr: Option, 161 | } 162 | 163 | impl UdpRelay { 164 | fn poll_send_one(&mut self, cx: &Context, data: (Vec, SocketAddr)) -> Poll> { 165 | match self.socket.poll_send_to(cx, &data.0, data.1) { 166 | Poll::Pending => { 167 | self.recv = Some(data); 168 | Poll::Pending 169 | } 170 | Poll::Ready(n) => { 171 | let _ = n?; 172 | Poll::Ready(Ok(())) 173 | } 174 | } 175 | } 176 | } 177 | 178 | impl Future for UdpRelay { 179 | type Output = io::Result<()>; 180 | 181 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 182 | let me = &mut *self; 183 | loop { 184 | match me.socket.poll_recv_from(cx, &mut me.buf) { 185 | Poll::Pending => { 186 | if let Some(data) = me.recv.take() { 187 | ready!(me.poll_send_one(cx, data))?; 188 | } 189 | while let Some(data) = ready!(Pin::new(&mut me.receiver).poll_next(cx)) { 190 | ready!(me.poll_send_one(cx, data))?; 191 | } 192 | } 193 | Poll::Ready(v) => { 194 | let (n, peer_addr) = v?; 195 | log::info!("recv {} byte from {:?}", n, peer_addr); 196 | let mut buf = vec![0u8; n]; 197 | buf.copy_from_slice(&me.buf[..n]); 198 | let cipher = me.cipher.reset(); 199 | let sender = me.sender.clone(); 200 | let server_addr = me.server_addr; 201 | let redir_addr = me.redir_addr; 202 | awak::spawn(async move { 203 | if let Err(e) = 204 | proxy_packet(server_addr, cipher, buf, peer_addr, redir_addr, sender) 205 | .await 206 | { 207 | log::error!("failed to proxy; error={}", e); 208 | }; 209 | }) 210 | .detach(); 211 | } 212 | } 213 | } 214 | } 215 | } 216 | 217 | async fn proxy_packet( 218 | server_addr: SocketAddr, 219 | mut cipher: Cipher, 220 | buf: Vec, 221 | peer_addr: SocketAddr, 222 | redir_addr: Option, 223 | mut sender: Sender<(Vec, SocketAddr)>, 224 | ) -> io::Result<(u64, u64)> { 225 | let redir_addr = if let Some(redir_addr) = redir_addr { 226 | redir_addr 227 | } else { 228 | unimplemented!() 229 | }; 230 | cipher.init_encrypt(); 231 | let mut data = cipher.iv().to_vec(); 232 | let rawaddr = generate_raw_addr(&redir_addr.ip().to_string(), redir_addr.port()); 233 | data.extend_from_slice(&rawaddr); 234 | data.extend_from_slice(&buf); 235 | cipher.encrypt(&mut data[cipher.iv_len()..]); 236 | 237 | let local: SocketAddr = ([0u8; 4], 0).into(); 238 | // send to and recv from target. 239 | let socket = UdpSocket::bind(local)?; 240 | socket.connect(server_addr)?; 241 | let _ = socket.send(&data).await?; 242 | let mut recv_buf = vec![0u8; MAX_UDP_BUFFER_SIZE]; 243 | let n = socket.recv(&mut recv_buf).await?; 244 | recv_buf.truncate(n); 245 | 246 | cipher.init_decrypt(); 247 | cipher.decrypt(&mut recv_buf); 248 | sender 249 | .try_send((recv_buf.to_vec(), peer_addr)) 250 | .map_err(|e| other(&format!("send fail: {e}")))?; 251 | Ok((buf.len() as u64, n as u64)) 252 | } 253 | 254 | async fn proxy( 255 | server_addr: SocketAddr, 256 | cipher: Cipher, 257 | socket1: &mut A, 258 | original_dst_addr: SocketAddr, 259 | ) -> io::Result<(u64, u64)> 260 | where 261 | A: AsyncRead + AsyncWrite + Unpin + ?Sized, 262 | { 263 | let cipher = Arc::new(Mutex::new(cipher)); 264 | 265 | let mut socket2 = timeout(DEFAULT_CONNECT_TIMEOUT, TcpStream::connect(&server_addr)).await??; 266 | log::debug!("connected to server {}", server_addr); 267 | 268 | let rawaddr = generate_raw_addr( 269 | &original_dst_addr.ip().to_string(), 270 | original_dst_addr.port(), 271 | ); 272 | write_all(cipher.clone(), &mut socket2, &rawaddr).await?; 273 | 274 | let (n1, n2) = IdleTimeout::new( 275 | copy_bidirectional(socket1, &mut socket2, cipher), 276 | DEFAULT_IDLE_TIMEOUT, 277 | DEFAULT_CHECK_INTERVAL, 278 | ) 279 | .await??; 280 | log::debug!("proxy local => remote: {}, remote => local: {:?}", n1, n2); 281 | Ok((n1, n2)) 282 | } 283 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::many_single_char_names)] 2 | use std::future::Future; 3 | use std::io; 4 | use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr}; 5 | use std::pin::Pin; 6 | use std::str; 7 | use std::sync::{Arc, Mutex}; 8 | use std::task::{ready, Context, Poll}; 9 | use std::time::Duration; 10 | 11 | use awak::net::{TcpStream, UdpSocket}; 12 | use awak::time::timeout; 13 | use awak::util::IdleTimeout; 14 | use futures_channel::mpsc::{channel, Receiver, Sender}; 15 | use futures_util::{future::join, AsyncRead, AsyncWrite, Stream}; 16 | 17 | use crate::cipher::Cipher; 18 | use crate::config::{self, Addr, Mode}; 19 | use crate::io::{copy_bidirectional, read_exact, DEFAULT_CHECK_INTERVAL, DEFAULT_IDLE_TIMEOUT}; 20 | use crate::listener::Listener; 21 | use crate::resolver::resolve; 22 | use crate::socks5::v5::{TYPE_DOMAIN, TYPE_IPV4, TYPE_IPV6}; 23 | use crate::util::other; 24 | 25 | const DEFAULT_GET_ADDR_INFO_TIMEOUT: Duration = Duration::from_secs(1); 26 | const DEFAULT_RESLOVE_TIMEOUT: Duration = Duration::from_secs(1); 27 | const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(1); 28 | const MAX_UDP_BUFFER_SIZE: usize = 65536; 29 | 30 | pub struct Server { 31 | services: Vec, 32 | } 33 | 34 | impl Server { 35 | pub fn new(configs: Vec) -> Server { 36 | let services = configs.into_iter().map(Service::new).collect(); 37 | Server { services } 38 | } 39 | 40 | pub fn serve(self) { 41 | for s in self.services { 42 | awak::spawn(async move { 43 | if let Err(e) = s.serve().await { 44 | log::error!("server fail: {:?}", e); 45 | } 46 | }) 47 | .detach(); 48 | } 49 | } 50 | } 51 | 52 | pub struct Service { 53 | config: config::Server, 54 | } 55 | 56 | impl Service { 57 | pub fn new(config: config::Server) -> Service { 58 | Service { config } 59 | } 60 | 61 | pub async fn stream_relay(&self) -> io::Result<()> { 62 | let cipher = Cipher::new(&self.config.method, &self.config.password); 63 | let listener = Listener::bind(self.config.local_addr.clone()).await?; 64 | log::info!("listening on {:?}", self.config.local_addr); 65 | loop { 66 | let mut socket = listener.accept().await?; 67 | let cipher = cipher.reset(); 68 | let proxy = async move { 69 | if let Err(e) = proxy(cipher, &mut socket).await { 70 | log::error!("failed to proxy; error={}", e); 71 | }; 72 | }; 73 | awak::spawn(proxy).detach(); 74 | } 75 | } 76 | 77 | pub async fn serve(&self) -> io::Result<()> { 78 | match self.config.mode { 79 | Mode::Tcp => self.stream_relay().await, 80 | Mode::Udp => self.packet_relay().await, 81 | Mode::Both => { 82 | let fut1 = self.stream_relay(); 83 | let fut2 = self.packet_relay(); 84 | let _ = join(fut1, fut2).await; 85 | Ok(()) 86 | } 87 | } 88 | } 89 | 90 | pub async fn packet_relay(&self) -> io::Result<()> { 91 | let cipher = Cipher::new(&self.config.method, &self.config.password); 92 | let addr = match &self.config.local_addr { 93 | Addr::Path(addr) => { 94 | return Err(io::Error::new( 95 | io::ErrorKind::InvalidInput, 96 | format!("invalid local_addr {addr:?}"), 97 | )); 98 | } 99 | Addr::Socket(addr) => addr, 100 | }; 101 | let socket = UdpSocket::bind(addr)?; 102 | log::info!("listening udp on {:?}", self.config.local_addr); 103 | UdpRelay::new(socket, cipher).await 104 | } 105 | } 106 | 107 | struct UdpRelay { 108 | buf: [u8; MAX_UDP_BUFFER_SIZE], 109 | sender: Sender<(Vec, SocketAddr)>, 110 | receiver: Receiver<(Vec, SocketAddr)>, 111 | socket: UdpSocket, 112 | recv: Option<(Vec, SocketAddr)>, 113 | cipher: Cipher, 114 | } 115 | 116 | impl UdpRelay { 117 | fn new(socket: UdpSocket, cipher: Cipher) -> UdpRelay { 118 | let (sender, receiver) = channel(1024); 119 | UdpRelay { 120 | buf: [0u8; MAX_UDP_BUFFER_SIZE], 121 | sender, 122 | receiver, 123 | socket, 124 | recv: None, 125 | cipher, 126 | } 127 | } 128 | 129 | fn poll_send_one(&mut self, cx: &Context, data: (Vec, SocketAddr)) -> Poll> { 130 | match self.socket.poll_send_to(cx, &data.0, data.1) { 131 | Poll::Pending => { 132 | self.recv = Some(data); 133 | Poll::Pending 134 | } 135 | Poll::Ready(n) => { 136 | let _ = n?; 137 | Poll::Ready(Ok(())) 138 | } 139 | } 140 | } 141 | } 142 | 143 | impl Future for UdpRelay { 144 | type Output = io::Result<()>; 145 | 146 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 147 | let me = &mut *self; 148 | loop { 149 | match me.socket.poll_recv_from(cx, &mut me.buf) { 150 | Poll::Pending => { 151 | if let Some(data) = me.recv.take() { 152 | ready!(me.poll_send_one(cx, data))?; 153 | } 154 | while let Some(data) = ready!(Pin::new(&mut me.receiver).poll_next(cx)) { 155 | ready!(me.poll_send_one(cx, data))?; 156 | } 157 | } 158 | Poll::Ready(v) => { 159 | let (n, peer_addr) = v?; 160 | log::info!("recv {} byte from {:?}", n, peer_addr); 161 | let mut buf = vec![0u8; n]; 162 | buf.copy_from_slice(&me.buf[..n]); 163 | let cipher = me.cipher.reset(); 164 | let sender = me.sender.clone(); 165 | awak::spawn(async move { 166 | if let Err(e) = proxy_packet(cipher, buf, peer_addr, sender).await { 167 | log::error!("failed to proxy; error={}", e); 168 | }; 169 | }) 170 | .detach(); 171 | } 172 | } 173 | } 174 | } 175 | } 176 | 177 | async fn proxy_packet( 178 | cipher: Cipher, 179 | mut buf: Vec, 180 | peer_addr: SocketAddr, 181 | mut sender: Sender<(Vec, SocketAddr)>, 182 | ) -> io::Result<(u64, u64)> { 183 | let iv_len = cipher.iv_len(); 184 | let cipher = Arc::new(Mutex::new(cipher)); 185 | 186 | // get host and port. 187 | let (n, host, port) = get_addr_info(cipher.clone(), &mut buf.as_slice()).await?; 188 | buf.drain(..n + iv_len); 189 | log::debug!("proxy to address: {}:{}", host, port); 190 | 191 | // resolver host if need. 192 | let addr = timeout(DEFAULT_RESLOVE_TIMEOUT, resolve(&host)).await??; 193 | log::debug!("resolver addr to ip: {}", addr); 194 | let local: SocketAddr = if addr.is_ipv4() { 195 | ([0u8; 4], 0).into() 196 | } else { 197 | ([0u16; 8], 0).into() 198 | }; 199 | 200 | // decrypt recv data, dec already init in get_addr_info() function. 201 | cipher.lock().unwrap().decrypt(&mut buf); 202 | 203 | // send to and recv from target. 204 | let socket = UdpSocket::bind(local)?; 205 | socket.connect((addr, port))?; 206 | let _ = socket.send(&buf).await?; 207 | let mut recv_buf = vec![0u8; MAX_UDP_BUFFER_SIZE]; 208 | let n = socket.recv(&mut recv_buf).await?; 209 | recv_buf.truncate(n); 210 | 211 | // encrypt return data. 212 | if !cipher.lock().unwrap().is_encrypt_inited() { 213 | cipher.lock().unwrap().init_encrypt(); 214 | } 215 | cipher.lock().unwrap().encrypt(&mut recv_buf); 216 | sender 217 | .try_send((recv_buf.to_vec(), peer_addr)) 218 | .map_err(|e| other(&format!("send fail: {e}")))?; 219 | Ok((buf.len() as u64, n as u64)) 220 | } 221 | 222 | async fn proxy(cipher: Cipher, socket1: &mut A) -> io::Result<(u64, u64)> 223 | where 224 | A: AsyncRead + AsyncWrite + Unpin + ?Sized, 225 | { 226 | let cipher = Arc::new(Mutex::new(cipher)); 227 | let (_, host, port) = timeout( 228 | DEFAULT_GET_ADDR_INFO_TIMEOUT, 229 | get_addr_info(cipher.clone(), socket1), 230 | ) 231 | .await 232 | .map_err(|e| other(&format!("get addr info timeout: {e:?}")))? 233 | .map_err(|e| other(&format!("get addr info fail: {e:?}")))?; 234 | log::debug!("proxy to address: {}:{}", host, port); 235 | 236 | let addr = timeout(DEFAULT_RESLOVE_TIMEOUT, resolve(&host)) 237 | .await 238 | .map_err(|e| other(&format!("resolve timeout: {e:?}")))? 239 | .map_err(|e| other(&format!("resolve fail: {e:?}")))?; 240 | log::debug!("resolver addr to ip: {}", addr); 241 | 242 | let mut socket2 = timeout(DEFAULT_CONNECT_TIMEOUT, TcpStream::connect((addr, port))) 243 | .await 244 | .map_err(|e| other(&format!("connect timeout: {e:?}")))? 245 | .map_err(|e| other(&format!("connect fail: {e:?}")))?; 246 | let _ = socket2.set_nodelay(true); 247 | log::debug!("connected to addr {}:{}", addr, port); 248 | 249 | let (n1, n2) = IdleTimeout::new( 250 | copy_bidirectional(&mut socket2, socket1, cipher), 251 | DEFAULT_IDLE_TIMEOUT, 252 | DEFAULT_CHECK_INTERVAL, 253 | ) 254 | .await 255 | .map_err(|e| other(&format!("idle timeout: {e:?}")))? 256 | .map_err(|e| other(&format!("copy bidirectional fail: {e:?}")))?; 257 | log::debug!("proxy local => remote: {}, remote => local: {}", n1, n2); 258 | Ok((n1, n2)) 259 | } 260 | 261 | async fn get_addr_info( 262 | cipher: Arc>, 263 | conn: &mut A, 264 | ) -> io::Result<(usize, String, u16)> 265 | where 266 | A: AsyncRead + Unpin + ?Sized, 267 | { 268 | let address_type = &mut [0u8; 1]; 269 | let _ = read_exact(cipher.clone(), conn, address_type).await?; 270 | match address_type.first() { 271 | // For IPv4 addresses, we read the 4 bytes for the address as 272 | // well as 2 bytes for the port. 273 | Some(&TYPE_IPV4) => { 274 | let buf = &mut [0u8; 6]; 275 | let _ = read_exact(cipher.clone(), conn, buf).await?; 276 | let addr = Ipv4Addr::new(buf[0], buf[1], buf[2], buf[3]); 277 | let port = ((buf[4] as u16) << 8) | (buf[5] as u16); 278 | Ok((7, format!("{addr}"), port)) 279 | } 280 | // For IPv6 addresses there's 16 bytes of an address plus two 281 | // bytes for a port, so we read that off and then keep going. 282 | Some(&TYPE_IPV6) => { 283 | let buf = &mut [0u8; 18]; 284 | let _ = read_exact(cipher.clone(), conn, buf).await?; 285 | let a = ((buf[0] as u16) << 8) | (buf[1] as u16); 286 | let b = ((buf[2] as u16) << 8) | (buf[3] as u16); 287 | let c = ((buf[4] as u16) << 8) | (buf[5] as u16); 288 | let d = ((buf[6] as u16) << 8) | (buf[7] as u16); 289 | let e = ((buf[8] as u16) << 8) | (buf[9] as u16); 290 | let f = ((buf[10] as u16) << 8) | (buf[11] as u16); 291 | let g = ((buf[12] as u16) << 8) | (buf[13] as u16); 292 | let h = ((buf[14] as u16) << 8) | (buf[15] as u16); 293 | let addr = Ipv6Addr::new(a, b, c, d, e, f, g, h); 294 | let port = ((buf[16] as u16) << 8) | (buf[17] as u16); 295 | Ok((19, format!("{addr}"), port)) 296 | } 297 | // The SOCKSv5 protocol not only supports proxying to specific 298 | // IP addresses, but also arbitrary hostnames. 299 | Some(&TYPE_DOMAIN) => { 300 | let buf1 = &mut [0u8]; 301 | let _ = read_exact(cipher.clone(), conn, buf1).await?; 302 | let buf2 = &mut vec![0u8; buf1[0] as usize + 2]; 303 | let _ = read_exact(cipher.clone(), conn, buf2).await?; 304 | let hostname = &buf2[..buf2.len() - 2]; 305 | let hostname = if let Ok(hostname) = str::from_utf8(hostname) { 306 | hostname 307 | } else { 308 | return Err(other("hostname include invalid utf8")); 309 | }; 310 | let pos = buf2.len() - 2; 311 | let port = ((buf2[pos] as u16) << 8) | (buf2[pos + 1] as u16); 312 | Ok((2 + buf2.len(), hostname.to_string(), port)) 313 | } 314 | n => { 315 | log::error!("unknown address type, received: {:?}", n); 316 | Err(other("unknown address type, received")) 317 | } 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /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 = "aes" 7 | version = "0.7.5" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" 10 | dependencies = [ 11 | "cfg-if", 12 | "cipher", 13 | "cpufeatures", 14 | "opaque-debug", 15 | ] 16 | 17 | [[package]] 18 | name = "aho-corasick" 19 | version = "0.7.20" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" 22 | dependencies = [ 23 | "memchr", 24 | ] 25 | 26 | [[package]] 27 | name = "anstream" 28 | version = "0.6.12" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" 31 | dependencies = [ 32 | "anstyle", 33 | "anstyle-parse", 34 | "anstyle-query", 35 | "anstyle-wincon", 36 | "colorchoice", 37 | "utf8parse", 38 | ] 39 | 40 | [[package]] 41 | name = "anstyle" 42 | version = "1.0.6" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" 45 | 46 | [[package]] 47 | name = "anstyle-parse" 48 | version = "0.2.3" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" 51 | dependencies = [ 52 | "utf8parse", 53 | ] 54 | 55 | [[package]] 56 | name = "anstyle-query" 57 | version = "1.0.2" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" 60 | dependencies = [ 61 | "windows-sys 0.52.0", 62 | ] 63 | 64 | [[package]] 65 | name = "anstyle-wincon" 66 | version = "3.0.2" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" 69 | dependencies = [ 70 | "anstyle", 71 | "windows-sys 0.52.0", 72 | ] 73 | 74 | [[package]] 75 | name = "async-task" 76 | version = "4.3.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" 79 | 80 | [[package]] 81 | name = "autocfg" 82 | version = "1.1.0" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 85 | 86 | [[package]] 87 | name = "awak" 88 | version = "0.2.36" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "7f52702c5ab0371aa24176e4c6b32d640ad12a72640d0eed1b19f080a9c4b16d" 91 | dependencies = [ 92 | "async-task", 93 | "cfg-if", 94 | "futures-core", 95 | "futures-io", 96 | "libc", 97 | "pin-project-lite", 98 | "rand", 99 | "slab", 100 | "socket2", 101 | "thread_local", 102 | ] 103 | 104 | [[package]] 105 | name = "cfb-mode" 106 | version = "0.7.1" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "750dfbb1b1f84475c1a92fed10fa5e76cb11adc0cda5225f137c5cac84e80860" 109 | dependencies = [ 110 | "cipher", 111 | ] 112 | 113 | [[package]] 114 | name = "cfg-if" 115 | version = "1.0.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 118 | 119 | [[package]] 120 | name = "chacha20" 121 | version = "0.7.3" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "f08493fa7707effc63254c66c6ea908675912493cd67952eda23c09fae2610b1" 124 | dependencies = [ 125 | "cfg-if", 126 | "cipher", 127 | "cpufeatures", 128 | ] 129 | 130 | [[package]] 131 | name = "cipher" 132 | version = "0.3.0" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" 135 | dependencies = [ 136 | "generic-array", 137 | ] 138 | 139 | [[package]] 140 | name = "colorchoice" 141 | version = "1.0.0" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 144 | 145 | [[package]] 146 | name = "cpufeatures" 147 | version = "0.2.5" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" 150 | dependencies = [ 151 | "libc", 152 | ] 153 | 154 | [[package]] 155 | name = "ctr" 156 | version = "0.7.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "a232f92a03f37dd7d7dd2adc67166c77e9cd88de5b019b9a9eecfaeaf7bfd481" 159 | dependencies = [ 160 | "cipher", 161 | ] 162 | 163 | [[package]] 164 | name = "deranged" 165 | version = "0.3.10" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" 168 | 169 | [[package]] 170 | name = "dns-resolver" 171 | version = "0.2.14" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "da783d66d90d938349ad4d67f13367236134998e01ea0d53b03f44db3e1b8d7c" 174 | dependencies = [ 175 | "awak", 176 | "domain", 177 | "futures-util", 178 | "lru_time_cache", 179 | "octseq", 180 | "smallvec", 181 | ] 182 | 183 | [[package]] 184 | name = "domain" 185 | version = "0.9.2" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "7af83e443e4bfe8602af356e5ca10b9676634e53d178875017f2ff729898a388" 188 | dependencies = [ 189 | "octseq", 190 | "rand", 191 | "smallvec", 192 | "time", 193 | ] 194 | 195 | [[package]] 196 | name = "env_filter" 197 | version = "0.1.0" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" 200 | dependencies = [ 201 | "log", 202 | "regex", 203 | ] 204 | 205 | [[package]] 206 | name = "env_logger" 207 | version = "0.11.2" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "6c012a26a7f605efc424dd53697843a72be7dc86ad2d01f7814337794a12231d" 210 | dependencies = [ 211 | "anstream", 212 | "anstyle", 213 | "env_filter", 214 | "humantime", 215 | "log", 216 | ] 217 | 218 | [[package]] 219 | name = "equivalent" 220 | version = "1.0.1" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 223 | 224 | [[package]] 225 | name = "futures-channel" 226 | version = "0.3.26" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" 229 | dependencies = [ 230 | "futures-core", 231 | ] 232 | 233 | [[package]] 234 | name = "futures-core" 235 | version = "0.3.26" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" 238 | 239 | [[package]] 240 | name = "futures-io" 241 | version = "0.3.26" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" 244 | 245 | [[package]] 246 | name = "futures-task" 247 | version = "0.3.26" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" 250 | 251 | [[package]] 252 | name = "futures-util" 253 | version = "0.3.26" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" 256 | dependencies = [ 257 | "futures-core", 258 | "futures-io", 259 | "futures-task", 260 | "memchr", 261 | "pin-project-lite", 262 | "pin-utils", 263 | "slab", 264 | ] 265 | 266 | [[package]] 267 | name = "generic-array" 268 | version = "0.14.6" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 271 | dependencies = [ 272 | "typenum", 273 | "version_check", 274 | ] 275 | 276 | [[package]] 277 | name = "getopts" 278 | version = "0.2.21" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 281 | dependencies = [ 282 | "unicode-width", 283 | ] 284 | 285 | [[package]] 286 | name = "getrandom" 287 | version = "0.2.8" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 290 | dependencies = [ 291 | "cfg-if", 292 | "libc", 293 | "wasi", 294 | ] 295 | 296 | [[package]] 297 | name = "hashbrown" 298 | version = "0.14.3" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 301 | 302 | [[package]] 303 | name = "humantime" 304 | version = "2.1.0" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 307 | 308 | [[package]] 309 | name = "indexmap" 310 | version = "2.1.0" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" 313 | dependencies = [ 314 | "equivalent", 315 | "hashbrown", 316 | ] 317 | 318 | [[package]] 319 | name = "libc" 320 | version = "0.2.139" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 323 | 324 | [[package]] 325 | name = "log" 326 | version = "0.4.17" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 329 | dependencies = [ 330 | "cfg-if", 331 | ] 332 | 333 | [[package]] 334 | name = "lru_time_cache" 335 | version = "0.11.11" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "9106e1d747ffd48e6be5bb2d97fa706ed25b144fbee4d5c02eae110cd8d6badd" 338 | 339 | [[package]] 340 | name = "md5" 341 | version = "0.7.0" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" 344 | 345 | [[package]] 346 | name = "memchr" 347 | version = "2.5.0" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 350 | 351 | [[package]] 352 | name = "octseq" 353 | version = "0.3.1" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "cd09a3d18c298e5d9b66cf65db82e2e1694b94fbc032f83e304a5cbc87bcc3bb" 356 | dependencies = [ 357 | "smallvec", 358 | ] 359 | 360 | [[package]] 361 | name = "once_cell" 362 | version = "1.19.0" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 365 | 366 | [[package]] 367 | name = "opaque-debug" 368 | version = "0.3.0" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 371 | 372 | [[package]] 373 | name = "pin-project-lite" 374 | version = "0.2.9" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 377 | 378 | [[package]] 379 | name = "pin-utils" 380 | version = "0.1.0" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 383 | 384 | [[package]] 385 | name = "ppv-lite86" 386 | version = "0.2.17" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 389 | 390 | [[package]] 391 | name = "proc-macro2" 392 | version = "1.0.50" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" 395 | dependencies = [ 396 | "unicode-ident", 397 | ] 398 | 399 | [[package]] 400 | name = "quote" 401 | version = "1.0.23" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 404 | dependencies = [ 405 | "proc-macro2", 406 | ] 407 | 408 | [[package]] 409 | name = "rabbit" 410 | version = "0.9.0" 411 | dependencies = [ 412 | "aes", 413 | "awak", 414 | "cfb-mode", 415 | "chacha20", 416 | "cipher", 417 | "ctr", 418 | "dns-resolver", 419 | "env_logger", 420 | "futures-channel", 421 | "futures-util", 422 | "getopts", 423 | "libc", 424 | "log", 425 | "md5", 426 | "pin-project-lite", 427 | "rand", 428 | "serde", 429 | "serde_derive", 430 | "socket2", 431 | "toml", 432 | ] 433 | 434 | [[package]] 435 | name = "rand" 436 | version = "0.8.5" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 439 | dependencies = [ 440 | "libc", 441 | "rand_chacha", 442 | "rand_core", 443 | ] 444 | 445 | [[package]] 446 | name = "rand_chacha" 447 | version = "0.3.1" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 450 | dependencies = [ 451 | "ppv-lite86", 452 | "rand_core", 453 | ] 454 | 455 | [[package]] 456 | name = "rand_core" 457 | version = "0.6.4" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 460 | dependencies = [ 461 | "getrandom", 462 | ] 463 | 464 | [[package]] 465 | name = "regex" 466 | version = "1.7.1" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" 469 | dependencies = [ 470 | "aho-corasick", 471 | "memchr", 472 | "regex-syntax", 473 | ] 474 | 475 | [[package]] 476 | name = "regex-syntax" 477 | version = "0.6.28" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 480 | 481 | [[package]] 482 | name = "serde" 483 | version = "1.0.152" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" 486 | 487 | [[package]] 488 | name = "serde_derive" 489 | version = "1.0.152" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" 492 | dependencies = [ 493 | "proc-macro2", 494 | "quote", 495 | "syn", 496 | ] 497 | 498 | [[package]] 499 | name = "serde_spanned" 500 | version = "0.6.4" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" 503 | dependencies = [ 504 | "serde", 505 | ] 506 | 507 | [[package]] 508 | name = "slab" 509 | version = "0.4.7" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 512 | dependencies = [ 513 | "autocfg", 514 | ] 515 | 516 | [[package]] 517 | name = "smallvec" 518 | version = "1.11.2" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" 521 | 522 | [[package]] 523 | name = "socket2" 524 | version = "0.5.1" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "bc8d618c6641ae355025c449427f9e96b98abf99a772be3cef6708d15c77147a" 527 | dependencies = [ 528 | "libc", 529 | "windows-sys 0.45.0", 530 | ] 531 | 532 | [[package]] 533 | name = "syn" 534 | version = "1.0.107" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 537 | dependencies = [ 538 | "proc-macro2", 539 | "quote", 540 | "unicode-ident", 541 | ] 542 | 543 | [[package]] 544 | name = "thread_local" 545 | version = "1.1.8" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 548 | dependencies = [ 549 | "cfg-if", 550 | "once_cell", 551 | ] 552 | 553 | [[package]] 554 | name = "time" 555 | version = "0.3.26" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "a79d09ac6b08c1ab3906a2f7cc2e81a0e27c7ae89c63812df75e52bef0751e07" 558 | dependencies = [ 559 | "deranged", 560 | "serde", 561 | "time-core", 562 | ] 563 | 564 | [[package]] 565 | name = "time-core" 566 | version = "0.1.1" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" 569 | 570 | [[package]] 571 | name = "toml" 572 | version = "0.8.8" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" 575 | dependencies = [ 576 | "serde", 577 | "serde_spanned", 578 | "toml_datetime", 579 | "toml_edit", 580 | ] 581 | 582 | [[package]] 583 | name = "toml_datetime" 584 | version = "0.6.5" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" 587 | dependencies = [ 588 | "serde", 589 | ] 590 | 591 | [[package]] 592 | name = "toml_edit" 593 | version = "0.21.0" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" 596 | dependencies = [ 597 | "indexmap", 598 | "serde", 599 | "serde_spanned", 600 | "toml_datetime", 601 | "winnow", 602 | ] 603 | 604 | [[package]] 605 | name = "typenum" 606 | version = "1.16.0" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 609 | 610 | [[package]] 611 | name = "unicode-ident" 612 | version = "1.0.6" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 615 | 616 | [[package]] 617 | name = "unicode-width" 618 | version = "0.1.10" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 621 | 622 | [[package]] 623 | name = "utf8parse" 624 | version = "0.2.1" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 627 | 628 | [[package]] 629 | name = "version_check" 630 | version = "0.9.4" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 633 | 634 | [[package]] 635 | name = "wasi" 636 | version = "0.11.0+wasi-snapshot-preview1" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 639 | 640 | [[package]] 641 | name = "windows-sys" 642 | version = "0.45.0" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 645 | dependencies = [ 646 | "windows-targets 0.42.1", 647 | ] 648 | 649 | [[package]] 650 | name = "windows-sys" 651 | version = "0.52.0" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 654 | dependencies = [ 655 | "windows-targets 0.52.0", 656 | ] 657 | 658 | [[package]] 659 | name = "windows-targets" 660 | version = "0.42.1" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" 663 | dependencies = [ 664 | "windows_aarch64_gnullvm 0.42.1", 665 | "windows_aarch64_msvc 0.42.1", 666 | "windows_i686_gnu 0.42.1", 667 | "windows_i686_msvc 0.42.1", 668 | "windows_x86_64_gnu 0.42.1", 669 | "windows_x86_64_gnullvm 0.42.1", 670 | "windows_x86_64_msvc 0.42.1", 671 | ] 672 | 673 | [[package]] 674 | name = "windows-targets" 675 | version = "0.52.0" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" 678 | dependencies = [ 679 | "windows_aarch64_gnullvm 0.52.0", 680 | "windows_aarch64_msvc 0.52.0", 681 | "windows_i686_gnu 0.52.0", 682 | "windows_i686_msvc 0.52.0", 683 | "windows_x86_64_gnu 0.52.0", 684 | "windows_x86_64_gnullvm 0.52.0", 685 | "windows_x86_64_msvc 0.52.0", 686 | ] 687 | 688 | [[package]] 689 | name = "windows_aarch64_gnullvm" 690 | version = "0.42.1" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" 693 | 694 | [[package]] 695 | name = "windows_aarch64_gnullvm" 696 | version = "0.52.0" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" 699 | 700 | [[package]] 701 | name = "windows_aarch64_msvc" 702 | version = "0.42.1" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" 705 | 706 | [[package]] 707 | name = "windows_aarch64_msvc" 708 | version = "0.52.0" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" 711 | 712 | [[package]] 713 | name = "windows_i686_gnu" 714 | version = "0.42.1" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" 717 | 718 | [[package]] 719 | name = "windows_i686_gnu" 720 | version = "0.52.0" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" 723 | 724 | [[package]] 725 | name = "windows_i686_msvc" 726 | version = "0.42.1" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" 729 | 730 | [[package]] 731 | name = "windows_i686_msvc" 732 | version = "0.52.0" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" 735 | 736 | [[package]] 737 | name = "windows_x86_64_gnu" 738 | version = "0.42.1" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" 741 | 742 | [[package]] 743 | name = "windows_x86_64_gnu" 744 | version = "0.52.0" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" 747 | 748 | [[package]] 749 | name = "windows_x86_64_gnullvm" 750 | version = "0.42.1" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" 753 | 754 | [[package]] 755 | name = "windows_x86_64_gnullvm" 756 | version = "0.52.0" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" 759 | 760 | [[package]] 761 | name = "windows_x86_64_msvc" 762 | version = "0.42.1" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" 765 | 766 | [[package]] 767 | name = "windows_x86_64_msvc" 768 | version = "0.52.0" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" 771 | 772 | [[package]] 773 | name = "winnow" 774 | version = "0.5.26" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "b67b5f0a4e7a27a64c651977932b9dc5667ca7fc31ac44b03ed37a0cf42fdfff" 777 | dependencies = [ 778 | "memchr", 779 | ] 780 | --------------------------------------------------------------------------------