updated code structure, added dns cache

This commit is contained in:
Y. T. Chung
2014-10-26 08:40:01 +08:00
parent 20fa86ebc0
commit d61791548f
7 changed files with 230 additions and 151 deletions

View File

@@ -105,6 +105,7 @@ It supports the following features:
* `UDP_ASSOCIATION` and `BIND` command
* Socks5 authentication
* <del>Extend configuration format</del>
* <del>DNS cache</del>
* Fully testing on servers
* Performance testing and improvement
* Multiple workers

View File

@@ -24,123 +24,13 @@
pub use self::local::RelayLocal;
pub use self::server::RelayServer;
use std::fmt::{Show, Formatter, FormatError};
use std::io::net::ip::{SocketAddr, Port};
use std::io::net::ip::{Ipv4Addr, Ipv6Addr};
mod tcprelay;
// pub mod udprelay;
pub mod local;
pub mod server;
mod loadbalancing;
mod socks5;
pub trait Relay {
fn run(&self);
}
pub const SOCKS5_VERSION : u8 = 0x05;
pub const SOCKS5_AUTH_METHOD_NONE : u8 = 0x00;
pub const SOCKS5_AUTH_METHOD_GSSAPI : u8 = 0x01;
pub const SOCKS5_AUTH_METHOD_PASSWORD : u8 = 0x02;
pub const SOCKS5_AUTH_METHOD_NOT_ACCEPTABLE : u8 = 0xff;
pub const SOCKS5_CMD_TCP_CONNECT : u8 = 0x01;
pub const SOCKS5_CMD_TCP_BIND : u8 = 0x02;
pub const SOCKS5_CMD_UDP_ASSOCIATE : u8 = 0x03;
pub const SOCKS5_ADDR_TYPE_IPV4 : u8 = 0x01;
pub const SOCKS5_ADDR_TYPE_DOMAIN_NAME : u8 = 0x03;
pub const SOCKS5_ADDR_TYPE_IPV6 : u8 = 0x04;
pub const SOCKS5_REPLY_SUCCEEDED : u8 = 0x00;
pub const SOCKS5_REPLY_GENERAL_FAILURE : u8 = 0x01;
pub const SOCKS5_REPLY_CONNECTION_NOT_ALLOWED : u8 = 0x02;
pub const SOCKS5_REPLY_NETWORK_UNREACHABLE : u8 = 0x03;
pub const SOCKS5_REPLY_HOST_UNREACHABLE : u8 = 0x04;
pub const SOCKS5_REPLY_CONNECTION_REFUSED : u8 = 0x05;
pub const SOCKS5_REPLY_TTL_EXPIRED : u8 = 0x06;
pub const SOCKS5_REPLY_COMMAND_NOT_SUPPORTED : u8 = 0x07;
pub const SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED : u8 = 0x08;
#[deriving(Show)]
pub enum Sock5CmdType {
Sock5CmdTcpConnect,
Sock5CmdTcpBind,
Sock5CmdUdpAssociate,
}
pub struct DomainNameAddr {
pub domain_name: String,
pub port: Port,
}
impl Show for DomainNameAddr {
fn fmt(&self, f: &mut Formatter) -> Result<(), FormatError> {
f.write(format!("{}:{}", self.domain_name, self.port).as_slice().as_bytes())
}
}
#[deriving(Show)]
pub enum Sock5AddrType {
Sock5SocketAddr(SocketAddr),
Sock5DomainNameAddr(DomainNameAddr),
}
pub fn parse_request_header(buf: &[u8]) -> Result<(uint, Sock5AddrType), u8> {
let atyp = buf[0];
match atyp {
SOCKS5_ADDR_TYPE_IPV4 => {
if buf.len() < 7 {
fail!("Invalid header");
}
let raw_addr = buf.slice(1, 5);
let v4addr = Ipv4Addr(raw_addr[0], raw_addr[1], raw_addr[2], raw_addr[3]);
let raw_port = buf.slice(5, 7);
let port = (raw_port[0] as u16 << 8) | raw_port[1] as u16;
Ok((7u, Sock5SocketAddr(SocketAddr{ip: v4addr, port: port})))
},
SOCKS5_ADDR_TYPE_IPV6 => {
if buf.len() < 19 {
fail!("Invalid header");
}
let raw_addr = buf.slice(1, 17);
let v6addr = Ipv6Addr((raw_addr[0] as u16 << 8) | raw_addr[1] as u16,
(raw_addr[2] as u16 << 8) | raw_addr[3] as u16,
(raw_addr[4] as u16 << 8) | raw_addr[5] as u16,
(raw_addr[6] as u16 << 8) | raw_addr[7] as u16,
(raw_addr[8] as u16 << 8) | raw_addr[9] as u16,
(raw_addr[10] as u16 << 8) | raw_addr[11] as u16,
(raw_addr[12] as u16 << 8) | raw_addr[13] as u16,
(raw_addr[14] as u16 << 8) | raw_addr[15] as u16);
let raw_port = buf.slice(17, 19);
// Big Endian
let port = (raw_port[0] as u16 << 8) | raw_port[1] as u16;
Ok((19u, Sock5SocketAddr(SocketAddr{ip: v6addr, port: port})))
},
SOCKS5_ADDR_TYPE_DOMAIN_NAME => {
let addr_len = buf[1] as uint;
if buf.len() < 4 + addr_len {
fail!("Invalid header");
}
let raw_addr = buf.slice(2, 2 + addr_len);
let raw_port = buf.slice(2 + addr_len, 4 + addr_len);
let port = (raw_port[0] as u16 << 8) | raw_port[1] as u16;
Ok((4 + addr_len, Sock5DomainNameAddr(DomainNameAddr{
domain_name: String::from_utf8(raw_addr.to_vec()).unwrap(),
port: port,
})))
},
_ => {
// Address type not supported
Err(SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED)
}
}
}

140
src/relay/socks5.rs Normal file
View File

@@ -0,0 +1,140 @@
// The MIT License (MIT)
// Copyright (c) 2014 Y. T. CHUNG
// 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.
use std::fmt::{Show, Formatter, FormatError};
use std::io::net::ip::{SocketAddr, Port};
use std::io::net::ip::{Ipv4Addr, Ipv6Addr};
pub const SOCKS5_VERSION : u8 = 0x05;
pub const SOCKS5_AUTH_METHOD_NONE : u8 = 0x00;
pub const SOCKS5_AUTH_METHOD_GSSAPI : u8 = 0x01;
pub const SOCKS5_AUTH_METHOD_PASSWORD : u8 = 0x02;
pub const SOCKS5_AUTH_METHOD_NOT_ACCEPTABLE : u8 = 0xff;
pub const SOCKS5_CMD_TCP_CONNECT : u8 = 0x01;
pub const SOCKS5_CMD_TCP_BIND : u8 = 0x02;
pub const SOCKS5_CMD_UDP_ASSOCIATE : u8 = 0x03;
pub const SOCKS5_ADDR_TYPE_IPV4 : u8 = 0x01;
pub const SOCKS5_ADDR_TYPE_DOMAIN_NAME : u8 = 0x03;
pub const SOCKS5_ADDR_TYPE_IPV6 : u8 = 0x04;
pub const SOCKS5_REPLY_SUCCEEDED : u8 = 0x00;
pub const SOCKS5_REPLY_GENERAL_FAILURE : u8 = 0x01;
pub const SOCKS5_REPLY_CONNECTION_NOT_ALLOWED : u8 = 0x02;
pub const SOCKS5_REPLY_NETWORK_UNREACHABLE : u8 = 0x03;
pub const SOCKS5_REPLY_HOST_UNREACHABLE : u8 = 0x04;
pub const SOCKS5_REPLY_CONNECTION_REFUSED : u8 = 0x05;
pub const SOCKS5_REPLY_TTL_EXPIRED : u8 = 0x06;
pub const SOCKS5_REPLY_COMMAND_NOT_SUPPORTED : u8 = 0x07;
pub const SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED : u8 = 0x08;
#[allow(dead_code)]
#[deriving(Show)]
pub enum CommandType {
TcpConnect,
TcpBind,
UdpAssociate,
}
pub struct DomainNameAddr {
pub domain_name: String,
pub port: Port,
}
impl Show for DomainNameAddr {
fn fmt(&self, f: &mut Formatter) -> Result<(), FormatError> {
f.write(format!("{}:{}", self.domain_name, self.port).as_slice().as_bytes())
}
}
pub enum AddressType {
SocketAddress(SocketAddr),
DomainNameAddress(DomainNameAddr),
}
impl Show for AddressType {
fn fmt(&self, f: &mut Formatter) -> Result<(), FormatError> {
match *self {
SocketAddress(ref addr) => addr.fmt(f),
DomainNameAddress(ref addr) => addr.fmt(f),
}
}
}
pub fn parse_request_header(buf: &[u8]) -> Result<(uint, AddressType), u8> {
let atyp = buf[0];
match atyp {
SOCKS5_ADDR_TYPE_IPV4 => {
if buf.len() < 7 {
fail!("Invalid header");
}
let raw_addr = buf.slice(1, 5);
let v4addr = Ipv4Addr(raw_addr[0], raw_addr[1], raw_addr[2], raw_addr[3]);
let raw_port = buf.slice(5, 7);
let port = (raw_port[0] as u16 << 8) | raw_port[1] as u16;
Ok((7u, SocketAddress(SocketAddr{ip: v4addr, port: port})))
},
SOCKS5_ADDR_TYPE_IPV6 => {
if buf.len() < 19 {
fail!("Invalid header");
}
let raw_addr = buf.slice(1, 17);
let v6addr = Ipv6Addr((raw_addr[0] as u16 << 8) | raw_addr[1] as u16,
(raw_addr[2] as u16 << 8) | raw_addr[3] as u16,
(raw_addr[4] as u16 << 8) | raw_addr[5] as u16,
(raw_addr[6] as u16 << 8) | raw_addr[7] as u16,
(raw_addr[8] as u16 << 8) | raw_addr[9] as u16,
(raw_addr[10] as u16 << 8) | raw_addr[11] as u16,
(raw_addr[12] as u16 << 8) | raw_addr[13] as u16,
(raw_addr[14] as u16 << 8) | raw_addr[15] as u16);
let raw_port = buf.slice(17, 19);
// Big Endian
let port = (raw_port[0] as u16 << 8) | raw_port[1] as u16;
Ok((19u, SocketAddress(SocketAddr{ip: v6addr, port: port})))
},
SOCKS5_ADDR_TYPE_DOMAIN_NAME => {
let addr_len = buf[1] as uint;
if buf.len() < 4 + addr_len {
fail!("Invalid header");
}
let raw_addr = buf.slice(2, 2 + addr_len);
let raw_port = buf.slice(2 + addr_len, 4 + addr_len);
let port = (raw_port[0] as u16 << 8) | raw_port[1] as u16;
Ok((4 + addr_len, DomainNameAddress(DomainNameAddr{
domain_name: String::from_utf8(raw_addr.to_vec()).unwrap(),
port: port,
})))
},
_ => {
// Address type not supported
Err(SOCKS5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED)
}
}
}

View File

@@ -0,0 +1,62 @@
// The MIT License (MIT)
// Copyright (c) 2014 Y. T. CHUNG
// 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.
#[phase(plugin, link)]
extern crate log;
use std::io::net::addrinfo::get_host_addresses;
use std::collections::lru_cache::LruCache;
use std::io::net::ip::IpAddr;
const DNS_CACHE_CAPACITY: uint = 200;
pub struct DnsCache {
lru_cache: LruCache<String, IpAddr>,
}
impl DnsCache {
pub fn new() -> DnsCache {
DnsCache {
lru_cache: LruCache::new(DNS_CACHE_CAPACITY),
}
}
pub fn get<'a>(&mut self, addr: &str) -> Option<IpAddr> {
match self.lru_cache.get(&addr.to_string()).map(|x| *x) {
Some(a) => {
Some(a.clone())
},
None => {
let ipaddr = match get_host_addresses(addr) {
Ok(addr_list) => {
addr_list[0]
},
Err(err) => {
error!("Error while resolving {}: {}", addr, err);
return None
}
};
self.lru_cache.put(addr.to_string(), ipaddr);
Some(ipaddr)
}
}
}
}

View File

@@ -35,13 +35,17 @@ use std::io::net::ip::{Ipv4Addr, Ipv6Addr};
use config::Config;
use relay::Relay;
use relay::parse_request_header;
use relay::socks5::parse_request_header;
use relay::tcprelay::send_error_reply;
use relay::{SOCKS5_VERSION, SOCKS5_AUTH_METHOD_NONE};
use relay::{SOCKS5_CMD_TCP_CONNECT, SOCKS5_CMD_TCP_BIND, SOCKS5_CMD_UDP_ASSOCIATE};
use relay::{SOCKS5_ADDR_TYPE_IPV6, SOCKS5_ADDR_TYPE_IPV4};
use relay::{SOCKS5_REPLY_COMMAND_NOT_SUPPORTED, SOCKS5_REPLY_HOST_UNREACHABLE, SOCKS5_REPLY_NETWORK_UNREACHABLE};
use relay::SOCKS5_REPLY_SUCCEEDED;
use relay::socks5::{SOCKS5_VERSION, SOCKS5_AUTH_METHOD_NONE};
use relay::socks5::{SOCKS5_CMD_TCP_CONNECT, SOCKS5_CMD_TCP_BIND, SOCKS5_CMD_UDP_ASSOCIATE};
use relay::socks5::{SOCKS5_ADDR_TYPE_IPV6, SOCKS5_ADDR_TYPE_IPV4};
use relay::socks5::{
SOCKS5_REPLY_COMMAND_NOT_SUPPORTED,
SOCKS5_REPLY_HOST_UNREACHABLE,
SOCKS5_REPLY_NETWORK_UNREACHABLE
};
use relay::socks5::SOCKS5_REPLY_SUCCEEDED;
use relay::loadbalancing::server::{LoadBalancer, RoundRobin};
use crypto::cipher;

View File

@@ -27,8 +27,9 @@ extern crate log;
extern crate native;
use std::io::TcpStream;
use relay::SOCKS5_VERSION;
use relay::socks5::SOCKS5_VERSION;
mod dnscache;
pub mod local;
pub mod server;

View File

@@ -24,21 +24,19 @@
#[phase(plugin, link)]
extern crate log;
use std::sync::Arc;
use std::sync::{Arc, Mutex};
use std::io::{Listener, TcpListener, Acceptor, TcpStream};
use std::io::net::ip::{Port, IpAddr};
use std::io::{EndOfFile, TimedOut, BrokenPipe};
use config::{Config, SingleServer, MultipleServer};
use relay::Relay;
use relay::{parse_request_header, Sock5SocketAddr, Sock5DomainNameAddr};
use relay::socks5::{parse_request_header, SocketAddress, DomainNameAddress};
use relay::tcprelay::dnscache::DnsCache;
use crypto::cipher;
use crypto::cipher::Cipher;
use crypto::cipher::CipherVariant;
use std::io::net::addrinfo::get_host_addresses;
#[deriving(Clone)]
pub struct TcpRelayServer {
config: Config,
@@ -63,18 +61,6 @@ impl TcpRelayServer {
}
}
fn connect_remote(addrs: Vec<IpAddr>, port: Port) -> Option<TcpStream> {
for addr in addrs.iter() {
match TcpStream::connect(addr.to_string().as_slice(), port) {
Ok(s) => {
return Some(s)
},
Err(..) => {}
}
}
None
}
fn handle_connect_remote(local_stream: &mut TcpStream, remote_stream: &mut TcpStream,
cipher: &mut CipherVariant) {
let mut buf = [0u8, .. 0xffff];
@@ -162,6 +148,8 @@ impl Relay for TcpRelayServer {
info!("Shadowsocks listening on {}:{}", server_addr, server_port);
let dnscache_arc = Arc::new(Mutex::new(DnsCache::new()));
loop {
match acceptor.accept() {
Ok(mut stream) => {
@@ -169,6 +157,7 @@ impl Relay for TcpRelayServer {
let password = password.clone();
let encrypt_method = encrypt_method.clone();
let dnscache = dnscache_arc.clone();
spawn(proc() {
let mut cipher = cipher::with_name(encrypt_method.as_slice(),
@@ -192,7 +181,7 @@ impl Relay for TcpRelayServer {
};
info!("Connecting to {}", addr);
let mut remote_stream = match addr {
Sock5SocketAddr(sockaddr) => {
SocketAddress(sockaddr) => {
match TcpStream::connect(sockaddr.ip.to_string().as_slice(), sockaddr.port) {
Ok(s) => s,
Err(err) => {
@@ -200,24 +189,16 @@ impl Relay for TcpRelayServer {
}
}
},
Sock5DomainNameAddr(domainaddr) => {
let addrs = match get_host_addresses(domainaddr.domain_name.as_slice()) {
Ok(ipaddrs) => ipaddrs,
Err(e) => {
fail!("Error occurs while get_host_addresses: {}", e);
DomainNameAddress(ref domainaddr) => {
let ipaddr = match dnscache.lock().get(domainaddr.domain_name.as_slice()) {
Some(addr) => addr,
None => {
fail!("Failed to resolve host {}", domainaddr)
}
};
if addrs.len() == 0 {
fail!("Cannot resolve host {}, empty host list", domainaddr.domain_name);
}
match TcpRelayServer::connect_remote(addrs, domainaddr.port) {
Some(s) => s,
None => {
fail!("Unable to resolve host {}", domainaddr)
}
}
TcpStream::connect(ipaddr.to_string().as_slice(), domainaddr.port)
.ok().expect(format!("Unable to connect host {}", domainaddr).as_slice())
}
};