diff options
-rw-r--r-- | src/android/net/apf/ApfFilter.java | 26 | ||||
-rw-r--r-- | src/android/net/ip/IpClient.java | 30 | ||||
-rw-r--r-- | src/android/net/ip/IpClientLinkObserver.java | 28 | ||||
-rw-r--r-- | tests/integration/src/android/net/ip/IpClientIntegrationTest.java | 32 | ||||
-rw-r--r-- | tests/unit/src/android/net/apf/ApfTest.java | 17 |
5 files changed, 123 insertions, 10 deletions
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java index 75a737d..165e37b 100644 --- a/src/android/net/apf/ApfFilter.java +++ b/src/android/net/apf/ApfFilter.java @@ -105,6 +105,7 @@ public class ApfFilter { public boolean multicastFilter; public boolean ieee802_3Filter; public int[] ethTypeBlackList; + public int minRdnssLifetimeSec; } // Enums describing the outcome of receiving an RA packet. @@ -358,6 +359,9 @@ public class ApfFilter { private final boolean mDrop802_3Frames; private final int[] mEthTypeBlackList; + // Ignore non-zero RDNSS lifetimes below this value. + private final int mMinRdnssLifetimeSec; + // Detects doze mode state transitions. private final BroadcastReceiver mDeviceIdleReceiver = new BroadcastReceiver() { @Override @@ -388,6 +392,7 @@ public class ApfFilter { mInterfaceParams = ifParams; mMulticastFilter = config.multicastFilter; mDrop802_3Frames = config.ieee802_3Filter; + mMinRdnssLifetimeSec = config.minRdnssLifetimeSec; mContext = context; if (mApfCapabilities.hasDataAccess()) { @@ -748,6 +753,19 @@ public class ApfFilter { return lifetime; } + // http://b/66928272 http://b/65056012 + // DnsServerRepository ignores RDNSS servers with lifetimes that are too low. Ignore these + // lifetimes for the purpose of filter lifetime calculations. + private boolean shouldIgnoreLifetime(int optionType, long lifetime) { + return optionType == ICMP6_RDNSS_OPTION_TYPE + && lifetime != 0 && lifetime < mMinRdnssLifetimeSec; + } + + private boolean isRelevantLifetime(PacketSection section) { + return section.type == PacketSection.Type.LIFETIME + && !shouldIgnoreLifetime(section.option, section.lifetime); + } + // Note that this parses RA and may throw InvalidRaException (from // Buffer.position(int) or due to an invalid-length option) or IndexOutOfBoundsException // (from ByteBuffer.get(int) ) if parsing encounters something non-compliant with @@ -862,7 +880,7 @@ public class ApfFilter { long minLifetime() { long minLifetime = Long.MAX_VALUE; for (PacketSection section : mPacketSections) { - if (section.type == PacketSection.Type.LIFETIME) { + if (isRelevantLifetime(section)) { minLifetime = Math.min(minLifetime, section.lifetime); } } @@ -902,9 +920,10 @@ public class ApfFilter { section.start + section.length), nextFilterLabel); } + // Generate code to test the lifetimes haven't gone down too far. - // The packet is accepted if any of its lifetimes are lower than filterLifetime. - if (section.type == PacketSection.Type.LIFETIME) { + // The packet is accepted if any non-ignored lifetime is lower than filterLifetime. + if (isRelevantLifetime(section)) { switch (section.length) { case 4: gen.addLoad32(Register.R0, section.start); break; case 2: gen.addLoad16(Register.R0, section.start); break; @@ -1913,6 +1932,7 @@ public class ApfFilter { pw.println("Capabilities: " + mApfCapabilities); pw.println("Receive thread: " + (mReceiveThread != null ? "RUNNING" : "STOPPED")); pw.println("Multicast: " + (mMulticastFilter ? "DROP" : "ALLOW")); + pw.println("Minimum RDNSS lifetime: " + mMinRdnssLifetimeSec); try { pw.println("IPv4 address: " + InetAddress.getByAddress(mIPv4Address).getHostAddress()); } catch (UnknownHostException|NullPointerException e) {} diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java index 9f1f17c..8808ee2 100644 --- a/src/android/net/ip/IpClient.java +++ b/src/android/net/ip/IpClient.java @@ -18,6 +18,7 @@ package android.net.ip; import static android.net.RouteInfo.RTN_UNICAST; import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable; +import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission; @@ -42,7 +43,9 @@ import android.net.metrics.IpManagerEvent; import android.net.shared.InitialConfiguration; import android.net.shared.ProvisioningConfiguration; import android.net.util.InterfaceParams; +import android.net.util.NetworkStackUtils; import android.net.util.SharedLog; +import android.os.Build; import android.os.ConditionVariable; import android.os.IBinder; import android.os.Message; @@ -65,6 +68,7 @@ import com.android.internal.util.Preconditions; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.internal.util.WakeupMessage; +import com.android.networkstack.apishim.ShimUtils; import com.android.server.NetworkObserverRegistry; import com.android.server.NetworkStackService.NetworkStackServiceManager; @@ -313,9 +317,15 @@ public class IpClient extends StateMachine { // IpClient shares a handler with DhcpClient: commands must not overlap public static final int DHCPCLIENT_CMD_BASE = 1000; + // Settings and default values. private static final int MAX_LOG_RECORDS = 500; private static final int MAX_PACKET_RECORDS = 100; + @VisibleForTesting + static final String CONFIG_MIN_RDNSS_LIFETIME = "ipclient_min_rdnss_lifetime"; + private static final int DEFAULT_MIN_RDNSS_LIFETIME = + ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q) ? 120 : 0; + private static final boolean NO_CALLBACKS = false; private static final boolean SEND_CALLBACKS = true; @@ -355,6 +365,9 @@ public class IpClient extends StateMachine { private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); private final InterfaceController mInterfaceCtrl; + // Ignore nonzero RDNSS option lifetimes below this value. 0 = disabled. + private final int mMinRdnssLifetimeSec; + private InterfaceParams mInterfaceParams; /** @@ -411,6 +424,14 @@ public class IpClient extends StateMachine { NetworkStackIpMemoryStore ipMemoryStore) { return new DhcpClient.Dependencies(ipMemoryStore); } + + /** + * Read an integer DeviceConfig property. + */ + public int getDeviceConfigPropertyInt(String name, int defaultValue) { + return NetworkStackUtils.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, name, + defaultValue); + } } public IpClient(Context context, String ifName, IIpClientCallbacks callback, @@ -449,9 +470,15 @@ public class IpClient extends StateMachine { mNetd = deps.getNetd(mContext); mInterfaceCtrl = new InterfaceController(mInterfaceName, mNetd, mLog); + mMinRdnssLifetimeSec = mDependencies.getDeviceConfigPropertyInt( + CONFIG_MIN_RDNSS_LIFETIME, DEFAULT_MIN_RDNSS_LIFETIME); + + IpClientLinkObserver.Configuration config = new IpClientLinkObserver.Configuration( + mMinRdnssLifetimeSec); + mLinkObserver = new IpClientLinkObserver( mInterfaceName, - () -> sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED)) { + () -> sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED), config) { @Override public void onInterfaceAdded(String iface) { super.onInterfaceAdded(iface); @@ -1500,6 +1527,7 @@ public class IpClient extends StateMachine { // Get the Configuration for ApfFilter from Context apfConfig.ieee802_3Filter = ApfCapabilities.getApfDrop8023Frames(); apfConfig.ethTypeBlackList = ApfCapabilities.getApfEtherTypeBlackList(); + apfConfig.minRdnssLifetimeSec = mMinRdnssLifetimeSec; mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback); // TODO: investigate the effects of any multicast filtering racing/interfering with the // rest of this IP configuration startup. diff --git a/src/android/net/ip/IpClientLinkObserver.java b/src/android/net/ip/IpClientLinkObserver.java index 3f399e9..02bf5f0 100644 --- a/src/android/net/ip/IpClientLinkObserver.java +++ b/src/android/net/ip/IpClientLinkObserver.java @@ -71,20 +71,31 @@ public class IpClientLinkObserver implements NetworkObserver { void update(); } + /** Configuration parameters for IpClientLinkObserver. */ + public static class Configuration { + public final int minRdnssLifetime; + + public Configuration(int minRdnssLifetime) { + this.minRdnssLifetime = minRdnssLifetime; + } + } + private final String mInterfaceName; private final Callback mCallback; private final LinkProperties mLinkProperties; private DnsServerRepository mDnsServerRepository; + private final Configuration mConfig; private static final boolean DBG = false; - public IpClientLinkObserver(String iface, Callback callback) { + public IpClientLinkObserver(String iface, Callback callback, Configuration config) { mTag = "NetlinkTracker/" + iface; mInterfaceName = iface; mCallback = callback; mLinkProperties = new LinkProperties(); mLinkProperties.setInterfaceName(mInterfaceName); - mDnsServerRepository = new DnsServerRepository(); + mConfig = config; + mDnsServerRepository = new DnsServerRepository(config.minRdnssLifetime); } private void maybeLog(String operation, String iface, LinkAddress address) { @@ -197,7 +208,7 @@ public class IpClientLinkObserver implements NetworkObserver { // Clear the repository before clearing mLinkProperties. That way, if a clear() happens // while interfaceDnsServerInfo() is being called, we'll end up with no DNS servers in // mLinkProperties, as desired. - mDnsServerRepository = new DnsServerRepository(); + mDnsServerRepository = new DnsServerRepository(mConfig.minRdnssLifetime); mLinkProperties.clear(); mLinkProperties.setInterfaceName(mInterfaceName); } @@ -260,10 +271,16 @@ public class IpClientLinkObserver implements NetworkObserver { */ private HashMap<InetAddress, DnsServerEntry> mIndex; - DnsServerRepository() { + /** + * Minimum (non-zero) RDNSS lifetime to accept. + */ + private final int mMinLifetime; + + DnsServerRepository(int minLifetime) { mCurrentServers = new HashSet<>(); mAllServers = new ArrayList<>(NUM_SERVERS); mIndex = new HashMap<>(NUM_SERVERS); + mMinLifetime = minLifetime; } /** Sets the DNS servers of the provided LinkProperties object to the current servers. */ @@ -277,6 +294,9 @@ public class IpClientLinkObserver implements NetworkObserver { * @param addresses the string representations of the IP addresses of DNS servers to use. */ public synchronized boolean addServers(long lifetime, String[] addresses) { + // If the servers are below the minimum lifetime, don't change anything. + if (lifetime != 0 && lifetime < mMinLifetime) return false; + // The lifetime is actually an unsigned 32-bit number, but Java doesn't have unsigned. // Technically 0xffffffff (the maximum) is special and means "forever", but 2^32 seconds // (136 years) is close enough. diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java index 63f4319..f34c26e 100644 --- a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java +++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java @@ -123,6 +123,7 @@ import java.net.NetworkInterface; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; @@ -235,6 +236,8 @@ public class IpClientIntegrationTest { private class Dependencies extends IpClient.Dependencies { private boolean mIsDhcpLeaseCacheEnabled; private boolean mIsDhcpRapidCommitEnabled; + // Can't use SparseIntArray, it doesn't have an easy way to know if a key is not present. + private HashMap<String, Integer> mIntConfigProperties = new HashMap<>(); public void setDhcpLeaseCacheEnabled(final boolean enable) { mIsDhcpLeaseCacheEnabled = enable; @@ -274,6 +277,19 @@ public class IpClientIntegrationTest { } }; } + + @Override + public int getDeviceConfigPropertyInt(String name, int defaultValue) { + Integer value = mIntConfigProperties.get(name); + if (value == null) { + throw new IllegalStateException("Non-mocked device config property " + name); + } + return value; + } + + public void setDeviceConfigProperty(String name, int value) { + mIntConfigProperties.put(name, value); + } } @Before @@ -288,6 +304,8 @@ public class IpClientIntegrationTest { when(mNetworkStackServiceManager.getIpMemoryStoreService()) .thenReturn(mIpMemoryStoreService); + mDependencies.setDeviceConfigProperty(IpClient.CONFIG_MIN_RDNSS_LIFETIME, 67); + setUpTapInterface(); setUpIpClient(); } @@ -410,6 +428,7 @@ public class IpClientIntegrationTest { try (FileOutputStream out = new FileOutputStream(mPacketReader.createFd())) { byte[] packetBytes = new byte[packet.limit()]; packet.get(packetBytes); + packet.flip(); // So we can reuse it in the future. out.write(packetBytes); } } @@ -868,11 +887,22 @@ public class IpClientIntegrationTest { verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture()); LinkProperties lp = captor.getValue(); + // Expect that DNS servers with lifetimes below CONFIG_MIN_RDNSS_LIFETIME are not accepted. + assertNotNull(lp); + assertEquals(1, lp.getDnsServers().size()); + assertTrue(lp.getDnsServers().contains(InetAddress.getByName(dnsServer))); + reset(mCb); + + // If the RDNSS lifetime is above the minimum, the DNS server is accepted. + rdnss1 = buildRdnssOption(68, lowlifeDnsServer); + ra = buildRaPacket(pio, rdnss1, rdnss2); + sendResponse(ra); + verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(captor.capture()); + lp = captor.getValue(); assertNotNull(lp); assertEquals(2, lp.getDnsServers().size()); assertTrue(lp.getDnsServers().contains(InetAddress.getByName(dnsServer))); assertTrue(lp.getDnsServers().contains(InetAddress.getByName(lowlifeDnsServer))); - reset(mCb); // Expect that setting RDNSS lifetime of 0 causes loss of provisioning. diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java index ab92892..006ad19 100644 --- a/tests/unit/src/android/net/apf/ApfTest.java +++ b/tests/unit/src/android/net/apf/ApfTest.java @@ -130,6 +130,8 @@ public class ApfTest { private static final boolean DROP_802_3_FRAMES = true; private static final boolean ALLOW_802_3_FRAMES = false; + private static final int MIN_RDNSS_LIFETIME_SEC = 0; + // Constants for opcode encoding private static final byte LI_OP = (byte)(13 << 3); private static final byte LDDW_OP = (byte)(22 << 3); @@ -146,6 +148,8 @@ public class ApfTest { config.multicastFilter = ALLOW_MULTICAST; config.ieee802_3Filter = ALLOW_802_3_FRAMES; config.ethTypeBlackList = new int[0]; + config.minRdnssLifetimeSec = MIN_RDNSS_LIFETIME_SEC; + config.minRdnssLifetimeSec = 67; return config; } @@ -2090,6 +2094,16 @@ public class ApfTest { verifyRaLifetime(apfFilter, ipClientCallback, rdnssOptionPacket, RDNSS_LIFETIME); verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, RDNSS_LIFETIME, -1)); + final int lowLifetime = 60; + ByteBuffer lowLifetimeRdnssOptionPacket = ByteBuffer.wrap( + new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN + IPV6_ADDR_LEN]); + basePacket.clear(); + lowLifetimeRdnssOptionPacket.put(basePacket); + addRdnssOption(lowLifetimeRdnssOptionPacket, lowLifetime, "2620:fe::9"); + verifyRaLifetime(apfFilter, ipClientCallback, lowLifetimeRdnssOptionPacket, + ROUTER_LIFETIME); + verifyRaEvent(new RaEvent(ROUTER_LIFETIME, -1, -1, -1, lowLifetime, -1)); + ByteBuffer routeInfoOptionPacket = ByteBuffer.wrap( new byte[ICMP6_RA_OPTION_OFFSET + ICMP6_4_BYTE_OPTION_LEN + IPV6_ADDR_LEN]); basePacket.clear(); @@ -2123,12 +2137,13 @@ public class ApfTest { verifyRaLifetime(apfFilter, ipClientCallback, largeRaPacket, 300); verifyRaEvent(new RaEvent(1800, 600, 300, 1200, 7200, -1)); - // Verify that current program filters all the RAs: + // Verify that current program filters all the RAs (note: ApfFilter.MAX_RAS == 10). program = ipClientCallback.getApfProgram(); verifyRaLifetime(program, basePacket, ROUTER_LIFETIME); verifyRaLifetime(program, newFlowLabelPacket, ROUTER_LIFETIME); verifyRaLifetime(program, prefixOptionPacket, PREFIX_PREFERRED_LIFETIME); verifyRaLifetime(program, rdnssOptionPacket, RDNSS_LIFETIME); + verifyRaLifetime(program, lowLifetimeRdnssOptionPacket, ROUTER_LIFETIME); verifyRaLifetime(program, routeInfoOptionPacket, ROUTE_LIFETIME); verifyRaLifetime(program, dnsslOptionPacket, ROUTER_LIFETIME); verifyRaLifetime(program, largeRaPacket, 300); |