diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java index 4fae73a617..ebb8de1125 100644 --- a/service/src/com/android/server/BpfNetMaps.java +++ b/service/src/com/android/server/BpfNetMaps.java @@ -911,7 +911,7 @@ public class BpfNetMaps { + "(" + iface + ")"); return; } - LocalNetAccessKey localNetAccessKey = new LocalNetAccessKey(lpmBitlen, ifIndex, + final LocalNetAccessKey localNetAccessKey = new LocalNetAccessKey(lpmBitlen, ifIndex, address, protocol, remotePort); try { @@ -923,13 +923,43 @@ public class BpfNetMaps { } /** - * 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 @@ public class BpfNetMaps { + 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 @@ public class BpfNetMaps { * @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 18801f0d60..2872751348 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.NET_ENTERPRISE_ID_5; 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_UDP4_SENDMSG; 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.ISocketKeepaliveCallback; 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 @@ public class ConnectivityService extends IConnectivityManager.Stub // 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 @@ public class ConnectivityService extends IConnectivityManager.Stub updateIngressToVpnAddressFiltering(newLp, oldLp, networkAgent); + updateLocalNetworkAddresses(newLp, oldLp); + updateMtu(newLp, oldLp); // TODO - figure out what to do for clat // for (LinkProperties lp : newLp.getStackedLinks()) { @@ -9761,6 +9767,134 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + /** + * 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 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 linkAddressDiff = new CompareResult<>( + oldLp != null ? oldLp.getLinkAddresses() : null, + newLp != null ? newLp.getLinkAddresses() : null); + + List unicastLocalPrefixesToBeAdded = new ArrayList<>(); + List 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 getLocalNetworkPrefixesForAddress(LinkAddress linkAddress) { + List 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 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 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 026e985ede..8d7ae5cae0 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.Inet4Address; 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 final class NetworkStackConstants { */ public static final String TEST_URL_EXPIRATION_TIME = "test_url_expiration_time"; + /** + * List of IpPrefix that are local network prefixes. + */ + public static final List 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 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 73eb24d3dc..fd926720b2 100644 --- a/tests/unit/java/com/android/server/BpfNetMapsTest.java +++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java @@ -269,9 +269,11 @@ public final class BpfNetMapsTest { 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()); } @@ -300,6 +302,53 @@ public final class BpfNetMapsTest { Inet4Address.getByName("100.68.0.0"), 0, 0)); } + @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() { @@ -309,9 +358,9 @@ public final class BpfNetMapsTest { @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 @@ public final class BpfNetMapsTest { @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 0000000000..73e7515fcb --- /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(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(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(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(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) + ) + } +}