以太网的功能是允许设备提供硬件接口通过插入网线的形式访问互联网的功能。接入网线之后,设备可以动态的获取IP,DNS,Gateway等一系列网络属性,我们也可以手动配置设备的网络属性,使用静态配置参数。Google已经有一套现成的机制使用有线网,但没有涉及有线网配置的功能,本文主要介绍如何Google现有机制的基础上实现静态网络的配置。本文基于高通MSM8953 Android 7.1平台进行开发,通过配置eth0网口的IP,DNS,Gateway三个参数,实现上网功能,若是其他平台或者非高通平台,可以当作参考。
此部分Google已经做好,当接入网线之后,在SystemBar中会出现有线网介入图标(<--->),此时设备已经接入有线网络,可以正常上网。
首先先来介绍一下相关java类:
(1)frameworks/base/core/java/android/net/IpConfiguration.java
IP状态配置,动态或者是静态,之后会介绍
(2)frameworks/base/core/java/android/net/StaticIpConfiguration.java
静态IP配置相关类,主要用于配置静态IP。
(3)frameworks/base/core/java/android/net/EthernetManager.java
上层配置IP的管理类,可以通过context.getSystemService(Context.ETHERNET_SERVICE)获得。
(4)frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetServiceImpl.java
通过实现IEthernetManager.aidl接口来处理一些远程的以太网请求。
(5)frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetNetworkFactory.java
以太网网络链接的管理类。
具体介绍之前,先来看一张简单配置UML的流程图,方便接下来的讲解.
接下来对照流程图逐步进行讲解。
我们自己的项目中是通过配置eth0的IP,DNS,Gateway来配置静态网络参数的。可以自己开发相应界面,让用户手动输入相关信息即可。
这一步不涉及配置代码,仅仅是获取用户的想要设置的配置信息。
首先我们需要将相关配置信息转化为StaticIpConfiguration,转化之前,先介绍两个枚举类:
public enum IpAssignment {
/* Use statically configured IP settings. Configuration can be accessed
* with staticIpConfiguration */
STATIC,
/* Use dynamically configured IP settigns */
DHCP,
/* no IP details are assigned, this is used to indicate
* that any existing IP settings should be retained */
UNASSIGNED
}
public enum ProxySettings {
/* No proxy is to be used. Any existing proxy settings
* should be cleared. */
NONE,
/* Use statically configured proxy. Configuration can be accessed
* with httpProxy. */
STATIC,
/* no proxy details are assigned, this is used to indicate
* that any existing proxy settings should be retained */
UNASSIGNED,
/* Use a Pac based proxy.
*/
PAC
}
这两个枚举类型在IpConfiguration类中,具体作用上面代码部分的注释也写明了。下面是将配置信息转化为StaticIpConfiguration的方法:
private StaticIpConfiguration validateIpConfigFields(String ip,String dns,String gateway) {
StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration();
//analysis ip address
Inet4Address inetAddr = getIPv4Address(ip);
if (inetAddr == null || inetAddr.equals(Inet4Address.ANY)) {
return -1;
}
staticIpConfiguration.ipAddress = new LinkAddress(inetAddr, DEFAULT_PREFIX_LENGTH);
//analysis gateway address
InetAddress gatewayAddr = getIPv4Address(gateway);
if (gatewayAddr == null) {
return -1;
}
if (gatewayAddr.isMulticastAddress()) {
return -1;
}
staticIpConfiguration.gateway = gatewayAddr;
//analysis dns address
InetAddress dnsAddr = getIPv4Address(dns);
if (dnsAddr == null) {
return -1;
}
staticIpConfiguration.dnsServers.add(dnsAddr);
return staticIpConfiguration;
}
private Inet4Address getIPv4Address(String text) {
try {
return (Inet4Address) NetworkUtils.numericToInetAddress(text);
} catch (IllegalArgumentException | ClassCastException e) {
Log.e(TAG,"getIPv4Address fail");
return null;
}
}
其中DEFAULT_PREFIX_LENGTH默认值是24,参考来自于Wifi模块。至此,我们就将用户输入的IP,DNS,Gateway转化为需要的StaticIpConfiguration。
由于最终调用EthernetManager的setConfiguration函数时传递的参数类型是IpConfiguration,查看StaticIpConfiguration,发现StaticIpConfiguration并不是IpConfiguration的子类,所以我们需要在将StaticIpConfiguration转化为IpConfiguration,查看IpConfiguration代码,发现IpConfiguration的构造函数中含有StaticIpConfiguration参数,另外,我们可以通过setStaticIpConfiguration改变IpConfiguration。这里我们选择前者,直接使用StaticIpConfiguration传入IpConfiguration的构造函数创建IpConfiguration对象,先看一下IpConfiguration的构造函数:
private void init(IpAssignment ipAssignment,
ProxySettings proxySettings,
StaticIpConfiguration staticIpConfiguration,
ProxyInfo httpProxy) {
this.ipAssignment = ipAssignment;
this.proxySettings = proxySettings;
this.staticIpConfiguration = (staticIpConfiguration == null) ?
null : new StaticIpConfiguration(staticIpConfiguration);
this.httpProxy = (httpProxy == null) ?
null : new ProxyInfo(httpProxy);
}
public IpConfiguration() {
init(IpAssignment.UNASSIGNED, ProxySettings.UNASSIGNED, null, null);
}
public IpConfiguration(IpAssignment ipAssignment,
ProxySettings proxySettings,
StaticIpConfiguration staticIpConfiguration,
ProxyInfo httpProxy) {
init(ipAssignment, proxySettings, staticIpConfiguration, httpProxy);
}
可以看出,无论是有参的构造函数还是无参的构造函数,最终都会调用IpConfiguration的init函数进行初始化配置。我们使用的是IpConfiguration中有参的构造函数,其中参数IpAssignment和ProxySettings是枚举类型,我们需要配置静态地址,所以应该传入IpAssignment.STATIC和ProxySettings.STATIC,第三个参数传入StaticIpConfiguration,第四个参数ProxyInfo传入空即可,不需要设置代理。
mIpAssignment = IpAssignment.STATIC;
mProxySettings = ProxySettings.STATIC;
mStaticIpConfiguration = validateIpConfigFields(ip,dns,gateway); // 注意此处的参数应正确配置
IpConfiguration ipconfig = new IpConfiguration(mIpAssignment,mProxySettings,mStaticIpConfiguration,null);
获取IpConfiguration之后,我们就可以调用EthernetManager的setConfiguration开始进行静态网络配置:
/**
* Set Ethernet configuration.
*/
public void setConfiguration(IpConfiguration config) {
try {
mService.setConfiguration(config);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
查看设置代码,函数会调用mService的setConfiguration并且可能会抛出RemoteException。说明这一操作应该是远程aidl的调用,跟踪代码发现mService的类型为EthernetServiceImpl,并且实现了IEthernetManager.aidl接口。
/**
* Set Ethernet configuration
*/
@Override
public void setConfiguration(IpConfiguration config) {
if (!mStarted.get()) {
Log.w(TAG, "System isn't ready enough to change ethernet configuration");
}
enforceConnectivityInternalPermission();
synchronized (mIpConfiguration) {
mEthernetConfigStore.writeIpAndProxyConfigurations(config);
// TODO: this does not check proxy settings, gateways, etc.
// Fix this by making IpConfiguration a complete representation of static configuration.
if (!config.equals(mIpConfiguration)) {
mIpConfiguration = new IpConfiguration(config);
mTracker.stop();
mTracker.start(mContext, mHandler);
}
}
}
代码中EthernetConfigStore将会把IpConfiguration的配置信息写入配置文件。进入EthernetConfigStore发现writeIpAndProxyConfigurations最后会调用EthernetConfigStore父类writeIpAndProxyConfigurations方法将配置信息写入配置文件。
之后判断当前地址是否跟配置地址一样,若不一样,则进行新地址的配置,由于配置信息已经通过EthernetConfigStore写入配置文件,mTracker也即EthernetNetworkFactory就会重启当前网络。这部分的逻辑代码都是Google已有的代码,这里不继续跟踪。
注:有时候EthernetNetworkFactory重启网络之后发现配置信息没有生效,我遇到这种情况后,发现此时需要重启eth0网口。重启网口的功能将会在下面的文章里介绍。
/**
* A listener interface to receive notification on changes in Ethernet.
*/
public interface Listener {
/**
* Called when Ethernet port's availability is changed.
* @param isAvailable {@code true} if one or more Ethernet port exists.
*/
public void onAvailabilityChanged(boolean isAvailable);
}
解释中说此接口可以接受以太网变化的通知。在实际应用时,发现插入网线和拔出网线确实能够接受到通知,说明这个接口正是我们需要的。查看代码发现,要使用这个接口,应该先调用addListener将实现该接口的子类加入到一个ArrayList的通知列表里面,这说明我们可以在不同的地方接受以太网状态变化的通知。
/**
* Adds a listener.
* @param listener A {@link Listener} to add.
* @throws IllegalArgumentException If the listener is null.
*/
public void addListener(Listener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener must not be null");
}
mListeners.add(listener);
if (mListeners.size() == 1) {
try {
mService.addListener(mServiceListener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
从上面代码可以看到,传递的参数被添加到了mListeners中,并且将mServiceListener添加到EthernetServiceImpl的远程监听接口中去。mServiceListener代码如下:
private final IEthernetServiceListener.Stub mServiceListener =
new IEthernetServiceListener.Stub() {
@Override
public void onAvailabilityChanged(boolean isAvailable) {
mHandler.obtainMessage(
MSG_AVAILABILITY_CHANGED, isAvailable ? 1 : 0, 0, null).sendToTarget();
}
};
若系统检测到以太网状态发生变化,则会通过调用mServiceListener来进行广播通知,接着在mHandler中会循坏便利mListeners列表中的监听对象,凡是注册了监听接口的类都会收到通知消息。
有时候我们需要在不拔出网线的同时关闭上网的功能,这个时候解决方安就是将网口关闭,等到允许上网时再打开网口,接下来就来介绍网口的打开与关闭操作。
查看EthernetNetworkFactory的代码可以发现有这样一个函数和之前一样,我们先到EthernetManager中查看有没有已经做好的功能可以供我们调用。很遗憾的是,EthernetManager并没有实现开关网口的功能。由于EthernetManager不是最终管理以太网的管理类,只是一个提供上层接口的一个中间类,所以要想查看以太网的所以功能,我们应该去查找以太网的管理类EthernetNetworkFactory。
查看EthernetNetworkFactory的代码可以发现有这样一个函数:
/**
* Updates interface state variables.
* Called on link state changes or on startup.
*/
private void updateInterfaceState(String iface, boolean up) {
Log.d(TAG, "updateInterface: " + iface + " link " + (up ? "up" : "down")+" , mIface : "+mIface);
if (!mIface.equals(iface)) {
return;
}
Log.d(TAG, "updateInterface: " + iface + " link " + (up ? "up" : "down"));
synchronized(this) {
mLinkUp = up;
mNetworkInfo.setIsAvailable(up);
if (!up) {
// Tell the agent we're disconnected. It will call disconnect().
mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr);
stopIpProvisioningThreadLocked();
}
updateAgent();
// set our score lower than any network could go
// so we get dropped. TODO - just unregister the factory
// when link goes down.
mFactory.setScoreFilter(up ? NETWORK_SCORE : -1);
}
}
这正是我们想要的功能,所以这个功能其实也是已经做好的,但是他是private类型的函数,说明Google并不想将这个功能公开出来。函数的第一个参数是想要打开或关闭的网口名称,第二个参数表明是打开还是关闭,true表示打开,false表示关闭。
既然已经有了功能,我们只需要调用即可,具体如何调用,我们可以模仿配置静态IP的方法,从EthernetManager开始,到EthernetNetworkFactory结束,将这个过程做成一个标准的功能。
首先我们在 EthernetManager中添加一个函数updateIface: /*
* up and down eth0
*/
public void updateIface(String iface,boolean up){
try {
mService.updateIfaceState(iface,up);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
此时需要在EthernetServiceImpl中添加函数updateIfaceState:
@Override
public void updateIfaceState(String iface,boolean up){
mTracker.changeEthernetState(iface,up);
}
Override注解说明这是重写函数,EthernetServiceImpl继承自IEthernetManager.Stub,所以我们需要在对应的IEthernetManager.aidl接口文件中加入updateIfaceState声明,如下所示:
// IethernetManager.aidl
interface IEthernetManager
{
IpConfiguration getConfiguration();
void setConfiguration(in IpConfiguration config);
boolean isAvailable();
void addListener(in IEthernetServiceListener listener);
void removeListener(in IEthernetServiceListener listener);
void updateIfaceState(String iface,boolean up);
}
这里之后会调用mTracker.changeEthernetState函数在EthernetNetworkFactory创建函数changeEthernetState:
public void changeEthernetState(String iface,boolean state){
Log.i(TAG,"changeEthernetState : iface : "+iface+" , state : "+state);
updateInterfaceState(iface,state);
}
到此结束,从EthernetManager到EthernetNetworkFactory中关于网口开关的功能就做完了,我们只需要调用EthernetManager中的updateIface函数就能实现网口的打开与关闭功能。
之前在第四节中介绍过监听网线的插拔功能,第五节中介绍了在不拔网线的情况下打开与关闭网口,这个时候就会遇到一个问题,那就是我无法正确的监听到网线的拔出。实际操作中会发现,打开与关闭网口是,监听器监听同样会被调用,但是此时我并没有拔出网线。换一个说法就是,网口的打开与关闭实际上模拟的就是网线的插拔功能。
那此时我就需要正确区分开网口的关闭与网线的拔出这两种情况。
查询EthernetNetworkFactory代码可以发现有一个内部类可以监听到网线的插入与拔出:
private class InterfaceObserver extends BaseNetworkObserver {
@Override
public void interfaceLinkStateChanged(String iface, boolean up) {
updateInterfaceState(iface, up);
}
@Override
public void interfaceAdded(String iface) {
maybeTrackInterface(iface);
}
@Override
public void interfaceRemoved(String iface) {
stopTrackingInterface(iface);
}
}
其中interfaceAdded表示网线的插入,interfaceRemoved表示网线的拔出。为了配合系统的原生代码结构,我们可以在EthernetManager的Listener接口中添加一个新函数声明,添加后的Listener接口如下:
/**
* A listener interface to receive notification on changes in Ethernet.
*/
public interface Listener {
/**
* Called when Ethernet port's availability is changed.
* @param isAvailable {@code true} if one or more Ethernet port exists.
*/
public void onAvailabilityChanged(boolean isAvailable);
/*
*Called when network wire take out
*/
public void onEthernetIfaceRemove();
}
监听的注册流程重EthernetManager的addListener,到EthernetServiceImpl中的addListener时,已经将其注册到了一个RemoteCallList的列表中,在通过构造mTacker是将监听列表传给了EthernetNetworkFactory。所以我们只需要在EthernetNetworkFactory实现通知网线拔出就可以了。
具体代码如下:private void notifyListenersRemoved(){
int n = mListeners.beginBroadcast();
Log.i("SIMCOMIP","notifyListenersRemoved state listener size : "+n);
for (int i = 0; i < n; i++) {
try {
mListeners.getBroadcastItem(i).onEthernetIfaceRemove();
} catch (RemoteException e) {
// Do nothing here.
}
}
mListeners.finishBroadcast();
}
首先,添加一个通知所有监听者网线拔出的函数,之后我们就可以在前面提到的InterfaceObserver的interfaceRemoved函数中调用一下就可以了:
private class InterfaceObserver extends BaseNetworkObserver {
@Override
public void interfaceLinkStateChanged(String iface, boolean up) {
updateInterfaceState(iface, up);
}
@Override
public void interfaceAdded(String iface) {
maybeTrackInterface(iface);
}
@Override
public void interfaceRemoved(String iface) {
stopTrackingInterface(iface);
notifyListenersRemoved();
}
}
上层的SystemUI显示图标及更新是通过NetworkControllerImpl.java文件完成,具体可以自己查看代码,这里不做解析了。
文章浏览阅读1.2k次,点赞35次,收藏18次。AutowiredPostConstruct 注释用于在依赖关系注入完成之后需要执行的方法上,以执行任何初始化。此方法必须在将类放入服务之前调用。支持依赖关系注入的所有类都必须支持此注释。即使类没有请求注入任何资源,用 PostConstruct 注释的方法也必须被调用。只有一个方法可以用此注释进行注释。_springboot2.7获取bean
文章浏览阅读2.1k次。理论介绍 节点定义package logistic;public class Instance { public int label; public double[] x; public Instance(){} public Instance(int label,double[] x){ this.label = label; th_logisticregression java
文章浏览阅读981次,点赞21次,收藏18次。本书是获得了很多读者好评的Linux经典畅销书**《Linux从入门到精通》的第2版**。下面我们来进行文件的恢复,执行下文中的lsof命令,在其返回结果中我们可以看到test-recovery.txt (deleted)被删除了,但是其存在一个进程tail使用它,tail进程的进程编号是1535。我们看到文件名为3的文件,就是我们刚刚“误删除”的文件,所以我们使用下面的cp命令把它恢复回去。命令进入该进程的文件目录下,1535是tail进程的进程id,这个文件目录里包含了若干该进程正在打开使用的文件。
文章浏览阅读10w+次,点赞12次,收藏72次。RTMP(Real Time Messaging Protocol)实时消息传输协议是Adobe公司提出得一种媒体流传输协议,其提供了一个双向得通道消息服务,意图在通信端之间传递带有时间信息得视频、音频和数据消息流,其通过对不同类型得消息分配不同得优先级,进而在网传能力限制下确定各种消息得传输次序。_rtmp
文章浏览阅读64次。2017年12月的计算机等级考试将要来临!出国留学网为考生们整理了2017年12月计算机一级MSOffice考试习题,希望能帮到大家,想了解更多计算机等级考试消息,请关注我们,我们会第一时间更新。2017年12月计算机一级MSOffice考试习题(二)一、单选题1). 计算机最主要的工作特点是( )。A.存储程序与自动控制B.高速度与高精度C.可靠性与可用性D.有记忆能力正确答案:A答案解析:计算...
文章浏览阅读356次。在学MYSQL的时候刚刚好看到了这个提权,很久之前用过别人现成的,但是一直时间没去细想, 这次就自己复现学习下。 0x00 UDF 什么是UDF? UDF (user defined function),即用户自定义函数。是通过添加新函数,对MySQL的功能进行扩充,就像使..._the provided input file '/usr/share/metasploit-framework/data/exploits/mysql
文章浏览阅读3.1w次,点赞71次,收藏485次。webService一 WebService概述1.1 WebService是什么WebService是一种跨编程语言和跨操作系统平台的远程调用技术。Web service是一个平台独立的,低耦合的,自包含的、基于可编程的web的应用程序,可使用开放的XML(标准通用标记语言下的一个子集)标准...
文章浏览阅读1w次。前言照例给出官网:Retrofit官网其实大家学习的时候,完全可以按照官网Introduction,自己写一个例子来运行。但是百密一疏,官网可能忘记添加了一句非常重要的话,导致你可能出现如下错误:Could not locate ResponseBody converter错误信息:Caused by: java.lang.IllegalArgumentException: Could not l_已添加addconverterfactory 但是 could not locate responsebody converter
文章浏览阅读1k次。一套键鼠控制Windows+Linux——Synergy在Windows10和Ubuntu18.04共控的实践Synergy简介准备工作(重要)Windows服务端配置Ubuntu客户端配置配置开机启动Synergy简介Synergy能够通过IP地址实现一套键鼠对多系统、多终端进行控制,免去了对不同终端操作时频繁切换键鼠的麻烦,可跨平台使用,拥有Linux、MacOS、Windows多个版本。Synergy应用分服务端和客户端,服务端即主控端,Synergy会共享连接服务端的键鼠给客户端终端使用。本文_linux 18.04 synergy
文章浏览阅读374次。写demo的时候遇到了很多问题,记录一下。安装nacos1.4.0配置mysql数据库,新建nacos_config数据库,并根据初始化脚本新建表,使配置从数据库读取,可单机模式启动也可以集群模式启动,启动时 ./start.sh -m standaloneapplication.properties 主要是db部分配置## Copyright 1999-2018 Alibaba Group Holding Ltd.## Licensed under the Apache License,_seata1.4.0 +nacos 集成
文章浏览阅读833次。iperf使用方法详解 iperf3是一款带宽测试工具,它支持调节各种参数,比如通信协议,数据包个数,发送持续时间,测试完会报告网络带宽,丢包率和其他参数。 安装 sudo apt-get install iperf3 iPerf3常用的参数: -c :指定客户端模式。例如:iperf3 -c 192.168.1.100。这将使用客户端模式连接到IP地址为192.16..._iperf客户端指定ip地址
文章浏览阅读7.4k次。 写这个函数目的不是为了和C/C++库中的函数在性能和安全性上一比高低,只是为了给那些喜欢探讨函数内部实现的网友,提供一种从浮点性到字符串转换的一种途径。 浮点数是有精度限制的,所以即使我们在使用C/C++中的sprintf或者cout 限制,当然这个精度限制是可以修改的。比方在C++中,我们可以cout.precision(10),不过这样设置的整个输出字符长度为10,而不是特定的小数点后1_c++浮点数 转 字符串 精度损失最小