Skip to content

Commit

Permalink
feat: support geosite (#466)
Browse files Browse the repository at this point in the history
  • Loading branch information
VendettaReborn authored Jul 3, 2024
1 parent 9f9f609 commit 9d9c3f2
Show file tree
Hide file tree
Showing 19 changed files with 848 additions and 98 deletions.
11 changes: 9 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: true

- uses: actions/cache@v4
with:
path: |
Expand Down Expand Up @@ -217,7 +216,15 @@ jobs:
with:
name: ${{ matrix.release-name || matrix.target }}
path: ${{ env.PACKAGE }}-${{ matrix.release-name || matrix.target }}${{ matrix.postfix }}


- name: Setup tmate session
if: ${{ failure() }}
uses: mxschmitt/action-tmate@v3
with:
detached: true
timeout-minutes: 15
limit-access-to-actor: true

release:
name: Release

Expand Down
45 changes: 17 additions & 28 deletions clash_lib/src/app/dns/system.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use async_trait::async_trait;
use hickory_resolver::{
name_server::{GenericConnector, TokioRuntimeProvider},
AsyncResolver,
};
use rand::seq::IteratorRandom;
use tracing::warn;

use super::{ClashResolver, ResolverKind};

pub struct SystemResolver;
pub struct SystemResolver(AsyncResolver<GenericConnector<TokioRuntimeProvider>>);

/// SystemResolver is a resolver that uses libc getaddrinfo to resolve
/// hostnames.
Expand All @@ -14,7 +18,12 @@ impl SystemResolver {
"Default dns resolver doesn't support ipv6, please enable clash dns \
resolver if you need ipv6 support."
);
Ok(Self)

let resolver: AsyncResolver<
GenericConnector<hickory_resolver::name_server::TokioRuntimeProvider>,
> = hickory_resolver::AsyncResolver::tokio_from_system_conf()?;

Ok(Self(resolver))
}
}

Expand All @@ -25,12 +34,9 @@ impl ClashResolver for SystemResolver {
host: &str,
_: bool,
) -> anyhow::Result<Option<std::net::IpAddr>> {
let response = tokio::net::lookup_host(format!("{}:0", host))
.await?
.collect::<Vec<_>>();
let response = self.0.lookup_ip(host).await?;
Ok(response
.iter()
.map(|x| x.ip())
.filter(|x| self.ipv6() || x.is_ipv4())
.choose(&mut rand::thread_rng()))
}
Expand All @@ -40,35 +46,17 @@ impl ClashResolver for SystemResolver {
host: &str,
_: bool,
) -> anyhow::Result<Option<std::net::Ipv4Addr>> {
let response = tokio::net::lookup_host(format!("{}:0", host))
.await?
.collect::<Vec<_>>();
Ok(response
.iter()
.map(|x| x.ip())
.filter_map(|ip| match ip {
std::net::IpAddr::V4(ip) => Some(ip),
_ => None,
})
.choose(&mut rand::thread_rng()))
let response = self.0.ipv4_lookup(host).await?;
Ok(response.iter().map(|x| x.0).choose(&mut rand::thread_rng()))
}

async fn resolve_v6(
&self,
host: &str,
_: bool,
) -> anyhow::Result<Option<std::net::Ipv6Addr>> {
let response = tokio::net::lookup_host(format!("{}:0", host))
.await?
.collect::<Vec<_>>();
Ok(response
.iter()
.map(|x| x.ip())
.filter_map(|ip| match ip {
std::net::IpAddr::V6(ip) => Some(ip),
_ => None,
})
.choose(&mut rand::thread_rng()))
let response = self.0.ipv6_lookup(host).await?;
Ok(response.iter().map(|x| x.0).choose(&mut rand::thread_rng()))
}

async fn exchange(
Expand All @@ -79,6 +67,7 @@ impl ClashResolver for SystemResolver {
}

fn ipv6(&self) -> bool {
// TODO: support ipv6
false
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::{
},
router::{map_rule_type, RuleMatcher},
},
common::{errors::map_io_error, mmdb::Mmdb, trie},
common::{errors::map_io_error, geodata::GeoData, mmdb::Mmdb, trie},
config::internal::rule::RuleType,
session::Session,
Error,
Expand Down Expand Up @@ -86,6 +86,7 @@ impl RuleProviderImpl {
interval: Duration,
vehicle: ThreadSafeProviderVehicle,
mmdb: Arc<Mmdb>,
geodata: Arc<GeoData>,
) -> Self {
let inner = Arc::new(tokio::sync::RwLock::new(Inner {
content: match behovior {
Expand Down Expand Up @@ -123,7 +124,12 @@ impl RuleProviderImpl {
n, x
))
})?;
let rules = make_rules(behovior, scheme.payload, mmdb.clone())?;
let rules = make_rules(
behovior,
scheme.payload,
mmdb.clone(),
geodata.clone(),
)?;
Ok(rules)
});

Expand Down Expand Up @@ -233,6 +239,7 @@ fn make_rules(
behavior: RuleSetBehavior,
rules: Vec<String>,
mmdb: Arc<Mmdb>,
geodata: Arc<GeoData>,
) -> Result<RuleContent, Error> {
match behavior {
RuleSetBehavior::Domain => {
Expand All @@ -241,9 +248,9 @@ fn make_rules(
RuleSetBehavior::Ipcidr => {
Ok(RuleContent::Ipcidr(Box::new(make_ip_cidr_rules(rules)?)))
}
RuleSetBehavior::Classical => {
Ok(RuleContent::Classical(make_classical_rules(rules, mmdb)?))
}
RuleSetBehavior::Classical => Ok(RuleContent::Classical(
make_classical_rules(rules, mmdb, geodata)?,
)),
}
}

Expand All @@ -266,6 +273,7 @@ fn make_ip_cidr_rules(rules: Vec<String>) -> Result<CidrTrie, Error> {
fn make_classical_rules(
rules: Vec<String>,
mmdb: Arc<Mmdb>,
geodata: Arc<GeoData>,
) -> Result<Vec<Box<dyn RuleMatcher>>, Error> {
let mut rv = vec![];
for rule in rules {
Expand All @@ -282,7 +290,8 @@ fn make_classical_rules(
_ => Err(Error::InvalidConfig(format!("invalid rule line: {}", rule))),
}?;

let rule_matcher = map_rule_type(rule_type, mmdb.clone(), None);
let rule_matcher =
map_rule_type(rule_type, mmdb.clone(), geodata.clone(), None);
rv.push(rule_matcher);
}
Ok(rv)
Expand Down
28 changes: 27 additions & 1 deletion clash_lib/src/app/router/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ use super::{
};

mod rules;

use crate::common::geodata::GeoData;
pub use rules::RuleMatcher;

pub struct Router {
Expand All @@ -46,6 +48,7 @@ impl Router {
rule_providers: HashMap<String, RuleProviderDef>,
dns_resolver: ThreadSafeDNSResolver,
mmdb: Arc<Mmdb>,
geodata: Arc<GeoData>,
cwd: String,
) -> Self {
let mut rule_provider_registry = HashMap::new();
Expand All @@ -55,6 +58,7 @@ impl Router {
&mut rule_provider_registry,
dns_resolver.clone(),
mmdb.clone(),
geodata.clone(),
cwd,
)
.await
Expand All @@ -64,7 +68,12 @@ impl Router {
rules: rules
.into_iter()
.map(|r| {
map_rule_type(r, mmdb.clone(), Some(&rule_provider_registry))
map_rule_type(
r,
mmdb.clone(),
geodata.clone(),
Some(&rule_provider_registry),
)
})
.collect(),
dns_resolver,
Expand Down Expand Up @@ -106,6 +115,7 @@ impl Router {
r.target(),
r.type_name()
);
debug!("matched rule details: {}", r);
return (r.target(), Some(r));
}
}
Expand All @@ -118,6 +128,7 @@ impl Router {
rule_provider_registry: &mut HashMap<String, ThreadSafeRuleProvider>,
resolver: ThreadSafeDNSResolver,
mmdb: Arc<Mmdb>,
geodata: Arc<GeoData>,
cwd: String,
) -> Result<(), Error> {
for (name, provider) in rule_providers.into_iter() {
Expand All @@ -138,6 +149,7 @@ impl Router {
Duration::from_secs(http.interval),
Arc::new(vehicle),
mmdb.clone(),
geodata.clone(),
);

rule_provider_registry.insert(name, Arc::new(provider));
Expand All @@ -156,6 +168,7 @@ impl Router {
Duration::from_secs(file.interval.unwrap_or_default()),
Arc::new(vehicle),
mmdb.clone(),
geodata.clone(),
);

rule_provider_registry.insert(name, Arc::new(provider));
Expand Down Expand Up @@ -194,6 +207,7 @@ impl Router {
pub fn map_rule_type(
rule_type: RuleType,
mmdb: Arc<Mmdb>,
geodata: Arc<GeoData>,
rule_provider_registry: Option<&HashMap<String, ThreadSafeRuleProvider>>,
) -> Box<dyn RuleMatcher> {
match rule_type {
Expand Down Expand Up @@ -245,6 +259,18 @@ pub fn map_rule_type(
no_resolve,
mmdb: mmdb.clone(),
}),
RuleType::GeoSite {
target,
country_code,
} => {
let res = rules::geodata::GeoSiteMatcher::new(
country_code,
target,
geodata.as_ref(),
)
.unwrap();
Box::new(res) as _
}
RuleType::SRCPort { target, port } => Box::new(rules::port::Port {
port,
target,
Expand Down
51 changes: 51 additions & 0 deletions clash_lib/src/app/router/rules/geodata/attribute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use crate::common::geodata::geodata_proto;

pub trait AttrMatcher {
fn matches(&self, domain: &geodata_proto::Domain) -> bool;
}

pub struct BooleanAttrMatcher(pub String);

impl AttrMatcher for BooleanAttrMatcher {
fn matches(&self, domain: &geodata_proto::Domain) -> bool {
for attr in &domain.attribute {
if attr.key.eq_ignore_ascii_case(&self.0) {
return true;
}
}
false
}
}

impl From<String> for BooleanAttrMatcher {
fn from(s: String) -> Self {
BooleanAttrMatcher(s)
}
}

// logical AND of multiple attribute matchers
pub struct AndAttrMatcher {
list: Vec<Box<dyn AttrMatcher>>,
}

impl From<Vec<String>> for AndAttrMatcher {
fn from(list: Vec<String>) -> Self {
AndAttrMatcher {
list: list
.into_iter()
.map(|s| Box::new(BooleanAttrMatcher(s)) as Box<dyn AttrMatcher>)
.collect(),
}
}
}

impl AttrMatcher for AndAttrMatcher {
fn matches(&self, domain: &geodata_proto::Domain) -> bool {
for matcher in &self.list {
if !matcher.matches(domain) {
return false;
}
}
true
}
}
Loading

0 comments on commit 9d9c3f2

Please sign in to comment.