diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index c22a7d7..f274a6e 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -114,6 +114,9 @@ jobs: if: ${{ ! endsWith(matrix.TARGET, 'freebsd') }} run: | bash ./.github/workflows/install_rust.sh + # this dir is a soft link generated by install_rust.sh + # kcp-sys need this to gen ffi bindings. without this clang may fail to find some libc headers such as bits/libc-header-start.h + export KCP_SYS_EXTRA_HEADER_PATH=/usr/include/musl-cross if [[ $OS =~ ^ubuntu.*$ && $TARGET =~ ^mips.*$ ]]; then cargo +nightly build -r --verbose --target $TARGET -Z build-std=std,panic_abort --no-default-features --features mips --package=easytier else @@ -142,14 +145,14 @@ jobs: whoami env | sort - sudo pkg install -y git protobuf + sudo pkg install -y git protobuf llvm-devel curl --proto 'https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y source $HOME/.cargo/env rustup set auto-self-update disable - rustup install 1.77 - rustup default 1.77 + rustup install 1.84 + rustup default 1.84 export CC=clang export CXX=clang++ diff --git a/.github/workflows/gui.yml b/.github/workflows/gui.yml index caa7820..3125be4 100644 --- a/.github/workflows/gui.yml +++ b/.github/workflows/gui.yml @@ -124,6 +124,20 @@ jobs: # GitHub repo token to use to avoid rate limiter repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Install GUI dependencies (x86 only) + if: ${{ matrix.TARGET == 'x86_64-unknown-linux-musl' }} + run: | + sudo apt install -qq libwebkit2gtk-4.1-dev \ + build-essential \ + curl \ + wget \ + file \ + libgtk-3-dev \ + librsvg2-dev \ + libxdo-dev \ + libssl-dev \ + patchelf + - name: Install GUI cross compile (aarch64 only) if: ${{ matrix.TARGET == 'aarch64-unknown-linux-musl' }} run: | diff --git a/.github/workflows/install_rust.sh b/.github/workflows/install_rust.sh index febe65c..2e5d0fe 100644 --- a/.github/workflows/install_rust.sh +++ b/.github/workflows/install_rust.sh @@ -8,20 +8,7 @@ # dependencies are only needed on ubuntu as that's the only place where # we make cross-compilation if [[ $OS =~ ^ubuntu.*$ ]]; then - sudo apt-get update && sudo apt-get install -qq crossbuild-essential-arm64 crossbuild-essential-armhf musl-tools libappindicator3-dev - # for easytier-gui - if [[ $GUI_TARGET != '' && $GUI_TARGET =~ ^x86_64.*$ ]]; then - sudo apt install -qq libwebkit2gtk-4.1-dev \ - build-essential \ - curl \ - wget \ - file \ - libgtk-3-dev \ - librsvg2-dev \ - libxdo-dev \ - libssl-dev \ - patchelf - fi + sudo apt-get update && sudo apt-get install -qq crossbuild-essential-arm64 crossbuild-essential-armhf musl-tools libappindicator3-dev llvm clang # curl -s musl.cc | grep mipsel case $TARGET in mipsel-unknown-linux-musl) @@ -52,13 +39,14 @@ if [[ $OS =~ ^ubuntu.*$ ]]; then wget -c https://musl.cc/${MUSL_URI}-cross.tgz -P ./musl_gcc/ tar zxf ./musl_gcc/${MUSL_URI}-cross.tgz -C ./musl_gcc/ sudo ln -s $(pwd)/musl_gcc/${MUSL_URI}-cross/bin/*gcc /usr/bin/ + sudo ln -s $(pwd)/musl_gcc/${MUSL_URI}-cross/${MUSL_URI}/include/ /usr/include/musl-cross fi fi # see https://github.com/rust-lang/rustup/issues/3709 rustup set auto-self-update disable -rustup install 1.77 -rustup default 1.77 +rustup install 1.84 +rustup default 1.84 # mips/mipsel cannot add target from rustup, need compile by ourselves if [[ $OS =~ ^ubuntu.*$ && $TARGET =~ ^mips.*$ ]]; then diff --git a/Cargo.lock b/Cargo.lock index ef5ae06..991118e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -183,9 +183,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "approx" @@ -506,9 +506,9 @@ dependencies = [ [[package]] name = "auto_impl" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +checksum = "e12882f59de5360c748c4cbf569a042d5fb0eb515f7bea9c1f470b47f6ffbd73" dependencies = [ "proc-macro2", "quote", @@ -684,6 +684,24 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags 2.8.0", + "cexpr", + "clang-sys", + "itertools 0.11.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.87", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -692,9 +710,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" dependencies = [ "serde", ] @@ -955,7 +973,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cairo-sys-rs", "glib", "libc", @@ -1018,9 +1036,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.12" +version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68064e60dbf1f17005c2fde4d07c16d8baa506fd7ffed8ccab702d93617975c7" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" dependencies = [ "jobserver", "libc", @@ -1033,6 +1051,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfb" version = "0.7.3" @@ -1125,6 +1152,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading 0.8.5", +] + [[package]] name = "clap" version = "4.5.15" @@ -1183,7 +1221,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block", "cocoa-foundation", "core-foundation 0.10.0", @@ -1199,7 +1237,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block", "core-foundation 0.10.0", "core-graphics-types 0.2.0", @@ -1335,7 +1373,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "core-foundation 0.10.0", "core-graphics-types 0.2.0", "foreign-types 0.5.0", @@ -1359,7 +1397,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "core-foundation 0.10.0", "libc", ] @@ -1843,7 +1881,7 @@ dependencies = [ "atomicbox", "auto_impl", "base64 0.22.1", - "bitflags 2.6.0", + "bitflags 2.8.0", "boringtun-easytier", "bytecodec", "byteorder", @@ -1866,6 +1904,7 @@ dependencies = [ "http 1.1.0", "humansize", "indexmap 1.9.3", + "kcp-sys", "machine-uid", "mimalloc-rust", "network-interface", @@ -2709,7 +2748,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "futures-channel", "futures-core", "futures-executor", @@ -3392,15 +3431,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "0.4.8" @@ -3518,13 +3548,35 @@ dependencies = [ "serde_json", ] +[[package]] +name = "kcp-sys" +version = "0.1.0" +source = "git+https://github.com/EasyTier/kcp-sys#a932f3ed394cad1ace9c56f90611b421d856e628" +dependencies = [ + "anyhow", + "auto_impl", + "bindgen", + "bitflags 2.8.0", + "bytes", + "cc", + "dashmap", + "parking_lot", + "rand 0.8.5", + "thiserror 2.0.11", + "tokio", + "tokio-util", + "tracing", + "tracing-subscriber", + "zerocopy", +] + [[package]] name = "keyboard-types" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "serde", "unicode-segmentation", ] @@ -3613,7 +3665,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "libc", ] @@ -3895,7 +3947,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "jni-sys", "log", "ndk-sys", @@ -4029,7 +4081,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg-if", "libc", "memoffset", @@ -4041,7 +4093,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg-if", "cfg_aliases", "libc", @@ -4250,7 +4302,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "libc", "objc2", @@ -4266,7 +4318,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "objc2", "objc2-core-location", @@ -4290,7 +4342,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "objc2", "objc2-foundation", @@ -4332,7 +4384,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "dispatch", "libc", @@ -4357,7 +4409,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "objc2", "objc2-foundation", @@ -4369,7 +4421,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "objc2", "objc2-foundation", @@ -4392,7 +4444,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "objc2", "objc2-cloud-kit", @@ -4424,7 +4476,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "objc2", "objc2-core-location", @@ -4437,7 +4489,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68bc69301064cebefc6c4c90ce9cba69225239e4b8ff99d445a2b5563797da65" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "block2", "objc2", "objc2-app-kit", @@ -4482,7 +4534,7 @@ version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg-if", "foreign-types 0.3.2", "libc", @@ -5288,7 +5340,7 @@ checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302" dependencies = [ "bytes", "heck 0.5.0", - "itertools 0.13.0", + "itertools 0.11.0", "log", "multimap", "once_cell", @@ -5308,7 +5360,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.11.0", "proc-macro2", "quote", "syn 2.0.87", @@ -5556,7 +5608,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -5942,9 +5994,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustc_version" @@ -5961,7 +6013,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "errno", "libc", "linux-raw-sys", @@ -6332,7 +6384,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -6716,9 +6768,9 @@ dependencies = [ [[package]] name = "smoltcp" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a1a996951e50b5971a2c8c0fa05a381480d70a933064245c4a223ddc87ccc97" +checksum = "dad095989c1533c1c266d9b1e8d70a1329dd3723c3edac6d03bbd67e7bf6f4bb" dependencies = [ "bitflags 1.3.2", "byteorder", @@ -6924,7 +6976,7 @@ dependencies = [ "atoi", "base64 0.22.1", "bigdecimal", - "bitflags 2.6.0", + "bitflags 2.8.0", "byteorder", "bytes", "chrono", @@ -6971,7 +7023,7 @@ dependencies = [ "atoi", "base64 0.22.1", "bigdecimal", - "bitflags 2.6.0", + "bitflags 2.8.0", "byteorder", "chrono", "crc", @@ -7257,7 +7309,7 @@ version = "0.30.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "833b4d43383d76d5078d72f3acd977f47eb5b6751eb40baa665d13828e7b79df" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cocoa", "core-foundation 0.10.0", "core-graphics 0.24.0", @@ -7406,7 +7458,7 @@ dependencies = [ "sha2", "syn 2.0.87", "tauri-utils", - "thiserror 2.0.2", + "thiserror 2.0.11", "time", "url", "uuid", @@ -7637,7 +7689,7 @@ dependencies = [ "serde_json", "serde_with", "swift-rs", - "thiserror 2.0.2", + "thiserror 2.0.11", "toml 0.8.19", "url", "urlpattern", @@ -7706,11 +7758,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.2" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037e29b009aa709f293b974da5cd33b15783c049e07f8435778ce8c4871525d8" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ - "thiserror-impl 2.0.2", + "thiserror-impl 2.0.11", ] [[package]] @@ -7726,9 +7778,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.2" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea4778c7e8ff768bdb32a58a2349903859fe719a320300d7d4ce8636f19a1e69" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", @@ -7885,9 +7937,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -8067,7 +8119,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" dependencies = [ "async-compression", - "bitflags 2.6.0", + "bitflags 2.8.0", "bytes", "futures-core", "http 1.1.0", @@ -8158,9 +8210,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -8182,9 +8234,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", @@ -8193,9 +8245,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -8214,9 +8266,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -8962,7 +9014,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d24d6bcc7f734a4091ecf8d7a64c5f7d7066f45585c1861eba06449909609c8a" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "widestring", "windows-sys 0.52.0", ] diff --git a/easytier-rpc-build/Cargo.toml b/easytier-rpc-build/Cargo.toml index b70b6c0..f4ca693 100644 --- a/easytier-rpc-build/Cargo.toml +++ b/easytier-rpc-build/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/EasyTier/EasyTier" authors = ["kkrainbow"] keywords = ["vpn", "p2p", "network", "easytier"] categories = ["network-programming", "command-line-utilities"] -rust-version = "1.77.0" +rust-version = "1.84.0" license-file = "LICENSE" readme = "README.md" diff --git a/easytier/Cargo.toml b/easytier/Cargo.toml index 0f1f0e8..a4425b0 100644 --- a/easytier/Cargo.toml +++ b/easytier/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" authors = ["kkrainbow"] keywords = ["vpn", "p2p", "network", "easytier"] categories = ["network-programming", "command-line-utilities"] -rust-version = "1.77.0" +rust-version = "1.84.0" license-file = "LICENSE" readme = "README.md" @@ -163,12 +163,13 @@ indexmap = { version = "~1.9.3", optional = false, features = ["std"] } atomic-shim = "0.2.0" -smoltcp = { version = "0.11.0", optional = true, default-features = false, features = [ +smoltcp = { version = "0.12.0", optional = true, default-features = false, features = [ "std", "medium-ip", "proto-ipv4", "proto-ipv6", "socket-tcp", + "socket-tcp-cubic", "async", ] } parking_lot = { version = "0.12.0", optional = true } @@ -185,6 +186,8 @@ service-manager = {git = "https://github.com/chipsenkbeil/service-manager-rs.git async-compression = { version = "0.4.17", default-features = false, features = ["zstd", "tokio"] } +kcp-sys = { git = "https://github.com/EasyTier/kcp-sys" } + [target.'cfg(any(target_os = "linux", target_os = "macos", target_os = "windows", target_os = "freebsd"))'.dependencies] machine-uid = "0.5.3" diff --git a/easytier/src/common/config.rs b/easytier/src/common/config.rs index e22f5d8..1efd153 100644 --- a/easytier/src/common/config.rs +++ b/easytier/src/common/config.rs @@ -30,6 +30,9 @@ pub fn gen_default_flags() -> Flags { multi_thread: true, data_compress_algo: CompressionAlgoPb::None.into(), bind_device: true, + enable_kcp_proxy: false, + disable_kcp_input: true, + disable_relay_kcp: true, } } diff --git a/easytier/src/common/ifcfg.rs b/easytier/src/common/ifcfg.rs index 646185f..a5de48f 100644 --- a/easytier/src/common/ifcfg.rs +++ b/easytier/src/common/ifcfg.rs @@ -295,7 +295,7 @@ impl IfConfiguerTrait for WindowsIfConfiger { }; run_shell_cmd( format!( - "route ADD {} MASK {} 10.1.1.1 IF {} METRIC 255", + "route ADD {} MASK {} 10.1.1.1 IF {} METRIC 9000", address, cidr_to_subnet_mask(cidr_prefix), idx diff --git a/easytier/src/easytier-core.rs b/easytier/src/easytier-core.rs index 7be00e9..0b3c8b6 100644 --- a/easytier/src/easytier-core.rs +++ b/easytier/src/easytier-core.rs @@ -313,6 +313,20 @@ struct Cli { help = t!("core_clap.bind_device").to_string() )] bind_device: Option, + + #[arg( + long, + help = t!("core_clap.enable_kcp_proxy").to_string(), + default_value = "false" + )] + enable_kcp_proxy: bool, + + #[arg( + long, + help = t!("core_clap.enable_kcp_proxy").to_string(), + default_value = "false" + )] + disable_kcp_input: bool, } rust_i18n::i18n!("locales", fallback = "en"); @@ -567,6 +581,8 @@ impl TryFrom<&Cli> for TomlConfigLoader { if let Some(bind_device) = cli.bind_device { f.bind_device = bind_device; } + f.enable_kcp_proxy = cli.enable_kcp_proxy; + f.disable_kcp_input = cli.disable_kcp_input; cfg.set_flags(f); cfg.set_exit_nodes(cli.exit_nodes.clone()); diff --git a/easytier/src/gateway/kcp_proxy.rs b/easytier/src/gateway/kcp_proxy.rs new file mode 100644 index 0000000..7627c09 --- /dev/null +++ b/easytier/src/gateway/kcp_proxy.rs @@ -0,0 +1,315 @@ +use std::{ + net::{IpAddr, SocketAddr}, + sync::Arc, + time::Duration, +}; + +use anyhow::Context; +use bytes::Bytes; +use kcp_sys::{ + endpoint::{KcpEndpoint, KcpPacketReceiver}, + packet_def::KcpPacket, + stream::KcpStream, +}; +use pnet::packet::{ip::IpNextHeaderProtocols, ipv4::Ipv4Packet}; +use prost::Message; +use tokio::{io::copy_bidirectional, task::JoinSet}; + +use super::{ + tcp_proxy::{NatDstConnector, NatDstTcpConnector, TcpProxy}, + CidrSet, +}; +use crate::{ + common::{ + error::Result, + global_ctx::{ArcGlobalCtx, GlobalCtx}, + }, + peers::{peer_manager::PeerManager, NicPacketFilter, PeerPacketFilter}, + proto::peer_rpc::KcpConnData, + tunnel::packet_def::{PacketType, PeerManagerHeader, ZCPacket}, +}; + +struct KcpEndpointFilter { + kcp_endpoint: Arc, + is_src: bool, +} + +#[async_trait::async_trait] +impl PeerPacketFilter for KcpEndpointFilter { + async fn try_process_packet_from_peer(&self, packet: ZCPacket) -> Option { + let t = packet.peer_manager_header().unwrap().packet_type; + if t == PacketType::KcpSrc as u8 && !self.is_src { + } else if t == PacketType::KcpDst as u8 && self.is_src { + } else { + return Some(packet); + } + + let _ = self + .kcp_endpoint + .input_sender_ref() + .send(KcpPacket::from(packet.payload_bytes())) + .await; + + None + } +} + +#[tracing::instrument] +async fn handle_kcp_output( + peer_mgr: Arc, + mut output_receiver: KcpPacketReceiver, + is_src: bool, +) { + while let Some(packet) = output_receiver.recv().await { + let dst_peer_id = if is_src { + packet.header().dst_session_id() + } else { + packet.header().src_session_id() + }; + let packet_type = if is_src { + PacketType::KcpSrc as u8 + } else { + PacketType::KcpDst as u8 + }; + let mut packet = ZCPacket::new_with_payload(&packet.inner().freeze()); + packet.fill_peer_manager_hdr(peer_mgr.my_peer_id(), dst_peer_id, packet_type as u8); + + if let Err(e) = peer_mgr.send_msg(packet, dst_peer_id).await { + tracing::error!("failed to send kcp packet to peer: {:?}", e); + } + } +} + +#[derive(Debug, Clone)] +pub struct NatDstKcpConnector { + kcp_endpoint: Arc, + peer_mgr: Arc, +} + +#[async_trait::async_trait] +impl NatDstConnector for NatDstKcpConnector { + type DstStream = KcpStream; + + async fn connect(&self, nat_dst: SocketAddr) -> Result { + let conn_data = KcpConnData { + dst: Some(nat_dst.into()), + }; + + let (dst_peers, _) = match nat_dst { + SocketAddr::V4(addr) => { + let ip = addr.ip(); + self.peer_mgr.get_msg_dst_peer(&ip).await + } + SocketAddr::V6(_) => return Err(anyhow::anyhow!("ipv6 is not supported").into()), + }; + + tracing::trace!("kcp nat dst: {:?}, dst peers: {:?}", nat_dst, dst_peers); + + if dst_peers.len() != 1 { + return Err(anyhow::anyhow!("no dst peer found for nat dst: {}", nat_dst).into()); + } + + let ret = self + .kcp_endpoint + .connect( + Duration::from_secs(10), + self.peer_mgr.my_peer_id(), + dst_peers[0], + Bytes::from(conn_data.encode_to_vec()), + ) + .await + .with_context(|| format!("failed to connect to nat dst: {}", nat_dst.to_string()))?; + + let stream = KcpStream::new(&self.kcp_endpoint, ret) + .ok_or(anyhow::anyhow!("failed to create kcp stream"))?; + + Ok(stream) + } + + fn check_packet_from_peer_fast(&self, _cidr_set: &CidrSet, _global_ctx: &GlobalCtx) -> bool { + // if kcp is turned off, the filter will not be added to the pipeline + true + } + + fn check_packet_from_peer( + &self, + _cidr_set: &CidrSet, + _global_ctx: &GlobalCtx, + _hdr: &PeerManagerHeader, + _ipv4: &Ipv4Packet, + ) -> bool { + true + } +} + +#[derive(Clone)] +struct TcpProxyForKcpSrc(Arc>); + +pub struct KcpProxySrc { + kcp_endpoint: Arc, + peer_manager: Arc, + + tcp_proxy: TcpProxyForKcpSrc, + tasks: JoinSet<()>, +} + +#[async_trait::async_trait] +impl NicPacketFilter for TcpProxyForKcpSrc { + async fn try_process_packet_from_nic(&self, zc_packet: &mut ZCPacket) -> bool { + let ret = self.0.try_process_packet_from_nic(zc_packet).await; + if ret { + return true; + } + + let Some(my_ipv4) = self.0.get_local_ip() else { + return false; + }; + + let data = zc_packet.payload(); + let ip_packet = Ipv4Packet::new(data).unwrap(); + if ip_packet.get_version() != 4 + // TODO: how to support net to net kcp proxy? + || ip_packet.get_source() != my_ipv4 + || ip_packet.get_next_level_protocol() != IpNextHeaderProtocols::Tcp + { + return false; + } + + zc_packet.mut_peer_manager_header().unwrap().to_peer_id = self.0.get_my_peer_id().into(); + + true + } +} + +impl KcpProxySrc { + pub async fn new(peer_manager: Arc) -> Self { + let mut kcp_endpoint = KcpEndpoint::new(); + kcp_endpoint.run().await; + + let output_receiver = kcp_endpoint.output_receiver().unwrap(); + let mut tasks = JoinSet::new(); + + tasks.spawn(handle_kcp_output( + peer_manager.clone(), + output_receiver, + true, + )); + + let kcp_endpoint = Arc::new(kcp_endpoint); + + let tcp_proxy = TcpProxy::new( + peer_manager.clone(), + NatDstKcpConnector { + kcp_endpoint: kcp_endpoint.clone(), + peer_mgr: peer_manager.clone(), + }, + ); + + Self { + kcp_endpoint, + peer_manager, + tcp_proxy: TcpProxyForKcpSrc(tcp_proxy), + tasks, + } + } + + pub async fn start(&self) { + self.peer_manager + .add_nic_packet_process_pipeline(Box::new(self.tcp_proxy.clone())) + .await; + self.peer_manager + .add_packet_process_pipeline(Box::new(self.tcp_proxy.0.clone())) + .await; + self.peer_manager + .add_packet_process_pipeline(Box::new(KcpEndpointFilter { + kcp_endpoint: self.kcp_endpoint.clone(), + is_src: true, + })) + .await; + self.tcp_proxy.0.start(false).await.unwrap(); + } +} + +pub struct KcpProxyDst { + kcp_endpoint: Arc, + peer_manager: Arc, + tasks: JoinSet<()>, +} + +impl KcpProxyDst { + pub async fn new(peer_manager: Arc) -> Self { + let mut kcp_endpoint = KcpEndpoint::new(); + kcp_endpoint.run().await; + + let mut tasks = JoinSet::new(); + let output_receiver = kcp_endpoint.output_receiver().unwrap(); + tasks.spawn(handle_kcp_output( + peer_manager.clone(), + output_receiver, + false, + )); + + Self { + kcp_endpoint: Arc::new(kcp_endpoint), + peer_manager, + tasks, + } + } + + #[tracing::instrument(ret)] + async fn handle_one_in_stream( + mut kcp_stream: KcpStream, + global_ctx: ArcGlobalCtx, + ) -> Result<()> { + let mut conn_data = kcp_stream.conn_data().clone(); + let parsed_conn_data = KcpConnData::decode(&mut conn_data) + .with_context(|| format!("failed to decode kcp conn data: {:?}", conn_data))?; + let mut dst_socket: SocketAddr = parsed_conn_data + .dst + .ok_or(anyhow::anyhow!( + "failed to get dst socket from kcp conn data: {:?}", + parsed_conn_data + ))? + .into(); + + if Some(dst_socket.ip()) == global_ctx.get_ipv4().map(|ip| IpAddr::V4(ip.address())) { + dst_socket = format!("127.0.0.1:{}", dst_socket.port()).parse().unwrap(); + } + + tracing::debug!("kcp connect to dst socket: {:?}", dst_socket); + + let _g = global_ctx.net_ns.guard(); + let connector = NatDstTcpConnector {}; + let mut ret = connector.connect(dst_socket).await?; + + copy_bidirectional(&mut ret, &mut kcp_stream).await?; + Ok(()) + } + + async fn run_accept_task(&mut self) { + let kcp_endpoint = self.kcp_endpoint.clone(); + let global_ctx = self.peer_manager.get_global_ctx().clone(); + self.tasks.spawn(async move { + while let Ok(conn) = kcp_endpoint.accept().await { + let stream = KcpStream::new(&kcp_endpoint, conn) + .ok_or(anyhow::anyhow!("failed to create kcp stream")) + .unwrap(); + + let global_ctx = global_ctx.clone(); + tokio::spawn(async move { + let _ = Self::handle_one_in_stream(stream, global_ctx).await; + }); + } + }); + } + + pub async fn start(&mut self) { + self.run_accept_task().await; + self.peer_manager + .add_packet_process_pipeline(Box::new(KcpEndpointFilter { + kcp_endpoint: self.kcp_endpoint.clone(), + is_src: false, + })) + .await; + } +} diff --git a/easytier/src/gateway/mod.rs b/easytier/src/gateway/mod.rs index 72c1edd..030b5b3 100644 --- a/easytier/src/gateway/mod.rs +++ b/easytier/src/gateway/mod.rs @@ -15,8 +15,10 @@ pub mod fast_socks5; #[cfg(feature = "socks5")] pub mod socks5; +pub mod kcp_proxy; + #[derive(Debug)] -struct CidrSet { +pub(crate) struct CidrSet { global_ctx: ArcGlobalCtx, cidr_set: Arc>>, tasks: JoinSet<()>, diff --git a/easytier/src/gateway/tcp_proxy.rs b/easytier/src/gateway/tcp_proxy.rs index a5987ae..d91fc7f 100644 --- a/easytier/src/gateway/tcp_proxy.rs +++ b/easytier/src/gateway/tcp_proxy.rs @@ -1,3 +1,4 @@ +use anyhow::Context; use cidr::Ipv4Inet; use core::panic; use crossbeam::atomic::AtomicCell; @@ -11,7 +12,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; use std::sync::atomic::{AtomicBool, AtomicU16}; use std::sync::Arc; use std::time::{Duration, Instant}; -use tokio::io::copy_bidirectional; +use tokio::io::{copy_bidirectional, AsyncRead, AsyncWrite}; use tokio::net::{TcpListener, TcpSocket, TcpStream}; use tokio::sync::{mpsc, Mutex}; use tokio::task::JoinSet; @@ -23,13 +24,74 @@ use crate::common::join_joinset_background; use crate::peers::peer_manager::PeerManager; use crate::peers::{NicPacketFilter, PeerPacketFilter}; -use crate::tunnel::packet_def::{PacketType, ZCPacket}; +use crate::tunnel::packet_def::{PacketType, PeerManagerHeader, ZCPacket}; use super::CidrSet; #[cfg(feature = "smoltcp")] use super::tokio_smoltcp::{self, channel_device, Net, NetConfig}; +#[async_trait::async_trait] +pub(crate) trait NatDstConnector: Send + Sync + Clone + 'static { + type DstStream: AsyncRead + AsyncWrite + Unpin + Send; + + async fn connect(&self, dst: SocketAddr) -> Result; + fn check_packet_from_peer_fast(&self, cidr_set: &CidrSet, global_ctx: &GlobalCtx) -> bool; + fn check_packet_from_peer( + &self, + cidr_set: &CidrSet, + global_ctx: &GlobalCtx, + hdr: &PeerManagerHeader, + ipv4: &Ipv4Packet, + ) -> bool; +} + +#[derive(Debug, Clone)] +pub struct NatDstTcpConnector; + +#[async_trait::async_trait] +impl NatDstConnector for NatDstTcpConnector { + type DstStream = TcpStream; + + async fn connect(&self, nat_dst: SocketAddr) -> Result { + let socket = TcpSocket::new_v4().unwrap(); + if let Err(e) = socket.set_nodelay(true) { + tracing::warn!("set_nodelay failed, ignore it: {:?}", e); + } + + Ok( + tokio::time::timeout(Duration::from_secs(10), socket.connect(nat_dst)) + .await? + .with_context(|| format!("connect to nat dst failed: {:?}", nat_dst))?, + ) + } + + fn check_packet_from_peer_fast(&self, cidr_set: &CidrSet, global_ctx: &GlobalCtx) -> bool { + !cidr_set.is_empty() || global_ctx.enable_exit_node() || global_ctx.no_tun() + } + + fn check_packet_from_peer( + &self, + cidr_set: &CidrSet, + global_ctx: &GlobalCtx, + hdr: &PeerManagerHeader, + ipv4: &Ipv4Packet, + ) -> bool { + let is_exit_node = hdr.is_exit_node(); + + if !cidr_set.contains_v4(ipv4.get_destination()) + && !is_exit_node + && !(global_ctx.no_tun() + && Some(ipv4.get_destination()) + == global_ctx.get_ipv4().as_ref().map(Ipv4Inet::address)) + { + return false; + } + + true + } +} + #[derive(Debug, Clone, Copy, PartialEq)] enum NatDstEntryState { // receive syn packet but not start connecting to dst @@ -83,7 +145,10 @@ impl ProxyTcpStream { } } - pub async fn copy_bidirectional(&mut self, dst: &mut TcpStream) -> Result<()> { + pub async fn copy_bidirectional( + &mut self, + dst: &mut D, + ) -> Result<()> { match self { Self::KernelTcpStream(stream) => { copy_bidirectional(stream, dst).await?; @@ -176,7 +241,7 @@ type ConnSockMap = Arc>; type AddrConnSockMap = Arc>; #[derive(Debug)] -pub struct TcpProxy { +pub struct TcpProxy { global_ctx: Arc, peer_manager: Arc, local_port: AtomicU16, @@ -194,10 +259,12 @@ pub struct TcpProxy { #[cfg(feature = "smoltcp")] smoltcp_net: Arc>>, enable_smoltcp: Arc, + + connector: C, } #[async_trait::async_trait] -impl PeerPacketFilter for TcpProxy { +impl PeerPacketFilter for TcpProxy { async fn try_process_packet_from_peer(&self, mut packet: ZCPacket) -> Option { if let Some(_) = self.try_handle_peer_packet(&mut packet).await { if self @@ -221,10 +288,10 @@ impl PeerPacketFilter for TcpProxy { } #[async_trait::async_trait] -impl NicPacketFilter for TcpProxy { - async fn try_process_packet_from_nic(&self, zc_packet: &mut ZCPacket) { +impl NicPacketFilter for TcpProxy { + async fn try_process_packet_from_nic(&self, zc_packet: &mut ZCPacket) -> bool { let Some(my_ipv4) = self.get_local_ip() else { - return; + return false; }; let data = zc_packet.payload(); @@ -233,25 +300,33 @@ impl NicPacketFilter for TcpProxy { || ip_packet.get_source() != my_ipv4 || ip_packet.get_next_level_protocol() != IpNextHeaderProtocols::Tcp { - return; + return false; } let tcp_packet = TcpPacket::new(ip_packet.payload()).unwrap(); if tcp_packet.get_source() != self.get_local_port() { - return; + return false; } - let dst_addr = SocketAddr::V4(SocketAddrV4::new( + let mut dst_addr = SocketAddr::V4(SocketAddrV4::new( ip_packet.get_destination(), tcp_packet.get_destination(), )); + let mut need_transform_dst = false; + + // for kcp proxy, the src ip of nat entry will be converted from my ip to fake ip + // here we need to convert it back + if !self.is_smoltcp_enabled() && dst_addr.ip() == Self::get_fake_local_ipv4(my_ipv4) { + dst_addr.set_ip(IpAddr::V4(my_ipv4)); + need_transform_dst = true; + } tracing::trace!(dst_addr = ?dst_addr, "tcp packet try find entry"); let entry = if let Some(entry) = self.addr_conn_map.get(&dst_addr) { entry } else { let Some(syn_entry) = self.syn_map.get(&dst_addr) else { - return; + return false; }; syn_entry }; @@ -267,9 +342,15 @@ impl NicPacketFilter for TcpProxy { .mut_peer_manager_header() .unwrap() .set_no_proxy(true); + if need_transform_dst { + zc_packet.mut_peer_manager_header().unwrap().to_peer_id = self.get_my_peer_id().into(); + } let mut ip_packet = MutableIpv4Packet::new(zc_packet.mut_payload()).unwrap(); ip_packet.set_source(ip); + if need_transform_dst { + ip_packet.set_destination(my_ipv4); + } let dst = ip_packet.get_destination(); let mut tcp_packet = MutableTcpPacket::new(ip_packet.payload_mut()).unwrap(); @@ -280,12 +361,15 @@ impl NicPacketFilter for TcpProxy { Self::update_ip_packet_checksum(&mut ip_packet); tracing::trace!(dst_addr = ?dst_addr, nat_entry = ?nat_entry, packet = ?ip_packet, "tcp packet after modified"); + + true } } -impl TcpProxy { - pub fn new(global_ctx: Arc, peer_manager: Arc) -> Arc { +impl TcpProxy { + pub fn new(peer_manager: Arc, connector: C) -> Arc { let (smoltcp_stack_sender, smoltcp_stack_receiver) = mpsc::channel::(1000); + let global_ctx = peer_manager.get_global_ctx(); Arc::new(Self { global_ctx: global_ctx.clone(), @@ -307,6 +391,8 @@ impl TcpProxy { smoltcp_net: Arc::new(Mutex::new(None)), enable_smoltcp: Arc::new(AtomicBool::new(true)), + + connector, }) } @@ -326,15 +412,17 @@ impl TcpProxy { ip_packet.set_checksum(pnet::packet::ipv4::checksum(&ip_packet.to_immutable())); } - pub async fn start(self: &Arc) -> Result<()> { + pub async fn start(self: &Arc, add_pipeline: bool) -> Result<()> { self.run_syn_map_cleaner().await?; self.run_listener().await?; - self.peer_manager - .add_packet_process_pipeline(Box::new(self.clone())) - .await; - self.peer_manager - .add_nic_packet_process_pipeline(Box::new(self.clone())) - .await; + if add_pipeline { + self.peer_manager + .add_packet_process_pipeline(Box::new(self.clone())) + .await; + self.peer_manager + .add_nic_packet_process_pipeline(Box::new(self.clone())) + .await; + } join_joinset_background(self.tasks.clone(), "TcpProxy".to_owned()); Ok(()) @@ -458,11 +546,26 @@ impl TcpProxy { let syn_map = self.syn_map.clone(); let conn_map = self.conn_map.clone(); let addr_conn_map = self.addr_conn_map.clone(); + let connector = self.connector.clone(); let accept_task = async move { let conn_map = conn_map.clone(); - while let Ok((tcp_stream, socket_addr)) = tcp_listener.accept().await { + while let Ok((tcp_stream, mut socket_addr)) = tcp_listener.accept().await { + let my_ip = global_ctx + .get_ipv4() + .as_ref() + .map(Ipv4Inet::address) + .unwrap_or(Ipv4Addr::UNSPECIFIED); + + if socket_addr.ip() == Self::get_fake_local_ipv4(my_ip) { + socket_addr.set_ip(IpAddr::V4(my_ip)); + } + let Some(entry) = syn_map.get(&socket_addr) else { - tracing::error!("tcp connection from unknown source: {:?}", socket_addr); + tracing::error!( + ?my_ip, + ?socket_addr, + "tcp connection from unknown source, ignore it" + ); continue; }; tracing::info!( @@ -483,6 +586,7 @@ impl TcpProxy { assert!(old_nat_val.is_none()); tasks.lock().unwrap().spawn(Self::connect_to_nat_dst( + connector.clone(), global_ctx.clone(), tcp_stream, conn_map.clone(), @@ -511,6 +615,7 @@ impl TcpProxy { } async fn connect_to_nat_dst( + connector: C, global_ctx: ArcGlobalCtx, src_tcp_stream: ProxyTcpStream, conn_map: ConnSockMap, @@ -521,12 +626,6 @@ impl TcpProxy { tracing::warn!("set_nodelay failed, ignore it: {:?}", e); } - let _guard = global_ctx.net_ns.guard(); - let socket = TcpSocket::new_v4().unwrap(); - if let Err(e) = socket.set_nodelay(true) { - tracing::warn!("set_nodelay failed, ignore it: {:?}", e); - } - let nat_dst = if Some(nat_entry.dst.ip()) == global_ctx.get_ipv4().map(|ip| IpAddr::V4(ip.address())) { @@ -537,12 +636,8 @@ impl TcpProxy { nat_entry.dst }; - let Ok(Ok(dst_tcp_stream)) = tokio::time::timeout( - Duration::from_secs(10), - TcpSocket::new_v4().unwrap().connect(nat_dst), - ) - .await - else { + let _guard = global_ctx.net_ns.guard(); + let Ok(dst_tcp_stream) = connector.connect(nat_dst).await else { tracing::error!("connect to dst failed: {:?}", nat_entry); nat_entry.state.store(NatDstEntryState::Closed); Self::remove_entry_from_all_conn_map(conn_map, addr_conn_map, nat_entry); @@ -567,7 +662,7 @@ impl TcpProxy { async fn handle_nat_connection( mut src_tcp_stream: ProxyTcpStream, - mut dst_tcp_stream: TcpStream, + mut dst_tcp_stream: C::DstStream, conn_map: ConnSockMap, addr_conn_map: AddrConnSockMap, nat_entry: ArcNatDstEntry, @@ -577,6 +672,10 @@ impl TcpProxy { let ret = src_tcp_stream.copy_bidirectional(&mut dst_tcp_stream).await; tracing::info!(nat_entry = ?nat_entry_clone, ret = ?ret, "nat tcp connection closed"); nat_entry_clone.state.store(NatDstEntryState::Closed); + drop(src_tcp_stream); + + // sleep later so the fin packet can be processed + tokio::time::sleep(Duration::from_secs(10)).await; Self::remove_entry_from_all_conn_map(conn_map, addr_conn_map, nat_entry_clone); }); @@ -586,11 +685,12 @@ impl TcpProxy { self.local_port.load(std::sync::atomic::Ordering::Relaxed) } + pub fn get_my_peer_id(&self) -> u32 { + self.peer_manager.my_peer_id() + } + pub fn get_local_ip(&self) -> Option { - if self - .enable_smoltcp - .load(std::sync::atomic::Ordering::Relaxed) - { + if self.is_smoltcp_enabled() { Some(Ipv4Addr::new(192, 88, 99, 254)) } else { self.global_ctx @@ -600,17 +700,26 @@ impl TcpProxy { } } + pub fn is_smoltcp_enabled(&self) -> bool { + self.enable_smoltcp + .load(std::sync::atomic::Ordering::Relaxed) + } + + pub fn get_fake_local_ipv4(local_ip: Ipv4Addr) -> Ipv4Addr { + let octets = local_ip.octets(); + Ipv4Addr::new(octets[0], octets[1], octets[2], 0) + } + async fn try_handle_peer_packet(&self, packet: &mut ZCPacket) -> Option<()> { - if self.cidr_set.is_empty() - && !self.global_ctx.enable_exit_node() - && !self.global_ctx.no_tun() + if !self + .connector + .check_packet_from_peer_fast(&self.cidr_set, &self.global_ctx) { return None; } let ipv4_addr = self.get_local_ip()?; - let hdr = packet.peer_manager_header().unwrap(); - let is_exit_node = hdr.is_exit_node(); + let hdr = packet.peer_manager_header().unwrap().clone(); if hdr.packet_type != PacketType::Data as u8 || hdr.is_no_proxy() { return None; @@ -623,11 +732,9 @@ impl TcpProxy { return None; } - if !self.cidr_set.contains_v4(ipv4.get_destination()) - && !is_exit_node - && !(self.global_ctx.no_tun() - && Some(ipv4.get_destination()) - == self.global_ctx.get_ipv4().as_ref().map(Ipv4Inet::address)) + if !self + .connector + .check_packet_from_peer(&self.cidr_set, &self.global_ctx, &hdr, &ipv4) { return None; } @@ -658,6 +765,10 @@ impl TcpProxy { } let mut ip_packet = MutableIpv4Packet::new(payload_bytes).unwrap(); + if !self.is_smoltcp_enabled() && source_ip == ipv4_addr { + // modify the source so the response packet can be handled by tun device + ip_packet.set_source(Self::get_fake_local_ipv4(ipv4_addr)); + } ip_packet.set_destination(ipv4_addr); let source = ip_packet.get_source(); diff --git a/easytier/src/gateway/tokio_smoltcp/device.rs b/easytier/src/gateway/tokio_smoltcp/device.rs index 987b3d1..3bbe54b 100644 --- a/easytier/src/gateway/tokio_smoltcp/device.rs +++ b/easytier/src/gateway/tokio_smoltcp/device.rs @@ -43,7 +43,7 @@ pub struct BufferRxToken(Packet); impl RxToken for BufferRxToken { fn consume(mut self, f: F) -> R where - F: FnOnce(&mut [u8]) -> R, + F: FnOnce(&[u8]) -> R, { let p = &mut self.0; let result = f(p); diff --git a/easytier/src/instance/instance.rs b/easytier/src/instance/instance.rs index 3fbc225..04ba5d7 100644 --- a/easytier/src/instance/instance.rs +++ b/easytier/src/instance/instance.rs @@ -17,7 +17,8 @@ use crate::connector::direct::DirectConnectorManager; use crate::connector::manual::{ConnectorManagerRpcService, ManualConnectorManager}; use crate::connector::udp_hole_punch::UdpHolePunchConnector; use crate::gateway::icmp_proxy::IcmpProxy; -use crate::gateway::tcp_proxy::TcpProxy; +use crate::gateway::kcp_proxy::{KcpProxyDst, KcpProxySrc}; +use crate::gateway::tcp_proxy::{NatDstTcpConnector, TcpProxy}; use crate::gateway::udp_proxy::UdpProxy; use crate::peer_center::instance::PeerCenterInstance; use crate::peers::peer_conn::PeerConnId; @@ -40,7 +41,7 @@ use crate::gateway::socks5::Socks5Server; #[derive(Clone)] struct IpProxy { - tcp_proxy: Arc, + tcp_proxy: Arc>, icmp_proxy: Arc, udp_proxy: Arc, global_ctx: ArcGlobalCtx, @@ -49,7 +50,7 @@ struct IpProxy { impl IpProxy { fn new(global_ctx: ArcGlobalCtx, peer_manager: Arc) -> Result { - let tcp_proxy = TcpProxy::new(global_ctx.clone(), peer_manager.clone()); + let tcp_proxy = TcpProxy::new(peer_manager.clone(), NatDstTcpConnector {}); let icmp_proxy = IcmpProxy::new(global_ctx.clone(), peer_manager.clone()) .with_context(|| "create icmp proxy failed")?; let udp_proxy = UdpProxy::new(global_ctx.clone(), peer_manager.clone()) @@ -72,7 +73,7 @@ impl IpProxy { } self.started.store(true, Ordering::Relaxed); - self.tcp_proxy.start().await?; + self.tcp_proxy.start(true).await?; self.icmp_proxy.start().await?; self.udp_proxy.start().await?; Ok(()) @@ -116,6 +117,9 @@ pub struct Instance { ip_proxy: Option, + kcp_proxy_src: Option, + kcp_proxy_dst: Option, + peer_center: Arc, vpn_portal: Arc>>, @@ -193,6 +197,8 @@ impl Instance { udp_hole_puncher: Arc::new(Mutex::new(udp_hole_puncher)), ip_proxy: None, + kcp_proxy_src: None, + kcp_proxy_dst: None, peer_center, @@ -383,6 +389,18 @@ impl Instance { )?); self.run_ip_proxy().await?; + if self.global_ctx.get_flags().enable_kcp_proxy { + let src_proxy = KcpProxySrc::new(self.get_peer_manager()).await; + src_proxy.start().await; + self.kcp_proxy_src = Some(src_proxy); + } + + if !self.global_ctx.get_flags().disable_kcp_input { + let mut dst_proxy = KcpProxyDst::new(self.get_peer_manager()).await; + dst_proxy.start().await; + self.kcp_proxy_dst = Some(dst_proxy); + } + self.udp_hole_puncher.lock().await.run().await?; self.peer_center.init().await; diff --git a/easytier/src/peers/mod.rs b/easytier/src/peers/mod.rs index ff11ef2..bb9fd19 100644 --- a/easytier/src/peers/mod.rs +++ b/easytier/src/peers/mod.rs @@ -33,7 +33,7 @@ pub trait PeerPacketFilter { #[async_trait::async_trait] #[auto_impl::auto_impl(Arc)] pub trait NicPacketFilter { - async fn try_process_packet_from_nic(&self, data: &mut ZCPacket); + async fn try_process_packet_from_nic(&self, data: &mut ZCPacket) -> bool; } type BoxPeerPacketFilter = Box; diff --git a/easytier/src/peers/peer_manager.rs b/easytier/src/peers/peer_manager.rs index 1be8d40..3a96843 100644 --- a/easytier/src/peers/peer_manager.rs +++ b/easytier/src/peers/peer_manager.rs @@ -675,7 +675,7 @@ impl PeerManager { async fn run_nic_packet_process_pipeline(&self, data: &mut ZCPacket) { for pipeline in self.nic_packet_process_pipeline.read().await.iter().rev() { - pipeline.try_process_packet_from_nic(data).await; + let _ = pipeline.try_process_packet_from_nic(data).await; } } @@ -722,13 +722,7 @@ impl PeerManager { } } - pub async fn send_msg_ipv4(&self, mut msg: ZCPacket, ipv4_addr: Ipv4Addr) -> Result<(), Error> { - tracing::trace!( - "do send_msg in peer manager, msg: {:?}, ipv4_addr: {}", - msg, - ipv4_addr - ); - + pub async fn get_msg_dst_peer(&self, ipv4_addr: &Ipv4Addr) -> (Vec, bool) { let mut is_exit_node = false; let mut dst_peers = vec![]; let network_length = self @@ -736,10 +730,10 @@ impl PeerManager { .get_ipv4() .map(|x| x.network_length()) .unwrap_or(24); - let ipv4_inet = cidr::Ipv4Inet::new(ipv4_addr, network_length).unwrap(); + let ipv4_inet = cidr::Ipv4Inet::new(*ipv4_addr, network_length).unwrap(); if ipv4_addr.is_broadcast() || ipv4_addr.is_multicast() - || ipv4_addr == ipv4_inet.last_address() + || *ipv4_addr == ipv4_inet.last_address() { dst_peers.extend( self.peers @@ -760,10 +754,15 @@ impl PeerManager { } } - if dst_peers.is_empty() { - tracing::info!("no peer id for ipv4: {}", ipv4_addr); - return Ok(()); - } + (dst_peers, is_exit_node) + } + + pub async fn send_msg_ipv4(&self, mut msg: ZCPacket, ipv4_addr: Ipv4Addr) -> Result<(), Error> { + tracing::trace!( + "do send_msg in peer manager, msg: {:?}, ipv4_addr: {}", + msg, + ipv4_addr + ); msg.fill_peer_manager_hdr( self.my_peer_id, @@ -771,6 +770,24 @@ impl PeerManager { tunnel::packet_def::PacketType::Data as u8, ); self.run_nic_packet_process_pipeline(&mut msg).await; + let cur_to_peer_id = msg.peer_manager_header().unwrap().to_peer_id.into(); + if cur_to_peer_id != 0 { + return Self::send_msg_internal( + &self.peers, + &self.foreign_network_client, + msg, + cur_to_peer_id, + ) + .await; + } + + let (dst_peers, is_exit_node) = self.get_msg_dst_peer(&ipv4_addr).await; + + if dst_peers.is_empty() { + tracing::info!("no peer id for ipv4: {}", ipv4_addr); + return Ok(()); + } + let compressor = DefaultCompressor {}; compressor .compress(&mut msg, self.data_compress_algo) diff --git a/easytier/src/proto/common.proto b/easytier/src/proto/common.proto index a4d44a7..5eb21f5 100644 --- a/easytier/src/proto/common.proto +++ b/easytier/src/proto/common.proto @@ -22,6 +22,13 @@ message FlagsInConfig { bool multi_thread = 15; CompressionAlgoPb data_compress_algo = 16; bool bind_device = 17; + + // should we convert all tcp streams into kcp streams + bool enable_kcp_proxy = 18; + // does this peer allow kcp input + bool disable_kcp_input = 19; + // allow relay kcp packets (for public server, this can reduce the throughput) + bool disable_relay_kcp = 20; } message RpcDescriptor { diff --git a/easytier/src/proto/peer_rpc.proto b/easytier/src/proto/peer_rpc.proto index f493e36..3ff6baa 100644 --- a/easytier/src/proto/peer_rpc.proto +++ b/easytier/src/proto/peer_rpc.proto @@ -205,3 +205,7 @@ message HandshakeRequest { string network_name = 5; bytes network_secret_digrest = 6; } + +message KcpConnData { + common.SocketAddr dst = 4; +} diff --git a/easytier/src/tunnel/packet_def.rs b/easytier/src/tunnel/packet_def.rs index 7f6c6a9..43956e8 100644 --- a/easytier/src/tunnel/packet_def.rs +++ b/easytier/src/tunnel/packet_def.rs @@ -61,6 +61,8 @@ pub enum PacketType { RpcReq = 8, RpcResp = 9, ForeignNetworkPacket = 10, + KcpSrc = 11, + KcpDst = 12, } bitflags::bitflags! { @@ -494,6 +496,10 @@ impl ZCPacket { &self.inner[self.payload_offset()..] } + pub fn payload_bytes(mut self) -> BytesMut { + self.inner.split_off(self.payload_offset()) + } + pub fn peer_manager_header(&self) -> Option<&PeerManagerHeader> { PeerManagerHeader::ref_from_prefix( &self.inner[self