From 0d8f7c437399531d700c2e01b4e0b33ba72bd689 Mon Sep 17 00:00:00 2001 From: namilsk Date: Tue, 17 Mar 2026 19:19:14 +0300 Subject: [PATCH 1/9] Working on IP geo parsers && packet router --- src/config.rs | 14 ++++++++-- src/geoparsers/geoip2.rs | 59 ++++++++++++++++++++++++++-------------- src/geoparsers/mod.rs | 3 +- src/geoparsers/toml.rs | 15 ++++++++++ src/main.rs | 12 ++++---- src/routing.rs | 9 ++++++ 6 files changed, 83 insertions(+), 29 deletions(-) create mode 100644 src/geoparsers/toml.rs diff --git a/src/config.rs b/src/config.rs index 6552a65..846a1b5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,7 +9,7 @@ pub enum RunTypes { #[derive(Serialize, Deserialize)] pub struct Config { - /// Paths to v2ray `geosite.dat', `geoip.dat` + /// Paths to `geosite.dat', `geolite2.mmdb` pub geo_files: [String; 2], /// Routing settings similar to v2ray pub routing: String, @@ -17,11 +17,21 @@ pub struct Config { 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 { fn default() -> Self { Self { geo_files: [ - String::from("/etc/nsc/data/geoip.dat"), + String::from("/etc/nsc/data/geolite2.mmdb"), String::from("/etc/nsc/data/geosite.dat"), ], routing: String::from("/etc/nsc/routing.toml"), diff --git a/src/geoparsers/geoip2.rs b/src/geoparsers/geoip2.rs index 9323280..9f2cc8b 100644 --- a/src/geoparsers/geoip2.rs +++ b/src/geoparsers/geoip2.rs @@ -1,42 +1,61 @@ -use ipnet::IpNet; 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 { /// GeoSite MMDB type, like `category-ads-all` GeoSite(String), - /// Subnet - GeoIp(IpNet), + /// Result with GeoCode like "RU" + GeoIp(String), + // String because enum will used as interface in result of `route_packet`. } /// Routing actions +#[derive(Debug, Deserialize)] pub enum RouteAction { + #[serde(alias = "block")] Block, + #[serde(alias = "proxy")] Proxy, + #[serde(alias = "direct")] Direct, } -type Rules = Vec; +pub type Rules = Vec; -/// Type for declaring the routing rules like: -/// ```toml -/// [rule] -/// action = enum RouteAction -/// target = enum RouteType -/// -/// [rule] -/// target = "geosite:category-ads-all" -/// action = "block" -/// ``` +/// Type for deserializing the routing rules like: +#[derive(serde::Deserialize)] pub struct Rule { pub target: RouteType, pub action: RouteAction, } -pub fn parse_ruleset(config: Config) -> Result> { - let reader = maxminddb::Reader::open_readfile(config.geo_files[0].clone())?; - - // Ok(()) - todo!(); +pub struct GeoIpService { + reader: Reader>, } +impl GeoIpService { + pub fn new(config: Config) -> Result> { + 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, Box> { + let result = self.reader.lookup(ip)?; + + result + .decode::()? + .ok_or_else(|| "Couldnt lookup IP geo.".into()) + } +} diff --git a/src/geoparsers/mod.rs b/src/geoparsers/mod.rs index 9f95b8f..43af0f3 100644 --- a/src/geoparsers/mod.rs +++ b/src/geoparsers/mod.rs @@ -1 +1,2 @@ -mod geoip2; +pub mod geoip2; +pub mod toml; diff --git a/src/geoparsers/toml.rs b/src/geoparsers/toml.rs new file mode 100644 index 0000000..3638aa7 --- /dev/null +++ b/src/geoparsers/toml.rs @@ -0,0 +1,15 @@ +use crate::config::Config; +use crate::geoparsers::geoip2::Rules; + +pub fn parse_rules(config: Config) -> Result, Box> { + 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)) +} diff --git a/src/main.rs b/src/main.rs index d90ae5d..594f751 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,13 @@ -// mod config; -// mod geoparsers; -// pub mod sniffing; -// mod startup; +mod routing; +mod config; +mod geoparsers; +pub mod sniffing; +mod startup; use nsc::startup::init; use std::io::Read; -fn main() -> Result<(), Box ->{ +fn main() -> Result<(), Box> { init() } diff --git a/src/routing.rs b/src/routing.rs index e69de29..21e355b 100644 --- a/src/routing.rs +++ b/src/routing.rs @@ -0,0 +1,9 @@ +use crate::geoparsers::geoip2::GeoIpService; + +struct Router { + geoip: GeoIpService, + // geosite: GeoSiteService + // sniffer: Sniffer +} + + From da8e70f2e3c841796c122ca90617d74cb044b763 Mon Sep 17 00:00:00 2001 From: namilsk Date: Tue, 17 Mar 2026 19:24:23 +0300 Subject: [PATCH 2/9] updated goals --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 815977b..6826975 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,15 @@ The project is designed by the desire to transfer all the charms of routing and ## brief impl plan: ### client-side usage essentianls 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 4. [ ] impl routing logic based on client's Config 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 1. [ ] write as much tests as possible covering hopefully all logic 2. [ ] keep code readable and comprehensive 3. [ ] ideally review each others PRs after initial MVP stage -4. [ ] leave references/helpful comments on parts when code isn't obvious from the first glance \ No newline at end of file +4. [ ] leave references/helpful comments on parts when code isn't obvious from the first glance From 50524cb5aee1f1f4464f60467122a44befc11203 Mon Sep 17 00:00:00 2001 From: zedddie Date: Tue, 17 Mar 2026 22:27:24 +0100 Subject: [PATCH 3/9] use ihl to derive port bytes --- src/sniffing/headers.rs | 6 ++++-- tests/headers.rs | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/sniffing/headers.rs b/src/sniffing/headers.rs index a5b0480..2472981 100644 --- a/src/sniffing/headers.rs +++ b/src/sniffing/headers.rs @@ -52,13 +52,15 @@ pub fn sniff_raw_packets(packet: &Packet) -> SniffedPacket { let ver = packet[0] >> 4; match ver { 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; if dst_port == 53 { dns = true; } else { dns = false; }; // FIXME: hardcoded IPv4 port offset let v4 = PacketInfo::V4{ src_ip: ::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: ::try_from(&packet[16..20])?, dst_port, protocol: match packet[9] { diff --git a/tests/headers.rs b/tests/headers.rs index d7daef5..3c1b74a 100644 --- a/tests/headers.rs +++ b/tests/headers.rs @@ -115,7 +115,7 @@ fn generic_typeck() -> Result<(), Box> { // [0] IPv4 TCP 192.168.1.100:4832 → 93.184.216.34:443 assert_eq!( sniff_raw_packets(test_suite[0])?, - PacketInfo::V4 { + PacketInfo::V6 { dns: false, src_ip: [192, 168, 1, 100], src_port: 4832, dst_ip: [93, 184, 216, 34], @@ -127,7 +127,7 @@ fn generic_typeck() -> Result<(), Box> { // [1] IPv4 UDP 10.0.0.9:5353 → 224.0.0.251:5353 (mDNS) assert_eq!( sniff_raw_packets(test_suite[1])?, - PacketInfo::V4 { + PacketInfo::V6 { dns: false, src_ip: [10, 0, 0, 9], src_port: 5353, dst_ip: [224, 0, 0, 251], @@ -139,7 +139,7 @@ fn generic_typeck() -> Result<(), Box> { // [2] IPv4 UDP 10.0.0.9:1024 → 8.8.8.8:53 (DNS) assert_eq!( sniff_raw_packets(test_suite[2])?, - PacketInfo::V4 { + PacketInfo::V6 { dns: false, src_ip: [10, 0, 0, 9], src_port: 1024, dst_ip: [8, 8, 8, 8], @@ -151,7 +151,7 @@ fn generic_typeck() -> Result<(), Box> { // [3] IPv4 TCP 10.0.0.5:54321 → 10.0.0.1:80 (HTTP) assert_eq!( sniff_raw_packets(test_suite[3])?, - PacketInfo::V4 { + PacketInfo::V6 { dns: false, src_ip: [10, 0, 0, 5], src_port: 54321, dst_ip: [10, 0, 0, 1], @@ -163,7 +163,7 @@ fn generic_typeck() -> Result<(), Box> { // [4] IPv4 TCP 172.16.0.1:65535 → 172.16.0.2:8080 assert_eq!( sniff_raw_packets(test_suite[4])?, - PacketInfo::V4 { + PacketInfo::V6 { dns: false, src_ip: [172, 16, 0, 1], src_port: 65535, dst_ip: [172, 16, 0, 2], @@ -175,7 +175,7 @@ fn generic_typeck() -> Result<(), Box> { // [5] IPv4 TCP IHL=6 10.0.0.1:9090 → 10.0.0.2:22 (requires IHL-based offset) assert_eq!( sniff_raw_packets(test_suite[5])?, - PacketInfo::V4 { + PacketInfo::V6 { dns: false, src_ip: [10, 0, 0, 1], src_port: 9090, dst_ip: [10, 0, 0, 2], @@ -187,7 +187,7 @@ fn generic_typeck() -> Result<(), Box> { // [6] IPv4 ICMP (unsupported, "ports" are just ICMP body bytes) assert_eq!( sniff_raw_packets(test_suite[6])?, - PacketInfo::V4 { + PacketInfo::V6 { dns: false, src_ip: [10, 0, 0, 9], src_port: 2048, dst_ip: [10, 0, 0, 1], @@ -199,7 +199,7 @@ fn generic_typeck() -> Result<(), Box> { // [7] IPv6 TCP [::1]:4000 → [2606:4700::1]:443 assert_eq!( 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_port: 4000, dst_ip: [0x26, 0x06, 0x47, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], @@ -211,7 +211,7 @@ fn generic_typeck() -> Result<(), Box> { // [8] IPv6 UDP [fd00::9]:1234 → [fd00::1]:53 (requires fixing 17 => UDP) assert_eq!( 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_port: 1234, dst_ip: [0xFD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], @@ -223,7 +223,7 @@ fn generic_typeck() -> Result<(), Box> { // [9] IPv6 ICMPv6 (unsupported, "ports" are ICMPv6 body bytes) assert_eq!( 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_port: 32768, dst_ip: [0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], From 1a5b7da6ae20cece911c05b1384a3373239772a3 Mon Sep 17 00:00:00 2001 From: zedddie Date: Tue, 17 Mar 2026 23:15:47 +0100 Subject: [PATCH 4/9] really broken WIP for now xD --- src/sniffing/headers.rs | 52 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/sniffing/headers.rs b/src/sniffing/headers.rs index 2472981..f5b3477 100644 --- a/src/sniffing/headers.rs +++ b/src/sniffing/headers.rs @@ -1,4 +1,5 @@ use tun::Error; +use std::fmt; // Here we will recieve bytes and try to get their destanation & apply Rules for them. use crate::config::Config; @@ -9,6 +10,13 @@ pub enum Protocol { UDP, Unsupported(u8) } +type SourceV4Ip = Ipv4; +type SourceV6Ip = Ipv6; +pub enum IpVersion { + V4, + V6 +} +// type IpVersion = String; type Ipv4 = [u8; 4]; type Ipv6 = [u8; 16]; type Port = u16; @@ -34,7 +42,51 @@ pub enum PacketInfo { } } +impl fmt::Display for PacketInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.version() == "Ipv4" { + let src_ip = self.src_ipv4_ip(); + let dst_ip = self.dst_ipv4_ip(); + write!(f, "{} {}.{}.{}.{}:{} -> {}.{}.{}.{}:{} PROTO {} DNS? {}", self.version(), 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()) + } + // write!(f, "{} {}:{} -> {}:{} PROTO {} DNS? {}", self.version(), self.) + } +} + impl PacketInfo { + pub fn dns(&self) -> &bool { + match self { + PacketInfo::V4 { dns, ..} => dns, + PacketInfo::V6 { dns, ..} => dns, + } + } + pub fn dst_ipv4_ip(&self) -> &SourceV4Ip { + match self { + PacketInfo::V4 { dst_ip, .. } => dst_ip, + _ => &[0x0, 0x0, 0x0, 0x0].try_into().expect("this never should fail or even be called in the first place.") + } + } + pub fn src_ipv6_ip(&self) -> &SourceV6Ip { + match self { + PacketInfo::V6 { src_ip, .. } => src_ip, + _ => &[0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0].try_into().expect("this never should fail or even be called in the first place.") + } + } + pub fn src_ipv4_ip(&self) -> &SourceV4Ip { + match self { + PacketInfo::V4 { src_ip, .. } => src_ip, + _ => &[0x0, 0x0, 0x0, 0x0].try_into().expect("this never should fail or even be called in the first place.") + } + } + pub fn src_ipv6_ip(&self) -> &SourceV6Ip { + PacketInfo::V6.src_ip + } + pub fn version(&self) -> &IpVersion { + match self { + PacketInfo::V4 { .. }=> &IpVersion::V4, + PacketInfo::V6 { .. }=> &IpVersion::V6 + } + } pub fn protocol(&self) -> &Protocol { match self { PacketInfo::V4 { protocol, .. } => protocol, From 8887a775f5c46551f8d9ea0f2197d129008eabf1 Mon Sep 17 00:00:00 2001 From: namilsk Date: Wed, 18 Mar 2026 21:21:21 +0300 Subject: [PATCH 5/9] Written geosite protobuf parser and tests 4 it --- Cargo.lock | 190 ++++++++++++++++++- Cargo.toml | 10 +- build.rs | 16 ++ src/geoparsers/mod.rs | 1 + src/geoparsers/v2ray/mod.rs | 2 + src/geoparsers/v2ray/parsing.rs | 79 ++++++++ src/geoparsers/v2ray/proto_src/geosite.proto | 66 +++++++ src/geoparsers/v2ray/types.rs | 121 ++++++++++++ src/main.rs | 13 +- tests/v2ray_geosite.rs | 68 +++++++ 10 files changed, 548 insertions(+), 18 deletions(-) create mode 100644 build.rs create mode 100644 src/geoparsers/v2ray/mod.rs create mode 100644 src/geoparsers/v2ray/parsing.rs create mode 100644 src/geoparsers/v2ray/proto_src/geosite.proto create mode 100644 src/geoparsers/v2ray/types.rs create mode 100644 tests/v2ray_geosite.rs diff --git a/Cargo.lock b/Cargo.lock index 8b92f8c..2274e88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -449,6 +449,9 @@ name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +dependencies = [ + "serde", +] [[package]] name = "c2rust-bitfields" @@ -1251,15 +1254,6 @@ dependencies = [ "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]] name = "event-listener" version = "5.4.1" @@ -1345,6 +1339,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.9" @@ -2207,6 +2207,12 @@ dependencies = [ "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]] name = "native-tls" version = "0.2.18" @@ -2343,15 +2349,19 @@ name = "nsc" version = "0.1.0" dependencies = [ "arti-client", - "etherparse", + "bytes", "ipnet", "iptables", "maxminddb", + "prost", + "prost-build", + "prost-types", "rtnetlink", "serde", "tokio", "toml 1.0.6+spec-1.1.0", "tun", + "ureq", ] [[package]] @@ -2662,6 +2672,17 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "phf" version = "0.13.1" @@ -2912,6 +2933,57 @@ dependencies = [ "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]] name = "pwd-grp" version = "1.0.2" @@ -3146,6 +3218,20 @@ dependencies = [ "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]] name = "rsa" version = "0.9.10" @@ -3242,6 +3328,41 @@ dependencies = [ "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]] name = "rustversion" version = "1.0.22" @@ -5166,12 +5287,34 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "unty" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "url" version = "2.5.8" @@ -5363,6 +5506,24 @@ dependencies = [ "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]] name = "winapi" version = "0.3.9" @@ -5533,6 +5694,15 @@ dependencies = [ "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]] name = "windows-sys" version = "0.59.0" diff --git a/Cargo.toml b/Cargo.toml index 5d073aa..7cb4c02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,14 +4,19 @@ version = "0.1.0" edition = "2024" description = "Tun-in interface for Mesh networks like Tor/I2P with traffic-routing support (DIRECT/PROXY/BLOCK etc.)" repository = "https://codeberg.org/NamelessTeam/nsc" +build = "build.rs" +[build-dependencies] +prost-build = "0.14.3" [dependencies] arti-client = "0.40.0" -etherparse = "0.19.0" +bytes = { version = "1.11.1", features = ["serde"] } ipnet = "2.12.0" iptables = "0.6.0" maxminddb = "0.27.3" +prost = "0.14.3" +prost-types = "0.14.3" rtnetlink = "0.20.0" serde = { version = "1.0.228", features = ["derive"] } tokio = { version = "1.50.0", features = ["full"] } @@ -24,4 +29,7 @@ lto = true codegen-units = 1 opt-level = 3 +[dev-dependencies] +ureq = "2.12" + diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..bf982dd --- /dev/null +++ b/build.rs @@ -0,0 +1,16 @@ +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(()) +} diff --git a/src/geoparsers/mod.rs b/src/geoparsers/mod.rs index 43af0f3..7828b9b 100644 --- a/src/geoparsers/mod.rs +++ b/src/geoparsers/mod.rs @@ -1,2 +1,3 @@ pub mod geoip2; pub mod toml; +pub mod v2ray; \ No newline at end of file diff --git a/src/geoparsers/v2ray/mod.rs b/src/geoparsers/v2ray/mod.rs new file mode 100644 index 0000000..971be55 --- /dev/null +++ b/src/geoparsers/v2ray/mod.rs @@ -0,0 +1,2 @@ +pub mod parsing; +pub mod types; diff --git a/src/geoparsers/v2ray/parsing.rs b/src/geoparsers/v2ray/parsing.rs new file mode 100644 index 0000000..0f897bf --- /dev/null +++ b/src/geoparsers/v2ray/parsing.rs @@ -0,0 +1,79 @@ +use crate::geoparsers::v2ray::types::{Domain, GeoSite, GeoSiteList}; +use prost::bytes::Buf; +use prost::Message; +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> { + 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> { + 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 }) +} diff --git a/src/geoparsers/v2ray/proto_src/geosite.proto b/src/geoparsers/v2ray/proto_src/geosite.proto new file mode 100644 index 0000000..e6c76dd --- /dev/null +++ b/src/geoparsers/v2ray/proto_src/geosite.proto @@ -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; +} diff --git a/src/geoparsers/v2ray/types.rs b/src/geoparsers/v2ray/types.rs new file mode 100644 index 0000000..d7c0436 --- /dev/null +++ b/src/geoparsers/v2ray/types.rs @@ -0,0 +1,121 @@ +// 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, +} +/// 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, + } + /// 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 { + 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, + /// 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, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GeoIpList { + #[prost(message, repeated, tag = "1")] + pub entry: ::prost::alloc::vec::Vec, +} +#[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, + /// resource_hash instruct simplified config converter to load domain from geo file. + #[prost(bytes = "vec", tag = "3")] + pub resource_hash: ::prost::alloc::vec::Vec, + #[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, +} diff --git a/src/main.rs b/src/main.rs index 594f751..df1a31c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,12 @@ -mod routing; -mod config; -mod geoparsers; -pub mod sniffing; -mod startup; +//mod routing; +//mod config; +//mod geoparsers; +//pub mod sniffing; +//mod startup; use nsc::startup::init; -use std::io::Read; - fn main() -> Result<(), Box> { + init() } diff --git a/tests/v2ray_geosite.rs b/tests/v2ray_geosite.rs new file mode 100644 index 0000000..f4ea664 --- /dev/null +++ b/tests/v2ray_geosite.rs @@ -0,0 +1,68 @@ +use nsc::geoparsers::v2ray::parsing::GeoSiteService; +use nsc::geoparsers::v2ray::types::Domain; +use std::fs; +use std::path::PathBuf; + +fn download_geosite() -> Result> { + 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> { + 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()); +} From d0513bec6a51ee5d27cc90003945185438eca4a6 Mon Sep 17 00:00:00 2001 From: zedddie Date: Mon, 23 Mar 2026 19:11:40 +0100 Subject: [PATCH 6/9] impl Display trait for PacketInfo --- src/sniffing/headers.rs | 57 +++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/src/sniffing/headers.rs b/src/sniffing/headers.rs index f5b3477..4514e3b 100644 --- a/src/sniffing/headers.rs +++ b/src/sniffing/headers.rs @@ -12,11 +12,11 @@ pub enum Protocol { } type SourceV4Ip = Ipv4; type SourceV6Ip = Ipv6; +#[derive(PartialEq, Debug)] pub enum IpVersion { V4, V6 } -// type IpVersion = String; type Ipv4 = [u8; 4]; type Ipv6 = [u8; 16]; type Port = u16; @@ -44,12 +44,16 @@ pub enum PacketInfo { impl fmt::Display for PacketInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.version() == "Ipv4" { - let src_ip = self.src_ipv4_ip(); - let dst_ip = self.dst_ipv4_ip(); - write!(f, "{} {}.{}.{}.{}:{} -> {}.{}.{}.{}:{} PROTO {} DNS? {}", self.version(), 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()) + 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? {:?}", self.version(), 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 + write!(f, "{:?} {}:{}:{}:{}:{}:{}:{}:{} port:{} -> {}:{}:{}:{}:{}:{}:{}:{} port:{} {:?} is dns? {:?}", self.version(), 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()) } - // write!(f, "{} {}:{} -> {}:{} PROTO {} DNS? {}", self.version(), self.) } } @@ -60,26 +64,41 @@ impl PacketInfo { PacketInfo::V6 { dns, ..} => dns, } } - pub fn dst_ipv4_ip(&self) -> &SourceV4Ip { + pub fn src_ipv6_ip(&self) -> Option<&SourceV6Ip> { match self { - PacketInfo::V4 { dst_ip, .. } => dst_ip, - _ => &[0x0, 0x0, 0x0, 0x0].try_into().expect("this never should fail or even be called in the first place.") + PacketInfo::V6 { src_ip, .. } => Some(src_ip), + _ => None } } - pub fn src_ipv6_ip(&self) -> &SourceV6Ip { + pub fn dst_ipv6_ip(&self) -> Option<&SourceV6Ip> { match self { - PacketInfo::V6 { src_ip, .. } => src_ip, - _ => &[0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0].try_into().expect("this never should fail or even be called in the first place.") + PacketInfo::V6 { dst_ip, .. } => Some(dst_ip), + _ => None } } - pub fn src_ipv4_ip(&self) -> &SourceV4Ip { + pub fn src_ipv4_ip(&self) -> Option<&SourceV4Ip> { match self { - PacketInfo::V4 { src_ip, .. } => src_ip, - _ => &[0x0, 0x0, 0x0, 0x0].try_into().expect("this never should fail or even be called in the first place.") + PacketInfo::V4 { src_ip, .. } => Some(src_ip), + _ => None, } } - pub fn src_ipv6_ip(&self) -> &SourceV6Ip { - PacketInfo::V6.src_ip + 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 { @@ -123,7 +142,7 @@ pub fn sniff_raw_packets(packet: &Packet) -> SniffedPacket { dns }; if !matches!(v4.protocol(), Protocol::Unsupported(_)) { - println!("{v4:?}"); + println!("{v4}"); } else { // TODO: make --debug option which will include this diagnostic, for general use this // should be off @@ -148,7 +167,7 @@ pub fn sniff_raw_packets(packet: &Packet) -> SniffedPacket { dns }; if !matches!(v6.protocol(), Protocol::Unsupported(_)) { - println!("{v6:?}"); + println!("{v6}"); } else { // TODO: make --debug option which will include this diagnostic, for general use this // should be off From eedb2c5d02fa5b124c9d859c973939880a84cb76 Mon Sep 17 00:00:00 2001 From: zedddie Date: Mon, 23 Mar 2026 20:23:41 +0100 Subject: [PATCH 7/9] wip&fixme --- src/sniffing/headers.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/sniffing/headers.rs b/src/sniffing/headers.rs index 4514e3b..d49d402 100644 --- a/src/sniffing/headers.rs +++ b/src/sniffing/headers.rs @@ -18,7 +18,7 @@ pub enum IpVersion { V6 } type Ipv4 = [u8; 4]; -type Ipv6 = [u8; 16]; +type Ipv6 = [u16; 8]; type Port = u16; #[derive(Debug, PartialEq)] pub enum PacketInfo { @@ -128,7 +128,6 @@ pub fn sniff_raw_packets(packet: &Packet) -> SniffedPacket { let dst_port = Port::from_be_bytes([packet[ihl+2], packet[ihl+3]]); let dns; if dst_port == 53 { dns = true; } else { dns = false; }; - // FIXME: hardcoded IPv4 port offset let v4 = PacketInfo::V4{ src_ip: ::try_from(&packet[12..16])?, src_port: Port::from_be_bytes([packet[ihl], packet[ihl+1]]), @@ -151,11 +150,14 @@ pub fn sniff_raw_packets(packet: &Packet) -> SniffedPacket { Ok(v4) }, 6 => { - let dst_port = Port::from_be_bytes([packet[22], packet[23]]); + // FIXME: fix ipv6 type representation to u16 paired u8 + let src_ip_bytes = &packet[8..24]; + let src_ip = src_ip_bytes.chunks(2).map(|b| u16::from_be_bytes(b[0], b[1])); + let dst_port = Port::from_be_bytes([packet[42], packet[43]]); let dns; if dst_port == 53 { dns = true; } else { dns = false; }; let v6 = PacketInfo::V6{ - src_ip: ::try_from(&packet[8..24])?, + src_ip, src_port: Port::from_be_bytes([packet[40], packet[41]]), dst_ip: ::try_from(&packet[24..40])?, dst_port, From f74089a3651b8f26e7bda24afe9fd92794eea33c Mon Sep 17 00:00:00 2001 From: zedddie Date: Mon, 23 Mar 2026 20:58:56 +0100 Subject: [PATCH 8/9] fix ipv6 src&dst ip representation in PacketInfo --- src/sniffing/headers.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/sniffing/headers.rs b/src/sniffing/headers.rs index d49d402..40a311c 100644 --- a/src/sniffing/headers.rs +++ b/src/sniffing/headers.rs @@ -47,12 +47,12 @@ impl fmt::Display for PacketInfo { 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? {:?}", self.version(), 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()) + 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 - write!(f, "{:?} {}:{}:{}:{}:{}:{}:{}:{} port:{} -> {}:{}:{}:{}:{}:{}:{}:{} port:{} {:?} is dns? {:?}", self.version(), 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()) + // 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()) } } } @@ -150,16 +150,17 @@ pub fn sniff_raw_packets(packet: &Packet) -> SniffedPacket { Ok(v4) }, 6 => { - // FIXME: fix ipv6 type representation to u16 paired u8 - let src_ip_bytes = &packet[8..24]; - let src_ip = src_ip_bytes.chunks(2).map(|b| u16::from_be_bytes(b[0], b[1])); + // 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; if dst_port == 53 { dns = true; } else { dns = false; }; let v6 = PacketInfo::V6{ src_ip, src_port: Port::from_be_bytes([packet[40], packet[41]]), - dst_ip: ::try_from(&packet[24..40])?, + dst_ip, dst_port, protocol: match packet[6] { 6 => Protocol::TCP, From b9399611816303da5a06dcfb30a6ab51f70ed4c1 Mon Sep 17 00:00:00 2001 From: namilsk Date: Tue, 24 Mar 2026 00:04:23 +0300 Subject: [PATCH 9/9] Fmt & Added CI --- .woodpecker.yaml | 34 ++++++++ Cargo.lock | 4 - Cargo.toml | 1 - build.rs | 6 +- src/config.rs | 16 ++-- src/geoparsers/geoip2.rs | 4 +- src/geoparsers/mod.rs | 2 +- src/geoparsers/v2ray/parsing.rs | 2 +- src/geoparsers/v2ray/types.rs | 12 +-- src/lib.rs | 2 +- src/main.rs | 7 +- src/sniffing/headers.rs | 142 +++++++++++++++++++++----------- src/sniffing/metadata.rs | 1 + src/startup.rs | 4 +- tests/headers.rs | 32 ++++--- tests/v2ray_geosite.rs | 16 ++-- 16 files changed, 181 insertions(+), 104 deletions(-) create mode 100644 .woodpecker.yaml diff --git a/.woodpecker.yaml b/.woodpecker.yaml new file mode 100644 index 0000000..e22e3fa --- /dev/null +++ b/.woodpecker.yaml @@ -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 ] diff --git a/Cargo.lock b/Cargo.lock index 2274e88..49d6b96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -449,9 +449,6 @@ name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -dependencies = [ - "serde", -] [[package]] name = "c2rust-bitfields" @@ -2349,7 +2346,6 @@ name = "nsc" version = "0.1.0" dependencies = [ "arti-client", - "bytes", "ipnet", "iptables", "maxminddb", diff --git a/Cargo.toml b/Cargo.toml index 7cb4c02..7121300 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,6 @@ prost-build = "0.14.3" [dependencies] arti-client = "0.40.0" -bytes = { version = "1.11.1", features = ["serde"] } ipnet = "2.12.0" iptables = "0.6.0" maxminddb = "0.27.3" diff --git a/build.rs b/build.rs index bf982dd..99e430f 100644 --- a/build.rs +++ b/build.rs @@ -2,13 +2,11 @@ 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/geosite.proto"], &["src/geoparsers/v2ray/proto_src/"], )?; diff --git a/src/config.rs b/src/config.rs index 846a1b5..a5739ec 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,14 +17,14 @@ pub struct Config { pub mode: RunTypes, } -// TODO: Think how to add other anonymisers -// Like VPN on localhost:10808 -// it can be like: -// ```toml +// 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" # ... +// name = "VPN" +// addr = "127.0.0.1:10808" +// type = "SOCKS5" # ... // ``` impl Default for Config { @@ -39,5 +39,3 @@ impl Default for Config { } } } - - diff --git a/src/geoparsers/geoip2.rs b/src/geoparsers/geoip2.rs index 9f2cc8b..1fd6ab1 100644 --- a/src/geoparsers/geoip2.rs +++ b/src/geoparsers/geoip2.rs @@ -3,8 +3,8 @@ use maxminddb::{Reader, geoip2}; use serde::Deserialize; use std::net::IpAddr; -// For now only MMDB because i cant found .proto schemes of -// V2Ray GeoSite.dat :(( +// 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 diff --git a/src/geoparsers/mod.rs b/src/geoparsers/mod.rs index 7828b9b..c3cfece 100644 --- a/src/geoparsers/mod.rs +++ b/src/geoparsers/mod.rs @@ -1,3 +1,3 @@ pub mod geoip2; pub mod toml; -pub mod v2ray; \ No newline at end of file +pub mod v2ray; diff --git a/src/geoparsers/v2ray/parsing.rs b/src/geoparsers/v2ray/parsing.rs index 0f897bf..4f0bbba 100644 --- a/src/geoparsers/v2ray/parsing.rs +++ b/src/geoparsers/v2ray/parsing.rs @@ -1,6 +1,6 @@ use crate::geoparsers::v2ray::types::{Domain, GeoSite, GeoSiteList}; -use prost::bytes::Buf; use prost::Message; +use prost::bytes::Buf; use std::fs; pub struct GeoSiteService { diff --git a/src/geoparsers/v2ray/types.rs b/src/geoparsers/v2ray/types.rs index d7c0436..b82fbbf 100644 --- a/src/geoparsers/v2ray/types.rs +++ b/src/geoparsers/v2ray/types.rs @@ -33,17 +33,7 @@ pub mod domain { } } /// Type of domain value. - #[derive( - Clone, - Copy, - Debug, - PartialEq, - Eq, - Hash, - PartialOrd, - Ord, - ::prost::Enumeration - )] + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum Type { /// The value is used as is. diff --git a/src/lib.rs b/src/lib.rs index 29905b0..2a02068 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -pub mod sniffing; pub mod config; pub mod geoparsers; +pub mod sniffing; pub mod startup; diff --git a/src/main.rs b/src/main.rs index df1a31c..c2b6707 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,11 @@ //mod routing; -//mod config; -//mod geoparsers; -//pub mod sniffing; +mod config; +mod geoparsers; +pub mod sniffing; //mod startup; use nsc::startup::init; fn main() -> Result<(), Box> { - init() } diff --git a/src/sniffing/headers.rs b/src/sniffing/headers.rs index 40a311c..1db0da6 100644 --- a/src/sniffing/headers.rs +++ b/src/sniffing/headers.rs @@ -1,5 +1,5 @@ -use tun::Error; use std::fmt; +use tun::Error; // Here we will recieve bytes and try to get their destanation & apply Rules for them. use crate::config::Config; @@ -8,14 +8,14 @@ use crate::config::Config; pub enum Protocol { TCP, UDP, - Unsupported(u8) + Unsupported(u8), } type SourceV4Ip = Ipv4; type SourceV6Ip = Ipv6; #[derive(PartialEq, Debug)] pub enum IpVersion { V4, - V6 + V6, } type Ipv4 = [u8; 4]; type Ipv6 = [u16; 8]; @@ -29,7 +29,7 @@ pub enum PacketInfo { dst_ip: Ipv4, dst_port: Port, protocol: Protocol, - dns: bool + dns: bool, }, // V6 { @@ -38,21 +38,59 @@ pub enum PacketInfo { dst_ip: Ipv6, dst_port: Port, 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()) + 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(); + 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()) + 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() + ) } } } @@ -60,20 +98,20 @@ impl fmt::Display for PacketInfo { impl PacketInfo { pub fn dns(&self) -> &bool { match self { - PacketInfo::V4 { dns, ..} => dns, - PacketInfo::V6 { dns, ..} => dns, + 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 + _ => None, } } pub fn dst_ipv6_ip(&self) -> Option<&SourceV6Ip> { match self { PacketInfo::V6 { dst_ip, .. } => Some(dst_ip), - _ => None + _ => None, } } pub fn src_ipv4_ip(&self) -> Option<&SourceV4Ip> { @@ -85,25 +123,25 @@ impl PacketInfo { pub fn dst_ipv4_ip(&self) -> Option<&SourceV4Ip> { match self { PacketInfo::V4 { dst_ip, .. } => Some(dst_ip), - _ => None + _ => None, } } pub fn src_port(&self) -> &Port { match self { PacketInfo::V4 { src_port, .. } => src_port, - PacketInfo::V6 { 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 + PacketInfo::V6 { dst_port, .. } => dst_port, } } pub fn version(&self) -> &IpVersion { match self { - PacketInfo::V4 { .. }=> &IpVersion::V4, - PacketInfo::V6 { .. }=> &IpVersion::V6 + PacketInfo::V4 { .. } => &IpVersion::V4, + PacketInfo::V6 { .. } => &IpVersion::V6, } } pub fn protocol(&self) -> &Protocol { @@ -125,39 +163,51 @@ pub fn sniff_raw_packets(packet: &Packet) -> SniffedPacket { 4 => { // 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 dst_port = Port::from_be_bytes([packet[ihl + 2], packet[ihl + 3]]); let dns; - if dst_port == 53 { dns = true; } else { dns = false; }; - let v4 = PacketInfo::V4{ + if dst_port == 53 { + dns = true; + } else { + dns = false; + }; + let v4 = PacketInfo::V4 { src_ip: ::try_from(&packet[12..16])?, - src_port: Port::from_be_bytes([packet[ihl], packet[ihl+1]]), + src_port: Port::from_be_bytes([packet[ihl], packet[ihl + 1]]), dst_ip: ::try_from(&packet[16..20])?, dst_port, protocol: match packet[9] { 6 => Protocol::TCP, 17 => Protocol::UDP, - p => Protocol::Unsupported(p) + p => Protocol::Unsupported(p), }, - dns + dns, }; - if !matches!(v4.protocol(), Protocol::Unsupported(_)) { - println!("{v4}"); - } else { - // TODO: make --debug option which will include this diagnostic, for general use this + if !matches!(v4.protocol(), Protocol::Unsupported(_)) { + println!("{v4}"); + } else { + // TODO: make --debug option which will include this diagnostic, for general use this // should be off - // println!("oppsie unsupported protocol: {:?}", v4.protocol()); + // println!("oppsie unsupported protocol: {:?}", v4.protocol()); } Ok(v4) - }, + } 6 => { // 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 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; - if dst_port == 53 { dns = true; } else { dns = false; }; - let v6 = PacketInfo::V6{ + if dst_port == 53 { + dns = true; + } else { + dns = false; + }; + let v6 = PacketInfo::V6 { src_ip, src_port: Port::from_be_bytes([packet[40], packet[41]]), dst_ip, @@ -165,22 +215,20 @@ pub fn sniff_raw_packets(packet: &Packet) -> SniffedPacket { protocol: match packet[6] { 6 => Protocol::TCP, 17 => Protocol::UDP, - p => Protocol::Unsupported(p) + p => Protocol::Unsupported(p), }, - dns + dns, }; - if !matches!(v6.protocol(), Protocol::Unsupported(_)) { - println!("{v6}"); - } else { - // TODO: make --debug option which will include this diagnostic, for general use this + if !matches!(v6.protocol(), Protocol::Unsupported(_)) { + println!("{v6}"); + } else { + // TODO: make --debug option which will include this diagnostic, for general use this // should be off - // println!("oppsie unsupported protocol: {:?}", v6.protocol()); + // println!("oppsie unsupported protocol: {:?}", v6.protocol()); } Ok(v6) - }, - ver => { - Err(format!("unsuppiorted ver: {ver}").into()) } + ver => Err(format!("unsuppiorted ver: {ver}").into()), } } diff --git a/src/sniffing/metadata.rs b/src/sniffing/metadata.rs index e69de29..8b13789 100644 --- a/src/sniffing/metadata.rs +++ b/src/sniffing/metadata.rs @@ -0,0 +1 @@ + diff --git a/src/startup.rs b/src/startup.rs index 10a8441..3a1a09a 100644 --- a/src/startup.rs +++ b/src/startup.rs @@ -1,7 +1,7 @@ // 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::sniff_raw_packets; +use std::io::Read; pub fn init() -> Result<(), Box> { let mut config = tun::Configuration::default(); config diff --git a/tests/headers.rs b/tests/headers.rs index 3c1b74a..ff51111 100644 --- a/tests/headers.rs +++ b/tests/headers.rs @@ -1,8 +1,8 @@ use nsc::sniffing::*; use crate::headers::Protocol; -use nsc::sniffing::headers::sniff_raw_packets; use nsc::sniffing::headers::PacketInfo; +use nsc::sniffing::headers::sniff_raw_packets; #[test] fn generic_typeck() -> Result<(), Box> { @@ -115,7 +115,8 @@ fn generic_typeck() -> Result<(), Box> { // [0] IPv4 TCP 192.168.1.100:4832 → 93.184.216.34:443 assert_eq!( sniff_raw_packets(test_suite[0])?, - PacketInfo::V6 { dns: false, + PacketInfo::V6 { + dns: false, src_ip: [192, 168, 1, 100], src_port: 4832, dst_ip: [93, 184, 216, 34], @@ -127,7 +128,8 @@ fn generic_typeck() -> Result<(), Box> { // [1] IPv4 UDP 10.0.0.9:5353 → 224.0.0.251:5353 (mDNS) assert_eq!( sniff_raw_packets(test_suite[1])?, - PacketInfo::V6 { dns: false, + PacketInfo::V6 { + dns: false, src_ip: [10, 0, 0, 9], src_port: 5353, dst_ip: [224, 0, 0, 251], @@ -139,7 +141,8 @@ fn generic_typeck() -> Result<(), Box> { // [2] IPv4 UDP 10.0.0.9:1024 → 8.8.8.8:53 (DNS) assert_eq!( sniff_raw_packets(test_suite[2])?, - PacketInfo::V6 { dns: false, + PacketInfo::V6 { + dns: false, src_ip: [10, 0, 0, 9], src_port: 1024, dst_ip: [8, 8, 8, 8], @@ -151,7 +154,8 @@ fn generic_typeck() -> Result<(), Box> { // [3] IPv4 TCP 10.0.0.5:54321 → 10.0.0.1:80 (HTTP) assert_eq!( sniff_raw_packets(test_suite[3])?, - PacketInfo::V6 { dns: false, + PacketInfo::V6 { + dns: false, src_ip: [10, 0, 0, 5], src_port: 54321, dst_ip: [10, 0, 0, 1], @@ -163,7 +167,8 @@ fn generic_typeck() -> Result<(), Box> { // [4] IPv4 TCP 172.16.0.1:65535 → 172.16.0.2:8080 assert_eq!( sniff_raw_packets(test_suite[4])?, - PacketInfo::V6 { dns: false, + PacketInfo::V6 { + dns: false, src_ip: [172, 16, 0, 1], src_port: 65535, dst_ip: [172, 16, 0, 2], @@ -175,7 +180,8 @@ fn generic_typeck() -> Result<(), Box> { // [5] IPv4 TCP IHL=6 10.0.0.1:9090 → 10.0.0.2:22 (requires IHL-based offset) assert_eq!( sniff_raw_packets(test_suite[5])?, - PacketInfo::V6 { dns: false, + PacketInfo::V6 { + dns: false, src_ip: [10, 0, 0, 1], src_port: 9090, dst_ip: [10, 0, 0, 2], @@ -187,7 +193,8 @@ fn generic_typeck() -> Result<(), Box> { // [6] IPv4 ICMP (unsupported, "ports" are just ICMP body bytes) assert_eq!( sniff_raw_packets(test_suite[6])?, - PacketInfo::V6 { dns: false, + PacketInfo::V6 { + dns: false, src_ip: [10, 0, 0, 9], src_port: 2048, dst_ip: [10, 0, 0, 1], @@ -199,7 +206,8 @@ fn generic_typeck() -> Result<(), Box> { // [7] IPv6 TCP [::1]:4000 → [2606:4700::1]:443 assert_eq!( sniff_raw_packets(test_suite[7])?, - PacketInfo::V6 { dns: false, + PacketInfo::V6 { + dns: false, src_ip: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], src_port: 4000, dst_ip: [0x26, 0x06, 0x47, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], @@ -211,7 +219,8 @@ fn generic_typeck() -> Result<(), Box> { // [8] IPv6 UDP [fd00::9]:1234 → [fd00::1]:53 (requires fixing 17 => UDP) assert_eq!( sniff_raw_packets(test_suite[8])?, - PacketInfo::V6 { dns: false, + PacketInfo::V6 { + dns: false, src_ip: [0xFD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9], src_port: 1234, dst_ip: [0xFD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], @@ -223,7 +232,8 @@ fn generic_typeck() -> Result<(), Box> { // [9] IPv6 ICMPv6 (unsupported, "ports" are ICMPv6 body bytes) assert_eq!( sniff_raw_packets(test_suite[9])?, - PacketInfo::V6 { dns: false, + PacketInfo::V6 { + dns: false, src_ip: [0xFE, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], src_port: 32768, dst_ip: [0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], diff --git a/tests/v2ray_geosite.rs b/tests/v2ray_geosite.rs index f4ea664..d400839 100644 --- a/tests/v2ray_geosite.rs +++ b/tests/v2ray_geosite.rs @@ -30,7 +30,11 @@ fn get_geosite_service() -> Result> { #[test] fn geosite_service_creation() { let service = get_geosite_service(); - assert!(service.is_ok(), "Failed to create GeoSiteService: {:?}", service.err()); + assert!( + service.is_ok(), + "Failed to create GeoSiteService: {:?}", + service.err() + ); } #[test] @@ -51,7 +55,10 @@ fn lookup_nonexistent_domain() { }; let result = service.lookup(domain.value.as_str()); - assert!(result.is_none(), "Should return none for not existing domain"); + assert!( + result.is_none(), + "Should return none for not existing domain" + ); println!("{:?}", result); } @@ -59,10 +66,7 @@ fn lookup_nonexistent_domain() { fn geosite_list_not_empty() { let service = get_geosite_service().expect("Failed to create service"); - assert!( - !service.is_empty(), - "GeoSiteList should not be empty" - ); + assert!(!service.is_empty(), "GeoSiteList should not be empty"); println!("Loaded {} GeoSite entries", service.len()); }