summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRemi NGUYEN VAN <reminv@google.com>2019-11-22 06:50:22 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2019-11-22 06:50:22 +0000
commitd53b4053df403e61ad8b6349561731e1ca51a4ab (patch)
treef8c07b5fd8e6ffb2fdf77f4c2cebb2f2bb5cb9d7
parent94b74699a2e90fff77859c25bdfe575f0f1f7172 (diff)
parent1fc930c51705a56d2b62320f99fa976a60c8382a (diff)
Merge "Move shared packages to NetworkStack project"
-rw-r--r--Android.bp2
-rw-r--r--common/moduleutils/src/android/net/ip/InterfaceController.java193
-rw-r--r--common/moduleutils/src/android/net/shared/InitialConfiguration.java240
-rw-r--r--common/moduleutils/src/android/net/shared/IpConfigurationParcelableUtil.java79
-rw-r--r--common/moduleutils/src/android/net/shared/LinkPropertiesParcelableUtil.java47
-rw-r--r--common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java65
-rw-r--r--common/moduleutils/src/android/net/shared/ParcelableUtil.java63
-rw-r--r--common/moduleutils/src/android/net/shared/PrivateDnsConfig.java90
-rw-r--r--common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java312
-rw-r--r--common/moduleutils/src/android/net/util/InterfaceParams.java97
-rw-r--r--common/netlinkclient/Android.bp27
-rw-r--r--common/netlinkclient/src/android/net/netlink/ConntrackMessage.java109
-rw-r--r--common/netlinkclient/src/android/net/netlink/InetDiagMessage.java221
-rw-r--r--common/netlinkclient/src/android/net/netlink/NetlinkConstants.java129
-rw-r--r--common/netlinkclient/src/android/net/netlink/NetlinkErrorMessage.java58
-rw-r--r--common/netlinkclient/src/android/net/netlink/NetlinkMessage.java92
-rw-r--r--common/netlinkclient/src/android/net/netlink/NetlinkSocket.java158
-rw-r--r--common/netlinkclient/src/android/net/netlink/RtNetlinkNeighborMessage.java245
-rw-r--r--common/netlinkclient/src/android/net/netlink/StructInetDiagMsg.java59
-rw-r--r--common/netlinkclient/src/android/net/netlink/StructInetDiagReqV2.java97
-rw-r--r--common/netlinkclient/src/android/net/netlink/StructInetDiagSockId.java86
-rw-r--r--common/netlinkclient/src/android/net/netlink/StructNdMsg.java165
-rw-r--r--common/netlinkclient/src/android/net/netlink/StructNdaCacheInfo.java117
-rw-r--r--common/netlinkclient/src/android/net/netlink/StructNfGenMsg.java49
-rw-r--r--common/netlinkclient/src/android/net/netlink/StructNlAttr.java208
-rw-r--r--common/netlinkclient/src/android/net/netlink/StructNlMsgErr.java68
-rw-r--r--common/netlinkclient/src/android/net/netlink/StructNlMsgHdr.java139
-rw-r--r--tests/unit/src/android/net/ip/InterfaceControllerTest.java91
-rw-r--r--tests/unit/src/android/net/netlink/ConntrackMessageTest.java130
-rw-r--r--tests/unit/src/android/net/netlink/InetDiagSocketTest.java448
-rw-r--r--tests/unit/src/android/net/netlink/NetlinkErrorMessageTest.java94
-rw-r--r--tests/unit/src/android/net/netlink/NetlinkSocketTest.java106
-rw-r--r--tests/unit/src/android/net/netlink/RtNetlinkNeighborMessageTest.java270
-rw-r--r--tests/unit/src/android/net/shared/Inet4AddressUtilsTest.java210
-rw-r--r--tests/unit/src/android/net/shared/InitialConfigurationTest.java87
-rw-r--r--tests/unit/src/android/net/shared/IpConfigurationParcelableUtilTest.java115
-rw-r--r--tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java139
-rw-r--r--tests/unit/src/android/net/util/InterfaceParamsTest.java54
38 files changed, 4958 insertions, 1 deletions
diff --git a/Android.bp b/Android.bp
index 2d57c35..e026fee 100644
--- a/Android.bp
+++ b/Android.bp
@@ -81,12 +81,12 @@ java_defaults {
name: "NetworkStackAndroidLibraryDefaults",
srcs: [
":framework-networkstack-shared-srcs",
- ":services-networkstack-shared-srcs",
":statslog-networkstack-java-gen",
],
static_libs: [
"androidx.annotation_annotation",
"netd_aidl_interface-V2-java",
+ "netlink-client",
"networkstack-client",
"datastallprotosnano",
"networkstackprotosnano",
diff --git a/common/moduleutils/src/android/net/ip/InterfaceController.java b/common/moduleutils/src/android/net/ip/InterfaceController.java
new file mode 100644
index 0000000..f32d143
--- /dev/null
+++ b/common/moduleutils/src/android/net/ip/InterfaceController.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2019 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 android.net.ip;
+
+import android.net.INetd;
+import android.net.InterfaceConfigurationParcel;
+import android.net.LinkAddress;
+import android.net.util.SharedLog;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.system.OsConstants;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+
+
+/**
+ * Encapsulates the multiple IP configuration operations performed on an interface.
+ *
+ * TODO: refactor/eliminate the redundant ways to set and clear addresses.
+ *
+ * @hide
+ */
+public class InterfaceController {
+ private final static boolean DBG = false;
+
+ private final String mIfName;
+ private final INetd mNetd;
+ private final SharedLog mLog;
+
+ public InterfaceController(String ifname, INetd netd, SharedLog log) {
+ mIfName = ifname;
+ mNetd = netd;
+ mLog = log;
+ }
+
+ private boolean setInterfaceAddress(LinkAddress addr) {
+ final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
+ ifConfig.ifName = mIfName;
+ ifConfig.ipv4Addr = addr.getAddress().getHostAddress();
+ ifConfig.prefixLength = addr.getPrefixLength();
+ ifConfig.hwAddr = "";
+ ifConfig.flags = new String[0];
+ try {
+ mNetd.interfaceSetCfg(ifConfig);
+ } catch (RemoteException | ServiceSpecificException e) {
+ logError("Setting IPv4 address to %s/%d failed: %s",
+ ifConfig.ipv4Addr, ifConfig.prefixLength, e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set the IPv4 address of the interface.
+ */
+ public boolean setIPv4Address(LinkAddress address) {
+ if (!(address.getAddress() instanceof Inet4Address)) {
+ return false;
+ }
+ return setInterfaceAddress(address);
+ }
+
+ /**
+ * Clear the IPv4Address of the interface.
+ */
+ public boolean clearIPv4Address() {
+ return setInterfaceAddress(new LinkAddress("0.0.0.0/0"));
+ }
+
+ private boolean setEnableIPv6(boolean enabled) {
+ try {
+ mNetd.interfaceSetEnableIPv6(mIfName, enabled);
+ } catch (RemoteException | ServiceSpecificException e) {
+ logError("%s IPv6 failed: %s", (enabled ? "enabling" : "disabling"), e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Enable IPv6 on the interface.
+ */
+ public boolean enableIPv6() {
+ return setEnableIPv6(true);
+ }
+
+ /**
+ * Disable IPv6 on the interface.
+ */
+ public boolean disableIPv6() {
+ return setEnableIPv6(false);
+ }
+
+ /**
+ * Enable or disable IPv6 privacy extensions on the interface.
+ * @param enabled Whether the extensions should be enabled.
+ */
+ public boolean setIPv6PrivacyExtensions(boolean enabled) {
+ try {
+ mNetd.interfaceSetIPv6PrivacyExtensions(mIfName, enabled);
+ } catch (RemoteException | ServiceSpecificException e) {
+ logError("error %s IPv6 privacy extensions: %s",
+ (enabled ? "enabling" : "disabling"), e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set IPv6 address generation mode on the interface.
+ *
+ * <p>IPv6 should be disabled before changing the mode.
+ */
+ public boolean setIPv6AddrGenModeIfSupported(int mode) {
+ try {
+ mNetd.setIPv6AddrGenMode(mIfName, mode);
+ } catch (RemoteException e) {
+ logError("Unable to set IPv6 addrgen mode: %s", e);
+ return false;
+ } catch (ServiceSpecificException e) {
+ if (e.errorCode != OsConstants.EOPNOTSUPP) {
+ logError("Unable to set IPv6 addrgen mode: %s", e);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Add an address to the interface.
+ */
+ public boolean addAddress(LinkAddress addr) {
+ return addAddress(addr.getAddress(), addr.getPrefixLength());
+ }
+
+ /**
+ * Add an address to the interface.
+ */
+ public boolean addAddress(InetAddress ip, int prefixLen) {
+ try {
+ mNetd.interfaceAddAddress(mIfName, ip.getHostAddress(), prefixLen);
+ } catch (ServiceSpecificException | RemoteException e) {
+ logError("failed to add %s/%d: %s", ip, prefixLen, e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Remove an address from the interface.
+ */
+ public boolean removeAddress(InetAddress ip, int prefixLen) {
+ try {
+ mNetd.interfaceDelAddress(mIfName, ip.getHostAddress(), prefixLen);
+ } catch (ServiceSpecificException | RemoteException e) {
+ logError("failed to remove %s/%d: %s", ip, prefixLen, e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Remove all addresses from the interface.
+ */
+ public boolean clearAllAddresses() {
+ try {
+ mNetd.interfaceClearAddrs(mIfName);
+ } catch (Exception e) {
+ logError("Failed to clear addresses: %s", e);
+ return false;
+ }
+ return true;
+ }
+
+ private void logError(String fmt, Object... args) {
+ mLog.e(String.format(fmt, args));
+ }
+}
diff --git a/common/moduleutils/src/android/net/shared/InitialConfiguration.java b/common/moduleutils/src/android/net/shared/InitialConfiguration.java
new file mode 100644
index 0000000..007c8ca
--- /dev/null
+++ b/common/moduleutils/src/android/net/shared/InitialConfiguration.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2019 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 android.net.shared;
+
+import static android.net.shared.ParcelableUtil.fromParcelableArray;
+import static android.net.shared.ParcelableUtil.toParcelableArray;
+import static android.text.TextUtils.join;
+
+import android.net.InetAddresses;
+import android.net.InitialConfigurationParcelable;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.RouteInfo;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/** @hide */
+public class InitialConfiguration {
+ public final Set<LinkAddress> ipAddresses = new HashSet<>();
+ public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>();
+ public final Set<InetAddress> dnsServers = new HashSet<>();
+
+ private static final int RFC6177_MIN_PREFIX_LENGTH = 48;
+ private static final int RFC7421_PREFIX_LENGTH = 64;
+
+ public static final InetAddress INET6_ANY = InetAddresses.parseNumericAddress("::");
+
+ /**
+ * Create a InitialConfiguration that is a copy of the specified configuration.
+ */
+ public static InitialConfiguration copy(InitialConfiguration config) {
+ if (config == null) {
+ return null;
+ }
+ InitialConfiguration configCopy = new InitialConfiguration();
+ configCopy.ipAddresses.addAll(config.ipAddresses);
+ configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes);
+ configCopy.dnsServers.addAll(config.dnsServers);
+ return configCopy;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s})",
+ join(", ", ipAddresses), join(", ", directlyConnectedRoutes),
+ join(", ", dnsServers));
+ }
+
+ /**
+ * Tests whether the contents of this IpConfiguration represent a valid configuration.
+ */
+ public boolean isValid() {
+ if (ipAddresses.isEmpty()) {
+ return false;
+ }
+
+ // For every IP address, there must be at least one prefix containing that address.
+ for (LinkAddress addr : ipAddresses) {
+ if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) {
+ return false;
+ }
+ }
+ // For every dns server, there must be at least one prefix containing that address.
+ for (InetAddress addr : dnsServers) {
+ if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) {
+ return false;
+ }
+ }
+ // All IPv6 LinkAddresses have an RFC7421-suitable prefix length
+ // (read: compliant with RFC4291#section2.5.4).
+ if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) {
+ return false;
+ }
+ // If directlyConnectedRoutes contains an IPv6 default route
+ // then ipAddresses MUST contain at least one non-ULA GUA.
+ if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute)
+ && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) {
+ return false;
+ }
+ // The prefix length of routes in directlyConnectedRoutes be within reasonable
+ // bounds for IPv6: /48-/64 just as we’d accept in RIOs.
+ if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) {
+ return false;
+ }
+ // There no more than one IPv4 address
+ if (ipAddresses.stream().filter(InitialConfiguration::isIPv4).count() > 1) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * @return true if the given list of addressess and routes satisfies provisioning for this
+ * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality
+ * because addresses and routes seen by Netlink will contain additional fields like flags,
+ * interfaces, and so on. If this InitialConfiguration has no IP address specified, the
+ * provisioning check always fails.
+ *
+ * If the given list of routes is null, only addresses are taken into considerations.
+ */
+ public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) {
+ if (ipAddresses.isEmpty()) {
+ return false;
+ }
+
+ for (LinkAddress addr : ipAddresses) {
+ if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) {
+ return false;
+ }
+ }
+
+ if (routes != null) {
+ for (IpPrefix prefix : directlyConnectedRoutes) {
+ if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Convert this configuration to a {@link InitialConfigurationParcelable}.
+ */
+ public InitialConfigurationParcelable toStableParcelable() {
+ final InitialConfigurationParcelable p = new InitialConfigurationParcelable();
+ p.ipAddresses = ipAddresses.toArray(new LinkAddress[0]);
+ p.directlyConnectedRoutes = directlyConnectedRoutes.toArray(new IpPrefix[0]);
+ p.dnsServers = toParcelableArray(
+ dnsServers, IpConfigurationParcelableUtil::parcelAddress, String.class);
+ return p;
+ }
+
+ /**
+ * Create an instance of {@link InitialConfiguration} based on the contents of the specified
+ * {@link InitialConfigurationParcelable}.
+ */
+ public static InitialConfiguration fromStableParcelable(InitialConfigurationParcelable p) {
+ if (p == null) return null;
+ final InitialConfiguration config = new InitialConfiguration();
+ config.ipAddresses.addAll(Arrays.asList(p.ipAddresses));
+ config.directlyConnectedRoutes.addAll(Arrays.asList(p.directlyConnectedRoutes));
+ config.dnsServers.addAll(
+ fromParcelableArray(p.dnsServers, IpConfigurationParcelableUtil::unparcelAddress));
+ return config;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof InitialConfiguration)) return false;
+ final InitialConfiguration other = (InitialConfiguration) obj;
+ return ipAddresses.equals(other.ipAddresses)
+ && directlyConnectedRoutes.equals(other.directlyConnectedRoutes)
+ && dnsServers.equals(other.dnsServers);
+ }
+
+ private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) {
+ return !route.hasGateway() && prefix.equals(route.getDestination());
+ }
+
+ private static boolean isPrefixLengthCompliant(LinkAddress addr) {
+ return isIPv4(addr) || isCompliantIPv6PrefixLength(addr.getPrefixLength());
+ }
+
+ private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
+ return isIPv4(prefix) || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
+ }
+
+ private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
+ return (RFC6177_MIN_PREFIX_LENGTH <= prefixLength)
+ && (prefixLength <= RFC7421_PREFIX_LENGTH);
+ }
+
+ private static boolean isIPv4(IpPrefix prefix) {
+ return prefix.getAddress() instanceof Inet4Address;
+ }
+
+ private static boolean isIPv4(LinkAddress addr) {
+ return addr.getAddress() instanceof Inet4Address;
+ }
+
+ private static boolean isIPv6DefaultRoute(IpPrefix prefix) {
+ return prefix.getAddress().equals(INET6_ANY);
+ }
+
+ private static boolean isIPv6GUA(LinkAddress addr) {
+ return addr.isIpv6() && addr.isGlobalPreferred();
+ }
+
+ // TODO: extract out into CollectionUtils.
+
+ /**
+ * Indicate whether any element of the specified iterable verifies the specified predicate.
+ */
+ public static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
+ for (T t : coll) {
+ if (fn.test(t)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Indicate whether all elements of the specified iterable verifies the specified predicate.
+ */
+ public static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
+ return !any(coll, not(fn));
+ }
+
+ /**
+ * Create a predicate that returns the opposite value of the specified predicate.
+ */
+ public static <T> Predicate<T> not(Predicate<T> fn) {
+ return (t) -> !fn.test(t);
+ }
+}
diff --git a/common/moduleutils/src/android/net/shared/IpConfigurationParcelableUtil.java b/common/moduleutils/src/android/net/shared/IpConfigurationParcelableUtil.java
new file mode 100644
index 0000000..172dc24
--- /dev/null
+++ b/common/moduleutils/src/android/net/shared/IpConfigurationParcelableUtil.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2019 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 android.net.shared;
+
+import android.annotation.Nullable;
+import android.net.DhcpResults;
+import android.net.DhcpResultsParcelable;
+import android.net.InetAddresses;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+
+/**
+ * Collection of utility methods to convert to and from stable AIDL parcelables for IpClient
+ * configuration classes.
+ * @hide
+ */
+public final class IpConfigurationParcelableUtil {
+ /**
+ * Convert DhcpResults to a DhcpResultsParcelable.
+ */
+ public static DhcpResultsParcelable toStableParcelable(@Nullable DhcpResults results) {
+ if (results == null) return null;
+ final DhcpResultsParcelable p = new DhcpResultsParcelable();
+ p.baseConfiguration = results.toStaticIpConfiguration();
+ p.leaseDuration = results.leaseDuration;
+ p.mtu = results.mtu;
+ p.serverAddress = parcelAddress(results.serverAddress);
+ p.vendorInfo = results.vendorInfo;
+ p.serverHostName = results.serverHostName;
+ return p;
+ }
+
+ /**
+ * Convert a DhcpResultsParcelable to DhcpResults.
+ */
+ public static DhcpResults fromStableParcelable(@Nullable DhcpResultsParcelable p) {
+ if (p == null) return null;
+ final DhcpResults results = new DhcpResults(p.baseConfiguration);
+ results.leaseDuration = p.leaseDuration;
+ results.mtu = p.mtu;
+ results.serverAddress = (Inet4Address) unparcelAddress(p.serverAddress);
+ results.vendorInfo = p.vendorInfo;
+ results.serverHostName = p.serverHostName;
+ return results;
+ }
+
+ /**
+ * Convert InetAddress to String.
+ * TODO: have an InetAddressParcelable
+ */
+ public static String parcelAddress(@Nullable InetAddress addr) {
+ if (addr == null) return null;
+ return addr.getHostAddress();
+ }
+
+ /**
+ * Convert String to InetAddress.
+ * TODO: have an InetAddressParcelable
+ */
+ public static InetAddress unparcelAddress(@Nullable String addr) {
+ if (addr == null) return null;
+ return InetAddresses.parseNumericAddress(addr);
+ }
+}
diff --git a/common/moduleutils/src/android/net/shared/LinkPropertiesParcelableUtil.java b/common/moduleutils/src/android/net/shared/LinkPropertiesParcelableUtil.java
new file mode 100644
index 0000000..1729da6
--- /dev/null
+++ b/common/moduleutils/src/android/net/shared/LinkPropertiesParcelableUtil.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 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 android.net.shared;
+
+import android.annotation.Nullable;
+import android.net.LinkProperties;
+import android.net.ProxyInfo;
+
+/**
+ * Collection of utility methods to convert to and from stable AIDL parcelables for LinkProperties
+ * and its attributes.
+ * @hide
+ */
+public final class LinkPropertiesParcelableUtil {
+ // Temporary methods to facilitate migrating clients away from LinkPropertiesParcelable
+ // TODO: remove the following methods after migrating clients.
+
+ /**
+ * @deprecated conversion to stable parcelable is no longer necessary.
+ */
+ @Deprecated
+ public static LinkProperties toStableParcelable(@Nullable LinkProperties lp) {
+ return lp;
+ }
+
+ /**
+ * @deprecated conversion to stable parcelable is no longer necessary.
+ */
+ @Deprecated
+ public static ProxyInfo toStableParcelable(@Nullable ProxyInfo info) {
+ return info;
+ }
+}
diff --git a/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java b/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java
new file mode 100644
index 0000000..8983d00
--- /dev/null
+++ b/common/moduleutils/src/android/net/shared/NetworkMonitorUtils.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2019 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 android.net.shared;
+
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+
+import android.net.NetworkCapabilities;
+
+/** @hide */
+public class NetworkMonitorUtils {
+
+ // Network conditions broadcast constants
+ public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
+ "android.net.conn.NETWORK_CONDITIONS_MEASURED";
+ public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type";
+ public static final String EXTRA_NETWORK_TYPE = "extra_network_type";
+ public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received";
+ public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal";
+ public static final String EXTRA_CELL_ID = "extra_cellid";
+ public static final String EXTRA_SSID = "extra_ssid";
+ public static final String EXTRA_BSSID = "extra_bssid";
+ /** real time since boot */
+ public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms";
+ public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms";
+ public static final String PERMISSION_ACCESS_NETWORK_CONDITIONS =
+ "android.permission.ACCESS_NETWORK_CONDITIONS";
+
+ /**
+ * Return whether validation is required for private DNS in strict mode.
+ * @param nc Network capabilities of the network to test.
+ */
+ public static boolean isPrivateDnsValidationRequired(NetworkCapabilities nc) {
+ // TODO: Consider requiring validation for DUN networks.
+ return nc != null
+ && nc.hasCapability(NET_CAPABILITY_INTERNET)
+ && nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ && nc.hasCapability(NET_CAPABILITY_TRUSTED);
+ }
+
+ /**
+ * Return whether validation is required for a network.
+ * @param nc Network capabilities of the network to test.
+ */
+ public static boolean isValidationRequired(NetworkCapabilities nc) {
+ // TODO: Consider requiring validation for DUN networks.
+ return isPrivateDnsValidationRequired(nc) && nc.hasCapability(NET_CAPABILITY_NOT_VPN);
+ }
+}
diff --git a/common/moduleutils/src/android/net/shared/ParcelableUtil.java b/common/moduleutils/src/android/net/shared/ParcelableUtil.java
new file mode 100644
index 0000000..3f40300
--- /dev/null
+++ b/common/moduleutils/src/android/net/shared/ParcelableUtil.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2019 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 android.net.shared;
+
+import android.annotation.NonNull;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.function.Function;
+
+/**
+ * Utility methods to help convert to/from stable parcelables.
+ * @hide
+ */
+public final class ParcelableUtil {
+ // Below methods could be implemented easily with streams, but streams are frowned upon in
+ // frameworks code.
+
+ /**
+ * Convert a list of BaseType items to an array of ParcelableType items using the specified
+ * converter function.
+ */
+ public static <ParcelableType, BaseType> ParcelableType[] toParcelableArray(
+ @NonNull Collection<BaseType> base,
+ @NonNull Function<BaseType, ParcelableType> conv,
+ @NonNull Class<ParcelableType> parcelClass) {
+ final ParcelableType[] out = (ParcelableType[]) Array.newInstance(parcelClass, base.size());
+ int i = 0;
+ for (BaseType b : base) {
+ out[i] = conv.apply(b);
+ i++;
+ }
+ return out;
+ }
+
+ /**
+ * Convert an array of ParcelableType items to a list of BaseType items using the specified
+ * converter function.
+ */
+ public static <ParcelableType, BaseType> ArrayList<BaseType> fromParcelableArray(
+ @NonNull ParcelableType[] parceled, @NonNull Function<ParcelableType, BaseType> conv) {
+ final ArrayList<BaseType> out = new ArrayList<>(parceled.length);
+ for (ParcelableType t : parceled) {
+ out.add(conv.apply(t));
+ }
+ return out;
+ }
+}
diff --git a/common/moduleutils/src/android/net/shared/PrivateDnsConfig.java b/common/moduleutils/src/android/net/shared/PrivateDnsConfig.java
new file mode 100644
index 0000000..61d62d3
--- /dev/null
+++ b/common/moduleutils/src/android/net/shared/PrivateDnsConfig.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 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 android.net.shared;
+
+import static android.net.shared.ParcelableUtil.fromParcelableArray;
+import static android.net.shared.ParcelableUtil.toParcelableArray;
+
+import android.net.PrivateDnsConfigParcel;
+import android.text.TextUtils;
+
+import java.net.InetAddress;
+import java.util.Arrays;
+
+/** @hide */
+public class PrivateDnsConfig {
+ public final boolean useTls;
+ public final String hostname;
+ public final InetAddress[] ips;
+
+ public PrivateDnsConfig() {
+ this(false);
+ }
+
+ public PrivateDnsConfig(boolean useTls) {
+ this.useTls = useTls;
+ this.hostname = "";
+ this.ips = new InetAddress[0];
+ }
+
+ public PrivateDnsConfig(String hostname, InetAddress[] ips) {
+ this.useTls = !TextUtils.isEmpty(hostname);
+ this.hostname = useTls ? hostname : "";
+ this.ips = (ips != null) ? ips : new InetAddress[0];
+ }
+
+ public PrivateDnsConfig(PrivateDnsConfig cfg) {
+ useTls = cfg.useTls;
+ hostname = cfg.hostname;
+ ips = cfg.ips;
+ }
+
+ /**
+ * Indicates whether this is a strict mode private DNS configuration.
+ */
+ public boolean inStrictMode() {
+ return useTls && !TextUtils.isEmpty(hostname);
+ }
+
+ @Override
+ public String toString() {
+ return PrivateDnsConfig.class.getSimpleName()
+ + "{" + useTls + ":" + hostname + "/" + Arrays.toString(ips) + "}";
+ }
+
+ /**
+ * Create a stable AIDL-compatible parcel from the current instance.
+ */
+ public PrivateDnsConfigParcel toParcel() {
+ final PrivateDnsConfigParcel parcel = new PrivateDnsConfigParcel();
+ parcel.hostname = hostname;
+ parcel.ips = toParcelableArray(
+ Arrays.asList(ips), IpConfigurationParcelableUtil::parcelAddress, String.class);
+
+ return parcel;
+ }
+
+ /**
+ * Build a configuration from a stable AIDL-compatible parcel.
+ */
+ public static PrivateDnsConfig fromParcel(PrivateDnsConfigParcel parcel) {
+ InetAddress[] ips = new InetAddress[parcel.ips.length];
+ ips = fromParcelableArray(parcel.ips, IpConfigurationParcelableUtil::unparcelAddress)
+ .toArray(ips);
+ return new PrivateDnsConfig(parcel.hostname, ips);
+ }
+}
diff --git a/common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java b/common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java
new file mode 100644
index 0000000..6f9c294
--- /dev/null
+++ b/common/moduleutils/src/android/net/shared/ProvisioningConfiguration.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2019 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 android.net.shared;
+
+import android.annotation.Nullable;
+import android.net.INetd;
+import android.net.Network;
+import android.net.ProvisioningConfigurationParcelable;
+import android.net.StaticIpConfiguration;
+import android.net.apf.ApfCapabilities;
+import android.net.ip.IIpClient;
+
+import java.util.Objects;
+import java.util.StringJoiner;
+
+/**
+ * This class encapsulates parameters to be passed to
+ * IpClient#startProvisioning(). A defensive copy is made by IpClient
+ * and the values specified herein are in force until IpClient#stop()
+ * is called.
+ *
+ * Example use:
+ *
+ * final ProvisioningConfiguration config =
+ * new ProvisioningConfiguration.Builder()
+ * .withPreDhcpAction()
+ * .withProvisioningTimeoutMs(36 * 1000)
+ * .build();
+ * mIpClient.startProvisioning(config.toStableParcelable());
+ * ...
+ * mIpClient.stop();
+ *
+ * The specified provisioning configuration will only be active until
+ * IIpClient#stop() is called. Future calls to IIpClient#startProvisioning()
+ * must specify the configuration again.
+ * @hide
+ */
+public class ProvisioningConfiguration {
+ // TODO: Delete this default timeout once those callers that care are
+ // fixed to pass in their preferred timeout.
+ //
+ // We pick 36 seconds so we can send DHCP requests at
+ //
+ // t=0, t=2, t=6, t=14, t=30
+ //
+ // allowing for 10% jitter.
+ private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
+
+ /**
+ * Builder to create a {@link ProvisioningConfiguration}.
+ */
+ public static class Builder {
+ protected ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
+
+ /**
+ * Specify that the configuration should not enable IPv4. It is enabled by default.
+ */
+ public Builder withoutIPv4() {
+ mConfig.mEnableIPv4 = false;
+ return this;
+ }
+
+ /**
+ * Specify that the configuration should not enable IPv6. It is enabled by default.
+ */
+ public Builder withoutIPv6() {
+ mConfig.mEnableIPv6 = false;
+ return this;
+ }
+
+ /**
+ * Specify that the configuration should not use a MultinetworkPolicyTracker. It is used
+ * by default.
+ */
+ public Builder withoutMultinetworkPolicyTracker() {
+ mConfig.mUsingMultinetworkPolicyTracker = false;
+ return this;
+ }
+
+ /**
+ * Specify that the configuration should not use a IpReachabilityMonitor. It is used by
+ * default.
+ */
+ public Builder withoutIpReachabilityMonitor() {
+ mConfig.mUsingIpReachabilityMonitor = false;
+ return this;
+ }
+
+ /**
+ * Identical to {@link #withPreDhcpAction(int)}, using a default timeout.
+ * @see #withPreDhcpAction(int)
+ */
+ public Builder withPreDhcpAction() {
+ mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS;
+ return this;
+ }
+
+ /**
+ * Specify that {@link IpClientCallbacks#onPreDhcpAction()} should be called. Clients must
+ * call {@link IIpClient#completedPreDhcpAction()} when the callback called. This behavior
+ * is disabled by default.
+ * @param dhcpActionTimeoutMs Timeout for clients to call completedPreDhcpAction().
+ */
+ public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
+ mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs;
+ return this;
+ }
+
+ /**
+ * Specify the initial provisioning configuration.
+ */
+ public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
+ mConfig.mInitialConfig = initialConfig;
+ return this;
+ }
+
+ /**
+ * Specify a static configuration for provisioning.
+ */
+ public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
+ mConfig.mStaticIpConfig = staticConfig;
+ return this;
+ }
+
+ /**
+ * Specify ApfCapabilities.
+ */
+ public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
+ mConfig.mApfCapabilities = apfCapabilities;
+ return this;
+ }
+
+ /**
+ * Specify the timeout to use for provisioning.
+ */
+ public Builder withProvisioningTimeoutMs(int timeoutMs) {
+ mConfig.mProvisioningTimeoutMs = timeoutMs;
+ return this;
+ }
+
+ /**
+ * Specify that IPv6 address generation should use a random MAC address.
+ */
+ public Builder withRandomMacAddress() {
+ mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
+ return this;
+ }
+
+ /**
+ * Specify that IPv6 address generation should use a stable MAC address.
+ */
+ public Builder withStableMacAddress() {
+ mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+ return this;
+ }
+
+ /**
+ * Specify the network to use for provisioning.
+ */
+ public Builder withNetwork(Network network) {
+ mConfig.mNetwork = network;
+ return this;
+ }
+
+ /**
+ * Specify the display name that the IpClient should use.
+ */
+ public Builder withDisplayName(String displayName) {
+ mConfig.mDisplayName = displayName;
+ return this;
+ }
+
+ /**
+ * Build the configuration using previously specified parameters.
+ */
+ public ProvisioningConfiguration build() {
+ return new ProvisioningConfiguration(mConfig);
+ }
+ }
+
+ public boolean mEnableIPv4 = true;
+ public boolean mEnableIPv6 = true;
+ public boolean mUsingMultinetworkPolicyTracker = true;
+ public boolean mUsingIpReachabilityMonitor = true;
+ public int mRequestedPreDhcpActionMs;
+ public InitialConfiguration mInitialConfig;
+ public StaticIpConfiguration mStaticIpConfig;
+ public ApfCapabilities mApfCapabilities;
+ public int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
+ public int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+ public Network mNetwork = null;
+ public String mDisplayName = null;
+
+ public ProvisioningConfiguration() {} // used by Builder
+
+ public ProvisioningConfiguration(ProvisioningConfiguration other) {
+ mEnableIPv4 = other.mEnableIPv4;
+ mEnableIPv6 = other.mEnableIPv6;
+ mUsingMultinetworkPolicyTracker = other.mUsingMultinetworkPolicyTracker;
+ mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
+ mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
+ mInitialConfig = InitialConfiguration.copy(other.mInitialConfig);
+ mStaticIpConfig = other.mStaticIpConfig == null
+ ? null
+ : new StaticIpConfiguration(other.mStaticIpConfig);
+ mApfCapabilities = other.mApfCapabilities;
+ mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
+ mIPv6AddrGenMode = other.mIPv6AddrGenMode;
+ mNetwork = other.mNetwork;
+ mDisplayName = other.mDisplayName;
+ }
+
+ /**
+ * Create a ProvisioningConfigurationParcelable from this ProvisioningConfiguration.
+ */
+ public ProvisioningConfigurationParcelable toStableParcelable() {
+ final ProvisioningConfigurationParcelable p = new ProvisioningConfigurationParcelable();
+ p.enableIPv4 = mEnableIPv4;
+ p.enableIPv6 = mEnableIPv6;
+ p.usingMultinetworkPolicyTracker = mUsingMultinetworkPolicyTracker;
+ p.usingIpReachabilityMonitor = mUsingIpReachabilityMonitor;
+ p.requestedPreDhcpActionMs = mRequestedPreDhcpActionMs;
+ p.initialConfig = mInitialConfig == null ? null : mInitialConfig.toStableParcelable();
+ p.staticIpConfig = mStaticIpConfig == null
+ ? null
+ : new StaticIpConfiguration(mStaticIpConfig);
+ p.apfCapabilities = mApfCapabilities; // ApfCapabilities is immutable
+ p.provisioningTimeoutMs = mProvisioningTimeoutMs;
+ p.ipv6AddrGenMode = mIPv6AddrGenMode;
+ p.network = mNetwork;
+ p.displayName = mDisplayName;
+ return p;
+ }
+
+ /**
+ * Create a ProvisioningConfiguration from a ProvisioningConfigurationParcelable.
+ */
+ public static ProvisioningConfiguration fromStableParcelable(
+ @Nullable ProvisioningConfigurationParcelable p) {
+ if (p == null) return null;
+ final ProvisioningConfiguration config = new ProvisioningConfiguration();
+ config.mEnableIPv4 = p.enableIPv4;
+ config.mEnableIPv6 = p.enableIPv6;
+ config.mUsingMultinetworkPolicyTracker = p.usingMultinetworkPolicyTracker;
+ config.mUsingIpReachabilityMonitor = p.usingIpReachabilityMonitor;
+ config.mRequestedPreDhcpActionMs = p.requestedPreDhcpActionMs;
+ config.mInitialConfig = InitialConfiguration.fromStableParcelable(p.initialConfig);
+ config.mStaticIpConfig = p.staticIpConfig == null
+ ? null
+ : new StaticIpConfiguration(p.staticIpConfig);
+ config.mApfCapabilities = p.apfCapabilities; // ApfCapabilities is immutable
+ config.mProvisioningTimeoutMs = p.provisioningTimeoutMs;
+ config.mIPv6AddrGenMode = p.ipv6AddrGenMode;
+ config.mNetwork = p.network;
+ config.mDisplayName = p.displayName;
+ return config;
+ }
+
+ @Override
+ public String toString() {
+ return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
+ .add("mEnableIPv4: " + mEnableIPv4)
+ .add("mEnableIPv6: " + mEnableIPv6)
+ .add("mUsingMultinetworkPolicyTracker: " + mUsingMultinetworkPolicyTracker)
+ .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
+ .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
+ .add("mInitialConfig: " + mInitialConfig)
+ .add("mStaticIpConfig: " + mStaticIpConfig)
+ .add("mApfCapabilities: " + mApfCapabilities)
+ .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
+ .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode)
+ .add("mNetwork: " + mNetwork)
+ .add("mDisplayName: " + mDisplayName)
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ProvisioningConfiguration)) return false;
+ final ProvisioningConfiguration other = (ProvisioningConfiguration) obj;
+ return mEnableIPv4 == other.mEnableIPv4
+ && mEnableIPv6 == other.mEnableIPv6
+ && mUsingMultinetworkPolicyTracker == other.mUsingMultinetworkPolicyTracker
+ && mUsingIpReachabilityMonitor == other.mUsingIpReachabilityMonitor
+ && mRequestedPreDhcpActionMs == other.mRequestedPreDhcpActionMs
+ && Objects.equals(mInitialConfig, other.mInitialConfig)
+ && Objects.equals(mStaticIpConfig, other.mStaticIpConfig)
+ && Objects.equals(mApfCapabilities, other.mApfCapabilities)
+ && mProvisioningTimeoutMs == other.mProvisioningTimeoutMs
+ && mIPv6AddrGenMode == other.mIPv6AddrGenMode
+ && Objects.equals(mNetwork, other.mNetwork)
+ && Objects.equals(mDisplayName, other.mDisplayName);
+ }
+
+ public boolean isValid() {
+ return (mInitialConfig == null) || mInitialConfig.isValid();
+ }
+}
diff --git a/common/moduleutils/src/android/net/util/InterfaceParams.java b/common/moduleutils/src/android/net/util/InterfaceParams.java
new file mode 100644
index 0000000..3ba02b5
--- /dev/null
+++ b/common/moduleutils/src/android/net/util/InterfaceParams.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2019 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 android.net.util;
+
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.net.MacAddress;
+import android.text.TextUtils;
+
+import java.net.NetworkInterface;
+import java.net.SocketException;
+
+
+/**
+ * Encapsulate the interface parameters common to IpClient/IpServer components.
+ *
+ * Basically all java.net.NetworkInterface methods throw Exceptions. IpClient
+ * and IpServer (sub)components need most or all of this information at some
+ * point during their lifecycles, so pass only this simplified object around
+ * which can be created once when IpClient/IpServer are told to start.
+ *
+ * @hide
+ */
+public class InterfaceParams {
+ public final String name;
+ public final int index;
+ public final MacAddress macAddr;
+ public final int defaultMtu;
+
+ // TODO: move the below to NetworkStackConstants when this class is moved to the NetworkStack.
+ private static final int ETHER_MTU = 1500;
+ private static final int IPV6_MIN_MTU = 1280;
+
+
+ public static InterfaceParams getByName(String name) {
+ final NetworkInterface netif = getNetworkInterfaceByName(name);
+ if (netif == null) return null;
+
+ // Not all interfaces have MAC addresses, e.g. rmnet_data0.
+ final MacAddress macAddr = getMacAddress(netif);
+
+ try {
+ return new InterfaceParams(name, netif.getIndex(), macAddr, netif.getMTU());
+ } catch (IllegalArgumentException|SocketException e) {
+ return null;
+ }
+ }
+
+ public InterfaceParams(String name, int index, MacAddress macAddr) {
+ this(name, index, macAddr, ETHER_MTU);
+ }
+
+ public InterfaceParams(String name, int index, MacAddress macAddr, int defaultMtu) {
+ checkArgument((!TextUtils.isEmpty(name)), "impossible interface name");
+ checkArgument((index > 0), "invalid interface index");
+ this.name = name;
+ this.index = index;
+ this.macAddr = (macAddr != null) ? macAddr : MacAddress.fromBytes(new byte[] {
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 });
+ this.defaultMtu = (defaultMtu > IPV6_MIN_MTU) ? defaultMtu : IPV6_MIN_MTU;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s/%d/%s/%d", name, index, macAddr, defaultMtu);
+ }
+
+ private static NetworkInterface getNetworkInterfaceByName(String name) {
+ try {
+ return NetworkInterface.getByName(name);
+ } catch (NullPointerException|SocketException e) {
+ return null;
+ }
+ }
+
+ private static MacAddress getMacAddress(NetworkInterface netif) {
+ try {
+ return MacAddress.fromBytes(netif.getHardwareAddress());
+ } catch (IllegalArgumentException|NullPointerException|SocketException e) {
+ return null;
+ }
+ }
+}
diff --git a/common/netlinkclient/Android.bp b/common/netlinkclient/Android.bp
new file mode 100644
index 0000000..32e6d56
--- /dev/null
+++ b/common/netlinkclient/Android.bp
@@ -0,0 +1,27 @@
+//
+// 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.
+//
+
+java_library {
+ name: "netlink-client",
+ srcs: [
+ "src/**/*.java",
+ ":framework-networkstack-shared-srcs",
+ ],
+ libs: [
+ "androidx.annotation_annotation",
+ ],
+ sdk_version: "system_current",
+} \ No newline at end of file
diff --git a/common/netlinkclient/src/android/net/netlink/ConntrackMessage.java b/common/netlinkclient/src/android/net/netlink/ConntrackMessage.java
new file mode 100644
index 0000000..6978739
--- /dev/null
+++ b/common/netlinkclient/src/android/net/netlink/ConntrackMessage.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 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 android.net.netlink;
+
+import static android.net.netlink.StructNlMsgHdr.NLM_F_ACK;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REPLACE;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+
+import static java.nio.ByteOrder.BIG_ENDIAN;
+
+import android.system.OsConstants;
+
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+
+/**
+ * A NetlinkMessage subclass for netlink conntrack messages.
+ *
+ * see also: &lt;linux_src&gt;/include/uapi/linux/netfilter/nfnetlink_conntrack.h
+ *
+ * @hide
+ */
+public class ConntrackMessage extends NetlinkMessage {
+ public static final int STRUCT_SIZE = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE;
+
+ public static final short NFNL_SUBSYS_CTNETLINK = 1;
+ public static final short IPCTNL_MSG_CT_NEW = 0;
+
+ // enum ctattr_type
+ public static final short CTA_TUPLE_ORIG = 1;
+ public static final short CTA_TUPLE_REPLY = 2;
+ public static final short CTA_TIMEOUT = 7;
+
+ // enum ctattr_tuple
+ public static final short CTA_TUPLE_IP = 1;
+ public static final short CTA_TUPLE_PROTO = 2;
+
+ // enum ctattr_ip
+ public static final short CTA_IP_V4_SRC = 1;
+ public static final short CTA_IP_V4_DST = 2;
+
+ // enum ctattr_l4proto
+ public static final short CTA_PROTO_NUM = 1;
+ public static final short CTA_PROTO_SRC_PORT = 2;
+ public static final short CTA_PROTO_DST_PORT = 3;
+
+ public static byte[] newIPv4TimeoutUpdateRequest(
+ int proto, Inet4Address src, int sport, Inet4Address dst, int dport, int timeoutSec) {
+ // *** STYLE WARNING ***
+ //
+ // Code below this point uses extra block indentation to highlight the
+ // packing of nested tuple netlink attribute types.
+ final StructNlAttr ctaTupleOrig = new StructNlAttr(CTA_TUPLE_ORIG,
+ new StructNlAttr(CTA_TUPLE_IP,
+ new StructNlAttr(CTA_IP_V4_SRC, src),
+ new StructNlAttr(CTA_IP_V4_DST, dst)),
+ new StructNlAttr(CTA_TUPLE_PROTO,
+ new StructNlAttr(CTA_PROTO_NUM, (byte) proto),
+ new StructNlAttr(CTA_PROTO_SRC_PORT, (short) sport, BIG_ENDIAN),
+ new StructNlAttr(CTA_PROTO_DST_PORT, (short) dport, BIG_ENDIAN)));
+
+ final StructNlAttr ctaTimeout = new StructNlAttr(CTA_TIMEOUT, timeoutSec, BIG_ENDIAN);
+
+ final int payloadLength = ctaTupleOrig.getAlignedLength() + ctaTimeout.getAlignedLength();
+ final byte[] bytes = new byte[STRUCT_SIZE + payloadLength];
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+ byteBuffer.order(ByteOrder.nativeOrder());
+
+ final ConntrackMessage ctmsg = new ConntrackMessage();
+ ctmsg.mHeader.nlmsg_len = bytes.length;
+ ctmsg.mHeader.nlmsg_type = (NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_NEW;
+ ctmsg.mHeader.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE;
+ ctmsg.mHeader.nlmsg_seq = 1;
+ ctmsg.pack(byteBuffer);
+
+ ctaTupleOrig.pack(byteBuffer);
+ ctaTimeout.pack(byteBuffer);
+
+ return bytes;
+ }
+
+ protected StructNfGenMsg mNfGenMsg;
+
+ private ConntrackMessage() {
+ super(new StructNlMsgHdr());
+ mNfGenMsg = new StructNfGenMsg((byte) OsConstants.AF_INET);
+ }
+
+ public void pack(ByteBuffer byteBuffer) {
+ mHeader.pack(byteBuffer);
+ mNfGenMsg.pack(byteBuffer);
+ }
+}
diff --git a/common/netlinkclient/src/android/net/netlink/InetDiagMessage.java b/common/netlinkclient/src/android/net/netlink/InetDiagMessage.java
new file mode 100644
index 0000000..ca07630
--- /dev/null
+++ b/common/netlinkclient/src/android/net/netlink/InetDiagMessage.java
@@ -0,0 +1,221 @@
+/*
+ * 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 android.net.netlink;
+
+import static android.net.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY;
+import static android.net.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+import static android.os.Process.INVALID_UID;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.NETLINK_INET_DIAG;
+
+import android.annotation.Nullable;
+import android.net.util.SocketUtils;
+import android.system.ErrnoException;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * A NetlinkMessage subclass for netlink inet_diag messages.
+ *
+ * see also: &lt;linux_src&gt;/include/uapi/linux/inet_diag.h
+ *
+ * @hide
+ */
+public class InetDiagMessage extends NetlinkMessage {
+ public static final String TAG = "InetDiagMessage";
+ private static final int TIMEOUT_MS = 500;
+
+ public static byte[] InetDiagReqV2(int protocol, InetSocketAddress local,
+ InetSocketAddress remote, int family, short flags) {
+ return InetDiagReqV2(protocol, local, remote, family, flags, 0 /* pad */,
+ 0 /* idiagExt */, StructInetDiagReqV2.INET_DIAG_REQ_V2_ALL_STATES);
+ }
+
+ /**
+ * Construct an inet_diag_req_v2 message. This method will throw {@code NullPointerException}
+ * if local and remote are not both null or both non-null.
+ *
+ * @param protocol the request protocol type. This should be set to one of IPPROTO_TCP,
+ * IPPROTO_UDP, or IPPROTO_UDPLITE.
+ * @param local local socket address of the target socket. This will be packed into a
+ * {@Code StructInetDiagSockId}. Request to diagnose for all sockets if both of
+ * local or remote address is null.
+ * @param remote remote socket address of the target socket. This will be packed into a
+ * {@Code StructInetDiagSockId}. Request to diagnose for all sockets if both of
+ * local or remote address is null.
+ * @param family the ip family of the request message. This should be set to either AF_INET or
+ * AF_INET6 for IPv4 or IPv6 sockets respectively.
+ * @param flags message flags. See &lt;linux_src&gt;/include/uapi/linux/netlink.h.
+ * @param pad for raw socket protocol specification.
+ * @param idiagExt a set of flags defining what kind of extended information to report.
+ * @param state a bit mask that defines a filter of socket states.
+ *
+ * @return bytes array representation of the message
+ **/
+ public static byte[] InetDiagReqV2(int protocol, @Nullable InetSocketAddress local,
+ @Nullable InetSocketAddress remote, int family, short flags, int pad, int idiagExt,
+ int state) throws NullPointerException {
+ final byte[] bytes = new byte[StructNlMsgHdr.STRUCT_SIZE + StructInetDiagReqV2.STRUCT_SIZE];
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+ byteBuffer.order(ByteOrder.nativeOrder());
+
+ final StructNlMsgHdr nlMsgHdr = new StructNlMsgHdr();
+ nlMsgHdr.nlmsg_len = bytes.length;
+ nlMsgHdr.nlmsg_type = SOCK_DIAG_BY_FAMILY;
+ nlMsgHdr.nlmsg_flags = flags;
+ nlMsgHdr.pack(byteBuffer);
+ final StructInetDiagReqV2 inetDiagReqV2 =
+ new StructInetDiagReqV2(protocol, local, remote, family, pad, idiagExt, state);
+
+ inetDiagReqV2.pack(byteBuffer);
+ return bytes;
+ }
+
+ public StructInetDiagMsg mStructInetDiagMsg;
+
+ private InetDiagMessage(StructNlMsgHdr header) {
+ super(header);
+ mStructInetDiagMsg = new StructInetDiagMsg();
+ }
+
+ public static InetDiagMessage parse(StructNlMsgHdr header, ByteBuffer byteBuffer) {
+ final InetDiagMessage msg = new InetDiagMessage(header);
+ msg.mStructInetDiagMsg = StructInetDiagMsg.parse(byteBuffer);
+ return msg;
+ }
+
+ private static int lookupUidByFamily(int protocol, InetSocketAddress local,
+ InetSocketAddress remote, int family, short flags,
+ FileDescriptor fd)
+ throws ErrnoException, InterruptedIOException {
+ byte[] msg = InetDiagReqV2(protocol, local, remote, family, flags);
+ NetlinkSocket.sendMessage(fd, msg, 0, msg.length, TIMEOUT_MS);
+ ByteBuffer response = NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT_MS);
+
+ final NetlinkMessage nlMsg = NetlinkMessage.parse(response);
+ final StructNlMsgHdr hdr = nlMsg.getHeader();
+ if (hdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) {
+ return INVALID_UID;
+ }
+ if (nlMsg instanceof InetDiagMessage) {
+ return ((InetDiagMessage) nlMsg).mStructInetDiagMsg.idiag_uid;
+ }
+ return INVALID_UID;
+ }
+
+ private static final int FAMILY[] = {AF_INET6, AF_INET};
+
+ private static int lookupUid(int protocol, InetSocketAddress local,
+ InetSocketAddress remote, FileDescriptor fd)
+ throws ErrnoException, InterruptedIOException {
+ int uid;
+
+ for (int family : FAMILY) {
+ /**
+ * For exact match lookup, swap local and remote for UDP lookups due to kernel
+ * bug which will not be fixed. See aosp/755889 and
+ * https://www.mail-archive.com/netdev@vger.kernel.org/msg248638.html
+ */
+ if (protocol == IPPROTO_UDP) {
+ uid = lookupUidByFamily(protocol, remote, local, family, NLM_F_REQUEST, fd);
+ } else {
+ uid = lookupUidByFamily(protocol, local, remote, family, NLM_F_REQUEST, fd);
+ }
+ if (uid != INVALID_UID) {
+ return uid;
+ }
+ }
+
+ /**
+ * For UDP it's possible for a socket to send packets to arbitrary destinations, even if the
+ * socket is not connected (and even if the socket is connected to a different destination).
+ * If we want this API to work for such packets, then on miss we need to do a second lookup
+ * with only the local address and port filled in.
+ * Always use flags == NLM_F_REQUEST | NLM_F_DUMP for wildcard.
+ */
+ if (protocol == IPPROTO_UDP) {
+ try {
+ InetSocketAddress wildcard = new InetSocketAddress(
+ Inet6Address.getByName("::"), 0);
+ uid = lookupUidByFamily(protocol, local, wildcard, AF_INET6,
+ (short) (NLM_F_REQUEST | NLM_F_DUMP), fd);
+ if (uid != INVALID_UID) {
+ return uid;
+ }
+ wildcard = new InetSocketAddress(Inet4Address.getByName("0.0.0.0"), 0);
+ uid = lookupUidByFamily(protocol, local, wildcard, AF_INET,
+ (short) (NLM_F_REQUEST | NLM_F_DUMP), fd);
+ if (uid != INVALID_UID) {
+ return uid;
+ }
+ } catch (UnknownHostException e) {
+ Log.e(TAG, e.toString());
+ }
+ }
+ return INVALID_UID;
+ }
+
+ /**
+ * Use an inet_diag socket to look up the UID associated with the input local and remote
+ * address/port and protocol of a connection.
+ */
+ public static int getConnectionOwnerUid(int protocol, InetSocketAddress local,
+ InetSocketAddress remote) {
+ int uid = INVALID_UID;
+ FileDescriptor fd = null;
+ try {
+ fd = NetlinkSocket.forProto(NETLINK_INET_DIAG);
+ NetlinkSocket.connectToKernel(fd);
+ uid = lookupUid(protocol, local, remote, fd);
+ } catch (ErrnoException | SocketException | IllegalArgumentException
+ | InterruptedIOException e) {
+ Log.e(TAG, e.toString());
+ } finally {
+ if (fd != null) {
+ try {
+ SocketUtils.closeSocket(fd);
+ } catch (IOException e) {
+ Log.e(TAG, e.toString());
+ }
+ }
+ }
+ return uid;
+ }
+
+ @Override
+ public String toString() {
+ return "InetDiagMessage{ "
+ + "nlmsghdr{" + (mHeader == null ? "" : mHeader.toString()) + "}, "
+ + "inet_diag_msg{"
+ + (mStructInetDiagMsg == null ? "" : mStructInetDiagMsg.toString()) + "} "
+ + "}";
+ }
+}
diff --git a/common/netlinkclient/src/android/net/netlink/NetlinkConstants.java b/common/netlinkclient/src/android/net/netlink/NetlinkConstants.java
new file mode 100644
index 0000000..fc1551c
--- /dev/null
+++ b/common/netlinkclient/src/android/net/netlink/NetlinkConstants.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2015 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 android.net.netlink;
+
+import android.system.OsConstants;
+import com.android.internal.util.HexDump;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * Various constants and static helper methods for netlink communications.
+ *
+ * Values taken from:
+ *
+ * &lt;linux_src&gt;/include/uapi/linux/netlink.h
+ * &lt;linux_src&gt;/include/uapi/linux/rtnetlink.h
+ *
+ * @hide
+ */
+public class NetlinkConstants {
+ private NetlinkConstants() {}
+
+ public static final int NLA_ALIGNTO = 4;
+
+ public static final int alignedLengthOf(short length) {
+ final int intLength = (int) length & 0xffff;
+ return alignedLengthOf(intLength);
+ }
+
+ public static final int alignedLengthOf(int length) {
+ if (length <= 0) { return 0; }
+ return (((length + NLA_ALIGNTO - 1) / NLA_ALIGNTO) * NLA_ALIGNTO);
+ }
+
+ public static String stringForAddressFamily(int family) {
+ if (family == OsConstants.AF_INET) { return "AF_INET"; }
+ if (family == OsConstants.AF_INET6) { return "AF_INET6"; }
+ if (family == OsConstants.AF_NETLINK) { return "AF_NETLINK"; }
+ return String.valueOf(family);
+ }
+
+ public static String stringForProtocol(int protocol) {
+ if (protocol == OsConstants.IPPROTO_TCP) { return "IPPROTO_TCP"; }
+ if (protocol == OsConstants.IPPROTO_UDP) { return "IPPROTO_UDP"; }
+ return String.valueOf(protocol);
+ }
+
+ public static String hexify(byte[] bytes) {
+ if (bytes == null) { return "(null)"; }
+ return HexDump.toHexString(bytes);
+ }
+
+ public static String hexify(ByteBuffer buffer) {
+ if (buffer == null) { return "(null)"; }
+ return HexDump.toHexString(
+ buffer.array(), buffer.position(), buffer.remaining());
+ }
+
+ // Known values for struct nlmsghdr nlm_type.
+ public static final short NLMSG_NOOP = 1; // Nothing
+ public static final short NLMSG_ERROR = 2; // Error
+ public static final short NLMSG_DONE = 3; // End of a dump
+ public static final short NLMSG_OVERRUN = 4; // Data lost
+ public static final short NLMSG_MAX_RESERVED = 15; // Max reserved value
+
+ public static final short RTM_NEWLINK = 16;
+ public static final short RTM_DELLINK = 17;
+ public static final short RTM_GETLINK = 18;
+ public static final short RTM_SETLINK = 19;
+ public static final short RTM_NEWADDR = 20;
+ public static final short RTM_DELADDR = 21;
+ public static final short RTM_GETADDR = 22;
+ public static final short RTM_NEWROUTE = 24;
+ public static final short RTM_DELROUTE = 25;
+ public static final short RTM_GETROUTE = 26;
+ public static final short RTM_NEWNEIGH = 28;
+ public static final short RTM_DELNEIGH = 29;
+ public static final short RTM_GETNEIGH = 30;
+ public static final short RTM_NEWRULE = 32;
+ public static final short RTM_DELRULE = 33;
+ public static final short RTM_GETRULE = 34;
+ public static final short RTM_NEWNDUSEROPT = 68;
+
+ /* see &lt;linux_src&gt;/include/uapi/linux/sock_diag.h */
+ public static final short SOCK_DIAG_BY_FAMILY = 20;
+
+ public static String stringForNlMsgType(short nlm_type) {
+ switch (nlm_type) {
+ case NLMSG_NOOP: return "NLMSG_NOOP";
+ case NLMSG_ERROR: return "NLMSG_ERROR";
+ case NLMSG_DONE: return "NLMSG_DONE";
+ case NLMSG_OVERRUN: return "NLMSG_OVERRUN";
+ case RTM_NEWLINK: return "RTM_NEWLINK";
+ case RTM_DELLINK: return "RTM_DELLINK";
+ case RTM_GETLINK: return "RTM_GETLINK";
+ case RTM_SETLINK: return "RTM_SETLINK";
+ case RTM_NEWADDR: return "RTM_NEWADDR";
+ case RTM_DELADDR: return "RTM_DELADDR";
+ case RTM_GETADDR: return "RTM_GETADDR";
+ case RTM_NEWROUTE: return "RTM_NEWROUTE";
+ case RTM_DELROUTE: return "RTM_DELROUTE";
+ case RTM_GETROUTE: return "RTM_GETROUTE";
+ case RTM_NEWNEIGH: return "RTM_NEWNEIGH";
+ case RTM_DELNEIGH: return "RTM_DELNEIGH";
+ case RTM_GETNEIGH: return "RTM_GETNEIGH";
+ case RTM_NEWRULE: return "RTM_NEWRULE";
+ case RTM_DELRULE: return "RTM_DELRULE";
+ case RTM_GETRULE: return "RTM_GETRULE";
+ case RTM_NEWNDUSEROPT: return "RTM_NEWNDUSEROPT";
+ default:
+ return "unknown RTM type: " + String.valueOf(nlm_type);
+ }
+ }
+}
diff --git a/common/netlinkclient/src/android/net/netlink/NetlinkErrorMessage.java b/common/netlinkclient/src/android/net/netlink/NetlinkErrorMessage.java
new file mode 100644
index 0000000..36b02c6
--- /dev/null
+++ b/common/netlinkclient/src/android/net/netlink/NetlinkErrorMessage.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 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 android.net.netlink;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * A NetlinkMessage subclass for netlink error messages.
+ *
+ * @hide
+ */
+public class NetlinkErrorMessage extends NetlinkMessage {
+
+ public static NetlinkErrorMessage parse(StructNlMsgHdr header, ByteBuffer byteBuffer) {
+ final NetlinkErrorMessage errorMsg = new NetlinkErrorMessage(header);
+
+ errorMsg.mNlMsgErr = StructNlMsgErr.parse(byteBuffer);
+ if (errorMsg.mNlMsgErr == null) {
+ return null;
+ }
+
+ return errorMsg;
+ }
+
+ private StructNlMsgErr mNlMsgErr;
+
+ NetlinkErrorMessage(StructNlMsgHdr header) {
+ super(header);
+ mNlMsgErr = null;
+ }
+
+ public StructNlMsgErr getNlMsgError() {
+ return mNlMsgErr;
+ }
+
+ @Override
+ public String toString() {
+ return "NetlinkErrorMessage{ "
+ + "nlmsghdr{" + (mHeader == null ? "" : mHeader.toString()) + "}, "
+ + "nlmsgerr{" + (mNlMsgErr == null ? "" : mNlMsgErr.toString()) + "} "
+ + "}";
+ }
+}
diff --git a/common/netlinkclient/src/android/net/netlink/NetlinkMessage.java b/common/netlinkclient/src/android/net/netlink/NetlinkMessage.java
new file mode 100644
index 0000000..b730032
--- /dev/null
+++ b/common/netlinkclient/src/android/net/netlink/NetlinkMessage.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2019 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 android.net.netlink;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * NetlinkMessage base class for other, more specific netlink message types.
+ *
+ * Classes that extend NetlinkMessage should:
+ * - implement a public static parse(StructNlMsgHdr, ByteBuffer) method
+ * - returning either null (parse errors) or a new object of the subclass
+ * type (cast-able to NetlinkMessage)
+ *
+ * NetlinkMessage.parse() should be updated to know which nlmsg_type values
+ * correspond with which message subclasses.
+ *
+ * @hide
+ */
+public class NetlinkMessage {
+ private final static String TAG = "NetlinkMessage";
+
+ public static NetlinkMessage parse(ByteBuffer byteBuffer) {
+ final int startPosition = (byteBuffer != null) ? byteBuffer.position() : -1;
+ final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(byteBuffer);
+ if (nlmsghdr == null) {
+ return null;
+ }
+
+ int payloadLength = NetlinkConstants.alignedLengthOf(nlmsghdr.nlmsg_len);
+ payloadLength -= StructNlMsgHdr.STRUCT_SIZE;
+ if (payloadLength < 0 || payloadLength > byteBuffer.remaining()) {
+ // Malformed message or runt buffer. Pretend the buffer was consumed.
+ byteBuffer.position(byteBuffer.limit());
+ return null;
+ }
+
+ switch (nlmsghdr.nlmsg_type) {
+ //case NetlinkConstants.NLMSG_NOOP:
+ case NetlinkConstants.NLMSG_ERROR:
+ return (NetlinkMessage) NetlinkErrorMessage.parse(nlmsghdr, byteBuffer);
+ case NetlinkConstants.NLMSG_DONE:
+ byteBuffer.position(byteBuffer.position() + payloadLength);
+ return new NetlinkMessage(nlmsghdr);
+ //case NetlinkConstants.NLMSG_OVERRUN:
+ case NetlinkConstants.RTM_NEWNEIGH:
+ case NetlinkConstants.RTM_DELNEIGH:
+ case NetlinkConstants.RTM_GETNEIGH:
+ return (NetlinkMessage) RtNetlinkNeighborMessage.parse(nlmsghdr, byteBuffer);
+ case NetlinkConstants.SOCK_DIAG_BY_FAMILY:
+ return (NetlinkMessage) InetDiagMessage.parse(nlmsghdr, byteBuffer);
+ default:
+ if (nlmsghdr.nlmsg_type <= NetlinkConstants.NLMSG_MAX_RESERVED) {
+ // Netlink control message. Just parse the header for now,
+ // pretending the whole message was consumed.
+ byteBuffer.position(byteBuffer.position() + payloadLength);
+ return new NetlinkMessage(nlmsghdr);
+ }
+ return null;
+ }
+ }
+
+ protected StructNlMsgHdr mHeader;
+
+ public NetlinkMessage(StructNlMsgHdr nlmsghdr) {
+ mHeader = nlmsghdr;
+ }
+
+ public StructNlMsgHdr getHeader() {
+ return mHeader;
+ }
+
+ @Override
+ public String toString() {
+ return "NetlinkMessage{" + (mHeader == null ? "" : mHeader.toString()) + "}";
+ }
+}
diff --git a/common/netlinkclient/src/android/net/netlink/NetlinkSocket.java b/common/netlinkclient/src/android/net/netlink/NetlinkSocket.java
new file mode 100644
index 0000000..7311fc5
--- /dev/null
+++ b/common/netlinkclient/src/android/net/netlink/NetlinkSocket.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2015 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 android.net.netlink;
+
+import static android.net.util.SocketUtils.makeNetlinkSocketAddress;
+import static android.system.OsConstants.AF_NETLINK;
+import static android.system.OsConstants.EIO;
+import static android.system.OsConstants.EPROTO;
+import static android.system.OsConstants.ETIMEDOUT;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_RCVBUF;
+import static android.system.OsConstants.SO_RCVTIMEO;
+import static android.system.OsConstants.SO_SNDTIMEO;
+
+import android.net.util.SocketUtils;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructTimeval;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+
+/**
+ * NetlinkSocket
+ *
+ * A small static class to assist with AF_NETLINK socket operations.
+ *
+ * @hide
+ */
+public class NetlinkSocket {
+ private static final String TAG = "NetlinkSocket";
+
+ public static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
+ public static final int SOCKET_RECV_BUFSIZE = 64 * 1024;
+
+ public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException {
+ final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage";
+ final long IO_TIMEOUT = 300L;
+
+ final FileDescriptor fd = forProto(nlProto);
+
+ try {
+ connectToKernel(fd);
+ sendMessage(fd, msg, 0, msg.length, IO_TIMEOUT);
+ final ByteBuffer bytes = recvMessage(fd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT);
+ // recvMessage() guaranteed to not return null if it did not throw.
+ final NetlinkMessage response = NetlinkMessage.parse(bytes);
+ if (response != null && response instanceof NetlinkErrorMessage &&
+ (((NetlinkErrorMessage) response).getNlMsgError() != null)) {
+ final int errno = ((NetlinkErrorMessage) response).getNlMsgError().error;
+ if (errno != 0) {
+ // TODO: consider ignoring EINVAL (-22), which appears to be
+ // normal when probing a neighbor for which the kernel does
+ // not already have / no longer has a link layer address.
+ Log.e(TAG, errPrefix + ", errmsg=" + response.toString());
+ // Note: convert kernel errnos (negative) into userspace errnos (positive).
+ throw new ErrnoException(response.toString(), Math.abs(errno));
+ }
+ } else {
+ final String errmsg;
+ if (response == null) {
+ bytes.position(0);
+ errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes);
+ } else {
+ errmsg = response.toString();
+ }
+ Log.e(TAG, errPrefix + ", errmsg=" + errmsg);
+ throw new ErrnoException(errmsg, EPROTO);
+ }
+ } catch (InterruptedIOException e) {
+ Log.e(TAG, errPrefix, e);
+ throw new ErrnoException(errPrefix, ETIMEDOUT, e);
+ } catch (SocketException e) {
+ Log.e(TAG, errPrefix, e);
+ throw new ErrnoException(errPrefix, EIO, e);
+ } finally {
+ try {
+ SocketUtils.closeSocket(fd);
+ } catch (IOException e) {
+ // Nothing we can do here
+ }
+ }
+ }
+
+ public static FileDescriptor forProto(int nlProto) throws ErrnoException {
+ final FileDescriptor fd = Os.socket(AF_NETLINK, SOCK_DGRAM, nlProto);
+ Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, SOCKET_RECV_BUFSIZE);
+ return fd;
+ }
+
+ public static void connectToKernel(FileDescriptor fd) throws ErrnoException, SocketException {
+ Os.connect(fd, makeNetlinkSocketAddress(0, 0));
+ }
+
+ private static void checkTimeout(long timeoutMs) {
+ if (timeoutMs < 0) {
+ throw new IllegalArgumentException("Negative timeouts not permitted");
+ }
+ }
+
+ /**
+ * Wait up to |timeoutMs| (or until underlying socket error) for a
+ * netlink message of at most |bufsize| size.
+ *
+ * Multi-threaded calls with different timeouts will cause unexpected results.
+ */
+ public static ByteBuffer recvMessage(FileDescriptor fd, int bufsize, long timeoutMs)
+ throws ErrnoException, IllegalArgumentException, InterruptedIOException {
+ checkTimeout(timeoutMs);
+
+ Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(timeoutMs));
+
+ ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize);
+ int length = Os.read(fd, byteBuffer);
+ if (length == bufsize) {
+ Log.w(TAG, "maximum read");
+ }
+ byteBuffer.position(0);
+ byteBuffer.limit(length);
+ byteBuffer.order(ByteOrder.nativeOrder());
+ return byteBuffer;
+ }
+
+ /**
+ * Send a message to a peer to which this socket has previously connected,
+ * waiting at most |timeoutMs| milliseconds for the send to complete.
+ *
+ * Multi-threaded calls with different timeouts will cause unexpected results.
+ */
+ public static int sendMessage(
+ FileDescriptor fd, byte[] bytes, int offset, int count, long timeoutMs)
+ throws ErrnoException, IllegalArgumentException, InterruptedIOException {
+ checkTimeout(timeoutMs);
+ Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(timeoutMs));
+ return Os.write(fd, bytes, offset, count);
+ }
+}
diff --git a/common/netlinkclient/src/android/net/netlink/RtNetlinkNeighborMessage.java b/common/netlinkclient/src/android/net/netlink/RtNetlinkNeighborMessage.java
new file mode 100644
index 0000000..8b9e7e0
--- /dev/null
+++ b/common/netlinkclient/src/android/net/netlink/RtNetlinkNeighborMessage.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2019 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 android.net.netlink;
+
+import static android.net.netlink.StructNlMsgHdr.NLM_F_ACK;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REPLACE;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+
+import android.system.OsConstants;
+
+import java.net.InetAddress;
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+
+/**
+ * A NetlinkMessage subclass for rtnetlink neighbor messages.
+ *
+ * see also: &lt;linux_src&gt;/include/uapi/linux/neighbour.h
+ *
+ * @hide
+ */
+public class RtNetlinkNeighborMessage extends NetlinkMessage {
+ public static final short NDA_UNSPEC = 0;
+ public static final short NDA_DST = 1;
+ public static final short NDA_LLADDR = 2;
+ public static final short NDA_CACHEINFO = 3;
+ public static final short NDA_PROBES = 4;
+ public static final short NDA_VLAN = 5;
+ public static final short NDA_PORT = 6;
+ public static final short NDA_VNI = 7;
+ public static final short NDA_IFINDEX = 8;
+ public static final short NDA_MASTER = 9;
+
+ private static StructNlAttr findNextAttrOfType(short attrType, ByteBuffer byteBuffer) {
+ while (byteBuffer != null && byteBuffer.remaining() > 0) {
+ final StructNlAttr nlAttr = StructNlAttr.peek(byteBuffer);
+ if (nlAttr == null) {
+ break;
+ }
+ if (nlAttr.nla_type == attrType) {
+ return StructNlAttr.parse(byteBuffer);
+ }
+ if (byteBuffer.remaining() < nlAttr.getAlignedLength()) {
+ break;
+ }
+ byteBuffer.position(byteBuffer.position() + nlAttr.getAlignedLength());
+ }
+ return null;
+ }
+
+ public static RtNetlinkNeighborMessage parse(StructNlMsgHdr header, ByteBuffer byteBuffer) {
+ final RtNetlinkNeighborMessage neighMsg = new RtNetlinkNeighborMessage(header);
+
+ neighMsg.mNdmsg = StructNdMsg.parse(byteBuffer);
+ if (neighMsg.mNdmsg == null) {
+ return null;
+ }
+
+ // Some of these are message-type dependent, and not always present.
+ final int baseOffset = byteBuffer.position();
+ StructNlAttr nlAttr = findNextAttrOfType(NDA_DST, byteBuffer);
+ if (nlAttr != null) {
+ neighMsg.mDestination = nlAttr.getValueAsInetAddress();
+ }
+
+ byteBuffer.position(baseOffset);
+ nlAttr = findNextAttrOfType(NDA_LLADDR, byteBuffer);
+ if (nlAttr != null) {
+ neighMsg.mLinkLayerAddr = nlAttr.nla_value;
+ }
+
+ byteBuffer.position(baseOffset);
+ nlAttr = findNextAttrOfType(NDA_PROBES, byteBuffer);
+ if (nlAttr != null) {
+ neighMsg.mNumProbes = nlAttr.getValueAsInt(0);
+ }
+
+ byteBuffer.position(baseOffset);
+ nlAttr = findNextAttrOfType(NDA_CACHEINFO, byteBuffer);
+ if (nlAttr != null) {
+ neighMsg.mCacheInfo = StructNdaCacheInfo.parse(nlAttr.getValueAsByteBuffer());
+ }
+
+ final int kMinConsumed = StructNlMsgHdr.STRUCT_SIZE + StructNdMsg.STRUCT_SIZE;
+ final int kAdditionalSpace = NetlinkConstants.alignedLengthOf(
+ neighMsg.mHeader.nlmsg_len - kMinConsumed);
+ if (byteBuffer.remaining() < kAdditionalSpace) {
+ byteBuffer.position(byteBuffer.limit());
+ } else {
+ byteBuffer.position(baseOffset + kAdditionalSpace);
+ }
+
+ return neighMsg;
+ }
+
+ /**
+ * A convenience method to create an RTM_GETNEIGH request message.
+ */
+ public static byte[] newGetNeighborsRequest(int seqNo) {
+ final int length = StructNlMsgHdr.STRUCT_SIZE + StructNdMsg.STRUCT_SIZE;
+ final byte[] bytes = new byte[length];
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+ byteBuffer.order(ByteOrder.nativeOrder());
+
+ final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
+ nlmsghdr.nlmsg_len = length;
+ nlmsghdr.nlmsg_type = NetlinkConstants.RTM_GETNEIGH;
+ nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ nlmsghdr.nlmsg_seq = seqNo;
+ nlmsghdr.pack(byteBuffer);
+
+ final StructNdMsg ndmsg = new StructNdMsg();
+ ndmsg.pack(byteBuffer);
+
+ return bytes;
+ }
+
+ /**
+ * A convenience method to create an RTM_NEWNEIGH message, to modify
+ * the kernel's state information for a specific neighbor.
+ */
+ public static byte[] newNewNeighborMessage(
+ int seqNo, InetAddress ip, short nudState, int ifIndex, byte[] llAddr) {
+ final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
+ nlmsghdr.nlmsg_type = NetlinkConstants.RTM_NEWNEIGH;
+ nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE;
+ nlmsghdr.nlmsg_seq = seqNo;
+
+ final RtNetlinkNeighborMessage msg = new RtNetlinkNeighborMessage(nlmsghdr);
+ msg.mNdmsg = new StructNdMsg();
+ msg.mNdmsg.ndm_family =
+ (byte) ((ip instanceof Inet6Address) ? OsConstants.AF_INET6 : OsConstants.AF_INET);
+ msg.mNdmsg.ndm_ifindex = ifIndex;
+ msg.mNdmsg.ndm_state = nudState;
+ msg.mDestination = ip;
+ msg.mLinkLayerAddr = llAddr; // might be null
+
+ final byte[] bytes = new byte[msg.getRequiredSpace()];
+ nlmsghdr.nlmsg_len = bytes.length;
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+ byteBuffer.order(ByteOrder.nativeOrder());
+ msg.pack(byteBuffer);
+ return bytes;
+ }
+
+ private StructNdMsg mNdmsg;
+ private InetAddress mDestination;
+ private byte[] mLinkLayerAddr;
+ private int mNumProbes;
+ private StructNdaCacheInfo mCacheInfo;
+
+ private RtNetlinkNeighborMessage(StructNlMsgHdr header) {
+ super(header);
+ mNdmsg = null;
+ mDestination = null;
+ mLinkLayerAddr = null;
+ mNumProbes = 0;
+ mCacheInfo = null;
+ }
+
+ public StructNdMsg getNdHeader() {
+ return mNdmsg;
+ }
+
+ public InetAddress getDestination() {
+ return mDestination;
+ }
+
+ public byte[] getLinkLayerAddress() {
+ return mLinkLayerAddr;
+ }
+
+ public int getProbes() {
+ return mNumProbes;
+ }
+
+ public StructNdaCacheInfo getCacheInfo() {
+ return mCacheInfo;
+ }
+
+ public int getRequiredSpace() {
+ int spaceRequired = StructNlMsgHdr.STRUCT_SIZE + StructNdMsg.STRUCT_SIZE;
+ if (mDestination != null) {
+ spaceRequired += NetlinkConstants.alignedLengthOf(
+ StructNlAttr.NLA_HEADERLEN + mDestination.getAddress().length);
+ }
+ if (mLinkLayerAddr != null) {
+ spaceRequired += NetlinkConstants.alignedLengthOf(
+ StructNlAttr.NLA_HEADERLEN + mLinkLayerAddr.length);
+ }
+ // Currently we don't write messages with NDA_PROBES nor NDA_CACHEINFO
+ // attributes appended. Fix later, if necessary.
+ return spaceRequired;
+ }
+
+ private static void packNlAttr(short nlType, byte[] nlValue, ByteBuffer byteBuffer) {
+ final StructNlAttr nlAttr = new StructNlAttr();
+ nlAttr.nla_type = nlType;
+ nlAttr.nla_value = nlValue;
+ nlAttr.nla_len = (short) (StructNlAttr.NLA_HEADERLEN + nlAttr.nla_value.length);
+ nlAttr.pack(byteBuffer);
+ }
+
+ public void pack(ByteBuffer byteBuffer) {
+ getHeader().pack(byteBuffer) ;
+ mNdmsg.pack(byteBuffer);
+
+ if (mDestination != null) {
+ packNlAttr(NDA_DST, mDestination.getAddress(), byteBuffer);
+ }
+ if (mLinkLayerAddr != null) {
+ packNlAttr(NDA_LLADDR, mLinkLayerAddr, byteBuffer);
+ }
+ }
+
+ @Override
+ public String toString() {
+ final String ipLiteral = (mDestination == null) ? "" : mDestination.getHostAddress();
+ return "RtNetlinkNeighborMessage{ "
+ + "nlmsghdr{" + (mHeader == null ? "" : mHeader.toString()) + "}, "
+ + "ndmsg{" + (mNdmsg == null ? "" : mNdmsg.toString()) + "}, "
+ + "destination{" + ipLiteral + "} "
+ + "linklayeraddr{" + NetlinkConstants.hexify(mLinkLayerAddr) + "} "
+ + "probes{" + mNumProbes + "} "
+ + "cacheinfo{" + (mCacheInfo == null ? "" : mCacheInfo.toString()) + "} "
+ + "}";
+ }
+}
diff --git a/common/netlinkclient/src/android/net/netlink/StructInetDiagMsg.java b/common/netlinkclient/src/android/net/netlink/StructInetDiagMsg.java
new file mode 100644
index 0000000..5772a94
--- /dev/null
+++ b/common/netlinkclient/src/android/net/netlink/StructInetDiagMsg.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2019 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 android.net.netlink;
+
+import java.nio.ByteBuffer;
+
+/**
+ * struct inet_diag_msg
+ *
+ * see &lt;linux_src&gt;/include/uapi/linux/inet_diag.h
+ *
+ * struct inet_diag_msg {
+ * __u8 idiag_family;
+ * __u8 idiag_state;
+ * __u8 idiag_timer;
+ * __u8 idiag_retrans;
+ * struct inet_diag_sockid id;
+ * __u32 idiag_expires;
+ * __u32 idiag_rqueue;
+ * __u32 idiag_wqueue;
+ * __u32 idiag_uid;
+ * __u32 idiag_inode;
+ * };
+ *
+ * @hide
+ */
+public class StructInetDiagMsg {
+ public static final int STRUCT_SIZE = 4 + StructInetDiagSockId.STRUCT_SIZE + 20;
+ private static final int IDIAG_UID_OFFSET = StructNlMsgHdr.STRUCT_SIZE + 4 +
+ StructInetDiagSockId.STRUCT_SIZE + 12;
+ public int idiag_uid;
+
+ public static StructInetDiagMsg parse(ByteBuffer byteBuffer) {
+ StructInetDiagMsg struct = new StructInetDiagMsg();
+ struct.idiag_uid = byteBuffer.getInt(IDIAG_UID_OFFSET);
+ return struct;
+ }
+
+ @Override
+ public String toString() {
+ return "StructInetDiagMsg{ "
+ + "idiag_uid{" + idiag_uid + "}, "
+ + "}";
+ }
+}
diff --git a/common/netlinkclient/src/android/net/netlink/StructInetDiagReqV2.java b/common/netlinkclient/src/android/net/netlink/StructInetDiagReqV2.java
new file mode 100644
index 0000000..520f0ef
--- /dev/null
+++ b/common/netlinkclient/src/android/net/netlink/StructInetDiagReqV2.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2019 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 android.net.netlink;
+
+import android.annotation.Nullable;
+
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+
+/**
+ * struct inet_diag_req_v2
+ *
+ * see &lt;linux_src&gt;/include/uapi/linux/inet_diag.h
+ *
+ * struct inet_diag_req_v2 {
+ * __u8 sdiag_family;
+ * __u8 sdiag_protocol;
+ * __u8 idiag_ext;
+ * __u8 pad;
+ * __u32 idiag_states;
+ * struct inet_diag_sockid id;
+ * };
+ *
+ * @hide
+ */
+public class StructInetDiagReqV2 {
+ public static final int STRUCT_SIZE = 8 + StructInetDiagSockId.STRUCT_SIZE;
+
+ private final byte mSdiagFamily;
+ private final byte mSdiagProtocol;
+ private final byte mIdiagExt;
+ private final byte mPad;
+ private final StructInetDiagSockId mId;
+ private final int mState;
+ public static final int INET_DIAG_REQ_V2_ALL_STATES = (int) 0xffffffff;
+
+ public StructInetDiagReqV2(int protocol, InetSocketAddress local, InetSocketAddress remote,
+ int family) {
+ this(protocol, local, remote, family, 0 /* pad */, 0 /* extension */,
+ INET_DIAG_REQ_V2_ALL_STATES);
+ }
+
+ public StructInetDiagReqV2(int protocol, @Nullable InetSocketAddress local,
+ @Nullable InetSocketAddress remote, int family, int pad, int extension, int state)
+ throws NullPointerException {
+ mSdiagFamily = (byte) family;
+ mSdiagProtocol = (byte) protocol;
+ // Request for all sockets if no specific socket is requested. Specify the local and remote
+ // socket address information for target request socket.
+ if ((local == null) != (remote == null)) {
+ throw new NullPointerException("Local and remote must be both null or both non-null");
+ }
+ mId = ((local != null && remote != null) ? new StructInetDiagSockId(local, remote) : null);
+ mPad = (byte) pad;
+ mIdiagExt = (byte) extension;
+ mState = state;
+ }
+
+ public void pack(ByteBuffer byteBuffer) {
+ // The ByteOrder must have already been set by the caller.
+ byteBuffer.put((byte) mSdiagFamily);
+ byteBuffer.put((byte) mSdiagProtocol);
+ byteBuffer.put((byte) mIdiagExt);
+ byteBuffer.put((byte) mPad);
+ byteBuffer.putInt(mState);
+ if (mId != null) mId.pack(byteBuffer);
+ }
+
+ @Override
+ public String toString() {
+ final String familyStr = NetlinkConstants.stringForAddressFamily(mSdiagFamily);
+ final String protocolStr = NetlinkConstants.stringForAddressFamily(mSdiagProtocol);
+
+ return "StructInetDiagReqV2{ "
+ + "sdiag_family{" + familyStr + "}, "
+ + "sdiag_protocol{" + protocolStr + "}, "
+ + "idiag_ext{" + mIdiagExt + ")}, "
+ + "pad{" + mPad + "}, "
+ + "idiag_states{" + Integer.toHexString(mState) + "}, "
+ + ((mId != null) ? mId.toString() : "inet_diag_sockid=null")
+ + "}";
+ }
+}
diff --git a/common/netlinkclient/src/android/net/netlink/StructInetDiagSockId.java b/common/netlinkclient/src/android/net/netlink/StructInetDiagSockId.java
new file mode 100644
index 0000000..2e9fa25
--- /dev/null
+++ b/common/netlinkclient/src/android/net/netlink/StructInetDiagSockId.java
@@ -0,0 +1,86 @@
+/*
+ * 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 android.net.netlink;
+
+import static java.nio.ByteOrder.BIG_ENDIAN;
+
+import java.net.Inet4Address;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * struct inet_diag_req_v2
+ *
+ * see &lt;linux_src&gt;/include/uapi/linux/inet_diag.h
+ *
+ * struct inet_diag_sockid {
+ * __be16 idiag_sport;
+ * __be16 idiag_dport;
+ * __be32 idiag_src[4];
+ * __be32 idiag_dst[4];
+ * __u32 idiag_if;
+ * __u32 idiag_cookie[2];
+ * #define INET_DIAG_NOCOOKIE (~0U)
+ * };
+ *
+ * @hide
+ */
+public class StructInetDiagSockId {
+ public static final int STRUCT_SIZE = 48;
+
+ private final InetSocketAddress mLocSocketAddress;
+ private final InetSocketAddress mRemSocketAddress;
+ private final byte[] INET_DIAG_NOCOOKIE = new byte[]{
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff};
+ private final byte[] IPV4_PADDING = new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+ public StructInetDiagSockId(InetSocketAddress loc, InetSocketAddress rem) {
+ mLocSocketAddress = loc;
+ mRemSocketAddress = rem;
+ }
+
+ public void pack(ByteBuffer byteBuffer) {
+ byteBuffer.order(BIG_ENDIAN);
+ byteBuffer.putShort((short) mLocSocketAddress.getPort());
+ byteBuffer.putShort((short) mRemSocketAddress.getPort());
+ byteBuffer.put(mLocSocketAddress.getAddress().getAddress());
+ if (mLocSocketAddress.getAddress() instanceof Inet4Address) {
+ byteBuffer.put(IPV4_PADDING);
+ }
+ byteBuffer.put(mRemSocketAddress.getAddress().getAddress());
+ if (mRemSocketAddress.getAddress() instanceof Inet4Address) {
+ byteBuffer.put(IPV4_PADDING);
+ }
+ byteBuffer.order(ByteOrder.nativeOrder());
+ byteBuffer.putInt(0);
+ byteBuffer.put(INET_DIAG_NOCOOKIE);
+ }
+
+ @Override
+ public String toString() {
+ return "StructInetDiagSockId{ "
+ + "idiag_sport{" + mLocSocketAddress.getPort() + "}, "
+ + "idiag_dport{" + mRemSocketAddress.getPort() + "}, "
+ + "idiag_src{" + mLocSocketAddress.getAddress().getHostAddress() + "}, "
+ + "idiag_dst{" + mRemSocketAddress.getAddress().getHostAddress() + "}, "
+ + "idiag_if{" + 0 + "} "
+ + "idiag_cookie{INET_DIAG_NOCOOKIE}"
+ + "}";
+ }
+}
diff --git a/common/netlinkclient/src/android/net/netlink/StructNdMsg.java b/common/netlinkclient/src/android/net/netlink/StructNdMsg.java
new file mode 100644
index 0000000..64364df
--- /dev/null
+++ b/common/netlinkclient/src/android/net/netlink/StructNdMsg.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2019 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 android.net.netlink;
+
+import android.system.OsConstants;
+import java.nio.ByteBuffer;
+
+
+/**
+ * struct ndmsg
+ *
+ * see: &lt;linux_src&gt;/include/uapi/linux/neighbour.h
+ *
+ * @hide
+ */
+public class StructNdMsg {
+ // Already aligned.
+ public static final int STRUCT_SIZE = 12;
+
+ // Neighbor Cache Entry States
+ public static final short NUD_NONE = 0x00;
+ public static final short NUD_INCOMPLETE = 0x01;
+ public static final short NUD_REACHABLE = 0x02;
+ public static final short NUD_STALE = 0x04;
+ public static final short NUD_DELAY = 0x08;
+ public static final short NUD_PROBE = 0x10;
+ public static final short NUD_FAILED = 0x20;
+ public static final short NUD_NOARP = 0x40;
+ public static final short NUD_PERMANENT = 0x80;
+
+ public static String stringForNudState(short nudState) {
+ switch (nudState) {
+ case NUD_NONE: return "NUD_NONE";
+ case NUD_INCOMPLETE: return "NUD_INCOMPLETE";
+ case NUD_REACHABLE: return "NUD_REACHABLE";
+ case NUD_STALE: return "NUD_STALE";
+ case NUD_DELAY: return "NUD_DELAY";
+ case NUD_PROBE: return "NUD_PROBE";
+ case NUD_FAILED: return "NUD_FAILED";
+ case NUD_NOARP: return "NUD_NOARP";
+ case NUD_PERMANENT: return "NUD_PERMANENT";
+ default:
+ return "unknown NUD state: " + String.valueOf(nudState);
+ }
+ }
+
+ public static boolean isNudStateConnected(short nudState) {
+ return ((nudState & (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE)) != 0);
+ }
+
+ public static boolean isNudStateValid(short nudState) {
+ return (isNudStateConnected(nudState) ||
+ ((nudState & (NUD_PROBE|NUD_STALE|NUD_DELAY)) != 0));
+ }
+
+ // Neighbor Cache Entry Flags
+ public static byte NTF_USE = (byte) 0x01;
+ public static byte NTF_SELF = (byte) 0x02;
+ public static byte NTF_MASTER = (byte) 0x04;
+ public static byte NTF_PROXY = (byte) 0x08;
+ public static byte NTF_ROUTER = (byte) 0x80;
+
+ public static String stringForNudFlags(byte flags) {
+ final StringBuilder sb = new StringBuilder();
+ if ((flags & NTF_USE) != 0) {
+ sb.append("NTF_USE");
+ }
+ if ((flags & NTF_SELF) != 0) {
+ if (sb.length() > 0) { sb.append("|"); }
+ sb.append("NTF_SELF");
+ }
+ if ((flags & NTF_MASTER) != 0) {
+ if (sb.length() > 0) { sb.append("|"); }
+ sb.append("NTF_MASTER");
+ }
+ if ((flags & NTF_PROXY) != 0) {
+ if (sb.length() > 0) { sb.append("|");
+ }
+ sb.append("NTF_PROXY"); }
+ if ((flags & NTF_ROUTER) != 0) {
+ if (sb.length() > 0) { sb.append("|"); }
+ sb.append("NTF_ROUTER");
+ }
+ return sb.toString();
+ }
+
+ private static boolean hasAvailableSpace(ByteBuffer byteBuffer) {
+ return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE;
+ }
+
+ public static StructNdMsg parse(ByteBuffer byteBuffer) {
+ if (!hasAvailableSpace(byteBuffer)) { return null; }
+
+ // The ByteOrder must have already been set by the caller. In most
+ // cases ByteOrder.nativeOrder() is correct, with the possible
+ // exception of usage within unittests.
+ final StructNdMsg struct = new StructNdMsg();
+ struct.ndm_family = byteBuffer.get();
+ final byte pad1 = byteBuffer.get();
+ final short pad2 = byteBuffer.getShort();
+ struct.ndm_ifindex = byteBuffer.getInt();
+ struct.ndm_state = byteBuffer.getShort();
+ struct.ndm_flags = byteBuffer.get();
+ struct.ndm_type = byteBuffer.get();
+ return struct;
+ }
+
+ public byte ndm_family;
+ public int ndm_ifindex;
+ public short ndm_state;
+ public byte ndm_flags;
+ public byte ndm_type;
+
+ public StructNdMsg() {
+ ndm_family = (byte) OsConstants.AF_UNSPEC;
+ }
+
+ public void pack(ByteBuffer byteBuffer) {
+ // The ByteOrder must have already been set by the caller. In most
+ // cases ByteOrder.nativeOrder() is correct, with the exception
+ // of usage within unittests.
+ byteBuffer.put(ndm_family);
+ byteBuffer.put((byte) 0); // pad1
+ byteBuffer.putShort((short) 0); // pad2
+ byteBuffer.putInt(ndm_ifindex);
+ byteBuffer.putShort(ndm_state);
+ byteBuffer.put(ndm_flags);
+ byteBuffer.put(ndm_type);
+ }
+
+ public boolean nudConnected() {
+ return isNudStateConnected(ndm_state);
+ }
+
+ public boolean nudValid() {
+ return isNudStateValid(ndm_state);
+ }
+
+ @Override
+ public String toString() {
+ final String stateStr = "" + ndm_state + " (" + stringForNudState(ndm_state) + ")";
+ final String flagsStr = "" + ndm_flags + " (" + stringForNudFlags(ndm_flags) + ")";
+ return "StructNdMsg{ "
+ + "family{" + NetlinkConstants.stringForAddressFamily((int) ndm_family) + "}, "
+ + "ifindex{" + ndm_ifindex + "}, "
+ + "state{" + stateStr + "}, "
+ + "flags{" + flagsStr + "}, "
+ + "type{" + ndm_type + "} "
+ + "}";
+ }
+}
diff --git a/common/netlinkclient/src/android/net/netlink/StructNdaCacheInfo.java b/common/netlinkclient/src/android/net/netlink/StructNdaCacheInfo.java
new file mode 100644
index 0000000..16cf563
--- /dev/null
+++ b/common/netlinkclient/src/android/net/netlink/StructNdaCacheInfo.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2015 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 android.net.netlink;
+
+import android.system.Os;
+import android.system.OsConstants;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * struct nda_cacheinfo
+ *
+ * see: &lt;linux_src&gt;/include/uapi/linux/neighbour.h
+ *
+ * @hide
+ */
+public class StructNdaCacheInfo {
+ // Already aligned.
+ public static final int STRUCT_SIZE = 16;
+
+ private static boolean hasAvailableSpace(ByteBuffer byteBuffer) {
+ return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE;
+ }
+
+ public static StructNdaCacheInfo parse(ByteBuffer byteBuffer) {
+ if (!hasAvailableSpace(byteBuffer)) { return null; }
+
+ // The ByteOrder must have already been set by the caller. In most
+ // cases ByteOrder.nativeOrder() is correct, with the possible
+ // exception of usage within unittests.
+ final StructNdaCacheInfo struct = new StructNdaCacheInfo();
+ struct.ndm_used = byteBuffer.getInt();
+ struct.ndm_confirmed = byteBuffer.getInt();
+ struct.ndm_updated = byteBuffer.getInt();
+ struct.ndm_refcnt = byteBuffer.getInt();
+ return struct;
+ }
+
+ // TODO: investigate whether this can change during device runtime and
+ // decide what (if anything) should be done about that.
+ private static final long CLOCK_TICKS_PER_SECOND = Os.sysconf(OsConstants._SC_CLK_TCK);
+
+ private static long ticksToMilliSeconds(int intClockTicks) {
+ final long longClockTicks = (long) intClockTicks & 0xffffffff;
+ return (longClockTicks * 1000) / CLOCK_TICKS_PER_SECOND;
+ }
+
+ /**
+ * Explanatory notes, for reference.
+ *
+ * Before being returned to user space, the neighbor entry times are
+ * converted to clock_t's like so:
+ *
+ * ndm_used = jiffies_to_clock_t(now - neigh->used);
+ * ndm_confirmed = jiffies_to_clock_t(now - neigh->confirmed);
+ * ndm_updated = jiffies_to_clock_t(now - neigh->updated);
+ *
+ * meaning that these values are expressed as "clock ticks ago". To
+ * convert these clock ticks to seconds divide by sysconf(_SC_CLK_TCK).
+ * When _SC_CLK_TCK is 100, for example, the ndm_* times are expressed
+ * in centiseconds.
+ *
+ * These values are unsigned, but fortunately being expressed as "some
+ * clock ticks ago", these values are typically very small (and
+ * 2^31 centiseconds = 248 days).
+ *
+ * By observation, it appears that:
+ * ndm_used: the last time ARP/ND took place for this neighbor
+ * ndm_confirmed: the last time ARP/ND succeeded for this neighbor OR
+ * higher layer confirmation (TCP or MSG_CONFIRM)
+ * was received
+ * ndm_updated: the time when the current NUD state was entered
+ */
+ public int ndm_used;
+ public int ndm_confirmed;
+ public int ndm_updated;
+ public int ndm_refcnt;
+
+ public StructNdaCacheInfo() {}
+
+ public long lastUsed() {
+ return ticksToMilliSeconds(ndm_used);
+ }
+
+ public long lastConfirmed() {
+ return ticksToMilliSeconds(ndm_confirmed);
+ }
+
+ public long lastUpdated() {
+ return ticksToMilliSeconds(ndm_updated);
+ }
+
+ @Override
+ public String toString() {
+ return "NdaCacheInfo{ "
+ + "ndm_used{" + lastUsed() + "}, "
+ + "ndm_confirmed{" + lastConfirmed() + "}, "
+ + "ndm_updated{" + lastUpdated() + "}, "
+ + "ndm_refcnt{" + ndm_refcnt + "} "
+ + "}";
+ }
+}
diff --git a/common/netlinkclient/src/android/net/netlink/StructNfGenMsg.java b/common/netlinkclient/src/android/net/netlink/StructNfGenMsg.java
new file mode 100644
index 0000000..8155977
--- /dev/null
+++ b/common/netlinkclient/src/android/net/netlink/StructNfGenMsg.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 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 android.net.netlink;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * struct nfgenmsg
+ *
+ * see &lt;linux_src&gt;/include/uapi/linux/netfilter/nfnetlink.h
+ *
+ * @hide
+ */
+public class StructNfGenMsg {
+ public static final int STRUCT_SIZE = 2 + Short.BYTES;
+
+ public static final int NFNETLINK_V0 = 0;
+
+ final public byte nfgen_family;
+ final public byte version;
+ final public short res_id; // N.B.: this is big endian in the kernel
+
+ public StructNfGenMsg(byte family) {
+ nfgen_family = family;
+ version = (byte) NFNETLINK_V0;
+ res_id = (short) 0;
+ }
+
+ public void pack(ByteBuffer byteBuffer) {
+ byteBuffer.put(nfgen_family);
+ byteBuffer.put(version);
+ byteBuffer.putShort(res_id);
+ }
+}
diff --git a/common/netlinkclient/src/android/net/netlink/StructNlAttr.java b/common/netlinkclient/src/android/net/netlink/StructNlAttr.java
new file mode 100644
index 0000000..747998d
--- /dev/null
+++ b/common/netlinkclient/src/android/net/netlink/StructNlAttr.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2019 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 android.net.netlink;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteOrder;
+import java.nio.ByteBuffer;
+
+
+/**
+ * struct nlattr
+ *
+ * see: &lt;linux_src&gt;/include/uapi/linux/netlink.h
+ *
+ * @hide
+ */
+public class StructNlAttr {
+ // Already aligned.
+ public static final int NLA_HEADERLEN = 4;
+ public static final int NLA_F_NESTED = (1 << 15);
+
+ public static short makeNestedType(short type) {
+ return (short) (type | NLA_F_NESTED);
+ }
+
+ // Return a (length, type) object only, without consuming any bytes in
+ // |byteBuffer| and without copying or interpreting any value bytes.
+ // This is used for scanning over a packed set of struct nlattr's,
+ // looking for instances of a particular type.
+ public static StructNlAttr peek(ByteBuffer byteBuffer) {
+ if (byteBuffer == null || byteBuffer.remaining() < NLA_HEADERLEN) {
+ return null;
+ }
+ final int baseOffset = byteBuffer.position();
+
+ // Assume the byte order of the buffer is the expected byte order of the value.
+ final StructNlAttr struct = new StructNlAttr(byteBuffer.order());
+ // The byte order of nla_len and nla_type is always native.
+ final ByteOrder originalOrder = byteBuffer.order();
+ byteBuffer.order(ByteOrder.nativeOrder());
+ try {
+ struct.nla_len = byteBuffer.getShort();
+ struct.nla_type = byteBuffer.getShort();
+ } finally {
+ byteBuffer.order(originalOrder);
+ }
+
+ byteBuffer.position(baseOffset);
+ if (struct.nla_len < NLA_HEADERLEN) {
+ // Malformed.
+ return null;
+ }
+ return struct;
+ }
+
+ public static StructNlAttr parse(ByteBuffer byteBuffer) {
+ final StructNlAttr struct = peek(byteBuffer);
+ if (struct == null || byteBuffer.remaining() < struct.getAlignedLength()) {
+ return null;
+ }
+
+ final int baseOffset = byteBuffer.position();
+ byteBuffer.position(baseOffset + NLA_HEADERLEN);
+
+ int valueLen = ((int) struct.nla_len) & 0xffff;
+ valueLen -= NLA_HEADERLEN;
+ if (valueLen > 0) {
+ struct.nla_value = new byte[valueLen];
+ byteBuffer.get(struct.nla_value, 0, valueLen);
+ byteBuffer.position(baseOffset + struct.getAlignedLength());
+ }
+ return struct;
+ }
+
+ public short nla_len = (short) NLA_HEADERLEN;
+ public short nla_type;
+ public byte[] nla_value;
+
+ // The byte order used to read/write the value member. Netlink length and
+ // type members are always read/written in native order.
+ private ByteOrder mByteOrder = ByteOrder.nativeOrder();
+
+ public StructNlAttr() {}
+
+ public StructNlAttr(ByteOrder byteOrder) {
+ mByteOrder = byteOrder;
+ }
+
+ public StructNlAttr(short type, byte value) {
+ nla_type = type;
+ setValue(new byte[1]);
+ nla_value[0] = value;
+ }
+
+ public StructNlAttr(short type, short value) {
+ this(type, value, ByteOrder.nativeOrder());
+ }
+
+ public StructNlAttr(short type, short value, ByteOrder order) {
+ this(order);
+ nla_type = type;
+ setValue(new byte[Short.BYTES]);
+ getValueAsByteBuffer().putShort(value);
+ }
+
+ public StructNlAttr(short type, int value) {
+ this(type, value, ByteOrder.nativeOrder());
+ }
+
+ public StructNlAttr(short type, int value, ByteOrder order) {
+ this(order);
+ nla_type = type;
+ setValue(new byte[Integer.BYTES]);
+ getValueAsByteBuffer().putInt(value);
+ }
+
+ public StructNlAttr(short type, InetAddress ip) {
+ nla_type = type;
+ setValue(ip.getAddress());
+ }
+
+ public StructNlAttr(short type, StructNlAttr... nested) {
+ this();
+ nla_type = makeNestedType(type);
+
+ int payloadLength = 0;
+ for (StructNlAttr nla : nested) payloadLength += nla.getAlignedLength();
+ setValue(new byte[payloadLength]);
+
+ final ByteBuffer buf = getValueAsByteBuffer();
+ for (StructNlAttr nla : nested) {
+ nla.pack(buf);
+ }
+ }
+
+ public int getAlignedLength() {
+ return NetlinkConstants.alignedLengthOf(nla_len);
+ }
+
+ public ByteBuffer getValueAsByteBuffer() {
+ if (nla_value == null) { return null; }
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(nla_value);
+ byteBuffer.order(mByteOrder);
+ return byteBuffer;
+ }
+
+ public int getValueAsInt(int defaultValue) {
+ final ByteBuffer byteBuffer = getValueAsByteBuffer();
+ if (byteBuffer == null || byteBuffer.remaining() != Integer.BYTES) {
+ return defaultValue;
+ }
+ return getValueAsByteBuffer().getInt();
+ }
+
+ public InetAddress getValueAsInetAddress() {
+ if (nla_value == null) { return null; }
+
+ try {
+ return InetAddress.getByAddress(nla_value);
+ } catch (UnknownHostException ignored) {
+ return null;
+ }
+ }
+
+ public void pack(ByteBuffer byteBuffer) {
+ final ByteOrder originalOrder = byteBuffer.order();
+ final int originalPosition = byteBuffer.position();
+
+ byteBuffer.order(ByteOrder.nativeOrder());
+ try {
+ byteBuffer.putShort(nla_len);
+ byteBuffer.putShort(nla_type);
+ if (nla_value != null) byteBuffer.put(nla_value);
+ } finally {
+ byteBuffer.order(originalOrder);
+ }
+ byteBuffer.position(originalPosition + getAlignedLength());
+ }
+
+ private void setValue(byte[] value) {
+ nla_value = value;
+ nla_len = (short) (NLA_HEADERLEN + ((nla_value != null) ? nla_value.length : 0));
+ }
+
+ @Override
+ public String toString() {
+ return "StructNlAttr{ "
+ + "nla_len{" + nla_len + "}, "
+ + "nla_type{" + nla_type + "}, "
+ + "nla_value{" + NetlinkConstants.hexify(nla_value) + "}, "
+ + "}";
+ }
+}
diff --git a/common/netlinkclient/src/android/net/netlink/StructNlMsgErr.java b/common/netlinkclient/src/android/net/netlink/StructNlMsgErr.java
new file mode 100644
index 0000000..9ea4364
--- /dev/null
+++ b/common/netlinkclient/src/android/net/netlink/StructNlMsgErr.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 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 android.net.netlink;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * struct nlmsgerr
+ *
+ * see &lt;linux_src&gt;/include/uapi/linux/netlink.h
+ *
+ * @hide
+ */
+public class StructNlMsgErr {
+ public static final int STRUCT_SIZE = Integer.BYTES + StructNlMsgHdr.STRUCT_SIZE;
+
+ public static boolean hasAvailableSpace(ByteBuffer byteBuffer) {
+ return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE;
+ }
+
+ public static StructNlMsgErr parse(ByteBuffer byteBuffer) {
+ if (!hasAvailableSpace(byteBuffer)) { return null; }
+
+ // The ByteOrder must have already been set by the caller. In most
+ // cases ByteOrder.nativeOrder() is correct, with the exception
+ // of usage within unittests.
+ final StructNlMsgErr struct = new StructNlMsgErr();
+ struct.error = byteBuffer.getInt();
+ struct.msg = StructNlMsgHdr.parse(byteBuffer);
+ return struct;
+ }
+
+ public int error;
+ public StructNlMsgHdr msg;
+
+ public void pack(ByteBuffer byteBuffer) {
+ // The ByteOrder must have already been set by the caller. In most
+ // cases ByteOrder.nativeOrder() is correct, with the possible
+ // exception of usage within unittests.
+ byteBuffer.putInt(error);
+ if (msg != null) {
+ msg.pack(byteBuffer);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "StructNlMsgErr{ "
+ + "error{" + error + "}, "
+ + "msg{" + (msg == null ? "" : msg.toString()) + "} "
+ + "}";
+ }
+}
diff --git a/common/netlinkclient/src/android/net/netlink/StructNlMsgHdr.java b/common/netlinkclient/src/android/net/netlink/StructNlMsgHdr.java
new file mode 100644
index 0000000..1d03a49
--- /dev/null
+++ b/common/netlinkclient/src/android/net/netlink/StructNlMsgHdr.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2019 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 android.net.netlink;
+
+import java.nio.ByteBuffer;
+
+
+/**
+ * struct nlmsghdr
+ *
+ * see &lt;linux_src&gt;/include/uapi/linux/netlink.h
+ *
+ * @hide
+ */
+public class StructNlMsgHdr {
+ // Already aligned.
+ public static final int STRUCT_SIZE = 16;
+
+ public static final short NLM_F_REQUEST = 0x0001;
+ public static final short NLM_F_MULTI = 0x0002;
+ public static final short NLM_F_ACK = 0x0004;
+ public static final short NLM_F_ECHO = 0x0008;
+ // Flags for a GET request.
+ public static final short NLM_F_ROOT = 0x0100;
+ public static final short NLM_F_MATCH = 0x0200;
+ public static final short NLM_F_DUMP = NLM_F_ROOT|NLM_F_MATCH;
+ // Flags for a NEW request.
+ public static final short NLM_F_REPLACE = 0x100;
+ public static final short NLM_F_EXCL = 0x200;
+ public static final short NLM_F_CREATE = 0x400;
+ public static final short NLM_F_APPEND = 0x800;
+
+
+ public static String stringForNlMsgFlags(short flags) {
+ final StringBuilder sb = new StringBuilder();
+ if ((flags & NLM_F_REQUEST) != 0) {
+ sb.append("NLM_F_REQUEST");
+ }
+ if ((flags & NLM_F_MULTI) != 0) {
+ if (sb.length() > 0) { sb.append("|"); }
+ sb.append("NLM_F_MULTI");
+ }
+ if ((flags & NLM_F_ACK) != 0) {
+ if (sb.length() > 0) { sb.append("|"); }
+ sb.append("NLM_F_ACK");
+ }
+ if ((flags & NLM_F_ECHO) != 0) {
+ if (sb.length() > 0) { sb.append("|"); }
+ sb.append("NLM_F_ECHO");
+ }
+ if ((flags & NLM_F_ROOT) != 0) {
+ if (sb.length() > 0) { sb.append("|"); }
+ sb.append("NLM_F_ROOT");
+ }
+ if ((flags & NLM_F_MATCH) != 0) {
+ if (sb.length() > 0) { sb.append("|"); }
+ sb.append("NLM_F_MATCH");
+ }
+ return sb.toString();
+ }
+
+ public static boolean hasAvailableSpace(ByteBuffer byteBuffer) {
+ return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE;
+ }
+
+ public static StructNlMsgHdr parse(ByteBuffer byteBuffer) {
+ if (!hasAvailableSpace(byteBuffer)) { return null; }
+
+ // The ByteOrder must have already been set by the caller. In most
+ // cases ByteOrder.nativeOrder() is correct, with the exception
+ // of usage within unittests.
+ final StructNlMsgHdr struct = new StructNlMsgHdr();
+ struct.nlmsg_len = byteBuffer.getInt();
+ struct.nlmsg_type = byteBuffer.getShort();
+ struct.nlmsg_flags = byteBuffer.getShort();
+ struct.nlmsg_seq = byteBuffer.getInt();
+ struct.nlmsg_pid = byteBuffer.getInt();
+
+ if (struct.nlmsg_len < STRUCT_SIZE) {
+ // Malformed.
+ return null;
+ }
+ return struct;
+ }
+
+ public int nlmsg_len;
+ public short nlmsg_type;
+ public short nlmsg_flags;
+ public int nlmsg_seq;
+ public int nlmsg_pid;
+
+ public StructNlMsgHdr() {
+ nlmsg_len = 0;
+ nlmsg_type = 0;
+ nlmsg_flags = 0;
+ nlmsg_seq = 0;
+ nlmsg_pid = 0;
+ }
+
+ public void pack(ByteBuffer byteBuffer) {
+ // The ByteOrder must have already been set by the caller. In most
+ // cases ByteOrder.nativeOrder() is correct, with the possible
+ // exception of usage within unittests.
+ byteBuffer.putInt(nlmsg_len);
+ byteBuffer.putShort(nlmsg_type);
+ byteBuffer.putShort(nlmsg_flags);
+ byteBuffer.putInt(nlmsg_seq);
+ byteBuffer.putInt(nlmsg_pid);
+ }
+
+ @Override
+ public String toString() {
+ final String typeStr = "" + nlmsg_type
+ + "(" + NetlinkConstants.stringForNlMsgType(nlmsg_type) + ")";
+ final String flagsStr = "" + nlmsg_flags
+ + "(" + stringForNlMsgFlags(nlmsg_flags) + ")";
+ return "StructNlMsgHdr{ "
+ + "nlmsg_len{" + nlmsg_len + "}, "
+ + "nlmsg_type{" + typeStr + "}, "
+ + "nlmsg_flags{" + flagsStr + ")}, "
+ + "nlmsg_seq{" + nlmsg_seq + "}, "
+ + "nlmsg_pid{" + nlmsg_pid + "} "
+ + "}";
+ }
+}
diff --git a/tests/unit/src/android/net/ip/InterfaceControllerTest.java b/tests/unit/src/android/net/ip/InterfaceControllerTest.java
new file mode 100644
index 0000000..02bc73c
--- /dev/null
+++ b/tests/unit/src/android/net/ip/InterfaceControllerTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 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 android.net.ip;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.net.INetd;
+import android.net.InetAddresses;
+import android.net.InterfaceConfigurationParcel;
+import android.net.LinkAddress;
+import android.net.util.SharedLog;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class InterfaceControllerTest {
+ private static final String TEST_IFACE = "testif";
+ private static final String TEST_IPV4_ADDR = "192.168.123.28";
+ private static final int TEST_PREFIXLENGTH = 31;
+
+ @Mock private INetd mNetd;
+ @Mock private SharedLog mLog;
+ @Captor private ArgumentCaptor<InterfaceConfigurationParcel> mConfigCaptor;
+
+ private InterfaceController mController;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mController = new InterfaceController(TEST_IFACE, mNetd, mLog);
+
+ doNothing().when(mNetd).interfaceSetCfg(mConfigCaptor.capture());
+ }
+
+ @Test
+ public void testSetIPv4Address() throws Exception {
+ mController.setIPv4Address(
+ new LinkAddress(InetAddresses.parseNumericAddress(TEST_IPV4_ADDR),
+ TEST_PREFIXLENGTH));
+ verify(mNetd, times(1)).interfaceSetCfg(any());
+ final InterfaceConfigurationParcel parcel = mConfigCaptor.getValue();
+ assertEquals(TEST_IFACE, parcel.ifName);
+ assertEquals(TEST_IPV4_ADDR, parcel.ipv4Addr);
+ assertEquals(TEST_PREFIXLENGTH, parcel.prefixLength);
+ assertEquals("", parcel.hwAddr);
+ assertArrayEquals(new String[0], parcel.flags);
+ }
+
+ @Test
+ public void testClearIPv4Address() throws Exception {
+ mController.clearIPv4Address();
+ verify(mNetd, times(1)).interfaceSetCfg(any());
+ final InterfaceConfigurationParcel parcel = mConfigCaptor.getValue();
+ assertEquals(TEST_IFACE, parcel.ifName);
+ assertEquals("0.0.0.0", parcel.ipv4Addr);
+ assertEquals(0, parcel.prefixLength);
+ assertEquals("", parcel.hwAddr);
+ assertArrayEquals(new String[0], parcel.flags);
+ }
+}
diff --git a/tests/unit/src/android/net/netlink/ConntrackMessageTest.java b/tests/unit/src/android/net/netlink/ConntrackMessageTest.java
new file mode 100644
index 0000000..5c86757
--- /dev/null
+++ b/tests/unit/src/android/net/netlink/ConntrackMessageTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2017 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 android.net.netlink;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ConntrackMessageTest {
+ private static final boolean USING_LE = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN);
+
+ // Example 1: TCP (192.168.43.209, 44333) -> (23.211.13.26, 443)
+ public static final String CT_V4UPDATE_TCP_HEX =
+ // struct nlmsghdr
+ "50000000" + // length = 80
+ "0001" + // type = (1 << 8) | 0
+ "0501" + // flags
+ "01000000" + // seqno = 1
+ "00000000" + // pid = 0
+ // struct nfgenmsg
+ "02" + // nfgen_family = AF_INET
+ "00" + // version = NFNETLINK_V0
+ "0000" + // res_id
+ // struct nlattr
+ "3400" + // nla_len = 52
+ "0180" + // nla_type = nested CTA_TUPLE_ORIG
+ // struct nlattr
+ "1400" + // nla_len = 20
+ "0180" + // nla_type = nested CTA_TUPLE_IP
+ "0800 0100 C0A82BD1" + // nla_type=CTA_IP_V4_SRC, ip=192.168.43.209
+ "0800 0200 17D30D1A" + // nla_type=CTA_IP_V4_DST, ip=23.211.13.26
+ // struct nlattr
+ "1C00" + // nla_len = 28
+ "0280" + // nla_type = nested CTA_TUPLE_PROTO
+ "0500 0100 06 000000" + // nla_type=CTA_PROTO_NUM, proto=6
+ "0600 0200 AD2D 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=44333 (big endian)
+ "0600 0300 01BB 0000" + // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian)
+ // struct nlattr
+ "0800" + // nla_len = 8
+ "0700" + // nla_type = CTA_TIMEOUT
+ "00069780"; // nla_value = 432000 (big endian)
+ public static final byte[] CT_V4UPDATE_TCP_BYTES =
+ HexEncoding.decode(CT_V4UPDATE_TCP_HEX.replaceAll(" ", "").toCharArray(), false);
+
+ // Example 2: UDP (100.96.167.146, 37069) -> (216.58.197.10, 443)
+ public static final String CT_V4UPDATE_UDP_HEX =
+ // struct nlmsghdr
+ "50000000" + // length = 80
+ "0001" + // type = (1 << 8) | 0
+ "0501" + // flags
+ "01000000" + // seqno = 1
+ "00000000" + // pid = 0
+ // struct nfgenmsg
+ "02" + // nfgen_family = AF_INET
+ "00" + // version = NFNETLINK_V0
+ "0000" + // res_id
+ // struct nlattr
+ "3400" + // nla_len = 52
+ "0180" + // nla_type = nested CTA_TUPLE_ORIG
+ // struct nlattr
+ "1400" + // nla_len = 20
+ "0180" + // nla_type = nested CTA_TUPLE_IP
+ "0800 0100 6460A792" + // nla_type=CTA_IP_V4_SRC, ip=100.96.167.146
+ "0800 0200 D83AC50A" + // nla_type=CTA_IP_V4_DST, ip=216.58.197.10
+ // struct nlattr
+ "1C00" + // nla_len = 28
+ "0280" + // nla_type = nested CTA_TUPLE_PROTO
+ "0500 0100 11 000000" + // nla_type=CTA_PROTO_NUM, proto=17
+ "0600 0200 90CD 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=37069 (big endian)
+ "0600 0300 01BB 0000" + // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian)
+ // struct nlattr
+ "0800" + // nla_len = 8
+ "0700" + // nla_type = CTA_TIMEOUT
+ "000000B4"; // nla_value = 180 (big endian)
+ public static final byte[] CT_V4UPDATE_UDP_BYTES =
+ HexEncoding.decode(CT_V4UPDATE_UDP_HEX.replaceAll(" ", "").toCharArray(), false);
+
+ @Test
+ public void testConntrackIPv4TcpTimeoutUpdate() throws Exception {
+ assumeTrue(USING_LE);
+
+ final byte[] tcp = ConntrackMessage.newIPv4TimeoutUpdateRequest(
+ OsConstants.IPPROTO_TCP,
+ (Inet4Address) InetAddress.getByName("192.168.43.209"), 44333,
+ (Inet4Address) InetAddress.getByName("23.211.13.26"), 443,
+ 432000);
+ assertArrayEquals(CT_V4UPDATE_TCP_BYTES, tcp);
+ }
+
+ @Test
+ public void testConntrackIPv4UdpTimeoutUpdate() throws Exception {
+ assumeTrue(USING_LE);
+
+ final byte[] udp = ConntrackMessage.newIPv4TimeoutUpdateRequest(
+ OsConstants.IPPROTO_UDP,
+ (Inet4Address) InetAddress.getByName("100.96.167.146"), 37069,
+ (Inet4Address) InetAddress.getByName("216.58.197.10"), 443,
+ 180);
+ assertArrayEquals(CT_V4UPDATE_UDP_BYTES, udp);
+ }
+}
diff --git a/tests/unit/src/android/net/netlink/InetDiagSocketTest.java b/tests/unit/src/android/net/netlink/InetDiagSocketTest.java
new file mode 100644
index 0000000..84c5784
--- /dev/null
+++ b/tests/unit/src/android/net/netlink/InetDiagSocketTest.java
@@ -0,0 +1,448 @@
+/*
+ * 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 android.net.netlink;
+
+import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_STREAM;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.netlink.StructNlMsgHdr;
+import android.os.Process;
+import android.system.Os;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileDescriptor;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class InetDiagSocketTest {
+ private final String TAG = "InetDiagSocketTest";
+ private ConnectivityManager mCm;
+ private Context mContext;
+ private final static int SOCKET_TIMEOUT_MS = 100;
+
+ @Before
+ public void setUp() throws Exception {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ mContext = instrumentation.getTargetContext();
+ mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+
+ private class Connection {
+ public int socketDomain;
+ public int socketType;
+ public InetAddress localAddress;
+ public InetAddress remoteAddress;
+ public InetAddress localhostAddress;
+ public InetSocketAddress local;
+ public InetSocketAddress remote;
+ public int protocol;
+ public FileDescriptor localFd;
+ public FileDescriptor remoteFd;
+
+ public FileDescriptor createSocket() throws Exception {
+ return Os.socket(socketDomain, socketType, protocol);
+ }
+
+ public Connection(String to, String from) throws Exception {
+ remoteAddress = InetAddress.getByName(to);
+ if (from != null) {
+ localAddress = InetAddress.getByName(from);
+ } else {
+ localAddress = (remoteAddress instanceof Inet4Address) ?
+ Inet4Address.getByName("localhost") : Inet6Address.getByName("::");
+ }
+ if ((localAddress instanceof Inet4Address) && (remoteAddress instanceof Inet4Address)) {
+ socketDomain = AF_INET;
+ localhostAddress = Inet4Address.getByName("localhost");
+ } else {
+ socketDomain = AF_INET6;
+ localhostAddress = Inet6Address.getByName("::");
+ }
+ }
+
+ public void close() throws Exception {
+ Os.close(localFd);
+ }
+ }
+
+ private class TcpConnection extends Connection {
+ public TcpConnection(String to, String from) throws Exception {
+ super(to, from);
+ protocol = IPPROTO_TCP;
+ socketType = SOCK_STREAM;
+
+ remoteFd = createSocket();
+ Os.bind(remoteFd, remoteAddress, 0);
+ Os.listen(remoteFd, 10);
+ int remotePort = ((InetSocketAddress) Os.getsockname(remoteFd)).getPort();
+
+ localFd = createSocket();
+ Os.bind(localFd, localAddress, 0);
+ Os.connect(localFd, remoteAddress, remotePort);
+
+ local = (InetSocketAddress) Os.getsockname(localFd);
+ remote = (InetSocketAddress) Os.getpeername(localFd);
+ }
+
+ public void close() throws Exception {
+ super.close();
+ Os.close(remoteFd);
+ }
+ }
+ private class UdpConnection extends Connection {
+ public UdpConnection(String to, String from) throws Exception {
+ super(to, from);
+ protocol = IPPROTO_UDP;
+ socketType = SOCK_DGRAM;
+
+ remoteFd = null;
+ localFd = createSocket();
+ Os.bind(localFd, localAddress, 0);
+
+ Os.connect(localFd, remoteAddress, 7);
+ local = (InetSocketAddress) Os.getsockname(localFd);
+ remote = new InetSocketAddress(remoteAddress, 7);
+ }
+ }
+
+ private void checkConnectionOwnerUid(int protocol, InetSocketAddress local,
+ InetSocketAddress remote, boolean expectSuccess) {
+ final int uid = mCm.getConnectionOwnerUid(protocol, local, remote);
+
+ if (expectSuccess) {
+ assertEquals(Process.myUid(), uid);
+ } else {
+ assertNotEquals(Process.myUid(), uid);
+ }
+ }
+
+ private int findLikelyFreeUdpPort(UdpConnection conn) throws Exception {
+ UdpConnection udp = new UdpConnection(conn.remoteAddress.getHostAddress(),
+ conn.localAddress.getHostAddress());
+ final int localPort = udp.local.getPort();
+ udp.close();
+ return localPort;
+ }
+
+ /**
+ * Create a test connection for UDP and TCP sockets and verify that this
+ * {protocol, local, remote} socket result in receiving a valid UID.
+ */
+ public void checkGetConnectionOwnerUid(String to, String from) throws Exception {
+ TcpConnection tcp = new TcpConnection(to, from);
+ checkConnectionOwnerUid(tcp.protocol, tcp.local, tcp.remote, true);
+ checkConnectionOwnerUid(IPPROTO_UDP, tcp.local, tcp.remote, false);
+ checkConnectionOwnerUid(tcp.protocol, new InetSocketAddress(0), tcp.remote, false);
+ checkConnectionOwnerUid(tcp.protocol, tcp.local, new InetSocketAddress(0), false);
+ tcp.close();
+
+ UdpConnection udp = new UdpConnection(to,from);
+ checkConnectionOwnerUid(udp.protocol, udp.local, udp.remote, true);
+ checkConnectionOwnerUid(IPPROTO_TCP, udp.local, udp.remote, false);
+ checkConnectionOwnerUid(udp.protocol, new InetSocketAddress(findLikelyFreeUdpPort(udp)),
+ udp.remote, false);
+ udp.close();
+ }
+
+ @Test
+ public void testGetConnectionOwnerUid() throws Exception {
+ checkGetConnectionOwnerUid("::", null);
+ checkGetConnectionOwnerUid("::", "::");
+ checkGetConnectionOwnerUid("0.0.0.0", null);
+ checkGetConnectionOwnerUid("0.0.0.0", "0.0.0.0");
+ checkGetConnectionOwnerUid("127.0.0.1", null);
+ checkGetConnectionOwnerUid("127.0.0.1", "127.0.0.2");
+ checkGetConnectionOwnerUid("::1", null);
+ checkGetConnectionOwnerUid("::1", "::1");
+ }
+
+ /* Verify fix for b/141603906 */
+ @Test
+ public void testB141603906() throws Exception {
+ final InetSocketAddress src = new InetSocketAddress(0);
+ final InetSocketAddress dst = new InetSocketAddress(0);
+ final int numThreads = 8;
+ final int numSockets = 5000;
+ final Thread[] threads = new Thread[numThreads];
+
+ for (int i = 0; i < numThreads; i++) {
+ threads[i] = new Thread(() -> {
+ for (int j = 0; j < numSockets; j++) {
+ mCm.getConnectionOwnerUid(IPPROTO_TCP, src, dst);
+ }
+ });
+ }
+
+ for (Thread thread : threads) {
+ thread.start();
+ }
+
+ for (Thread thread : threads) {
+ thread.join();
+ }
+ }
+
+ // Hexadecimal representation of InetDiagReqV2 request.
+ private static final String INET_DIAG_REQ_V2_UDP_INET4_HEX =
+ // struct nlmsghdr
+ "48000000" + // length = 72
+ "1400" + // type = SOCK_DIAG_BY_FAMILY
+ "0103" + // flags = NLM_F_REQUEST | NLM_F_DUMP
+ "00000000" + // seqno
+ "00000000" + // pid (0 == kernel)
+ // struct inet_diag_req_v2
+ "02" + // family = AF_INET
+ "11" + // protcol = IPPROTO_UDP
+ "00" + // idiag_ext
+ "00" + // pad
+ "ffffffff" + // idiag_states
+ // inet_diag_sockid
+ "a5de" + // idiag_sport = 42462
+ "b971" + // idiag_dport = 47473
+ "0a006402000000000000000000000000" + // idiag_src = 10.0.100.2
+ "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8
+ "00000000" + // idiag_if
+ "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE
+ private static final byte[] INET_DIAG_REQ_V2_UDP_INET4_BYTES =
+ HexEncoding.decode(INET_DIAG_REQ_V2_UDP_INET4_HEX.toCharArray(), false);
+
+ @Test
+ public void testInetDiagReqV2UdpInet4() throws Exception {
+ InetSocketAddress local = new InetSocketAddress(InetAddress.getByName("10.0.100.2"),
+ 42462);
+ InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"),
+ 47473);
+ final byte[] msg = InetDiagMessage.InetDiagReqV2(IPPROTO_UDP, local, remote, AF_INET,
+ (short) (NLM_F_REQUEST | NLM_F_DUMP));
+ assertArrayEquals(INET_DIAG_REQ_V2_UDP_INET4_BYTES, msg);
+ }
+
+ // Hexadecimal representation of InetDiagReqV2 request.
+ private static final String INET_DIAG_REQ_V2_TCP_INET6_HEX =
+ // struct nlmsghdr
+ "48000000" + // length = 72
+ "1400" + // type = SOCK_DIAG_BY_FAMILY
+ "0100" + // flags = NLM_F_REQUEST
+ "00000000" + // seqno
+ "00000000" + // pid (0 == kernel)
+ // struct inet_diag_req_v2
+ "0a" + // family = AF_INET6
+ "06" + // protcol = IPPROTO_TCP
+ "00" + // idiag_ext
+ "00" + // pad
+ "ffffffff" + // idiag_states
+ // inet_diag_sockid
+ "a5de" + // idiag_sport = 42462
+ "b971" + // idiag_dport = 47473
+ "fe8000000000000086c9b2fffe6aed4b" + // idiag_src = fe80::86c9:b2ff:fe6a:ed4b
+ "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8
+ "00000000" + // idiag_if
+ "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE
+ private static final byte[] INET_DIAG_REQ_V2_TCP_INET6_BYTES =
+ HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET6_HEX.toCharArray(), false);
+
+ @Test
+ public void testInetDiagReqV2TcpInet6() throws Exception {
+ InetSocketAddress local = new InetSocketAddress(
+ InetAddress.getByName("fe80::86c9:b2ff:fe6a:ed4b"), 42462);
+ InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"),
+ 47473);
+ byte[] msg = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, local, remote, AF_INET6,
+ NLM_F_REQUEST);
+
+ assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_BYTES, msg);
+ }
+
+ // Hexadecimal representation of InetDiagReqV2 request with extension, INET_DIAG_INFO.
+ private static final String INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_HEX =
+ // struct nlmsghdr
+ "48000000" + // length = 72
+ "1400" + // type = SOCK_DIAG_BY_FAMILY
+ "0100" + // flags = NLM_F_REQUEST
+ "00000000" + // seqno
+ "00000000" + // pid (0 == kernel)
+ // struct inet_diag_req_v2
+ "02" + // family = AF_INET
+ "06" + // protcol = IPPROTO_TCP
+ "02" + // idiag_ext = INET_DIAG_INFO
+ "00" + // pad
+ "ffffffff" + // idiag_states
+ // inet_diag_sockid
+ "3039" + // idiag_sport = 12345
+ "d431" + // idiag_dport = 54321
+ "01020304000000000000000000000000" + // idiag_src = 1.2.3.4
+ "08080404000000000000000000000000" + // idiag_dst = 8.8.4.4
+ "00000000" + // idiag_if
+ "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE
+
+ private static final byte[] INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_BYTES =
+ HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_HEX.toCharArray(), false);
+ private static final int TCP_ALL_STATES = 0xffffffff;
+ @Test
+ public void testInetDiagReqV2TcpInetWithExt() throws Exception {
+ InetSocketAddress local = new InetSocketAddress(
+ InetAddress.getByName("1.2.3.4"), 12345);
+ InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.4.4"),
+ 54321);
+ byte[] msg = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, local, remote, AF_INET,
+ NLM_F_REQUEST, 0 /* pad */, 2 /* idiagExt */, TCP_ALL_STATES);
+
+ assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_BYTES, msg);
+
+ local = new InetSocketAddress(
+ InetAddress.getByName("fe80::86c9:b2ff:fe6a:ed4b"), 42462);
+ remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"),
+ 47473);
+ msg = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, local, remote, AF_INET6,
+ NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES);
+
+ assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_BYTES, msg);
+ }
+
+ // Hexadecimal representation of InetDiagReqV2 request with no socket specified.
+ private static final String INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_HEX =
+ // struct nlmsghdr
+ "48000000" + // length = 72
+ "1400" + // type = SOCK_DIAG_BY_FAMILY
+ "0100" + // flags = NLM_F_REQUEST
+ "00000000" + // seqno
+ "00000000" + // pid (0 == kernel)
+ // struct inet_diag_req_v2
+ "0a" + // family = AF_INET6
+ "06" + // protcol = IPPROTO_TCP
+ "00" + // idiag_ext
+ "00" + // pad
+ "ffffffff" + // idiag_states
+ // inet_diag_sockid
+ "0000" + // idiag_sport
+ "0000" + // idiag_dport
+ "00000000000000000000000000000000" + // idiag_src
+ "00000000000000000000000000000000" + // idiag_dst
+ "00000000" + // idiag_if
+ "0000000000000000"; // idiag_cookie
+
+ private static final byte[] INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFED_BYTES =
+ HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_HEX.toCharArray(), false);
+
+ @Test
+ public void testInetDiagReqV2TcpInet6NoIdSpecified() throws Exception {
+ InetSocketAddress local = new InetSocketAddress(
+ InetAddress.getByName("fe80::fe6a:ed4b"), 12345);
+ InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.4.4"),
+ 54321);
+ // Verify no socket specified if either local or remote socket address is null.
+ byte[] msgExt = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, null, null, AF_INET6,
+ NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES);
+ byte[] msg;
+ try {
+ msg = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, null, remote, AF_INET6,
+ NLM_F_REQUEST);
+ fail("Both remote and local should be null, expected UnknownHostException");
+ } catch (NullPointerException e) {
+ }
+
+ try {
+ msg = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, local, null, AF_INET6,
+ NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES);
+ fail("Both remote and local should be null, expected UnknownHostException");
+ } catch (NullPointerException e) {
+ }
+
+ msg = InetDiagMessage.InetDiagReqV2(IPPROTO_TCP, null, null, AF_INET6,
+ NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES);
+ assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFED_BYTES, msg);
+ assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFED_BYTES, msgExt);
+ }
+
+ // Hexadecimal representation of InetDiagReqV2 request.
+ private static final String INET_DIAG_MSG_HEX =
+ // struct nlmsghdr
+ "58000000" + // length = 88
+ "1400" + // type = SOCK_DIAG_BY_FAMILY
+ "0200" + // flags = NLM_F_MULTI
+ "00000000" + // seqno
+ "f5220000" + // pid (0 == kernel)
+ // struct inet_diag_msg
+ "0a" + // family = AF_INET6
+ "01" + // idiag_state
+ "00" + // idiag_timer
+ "00" + // idiag_retrans
+ // inet_diag_sockid
+ "a817" + // idiag_sport = 43031
+ "960f" + // idiag_dport = 38415
+ "fe8000000000000086c9b2fffe6aed4b" + // idiag_src = fe80::86c9:b2ff:fe6a:ed4b
+ "00000000000000000000ffff08080808" + // idiag_dst = 8.8.8.8
+ "00000000" + // idiag_if
+ "ffffffffffffffff" + // idiag_cookie = INET_DIAG_NOCOOKIE
+ "00000000" + // idiag_expires
+ "00000000" + // idiag_rqueue
+ "00000000" + // idiag_wqueue
+ "a3270000" + // idiag_uid
+ "A57E1900"; // idiag_inode
+ private static final byte[] INET_DIAG_MSG_BYTES =
+ HexEncoding.decode(INET_DIAG_MSG_HEX.toCharArray(), false);
+
+ @Test
+ public void testParseInetDiagResponse() throws Exception {
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(INET_DIAG_MSG_BYTES);
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+ final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer);
+ assertNotNull(msg);
+
+ assertTrue(msg instanceof InetDiagMessage);
+ final InetDiagMessage inetDiagMsg = (InetDiagMessage) msg;
+ assertEquals(10147, inetDiagMsg.mStructInetDiagMsg.idiag_uid);
+
+ final StructNlMsgHdr hdr = inetDiagMsg.getHeader();
+ assertNotNull(hdr);
+ assertEquals(NetlinkConstants.SOCK_DIAG_BY_FAMILY, hdr.nlmsg_type);
+ assertEquals(StructNlMsgHdr.NLM_F_MULTI, hdr.nlmsg_flags);
+ assertEquals(0, hdr.nlmsg_seq);
+ assertEquals(8949, hdr.nlmsg_pid);
+ }
+}
diff --git a/tests/unit/src/android/net/netlink/NetlinkErrorMessageTest.java b/tests/unit/src/android/net/netlink/NetlinkErrorMessageTest.java
new file mode 100644
index 0000000..44ab605
--- /dev/null
+++ b/tests/unit/src/android/net/netlink/NetlinkErrorMessageTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2015 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 android.net.netlink;
+
+import static android.net.netlink.StructNlMsgHdr.NLM_F_ACK;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REPLACE;
+import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.netlink.NetlinkConstants;
+import android.net.netlink.NetlinkErrorMessage;
+import android.net.netlink.NetlinkMessage;
+import android.net.netlink.StructNlMsgErr;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetlinkErrorMessageTest {
+ private final String TAG = "NetlinkErrorMessageTest";
+
+ // Hexadecimal representation of packet capture.
+ public static final String NLM_ERROR_OK_HEX =
+ // struct nlmsghdr
+ "24000000" + // length = 36
+ "0200" + // type = 2 (NLMSG_ERROR)
+ "0000" + // flags
+ "26350000" + // seqno
+ "64100000" + // pid = userspace process
+ // error integer
+ "00000000" + // "errno" (0 == OK)
+ // struct nlmsghdr
+ "30000000" + // length (48) of original request
+ "1C00" + // type = 28 (RTM_NEWNEIGH)
+ "0501" + // flags (NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE)
+ "26350000" + // seqno
+ "00000000"; // pid = kernel
+ public static final byte[] NLM_ERROR_OK =
+ HexEncoding.decode(NLM_ERROR_OK_HEX.toCharArray(), false);
+
+ @Test
+ public void testParseNlmErrorOk() {
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(NLM_ERROR_OK);
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing.
+ final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer);
+ assertNotNull(msg);
+ assertTrue(msg instanceof NetlinkErrorMessage);
+ final NetlinkErrorMessage errorMsg = (NetlinkErrorMessage) msg;
+
+ final StructNlMsgHdr hdr = errorMsg.getHeader();
+ assertNotNull(hdr);
+ assertEquals(36, hdr.nlmsg_len);
+ assertEquals(NetlinkConstants.NLMSG_ERROR, hdr.nlmsg_type);
+ assertEquals(0, hdr.nlmsg_flags);
+ assertEquals(13606, hdr.nlmsg_seq);
+ assertEquals(4196, hdr.nlmsg_pid);
+
+ final StructNlMsgErr err = errorMsg.getNlMsgError();
+ assertNotNull(err);
+ assertEquals(0, err.error);
+ assertNotNull(err.msg);
+ assertEquals(48, err.msg.nlmsg_len);
+ assertEquals(NetlinkConstants.RTM_NEWNEIGH, err.msg.nlmsg_type);
+ assertEquals((NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE), err.msg.nlmsg_flags);
+ assertEquals(13606, err.msg.nlmsg_seq);
+ assertEquals(0, err.msg.nlmsg_pid);
+ }
+}
diff --git a/tests/unit/src/android/net/netlink/NetlinkSocketTest.java b/tests/unit/src/android/net/netlink/NetlinkSocketTest.java
new file mode 100644
index 0000000..3916578
--- /dev/null
+++ b/tests/unit/src/android/net/netlink/NetlinkSocketTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015 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 android.net.netlink;
+
+import static android.net.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE;
+import static android.system.OsConstants.NETLINK_ROUTE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.netlink.NetlinkSocket;
+import android.net.netlink.RtNetlinkNeighborMessage;
+import android.net.netlink.StructNlMsgHdr;
+import android.system.NetlinkSocketAddress;
+import android.system.Os;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.io.IoUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileDescriptor;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetlinkSocketTest {
+ private final String TAG = "NetlinkSocketTest";
+
+ @Test
+ public void testBasicWorkingGetNeighborsQuery() throws Exception {
+ final FileDescriptor fd = NetlinkSocket.forProto(NETLINK_ROUTE);
+ assertNotNull(fd);
+
+ NetlinkSocket.connectToKernel(fd);
+
+ final NetlinkSocketAddress localAddr = (NetlinkSocketAddress) Os.getsockname(fd);
+ assertNotNull(localAddr);
+ assertEquals(0, localAddr.getGroupsMask());
+ assertTrue(0 != localAddr.getPortId());
+
+ final int TEST_SEQNO = 5;
+ final byte[] req = RtNetlinkNeighborMessage.newGetNeighborsRequest(TEST_SEQNO);
+ assertNotNull(req);
+
+ final long TIMEOUT = 500;
+ assertEquals(req.length, NetlinkSocket.sendMessage(fd, req, 0, req.length, TIMEOUT));
+
+ int neighMessageCount = 0;
+ int doneMessageCount = 0;
+
+ while (doneMessageCount == 0) {
+ ByteBuffer response = NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT);
+ assertNotNull(response);
+ assertTrue(StructNlMsgHdr.STRUCT_SIZE <= response.limit());
+ assertEquals(0, response.position());
+ assertEquals(ByteOrder.nativeOrder(), response.order());
+
+ // Verify the messages at least appears minimally reasonable.
+ while (response.remaining() > 0) {
+ final NetlinkMessage msg = NetlinkMessage.parse(response);
+ assertNotNull(msg);
+ final StructNlMsgHdr hdr = msg.getHeader();
+ assertNotNull(hdr);
+
+ if (hdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) {
+ doneMessageCount++;
+ continue;
+ }
+
+ assertEquals(NetlinkConstants.RTM_NEWNEIGH, hdr.nlmsg_type);
+ assertTrue(msg instanceof RtNetlinkNeighborMessage);
+ assertTrue((hdr.nlmsg_flags & StructNlMsgHdr.NLM_F_MULTI) != 0);
+ assertEquals(TEST_SEQNO, hdr.nlmsg_seq);
+ assertEquals(localAddr.getPortId(), hdr.nlmsg_pid);
+
+ neighMessageCount++;
+ }
+ }
+
+ assertEquals(1, doneMessageCount);
+ // TODO: make sure this test passes sanely in airplane mode.
+ assertTrue(neighMessageCount > 0);
+
+ IoUtils.closeQuietly(fd);
+ }
+}
diff --git a/tests/unit/src/android/net/netlink/RtNetlinkNeighborMessageTest.java b/tests/unit/src/android/net/netlink/RtNetlinkNeighborMessageTest.java
new file mode 100644
index 0000000..72e6bca
--- /dev/null
+++ b/tests/unit/src/android/net/netlink/RtNetlinkNeighborMessageTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2019 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 android.net.netlink;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.netlink.NetlinkConstants;
+import android.net.netlink.NetlinkMessage;
+import android.net.netlink.RtNetlinkNeighborMessage;
+import android.net.netlink.StructNdMsg;
+import android.net.netlink.StructNlMsgHdr;
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RtNetlinkNeighborMessageTest {
+ private final String TAG = "RtNetlinkNeighborMessageTest";
+
+ // Hexadecimal representation of packet capture.
+ public static final String RTM_DELNEIGH_HEX =
+ // struct nlmsghdr
+ "4c000000" + // length = 76
+ "1d00" + // type = 29 (RTM_DELNEIGH)
+ "0000" + // flags
+ "00000000" + // seqno
+ "00000000" + // pid (0 == kernel)
+ // struct ndmsg
+ "02" + // family
+ "00" + // pad1
+ "0000" + // pad2
+ "15000000" + // interface index (21 == wlan0, on test device)
+ "0400" + // NUD state (0x04 == NUD_STALE)
+ "00" + // flags
+ "01" + // type
+ // struct nlattr: NDA_DST
+ "0800" + // length = 8
+ "0100" + // type (1 == NDA_DST, for neighbor messages)
+ "c0a89ffe" + // IPv4 address (== 192.168.159.254)
+ // struct nlattr: NDA_LLADDR
+ "0a00" + // length = 10
+ "0200" + // type (2 == NDA_LLADDR, for neighbor messages)
+ "00005e000164" + // MAC Address (== 00:00:5e:00:01:64)
+ "0000" + // padding, for 4 byte alignment
+ // struct nlattr: NDA_PROBES
+ "0800" + // length = 8
+ "0400" + // type (4 == NDA_PROBES, for neighbor messages)
+ "01000000" + // number of probes
+ // struct nlattr: NDA_CACHEINFO
+ "1400" + // length = 20
+ "0300" + // type (3 == NDA_CACHEINFO, for neighbor messages)
+ "05190000" + // ndm_used, as "clock ticks ago"
+ "05190000" + // ndm_confirmed, as "clock ticks ago"
+ "190d0000" + // ndm_updated, as "clock ticks ago"
+ "00000000"; // ndm_refcnt
+ public static final byte[] RTM_DELNEIGH =
+ HexEncoding.decode(RTM_DELNEIGH_HEX.toCharArray(), false);
+
+ // Hexadecimal representation of packet capture.
+ public static final String RTM_NEWNEIGH_HEX =
+ // struct nlmsghdr
+ "58000000" + // length = 88
+ "1c00" + // type = 28 (RTM_NEWNEIGH)
+ "0000" + // flags
+ "00000000" + // seqno
+ "00000000" + // pid (0 == kernel)
+ // struct ndmsg
+ "0a" + // family
+ "00" + // pad1
+ "0000" + // pad2
+ "15000000" + // interface index (21 == wlan0, on test device)
+ "0400" + // NUD state (0x04 == NUD_STALE)
+ "80" + // flags
+ "01" + // type
+ // struct nlattr: NDA_DST
+ "1400" + // length = 20
+ "0100" + // type (1 == NDA_DST, for neighbor messages)
+ "fe8000000000000086c9b2fffe6aed4b" + // IPv6 address (== fe80::86c9:b2ff:fe6a:ed4b)
+ // struct nlattr: NDA_LLADDR
+ "0a00" + // length = 10
+ "0200" + // type (2 == NDA_LLADDR, for neighbor messages)
+ "84c9b26aed4b" + // MAC Address (== 84:c9:b2:6a:ed:4b)
+ "0000" + // padding, for 4 byte alignment
+ // struct nlattr: NDA_PROBES
+ "0800" + // length = 8
+ "0400" + // type (4 == NDA_PROBES, for neighbor messages)
+ "01000000" + // number of probes
+ // struct nlattr: NDA_CACHEINFO
+ "1400" + // length = 20
+ "0300" + // type (3 == NDA_CACHEINFO, for neighbor messages)
+ "eb0e0000" + // ndm_used, as "clock ticks ago"
+ "861f0000" + // ndm_confirmed, as "clock ticks ago"
+ "00000000" + // ndm_updated, as "clock ticks ago"
+ "05000000"; // ndm_refcnt
+ public static final byte[] RTM_NEWNEIGH =
+ HexEncoding.decode(RTM_NEWNEIGH_HEX.toCharArray(), false);
+
+ // An example of the full response from an RTM_GETNEIGH query.
+ private static final String RTM_GETNEIGH_RESPONSE_HEX =
+ // <-- struct nlmsghr -->|<-- struct ndmsg -->|<-- struct nlattr: NDA_DST -->|<-- NDA_LLADDR -->|<-- NDA_PROBES -->|<-- NDA_CACHEINFO -->|
+ "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff020000000000000000000000000001 0a00 0200 333300000001 0000 0800 0400 00000000 1400 0300 a2280000 32110000 32110000 01000000" +
+ "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ff000001 0a00 0200 3333ff000001 0000 0800 0400 00000000 1400 0300 0d280000 9d100000 9d100000 00000000" +
+ "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 0400 80 01 1400 0100 20010db800040ca00000000000000001 0a00 0200 84c9b26aed4b 0000 0800 0400 04000000 1400 0300 90100000 90100000 90080000 01000000" +
+ "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ff47da19 0a00 0200 3333ff47da19 0000 0800 0400 00000000 1400 0300 a1280000 31110000 31110000 01000000" +
+ "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 14000000 4000 00 05 1400 0100 ff020000000000000000000000000016 0a00 0200 333300000016 0000 0800 0400 00000000 1400 0300 912a0000 21130000 21130000 00000000" +
+ "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 14000000 4000 00 05 1400 0100 ff0200000000000000000001ffeace3b 0a00 0200 3333ffeace3b 0000 0800 0400 00000000 1400 0300 922a0000 22130000 22130000 00000000" +
+ "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ff5c2a83 0a00 0200 3333ff5c2a83 0000 0800 0400 00000000 1400 0300 391c0000 c9040000 c9040000 01000000" +
+ "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 01000000 4000 00 02 1400 0100 00000000000000000000000000000000 0a00 0200 000000000000 0000 0800 0400 00000000 1400 0300 cd180200 5d010200 5d010200 08000000" +
+ "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff020000000000000000000000000002 0a00 0200 333300000002 0000 0800 0400 00000000 1400 0300 352a0000 c5120000 c5120000 00000000" +
+ "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff020000000000000000000000000016 0a00 0200 333300000016 0000 0800 0400 00000000 1400 0300 982a0000 28130000 28130000 00000000" +
+ "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 0800 80 01 1400 0100 fe8000000000000086c9b2fffe6aed4b 0a00 0200 84c9b26aed4b 0000 0800 0400 00000000 1400 0300 23000000 24000000 57000000 13000000" +
+ "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ffeace3b 0a00 0200 3333ffeace3b 0000 0800 0400 00000000 1400 0300 992a0000 29130000 29130000 01000000" +
+ "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 14000000 4000 00 05 1400 0100 ff020000000000000000000000000002 0a00 0200 333300000002 0000 0800 0400 00000000 1400 0300 2e2a0000 be120000 be120000 00000000" +
+ "44000000 1c00 0200 00000000 3e2b0000 02 00 0000 18000000 4000 00 03 0800 0100 00000000 0400 0200 0800 0400 00000000 1400 0300 75280000 05110000 05110000 22000000";
+ public static final byte[] RTM_GETNEIGH_RESPONSE =
+ HexEncoding.decode(RTM_GETNEIGH_RESPONSE_HEX.replaceAll(" ", "").toCharArray(), false);
+
+ @Test
+ public void testParseRtmDelNeigh() {
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_DELNEIGH);
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing.
+ final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer);
+ assertNotNull(msg);
+ assertTrue(msg instanceof RtNetlinkNeighborMessage);
+ final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) msg;
+
+ final StructNlMsgHdr hdr = neighMsg.getHeader();
+ assertNotNull(hdr);
+ assertEquals(76, hdr.nlmsg_len);
+ assertEquals(NetlinkConstants.RTM_DELNEIGH, hdr.nlmsg_type);
+ assertEquals(0, hdr.nlmsg_flags);
+ assertEquals(0, hdr.nlmsg_seq);
+ assertEquals(0, hdr.nlmsg_pid);
+
+ final StructNdMsg ndmsgHdr = neighMsg.getNdHeader();
+ assertNotNull(ndmsgHdr);
+ assertEquals((byte) OsConstants.AF_INET, ndmsgHdr.ndm_family);
+ assertEquals(21, ndmsgHdr.ndm_ifindex);
+ assertEquals(StructNdMsg.NUD_STALE, ndmsgHdr.ndm_state);
+ final InetAddress destination = neighMsg.getDestination();
+ assertNotNull(destination);
+ assertEquals(InetAddress.parseNumericAddress("192.168.159.254"), destination);
+ }
+
+ @Test
+ public void testParseRtmNewNeigh() {
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_NEWNEIGH);
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing.
+ final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer);
+ assertNotNull(msg);
+ assertTrue(msg instanceof RtNetlinkNeighborMessage);
+ final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) msg;
+
+ final StructNlMsgHdr hdr = neighMsg.getHeader();
+ assertNotNull(hdr);
+ assertEquals(88, hdr.nlmsg_len);
+ assertEquals(NetlinkConstants.RTM_NEWNEIGH, hdr.nlmsg_type);
+ assertEquals(0, hdr.nlmsg_flags);
+ assertEquals(0, hdr.nlmsg_seq);
+ assertEquals(0, hdr.nlmsg_pid);
+
+ final StructNdMsg ndmsgHdr = neighMsg.getNdHeader();
+ assertNotNull(ndmsgHdr);
+ assertEquals((byte) OsConstants.AF_INET6, ndmsgHdr.ndm_family);
+ assertEquals(21, ndmsgHdr.ndm_ifindex);
+ assertEquals(StructNdMsg.NUD_STALE, ndmsgHdr.ndm_state);
+ final InetAddress destination = neighMsg.getDestination();
+ assertNotNull(destination);
+ assertEquals(InetAddress.parseNumericAddress("fe80::86c9:b2ff:fe6a:ed4b"), destination);
+ }
+
+ @Test
+ public void testParseRtmGetNeighResponse() {
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_GETNEIGH_RESPONSE);
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing.
+
+ int messageCount = 0;
+ while (byteBuffer.remaining() > 0) {
+ final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer);
+ assertNotNull(msg);
+ assertTrue(msg instanceof RtNetlinkNeighborMessage);
+ final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) msg;
+
+ final StructNlMsgHdr hdr = neighMsg.getHeader();
+ assertNotNull(hdr);
+ assertEquals(NetlinkConstants.RTM_NEWNEIGH, hdr.nlmsg_type);
+ assertEquals(StructNlMsgHdr.NLM_F_MULTI, hdr.nlmsg_flags);
+ assertEquals(0, hdr.nlmsg_seq);
+ assertEquals(11070, hdr.nlmsg_pid);
+
+ messageCount++;
+ }
+ // TODO: add more detailed spot checks.
+ assertEquals(14, messageCount);
+ }
+
+ @Test
+ public void testCreateRtmNewNeighMessage() {
+ final int seqNo = 2635;
+ final int ifIndex = 14;
+ final byte[] llAddr =
+ new byte[] { (byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5, (byte) 6 };
+
+ // Hexadecimal representation of our created packet.
+ final String expectedNewNeighHex =
+ // struct nlmsghdr
+ "30000000" + // length = 48
+ "1c00" + // type = 28 (RTM_NEWNEIGH)
+ "0501" + // flags (NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE)
+ "4b0a0000" + // seqno
+ "00000000" + // pid (0 == kernel)
+ // struct ndmsg
+ "02" + // family
+ "00" + // pad1
+ "0000" + // pad2
+ "0e000000" + // interface index (14)
+ "0800" + // NUD state (0x08 == NUD_DELAY)
+ "00" + // flags
+ "00" + // type
+ // struct nlattr: NDA_DST
+ "0800" + // length = 8
+ "0100" + // type (1 == NDA_DST, for neighbor messages)
+ "7f000001" + // IPv4 address (== 127.0.0.1)
+ // struct nlattr: NDA_LLADDR
+ "0a00" + // length = 10
+ "0200" + // type (2 == NDA_LLADDR, for neighbor messages)
+ "010203040506" + // MAC Address (== 01:02:03:04:05:06)
+ "0000"; // padding, for 4 byte alignment
+ final byte[] expectedNewNeigh =
+ HexEncoding.decode(expectedNewNeighHex.toCharArray(), false);
+
+ final byte[] bytes = RtNetlinkNeighborMessage.newNewNeighborMessage(
+ seqNo, Inet4Address.LOOPBACK, StructNdMsg.NUD_DELAY, ifIndex, llAddr);
+ if (!Arrays.equals(expectedNewNeigh, bytes)) {
+ assertEquals(expectedNewNeigh.length, bytes.length);
+ for (int i = 0; i < Math.min(expectedNewNeigh.length, bytes.length); i++) {
+ assertEquals(expectedNewNeigh[i], bytes[i]);
+ }
+ }
+ }
+}
diff --git a/tests/unit/src/android/net/shared/Inet4AddressUtilsTest.java b/tests/unit/src/android/net/shared/Inet4AddressUtilsTest.java
new file mode 100644
index 0000000..35f8c79
--- /dev/null
+++ b/tests/unit/src/android/net/shared/Inet4AddressUtilsTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2019 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 android.net.shared;
+
+import static android.net.shared.Inet4AddressUtils.getBroadcastAddress;
+import static android.net.shared.Inet4AddressUtils.getImplicitNetmask;
+import static android.net.shared.Inet4AddressUtils.getPrefixMaskAsInet4Address;
+import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
+import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTL;
+import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
+import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTL;
+import static android.net.shared.Inet4AddressUtils.netmaskToPrefixLength;
+import static android.net.shared.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH;
+import static android.net.shared.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTL;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.junit.Assert.fail;
+
+import android.net.InetAddresses;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class Inet4AddressUtilsTest {
+
+ @Test
+ public void testInet4AddressToIntHTL() {
+ assertEquals(0, inet4AddressToIntHTL(ipv4Address("0.0.0.0")));
+ assertEquals(0x000080ff, inet4AddressToIntHTL(ipv4Address("255.128.0.0")));
+ assertEquals(0x0080ff0a, inet4AddressToIntHTL(ipv4Address("10.255.128.0")));
+ assertEquals(0x00feff0a, inet4AddressToIntHTL(ipv4Address("10.255.254.0")));
+ assertEquals(0xfeffa8c0, inet4AddressToIntHTL(ipv4Address("192.168.255.254")));
+ assertEquals(0xffffa8c0, inet4AddressToIntHTL(ipv4Address("192.168.255.255")));
+ }
+
+ @Test
+ public void testIntToInet4AddressHTL() {
+ assertEquals(ipv4Address("0.0.0.0"), intToInet4AddressHTL(0));
+ assertEquals(ipv4Address("255.128.0.0"), intToInet4AddressHTL(0x000080ff));
+ assertEquals(ipv4Address("10.255.128.0"), intToInet4AddressHTL(0x0080ff0a));
+ assertEquals(ipv4Address("10.255.254.0"), intToInet4AddressHTL(0x00feff0a));
+ assertEquals(ipv4Address("192.168.255.254"), intToInet4AddressHTL(0xfeffa8c0));
+ assertEquals(ipv4Address("192.168.255.255"), intToInet4AddressHTL(0xffffa8c0));
+ }
+
+ @Test
+ public void testInet4AddressToIntHTH() {
+ assertEquals(0, inet4AddressToIntHTH(ipv4Address("0.0.0.0")));
+ assertEquals(0xff800000, inet4AddressToIntHTH(ipv4Address("255.128.0.0")));
+ assertEquals(0x0aff8000, inet4AddressToIntHTH(ipv4Address("10.255.128.0")));
+ assertEquals(0x0afffe00, inet4AddressToIntHTH(ipv4Address("10.255.254.0")));
+ assertEquals(0xc0a8fffe, inet4AddressToIntHTH(ipv4Address("192.168.255.254")));
+ assertEquals(0xc0a8ffff, inet4AddressToIntHTH(ipv4Address("192.168.255.255")));
+ }
+
+ @Test
+ public void testIntToInet4AddressHTH() {
+ assertEquals(ipv4Address("0.0.0.0"), intToInet4AddressHTH(0));
+ assertEquals(ipv4Address("255.128.0.0"), intToInet4AddressHTH(0xff800000));
+ assertEquals(ipv4Address("10.255.128.0"), intToInet4AddressHTH(0x0aff8000));
+ assertEquals(ipv4Address("10.255.254.0"), intToInet4AddressHTH(0x0afffe00));
+ assertEquals(ipv4Address("192.168.255.254"), intToInet4AddressHTH(0xc0a8fffe));
+ assertEquals(ipv4Address("192.168.255.255"), intToInet4AddressHTH(0xc0a8ffff));
+ }
+
+
+ @Test
+ public void testPrefixLengthToV4NetmaskIntHTL() {
+ assertEquals(0, prefixLengthToV4NetmaskIntHTL(0));
+ assertEquals(0x000080ff /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTL(9));
+ assertEquals(0x0080ffff /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTL(17));
+ assertEquals(0x00feffff /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTL(23));
+ assertEquals(0xfeffffff /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTL(31));
+ assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTL(32));
+ }
+
+ @Test
+ public void testPrefixLengthToV4NetmaskIntHTH() {
+ assertEquals(0, prefixLengthToV4NetmaskIntHTH(0));
+ assertEquals(0xff800000 /* 255.128.0.0 */, prefixLengthToV4NetmaskIntHTH(9));
+ assertEquals(0xffff8000 /* 255.255.128.0 */, prefixLengthToV4NetmaskIntHTH(17));
+ assertEquals(0xfffffe00 /* 255.255.254.0 */, prefixLengthToV4NetmaskIntHTH(23));
+ assertEquals(0xfffffffe /* 255.255.255.254 */, prefixLengthToV4NetmaskIntHTH(31));
+ assertEquals(0xffffffff /* 255.255.255.255 */, prefixLengthToV4NetmaskIntHTH(32));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testPrefixLengthToV4NetmaskIntHTH_NegativeLength() {
+ prefixLengthToV4NetmaskIntHTH(-1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testPrefixLengthToV4NetmaskIntHTH_LengthTooLarge() {
+ prefixLengthToV4NetmaskIntHTH(33);
+ }
+
+ private void checkAddressMasking(String expectedAddr, String addr, int prefixLength) {
+ final int prefix = prefixLengthToV4NetmaskIntHTH(prefixLength);
+ final int addrInt = inet4AddressToIntHTH(ipv4Address(addr));
+ assertEquals(ipv4Address(expectedAddr), intToInet4AddressHTH(prefix & addrInt));
+ }
+
+ @Test
+ public void testPrefixLengthToV4NetmaskIntHTH_MaskAddr() {
+ checkAddressMasking("192.168.0.0", "192.168.128.1", 16);
+ checkAddressMasking("255.240.0.0", "255.255.255.255", 12);
+ checkAddressMasking("255.255.255.255", "255.255.255.255", 32);
+ checkAddressMasking("0.0.0.0", "255.255.255.255", 0);
+ }
+
+ @Test
+ public void testGetImplicitNetmask() {
+ assertEquals(8, getImplicitNetmask(ipv4Address("4.2.2.2")));
+ assertEquals(8, getImplicitNetmask(ipv4Address("10.5.6.7")));
+ assertEquals(16, getImplicitNetmask(ipv4Address("173.194.72.105")));
+ assertEquals(16, getImplicitNetmask(ipv4Address("172.23.68.145")));
+ assertEquals(24, getImplicitNetmask(ipv4Address("192.0.2.1")));
+ assertEquals(24, getImplicitNetmask(ipv4Address("192.168.5.1")));
+ assertEquals(32, getImplicitNetmask(ipv4Address("224.0.0.1")));
+ assertEquals(32, getImplicitNetmask(ipv4Address("255.6.7.8")));
+ }
+
+ private void assertInvalidNetworkMask(Inet4Address addr) {
+ try {
+ netmaskToPrefixLength(addr);
+ fail("Invalid netmask " + addr.getHostAddress() + " did not cause exception");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testNetmaskToPrefixLength() {
+ assertEquals(0, netmaskToPrefixLength(ipv4Address("0.0.0.0")));
+ assertEquals(9, netmaskToPrefixLength(ipv4Address("255.128.0.0")));
+ assertEquals(17, netmaskToPrefixLength(ipv4Address("255.255.128.0")));
+ assertEquals(23, netmaskToPrefixLength(ipv4Address("255.255.254.0")));
+ assertEquals(31, netmaskToPrefixLength(ipv4Address("255.255.255.254")));
+ assertEquals(32, netmaskToPrefixLength(ipv4Address("255.255.255.255")));
+
+ assertInvalidNetworkMask(ipv4Address("0.0.0.1"));
+ assertInvalidNetworkMask(ipv4Address("255.255.255.253"));
+ assertInvalidNetworkMask(ipv4Address("255.255.0.255"));
+ }
+
+ @Test
+ public void testGetPrefixMaskAsAddress() {
+ assertEquals("255.255.240.0", getPrefixMaskAsInet4Address(20).getHostAddress());
+ assertEquals("255.0.0.0", getPrefixMaskAsInet4Address(8).getHostAddress());
+ assertEquals("0.0.0.0", getPrefixMaskAsInet4Address(0).getHostAddress());
+ assertEquals("255.255.255.255", getPrefixMaskAsInet4Address(32).getHostAddress());
+ }
+
+ @Test
+ public void testGetBroadcastAddress() {
+ assertEquals("192.168.15.255",
+ getBroadcastAddress(ipv4Address("192.168.0.123"), 20).getHostAddress());
+ assertEquals("192.255.255.255",
+ getBroadcastAddress(ipv4Address("192.168.0.123"), 8).getHostAddress());
+ assertEquals("192.168.0.123",
+ getBroadcastAddress(ipv4Address("192.168.0.123"), 32).getHostAddress());
+ assertEquals("255.255.255.255",
+ getBroadcastAddress(ipv4Address("192.168.0.123"), 0).getHostAddress());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetBroadcastAddress_PrefixTooLarge() {
+ getBroadcastAddress(ipv4Address("192.168.0.123"), 33);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetBroadcastAddress_NegativePrefix() {
+ getBroadcastAddress(ipv4Address("192.168.0.123"), -1);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetPrefixMaskAsAddress_PrefixTooLarge() {
+ getPrefixMaskAsInet4Address(33);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetPrefixMaskAsAddress_NegativePrefix() {
+ getPrefixMaskAsInet4Address(-1);
+ }
+
+ private Inet4Address ipv4Address(String addr) {
+ return (Inet4Address) InetAddresses.parseNumericAddress(addr);
+ }
+}
diff --git a/tests/unit/src/android/net/shared/InitialConfigurationTest.java b/tests/unit/src/android/net/shared/InitialConfigurationTest.java
new file mode 100644
index 0000000..17f8324
--- /dev/null
+++ b/tests/unit/src/android/net/shared/InitialConfigurationTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2019 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 android.net.shared;
+
+import static android.net.InetAddresses.parseNumericAddress;
+
+import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.function.Consumer;
+
+/**
+ * Tests for {@link InitialConfiguration}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class InitialConfigurationTest {
+ private InitialConfiguration mConfig;
+
+ @Before
+ public void setUp() {
+ mConfig = new InitialConfiguration();
+ mConfig.ipAddresses.addAll(Arrays.asList(
+ new LinkAddress(parseNumericAddress("192.168.45.45"), 16),
+ new LinkAddress(parseNumericAddress("2001:db8::45"), 33)));
+ mConfig.directlyConnectedRoutes.addAll(Arrays.asList(
+ new IpPrefix(parseNumericAddress("192.168.46.46"), 17),
+ new IpPrefix(parseNumericAddress("2001:db8::46"), 34)));
+ mConfig.dnsServers.addAll(Arrays.asList(
+ parseNumericAddress("192.168.47.47"),
+ parseNumericAddress("2001:db8::47")));
+ // Any added InitialConfiguration field must be included in equals() to be tested properly
+ assertFieldCountEquals(3, InitialConfiguration.class);
+ }
+
+ @Test
+ public void testParcelUnparcelInitialConfiguration() {
+ final InitialConfiguration unparceled =
+ InitialConfiguration.fromStableParcelable(mConfig.toStableParcelable());
+ assertEquals(mConfig, unparceled);
+ }
+
+ @Test
+ public void testEquals() {
+ assertEquals(mConfig, InitialConfiguration.copy(mConfig));
+
+ assertNotEqualsAfterChange(c -> c.ipAddresses.add(
+ new LinkAddress(parseNumericAddress("192.168.47.47"), 24)));
+ assertNotEqualsAfterChange(c -> c.directlyConnectedRoutes.add(
+ new IpPrefix(parseNumericAddress("192.168.46.46"), 32)));
+ assertNotEqualsAfterChange(c -> c.dnsServers.add(parseNumericAddress("2001:db8::49")));
+ assertFieldCountEquals(3, InitialConfiguration.class);
+ }
+
+ private void assertNotEqualsAfterChange(Consumer<InitialConfiguration> mutator) {
+ final InitialConfiguration newConfig = InitialConfiguration.copy(mConfig);
+ mutator.accept(newConfig);
+ assertNotEquals(mConfig, newConfig);
+ }
+}
diff --git a/tests/unit/src/android/net/shared/IpConfigurationParcelableUtilTest.java b/tests/unit/src/android/net/shared/IpConfigurationParcelableUtilTest.java
new file mode 100644
index 0000000..f987389
--- /dev/null
+++ b/tests/unit/src/android/net/shared/IpConfigurationParcelableUtilTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 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 android.net.shared;
+
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcelable;
+import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable;
+
+import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.DhcpResults;
+import android.net.LinkAddress;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+
+/**
+ * Tests for {@link IpConfigurationParcelableUtil}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpConfigurationParcelableUtilTest {
+ private DhcpResults mDhcpResults;
+
+ @Before
+ public void setUp() {
+ mDhcpResults = new DhcpResults();
+ mDhcpResults.ipAddress = new LinkAddress(parseNumericAddress("2001:db8::42"), 64);
+ mDhcpResults.gateway = parseNumericAddress("192.168.42.42");
+ mDhcpResults.dnsServers.add(parseNumericAddress("2001:db8::43"));
+ mDhcpResults.dnsServers.add(parseNumericAddress("192.168.43.43"));
+ mDhcpResults.domains = "example.com";
+ mDhcpResults.serverAddress = (Inet4Address) parseNumericAddress("192.168.44.44");
+ mDhcpResults.vendorInfo = "TEST_VENDOR_INFO";
+ mDhcpResults.leaseDuration = 3600;
+ mDhcpResults.serverHostName = "dhcp.example.com";
+ mDhcpResults.mtu = 1450;
+ // Any added DhcpResults field must be included in equals() to be tested properly
+ assertFieldCountEquals(9, DhcpResults.class);
+ }
+
+ @Test
+ public void testParcelUnparcelDhcpResults() {
+ doDhcpResultsParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcelDhcpResults_NullIpAddress() {
+ mDhcpResults.ipAddress = null;
+ doDhcpResultsParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcelDhcpResults_NullGateway() {
+ mDhcpResults.gateway = null;
+ doDhcpResultsParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcelDhcpResults_NullDomains() {
+ mDhcpResults.domains = null;
+ doDhcpResultsParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcelDhcpResults_EmptyDomains() {
+ mDhcpResults.domains = "";
+ doDhcpResultsParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcelDhcpResults_NullServerAddress() {
+ mDhcpResults.serverAddress = null;
+ doDhcpResultsParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcelDhcpResults_NullVendorInfo() {
+ mDhcpResults.vendorInfo = null;
+ doDhcpResultsParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcelDhcpResults_NullServerHostName() {
+ mDhcpResults.serverHostName = null;
+ doDhcpResultsParcelUnparcelTest();
+ }
+
+ private void doDhcpResultsParcelUnparcelTest() {
+ final DhcpResults unparceled = fromStableParcelable(toStableParcelable(mDhcpResults));
+ assertEquals(mDhcpResults, unparceled);
+ }
+}
diff --git a/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java b/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java
new file mode 100644
index 0000000..7079a28
--- /dev/null
+++ b/tests/unit/src/android/net/shared/ProvisioningConfigurationTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2019 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 android.net.shared;
+
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.shared.ProvisioningConfiguration.fromStableParcelable;
+
+import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.net.LinkAddress;
+import android.net.Network;
+import android.net.StaticIpConfiguration;
+import android.net.apf.ApfCapabilities;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+/**
+ * Tests for {@link ProvisioningConfiguration}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ProvisioningConfigurationTest {
+ private ProvisioningConfiguration mConfig;
+
+ @Before
+ public void setUp() {
+ mConfig = new ProvisioningConfiguration();
+ mConfig.mEnableIPv4 = true;
+ mConfig.mEnableIPv6 = true;
+ mConfig.mUsingMultinetworkPolicyTracker = true;
+ mConfig.mUsingIpReachabilityMonitor = true;
+ mConfig.mRequestedPreDhcpActionMs = 42;
+ mConfig.mInitialConfig = new InitialConfiguration();
+ mConfig.mInitialConfig.ipAddresses.add(
+ new LinkAddress(parseNumericAddress("192.168.42.42"), 24));
+ mConfig.mStaticIpConfig = new StaticIpConfiguration();
+ mConfig.mStaticIpConfig.ipAddress =
+ new LinkAddress(parseNumericAddress("2001:db8::42"), 90);
+ // Not testing other InitialConfig or StaticIpConfig members: they have their own unit tests
+ mConfig.mApfCapabilities = new ApfCapabilities(1, 2, 3);
+ mConfig.mProvisioningTimeoutMs = 4200;
+ mConfig.mIPv6AddrGenMode = 123;
+ mConfig.mNetwork = new Network(321);
+ mConfig.mDisplayName = "test_config";
+ // Any added field must be included in equals() to be tested properly
+ assertFieldCountEquals(12, ProvisioningConfiguration.class);
+ }
+
+ @Test
+ public void testParcelUnparcel() {
+ doParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcel_NullInitialConfiguration() {
+ mConfig.mInitialConfig = null;
+ doParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcel_NullStaticConfiguration() {
+ mConfig.mStaticIpConfig = null;
+ doParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcel_NullApfCapabilities() {
+ mConfig.mApfCapabilities = null;
+ doParcelUnparcelTest();
+ }
+
+ @Test
+ public void testParcelUnparcel_NullNetwork() {
+ mConfig.mNetwork = null;
+ doParcelUnparcelTest();
+ }
+
+ private void doParcelUnparcelTest() {
+ final ProvisioningConfiguration unparceled =
+ fromStableParcelable(mConfig.toStableParcelable());
+ assertEquals(mConfig, unparceled);
+ }
+
+ @Test
+ public void testEquals() {
+ assertEquals(mConfig, new ProvisioningConfiguration(mConfig));
+
+ assertNotEqualsAfterChange(c -> c.mEnableIPv4 = false);
+ assertNotEqualsAfterChange(c -> c.mEnableIPv6 = false);
+ assertNotEqualsAfterChange(c -> c.mUsingMultinetworkPolicyTracker = false);
+ assertNotEqualsAfterChange(c -> c.mUsingIpReachabilityMonitor = false);
+ assertNotEqualsAfterChange(c -> c.mRequestedPreDhcpActionMs++);
+ assertNotEqualsAfterChange(c -> c.mInitialConfig.ipAddresses.add(
+ new LinkAddress(parseNumericAddress("192.168.47.47"), 16)));
+ assertNotEqualsAfterChange(c -> c.mInitialConfig = null);
+ assertNotEqualsAfterChange(c -> c.mStaticIpConfig.ipAddress =
+ new LinkAddress(parseNumericAddress("2001:db8::47"), 64));
+ assertNotEqualsAfterChange(c -> c.mStaticIpConfig = null);
+ assertNotEqualsAfterChange(c -> c.mApfCapabilities = new ApfCapabilities(4, 5, 6));
+ assertNotEqualsAfterChange(c -> c.mApfCapabilities = null);
+ assertNotEqualsAfterChange(c -> c.mProvisioningTimeoutMs++);
+ assertNotEqualsAfterChange(c -> c.mIPv6AddrGenMode++);
+ assertNotEqualsAfterChange(c -> c.mNetwork = new Network(123));
+ assertNotEqualsAfterChange(c -> c.mNetwork = null);
+ assertNotEqualsAfterChange(c -> c.mDisplayName = "other_test");
+ assertNotEqualsAfterChange(c -> c.mDisplayName = null);
+ assertFieldCountEquals(12, ProvisioningConfiguration.class);
+ }
+
+ private void assertNotEqualsAfterChange(Consumer<ProvisioningConfiguration> mutator) {
+ final ProvisioningConfiguration newConfig = new ProvisioningConfiguration(mConfig);
+ mutator.accept(newConfig);
+ assertNotEquals(mConfig, newConfig);
+ }
+}
diff --git a/tests/unit/src/android/net/util/InterfaceParamsTest.java b/tests/unit/src/android/net/util/InterfaceParamsTest.java
new file mode 100644
index 0000000..5a2b9c6
--- /dev/null
+++ b/tests/unit/src/android/net/util/InterfaceParamsTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 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 android.net.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.util.NetworkStackConstants;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class InterfaceParamsTest {
+ @Test
+ public void testNullInterfaceReturnsNull() {
+ assertNull(InterfaceParams.getByName(null));
+ }
+
+ @Test
+ public void testNonExistentInterfaceReturnsNull() {
+ assertNull(InterfaceParams.getByName("doesnotexist0"));
+ }
+
+ @Test
+ public void testLoopback() {
+ final InterfaceParams ifParams = InterfaceParams.getByName("lo");
+ assertNotNull(ifParams);
+ assertEquals("lo", ifParams.name);
+ assertTrue(ifParams.index > 0);
+ assertNotNull(ifParams.macAddr);
+ assertTrue(ifParams.defaultMtu >= NetworkStackConstants.ETHER_MTU);
+ }
+}