|
|
|
@ -1,12 +1,11 @@
|
|
|
|
|
use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian};
|
|
|
|
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
|
|
|
|
use std::cmp;
|
|
|
|
|
use std::io::{self, Read, Write};
|
|
|
|
|
use std::net::{SocketAddr, ToSocketAddrs, SocketAddrV4, SocketAddrV6, TcpStream, Ipv4Addr,
|
|
|
|
|
Ipv6Addr, UdpSocket};
|
|
|
|
|
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, TcpStream, ToSocketAddrs, UdpSocket};
|
|
|
|
|
use std::ptr;
|
|
|
|
|
|
|
|
|
|
use {ToTargetAddr, TargetAddr};
|
|
|
|
|
use writev::WritevExt;
|
|
|
|
|
use {TargetAddr, ToTargetAddr};
|
|
|
|
|
|
|
|
|
|
const MAX_ADDR_LEN: usize = 260;
|
|
|
|
|
|
|
|
|
@ -21,8 +20,7 @@ fn read_addr<R: Read>(socket: &mut R) -> io::Result<TargetAddr> {
|
|
|
|
|
let len = socket.read_u8()?;
|
|
|
|
|
let mut domain = vec![0; len as usize];
|
|
|
|
|
socket.read_exact(&mut domain)?;
|
|
|
|
|
let domain = String::from_utf8(domain)
|
|
|
|
|
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
|
|
|
|
let domain = String::from_utf8(domain).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
|
|
|
|
let port = socket.read_u16::<BigEndian>()?;
|
|
|
|
|
Ok(TargetAddr::Domain(domain, port))
|
|
|
|
|
}
|
|
|
|
@ -38,7 +36,6 @@ fn read_addr<R: Read>(socket: &mut R) -> io::Result<TargetAddr> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn read_response(socket: &mut TcpStream) -> io::Result<TargetAddr> {
|
|
|
|
|
xs
|
|
|
|
|
if socket.read_u8()? != 5 {
|
|
|
|
|
return Err(io::Error::new(io::ErrorKind::InvalidData, "invalid response version"));
|
|
|
|
|
}
|
|
|
|
@ -94,14 +91,14 @@ fn write_addr(mut packet: &mut [u8], target: &TargetAddr) -> io::Result<usize> {
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
enum Authentication<'a> {
|
|
|
|
|
Password { username: &'a str, password: &'a str },
|
|
|
|
|
None
|
|
|
|
|
None,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> Authentication<'a> {
|
|
|
|
|
fn id(&self) -> u8 {
|
|
|
|
|
match *self {
|
|
|
|
|
Authentication::Password { .. } => 2,
|
|
|
|
|
Authentication::None => 0
|
|
|
|
|
Authentication::None => 0,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -124,8 +121,9 @@ pub struct Socks5Stream {
|
|
|
|
|
impl Socks5Stream {
|
|
|
|
|
/// Connects to a target server through a SOCKS5 proxy.
|
|
|
|
|
pub fn connect<T, U>(proxy: T, target: U) -> io::Result<Socks5Stream>
|
|
|
|
|
where T: ToSocketAddrs,
|
|
|
|
|
U: ToTargetAddr
|
|
|
|
|
where
|
|
|
|
|
T: ToSocketAddrs,
|
|
|
|
|
U: ToTargetAddr,
|
|
|
|
|
{
|
|
|
|
|
Self::connect_raw(1, proxy, target, &Authentication::None)
|
|
|
|
|
}
|
|
|
|
@ -133,16 +131,18 @@ impl Socks5Stream {
|
|
|
|
|
/// Connects to a target server through a SOCKS5 proxy using given
|
|
|
|
|
/// username and password.
|
|
|
|
|
pub fn connect_with_password<T, U>(proxy: T, target: U, username: &str, password: &str) -> io::Result<Socks5Stream>
|
|
|
|
|
where T: ToSocketAddrs,
|
|
|
|
|
U: ToTargetAddr
|
|
|
|
|
where
|
|
|
|
|
T: ToSocketAddrs,
|
|
|
|
|
U: ToTargetAddr,
|
|
|
|
|
{
|
|
|
|
|
let auth = Authentication::Password { username, password };
|
|
|
|
|
Self::connect_raw(1, proxy, target, &auth)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn connect_raw<T, U>(command: u8, proxy: T, target: U, auth: &Authentication) -> io::Result<Socks5Stream>
|
|
|
|
|
where T: ToSocketAddrs,
|
|
|
|
|
U: ToTargetAddr
|
|
|
|
|
where
|
|
|
|
|
T: ToSocketAddrs,
|
|
|
|
|
U: ToTargetAddr,
|
|
|
|
|
{
|
|
|
|
|
let mut socket = TcpStream::connect(proxy)?;
|
|
|
|
|
|
|
|
|
@ -150,10 +150,10 @@ impl Socks5Stream {
|
|
|
|
|
|
|
|
|
|
let packet_len = if auth.is_no_auth() { 3 } else { 4 };
|
|
|
|
|
let packet = [
|
|
|
|
|
5, // protocol version
|
|
|
|
|
5, // protocol version
|
|
|
|
|
if auth.is_no_auth() { 1 } else { 2 }, // method count
|
|
|
|
|
auth.id(), // method
|
|
|
|
|
0, // no auth (always offered)
|
|
|
|
|
auth.id(), // method
|
|
|
|
|
0, // no auth (always offered)
|
|
|
|
|
];
|
|
|
|
|
socket.write_all(&packet[..packet_len])?;
|
|
|
|
|
|
|
|
|
@ -167,18 +167,16 @@ impl Socks5Stream {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if selected_method == 0xff {
|
|
|
|
|
return Err(io::Error::new(io::ErrorKind::Other, "no acceptable auth methods"))
|
|
|
|
|
return Err(io::Error::new(io::ErrorKind::Other, "no acceptable auth methods"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if selected_method != auth.id() && selected_method != Authentication::None.id() {
|
|
|
|
|
return Err(io::Error::new(io::ErrorKind::Other, "unknown auth method"))
|
|
|
|
|
return Err(io::Error::new(io::ErrorKind::Other, "unknown auth method"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match *auth {
|
|
|
|
|
Authentication::Password { username, password } if selected_method == auth.id() => {
|
|
|
|
|
Self::password_authentication(&mut socket, username, password)?
|
|
|
|
|
},
|
|
|
|
|
_ => ()
|
|
|
|
|
Authentication::Password { username, password } if selected_method == auth.id() => Self::password_authentication(&mut socket, username, password)?,
|
|
|
|
|
_ => (),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut packet = [0; MAX_ADDR_LEN + 3];
|
|
|
|
@ -198,10 +196,10 @@ impl Socks5Stream {
|
|
|
|
|
|
|
|
|
|
fn password_authentication(socket: &mut TcpStream, username: &str, password: &str) -> io::Result<()> {
|
|
|
|
|
if username.len() < 1 || username.len() > 255 {
|
|
|
|
|
return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid username"))
|
|
|
|
|
return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid username"));
|
|
|
|
|
};
|
|
|
|
|
if password.len() < 1 || password.len() > 255 {
|
|
|
|
|
return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid password"))
|
|
|
|
|
return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid password"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut packet = [0; 515];
|
|
|
|
@ -289,8 +287,9 @@ impl Socks5Listener {
|
|
|
|
|
/// The proxy will filter incoming connections based on the value of
|
|
|
|
|
/// `target`.
|
|
|
|
|
pub fn bind<T, U>(proxy: T, target: U) -> io::Result<Socks5Listener>
|
|
|
|
|
where T: ToSocketAddrs,
|
|
|
|
|
U: ToTargetAddr
|
|
|
|
|
where
|
|
|
|
|
T: ToSocketAddrs,
|
|
|
|
|
U: ToTargetAddr,
|
|
|
|
|
{
|
|
|
|
|
Socks5Stream::connect_raw(2, proxy, target, &Authentication::None).map(Socks5Listener)
|
|
|
|
|
}
|
|
|
|
@ -300,8 +299,9 @@ impl Socks5Listener {
|
|
|
|
|
/// The proxy will filter incoming connections based on the value of
|
|
|
|
|
/// `target`.
|
|
|
|
|
pub fn bind_with_password<T, U>(proxy: T, target: U, username: &str, password: &str) -> io::Result<Socks5Listener>
|
|
|
|
|
where T: ToSocketAddrs,
|
|
|
|
|
U: ToTargetAddr
|
|
|
|
|
where
|
|
|
|
|
T: ToSocketAddrs,
|
|
|
|
|
U: ToTargetAddr,
|
|
|
|
|
{
|
|
|
|
|
let auth = Authentication::Password { username, password };
|
|
|
|
|
Socks5Stream::connect_raw(2, proxy, target, &auth).map(Socks5Listener)
|
|
|
|
@ -337,8 +337,9 @@ impl Socks5Datagram {
|
|
|
|
|
/// Creates a UDP socket bound to the specified address which will have its
|
|
|
|
|
/// traffic routed through the specified proxy.
|
|
|
|
|
pub fn bind<T, U>(proxy: T, addr: U) -> io::Result<Socks5Datagram>
|
|
|
|
|
where T: ToSocketAddrs,
|
|
|
|
|
U: ToSocketAddrs
|
|
|
|
|
where
|
|
|
|
|
T: ToSocketAddrs,
|
|
|
|
|
U: ToSocketAddrs,
|
|
|
|
|
{
|
|
|
|
|
Self::bind_internal(proxy, addr, &Authentication::None)
|
|
|
|
|
}
|
|
|
|
@ -346,16 +347,18 @@ impl Socks5Datagram {
|
|
|
|
|
/// traffic routed through the specified proxy. The given username and password
|
|
|
|
|
/// is used to authenticate to the SOCKS proxy.
|
|
|
|
|
pub fn bind_with_password<T, U>(proxy: T, addr: U, username: &str, password: &str) -> io::Result<Socks5Datagram>
|
|
|
|
|
where T: ToSocketAddrs,
|
|
|
|
|
U: ToSocketAddrs
|
|
|
|
|
where
|
|
|
|
|
T: ToSocketAddrs,
|
|
|
|
|
U: ToSocketAddrs,
|
|
|
|
|
{
|
|
|
|
|
let auth = Authentication::Password { username, password };
|
|
|
|
|
Self::bind_internal(proxy, addr, &auth)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn bind_internal<T, U>(proxy: T, addr: U, auth: &Authentication) -> io::Result<Socks5Datagram>
|
|
|
|
|
where T: ToSocketAddrs,
|
|
|
|
|
U: ToSocketAddrs
|
|
|
|
|
where
|
|
|
|
|
T: ToSocketAddrs,
|
|
|
|
|
U: ToSocketAddrs,
|
|
|
|
|
{
|
|
|
|
|
// we don't know what our IP is from the perspective of the proxy, so
|
|
|
|
|
// don't try to pass `addr` in here.
|
|
|
|
@ -365,10 +368,7 @@ impl Socks5Datagram {
|
|
|
|
|
let socket = UdpSocket::bind(addr)?;
|
|
|
|
|
socket.connect(&stream.proxy_addr)?;
|
|
|
|
|
|
|
|
|
|
Ok(Socks5Datagram {
|
|
|
|
|
socket: socket,
|
|
|
|
|
stream: stream,
|
|
|
|
|
})
|
|
|
|
|
Ok(Socks5Datagram { socket: socket, stream: stream })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Like `UdpSocket::send_to`.
|
|
|
|
@ -379,7 +379,8 @@ impl Socks5Datagram {
|
|
|
|
|
/// header will be 10 bytes for an IPv4 address, 22 bytes for an IPv6
|
|
|
|
|
/// address, and 7 bytes plus the length of the domain for a domain address.
|
|
|
|
|
pub fn send_to<A>(&self, buf: &[u8], addr: A) -> io::Result<usize>
|
|
|
|
|
where A: ToTargetAddr
|
|
|
|
|
where
|
|
|
|
|
A: ToTargetAddr,
|
|
|
|
|
{
|
|
|
|
|
let addr = addr.to_target_addr()?;
|
|
|
|
|
|
|
|
|
@ -438,7 +439,7 @@ impl Socks5Datagram {
|
|
|
|
|
mod test {
|
|
|
|
|
use std::error::Error;
|
|
|
|
|
use std::io::{Read, Write};
|
|
|
|
|
use std::net::{ToSocketAddrs, TcpStream, UdpSocket};
|
|
|
|
|
use std::net::{TcpStream, ToSocketAddrs, UdpSocket};
|
|
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
@ -455,12 +456,7 @@ mod test {
|
|
|
|
|
#[test]
|
|
|
|
|
fn google_with_password() {
|
|
|
|
|
let addr = "google.com:80".to_socket_addrs().unwrap().next().unwrap();
|
|
|
|
|
let socket = Socks5Stream::connect_with_password(
|
|
|
|
|
SOCKS_PROXY_PASSWD_ONLY,
|
|
|
|
|
addr,
|
|
|
|
|
"testuser",
|
|
|
|
|
"testpass"
|
|
|
|
|
).unwrap();
|
|
|
|
|
let socket = Socks5Stream::connect_with_password(SOCKS_PROXY_PASSWD_ONLY, addr, "testuser", "testpass").unwrap();
|
|
|
|
|
google(socket);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -497,24 +493,14 @@ mod test {
|
|
|
|
|
#[test]
|
|
|
|
|
fn bind_with_password_supported_but_no_auth_used() {
|
|
|
|
|
let addr = find_address();
|
|
|
|
|
let listener = Socks5Listener::bind_with_password(
|
|
|
|
|
SOCKS_PROXY_NO_AUTH_ONLY,
|
|
|
|
|
addr,
|
|
|
|
|
"unused_and_invalid_username",
|
|
|
|
|
"unused_and_invalid_password"
|
|
|
|
|
).unwrap();
|
|
|
|
|
let listener = Socks5Listener::bind_with_password(SOCKS_PROXY_NO_AUTH_ONLY, addr, "unused_and_invalid_username", "unused_and_invalid_password").unwrap();
|
|
|
|
|
bind(listener);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn bind_with_password() {
|
|
|
|
|
let addr = find_address();
|
|
|
|
|
let listener = Socks5Listener::bind_with_password(
|
|
|
|
|
"127.0.0.1:1081",
|
|
|
|
|
addr,
|
|
|
|
|
"testuser",
|
|
|
|
|
"testpass"
|
|
|
|
|
).unwrap();
|
|
|
|
|
let listener = Socks5Listener::bind_with_password("127.0.0.1:1081", addr, "testuser", "testpass").unwrap();
|
|
|
|
|
bind(listener);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -543,12 +529,7 @@ mod test {
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn associate_with_password() {
|
|
|
|
|
let socks = Socks5Datagram::bind_with_password(
|
|
|
|
|
SOCKS_PROXY_PASSWD_ONLY,
|
|
|
|
|
"127.0.0.1:15414",
|
|
|
|
|
"testuser",
|
|
|
|
|
"testpass"
|
|
|
|
|
).unwrap();
|
|
|
|
|
let socks = Socks5Datagram::bind_with_password(SOCKS_PROXY_PASSWD_ONLY, "127.0.0.1:15414", "testuser", "testpass").unwrap();
|
|
|
|
|
associate(socks, "127.0.0.1:15415");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -596,12 +577,7 @@ mod test {
|
|
|
|
|
#[test]
|
|
|
|
|
fn incorrect_password() {
|
|
|
|
|
let addr = "google.com:80".to_socket_addrs().unwrap().next().unwrap();
|
|
|
|
|
let err = Socks5Stream::connect_with_password(
|
|
|
|
|
SOCKS_PROXY_PASSWD_ONLY,
|
|
|
|
|
addr,
|
|
|
|
|
"testuser",
|
|
|
|
|
"invalid"
|
|
|
|
|
).unwrap_err();
|
|
|
|
|
let err = Socks5Stream::connect_with_password(SOCKS_PROXY_PASSWD_ONLY, addr, "testuser", "invalid").unwrap_err();
|
|
|
|
|
|
|
|
|
|
assert_eq!(err.kind(), io::ErrorKind::PermissionDenied);
|
|
|
|
|
assert_eq!(err.description(), "password authentication failed");
|
|
|
|
@ -620,57 +596,27 @@ mod test {
|
|
|
|
|
fn username_and_password_length() {
|
|
|
|
|
let addr = "google.com:80".to_socket_addrs().unwrap().next().unwrap();
|
|
|
|
|
|
|
|
|
|
let err = Socks5Stream::connect_with_password(
|
|
|
|
|
SOCKS_PROXY_PASSWD_ONLY,
|
|
|
|
|
addr,
|
|
|
|
|
&string_of_size(1),
|
|
|
|
|
&string_of_size(1)
|
|
|
|
|
).unwrap_err();
|
|
|
|
|
let err = Socks5Stream::connect_with_password(SOCKS_PROXY_PASSWD_ONLY, addr, &string_of_size(1), &string_of_size(1)).unwrap_err();
|
|
|
|
|
assert_eq!(err.kind(), io::ErrorKind::PermissionDenied);
|
|
|
|
|
assert_eq!(err.description(), "password authentication failed");
|
|
|
|
|
|
|
|
|
|
let err = Socks5Stream::connect_with_password(
|
|
|
|
|
SOCKS_PROXY_PASSWD_ONLY,
|
|
|
|
|
addr,
|
|
|
|
|
&string_of_size(255),
|
|
|
|
|
&string_of_size(255)
|
|
|
|
|
).unwrap_err();
|
|
|
|
|
let err = Socks5Stream::connect_with_password(SOCKS_PROXY_PASSWD_ONLY, addr, &string_of_size(255), &string_of_size(255)).unwrap_err();
|
|
|
|
|
assert_eq!(err.kind(), io::ErrorKind::PermissionDenied);
|
|
|
|
|
assert_eq!(err.description(), "password authentication failed");
|
|
|
|
|
|
|
|
|
|
let err = Socks5Stream::connect_with_password(
|
|
|
|
|
SOCKS_PROXY_PASSWD_ONLY,
|
|
|
|
|
addr,
|
|
|
|
|
&string_of_size(0),
|
|
|
|
|
&string_of_size(255)
|
|
|
|
|
).unwrap_err();
|
|
|
|
|
let err = Socks5Stream::connect_with_password(SOCKS_PROXY_PASSWD_ONLY, addr, &string_of_size(0), &string_of_size(255)).unwrap_err();
|
|
|
|
|
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
|
|
|
|
|
assert_eq!(err.description(), "invalid username");
|
|
|
|
|
|
|
|
|
|
let err = Socks5Stream::connect_with_password(
|
|
|
|
|
SOCKS_PROXY_PASSWD_ONLY,
|
|
|
|
|
addr,
|
|
|
|
|
&string_of_size(256),
|
|
|
|
|
&string_of_size(255)
|
|
|
|
|
).unwrap_err();
|
|
|
|
|
let err = Socks5Stream::connect_with_password(SOCKS_PROXY_PASSWD_ONLY, addr, &string_of_size(256), &string_of_size(255)).unwrap_err();
|
|
|
|
|
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
|
|
|
|
|
assert_eq!(err.description(), "invalid username");
|
|
|
|
|
|
|
|
|
|
let err = Socks5Stream::connect_with_password(
|
|
|
|
|
SOCKS_PROXY_PASSWD_ONLY,
|
|
|
|
|
addr,
|
|
|
|
|
&string_of_size(255),
|
|
|
|
|
&string_of_size(0)
|
|
|
|
|
).unwrap_err();
|
|
|
|
|
let err = Socks5Stream::connect_with_password(SOCKS_PROXY_PASSWD_ONLY, addr, &string_of_size(255), &string_of_size(0)).unwrap_err();
|
|
|
|
|
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
|
|
|
|
|
assert_eq!(err.description(), "invalid password");
|
|
|
|
|
|
|
|
|
|
let err = Socks5Stream::connect_with_password(
|
|
|
|
|
SOCKS_PROXY_PASSWD_ONLY,
|
|
|
|
|
addr,
|
|
|
|
|
&string_of_size(255),
|
|
|
|
|
&string_of_size(256)
|
|
|
|
|
).unwrap_err();
|
|
|
|
|
let err = Socks5Stream::connect_with_password(SOCKS_PROXY_PASSWD_ONLY, addr, &string_of_size(255), &string_of_size(256)).unwrap_err();
|
|
|
|
|
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
|
|
|
|
|
assert_eq!(err.description(), "invalid password");
|
|
|
|
|
}
|
|
|
|
|