前言
因需求,急需用于跑安卓自动化接口类任务的虚拟机。 满足以下条件:
- 1、开启root权限
- 2、x86架构兼容arm
- 3、实现可静态配置ip
- 4、最好是iso镜像文件
- 5、系统版本最低为9
- 6、系统性能好流畅
- 7、adb connect自动信任连接
1、调研开源可用的虚拟机
1)google开源docker虚拟机
优点:
- ①支持安卓11
- ②系统较为流畅
- ③官方开源
缺点:
- ①不能root
- ②x86_64架构不兼容arm
- ③需要安装docker使用
2)三方开源docker虚拟机
优点:
- ①可root
- ②系统较为流畅
- ③支持安卓9
缺点:
- ①x86_64架构不兼容arm
- ②需要安装docker使用
3)Android Studio自带模拟器
优点:
- ①安卓9和安卓11模拟器可用
- ②可root
- ③x86架构兼容arm
- ④系统较为流畅
缺点:
- ①依赖ide
4)Androidx86开源镜像
优点:
- ①现成iso镜像
- ②可root
- ③系统较为流畅
- ④支持安卓9
- ⑤x86架构兼容arm
缺点:
- ①仅支持wifi连接模式
总结
根据以上调研,Androidx86开源镜像最贴近需求,决定尝试拉取源码更改支持静态ip模式编译iso文件即满足需求。
2、Androidx86 9.0编译
编译系统环境:Ubuntu18.04 jdk1.8和python3.8等安装自动百度即可
1)官方拉取源码构建教程
2)修改源码支持静态ip
参考文档:
源码修改
仅展示android9.0中与android8.1设置静态ip主要逻辑有区别的三个类,其余修改逻辑参考文档②。
[1]frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetNetworkFactory.java
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.ethernet;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import android.content.Context;
import android.net.IpConfiguration;
import android.net.IpConfiguration.IpAssignment;
import android.net.IpConfiguration.ProxySettings;
import android.net.LinkProperties;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
import android.net.NetworkFactory;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
import android.net.StringNetworkSpecifier;
import android.net.ip.IpClient;
import android.net.ip.IpClient.ProvisioningConfiguration;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.util.IndentingPrintWriter;
import java.io.FileDescriptor;
import java.util.concurrent.ConcurrentHashMap;
/**
* {@link NetworkFactory} that represents Ethernet networks.
*
* This class reports a static network score of 70 when it is tracking an interface and that
* interface's link is up, and a score of 0 otherwise.
*/
public class EthernetNetworkFactory extends NetworkFactory {
private final static String TAG = EthernetNetworkFactory.class.getSimpleName();
final static boolean DBG = true;
private final static int NETWORK_SCORE = 70;
private static final String NETWORK_TYPE = "Ethernet";
private final ConcurrentHashMap<String, NetworkInterfaceState> mTrackingInterfaces =
new ConcurrentHashMap<>();
private final Handler mHandler;
private final Context mContext;
public EthernetNetworkFactory(Handler handler, Context context, NetworkCapabilities filter) {
super(handler.getLooper(), context, NETWORK_TYPE, filter);
mHandler = handler;
mContext = context;
setScoreFilter(NETWORK_SCORE);
}
@Override
public boolean acceptRequest(NetworkRequest request, int score) {
if (DBG) {
Log.d(TAG, "acceptRequest, request: " + request + ", score: " + score);
}
return networkForRequest(request) != null;
}
@Override
protected void needNetworkFor(NetworkRequest networkRequest, int score) {
NetworkInterfaceState network = networkForRequest(networkRequest);
if (network == null) {
Log.e(TAG, "needNetworkFor, failed to get a network for " + networkRequest);
return;
}
if (++network.refCount == 1) {
network.start();
}
}
@Override
protected void releaseNetworkFor(NetworkRequest networkRequest) {
NetworkInterfaceState network = networkForRequest(networkRequest);
if (network == null) {
Log.e(TAG, "needNetworkFor, failed to get a network for " + networkRequest);
return;
}
if (--network.refCount == 1) {
network.stop();
}
}
/**
* Returns an array of available interface names. The array is sorted: unrestricted interfaces
* goes first, then sorted by name.
*/
String[] getAvailableInterfaces(boolean includeRestricted) {
return mTrackingInterfaces.values()
.stream()
.filter(iface -> !iface.isRestricted() || includeRestricted)
.sorted((iface1, iface2) -> {
int r = Boolean.compare(iface1.isRestricted(), iface2.isRestricted());
return r == 0 ? iface1.name.compareTo(iface2.name) : r;
})
.map(iface -> iface.name)
.toArray(String[]::new);
}
NetworkInterfaceState addInterface(String ifaceName, String hwAddress, NetworkCapabilities capabilities,
IpConfiguration ipConfiguration) {
if (mTrackingInterfaces.containsKey(ifaceName)) {
Log.e(TAG, "Interface with name " + ifaceName + " already exists.");
return null;
}
if (DBG) {
Log.d(TAG, "addInterface, iface: " + ifaceName + ", capabilities: " + capabilities);
}
NetworkInterfaceState iface = new NetworkInterfaceState(
ifaceName, hwAddress, mHandler, mContext, capabilities);
iface.setIpConfig(ipConfiguration);
mTrackingInterfaces.put(ifaceName, iface);
updateCapabilityFilter();
return iface;
}
private void updateCapabilityFilter() {
NetworkCapabilities capabilitiesFilter = new NetworkCapabilities();
capabilitiesFilter.clearAll();
for (NetworkInterfaceState iface: mTrackingInterfaces.values()) {
capabilitiesFilter.combineCapabilities(iface.mCapabilities);
}
if (DBG) Log.d(TAG, "updateCapabilityFilter: " + capabilitiesFilter);
setCapabilityFilter(capabilitiesFilter);
}
void removeInterface(String interfaceName) {
NetworkInterfaceState iface = mTrackingInterfaces.remove(interfaceName);
if (iface != null) {
iface.stop();
}
updateCapabilityFilter();
}
/** Returns true if state has been modified */
boolean updateInterfaceLinkState(String ifaceName, boolean up) {
if (!mTrackingInterfaces.containsKey(ifaceName)) {
return false;
}
if (DBG) {
Log.d(TAG, "updateInterfaceLinkState, iface: " + ifaceName + ", up: " + up);
}
NetworkInterfaceState iface = mTrackingInterfaces.get(ifaceName);
return iface.updateLinkState(up);
}
boolean hasInterface(String interfacName) {
return mTrackingInterfaces.containsKey(interfacName);
}
void updateIpConfiguration(String iface, IpConfiguration ipConfiguration) {
NetworkInterfaceState network = mTrackingInterfaces.get(iface);
if (network != null) {
network.setIpConfig(ipConfiguration);
}
}
private NetworkInterfaceState networkForRequest(NetworkRequest request) {
String requestedIface = null;
NetworkSpecifier specifier = request.networkCapabilities.getNetworkSpecifier();
if (specifier instanceof StringNetworkSpecifier) {
requestedIface = ((StringNetworkSpecifier) specifier).specifier;
}
NetworkInterfaceState network = null;
if (!TextUtils.isEmpty(requestedIface)) {
NetworkInterfaceState n = mTrackingInterfaces.get(requestedIface);
if (n != null && n.statisified(request.networkCapabilities)) {
network = n;
}
} else {
for (NetworkInterfaceState n : mTrackingInterfaces.values()) {
if (n.statisified(request.networkCapabilities)) {
network = n;
break;
}
}
}
if (DBG) {
Log.i(TAG, "networkForRequest, request: " + request + ", network: " + network);
}
return network;
}
public static class NetworkInterfaceState {
final String name;
private final String mHwAddress;
private final NetworkCapabilities mCapabilities;
private final Handler mHandler;
private final Context mContext;
private final NetworkInfo mNetworkInfo;
private static String sTcpBufferSizes = null; // Lazy initialized.
private boolean mLinkUp;
public LinkProperties mLinkProperties = new LinkProperties();
private IpClient mIpClient;
private NetworkAgent mNetworkAgent;
private IpConfiguration mIpConfig;
long refCount = 0;
private final IpClient.Callback mIpClientCallback = new IpClient.Callback() {
@Override
public void onProvisioningSuccess(LinkProperties newLp) {
mHandler.post(() -> onIpLayerStarted(newLp));
}
@Override
public void onProvisioningFailure(LinkProperties newLp) {
mHandler.post(() -> onIpLayerStopped(newLp));
}
@Override
public void onLinkPropertiesChange(LinkProperties newLp) {
mHandler.post(() -> updateLinkProperties(newLp));
}
};
NetworkInterfaceState(String ifaceName, String hwAddress, Handler handler, Context context,
NetworkCapabilities capabilities) {
name = ifaceName;
mCapabilities = capabilities;
mHandler = handler;
mContext = context;
mHwAddress = hwAddress;
mNetworkInfo = new NetworkInfo(TYPE_ETHERNET, 0, NETWORK_TYPE, "");
mNetworkInfo.setExtraInfo(mHwAddress);
mNetworkInfo.setIsAvailable(true);
}
void setIpConfig(IpConfiguration ipConfig) {
this.mIpConfig = ipConfig;
}
LinkProperties getLinkProperties(){
return this.mLinkProperties;
}
boolean statisified(NetworkCapabilities requestedCapabilities) {
return requestedCapabilities.satisfiedByNetworkCapabilities(mCapabilities);
}
boolean isRestricted() {
return mCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
}
private void start() {
if (mIpClient != null) {
if (DBG) Log.d(TAG, "IpClient already started");
return;
}
if (DBG) {
Log.d(TAG, String.format("starting IpClient(%s): mNetworkInfo=%s", name,
mNetworkInfo));
}
mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, mHwAddress);
mIpClient = new IpClient(mContext, name, mIpClientCallback);
if (sTcpBufferSizes == null) {
sTcpBufferSizes = mContext.getResources().getString(
com.android.internal.R.string.config_ethernet_tcp_buffers);
}
provisionIpClient(mIpClient, mIpConfig, sTcpBufferSizes);
}
void onIpLayerStarted(LinkProperties linkProperties) {
if (mNetworkAgent != null) {
Log.e(TAG, "Already have a NetworkAgent - aborting new request");
stop();
return;
}
mLinkProperties = linkProperties;
mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, mHwAddress);
mNetworkInfo.setIsAvailable(true);
// Create our NetworkAgent.
mNetworkAgent = new NetworkAgent(mHandler.getLooper(), mContext,
NETWORK_TYPE, mNetworkInfo, mCapabilities, mLinkProperties,
NETWORK_SCORE) {
public void unwanted() {
if (this == mNetworkAgent) {
stop();
} else if (mNetworkAgent != null) {
Log.d(TAG, "Ignoring unwanted as we have a more modern " +
"instance");
} // Otherwise, we've already called stop.
}
};
}
void onIpLayerStopped(LinkProperties linkProperties) {
// This cannot happen due to provisioning timeout, because our timeout is 0. It can only
// happen if we're provisioned and we lose provisioning.
stop();
start();
}
void updateLinkProperties(LinkProperties linkProperties) {
mLinkProperties = linkProperties;
if (mNetworkAgent != null) {
mNetworkAgent.sendLinkProperties(linkProperties);
}
}
/** Returns true if state has been modified */
boolean updateLinkState(boolean up) {
if (mLinkUp == up) return false;
mLinkUp = up;
stop();
if (up) {
start();
}
return true;
}
void stop() {
if (mIpClient != null) {
mIpClient.shutdown();
mIpClient.awaitShutdown();
mIpClient = null;
}
// ConnectivityService will only forget our NetworkAgent if we send it a NetworkInfo object
// with a state of DISCONNECTED or SUSPENDED. So we can't simply clear our NetworkInfo here:
// that sets the state to IDLE, and ConnectivityService will still think we're connected.
//
mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddress);
if (mNetworkAgent != null) {
updateAgent();
mNetworkAgent = null;
}
clear();
}
private void updateAgent() {
if (mNetworkAgent == null) return;
if (DBG) {
Log.i(TAG, "Updating mNetworkAgent with: " +
mCapabilities + ", " +
mNetworkInfo + ", " +
mLinkProperties);
}
mNetworkAgent.sendNetworkCapabilities(mCapabilities);
mNetworkAgent.sendNetworkInfo(mNetworkInfo);
mNetworkAgent.sendLinkProperties(mLinkProperties);
// never set the network score below 0.
mNetworkAgent.sendNetworkScore(mLinkUp? NETWORK_SCORE : 0);
}
private void clear() {
mLinkProperties.clear();
mNetworkInfo.setDetailedState(DetailedState.IDLE, null, null);
mNetworkInfo.setIsAvailable(false);
}
private static void provisionIpClient(IpClient ipClient, IpConfiguration config,
String tcpBufferSizes) {
if (config.getProxySettings() == ProxySettings.STATIC ||
config.getProxySettings() == ProxySettings.PAC) {
ipClient.setHttpProxy(config.getHttpProxy());
}
if (!TextUtils.isEmpty(tcpBufferSizes)) {
ipClient.setTcpBufferSizes(tcpBufferSizes);
}
final ProvisioningConfiguration provisioningConfiguration;
if (config.getIpAssignment() == IpAssignment.STATIC) {
provisioningConfiguration = IpClient.buildProvisioningConfiguration()
.withStaticConfiguration(config.getStaticIpConfiguration())
.build();
} else {
provisioningConfiguration = IpClient.buildProvisioningConfiguration()
.withProvisioningTimeoutMs(0)
.build();
}
ipClient.startProvisioning(provisioningConfiguration);
}
@Override
public String toString() {
return getClass().getSimpleName() + "{ "
+ "iface: " + name + ", "
+ "up: " + mLinkUp + ", "
+ "hwAddress: " + mHwAddress + ", "
+ "networkInfo: " + mNetworkInfo + ", "
+ "networkAgent: " + mNetworkAgent + ", "
+ "ipClient: " + mIpClient + ","
+ "linkProperties: " + mLinkProperties
+ "}";
}
}
void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
super.dump(fd, pw, args);
pw.println(getClass().getSimpleName());
pw.println("Tracking interfaces:");
pw.increaseIndent();
for (String iface: mTrackingInterfaces.keySet()) {
NetworkInterfaceState ifaceState = mTrackingInterfaces.get(iface);
pw.println(iface + ":" + ifaceState);
pw.increaseIndent();
final IpClient ipClient = ifaceState.mIpClient;
if (ipClient != null) {
ipClient.dump(fd, pw, args);
} else {
pw.println("IpClient is null");
}
pw.decreaseIndent();
}
pw.decreaseIndent();
}
}
[2]frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetServiceImpl.java
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.ethernet;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.IEthernetManager;
import android.net.IEthernetServiceListener;
import android.net.IpConfiguration;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.RemoteException;
import android.util.Log;
import android.util.PrintWriterPrinter;
import com.android.internal.util.IndentingPrintWriter;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* EthernetServiceImpl handles remote Ethernet operation requests by implementing
* the IEthernetManager interface.
*/
public class EthernetServiceImpl extends IEthernetManager.Stub {
private static final String TAG = "EthernetServiceImpl";
private final Context mContext;
private final AtomicBoolean mStarted = new AtomicBoolean(false);
private Handler mHandler;
private EthernetTracker mTracker;
public EthernetServiceImpl(Context context) {
mContext = context;
}
private void enforceAccessPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.ACCESS_NETWORK_STATE,
"EthernetService");
}
private void enforceConnectivityInternalPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CONNECTIVITY_INTERNAL,
"ConnectivityService");
}
private void enforceUseRestrictedNetworksPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS,
"ConnectivityService");
}
private boolean checkUseRestrictedNetworksPermission() {
return mContext.checkCallingOrSelfPermission(
android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS)
== PackageManager.PERMISSION_GRANTED;
}
public void start() {
Log.i(TAG, "Starting Ethernet service");
HandlerThread handlerThread = new HandlerThread("EthernetServiceThread");
handlerThread.start();
mHandler = new Handler(handlerThread.getLooper());
mTracker = new EthernetTracker(mContext, mHandler);
mTracker.start();
mStarted.set(true);
}
@Override
public String[] getAvailableInterfaces() throws RemoteException {
return mTracker.getInterfaces(checkUseRestrictedNetworksPermission());
}
/**
* Get Ethernet configuration
* @return the Ethernet Configuration, contained in {@link IpConfiguration}.
*/
@Override
public IpConfiguration getConfiguration(String iface) {
enforceAccessPermission();
if (mTracker.isRestrictedInterface(iface)) {
enforceUseRestrictedNetworksPermission();
}
return new IpConfiguration(mTracker.getIpConfiguration(iface));
}
/**
* 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");
}
enforceConnectivityInternalPermission();
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));
}
/**
* Indicates whether given interface is available.
*/
@Override
public boolean isAvailable(String iface) {
enforceAccessPermission();
if (mTracker.isRestrictedInterface(iface)) {
enforceUseRestrictedNetworksPermission();
}
return mTracker.isTrackingInterface(iface);
}
/**
* Adds a listener.
* @param listener A {@link IEthernetServiceListener} to add.
*/
public void addListener(IEthernetServiceListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener must not be null");
}
enforceAccessPermission();
mTracker.addListener(listener, checkUseRestrictedNetworksPermission());
}
/**
* Removes a listener.
* @param listener A {@link IEthernetServiceListener} to remove.
*/
public void removeListener(IEthernetServiceListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener must not be null");
}
enforceAccessPermission();
mTracker.removeListener(listener);
}
@Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
pw.println("Permission Denial: can't dump EthernetService from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid());
return;
}
pw.println("Current Ethernet state: ");
pw.increaseIndent();
mTracker.dump(fd, pw, args);
pw.decreaseIndent();
pw.println("Handler:");
pw.increaseIndent();
mHandler.dump(new PrintWriterPrinter(pw), "EthernetServiceImpl");
pw.decreaseIndent();
}
@Override
public int getEthernetCarrierState(String ifname) {
enforceAccessPermission();
return mTracker.getEthernetCarrierState(ifname);
}
@Override
public String getEthernetMacAddress(String ifname) {
enforceAccessPermission();
return mTracker.getEthernetMacAddress(ifname);
}
@Override
public int getEthernetConnectState() {
enforceAccessPermission();
return mTracker.mEthernetCurrentState;
}
@Override
public String getIpAddress(String ifname) {
enforceAccessPermission();
return mTracker.getIpAddress(ifname);
}
@Override
public String getNetmask(String ifname) {
enforceAccessPermission();
return mTracker.getNetmask(ifname);
}
@Override
public String getGateway(String ifname) {
enforceAccessPermission();
return mTracker.getGateway(ifname);
}
@Override
public String getDns(String ifname) {
enforceAccessPermission();
return mTracker.getDns(ifname);
}
@Override
public String dumpCurrentState(int state) {
enforceAccessPermission();
return mTracker.dumpEthCurrentState(state);
}
@Override
public void reconnect(String iface) {
enforceAccessPermission();
mTracker.reconnect(iface);
}
@Override
public void disconnect(String iface) {
enforceAccessPermission();
mTracker.disconnect(iface);
}
}
[3]frameworks/opt/net/ethernet/java/com/android/server/ethernet/EthernetTracker.java
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.ethernet;
import android.annotation.Nullable;
import android.content.Context;
import android.net.IEthernetServiceListener;
import android.net.InterfaceConfiguration;
import android.net.IpConfiguration;
import android.net.IpConfiguration.IpAssignment;
import android.net.IpConfiguration.ProxySettings;
import android.net.LinkAddress;
import android.net.NetworkCapabilities;
import android.net.StaticIpConfiguration;
import android.os.Handler;
import android.os.IBinder;
import android.os.INetworkManagementService;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.net.BaseNetworkObserver;
import java.io.FileDescriptor;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import android.net.EthernetManager;
import android.net.RouteInfo;
import android.net.LinkAddress;
import android.net.NetworkUtils;
import android.content.Intent;
import android.os.UserHandle;
import android.provider.Settings;
import java.io.FileDescriptor;
import java.io.File;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.Exception;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
/**
* Tracks Ethernet interfaces and manages interface configurations.
*
* <p>Interfaces may have different {@link android.net.NetworkCapabilities}. This mapping is defined
* in {@code config_ethernet_interfaces}. Notably, some interfaces could be marked as restricted by
* not specifying {@link android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED} flag.
* Interfaces could have associated {@link android.net.IpConfiguration}.
* Ethernet Interfaces may be present at boot time or appear after boot (e.g., for Ethernet adapters
* connected over USB). This class supports multiple interfaces. When an interface appears on the
* system (or is present at boot time) this class will start tracking it and bring it up. Only
* interfaces whose names match the {@code config_ethernet_iface_regex} regular expression are
* tracked.
*
* <p>All public or package private methods must be thread-safe unless stated otherwise.
*/
final class EthernetTracker {
private final static String TAG = EthernetTracker.class.getSimpleName();
private final static boolean DBG = EthernetNetworkFactory.DBG;
/** Product-dependent regular expression of interface names we track. */
private final String mIfaceMatch;
/** Mapping between {iface name | mac address} -> {NetworkCapabilities} */
private final ConcurrentHashMap<String, NetworkCapabilities> mNetworkCapabilities =
new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, IpConfiguration> mIpConfigurations =
new ConcurrentHashMap<>();
private final INetworkManagementService mNMService;
private final Handler mHandler;
private final EthernetNetworkFactory mFactory;
private final EthernetConfigStore mConfigStore;
private final RemoteCallbackList<IEthernetServiceListener> mListeners =
new RemoteCallbackList<>();
private volatile IpConfiguration mIpConfigForDefaultInterface;
private boolean mReconnecting = false;
private String mIface = "eth0";
/** For static IP configuration */
private EthernetManager mEthernetManager;
public int mEthernetCurrentState = EthernetManager.ETHER_STATE_DISCONNECTED;
private EthernetNetworkFactory.NetworkInterfaceState mInterfaceSate;
private Context mContext;
EthernetTracker(Context context, Handler handler) {
mHandler = handler;
mContext = context;
// The services we use.
IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
mNMService = INetworkManagementService.Stub.asInterface(b);
mEthernetManager = (EthernetManager) context.getSystemService(Context.ETHERNET_SERVICE);
// Interface match regex.
mIfaceMatch = context.getResources().getString(
com.android.internal.R.string.config_ethernet_iface_regex);
// Read default Ethernet interface configuration from resources
final String[] interfaceConfigs = context.getResources().getStringArray(
com.android.internal.R.array.config_ethernet_interfaces);
for (String strConfig : interfaceConfigs) {
parseEthernetConfig(strConfig);
}
mConfigStore = new EthernetConfigStore();
NetworkCapabilities nc = createNetworkCapabilities(true /* clear default capabilities */);
mFactory = new EthernetNetworkFactory(handler, context, nc);
mFactory.register();
}
void start() {
mConfigStore.read();
// Default interface is just the first one we want to track.
mIpConfigForDefaultInterface = mConfigStore.getIpConfigurationForDefaultInterface();
final ArrayMap<String, IpConfiguration> configs = mConfigStore.getIpConfigurations();
for (int i = 0; i < configs.size(); i++) {
mIpConfigurations.put(configs.keyAt(i), configs.valueAt(i));
}
try {
mNMService.registerObserver(new InterfaceObserver());
} catch (RemoteException e) {
Log.e(TAG, "Could not register InterfaceObserver " + e);
}
mHandler.post(this::trackAvailableInterfaces);
}
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));
}
IpConfiguration getIpConfiguration(String iface) {
return mIpConfigurations.get(iface);
}
boolean isTrackingInterface(String iface) {
return mFactory.hasInterface(iface);
}
String[] getInterfaces(boolean includeRestricted) {
return mFactory.getAvailableInterfaces(includeRestricted);
}
/**
* Returns true if given interface was configured as restricted (doesn't have
* NET_CAPABILITY_NOT_RESTRICTED) capability. Otherwise, returns false.
*/
boolean isRestrictedInterface(String iface) {
final NetworkCapabilities nc = mNetworkCapabilities.get(iface);
return nc != null && !nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
}
void addListener(IEthernetServiceListener listener, boolean canUseRestrictedNetworks) {
mListeners.register(listener, new ListenerInfo(canUseRestrictedNetworks));
}
void removeListener(IEthernetServiceListener listener) {
mListeners.unregister(listener);
}
private void removeInterface(String iface) {
mFactory.removeInterface(iface);
}
private void addInterface(String iface) {
InterfaceConfiguration config = null;
// Bring up the interface so we get link status indications.
try {
mNMService.setInterfaceUp(iface);
config = mNMService.getInterfaceConfig(iface);
} catch (RemoteException | IllegalStateException e) {
// Either the system is crashing or the interface has disappeared. Just ignore the
// error; we haven't modified any state because we only do that if our calls succeed.
Log.e(TAG, "Error upping interface " + iface, e);
}
if (config == null) {
Log.e(TAG, "Null interface config for " + iface + ". Bailing out.");
return;
}
final String hwAddress = config.getHardwareAddress();
NetworkCapabilities nc = mNetworkCapabilities.get(iface);
if (nc == null) {
// Try to resolve using mac address
nc = mNetworkCapabilities.get(hwAddress);
if (nc == null) {
nc = createDefaultNetworkCapabilities();
}
}
IpConfiguration ipConfiguration = mIpConfigurations.get(iface);
if (ipConfiguration == null) {
ipConfiguration = createDefaultIpConfiguration();
}
Log.d(TAG, "Started tracking interface " + iface);
EthernetNetworkFactory.NetworkInterfaceState interfaceSate = mFactory.addInterface(iface, hwAddress, nc, ipConfiguration);
if (interfaceSate !=null) {
mInterfaceSate = interfaceSate;
}
// Note: if the interface already has link (e.g., if we crashed and got
// restarted while it was running), we need to fake a link up notification so we
// start configuring it.
if (config.hasFlag("running")) {
updateInterfaceState(iface, true);
}
}
private void updateInterfaceState(String iface, boolean up) {
boolean modified = mFactory.updateInterfaceLinkState(iface, up);
if (modified) {
boolean restricted = isRestrictedInterface(iface);
int n = mListeners.beginBroadcast();
for (int i = 0; i < n; i++) {
try {
if (restricted) {
ListenerInfo listenerInfo = (ListenerInfo) mListeners.getBroadcastCookie(i);
if (!listenerInfo.canUseRestrictedNetworks) {
continue;
}
}
mListeners.getBroadcastItem(i).onAvailabilityChanged(iface, up);
} catch (RemoteException e) {
// Do nothing here.
}
}
mListeners.finishBroadcast();
}
if (!up) {
sendEthernetStateChangedBroadcast(EthernetManager.ETHER_STATE_DISCONNECTING);
}
}
private void maybeTrackInterface(String iface) {
if (DBG) Log.i(TAG, "maybeTrackInterface " + iface);
// If we don't already track this interface, and if this interface matches
// our regex, start tracking it.
if (!iface.matches(mIfaceMatch) || mFactory.hasInterface(iface)) {
return;
}
if (mIpConfigForDefaultInterface != null) {
updateIpConfiguration(iface, mIpConfigForDefaultInterface);
mIpConfigForDefaultInterface = null;
}
addInterface(iface);
}
private void trackAvailableInterfaces() {
try {
final String[] ifaces = mNMService.listInterfaces();
for (String iface : ifaces) {
maybeTrackInterface(iface);
}
} catch (RemoteException | IllegalStateException e) {
Log.e(TAG, "Could not get list of interfaces " + e);
}
}
private class InterfaceObserver extends BaseNetworkObserver {
@Override
public void interfaceLinkStateChanged(String iface, boolean up) {
if (DBG) {
Log.i(TAG, "interfaceLinkStateChanged, iface: " + iface + ", up: " + up);
}
if (up) {
sendEthernetStateChangedBroadcast(EthernetManager.ETHER_STATE_CONNECTED);
} else {
sendEthernetStateChangedBroadcast(EthernetManager.ETHER_STATE_DISCONNECTED);
}
mHandler.post(() -> updateInterfaceState(iface, up));
}
@Override
public void interfaceAdded(String iface) {
sendEthernetStateChangedBroadcast(EthernetManager.ETHER_STATE_CONNECTING);
mHandler.post(() -> maybeTrackInterface(iface));
}
@Override
public void interfaceRemoved(String iface) {
sendEthernetStateChangedBroadcast(EthernetManager.ETHER_STATE_DISCONNECTING);
mHandler.post(() -> removeInterface(iface));
}
}
private static class ListenerInfo {
boolean canUseRestrictedNetworks = false;
ListenerInfo(boolean canUseRestrictedNetworks) {
this.canUseRestrictedNetworks = canUseRestrictedNetworks;
}
}
private void parseEthernetConfig(String configString) {
String[] tokens = configString.split(";");
String name = tokens[0];
String capabilities = tokens.length > 1 ? tokens[1] : null;
NetworkCapabilities nc = createNetworkCapabilities(
!TextUtils.isEmpty(capabilities) /* clear default capabilities */, capabilities);
mNetworkCapabilities.put(name, nc);
if (tokens.length > 2 && !TextUtils.isEmpty(tokens[2])) {
IpConfiguration ipConfig = parseStaticIpConfiguration(tokens[2]);
mIpConfigurations.put(name, ipConfig);
}
}
private static NetworkCapabilities createDefaultNetworkCapabilities() {
NetworkCapabilities nc = createNetworkCapabilities(false /* clear default capabilities */);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
return nc;
}
private static NetworkCapabilities createNetworkCapabilities(boolean clearDefaultCapabilities) {
return createNetworkCapabilities(clearDefaultCapabilities, null);
}
private static NetworkCapabilities createNetworkCapabilities(
boolean clearDefaultCapabilities, @Nullable String commaSeparatedCapabilities) {
NetworkCapabilities nc = new NetworkCapabilities();
if (clearDefaultCapabilities) {
nc.clearAll(); // Remove default capabilities.
}
nc.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET);
nc.setLinkUpstreamBandwidthKbps(100 * 1000);
nc.setLinkDownstreamBandwidthKbps(100 * 1000);
if (!TextUtils.isEmpty(commaSeparatedCapabilities)) {
for (String strNetworkCapability : commaSeparatedCapabilities.split(",")) {
if (!TextUtils.isEmpty(strNetworkCapability)) {
nc.addCapability(Integer.valueOf(strNetworkCapability));
}
}
}
return nc;
}
/**
* Parses static IP configuration.
*
* @param staticIpConfig represents static IP configuration in the following format: {@code
* ip=<ip-address/mask> gateway=<ip-address> dns=<comma-sep-ip-addresses>
* domains=<comma-sep-domains>}
*/
@VisibleForTesting
static IpConfiguration parseStaticIpConfiguration(String staticIpConfig) {
StaticIpConfiguration ipConfig = new StaticIpConfiguration();
for (String keyValueAsString : staticIpConfig.trim().split(" ")) {
if (TextUtils.isEmpty(keyValueAsString)) continue;
String[] pair = keyValueAsString.split("=");
if (pair.length != 2) {
throw new IllegalArgumentException("Unexpected token: " + keyValueAsString
+ " in " + staticIpConfig);
}
String key = pair[0];
String value = pair[1];
switch (key) {
case "ip":
ipConfig.ipAddress = new LinkAddress(value);
break;
case "domains":
ipConfig.domains = value;
break;
case "gateway":
ipConfig.gateway = InetAddress.parseNumericAddress(value);
break;
case "dns": {
ArrayList<InetAddress> dnsAddresses = new ArrayList<>();
for (String address: value.split(",")) {
dnsAddresses.add(InetAddress.parseNumericAddress(address));
}
ipConfig.dnsServers.addAll(dnsAddresses);
break;
}
default : {
throw new IllegalArgumentException("Unexpected key: " + key
+ " in " + staticIpConfig);
}
}
}
return new IpConfiguration(IpAssignment.STATIC, ProxySettings.NONE, ipConfig, null);
}
private static IpConfiguration createDefaultIpConfiguration() {
return new IpConfiguration(IpAssignment.DHCP, ProxySettings.NONE, null, null);
}
private void postAndWaitForRunnable(Runnable r) {
mHandler.runWithScissors(r, 2000L /* timeout */);
}
void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
postAndWaitForRunnable(() -> {
pw.println(getClass().getSimpleName());
pw.println("Ethernet interface name filter: " + mIfaceMatch);
pw.println("Listeners: " + mListeners.getRegisteredCallbackCount());
pw.println("IP Configurations:");
pw.increaseIndent();
for (String iface : mIpConfigurations.keySet()) {
pw.println(iface + ": " + mIpConfigurations.get(iface));
}
pw.decreaseIndent();
pw.println();
pw.println("mEthernetCurrentState: " + dumpEthCurrentState(mEthernetCurrentState));
pw.println("Network Capabilities:");
pw.increaseIndent();
for (String iface : mNetworkCapabilities.keySet()) {
pw.println(iface + ": " + mNetworkCapabilities.get(iface));
}
pw.decreaseIndent();
pw.println();
mFactory.dump(fd, pw, args);
});
}
public int getEthernetCarrierState(String ifname) {
if(ifname != "") {
try {
File file = new File("/sys/class/net/" + ifname + "/carrier");
String carrier = ReadFromFile(file);
return Integer.parseInt(carrier);
} catch(Exception e) {
e.printStackTrace();
return 0;
}
} else {
return 0;
}
}
public String getEthernetMacAddress(String ifname) {
if(ifname != "") {
try {
File file = new File("/sys/class/net/" + ifname + "/address");
String address = ReadFromFile(file);
return address;
} catch(Exception e) {
e.printStackTrace();
return "";
}
} else {
return "";
}
}
public String getIpAddress(String ifname) {
IpConfiguration config = mEthernetManager.getConfiguration(ifname);
if (config.getIpAssignment() == IpAssignment.STATIC) {
return config.getStaticIpConfiguration().ipAddress.getAddress().getHostAddress();
} else {
for (LinkAddress l : mInterfaceSate.getLinkProperties().getLinkAddresses()) {
InetAddress source = l.getAddress();
//Log.d(TAG, "getIpAddress: " source.getHostAddress());
if (source instanceof Inet4Address) {
return source.getHostAddress();
}
}
}
return "";
}
private String prefix2netmask(int prefix) {
// convert prefix to netmask
if (true) {
int mask = 0xFFFFFFFF << (32 - prefix);
//Log.d(TAG, "mask = " mask " prefix = " prefix);
return ((mask>>>24) & 0xff) + "." + ((mask>>>16) & 0xff) + "." + ((mask>>>8) & 0xff) + "." + ((mask) & 0xff);
} else {
return NetworkUtils.intToInetAddress(NetworkUtils.prefixLengthToNetmaskInt(prefix)).getHostName();
}
}
public String getNetmask(String ifname) {
IpConfiguration config = mEthernetManager.getConfiguration(ifname);
if (config.getIpAssignment() == IpAssignment.STATIC) {
return prefix2netmask(config.getStaticIpConfiguration().ipAddress.getPrefixLength());
} else {
for (LinkAddress l : mInterfaceSate.getLinkProperties().getLinkAddresses()) {
InetAddress source = l.getAddress();
if (source instanceof Inet4Address) {
return prefix2netmask(l.getPrefixLength());
}
}
}
return "";
}
public String getGateway(String ifname) {
IpConfiguration config = mEthernetManager.getConfiguration(ifname);
if (config.getIpAssignment() == IpAssignment.STATIC) {
return config.getStaticIpConfiguration().gateway.getHostAddress();
} else {
for (RouteInfo route : mInterfaceSate.getLinkProperties().getRoutes()) {
if (route.hasGateway()) {
InetAddress gateway = route.getGateway();
if (route.isIPv4Default()) {
return gateway.getHostAddress();
}
}
}
}
return "";
}
/*
* return dns format: "8.8.8.8,4.4.4.4"
*/
public String getDns(String ifname) {
String dns = "";
IpConfiguration config = mEthernetManager.getConfiguration(ifname);
if (config.getIpAssignment() == IpAssignment.STATIC) {
for (InetAddress nameserver : config.getStaticIpConfiguration().dnsServers) {
dns = nameserver.getHostAddress() + ",";
}
} else {
for (InetAddress nameserver : mInterfaceSate.getLinkProperties().getDnsServers()) {
dns = nameserver.getHostAddress() + ",";
}
}
return dns;
}
public String dumpEthCurrentState(int curState) {
if (curState == EthernetManager.ETHER_STATE_DISCONNECTED)
return "DISCONNECTED";
else if (curState == EthernetManager.ETHER_STATE_CONNECTING)
return "CONNECTING";
else if (curState == EthernetManager.ETHER_STATE_CONNECTED)
return "CONNECTED";
else if (curState == EthernetManager.ETHER_STATE_DISCONNECTING)
return "DISCONNECTING";
return "DISCONNECTED";
}
// first disconnect, then connect
public void reconnect(String iface) {
Log.d(TAG, "reconnect:");
mReconnecting = true;
if (iface == null)
iface = mIface;
updateInterfaceState(iface, false);
Log.d(TAG, "first disconnect");
try {
Thread.sleep(1000);
} catch (InterruptedException ignore) {
}
updateInterfaceState(iface, true);
mReconnecting = false;
Log.d(TAG, "then connect");
}
public void disconnect(String iface) {
mReconnecting = true;
if (iface == null)
iface = mIface;
updateInterfaceState(iface, false);
mReconnecting = false;
Log.d(TAG, "disconnect:");
}
private String ReadFromFile(File file) {
if((file != null) && file.exists()) {
try {
FileInputStream fin= new FileInputStream(file);
BufferedReader reader= new BufferedReader(new InputStreamReader(fin));
String flag = reader.readLine();
fin.close();
return flag;
} catch(Exception e) {
e.printStackTrace();
}
}
return null;
}
private void sendEthernetStateChangedBroadcast(int curState) {
if (mEthernetCurrentState == curState)
return;
Log.d(TAG, "sendEthernetStateChangedBroadcast: curState = " + dumpEthCurrentState(curState));
mEthernetCurrentState = curState;
final Intent intent = new Intent(EthernetManager.ETHERNET_STATE_CHANGED_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
intent.putExtra(EthernetManager.EXTRA_ETHERNET_STATE, curState);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
}