0%

安卓10、11 固定Wifi热点IP (Xposed)

更新

安卓12 固定Wifi热点IP (Xposed)

项目地址

SoftApHelper

概述

前文通过分析Android 9源码,定位到Hook点,然后使用Xposed Hook,实现了Wifi热点IP的固定。

系统升级到Android 11后,发现源码有变化,插件不起作用了。于是再来操作一波。

Hook点

安卓10

android.net.ip.IpServergetRandomWifiIPv4Address函数。

IpServer.java#469

1
private String getRandomWifiIPv4Address()

安卓11

android.net.ip.IpServerrequestIpv4Address函数。

IpServer.java#645

1
2
3
4
5
6
7
8
9
private LinkAddress requestIpv4Address(s) {
if (mStaticIpv4ServerAddr != null) return mStaticIpv4ServerAddr;

if (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) {
return new LinkAddress(BLUETOOTH_IFACE_ADDR);
}

return mPrivateAddressCoordinator.requestDownstreamAddress(this);
}

由于该函数还被用于其他方式的网络共享及更换前缀,所以需要判断网络类型(mInterfaceType == TETHERING_WIFI)和调用者(遍历堆栈查找configureIPv4),最后进行替换。

安卓10

源码分析

通过前文可知,设置热点IP的流程如下:
configureIPv4函数中,首先调用getRandomWifiIPv4Address生成字符串形式的IP,然后转换为LinkAddress,最后调用ifcg.setLinkAddress设置IP地址。(其中ifcg类型为InterfaceConfiguration

在源码中搜索setLinkAddress,可以发现有以下几处引用

其中/frameworks/base/services/net/java/android/net/ip/IpServer.java调用者为configureIPv4函数(官方文档:IP 服务重构路径

查看源码IpServer.java#404,发现并无太大变化(增加了configureDhcp已更新 DHCP 服务器),仍使用getRandomWifiIPv4Address生成随机IP地址:

1
2
3
InetAddress addr = NetworkUtils.numericToInetAddress(ipAsString);
linkAddr = new LinkAddress(addr, prefixLen);
ifcg.setLinkAddress(linkAddr);

查找getRandomWifiIPv4Address的交叉引用,发现还是只有这一处。因此,修改Hook类名为android.net.ip.IpServer即可适配安卓10

Hook点

android.net.ip.IpServergetRandomWifiIPv4Address函数,返回固定字符串即可。

1
private String getRandomWifiIPv4Address()

安卓11

源码分析

搜索setLinkAddress,仍然只有几处引用

相比于安卓10,IpServer.javaWifiP2pServiceImpl.java的引用消失了,变成了NetdWrapper.javafromStableParcelsetInterfaceLinkAddress函数(官方文档:模块边界

查找fromStableParcel的交叉引用,发现NetdWrapper.javaNetworkManagementService.javagetInterfaceConfig函数有调用。
查看源码,发现只有一行代码区别

1
NetworkStack.checkNetworkStackPermissionOr(mContext, CONNECTIVITY_INTERNAL);

两者都是调用mNetdService.interfaceGetCfg(iface)获取配置。

查找setInterfaceLinkAddress的交叉引用,发现并没有函数调用它

于是搜索configureIPv4,发现只有一处引用,仍位于android.net.ip.IpServer类中
(由/frameworks/base/services/net/java/android/net/ip/IpServer.java移动到了/frameworks/base/packages/Tethering/src/android/net/ip/IpServer.java

分析该函数(源代码IpServer.java#596),发现有:

1
2
3
4
if (enabled) {
mLinkProperties.addLinkAddress(mIpv4Address);
mLinkProperties.addRoute(getDirectConnectedRoute(mIpv4Address));
}

mIpv4Address是在configureIPv4函数开头设置的:

1
2
3
if (enabled) {
mIpv4Address = requestIpv4Address();
}

查看requestIpv4Address函数:

1
2
3
4
5
6
7
8
9
private LinkAddress requestIpv4Address() {
if (mStaticIpv4ServerAddr != null) return mStaticIpv4ServerAddr;

if (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) {
return new LinkAddress(BLUETOOTH_IFACE_ADDR);
}

return mPrivateAddressCoordinator.requestDownstreamAddress(this);
}

首先判断是否设置了静态IP,如果mStaticIpv4ServerAddr不为空则直接使用(由maybeConfigureStaticIp设置)。
然后判断是否为蓝牙,是则使用BLUETOOTH_IFACE_ADDR(192.168.44.1/24)。
其他情况调用mPrivateAddressCoordinator.requestDownstreamAddress(this)

后者位于frameworks/base/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public LinkAddress requestDownstreamAddress(final IpServer ipServer) {
maybeRemoveDeprectedUpstreams();

// Address would be 192.168.[subAddress]/24.
final byte[] bytes = mTetheringPrefix.getRawAddress();
final int subAddress = getRandomSubAddr();
final int subNet = (subAddress >> 8) & BYTE_MASK;
bytes[3] = getSanitizedAddressSuffix(subAddress, (byte) 0, (byte) 1, (byte) 0xff);
for (int i = 0; i < MAX_UBYTE; i++) {
final int newSubNet = (subNet + i) & BYTE_MASK;
if (newSubNet == BLUETOOTH_RESERVED) continue;

bytes[2] = (byte) newSubNet;
final InetAddress addr;
try {
addr = InetAddress.getByAddress(bytes);
} catch (UnknownHostException e) {
throw new IllegalStateException("Invalid address, shouldn't happen.", e);
}

final IpPrefix prefix = new IpPrefix(addr, PREFIX_LENGTH);
// Check whether this prefix is in use.
if (isDownstreamPrefixInUse(prefix)) continue;
// Check whether this prefix is conflict with any current upstream network.
if (isConflictWithUpstream(prefix)) continue;

mDownstreams.add(ipServer);
return new LinkAddress(addr, PREFIX_LENGTH);
}

// No available address.
return null;
}

/** Get random sub address value. Return value is in 0 ~ 0xffff. */
@VisibleForTesting
public int getRandomSubAddr() {
return ((new Random()).nextInt()) & 0xffff; // subNet is in 0 ~ 0xffff.
}

调用getRandomSubAddr生成子网地址,然后判断是否存在与上下游冲突。

查找requestIpv4Address交叉引用,发现还会被handleNewPrefixRequest调用,向上追溯找到onNewPrefixRequest(DHCP服务器通知前缀改变时触发)

1
2
3
4
5
6
7
8
// request from DHCP server that it wants to have a new prefix
public static final int CMD_NEW_PREFIX_REQUEST = BASE_IPSERVER + 12;

@Override
public void onNewPrefixRequest(@NonNull final IpPrefix currentPrefix) {
Objects.requireNonNull(currentPrefix);
sendMessage(CMD_NEW_PREFIX_REQUEST, currentPrefix);
}

Hook点

android.net.ip.IpServerrequestIpv4Address函数。

1
private LinkAddress requestIpv4Address()

由于该函数还被用于其他方式的网络共享及更换前缀,所以需要判断网络类型(mInterfaceType == TETHERING_WIFI)和调用者(遍历堆栈查找configureIPv4),最后进行替换。

PS:测试发现Android 11设置IP的包名不再是android,而是com.android.networkstack.tethering.inprocess

Hook代码

见GitHub:MainHook.java

参考

How can I permanently assign a static IP address to Wi-Fi clients? (Lineage OS 17.1 Android 10)