@sunjiehao/zyhy-network 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -0
- package/bin/zyhy-network +21 -0
- package/bin/zyhy-network.js +56 -0
- package/install.sh +22 -0
- package/package.json +44 -0
- package/zyhy-private-split +987 -0
- package/zyhy-private-split.conf +10 -0
package/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# ZYHY-Network
|
|
2
|
+
|
|
3
|
+
当前版本:`V0.2.1`
|
|
4
|
+
|
|
5
|
+
办公时不用再在「网线」和「公司 Wi-Fi」之间来回切。
|
|
6
|
+
|
|
7
|
+
- 看网页、刷视频、走代理:用网线
|
|
8
|
+
- 打开 OA、邮箱、iHR:自动走公司 Wi-Fi `ZYHY-Private`
|
|
9
|
+
|
|
10
|
+
两个可以同时开着。
|
|
11
|
+
|
|
12
|
+
## 你需要
|
|
13
|
+
|
|
14
|
+
1. 一台 Mac
|
|
15
|
+
2. 插上公司网线
|
|
16
|
+
3. Wi-Fi 连上 **ZYHY-Private**(不要连别的公司 Wi-Fi)
|
|
17
|
+
|
|
18
|
+
## 怎么装
|
|
19
|
+
|
|
20
|
+
打开「终端」,把下面三行粘贴进去,回车,输入开机密码:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
git clone https://github.com/lampardrodgers/ZYHY-Network.git
|
|
24
|
+
cd ZYHY-Network
|
|
25
|
+
sudo ./install.sh
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
装完后:
|
|
29
|
+
|
|
30
|
+
1. 用 **新标签页** 打开 OA、邮箱(不要刷新刚才失败的那一页)
|
|
31
|
+
2. 如果用的是 Chrome:设置 → 隐私和安全 → 安全 → 关掉「使用安全 DNS」
|
|
32
|
+
|
|
33
|
+
能打开就行,不用再配域名。
|
|
34
|
+
|
|
35
|
+
## 用的时候注意
|
|
36
|
+
|
|
37
|
+
- 网线要插着,Wi-Fi 必须是 `ZYHY-Private`,分流才会开
|
|
38
|
+
- 拔掉网线、或换成别的 Wi-Fi:全部按系统原来的方式上网,内网入口可能会打不开
|
|
39
|
+
- 插回网线并连回 `ZYHY-Private`:等几秒会自动恢复
|
|
40
|
+
|
|
41
|
+
后台会监听 macOS 的网络状态变化,只在有线网、公司 Wi-Fi、网关或 DNS
|
|
42
|
+
等实际发生变化时更新分流配置。另有每 5 分钟一次的低频校验,用来补偿
|
|
43
|
+
偶尔漏掉的系统事件和内网域名解析变化;配置内容没变时不会重写网络设置。
|
|
44
|
+
|
|
45
|
+
GitHub 安装和 npm 安装提供完全相同的命令。需要立即重新解析内网 DNS
|
|
46
|
+
并校验全部分流配置时:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
zyhy-network refresh
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## npm 命令行版本
|
|
53
|
+
|
|
54
|
+
也可以从 npm 安装:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install -g @sunjiehao/zyhy-network
|
|
58
|
+
zyhy-network install
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
无论使用哪种安装方式,需要修改系统配置时都会自动请求管理员权限。常用命令:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
zyhy-network status
|
|
65
|
+
zyhy-network refresh
|
|
66
|
+
zyhy-network add '*.hq.cmcc'
|
|
67
|
+
zyhy-network uninstall
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 不想用了
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
zyhy-network uninstall
|
|
74
|
+
```
|
package/bin/zyhy-network
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# zyhy-network managed GitHub-install wrapper
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
TARGET="/usr/local/sbin/zyhy-private-split"
|
|
6
|
+
|
|
7
|
+
if [[ ! -x "$TARGET" ]]; then
|
|
8
|
+
echo "zyhy-network is not installed; run sudo ./install.sh first" >&2
|
|
9
|
+
exit 1
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
command=${1:-status}
|
|
13
|
+
case "$command" in
|
|
14
|
+
add|apply|clear|install|refresh|remove|uninstall)
|
|
15
|
+
if [[ $(id -u) -ne 0 ]]; then
|
|
16
|
+
exec /usr/bin/sudo "$TARGET" "$@"
|
|
17
|
+
fi
|
|
18
|
+
;;
|
|
19
|
+
esac
|
|
20
|
+
|
|
21
|
+
exec "$TARGET" "$@"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { spawnSync } = require('node:child_process');
|
|
7
|
+
const packageJson = require('../package.json');
|
|
8
|
+
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
|
|
11
|
+
if (args.length === 1 && (args[0] === '--version' || args[0] === '-v')) {
|
|
12
|
+
console.log(packageJson.version);
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (process.platform !== 'darwin') {
|
|
17
|
+
console.error('zyhy-network only supports macOS.');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const script = path.join(__dirname, '..', 'zyhy-private-split');
|
|
22
|
+
const command = args[0] || 'status';
|
|
23
|
+
const rootCommands = new Set([
|
|
24
|
+
'add',
|
|
25
|
+
'apply',
|
|
26
|
+
'clear',
|
|
27
|
+
'install',
|
|
28
|
+
'refresh',
|
|
29
|
+
'remove',
|
|
30
|
+
'uninstall'
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
let executable = '/bin/bash';
|
|
34
|
+
let commandArgs = [script, ...args];
|
|
35
|
+
|
|
36
|
+
if (rootCommands.has(command) && process.getuid?.() !== 0) {
|
|
37
|
+
executable = '/usr/bin/sudo';
|
|
38
|
+
commandArgs = ['/bin/bash', script, ...args];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const result = spawnSync(executable, commandArgs, { stdio: 'inherit' });
|
|
42
|
+
|
|
43
|
+
if (result.error) {
|
|
44
|
+
console.error(`failed to run zyhy-network: ${result.error.message}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (result.signal) {
|
|
49
|
+
if (result.signal === 'SIGPIPE') {
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
console.error(`zyhy-network stopped by signal ${result.signal}`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
process.exit(result.status ?? 1);
|
package/install.sh
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# 开箱安装:拷配置并启用分流。需要 sudo。
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
if [[ $(id -u) -ne 0 ]]; then
|
|
5
|
+
echo "请用: sudo $0" >&2
|
|
6
|
+
exit 1
|
|
7
|
+
fi
|
|
8
|
+
cd "$(dirname "$0")"
|
|
9
|
+
chmod 755 zyhy-private-split bin/zyhy-network
|
|
10
|
+
mkdir -p /usr/local/bin /usr/local/etc /usr/local/sbin
|
|
11
|
+
cp zyhy-private-split.conf /usr/local/etc/zyhy-private-split.conf
|
|
12
|
+
./zyhy-private-split install
|
|
13
|
+
if [[ -e /usr/local/bin/zyhy-network ]] && \
|
|
14
|
+
! grep -q '^# zyhy-network managed GitHub-install wrapper$' /usr/local/bin/zyhy-network 2>/dev/null; then
|
|
15
|
+
echo "保留已有的 /usr/local/bin/zyhy-network(它不是本项目安装的文件)。" >&2
|
|
16
|
+
else
|
|
17
|
+
cp bin/zyhy-network /usr/local/bin/zyhy-network
|
|
18
|
+
chmod 755 /usr/local/bin/zyhy-network
|
|
19
|
+
fi
|
|
20
|
+
echo
|
|
21
|
+
echo "装好了。新开浏览器标签访问 OA / 邮箱即可。"
|
|
22
|
+
echo "查看状态: zyhy-network status"
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sunjiehao/zyhy-network",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "macOS Ethernet and ZYHY-Private Wi-Fi split routing CLI",
|
|
5
|
+
"bin": {
|
|
6
|
+
"zyhy-network": "bin/zyhy-network.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"zyhy-private-split",
|
|
11
|
+
"zyhy-private-split.conf",
|
|
12
|
+
"install.sh",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "bash -n zyhy-private-split install.sh bin/zyhy-network && node --check bin/zyhy-network.js",
|
|
17
|
+
"prepack": "npm test"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/lampardrodgers/ZYHY-Network.git"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/lampardrodgers/ZYHY-Network/issues"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/lampardrodgers/ZYHY-Network#readme",
|
|
30
|
+
"keywords": [
|
|
31
|
+
"macos",
|
|
32
|
+
"network",
|
|
33
|
+
"split-routing",
|
|
34
|
+
"launchd"
|
|
35
|
+
],
|
|
36
|
+
"author": "sunjiehao (lampardrodgers)",
|
|
37
|
+
"license": "UNLICENSED",
|
|
38
|
+
"os": [
|
|
39
|
+
"darwin"
|
|
40
|
+
],
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,987 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Split-route intranet via Wi-Fi only when Ethernet is up AND Wi-Fi DHCP
|
|
3
|
+
# domain is ZYHY-Private. Otherwise keep system order (Ethernet first,
|
|
4
|
+
# Wi-Fi if the cable is unplugged). Does not edit Shadowrocket.
|
|
5
|
+
#
|
|
6
|
+
# sudo zyhy-private-split install
|
|
7
|
+
# sudo zyhy-private-split add oa.hq.cmcc
|
|
8
|
+
# sudo zyhy-private-split add '*.hq.cmcc' # whole suffix
|
|
9
|
+
# sudo zyhy-private-split add 172.21.0.0/16 # whole CIDR
|
|
10
|
+
# sudo zyhy-private-split remove oa.hq.cmcc
|
|
11
|
+
# sudo zyhy-private-split refresh
|
|
12
|
+
# zyhy-private-split list
|
|
13
|
+
# zyhy-private-split status
|
|
14
|
+
# zyhy-private-split bundle ~/Desktop/zyhy-private-split.tgz
|
|
15
|
+
# sudo zyhy-private-split uninstall
|
|
16
|
+
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
|
|
19
|
+
LABEL="com.zyhy.network.split"
|
|
20
|
+
VERSION="0.2.1"
|
|
21
|
+
PLIST="/Library/LaunchDaemons/${LABEL}.plist"
|
|
22
|
+
RECONCILE_LABEL="${LABEL}.reconcile"
|
|
23
|
+
RECONCILE_PLIST="/Library/LaunchDaemons/${RECONCILE_LABEL}.plist"
|
|
24
|
+
LEGACY_LABELS="com.misakamikoto.zyhy-private-split"
|
|
25
|
+
INSTALL_BIN="/usr/local/sbin/zyhy-private-split"
|
|
26
|
+
INSTALL_CLI="/usr/local/bin/zyhy-network"
|
|
27
|
+
CONF="/usr/local/etc/zyhy-private-split.conf"
|
|
28
|
+
STATE_DIR="/var/run/zyhy-private-split"
|
|
29
|
+
STATE_FILE="${STATE_DIR}/applied"
|
|
30
|
+
INPUT_STATE_FILE="${STATE_DIR}/network-inputs"
|
|
31
|
+
RESOLVED_STATE_FILE="${STATE_DIR}/resolved-hosts"
|
|
32
|
+
RESOLVER_LIST="${STATE_DIR}/resolvers"
|
|
33
|
+
LOCK_DIR="/var/run/zyhy-private-split.apply.lock"
|
|
34
|
+
WATCH_DIR="/var/run/zyhy-private-split-watch"
|
|
35
|
+
WATCH_COMMAND_FIFO="${WATCH_DIR}/commands"
|
|
36
|
+
WATCH_EVENT_FIFO="${WATCH_DIR}/events"
|
|
37
|
+
HOSTS_BEGIN="# BEGIN zyhy-private-split"
|
|
38
|
+
HOSTS_END="# END zyhy-private-split"
|
|
39
|
+
|
|
40
|
+
# Defaults (overridden by $CONF).
|
|
41
|
+
WIFI_DOMAIN="ZYHY-Private"
|
|
42
|
+
DOMAINS="oa.hq.cmcc mail.cmhi.chinamobile.com"
|
|
43
|
+
SUFFIXES="hq.cmcc cmhi.chinamobile.com"
|
|
44
|
+
CIDRS="172.21.0.0/16 172.16.0.0/16"
|
|
45
|
+
RECONCILE_INTERVAL="300"
|
|
46
|
+
EVENT_SETTLE_SECONDS="2"
|
|
47
|
+
|
|
48
|
+
FILES_CHANGED=0
|
|
49
|
+
WATCH_CHILD_PID=""
|
|
50
|
+
|
|
51
|
+
log() { printf '%s %s\n' "$(date '+%F %T')" "$*" >&2; }
|
|
52
|
+
|
|
53
|
+
require_root() {
|
|
54
|
+
if [[ $(id -u) -ne 0 ]]; then
|
|
55
|
+
echo "need root: sudo $0 $*" >&2
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
normalize_words() {
|
|
61
|
+
echo "$*" | tr ' \t' '\n' | sed '/^$/d' | awk '!seen[$0]++'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
load_config() {
|
|
65
|
+
if [[ -f "$CONF" ]]; then
|
|
66
|
+
# shellcheck disable=SC1090
|
|
67
|
+
. "$CONF"
|
|
68
|
+
fi
|
|
69
|
+
DOMAINS=$(normalize_words ${DOMAINS-} | tr '\n' ' ')
|
|
70
|
+
SUFFIXES=$(normalize_words ${SUFFIXES-} | tr '\n' ' ')
|
|
71
|
+
CIDRS=$(normalize_words ${CIDRS-} | tr '\n' ' ')
|
|
72
|
+
WIFI_DOMAIN=${WIFI_DOMAIN:-ZYHY-Private}
|
|
73
|
+
RECONCILE_INTERVAL=${RECONCILE_INTERVAL:-300}
|
|
74
|
+
EVENT_SETTLE_SECONDS=${EVENT_SETTLE_SECONDS:-2}
|
|
75
|
+
[[ "$RECONCILE_INTERVAL" =~ ^[0-9]+$ ]] || RECONCILE_INTERVAL=300
|
|
76
|
+
[[ "$EVENT_SETTLE_SECONDS" =~ ^[0-9]+$ ]] || EVENT_SETTLE_SECONDS=2
|
|
77
|
+
DOMAINS=${DOMAINS% }
|
|
78
|
+
SUFFIXES=${SUFFIXES% }
|
|
79
|
+
CIDRS=${CIDRS% }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
save_config() {
|
|
83
|
+
mkdir -p "$(dirname "$CONF")"
|
|
84
|
+
cat >"$CONF" <<EOF
|
|
85
|
+
# zyhy-private-split config
|
|
86
|
+
# After editing: sudo zyhy-private-split apply
|
|
87
|
+
# add hostname: sudo zyhy-private-split add foo.hq.cmcc
|
|
88
|
+
# add suffix: sudo zyhy-private-split add '*.hq.cmcc'
|
|
89
|
+
# add CIDR: sudo zyhy-private-split add 172.21.0.0/16
|
|
90
|
+
|
|
91
|
+
WIFI_DOMAIN="${WIFI_DOMAIN}"
|
|
92
|
+
DOMAINS="${DOMAINS}"
|
|
93
|
+
SUFFIXES="${SUFFIXES}"
|
|
94
|
+
CIDRS="${CIDRS}"
|
|
95
|
+
RECONCILE_INTERVAL="${RECONCILE_INTERVAL}"
|
|
96
|
+
EVENT_SETTLE_SECONDS="${EVENT_SETTLE_SECONDS}"
|
|
97
|
+
EOF
|
|
98
|
+
chmod 644 "$CONF"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
words_has() {
|
|
102
|
+
local needle=$1
|
|
103
|
+
shift
|
|
104
|
+
printf '%s\n' $* | grep -qxF "$needle"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
add_word() {
|
|
108
|
+
local cur=$1 word=$2
|
|
109
|
+
if [[ -z "$cur" ]]; then
|
|
110
|
+
echo "$word"
|
|
111
|
+
return
|
|
112
|
+
fi
|
|
113
|
+
if printf '%s\n' $cur | grep -qxF "$word"; then
|
|
114
|
+
echo "$cur"
|
|
115
|
+
return
|
|
116
|
+
fi
|
|
117
|
+
echo "$cur $word"
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
remove_word() {
|
|
121
|
+
local cur=$1 word=$2
|
|
122
|
+
printf '%s\n' $cur | grep -vxF "$word" | tr '\n' ' ' | sed 's/[[:space:]]*$//'
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
wifi_device() {
|
|
126
|
+
networksetup -listallhardwareports | awk '
|
|
127
|
+
/^Hardware Port: Wi-Fi/{getline; if ($1=="Device:") {print $2; exit}}
|
|
128
|
+
'
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
ethernet_devices() {
|
|
132
|
+
networksetup -listallhardwareports | awk '
|
|
133
|
+
/^Hardware Port: / {
|
|
134
|
+
port=$0
|
|
135
|
+
sub(/^Hardware Port: /,"",port)
|
|
136
|
+
getline
|
|
137
|
+
if ($1=="Device:") dev=$2
|
|
138
|
+
if (port=="Ethernet" || port ~ /^USB /) print dev
|
|
139
|
+
}
|
|
140
|
+
'
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
iface_active_ipv4() {
|
|
144
|
+
local ifc=$1
|
|
145
|
+
ifconfig "$ifc" 2>/dev/null | awk '
|
|
146
|
+
/status: active/{up=1}
|
|
147
|
+
$1=="inet"{ip=$2}
|
|
148
|
+
END{exit !(up && ip!="")}
|
|
149
|
+
'
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
wifi_is_private() {
|
|
153
|
+
local ifc=$1 domain
|
|
154
|
+
domain=$(ipconfig getoption "$ifc" domain_name 2>/dev/null || true)
|
|
155
|
+
[[ "$domain" == "$WIFI_DOMAIN" ]]
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
wired_up() {
|
|
159
|
+
local d
|
|
160
|
+
for d in $(ethernet_devices); do
|
|
161
|
+
if iface_active_ipv4 "$d"; then
|
|
162
|
+
echo "$d"
|
|
163
|
+
return 0
|
|
164
|
+
fi
|
|
165
|
+
done
|
|
166
|
+
return 1
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
split_should_run() {
|
|
170
|
+
local wifi eth
|
|
171
|
+
wifi=$(wifi_device)
|
|
172
|
+
[[ -n "$wifi" ]] || return 1
|
|
173
|
+
iface_active_ipv4 "$wifi" || return 1
|
|
174
|
+
wifi_is_private "$wifi" || return 1
|
|
175
|
+
eth=$(wired_up) || return 1
|
|
176
|
+
echo "$eth $wifi"
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
add_host_v4() {
|
|
180
|
+
local ip=$1 gw=$2
|
|
181
|
+
if route -n get -host "$ip" 2>/dev/null | grep -q "gateway: ${gw}$"; then
|
|
182
|
+
return 0
|
|
183
|
+
fi
|
|
184
|
+
route -n delete -host "$ip" >/dev/null 2>&1 || true
|
|
185
|
+
route -n add -host "$ip" "$gw" >/dev/null
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
del_host_v4() {
|
|
189
|
+
route -n delete -host "$1" >/dev/null 2>&1 || true
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
add_net_v4() {
|
|
193
|
+
local cidr=$1 gw=$2
|
|
194
|
+
if route -n get -net "$cidr" 2>/dev/null | grep -q "gateway: ${gw}$"; then
|
|
195
|
+
return 0
|
|
196
|
+
fi
|
|
197
|
+
route -n delete -net "$cidr" >/dev/null 2>&1 || true
|
|
198
|
+
route -n add -net "$cidr" "$gw" >/dev/null
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
del_net_v4() {
|
|
202
|
+
route -n delete -net "$1" >/dev/null 2>&1 || true
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
del_reject_v6() {
|
|
206
|
+
route -n delete -inet6 -host "$1" >/dev/null 2>&1 || true
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
strip_hosts_block() {
|
|
210
|
+
awk -v b="$HOSTS_BEGIN" -v e="$HOSTS_END" '
|
|
211
|
+
$0==b {skip=1; next}
|
|
212
|
+
$0==e {skip=0; next}
|
|
213
|
+
skip==0 {print}
|
|
214
|
+
' /etc/hosts
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
write_hosts_block() {
|
|
218
|
+
local body=$1 tmp
|
|
219
|
+
tmp=$(mktemp)
|
|
220
|
+
{
|
|
221
|
+
strip_hosts_block
|
|
222
|
+
if [[ -n "$body" ]]; then
|
|
223
|
+
printf '%s\n%s%s\n' "$HOSTS_BEGIN" "$body" "$HOSTS_END"
|
|
224
|
+
fi
|
|
225
|
+
} >"$tmp"
|
|
226
|
+
if cmp -s "$tmp" /etc/hosts; then
|
|
227
|
+
rm -f "$tmp"
|
|
228
|
+
return 0
|
|
229
|
+
fi
|
|
230
|
+
cat "$tmp" >/etc/hosts
|
|
231
|
+
rm -f "$tmp"
|
|
232
|
+
FILES_CHANGED=1
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
resolver_names() {
|
|
236
|
+
printf '%s\n' $DOMAINS $SUFFIXES | sed '/^$/d' | awk 'NF && !seen[$0]++'
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
clear_resolvers() {
|
|
240
|
+
local name
|
|
241
|
+
while IFS= read -r name; do
|
|
242
|
+
[[ -n "$name" ]] || continue
|
|
243
|
+
if [[ -e "/etc/resolver/${name}" ]]; then
|
|
244
|
+
rm -f "/etc/resolver/${name}"
|
|
245
|
+
FILES_CHANGED=1
|
|
246
|
+
fi
|
|
247
|
+
done < <(
|
|
248
|
+
{
|
|
249
|
+
[[ -f "$RESOLVER_LIST" ]] && cat "$RESOLVER_LIST"
|
|
250
|
+
resolver_names
|
|
251
|
+
} | awk 'NF && !seen[$0]++'
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
write_resolvers() {
|
|
256
|
+
local dns=$1 name old_name desired_list resolver_tmp
|
|
257
|
+
mkdir -p /etc/resolver
|
|
258
|
+
desired_list=$(mktemp)
|
|
259
|
+
resolver_names >"$desired_list"
|
|
260
|
+
|
|
261
|
+
if [[ -f "$RESOLVER_LIST" ]]; then
|
|
262
|
+
while IFS= read -r old_name; do
|
|
263
|
+
[[ -n "$old_name" ]] || continue
|
|
264
|
+
if ! grep -qxF "$old_name" "$desired_list" && [[ -e "/etc/resolver/${old_name}" ]]; then
|
|
265
|
+
rm -f "/etc/resolver/${old_name}"
|
|
266
|
+
FILES_CHANGED=1
|
|
267
|
+
fi
|
|
268
|
+
done <"$RESOLVER_LIST"
|
|
269
|
+
fi
|
|
270
|
+
|
|
271
|
+
while IFS= read -r name; do
|
|
272
|
+
[[ -n "$name" ]] || continue
|
|
273
|
+
resolver_tmp=$(mktemp)
|
|
274
|
+
cat >"$resolver_tmp" <<EOF
|
|
275
|
+
nameserver ${dns}
|
|
276
|
+
timeout 2
|
|
277
|
+
EOF
|
|
278
|
+
if ! cmp -s "$resolver_tmp" "/etc/resolver/${name}"; then
|
|
279
|
+
cat "$resolver_tmp" >"/etc/resolver/${name}"
|
|
280
|
+
FILES_CHANGED=1
|
|
281
|
+
fi
|
|
282
|
+
rm -f "$resolver_tmp"
|
|
283
|
+
done <"$desired_list"
|
|
284
|
+
|
|
285
|
+
if ! cmp -s "$desired_list" "$RESOLVER_LIST"; then
|
|
286
|
+
mv "$desired_list" "$RESOLVER_LIST"
|
|
287
|
+
else
|
|
288
|
+
rm -f "$desired_list"
|
|
289
|
+
fi
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
flush_dns() {
|
|
293
|
+
dscacheutil -flushcache >/dev/null 2>&1 || true
|
|
294
|
+
killall -HUP mDNSResponder >/dev/null 2>&1 || true
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
resolve_via_wifi() {
|
|
298
|
+
local wifi_ip=$1 dns=$2 name=$3
|
|
299
|
+
dig -b "$wifi_ip" @"$dns" +short +time=2 +tries=1 "$name" A \
|
|
300
|
+
| awk '/^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/{print}' \
|
|
301
|
+
| sort -u
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
wanted_proxy_exceptions() {
|
|
305
|
+
local d s
|
|
306
|
+
for d in $DOMAINS; do
|
|
307
|
+
printf '%s\n' "$d" "*.${d}"
|
|
308
|
+
done
|
|
309
|
+
for s in $SUFFIXES; do
|
|
310
|
+
printf '%s\n' "$s" "*.${s}"
|
|
311
|
+
done
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
http_proxy_keys() {
|
|
315
|
+
local key
|
|
316
|
+
echo "State:/Network/Global/Proxies"
|
|
317
|
+
while IFS= read -r key; do
|
|
318
|
+
[[ -n "$key" ]] || continue
|
|
319
|
+
if echo "show ${key}" | scutil | grep -q 'HTTPEnable : 1'; then
|
|
320
|
+
printf '%s\n' "$key"
|
|
321
|
+
fi
|
|
322
|
+
done < <(echo "list State:/Network/Service/.*/Proxies" | scutil | awk -F'= ' '/subKey/{print $2}')
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
read_proxy_exceptions() {
|
|
326
|
+
local key=${1-}
|
|
327
|
+
[[ -n "$key" ]] || key=$(http_proxy_keys | awk 'NR==2{print; exit} END{if(NR==1) print}')
|
|
328
|
+
echo "show ${key}" | scutil | awk '
|
|
329
|
+
/ExceptionsList[[:space:]]*:/ {inlist=1; next}
|
|
330
|
+
inlist && /}/ {exit}
|
|
331
|
+
inlist && /: / {
|
|
332
|
+
sub(/^[^:]+:[[:space:]]*/, "")
|
|
333
|
+
if ($0 != "") print
|
|
334
|
+
}
|
|
335
|
+
'
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
write_proxy_exceptions() {
|
|
339
|
+
local items=$*
|
|
340
|
+
local key
|
|
341
|
+
[[ -n "$items" ]] || return 0
|
|
342
|
+
while IFS= read -r key; do
|
|
343
|
+
[[ -n "$key" ]] || continue
|
|
344
|
+
scutil >/dev/null <<EOF
|
|
345
|
+
open
|
|
346
|
+
d.init
|
|
347
|
+
get ${key}
|
|
348
|
+
d.add ExceptionsList * ${items}
|
|
349
|
+
set ${key}
|
|
350
|
+
quit
|
|
351
|
+
EOF
|
|
352
|
+
done < <(http_proxy_keys | awk 'NF && !seen[$0]++')
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
list_has() {
|
|
356
|
+
local needle=$1
|
|
357
|
+
shift
|
|
358
|
+
local y
|
|
359
|
+
for y in "$@"; do
|
|
360
|
+
[[ "$y" == "$needle" ]] && return 0
|
|
361
|
+
done
|
|
362
|
+
return 1
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
ensure_proxy_exceptions() {
|
|
366
|
+
local -a cur=() want=() merged=()
|
|
367
|
+
local x
|
|
368
|
+
while IFS= read -r x; do
|
|
369
|
+
[[ -n "$x" ]] && cur+=("$x")
|
|
370
|
+
done < <(read_proxy_exceptions "")
|
|
371
|
+
while IFS= read -r x; do
|
|
372
|
+
[[ -n "$x" ]] && want+=("$x")
|
|
373
|
+
done < <(wanted_proxy_exceptions | awk 'NF && !seen[$0]++')
|
|
374
|
+
if [[ ${#cur[@]} -gt 0 ]]; then
|
|
375
|
+
merged=("${cur[@]}")
|
|
376
|
+
fi
|
|
377
|
+
for x in "${want[@]}"; do
|
|
378
|
+
if [[ ${#cur[@]} -gt 0 ]] && list_has "$x" "${cur[@]}"; then
|
|
379
|
+
continue
|
|
380
|
+
fi
|
|
381
|
+
merged+=("$x")
|
|
382
|
+
done
|
|
383
|
+
if [[ ${#merged[@]} -eq ${#cur[@]} ]]; then
|
|
384
|
+
return 0
|
|
385
|
+
fi
|
|
386
|
+
write_proxy_exceptions "${merged[*]}"
|
|
387
|
+
log "proxy bypass +${want[*]}"
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
remove_proxy_exceptions() {
|
|
391
|
+
local -a cur=() keep=() want=()
|
|
392
|
+
local x
|
|
393
|
+
while IFS= read -r x; do
|
|
394
|
+
[[ -n "$x" ]] && cur+=("$x")
|
|
395
|
+
done < <(read_proxy_exceptions "")
|
|
396
|
+
[[ ${#cur[@]} -gt 0 ]] || return 0
|
|
397
|
+
while IFS= read -r x; do
|
|
398
|
+
[[ -n "$x" ]] && want+=("$x")
|
|
399
|
+
done < <(wanted_proxy_exceptions)
|
|
400
|
+
for x in "${cur[@]}"; do
|
|
401
|
+
if [[ ${#want[@]} -gt 0 ]] && list_has "$x" "${want[@]}"; then
|
|
402
|
+
continue
|
|
403
|
+
fi
|
|
404
|
+
keep+=("$x")
|
|
405
|
+
done
|
|
406
|
+
if [[ ${#keep[@]} -eq ${#cur[@]} ]]; then
|
|
407
|
+
return 0
|
|
408
|
+
fi
|
|
409
|
+
write_proxy_exceptions "${keep[*]}"
|
|
410
|
+
log "proxy bypass restored"
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
drop_state_entry() {
|
|
414
|
+
local kind=$1 target=$2
|
|
415
|
+
case "$kind" in
|
|
416
|
+
v6) del_reject_v6 "$target" ;;
|
|
417
|
+
net) del_net_v4 "$target" ;;
|
|
418
|
+
*) del_host_v4 "$target" ;;
|
|
419
|
+
esac
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
clear_applied() {
|
|
423
|
+
local kind ip
|
|
424
|
+
if [[ -f "$STATE_FILE" ]]; then
|
|
425
|
+
while read -r kind ip; do
|
|
426
|
+
[[ -n "$kind" ]] || continue
|
|
427
|
+
if [[ -z "$ip" ]]; then
|
|
428
|
+
ip=$kind
|
|
429
|
+
kind=v4
|
|
430
|
+
fi
|
|
431
|
+
drop_state_entry "$kind" "$ip"
|
|
432
|
+
done <"$STATE_FILE"
|
|
433
|
+
fi
|
|
434
|
+
write_hosts_block ""
|
|
435
|
+
clear_resolvers
|
|
436
|
+
remove_proxy_exceptions
|
|
437
|
+
rm -rf "$STATE_DIR"
|
|
438
|
+
flush_dns
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
cmd_clear() {
|
|
442
|
+
require_root
|
|
443
|
+
load_config
|
|
444
|
+
clear_applied
|
|
445
|
+
log "cleared split routes"
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
write_network_inputs() {
|
|
449
|
+
local dest=$1 eth=$2 wifi=$3 wifi_ip=$4 wifi_gw=$5 wifi_dns=$6
|
|
450
|
+
{
|
|
451
|
+
printf 'ethernet=%s\n' "$eth"
|
|
452
|
+
printf 'ethernet_ip=%s\n' "$(ipconfig getifaddr "$eth" 2>/dev/null || true)"
|
|
453
|
+
printf 'wifi=%s\n' "$wifi"
|
|
454
|
+
printf 'wifi_ip=%s\n' "$wifi_ip"
|
|
455
|
+
printf 'wifi_gateway=%s\n' "$wifi_gw"
|
|
456
|
+
printf 'wifi_dns=%s\n' "$wifi_dns"
|
|
457
|
+
printf 'wifi_domain=%s\n' "$(ipconfig getoption "$wifi" domain_name 2>/dev/null || true)"
|
|
458
|
+
printf 'domains=%s\n' "$DOMAINS"
|
|
459
|
+
printf 'suffixes=%s\n' "$SUFFIXES"
|
|
460
|
+
printf 'cidrs=%s\n' "$CIDRS"
|
|
461
|
+
} >"$dest"
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
acquire_apply_lock() {
|
|
465
|
+
local old_pid=""
|
|
466
|
+
if mkdir "$LOCK_DIR" 2>/dev/null; then
|
|
467
|
+
printf '%s\n' "$$" >"${LOCK_DIR}/pid"
|
|
468
|
+
return 0
|
|
469
|
+
fi
|
|
470
|
+
[[ -r "${LOCK_DIR}/pid" ]] && old_pid=$(<"${LOCK_DIR}/pid")
|
|
471
|
+
if [[ "$old_pid" =~ ^[0-9]+$ ]] && kill -0 "$old_pid" 2>/dev/null; then
|
|
472
|
+
return 1
|
|
473
|
+
fi
|
|
474
|
+
rm -f "${LOCK_DIR}/pid"
|
|
475
|
+
rmdir "$LOCK_DIR" 2>/dev/null || return 1
|
|
476
|
+
mkdir "$LOCK_DIR"
|
|
477
|
+
printf '%s\n' "$$" >"${LOCK_DIR}/pid"
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
release_apply_lock() {
|
|
481
|
+
rm -f "${LOCK_DIR}/pid"
|
|
482
|
+
rmdir "$LOCK_DIR" 2>/dev/null || true
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
cmd_apply_locked() {
|
|
486
|
+
local force=${1-}
|
|
487
|
+
load_config
|
|
488
|
+
mkdir -p "$STATE_DIR"
|
|
489
|
+
FILES_CHANGED=0
|
|
490
|
+
local pair eth wifi wifi_ip wifi_gw wifi_dns
|
|
491
|
+
if ! pair=$(split_should_run); then
|
|
492
|
+
if [[ -f "$STATE_FILE" ]]; then
|
|
493
|
+
log "condition not met; tearing down"
|
|
494
|
+
clear_applied
|
|
495
|
+
fi
|
|
496
|
+
return 0
|
|
497
|
+
fi
|
|
498
|
+
eth=${pair%% *}
|
|
499
|
+
wifi=${pair##* }
|
|
500
|
+
wifi_ip=$(ipconfig getifaddr "$wifi")
|
|
501
|
+
wifi_gw=$(ipconfig getoption "$wifi" router)
|
|
502
|
+
wifi_dns=$(ipconfig getoption "$wifi" domain_name_server | awk '{print $1}')
|
|
503
|
+
if [[ -z "$wifi_ip" || -z "$wifi_gw" || -z "$wifi_dns" ]]; then
|
|
504
|
+
log "wifi details incomplete; skip"
|
|
505
|
+
return 0
|
|
506
|
+
fi
|
|
507
|
+
|
|
508
|
+
local input_tmp
|
|
509
|
+
input_tmp=$(mktemp)
|
|
510
|
+
write_network_inputs "$input_tmp" "$eth" "$wifi" "$wifi_ip" "$wifi_gw" "$wifi_dns"
|
|
511
|
+
if [[ "$force" != "--force" && -f "$STATE_FILE" && -f "$INPUT_STATE_FILE" ]] \
|
|
512
|
+
&& cmp -s "$input_tmp" "$INPUT_STATE_FILE"; then
|
|
513
|
+
rm -f "$input_tmp"
|
|
514
|
+
return 0
|
|
515
|
+
fi
|
|
516
|
+
|
|
517
|
+
local -a lines=()
|
|
518
|
+
local hosts_body="" d ip first cidr resolved resolved_tmp previous
|
|
519
|
+
resolved_tmp=$(mktemp)
|
|
520
|
+
add_host_v4 "$wifi_dns" "$wifi_gw"
|
|
521
|
+
lines+=("v4 $wifi_dns")
|
|
522
|
+
|
|
523
|
+
for cidr in $CIDRS; do
|
|
524
|
+
add_net_v4 "$cidr" "$wifi_gw"
|
|
525
|
+
lines+=("net $cidr")
|
|
526
|
+
done
|
|
527
|
+
|
|
528
|
+
for d in $DOMAINS; do
|
|
529
|
+
first=""
|
|
530
|
+
resolved=$(resolve_via_wifi "$wifi_ip" "$wifi_dns" "$d" || true)
|
|
531
|
+
if [[ -z "$resolved" ]]; then
|
|
532
|
+
previous=""
|
|
533
|
+
if [[ -f "$RESOLVED_STATE_FILE" ]]; then
|
|
534
|
+
previous=$(awk -v domain="$d" '$1==domain {print $2}' "$RESOLVED_STATE_FILE")
|
|
535
|
+
fi
|
|
536
|
+
if [[ -z "$previous" ]]; then
|
|
537
|
+
log "WARN: no A record for $d via $wifi_dns"
|
|
538
|
+
continue
|
|
539
|
+
fi
|
|
540
|
+
log "WARN: no A record for $d via $wifi_dns; keeping previous result"
|
|
541
|
+
resolved=$previous
|
|
542
|
+
fi
|
|
543
|
+
while read -r ip; do
|
|
544
|
+
[[ -n "$ip" ]] || continue
|
|
545
|
+
printf '%s %s\n' "$d" "$ip" >>"$resolved_tmp"
|
|
546
|
+
add_host_v4 "$ip" "$wifi_gw"
|
|
547
|
+
lines+=("v4 $ip")
|
|
548
|
+
if [[ -z "$first" ]]; then
|
|
549
|
+
first=$ip
|
|
550
|
+
hosts_body+="${ip} ${d}"$'\n'
|
|
551
|
+
fi
|
|
552
|
+
done <<<"$resolved"
|
|
553
|
+
done
|
|
554
|
+
|
|
555
|
+
write_resolvers "$wifi_dns"
|
|
556
|
+
write_hosts_block "$hosts_body"
|
|
557
|
+
ensure_proxy_exceptions
|
|
558
|
+
|
|
559
|
+
local tmp state_changed=0
|
|
560
|
+
tmp=$(mktemp)
|
|
561
|
+
printf '%s\n' "${lines[@]}" | awk 'NF && !seen[$0]++' | sort >"$tmp"
|
|
562
|
+
if [[ -f "$STATE_FILE" ]]; then
|
|
563
|
+
while read -r kind ip; do
|
|
564
|
+
[[ -n "$kind" ]] || continue
|
|
565
|
+
if [[ -z "$ip" ]]; then
|
|
566
|
+
ip=$kind
|
|
567
|
+
kind=v4
|
|
568
|
+
fi
|
|
569
|
+
if grep -qx "$kind $ip" "$tmp"; then
|
|
570
|
+
continue
|
|
571
|
+
fi
|
|
572
|
+
drop_state_entry "$kind" "$ip"
|
|
573
|
+
done <"$STATE_FILE"
|
|
574
|
+
fi
|
|
575
|
+
if [[ -f "$STATE_FILE" ]] && cmp -s "$tmp" "$STATE_FILE"; then
|
|
576
|
+
rm -f "$tmp"
|
|
577
|
+
else
|
|
578
|
+
mv "$tmp" "$STATE_FILE"
|
|
579
|
+
state_changed=1
|
|
580
|
+
fi
|
|
581
|
+
|
|
582
|
+
if ! cmp -s "$input_tmp" "$INPUT_STATE_FILE"; then
|
|
583
|
+
mv "$input_tmp" "$INPUT_STATE_FILE"
|
|
584
|
+
else
|
|
585
|
+
rm -f "$input_tmp"
|
|
586
|
+
fi
|
|
587
|
+
if ! cmp -s "$resolved_tmp" "$RESOLVED_STATE_FILE"; then
|
|
588
|
+
mv "$resolved_tmp" "$RESOLVED_STATE_FILE"
|
|
589
|
+
else
|
|
590
|
+
rm -f "$resolved_tmp"
|
|
591
|
+
fi
|
|
592
|
+
|
|
593
|
+
chmod 755 "$STATE_DIR" 2>/dev/null || true
|
|
594
|
+
chmod 644 "$STATE_FILE" "$INPUT_STATE_FILE" "$RESOLVED_STATE_FILE" "$RESOLVER_LIST" 2>/dev/null || true
|
|
595
|
+
if [[ "$FILES_CHANGED" -eq 1 ]]; then
|
|
596
|
+
flush_dns
|
|
597
|
+
fi
|
|
598
|
+
if [[ "$state_changed" -eq 1 || "$FILES_CHANGED" -eq 1 ]]; then
|
|
599
|
+
log "applied via ${wifi} gw=${wifi_gw} dns=${wifi_dns} (wired ${eth}): $(tr '\n' ' ' <"$STATE_FILE")"
|
|
600
|
+
fi
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
cmd_apply() {
|
|
604
|
+
require_root
|
|
605
|
+
if ! acquire_apply_lock; then
|
|
606
|
+
return 0
|
|
607
|
+
fi
|
|
608
|
+
local rc=0
|
|
609
|
+
cmd_apply_locked "${1-}" || rc=$?
|
|
610
|
+
release_apply_lock
|
|
611
|
+
return "$rc"
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
cmd_refresh() {
|
|
615
|
+
require_root
|
|
616
|
+
cmd_apply --force
|
|
617
|
+
echo "refreshed: routes, DNS, hosts, resolvers and proxy exceptions checked"
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
cmd_status() {
|
|
621
|
+
load_config
|
|
622
|
+
local wifi eth pair
|
|
623
|
+
wifi=$(wifi_device)
|
|
624
|
+
echo "wifi device : ${wifi:-none}"
|
|
625
|
+
if [[ -n "$wifi" ]]; then
|
|
626
|
+
echo "wifi ip : $(ipconfig getifaddr "$wifi" 2>/dev/null || echo none)"
|
|
627
|
+
echo "wifi domain : $(ipconfig getoption "$wifi" domain_name 2>/dev/null || echo none)"
|
|
628
|
+
echo "wifi dns : $(ipconfig getoption "$wifi" domain_name_server 2>/dev/null || echo none)"
|
|
629
|
+
echo "wifi gw : $(ipconfig getoption "$wifi" router 2>/dev/null || echo none)"
|
|
630
|
+
fi
|
|
631
|
+
echo "wired ifaces: $(ethernet_devices | tr '\n' ' ')"
|
|
632
|
+
if eth=$(wired_up); then
|
|
633
|
+
echo "wired up : $eth $(ipconfig getifaddr "$eth")"
|
|
634
|
+
else
|
|
635
|
+
echo "wired up : no"
|
|
636
|
+
fi
|
|
637
|
+
if pair=$(split_should_run); then
|
|
638
|
+
echo "split cond : YES ($pair)"
|
|
639
|
+
else
|
|
640
|
+
echo "split cond : no"
|
|
641
|
+
fi
|
|
642
|
+
echo "config : $CONF"
|
|
643
|
+
echo "domains : ${DOMAINS:-<none>}"
|
|
644
|
+
echo "suffixes : ${SUFFIXES:-<none>}"
|
|
645
|
+
echo "cidrs : ${CIDRS:-<none>}"
|
|
646
|
+
echo "state file : $( [[ -f $STATE_FILE ]] && echo present || echo absent )"
|
|
647
|
+
if [[ -r "$STATE_FILE" ]]; then
|
|
648
|
+
echo "routed:"
|
|
649
|
+
sed 's/^/ /' "$STATE_FILE"
|
|
650
|
+
elif [[ -f "$STATE_FILE" ]]; then
|
|
651
|
+
echo "routed: (not readable without root)"
|
|
652
|
+
fi
|
|
653
|
+
echo "hosts block :"
|
|
654
|
+
awk -v b="$HOSTS_BEGIN" -v e="$HOSTS_END" '
|
|
655
|
+
$0==b{on=1}
|
|
656
|
+
on{print " "$0}
|
|
657
|
+
$0==e{on=0}
|
|
658
|
+
' /etc/hosts || true
|
|
659
|
+
echo "watcher : $(launchctl print "system/${LABEL}" >/dev/null 2>&1 && echo loaded || echo not-loaded)"
|
|
660
|
+
echo "reconcile : $(launchctl print "system/${RECONCILE_LABEL}" >/dev/null 2>&1 && echo "loaded (${RECONCILE_INTERVAL}s)" || echo not-loaded)"
|
|
661
|
+
echo "sys proxy : $(scutil --proxy | awk '/HTTPEnable/{e=$3} /HTTPProxy/{p=$3} /HTTPPort/{n=$3} END{if(e==1) print p":"n; else print "off"}')"
|
|
662
|
+
echo "proxy bypass:"
|
|
663
|
+
read_proxy_exceptions | sed 's/^/ /' || true
|
|
664
|
+
local d
|
|
665
|
+
for d in $DOMAINS; do
|
|
666
|
+
echo "resolve $d:"
|
|
667
|
+
dscacheutil -q host -a name "$d" 2>/dev/null | sed 's/^/ /' || true
|
|
668
|
+
done
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
cmd_list() {
|
|
672
|
+
load_config
|
|
673
|
+
echo "config : $CONF"
|
|
674
|
+
echo "domains :"
|
|
675
|
+
printf '%s\n' $DOMAINS | sed 's/^/ /'
|
|
676
|
+
echo "suffixes :"
|
|
677
|
+
printf '%s\n' $SUFFIXES | sed 's/^/ /'
|
|
678
|
+
echo "cidrs :"
|
|
679
|
+
printf '%s\n' $CIDRS | sed 's/^/ /'
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
classify_item() {
|
|
683
|
+
local item=$1
|
|
684
|
+
case "$item" in
|
|
685
|
+
*/*) echo cidr ;;
|
|
686
|
+
\*.*) echo suffix ;;
|
|
687
|
+
*) echo domain ;;
|
|
688
|
+
esac
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
normalize_item() {
|
|
692
|
+
local kind=$1 item=$2
|
|
693
|
+
case "$kind" in
|
|
694
|
+
suffix) echo "${item#\*.}" ;;
|
|
695
|
+
*) echo "$item" ;;
|
|
696
|
+
esac
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
cmd_add() {
|
|
700
|
+
require_root
|
|
701
|
+
load_config
|
|
702
|
+
local item=${1-} kind
|
|
703
|
+
[[ -n "$item" ]] || { echo "usage: $0 add <host|*.suffix|cidr>" >&2; exit 2; }
|
|
704
|
+
kind=$(classify_item "$item")
|
|
705
|
+
item=$(normalize_item "$kind" "$item")
|
|
706
|
+
case "$kind" in
|
|
707
|
+
cidr) DOMAINS="$DOMAINS"; CIDRS=$(add_word "$CIDRS" "$item") ;;
|
|
708
|
+
suffix) SUFFIXES=$(add_word "$SUFFIXES" "$item") ;;
|
|
709
|
+
domain) DOMAINS=$(add_word "$DOMAINS" "$item") ;;
|
|
710
|
+
esac
|
|
711
|
+
save_config
|
|
712
|
+
log "added $kind $item"
|
|
713
|
+
cmd_apply
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
cmd_remove() {
|
|
717
|
+
require_root
|
|
718
|
+
load_config
|
|
719
|
+
local item=${1-} kind
|
|
720
|
+
[[ -n "$item" ]] || { echo "usage: $0 remove <host|*.suffix|cidr>" >&2; exit 2; }
|
|
721
|
+
kind=$(classify_item "$item")
|
|
722
|
+
item=$(normalize_item "$kind" "$item")
|
|
723
|
+
case "$kind" in
|
|
724
|
+
cidr) CIDRS=$(remove_word "$CIDRS" "$item") ;;
|
|
725
|
+
suffix) SUFFIXES=$(remove_word "$SUFFIXES" "$item") ;;
|
|
726
|
+
domain) DOMAINS=$(remove_word "$DOMAINS" "$item") ;;
|
|
727
|
+
esac
|
|
728
|
+
save_config
|
|
729
|
+
log "removed $kind $item"
|
|
730
|
+
cmd_apply
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
close_network_event_stream() {
|
|
734
|
+
{ exec 3>&-; } 2>/dev/null || true
|
|
735
|
+
{ exec 4<&-; } 2>/dev/null || true
|
|
736
|
+
if [[ "$WATCH_CHILD_PID" =~ ^[0-9]+$ ]]; then
|
|
737
|
+
kill "$WATCH_CHILD_PID" >/dev/null 2>&1 || true
|
|
738
|
+
wait "$WATCH_CHILD_PID" 2>/dev/null || true
|
|
739
|
+
fi
|
|
740
|
+
WATCH_CHILD_PID=""
|
|
741
|
+
rm -f "$WATCH_COMMAND_FIFO" "$WATCH_EVENT_FIFO"
|
|
742
|
+
rmdir "$WATCH_DIR" 2>/dev/null || true
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
stop_watch() {
|
|
746
|
+
close_network_event_stream
|
|
747
|
+
exit 0
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
start_network_event_stream() {
|
|
751
|
+
mkdir -p "$WATCH_DIR"
|
|
752
|
+
rm -f "$WATCH_COMMAND_FIFO" "$WATCH_EVENT_FIFO"
|
|
753
|
+
mkfifo "$WATCH_COMMAND_FIFO" "$WATCH_EVENT_FIFO"
|
|
754
|
+
/usr/sbin/scutil <"$WATCH_COMMAND_FIFO" >"$WATCH_EVENT_FIFO" 2>/dev/null &
|
|
755
|
+
WATCH_CHILD_PID=$!
|
|
756
|
+
exec 3>"$WATCH_COMMAND_FIFO"
|
|
757
|
+
exec 4<"$WATCH_EVENT_FIFO"
|
|
758
|
+
cat >&3 <<'EOF'
|
|
759
|
+
n.add ^State:/Network/(Global/(IPv4|DNS)|Interface/.*/(IPv4|Link|AirPort)|Service/.*/(DHCP|DNS|IPv4))$ pattern
|
|
760
|
+
n.add ^Setup:/Network/Service/.*/(DNS|IPv4)$ pattern
|
|
761
|
+
n.watch
|
|
762
|
+
EOF
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
cmd_watch() {
|
|
766
|
+
require_root
|
|
767
|
+
trap stop_watch INT TERM
|
|
768
|
+
log "network watcher started"
|
|
769
|
+
cmd_apply --force
|
|
770
|
+
local line now last_apply=0
|
|
771
|
+
while :; do
|
|
772
|
+
start_network_event_stream
|
|
773
|
+
while IFS= read -r line <&4; do
|
|
774
|
+
case "$line" in
|
|
775
|
+
*"changedKey ["*) ;;
|
|
776
|
+
*) continue ;;
|
|
777
|
+
esac
|
|
778
|
+
now=$(date +%s)
|
|
779
|
+
if (( now - last_apply <= EVENT_SETTLE_SECONDS )); then
|
|
780
|
+
continue
|
|
781
|
+
fi
|
|
782
|
+
sleep "$EVENT_SETTLE_SECONDS"
|
|
783
|
+
cmd_apply
|
|
784
|
+
last_apply=$(date +%s)
|
|
785
|
+
done
|
|
786
|
+
close_network_event_stream
|
|
787
|
+
log "WARN: network event stream stopped; retrying"
|
|
788
|
+
sleep 5
|
|
789
|
+
done
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
write_install_plists() {
|
|
793
|
+
local bin=$1
|
|
794
|
+
cat >"$PLIST" <<EOF
|
|
795
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
796
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
797
|
+
<plist version="1.0">
|
|
798
|
+
<dict>
|
|
799
|
+
<key>Label</key>
|
|
800
|
+
<string>${LABEL}</string>
|
|
801
|
+
<key>ProgramArguments</key>
|
|
802
|
+
<array>
|
|
803
|
+
<string>${bin}</string>
|
|
804
|
+
<string>watch</string>
|
|
805
|
+
</array>
|
|
806
|
+
<key>RunAtLoad</key>
|
|
807
|
+
<true/>
|
|
808
|
+
<key>KeepAlive</key>
|
|
809
|
+
<true/>
|
|
810
|
+
<key>ThrottleInterval</key>
|
|
811
|
+
<integer>10</integer>
|
|
812
|
+
<key>StandardOutPath</key>
|
|
813
|
+
<string>/var/log/zyhy-private-split.log</string>
|
|
814
|
+
<key>StandardErrorPath</key>
|
|
815
|
+
<string>/var/log/zyhy-private-split.log</string>
|
|
816
|
+
</dict>
|
|
817
|
+
</plist>
|
|
818
|
+
EOF
|
|
819
|
+
|
|
820
|
+
cat >"$RECONCILE_PLIST" <<EOF
|
|
821
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
822
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
823
|
+
<plist version="1.0">
|
|
824
|
+
<dict>
|
|
825
|
+
<key>Label</key>
|
|
826
|
+
<string>${RECONCILE_LABEL}</string>
|
|
827
|
+
<key>ProgramArguments</key>
|
|
828
|
+
<array>
|
|
829
|
+
<string>${bin}</string>
|
|
830
|
+
<string>apply</string>
|
|
831
|
+
<string>--force</string>
|
|
832
|
+
</array>
|
|
833
|
+
<key>StartInterval</key>
|
|
834
|
+
<integer>${RECONCILE_INTERVAL}</integer>
|
|
835
|
+
<key>StandardOutPath</key>
|
|
836
|
+
<string>/var/log/zyhy-private-split.log</string>
|
|
837
|
+
<key>StandardErrorPath</key>
|
|
838
|
+
<string>/var/log/zyhy-private-split.log</string>
|
|
839
|
+
</dict>
|
|
840
|
+
</plist>
|
|
841
|
+
EOF
|
|
842
|
+
chmod 644 "$PLIST" "$RECONCILE_PLIST"
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
bootstrap_with_retry() {
|
|
846
|
+
local plist=$1 attempt=1 output=""
|
|
847
|
+
while (( attempt <= 5 )); do
|
|
848
|
+
if output=$(launchctl bootstrap system "$plist" 2>&1); then
|
|
849
|
+
return 0
|
|
850
|
+
fi
|
|
851
|
+
if (( attempt == 5 )); then
|
|
852
|
+
echo "$output" >&2
|
|
853
|
+
return 1
|
|
854
|
+
fi
|
|
855
|
+
log "WARN: launchd bootstrap retry ${attempt}/5 for $(basename "$plist")"
|
|
856
|
+
sleep 1
|
|
857
|
+
attempt=$((attempt + 1))
|
|
858
|
+
done
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
cmd_install() {
|
|
862
|
+
require_root
|
|
863
|
+
local self legacy_label
|
|
864
|
+
self=$(cd "$(dirname "$0")" && pwd)/$(basename "$0")
|
|
865
|
+
launchctl bootout "system/${LABEL}" >/dev/null 2>&1 || true
|
|
866
|
+
launchctl bootout "system/${RECONCILE_LABEL}" >/dev/null 2>&1 || true
|
|
867
|
+
for legacy_label in $LEGACY_LABELS; do
|
|
868
|
+
launchctl bootout "system/${legacy_label}" >/dev/null 2>&1 || true
|
|
869
|
+
done
|
|
870
|
+
mkdir -p /usr/local/sbin /usr/local/etc
|
|
871
|
+
if [[ "$self" != "$INSTALL_BIN" ]]; then
|
|
872
|
+
cp "$self" "$INSTALL_BIN"
|
|
873
|
+
fi
|
|
874
|
+
chmod 755 "$INSTALL_BIN"
|
|
875
|
+
load_config
|
|
876
|
+
save_config
|
|
877
|
+
for legacy_label in $LEGACY_LABELS; do
|
|
878
|
+
rm -f "/Library/LaunchDaemons/${legacy_label}.plist"
|
|
879
|
+
done
|
|
880
|
+
write_install_plists "$INSTALL_BIN"
|
|
881
|
+
cmd_apply --force
|
|
882
|
+
bootstrap_with_retry "$PLIST"
|
|
883
|
+
if ! bootstrap_with_retry "$RECONCILE_PLIST"; then
|
|
884
|
+
launchctl bootout "system/${LABEL}" >/dev/null 2>&1 || true
|
|
885
|
+
return 1
|
|
886
|
+
fi
|
|
887
|
+
log "installed ${INSTALL_BIN} + ${CONF}"
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
cmd_uninstall() {
|
|
891
|
+
require_root
|
|
892
|
+
load_config
|
|
893
|
+
launchctl bootout "system/${LABEL}" >/dev/null 2>&1 || true
|
|
894
|
+
launchctl bootout "system/${RECONCILE_LABEL}" >/dev/null 2>&1 || true
|
|
895
|
+
local legacy_label
|
|
896
|
+
for legacy_label in $LEGACY_LABELS; do
|
|
897
|
+
launchctl bootout "system/${legacy_label}" >/dev/null 2>&1 || true
|
|
898
|
+
rm -f "/Library/LaunchDaemons/${legacy_label}.plist"
|
|
899
|
+
done
|
|
900
|
+
rm -f "$PLIST" "$RECONCILE_PLIST"
|
|
901
|
+
if [[ -f "$INSTALL_CLI" ]] && \
|
|
902
|
+
grep -q '^# zyhy-network managed GitHub-install wrapper$' "$INSTALL_CLI" 2>/dev/null; then
|
|
903
|
+
rm -f "$INSTALL_CLI"
|
|
904
|
+
fi
|
|
905
|
+
clear_applied
|
|
906
|
+
log "uninstalled (config kept at ${CONF})"
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
cmd_bundle() {
|
|
910
|
+
load_config
|
|
911
|
+
local dest=${1-} tmp
|
|
912
|
+
if [[ -z "$dest" ]]; then
|
|
913
|
+
dest="$HOME/Downloads/zyhy-private-split.tgz"
|
|
914
|
+
fi
|
|
915
|
+
tmp=$(mktemp -d)
|
|
916
|
+
mkdir -p "$tmp/zyhy-private-split"
|
|
917
|
+
local src
|
|
918
|
+
src=$(cd "$(dirname "$0")" && pwd)/$(basename "$0")
|
|
919
|
+
cp "$src" "$tmp/zyhy-private-split/zyhy-private-split"
|
|
920
|
+
chmod 755 "$tmp/zyhy-private-split/zyhy-private-split"
|
|
921
|
+
if [[ -f "$CONF" ]]; then
|
|
922
|
+
cp "$CONF" "$tmp/zyhy-private-split/zyhy-private-split.conf"
|
|
923
|
+
else
|
|
924
|
+
cat >"$tmp/zyhy-private-split/zyhy-private-split.conf" <<EOF
|
|
925
|
+
WIFI_DOMAIN="${WIFI_DOMAIN}"
|
|
926
|
+
DOMAINS="${DOMAINS}"
|
|
927
|
+
SUFFIXES="${SUFFIXES}"
|
|
928
|
+
CIDRS="${CIDRS}"
|
|
929
|
+
EVENT_SETTLE_SECONDS="${EVENT_SETTLE_SECONDS}"
|
|
930
|
+
RECONCILE_INTERVAL="${RECONCILE_INTERVAL}"
|
|
931
|
+
EOF
|
|
932
|
+
fi
|
|
933
|
+
cat >"$tmp/zyhy-private-split/INSTALL.txt" <<'EOF'
|
|
934
|
+
macOS only. Target machine also needs Ethernet + Wi-Fi ZYHY-Private.
|
|
935
|
+
|
|
936
|
+
chmod 755 zyhy-private-split
|
|
937
|
+
sudo mkdir -p /usr/local/etc
|
|
938
|
+
sudo cp zyhy-private-split.conf /usr/local/etc/zyhy-private-split.conf
|
|
939
|
+
sudo ./zyhy-private-split install
|
|
940
|
+
|
|
941
|
+
Then:
|
|
942
|
+
zyhy-private-split status
|
|
943
|
+
sudo zyhy-private-split add '*.hq.cmcc'
|
|
944
|
+
sudo zyhy-private-split add 172.21.0.0/16
|
|
945
|
+
EOF
|
|
946
|
+
tar -C "$tmp" -czf "$dest" zyhy-private-split
|
|
947
|
+
rm -rf "$tmp"
|
|
948
|
+
echo "bundle: $dest"
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
usage() {
|
|
952
|
+
cat <<EOF
|
|
953
|
+
usage: $0 <command>
|
|
954
|
+
|
|
955
|
+
status show split state
|
|
956
|
+
list show domains / suffixes / CIDRs
|
|
957
|
+
add <item> add host, '*.suffix', or CIDR (needs sudo)
|
|
958
|
+
remove <item> remove one of the above (needs sudo)
|
|
959
|
+
refresh force DNS resolution and reconcile now (needs sudo)
|
|
960
|
+
apply [--force] apply now; --force also reconciles DNS (needs sudo)
|
|
961
|
+
clear tear down routes (needs sudo)
|
|
962
|
+
install copy to /usr/local/sbin and load daemon (needs sudo)
|
|
963
|
+
uninstall remove daemon and routes; keep config (needs sudo)
|
|
964
|
+
bundle [path.tgz] pack script+config for another Mac
|
|
965
|
+
EOF
|
|
966
|
+
exit 2
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
cmd=${1:-status}
|
|
970
|
+
shift || true
|
|
971
|
+
load_config
|
|
972
|
+
case "$cmd" in
|
|
973
|
+
apply) cmd_apply "${1-}" ;;
|
|
974
|
+
refresh) cmd_refresh ;;
|
|
975
|
+
watch) cmd_watch ;;
|
|
976
|
+
clear) cmd_clear ;;
|
|
977
|
+
status) cmd_status ;;
|
|
978
|
+
list) cmd_list ;;
|
|
979
|
+
add) cmd_add "${1-}" ;;
|
|
980
|
+
remove) cmd_remove "${1-}" ;;
|
|
981
|
+
install) cmd_install ;;
|
|
982
|
+
uninstall) cmd_uninstall ;;
|
|
983
|
+
bundle) cmd_bundle "${1-}" ;;
|
|
984
|
+
help|-h|--help) usage ;;
|
|
985
|
+
-v|--version) echo "$VERSION" ;;
|
|
986
|
+
*) usage ;;
|
|
987
|
+
esac
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# 这台已验证可用的配置,安装脚本会原样拷到 /usr/local/etc/
|
|
2
|
+
|
|
3
|
+
WIFI_DOMAIN="ZYHY-Private"
|
|
4
|
+
DOMAINS="oa.hq.cmcc mail.cmhi.chinamobile.com"
|
|
5
|
+
SUFFIXES="hq.cmcc cmhi.chinamobile.com"
|
|
6
|
+
CIDRS="172.21.0.0/16 172.16.0.0/16"
|
|
7
|
+
|
|
8
|
+
# 网络变化后等待接口稳定的秒数;另每 300 秒做一次只读为主的兜底校验。
|
|
9
|
+
EVENT_SETTLE_SECONDS="2"
|
|
10
|
+
RECONCILE_INTERVAL="300"
|