前言

之前自己的手机上一直使用SS作为外网访问工具使用至今。不过在半年前自己将家里路由的外网访问工具切换成了V2ray,并且使用iptables将网络包进行过滤做成了全局透明代理。最近自己将手机解锁并换成了AOSP,有了root权限干啥都方便.相较于VPN模式,全局透明代理会更加省电和无感.在这里也做一下记录防止某天手机炸机了需要重新设置什么的.

本人手机环境

  • Android 11
  • Magisk

Magisk的插件市场上有一个V2ray for Android,但是这个插件作者已经没有维护了,最新的插件部署也有点问题,所以自己fork了一份并且修改为可用,并且增加了一个App来辅助管理。

安装

下载Zip文件之后在Magisk Manager中本地安装即可。

第一次运行

安装完成后/data/v2ray下已经有一份config.json,建议仅编辑outbound的代理服务器规则:

{
  ...
  "outbounds": [{
    "protocol": "vmess",
    "settings": {"vnext":[{"address":"110.110.110.110","port":8080,"users":[{"id":"xxxx","alterId":0}]}]},
    "tag": "tk"
  }
  ,{"protocol": "freedom","settings": {},"tag": "direct"}
  ***
  ],
  "dns":{
    ...
    "servers":[
      ...
      {
        "address":"https://dns.google/dns-query",
        "clientIp": "10.10.10.10", "skipFallback": true,
        "domains":["geosite:gfw"]
      }
    ]
  },
  ...
}

插件使用

由于集成了管理应用,可以更方便起停程序和iptables规则:

APP已实现的功能

  • 启动/停止V2Ray程序
  • 启动/停止iptables规则
  • 刷新iptables规则
  • 查看/修改config.json,目前仅支持文本模式修改,更方便的设置页面还在施工中
  • 分应用代理/全局代理,分应用代理不支持选择系统应用

使用命令行

虽然有了管理应用来进行基本的管理,不过插件底层是通过shell脚本来实现功能的。在shell中执行也可以实现插件的管理

/data/adb/modules/v2ray/script/v2ray.service (start|stop|restart|status) # V2ray进程操作
/data/adb/modules/v2ray/script/v2ray.tproxy (enable|disable|renew) # iptables规则操作

详细信息请参考项目说明页面

管理APP截图

v2manager

关于DNS污染问题

基于前文的默认配置,在手机层面只需指定dns.alidns.com或者d.rubyfish.cn等国内比较快速的DOT服务就好,对于被墙域名则会根据配置文件的路由规则侦测出域名后重新使用DOH代理查询.这样既保证了国内网络的速度也保证了外部网络的可用.

DNS设置

  • Android 9.0 +
    网络和互联网>私人DNS中设置
  • Android 9.0 -
    编辑/data/v2ray/resolv.conf文件

关于Ipv6

目前的代理并未全局考虑在移动数据中的ipv6的情况,所以如果手机开启了ipv6的话可能会出现无法代理的情况,建议禁用移动数据的ipv6.
可以在移动数据的接入点(APN)设置中的APN协议APN漫游协议中选择IPv4来禁用.

原理

前文说过这里的代理实现是基于iptables实现的透明代理,这些设置可以通用至所有的Linux,如果在使用Linux搭建主路由或者旁路由,这个方式都适用,所以特别来说明一下.简单来说就下面这两个步骤:

  • iptables拦截流量转发至v2ray
  • v2ray甄别流量选择代理

虽然就这两个简单的步骤,但是在实施的过程中还是有很多需要注意点.

iptables拦截流量

对于iptables的基本使用请另行查阅,这里不作介绍.在本文的Android环境下,主要使用如下命令来拦截并转发流量:

iptables -t nat -N V2RAY # 建立V2子链
iptables -t nat -A V2RAY -p tcp -j REDIRECT --to-ports 65535 # V2子链中的请求会被转发到V2监听端口65535
iptables -t nat -A OUTPUT -m owner --uid-owner xxx -j V2RAY # 将应用流量转发至V2子链

在Android中拦截应用流量分两种:

iptables -t nat -A OUTPUT -m owner --uid-owner xxx -j V2RAY # 某一应用的流量
iptables -t nat -A OUTPUT -m owner ! --uid-owner inet -j V2RAY # 全局(不属于V2的所有流量)

ps:v2运行在inet用户下,所以第二条命令会转发所有不是v2发出的流量.

内网广播环境下的流量风暴

在局域网环境下除了我们上网产生的正常流量之外还有我们其他网络服务产生的另外一种流量:广播,这种流量没有目标地址,是对局域网内的所有机器发出的,本来这种流量会被操作系统正常的处理,但是在v2ray却没有相应的处置方式,如果这种流量进入了v2则会导致广播风暴,大幅占用系统资源甚至崩溃.
对于这种请求,我们需要完全隔绝内网请求进入v2:

iptables -t nat -A V2RAY -d 0.0.0.0/8 -j RETURN
iptables -t nat -A V2RAY -d 127.0.0.0/8 -j RETURN
iptables -t nat -A V2RAY -d 169.254.0.0/16 -j RETURN
iptables -t nat -A V2RAY -d 172.16.0.0/12 -j RETURN
iptables -t nat -A V2RAY -d 192.168.0.0/16 -j RETURN
iptables -t nat -A V2RAY -d 224.0.0.0/4 -j RETURN
iptables -t nat -A V2RAY -d 240.0.0.0/4 -j RETURN

ps:根据iptables原理,请确保内网拦截的命令在其它命令之前.

v2ray流量甄别

v2的流量甄别主要依靠如下的设置,打开sniffing之后就会嗅探出请求的域名,并根据路由规则来选择是否需要代理.

  "inbounds": [{
    ...
    "sniffing": {
      "enabled": true, 
      "destOverride": ["http", "tls"]
    }
    ...
  }],

在嗅探出域名之后v2并不会信任原来的DNS结果,它自己会发出安全的DNS请求,所以在v2中也需要相应的设置DNS.

"dns":{
    "hosts": {"dns.google":"8.8.8.8", "dns.alidns.com":"223.5.5.5"},
    "servers":[
      {
        "address":"223.5.5.5",
        "skipFallback": true,
        "domains":["geosite:cn"]
      },
      {
        "address":"https://dns.google/dns-query",
        "clientIp": "10.10.10.10", "skipFallback": true,
        "domains":["geosite:gfw"]
      }
    ]
  }

对于未匹配的域名将使用第一个DNS来解析,这里需要大家根据情况调整.

最后就是路由选择了.

  "routing": {
    "rules":[
      ,{"type": "field","domain": ["geosite:cn"],"outboundTag": "direct"}
      ,{"type": "field","domain": ["geosite:gfw"],"outboundTag": "tk"}
      ,{"type": "field","network": "tcp,udp","outboundTag": "direct"}
    ]
  }

扩展至路由

上面的设置是基于Android的,如果在路由环境下来实现则需要一些取舍才行.

1.进入V2的流量取舍

Android下iptables可以通过-m owner --uid-owner xxx来筛选某一用户的流量,但是普通Linux没有这个模块,我们只能选择所有流量进入V2RAY链或者采用其他方式来筛选.

  • 所有流量进入V2
    在国内直连的情况下,大流量会带来的巨大性能开销,小路由可能会内存紧张.

  • 通过ipset筛选流量
    目前ipset仅能通过dnsmasq来设置,如果有使用dnsmasq来架设本地安全DNS最好,没有的话需要另外再折腾了.下面是dnsmasq设置的概要:

# 新建ipset
ipset create xxx ip:hash
# dnsmasq在查询域名时将某个域名结果加入ipset(dnsmasq.conf)
server=/google.com/127.0.0.1#8053 #这个域名会被转发至安全DNS
ipset=/google.com/xxx
# iptables筛选ip流量
iptables -t nat -D PREROUTING -m set --match-set xxx dst -j V2RAY # 路由模式下在PREROUTING链拦截

2.流量侦测所带来的二次DNS的性能取舍

其实这算不上什么取舍,国内DNS基本几ms的事,被墙的再多走一次安全路线也无可厚非.如果前端有ipset进行过流量筛选,那么后面就没必要设置这么多了,流量侦测.DNS.路由什么的都可以不用设置,简单粗暴的直接走代理就完事.

关于路由下可以使用的iptables规则有个自己正在使用的脚本,可以参考一下.

这里仅简单做一个路由设置的基本说明,路由上的具体实现也有很多方法,需要配合实际环境来设置,如有需要在另写文章吧.