Android network — 进程指定网络发包
- 0. 前言
- 1. 进程绑定网络
- 1.1 App进程绑定网络
- 1.2 Native进程绑定网络
- 2. 源码原理分析
- 2.1 申请网络requestNetwork
- 2.2 绑定网络 BindProcessToNetwork
- 3. 总结
0. 前言
在android 中,一个app使用网络,需要在manifest 申请一下
这种方式将使用default网络,比如WIFI 和 数据网络,android 同一个时间点,只能有一个default网络,default网络由Android 网络评分机制选择。
那有没有一种方式可以不使用默认网络呢,比如某一个App只想使用WiFi或者别的某一个网络,而不受默认网络变化的影响,答案是有的
1. 进程绑定网络
1.1 App进程绑定网络
对于App进程,ConnectivityService中提供了bindProcessToNetwork 接口进行绑定,使用说明如下
- 通过 requestNetwork 申请一个网络
- 在NetworkCallback中的onAvailable的方法去调用bindProcessToNetwork 去bind这个网络
- 上两步后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"); } } });
1.2 Native进程绑定网络
对于Native进程,我们可以模仿Framework的底层实现,具体可参考后面2. 原理实现部分
- #include “NetdClient.h” 此文件,此文件在netd的源码中,并 动态链接 libnetd_client.so ,注意一定是动态链接
- 调用 setNetworkForProcess() 传入需要绑定网络的 netid
- 强调一下,一定是动态链接,具体原因在后面原理分析中进行解释
补充说明一下 :同一网络,如某一个wifi或以太网,在断开重连后,netid是变化的,因此,实际使用中,要考虑到异常断开场景后,netid如何固定下来
使用示例:
// Android.bp cc_binary { name: "netd_client_example", srcs: ["main.cpp"], vendor: true, sdk_version: "current", defaults: ["netd_defaults"], shared_libs: [ ...... "libnetd_client" ], ...... } // main.cpp #include
...... result = setNetworkForProcess(netId); ...... 2. 源码原理分析
2.1 申请网络requestNetwork
//frameworks/base/core/java/android/net/ConnectivityManager.java public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback) { }
- NetworkRequest 可以设置 TransportType 比如 TRANSPORT_CELLULAR或者 TRANSPORT_WIFI
- NetworkRequest 可以设置NetworkCapabilities比如NET_CAPABILITY_INTERNET或者其他类型
这个方法可能导致一个新的Network的出现,对应ConnectivityService中就是一个NetworkAgentInfo,这里可以简单的认为一个NetworkAgentInfo代表一个网络通道
NetworkCallback 里面有一些回调,说明一下
回调名称 说明 onAvailable(Network network) 在框架连接并声明新网络可供使用时调用。 onBlockedStatusChanged(Network network, boolean blocked) 在阻止或取消阻止对指定网络的访问时调用。 onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) 当连接到该请求的框架改变功能但仍满足所述需求时调用该网络。 onLinkPropertiesChanged(Network network, LinkProperties linkProperties) 当网络连接到此请求的框架更改 LinkProperties 。 onLosing(Network network, int maxMsToLive) 在网络即将丢失时调用,通常是因为没有未完成留给它的请求。 onLost(Network network) 当网络断开连接或以其他方式不再满足此请求时调用 onUnavailable() 如果在调用中指定的超时时间内未找到网络,或者如果 无法满足请求的网络请求(无论是否超时 指定) 常用回调发生情况:
2.2 绑定网络 BindProcessToNetwork
// packages/modules/Connectivity/framework/src/android/net/ConnectivityManager.java public boolean bindProcessToNetwork(@Nullable Network network) { // Forcing callers to call through non-static function ensures ConnectivityManager // instantiated. return setProcessDefaultNetwork(network); } @Deprecated public static boolean setProcessDefaultNetwork(@Nullable Network network) { int netId = (network == null) ? NETID_UNSET : network.netId; boolean isSameNetId = (netId == NetworkUtils.getBoundNetworkForProcess()); if (netId != NETID_UNSET) { netId = network.getNetIdForResolv(); } if (!NetworkUtils.bindProcessToNetwork(netId)) { return false; } ...... return true; }
我们可以看到实际是调用到NetworkUtils.bindProcessToNetwork
// packages/modules/Connectivity/framework/src/android/net/NetworkUtils.java public static boolean bindProcessToNetwork(int netId) { return bindProcessToNetworkHandle(new Network(netId).getNetworkHandle()); } private static native boolean bindProcessToNetworkHandle(long netHandle);
这里是通过jni调用
//packages/modules/Connectivity/framework/jni/android_net_NetworkUtils.cpp static const JNINativeMethod gNetworkUtilMethods[] = { /* name, signature, funcPtr */ { "bindProcessToNetworkHandle", "(J)Z", (void*) android_net_utils_bindProcessToNetworkHandle }, ....... }; static jboolean android_net_utils_bindProcessToNetworkHandle(JNIEnv *env, jclass clazz, jlong netHandle) { return (jboolean) !android_setprocnetwork(netHandle); }
我们继续跟踪android_setprocnetwork看看
// frameworks/base/native/android/net.c int android_setprocnetwork(net_handle_t network) { unsigned netid; if (!getnetidfromhandle(network, &netid)) { errno = EINVAL; return -1; } int rval = setNetworkForProcess(netid);// libnetd_client.so if (rval < 0) { errno = -rval; rval = -1; } return rval; }
这里我们可以看到bindProcessToNetwork ,这个方法通过jni的方式调用了libnetd_client.so 的setNetworkForProcess
//system/netd/client/NetdClient.cpp extern "C" int setNetworkForProcess(unsigned netId) { return setNetworkForTarget(netId, &netIdForProcess); } int setNetworkForTarget(unsigned netId, std::atomic_uint* target) { const unsigned requestedNetId = netId; netId &= ~NETID_USE_LOCAL_NAMESERVERS; 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. const auto socketFunc = libcSocket ? libcSocket : socket; int socketFd = socketFunc(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (socketFd < 0) { return -errno; } int error = setNetworkForSocket(netId, socketFd); //设置mark标记 if (!error) { *target = requestedNetId; //将netIdForProcess设置为我们选择的netid } close(socketFd); return error; }
这里我们看到setNetworkForProcess 最终将netIdForProcess设置为了我们选择的netid,那为什么调用了setNetworkForProcess ,之后app不管采用何种方式的访问网络,比如okhttp 或者HttpURLConnection的原生方式都能路由到特定的网卡上呢?让我们来看下
其实不管采用方式,本质都使用了socket的,最终都会调用到sys/socket.h的socket(c库)
//bionic/libc/include/sys/socket.h #include
代码使用 int socket(int domain, int type, int protocol) { return FDTRACK_CREATE(__netdClientDispatch.socket(domain, type, protocol)); } __netdClientDispatch.socket 最初会被赋值为__socket(int, int, int);
// bionic/libc/bionic/NetdClientDispatch.cpp extern "C" __socketcall int __socket(int, int, int);
在__libc_preinit_impl 的时候会通过dlsym的方式调用/system/lib/libnetd_client.so中的netdClientSocket(前面说的要动态链接的原因)
// bionic/libc/bionic/libc_init_dynamic.cpp __attribute__((noinline)) static void __libc_preinit_impl() { ...... netdClientInit(); }
// bionic/libc/bionic/NetdClient.cpp static void netdClientInitFunction(void* handle, const char* symbol, FunctionType* function) { typedef void (*InitFunctionType)(FunctionType*); InitFunctionType initFunction = reinterpret_cast
(dlsym(handle, symbol));// dlsym 的方式 if (initFunction != nullptr) { initFunction(function); } } static void netdClientInitImpl() { ...... void* handle = dlopen("libnetd_client.so", RTLD_NOW); // dlopen 打开 libnetd_client.so if (handle == nullptr) { // If the library is not available, it's not an error. We'll just use // default implementations of functions that it would've overridden. return; } netdClientInitFunction(handle, "netdClientInitAccept4", &__netdClientDispatch.accept4); netdClientInitFunction(handle, "netdClientInitConnect", &__netdClientDispatch.connect); netdClientInitFunction(handle, "netdClientInitSendmmsg", &__netdClientDispatch.sendmmsg); netdClientInitFunction(handle, "netdClientInitSendmsg", &__netdClientDispatch.sendmsg); netdClientInitFunction(handle, "netdClientInitSendto", &__netdClientDispatch.sendto); netdClientInitFunction(handle, "netdClientInitSocket", &__netdClientDispatch.socket); // 通过dlsym 动态链接找到netdClientInitSocket netdClientInitFunction(handle, "netdClientInitNetIdForResolv", &__netdClientDispatch.netIdForResolv); netdClientInitFunction(handle, "netdClientInitDnsOpenProxy", &__netdClientDispatch.dnsOpenProxy); } static pthread_once_t netdClientInitOnce = PTHREAD_ONCE_INIT; extern "C" __LIBC_HIDDEN__ void netdClientInit() { if (pthread_once(&netdClientInitOnce, netdClientInitImpl)) { async_safe_format_log(ANDROID_LOG_ERROR, "netdClient", "Failed to initialize libnetd_client"); } netdClientInitSocket 执行后会使得__netdClientDispatch.socket 被赋值为netdClientSocket 而libcSocket赋值为__scoket(系统调用)
// system/netd/client/NetdClient.cpp #define HOOK_ON_FUNC(remoteFunc, nativeFunc, localFunc) \ do { \ if ((remoteFunc) && *(remoteFunc)) { \ (nativeFunc) = *(remoteFunc); \ *(remoteFunc) = (localFunc); \ }\ } while (false) extern "C" void netdClientInitSocket(SocketFunctionType* function) { HOOK_ON_FUNC(function, libcSocket, netdClientSocket); }
Android app 和 native 创建的socket最终会调用到netClientSocket
// system/netd/client/NetdClient.cpp int netdClientSocket(int domain, int type, int protocol) { // Block creating AF_INET/AF_INET6 socket if networking is not allowed. if (FwmarkCommand::isSupportedFamily(domain) && !allowNetworkingForProcess.load()) { errno = EPERM; return -1; } // 系统调用得到一个标准的socket int socketFd = libcSocket(domain, type, protocol); if (socketFd == -1) { return -1; } // 将netdid 设置为我们之前保存的netIdForProcess unsigned netId = netIdForProcess & ~NETID_USE_LOCAL_NAMESERVERS; // **将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的思想的体现!!
这个mark将会匹配到android的策略路由中,走到network对应网卡的路由表中.
例如network 的netId =101
#ip rule 查看策略路由 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 ,65就是101的16进制,就是说设置netid 101 mark的数据包会走到这条策略路由,进而通过rmnet_data网卡发送数据。
3. 总结
再次感叹android的源码真优雅,设计的如此巧妙,修改了linux的c库,通过hook的方式,在app 创建的socket自动打上mark,结合策略路由,实现了数据包的指定发送!!
还没有评论,来说两句吧...