setProcessDefaultNetwork()接口介绍

介绍Android 5.0及以上版本中新增的ConnectivityManager.setProcessDefaultNetwork()接口,该接口允许应用程序指定其网络流量应绑定到哪个网络,如移动数据或Wi-Fi,并深入探讨其实现原理。

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函数设置该socketSO_MARKnetIdandroid有自己的管理逻辑,每个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最终会调用到netdNetdClient.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,最后通过setsocketoptfwmark设置到socket中,这样就为建立的socket设置了mark

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

下面就是看如何使用mark

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



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



0x10064
其中0x64(十进制为100)就是该网络的netid,如果应用选择在这个网络上建立socket,那么通过上面那些步骤建立的socket,就会被带有0x64mark,这样当改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");

}



在 Android 系统中,若需配置 APK 使用特定网络接口(如 eth0、eth1 或 WiFi)进行网络通信,可以通过 `ConnectivityManager` 和 `NetworkSpecifier` 来实现。以下为具体实现方式: ### 3.1 使用 ConnectivityManager 和 NetworkSpecifier 在 Android 中,通过 `ConnectivityManager` 可以请求特定类型的网络,并绑定到当前进程。例如,若需要绑定到以太网接口 eth1,可以使用 `EthernetNetworkSpecifier` 来指定网络接口: ```java public void testBindNetwork() { ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkSpecifier specifier = new EthernetNetworkSpecifier("eth1"); NetworkRequest.Builder req = new NetworkRequest.Builder(); req.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) .setNetworkSpecifier(specifier); connectivityManager.requestNetwork(req.build(), new ConnectivityManager.NetworkCallback() { @Override public void onAvailable(Network network) { try { boolean ret = connectivityManager.bindProcessToNetwork(network); Log.d("dbg@net", "ret = " + ret); mNetwork = network; } catch (Exception e) { e.printStackTrace(); } } }); } ``` 通过 `bindProcessToNetwork(network)`,可以将当前进程绑定到指定网络,确保后续的网络通信都通过该网络接口进行 [^3]。 ### 3.2 选择特定网络类型(如 WiFi 或移动数据) 如果希望绑定到 WiFi 或移动数据网络,可以使用不同的 `NetworkCapabilities.TRANSPORT_*` 类型。例如,绑定到 WiFi 网络: ```java public void selectNetworkType() { ConnectivityManager connectivityManager = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkRequest.Builder mRequest = new NetworkRequest.Builder(); mRequest.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); ConnectivityManager.NetworkCallback callback = new ConnectivityManager.NetworkCallback() { @Override public void onAvailable(Network network) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { connectivityManager.setProcessDefaultNetwork(network); } else { connectivityManager.bindProcessToNetwork(network); } } }; connectivityManager.requestNetwork(mRequest.build(), callback); } ``` 此方法可以确保应用在检测到合适网络时自动切换到该网络,并绑定当前进程 [^4]。 ### 3.3 配置网络策略(如 IP 规则) 在某些嵌入式设备中,可以通过 shell 脚本配置 IP 规则,使得特定目的 IP 的流量走指定网卡。例如: ```sh ip rule add to 192.168.1.78 table eth0 ip rule add to 192.168.1.77 table eth0 ``` 这种方式适用于系统级网络配置,通常在设备启动时通过 init.rc 启动服务进行配置 [^5]。 ### 3.4 安全性与兼容性考虑 在 Android 9(Pie)及以上版本中,默认禁止使用明文流量(HTTP),以提高安全性。如果应用需要使用非加密的 HTTP 请求,可以在 `AndroidManifest.xml` 中设置 `usesCleartextTraffic` 属性: ```xml <application android:usesCleartextTraffic="true" ...> ... </application> ``` 该设置允许应用发送和接收未加密的流量,适用于需要与不支持 HTTPS 的服务器通信的场景 [^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值