Android中如何屏蔽IP地址

前言

前几篇介绍了iptables | 路由策略 | DNS等相关理论基础知识,现在在这基础上,去学习安卓的网络框架并应用这些知识点。Android的网络框架可以细分很多部分,按功能分的话,可以分网络评分与选择,apn管理,网络策略管理等;按层次分的话,可以分framework部分,native netd部分。现在需要定制一些iptables规则,然后应用到安卓源码。主要目的在于:

  • iptables规则是在android源码的哪个地方添加?
  • 如何给上层提供网络相关的接口,涉及到framework/native哪部分代码?

带着上面的两个目的,本文主要介绍netdNetworkManagementService。弄懂这两部分的流程,就可以定制iptables规则,向上提供接口,管理网络相关的功能。

基本需求

要学习安卓网络框架相关的知识,先确定你想通过它来达到什么目的。目前的想法就是做个demo app,实现下面的功能。

  • 显示当前网络信息,网口名称/IP地址/DNS地址/连接状态等。
  • 建立白名单模式和黑名单模式,允许/屏蔽某个IP地址访问网络。
  • 查询网址的IP地址,方便我们屏蔽或放行。
  • 提供屏蔽/允许DNS查询功能。

带着上面等待实现的小功能,开始学习安卓网络框架。

netd(Network Daemon)

基本概念

Network Daemon是Android系统中专门负责网络管理和控制的后台守护进程。它封装了底层的各种网络,隔离了底层网络接口的差异,给Framework提供了统一调用接口,简化了网络的使用。它通过netlink,虚拟文件系统等linux内核提供的用户接口,与内核通信,管理网络相关部分。主要功能有三部分:

  • 设置防火墙(Firewall),网络地址转换(NAT),带宽控制,流量统计,无线网上软接入点(soft acess point)控制,网络设备绑定(Tether),配置路由表,interface管理等。
  • Android系统中的DNS信息缓存和管理。
  • 网络服务搜索(Net Service Discovery, NSD)功能。

netd位于framework层和kernel层中间,是Android系统中网络相关消息和命令转发及处理的中枢模块。netd的工作流程可分为两部分:

  • netd接收并处理来自framework层的NetworkManagementService或NsdService的命令。
  • netd接收并解析来自kernel的UEvent消息,然后再转发给framework层对应的service去处理。
    在这里插入图片描述

启动与初始化

手机启动的过程会加载netd.rc文件,启动netd进程。文件如下所示:

android/system/netd/server/netd.rc

在这里插入图片描述

从这里看出,配置了几个socket(dnsproxyd, mdns, fwmarkd),netd进程初始化时会去监听这几个socket,上层可以通过这些socket和netd进行通信。

SOCKET描述
dnsproxydDNS代理的控制与配置
mdns多播DNS,Multicast DNS
fwmarkdiptables的策略路由配置,流量打标签,设置网络权限等

接着看一下netd的main函数,代码删去部分细节,只留主逻辑,下图标出来的就是重点要学习的地方。
在这里插入图片描述

从这里看出,netd的main函数很简洁,主要是对rc文件中三个socket进行监听,并初始化NetlinkManagerNetdNativeServiceController。接下来,对上面标出的几个类做详细的分析。

NetlinkManager

NetlinkManager主要负责接收并解析来自kernel的UEvent消息,这里不贴代码。它的初始化流程图如下所示:
在这里插入图片描述

从上面看出,它创建了四个不同协议的socket和对应的handler对象,每个socket都会对应的创建一个线程进行poll,监听不同协议的kernel事件。当有事件来,便会调用SocketListener的onDataAvailable方法,该方法内部会创建一个NetlinkEvent对象。NetlinkEvent对象根据socket创建时指定的解析类型去解析来自kernel的消息,最后onEvent方法被调用,不同类型的事件在这个方法中进行分类处理,它调用EventReporter通过binder的方式向framework层NetworkManagementService发起回调,调用对应的notifyXXX方法。下面是四个socket的描述:

协议描述
NETLINK_KOBJECT_UEVENT反映网络设备的事件和状态,如网口状态的变化
NETLINK_ROUTE接收路由信息, 如路由表的更新与删除
NETLINK_NFLOG接收数据流量使用配额的消息, 如数据使用超限
NETLINK_NETFILTER接收包过滤(netfilter)的消息

再来看一下NetlinkManager涉及到的几个类的类图,NetlinkManager->NetlinkHanler->NetlinkListener->SocketListener.

在这里插入图片描述

从这个类图看,NetlinkManager启动NetlinkHandler之后,就交给它去处理事件了。NetlinkHandler继承NetlinkListener, NetlinkListener继承SocketListenerSocketListener主要是处理socket相关的逻辑,起线程监听kernel事件。NetlinkListener主要是创建NetlinkEvent,把读取到的事件消息放在这里进行解析。最后NetlinkHandler负责把解析后的消息,传递给上层framework。可以看到每个类都有自己的业务逻辑,清晰明了。

Controllers

Controllers顾名思义,是一系列的控制器,包括防火墙,流量统计,带宽,路由表等功能的控制器;它的初始化流程如下图所示。
在这里插入图片描述
可以看到,Controllers的主要作用就是创建了很多规则子链,用来控制网络相关的功能。比较老的android版本,都是通过iptables创建规则链来实现这些功能的;但是android引入了bpf之后,很多都是通过bpf来实现,包括数据包的过滤和拦截,app的上网控制等。像之前的fw_dozable,fw_standby,fw_powersave规则子链,都没有创建了,改成采用bpf过滤的方式。而bpf的相关功能实现,是通过TrafficController关联bpf来实现的。

Controllers的成员变量,包括了下面的各个controller:

控制器描述
NetworkController保存网络信息,包括NetId | 接口 | IP地址
TetherController网络设备绑定,如通过USB共享网络
PppControllertty终端绑定pppd(点对点协议的守护进程,在使用VPN时才需要使用它)
BandwidthController带宽控制,实际是监控系统数据流量,流量阈值提醒,流量使用上限,限制app后台流量
IdletimerController监听网口在设定时间内没有数据通过时,上报一个netlink事件
FirewallController防火墙功能,制定防火墙规则
ClatdControllerIPv4-IPv6 地址的转换器(不确定)
StrictController检测app运行网络通信是否进行SSL/TLS数据加密
EventReporter负责事件上报到framework,提供注册接口,保存Listener
IptablesRestoreController负责执行iptables命令
WakeupController不懂
XfrmController与Ipsec相关
TrafficController与eBPF结合,负责对数据包过滤,流量统计,按UID划分防火墙等
TcpSocketMonitor定时监听物理网络tcp相关信息,包括发包/丢包数目,rtt等等

通过上面这个表格,对这些controller我们有了初步的了解,知道它大致的功能,日后需要调查具体的细节,在回过头去查找就可以。也看到了iptables规则的创建,就在这些controller之中。由此推测,假如需要定制一些网络功能,那么可能要在这些controller中增加iptables规则。

FwmarkServer

FwmarkServer的作用就是给数据打标签。从前面的netd.rc文件中已经配置socket_namefwmarkd,netd进程会去初始化,进行监听。请看下面流程图:
在这里插入图片描述

从这里看出,主要的逻辑是在processClient中进行处理。其中的逻辑包括,在socket进行connect时,accept完成时,根据具体条件对socket的fwmark进行设置(fwmark包括permission,netid两部分)。这其实是对socket相关系统函数(connect|accept)进行hook,在hook函数中通过链接到FwmarkServer进行打标签。Hook流程和数据进来打标签可以参考Android 策略路由

NetdNativeService

NetdNativeService是一个binder服务,它是framework层和HAL层通信的媒介。它发布一个netd服务供framework层使用,实现INetd.aidl接口。

system\netd\server\binder\android\net\INetd.aidl

NetdNativeService的功能实际上是通过调用Controller去实现的,所以说它只是通信的媒介,具体功能实现是放在Controller。那么想要增加新的接口,似乎可以在这里增加。

下面是它的初始化流程:

在这里插入图片描述

NetworkManagementService

NetworkManagementService在SystemServer进程中启动,它主要用于获取netd服务和native层通信,对网络进行管理。注册监听事件(unsolicited event listener)到netd,用于native上报网络事件。同时获取IOemNetd实例,实际它是一系列接口,如果要新增接口,可以放在这个IOemNetd对象里面。而NetworkManagementService本身也是一个服务,SystemServer把它发布到系统中,供其他模块使用,服务名是"network_management"。

下面是它的初始化流程
在这里插入图片描述

从上面看出,启动后时主要做了几件事

  1. 获取netd服务,通过它和native层通信
  2. 注册listener到native层监听网络事件
  3. 获取IOemNetd对象,是一系列接口,扩展用
  4. 发布自身服务到系统

到这里,初步可以回答本文开头的问题了。

  • iptables相关的规则,需要在native netd中添加,可以参考controller中的实现。

  • 增加接口,需要在framework的NetworkManagementService添加;也需要在native层的INetd.aidl/IOemNetd.aidl中添加,在native为了方便区分系统自带的接口和自定义接口,统一放在IOemNetd.aidl这里添加便可以。上层通过获取netd服务,调用接口

    IOemNetd.Stub.asInterface(mNetdService.getOemNetd());

    得到IOemNetd的实例OemNetdListener,再调用具体接口。实现IOemNetd.aidl的具体对象如下:

    system\netd\server\OemNetdListener.cpp

方案实现

这里可以简单分三步,按照下面的步骤去实现功能就行。

  1. 设计接口(netd & framework)
  2. 实现接口(iptables规则)
  3. 编写APK(UI界面,编译源码)

netd 和 framework 涉及修改的文件如下:

system\netd\server\binder\com\android\internal\net\IOemNetd.aidl

system\netd\server\OemNetdListener.h

system\netd\server\OemNetdListener.cpp

frameworks\base\core\java\android\os\INetworkManagementService.aidl

frameworks\base\services\core\java\com\android\server\NetworkManagementService.java

其中接口实现是放在OemNetdListener.cpp。framework增加相应的接口给APK调用。

设计接口

  1. 黑名单模式屏蔽IP地址
  2. 白名单模式屏蔽IP地址
  3. 屏蔽DNS的开关

三个接口功能不同,先创建对应的三个规则子链;然后在子链中添加规则,实现具体功能。netd和framework同步增加接口,一一对应。所以增加的接口如下所示:

 oneway void makeSeaiceChildChain(); //创建子链
 oneway void setIdleType(); //清空规则
 oneway void setBlackTypeIpList(in @utf8InCpp String addr);  //黑名单模式
 oneway void setWhiteTypeIpList(in @utf8InCpp String addr); //白名单模式
 oneway void setBlockDns(boolean enable); //屏蔽查询DNS与否

实现接口

接口的实现主要就是添加iptables规则,如何添加可以参考FirewallController现成的代码(@makeUidRules)。接口实现在下面这个文件:

netd\server\OemNetdListener.cpp

makeSeaiceChildChain的实现代码如下,它是在启动的时候调用一次,可以在NetworkManagementServer的systemReady调用,创建出三个子链。

::android::binder::Status OemNetdListener::makeSeaiceChildChain() {
    int res = -1;
    IptablesTarget target = V4V6;
    std::string command = "*filter\n";
    std::vector<std::string> seaiceChains;
    //子链
    seaiceChains = {WHITE_NAME_TYPE, BLACK_NAME_TYPE, REJECT_DNS_QUERY};
    
    for(std::string& chain : seaiceChains) {
        ::android::base::StringAppendF(&command, "-N %s\n", chain.c_str());
    }
    ::android::base::StringAppendF(&command, "COMMIT\n");
    
    res = execIptablesRestore(target, command.c_str());
    
    if(res == 0) {
         return ::android::binder::Status::ok();
    } else {
        return ::android::binder::Status::fromServiceSpecificError(res,
            ::android::String8::format("makeSeaiceChildChain error: %d", res));
    }
}

setBlackTypeIpList接口的实现代码如下:

::android::binder::Status OemNetdListener::setBlackTypeIpList(const std::string& addr) {
    std::lock_guard lock(mMutex);

    //check chain exist or not
    int res = -1;
    IptablesTarget target = V4V6;
    std::string command = "*filter\n";
    ::android::base::StringAppendF(&command, "-C OUTPUT -j %s\n", BLACK_NAME_TYPE);
    ::android::base::StringAppendF(&command, "COMMIT\n");
    res = execIptablesRestore(target, command.c_str());
    
    //clean chain
    command = "*filter\n";
    if (!res) {
        ::android::base::StringAppendF(&command, "-D OUTPUT -j %s\n", BLACK_NAME_TYPE);
    }
    ::android::base::StringAppendF(&command, "-F %s\n", BLACK_NAME_TYPE);
    ::android::base::StringAppendF(&command, "-A OUTPUT -j %s\n", BLACK_NAME_TYPE);
    
    std::stringstream ss(addr);
    std::string ipAddr;
    while(std::getline(ss, ipAddr, '-')) {
        ::android::base::StringAppendF(&command, "-A %s -d %s -j DROP\n", BLACK_NAME_TYPE, ipAddr.c_str());
    }
    
    ::android::base::StringAppendF(&command, "-A %s -p all -j RETURN\n", BLACK_NAME_TYPE);
    ::android::base::StringAppendF(&command, "COMMIT\n");
    
    res = execIptablesRestore(target, command.c_str());
    if(res == 0) {
         return ::android::binder::Status::ok();
    } else {
        return ::android::binder::Status::fromServiceSpecificError(res,
            ::android::String8::format("makeSeaiceChildChain error: %d", res));
    }

}

其他接口的实现不再一一列举。

编写APK

NetworkManagementService的接口并不对外公开,不是标准的SDK API,所以需要编写一个需要系统权限的APK。这里主要涉及如何编译一个具有系统权限的APK。参考其他系统APK,主要在于编写正确的android.bp, 然后通过系统编译。如下:

package {
    // See: http://go/android-license-faq
    // A large-scale-change added 'default_applicable_licenses' to import
    // all of the 'license_kinds' from "packages_services_Telephony_license"
    // to get the below license kinds:
    //   SPDX-license-identifier-Apache-2.0
    // default_applicable_licenses: ["packages_services_Telephony_license"],
}

android_app {
    name: "seaiceNetApp",

    srcs: [
        "java/**/*.java",
    ],
    
    static_libs: [
        "androidx-constraintlayout_constraintlayout",
        "androidx.cardview_cardview",
        "androidx.appcompat_appcompat",
    ],

    //platform:平台的核心应用签名,签名的apk是完成系统的核心功能。
    //这些apk所在的进程UID是system。manifest节点中有添加android:sharedUserId="android.uid.system"。
    certificate: "platform",
    //设置该标记后会使用sdk的hide的api來编译。编译的APK中使用了系统级API,必须设定该值。
    //和Android.mk中的LOCAL_PRIVATE_PLATFORM_APIS的作用相同
    platform_apis: true,
}

以上要主要理解和定义这两个值certificateplatform_apis,才能正确编译。其他的不打算贴代码了,都是一些UI上的逻辑。最后的效果如下所示:

在这里插入图片描述

参考文档

深入理解Android系统网络架构
Android 系统网络框架
Android 框架实现分析 - 网络 - Native层
Netd中的“netd”套接字
Android 策略路由
Android系统中iptables的应用(五)IdlertimerController
Android 6.0 StrictController
Android 10 路由添加原理
Android系统签名简介
About LOCAL_PRIVATE_PLATFORM_APIS in Android.mk
针对非 SDK 接口的限制
Android中的 eBPF 流量监控
速查 | Android 系统目录功能
Android Doze and App Standby模式详解
重看ebpf -代码载入执行点-hook

  • 8
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值