diff options
38 files changed, 4958 insertions, 1 deletions
@@ -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: <linux_src>/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: <linux_src>/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 <linux_src>/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: + * + * <linux_src>/include/uapi/linux/netlink.h + * <linux_src>/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 <linux_src>/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: <linux_src>/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 <linux_src>/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 <linux_src>/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 <linux_src>/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: <linux_src>/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: <linux_src>/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 <linux_src>/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: <linux_src>/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 <linux_src>/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 <linux_src>/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); + } +} |