diff --git a/.woodpecker.yaml b/.woodpecker.yaml deleted file mode 100644 index e22e3fa..0000000 --- a/.woodpecker.yaml +++ /dev/null @@ -1,34 +0,0 @@ -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 49d6b96..8b92f8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1251,6 +1251,15 @@ 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" @@ -1336,12 +1345,6 @@ 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" @@ -2204,12 +2207,6 @@ 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" @@ -2346,18 +2343,15 @@ name = "nsc" version = "0.1.0" dependencies = [ "arti-client", + "etherparse", "ipnet", "iptables", "maxminddb", - "prost", - "prost-build", - "prost-types", "rtnetlink", "serde", "tokio", "toml 1.0.6+spec-1.1.0", "tun", - "ureq", ] [[package]] @@ -2668,17 +2662,6 @@ 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" @@ -2929,57 +2912,6 @@ 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" @@ -3214,20 +3146,6 @@ 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" @@ -3324,41 +3242,6 @@ 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" @@ -5283,34 +5166,12 @@ 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" @@ -5502,24 +5363,6 @@ 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" @@ -5690,15 +5533,6 @@ 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 7121300..5d073aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,18 +4,14 @@ 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" 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"] } @@ -28,7 +24,4 @@ lto = true codegen-units = 1 opt-level = 3 -[dev-dependencies] -ureq = "2.12" - diff --git a/README.md b/README.md index 6826975..815977b 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,14 @@ 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 (mmdb for now) thingies ( -3. [ ] add v2ray protocols buffer support (geo{ip,site}.dat) +2. [ ] parse geoip2 thingies 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 (via proxies like 127.0.0.1:10808) +6. [ ] custom Tor/i2p profiles/Config presets mayber???? ### 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 +4. [ ] leave references/helpful comments on parts when code isn't obvious from the first glance \ No newline at end of file diff --git a/build.rs b/build.rs deleted file mode 100644 index 99e430f..0000000 --- a/build.rs +++ /dev/null @@ -1,14 +0,0 @@ -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/config.rs b/src/config.rs index a5739ec..6552a65 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,7 +9,7 @@ pub enum RunTypes { #[derive(Serialize, Deserialize)] pub struct Config { - /// Paths to `geosite.dat', `geolite2.mmdb` + /// Paths to v2ray `geosite.dat', `geoip.dat` pub geo_files: [String; 2], /// Routing settings similar to v2ray pub routing: String, @@ -17,21 +17,11 @@ 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/geolite2.mmdb"), + String::from("/etc/nsc/data/geoip.dat"), String::from("/etc/nsc/data/geosite.dat"), ], routing: String::from("/etc/nsc/routing.toml"), @@ -39,3 +29,5 @@ impl Default for Config { } } } + + diff --git a/src/geoparsers/geoip2.rs b/src/geoparsers/geoip2.rs index 1fd6ab1..9323280 100644 --- a/src/geoparsers/geoip2.rs +++ b/src/geoparsers/geoip2.rs @@ -1,61 +1,42 @@ +use ipnet::IpNet; use crate::config::Config; -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 :(( -// TODO: V2Ray protobuf parsing && Test 4 ts - -/// Interface enum for `dst_addr` info -#[derive(Debug, Deserialize)] +/// Enum for declaring GeoSite/IP routing pub enum RouteType { /// GeoSite MMDB type, like `category-ads-all` GeoSite(String), - /// Result with GeoCode like "RU" - GeoIp(String), - // String because enum will used as interface in result of `route_packet`. + /// Subnet + GeoIp(IpNet), } /// Routing actions -#[derive(Debug, Deserialize)] pub enum RouteAction { - #[serde(alias = "block")] Block, - #[serde(alias = "proxy")] Proxy, - #[serde(alias = "direct")] Direct, } -pub type Rules = Vec; +type Rules = Vec; -/// Type for deserializing the routing rules like: -#[derive(serde::Deserialize)] +/// Type for declaring the routing rules like: +/// ```toml +/// [rule] +/// action = enum RouteAction +/// target = enum RouteType +/// +/// [rule] +/// target = "geosite:category-ads-all" +/// action = "block" +/// ``` pub struct Rule { pub target: RouteType, pub action: RouteAction, } -pub struct GeoIpService { - reader: Reader>, +pub fn parse_ruleset(config: Config) -> Result> { + let reader = maxminddb::Reader::open_readfile(config.geo_files[0].clone())?; + + // Ok(()) + todo!(); } -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 c3cfece..9f95b8f 100644 --- a/src/geoparsers/mod.rs +++ b/src/geoparsers/mod.rs @@ -1,3 +1 @@ -pub mod geoip2; -pub mod toml; -pub mod v2ray; +mod geoip2; diff --git a/src/geoparsers/toml.rs b/src/geoparsers/toml.rs deleted file mode 100644 index 3638aa7..0000000 --- a/src/geoparsers/toml.rs +++ /dev/null @@ -1,15 +0,0 @@ -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/geoparsers/v2ray/mod.rs b/src/geoparsers/v2ray/mod.rs deleted file mode 100644 index 971be55..0000000 --- a/src/geoparsers/v2ray/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod parsing; -pub mod types; diff --git a/src/geoparsers/v2ray/parsing.rs b/src/geoparsers/v2ray/parsing.rs deleted file mode 100644 index 4f0bbba..0000000 --- a/src/geoparsers/v2ray/parsing.rs +++ /dev/null @@ -1,79 +0,0 @@ -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> { - 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 deleted file mode 100644 index e6c76dd..0000000 --- a/src/geoparsers/v2ray/proto_src/geosite.proto +++ /dev/null @@ -1,66 +0,0 @@ -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 deleted file mode 100644 index b82fbbf..0000000 --- a/src/geoparsers/v2ray/types.rs +++ /dev/null @@ -1,111 +0,0 @@ -// 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/lib.rs b/src/lib.rs index 2a02068..29905b0 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 c2b6707..d90ae5d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,13 @@ -//mod routing; -mod config; -mod geoparsers; -pub mod sniffing; -//mod startup; +// mod config; +// mod geoparsers; +// pub mod sniffing; +// mod startup; use nsc::startup::init; -fn main() -> Result<(), Box> { +use std::io::Read; + +fn main() -> Result<(), Box +>{ init() } diff --git a/src/routing.rs b/src/routing.rs index 21e355b..e69de29 100644 --- a/src/routing.rs +++ b/src/routing.rs @@ -1,9 +0,0 @@ -use crate::geoparsers::geoip2::GeoIpService; - -struct Router { - geoip: GeoIpService, - // geosite: GeoSiteService - // sniffer: Sniffer -} - - diff --git a/src/sniffing/headers.rs b/src/sniffing/headers.rs index 1db0da6..a5b0480 100644 --- a/src/sniffing/headers.rs +++ b/src/sniffing/headers.rs @@ -1,4 +1,3 @@ -use std::fmt; use tun::Error; // Here we will recieve bytes and try to get their destanation & apply Rules for them. @@ -8,17 +7,10 @@ use crate::config::Config; pub enum Protocol { TCP, UDP, - Unsupported(u8), -} -type SourceV4Ip = Ipv4; -type SourceV6Ip = Ipv6; -#[derive(PartialEq, Debug)] -pub enum IpVersion { - V4, - V6, + Unsupported(u8) } type Ipv4 = [u8; 4]; -type Ipv6 = [u16; 8]; +type Ipv6 = [u8; 16]; type Port = u16; #[derive(Debug, PartialEq)] pub enum PacketInfo { @@ -29,7 +21,7 @@ pub enum PacketInfo { dst_ip: Ipv4, dst_port: Port, protocol: Protocol, - dns: bool, + dns: bool }, // V6 { @@ -38,112 +30,11 @@ pub enum PacketInfo { dst_ip: Ipv6, dst_port: Port, protocol: Protocol, - 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() - ) - } + dns: bool } } 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 { match self { PacketInfo::V4 { protocol, .. } => protocol, @@ -161,74 +52,59 @@ pub fn sniff_raw_packets(packet: &Packet) -> SniffedPacket { let ver = packet[0] >> 4; match ver { 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[22], packet[23]]); let dns; - if dst_port == 53 { - dns = true; - } else { - dns = false; - }; - let v4 = PacketInfo::V4 { + 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]]), + src_port: Port::from_be_bytes([packet[20], packet[21]]), 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 dst_port = Port::from_be_bytes([packet[42], packet[43]]); + let dst_port = Port::from_be_bytes([packet[22], packet[23]]); let dns; - if dst_port == 53 { - dns = true; - } else { - dns = false; - }; - let v6 = PacketInfo::V6 { - src_ip, + if dst_port == 53 { dns = true; } else { dns = false; }; + let v6 = PacketInfo::V6{ + src_ip: ::try_from(&packet[8..24])?, src_port: Port::from_be_bytes([packet[40], packet[41]]), - dst_ip, + dst_ip: ::try_from(&packet[24..40])?, dst_port, 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 8b13789..e69de29 100644 --- a/src/sniffing/metadata.rs +++ b/src/sniffing/metadata.rs @@ -1 +0,0 @@ - diff --git a/src/startup.rs b/src/startup.rs index 3a1a09a..10a8441 100644 --- a/src/startup.rs +++ b/src/startup.rs @@ -1,7 +1,7 @@ // Here we iniitialize systems crucial for nsc -use crate::sniffing::headers::Protocol; -use crate::sniffing::headers::sniff_raw_packets; use std::io::Read; +use crate::sniffing::headers::sniff_raw_packets; +use crate::sniffing::headers::Protocol; pub fn init() -> Result<(), Box> { let mut config = tun::Configuration::default(); config diff --git a/tests/headers.rs b/tests/headers.rs index ff51111..d7daef5 100644 --- a/tests/headers.rs +++ b/tests/headers.rs @@ -1,8 +1,8 @@ use nsc::sniffing::*; use crate::headers::Protocol; -use nsc::sniffing::headers::PacketInfo; use nsc::sniffing::headers::sniff_raw_packets; +use nsc::sniffing::headers::PacketInfo; #[test] fn generic_typeck() -> Result<(), Box> { @@ -115,8 +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::V6 { - dns: false, + PacketInfo::V4 { src_ip: [192, 168, 1, 100], src_port: 4832, dst_ip: [93, 184, 216, 34], @@ -128,8 +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::V6 { - dns: false, + PacketInfo::V4 { src_ip: [10, 0, 0, 9], src_port: 5353, dst_ip: [224, 0, 0, 251], @@ -141,8 +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::V6 { - dns: false, + PacketInfo::V4 { src_ip: [10, 0, 0, 9], src_port: 1024, dst_ip: [8, 8, 8, 8], @@ -154,8 +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::V6 { - dns: false, + PacketInfo::V4 { src_ip: [10, 0, 0, 5], src_port: 54321, dst_ip: [10, 0, 0, 1], @@ -167,8 +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::V6 { - dns: false, + PacketInfo::V4 { src_ip: [172, 16, 0, 1], src_port: 65535, dst_ip: [172, 16, 0, 2], @@ -180,8 +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::V6 { - dns: false, + PacketInfo::V4 { src_ip: [10, 0, 0, 1], src_port: 9090, dst_ip: [10, 0, 0, 2], @@ -193,8 +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::V6 { - dns: false, + PacketInfo::V4 { src_ip: [10, 0, 0, 9], src_port: 2048, dst_ip: [10, 0, 0, 1], @@ -207,7 +200,6 @@ fn generic_typeck() -> Result<(), Box> { assert_eq!( sniff_raw_packets(test_suite[7])?, 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], @@ -220,7 +212,6 @@ fn generic_typeck() -> Result<(), Box> { assert_eq!( sniff_raw_packets(test_suite[8])?, 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], @@ -233,7 +224,6 @@ fn generic_typeck() -> Result<(), Box> { assert_eq!( sniff_raw_packets(test_suite[9])?, 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 deleted file mode 100644 index d400839..0000000 --- a/tests/v2ray_geosite.rs +++ /dev/null @@ -1,72 +0,0 @@ -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()); -}