menu Chancel's blog
rss_feed
Chancel's blog
秋雨一何碧,山色倚晴空。

Linux下配置透明网关实现智能分流方案

作者:Chancel Yang, 时间:2022 Aug 02, 阅读:133

一直以来都是用openwrt作为旁路由来实现科学爱国的,使用起来十分爽快,只需要一个vm或者docker就可以欢快地跑起来

尽管openwrt是自己编译lede大的仓库,插件也是非必需不选,但这个安全性仍然非常堪忧

于是打算自己实现一个简单的旁路由透明代理的分流方案,查阅了一些相关资料,计划实现如下

  • Alpine作为操作系统(以适用于docker/vm)
  • iptables配合ipset实现透明网关
  • overture作为DNS解析
  • gost或者ss-redir实现中继

效果如下

  • 解除DNS污染,加速访问github等无需科学爱国的网站
  • 访问完全受限的部分海外网站(gfwlist)
  • 国内地址采用国内DNS解析直连访问,国外地址采用国外DNS通过ss访问
  • 让局域网设备也能通过这个透明网关实现欢快地科学爱国

这里假设我的网络是192.168.10.0/20,路由是是192.168.10.1,实现透明网关的Alpine虚拟机IP是192.168.10.100

最终希望局域网内其他设备将网关地址和DNS设置为192.168.10.100实现设备的透明代理

以下是实践过程,过程是以proxmox新建虚拟机为例,docker方案略有差别,可以自行参考实现

1. alpine

alpine是一个非常迷你的操作系统,docker容器大小仅为5Mb,ISO镜像也仅有100+M,非常适合用来定制Linux透明网关

1.1. alpine安装

下载地址: alpinelinux下载,我下载的版本是3.16.1版本

下载后导入proxmox中,并创建虚拟机

我选择的虚拟机配置大小

  • CPU:2C4T
  • MEM:1G
  • DISK:1G

开启虚拟机后,输入root用户登录,无需密码,然后输入setup-alpine开始安装

之后的安装过程与其他发行版安装无异,请自行安装

1.2. alpine网络设置

在系统安装完成后,使用root身份ssh远程进去,并修改网络设置,新增nameservers

vi /etc/network/interfaces

内容如下

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
        address 192.168.10.100
        netmask 255.255.255.0
        gateway 192.168.10.1
        nameservers 114.114.114.114

修改完成后重启网络

service networking restart

1.3. alpine软件源

重启网络后,需要更新软件源,编辑/etc/apk/repositories文件,取消community和edge的仓库注释

vi /etc/apk/repositories

内容如下

#/media/cdrom/apks
https://mirrors.tuna.tsinghua.edu.cn/alpine/v3.16/main
https://mirrors.tuna.tsinghua.edu.cn/alpine/v3.16/community
https://mirrors.tuna.tsinghua.edu.cn/alpine/edge/main
https://mirrors.tuna.tsinghua.edu.cn/alpine/edge/community
https://mirrors.tuna.tsinghua.edu.cn/alpine/edge/testing

然后更新apk仓库

apk update

一些基础的apk操作示例

# 搜索软件
apk search git
# 安装软件
apk add git
# 卸载软件
apk del git

1.4. alpine的准备工作

接下来准备相关工具,安装以下软件,并初始化运行

apk add supervisor curl iptables iptables vim wget ipset
service iptables save
service iptables start
service supervisord start

由于alpine没有使用较为先进的systemd来管理系统,我们需要手动添加以上部分软件的自启动

crontab -e

输入下面内容以确保supervisordiptables服务会在开机之后自动启动

# do daily/weekly/monthly maintenance
# min   hour    day     month   weekday command
...

@reboot /sbin/service supervisord start
@reboot /sbin/service iptables start

允许机器转发局域网请求

vim /etc/sysctl.conf

添加如下字段

net.ipv4.ip_forward=1

更新sysctl配置

sysctl -p

到此,系统的初始化设置完毕

2. gost

gost是一款使用golang编写的网络安全隧道工具,支持大部分主流协议,目前更新到V3.0版本,本文使用v2.11版本

2.1. gost服务部署

顺嘴提一下服务端的部署,服务端下载gost: gost v2.11.2下载

并使用gzip解压

gzip -d gost-linux-amd64-2.11.2.gz

运行ss协议非常简单,如下

# 方式一:直接运行ss协议
./gost-linux-amd64-2.11.2 -L=ss://chacha20:password@:8338
# 方式二:运行ss+kcp协议
./gost-linux-amd64-2.11.2 -L=ss+kcp://chacha20:password@:8338

如果VPS的网络相对较差,可以考虑采用方式二来牺牲带宽降低网络延迟

下面以方式一运行为例子,方式二请自行修改部分配置以便适配

2.2. gost客户端设置

回到我们本地的alpine系统,先下载gost: gost v2.11.2下载

并使用gzip解压

gzip -d gost-linux-amd64-2.11.2.gz

下载完成后先尝试运行

./gost -L=:18080 -F="ss://chacha20:password@server_ip:8338"

在本地使用连接192.168.10.100的18080端口,测试网络是否正常,确保ss连接是成功的

2.3. 运行gost

在直连测试ss连接是正常后,我们测试一下开启gost的透明网关模式,如下

chmod +x gost-linux-amd64-2.11.2
./gost-linux-amd64-2.11.2 -L=red://:28080 -F="ss://chacha20:password@server_ip:8338"

检查以上命令运行无报错输出后结束,写入到supervisor中作为daemon程序运行

mkdir /etc/supervisor.d
vim /etc/supervisor.d/gost.ini

输入内容如下,除了透明网关外,我们还需要保留socks5代理以方便后面DNS解析时使用

[program:gost-ss-direct]
command=/root/gost-linux-amd64-2.11.2 -L=:11080 -F="ss://chacha20:password@server_ip:8338"
autostart=true
autorestart=true
startsecs=10
user = root


[program:gost-ss-redir]
command=/root/gost-linux-amd64-2.11.2 -L=red://:28080 -F="ss://chacha20:password@server_ip:8338"
autostart=true
autorestart=true
startsecs=10
user = root

更新supervisor运行

supervisorctl update

检查运行状态

supervisorctl status

3. iptables

iptables是Linux中用来管理网络包的用户态工具,大部分操作都需要超级用户权限,其路径通常位于/sbin/iptables

iptables是由table、chain、rules组成的,用不同的表来处理不同类型的数据包,用链来处理不同时期的数据包,用规则来管理数据包的行为

3.1. 基础知识(可跳过)

要使用iptables,要掌握一定的基础网络知识,这里受限于篇幅,仅查阅了一些关于透明网关相关的iptables操作

iptables的基本构成是4表5链

  • Filter表:iptables的默认表,包含INPUT/OUTPUT/FORWARD链
  • NAT表:地址转换表,包含了OUTPUT/PREROUTING/POSTROUTING链
  • Mangle表:用于管理数据包内容的表,包含了PREROUTING/OUTPUT/FORWARD/INPUT/POSTROUTING链
  • raw表:处理异常的表,包含了PREROUTING/OUTPUT链

注:还有一个不常见的security表

此外还包含以下动作(即-j参数值)

  1. ACCEPT
  2. DROP
  3. REDIRECT
  4. RETURN
  5. SNAT
  6. DNAT
  7. MASQUERADE
  8. LOG
  9. SEMARK

iptables -A INPUT -p all -s 192.168.1.0/24 -j ACCEPT为例

其中-A表示append,-p表示协议类型,-s表示来源,-j表示动作

由上可以看出iptables的基础命令形式

iptables -t 表名 <-A/I/D/R> 规则链名 [规则号] <-i/o 网络设备> -p 协议名 <-s 源IP/源子网> --sport 源端口 <-d 目标IP/目标子网> --dport 目标端口 -j 动作

相关参数解释

  • <-A/I/D/R>:A代表添加,I代表插入,D代表删除,R代表替换
  • <-i/o 网络设备>:-i表示流入设备名称,-o表示流出设备名称
  • -p:表示protocol,常见的如tcp/udp/icmp/stcp等

接下来,我们可以看看下面的例子,帮助理解iptables的命令行规则

创建与删除规则示例

# 允许192.168.1.0/24网段访问本机22端口
iptables -A INPUT -s 192.168.1.0/24 -p tcp --dport 22 -j ACCEPT
# 查看刚才创建的规则
iptables -L -n -v
# 删除“允许192.168.1.0/24网段访问本机22端口”规则
iptables -D INPUT -s 192.168.1.0/24 -p tcp --dport 22 -j ACCEPT

iptables常见操作例子

# 允许访问本机22,80,443端口
iptables -A INPUT  -p tcp -m multiport --dports 22,80,443 -j ACCEPT
# 允许本机访问192.168.1.0/24网段的所有22端口
iptables -A OUTPUT -p tcp -d 192.168.1.0/24 --dport 22 -j ACCEPT
# 禁止访问192.168.1.1(注意协议类型为all)
iptables -A OUTPUT -p all -d 192.168.1.1 -j DROP
# 将本机端口12222的请求转发到22
iptables -t nat -A PREROUTING -p tcp --dport 12222 -j REDIRECT --to-port 22
# 限制本机端口80的并发数量最高为200,每分钟最高100个(应对DDOS攻击)
iptables -A INPUT -p tcp --dport 80 -m limit --limit 100/minute --limit-burst 200 -j ACCEPT
# 限制本机的ICMP请求
iptables -A INPUT -p icmp -j DROP
# 限制mac地址访问本机
iptables -A INPUT -m mac --mac-source 32:e6:37:5c:40 -j DROP
# 清除所有规则
iptables -F

下面是设置网关转发需要用到的NAT表操作

# 创建一个nat表,名为SSNAT
iptables -t nat -N SSNAT
# 为SSNAT表设置遇到192.168.1.1/20网段的目标IP网段则终止当前链返回上一个调用链
iptables -t nat -A SSNAT -d 192.168.1.1/20 -j RETURN
# 为SSNAT表设置将所有tcp请求转发到本机18080端口
iptables -t nat -A SSNAT -p tcp -j REDIRECT --to-port 18080
# 将SSNAT链中所有的规则追加到OUTPUT链中
iptables -t nat -A SSNAT -p tcp -j SHADOWSOCKS
# 查看所有NAT表规则
iptables -t nat -L -v -n

掌握了以上的iptables基础操作,就可以操作iptables转发来实现透明网关

3.2. 实现分流

iptables在进行地址集合查找时效率并不高,所以我们引入iptables的扩展插件ipset来处理

按照目标实现,我们需要有一份国内的IP网段,目前在github.com上已经有现成的 china_ip_list

按照我们要实现的透明网关目标做了以下的sh脚本,根据实际情况填写$your_server_ip$your_local_port两个变量

vim /root/iptables.sh

内容如下

#/bin/bash
#author:Chancel.Yang
#date:2022-07-26

remote_ip=[your_server_ip]
local_port=[your_local_port]


# 允许使用转发服务的pc白名单
for mac in $(cat ./mac-whitelist.txt ); 
do 
    /sbin/iptables -A FORWARD -m mac --mac-source $mac -j ACCEPT
done 

# 创建ipset的国内IP列表 -> https://raw.githubusercontent.com/17mon/china_ip_list/master/china_ip_list.txt
/usr/sbin/ipset -N china hash:net
for i in $(cat ./china_ip_list.txt ); 
do
    ipset -A china $i; 
done

# Create nat rules
/sbin/iptables -t nat -N SSNAT
# 在这里添加无需走ss流量的ip地址
/sbin/iptables -t nat -A SSNAT -p all -m set --match-set china dst -j RETURN
/sbin/iptables -t nat -A SSNAT -d 0.0.0.0/8 -j RETURN
/sbin/iptables -t nat -A SSNAT -d 10.0.0.0/8 -j RETURN
/sbin/iptables -t nat -A SSNAT -d 127.0.0.0/8 -j RETURN
/sbin/iptables -t nat -A SSNAT -d 169.254.0.0/16 -j RETURN
/sbin/iptables -t nat -A SSNAT -d 172.16.0.0/12 -j RETURN
/sbin/iptables -t nat -A SSNAT -d 192.168.0.0/16 -j RETURN
/sbin/iptables -t nat -A SSNAT -d 224.0.0.0/4 -j RETURN
/sbin/iptables -t nat -A SSNAT -d $remote_ip -j RETURN
# ss中继流量转发包括了udp、icmp、udp,看情况可以仅允许转发tcp流量
/sbin/iptables -t nat -A SSNAT -p tcp -j REDIRECT --to-port $local_port
/sbin/iptables -t nat -A SSNAT -p udp -j REDIRECT --to-port $local_port
/sbin/iptables -t nat -A SSNAT -p icmp -j REDIRECT --to-port $local_port
# 全局规则
/sbin/iptables -t nat -A PREROUTING -p tcp -j SSNAT
/sbin/iptables -A FORWARD -j REJECT

以上脚本主要分为3部分

  1. 使用ipset导入china_ip_list.txt文件并命名集合名为china
  2. 创建名为SSNAT的nat链,排除掉本地地址与服务器地址的连接
  3. SSNAT加入到OUTPUT链中,同时允许SSNAT转发局域网请求

运行上面的脚本缺少china_ip_list.txt 文件,china_ip_list.txt 文件在github.com可以下载,地址如下

wget https://raw.githubusercontent.com/17mon/china_ip_list/master/china_ip_list.txt

为了防止流量中转被滥用,脚本中还采用了mac地址过滤的方法,创建一个mac-whitelist.txt文件,其内容是允许使用转发服务PC的mac地址,如下

d2:d9:f8:1d:b4:2f
46:ba:63:03:8a:df

脚本目录如下

alpine:~# ls -l
total 13212
-rw-r--r--    1 root     root          1526 Aug  2 10:13 iptables.sh
-rw-r--r--    1 root     root         95316 Jul 27 11:51 china_ip_list.txt
-rw-r--r--    1 root     root            36 Aug  1 20:40 mac-whitelist.txt

执行脚本查看结果

sh /root/iptables.sh

没有错误输出的话,就可以将这个脚本设置为开机执行

crontab -e
# do daily/weekly/monthly maintenance
# min   hour    day     month   weekday command
...

@reboot /bin/sh /root/iptables.sh

到这一步,我们可以让局域网内其他机器将网关设置成192.168.10.100来测试能否正常转发网络请求

因为DNS污染仍然没有解决,即便能转发请求,但仍然无法正常访问国外网站

注:DNS污染指通过国内DNS解析去解析国外的IP地址时,DNS服务器返回一个错误的IP(或者根本不存在的IP)

此时我们虽然能区分国内外IP做到分流,也能转发局域网机器的网络请求,但面对一个错误的IP解析结果,自然无法正常访问

所以我们需要自建一个正常的DNS解析服务

4. overture

overture是一个基于Go语言开发的DNS解析服务程序,比起经典老菜ChinaDNS的优点是设置更加丰富,设置也更简单一些

4.1. 下载安装

安装方法如下

mkdir -p /root/overture && cd /root/overture
wget https://github.com/shawn1m/overture/releases/download/v1.8/overture-linux-amd64.zip
unzip overture-linux-amd64.zip

# 保留默认配置文件用于恢复(可选)
cp config.yml config.yml.bak

4.2. 策略配置

编辑配置文件config.yaml

vim config.yml

内容如下

bindAddress: :53
debugHTTPAddress: 127.0.0.1:55555
dohEnabled: false
primaryDNS:
  - name: DNS114
    address: 114.114.114.114:53
    protocol: udp
    socks5Address: 
    timeout: 6
    ednsClientSubnet:
      policy: disable
      externalIP:
      noCookie: true
  - name: AliDNS
    address: 223.5.5.5:53
    protocol: udp
    socks5Address:
    timeout: 6
    ednsClientSubnet:
      policy: disable
      externalIP:
      noCookie: true
onlyPrimaryDNS: false
alternativeDNS:
  - name: CloudFlareDNS
    address: 1.1.1.1:53
    protocol: tcp
    # 是否使用socks5代理来进行DNS查询(注意代理要支持protocol协议)
    socks5Address: 127.0.0.1:11080
    timeout: 6
    ednsClientSubnet:
      policy: disable
      externalIP:
      noCookie: true
  - name: GoogleDNS
    address: 8.8.4.4:53
    protocol: tcp
    # 是否使用socks5代理来进行DNS查询(注意代理要支持protocol协议)
    socks5Address: 127.0.0.1:11080
    timeout: 6
    ednsClientSubnet:
      policy: disable
      externalIP:
      noCookie: true
ipv6UseAlternativeDNS: false
alternativeDNSConcurrent: false
whenPrimaryDNSAnswerNoneUse: primaryDNS
ipNetworkFile: # 优先级匹配,如IP在primary文件中则直接返回primaryDNS结果,反之亦然,这里可以引入国内的IP段(如chnrouter列表),alternative默认即可
  primary: /root/overture/china_ip_list.txt
  alternative: /root/overture/ip_network_alternative_sample
domainFile: # 优先级匹配,如域名在primary文件中则直接返回primaryDNS结果,反之亦然,这里在alternative中引入gfwlist列表,primary默认即可
  primary: /root/overture/domain_primary_sample
  alternative: /root/overture/gfw_all_domain.txt
  matcher: full-map
hostsFile:
  hostsFile: /etc/hosts
  finder: full-map
minimumTTL: 0
domainTTLFile: /root/overture/domain_ttl_sample
cacheSize: 100
cacheRedisUrl:
cacheRedisConnectionPoolSize:
rejectQType:
  - 255

配置中比较重要的有3个点

  1. primaryDNS表示主DNS列表,alternativeDNS表示副DNS列表
  2. alternativeDNS中的2个DNS服务器protocol采用tcp还是udp取决于gost配置ss时有没有配置能否支持udp转发
  3. china_ip_list.txtgfw_all_domain.txt文件需要定期更新(短时间内不更新问题不大)

下面是获取china_ip_list.txtgfw_all_domain.txt文件的脚本

vim /root/overture/overture_file_download.sh

内容如下

wget https://raw.githubusercontent.com/17mon/china_ip_list/master/china_ip_list.txt
curl https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt | base64 -d | sort -u | sed '/^$\|@@/d'| sed 's#!.\+##; s#|##g; s#@##g; s#http:\/\/##; s#https:\/\/##;' | sed '/\*/d; /apple\.com/d; /sina\.cn/d; /sina\.com\.cn/d; /baidu\.com/d; /qq\.com/d' | sed '/^[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+$/d' | grep '^[0-9a-zA-Z\.-]\+$' | grep '\.' | sed 's#^\.\+##' | sort -u > temp_gfwlist.txt
curl https://raw.githubusercontent.com/hq450/fancyss/master/rules/gfwlist.conf | sed 's/ipset=\/\.//g; s/\/gfwlist//g; /^server/d' > temp_koolshare.txt
cat temp_gfwlist.txt temp_koolshare.txt | sort -u > gfw_all_domain.txt
rm -f temp_gfwlist.txt temp_koolshare.txt

运行脚本下载文件,然后执行overture程序

cd /root
sh /root/overture/overture_file_download.sh
./overture-linux-amd64 -c ./config.yml

如果输出中没有错误即可,用局域网内其他机器测试能否正常解析github.com的网址(可选)

➜  dig @192.168.10.100 www.github.com

; <<>> DiG 9.18.4 <<>> @192.168.10.100 www.github.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8772
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;www.github.com.                        IN      A

;; ANSWER SECTION:
www.github.com.         1552    IN      CNAME   github.com.
github.com.             60      IN      A       192.30.255.113

;; Query time: 243 msec
;; SERVER: 192.168.10.100#53(192.168.10.100) (UDP)
;; WHEN: Tue Jul 26 18:46:44 CST 2022
;; MSG SIZE  rcvd: 107

检查解析服务日志输出是否有异常以及解析结果是否正常

最后使用supervisor将overture设置为daemon程序运行

vim /etc/supervisor.d/overture.ini
[program:overture]
directory=/root/overture
command=/root/overture/overture-linux-amd64 -c /root/overture/config.yml
autostart=true
autorestart=true
startsecs=10
user=root

更新supervisor运行

supervisorctl update

检查运行状态

supervisorctl status

最后修改alpine网络配置中dns服务地址

vim /etc/network/interfaces
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
        address 192.168.10.100
        netmask 255.255.240.0
        gateway 192.168.2.1
        nameservers 127.0.0.1

重启网络

service networking restart

现在可以将局域网其他设备设置为192.168.10.100验证透明网关能否正常工作了

5. 结束语

由于透明网关涉及到的知识实在过于复杂了,不太适合新手折腾,而且总体来说效果不如openwrt来的方便

本文仅抛砖引玉,若对透明网关有兴趣还需要多多查阅资料

资料参考


[[replyMessage== null?"发表评论":"发表评论 @ " + replyMessage.m_author]]

account_circle
email
web_asset
textsms

评论列表([[messageResponse.total]])

还没有可以显示的留言...
[[messageItem.m_author]] [[messageItem.m_author]]
[[messageItem.create_time]]
[[getEnviron(messageItem.m_environ)]]
[[subMessage.m_author]] [[subMessage.m_author]] @ [[subMessage.parent_message.m_author]] [[subMessage.parent_message.m_author]]
[[subMessage.create_time]]
[[getEnviron(messageItem.m_environ)]]
目录