Merge "IpManager: define InitialConfiguration"

This commit is contained in:
Treehugger Robot
2017-07-13 20:45:21 +00:00
committed by Gerrit Code Review
5 changed files with 326 additions and 17 deletions

View File

@@ -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);
}

View File

@@ -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 {}

View File

@@ -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 wed 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);

View File

@@ -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.

View File

@@ -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;
}
}