Skip to content

Commit aa0914d

Browse files
committed
fix(dns): own resolver runtime while box service is active
1 parent 14f9009 commit aa0914d

16 files changed

Lines changed: 1022 additions & 75 deletions

lib/common.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,3 +323,15 @@ read_pid_file() {
323323
tr -d '[:space:]' <"${pid_file}"
324324
fi
325325
}
326+
327+
spawn_detached_process() {
328+
local log_file="${1:?missing log file}"
329+
local pid
330+
shift
331+
[[ "$#" -gt 0 ]] || return 1
332+
333+
nohup "$@" >>"${log_file}" 2>&1 </dev/null &
334+
pid="$!"
335+
disown "${pid}" 2>/dev/null || disown 2>/dev/null || true
336+
printf '%s\n' "${pid}"
337+
}

lib/config.sh

Lines changed: 154 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ BOX_DNS_ENHANCED_MODE=""
1818
BOX_DNS_COEXIST_MODE=""
1919
BOX_IPV6_ENABLED=""
2020
BOX_CAMPUS_DNS_MODE=""
21+
BOX_CAMPUS_DNS_SUFFIX_POLICY=""
2122
BOX_TAILSCALE_IFACE=""
2223
BOX_TAILNET_IPV4_CIDR=""
2324
BOX_TAILNET_IPV6_CIDR=""
@@ -119,6 +120,7 @@ config_defaults() {
119120
BOX_DNS_COEXIST_MODE="preserve_tailnet"
120121
BOX_IPV6_ENABLED="true"
121122
BOX_CAMPUS_DNS_MODE="auto"
123+
BOX_CAMPUS_DNS_SUFFIX_POLICY="org_only"
122124
BOX_TAILSCALE_IFACE="tailscale0"
123125
BOX_TAILNET_IPV4_CIDR="100.64.0.0/10"
124126
BOX_TAILNET_IPV6_CIDR="fd7a:115c:a1e0::/48"
@@ -486,6 +488,14 @@ normalize_org_dns_mode() {
486488
esac
487489
}
488490

491+
normalize_org_dns_suffix_policy() {
492+
case "${1:-}" in
493+
""|org|org_only) printf 'org_only\n' ;;
494+
best_match|link) printf 'best_match\n' ;;
495+
*) printf '%s\n' "${1:-}" ;;
496+
esac
497+
}
498+
489499
box_active_default_iface() {
490500
local ip_cmd="${BOX_IP_CMD:-ip}"
491501
command -v "${ip_cmd}" >/dev/null 2>&1 || return 1
@@ -508,6 +518,28 @@ box_active_link_dns_servers() {
508518
'
509519
}
510520

521+
box_dns_link_ifaces() {
522+
local resolvectl_cmd="${BOX_RESOLVECTL_CMD:-resolvectl}"
523+
command -v "${resolvectl_cmd}" >/dev/null 2>&1 || return 1
524+
"${resolvectl_cmd}" dns 2>/dev/null | awk '
525+
/^Link [0-9]+ \([^)]*\):/ {
526+
iface = $3
527+
gsub(/^\(/, "", iface)
528+
gsub(/\):$/, "", iface)
529+
has_ipv4 = 0
530+
for (i = 4; i <= NF; i++) {
531+
if ($i ~ /^([0-9]{1,3}\.){3}[0-9]{1,3}$/) {
532+
has_ipv4 = 1
533+
break
534+
}
535+
}
536+
if (iface != "" && has_ipv4) {
537+
print iface
538+
}
539+
}
540+
'
541+
}
542+
511543
box_probe_host_via_dns() {
512544
local server="${1:-}"
513545
local host="${2:-}"
@@ -528,6 +560,101 @@ box_is_private_ipv4() {
528560
return 1
529561
}
530562

563+
box_org_dns_probe_count() {
564+
local probe_host count=0
565+
for probe_host in "${BOX_CAMPUS_DNS_PROBE_HOSTS[@]}"; do
566+
[[ -n "${probe_host}" ]] || continue
567+
((count += 1))
568+
done
569+
printf '%s\n' "${count}"
570+
}
571+
572+
box_org_dns_probe_score_for_iface() {
573+
local iface="${1:-}"
574+
local dns_server probe_host answer
575+
local -a dns_servers=()
576+
local score=0
577+
578+
[[ -n "${iface}" ]] || {
579+
printf '0\n'
580+
return 0
581+
}
582+
583+
mapfile -t dns_servers < <(box_active_link_dns_servers "${iface}" || true)
584+
if [[ "${#dns_servers[@]}" -eq 0 ]]; then
585+
printf '0\n'
586+
return 0
587+
fi
588+
589+
for probe_host in "${BOX_CAMPUS_DNS_PROBE_HOSTS[@]}"; do
590+
[[ -n "${probe_host}" ]] || continue
591+
for dns_server in "${dns_servers[@]}"; do
592+
answer="$(box_probe_host_via_dns "${dns_server}" "${probe_host}" || true)"
593+
if [[ -n "${answer}" ]] && box_is_private_ipv4 "${answer}"; then
594+
((score += 1))
595+
break
596+
fi
597+
done
598+
done
599+
600+
printf '%s\n' "${score}"
601+
}
602+
603+
box_detect_org_dns_iface() {
604+
local iface required_score score default_iface first_full_match=""
605+
local -a candidate_ifaces=()
606+
607+
required_score="$(box_org_dns_probe_count)"
608+
[[ "${required_score}" -gt 0 ]] || return 1
609+
default_iface="$(box_active_default_iface || true)"
610+
611+
mapfile -t candidate_ifaces < <(box_dns_link_ifaces || true)
612+
if [[ "${#candidate_ifaces[@]}" -eq 0 ]]; then
613+
[[ -n "${default_iface}" ]] && candidate_ifaces=("${default_iface}")
614+
fi
615+
616+
for iface in "${candidate_ifaces[@]}"; do
617+
[[ -n "${iface}" ]] || continue
618+
score="$(box_org_dns_probe_score_for_iface "${iface}")"
619+
if [[ "${score}" -eq "${required_score}" ]]; then
620+
if [[ -z "${first_full_match}" ]]; then
621+
first_full_match="${iface}"
622+
fi
623+
if [[ -n "${default_iface}" && "${iface}" == "${default_iface}" ]]; then
624+
printf '%s\n' "${iface}"
625+
return 0
626+
fi
627+
fi
628+
done
629+
630+
[[ -n "${first_full_match}" ]] || return 1
631+
printf '%s\n' "${first_full_match}"
632+
}
633+
634+
box_best_org_dns_candidate_iface() {
635+
local iface best_iface="" score best_score=0 default_iface
636+
local -a candidate_ifaces=()
637+
default_iface="$(box_active_default_iface || true)"
638+
639+
mapfile -t candidate_ifaces < <(box_dns_link_ifaces || true)
640+
if [[ "${#candidate_ifaces[@]}" -eq 0 ]]; then
641+
[[ -n "${default_iface}" ]] && candidate_ifaces=("${default_iface}")
642+
fi
643+
644+
for iface in "${candidate_ifaces[@]}"; do
645+
[[ -n "${iface}" ]] || continue
646+
score="$(box_org_dns_probe_score_for_iface "${iface}")"
647+
if [[ "${score}" -gt "${best_score}" ]] || \
648+
([[ "${score}" -eq "${best_score}" ]] && [[ -n "${default_iface}" && "${iface}" == "${default_iface}" ]] && [[ "${best_iface}" != "${default_iface}" ]]); then
649+
best_score="${score}"
650+
best_iface="${iface}"
651+
fi
652+
done
653+
654+
[[ -n "${best_iface}" ]] || return 1
655+
printf '%s\n' "${best_iface}"
656+
}
657+
531658
box_org_dns_mode_configured() {
532659
local mode
533660
if [[ "${#BOX_CAMPUS_DNS_SUFFIXES[@]}" -eq 0 ]]; then
@@ -539,8 +666,7 @@ box_org_dns_mode_configured() {
539666
}
540667

541668
box_detect_org_dns_mode() {
542-
local configured_mode iface dns_server probe_host answer
543-
local -a dns_servers=()
669+
local configured_mode detected_iface
544670

545671
configured_mode="$(box_org_dns_mode_configured)"
546672
case "${configured_mode}" in
@@ -550,28 +676,24 @@ box_detect_org_dns_mode() {
550676
;;
551677
esac
552678

553-
iface="$(box_active_default_iface || true)"
554-
mapfile -t dns_servers < <(box_active_link_dns_servers "${iface}" || true)
555-
if [[ "${#dns_servers[@]}" -eq 0 ]]; then
679+
detected_iface="$(box_detect_org_dns_iface || true)"
680+
if [[ -n "${detected_iface}" ]]; then
681+
printf 'org\n'
682+
else
556683
printf 'public\n'
557-
return 0
558684
fi
559-
560-
for probe_host in "${BOX_CAMPUS_DNS_PROBE_HOSTS[@]}"; do
561-
[[ -n "${probe_host}" ]] || continue
562-
for dns_server in "${dns_servers[@]}"; do
563-
answer="$(box_probe_host_via_dns "${dns_server}" "${probe_host}" || true)"
564-
if [[ -n "${answer}" ]] && box_is_private_ipv4 "${answer}"; then
565-
printf 'org\n'
566-
return 0
567-
fi
568-
done
569-
done
570-
571-
printf 'public\n'
572685
}
573686

574687
box_org_dns_status_iface() {
688+
local active_mode detected_iface
689+
active_mode="$(box_detect_org_dns_mode)"
690+
if [[ "${active_mode}" == "org" ]]; then
691+
detected_iface="$(box_detect_org_dns_iface || true)"
692+
if [[ -n "${detected_iface}" ]]; then
693+
printf '%s\n' "${detected_iface}"
694+
return 0
695+
fi
696+
fi
575697
box_active_default_iface || true
576698
}
577699

@@ -660,6 +782,15 @@ validate_config() {
660782
;;
661783
esac
662784

785+
case "$(normalize_org_dns_suffix_policy "${BOX_CAMPUS_DNS_SUFFIX_POLICY}")" in
786+
org_only|best_match) ;;
787+
*)
788+
log "ERROR" "config" "E_CONFIG_ORG_DNS_SUFFIX_POLICY" \
789+
"network org_dns_suffix_policy must be org_only|best_match: ${BOX_CAMPUS_DNS_SUFFIX_POLICY}"
790+
return "${E_CONFIG}"
791+
;;
792+
esac
793+
663794
case "${BOX_DNS_COEXIST_MODE}" in
664795
preserve_tailnet|strict_box) ;;
665796
*)
@@ -870,7 +1001,7 @@ load_config() {
8701001
log "WARN" "config" "W_CONFIG_DEFAULTS" "no box.toml found; using defaults"
8711002
validate_config
8721003
export BOX_CONFIG_FILE BOX_CONFIG_SOURCE
873-
export BOX_CORE BOX_NETWORK_MODE BOX_TPROXY_PORT BOX_REDIR_PORT BOX_DNS_PORT BOX_DNS_HIJACK_MODE BOX_DNS_ENHANCED_MODE BOX_DNS_COEXIST_MODE BOX_IPV6_ENABLED BOX_CAMPUS_DNS_MODE
1004+
export BOX_CORE BOX_NETWORK_MODE BOX_TPROXY_PORT BOX_REDIR_PORT BOX_DNS_PORT BOX_DNS_HIJACK_MODE BOX_DNS_ENHANCED_MODE BOX_DNS_COEXIST_MODE BOX_IPV6_ENABLED BOX_CAMPUS_DNS_MODE BOX_CAMPUS_DNS_SUFFIX_POLICY
8741005
export BOX_TAILSCALE_IFACE BOX_TAILNET_IPV4_CIDR BOX_TAILNET_IPV6_CIDR BOX_TAILSCALE_DNS_RESOLVER BOX_TAILSCALE_FWMARK BOX_TAILSCALE_ROUTE_TABLE
8751006
export BOX_FIREWALL_BACKEND BOX_ROUTE_TABLE BOX_ROUTE_PREF BOX_FWMARK BOX_BYPASS_PRIVATE_IP BOX_BYPASS_CN_IP BOX_BYPASS_CN_FILE
8761007
export BOX_POLICY_ENABLED BOX_POLICY_PROXY_MODE BOX_POLICY_DEBOUNCE_SECONDS BOX_POLICY_USE_MODULE_ON_WIFI_DISCONNECT BOX_POLICY_DISABLE_MARKER
@@ -907,6 +1038,7 @@ load_config() {
9071038
BOX_DNS_COEXIST_MODE="$(config_read_value "${BOX_CONFIG_FILE}" "network" "dns_coexist_mode" || printf '%s' "${BOX_DNS_COEXIST_MODE}")"
9081039
BOX_IPV6_ENABLED="$(config_read_value "${BOX_CONFIG_FILE}" "network" "ipv6" || printf '%s' "${BOX_IPV6_ENABLED}")"
9091040
BOX_CAMPUS_DNS_MODE="$(config_read_value_first "${BOX_CONFIG_FILE}" "network" "org_dns_mode" "campus_dns_mode" || printf '%s' "${BOX_CAMPUS_DNS_MODE}")"
1041+
BOX_CAMPUS_DNS_SUFFIX_POLICY="$(config_read_value_first "${BOX_CONFIG_FILE}" "network" "org_dns_suffix_policy" "campus_dns_suffix_policy" || printf '%s' "${BOX_CAMPUS_DNS_SUFFIX_POLICY}")"
9101042
BOX_TAILSCALE_IFACE="$(config_read_value "${BOX_CONFIG_FILE}" "network" "tailscale_iface" || printf '%s' "${BOX_TAILSCALE_IFACE}")"
9111043
BOX_TAILNET_IPV4_CIDR="$(config_read_value "${BOX_CONFIG_FILE}" "network" "tailnet_ipv4_cidr" || printf '%s' "${BOX_TAILNET_IPV4_CIDR}")"
9121044
BOX_TAILNET_IPV6_CIDR="$(config_read_value "${BOX_CONFIG_FILE}" "network" "tailnet_ipv6_cidr" || printf '%s' "${BOX_TAILNET_IPV6_CIDR}")"
@@ -917,6 +1049,7 @@ load_config() {
9171049
mapfile -t BOX_CAMPUS_DNS_PROBE_HOSTS < <(config_read_array_first "${BOX_CONFIG_FILE}" "network" "org_dns_probe_hosts" "campus_dns_probe_hosts" || printf '%s\n' "${BOX_CAMPUS_DNS_PROBE_HOSTS[@]}")
9181050
mapfile -t BOX_CAMPUS_DNS_PUBLIC_SERVERS < <(config_read_array_first "${BOX_CONFIG_FILE}" "network" "org_dns_public_servers" "campus_dns_public_servers" || printf '%s\n' "${BOX_CAMPUS_DNS_PUBLIC_SERVERS[@]}")
9191051
BOX_CAMPUS_DNS_MODE="$(normalize_org_dns_mode "${BOX_CAMPUS_DNS_MODE}")"
1052+
BOX_CAMPUS_DNS_SUFFIX_POLICY="$(normalize_org_dns_suffix_policy "${BOX_CAMPUS_DNS_SUFFIX_POLICY}")"
9201053

9211054
BOX_FIREWALL_BACKEND="$(config_read_value "${BOX_CONFIG_FILE}" "firewall" "backend" || printf '%s' "${BOX_FIREWALL_BACKEND}")"
9221055
BOX_ROUTE_TABLE="$(config_read_value "${BOX_CONFIG_FILE}" "firewall" "route_table" || printf '%s' "${BOX_ROUTE_TABLE}")"
@@ -1008,7 +1141,7 @@ load_config() {
10081141
fi
10091142

10101143
export BOX_CONFIG_FILE BOX_CONFIG_SOURCE
1011-
export BOX_CORE BOX_NETWORK_MODE BOX_TPROXY_PORT BOX_REDIR_PORT BOX_DNS_PORT BOX_DNS_HIJACK_MODE BOX_DNS_ENHANCED_MODE BOX_DNS_COEXIST_MODE BOX_IPV6_ENABLED BOX_CAMPUS_DNS_MODE
1144+
export BOX_CORE BOX_NETWORK_MODE BOX_TPROXY_PORT BOX_REDIR_PORT BOX_DNS_PORT BOX_DNS_HIJACK_MODE BOX_DNS_ENHANCED_MODE BOX_DNS_COEXIST_MODE BOX_IPV6_ENABLED BOX_CAMPUS_DNS_MODE BOX_CAMPUS_DNS_SUFFIX_POLICY
10121145
export BOX_TAILSCALE_IFACE BOX_TAILNET_IPV4_CIDR BOX_TAILNET_IPV6_CIDR BOX_TAILSCALE_DNS_RESOLVER BOX_TAILSCALE_FWMARK BOX_TAILSCALE_ROUTE_TABLE
10131146
export BOX_FIREWALL_BACKEND BOX_ROUTE_TABLE BOX_ROUTE_PREF BOX_FWMARK BOX_BYPASS_PRIVATE_IP BOX_BYPASS_CN_IP BOX_BYPASS_CN_FILE
10141147
export BOX_POLICY_ENABLED BOX_POLICY_PROXY_MODE BOX_POLICY_DEBOUNCE_SECONDS BOX_POLICY_USE_MODULE_ON_WIFI_DISCONNECT BOX_POLICY_DISABLE_MARKER

lib/firewall/backend_iptables.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,22 @@ backend_iptables_apply_tailscale_bypass() {
339339
# TODO(phase-3): add dedicated ip6tables/nft backend for explicit v6 chain rules.
340340
}
341341

342+
backend_iptables_apply_org_dns_bypass() {
343+
local dns_server
344+
local -a dns_servers=()
345+
346+
mapfile -t dns_servers < <(firewall_org_dns_bypass_servers || true)
347+
[[ "${#dns_servers[@]}" -gt 0 ]] || return 0
348+
349+
for dns_server in "${dns_servers[@]}"; do
350+
[[ "${dns_server}" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || continue
351+
backend_iptables_add_rule_checked mangle "${BOX_CHAIN_DNS_MANGLE}" -d "${dns_server}" -p udp --dport 53 -j RETURN
352+
backend_iptables_add_rule_checked mangle "${BOX_CHAIN_DNS_MANGLE}" -d "${dns_server}" -p tcp --dport 53 -j RETURN
353+
backend_iptables_add_rule_checked nat "${BOX_CHAIN_DNS_NAT}" -d "${dns_server}" -p udp --dport 53 -j RETURN
354+
backend_iptables_add_rule_checked nat "${BOX_CHAIN_DNS_NAT}" -d "${dns_server}" -p tcp --dport 53 -j RETURN
355+
done
356+
}
357+
342358
backend_iptables_apply_kernel_bypass() {
343359
if firewall_bool_enabled "${BOX_BYPASS_PRIVATE_IP:-false}"; then
344360
if ! backend_iptables_apply_private_bypass; then
@@ -584,6 +600,12 @@ backend_iptables_apply_mode() {
584600
FW_TAILSCALE_BYPASS_APPLIED="false"
585601
fi
586602

603+
if ! backend_iptables_apply_org_dns_bypass; then
604+
FW_LAST_ERROR="failed to apply org dns bypass"
605+
backend_iptables_cleanup || true
606+
return "${E_FIREWALL_APPLY}"
607+
fi
608+
587609
if ! backend_iptables_apply_mode_rules "${mode}"; then
588610
FW_LAST_ERROR="${FW_LAST_ERROR:-failed to apply mode rules}"
589611
backend_iptables_cleanup || true

lib/firewall/backend_nft.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,24 @@ EOF
273273
fi
274274
}
275275

276+
backend_nft_build_org_dns_bypass_rules() {
277+
local dns_server
278+
local -a dns_servers=()
279+
280+
mapfile -t dns_servers < <(firewall_org_dns_bypass_servers || true)
281+
[[ "${#dns_servers[@]}" -gt 0 ]] || return 0
282+
283+
for dns_server in "${dns_servers[@]}"; do
284+
[[ "${dns_server}" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || continue
285+
cat <<EOF
286+
add rule inet ${BOX_NFT_TABLE_INET} box_dns ip daddr ${dns_server} udp dport 53 return
287+
add rule inet ${BOX_NFT_TABLE_INET} box_dns ip daddr ${dns_server} tcp dport 53 return
288+
add rule ip ${BOX_NFT_TABLE_IP} box_dns ip daddr ${dns_server} udp dport 53 return
289+
add rule ip ${BOX_NFT_TABLE_IP} box_dns ip daddr ${dns_server} tcp dport 53 return
290+
EOF
291+
done
292+
}
293+
276294
backend_nft_build_ruleset() {
277295
local mode="${1:?missing mode}"
278296
local mark_value tailscale_mark tailscale_mask
@@ -311,6 +329,7 @@ EOF
311329

312330
backend_nft_build_bypass_sets || return "${E_FIREWALL_APPLY}"
313331
backend_nft_build_bypass_rules || return "${E_FIREWALL_APPLY}"
332+
backend_nft_build_org_dns_bypass_rules || return "${E_FIREWALL_APPLY}"
314333

315334
if [[ "${BOX_DNS_COEXIST_MODE}" == "preserve_tailnet" ]]; then
316335
cat <<EOF

lib/firewall/firewall.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ firewall_org_dns_iface() {
1919
box_org_dns_status_iface
2020
}
2121

22+
firewall_org_dns_bypass_servers() {
23+
local active_mode
24+
active_mode="$(firewall_org_dns_mode_active)"
25+
[[ "${active_mode}" == "org" ]] || return 0
26+
box_org_dns_status_servers || true
27+
}
28+
2229
firewall_bool_enabled() {
2330
case "${1:-}" in
2431
true|1) return 0 ;;

lib/policy/policy.sh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,8 +292,7 @@ policy_enable() {
292292
fi
293293

294294
rm -f "${pid_file}"
295-
nohup "${BOXCTL_SELF_PATH}" policy monitor >>"${BOX_LOG_DIR}/policy.log" 2>&1 &
296-
disown || true
295+
spawn_detached_process "${BOX_LOG_DIR}/policy.log" "${BOXCTL_SELF_PATH}" policy monitor >/dev/null
297296

298297
for attempt in $(seq 1 15); do
299298
sleep 0.2

lib/supervisor/adapter_mihomo.sh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ adapter_mihomo_start() {
3131
local workdir="${3:?missing workdir}"
3232
local service_log="${4:?missing service log path}"
3333

34-
"${bin}" -d "${workdir}" -f "${rendered_config}" >>"${service_log}" 2>&1 &
35-
printf '%s\n' "$!"
34+
spawn_detached_process "${service_log}" "${bin}" -d "${workdir}" -f "${rendered_config}"
3635
}
3736

3837
adapter_mihomo_read_value() {

lib/supervisor/adapter_sing_box.sh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ adapter_sing_box_start() {
3131
local workdir="${3:?missing workdir}"
3232
local service_log="${4:?missing service log path}"
3333

34-
"${bin}" run -c "${rendered_config}" -D "${workdir}" >>"${service_log}" 2>&1 &
35-
printf '%s\n' "$!"
34+
spawn_detached_process "${service_log}" "${bin}" run -c "${rendered_config}" -D "${workdir}"
3635
}
3736

3837
adapter_sing_box_reload() {

0 commit comments

Comments
 (0)