Android network — 进程指定网络发包

Android network --- 进程指定网络发包

  • [0. 前言](#0. 前言)
  • [1. 进程绑定网络](#1. 进程绑定网络)
    • [1.1 App进程绑定网络](#1.1 App进程绑定网络)
    • [1.2 Native进程绑定网络](#1.2 Native进程绑定网络)
  • [2. 源码原理分析](#2. 源码原理分析)
    • [2.1 申请网络requestNetwork](#2.1 申请网络requestNetwork)
    • [2.2 绑定网络 BindProcessToNetwork](#2.2 绑定网络 BindProcessToNetwork)
  • [3. 总结](#3. 总结)

0. 前言

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

xml 复制代码
<uses-permission android:name="android.permission.INTERNET"/>

这种方式将使用default网络,比如WIFI 和 数据网络,android 同一个时间点,只能有一个default网络,default网络由Android 网络评分机制选择。

那有没有一种方式可以不使用默认网络呢,比如某一个App只想使用WiFi或者别的某一个网络,而不受默认网络变化的影响,答案是有的

1. 进程绑定网络

1.1 App进程绑定网络

对于App进程,ConnectivityService中提供了bindProcessToNetwork 接口进行绑定,使用说明如下

  1. 通过 requestNetwork 申请一个网络
  2. 在NetworkCallback中的onAvailable的方法去调用bindProcessToNetwork 去bind这个网络
  3. 上两步后APP的网络流量将会走这个network,或者说走这个network 指定的 网卡

补充说明一下 :NetworkRequest 在CS对应一个NetworkRequestInfo ,一般情况下一个NetworkRequestInfo对应了一个client进程

使用示例:

java 复制代码
		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. 原理实现部分

  1. #include "NetdClient.h" 此文件,此文件在netd的源码中,并 动态链接 libnetd_client.so ,注意一定是动态链接
  2. 调用 setNetworkForProcess() 传入需要绑定网络的 netid
  3. 强调一下,一定是动态链接,具体原因在后面原理分析中进行解释

补充说明一下 :同一网络,如某一个wifi或以太网,在断开重连后,netid是变化的,因此,实际使用中,要考虑到异常断开场景后,netid如何固定下来

使用示例:

cpp 复制代码
// 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 <NetdClient.h>

......
result = setNetworkForProcess(netId);

......

2. 源码原理分析

2.1 申请网络requestNetwork

java 复制代码
//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

java 复制代码
// 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

java 复制代码
	// 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调用

cpp 复制代码
	//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看看

c 复制代码
// 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

cpp 复制代码
//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库)

cpp 复制代码
//bionic/libc/include/sys/socket.h
#include <sys/socket.h>    代码使用

int socket(int domain, int type, int protocol) {
  return FDTRACK_CREATE(__netdClientDispatch.socket(domain, type, protocol));
}

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

cpp 复制代码
// bionic/libc/bionic/NetdClientDispatch.cpp
extern "C" __socketcall int __socket(int, int, int); 

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

cpp 复制代码
// bionic/libc/bionic/libc_init_dynamic.cpp
__attribute__((noinline))
static void __libc_preinit_impl() {
  ......
  netdClientInit();
}
cpp 复制代码
// bionic/libc/bionic/NetdClient.cpp
static void netdClientInitFunction(void* handle, const char* symbol, FunctionType* function) {
    typedef void (*InitFunctionType)(FunctionType*);
    InitFunctionType initFunction = reinterpret_cast<InitFunctionType>(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(系统调用)**

cpp 复制代码
// 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

cpp 复制代码
// 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

powershell 复制代码
#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,结合策略路由,实现了数据包的指定发送!!

相关推荐
jwn99918 小时前
PHP vs 汇编:编程语言的两极对决
开发语言·汇编·php
hzhsec18 小时前
密码爆破日志分析与隧道流量检查
linux·网络·windows·网络安全·应急响应
源远流长jerry18 小时前
负载均衡概述
linux·运维·网络·架构·负载均衡·lvs
huwuhang18 小时前
植物大战僵尸版本所有版本合集下载含杂交版 融合版 火影版 二战版 无双版 抽卡版 β版等等
android·游戏·电脑·游戏机
芯智工坊19 小时前
第6章 Mosquitto用户认证与访问控制
网络·人工智能·mqtt·开源
尤老师FPGA1 天前
petalinux修改设备树添加vdma生成linux系统
android·linux·运维
月山知了1 天前
linux kernel component子系统:基于rk3588 Android 14 kernel-6.1 display-subsystem代码分析
android·linux·运维
SEO-狼术1 天前
Support Network Diagnostics in .NET
运维·服务器·网络
不才小强1 天前
Linux系统常用命令
linux·运维·网络
桌面运维家1 天前
VHD/VHDX数据安全:块位图损坏与NTFS恢复
网络