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.mzph.cn/web/16403.shtml

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

相关文章

X-CSV-Reader:一个使用Rust实现CSV命令行读取器

&#x1f388;效果演示 ⚡️快速上手 依赖导入&#xff1a; cargo add csv读取实现&#xff1a; use std::error::Error; use std::fs::File; use std::path::Path;fn read_csv<P: AsRef<Path>>(filename: P) -> Result<(), Box<dyn Error>> {le…

【Java面试】二、Redis篇(中)

文章目录 1、Redis持久化1.1 RDB1.2 AOF1.3 RDB与AOF的对比 2、数据过期策略&#xff08;删除策略&#xff09;2.1 惰性删除2.2 定期删除 3、数据淘汰策略4、主从复制4.1 主从全量同步4.2 增量同步 5、哨兵模式5.1 服务状态监控5.2 哨兵选主规则5.3 哨兵模式下&#xff0c;Redi…

商标注册申请名称的概率,多想名称选通过率好的!

近日给深圳客户申请的商标初审下来了&#xff0c;两个类别都下的初审&#xff0c;和当初的判断基本一致&#xff0c;普推知产老杨当时沟通说需要做担保申请注册也可以&#xff0c;后面选择了管家注册&#xff0c;最近大量的帮客户检索商标名称&#xff0c;分享下经验。 两个字基…

【PB案例学习笔记】-09滚动条使用

写在前面 这是PB案例学习笔记系列文章的第8篇&#xff0c;该系列文章适合具有一定PB基础的读者。 通过一个个由浅入深的编程实战案例学习&#xff0c;提高编程技巧&#xff0c;以保证小伙伴们能应付公司的各种开发需求。 文章中设计到的源码&#xff0c;小凡都上传到了gitee…

新书推荐:7.1 do while语句

本节必须掌握的知识点&#xff1a; 示例二十二 代码分析 汇编解析 ■do while语句其语法形式&#xff1a; do{ 语句块; }while(表达式) ■语法解析&#xff1a; ●执行do循环体内的语句块&#xff1b; ●判断while语句里的表达式&#xff0c;表达式为真继续下次循环&#…

stm32学习-串口收发(HEX/文本)数据包

串口收发HEX数据包 接线 TXDPA10RXDPA9按键PB1 配置流程 定义为固定包长&#xff0c;含包头包尾&#xff08;其中包头为0xFF&#xff0c;载荷数据固定为4字节&#xff0c;包围为0xFE&#xff09; 注意&#xff1a;固定包长/可变包长&#xff0c;或者包头包围是什么&#xf…

UI控件与视图层次:探索界面的无限可能

[OC]UI学习笔记 文章目录 [OC]UI学习笔记视图和视图层次结构CGRectUILabelUIButtonUIView控件UIView的层级关系UIWindow定时器和视图移动UISwitch进度条和滑动条控件步进器和分栏控件警告对话框与等待指示器UITextField 视图和视图层次结构 Objective-C中的UI编程主要围绕视图…

C++的哈希 哈希表 哈希桶

目录 Unordered系列关联式容器 什么是哈希 哈希表 闭散列 载荷因子α 扩容 查找 删除 字符串哈希算法 最终代码 开散列 插入 查找 删除 最终代码 完整代码 Unordered系列关联式容器 C98中&#xff0c;STL提供了底层为红黑树结构的一系列关联式容器&#xff0…

大学校园广播“录编播”与IP广播系统技术方案

一、项目概述 1、校园IP网络广播系统概述 大学校园广播系统是学校整个弱电系统中的子系统&#xff0c;它是每个学校不可缺少的基础设施之一&#xff0c;在传递校园文化、传播校园新闻资讯方面发挥着重要的作用。近几年来&#xff0c;虽然视频技术和网络技术在飞速发展&#xf…

行为设计模式之策略模式

文章目录 概述原理结构图 代码实现小结 概述 策略模式(strategy pattern)的原始定义是&#xff1a;定义一系列算法&#xff0c;将每一个算法封装起来&#xff0c;并使它们可以相互替换。策略模式让算法可以独立于使用它的客户端而变化。 在软件开发中也会遇到相似的情况&…

基于双差分值和RR间隔处理的心电信号R峰检测算法(MATLAB R2018A)

心电信号中的R峰是确定心率和节律、以及检测其它波形特征点&#xff08;图1A&#xff09;的基础。R峰的准确检测是心率变异性分析、心拍分割和心律失常识别重要的处理步骤。 现有的心电信号R峰检测方法主要为基于规则的决策法和基于深度学习的检测方法。基于规则的决策法通常对…

中国低调海外巨头,实力超乎想象!

在全球化的浪潮中&#xff0c;中国公司正以前所未有的速度和规模走向世界。他们或许低调&#xff0c;但却实力非凡&#xff0c;在国际市场上掀起了一股不可小觑的“中国风暴”。今天&#xff0c;就让我们揭开那些在国外牛逼到爆炸的中国公司的神秘面纱&#xff0c;深度解析他们…

mysql中InnoDB的表空间--系统表空间

大家好。上篇文章我们讲了InnoDB的独立表空间&#xff0c;我们知道了表空间被划分为许多连续的区&#xff0c;对于大小为16KB的页面来说&#xff0c;每个区默认由64个页组成&#xff0c;每256个区为一组&#xff0c;每个组最开始的几个页的类型是固定的。&#xff08;在这里强烈…

HCIP-Datacom-ARST自选题库__BGP判断【20道题】

1.传统的BGP-4只能管理IPV4单播路由信息&#xff0c;MP-BGP为了提供对多种网络层协议的支持&#xff0c;对BGP-4进行了扩展。其中MP-BGP对IPv6单播网络的支持特性称为BGP4&#xff0c;BGP4通过Next Hop属性携带路由下一跳地址信息。 2.BGP4通过Update报文中的Next Hop属性携带…

RK3568笔记二十六:音频应用

若该文为原创文章&#xff0c;转载请注明原文出处。 一、介绍 音频是我们最常用到的功能&#xff0c;音频也是 linux 和安卓的重点应用场合。 测试使用的是ATK-DLR3568板子&#xff0c;板载外挂RK809 CODEC芯片&#xff0c;RK官方驱动是写好的&#xff0c;不用在自己重新写。…

智慧城市运维可视化:透视未来城市高效管理的新视窗

行业痛点 现代城市运维是一个复杂而庞大的系统&#xff0c;涉及到诸多方面&#xff0c;包括交通、环境、能源等等。然而&#xff0c;在城市运维中&#xff0c;存在着一些现实的痛点&#xff0c;给城市管理者带来了不小的压力和困扰&#xff1a; 1、交通拥堵 随着城市化进程的…

帝国cms自定义专题列表模板list.var中获取对应专题下的信息、信息数量及信息所属栏目名称

帝国cms自定义专题列表模板list.var中获取对应专题下的信息、信息数量及信息所属栏目名称 代码如下&#xff1a; $rr $empire->fetch1("SELECT GROUP_CONCAT(id) from phome_enewsztinfo where ztid$r[id]"); $ff $rr[0]; $ga explode(",", $ff); …

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《考虑分布式光伏高效消纳与负荷损失最小的区域配电网应急资源协同配置策略》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

一款网站源码下载开源工具 Website Downloader

一、简介 Website Downloader 是一款免费的网站源码下载器&#xff0c;下载任何网站的完整源代码&#xff0c;包括 JavaScript、样式表、图像等等&#xff0c;而且使用也非常简单&#xff0c;只需要粘贴网址&#xff0c;用户即可将网页链接内容下载到本地&#xff0c;而且自动…

堆(建堆算法,堆排序)

目录 一.什么是堆&#xff1f; 1.堆 2.堆的储存 二.堆结构的创建 1.头文件的声明&#xff1a; 2.向上调整 3.向下调整 4.源码&#xff1a; 三.建堆算法 1.向上建堆法 2.向下建堆法 四.堆排序 五.在文件中Top出最小的K个数 一.什么是堆&#xff1f; 1.堆 堆就…