[NFCT.TETHER.1] Add conntrack monitor to BpfCoordinator

A preparation for starting monitoring conntrack event which is required
by IPv4 tethering offload.

Test: atest TetheringCoverageTests
Change-Id: Ied46aeca193554f52a90889dfdf92827e94845d6
This commit is contained in:
Hungming Chen
2020-10-15 17:25:36 +08:00
parent b199742d73
commit 46c30b1fd4
4 changed files with 142 additions and 0 deletions

View File

@@ -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();
}

View File

@@ -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<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>>
mIpv6ForwardingRules = new LinkedHashMap<>();
// Set for which downstream is monitoring the conntrack netlink message.
private final Set<IpServer> 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 */;

View File

@@ -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<TetherDownstream6Key, TetherDownstream6Value> mBpfDownstream6Map;
@Mock private BpfMap<TetherStatsKey, TetherStatsValue> mBpfStatsMap;
@Mock private BpfMap<TetherLimitKey, TetherLimitValue> mBpfLimitMap;
@@ -295,6 +296,12 @@ public class IpServerTest {
return mTetherConfig;
}
@NonNull
public ConntrackMonitor getConntrackMonitor(
ConntrackMonitor.ConntrackEventConsumer consumer) {
return mConntrackMonitor;
}
@Nullable
public BpfMap<TetherDownstream6Key, TetherDownstream6Value>
getBpfDownstream6Map() {

View File

@@ -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<TetherDownstream6Key, TetherDownstream6Value> 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<TetherDownstream6Key, TetherDownstream6Value>
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();
}
}