summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock190
-rw-r--r--Cargo.toml10
-rw-r--r--build.rs16
-rw-r--r--src/geoparsers/mod.rs1
-rw-r--r--src/geoparsers/v2ray/mod.rs2
-rw-r--r--src/geoparsers/v2ray/parsing.rs79
-rw-r--r--src/geoparsers/v2ray/proto_src/geosite.proto66
-rw-r--r--src/geoparsers/v2ray/types.rs121
-rw-r--r--src/main.rs13
-rw-r--r--tests/v2ray_geosite.rs68
10 files changed, 548 insertions, 18 deletions
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"
@@ -1252,15 +1255,6 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1346,6 +1340,12 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2208,6 +2208,12 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -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]]
@@ -2663,6 +2673,17 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2913,6 +2934,57 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3147,6 +3219,20 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3243,6 +3329,41 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5167,12 +5288,34 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5364,6 +5507,24 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5535,6 +5696,15 @@ dependencies = [
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
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<Self, Box<dyn std::error::Error>> {
+ let bytes = fs::read(path)?;
+ let geosite_list = decode_geosite_stream(&bytes)?;
+
+ Ok(Self {
+ index: geosite_list,
+ })
+ }
+
+ // Idk but i think it can work
+ pub fn lookup(&self, value: &str) -> Option<&GeoSite> {
+ self.index
+ .entry
+ .iter()
+ .find(|site| site.domain.iter().any(|d| d.value == value))
+ }
+
+ /// Returns the number of GeoSite entries in the list
+ pub fn len(&self) -> usize {
+ self.index.entry.len()
+ }
+
+ /// Returns true if the GeoSite list is empty
+ pub fn is_empty(&self) -> bool {
+ self.index.entry.is_empty()
+ }
+}
+
+/// Decode a stream of length-delimited GeoSite messages
+/// `geosite.dat` ts is not one protobuf-message, stream of length-delimited messages
+/// so we need ts helper
+fn decode_geosite_stream(bytes: &[u8]) -> Result<GeoSiteList, Box<dyn std::error::Error>> {
+ let mut buf = bytes;
+ let mut entries = Vec::new();
+
+ while buf.has_remaining() {
+ // Read tag (0x0a field 1, wire type 2)
+ let tag = buf.get_u8();
+ if tag != 0x0a {
+ return Err(format!("Unexpected tag: {:#04x}", tag).into());
+ }
+ // varint
+ let mut len = 0usize;
+ let mut shift = 0;
+ loop {
+ if !buf.has_remaining() {
+ return Err("Unexpected end of buffer while reading varint".into());
+ }
+ let b = buf.get_u8();
+ len |= ((b & 0x7f) as usize) << shift;
+ if b & 0x80 == 0 {
+ break;
+ }
+ shift += 7;
+ if shift >= 70 {
+ return Err("Varint too long".into());
+ }
+ }
+
+ let entry_bytes = &buf[..len];
+ let site = GeoSite::decode(entry_bytes)?;
+ entries.push(site);
+
+ buf.advance(len);
+ }
+
+ Ok(GeoSiteList { entry: entries })
+}
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<domain::Attribute>,
+}
+/// Nested message and enum types in `Domain`.
+pub mod domain {
+ /// Attribute of the domain.
+ #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
+ pub struct Attribute {
+ #[prost(string, tag = "1")]
+ pub key: ::prost::alloc::string::String,
+ #[prost(oneof = "attribute::TypedValue", tags = "2, 3")]
+ pub typed_value: ::core::option::Option<attribute::TypedValue>,
+ }
+ /// Nested message and enum types in `Attribute`.
+ pub mod attribute {
+ #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Oneof)]
+ pub enum TypedValue {
+ #[prost(bool, tag = "2")]
+ BoolValue(bool),
+ #[prost(int64, tag = "3")]
+ IntValue(i64),
+ }
+ }
+ /// Type of domain value.
+ #[derive(
+ Clone,
+ Copy,
+ Debug,
+ PartialEq,
+ Eq,
+ Hash,
+ PartialOrd,
+ Ord,
+ ::prost::Enumeration
+ )]
+ #[repr(i32)]
+ pub enum Type {
+ /// The value is used as is.
+ Plain = 0,
+ /// The value is used as a regular expression.
+ Regex = 1,
+ /// The value is a root domain.
+ Domain = 2,
+ /// The value is a domain.
+ Full = 3,
+ }
+ impl Type {
+ /// String value of the enum field names used in the ProtoBuf definition.
+ ///
+ /// The values are not transformed in any way and thus are considered stable
+ /// (if the ProtoBuf definition does not change) and safe for programmatic use.
+ pub fn as_str_name(&self) -> &'static str {
+ match self {
+ Self::Plain => "Plain",
+ Self::Regex => "Regex",
+ Self::Domain => "Domain",
+ Self::Full => "Full",
+ }
+ }
+ /// Creates an enum from field names used in the ProtoBuf definition.
+ pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
+ match value {
+ "Plain" => Some(Self::Plain),
+ "Regex" => Some(Self::Regex),
+ "Domain" => Some(Self::Domain),
+ "Full" => Some(Self::Full),
+ _ => None,
+ }
+ }
+ }
+}
+/// IP for routing decision, in CIDR form.
+#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
+pub struct Cidr {
+ /// IP address, should be either 4 or 16 bytes.
+ #[prost(bytes = "vec", tag = "1")]
+ pub ip: ::prost::alloc::vec::Vec<u8>,
+ /// Number of leading ones in the network mask.
+ #[prost(uint32, tag = "2")]
+ pub prefix: u32,
+}
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct GeoIp {
+ #[prost(string, tag = "1")]
+ pub country_code: ::prost::alloc::string::String,
+ #[prost(message, repeated, tag = "2")]
+ pub cidr: ::prost::alloc::vec::Vec<Cidr>,
+}
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct GeoIpList {
+ #[prost(message, repeated, tag = "1")]
+ pub entry: ::prost::alloc::vec::Vec<GeoIp>,
+}
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct GeoSite {
+ #[prost(string, tag = "1")]
+ pub country_code: ::prost::alloc::string::String,
+ #[prost(message, repeated, tag = "2")]
+ pub domain: ::prost::alloc::vec::Vec<Domain>,
+ /// resource_hash instruct simplified config converter to load domain from geo file.
+ #[prost(bytes = "vec", tag = "3")]
+ pub resource_hash: ::prost::alloc::vec::Vec<u8>,
+ #[prost(string, tag = "4")]
+ pub code: ::prost::alloc::string::String,
+}
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct GeoSiteList {
+ #[prost(message, repeated, tag = "1")]
+ pub entry: ::prost::alloc::vec::Vec<GeoSite>,
+}
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<dyn std::error::Error + Send + Sync + 'static>> {
+
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<PathBuf, Box<dyn std::error::Error>> {
+ let tmp_dir = std::env::temp_dir().join("seccontrol_test");
+ fs::create_dir_all(&tmp_dir)?;
+
+ let geosite_path = tmp_dir.join("geosite.dat");
+
+ if !geosite_path.exists() {
+ // Use v2fly domain-list-community which has standard protobuf format
+ let url = "https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat";
+ let response = ureq::get(url).call()?;
+ let mut file = fs::File::create(&geosite_path)?;
+ let mut reader = response.into_reader();
+ std::io::copy(&mut reader, &mut file)?;
+ }
+
+ Ok(geosite_path)
+}
+
+fn get_geosite_service() -> Result<GeoSiteService, Box<dyn std::error::Error>> {
+ let geosite_path = download_geosite()?;
+ let service = GeoSiteService::new(geosite_path.to_str().unwrap())?;
+ Ok(service)
+}
+
+#[test]
+fn geosite_service_creation() {
+ let service = get_geosite_service();
+ assert!(service.is_ok(), "Failed to create GeoSiteService: {:?}", service.err());
+}
+
+#[test]
+fn lookup_existing_domain() {
+ let service = get_geosite_service().expect("Failed to create service");
+
+ assert!(!service.is_empty(), "Service should have entries");
+ println!("Loaded {} GeoSite entries", service.len());
+}
+
+#[test]
+fn lookup_nonexistent_domain() {
+ let service = get_geosite_service().expect("Failed to create service");
+ let domain = Domain {
+ r#type: nsc::geoparsers::v2ray::types::domain::Type::Full as i32,
+ value: "nfaklsfjlasfvjkcnjnasxcjsas-not-existing-domain.com".to_string(),
+ attribute: vec![],
+ };
+
+ let result = service.lookup(domain.value.as_str());
+ assert!(result.is_none(), "Should return none for not existing domain");
+ println!("{:?}", result);
+}
+
+#[test]
+fn geosite_list_not_empty() {
+ let service = get_geosite_service().expect("Failed to create service");
+
+ assert!(
+ !service.is_empty(),
+ "GeoSiteList should not be empty"
+ );
+
+ println!("Loaded {} GeoSite entries", service.len());
+}