Merge "ethernet: replace netd link observer with NetlinkMonitor"

This commit is contained in:
Patrick Rohr
2022-08-12 15:55:44 +00:00
committed by Gerrit Code Review
3 changed files with 80 additions and 88 deletions

View File

@@ -43,15 +43,21 @@ import android.os.Handler;
import android.os.RemoteCallbackList; import android.os.RemoteCallbackList;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceSpecificException; import android.os.ServiceSpecificException;
import android.system.OsConstants;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.util.Log; import android.util.Log;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.IndentingPrintWriter;
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
import com.android.net.module.util.NetdUtils; import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.PermissionUtils; import com.android.net.module.util.PermissionUtils;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.ip.NetlinkMonitor;
import com.android.net.module.util.netlink.NetlinkConstants;
import com.android.net.module.util.netlink.NetlinkMessage;
import com.android.net.module.util.netlink.RtNetlinkLinkMessage;
import com.android.net.module.util.netlink.StructIfinfoMsg;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.net.InetAddress; import java.net.InetAddress;
@@ -86,6 +92,9 @@ public class EthernetTracker {
private static final String TEST_IFACE_REGEXP = TEST_TAP_PREFIX + "\\d+"; private static final String TEST_IFACE_REGEXP = TEST_TAP_PREFIX + "\\d+";
// TODO: consider using SharedLog consistently across ethernet service.
private static final SharedLog sLog = new SharedLog(TAG);
/** /**
* Interface names we track. This is a product-dependent regular expression. * Interface names we track. This is a product-dependent regular expression.
* Use isValidEthernetInterface to check if a interface name is a valid ethernet interface (this * Use isValidEthernetInterface to check if a interface name is a valid ethernet interface (this
@@ -110,6 +119,7 @@ public class EthernetTracker {
private final Handler mHandler; private final Handler mHandler;
private final EthernetNetworkFactory mFactory; private final EthernetNetworkFactory mFactory;
private final EthernetConfigStore mConfigStore; private final EthernetConfigStore mConfigStore;
private final NetlinkMonitor mNetlinkMonitor;
private final Dependencies mDeps; private final Dependencies mDeps;
private final RemoteCallbackList<IEthernetServiceListener> mListeners = private final RemoteCallbackList<IEthernetServiceListener> mListeners =
@@ -148,6 +158,69 @@ public class EthernetTracker {
} }
} }
private class EthernetNetlinkMonitor extends NetlinkMonitor {
EthernetNetlinkMonitor(Handler handler) {
super(handler, sLog, EthernetNetlinkMonitor.class.getSimpleName(),
OsConstants.NETLINK_ROUTE, NetlinkConstants.RTMGRP_LINK);
}
private void onNewLink(String ifname, boolean linkUp) {
if (!mFactory.hasInterface(ifname) && !ifname.equals(mDefaultInterface)) {
Log.i(TAG, "onInterfaceAdded, iface: " + ifname);
maybeTrackInterface(ifname);
}
Log.i(TAG, "interfaceLinkStateChanged, iface: " + ifname + ", up: " + linkUp);
updateInterfaceState(ifname, linkUp);
}
private void onDelLink(String ifname) {
Log.i(TAG, "onInterfaceRemoved, iface: " + ifname);
stopTrackingInterface(ifname);
}
private void processRtNetlinkLinkMessage(RtNetlinkLinkMessage msg) {
final StructIfinfoMsg ifinfomsg = msg.getIfinfoHeader();
// check if the message is valid
if (ifinfomsg.family != OsConstants.AF_UNSPEC) return;
// ignore messages for the loopback interface
if ((ifinfomsg.flags & OsConstants.IFF_LOOPBACK) != 0) return;
// check if the received message applies to an ethernet interface.
final String ifname = msg.getInterfaceName();
if (!isValidEthernetInterface(ifname)) return;
switch (msg.getHeader().nlmsg_type) {
case NetlinkConstants.RTM_NEWLINK:
final boolean linkUp = (ifinfomsg.flags & NetlinkConstants.IFF_LOWER_UP) != 0;
onNewLink(ifname, linkUp);
break;
case NetlinkConstants.RTM_DELLINK:
onDelLink(ifname);
break;
default:
Log.e(TAG, "Unknown rtnetlink link msg type: " + msg);
break;
}
}
// Note: processNetlinkMessage is called on the handler thread.
@Override
protected void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) {
// ignore all updates when ethernet is disabled.
if (mEthernetState == ETHERNET_STATE_DISABLED) return;
if (nlMsg instanceof RtNetlinkLinkMessage) {
processRtNetlinkLinkMessage((RtNetlinkLinkMessage) nlMsg);
} else {
Log.e(TAG, "Unknown netlink message: " + nlMsg);
}
}
}
EthernetTracker(@NonNull final Context context, @NonNull final Handler handler, EthernetTracker(@NonNull final Context context, @NonNull final Handler handler,
@NonNull final EthernetNetworkFactory factory, @NonNull final INetd netd) { @NonNull final EthernetNetworkFactory factory, @NonNull final INetd netd) {
this(context, handler, factory, netd, new Dependencies()); this(context, handler, factory, netd, new Dependencies());
@@ -173,6 +246,7 @@ public class EthernetTracker {
} }
mConfigStore = new EthernetConfigStore(); mConfigStore = new EthernetConfigStore();
mNetlinkMonitor = new EthernetNetlinkMonitor(mHandler);
} }
void start() { void start() {
@@ -186,14 +260,10 @@ public class EthernetTracker {
mIpConfigurations.put(configs.keyAt(i), configs.valueAt(i)); mIpConfigurations.put(configs.keyAt(i), configs.valueAt(i));
} }
try { mHandler.post(() -> {
PermissionUtils.enforceNetworkStackPermission(mContext); mNetlinkMonitor.start();
mNetd.registerUnsolicitedEventListener(new InterfaceObserver()); trackAvailableInterfaces();
} catch (RemoteException | ServiceSpecificException e) { });
Log.e(TAG, "Could not register InterfaceObserver " + e);
}
mHandler.post(this::trackAvailableInterfaces);
} }
void updateIpConfiguration(String iface, IpConfiguration ipConfiguration) { void updateIpConfiguration(String iface, IpConfiguration ipConfiguration) {
@@ -617,43 +687,6 @@ public class EthernetTracker {
} }
} }
@VisibleForTesting
class InterfaceObserver extends BaseNetdUnsolicitedEventListener {
@Override
public void onInterfaceLinkStateChanged(String iface, boolean up) {
if (DBG) {
Log.i(TAG, "interfaceLinkStateChanged, iface: " + iface + ", up: " + up);
}
mHandler.post(() -> {
if (mEthernetState == ETHERNET_STATE_DISABLED) return;
updateInterfaceState(iface, up);
});
}
@Override
public void onInterfaceAdded(String iface) {
if (DBG) {
Log.i(TAG, "onInterfaceAdded, iface: " + iface);
}
mHandler.post(() -> {
if (mEthernetState == ETHERNET_STATE_DISABLED) return;
maybeTrackInterface(iface);
});
}
@Override
public void onInterfaceRemoved(String iface) {
if (DBG) {
Log.i(TAG, "onInterfaceRemoved, iface: " + iface);
}
mHandler.post(() -> {
if (mEthernetState == ETHERNET_STATE_DISABLED) return;
stopTrackingInterface(iface);
});
}
}
private static class ListenerInfo { private static class ListenerInfo {
boolean canUseRestrictedNetworks = false; boolean canUseRestrictedNetworks = false;

View File

@@ -162,6 +162,7 @@ java_library {
"modules-utils-shell-command-handler", "modules-utils-shell-command-handler",
"net-utils-device-common", "net-utils-device-common",
"net-utils-device-common-bpf", "net-utils-device-common-bpf",
"net-utils-device-common-ip",
"net-utils-device-common-netlink", "net-utils-device-common-netlink",
"net-utils-services-common", "net-utils-services-common",
"netd-client", "netd-client",

View File

@@ -30,10 +30,8 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset; import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -64,7 +62,6 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
@@ -476,43 +473,4 @@ public class EthernetTrackerTest {
verify(listener).onInterfaceStateChanged(eq(testIface), eq(EthernetManager.STATE_LINK_UP), verify(listener).onInterfaceStateChanged(eq(testIface), eq(EthernetManager.STATE_LINK_UP),
anyInt(), any()); anyInt(), any());
} }
@Test
public void testListenEthernetStateChange_unsolicitedEventListener() throws Exception {
when(mNetd.interfaceGetList()).thenReturn(new String[] {});
doReturn(new String[] {}).when(mFactory).getAvailableInterfaces(anyBoolean());
tracker.setIncludeTestInterfaces(true);
tracker.start();
final ArgumentCaptor<EthernetTracker.InterfaceObserver> captor =
ArgumentCaptor.forClass(EthernetTracker.InterfaceObserver.class);
verify(mNetd, timeout(TIMEOUT_MS)).registerUnsolicitedEventListener(captor.capture());
final EthernetTracker.InterfaceObserver observer = captor.getValue();
tracker.setEthernetEnabled(false);
waitForIdle();
reset(mFactory);
reset(mNetd);
final String testIface = "testtap1";
observer.onInterfaceAdded(testIface);
verify(mFactory, never()).addInterface(eq(testIface), anyString(), any(), any());
observer.onInterfaceRemoved(testIface);
verify(mFactory, never()).removeInterface(eq(testIface));
final String testHwAddr = "11:22:33:44:55:66";
final InterfaceConfigurationParcel testIfaceParce =
createMockedIfaceParcel(testIface, testHwAddr);
when(mNetd.interfaceGetList()).thenReturn(new String[] {testIface});
when(mNetd.interfaceGetCfg(eq(testIface))).thenReturn(testIfaceParce);
doReturn(new String[] {testIface}).when(mFactory).getAvailableInterfaces(anyBoolean());
tracker.setEthernetEnabled(true);
waitForIdle();
reset(mFactory);
final String testIface2 = "testtap2";
observer.onInterfaceRemoved(testIface2);
verify(mFactory, timeout(TIMEOUT_MS)).removeInterface(eq(testIface2));
}
} }