Loading core/java/android/net/LinkAddress.java +10 −10 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. * Loading Loading @@ -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); } Loading core/java/android/net/metrics/IpManagerEvent.java +3 −0 Original line number Diff line number Diff line Loading @@ -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 {} Loading services/net/java/android/net/ip/IpManager.java +137 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; /** Loading Loading @@ -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; Loading Loading @@ -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; Loading @@ -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"; Loading Loading @@ -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))); } Loading @@ -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(); Loading Loading @@ -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); Loading services/net/java/android/net/util/NetworkConstants.java +1 −0 Original line number Diff line number Diff line Loading @@ -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. Loading tests/net/java/android/net/ip/IpManagerTest.java +175 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. */ Loading @@ -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); Loading @@ -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; } } Loading
core/java/android/net/LinkAddress.java +10 −10 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. * Loading Loading @@ -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); } Loading
core/java/android/net/metrics/IpManagerEvent.java +3 −0 Original line number Diff line number Diff line Loading @@ -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 {} Loading
services/net/java/android/net/ip/IpManager.java +137 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; /** Loading Loading @@ -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; Loading Loading @@ -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; Loading @@ -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"; Loading Loading @@ -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))); } Loading @@ -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(); Loading Loading @@ -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); Loading
services/net/java/android/net/util/NetworkConstants.java +1 −0 Original line number Diff line number Diff line Loading @@ -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. Loading
tests/net/java/android/net/ip/IpManagerTest.java +175 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. */ Loading @@ -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); Loading @@ -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; } }