9
7
2022
17

让离线软件真正离线

去年我做了个索引 Telegram 群组的软件——落絮,终于可以搜索到群里的中文消息了。然而后来发现,好多消息群友都是通过截图发送的,落絮就索引不到了。也不能不让人截图嘛,毕竟很多人描述能力有限,甚至让复制粘贴都能粘出错,截图就相对客观真实可靠多了。

所以落絮想要 OCR。我知道百度有 OCR 服务,但是我显然不会在落絮上使用。我平常使用的 OCR 工具是 tesseract,不少开源软件也用的它。它对英文的识别能力还可以,尤其是可自定义字符集所以识别 IP 地址的效果非常好,但是对中文的识别能力不怎么样,图片稍有不清晰(比如被 Telegram JPEG 压缩)、变形(比如拍照),它就乱得一塌糊涂,就不说它给汉字之间加空格是啥奇怪行为了。

后来听群友说 PaddleOCR 的中文识别效果非常好。我实际测试了一下,确实相当不错,而且完全离线工作还开源。但是,开源是开源了,我又没能力审查它所有的代码,用户量太小也不能指望「有足够多的眼睛」。作为基于机器学习的软件,它也继承了该领域十分复杂难解的构建过程,甚至依赖了个叫「opencv-contrib-python」的自带了 ffmpeg、Qt5、OpenSSL、XCB 各种库的、不知道干什么的组件,试图编译某个旧版 numpy 结果由于太旧不支持 Python 3.10 而失败。所以我决定在 Debian chroot 里安装,那边有 Python 3.9 可以直接使用预编译包。所以问题来了:这么一大堆来源不明的二进制库,用起来真的安全吗?

我不知道。但是我知道,如果它联不上网的话,那还是相对安全的。毕竟我最关心的就是隐私安全——一定不能把群友发的图片泄漏给未知的第三方。而且联不上网的话,不管你是要 DDoS 别人、还是想挖矿,收不到指令、传不出数据,都行不通了嘛。我只要它能从外界读取图片,然后把识别的结果返回给我就好了。

于是一个简单的办法是,拿 bwrap 给它个只能访问自己的独立网络空间它不就访问不了互联网了吗?不过说起来简单,做起来还真不容易。首先,debootstrap 需要使用 root 执行,执行完之后再 chown。为了进一步限制权限,我使用了 subuid,但这也使得事情复杂了起来——我自己都难以访问到它了。几经摸索,我找到了让我进入这个 chroot 环境的方法:

#!/bin/bash -e

user="$(id -un)"
group="$(id -gn)"

# Create a new user namespace in the background with a dummy process just to
# keep it alive.
unshare -U sh -c "sleep 30" &
child_pid=$!

# Set {uid,gid}_map in new user namespace to max allowed range.
# Need to have appropriate entries for user in /etc/subuid and /etc/subgid.
# shellcheck disable=SC2046
newuidmap $child_pid 0 $(grep "^${user}:" /etc/subuid | cut -d : -f 2- | tr : ' ')
# shellcheck disable=SC2046
newgidmap $child_pid 0 $(grep "^${group}:" /etc/subgid | cut -d : -f 2- | tr : ' ')

# Tell Bubblewrap to use our user namespace through fd 5.
5< /proc/$child_pid/ns/user bwrap \
  --userns 5 \
  --cap-add ALL \
  --uid 0 \
  --gid 0 \
  --unshare-ipc --unshare-pid --unshare-uts --unshare-cgroup --share-net \
  --die-with-parent --bind ~/rootfs-debian / --tmpfs /sys --tmpfs /tmp --tmpfs /run --proc /proc --dev /dev \
  -- \
  /bin/bash -l

这里给了联网权限,是因为我需要安装 PaddleOCR。没有在创建好 chroot 之后、chown 之前安装,是因为我觉得拿着虽然在 chroot 里但依旧真实的 root 权限装不信任的软件实在是风险太大了。装好之后,再随便找个图,每种语言都识别一遍,让它下载好各种语言的模型,接下来它就再也上不了网啦(为避免恶意代码储存数据在有网的时候再发送):

#!/bin/bash -e

dir="$(dirname $2)"
file="$(basename $2)"

user="$(id -un)"
group="$(id -gn)"

# Create a new user namespace in the background with a dummy process just to
# keep it alive.
unshare -U sh -c "sleep 30" &
child_pid=$!

# Set {uid,gid}_map in new user namespace to max allowed range.
# Need to have appropriate entries for user in /etc/subuid and /etc/subgid.
# shellcheck disable=SC2046
newuidmap $child_pid 0 $(grep "^${user}:" /etc/subuid | cut -d : -f 2- | tr : ' ')
# shellcheck disable=SC2046
newgidmap $child_pid 0 $(grep "^${group}:" /etc/subgid | cut -d : -f 2- | tr : ' ')

# Tell Bubblewrap to use our user namespace through fd 5.
5< /proc/$child_pid/ns/user bwrap \
  --userns 5 \
  --uid 1000 \
  --gid 1000 \
  --unshare-ipc --unshare-pid --unshare-uts --unshare-cgroup --unshare-net \
  --die-with-parent --bind ~/rootfs-debian / --tmpfs /sys --tmpfs /tmp --tmpfs /run --proc /proc --dev /dev \
  --ro-bind "$dir" /workspace --chdir /workspace \
  --setenv PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
  --setenv HOME /home/worker \
  -- \
  /home/worker/paddleocr/ocr.py "$1" "$file"

kill $child_pid

这个脚本会把指定文件所在的目录挂载到 chroot 内部,然后对着这个文件调用 PaddleOCR 来识别并通过返回结果。这个调用 PaddleOCR 的 ocr.py 脚本位于我的 paddleocr-web 项目

不过这也太复杂了。后来我又使用 systemd 做了个服务,简单多了:

[Unit]
Description=PaddleOCR HTTP service

[Service]
Type=exec
RootDirectory=/var/lib/machines/lxc-debian/
ExecStart=/home/lilydjwg/PaddleOCR/paddleocr-http --loglevel=warn -j 2
Restart=on-failure
RestartSec=5s

User=1000
NoNewPrivileges=true
PrivateTmp=true
CapabilityBoundingSet=
IPAddressAllow=localhost
IPAddressDeny=any
SocketBindAllow=tcp:端口号
SocketBindDeny=any
SystemCallArchitectures=native
SystemCallFilter=~connect

[Install]
WantedBy=multi-user.target

这里的「paddleocr-http」脚本就是 paddleocr-web 里那个「server.py」。

但它的防护力也差了一些。首先这里只限制了它只能访问本地网络,TCP 方面只允许它绑定指定的端口、不允许调用 connect 系统调用,但是它依旧能向本地发送 UDP 包。其次运行这个进程的用户就是我自己的用户,虽然被 chroot 到了容器里应该出不来。嗯,我大概应该给它换个用户,比如 uid 1500,应该能起到跟 subuid 差不多的效果。

顺便提一句,这个 PaddleOCR 说的是支持那么多种语言,但实际上只有简体中文等少数语言支持得好(繁体都不怎么样),别的语言甚至连语言名和缩写都弄错,越南语识别出来附加符号几乎全军覆没。

Category: Linux | Tags: linux 安全 隐私 网络
5
19
2020
12

桥接无线网卡!

众所周知,大部分无线网卡是不支持桥接操作的。

但是 VirtualBox 就是能,因为它做了特殊处理:来回改 MAC。

那么,我的 LXCnetnsKVM 啥的也想这么玩,成不?

实际上不仅能成,而且 Debian Wiki 还给出了两个方案。方案一是用 ebtables 来回改 MAC。不过我失败了,可能是 ebtables 不支持改完 MAC 再把包发往另外的网络接口吧。

方案二是内核的一个叫 Proxy ARP 的功能。设置起来超级简单:往/proc/sys/net/ipv4/conf/all/proxy_arp里写1,然后给需要的 IP 地址加一条 /32 路由项就可以了。

这方案相比起 VirtualBox 来是非常手动了,也不支持 DHCP 自动配置的 IP 地址,但好歹能用。至少微信备份能用。(火狐的 Wi-Fi 远程调试已经坏掉了,倒是那个「USB 调试」其实只要 adb 连接上就能用,不一定要走 USB 线。)

Category: 网络 | Tags: linux 网络 虚拟机
8
16
2019
10

寻找最快的 GitHub IP

部分国内网络访问 GitHub 会很慢,严重拖慢了学习和开发效率。除了使用代理绕路之外,有没有什么简便的办法呢?最近我写了个脚本,用来测试所有已经的 GitHub IP 并计时,然后就可以挑一个访问快的写在 hosts 文件里了。

获取脚本请访问 gh-check。脚本依赖 Python 3 近期版本及 aiohttp。

中国大陆目前自然解析 github.com,通常会得到位于新加坡的 IP。然而这几个 IP 的访问速度经常不怎么好。我之前是手动尝试使用西雅图或者阿什本的 IP,但是它们也并不总是很流畅。现在,终于可以让数据说话了:

gh-check demo

IP 来源于四个 GitHub 区域域名的解析结果,以及另外两个我自己通过 DNS 发现的。

检查分为两种:HTTP 和 SSH。默认两者都测试,可如上图中那样通过参数指定只测试一种协议。HTTP 测试时,会验证服务器的 TLS 证书。

Category: 网络 | Tags: github 网络
4
4
2019
4

系统在解析哪些域名呢?

最近用 Rust 写了个叫 capture-dns 的小程序,实时显示 DNS 查询结果的。配合 ipmarkup 的效果是这样的:

>>> sudo capture-dns lo | ipmarkup
[sudo] lilydjwg 的密码:
github.com -> 52.74.223.119(新加坡Amazon数据中心)
github.com -> 13.229.188.59(新加坡Amazon数据中心)
github.com -> 13.250.177.223(新加坡Amazon数据中心)
live.github.com -> 192.30.253.125(美国弗吉尼亚州阿什本GitHub)
live.github.com -> 192.30.253.124(美国弗吉尼亚州阿什本GitHub)
collector.githubapp.com -> 34.193.248.191(美国弗吉尼亚州阿什本Amazon数据中心)
collector.githubapp.com -> 52.20.29.9(美国弗吉尼亚州阿什本Amazon数据中心)
collector.githubapp.com -> 34.197.57.23(美国弗吉尼亚州阿什本Amazon数据中心)
api.github.com -> 13.250.94.254(美国Amazon数据中心)
api.github.com -> 13.250.168.23(美国Amazon数据中心)
api.github.com -> 54.169.195.247(新加坡Amazon数据中心)
ocsp.digicert.com -> 117.18.237.29(澳大利亚美国MCI通信服务有限公司(韦里孙商业Verizon Business)EdgeCast亚太网络CDN节点)

可以看到本地的软件们都在查询哪些域名,得到的 IP 又是什么。抓取的是应答,所以没得到 IP 结果的不会显示。我抓取的是 lo 网络接口,因为我本地有用 dnsmasq 做缓存。

其实这个程序一开始不是这样子的。群里有人想抓取系统上进行的 DNS 查询的域名。一开始是用 tshark 抓取的,然而它太占用内存了。我粗略看了一下 Python 的 scapy 工具,也用掉了大几十M内存。那么,用 Rust 写一个好了,也顺便练习一下 Rust。

这个程序运行时只有几M的内存占用,CPU 占用也是非常低的。不过它并没有做完全的协议分析,而是假设抓得的包是以太网帧封装的 IPv4 报文封装的 UDP 数据包里包着 DNS 应答报文。所以如果你是在 eth0 上跑 PPPoE 的话,抓 eth0 上的包就不行了,得抓 ppp0 这种了。当然你要是 IPv6 啊 DoH、DoT 啥的就更抓不到了。

后来我用 bcc 的 tcpretrans 脚本查看我这里到哪些地方的 TCP 连接不太通畅,然而经常会看到一些我猜不到是干嘛的 IP。所以就把这个程序改了一下,把域名对应的解析结果显示出来了。

Rust 不仅节省资源,而且开发的体验真的很棒呢,编译成功之后就能按我预期的运行了。也不用担心什么时候遇到个有问题的报文导致程序崩掉,因为写的时候就已经处理好了出错的情况。不像 Python 写的脚本,刚写好,一跑就抛个异常出来,提示我哪里不小心写错了。好不容易调试好了,跑着跑着,遇到意外情况就挂掉了……

Category: 编程 | Tags: Rust linux 网络 DNS
3
3
2019
16

使用 cgroups net_cls 来让 docker 走代理

我这里 docker hub 连不上或者连上了访问很慢,根本没法用。本来我常规代理的办法,要么是 proxychains,要么是用 iptables 代理特定的 IP 段。至于 docker 嘛,亚马逊的 IP 段那么多,它用到的域名我也不是很清楚,一点点加好麻烦。作为系统服务,用 proxychains 不仅得修改 systemd 服务配置,而且不知道会不会出什么幺蛾子。最近刚好在某个地方看到这一手,就试试啰。

其实用法很简单的。去 /sys/fs/cgroup/net_cls 下建立个目录,往 net_cls.classid 里写一个整数(支持十六进制的 0x 表示法),然后把 dockerd 的 pid 写到 cgroup.procs 里去。最后用 iptables 代理这部分流量即可。现在都用 443 端口啦,所以只要代理它便好,也避免影响了别的东西:

iptables -t nat -A OUTPUT -p tcp --dport 443 -m cgroup --cgroup 0x110001 -j REDIRECT --to-ports XXX

XXX 是 ss-redir 的端口啦。

注意不要把进程的 pid 往 tasks 文件里写。那里得写的是 task 的 id 而不是 process 的 id,也就是说(用内核的术语来说)是线程的 pid 而不是进程的 tgid(thread group id)。所以非要写 tasks 文件的话,得把 docker 所有的线程的 pid 都写进去才行。真是混乱呢……画个表格好了:

用户态 内核 相关系统调用
pid tgid getpid, kill
tid pid gettid, tgkill
process task group fork, clone without CLONE_THREAD
thread task clone with CLONE_THREAD

另外如果更新过内核的话,那句 iptables 有可能会找不到模块的。(所以更新内核之后还是重启一下以避免尴尬吧。)

Category: shell | Tags: iptables linux cgroups 网络 代理
11
8
2018
8

与 Android 进行 WLAN Direct 连接

首先 iw list 看是否支持。如果支持,那就

iw dev wlan0 interface add p2p0 type __p2pdev

这样其实并不会多出一个叫 p2p0 的网络接口。iw dev 能看到多了个「Unnamed/non-netdev」设备。不执行这个也可以连接上 WLAN Direct,但是当前的 managed Wi-Fi 连接会断掉。执行之后再连接,managed 连接会持续,iw dev 里会有两个 Unnamed,不知道何故。另外这个 type __p2pdev 加上去了我就不知道怎么删除了。试了几个命令,结果搞得内核 oops 了……

然后是 wpa_supplicant 配置文件:

ctrl_interface=/run/wpa_supplicant_p2p
ap_scan=1

device_name=起一个名字
device_type=1-0050F204-1

driver_param=use_p2p_group_interface=1

wpa_supplicant 跑起来。注意这里的接口名还是那个 managed 接口的。

wpa_supplicant -i wlan0 -c p2p_config.conf

然后 wpa_cli 连过去操作:

wpa_cli -p /run/wpa_supplicant_p2p

首先用 p2p_find 开启搜索。这时候对端设备能够看到自己了。使用 p2p_connect 对端MAC pbc go_intent=0 连接,在对端接受连接即可。go_intent=0 是让对方作为 group owner,这样对端 Android 才会提供 DHCP 服务(否则要本地提供了)。

然后就可以给自己添加 IP 地址了。此时是可以用 dhcpcd 的,然而直接跑的话它会抢走默认路由,所以知道地址范围之后手动加一个好了:

ip a add 192.168.49.22/24 dev p2p-wlan0-1

Android 设备的地址是 192.168.49.1。

之后就可以用 adb connect 然后 scrcpy 了。

PS: Android 很喜欢四十几的 IP 段呢。USB 网络共享是 192.168.42.129/24,Wi-Fi 网络共享是 192.168.43.1/24,而 WLAN Direct 是 192.168.49.1/24。不知道蓝牙网络共享是多少呢。

PPS: scrcpy 在我的 XZ2C 上运行完美,但是在 MIUI 10 上需要去开发者选项里开启选项「USB 调试(安全设置)」,否则会是「只读模式」,只能看,所有交互操作无效。

参考资料

Category: 网络 | Tags: linux 网络 Android
7
16
2018
5

使用 iptables 透明代理 TCP 与 UDP

很早之前,我在《Linux「真」全局 HTTP 代理方案》中介绍了 redsocks 方案。不过它只处理了 TCP,并没有处理 UDP,DNS 也是采用强制 TCP 的方式来处理的,再加上它本身还要将请求转发到真正的代理客户端,延迟比较高。然后,还可以结合 Wi-Fi 分享 或者网络命令空间,玩点更有趣的。

首先要有支持的代理客户端,比如 ss-redir。这个就不用多介绍了,配置好、跑起来即可。以下假设此代理跑在 127.0.0.1 的 $PPROT 端口上。

然后,TCP 的代理设置。使用的是和 redoscks 一样的方案。这个比较简单,除了有一点需要注意:DNAT 到 127.0.0.1 时,需要设置内核选项net.ipv4.conf.all.route_localnet=1

最麻烦的是 UDP 的代理,使用的是 TPROXY。首先,需要把要走代理的数据包路由到本地。以下假设我们给要代理的数据包打上标签 1。那么执行:

ip rule add fwmark 1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100

那个 100 是路由表的编号,可以自己选一个喜欢的。

然后,对于转发流量(来自局域网或者另外的网络命名空间),直接把需要代理的数据包扔给 TPROXY 目标,并且打上对应的标签即可。而对于本地产生的流量,不仅要带有对应的标签,而且需要在 OUTPUT 链打上一个(与之前不同的)标签,触发 reroute check 才行。

最后,对需要代理的数据包设置 iptables 规则:

协议 来源 目标
TCP 本地 nat OUTPUT -j REDIRECT --to-ports $PPROT
转发 PREROUTING -j DNAT --to-destination 127.0.0.1:$PPROT
UDP 本地 mangle OUTPUT
PREROUTING
-j MARK --set-mark 1
-j TPROXY --on-port $PPROT --on-ip 127.0.0.1
转发 PREROUTING -j TPROXY --on-port $PPROT --on-ip 127.0.0.1 --tproxy-mark 1/1

比如来自网络命名空间或者局域网的 IP 段 192.168.57.0/24 全部走代理:

iptables -t nat -A PREROUTING -p tcp -s 192.168.57.0/24 ! -d 192.168.57.0/24 -j DNAT --to-destination 127.0.0.1:$PPROT
iptables -t mangle -A PREROUTING -p udp -s 192.168.57.0/24 ! -d 192.168.57.0/24 -j TPROXY --on-port $PPROT --on-ip 127.0.0.1 --tproxy-mark 1/1
Category: 网络 | Tags: linux 网络 iptables UDP
5
14
2018
24

Windows 10 中配置网络共享

有一个很讨厌的公司叫「深信服」,英文名叫「Sangfor」。它开发的私有协议的 SSL VPN 客户端 EasyConnect 终于支持 Ubuntu 了,然而我没能在 Arch Linux 上正确地把它跑起来。图形界面是 electron 之类的,这部分没有问题。但有一个会往 /usr/share 下写日志的 root 进程,应该是负责真正的 VPN 创建的,死活连不上。

那么就在 Windows 虚拟机里跑吧。所以我的 Linux 系统需要通过 Windows 访问部分网络了。只是 HTTP 的话倒是可以装个 HTTP 代理搞定,但是我还要连 ssh 的。

正文开始了。其实整个过程并不难,关键在于很难找到资料。

首先,当然是给 Windows 一张 host only 的网卡,用于两个系统之间的连接。以及,把那个 VPN 连上。

然后,打开「网络设置」,选择「更改适配器选项」。右键单击要共享的网络适配器(比如此例中是那张 Sangfor VPN 的网卡),选择「属性」。

选择「共享」选项卡,把第一个框给勾选上,下边下拉菜单选择那张 host only 的适配器。确定。

就这样,OK 了。

Windows 10 中配置网络共享

要注意的是,此操作会将网卡的 IP 强制设置为「192.168.137.1」。就像图中可以看到的那样,微软总喜欢「提供策略,而非机制」。假定你是给你的家庭共享。假定你要共享的是 Internet 连接(本例中其实是 VPN 连接)。所以它也假定了你的「家庭」网络中 Windows 可以随意选择 IP 地址,假定你需要 DHCP 服务。

Linux 这边配置起来就容易多了。Virtualbox 的 vboxnet0 接口本来用的是 192.168.56.0/24 网段,但是给它配置另外的 IP 地址,往它里边扔它不了解的目标地址的包,它也不介意的。

sudo ip a add 192.168.137.2/24 dev vboxnet0
sudo ip r add 172.16.2.9/32 via 192.168.137.1

以上给 vboxnet0 添加了 IP 地址,并且让需要走 VPN 的目标地址(172.16.2.9)走 Windows 的网络。

PS: 开着防火墙的话,Windows 10 默认是忽略 ping 的。在防火墙的「入站规则」里启用「文件和打印机共享(回显请求—ICMPv4-In)」之后才能 ping 它。

另见阿森人的《伪·如何在 Linux 下使用深信服 SSL VPN》一文。

Category: 网络 | Tags: 微软 windows 网络
10
10
2017
42

WireGuard: 简单好用的 VPN

家里和公司电脑连接,因为厌倦了一个个做端口映射,有些还因为安全原因得走 ssh,所以决定弄个 VPN。之前使用过 OpenVPN,然而现在懒得再去配置 OpenVPN 的证书了,所以决定尝试一下新东西。

首先,去 WireGuard 官网上转了一圈,结果还是没弄明白怎么配置。后来尝试了一下 demo,把服务端和客户端的脚本分别看了一下,才弄明白。其实在 WireGuard 里,客户端和服务端基本是平等的,差别只是谁主动连接谁而已。双方都会监听一个 UDP 端口。双方都需要一对密钥。双方都需要把对方的公钥加进来。最后一步,谁主动连接,谁就是客户端。因为家里路由器有公网 IP,我做了端口映射,所以我当然是从公司连家里方便了,用不着麻烦的打洞脚本。

首先 pacman -S wireguard-tools 安装。这也会安装上 WireGuard 的内核模块。然后使用熟悉的 ip 命令添加并配置 WireGuard 的网络接口:

# 生成密钥对
wg genkey | tee privatekey | wg pubkey > publickey

sudo ip link add dev wg0 type wireguard
sudo ip address add dev wg0 192.168.58.1/24
sudo wg set wg0 listen-port 60010 private-key privatekey
sudo ip link set wg0 up

这是我家里的配置。使用的是网段 192.168.58.0/24,因为 56 是 vbox 虚拟机用的,57 分配给 lxc 和我的网络命名空间了。指定了一下监听的端口号。我把之前给 mosh 配置了转发的端口号中最高的那个挪用了。

公司里也是同时的配置,只是不需要指定监听端口号,然后把家里那边设置成 peer,并且连过去(相同的命令我没写):

sudo wg set wg0 private-key privatekey peer 这里是公钥 endpoint 家里的IP:60010 allowed-ips 0.0.0.0/0 persistent-keepalive 180

allowed-ips 指定过来的 IP。这里没怎么限制。persistent-keepalive 是为 NAT 设置的。WireGuard 本来很安静,不需要说话的时候就不说话,但是要往 NAT 后边的主机发送信息,需要经常通信,让 NAT 记得对应的映射关系。

然后家里那边也需要添加一下公司这边的公钥:

sudo wg set wg0 peer YiyFylL+1Dr3j2Cyf0lwXQYz2qaNwm3XyV5YvMFp3Vs= allowed-ips 192.168.58.2/32

IP 限制加上也是没有问题的。这里就不用加上 endpoint 了,它连过来的时候自然就知道了。WireGuard 是支持漫游的,也就是说,双方不管谁的地址变动了,WireGuard 在看到对方从新地址说话的时候,就会记住它的新地址(跟 mosh 一样,不过是双向的)。所以双方要是一直保持在线,并且通信足够频繁的话(比如配置 persistent-keepalive),两边的 IP 都不固定也不影响的。

最后,用得不错,可以把这几条命令写到一个 systemd service 文件里,就可以不用每次敲一串命令了,也可以做到联网后自动启动。


刚刚找了一下,其实之前使用的证书什么的还在,而且还没过期。而且因为弄 nghttpx,用了一下 xca,比 easy-rsa 好用很多呢。不过 WireGuard 的双向漫游很棒啊~

Category: 网络 | Tags: linux 网络 UDP
9
11
2017
3

等连上互联网之后再来找我吧

最近公司弄了 Wi-Fi 登录。就是那个叫 captive portal 的东西。

Android 早就会在连接 Wi-Fi 时检测网络是不是要登录了,为此 Google 弄了个 /generate_204 的 URL。小米、高通、USTC、v2ex 也都提供了这个东西,方便广大中国大陆 Android 用户使用。(我发现我的 Android 使用的是高通的地址,没有用 Google 的。)

但我使用的 Arch Linux 自行开发的 netctl 网络管理工具没这种功能。火狐倒是不知道什么时候加上了,不过使用的地址 http://detectportal.firefox.com/success.txt 是返回 200 的。

所以我启动火狐就可以看到要登录的提示了。然而问题是,其它程序不知道要登录啊。像 offlineimap、openvpn、rescuetime 这种还好,会自己重试。可每次网络需要登录的时候 dcron 就会给我发一堆邮件告诉我我的 git pull 都失败了……当然还有我老早就注意到的 pkgstats,经常会因为启动过早而无法发送统计数据。

所以呢,得想个办法,等连上互联网之后再跑那些脚本啊服务什么的。

检测是不是连好了很简单,不断尝试就可以了。但我需要一个系统级的 Condition 对象来通知等待方可以继续了。然而我只知道 Linux 有提供信号量。难道要自己弄共享内存来用么?

#archlinux-cn 问了一下,farseerfc 说试试命名管道。我想了想,还真可以。只有读端的时候进程就会阻塞,一旦有写端就能成功打开了。当然没有读端的打开写端会打不开,不过没关系,反正这进程也不能退出,得一直拿着这个文件描述符。

没想到很少用到的命名管道有意想不到的用法呢。我以前还为了不阻塞而专门写了篇文章呢。

于是负责检测网络连通的 check-online 和等待网络连好的 wait-online 都写好了。

check-online 应当是个服务。那就交给 systemd 吧。然后……systemd 不是有个 network-online.target 么?正好可以让 check-online 来达成这个目标呢,多合适呀。

于是服务写好了。测试了几天,大成功!不仅 wait-online 很好地工作了,而且我发现 openvpn 和 pkgstats 自动排到 network-online.target 后边去了。nginx 的 OSCP staple 经常因为 DNS 失败而无法成功,我也可以在联好网之后去 reload 一下它了。(不是强依赖,我可不希望连不上网的时候我本地的 wiki 也访问不了。)

整个项目就叫作 wait-online,在 GitHub 上,欢迎送小星星哦~Arch Linux 包可以从 [archlinuxcn] 仓库 安装 wait-online-git 包。

| Theme: Aeros 2.0 by TheBuckmaker.com