setProcessDefaultNetwork()接口介绍

android支持多种网络类型,例如WIFI,移动数据等。目前android的实现是,WIFI和移动数据只能同时存在一个(优先级),例如当WIFI连接后,数据通路就从移动数据切换到WIFI。对上层app而言,这时候数据通路也就从移动数据切换到WIFI上。

考虑一个特殊的需求,某app只能通过移动数据接口去传输数据或则当移动数据同时存在几个链路承载时,指定某一个特定的链路去传输数据,是否可以实现?android5.0及以后的版本添加了接口ConnectivityManager.setProcessDefaultNetwork()来支持这一功能.


1.该接口的详细描述如下:

/**
*Binds the current process to {@code network}. All Sockets created inthe future
*(and not explicitly bound via a bound SocketFactory from
*{@link Network#getSocketFactory() Network.getSocketFactory()}) willbe bound to
*{@code network}. All host name resolutions will be limited to {@codenetwork} as well.
*Note that if {@code network} ever disconnects, all Sockets created inthis way will cease to
*work and all host name resolutions will fail. This is by design soan application doesn't
*accidentally use Sockets it thinks are still bound to a particular{@link Network}.
*To clear binding pass {@code null} for {@code network}. Usingindividually bound
*Sockets created by Network.getSocketFactory().createSocket() and
*performing network-specific host name resolutions via
*{@link Network#getAllByName Network.getAllByName} is preferred tocalling
*{@code setProcessDefaultNetwork}.
*
*@param network The {@link Network} to bind the current process to, or{@code null} to clear
* the current binding.
*@return {@code true} on success, {@code false} if the {@link Network}is no longer valid.
*/
publicstatic boolean setProcessDefaultNetwork(Network network) {

}

2.该函数的实现原理大致为

a.进程在创建socket时(app首先调用setProcessDefaultNetwork()),android底层会利用setsockopt函数设置该socket的SO_MARK为netId(android有自己的管理逻辑,每个Network有对应的ID),以后利用该socket发送的数据都会被打上netId的标记(fwmark值)。

b.利用策略路由,将打着netId标记的数据包都路由到当前链路的网络端口,下面是策略路由表的一个简单例子。

1|root@le_s2_na:/# ip rule list
0: fromall lookup local 
10000: fromall fwmark 0xc0000/0xd0000 lookup legacy_system 
10500: fromall oif dummy0 uidrange 0-0 lookup dummy0 
10500: fromall oif rmnet_data0 uidrange 0-0 lookup rmnet_data0 
13000: fromall fwmark 0x10063/0x1ffff lookup local_network 
13000: fromall fwmark 0x1006a/0x1ffff lookup rmnet_data0 
14000: fromall oif dummy0 lookup dummy0 
14000: fromall oif rmnet_data0 lookup rmnet_data0 
15000: fromall fwmark 0x0/0x10000 lookup legacy_system 
16000: fromall fwmark 0x0/0x10000 lookup legacy_network 
17000: fromall fwmark 0x0/0x10000 lookup local_network 
19000: fromall fwmark 0x6a/0x1ffff lookup rmnet_data0 
22000: fromall fwmark 0x0/0xffff lookup rmnet_data0 
23000: fromall fwmark 0x0/0xffff uidrange 0-0 lookup main 
32000: fromall unreachable
root@le_s2_na:/#


setProcessDefaultNetwork()的实现

publicstatic boolean setProcessDefaultNetwork(Network network) {
intnetId = (network == null) ? NETID_UNSET : network.netId;
if(netId == NetworkUtils.getNetworkBoundToProcess()) {
returntrue;
}
//调用bindProcessToNetwork()
if(NetworkUtils.bindProcessToNetwork(netId)) {
returntrue;
}else {
returnfalse;
}
}
setProcessDefaultNetwork最终会调用到netd的NetdClient.cpp
extern "C" int setNetworkForProcess(unsigned netId) {
return setNetworkForTarget(netId, &netIdForProcess);  
}

下面函数主要作用是将当前链路的netid赋值给netIdForProcess,当应用建立socket的时候会用到该值

int setNetworkForTarget(unsigned netId, std::atomic_uint* target) {  
if (netId == NETID_UNSET) {  
*target = netId;  
return 0;  
}  
// Verify that we are allowed to use |netId|, by creating a socket and trying to have it marked  
// with the netId. Call libcSocket() directly; else the socket creation (via netdClientSocket())  
// might itself cause another check with the fwmark server, which would be wasteful.  
int socketFd;  
if (libcSocket) {  
socketFd = libcSocket(AF_INET6, SOCK_DGRAM, 0);  
} else {  
socketFd = socket(AF_INET6, SOCK_DGRAM, 0);  
}  
if (socketFd < 0) {  
return -errno;  
}  
int error = setNetworkForSocket(netId, socketFd);  
if (!error) {  
*target = netId;  
}  
close(socketFd);  
return error;  
}

Socket中连接中使用netIdForProcess

当应用建立socket的时候会调用到下面这个api

NetdClient.cpp
int netdClientSocket(int domain, int type, int protocol) {  
int socketFd = libcSocket(domain, type, protocol);  
if (socketFd == -1) {             
return -1;  
}  
unsigned netId = netIdForProcess;  
if (netId != NETID_UNSET && FwmarkClient::shouldSetFwmark(domain)) {   
if (int error = setNetworkForSocket(netId, socketFd)) {  
return closeFdAndSetErrno(socketFd, error);  
}  
}  
return socketFd;                                                                                                                                                      
}

这里netIdForProcess就是前面设置下来的,因为它的值不再是NETID_UNSET,导致条件满足,调用到setNetworkForSocket


extern "C" int setNetworkForSocket(unsigned netId, int socketFd) {  
if (socketFd < 0) {  
return -EBADF;  
}  
FwmarkCommand command = {FwmarkCommand::SELECT_NETWORK, netId, 0};  
return FwmarkClient().send(&command, sizeof(command), socketFd);  
}  
这个function最终将netId通过本地socket发送出去,结果由服务FwmakrServer接收并处理
case FwmarkCommand::SELECT_NETWORK: {                                                                                                                
fwmark.netId = command.netId;  
if (command.netId == NETID_UNSET) {  
fwmark.explicitlySelected = false;  
fwmark.protectedFromVpn = false;  
permission = PERMISSION_NONE;  
} else {  
if (int ret = mNetworkController->checkUserNetworkAccess(client->getUid(),  command.netId)) {  
return ret;  
}
fwmark.explicitlySelected = true;  
fwmark.protectedFromVpn = mNetworkController->canProtect(client->getUid());  
}  
break;  
}  
fwmark.permission = permission;                                                                                                                                       
if (setsockopt(*socketFd, SOL_SOCKET, SO_MARK, &fwmark.intValue,  
sizeof(fwmark.intValue)) == -1) {  
return -errno;  
}

上面的处理中,首先会将netid赋值给fwmark,最后通过setsocketopt将fwmark设置到socket中,这样就为建立的socket设置了mark。

上面的处理中,首先会将netid赋值给fwmark,最后通过setsocketopt将fwmark设置到socket中,这样就为建立的socket设置了mark从该socket发出的数据包都被netid的值标记。

下面就是看如何使用mark。

下图列举了某一时刻一款android系统中的所有的路由规则

以下面这条路由规则为例:

0x10064其中0x64(十进制为100)就是该网络的netid,如果应用选择在这个网络上建立socket,那么通过上面那些步骤建立的socket,就会被带有0x64的mark,这样当改socket输出数据时候,就会被上面的路由规则捕获,从而使用eth0这个路由表。

下面是eth0路由表的内容

什么是netID

netID一个链路承载的标示,取值范围在<100,65535>之间,当请求建立链路承载时会调用下面函数选取一个合适的值
ConnectivityService.Java

@VisibleForTesting
protectedint reserveNetId() {
synchronized(mNetworkForNetId) {
for(int i = MIN_NET_ID; i <= MAX_NET_ID; i++) {
intnetId = mNextNetId;
if(++mNextNetId > MAX_NET_ID) mNextNetId = MIN_NET_ID;
//Make sure NetID unused. http://b/16815182
if(!mNetIdInUse.get(netId)) {
mNetIdInUse.put(netId,true);
returnnetId;
}
}
}
thrownew IllegalStateException("No free netIds");
}