From 0cb7bd428ba2cd85269e60f2897684fc10a7a26e Mon Sep 17 00:00:00 2001 From: "Y. T. Chung" Date: Thu, 16 Feb 2017 21:38:35 +0800 Subject: [PATCH] udp relay supports AEAD ciphers --- src/relay/udprelay/crypto_io.rs | 113 ++++++++++++++++++++++++++++++++ src/relay/udprelay/local.rs | 46 +++---------- src/relay/udprelay/mod.rs | 87 +----------------------- src/relay/udprelay/server.rs | 50 ++++---------- 4 files changed, 135 insertions(+), 161 deletions(-) create mode 100644 src/relay/udprelay/crypto_io.rs diff --git a/src/relay/udprelay/crypto_io.rs b/src/relay/udprelay/crypto_io.rs new file mode 100644 index 00000000..0dca2864 --- /dev/null +++ b/src/relay/udprelay/crypto_io.rs @@ -0,0 +1,113 @@ +//! Crypto protocol for ShadowSocks UDP +//! +//! Payload with stream cipher +//! ```plain +//! +-------+----------+ +//! | IV | Payload | +//! +-------+----------+ +//! | Fixed | Variable | +//! +-------+----------+ +//! ``` +//! +//! Payload with AEAD cipher +//! +//! ```plain +//! UDP (after encryption, *ciphertext*) +//! +--------+-----------+-----------+ +//! | NONCE | *Data* | Data_TAG | +//! +--------+-----------+-----------+ +//! | Fixed | Variable | Fixed | +//! +--------+-----------+-----------+ +//! ``` + +use std::io; + +use crypto::{self, CipherType, CipherCategory, CryptoMode}; +use crypto::StreamCipher; + +/// Encrypt payload into ShadowSocks UDP encrypted packet +pub fn encrypt_payload(t: CipherType, key: &[u8], payload: &[u8]) -> io::Result> { + match t.category() { + CipherCategory::Stream => encrypt_payload_stream(t, key, payload), + CipherCategory::Aead => encrypt_payload_aead(t, key, payload), + } +} + +fn encrypt_payload_stream(t: CipherType, key: &[u8], payload: &[u8]) -> io::Result> { + let mut iv = t.gen_init_vec(); + let mut cipher = crypto::new_stream(t, key, &iv, CryptoMode::Encrypt); + + let mut send_payload = Vec::with_capacity(iv.len() + payload.len()); + send_payload.append(&mut iv); + try!(cipher.update(&payload[..], &mut send_payload)); + try!(cipher.finalize(&mut send_payload)); + Ok(send_payload) +} + +fn encrypt_payload_aead(t: CipherType, key: &[u8], payload: &[u8]) -> io::Result> { + let mut iv = t.gen_init_vec(); + let tag_size = t.tag_size(); + let mut cipher = crypto::new_aead_encryptor(t, key, &iv); + + let mut send_payload = Vec::with_capacity(iv.len() + payload.len() + tag_size); + send_payload.append(&mut iv); + let start_pos = send_payload.len(); + send_payload.resize(start_pos + payload.len(), 0); + + let mut tag_buf = vec![0u8; tag_size]; + + cipher.encrypt(payload, &mut send_payload[start_pos..], &mut tag_buf); + + send_payload.append(&mut tag_buf); + + Ok(send_payload) +} + +/// Decrypt payload from ShadowSocks UDP encrypted packet +pub fn decrypt_payload(t: CipherType, key: &[u8], payload: &[u8]) -> io::Result> { + match t.category() { + CipherCategory::Stream => decrypt_payload_stream(t, key, payload), + CipherCategory::Aead => decrypt_payload_aead(t, key, payload), + } +} + +fn decrypt_payload_stream(t: CipherType, key: &[u8], payload: &[u8]) -> io::Result> { + let iv_size = t.iv_size(); + if payload.len() < iv_size { + let err = io::Error::new(io::ErrorKind::Other, "udp packet too short"); + return Err(err); + } + + let iv = &payload[..iv_size]; + let data = &payload[iv_size..]; + + let mut cipher = crypto::new_stream(t, key, iv, CryptoMode::Decrypt); + + let mut recv_payload = Vec::with_capacity(data.len()); + try!(cipher.update(data, &mut recv_payload)); + try!(cipher.finalize(&mut recv_payload)); + + Ok(recv_payload) +} + +fn decrypt_payload_aead(t: CipherType, key: &[u8], payload: &[u8]) -> io::Result> { + let tag_size = t.tag_size(); + let iv_size = t.iv_size(); + + if payload.len() < tag_size + iv_size { + let err = io::Error::new(io::ErrorKind::Other, "udp packet too short"); + return Err(err); + } + + let nounce = &payload[..iv_size]; + let data = &payload[iv_size..payload.len() - tag_size]; + let tag = &payload[payload.len() - tag_size..]; + let data_length = payload.len() - tag_size - iv_size; + + let mut cipher = crypto::new_aead_decryptor(t, key, nounce); + + let mut recv_payload = vec![0u8; data_length]; + try!(cipher.decrypt(data, &mut recv_payload, tag)); + + Ok(recv_payload) +} \ No newline at end of file diff --git a/src/relay/udprelay/local.rs b/src/relay/udprelay/local.rs index 6d048dfd..8d6b4a8a 100644 --- a/src/relay/udprelay/local.rs +++ b/src/relay/udprelay/local.rs @@ -77,11 +77,9 @@ use relay::{BoxIoFuture, boxed_future}; use relay::loadbalancing::server::{LoadBalancer, RoundRobin}; use relay::dns_resolver::DnsResolver; use relay::socks5::{Address, UdpAssociateHeader}; -use crypto::{self, StreamCipher}; -use crypto::CryptoMode; use super::{MAXIMUM_ASSOCIATE_MAP_SIZE, MAXIMUM_UDP_PAYLOAD_SIZE}; -use super::{send_to, recv_from}; +use super::crypto_io::{encrypt_payload, decrypt_payload}; type AssociateMap = LruCache; type ServerCache = LruCache>; @@ -126,24 +124,7 @@ impl Client { svr_cfg.addr(), buf.len()); - let iv_len = svr_cfg.method().iv_size(); - if buf.len() < iv_len { - error!("Invalid ShadowSocks UDP packet, expected IV length {}, packet length {}", - iv_len, - buf.len()); - let err = io::Error::new(io::ErrorKind::Other, "early eof"); - return Err(err); - } - - let iv = &buf[..iv_len]; - let mut cipher = crypto::new_stream(svr_cfg.method(), svr_cfg.key(), iv, CryptoMode::Decrypt); - - // Decrypt payload with cipher - let mut payload = Vec::with_capacity(buf.len()); - try!(cipher.update(&buf[iv_len..], &mut payload)); - try!(cipher.finalize(&mut payload)); - - Ok(payload) + decrypt_payload(svr_cfg.method(), svr_cfg.key(), buf) }) .and_then(move |payload| { // Get Address from the front of payload (ShadowSocks protocol) @@ -180,7 +161,7 @@ impl Client { }) }) .and_then(|(client_addr, assoc, reply_body)| { - send_to(socket, reply_body, client_addr).map(move |(socket, _, _)| { + socket.send_dgram(reply_body, client_addr).map(move |(socket, _)| { Client { assoc: assoc, servers: servers, @@ -235,26 +216,15 @@ impl Client { let svr_cfg = server_picker.borrow_mut().pick_server(); // Client -> Proxy - let iv = svr_cfg.method().gen_init_vec(); - let mut cipher = crypto::new_stream(svr_cfg.method(), - svr_cfg.key(), - &iv[..], - CryptoMode::Encrypt); - - let payload_buf = Vec::with_capacity(payload.len()); // Append Address to the front (ShadowSocks protocol) - assoc_addr.write_to(payload_buf) + assoc_addr.write_to(Vec::with_capacity(payload.len())) .and_then(move |mut payload_buf| { payload_buf.extend_from_slice(&payload[header_len..]); Ok(payload_buf) }) .and_then(move |payload| -> io::Result<_> { // Encrypt the whole body as payload - let mut send_payload = Vec::with_capacity(iv.len() + payload.len()); - send_payload.extend_from_slice(&iv[..]); - try!(cipher.update(&payload[..], &mut send_payload)); - try!(cipher.finalize(&mut send_payload)); - Ok((svr_cfg, send_payload)) + encrypt_payload(svr_cfg.method(), svr_cfg.key(), &payload).map(move |b| (svr_cfg, b)) }) .map_err(From::from) .and_then(move |(svr_cfg, payload)| { @@ -267,8 +237,8 @@ impl Client { svrs_ref.insert(addr, svr_cfg.clone()); } - send_to(socket, payload, addr).map(|(socket, body, len)| { - trace!("Body size: {}, sent packet size: {}", body.len(), len); + socket.send_dgram(payload, addr).map(|(socket, body)| { + trace!("Sent body, size: {}", body.len()); Client { assoc: assoc, server_picker: server_picker, @@ -287,7 +257,7 @@ impl Client { fn handle_once(self) -> BoxIoFuture { let Client { assoc, server_picker, servers, socket } = self; - let fut = recv_from(socket, vec![0u8; MAXIMUM_UDP_PAYLOAD_SIZE]).and_then(move |(socket, buf, n, src)| { + let fut = socket.recv_dgram(vec![0u8; MAXIMUM_UDP_PAYLOAD_SIZE]).and_then(move |(socket, buf, n, src)| { // Reassemble Client let c = Client { assoc: assoc, diff --git a/src/relay/udprelay/mod.rs b/src/relay/udprelay/mod.rs index 4c57a95a..17d94e20 100644 --- a/src/relay/udprelay/mod.rs +++ b/src/relay/udprelay/mod.rs @@ -21,19 +21,15 @@ //! Relay for UDP implementation -use std::net::SocketAddr; -use std::mem; use std::io; -use tokio_core::net::UdpSocket; - use net2::UdpBuilder; -use futures::{Future, Poll, Async}; - pub mod local; pub mod server; +mod crypto_io; + /// The maximum UDP payload size (defined in the original shadowsocks Python) /// /// *I cannot find any references about why clowwindy used this value as the maximum @@ -44,85 +40,6 @@ pub const MAXIMUM_UDP_PAYLOAD_SIZE: usize = 65536; /// Maximum associations to maintain pub const MAXIMUM_ASSOCIATE_MAP_SIZE: usize = 65536; -/// Future for `recv_from` -pub enum RecvFromUdpSocket> { - Pending { socket: UdpSocket, buf: B }, - Empty, -} - -impl> Future for RecvFromUdpSocket { - type Item = (UdpSocket, B, usize, SocketAddr); - type Error = io::Error; - - fn poll(&mut self) -> Poll { - let (length, src) = match *self { - RecvFromUdpSocket::Empty => panic!("poll after RecvFromUdpSocket is finished"), - RecvFromUdpSocket::Pending { ref socket, ref mut buf } => { - if socket.poll_read().is_not_ready() { - return Ok(Async::NotReady); - } - - try_nb!(socket.recv_from(buf.as_mut())) - } - }; - - match mem::replace(self, RecvFromUdpSocket::Empty) { - RecvFromUdpSocket::Pending { socket, buf } => Ok((socket, buf, length, src).into()), - RecvFromUdpSocket::Empty => unreachable!(), - } - } -} - -/// Receive from UdpSocket -pub fn recv_from>(socket: UdpSocket, buf: B) -> RecvFromUdpSocket { - RecvFromUdpSocket::Pending { - socket: socket, - buf: buf, - } -} - -/// Future for `send_to` -pub enum SendToUdpSocket> { - Pending { - socket: UdpSocket, - buf: B, - target_addr: SocketAddr, - }, - Empty, -} - -impl> Future for SendToUdpSocket { - type Item = (UdpSocket, B, usize); - type Error = io::Error; - - fn poll(&mut self) -> Poll { - let length = match self { - &mut SendToUdpSocket::Empty => panic!("poll after SendToUdpSocket is finished"), - &mut SendToUdpSocket::Pending { ref socket, ref buf, ref target_addr } => { - if socket.poll_write().is_not_ready() { - return Ok(Async::NotReady); - } - - try_nb!(socket.send_to(buf.as_ref(), target_addr)) - } - }; - - match mem::replace(self, SendToUdpSocket::Empty) { - SendToUdpSocket::Pending { socket, buf, .. } => Ok((socket, buf, length).into()), - SendToUdpSocket::Empty => unreachable!(), - } - } -} - -/// Send data to `UdpSocket` -pub fn send_to>(socket: UdpSocket, buf: B, target: SocketAddr) -> SendToUdpSocket { - SendToUdpSocket::Pending { - socket: socket, - buf: buf, - target_addr: target, - } -} - #[cfg(unix)] fn reuse_port(builder: &UdpBuilder) -> io::Result<&UdpBuilder> { use net2::unix::UnixUdpBuilderExt; diff --git a/src/relay/udprelay/server.rs b/src/relay/udprelay/server.rs index 33856d39..4e32ab59 100644 --- a/src/relay/udprelay/server.rs +++ b/src/relay/udprelay/server.rs @@ -41,11 +41,9 @@ use config::{Config, ServerConfig}; use relay::{BoxIoFuture, boxed_future}; use relay::dns_resolver::DnsResolver; use relay::socks5::Address; -use crypto::{self, StreamCipher}; -use crypto::CryptoMode; use super::{MAXIMUM_ASSOCIATE_MAP_SIZE, MAXIMUM_UDP_PAYLOAD_SIZE}; -use super::{send_to, recv_from}; +use super::crypto_io::{encrypt_payload, decrypt_payload}; #[derive(Debug, Clone)] struct Associate { @@ -72,25 +70,7 @@ impl ConnectionContext { // Client -> Remote let fut = futures::lazy(move || { let buf = &buf[..n]; - - let iv_len = svr_cfg.method().iv_size(); - if buf.len() < iv_len { - error!("Invalid ShadowSocks UDP packet, expected IV length {}, packet length {}", - iv_len, - buf.len()); - let err = io::Error::new(io::ErrorKind::Other, "early eof"); - return Err(err); - } - - let iv = &buf[..iv_len]; - let mut cipher = crypto::new_stream(svr_cfg.method(), svr_cfg.key(), iv, CryptoMode::Decrypt); - - // Decrypt payload from Client - let mut payload = Vec::with_capacity(buf.len()); - try!(cipher.update(&buf[iv_len..], &mut payload)); - try!(cipher.finalize(&mut payload)); - - Ok((payload, svr_cfg)) + decrypt_payload(svr_cfg.method(), svr_cfg.key(), buf).map(move |b| (b, svr_cfg)) }) .and_then(move |(payload, svr_cfg)| { // Read Address in the front (ShadowSocks protocol) @@ -117,10 +97,10 @@ impl ConnectionContext { client_addr: src, }); - send_to(socket, body, remote_addr) + socket.send_dgram(body, remote_addr) }) - .map(move |(socket, body, len)| { - trace!("Sent body {} bytes, actual {} bytes", body.len(), len); + .map(move |(socket, body)| { + trace!("Sent body, len: {} bytes", body.len()); ConnectionContext { assoc: assoc, svr_cfg: svr_cfg, @@ -149,26 +129,20 @@ impl ConnectionContext { buf_len); // Client <- Remote - let mut iv = svr_cfg.method().gen_init_vec(); - let mut cipher = crypto::new_stream(svr_cfg.method(), - svr_cfg.key(), - &iv[..], - CryptoMode::Encrypt); - // Append Address in front of body (ShadowSocks protocol) + let cloned_svr_cfg = svr_cfg.clone(); let fut = address.write_to(Vec::with_capacity(buf_len)) .map(move |mut send_buf| { send_buf.extend_from_slice(&buf[..n]); send_buf }) .and_then(move |send_buf| -> io::Result<_> { - try!(cipher.update(&send_buf[..], &mut iv)); - try!(cipher.finalize(&mut iv)); - Ok(iv) + let svr_cfg = cloned_svr_cfg; + encrypt_payload(svr_cfg.method(), svr_cfg.key(), &send_buf) }) - .and_then(move |final_buf| send_to(socket, final_buf, client_addr)) - .map(|(socket, buf, len)| { - trace!("Sent body {} actual {}", buf.len(), len); + .and_then(move |final_buf| socket.send_dgram(final_buf, client_addr)) + .map(|(socket, buf)| { + trace!("Sent body len: {}", buf.len()); ConnectionContext { assoc: assoc, svr_cfg: svr_cfg, @@ -183,7 +157,7 @@ impl ConnectionContext { fn handle_once(self) -> BoxIoFuture { let ConnectionContext { assoc, svr_cfg, socket } = self; - let fut = recv_from(socket, vec![0u8; MAXIMUM_UDP_PAYLOAD_SIZE]) + let fut = socket.recv_dgram(vec![0u8; MAXIMUM_UDP_PAYLOAD_SIZE]) .and_then(move |(socket, buf, n, src)| { let c = ConnectionContext { assoc: assoc.clone(),