mirror of
https://github.com/TrustTunnel/TrustTunnel.git
synced 2026-04-24 03:30:41 +00:00
Pull request 80: Add rules config support for endpoint connection filtering
Squashed commit of the following: commit 7b8cf69c390778ea6bd4431fefb047ffa9a3002d Author: Alexey Zhavoronkov <a.zhavoronkov@adguard.com> Date: Mon Sep 1 13:27:26 2025 +0300 Refactoring commit 077096b6c81109479229dc7132e254ec5d10905c Author: Alexey Zhavoronkov <a.zhavoronkov@adguard.com> Date: Mon Sep 1 11:44:53 2025 +0300 Apply filtering rules commit 1151ef7199853e92dbe62370f94116a395168d16 Author: Alexey Zhavoronkov <a.zhavoronkov@adguard.com> Date: Mon Sep 1 11:04:45 2025 +0300 Add missed cargo file commit 509a9fe5eddd73fd49b9160e320fd48d5fba4574 Author: Alexey Zhavoronkov <a.zhavoronkov@adguard.com> Date: Mon Sep 1 11:04:00 2025 +0300 Fix test commit 9d03678c23e3053e6d4685fd060bce51f1335c79 Author: Alexey Zhavoronkov <a.zhavoronkov@adguard.com> Date: Mon Sep 1 11:03:34 2025 +0300 Add rules config commit baa6c918efa3b401d9688df44c85303038256db0 Author: Alexey Zhavoronkov <a.zhavoronkov@adguard.com> Date: Mon Sep 1 08:28:30 2025 +0300 Remove check tls client random from authenticator commit cafc71d4b95b05f4f75c5a335e962e510d1b4edc Author: Alexey Zhavoronkov <a.zhavoronkov@adguard.com> Date: Wed Aug 27 20:24:53 2025 +0300 Refactor commit 1e950d707c63622de1747e1c79befaf700cfb8f7 Author: Alexey Zhavoronkov <a.zhavoronkov@adguard.com> Date: Wed Aug 27 20:14:14 2025 +0300 Rename fields and validate client_random earlier commit efdcd2bb193641a5914c82522cdc2376100cd6a6 Author: Alexey Zhavoronkov <a.zhavoronkov@adguard.com> Date: Tue Aug 26 09:31:23 2025 +0300 Add missing field value commit 23d72ba188959d198bbb5b7cb84fb074eef45342 Author: Alexey Zhavoronkov <a.zhavoronkov@adguard.com> Date: Tue Aug 26 09:01:10 2025 +0300 Add rules config support for endpoint connection filtering Now we have rules.toml configuration that defines filter rules for incoming connections. Each rule can specify cidr and/or client_random_prefix and action (allow/deny). Both cidr and client_random_prefix are optional - if specified, both must match for the rule to apply. If only one is specified, only that condition needs to match. If no rules match, the connection is allowed by default. This behavior can be changed by the empty rule with deny action: [[rule]] action = "deny" Resolves: AG-42959 Signed-off-by: Alexey Zhavoronkov <a.zhavoronkov@adguard.com>
This commit is contained in:
463
Cargo.lock
generated
463
Cargo.lock
generated
@@ -148,9 +148,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
@@ -160,7 +160,7 @@ checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"bitflags",
|
||||
"bitflags 1.3.2",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
@@ -224,12 +224,62 @@ version = "0.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.71.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn 2.0.43",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
|
||||
|
||||
[[package]]
|
||||
name = "boring"
|
||||
version = "4.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f4ea552f8764e7235bb0b6aaec33b891e5b178c77d8c96cfad6c10f057c64a6"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"boring-sys",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"openssl-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "boring-sys"
|
||||
version = "4.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b753c2916f46e25e08abd2cd52b35223a65b7e8a1696ee33b45e20927114696f"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bindgen",
|
||||
"cmake",
|
||||
"fs_extra",
|
||||
"fslock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.13.0"
|
||||
@@ -254,6 +304,15 @@ version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
@@ -272,6 +331,17 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.3.8"
|
||||
@@ -289,9 +359,9 @@ checksum = "9a78fbdd3cc2914ddf37ba444114bc7765bbdcb55ec9cbe6fa054f0137400717"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"bitflags",
|
||||
"bitflags 1.3.2",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
"strsim 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -400,9 +470,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.13.4"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
|
||||
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
@@ -410,27 +480,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.13.4"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
|
||||
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 1.0.109",
|
||||
"strsim 0.11.1",
|
||||
"syn 2.0.43",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.13.4"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
|
||||
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.43",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -439,6 +509,12 @@ version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
||||
|
||||
[[package]]
|
||||
name = "debug_panic"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9377eb110cece2e9431deb8d7d2ec8c116510b896741f9f2bf02b352147aa2a6"
|
||||
|
||||
[[package]]
|
||||
name = "debugid"
|
||||
version = "0.8.0"
|
||||
@@ -529,6 +605,18 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum_dispatch"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.43",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.3.25"
|
||||
@@ -584,6 +672,33 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
|
||||
dependencies = [
|
||||
"foreign-types-macros",
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-macros"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.43",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.0"
|
||||
@@ -593,6 +708,22 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "fslock"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04412b8935272e3a9bae6f48c7bfff74c2911f60525404edfdd28e49884c3bfb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.28"
|
||||
@@ -699,6 +830,12 @@ version = "0.27.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.20"
|
||||
@@ -916,6 +1053,15 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "intrusive-collections"
|
||||
version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86"
|
||||
dependencies = [
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.11"
|
||||
@@ -929,9 +1075,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.7.2"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
|
||||
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
@@ -981,6 +1127,16 @@ version = "0.2.147"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.7"
|
||||
@@ -1011,9 +1167,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.19"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "macros"
|
||||
@@ -1051,6 +1207,15 @@ version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
@@ -1102,6 +1267,28 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom-derive"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ff943d68b88d0b87a6e0d58615e8fa07f9fd5a1319fa0a72efc1f62275c79a7"
|
||||
dependencies = [
|
||||
"nom",
|
||||
"nom-derive-impl",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom-derive-impl"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd0b9a93a84b0d3ec3e70e02d332dc33ac6dfac9cde63e17fcb77172dededa62"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.3"
|
||||
@@ -1148,6 +1335,28 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.43",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.30.4"
|
||||
@@ -1159,9 +1368,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "octets"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a74f2cda724d43a0a63140af89836d4e7db6138ef67c9f96d3a0f0150d05000"
|
||||
checksum = "109983a091271ee8916076731ba5fdc9ee22fea871bc7c6ceab9bfd423eb1d99"
|
||||
|
||||
[[package]]
|
||||
name = "oid-registry"
|
||||
@@ -1178,6 +1387,17 @@ version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.43",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_info"
|
||||
version = "3.7.0"
|
||||
@@ -1227,6 +1447,44 @@ version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.0"
|
||||
@@ -1271,6 +1529,16 @@ version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
@@ -1286,7 +1554,7 @@ version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1de8dacb0873f77e6aefc6d71e044761fcc68060290f5b1089fcdf84626bb69"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 1.3.2",
|
||||
"byteorder",
|
||||
"hex",
|
||||
"lazy_static",
|
||||
@@ -1350,12 +1618,11 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"
|
||||
|
||||
[[package]]
|
||||
name = "qlog"
|
||||
version = "0.9.0"
|
||||
version = "0.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "321df7a3199d152be256a416096136191e88b7716f1e2e4c8c05b9f77ffb648b"
|
||||
checksum = "0f15b83c59e6b945f2261c95a1dd9faf239187f32ff0a96af1d1d28c4557f919"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"smallvec",
|
||||
@@ -1363,21 +1630,25 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quiche"
|
||||
version = "0.17.2"
|
||||
version = "0.24.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d9e4fa8718d45fd25dd89c196e128d6c3527b5b1735db47eb4bdb9ba3e4cc1c"
|
||||
checksum = "88fa45f0d2e3fc7ca8b4306408f8b15df9a3b5ac70197f84dfc6ebf58fb7e26a"
|
||||
dependencies = [
|
||||
"boring",
|
||||
"cmake",
|
||||
"lazy_static",
|
||||
"debug_panic",
|
||||
"either",
|
||||
"enum_dispatch",
|
||||
"foreign-types-shared",
|
||||
"intrusive-collections",
|
||||
"libc",
|
||||
"libm",
|
||||
"log",
|
||||
"octets",
|
||||
"qlog",
|
||||
"ring",
|
||||
"slab",
|
||||
"smallvec",
|
||||
"winapi",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1437,7 +1708,7 @@ version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1532,6 +1803,12 @@ version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
@@ -1556,7 +1833,7 @@ version = "0.36.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14e4d67015953998ad0eb82887a0eb0129e18a7e2f3b7b0f6c422fddcd503d62"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 1.3.2",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
@@ -1570,7 +1847,7 @@ version = "0.37.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 1.3.2",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
@@ -1795,24 +2072,25 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "1.14.0"
|
||||
version = "3.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff"
|
||||
checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_with_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "1.5.2"
|
||||
version = "3.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
|
||||
checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.43",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1830,6 +2108,12 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.1"
|
||||
@@ -1839,6 +2123,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.8"
|
||||
@@ -1879,6 +2169,12 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
@@ -2009,6 +2305,20 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tls-parser"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22c36249c6082584b1f224e70f6bdadf5102197be6cfa92b353efe605d9ac741"
|
||||
dependencies = [
|
||||
"nom",
|
||||
"nom-derive",
|
||||
"num_enum",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"rusticata-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.28.2"
|
||||
@@ -2349,6 +2659,8 @@ dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"dialoguer",
|
||||
"hex",
|
||||
"ipnet",
|
||||
"lazy_static",
|
||||
"once_cell",
|
||||
"rcgen",
|
||||
@@ -2365,15 +2677,18 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.21.2",
|
||||
"boring",
|
||||
"bytes",
|
||||
"cc",
|
||||
"chrono",
|
||||
"dynfmt",
|
||||
"futures",
|
||||
"h2",
|
||||
"hex",
|
||||
"http",
|
||||
"httparse",
|
||||
"hyper",
|
||||
"ipnet",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
@@ -2386,6 +2701,7 @@ dependencies = [
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"tls-parser",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"toml_edit",
|
||||
@@ -2559,6 +2875,15 @@ dependencies = [
|
||||
"windows-targets 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
@@ -2589,6 +2914,22 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
@@ -2601,6 +2942,12 @@ version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
@@ -2613,6 +2960,12 @@ version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
@@ -2625,6 +2978,18 @@ version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
@@ -2637,6 +3002,12 @@ version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
@@ -2649,6 +3020,12 @@ version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
@@ -2661,6 +3038,12 @@ version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
@@ -2673,6 +3056,12 @@ version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.4.7"
|
||||
|
||||
@@ -15,20 +15,23 @@ cc = "1.0.79"
|
||||
[dependencies]
|
||||
async-trait = "0.1.68"
|
||||
base64 = "0.21.2"
|
||||
tls-parser = "0.12.2"
|
||||
bytes = "1.4.0"
|
||||
chrono = { version = "0.4.26", default-features = false, features = ["clock"] }
|
||||
dynfmt = { version = "0.1.5", features = ["curly"], default-features = false }
|
||||
futures = "0.3.28"
|
||||
h2 = "0.3.20"
|
||||
hex = "0.4.3"
|
||||
http = "0.2.9"
|
||||
httparse = "1.8.0"
|
||||
ipnet = "2.9"
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2.147"
|
||||
log = "0.4.19"
|
||||
macros = { version = "0.1.0", path = "../macros", optional = true }
|
||||
once_cell = "1.18.0"
|
||||
prometheus = { version = "0.13.3", features = ["process"] }
|
||||
quiche = { version = "0.17.2", features = ["qlog"] }
|
||||
quiche = { version = "0.24.5", features = ["qlog", "boringssl-boring-crate"] }
|
||||
ring = "0.16.20"
|
||||
rustls = { version = "0.21.2", features = ["logging"] }
|
||||
rustls-pemfile = "1.0.2"
|
||||
@@ -37,6 +40,7 @@ smallvec = "1.10.0"
|
||||
tokio = { version = "1.28.2", features = ["net", "rt", "sync", "time", "macros", "rt-multi-thread"] }
|
||||
tokio-rustls = "0.24.1"
|
||||
toml_edit = "0.19.10"
|
||||
boring = "4"
|
||||
|
||||
[dev-dependencies]
|
||||
hyper = { version = "0.14.26", features = ["http1", "http2", "client", "server", "runtime", "stream"] }
|
||||
|
||||
@@ -5,7 +5,8 @@ use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::net::{TcpListener, UdpSocket};
|
||||
use crate::direct_forwarder::DirectForwarder;
|
||||
use crate::{authentication, http_ping_handler, http_speedtest_handler, log_id, log_utils, metrics, net_utils, reverse_proxy, settings, tls_demultiplexer, tunnel};
|
||||
use crate::{authentication, http_ping_handler, http_speedtest_handler, log_id, log_utils, metrics, net_utils, reverse_proxy, rules, settings, tls_demultiplexer, tunnel};
|
||||
use crate::net_utils::PeerAddr;
|
||||
use crate::tls_demultiplexer::TlsDemux;
|
||||
use crate::forwarder::Forwarder;
|
||||
use crate::http1_codec::Http1Codec;
|
||||
@@ -158,7 +159,7 @@ impl Core {
|
||||
let client_id = log_utils::IdChain::from(log_utils::IdItem::new(
|
||||
log_utils::CLIENT_ID_FMT, self.context.next_client_id.fetch_add(1, Ordering::Relaxed),
|
||||
));
|
||||
let stream = match tcp_listener.accept().await
|
||||
let (stream, client_addr) = match tcp_listener.accept().await
|
||||
.and_then(|(s, a)| {
|
||||
s.set_nodelay(true)?;
|
||||
Ok((s, a))
|
||||
@@ -166,7 +167,7 @@ impl Core {
|
||||
{
|
||||
Ok((stream, addr)) => if has_tcp_based_codec {
|
||||
log_id!(debug, client_id, "New TCP client: {}", addr);
|
||||
stream
|
||||
(stream, addr)
|
||||
} else {
|
||||
continue; // accept just for pings
|
||||
}
|
||||
@@ -185,9 +186,9 @@ impl Core {
|
||||
.await
|
||||
.unwrap_or_else(|_| Err(io::Error::from(ErrorKind::TimedOut)))
|
||||
{
|
||||
Ok(stream) =>
|
||||
Ok(acceptor) =>
|
||||
if let Err((client_id, message)) = Core::on_new_tls_connection(
|
||||
context.clone(), stream, client_id,
|
||||
context.clone(), acceptor, client_addr.ip(), client_id,
|
||||
).await {
|
||||
log_id!(debug, client_id, "{}", message);
|
||||
},
|
||||
@@ -240,12 +241,19 @@ impl Core {
|
||||
async fn on_new_tls_connection(
|
||||
context: Arc<Context>,
|
||||
acceptor: TlsAcceptor,
|
||||
client_ip: std::net::IpAddr,
|
||||
client_id: log_utils::IdChain<u64>,
|
||||
) -> Result<(), (log_utils::IdChain<u64>, String)> {
|
||||
let sni = match acceptor.sni() {
|
||||
Some(s) => s,
|
||||
None => return Err((client_id, "Drop TLS connection due to absence of SNI".to_string())),
|
||||
};
|
||||
// Apply connection filtering rules
|
||||
if let Err(deny_reason) = Self::evaluate_connection_rules(
|
||||
&context, Some(client_ip), acceptor.client_random().as_deref(), &client_id
|
||||
) {
|
||||
return Err((client_id, deny_reason));
|
||||
}
|
||||
|
||||
let core_settings = context.settings.clone();
|
||||
let tls_connection_meta =
|
||||
@@ -336,6 +344,17 @@ impl Core {
|
||||
socket: QuicSocket,
|
||||
client_id: log_utils::IdChain<u64>,
|
||||
) {
|
||||
// Apply connection filtering rules
|
||||
let client_ip = socket.peer_addr().ok().map(|addr| addr.ip());
|
||||
let client_random = Some(socket.client_random());
|
||||
|
||||
if let Err(deny_reason) = Self::evaluate_connection_rules(
|
||||
&context, client_ip, client_random.as_deref(), &client_id
|
||||
) {
|
||||
log_id!(debug, client_id, "{}", deny_reason);
|
||||
return; // Drop the connection
|
||||
}
|
||||
|
||||
let tls_connection_meta = socket.tls_connection_meta();
|
||||
log_id!(debug, client_id, "Connection meta: {:?}", tls_connection_meta);
|
||||
|
||||
@@ -383,6 +402,32 @@ impl Core {
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to evaluate connection filtering rules
|
||||
fn evaluate_connection_rules(
|
||||
context: &Arc<Context>,
|
||||
client_ip: Option<std::net::IpAddr>,
|
||||
client_random: Option<&[u8]>,
|
||||
log_id: &log_utils::IdChain<u64>,
|
||||
) -> Result<(), String> {
|
||||
if let Some(rules_engine) = &context.settings.rules_engine {
|
||||
if let Some(ip) = client_ip {
|
||||
let rule_result = rules_engine.evaluate(&ip, client_random);
|
||||
match rule_result {
|
||||
rules::RuleEvaluation::Deny => {
|
||||
log_id!(debug, log_id, "Connection denied by filtering rules for IP: {}", ip);
|
||||
return Err("Connection denied by filtering rules".to_string());
|
||||
}
|
||||
rules::RuleEvaluation::Allow => {
|
||||
log_id!(debug, log_id, "Connection allowed by filtering rules");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log_id!(warn, log_id, "Could not extract client IP for rules evaluation");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn on_tunnel_request(
|
||||
context: Arc<Context>,
|
||||
protocol: tls_demultiplexer::Protocol,
|
||||
|
||||
@@ -11,6 +11,7 @@ pub mod shutdown;
|
||||
pub mod net_utils;
|
||||
pub mod utils;
|
||||
pub mod client_config;
|
||||
pub mod rules;
|
||||
|
||||
mod direct_forwarder;
|
||||
mod downstream;
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::time::Duration;
|
||||
use boring::ssl::{SelectCertError, SslContextBuilder, SslMethod, SslRef};
|
||||
use bytes::{Buf, Bytes, BytesMut};
|
||||
use http::header::InvalidHeaderName;
|
||||
use lazy_static::lazy_static;
|
||||
@@ -23,6 +24,7 @@ use crate::utils::Either;
|
||||
const TOKEN_PREFIX_SIZE: usize = 16;
|
||||
const MUX_ID_FMT: &str = "QMUX={}";
|
||||
const SOCKET_ID_FMT: &str = "QSOCK={}";
|
||||
|
||||
const QUIC_CONNECTION_CLOSE_CODE: u64 = 0x42;
|
||||
|
||||
type QuicConnection = quiche::Connection;
|
||||
@@ -56,6 +58,8 @@ pub(crate) struct QuicSocket {
|
||||
waiting_writable_streams: std::sync::Mutex<HashSet<u64>>,
|
||||
id: log_utils::IdChain<u64>,
|
||||
tls_connection_meta: tls_demultiplexer::ConnectionMeta,
|
||||
/// TLS client_random extracted from QUIC handshake
|
||||
client_random: Vec<u8>,
|
||||
}
|
||||
|
||||
pub(crate) enum QuicSocketEvent {
|
||||
@@ -453,6 +457,15 @@ impl QuicMultiplexer {
|
||||
Arc::new(std::sync::Mutex::new(h3_conn))
|
||||
};
|
||||
|
||||
// Extract client_random from QUIC after handshake is complete
|
||||
let extracted_client_random = {
|
||||
let mut quic = quic_conn.lock().unwrap();
|
||||
let ssl: &mut SslRef = quic.as_mut();
|
||||
let mut client_random = [0u8; 32];
|
||||
ssl.client_random(&mut client_random);
|
||||
client_random.to_vec()
|
||||
};
|
||||
|
||||
let (tx, rx) = mpsc::channel(1);
|
||||
self.connections.insert(conn_id.clone().into_owned(), Connection::Established(EstablishedConnection {
|
||||
socket_tx: tx,
|
||||
@@ -471,6 +484,7 @@ impl QuicMultiplexer {
|
||||
SOCKET_ID_FMT, self.next_socket_id.fetch_add(1, Ordering::Relaxed),
|
||||
)),
|
||||
tls_connection_meta: conn.tls_connection_meta,
|
||||
client_random: extracted_client_random,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -634,6 +648,10 @@ impl QuicSocket {
|
||||
&self.tls_connection_meta
|
||||
}
|
||||
|
||||
pub fn client_random(&self) -> Vec<u8> {
|
||||
self.client_random.clone()
|
||||
}
|
||||
|
||||
pub fn send_response(&self, stream_id: u64, response: ResponseHeaders, fin: bool) -> io::Result<()> {
|
||||
let response: Vec<_> =
|
||||
std::iter::once(h3::HeaderRef::new(b":status", response.status.as_str().as_bytes()))
|
||||
@@ -814,9 +832,6 @@ impl QuicSocket {
|
||||
log_id!(trace, self.id, "Stream reset by client: id={}, err={}", stream_id, err);
|
||||
Ok(Some(QuicSocketEvent::Close(stream_id)))
|
||||
}
|
||||
Ok((_, h3::Event::Datagram)) => Err(io::Error::new(
|
||||
ErrorKind::Other, "Received unexpected datagram frame",
|
||||
)),
|
||||
Ok((_, h3::Event::PriorityUpdate)) => Ok(None),
|
||||
Ok((_, h3::Event::GoAway)) => Err(io::Error::new(
|
||||
ErrorKind::UnexpectedEof, "Received GOAWAY",
|
||||
@@ -937,9 +952,11 @@ fn quic_recv(
|
||||
fn make_quic_conn_config(
|
||||
core_settings: &Settings, cert_chain_file: &str, priv_key_file: &str,
|
||||
) -> io::Result<quiche::Config> {
|
||||
|
||||
let ctx = SslContextBuilder::new(SslMethod::tls())?;
|
||||
let quic_settings = core_settings.listen_protocols.quic.as_ref().unwrap();
|
||||
|
||||
let mut cfg = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();
|
||||
let mut cfg = quiche::Config::with_boring_ssl_ctx_builder(quiche::PROTOCOL_VERSION, ctx).unwrap();
|
||||
cfg.load_cert_chain_from_pem_file(cert_chain_file).unwrap();
|
||||
cfg.load_priv_key_from_pem_file(priv_key_file).unwrap();
|
||||
cfg.set_application_protos(h3::APPLICATION_PROTOCOL).unwrap();
|
||||
|
||||
214
lib/src/rules.rs
Normal file
214
lib/src/rules.rs
Normal file
@@ -0,0 +1,214 @@
|
||||
use std::net::IpAddr;
|
||||
use std::path::Path;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ipnet::IpNet;
|
||||
|
||||
/// Action to take when a rule matches
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum RuleAction {
|
||||
Allow,
|
||||
Deny,
|
||||
}
|
||||
|
||||
/// Individual filter rule
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Rule {
|
||||
/// CIDR range to match against client IP
|
||||
#[serde(default)]
|
||||
pub cidr: Option<String>,
|
||||
|
||||
/// Client random prefix to match (hex-encoded)
|
||||
#[serde(default)]
|
||||
pub client_random_prefix: Option<String>,
|
||||
|
||||
/// Action to take when this rule matches
|
||||
pub action: RuleAction,
|
||||
}
|
||||
|
||||
/// Rules configuration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RulesConfig {
|
||||
/// List of filter rules
|
||||
#[serde(default)]
|
||||
pub rule: Vec<Rule>,
|
||||
}
|
||||
|
||||
/// Rule evaluation engine
|
||||
pub struct RulesEngine {
|
||||
rules: RulesConfig,
|
||||
}
|
||||
|
||||
/// Result of rule evaluation
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum RuleEvaluation {
|
||||
Allow,
|
||||
Deny,
|
||||
}
|
||||
|
||||
impl Rule {
|
||||
/// Check if this rule matches the given connection parameters
|
||||
pub fn matches(&self, client_ip: &IpAddr, client_random: Option<&[u8]>) -> bool {
|
||||
let mut matches = true;
|
||||
|
||||
// Check CIDR match if specified
|
||||
if let Some(cidr_str) = &self.cidr {
|
||||
if let Ok(cidr) = cidr_str.parse::<IpNet>() {
|
||||
matches &= cidr.contains(client_ip);
|
||||
} else {
|
||||
// Invalid CIDR, rule doesn't match
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check client_random prefix if specified
|
||||
if let Some(prefix_str) = &self.client_random_prefix {
|
||||
if let Some(client_random_data) = client_random {
|
||||
if let Ok(prefix_bytes) = hex::decode(prefix_str) {
|
||||
matches &= client_random_data.starts_with(&prefix_bytes);
|
||||
} else {
|
||||
// Invalid hex prefix, rule doesn't match
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// No client_random provided but rule requires it, doesn't match
|
||||
matches = false;
|
||||
}
|
||||
}
|
||||
|
||||
matches
|
||||
}
|
||||
}
|
||||
|
||||
impl RulesEngine {
|
||||
/// Create a new rules engine from rules config
|
||||
pub fn from_config(rules: RulesConfig) -> Self {
|
||||
Self { rules }
|
||||
}
|
||||
|
||||
/// Create a default rules engine that allows all connections
|
||||
pub fn default_allow() -> Self {
|
||||
Self {
|
||||
rules: RulesConfig { rule: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate connection against all rules
|
||||
/// Returns the action from the first matching rule, or Allow if no rules match
|
||||
pub fn evaluate(&self, client_ip: &IpAddr, client_random: Option<&[u8]>) -> RuleEvaluation {
|
||||
for rule in &self.rules.rule {
|
||||
if rule.matches(client_ip, client_random) {
|
||||
return match rule.action {
|
||||
RuleAction::Allow => RuleEvaluation::Allow,
|
||||
RuleAction::Deny => RuleEvaluation::Deny,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Default action if no rules match: allow
|
||||
RuleEvaluation::Allow
|
||||
}
|
||||
|
||||
/// Get a reference to the rules configuration
|
||||
pub fn config(&self) -> &RulesConfig {
|
||||
&self.rules
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RulesConfig {
|
||||
fn default() -> Self {
|
||||
Self { rule: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[test]
|
||||
fn test_cidr_rule_matching() {
|
||||
let rule = Rule {
|
||||
cidr: Some("192.168.1.0/24".to_string()),
|
||||
client_random_prefix: None,
|
||||
action: RuleAction::Allow,
|
||||
};
|
||||
|
||||
let ip_match = IpAddr::from_str("192.168.1.100").unwrap();
|
||||
let ip_no_match = IpAddr::from_str("10.0.0.1").unwrap();
|
||||
|
||||
assert!(rule.matches(&ip_match, None));
|
||||
assert!(!rule.matches(&ip_no_match, None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_client_random_prefix_matching() {
|
||||
let rule = Rule {
|
||||
cidr: None,
|
||||
client_random_prefix: Some("aabbcc".to_string()),
|
||||
action: RuleAction::Deny,
|
||||
};
|
||||
|
||||
let client_random_match = hex::decode("aabbccddee").unwrap();
|
||||
let client_random_no_match = hex::decode("112233").unwrap();
|
||||
|
||||
let ip = IpAddr::from_str("127.0.0.1").unwrap();
|
||||
|
||||
assert!(rule.matches(&ip, Some(&client_random_match)));
|
||||
assert!(!rule.matches(&ip, Some(&client_random_no_match)));
|
||||
assert!(!rule.matches(&ip, None)); // No client random provided
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_combined_rule_matching() {
|
||||
let rule = Rule {
|
||||
cidr: Some("10.0.0.0/8".to_string()),
|
||||
client_random_prefix: Some("ff".to_string()),
|
||||
action: RuleAction::Allow,
|
||||
};
|
||||
|
||||
let ip_match = IpAddr::from_str("10.1.2.3").unwrap();
|
||||
let ip_no_match = IpAddr::from_str("192.168.1.1").unwrap();
|
||||
let client_random_match = hex::decode("ff00112233").unwrap();
|
||||
let client_random_no_match = hex::decode("0011223344").unwrap();
|
||||
|
||||
// Both must match
|
||||
assert!(rule.matches(&ip_match, Some(&client_random_match)));
|
||||
assert!(!rule.matches(&ip_match, Some(&client_random_no_match)));
|
||||
assert!(!rule.matches(&ip_no_match, Some(&client_random_match)));
|
||||
assert!(!rule.matches(&ip_no_match, Some(&client_random_no_match)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rules_engine_evaluation() {
|
||||
let rules = RulesConfig {
|
||||
rule: vec![
|
||||
Rule {
|
||||
cidr: Some("192.168.1.0/24".to_string()),
|
||||
client_random_prefix: None,
|
||||
action: RuleAction::Deny,
|
||||
},
|
||||
Rule {
|
||||
cidr: Some("10.0.0.0/8".to_string()),
|
||||
client_random_prefix: None,
|
||||
action: RuleAction::Allow,
|
||||
},
|
||||
Rule {
|
||||
cidr: None,
|
||||
client_random_prefix: None,
|
||||
action: RuleAction::Deny, // Catch-all deny
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let engine = RulesEngine::from_config(rules);
|
||||
|
||||
let ip_deny = IpAddr::from_str("192.168.1.100").unwrap();
|
||||
let ip_allow = IpAddr::from_str("10.1.2.3").unwrap();
|
||||
let ip_default = IpAddr::from_str("172.16.1.1").unwrap();
|
||||
|
||||
assert_eq!(engine.evaluate(&ip_deny, None), RuleEvaluation::Deny);
|
||||
assert_eq!(engine.evaluate(&ip_allow, None), RuleEvaluation::Allow);
|
||||
assert_eq!(engine.evaluate(&ip_default, None), RuleEvaluation::Deny); // Default deny
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ use macros::{Getter, RuntimeDoc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use toml_edit::{Document, Item};
|
||||
use authentication::registry_based::Client;
|
||||
use crate::{authentication, utils};
|
||||
use crate::{authentication, utils, rules};
|
||||
|
||||
pub type Socks5BuilderResult<T> = Result<T, Socks5Error>;
|
||||
|
||||
@@ -28,6 +28,8 @@ pub enum ValidationError {
|
||||
ReverseProxy(String),
|
||||
/// Invalid [`Settings.listen_protocols`]
|
||||
ListenProtocols(String),
|
||||
/// Invalid rules file
|
||||
RulesFile(String),
|
||||
}
|
||||
|
||||
impl Debug for ValidationError {
|
||||
@@ -39,6 +41,7 @@ impl Debug for ValidationError {
|
||||
Self::SpeedTlsHostInfo(x) => write!(f, "Invalid speedtest TLS hosts: {}", x),
|
||||
Self::ReverseProxy(x) => write!(f, "Invalid reverse proxy settings: {}", x),
|
||||
Self::ListenProtocols(x) => write!(f, "Invalid listen protocols settings: {}", x),
|
||||
Self::RulesFile(x) => write!(f, "Invalid rules file: {}", x),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,6 +145,13 @@ pub struct Settings {
|
||||
pub(crate) icmp: Option<IcmpSettings>,
|
||||
/// The metrics gathering request handler settings
|
||||
pub(crate) metrics: Option<MetricsSettings>,
|
||||
/// Path to the rules file for connection filtering.
|
||||
/// If not specified or file doesn't exist, all connections are allowed by default.
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing)]
|
||||
#[serde(rename(deserialize = "rules_file"))]
|
||||
#[serde(deserialize_with = "deserialize_rules")]
|
||||
pub(crate) rules_engine: Option<rules::RulesEngine>,
|
||||
|
||||
/// Whether an instance was built through a [`SettingsBuilder`].
|
||||
/// This flag is a workaround for absence of the ability to validate
|
||||
@@ -496,6 +506,7 @@ impl Default for Settings {
|
||||
reverse_proxy: None,
|
||||
icmp: None,
|
||||
metrics: Default::default(),
|
||||
rules_engine: Some(rules::RulesEngine::default_allow()),
|
||||
built: false,
|
||||
}
|
||||
}
|
||||
@@ -732,6 +743,7 @@ impl SettingsBuilder {
|
||||
reverse_proxy: None,
|
||||
icmp: None,
|
||||
metrics: Default::default(),
|
||||
rules_engine: Some(rules::RulesEngine::default_allow()),
|
||||
built: true,
|
||||
},
|
||||
}
|
||||
@@ -839,6 +851,12 @@ impl SettingsBuilder {
|
||||
self.settings.metrics = Some(x);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the rules engine for connection filtering
|
||||
pub fn rules_engine(mut self, x: rules::RulesEngine) -> Self {
|
||||
self.settings.rules_engine = Some(x);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl TlsSettingsBuilder {
|
||||
@@ -1311,6 +1329,75 @@ fn deserialize_clients<'de, D>(deserializer: D) -> Result<Vec<Client>, D::Error>
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn deserialize_rules<'de, D>(deserializer: D) -> Result<Option<rules::RulesEngine>, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>,
|
||||
{
|
||||
let path = match deserialize_file_path(deserializer) {
|
||||
Ok(path) => path,
|
||||
Err(_) => {
|
||||
// No rules file specified, default to allow all
|
||||
return Ok(Some(rules::RulesEngine::default_allow()));
|
||||
}
|
||||
};
|
||||
|
||||
let content = match std::fs::read_to_string(&path) {
|
||||
Ok(content) => content,
|
||||
Err(e) => {
|
||||
// Log warning but don't fail - default to allow all
|
||||
eprintln!("Warning: Could not read rules file '{}': {}. Defaulting to allow all connections.", path, e);
|
||||
return Ok(Some(rules::RulesEngine::default_allow()));
|
||||
}
|
||||
};
|
||||
|
||||
let rules_doc: Document = match content.parse() {
|
||||
Ok(doc) => doc,
|
||||
Err(e) => {
|
||||
eprintln!("Warning: Could not parse rules file '{}': {}. Defaulting to allow all connections.", path, e);
|
||||
return Ok(Some(rules::RulesEngine::default_allow()));
|
||||
}
|
||||
};
|
||||
|
||||
let rules_config = match rules_doc.get("rule").and_then(Item::as_array_of_tables) {
|
||||
Some(rules_array) => {
|
||||
let rules: Vec<rules::Rule> = rules_array
|
||||
.iter()
|
||||
.filter_map(|rule_table| {
|
||||
let cidr = rule_table.get("cidr")
|
||||
.and_then(Item::as_str)
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let client_random_prefix = rule_table.get("client_random_prefix")
|
||||
.and_then(Item::as_str)
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let action = rule_table.get("action")
|
||||
.and_then(Item::as_str)
|
||||
.and_then(|s| match s {
|
||||
"allow" => Some(rules::RuleAction::Allow),
|
||||
"deny" => Some(rules::RuleAction::Deny),
|
||||
_ => None,
|
||||
})?;
|
||||
|
||||
Some(rules::Rule {
|
||||
cidr,
|
||||
client_random_prefix,
|
||||
action,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
rules::RulesConfig { rule: rules }
|
||||
}
|
||||
None => {
|
||||
// No rules array found, create empty config
|
||||
rules::RulesConfig { rule: vec![] }
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(rules::RulesEngine::from_config(rules_config)))
|
||||
}
|
||||
|
||||
fn demangle_toml_string(x: String) -> String {
|
||||
x.replace('"', "")
|
||||
.trim()
|
||||
|
||||
@@ -5,13 +5,14 @@ use rustls::{Certificate, PrivateKey, ServerConfig};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_rustls::{LazyConfigAcceptor, StartHandshake};
|
||||
use tokio_rustls::server::TlsStream;
|
||||
use tls_parser::{parse_tls_plaintext, TlsMessage};
|
||||
use crate::{log_utils, tls_demultiplexer};
|
||||
|
||||
|
||||
pub(crate) struct TlsListener {}
|
||||
|
||||
pub(crate) struct TlsAcceptor {
|
||||
inner: StartHandshake<TcpStream>,
|
||||
client_random: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl TlsListener {
|
||||
@@ -20,12 +21,47 @@ impl TlsListener {
|
||||
}
|
||||
|
||||
pub async fn listen(&self, stream: TcpStream) -> io::Result<TlsAcceptor> {
|
||||
// Peek at the first 1024 bytes to parse Client Hello
|
||||
let mut buffer = vec![0u8; 1024];
|
||||
let bytes_peeked = stream.peek(&mut buffer).await?;
|
||||
|
||||
// Extract client_random from the peeked data
|
||||
let client_random = Self::extract_client_random(&buffer[..bytes_peeked]);
|
||||
|
||||
// Now let rustls handle the stream normally
|
||||
LazyConfigAcceptor::new(rustls::server::Acceptor::default(), stream)
|
||||
.await
|
||||
.map(|hs| TlsAcceptor {
|
||||
inner: hs,
|
||||
client_random,
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_client_random(data: &[u8]) -> Option<Vec<u8>> {
|
||||
// Parse TLS plaintext record
|
||||
match parse_tls_plaintext(data) {
|
||||
Ok((_, plaintext)) => {
|
||||
// Look for handshake messages
|
||||
for message in &plaintext.msg {
|
||||
if let TlsMessage::Handshake(handshake) = message {
|
||||
// Check if this is a ClientHello handshake
|
||||
if matches!(handshake, tls_parser::TlsMessageHandshake::ClientHello(..)) {
|
||||
// Extract the ClientHello data
|
||||
if let tls_parser::TlsMessageHandshake::ClientHello(client_hello) = handshake {
|
||||
if client_hello.random.len() >= 32 {
|
||||
let client_random = client_hello.random[..32].to_vec();
|
||||
|
||||
return Some(client_random);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => log::debug!("Failed to parse TLS plaintext: {:?}", e),
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl TlsAcceptor {
|
||||
@@ -40,6 +76,10 @@ impl TlsAcceptor {
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn client_random(&self) -> Option<Vec<u8>> {
|
||||
self.client_random.clone()
|
||||
}
|
||||
|
||||
pub async fn accept(
|
||||
self,
|
||||
protocol: tls_demultiplexer::Protocol,
|
||||
@@ -62,4 +102,4 @@ impl TlsAcceptor {
|
||||
|
||||
self.inner.into_stream(tls_config).await
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ path = "setup_wizard/main.rs"
|
||||
chrono = { version = "0.4.26", default-features = false, features = ["clock"] }
|
||||
clap = "4.3.8"
|
||||
dialoguer = "0.10.4"
|
||||
hex = "0.4"
|
||||
ipnet = "2.9"
|
||||
lazy_static = "1.4.0"
|
||||
once_cell = "1.18.0"
|
||||
rcgen = "0.10.0"
|
||||
|
||||
@@ -4,8 +4,8 @@ use vpn_libs_endpoint::settings::{ForwardProtocolSettings, Http1Settings, Http2S
|
||||
use vpn_libs_endpoint::utils::{IterJoin, ToTomlComment};
|
||||
use crate::template_settings;
|
||||
|
||||
pub fn compose_document(settings: &Settings, credentials_path: &str) -> String {
|
||||
once(compose_main_table(settings, credentials_path))
|
||||
pub fn compose_document(settings: &Settings, credentials_path: &str, rules_path: &str) -> String {
|
||||
once(compose_main_table(settings, credentials_path, rules_path))
|
||||
.chain(once(compose_forward_protocol_table(settings.get_forward_protocol())))
|
||||
.chain(once(compose_listener_protocol_table(settings.get_listen_protocols())))
|
||||
.chain(once(compose_icmp_table(settings.get_icmp().as_ref())))
|
||||
@@ -13,11 +13,12 @@ pub fn compose_document(settings: &Settings, credentials_path: &str) -> String {
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
fn compose_main_table(settings: &Settings, credentials_path: &str) -> String {
|
||||
fn compose_main_table(settings: &Settings, credentials_path: &str, rules_path: &str) -> String {
|
||||
let mut doc: Document = template_settings::MAIN_TABLE.parse().unwrap();
|
||||
|
||||
doc["listen_address"] = value(settings.get_listen_address().to_string());
|
||||
doc["credentials_file"] = value(credentials_path);
|
||||
doc["rules_file"] = value(rules_path);
|
||||
doc["ipv6_available"] = value(*settings.get_ipv6_available());
|
||||
doc["allow_private_network_connections"] = value(*settings.get_allow_private_network_connections());
|
||||
doc["tls_handshake_timeout_secs"] = value(settings.get_tls_handshake_timeout().as_secs() as i64);
|
||||
|
||||
@@ -5,10 +5,12 @@ use crate::Mode;
|
||||
use crate::user_interaction::{ask_for_agreement, ask_for_input, ask_for_password, checked_overwrite, select_variant};
|
||||
|
||||
pub const DEFAULT_CREDENTIALS_PATH: &str = "credentials.toml";
|
||||
pub const DEFAULT_RULES_PATH: &str = "rules.toml";
|
||||
|
||||
pub struct Built {
|
||||
pub settings: Settings,
|
||||
pub credentials_path: String,
|
||||
pub rules_path: String,
|
||||
}
|
||||
|
||||
pub fn build() -> Built {
|
||||
@@ -30,6 +32,7 @@ pub fn build() -> Built {
|
||||
})
|
||||
.build().expect("Couldn't build the library settings"),
|
||||
credentials_path: build_authenticator(),
|
||||
rules_path: build_rules(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +60,31 @@ fn build_authenticator() -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_rules() -> String {
|
||||
if crate::get_mode() != Mode::NonInteractive
|
||||
&& check_file_exists(".", DEFAULT_RULES_PATH)
|
||||
&& ask_for_agreement(&format!("Reuse the existing rules file: {DEFAULT_RULES_PATH}?"))
|
||||
{
|
||||
DEFAULT_RULES_PATH.into()
|
||||
} else {
|
||||
let path = ask_for_input::<String>(
|
||||
"Path to the rules file",
|
||||
Some(DEFAULT_RULES_PATH.into()),
|
||||
);
|
||||
|
||||
if checked_overwrite(&path, "Overwrite the existing rules file?") {
|
||||
println!("Let's create connection filtering rules");
|
||||
let rules_config = crate::rules_settings::build();
|
||||
let rules_content = generate_rules_toml_content(&rules_config);
|
||||
fs::write(&path, rules_content)
|
||||
.expect("Couldn't write the rules into a file");
|
||||
println!("The rules configuration is written to file: {}", path);
|
||||
}
|
||||
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
fn build_user_list() -> Vec<(String, String)> {
|
||||
if let Some(x) = crate::get_predefined_params().credentials.clone() {
|
||||
return vec![x];
|
||||
@@ -96,6 +124,33 @@ fn compose_credentials_content(clients: impl Iterator<Item=(String, String)>) ->
|
||||
doc.to_string()
|
||||
}
|
||||
|
||||
fn generate_rules_toml_content(rules_config: &vpn_libs_endpoint::rules::RulesConfig) -> String {
|
||||
let mut content = String::new();
|
||||
|
||||
// Add header comments explaining the format
|
||||
content.push_str("# Rules configuration for VPN endpoint connection filtering\n");
|
||||
content.push_str("# \n");
|
||||
content.push_str("# This file defines filter rules for incoming connections.\n");
|
||||
content.push_str("# Rules are evaluated in order, and the first matching rule's action is applied.\n");
|
||||
content.push_str("# If no rules match, the connection is allowed by default.\n");
|
||||
content.push_str("#\n");
|
||||
content.push_str("# Each rule can specify:\n");
|
||||
content.push_str("# - cidr: IP address range in CIDR notation\n");
|
||||
content.push_str("# - client_random_prefix: Hex-encoded prefix of TLS client random data\n");
|
||||
content.push_str("# - action: \"allow\" or \"deny\"\n");
|
||||
content.push_str("#\n");
|
||||
content.push_str("# Both cidr and client_random_prefix are optional - if specified, both must match for the rule to apply.\n");
|
||||
content.push_str("# If only one is specified, only that condition needs to match.\n\n");
|
||||
|
||||
// Serialize the actual rules (usually empty)
|
||||
if !rules_config.rule.is_empty() {
|
||||
content.push_str(&toml::ser::to_string(rules_config).unwrap());
|
||||
content.push_str("\n");
|
||||
}
|
||||
|
||||
content
|
||||
}
|
||||
|
||||
fn check_file_exists(path: &str, name: &str) -> bool {
|
||||
match fs::read_dir(path) {
|
||||
Ok(x) => x.filter_map(Result::ok)
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::user_interaction::{ask_for_agreement, ask_for_input, checked_overwrit
|
||||
|
||||
mod composer;
|
||||
mod library_settings;
|
||||
mod rules_settings;
|
||||
mod template_settings;
|
||||
mod tls_hosts_settings;
|
||||
mod user_interaction;
|
||||
@@ -134,7 +135,7 @@ Required in non-interactive mode."#),
|
||||
Some("vpn.toml".into()),
|
||||
));
|
||||
if checked_overwrite(&path, "Overwrite the existing library settings file?") {
|
||||
let doc = composer::compose_document(&built.settings, &built.credentials_path);
|
||||
let doc = composer::compose_document(&built.settings, &built.credentials_path, &built.rules_path);
|
||||
fs::write(&path, doc)
|
||||
.expect("Couldn't write the library settings to a file");
|
||||
}
|
||||
|
||||
152
tools/setup_wizard/rules_settings.rs
Normal file
152
tools/setup_wizard/rules_settings.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
use vpn_libs_endpoint::rules::{Rule, RuleAction, RulesConfig};
|
||||
use crate::user_interaction::{ask_for_agreement, ask_for_input};
|
||||
use crate::get_mode;
|
||||
|
||||
pub fn build() -> RulesConfig {
|
||||
match get_mode() {
|
||||
crate::Mode::NonInteractive => build_non_interactive(),
|
||||
crate::Mode::Interactive => build_interactive(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_non_interactive() -> RulesConfig {
|
||||
// In non-interactive mode, generate empty rules
|
||||
// The actual examples will be in the serialized TOML comments
|
||||
RulesConfig { rule: vec![] }
|
||||
}
|
||||
|
||||
fn build_interactive() -> RulesConfig {
|
||||
println!("Setting up connection filtering rules...");
|
||||
|
||||
let mut rules = Vec::new();
|
||||
|
||||
// Ask if user wants to configure rules
|
||||
if !ask_for_agreement("Do you want to configure connection filtering rules? (if not, all connections will be allowed)") {
|
||||
println!("Skipping rules configuration - all connections will be allowed.");
|
||||
return RulesConfig { rule: vec![] };
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("You can configure rules to allow/deny connections based on:");
|
||||
println!(" - Client IP address (CIDR notation, e.g., 192.168.1.0/24)");
|
||||
println!(" - TLS client random prefix (hex-encoded, e.g., aabbcc)");
|
||||
println!(" - Both conditions together");
|
||||
println!();
|
||||
|
||||
add_custom_rules(&mut rules);
|
||||
|
||||
RulesConfig { rule: rules }
|
||||
}
|
||||
|
||||
fn add_custom_rules(rules: &mut Vec<Rule>) {
|
||||
println!();
|
||||
while ask_for_agreement("Add a custom rule?") {
|
||||
let rule_type = ask_for_input::<String>(
|
||||
"Rule type (1=IP range, 2=client random prefix, 3=both)",
|
||||
Some("1".to_string()),
|
||||
);
|
||||
|
||||
match rule_type.as_str() {
|
||||
"1" => add_ip_rule(rules),
|
||||
"2" => add_client_random_rule(rules),
|
||||
"3" => add_combined_rule(rules),
|
||||
_ => {
|
||||
println!("Invalid choice. Skipping rule.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
fn add_ip_rule(rules: &mut Vec<Rule>) {
|
||||
let cidr = ask_for_input::<String>(
|
||||
"Enter IP range in CIDR notation (e.g., 203.0.113.0/24)",
|
||||
None,
|
||||
);
|
||||
|
||||
// Validate CIDR format
|
||||
if let Err(_) = cidr.parse::<ipnet::IpNet>() {
|
||||
println!("Invalid CIDR format. Skipping rule.");
|
||||
return;
|
||||
}
|
||||
|
||||
let action = ask_for_rule_action();
|
||||
|
||||
rules.push(Rule {
|
||||
cidr: Some(cidr),
|
||||
client_random_prefix: None,
|
||||
action,
|
||||
});
|
||||
|
||||
println!("Rule added successfully.");
|
||||
}
|
||||
|
||||
fn add_client_random_rule(rules: &mut Vec<Rule>) {
|
||||
let prefix = ask_for_input::<String>(
|
||||
"Enter client random prefix (hex, e.g., aabbcc)",
|
||||
None,
|
||||
);
|
||||
|
||||
// Validate hex format
|
||||
if let Err(_) = hex::decode(&prefix) {
|
||||
println!("Invalid hex format. Skipping rule.");
|
||||
return;
|
||||
}
|
||||
|
||||
let action = ask_for_rule_action();
|
||||
|
||||
rules.push(Rule {
|
||||
cidr: None,
|
||||
client_random_prefix: Some(prefix),
|
||||
action,
|
||||
});
|
||||
|
||||
println!("Rule added successfully.");
|
||||
}
|
||||
|
||||
fn add_combined_rule(rules: &mut Vec<Rule>) {
|
||||
let cidr = ask_for_input::<String>(
|
||||
"Enter IP range in CIDR notation (e.g., 172.16.0.0/12)",
|
||||
None,
|
||||
);
|
||||
|
||||
// Validate CIDR format
|
||||
if let Err(_) = cidr.parse::<ipnet::IpNet>() {
|
||||
println!("Invalid CIDR format. Skipping rule.");
|
||||
return;
|
||||
}
|
||||
|
||||
let prefix = ask_for_input::<String>(
|
||||
"Enter client random prefix (hex, e.g., 001122)",
|
||||
None,
|
||||
);
|
||||
|
||||
// Validate hex format
|
||||
if let Err(_) = hex::decode(&prefix) {
|
||||
println!("Invalid hex format. Skipping rule.");
|
||||
return;
|
||||
}
|
||||
|
||||
let action = ask_for_rule_action();
|
||||
|
||||
rules.push(Rule {
|
||||
cidr: Some(cidr),
|
||||
client_random_prefix: Some(prefix),
|
||||
action,
|
||||
});
|
||||
|
||||
println!("Rule added successfully.");
|
||||
}
|
||||
|
||||
fn ask_for_rule_action() -> RuleAction {
|
||||
let action_str = ask_for_input::<String>(
|
||||
"Action (allow/deny)",
|
||||
Some("allow".to_string()),
|
||||
);
|
||||
|
||||
match action_str.to_lowercase().as_str() {
|
||||
"deny" => RuleAction::Deny,
|
||||
_ => RuleAction::Allow,
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,24 @@ listen_address = ""
|
||||
# ```
|
||||
credentials_file = "{}"
|
||||
|
||||
# The path to a TOML file for connection filtering rules in the following format:
|
||||
#
|
||||
# ```
|
||||
# [[rule]]
|
||||
# cidr = "192.168.0.0/16"
|
||||
# action = "allow"
|
||||
#
|
||||
# [[rule]]
|
||||
# client_random_prefix = "aabbcc"
|
||||
# action = "deny"
|
||||
#
|
||||
# [[rule]]
|
||||
# action = "deny"
|
||||
#
|
||||
# If no rules in this file, all connections are allowed by default.
|
||||
# ```
|
||||
rules_file = "{}"
|
||||
|
||||
{}
|
||||
ipv6_available = {}
|
||||
|
||||
@@ -41,6 +59,7 @@ udp_connections_timeout_secs = {}
|
||||
"#,
|
||||
Settings::doc_listen_address().to_toml_comment(),
|
||||
crate::library_settings::DEFAULT_CREDENTIALS_PATH,
|
||||
crate::library_settings::DEFAULT_RULES_PATH,
|
||||
Settings::doc_ipv6_available().to_toml_comment(),
|
||||
Settings::default_ipv6_available(),
|
||||
Settings::doc_allow_private_network_connections().to_toml_comment(),
|
||||
|
||||
Reference in New Issue
Block a user