├── .github └── workflows │ └── workflow.yml ├── .gitignore ├── Cargo.toml ├── PKGBUILD ├── readme.md ├── src ├── lib.rs ├── main.rs ├── socks.rs └── vmess.rs └── v2socks.service /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: CI & Build 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | test: 6 | name: Test 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | include: 11 | - { name: x86_64-linux-gnu, os: ubuntu-latest, target: x86_64-unknown-linux-gnu } 12 | - { name: x86_64-linux-musl, os: ubuntu-latest, target: x86_64-unknown-linux-musl } 13 | - { name: x86_64-darwin, os: macos-latest, target: x86_64-apple-darwin } 14 | - { name: x86_64-windows-msvc, os: windows-latest, target: x86_64-pc-windows-msvc } 15 | runs-on: ${{ matrix.os }} 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: dtolnay/rust-toolchain@master 19 | with: 20 | toolchain: stable 21 | target: ${{ matrix.target }} 22 | - run: sudo apt-get install musl-tools 23 | if: matrix.name == 'x86_64-linux-musl' 24 | # - run: cargo test --target ${{ matrix.target }} 25 | - run: cargo build --release --target ${{ matrix.target }} 26 | - run: strip target/${{ matrix.target }}/release/v2socks 27 | if: matrix.name != 'x86_64-windows-msvc' 28 | - run: mv target/${{ matrix.target }}/release/v2socks.exe target/${{ matrix.target }}/release/v2socks 29 | if: matrix.name == 'x86_64-windows-msvc' 30 | - uses: actions/upload-artifact@v4 31 | with: 32 | name: v2socks-${{ matrix.name }} 33 | path: target/${{ matrix.target }}/release/v2socks 34 | release: 35 | name: Release 36 | runs-on: ubuntu-latest 37 | if: startsWith(github.ref, 'refs/tags/v') 38 | needs: [test] 39 | steps: 40 | - uses: actions/checkout@v4 41 | - uses: actions/download-artifact@v4 42 | - run: | 43 | mkdir artifacts 44 | mv v2socks-x86_64-linux-gnu/v2socks artifacts/v2socks-x86_64-linux-gnu 45 | mv v2socks-x86_64-linux-musl/v2socks artifacts/v2socks-x86_64-linux-musl 46 | mv v2socks-x86_64-darwin/v2socks artifacts/v2socks-x86_64-darwin 47 | mv v2socks-x86_64-windows-msvc/v2socks artifacts/v2socks-x86_64-windows-msvc 48 | - uses: softprops/action-gh-release@v1 49 | with: 50 | draft: true 51 | files: artifacts/* 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "v2socks" 3 | version = "0.2.4" 4 | authors = ["ylxdzsw "] 5 | edition = "2021" 6 | 7 | [profile.release] 8 | panic = "abort" 9 | lto = true 10 | 11 | [dependencies] 12 | absurd = { git = "https://github.com/ylxdzsw/absurd" } 13 | rust-crypto = "0.2" 14 | rand = "0.8" 15 | anyhow = "1.0" 16 | -------------------------------------------------------------------------------- /PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Shiwei Zhang 2 | 3 | pkgname=v2socks 4 | pkgdesc="An opinioned lightweight socks5 server and vmess (v2ray) client." 5 | pkgrel=1 6 | pkgver=0.2.4 7 | arch=(any) 8 | url='https://github.com/ylxdzsw/v2socks' 9 | license=(GPL3) 10 | makedepends=(cargo) 11 | source=("https://github.com/ylxdzsw/v2socks/archive/v$pkgver.tar.gz") 12 | md5sums=(SKIP) 13 | 14 | package() { 15 | cd "$srcdir/v2socks-$pkgver" 16 | cargo build --release 17 | install -D "$srcdir"/v2socks-$pkgver/target/release/v2socks "$pkgdir"/usr/bin/v2socks 18 | install -Dm644 "$srcdir"/v2socks-$pkgver/v2socks.service "$pkgdir"/usr/lib/systemd/system/v2socks.service 19 | } 20 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | V2socks 2 | ======= 3 | 4 | An opinionated lightweight socks5 server and vmess (v2ray) client implemented in Rust. 5 | 6 | #### Deprecation 7 | 8 | This software is no longer maintained. (It still works as of Sept. 2024) 9 | 10 | You might be interested in another of my project [sopipe](https://github.com/ylxdzsw/sopipe), which also implements the 11 | functionalities in this project, plus much more! 12 | 13 | Example of running a socks5 server and a vmess client using `sopipe`: 14 | 15 | ```sh 16 | $ sopipe 'tcp(1080) => socks5_server => vmess_client("49aa7c07-2cd4-4585-b645-3392fde45b90") => tcp("example.com:3399")' 17 | ``` 18 | 19 | or through HTTP2 over TLS: 20 | 21 | ```sh 22 | $ sopipe 'tcp(1080) => socks5_server => vmess_client("49aa7c07-2cd4-4585-b645-3392fde45b90") => http2_client("example.com", "/")' 23 | ``` 24 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod socks; 2 | mod vmess; 3 | 4 | pub use socks::*; 5 | pub use vmess::*; 6 | 7 | #[derive(Debug, Clone)] 8 | pub enum Addr { 9 | V4([u8; 4]), 10 | V6([u8; 16]), 11 | Domain(Box<[u8]>) 12 | } 13 | 14 | impl std::fmt::Display for Addr { 15 | fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 16 | match self { 17 | Addr::V4(x) => std::fmt::Display::fmt(&std::net::Ipv4Addr::from(*x), fmt), 18 | Addr::V6(x) => std::fmt::Display::fmt(&std::net::Ipv6Addr::from(*x), fmt), 19 | Addr::Domain(x) => std::fmt::Display::fmt(std::str::from_utf8(x).map_err(|_| std::fmt::Error)?, fmt) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use v2socks::*; 2 | use absurd::*; 3 | use rand::prelude::*; 4 | use std::io::prelude::*; 5 | 6 | // 1. main thread listen for socks5 connections 7 | // 2. spawn a child thread for each connection, perform the sock5 and vmess handshake respectively 8 | // 3. after the handshake succeed, spawn a pair of threads to pipe the two connections forward and backward 9 | // todo: use thread pool or async io 10 | 11 | const USAGE: &str = " 12 | Usage: v2socks plain [local_port=1080] 13 | v2socks vmess : [local_port=1080] 14 | "; 15 | 16 | fn main() { 17 | let args: Vec<_> = std::env::args().skip(1).collect(); 18 | let args: Vec<_> = args.iter().map(|x| &x[..]).collect(); 19 | match args[..] { 20 | ["plain"] | ["plain", _] => { 21 | let port: u16 = args.get(1).map(|x| x.parse().unwrap()).unwrap_or(1080); 22 | let server = Socks5Server::new(port); 23 | plain(&server) 24 | }, 25 | ["vmess", proxy, user_id] | ["vmess", proxy, user_id, _] => { 26 | let port: u16 = args.get(3).map(|x| x.parse().unwrap()).unwrap_or(1080); 27 | let server = Socks5Server::new(port); 28 | vmess(&server, proxy.into(), parse_uid(user_id).unwrap()) 29 | }, 30 | _ => eprint!("{}", USAGE) 31 | } 32 | } 33 | 34 | fn parse_uid(x: &str) -> Option<[u8; 16]> { 35 | let x = x.replace('-', ""); 36 | let list: Vec<_> = (0..32).step_by(2).map(|i| u8::from_str_radix(&x[i..i+2], 16).unwrap()).collect(); 37 | list.get(0..16).map(|x| [0; 16].apply(|buf| buf.copy_from_slice(x))) 38 | } 39 | 40 | fn is_normal_close(e: &std::io::Error) -> bool { 41 | matches!(e.kind(), std::io::ErrorKind::BrokenPipe | std::io::ErrorKind::UnexpectedEof | std::io::ErrorKind::ConnectionReset) 42 | } 43 | 44 | fn vmess(server: &Socks5Server, proxy: String, user_id: [u8; 16]) { 45 | let connect = move |dest, port| { 46 | let client = std::net::TcpStream::connect(&proxy)?; 47 | debug!("connect {}:{} through proxy", &dest, port); 48 | 49 | let local = client.local_addr()?; 50 | let local_addr = match local.ip() { 51 | std::net::IpAddr::V4(x) => Addr::V4(x.octets()), 52 | std::net::IpAddr::V6(x) => Addr::V6(x.octets()), 53 | }; 54 | let local_port = local.port(); 55 | 56 | Ok((local_addr, local_port, (dest, port, client))) 57 | }; 58 | 59 | #[allow(non_snake_case)] 60 | let pass = move |(dest, port, conn): (Addr, u16, std::net::TcpStream), mut stream: std::net::TcpStream| { 61 | let key = [0; 16].apply(|x| thread_rng().fill_bytes(x)); 62 | let IV = [0; 16].apply(|x| thread_rng().fill_bytes(x)); 63 | 64 | { 65 | let conn = conn.try_clone().expect("failed to clone TCP handle"); 66 | let mut stream = stream.try_clone().expect("failed to clone TCP handle"); 67 | 68 | std::thread::spawn(move || { 69 | let mut buffer = Box::new([0; 16384]); 70 | let mut reader = match VmessReader::new(conn, key, IV) { 71 | Some(x) => x, 72 | None => return warn!("reader handshake failed") 73 | }; 74 | loop { 75 | let len = match reader.read(&mut *buffer) { 76 | Ok(0) => break, 77 | Ok(x) => x, 78 | Err(ref e) if is_normal_close(e) => break, 79 | Err(e) => { warn!("{}", e); break } 80 | }; 81 | 82 | match stream.write_all(&buffer[..len]) { 83 | Ok(_) => debug!("read {} bytes", len), 84 | Err(ref e) if is_normal_close(e) => break, 85 | Err(e) => { warn!("{}", e); break } 86 | } 87 | } 88 | reader.close(); 89 | debug!("closed reading") 90 | }); 91 | } 92 | 93 | let mut buffer = Box::new([0; 16384]); 94 | let mut writer = match VmessWriter::new(conn, user_id, dest, port, key, IV) { 95 | Some(x) => x, 96 | None => return warn!("writer handshake failed") 97 | }; 98 | loop { 99 | let len = match stream.read(&mut *buffer) { 100 | Ok(0) => break, 101 | Ok(x) => x, 102 | Err(ref e) if is_normal_close(e) => break, 103 | Err(e) => { warn!("{}", e); break } 104 | }; 105 | 106 | match writer.write_all(&buffer[..len]) { 107 | Ok(_) => debug!("sent {} bytes", len), 108 | Err(ref e) if is_normal_close(e) => break, 109 | Err(e) => { warn!("{}", e); break } 110 | } 111 | } 112 | 113 | writer.close(); 114 | debug!("closed writing"); 115 | }; 116 | 117 | server.listen(connect.box_and_leak(), pass.box_and_leak()) 118 | } 119 | 120 | fn plain(server: &Socks5Server) { 121 | server.listen(&|dest, port| { 122 | let client = std::net::TcpStream::connect(format!("{}:{}", dest, port))?; 123 | debug!("connect {}:{}", dest, port); 124 | 125 | let local = client.local_addr()?; 126 | let local_addr = match local.ip() { 127 | std::net::IpAddr::V4(x) => Addr::V4(x.octets()), 128 | std::net::IpAddr::V6(x) => Addr::V6(x.octets()), 129 | }; 130 | let local_port = local.port(); 131 | 132 | Ok((local_addr, local_port, client)) 133 | }, &|mut proxy, mut stream| { 134 | { 135 | let mut proxy = proxy.try_clone().expect("failed to clone TCP handle"); 136 | let mut stream = stream.try_clone().expect("failed to clone TCP handle"); 137 | std::thread::spawn(move || { 138 | std::io::copy(&mut proxy, &mut stream).ignore() 139 | }); 140 | } 141 | std::io::copy(&mut stream, &mut proxy).ignore(); 142 | }) 143 | } 144 | -------------------------------------------------------------------------------- /src/socks.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use absurd::*; 3 | use anyhow::{Context, Result, anyhow}; 4 | use std::net::{TcpListener, TcpStream}; 5 | use std::io::prelude::*; 6 | 7 | macro_rules! read_exact { 8 | ($stream: expr, $array: expr) => {{ 9 | let mut x = $array; 10 | $stream.read_exact(&mut x).map(|_| x) 11 | }} 12 | } 13 | 14 | macro_rules! close_on_error { 15 | ($ex: expr) => {{ 16 | match $ex { 17 | Ok(x) => x, 18 | Err(e) => { 19 | return warn!("{}", e) 20 | } 21 | } 22 | }} 23 | } 24 | 25 | pub struct Socks5Server { 26 | port: u16 27 | } 28 | 29 | impl Socks5Server { 30 | pub fn new(port: u16) -> Socks5Server { 31 | Socks5Server { port } 32 | } 33 | 34 | pub fn listen(&self, connect: &'static (impl Fn(Addr, u16) -> std::io::Result<(Addr, u16, T)> + Sync), pass: &'static (impl Fn(T, TcpStream) + Sync)) -> ! { 35 | let socket = TcpListener::bind(format!("0.0.0.0:{}", self.port)).expect("Address already in use"); 36 | info!("v2socks starts listening at 0.0.0.0:{}", self.port); 37 | 38 | for stream in socket.incoming() { 39 | let stream = stream.unwrap(); 40 | std::thread::spawn(move || { 41 | close_on_error!(initialize(&mut &stream)); 42 | let (addr, port) = close_on_error!(read_request(&mut &stream)); // TODO: properly respond with correct error number 43 | let (local_addr, local_port, proxy) = close_on_error!(connect(addr, port)); 44 | close_on_error!(reply_request(&mut &stream, local_addr, local_port)); 45 | pass(proxy, stream); 46 | }); 47 | } 48 | 49 | unreachable!() 50 | } 51 | } 52 | 53 | fn initialize(stream: &mut (impl ReadExt + Write)) -> Result<()> { 54 | let header = read_exact!(stream, [0, 0]).context("read initial bits failed")?; 55 | 56 | if header[0] != 5 { 57 | let hint = "if the version is 71, the software probabily used it as an HTTP proxy"; 58 | return Err(anyhow!("unsupported socks version {}. Hint: {}", header[0], hint)) 59 | } 60 | 61 | let list = stream.read_exact_alloc(header[1] as usize).context("read methods failed")?; 62 | 63 | if !list.contains(&0) { 64 | stream.write(&[5, 0xff]).context("write response failed")?; 65 | return Err(anyhow!("client do not support NO AUTH method")) 66 | } 67 | 68 | stream.write(&[5, 0]).context("write response failed")?; 69 | Ok(()) 70 | } 71 | 72 | fn read_request(stream: &mut (impl ReadExt + Write)) -> Result<(Addr, u16)> { 73 | let [ver, cmd, _rev, atyp] = read_exact!(stream, [0; 4]).context("read request header failed")?; 74 | 75 | if ver != 5 { 76 | return Err(anyhow!("unsupported socks version {}", ver)) 77 | } 78 | 79 | if cmd != 1 { 80 | return Err(anyhow!("unsupported command type {}", cmd)) 81 | } 82 | 83 | let addr = match atyp { 84 | 0x01 => Addr::V4(read_exact!(stream, [0; 4]).context("read v4 address failed")?), 85 | 0x04 => Addr::V6(read_exact!(stream, [0; 16]).context("read v6 address failed")?), 86 | 0x03 => { 87 | let len = read_exact!(stream, [0]).context("read domain length failed")?[0]; 88 | Addr::Domain(stream.read_exact_alloc(len as usize).context("read domain failed")?) 89 | }, 90 | _ => return Err(anyhow!("unknown ATYP")) 91 | }; 92 | 93 | let port = read_exact!(stream, [0; 2]).context("read port failed")?; 94 | let port = (port[0] as u16) << 8 | port[1] as u16; 95 | 96 | Ok((addr, port)) 97 | } 98 | 99 | fn reply_request(stream: &mut (impl ReadExt + Write), addr: Addr, port: u16) -> Result<()> { 100 | let mut reply = Vec::with_capacity(22); // cover V4 and V6 101 | reply.extend_from_slice(&[5, 0, 0]); 102 | 103 | match addr { 104 | Addr::V4(x) => { 105 | reply.push(1); 106 | reply.extend_from_slice(&x); 107 | }, 108 | Addr::V6(x) => { 109 | reply.push(4); 110 | reply.extend_from_slice(&x); 111 | }, 112 | Addr::Domain(x) => { 113 | reply.push(3); 114 | reply.push(x.len() as u8); 115 | reply.extend_from_slice(&x); 116 | } 117 | } 118 | 119 | reply.push((port >> 8) as u8); 120 | reply.push(port as u8); 121 | 122 | stream.write(&reply).context("write reply failed")?; 123 | 124 | Ok(()) 125 | } 126 | -------------------------------------------------------------------------------- /src/vmess.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use absurd::*; 3 | use crypto::digest::Digest; 4 | use crypto::symmetriccipher::BlockEncryptor; 5 | use crypto::mac::Mac; 6 | use std::io::prelude::*; 7 | 8 | macro_rules! md5 { 9 | ($($x:expr),*) => {{ 10 | let mut digest = crypto::md5::Md5::new(); 11 | let mut result = [0; 16]; 12 | $(digest.input($x);)* 13 | digest.result(&mut result); 14 | result 15 | }} 16 | } 17 | 18 | /// read from vmess connection and produce decoded stream 19 | #[derive(Debug)] 20 | pub struct VmessReader { 21 | reader: R, 22 | decoder: AES128CFB 23 | } 24 | 25 | impl VmessReader> { 26 | /// key and IV are just data key and iv in the request header, this function will calculate the md5 it selfs 27 | #[allow(non_snake_case)] 28 | pub fn new(conn: std::net::TcpStream, key: [u8; 16], IV: [u8; 16]) -> Option { 29 | let mut reader = VmessReader { 30 | reader: std::io::BufReader::with_capacity(1<<14, conn), 31 | decoder: AES128CFB::new(md5!(&key), md5!(&IV)) 32 | }; 33 | reader.handshake().ok()?; 34 | Some(reader) 35 | } 36 | 37 | pub fn into_inner(self) -> std::net::TcpStream { 38 | self.reader.into_inner() 39 | } 40 | 41 | pub fn close(self) { 42 | self.into_inner().shutdown(std::net::Shutdown::Read).ignore() 43 | } 44 | } 45 | 46 | impl VmessReader { 47 | fn handshake(&mut self) -> std::io::Result<()> { 48 | let mut head = [0; 4]; 49 | 50 | self.reader.read_exact(&mut head)?; 51 | self.decoder.decode(&mut head); 52 | 53 | assert!(head[0] == 39); // match the number provided at request handshaking 54 | let mut cmd = self.reader.read_exact_alloc(head[3] as usize)?; 55 | self.decoder.decode(&mut cmd); 56 | Ok(()) 57 | } 58 | } 59 | 60 | impl Read for VmessReader { 61 | fn read(&mut self, buf: &mut [u8]) -> std::io::Result { 62 | let mut temp = [0; 4]; 63 | assert!(buf.len() >= (1<<14) - 4); 64 | 65 | // 1. read and decode length 66 | if let Err(e) = self.reader.read_exact(&mut temp[..2]) { 67 | match e.kind() { 68 | std::io::ErrorKind::UnexpectedEof | std::io::ErrorKind::ConnectionReset => return Ok(0), 69 | _ => return Err(e) 70 | } 71 | } 72 | self.decoder.decode(&mut temp[..2]); 73 | let len = (temp[0] as usize) << 8 | temp[1] as usize; 74 | 75 | // 2. read and decode checksum 76 | self.reader.read_exact(&mut temp).unwrap(); 77 | self.decoder.decode(&mut temp); 78 | 79 | // 3. read and decode data 80 | self.reader.read_exact(&mut buf[..len-4]).unwrap(); 81 | self.decoder.decode(&mut buf[..len-4]); 82 | 83 | // 4. verify checksum 84 | let checksum = fnv1a(&buf[..len-4]); 85 | if checksum.to_be_bytes() != temp { 86 | return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid checksum!")) 87 | } 88 | 89 | Ok(len-4) 90 | } 91 | } 92 | 93 | /// write to vmess connection 94 | #[derive(Debug)] 95 | pub struct VmessWriter { 96 | writer: W, 97 | encoder: AES128CFB 98 | } 99 | 100 | impl VmessWriter { 101 | #[allow(non_snake_case)] 102 | pub fn new(conn: std::net::TcpStream, user_id: [u8; 16], addr: Addr, port: u16, key: [u8; 16], IV: [u8; 16]) -> Option { 103 | let mut writer = VmessWriter { 104 | writer: conn, 105 | encoder: AES128CFB::new(key, IV) 106 | }; 107 | writer.handshake(user_id, addr, port, key, IV).ok()?; 108 | Some(writer) 109 | } 110 | 111 | pub fn into_inner(self) -> std::net::TcpStream { 112 | self.writer 113 | } 114 | 115 | pub fn close(mut self) { 116 | self.write(&[]).ignore(); // as required by the spec 117 | self.into_inner().shutdown(std::net::Shutdown::Write).ignore() 118 | } 119 | } 120 | 121 | impl VmessWriter { 122 | #[allow(non_snake_case, non_upper_case_globals)] 123 | fn handshake(&mut self, user_id: [u8; 16], addr: Addr, port: u16, key: [u8; 16], IV: [u8; 16]) -> std::io::Result<()> { 124 | let time = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs().to_be_bytes(); 125 | let mut hmac = crypto::hmac::Hmac::new(crypto::md5::Md5::new(), &user_id); 126 | hmac.input(&time); 127 | let mut auth = [0; 16]; 128 | hmac.raw_result(&mut auth); 129 | self.writer.write_all(&auth)?; 130 | 131 | let mut buffer = Vec::new(); 132 | 133 | let version = 1; 134 | buffer.push(version); 135 | 136 | buffer.extend_from_slice(&IV); 137 | buffer.extend_from_slice(&key); 138 | 139 | let V = 39; // should be random but who bother 140 | buffer.push(V); 141 | 142 | let opt = 0b0000_0001; 143 | buffer.push(opt); 144 | 145 | const P_len: u8 = 0; 146 | let sec = 0; // AES-128-CFB 147 | buffer.push((P_len << 4) | (sec & 0x0f)); 148 | 149 | let rev = 0; // reserved 150 | buffer.push(rev); 151 | 152 | let cmd = 1; // tcp 153 | buffer.push(cmd); 154 | 155 | let port = port.to_be_bytes(); 156 | buffer.extend_from_slice(&port); 157 | 158 | match addr { 159 | Addr::V4(x) => { 160 | buffer.push(1); 161 | buffer.extend_from_slice(&x); 162 | } 163 | Addr::V6(x) => { 164 | buffer.push(3); 165 | buffer.extend_from_slice(&x); 166 | }, 167 | Addr::Domain(x) => { 168 | buffer.push(2); 169 | buffer.push(x.len() as u8); 170 | buffer.extend_from_slice(&x); 171 | } 172 | } 173 | 174 | let P = [0; P_len as usize]; 175 | buffer.extend_from_slice(&P); 176 | 177 | let F = fnv1a(&buffer); 178 | buffer.extend_from_slice(&F.to_be_bytes()); 179 | 180 | let header_key = md5!(&user_id, b"c48619fe-8f02-49e0-b9e9-edf763e17e21"); 181 | let header_IV = md5!(&time, &time, &time, &time); 182 | 183 | AES128CFB::new(header_key, header_IV).encode(&mut buffer); 184 | 185 | self.writer.write_all(&buffer) 186 | } 187 | } 188 | 189 | impl Write for VmessWriter { 190 | fn write(&mut self, data: &[u8]) -> std::io::Result { 191 | let len = data.len() + 4; 192 | let mut buf = Vec::with_capacity(len + 2); 193 | buf.extend_from_slice(&(len as u16).to_be_bytes()); 194 | buf.extend_from_slice(&fnv1a(data).to_be_bytes()); 195 | buf.extend_from_slice(data); 196 | self.encoder.encode(&mut buf); // this is the right code. the fucking protocol document is misleading! 197 | self.writer.write_all(&buf)?; 198 | Ok(data.len()) 199 | } 200 | 201 | fn flush(&mut self) -> std::io::Result<()> { 202 | self.writer.flush() 203 | } 204 | } 205 | 206 | #[allow(clippy::unreadable_literal)] 207 | fn fnv1a(x: &[u8]) -> u32 { 208 | let prime = 16777619; 209 | let mut hash = 0x811c9dc5; 210 | for byte in x.iter() { 211 | hash ^= *byte as u32; 212 | hash = hash.wrapping_mul(prime); 213 | } 214 | hash 215 | } 216 | 217 | #[derive(Debug)] 218 | struct AES128CFB { 219 | key: [u8; 16], 220 | state: [u8; 16], 221 | p: usize, 222 | } 223 | 224 | impl AES128CFB { 225 | #[allow(non_snake_case)] 226 | fn new(key: [u8; 16], IV: [u8; 16]) -> AES128CFB { 227 | AES128CFB { key, state: IV, p: 16 } 228 | } 229 | 230 | fn encode(&mut self, data: &mut [u8]) { 231 | for byte in data.iter_mut() { 232 | if self.p == 16 { 233 | crypto::aessafe::AesSafe128Encryptor::new(&self.key).encrypt_block(&self.state.clone(), &mut self.state); 234 | self.p = 0; 235 | } 236 | *byte ^= self.state[self.p]; 237 | self.state[self.p] = *byte; 238 | self.p += 1; 239 | } 240 | } 241 | 242 | fn decode(&mut self, data: &mut [u8]) { 243 | for byte in data.iter_mut() { 244 | if self.p == 16 { 245 | crypto::aessafe::AesSafe128Encryptor::new(&self.key).encrypt_block(&self.state.clone(), &mut self.state); // yes it's encrypt 246 | self.p = 0; 247 | } 248 | let temp = *byte; 249 | *byte ^= self.state[self.p]; 250 | self.state[self.p] = temp; 251 | self.p += 1; 252 | } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /v2socks.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=v2socks 3 | 4 | [Service] 5 | Type=simple 6 | ExecStart=/usr/bin/v2socks plain --------------------------------------------------------------------------------