diff options
| -rw-r--r-- | core/java/android/net/LinkAddress.java | 20 | ||||
| -rw-r--r-- | core/java/android/net/metrics/IpManagerEvent.java | 3 | ||||
| -rw-r--r-- | services/net/java/android/net/ip/IpManager.java | 141 | ||||
| -rw-r--r-- | services/net/java/android/net/util/NetworkConstants.java | 1 | ||||
| -rw-r--r-- | tests/net/java/android/net/ip/IpManagerTest.java | 178 |
5 files changed, 326 insertions, 17 deletions
diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java index 6e74f14bd138..62de9911bc48 100644 --- a/core/java/android/net/LinkAddress.java +++ b/core/java/android/net/LinkAddress.java @@ -16,6 +16,15 @@ package android.net; +import static android.system.OsConstants.IFA_F_DADFAILED; +import static android.system.OsConstants.IFA_F_DEPRECATED; +import static android.system.OsConstants.IFA_F_OPTIMISTIC; +import static android.system.OsConstants.IFA_F_TENTATIVE; +import static android.system.OsConstants.RT_SCOPE_HOST; +import static android.system.OsConstants.RT_SCOPE_LINK; +import static android.system.OsConstants.RT_SCOPE_SITE; +import static android.system.OsConstants.RT_SCOPE_UNIVERSE; + import android.os.Parcel; import android.os.Parcelable; import android.util.Pair; @@ -26,15 +35,6 @@ import java.net.InetAddress; import java.net.InterfaceAddress; import java.net.UnknownHostException; -import static android.system.OsConstants.IFA_F_DADFAILED; -import static android.system.OsConstants.IFA_F_DEPRECATED; -import static android.system.OsConstants.IFA_F_OPTIMISTIC; -import static android.system.OsConstants.IFA_F_TENTATIVE; -import static android.system.OsConstants.RT_SCOPE_HOST; -import static android.system.OsConstants.RT_SCOPE_LINK; -import static android.system.OsConstants.RT_SCOPE_SITE; -import static android.system.OsConstants.RT_SCOPE_UNIVERSE; - /** * Identifies an IP address on a network link. * @@ -101,7 +101,7 @@ public class LinkAddress implements Parcelable { * Per RFC 4193 section 8, fc00::/7 identifies these addresses. */ private boolean isIPv6ULA() { - if (address != null && address instanceof Inet6Address) { + if (address instanceof Inet6Address) { byte[] bytes = address.getAddress(); return ((bytes[0] & (byte)0xfe) == (byte)0xfc); } diff --git a/core/java/android/net/metrics/IpManagerEvent.java b/core/java/android/net/metrics/IpManagerEvent.java index e0a026ed678d..007846e8c1ae 100644 --- a/core/java/android/net/metrics/IpManagerEvent.java +++ b/core/java/android/net/metrics/IpManagerEvent.java @@ -42,10 +42,13 @@ public final class IpManagerEvent implements Parcelable { /** @hide */ public static final int ERROR_STARTING_IPV6 = 5; /** @hide */ public static final int ERROR_STARTING_IPREACHABILITYMONITOR = 6; + /** @hide */ public static final int ERROR_INVALID_PROVISIONING = 7; + /** {@hide} */ @IntDef(value = { PROVISIONING_OK, PROVISIONING_FAIL, COMPLETE_LIFECYCLE, ERROR_STARTING_IPV4, ERROR_STARTING_IPV6, ERROR_STARTING_IPREACHABILITYMONITOR, + ERROR_INVALID_PROVISIONING, }) @Retention(RetentionPolicy.SOURCE) public @interface EventType {} diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java index b2930a4d536d..adaf59974caf 100644 --- a/services/net/java/android/net/ip/IpManager.java +++ b/services/net/java/android/net/ip/IpManager.java @@ -23,6 +23,7 @@ import android.content.Context; import android.net.DhcpResults; import android.net.INetd; import android.net.InterfaceConfiguration; +import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties.ProvisioningChange; import android.net.LinkProperties; @@ -35,6 +36,7 @@ import android.net.dhcp.DhcpClient; import android.net.metrics.IpConnectivityLog; import android.net.metrics.IpManagerEvent; import android.net.util.MultinetworkPolicyTracker; +import android.net.util.NetworkConstants; import android.net.util.SharedLog; import android.os.INetworkManagementService; import android.os.Message; @@ -51,17 +53,25 @@ import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.IState; +import com.android.internal.util.Preconditions; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.server.net.NetlinkTracker; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; +import java.util.Collection; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; import java.util.StringJoiner; +import java.util.function.Predicate; +import java.util.stream.Collectors; /** @@ -308,6 +318,11 @@ public class IpManager extends StateMachine { return this; } + public Builder withInitialConfiguration(InitialConfiguration initialConfig) { + mConfig.mInitialConfig = initialConfig; + return this; + } + public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) { mConfig.mStaticIpConfig = staticConfig; return this; @@ -342,18 +357,20 @@ public class IpManager extends StateMachine { /* package */ boolean mEnableIPv6 = true; /* package */ boolean mUsingIpReachabilityMonitor = true; /* package */ int mRequestedPreDhcpActionMs; + /* package */ InitialConfiguration mInitialConfig; /* package */ StaticIpConfiguration mStaticIpConfig; /* package */ ApfCapabilities mApfCapabilities; /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS; /* package */ int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY; - public ProvisioningConfiguration() {} + public ProvisioningConfiguration() {} // used by Builder public ProvisioningConfiguration(ProvisioningConfiguration other) { mEnableIPv4 = other.mEnableIPv4; mEnableIPv6 = other.mEnableIPv6; mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor; mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs; + mInitialConfig = InitialConfiguration.copy(other.mInitialConfig); mStaticIpConfig = other.mStaticIpConfig; mApfCapabilities = other.mApfCapabilities; mProvisioningTimeoutMs = other.mProvisioningTimeoutMs; @@ -366,12 +383,124 @@ public class IpManager extends StateMachine { .add("mEnableIPv6: " + mEnableIPv6) .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor) .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs) + .add("mInitialConfig: " + mInitialConfig) .add("mStaticIpConfig: " + mStaticIpConfig) .add("mApfCapabilities: " + mApfCapabilities) .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs) .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode) .toString(); } + + public boolean isValid() { + return (mInitialConfig == null) || mInitialConfig.isValid(); + } + } + + public static class InitialConfiguration { + public final Set<LinkAddress> ipAddresses = new HashSet<>(); + public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>(); + public final Set<InetAddress> dnsServers = new HashSet<>(); + public Inet4Address gateway; // WiFi legacy behavior with static ipv4 config + + 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}, v4 gateway: %s)", + join(", ", ipAddresses), join(", ", directlyConnectedRoutes), + join(", ", dnsServers), gateway); + } + + public boolean isValid() { + // 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(Inet4Address.class::isInstance).count() > 1) { + return false; + } + + return true; + } + + private static boolean isPrefixLengthCompliant(LinkAddress addr) { + return (addr.getAddress() instanceof Inet4Address) + || isCompliantIPv6PrefixLength(addr.getPrefixLength()); + } + + private static boolean isPrefixLengthCompliant(IpPrefix prefix) { + return (prefix.getAddress() instanceof Inet4Address) + || isCompliantIPv6PrefixLength(prefix.getPrefixLength()); + } + + private static boolean isCompliantIPv6PrefixLength(int prefixLength) { + return (NetworkConstants.RFC6177_MIN_PREFIX_LENGTH <= prefixLength) + && (prefixLength <= NetworkConstants.RFC7421_PREFIX_LENGTH); + } + + private static boolean isIPv6DefaultRoute(IpPrefix prefix) { + return prefix.getAddress().equals(Inet6Address.ANY); + } + + private static boolean isIPv6GUA(LinkAddress addr) { + return (addr.getAddress() instanceof Inet6Address) && addr.isGlobalPreferred(); + } + + private static <T> boolean any(Iterable<T> coll, Predicate<T> fn) { + for (T t : coll) { + if (fn.test(t)) { + return true; + } + } + return false; + } + + private static <T> boolean all(Iterable<T> coll, Predicate<T> fn) { + return !any(coll, not(fn)); + } + + private static <T> Predicate<T> not(Predicate<T> fn) { + return (t) -> !fn.test(t); + } + + private static <T> String join(String delimiter, Collection<T> coll) { + return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter)); + } } public static final String DUMP_ARG = "ipmanager"; @@ -436,8 +565,7 @@ public class IpManager extends StateMachine { private boolean mMulticastFiltering; private long mStartTimeMillis; - public IpManager(Context context, String ifName, Callback callback) - throws IllegalArgumentException { + public IpManager(Context context, String ifName, Callback callback) { this(context, ifName, callback, INetworkManagementService.Stub.asInterface( ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE))); } @@ -446,7 +574,7 @@ public class IpManager extends StateMachine { * An expanded constructor, useful for dependency injection. */ public IpManager(Context context, String ifName, Callback callback, - INetworkManagementService nwService) throws IllegalArgumentException { + INetworkManagementService nwService) { super(IpManager.class.getSimpleName() + "." + ifName); mTag = getName(); @@ -563,6 +691,11 @@ public class IpManager extends StateMachine { } public void startProvisioning(ProvisioningConfiguration req) { + if (!req.isValid()) { + doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); + return; + } + getNetworkInterface(); mCallback.setNeighborDiscoveryOffload(true); diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java index a012e0cb0547..9b3bc3f0301a 100644 --- a/services/net/java/android/net/util/NetworkConstants.java +++ b/services/net/java/android/net/util/NetworkConstants.java @@ -102,6 +102,7 @@ public final class NetworkConstants { public static final int IPV6_ADDR_LEN = 16; public static final int IPV6_MIN_MTU = 1280; public static final int RFC7421_PREFIX_LENGTH = 64; + public static final int RFC6177_MIN_PREFIX_LENGTH = 48; /** * ICMPv6 constants. diff --git a/tests/net/java/android/net/ip/IpManagerTest.java b/tests/net/java/android/net/ip/IpManagerTest.java index 025b01740aad..e7dbfe3f5044 100644 --- a/tests/net/java/android/net/ip/IpManagerTest.java +++ b/tests/net/java/android/net/ip/IpManagerTest.java @@ -16,11 +16,26 @@ package android.net.ip; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.AlarmManager; import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; +import android.net.IpPrefix; +import android.net.LinkAddress; +import android.net.ip.IpManager.Callback; +import android.net.ip.IpManager.InitialConfiguration; +import android.net.ip.IpManager.ProvisioningConfiguration; import android.os.INetworkManagementService; import android.provider.Settings; import android.support.test.filters.SmallTest; @@ -31,11 +46,17 @@ import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.R; import org.junit.Before; -import org.junit.runner.RunWith; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.util.HashSet; +import java.util.Set; + /** * Tests for IpManager. */ @@ -44,14 +65,20 @@ import org.mockito.MockitoAnnotations; public class IpManagerTest { private static final int DEFAULT_AVOIDBADWIFI_CONFIG_VALUE = 1; + private static final String VALID = "VALID"; + private static final String INVALID = "INVALID"; + @Mock private Context mContext; @Mock private INetworkManagementService mNMService; @Mock private Resources mResources; + @Mock private Callback mCb; + @Mock private AlarmManager mAlarm; private MockContentResolver mContentResolver; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm); when(mContext.getResources()).thenReturn(mResources); when(mResources.getInteger(R.integer.config_networkAvoidBadWifi)) .thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE); @@ -68,7 +95,152 @@ public class IpManagerTest { @Test public void testInvalidInterfaceDoesNotThrow() throws Exception { - final IpManager.Callback cb = new IpManager.Callback(); - final IpManager ipm = new IpManager(mContext, "test_wlan0", cb, mNMService); + final IpManager ipm = new IpManager(mContext, "test_wlan0", mCb, mNMService); + } + + @Test + public void testDefaultProvisioningConfiguration() throws Exception { + final String iface = "test_wlan0"; + final IpManager ipm = new IpManager(mContext, iface, mCb, mNMService); + ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() + .withoutIPv4() + // TODO: mock IpReachabilityMonitor's dependencies (NetworkInterface, PowerManager) + // and enable it in this test + .withoutIpReachabilityMonitor() + .build(); + + ipm.startProvisioning(config); + verify(mCb, times(1)).setNeighborDiscoveryOffload(true); + verify(mCb, timeout(100).times(1)).setFallbackMulticastFilter(false); + verify(mCb, never()).onProvisioningFailure(any()); + + ipm.stop(); + verify(mNMService, timeout(100).times(1)).disableIpv6(iface); + verify(mNMService, timeout(100).times(1)).clearInterfaceAddresses(iface); + } + + @Test + public void testInitialConfigurations() throws Exception { + InitialConfigurationTestCase[] testcases = { + validConf("valid IPv4 configuration", + links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns("192.0.2.2")), + validConf("another valid IPv4 configuration", + links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns()), + validConf("valid IPv6 configurations", + links("2001:db8:dead:beef:f00::a0/64", "fe80::1/64"), + prefixes("2001:db8:dead:beef::/64", "fe80::/64"), + dns("2001:db8:dead:beef:f00::02")), + validConf("valid IPv6 configurations", + links("fe80::1/64"), prefixes("fe80::/64"), dns()), + validConf("valid IPv6/v4 configuration", + links("2001:db8:dead:beef:f00::a0/48", "192.0.2.12/24"), + prefixes("2001:db8:dead:beef::/64", "192.0.2.0/24"), + dns("192.0.2.2", "2001:db8:dead:beef:f00::02")), + validConf("valid IPv6 configuration without any GUA.", + links("fd00:1234:5678::1/48"), + prefixes("fd00:1234:5678::/48"), + dns("fd00:1234:5678::1000")), + + invalidConf("v4 addr and dns not in any prefix", + links("192.0.2.12/24"), prefixes("198.51.100.0/24"), dns("192.0.2.2")), + invalidConf("v4 addr not in any prefix", + links("198.51.2.12/24"), prefixes("198.51.100.0/24"), dns("192.0.2.2")), + invalidConf("v4 dns addr not in any prefix", + links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns("198.51.100.2")), + invalidConf("v6 addr not in any prefix", + links("2001:db8:dead:beef:f00::a0/64", "fe80::1/64"), + prefixes("2001:db8:dead:beef::/64"), + dns("2001:db8:dead:beef:f00::02")), + invalidConf("v6 dns addr not in any prefix", + links("fe80::1/64"), prefixes("fe80::/64"), dns("2001:db8:dead:beef:f00::02")), + invalidConf("default ipv6 route and no GUA", + links("fd01:1111:2222:3333::a0/128"), prefixes("::/0"), dns()), + invalidConf("invalid v6 prefix length", + links("2001:db8:dead:beef:f00::a0/128"), prefixes("2001:db8:dead:beef::/32"), + dns()), + invalidConf("another invalid v6 prefix length", + links("2001:db8:dead:beef:f00::a0/128"), prefixes("2001:db8:dead:beef::/72"), + dns()) + }; + + for (InitialConfigurationTestCase testcase : testcases) { + if (testcase.config.isValid() != testcase.isValid) { + fail(testcase.errorMessage()); + } + } + } + + static class InitialConfigurationTestCase { + String descr; + boolean isValid; + InitialConfiguration config; + public String errorMessage() { + return String.format("%s: expected configuration %s to be %s, but was %s", + descr, config, validString(isValid), validString(!isValid)); + } + } + + static String validString(boolean isValid) { + return isValid ? VALID : INVALID; + } + + static InitialConfigurationTestCase validConf(String descr, Set<LinkAddress> links, + Set<IpPrefix> prefixes, Set<InetAddress> dns) { + return confTestCase(descr, true, conf(links, prefixes, dns)); + } + + static InitialConfigurationTestCase invalidConf(String descr, Set<LinkAddress> links, + Set<IpPrefix> prefixes, Set<InetAddress> dns) { + return confTestCase(descr, false, conf(links, prefixes, dns)); + } + + static InitialConfigurationTestCase confTestCase( + String descr, boolean isValid, InitialConfiguration config) { + InitialConfigurationTestCase testcase = new InitialConfigurationTestCase(); + testcase.descr = descr; + testcase.isValid = isValid; + testcase.config = config; + return testcase; + } + + static InitialConfiguration conf( + Set<LinkAddress> links, Set<IpPrefix> prefixes, Set<InetAddress> dns) { + InitialConfiguration conf = new InitialConfiguration(); + conf.ipAddresses.addAll(links); + conf.directlyConnectedRoutes.addAll(prefixes); + conf.dnsServers.addAll(dns); + return conf; + } + + static Set<IpPrefix> prefixes(String... prefixes) { + return mapIntoSet(prefixes, IpPrefix::new); + } + + static Set<LinkAddress> links(String... addresses) { + return mapIntoSet(addresses, LinkAddress::new); + } + + static Set<InetAddress> ips(String... addresses) { + return mapIntoSet(addresses, InetAddress::getByName); + } + + static Set<InetAddress> dns(String... addresses) { + return ips(addresses); + } + + static <A, B> Set<B> mapIntoSet(A[] in, Fn<A, B> fn) { + Set<B> out = new HashSet<>(in.length); + for (A item : in) { + try { + out.add(fn.call(item)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return out; + } + + interface Fn<A,B> { + B call(A a) throws Exception; } } |