>>> thanks Zsy


在树莓派上,利用V2Ray构建透明网关,实现Wireguard接入后无感科学上网

前言

网上关于透明网关,旁路由的文章很多,大多数基于OpenWrt,硬件方面使用R2s,刷路由固件等等,它们的优点是简单快捷易上手。
而我的需求是,开源,低依赖,兼顾安全和使用。最好能设备之间获取相同的体验,一次(一处)配置,重复利用,减少配置。
根据"V2Ray 白话文指南 - 透明代理"的指引进行了简单的改进,使Wireguard接入的client都可以访问内部的DNS,自带透明代理。

2023.10.21,更新V2Ray与Wireguard的nftables共存实验 (终结篇)

准备

  • 树莓派,nftables
  • V2Ray/V2fly
  • Wireguard

数据流向

        WG proto   ┌────────┐  port forward   ┌───────┐  dokodemo-door   ┌───────────┐
 Client───────────►│ Router ├────────────────►│ V2Ray ├─────────────────►│ Wireguard │
                   └────────┘                 └───────┘                  └───────────┘

主要步骤:

  1. nftable;
  2. V2Ray;
  3. 创建Wireguard Client;
  4. 测试。

拓扑情况:

  • 局域网网段:10.12.0.0/24
    • 路由地址: 10.12.0.200
    • 树莓派地址: 10.12.0.100
  • Wireguard 网段:10.16.0.0/24

nftables部分 —— ipv4-chnroute.nft

用于nftable对国内IP的进行直连。sed指令用于移除数据源中IPv6的数据。
创建 chnroute.nft,保存到 /etc/nftables/chnroute.nft

curl -sL https://github.com/Loyalsoldier/geoip/raw/release/text/cn.txt -o china_ip_list.txt
sed -i -r -e '/([^:]+\:){1,}.*$/d' china_ip_list.txt

echo "define chnroute_ipv4 = {" > chnroute.nft
cat china_ip_list.txt | awk -F\| '{ printf("%s,\n", $1) }' >> chnroute.nft
echo "}" >> chnroute.nft

nftables部分 —— ipv4-private.nft

用于nftable对局域网IP的进行直连。
创建 private-ipv4.nft,保存到 /etc/nftables/private-ipv4.nft

define private_ipv4 = {
    0.0.0.0/8         ,
    10.0.0.0/8        ,
    100.64.0.0/10     ,
    127.0.0.0/8       ,
    169.254.0.0/16    ,
    172.16.0.0/12     ,
    192.0.0.0/24      ,
    192.0.2.0/24      ,
    192.88.99.0/24    ,
    192.168.0.0/16    ,
    198.18.0.0/15     ,
    198.51.100.0/24   ,
    203.0.113.0/24    ,
    224.0.0.0/4       ,
    233.252.0.0/24    ,
    240.0.0.0/4       ,
    255.255.255.255/32,
}

注意,private_ipv4这里的IP地址,记得把服务器的地址加进去,就是直连远端的服务器。当然,也可以加一些DNS服务商的地址,令DNS查询直连。

nftables部分 —— ipv4-whitelist.nft

顾名思义白名单,主要用途为服务器地址,ntp服务,dns服务等等的直连操作。

nftables部分 —— V2Ray(V2Fly)

保存为v2ray.nft
本部分主要兼顾透明代理,假如单单网关或代理模式,则无需本部分

include "/etc/nftables/ipv4-whitelist.nft"
include "/etc/nftables/ipv4-private.nft"
include "/etc/nftables/ipv4-chnroute.nft"

define DIRECT-IPV4 = {
    $whitelist_ipv4,
    $private_ipv4,
    $chnroute_ipv4,
}

table ip v2ray {
    chain PREROUTING {
        type filter hook prerouting priority filter; policy accept;
        ip daddr $DIRECT-IPV4 return

        meta l4proto { tcp, udp } ip daddr 10.12.0.0-10.12.0.255 return
        meta mark 0x000000ff return
        meta l4proto { tcp, udp } meta mark set 0x00000001 tproxy to 127.0.0.1:12345 accept
    }

    chain OUTPUT {
        type route hook output priority filter; policy accept;
        ip daddr $DIRECT-IPV4 return

        meta l4proto { tcp, udp } ip daddr 10.12.0.0-10.12.0.255 return
        meta mark 0x000000ff return
        meta l4proto { tcp, udp } meta mark set 0x00000001 accept
    }
}

table ip v2ray-filter {
    chain DIVERT {
        type filter hook prerouting priority mangle; policy accept;
        meta l4proto tcp socket transparent 1 meta mark set 0x00000001 accept
    }
}

nftables部分 —— Wireguard

保存为wg.sh

nft add table wg2nat
nft add chain wg2nat PREROUTING { type nat hook prerouting priority -100 \; }
nft add rule wg2nat PREROUTING meta nftrace set 1
nft add chain wg2nat INPUT { type nat hook input priority 100 \; }
nft add chain wg2nat OUTPUT { type nat hook output priority -100 \; }
nft add chain wg2nat POSTROUTING { type nat hook postrouting priority 100 \; }
nft add rule wg2nat POSTROUTING oifname "eth0" ip saddr 10.16.0.0/24 counter masquerade comment "wireguard-nat-rule"

或者使用nftable格式,下面的格式并没有counter,意思为不会统计包的数量。下面内容追加到nftables部分 —— V2Ray(V2Fly)之后。

table ip wg2nat {
    chain PREROUTING {
        type nat hook prerouting priority dstnat; policy accept;
    }
    
    chain INPUT {
        type nat hook input priority 100; policy accept;
    }
    
    chain OUTPUT {
        type nat hook output priority -100; policy accept;
    }
    
    chain POSTROUTING {
        type nat hook postrouting priority srcnat; policy accept;
        oifname "eth0" ip saddr 10.16.0.0/24 masquerade comment "wireguard-nat-rule"
    }
}

V2Ray部分

主要部分routing,inbounds。PORT-ROUTE-FORWARD-PORT为路由映射的端口,PORT-WG-REAL-WORKING-PORT为Wireguard实际运行的端口。

routing需要配置国内geoip的规则,因为Wireguard的流量判断不被nftable处理。

特别,routing的规则(rules)可做2条,1允许进入的IP白名单,2拒绝其他的IP,以提高安全。
特别2,wg接入的客户端访问内网设备的时候,需要在inbounds添加dokodemo-door做转发,通过访问网关实现。
后续,思考如何不通过V2Ray进行透明代理,添加 meta l4proto { tcp, udp } ip daddr 10.16.0.0-10.16.0.255 return 使wg的IP穿过?有经验的朋友,可以在v2ex上留言讨论。

{
  "log": {},
  "dns": {},
  "routing": {
    "domainStrategy": "IPIfNonMatch",
    "domainMatcher": "mph",
    "rules": [
      {
        "outboundTag": "Direct",
        "port": "123",
        "network": "udp",
        "type": "field"
      },
      {
        "outboundTag": "Direct",
        "port": "{PORT-WG-FORWARD-PORT}",
        "network": "udp",
        "type": "field"
      }
    ]
  },
  "inbounds": [
    {
      "tag": "TSP",
      "protocol": "dokodemo-door",
      "listen": "127.0.0.1",
      "port": 12345,
      "settings": {
        "network": "tcp,udp",
        "followRedirect": true
      },
      "streamSettings": {
        "sockopt": {
          "tproxy": "tproxy",
          "mark": 255
        }
      }
    },
    {
      "tag": "WG",
      "protocol": "dokodemo-door",
      "listen": "0.0.0.0",
      "port": {PORT-ROUTE-FORWARD-PORT},
      "settings": {
        "address": "127.0.0.1",
        "port": {PORT-WG-REAL-WORKING-PORT},
        "network": "udp",
        "followRedirect": false
      },
      "streamSettings": {
        "sockopt": {
          "tproxy": "tproxy",
          "mark": 255
        }
      }
    }
  ]
}

Wireguard部分

没有什么要注意的,根据上面规划来划分网段即可。PiVPN已经提供了很完备的安装流程。
用tcpdump去debug数据包时的指令:
tcpdump -i wg0 -i eth0 -nn not icmp and 'port !443 && port !80 && port !53 && port !33 ' -w wg2.cap

以服务方式运行——V2Ray

以服务方式运行v2ray,储存为/lib/systemd/system/net.service

[Unit]
Description=Net Service
After=network.target nss-lookup.target

[Service]
User=nobody
WorkingDirectory=/{工作目录}

Environment="V2RAY_CONF_GEOLOADER=memconservative"
Environment="V2RAY_BUF_READV=auto"
Environment="V2RAY_LOCATION_ASSET=/{V2RAY的资源目录}"

CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
NoNewPrivileges=true
ExecStartPre=/usr/sbin/sysctl -w net.core.rmem_max=2500000
ExecStart=/usr/local/bin/net -c /{配置目录}/config.json
# ExecStopPost=/usr/bin/systemctl stop coredns.service
Restart=on-failure
RestartPreventExitStatus=23
LimitNPROC=500
LimitNOFILE=1000000

RestartSec=3
TimeoutStopSec=10

StandardOutput=append:/var/log/net.log
StandardError=append:/var/log/net.log

[Install]
WantedBy=multi-user.target

以服务方式运行——nftables规则

以服务方式运行透明网关,储存为/etc/systemd/system/tproxyrule.service

[Unit]
Description=Tproxy rule
After=network.target
Wants=network.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStartPre=/sbin/ip rule add fwmark 1 table 100 ; /sbin/ip route add local 0.0.0.0/0 dev lo table 100
ExecStart=/sbin/nft -f /etc/nftables/tproxy-rules.nft
ExecStop=/sbin/ip rule del fwmark 1 table 100 ; /sbin/ip route del local 0.0.0.0/0 dev lo table 100 ; /sbin/nft flush ruleset

[Install]
WantedBy=multi-user.target

参考资料