Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): show ASN in connection metadata #616

Merged
merged 3 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added clash/tests/data/config/Country-asn.mmdb
Binary file not shown.
Binary file added clash/tests/data/config/GeoLite2-ASN.mmdb
Binary file not shown.
2 changes: 2 additions & 0 deletions clash/tests/data/config/rules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ external-ui: "public"
experimental:
ignore-resolve-fail: true

asn-mmdb: Country-asn.mmdb

profile:
store-selected: true
store-fake-ip: false
Expand Down
4 changes: 2 additions & 2 deletions clash_lib/src/app/dispatcher/dispatcher_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ impl Dispatcher {
let mode = *self.mode.lock().unwrap();
let (outbound_name, rule) = match mode {
RunMode::Global => (PROXY_GLOBAL, None),
RunMode::Rule => self.router.match_route(&sess).await,
RunMode::Rule => self.router.match_route(&mut sess).await,
RunMode::Direct => (PROXY_DIRECT, None),
};

Expand Down Expand Up @@ -315,7 +315,7 @@ impl Dispatcher {

let (outbound_name, rule) = match mode {
RunMode::Global => (PROXY_GLOBAL, None),
RunMode::Rule => router.match_route(&sess).await,
RunMode::Rule => router.match_route(&mut sess).await,
RunMode::Direct => (PROXY_DIRECT, None),
};

Expand Down
2 changes: 1 addition & 1 deletion clash_lib/src/app/dns/filters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ impl GeoIPFilter {
impl FallbackIPFilter for GeoIPFilter {
fn apply(&self, ip: &net::IpAddr) -> bool {
self.1
.lookup(*ip)
.lookup_contry(*ip)
.map(|x| x.country)
.is_ok_and(|x| x.is_some_and(|x| x.iso_code == Some(self.0.as_str())))
}
Expand Down
8 changes: 7 additions & 1 deletion clash_lib/src/app/dns/resolver/enhanced.rs
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,13 @@ impl ClashResolver for EnhancedResolver {

async fn exchange(&self, message: op::Message) -> anyhow::Result<op::Message> {
let rv = self.exchange(&message).await?;
let hostname = message.query().unwrap().name().to_ascii();
let hostname = message
.query()
.unwrap()
.name()
.to_utf8()
.trim_end_matches('.')
.to_owned();
let ip_list = EnhancedResolver::ip_list_of_message(&rv);
if !ip_list.is_empty() {
for ip in ip_list {
Expand Down
62 changes: 46 additions & 16 deletions clash_lib/src/app/router/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::app::router::rules::final_::Final;
use std::{collections::HashMap, path::PathBuf, sync::Arc, time::Duration};

use hyper::Uri;
use tracing::{error, info};
use tracing::{error, info, trace};

use super::{
dns::ThreadSafeDNSResolver,
Expand All @@ -33,9 +33,9 @@ pub use rules::RuleMatcher;

pub struct Router {
rules: Vec<Box<dyn RuleMatcher>>,
#[allow(dead_code)]
rule_provider_registry: HashMap<String, ThreadSafeRuleProvider>,
dns_resolver: ThreadSafeDNSResolver,

asn_mmdb: Option<Arc<Mmdb>>,
}

pub type ThreadSafeRouter = Arc<Router>;
Expand All @@ -47,7 +47,8 @@ impl Router {
rules: Vec<RuleType>,
rule_providers: HashMap<String, RuleProviderDef>,
dns_resolver: ThreadSafeDNSResolver,
mmdb: Arc<Mmdb>,
country_mmdb: Arc<Mmdb>,
asn_mmdb: Option<Arc<Mmdb>>,
geodata: Arc<GeoData>,
cwd: String,
) -> Self {
Expand All @@ -57,7 +58,7 @@ impl Router {
rule_providers,
&mut rule_provider_registry,
dns_resolver.clone(),
mmdb.clone(),
country_mmdb.clone(),
geodata.clone(),
cwd,
)
Expand All @@ -70,23 +71,24 @@ impl Router {
.map(|r| {
map_rule_type(
r,
mmdb.clone(),
country_mmdb.clone(),
geodata.clone(),
Some(&rule_provider_registry),
)
})
.collect(),
dns_resolver,
rule_provider_registry,

asn_mmdb,
}
}

/// this mutates the session, attaching resolved IP and ASN
pub async fn match_route(
&self,
sess: &Session,
sess: &mut Session,
) -> (&str, Option<&Box<dyn RuleMatcher>>) {
let mut sess_resolved = false;
let mut sess_dup = sess.clone();

for r in self.rules.iter() {
if sess.destination.is_domain()
Expand All @@ -98,15 +100,40 @@ impl Router {
.resolve(sess.destination.domain().unwrap(), false)
.await
{
sess_dup.resolved_ip = Some(ip);
sess.resolved_ip = Some(ip);
sess_resolved = true;
}
}

if r.apply(&sess_dup) {
let mayby_ip = sess.resolved_ip.or(sess.destination.ip());
if let (Some(ip), Some(asn_mmdb)) = (mayby_ip, &self.asn_mmdb) {
// try simplified mmdb first
let rv = asn_mmdb.lookup_contry(ip);
if let Ok(country) = rv {
sess.asn = country
.country
.and_then(|c| c.iso_code)
.map(|s| s.to_string());
}
if sess.asn.is_none() {
match asn_mmdb.lookup_asn(ip) {
Ok(asn) => {
trace!("asn for {} is {:?}", ip, asn);
sess.asn = asn
.autonomous_system_organization
.map(|s| s.to_string());
}
Err(e) => {
trace!("failed to lookup ASN for {}: {}", ip, e);
}
}
}
}

if r.apply(sess) {
info!(
"matched {} to target {}[{}]",
&sess_dup,
&sess,
r.target(),
r.type_name()
);
Expand Down Expand Up @@ -303,6 +330,8 @@ pub fn map_rule_type(
.clone(),
)),
None => {
// this is called in remote rule provider with no rule provider
// registry, in this case, we should panic
unreachable!("you shouldn't nest rule-set within another rule-set")
}
},
Expand Down Expand Up @@ -390,14 +419,15 @@ mod tests {
Default::default(),
mock_resolver,
Arc::new(mmdb),
None,
Arc::new(geodata),
temp_dir.path().to_str().unwrap().to_string(),
)
.await;

assert_eq!(
router
.match_route(&Session {
.match_route(&mut Session {
destination: crate::session::SocksAddr::Domain(
"china.com".to_string(),
1111,
Expand All @@ -412,7 +442,7 @@ mod tests {

assert_eq!(
router
.match_route(&Session {
.match_route(&mut Session {
destination: crate::session::SocksAddr::Domain(
"t.me".to_string(),
1111,
Expand All @@ -427,7 +457,7 @@ mod tests {

assert_eq!(
router
.match_route(&Session {
.match_route(&mut Session {
destination: crate::session::SocksAddr::Domain(
"git.io".to_string(),
1111
Expand All @@ -443,7 +473,7 @@ mod tests {

assert_eq!(
router
.match_route(&Session {
.match_route(&mut Session {
destination: crate::session::SocksAddr::Domain(
"no-match".to_string(),
1111
Expand Down
2 changes: 1 addition & 1 deletion clash_lib/src/app/router/rules/geoip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ impl RuleMatcher for GeoIP {
};

if let Some(ip) = ip {
match self.mmdb.lookup(ip) {
match self.mmdb.lookup_contry(ip) {
Ok(country) => {
country
.country
Expand Down
6 changes: 5 additions & 1 deletion clash_lib/src/common/mmdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,13 @@ impl Mmdb {
}
}

pub fn lookup(&self, ip: IpAddr) -> std::io::Result<geoip2::Country> {
pub fn lookup_contry(&self, ip: IpAddr) -> std::io::Result<geoip2::Country> {
self.reader
.lookup::<geoip2::Country>(ip)
.map_err(map_io_error)
}

pub fn lookup_asn(&self, ip: IpAddr) -> std::io::Result<geoip2::Asn> {
self.reader.lookup::<geoip2::Asn>(ip).map_err(map_io_error)
}
}
7 changes: 7 additions & 0 deletions clash_lib/src/config/def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ fn default_tun_address() -> String {
#[serde(rename_all = "kebab-case")]
pub struct TunConfig {
pub enable: bool,
#[serde(alias = "device-url")]
pub device_id: String,
/// tun interface address
#[serde(default = "default_tun_address")]
Expand Down Expand Up @@ -293,6 +294,10 @@ pub struct Config {
pub mmdb: String,
/// Country database download url
pub mmdb_download_url: Option<String>,
/// Optional ASN database path relative to the $CWD
pub asn_mmdb: String,
/// Optional ASN database download url
pub asn_mmdb_download_url: Option<String>,
/// Geosite database path relative to the $CWD
pub geosite: String,
/// Geosite database download url
Expand Down Expand Up @@ -396,6 +401,8 @@ impl Default for Config {
"https://github.com/Loyalsoldier/geoip/releases/download/202307271745/Country.mmdb"
.to_owned(),
),
asn_mmdb: "Country-asn.mmdb".to_string(),
asn_mmdb_download_url: None, // can be downloaded from the same release but let's not make it default
geosite: "geosite.dat".to_string(),
geosite_download_url: Some("https://github.com/Loyalsoldier/v2ray-rules-dat/releases/download/202406182210/geosite.dat".to_owned()),
tun: Default::default(),
Expand Down
4 changes: 4 additions & 0 deletions clash_lib/src/config/internal/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ impl TryFrom<def::Config> for Config {
routing_mask: c.routing_mask,
mmdb: c.mmdb.to_owned(),
mmdb_download_url: c.mmdb_download_url.to_owned(),
asn_mmdb: c.asn_mmdb.to_owned(),
asn_mmdb_download_url: c.asn_mmdb_download_url.to_owned(),
geosite: c.geosite.to_owned(),
geosite_download_url: c.geosite_download_url.to_owned(),
},
Expand Down Expand Up @@ -283,6 +285,8 @@ pub struct General {
pub routing_mask: Option<u32>,
pub mmdb: String,
pub mmdb_download_url: Option<String>,
pub asn_mmdb: String,
pub asn_mmdb_download_url: Option<String>,

pub geosite: String,
pub geosite_download_url: Option<String>,
Expand Down
24 changes: 17 additions & 7 deletions clash_lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,22 +332,20 @@ async fn create_components(
.map_err(|x| Error::DNSError(x.to_string()))?;

debug!("initializing mmdb");
let mmdb = Arc::new(
let country_mmdb = Arc::new(
mmdb::Mmdb::new(
cwd.join(&config.general.mmdb),
config.general.mmdb_download_url,
client,
client.clone(),
)
.await?,
);

let client = new_http_client(system_resolver)
.map_err(|x| Error::DNSError(x.to_string()))?;
let geodata = Arc::new(
geodata::GeoData::new(
cwd.join(&config.general.geosite),
config.general.geosite_download_url,
client,
client.clone(),
)
.await?,
);
Expand All @@ -362,7 +360,7 @@ async fn create_components(
let dns_resolver = dns::new_resolver(
&config.dns,
Some(cache_store.clone()),
Some(mmdb.clone()),
Some(country_mmdb.clone()),
)
.await;

Expand Down Expand Up @@ -394,13 +392,25 @@ async fn create_components(
.await?,
);

debug!("initializing country asn mmdb");
let p = cwd.join(&config.general.asn_mmdb);
let asn_mmdb = if p.exists() || config.general.asn_mmdb_download_url.is_some() {
Some(Arc::new(
mmdb::Mmdb::new(p, config.general.asn_mmdb_download_url, client.clone())
.await?,
))
} else {
None
};

debug!("initializing router");
let router = Arc::new(
Router::new(
config.rules,
config.rule_providers,
dns_resolver.clone(),
mmdb,
country_mmdb,
asn_mmdb,
geodata,
cwd.to_string_lossy().to_string(),
)
Expand Down
Loading
Loading