Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
279 changes: 279 additions & 0 deletions scripts/libs/yaml_dns.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
#!/bin/sh
# Copyright (C) Juewuy

# Extract the top-level dns section without requiring a YAML parser. The
# merger below normalizes block and one-line flow mappings before overlaying
# fields, so the final configuration contains only one dns key.

yaml_dns_extract() {
awk '
function top_level(line) {
return line !~ /^[[:space:]#]/ && line ~ /^[^:]+:/
}
function brace_delta(line, i, c, quote, escaped, delta) {
for (i = 1; i <= length(line); i++) {
c = substr(line, i, 1)
if (quote) {
if (quote == "\042" && escaped) escaped = 0
else if (quote == "\042" && c == "\\") escaped = 1
else if (c == quote) quote = 0
} else if (c == "\047" || c == "\042") quote = c
else if (c == "{") delta++
else if (c == "}") delta--
}
return delta
}
{
if (!found) {
if ($0 ~ /^dns:[[:space:]]*\{/) {
print
flow = 1
balance = brace_delta($0)
if (balance <= 0) exit
found = 1
next
}
if ($0 ~ /^dns:[[:space:]]*($|#)/) {
print
found = 1
}
next
}
if (flow) {
print
balance += brace_delta($0)
if (balance <= 0) exit
next
}
if (top_level($0)) exit
print
}' "$1"
}

yaml_dns_without() {
awk '
function top_level(line) {
return line !~ /^[[:space:]#]/ && line ~ /^[^:]+:/
}
function brace_delta(line, i, c, quote, escaped, delta) {
for (i = 1; i <= length(line); i++) {
c = substr(line, i, 1)
if (quote) {
if (quote == "\042" && escaped) escaped = 0
else if (quote == "\042" && c == "\\") escaped = 1
else if (c == quote) quote = 0
} else if (c == "\047" || c == "\042") quote = c
else if (c == "{") delta++
else if (c == "}") delta--
}
return delta
}
{
if (!in_dns) {
if ($0 ~ /^dns:[[:space:]]*\{/) {
in_dns = 1
flow = 1
balance = brace_delta($0)
if (balance <= 0) in_dns = 0
next
}
if ($0 ~ /^dns:[[:space:]]*($|#)/) {
in_dns = 1
next
}
print
next
}
if (flow) {
balance += brace_delta($0)
if (balance <= 0) in_dns = 0
next
}
if (top_level($0)) {
in_dns = 0
print
next
}
}' "$1"
}

yaml_dns_merge() {
awk -v original="${1:-/dev/null}" \
-v managed="${2:-/dev/null}" \
-v user="${3:-/dev/null}" '
function trim(value) {
sub(/^[[:space:]]+/, "", value)
sub(/[[:space:]]+$/, "", value)
return value
}
function brace_delta(line, i, c, quote, escaped, delta) {
for (i = 1; i <= length(line); i++) {
c = substr(line, i, 1)
if (quote) {
if (quote == "\042" && escaped) escaped = 0
else if (quote == "\042" && c == "\\") escaped = 1
else if (c == quote) quote = 0
} else if (c == "\047" || c == "\042") quote = c
else if (c == "{") delta++
else if (c == "}") delta--
}
return delta
}
function add_entry(source, key, value) {
if (!(source SUBSEP key in entry)) {
order[source, ++count[source]] = key
}
entry[source, key] = value
}
function append_entry(source, key, line) {
if (entry[source, key] == "") entry[source, key] = line
else entry[source, key] = entry[source, key] "\n" line
}
function flow_pair(source, pair, i, c, depth_brace, depth_square, quote, escaped, colon, key, value) {
pair = trim(pair)
colon = 0
depth_brace = depth_square = 0
quote = escaped = 0
for (i = 1; i <= length(pair); i++) {
c = substr(pair, i, 1)
if (quote) {
if (quote == "\042" && escaped) escaped = 0
else if (quote == "\042" && c == "\\") escaped = 1
else if (c == quote) quote = 0
continue
}
if (c == "\047") quote = "\047"
else if (c == "\042") quote = "\042"
else if (c == "{") depth_brace++
else if (c == "}") depth_brace--
else if (c == "[") depth_square++
else if (c == "]") depth_square--
else if (c == ":" && depth_brace == 0 && depth_square == 0) {
colon = i
break
}
}
if (!colon) return
key = trim(substr(pair, 1, colon - 1))
value = trim(substr(pair, colon + 1))
if ((substr(key, 1, 1) == "\047" && substr(key, length(key), 1) == "\047") ||
(substr(key, 1, 1) == "\042" && substr(key, length(key), 1) == "\042"))
key = substr(key, 2, length(key) - 2)
add_entry(source, key, " " key ": " value)
}
function parse_flow(source, text, i, c, depth_brace, depth_square, quote, escaped, start) {
text = trim(text)
if (substr(text, 1, 1) == "{") text = substr(text, 2)
if (substr(text, length(text), 1) == "}") text = substr(text, 1, length(text) - 1)
Comment thread
yorkyang2333 marked this conversation as resolved.
start = 1
depth_brace = depth_square = 0
quote = escaped = 0
for (i = 1; i <= length(text); i++) {
c = substr(text, i, 1)
if (quote) {
if (quote == "\042" && escaped) escaped = 0
else if (quote == "\042" && c == "\\") escaped = 1
else if (c == quote) quote = 0
continue
}
if (c == "\047") quote = "\047"
else if (c == "\042") quote = "\042"
else if (c == "{") depth_brace++
else if (c == "}") depth_brace--
else if (c == "[") depth_square++
else if (c == "]") depth_square--
else if (c == "," && depth_brace == 0 && depth_square == 0) {
flow_pair(source, substr(text, start, i - start))
start = i + 1
}
}
flow_pair(source, substr(text, start))
}
function parse_file(file, source, line, rest, key, value) {
started = flow = balance = 0
current = ""
flow_text = ""
while ((getline line < file) > 0) {
if (!started) {
if (line ~ /^dns:[[:space:]]*\{/) {
rest = line
sub(/^dns:[[:space:]]*/, "", rest)
flow_text = rest
flow = 1
balance = brace_delta(line)
started = 1
if (balance <= 0) {
parse_flow(source, flow_text)
started = flow = 0
}
} else if (line ~ /^dns:[[:space:]]*($|#)/) {
started = 1
}
continue
}
if (flow) {
flow_text = flow_text " " trim(line)
balance += brace_delta(line)
if (balance <= 0) {
parse_flow(source, flow_text)
started = flow = 0
}
continue
}
if (line ~ /^[[:space:]][[:space:]][A-Za-z0-9_-]+[[:space:]]*:/) {
if (current != "") add_entry(source, key, value)
key = line
sub(/^[[:space:]]+/, "", key)
sub(/[[:space:]]*:.*/, "", key)
value = line
current = key
} else if (current != "") {
value = value "\n" line
}
}
if (current != "") add_entry(source, key, value)
close(file)
}
function managed_key(key) {
return key == "enable" || key == "listen" || key == "use-hosts" ||
key == "ipv6" || key == "default-nameserver" || key == "direct-nameserver" ||
key == "enhanced-mode" || key == "fake-ip-range" || key == "fake-ip-range6" ||
key == "fake-ip-filter" || key == "respect-rules" ||
key == "proxy-server-nameserver" || key == "nameserver"
}
BEGIN {
parse_file(original, "original")
parse_file(managed, "managed")
parse_file(user, "user")
print "dns:"
for (i = 1; i <= count["original"]; i++) {
key = order["original", i]
# nameserver-policy is intentionally passthrough: ShellCrash
# generated policy must not replace subscription-specific routes.
if (!managed_key(key) && !("user" SUBSEP key in entry))
print entry["original", key]
}
for (i = 1; i <= count["managed"]; i++) {
key = order["managed", i]
if (!("user" SUBSEP key in entry) &&
!(key == "nameserver-policy" && ("original" SUBSEP key in entry)))
print entry["managed", key]
}
for (i = 1; i <= count["user"]; i++) {
key = order["user", i]
print entry["user", key]
}
}'
}

case "${1:-}" in
extract)
yaml_dns_extract "$2"
;;
without)
yaml_dns_without "$2"
;;
merge)
yaml_dns_merge "$2" "$3" "$4"
;;
esac
4 changes: 3 additions & 1 deletion scripts/menus/dns.sh
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,9 @@ set_dns_adv() {
if echo "$crashcore" | grep -qE 'meta|singbox'; then
dns_nameserver='https://dns.alidns.com/dns-query, https://doh.pub/dns-query'
dns_fallback='https://cloudflare-dns.com/dns-query, https://dns.google/dns-query, https://doh.opendns.com/dns-query'
dns_resolver='https://223.5.5.5/dns-query, 2400:3200::1'
# Mihomo's default-nameserver resolver must be plain IPs;
# encrypted resolvers belong in dns_nameserver/dns_fallback.
dns_resolver='223.5.5.5, 2400:3200::1'
setconfig dns_nameserver "'$dns_nameserver'"
setconfig dns_fallback "'$dns_fallback'"
setconfig dns_resolver "'$dns_resolver'"
Expand Down
6 changes: 5 additions & 1 deletion scripts/menus/providers_clash.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ __IS_PROVIDERS_CLASH=1
load_lang providers

. "$CRASHDIR"/libs/web_get_bin.sh
. "$CRASHDIR"/libs/yaml_dns.sh

# 生成clash的providers配置文件
gen_providers() {
Expand All @@ -29,6 +30,9 @@ gen_providers() {
fi
# 生成proxy_providers模块
mkdir -p "$TMPDIR"/providers
# Keep an optional DNS section from the provider template. It is otherwise
# lost when the template is reduced to proxy providers, groups and rules.
yaml_dns_extract "$TMPDIR"/provider_temp_file >"$TMPDIR"/providers/dns.yaml
# 预创建文件并写入对应文件头
echo 'proxy-providers:' >"$TMPDIR"/providers/providers.yaml
# 切割模版文件
Expand Down Expand Up @@ -58,7 +62,7 @@ gen_providers() {
fi
# 修饰模版文件并合并
sed -i "s/{providers_tags}/$providers_tags/g" "$TMPDIR"/providers/proxy-groups.yaml
cut -c 1- "$TMPDIR"/providers/providers.yaml "$TMPDIR"/providers/proxy-groups.yaml "$TMPDIR"/providers/rules.yaml >"$TMPDIR"/config.yaml
cut -c 1- "$TMPDIR"/providers/providers.yaml "$TMPDIR"/providers/proxy-groups.yaml "$TMPDIR"/providers/rules.yaml "$TMPDIR"/providers/dns.yaml >"$TMPDIR"/config.yaml
Comment thread
yorkyang2333 marked this conversation as resolved.
rm -rf "$TMPDIR"/providers
# 调用内核测试
. "$CRASHDIR"/starts/check_core.sh && check_core && "$TMPDIR"/CrashCore -t -d "$BINDIR" -f "$TMPDIR"/config.yaml
Expand Down
32 changes: 20 additions & 12 deletions scripts/starts/clash_modify.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,16 @@ prepare_clash_base_config() {
fi
}
}
#dns配置
[ -z "$(cat "$CRASHDIR"/yamls/user.yaml 2>/dev/null | grep '^dns:')" ] && {
[ "$crashcore" != meta ] && dns_resolver='223.5.5.5'
cat >"$TMPDIR"/dns.yaml <<EOF
. "$CRASHDIR"/libs/yaml_dns.sh
# Build one DNS section from the subscription, ShellCrash defaults and
# user overrides. This avoids duplicate top-level dns keys in config.yaml.
yaml_dns_extract "$core_config" >"$TMPDIR"/original-dns.yaml
if [ -s "$CRASHDIR"/yamls/user.yaml ]; then
yaml_dns_extract "$CRASHDIR"/yamls/user.yaml >"$TMPDIR"/user-dns.yaml
yaml_dns_without "$CRASHDIR"/yamls/user.yaml >"$TMPDIR"/user.yaml
fi
[ "$crashcore" != meta ] && dns_resolver='223.5.5.5'
cat >"$TMPDIR"/managed-dns.yaml <<EOF
dns:
enable: true
listen: :$dns_port
Expand All @@ -42,27 +48,29 @@ dns:
fake-ip-filter:
EOF
if [ "$dns_mod" = "mix" ] || [ "$dns_mod" = "fake-ip" ]; then
cat "$CRASHDIR"/configs/fake_ip_filter "$CRASHDIR"/configs/fake_ip_filter.list 2>/dev/null | grep -v '#' | sed "s/^/ - '/" | sed "s/$/'/" >>"$TMPDIR"/dns.yaml
cat "$CRASHDIR"/configs/fake_ip_filter "$CRASHDIR"/configs/fake_ip_filter.list 2>/dev/null | grep -v '#' | sed "s/^/ - '/" | sed "s/$/'/" >>"$TMPDIR"/managed-dns.yaml
else
echo " - '+.*'" >>"$TMPDIR"/dns.yaml #使用fake-ip模拟redir_host
echo " - '+.*'" >>"$TMPDIR"/managed-dns.yaml #使用fake-ip模拟redir_host
fi
#mix模式fakeip绕过cn
[ "$dns_mod" = "mix" ] && echo ' - "rule-set:cn"' >>"$TMPDIR"/dns.yaml
[ "$dns_mod" = "mix" ] && echo ' - "rule-set:cn"' >>"$TMPDIR"/managed-dns.yaml
#mix模式和route模式插入分流设置
if [ "$dns_mod" = "mix" ] || [ "$dns_mod" = "route" ]; then
[ "$dns_protect" != "OFF" ] && dns_final="$dns_fallback" || dns_final="$dns_nameserver"
cat >>"$TMPDIR"/dns.yaml <<EOF
cat >>"$TMPDIR"/managed-dns.yaml <<EOF
respect-rules: true
nameserver-policy: {'rule-set:cn': [ $dns_nameserver ]}
proxy-server-nameserver : [ $dns_proxy_server ]
nameserver: [ $dns_final ]
EOF
else
cat >>"$TMPDIR"/dns.yaml <<EOF
cat >>"$TMPDIR"/managed-dns.yaml <<EOF
nameserver: [ $dns_nameserver ]
EOF
fi
}
# nameserver-policy is intentionally preserved from the subscription;
# its provider-specific routes are outside ShellCrash's managed fields.
yaml_dns_merge "$TMPDIR"/original-dns.yaml "$TMPDIR"/managed-dns.yaml "$TMPDIR"/user-dns.yaml >"$TMPDIR"/dns.yaml
#域名嗅探配置
[ "$sniffer" = "ON" ] && [ "$crashcore" = "meta" ] && sniffer_set="sniffer: {enable: true, parse-pure-ip: true, skip-domain: ['+.push.apple.com', 'Mijia Cloud'], sniff: {http: {ports: [80, 8080-8880], override-destination: true}, tls: {ports: [443, 8443]}, quic: {ports: [443, 8443]}}}"
[ "$crashcore" = "clashpre" ] && [ "$dns_mod" = "redir_host" -o "$sniffer" = "ON" ] && exper="experimental: {ignore-resolve-fail: true, interface-name: en0,sniff-tls-sni: true}"
Expand Down Expand Up @@ -212,7 +220,7 @@ merger_yaml() {
sed -i 's/^ *-/ -/g' "$TMPDIR"/rules.yaml
#合并文件
[ -s "$CRASHDIR"/yamls/user.yaml ] && {
yaml_user="$CRASHDIR"/yamls/user.yaml
yaml_user="$TMPDIR"/user.yaml
#set和user去重,且优先使用user.yaml
cp -f "$TMPDIR"/set.yaml "$TMPDIR"/set_bak.yaml
for char in mode allow-lan log-level tun experimental external-ui-url interface-name dns store-selected unified-delay; do
Expand Down Expand Up @@ -253,7 +261,7 @@ finalize_clash_yaml() {
#建立软连接
[ ""$TMPDIR"" = ""$BINDIR"" ] || ln -sf "$TMPDIR"/config.yaml "$BINDIR"/config.yaml 2>/dev/null || cp -f "$TMPDIR"/config.yaml "$BINDIR"/config.yaml
#清理缓存
for char in $yaml_char set set_bak dns hosts; do
for char in $yaml_char set set_bak dns managed-dns original-dns user-dns user hosts; do
rm -f "$TMPDIR"/${char}.yaml
done
}
Expand Down
Loading