Android network — 进程指定网络发包

Android network — 进程指定网络发包

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

0. 前言

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

<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进程

使用示例:

		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如何固定下来

使用示例:

// 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

//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 <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);

// 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<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(系统调用)

// 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,结合策略路由,实现了数据包的指定发送!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/647289.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从零开始搭建Springboot项目脚手架4:保存操作日志

目的&#xff1a;通过AOP切面&#xff0c;统一记录接口的访问日志 1、加maven依赖 2、 增加日志类RequestLog 3、 配置AOP切面&#xff0c;把请求前的request、返回的response一起记录 package com.template.common.config;import cn.hutool.core.util.ArrayUtil; import cn.hu…

【C/C++】Makefile文件的介绍与基本用法

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

烟囱ERP系统

一、烟囱系统定义 “烟囱式”系统&#xff0c;来自维基百科的解释是&#xff1a;一种不能与其他系统进行有效协调工作的信息系统&#xff0c;又称为孤岛系统。 二、烟囱系统的案例 比如&#xff1a;就像以下一样&#xff0c;各个系统之间是独立的&#xff0c;所有对接是通过…

揭秘Markdown:轻松掌握基础语法,让你的写作更高效、优雅!

文章目录 前言1.标题1.1 使用 和 - 标记一级和二级标题1.2 使用 # 号标记 2.段落格式2.1 字体2.2 分割线2.3 删除线2.4 下划线2.5 脚注 3.列表3.1 无序列表3.2 有序列表3.3 列表嵌套 4.区块4.1 区块中使用列表4.2 列表中使用区块 5.代码代码区块 6.链接7.图片8.表格9.高级技巧…

访问元组元素

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 在Python中&#xff0c;如果想将元组的内容输出也比较简单&#xff0c;可以直接使用print()函数即可。例如&#xff0c;要想打印上面元组中的untitle…

嵌入式进阶——震动马达

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 原理图控制分析功能设计 原理图 控制分析 S8050 NPN三极管特性 NPN型三极管的工作原理是基于PN结和PNP型晶体管的工作原理。 当外…

【大数据面试题】32 Flink 怎么重复读 Kafka?

一步一个脚印&#xff0c;一天一道面试题 首先&#xff0c;为什么要读过的 Kafka 数据重写读一次呢&#xff1f;什么场景下要怎么做呢&#xff1f; 答&#xff1a;当任务失败&#xff0c;从检查点Checkpoint 开始重启时&#xff0c;检查点的数据是之前的了&#xff0c;就需要…

扩散模型学习1

DDPM 总体训练原理 https://www.bilibili.com/video/BV1nB4y1h7CN/?spm_id_from333.337.search-card.all.click&vd_sourcef745c116402814185ab0e8636c993d8f 讲得很好&#xff1a;每次都是输入t和noise-x的图像&#xff0c;预测noise之后得到和加入的noise比较&#xff1b…

【Spring】深入理解 Spring 上下文(Context)层次结构

前言 在使用 Spring 框架进行应用程序开发时&#xff0c;Spring 上下文&#xff08;Context&#xff09;是一个非常重要的概念。Spring 上下文提供了一个环境&#xff0c;用于管理应用程序中的对象&#xff08;通常称为 Bean&#xff09;及其之间的依赖关系。在复杂的应用程序…

Virtual Box安装Ubuntu及设置

Virtual Box安装Ubuntu及设置 本文包含以下内容&#xff1a; 使用Virtual Box安装Ubuntu Desktop。设置虚拟机中的Ubuntu&#xff0c;使之可访问互联网并可通过SSH访问。 Ubuntu Desktop下载 从官网下载&#xff0c;地址为&#xff1a;Download Ubuntu Desktop | Ubuntu U…

HTML.

HTML:超文本标记语言&#xff08;Hyper Text Markup Language&#xff09; 超文本&#xff1a;不同于普通文本&#xff0c;可以定义图片&#xff0c;音频&#xff0c;视频等内容 标记语言&#xff1a;由标签构成的语言 HTML标签都是预定义好的HTML代码直接在浏览器中运行&#…

B站大数据分享视频创作300天100+原创内容4000+粉

以今年五一作为一个里程碑参考点&#xff0c;给明年一个可以比较的数据。 我正经发力创作是2023.06.17 (前面几个视频是试水)&#xff0c;300天不到一年时间 创作了100原创数据相关视频&#xff0c;创作频率应该很高了&#xff0c;收获了下面几个数字&#xff0c;审视自身&…

Cadence OrCAD学习笔记(3)capture使用技巧_1

本期介绍capture的一些使用技巧。资料来源于小破站up主硬小二 1、导出像Visio规格的图纸 2、全局修改元件属性 然后保存、关闭即可。 3、导出BOM 4、导出网表 5、元件自动编号 6、capture软件和allegro关联 7、新建原理图symbol 以上为添加封装库的路径 如果要创建多部分的sy…

Pandas 多层索引中的索引和切片操作你学会了吗

1. Series的索引操作 对于Series来说&#xff0c;直接中括号[]与使用 .loc() 完全一样 显式索引 # 导包import numpy as npimport pandas as pd data np.random.randint(0,100,size6) index [ ["1班","1班","1班","2班","…

MySQL事务篇1:事物的四大特性(ACID)、三类数据读取问题与隔离级别

一、什么是事务&#xff1f; MySQL的事务&#xff08;Transaction&#xff09;是一组由数据库管理系统&#xff08;DBMS&#xff09;执行的一个或多个SQL语句的集合&#xff0c;这些SQL语句作为一个单独的工作单元执行。事务的主要目的是确保数据库的一致性和完整性&#xff0c…

Qt 界面上字体自适应控件大小 - 随控件缩放

Qt 界面上字体自适应控件大小 - 随控件缩放 引言一、设计思路二、进阶版大致思路三、参考链接 引言 Qt控件自适应字体大小可以用adjustSize()函数&#xff0c;但字体自适应控件大小并没有现成的函数可调. - 本文实现了按钮上的字体随按钮大小变化而变化 (如上图所示) - 其他控件…

webgl入门-绘制三角形

绘制三角形 前言 三角形是一个最简单、最稳定的面&#xff0c;webgl 中的三维模型都是由三角面组成的。咱们这一篇就说一下三角形的绘制方法。 课堂目标 理解多点绘图原理。可以绘制三角形&#xff0c;并将其组合成多边形。 知识点 缓冲区对象点、线、面图形 第一章 web…

Denoising Diffusion Probabilistic Models 全过程概述 + 论文总结

标题&#xff1a;Denoising&#xff08;&#x1f31f;去噪&#xff09;Diffusion Probabilistic Models&#xff08;扩散概率模型&#xff09; 论文&#xff08;NeurIPS会议 CCF A 类&#xff09;&#xff1a;Denoising Diffusion Probabilistic Models 源码&#xff1a;hojona…

通过unsplash引入图片素材

如果您还没听说过——当您需要无版权费的照片用于项目时&#xff0c;无论是否用于商业目的&#xff0c;Unsplash 都是您的不二之选。我自己也经常用它来获取大型背景图像。 虽然他们为开发者提供了出色的 API&#xff0c;但他们还为您提供了通过 URL 直接访问随机图片的选项。…

《python编程从入门到实践》day39加更

# 昨日知识点回顾 添加主题、条目 # 今日知识点学习 19.1.3 编辑条目 1.URL模式edit——entry # learning_logs/urls.py ---snip---# 用于编辑条目的页面path(edit_entry/<int:entry_id>/, views.edit_entry, nameedit_entry), ] 2.视图函数edit_entry() # views.py fr…