From d71c06ec712b953b7c61a1af746e3ef921718c96 Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Mon, 21 Dec 2020 19:39:49 +0800 Subject: [PATCH 1/2] [NFCT.TETHER.7] Prepare the downstream information for IPv4 offload rule Add and remove downstream client information to BpfCoordinator Required for building IPv4 forwarding rule when a conntrack event is received. The IpServer provides the following elements of a rule which is not included in conntrack event: - Downstream interface index - Downstream Mac address - Client IP address to Client Mac address Test: atest TetheringCoverageTests Change-Id: I84db13acc047ace5730d17f0d3dd99544f516084 --- Tethering/src/android/net/ip/IpServer.java | 28 ++++++ .../tethering/BpfCoordinator.java | 89 +++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java index bcabc0f197..7f3e80fb27 100644 --- a/Tethering/src/android/net/ip/IpServer.java +++ b/Tethering/src/android/net/ip/IpServer.java @@ -67,6 +67,7 @@ import com.android.internal.util.MessageUtils; import com.android.internal.util.State; import com.android.internal.util.StateMachine; import com.android.networkstack.tethering.BpfCoordinator; +import com.android.networkstack.tethering.BpfCoordinator.ClientInfo; import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; import com.android.networkstack.tethering.PrivateAddressCoordinator; @@ -941,11 +942,38 @@ public class IpServer extends StateMachine { } } + // TODO: consider moving into BpfCoordinator. + private void updateClientInfoIpv4(NeighborEvent e) { + // TODO: Perhaps remove this protection check. + // See the related comment in #addIpv6ForwardingRule. + if (!mUsingBpfOffload) return; + + if (e == null) return; + if (!(e.ip instanceof Inet4Address) || e.ip.isMulticastAddress() + || e.ip.isLoopbackAddress() || e.ip.isLinkLocalAddress()) { + return; + } + + // When deleting clients, IpServer still need to pass a non-null MAC, even though it's + // ignored. Do this here instead of in the ClientInfo constructor to ensure that + // IpServer never add clients with a null MAC, only delete them. + final MacAddress clientMac = e.isValid() ? e.macAddr : NULL_MAC_ADDRESS; + final ClientInfo clientInfo = new ClientInfo(mInterfaceParams.index, + mInterfaceParams.macAddr, (Inet4Address) e.ip, clientMac); + if (e.isValid()) { + mBpfCoordinator.tetherOffloadClientAdd(this, clientInfo); + } else { + // TODO: Delete all related offload rules which are using this client. + mBpfCoordinator.tetherOffloadClientRemove(this, clientInfo); + } + } + private void handleNeighborEvent(NeighborEvent e) { if (mInterfaceParams != null && mInterfaceParams.index == e.ifindex && mInterfaceParams.hasMacAddress) { updateIpv6ForwardingRules(mLastIPv6UpstreamIfindex, mLastIPv6UpstreamIfindex, e); + updateClientInfoIpv4(e); } } diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java index ea360b4c68..8ef0def8d4 100644 --- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java +++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java @@ -56,6 +56,7 @@ import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.NetworkStackConstants; import com.android.networkstack.tethering.apishim.common.BpfCoordinatorShim; +import java.net.Inet4Address; import java.net.Inet6Address; import java.util.ArrayList; import java.util.HashMap; @@ -162,6 +163,16 @@ public class BpfCoordinator { private final HashMap> mIpv6ForwardingRules = new LinkedHashMap<>(); + // Map of downstream client maps. Each of these maps represents the IPv4 clients for a given + // downstream. Needed to build IPv4 forwarding rules when conntrack events are received. + // Each map: + // - Is owned by the IpServer that is responsible for that downstream. + // - Must only be modified by that IpServer. + // - Is created when the IpServer adds its first client, and deleted when the IpServer deletes + // its last client. + private final HashMap> + mTetherClients = new HashMap<>(); + // Set for which downstream is monitoring the conntrack netlink message. private final Set mMonitoringIpServers = new HashSet<>(); @@ -504,6 +515,41 @@ public class BpfCoordinator { } } + /** + * Add downstream client. + */ + public void tetherOffloadClientAdd(@NonNull final IpServer ipServer, + @NonNull final ClientInfo client) { + if (!isUsingBpf()) return; + + if (!mTetherClients.containsKey(ipServer)) { + mTetherClients.put(ipServer, new HashMap()); + } + + HashMap clients = mTetherClients.get(ipServer); + clients.put(client.clientAddress, client); + } + + /** + * Remove downstream client. + */ + public void tetherOffloadClientRemove(@NonNull final IpServer ipServer, + @NonNull final ClientInfo client) { + if (!isUsingBpf()) return; + + HashMap clients = mTetherClients.get(ipServer); + if (clients == null) return; + + // If no rule is removed, return early. Avoid unnecessary work on a non-existent rule + // which may have never been added or removed already. + if (clients.remove(client.clientAddress) == null) return; + + // Remove the downstream entry if it has no more rule. + if (clients.isEmpty()) { + mTetherClients.remove(ipServer); + } + } + /** * Dump information. * Block the function until all the data are dumped on the handler thread or timed-out. The @@ -581,6 +627,7 @@ public class BpfCoordinator { public final int upstreamIfindex; public final int downstreamIfindex; + // TODO: store a ClientInfo object instead of storing address, srcMac, and dstMac directly. @NonNull public final Inet6Address address; @NonNull @@ -657,6 +704,48 @@ public class BpfCoordinator { } } + /** Tethering client information class. */ + public static class ClientInfo { + public final int downstreamIfindex; + + @NonNull + public final MacAddress downstreamMac; + @NonNull + public final Inet4Address clientAddress; + @NonNull + public final MacAddress clientMac; + + public ClientInfo(int downstreamIfindex, + @NonNull MacAddress downstreamMac, @NonNull Inet4Address clientAddress, + @NonNull MacAddress clientMac) { + this.downstreamIfindex = downstreamIfindex; + this.downstreamMac = downstreamMac; + this.clientAddress = clientAddress; + this.clientMac = clientMac; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof ClientInfo)) return false; + ClientInfo that = (ClientInfo) o; + return this.downstreamIfindex == that.downstreamIfindex + && Objects.equals(this.downstreamMac, that.downstreamMac) + && Objects.equals(this.clientAddress, that.clientAddress) + && Objects.equals(this.clientMac, that.clientMac); + } + + @Override + public int hashCode() { + return Objects.hash(downstreamIfindex, downstreamMac, clientAddress, clientMac); + } + + @Override + public String toString() { + return String.format("downstream: %d (%s), client: %s (%s)", + downstreamIfindex, downstreamMac, clientAddress, clientMac); + } + } + /** * A BPF tethering stats provider to provide network statistics to the system. * Note that this class' data may only be accessed on the handler thread. From 8784a60eabc3f908626f40e311ca3e6aa771f7df Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Tue, 22 Dec 2020 20:05:34 +0800 Subject: [PATCH 2/2] [NFCT.TETHER.8] Prepare the upstream information for IPv4 offload rule Add upstream interface index and its IPv4 address mapping Required for building IPv4 forwarding rule when a conntrack event is received. Test: atest TetheringCoverageTests Change-Id: I73f304777704f09481b80d18751e0bab5bab2edc --- .../tethering/BpfCoordinator.java | 42 +++++++++++++++++++ .../networkstack/tethering/Tethering.java | 7 ++++ 2 files changed, 49 insertions(+) diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java index 8ef0def8d4..3268e94993 100644 --- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java +++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java @@ -30,6 +30,7 @@ import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_ import android.app.usage.NetworkStatsManager; import android.net.INetd; +import android.net.LinkProperties; import android.net.MacAddress; import android.net.NetworkStats; import android.net.NetworkStats.Entry; @@ -38,6 +39,7 @@ import android.net.ip.ConntrackMonitor; import android.net.ip.ConntrackMonitor.ConntrackEventConsumer; import android.net.ip.IpServer; import android.net.netstats.provider.NetworkStatsProvider; +import android.net.util.InterfaceParams; import android.net.util.SharedLog; import android.net.util.TetheringUtils.ForwardedStats; import android.os.ConditionVariable; @@ -58,7 +60,9 @@ import com.android.networkstack.tethering.apishim.common.BpfCoordinatorShim; import java.net.Inet4Address; import java.net.Inet6Address; +import java.net.InetAddress; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -176,6 +180,11 @@ public class BpfCoordinator { // Set for which downstream is monitoring the conntrack netlink message. private final Set mMonitoringIpServers = new HashSet<>(); + // Map of upstream interface IPv4 address to interface index. + // TODO: consider making the key to be unique because the upstream address is not unique. It + // is okay for now because there have only one upstream generally. + private final HashMap mIpv4UpstreamIndices = new HashMap<>(); + // Runnable that used by scheduling next polling of stats. private final Runnable mScheduledPollingTask = () -> { updateForwardedStats(); @@ -550,6 +559,39 @@ public class BpfCoordinator { } } + /** + * Call when UpstreamNetworkState may be changed. + * If upstream has ipv4 for tethering, update this new UpstreamNetworkState to map. The + * upstream interface index and its address mapping is prepared for building IPv4 + * offload rule. + * + * TODO: Delete the unused upstream interface mapping. + * TODO: Support ether ip upstream interface. + */ + public void addUpstreamIfindexToMap(LinkProperties lp) { + if (!mPollingStarted) return; + + // This will not work on a network that is using 464xlat because hasIpv4Address will not be + // true. + // TODO: need to consider 464xlat. + if (lp == null || !lp.hasIpv4Address()) return; + + // Support raw ip upstream interface only. + final InterfaceParams params = InterfaceParams.getByName(lp.getInterfaceName()); + if (params == null || params.hasMacAddress) return; + + Collection addresses = lp.getAddresses(); + for (InetAddress addr: addresses) { + if (addr instanceof Inet4Address) { + Inet4Address i4addr = (Inet4Address) addr; + if (!i4addr.isAnyLocalAddress() && !i4addr.isLinkLocalAddress() + && !i4addr.isLoopbackAddress() && !i4addr.isMulticastAddress()) { + mIpv4UpstreamIndices.put(i4addr, params.index); + } + } + } + } + /** * Dump information. * Block the function until all the data are dumped on the handler thread or timed-out. The diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java index fdd1c40949..2354c2d2ed 100644 --- a/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -1636,6 +1636,13 @@ public class Tethering { protected void handleNewUpstreamNetworkState(UpstreamNetworkState ns) { mIPv6TetheringCoordinator.updateUpstreamNetworkState(ns); mOffload.updateUpstreamNetworkState(ns); + + // TODO: Delete all related offload rules which are using this upstream. + if (ns != null) { + // Add upstream index to the map. The upstream interface index is required while + // the conntrack event builds the offload rules. + mBpfCoordinator.addUpstreamIfindexToMap(ns.linkProperties); + } } private void handleInterfaceServingStateActive(int mode, IpServer who) {