diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java index 52d59fcdc1..bcabc0f197 100644 --- a/Tethering/src/android/net/ip/IpServer.java +++ b/Tethering/src/android/net/ip/IpServer.java @@ -1111,9 +1111,19 @@ public class IpServer extends StateMachine { } } + private void startConntrackMonitoring() { + mBpfCoordinator.startMonitoring(this); + } + + private void stopConntrackMonitoring() { + mBpfCoordinator.stopMonitoring(this); + } + class BaseServingState extends State { @Override public void enter() { + startConntrackMonitoring(); + if (!startIPv4()) { mLastError = TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR; return; @@ -1149,6 +1159,7 @@ public class IpServer extends StateMachine { } stopIPv4(); + stopConntrackMonitoring(); resetLinkProperties(); } diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java index acc6056f24..ea360b4c68 100644 --- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java +++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java @@ -34,6 +34,8 @@ import android.net.MacAddress; import android.net.NetworkStats; import android.net.NetworkStats.Entry; import android.net.TetherOffloadRuleParcel; +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.SharedLog; @@ -57,9 +59,11 @@ import com.android.networkstack.tethering.apishim.common.BpfCoordinatorShim; import java.net.Inet6Address; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; +import java.util.Set; /** * This coordinator is responsible for providing BPF offload relevant functionality. @@ -94,6 +98,8 @@ public class BpfCoordinator { private final SharedLog mLog; @NonNull private final Dependencies mDeps; + @NonNull + private final ConntrackMonitor mConntrackMonitor; @Nullable private final BpfTetherStatsProvider mStatsProvider; @NonNull @@ -156,6 +162,9 @@ public class BpfCoordinator { private final HashMap> mIpv6ForwardingRules = new LinkedHashMap<>(); + // Set for which downstream is monitoring the conntrack netlink message. + private final Set mMonitoringIpServers = new HashSet<>(); + // Runnable that used by scheduling next polling of stats. private final Runnable mScheduledPollingTask = () -> { updateForwardedStats(); @@ -179,6 +188,11 @@ public class BpfCoordinator { /** Get tethering configuration. */ @Nullable public abstract TetheringConfiguration getTetherConfig(); + /** Get conntrack monitor. */ + @NonNull public ConntrackMonitor getConntrackMonitor(ConntrackEventConsumer consumer) { + return new ConntrackMonitor(getHandler(), getSharedLog(), consumer); + } + /** * Check OS Build at least S. * @@ -232,6 +246,7 @@ public class BpfCoordinator { mNetd = mDeps.getNetd(); mLog = mDeps.getSharedLog().forSubComponent(TAG); mIsBpfEnabled = isBpfEnabled(); + mConntrackMonitor = mDeps.getConntrackMonitor(new BpfConntrackEventConsumer()); BpfTetherStatsProvider provider = new BpfTetherStatsProvider(); try { mDeps.getNetworkStatsManager().registerNetworkStatsProvider( @@ -295,6 +310,58 @@ public class BpfCoordinator { return mIsBpfEnabled && mBpfCoordinatorShim.isInitialized(); } + /** + * Start conntrack message monitoring. + * Note that this can be only called on handler thread. + * + * TODO: figure out a better logging for non-interesting conntrack message. + * For example, the following logging is an IPCTNL_MSG_CT_GET message but looks scary. + * +---------------------------------------------------------------------------+ + * | ERROR unparsable netlink msg: 1400000001010103000000000000000002000000 | + * +------------------+--------------------------------------------------------+ + * | | struct nlmsghdr | + * | 14000000 | length = 20 | + * | 0101 | type = NFNL_SUBSYS_CTNETLINK << 8 | IPCTNL_MSG_CT_GET | + * | 0103 | flags | + * | 00000000 | seqno = 0 | + * | 00000000 | pid = 0 | + * | | struct nfgenmsg | + * | 02 | nfgen_family = AF_INET | + * | 00 | version = NFNETLINK_V0 | + * | 0000 | res_id | + * +------------------+--------------------------------------------------------+ + * See NetlinkMonitor#handlePacket, NetlinkMessage#parseNfMessage. + */ + public void startMonitoring(@NonNull final IpServer ipServer) { + if (!isUsingBpf()) return; + + if (mMonitoringIpServers.contains(ipServer)) { + Log.wtf(TAG, "The same downstream " + ipServer.interfaceName() + + " should not start monitoring twice."); + return; + } + + if (mMonitoringIpServers.isEmpty()) { + mConntrackMonitor.start(); + mLog.i("Monitoring started"); + } + + mMonitoringIpServers.add(ipServer); + } + + /** + * Stop conntrack event monitoring. + * Note that this can be only called on handler thread. + */ + public void stopMonitoring(@NonNull final IpServer ipServer) { + mMonitoringIpServers.remove(ipServer); + + if (!mMonitoringIpServers.isEmpty()) return; + + mConntrackMonitor.stop(); + mLog.i("Monitoring stopped"); + } + /** * Add forwarding rule. After adding the first rule on a given upstream, must add the data * limit on the given upstream. @@ -656,6 +723,10 @@ public class BpfCoordinator { } } + private class BpfConntrackEventConsumer implements ConntrackEventConsumer { + public void accept(ConntrackMonitor.ConntrackEvent e) { /* TODO */ } + } + private boolean isBpfEnabled() { final TetheringConfiguration config = mDeps.getTetherConfig(); return (config != null) ? config.isBpfOffloadEnabled() : true /* default value */; diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java index 6668402e27..e20e011598 100644 --- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java +++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java @@ -172,6 +172,7 @@ public class IpServerTest { @Mock private PrivateAddressCoordinator mAddressCoordinator; @Mock private NetworkStatsManager mStatsManager; @Mock private TetheringConfiguration mTetherConfig; + @Mock private ConntrackMonitor mConntrackMonitor; @Mock private BpfMap mBpfDownstream6Map; @Mock private BpfMap mBpfStatsMap; @Mock private BpfMap mBpfLimitMap; @@ -295,6 +296,12 @@ public class IpServerTest { return mTetherConfig; } + @NonNull + public ConntrackMonitor getConntrackMonitor( + ConntrackMonitor.ConntrackEventConsumer consumer) { + return mConntrackMonitor; + } + @Nullable public BpfMap getBpfDownstream6Map() { diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java index c934d074b8..764e6516d1 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java @@ -56,6 +56,8 @@ import android.net.MacAddress; import android.net.NetworkStats; import android.net.TetherOffloadRuleParcel; import android.net.TetherStatsParcel; +import android.net.ip.ConntrackMonitor; +import android.net.ip.ConntrackMonitor.ConntrackEventConsumer; import android.net.ip.IpServer; import android.net.util.SharedLog; import android.os.Build; @@ -153,7 +155,9 @@ public class BpfCoordinatorTest { @Mock private NetworkStatsManager mStatsManager; @Mock private INetd mNetd; @Mock private IpServer mIpServer; + @Mock private IpServer mIpServer2; @Mock private TetheringConfiguration mTetherConfig; + @Mock private ConntrackMonitor mConntrackMonitor; @Mock private BpfMap mBpfDownstream6Map; // Late init since methods must be called by the thread that created this object. @@ -193,6 +197,11 @@ public class BpfCoordinatorTest { return mTetherConfig; } + @NonNull + public ConntrackMonitor getConntrackMonitor(ConntrackEventConsumer consumer) { + return mConntrackMonitor; + } + @Nullable public BpfMap getBpfDownstream6Map() { @@ -983,4 +992,48 @@ public class BpfCoordinatorTest { waitForIdle(); verifyTetherOffloadGetStats(); } + + @Test + public void testStartStopConntrackMonitoring() throws Exception { + setupFunctioningNetdInterface(); + + final BpfCoordinator coordinator = makeBpfCoordinator(); + + // [1] Don't stop monitoring if it has never started. + coordinator.stopMonitoring(mIpServer); + verify(mConntrackMonitor, never()).start(); + + // [2] Start monitoring. + coordinator.startMonitoring(mIpServer); + verify(mConntrackMonitor).start(); + clearInvocations(mConntrackMonitor); + + // [3] Stop monitoring. + coordinator.stopMonitoring(mIpServer); + verify(mConntrackMonitor).stop(); + } + + @Test + public void testStartStopConntrackMonitoringWithTwoDownstreamIfaces() throws Exception { + setupFunctioningNetdInterface(); + + final BpfCoordinator coordinator = makeBpfCoordinator(); + + // [1] Start monitoring at the first IpServer adding. + coordinator.startMonitoring(mIpServer); + verify(mConntrackMonitor).start(); + clearInvocations(mConntrackMonitor); + + // [2] Don't start monitoring at the second IpServer adding. + coordinator.startMonitoring(mIpServer2); + verify(mConntrackMonitor, never()).start(); + + // [3] Don't stop monitoring if any downstream interface exists. + coordinator.stopMonitoring(mIpServer2); + verify(mConntrackMonitor, never()).stop(); + + // [4] Stop monitoring if no downstream exists. + coordinator.stopMonitoring(mIpServer); + verify(mConntrackMonitor).stop(); + } }