[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 { class BaseServingState extends State {
@Override @Override
public void enter() { public void enter() {
startConntrackMonitoring();
if (!startIPv4()) { if (!startIPv4()) {
mLastError = TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR; mLastError = TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
return; return;
@@ -1149,6 +1159,7 @@ public class IpServer extends StateMachine {
} }
stopIPv4(); stopIPv4();
stopConntrackMonitoring();
resetLinkProperties(); resetLinkProperties();
} }

View File

@@ -34,6 +34,8 @@ import android.net.MacAddress;
import android.net.NetworkStats; import android.net.NetworkStats;
import android.net.NetworkStats.Entry; import android.net.NetworkStats.Entry;
import android.net.TetherOffloadRuleParcel; import android.net.TetherOffloadRuleParcel;
import android.net.ip.ConntrackMonitor;
import android.net.ip.ConntrackMonitor.ConntrackEventConsumer;
import android.net.ip.IpServer; import android.net.ip.IpServer;
import android.net.netstats.provider.NetworkStatsProvider; import android.net.netstats.provider.NetworkStatsProvider;
import android.net.util.SharedLog; import android.net.util.SharedLog;
@@ -57,9 +59,11 @@ import com.android.networkstack.tethering.apishim.common.BpfCoordinatorShim;
import java.net.Inet6Address; import java.net.Inet6Address;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
/** /**
* This coordinator is responsible for providing BPF offload relevant functionality. * This coordinator is responsible for providing BPF offload relevant functionality.
@@ -94,6 +98,8 @@ public class BpfCoordinator {
private final SharedLog mLog; private final SharedLog mLog;
@NonNull @NonNull
private final Dependencies mDeps; private final Dependencies mDeps;
@NonNull
private final ConntrackMonitor mConntrackMonitor;
@Nullable @Nullable
private final BpfTetherStatsProvider mStatsProvider; private final BpfTetherStatsProvider mStatsProvider;
@NonNull @NonNull
@@ -156,6 +162,9 @@ public class BpfCoordinator {
private final HashMap<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>> private final HashMap<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>>
mIpv6ForwardingRules = new LinkedHashMap<>(); 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. // Runnable that used by scheduling next polling of stats.
private final Runnable mScheduledPollingTask = () -> { private final Runnable mScheduledPollingTask = () -> {
updateForwardedStats(); updateForwardedStats();
@@ -179,6 +188,11 @@ public class BpfCoordinator {
/** Get tethering configuration. */ /** Get tethering configuration. */
@Nullable public abstract TetheringConfiguration getTetherConfig(); @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. * Check OS Build at least S.
* *
@@ -232,6 +246,7 @@ public class BpfCoordinator {
mNetd = mDeps.getNetd(); mNetd = mDeps.getNetd();
mLog = mDeps.getSharedLog().forSubComponent(TAG); mLog = mDeps.getSharedLog().forSubComponent(TAG);
mIsBpfEnabled = isBpfEnabled(); mIsBpfEnabled = isBpfEnabled();
mConntrackMonitor = mDeps.getConntrackMonitor(new BpfConntrackEventConsumer());
BpfTetherStatsProvider provider = new BpfTetherStatsProvider(); BpfTetherStatsProvider provider = new BpfTetherStatsProvider();
try { try {
mDeps.getNetworkStatsManager().registerNetworkStatsProvider( mDeps.getNetworkStatsManager().registerNetworkStatsProvider(
@@ -295,6 +310,58 @@ public class BpfCoordinator {
return mIsBpfEnabled && mBpfCoordinatorShim.isInitialized(); 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 * Add forwarding rule. After adding the first rule on a given upstream, must add the data
* limit on the given upstream. * 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() { private boolean isBpfEnabled() {
final TetheringConfiguration config = mDeps.getTetherConfig(); final TetheringConfiguration config = mDeps.getTetherConfig();
return (config != null) ? config.isBpfOffloadEnabled() : true /* default value */; return (config != null) ? config.isBpfOffloadEnabled() : true /* default value */;

View File

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

View File

@@ -56,6 +56,8 @@ import android.net.MacAddress;
import android.net.NetworkStats; import android.net.NetworkStats;
import android.net.TetherOffloadRuleParcel; import android.net.TetherOffloadRuleParcel;
import android.net.TetherStatsParcel; import android.net.TetherStatsParcel;
import android.net.ip.ConntrackMonitor;
import android.net.ip.ConntrackMonitor.ConntrackEventConsumer;
import android.net.ip.IpServer; import android.net.ip.IpServer;
import android.net.util.SharedLog; import android.net.util.SharedLog;
import android.os.Build; import android.os.Build;
@@ -153,7 +155,9 @@ public class BpfCoordinatorTest {
@Mock private NetworkStatsManager mStatsManager; @Mock private NetworkStatsManager mStatsManager;
@Mock private INetd mNetd; @Mock private INetd mNetd;
@Mock private IpServer mIpServer; @Mock private IpServer mIpServer;
@Mock private IpServer mIpServer2;
@Mock private TetheringConfiguration mTetherConfig; @Mock private TetheringConfiguration mTetherConfig;
@Mock private ConntrackMonitor mConntrackMonitor;
@Mock private BpfMap<TetherDownstream6Key, TetherDownstream6Value> mBpfDownstream6Map; @Mock private BpfMap<TetherDownstream6Key, TetherDownstream6Value> mBpfDownstream6Map;
// Late init since methods must be called by the thread that created this object. // Late init since methods must be called by the thread that created this object.
@@ -193,6 +197,11 @@ public class BpfCoordinatorTest {
return mTetherConfig; return mTetherConfig;
} }
@NonNull
public ConntrackMonitor getConntrackMonitor(ConntrackEventConsumer consumer) {
return mConntrackMonitor;
}
@Nullable @Nullable
public BpfMap<TetherDownstream6Key, TetherDownstream6Value> public BpfMap<TetherDownstream6Key, TetherDownstream6Value>
getBpfDownstream6Map() { getBpfDownstream6Map() {
@@ -983,4 +992,48 @@ public class BpfCoordinatorTest {
waitForIdle(); waitForIdle();
verifyTetherOffloadGetStats(); 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();
}
} }