流量DNS分流实践

最近在给办公室配置网络, 要求访问国内网站时走国内DNS解析+国内运营商直出, 访问国外网站时走国外DNS解析+专线转发.

流量分流

整体思路上是根据IP段/CIDR进行分流, 由于主要访问国内网站, 整理了一份海外IP列表. 命中列表的IP请求走专线转发.

创建ipset: ipset create overseas hash:net 其中 overseas 是名字, 可以修改为别的.

添加IP段到ipset中: ipset add overseas 1.1.64.0/18

使用iptables标记流量, 这里的 2 是对流量添加的 fwmark, 可以选择任意值.

1
2
3
iptables -t mangle -A PREROUTING -m set --match-set overseas dst -j MARK --set-mark 2
iptables -t mangle -A OUTPUT -m set --match-set overseas dst -j MARK --set-mark 2
iptables -t nat -A POSTROUTING -m mark --mark 2 -j MASQUERADE

添加一个独立的路由表, 将默认路由修改为专线. 这里的 55 是 TableID, 可以选择任意值.

1
ip route add default via 10.10.0.1 table 55

添加ip rule, 将fwmark和路由表关联起来:

1
ip rule add fwmark 2 table 55

这样, 从本机和其他网络传递过来的包, 如果命中海外IP列表就会走专线转发. 注意, 如果要给其他网络提供功能(即作为路由器), 需要添加NAT, 即:

1
2
sysctl net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -s 192.168.88.0/24 -o <通往互联网的网卡> -j MASQUERADE

这样发往互联网的数据包的源IP就是互联网网卡的IP, 而不是内网IP.

DNS分流

可选的方案有CoreDNS, Dnsmasq, Pihole, BIND9, PowerDNS等等. 在权衡之后选择了BIND9方案.

(Github上有一个SmartDNS项目也可以实现类似的功能, 但是多重考虑下使用了比较原始的方案)

  1. 安装BIND9
1
sudo apt update && sudo apt install bind9 bind9utils bind9-doc
  1. 编写配置文件 /etc/bind/named.conf.options

注意, 这个配置关闭了DNSSEC. 不然会出现类似 broken trust chain resolving 'github.com/A/IN': 8.8.8.8#53 的报错. (当然更好的解决办法是配置DNSSEC)

默认使用了腾讯云, 阿里云, 114的公共DNS. 日志输出到了syslog, 可以通过 journalctl 检索.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
options {
directory "/var/cache/bind";

dnssec-validation no;

listen-on-v6 { any; };

max-cache-size 512M; // Limit cache size to prevent memory overuse
max-cache-ttl 86400; // Maximum time to keep cached records (1 day)
max-ncache-ttl 3600; // Negative caching time (1 hour)

recursion yes;
allow-recursion { any; };

forwarders {
119.29.29.29; 223.5.5.5; 114.114.114.114;
};

forward only;
};

logging {
channel default_log {
# file "/var/log/named.log";
syslog daemon;
severity debug;
print-time yes;
};
category queries { default_log; };
};
  1. 编写Zone文件 /etc/bind/named.conf.whitelist-zones
1
2
3
4
5
6
7
8
9
10
11
zone "docker.com" {
type forward;
forward only;
forwarders { 8.8.8.8; 1.1.1.1; };
};
zone "github.com" {
type forward;
forward only;
forwarders { 8.8.8.8; 1.1.1.1; };
};
...

可以通过python脚本比较方便的生成zone文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import subprocess
import traceback


with open("domains.txt") as f:
content = f.read()

lines = content.split('\n')
lines = list(set([line.strip() for line in lines if line and not line.strip().startswith('#')]))
print("{} domains imported".format(lines))

with open("/etc/bind/named.conf.whitelist-zones", "w") as f:
for line in lines:
f.write(f'''zone "{line}" {{
type forward;
forward only;
forwarders {{ 8.8.8.8; 1.1.1.1; }};
}};
''')

print("New config generated. Validating...")
subprocess.check_call(["named-checkconf", "/etc/bind/named.conf"])

print("Reloading DNS server...")
subprocess.check_call(["systemctl", "reload", "named"])

print("Reload complete. Showing latest logs... (Ctrl+C to close)")
subprocess.call(["journalctl", "-t", "named", "-f"])
  1. 重启BIND服务器以使配置生效
1
sudo systemctl restart named

如果配置没有问题的话可以看到named输出的日志:

1
2
3
4
5
6
7
Mar 05 13:36:19 office-server named[6387]: managed-keys-zone: loaded serial 7
Mar 05 13:36:19 office-server named[6387]: zone 255.in-addr.arpa/IN: loaded serial 1
Mar 05 13:36:19 office-server named[6387]: zone 0.in-addr.arpa/IN: loaded serial 1
Mar 05 13:36:19 office-server named[6387]: zone 127.in-addr.arpa/IN: loaded serial 1
Mar 05 13:36:19 office-server named[6387]: zone localhost/IN: loaded serial 2
Mar 05 13:36:19 office-server named[6387]: all zones loaded
Mar 05 13:36:19 office-server named[6387]: running

有解析请求时会输出日志:

1
Mar 05 13:37:16 office-server named[6387]: 05-Mar-2025 13:37:16.472 client @0x7a7f2c005368 <...>#25256 (main.vscode-cdn.net): query: main.vscode-cdn.net IN A + (...)

可以通过以下命令来测试DNS响应

1
dig +short @127.0.0.1 baidu.com

参考

dnsmasq ipset iptables 实现对流量进行分流 很棒的文章,思路很清晰一下就明白了

How To Configure BIND as a Private Network DNS Server on Ubuntu 20.04

巧用 DNS 实现国内外域名 ip 分流上网

Openwrt使用SmartDNS进行国内外DNS分流,提高访问速度,防止DNS污染和泄露。