Skip to content

Commit

Permalink
[confgen] ensure resolveable rules go to last
Browse files Browse the repository at this point in the history
  • Loading branch information
lirundong committed Dec 29, 2023
1 parent b3dbdb1 commit 623cabf
Show file tree
Hide file tree
Showing 11 changed files with 67 additions and 39 deletions.
10 changes: 8 additions & 2 deletions conf-gen/generator/clash_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,15 @@ def generate(self, file):
conf.update(self._general_options)
conf["proxies"] = [p.clash_proxy for p in self._proxies]
conf["proxy-groups"] = [g.clash_proxy_group for g in self._proxy_groups]
conf["rules"] = []

# Ensure rules that require hostname resolving go to the ending of Clash rules.
no_resolve_rules = []
resolve_rules = []
for g in self._proxy_groups:
conf["rules"] += g.clash_rules
no_resolve_r, resolve_r = g.clash_rules
no_resolve_rules += no_resolve_r
resolve_rules += resolve_r
conf["rules"] = no_resolve_rules + resolve_rules

# Deduplicate rules. Clash performs rule traversal in O(N) thus this could improve perf.
num_duplicates = 0
Expand Down
26 changes: 15 additions & 11 deletions conf-gen/generator/quantumult_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@


class QuantumultGenerator(GeneratorBase):

_MANDATORY_SECTIONS = (
"dns",
"general",
Expand Down Expand Up @@ -108,20 +107,25 @@ def generate(self, file):
# Filter.
f.write("[filter_local]\n")
missing_sections.remove("filter_local")
filters = []
no_resolve_filters = []
resolve_filters = []
existing_matchers = set()
num_duplications = 0
for g in self._proxy_groups:
for filter in g.quantumult_filters:
matcher = ",".join(filter.split(",")[:2])
if matcher not in existing_matchers:
existing_matchers.add(matcher)
filters.append(filter)
else:
num_duplications += 1
for filters, filters_in_g in zip(
[no_resolve_filters, resolve_filters], g.quantumult_filters
):
for filter in filters_in_g:
matcher = ",".join(filter.split(",")[:2])
if matcher not in existing_matchers:
existing_matchers.add(matcher)
filters.append(filter)
else:
num_duplications += 1
if 0 < num_duplications:
print(f"Filtered out {num_duplications} duplications in " "Quantumult-x filters.")
f.write("\n".join(filters) + "\n")
print(f"Filtered out {num_duplications} duplications in Quantumult-x filters.")
f.write("\n".join(no_resolve_filters) + "\n")
f.write("\n".join(resolve_filters) + "\n")
# Rewrite.
f.write("[rewrite_local]\n")
missing_sections.remove("rewrite_local")
Expand Down
3 changes: 1 addition & 2 deletions conf-gen/proxy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from proxy._base_proxy import ProxyBase, ProxyT
from proxy._base_proxy import ProxyBase
from proxy.parser import (
parse_clash_proxies,
parse_clash_subscription,
Expand All @@ -12,7 +12,6 @@

__all__ = (
"ProxyBase",
"ProxyT",
"ShadowSocks2022CiphersT",
"ShadowSocksProxy",
"ShadowSocks2022Proxy",
Expand Down
3 changes: 0 additions & 3 deletions conf-gen/proxy/_base_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,3 @@ def sing_box_proxy(self) -> SingBoxProxyT:
"server_port": self.port,
"domain_strategy": "prefer_ipv6",
}


ProxyT = Union[ProxyBase, str, Dict[str, str]]
32 changes: 23 additions & 9 deletions conf-gen/proxy_group/_base_proxy_group.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
from collections import defaultdict
import re
from typing import Dict, Optional, List, Union
from typing import Dict, Optional, List, Tuple, Union

from proxy import ProxyBase, ProxyT
from proxy import ProxyBase
from rule import FilterT, parse_filter
from rule._base_ir import IRBase
from rule.ir import ProcessName


ProxyT = Union[ProxyBase, str, Dict[str, str]]
ProxyLeafT = Union[ProxyBase, str]


def group_sing_box_filters(
filters: List[IRBase],
skip_process_names: bool = False,
Expand Down Expand Up @@ -69,9 +73,14 @@ def quantumult_policy(self) -> str:
raise NotImplementedError()

@property
def quantumult_filters(self) -> List[str]:
filters = []
def quantumult_filters(self) -> Tuple[List[str], List[str]]:
no_resolve_filters = []
resolve_filters = []
for filter in self._filters:
if filter._might_resolvable and filter._resolve:
filters = resolve_filters
else:
filters = no_resolve_filters
try:
filter = filter.quantumult_rule.split(",")
except ValueError as e:
Expand All @@ -84,16 +93,21 @@ def quantumult_filters(self) -> List[str]:
else:
filter.append(self.name)
filters.append(",".join(filter))
return filters
return no_resolve_filters, resolve_filters

@property
def clash_proxy_group(self) -> Dict[str, Union[str, List[str], int]]:
raise NotImplementedError()

@property
def clash_rules(self) -> List[str]:
ret = []
def clash_rules(self) -> Tuple[List[str], List[str]]:
no_resolve_rules = []
resolve_rules = []
for filter in self._filters:
if filter._might_resolvable and filter._resolve:
rules = resolve_rules
else:
rules = no_resolve_rules
try:
clash_rule = filter.clash_rule.split(",")
except ValueError as e:
Expand All @@ -105,8 +119,8 @@ def clash_rules(self) -> List[str]:
clash_rule.insert(-1, self.name)
else:
clash_rule.append(self.name)
ret.append(",".join(clash_rule))
return ret
rules.append(",".join(clash_rule))
return no_resolve_rules, resolve_rules

@property
def sing_box_outbound(self) -> Dict:
Expand Down
7 changes: 3 additions & 4 deletions conf-gen/proxy_group/fallback_proxy_group.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from typing import Dict, Optional, List, Union
from typing import Dict, get_args, Optional, List, Union

from proxy import ProxyBase, ProxyT
from proxy_group._base_proxy_group import ProxyGroupBase
from proxy_group._base_proxy_group import ProxyGroupBase, ProxyT, ProxyLeafT
from rule import FilterT


Expand All @@ -19,7 +18,7 @@ def __init__(
if filters is not None:
raise ValueError(f"{self.__class.__name__} doesn't accept filters/rules.")
for proxy in proxies:
if not isinstance(proxy, (str, ProxyBase)):
if not isinstance(proxy, get_args(ProxyLeafT)):
raise ValueError(f"{self.__class__.__name__} only accept leaf proxy specs.")
if proxy_check_interval <= 0:
raise ValueError(f"Invalid proxy check interval {proxy_check_interval}")
Expand Down
2 changes: 1 addition & 1 deletion conf-gen/proxy_group/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import emoji
import pycountry

from proxy import ProxyBase
from proxy_group import ProxyGroupBase
from proxy_group._base_proxy_group import ProxyBase
from proxy_group.fallback_proxy_group import FallbackProxyGroup
from proxy_group.selective_proxy_group import SelectProxyGroup

Expand Down
3 changes: 1 addition & 2 deletions conf-gen/proxy_group/selective_proxy_group.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from typing import Dict, Optional, List, Union

from proxy import ProxyT
from proxy_group import ProxyGroupBase
from proxy_group._base_proxy_group import ProxyGroupBase, ProxyT
from rule import FilterT


Expand Down
1 change: 1 addition & 0 deletions conf-gen/rule/ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ class Match(_IRBase):

_clash_prefix = "MATCH"
_quantumult_prefix = "final"
_might_resolvable = True # Ensure this is the last rule for Clash and Quantumult-X.

def __init__(self, val: Optional[str]=None, resolve: Optional[bool]=None) -> None:
if val is None:
Expand Down
5 changes: 3 additions & 2 deletions conf-gen/rule/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,8 @@ def parse_filter(
else:
if filter["type"] not in ("final", "match"):
raise RuntimeError(f"Got incomplete rule {filter}")
ir = _IR_REGISTRY[filter["type"]](val="match")
# Set the final match resolvable to ensure it goes to the last of rule-list.
ir = _IR_REGISTRY[filter["type"]](val="match", resolve=True)
ret = [
ir,
]
Expand All @@ -270,7 +271,7 @@ def parse_filter(
else:
if type not in ("final", "match"):
raise RuntimeError(f"Got incomplete rule {filter}")
ir = _IR_REGISTRY[type](val="match")
ir = _IR_REGISTRY[type](val="match", resolve=True)
ret = [
ir,
]
Expand Down
14 changes: 11 additions & 3 deletions conf-gen/source.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ rules:
format: text
resolve: false
url: https://ruleset.skk.moe/Clash/non_ip/reject.txt
- type: quantumult
resolve: false
url: https://github.com/GeQ1an/Rules/raw/master/QuantumultX/Filter/AdBlock.list
# NOTE: This is a VERY long list, use with caution!
# - type: domain-list
# format: text
Expand Down Expand Up @@ -235,9 +238,10 @@ rules:
- name: Apple CDN
type: select
filters:
- type: domain-list
- type: clash-classical
format: text
url: https://ruleset.skk.moe/Clash/domainset/apple_cdn.txt
resolve: false
url: https://ruleset.skk.moe/Clash/non_ip/apple_cdn.txt
img-url: https://github.com/GeQ1an/Rules/raw/master/QuantumultX/IconSet/Apple.png
proxies:
- Mainland
Expand Down Expand Up @@ -328,6 +332,10 @@ rules:
- DOMAIN,uts-api.itunes.apple.com
- DOMAIN,hls-amt.itunes.apple.com
- DOMAIN,hls.itunes.apple.com
- type: clash-classical
format: text
resolve: false
url: https://ruleset.skk.moe/Clash/non_ip/apple_cn.txt
- type: clash-classical
format: text
resolve: false
Expand All @@ -346,7 +354,7 @@ rules:
- name: Mainland
type: select
filters:
# Handle smart home devices, especially the weird "Mijia Cloud" hostname.
# Handle smart home devices, including the weird "Mijia Cloud" hostname.
- DOMAIN-SUFFIX,aqara.cn
- DOMAIN-SUFFIX,smartmidea.net
- DOMAIN-SUFFIX,midea.com.cn
Expand Down

0 comments on commit 623cabf

Please sign in to comment.