1. 有线网络配置
1.1 配置流程
- APK获取用户配置的网络参数(IP地址、是否开启DHCP、子网掩码、默认网关、DNS)。
- 调用方法函数将字符串形式的网络参数转化为StaticIpConfiguration(定义静态IP配置的详细信息),然后转化为IpConfiguration(定义IP配置方式)。
- 调用EthernetManager的setConfiguration进行网络配置,该请求会被发送到系统服务层。
- EthernetServiceImpl处理远程网络配置请求。
- EthernetNetworkFactory管理类处理进一步处理链接请求。
1.2 源码分析
1.2.1 APK配置网络参数
APK中写一个setEthernetStaticInfo()接口,用来传递要配置的有线网络参数,包括网路接口名称(区分多网络网络)、IP地址、子网掩码、默认网关、DNS。
首先调用getIPv4Address()和maskStr2InetMask()方法把字符串形式的网络参数转换为StaticIpConfiguration中参数类型一致。
新建并初始化StaticIpConfiguration对象。
创建IpConfiguration,指定IP配置方式,并把包含网络参数的StaticIpConfiguration对象传入。
IpConfiguration.IpAssignment如果设置为静态IP必须传入网络参数StaticIpConfiguration。
- 通过上层配置IP的管理类EthernetManager配置有线网络。
<MainActivity.java>
/*
* 设置静态网络参数
*/
public void setEthernetStaticInfo(String iface, String ipAddress, String subnetMask, String gateway, String dns1, String dns2) {
EthernetManager ethernetManager = (EthernetManager) getSystemService(Context.ETHERNET_SERVICE);
if (ethernetManager == null){
Log.e(TAG, "setEthernetStaticInfo ethernetManager == null !");
return;
}
try {
Log.d(TAG, "ipAddress: " + ipAddress + "subnetMask: " + subnetMask + "gateway: " + gateway + "dns1: " + dns1 + "dns2: " + dns2);
// 修改网络参数
Inet4Address inetAddr = getIPv4Address(ipAddress);
int prefixLength = maskStr2InetMask(subnetMask);
InetAddress gatewayAddr = getIPv4Address(gateway);
InetAddress dnsAddr = getIPv4Address(dns1);
InetAddress dns2Addr = getIPv4Address(dns1);
if (TextUtils.isEmpty(ipAddress) || prefixLength == 0 || TextUtils.isEmpty(gateway) || TextUtils.isEmpty(dns1) || TextUtils.isEmpty(dns2)) {
Log.e(TAG, "setEthernetStaticInfo ip or mask or gateway or dns1 or dns2 is wrong!");
return;
}
StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration();
staticIpConfiguration.ipAddress = new LinkAddress(inetAddr, prefixLength); // IP地址、子网掩码
staticIpConfiguration.gateway = gatewayAddr; // 网关
staticIpConfiguration.dnsServers.add(dnsAddr); // 首选DNS
staticIpConfiguration.dnsServers.add(dns2Addr); // 备选DNS
IpConfiguration ipConfiguration = new IpConfiguration(IpConfiguration.IpAssignment.STATIC, IpConfiguration.ProxySettings.NONE, staticIpConfiguration, null);
ethernetManager.setConfiguration(iface, ipConfiguration);
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "setStaticIpConfiguration error :" + e);
}
}
以下两个接口方法是Android11源码中提供的。
<MainActivity.java>
private Inet4Address getIPv4Address(String text) {
try {
return (Inet4Address) NetworkUtils.numericToInetAddress(text);
} catch (IllegalArgumentException | ClassCastException e) {
return null;
}
}
/*
* convert subMask string to prefix length
*/
private int maskStr2InetMask(String maskStr) {
StringBuffer sb;
String str;
int inetmask = 0;
int count = 0;
/*
* check the subMask format
*/
Pattern pattern = Pattern.compile("(^((\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(\\d|[01]?\\d\\d|2[0-4]\\d|25[0-5])$)|^(\\d|[1-2]\\d|3[0-2])$");
if (pattern.matcher(maskStr).matches() == false) {
Log.e(TAG, "maskStr2InetMask subMask is error!");
return 0;
}
String[] ipSegment = maskStr.split("\\.");
for (int n = 0; n < ipSegment.length; n++) {
sb = new StringBuffer(Integer.toBinaryString(Integer.parseInt(ipSegment[n])));
str = sb.reverse().toString();
count = 0;
for (int i = 0; i < str.length(); i++) {
i = str.indexOf("1", i);
if (i == -1)
break;
count++;
}
inetmask += count;
}
return inetmask;
}
网络参数配置成功可以通过监听网络状态改变的广播来更新UI显示。
1.2.2 EthernetManager.setConfiguration()
- 上层配置IP的管理类EthernetManager的setConfiguration()会将配置网络的请求通过Binder发送到系统服务层。
<frameworks/base/core/java/android/net/EthernetManager.java>
/**
* Set Ethernet configuration.
* @hide
*/
@UnsupportedAppUsage
public void setConfiguration(String iface, IpConfiguration config) {
try {
mService.setConfiguration(iface, config);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
- EthernetServiceImpl处理远程请求。
EthernetService在SystemServer检测到设备支持以太网时就启动了。
EthernetService的构造函数中会创建EthernetServiceImpl实例,EthernetServiceImpl中的setConfiguration会通知EthernetTracker更新网络配置。
<frameworks/base/opt/net/ethernet/java/com/android/server/ethernet/EthernetServiceImpl.java>
/**
* Set Ethernet configuration
*/
@Override
public void setConfiguration(String iface, IpConfiguration config) {
if (!mStarted.get()) {
Log.w(TAG, "System isn't ready enough to change ethernet configuration");
}
NetworkStack.checkNetworkStackPermission(mContext);
if (mTracker.isRestrictedInterface(iface)) {
enforceUseRestrictedNetworksPermission();
}
// TODO: this does not check proxy settings, gateways, etc.
// Fix this by making IpConfiguration a complete representation of static configuration.
mTracker.updateIpConfiguration(iface, new IpConfiguration(config));
}
- EthernetTracker的updateIpConfiguration()方法将配置内容保存到配置文件中,然后通过Handler异步更新指定网络接口的IP配置。
mConfigStore.write()方法将网络参数写入EthernetConfigStore中的常量ipconfigFile文件中“/misc/ethernet/ipconfig.txt”
<frameworks/base/opt/net/ethernet/java/com/android/server/ethernet/EthernetTracker.java>
void updateIpConfiguration(String iface, IpConfiguration ipConfiguration) {
if (DBG) {
Log.i(TAG, "updateIpConfiguration, iface: " + iface + ", cfg: " + ipConfiguration);
}
mConfigStore.write(iface, ipConfiguration);
mIpConfigurations.put(iface, ipConfiguration);
mHandler.post(() -> mFactory.updateIpConfiguration(iface, ipConfiguration));
}
- EthernetNetworkFactory的updateIpConfiguration会调用内部类NetworkInterfaceState的setIpConfig()方法。
<frameworks/base/opt/net/ethernet/java/com/android/server/ethernet/EthernetNetworkFactory.java>
void updateIpConfiguration(String iface, IpConfiguration ipConfiguration) {
NetworkInterfaceState network = mTrackingInterfaces.get(iface);
if (network != null) {
network.setIpConfig(ipConfiguration);
}
}
- NetworkInterfaceState的setIpConfig()方法将IpConfiguration内容保存到私有变量mIpConfig中然后重启当前网络。
在start()中,保存的mIpConfig会传入到provisionIpClient()中用作初始化配置网络接口参数。
<frameworks/base/opt/net/ethernet/java/com/android/server/ethernet/EthernetNetworkFactory.NetworkInterfaceState>
void setIpConfig(IpConfiguration ipConfig) {
if (Objects.equals(this.mIpConfig, ipConfig)) {
if (DBG) Log.d(TAG, "ipConfig have not changed,so ignore setIpConfig");
return;
}
this.mIpConfig = ipConfig;
if (mNetworkAgent != null) {
restart();
}
}
void restart(){
if (DBG) Log.d(TAG, "reconnecting Etherent");
stop();
start();
}
private void start() {
if (mIpClient != null) {
if (DBG) Log.d(TAG, "IpClient already started");
return;
}
if (DBG) {
Log.d(TAG, String.format("Starting Ethernet IpClient(%s)", name));
}
mIpClientCallback = new IpClientCallbacksImpl();
IpClientUtil.makeIpClient(mContext, name, mIpClientCallback);
mIpClientCallback.awaitIpClientStart();
if (sTcpBufferSizes == null) {
sTcpBufferSizes = mContext.getResources().getString(
com.android.internal.R.string.config_ethernet_tcp_buffers);
}
provisionIpClient(mIpClient, mIpConfig, sTcpBufferSizes);
}
2. 有线网络参数获取
2.1 获取方法
通过Android提供的系统服务接口ConnectivityManager和网络管理接口类LinkProperties获取以太网连接信息。
通过Java提供的网络接口类NetworkInterface 获取当前网络的详细信息,判断是否为以太网连接。
通过Android提供的以太网管理类EthernetManager获取有线网络的详细信息。
2.2 流程分析
2.2.1 通过ConnectivityManager和LinkProperties获取
ConnectivityManager是Android提供的一个系统服务接口,用于管理网络连接,而其中的getLinkProperties()方法是一个Android API方法,用于获取指定网络链路属性。
1. APK获取网络参数
(1)首先获取 ConnectivityManager 的实例。
(2)通过getAllNetworks获取设备支持的所有网络类型(以太网、无线、移动数据)的链接状态信息。
(3)遍历获取已连接网络的链路属性,通过linkPropertie中的方法获取网络信息。
(4)这里要获取有线网络的信息,所以筛选出以太网,通过getLinkAddresses()获取IP地址和子网掩码、通过getRoutes()获取网关、通过getDnsServers()获取DNS。
(5)for循环会遍历到所有的信息,可以通过instanceof筛选IPV4网络或者IPV6网络。
<MainActivity.java>
public static void getEthernetNetworkInfo1(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm != null) {
Log.d(TAG, "network size = " + cm.getAllNetworks().length);
for (Network network : cm.getAllNetworks()) {
LinkProperties linkProperties = cm.getLinkProperties(network);
if (linkProperties != null) {
String interfaceName = linkProperties.getInterfaceName();
if (interfaceName != null && interfaceName.startsWith("eth")) {
Log.d(TAG, "all Interface: " + interfaceName);
for (LinkAddress linkAddress : linkProperties.getLinkAddresses()) {
Log.d(TAG, "all IP Address: " + linkAddress.getAddress().getHostAddress());
Log.d(TAG, "all Subnet Mask: " + linkAddress.getNetworkPrefixLength());
}
for (RouteInfo route : linkProperties.getRoutes()) {
if (route.getDestination().getAddress().isAnyLocalAddress()) {
Log.d(TAG, "all Default Gateway: " + route.getGateway().getHostAddress());
}
}
for (InetAddress dns : linkProperties.getDnsServers()) {
Log.d(TAG, "all DNS Server: " + dns.getHostAddress());
}
}
}
}
}
}
2. LinkProperties底层调用
(1)ConnectivityManager向系统服务发送请求获取连接网络的链路属性linkProperties。
<frameworks/base/core/java/android/net/ConnectivityManager.java>
public LinkProperties getLinkProperties(int networkType) {
try {
return mService.getLinkPropertiesForType(networkType);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
(2)ConnectivityService接收到请求后从NetworkAgentInfo中查询指定网络链路属性,NetworkAgentInfo中的网络链路属性是在网络连接时赋值的,这里不做分析。
<frameworks/base/services/core/java/com/android/server/ethernet/ConnectivityService.java>
public LinkProperties getLinkPropertiesForType(int networkType) {
enforceAccessPermission();
NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
final LinkProperties lp = getLinkProperties(nai);
if (lp == null) return null;
return linkPropertiesRestrictedForCallerPermissions(
lp, Binder.getCallingPid(), Binder.getCallingUid());
}
2.2.2 通过NetworkInterface获取
NetworkInterface是Java提供的一个java.net 包,用于获取和操作网络接口的详细信息。
1. APK获取网络参数。
(1)获取NetworkInterface实例,筛选出以太网络,然后使用NetworkInterface中的getInetAddresses()获取IP地址、用getInterfaceAddresses获取子网掩码。
(2)NetworkInterface 未提供获取网关和DNS的功能,需要通过其他接口获取。
<MainActivity.java>
public static void getEthernetNetworkInfo() {
try {
// 获取所有网络接口
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface networkInterface = interfaces.nextElement();
// 确定以太网接口(通常是 eth0 或者类似的名称)
if (networkInterface.getName().equalsIgnoreCase("eth0")) {
// 获取接口的所有 IP 地址
Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
while (inetAddresses.hasMoreElements()) {
InetAddress inetAddress = inetAddresses.nextElement();
if (!inetAddress.isLoopbackAddress()) {
// 输出有效的 IP 地址
Log.d("Network", "IP Address: " + inetAddress.getHostAddress());
}
}
// 获取子网掩码
NetworkInterface temp = NetworkInterface.getByName("eth0");
if (temp != null) {
List<InterfaceAddress> interfaceAddresses = temp.getInterfaceAddresses();
for (InterfaceAddress interfaceAddress : interfaceAddresses) {
// 输出子网掩码
Log.d("Network", "Subnet Mask: " + interfaceAddress.getNetworkPrefixLength());
}
}
// 获取默认网关
List<InetAddress> gateways = getDefaultGateways();
for (InetAddress gateway : gateways) {
Log.d("Network", "Default Gateway: " + gateway.getHostAddress());
}
// 获取 DNS
List<InetAddress> dnsAddresses = getDnsAddresses();
for (InetAddress dns : dnsAddresses) {
Log.d("Network", "DNS: " + dns.getHostAddress());
}
}
}
} catch (Exception e) {
Log.e("Network", "Error getting network information", e);
}
}
// 获取默认网关的方法
private static List<InetAddress> getDefaultGateways() {
List<InetAddress> gateways = new ArrayList<>();
try {
// 获取默认网关地址
NetworkInterface networkInterface = NetworkInterface.getByName("eth0");
if (networkInterface != null) {
for (InterfaceAddress address : networkInterface.getInterfaceAddresses()) {
InetAddress gateway = address.getAddress();
gateways.add(gateway);
}
}
} catch (SocketException e) {
Log.e("Network", "Error getting gateways", e);
}
return gateways;
}
// 获取 DNS 服务器地址的方法
private static List<InetAddress> getDnsAddresses() {
List<InetAddress> dnsAddresses = new ArrayList<>();
try {
// 获取 DNS 信息,Android 提供的系统设置会包含 DNS 服务器地址
InetAddress dns = InetAddress.getByName("8.8.8.8"); // 使用 Google 公共 DNS 作为示例
dnsAddresses.add(dns);
} catch (UnknownHostException e) {
Log.e("Network", "Error getting DNS addresses", e);
}
return dnsAddresses;
}
2. NetworkInterface底层调用
(1)Java层通过调用networkInterface.getInetAddresses()获取网络IP地址,该方法会调用到JNI中的接口,然后调用到底层C/C++代码。
(2)JNI调用会进一步调用系统接口管理函数,如getifaddrs,用于获取网络接口信息。
(3)系统调用getifaddrs从内核中获取网络接口的配置信息。
(4)获取到的网络接口信息通过JNI返回到Java层,最终返回给调用者。
2.2.3 通过EthernetManager获取
EthernetManager时Android提供的隐藏系统API,用于管理设备以太网连接,通常不直接对普通开发者开放,其中的功能接口包括 启用和禁用以太网接口、获取连接状态、配置网络等。
1. APK获取网络参数。
(1)获取静态网络参数可以通过StaticIpConfiguration中获取
首先从上层网络管理类EthernetManager获取到指定网络的IpConfiguration,然后从IpConfiguration中获取静态网络配置类StaticIpConfiguration。
然后从StaticIpConfiguration获取需要的网络信息即可。
<MainActivity.java>
private void getStaticEthernetInfo(Context context, String iface) {
EthernetManager ethernetManager = (EthernetManager) context.getSystemService(Context.ETHERNET_SERVICE);
if (ethernetManager == null) {
Log.e(TAG, "getStaticEthernetInfo ethernetManager == null !");
return;
}
IpConfiguration ipConfiguration = ethernetManager.getConfiguration(iface);
if (ipConfiguration == null) {
Log.e(TAG, "getStaticEthernetInfo ipConfiguration == null !");
return;
}
StaticIpConfiguration staticIpConfiguration = ipConfiguration.getStaticIpConfiguration();
if (staticIpConfiguration != null && ipConfiguration.getIpAssignment() == IpConfiguration.IpAssignment.STATIC) {
// 获取IP地址
LinkAddress ipAddress = staticIpConfiguration.ipAddress;
try {
Log.d(TAG, iface + ", IP Address: " + ipAddress.getAddress().getHostAddress());
Log.d(TAG, iface + ", Subnet Mask: " + intToIPv4Mask(ipAddress.getNetworkPrefixLength()));
// 获取默认网关
InetAddress gateway = staticIpConfiguration.gateway;
Log.d(TAG, iface + ", Default Gateway: " + gateway.getHostAddress());
// 获取DNS服务器
for (InetAddress dns : staticIpConfiguration.dnsServers) {
Log.d(TAG, iface + ", DNS Server: " + dns.getHostAddress());
}
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "getStaticEthernetInfo error :" + e);
}
}
}
/*
* 子网掩码长度转IP
*/
private static String intToIPv4Mask(int prefixLength) {
long mask = ~((1L << (32 - prefixLength)) - 1);
return ((mask >> 24) & 0xFF) + "." +
((mask >> 16) & 0xFF) + "." +
((mask >> 8) & 0xFF) + "." +
(mask & 0xFF);
}
(2)获取动态DHCP网络参数可以直接从EthernetManager中获取
网络状态需要通过ipConfiguration.getIpAssignment()获取,其他参数直接从EthernetManager中获取即可。
<MainActivity.java>
private void getDhcpEthernetInfo(Context context, String iface){
EthernetManager ethernetManager = (EthernetManager) context.getSystemService(Context.ETHERNET_SERVICE);
if (ethernetManager == null) {
Log.e(TAG, "getDhcpEthernetInfo ethernetManager == null !");
return;
}
IpConfiguration ipConfiguration = ethernetManager.getConfiguration(iface);
if (ipConfiguration != null) {
Log.d(TAG, iface + ",getDhcp: " + (ipConfiguration.getIpAssignment() == IpConfiguration.IpAssignment.DHCP));
}
// BSP可以直接从EthernetManager中获取有线网络信息
try {
Log.d(TAG, iface + ",getIpAddress: " + ethernetManager.getIpAddress(iface));
Log.d(TAG, iface + ",getNetmask: " + ethernetManager.getNetmask(iface));
Log.d(TAG, iface + ",getGateway: " + ethernetManager.getGateway(iface));
Log.d(TAG, iface + ",getDns: " + ethernetManager.getDns(iface));
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "getDhcpEthernetInfo error :" + e);
}
}
2. EthernetManager源码分析
(1)以EthernetManager.getIpAddress()为例,调用该方法后EthernetManager会向系统服务层发送获取IP地址的请求。
<frameworks/base/core/java/android/net/EthernetManager.java>
public String getIpAddress(String iface) {
try {
return mService.getIpAddress(iface);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
(2)服务层接收到请求后会在EthernetServiceImpl的getConfiguration()中处理请求。
<frameworks/base/opt/net/ethernet/java/com/android/server/ethernet/EthernetServiceImpl.java>
public IpConfiguration getConfiguration(String iface) {
enforceAccessPermission();
if (mTracker.isRestrictedInterface(iface)) {
enforceUseRestrictedNetworksPermission();
}
return new IpConfiguration(mTracker.getIpConfiguration(iface));
}
(3)然后调用EthernetTracker中的getIpConfiguration()返回指定网络的IP地址。
其中mIpConfigurations是一个HashMap,在启动网络的start()中就已经通过EthernetConfigStore将配置文件中的参数读出到mIpConfigurations中了,所以get的时候可以直接将结果返回。
<frameworks/base/opt/net/ethernet/java/com/android/server/ethernet/EthernetTracker.java>
IpConfiguration getIpConfiguration(String iface) {
return mIpConfigurations.get(iface);
}