使用 Gentoo Linux 手搓路由器之三 -- 国内访问加速
本文的做法同样适用 OpenWRT
继之前的三篇文章
文章中的 iptables 指令中包含了一些和ipset相关的,虽然做了一些注释,但是还是发文解释一下为何要这样做,好处是什么? 原理又是什么?
之前文章中的 iptables 指令. 区分来源IP的原因是,我希望有些场景下不走直连。
1## 国内IP直连
2iptables -t mangle -A XRAY -p TCP -s 192.168.88.0/24 -m set --match-set ${IPSET_CN_IP} dst -j RETURN
3iptables -t mangle -A XRAY -p TCP -s 192.168.1.0/24 -m set --match-set ${IPSET_CN_IP} dst -j RETURN
4iptables -t mangle -A XRAY -p TCP -s 10.8.9.0/24 -m set --match-set ${IPSET_CN_IP} dst -j RETURN
5iptables -t mangle -A XRAY -p UDP -s 192.168.88.0/24 -m set --match-set ${IPSET_CN_UDP_IP} dst -j RETURN
6iptables -t mangle -A XRAY -p UDP -s 192.168.1.0/24 -m set --match-set ${IPSET_CN_UDP_IP} dst -j RETURN
当你按之前的三篇文章做完,你的路由器大概的工作原理是,将所有的流量(内网,本机。排除掉目标地址是内网地址的部分),发住 xray, 在xray里配置了国内走
direct
, 国外走proxy
。也就是说把对域名和IP的判断交给了 Xray 。 对 Linux 有了解的人都知道, iptables 本质修改的是netfilter, 而netfilter是在内核空间里的,Xray是工作在用户空间的。对 Linux 路由器来说,转发流量只需要走内核就可以完成。而访问google必须要走xray,也就是用户空间。那么有没有一种办法让国内流量直接走内核空间转发?
以上只条 iptables 的指令大概的意思就是如果 dst
目标IP是在 ${IPSET_CN_IP}
里,就不走 tproxy
了。这个 ${IPSET_CN_IP}
就是一个保存了国内 IP 地址的 IPSET。
ipset是iptables的扩展,它允许你创建 匹配整个地址集合的规则。而不像普通的iptables链只能单IP匹配, ip集合存储在带索引的数据结构中,这种结构即时集合比较大也可以进行高效的查找,除了一些常用的情况,比如阻止一些危险主机访问本机,从而减少系统资源占用或网络拥塞,IPsets也具备一些新防火墙设计方法,并简化了配置.
那也就是说, 我们只要把国内的IP地址写入这个 ${IPSET_CN_IP}
IPSET中就可以了。那如何实现比较好呢?
简单做法:直接使用 APNIC 的国内的IP段列表
简单做法就是直接把 APNIC 的国内IP网段全部直接转发
这个IP段的列表非常容易拿到 在亚太互联网络信息中心的官网直接就下载的. 过滤一下里面的 CN, IPv4, 就得到了 中国的 IPv4的IP网段, 一般不会发生变化,全球的IPv4地址基本上都分配完了。
把这个 IP 段加到 ${IPSET_CN_IP}
就可以了
创建一个 ipset
1ipset -N chnroute hash:net maxelem 65536
2ipset add chnroute xxxx/xx
写一个脚本,直接导入
1#!/bin/bash
2CHNROUTE_TMP_FILE="/home/zmhu/tmp/chnroute.txt"
3
4rm -f "${CHNROUTE_TMP_FILE}"
5echo "${CHNROUTE_TMP_FILE}"
6curl -s 'https://ftp.apnic.net/stats/apnic/delegated-apnic-latest' | \
7 awk -F '|' '{if($2=="CN"&&$3=="ipv4"){printf "%s/%d\n",$4,32-log($5)/log(2)}}' \
8 > "${CHNROUTE_TMP_FILE}"
9
10# 清空一下ipset
11ipset flush chnroute
12
13for ip in $(cat $CHNROUTE_TMP_FILE); do
14 ipset add chnroute $ip
15done
进阶做法,可防广告,可防DNS泄漏
我的广告拦截是配置在 xray里面的,geoip里有广告地址
在 xray 的配置文件里 routing
-> rules
里增加配置
1// 广告流量 拦截
2{
3 "type": "field",
4 "domain": [
5 "geosite:category-ads-all"
6 ],
7 "outboundTag": "out-block"
8},
故,如果国内的广告IP直接被转发了,这里就启不到作用了。所以我想了一个取巧的办法: 监听 xray的access log
把 direct
和 out-proxy
的IP地址拿出来判断一下是否是国内的IP,如果是创建一个CN的IPSET,写进去。在 iptables 里仅针对这个CNIP的IPSET进行判断, 不仅可以排除广告的IP,还可以排除掉 DNS服务器的IP(排除掉DNS服务器的IP对防 DNS 泄漏有好处) 这里针对是否是国内IP的判断,使用了上面写的简单做法中的 APNIC
的IPSET判断就好了,简单直接。
创建国内IP列表. 注意这里是IP地址,不是IP段了,所以创建的是 hash:ip
而不是 hash:net
1ipset -N cntcpip hash:ip maxelem 65536
开启 xray的log
1{
2 "log": {
3 "loglevel": "info",
4 "access": "/mnt/tmp/xray.acess.log"
5 //"error": "/var/log/xray.error.log"
6 }
7}
监听 log 并写入 ipset 的脚本, 脚本中区分了tcp和udp(排除了53端口),区分 udp 是为了给 bittorrent 协议单独做规则用的。
cniprouter.py
1import socket
2import re
3from async_tail import tail
4from pr2modules.ipset import IPSet, PortRange, PortEntry
5from dotenv import dotenv_values
6# https://github.com/svinota/pyroute2/blob/master/examples/ipset.py
7
8def ipset_save(name, value, ipset):
9 if (not ipset.test(name, value)):
10 ipset.add(name, value)
11
12def parser_log(log_str: str):
13 if len(log_str) <=0:
14 return None
15 pattern = r"accepted (tcp|udp):([\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}):([\d]+) \[([\w|-]+) (>>|\->) ([\w|-]+)\]"
16 matches = re.search(pattern, log_str)
17 if matches:
18 group = matches.groups()
19 return {
20 'proto': group[0],
21 'remote_ip': group[1],
22 'port': group[2],
23 'in': group[3],
24 'out': group[5]
25 }
26 else:
27 return None
28def is_cn_ip(remote_ip, ipset_cn_pool_name, ipset):
29 return ipset.test(ipset_cn_pool_name, remote_ip)
30
31def save_to_ipset_dispatcher(results, config, ipset: IPSet):
32 if results['proto'] == "tcp":
33 ipset_save(config['IPSET_CN_TCP_IP'], results['remote_ip'], ipset)
34 elif results['proto'] == "udp" and results['port'] != '53':
35 ipset_save(config['IPSET_CN_UDP_IP'], results['remote_ip'], ipset)
36
37def need_to_save(results):
38 need_save = False
39 match results['out']:
40 case 'direct':
41 need_save = True
42 case 'out-kiwi':
43 need_save = True
44 case 'out-bt':
45 need_save = True
46 if (results['proto'] == 'udp' and results['port'] == '53'):
47 need_save = False
48 return need_save
49
50if __name__ == '__main__':
51 config = dotenv_values(".env")
52 XRAY_LOG_FILE = config['XRAY_LOG_FILE']
53 IPSET_APNIC_CN_IP = config['IPSET_APNIC_CN_IP']
54 IPSET_CN_UDP_IP = config['IPSET_CN_UDP_IP']
55 IPSET_CN_TCP_IP = config['IPSET_CN_TCP_IP']
56 ipset = IPSet()
57
58 for log_line in tail(XRAY_LOG_FILE):
59 for line in log_line:
60 log_str = line[0] if len(line) > 0 else ""
61 is_cn = False
62 need_save = False
63 results = parser_log(log_str)
64 if (results is not None):
65 ## 如果是direct流量可以跳过IP是否是中国的判断
66 if results['out'] in ['direct', 'out-bt']:
67 need_save = need_to_save(results)
68 if (need_save):
69 save_to_ipset_dispatcher(results, config, ipset)
70 ## 非direct流量判断一下是否是中国, 再判断是否需要写入ipset
71 else:
72 is_cn = is_cn_ip(results['remote_ip'], IPSET_APNIC_CN_IP, ipset)
73 if is_cn:
74 need_save = need_to_save(results)
75 if (need_save):
76 save_to_ipset_dispatcher(results, config, ipset)
77 print("Dst ip: {}, is_cn: {}, out: {}, proto: {}, port: {}, save: {}".format(
78 results['remote_ip'], is_cn, results['out'], results['proto'], results['port'], need_save))
.env
1XRAY_LOG_FILE=/mnt/tmp/xray.acess.log
2IPSET_APNIC_CN_IP=chnroute
3IPSET_CN_UDP_IP=cnudpip
4IPSET_CN_TCP_IP=cntcpip
requirements.txt
1async-tail==0.2.0
2pyroute2.core==0.6.13
3pyroute2.ipset==0.6.13
4python-dotenv==1.0.1
启动这个脚本,一直监听 xray的log,实时修改 IPSET. 随着你访问的越来越多, 这个IPSET就会越来越全面,你访问国内站点就会越来越快,不会发生DNS泄漏,而且广告拦截也不会受到影响。
Posts in this series
- 使用Gentoo Linux手搓路由器之四 -- 支持 IPv6
- 使用 Gentoo Linux 手搓路由器之三 -- 国内访问加速
- 使用 Gentoo Linux 手搓路由器之二 -- 透明代理
- IPTV单线复用——在Linux系统上配置
- 使用 Gentoo Linux 搭建简单路由器并支持IPTV功能