├── .dockerignore ├── .gitignore ├── .rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── Dockerfile-client ├── LICENSE ├── README.md ├── bpf ├── .cargo │ └── config.toml ├── Cargo.toml ├── build.rs ├── require.h └── src │ ├── cgroup_connect4.rs │ ├── cgroup_connect6.rs │ ├── command_check.rs │ ├── get_peer_name.rs │ ├── get_sock_name.rs │ ├── kernel_binding.rs │ ├── lib.rs │ ├── main.rs │ ├── map.rs │ ├── safe_helper.rs │ └── sockops_callback.rs ├── client ├── Cargo.toml ├── example.yaml ├── src │ ├── args.rs │ ├── bpf_map_name.rs │ ├── bpf_share.rs │ ├── client.rs │ ├── config.rs │ ├── connect │ │ ├── hyper.rs │ │ └── mod.rs │ ├── lib.rs │ ├── listener │ │ ├── bpf.rs │ │ └── mod.rs │ ├── main.rs │ ├── mptcp.rs │ └── owned_link.rs └── tests │ ├── bpf_test.rs │ └── bpf_test6.rs ├── protocol ├── Cargo.toml └── src │ ├── accept.rs │ ├── auth.rs │ ├── connect.rs │ ├── h2_config.rs │ ├── hyper_body.rs │ └── lib.rs ├── server ├── Cargo.toml ├── example.yaml ├── src │ ├── args.rs │ ├── config.rs │ ├── err.rs │ ├── lib.rs │ ├── main.rs │ ├── mptcp.rs │ └── server.rs └── tests │ ├── ca.cert │ ├── ca.info │ ├── ca.key │ ├── proxy.rs │ ├── server.cert │ ├── server.info │ └── server.key └── share ├── Cargo.toml └── src ├── async_iter_ext.rs ├── dns.rs ├── helper.rs ├── lib.rs ├── log.rs ├── proxy.rs └── tcp_wrapper.rs /.dockerignore: -------------------------------------------------------------------------------- 1 | ./target 2 | Dockerfile-client 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | client/whitelist.txt 4 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2024" 2 | group_imports = "StdExternalCrate" 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["server", "bpf", "client", "share", "protocol"] 3 | resolver = "2" 4 | 5 | [workspace.dependencies] 6 | anyhow = "1" 7 | bytes = "1" 8 | cidr = "0.2" 9 | clap = { version = "4", features = ["derive", "color", "suggestions"] } 10 | futures-channel = "0.3" 11 | futures-rustls = "0.26" 12 | futures-util = "0.3" 13 | hickory-resolver = { version = "0.24", default-features = false } 14 | http = "1" 15 | http-body-util = "0.1" 16 | hyper = "1" 17 | hyper-util = "0.1" 18 | libc = "0.2" 19 | pin-project = "1" 20 | rustls-native-certs = "0.7" 21 | rustls-pemfile = "2" 22 | serde = "1" 23 | serde_yaml = "0.9" 24 | socket2 = "0.5" 25 | tap = "1" 26 | tokio = "1" 27 | tokio-stream = "0.1" 28 | tokio-util = "0.7" 29 | totp-rs = "5" 30 | thiserror = "1" 31 | tower-service = "0.3" 32 | tracing = "0.1" 33 | tracing-subscriber = "0.3" 34 | trait-make = "0.1" 35 | -------------------------------------------------------------------------------- /Dockerfile-client: -------------------------------------------------------------------------------- 1 | FROM archlinux as bpf-builder 2 | 3 | RUN pacman -Sy rustup clang linux-api-headers lib32-glibc --noconfirm 4 | 5 | RUN rustup toolchain install nightly 6 | RUN rustup default nightly-x86_64-unknown-linux-gnu 7 | RUN rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu 8 | 9 | RUN cargo install bpf-linker 10 | 11 | COPY . /lycoris 12 | 13 | WORKDIR /lycoris/bpf 14 | 15 | RUN cargo build --release -Z trim-paths 16 | 17 | RUN cp /lycoris/target/bpfel-unknown-none/release/lycoris-bpf /lycoris-bpf 18 | 19 | FROM rust:slim as builder 20 | 21 | RUN rustup toolchain install nightly 22 | RUN rustup default nightly-x86_64-unknown-linux-gnu 23 | 24 | RUN apt update && apt install libclang-dev linux-headers-amd64 libc6-dev -y 25 | 26 | COPY . /lycoris 27 | 28 | WORKDIR /lycoris/client 29 | 30 | RUN cargo build --release -Z trim-paths 31 | 32 | FROM gcr.io/distroless/cc-debian12 33 | LABEL authors="Sherlock Holo" 34 | 35 | COPY --from=builder /lycoris/target/release/lycoris-client /lycoris-client 36 | COPY --from=bpf-builder /lycoris-bpf /lycoris-bpf 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Sherlock Holo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lycoris 2 | 3 | ## a bpf based proxy 4 | 5 | currently, many proxy use http proxy or socks5 proxy protocol, all of them need the program support proxy protocol 6 | 7 | actually this is a big limit, when you use some programs don't support specify the proxy, or it's very hard to enable 8 | the proxy, you will want to smash you computer :( 9 | 10 | lycoris can solve this problem, at least solve the 95% 11 | 12 | ## how it work 13 | 14 | there are 3 parts about lycoris 15 | 16 | - lycoris-bpf 17 | - lycoris-client 18 | - lycoris-server 19 | - lycoris-protocol 20 | 21 | ### lycoris-bpf 22 | 23 | `lycoris-bpf` will hook all socket connect, and check if the dst ip should be proxies or not, if it is a need proxy ip, 24 | lycoris-bpf will change the socket dst ip to `lycoris-client`, and save the real dst ip and port in bpf socket 25 | `sk_storage`, then hook `lycoris-client` `TcpListener` `getsockname` to allow `lycoris-client` get the real dst ip and 26 | port, so `lycoris-client` can get it and send the dst ip and port to `lycoris-server` to connect the 27 | target 28 | 29 | also `lycoris-bpf>4.0` now will also hook `getpeername` for the connecting socket so it will get the real dst ip and 30 | port too 31 | 32 | ### lycoris-client 33 | 34 | `lycoris-client` will listen a tcp socket, when a new tcp accepted, it will get the real dst ip and port through hooked 35 | `getsockname`, and send to `lycoris-server` 36 | 37 | ### lycoris-server 38 | 39 | `lycoris-server` is a simple proxy server, like [camouflage](https://github.com/Sherlock-Holo/camouflage), trojan or 40 | something else 41 | 42 | ### lycoris-protocol 43 | 44 | `lycoris-protocol` provide a way to use lycoris transport protocol, it can base on any reliable transmission 45 | 46 | ## the ip list file 47 | 48 | it just a simple txt like 49 | 50 | ``` 51 | 127.0.0.1/8 52 | 10.100.0.0/16 53 | ``` 54 | 55 | ## features 56 | 57 | - [x] TCP4 proxy 58 | - [ ] UDP4 proxy(needs good idea) 59 | - [x] TCP6 proxy 60 | - [ ] UDP6 proxy(needs good idea) 61 | - [x] ip list filter 62 | - [x] container proxy 63 | - need set `container_bridge_listen_addr` and `container_bridge_listen_addr_v6` 64 | - podman with slirp4netns doesn't need set, it connects tcp outside the container 65 | - docker need set, if use bridge+veth mode 66 | - [x] process comm filter 67 | 68 | ## build 69 | 70 | lycoris require nightly rust toolchain when build `lycoris-bpf` 71 | 72 | ### build dependencies 73 | 74 | - rust toolchain 75 | - bpf-linker(can installed by `cargo install`) 76 | 77 | just run `cargo build --release` 78 | 79 | ### notes 80 | 81 | 1. you can't build in the lycoris root dir, you should build inside `bpf`, `client`, `server` dir, because `lycoris-bpf` 82 | target is `bpfel-unknown-none` 83 | 2. when build the `lycoris-bpf`, you must use release build mode, otherwise rustc and llvm will generate some 84 | instruction which will make bpf verifier unhappy 85 | -------------------------------------------------------------------------------- /bpf/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "bpfel-unknown-none" 3 | 4 | [unstable] 5 | build-std = ["core"] 6 | 7 | [target.bpfel-unknown-none] 8 | rustflags = ["-C", "debuginfo=2", "-C", "link-arg=--btf"] 9 | -------------------------------------------------------------------------------- /bpf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lycoris-bpf" 3 | version = "0.4.0" 4 | edition = "2021" 5 | license = "MIT" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | aya-ebpf = "0.1" 11 | aya-log-ebpf = "0.1" 12 | unroll = "0.1" 13 | 14 | [build-dependencies] 15 | bindgen = "0.69" 16 | -------------------------------------------------------------------------------- /bpf/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::PathBuf; 3 | 4 | use bindgen::EnumVariation; 5 | 6 | fn main() { 7 | println!("cargo:rerun-if-changed=require.h"); 8 | 9 | generate_require(); 10 | } 11 | 12 | fn generate_require() { 13 | let bindings = bindgen::builder() 14 | .use_core() 15 | .ctypes_prefix("::aya_ebpf::cty") 16 | .layout_tests(false) 17 | .generate_comments(false) 18 | .clang_arg("-Wno-unknown-attributes") 19 | .default_enum_style(EnumVariation::ModuleConsts) 20 | .prepend_enum_name(false) 21 | .derive_debug(false) 22 | .header("require.h") 23 | .allowlist_type(".*") 24 | .allowlist_var(".*") 25 | .size_t_is_usize(false) 26 | .generate() 27 | .unwrap(); 28 | 29 | let path = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("require.rs"); 30 | 31 | bindings.write_to_file(path).unwrap(); 32 | } 33 | -------------------------------------------------------------------------------- /bpf/require.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by sherlock on 22-7-3. 3 | // 4 | 5 | #ifndef BPF_LEARN_REQUIRE_H 6 | #define BPF_LEARN_REQUIRE_H 7 | 8 | // fix require stub-32.sh problem 9 | #ifndef __x86_64__ 10 | #define __x86_64__ 1 11 | #endif 12 | #ifndef __LP64__ 13 | #define __LP64__ 1 14 | #endif 15 | 16 | #include 17 | 18 | #endif //BPF_LEARN_REQUIRE_H 19 | -------------------------------------------------------------------------------- /bpf/src/cgroup_connect4.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_long; 2 | use core::ptr; 3 | use core::ptr::addr_of_mut; 4 | 5 | use aya_ebpf::bindings::BPF_LOCAL_STORAGE_GET_F_CREATE; 6 | use aya_ebpf::helpers::*; 7 | use aya_ebpf::maps::lpm_trie::Key; 8 | use aya_ebpf::programs::SockAddrContext; 9 | use aya_log_ebpf::{debug, error}; 10 | 11 | use crate::command_check::command_can_connect_directly; 12 | use crate::kernel_binding::require; 13 | use crate::map::*; 14 | use crate::{connect_directly, Ipv4Addr}; 15 | 16 | /// check connect ipv4 in proxy ipv4 list or not, if in list, save the origin dst ipv4 addr into 17 | /// DST_IPV4_ADDR_STORE with (cookie, origin_dst_ipv4_addr), otherwise let it connect directly 18 | pub fn handle_cgroup_connect4(ctx: SockAddrContext) -> Result<(), c_long> { 19 | let sock_addr = unsafe { &mut *ctx.sock_addr }; 20 | 21 | if sock_addr.type_ != require::__socket_type::SOCK_STREAM 22 | || sock_addr.family != require::AF_INET 23 | { 24 | return Ok(()); 25 | } 26 | 27 | if command_can_connect_directly(&ctx)? { 28 | return Ok(()); 29 | } 30 | 31 | let in_container = unsafe { 32 | let root_netns_cookie = bpf_get_netns_cookie(ptr::null_mut()); 33 | let current_netns_cookie = bpf_get_netns_cookie(ctx.sock_addr as _); 34 | 35 | root_netns_cookie != current_netns_cookie 36 | }; 37 | 38 | let user_ip4_u32 = u32::from_be(sock_addr.user_ip4); 39 | let user_ip4 = user_ip4_u32.to_be_bytes(); 40 | let key = Key::new(32, user_ip4); 41 | 42 | let in_list_connect_directly = match PROXY_LIST_MODE.get(0) { 43 | None => { 44 | debug!(&ctx, "get proxy list mode failed"); 45 | 46 | return Err(0); 47 | } 48 | 49 | Some(mode) => *mode == CONNECT_DIRECTLY_MODE, 50 | }; 51 | 52 | let in_list = PROXY_IPV4_LIST.get(&key).copied().unwrap_or(0) > 0; 53 | if connect_directly(in_list_connect_directly, in_list) { 54 | debug!(&ctx, "{:i} is direct connect ip", user_ip4_u32); 55 | 56 | return Ok(()); 57 | } 58 | 59 | let index = if in_container { 1 } else { 0 }; 60 | let proxy_client: &Ipv4Addr = match PROXY_IPV4_CLIENT.get(index) { 61 | None => { 62 | debug!( 63 | &ctx, 64 | "maybe proxy server is not set yet, let {:i} connect directly", user_ip4_u32 65 | ); 66 | 67 | return Ok(()); 68 | } 69 | 70 | Some(proxy_server) => proxy_server, 71 | }; 72 | 73 | if in_container && proxy_client.addr == [0; 4] { 74 | debug!(&ctx, "container bridge listen addr not set, ignore it"); 75 | 76 | return Ok(()); 77 | } 78 | 79 | if user_ip4 == proxy_client.addr { 80 | debug!( 81 | &ctx, 82 | "proxy client ip {:i} need connect directly", user_ip4_u32 83 | ); 84 | 85 | return Ok(()); 86 | } 87 | 88 | debug!(&ctx, "{:i} need proxy", user_ip4_u32); 89 | 90 | debug!( 91 | &ctx, 92 | "get proxy server done {:i}", 93 | u32::from_be_bytes(proxy_client.addr) 94 | ); 95 | 96 | let origin_dst_ipv4_addr = Ipv4Addr { 97 | addr: user_ip4, 98 | port: u16::from_be(sock_addr.user_port as _), 99 | _padding: [0; 2], 100 | }; 101 | 102 | unsafe { 103 | let ptr = bpf_sk_storage_get( 104 | addr_of_mut!(CONNECT_DST_IPV4_ADDR_STORAGE) as _, 105 | (*ctx.sock_addr).__bindgen_anon_1.sk as _, 106 | ptr::null_mut(), 107 | BPF_LOCAL_STORAGE_GET_F_CREATE as _, 108 | ); 109 | if ptr.is_null() { 110 | error!(&ctx, "get sk_storage ptr failed"); 111 | 112 | return Err(0); 113 | } 114 | 115 | let ptr = ptr as *mut Ipv4Addr; 116 | ptr.write(origin_dst_ipv4_addr); 117 | 118 | debug!(&ctx, "write sk_storage ptr done"); 119 | } 120 | 121 | debug!(&ctx, "set cookie and origin dst ipv4 addr done"); 122 | 123 | sock_addr.user_ip4 = u32::from_be_bytes(proxy_client.addr).to_be(); 124 | sock_addr.user_port = proxy_client.port.to_be() as _; 125 | 126 | debug!(&ctx, "set user_ip4 and user_port to proxy server done"); 127 | 128 | Ok(()) 129 | } 130 | -------------------------------------------------------------------------------- /bpf/src/cgroup_connect6.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_long; 2 | use core::ptr::addr_of_mut; 3 | use core::{mem, net, ptr}; 4 | 5 | use aya_ebpf::bindings::{bpf_sock_addr, BPF_LOCAL_STORAGE_GET_F_CREATE}; 6 | use aya_ebpf::helpers::*; 7 | use aya_ebpf::maps::lpm_trie::Key; 8 | use aya_ebpf::programs::SockAddrContext; 9 | use aya_log_ebpf::{debug, error, info}; 10 | 11 | use crate::command_check::command_can_connect_directly; 12 | use crate::kernel_binding::require; 13 | use crate::map::*; 14 | use crate::{connect_directly, u16_ipv6_to_u8_ipv6, Ipv6Addr}; 15 | 16 | /// check connect ipv6 in proxy ipv6 list or not, if in list, save the origin dst ipv6 addr into 17 | /// DST_IPV6_ADDR_STORE with (cookie, origin_dst_ipv6_addr), otherwise let it connect directly 18 | pub fn handle_cgroup_connect6(ctx: SockAddrContext) -> Result<(), c_long> { 19 | let sock_addr = unsafe { &mut *ctx.sock_addr }; 20 | 21 | if sock_addr.type_ != require::__socket_type::SOCK_STREAM 22 | || sock_addr.family != require::AF_INET6 23 | { 24 | return Ok(()); 25 | } 26 | 27 | if command_can_connect_directly(&ctx)? { 28 | return Ok(()); 29 | } 30 | 31 | let in_container = unsafe { 32 | let root_netns_cookie = bpf_get_netns_cookie(ptr::null_mut()); 33 | let current_netns_cookie = bpf_get_netns_cookie(ctx.sock_addr as _); 34 | 35 | root_netns_cookie != current_netns_cookie 36 | }; 37 | 38 | let user_ipv6 = get_ipv6_segments(sock_addr); 39 | let user_ipv6_octets = u16_ipv6_to_u8_ipv6(user_ipv6); 40 | match net::Ipv6Addr::from(user_ipv6_octets).to_ipv4_mapped() { 41 | Some(ipv4) => { 42 | let ipv4_octets = ipv4.octets(); 43 | info!( 44 | &ctx, 45 | "ipv6 addr {:i} is ipv4 mapped addr {:i}", 46 | user_ipv6_octets, 47 | u32::from_be_bytes(ipv4_octets), 48 | ); 49 | 50 | let key = Key::new(32, ipv4_octets); 51 | 52 | let in_list_connect_directly = match PROXY_LIST_MODE.get(0) { 53 | None => { 54 | debug!(&ctx, "get proxy list mode failed"); 55 | 56 | return Err(0); 57 | } 58 | 59 | Some(mode) => *mode == CONNECT_DIRECTLY_MODE, 60 | }; 61 | 62 | let in_list = PROXY_IPV4_LIST.get(&key).copied().unwrap_or(0) > 0; 63 | if connect_directly(in_list_connect_directly, in_list) { 64 | debug!( 65 | &ctx, 66 | "{:i} is direct connect ip", 67 | u32::from_be_bytes(ipv4_octets) 68 | ); 69 | 70 | return Ok(()); 71 | } 72 | } 73 | 74 | None => { 75 | let key = Key::new(128, user_ipv6); 76 | 77 | let in_list_connect_directly = match PROXY_LIST_MODE.get(0) { 78 | None => { 79 | debug!(&ctx, "get proxy list mode failed"); 80 | 81 | return Err(0); 82 | } 83 | 84 | Some(mode) => *mode == CONNECT_DIRECTLY_MODE, 85 | }; 86 | 87 | let in_list = PROXY_IPV6_LIST.get(&key).copied().unwrap_or(0) > 0; 88 | if connect_directly(in_list_connect_directly, in_list) { 89 | debug!(&ctx, "{:i} is direct connect ip", user_ipv6_octets); 90 | 91 | return Ok(()); 92 | } 93 | } 94 | } 95 | 96 | let index = if in_container { 1 } else { 0 }; 97 | let proxy_client: &Ipv6Addr = match PROXY_IPV6_CLIENT.get(index) { 98 | None => { 99 | debug!( 100 | &ctx, 101 | "maybe proxy server is not set yet, let {:i} connect directly", user_ipv6_octets 102 | ); 103 | 104 | return Ok(()); 105 | } 106 | 107 | Some(proxy_server) => proxy_server, 108 | }; 109 | 110 | if in_container && proxy_client.addr == [0; 16] { 111 | debug!(&ctx, "container bridge listen addr v6 not set, ignore it"); 112 | 113 | return Ok(()); 114 | } 115 | 116 | if user_ipv6_octets == proxy_client.addr { 117 | debug!( 118 | &ctx, 119 | "proxy client ip {:i} need connect directly", user_ipv6_octets 120 | ); 121 | 122 | return Ok(()); 123 | } 124 | 125 | debug!(&ctx, "{:i} need proxy", user_ipv6_octets); 126 | 127 | debug!( 128 | &ctx, 129 | "get proxy server done [{:i}]:{}", proxy_client.addr, proxy_client.port 130 | ); 131 | 132 | let origin_dst_ipv6_addr = Ipv6Addr { 133 | addr: user_ipv6_octets, 134 | port: u16::from_be(sock_addr.user_port as _), 135 | }; 136 | 137 | unsafe { 138 | let ptr = bpf_sk_storage_get( 139 | addr_of_mut!(CONNECT_DST_IPV6_ADDR_STORAGE) as _, 140 | (*ctx.sock_addr).__bindgen_anon_1.sk as _, 141 | ptr::null_mut(), 142 | BPF_LOCAL_STORAGE_GET_F_CREATE as _, 143 | ); 144 | if ptr.is_null() { 145 | error!(&ctx, "get sk_storage ptr failed"); 146 | 147 | return Err(0); 148 | } 149 | 150 | let ptr = ptr as *mut Ipv6Addr; 151 | ptr.write(origin_dst_ipv6_addr); 152 | 153 | debug!(&ctx, "write sk_storage ptr done"); 154 | } 155 | 156 | set_ipv6_segments(sock_addr, proxy_client.addr); 157 | sock_addr.user_port = proxy_client.port.to_be() as _; 158 | 159 | debug!(&ctx, "set user_ip6 and user_port to proxy server done"); 160 | 161 | Ok(()) 162 | } 163 | 164 | #[inline] 165 | fn get_ipv6_segments(sock_addr: &bpf_sock_addr) -> [u16; 8] { 166 | let addr = [ 167 | sock_addr.user_ip6[0], 168 | sock_addr.user_ip6[1], 169 | sock_addr.user_ip6[2], 170 | sock_addr.user_ip6[3], 171 | ]; 172 | 173 | // Safety: [u16; 8] equal [u32; 4] 174 | unsafe { mem::transmute(addr) } 175 | } 176 | 177 | #[inline] 178 | fn set_ipv6_segments(sock_addr: &mut bpf_sock_addr, value: [u8; 16]) { 179 | let value: [u32; 4] = unsafe { mem::transmute(value) }; 180 | 181 | sock_addr.user_ip6[0] = value[0]; 182 | sock_addr.user_ip6[1] = value[1]; 183 | sock_addr.user_ip6[2] = value[2]; 184 | sock_addr.user_ip6[3] = value[3]; 185 | } 186 | -------------------------------------------------------------------------------- /bpf/src/command_check.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_long; 2 | 3 | use aya_ebpf::programs::SockAddrContext; 4 | use aya_log_ebpf::debug; 5 | 6 | use crate::map::{COMM_MAP, COMM_MAP_MODE}; 7 | use crate::safe_helper::CommandStr; 8 | 9 | #[inline] 10 | pub fn command_can_connect_directly(ctx: &SockAddrContext) -> Result { 11 | let command_str = CommandStr::get_command()?; 12 | 13 | // default 0 mode 14 | let mode = COMM_MAP_MODE.get(0).copied().unwrap_or(0); 15 | 16 | let can_connect_directly = unsafe { 17 | match COMM_MAP.get(command_str.as_array()) { 18 | None => mode == 1, 19 | Some(_) => mode == 0, 20 | } 21 | }; 22 | 23 | if can_connect_directly { 24 | debug!(ctx, "command {} can connect directly", command_str); 25 | } else { 26 | debug!( 27 | ctx, 28 | "command {} can not connect directly, need next check step", command_str 29 | ); 30 | } 31 | 32 | Ok(can_connect_directly) 33 | } 34 | -------------------------------------------------------------------------------- /bpf/src/get_peer_name.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_long; 2 | use core::ptr::addr_of_mut; 3 | use core::{net, ptr, slice}; 4 | 5 | use aya_ebpf::helpers::bpf_sk_storage_get; 6 | use aya_ebpf::programs::SockAddrContext; 7 | use aya_log_ebpf::{debug, info}; 8 | 9 | use crate::kernel_binding::require::__socket_type::SOCK_STREAM; 10 | use crate::kernel_binding::require::{AF_INET, AF_INET6}; 11 | use crate::map::{CONNECT_DST_IPV4_ADDR_STORAGE, CONNECT_DST_IPV6_ADDR_STORAGE}; 12 | use crate::{Ipv4Addr, Ipv6Addr}; 13 | 14 | pub fn getpeername4(ctx: SockAddrContext) -> Result<(), c_long> { 15 | let sock_addr = unsafe { &mut *ctx.sock_addr }; 16 | 17 | if sock_addr.type_ != SOCK_STREAM && sock_addr.family != AF_INET { 18 | return Ok(()); 19 | } 20 | 21 | unsafe { 22 | let ptr = bpf_sk_storage_get( 23 | addr_of_mut!(CONNECT_DST_IPV4_ADDR_STORAGE) as _, 24 | sock_addr.__bindgen_anon_1.sk as _, 25 | ptr::null_mut(), 26 | 0, 27 | ); 28 | if ptr.is_null() { 29 | return Ok(()); 30 | } 31 | 32 | let ptr = ptr as *const Ipv4Addr; 33 | 34 | sock_addr.user_ip4 = u32::from_be_bytes((*ptr).addr).to_be(); 35 | sock_addr.user_port = (*ptr).port.to_be() as _; 36 | 37 | debug!( 38 | &ctx, 39 | "hook ipv4 getpeername done, origin dst addr {:i}:{}", 40 | u32::from_be_bytes((*ptr).addr), 41 | (*ptr).port 42 | ); 43 | } 44 | 45 | Ok(()) 46 | } 47 | 48 | pub fn getpeername6(ctx: SockAddrContext) -> Result<(), c_long> { 49 | let sock_addr = unsafe { &mut *ctx.sock_addr }; 50 | 51 | if sock_addr.type_ != SOCK_STREAM && sock_addr.family != AF_INET6 { 52 | return Ok(()); 53 | } 54 | 55 | unsafe { 56 | let ptr = bpf_sk_storage_get( 57 | addr_of_mut!(CONNECT_DST_IPV6_ADDR_STORAGE) as _, 58 | sock_addr.__bindgen_anon_1.sk as _, 59 | ptr::null_mut(), 60 | 0, 61 | ); 62 | if ptr.is_null() { 63 | return Ok(()); 64 | } 65 | 66 | let ptr = ptr as *const Ipv6Addr; 67 | let addr = slice::from_raw_parts((*ptr).addr.as_ptr() as *const u32, 4); 68 | 69 | match net::Ipv6Addr::from((*ptr).addr).to_ipv4_mapped() { 70 | None => { 71 | sock_addr.user_ip6[0] = addr[0]; 72 | sock_addr.user_ip6[1] = addr[1]; 73 | sock_addr.user_ip6[2] = addr[2]; 74 | sock_addr.user_ip6[3] = addr[3]; 75 | sock_addr.user_port = (*ptr).port.to_be() as _; 76 | } 77 | 78 | Some(mut ipv4) => { 79 | ipv4 = net::Ipv4Addr::from(u32::from_be_bytes(ipv4.octets())); 80 | 81 | info!( 82 | &ctx, 83 | "ipv6 addr {:i} is ipv4 mapped addr {:i}", 84 | (*ptr).addr, 85 | u32::from_be_bytes(ipv4.octets()), 86 | ); 87 | 88 | sock_addr.user_ip6[0] = addr[0]; 89 | sock_addr.user_ip6[1] = addr[1]; 90 | sock_addr.user_ip6[2] = addr[2]; 91 | sock_addr.user_ip6[3] = ipv4.to_bits().to_be(); 92 | } 93 | } 94 | 95 | debug!( 96 | &ctx, 97 | "hook ipv6 getpeername done, origin dst addr [{:i}]:{}", 98 | (*ptr).addr, 99 | (*ptr).port 100 | ); 101 | } 102 | 103 | Ok(()) 104 | } 105 | -------------------------------------------------------------------------------- /bpf/src/get_sock_name.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_long; 2 | use core::ptr::addr_of_mut; 3 | use core::{net, ptr, slice}; 4 | 5 | use aya_ebpf::helpers::bpf_sk_storage_get; 6 | use aya_ebpf::programs::SockAddrContext; 7 | use aya_log_ebpf::{debug, info}; 8 | 9 | use crate::kernel_binding::require::__socket_type::SOCK_STREAM; 10 | use crate::kernel_binding::require::{AF_INET, AF_INET6}; 11 | use crate::map::{PASSIVE_DST_IPV4_ADDR_STORAGE, PASSIVE_DST_IPV6_ADDR_STORAGE}; 12 | use crate::{Ipv4Addr, Ipv6Addr}; 13 | 14 | pub fn getsockname4(ctx: SockAddrContext) -> Result<(), c_long> { 15 | let sock_addr = unsafe { &mut *ctx.sock_addr }; 16 | 17 | if sock_addr.type_ != SOCK_STREAM && sock_addr.family != AF_INET { 18 | return Ok(()); 19 | } 20 | 21 | unsafe { 22 | let ptr = bpf_sk_storage_get( 23 | addr_of_mut!(PASSIVE_DST_IPV4_ADDR_STORAGE) as _, 24 | sock_addr.__bindgen_anon_1.sk as _, 25 | ptr::null_mut(), 26 | 0, 27 | ); 28 | if ptr.is_null() { 29 | return Ok(()); 30 | } 31 | 32 | let ptr = ptr as *const Ipv4Addr; 33 | 34 | sock_addr.user_ip4 = u32::from_be_bytes((*ptr).addr).to_be(); 35 | sock_addr.user_port = (*ptr).port.to_be() as _; 36 | 37 | debug!( 38 | &ctx, 39 | "hook ipv4 getsockname done, origin dst addr {:i}:{}", 40 | u32::from_be_bytes((*ptr).addr), 41 | (*ptr).port 42 | ); 43 | } 44 | 45 | Ok(()) 46 | } 47 | 48 | pub fn getsockname6(ctx: SockAddrContext) -> Result<(), c_long> { 49 | let sock_addr = unsafe { &mut *ctx.sock_addr }; 50 | 51 | if sock_addr.type_ != SOCK_STREAM && sock_addr.family != AF_INET6 { 52 | return Ok(()); 53 | } 54 | 55 | unsafe { 56 | let ptr = bpf_sk_storage_get( 57 | addr_of_mut!(PASSIVE_DST_IPV6_ADDR_STORAGE) as _, 58 | sock_addr.__bindgen_anon_1.sk as _, 59 | ptr::null_mut(), 60 | 0, 61 | ); 62 | if ptr.is_null() { 63 | return Ok(()); 64 | } 65 | 66 | let ptr = ptr as *const Ipv6Addr; 67 | let addr = slice::from_raw_parts((*ptr).addr.as_ptr() as *const u32, 4); 68 | 69 | match net::Ipv6Addr::from((*ptr).addr).to_ipv4_mapped() { 70 | None => { 71 | sock_addr.user_ip6[0] = addr[0]; 72 | sock_addr.user_ip6[1] = addr[1]; 73 | sock_addr.user_ip6[2] = addr[2]; 74 | sock_addr.user_ip6[3] = addr[3]; 75 | } 76 | 77 | Some(mut ipv4) => { 78 | ipv4 = net::Ipv4Addr::from(u32::from_be_bytes(ipv4.octets())); 79 | 80 | info!( 81 | &ctx, 82 | "ipv6 addr {:i} is ipv4 mapped addr {:i}", 83 | (*ptr).addr, 84 | u32::from_be_bytes(ipv4.octets()), 85 | ); 86 | 87 | sock_addr.user_ip6[0] = addr[0]; 88 | sock_addr.user_ip6[1] = addr[1]; 89 | sock_addr.user_ip6[2] = addr[2]; 90 | sock_addr.user_ip6[3] = ipv4.to_bits().to_be(); 91 | } 92 | } 93 | 94 | sock_addr.user_port = (*ptr).port.to_be() as _; 95 | 96 | debug!( 97 | &ctx, 98 | "hook ipv6 getsockname done, origin dst addr [{:i}]:{}", 99 | (*ptr).addr, 100 | (*ptr).port 101 | ); 102 | } 103 | 104 | Ok(()) 105 | } 106 | -------------------------------------------------------------------------------- /bpf/src/kernel_binding.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | #![allow(non_upper_case_globals)] 3 | #![allow(non_snake_case)] 4 | #![allow(unused_unsafe)] 5 | #![allow(clippy::all)] 6 | #![allow(unused)] 7 | 8 | pub mod require { 9 | include!(concat!(env!("OUT_DIR"), "/require.rs")); 10 | } 11 | -------------------------------------------------------------------------------- /bpf/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | use core::mem; 4 | 5 | pub use cgroup_connect4::handle_cgroup_connect4; 6 | pub use cgroup_connect6::handle_cgroup_connect6; 7 | pub use get_peer_name::{getpeername4, getpeername6}; 8 | pub use get_sock_name::{getsockname4, getsockname6}; 9 | pub use sockops_callback::handle_sockops; 10 | 11 | mod cgroup_connect4; 12 | mod cgroup_connect6; 13 | mod command_check; 14 | mod get_peer_name; 15 | mod get_sock_name; 16 | mod kernel_binding; 17 | mod map; 18 | mod safe_helper; 19 | mod sockops_callback; 20 | 21 | #[repr(C)] 22 | #[derive(Copy, Clone)] 23 | pub struct ConnectedIpv4Addr { 24 | /// sport is native order 25 | pub sport: u16, 26 | 27 | /// dport is native order 28 | pub dport: u16, 29 | 30 | /// saddr is network order 31 | pub saddr: [u8; 4], 32 | pub daddr: [u8; 4], 33 | } 34 | 35 | #[repr(C)] 36 | #[derive(Copy, Clone)] 37 | pub struct Ipv4Addr { 38 | /// addr is network order 39 | pub addr: [u8; 4], 40 | 41 | /// port is native order 42 | pub port: u16, 43 | pub _padding: [u8; 2], 44 | } 45 | 46 | impl PartialEq<([u8; 4], u16)> for Ipv4Addr { 47 | fn eq(&self, other: &([u8; 4], u16)) -> bool { 48 | self.addr == other.0 && self.port == other.1 49 | } 50 | } 51 | 52 | #[repr(C)] 53 | #[derive(Copy, Clone)] 54 | pub struct ConnectedIpv6Addr { 55 | /// sport is native order 56 | pub sport: u16, 57 | 58 | /// dport is native order 59 | pub dport: u16, 60 | 61 | pub saddr: [u8; 16], 62 | pub daddr: [u8; 16], 63 | } 64 | 65 | #[repr(C)] 66 | #[derive(Copy, Clone, Eq, PartialEq)] 67 | pub struct Ipv6Addr { 68 | /// addr is network order 69 | pub addr: [u8; 16], 70 | 71 | /// port is native order 72 | pub port: u16, 73 | } 74 | 75 | #[inline] 76 | fn connect_directly(in_list_connect_directly: bool, in_list: bool) -> bool { 77 | if in_list_connect_directly { 78 | in_list 79 | } else { 80 | !in_list 81 | } 82 | } 83 | 84 | #[inline] 85 | fn u16_ipv6_to_u8_ipv6(addr: [u16; 8]) -> [u8; 16] { 86 | unsafe { mem::transmute(addr) } 87 | } 88 | -------------------------------------------------------------------------------- /bpf/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use core::ffi::{c_int, c_uint}; 5 | 6 | use aya_ebpf::macros::{cgroup_sock_addr, sock_ops}; 7 | use aya_ebpf::programs::{SockAddrContext, SockOpsContext}; 8 | 9 | #[cgroup_sock_addr(connect4)] 10 | fn connect4(ctx: SockAddrContext) -> c_int { 11 | lycoris_bpf::handle_cgroup_connect4(ctx) 12 | .map(|_| 1) 13 | .unwrap_or_else(|_| 0) 14 | } 15 | 16 | #[cgroup_sock_addr(connect6)] 17 | fn connect6(ctx: SockAddrContext) -> c_int { 18 | lycoris_bpf::handle_cgroup_connect6(ctx) 19 | .map(|_| 1) 20 | .unwrap_or_else(|_| 0) 21 | } 22 | 23 | #[sock_ops] 24 | fn established_connect(ctx: SockOpsContext) -> c_uint { 25 | lycoris_bpf::handle_sockops(ctx) 26 | .map(|_| 1) 27 | .unwrap_or_else(|_| 0) 28 | } 29 | 30 | #[cgroup_sock_addr(getsockname4)] 31 | fn getsockname4(ctx: SockAddrContext) -> c_int { 32 | lycoris_bpf::getsockname4(ctx) 33 | .map(|_| 1) 34 | .unwrap_or_else(|err| err as _) 35 | } 36 | 37 | #[cgroup_sock_addr(getsockname6)] 38 | fn getsockname6(ctx: SockAddrContext) -> c_int { 39 | lycoris_bpf::getsockname6(ctx) 40 | .map(|_| 1) 41 | .unwrap_or_else(|err| err as _) 42 | } 43 | 44 | #[cgroup_sock_addr(getpeername4)] 45 | fn getpeername4(ctx: SockAddrContext) -> c_int { 46 | lycoris_bpf::getpeername4(ctx) 47 | .map(|_| 1) 48 | .unwrap_or_else(|err| err as _) 49 | } 50 | 51 | #[cgroup_sock_addr(getpeername6)] 52 | fn getpeername6(ctx: SockAddrContext) -> c_int { 53 | lycoris_bpf::getpeername6(ctx) 54 | .map(|_| 1) 55 | .unwrap_or_else(|err| err as _) 56 | } 57 | 58 | #[panic_handler] 59 | fn panic(_info: &core::panic::PanicInfo) -> ! { 60 | unsafe { core::hint::unreachable_unchecked() } 61 | } 62 | -------------------------------------------------------------------------------- /bpf/src/map.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_int; 2 | use core::ptr; 3 | 4 | use aya_ebpf::bindings::bpf_map_type::BPF_MAP_TYPE_SK_STORAGE; 5 | use aya_ebpf::bindings::BPF_F_NO_PREALLOC; 6 | use aya_ebpf::macros::map; 7 | use aya_ebpf::maps::{Array, HashMap, LpmTrie, LruHashMap}; 8 | 9 | use crate::{ConnectedIpv4Addr, ConnectedIpv6Addr, Ipv4Addr, Ipv6Addr}; 10 | 11 | /// key is ipv4 tcp 4 tuple, value is origin dst ipv4 addr 12 | #[map] 13 | pub static DST_IPV4_ADDR_STORE: LruHashMap = 14 | LruHashMap::with_max_entries(4096, 0); 15 | 16 | /// key is ipv6 tcp 4 tuple, value is origin dst ipv6 addr 17 | #[map] 18 | pub static DST_IPV6_ADDR_STORE: LruHashMap = 19 | LruHashMap::with_max_entries(4096, 0); 20 | 21 | /// key is need proxy ipv4 addr, value is u8 and it's a bool type 22 | #[map] 23 | pub static PROXY_IPV4_LIST: LpmTrie<[u8; 4], u8> = 24 | LpmTrie::with_max_entries(65535, BPF_F_NO_PREALLOC); 25 | 26 | /// key is need proxy ipv6 addr, value is u8 and it's a bool type 27 | #[map] 28 | pub static PROXY_IPV6_LIST: LpmTrie<[u16; 8], u8> = 29 | LpmTrie::with_max_entries(65535, BPF_F_NO_PREALLOC); 30 | 31 | /// proxy [`PROXY_IPV4_LIST`] and [`PROXY_IPV6_LIST`] list mode 32 | /// 33 | /// `0` means the dst ip in list will connect directly 34 | /// 35 | /// `1` means the dst ip in list will connect through proxy 36 | #[map] 37 | pub static PROXY_LIST_MODE: Array = Array::with_max_entries(1, 0); 38 | 39 | pub const CONNECT_DIRECTLY_MODE: u8 = 0; 40 | 41 | /// only has 1 element 42 | #[map] 43 | pub static PROXY_IPV4_CLIENT: Array = Array::with_max_entries(2, 0); 44 | 45 | /// only has 1 element 46 | #[map] 47 | pub static PROXY_IPV6_CLIENT: Array = Array::with_max_entries(2, 0); 48 | 49 | /// process comm map 50 | #[map] 51 | pub static COMM_MAP: HashMap<[u8; 16], u8> = HashMap::with_max_entries(1024, 0); 52 | 53 | /// [`COMM_MAP`] mode 54 | /// 55 | /// `0` means when comm in [`COMM_MAP`], connect directly 56 | /// 57 | /// `1` means when comm not in [`COMM_MAP`], connect directly 58 | #[map] 59 | pub static COMM_MAP_MODE: Array = Array::with_max_entries(1, 0); 60 | 61 | #[repr(C)] 62 | pub struct SkStore { 63 | r#type: *const [c_int; BPF_MAP_TYPE_SK_STORAGE as _], 64 | map_flags: *const [c_int; BPF_F_NO_PREALLOC as _], 65 | key: *const c_int, 66 | value: *const V, 67 | max_entries: *const [c_int; 0], 68 | } 69 | 70 | unsafe impl Sync for SkStore {} 71 | 72 | // can't use UnsafeCell or SyncUnsafeCell, otherwise will report error 73 | // ParseError(BtfError(UnexpectedBtfType { type_id: 1 })) 74 | #[link_section = ".maps"] 75 | #[export_name = "CONNECT_DST_IPV4_ADDR_STORAGE"] 76 | pub static mut CONNECT_DST_IPV4_ADDR_STORAGE: SkStore = SkStore { 77 | r#type: ptr::null(), 78 | map_flags: ptr::null(), 79 | key: ptr::null(), 80 | value: ptr::null(), 81 | max_entries: ptr::null(), 82 | }; 83 | 84 | #[link_section = ".maps"] 85 | #[export_name = "CONNECT_DST_IPV6_ADDR_STORAGE"] 86 | pub static mut CONNECT_DST_IPV6_ADDR_STORAGE: SkStore = SkStore { 87 | r#type: ptr::null(), 88 | map_flags: ptr::null(), 89 | key: ptr::null(), 90 | value: ptr::null(), 91 | max_entries: ptr::null(), 92 | }; 93 | 94 | #[link_section = ".maps"] 95 | #[export_name = "PASSIVE_DST_IPV4_ADDR_STORAGE"] 96 | pub static mut PASSIVE_DST_IPV4_ADDR_STORAGE: SkStore = SkStore { 97 | r#type: ptr::null(), 98 | map_flags: ptr::null(), 99 | key: ptr::null(), 100 | value: ptr::null(), 101 | max_entries: ptr::null(), 102 | }; 103 | 104 | #[link_section = ".maps"] 105 | #[export_name = "PASSIVE_DST_IPV6_ADDR_STORAGE"] 106 | pub static mut PASSIVE_DST_IPV6_ADDR_STORAGE: SkStore = SkStore { 107 | r#type: ptr::null(), 108 | map_flags: ptr::null(), 109 | key: ptr::null(), 110 | value: ptr::null(), 111 | max_entries: ptr::null(), 112 | }; 113 | -------------------------------------------------------------------------------- /bpf/src/safe_helper.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::{c_long, CStr}; 2 | use core::num::NonZeroUsize; 3 | use core::str::from_utf8_unchecked; 4 | 5 | use aya_ebpf::helpers::gen; 6 | use aya_log_ebpf::macro_support::DefaultFormatter; 7 | use aya_log_ebpf::WriteToBuf; 8 | use unroll::unroll_for_loops; 9 | 10 | pub struct CommandStr { 11 | buf: [u8; 16], 12 | index: usize, 13 | } 14 | 15 | impl CommandStr { 16 | #[inline] 17 | pub fn get_command() -> Result { 18 | let mut this = Self { 19 | buf: [0; 16], 20 | index: 0, 21 | }; 22 | 23 | let index = bpf_get_current_comm(&mut this.buf)?; 24 | this.index = index; 25 | 26 | Ok(this) 27 | } 28 | 29 | pub fn as_str(&self) -> &str { 30 | let command = unsafe { CStr::from_bytes_with_nul_unchecked(&self.buf[..self.index + 1]) }; 31 | 32 | unsafe { from_utf8_unchecked(command.to_bytes()) } 33 | } 34 | 35 | pub fn as_array(&self) -> &[u8; 16] { 36 | &self.buf 37 | } 38 | } 39 | 40 | impl WriteToBuf for CommandStr { 41 | fn write(self, buf: &mut [u8]) -> Option { 42 | self.as_str().write(buf) 43 | } 44 | } 45 | 46 | impl DefaultFormatter for CommandStr {} 47 | 48 | /// get current comm safe helper, return nul index 49 | fn bpf_get_current_comm(buf: &mut [u8; 16]) -> Result { 50 | unsafe { 51 | let res = gen::bpf_get_current_comm(buf.as_mut_ptr() as *mut _, buf.len() as _); 52 | if res < 0 { 53 | return Err(res); 54 | } 55 | } 56 | 57 | Ok(find_nul_index(buf)) 58 | } 59 | 60 | #[unroll_for_loops] 61 | fn find_nul_index(buf: &[u8; 16]) -> usize { 62 | for i in 0..16 { 63 | if buf[i] == 0 { 64 | return i; 65 | } 66 | } 67 | 68 | 16 - 1 69 | } 70 | -------------------------------------------------------------------------------- /bpf/src/sockops_callback.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_long; 2 | use core::ptr::addr_of_mut; 3 | use core::{mem, ptr}; 4 | 5 | use aya_ebpf::bindings::{ 6 | BPF_LOCAL_STORAGE_GET_F_CREATE, BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB, 7 | BPF_SOCK_OPS_TCP_CONNECT_CB, 8 | }; 9 | use aya_ebpf::helpers::*; 10 | use aya_ebpf::programs::SockOpsContext; 11 | use aya_log_ebpf::{debug, error}; 12 | 13 | use crate::kernel_binding::require::{AF_INET, AF_INET6}; 14 | use crate::map::*; 15 | use crate::{ConnectedIpv4Addr, ConnectedIpv6Addr, Ipv4Addr, Ipv6Addr}; 16 | 17 | /// when the socket is active established, get origin_dst_ipv4_addr by its cookie, if not exists, 18 | /// ignore it because this socket doesn't a need proxy socket 19 | /// 20 | /// when get the origin dst ipv4 addr, insert ((saddr, daddr), origin_dst_ipv4_addr) into the map 21 | /// so userspace can get the origin dst ipv4 addr 22 | pub fn handle_sockops(ctx: SockOpsContext) -> Result<(), c_long> { 23 | match ctx.op() { 24 | BPF_SOCK_OPS_TCP_CONNECT_CB => { 25 | if ctx.family() == AF_INET { 26 | handle_ipv4_connect(ctx) 27 | } else if ctx.family() == AF_INET6 { 28 | handle_ipv6_connect(ctx) 29 | } else { 30 | Ok(()) 31 | } 32 | } 33 | 34 | BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB => { 35 | if ctx.family() == AF_INET { 36 | handle_ipv4_accept(ctx) 37 | } else if ctx.family() == AF_INET6 { 38 | handle_ipv6_accept(ctx) 39 | } else { 40 | Ok(()) 41 | } 42 | } 43 | 44 | _ => Ok(()), 45 | } 46 | } 47 | 48 | fn handle_ipv4_connect(ctx: SockOpsContext) -> Result<(), c_long> { 49 | let dst_addr_ptr = unsafe { 50 | let bpf_sk = (*ctx.ops).__bindgen_anon_2.sk; 51 | if bpf_sk.is_null() { 52 | error!(&ctx, "bpf_sk is null"); 53 | 54 | return Err(0); 55 | } 56 | 57 | let ptr = bpf_sk_storage_get( 58 | addr_of_mut!(CONNECT_DST_IPV4_ADDR_STORAGE) as _, 59 | bpf_sk as _, 60 | ptr::null_mut(), 61 | 0, 62 | ); 63 | 64 | if ptr.is_null() { 65 | return Ok(()); 66 | } 67 | 68 | ptr as *mut Ipv4Addr 69 | }; 70 | 71 | let saddr = u32::from_be(ctx.local_ip4()).to_be_bytes(); 72 | let sport = ctx.local_port() as u16; 73 | let daddr = u32::from_be(ctx.remote_ip4()).to_be_bytes(); 74 | let dport = u32::from_be(ctx.remote_port()) as u16; 75 | 76 | let connected_addr = ConnectedIpv4Addr { 77 | sport, 78 | dport, 79 | saddr, 80 | daddr, 81 | }; 82 | 83 | let dst_addr = unsafe { &*dst_addr_ptr }; 84 | 85 | DST_IPV4_ADDR_STORE.insert(&connected_addr, dst_addr, 0) 86 | } 87 | 88 | fn handle_ipv6_connect(ctx: SockOpsContext) -> Result<(), c_long> { 89 | let dst_addr_ptr = unsafe { 90 | let bpf_sk = (*ctx.ops).__bindgen_anon_2.sk; 91 | if bpf_sk.is_null() { 92 | error!(&ctx, "bpf_sk is null"); 93 | 94 | return Err(0); 95 | } 96 | 97 | let ptr = bpf_sk_storage_get( 98 | addr_of_mut!(CONNECT_DST_IPV6_ADDR_STORAGE) as _, 99 | bpf_sk as _, 100 | ptr::null_mut(), 101 | 0, 102 | ); 103 | 104 | if ptr.is_null() { 105 | return Ok(()); 106 | } 107 | 108 | ptr as *mut Ipv6Addr 109 | }; 110 | 111 | let saddr = unsafe { 112 | mem::transmute::<[u32; 4], [u8; 16]>([ 113 | ctx.local_ip6()[0], 114 | ctx.local_ip6()[1], 115 | ctx.local_ip6()[2], 116 | ctx.local_ip6()[3], 117 | ]) 118 | }; 119 | let sport = ctx.local_port() as u16; 120 | let daddr = unsafe { 121 | mem::transmute::<[u32; 4], [u8; 16]>([ 122 | ctx.remote_ip6()[0], 123 | ctx.remote_ip6()[1], 124 | ctx.remote_ip6()[2], 125 | ctx.remote_ip6()[3], 126 | ]) 127 | }; 128 | let dport = u32::from_be(ctx.remote_port()) as u16; 129 | 130 | let connected_addr = ConnectedIpv6Addr { 131 | sport, 132 | dport, 133 | saddr, 134 | daddr, 135 | }; 136 | 137 | let dst_addr = unsafe { &*dst_addr_ptr }; 138 | 139 | DST_IPV6_ADDR_STORE.insert(&connected_addr, dst_addr, 0) 140 | } 141 | 142 | /// when the socket is active established, get origin_dst_ipv4_addr by its cookie, if not exists, 143 | /// ignore it because this socket doesn't a need proxy socket 144 | /// 145 | /// when get the origin dst ipv4 addr, insert ((saddr, daddr), origin_dst_ipv4_addr) into the map 146 | /// so userspace can get the origin dst ipv4 addr 147 | fn handle_ipv4_accept(ctx: SockOpsContext) -> Result<(), c_long> { 148 | let local_addr = u32::from_be(ctx.local_ip4()).to_be_bytes(); 149 | let local_port = ctx.local_port() as u16; 150 | let remote_addr = u32::from_be(ctx.remote_ip4()).to_be_bytes(); 151 | let remote_port = u32::from_be(ctx.remote_port()) as u16; 152 | 153 | debug!( 154 | &ctx, 155 | "local_addr {:i}:{} , remote_addr {:i}:{}", 156 | u32::from_be_bytes(local_addr), 157 | local_port, 158 | u32::from_be_bytes(remote_addr), 159 | remote_port, 160 | ); 161 | 162 | let connected_addr = ConnectedIpv4Addr { 163 | sport: remote_port, 164 | dport: local_port, 165 | saddr: remote_addr, 166 | daddr: local_addr, 167 | }; 168 | 169 | unsafe { 170 | let dst_addr = match DST_IPV4_ADDR_STORE.get(&connected_addr) { 171 | None => return Ok(()), 172 | Some(dst_addr) => *dst_addr, 173 | }; 174 | 175 | let _ = DST_IPV4_ADDR_STORE.remove(&connected_addr); 176 | 177 | let bpf_sk = (*ctx.ops).__bindgen_anon_2.sk; 178 | if bpf_sk.is_null() { 179 | error!(&ctx, "bpf_sk is null"); 180 | 181 | return Err(0); 182 | } 183 | 184 | let ptr = bpf_sk_storage_get( 185 | addr_of_mut!(PASSIVE_DST_IPV4_ADDR_STORAGE) as _, 186 | bpf_sk as _, 187 | ptr::null_mut(), 188 | BPF_LOCAL_STORAGE_GET_F_CREATE as _, 189 | ); 190 | if ptr.is_null() { 191 | error!(&ctx, "get dst ipv4 addr storage ptr failed"); 192 | 193 | return Err(0); 194 | } 195 | 196 | *(ptr as *mut Ipv4Addr) = dst_addr; 197 | } 198 | 199 | Ok(()) 200 | } 201 | 202 | /// when the socket is active established, get origin_dst_ipv6_addr by its cookie, if not exists, 203 | /// ignore it because this socket doesn't a need proxy socket 204 | /// 205 | /// when get the origin dst ipv6 addr, insert ((saddr, daddr), origin_dst_ipv6_addr) into the map 206 | /// so userspace can get the origin dst ipv6 addr 207 | fn handle_ipv6_accept(ctx: SockOpsContext) -> Result<(), c_long> { 208 | let local_addr = unsafe { 209 | mem::transmute::<[u32; 4], [u8; 16]>([ 210 | ctx.local_ip6()[0], 211 | ctx.local_ip6()[1], 212 | ctx.local_ip6()[2], 213 | ctx.local_ip6()[3], 214 | ]) 215 | }; 216 | let local_port = ctx.local_port() as u16; 217 | let remote_addr = unsafe { 218 | mem::transmute::<[u32; 4], [u8; 16]>([ 219 | ctx.remote_ip6()[0], 220 | ctx.remote_ip6()[1], 221 | ctx.remote_ip6()[2], 222 | ctx.remote_ip6()[3], 223 | ]) 224 | }; 225 | let remote_port = u32::from_be(ctx.remote_port()) as u16; 226 | 227 | debug!( 228 | &ctx, 229 | "local_addr [{:i}]:{} , remote_addr [{:i}]:{}", 230 | local_addr, 231 | local_port, 232 | remote_addr, 233 | remote_port, 234 | ); 235 | 236 | let connected_addr = ConnectedIpv6Addr { 237 | sport: remote_port, 238 | dport: local_port, 239 | saddr: remote_addr, 240 | daddr: local_addr, 241 | }; 242 | 243 | unsafe { 244 | let dst_addr = match DST_IPV6_ADDR_STORE.get(&connected_addr) { 245 | None => return Ok(()), 246 | Some(dst_addr) => *dst_addr, 247 | }; 248 | 249 | let _ = DST_IPV6_ADDR_STORE.remove(&connected_addr); 250 | 251 | let bpf_sk = (*ctx.ops).__bindgen_anon_2.sk; 252 | if bpf_sk.is_null() { 253 | error!(&ctx, "bpf_sk is null"); 254 | 255 | return Err(0); 256 | } 257 | 258 | let ptr = bpf_sk_storage_get( 259 | addr_of_mut!(PASSIVE_DST_IPV6_ADDR_STORAGE) as _, 260 | bpf_sk as _, 261 | ptr::null_mut(), 262 | BPF_LOCAL_STORAGE_GET_F_CREATE as _, 263 | ); 264 | if ptr.is_null() { 265 | error!(&ctx, "get dst ipv6 addr storage ptr failed"); 266 | 267 | return Err(0); 268 | } 269 | 270 | *(ptr as *mut Ipv6Addr) = dst_addr; 271 | } 272 | 273 | Ok(()) 274 | } 275 | -------------------------------------------------------------------------------- /client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lycoris-client" 3 | version = "0.4.0" 4 | edition = "2024" 5 | license = "MIT" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | anyhow = { workspace = true } 11 | bytes = { workspace = true } 12 | cidr = { workspace = true } 13 | clap = { workspace = true, features = ["derive", "color", "suggestions"] } 14 | hickory-resolver = { workspace = true, features = ["default", "system-config"] } 15 | http = { workspace = true } 16 | hyper = { workspace = true, features = ["http2", "client"] } 17 | hyper-util = { workspace = true, features = ["client-legacy", "http2", "tokio"] } 18 | http-body-util = { workspace = true } 19 | futures-channel = { workspace = true, features = ["sink"] } 20 | futures-rustls = { workspace = true } 21 | futures-util = { workspace = true, features = ["io"] } 22 | libc = { workspace = true } 23 | rustls-native-certs = { workspace = true } 24 | rustls-pemfile = { workspace = true } 25 | serde = { workspace = true, features = ["derive"] } 26 | serde_yaml = { workspace = true } 27 | socket2 = { workspace = true } 28 | tap = { workspace = true } 29 | tokio = { workspace = true, features = ["macros", "rt-multi-thread", "net", "io-util", "fs", "sync", "time"] } 30 | tokio-stream = { workspace = true, features = ["time", "io-util", "net"] } 31 | tokio-util = { workspace = true, features = ["compat", "io"] } 32 | tracing = { workspace = true } 33 | trait-make = { workspace = true } 34 | 35 | protocol = { path = "../protocol" } 36 | share = { path = "../share" } 37 | 38 | # bpf 39 | aya = { version = "0.12", features = ["async_tokio"] } 40 | aya-log = "0.2" 41 | tracing-log = "0.2" 42 | 43 | [dev-dependencies] 44 | rustix = { version = "0.38", features = ["process"] } 45 | h2 = { version = "0.4", features = ["stream"] } 46 | -------------------------------------------------------------------------------- /client/example.yaml: -------------------------------------------------------------------------------- 1 | listen_addr: 127.0.0.1:1980 2 | listen_addr_v6: '[::1]:1980' 3 | container_bridge_listen_addr: null 4 | container_bridge_listen_addr_v6: null 5 | remote_domain: example.com 6 | remote_port: 443 7 | ca_cert: example/ca.cert 8 | token_secret: test 9 | token_header: x-token 10 | cgroup_path: /sys/fs/cgroup 11 | ip_in_list_directly: true 12 | ip_list: 13 | - file1 14 | - file2 15 | -------------------------------------------------------------------------------- /client/src/args.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use clap::Parser; 4 | 5 | #[derive(Debug, Parser)] 6 | pub struct Args { 7 | /// config path 8 | #[clap(short, long)] 9 | pub config: PathBuf, 10 | 11 | /// bpf elf path 12 | #[clap(short, long)] 13 | pub bpf_elf: PathBuf, 14 | 15 | /// debug log 16 | #[clap(short, long, action)] 17 | pub debug: bool, 18 | } 19 | -------------------------------------------------------------------------------- /client/src/bpf_map_name.rs: -------------------------------------------------------------------------------- 1 | pub const PROXY_IPV4_LIST: &str = "PROXY_IPV4_LIST"; 2 | pub const PROXY_IPV6_LIST: &str = "PROXY_IPV6_LIST"; 3 | 4 | pub const PROXY_LIST_MODE: &str = "PROXY_LIST_MODE"; 5 | pub const PROXY_IPV4_CLIENT: &str = "PROXY_IPV4_CLIENT"; 6 | pub const PROXY_IPV6_CLIENT: &str = "PROXY_IPV6_CLIENT"; 7 | 8 | pub const COMM_MAP: &str = "COMM_MAP"; 9 | pub const COMM_MAP_MODE: &str = "COMM_MAP_MODE"; 10 | -------------------------------------------------------------------------------- /client/src/bpf_share.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::Display; 2 | use core::fmt::Formatter; 3 | use core::net::IpAddr; 4 | use core::net::{self, SocketAddr}; 5 | 6 | use aya::Pod; 7 | use share::helper::ArrayExt; 8 | 9 | #[repr(C)] 10 | #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] 11 | pub struct ConnectedIpv4Addr { 12 | /// sport is native order 13 | pub sport: u16, 14 | 15 | /// dport is native order 16 | pub dport: u16, 17 | 18 | /// saddr is network order 19 | pub saddr: [u8; 4], 20 | pub daddr: [u8; 4], 21 | } 22 | 23 | unsafe impl Pod for ConnectedIpv4Addr {} 24 | 25 | #[repr(C)] 26 | #[derive(Copy, Clone, Debug)] 27 | pub struct Ipv4Addr { 28 | /// addr is network order 29 | pub addr: [u8; 4], 30 | 31 | /// port is native order 32 | pub port: u16, 33 | pub _padding: [u8; 2], 34 | } 35 | 36 | unsafe impl Pod for Ipv4Addr {} 37 | 38 | #[repr(C)] 39 | #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] 40 | pub struct ConnectedIpv6Addr { 41 | /// sport is native order 42 | pub sport: u16, 43 | 44 | /// dport is native order 45 | pub dport: u16, 46 | 47 | pub saddr: [u8; 16], 48 | pub daddr: [u8; 16], 49 | } 50 | 51 | impl Display for ConnectedIpv6Addr { 52 | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 53 | f.debug_struct("ConnectedIpv6Addr") 54 | .field( 55 | "saddr:sport", 56 | &SocketAddr::new( 57 | IpAddr::V6(net::Ipv6Addr::from(self.saddr.swap_bytes())), 58 | self.sport, 59 | ), 60 | ) 61 | .field( 62 | "daddr:dport", 63 | &SocketAddr::new( 64 | IpAddr::V6(net::Ipv6Addr::from(self.daddr.swap_bytes())), 65 | self.dport, 66 | ), 67 | ) 68 | .finish() 69 | } 70 | } 71 | 72 | unsafe impl Pod for ConnectedIpv6Addr {} 73 | 74 | #[repr(C)] 75 | #[derive(Copy, Clone, Debug)] 76 | pub struct Ipv6Addr { 77 | /// addr is network order 78 | pub addr: [u8; 16], 79 | 80 | /// port is native order 81 | pub port: u16, 82 | } 83 | 84 | unsafe impl Pod for Ipv6Addr {} 85 | -------------------------------------------------------------------------------- /client/src/client.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use std::time::Duration; 3 | 4 | use anyhow::Context; 5 | use protocol::DomainOrSocketAddr; 6 | use share::proxy; 7 | use tokio::time; 8 | use tracing::{info, instrument}; 9 | 10 | use crate::connect::Connect; 11 | use crate::listener::{Listener, Split}; 12 | 13 | pub struct Client { 14 | connector: Arc, 15 | listener: L, 16 | } 17 | 18 | impl Client { 19 | pub fn new(connector: C, listener: L) -> Self { 20 | Self { 21 | connector: Arc::new(connector), 22 | listener, 23 | } 24 | } 25 | } 26 | 27 | impl Client 28 | where 29 | C: Connect + Send + Sync + 'static, 30 | L: Listener + Send, 31 | L::Stream: Send + 'static, 32 | { 33 | pub async fn start(&mut self) -> anyhow::Result<()> { 34 | loop { 35 | let (tcp_stream, addr) = if let Ok(result) = self.listener.accept().await { 36 | result 37 | } else { 38 | continue; 39 | }; 40 | 41 | info!(?addr, "accept new tcp stream"); 42 | 43 | let connector = self.connector.clone(); 44 | 45 | tokio::spawn(handle_proxy(connector, addr, tcp_stream)); 46 | } 47 | } 48 | } 49 | 50 | #[instrument(skip(connector, tcp_stream), err(Debug))] 51 | async fn handle_proxy( 52 | connector: Arc, 53 | addr: DomainOrSocketAddr, 54 | tcp_stream: S, 55 | ) -> anyhow::Result<()> { 56 | const CONNECT_TIMEOUT: Duration = Duration::from_secs(10); 57 | 58 | let (read, write) = time::timeout(CONNECT_TIMEOUT, connector.connect(addr)) 59 | .await 60 | .with_context(|| "connect timeout")??; 61 | 62 | info!("connect done"); 63 | 64 | let (tcp_read, tcp_write) = tcp_stream.into_split(); 65 | 66 | proxy::proxy(read, write, tcp_read, tcp_write).await?; 67 | 68 | Ok(()) 69 | } 70 | -------------------------------------------------------------------------------- /client/src/config.rs: -------------------------------------------------------------------------------- 1 | use std::net::{SocketAddrV4, SocketAddrV6}; 2 | use std::path::PathBuf; 3 | 4 | use serde::Deserialize; 5 | 6 | #[derive(Debug, Deserialize)] 7 | pub struct Config { 8 | pub listen_addr: SocketAddrV4, 9 | pub listen_addr_v6: SocketAddrV6, 10 | pub container_bridge_listen_addr: Option, 11 | pub container_bridge_listen_addr_v6: Option, 12 | pub remote_domain: String, 13 | pub remote_port: Option, 14 | pub ca_cert: Option, 15 | pub token_secret: String, 16 | pub token_header: String, 17 | pub cgroup_path: PathBuf, 18 | #[serde(default = "default_ip_in_list_directly")] 19 | pub ip_in_list_directly: bool, 20 | #[serde(default)] 21 | pub command_list: Vec, 22 | #[serde(default = "default_command_in_list_directly")] 23 | pub command_in_list_directly: bool, 24 | #[serde(default)] 25 | pub ip_list: Vec, 26 | } 27 | 28 | impl Config { 29 | pub fn check(&self) -> anyhow::Result<()> { 30 | for command in &self.command_list { 31 | if command.len() > 15 { 32 | return Err(anyhow::anyhow!("command {command} length > 15")); 33 | } 34 | } 35 | 36 | Ok(()) 37 | } 38 | } 39 | 40 | const fn default_command_in_list_directly() -> bool { 41 | true 42 | } 43 | 44 | const fn default_ip_in_list_directly() -> bool { 45 | true 46 | } 47 | -------------------------------------------------------------------------------- /client/src/connect/hyper.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::net::SocketAddr; 3 | 4 | use futures_util::Stream; 5 | use hyper_util::rt::{TokioExecutor, TokioTimer}; 6 | use protocol::auth::Auth; 7 | use protocol::connect::TcpConnector; 8 | use protocol::{DomainOrSocketAddr, HyperConnectorConfig}; 9 | use share::dns::HickoryDnsResolver; 10 | use share::tcp_wrapper::TokioTcp; 11 | use tokio::net::TcpStream; 12 | 13 | use super::Connect; 14 | use crate::mptcp::MptcpExt; 15 | 16 | #[derive(Debug, Default, Clone)] 17 | struct TokioConnector; 18 | 19 | impl TcpConnector for TokioConnector { 20 | type ConnectedTcpStream = TokioTcp; 21 | 22 | async fn connect> + Send>( 23 | &mut self, 24 | addrs: S, 25 | ) -> io::Result { 26 | TcpStream::connect_mptcp(addrs).await.map(Into::into) 27 | } 28 | } 29 | 30 | #[derive(Clone)] 31 | pub struct HyperConnector { 32 | protocol_connector: protocol::HyperConnector, 33 | } 34 | 35 | impl HyperConnector { 36 | pub fn new( 37 | tls_client_config: protocol::connect::ClientConfig, 38 | remote_domain: String, 39 | remote_port: u16, 40 | token_header: String, 41 | auth: Auth, 42 | ) -> anyhow::Result { 43 | Ok(Self { 44 | protocol_connector: protocol::HyperConnector::new(HyperConnectorConfig { 45 | tls_client_config, 46 | remote_domain, 47 | remote_port, 48 | auth, 49 | token_header, 50 | dns_resolver: HickoryDnsResolver::new()?, 51 | tcp_connector: TokioConnector, 52 | executor: TokioExecutor::new(), 53 | timer: TokioTimer::new(), 54 | })?, 55 | }) 56 | } 57 | } 58 | 59 | impl Connect for HyperConnector { 60 | type Read = impl tokio::io::AsyncRead + Unpin + Send + 'static; 61 | type Write = impl tokio::io::AsyncWrite + Unpin + Send + 'static; 62 | 63 | async fn connect(&self, addr: DomainOrSocketAddr) -> anyhow::Result<(Self::Read, Self::Write)> { 64 | let rw = self.protocol_connector.connect(addr).await?; 65 | 66 | Ok(rw) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /client/src/connect/mod.rs: -------------------------------------------------------------------------------- 1 | use protocol::DomainOrSocketAddr; 2 | use tokio::io::{AsyncRead, AsyncWrite}; 3 | 4 | pub mod hyper; 5 | 6 | #[trait_make::make(Send)] 7 | pub trait Connect { 8 | type Read: AsyncRead + Unpin + Send + 'static; 9 | type Write: AsyncWrite + Unpin + Send + 'static; 10 | 11 | async fn connect(&self, addr: DomainOrSocketAddr) -> anyhow::Result<(Self::Read, Self::Write)>; 12 | } 13 | -------------------------------------------------------------------------------- /client/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(impl_trait_in_assoc_type, gen_blocks, async_iterator)] 2 | 3 | use std::ffi::CString; 4 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6}; 5 | use std::path::Path; 6 | use std::str::FromStr; 7 | 8 | use aya::maps::Array; 9 | use aya::maps::lpm_trie::{Key, LpmTrie}; 10 | use aya::programs::cgroup_sock_addr::CgroupSockAddrLink; 11 | use aya::programs::{CgroupSockAddr, Link, SockOps}; 12 | use aya::{Bpf, BpfLoader, maps}; 13 | use aya_log::BpfLogger; 14 | use cidr::{Ipv4Inet, Ipv6Inet}; 15 | use clap::Parser; 16 | use futures_rustls::rustls::{ClientConfig, RootCertStore}; 17 | use futures_util::{StreamExt, future}; 18 | use hickory_resolver::AsyncResolver; 19 | use hickory_resolver::error::ResolveErrorKind; 20 | use protocol::auth::Auth; 21 | use share::log::init_log; 22 | use tokio::fs::{self, File}; 23 | use tokio::io::{AsyncBufReadExt, BufReader}; 24 | use tokio_stream::wrappers::LinesStream; 25 | use tracing::{info, warn}; 26 | use tracing_log::LogTracer; 27 | 28 | use self::bpf_map_name::*; 29 | use crate::args::Args; 30 | use crate::bpf_share::{Ipv4Addr as ShareIpv4Addr, Ipv6Addr as ShareIpv6Addr}; 31 | pub use crate::client::Client; 32 | use crate::config::Config; 33 | #[doc(hidden)] 34 | pub use crate::connect::hyper::HyperConnector; 35 | #[doc(hidden)] 36 | pub use crate::listener::Listener; 37 | #[doc(hidden)] 38 | pub use crate::listener::bpf::BpfListener; 39 | pub use crate::owned_link::OwnedLink; 40 | 41 | mod args; 42 | pub mod bpf_map_name; 43 | pub mod bpf_share; 44 | mod client; 45 | mod config; 46 | mod connect; 47 | mod listener; 48 | mod mptcp; 49 | mod owned_link; 50 | 51 | pub async fn run() -> anyhow::Result<()> { 52 | let args = Args::parse(); 53 | 54 | init_log(args.debug); 55 | 56 | let config = fs::read(&args.config).await?; 57 | let config = serde_yaml::from_slice::(&config)?; 58 | config.check()?; 59 | 60 | info!(?config, "load config done"); 61 | 62 | run_bpf(args, config).await 63 | } 64 | 65 | async fn run_bpf(args: Args, config: Config) -> anyhow::Result<()> { 66 | let remote_domain_ips = get_remote_domain_ips(&config.remote_domain).await?; 67 | 68 | info!(?remote_domain_ips, "get remote domain ip done"); 69 | 70 | let mut bpf = BpfLoader::new() 71 | // allow bpf elf contains sk_storage bpf map 72 | .allow_unsupported_maps() 73 | .load_file(&args.bpf_elf)?; 74 | 75 | info!("load bpf done"); 76 | 77 | init_bpf_log(&mut bpf); 78 | 79 | set_proxy_addr( 80 | &mut bpf, 81 | config.listen_addr, 82 | config.listen_addr_v6, 83 | config.container_bridge_listen_addr, 84 | config.container_bridge_listen_addr_v6, 85 | )?; 86 | 87 | info!(listen_addr = %config.listen_addr, "set proxy addr done"); 88 | 89 | set_proxy_ip_list(&mut bpf, config.ip_list.iter().map(|path| path.as_path())).await?; 90 | 91 | info!("set target ip done"); 92 | 93 | set_proxy_ip_list_mode(&mut bpf, config.ip_in_list_directly)?; 94 | set_command_list( 95 | &mut bpf, 96 | config.command_list, 97 | config.command_in_list_directly, 98 | )?; 99 | 100 | if config.ip_in_list_directly { 101 | append_remote_ip_list(&mut bpf, &remote_domain_ips)?; 102 | } 103 | 104 | info!( 105 | ip_in_list_directly = config.ip_in_list_directly, 106 | "set proxy ip list mode done" 107 | ); 108 | 109 | let _connect4_link = load_connect4(&mut bpf, &config.cgroup_path).await?; 110 | 111 | info!("load connect4 done"); 112 | 113 | let _connect6_link = load_connect6(&mut bpf, &config.cgroup_path).await?; 114 | 115 | let _getsockname4 = load_getsockname4(&mut bpf, &config.cgroup_path).await?; 116 | let _getsockname6 = load_getsockname6(&mut bpf, &config.cgroup_path).await?; 117 | 118 | let _getpeername4 = load_getpeername4(&mut bpf, &config.cgroup_path).await?; 119 | let _getpeername6 = load_getpeername6(&mut bpf, &config.cgroup_path).await?; 120 | 121 | let _sockops_link = load_sockops(&mut bpf, &config.cgroup_path).await?; 122 | 123 | info!("load sockops done"); 124 | 125 | let bpf_listener = load_listener( 126 | config.listen_addr, 127 | config.listen_addr_v6, 128 | config.container_bridge_listen_addr, 129 | config.container_bridge_listen_addr_v6, 130 | ) 131 | .await?; 132 | 133 | info!("load listener done"); 134 | 135 | let connector = load_connector( 136 | config.remote_domain, 137 | config.remote_port.unwrap_or(443), 138 | config.ca_cert.as_deref(), 139 | config.token_secret, 140 | config.token_header, 141 | ) 142 | .await?; 143 | 144 | info!("load connector done"); 145 | 146 | let mut client = Client::new(connector, bpf_listener); 147 | 148 | client.start().await 149 | } 150 | 151 | async fn load_connect4( 152 | bpf: &mut Bpf, 153 | cgroup_path: &Path, 154 | ) -> anyhow::Result> { 155 | let cgroup_file = File::open(cgroup_path).await?; 156 | 157 | let connect4_prog: &mut CgroupSockAddr = bpf 158 | .program_mut("connect4") 159 | .expect("bpf connect4 not found") 160 | .try_into()?; 161 | 162 | connect4_prog.load()?; 163 | 164 | info!("load connect4 done"); 165 | 166 | let connect4_link_id = connect4_prog.attach(cgroup_file)?; 167 | 168 | info!(?cgroup_path, "attach cgroup done"); 169 | 170 | Ok(connect4_prog.take_link(connect4_link_id)?.into()) 171 | } 172 | 173 | async fn load_connect6( 174 | bpf: &mut Bpf, 175 | cgroup_path: &Path, 176 | ) -> anyhow::Result> { 177 | let cgroup_file = File::open(cgroup_path).await?; 178 | 179 | let connect6_prog: &mut CgroupSockAddr = bpf 180 | .program_mut("connect6") 181 | .expect("bpf connect6 not found") 182 | .try_into()?; 183 | 184 | connect6_prog.load()?; 185 | 186 | info!("load connect6 done"); 187 | 188 | let connect6_link_id = connect6_prog.attach(cgroup_file)?; 189 | 190 | info!(?cgroup_path, "attach cgroup done"); 191 | 192 | Ok(connect6_prog.take_link(connect6_link_id)?.into()) 193 | } 194 | 195 | async fn load_getsockname4( 196 | bpf: &mut Bpf, 197 | cgroup_path: &Path, 198 | ) -> anyhow::Result> { 199 | let cgroup_file = File::open(cgroup_path).await?; 200 | 201 | let prog: &mut CgroupSockAddr = bpf 202 | .program_mut("getsockname4") 203 | .expect("bpf getsockname4 not found") 204 | .try_into()?; 205 | 206 | prog.load()?; 207 | 208 | info!("load getsockname4 done"); 209 | 210 | let link_id = prog.attach(cgroup_file)?; 211 | 212 | info!(?cgroup_path, "attach cgroup done"); 213 | 214 | Ok(prog.take_link(link_id)?.into()) 215 | } 216 | 217 | async fn load_getsockname6( 218 | bpf: &mut Bpf, 219 | cgroup_path: &Path, 220 | ) -> anyhow::Result> { 221 | let cgroup_file = File::open(cgroup_path).await?; 222 | 223 | let prog: &mut CgroupSockAddr = bpf 224 | .program_mut("getsockname6") 225 | .expect("bpf getsockname4 not found") 226 | .try_into()?; 227 | 228 | prog.load()?; 229 | 230 | info!("load getsockname6 done"); 231 | 232 | let link_id = prog.attach(cgroup_file)?; 233 | 234 | info!(?cgroup_path, "attach cgroup done"); 235 | 236 | Ok(prog.take_link(link_id)?.into()) 237 | } 238 | 239 | async fn load_getpeername4( 240 | bpf: &mut Bpf, 241 | cgroup_path: &Path, 242 | ) -> anyhow::Result> { 243 | let cgroup_file = File::open(cgroup_path).await?; 244 | 245 | let prog: &mut CgroupSockAddr = bpf 246 | .program_mut("getpeername4") 247 | .expect("bpf getpeername4 not found") 248 | .try_into()?; 249 | 250 | prog.load()?; 251 | 252 | info!("load getpeername4 done"); 253 | 254 | let link_id = prog.attach(cgroup_file)?; 255 | 256 | info!(?cgroup_path, "attach cgroup done"); 257 | 258 | Ok(prog.take_link(link_id)?.into()) 259 | } 260 | 261 | async fn load_getpeername6( 262 | bpf: &mut Bpf, 263 | cgroup_path: &Path, 264 | ) -> anyhow::Result> { 265 | let cgroup_file = File::open(cgroup_path).await?; 266 | 267 | let prog: &mut CgroupSockAddr = bpf 268 | .program_mut("getpeername6") 269 | .expect("bpf getpeername6 not found") 270 | .try_into()?; 271 | 272 | prog.load()?; 273 | 274 | info!("load getpeername6 done"); 275 | 276 | let link_id = prog.attach(cgroup_file)?; 277 | 278 | info!(?cgroup_path, "attach cgroup done"); 279 | 280 | Ok(prog.take_link(link_id)?.into()) 281 | } 282 | 283 | // return OwnedLink because the SockOpsLink is un-exported 284 | async fn load_sockops(bpf: &mut Bpf, cgroup_path: &Path) -> anyhow::Result> { 285 | let cgroup_file = File::open(cgroup_path).await?; 286 | 287 | let prog: &mut SockOps = bpf 288 | .program_mut("established_connect") 289 | .expect("bpf established_connect not found") 290 | .try_into()?; 291 | 292 | prog.load()?; 293 | 294 | info!("loaded established_connect done"); 295 | 296 | let link_id = prog.attach(cgroup_file).unwrap(); 297 | 298 | info!("attach established_connect done"); 299 | 300 | Ok(OwnedLink::from(prog.take_link(link_id)?)) 301 | } 302 | 303 | fn set_proxy_addr( 304 | bpf: &mut Bpf, 305 | mut addr: SocketAddrV4, 306 | mut addr_v6: SocketAddrV6, 307 | container_bridge_listen_addr: Option, 308 | container_bridge_listen_addr_v6: Option, 309 | ) -> anyhow::Result<()> { 310 | let mut v4_proxy_server: Array<_, ShareIpv4Addr> = bpf 311 | .map_mut(PROXY_IPV4_CLIENT) 312 | .expect("PROXY_IPV4_CLIENT bpf array not found") 313 | .try_into()?; 314 | 315 | if *addr.ip() == Ipv4Addr::new(0, 0, 0, 0) { 316 | // when set 0.0.0.0, we need bpf use 127.0.0.1 to connect local 317 | addr.set_ip(Ipv4Addr::LOCALHOST); 318 | } 319 | 320 | let proxy_addr = ShareIpv4Addr { 321 | addr: addr.ip().octets(), 322 | port: addr.port(), 323 | _padding: [0; 2], 324 | }; 325 | 326 | v4_proxy_server.set(0, proxy_addr, 0)?; 327 | 328 | if let Some(addr) = container_bridge_listen_addr { 329 | let proxy_addr = ShareIpv4Addr { 330 | addr: addr.ip().octets(), 331 | port: addr.port(), 332 | _padding: [0; 2], 333 | }; 334 | 335 | v4_proxy_server.set(1, proxy_addr, 0)?; 336 | } 337 | 338 | let mut v6_proxy_server: Array<_, ShareIpv6Addr> = bpf 339 | .map_mut(PROXY_IPV6_CLIENT) 340 | .expect("PROXY_IPV6_CLIENT bpf array not found") 341 | .try_into()?; 342 | 343 | if *addr_v6.ip() == Ipv6Addr::from([0, 0, 0, 0, 0, 0, 0, 0]) { 344 | // when set 0.0.0.0, we need bpf use ::1 to connect local 345 | addr_v6.set_ip(Ipv6Addr::LOCALHOST); 346 | } 347 | 348 | let proxy_addr = ShareIpv6Addr { 349 | addr: addr_v6.ip().to_bits().to_be_bytes(), 350 | port: addr_v6.port(), 351 | }; 352 | 353 | v6_proxy_server.set(0, proxy_addr, 0)?; 354 | 355 | if let Some(addr_v6) = container_bridge_listen_addr_v6 { 356 | let proxy_addr = ShareIpv6Addr { 357 | addr: addr_v6.ip().to_bits().to_be_bytes(), 358 | port: addr_v6.port(), 359 | }; 360 | 361 | v6_proxy_server.set(0, proxy_addr, 0)?; 362 | } 363 | 364 | Ok(()) 365 | } 366 | 367 | async fn load_listener( 368 | listen_addr: SocketAddrV4, 369 | listen_addr_v6: SocketAddrV6, 370 | container_bridge_listen_addr: Option, 371 | container_bridge_listen_addr_v6: Option, 372 | ) -> anyhow::Result { 373 | BpfListener::new( 374 | listen_addr, 375 | listen_addr_v6, 376 | container_bridge_listen_addr, 377 | container_bridge_listen_addr_v6, 378 | ) 379 | .await 380 | } 381 | 382 | async fn load_connector( 383 | remote_domain: String, 384 | remote_port: u16, 385 | ca_cert: Option<&Path>, 386 | token_secret: String, 387 | token_header: String, 388 | ) -> anyhow::Result { 389 | let mut root_cert_store = RootCertStore::empty(); 390 | for cert in rustls_native_certs::load_native_certs()? { 391 | root_cert_store.add(cert)?; 392 | } 393 | 394 | if let Some(ca_cert) = ca_cert { 395 | let ca_cert = fs::read(ca_cert).await?; 396 | 397 | for cert in rustls_pemfile::certs(&mut ca_cert.as_slice()) { 398 | let cert = cert?; 399 | root_cert_store.add(cert)?; 400 | } 401 | } 402 | 403 | let client_config = ClientConfig::builder() 404 | .with_root_certificates(root_cert_store) 405 | .with_no_client_auth(); 406 | let auth = Auth::new(token_secret, None)?; 407 | 408 | HyperConnector::new( 409 | client_config, 410 | remote_domain, 411 | remote_port, 412 | token_header, 413 | auth, 414 | ) 415 | } 416 | 417 | async fn set_proxy_ip_list<'a, I: Iterator>( 418 | bpf: &mut Bpf, 419 | ip_list_paths: I, 420 | ) -> anyhow::Result<()> { 421 | for ip_list_path in ip_list_paths { 422 | let ip_list = File::open(ip_list_path).await?; 423 | let mut reader = LinesStream::new(BufReader::new(ip_list).lines()); 424 | 425 | while let Some(result) = reader.next().await { 426 | let line = result?; 427 | let line = line.trim(); 428 | if line.is_empty() || line.starts_with('#') { 429 | continue; 430 | } 431 | 432 | match Ipv4Inet::from_str(line) { 433 | Err(v4_err) => match Ipv6Inet::from_str(line) { 434 | Err(v6_err) => { 435 | warn!( 436 | %v4_err, 437 | %v6_err, 438 | ip_cidr = %line, 439 | "ip cidr is not ipv4 cidr or ipv6 cidr, ignore" 440 | ); 441 | 442 | continue; 443 | } 444 | 445 | Ok(ipv6_net) => { 446 | let mut proxy_ipv6_list: LpmTrie<_, [u8; 16], u8> = bpf 447 | .map_mut(PROXY_IPV6_LIST) 448 | .expect("PROXY_IPV6_LIST not found") 449 | .try_into()?; 450 | 451 | proxy_ipv6_list.insert( 452 | &Key::new( 453 | ipv6_net.network_length() as _, 454 | ipv6_net.first_address().to_bits().to_be_bytes(), 455 | ), 456 | 1, 457 | 0, 458 | )?; 459 | } 460 | }, 461 | 462 | Ok(ipv4_inet) => { 463 | let mut proxy_ipv4_list: LpmTrie<_, [u8; 4], u8> = bpf 464 | .map_mut(PROXY_IPV4_LIST) 465 | .expect("PROXY_IPV4_LIST not found") 466 | .try_into()?; 467 | 468 | proxy_ipv4_list.insert( 469 | &Key::new( 470 | ipv4_inet.network_length() as _, 471 | ipv4_inet.first_address().octets(), 472 | ), 473 | 1, 474 | 0, 475 | )?; 476 | } 477 | } 478 | } 479 | } 480 | 481 | Ok(()) 482 | } 483 | 484 | fn append_remote_ip_list(bpf: &mut Bpf, remote_domain_ip: &[IpAddr]) -> anyhow::Result<()> { 485 | let mut proxy_ipv4_list: LpmTrie<_, [u8; 4], u8> = bpf 486 | .map_mut(PROXY_IPV4_LIST) 487 | .expect("PROXY_IPV4_LIST not found") 488 | .try_into()?; 489 | 490 | for ipv4_addr in remote_domain_ip.iter().filter_map(|addr| { 491 | if let IpAddr::V4(addr) = addr { 492 | Some(addr) 493 | } else { 494 | None 495 | } 496 | }) { 497 | proxy_ipv4_list.insert(&Key::new(32, ipv4_addr.octets()), 1, 0)?; 498 | } 499 | 500 | Ok(()) 501 | } 502 | 503 | fn set_proxy_ip_list_mode(bpf: &mut Bpf, ip_in_list_directly: bool) -> anyhow::Result<()> { 504 | let mut proxy_list_mode: Array<_, u8> = bpf 505 | .map_mut(PROXY_LIST_MODE) 506 | .expect("PROXY_LIST_MODE not found") 507 | .try_into()?; 508 | 509 | let mode = if ip_in_list_directly { 0 } else { 1 }; 510 | 511 | proxy_list_mode.set(0, mode, 0)?; 512 | 513 | Ok(()) 514 | } 515 | 516 | fn set_command_list( 517 | bpf: &mut Bpf, 518 | commands: Vec, 519 | command_in_list_directly: bool, 520 | ) -> anyhow::Result<()> { 521 | let mut command_map: maps::HashMap<_, [u8; 16], u8> = bpf 522 | .map_mut(COMM_MAP) 523 | .expect("COMM_MAP not found") 524 | .try_into()?; 525 | 526 | for command in commands { 527 | let command = CString::new(command)?; 528 | let command = command.as_bytes_with_nul(); 529 | let mut buf = [0u8; 16]; 530 | let n = buf.len().min(command.len()); 531 | buf[..n].copy_from_slice(&command[..n]); 532 | 533 | command_map.insert(buf, 1, 0)?; 534 | } 535 | 536 | let mut command_mode: Array<_, u8> = bpf 537 | .map_mut(COMM_MAP_MODE) 538 | .expect("COMM_MAP_MODE not found") 539 | .try_into()?; 540 | 541 | let command_in_list_directly = if command_in_list_directly { 0 } else { 1 }; 542 | 543 | command_mode.set(0, command_in_list_directly, 0)?; 544 | 545 | Ok(()) 546 | } 547 | 548 | async fn get_remote_domain_ips(domain: &str) -> anyhow::Result> { 549 | if let Ok(ip_addr) = IpAddr::from_str(domain) { 550 | return Ok(vec![ip_addr]); 551 | } 552 | 553 | let async_resolver = AsyncResolver::tokio_from_system_conf()?; 554 | 555 | let ipv4_fut = async { 556 | match async_resolver.ipv4_lookup(domain).await { 557 | Err(err) if matches!(err.kind(), &ResolveErrorKind::NoRecordsFound { .. }) => Ok(None), 558 | 559 | Err(err) => Err(anyhow::Error::from(err)), 560 | 561 | Ok(ipv4lookup) => Ok(Some(ipv4lookup)), 562 | } 563 | }; 564 | 565 | let ipv6_fut = async { 566 | match async_resolver.ipv6_lookup(domain).await { 567 | Err(err) if matches!(err.kind(), &ResolveErrorKind::NoRecordsFound { .. }) => Ok(None), 568 | 569 | Err(err) => Err(err.into()), 570 | 571 | Ok(ipv6lookup) => Ok(Some(ipv6lookup)), 572 | } 573 | }; 574 | 575 | let (ipv4_lookup, ipv6_lookup) = future::try_join(ipv4_fut, ipv6_fut).await?; 576 | 577 | Ok(ipv4_lookup 578 | .into_iter() 579 | .flatten() 580 | .map(|record| IpAddr::from(record.0)) 581 | .chain( 582 | ipv6_lookup 583 | .into_iter() 584 | .flatten() 585 | .map(|record| IpAddr::from(record.0)), 586 | ) 587 | .collect()) 588 | } 589 | 590 | fn init_bpf_log(bpf: &mut Bpf) { 591 | LogTracer::builder().ignore_crate("rustls").init().unwrap(); 592 | 593 | BpfLogger::init(bpf).unwrap(); 594 | } 595 | -------------------------------------------------------------------------------- /client/src/listener/bpf.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Formatter}; 2 | use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6}; 3 | 4 | use anyhow::Context; 5 | use futures_util::stream::SelectAll; 6 | use futures_util::{StreamExt, stream}; 7 | use protocol::DomainOrSocketAddr; 8 | use share::tcp_wrapper::TcpListenerAddrStream; 9 | use tap::TapFallible; 10 | use tokio::net::{TcpListener, TcpStream}; 11 | use tracing::{error, info, instrument, warn}; 12 | 13 | use super::Listener; 14 | 15 | pub struct BpfListener { 16 | listen_addr: SocketAddrV4, 17 | listen_addr_v6: SocketAddrV6, 18 | container_bridge_listen_addr: Option, 19 | container_bridge_listen_addr_v6: Option, 20 | tcp_listeners: SelectAll, 21 | } 22 | 23 | impl Debug for BpfListener { 24 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 25 | f.debug_struct("BpfListener") 26 | .field("listen_addr", &self.listen_addr) 27 | .field("listen_addr_v6", &self.listen_addr_v6) 28 | .field( 29 | "container_bridge_listen_addr", 30 | &self.container_bridge_listen_addr, 31 | ) 32 | .field( 33 | "container_bridge_listen_addr_v6", 34 | &self.container_bridge_listen_addr_v6, 35 | ) 36 | .finish_non_exhaustive() 37 | } 38 | } 39 | 40 | impl BpfListener { 41 | pub async fn new( 42 | listen_addr: SocketAddrV4, 43 | listen_addr_v6: SocketAddrV6, 44 | container_bridge_listen_addr: Option, 45 | container_bridge_listen_addr_v6: Option, 46 | ) -> anyhow::Result { 47 | let tcp_listener = TcpListener::bind(listen_addr) 48 | .await 49 | .tap_err(|err| error!(%err, %listen_addr, "listen tcp4 failed"))?; 50 | let tcp_listener6 = TcpListener::bind(listen_addr_v6) 51 | .await 52 | .tap_err(|err| error!(%err, "listen tcp6 failed"))?; 53 | 54 | let mut listeners = vec![ 55 | TcpListenerAddrStream::from(tcp_listener), 56 | TcpListenerAddrStream::from(tcp_listener6), 57 | ]; 58 | 59 | if let Some(addr) = container_bridge_listen_addr { 60 | let tcp_listener = TcpListener::bind(addr) 61 | .await 62 | .tap_err(|err| error!(%err, %addr, "listen container addr tcp4 failed"))?; 63 | 64 | listeners.push(TcpListenerAddrStream::from(tcp_listener)); 65 | } 66 | if let Some(addr) = container_bridge_listen_addr_v6 { 67 | let tcp_listener = TcpListener::bind(addr) 68 | .await 69 | .tap_err(|err| error!(%err, %addr, "listen container addr tcp6 failed"))?; 70 | 71 | listeners.push(TcpListenerAddrStream::from(tcp_listener)); 72 | } 73 | 74 | let tcp_listeners = stream::select_all(listeners); 75 | 76 | Ok(Self { 77 | listen_addr, 78 | listen_addr_v6, 79 | container_bridge_listen_addr, 80 | container_bridge_listen_addr_v6, 81 | tcp_listeners, 82 | }) 83 | } 84 | 85 | fn is_listen_addr(&self, addr: SocketAddr) -> bool { 86 | for listen_addr in [ 87 | SocketAddr::from(self.listen_addr), 88 | SocketAddr::from(self.listen_addr_v6), 89 | ] 90 | .into_iter() 91 | .chain(self.container_bridge_listen_addr.map(SocketAddr::from)) 92 | .chain(self.container_bridge_listen_addr_v6.map(SocketAddr::from)) 93 | { 94 | if listen_addr == addr { 95 | return true; 96 | } 97 | } 98 | 99 | false 100 | } 101 | } 102 | 103 | impl Listener for BpfListener { 104 | type Stream = TcpStream; 105 | 106 | #[instrument(err(Debug))] 107 | async fn accept(&mut self) -> anyhow::Result<(Self::Stream, DomainOrSocketAddr)> { 108 | while let Some(result) = self.tcp_listeners.next().await { 109 | let (tcp_stream, _) = match result { 110 | Err(err) => { 111 | warn!(%err, "accept tcp failed"); 112 | 113 | continue; 114 | } 115 | 116 | Ok(result) => result, 117 | }; 118 | 119 | let addr = tcp_stream 120 | .local_addr() 121 | .with_context(|| "get tcp local addr failed")?; 122 | 123 | if self.is_listen_addr(addr) { 124 | error!(%addr, "origin dst addr is listen addr, that's not allowed"); 125 | 126 | continue; 127 | } 128 | 129 | info!("accept tcp done"); 130 | 131 | return Ok((tcp_stream, DomainOrSocketAddr::SocketAddr(addr))); 132 | } 133 | 134 | Err(anyhow::anyhow!("listener stop unexpectedly")) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /client/src/listener/mod.rs: -------------------------------------------------------------------------------- 1 | use protocol::DomainOrSocketAddr; 2 | use tokio::io; 3 | use tokio::io::{AsyncRead, AsyncWrite, BufStream, ReadHalf, WriteHalf}; 4 | use tokio::net::TcpStream; 5 | use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf}; 6 | 7 | pub mod bpf; 8 | 9 | #[trait_make::make(Send)] 10 | pub trait Listener { 11 | type Stream: Split; 12 | 13 | async fn accept(&mut self) -> anyhow::Result<(Self::Stream, DomainOrSocketAddr)>; 14 | } 15 | 16 | pub trait Split { 17 | type Read: AsyncRead + Send + Unpin; 18 | type Write: AsyncWrite + Send + Unpin; 19 | 20 | fn into_split(self) -> (Self::Read, Self::Write); 21 | } 22 | 23 | impl Split for TcpStream { 24 | type Read = OwnedReadHalf; 25 | type Write = OwnedWriteHalf; 26 | 27 | fn into_split(self) -> (Self::Read, Self::Write) { 28 | self.into_split() 29 | } 30 | } 31 | 32 | impl Split for BufStream { 33 | type Read = ReadHalf; 34 | type Write = WriteHalf; 35 | 36 | fn into_split(self) -> (Self::Read, Self::Write) { 37 | io::split(self) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /client/src/main.rs: -------------------------------------------------------------------------------- 1 | #[tokio::main] 2 | async fn main() -> anyhow::Result<()> { 3 | lycoris_client::run().await 4 | } 5 | -------------------------------------------------------------------------------- /client/src/mptcp.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use std::ffi::c_int; 3 | use std::io; 4 | use std::io::ErrorKind; 5 | use std::net::{IpAddr, SocketAddr}; 6 | use std::pin::pin; 7 | use std::time::Duration; 8 | 9 | use futures_util::stream::FuturesUnordered; 10 | use futures_util::{Stream, StreamExt}; 11 | use libc::SOCK_NONBLOCK; 12 | use share::async_iter_ext::AsyncIteratorExt; 13 | use socket2::{Domain, Protocol, Socket, Type}; 14 | use tokio::net::{TcpSocket, TcpStream}; 15 | use tokio::time; 16 | use tracing::{debug, error, instrument, warn}; 17 | 18 | pub trait MptcpExt { 19 | async fn connect_mptcp>>(addrs: S) -> io::Result 20 | where 21 | Self: Sized; 22 | } 23 | 24 | impl MptcpExt for TcpStream { 25 | #[instrument(level = "debug", skip(addrs), err)] 26 | async fn connect_mptcp>>(addrs: S) -> io::Result 27 | where 28 | Self: Sized, 29 | { 30 | let mut addrs = pin!(reorder_addrs(addrs).enumerate()); 31 | let mut futs = FuturesUnordered::new(); 32 | while let Some((i, addr)) = addrs.next().await { 33 | let addr = match addr { 34 | Err(err) => { 35 | warn!(%err, "resolve addr failed"); 36 | 37 | continue; 38 | } 39 | 40 | Ok(addr) => addr, 41 | }; 42 | 43 | futs.push(async move { 44 | if i > 0 { 45 | time::sleep(Duration::from_millis(250 * i as u64)).await; 46 | } 47 | 48 | debug!(%addr, "start happy eyeballs connect"); 49 | 50 | connect_mptcp_addr(addr) 51 | .await 52 | .map(|stream| (stream, addr)) 53 | .inspect_err(|err| error!(%err, %addr, "connect failed")) 54 | }); 55 | } 56 | 57 | while let Some(res) = futs.next().await { 58 | if let Ok((stream, addr)) = res { 59 | debug!(%addr, "happy eyeballs connect done"); 60 | 61 | return Ok(stream); 62 | } 63 | } 64 | 65 | Err(io::Error::new(ErrorKind::Other, "all addrs connect failed")) 66 | } 67 | } 68 | 69 | async gen fn reorder_addrs>>( 70 | addrs: S, 71 | ) -> io::Result { 72 | let mut v6 = VecDeque::new(); 73 | let mut v4 = VecDeque::new(); 74 | let mut yield_v6 = true; 75 | let mut addrs = pin!(addrs); 76 | 77 | loop { 78 | if yield_v6 { 79 | if let Some(addr) = v6.pop_front() { 80 | yield_v6 = false; 81 | 82 | yield Ok(addr); 83 | } 84 | 85 | match addrs.next().await { 86 | None => return, 87 | Some(Err(err)) => yield Err(err), 88 | Some(Ok(addr)) => { 89 | if addr.is_ipv4() { 90 | v4.push_back(addr); 91 | 92 | continue; 93 | } 94 | 95 | yield_v6 = false; 96 | 97 | yield Ok(addr) 98 | } 99 | } 100 | } else { 101 | if let Some(addr) = v4.pop_front() { 102 | yield_v6 = true; 103 | 104 | yield Ok(addr); 105 | } 106 | 107 | match addrs.next().await { 108 | None => return, 109 | Some(Err(err)) => yield Err(err), 110 | Some(Ok(addr)) => { 111 | if addr.is_ipv6() { 112 | v6.push_back(addr); 113 | 114 | continue; 115 | } 116 | 117 | yield_v6 = true; 118 | 119 | yield Ok(addr) 120 | } 121 | } 122 | } 123 | } 124 | } 125 | 126 | async fn connect_mptcp_addr(mut addr: SocketAddr) -> io::Result { 127 | let ty = Type::from(SOCK_NONBLOCK | c_int::from(Type::STREAM)); 128 | let socket = Socket::new(Domain::IPV6, ty, Some(Protocol::MPTCP))?; 129 | if let IpAddr::V4(ip) = addr.ip() { 130 | addr.set_ip(ip.to_ipv6_mapped().into()); 131 | } 132 | 133 | TcpSocket::from_std_stream(socket.into()) 134 | .connect(addr) 135 | .await 136 | } 137 | -------------------------------------------------------------------------------- /client/src/owned_link.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | 3 | use aya::programs::Link; 4 | 5 | pub struct OwnedLink { 6 | link: Option, 7 | } 8 | 9 | impl From for OwnedLink { 10 | fn from(link: T) -> Self { 11 | Self { link: Some(link) } 12 | } 13 | } 14 | 15 | impl Deref for OwnedLink { 16 | type Target = T; 17 | 18 | fn deref(&self) -> &Self::Target { 19 | self.link.as_ref().unwrap() 20 | } 21 | } 22 | 23 | impl DerefMut for OwnedLink { 24 | fn deref_mut(&mut self) -> &mut Self::Target { 25 | self.link.as_mut().unwrap() 26 | } 27 | } 28 | 29 | impl Drop for OwnedLink { 30 | fn drop(&mut self) { 31 | let _ = self.link.take().unwrap().detach(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/tests/bpf_test.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::env; 3 | use std::ffi::OsStr; 4 | use std::net::{SocketAddrV4, SocketAddrV6}; 5 | use std::path::Path; 6 | use std::str::FromStr; 7 | 8 | use aya::maps::Array; 9 | use aya::maps::lpm_trie::{Key, LpmTrie}; 10 | use aya::programs::cgroup_sock_addr::CgroupSockAddrLink; 11 | use aya::programs::{CgroupSockAddr, SockOps}; 12 | use aya::{Bpf, BpfLoader}; 13 | use aya_log::BpfLogger; 14 | use cidr::Ipv4Inet; 15 | use lycoris_client::bpf_map_name::*; 16 | use lycoris_client::bpf_share::{Ipv4Addr, Ipv6Addr}; 17 | use lycoris_client::{BpfListener, Listener, OwnedLink}; 18 | use protocol::DomainOrSocketAddr; 19 | use rustix::process::getuid; 20 | use share::log::init_log; 21 | use tokio::fs::File; 22 | use tokio::io::AsyncWriteExt; 23 | use tokio::net::TcpStream; 24 | use tokio_util::compat::TokioAsyncReadCompatExt; 25 | use tracing::info; 26 | use tracing_log::LogTracer; 27 | 28 | const CGROUP_PATH: &str = "/sys/fs/cgroup"; 29 | const BPF_ELF: &str = "../target/bpfel-unknown-none/release/lycoris-bpf"; 30 | const TEST_LISTEN_ADDR: &str = "127.0.0.1:23333"; 31 | const TEST_LISTEN_ADDR_V6: &str = "[::1]:23333"; 32 | const TEST_IP_CIDR: &str = "172.20.0.0/16"; 33 | const TEST_TARGET_ADDR: &str = "172.20.0.1:80"; 34 | 35 | #[tokio::test] 36 | async fn main() { 37 | if env::var_os("TESTPROXY") 38 | .as_deref() 39 | .unwrap_or_else(|| OsStr::new("")) 40 | != OsStr::new("4") 41 | { 42 | eprintln!("skip proxy"); 43 | 44 | return; 45 | } 46 | 47 | if !getuid().is_root() { 48 | panic!("this integration test must run with root"); 49 | } 50 | 51 | init_log(true); 52 | 53 | let listen_addr = SocketAddrV4::from_str(TEST_LISTEN_ADDR).unwrap(); 54 | let listen_addr_v6 = SocketAddrV6::from_str(TEST_LISTEN_ADDR_V6).unwrap(); 55 | 56 | let mut bpf = BpfLoader::new() 57 | .allow_unsupported_maps() 58 | .load_file(BPF_ELF) 59 | .unwrap(); 60 | 61 | init_bpf_log(&mut bpf); 62 | 63 | set_proxy_addr(&mut bpf, listen_addr, listen_addr_v6); 64 | set_proxy_ip_list_mode(&mut bpf); 65 | load_target_ip(&mut bpf); 66 | 67 | let _connect4_link = load_connect4(&mut bpf, Path::new(CGROUP_PATH)).await; 68 | let _getsockname_link = load_getsockname4(&mut bpf, Path::new(CGROUP_PATH)).await; 69 | let _getpeername4 = load_getpeername4(&mut bpf, Path::new(CGROUP_PATH)).await; 70 | let _sockops_link = load_established_sockops(&mut bpf, Path::new(CGROUP_PATH)).await; 71 | 72 | let mut listener = load_listener(listen_addr, listen_addr_v6).await; 73 | 74 | tokio::spawn(async move { 75 | let (mut stream, addr) = listener.accept().await.unwrap(); 76 | stream.write_all(&addr.encode()).await.unwrap(); 77 | }); 78 | 79 | tokio::time::sleep(std::time::Duration::from_secs(1)).await; 80 | 81 | let tcp_stream = TcpStream::connect(TEST_TARGET_ADDR).await.unwrap(); 82 | let local_addr = tcp_stream.local_addr().unwrap(); 83 | let peer_addr = tcp_stream.peer_addr().unwrap(); 84 | let mut tcp_stream = tcp_stream.compat(); 85 | 86 | info!(%local_addr, %peer_addr, "get tcp addr done"); 87 | 88 | let domain_or_socket_addr = DomainOrSocketAddr::parse(&mut tcp_stream).await.unwrap(); 89 | 90 | info!(?domain_or_socket_addr, "domain or socket addr"); 91 | } 92 | 93 | async fn load_listener(listen_addr: SocketAddrV4, listen_addr_v6: SocketAddrV6) -> BpfListener { 94 | BpfListener::new(listen_addr, listen_addr_v6, None, None) 95 | .await 96 | .unwrap() 97 | } 98 | 99 | async fn load_connect4(bpf: &mut Bpf, cgroup_path: &Path) -> OwnedLink { 100 | let cgroup_file = File::open(cgroup_path).await.unwrap(); 101 | 102 | let connect4_prog: &mut CgroupSockAddr = bpf 103 | .program_mut("connect4") 104 | .expect("bpf connect4 not found") 105 | .try_into() 106 | .unwrap(); 107 | 108 | connect4_prog.load().unwrap(); 109 | 110 | info!("load connect4 done"); 111 | 112 | let connect4_link_id = connect4_prog.attach(cgroup_file).unwrap(); 113 | 114 | info!(?cgroup_path, "attach cgroup done"); 115 | 116 | connect4_prog.take_link(connect4_link_id).unwrap().into() 117 | } 118 | 119 | async fn load_getsockname4(bpf: &mut Bpf, cgroup_path: &Path) -> OwnedLink { 120 | let cgroup_file = File::open(cgroup_path).await.unwrap(); 121 | 122 | let prog: &mut CgroupSockAddr = bpf 123 | .program_mut("getsockname4") 124 | .expect("bpf getsockname4 not found") 125 | .try_into() 126 | .unwrap(); 127 | 128 | prog.load().unwrap(); 129 | 130 | info!("load getsockname4 done"); 131 | 132 | let link_id = prog.attach(cgroup_file).unwrap(); 133 | 134 | info!(?cgroup_path, "attach cgroup done"); 135 | 136 | prog.take_link(link_id).unwrap().into() 137 | } 138 | 139 | async fn load_getpeername4(bpf: &mut Bpf, cgroup_path: &Path) -> OwnedLink { 140 | let cgroup_file = File::open(cgroup_path).await.unwrap(); 141 | 142 | let prog: &mut CgroupSockAddr = bpf 143 | .program_mut("getpeername4") 144 | .expect("bpf getpeername4 not found") 145 | .try_into() 146 | .unwrap(); 147 | 148 | prog.load().unwrap(); 149 | 150 | info!("load getpeername4 done"); 151 | 152 | let link_id = prog.attach(cgroup_file).unwrap(); 153 | 154 | info!(?cgroup_path, "attach cgroup done"); 155 | 156 | prog.take_link(link_id).unwrap().into() 157 | } 158 | 159 | // return Box because the SockOpsLink is un-exported 160 | async fn load_established_sockops(bpf: &mut Bpf, cgroup_path: &Path) -> Box { 161 | let cgroup_file = File::open(cgroup_path).await.unwrap(); 162 | 163 | let prog: &mut SockOps = bpf 164 | .program_mut("established_connect") 165 | .expect("bpf established_connect not found") 166 | .try_into() 167 | .unwrap(); 168 | 169 | prog.load().unwrap(); 170 | 171 | info!("loaded established_connect done"); 172 | 173 | let link_id = prog.attach(cgroup_file).unwrap(); 174 | 175 | info!("attach established_connect done"); 176 | 177 | Box::new(OwnedLink::from(prog.take_link(link_id).unwrap())) 178 | } 179 | 180 | fn load_target_ip(bpf: &mut Bpf) { 181 | let mut proxy_ipv4_list: LpmTrie<_, [u8; 4], u8> = bpf 182 | .map_mut(PROXY_IPV4_LIST) 183 | .expect("PROXY_IPV4_LIST not found") 184 | .try_into() 185 | .unwrap(); 186 | 187 | let ipv4_inet = Ipv4Inet::from_str(TEST_IP_CIDR).unwrap(); 188 | 189 | proxy_ipv4_list 190 | .insert( 191 | &Key::new( 192 | ipv4_inet.network_length() as _, 193 | ipv4_inet.first_address().octets(), 194 | ), 195 | 1, 196 | 0, 197 | ) 198 | .unwrap(); 199 | } 200 | 201 | fn set_proxy_addr(bpf: &mut Bpf, addr: SocketAddrV4, addr_v6: SocketAddrV6) { 202 | let mut proxy_server: Array<_, Ipv4Addr> = bpf 203 | .map_mut(PROXY_IPV4_CLIENT) 204 | .expect("PROXY_IPV4_CLIENT bpf array not found") 205 | .try_into() 206 | .unwrap(); 207 | 208 | let proxy_addr = Ipv4Addr { 209 | addr: addr.ip().octets(), 210 | port: addr.port(), 211 | _padding: [0; 2], 212 | }; 213 | 214 | proxy_server.set(0, proxy_addr, 0).unwrap(); 215 | 216 | let mut v6_proxy_server: Array<_, Ipv6Addr> = bpf 217 | .map_mut(PROXY_IPV6_CLIENT) 218 | .expect("PROXY_IPV6_CLIENT bpf array not found") 219 | .try_into() 220 | .unwrap(); 221 | 222 | let proxy_addr = Ipv6Addr { 223 | addr: addr_v6.ip().to_bits().to_be_bytes(), 224 | port: addr.port(), 225 | }; 226 | 227 | v6_proxy_server.set(0, proxy_addr, 0).unwrap(); 228 | } 229 | 230 | fn set_proxy_ip_list_mode(bpf: &mut Bpf) { 231 | let mut proxy_list_mode: Array<_, u8> = bpf 232 | .map_mut(PROXY_LIST_MODE) 233 | .expect("PROXY_LIST_MODE not found") 234 | .try_into() 235 | .unwrap(); 236 | 237 | proxy_list_mode.set(0, 1u8, 0).unwrap(); 238 | } 239 | 240 | fn init_bpf_log(bpf: &mut Bpf) { 241 | LogTracer::builder().ignore_crate("rustls").init().unwrap(); 242 | 243 | BpfLogger::init(bpf).unwrap(); 244 | } 245 | -------------------------------------------------------------------------------- /client/tests/bpf_test6.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::env; 3 | use std::ffi::OsStr; 4 | use std::net::{SocketAddrV4, SocketAddrV6}; 5 | use std::path::Path; 6 | use std::str::FromStr; 7 | 8 | use aya::maps::Array; 9 | use aya::maps::lpm_trie::{Key, LpmTrie}; 10 | use aya::programs::cgroup_sock_addr::CgroupSockAddrLink; 11 | use aya::programs::{CgroupSockAddr, SockOps}; 12 | use aya::{Bpf, BpfLoader}; 13 | use aya_log::BpfLogger; 14 | use cidr::Ipv6Inet; 15 | use lycoris_client::bpf_map_name::*; 16 | use lycoris_client::bpf_share::{Ipv4Addr, Ipv6Addr}; 17 | use lycoris_client::{BpfListener, Listener, OwnedLink}; 18 | use protocol::DomainOrSocketAddr; 19 | use rustix::process::getuid; 20 | use share::log::init_log; 21 | use tokio::fs::File; 22 | use tokio::io::AsyncWriteExt; 23 | use tokio::net::TcpStream; 24 | use tokio_util::compat::TokioAsyncReadCompatExt; 25 | use tracing::info; 26 | use tracing_log::LogTracer; 27 | 28 | const CGROUP_PATH: &str = "/sys/fs/cgroup"; 29 | const BPF_ELF: &str = "../target/bpfel-unknown-none/release/lycoris-bpf"; 30 | const TEST_LISTEN_ADDR: &str = "127.0.0.1:23333"; 31 | const TEST_LISTEN_ADDR_V6: &str = "[::1]:23333"; 32 | const TEST_IP_CIDR: &str = "fd00::/8"; 33 | const TEST_TARGET_ADDR: &str = "fd00::1:80"; 34 | 35 | #[tokio::test] 36 | async fn main() { 37 | if env::var_os("TESTPROXY") 38 | .as_deref() 39 | .unwrap_or_else(|| OsStr::new("")) 40 | != OsStr::new("6") 41 | { 42 | eprintln!("skip proxy"); 43 | 44 | return; 45 | } 46 | 47 | if !getuid().is_root() { 48 | panic!("this integration test must run with root"); 49 | } 50 | 51 | init_log(true); 52 | 53 | let listen_addr = SocketAddrV4::from_str(TEST_LISTEN_ADDR).unwrap(); 54 | let listen_addr_v6 = SocketAddrV6::from_str(TEST_LISTEN_ADDR_V6).unwrap(); 55 | 56 | let mut bpf = BpfLoader::new() 57 | .allow_unsupported_maps() 58 | .load_file(BPF_ELF) 59 | .unwrap(); 60 | 61 | init_bpf_log(&mut bpf); 62 | 63 | set_proxy_addr(&mut bpf, listen_addr, listen_addr_v6); 64 | set_proxy_ip_list_mode(&mut bpf); 65 | load_target_ip(&mut bpf); 66 | 67 | let _connect6_link = load_connect6(&mut bpf, Path::new(CGROUP_PATH)).await; 68 | let _getsockname_link = load_getsockname6(&mut bpf, Path::new(CGROUP_PATH)).await; 69 | let _getpeername6 = load_getpeername6(&mut bpf, Path::new(CGROUP_PATH)).await; 70 | let _sockops_link = load_established_sockops(&mut bpf, Path::new(CGROUP_PATH)).await; 71 | 72 | let mut listener = load_listener(listen_addr, listen_addr_v6).await; 73 | 74 | tokio::spawn(async move { 75 | let (mut stream, addr) = listener.accept().await.unwrap(); 76 | stream.write_all(&addr.encode()).await.unwrap(); 77 | }); 78 | 79 | tokio::time::sleep(std::time::Duration::from_secs(1)).await; 80 | 81 | let tcp_stream = TcpStream::connect(TEST_TARGET_ADDR).await.unwrap(); 82 | let local_addr = tcp_stream.local_addr().unwrap(); 83 | let peer_addr = tcp_stream.peer_addr().unwrap(); 84 | let mut tcp_stream = tcp_stream.compat(); 85 | 86 | info!(%local_addr, %peer_addr, "get tcp addr done"); 87 | 88 | let domain_or_socket_addr = DomainOrSocketAddr::parse(&mut tcp_stream).await.unwrap(); 89 | 90 | info!(?domain_or_socket_addr, "domain or socket addr"); 91 | } 92 | 93 | async fn load_listener(listen_addr: SocketAddrV4, listen_addr_v6: SocketAddrV6) -> BpfListener { 94 | BpfListener::new(listen_addr, listen_addr_v6, None, None) 95 | .await 96 | .unwrap() 97 | } 98 | 99 | async fn load_connect6(bpf: &mut Bpf, cgroup_path: &Path) -> OwnedLink { 100 | let cgroup_file = File::open(cgroup_path).await.unwrap(); 101 | 102 | let connect6_prog: &mut CgroupSockAddr = bpf 103 | .program_mut("connect6") 104 | .expect("bpf connect6 not found") 105 | .try_into() 106 | .unwrap(); 107 | 108 | connect6_prog.load().unwrap(); 109 | 110 | info!("load connect6 done"); 111 | 112 | let connect6_link_id = connect6_prog.attach(cgroup_file).unwrap(); 113 | 114 | info!(?cgroup_path, "attach cgroup done"); 115 | 116 | connect6_prog.take_link(connect6_link_id).unwrap().into() 117 | } 118 | 119 | async fn load_getsockname6(bpf: &mut Bpf, cgroup_path: &Path) -> OwnedLink { 120 | let cgroup_file = File::open(cgroup_path).await.unwrap(); 121 | 122 | let prog: &mut CgroupSockAddr = bpf 123 | .program_mut("getsockname6") 124 | .expect("bpf getsockname6 not found") 125 | .try_into() 126 | .unwrap(); 127 | 128 | prog.load().unwrap(); 129 | 130 | info!("load getsockname6 done"); 131 | 132 | let link_id = prog.attach(cgroup_file).unwrap(); 133 | 134 | info!(?cgroup_path, "attach cgroup done"); 135 | 136 | prog.take_link(link_id).unwrap().into() 137 | } 138 | 139 | async fn load_getpeername6(bpf: &mut Bpf, cgroup_path: &Path) -> OwnedLink { 140 | let cgroup_file = File::open(cgroup_path).await.unwrap(); 141 | 142 | let prog: &mut CgroupSockAddr = bpf 143 | .program_mut("getpeername6") 144 | .expect("bpf getpeername6 not found") 145 | .try_into() 146 | .unwrap(); 147 | 148 | prog.load().unwrap(); 149 | 150 | info!("load getpeername6 done"); 151 | 152 | let link_id = prog.attach(cgroup_file).unwrap(); 153 | 154 | info!(?cgroup_path, "attach cgroup done"); 155 | 156 | prog.take_link(link_id).unwrap().into() 157 | } 158 | 159 | // return Box because the SockOpsLink is un-exported 160 | async fn load_established_sockops(bpf: &mut Bpf, cgroup_path: &Path) -> Box { 161 | let cgroup_file = File::open(cgroup_path).await.unwrap(); 162 | 163 | let prog: &mut SockOps = bpf 164 | .program_mut("established_connect") 165 | .expect("bpf established_connect not found") 166 | .try_into() 167 | .unwrap(); 168 | 169 | prog.load().unwrap(); 170 | 171 | info!("loaded established_connect done"); 172 | 173 | let link_id = prog.attach(cgroup_file).unwrap(); 174 | 175 | info!("attach established_connect done"); 176 | 177 | Box::new(OwnedLink::from(prog.take_link(link_id).unwrap())) 178 | } 179 | 180 | fn load_target_ip(bpf: &mut Bpf) { 181 | let mut proxy_ipv6_list: LpmTrie<_, [u8; 16], u8> = bpf 182 | .map_mut(PROXY_IPV6_LIST) 183 | .expect("PROXY_IPV6_LIST not found") 184 | .try_into() 185 | .unwrap(); 186 | 187 | let ipv6_inet = Ipv6Inet::from_str(TEST_IP_CIDR).unwrap(); 188 | 189 | proxy_ipv6_list 190 | .insert( 191 | &Key::new( 192 | ipv6_inet.network_length() as _, 193 | ipv6_inet.first_address().to_bits().to_be_bytes(), 194 | ), 195 | 1, 196 | 0, 197 | ) 198 | .unwrap(); 199 | } 200 | 201 | fn set_proxy_addr(bpf: &mut Bpf, addr: SocketAddrV4, addr_v6: SocketAddrV6) { 202 | let mut proxy_server: Array<_, Ipv4Addr> = bpf 203 | .map_mut(PROXY_IPV4_CLIENT) 204 | .expect("PROXY_IPV4_CLIENT bpf array not found") 205 | .try_into() 206 | .unwrap(); 207 | 208 | let proxy_addr = Ipv4Addr { 209 | addr: addr.ip().octets(), 210 | port: addr.port(), 211 | _padding: [0; 2], 212 | }; 213 | 214 | proxy_server.set(0, proxy_addr, 0).unwrap(); 215 | 216 | let mut v6_proxy_server: Array<_, Ipv6Addr> = bpf 217 | .map_mut(PROXY_IPV6_CLIENT) 218 | .expect("PROXY_IPV6_CLIENT bpf array not found") 219 | .try_into() 220 | .unwrap(); 221 | 222 | let proxy_addr = Ipv6Addr { 223 | addr: addr_v6.ip().to_bits().to_be_bytes(), 224 | port: addr.port(), 225 | }; 226 | 227 | v6_proxy_server.set(0, proxy_addr, 0).unwrap(); 228 | } 229 | 230 | fn set_proxy_ip_list_mode(bpf: &mut Bpf) { 231 | let mut proxy_list_mode: Array<_, u8> = bpf 232 | .map_mut(PROXY_LIST_MODE) 233 | .expect("PROXY_LIST_MODE not found") 234 | .try_into() 235 | .unwrap(); 236 | 237 | proxy_list_mode.set(0, 1u8, 0).unwrap(); 238 | } 239 | 240 | fn init_bpf_log(bpf: &mut Bpf) { 241 | LogTracer::builder().ignore_crate("rustls").init().unwrap(); 242 | 243 | BpfLogger::init(bpf).unwrap(); 244 | } 245 | -------------------------------------------------------------------------------- /protocol/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "protocol" 3 | version = "0.2.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | bytes = { workspace = true } 8 | futures-util = { workspace = true, features = ["io", "sink"] } 9 | futures-channel = { workspace = true, features = ["sink"] } 10 | futures-rustls = { workspace = true, features = ["early-data"] } 11 | http = { workspace = true } 12 | http-body-util = { workspace = true } 13 | hyper = { workspace = true, features = ["http2", "client"] } 14 | hyper-util = { workspace = true, features = ["client-legacy", "http2", "server"] } 15 | pin-project = { workspace = true } 16 | tokio = { workspace = true } 17 | tokio-util = { workspace = true, features = ["io"] } 18 | totp-rs = { workspace = true, features = ["otpauth"] } 19 | thiserror = { workspace = true } 20 | tower-service = { workspace = true } 21 | tracing = { workspace = true } 22 | trait-make = { workspace = true } 23 | -------------------------------------------------------------------------------- /protocol/src/accept.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | use std::future::Future; 3 | use std::io::ErrorKind; 4 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; 5 | use std::pin::Pin; 6 | use std::sync::Arc; 7 | use std::task::{Context, Poll}; 8 | use std::{io, mem}; 9 | 10 | use bytes::{Buf, Bytes}; 11 | use futures_channel::mpsc::{self, UnboundedReceiver, UnboundedSender}; 12 | pub use futures_rustls::rustls::ServerConfig; 13 | use futures_util::future::BoxFuture; 14 | use futures_util::{AsyncRead, AsyncWrite, Stream, TryStreamExt}; 15 | use http::{Request, Response, StatusCode, Version}; 16 | use http_body_util::combinators::BoxBody; 17 | use http_body_util::{BodyExt, Empty, StreamBody}; 18 | use hyper::body::Incoming; 19 | use hyper::rt::{Executor as HyperExecutor, Timer}; 20 | use hyper::service::service_fn; 21 | use hyper_util::server::conn::auto::Builder; 22 | use thiserror::Error; 23 | use tokio_util::io::{SinkWriter, StreamReader}; 24 | use tracing::{debug, error, info, instrument}; 25 | 26 | use crate::auth::Auth; 27 | use crate::h2_config::{ 28 | INITIAL_CONNECTION_WINDOW_SIZE, INITIAL_WINDOW_SIZE, MAX_FRAME_SIZE, PING_INTERVAL, 29 | PING_TIMEOUT, 30 | }; 31 | use crate::hyper_body::{BodyStream, SinkBodySender}; 32 | use crate::{DnsResolver, GenericTlsStream, Reader, Writer}; 33 | 34 | #[must_use] 35 | pub struct ListenTask { 36 | fut: BoxFuture<'static, ()>, 37 | } 38 | 39 | impl Future for ListenTask { 40 | type Output = (); 41 | 42 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 43 | Pin::new(&mut self.fut).poll(cx) 44 | } 45 | } 46 | 47 | pub struct HyperListener { 48 | tls_acceptor: TlsAcceptor, 49 | executor: E, 50 | builder: Builder>, 51 | token_header: String, 52 | auth: Auth, 53 | dns_resolver: D, 54 | } 55 | 56 | impl HyperListener { 57 | pub fn new( 58 | tcp_acceptor: A, 59 | executor: E, 60 | server_tls_config: ServerConfig, 61 | token_header: String, 62 | auth: Auth, 63 | dns_resolver: D, 64 | timer: T, 65 | ) -> Self { 66 | let mut builder = Builder::new(ExecutorWrapper(executor.clone())); 67 | builder 68 | .http2() 69 | .timer(timer) 70 | .initial_stream_window_size(INITIAL_WINDOW_SIZE) 71 | .initial_connection_window_size(INITIAL_CONNECTION_WINDOW_SIZE) 72 | .max_frame_size(MAX_FRAME_SIZE) 73 | .keep_alive_timeout(PING_TIMEOUT) 74 | .keep_alive_interval(PING_INTERVAL); 75 | 76 | Self { 77 | tls_acceptor: TlsAcceptor::new(tcp_acceptor, server_tls_config), 78 | executor, 79 | builder, 80 | token_header, 81 | auth, 82 | dns_resolver, 83 | } 84 | } 85 | } 86 | 87 | pub trait Executor { 88 | fn execute(&self, fut: Fut) 89 | where 90 | Fut: Future + Send + 'static, 91 | Fut::Output: Send + 'static; 92 | } 93 | 94 | #[derive(Clone)] 95 | struct ExecutorWrapper(E); 96 | 97 | impl HyperExecutor for ExecutorWrapper 98 | where 99 | Fut: Future + Send + 'static, 100 | Fut::Output: Send + 'static, 101 | { 102 | fn execute(&self, fut: Fut) { 103 | self.0.execute(fut) 104 | } 105 | } 106 | 107 | impl HyperListener 108 | where 109 | IO: AsyncRead + AsyncWrite + Unpin + Send + 'static, 110 | A: Stream> + Unpin + Send + 'static, 111 | E: Executor + Clone + Send + Sync + 'static, 112 | D: DnsResolver + Clone + 'static, 113 | { 114 | pub fn listen(self) -> (ListenTask, HyperAcceptor) { 115 | let Self { 116 | mut tls_acceptor, 117 | executor, 118 | builder, 119 | token_header, 120 | auth, 121 | dns_resolver, 122 | } = self; 123 | 124 | let (acceptor_tx, acceptor_rx) = mpsc::unbounded(); 125 | let builder = Arc::new(builder); 126 | let handler = Arc::new(HyperServerHandler { token_header, auth }); 127 | 128 | let fut = Box::pin(async move { 129 | loop { 130 | let stream = match tls_acceptor.accept().await { 131 | Err(err) => { 132 | error!( % err, "accept tls failed"); 133 | 134 | continue; 135 | } 136 | 137 | Ok(stream) => stream, 138 | }; 139 | 140 | let handler = handler.clone(); 141 | let builder = builder.clone(); 142 | let acceptor_tx = acceptor_tx.clone(); 143 | let dns_resolver = dns_resolver.clone(); 144 | 145 | executor.execute(Box::pin(async move { 146 | let connection = builder.serve_connection( 147 | stream, 148 | service_fn(move |req| { 149 | let handler = handler.clone(); 150 | let acceptor_tx = acceptor_tx.clone(); 151 | let dns_resolver = dns_resolver.clone(); 152 | 153 | async move { handler.handle(req, acceptor_tx, dns_resolver).await } 154 | }), 155 | ); 156 | 157 | connection.await.map_err(Error::Other)?; 158 | 159 | Ok::<_, Error>(()) 160 | })); 161 | } 162 | }); 163 | 164 | ( 165 | ListenTask { fut }, 166 | HyperAcceptor { 167 | receiver: acceptor_rx, 168 | }, 169 | ) 170 | } 171 | } 172 | 173 | pub type AcceptorResult = (Vec, Writer, Reader); 174 | 175 | #[derive(Debug)] 176 | pub struct HyperAcceptor { 177 | receiver: UnboundedReceiver, 178 | } 179 | 180 | impl Stream for HyperAcceptor { 181 | type Item = AcceptorResult; 182 | 183 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 184 | Pin::new(&mut self.receiver).poll_next(cx) 185 | } 186 | } 187 | 188 | #[derive(Debug)] 189 | struct HyperServerHandler { 190 | token_header: String, 191 | auth: Auth, 192 | } 193 | 194 | impl HyperServerHandler { 195 | #[instrument(skip(dns_resolver))] 196 | async fn handle( 197 | &self, 198 | request: Request, 199 | result_tx: UnboundedSender, 200 | dns_resolver: D, 201 | ) -> Result>, Error> { 202 | if request.version() != Version::HTTP_2 { 203 | error!("reject not http2 request"); 204 | 205 | let mut response = Response::new(Empty::new().boxed()); 206 | *response.status_mut() = StatusCode::UNAUTHORIZED; 207 | 208 | return Ok(response); 209 | } 210 | 211 | if !self.auth_token(&request) { 212 | let mut response = Response::new(Empty::new().boxed()); 213 | *response.status_mut() = StatusCode::UNAUTHORIZED; 214 | 215 | return Ok(response); 216 | } 217 | 218 | info!("http2 token auth done"); 219 | 220 | let mut body = request.into_body(); 221 | let addrs = match Self::get_remote_addrs(&mut body, dns_resolver).await { 222 | Err(Error::AddrNotEnough) => { 223 | let mut response = Response::new(Empty::new().boxed()); 224 | *response.status_mut() = StatusCode::BAD_REQUEST; 225 | 226 | return Ok(response); 227 | } 228 | 229 | Err(err) => return Err(err), 230 | 231 | Ok(addrs) => addrs, 232 | }; 233 | 234 | info!(?addrs, "get remote addrs done"); 235 | 236 | let (body_tx, body_rx) = mpsc::channel(1); 237 | let response = Response::new(StreamBody::new(body_rx).boxed()); 238 | let stream_reader = StreamReader::new(BodyStream::from(body)); 239 | let sink_body_sender = SinkBodySender::from(body_tx); 240 | 241 | result_tx 242 | .unbounded_send(( 243 | addrs, 244 | Writer(SinkWriter::new(sink_body_sender)), 245 | Reader(stream_reader), 246 | )) 247 | .map_err(|err| Error::Other(err.into()))?; 248 | 249 | Ok(response) 250 | } 251 | 252 | #[instrument(skip(dns_resolver), err(Debug))] 253 | async fn get_remote_addrs( 254 | body: &mut Incoming, 255 | dns_resolver: D, 256 | ) -> Result, Error> { 257 | let frame = match body.frame().await { 258 | None => return Err(Error::AddrNotEnough), 259 | Some(Err(err)) => return Err(err.into()), 260 | Some(Ok(frame)) => frame, 261 | }; 262 | 263 | let data = match frame.into_data() { 264 | Ok(data) => data, 265 | Err(_) => return Err(Error::AddrNotEnough), 266 | }; 267 | 268 | debug!("get remote addrs data done"); 269 | 270 | parse_addr(&data, dns_resolver).await 271 | } 272 | 273 | #[instrument(ret)] 274 | fn auth_token(&self, request: &Request) -> bool { 275 | let token = match request.headers().get(&self.token_header) { 276 | None => { 277 | error!("the h2 request doesn't have token header, reject it"); 278 | 279 | return false; 280 | } 281 | 282 | Some(token) => match token.to_str() { 283 | Err(err) => { 284 | error!(%err,"token is not valid utf8 string"); 285 | 286 | return false; 287 | } 288 | 289 | Ok(token) => token, 290 | }, 291 | }; 292 | 293 | self.auth.auth(token) 294 | } 295 | } 296 | 297 | #[derive(Clone)] 298 | struct TlsAcceptor { 299 | tcp_acceptor: A, 300 | tls_acceptor: futures_rustls::TlsAcceptor, 301 | } 302 | 303 | impl TlsAcceptor { 304 | fn new(tcp_acceptor: A, server_tls_config: ServerConfig) -> Self { 305 | Self { 306 | tcp_acceptor, 307 | tls_acceptor: futures_rustls::TlsAcceptor::from(Arc::new(server_tls_config)), 308 | } 309 | } 310 | } 311 | 312 | impl> + Unpin> TlsAcceptor { 313 | async fn accept(&mut self) -> io::Result> { 314 | let stream = self 315 | .tcp_acceptor 316 | .try_next() 317 | .await? 318 | .ok_or_else(|| io::Error::new(ErrorKind::BrokenPipe, "TCP acceptor closed"))?; 319 | self.tls_acceptor 320 | .accept(stream) 321 | .await 322 | .map(|tls_stream| GenericTlsStream { 323 | tls_stream: tls_stream.into(), 324 | }) 325 | } 326 | } 327 | 328 | #[derive(Debug, Error)] 329 | pub enum Error { 330 | #[error("io error: {0}")] 331 | Io(#[from] io::Error), 332 | 333 | #[error("http error: {0}")] 334 | Hyper(#[from] hyper::Error), 335 | 336 | #[error("address data is not enough")] 337 | AddrNotEnough, 338 | 339 | #[error("address type {0} is invalid, valid is `4` and `6`")] 340 | AddrTypeInvalid(u8), 341 | 342 | #[error("address domain invalid")] 343 | AddrDomainInvalid, 344 | 345 | #[error(transparent)] 346 | Other(Box), 347 | } 348 | 349 | /// Parse addr from data 350 | /// the data format is \[addr_type:1,addr:variant,port:2\] 351 | async fn parse_addr( 352 | mut data: &[u8], 353 | mut dns_resolver: D, 354 | ) -> Result, Error> { 355 | if data.is_empty() { 356 | return Err(Error::AddrNotEnough); 357 | } 358 | 359 | let addrs = match data[0] { 360 | 1 => { 361 | data.advance(1); 362 | 363 | if data.len() < 2 { 364 | return Err(Error::AddrNotEnough); 365 | } 366 | 367 | let domain_len = data.get_u16() as usize; 368 | if data.len() < domain_len { 369 | return Err(Error::AddrNotEnough); 370 | } 371 | 372 | let domain = data.get(0..domain_len).unwrap(); 373 | let domain = match String::from_utf8(domain.to_vec()) { 374 | Err(_) => { 375 | return Err(Error::AddrDomainInvalid); 376 | } 377 | 378 | Ok(domain) => domain, 379 | }; 380 | 381 | let addrs = dns_resolver.resolve(&domain).await?; 382 | 383 | data.advance(domain_len); 384 | if data.len() < 2 { 385 | return Err(Error::AddrNotEnough); 386 | } 387 | 388 | let port = data.get_u16(); 389 | 390 | addrs 391 | .map_ok(|ip| SocketAddr::new(ip, port)) 392 | .try_collect() 393 | .await? 394 | } 395 | 396 | 4 => { 397 | data.advance(1); 398 | 399 | if data.len() != 4 + 2 { 400 | return Err(Error::AddrNotEnough); 401 | } 402 | 403 | vec![SocketAddr::new( 404 | IpAddr::V4(Ipv4Addr::new(data[0], data[1], data[2], data[3])), 405 | u16::from_be_bytes(data[4..6].try_into().unwrap()), 406 | )] 407 | } 408 | 409 | 6 => { 410 | data.advance(1); 411 | 412 | // 128 is ipv6 bits 413 | if data.len() != mem::size_of::() + 2 { 414 | return Err(Error::AddrNotEnough); 415 | } 416 | 417 | vec![SocketAddr::new( 418 | IpAddr::V6(Ipv6Addr::from(u128::from_be_bytes( 419 | data[..16].try_into().unwrap(), 420 | ))), 421 | u16::from_be_bytes(data[16..18].try_into().unwrap()), 422 | )] 423 | } 424 | 425 | n => return Err(Error::AddrTypeInvalid(n)), 426 | }; 427 | 428 | Ok(addrs) 429 | } 430 | -------------------------------------------------------------------------------- /protocol/src/auth.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Formatter}; 2 | 3 | use totp_rs::{Algorithm, TotpUrlError, TOTP}; 4 | 5 | pub struct Auth { 6 | totp: TOTP, 7 | } 8 | 9 | impl Debug for Auth { 10 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 11 | f.debug_struct("Auth") 12 | .field("totp", &self.totp.to_string()) 13 | .finish() 14 | } 15 | } 16 | 17 | impl Auth { 18 | pub fn new( 19 | token_secret: String, 20 | account_name: impl Into>, 21 | ) -> Result { 22 | Ok(Self { 23 | totp: TOTP::new( 24 | Algorithm::SHA512, 25 | 8, 26 | 1, 27 | 30, 28 | token_secret.into_bytes(), 29 | None, 30 | account_name 31 | .into() 32 | .unwrap_or_else(|| "default_account".to_string()), 33 | )?, 34 | }) 35 | } 36 | 37 | pub fn generate_token(&self) -> String { 38 | self.totp 39 | .generate_current() 40 | .unwrap_or_else(|err| panic!("generate current token failed: {}", err)) 41 | } 42 | 43 | pub fn auth(&self, token: &str) -> bool { 44 | self.totp.check_current(token).unwrap_or(false) 45 | } 46 | } 47 | 48 | #[cfg(test)] 49 | mod tests { 50 | use super::*; 51 | 52 | #[test] 53 | fn test_generate_token() { 54 | const TOKEN_SECRET: &str = "testtesttesttest"; 55 | const ACCOUNT_NAME: Option = None; 56 | 57 | let token_generator = Auth::new(TOKEN_SECRET.to_string(), ACCOUNT_NAME.clone()).unwrap(); 58 | let totp = TOTP::new( 59 | Algorithm::SHA512, 60 | 8, 61 | 1, 62 | 30, 63 | TOKEN_SECRET.as_bytes().to_vec(), 64 | None, 65 | ACCOUNT_NAME.unwrap_or_else(|| "default_account".to_string()), 66 | ) 67 | .unwrap(); 68 | 69 | let token = token_generator.generate_token(); 70 | let expect = totp.generate_current().unwrap(); 71 | 72 | assert_eq!(token, expect); 73 | } 74 | 75 | #[test] 76 | fn check_auth() { 77 | let secret = "test-secrettest-secret".to_string(); 78 | let account_name = "test".to_string(); 79 | 80 | let totp = TOTP::new( 81 | Algorithm::SHA512, 82 | 8, 83 | 1, 84 | 30, 85 | secret.clone().into_bytes(), 86 | None, 87 | account_name.clone(), 88 | ) 89 | .unwrap(); 90 | 91 | let auth = Auth::new(secret, account_name).unwrap(); 92 | 93 | let token = totp.generate_current().unwrap(); 94 | 95 | dbg!(&token); 96 | 97 | assert!(auth.auth(&token)); 98 | } 99 | 100 | #[test] 101 | fn check_auth_with_different_account_name() { 102 | let secret = "test-secrettest-secret".to_string(); 103 | 104 | let totp = TOTP::new( 105 | Algorithm::SHA512, 106 | 8, 107 | 1, 108 | 30, 109 | secret.clone().into_bytes(), 110 | None, 111 | "test".to_string(), 112 | ) 113 | .unwrap(); 114 | 115 | let auth = Auth::new(secret, None).unwrap(); 116 | 117 | let token = totp.generate_current().unwrap(); 118 | 119 | dbg!(&token); 120 | 121 | assert!(auth.auth(&token)); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /protocol/src/connect.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | use std::future::Future; 3 | use std::io::{self, ErrorKind}; 4 | use std::net::SocketAddr; 5 | use std::pin::Pin; 6 | use std::sync::Arc; 7 | use std::task::{Context, Poll}; 8 | 9 | use bytes::Bytes; 10 | use futures_channel::mpsc; 11 | use futures_rustls::pki_types::{DnsName, ServerName}; 12 | pub use futures_rustls::rustls::ClientConfig; 13 | use futures_rustls::TlsConnector; 14 | use futures_util::future::BoxFuture; 15 | use futures_util::{stream, AsyncRead, AsyncWrite, SinkExt, Stream, TryStreamExt}; 16 | use http::{Request, StatusCode, Uri, Version}; 17 | use http_body_util::combinators::BoxBody; 18 | use http_body_util::StreamBody; 19 | use hyper::body::Frame; 20 | use hyper::rt::Timer; 21 | use hyper_util::client::legacy::connect::Connection; 22 | use hyper_util::client::legacy::Client; 23 | use tokio_util::io::{SinkWriter, StreamReader}; 24 | use tower_service::Service; 25 | use tracing::{error, info, instrument}; 26 | 27 | use crate::auth::Auth; 28 | use crate::h2_config::{ 29 | INITIAL_CONNECTION_WINDOW_SIZE, INITIAL_WINDOW_SIZE, MAX_FRAME_SIZE, PING_INTERVAL, 30 | PING_TIMEOUT, 31 | }; 32 | use crate::hyper_body::BodyStream; 33 | use crate::{DnsResolver, DomainOrSocketAddr, GenericTlsStream, Reader, Writer}; 34 | 35 | #[derive(Debug, thiserror::Error)] 36 | pub enum Error { 37 | #[error(transparent)] 38 | Http(#[from] http::Error), 39 | 40 | #[error("hyper error: {0}")] 41 | Hyper(#[from] hyper_util::client::legacy::Error), 42 | 43 | #[error(transparent)] 44 | Other(Box), 45 | } 46 | 47 | #[derive(Debug)] 48 | pub struct HyperConnectorConfig { 49 | pub tls_client_config: ClientConfig, 50 | pub remote_domain: String, 51 | pub remote_port: u16, 52 | pub auth: Auth, 53 | pub token_header: String, 54 | pub dns_resolver: DR, 55 | pub tcp_connector: TC, 56 | pub executor: E, 57 | pub timer: T, 58 | } 59 | 60 | #[derive(Debug, Clone)] 61 | pub struct HyperConnector { 62 | inner: Arc>, 63 | } 64 | 65 | #[derive(Debug)] 66 | struct HyperConnectorInner { 67 | client: Client, BoxBody>, 68 | remote_addr: Uri, 69 | token_generator: Auth, 70 | token_header: String, 71 | } 72 | 73 | impl HyperConnector { 74 | pub fn new(config: HyperConnectorConfig) -> Result 75 | where 76 | E: hyper::rt::Executor + Send>>> 77 | + Send 78 | + Sync 79 | + Clone 80 | + 'static, 81 | T: Timer + Send + Sync + 'static, 82 | { 83 | let https_connector = GenericHttpsConnector { 84 | dns_resolver: config.dns_resolver, 85 | tcp_connector: config.tcp_connector, 86 | tls_connector: TlsConnector::from(Arc::new(config.tls_client_config)).early_data(true), 87 | }; 88 | 89 | let client = Client::builder(config.executor) 90 | .timer(config.timer) 91 | .http2_only(true) 92 | .http2_initial_connection_window_size(INITIAL_WINDOW_SIZE) 93 | .http2_initial_connection_window_size(INITIAL_CONNECTION_WINDOW_SIZE) 94 | .http2_max_frame_size(MAX_FRAME_SIZE) 95 | .http2_keep_alive_timeout(PING_TIMEOUT) 96 | .http2_keep_alive_interval(PING_INTERVAL) 97 | .build(https_connector); 98 | 99 | Ok(Self { 100 | inner: Arc::new(HyperConnectorInner { 101 | client, 102 | remote_addr: Uri::try_from(format!( 103 | "https://{}:{}", 104 | config.remote_domain, config.remote_port 105 | )) 106 | .map_err(|err| Error::Other(err.into()))?, 107 | token_generator: config.auth, 108 | token_header: config.token_header, 109 | }), 110 | }) 111 | } 112 | 113 | #[instrument(skip(self), err(Debug))] 114 | pub async fn connect( 115 | &self, 116 | remote_addr: DomainOrSocketAddr, 117 | ) -> Result<(Reader, Writer), Error> { 118 | let token = self.inner.token_generator.generate_token(); 119 | let remote_addr_data = remote_addr.encode(); 120 | 121 | let (mut req_body_tx, req_body_rx) = mpsc::channel(1); 122 | req_body_tx 123 | .send(Ok(Frame::data(remote_addr_data))) 124 | .await 125 | .expect("unbounded_send should not fail"); 126 | 127 | let request = Request::builder() 128 | .version(Version::HTTP_2) 129 | .uri(self.inner.remote_addr.clone()) 130 | .header(&self.inner.token_header, token) 131 | .body(BoxBody::new(StreamBody::new(req_body_rx)))?; 132 | 133 | let response = self.inner.client.request(request).await?; 134 | 135 | info!("receive h2 response done"); 136 | 137 | if response.status() != StatusCode::OK { 138 | let status_code = response.status(); 139 | error!(%status_code, "status code is not 200"); 140 | 141 | return Err(Error::Other( 142 | format!("status {status_code} is not 200").into(), 143 | )); 144 | } 145 | 146 | info!("get h2 stream done"); 147 | 148 | let reader = StreamReader::new(BodyStream::from(response.into_body())); 149 | 150 | Ok((Reader(reader), Writer(SinkWriter::new(req_body_tx.into())))) 151 | } 152 | } 153 | 154 | #[trait_make::make(Send)] 155 | pub trait TcpConnector: Clone { 156 | type ConnectedTcpStream: AsyncRead + AsyncWrite + Connection + Send + Sync + Unpin + 'static; 157 | 158 | async fn connect> + Send>( 159 | &mut self, 160 | addrs: S, 161 | ) -> io::Result; 162 | } 163 | 164 | #[derive(Clone)] 165 | struct GenericHttpsConnector { 166 | dns_resolver: DR, 167 | tcp_connector: TC, 168 | tls_connector: TlsConnector, 169 | } 170 | 171 | impl Service 172 | for GenericHttpsConnector 173 | { 174 | type Response = GenericTlsStream; 175 | type Error = io::Error; 176 | type Future = BoxFuture<'static, Result>; 177 | 178 | fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { 179 | Poll::Ready(Ok(())) 180 | } 181 | 182 | fn call(&mut self, req: Uri) -> Self::Future { 183 | let mut this = self.clone(); 184 | 185 | Box::pin(async move { 186 | let host = req 187 | .host() 188 | .ok_or_else(|| io::Error::new(ErrorKind::Other, "miss host"))? 189 | .trim_start_matches('[') 190 | .trim_end_matches(']'); 191 | 192 | let port = req.port_u16().unwrap_or(443); 193 | 194 | let server_name = 195 | ServerName::try_from(host).map_err(|err| io::Error::new(ErrorKind::Other, err))?; 196 | let server_name = server_name.to_owned(); 197 | 198 | let tcp_stream = match &server_name { 199 | &ServerName::IpAddress(ip) => { 200 | this.tcp_connector 201 | .connect(stream::iter([Ok(SocketAddr::new(ip.into(), port))])) 202 | .await? 203 | } 204 | 205 | ServerName::DnsName(dns_name) => this.connect_dns_name(dns_name, port).await?, 206 | 207 | _ => { 208 | return Err(io::Error::new( 209 | ErrorKind::Other, 210 | format!("unknown server name {server_name:?}"), 211 | )); 212 | } 213 | }; 214 | 215 | let tls_stream = this.tls_connector.connect(server_name, tcp_stream).await?; 216 | 217 | Ok(GenericTlsStream { 218 | tls_stream: tls_stream.into(), 219 | }) 220 | }) 221 | } 222 | } 223 | 224 | impl GenericHttpsConnector { 225 | async fn connect_dns_name( 226 | &mut self, 227 | dns_name: &DnsName<'_>, 228 | port: u16, 229 | ) -> io::Result { 230 | let ip = self.dns_resolver.resolve(dns_name.as_ref()).await?; 231 | 232 | self.tcp_connector 233 | .connect(ip.map_ok(|ip| SocketAddr::new(ip, port))) 234 | .await 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /protocol/src/h2_config.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | /// 20 MiB 4 | pub const INITIAL_WINDOW_SIZE: u32 = 20 * 1024 * 1024; 5 | /// 100 MiB 6 | pub const INITIAL_CONNECTION_WINDOW_SIZE: u32 = 100 * 1024 * 1024; 7 | /// the h2 lib allow max size 8 | pub const MAX_FRAME_SIZE: u32 = 16777215; 9 | /// ping interval 10 | pub const PING_INTERVAL: Duration = Duration::from_secs(10); 11 | /// ping timeout 12 | pub const PING_TIMEOUT: Duration = Duration::from_secs(10); 13 | -------------------------------------------------------------------------------- /protocol/src/hyper_body.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::io::ErrorKind; 3 | use std::pin::Pin; 4 | use std::task::{ready, Context, Poll}; 5 | 6 | use bytes::Bytes; 7 | use futures_channel::mpsc::Sender; 8 | use futures_util::{Sink, SinkExt, Stream}; 9 | use http::HeaderMap; 10 | use hyper::body::{Body, Frame, Incoming}; 11 | 12 | #[derive(Debug)] 13 | pub struct BodyStream { 14 | body: Incoming, 15 | } 16 | 17 | impl From for BodyStream { 18 | fn from(value: Incoming) -> Self { 19 | Self { body: value } 20 | } 21 | } 22 | 23 | impl Stream for BodyStream { 24 | type Item = io::Result; 25 | 26 | #[inline] 27 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 28 | let result = Pin::new(&mut self.body) 29 | .poll_frame(cx) 30 | .map_err(|err| io::Error::new(ErrorKind::Other, err))?; 31 | match ready!(result) { 32 | None => Poll::Ready(None), 33 | Some(frame) => match frame.into_data() { 34 | Err(_) => Poll::Ready(None), 35 | Ok(data) => Poll::Ready(Some(Ok(data))), 36 | }, 37 | } 38 | } 39 | } 40 | 41 | #[derive(Debug)] 42 | pub struct SinkBodySender { 43 | sender: Sender, E>>, 44 | is_trailer_sent: bool, 45 | } 46 | 47 | impl From, E>>> for SinkBodySender { 48 | fn from(value: Sender, E>>) -> Self { 49 | Self { 50 | sender: value, 51 | is_trailer_sent: false, 52 | } 53 | } 54 | } 55 | 56 | impl<'a, E> Sink<&'a [u8]> for SinkBodySender { 57 | type Error = io::Error; 58 | 59 | #[inline] 60 | fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 61 | self.sender 62 | .poll_ready(cx) 63 | .map_err(|err| io::Error::new(ErrorKind::Other, err)) 64 | } 65 | 66 | #[inline] 67 | fn start_send(mut self: Pin<&mut Self>, item: &'a [u8]) -> Result<(), Self::Error> { 68 | self.sender 69 | .start_send_unpin(Ok(Frame::data(Bytes::copy_from_slice(item)))) 70 | .map_err(|err| io::Error::new(ErrorKind::Other, err)) 71 | } 72 | 73 | #[inline] 74 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 75 | self.sender 76 | .poll_flush_unpin(cx) 77 | .map_err(|err| io::Error::new(ErrorKind::Other, err)) 78 | } 79 | 80 | #[inline] 81 | fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 82 | if !self.is_trailer_sent { 83 | ready!(self.as_mut().poll_ready(cx))?; 84 | 85 | self.sender 86 | .start_send_unpin(Ok(Frame::trailers(HeaderMap::new()))) 87 | .map_err(|_| io::Error::from(ErrorKind::BrokenPipe))?; 88 | 89 | self.is_trailer_sent = true; 90 | } 91 | 92 | self.sender 93 | .poll_close_unpin(cx) 94 | .map_err(|err| io::Error::new(ErrorKind::Other, err)) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /protocol/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | use std::io::{ErrorKind, IoSlice}; 3 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; 4 | use std::pin::Pin; 5 | use std::task::{ready, Context, Poll}; 6 | use std::{io, slice}; 7 | 8 | pub use accept::{HyperAcceptor, HyperListener}; 9 | use bytes::{BufMut, Bytes, BytesMut}; 10 | pub use connect::{HyperConnector, HyperConnectorConfig}; 11 | use futures_rustls::TlsStream; 12 | use futures_util::{AsyncRead, AsyncReadExt, AsyncWrite, Stream}; 13 | use hyper::rt::ReadBufCursor; 14 | use hyper_util::client::legacy::connect::{Connected, Connection}; 15 | use tokio::io::{AsyncRead as TAsyncRead, AsyncWrite as TAsyncWrite, ReadBuf}; 16 | use tokio_util::io::{SinkWriter, StreamReader}; 17 | 18 | use crate::hyper_body::{BodyStream, SinkBodySender}; 19 | 20 | pub mod accept; 21 | pub mod auth; 22 | pub mod connect; 23 | mod h2_config; 24 | mod hyper_body; 25 | 26 | #[derive(Debug)] 27 | pub struct Reader(StreamReader); 28 | 29 | impl AsyncRead for Reader { 30 | #[inline] 31 | fn poll_read( 32 | mut self: Pin<&mut Self>, 33 | cx: &mut Context<'_>, 34 | buf: &mut [u8], 35 | ) -> Poll> { 36 | let mut read_buf = ReadBuf::new(buf); 37 | ready!(Pin::new(&mut self.0).poll_read(cx, &mut read_buf))?; 38 | 39 | Poll::Ready(Ok(read_buf.filled().len())) 40 | } 41 | } 42 | 43 | impl TAsyncRead for Reader { 44 | fn poll_read( 45 | mut self: Pin<&mut Self>, 46 | cx: &mut Context<'_>, 47 | buf: &mut ReadBuf<'_>, 48 | ) -> Poll> { 49 | Pin::new(&mut self.0).poll_read(cx, buf) 50 | } 51 | } 52 | 53 | #[derive(Debug)] 54 | pub struct Writer(SinkWriter>); 55 | 56 | impl AsyncWrite for Writer { 57 | #[inline] 58 | fn poll_write( 59 | mut self: Pin<&mut Self>, 60 | cx: &mut Context<'_>, 61 | buf: &[u8], 62 | ) -> Poll> { 63 | Pin::new(&mut self.0).poll_write(cx, buf) 64 | } 65 | 66 | #[inline] 67 | fn poll_write_vectored( 68 | mut self: Pin<&mut Self>, 69 | cx: &mut Context<'_>, 70 | bufs: &[IoSlice<'_>], 71 | ) -> Poll> { 72 | Pin::new(&mut self.0).poll_write_vectored(cx, bufs) 73 | } 74 | 75 | #[inline] 76 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 77 | Pin::new(&mut self.0).poll_flush(cx) 78 | } 79 | 80 | #[inline] 81 | fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 82 | Pin::new(&mut self.0).poll_shutdown(cx) 83 | } 84 | } 85 | 86 | impl TAsyncWrite for Writer { 87 | #[inline] 88 | fn poll_write( 89 | mut self: Pin<&mut Self>, 90 | cx: &mut Context<'_>, 91 | buf: &[u8], 92 | ) -> Poll> { 93 | Pin::new(&mut self.0).poll_write(cx, buf) 94 | } 95 | 96 | #[inline] 97 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 98 | Pin::new(&mut self.0).poll_flush(cx) 99 | } 100 | 101 | #[inline] 102 | fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 103 | Pin::new(&mut self.0).poll_shutdown(cx) 104 | } 105 | 106 | #[inline] 107 | fn poll_write_vectored( 108 | mut self: Pin<&mut Self>, 109 | cx: &mut Context<'_>, 110 | bufs: &[IoSlice<'_>], 111 | ) -> Poll> { 112 | Pin::new(&mut self.0).poll_write_vectored(cx, bufs) 113 | } 114 | 115 | #[inline] 116 | fn is_write_vectored(&self) -> bool { 117 | self.0.is_write_vectored() 118 | } 119 | } 120 | 121 | #[trait_make::make(Send)] 122 | pub trait DnsResolver: Clone { 123 | async fn resolve( 124 | &mut self, 125 | name: &str, 126 | ) -> io::Result> + Send>; 127 | } 128 | 129 | #[derive(Debug, Clone)] 130 | pub enum DomainOrSocketAddr { 131 | Domain { domain: String, port: u16 }, 132 | 133 | SocketAddr(SocketAddr), 134 | } 135 | 136 | impl DomainOrSocketAddr { 137 | const DOMAIN_TYPE: u8 = 1; 138 | const SOCKET_ADDR4_TYPE: u8 = 4; 139 | const SOCKET_ADDR6_TYPE: u8 = 6; 140 | 141 | /// Encode addr to \[addr_type:1, addr:variant, port:2\] 142 | pub fn encode(self) -> Bytes { 143 | match self { 144 | DomainOrSocketAddr::Domain { domain, port } => { 145 | let mut buf = BytesMut::with_capacity(1 + 2 + domain.len() + 2); 146 | 147 | buf.put_u8(1); 148 | buf.put_u16(domain.len() as _); 149 | buf.put(domain.as_bytes()); 150 | buf.put_u16(port); 151 | 152 | buf.freeze() 153 | } 154 | DomainOrSocketAddr::SocketAddr(addr) => match addr { 155 | SocketAddr::V4(v4_addr) => { 156 | let mut buf = BytesMut::with_capacity(1 + 4 + 2); 157 | 158 | buf.put_u8(4); 159 | buf.put(v4_addr.ip().octets().as_slice()); 160 | buf.put_u16(v4_addr.port()); 161 | 162 | buf.freeze() 163 | } 164 | 165 | SocketAddr::V6(v6_addr) => { 166 | let mut buf = BytesMut::with_capacity(1 + 16 + 2); 167 | 168 | buf.put_u8(6); 169 | buf.put(v6_addr.ip().octets().as_slice()); 170 | buf.put_u16(v6_addr.port()); 171 | 172 | buf.freeze() 173 | } 174 | }, 175 | } 176 | } 177 | 178 | pub async fn parse(reader: &mut T) -> io::Result { 179 | let mut r#type = [0]; 180 | reader.read_exact(&mut r#type).await?; 181 | 182 | let ip = match r#type[0] { 183 | Self::DOMAIN_TYPE => { 184 | let mut domain_len = [0; 2]; 185 | reader.read_exact(&mut domain_len).await?; 186 | 187 | let mut domain = vec![0; u16::from_be_bytes(domain_len) as _]; 188 | reader.read_exact(&mut domain).await?; 189 | let domain = String::from_utf8(domain) 190 | .map_err(|err| io::Error::new(ErrorKind::Other, err))?; 191 | 192 | let mut port = [0; 2]; 193 | reader.read_exact(&mut port).await?; 194 | 195 | return Ok(Self::Domain { 196 | domain, 197 | port: u16::from_be_bytes(port), 198 | }); 199 | } 200 | 201 | Self::SOCKET_ADDR4_TYPE => { 202 | let mut addr = [0; 4]; 203 | reader.read_exact(&mut addr).await?; 204 | 205 | IpAddr::from(Ipv4Addr::from(addr)) 206 | } 207 | 208 | Self::SOCKET_ADDR6_TYPE => { 209 | let mut addr = [0; 16]; 210 | reader.read_exact(&mut addr).await?; 211 | 212 | IpAddr::from(Ipv6Addr::from(addr)) 213 | } 214 | 215 | _ => { 216 | return Err(io::Error::new( 217 | ErrorKind::Other, 218 | format!("unknown type {}", r#type[0]), 219 | )) 220 | } 221 | }; 222 | 223 | let mut port = [0; 2]; 224 | reader.read_exact(&mut port).await?; 225 | 226 | Ok(Self::SocketAddr(SocketAddr::new( 227 | ip, 228 | u16::from_be_bytes(port), 229 | ))) 230 | } 231 | } 232 | 233 | impl From for DomainOrSocketAddr { 234 | fn from(value: SocketAddr) -> Self { 235 | Self::SocketAddr(value) 236 | } 237 | } 238 | 239 | pub struct GenericTlsStream { 240 | tls_stream: TlsStream, 241 | } 242 | 243 | impl Connection for GenericTlsStream { 244 | fn connected(&self) -> Connected { 245 | self.tls_stream.get_ref().0.connected() 246 | } 247 | } 248 | 249 | impl hyper::rt::Read for GenericTlsStream { 250 | fn poll_read( 251 | mut self: Pin<&mut Self>, 252 | cx: &mut Context<'_>, 253 | mut buf: ReadBufCursor<'_>, 254 | ) -> Poll> { 255 | // Safety: we won't read it, unless IO implement is stupid:( 256 | let buf_mut = unsafe { 257 | let buf_mut = buf.as_mut(); 258 | slice::from_raw_parts_mut(buf_mut.as_mut_ptr().cast(), buf_mut.len()) 259 | }; 260 | 261 | let n = ready!(Pin::new(&mut self.tls_stream).poll_read(cx, buf_mut))?; 262 | 263 | // Safety: n is written 264 | unsafe { 265 | buf.advance(n); 266 | } 267 | 268 | Poll::Ready(Ok(())) 269 | } 270 | } 271 | 272 | impl hyper::rt::Write for GenericTlsStream { 273 | fn poll_write( 274 | mut self: Pin<&mut Self>, 275 | cx: &mut Context<'_>, 276 | buf: &[u8], 277 | ) -> Poll> { 278 | Pin::new(&mut self.tls_stream).poll_write(cx, buf) 279 | } 280 | 281 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 282 | Pin::new(&mut self.tls_stream).poll_flush(cx) 283 | } 284 | 285 | fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 286 | Pin::new(&mut self.tls_stream).poll_close(cx) 287 | } 288 | } 289 | 290 | #[cfg(test)] 291 | mod tests { 292 | use std::net::Ipv6Addr; 293 | use std::str::FromStr; 294 | 295 | use super::*; 296 | 297 | #[test] 298 | fn encode_v4() { 299 | let data = 300 | DomainOrSocketAddr::SocketAddr(SocketAddr::from_str("127.0.0.1:80").unwrap()).encode(); 301 | 302 | let mut correct = BytesMut::from(&[4, 127, 0, 0, 1][..]); 303 | correct.put_u16(80); 304 | 305 | assert_eq!(data, correct); 306 | } 307 | 308 | #[test] 309 | fn encode_v6() { 310 | let data = 311 | DomainOrSocketAddr::SocketAddr(SocketAddr::from_str("[::1]:80").unwrap()).encode(); 312 | 313 | let mut correct = BytesMut::from(&[6][..]); 314 | correct.put(Ipv6Addr::from_str("::1").unwrap().octets().as_slice()); 315 | correct.put_u16(80); 316 | 317 | assert_eq!(data, correct); 318 | } 319 | 320 | #[test] 321 | fn encode_domain() { 322 | let data = DomainOrSocketAddr::Domain { 323 | domain: "www.example.com".to_string(), 324 | port: 80, 325 | } 326 | .encode(); 327 | 328 | let mut correct = BytesMut::from(&[1][..]); 329 | correct.put_u16("www.example.com".as_bytes().len() as _); 330 | correct.put("www.example.com".as_bytes()); 331 | correct.put_u16(80); 332 | 333 | assert_eq!(data, correct); 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lycoris-server" 3 | version = "0.4.0" 4 | edition = "2021" 5 | license = "MIT" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | bytes = { workspace = true } 11 | clap = { workspace = true, features = ["derive", "color", "suggestions"] } 12 | futures-channel = { workspace = true } 13 | futures-rustls = { workspace = true } 14 | futures-util = { workspace = true, features = ["io"] } 15 | http = { workspace = true } 16 | hyper = { workspace = true, features = ["http2", "http1", "server"] } 17 | http-body-util = { workspace = true } 18 | hyper-util = { workspace = true, features = ["server", "server-auto", "http1", "http2", "tokio"] } 19 | libc = { workspace = true } 20 | rustls-pemfile = { workspace = true } 21 | serde = { workspace = true, features = ["derive"] } 22 | serde_yaml = { workspace = true } 23 | socket2 = { workspace = true } 24 | tap = { workspace = true } 25 | tokio = { workspace = true, features = ["macros", "rt-multi-thread", "net", "io-util", "fs"] } 26 | tracing = { workspace = true } 27 | thiserror = { workspace = true } 28 | tokio-util = { workspace = true, features = ["compat"] } 29 | 30 | protocol = { path = "../protocol" } 31 | share = { path = "../share" } 32 | 33 | [dev-dependencies] 34 | hyper = { workspace = true, features = ["client"] } 35 | hyper-rustls = { version = "0.27", features = ["http2", "logging"] } 36 | hyper-util = { workspace = true, features = ["client"] } 37 | totp-rs = { workspace = true } 38 | -------------------------------------------------------------------------------- /server/example.yaml: -------------------------------------------------------------------------------- 1 | key: example/server.key 2 | cert: example/server.cert 3 | listen_addr: 0.0.0.0:443 4 | token_secret: test 5 | token_header: x-token 6 | -------------------------------------------------------------------------------- /server/src/args.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use clap::Parser; 4 | 5 | #[derive(Debug, Parser)] 6 | pub struct Args { 7 | /// config path 8 | #[clap(short, long)] 9 | pub config: PathBuf, 10 | 11 | /// debug log 12 | #[clap(short, long, action)] 13 | pub debug: bool, 14 | } 15 | -------------------------------------------------------------------------------- /server/src/config.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | use std::path::PathBuf; 3 | 4 | use serde::Deserialize; 5 | 6 | #[derive(Debug, Deserialize)] 7 | pub struct Config { 8 | pub key: PathBuf, 9 | pub cert: PathBuf, 10 | pub listen_addr: SocketAddr, 11 | pub token_secret: String, 12 | pub token_header: String, 13 | } 14 | -------------------------------------------------------------------------------- /server/src/err.rs: -------------------------------------------------------------------------------- 1 | use std::io::Error as IoError; 2 | 3 | use thiserror::Error; 4 | 5 | #[derive(Debug, Error)] 6 | pub enum Error { 7 | #[error("io error: {0}")] 8 | Io(#[from] IoError), 9 | 10 | #[error("http error: {0}")] 11 | Hyper(#[from] hyper::Error), 12 | 13 | #[error("address data is not enough")] 14 | AddrNotEnough, 15 | 16 | #[error("address type {0} is invalid, valid is `4` and `6`")] 17 | AddrTypeInvalid(u8), 18 | 19 | #[error("address domain invalid")] 20 | AddrDomainInvalid, 21 | 22 | #[error("auth failed")] 23 | AuthFailed, 24 | 25 | #[error("parse config failed: {0}")] 26 | Config(#[from] serde_yaml::Error), 27 | 28 | #[error(transparent)] 29 | Other(Box), 30 | 31 | #[error("build tls config failed: {0}")] 32 | TlsConfig(#[from] futures_rustls::rustls::Error), 33 | 34 | #[error("parse tls file failed: {0:?}")] 35 | TlsFile(rustls_pemfile::Error), 36 | } 37 | 38 | impl From for Error { 39 | fn from(value: rustls_pemfile::Error) -> Self { 40 | Self::TlsFile(value) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /server/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(type_alias_impl_trait)] 2 | 3 | use std::path::Path; 4 | 5 | use args::Args; 6 | use clap::Parser; 7 | use futures_rustls::pki_types::{CertificateDer, PrivateKeyDer}; 8 | use futures_rustls::rustls::ServerConfig; 9 | use protocol::auth::Auth; 10 | use rustls_pemfile::Item; 11 | use share::log::init_log; 12 | use tokio::fs; 13 | use tokio::net::TcpListener; 14 | use tracing::info; 15 | 16 | use self::config::Config; 17 | pub use self::err::Error; 18 | #[doc(hidden)] 19 | pub use self::mptcp::MptcpListenerExt; 20 | #[doc(hidden)] 21 | pub use self::server::HyperServer; 22 | 23 | mod args; 24 | mod config; 25 | mod err; 26 | mod mptcp; 27 | mod server; 28 | 29 | pub async fn run() -> Result<(), Error> { 30 | let args = Args::parse(); 31 | let config = fs::read(args.config).await?; 32 | let config = serde_yaml::from_slice::(&config)?; 33 | 34 | init_log(args.debug); 35 | 36 | info!(token_header = %config.token_header, "get token header"); 37 | 38 | let certs = load_certs(&config.cert).await?; 39 | let key = load_key(&config.key).await?; 40 | let server_config = ServerConfig::builder() 41 | .with_no_client_auth() 42 | .with_single_cert(certs, key)?; 43 | 44 | let tcp_listener = TcpListener::listen_mptcp(config.listen_addr).await?; 45 | 46 | info!(listen_addr = %config.listen_addr, "start listen"); 47 | 48 | let auth = Auth::new(config.token_secret, None).map_err(|err| Error::Other(err.into()))?; 49 | let server = HyperServer::new(config.token_header, auth, tcp_listener, server_config)?; 50 | 51 | server.start().await 52 | } 53 | 54 | async fn load_certs(path: &Path) -> Result>, Error> { 55 | let certs = fs::read(path).await?; 56 | 57 | Ok(rustls_pemfile::certs(&mut certs.as_slice()).collect::, _>>()?) 58 | } 59 | 60 | async fn load_key(path: &Path) -> Result, Error> { 61 | let data = fs::read(path).await?; 62 | 63 | let item = rustls_pemfile::read_one_from_slice(&data)? 64 | .ok_or_else(|| Error::Other("no key found".into()))? 65 | .0; 66 | 67 | match item { 68 | Item::X509Certificate(_) | Item::SubjectPublicKeyInfo(_) => { 69 | Err(Error::Other("certificate is not private key".into())) 70 | } 71 | 72 | Item::Crl(_) => Err(Error::Other("crl is not private key".into())), 73 | Item::Csr(_) => Err(Error::Other("csr is not private key".into())), 74 | 75 | Item::Pkcs1Key(key) => Ok(key.into()), 76 | Item::Pkcs8Key(key) => Ok(key.into()), 77 | Item::Sec1Key(key) => Ok(key.into()), 78 | 79 | _ => Err(Error::Other("unknown key file".into())), 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /server/src/main.rs: -------------------------------------------------------------------------------- 1 | use lycoris_server::Error; 2 | 3 | #[tokio::main] 4 | async fn main() -> Result<(), Error> { 5 | lycoris_server::run().await 6 | } 7 | -------------------------------------------------------------------------------- /server/src/mptcp.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::c_int; 2 | use std::io; 3 | use std::net::{IpAddr, SocketAddr}; 4 | 5 | use libc::SOCK_NONBLOCK; 6 | use socket2::{Domain, Protocol, Socket, Type}; 7 | use tokio::net::{TcpListener, TcpSocket}; 8 | 9 | #[allow(async_fn_in_trait)] 10 | pub trait MptcpListenerExt { 11 | async fn listen_mptcp(addr: SocketAddr) -> io::Result 12 | where 13 | Self: Sized; 14 | } 15 | 16 | impl MptcpListenerExt for TcpListener { 17 | async fn listen_mptcp(mut addr: SocketAddr) -> io::Result 18 | where 19 | Self: Sized, 20 | { 21 | let ty = Type::from(SOCK_NONBLOCK | c_int::from(Type::STREAM)); 22 | let socket = Socket::new(Domain::IPV6, ty, Some(Protocol::MPTCP))?; 23 | socket.set_reuse_address(true)?; 24 | socket.set_reuse_port(true)?; 25 | if let IpAddr::V4(ip) = addr.ip() { 26 | addr.set_ip(ip.to_ipv6_mapped().into()); 27 | } 28 | 29 | let tcp_socket = TcpSocket::from_std_stream(socket.into()); 30 | tcp_socket.bind(addr)?; 31 | tcp_socket.listen(1024) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /server/src/server.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | use futures_rustls::rustls::ServerConfig; 4 | use futures_util::StreamExt; 5 | use hyper_util::rt::TokioTimer; 6 | use protocol::accept::Executor; 7 | use protocol::auth::Auth; 8 | use protocol::HyperListener; 9 | use share::dns::HickoryDnsResolver; 10 | use share::proxy; 11 | use tap::TapFallible; 12 | use tokio::net::{TcpListener, TcpStream}; 13 | use tracing::{error, info}; 14 | 15 | use self::hidden::*; 16 | use crate::Error; 17 | 18 | mod hidden { 19 | use std::io; 20 | 21 | use futures_util::{Stream, TryStreamExt}; 22 | use share::tcp_wrapper::{TcpListenerAddrStream, TokioTcp}; 23 | use tokio::net::TcpListener; 24 | 25 | pub type TokioTcpAcceptor = impl Stream> + Send + Unpin; 26 | 27 | pub fn new_it(tcp_listener: TcpListener) -> TokioTcpAcceptor { 28 | TcpListenerAddrStream::from(tcp_listener).map_ok(|(stream, _)| TokioTcp::from(stream)) 29 | } 30 | } 31 | 32 | pub struct HyperServer { 33 | protocol_listener: HyperListener, 34 | } 35 | 36 | #[derive(Clone)] 37 | struct TokioExecutorWrapper; 38 | 39 | impl Executor for TokioExecutorWrapper { 40 | fn execute(&self, fut: Fut) 41 | where 42 | Fut: Future + Send + 'static, 43 | Fut::Output: Send + 'static, 44 | { 45 | tokio::spawn(fut); 46 | } 47 | } 48 | 49 | impl HyperServer { 50 | pub fn new( 51 | token_header: String, 52 | auth: Auth, 53 | tcp_listener: TcpListener, 54 | server_tls_config: ServerConfig, 55 | ) -> Result { 56 | let tcp_listener = new_it(tcp_listener); 57 | 58 | let protocol_listener = HyperListener::new( 59 | tcp_listener, 60 | TokioExecutorWrapper, 61 | server_tls_config, 62 | token_header, 63 | auth, 64 | HickoryDnsResolver::new()?, 65 | TokioTimer::new(), 66 | ); 67 | 68 | Ok(Self { protocol_listener }) 69 | } 70 | 71 | pub async fn start(self) -> Result<(), Error> { 72 | let (task, mut acceptor) = self.protocol_listener.listen(); 73 | tokio::spawn(task); 74 | 75 | while let Some(res) = acceptor.next().await { 76 | let addrs = res.0; 77 | let writer = res.1; 78 | let reader = res.2; 79 | 80 | tokio::spawn(async move { 81 | let tcp_stream = TcpStream::connect(addrs.as_slice()) 82 | .await 83 | .tap_err(|err| error!(?addrs, %err, "connect to target failed"))?; 84 | 85 | info!(?addrs, "connect to remote done"); 86 | 87 | let (remote_in_tcp, remote_out_tcp) = tcp_stream.into_split(); 88 | 89 | proxy::proxy(remote_in_tcp, remote_out_tcp, reader, writer).await 90 | }); 91 | } 92 | 93 | Err(Error::Other("acceptor stopped".into())) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /server/tests/ca.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID9TCCAl2gAwIBAgIBATANBgkqhkiG9w0BAQsFADAcMQ0wCwYDVQQKEwR0ZXN0 3 | MQswCQYDVQQDEwJDQTAeFw0yMjA3MjUxMDMyNDNaFw0zMjA3MjIxMDMyNDNaMBwx 4 | DTALBgNVBAoTBHRlc3QxCzAJBgNVBAMTAkNBMIIBojANBgkqhkiG9w0BAQEFAAOC 5 | AY8AMIIBigKCAYEApwVUeJEgCQQ8X3D5nzORoOZtMUwPA8y+5tChpSZ28CTbQiEP 6 | lxbJSZCRsEdb066YRNG6ztq62ZJEf3/+Tw0AcFvcMW8XzNajDE994+MWpujuoWiU 7 | dBMnTUHsDzTyPH6Z8BYY2J6ODNv01Nyy8/0x8x/l6wLAlib2B7ZcLtPDtWl+jVyS 8 | 6Ed7+avxluz4Iw4FLPaThOHjJdqrnpsNRHhSnIy2W5w2ZixPwHCw16tQGWS+yfj1 9 | v7iyP3Ak/VO4Edz0+x+kNSqKzLok9rCWgf+2pPt2gp5TfyAjFEanJ17Dwj7X7rk2 10 | no/QQZFMCzpD+9nSbo804kOVjs5gx6susMvzZPSFFt8qwHCxPUY9ImQuD7ep2frn 11 | R0yl1oSC+7sQtzN2YrlSW8wMfb6+BYrZmnNkgbm0ZD1D36C15ZP3Buw88lDSmJ8n 12 | H3tGioioqiOOGJ03hyTVJd5wJNAw9aGJaF8S6zXLqSUcop0leX2OGD7wAdu782NX 13 | kVByrddEFrLyxqh7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ 14 | BAQDAgGGMB0GA1UdDgQWBBRuJMLilbA/fe+/664fCXRKtpqOGTANBgkqhkiG9w0B 15 | AQsFAAOCAYEAFN/tBSPTL4ehpsCpTwfe26a/yxlzu75G9pRZTNHj3HRpU75Ns9Gk 16 | TNdo/DIvbL+M9NnMFvZrgis0ZvmM+u0ZUlzomuHo2WSTy/J5UhIAxi4sQTygCfLj 17 | nCjhSk14bhvirKlPLk6qJb/MDGKSigeU+QS/MZ4ogTsXKBed3+MVH3dSRYRR6z4E 18 | rU205DE8HdDyxSz5D+eCNm4VjHpeRyZUzsTbGQQ7jVqqMsk0FhBBOTCxO9YuKkOe 19 | 6YfmpHsPyAC6zFdiRmmOdlXdlQlLOidD3LG6BB8DqpGPI7ZjaKZjFIdOQr90ucfn 20 | gCeMXmX97M5d5DcyS6JYaGeXG7Kyg5TRMPA7ggOlADHv4p6iW4hO2E9BBfkdR/uf 21 | xn+OOWogD6bDZg0IjwFlCbHM1GWmIPpdaHmvlSv5JCiys4ehUSuqKb63el8QxCTP 22 | aOkr1NkY58y73EffEaPMX5qoNV4WBcfUC0FGGoHU0C5o+AqbnELBvUltmaJp62Yb 23 | 8emij/r56/1u 24 | -----END CERTIFICATE----- 25 | -------------------------------------------------------------------------------- /server/tests/ca.info: -------------------------------------------------------------------------------- 1 | cn = "CA" 2 | organization = "test" 3 | serial = 1 4 | ca 5 | signing_key 6 | cert_signing_key 7 | crl_signing_key 8 | expiration_days = 3650 9 | -------------------------------------------------------------------------------- /server/tests/ca.key: -------------------------------------------------------------------------------- 1 | Public Key Info: 2 | Public Key Algorithm: RSA 3 | Key Security Level: High (3072 bits) 4 | 5 | modulus: 6 | 00:a7:05:54:78:91:20:09:04:3c:5f:70:f9:9f:33:91 7 | a0:e6:6d:31:4c:0f:03:cc:be:e6:d0:a1:a5:26:76:f0 8 | 24:db:42:21:0f:97:16:c9:49:90:91:b0:47:5b:d3:ae 9 | 98:44:d1:ba:ce:da:ba:d9:92:44:7f:7f:fe:4f:0d:00 10 | 70:5b:dc:31:6f:17:cc:d6:a3:0c:4f:7d:e3:e3:16:a6 11 | e8:ee:a1:68:94:74:13:27:4d:41:ec:0f:34:f2:3c:7e 12 | 99:f0:16:18:d8:9e:8e:0c:db:f4:d4:dc:b2:f3:fd:31 13 | f3:1f:e5:eb:02:c0:96:26:f6:07:b6:5c:2e:d3:c3:b5 14 | 69:7e:8d:5c:92:e8:47:7b:f9:ab:f1:96:ec:f8:23:0e 15 | 05:2c:f6:93:84:e1:e3:25:da:ab:9e:9b:0d:44:78:52 16 | 9c:8c:b6:5b:9c:36:66:2c:4f:c0:70:b0:d7:ab:50:19 17 | 64:be:c9:f8:f5:bf:b8:b2:3f:70:24:fd:53:b8:11:dc 18 | f4:fb:1f:a4:35:2a:8a:cc:ba:24:f6:b0:96:81:ff:b6 19 | a4:fb:76:82:9e:53:7f:20:23:14:46:a7:27:5e:c3:c2 20 | 3e:d7:ee:b9:36:9e:8f:d0:41:91:4c:0b:3a:43:fb:d9 21 | d2:6e:8f:34:e2:43:95:8e:ce:60:c7:ab:2e:b0:cb:f3 22 | 64:f4:85:16:df:2a:c0:70:b1:3d:46:3d:22:64:2e:0f 23 | b7:a9:d9:fa:e7:47:4c:a5:d6:84:82:fb:bb:10:b7:33 24 | 76:62:b9:52:5b:cc:0c:7d:be:be:05:8a:d9:9a:73:64 25 | 81:b9:b4:64:3d:43:df:a0:b5:e5:93:f7:06:ec:3c:f2 26 | 50:d2:98:9f:27:1f:7b:46:8a:88:a8:aa:23:8e:18:9d 27 | 37:87:24:d5:25:de:70:24:d0:30:f5:a1:89:68:5f:12 28 | eb:35:cb:a9:25:1c:a2:9d:25:79:7d:8e:18:3e:f0:01 29 | db:bb:f3:63:57:91:50:72:ad:d7:44:16:b2:f2:c6:a8 30 | 7b: 31 | 32 | public exponent: 33 | 01:00:01: 34 | 35 | private exponent: 36 | 00:a5:1d:52:cd:c2:82:ce:07:1e:fc:27:7e:cf:51:36 37 | d5:bf:17:11:96:24:7d:d8:2a:9d:9a:81:42:f8:4c:ba 38 | c9:64:ec:f2:2c:2c:6c:bd:5f:79:89:5d:a0:9d:fa:1d 39 | ef:b0:91:3a:41:86:e1:13:78:53:39:4c:ba:87:41:ce 40 | 3e:45:c1:e0:28:f8:79:6b:73:53:17:3e:d8:4c:46:b7 41 | 89:2d:67:ea:30:60:b4:12:b5:80:c7:32:9d:60:20:47 42 | f4:3c:f2:4b:6b:d3:0b:91:50:dd:b0:95:e2:5f:ec:de 43 | 35:81:42:55:db:a9:b2:87:1f:d0:ae:0a:61:cc:bd:fc 44 | 99:27:1f:ee:f5:d8:a5:da:bd:0f:ab:cd:4d:c5:5d:3a 45 | 5e:09:7e:56:86:16:9d:ed:6d:49:1e:8a:20:4a:a1:3c 46 | e2:41:05:82:79:63:1a:0d:12:66:a7:c1:5b:e4:a3:52 47 | 07:53:c9:ed:d2:5c:cf:e5:7f:6b:89:55:db:2e:ec:91 48 | e7:11:41:7a:ed:27:66:fc:c3:9c:8d:07:75:32:cc:57 49 | 39:7b:9c:b6:a0:b8:e4:0d:a7:fa:d2:84:d4:ff:74:2b 50 | 96:8c:05:e6:6d:85:fc:0e:4a:48:c1:b1:53:51:3c:a5 51 | e6:b9:8c:92:be:b1:58:2d:df:e5:02:de:29:59:24:4d 52 | 70:c3:10:5e:cb:24:7e:36:d3:0a:68:8b:be:c2:2f:1c 53 | e8:f8:19:55:d6:11:71:8c:00:f2:d7:84:f2:48:bb:ca 54 | c8:ca:ca:76:45:a4:d3:dc:1e:87:1f:29:85:a4:de:e4 55 | 07:e7:5e:d6:74:cd:b1:72:26:62:0d:ab:16:c4:1e:c2 56 | 14:4b:14:0f:1b:2b:ac:6c:83:f7:95:2d:24:71:99:1c 57 | 79:47:d7:30:03:45:03:76:7f:09:28:6f:09:33:75:30 58 | e2:73:49:75:e5:27:bd:d8:10:44:ba:f1:4f:51:64:ac 59 | e7:e8:83:58:18:23:f8:16:06:4a:8e:78:eb:4b:39:8c 60 | e1: 61 | 62 | prime1: 63 | 00:c7:53:b1:42:bf:bb:35:72:01:f2:0b:e0:4c:8e:7b 64 | 58:e4:89:f7:f0:af:9a:e4:38:0e:36:a5:84:bd:76:38 65 | 21:19:4f:c6:95:ed:97:47:bd:0d:0c:76:01:25:d8:df 66 | 05:44:5d:b1:ba:dd:d9:a3:c9:17:44:ae:1c:dc:5f:0a 67 | ba:c0:b3:4f:d0:11:f1:89:2e:b7:ab:c1:a2:ac:dc:d9 68 | b1:69:9f:b8:71:6f:2b:0e:3b:95:f4:73:8c:9a:bb:74 69 | 03:c8:e9:df:7b:f3:e1:46:cd:25:d4:36:01:25:69:ce 70 | 6b:ce:6f:04:32:0c:b1:9d:7d:b3:36:f0:28:e6:65:11 71 | 09:ed:4e:5e:8d:38:fd:ad:c0:c2:fe:7b:68:2e:5a:43 72 | e0:97:9b:e5:da:43:cc:4e:9c:20:61:42:27:0f:59:c4 73 | 06:7f:1d:7c:c7:f8:e9:c0:d9:f2:cf:c4:ab:6a:91:5b 74 | 4f:d7:bf:c8:0b:78:32:bf:1a:17:06:ef:bd:39:0b:c9 75 | f1: 76 | 77 | prime2: 78 | 00:d6:82:30:89:80:57:46:08:74:e2:e4:37:48:d3:40 79 | 4a:d7:72:1e:35:b2:61:bd:98:62:72:14:27:25:b2:b2 80 | 3f:20:6a:de:35:b0:54:14:5f:44:82:9e:c0:ea:d5:50 81 | 08:7e:de:4b:07:5c:29:1c:6c:5e:6a:38:a0:66:22:6e 82 | c3:6e:f0:26:d5:0c:22:91:51:c4:1a:44:37:18:7b:de 83 | 67:7e:fa:0e:2d:e2:e1:57:ad:e7:11:35:b4:ff:e2:0e 84 | f8:0e:a8:26:22:d1:7f:3e:30:59:10:6f:35:94:5f:7c 85 | d7:76:a0:44:70:05:2c:d9:fd:0e:97:98:a0:ea:53:0e 86 | 80:d0:22:2a:72:05:58:07:fb:d8:ae:87:b1:56:b8:77 87 | 19:61:9b:53:1a:61:fc:cb:44:a8:57:be:46:9a:92:30 88 | a3:cb:12:c4:b7:14:87:87:8b:1c:6c:86:f5:9f:d4:58 89 | ae:18:97:39:5e:dd:d1:72:12:3a:b7:c7:4d:d6:b2:8d 90 | 2b: 91 | 92 | coefficient: 93 | 0d:d5:8c:f4:25:39:8b:ba:df:c5:48:9d:2a:da:e4:40 94 | 4b:53:c3:09:54:16:15:3e:66:68:6d:18:be:19:19:fe 95 | 3e:8d:1b:8c:97:65:1c:ef:cd:c9:55:bc:7e:9a:d0:0f 96 | c0:d4:36:71:a2:30:c5:b0:9d:88:c1:69:d6:4c:ff:3a 97 | 85:a9:95:31:2c:00:14:5d:b0:a8:5a:39:82:67:8e:bf 98 | b7:9f:32:0c:c6:52:f5:4b:1c:7a:b1:04:53:c2:0c:56 99 | 6b:ef:e0:09:71:9b:05:f6:5f:48:45:fc:7f:b7:22:e5 100 | 78:77:60:23:0c:f2:a9:be:07:50:3d:a1:56:60:e0:b6 101 | c3:8b:2f:85:96:d6:70:49:30:90:5a:9a:2c:4a:35:3b 102 | 59:f0:0f:00:a2:3f:b6:c5:7c:55:43:ab:80:d4:d5:b1 103 | 30:95:8c:a8:a5:1c:70:36:f6:26:76:fb:06:1d:6f:f2 104 | 51:89:6c:6f:0c:fb:d4:e4:f2:fb:9c:64:99:b7:14:45 105 | 106 | 107 | exp1: 108 | 10:ec:2e:14:78:aa:b0:32:e9:af:5f:0a:bf:f5:28:cb 109 | b7:3b:6f:6d:63:bb:46:bc:eb:98:00:1d:70:e8:64:83 110 | 8a:de:97:01:8b:96:58:64:d8:82:c2:51:fe:a0:96:ee 111 | 9f:8b:44:d7:94:96:31:ad:94:e8:23:d0:17:75:8a:37 112 | a8:bc:90:7e:1d:c7:df:d9:b3:86:45:7e:46:26:c9:e4 113 | a2:0f:09:5d:b8:0e:73:ba:e3:54:49:ab:1a:47:e7:e5 114 | 9a:5d:20:b8:29:50:6a:ee:67:d3:c8:71:f8:77:2f:2b 115 | 3f:ef:74:ef:ba:dc:29:7e:ce:84:4f:87:69:68:13:36 116 | 40:8b:72:c3:92:9f:16:ed:52:a0:c0:61:6b:ee:05:ed 117 | 3f:71:e0:e5:d2:f3:b9:88:cf:c6:f2:12:d6:de:cc:21 118 | b1:11:13:d2:f6:73:02:08:7e:f0:dc:c5:a0:da:8e:62 119 | 0c:f3:1f:4c:b9:fb:b4:f5:c6:16:b4:ab:df:f7:fc:41 120 | 121 | 122 | exp2: 123 | 00:9c:ad:f9:3f:e5:e3:59:3f:02:09:5c:02:59:8a:1b 124 | f2:ce:8d:07:2d:d7:7e:76:da:a0:45:8d:1c:ce:f7:48 125 | 24:bd:cb:f1:44:ab:e7:ad:fd:ab:ef:f0:7f:1e:c7:f2 126 | 2e:52:64:91:ce:ed:6d:05:da:cd:d7:ae:46:d2:73:22 127 | 0e:53:b0:cb:dc:5f:81:bc:3d:4a:0c:99:78:d8:c4:59 128 | 3d:a5:4c:86:20:d4:10:1e:57:e2:67:fa:9f:9b:2b:86 129 | 1d:7f:af:28:33:d4:6b:a9:83:ec:fa:09:10:cc:b0:36 130 | 62:b6:bd:f7:5f:0f:18:a4:9c:a7:14:e9:a1:d7:a6:14 131 | f8:d7:8d:ad:0d:1d:84:20:48:bf:0b:34:07:bf:3c:fb 132 | 99:29:67:27:7b:24:c9:5d:bb:67:6e:b7:87:a0:8d:59 133 | 43:ba:27:9a:51:d3:87:ee:e4:18:3d:d9:11:fd:cd:3f 134 | 7c:e2:6f:4a:b3:55:38:0f:a6:eb:06:a3:67:e2:83:06 135 | 83: 136 | 137 | 138 | Public Key PIN: 139 | pin-sha256:r6Br0erwX5scnSLsQiZ6ulwC60ednQTN7nkbNpl+XRY= 140 | Public Key ID: 141 | sha256:afa06bd1eaf05f9b1c9d22ec42267aba5c02eb479d9d04cdee791b36997e5d16 142 | sha1:6e24c2e295b03f7defbfebae1f09744ab69a8e19 143 | 144 | -----BEGIN RSA PRIVATE KEY----- 145 | MIIG5AIBAAKCAYEApwVUeJEgCQQ8X3D5nzORoOZtMUwPA8y+5tChpSZ28CTbQiEP 146 | lxbJSZCRsEdb066YRNG6ztq62ZJEf3/+Tw0AcFvcMW8XzNajDE994+MWpujuoWiU 147 | dBMnTUHsDzTyPH6Z8BYY2J6ODNv01Nyy8/0x8x/l6wLAlib2B7ZcLtPDtWl+jVyS 148 | 6Ed7+avxluz4Iw4FLPaThOHjJdqrnpsNRHhSnIy2W5w2ZixPwHCw16tQGWS+yfj1 149 | v7iyP3Ak/VO4Edz0+x+kNSqKzLok9rCWgf+2pPt2gp5TfyAjFEanJ17Dwj7X7rk2 150 | no/QQZFMCzpD+9nSbo804kOVjs5gx6susMvzZPSFFt8qwHCxPUY9ImQuD7ep2frn 151 | R0yl1oSC+7sQtzN2YrlSW8wMfb6+BYrZmnNkgbm0ZD1D36C15ZP3Buw88lDSmJ8n 152 | H3tGioioqiOOGJ03hyTVJd5wJNAw9aGJaF8S6zXLqSUcop0leX2OGD7wAdu782NX 153 | kVByrddEFrLyxqh7AgMBAAECggGBAKUdUs3Cgs4HHvwnfs9RNtW/FxGWJH3YKp2a 154 | gUL4TLrJZOzyLCxsvV95iV2gnfod77CROkGG4RN4UzlMuodBzj5FweAo+Hlrc1MX 155 | PthMRreJLWfqMGC0ErWAxzKdYCBH9DzyS2vTC5FQ3bCV4l/s3jWBQlXbqbKHH9Cu 156 | CmHMvfyZJx/u9dil2r0Pq81NxV06Xgl+VoYWne1tSR6KIEqhPOJBBYJ5YxoNEman 157 | wVvko1IHU8nt0lzP5X9riVXbLuyR5xFBeu0nZvzDnI0HdTLMVzl7nLaguOQNp/rS 158 | hNT/dCuWjAXmbYX8DkpIwbFTUTyl5rmMkr6xWC3f5QLeKVkkTXDDEF7LJH420wpo 159 | i77CLxzo+BlV1hFxjADy14TySLvKyMrKdkWk09wehx8phaTe5AfnXtZ0zbFyJmIN 160 | qxbEHsIUSxQPGyusbIP3lS0kcZkceUfXMANFA3Z/CShvCTN1MOJzSXXlJ73YEES6 161 | 8U9RZKzn6INYGCP4FgZKjnjrSzmM4QKBwQDHU7FCv7s1cgHyC+BMjntY5In38K+a 162 | 5DgONqWEvXY4IRlPxpXtl0e9DQx2ASXY3wVEXbG63dmjyRdErhzcXwq6wLNP0BHx 163 | iS63q8GirNzZsWmfuHFvKw47lfRzjJq7dAPI6d978+FGzSXUNgElac5rzm8EMgyx 164 | nX2zNvAo5mURCe1OXo04/a3Awv57aC5aQ+CXm+XaQ8xOnCBhQicPWcQGfx18x/jp 165 | wNnyz8SrapFbT9e/yAt4Mr8aFwbvvTkLyfECgcEA1oIwiYBXRgh04uQ3SNNAStdy 166 | HjWyYb2YYnIUJyWysj8gat41sFQUX0SCnsDq1VAIft5LB1wpHGxeajigZiJuw27w 167 | JtUMIpFRxBpENxh73md++g4t4uFXrecRNbT/4g74DqgmItF/PjBZEG81lF9813ag 168 | RHAFLNn9DpeYoOpTDoDQIipyBVgH+9iuh7FWuHcZYZtTGmH8y0SoV75GmpIwo8sS 169 | xLcUh4eLHGyG9Z/UWK4Ylzle3dFyEjq3x03Wso0rAoHAEOwuFHiqsDLpr18Kv/Uo 170 | y7c7b21ju0a865gAHXDoZIOK3pcBi5ZYZNiCwlH+oJbun4tE15SWMa2U6CPQF3WK 171 | N6i8kH4dx9/Zs4ZFfkYmyeSiDwlduA5zuuNUSasaR+flml0guClQau5n08hx+Hcv 172 | Kz/vdO+63Cl+zoRPh2loEzZAi3LDkp8W7VKgwGFr7gXtP3Hg5dLzuYjPxvIS1t7M 173 | IbERE9L2cwIIfvDcxaDajmIM8x9Mufu09cYWtKvf9/xBAoHBAJyt+T/l41k/Aglc 174 | AlmKG/LOjQct13522qBFjRzO90gkvcvxRKvnrf2r7/B/HsfyLlJkkc7tbQXazdeu 175 | RtJzIg5TsMvcX4G8PUoMmXjYxFk9pUyGINQQHlfiZ/qfmyuGHX+vKDPUa6mD7PoJ 176 | EMywNmK2vfdfDxiknKcU6aHXphT4142tDR2EIEi/CzQHvzz7mSlnJ3skyV27Z263 177 | h6CNWUO6J5pR04fu5Bg92RH9zT984m9Ks1U4D6brBqNn4oMGgwKBwA3VjPQlOYu6 178 | 38VInSra5EBLU8MJVBYVPmZobRi+GRn+Po0bjJdlHO/NyVW8fprQD8DUNnGiMMWw 179 | nYjBadZM/zqFqZUxLAAUXbCoWjmCZ46/t58yDMZS9UscerEEU8IMVmvv4AlxmwX2 180 | X0hF/H+3IuV4d2AjDPKpvgdQPaFWYOC2w4svhZbWcEkwkFqaLEo1O1nwDwCiP7bF 181 | fFVDq4DU1bEwlYyopRxwNvYmdvsGHW/yUYlsbwz71OTy+5xkmbcURQ== 182 | -----END RSA PRIVATE KEY----- 183 | -------------------------------------------------------------------------------- /server/tests/proxy.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | use std::net::IpAddr; 3 | use std::path::Path; 4 | 5 | use bytes::{BufMut, Bytes, BytesMut}; 6 | use futures_channel::mpsc; 7 | use futures_rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer}; 8 | use futures_rustls::rustls::{ClientConfig, RootCertStore, ServerConfig}; 9 | use futures_util::SinkExt; 10 | use http::{HeaderMap, HeaderValue, Request, Uri}; 11 | use http_body_util::{BodyExt, StreamBody}; 12 | use hyper::body::Frame; 13 | use hyper_rustls::{FixedServerNameResolver, HttpsConnectorBuilder}; 14 | use hyper_util::client::legacy::Client; 15 | use hyper_util::rt::{TokioExecutor, TokioTimer}; 16 | use lycoris_server::{HyperServer, MptcpListenerExt}; 17 | use protocol::auth::Auth; 18 | use share::log::init_log; 19 | use tokio::fs; 20 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 21 | use tokio::net::TcpListener; 22 | use totp_rs::{Algorithm, TOTP}; 23 | use tracing::debug; 24 | 25 | #[tokio::test] 26 | async fn main() { 27 | const TOTP_SECRET: &str = "test-secrettest-secret"; 28 | const TOTP_HEADER: &str = "x-secret"; 29 | 30 | init_log(true); 31 | 32 | let auth = Auth::new(TOTP_SECRET.to_string(), None).unwrap(); 33 | 34 | let mut keys = load_keys(Path::new("tests/server.key")).await; 35 | let certs = load_certs(Path::new("tests/server.cert")).await; 36 | let server_config = ServerConfig::builder() 37 | .with_no_client_auth() 38 | .with_single_cert(certs, keys.remove(0).into()) 39 | .unwrap(); 40 | let client_config = create_client_config().await; 41 | 42 | let listener = TcpListener::listen_mptcp("127.0.0.1:0".parse().unwrap()) 43 | .await 44 | .unwrap(); 45 | let addr = listener.local_addr().unwrap(); 46 | let server = HyperServer::new(TOTP_HEADER.to_string(), auth, listener, server_config).unwrap(); 47 | 48 | tokio::spawn(async move { 49 | if let Err(err) = server.start().await { 50 | eprintln!("{:?}", err); 51 | } 52 | }); 53 | 54 | let https_connector = HttpsConnectorBuilder::new() 55 | .with_tls_config(client_config) 56 | .https_only() 57 | .with_server_name_resolver(FixedServerNameResolver::new( 58 | "localhost".try_into().unwrap(), 59 | )) 60 | .enable_http2() 61 | .build(); 62 | let client = Client::builder(TokioExecutor::new()) 63 | .timer(TokioTimer::new()) 64 | .http2_only(true) 65 | .build(https_connector); 66 | 67 | let totp = create_totp(TOTP_SECRET.to_string()); 68 | let secret = totp.generate_current().unwrap(); 69 | 70 | let remote_listener = TcpListener::listen_mptcp("127.0.0.1:0".parse().unwrap()) 71 | .await 72 | .unwrap(); 73 | let remote_addr = remote_listener.local_addr().unwrap(); 74 | 75 | let task = tokio::spawn(async move { 76 | let (mut accepted_stream, _) = remote_listener.accept().await.unwrap(); 77 | 78 | accepted_stream.write_all(b"test").await.unwrap(); 79 | 80 | let mut buf = [0; 4]; 81 | 82 | accepted_stream.read_exact(&mut buf).await.unwrap(); 83 | 84 | assert_eq!(&buf, b"test"); 85 | assert_eq!(accepted_stream.read(&mut [0; 1]).await.unwrap(), 0); 86 | }); 87 | 88 | let remote_ip = if let IpAddr::V4(remote_ip) = remote_addr.ip() { 89 | remote_ip.octets() 90 | } else { 91 | panic!("{} is not ipv4", remote_addr.ip()); 92 | }; 93 | 94 | let (mut tx, rx) = mpsc::unbounded(); 95 | let mut request = Request::new(StreamBody::new(rx)); 96 | *request.uri_mut() = Uri::try_from(format!("https://localhost:{}", addr.port())).unwrap(); 97 | request 98 | .headers_mut() 99 | .append(TOTP_HEADER, HeaderValue::from_str(&secret).unwrap()); 100 | 101 | let mut buf = BytesMut::with_capacity(1 + 4 + 2); 102 | buf.put_u8(4); 103 | buf.put(remote_ip.as_slice()); 104 | buf.put_u16(remote_addr.port()); 105 | 106 | tx.send(Ok::<_, Infallible>(Frame::data(buf.freeze()))) 107 | .await 108 | .unwrap(); 109 | 110 | let response = client.request(request).await.unwrap(); 111 | 112 | let mut recv_stream = response.into_body(); 113 | 114 | let data = recv_stream 115 | .frame() 116 | .await 117 | .unwrap() 118 | .unwrap() 119 | .into_data() 120 | .unwrap(); 121 | assert_eq!(data.as_ref(), b"test"); 122 | 123 | tx.send(Ok(Frame::data(Bytes::from_static(b"test")))) 124 | .await 125 | .unwrap(); 126 | tx.send(Ok(Frame::trailers(HeaderMap::new()))) 127 | .await 128 | .unwrap(); 129 | 130 | debug!("send trailers done"); 131 | 132 | drop(tx); 133 | 134 | task.await.unwrap(); 135 | 136 | debug!("task done"); 137 | 138 | let trailers = recv_stream 139 | .frame() 140 | .await 141 | .unwrap() 142 | .unwrap() 143 | .into_trailers() 144 | .unwrap(); 145 | assert!(trailers.is_empty()); 146 | } 147 | 148 | async fn load_certs(path: &Path) -> Vec { 149 | let certs = fs::read(path).await.unwrap(); 150 | 151 | rustls_pemfile::certs(&mut certs.as_slice()) 152 | .collect::, _>>() 153 | .unwrap() 154 | } 155 | 156 | async fn load_keys(path: &Path) -> Vec { 157 | let keys = fs::read(path).await.unwrap(); 158 | 159 | rustls_pemfile::pkcs8_private_keys(&mut keys.as_slice()) 160 | .collect::, _>>() 161 | .unwrap() 162 | } 163 | 164 | async fn create_client_config() -> ClientConfig { 165 | let mut root_cert_store = RootCertStore::empty(); 166 | let ca_certs = fs::read("tests/ca.cert").await.unwrap(); 167 | 168 | for ca_cert in rustls_pemfile::certs(&mut ca_certs.as_slice()) { 169 | root_cert_store.add(ca_cert.unwrap()).unwrap(); 170 | } 171 | 172 | ClientConfig::builder() 173 | .with_root_certificates(root_cert_store) 174 | .with_no_client_auth() 175 | } 176 | 177 | fn create_totp(secret: String) -> TOTP { 178 | TOTP::new( 179 | Algorithm::SHA512, 180 | 8, 181 | 1, 182 | 30, 183 | secret.into(), 184 | None, 185 | "default_account".to_string(), 186 | ) 187 | .unwrap() 188 | } 189 | -------------------------------------------------------------------------------- /server/tests/server.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEWjCCAsKgAwIBAgIUZ3eAw4IW0LTmeYzD67n45zhVG7swDQYJKoZIhvcNAQEL 3 | BQAwHDENMAsGA1UEChMEdGVzdDELMAkGA1UEAxMCQ0EwHhcNMjIwOTExMTA0MTEy 4 | WhcNMzIwOTA4MTA0MTEyWjAjMQ0wCwYDVQQKEwR0ZXN0MRIwEAYDVQQDEwlsb2Nh 5 | bGhvc3QwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQC1p9iWk6v0J0dV 6 | jPuC6jVpgfx+pA3WIRZ5PI/5Y4yPszP3amJQzmD9ndT24DfCc843h854fMYPPL4v 7 | 2Qq6kx9hh82mLgte6cYpWpybP8uRfgxo2TW6Dy1plEvzIKiWWAxV1V1/aLMVEtp9 8 | R0DwMR87d3qDXDIIlSySg5R0jtnul1RnqNqiU1QP/rsvvmoN1c748TOhLMfeaCNL 9 | vw6Di12UWgmISOuidBKK3ubUnrz0PY4SLoOK9JU5ih9gw1eL0g/YGoEP8bh2UhKz 10 | lE5jIfEmcZou/dz+zkdSXFrIk3hnDihU8TbqDKXleQY7DhQV7nkaJZ+H+NOB4ySb 11 | wENRDQAbR+rzNmaRH5I1WJ+c7U4aLD/2OLsL2wHtQURHZXGm8jsjJ9Wv0SMVxW1l 12 | UPGwVY9mY+Syr45INR6CllmNUJvXKLV/WF4iG4xR5Rm5BPzVEYaRx+IcFXDecwNC 13 | GLC5qd9/cM83r4O+aDr7x6kJ4U6eRVvmShv8OysQoF/V6cTfI/0CAwEAAaOBjDCB 14 | iTAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDATBgNVHSUEDDAK 15 | BggrBgEFBQcDATAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0OBBYEFL2ThpqUFaQ8O1f1 16 | q04ljZnN1+w9MB8GA1UdIwQYMBaAFG4kwuKVsD9977/rrh8JdEq2mo4ZMA0GCSqG 17 | SIb3DQEBCwUAA4IBgQBvQ/QX61p+tszPvyArjh8u7j2M125q9PrKd6yF3gWATudn 18 | waszwyap6xhiqIEdPZtSr+dOOnGamson/7q4VfwTUw3FxXGPa20rbpz7vThTc5G7 19 | 5kJpw6deIcwDgZtClmGxYC1MdSzhRZ1NCaveisKmadhiqZ/XqreA8rhuvDetbmIa 20 | rw9flgmlVZ5qX+I+aJdsvjlB0bCd9T9me0fHsjZy4eFxgUck8lCtuE4/WerWUOfu 21 | TE2BhB6lllVgaaPA2C881Gs1cK29CDVkeEDouAsLlcpctunlrndEC5mFml8WCRkw 22 | 8wZJdpaJWAri3V+ojge4cyPZCR5SE0wfI7E+TL7o4mifFW6xPB9x3DC4W4ba7+vT 23 | 4I9X8okhTLvqkxc7mubRoYnDGLqpKzZGSkaeuiSF54ywOwlmO10DmrhdmxHyYqMx 24 | AywfsaLgNJwWN2M8XgoIsInxbzyb9cGjmV4zPMu7gDjc4Mk+UjWXNxiPsmTbd33H 25 | oyQ2i9THWhhuZ9D/FF8= 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /server/tests/server.info: -------------------------------------------------------------------------------- 1 | organization = "test" 2 | cn = "localhost" 3 | dns_name = "localhost" 4 | tls_www_server 5 | encryption_key 6 | signing_key 7 | expiration_days = 3650 8 | -------------------------------------------------------------------------------- /server/tests/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQC1p9iWk6v0J0dV 3 | jPuC6jVpgfx+pA3WIRZ5PI/5Y4yPszP3amJQzmD9ndT24DfCc843h854fMYPPL4v 4 | 2Qq6kx9hh82mLgte6cYpWpybP8uRfgxo2TW6Dy1plEvzIKiWWAxV1V1/aLMVEtp9 5 | R0DwMR87d3qDXDIIlSySg5R0jtnul1RnqNqiU1QP/rsvvmoN1c748TOhLMfeaCNL 6 | vw6Di12UWgmISOuidBKK3ubUnrz0PY4SLoOK9JU5ih9gw1eL0g/YGoEP8bh2UhKz 7 | lE5jIfEmcZou/dz+zkdSXFrIk3hnDihU8TbqDKXleQY7DhQV7nkaJZ+H+NOB4ySb 8 | wENRDQAbR+rzNmaRH5I1WJ+c7U4aLD/2OLsL2wHtQURHZXGm8jsjJ9Wv0SMVxW1l 9 | UPGwVY9mY+Syr45INR6CllmNUJvXKLV/WF4iG4xR5Rm5BPzVEYaRx+IcFXDecwNC 10 | GLC5qd9/cM83r4O+aDr7x6kJ4U6eRVvmShv8OysQoF/V6cTfI/0CAwEAAQKCAYEA 11 | rJP3aqaEuIIQfTBhSfCkKcZ6UP4evw3UQf3NckqnIWZIuodxO/+1mXy29IvzrWND 12 | yPHWsBUqPROiQ+bZEIPxTkEsmPIpj1RdFLCnE7858NFljL6/EjKVHDbtKOAgCiVA 13 | ItN/7ilT6XNt65MkGI0ydNZ/qAiQ6CKAo/kpssp8NPXEFG8QtyKfPErvHt6x9s7n 14 | wQ211Sur/pnE3pN2gHaIwwX/BGDyq/XGH/TAK8NbFFbegFt8USkoE/Aq+0zaAVXg 15 | aYLloxvo4lMA6G97n0YRCx+WOu2k90/XGfsppcFD9nTm5bjU3tB+sIjHsyLQjwdz 16 | 2HeAcyxDEMGa74WCbjLjUDHMdm927vcCEGPbFFFDEdVeYcrDjeDvYL3S+G4AjVV1 17 | Fb0LZ6nHNOXJd9MSoUVxN12Ye9U3Hh2gCDSSluLVkGwJwZ8pbzZPjqReH/vfMCx7 18 | KjCL3y0GPPxB+6qV74TcVKIa8JOlwMbyJn6I+xJaqKzr0G8G6eTfDlTKq8yBOfYB 19 | AoHBAM8Z9bPOo8b56pGH9giM42R3xLsFy/GGb9tfFY9b6Om2E8Vj3aUInImXRsh2 20 | Tc1QSDCBxr0rGWkFamyEwH0exGzaajliTnFwJAw2YqZcaLCcFLg/nxJikXlIUIX0 21 | me2Rt7rvx98tNteC9Y0PC+moJ/lcRTgKKMs6Ob1d+Tn459OUNxjEJdFZetxU19vi 22 | xd6ntThUD/Zb5ZVM0vc/eE6jrvwp2DjfxA5BCYay0Yey2U3mcFT794GwZXUqUwfy 23 | VGCfPQKBwQDgi9e0g82vEs3zQYZq8I+9Hx73og0Zyw80AmUiaPejmGn5R8kGoMqa 24 | QgJNs3J/OH96gS6KylifO3lt8ymbKN4L1wvfI1H7Mtv8Sta9CMc0IFKeZzbDbty2 25 | 2yCc3iJ8KaESaHwNyLXZ9smYJW9PQhJA8xC3auiHi0DY+FrAm673yhnOaTdHHPlJ 26 | eGxHOP8mlj4qcOBusd2s0mnr8vo5hled4tTO6IY2rwOMg9m6/amHG10X1dA8OggQ 27 | KsLundNk48ECgcEAg9nHL96TLo7V5ClnXjI0YzZNLbSXDwHEHSoAE/Ez6/dbxwO5 28 | jORapM6G5gFLrzc5fohg+vaBivdDtiseUYpRC+xSTI5l2prGmG/Xhoq2rBnypwr6 29 | Pz4517lOaro7daCcFIur+E5U6AgcRN4be3rvWDaCaxcsuZYsRH+buGjbTzzBhyb5 30 | 3LrXGYd6lideSk8DdQUv1GzVUaRDe94QviA/u5L4cd0keN63LeaSt44KAyp+wdxY 31 | y1x+e4WiryB8z7cRAoHAbiMJZUg0zceKbuNX3AIsbpXiKZI5nMolpdv64RrdREfQ 32 | yu1z46VgWrM/z/7RjScYF+1e98JyH4AbeluHr8wmxWXwM71uS6jRj/jhRQ0WMDIe 33 | dAWqLcelyKbIY0jtXztH/vBy0Iv+7m8GyHKsGxMMQwE11NFCRqF96C5AzTJPasid 34 | Scn/zAihGXFQosqJE9MjoMuGuaTSVx9JxuqB409KkG2e9fHFSvMi6XwOgpJC4bsB 35 | OVyta60HIW7n+AQ/mL/BAoHAchjuz/TIxR8UO5dWAX9lQmeQOSK2gcIllLkbNPTw 36 | pCpYGoSOs6cLsszWmzAMwpJgFB56iP73nk9FQ/0IULE5D8Q7eloP+Qnc9cJ/fxGj 37 | q2HsNwIGnQNMF0kEUkRBgNZCTGMwmHcIWYnM93XQDJvyrhc1JhP2sCtjktakHBdi 38 | 5G3JmHbUAeGlVxZA+HsjvyvXPYTyNcf4TrxiNPOlHVkaDL7DPtY/g5UPk2kI5Su9 39 | iNPemuiimgPxmXcsZX5CYqtL 40 | -----END PRIVATE KEY----- 41 | -------------------------------------------------------------------------------- /share/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "share" 3 | version = "0.4.0" 4 | edition = "2024" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | bytes = { workspace = true } 10 | futures-channel = { workspace = true, features = ["sink"] } 11 | futures-util = { workspace = true, features = ["io", "sink"] } 12 | hickory-resolver = { workspace = true, features = ["default", "system-config"] } 13 | http = { workspace = true } 14 | hyper = { workspace = true } 15 | hyper-util = { workspace = true, features = ["client-legacy", "http2", "tokio"] } 16 | tap = { workspace = true } 17 | tokio = { workspace = true, features = ["macros", "rt", "io-util", "net"] } 18 | tokio-util = { workspace = true, features = ["compat"] } 19 | tracing = { workspace = true } 20 | tracing-subscriber = { workspace = true } 21 | 22 | protocol = { path = "../protocol" } 23 | -------------------------------------------------------------------------------- /share/src/async_iter_ext.rs: -------------------------------------------------------------------------------- 1 | use core::async_iter::AsyncIterator; 2 | use core::future::poll_fn; 3 | use core::pin::{Pin, pin}; 4 | 5 | #[allow(async_fn_in_trait)] 6 | pub trait AsyncIteratorExt: AsyncIterator { 7 | async fn next(&mut self) -> Option 8 | where 9 | Self: Unpin, 10 | { 11 | let mut this = Pin::new(self); 12 | poll_fn(|cx| this.as_mut().poll_next(cx)).await 13 | } 14 | 15 | async gen fn enumerate(mut self) -> (usize, Self::Item) 16 | where 17 | Self: Sized, 18 | { 19 | let mut this = pin!(self); 20 | let mut idx = 0; 21 | while let Some(item) = this.next().await { 22 | yield (idx, item); 23 | idx += 1; 24 | } 25 | } 26 | } 27 | 28 | impl AsyncIteratorExt for T {} 29 | -------------------------------------------------------------------------------- /share/src/dns.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::net::IpAddr; 3 | 4 | use futures_util::future::join; 5 | use futures_util::{Stream, stream}; 6 | use hickory_resolver::AsyncResolver; 7 | use hickory_resolver::name_server::{GenericConnector, TokioRuntimeProvider}; 8 | use protocol::DnsResolver; 9 | 10 | #[derive(Clone)] 11 | pub struct HickoryDnsResolver { 12 | resolver: AsyncResolver>, 13 | } 14 | 15 | impl HickoryDnsResolver { 16 | pub fn new() -> io::Result { 17 | let resolver = AsyncResolver::tokio_from_system_conf()?; 18 | 19 | Ok(Self { resolver }) 20 | } 21 | } 22 | 23 | impl DnsResolver for HickoryDnsResolver { 24 | async fn resolve( 25 | &mut self, 26 | name: &str, 27 | ) -> io::Result> + Send> { 28 | let (addrs1, addrs2) = join( 29 | async { 30 | let addrs = self.resolver.ipv6_lookup(name).await?; 31 | Ok::<_, io::Error>(addrs.into_iter().map(|addr| IpAddr::from(addr.0))) 32 | }, 33 | async { 34 | let addrs = self.resolver.ipv4_lookup(name).await?; 35 | Ok::<_, io::Error>(addrs.into_iter().map(|addr| IpAddr::from(addr.0))) 36 | }, 37 | ) 38 | .await; 39 | 40 | let addrs = match (addrs1, addrs2) { 41 | (Err(err), Err(_)) => return Err(err), 42 | (addrs1, addrs2) => addrs1 43 | .ok() 44 | .into_iter() 45 | .flatten() 46 | .chain(addrs2.ok().into_iter().flatten()), 47 | }; 48 | 49 | Ok(stream::iter(addrs.map(Ok))) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /share/src/helper.rs: -------------------------------------------------------------------------------- 1 | pub trait ArrayExt { 2 | /// swap every element bytes 3 | fn swap_bytes(self) -> Self; 4 | } 5 | 6 | macro_rules! array_ext { 7 | ($t:ty) => { 8 | impl ArrayExt for [$t; N] { 9 | fn swap_bytes(mut self) -> Self { 10 | for v in self.iter_mut() { 11 | *v = v.swap_bytes(); 12 | } 13 | 14 | self 15 | } 16 | } 17 | }; 18 | } 19 | 20 | array_ext!(u8); 21 | array_ext!(u16); 22 | -------------------------------------------------------------------------------- /share/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(async_iterator)] 2 | #![feature(gen_blocks)] 3 | 4 | pub mod async_iter_ext; 5 | pub mod dns; 6 | pub mod helper; 7 | pub mod log; 8 | pub mod proxy; 9 | pub mod tcp_wrapper; 10 | -------------------------------------------------------------------------------- /share/src/log.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use tracing::level_filters::LevelFilter; 4 | use tracing::subscriber; 5 | use tracing_subscriber::filter::Targets; 6 | use tracing_subscriber::layer::SubscriberExt; 7 | use tracing_subscriber::{Registry, fmt}; 8 | 9 | pub fn init_log(debug: bool) { 10 | let layer = fmt::layer() 11 | .pretty() 12 | .with_target(true) 13 | .with_writer(io::stderr); 14 | 15 | let level = if debug { 16 | LevelFilter::DEBUG 17 | } else { 18 | LevelFilter::INFO 19 | }; 20 | 21 | let targets = Targets::new() 22 | .with_target("h2", LevelFilter::OFF) 23 | .with_default(LevelFilter::DEBUG); 24 | 25 | let layered = Registry::default().with(targets).with(layer).with(level); 26 | 27 | subscriber::set_global_default(layered).unwrap(); 28 | } 29 | -------------------------------------------------------------------------------- /share/src/proxy.rs: -------------------------------------------------------------------------------- 1 | use std::io::Error; 2 | 3 | use futures_util::{FutureExt, future}; 4 | use tap::TapFallible; 5 | use tokio::io::{self, AsyncRead, AsyncWrite, AsyncWriteExt}; 6 | use tracing::error; 7 | 8 | /// copy data from in_stream to remote_tcp and copy data from remote_tcp to out_stream 9 | pub async fn proxy( 10 | mut remote_in_stream: RemoteIn, 11 | mut remote_out_stream: RemoteOut, 12 | mut client_in_stream: ClientIn, 13 | mut client_out_stream: ClientOut, 14 | ) -> Result<(), Error> 15 | where 16 | RemoteIn: AsyncRead + Unpin + Send + 'static, 17 | RemoteOut: AsyncWrite + Unpin + Send + 'static, 18 | ClientIn: AsyncRead + Unpin + Send + 'static, 19 | ClientOut: AsyncWrite + Unpin + Send + 'static, 20 | { 21 | let task1 = tokio::spawn(async move { 22 | io::copy(&mut client_in_stream, &mut remote_out_stream) 23 | .await 24 | .tap_err( 25 | |err| error!(%err, "copy data from client_in_stream to remote_out_stream failed"), 26 | )?; 27 | 28 | remote_out_stream.shutdown().await 29 | }) 30 | .map(|task| task.unwrap()); 31 | 32 | let task2 = tokio::spawn(async move { 33 | io::copy(&mut remote_in_stream, &mut client_out_stream) 34 | .await 35 | .tap_err( 36 | |err| error!(%err, "copy data from remote_in_stream to client_out_stream failed"), 37 | )?; 38 | 39 | client_out_stream.shutdown().await 40 | }) 41 | .map(|task| task.unwrap()); 42 | 43 | future::try_join(task1, task2).await?; 44 | 45 | Ok(()) 46 | } 47 | 48 | #[cfg(test)] 49 | mod tests { 50 | use std::io::Cursor; 51 | use std::pin::Pin; 52 | use std::sync::{Arc, Mutex}; 53 | use std::task::{Context, Poll}; 54 | 55 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; 56 | use tokio::net::{TcpListener, TcpStream}; 57 | 58 | use super::*; 59 | 60 | #[derive(Clone)] 61 | struct WatchBuf { 62 | buf: Arc>>, 63 | } 64 | 65 | impl AsyncWrite for WatchBuf { 66 | fn poll_write( 67 | self: Pin<&mut Self>, 68 | _cx: &mut Context<'_>, 69 | buf: &[u8], 70 | ) -> Poll> { 71 | self.buf.lock().unwrap().extend_from_slice(buf); 72 | 73 | Poll::Ready(Ok(buf.len())) 74 | } 75 | 76 | fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 77 | Poll::Ready(Ok(())) 78 | } 79 | 80 | fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { 81 | Poll::Ready(Ok(())) 82 | } 83 | } 84 | 85 | #[tokio::test] 86 | async fn test_proxy() { 87 | let listener = TcpListener::bind("0.0.0.0:0").await.unwrap(); 88 | let addr = listener.local_addr().unwrap(); 89 | 90 | let in_stream = Cursor::new([1, 2, 3]); 91 | let out_stream = WatchBuf { 92 | buf: Arc::new(Mutex::new(vec![])), 93 | }; 94 | let watch = out_stream.clone(); 95 | 96 | let task = tokio::spawn(async move { 97 | let tcp_stream = TcpStream::connect(addr).await.unwrap(); 98 | let (tcp_in_stream, tcp_out_stream) = tcp_stream.into_split(); 99 | 100 | proxy(tcp_in_stream, tcp_out_stream, in_stream, out_stream).await 101 | }); 102 | 103 | let (mut tcp_stream, _) = listener.accept().await.unwrap(); 104 | 105 | let mut buf = vec![0; 3]; 106 | 107 | tcp_stream.read_exact(&mut buf).await.unwrap(); 108 | assert_eq!(buf, [1, 2, 3]); 109 | 110 | tcp_stream.write_all(b"test").await.unwrap(); 111 | 112 | drop(tcp_stream); 113 | 114 | task.await.unwrap().unwrap(); 115 | 116 | assert_eq!(watch.buf.lock().unwrap().as_slice(), b"test"); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /share/src/tcp_wrapper.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::io::{Error, IoSlice, IoSliceMut}; 3 | use std::net::SocketAddr; 4 | use std::pin::Pin; 5 | use std::task::{Context, Poll}; 6 | 7 | use futures_util::{AsyncRead, AsyncWrite, Stream}; 8 | use hyper_util::client::legacy::connect::{Connected, Connection}; 9 | use tokio::net::{TcpListener, TcpStream}; 10 | use tokio_util::compat::{Compat, TokioAsyncReadCompatExt}; 11 | 12 | pub struct TokioTcp(Compat); 13 | 14 | impl From for TokioTcp { 15 | fn from(value: TcpStream) -> Self { 16 | Self(value.compat()) 17 | } 18 | } 19 | 20 | impl Connection for TokioTcp { 21 | fn connected(&self) -> Connected { 22 | self.0.get_ref().connected() 23 | } 24 | } 25 | 26 | impl AsyncRead for TokioTcp { 27 | fn poll_read( 28 | mut self: Pin<&mut Self>, 29 | cx: &mut Context<'_>, 30 | buf: &mut [u8], 31 | ) -> Poll> { 32 | Pin::new(&mut self.0).poll_read(cx, buf) 33 | } 34 | 35 | fn poll_read_vectored( 36 | mut self: Pin<&mut Self>, 37 | cx: &mut Context<'_>, 38 | bufs: &mut [IoSliceMut<'_>], 39 | ) -> Poll> { 40 | Pin::new(&mut self.0).poll_read_vectored(cx, bufs) 41 | } 42 | } 43 | 44 | impl AsyncWrite for TokioTcp { 45 | fn poll_write( 46 | mut self: Pin<&mut Self>, 47 | cx: &mut Context<'_>, 48 | buf: &[u8], 49 | ) -> Poll> { 50 | Pin::new(&mut self.0).poll_write(cx, buf) 51 | } 52 | 53 | fn poll_write_vectored( 54 | mut self: Pin<&mut Self>, 55 | cx: &mut Context<'_>, 56 | bufs: &[IoSlice<'_>], 57 | ) -> Poll> { 58 | Pin::new(&mut self.0).poll_write_vectored(cx, bufs) 59 | } 60 | 61 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 62 | Pin::new(&mut self.0).poll_flush(cx) 63 | } 64 | 65 | fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 66 | Pin::new(&mut self.0).poll_close(cx) 67 | } 68 | } 69 | 70 | pub struct TcpListenerAddrStream { 71 | listener: TcpListener, 72 | } 73 | 74 | impl From for TcpListenerAddrStream { 75 | fn from(listener: TcpListener) -> Self { 76 | Self { listener } 77 | } 78 | } 79 | 80 | impl Stream for TcpListenerAddrStream { 81 | type Item = Result<(TcpStream, SocketAddr), Error>; 82 | 83 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 84 | let listener = Pin::new(&mut self.listener); 85 | 86 | listener.poll_accept(cx).map(Some) 87 | } 88 | } 89 | --------------------------------------------------------------------------------