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..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; @@ -56,8 +58,11 @@ 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.net.InetAddress; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -162,9 +167,24 @@ 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<>(); + // 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(); @@ -504,6 +524,74 @@ 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); + } + } + + /** + * 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 @@ -581,6 +669,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 +746,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. 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) {