多路出口带宽代理实现

背景

20条宽带出口实现20个独立IP的代理服务,如用户从出口A访问进来,代理服务也必须是使用出口A去访问被代理的服务。

涉及资源

  • 交换机:华为 S5735S-L48TAS-A *1
  • 路由:Mikrotik RB5009UG+S+(v7.2.3) *1
  • 服务器:CentOS7.9 *1
  • ISP: 电信(PPPoE拨号) * 40

涉及技术点

  • 交换机:VLAN、Trunk、链路聚合等
  • 软路由:SNAT、DNAT、Bridge、策略路由等
  • 代理服务器: nginx(HTTP代理)、dante(Socks5代理)

网段规划

  • 内网网关:172.16.255.1/24
  • 代理服务器:
    • 172.16.255.101/24 <-> pppoe-out01
    • 172.16.255.102/24 <-> pppoe-out02
    • ...
    • 172.16.255.140/24 <-> pppoe-out40

部署要略

交换机设备

根据软规则对交换机进行如下端口规划:

序号 端口号 VLAN 备注
1 GE 0/0/1 101 pppoe-out01
2 GE 0/0/2 102 pppoe-out02
3 GE 0/0/3 103 pppoe-out03
...
40 GE 0/0/40 140 pppoe-out40
21 GE 0/0/45 - 聚合链路成员接口
22 GE 0/0/46 - 聚合链路成员接口
23 GE 0/0/47 - 聚合链路成员接口
24 GE 0/0/48 - 聚合链路成员接口

Mikrotik RouterOS设备

创建聚合接口

创建脚本

/interface bonding
add arp=enabled arp-interval=100ms disabled=no mode=802.3ad mtu=1500 name=bond0 primary=none \
    slaves=ether7,ether8,ether5,ether6

基于聚合接口创建vlan子接口

创建脚本

:for no  from=1 to=9 do={  /interface vlan add interface=bond0  mtu=1500 name=("vlan1".$no use-service-tag=no vlan-id=("10".$no) }
:for no  from=10 to=40 do={  /interface vlan add interface=bond0  mtu=1500 name=("vlan1".$no use-service-tag=no vlan-id=("1".$no) }

基于聚合接口创建bridge接口

新增专用bridge接口,脚本如下(部分省略):

<...>
:for no  from=10 to=40 do={ /interface bridge add admin-mac=("E4:8D:8C:1D:25:".$no) ageing-time=5m arp=enabled arp-timeout=auto auto-mac=no dhcp-snooping=no disabled=no fast-forward=no igmp-snooping=no mtu=auto name=("ct-".$no) }

将vlan接口添加到对应的bridge接口

脚本如下(部分省略)

<...>
:for no  from=41 to=60 do={ /interface bridge port add auto-isolate=no bpdu-guard=no bridge=("ct-".$no) interface=("vlan1".$no) internal-path-cost=10 learn=auto  pvid=1 restricted-role=no restricted-tcn=no tag-stacking=no trusted=no unknown-multicast-flood=yes unknown-unicast-flood=yes }

创建pppoe拨号接口

脚本如下(部分省略)

/interface pppoe-client
add add-default-route=yes allow=pap,chap,mschap1,mschap2 default-route-distance=2 dial-on-demand=no disabled=no interface=ct-01\
  name=pppoe-out01 profile=default use-peer-dns=no user=USERNAME
add add-default-route=yes allow=pap,chap,mschap1,mschap2 dial-on-demand=no disabled=no interface=ct-02\
  name=pppoe-out01 profile=default use-peer-dns=no user=USERNAME
<...>

策略路由

每个源IP需要保证固定出口,所以我们需要使用策略路由来实现,大致方法就是根据源IP去决定走哪个出口,实现如下:

脚本如下(部分省略)

  • 创建路由表
...
:for no  from=10 to=40 do={  /routing table add name=("proxy".$no) fib }

玛德,规则软过头了

  • 在Mangle表中的Prerouting链定义源IP并生成20个路由表
...
:for no  from=10 to=40 do={ /ip/firewall/mangle add src-address=("172.16.255.1".$no) chain=prerouting action=mark-routing new-routing-mark=("proxy".$no) passthrough=yes }
  • 根据创建好的路由表,添加路由
...
:for no  from=10 to=40 do={  /ip/route add dst-address=0.0.0.0/0 gateway=("pppoe-out".$no) routing-table=("proxy".$no) }

规则下次适度软下就好了。

  • 创建SNAT规则
...
:for no  from=10 to=40 do={ /ip firewall nat add action=masquerade chain=srcnat out-interface=("pppoe-out".$no) src-address=("172.16.255.1".$no) }

  • 创建DNAT规则
<...>
:for no  from=41 to=60 do={ /ip firewall nat add chain=dstnat action=dst-nat to-addresses=("172.16.255.1".$no) to-ports=3014 protocol=tcp src-address-list=src-access-proxy in-interface=("pppoe-out".$no) dst-port=3014 log=no }

<...>
:for no  from=41 to=60 do={ /ip firewall nat add chain=dstnat action=dst-nat to-addresses=("172.16.255.1".$no) to-ports=3015 protocol=tcp src-address-list=src-access-proxy in-interface=("pppoe-out".$no) dst-port=3015 log=no }

因未配置用户名密码且客户都是固定IP,所以定义了一个源地址访问地址列表:src-access-proxy

代理服务器

多IP配置

往`/etc/sysconfig/network-scripts/ifcfg-eth0文件中加入以下内容:

IPADDR1=172.16.255.102
PREFIX1=24
...
IPADDR59=172.16.255.60
PREFIX59=24

快速生效

# nmcli conn reload 
# nmcli conn up eth0

socks5代理

批量生成配置文件、systemd服务

#!/usr/bin/env bash

prefix=172.16.255
port=3014

for no in {101..140}
do
cat  > /etc/sockd_$no.conf<<EOF
logoutput: syslog
user.privileged: root
user.unprivileged: nobody

internal: $prefix.$no port=$port
external: $prefix.$no

socksmethod: none
clientmethod: none

client pass {
    from: 0.0.0.0/0 to: 0.0.0.0/0
    log: connect disconnect error
}

socks pass {
    from: 0.0.0.0/0 to: 0.0.0.0/0
    log: error
}
EOF

cat  > /usr/lib/systemd/system/sockd_$no.service<<EOF
[Unit]
Description=Dante SOCKS proxy
After=network-online.target

[Service]
Type=simple
ExecStart=/usr/sbin/sockd -f /etc/sockd_$no.conf
StandardOutput=syslog
StandardError=syslog
LimitNPROC=infinity
LimitNOFILE=10240
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

systemctl start sockd_$no.service
systemctl enable sockd_$no.service
done

查看端口监听状态

# ss -tln |grep 3014
LISTEN     0      511    172.16.255.140:3014                     *:*
...
LISTEN     0      511    172.16.255.103:3014                     *:*
LISTEN     0      511    172.16.255.102:3014                     *:*
LISTEN     0      511    172.16.255.101:3014                     *:*

HTTP代理

http代理第一闪现的是squid,经过测试squid在单节点多IP的场景不受控制,遂转入到nginx,使用以下脚本快速生成配置文件

prefix=172.16.255

for no in {101..140}
do
cat >/opt/nginx/conf/conf.d/$no.conf<<EOF
server {
    listen   $prefix.$no:3015;

    # forward proxy for CONNECT request
    proxy_connect;
    proxy_connect_allow            all;
    proxy_connect_connect_timeout  30s;
    proxy_connect_read_timeout     90s;
    proxy_connect_send_timeout     90s;
    
    proxy_connect_bind     $prefix.$no;
    
    location / {
        proxy_pass \$scheme://\$http_host\$request_uri;
        proxy_bind $prefix.$no;
    }

    access_log /var/log/nginx/${no}_access.log combined buffer=64k flush=1m;
}
EOF
done

测试

测试脚本

#!/usr/bin/env bash


proxy_ips=(
   IP1
   IP2
   ...
   IP40
)

url=${1:-myip.ipip.net}

echo "[+] socks5 proxy testing"
for ip in ${proxy_ips[@]}
do
    echo "[-]$ip testing result: $(curl -s /dev/null --connect-timeout 3 -x socks5://$ip:3014 $url)"
done

echo "[+] http proxy testing"
for ip in ${proxy_ips[@]}
do
    echo "[-]$ip testing result: $(curl -s /dev/null --connect-timeout 3 -x socks5://$ip:3015 $url)"
done

测试结果:代理IP的返回来的IP一致。

后续

DDNS

由于是当前是使用IP,可以使用域名来绑定出口。

问题答疑

1、代理服务器重启后,有几个socks 地址代理失败,经过查看,失败是因为系统未监听该端口,查看日志,内存资源分配失败导致,当前虚拟机内存1G。加资源管上。