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() {@Overridepublic void onAvailable(final Network network) {runOnUiThread(new Runnable() {@Overridepublic void run() {// requires android.permission.INTERNETif (!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.javapublic boolean bindProcessToNetwork(@Nullable Network network) {// Forcing callers to call through non-static function ensures ConnectivityManager// instantiated.return setProcessDefaultNetwork(network);}@Deprecatedpublic 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.javapublic 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.cppstatic 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.soif (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.soif (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 动态链接找到netdClientInitSocketnetdClientInitFunction(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;}// 系统调用得到一个标准的socketint socketFd = libcSocket(domain, type, protocol);if (socketFd == -1) {return -1;}// 将netdid 设置为我们之前保存的netIdForProcessunsigned 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,结合策略路由,实现了数据包的指定发送!!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

el-table 实现嵌套表格的思路及完整功能代码

要实现的需求是这样的&#xff1a; 本来我是用 el-table 的 :span-method 方法实现的&#xff0c;但发现合并起来有问题&#xff0c;跟我的需求差距有些大&#xff0c;于是我想到了嵌套表格。但是嵌套完之后的样子也是很奇怪&#xff1a; 不要气馁&#xff0c;思路还是对的&a…

InTouch历史报警、历史事件按时段查询,导出

简介&#xff1a;本插件基于上位机组态InTouch的历史报警、操作记录而开发 适用InTouch版本&#xff1a;不限 适用Windows系统&#xff1a;不限 适用数据库&#xff1a;SQL Server 标记名点数&#xff1a;不限 配套软件安装&#xff1a;Excel、WPS、SQL Server 功能&…

创新力作 焕新首发丨捷顺科技·捷曜系列智慧停车新品全新上市

2024捷顺科技智慧停车全家族新品全面上市 全新外观、全新特性、全新体验 新控制机、新道闸、新超眸相机... 每款新品都有哪些功能亮点 带您一探究竟

linux mail命令及其历史

一、【问题描述】 最近隔壁组有人把crontab删了&#xff0c;crontab这个命令有点反人类&#xff0c;它的参数特别容易误操作&#xff1a; crontab - 是删除计划表 crontab -e 是编辑&#xff0c;总之就是特别容易输入错误。 好在可以通过mail命令找回&#xff0c;但是mai…

2.冒泡排序

样例输入 5 8 3 6 4 9 样例输出 3 4 6 8 9 以下是解题答案&#xff1a; class demo1{public static void main(String[] args) {Scanner scnnew Scanner(System.in);int[] array new int[scn.nextInt()];if(array.length>0&&array.length<200){for(int…

书生·浦语大模型全链路开源体系-作业1

视频链接&#xff1a;书生浦语大模型全链路开源体系_哔哩哔哩_bilibili 1. LLM发展 LLM是近年来人工智能领域的一个重要发展方向。大型语言模型的历史可以追溯到2017年,当时OpenAI推出了GPT-1(Generative Pre-trained Transformer)模型,这是一个基于Transformer架构的语言生成…

【前端三剑客之HTML】详解HTML

1. HTML(超文本标记语言) HTML意为超文本标记语言&#xff0c;其可以通过标签把其他网页/图片/视频等资源引入到当前网页中&#xff0c;让网页最终呈现出来的效果超越了文本.HTML是一种标记语言&#xff0c;其是由一系列标签组成的. 而且每个标签都有特定的含义和确定的页面显…

LeetCode/NowCoder-链表经典算法OJ练习3

孜孜不倦&#xff1a;孜孜&#xff1a;勤勉&#xff0c;不懈怠。指工作或学习勤奋不知疲倦。&#x1f493;&#x1f493;&#x1f493; 目录 说在前面 题目一&#xff1a;返回倒数第k个节点 题目二&#xff1a;链表的回文结构 题目三&#xff1a;相交链表 SUMUP结尾 说在前…

K-means聚类算法详细介绍

目录 &#x1f349;简介 &#x1f348;K-means聚类模型详解 &#x1f348;K-means聚类的基本原理 &#x1f348;K-means聚类的算法步骤 &#x1f348;K-means聚类的优缺点 &#x1f34d;优点 &#x1f34d;缺点 &#x1f348;K-means聚类的应用场景 &#x1f348;K-mea…

全局查询筛选器适用场景 以及各场景示例

EF Core中的全局查询筛选器&#xff08;Global Query Filters&#xff09;是一种强大的功能&#xff0c;可以在实体框架的DbContext级别为特定的EntityType设置默认的过滤条件。这些筛选器自动应用于所有涉及到相关实体的LINQ查询中&#xff0c;无论是直接查询还是通过Include或…

借助 CloudFlare 增强站点内容保护防采集

今天在一位站长的帮助下实测了 CloudFlare 增强站点内容保护实现防采集的功能,效果那是杠杠的,如果您的站点原创内容比较多的话,明月强烈建议试试 CloudFlare 这个内容保护,无论是 WordPress 、Typecho 都有非常好的效果,并且几乎没有任何误伤,搜索引擎爬虫蜘蛛更是不会影…

利用边缘计算网关的工业设备数据采集方案探讨-天拓四方

随着工业4.0时代的到来&#xff0c;工业设备数据采集成为了实现智能制造、提升生产效率的关键环节。传统的数据采集方案往往依赖于中心化的数据处理方式&#xff0c;但这种方式在面对海量数据、实时性要求高的工业场景时&#xff0c;往往显得力不从心。因此&#xff0c;利用边缘…

内存泄漏案例分享3-view的内存泄漏

案例3——view内存泄漏 前文提到&#xff0c;profile#Leaks视图无法展示非Activity、非Fragment的内存泄漏&#xff0c;换言之&#xff0c;除了Activity、Fragment的内存泄漏外&#xff0c;其他类的内存问题我们只能自己检索hprof文件查询了。 下面有一个极佳的view内存泄漏例子…

数据结构——不相交集(并查集)

一、基本概念 关系&#xff1a;定义在集合S上的关系指对于a&#xff0c;b∈S&#xff0c;若aRb为真&#xff0c;则a与b相关 等价关系&#xff1a;满足以下三个特性的关系R称为等价关系 (1)对称性&#xff0c;aRb为真则bRa为真&#xff1b; (2)反身性,aRa为真; (3)传递性,aRb为真…

【程序员如何送外卖】

嘿&#xff0c;咱程序员要在美团送外卖&#xff0c;那还真有一番说道呢。 先说说优势哈&#xff0c;咱程序员那逻辑思维可不是盖的&#xff0c;规划送餐路线什么的&#xff0c;简直小菜一碟。就像敲代码找最优解一样&#xff0c;能迅速算出怎么送最省时间最有效率。而且咱平时…

“技术与管理并重:构建以等保测评为导向的全方位防御体系“

在数字化转型浪潮下&#xff0c;企业信息安全面临着前所未有的挑战。为了有效应对日益复杂的网络威胁&#xff0c;构建一个稳固的信息安全防线&#xff0c;技术手段与管理制度的有机结合显得尤为重要。本文将探讨如何以信息安全等级保护测评&#xff08;等保测评&#xff09;为…

【HUST】信道编码|基于LDPC码的物理层安全编码方案概述

本文对方案的总结是靠 Kimi 阅读相关论文后生成的&#xff0c;我只看了标题和摘要感觉确实是这么回事&#xff0c;并没有阅读原文。 行文逻辑&#xff1a;是我自己设定的&#xff0c;但我并不是这个研究领域的&#xff0c;所以如果章节划分时有问题&#xff0c;期待指出&#x…

音乐编曲软件哪个好用 studio one和fl studio哪个好

编曲软件的出现&#xff0c;打破了时间与空间的限制&#xff0c;使得创作者能随时随地进行音乐创作。随着信息时代的发展&#xff0c;使用编曲软件进行音乐创作已经成为业界主流。业内常用的有Cubsae、LogicPro、Studio One、Ableton live等&#xff0c;这次教程我将为大家解读…

云计算期末复习(1)

云计算基础 作业&#xff08;问答题&#xff09; &#xff08;1&#xff09;总结云计算的特点。 透明的云端计算服务 “无限”多的计算资源&#xff0c;提供强大的计算能力 按需分配&#xff0c;弹性伸缩&#xff0c;取用方便&#xff0c;成本低廉资源共享&#xff0c;降低企…

【全开源】填表统计预约打卡表单系统FastAdmin+ThinkPHP+UniApp

简化流程&#xff0c;提升效率 一、引言&#xff1a;传统表单处理的局限性 在日常工作和生活中&#xff0c;我们经常会遇到需要填写表单、统计数据和预约打卡等场景。然而&#xff0c;传统的处理方式往往效率低下、易出错&#xff0c;且不利于数据的统计和分析。为了解决这些…