Wi-Fi Call Wherever you are

从 4G 时代开始，蜂窝网络的语音功能不再支持传统的分组交换系统，而是以 IP 网络替代。这意味着，目前在纯 LTE 或更高级网络（NR）上的语音通话本质上都是 VoIP 实现，这就带来了一个机遇：为什么一定要在运营商的蜂窝网络内实现 VoIP 呢？我们完全可以在外部网络访问这一基于 IP 的核心网并使用移动语音和多媒体功能。这就是 UMA（Unlicensed Mobile Access），也就是我们今天所知道的「Wi-Fi Call / VoWiFi」。

Wi-Fi Call 的本质是建立一个到运营商 GAN（通用接入网）的 IPSec 隧道，并且由此连接到 IMS 核心网来传输语音和多媒体业务。

现状

要使用 Wi-Fi Call 非常简单，对于 iOS，我们只需要在设置 > 蜂窝网络 > 运营商 > Wi-Fi 通话中启用即可。而 Android 则由于系统碎片化而存在差异，但一般是在「通话」App 的设置中打开 Wi-Fi 通话。

要注意的是，运营商并不总是（或者说，永远不）平等对待客户。与 eSIM 类似，很多运营商只为后付费客户（准确来说是有长期服务合约的客户）提供这一功能，而预付费客户则无法使用。

当你身处当地时，Wi-Fi Call 的唯一作用可能只是帮助你在蜂窝信号不佳的地区获得稳定的通话和短信，只有极少数运营商可能为使用 Wi-Fi Call 的客户提供区别于蜂窝网络的优惠套餐。

然而，当你身处海外时情况就有所不同了。由于 Wi-Fi Call 直接接入运营商核心网，避免了漫游，因此可以帮你省下一笔可观的漫游费用。然而对运营商来说，这就导致他们损失了一笔收入。这导致他们会设下限制以阻止你在海外使用 Wi-Fi Call。某些运营商也可能因为当地监管原因或其他因素阻止他们的客户在特定地区使用 Wi-Fi Call。

改变

运营商如何得知你的位置？一般而言主要有两种：一是移动设备内置的访问限制，二是你访问运营商 IMS 时的 IP Geo 信息。

移动设备限制

现代的 iOS 和 Android 设备都会有每个运营商提供的网络配置文件，在其中定义了有关运营商和网络连接的各种信息。例如是否允许连接到 4G / 5G 网络、网络接入点（APN）、网络运营商显示名称。而 iOS 的 IPCC（iOS Carrier Profiles）则提供了更多功能，这其中就包括运营商对 Wi-Fi Call 的区域限制。

例如，香港的运营商一般都限制只能在香港内使用 Wi-Fi Call。你可能会想我开一个香港的代理去连接运营商核心网不就行了吗？但实际上你会发现无论你用什么代理都不会连上 Wi-Fi Call。这是因为 iOS 中的 IPCC 限制了只允许设备认为当前处于香港时才会尝试去连接 Wi-Fi Call，而 iOS 的「地区检测」其实与 GPS 无关，而是另外一套判断机制：

对于有蜂窝网络连接的设备，通过基站信息判断位置

对于没有蜂窝网络连接的设备，通过网络信息判断位置

当你有一个已经连接到蜂窝网络的设备时，iOS 很容易会从基站获取到 MNC / MCC，便可以知道你目前所处的地区。而当你使用不具有蜂窝网络功能的设备，或是处于无网络 / 飞行模式等状态时，iOS 就会采用基于网络的地区检测，具体来说，是通过 https://gspe1-ssl.ls.apple.com/pep/gcc 所返回的国家代码来判断。

因此，当我们把设备置于飞行模式，并且让 Apple 的网络地区检测 API 通过我们指定地区的网络代理去访问时，就可以绕过运营商设置在我们设备上 Wi-Fi Call 限制。

那么有没有更直接的方案呢？有，只需要在设置 > 隐私和安全性 > 定位服务 > 系统服务中，关闭「Wi-Fi 通话」（中国大陆销售机型上被称为「WLAN 通话」）的定位权限即可。当然，你仍然需要让手机处于飞行模式以绕过基于蜂窝网络基站的地区检测。

2023-08-25 更新：经过后续实践，此选项并不影响 iOS 的 WiFi Call 检测，推测此功能可能是用于 e-911 的地理位置检测和传输。

GAN 接入限制

由于设备限制大多数情况下只在 iOS 设备有效，部分运营商为了更彻底的限制，还在 GAN 接入时限制了连入的 IP Geo。例如，香港的 ClubSIM 不但在 IPCC 中限制了仅限设备在香港时才能接入，还限制了只有香港 IP 才可以访问。

此外，在中国大陆的读者还需要担心国家网络防火墙（GFW）的问题。2022 年下半年 GFW 曾经屏蔽了部分 T-Mobile 的 Wi-Fi Call 接入点的 IP 地址，导致很多人的 Wi-Fi Call 时断时续甚至根本无法连上。不得不采用各种办法来寻找并尝试连上那些还没有被屏蔽的 IP。

绕过 IP 限制当然很简单，我们只需要网络代理就可以解决。但是在默认情况下，Wi-Fi Call 的 IPSec 流量并不通过本机 VPN（包括传统 VPN 和应用程序创建的 VPN 接口）进行路由。Android 用户可以在 root 后通过 iptables 解决此问题，但 iOS 在 16.4 之前没有办法做到这一点，不过事情在 iOS 16.4 之后发生了变化。iOS 提供了新的 API 以允许将蜂窝 IP 服务的流量通过 Network Extension 路由，而 Surge for iOS 率先支持了这一点。如果读者同时满足 iOS 16.4+ 及拥有 Surge 最新功能订阅的情况下，只需要添加如下 Module 即可实现：

（2023-08-25 更新：目前此功能在新版 Surge 上不做工，退回到最初添加此功能的 Surge 版本也不做工，怀疑 iOS 可能又限制了 NE 对系统底层连接的接管）

Copy
#!name= Enable Wi-Fi Call for Local VPN
#!desc= 允许在本地 VPN 上代理 Wi-Fi Call 流量
#!system=ios

[General]
include-all-networks = true
include-cellular-services = true

如果你希望更精细化地控制 Wi-Fi Call 流量，例如在双卡手机上使用了不同国家的 SIM 卡的情况。那么只需要参考下文的 Surge 分流规则即可。

对于设备本身就在使用网关代理（网关层面的透明代理，包括各种旁路由方案）的读者而言，只需要在网关代理上添加几条规则即可实现。但如果你因为各种原因不愿意设备的流量全部通过网关代理，那么我们就需要一点小小的路由魔法。

策略路由

在本文的最前面提到，Wi-Fi Call 的本质是一个 IPSec 隧道，而 IPSec 隧道使用的是 500 UDP 和 4500 UDP 端口，我们只需要将这两个端口的流量在网关上进行标记，并转发给代理或旁路由就可以了。如果你使用旁路网关代理方案，并且主网关是基于 Linux 的控制平面，那么可以参考本文作者的方案。本文作者使用 UniFi Dream Machine Pro 作为主网关，使用 Surge for Mac 作为旁路代理。但如果你理解了这部分内容，其思路对任何设备都是相通的。

Copy
#!/usr/bin/env bash

# Pre Env
touch /run/routing-zero-wifi-call.lock

if [ -f /run/routing-zero-wifi-call.lock ]; then
iptables -t mangle -D PREROUTING -p udp -m multiport --dports 500,4500 -s 10.1.1.0/24 ! -d 10.1.1.0/24 -j WIFI_CALL
iptables -t mangle -F WIFI_CALL
iptables -t mangle -X WIFI_CALL
ip r flush table wificall
ip ru d fwmark 0x65
sed -i "/wificall/d" /etc/iproute2/rt_tables
fi

# Create Routing Table
echo "101 wificall" >> /etc/iproute2/rt_tables

# Create Routing Rules
ip ru a fwmark 0x65 lookup wificall
ip r a default via 10.1.1.233 dev br0 table wificall

# Set Firewall Mark
iptables -t mangle -N WIFI_CALL
iptables -t mangle -A WIFI_CALL -s 10.1.1.233/32 -j RETURN
iptables -t mangle -A WIFI_CALL -p udp -m multiport --dports 500,4500 -j MARK --set-mark 0x65
iptables -t mangle -A PREROUTING -p udp -m multiport --dports 500,4500 -s 10.1.1.0/24 ! -d 10.1.1.0/24 -j WIFI_CALL

这其中，10.1.1.0/24 是 LAN 网络的 IP 段，10.1.1.233 是 Surge for Mac 所在设备的 IP 地址。我们的思路就是让整个子网下，除旁路代理网关发送的，所有目标端口为 500 和 4500 UDP 端口且目标 IP 不是本地网络的流量，都转发给旁路网关处理。方式是给所有满足上述条件的流量设置防火墙标记（FwMark），并且添加策略路由来让主网关把有这些标记的流量转发给旁路代理网关。

之后我们需要为代理程序添加一些规则，来使得所有 500 UDP 和 4500 UDP 的流量根据其目标 IP Geo 来使用对应国家的代理来转发，以本站作者使用 US / HK / UK 卡的 Wi-Fi Call 为例：

Copy
[Proxy Group]
US Wi-Fi Call = select, US Proxy 1, US Proxy 1
HK Wi-Fi Call = select, HK Proxy 1, HK Proxy 2
UK Wi-Fi Call = select, UK Proxy 1, UK Proxy 2

...

[Rule]
AND,((GEOIP,US),(AND,((PROTOCOL,UDP),(OR,((DEST-PORT,500),(DEST-PORT,4500)))))), US Wi-Fi Call
AND,((GEOIP,HK),(AND,((PROTOCOL,UDP),(OR,((DEST-PORT,500),(DEST-PORT,4500)))))), HK Wi-Fi Call
AND,((GEOIP,GB),(AND,((PROTOCOL,UDP),(OR,((DEST-PORT,500),(DEST-PORT,4500)))))), UK Wi-Fi Call

...

Clash 和其他主流的代理软件都有类似的逻辑判断规则，可以尝试自行修改使用。

IMS 状态

要注意的是，部分运营商，例如香港全部运营商（CSL 系除外，包括 ClubSIM 等子品牌），都只实现了语音接入，而没有接入多媒体系统。这代表当你完全没有接入到蜂窝网络时，你是无法收到短信的！这些运营商非常字面意义地给客户实现了 Wi-Fi「Call」。

在设置 > 通用 > 关于本机中，点击「运营商」就可以切换显示信息，并可以在 Wi-Fi Call 状态下查看 IMS 状态。

以下为 2023.04.14 更新

世事无常，在发布了 iOS 16.4 更新后同时更新了 CMHK 54.0 的 IPCC，该 IPCC 允许 CMHK 在 Wi-Fi Call 状态在收发短信，终于变成了完整体！