Compare commits
10 commits
b2e7bb0317
...
b939961181
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b939961181 | ||
|
|
39fe1b820b | ||
|
|
f74089a365 | ||
|
|
eedb2c5d02 | ||
|
|
d0513bec6a | ||
|
|
8887a775f5 | ||
|
|
1a5b7da6ae | ||
|
|
50524cb5ae | ||
|
|
da8e70f2e3 | ||
|
|
0d8f7c4373 |
21 changed files with 828 additions and 90 deletions
34
.woodpecker.yaml
Normal file
34
.woodpecker.yaml
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
steps:
|
||||||
|
build:
|
||||||
|
image: rust:1.94.0-bullseye
|
||||||
|
environment:
|
||||||
|
RUST_BACKTRACE: 1
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
commands:
|
||||||
|
- rustup default stable
|
||||||
|
- cargo build --verbose --release --jobs 4
|
||||||
|
when:
|
||||||
|
branch: main
|
||||||
|
event: [ push, pull_request ]
|
||||||
|
tests:
|
||||||
|
image: rust
|
||||||
|
environment:
|
||||||
|
RUST_BACKTRACE: 1
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
commands:
|
||||||
|
- cargo test --verbose --jobs 4 -- --test-threads=4
|
||||||
|
when:
|
||||||
|
event: [ pull_request, push ]
|
||||||
|
branch: main
|
||||||
|
clippy_and_fmt:
|
||||||
|
image: rust:1.94.0-bullseye
|
||||||
|
environment:
|
||||||
|
RUST_BACKTRACE: 1
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
commands:
|
||||||
|
- rustup component add clippy rustfmt
|
||||||
|
- cargo fmt --all --check
|
||||||
|
- cargo clippy --jobs 4 -- -D clippy::all # -D warnings
|
||||||
|
when:
|
||||||
|
branch: main
|
||||||
|
event: [ pull_request ]
|
||||||
186
Cargo.lock
generated
186
Cargo.lock
generated
|
|
@ -1251,15 +1251,6 @@ dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "etherparse"
|
|
||||||
version = "0.19.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b119b9796ff800751a220394b8b3613f26dd30c48f254f6837e64c464872d1c7"
|
|
||||||
dependencies = [
|
|
||||||
"arrayvec",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "event-listener"
|
name = "event-listener"
|
||||||
version = "5.4.1"
|
version = "5.4.1"
|
||||||
|
|
@ -1345,6 +1336,12 @@ version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fixedbitset"
|
||||||
|
version = "0.5.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.1.9"
|
version = "1.1.9"
|
||||||
|
|
@ -2207,6 +2204,12 @@ dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "multimap"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "native-tls"
|
name = "native-tls"
|
||||||
version = "0.2.18"
|
version = "0.2.18"
|
||||||
|
|
@ -2343,15 +2346,18 @@ name = "nsc"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arti-client",
|
"arti-client",
|
||||||
"etherparse",
|
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"iptables",
|
"iptables",
|
||||||
"maxminddb",
|
"maxminddb",
|
||||||
|
"prost",
|
||||||
|
"prost-build",
|
||||||
|
"prost-types",
|
||||||
"rtnetlink",
|
"rtnetlink",
|
||||||
"serde",
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml 1.0.6+spec-1.1.0",
|
"toml 1.0.6+spec-1.1.0",
|
||||||
"tun",
|
"tun",
|
||||||
|
"ureq",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2662,6 +2668,17 @@ version = "2.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "petgraph"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
|
||||||
|
dependencies = [
|
||||||
|
"fixedbitset",
|
||||||
|
"hashbrown 0.15.5",
|
||||||
|
"indexmap 2.13.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf"
|
name = "phf"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
|
|
@ -2912,6 +2929,57 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prost"
|
||||||
|
version = "0.14.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"prost-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prost-build"
|
||||||
|
version = "0.14.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"itertools 0.14.0",
|
||||||
|
"log",
|
||||||
|
"multimap",
|
||||||
|
"petgraph",
|
||||||
|
"prettyplease",
|
||||||
|
"prost",
|
||||||
|
"prost-types",
|
||||||
|
"regex",
|
||||||
|
"syn 2.0.117",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prost-derive"
|
||||||
|
version = "0.14.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"itertools 0.14.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prost-types"
|
||||||
|
version = "0.14.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7"
|
||||||
|
dependencies = [
|
||||||
|
"prost",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pwd-grp"
|
name = "pwd-grp"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
|
@ -3146,6 +3214,20 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ring"
|
||||||
|
version = "0.17.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"getrandom 0.2.17",
|
||||||
|
"libc",
|
||||||
|
"untrusted",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rsa"
|
name = "rsa"
|
||||||
version = "0.9.10"
|
version = "0.9.10"
|
||||||
|
|
@ -3242,6 +3324,41 @@ dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls"
|
||||||
|
version = "0.23.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"ring",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"rustls-webpki",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-pki-types"
|
||||||
|
version = "1.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
|
||||||
|
dependencies = [
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-webpki"
|
||||||
|
version = "0.103.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
|
||||||
|
dependencies = [
|
||||||
|
"ring",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"untrusted",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.22"
|
version = "1.0.22"
|
||||||
|
|
@ -5166,12 +5283,34 @@ version = "0.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "untrusted"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unty"
|
name = "unty"
|
||||||
version = "0.0.4"
|
version = "0.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
|
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ureq"
|
||||||
|
version = "2.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"flate2",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"url",
|
||||||
|
"webpki-roots 0.26.11",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.8"
|
version = "2.5.8"
|
||||||
|
|
@ -5363,6 +5502,24 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki-roots"
|
||||||
|
version = "0.26.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
|
||||||
|
dependencies = [
|
||||||
|
"webpki-roots 1.0.6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki-roots"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed"
|
||||||
|
dependencies = [
|
||||||
|
"rustls-pki-types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|
@ -5533,6 +5690,15 @@ dependencies = [
|
||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.59.0"
|
version = "0.59.0"
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,18 @@ version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Tun-in interface for Mesh networks like Tor/I2P with traffic-routing support (DIRECT/PROXY/BLOCK etc.)"
|
description = "Tun-in interface for Mesh networks like Tor/I2P with traffic-routing support (DIRECT/PROXY/BLOCK etc.)"
|
||||||
repository = "https://codeberg.org/NamelessTeam/nsc"
|
repository = "https://codeberg.org/NamelessTeam/nsc"
|
||||||
|
build = "build.rs"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
prost-build = "0.14.3"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arti-client = "0.40.0"
|
arti-client = "0.40.0"
|
||||||
etherparse = "0.19.0"
|
|
||||||
ipnet = "2.12.0"
|
ipnet = "2.12.0"
|
||||||
iptables = "0.6.0"
|
iptables = "0.6.0"
|
||||||
maxminddb = "0.27.3"
|
maxminddb = "0.27.3"
|
||||||
|
prost = "0.14.3"
|
||||||
|
prost-types = "0.14.3"
|
||||||
rtnetlink = "0.20.0"
|
rtnetlink = "0.20.0"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
tokio = { version = "1.50.0", features = ["full"] }
|
tokio = { version = "1.50.0", features = ["full"] }
|
||||||
|
|
@ -24,4 +28,7 @@ lto = true
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
ureq = "2.12"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,12 @@ The project is designed by the desire to transfer all the charms of routing and
|
||||||
## brief impl plan:
|
## brief impl plan:
|
||||||
### client-side usage essentianls
|
### client-side usage essentianls
|
||||||
1. [ ] TUN raw data (headers) parsing 50-70% done =)
|
1. [ ] TUN raw data (headers) parsing 50-70% done =)
|
||||||
2. [ ] parse geoip2 thingies
|
2. [ ] parse geoip2 (mmdb for now) thingies (
|
||||||
|
3. [ ] add v2ray protocols buffer support (geo{ip,site}.dat)
|
||||||
3. [ ] parse user's Config
|
3. [ ] parse user's Config
|
||||||
4. [ ] impl routing logic based on client's Config
|
4. [ ] impl routing logic based on client's Config
|
||||||
5. [ ] make this thing easy adn convenient to use
|
5. [ ] make this thing easy adn convenient to use
|
||||||
6. [ ] custom Tor/i2p profiles/Config presets mayber????
|
6. [ ] custom Tor/i2p profiles/Config presets (via proxies like 127.0.0.1:10808)
|
||||||
|
|
||||||
### inner infra
|
### inner infra
|
||||||
1. [ ] write as much tests as possible covering hopefully all logic
|
1. [ ] write as much tests as possible covering hopefully all logic
|
||||||
|
|
|
||||||
14
build.rs
Normal file
14
build.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
use std::io::Result;
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let out_dir = std::path::PathBuf::from("src/geoparsers/v2ray/");
|
||||||
|
|
||||||
|
prost_build::Config::new()
|
||||||
|
.out_dir(&out_dir)
|
||||||
|
.compile_protos(
|
||||||
|
&["src/geoparsers/v2ray/proto_src/geosite.proto"],
|
||||||
|
&["src/geoparsers/v2ray/proto_src/"],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ pub enum RunTypes {
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// Paths to v2ray `geosite.dat', `geoip.dat`
|
/// Paths to `geosite.dat', `geolite2.mmdb`
|
||||||
pub geo_files: [String; 2],
|
pub geo_files: [String; 2],
|
||||||
/// Routing settings similar to v2ray
|
/// Routing settings similar to v2ray
|
||||||
pub routing: String,
|
pub routing: String,
|
||||||
|
|
@ -17,11 +17,21 @@ pub struct Config {
|
||||||
pub mode: RunTypes,
|
pub mode: RunTypes,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Think how to add other anonymisers
|
||||||
|
// Like VPN on localhost:10808
|
||||||
|
// it can be like:
|
||||||
|
// ```toml
|
||||||
|
// [[proxy]]
|
||||||
|
// name = "VPN"
|
||||||
|
// addr = "127.0.0.1:10808"
|
||||||
|
// type = "SOCKS5" # ...
|
||||||
|
// ```
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
geo_files: [
|
geo_files: [
|
||||||
String::from("/etc/nsc/data/geoip.dat"),
|
String::from("/etc/nsc/data/geolite2.mmdb"),
|
||||||
String::from("/etc/nsc/data/geosite.dat"),
|
String::from("/etc/nsc/data/geosite.dat"),
|
||||||
],
|
],
|
||||||
routing: String::from("/etc/nsc/routing.toml"),
|
routing: String::from("/etc/nsc/routing.toml"),
|
||||||
|
|
@ -29,5 +39,3 @@ impl Default for Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,61 @@
|
||||||
use ipnet::IpNet;
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
|
use maxminddb::{Reader, geoip2};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
/// Enum for declaring GeoSite/IP routing
|
// For now only MMDB because i cant found .proto schemes of
|
||||||
|
// V2Ray GeoSite.dat :((
|
||||||
|
// TODO: V2Ray protobuf parsing && Test 4 ts
|
||||||
|
|
||||||
|
/// Interface enum for `dst_addr` info
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
pub enum RouteType {
|
pub enum RouteType {
|
||||||
/// GeoSite MMDB type, like `category-ads-all`
|
/// GeoSite MMDB type, like `category-ads-all`
|
||||||
GeoSite(String),
|
GeoSite(String),
|
||||||
/// Subnet
|
/// Result with GeoCode like "RU"
|
||||||
GeoIp(IpNet),
|
GeoIp(String),
|
||||||
|
// String because enum will used as interface in result of `route_packet`.
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Routing actions
|
/// Routing actions
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
pub enum RouteAction {
|
pub enum RouteAction {
|
||||||
|
#[serde(alias = "block")]
|
||||||
Block,
|
Block,
|
||||||
|
#[serde(alias = "proxy")]
|
||||||
Proxy,
|
Proxy,
|
||||||
|
#[serde(alias = "direct")]
|
||||||
Direct,
|
Direct,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Rules = Vec<Rule>;
|
pub type Rules = Vec<Rule>;
|
||||||
|
|
||||||
/// Type for declaring the routing rules like:
|
/// Type for deserializing the routing rules like:
|
||||||
/// ```toml
|
#[derive(serde::Deserialize)]
|
||||||
/// [rule]
|
|
||||||
/// action = enum RouteAction
|
|
||||||
/// target = enum RouteType
|
|
||||||
///
|
|
||||||
/// [rule]
|
|
||||||
/// target = "geosite:category-ads-all"
|
|
||||||
/// action = "block"
|
|
||||||
/// ```
|
|
||||||
pub struct Rule {
|
pub struct Rule {
|
||||||
pub target: RouteType,
|
pub target: RouteType,
|
||||||
pub action: RouteAction,
|
pub action: RouteAction,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_ruleset(config: Config) -> Result<Rules, Box<dyn std::error::Error>> {
|
pub struct GeoIpService {
|
||||||
let reader = maxminddb::Reader::open_readfile(config.geo_files[0].clone())?;
|
reader: Reader<Vec<u8>>,
|
||||||
|
|
||||||
// Ok(())
|
|
||||||
todo!();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl GeoIpService {
|
||||||
|
pub fn new(config: Config) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
let path = config.geo_files.get(0).unwrap();
|
||||||
|
let reader = Reader::open_readfile(path)?;
|
||||||
|
Ok(Self { reader })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lookup_country<'a>(
|
||||||
|
&'a self,
|
||||||
|
ip: IpAddr,
|
||||||
|
) -> Result<maxminddb::geoip2::Country<'a>, Box<dyn std::error::Error>> {
|
||||||
|
let result = self.reader.lookup(ip)?;
|
||||||
|
|
||||||
|
result
|
||||||
|
.decode::<geoip2::Country>()?
|
||||||
|
.ok_or_else(|| "Couldnt lookup IP geo.".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
mod geoip2;
|
pub mod geoip2;
|
||||||
|
pub mod toml;
|
||||||
|
pub mod v2ray;
|
||||||
|
|
|
||||||
15
src/geoparsers/toml.rs
Normal file
15
src/geoparsers/toml.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::geoparsers::geoip2::Rules;
|
||||||
|
|
||||||
|
pub fn parse_rules(config: Config) -> Result<Option<Rules>, Box<dyn std::error::Error>> {
|
||||||
|
let data = match std::fs::read_to_string(config.routing) {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(_) => {
|
||||||
|
println!("Couldnt find your `rules.toml`; Using default mode. All to anonymizers");
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let rules: Rules = toml::from_str(&data)?;
|
||||||
|
Ok(Some(rules))
|
||||||
|
}
|
||||||
2
src/geoparsers/v2ray/mod.rs
Normal file
2
src/geoparsers/v2ray/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod parsing;
|
||||||
|
pub mod types;
|
||||||
79
src/geoparsers/v2ray/parsing.rs
Normal file
79
src/geoparsers/v2ray/parsing.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
use crate::geoparsers::v2ray::types::{Domain, GeoSite, GeoSiteList};
|
||||||
|
use prost::Message;
|
||||||
|
use prost::bytes::Buf;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
pub struct GeoSiteService {
|
||||||
|
index: GeoSiteList,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GeoSiteService {
|
||||||
|
// TODO: Make more smart memory mapping; geosite files can be > 70MB
|
||||||
|
pub fn new(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
|
let bytes = fs::read(path)?;
|
||||||
|
let geosite_list = decode_geosite_stream(&bytes)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
index: geosite_list,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Idk but i think it can work
|
||||||
|
pub fn lookup(&self, value: &str) -> Option<&GeoSite> {
|
||||||
|
self.index
|
||||||
|
.entry
|
||||||
|
.iter()
|
||||||
|
.find(|site| site.domain.iter().any(|d| d.value == value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of GeoSite entries in the list
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.index.entry.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the GeoSite list is empty
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.index.entry.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode a stream of length-delimited GeoSite messages
|
||||||
|
/// `geosite.dat` ts is not one protobuf-message, stream of length-delimited messages
|
||||||
|
/// so we need ts helper
|
||||||
|
fn decode_geosite_stream(bytes: &[u8]) -> Result<GeoSiteList, Box<dyn std::error::Error>> {
|
||||||
|
let mut buf = bytes;
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
|
||||||
|
while buf.has_remaining() {
|
||||||
|
// Read tag (0x0a field 1, wire type 2)
|
||||||
|
let tag = buf.get_u8();
|
||||||
|
if tag != 0x0a {
|
||||||
|
return Err(format!("Unexpected tag: {:#04x}", tag).into());
|
||||||
|
}
|
||||||
|
// varint
|
||||||
|
let mut len = 0usize;
|
||||||
|
let mut shift = 0;
|
||||||
|
loop {
|
||||||
|
if !buf.has_remaining() {
|
||||||
|
return Err("Unexpected end of buffer while reading varint".into());
|
||||||
|
}
|
||||||
|
let b = buf.get_u8();
|
||||||
|
len |= ((b & 0x7f) as usize) << shift;
|
||||||
|
if b & 0x80 == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
shift += 7;
|
||||||
|
if shift >= 70 {
|
||||||
|
return Err("Varint too long".into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry_bytes = &buf[..len];
|
||||||
|
let site = GeoSite::decode(entry_bytes)?;
|
||||||
|
entries.push(site);
|
||||||
|
|
||||||
|
buf.advance(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(GeoSiteList { entry: entries })
|
||||||
|
}
|
||||||
66
src/geoparsers/v2ray/proto_src/geosite.proto
Normal file
66
src/geoparsers/v2ray/proto_src/geosite.proto
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package types;
|
||||||
|
|
||||||
|
// Domain for routing decision.
|
||||||
|
message Domain {
|
||||||
|
// Type of domain value.
|
||||||
|
enum Type {
|
||||||
|
// The value is used as is.
|
||||||
|
Plain = 0;
|
||||||
|
// The value is used as a regular expression.
|
||||||
|
Regex = 1;
|
||||||
|
// The value is a root domain.
|
||||||
|
Domain = 2;
|
||||||
|
// The value is a domain.
|
||||||
|
Full = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain matching type.
|
||||||
|
Type type = 1;
|
||||||
|
|
||||||
|
// Domain value.
|
||||||
|
string value = 2;
|
||||||
|
|
||||||
|
// Attribute of the domain.
|
||||||
|
message Attribute {
|
||||||
|
string key = 1;
|
||||||
|
oneof typed_value {
|
||||||
|
bool bool_value = 2;
|
||||||
|
int64 int_value = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attributes of this domain. May be used for filtering.
|
||||||
|
repeated Attribute attribute = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IP for routing decision, in CIDR form.
|
||||||
|
message CIDR {
|
||||||
|
// IP address, should be either 4 or 16 bytes.
|
||||||
|
bytes ip = 1;
|
||||||
|
|
||||||
|
// Number of leading ones in the network mask.
|
||||||
|
uint32 prefix = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GeoIP {
|
||||||
|
string country_code = 1;
|
||||||
|
repeated CIDR cidr = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GeoIPList {
|
||||||
|
repeated GeoIP entry = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GeoSite {
|
||||||
|
string country_code = 1;
|
||||||
|
repeated Domain domain = 2;
|
||||||
|
// resource_hash instruct simplified config converter to load domain from geo file.
|
||||||
|
bytes resource_hash = 3;
|
||||||
|
string code = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GeoSiteList {
|
||||||
|
repeated GeoSite entry = 1;
|
||||||
|
}
|
||||||
111
src/geoparsers/v2ray/types.rs
Normal file
111
src/geoparsers/v2ray/types.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
// This file is @generated by prost-build.
|
||||||
|
/// Domain for routing decision.
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct Domain {
|
||||||
|
/// Domain matching type.
|
||||||
|
#[prost(enumeration = "domain::Type", tag = "1")]
|
||||||
|
pub r#type: i32,
|
||||||
|
/// Domain value.
|
||||||
|
#[prost(string, tag = "2")]
|
||||||
|
pub value: ::prost::alloc::string::String,
|
||||||
|
/// Attributes of this domain. May be used for filtering.
|
||||||
|
#[prost(message, repeated, tag = "3")]
|
||||||
|
pub attribute: ::prost::alloc::vec::Vec<domain::Attribute>,
|
||||||
|
}
|
||||||
|
/// Nested message and enum types in `Domain`.
|
||||||
|
pub mod domain {
|
||||||
|
/// Attribute of the domain.
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
||||||
|
pub struct Attribute {
|
||||||
|
#[prost(string, tag = "1")]
|
||||||
|
pub key: ::prost::alloc::string::String,
|
||||||
|
#[prost(oneof = "attribute::TypedValue", tags = "2, 3")]
|
||||||
|
pub typed_value: ::core::option::Option<attribute::TypedValue>,
|
||||||
|
}
|
||||||
|
/// Nested message and enum types in `Attribute`.
|
||||||
|
pub mod attribute {
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Oneof)]
|
||||||
|
pub enum TypedValue {
|
||||||
|
#[prost(bool, tag = "2")]
|
||||||
|
BoolValue(bool),
|
||||||
|
#[prost(int64, tag = "3")]
|
||||||
|
IntValue(i64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Type of domain value.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum Type {
|
||||||
|
/// The value is used as is.
|
||||||
|
Plain = 0,
|
||||||
|
/// The value is used as a regular expression.
|
||||||
|
Regex = 1,
|
||||||
|
/// The value is a root domain.
|
||||||
|
Domain = 2,
|
||||||
|
/// The value is a domain.
|
||||||
|
Full = 3,
|
||||||
|
}
|
||||||
|
impl Type {
|
||||||
|
/// String value of the enum field names used in the ProtoBuf definition.
|
||||||
|
///
|
||||||
|
/// The values are not transformed in any way and thus are considered stable
|
||||||
|
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
|
||||||
|
pub fn as_str_name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Plain => "Plain",
|
||||||
|
Self::Regex => "Regex",
|
||||||
|
Self::Domain => "Domain",
|
||||||
|
Self::Full => "Full",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Creates an enum from field names used in the ProtoBuf definition.
|
||||||
|
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
|
||||||
|
match value {
|
||||||
|
"Plain" => Some(Self::Plain),
|
||||||
|
"Regex" => Some(Self::Regex),
|
||||||
|
"Domain" => Some(Self::Domain),
|
||||||
|
"Full" => Some(Self::Full),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// IP for routing decision, in CIDR form.
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
|
||||||
|
pub struct Cidr {
|
||||||
|
/// IP address, should be either 4 or 16 bytes.
|
||||||
|
#[prost(bytes = "vec", tag = "1")]
|
||||||
|
pub ip: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
/// Number of leading ones in the network mask.
|
||||||
|
#[prost(uint32, tag = "2")]
|
||||||
|
pub prefix: u32,
|
||||||
|
}
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct GeoIp {
|
||||||
|
#[prost(string, tag = "1")]
|
||||||
|
pub country_code: ::prost::alloc::string::String,
|
||||||
|
#[prost(message, repeated, tag = "2")]
|
||||||
|
pub cidr: ::prost::alloc::vec::Vec<Cidr>,
|
||||||
|
}
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct GeoIpList {
|
||||||
|
#[prost(message, repeated, tag = "1")]
|
||||||
|
pub entry: ::prost::alloc::vec::Vec<GeoIp>,
|
||||||
|
}
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct GeoSite {
|
||||||
|
#[prost(string, tag = "1")]
|
||||||
|
pub country_code: ::prost::alloc::string::String,
|
||||||
|
#[prost(message, repeated, tag = "2")]
|
||||||
|
pub domain: ::prost::alloc::vec::Vec<Domain>,
|
||||||
|
/// resource_hash instruct simplified config converter to load domain from geo file.
|
||||||
|
#[prost(bytes = "vec", tag = "3")]
|
||||||
|
pub resource_hash: ::prost::alloc::vec::Vec<u8>,
|
||||||
|
#[prost(string, tag = "4")]
|
||||||
|
pub code: ::prost::alloc::string::String,
|
||||||
|
}
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct GeoSiteList {
|
||||||
|
#[prost(message, repeated, tag = "1")]
|
||||||
|
pub entry: ::prost::alloc::vec::Vec<GeoSite>,
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
pub mod sniffing;
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod geoparsers;
|
pub mod geoparsers;
|
||||||
|
pub mod sniffing;
|
||||||
pub mod startup;
|
pub mod startup;
|
||||||
|
|
|
||||||
14
src/main.rs
14
src/main.rs
|
|
@ -1,13 +1,11 @@
|
||||||
// mod config;
|
//mod routing;
|
||||||
// mod geoparsers;
|
mod config;
|
||||||
// pub mod sniffing;
|
mod geoparsers;
|
||||||
// mod startup;
|
pub mod sniffing;
|
||||||
|
//mod startup;
|
||||||
|
|
||||||
use nsc::startup::init;
|
use nsc::startup::init;
|
||||||
|
|
||||||
use std::io::Read;
|
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>
|
|
||||||
>{
|
|
||||||
init()
|
init()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
use crate::geoparsers::geoip2::GeoIpService;
|
||||||
|
|
||||||
|
struct Router {
|
||||||
|
geoip: GeoIpService,
|
||||||
|
// geosite: GeoSiteService
|
||||||
|
// sniffer: Sniffer
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use std::fmt;
|
||||||
use tun::Error;
|
use tun::Error;
|
||||||
|
|
||||||
// Here we will recieve bytes and try to get their destanation & apply Rules for them.
|
// Here we will recieve bytes and try to get their destanation & apply Rules for them.
|
||||||
|
|
@ -7,10 +8,17 @@ use crate::config::Config;
|
||||||
pub enum Protocol {
|
pub enum Protocol {
|
||||||
TCP,
|
TCP,
|
||||||
UDP,
|
UDP,
|
||||||
Unsupported(u8)
|
Unsupported(u8),
|
||||||
|
}
|
||||||
|
type SourceV4Ip = Ipv4;
|
||||||
|
type SourceV6Ip = Ipv6;
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub enum IpVersion {
|
||||||
|
V4,
|
||||||
|
V6,
|
||||||
}
|
}
|
||||||
type Ipv4 = [u8; 4];
|
type Ipv4 = [u8; 4];
|
||||||
type Ipv6 = [u8; 16];
|
type Ipv6 = [u16; 8];
|
||||||
type Port = u16;
|
type Port = u16;
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum PacketInfo {
|
pub enum PacketInfo {
|
||||||
|
|
@ -21,7 +29,7 @@ pub enum PacketInfo {
|
||||||
dst_ip: Ipv4,
|
dst_ip: Ipv4,
|
||||||
dst_port: Port,
|
dst_port: Port,
|
||||||
protocol: Protocol,
|
protocol: Protocol,
|
||||||
dns: bool
|
dns: bool,
|
||||||
},
|
},
|
||||||
// <https://www.geeksforgeeks.org/computer-networks/internet-protocol-version-6-ipv6-header/>
|
// <https://www.geeksforgeeks.org/computer-networks/internet-protocol-version-6-ipv6-header/>
|
||||||
V6 {
|
V6 {
|
||||||
|
|
@ -30,11 +38,112 @@ pub enum PacketInfo {
|
||||||
dst_ip: Ipv6,
|
dst_ip: Ipv6,
|
||||||
dst_port: Port,
|
dst_port: Port,
|
||||||
protocol: Protocol,
|
protocol: Protocol,
|
||||||
dns: bool
|
dns: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for PacketInfo {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
if self.version() == &IpVersion::V4 {
|
||||||
|
let src_ip = self.src_ipv4_ip().unwrap();
|
||||||
|
let dst_ip = self.dst_ipv4_ip().unwrap();
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}.{}.{}.{}:{} -> {}.{}.{}.{}:{} {:?} is dns? {:?}",
|
||||||
|
src_ip[0],
|
||||||
|
src_ip[1],
|
||||||
|
src_ip[2],
|
||||||
|
src_ip[3],
|
||||||
|
self.src_port(),
|
||||||
|
dst_ip[0],
|
||||||
|
dst_ip[1],
|
||||||
|
dst_ip[2],
|
||||||
|
dst_ip[3],
|
||||||
|
self.dst_port(),
|
||||||
|
self.protocol(),
|
||||||
|
self.dns()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let src_ip = self.src_ipv6_ip().unwrap();
|
||||||
|
let dst_ip = self.dst_ipv6_ip().unwrap();
|
||||||
|
// y:y:y:y:y:y:y:y = 8 hexademical; y = segment, pair of 2 u8 big endian
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x} port:{} -> {:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x}:{:x} port:{} {:?} is dns? {:?}",
|
||||||
|
src_ip[0],
|
||||||
|
src_ip[1],
|
||||||
|
src_ip[2],
|
||||||
|
src_ip[3],
|
||||||
|
src_ip[4],
|
||||||
|
src_ip[5],
|
||||||
|
src_ip[6],
|
||||||
|
src_ip[7],
|
||||||
|
self.src_port(),
|
||||||
|
dst_ip[0],
|
||||||
|
dst_ip[1],
|
||||||
|
dst_ip[2],
|
||||||
|
dst_ip[3],
|
||||||
|
dst_ip[4],
|
||||||
|
dst_ip[5],
|
||||||
|
dst_ip[6],
|
||||||
|
dst_ip[7],
|
||||||
|
self.dst_port(),
|
||||||
|
self.protocol(),
|
||||||
|
self.dns()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PacketInfo {
|
impl PacketInfo {
|
||||||
|
pub fn dns(&self) -> &bool {
|
||||||
|
match self {
|
||||||
|
PacketInfo::V4 { dns, .. } => dns,
|
||||||
|
PacketInfo::V6 { dns, .. } => dns,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn src_ipv6_ip(&self) -> Option<&SourceV6Ip> {
|
||||||
|
match self {
|
||||||
|
PacketInfo::V6 { src_ip, .. } => Some(src_ip),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn dst_ipv6_ip(&self) -> Option<&SourceV6Ip> {
|
||||||
|
match self {
|
||||||
|
PacketInfo::V6 { dst_ip, .. } => Some(dst_ip),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn src_ipv4_ip(&self) -> Option<&SourceV4Ip> {
|
||||||
|
match self {
|
||||||
|
PacketInfo::V4 { src_ip, .. } => Some(src_ip),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn dst_ipv4_ip(&self) -> Option<&SourceV4Ip> {
|
||||||
|
match self {
|
||||||
|
PacketInfo::V4 { dst_ip, .. } => Some(dst_ip),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn src_port(&self) -> &Port {
|
||||||
|
match self {
|
||||||
|
PacketInfo::V4 { src_port, .. } => src_port,
|
||||||
|
PacketInfo::V6 { src_port, .. } => src_port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn dst_port(&self) -> &Port {
|
||||||
|
match self {
|
||||||
|
PacketInfo::V4 { dst_port, .. } => dst_port,
|
||||||
|
PacketInfo::V6 { dst_port, .. } => dst_port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn version(&self) -> &IpVersion {
|
||||||
|
match self {
|
||||||
|
PacketInfo::V4 { .. } => &IpVersion::V4,
|
||||||
|
PacketInfo::V6 { .. } => &IpVersion::V6,
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn protocol(&self) -> &Protocol {
|
pub fn protocol(&self) -> &Protocol {
|
||||||
match self {
|
match self {
|
||||||
PacketInfo::V4 { protocol, .. } => protocol,
|
PacketInfo::V4 { protocol, .. } => protocol,
|
||||||
|
|
@ -52,59 +161,74 @@ pub fn sniff_raw_packets(packet: &Packet) -> SniffedPacket {
|
||||||
let ver = packet[0] >> 4;
|
let ver = packet[0] >> 4;
|
||||||
match ver {
|
match ver {
|
||||||
4 => {
|
4 => {
|
||||||
let dst_port = Port::from_be_bytes([packet[22], packet[23]]);
|
// Internet Header Length (IHL).
|
||||||
|
let ihl = (packet[0] & 0x0F) as usize * 4;
|
||||||
|
let dst_port = Port::from_be_bytes([packet[ihl + 2], packet[ihl + 3]]);
|
||||||
let dns;
|
let dns;
|
||||||
if dst_port == 53 { dns = true; } else { dns = false; };
|
if dst_port == 53 {
|
||||||
// FIXME: hardcoded IPv4 port offset
|
dns = true;
|
||||||
let v4 = PacketInfo::V4{
|
} else {
|
||||||
|
dns = false;
|
||||||
|
};
|
||||||
|
let v4 = PacketInfo::V4 {
|
||||||
src_ip: <Ipv4>::try_from(&packet[12..16])?,
|
src_ip: <Ipv4>::try_from(&packet[12..16])?,
|
||||||
src_port: Port::from_be_bytes([packet[20], packet[21]]),
|
src_port: Port::from_be_bytes([packet[ihl], packet[ihl + 1]]),
|
||||||
dst_ip: <Ipv4>::try_from(&packet[16..20])?,
|
dst_ip: <Ipv4>::try_from(&packet[16..20])?,
|
||||||
dst_port,
|
dst_port,
|
||||||
protocol: match packet[9] {
|
protocol: match packet[9] {
|
||||||
6 => Protocol::TCP,
|
6 => Protocol::TCP,
|
||||||
17 => Protocol::UDP,
|
17 => Protocol::UDP,
|
||||||
p => Protocol::Unsupported(p)
|
p => Protocol::Unsupported(p),
|
||||||
},
|
},
|
||||||
dns
|
dns,
|
||||||
};
|
};
|
||||||
if !matches!(v4.protocol(), Protocol::Unsupported(_)) {
|
if !matches!(v4.protocol(), Protocol::Unsupported(_)) {
|
||||||
println!("{v4:?}");
|
println!("{v4}");
|
||||||
} else {
|
} else {
|
||||||
// TODO: make --debug option which will include this diagnostic, for general use this
|
// TODO: make --debug option which will include this diagnostic, for general use this
|
||||||
// should be off
|
// should be off
|
||||||
// println!("oppsie unsupported protocol: {:?}", v4.protocol());
|
// println!("oppsie unsupported protocol: {:?}", v4.protocol());
|
||||||
}
|
}
|
||||||
Ok(v4)
|
Ok(v4)
|
||||||
},
|
}
|
||||||
6 => {
|
6 => {
|
||||||
let dst_port = Port::from_be_bytes([packet[22], packet[23]]);
|
// y:y:y:y:y:y:y:y hexademical; y = segment, pair of 2 u8 in big endian
|
||||||
|
let src_ip = std::array::from_fn(|i| {
|
||||||
|
u16::from_be_bytes([packet[8 + i * 2], packet[8 + i * 2 + 1]])
|
||||||
|
});
|
||||||
|
let dst_ip = std::array::from_fn(|i| {
|
||||||
|
u16::from_be_bytes([packet[24 + i * 2], packet[24 + i * 2 + 1]])
|
||||||
|
});
|
||||||
|
|
||||||
|
let dst_port = Port::from_be_bytes([packet[42], packet[43]]);
|
||||||
let dns;
|
let dns;
|
||||||
if dst_port == 53 { dns = true; } else { dns = false; };
|
if dst_port == 53 {
|
||||||
let v6 = PacketInfo::V6{
|
dns = true;
|
||||||
src_ip: <Ipv6>::try_from(&packet[8..24])?,
|
} else {
|
||||||
|
dns = false;
|
||||||
|
};
|
||||||
|
let v6 = PacketInfo::V6 {
|
||||||
|
src_ip,
|
||||||
src_port: Port::from_be_bytes([packet[40], packet[41]]),
|
src_port: Port::from_be_bytes([packet[40], packet[41]]),
|
||||||
dst_ip: <Ipv6>::try_from(&packet[24..40])?,
|
dst_ip,
|
||||||
dst_port,
|
dst_port,
|
||||||
protocol: match packet[6] {
|
protocol: match packet[6] {
|
||||||
6 => Protocol::TCP,
|
6 => Protocol::TCP,
|
||||||
17 => Protocol::UDP,
|
17 => Protocol::UDP,
|
||||||
p => Protocol::Unsupported(p)
|
p => Protocol::Unsupported(p),
|
||||||
},
|
},
|
||||||
dns
|
dns,
|
||||||
};
|
};
|
||||||
if !matches!(v6.protocol(), Protocol::Unsupported(_)) {
|
if !matches!(v6.protocol(), Protocol::Unsupported(_)) {
|
||||||
println!("{v6:?}");
|
println!("{v6}");
|
||||||
} else {
|
} else {
|
||||||
// TODO: make --debug option which will include this diagnostic, for general use this
|
// TODO: make --debug option which will include this diagnostic, for general use this
|
||||||
// should be off
|
// should be off
|
||||||
// println!("oppsie unsupported protocol: {:?}", v6.protocol());
|
// println!("oppsie unsupported protocol: {:?}", v6.protocol());
|
||||||
}
|
}
|
||||||
Ok(v6)
|
Ok(v6)
|
||||||
},
|
|
||||||
ver => {
|
|
||||||
Err(format!("unsuppiorted ver: {ver}").into())
|
|
||||||
}
|
}
|
||||||
|
ver => Err(format!("unsuppiorted ver: {ver}").into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Here we iniitialize systems crucial for nsc
|
// Here we iniitialize systems crucial for nsc
|
||||||
use std::io::Read;
|
|
||||||
use crate::sniffing::headers::sniff_raw_packets;
|
|
||||||
use crate::sniffing::headers::Protocol;
|
use crate::sniffing::headers::Protocol;
|
||||||
|
use crate::sniffing::headers::sniff_raw_packets;
|
||||||
|
use std::io::Read;
|
||||||
pub fn init() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
pub fn init() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||||
let mut config = tun::Configuration::default();
|
let mut config = tun::Configuration::default();
|
||||||
config
|
config
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use nsc::sniffing::*;
|
use nsc::sniffing::*;
|
||||||
|
|
||||||
use crate::headers::Protocol;
|
use crate::headers::Protocol;
|
||||||
use nsc::sniffing::headers::sniff_raw_packets;
|
|
||||||
use nsc::sniffing::headers::PacketInfo;
|
use nsc::sniffing::headers::PacketInfo;
|
||||||
|
use nsc::sniffing::headers::sniff_raw_packets;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn generic_typeck() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
fn generic_typeck() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
|
@ -115,7 +115,8 @@ fn generic_typeck() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
// [0] IPv4 TCP 192.168.1.100:4832 → 93.184.216.34:443
|
// [0] IPv4 TCP 192.168.1.100:4832 → 93.184.216.34:443
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sniff_raw_packets(test_suite[0])?,
|
sniff_raw_packets(test_suite[0])?,
|
||||||
PacketInfo::V4 {
|
PacketInfo::V6 {
|
||||||
|
dns: false,
|
||||||
src_ip: [192, 168, 1, 100],
|
src_ip: [192, 168, 1, 100],
|
||||||
src_port: 4832,
|
src_port: 4832,
|
||||||
dst_ip: [93, 184, 216, 34],
|
dst_ip: [93, 184, 216, 34],
|
||||||
|
|
@ -127,7 +128,8 @@ fn generic_typeck() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
// [1] IPv4 UDP 10.0.0.9:5353 → 224.0.0.251:5353 (mDNS)
|
// [1] IPv4 UDP 10.0.0.9:5353 → 224.0.0.251:5353 (mDNS)
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sniff_raw_packets(test_suite[1])?,
|
sniff_raw_packets(test_suite[1])?,
|
||||||
PacketInfo::V4 {
|
PacketInfo::V6 {
|
||||||
|
dns: false,
|
||||||
src_ip: [10, 0, 0, 9],
|
src_ip: [10, 0, 0, 9],
|
||||||
src_port: 5353,
|
src_port: 5353,
|
||||||
dst_ip: [224, 0, 0, 251],
|
dst_ip: [224, 0, 0, 251],
|
||||||
|
|
@ -139,7 +141,8 @@ fn generic_typeck() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
// [2] IPv4 UDP 10.0.0.9:1024 → 8.8.8.8:53 (DNS)
|
// [2] IPv4 UDP 10.0.0.9:1024 → 8.8.8.8:53 (DNS)
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sniff_raw_packets(test_suite[2])?,
|
sniff_raw_packets(test_suite[2])?,
|
||||||
PacketInfo::V4 {
|
PacketInfo::V6 {
|
||||||
|
dns: false,
|
||||||
src_ip: [10, 0, 0, 9],
|
src_ip: [10, 0, 0, 9],
|
||||||
src_port: 1024,
|
src_port: 1024,
|
||||||
dst_ip: [8, 8, 8, 8],
|
dst_ip: [8, 8, 8, 8],
|
||||||
|
|
@ -151,7 +154,8 @@ fn generic_typeck() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
// [3] IPv4 TCP 10.0.0.5:54321 → 10.0.0.1:80 (HTTP)
|
// [3] IPv4 TCP 10.0.0.5:54321 → 10.0.0.1:80 (HTTP)
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sniff_raw_packets(test_suite[3])?,
|
sniff_raw_packets(test_suite[3])?,
|
||||||
PacketInfo::V4 {
|
PacketInfo::V6 {
|
||||||
|
dns: false,
|
||||||
src_ip: [10, 0, 0, 5],
|
src_ip: [10, 0, 0, 5],
|
||||||
src_port: 54321,
|
src_port: 54321,
|
||||||
dst_ip: [10, 0, 0, 1],
|
dst_ip: [10, 0, 0, 1],
|
||||||
|
|
@ -163,7 +167,8 @@ fn generic_typeck() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
// [4] IPv4 TCP 172.16.0.1:65535 → 172.16.0.2:8080
|
// [4] IPv4 TCP 172.16.0.1:65535 → 172.16.0.2:8080
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sniff_raw_packets(test_suite[4])?,
|
sniff_raw_packets(test_suite[4])?,
|
||||||
PacketInfo::V4 {
|
PacketInfo::V6 {
|
||||||
|
dns: false,
|
||||||
src_ip: [172, 16, 0, 1],
|
src_ip: [172, 16, 0, 1],
|
||||||
src_port: 65535,
|
src_port: 65535,
|
||||||
dst_ip: [172, 16, 0, 2],
|
dst_ip: [172, 16, 0, 2],
|
||||||
|
|
@ -175,7 +180,8 @@ fn generic_typeck() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
// [5] IPv4 TCP IHL=6 10.0.0.1:9090 → 10.0.0.2:22 (requires IHL-based offset)
|
// [5] IPv4 TCP IHL=6 10.0.0.1:9090 → 10.0.0.2:22 (requires IHL-based offset)
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sniff_raw_packets(test_suite[5])?,
|
sniff_raw_packets(test_suite[5])?,
|
||||||
PacketInfo::V4 {
|
PacketInfo::V6 {
|
||||||
|
dns: false,
|
||||||
src_ip: [10, 0, 0, 1],
|
src_ip: [10, 0, 0, 1],
|
||||||
src_port: 9090,
|
src_port: 9090,
|
||||||
dst_ip: [10, 0, 0, 2],
|
dst_ip: [10, 0, 0, 2],
|
||||||
|
|
@ -187,7 +193,8 @@ fn generic_typeck() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
// [6] IPv4 ICMP (unsupported, "ports" are just ICMP body bytes)
|
// [6] IPv4 ICMP (unsupported, "ports" are just ICMP body bytes)
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sniff_raw_packets(test_suite[6])?,
|
sniff_raw_packets(test_suite[6])?,
|
||||||
PacketInfo::V4 {
|
PacketInfo::V6 {
|
||||||
|
dns: false,
|
||||||
src_ip: [10, 0, 0, 9],
|
src_ip: [10, 0, 0, 9],
|
||||||
src_port: 2048,
|
src_port: 2048,
|
||||||
dst_ip: [10, 0, 0, 1],
|
dst_ip: [10, 0, 0, 1],
|
||||||
|
|
@ -200,6 +207,7 @@ fn generic_typeck() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sniff_raw_packets(test_suite[7])?,
|
sniff_raw_packets(test_suite[7])?,
|
||||||
PacketInfo::V6 {
|
PacketInfo::V6 {
|
||||||
|
dns: false,
|
||||||
src_ip: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
src_ip: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||||
src_port: 4000,
|
src_port: 4000,
|
||||||
dst_ip: [0x26, 0x06, 0x47, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
dst_ip: [0x26, 0x06, 0x47, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||||
|
|
@ -212,6 +220,7 @@ fn generic_typeck() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sniff_raw_packets(test_suite[8])?,
|
sniff_raw_packets(test_suite[8])?,
|
||||||
PacketInfo::V6 {
|
PacketInfo::V6 {
|
||||||
|
dns: false,
|
||||||
src_ip: [0xFD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
|
src_ip: [0xFD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
|
||||||
src_port: 1234,
|
src_port: 1234,
|
||||||
dst_ip: [0xFD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
dst_ip: [0xFD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||||
|
|
@ -224,6 +233,7 @@ fn generic_typeck() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sniff_raw_packets(test_suite[9])?,
|
sniff_raw_packets(test_suite[9])?,
|
||||||
PacketInfo::V6 {
|
PacketInfo::V6 {
|
||||||
|
dns: false,
|
||||||
src_ip: [0xFE, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
src_ip: [0xFE, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||||
src_port: 32768,
|
src_port: 32768,
|
||||||
dst_ip: [0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
dst_ip: [0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
|
||||||
|
|
|
||||||
72
tests/v2ray_geosite.rs
Normal file
72
tests/v2ray_geosite.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
use nsc::geoparsers::v2ray::parsing::GeoSiteService;
|
||||||
|
use nsc::geoparsers::v2ray::types::Domain;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn download_geosite() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||||
|
let tmp_dir = std::env::temp_dir().join("seccontrol_test");
|
||||||
|
fs::create_dir_all(&tmp_dir)?;
|
||||||
|
|
||||||
|
let geosite_path = tmp_dir.join("geosite.dat");
|
||||||
|
|
||||||
|
if !geosite_path.exists() {
|
||||||
|
// Use v2fly domain-list-community which has standard protobuf format
|
||||||
|
let url = "https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat";
|
||||||
|
let response = ureq::get(url).call()?;
|
||||||
|
let mut file = fs::File::create(&geosite_path)?;
|
||||||
|
let mut reader = response.into_reader();
|
||||||
|
std::io::copy(&mut reader, &mut file)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(geosite_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_geosite_service() -> Result<GeoSiteService, Box<dyn std::error::Error>> {
|
||||||
|
let geosite_path = download_geosite()?;
|
||||||
|
let service = GeoSiteService::new(geosite_path.to_str().unwrap())?;
|
||||||
|
Ok(service)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn geosite_service_creation() {
|
||||||
|
let service = get_geosite_service();
|
||||||
|
assert!(
|
||||||
|
service.is_ok(),
|
||||||
|
"Failed to create GeoSiteService: {:?}",
|
||||||
|
service.err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lookup_existing_domain() {
|
||||||
|
let service = get_geosite_service().expect("Failed to create service");
|
||||||
|
|
||||||
|
assert!(!service.is_empty(), "Service should have entries");
|
||||||
|
println!("Loaded {} GeoSite entries", service.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lookup_nonexistent_domain() {
|
||||||
|
let service = get_geosite_service().expect("Failed to create service");
|
||||||
|
let domain = Domain {
|
||||||
|
r#type: nsc::geoparsers::v2ray::types::domain::Type::Full as i32,
|
||||||
|
value: "nfaklsfjlasfvjkcnjnasxcjsas-not-existing-domain.com".to_string(),
|
||||||
|
attribute: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = service.lookup(domain.value.as_str());
|
||||||
|
assert!(
|
||||||
|
result.is_none(),
|
||||||
|
"Should return none for not existing domain"
|
||||||
|
);
|
||||||
|
println!("{:?}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn geosite_list_not_empty() {
|
||||||
|
let service = get_geosite_service().expect("Failed to create service");
|
||||||
|
|
||||||
|
assert!(!service.is_empty(), "GeoSiteList should not be empty");
|
||||||
|
|
||||||
|
println!("Loaded {} GeoSite entries", service.len());
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue