使用Debian做路由器

提醒:本文最后更新于 2021 天前,文中所描述的信息可能已发生改变,请谨慎使用。

计划

以前一直使用RouterOS,最近更新之后遇到了一些严重的BUG,决定把它换成Linux,免费并且可以顺便学习一下Linux。

Debian作为最正统的发行版,我对它一直很有好感,前几天Debian 10刚好也发布了,那么就选它吧!

开工之前先要明确目标,不然就是瞎干了。

查了半天资料后整理出来以下:

* Debian 路由器是核心路由器,只关心数据的转发,不做任何其他事情。
* `pppoeconf`实现PPPoE拨号上网。
* `wide-dhcpv6-client`从运营商那里获取IPv6前缀。
* `ipset`建立国内IP地址集合,以供iptables使用。
* `iptables`进行NAT以及对非国内地址的连接进行标记,供`ip rule(策略路由)`使用。
* `dnsmasq`作为DNS服务器以及DHCP服务器。

开工

我是在ESXI中运行路由系统的,安装过程不再赘述,这些都是基本操作了。

我先大概描述一下我的网络环境,诸位观看(真的有人吗?)理解时也会更容易。

暂时是用RouterOS上网的,等Debian 配置完成后就会关闭RouterOS,使用Debian作为网关路由器。

img

建议刚装完时先连接上别的网络,因为需要安装各种包以及更新。等pppoe连接上以后就可以直接使用pppoe上网了,

开启免密登录,不然每次输入密码登录debian非常麻烦,在自己的电脑上生成自己的key,然后cat ~/.ssh/id_rsa.pub,复制之后切换到Debian的终端,然后

echo "你的key" >> ~/.ssh/authorized_keys

安装完成后先换源,建议使用清华源,切换到root然后apt update && apt upgrade && apt install sudo三连警告。

一些常用工具装上,方便调试。

apt install dnsutils wget curl vim

Debian 10安装完成后普通用户是没有sudo权限的,我们需要去给它加上sudo权限。虽然待会安装过程中全程使用root账户,但是还是加上舒服一点。

切换到root账户,然后输入visudo,添加一行代码,顺便把默认编辑器改成vim。

Defaults        editor=/usr/bin/vim  # 改成这样

...

root    ALL=(ALL:ALL) ALL # 没错就是在这行下面👇添加
user    ALL=(ALL:ALL) ALL # user替换成你的用户名

安装的vim默认开启了可视化,想用鼠标复制个东西很困难,所以需要关闭vim可视化,在/etc/vim/vimrc最后添加两行代码:

set mouse=
set ttymouse=

接下来设置网卡IP地址:

vim /etc/network/interface

auto eth1
iface eth1 inet static
    address 10.1.1.254/24
    gateway 10.1.1.233 # 这行只是为了临时上网,待pppoe设置完成之后删除这行

iface eth1 inet6 
    address dd11:1111:1111::1/48

接下来需要开启内核转发,vim /etc/sysctl.conf

添加如下内容:

net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
net.ipv6.conf.ppp0.accept_ra=2

然后执行sysctl -p /etc/sysctl.conf

PPPoE

首先需要安装pppoeconf来进行pppoe拨号:

apt install pppoeconf

安装过程中会出现设置向导,按照指引输入账号密码和对应的网卡接口就可以设置完成。

设置完成后输入ip a就可以看到pppoe已经成功拨号。

img

此时它会在/etc/ppp/peers/下生成一个dsl-provider文件,里面包含了拨号用的各种配置信息。

此时我们可以用pon dsl-providerpoff进行pppoe的连接和断开,但是这么做也太不优雅了,而且以后管理起来也不甚方便,所以我们为它制作一个systemd服务。

首先删除/etc/network/interface文件里pppoe自动添加的配置,由于我已经删掉了相关代码片段,所以就无法上图了,诸位自行删除就好,以auto ppp0开头的就是,同时旁边有注释表明此段配置信息由pppoeconf自动生成。

删除后我们来制作pppoe.serivce

vim /etc/systemd/system/pppoe.service

内容如下:

[Unit]
Description=PPPoE connection
Before=wide-dhcpv6-client.service
[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/usr/bin/pon dsl-provider
ExecStop=/usr/bin/poff -a

[Install]
WantedBy=default.target

文件里[Unit]字段中的Before=wide-dhcpv6-client.service是为了让pppoe.serive在请求ipv6前缀服务之前启动,否则请求ipv6前缀服务会失败,我们下面会详细讲解。

写入之后用systemctl daemon-reload 命令重新加载systemd,此时就可以用systemctl start pppoesystemctl stop pppoe进行管理了,另外这里记得使用systemctl enable pppoe使它开机自启动。

现在我们就可以将/etc/network/interface中网关那一行删掉了,现在我们不需要它就可以上网了。

请求IPv6前缀

首先要安装相应的包:apt install wide-dhcpv6-client,然后为修改配置文件:

vim /etc/wide-dhcpv6/dhcp6c.conf

内容如下:

interface ppp0 {
    send ia-pd 0;
};

id-assoc pd 0 {
    prefix-interface eth0 {
        sla-id 1;
        sla-len 0;
    };
};

上述配置文件中eth0 是我用来拨号的物理接口,sla-len视前缀情况而定,总之这个数字和你的前缀相加要等于64,电信给我的前缀是::/64,我这里就填0;如果你的是::/56,那么你这里就填8;

接下来使用systemctl start wide-dhcpv6-client进行获取前缀,待命令执行结束使用ip a命令就可以看到ipv6地址已经下发成功。

使用systemctl enable wide-dhcpv6-client实现开机自启动,但是我这里有点坑,重启后发现这个服务会启动失败,查看失败原因是因为ppp0接口不存在?!

可是我明明写了Before让这个服务在pppoe服务之后启动,用了一些其他技巧也着实证明了启动顺序没错,百思不得其解之后我用了最简单粗暴的方法-延时启动

vim /etc/init.d/wide-dhcpv6-client

在启动脚本之前让它睡上8秒:

STOP_RETRY_SCHEDULE='TERM/20/forever/KILL/1'

. /lib/lsb/init-functions

test -x $DHCP6CBIN || exit 0

sleep 8  # 没错,就是在这里念咒语让它睡8秒。

if [ ! -f /etc/default/wide-dhcpv6-client ]; then
    exit 0
else
    . /etc/default/wide-dhcpv6-client
fi

现在,开机之后可以正常请求到IPv6前缀了。

电信很多地方每隔48或者72小时会强行掉线一次,之后我们又需要手动重启wide-dhcpv6-client服务,所以我们搞个脚本让它凌晨2点自动重新拨号并获取IPv6前缀。


vim /root/tools/autoConnect.sh


#!/bin/bash
systemctl restart pppoe
systemctl restart wide-dhcpv6-client

然后使用crontab -e添加脚本:

0 2 * * * /bin/bash /root/tools/autoConnect.sh

dnsmasq

首先安装dnsmasq

apt install dnsmasq

dnsmasq的配置文件放在/etc/dnsmasq.d下面,以.conf结尾的配置文件会被dnsmasq解析,而dnsmasq的配置模版文件在/etc/dnsmasq.conf,关于配置文件我就不再赘述,网上有很多详解,我这里展示一下我的配置文件。

cat /etc/dnsmasq.d/custom.dnsmasq.conf

内容如下:

interface=eth1
bind-interfaces
dhcp-range=eth1,10.1.1.100,10.1.1.140,255.255.255.0,12h
dhcp-range=eth1,::1,constructor:eth1,ra-names,12h
listen-address=127.0.0.1,10.1.1.254,dd78:1111:1111::1,::1
port=53
server=1.2.4.8
server=114.114.114.114
enable-ra

# Option
dhcp-option=option:netmask,255.255.255.0
dhcp-option=option:router,10.1.1.254
dhcp-option=option:dns-server,10.1.1.254,1.2.4.8,114.114.114.114
dhcp-option=option6:dns-server,[dd78:1111:1111::1]

现在我们可以将RouterOS关闭了,debian已基本配置完成,我们设置NAT之后就可以上网了。

使用systemctl restart dnsmasq重新启动dnsmasq,开机启动服务的指令相比我也不用在重复了。

iptables

电脑现在还不能上网,先配置两条规则,让电脑可以上网:

iptables -t nat -A POSTROUTING -s 10.1.1.0/24 -o ppp0 -j MASQUERADE
ip6tables -t nat -A POSTROUTING -s dd78:1111:1111::/48 -j MASQUERADE

iptables规则生成之后是在内存里的,重启之后会失效,我们需要一个守护程序来保存iptables规则,并且在开机时恢复规则。

apt install iptables-persistent

然后我们将规则保存在这里:

iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6

将此服务设置为开机启动后,它会自动从这两个文件读取配置信息。

另外iptables-persistent安装后用systemctl管理时,名称为netfilter-persistent,我刚开始设置开机启动时提示没有这个服务也是摸不着头脑,查了半天才知道安装之后是叫这个名字。

ipset

现在基本配置完成,但是我们需要分流国内外流量,将国外IP流量定向至另一条线路进行加速,所以我们需要用ipset维护一个国内IP列表。

首先安装ipset:

apt install ipset

首先创建一个非公网地址的列表,后面有用得到,代码如下:

ipset create localIPv4 hash:net
ipset add localIPv4 10.0.0.0/8
ipset add localIPv4 172.16.0.0/12
ipset add localIPv4 192.168.0.0/16
ipset add localIPv4 100.64.0.0/10

ipset save > /etc/ipset/ipset.conf  # 保存到文件里

ipset列表保存之后保存在内存里,我们需要一个服务来让它开机读取文件恢复列表。

代码如下:

vim /etc/systemd/system/ipset.service


[Unit]
Description=ipset persistent rule service
Before=iptables.service netfilter-persistent.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/ipset restore -f /etc/ipset/ipset.conf
ExecStop=/usr/sbin/ipset save > /etc/ipset/ipset.conf
[Install]
WantedBy=multi-user.target

然后systemctl三连:

systemctl daemon-reload
systemctl enable ipset
systemctl start ipset

还需要将中国大陆的IP段写入ipset,这里我搞了个脚本,可以直接拿去使用。

#!/bin/bash

curl 'http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest' | grep CN > /tmp/cnIP.txt

# Test IPv4 list

ipError="NOT"
listError="not exist"
result=$(ipset list cnIPv4 2>&1)

if [[ $result =~ $listError ]]
then
    ipset create cnIPv4 hash:net
    echo "cnIPv4集合不存在,已创建!"
else
    ipset flush cnIPv4
    echo "cnIPv4集合存在,已清空!"
fi

# Update IPv4 List
cat /tmp/cnIP.txt | grep ipv4 | awk -F\| '{ printf("%s/%d\n", $4, 32-log($5)/log(2)) }' > /tmp/cnIPv4.txt
cat /tmp/cnIPv4.txt | while read line
do
    result=$(ipset test cnIPv4 $line 2>&1)
    if [[ $result =~ $ipError ]]
    then
	    ipset add cnIPv4 $line
    fi
done
echo "cnIPv4更新完毕!!"
rm /tmp/cnIPv4.txt

# Test IPv6 List

result=$(ipset list cnIPv6 2>&1)

if [[ $result =~ $listError ]]
then
    ipset create cnIPv6 hash:net family inet6
    echo "cnIPv6集合不存在,已创建!"
else
    ipset flush cnIPv6
    echo "cnIPv6集合存在,已清空!"
fi

# Update IPv6 List
cat /tmp/cnIP.txt | grep ipv6 | awk -F\| '{ printf("%s/%d\n", $4, 32-log($5)/log(2)) }' > /tmp/cnIPv6.txt
cat /tmp/cnIPv6.txt | while read line
do
    result=$(ipset test cnIPv6 $line 2>&1)
    if [[ $result =~ $ipError ]]
    then
        ipset add cnIPv6 $line
    fi
done
echo "cnIPv6更新完毕!!"
rm /tmp/cnIPv6.txt
rm /tmp/cnIP.txt

# Save to files
ipset save > /etc/ipset/ipset.conf
echo "ipset已写入文件!!!"

策略路由

内网有一台代理服务器,我们将所有去往国内的流量全部交给这台代理服务器,这里就需要做策略路由。

同时因为为了防止浪费流量,只将需要加速的设备流量转发给代理服务器,所以还需要维护一个ipset列表,将这些设备IP地址放进去。

代码如下:

ipset create goWorld hash:net
ipset add goWorld 10.1.1.22  ##自行输入你的设备ip地址

然后添加一个路由表:

vim /etc/iproute2/rt_tables


252 goProxy

局域网代理服务器地址为10.1.1.88。

然后添加一条路由:

ip route add default via 10.1.1.88 table goProxy

然后在iptables防火墙里对这些设备流量进行标记,并将这些设备的dns请求转发给代理服务器:

iptables -t mangle -A PREROUTING -m set --match-set goWorld src -m set ! --match-set localIPv4 dst -m set ! --match-set cnIPv4 dst -j MARK --set-mark 20

iptables -t nat -A PREROUTING -p udp --dport 53 -m set --match-set goWorld src -j DNAT  --to-destination 10.1.1.88:53

记得用iptables-save > /etc/iptables/rules.v4进行保存!

然后添加策略路由:

ip rule add fwmark 20 table goProxy

需要注意这些路由也是保存在内存的,为了让它永久生效,需要将它写进网卡配置文件里:

vim /etc/network/interface

...

auto eth1
iface eth1 inet static
    address 10.1.1.254/24
    up ip route add default via 10.1.1.88 table goProxy
    down ip route del default via 10.1.1.88 table goProxy
    up ip rule add fwmark 20 table goProxy
    down ip rule del fwmark 20 table goProxy

现在,去往国外的流量就全部走了代理服务器,安装npm包的时候再也不用等个十分钟啦!万岁!

ip6tables

ipv6代理和ipv4是一个套路,我就不再赘述了。

另外我IPv6的代理搭建完毕后,发现速度还是非常差劲,不如IPv4,我就直接不用IPv6了。

注意这里的IPv6不是说完全不用,只是在防火墙上将去往国外的IPv6流量全部reject掉,这样程序就会乖乖的使用IPv4,而国内IPv6速度尚可,完全不用关闭它。

return这里我实验了很多次才调试好,很多程序在使用防火墙drop时会立刻回落到IPv4,有些程序在reject时才会立刻回落到IPv4,搞得人很是头大,最后的试验出的具体规则如下:

ip6tables -A FORWARD -m set ! --match-set cnIPv6 dst ! -d dd78:1111:1111::/48 -j REJECT --reject-with icmp6-adm-prohibited

DDNS

这里我也写了一个现成的shell脚本,我放在了github上,有需要请自取。

Powered By Hexo & Theme Veni