Compare commits
14 Commits
220830
...
caa62b8e49
| Author | SHA1 | Date | |
|---|---|---|---|
| caa62b8e49 | |||
| 7c64003b13 | |||
| b24e8ba68b | |||
| 3be4c5a68e | |||
| 27b50d2333 | |||
| c173ed3c5c | |||
| 1ce19ff56a | |||
| c063cdcef6 | |||
| 3cbbd72a14 | |||
| cd0f6fea94 | |||
| 27b52dfcb7 | |||
| 1b95b10553 | |||
| 723ed5e390 | |||
| 3b2866fa6a |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,5 @@
|
|||||||
target
|
target
|
||||||
.idea
|
.idea
|
||||||
|
*.crt
|
||||||
|
*.key
|
||||||
|
pki
|
||||||
|
|||||||
468
Cargo.lock
generated
468
Cargo.lock
generated
@@ -52,6 +52,7 @@ dependencies = [
|
|||||||
"actix-codec",
|
"actix-codec",
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
"actix-service",
|
"actix-service",
|
||||||
|
"actix-tls",
|
||||||
"actix-utils",
|
"actix-utils",
|
||||||
"ahash",
|
"ahash",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -143,6 +144,24 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "actix-tls"
|
||||||
|
version = "3.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9fde0cf292f7cdc7f070803cb9a0d45c018441321a78b1042ffbbb81ec333297"
|
||||||
|
dependencies = [
|
||||||
|
"actix-codec",
|
||||||
|
"actix-rt",
|
||||||
|
"actix-service",
|
||||||
|
"actix-utils",
|
||||||
|
"futures-core",
|
||||||
|
"log",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio-rustls",
|
||||||
|
"tokio-util",
|
||||||
|
"webpki-roots",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix-utils"
|
name = "actix-utils"
|
||||||
version = "3.0.0"
|
version = "3.0.0"
|
||||||
@@ -166,6 +185,7 @@ dependencies = [
|
|||||||
"actix-rt",
|
"actix-rt",
|
||||||
"actix-server",
|
"actix-server",
|
||||||
"actix-service",
|
"actix-service",
|
||||||
|
"actix-tls",
|
||||||
"actix-utils",
|
"actix-utils",
|
||||||
"actix-web-codegen",
|
"actix-web-codegen",
|
||||||
"ahash",
|
"ahash",
|
||||||
@@ -274,6 +294,45 @@ dependencies = [
|
|||||||
"alloc-no-stdlib",
|
"alloc-no-stdlib",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "asn1-rs"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf6690c370453db30743b373a60ba498fc0d6d83b11f4abfd87a84a075db5dd4"
|
||||||
|
dependencies = [
|
||||||
|
"asn1-rs-derive",
|
||||||
|
"asn1-rs-impl",
|
||||||
|
"displaydoc",
|
||||||
|
"nom",
|
||||||
|
"num-traits",
|
||||||
|
"rusticata-macros",
|
||||||
|
"thiserror",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "asn1-rs-derive"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"synstructure",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "asn1-rs-impl"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "atty"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
@@ -291,13 +350,6 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "base"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
@@ -502,6 +554,26 @@ dependencies = [
|
|||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "data-encoding"
|
||||||
|
version = "2.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "der-parser"
|
||||||
|
version = "8.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42d4bc9b0db0a0df9ae64634ac5bdefb7afcb534e182275ca0beadbe486701c1"
|
||||||
|
dependencies = [
|
||||||
|
"asn1-rs",
|
||||||
|
"displaydoc",
|
||||||
|
"nom",
|
||||||
|
"num-bigint",
|
||||||
|
"num-traits",
|
||||||
|
"rusticata-macros",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "0.99.17"
|
version = "0.99.17"
|
||||||
@@ -525,6 +597,17 @@ dependencies = [
|
|||||||
"crypto-common",
|
"crypto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "displaydoc"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.31"
|
version = "0.8.31"
|
||||||
@@ -547,15 +630,6 @@ dependencies = [
|
|||||||
"termcolor",
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fastrand"
|
|
||||||
version = "1.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
|
|
||||||
dependencies = [
|
|
||||||
"instant",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "firestorm"
|
name = "firestorm"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -578,21 +652,6 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foreign-types"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
|
||||||
dependencies = [
|
|
||||||
"foreign-types-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foreign-types-shared"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "form_urlencoded"
|
name = "form_urlencoded"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@@ -818,16 +877,18 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-tls"
|
name = "hyper-rustls"
|
||||||
version = "0.5.0"
|
version = "0.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"http",
|
||||||
"hyper",
|
"hyper",
|
||||||
"native-tls",
|
"log",
|
||||||
|
"rustls",
|
||||||
|
"rustls-native-certs",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
"tokio-rustls",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -851,15 +912,6 @@ dependencies = [
|
|||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "instant"
|
|
||||||
version = "0.1.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@@ -963,6 +1015,12 @@ version = "0.3.16"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
@@ -985,21 +1043,43 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "native-tls"
|
name = "nom"
|
||||||
version = "0.2.10"
|
version = "7.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9"
|
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"memchr",
|
||||||
"libc",
|
"minimal-lexical",
|
||||||
"log",
|
]
|
||||||
"openssl",
|
|
||||||
"openssl-probe",
|
[[package]]
|
||||||
"openssl-sys",
|
name = "num-bigint"
|
||||||
"schannel",
|
version = "0.4.3"
|
||||||
"security-framework",
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
"security-framework-sys",
|
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
|
||||||
"tempfile",
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.45"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1021,57 +1101,27 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "oid-registry"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d4bda43fd1b844cbc6e6e54b5444e2b1bc7838bce59ad205902cccbb26d6761"
|
||||||
|
dependencies = [
|
||||||
|
"asn1-rs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.13.1"
|
version = "1.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
|
checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl"
|
|
||||||
version = "0.10.41"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"cfg-if",
|
|
||||||
"foreign-types",
|
|
||||||
"libc",
|
|
||||||
"once_cell",
|
|
||||||
"openssl-macros",
|
|
||||||
"openssl-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-macros"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-probe"
|
name = "openssl-probe"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-sys"
|
|
||||||
version = "0.9.75"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"cc",
|
|
||||||
"libc",
|
|
||||||
"pkg-config",
|
|
||||||
"vcpkg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "os_str_bytes"
|
name = "os_str_bytes"
|
||||||
version = "6.3.0"
|
version = "6.3.0"
|
||||||
@@ -1107,6 +1157,15 @@ version = "1.0.8"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22"
|
checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pem"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
@@ -1125,12 +1184,6 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pkg-config"
|
|
||||||
version = "0.3.25"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
@@ -1235,15 +1288,6 @@ version = "0.6.27"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "remove_dir_all"
|
|
||||||
version = "0.5.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
|
||||||
dependencies = [
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.11.11"
|
version = "0.11.11"
|
||||||
@@ -1259,28 +1303,45 @@ dependencies = [
|
|||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"hyper",
|
"hyper",
|
||||||
"hyper-tls",
|
"hyper-rustls",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"mime",
|
"mime",
|
||||||
"native-tls",
|
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pemfile",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
"tokio-rustls",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"url",
|
"url",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
|
"webpki-roots",
|
||||||
"winreg",
|
"winreg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -1290,6 +1351,48 @@ dependencies = [
|
|||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rusticata-macros"
|
||||||
|
version = "4.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
|
||||||
|
dependencies = [
|
||||||
|
"nom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls"
|
||||||
|
version = "0.20.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"ring",
|
||||||
|
"sct",
|
||||||
|
"webpki",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-native-certs"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50"
|
||||||
|
dependencies = [
|
||||||
|
"openssl-probe",
|
||||||
|
"rustls-pemfile",
|
||||||
|
"schannel",
|
||||||
|
"security-framework",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-pemfile"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
@@ -1312,6 +1415,16 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sct"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
|
||||||
|
dependencies = [
|
||||||
|
"ring",
|
||||||
|
"untrusted",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "2.7.0"
|
version = "2.7.0"
|
||||||
@@ -1440,6 +1553,12 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spin"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
@@ -1458,48 +1577,41 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tcp_relay_client"
|
name = "synstructure"
|
||||||
version = "0.1.0"
|
version = "0.12.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base",
|
"proc-macro2",
|
||||||
"clap",
|
"quote",
|
||||||
"env_logger",
|
"syn",
|
||||||
"futures",
|
"unicode-xid",
|
||||||
"log",
|
|
||||||
"reqwest",
|
|
||||||
"tokio",
|
|
||||||
"tokio-tungstenite",
|
|
||||||
"urlencoding",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tcp_relay_server"
|
name = "tcp_over_http"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix",
|
"actix",
|
||||||
|
"actix-tls",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"actix-web-actors",
|
"actix-web-actors",
|
||||||
"base",
|
"bytes",
|
||||||
"clap",
|
"clap",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"futures",
|
"futures",
|
||||||
|
"hyper-rustls",
|
||||||
"log",
|
"log",
|
||||||
|
"pem",
|
||||||
|
"reqwest",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pemfile",
|
||||||
"serde",
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
"tokio-tungstenite",
|
||||||
|
"urlencoding",
|
||||||
[[package]]
|
"webpki",
|
||||||
name = "tempfile"
|
"x509-parser",
|
||||||
version = "3.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"fastrand",
|
|
||||||
"libc",
|
|
||||||
"redox_syscall",
|
|
||||||
"remove_dir_all",
|
|
||||||
"winapi",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1603,13 +1715,14 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-native-tls"
|
name = "tokio-rustls"
|
||||||
version = "0.3.0"
|
version = "0.23.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
|
checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"native-tls",
|
"rustls",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"webpki",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1620,8 +1733,12 @@ checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"log",
|
"log",
|
||||||
|
"rustls",
|
||||||
|
"rustls-native-certs",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-rustls",
|
||||||
"tungstenite",
|
"tungstenite",
|
||||||
|
"webpki",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1684,10 +1801,12 @@ dependencies = [
|
|||||||
"httparse",
|
"httparse",
|
||||||
"log",
|
"log",
|
||||||
"rand",
|
"rand",
|
||||||
|
"rustls",
|
||||||
"sha-1",
|
"sha-1",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"url",
|
"url",
|
||||||
"utf-8",
|
"utf-8",
|
||||||
|
"webpki",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1717,6 +1836,18 @@ dependencies = [
|
|||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "untrusted"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.2.2"
|
version = "2.2.2"
|
||||||
@@ -1741,12 +1872,6 @@ version = "0.7.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "vcpkg"
|
|
||||||
version = "0.2.15"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@@ -1845,6 +1970,25 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki"
|
||||||
|
version = "0.22.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
|
||||||
|
dependencies = [
|
||||||
|
"ring",
|
||||||
|
"untrusted",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki-roots"
|
||||||
|
version = "0.22.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf"
|
||||||
|
dependencies = [
|
||||||
|
"webpki",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@@ -1928,6 +2072,24 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x509-parser"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0ecbeb7b67ce215e40e3cc7f2ff902f94a223acf44995934763467e7b1febc8"
|
||||||
|
dependencies = [
|
||||||
|
"asn1-rs",
|
||||||
|
"base64",
|
||||||
|
"data-encoding",
|
||||||
|
"der-parser",
|
||||||
|
"lazy_static",
|
||||||
|
"nom",
|
||||||
|
"oid-registry",
|
||||||
|
"rusticata-macros",
|
||||||
|
"thiserror",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zstd"
|
name = "zstd"
|
||||||
version = "0.11.2+zstd.1.5.2"
|
version = "0.11.2+zstd.1.5.2"
|
||||||
|
|||||||
32
Cargo.toml
32
Cargo.toml
@@ -1,7 +1,27 @@
|
|||||||
[workspace]
|
[package]
|
||||||
|
name = "tcp_over_http"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "TCP-over-HTTP solution"
|
||||||
|
|
||||||
members = [
|
[dependencies]
|
||||||
"base",
|
clap = { version = "3.2.18", features = ["derive", "env"] }
|
||||||
"tcp_relay_server",
|
log = "0.4.17"
|
||||||
"tcp_relay_client"
|
env_logger = "0.9.0"
|
||||||
]
|
actix = "0.13.0"
|
||||||
|
actix-web = { version = "4", features = ["rustls"] }
|
||||||
|
actix-web-actors = "4.1.0"
|
||||||
|
actix-tls = "3.0.3"
|
||||||
|
serde = { version = "1.0.144", features = ["derive"] }
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
futures = "0.3.24"
|
||||||
|
webpki = "0.22.0"
|
||||||
|
x509-parser = "0.14.0"
|
||||||
|
pem = "1.1.0"
|
||||||
|
reqwest = { version = "0.11", features = ["json", "rustls-tls"], default-features = false }
|
||||||
|
tokio-tungstenite = { version = "0.17.2", features = ["__rustls-tls", "rustls-tls-native-roots"] }
|
||||||
|
urlencoding = "2.1.0"
|
||||||
|
hyper-rustls = { version = "0.23.0", features = ["rustls-native-certs"] }
|
||||||
|
bytes = "1.2.1"
|
||||||
|
rustls-pemfile = "1.0.1"
|
||||||
|
rustls = "0.20.6"
|
||||||
23
README.MD
23
README.MD
@@ -6,17 +6,28 @@ This project aims to provide an easy-to-setup TCP forwarding solution:
|
|||||||
| | | Client | | Server | | |
|
| | | Client | | Server | | |
|
||||||
| Client | -- TCP xx -- | | -- HTTP 80 / 443 -- | | -- TCP xx -- | Server |
|
| Client | -- TCP xx -- | | -- HTTP 80 / 443 -- | | -- TCP xx -- | Server |
|
||||||
| | | Relay | | Relay | | |
|
| | | Relay | | Relay | | |
|
||||||
|--------| |--------| |--------| |--------|
|
|--------| |--------| |--------| |--------|
|
||||||
```
|
```
|
||||||
|
|
||||||
This project can be used especially to bypass firewalls that blocks traffics
|
This project can be used especially to bypass firewalls that blocks traffics
|
||||||
from ports others than the 80 / 443 duo.
|
from ports others than the HTTP / HTTPS ports. The TCP traffic is encapsulated inside an
|
||||||
|
HTTP WebSocket between the client and the server relays.
|
||||||
|
|
||||||
This repository contains two binaries:
|
## Authentication
|
||||||
|
The client can authenticate against the server relays through two different means:
|
||||||
|
|
||||||
* `tpc_relay_server`: The server relay
|
* Using a token (the server relay can have several tokens at the same time)
|
||||||
* `tcp_relay_client`: The client relay
|
* Using a client TLS certificate. In this case, the server relay must act as a HTTPS server, and you must provide the
|
||||||
|
server the required certificates / key files in PEM format. It is also possible to provide the server a CRL file.
|
||||||
|
|
||||||
The clients relay authenticates itself to the server using a token.
|
|
||||||
|
## Binary
|
||||||
|
This repository contains a single binary which can be used as a server or a client, depending of command line arguments:
|
||||||
|
|
||||||
|
* Server mode: Act as a server relay. In case of token authentication (NOT TLS authentication), it can be put behind a reverse proxy.
|
||||||
|
* Client mode: Act as a client relay. It basically does three things:
|
||||||
|
* Fetch the list of forwared ports configuration from the server
|
||||||
|
* Listen to these port locally
|
||||||
|
* When a connection occurs on one of these ports, it forward the data exchanged by the socket to and from the server.
|
||||||
|
|
||||||
A single server - client relay pair can relay multiple ports simultaneously from the same machine.
|
A single server - client relay pair can relay multiple ports simultaneously from the same machine.
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "base"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
serde = { version = "1.0.144", features = ["derive"] }
|
|
||||||
87
src/base/cert_utils.rs
Normal file
87
src/base/cert_utils.rs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
use std::io::{Cursor, ErrorKind};
|
||||||
|
|
||||||
|
use rustls::{Certificate, PrivateKey};
|
||||||
|
use rustls_pemfile::{read_one, Item};
|
||||||
|
|
||||||
|
/// Parse PEM certificates bytes into a [`rustls::Certificate`] structure
|
||||||
|
///
|
||||||
|
/// An error is returned if not any certificate could be found
|
||||||
|
pub fn parse_pem_certificates(certs: &[u8]) -> Result<Vec<Certificate>, Box<dyn Error>> {
|
||||||
|
let certs = rustls_pemfile::certs(&mut Cursor::new(certs))?
|
||||||
|
.into_iter()
|
||||||
|
.map(Certificate)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if certs.is_empty() {
|
||||||
|
Err(std::io::Error::new(
|
||||||
|
ErrorKind::InvalidData,
|
||||||
|
"Could not find any certificate!",
|
||||||
|
))?;
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(certs)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse PEM private key bytes into a [`rustls::PrivateKey`] structure
|
||||||
|
pub fn parse_pem_private_key(privkey: &[u8]) -> Result<PrivateKey, Box<dyn Error>> {
|
||||||
|
let key = match read_one(&mut Cursor::new(privkey))? {
|
||||||
|
None => {
|
||||||
|
Err(std::io::Error::new(
|
||||||
|
ErrorKind::Other,
|
||||||
|
"Failed to extract private key!",
|
||||||
|
))?;
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
Some(Item::PKCS8Key(key)) => key,
|
||||||
|
Some(Item::RSAKey(key)) => key,
|
||||||
|
_ => {
|
||||||
|
Err(std::io::Error::new(
|
||||||
|
ErrorKind::Other,
|
||||||
|
"Unsupported private key type!",
|
||||||
|
))?;
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(PrivateKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::cert_utils::{parse_pem_certificates, parse_pem_private_key};
|
||||||
|
|
||||||
|
const SAMPLE_CERT: &[u8] = include_bytes!("../samples/TCPTunnelTest.crt");
|
||||||
|
const SAMPLE_KEY: &[u8] = include_bytes!("../samples/TCPTunnelTest.key");
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_valid_cert() {
|
||||||
|
parse_pem_certificates(SAMPLE_CERT).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_invalid_cert_1() {
|
||||||
|
parse_pem_certificates("Random content".as_bytes()).unwrap_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_invalid_cert_2() {
|
||||||
|
parse_pem_certificates(SAMPLE_KEY).unwrap_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_valid_key() {
|
||||||
|
parse_pem_private_key(SAMPLE_KEY).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_invalid_key_1() {
|
||||||
|
parse_pem_private_key("Random content".as_bytes()).unwrap_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_invalid_key_2() {
|
||||||
|
parse_pem_private_key(SAMPLE_CERT).unwrap_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/base/mod.rs
Normal file
4
src/base/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
pub mod cert_utils;
|
||||||
|
mod structs;
|
||||||
|
|
||||||
|
pub use structs::{RelayedPort, RemoteConfig};
|
||||||
3
src/lib.rs
Normal file
3
src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
mod base;
|
||||||
|
pub mod tcp_relay_client;
|
||||||
|
pub mod tcp_relay_server;
|
||||||
38
src/main.rs
Normal file
38
src/main.rs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
|
use tcp_over_http::tcp_relay_client::client_config::ClientConfig;
|
||||||
|
use tcp_over_http::tcp_relay_server::server_config::ServerConfig;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[clap(
|
||||||
|
author,
|
||||||
|
version,
|
||||||
|
about,
|
||||||
|
long_about = "Encapsulate TCP sockets inside HTTP WebSockets"
|
||||||
|
)]
|
||||||
|
struct CliArgs {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
command: SubCommands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
enum SubCommands {
|
||||||
|
/// Run as server
|
||||||
|
Server(ServerConfig),
|
||||||
|
|
||||||
|
/// Run as client
|
||||||
|
Client(ClientConfig),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() -> std::io::Result<()> {
|
||||||
|
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||||
|
|
||||||
|
let args: CliArgs = CliArgs::parse();
|
||||||
|
|
||||||
|
// Dispatch the request to the appropriate part of the program
|
||||||
|
match args.command {
|
||||||
|
SubCommands::Server(c) => tcp_over_http::tcp_relay_server::run_app(c).await,
|
||||||
|
SubCommands::Client(c) => tcp_over_http::tcp_relay_client::run_app(c).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
100
src/tcp_relay_client/client_config.rs
Normal file
100
src/tcp_relay_client/client_config.rs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
use bytes::BufMut;
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
/// TCP relay client
|
||||||
|
#[derive(Parser, Debug, Clone)]
|
||||||
|
#[clap(author, version, about, long_about = None)]
|
||||||
|
pub struct ClientConfig {
|
||||||
|
/// Access token
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub token: Option<String>,
|
||||||
|
|
||||||
|
/// Relay server
|
||||||
|
#[clap(short, long, default_value = "http://127.0.0.1:8000")]
|
||||||
|
pub relay_url: String,
|
||||||
|
|
||||||
|
/// Listen address
|
||||||
|
#[clap(short, long, default_value = "127.0.0.1")]
|
||||||
|
pub listen_address: String,
|
||||||
|
|
||||||
|
/// Alternative root certificate to use for server authentication
|
||||||
|
#[clap(short = 'c', long)]
|
||||||
|
pub root_certificate: Option<String>,
|
||||||
|
|
||||||
|
#[clap(skip)]
|
||||||
|
_root_certificate_cache: Option<Vec<u8>>,
|
||||||
|
|
||||||
|
/// TLS certificate for TLS authentication.
|
||||||
|
#[clap(long)]
|
||||||
|
pub tls_cert: Option<String>,
|
||||||
|
|
||||||
|
#[clap(skip)]
|
||||||
|
_tls_cert_cache: Option<Vec<u8>>,
|
||||||
|
|
||||||
|
/// TLS key for TLS authentication.
|
||||||
|
#[clap(long)]
|
||||||
|
pub tls_key: Option<String>,
|
||||||
|
|
||||||
|
#[clap(skip)]
|
||||||
|
_tls_key_cache: Option<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClientConfig {
|
||||||
|
/// Load certificates and put them in cache
|
||||||
|
pub fn load_certificates(&mut self) {
|
||||||
|
self._root_certificate_cache = self
|
||||||
|
.root_certificate
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| std::fs::read(c).expect("Failed to read root certificate!"));
|
||||||
|
|
||||||
|
self._tls_cert_cache = self
|
||||||
|
.tls_cert
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| std::fs::read(c).expect("Failed to read client certificate!"));
|
||||||
|
|
||||||
|
self._tls_key_cache = self
|
||||||
|
.tls_key
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| std::fs::read(c).expect("Failed to read client key!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get client token, returning a dummy token if none was specified
|
||||||
|
pub fn get_auth_token(&self) -> &str {
|
||||||
|
self.token.as_deref().unwrap_or("none")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get root certificate content
|
||||||
|
pub fn get_root_certificate(&self) -> Option<Vec<u8>> {
|
||||||
|
self._root_certificate_cache.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get client certificate & key pair, if available
|
||||||
|
pub fn get_client_keypair(&self) -> Option<(&Vec<u8>, &Vec<u8>)> {
|
||||||
|
if let (Some(cert), Some(key)) = (&self._tls_cert_cache, &self._tls_key_cache) {
|
||||||
|
Some((cert, key))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get client certificate & key pair, in a single memory buffer
|
||||||
|
pub fn get_merged_client_keypair(&self) -> Option<Vec<u8>> {
|
||||||
|
self.get_client_keypair().map(|(c, k)| {
|
||||||
|
let mut out = k.to_vec();
|
||||||
|
out.put_slice("\n".as_bytes());
|
||||||
|
out.put_slice(c);
|
||||||
|
out
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::client_config::ClientConfig;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_cli() {
|
||||||
|
use clap::CommandFactory;
|
||||||
|
ClientConfig::command().debug_assert()
|
||||||
|
}
|
||||||
|
}
|
||||||
100
src/tcp_relay_client/mod.rs
Normal file
100
src/tcp_relay_client/mod.rs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
extern crate core;
|
||||||
|
|
||||||
|
use std::error::Error;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use futures::future::join_all;
|
||||||
|
use reqwest::{Certificate, Identity};
|
||||||
|
|
||||||
|
use crate::base::RemoteConfig;
|
||||||
|
use crate::tcp_relay_client::client_config::ClientConfig;
|
||||||
|
use crate::tcp_relay_client::relay_client::relay_client;
|
||||||
|
|
||||||
|
pub mod client_config;
|
||||||
|
mod relay_client;
|
||||||
|
|
||||||
|
/// Get remote server config i.e. get the list of forwarded ports
|
||||||
|
async fn get_server_config(conf: &ClientConfig) -> Result<RemoteConfig, Box<dyn Error>> {
|
||||||
|
let url = format!("{}/config", conf.relay_url);
|
||||||
|
log::info!("Retrieving configuration on {}", url);
|
||||||
|
|
||||||
|
let mut client = reqwest::Client::builder();
|
||||||
|
|
||||||
|
// Specify root certificate, if any was specified in the command line
|
||||||
|
if let Some(cert) = conf.get_root_certificate() {
|
||||||
|
client = client.add_root_certificate(Certificate::from_pem(&cert)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify client certificate, if any
|
||||||
|
if let Some(kp) = conf.get_merged_client_keypair() {
|
||||||
|
let identity = Identity::from_pem(&kp).expect("Failed to load certificates for reqwest!");
|
||||||
|
client = client.identity(identity).use_rustls_tls();
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = client.build().expect("Failed to build reqwest client");
|
||||||
|
|
||||||
|
let req = client
|
||||||
|
.get(url)
|
||||||
|
.header("Authorization", format!("Bearer {}", conf.get_auth_token()))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
if req.status().as_u16() != 200 {
|
||||||
|
log::error!(
|
||||||
|
"Could not retrieve configuration! (got status {})",
|
||||||
|
req.status()
|
||||||
|
);
|
||||||
|
std::process::exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(req.json::<RemoteConfig>().await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Core logic of the application
|
||||||
|
pub async fn run_app(mut args: ClientConfig) -> std::io::Result<()> {
|
||||||
|
args.load_certificates();
|
||||||
|
let args = Arc::new(args);
|
||||||
|
|
||||||
|
// Check arguments coherence
|
||||||
|
if args.tls_cert.is_some() != args.tls_key.is_some() {
|
||||||
|
log::error!(
|
||||||
|
"If you specify one of TLS certificate / key, you must then specify the other!"
|
||||||
|
);
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.get_client_keypair().is_some() {
|
||||||
|
log::info!("Using client-side authentication");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get server relay configuration (fetch the list of port to forward)
|
||||||
|
let remote_conf = match get_server_config(&args).await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to fetch relay configuration from server! {}", e);
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start to listen port
|
||||||
|
let mut handles = vec![];
|
||||||
|
for port in remote_conf {
|
||||||
|
let listen_address = format!("{}:{}", args.listen_address, port.port);
|
||||||
|
|
||||||
|
let h = tokio::spawn(relay_client(
|
||||||
|
format!(
|
||||||
|
"{}/ws?id={}&token={}",
|
||||||
|
args.relay_url,
|
||||||
|
port.id,
|
||||||
|
urlencoding::encode(args.get_auth_token())
|
||||||
|
)
|
||||||
|
.replace("http", "ws"),
|
||||||
|
listen_address,
|
||||||
|
args.clone(),
|
||||||
|
));
|
||||||
|
handles.push(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
join_all(handles).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
146
src/tcp_relay_client/relay_client.rs
Normal file
146
src/tcp_relay_client/relay_client.rs
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use futures::{SinkExt, StreamExt};
|
||||||
|
use hyper_rustls::ConfigBuilderExt;
|
||||||
|
use rustls::RootCertStore;
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
|
use tokio_tungstenite::tungstenite::Message;
|
||||||
|
|
||||||
|
use crate::base::cert_utils;
|
||||||
|
|
||||||
|
use crate::tcp_relay_client::client_config::ClientConfig;
|
||||||
|
|
||||||
|
pub async fn relay_client(ws_url: String, listen_address: String, config: Arc<ClientConfig>) {
|
||||||
|
log::info!("Start to listen on {}", listen_address);
|
||||||
|
let listener = match TcpListener::bind(&listen_address).await {
|
||||||
|
Ok(l) => l,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to start to listen on {}! {}", listen_address, e);
|
||||||
|
std::process::exit(3);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (socket, _) = listener
|
||||||
|
.accept()
|
||||||
|
.await
|
||||||
|
.expect("Failed to accept new connection!");
|
||||||
|
|
||||||
|
tokio::spawn(relay_connection(ws_url.clone(), socket, config.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Relay connection
|
||||||
|
///
|
||||||
|
/// WS read => TCP write
|
||||||
|
/// TCP read => WS write
|
||||||
|
async fn relay_connection(ws_url: String, socket: TcpStream, conf: Arc<ClientConfig>) {
|
||||||
|
log::debug!("Connecting to {}...", ws_url);
|
||||||
|
|
||||||
|
let ws_stream = if ws_url.starts_with("wss") {
|
||||||
|
let config = rustls::ClientConfig::builder().with_safe_defaults();
|
||||||
|
|
||||||
|
let config = match conf.get_root_certificate() {
|
||||||
|
None => config.with_native_roots(),
|
||||||
|
Some(cert) => {
|
||||||
|
log::debug!("Using custom root certificates");
|
||||||
|
let mut store = RootCertStore::empty();
|
||||||
|
cert_utils::parse_pem_certificates(&cert)
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.for_each(|c| store.add(c).expect("Failed to add certificate to chain!"));
|
||||||
|
|
||||||
|
config.with_root_certificates(store)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = match conf.get_client_keypair() {
|
||||||
|
None => config.with_no_client_auth(),
|
||||||
|
Some((certs, key)) => {
|
||||||
|
let certs = cert_utils::parse_pem_certificates(certs)
|
||||||
|
.expect("Failed to parse client certificate!");
|
||||||
|
|
||||||
|
let key = cert_utils::parse_pem_private_key(key)
|
||||||
|
.expect("Failed to parse client auth private key!");
|
||||||
|
|
||||||
|
config
|
||||||
|
.with_single_cert(certs, key)
|
||||||
|
.expect("Failed to set client certificate!")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let connector = tokio_tungstenite::Connector::Rustls(Arc::new(config));
|
||||||
|
|
||||||
|
let (ws_stream, _) =
|
||||||
|
tokio_tungstenite::connect_async_tls_with_config(ws_url, None, Some(connector))
|
||||||
|
.await
|
||||||
|
.expect("Failed to connect to server relay!");
|
||||||
|
|
||||||
|
ws_stream
|
||||||
|
} else {
|
||||||
|
let (ws_stream, _) = tokio_tungstenite::connect_async(ws_url)
|
||||||
|
.await
|
||||||
|
.expect("Failed to connect to server relay!");
|
||||||
|
|
||||||
|
ws_stream
|
||||||
|
};
|
||||||
|
|
||||||
|
let (mut tcp_read, mut tcp_write) = socket.into_split();
|
||||||
|
let (mut ws_write, mut ws_read) = ws_stream.split();
|
||||||
|
|
||||||
|
// TCP read -> WS write
|
||||||
|
let future = async move {
|
||||||
|
let mut buff: [u8; 5000] = [0; 5000];
|
||||||
|
loop {
|
||||||
|
match tcp_read.read(&mut buff).await {
|
||||||
|
Ok(s) => {
|
||||||
|
if let Err(e) = ws_write.send(Message::Binary(Vec::from(&buff[0..s]))).await {
|
||||||
|
log::error!(
|
||||||
|
"Failed to write to WS connection! {:?} Exiting TCP read -> WS write loop...",e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if s == 0 {
|
||||||
|
log::info!("Got empty read TCP buffer. Stopping...");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!(
|
||||||
|
"Failed to read from TCP connection! {:?} Exitin TCP read -> WS write loop...",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tokio::spawn(future);
|
||||||
|
|
||||||
|
// WS read -> TCP write
|
||||||
|
while let Some(m) = ws_read.next().await {
|
||||||
|
match m {
|
||||||
|
Err(e) => {
|
||||||
|
log::error!(
|
||||||
|
"Failed to read from WebSocket. Breaking read loop... {:?}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Ok(Message::Binary(b)) => {
|
||||||
|
if let Err(e) = tcp_write.write_all(&b).await {
|
||||||
|
log::error!(
|
||||||
|
"Failed to forward message to websocket. Closing reading end... {:?}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(Message::Close(_r)) => {
|
||||||
|
log::info!("Server asked to close this WebSocket connection");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Ok(m) => log::info!("{:?}", m),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
125
src/tcp_relay_server/mod.rs
Normal file
125
src/tcp_relay_server/mod.rs
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use actix_web::{middleware, web, App, HttpRequest, HttpResponse, HttpServer, Responder};
|
||||||
|
|
||||||
|
use crate::base::{cert_utils, RelayedPort};
|
||||||
|
|
||||||
|
use crate::tcp_relay_server::relay_ws::relay_ws;
|
||||||
|
use crate::tcp_relay_server::server_config::ServerConfig;
|
||||||
|
use crate::tcp_relay_server::tls_cert_client_verifier::CustomCertClientVerifier;
|
||||||
|
|
||||||
|
mod relay_ws;
|
||||||
|
pub mod server_config;
|
||||||
|
mod tls_cert_client_verifier;
|
||||||
|
|
||||||
|
pub async fn hello_route() -> &'static str {
|
||||||
|
"Hello world!"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn config_route(req: HttpRequest, data: Data<Arc<ServerConfig>>) -> impl Responder {
|
||||||
|
if data.has_token_auth() {
|
||||||
|
let token = req
|
||||||
|
.headers()
|
||||||
|
.get("Authorization")
|
||||||
|
.map(|t| t.to_str().unwrap_or_default())
|
||||||
|
.unwrap_or_default()
|
||||||
|
.strip_prefix("Bearer ")
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if !data.tokens.iter().any(|t| t.eq(token)) {
|
||||||
|
return HttpResponse::Unauthorized().json("Missing / invalid token");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse::Ok().json(
|
||||||
|
data.ports
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(id, port)| RelayedPort {
|
||||||
|
id,
|
||||||
|
port: port + data.increment_ports,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_app(mut config: ServerConfig) -> std::io::Result<()> {
|
||||||
|
// Check if no port are to be forwarded
|
||||||
|
if config.ports.is_empty() {
|
||||||
|
log::error!("No port to forward!");
|
||||||
|
std::process::exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read tokens from file, if any
|
||||||
|
if let Some(file) = &config.tokens_file {
|
||||||
|
std::fs::read_to_string(file)
|
||||||
|
.expect("Failed to read tokens file!")
|
||||||
|
.split('\n')
|
||||||
|
.filter(|l| !l.is_empty())
|
||||||
|
.for_each(|t| config.tokens.push(t.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !config.has_auth() {
|
||||||
|
log::error!("No authentication method specified!");
|
||||||
|
std::process::exit(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.has_tls_client_auth() && !config.has_tls_config() {
|
||||||
|
log::error!("Cannot provide client auth without TLS configuration!");
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
|
||||||
|
let args = Arc::new(config);
|
||||||
|
|
||||||
|
// Load TLS configuration, if any
|
||||||
|
let tls_config = if let (Some(cert), Some(key)) = (&args.tls_cert, &args.tls_key) {
|
||||||
|
// Load TLS certificate & private key
|
||||||
|
let cert_file = std::fs::read(cert).expect("Failed to read certificate file");
|
||||||
|
let key_file = std::fs::read(key).expect("Failed to read server private key");
|
||||||
|
|
||||||
|
// Get certificates chain
|
||||||
|
let cert_chain =
|
||||||
|
cert_utils::parse_pem_certificates(&cert_file).expect("Failed to extract certificates");
|
||||||
|
|
||||||
|
// Get private key
|
||||||
|
let key =
|
||||||
|
cert_utils::parse_pem_private_key(&key_file).expect("Failed to extract private key!");
|
||||||
|
|
||||||
|
let config = rustls::ServerConfig::builder().with_safe_defaults();
|
||||||
|
|
||||||
|
let config = match args.has_tls_client_auth() {
|
||||||
|
true => config
|
||||||
|
.with_client_cert_verifier(Arc::new(CustomCertClientVerifier::new(args.clone()))),
|
||||||
|
false => config.with_no_client_auth(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = config
|
||||||
|
.with_single_cert(cert_chain, key)
|
||||||
|
.expect("Failed to load TLS certificate!");
|
||||||
|
|
||||||
|
Some(config)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
log::info!("Starting relay on http://{}", args.listen_address);
|
||||||
|
|
||||||
|
let args_clone = args.clone();
|
||||||
|
let server = HttpServer::new(move || {
|
||||||
|
App::new()
|
||||||
|
.wrap(middleware::Logger::default())
|
||||||
|
.app_data(Data::new(args_clone.clone()))
|
||||||
|
.route("/", web::get().to(hello_route))
|
||||||
|
.route("/config", web::get().to(config_route))
|
||||||
|
.route("/ws", web::get().to(relay_ws))
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(tls_conf) = tls_config {
|
||||||
|
server.bind_rustls(&args.listen_address, tls_conf)?
|
||||||
|
} else {
|
||||||
|
server.bind(&args.listen_address)?
|
||||||
|
}
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
}
|
||||||
@@ -2,14 +2,14 @@ use std::sync::Arc;
|
|||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use actix::{Actor, ActorContext, AsyncContext, Handler, Message, StreamHandler};
|
use actix::{Actor, ActorContext, AsyncContext, Handler, Message, StreamHandler};
|
||||||
use actix_web::{Error, HttpRequest, HttpResponse, web};
|
use actix_web::{web, Error, HttpRequest, HttpResponse};
|
||||||
use actix_web_actors::ws;
|
use actix_web_actors::ws;
|
||||||
use actix_web_actors::ws::{CloseCode, CloseReason};
|
use actix_web_actors::ws::{CloseCode, CloseReason};
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
|
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
use crate::args::Args;
|
use crate::tcp_relay_server::server_config::ServerConfig;
|
||||||
|
|
||||||
/// How often heartbeat pings are sent
|
/// How often heartbeat pings are sent
|
||||||
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
|
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
|
||||||
@@ -32,7 +32,6 @@ struct RelayWS {
|
|||||||
|
|
||||||
// Client must respond to ping at a specific interval, otherwise we drop connection
|
// Client must respond to ping at a specific interval, otherwise we drop connection
|
||||||
hb: Instant,
|
hb: Instant,
|
||||||
|
|
||||||
// TODO : handle socket close
|
// TODO : handle socket close
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +93,6 @@ impl Actor for RelayWS {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log::info!("Exited read loop");
|
log::info!("Exited read loop");
|
||||||
// TODO : notify context
|
|
||||||
};
|
};
|
||||||
|
|
||||||
tokio::spawn(future);
|
tokio::spawn(future);
|
||||||
@@ -110,7 +108,9 @@ impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for RelayWS {
|
|||||||
Ok(ws::Message::Text(text)) => ctx.text(text),
|
Ok(ws::Message::Text(text)) => ctx.text(text),
|
||||||
Ok(ws::Message::Close(_reason)) => ctx.stop(),
|
Ok(ws::Message::Close(_reason)) => ctx.stop(),
|
||||||
Ok(ws::Message::Binary(data)) => {
|
Ok(ws::Message::Binary(data)) => {
|
||||||
if let Err(e) = futures::executor::block_on(self.tcp_write.write_all(&data.to_vec())) {
|
if let Err(e) =
|
||||||
|
futures::executor::block_on(self.tcp_write.write_all(&data.to_vec()))
|
||||||
|
{
|
||||||
log::error!("Failed to forward some data, closing connection! {:?}", e);
|
log::error!("Failed to forward some data, closing connection! {:?}", e);
|
||||||
ctx.stop();
|
ctx.stop();
|
||||||
}
|
}
|
||||||
@@ -148,19 +148,33 @@ impl Handler<TCPReadEndClosed> for RelayWS {
|
|||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
pub struct WebSocketQuery {
|
pub struct WebSocketQuery {
|
||||||
id: usize,
|
id: usize,
|
||||||
token: String,
|
token: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn relay_ws(req: HttpRequest, stream: web::Payload,
|
pub async fn relay_ws(
|
||||||
query: web::Query<WebSocketQuery>,
|
req: HttpRequest,
|
||||||
conf: web::Data<Arc<Args>>) -> Result<HttpResponse, Error> {
|
stream: web::Payload,
|
||||||
if !conf.tokens.contains(&query.token) {
|
query: web::Query<WebSocketQuery>,
|
||||||
log::error!("Rejected WS request from {:?} due to invalid token!", req.peer_addr());
|
conf: web::Data<Arc<ServerConfig>>,
|
||||||
|
) -> Result<HttpResponse, Error> {
|
||||||
|
if conf.has_token_auth()
|
||||||
|
&& !conf
|
||||||
|
.tokens
|
||||||
|
.iter()
|
||||||
|
.any(|t| t == query.token.as_deref().unwrap_or_default())
|
||||||
|
{
|
||||||
|
log::error!(
|
||||||
|
"Rejected WS request from {:?} due to invalid token!",
|
||||||
|
req.peer_addr()
|
||||||
|
);
|
||||||
return Ok(HttpResponse::Unauthorized().json("Invalid / missing token!"));
|
return Ok(HttpResponse::Unauthorized().json("Invalid / missing token!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if conf.ports.len() <= query.id {
|
if conf.ports.len() <= query.id {
|
||||||
log::error!("Rejected WS request from {:?} due to invalid port number!", req.peer_addr());
|
log::error!(
|
||||||
|
"Rejected WS request from {:?} due to invalid port number!",
|
||||||
|
req.peer_addr()
|
||||||
|
);
|
||||||
return Ok(HttpResponse::BadRequest().json("Invalid port number!"));
|
return Ok(HttpResponse::BadRequest().json("Invalid port number!"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,14 +183,24 @@ pub async fn relay_ws(req: HttpRequest, stream: web::Payload,
|
|||||||
let (tcp_read, tcp_write) = match TcpStream::connect(&upstream_addr).await {
|
let (tcp_read, tcp_write) = match TcpStream::connect(&upstream_addr).await {
|
||||||
Ok(s) => s.into_split(),
|
Ok(s) => s.into_split(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to establish connection with upstream server! {:?}", e);
|
log::error!(
|
||||||
return Ok(HttpResponse::InternalServerError()
|
"Failed to establish connection with upstream server! {:?}",
|
||||||
.json("Failed to establish connection!"));
|
e
|
||||||
|
);
|
||||||
|
return Ok(HttpResponse::InternalServerError().json("Failed to establish connection!"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let relay = RelayWS { tcp_read: Some(tcp_read), tcp_write, hb: Instant::now() };
|
let relay = RelayWS {
|
||||||
|
tcp_read: Some(tcp_read),
|
||||||
|
tcp_write,
|
||||||
|
hb: Instant::now(),
|
||||||
|
};
|
||||||
let resp = ws::start(relay, &req, stream);
|
let resp = ws::start(relay, &req, stream);
|
||||||
log::info!("Opening new WS connection for {:?} to {}", req.peer_addr(), upstream_addr);
|
log::info!(
|
||||||
|
"Opening new WS connection for {:?} to {}",
|
||||||
|
req.peer_addr(),
|
||||||
|
upstream_addr
|
||||||
|
);
|
||||||
resp
|
resp
|
||||||
}
|
}
|
||||||
84
src/tcp_relay_server/server_config.rs
Normal file
84
src/tcp_relay_server/server_config.rs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
/// TCP relay server mode
|
||||||
|
#[derive(Parser, Debug, Clone)]
|
||||||
|
#[clap(
|
||||||
|
author,
|
||||||
|
version,
|
||||||
|
about,
|
||||||
|
long_about = "TCP-over-HTTP server. This program can be configured behind a reverse-proxy (without TLS authentication)."
|
||||||
|
)]
|
||||||
|
pub struct ServerConfig {
|
||||||
|
/// Access tokens
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub tokens: Vec<String>,
|
||||||
|
|
||||||
|
/// Access tokens stored in a file, one token per line
|
||||||
|
#[clap(long)]
|
||||||
|
pub tokens_file: Option<String>,
|
||||||
|
|
||||||
|
/// Forwarded ports
|
||||||
|
#[clap(short, long)]
|
||||||
|
pub ports: Vec<u16>,
|
||||||
|
|
||||||
|
/// Upstream server
|
||||||
|
#[clap(short, long, default_value = "127.0.0.1")]
|
||||||
|
pub upstream_server: String,
|
||||||
|
|
||||||
|
/// HTTP server listen address
|
||||||
|
#[clap(short, long, default_value = "0.0.0.0:8000")]
|
||||||
|
pub listen_address: String,
|
||||||
|
|
||||||
|
/// Increment ports on client. Useful for debugging and running both client and server
|
||||||
|
/// on the same machine
|
||||||
|
#[clap(short, long, default_value_t = 0)]
|
||||||
|
pub increment_ports: u16,
|
||||||
|
|
||||||
|
/// TLS certificate. Specify also private key to use HTTPS/TLS instead of HTTP
|
||||||
|
#[clap(long)]
|
||||||
|
pub tls_cert: Option<String>,
|
||||||
|
|
||||||
|
/// TLS private key. Specify also certificate to use HTTPS/TLS instead of HTTP
|
||||||
|
#[clap(long)]
|
||||||
|
pub tls_key: Option<String>,
|
||||||
|
|
||||||
|
/// Restrict TLS client authentication to certificates signed directly or indirectly by the
|
||||||
|
/// provided root certificates
|
||||||
|
///
|
||||||
|
/// This option automatically enable TLS client authentication
|
||||||
|
#[clap(long)]
|
||||||
|
pub tls_client_auth_root_cert: Option<String>,
|
||||||
|
|
||||||
|
/// TLS client authentication revocation list (CRL file)
|
||||||
|
#[clap(long)]
|
||||||
|
pub tls_revocation_list: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerConfig {
|
||||||
|
pub fn has_token_auth(&self) -> bool {
|
||||||
|
!self.tokens.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_tls_config(&self) -> bool {
|
||||||
|
self.tls_cert.is_some() && self.tls_key.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_tls_client_auth(&self) -> bool {
|
||||||
|
self.tls_client_auth_root_cert.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_auth(&self) -> bool {
|
||||||
|
self.has_token_auth() || self.has_tls_client_auth()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::server_config::ServerConfig;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn verify_cli() {
|
||||||
|
use clap::CommandFactory;
|
||||||
|
ServerConfig::command().debug_assert()
|
||||||
|
}
|
||||||
|
}
|
||||||
103
src/tcp_relay_server/tls_cert_client_verifier.rs
Normal file
103
src/tcp_relay_server/tls_cert_client_verifier.rs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
use rustls::internal::msgs::enums::AlertDescription;
|
||||||
|
use rustls::server::{AllowAnyAuthenticatedClient, ClientCertVerified, ClientCertVerifier};
|
||||||
|
use rustls::{Certificate, DistinguishedNames, Error, RootCertStore};
|
||||||
|
use x509_parser::prelude::{CertificateRevocationList, FromDer, X509Certificate};
|
||||||
|
|
||||||
|
use crate::base::cert_utils::parse_pem_certificates;
|
||||||
|
|
||||||
|
use crate::tcp_relay_server::server_config::ServerConfig;
|
||||||
|
|
||||||
|
pub struct CustomCertClientVerifier {
|
||||||
|
upstream_cert_verifier: Box<Arc<dyn ClientCertVerifier>>,
|
||||||
|
crl: Option<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomCertClientVerifier {
|
||||||
|
pub fn new(conf: Arc<ServerConfig>) -> Self {
|
||||||
|
// Build root certifications list
|
||||||
|
let cert_path = conf
|
||||||
|
.tls_client_auth_root_cert
|
||||||
|
.as_deref()
|
||||||
|
.expect("No root certificates for client authentication provided!");
|
||||||
|
let cert_file = std::fs::read(cert_path)
|
||||||
|
.expect("Failed to read root certificates for client authentication!");
|
||||||
|
|
||||||
|
let root_certs = parse_pem_certificates(&cert_file)
|
||||||
|
.expect("Failed to read root certificates for server authentication!");
|
||||||
|
|
||||||
|
if root_certs.is_empty() {
|
||||||
|
log::error!("No certificates found for client authentication!");
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut store = RootCertStore::empty();
|
||||||
|
for cert in root_certs {
|
||||||
|
store
|
||||||
|
.add(&cert)
|
||||||
|
.expect("Failed to add certificate to root store");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse CRL file (if any)
|
||||||
|
let crl = if let Some(crl_file) = &conf.tls_revocation_list {
|
||||||
|
let crl_file = std::fs::read(crl_file).expect("Failed to open / read CRL file!");
|
||||||
|
|
||||||
|
let parsed_crl = pem::parse(crl_file).expect("Failed to decode CRL file!");
|
||||||
|
|
||||||
|
Some(parsed_crl.contents)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
upstream_cert_verifier: Box::new(AllowAnyAuthenticatedClient::new(store)),
|
||||||
|
crl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClientCertVerifier for CustomCertClientVerifier {
|
||||||
|
fn offer_client_auth(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn client_auth_mandatory(&self) -> Option<bool> {
|
||||||
|
Some(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn client_auth_root_subjects(&self) -> Option<DistinguishedNames> {
|
||||||
|
Some(vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_client_cert(
|
||||||
|
&self,
|
||||||
|
end_entity: &Certificate,
|
||||||
|
intermediates: &[Certificate],
|
||||||
|
now: SystemTime,
|
||||||
|
) -> Result<ClientCertVerified, Error> {
|
||||||
|
// Check the certificates sent by the client has been revoked
|
||||||
|
if let Some(crl) = &self.crl {
|
||||||
|
let (_rem, crl) =
|
||||||
|
CertificateRevocationList::from_der(crl).expect("Failed to read CRL!");
|
||||||
|
|
||||||
|
let (_rem, cert) =
|
||||||
|
X509Certificate::from_der(&end_entity.0).expect("Failed to read certificate!");
|
||||||
|
|
||||||
|
for revoked in crl.iter_revoked_certificates() {
|
||||||
|
if revoked.user_certificate == cert.serial {
|
||||||
|
log::error!(
|
||||||
|
"Client attempted to use a revoked certificate! Serial={:?} Subject={}",
|
||||||
|
cert.serial,
|
||||||
|
cert.subject
|
||||||
|
);
|
||||||
|
return Err(Error::AlertReceived(AlertDescription::CertificateRevoked));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.upstream_cert_verifier
|
||||||
|
.verify_client_cert(end_entity, intermediates, now)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "tcp_relay_client"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
base = { path = "../base" }
|
|
||||||
clap = { version = "3.2.18", features = ["derive", "env"] }
|
|
||||||
log = "0.4.17"
|
|
||||||
env_logger = "0.9.0"
|
|
||||||
reqwest = { version = "0.11", features = ["json"] }
|
|
||||||
tokio = { version = "1", features = ["full"] }
|
|
||||||
futures = "0.3.24"
|
|
||||||
tokio-tungstenite = "0.17.2"
|
|
||||||
urlencoding = "2.1.0"
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
pub mod relay_client;
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use clap::Parser;
|
|
||||||
use futures::future::join_all;
|
|
||||||
|
|
||||||
use base::RemoteConfig;
|
|
||||||
use tcp_relay_client::relay_client::relay_client;
|
|
||||||
|
|
||||||
/// TCP relay client
|
|
||||||
#[derive(Parser, Debug, Clone)]
|
|
||||||
#[clap(author, version, about, long_about = None)]
|
|
||||||
pub struct Args {
|
|
||||||
/// Access token
|
|
||||||
#[clap(short, long)]
|
|
||||||
pub token: String,
|
|
||||||
|
|
||||||
/// Relay server
|
|
||||||
#[clap(short, long, default_value = "http://127.0.0.1:8000")]
|
|
||||||
pub relay_url: String,
|
|
||||||
|
|
||||||
/// Listen address
|
|
||||||
#[clap(short, long, default_value = "127.0.0.1")]
|
|
||||||
pub listen_address: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
|
||||||
|
|
||||||
let args: Args = Args::parse();
|
|
||||||
let args = Arc::new(args);
|
|
||||||
|
|
||||||
// Get server relay configuration (fetch the list of port to forward)
|
|
||||||
let url = format!("{}/config", args.relay_url);
|
|
||||||
log::info!("Retrieving configuration on {}", url);
|
|
||||||
let req = reqwest::Client::new().get(url)
|
|
||||||
.header("Authorization", format!("Bearer {}", args.token))
|
|
||||||
.send()
|
|
||||||
.await?;
|
|
||||||
if req.status().as_u16() != 200 {
|
|
||||||
log::error!("Could not retrieve configuration! (got status {})", req.status());
|
|
||||||
std::process::exit(2);
|
|
||||||
}
|
|
||||||
let conf = req.json::<RemoteConfig>()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Start to listen port
|
|
||||||
let mut handles = vec![];
|
|
||||||
for port in conf {
|
|
||||||
let listen_address = format!("{}:{}", args.listen_address, port.port);
|
|
||||||
|
|
||||||
let h = tokio::spawn(relay_client(
|
|
||||||
format!("{}/ws?id={}&token={}",
|
|
||||||
args.relay_url, port.id, urlencoding::encode(&args.token))
|
|
||||||
.replace("http", "ws"),
|
|
||||||
listen_address,
|
|
||||||
));
|
|
||||||
handles.push(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
join_all(handles).await;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
use futures::{SinkExt, StreamExt};
|
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
|
||||||
use tokio_tungstenite::tungstenite::Message;
|
|
||||||
|
|
||||||
pub async fn relay_client(ws_url: String, listen_address: String) {
|
|
||||||
log::info!("Start to listen on {}", listen_address);
|
|
||||||
let listener = match TcpListener::bind(&listen_address).await {
|
|
||||||
Ok(l) => l,
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Failed to start to listen on {}! {}", listen_address, e);
|
|
||||||
std::process::exit(3);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let (socket, _) = listener.accept().await
|
|
||||||
.expect("Failed to accept new connection!");
|
|
||||||
|
|
||||||
tokio::spawn(relay_connection(ws_url.clone(), socket));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Relay connection
|
|
||||||
///
|
|
||||||
/// WS read => TCP write
|
|
||||||
/// TCP read => WS write
|
|
||||||
async fn relay_connection(ws_url: String, socket: TcpStream) {
|
|
||||||
log::debug!("Connecting to {}...", ws_url);
|
|
||||||
let (ws_stream, _) = tokio_tungstenite::connect_async(ws_url)
|
|
||||||
.await.expect("Failed to connect to server relay!");
|
|
||||||
|
|
||||||
|
|
||||||
let (mut tcp_read, mut tcp_write) = socket.into_split();
|
|
||||||
let (mut ws_write, mut ws_read) =
|
|
||||||
ws_stream.split();
|
|
||||||
|
|
||||||
// TCP read -> WS write
|
|
||||||
let future = async move {
|
|
||||||
let mut buff: [u8; 5000] = [0; 5000];
|
|
||||||
loop {
|
|
||||||
match tcp_read.read(&mut buff).await {
|
|
||||||
Ok(s) => {
|
|
||||||
if let Err(e) = ws_write.send(Message::Binary(Vec::from(&buff[0..s]))).await {
|
|
||||||
log::error!(
|
|
||||||
"Failed to write to WS connection! {:?} Exiting TCP read -> WS write loop...",e);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if s == 0 {
|
|
||||||
log::info!("Got empty read TCP buffer. Stopping...");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!(
|
|
||||||
"Failed to read from TCP connection! {:?} Exitin TCP read -> WS write loop...",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
tokio::spawn(future);
|
|
||||||
|
|
||||||
// WS read -> TCP write
|
|
||||||
while let Some(m) = ws_read.next().await {
|
|
||||||
match m {
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Failed to read from WebSocket. Breaking read loop... {:?}", e);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Ok(Message::Binary(b)) => {
|
|
||||||
if let Err(e) = tcp_write.write_all(&b).await {
|
|
||||||
log::error!("Failed to forward message to websocket. Closing reading end... {:?}", e);
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Ok(Message::Close(_r)) => {
|
|
||||||
log::info!("Server asked to close this WebSocket connection");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Ok(m) => log::info!("{:?}", m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "tcp_relay_server"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
base = { path = "../base" }
|
|
||||||
clap = { version = "3.2.18", features = ["derive", "env"] }
|
|
||||||
log = "0.4.17"
|
|
||||||
env_logger = "0.9.0"
|
|
||||||
actix = "0.13.0"
|
|
||||||
actix-web = "4"
|
|
||||||
actix-web-actors = "4.1.0"
|
|
||||||
serde = { version = "1.0.144", features = ["derive"] }
|
|
||||||
tokio = { version = "1", features = ["full"] }
|
|
||||||
futures = "0.3.24"
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
use clap::Parser;
|
|
||||||
|
|
||||||
/// TCP relay server
|
|
||||||
#[derive(Parser, Debug, Clone)]
|
|
||||||
#[clap(author, version, about, long_about = None)]
|
|
||||||
pub struct Args {
|
|
||||||
/// Access tokens
|
|
||||||
#[clap(short, long)]
|
|
||||||
pub tokens: Vec<String>,
|
|
||||||
|
|
||||||
/// Access tokens stored in a file, one token per line
|
|
||||||
#[clap(long)]
|
|
||||||
pub tokens_file: Option<String>,
|
|
||||||
|
|
||||||
/// Forwarded ports
|
|
||||||
#[clap(short, long)]
|
|
||||||
pub ports: Vec<u16>,
|
|
||||||
|
|
||||||
/// Upstream server
|
|
||||||
#[clap(short, long, default_value = "127.0.0.1")]
|
|
||||||
pub upstream_server: String,
|
|
||||||
|
|
||||||
/// HTTP server listen address
|
|
||||||
#[clap(short, long, default_value = "0.0.0.0:8000")]
|
|
||||||
pub listen_address: String,
|
|
||||||
|
|
||||||
/// Increment ports on client. Useful for debugging and running both client and server
|
|
||||||
/// on the same machine
|
|
||||||
#[clap(short, long, default_value_t = 0)]
|
|
||||||
pub increment_ports: u16,
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
pub mod args;
|
|
||||||
pub mod relay_ws;
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use actix_web::{App, HttpRequest, HttpResponse, HttpServer, middleware, Responder, web};
|
|
||||||
use actix_web::web::Data;
|
|
||||||
use clap::Parser;
|
|
||||||
|
|
||||||
use base::RelayedPort;
|
|
||||||
use tcp_relay_server::args::Args;
|
|
||||||
use tcp_relay_server::relay_ws::relay_ws;
|
|
||||||
|
|
||||||
pub async fn hello_route() -> &'static str {
|
|
||||||
"Hello world!"
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn config_route(req: HttpRequest, data: Data<Arc<Args>>) -> impl Responder {
|
|
||||||
let token = req.headers().get("Authorization")
|
|
||||||
.map(|t| t.to_str().unwrap_or_default())
|
|
||||||
.unwrap_or_default()
|
|
||||||
.strip_prefix("Bearer ")
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
if !data.tokens.iter().any(|t| t.eq(token)) {
|
|
||||||
return HttpResponse::Unauthorized().json("Missing / invalid token");
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpResponse::Ok().json(
|
|
||||||
data.ports.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(id, port)| RelayedPort { id, port: port + data.increment_ports })
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::main]
|
|
||||||
async fn main() -> std::io::Result<()> {
|
|
||||||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
|
||||||
|
|
||||||
let mut args: Args = Args::parse();
|
|
||||||
|
|
||||||
if args.ports.is_empty() {
|
|
||||||
log::error!("No port to forward!");
|
|
||||||
std::process::exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read tokens from file, if any
|
|
||||||
if let Some(file) = &args.tokens_file {
|
|
||||||
std::fs::read_to_string(file)
|
|
||||||
.expect("Failed to read tokens file!")
|
|
||||||
.split('\n')
|
|
||||||
.filter(|l| !l.is_empty())
|
|
||||||
.for_each(|t| args.tokens.push(t.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.tokens.is_empty() {
|
|
||||||
log::error!("No tokens specified!");
|
|
||||||
std::process::exit(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
log::info!("Starting relay on http://{}", args.listen_address);
|
|
||||||
|
|
||||||
let args = Arc::new(args);
|
|
||||||
let args_clone = args.clone();
|
|
||||||
HttpServer::new(move || {
|
|
||||||
App::new()
|
|
||||||
.wrap(middleware::Logger::default())
|
|
||||||
.app_data(Data::new(args_clone.clone()))
|
|
||||||
.route("/", web::get().to(hello_route))
|
|
||||||
.route("/config", web::get().to(config_route))
|
|
||||||
.route("/ws", web::get().to(relay_ws))
|
|
||||||
})
|
|
||||||
.bind(&args.listen_address)?
|
|
||||||
.run()
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user