clone from github.com/sticnarf/tokio-socks
This commit is contained in:
49
CHANGELOG.md
Normal file
49
CHANGELOG.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# 0.5.1
|
||||
|
||||
* Reduce dependencies on `futures` crate (#30)
|
||||
|
||||
# 0.5.0
|
||||
|
||||
* Upgrade tokio to 1.0 (#28)
|
||||
|
||||
# 0.4.0
|
||||
|
||||
* Return error if authorization is required but credentials are not present (#24)
|
||||
|
||||
* Upgrade tokio to 0.3 (#27)
|
||||
|
||||
# 0.3.0
|
||||
|
||||
* Allow to take arbitrary socket instead of address to establish connections to proxy (#20)
|
||||
|
||||
# 0.2.2
|
||||
|
||||
* Replace failure with thiserror (#17)
|
||||
|
||||
# 0.2.1
|
||||
|
||||
* Remove dependency derefable (#16)
|
||||
|
||||
# 0.2.0
|
||||
|
||||
* Support tokio 0.2 (#10)
|
||||
|
||||
# 0.1.3
|
||||
|
||||
* Implement `IntoTargetAddr<'static>` for `String` (#8)
|
||||
|
||||
# 0.1.2
|
||||
|
||||
* Fix ConnectFuture buffer too small (#1)
|
||||
|
||||
# 0.1.1
|
||||
|
||||
* Support SOCKS5 `BIND` command.
|
||||
|
||||
* Implement `std::net::ToSocketAddrs` for `TargetAddr`.
|
||||
|
||||
# 0.1.0
|
||||
|
||||
* Support SOCKS5 `CONNECT` command.
|
||||
|
||||
* Support username authentication.
|
||||
39
Cargo.toml
Normal file
39
Cargo.toml
Normal file
@@ -0,0 +1,39 @@
|
||||
[package]
|
||||
name = "tokio-socks"
|
||||
description = "Asynchronous SOCKS proxy support for Rust."
|
||||
documentation = "https://docs.rs/tokio-socks"
|
||||
homepage = "https://github.com/sticnarf/tokio-socks"
|
||||
repository = "https://github.com/sticnarf/tokio-socks"
|
||||
readme = "README.md"
|
||||
categories = ["asynchronous", "network-programming"]
|
||||
keywords = ["tokio", "async", "proxy", "socks", "socks5"]
|
||||
license = "MIT"
|
||||
version = "0.5.1"
|
||||
authors = ["Yilin Chen <sticnarf@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "sticnarf/tokio-socks" }
|
||||
|
||||
[features]
|
||||
tor = []
|
||||
|
||||
[[example]]
|
||||
name = "socket"
|
||||
required-features = ["tor"]
|
||||
|
||||
[[example]]
|
||||
name = "tor"
|
||||
required-features = ["tor"]
|
||||
|
||||
[dependencies]
|
||||
futures-util = { version = "0.3", default-features = false }
|
||||
tokio = { version = "1.0", features = ["io-util", "net"] }
|
||||
either = "1"
|
||||
thiserror = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
futures-executor = "0.3"
|
||||
tokio = { version = "1.0", features = ["io-util", "rt-multi-thread", "net"] }
|
||||
once_cell = "1.2.0"
|
||||
hyper = "0.14"
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Yilin Chen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
27
README.md
27
README.md
@@ -1,3 +1,26 @@
|
||||
# another-tokio-socks
|
||||
# tokio-socks
|
||||
|
||||
Clone from: https://github.com/sticnarf/tokio-socks
|
||||
[](https://travis-ci.org/sticnarf/tokio-socks)
|
||||
[](https://crates.io/crates/tokio-socks)
|
||||
[](https://docs.rs/tokio-socks)
|
||||
|
||||
Asynchronous SOCKS proxy support for Rust.
|
||||
|
||||
## Features
|
||||
|
||||
- [x] `CONNECT` command
|
||||
- [x] `BIND` command
|
||||
- [ ] `ASSOCIATE` command
|
||||
- [x] Username/password authentication
|
||||
- [ ] GSSAPI authentication
|
||||
- [ ] Asynchronous DNS resolution
|
||||
- [X] Chain proxies ([see example](examples/chainproxy.rs))
|
||||
- [ ] SOCKS4
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](/LICENSE) file for details.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
* [sfackler/rust-socks](https://github.com/sfackler/rust-socks)
|
||||
|
||||
33
examples/chainproxy.rs
Normal file
33
examples/chainproxy.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
//! Test the proxy chaining capabilities
|
||||
//!
|
||||
//! This example make uses of several public proxy.
|
||||
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::TcpStream,
|
||||
runtime::Runtime,
|
||||
};
|
||||
use tokio_socks::{tcp::Socks5Stream, Error};
|
||||
|
||||
const PROXY_ADDR: [&str; 2] = ["184.176.166.20:4145", "90.89.205.248:1080"]; // public proxies found here : http://spys.one/en/socks-proxy-list/
|
||||
const DEST_ADDR: &str = "duckduckgo.com:80";
|
||||
|
||||
async fn connect_chained_proxy() -> Result<(), Error> {
|
||||
let proxy_stream = TcpStream::connect(PROXY_ADDR[0]).await?;
|
||||
let chained_proxy_stream = Socks5Stream::connect_with_socket(proxy_stream, PROXY_ADDR[1]).await?;
|
||||
let mut stream = Socks5Stream::connect_with_socket(chained_proxy_stream, DEST_ADDR).await?;
|
||||
|
||||
stream.write_all(b"GET /\n\n").await?;
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let n = stream.read_to_end(&mut buf).await?;
|
||||
|
||||
println!("{} bytes read\n\n{}", n, String::from_utf8_lossy(&buf));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let rt = Runtime::new().unwrap();
|
||||
rt.block_on(connect_chained_proxy()).unwrap();
|
||||
}
|
||||
44
examples/socket.rs
Normal file
44
examples/socket.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
//! Test the tor proxy capabilities
|
||||
//!
|
||||
//! This example requires a running tor proxy.
|
||||
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::{TcpStream, UnixStream},
|
||||
runtime::Runtime,
|
||||
};
|
||||
use tokio_socks::{tcp::Socks5Stream, Error};
|
||||
|
||||
const UNIX_PROXY_ADDR: &str = "/tmp/tor/socket.s";
|
||||
const TCP_PROXY_ADDR: &str = "127.0.0.1:9050";
|
||||
const ONION_ADDR: &str = "3g2upl4pq6kufc4m.onion:80"; // DuckDuckGo
|
||||
|
||||
async fn connect() -> Result<(), Error> {
|
||||
// This require Tor to listen on and Unix Domain Socket.
|
||||
// You have to create a directory /tmp/tor owned by tor, and for which only tor
|
||||
// has rights, and add the following line to your torrc :
|
||||
// SocksPort unix:/tmp/tor/socket.s
|
||||
let socket = UnixStream::connect(UNIX_PROXY_ADDR).await?;
|
||||
let target = Socks5Stream::tor_resolve_with_socket(socket, "duckduckgo.com:0").await?;
|
||||
eprintln!("duckduckgo.com = {:?}", target);
|
||||
let socket = UnixStream::connect(UNIX_PROXY_ADDR).await?;
|
||||
let target = Socks5Stream::tor_resolve_ptr_with_socket(socket, "176.34.155.23:0").await?;
|
||||
eprintln!("176.34.155.23 = {:?}", target);
|
||||
|
||||
let socket = TcpStream::connect(TCP_PROXY_ADDR).await?;
|
||||
socket.set_nodelay(true)?;
|
||||
let mut conn = Socks5Stream::connect_with_socket(socket, ONION_ADDR).await?;
|
||||
conn.write_all(b"GET /\n\n").await?;
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let n = conn.read_to_end(&mut buf).await?;
|
||||
|
||||
println!("{} bytes read\n\n{}", n, String::from_utf8_lossy(&buf));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut rt = Runtime::new().unwrap();
|
||||
rt.block_on(connect()).unwrap();
|
||||
}
|
||||
34
examples/tor.rs
Normal file
34
examples/tor.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
//! Test the tor proxy capabilities
|
||||
//!
|
||||
//! This example requires a running tor proxy.
|
||||
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
runtime::Runtime,
|
||||
};
|
||||
use tokio_socks::{tcp::Socks5Stream, Error};
|
||||
|
||||
const PROXY_ADDR: &str = "127.0.0.1:9050";
|
||||
const ONION_ADDR: &str = "3g2upl4pq6kufc4m.onion:80"; // DuckDuckGo
|
||||
|
||||
async fn connect() -> Result<(), Error> {
|
||||
let target = Socks5Stream::tor_resolve(PROXY_ADDR, "duckduckgo.com:0").await?;
|
||||
eprintln!("duckduckgo.com = {:?}", target);
|
||||
let target = Socks5Stream::tor_resolve_ptr(PROXY_ADDR, "176.34.155.23:0").await?;
|
||||
eprintln!("176.34.155.23 = {:?}", target);
|
||||
|
||||
let mut conn = Socks5Stream::connect(PROXY_ADDR, ONION_ADDR).await?;
|
||||
conn.write_all(b"GET /\n\n").await?;
|
||||
|
||||
let mut buf = Vec::new();
|
||||
let n = conn.read_to_end(&mut buf).await?;
|
||||
|
||||
println!("{} bytes read\n\n{}", n, String::from_utf8_lossy(&buf));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut rt = Runtime::new().unwrap();
|
||||
rt.block_on(connect()).unwrap();
|
||||
}
|
||||
24
rustfmt.toml
Normal file
24
rustfmt.toml
Normal file
@@ -0,0 +1,24 @@
|
||||
use_small_heuristics = "default"
|
||||
hard_tabs = false
|
||||
imports_layout = "HorizontalVertical"
|
||||
merge_imports = true
|
||||
match_block_trailing_comma = true
|
||||
max_width = 120
|
||||
newline_style = "Unix"
|
||||
normalize_comments = true
|
||||
reorder_imports = true
|
||||
reorder_modules = true
|
||||
reorder_impl_items = true
|
||||
report_todo = "Never"
|
||||
report_fixme = "Never"
|
||||
space_after_colon = true
|
||||
space_before_colon = false
|
||||
struct_lit_single_line = true
|
||||
use_field_init_shorthand = true
|
||||
use_try_shorthand = true
|
||||
unstable_features = true
|
||||
format_code_in_doc_comments = true
|
||||
where_single_line = true
|
||||
wrap_comments = true
|
||||
overflow_delimited_expr = true
|
||||
edition = "2018"
|
||||
71
src/error.rs
Normal file
71
src/error.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
/// Error type of `tokio-socks`
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
/// Failure caused by an IO error.
|
||||
#[error("{0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
/// Failure when parsing a `String`.
|
||||
#[error("{0}")]
|
||||
ParseError(#[from] std::string::ParseError),
|
||||
/// Failure due to invalid target address. It contains the detailed error
|
||||
/// message.
|
||||
#[error("Target address is invalid: {0}")]
|
||||
InvalidTargetAddress(&'static str),
|
||||
/// Proxy server unreachable.
|
||||
#[error("Proxy server unreachable")]
|
||||
ProxyServerUnreachable,
|
||||
/// Proxy server returns an invalid version number.
|
||||
#[error("Invalid response version")]
|
||||
InvalidResponseVersion,
|
||||
/// No acceptable auth methods
|
||||
#[error("No acceptable auth methods")]
|
||||
NoAcceptableAuthMethods,
|
||||
/// Unknown auth method
|
||||
#[error("Unknown auth method")]
|
||||
UnknownAuthMethod,
|
||||
/// General SOCKS server failure
|
||||
#[error("General SOCKS server failure")]
|
||||
GeneralSocksServerFailure,
|
||||
/// Connection not allowed by ruleset
|
||||
#[error("Connection not allowed by ruleset")]
|
||||
ConnectionNotAllowedByRuleset,
|
||||
/// Network unreachable
|
||||
#[error("Network unreachable")]
|
||||
NetworkUnreachable,
|
||||
/// Host unreachable
|
||||
#[error("Host unreachable")]
|
||||
HostUnreachable,
|
||||
/// Connection refused
|
||||
#[error("Connection refused")]
|
||||
ConnectionRefused,
|
||||
/// TTL expired
|
||||
#[error("TTL expired")]
|
||||
TtlExpired,
|
||||
/// Command not supported
|
||||
#[error("Command not supported")]
|
||||
CommandNotSupported,
|
||||
/// Address type not supported
|
||||
#[error("Address type not supported")]
|
||||
AddressTypeNotSupported,
|
||||
/// Unknown error
|
||||
#[error("Unknown error")]
|
||||
UnknownError,
|
||||
/// Invalid reserved byte
|
||||
#[error("Invalid reserved byte")]
|
||||
InvalidReservedByte,
|
||||
/// Unknown address type
|
||||
#[error("Unknown address type")]
|
||||
UnknownAddressType,
|
||||
/// Invalid authentication values. It contains the detailed error message.
|
||||
#[error("Invalid auth values: {0}")]
|
||||
InvalidAuthValues(&'static str),
|
||||
/// Password auth failure
|
||||
#[error("Password auth failure, code: {0}")]
|
||||
PasswordAuthFailure(u8),
|
||||
|
||||
#[error("Authorization required")]
|
||||
AuthorizationRequired,
|
||||
}
|
||||
|
||||
///// Result type of `tokio-socks`
|
||||
// pub type Result<T> = std::result::Result<T, Error>;
|
||||
374
src/lib.rs
Normal file
374
src/lib.rs
Normal file
@@ -0,0 +1,374 @@
|
||||
use either::Either;
|
||||
use futures_util::{
|
||||
future,
|
||||
stream::{self, Once, Stream},
|
||||
};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
io,
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs},
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
vec,
|
||||
};
|
||||
|
||||
pub use error::Error;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// A trait for objects which can be converted or resolved to one or more
|
||||
/// `SocketAddr` values, which are going to be connected as the the proxy
|
||||
/// server.
|
||||
///
|
||||
/// This trait is similar to `std::net::ToSocketAddrs` but allows asynchronous
|
||||
/// name resolution.
|
||||
pub trait ToProxyAddrs {
|
||||
type Output: Stream<Item = Result<SocketAddr>> + Unpin;
|
||||
|
||||
fn to_proxy_addrs(&self) -> Self::Output;
|
||||
}
|
||||
|
||||
macro_rules! trivial_impl_to_proxy_addrs {
|
||||
($t: ty) => {
|
||||
impl ToProxyAddrs for $t {
|
||||
type Output = Once<future::Ready<Result<SocketAddr>>>;
|
||||
|
||||
fn to_proxy_addrs(&self) -> Self::Output {
|
||||
stream::once(future::ready(Ok(SocketAddr::from(*self))))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
trivial_impl_to_proxy_addrs!(SocketAddr);
|
||||
trivial_impl_to_proxy_addrs!((IpAddr, u16));
|
||||
trivial_impl_to_proxy_addrs!((Ipv4Addr, u16));
|
||||
trivial_impl_to_proxy_addrs!((Ipv6Addr, u16));
|
||||
trivial_impl_to_proxy_addrs!(SocketAddrV4);
|
||||
trivial_impl_to_proxy_addrs!(SocketAddrV6);
|
||||
|
||||
impl<'a> ToProxyAddrs for &'a [SocketAddr] {
|
||||
type Output = ProxyAddrsStream;
|
||||
|
||||
fn to_proxy_addrs(&self) -> Self::Output {
|
||||
ProxyAddrsStream(Some(io::Result::Ok(self.to_vec().into_iter())))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToProxyAddrs for str {
|
||||
type Output = ProxyAddrsStream;
|
||||
|
||||
fn to_proxy_addrs(&self) -> Self::Output {
|
||||
ProxyAddrsStream(Some(self.to_socket_addrs()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ToProxyAddrs for (&'a str, u16) {
|
||||
type Output = ProxyAddrsStream;
|
||||
|
||||
fn to_proxy_addrs(&self) -> Self::Output {
|
||||
ProxyAddrsStream(Some(self.to_socket_addrs()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ToProxyAddrs + ?Sized> ToProxyAddrs for &'a T {
|
||||
type Output = T::Output;
|
||||
|
||||
fn to_proxy_addrs(&self) -> Self::Output {
|
||||
(**self).to_proxy_addrs()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProxyAddrsStream(Option<io::Result<vec::IntoIter<SocketAddr>>>);
|
||||
|
||||
impl Stream for ProxyAddrsStream {
|
||||
type Item = Result<SocketAddr>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
match self.0.as_mut() {
|
||||
Some(Ok(iter)) => Poll::Ready(iter.next().map(Result::Ok)),
|
||||
Some(Err(_)) => {
|
||||
let err = self.0.take().unwrap().unwrap_err();
|
||||
Poll::Ready(Some(Err(err.into())))
|
||||
},
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A SOCKS connection target.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum TargetAddr<'a> {
|
||||
/// Connect to an IP address.
|
||||
Ip(SocketAddr),
|
||||
|
||||
/// Connect to a fully-qualified domain name.
|
||||
///
|
||||
/// The domain name will be passed along to the proxy server and DNS lookup
|
||||
/// will happen there.
|
||||
Domain(Cow<'a, str>, u16),
|
||||
}
|
||||
|
||||
impl<'a> TargetAddr<'a> {
|
||||
/// Creates owned `TargetAddr` by cloning. It is usually used to eliminate
|
||||
/// the lifetime bound.
|
||||
pub fn to_owned(&self) -> TargetAddr<'static> {
|
||||
match self {
|
||||
TargetAddr::Ip(addr) => TargetAddr::Ip(*addr),
|
||||
TargetAddr::Domain(domain, port) => TargetAddr::Domain(String::from(domain.clone()).into(), *port),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ToSocketAddrs for TargetAddr<'a> {
|
||||
type Iter = Either<std::option::IntoIter<SocketAddr>, std::vec::IntoIter<SocketAddr>>;
|
||||
|
||||
fn to_socket_addrs(&self) -> io::Result<Self::Iter> {
|
||||
Ok(match self {
|
||||
TargetAddr::Ip(addr) => Either::Left(addr.to_socket_addrs()?),
|
||||
TargetAddr::Domain(domain, port) => Either::Right((&**domain, *port).to_socket_addrs()?),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for objects that can be converted to `TargetAddr`.
|
||||
pub trait IntoTargetAddr<'a> {
|
||||
/// Converts the value of self to a `TargetAddr`.
|
||||
fn into_target_addr(self) -> Result<TargetAddr<'a>>;
|
||||
}
|
||||
|
||||
macro_rules! trivial_impl_into_target_addr {
|
||||
($t: ty) => {
|
||||
impl<'a> IntoTargetAddr<'a> for $t {
|
||||
fn into_target_addr(self) -> Result<TargetAddr<'a>> {
|
||||
Ok(TargetAddr::Ip(SocketAddr::from(self)))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
trivial_impl_into_target_addr!(SocketAddr);
|
||||
trivial_impl_into_target_addr!((IpAddr, u16));
|
||||
trivial_impl_into_target_addr!((Ipv4Addr, u16));
|
||||
trivial_impl_into_target_addr!((Ipv6Addr, u16));
|
||||
trivial_impl_into_target_addr!(SocketAddrV4);
|
||||
trivial_impl_into_target_addr!(SocketAddrV6);
|
||||
|
||||
impl<'a> IntoTargetAddr<'a> for TargetAddr<'a> {
|
||||
fn into_target_addr(self) -> Result<TargetAddr<'a>> {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoTargetAddr<'a> for (&'a str, u16) {
|
||||
fn into_target_addr(self) -> Result<TargetAddr<'a>> {
|
||||
// Try IP address first
|
||||
if let Ok(addr) = self.0.parse::<IpAddr>() {
|
||||
return (addr, self.1).into_target_addr();
|
||||
}
|
||||
|
||||
// Treat as domain name
|
||||
if self.0.len() > 255 {
|
||||
return Err(Error::InvalidTargetAddress("overlong domain"));
|
||||
}
|
||||
// TODO: Should we validate the domain format here?
|
||||
|
||||
Ok(TargetAddr::Domain(self.0.into(), self.1))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoTargetAddr<'a> for &'a str {
|
||||
fn into_target_addr(self) -> Result<TargetAddr<'a>> {
|
||||
// Try IP address first
|
||||
if let Ok(addr) = self.parse::<SocketAddr>() {
|
||||
return addr.into_target_addr();
|
||||
}
|
||||
|
||||
let mut parts_iter = self.rsplitn(2, ':');
|
||||
let port: u16 = parts_iter
|
||||
.next()
|
||||
.and_then(|port_str| port_str.parse().ok())
|
||||
.ok_or(Error::InvalidTargetAddress("invalid address format"))?;
|
||||
let domain = parts_iter
|
||||
.next()
|
||||
.ok_or(Error::InvalidTargetAddress("invalid address format"))?;
|
||||
if domain.len() > 255 {
|
||||
return Err(Error::InvalidTargetAddress("overlong domain"));
|
||||
}
|
||||
Ok(TargetAddr::Domain(domain.into(), port))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoTargetAddr<'static> for String {
|
||||
fn into_target_addr(mut self) -> Result<TargetAddr<'static>> {
|
||||
// Try IP address first
|
||||
if let Ok(addr) = self.parse::<SocketAddr>() {
|
||||
return addr.into_target_addr();
|
||||
}
|
||||
|
||||
let mut parts_iter = self.rsplitn(2, ':');
|
||||
let port: u16 = parts_iter
|
||||
.next()
|
||||
.and_then(|port_str| port_str.parse().ok())
|
||||
.ok_or(Error::InvalidTargetAddress("invalid address format"))?;
|
||||
let domain_len = parts_iter
|
||||
.next()
|
||||
.ok_or(Error::InvalidTargetAddress("invalid address format"))?
|
||||
.len();
|
||||
if domain_len > 255 {
|
||||
return Err(Error::InvalidTargetAddress("overlong domain"));
|
||||
}
|
||||
self.truncate(domain_len);
|
||||
Ok(TargetAddr::Domain(self.into(), port))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoTargetAddr<'static> for (String, u16) {
|
||||
fn into_target_addr(self) -> Result<TargetAddr<'static>> {
|
||||
let addr = (self.0.as_str(), self.1).into_target_addr()?;
|
||||
if let TargetAddr::Ip(addr) = addr {
|
||||
Ok(TargetAddr::Ip(addr))
|
||||
} else {
|
||||
Ok(TargetAddr::Domain(self.0.into(), self.1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> IntoTargetAddr<'a> for &'a T
|
||||
where T: IntoTargetAddr<'a> + Copy
|
||||
{
|
||||
fn into_target_addr(self) -> Result<TargetAddr<'a>> {
|
||||
(*self).into_target_addr()
|
||||
}
|
||||
}
|
||||
|
||||
/// Authentication methods
|
||||
#[derive(Debug)]
|
||||
enum Authentication<'a> {
|
||||
Password { username: &'a str, password: &'a str },
|
||||
None,
|
||||
}
|
||||
|
||||
impl<'a> Authentication<'a> {
|
||||
fn id(&self) -> u8 {
|
||||
match self {
|
||||
Authentication::Password { .. } => 0x02,
|
||||
Authentication::None => 0x00,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod error;
|
||||
pub mod tcp;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use futures_executor::block_on;
|
||||
use futures_util::StreamExt;
|
||||
|
||||
fn to_proxy_addrs<T: ToProxyAddrs>(t: T) -> Result<Vec<SocketAddr>> {
|
||||
Ok(block_on(t.to_proxy_addrs().map(Result::unwrap).collect()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converts_socket_addr_to_proxy_addrs() -> Result<()> {
|
||||
let addr = SocketAddr::from(([1, 1, 1, 1], 443));
|
||||
let res = to_proxy_addrs(addr)?;
|
||||
assert_eq!(&res[..], &[addr]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converts_socket_addr_ref_to_proxy_addrs() -> Result<()> {
|
||||
let addr = SocketAddr::from(([1, 1, 1, 1], 443));
|
||||
let res = to_proxy_addrs(&addr)?;
|
||||
assert_eq!(&res[..], &[addr]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converts_socket_addrs_to_proxy_addrs() -> Result<()> {
|
||||
let addrs = [
|
||||
SocketAddr::from(([1, 1, 1, 1], 443)),
|
||||
SocketAddr::from(([8, 8, 8, 8], 53)),
|
||||
];
|
||||
let res = to_proxy_addrs(&addrs[..])?;
|
||||
assert_eq!(&res[..], &addrs);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn into_target_addr<'a, T>(t: T) -> Result<TargetAddr<'a>>
|
||||
where T: IntoTargetAddr<'a> {
|
||||
t.into_target_addr()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converts_socket_addr_to_target_addr() -> Result<()> {
|
||||
let addr = SocketAddr::from(([1, 1, 1, 1], 443));
|
||||
let res = into_target_addr(addr)?;
|
||||
assert_eq!(TargetAddr::Ip(addr), res);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converts_socket_addr_ref_to_target_addr() -> Result<()> {
|
||||
let addr = SocketAddr::from(([1, 1, 1, 1], 443));
|
||||
let res = into_target_addr(&addr)?;
|
||||
assert_eq!(TargetAddr::Ip(addr), res);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converts_socket_addr_str_to_target_addr() -> Result<()> {
|
||||
let addr = SocketAddr::from(([1, 1, 1, 1], 443));
|
||||
let ip_str = format!("{}", addr);
|
||||
let res = into_target_addr(ip_str.as_str())?;
|
||||
assert_eq!(TargetAddr::Ip(addr), res);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converts_ip_str_and_port_target_addr() -> Result<()> {
|
||||
let addr = SocketAddr::from(([1, 1, 1, 1], 443));
|
||||
let ip_str = format!("{}", addr.ip());
|
||||
let res = into_target_addr((ip_str.as_str(), addr.port()))?;
|
||||
assert_eq!(TargetAddr::Ip(addr), res);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converts_domain_to_target_addr() -> Result<()> {
|
||||
let domain = "www.example.com:80";
|
||||
let res = into_target_addr(domain)?;
|
||||
assert_eq!(TargetAddr::Domain(Cow::Borrowed("www.example.com"), 80), res);
|
||||
|
||||
let res = into_target_addr(domain.to_owned())?;
|
||||
assert_eq!(TargetAddr::Domain(Cow::Owned("www.example.com".to_owned()), 80), res);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn converts_domain_and_port_to_target_addr() -> Result<()> {
|
||||
let domain = "www.example.com";
|
||||
let res = into_target_addr((domain, 80))?;
|
||||
assert_eq!(TargetAddr::Domain(Cow::Borrowed("www.example.com"), 80), res);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlong_domain_to_target_addr_should_fail() {
|
||||
let domain = format!("www.{:a<1$}.com:80", 'a', 300);
|
||||
assert!(into_target_addr(domain.as_str()).is_err());
|
||||
let domain = format!("www.{:a<1$}.com", 'a', 300);
|
||||
assert!(into_target_addr((domain.as_str(), 80)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn addr_with_invalid_port_to_target_addr_should_fail() {
|
||||
let addr = "[ffff::1]:65536";
|
||||
assert!(into_target_addr(addr).is_err());
|
||||
let addr = "www.example.com:65536";
|
||||
assert!(into_target_addr(addr).is_err());
|
||||
}
|
||||
}
|
||||
688
src/tcp.rs
Normal file
688
src/tcp.rs
Normal file
@@ -0,0 +1,688 @@
|
||||
use crate::{Authentication, Error, IntoTargetAddr, Result, TargetAddr, ToProxyAddrs};
|
||||
use futures_util::stream::{self, Fuse, Stream, StreamExt};
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
io,
|
||||
net::{Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||
ops::{Deref, DerefMut},
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tokio::{
|
||||
io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf},
|
||||
net::TcpStream,
|
||||
};
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy)]
|
||||
enum Command {
|
||||
Connect = 0x01,
|
||||
Bind = 0x02,
|
||||
#[allow(dead_code)]
|
||||
Associate = 0x03,
|
||||
#[cfg(feature = "tor")]
|
||||
TorResolve = 0xF0,
|
||||
#[cfg(feature = "tor")]
|
||||
TorResolvePtr = 0xF1,
|
||||
}
|
||||
|
||||
/// A SOCKS5 client.
|
||||
///
|
||||
/// For convenience, it can be dereferenced to it's inner socket.
|
||||
#[derive(Debug)]
|
||||
pub struct Socks5Stream<S> {
|
||||
socket: S,
|
||||
target: TargetAddr<'static>,
|
||||
}
|
||||
|
||||
impl<S> Deref for Socks5Stream<S> {
|
||||
type Target = S;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.socket
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> DerefMut for Socks5Stream<S> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.socket
|
||||
}
|
||||
}
|
||||
|
||||
impl Socks5Stream<TcpStream> {
|
||||
/// Connects to a target server through a SOCKS5 proxy given the proxy
|
||||
/// address.
|
||||
///
|
||||
/// # Error
|
||||
///
|
||||
/// It propagates the error that occurs in the conversion from `T` to
|
||||
/// `TargetAddr`.
|
||||
pub async fn connect<'t, P, T>(proxy: P, target: T) -> Result<Socks5Stream<TcpStream>>
|
||||
where
|
||||
P: ToProxyAddrs,
|
||||
T: IntoTargetAddr<'t>,
|
||||
{
|
||||
Self::execute_command(proxy, target, Authentication::None, Command::Connect).await
|
||||
}
|
||||
|
||||
/// Connects to a target server through a SOCKS5 proxy using given username,
|
||||
/// password and the address of the proxy.
|
||||
///
|
||||
/// # Error
|
||||
///
|
||||
/// It propagates the error that occurs in the conversion from `T` to
|
||||
/// `TargetAddr`.
|
||||
pub async fn connect_with_password<'a, 't, P, T>(
|
||||
proxy: P,
|
||||
target: T,
|
||||
username: &'a str,
|
||||
password: &'a str,
|
||||
) -> Result<Socks5Stream<TcpStream>>
|
||||
where
|
||||
P: ToProxyAddrs,
|
||||
T: IntoTargetAddr<'t>,
|
||||
{
|
||||
Self::execute_command(
|
||||
proxy,
|
||||
target,
|
||||
Authentication::Password { username, password },
|
||||
Command::Connect,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(feature = "tor")]
|
||||
/// Resolve the domain name to an ip using special Tor Resolve command, by
|
||||
/// connecting to a Tor compatible proxy given it's address.
|
||||
pub async fn tor_resolve<'t, P, T>(proxy: P, target: T) -> Result<TargetAddr<'static>>
|
||||
where
|
||||
P: ToProxyAddrs,
|
||||
T: IntoTargetAddr<'t>,
|
||||
{
|
||||
let sock = Self::execute_command(proxy, target, Authentication::None, Command::TorResolve).await?;
|
||||
|
||||
Ok(sock.target_addr().to_owned())
|
||||
}
|
||||
|
||||
#[cfg(feature = "tor")]
|
||||
/// Perform a reverse DNS query on the given ip using special Tor Resolve
|
||||
/// PTR command, by connecting to a Tor compatible proxy given it's
|
||||
/// address.
|
||||
pub async fn tor_resolve_ptr<'t, P, T>(proxy: P, target: T) -> Result<TargetAddr<'static>>
|
||||
where
|
||||
P: ToProxyAddrs,
|
||||
T: IntoTargetAddr<'t>,
|
||||
{
|
||||
let sock = Self::execute_command(proxy, target, Authentication::None, Command::TorResolvePtr).await?;
|
||||
|
||||
Ok(sock.target_addr().to_owned())
|
||||
}
|
||||
|
||||
async fn execute_command<'a, 't, P, T>(
|
||||
proxy: P,
|
||||
target: T,
|
||||
auth: Authentication<'a>,
|
||||
command: Command,
|
||||
) -> Result<Socks5Stream<TcpStream>>
|
||||
where
|
||||
P: ToProxyAddrs,
|
||||
T: IntoTargetAddr<'t>,
|
||||
{
|
||||
Self::validate_auth(&auth)?;
|
||||
|
||||
let sock = SocksConnector::new(auth, command, proxy.to_proxy_addrs().fuse(), target.into_target_addr()?)
|
||||
.execute()
|
||||
.await?;
|
||||
|
||||
Ok(sock)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Socks5Stream<S>
|
||||
where S: AsyncRead + AsyncWrite + Unpin
|
||||
{
|
||||
/// Connects to a target server through a SOCKS5 proxy given a socket to it.
|
||||
///
|
||||
/// # Error
|
||||
///
|
||||
/// It propagates the error that occurs in the conversion from `T` to
|
||||
/// `TargetAddr`.
|
||||
pub async fn connect_with_socket<'t, T>(socket: S, target: T) -> Result<Socks5Stream<S>>
|
||||
where T: IntoTargetAddr<'t> {
|
||||
Self::execute_command_with_socket(socket, target, Authentication::None, Command::Connect).await
|
||||
}
|
||||
|
||||
/// Connects to a target server through a SOCKS5 proxy using given username,
|
||||
/// password and a socket to the proxy
|
||||
///
|
||||
/// # Error
|
||||
///
|
||||
/// It propagates the error that occurs in the conversion from `T` to
|
||||
/// `TargetAddr`.
|
||||
pub async fn connect_with_password_and_socket<'a, 't, T>(
|
||||
socket: S,
|
||||
target: T,
|
||||
username: &'a str,
|
||||
password: &'a str,
|
||||
) -> Result<Socks5Stream<S>>
|
||||
where
|
||||
T: IntoTargetAddr<'t>,
|
||||
{
|
||||
Self::execute_command_with_socket(
|
||||
socket,
|
||||
target,
|
||||
Authentication::Password { username, password },
|
||||
Command::Connect,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn validate_auth<'a>(auth: &Authentication<'a>) -> Result<()> {
|
||||
match auth {
|
||||
Authentication::Password { username, password } => {
|
||||
let username_len = username.as_bytes().len();
|
||||
if username_len < 1 || username_len > 255 {
|
||||
Err(Error::InvalidAuthValues("username length should between 1 to 255"))?
|
||||
}
|
||||
let password_len = password.as_bytes().len();
|
||||
if password_len < 1 || password_len > 255 {
|
||||
Err(Error::InvalidAuthValues("password length should between 1 to 255"))?
|
||||
}
|
||||
},
|
||||
Authentication::None => {},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "tor")]
|
||||
/// Resolve the domain name to an ip using special Tor Resolve command, by
|
||||
/// connecting to a Tor compatible proxy given a socket to it.
|
||||
pub async fn tor_resolve_with_socket<'t, T>(socket: S, target: T) -> Result<TargetAddr<'static>>
|
||||
where T: IntoTargetAddr<'t> {
|
||||
let sock = Self::execute_command_with_socket(socket, target, Authentication::None, Command::TorResolve).await?;
|
||||
|
||||
Ok(sock.target_addr().to_owned())
|
||||
}
|
||||
|
||||
#[cfg(feature = "tor")]
|
||||
/// Perform a reverse DNS query on the given ip using special Tor Resolve
|
||||
/// PTR command, by connecting to a Tor compatible proxy given a socket
|
||||
/// to it.
|
||||
pub async fn tor_resolve_ptr_with_socket<'t, T>(socket: S, target: T) -> Result<TargetAddr<'static>>
|
||||
where T: IntoTargetAddr<'t> {
|
||||
let sock =
|
||||
Self::execute_command_with_socket(socket, target, Authentication::None, Command::TorResolvePtr).await?;
|
||||
|
||||
Ok(sock.target_addr().to_owned())
|
||||
}
|
||||
|
||||
async fn execute_command_with_socket<'a, 't, T>(
|
||||
socket: S,
|
||||
target: T,
|
||||
auth: Authentication<'a>,
|
||||
command: Command,
|
||||
) -> Result<Socks5Stream<S>>
|
||||
where
|
||||
T: IntoTargetAddr<'t>,
|
||||
{
|
||||
Self::validate_auth(&auth)?;
|
||||
|
||||
let sock = SocksConnector::new(auth, command, stream::empty().fuse(), target.into_target_addr()?)
|
||||
.execute_with_socket(socket)
|
||||
.await?;
|
||||
|
||||
Ok(sock)
|
||||
}
|
||||
|
||||
/// Consumes the `Socks5Stream`, returning the inner socket.
|
||||
pub fn into_inner(self) -> S {
|
||||
self.socket
|
||||
}
|
||||
|
||||
/// Returns the target address that the proxy server connects to.
|
||||
pub fn target_addr(&self) -> TargetAddr<'_> {
|
||||
match &self.target {
|
||||
TargetAddr::Ip(addr) => TargetAddr::Ip(*addr),
|
||||
TargetAddr::Domain(domain, port) => {
|
||||
let domain: &str = domain.borrow();
|
||||
TargetAddr::Domain(domain.into(), *port)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A `Future` which resolves to a socket to the target server through proxy.
|
||||
pub struct SocksConnector<'a, 't, S> {
|
||||
auth: Authentication<'a>,
|
||||
command: Command,
|
||||
proxy: Fuse<S>,
|
||||
target: TargetAddr<'t>,
|
||||
buf: [u8; 513],
|
||||
ptr: usize,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl<'a, 't, S> SocksConnector<'a, 't, S>
|
||||
where S: Stream<Item = Result<SocketAddr>> + Unpin
|
||||
{
|
||||
fn new(auth: Authentication<'a>, command: Command, proxy: Fuse<S>, target: TargetAddr<'t>) -> Self {
|
||||
SocksConnector {
|
||||
auth,
|
||||
command,
|
||||
proxy,
|
||||
target,
|
||||
buf: [0; 513],
|
||||
ptr: 0,
|
||||
len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Connect to the proxy server, authenticate and issue the SOCKS command
|
||||
pub async fn execute(&mut self) -> Result<Socks5Stream<TcpStream>> {
|
||||
let next_addr = self.proxy.select_next_some().await?;
|
||||
let tcp = TcpStream::connect(next_addr)
|
||||
.await
|
||||
.map_err(|_| Error::ProxyServerUnreachable)?;
|
||||
|
||||
self.execute_with_socket(tcp).await
|
||||
}
|
||||
|
||||
pub async fn execute_with_socket<T: AsyncRead + AsyncWrite + Unpin>(
|
||||
&mut self,
|
||||
mut socket: T,
|
||||
) -> Result<Socks5Stream<T>>
|
||||
{
|
||||
self.authenticate(&mut socket).await?;
|
||||
|
||||
// Send request address that should be proxied
|
||||
self.prepare_send_request();
|
||||
socket.write_all(&self.buf[self.ptr..self.len]).await?;
|
||||
|
||||
let target = self.receive_reply(&mut socket).await?;
|
||||
|
||||
Ok(Socks5Stream { socket, target })
|
||||
}
|
||||
|
||||
fn prepare_send_method_selection(&mut self) {
|
||||
self.ptr = 0;
|
||||
self.buf[0] = 0x05;
|
||||
match self.auth {
|
||||
Authentication::None => {
|
||||
self.buf[1..3].copy_from_slice(&[1, 0x00]);
|
||||
self.len = 3;
|
||||
},
|
||||
Authentication::Password { .. } => {
|
||||
self.buf[1..4].copy_from_slice(&[2, 0x00, 0x02]);
|
||||
self.len = 4;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_recv_method_selection(&mut self) {
|
||||
self.ptr = 0;
|
||||
self.len = 2;
|
||||
}
|
||||
|
||||
fn prepare_send_password_auth(&mut self) {
|
||||
if let Authentication::Password { username, password } = self.auth {
|
||||
self.ptr = 0;
|
||||
self.buf[0] = 0x01;
|
||||
let username_bytes = username.as_bytes();
|
||||
let username_len = username_bytes.len();
|
||||
self.buf[1] = username_len as u8;
|
||||
self.buf[2..(2 + username_len)].copy_from_slice(username_bytes);
|
||||
let password_bytes = password.as_bytes();
|
||||
let password_len = password_bytes.len();
|
||||
self.len = 3 + username_len + password_len;
|
||||
self.buf[(2 + username_len)] = password_len as u8;
|
||||
self.buf[(3 + username_len)..self.len].copy_from_slice(password_bytes);
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_recv_password_auth(&mut self) {
|
||||
self.ptr = 0;
|
||||
self.len = 2;
|
||||
}
|
||||
|
||||
fn prepare_send_request(&mut self) {
|
||||
self.ptr = 0;
|
||||
self.buf[..3].copy_from_slice(&[0x05, self.command as u8, 0x00]);
|
||||
match &self.target {
|
||||
TargetAddr::Ip(SocketAddr::V4(addr)) => {
|
||||
self.buf[3] = 0x01;
|
||||
self.buf[4..8].copy_from_slice(&addr.ip().octets());
|
||||
self.buf[8..10].copy_from_slice(&addr.port().to_be_bytes());
|
||||
self.len = 10;
|
||||
},
|
||||
TargetAddr::Ip(SocketAddr::V6(addr)) => {
|
||||
self.buf[3] = 0x04;
|
||||
self.buf[4..20].copy_from_slice(&addr.ip().octets());
|
||||
self.buf[20..22].copy_from_slice(&addr.port().to_be_bytes());
|
||||
self.len = 22;
|
||||
},
|
||||
TargetAddr::Domain(domain, port) => {
|
||||
self.buf[3] = 0x03;
|
||||
let domain = domain.as_bytes();
|
||||
let len = domain.len();
|
||||
self.buf[4] = len as u8;
|
||||
self.buf[5..5 + len].copy_from_slice(domain);
|
||||
self.buf[(5 + len)..(7 + len)].copy_from_slice(&port.to_be_bytes());
|
||||
self.len = 7 + len;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_recv_reply(&mut self) {
|
||||
self.ptr = 0;
|
||||
self.len = 4;
|
||||
}
|
||||
|
||||
async fn password_authentication_protocol<T: AsyncRead + AsyncWrite + Unpin>(&mut self, tcp: &mut T) -> Result<()> {
|
||||
if let Authentication::None = self.auth {
|
||||
return Err(Error::AuthorizationRequired);
|
||||
}
|
||||
|
||||
self.prepare_send_password_auth();
|
||||
tcp.write_all(&self.buf[self.ptr..self.len]).await?;
|
||||
|
||||
self.prepare_recv_password_auth();
|
||||
tcp.read_exact(&mut self.buf[self.ptr..self.len]).await?;
|
||||
|
||||
if self.buf[0] != 0x01 {
|
||||
return Err(Error::InvalidResponseVersion);
|
||||
}
|
||||
if self.buf[1] != 0x00 {
|
||||
return Err(Error::PasswordAuthFailure(self.buf[1]));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn authenticate<T: AsyncRead + AsyncWrite + Unpin>(&mut self, tcp: &mut T) -> Result<()> {
|
||||
// Write request to connect/authenticate
|
||||
self.prepare_send_method_selection();
|
||||
tcp.write_all(&self.buf[self.ptr..self.len]).await?;
|
||||
|
||||
// Receive authentication method
|
||||
self.prepare_recv_method_selection();
|
||||
tcp.read_exact(&mut self.buf[self.ptr..self.len]).await?;
|
||||
if self.buf[0] != 0x05 {
|
||||
return Err(Error::InvalidResponseVersion);
|
||||
}
|
||||
match self.buf[1] {
|
||||
0x00 => {
|
||||
// No auth
|
||||
},
|
||||
0x02 => {
|
||||
self.password_authentication_protocol(tcp).await?;
|
||||
},
|
||||
0xff => {
|
||||
return Err(Error::NoAcceptableAuthMethods);
|
||||
},
|
||||
m if m != self.auth.id() => return Err(Error::UnknownAuthMethod),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn receive_reply<T: AsyncRead + AsyncWrite + Unpin>(&mut self, tcp: &mut T) -> Result<TargetAddr<'static>> {
|
||||
self.prepare_recv_reply();
|
||||
self.ptr += tcp.read_exact(&mut self.buf[self.ptr..self.len]).await?;
|
||||
if self.buf[0] != 0x05 {
|
||||
return Err(Error::InvalidResponseVersion);
|
||||
}
|
||||
if self.buf[2] != 0x00 {
|
||||
return Err(Error::InvalidReservedByte);
|
||||
}
|
||||
|
||||
match self.buf[1] {
|
||||
0x00 => {}, // succeeded
|
||||
0x01 => Err(Error::GeneralSocksServerFailure)?,
|
||||
0x02 => Err(Error::ConnectionNotAllowedByRuleset)?,
|
||||
0x03 => Err(Error::NetworkUnreachable)?,
|
||||
0x04 => Err(Error::HostUnreachable)?,
|
||||
0x05 => Err(Error::ConnectionRefused)?,
|
||||
0x06 => Err(Error::TtlExpired)?,
|
||||
0x07 => Err(Error::CommandNotSupported)?,
|
||||
0x08 => Err(Error::AddressTypeNotSupported)?,
|
||||
_ => Err(Error::UnknownAuthMethod)?,
|
||||
}
|
||||
|
||||
match self.buf[3] {
|
||||
// IPv4
|
||||
0x01 => {
|
||||
self.len = 10;
|
||||
},
|
||||
// IPv6
|
||||
0x04 => {
|
||||
self.len = 22;
|
||||
},
|
||||
// Domain
|
||||
0x03 => {
|
||||
self.len = 5;
|
||||
self.ptr += tcp.read_exact(&mut self.buf[self.ptr..self.len]).await?;
|
||||
self.len += self.buf[4] as usize + 2;
|
||||
},
|
||||
_ => Err(Error::UnknownAddressType)?,
|
||||
}
|
||||
|
||||
self.ptr += tcp.read_exact(&mut self.buf[self.ptr..self.len]).await?;
|
||||
let target: TargetAddr<'static> = match self.buf[3] {
|
||||
// IPv4
|
||||
0x01 => {
|
||||
let mut ip = [0; 4];
|
||||
ip[..].copy_from_slice(&self.buf[4..8]);
|
||||
let ip = Ipv4Addr::from(ip);
|
||||
let port = u16::from_be_bytes([self.buf[8], self.buf[9]]);
|
||||
(ip, port).into_target_addr()?
|
||||
},
|
||||
// IPv6
|
||||
0x04 => {
|
||||
let mut ip = [0; 16];
|
||||
ip[..].copy_from_slice(&self.buf[4..20]);
|
||||
let ip = Ipv6Addr::from(ip);
|
||||
let port = u16::from_be_bytes([self.buf[20], self.buf[21]]);
|
||||
(ip, port).into_target_addr()?
|
||||
},
|
||||
// Domain
|
||||
0x03 => {
|
||||
let domain_bytes = (&self.buf[5..(self.len - 2)]).to_vec();
|
||||
let domain = String::from_utf8(domain_bytes)
|
||||
.map_err(|_| Error::InvalidTargetAddress("not a valid UTF-8 string"))?;
|
||||
let port = u16::from_be_bytes([self.buf[self.len - 2], self.buf[self.len - 1]]);
|
||||
TargetAddr::Domain(domain.into(), port)
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
Ok(target)
|
||||
}
|
||||
}
|
||||
|
||||
/// A SOCKS5 BIND client.
|
||||
///
|
||||
/// Once you get an instance of `Socks5Listener`, you should send the
|
||||
/// `bind_addr` to the remote process via the primary connection. Then, call the
|
||||
/// `accept` function and wait for the other end connecting to the rendezvous
|
||||
/// address.
|
||||
pub struct Socks5Listener<S> {
|
||||
inner: Socks5Stream<S>,
|
||||
}
|
||||
|
||||
impl Socks5Listener<TcpStream> {
|
||||
/// Initiates a BIND request to the specified proxy.
|
||||
///
|
||||
/// The proxy will filter incoming connections based on the value of
|
||||
/// `target`.
|
||||
///
|
||||
/// # Error
|
||||
///
|
||||
/// It propagates the error that occurs in the conversion from `T` to
|
||||
/// `TargetAddr`.
|
||||
pub async fn bind<'t, P, T>(proxy: P, target: T) -> Result<Socks5Listener<TcpStream>>
|
||||
where
|
||||
P: ToProxyAddrs,
|
||||
T: IntoTargetAddr<'t>,
|
||||
{
|
||||
Self::bind_with_auth(Authentication::None, proxy, target).await
|
||||
}
|
||||
|
||||
/// Initiates a BIND request to the specified proxy using given username
|
||||
/// and password.
|
||||
///
|
||||
/// The proxy will filter incoming connections based on the value of
|
||||
/// `target`.
|
||||
///
|
||||
/// # Error
|
||||
///
|
||||
/// It propagates the error that occurs in the conversion from `T` to
|
||||
/// `TargetAddr`.
|
||||
pub async fn bind_with_password<'a, 't, P, T>(
|
||||
proxy: P,
|
||||
target: T,
|
||||
username: &'a str,
|
||||
password: &'a str,
|
||||
) -> Result<Socks5Listener<TcpStream>>
|
||||
where
|
||||
P: ToProxyAddrs,
|
||||
T: IntoTargetAddr<'t>,
|
||||
{
|
||||
Self::bind_with_auth(Authentication::Password { username, password }, proxy, target).await
|
||||
}
|
||||
|
||||
async fn bind_with_auth<'t, P, T>(
|
||||
auth: Authentication<'_>,
|
||||
proxy: P,
|
||||
target: T,
|
||||
) -> Result<Socks5Listener<TcpStream>>
|
||||
where
|
||||
P: ToProxyAddrs,
|
||||
T: IntoTargetAddr<'t>,
|
||||
{
|
||||
let socket = SocksConnector::new(
|
||||
auth,
|
||||
Command::Bind,
|
||||
proxy.to_proxy_addrs().fuse(),
|
||||
target.into_target_addr()?,
|
||||
)
|
||||
.execute()
|
||||
.await?;
|
||||
|
||||
Ok(Socks5Listener { inner: socket })
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Socks5Listener<S>
|
||||
where S: AsyncRead + AsyncWrite + Unpin
|
||||
{
|
||||
/// Initiates a BIND request to the specified proxy using the given socket
|
||||
/// to it.
|
||||
///
|
||||
/// The proxy will filter incoming connections based on the value of
|
||||
/// `target`.
|
||||
///
|
||||
/// # Error
|
||||
///
|
||||
/// It propagates the error that occurs in the conversion from `T` to
|
||||
/// `TargetAddr`.
|
||||
pub async fn bind_with_socket<'t, T>(socket: S, target: T) -> Result<Socks5Listener<S>>
|
||||
where T: IntoTargetAddr<'t> {
|
||||
Self::bind_with_auth_and_socket(Authentication::None, socket, target).await
|
||||
}
|
||||
|
||||
/// Initiates a BIND request to the specified proxy using given username,
|
||||
/// password and socket to the proxy.
|
||||
///
|
||||
/// The proxy will filter incoming connections based on the value of
|
||||
/// `target`.
|
||||
///
|
||||
/// # Error
|
||||
///
|
||||
/// It propagates the error that occurs in the conversion from `T` to
|
||||
/// `TargetAddr`.
|
||||
pub async fn bind_with_password_and_socket<'a, 't, T>(
|
||||
socket: S,
|
||||
target: T,
|
||||
username: &'a str,
|
||||
password: &'a str,
|
||||
) -> Result<Socks5Listener<S>>
|
||||
where
|
||||
T: IntoTargetAddr<'t>,
|
||||
{
|
||||
Self::bind_with_auth_and_socket(Authentication::Password { username, password }, socket, target).await
|
||||
}
|
||||
|
||||
async fn bind_with_auth_and_socket<'t, T>(
|
||||
auth: Authentication<'_>,
|
||||
socket: S,
|
||||
target: T,
|
||||
) -> Result<Socks5Listener<S>>
|
||||
where
|
||||
T: IntoTargetAddr<'t>,
|
||||
{
|
||||
let socket = SocksConnector::new(auth, Command::Bind, stream::empty().fuse(), target.into_target_addr()?)
|
||||
.execute_with_socket(socket)
|
||||
.await?;
|
||||
|
||||
Ok(Socks5Listener { inner: socket })
|
||||
}
|
||||
|
||||
/// Returns the address of the proxy-side TCP listener.
|
||||
///
|
||||
/// This should be forwarded to the remote process, which should open a
|
||||
/// connection to it.
|
||||
pub fn bind_addr(&self) -> TargetAddr {
|
||||
self.inner.target_addr()
|
||||
}
|
||||
|
||||
/// Consumes this listener, returning a `Future` which resolves to the
|
||||
/// `Socks5Stream` connected to the target server through the proxy.
|
||||
///
|
||||
/// The value of `bind_addr` should be forwarded to the remote process
|
||||
/// before this method is called.
|
||||
pub async fn accept(mut self) -> Result<Socks5Stream<S>> {
|
||||
let mut connector = SocksConnector {
|
||||
auth: Authentication::None,
|
||||
command: Command::Bind,
|
||||
proxy: stream::empty().fuse(),
|
||||
target: self.inner.target,
|
||||
buf: [0; 513],
|
||||
ptr: 0,
|
||||
len: 0,
|
||||
};
|
||||
|
||||
let target = connector.receive_reply(&mut self.inner.socket).await?;
|
||||
|
||||
Ok(Socks5Stream {
|
||||
socket: self.inner.socket,
|
||||
target,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsyncRead for Socks5Stream<T>
|
||||
where T: AsyncRead + Unpin
|
||||
{
|
||||
fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<()>> {
|
||||
AsyncRead::poll_read(Pin::new(&mut self.socket), cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsyncWrite for Socks5Stream<T>
|
||||
where T: AsyncWrite + Unpin
|
||||
{
|
||||
fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<io::Result<usize>> {
|
||||
AsyncWrite::poll_write(Pin::new(&mut self.socket), cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
AsyncWrite::poll_flush(Pin::new(&mut self.socket), cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
AsyncWrite::poll_shutdown(Pin::new(&mut self.socket), cx)
|
||||
}
|
||||
}
|
||||
74
tests/common.rs
Normal file
74
tests/common.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
net::{SocketAddr, TcpStream as StdTcpStream},
|
||||
sync::Mutex,
|
||||
};
|
||||
use tokio::{
|
||||
io::{copy, split, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
|
||||
net::{TcpListener, UnixStream},
|
||||
runtime::Runtime,
|
||||
};
|
||||
use tokio_socks::{
|
||||
tcp::{Socks5Listener, Socks5Stream},
|
||||
Error,
|
||||
Result,
|
||||
};
|
||||
|
||||
pub const UNIX_PROXY_ADDR: &'static str = "/tmp/proxy.s";
|
||||
pub const PROXY_ADDR: &'static str = "127.0.0.1:41080";
|
||||
pub const ECHO_SERVER_ADDR: &'static str = "localhost:10007";
|
||||
pub const MSG: &[u8] = b"hello";
|
||||
|
||||
pub async fn echo_server() -> Result<()> {
|
||||
let listener = TcpListener::bind(&SocketAddr::from(([0, 0, 0, 0], 10007))).await?;
|
||||
loop {
|
||||
let (mut stream, _) = listener.accept().await?;
|
||||
tokio::spawn(async move {
|
||||
let (mut reader, mut writer) = stream.split();
|
||||
copy(&mut reader, &mut writer).await.unwrap();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn reply_response<S: AsyncRead + AsyncWrite + Unpin>(mut socket: Socks5Stream<S>) -> Result<[u8; 5]> {
|
||||
socket.write_all(MSG).await?;
|
||||
let mut buf = [0; 5];
|
||||
socket.read_exact(&mut buf).await?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
pub async fn test_connect<S: AsyncRead + AsyncWrite + Unpin>(socket: Socks5Stream<S>) -> Result<()> {
|
||||
let res = reply_response(socket).await?;
|
||||
assert_eq!(&res[..], MSG);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn test_bind<S: 'static + AsyncRead + AsyncWrite + Unpin + Send>(listener: Socks5Listener<S>) -> Result<()> {
|
||||
let bind_addr = listener.bind_addr().to_owned();
|
||||
runtime().lock().unwrap().spawn(async move {
|
||||
let stream = listener.accept().await.unwrap();
|
||||
let (mut reader, mut writer) = split(stream);
|
||||
copy(&mut reader, &mut writer).await.unwrap();
|
||||
});
|
||||
|
||||
let mut tcp = StdTcpStream::connect(bind_addr)?;
|
||||
tcp.write_all(MSG)?;
|
||||
let mut buf = [0; 5];
|
||||
tcp.read_exact(&mut buf[..])?;
|
||||
assert_eq!(&buf[..], MSG);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn connect_unix() -> Result<UnixStream> {
|
||||
UnixStream::connect(UNIX_PROXY_ADDR).await.map_err(Error::Io)
|
||||
}
|
||||
|
||||
pub fn runtime() -> &'static Mutex<Runtime> {
|
||||
static RUNTIME: OnceCell<Mutex<Runtime>> = OnceCell::new();
|
||||
RUNTIME.get_or_init(|| {
|
||||
let runtime = Runtime::new().expect("Unable to create runtime");
|
||||
runtime.spawn(async { echo_server().await.expect("Unable to bind") });
|
||||
Mutex::new(runtime)
|
||||
})
|
||||
}
|
||||
35
tests/integration_tests.sh
Executable file
35
tests/integration_tests.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
set -x
|
||||
|
||||
dir="$(dirname "$(which "$0")")"
|
||||
SOCK="/tmp/proxy.s"
|
||||
PROXY_HOST="127.0.0.1:41080"
|
||||
|
||||
|
||||
#socat tcp-listen:10007,fork exec:cat &
|
||||
#echo $! > /tmp/socat-test.pid
|
||||
|
||||
if test -z "$@"; then
|
||||
list="no_auth username_auth long_username_password_auth"
|
||||
else
|
||||
list="$@"
|
||||
fi
|
||||
|
||||
socat UNIX-LISTEN:${SOCK},reuseaddr,fork TCP:${PROXY_HOST} &
|
||||
|
||||
for test in ${list}; do
|
||||
3proxy ${dir}/${test}.cfg
|
||||
sleep 1
|
||||
cargo test --test ${test} -- --test-threads 1
|
||||
test_exit_code=$?
|
||||
|
||||
pkill -F /tmp/3proxy-test.pid
|
||||
|
||||
if test "$test_exit_code" -ne 0; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
#pkill -F /tmp/socat-test.pid
|
||||
exit ${test_exit_code}
|
||||
6
tests/long_username_password_auth.cfg
Normal file
6
tests/long_username_password_auth.cfg
Normal file
@@ -0,0 +1,6 @@
|
||||
daemon
|
||||
users mylonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglogin:CL:longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglongpassword
|
||||
pidfile /tmp/3proxy-test.pid
|
||||
auth strong
|
||||
allow mylonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglogin
|
||||
socks -p41080
|
||||
55
tests/long_username_password_auth.rs
Normal file
55
tests/long_username_password_auth.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
mod common;
|
||||
|
||||
use common::{connect_unix, runtime, test_bind, test_connect, ECHO_SERVER_ADDR, PROXY_ADDR};
|
||||
use tokio_socks::{
|
||||
tcp::{Socks5Listener, Socks5Stream},
|
||||
Result,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn connect_long_username_password() -> Result<()> {
|
||||
let runtime = runtime().lock().unwrap();
|
||||
let conn = runtime.block_on(Socks5Stream::connect_with_password(
|
||||
PROXY_ADDR, ECHO_SERVER_ADDR, "mylonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglogin",
|
||||
"longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglongpassword"))?;
|
||||
runtime.block_on(test_connect(conn))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bind_long_username_password() -> Result<()> {
|
||||
let bind = {
|
||||
let runtime = runtime().lock().unwrap();
|
||||
runtime.block_on(Socks5Listener::bind_with_password(
|
||||
PROXY_ADDR,
|
||||
ECHO_SERVER_ADDR,
|
||||
"mylonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglogin",
|
||||
"longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglongpassword"
|
||||
))
|
||||
}?;
|
||||
test_bind(bind)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect_with_socket_long_username_password() -> Result<()> {
|
||||
let runtime = runtime().lock().unwrap();
|
||||
let socket = runtime.block_on(connect_unix())?;
|
||||
let conn = runtime.block_on(Socks5Stream::connect_with_password_and_socket(
|
||||
socket, ECHO_SERVER_ADDR, "mylonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglogin",
|
||||
"longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglongpassword"))?;
|
||||
runtime.block_on(test_connect(conn))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bind_with_socket_long_username_password() -> Result<()> {
|
||||
let bind = {
|
||||
let runtime = runtime().lock().unwrap();
|
||||
let socket = runtime.block_on(connect_unix())?;
|
||||
runtime.block_on(Socks5Listener::bind_with_password_and_socket(
|
||||
socket,
|
||||
ECHO_SERVER_ADDR,
|
||||
"mylonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglogin",
|
||||
"longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglongpassword"
|
||||
))
|
||||
}?;
|
||||
test_bind(bind)
|
||||
}
|
||||
4
tests/no_auth.cfg
Normal file
4
tests/no_auth.cfg
Normal file
@@ -0,0 +1,4 @@
|
||||
daemon
|
||||
pidfile /tmp/3proxy-test.pid
|
||||
auth none
|
||||
socks -p41080
|
||||
42
tests/no_auth.rs
Normal file
42
tests/no_auth.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
mod common;
|
||||
|
||||
use crate::common::{runtime, test_bind};
|
||||
use common::{connect_unix, test_connect, ECHO_SERVER_ADDR, PROXY_ADDR};
|
||||
use tokio_socks::{
|
||||
tcp::{Socks5Listener, Socks5Stream},
|
||||
Result,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn connect_no_auth() -> Result<()> {
|
||||
let runtime = runtime().lock().unwrap();
|
||||
let conn = runtime.block_on(Socks5Stream::connect(PROXY_ADDR, ECHO_SERVER_ADDR))?;
|
||||
runtime.block_on(test_connect(conn))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bind_no_auth() -> Result<()> {
|
||||
let bind = {
|
||||
let runtime = runtime().lock().unwrap();
|
||||
runtime.block_on(Socks5Listener::bind(PROXY_ADDR, ECHO_SERVER_ADDR))
|
||||
}?;
|
||||
test_bind(bind)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect_with_socket_no_auth() -> Result<()> {
|
||||
let runtime = runtime().lock().unwrap();
|
||||
let socket = runtime.block_on(connect_unix())?;
|
||||
let conn = runtime.block_on(Socks5Stream::connect_with_socket(socket, ECHO_SERVER_ADDR))?;
|
||||
runtime.block_on(test_connect(conn))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bind_with_socket_no_auth() -> Result<()> {
|
||||
let bind = {
|
||||
let runtime = runtime().lock().unwrap();
|
||||
let socket = runtime.block_on(connect_unix())?;
|
||||
runtime.block_on(Socks5Listener::bind_with_socket(socket, ECHO_SERVER_ADDR))
|
||||
}?;
|
||||
test_bind(bind)
|
||||
}
|
||||
6
tests/username_auth.cfg
Normal file
6
tests/username_auth.cfg
Normal file
@@ -0,0 +1,6 @@
|
||||
daemon
|
||||
users mylogin:CL:mypassword
|
||||
pidfile /tmp/3proxy-test.pid
|
||||
auth strong
|
||||
allow mylogin
|
||||
socks -p41080
|
||||
61
tests/username_auth.rs
Normal file
61
tests/username_auth.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
mod common;
|
||||
|
||||
use common::{connect_unix, runtime, test_bind, test_connect, ECHO_SERVER_ADDR, PROXY_ADDR};
|
||||
use tokio_socks::{
|
||||
tcp::{Socks5Listener, Socks5Stream},
|
||||
Result,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn connect_username_auth() -> Result<()> {
|
||||
let runtime = runtime().lock().unwrap();
|
||||
let conn = runtime.block_on(Socks5Stream::connect_with_password(
|
||||
PROXY_ADDR,
|
||||
ECHO_SERVER_ADDR,
|
||||
"mylogin",
|
||||
"mypassword",
|
||||
))?;
|
||||
runtime.block_on(test_connect(conn))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bind_username_auth() -> Result<()> {
|
||||
let bind = {
|
||||
let runtime = runtime().lock().unwrap();
|
||||
runtime.block_on(Socks5Listener::bind_with_password(
|
||||
PROXY_ADDR,
|
||||
ECHO_SERVER_ADDR,
|
||||
"mylogin",
|
||||
"mypassword",
|
||||
))
|
||||
}?;
|
||||
test_bind(bind)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connect_with_socket_username_auth() -> Result<()> {
|
||||
let runtime = runtime().lock().unwrap();
|
||||
let socket = runtime.block_on(connect_unix())?;
|
||||
let conn = runtime.block_on(Socks5Stream::connect_with_password_and_socket(
|
||||
socket,
|
||||
ECHO_SERVER_ADDR,
|
||||
"mylogin",
|
||||
"mypassword",
|
||||
))?;
|
||||
runtime.block_on(test_connect(conn))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bind_with_socket_username_auth() -> Result<()> {
|
||||
let bind = {
|
||||
let runtime = runtime().lock().unwrap();
|
||||
let socket = runtime.block_on(connect_unix())?;
|
||||
runtime.block_on(Socks5Listener::bind_with_password_and_socket(
|
||||
socket,
|
||||
ECHO_SERVER_ADDR,
|
||||
"mylogin",
|
||||
"mypassword",
|
||||
))
|
||||
}?;
|
||||
test_bind(bind)
|
||||
}
|
||||
Reference in New Issue
Block a user