4

一个系统,两套网络

 2 years ago
source link: https://blog.lilydjwg.me/2016/4/29/one-system-two-networks.200002.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

创建并配置一个新的网络命名空间

公司 VPN 用的网段是 192.168.1.0/24,而我家里的网络,就像大多数人的一样,也是 192.168.1.0/24。网段冲突了。当然啦,隔离得相当好的 lxc 可以解决这个问题。可是它隔离得太好了,我可不想再多维护一套环境。那么,就只隔离网络好了!

正好之前看到 iproute2 套件可以管理网络命名空间。那么,就用它啦。

首先,创建一个名为 vpn 的网络命名空间:

ip netns add vpn

现在如果进去看的话,会发现里边只有一个孤零零的 lo 网络接口,所以里边是一片与世隔绝的黑暗。我没有其它的网络接口给它用。桥接当然是可以尝试的,只是需要更改已有的网络配置。所以还是用另外的方案吧。弄根网线来,把里边和外边连接起来好了。

创建一对 veth 接口。这就是我们的「网线」~

ip link add vpn0 type veth peer name vpn1

一端留外边,另一端移到 vpn 里边去:

ip link set vpn1 netns vpn

接好网线的两端:

ip link set vpn0 up
ip netns exec vpn ip link set vpn1 up

好了,现在两个网络之间可以通信了~当然,只是链路层。为了使用 TCP/IP,我们得分配 IP 地址。在外边的这个就不需要 IP 地址了,因为我要把它接到一个网桥上,网桥自己有 IP 的。

brctl addif br0 vpn0

这里 br0 是我已有的网桥(给 lxc 用的那个)。如果你没有的话,就自己按以下方法弄一个。我用的是 bridge-utils 里的 brctl 命令。iproute2 也可以做的。

brctl addbr br0
ifconfig br0 192.168.57.1
iptables -t nat -A POSTROUTING -s 192.168.57.1/24 -j MASQUERADE

最后的 iptables 命令是做 NAT 啦。当然内核的 IPv4 转发功能还要开启的。

vpn0 这端弄好了,再给 vpn1 分配 IP,并设置相关路由表项:

ip netns exec vpn ip address add dev vpn1 192.168.57.101/24
ip netns exec vpn ip route add default via 192.168.57.1

现在就可以在里边连 VPN 啦~

ip netns exec vpn openvpn ...

当然也可以去里边开个 zsh 用

ip netns exec vpn zsh

不过要注意如果跑 GUI,或者连接 D-Bus 的话,需要手动把相关环境变量移进去。虽然是独立的网络命名空间,但是因为共享了文件系统,所以虽然看不到在监听的 UNIX 套接字,但是连起来还是没有问题的。X 和 D-Bus 都可以良好工作的。

export DISPLAY=:0 LANG=zh_CN.UTF-8 LANGUAGE=zh_CN:zh_TW
export GTK_IM_MODULE=fcitx QT_IM_MODULE=fcitx XMODIFIERS=@im=fcitx
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
# 如果在 tmux 里的话
export TMUX=1

再加上 mount 命名空间吧

我这里在里边得用 IP 地址,因为我的 DNS 是 127.0.0.1,在里边当然是用不了的。怎么办呢?我不想改 DNS 服务器地址,因为里外的文件系统是共享的。那就再加上 mount 命名空间吧,这样就可以 bind mount 一个修改过的文件到 /etc/resolv.conf 上了。

其实呢,ip netns 是通过把网络命名空间(/proc/<pid>/ns/net)bind mount 到文件来实现命名空间的持久化的(不然使用这个命名空间的进程都退出,该命名空间就销毁了)。其文件位于 /run/netns 下。对于 mount 命名空间我们可以手工这么做:

mkdir -p /var/run/ns
mount --bind /var/run/ns /var/run/ns
# 命名空间只能 bind mount 到 private 挂载的文件系统上
mount --make-private /var/run/ns
# 随意找个普通文件就行。一般是用空文件;我这样可以省一个文件~
cp /etc/resolv.conf /var/run/ns

然后用 unshare 建立新的 mount 命名空间,并进入之前的 vpn 网络命名空间(当然用 nsenter 进入也是可以的):

unshare --mount=/var/run/ns/resolv.conf ip netns exec vpn zsh

创建了之后就可以用 nsenter 进去玩儿了:

nsenter --mount=/var/run/ns/resolv.conf --net=/var/run/netns/vpn zsh

可以在里边各种 bind mount,不会影响外边的哦:

# 在新的命名空间里边
mount --bind /var/run/ns/resolv.conf /etc/resolv.conf
vim /etc/resolv.conf

组合更多的命名空间

当然也可以组合更多种的命名空间的。我还试过 pid 命名空间,不过 pid 命名空间比较特殊:它在 fork 后才生效,当 init 进程(pid=1 的进程)退出之后所有位于此 pid 命名空间的进程都会被杀死,并且再也进不去了。所以不是很好玩的啦。

创建与进入的命令如下:

unshare --mount-proc -f --pid=/var/run/ns/pid --mount=/var/run/ns/resolv.conf nsenter --net=/var/run/netns/vpn su - lilydjwg
# 进入时用 -t 选项,或者重新 bind mount 文件(不然新进程会在原 pid 命名空间)
nsenter -t 9416 --mount=/var/run/mountns/resolv.conf --net=/var/run/netns/vpn --pid su - lilydjwg

除了可能偶尔连接公司 VPN 会用到这个技术之外,我又给其找到了一个更好的使用场合:Wine QQ!于是本地的 nginx 日志里终于不会再有 /srv/http/cgi/reccom 找不到的提示了,CGI 服务也不会被不必要地启动了。

2016年5月13日更新:自用的脚本放在 Gist 上了。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK