Yujia Qiao 4 роки тому
коміт
8f3bf5c7c7

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+/target
+perf.data
+perf.data.old

+ 932 - 0
Cargo.lock

@@ -0,0 +1,932 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203"
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "backoff"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fe17f59a06fe8b87a6fc8bf53bb70b3aba76d7685f432487a68cd5552853625"
+dependencies = [
+ "futures-core",
+ "getrandom",
+ "instant",
+ "pin-project",
+ "rand",
+ "tokio",
+]
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bumpalo"
+version = "3.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
+
+[[package]]
+name = "bytes"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
+
+[[package]]
+name = "cc"
+version = "1.0.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "winapi",
+]
+
+[[package]]
+name = "clap"
+version = "3.0.0-beta.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "feff3878564edb93745d58cf63e17b63f24142506e7a20c87a5521ed7bfb1d63"
+dependencies = [
+ "atty",
+ "bitflags",
+ "clap_derive",
+ "indexmap",
+ "lazy_static",
+ "os_str_bytes",
+ "strsim",
+ "termcolor",
+ "textwrap",
+ "unicase",
+]
+
+[[package]]
+name = "clap_derive"
+version = "3.0.0-beta.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b15c6b4f786ffb6192ffe65a36855bc1fc2444bcd0945ae16748dcd6ed7d0d3"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "fdlimit"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c4c9e43643f5a3be4ca5b67d26b98031ff9db6806c3440ae32e02e3ceac3f1b"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445"
+
+[[package]]
+name = "getrandom"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+
+[[package]]
+name = "heck"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "indexmap"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
+
+[[package]]
+name = "js-sys"
+version = "0.3.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
+
+[[package]]
+name = "lock_api"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "matchers"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1"
+dependencies = [
+ "regex-automata",
+]
+
+[[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+
+[[package]]
+name = "mio"
+version = "0.7.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
+dependencies = [
+ "libc",
+ "log",
+ "miow",
+ "ntapi",
+ "winapi",
+]
+
+[[package]]
+name = "miow"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "ntapi"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
+
+[[package]]
+name = "os_str_bytes"
+version = "4.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "addaa943333a514159c80c97ff4a93306530d965d27e139188283cd13e06a799"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
+dependencies = [
+ "cfg-if",
+ "instant",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "winapi",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "576bc800220cc65dac09e99e97b08b358cfab6e17078de8dc5fee223bd2d0c08"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "rathole"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "backoff",
+ "bincode",
+ "bytes",
+ "clap",
+ "fdlimit",
+ "hex",
+ "lazy_static",
+ "rand",
+ "ring",
+ "serde",
+ "socket2",
+ "tokio",
+ "toml",
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "regex"
+version = "1.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
+dependencies = [
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+
+[[package]]
+name = "ring"
+version = "0.16.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell",
+ "spin",
+ "untrusted",
+ "web-sys",
+ "winapi",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b30e4c09749c107e83dd61baf9604198efc4542863c88af39dafcaca89c7c9f9"
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "serde"
+version = "1.0.130"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.130"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
+
+[[package]]
+name = "socket2"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "syn"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "tokio"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144"
+dependencies = [
+ "autocfg",
+ "bytes",
+ "libc",
+ "memchr",
+ "mio",
+ "num_cpus",
+ "once_cell",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "tokio-macros",
+ "winapi",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9efc1aba077437943f7515666aa2b882dfabfbfdf89c819ea75a8d6e9eaba5e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
+dependencies = [
+ "cfg-if",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3"
+dependencies = [
+ "lazy_static",
+ "log",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-serde"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b"
+dependencies = [
+ "serde",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.2.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71"
+dependencies = [
+ "ansi_term",
+ "chrono",
+ "lazy_static",
+ "matchers",
+ "regex",
+ "serde",
+ "serde_json",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+ "tracing-serde",
+]
+
+[[package]]
+name = "unicase"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
+dependencies = [
+ "version_check",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "untrusted"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
+[[package]]
+name = "version_check"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
+
+[[package]]
+name = "web-sys"
+version = "0.3.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

+ 28 - 0
Cargo.toml

@@ -0,0 +1,28 @@
+[package]
+name = "rathole"
+version = "0.1.0"
+edition = "2021"
+authors = ["Yujia Qiao <code@rapiz.me>"]
+description = "A reverse proxy for NAT traversal"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[profile.bench]
+debug = 1
+
+[dependencies]
+tokio = { version = "1", features = ["full"] }
+bytes = { version = "1"}
+clap = "3.0.0-beta.5"
+toml = "0.5"
+serde = {version = "1.0", features = ["derive"]}
+anyhow = "1.0"
+ring = "0.16"
+bincode = "1"
+lazy_static = "1.4.0"
+hex = "0.4"
+rand = "0.8.0"
+backoff = {version="0.3.0", features=["tokio"]}
+tracing = "0.1"
+tracing-subscriber = "0.2"
+socket2 = "0.4"
+fdlimit = "0.2.1"

+ 176 - 0
LICENSE

@@ -0,0 +1,176 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS

+ 100 - 0
README.md

@@ -0,0 +1,100 @@
+# rathole
+![rathole-logo](./doc/img/rathole-logo.png)
+
+A fast and stable reverse proxy for NAT traversal, written in Rust
+
+rathole, like frp, can help to expose the service on the device behind the NAT to the Internet, via a server with a public IP.
+
+## Quickstart
+
+To use rathole, you need a server with a public IP, and a device behind the NAT, where some services that need to be exposed to the Internet. 
+
+Assuming you have a NAS at home behind the NAT, and want to expose its ssh service to the Internet:
+
+1. On the server which has a public IP
+
+Create `server.toml` with the following content and accommodate it to your needs.
+```toml
+# server.toml
+[server]
+bind_addr = "0.0.0.0:2333" # `2333` specifys the port that rathole listens for clients
+
+[server.services.my_nas_ssh]
+token = "use_a_secret_that_only_you_know" # Token that is used to authenticate the client for the service. Change to a arbitrary value.
+bind_addr = "0.0.0.0:5202" # `5202` specifys the port that exposes `my_nas_ssh` to the Internet
+```
+
+Then run:
+```bash
+./rathole server.toml
+```
+
+2. On the host which is behind the NAT (your NAS)
+
+Create `client.toml` with the following content and accommodate it to your needs.
+```toml
+[client]
+remote_addr = "myserver.com:2333" # The address of the server. The port must be the same with the port in `server.bind_addr`
+
+[client.services.my_nas_ssh]
+token = "use_a_secret_that_only_you_know" # Must be the same with the server to pass the validataion
+local_addr = "127.0.0.1:22" # The address of the service that needs to be forwarded
+```
+
+Then run:
+```bash
+./rathole client.toml
+```
+
+3. Now the client will try to connect to the server `myserver.com` on port `2333`, and any traffic to `myserver.com:5202` will be forwarded to the client's port `22`.
+
+So you can `ssh myserver.com:5202` to ssh to your NAS.
+
+## Configuration
+`rathole` can automatically determine to run in the server mode or the client mode, according to the content of the configuration file, if only one of `[server]` and `[client]` block is present, like the example in [Quickstart](#Quickstart).
+
+But the `[client]` and `[server]` block can also be put in one file. Then on the server side, run `rathole --server config.toml` and on the client side, run `rathole --client config.toml` to explictly tell `rathole` the running mode.
+
+Here is the full configuration specification:
+```toml
+[client]
+remote_addr = "example.com:2333" # Necessary. The address of the server
+default_token = "default_token_if_not_specify" # Optional. The default token of services, if they don't define their own ones
+
+[client.services.service1] # A service that needs forwarding. The name `service1` can change arbitrarily, as long as identical to the name in the server's configuration
+token = "whatever" # Necessary if `client.default_token` not set
+local_addr = "127.0.0.1:1081" # Necessary. The address of the service that needs to be forwarded
+
+[client.services.service2] # Multiple services can be defined
+local_addr = "127.0.0.1:1082"
+
+[server]
+bind_addr = "0.0.0.0:2333" # Necessary. The address that the server listens for clients. Generally only the port needs to be change. 
+default_token = "default_token_if_not_specify" # Optional
+
+[server.services.service1] # The service name must be identical to the client side
+token = "whatever" # Necesary if `server.default_token` not set
+bind_addr = "0.0.0.0:8081" # Necessary. The address of the service is exposed at. Generally only the port needs to be change. 
+
+[server.services.service2] 
+bind_addr = "0.0.0.1:8082"
+```
+
+# Benchmark
+
+rathole has similiar latency to frp, but can handle more connections. Also it can provide much better bandwidth than frp.
+
+See also [Benchmark](./doc/benchmark.md).
+
+![tcp_bitrate](./doc/img/tcp_bitrate.svg)
+
+![tcp_latency](./doc/img/tcp_latency.svg)
+
+# Development
+
+`rathole` is in active development. A load of features is on the way:
+
+- [ ] UDP support
+- [ ] TLS transport
+- [ ] Hot reloading
+- [ ] HTTP APIs for configuration

+ 87 - 0
doc/benchmark.md

@@ -0,0 +1,87 @@
+# Benchmark
+
+> Date: 2021/12/14
+> 
+> Arch Linux with 5.15.7-arch1-1 kernel
+>
+> Intel i7-6600U CPU @ 2.60GHz
+>
+> 20GB RAM
+
+
+## Bitrate
+
+![tcp_bitrate](./img/tcp_bitrate.svg)
+
+rathole with the following configuration:
+```toml
+[client]
+remote_addr = "localhost:2333"
+default_token = "123"
+
+[client.services.foo1]
+local_addr = "127.0.0.1:80"
+
+[server]
+bind_addr = "0.0.0.0:2333"
+default_token = "123"
+
+[server.services.foo1]
+bind_addr = "0.0.0.0:5202"
+```
+
+frp 0.38.0 with the following configuration:
+```ini
+[common]
+bind_port = 7000
+authentication_method = token
+token = 1233
+```
+```ini
+# frpc.ini
+[common]
+server_addr = 127.0.0.1
+#server_addr = 47.100.208.60
+server_port = 7000
+authentication_method = token
+token = 1233
+
+[ssh]
+type = tcp
+local_ip = 127.0.0.1
+local_port = 80
+remote_port = 5203
+```
+
+```
+$ iperf3 -v
+iperf 3.10.1 (cJSON 1.7.13)
+Linux sig 5.15.7-arch1-1 #1 SMP PREEMPT Wed, 08 Dec 2021 14:33:16 +0000 x86_64
+Optional features available: CPU affinity setting, IPv6 flow label, TCP congestion algorithm setting, sendfile / zerocopy, socket pacing, authentication, bind to device, support IPv4 don't fragment
+$ sudo iperf3 -s -p 80
+```
+
+For rathole benchmark:
+```
+$ iperf3 -c 127.0.0.1 -p 5202
+```
+
+For frp benchmark:
+```
+$ iperf3 -c 127.0.0.1 -p 5203
+```
+
+## Latency
+
+nginx/1.20.2 listens on port 80, with the default test page.
+
+frp and rathole configuration is same with the previous section.
+
+Using [ali](https://github.com/nakabonne/ali) with different rate.
+
+e.g. for rathole 10 QPS benchmark:
+```
+ali -r 10 http://127.0.0.1:5202
+```
+
+![tcp_latency](./img/tcp_latency.svg)

+ 1356 - 0
doc/img/overview.excalidraw

@@ -0,0 +1,1356 @@
+{
+  "type": "excalidraw",
+  "version": 2,
+  "source": "https://excalidraw.com",
+  "elements": [
+    {
+      "type": "rectangle",
+      "version": 127,
+      "versionNonce": 80643966,
+      "isDeleted": false,
+      "id": "_ROJe0KCjbnKQjLDcc-Ag",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "angle": 0,
+      "x": 274.66668701171875,
+      "y": 87.49995422363281,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "width": 450.66668701171875,
+      "height": 208.66667175292972,
+      "seed": 1939336259,
+      "groupIds": [],
+      "strokeSharpness": "sharp",
+      "boundElementIds": [
+        "1Sorez2zxxKqyRilx21-m",
+        "uJx77oj5eyZPw61wszaJN"
+      ],
+      "updated": 1639393963541
+    },
+    {
+      "type": "text",
+      "version": 248,
+      "versionNonce": 1524512610,
+      "isDeleted": false,
+      "id": "X-BwNQGYSBy-tPINiKBCt",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "angle": 0,
+      "x": 283.33331298828125,
+      "y": 94.50007629394531,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "width": 61,
+      "height": 25,
+      "seed": 429932333,
+      "groupIds": [],
+      "strokeSharpness": "sharp",
+      "boundElementIds": [],
+      "updated": 1639393963541,
+      "fontSize": 20,
+      "fontFamily": 1,
+      "text": "Server",
+      "baseline": 18,
+      "textAlign": "left",
+      "verticalAlign": "top"
+    },
+    {
+      "type": "ellipse",
+      "version": 166,
+      "versionNonce": 1926031294,
+      "isDeleted": false,
+      "id": "5KLQ8EXnY3KjzuLRGbhJU",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "angle": 0,
+      "x": 873.3333129882812,
+      "y": 151.5,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "width": 37.33331298828125,
+      "height": 34.66667175292969,
+      "seed": 565619875,
+      "groupIds": [],
+      "strokeSharpness": "sharp",
+      "boundElementIds": [
+        "-lU_z4mfDB58ZiJ8HlTxY"
+      ],
+      "updated": 1639393963541
+    },
+    {
+      "type": "line",
+      "version": 112,
+      "versionNonce": 447019810,
+      "isDeleted": false,
+      "id": "ViC_qO7r1ED1cN1IlPe7s",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "angle": 0,
+      "x": 892.6666259765625,
+      "y": 188.16668701171875,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "width": 0,
+      "height": 34,
+      "seed": 1032403459,
+      "groupIds": [],
+      "strokeSharpness": "round",
+      "boundElementIds": [],
+      "updated": 1639393963541,
+      "startBinding": null,
+      "endBinding": null,
+      "lastCommittedPoint": null,
+      "startArrowhead": null,
+      "endArrowhead": null,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          0,
+          34
+        ]
+      ]
+    },
+    {
+      "type": "line",
+      "version": 86,
+      "versionNonce": 177553406,
+      "isDeleted": false,
+      "id": "8SCEaNme89qCxY-xAS0it",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "angle": 0,
+      "x": 890,
+      "y": 199.5,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "width": 24,
+      "height": 18.66668701171875,
+      "seed": 773580109,
+      "groupIds": [],
+      "strokeSharpness": "round",
+      "boundElementIds": [],
+      "updated": 1639393963541,
+      "startBinding": null,
+      "endBinding": null,
+      "lastCommittedPoint": null,
+      "startArrowhead": null,
+      "endArrowhead": null,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          -24,
+          18.66668701171875
+        ]
+      ]
+    },
+    {
+      "type": "line",
+      "version": 110,
+      "versionNonce": 73221858,
+      "isDeleted": false,
+      "id": "Kbl62J0jyfWlbTEVgMqJH",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "angle": 0,
+      "x": 895.3333129882812,
+      "y": 197.5,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "width": 24,
+      "height": 18,
+      "seed": 464452045,
+      "groupIds": [],
+      "strokeSharpness": "round",
+      "boundElementIds": [],
+      "updated": 1639393963541,
+      "startBinding": null,
+      "endBinding": null,
+      "lastCommittedPoint": null,
+      "startArrowhead": null,
+      "endArrowhead": null,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          24,
+          18
+        ]
+      ]
+    },
+    {
+      "type": "line",
+      "version": 130,
+      "versionNonce": 1881706558,
+      "isDeleted": false,
+      "id": "PRHAdurETJSYCaa5l6Iwa",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "angle": 0,
+      "x": 892,
+      "y": 222.16668701171875,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "width": 14.66668701171875,
+      "height": 25.33331298828125,
+      "seed": 1595489411,
+      "groupIds": [],
+      "strokeSharpness": "round",
+      "boundElementIds": [],
+      "updated": 1639393963542,
+      "startBinding": null,
+      "endBinding": null,
+      "lastCommittedPoint": null,
+      "startArrowhead": null,
+      "endArrowhead": null,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          -14.66668701171875,
+          25.33331298828125
+        ]
+      ]
+    },
+    {
+      "type": "line",
+      "version": 162,
+      "versionNonce": 1888885410,
+      "isDeleted": false,
+      "id": "HkWBRTjPa-LTKYPbw8XS-",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "angle": 0,
+      "x": 894,
+      "y": 223.5,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "width": 17.630663207545922,
+      "height": 23.561635782942176,
+      "seed": 1412110733,
+      "groupIds": [],
+      "strokeSharpness": "round",
+      "boundElementIds": [],
+      "updated": 1639393963542,
+      "startBinding": null,
+      "endBinding": null,
+      "lastCommittedPoint": null,
+      "startArrowhead": null,
+      "endArrowhead": null,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          17.630663207545922,
+          23.561635782942176
+        ]
+      ]
+    },
+    {
+      "type": "rectangle",
+      "version": 307,
+      "versionNonce": 1975983586,
+      "isDeleted": false,
+      "id": "2GauISsAXxBxXaARLdO2v",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "angle": 0,
+      "x": 277.6666564941406,
+      "y": 419.99999237060547,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "width": 442,
+      "height": 132.0000152587891,
+      "seed": 1008142253,
+      "groupIds": [],
+      "strokeSharpness": "sharp",
+      "boundElementIds": [],
+      "updated": 1639394067925
+    },
+    {
+      "type": "text",
+      "version": 375,
+      "versionNonce": 69970238,
+      "isDeleted": false,
+      "id": "4t6IqDCz_2ovUHEWf3VyP",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "angle": 0,
+      "x": 287.16668701171875,
+      "y": 428.4999084472656,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "width": 54,
+      "height": 25,
+      "seed": 1136307299,
+      "groupIds": [],
+      "strokeSharpness": "sharp",
+      "boundElementIds": [
+        "uJx77oj5eyZPw61wszaJN"
+      ],
+      "updated": 1639394067925,
+      "fontSize": 20,
+      "fontFamily": 1,
+      "text": "Client",
+      "baseline": 18,
+      "textAlign": "left",
+      "verticalAlign": "top"
+    },
+    {
+      "type": "text",
+      "version": 406,
+      "versionNonce": 1298876478,
+      "isDeleted": false,
+      "id": "i-iOOSRyBhiISzIY5AG2O",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "angle": 0,
+      "x": 650,
+      "y": 135.50001525878906,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "width": 72,
+      "height": 40,
+      "seed": 1004543373,
+      "groupIds": [],
+      "strokeSharpness": "round",
+      "boundElementIds": [
+        "1Sorez2zxxKqyRilx21-m"
+      ],
+      "updated": 1639394028864,
+      "fontSize": 16,
+      "fontFamily": 1,
+      "text": "service1\nbind addr",
+      "baseline": 34,
+      "textAlign": "left",
+      "verticalAlign": "top"
+    },
+    {
+      "type": "text",
+      "version": 471,
+      "versionNonce": 437130622,
+      "isDeleted": false,
+      "id": "Lld8m5f8AeGoMRmkfryGK",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "angle": 0,
+      "x": 650,
+      "y": 246.1667022705078,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "width": 72,
+      "height": 40,
+      "seed": 1760597182,
+      "groupIds": [],
+      "strokeSharpness": "round",
+      "boundElementIds": [
+        "1Sorez2zxxKqyRilx21-m"
+      ],
+      "updated": 1639394090709,
+      "fontSize": 16,
+      "fontFamily": 1,
+      "text": "service2\nbind addr",
+      "baseline": 34,
+      "textAlign": "left",
+      "verticalAlign": "top"
+    },
+    {
+      "type": "text",
+      "version": 212,
+      "versionNonce": 1840891362,
+      "isDeleted": false,
+      "id": "5io-dv6h3U5ORt9DXFq37",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "angle": 0,
+      "x": 308.66668701171875,
+      "y": 250.83334350585938,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "width": 72,
+      "height": 40,
+      "seed": 1771953379,
+      "groupIds": [],
+      "strokeSharpness": "round",
+      "boundElementIds": [
+        "wLQ-nby5mNnwfX9LnFrEt"
+      ],
+      "updated": 1639393963542,
+      "fontSize": 16,
+      "fontFamily": 1,
+      "text": "server\nbind addr",
+      "baseline": 34,
+      "textAlign": "center",
+      "verticalAlign": "top"
+    },
+    {
+      "type": "arrow",
+      "version": 228,
+      "versionNonce": 1728857058,
+      "isDeleted": false,
+      "id": "-lU_z4mfDB58ZiJ8HlTxY",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "angle": 0,
+      "x": 870.7065228655306,
+      "y": 194.87651239705974,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "width": 129.96929122242796,
+      "height": 1.4508687081649327,
+      "seed": 585847683,
+      "groupIds": [],
+      "strokeSharpness": "round",
+      "boundElementIds": [],
+      "updated": 1639393963543,
+      "startBinding": {
+        "elementId": "5KLQ8EXnY3KjzuLRGbhJU",
+        "focus": -1.5107931785090518,
+        "gap": 15.791401051287782
+      },
+      "endBinding": {
+        "elementId": "mR2qjxJFdOso9NGgCoq4h",
+        "focus": 0.3208591338543321,
+        "gap": 10.737231643102632
+      },
+      "lastCommittedPoint": null,
+      "startArrowhead": null,
+      "endArrowhead": "arrow",
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          -129.96929122242796,
+          -1.4508687081649327
+        ]
+      ]
+    },
+    {
+      "id": "2DQbzxVigt_dM1muvXWYN",
+      "type": "text",
+      "x": 872.8333740234375,
+      "y": 127,
+      "width": 49,
+      "height": 20,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "sharp",
+      "seed": 1110579390,
+      "version": 21,
+      "versionNonce": 974179198,
+      "isDeleted": false,
+      "boundElementIds": null,
+      "updated": 1639393963543,
+      "text": "visitor",
+      "fontSize": 16,
+      "fontFamily": 1,
+      "textAlign": "center",
+      "verticalAlign": "top",
+      "baseline": 14
+    },
+    {
+      "id": "LU4D6A2Ugd1V9uKE6SwOC",
+      "type": "arrow",
+      "x": 696.2380319060499,
+      "y": 189.44727288880773,
+      "width": 134.59847736695497,
+      "height": 6.61671213194353,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "round",
+      "seed": 275565346,
+      "version": 351,
+      "versionNonce": 481916706,
+      "isDeleted": false,
+      "boundElementIds": null,
+      "updated": 1639393963543,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          -134.59847736695497,
+          6.61671213194353
+        ]
+      ],
+      "lastCommittedPoint": null,
+      "startBinding": {
+        "elementId": "mR2qjxJFdOso9NGgCoq4h",
+        "focus": 0.0597242207313621,
+        "gap": 9.095342117387645
+      },
+      "endBinding": {
+        "elementId": "NzpaVP1cgsvfg6KfdD99G",
+        "focus": 0.1507396149689704,
+        "gap": 3.4879322512409843
+      },
+      "startArrowhead": null,
+      "endArrowhead": null
+    },
+    {
+      "id": "NzpaVP1cgsvfg6KfdD99G",
+      "type": "diamond",
+      "x": 372.66668701171875,
+      "y": 152.5,
+      "width": 184,
+      "height": 84,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "sharp",
+      "seed": 891739170,
+      "version": 42,
+      "versionNonce": 1136678334,
+      "isDeleted": false,
+      "boundElementIds": [
+        "LU4D6A2Ugd1V9uKE6SwOC",
+        "xm8fFB4fOVowURVtFEyfx",
+        "E-k0fg9CKUsCbBcIFgpQN",
+        "iqQRk3oncpFlTohh4RxWf",
+        "S1o9eYMClf4Mrmfw9HlDs",
+        "nbIlU5kICCXoOhMWP1aoq",
+        "4mPQElLVeuU0MBB9zyNTL"
+      ],
+      "updated": 1639394052440
+    },
+    {
+      "id": "6Ym2F9bT0rNpkiLbkm6Ku",
+      "type": "text",
+      "x": 414.6666564941406,
+      "y": 185.50003051757812,
+      "width": 112,
+      "height": 20,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "sharp",
+      "seed": 162216574,
+      "version": 33,
+      "versionNonce": 199316542,
+      "isDeleted": false,
+      "boundElementIds": null,
+      "updated": 1639393963543,
+      "text": "rathole server",
+      "fontSize": 16,
+      "fontFamily": 1,
+      "textAlign": "left",
+      "verticalAlign": "top",
+      "baseline": 14
+    },
+    {
+      "id": "72LJc8JYfizCW-59n-YiJ",
+      "type": "diamond",
+      "x": 313.9999694824219,
+      "y": 456.83331298828125,
+      "width": 172.66668701171875,
+      "height": 74.66668701171876,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "sharp",
+      "seed": 627082402,
+      "version": 203,
+      "versionNonce": 1475968062,
+      "isDeleted": false,
+      "boundElementIds": [
+        "xm8fFB4fOVowURVtFEyfx",
+        "CYPbqJ97T4dK8aTY2NoA6",
+        "xQjRQnu2M-Lx4L_FApAWi",
+        "ZBwjcWgJYIRx-XieGSul2",
+        "DjwSuFQtjGNkkF4rl7myd",
+        "8qillKpd5VKO0hrasQMVX"
+      ],
+      "updated": 1639394068076
+    },
+    {
+      "id": "mR2qjxJFdOso9NGgCoq4h",
+      "type": "rectangle",
+      "x": 705.3333740234375,
+      "y": 176.83335876464844,
+      "width": 24.6666259765625,
+      "height": 24.6666259765625,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "sharp",
+      "seed": 1143103778,
+      "version": 45,
+      "versionNonce": 1343517282,
+      "isDeleted": false,
+      "boundElementIds": [
+        "-lU_z4mfDB58ZiJ8HlTxY",
+        "LU4D6A2Ugd1V9uKE6SwOC"
+      ],
+      "updated": 1639393963543
+    },
+    {
+      "id": "rp7H2PQFGWvQJIbz1y8IG",
+      "type": "rectangle",
+      "x": 705.3333740234375,
+      "y": 220.83335876464844,
+      "width": 24.6666259765625,
+      "height": 24.6666259765625,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "sharp",
+      "seed": 764034658,
+      "version": 77,
+      "versionNonce": 1528280446,
+      "isDeleted": false,
+      "boundElementIds": [
+        "-lU_z4mfDB58ZiJ8HlTxY",
+        "LU4D6A2Ugd1V9uKE6SwOC",
+        "n9WWKSJRRhkFG2L3AY6W_",
+        "4mPQElLVeuU0MBB9zyNTL"
+      ],
+      "updated": 1639394052440
+    },
+    {
+      "id": "5H4DUHb4ELZWIIpXO32ix",
+      "type": "rectangle",
+      "x": 385.3333740234375,
+      "y": 284.16668701171875,
+      "width": 22,
+      "height": 22,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "sharp",
+      "seed": 1387104638,
+      "version": 257,
+      "versionNonce": 947580094,
+      "isDeleted": false,
+      "boundElementIds": [
+        "uJx77oj5eyZPw61wszaJN",
+        "CYPbqJ97T4dK8aTY2NoA6",
+        "E-k0fg9CKUsCbBcIFgpQN",
+        "iqQRk3oncpFlTohh4RxWf",
+        "xQjRQnu2M-Lx4L_FApAWi",
+        "ZBwjcWgJYIRx-XieGSul2",
+        "S1o9eYMClf4Mrmfw9HlDs",
+        "nbIlU5kICCXoOhMWP1aoq"
+      ],
+      "updated": 1639393977422
+    },
+    {
+      "id": "1-O9JOrs2pnONGNtZiH4B",
+      "type": "text",
+      "x": 349.1666564941406,
+      "y": 482.8332824707031,
+      "width": 105,
+      "height": 20,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "sharp",
+      "seed": 1412742590,
+      "version": 106,
+      "versionNonce": 541336190,
+      "isDeleted": false,
+      "boundElementIds": null,
+      "updated": 1639394067926,
+      "text": "rathole client",
+      "fontSize": 16,
+      "fontFamily": 1,
+      "textAlign": "center",
+      "verticalAlign": "middle",
+      "baseline": 14
+    },
+    {
+      "id": "CYPbqJ97T4dK8aTY2NoA6",
+      "type": "arrow",
+      "x": 400.5230856224749,
+      "y": 452.05962166754307,
+      "width": 3.021429201284718,
+      "height": 139.89293465582432,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "round",
+      "seed": 941766434,
+      "version": 627,
+      "versionNonce": 840863074,
+      "isDeleted": false,
+      "boundElementIds": null,
+      "updated": 1639394067926,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          -3.021429201284718,
+          -139.89293465582432
+        ]
+      ],
+      "lastCommittedPoint": null,
+      "startBinding": {
+        "elementId": "72LJc8JYfizCW-59n-YiJ",
+        "focus": 0.012722437706498701,
+        "gap": 4.777461928511805
+      },
+      "endBinding": {
+        "elementId": "5H4DUHb4ELZWIIpXO32ix",
+        "focus": -0.07128881792747094,
+        "gap": 6
+      },
+      "startArrowhead": null,
+      "endArrowhead": null
+    },
+    {
+      "id": "iqQRk3oncpFlTohh4RxWf",
+      "type": "arrow",
+      "x": 411.88175007980965,
+      "y": 278.4142786269854,
+      "width": 41.1875896678809,
+      "height": 38.07827569869187,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "round",
+      "seed": 316085374,
+      "version": 241,
+      "versionNonce": 705621374,
+      "isDeleted": false,
+      "boundElementIds": null,
+      "updated": 1639393963544,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          41.1875896678809,
+          -38.07827569869187
+        ]
+      ],
+      "lastCommittedPoint": null,
+      "startBinding": {
+        "elementId": "5H4DUHb4ELZWIIpXO32ix",
+        "focus": -0.111311585930796,
+        "gap": 7.333343505859375
+      },
+      "endBinding": {
+        "elementId": "NzpaVP1cgsvfg6KfdD99G",
+        "focus": -0.4128416678755755,
+        "gap": 8.3058554409374
+      },
+      "startArrowhead": null,
+      "endArrowhead": null
+    },
+    {
+      "id": "xQjRQnu2M-Lx4L_FApAWi",
+      "type": "arrow",
+      "x": 415.03538422542744,
+      "y": 453.3711527236708,
+      "width": 10.189618736042235,
+      "height": 139.87115272367078,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "round",
+      "seed": 419928446,
+      "version": 136,
+      "versionNonce": 325751074,
+      "isDeleted": false,
+      "boundElementIds": null,
+      "updated": 1639394067926,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          -10.189618736042235,
+          -139.87115272367078
+        ]
+      ],
+      "lastCommittedPoint": null,
+      "startBinding": {
+        "elementId": "72LJc8JYfizCW-59n-YiJ",
+        "focus": 0.20468988783315253,
+        "gap": 9.013184853545113
+      },
+      "endBinding": {
+        "elementId": "5H4DUHb4ELZWIIpXO32ix",
+        "focus": -0.6081345501761519,
+        "gap": 7.33331298828125
+      },
+      "startArrowhead": null,
+      "endArrowhead": null
+    },
+    {
+      "id": "ZBwjcWgJYIRx-XieGSul2",
+      "type": "arrow",
+      "x": 381.63001651123903,
+      "y": 456.0383959625252,
+      "width": 2.158517689316966,
+      "height": 145.46817028787365,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "round",
+      "seed": 39236898,
+      "version": 133,
+      "versionNonce": 1006640354,
+      "isDeleted": false,
+      "boundElementIds": null,
+      "updated": 1639394067926,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          2.158517689316966,
+          -145.46817028787365
+        ]
+      ],
+      "lastCommittedPoint": null,
+      "startBinding": {
+        "elementId": "72LJc8JYfizCW-59n-YiJ",
+        "focus": -0.22319845234228697,
+        "gap": 8.153168060821436
+      },
+      "endBinding": {
+        "elementId": "5H4DUHb4ELZWIIpXO32ix",
+        "focus": 1.1032903390278315,
+        "gap": 4.666656494140625
+      },
+      "startArrowhead": null,
+      "endArrowhead": null
+    },
+    {
+      "id": "S1o9eYMClf4Mrmfw9HlDs",
+      "type": "arrow",
+      "x": 418.66668701171875,
+      "y": 287.5,
+      "width": 38.666656494140625,
+      "height": 43.33331298828125,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "round",
+      "seed": 247933282,
+      "version": 30,
+      "versionNonce": 1800972706,
+      "isDeleted": false,
+      "boundElementIds": null,
+      "updated": 1639393973403,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          38.666656494140625,
+          -43.33331298828125
+        ]
+      ],
+      "lastCommittedPoint": null,
+      "startBinding": {
+        "elementId": "5H4DUHb4ELZWIIpXO32ix",
+        "focus": 0.7442699983400397,
+        "gap": 11.33331298828125
+      },
+      "endBinding": {
+        "elementId": "NzpaVP1cgsvfg6KfdD99G",
+        "focus": -0.4020068751542848,
+        "gap": 10.019774658829391
+      },
+      "startArrowhead": null,
+      "endArrowhead": null
+    },
+    {
+      "id": "nbIlU5kICCXoOhMWP1aoq",
+      "type": "arrow",
+      "x": 403.3333435058594,
+      "y": 273.5,
+      "width": 44,
+      "height": 37.33331298828125,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "round",
+      "seed": 261563746,
+      "version": 26,
+      "versionNonce": 1384498722,
+      "isDeleted": false,
+      "boundElementIds": null,
+      "updated": 1639393977422,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          44,
+          -37.33331298828125
+        ]
+      ],
+      "lastCommittedPoint": null,
+      "startBinding": {
+        "elementId": "5H4DUHb4ELZWIIpXO32ix",
+        "focus": -0.7734750559093652,
+        "gap": 10.66668701171875
+      },
+      "endBinding": {
+        "elementId": "NzpaVP1cgsvfg6KfdD99G",
+        "focus": -0.6418956116222861,
+        "gap": 6.895194123821575
+      },
+      "startArrowhead": null,
+      "endArrowhead": null
+    },
+    {
+      "id": "7N39v3qK0fltyhClnuI_Q",
+      "type": "ellipse",
+      "x": 582.0000305175781,
+      "y": 444.16668701171875,
+      "width": 112.6666259765625,
+      "height": 47.33331298828125,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "sharp",
+      "seed": 1272534334,
+      "version": 116,
+      "versionNonce": 1964223486,
+      "isDeleted": false,
+      "boundElementIds": [
+        "DjwSuFQtjGNkkF4rl7myd"
+      ],
+      "updated": 1639394068076
+    },
+    {
+      "id": "THrrqy4Axfy1vlF2wrI9s",
+      "type": "ellipse",
+      "x": 582.0000305175781,
+      "y": 498.8333740234375,
+      "width": 112.6666259765625,
+      "height": 47.33331298828125,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "sharp",
+      "seed": 614595234,
+      "version": 142,
+      "versionNonce": 909853822,
+      "isDeleted": false,
+      "boundElementIds": [
+        "8qillKpd5VKO0hrasQMVX"
+      ],
+      "updated": 1639394068076
+    },
+    {
+      "id": "WyAj01yc3DnhvWQG7tHd9",
+      "type": "text",
+      "x": 605.8333435058594,
+      "y": 457.8333435058594,
+      "width": 65,
+      "height": 20,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "sharp",
+      "seed": 2040747710,
+      "version": 45,
+      "versionNonce": 172071906,
+      "isDeleted": false,
+      "boundElementIds": null,
+      "updated": 1639394067926,
+      "text": "service 1",
+      "fontSize": 16,
+      "fontFamily": 1,
+      "textAlign": "center",
+      "verticalAlign": "middle",
+      "baseline": 14
+    },
+    {
+      "id": "CDfW7H0EVISeS0Zugsf8W",
+      "type": "text",
+      "x": 600.9999694824219,
+      "y": 512.5000305175781,
+      "width": 72,
+      "height": 20,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "sharp",
+      "seed": 1292334754,
+      "version": 74,
+      "versionNonce": 1763707710,
+      "isDeleted": false,
+      "boundElementIds": null,
+      "updated": 1639394067926,
+      "text": "service 2",
+      "fontSize": 16,
+      "fontFamily": 1,
+      "textAlign": "center",
+      "verticalAlign": "middle",
+      "baseline": 14
+    },
+    {
+      "id": "DjwSuFQtjGNkkF4rl7myd",
+      "type": "arrow",
+      "x": 493.3333435058594,
+      "y": 489.0001220703125,
+      "width": 80.49465291276579,
+      "height": 19.591364584118082,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "round",
+      "seed": 1489129058,
+      "version": 177,
+      "versionNonce": 1120784162,
+      "isDeleted": false,
+      "boundElementIds": null,
+      "updated": 1639394068076,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          80.49465291276579,
+          -19.591364584118082
+        ]
+      ],
+      "lastCommittedPoint": null,
+      "startBinding": {
+        "elementId": "72LJc8JYfizCW-59n-YiJ",
+        "focus": 0.46790554502387516,
+        "gap": 7.388222614736868
+      },
+      "endBinding": {
+        "elementId": "7N39v3qK0fltyhClnuI_Q",
+        "focus": 0.5164042977199623,
+        "gap": 8.24035262874532
+      },
+      "startArrowhead": null,
+      "endArrowhead": null
+    },
+    {
+      "id": "8qillKpd5VKO0hrasQMVX",
+      "type": "arrow",
+      "x": 497.3333435058594,
+      "y": 496.33331298828125,
+      "width": 78,
+      "height": 22,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "round",
+      "seed": 1551722530,
+      "version": 144,
+      "versionNonce": 1536814754,
+      "isDeleted": false,
+      "boundElementIds": null,
+      "updated": 1639394068076,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          78,
+          22
+        ]
+      ],
+      "lastCommittedPoint": null,
+      "startBinding": {
+        "elementId": "72LJc8JYfizCW-59n-YiJ",
+        "focus": -0.6747942752141096,
+        "gap": 6.222408426625634
+      },
+      "endBinding": {
+        "elementId": "THrrqy4Axfy1vlF2wrI9s",
+        "focus": -0.4771879886646304,
+        "gap": 7.177668745668626
+      },
+      "startArrowhead": null,
+      "endArrowhead": null
+    },
+    {
+      "id": "n9WWKSJRRhkFG2L3AY6W_",
+      "type": "arrow",
+      "x": 876.3054169557988,
+      "y": 206.15009644516743,
+      "width": 139.63872994408007,
+      "height": 28.349903554832565,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "round",
+      "seed": 1631704318,
+      "version": 50,
+      "versionNonce": 1307848610,
+      "isDeleted": false,
+      "boundElementIds": null,
+      "updated": 1639394048593,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          -139.63872994408007,
+          28.349903554832565
+        ]
+      ],
+      "lastCommittedPoint": null,
+      "startBinding": null,
+      "endBinding": {
+        "elementId": "rp7H2PQFGWvQJIbz1y8IG",
+        "focus": 0.3498468485388594,
+        "gap": 6.66668701171875
+      },
+      "startArrowhead": null,
+      "endArrowhead": "arrow"
+    },
+    {
+      "id": "4mPQElLVeuU0MBB9zyNTL",
+      "type": "arrow",
+      "x": 696.6666870117188,
+      "y": 231.83334350585938,
+      "width": 140.66668701171875,
+      "height": 30.666656494140625,
+      "angle": 0,
+      "strokeColor": "#000000",
+      "backgroundColor": "transparent",
+      "fillStyle": "hachure",
+      "strokeWidth": 1,
+      "strokeStyle": "solid",
+      "roughness": 1,
+      "opacity": 100,
+      "groupIds": [],
+      "strokeSharpness": "round",
+      "seed": 1042183102,
+      "version": 16,
+      "versionNonce": 392654114,
+      "isDeleted": false,
+      "boundElementIds": null,
+      "updated": 1639394053746,
+      "points": [
+        [
+          0,
+          0
+        ],
+        [
+          -140.66668701171875,
+          -30.666656494140625
+        ]
+      ],
+      "lastCommittedPoint": null,
+      "startBinding": {
+        "elementId": "rp7H2PQFGWvQJIbz1y8IG",
+        "focus": -0.21600645731035134,
+        "gap": 8.66668701171875
+      },
+      "endBinding": {
+        "elementId": "NzpaVP1cgsvfg6KfdD99G",
+        "focus": -0.31535312984667163,
+        "gap": 5.787735184532465
+      },
+      "startArrowhead": null,
+      "endArrowhead": null
+    }
+  ],
+  "appState": {
+    "gridSize": null,
+    "viewBackgroundColor": "#ffffff"
+  },
+  "files": {}
+}

BIN
doc/img/overview.png


BIN
doc/img/rathole-logo.png


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
doc/img/tcp_bitrate.svg


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
doc/img/tcp_latency.svg


+ 34 - 0
doc/internals.md

@@ -0,0 +1,34 @@
+# Internals
+
+![overview](./img/overview.png)
+
+## Conceptions
+### Service
+The entity whose traffic needs to be forwarded
+
+### Server
+The host that runs `rathole` in the server mode
+
+### Client
+The host behind the NAT that runs `rathole` in the client mode. It has some services that need to be forwarded.
+
+### Visitor
+Who visists a *service*, via the *server*
+
+### Control Channel
+A control channel is a TCP connection between the *server* and the *client* that only carries `rathole` control commands for one *service*.
+
+### Data Channel
+
+A data channel is a TCP connection between the *server* and the *client* that only carries the encapsulated data that needs forwarding for one *service*.
+
+## The Process
+
+*TODO: Add more details about the protocol*
+
+When `rathole` starts in the client mode, it creates connections to `server.common.bind_addr` for each service. These connection acts as control channels.
+
+When a control channel starts, the server challenge the client by a nonce, the client is required to authenticate as the service it wants to represent. Then the forwarding of that service is set up.
+
+When the server accepts a connection on a service's `bind_port`, it sends a control command to the client via the corresponding contorl channel. Then the client connects to the server to create a data channel. In this way, a forwarding is set up. The server also creates a few data channels in advance to improve the latency.
+

+ 9 - 0
doc/out-of-scope.md

@@ -0,0 +1,9 @@
+# Out of Scope
+
+- *domain based forwarding for HTTP*
+
+  Use nginx to do this.
+
+- *frp's STCP*
+
+  You may want to consider secure tunnels like wireguard or zerotier.

+ 6 - 0
example/minimal/client.toml

@@ -0,0 +1,6 @@
+[client]
+remote_addr = "localhost:2333"
+default_token = "123"
+
+[client.services.foo1]
+local_addr = "127.0.0.1:80"

+ 7 - 0
example/minimal/server.toml

@@ -0,0 +1,7 @@
+[server]
+bind_addr = "0.0.0.0:2333"
+default_token = "123"
+
+[server.services.foo1]
+bind_addr = "0.0.0.0:5202"
+

+ 20 - 0
src/cli.rs

@@ -0,0 +1,20 @@
+use clap::{AppSettings, Parser};
+
+#[derive(Parser, Debug)]
+#[clap(about, version, setting(AppSettings::DeriveDisplayOrder))]
+pub struct Cli {
+    /// The path to the configuration file
+    ///
+    /// Running as a client or a server is automatically determined
+    /// according to the configuration file.
+    #[clap(parse(from_os_str), name = "config")]
+    pub config_path: std::path::PathBuf,
+
+    /// Run as a server
+    #[clap(long, short)]
+    pub server: bool,
+
+    /// Run as a client
+    #[clap(long, short)]
+    pub client: bool,
+}

+ 242 - 0
src/client.rs

@@ -0,0 +1,242 @@
+use std::collections::HashMap;
+use std::sync::Arc;
+
+use crate::config::{ClientConfig, ClientServiceConfig, Config};
+use crate::protocol::{
+    self, read_hello, DataChannelCmd,
+    Hello::{self, *},
+    CURRENT_PROTO_VRESION, HASH_WIDTH_IN_BYTES,
+};
+use crate::protocol::{read_data_cmd, Ack, Auth, ControlChannelCmd};
+use anyhow::{anyhow, bail, Context, Result};
+use backoff::ExponentialBackoff;
+use tokio::io;
+use tokio::sync::oneshot;
+use tokio::time::{self, Duration};
+use tokio::{self, io::AsyncWriteExt, net::TcpStream};
+use tracing::{debug, error, info, instrument, Instrument, Span};
+
+pub async fn run_client(config: &Config) -> Result<()> {
+    let mut client = Client::from(config)?;
+    client.run().await
+}
+
+type ServiceDigest = protocol::Digest;
+type Nonce = protocol::Digest;
+
+struct Client<'a> {
+    config: &'a ClientConfig,
+    service_handles: HashMap<String, ControlChannelHandle>,
+}
+
+impl<'a> Client<'a> {
+    fn from(config: &'a Config) -> Result<Client> {
+        if let Some(config) = &config.client {
+            Ok(Client {
+                config,
+                service_handles: HashMap::new(),
+            })
+        } else {
+            Err(anyhow!("Try to run as a client, but the configuration is missing. Please add the `[client]` block"))
+        }
+    }
+
+    async fn run(&mut self) -> Result<()> {
+        for (name, config) in &self.config.services {
+            let handle =
+                ControlChannelHandle::new((*config).clone(), self.config.remote_addr.clone());
+            self.service_handles.insert(name.clone(), handle);
+        }
+
+        loop {
+            tokio::select! {
+                val = tokio::signal::ctrl_c() => {
+                    match val {
+                        Ok(()) => {}
+                        Err(err) => {
+                            error!("Unable to listen for shutdown signal: {}", err);
+                        }
+                    }
+                    break;
+                },
+            }
+        }
+
+        // Shutdown all services
+        for (_, handle) in self.service_handles.drain() {
+            handle.shutdown();
+        }
+
+        Ok(())
+    }
+}
+
+struct RunDataChannelArgs {
+    session_key: Nonce,
+    remote_addr: String,
+    local_addr: String,
+}
+
+async fn run_data_channel(args: Arc<RunDataChannelArgs>) -> Result<()> {
+    // Retry at least every 100ms, at most for 10 seconds
+    let backoff = ExponentialBackoff {
+        max_interval: Duration::from_millis(100),
+        max_elapsed_time: Some(Duration::from_secs(10)),
+        ..Default::default()
+    };
+
+    // Connect to remote_addr
+    let mut conn = backoff::future::retry(backoff, || async {
+        Ok(TcpStream::connect(&args.remote_addr)
+            .await
+            .with_context(|| "Failed to connect to remote_addr")?)
+    })
+    .await?;
+
+    // Send nonce
+    let v: &[u8; HASH_WIDTH_IN_BYTES] = args.session_key[..].try_into().unwrap();
+    let hello = Hello::DataChannelHello(CURRENT_PROTO_VRESION, v.to_owned());
+    conn.write_all(&bincode::serialize(&hello).unwrap()).await?;
+
+    // Forward
+    match read_data_cmd(&mut conn).await? {
+        DataChannelCmd::StartForward => {
+            let mut local = TcpStream::connect(&args.local_addr)
+                .await
+                .with_context(|| "Failed to conenct to local_addr")?;
+            let _ = io::copy_bidirectional(&mut conn, &mut local).await;
+        }
+    }
+    Ok(())
+}
+
+struct ControlChannel {
+    digest: ServiceDigest,
+    service: ClientServiceConfig,
+    shutdown_rx: oneshot::Receiver<u8>,
+    remote_addr: String,
+}
+
+struct ControlChannelHandle {
+    shutdown_tx: oneshot::Sender<u8>,
+}
+
+impl ControlChannel {
+    #[instrument(skip(self), fields(service=%self.service.name))]
+    async fn run(&mut self) -> Result<()> {
+        let mut conn = TcpStream::connect(&self.remote_addr)
+            .await
+            .with_context(|| format!("Failed to connect to the server: {}", &self.remote_addr))?;
+
+        // Send hello
+        let hello_send =
+            Hello::ControlChannelHello(CURRENT_PROTO_VRESION, self.digest[..].try_into().unwrap());
+        conn.write_all(&bincode::serialize(&hello_send).unwrap())
+            .await?;
+
+        // Read hello
+        let nonce = match read_hello(&mut conn)
+            .await
+            .with_context(|| "Failed to read hello from the server")?
+        {
+            ControlChannelHello(_, d) => d,
+            _ => {
+                bail!("Unexpected type of hello");
+            }
+        };
+
+        // Send auth
+        let mut concat = Vec::from(self.service.token.as_ref().unwrap().as_bytes());
+        concat.extend_from_slice(&nonce);
+
+        let session_key = protocol::digest(&concat);
+        let auth = Auth(session_key);
+        conn.write_all(&bincode::serialize(&auth).unwrap()).await?;
+
+        // Read ack
+        match protocol::read_ack(&mut conn).await? {
+            Ack::Ok => {}
+            v => {
+                return Err(anyhow!("{}", v))
+                    .with_context(|| format!("Authentication failed: {}", self.service.name));
+            }
+        }
+
+        // Channel ready
+        info!("Control channel established");
+
+        let remote_addr = self.remote_addr.clone();
+        let local_addr = self.service.local_addr.clone();
+        let data_ch_args = Arc::new(RunDataChannelArgs {
+            session_key,
+            remote_addr,
+            local_addr,
+        });
+
+        loop {
+            tokio::select! {
+                val = protocol::read_control_cmd(&mut conn) => {
+                    let val = val?;
+                    debug!( "Received {:?}", val);
+                    match val {
+                        ControlChannelCmd::CreateDataChannel => {
+                            let args = data_ch_args.clone();
+                            tokio::spawn(async move {
+                                if let Err(e) = run_data_channel(args).await.with_context(|| "Failed to run the data channel") {
+                                    error!("{:?}", e);
+                                }
+                            }.instrument(Span::current()));
+                        }
+                    }
+                },
+                _ = &mut self.shutdown_rx => {
+                    info!( "Shutting down gracefully...");
+                    break;
+                }
+            }
+        }
+
+        Ok(())
+    }
+}
+
+impl ControlChannelHandle {
+    #[instrument(skip_all, fields(service = %service.name))]
+    fn new(service: ClientServiceConfig, remote_addr: String) -> ControlChannelHandle {
+        let digest = protocol::digest(service.name.as_bytes());
+        let (shutdown_tx, shutdown_rx) = oneshot::channel();
+        let mut s = ControlChannel {
+            digest,
+            service,
+            shutdown_rx,
+            remote_addr,
+        };
+
+        tokio::spawn(
+            async move {
+                loop {
+                    if let Err(err) = s
+                        .run()
+                        .await
+                        .with_context(|| "Failed to run the control channel")
+                    {
+                        let duration = Duration::from_secs(2);
+                        error!("{:?}\n\nRetry in {:?}...", err, duration);
+                        time::sleep(duration).await;
+                    } else {
+                        // Shutdown
+                        break;
+                    }
+                }
+            }
+            .instrument(Span::current()),
+        );
+
+        ControlChannelHandle { shutdown_tx }
+    }
+
+    fn shutdown(self) {
+        // A send failure shows that the actor has already shutdown.
+        let _ = self.shutdown_tx.send(0u8);
+    }
+}

+ 124 - 0
src/config.rs

@@ -0,0 +1,124 @@
+use anyhow::{anyhow, bail, Context, Result};
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+use std::path::PathBuf;
+use tokio::fs;
+use toml;
+
+#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
+pub enum Encryption {
+    #[serde(rename = "none")]
+    None,
+    #[serde(rename = "aes")]
+    Aes,
+}
+
+fn default_encryption() -> Encryption {
+    Encryption::None
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct ClientServiceConfig {
+    #[serde(skip)]
+    pub name: String,
+    pub local_addr: String,
+    pub token: Option<String>,
+    #[serde(default = "default_encryption")]
+    pub encryption: Encryption,
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct ServerServiceConfig {
+    #[serde(skip)]
+    pub name: String,
+    pub bind_addr: String,
+    pub token: Option<String>,
+    #[serde(default = "default_encryption")]
+    pub encryption: Encryption,
+}
+
+#[derive(Debug, Serialize, Deserialize, Default)]
+pub struct ClientConfig {
+    pub remote_addr: String,
+    pub default_token: Option<String>,
+    pub services: HashMap<String, ClientServiceConfig>,
+}
+
+#[derive(Debug, Serialize, Deserialize, Default)]
+pub struct ServerConfig {
+    pub bind_addr: String,
+    pub default_token: Option<String>,
+    pub services: HashMap<String, ServerServiceConfig>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct Config {
+    pub server: Option<ServerConfig>,
+    pub client: Option<ClientConfig>,
+}
+
+impl Config {
+    fn from_str(s: &str) -> Result<Config> {
+        let mut config: Config =
+            toml::from_str(&s).with_context(|| "Failed to parse the config")?;
+        if let Some(server) = config.server.as_mut() {
+            for (name, s) in &mut server.services {
+                s.name = name.clone();
+                if s.token.is_none() {
+                    s.token = server.default_token.clone();
+                    if s.token.is_none() {
+                        bail!("The token of service {} is not set", name);
+                    }
+                }
+            }
+        }
+        if let Some(client) = config.client.as_mut() {
+            for (name, s) in &mut client.services {
+                s.name = name.clone();
+                if s.token.is_none() {
+                    s.token = client.default_token.clone();
+                    if s.token.is_none() {
+                        bail!("The token of service {} is not set", name);
+                    }
+                }
+            }
+        }
+        if config.server.is_none() && config.client.is_none() {
+            Err(anyhow!("Neither of `[server]` or `[client]` is defined"))
+        } else {
+            Ok(config)
+        }
+    }
+
+    pub async fn from_file(path: &PathBuf) -> Result<Config> {
+        let s: String = fs::read_to_string(path)
+            .await
+            .with_context(|| format!("Failed to read the config {:?}", path))?;
+        Config::from_str(&s).with_context(|| {
+            "Configuration is invalid. Please refer to the configuration specification."
+        })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::fs;
+
+    use anyhow::Result;
+
+    #[test]
+    fn test_mimic_client_config() -> Result<()> {
+        let s = fs::read_to_string("./example/mimic/client.toml").unwrap();
+        Config::from_str(&s)?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_mimic_server_config() -> Result<()> {
+        let s = fs::read_to_string("./example/mimic/server.toml").unwrap();
+        Config::from_str(&s)?;
+        Ok(())
+    }
+}

+ 15 - 0
src/helper.rs

@@ -0,0 +1,15 @@
+use std::time::Duration;
+
+use anyhow::{Context, Result};
+use socket2::{SockRef, TcpKeepalive};
+use tokio::net::TcpStream;
+
+// Tokio hesitates to expose this option...So we have to do it on our own :(
+// The good news is that using socket2 it can be easily done, without losing portablity.
+// See https://github.com/tokio-rs/tokio/issues/3082
+pub fn set_tcp_keepalive(conn: &TcpStream) -> Result<()> {
+    let s = SockRef::from(conn);
+    let keepalive = TcpKeepalive::new().with_time(Duration::from_secs(60));
+    s.set_tcp_keepalive(&keepalive)
+        .with_context(|| "Failed to set keepalive")
+}

+ 153 - 0
src/lib.rs

@@ -0,0 +1,153 @@
+mod cli;
+mod client;
+mod config;
+mod helper;
+mod multi_map;
+mod protocol;
+mod server;
+
+pub use cli::Cli;
+pub use config::Config;
+
+use anyhow::{anyhow, Result};
+use tracing::debug;
+
+use client::run_client;
+use server::run_server;
+
+pub async fn run(args: &Cli) -> Result<()> {
+    let config = Config::from_file(&args.config_path).await?;
+
+    tracing_subscriber::fmt::init();
+
+    debug!("{:?}", config);
+
+    // Raise `nofile` limit on linux and mac
+    fdlimit::raise_fd_limit();
+
+    match determine_run_mode(&config, &args) {
+        RunMode::Undetermine => Err(anyhow!("Cannot determine running as a server or a client")),
+        RunMode::Client => run_client(&config).await,
+        RunMode::Server => run_server(&config).await,
+    }
+}
+
+#[derive(PartialEq, Eq, Debug)]
+enum RunMode {
+    Server,
+    Client,
+    Undetermine,
+}
+
+fn determine_run_mode(config: &Config, args: &Cli) -> RunMode {
+    use RunMode::*;
+    if args.client && args.server {
+        Undetermine
+    } else {
+        if args.client {
+            Client
+        } else if args.server {
+            Server
+        } else {
+            if config.server.is_some() && config.client.is_none() {
+                Server
+            } else if config.client.is_some() && config.server.is_none() {
+                Client
+            } else {
+                Undetermine
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_determine_run_mode() {
+        use config::*;
+        use RunMode::*;
+
+        struct T {
+            cfg_s: bool,
+            cfg_c: bool,
+            arg_s: bool,
+            arg_c: bool,
+            run_mode: RunMode,
+        }
+
+        let tests = [
+            T {
+                cfg_s: false,
+                cfg_c: false,
+                arg_s: false,
+                arg_c: false,
+                run_mode: Undetermine,
+            },
+            T {
+                cfg_s: true,
+                cfg_c: false,
+                arg_s: false,
+                arg_c: false,
+                run_mode: Server,
+            },
+            T {
+                cfg_s: false,
+                cfg_c: true,
+                arg_s: false,
+                arg_c: false,
+                run_mode: Client,
+            },
+            T {
+                cfg_s: true,
+                cfg_c: true,
+                arg_s: false,
+                arg_c: false,
+                run_mode: Undetermine,
+            },
+            T {
+                cfg_s: true,
+                cfg_c: true,
+                arg_s: true,
+                arg_c: false,
+                run_mode: Server,
+            },
+            T {
+                cfg_s: true,
+                cfg_c: true,
+                arg_s: false,
+                arg_c: true,
+                run_mode: Client,
+            },
+            T {
+                cfg_s: true,
+                cfg_c: true,
+                arg_s: true,
+                arg_c: true,
+                run_mode: Undetermine,
+            },
+        ];
+
+        for t in tests {
+            let config = Config {
+                server: match t.cfg_s {
+                    true => Some(ServerConfig::default()),
+                    false => None,
+                },
+                client: match t.cfg_c {
+                    true => Some(ClientConfig::default()),
+                    false => None,
+                },
+            };
+
+            let args = Cli {
+                config_path: std::path::PathBuf::new(),
+                server: t.arg_s,
+                client: t.arg_c,
+            };
+
+            assert_eq!(determine_run_mode(&config, &args), t.run_mode);
+        }
+    }
+}

+ 10 - 0
src/main.rs

@@ -0,0 +1,10 @@
+use anyhow::Result;
+use clap::Parser;
+use rathole::{run, Cli};
+use tokio;
+
+#[tokio::main]
+async fn main() -> Result<()> {
+    let args = Cli::parse();
+    run(&args).await
+}

+ 114 - 0
src/multi_map.rs

@@ -0,0 +1,114 @@
+use std::borrow::Borrow;
+use std::collections::HashMap;
+use std::hash::{Hash, Hasher};
+
+struct RawItem<K1, K2, V>(*mut (K1, K2, V));
+unsafe impl<K1, K2, V> Send for RawItem<K1, K2, V> {}
+unsafe impl<K1, K2, V> Sync for RawItem<K1, K2, V> {}
+
+pub struct MultiMap<K1, K2, V> {
+    map1: HashMap<Key<K1>, RawItem<K1, K2, V>>,
+    map2: HashMap<Key<K2>, RawItem<K1, K2, V>>,
+}
+
+struct Key<T>(*const T);
+
+unsafe impl<T> Send for Key<T> {}
+unsafe impl<T> Sync for Key<T> {}
+
+impl<T> Borrow<T> for Key<T> {
+    fn borrow(&self) -> &T {
+        unsafe { &*self.0 }
+    }
+}
+
+impl<T: Hash> Hash for Key<T> {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        (self.borrow() as &T).hash(state)
+    }
+}
+
+impl<T: PartialEq> PartialEq for Key<T> {
+    fn eq(&self, other: &Self) -> bool {
+        (self.borrow() as &T).eq(other.borrow())
+    }
+}
+
+impl<T: Eq> Eq for Key<T> {}
+
+impl<K1, K2, V> MultiMap<K1, K2, V> {
+    pub fn new() -> Self {
+        MultiMap {
+            map1: HashMap::new(),
+            map2: HashMap::new(),
+        }
+    }
+}
+
+#[allow(dead_code)]
+impl<K1, K2, V> MultiMap<K1, K2, V>
+where
+    K1: Hash + Eq + Send,
+    K2: Hash + Eq + Send,
+    V: Send,
+{
+    pub fn insert(&mut self, k1: K1, k2: K2, v: V) -> Result<(), (K1, K2, V)> {
+        if self.map1.contains_key(&k1) || self.map2.contains_key(&k2) {
+            return Err((k1, k2, v));
+        }
+        let item = Box::new((k1, k2, v));
+        let k1 = Key(&item.0);
+        let k2 = Key(&item.1);
+        let item = Box::into_raw(item);
+        self.map1.insert(k1, RawItem(item));
+        self.map2.insert(k2, RawItem(item));
+        Ok(())
+    }
+
+    pub fn get1(&self, k1: &K1) -> Option<&V> {
+        let item = self.map1.get(k1)?;
+        let item = unsafe { &*item.0 };
+        Some(&item.2)
+    }
+
+    pub fn get1_mut(&mut self, k1: &K1) -> Option<&mut V> {
+        let item = self.map1.get(k1)?;
+        let item = unsafe { &mut *item.0 };
+        Some(&mut item.2)
+    }
+
+    pub fn get2(&self, k2: &K2) -> Option<&V> {
+        let item = self.map2.get(k2)?;
+        let item = unsafe { &*item.0 };
+        Some(&item.2)
+    }
+
+    pub fn get_mut2(&mut self, k2: &K2) -> Option<&mut V> {
+        let item = self.map2.get(k2)?;
+        let item = unsafe { &mut *item.0 };
+        Some(&mut item.2)
+    }
+
+    pub fn remove1(&mut self, k1: &K1) -> Option<V> {
+        let item = self.map1.remove(k1)?;
+        let item = unsafe { Box::from_raw(item.0) };
+        self.map2.remove(&item.1);
+        Some(item.2)
+    }
+
+    pub fn remove2(&mut self, k2: &K2) -> Option<V> {
+        let item = self.map2.remove(k2)?;
+        let item = unsafe { Box::from_raw(item.0) };
+        self.map1.remove(&item.0);
+        Some(item.2)
+    }
+}
+
+impl<K1, K2, V> Drop for MultiMap<K1, K2, V> {
+    fn drop(&mut self) {
+        self.map1.clear();
+        self.map2
+            .drain()
+            .for_each(|(_, item)| drop(unsafe { Box::from_raw(item.0) }));
+    }
+}

+ 137 - 0
src/protocol.rs

@@ -0,0 +1,137 @@
+pub const HASH_WIDTH_IN_BYTES: usize = 32;
+use anyhow::{Context, Result};
+use bincode;
+
+use lazy_static::lazy_static;
+
+use serde::{Deserialize, Serialize};
+use tokio::io::AsyncReadExt;
+use tokio::net::TcpStream;
+
+type ProtocolVersion = u8;
+const PROTO_V0: u8 = 0u8;
+
+pub const CURRENT_PROTO_VRESION: ProtocolVersion = PROTO_V0;
+
+pub type Digest = [u8; HASH_WIDTH_IN_BYTES];
+
+#[derive(Deserialize, Serialize, Debug)]
+pub enum Hello {
+    ControlChannelHello(ProtocolVersion, Digest), // sha256sum(service name) or a nonce
+    DataChannelHello(ProtocolVersion, Digest),    // token provided by CreateDataChannel
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+pub struct Auth(pub Digest);
+
+#[derive(Deserialize, Serialize, Debug)]
+pub enum Ack {
+    Ok,
+    ServiceNotExist,
+    AuthFailed,
+}
+
+impl std::fmt::Display for Ack {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "{}",
+            match self {
+                Ack::Ok => "Ok",
+                Ack::ServiceNotExist => "Service not exist",
+                Ack::AuthFailed => "Incorrect token",
+            }
+        )
+    }
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+pub enum ControlChannelCmd {
+    CreateDataChannel,
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+pub enum DataChannelCmd {
+    StartForward,
+}
+
+pub fn digest(data: &[u8]) -> Digest {
+    let d = ring::digest::digest(&ring::digest::SHA256, data);
+    d.as_ref().try_into().unwrap()
+}
+
+struct PacketLength {
+    hello: usize,
+    ack: usize,
+    auth: usize,
+    c_cmd: usize,
+    d_cmd: usize,
+}
+
+impl PacketLength {
+    pub fn new() -> PacketLength {
+        let username = "default";
+        let d = digest(username.as_bytes());
+        let hello = bincode::serialized_size(&Hello::ControlChannelHello(CURRENT_PROTO_VRESION, d))
+            .unwrap() as usize;
+        let c_cmd =
+            bincode::serialized_size(&ControlChannelCmd::CreateDataChannel).unwrap() as usize;
+        let d_cmd = bincode::serialized_size(&DataChannelCmd::StartForward).unwrap() as usize;
+        let ack = Ack::Ok;
+        let ack = bincode::serialized_size(&ack).unwrap() as usize;
+
+        let auth = bincode::serialized_size(&Auth(d)).unwrap() as usize;
+        PacketLength {
+            hello,
+            ack,
+            auth,
+            c_cmd,
+            d_cmd,
+        }
+    }
+}
+
+lazy_static! {
+    static ref PACKET_LEN: PacketLength = PacketLength::new();
+}
+
+pub async fn read_hello(conn: &mut TcpStream) -> Result<Hello> {
+    let mut buf = vec![0u8; PACKET_LEN.hello];
+    conn.read_exact(&mut buf)
+        .await
+        .with_context(|| "Failed to read hello")?;
+    let hello = bincode::deserialize(&buf).with_context(|| "Failed to deserialize hello")?;
+    Ok(hello)
+}
+
+pub async fn read_auth(conn: &mut TcpStream) -> Result<Auth> {
+    let mut buf = vec![0u8; PACKET_LEN.auth];
+    conn.read_exact(&mut buf)
+        .await
+        .with_context(|| "Failed to read auth")?;
+    bincode::deserialize(&buf).with_context(|| "Failed to deserialize auth")
+}
+
+pub async fn read_ack(conn: &mut TcpStream) -> Result<Ack> {
+    let mut bytes = vec![0u8; PACKET_LEN.ack];
+    conn.read_exact(&mut bytes)
+        .await
+        .with_context(|| "Failed to read ack")?;
+    bincode::deserialize(&bytes).with_context(|| "Failed to deserialize ack")
+}
+
+pub async fn read_control_cmd(conn: &mut TcpStream) -> Result<ControlChannelCmd> {
+    let mut bytes = vec![0u8; PACKET_LEN.c_cmd];
+    conn.read_exact(&mut bytes)
+        .await
+        .with_context(|| "Failed to read control cmd")?;
+    bincode::deserialize(&bytes).with_context(|| "Failed to deserialize control cmd")
+}
+
+pub async fn read_data_cmd(conn: &mut TcpStream) -> Result<DataChannelCmd> {
+    let mut bytes = vec![0u8; PACKET_LEN.d_cmd];
+    conn.read_exact(&mut bytes)
+        .await
+        .with_context(|| "Failed to read data cmd")?;
+    bincode::deserialize(&bytes).with_context(|| "Failed to deserialize data cmd")
+}

+ 389 - 0
src/server.rs

@@ -0,0 +1,389 @@
+use std::collections::HashMap;
+use std::net::SocketAddr;
+use std::sync::Arc;
+use std::time::Duration;
+
+use crate::config::{Config, ServerConfig, ServerServiceConfig};
+use crate::helper::set_tcp_keepalive;
+use crate::multi_map::MultiMap;
+use crate::protocol::{
+    self, read_hello, Hello, Hello::ControlChannelHello, Hello::DataChannelHello,
+};
+use crate::protocol::{read_auth, Ack, ControlChannelCmd, DataChannelCmd, HASH_WIDTH_IN_BYTES};
+use anyhow::{anyhow, bail, Context, Result};
+use rand::RngCore;
+use tokio::io::{self, AsyncWriteExt};
+use tokio::sync::mpsc;
+use tokio::sync::{oneshot, RwLock};
+use tokio::time;
+use tokio::{
+    self,
+    net::{self, TcpListener, TcpStream},
+};
+use tracing::{debug, error, info, info_span, warn, Instrument};
+
+use backoff::{backoff::Backoff, ExponentialBackoff};
+
+type ServiceDigest = protocol::Digest;
+type Nonce = protocol::Digest;
+
+const POOL_SIZE: usize = 64;
+const CHAN_SIZE: usize = 2048;
+
+pub async fn run_server(config: &Config) -> Result<()> {
+    let mut server = Server::from(config)?;
+
+    server.run().await
+}
+
+type ControlChannelMap = MultiMap<ServiceDigest, Nonce, ControlChannelHandle>;
+struct Server<'a> {
+    config: &'a ServerConfig,
+    services: Arc<RwLock<HashMap<ServiceDigest, ServerServiceConfig>>>,
+    control_channels: Arc<RwLock<ControlChannelMap>>,
+}
+
+impl<'a> Server<'a> {
+    pub fn from(config: &'a Config) -> Result<Server> {
+        match &config.server {
+            Some(config) => Ok(Server {
+                config,
+                services: Arc::new(RwLock::new(Server::generate_service_hashmap(config))),
+                control_channels: Arc::new(RwLock::new(ControlChannelMap::new())),
+            }),
+            None =>
+            Err(anyhow!("Try to run as a server, but the configuration is missing. Please add the `[server]` block"))
+        }
+    }
+
+    fn generate_service_hashmap(
+        server_config: &ServerConfig,
+    ) -> HashMap<ServiceDigest, ServerServiceConfig> {
+        let mut ret = HashMap::new();
+        for u in &server_config.services {
+            ret.insert(protocol::digest(u.0.as_bytes()), (*u.1).clone());
+        }
+        ret
+    }
+
+    pub async fn run(&mut self) -> Result<()> {
+        let l = net::TcpListener::bind(&self.config.bind_addr)
+            .await
+            .with_context(|| "Failed to listen at `server.bind_addr`")?;
+        info!("Listening at {}", self.config.bind_addr);
+
+        // Retry at least every 100ms
+        let mut backoff = ExponentialBackoff {
+            max_interval: Duration::from_millis(100),
+            max_elapsed_time: None,
+            ..Default::default()
+        };
+
+        // Listen for incoming control or data channels
+        loop {
+            tokio::select! {
+                ret = l.accept() => {
+                    match ret {
+                        Err(err) => {
+                            // Possibly a EMFILE. So sleep for a while and retry
+                            if let Some(d) = backoff.next_backoff() {
+                                error!("Failed to accept: {}. Retry in {:?}...", err, d);
+                                time::sleep(d).await;
+                            } else {
+                                // This branch will never be executed according to the current retry policy
+                                error!("Too many retries. Aborting...");
+                                break;
+                            }
+                        }
+                        Ok((conn, addr)) => {
+                            backoff.reset();
+                            debug!("Incomming connection from {}", addr);
+                            let services = self.services.clone();
+                            let control_channels = self.control_channels.clone();
+                            tokio::spawn(async move {
+                                if let Err(err) = handle_connection(conn, addr, services, control_channels).await.with_context(||"Failed to handle a connection to `server.bind_addr`") {
+                                    error!("{:?}", err);
+                                }
+                            }.instrument(info_span!("handle_connection", %addr)));
+                        }
+                    }
+                },
+                _ = tokio::signal::ctrl_c() => {
+                    info!("Shuting down gracefully...");
+                    break;
+                }
+            }
+        }
+
+        Ok(())
+    }
+}
+
+async fn handle_connection(
+    mut conn: TcpStream,
+    addr: SocketAddr,
+    services: Arc<RwLock<HashMap<ServiceDigest, ServerServiceConfig>>>,
+    control_channels: Arc<RwLock<ControlChannelMap>>,
+) -> Result<()> {
+    // Read hello
+    let hello = read_hello(&mut conn).await?;
+    match hello {
+        ControlChannelHello(_, service_digest) => {
+            info!("New control channel incomming from {}", addr);
+
+            // Generate a nonce
+            let mut nonce = vec![0u8; HASH_WIDTH_IN_BYTES];
+            rand::thread_rng().fill_bytes(&mut nonce);
+
+            // Send hello
+            let hello_send = Hello::ControlChannelHello(
+                protocol::CURRENT_PROTO_VRESION,
+                nonce.clone().try_into().unwrap(),
+            );
+            conn.write_all(&bincode::serialize(&hello_send).unwrap())
+                .await?;
+
+            // Lookup the service
+            let services_guard = services.read().await;
+            let service_config = match services_guard.get(&service_digest) {
+                Some(v) => v,
+                None => {
+                    conn.write_all(&bincode::serialize(&Ack::ServiceNotExist).unwrap())
+                        .await?;
+                    bail!("No such a service {}", hex::encode(&service_digest));
+                }
+            };
+            let service_name = &service_config.name;
+
+            // Calculate the checksum
+            let mut concat = Vec::from(service_config.token.as_ref().unwrap().as_bytes());
+            concat.append(&mut nonce);
+
+            // Read auth
+            let d = match read_auth(&mut conn).await? {
+                protocol::Auth(v) => v,
+            };
+
+            // Validate
+            let session_key = protocol::digest(&concat);
+            if session_key != d {
+                conn.write_all(&bincode::serialize(&Ack::AuthFailed).unwrap())
+                    .await?;
+                debug!(
+                    "Expect {}, but got {}",
+                    hex::encode(session_key),
+                    hex::encode(d)
+                );
+                bail!("Service {} failed the authentication", service_name);
+            } else {
+                let mut h = control_channels.write().await;
+
+                if let Some(_) = h.remove1(&service_digest) {
+                    warn!(
+                        "Dropping previous control channel for digest {}",
+                        hex::encode(service_digest)
+                    );
+                }
+
+                let service_config = service_config.clone();
+                drop(services_guard);
+
+                // Send ack
+                conn.write_all(&bincode::serialize(&Ack::Ok).unwrap())
+                    .await?;
+
+                info!(service = %service_config.name, "Control channel established");
+                let handle = ControlChannelHandle::new(conn, service_config);
+
+                // Drop the old handle
+                let _ = h.insert(service_digest, session_key, handle);
+            }
+        }
+        DataChannelHello(_, nonce) => {
+            // Validate
+            let control_channels_guard = control_channels.read().await;
+            match control_channels_guard.get2(&nonce) {
+                Some(c_ch) => {
+                    if let Err(e) = set_tcp_keepalive(&conn) {
+                        error!("The connection may be unstable! {:?}", e);
+                    }
+
+                    // Send the data channel to the corresponding control channel
+                    c_ch.conn_pool.data_ch_tx.send(conn).await?;
+                }
+                None => {
+                    warn!("Data channel has incorrect nonce");
+                }
+            }
+        }
+    }
+    Ok(())
+}
+
+struct ControlChannel {
+    conn: TcpStream,
+    service: ServerServiceConfig,
+    shutdown_rx: oneshot::Receiver<bool>,
+    visitor_tx: mpsc::Sender<TcpStream>,
+}
+
+struct ControlChannelHandle {
+    shutdown_tx: oneshot::Sender<bool>,
+    conn_pool: ConnectionPoolHandle,
+}
+
+impl ControlChannelHandle {
+    fn new(conn: TcpStream, service: ServerServiceConfig) -> ControlChannelHandle {
+        let (shutdown_tx, shutdown_rx) = oneshot::channel::<bool>();
+        let name = service.name.clone();
+        let conn_pool = ConnectionPoolHandle::new();
+        let actor = ControlChannel {
+            conn,
+            shutdown_rx,
+            service,
+            visitor_tx: conn_pool.visitor_tx.clone(),
+        };
+
+        tokio::spawn(async move {
+            if let Err(err) = actor.run().await {
+                error!(%name, "{}", err);
+            }
+        });
+
+        ControlChannelHandle {
+            shutdown_tx,
+            conn_pool,
+        }
+    }
+}
+
+impl ControlChannel {
+    #[tracing::instrument(skip(self), fields(service = %self.service.name))]
+    async fn run(mut self) -> Result<()> {
+        if let Err(e) = set_tcp_keepalive(&self.conn) {
+            error!("The connection may be unstable! {:?}", e);
+        }
+
+        let l = match TcpListener::bind(&self.service.bind_addr).await {
+            Ok(v) => v,
+            Err(e) => {
+                let duration = Duration::from_secs(1);
+                error!(
+                    "Failed to listen on service.bind_addr: {}. Retry in {:?}...",
+                    e, duration
+                );
+                time::sleep(duration).await;
+                TcpListener::bind(&self.service.bind_addr).await?
+            }
+        };
+
+        info!("Listening at {}", &self.service.bind_addr);
+
+        let (data_req_tx, mut data_req_rx) = mpsc::unbounded_channel::<u8>();
+        tokio::spawn(async move {
+            let cmd = bincode::serialize(&ControlChannelCmd::CreateDataChannel).unwrap();
+            while let Some(_) = data_req_rx.recv().await {
+                if self.conn.write_all(&cmd).await.is_err() {
+                    break;
+                }
+            }
+        });
+
+        for _i in 0..POOL_SIZE {
+            if let Err(e) = data_req_tx.send(0) {
+                error!("Failed to request data channel {}", e);
+            };
+        }
+
+        let mut backoff = ExponentialBackoff {
+            max_interval: Duration::from_secs(1),
+            ..Default::default()
+        };
+        loop {
+            tokio::select! {
+                val = l.accept() => {
+                    match val {
+                        Err(e) => {
+                            error!("{}. Sleep for a while", e);
+                            if let Some(d) = backoff.next_backoff() {
+                                time::sleep(d).await;
+                            } else {
+                                error!("Too many retries. Aborting...");
+                                break;
+                            }
+                        },
+                        Ok((incoming, addr)) => {
+                            if let Err(e) = data_req_tx.send(0) {
+                                error!("{}", e);
+                                break;
+                            };
+
+                            backoff.reset();
+
+                            debug!("New visitor from {}", addr);
+
+                            let _ = self.visitor_tx.send(incoming).await;
+                        }
+                    }
+                },
+                _ = &mut self.shutdown_rx => {
+                    break;
+                }
+            }
+        }
+        info!("Service shuting down");
+
+        Ok(())
+    }
+}
+
+#[derive(Debug)]
+struct ConnectionPool {
+    visitor_rx: mpsc::Receiver<TcpStream>,
+    data_ch_rx: mpsc::Receiver<TcpStream>,
+}
+
+struct ConnectionPoolHandle {
+    visitor_tx: mpsc::Sender<TcpStream>,
+    data_ch_tx: mpsc::Sender<TcpStream>,
+}
+
+impl ConnectionPoolHandle {
+    fn new() -> ConnectionPoolHandle {
+        let (data_ch_tx, data_ch_rx) = mpsc::channel(CHAN_SIZE * 2);
+        let (visitor_tx, visitor_rx) = mpsc::channel(CHAN_SIZE);
+        let conn_pool = ConnectionPool {
+            data_ch_rx,
+            visitor_rx,
+        };
+
+        tokio::spawn(async move { conn_pool.run().await });
+
+        ConnectionPoolHandle {
+            data_ch_tx,
+            visitor_tx,
+        }
+    }
+}
+
+impl ConnectionPool {
+    #[tracing::instrument]
+    async fn run(mut self) {
+        loop {
+            if let Some(mut visitor) = self.visitor_rx.recv().await {
+                if let Some(mut ch) = self.data_ch_rx.recv().await {
+                    tokio::spawn(async move {
+                        let cmd = bincode::serialize(&DataChannelCmd::StartForward).unwrap();
+                        if ch.write_all(&cmd).await.is_ok() {
+                            let _ = io::copy_bidirectional(&mut ch, &mut visitor).await;
+                        }
+                    });
+                } else {
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
+    }
+}

Деякі файли не було показано, через те що забагато файлів було змінено