├── .gitignore ├── README.md ├── src ├── future.rs ├── config.rs ├── backoff.rs ├── magic.rs ├── err.rs ├── heartbeat.rs ├── main.rs ├── opt.rs ├── stream.rs ├── client.rs ├── rw.rs └── server.rs ├── Cargo.toml ├── LICENSE ├── .github └── workflows │ └── ci.yml └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # These are backup files generated by rustfmt 6 | **/*.rs.bk 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # relayed 2 | 3 | Relay a TCP socket to a machine behind a dynamic IP/firewall. 4 | 5 | ⚠️ MOVED TO A SUBCOMMAND OF https://github.com/erikdesjardins/re ⚠️ 6 | -------------------------------------------------------------------------------- /src/future.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | pub async fn select_ok(iter: impl IntoIterator) -> Result 4 | where 5 | F: Future>, 6 | { 7 | let mut last_error = None; 8 | for fut in iter { 9 | match fut.await { 10 | Ok(x) => return Ok(x), 11 | Err(e) => last_error = Some(e), 12 | } 13 | } 14 | match last_error { 15 | Some(e) => Err(e), 16 | None => panic!("select_ok: no elements"), 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::ops::RangeInclusive; 2 | use std::time::Duration; 3 | 4 | pub const MIN_BUFFER_SIZE: usize = 4 * 1024; 5 | pub const MAX_BUFFER_SIZE: usize = 2 * 1024 * 1024; 6 | 7 | pub const QUEUE_TIMEOUT: Duration = Duration::from_secs(60); 8 | pub const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(5); 9 | pub const HEARTBEAT_TIMEOUT: Duration = Duration::from_secs(10); 10 | 11 | pub const SERVER_ACCEPT_BACKOFF_SECS: RangeInclusive = 1..=64; 12 | pub const CLIENT_BACKOFF_SECS: RangeInclusive = 1..=64; 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "relayed" 3 | version = "1.2.12" 4 | authors = [] 5 | description = "Relay a TCP socket to a machine behind a dynamic IP/firewall." 6 | edition = "2018" 7 | 8 | [dependencies] 9 | clap = { version = "4", features = ["derive"] } 10 | env_logger = { version = "0.10", default-features = false, features = ["humantime"] } 11 | futures = "0.3" 12 | log = "0.4" 13 | pin-utils = "0.1" 14 | tokio = { version = "1.0", features = ["io-util", "macros", "net", "rt", "sync", "time"] } 15 | 16 | [profile.release] 17 | panic = "abort" 18 | lto = true 19 | codegen-units = 1 20 | -------------------------------------------------------------------------------- /src/backoff.rs: -------------------------------------------------------------------------------- 1 | use std::ops::RangeInclusive; 2 | 3 | pub struct Backoff { 4 | value: u8, 5 | min: u8, 6 | max: u8, 7 | } 8 | 9 | impl Backoff { 10 | pub fn new(range: RangeInclusive) -> Self { 11 | Backoff { 12 | value: *range.start(), 13 | min: *range.start(), 14 | max: *range.end(), 15 | } 16 | } 17 | 18 | pub fn next(&mut self) -> u8 { 19 | let old_value = self.value; 20 | self.value = old_value.saturating_mul(2).min(self.max); 21 | old_value 22 | } 23 | 24 | pub fn reset(&mut self) { 25 | self.value = self.min; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/magic.rs: -------------------------------------------------------------------------------- 1 | use crate::config::HANDSHAKE_TIMEOUT; 2 | use std::io; 3 | use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 4 | use tokio::time::timeout; 5 | 6 | const MAGIC: [u8; 1] = [42]; 7 | 8 | pub async fn read_from(mut reader: impl AsyncRead + Unpin) -> Result<(), io::Error> { 9 | let mut buf = [0; 1]; 10 | timeout(HANDSHAKE_TIMEOUT, reader.read_exact(&mut buf)).await??; 11 | match buf { 12 | MAGIC => Ok(()), 13 | _ => Err(io::ErrorKind::InvalidData.into()), 14 | } 15 | } 16 | 17 | pub async fn write_to(mut writer: impl AsyncWrite + Unpin) -> Result<(), io::Error> { 18 | writer.write_all(&MAGIC).await 19 | } 20 | -------------------------------------------------------------------------------- /src/err.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Debug, Display}; 2 | use std::io; 3 | 4 | pub struct DebugFromDisplay(T); 5 | 6 | impl Debug for DebugFromDisplay { 7 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 8 | Display::fmt(&self.0, f) 9 | } 10 | } 11 | 12 | impl From for DebugFromDisplay { 13 | fn from(display: T) -> Self { 14 | DebugFromDisplay(display) 15 | } 16 | } 17 | 18 | pub trait IoErrorExt { 19 | fn applies_to(&self) -> AppliesTo; 20 | } 21 | 22 | impl IoErrorExt for io::Error { 23 | fn applies_to(&self) -> AppliesTo { 24 | match self.kind() { 25 | io::ErrorKind::ConnectionRefused 26 | | io::ErrorKind::ConnectionAborted 27 | | io::ErrorKind::ConnectionReset => AppliesTo::Connection, 28 | _ => AppliesTo::Listener, 29 | } 30 | } 31 | } 32 | 33 | pub enum AppliesTo { 34 | Connection, 35 | Listener, 36 | } 37 | -------------------------------------------------------------------------------- /src/heartbeat.rs: -------------------------------------------------------------------------------- 1 | use crate::config::HEARTBEAT_TIMEOUT; 2 | use std::convert::Infallible; 3 | use std::io; 4 | use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; 5 | use tokio::time::{interval, timeout}; 6 | 7 | const HEARTBEAT: [u8; 1] = [0xdd]; 8 | const EXIT: [u8; 1] = [0x1c]; 9 | 10 | pub async fn read_from(mut reader: impl AsyncRead + Unpin) -> Result<(), io::Error> { 11 | let mut buf = [0; 1]; 12 | loop { 13 | timeout(HEARTBEAT_TIMEOUT, reader.read_exact(&mut buf)).await??; 14 | match buf { 15 | HEARTBEAT => continue, 16 | EXIT => return Ok(()), 17 | _ => return Err(io::ErrorKind::InvalidData.into()), 18 | } 19 | } 20 | } 21 | 22 | pub async fn write_forever(mut writer: impl AsyncWrite + Unpin) -> Result { 23 | let mut heartbeat = interval(HEARTBEAT_TIMEOUT / 2); 24 | loop { 25 | writer.write_all(&HEARTBEAT).await?; 26 | heartbeat.tick().await; 27 | } 28 | } 29 | 30 | pub async fn write_final(mut writer: impl AsyncWrite + Unpin) -> Result<(), io::Error> { 31 | writer.write_all(&EXIT).await 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Erik Desjardins 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/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::manual_map)] 2 | 3 | mod backoff; 4 | mod client; 5 | mod config; 6 | mod err; 7 | mod future; 8 | mod heartbeat; 9 | mod magic; 10 | mod opt; 11 | mod rw; 12 | mod server; 13 | mod stream; 14 | 15 | #[tokio::main(flavor = "current_thread")] 16 | async fn main() -> Result<(), err::DebugFromDisplay> { 17 | let opt::Options { verbose, mode } = clap::Parser::parse(); 18 | 19 | env_logger::Builder::new() 20 | .filter_level(match verbose { 21 | 0 => log::LevelFilter::Warn, 22 | 1 => log::LevelFilter::Info, 23 | 2 => log::LevelFilter::Debug, 24 | _ => log::LevelFilter::Trace, 25 | }) 26 | .init(); 27 | 28 | let local = tokio::task::LocalSet::new(); 29 | 30 | match mode { 31 | opt::Mode::Server { gateway, public } => { 32 | local 33 | .run_until(server::run(&local, &gateway, &public)) 34 | .await?; 35 | } 36 | opt::Mode::Client { gateway, private } => { 37 | local 38 | .run_until(client::run(&local, &gateway, &private)) 39 | .await; 40 | } 41 | } 42 | 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /src/opt.rs: -------------------------------------------------------------------------------- 1 | use clap::{ArgAction, Parser, Subcommand}; 2 | use std::io; 3 | use std::net::{SocketAddr, ToSocketAddrs}; 4 | 5 | #[derive(Parser, Debug)] 6 | #[clap(version, about)] 7 | pub struct Options { 8 | /// Logging verbosity (-v info, -vv debug, -vvv trace) 9 | #[arg(short = 'v', long = "verbose", action = ArgAction::Count, global = true)] 10 | pub verbose: u8, 11 | 12 | #[command(subcommand)] 13 | pub mode: Mode, 14 | } 15 | 16 | #[derive(Subcommand, Debug)] 17 | pub enum Mode { 18 | /// Run the server half on a public machine 19 | Server { 20 | /// Socket address to receive gateway connections from client 21 | gateway: SocketAddr, 22 | 23 | /// Socket address to receive public traffic on 24 | public: SocketAddr, 25 | }, 26 | /// Run the client half on a private machine 27 | Client { 28 | /// Address of server's gateway 29 | #[arg(value_parser = socket_addrs)] 30 | gateway: V, 31 | 32 | /// Address to relay public traffic to 33 | #[arg(value_parser = socket_addrs)] 34 | private: V, 35 | }, 36 | } 37 | 38 | /// Alias to avoid clap special-casing `Vec` 39 | type V = Vec; 40 | 41 | fn socket_addrs(arg: &str) -> Result, io::Error> { 42 | let addrs = arg.to_socket_addrs()?.collect::>(); 43 | match addrs.len() { 44 | 0 => Err(io::Error::new( 45 | io::ErrorKind::AddrNotAvailable, 46 | "Resolved to zero addresses", 47 | )), 48 | _ => Ok(addrs), 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - v*.*.* 9 | pull_request: 10 | 11 | jobs: 12 | fmt: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - run: rustup toolchain install stable --profile minimal 17 | - run: rustup component add rustfmt 18 | 19 | - run: cargo fmt --all -- --check 20 | 21 | clippy: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v3 25 | - run: rustup toolchain install stable --profile minimal 26 | - run: rustup component add clippy 27 | 28 | - run: RUSTFLAGS="-D warnings" cargo clippy 29 | 30 | test: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v3 34 | - run: rustup toolchain install stable --profile minimal 35 | 36 | - run: cargo test 37 | 38 | build-linux: 39 | runs-on: ubuntu-latest 40 | steps: 41 | - uses: actions/checkout@v3 42 | - run: rustup toolchain install stable --profile minimal 43 | - run: rustup target add x86_64-unknown-linux-musl 44 | - run: sudo apt-get install musl-tools 45 | 46 | - run: cargo build --release --target=x86_64-unknown-linux-musl 47 | - run: strip target/x86_64-unknown-linux-musl/release/relayed 48 | - run: ls -lh target/x86_64-unknown-linux-musl/release/relayed 49 | 50 | - uses: softprops/action-gh-release@v1 51 | if: startsWith(github.ref, 'refs/tags/') 52 | with: 53 | files: target/x86_64-unknown-linux-musl/release/relayed 54 | env: 55 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 56 | 57 | build-windows: 58 | runs-on: windows-latest 59 | steps: 60 | - uses: actions/checkout@v3 61 | - run: rustup toolchain install stable --profile minimal 62 | 63 | - run: cargo build --release 64 | env: 65 | RUSTFLAGS: -Ctarget-feature=+crt-static 66 | - run: dir target/release/relayed.exe 67 | 68 | - uses: softprops/action-gh-release@v1 69 | if: startsWith(github.ref, 'refs/tags/') 70 | with: 71 | files: target/release/relayed.exe 72 | env: 73 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 74 | -------------------------------------------------------------------------------- /src/stream.rs: -------------------------------------------------------------------------------- 1 | use futures::stream; 2 | use futures::{Stream, StreamExt}; 3 | use pin_utils::pin_mut; 4 | use std::mem::ManuallyDrop; 5 | use std::pin::Pin; 6 | use std::task::Context; 7 | use std::task::Poll; 8 | use tokio::sync::mpsc; 9 | use tokio::task::LocalSet; 10 | 11 | /// Spawns a stream onto the local set to perform idle work. 12 | /// This keeps polling the inner stream even when no item is demanded by the parent, 13 | /// allowing it to keep making progress. 14 | pub fn spawn_idle(local: &LocalSet, f: impl FnOnce(Requests) -> S) -> impl Stream 15 | where 16 | T: 'static, 17 | S: Stream + 'static, 18 | { 19 | let (request, requests) = mpsc::channel(1); 20 | let (response, responses) = mpsc::channel(1); 21 | 22 | let idle = f(Requests(requests)); 23 | local.spawn_local(async move { 24 | pin_mut!(idle); 25 | loop { 26 | match idle.next().await { 27 | Some((token, val)) => match response.send((ManuallyDrop::new(token), val)).await { 28 | Ok(()) => continue, 29 | Err(mpsc::error::SendError(_)) => return, 30 | }, 31 | None => return, 32 | } 33 | } 34 | }); 35 | 36 | stream::unfold( 37 | (request, responses, ManuallyDrop::new(RequestToken(()))), 38 | |(request, mut responses, token)| async { 39 | match request.send(token).await { 40 | Ok(()) => match responses.recv().await { 41 | Some((token, val)) => Some((val, (request, responses, token))), 42 | None => None, 43 | }, 44 | Err(mpsc::error::SendError(_)) => None, 45 | } 46 | }, 47 | ) 48 | } 49 | 50 | pub struct RequestToken(()); 51 | 52 | impl Drop for RequestToken { 53 | fn drop(&mut self) { 54 | panic!("Deadlock: request token dropped"); 55 | } 56 | } 57 | 58 | pub struct Requests(mpsc::Receiver>); 59 | 60 | impl Stream for Requests { 61 | type Item = RequestToken; 62 | 63 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 64 | self.0 65 | .poll_recv(cx) 66 | .map(|p| p.map(ManuallyDrop::into_inner)) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | use crate::backoff::Backoff; 2 | use crate::config::CLIENT_BACKOFF_SECS; 3 | use crate::future::select_ok; 4 | use crate::heartbeat; 5 | use crate::magic; 6 | use crate::rw::conjoin; 7 | use std::io; 8 | use std::net::SocketAddr; 9 | use std::rc::Rc; 10 | use std::sync::atomic::{AtomicUsize, Ordering::*}; 11 | use std::time::Duration; 12 | use tokio::net::TcpStream; 13 | use tokio::task::LocalSet; 14 | use tokio::time::sleep; 15 | 16 | async fn connect(addrs: &[SocketAddr]) -> Result { 17 | let stream = select_ok(addrs.iter().map(TcpStream::connect)).await?; 18 | stream.set_nodelay(true)?; 19 | Ok(stream) 20 | } 21 | 22 | pub async fn run( 23 | local: &LocalSet, 24 | gateway_addrs: &[SocketAddr], 25 | private_addrs: &[SocketAddr], 26 | ) -> ! { 27 | let mut backoff = Backoff::new(CLIENT_BACKOFF_SECS); 28 | let active = Rc::new(AtomicUsize::new(0)); 29 | 30 | loop { 31 | let one_round = async { 32 | log::info!("Connecting to gateway"); 33 | let mut gateway = connect(gateway_addrs).await?; 34 | 35 | log::info!("Sending early handshake"); 36 | magic::write_to(&mut gateway).await?; 37 | 38 | log::info!("Waiting for end of heartbeat"); 39 | heartbeat::read_from(&mut gateway).await?; 40 | 41 | log::info!("Sending late handshake"); 42 | magic::write_to(&mut gateway).await?; 43 | 44 | log::info!("Connecting to private"); 45 | let private = connect(private_addrs).await?; 46 | 47 | log::info!("Spawning ({} active)", active.fetch_add(1, Relaxed) + 1); 48 | let active = active.clone(); 49 | local.spawn_local(async move { 50 | let done = conjoin(gateway, private).await; 51 | let active = active.fetch_sub(1, Relaxed) - 1; 52 | match done { 53 | Ok((down, up)) => log::info!("Closing ({} active): {}/{}", active, down, up), 54 | Err(e) => log::info!("Closing ({} active): {}", active, e), 55 | } 56 | }); 57 | 58 | Ok::<(), io::Error>(()) 59 | } 60 | .await; 61 | 62 | match one_round { 63 | Ok(()) => { 64 | backoff.reset(); 65 | } 66 | Err(e) => { 67 | log::error!("Failed: {}", e); 68 | let seconds = backoff.next(); 69 | log::warn!("Retrying in {} seconds", seconds); 70 | sleep(Duration::from_secs(u64::from(seconds))).await; 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/rw.rs: -------------------------------------------------------------------------------- 1 | use crate::config::{MAX_BUFFER_SIZE, MIN_BUFFER_SIZE}; 2 | use futures::future; 3 | use futures::ready; 4 | use std::future::Future; 5 | use std::io; 6 | use std::pin::Pin; 7 | use std::task::{Context, Poll}; 8 | use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; 9 | 10 | pub fn conjoin( 11 | mut a: impl AsyncRead + AsyncWrite + Unpin, 12 | mut b: impl AsyncRead + AsyncWrite + Unpin, 13 | ) -> impl Future> { 14 | let mut a_to_b = Buf::new(); 15 | let mut b_to_a = Buf::new(); 16 | future::poll_fn(move |cx| { 17 | // always attempt transfers in both directions 18 | let a_to_b = a_to_b.try_copy(&mut a, &mut b, cx)?; 19 | let b_to_a = b_to_a.try_copy(&mut b, &mut a, cx)?; 20 | // once both transfers are done, return transferred bytes 21 | Poll::Ready(Ok((ready!(a_to_b), ready!(b_to_a)))) 22 | }) 23 | } 24 | 25 | struct Buf { 26 | state: BufState, 27 | pos: usize, 28 | cap: usize, 29 | amt: u64, 30 | buf: Vec, 31 | } 32 | 33 | enum BufState { 34 | ReadWrite, 35 | Shutdown, 36 | Done, 37 | } 38 | 39 | impl Buf { 40 | fn new() -> Self { 41 | Self { 42 | state: BufState::ReadWrite, 43 | pos: 0, 44 | cap: 0, 45 | amt: 0, 46 | buf: vec![0; MIN_BUFFER_SIZE], 47 | } 48 | } 49 | 50 | fn try_copy( 51 | &mut self, 52 | reader: &mut (impl AsyncRead + Unpin), 53 | writer: &mut (impl AsyncWrite + Unpin), 54 | cx: &mut Context<'_>, 55 | ) -> Poll> { 56 | loop { 57 | match self.state { 58 | BufState::ReadWrite => { 59 | if self.pos == self.cap { 60 | let mut buf = ReadBuf::new(&mut self.buf); 61 | ready!(Pin::new(&mut *reader).poll_read(cx, &mut buf))?; 62 | if buf.filled().is_empty() { 63 | self.state = BufState::Shutdown; 64 | } else { 65 | self.pos = 0; 66 | self.cap = buf.filled().len(); 67 | } 68 | } 69 | 70 | while self.pos < self.cap { 71 | let i = 72 | ready!(Pin::new(&mut *writer) 73 | .poll_write(cx, &self.buf[self.pos..self.cap])?); 74 | if i == 0 { 75 | return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); 76 | } else { 77 | self.pos += i; 78 | self.amt += i as u64; 79 | // if we read and write the full buffer at once, double it 80 | if i == self.buf.len() && self.buf.len() < MAX_BUFFER_SIZE { 81 | let double_len = self.buf.len() * 2; 82 | self.buf.resize(double_len, 0); 83 | } 84 | } 85 | } 86 | } 87 | BufState::Shutdown => { 88 | ready!(Pin::new(&mut *writer).poll_shutdown(cx)?); 89 | self.state = BufState::Done; 90 | } 91 | BufState::Done => { 92 | return Poll::Ready(Ok(self.amt)); 93 | } 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | use crate::backoff::Backoff; 2 | use crate::config::{QUEUE_TIMEOUT, SERVER_ACCEPT_BACKOFF_SECS}; 3 | use crate::err::{AppliesTo, IoErrorExt}; 4 | use crate::heartbeat; 5 | use crate::magic; 6 | use crate::rw::conjoin; 7 | use crate::stream::spawn_idle; 8 | use futures::future::{select, Either}; 9 | use futures::stream; 10 | use futures::StreamExt; 11 | use pin_utils::pin_mut; 12 | use std::io; 13 | use std::net::SocketAddr; 14 | use std::rc::Rc; 15 | use std::sync::atomic::{AtomicUsize, Ordering::*}; 16 | use std::time::Duration; 17 | use tokio::net::{TcpListener, TcpStream}; 18 | use tokio::task::LocalSet; 19 | use tokio::time::error::Elapsed; 20 | use tokio::time::{sleep, timeout}; 21 | 22 | async fn accept(listener: &mut TcpListener) -> TcpStream { 23 | let mut backoff = Backoff::new(SERVER_ACCEPT_BACKOFF_SECS); 24 | loop { 25 | match listener.accept().await { 26 | Ok((stream, _addr)) => { 27 | backoff.reset(); 28 | if let Err(e) = stream.set_nodelay(true) { 29 | log::warn!("Failed to set nodelay: {}", e); 30 | continue; 31 | } 32 | return stream; 33 | } 34 | Err(e) => match e.applies_to() { 35 | AppliesTo::Connection => log::info!("Aborted connection dropped: {}", e), 36 | AppliesTo::Listener => { 37 | log::error!("Error accepting connections: {}", e); 38 | let seconds = backoff.next(); 39 | log::warn!("Retrying in {} seconds", seconds); 40 | sleep(Duration::from_secs(u64::from(seconds))).await; 41 | } 42 | }, 43 | } 44 | } 45 | } 46 | 47 | async fn drain_queue(listener: &mut TcpListener) { 48 | loop { 49 | // timeout because we need to yield to receive the second queued conn 50 | // (listener.poll_recv() won't return Poll::Ready twice in a row, 51 | // even if there are multiple queued connections) 52 | match timeout(Duration::from_millis(1), listener.accept()).await { 53 | Ok(Ok((_, _))) => log::info!("Queued conn dropped"), 54 | Ok(Err(e)) => match e.applies_to() { 55 | AppliesTo::Connection => log::info!("Queued conn dropped: {}", e), 56 | AppliesTo::Listener => break, 57 | }, 58 | Err(e) => { 59 | let _: Elapsed = e; 60 | break; 61 | } 62 | } 63 | } 64 | } 65 | 66 | pub async fn run( 67 | local: &LocalSet, 68 | gateway_addr: &SocketAddr, 69 | public_addr: &SocketAddr, 70 | ) -> Result<(), io::Error> { 71 | let active = Rc::new(AtomicUsize::new(0)); 72 | 73 | log::info!("Binding to gateway: {}", gateway_addr); 74 | let gateway_connections = TcpListener::bind(gateway_addr).await?; 75 | log::info!("Binding to public: {}", public_addr); 76 | let mut public_connections = TcpListener::bind(public_addr).await?; 77 | 78 | let gateway_connections = spawn_idle(local, |requests| { 79 | stream::unfold( 80 | (gateway_connections, requests), 81 | |(mut gateway_connections, mut requests)| async { 82 | loop { 83 | let mut gateway = accept(&mut gateway_connections).await; 84 | 85 | // early handshake: immediately kill unknown connections 86 | match magic::read_from(&mut gateway).await { 87 | Ok(()) => log::info!("Early handshake succeeded"), 88 | Err(e) => { 89 | log::info!("Early handshake failed: {}", e); 90 | continue; 91 | } 92 | } 93 | 94 | // heartbeat: so the client can tell if the connection drops 95 | let token = { 96 | let heartbeat = heartbeat::write_forever(&mut gateway); 97 | pin_mut!(heartbeat); 98 | match select(requests.next(), heartbeat).await { 99 | Either::Left((Some(token), _)) => token, 100 | Either::Left((None, _)) => return None, 101 | Either::Right((Ok(i), _)) => match i {}, 102 | Either::Right((Err(e), _)) => { 103 | log::info!("Heartbeat failed: {}", e); 104 | continue; 105 | } 106 | } 107 | }; 108 | 109 | return Some(((token, gateway), (gateway_connections, requests))); 110 | } 111 | }, 112 | ) 113 | }); 114 | 115 | pin_mut!(gateway_connections); 116 | 117 | 'public: loop { 118 | let public = accept(&mut public_connections).await; 119 | 120 | let gateway = loop { 121 | // drop public connections which wait for too long, to avoid unlimited queuing when no gateway is connected 122 | let mut gateway = match timeout(QUEUE_TIMEOUT, gateway_connections.next()).await { 123 | Ok(Some(gateway)) => gateway, 124 | Ok(None) => return Ok(()), 125 | Err(e) => { 126 | let _: Elapsed = e; 127 | log::info!("Public connection expired waiting for gateway"); 128 | drain_queue(&mut public_connections).await; 129 | continue 'public; 130 | } 131 | }; 132 | 133 | // finish heartbeat: do this as late as possible so clients can't send late handshake and disconnect 134 | match heartbeat::write_final(&mut gateway).await { 135 | Ok(()) => log::info!("Heartbeat completed"), 136 | Err(e) => { 137 | log::info!("Heartbeat failed at finalization: {}", e); 138 | continue; 139 | } 140 | } 141 | 142 | // late handshake: ensure that client hasn't disappeared some time after early handshake 143 | match magic::read_from(&mut gateway).await { 144 | Ok(()) => log::info!("Late handshake succeeded"), 145 | Err(e) => { 146 | log::info!("Late handshake failed: {}", e); 147 | continue; 148 | } 149 | } 150 | 151 | break gateway; 152 | }; 153 | 154 | log::info!("Spawning ({} active)", active.fetch_add(1, Relaxed) + 1); 155 | let active = active.clone(); 156 | local.spawn_local(async move { 157 | let done = conjoin(public, gateway).await; 158 | let active = active.fetch_sub(1, Relaxed) - 1; 159 | match done { 160 | Ok((down, up)) => log::info!("Closing ({} active): {}/{}", active, down, up), 161 | Err(e) => log::info!("Closing ({} active): {}", active, e), 162 | } 163 | }); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /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 = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "1.3.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 16 | 17 | [[package]] 18 | name = "bytes" 19 | version = "1.4.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 22 | 23 | [[package]] 24 | name = "cc" 25 | version = "1.0.79" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" 28 | 29 | [[package]] 30 | name = "cfg-if" 31 | version = "1.0.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 34 | 35 | [[package]] 36 | name = "clap" 37 | version = "4.1.4" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76" 40 | dependencies = [ 41 | "bitflags", 42 | "clap_derive", 43 | "clap_lex", 44 | "is-terminal", 45 | "once_cell", 46 | "strsim", 47 | "termcolor", 48 | ] 49 | 50 | [[package]] 51 | name = "clap_derive" 52 | version = "4.1.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" 55 | dependencies = [ 56 | "heck", 57 | "proc-macro-error", 58 | "proc-macro2", 59 | "quote", 60 | "syn", 61 | ] 62 | 63 | [[package]] 64 | name = "clap_lex" 65 | version = "0.3.1" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" 68 | dependencies = [ 69 | "os_str_bytes", 70 | ] 71 | 72 | [[package]] 73 | name = "env_logger" 74 | version = "0.10.0" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" 77 | dependencies = [ 78 | "humantime", 79 | "log", 80 | ] 81 | 82 | [[package]] 83 | name = "errno" 84 | version = "0.2.8" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" 87 | dependencies = [ 88 | "errno-dragonfly", 89 | "libc", 90 | "winapi", 91 | ] 92 | 93 | [[package]] 94 | name = "errno-dragonfly" 95 | version = "0.1.2" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 98 | dependencies = [ 99 | "cc", 100 | "libc", 101 | ] 102 | 103 | [[package]] 104 | name = "futures" 105 | version = "0.3.26" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" 108 | dependencies = [ 109 | "futures-channel", 110 | "futures-core", 111 | "futures-executor", 112 | "futures-io", 113 | "futures-sink", 114 | "futures-task", 115 | "futures-util", 116 | ] 117 | 118 | [[package]] 119 | name = "futures-channel" 120 | version = "0.3.26" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" 123 | dependencies = [ 124 | "futures-core", 125 | "futures-sink", 126 | ] 127 | 128 | [[package]] 129 | name = "futures-core" 130 | version = "0.3.26" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" 133 | 134 | [[package]] 135 | name = "futures-executor" 136 | version = "0.3.26" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" 139 | dependencies = [ 140 | "futures-core", 141 | "futures-task", 142 | "futures-util", 143 | ] 144 | 145 | [[package]] 146 | name = "futures-io" 147 | version = "0.3.26" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" 150 | 151 | [[package]] 152 | name = "futures-macro" 153 | version = "0.3.26" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" 156 | dependencies = [ 157 | "proc-macro2", 158 | "quote", 159 | "syn", 160 | ] 161 | 162 | [[package]] 163 | name = "futures-sink" 164 | version = "0.3.26" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" 167 | 168 | [[package]] 169 | name = "futures-task" 170 | version = "0.3.26" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" 173 | 174 | [[package]] 175 | name = "futures-util" 176 | version = "0.3.26" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" 179 | dependencies = [ 180 | "futures-channel", 181 | "futures-core", 182 | "futures-io", 183 | "futures-macro", 184 | "futures-sink", 185 | "futures-task", 186 | "memchr", 187 | "pin-project-lite", 188 | "pin-utils", 189 | "slab", 190 | ] 191 | 192 | [[package]] 193 | name = "heck" 194 | version = "0.4.1" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 197 | 198 | [[package]] 199 | name = "hermit-abi" 200 | version = "0.3.1" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" 203 | 204 | [[package]] 205 | name = "humantime" 206 | version = "2.1.0" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 209 | 210 | [[package]] 211 | name = "io-lifetimes" 212 | version = "1.0.5" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" 215 | dependencies = [ 216 | "libc", 217 | "windows-sys 0.45.0", 218 | ] 219 | 220 | [[package]] 221 | name = "is-terminal" 222 | version = "0.4.3" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef" 225 | dependencies = [ 226 | "hermit-abi", 227 | "io-lifetimes", 228 | "rustix", 229 | "windows-sys 0.45.0", 230 | ] 231 | 232 | [[package]] 233 | name = "libc" 234 | version = "0.2.139" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 237 | 238 | [[package]] 239 | name = "linux-raw-sys" 240 | version = "0.1.4" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" 243 | 244 | [[package]] 245 | name = "log" 246 | version = "0.4.17" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 249 | dependencies = [ 250 | "cfg-if", 251 | ] 252 | 253 | [[package]] 254 | name = "memchr" 255 | version = "2.5.0" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 258 | 259 | [[package]] 260 | name = "mio" 261 | version = "0.8.5" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" 264 | dependencies = [ 265 | "libc", 266 | "log", 267 | "wasi", 268 | "windows-sys 0.42.0", 269 | ] 270 | 271 | [[package]] 272 | name = "once_cell" 273 | version = "1.17.0" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" 276 | 277 | [[package]] 278 | name = "os_str_bytes" 279 | version = "6.4.1" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" 282 | 283 | [[package]] 284 | name = "pin-project-lite" 285 | version = "0.2.9" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 288 | 289 | [[package]] 290 | name = "pin-utils" 291 | version = "0.1.0" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 294 | 295 | [[package]] 296 | name = "proc-macro-error" 297 | version = "1.0.4" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 300 | dependencies = [ 301 | "proc-macro-error-attr", 302 | "proc-macro2", 303 | "quote", 304 | "syn", 305 | "version_check", 306 | ] 307 | 308 | [[package]] 309 | name = "proc-macro-error-attr" 310 | version = "1.0.4" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 313 | dependencies = [ 314 | "proc-macro2", 315 | "quote", 316 | "version_check", 317 | ] 318 | 319 | [[package]] 320 | name = "proc-macro2" 321 | version = "1.0.51" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" 324 | dependencies = [ 325 | "unicode-ident", 326 | ] 327 | 328 | [[package]] 329 | name = "quote" 330 | version = "1.0.23" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 333 | dependencies = [ 334 | "proc-macro2", 335 | ] 336 | 337 | [[package]] 338 | name = "relayed" 339 | version = "1.2.12" 340 | dependencies = [ 341 | "clap", 342 | "env_logger", 343 | "futures", 344 | "log", 345 | "pin-utils", 346 | "tokio", 347 | ] 348 | 349 | [[package]] 350 | name = "rustix" 351 | version = "0.36.8" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" 354 | dependencies = [ 355 | "bitflags", 356 | "errno", 357 | "io-lifetimes", 358 | "libc", 359 | "linux-raw-sys", 360 | "windows-sys 0.45.0", 361 | ] 362 | 363 | [[package]] 364 | name = "slab" 365 | version = "0.4.7" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 368 | dependencies = [ 369 | "autocfg", 370 | ] 371 | 372 | [[package]] 373 | name = "socket2" 374 | version = "0.4.7" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" 377 | dependencies = [ 378 | "libc", 379 | "winapi", 380 | ] 381 | 382 | [[package]] 383 | name = "strsim" 384 | version = "0.10.0" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 387 | 388 | [[package]] 389 | name = "syn" 390 | version = "1.0.107" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 393 | dependencies = [ 394 | "proc-macro2", 395 | "quote", 396 | "unicode-ident", 397 | ] 398 | 399 | [[package]] 400 | name = "termcolor" 401 | version = "1.2.0" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" 404 | dependencies = [ 405 | "winapi-util", 406 | ] 407 | 408 | [[package]] 409 | name = "tokio" 410 | version = "1.25.0" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" 413 | dependencies = [ 414 | "autocfg", 415 | "bytes", 416 | "libc", 417 | "memchr", 418 | "mio", 419 | "pin-project-lite", 420 | "socket2", 421 | "tokio-macros", 422 | "windows-sys 0.42.0", 423 | ] 424 | 425 | [[package]] 426 | name = "tokio-macros" 427 | version = "1.8.2" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" 430 | dependencies = [ 431 | "proc-macro2", 432 | "quote", 433 | "syn", 434 | ] 435 | 436 | [[package]] 437 | name = "unicode-ident" 438 | version = "1.0.6" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 441 | 442 | [[package]] 443 | name = "version_check" 444 | version = "0.9.4" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 447 | 448 | [[package]] 449 | name = "wasi" 450 | version = "0.11.0+wasi-snapshot-preview1" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 453 | 454 | [[package]] 455 | name = "winapi" 456 | version = "0.3.9" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 459 | dependencies = [ 460 | "winapi-i686-pc-windows-gnu", 461 | "winapi-x86_64-pc-windows-gnu", 462 | ] 463 | 464 | [[package]] 465 | name = "winapi-i686-pc-windows-gnu" 466 | version = "0.4.0" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 469 | 470 | [[package]] 471 | name = "winapi-util" 472 | version = "0.1.5" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 475 | dependencies = [ 476 | "winapi", 477 | ] 478 | 479 | [[package]] 480 | name = "winapi-x86_64-pc-windows-gnu" 481 | version = "0.4.0" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 484 | 485 | [[package]] 486 | name = "windows-sys" 487 | version = "0.42.0" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 490 | dependencies = [ 491 | "windows_aarch64_gnullvm", 492 | "windows_aarch64_msvc", 493 | "windows_i686_gnu", 494 | "windows_i686_msvc", 495 | "windows_x86_64_gnu", 496 | "windows_x86_64_gnullvm", 497 | "windows_x86_64_msvc", 498 | ] 499 | 500 | [[package]] 501 | name = "windows-sys" 502 | version = "0.45.0" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 505 | dependencies = [ 506 | "windows-targets", 507 | ] 508 | 509 | [[package]] 510 | name = "windows-targets" 511 | version = "0.42.1" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" 514 | dependencies = [ 515 | "windows_aarch64_gnullvm", 516 | "windows_aarch64_msvc", 517 | "windows_i686_gnu", 518 | "windows_i686_msvc", 519 | "windows_x86_64_gnu", 520 | "windows_x86_64_gnullvm", 521 | "windows_x86_64_msvc", 522 | ] 523 | 524 | [[package]] 525 | name = "windows_aarch64_gnullvm" 526 | version = "0.42.1" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" 529 | 530 | [[package]] 531 | name = "windows_aarch64_msvc" 532 | version = "0.42.1" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" 535 | 536 | [[package]] 537 | name = "windows_i686_gnu" 538 | version = "0.42.1" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" 541 | 542 | [[package]] 543 | name = "windows_i686_msvc" 544 | version = "0.42.1" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" 547 | 548 | [[package]] 549 | name = "windows_x86_64_gnu" 550 | version = "0.42.1" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" 553 | 554 | [[package]] 555 | name = "windows_x86_64_gnullvm" 556 | version = "0.42.1" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" 559 | 560 | [[package]] 561 | name = "windows_x86_64_msvc" 562 | version = "0.42.1" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" 565 | --------------------------------------------------------------------------------