Android app 指定网络发送数据包的实现与原理分析

APP 和 native 使用的说明

App 使用默认网络

在android 中 一个app使用网络,需要在manifest 申请一下

<uses-permission android:name="android.permission.INTERNET"/>

这种方式将使用default网络,比如WIFI 和 数据网络,android 同一个时间点,只能有一个default网络(这个默认网络实质是什么或者怎么实现的,后面分析netd的文章会写)

那有没有一种方式可以不使用默认网络呢,比如使用一个专有的网络通道访问专有的服务,答案是有的
APP使用指定网络

  1. 通过 requestNetwork 申请一个网络
  2. 在NetworkCallback中的onAvailable的方法去调用bindProcessToNetwork 去bind这个网络
  3. 上两步后APP的网络流量将会走这个network,或者说走这个network 指定的 网卡
    补充说明一下 :NetworkRequest 在CS对应一个NetworkRequestInfo ,一般情况下一个NetworkRequestInfo对应了一个client进程

一个例子

NetworkRequest request = new NetworkRequest.Builder()
                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                .build();

        mNetworkCallback = new ConnectivityManager.NetworkCallback() {
            @Override
            public void onAvailable(final Network network) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        // requires android.permission.INTERNET
                      if (!mConnectivityManager.bindProcessToNetwork(network)) {
                        } else {
                            Log.d(TAG, "Network available");
                        }
                    }
                });
            }

Native进程使用指定网络

  1. #include “NetdClient.h” 此文件,此文件在netd的源码中,并动态链接libnetd_client.so ,注意一定是动态链接,
  2. 调用 setNetworkForProcess()
  3. 强调一下,一定是动态链接

原理分析

申请网络requestNetwork

//frameworks/base/core/java/android/net/ConnectivityManager.java
public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback) {
}
  1. NetworkRequest 可以设置 TransportType 比如 TRANSPORT_CELLULAR或者 TRANSPORT_WIFI
  2. NetworkRequest 可以设置NetworkCapabilities比如NET_CAPABILITY_INTERNET或者其他类型
    这个方法可能导致一个新的Network的出现,对应ConnectivityService中就是一个NetworkAgentInfo,关于这一点另一篇文章会详细的说一下,这里可以简单的认为一个NetworkAgentInfo代表一个网络通道

NetworkCallback 里面有一些回调,说明一下

在这里插入图片描述

绑定网络 BindProcessToNetwork

public static boolean setProcessDefaultNetwork(Network network) {
    int netId = (network == null) ? NETID_UNSET : network.netId;
    if (netId == NetworkUtils.getBoundNetworkForProcess()) {
        return true;
    }
    if (NetworkUtils.bindProcessToNetwork(netId)) {}
}

BindProcessToNetwork底层原理分析

bindProcessToNetwork ,这个方法通过jni的方式调用了libnetd_client.so
用network中netId作为mark 设置到socket,导致app socket的数据包打上此mark,这个mark将会匹配到android的策略路由中,走到network对应网卡的路由表中.

例如network 的netId =101

adb 查看策略路由

0:      from all lookup local  
9000:   from all lookup main  
10000:  from all fwmark 0xc0000/0xd0000 lookup legacy_system  
10500:  from all oif dummy0 uidrange 0-0 lookup dummy0  
10500:  from all oif rmnet_data1 uidrange 0-0 lookup rmnet_data1  
10500:  from all oif rmnet_data0 uidrange 0-0 lookup rmnet_data0  
10500:  from all oif p2p0 uidrange 0-0 lookup local_network  
13000:  from all fwmark 0x10063/0x1ffff lookup local_network  
13000:  from all fwmark 0x10066/0x1ffff lookup rmnet_data1  
13000:  from all fwmark 0x10065/0x1ffff lookup rmnet_data0 

注意这个 0x10065 ,就是101的16进制,就是说设置netid 101 mark的数据包会走到这条策略路由,进而通过rmnet_data网卡发送数据

问题思考

为什么调用了setNetworkForProcess ,之后app不管采用何种方式的访问网络,比如okhttp 或者HttpURLConnection的原生方式都能路由到特定的网卡上呢

不管采用方式,本质都使用了socket的,最终都会调用到sys/socket.h的socket(c库)

bionic/libc/include/sys/socket.h
#include <sys/socket.h>    代码使用
int socket(int domain, int type, int protocol) {   
  return __netdClientDispatch.socket(domain, type, protocol);   
}

__netdClientDispatch.socket 最初会被赋值为__socket(int, int, int);

extern "C" __socketcall int __socket(int, int, int); 

在__libc_preinit_impl 的时候会通过dlsym的方式调用/system/lib/libnetd_client.so中的netdClientSocket(前面说的要动态链接的原因)

extern "C" void netdClientInitSocket(SocketFunctionType* function) {   
if (function && *function) {      
    libcSocket = *function;        
    *function = netdClientSocket;   
 }}

netdClientInitSocket 执行后会使得 __netdClientDispatch.socket 被赋值为netdClientSocket 而libcSocket赋值为__scoket(系统调用)

Android app 和 native 创建的socket最终会调用到netClientSocket

int netdClientSocket(int domain, int type, int protocol) {
    int socketFd;
#ifndef USE_WRAPPER
  // 系统调用得到一个标准的socket
    socketFd = libcSocket(domain, type, protocol);
#else
    if( __propClientDispatch.propSocket ) {
        socketFd = __propClientDispatch.propSocket(domain, type, protocol);
    } else {
        socketFd = libcSocket(domain, type, protocol);
    }
#endif
    if (socketFd == -1) {
        return -1;
    }
    unsigned netId = netIdForProcess;
     // **将socket 打上 netId的mark**
    if (netId != NETID_UNSET && FwmarkClient::shouldSetFwmark(domain)) {
        if (int error = setNetworkForSocket(netId, socketFd)) {
            return closeFdAndSetErrno(socketFd, error);
        }
    }
    return socketFd;
}

在netdClientSocket创建的socket 会给socket打上netIdForProcess数值的mark,这个netIdForProcess其实就是bindProcessToNetwork 设置的netId,这样导致创建的socket都含有此mark,自然路由到netId对应的网卡了,hook的思想的体现!!

总结

再次感叹android的源码真牛逼,设计的如此巧妙,修改了linux的c库,通过hook的方式,在app 创建的socket自动打上mark,实现了数据包的路由!!

  • 7
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值