[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
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index bcabc0f..7f3e80f 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.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 @@
}
}
+ // 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 ea360b4..8ef0def 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.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 @@
private final HashMap<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>>
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<IpServer, HashMap<Inet4Address, ClientInfo>>
+ mTetherClients = new HashMap<>();
+
// Set for which downstream is monitoring the conntrack netlink message.
private final Set<IpServer> mMonitoringIpServers = new HashSet<>();
@@ -505,6 +516,41 @@
}
/**
+ * 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<Inet4Address, ClientInfo>());
+ }
+
+ HashMap<Inet4Address, ClientInfo> 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<Inet4Address, ClientInfo> 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
* reason is that dumpsys invokes this function on the thread of caller and the data may only
@@ -581,6 +627,7 @@
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 @@
}
}
+ /** 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.