Populating local addresses to local_net_access_map
Whenever LinkProperties is added/modified/removes, we are updating local
network prefixes for the modified interface/addresses. These prefixes
are used by BPF egress and ingress cgroup hooks to disallow local
network packets if the calling application do not have required
permission.
Bug: 369815823
Test: Manually tested
Test: atest ConnectivityCoverageTests:android.net.connectivity.com.android.server.BpfNetMapsTest
Test: atest ConnectivityCoverageTests:android.net.connectivity.com.android.server.CSLocalNetworkProtectionTest
Change-Id: Ie592ecb948ae05a0a73f47d0573560eb705f6bcf
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 4fae73a..ebb8de1 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -911,7 +911,7 @@
+ "(" + iface + ")");
return;
}
- LocalNetAccessKey localNetAccessKey = new LocalNetAccessKey(lpmBitlen, ifIndex,
+ final LocalNetAccessKey localNetAccessKey = new LocalNetAccessKey(lpmBitlen, ifIndex,
address, protocol, remotePort);
try {
@@ -923,13 +923,43 @@
}
/**
- * False if the configuration is disallowed.
- *
+ * Remove configuration to local_net_access trie map.
+ * @param lpmBitlen prefix length that will be used for longest matching
+ * @param iface interface name
+ * @param address remote address. ipv4 addresses would be mapped to v6
+ * @param protocol required for longest match in special cases
+ * @param remotePort src/dst port for ingress/egress
+ */
+ @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
+ public void removeLocalNetAccess(final int lpmBitlen, final String iface,
+ final InetAddress address, final int protocol, final int remotePort) {
+ throwIfPre25Q2("removeLocalNetAccess is not available on pre-B devices");
+ final int ifIndex = mDeps.getIfIndex(iface);
+ if (ifIndex == 0) {
+ Log.e(TAG, "Failed to get if index, skip removeLocalNetAccess for " + address
+ + "(" + iface + ")");
+ return;
+ }
+ final LocalNetAccessKey localNetAccessKey = new LocalNetAccessKey(lpmBitlen, ifIndex,
+ address, protocol, remotePort);
+
+ try {
+ sLocalNetAccessMap.deleteEntry(localNetAccessKey);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed to remove local network access for localNetAccessKey : "
+ + localNetAccessKey);
+ }
+ }
+
+ /**
+ * Fetches value available in local_net_access bpf map for provided configuration
* @param lpmBitlen prefix length that will be used for longest matching
* @param iface interface name
* @param address remote address. ipv4 addresses would be mapped to v6
* @param protocol required for longest match in special cases
* @param remotePort src/dst port for ingress/egress
+ * @return false if the configuration is disallowed, true if the configuration is absent i.e. it
+ * is not local network or if configuration is allowed like local dns servers.
*/
@RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
public boolean getLocalNetAccess(final int lpmBitlen, final String iface,
@@ -941,10 +971,10 @@
+ address + "(" + iface + ")");
return true;
}
- LocalNetAccessKey localNetAccessKey = new LocalNetAccessKey(lpmBitlen, ifIndex,
+ final LocalNetAccessKey localNetAccessKey = new LocalNetAccessKey(lpmBitlen, ifIndex,
address, protocol, remotePort);
try {
- Bool value = sLocalNetAccessMap.getValue(localNetAccessKey);
+ final Bool value = sLocalNetAccessMap.getValue(localNetAccessKey);
return value == null ? true : value.val;
} catch (ErrnoException e) {
Log.e(TAG, "Failed to find local network access configuration for "
@@ -972,10 +1002,10 @@
* @param uid application uid that needs check if local network calls are blocked.
*/
@RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT)
- public boolean getUidValueFromLocalNetBlockMap(final int uid) {
- throwIfPre25Q2("getUidValueFromLocalNetBlockMap is not available on pre-B devices");
+ public boolean isUidBlockedFromUsingLocalNetwork(final int uid) {
+ throwIfPre25Q2("isUidBlockedFromUsingLocalNetwork is not available on pre-B devices");
try {
- Bool value = sLocalNetBlockedUidMap.getValue(new U32(uid));
+ final Bool value = sLocalNetBlockedUidMap.getValue(new U32(uid));
return value == null ? false : value.val;
} catch (ErrnoException e) {
Log.e(TAG, "Failed to find uid(" + uid
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 18801f0..2872751 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -109,8 +109,8 @@
import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS;
import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
-import static android.net.NetworkCapabilities.RES_ID_UNSET;
import static android.net.NetworkCapabilities.RES_ID_MATCH_ALL_RESERVATIONS;
+import static android.net.NetworkCapabilities.RES_ID_UNSET;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
@@ -146,6 +146,8 @@
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_UDP6_RECVMSG;
import static com.android.net.module.util.BpfUtils.BPF_CGROUP_UDP6_SENDMSG;
import static com.android.net.module.util.NetworkMonitorUtils.isPrivateDnsValidationRequired;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_LOCAL_PREFIXES;
+import static com.android.net.module.util.NetworkStackConstants.MULTICAST_AND_BROADCAST_PREFIXES;
import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf;
import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission;
import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr;
@@ -214,6 +216,7 @@
import android.net.InetAddresses;
import android.net.IpMemoryStore;
import android.net.IpPrefix;
+import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.LocalNetworkConfig;
import android.net.LocalNetworkInfo;
@@ -5683,6 +5686,7 @@
// destroyed pending replacement they will be sent when it is disconnected.
maybeDisableForwardRulesForDisconnectingNai(nai, false /* sendCallbacks */);
updateIngressToVpnAddressFiltering(null, nai.linkProperties, nai);
+ updateLocalNetworkAddresses(null, nai.linkProperties);
try {
mNetd.networkDestroy(nai.network.getNetId());
} catch (RemoteException | ServiceSpecificException e) {
@@ -9583,6 +9587,8 @@
updateIngressToVpnAddressFiltering(newLp, oldLp, networkAgent);
+ updateLocalNetworkAddresses(newLp, oldLp);
+
updateMtu(newLp, oldLp);
// TODO - figure out what to do for clat
// for (LinkProperties lp : newLp.getStackedLinks()) {
@@ -9762,6 +9768,134 @@
}
/**
+ * Update Local Network Addresses to LocalNetAccess BPF map.
+ * @param newLp new link properties
+ * @param oldLp old link properties
+ */
+ private void updateLocalNetworkAddresses(@Nullable final LinkProperties newLp,
+ @NonNull final LinkProperties oldLp) {
+
+ // The maps are available only after 25Q2 release
+ if (!BpfNetMaps.isAtLeast25Q2()) {
+ return;
+ }
+
+ final CompareResult<String> interfaceDiff = new CompareResult<>(
+ oldLp != null ? oldLp.getAllInterfaceNames() : null,
+ newLp != null ? newLp.getAllInterfaceNames() : null);
+
+ for (final String iface : interfaceDiff.added) {
+ addLocalAddressesToBpfMap(iface, MULTICAST_AND_BROADCAST_PREFIXES);
+ }
+ for (final String iface : interfaceDiff.removed) {
+ removeLocalAddressesFromBpfMap(iface, MULTICAST_AND_BROADCAST_PREFIXES);
+ }
+
+ final CompareResult<LinkAddress> linkAddressDiff = new CompareResult<>(
+ oldLp != null ? oldLp.getLinkAddresses() : null,
+ newLp != null ? newLp.getLinkAddresses() : null);
+
+ List<IpPrefix> unicastLocalPrefixesToBeAdded = new ArrayList<>();
+ List<IpPrefix> unicastLocalPrefixesToBeRemoved = new ArrayList<>();
+
+ // Finding the list of local network prefixes that needs to be added
+ if (newLp != null) {
+ for (LinkAddress linkAddress : newLp.getLinkAddresses()) {
+ unicastLocalPrefixesToBeAdded.addAll(
+ getLocalNetworkPrefixesForAddress(linkAddress));
+ }
+ }
+
+ for (LinkAddress linkAddress : linkAddressDiff.removed) {
+ unicastLocalPrefixesToBeRemoved.addAll(getLocalNetworkPrefixesForAddress(linkAddress));
+ }
+
+ // If newLp is not null, adding local network prefixes using interface name of newLp
+ if (newLp != null) {
+ addLocalAddressesToBpfMap(newLp.getInterfaceName(),
+ new ArrayList<>(unicastLocalPrefixesToBeAdded));
+ }
+ if (oldLp != null) {
+ // excluding removal of ip prefixes that needs to be added for newLp, but also
+ // removed for oldLp.
+ if (newLp != null && Objects.equals(oldLp.getInterfaceName(),
+ newLp.getInterfaceName())) {
+ unicastLocalPrefixesToBeRemoved.removeAll(unicastLocalPrefixesToBeAdded);
+ }
+ // removing ip local network prefixes because of change in link addresses.
+ removeLocalAddressesFromBpfMap(oldLp.getInterfaceName(),
+ new ArrayList<>(unicastLocalPrefixesToBeRemoved));
+ }
+
+ }
+
+ /**
+ * Filters IpPrefix that are local prefixes and LinkAddress is part of them.
+ * @param linkAddress link address used for filtering
+ * @return list of IpPrefix that are local addresses.
+ */
+ private List<IpPrefix> getLocalNetworkPrefixesForAddress(LinkAddress linkAddress) {
+ List<IpPrefix> localPrefixes = new ArrayList<>();
+ if (linkAddress.isIpv6()) {
+ // For IPv6, if the prefix length is greater than zero then they are part of local
+ // network
+ if (linkAddress.getPrefixLength() != 0) {
+ localPrefixes.add(
+ new IpPrefix(linkAddress.getAddress(), linkAddress.getPrefixLength()));
+ }
+ } else {
+ // For IPv4, if the linkAddress is part of IpPrefix adding prefix to result.
+ for (IpPrefix ipv4LocalPrefix : IPV4_LOCAL_PREFIXES) {
+ if (ipv4LocalPrefix.containsPrefix(
+ new IpPrefix(linkAddress.getAddress(), linkAddress.getPrefixLength()))) {
+ localPrefixes.add(ipv4LocalPrefix);
+ }
+ }
+ }
+ return localPrefixes;
+ }
+
+ /**
+ * Adds list of prefixes(addresses) to local network access map.
+ * @param iface interface name
+ * @param prefixes list of prefixes/addresses
+ */
+ private void addLocalAddressesToBpfMap(final String iface, final List<IpPrefix> prefixes) {
+ if (!BpfNetMaps.isAtLeast25Q2()) return;
+ for (IpPrefix prefix : prefixes) {
+ /*
+ Prefix length is used by LPM trie map(local_net_access_map) for performing longest
+ prefix matching, this length represents the maximum number of bits used for matching.
+ The interface index should always be matched which is 32-bit integer. For IPv6, prefix
+ length is calculated by adding the ip address prefix length along with interface index
+ making it (32 + length). IPv4 addresses are stored as ipv4-mapped-ipv6 which implies
+ first 96 bits are common for all ipv4 addresses. Hence, prefix length is calculated as
+ 32(interface index) + 96 (common for ipv4-mapped-ipv6) + length.
+ */
+ final int prefixLengthConstant = (prefix.isIPv4() ? (32 + 96) : 32);
+ mBpfNetMaps.addLocalNetAccess(prefixLengthConstant + prefix.getPrefixLength(),
+ iface, prefix.getAddress(), 0, 0, false);
+
+ }
+
+ }
+
+ /**
+ * Removes list of prefixes(addresses) from local network access map.
+ * @param iface interface name
+ * @param prefixes list of prefixes/addresses
+ */
+ private void removeLocalAddressesFromBpfMap(final String iface, final List<IpPrefix> prefixes) {
+ if (!BpfNetMaps.isAtLeast25Q2()) return;
+ for (IpPrefix prefix : prefixes) {
+ // The reasoning for prefix length is explained in addLocalAddressesToBpfMap()
+ final int prefixLengthConstant = (prefix.isIPv4() ? (32 + 96) : 32);
+ mBpfNetMaps.removeLocalNetAccess(prefixLengthConstant
+ + prefix.getPrefixLength(), iface, prefix.getAddress(), 0, 0);
+ }
+ }
+
+ /**
* Have netd update routes from oldLp to newLp.
* @return true if routes changed between oldLp and newLp
*/
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index 026e985..8d7ae5c 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -23,6 +23,7 @@
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.util.List;
/**
* Network constants used by the network stack.
@@ -341,6 +342,26 @@
*/
public static final String TEST_URL_EXPIRATION_TIME = "test_url_expiration_time";
+ /**
+ * List of IpPrefix that are local network prefixes.
+ */
+ public static final List<IpPrefix> IPV4_LOCAL_PREFIXES = List.of(
+ new IpPrefix("169.254.0.0/16"), // Link Local
+ new IpPrefix("100.64.0.0/10"), // CGNAT
+ new IpPrefix("10.0.0.0/8"), // RFC1918
+ new IpPrefix("172.16.0.0/12"), // RFC1918
+ new IpPrefix("192.168.0.0/16") // RFC1918
+ );
+
+ /**
+ * List of IpPrefix that are multicast and broadcast prefixes.
+ */
+ public static final List<IpPrefix> MULTICAST_AND_BROADCAST_PREFIXES = List.of(
+ new IpPrefix("224.0.0.0/4"), // Multicast
+ new IpPrefix("ff00::/8"), // Multicast
+ new IpPrefix("255.255.255.255/32") // Broadcast
+ );
+
// TODO: Move to Inet4AddressUtils
// See aosp/1455936: NetworkStackConstants can't depend on it as it causes jarjar-related issues
// for users of both the net-utils-device-common and net-utils-framework-common libraries.
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index 73eb24d..fd92672 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -269,9 +269,11 @@
public void testAddLocalNetAccessAfterVWithIncorrectInterface() throws Exception {
assertTrue(mLocalNetAccessMap.isEmpty());
+ // wlan2 is an incorrect interface
mBpfNetMaps.addLocalNetAccess(160, "wlan2",
Inet4Address.getByName("196.68.0.0"), 0, 0, true);
+ // As we tried to add incorrect interface, it would be skipped and map should be empty.
assertTrue(mLocalNetAccessMap.isEmpty());
}
@@ -302,6 +304,53 @@
@Test
@IgnoreAfter(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testRemoveLocalNetAccessBeforeV() {
+ assertThrows(UnsupportedOperationException.class, () ->
+ mBpfNetMaps.removeLocalNetAccess(0, TEST_IF_NAME, Inet6Address.ANY, 0, 0));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testRemoveLocalNetAccessAfterV() throws Exception {
+ assertTrue(mLocalNetAccessMap.isEmpty());
+
+ mBpfNetMaps.addLocalNetAccess(160, TEST_IF_NAME,
+ Inet4Address.getByName("196.68.0.0"), 0, 0, true);
+
+ assertNotNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+ Inet4Address.getByName("196.68.0.0"), 0, 0)));
+ assertNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+ Inet4Address.getByName("100.68.0.0"), 0, 0)));
+
+ mBpfNetMaps.removeLocalNetAccess(160, TEST_IF_NAME,
+ Inet4Address.getByName("196.68.0.0"), 0, 0);
+ assertNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+ Inet4Address.getByName("196.68.0.0"), 0, 0)));
+ assertNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+ Inet4Address.getByName("100.68.0.0"), 0, 0)));
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void testRemoveLocalNetAccessAfterVWithIncorrectInterface() throws Exception {
+ assertTrue(mLocalNetAccessMap.isEmpty());
+
+ mBpfNetMaps.addLocalNetAccess(160, TEST_IF_NAME,
+ Inet4Address.getByName("196.68.0.0"), 0, 0, true);
+
+ assertNotNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+ Inet4Address.getByName("196.68.0.0"), 0, 0)));
+ assertNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+ Inet4Address.getByName("100.68.0.0"), 0, 0)));
+
+ mBpfNetMaps.removeLocalNetAccess(160, "wlan2",
+ Inet4Address.getByName("196.68.0.0"), 0, 0);
+ assertNotNull(mLocalNetAccessMap.getValue(new LocalNetAccessKey(160, TEST_IF_INDEX,
+ Inet4Address.getByName("196.68.0.0"), 0, 0)));
+ }
+
+ @Test
+ @IgnoreAfter(Build.VERSION_CODES.VANILLA_ICE_CREAM)
public void testAddUidToLocalNetBlockMapBeforeV() {
assertThrows(UnsupportedOperationException.class, () ->
mBpfNetMaps.addUidToLocalNetBlockMap(0));
@@ -309,9 +358,9 @@
@Test
@IgnoreAfter(Build.VERSION_CODES.VANILLA_ICE_CREAM)
- public void testIsUidPresentInLocalNetBlockMapBeforeV() {
+ public void testIsUidBlockedFromUsingLocalNetworkBeforeV() {
assertThrows(UnsupportedOperationException.class, () ->
- mBpfNetMaps.getUidValueFromLocalNetBlockMap(0));
+ mBpfNetMaps.isUidBlockedFromUsingLocalNetwork(0));
}
@Test
@@ -339,18 +388,18 @@
@Test
@IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
- public void testIsUidPresentInLocalNetBlockMapAfterV() throws Exception {
+ public void testIsUidBlockedFromUsingLocalNetworkAfterV() throws Exception {
final int uid0 = TEST_UIDS[0];
final int uid1 = TEST_UIDS[1];
assertTrue(mLocalNetAccessMap.isEmpty());
mLocalNetBlockedUidMap.updateEntry(new U32(uid0), new Bool(true));
- assertTrue(mBpfNetMaps.getUidValueFromLocalNetBlockMap(uid0));
- assertFalse(mBpfNetMaps.getUidValueFromLocalNetBlockMap(uid1));
+ assertTrue(mBpfNetMaps.isUidBlockedFromUsingLocalNetwork(uid0));
+ assertFalse(mBpfNetMaps.isUidBlockedFromUsingLocalNetwork(uid1));
mLocalNetBlockedUidMap.updateEntry(new U32(uid1), new Bool(true));
- assertTrue(mBpfNetMaps.getUidValueFromLocalNetBlockMap(uid1));
+ assertTrue(mBpfNetMaps.isUidBlockedFromUsingLocalNetwork(uid1));
}
@Test
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSLocalNetworkProtectionTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSLocalNetworkProtectionTest.kt
new file mode 100644
index 0000000..73e7515
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSLocalNetworkProtectionTest.kt
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License")
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server
+
+import android.net.InetAddresses
+import android.net.IpPrefix
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
+import android.net.NetworkCapabilities.TRANSPORT_VPN
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.Lost
+import com.android.testutils.TestableNetworkCallback
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+private const val LONG_TIMEOUT_MS = 5_000
+private const val PREFIX_LENGTH_IPV4 = 32 + 96
+private const val PREFIX_LENGTH_IPV6 = 32
+private const val WIFI_IFNAME = "wlan0"
+private const val WIFI_IFNAME_2 = "wlan1"
+
+private val wifiNc = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .build()
+
+private fun lp(iface: String, vararg linkAddresses: LinkAddress) = LinkProperties().apply {
+ interfaceName = iface
+ for (linkAddress in linkAddresses) {
+ addLinkAddress(linkAddress)
+ }
+}
+
+private fun nr(transport: Int) = NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(transport).apply {
+ if (transport != TRANSPORT_VPN) {
+ addCapability(NET_CAPABILITY_NOT_VPN)
+ }
+ }.build()
+
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+class CSLocalNetworkProtectionTest : CSTest() {
+ private val LOCAL_IPV6_IP_ADDRESS_PREFIX = IpPrefix("fe80::1cf1:35ff:fe8c:db87/64")
+ private val LOCAL_IPV6_LINK_ADDRESS = LinkAddress(
+ LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress(),
+ LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()
+ )
+
+ private val LOCAL_IPV4_IP_ADDRESS_PREFIX_1 = IpPrefix("10.0.0.184/24")
+ private val LOCAL_IPV4_LINK_ADDRRESS_1 =
+ LinkAddress(
+ LOCAL_IPV4_IP_ADDRESS_PREFIX_1.getAddress(),
+ LOCAL_IPV4_IP_ADDRESS_PREFIX_1.getPrefixLength()
+ )
+
+ private val LOCAL_IPV4_IP_ADDRESS_PREFIX_2 = IpPrefix("10.0.255.184/24")
+ private val LOCAL_IPV4_LINK_ADDRRESS_2 =
+ LinkAddress(
+ LOCAL_IPV4_IP_ADDRESS_PREFIX_2.getAddress(),
+ LOCAL_IPV4_IP_ADDRESS_PREFIX_2.getPrefixLength()
+ )
+
+ @Test
+ fun testNetworkWithIPv6LocalAddress_AddressAddedToBpfMap() {
+ val nr = nr(TRANSPORT_WIFI)
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(nr, cb)
+
+ // Connecting to network with IPv6 local address in LinkProperties
+ val wifiLp = lp(WIFI_IFNAME, LOCAL_IPV6_LINK_ADDRESS)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+
+ // Multicast and Broadcast address should always be populated in local_net_access map
+ verifyPopulationOfMulticastAndBroadcastAddress()
+ // Verifying IPv6 address should be populated in local_net_access map
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()),
+ eq(WIFI_IFNAME),
+ eq(LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress()),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+ }
+
+ @Test
+ fun testNetworkWithIPv4LocalAddress_AddressAddedToBpfMap() {
+ val nr = nr(TRANSPORT_WIFI)
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(nr, cb)
+
+ val wifiLp = lp(WIFI_IFNAME, LOCAL_IPV4_LINK_ADDRRESS_1)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+
+ // Multicast and Broadcast address should always be populated in local_net_access map
+ verifyPopulationOfMulticastAndBroadcastAddress()
+
+ // Verifying IPv4 matching prefix(10.0.0.0/8) should be populated in local_net_access map
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV4 + 8),
+ eq(WIFI_IFNAME),
+ eq(InetAddresses.parseNumericAddress("10.0.0.0")),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+ }
+
+ @Test
+ fun testChangeLinkPropertiesWithDifferentLinkAddresses_AddressReplacedInBpfMap() {
+ val nr = nr(TRANSPORT_WIFI)
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(nr, cb)
+
+ val wifiLp = lp(WIFI_IFNAME, LOCAL_IPV6_LINK_ADDRESS)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+
+ // Multicast and Broadcast address should always be populated in local_net_access map
+ verifyPopulationOfMulticastAndBroadcastAddress()
+ // Verifying IPv6 address should be populated in local_net_access map
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()),
+ eq(WIFI_IFNAME),
+ eq(LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress()),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+
+ // Updating Link Property from IPv6 in Link Address to IPv4 in Link Address
+ val wifiLp2 = lp(WIFI_IFNAME, LOCAL_IPV4_LINK_ADDRRESS_1)
+ wifiAgent.sendLinkProperties(wifiLp2)
+ cb.expect<LinkPropertiesChanged>(wifiAgent.network)
+
+ // Verifying IPv4 matching prefix(10.0.0.0/8) should be populated in local_net_access map
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV4 + 8),
+ eq(WIFI_IFNAME),
+ eq(InetAddresses.parseNumericAddress("10.0.0.0")),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+ // Verifying IPv6 address should be removed from local_net_access map
+ verify(bpfNetMaps).removeLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()),
+ eq(WIFI_IFNAME),
+ eq(LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress()),
+ eq(0),
+ eq(0)
+ )
+ }
+
+ @Test
+ fun testChangeLinkPropertiesWithLinkAddressesInSameRange_AddressIntactInBpfMap() {
+ val nr = nr(TRANSPORT_WIFI)
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(nr, cb)
+
+ val wifiLp = lp(WIFI_IFNAME, LOCAL_IPV4_LINK_ADDRRESS_1)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+
+ // Multicast and Broadcast address should always be populated in local_net_access map
+ verifyPopulationOfMulticastAndBroadcastAddress()
+ // Verifying IPv4 matching prefix(10.0.0.0/8) should be populated in local_net_access map
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV4 + 8),
+ eq(WIFI_IFNAME),
+ eq(LOCAL_IPV4_IP_ADDRESS_PREFIX_1.getAddress()),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+
+ // Updating Link Property from one IPv4 to another IPv4 within same range(10.0.0.0/8)
+ val wifiLp2 = lp(WIFI_IFNAME, LOCAL_IPV4_LINK_ADDRRESS_2)
+ wifiAgent.sendLinkProperties(wifiLp2)
+ cb.expect<LinkPropertiesChanged>(wifiAgent.network)
+
+ // As both addresses below to same range, so no address should be removed from the map.
+ verify(bpfNetMaps, never()).removeLocalNetAccess(any(), any(), any(), any(), any())
+ }
+
+ @Test
+ fun testChangeLinkPropertiesWithDifferentInterface_AddressReplacedInBpfMap() {
+ val nr = nr(TRANSPORT_WIFI)
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(nr, cb)
+
+ val wifiLp = lp(WIFI_IFNAME, LOCAL_IPV6_LINK_ADDRESS)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+
+ // Multicast and Broadcast address should always be populated in local_net_access map
+ verifyPopulationOfMulticastAndBroadcastAddress()
+ // Verifying IPv6 address should be populated in local_net_access map
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()),
+ eq(WIFI_IFNAME),
+ eq(LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress()),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+
+ // Updating Link Property by changing interface name which has IPv4 instead of IPv6
+ val wifiLp2 = lp(WIFI_IFNAME_2, LOCAL_IPV4_LINK_ADDRRESS_1)
+ wifiAgent.sendLinkProperties(wifiLp2)
+ cb.expect<LinkPropertiesChanged>(wifiAgent.network)
+
+ // Multicast and Broadcast address should be populated in local_net_access map for
+ // new interface
+ verifyPopulationOfMulticastAndBroadcastAddress(WIFI_IFNAME_2)
+ // Verifying IPv4 matching prefix(10.0.0.0/8) should be populated in local_net_access map
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV4 + 8),
+ eq(WIFI_IFNAME_2),
+ eq(InetAddresses.parseNumericAddress("10.0.0.0")),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+ // Multicast and Broadcast address should be removed in local_net_access map for
+ // old interface
+ verifyRemovalOfMulticastAndBroadcastAddress()
+ // Verifying IPv6 address should be removed from local_net_access map
+ verify(bpfNetMaps).removeLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()),
+ eq(WIFI_IFNAME),
+ eq(LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress()),
+ eq(0),
+ eq(0)
+ )
+ }
+
+ @Test
+ fun testAddingAnotherNetwork_AllAddressesAddedInBpfMap() {
+ val nr = nr(TRANSPORT_WIFI)
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(nr, cb)
+
+ val wifiLp = lp(WIFI_IFNAME, LOCAL_IPV6_LINK_ADDRESS)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+
+ // Multicast and Broadcast address should always be populated in local_net_access map
+ verifyPopulationOfMulticastAndBroadcastAddress()
+ // Verifying IPv6 address should be populated in local_net_access map
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()),
+ eq(WIFI_IFNAME),
+ eq(LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress()),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+
+ // Adding another network with LinkProperty having IPv4 in LinkAddress
+ val wifiLp2 = lp(WIFI_IFNAME_2, LOCAL_IPV4_LINK_ADDRRESS_1)
+ val wifiAgent2 = Agent(nc = wifiNc, lp = wifiLp2)
+ wifiAgent2.connect()
+
+ // Multicast and Broadcast address should always be populated in local_net_access map
+ verifyPopulationOfMulticastAndBroadcastAddress(WIFI_IFNAME_2)
+ // Verifying IPv4 matching prefix(10.0.0.0/8) should be populated in local_net_access map
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV4 + 8),
+ eq(WIFI_IFNAME_2),
+ eq(InetAddresses.parseNumericAddress("10.0.0.0")),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+ // Verifying nothing should be removed from local_net_access map
+ verify(bpfNetMaps, never()).removeLocalNetAccess(any(), any(), any(), any(), any())
+ }
+
+ @Test
+ fun testDestroyingNetwork_AddressesRemovedFromBpfMap() {
+ val nr = nr(TRANSPORT_WIFI)
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(nr, cb)
+
+ val wifiLp = lp(WIFI_IFNAME, LOCAL_IPV6_LINK_ADDRESS)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+
+ // Multicast and Broadcast address should always be populated in local_net_access map
+ verifyPopulationOfMulticastAndBroadcastAddress()
+ // Verifying IPv6 address should be populated in local_net_access map
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq( PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()),
+ eq(WIFI_IFNAME),
+ eq(LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress()),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+
+ // Unregistering the network
+ wifiAgent.unregisterAfterReplacement(LONG_TIMEOUT_MS)
+ cb.expect<Lost>(wifiAgent.network)
+
+ // Multicast and Broadcast address should be removed in local_net_access map for
+ // old interface
+ verifyRemovalOfMulticastAndBroadcastAddress()
+ // Verifying IPv6 address should be removed from local_net_access map
+ verify(bpfNetMaps).removeLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV6 + LOCAL_IPV6_IP_ADDRESS_PREFIX.getPrefixLength()),
+ eq(WIFI_IFNAME),
+ eq(LOCAL_IPV6_IP_ADDRESS_PREFIX.getAddress()),
+ eq(0),
+ eq(0)
+ )
+ }
+
+ // Verify if multicast and broadcast addresses have been added using addLocalNetAccess
+ fun verifyPopulationOfMulticastAndBroadcastAddress(
+ interfaceName: String = WIFI_IFNAME
+ ) {
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV4 + 4),
+ eq(interfaceName),
+ eq(InetAddresses.parseNumericAddress("224.0.0.0")),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV6 + 8),
+ eq(interfaceName),
+ eq(InetAddresses.parseNumericAddress("ff00::")),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+ verify(bpfNetMaps).addLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV4 + 32),
+ eq(interfaceName),
+ eq(InetAddresses.parseNumericAddress("255.255.255.255")),
+ eq(0),
+ eq(0),
+ eq(false)
+ )
+ }
+
+ // Verify if multicast and broadcast addresses have been removed using removeLocalNetAccess
+ fun verifyRemovalOfMulticastAndBroadcastAddress(
+ interfaceName: String = WIFI_IFNAME
+ ) {
+ verify(bpfNetMaps).removeLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV4 + 4),
+ eq(interfaceName),
+ eq(InetAddresses.parseNumericAddress("224.0.0.0")),
+ eq(0),
+ eq(0)
+ )
+ verify(bpfNetMaps).removeLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV6 + 8),
+ eq(interfaceName),
+ eq(InetAddresses.parseNumericAddress("ff00::")),
+ eq(0),
+ eq(0)
+ )
+ verify(bpfNetMaps).removeLocalNetAccess(
+ eq(PREFIX_LENGTH_IPV4 + 32),
+ eq(interfaceName),
+ eq(InetAddresses.parseNumericAddress("255.255.255.255")),
+ eq(0),
+ eq(0)
+ )
+ }
+}