Merge "Make members final in TetheringService"
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 29660a9..eb72b81 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -114,6 +114,7 @@
// InProcessTethering is a replacement for Tethering
overrides: ["Tethering"],
apex_available: ["com.android.tethering"],
+ min_sdk_version: "current",
}
// Updatable tethering packaged as an application
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index 7ac9d25..79a1782 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -13,49 +13,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// AIDL interfaces between the core system and the tethering mainline module.
-aidl_interface {
- name: "tethering-aidl-interfaces",
- unstable: true,
- local_include_dir: "src",
- include_dirs: ["frameworks/base/core/java"], // For framework parcelables.
- srcs: [
- // @JavaOnlyStableParcelable aidl declarations must not be listed here, as this would cause
- // compilation to fail (b/148001843).
- "src/android/net/IIntResultListener.aidl",
- "src/android/net/ITetheringConnector.aidl",
- "src/android/net/ITetheringEventCallback.aidl",
- "src/android/net/TetheringCallbackStartedParcel.aidl",
- "src/android/net/TetheringConfigurationParcel.aidl",
- "src/android/net/TetheringRequestParcel.aidl",
- "src/android/net/TetherStatesParcel.aidl",
- ],
- backend: {
- ndk: {
- enabled: false,
- },
- cpp: {
- enabled: false,
- },
- java: {
- apex_available: [
- "//apex_available:platform",
- "com.android.tethering",
- ],
- },
- },
-}
-
java_library {
name: "framework-tethering",
sdk_version: "module_current",
srcs: [
- "src/android/net/TetheredClient.java",
- "src/android/net/TetheringManager.java",
- "src/android/net/TetheringConstants.java",
- ],
- static_libs: [
- "tethering-aidl-interfaces-java",
+ ":framework-tethering-srcs",
],
jarjar_rules: "jarjar-rules.txt",
installable: true,
diff --git a/Tethering/res/values/config.xml b/Tethering/res/values/config.xml
index c999221..3f5bc90 100644
--- a/Tethering/res/values/config.xml
+++ b/Tethering/res/values/config.xml
@@ -55,6 +55,12 @@
<item>"bt-pan"</item>
</string-array>
+ <!-- Use the BPF offload for tethering when the kernel has support. True by default.
+ If the device doesn't want to support tether BPF offload, this should be false.
+ Note that this setting could be overridden by device config.
+ -->
+ <bool translatable="false" name="config_tether_enable_bpf_offload">true</bool>
+
<!-- Use the old dnsmasq DHCP server for tethering instead of the framework implementation. -->
<bool translatable="false" name="config_tether_enable_legacy_dhcp_server">false</bool>
diff --git a/Tethering/res/values/overlayable.xml b/Tethering/res/values/overlayable.xml
index 4c78a74..4e2bb1e 100644
--- a/Tethering/res/values/overlayable.xml
+++ b/Tethering/res/values/overlayable.xml
@@ -23,6 +23,11 @@
<item type="array" name="config_tether_wifi_p2p_regexs"/>
<item type="array" name="config_tether_bluetooth_regexs"/>
<item type="array" name="config_tether_dhcp_range"/>
+ <!-- Use the BPF offload for tethering when the kernel has support. True by default.
+ If the device doesn't want to support tether BPF offload, this should be false.
+ Note that this setting could be overridden by device config.
+ -->
+ <item type="bool" name="config_tether_enable_bpf_offload"/>
<item type="bool" name="config_tether_enable_legacy_dhcp_server"/>
<item type="integer" name="config_tether_offload_poll_interval"/>
<item type="array" name="config_tether_upstream_types"/>
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 83727bc..d993306 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -227,6 +227,7 @@
private final int mInterfaceType;
private final LinkProperties mLinkProperties;
private final boolean mUsingLegacyDhcp;
+ private final boolean mUsingBpfOffload;
private final Dependencies mDeps;
@@ -302,9 +303,12 @@
private final IpNeighborMonitor mIpNeighborMonitor;
+ // TODO: Add a dependency object to pass the data members or variables from the tethering
+ // object. It helps to reduce the arguments of the constructor.
public IpServer(
String ifaceName, Looper looper, int interfaceType, SharedLog log,
- INetd netd, Callback callback, boolean usingLegacyDhcp, Dependencies deps) {
+ INetd netd, Callback callback, boolean usingLegacyDhcp, boolean usingBpfOffload,
+ Dependencies deps) {
super(ifaceName, looper);
mLog = log.forSubComponent(ifaceName);
mNetd = netd;
@@ -314,6 +318,7 @@
mInterfaceType = interfaceType;
mLinkProperties = new LinkProperties();
mUsingLegacyDhcp = usingLegacyDhcp;
+ mUsingBpfOffload = usingBpfOffload;
mDeps = deps;
resetLinkProperties();
mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
@@ -321,7 +326,12 @@
mIpNeighborMonitor = mDeps.getIpNeighborMonitor(getHandler(), mLog,
new MyNeighborEventConsumer());
- if (!mIpNeighborMonitor.start()) {
+
+ // IP neighbor monitor monitors the neighbor events for adding/removing offload
+ // forwarding rules per client. If BPF offload is not supported, don't start listening
+ // for neighbor events. See updateIpv6ForwardingRules, addIpv6ForwardingRule,
+ // removeIpv6ForwardingRule.
+ if (mUsingBpfOffload && !mIpNeighborMonitor.start()) {
mLog.e("Failed to create IpNeighborMonitor on " + mIfaceName);
}
@@ -715,12 +725,12 @@
final String upstreamIface = v6only.getInterfaceName();
params = new RaParams();
- // We advertise an mtu lower by 16, which is the closest multiple of 8 >= 14,
- // the ethernet header size. This makes kernel ebpf tethering offload happy.
- // This hack should be reverted once we have the kernel fixed up.
+ // When BPF offload is enabled, we advertise an mtu lower by 16, which is the closest
+ // multiple of 8 >= 14, the ethernet header size. This makes kernel ebpf tethering
+ // offload happy. This hack should be reverted once we have the kernel fixed up.
// Note: this will automatically clamp to at least 1280 (ipv6 minimum mtu)
// see RouterAdvertisementDaemon.java putMtu()
- params.mtu = v6only.getMtu() - 16;
+ params.mtu = mUsingBpfOffload ? v6only.getMtu() - 16 : v6only.getMtu();
params.hasDefaultRoute = v6only.hasIpv6DefaultRoute();
if (params.hasDefaultRoute) params.hopLimit = getHopLimit(upstreamIface);
@@ -844,6 +854,11 @@
}
private void addIpv6ForwardingRule(Ipv6ForwardingRule rule) {
+ // Theoretically, we don't need this check because IP neighbor monitor doesn't start if BPF
+ // offload is disabled. Add this check just in case.
+ // TODO: Perhaps remove this protection check.
+ if (!mUsingBpfOffload) return;
+
try {
mNetd.tetherOffloadRuleAdd(rule.toTetherOffloadRuleParcel());
mIpv6ForwardingRules.put(rule.address, rule);
@@ -853,6 +868,11 @@
}
private void removeIpv6ForwardingRule(Ipv6ForwardingRule rule, boolean removeFromMap) {
+ // Theoretically, we don't need this check because IP neighbor monitor doesn't start if BPF
+ // offload is disabled. Add this check just in case.
+ // TODO: Perhaps remove this protection check.
+ if (!mUsingBpfOffload) return;
+
try {
mNetd.tetherOffloadRuleRemove(rule.toTetherOffloadRuleParcel());
if (removeFromMap) {
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
index 293f8ea..fe92204 100644
--- a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -66,6 +66,7 @@
private final Handler mHandler;
private final SharedLog mLog;
+ private final Dependencies mDeps;
private IOffloadControl mOffloadControl;
private TetheringOffloadCallback mTetheringOffloadCallback;
private ControlCallback mControlCallback;
@@ -126,8 +127,76 @@
}
public OffloadHardwareInterface(Handler h, SharedLog log) {
+ this(h, log, new Dependencies(log));
+ }
+
+ OffloadHardwareInterface(Handler h, SharedLog log, Dependencies deps) {
mHandler = h;
mLog = log.forSubComponent(TAG);
+ mDeps = deps;
+ }
+
+ /** Capture OffloadHardwareInterface dependencies, for injection. */
+ static class Dependencies {
+ private final SharedLog mLog;
+
+ Dependencies(SharedLog log) {
+ mLog = log;
+ }
+
+ public IOffloadConfig getOffloadConfig() {
+ try {
+ return IOffloadConfig.getService(true /*retry*/);
+ } catch (RemoteException | NoSuchElementException e) {
+ mLog.e("getIOffloadConfig error " + e);
+ return null;
+ }
+ }
+
+ public IOffloadControl getOffloadControl() {
+ try {
+ return IOffloadControl.getService(true /*retry*/);
+ } catch (RemoteException | NoSuchElementException e) {
+ mLog.e("tethering offload control not supported: " + e);
+ return null;
+ }
+ }
+
+ public NativeHandle createConntrackSocket(final int groups) {
+ final FileDescriptor fd;
+ try {
+ fd = NetlinkSocket.forProto(OsConstants.NETLINK_NETFILTER);
+ } catch (ErrnoException e) {
+ mLog.e("Unable to create conntrack socket " + e);
+ return null;
+ }
+
+ final SocketAddress sockAddr = SocketUtils.makeNetlinkSocketAddress(0, groups);
+ try {
+ Os.bind(fd, sockAddr);
+ } catch (ErrnoException | SocketException e) {
+ mLog.e("Unable to bind conntrack socket for groups " + groups + " error: " + e);
+ try {
+ SocketUtils.closeSocket(fd);
+ } catch (IOException ie) {
+ // Nothing we can do here
+ }
+ return null;
+ }
+ try {
+ Os.connect(fd, sockAddr);
+ } catch (ErrnoException | SocketException e) {
+ mLog.e("connect to kernel fail for groups " + groups + " error: " + e);
+ try {
+ SocketUtils.closeSocket(fd);
+ } catch (IOException ie) {
+ // Nothing we can do here
+ }
+ return null;
+ }
+
+ return new NativeHandle(fd, true);
+ }
}
/** Get default value indicating whether offload is supported. */
@@ -141,13 +210,7 @@
* share them with offload management process.
*/
public boolean initOffloadConfig() {
- IOffloadConfig offloadConfig;
- try {
- offloadConfig = IOffloadConfig.getService(true /*retry*/);
- } catch (RemoteException | NoSuchElementException e) {
- mLog.e("getIOffloadConfig error " + e);
- return false;
- }
+ final IOffloadConfig offloadConfig = mDeps.getOffloadConfig();
if (offloadConfig == null) {
mLog.e("Could not find IOffloadConfig service");
return false;
@@ -159,11 +222,11 @@
//
// h2 provides a file descriptor bound to the following netlink groups
// (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY).
- final NativeHandle h1 = createConntrackSocket(
+ final NativeHandle h1 = mDeps.createConntrackSocket(
NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY);
if (h1 == null) return false;
- final NativeHandle h2 = createConntrackSocket(
+ final NativeHandle h2 = mDeps.createConntrackSocket(
NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY);
if (h2 == null) {
closeFdInNativeHandle(h1);
@@ -198,53 +261,12 @@
}
}
- private NativeHandle createConntrackSocket(final int groups) {
- FileDescriptor fd;
- try {
- fd = NetlinkSocket.forProto(OsConstants.NETLINK_NETFILTER);
- } catch (ErrnoException e) {
- mLog.e("Unable to create conntrack socket " + e);
- return null;
- }
-
- final SocketAddress sockAddr = SocketUtils.makeNetlinkSocketAddress(0, groups);
- try {
- Os.bind(fd, sockAddr);
- } catch (ErrnoException | SocketException e) {
- mLog.e("Unable to bind conntrack socket for groups " + groups + " error: " + e);
- try {
- SocketUtils.closeSocket(fd);
- } catch (IOException ie) {
- // Nothing we can do here
- }
- return null;
- }
- try {
- Os.connect(fd, sockAddr);
- } catch (ErrnoException | SocketException e) {
- mLog.e("connect to kernel fail for groups " + groups + " error: " + e);
- try {
- SocketUtils.closeSocket(fd);
- } catch (IOException ie) {
- // Nothing we can do here
- }
- return null;
- }
-
- return new NativeHandle(fd, true);
- }
-
/** Initialize the tethering offload HAL. */
public boolean initOffloadControl(ControlCallback controlCb) {
mControlCallback = controlCb;
if (mOffloadControl == null) {
- try {
- mOffloadControl = IOffloadControl.getService(true /*retry*/);
- } catch (RemoteException | NoSuchElementException e) {
- mLog.e("tethering offload control not supported: " + e);
- return false;
- }
+ mOffloadControl = mDeps.getOffloadControl();
if (mOffloadControl == null) {
mLog.e("tethering IOffloadControl.getService() returned null");
return false;
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 19a8a20..3ce3b45 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -2314,7 +2314,7 @@
final TetherState tetherState = new TetherState(
new IpServer(iface, mLooper, interfaceType, mLog, mNetd,
makeControlCallback(), mConfig.enableLegacyDhcpServer,
- mDeps.getIpServerDependencies()));
+ mConfig.enableBpfOffload, mDeps.getIpServerDependencies()));
mTetherStates.put(iface, tetherState);
tetherState.ipServer.start();
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 9d4e747..91a6e29 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -73,6 +73,12 @@
private static final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"};
/**
+ * Override enabling BPF offload configuration for tethering.
+ */
+ public static final String OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD =
+ "override_tether_enable_bpf_offload";
+
+ /**
* Use the old dnsmasq DHCP server for tethering instead of the framework implementation.
*/
public static final String TETHER_ENABLE_LEGACY_DHCP_SERVER =
@@ -95,6 +101,8 @@
public final String[] legacyDhcpRanges;
public final String[] defaultIPv4DNS;
public final boolean enableLegacyDhcpServer;
+ // TODO: Add to TetheringConfigurationParcel if required.
+ public final boolean enableBpfOffload;
public final String[] provisioningApp;
public final String provisioningAppNoUi;
@@ -124,11 +132,12 @@
isDunRequired = checkDunRequired(ctx);
chooseUpstreamAutomatically = getResourceBoolean(
- res, R.bool.config_tether_upstream_automatic);
+ res, R.bool.config_tether_upstream_automatic, false /** default value */);
preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, isDunRequired);
legacyDhcpRanges = getLegacyDhcpRanges(res);
defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
+ enableBpfOffload = getEnableBpfOffload(res);
enableLegacyDhcpServer = getEnableLegacyDhcpServer(res);
provisioningApp = getResourceStringArray(res, R.array.config_mobile_hotspot_provision_app);
@@ -208,6 +217,9 @@
pw.print("provisioningAppNoUi: ");
pw.println(provisioningAppNoUi);
+ pw.print("enableBpfOffload: ");
+ pw.println(enableBpfOffload);
+
pw.print("enableLegacyDhcpServer: ");
pw.println(enableLegacyDhcpServer);
}
@@ -228,6 +240,7 @@
toIntArray(preferredUpstreamIfaceTypes)));
sj.add(String.format("provisioningApp:%s", makeString(provisioningApp)));
sj.add(String.format("provisioningAppNoUi:%s", provisioningAppNoUi));
+ sj.add(String.format("enableBpfOffload:%s", enableBpfOffload));
sj.add(String.format("enableLegacyDhcpServer:%s", enableLegacyDhcpServer));
return String.format("TetheringConfiguration{%s}", sj.toString());
}
@@ -332,11 +345,11 @@
}
}
- private static boolean getResourceBoolean(Resources res, int resId) {
+ private static boolean getResourceBoolean(Resources res, int resId, boolean defaultValue) {
try {
return res.getBoolean(resId);
} catch (Resources.NotFoundException e404) {
- return false;
+ return defaultValue;
}
}
@@ -357,8 +370,29 @@
}
}
+ private boolean getEnableBpfOffload(final Resources res) {
+ // Get BPF offload config
+ // Priority 1: Device config
+ // Priority 2: Resource config
+ // Priority 3: Default value
+ final boolean resourceValue = getResourceBoolean(
+ res, R.bool.config_tether_enable_bpf_offload, true /** default value */);
+
+ // Due to the limitation of static mock for testing, using #getProperty directly instead
+ // of getDeviceConfigBoolean. getDeviceConfigBoolean is not invoked because it uses
+ // #getBoolean to get the boolean device config. The test can't know that the returned
+ // boolean value comes from device config or default value (because of null property
+ // string). Because the test would like to verify null property boolean string case,
+ // use DeviceConfig.getProperty here. See also the test case testBpfOffload{*} in
+ // TetheringConfigurationTest.java.
+ final String value = DeviceConfig.getProperty(
+ NAMESPACE_CONNECTIVITY, OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD);
+ return (value != null) ? Boolean.parseBoolean(value) : resourceValue;
+ }
+
private boolean getEnableLegacyDhcpServer(final Resources res) {
- return getResourceBoolean(res, R.bool.config_tether_enable_legacy_dhcp_server)
+ return getResourceBoolean(
+ res, R.bool.config_tether_enable_legacy_dhcp_server, false /** default value */)
|| getDeviceConfigBoolean(TETHER_ENABLE_LEGACY_DHCP_SERVER);
}
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index f9be7b9..b9622da 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -106,6 +106,7 @@
private static final String BLUETOOTH_IFACE_ADDR = "192.168.42.1";
private static final int BLUETOOTH_DHCP_PREFIX_LENGTH = 24;
private static final int DHCP_LEASE_TIME_SECS = 3600;
+ private static final boolean DEFAULT_USING_BPF_OFFLOAD = true;
private static final InterfaceParams TEST_IFACE_PARAMS = new InterfaceParams(
IFACE_NAME, 42 /* index */, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */);
@@ -130,10 +131,11 @@
private NeighborEventConsumer mNeighborEventConsumer;
private void initStateMachine(int interfaceType) throws Exception {
- initStateMachine(interfaceType, false /* usingLegacyDhcp */);
+ initStateMachine(interfaceType, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD);
}
- private void initStateMachine(int interfaceType, boolean usingLegacyDhcp) throws Exception {
+ private void initStateMachine(int interfaceType, boolean usingLegacyDhcp,
+ boolean usingBpfOffload) throws Exception {
doAnswer(inv -> {
final IDhcpServerCallbacks cb = inv.getArgument(2);
new Thread(() -> {
@@ -165,7 +167,7 @@
mIpServer = new IpServer(
IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd,
- mCallback, usingLegacyDhcp, mDependencies);
+ mCallback, usingLegacyDhcp, usingBpfOffload, mDependencies);
mIpServer.start();
mNeighborEventConsumer = neighborCaptor.getValue();
@@ -179,12 +181,13 @@
private void initTetheredStateMachine(int interfaceType, String upstreamIface)
throws Exception {
- initTetheredStateMachine(interfaceType, upstreamIface, false);
+ initTetheredStateMachine(interfaceType, upstreamIface, false,
+ DEFAULT_USING_BPF_OFFLOAD);
}
private void initTetheredStateMachine(int interfaceType, String upstreamIface,
- boolean usingLegacyDhcp) throws Exception {
- initStateMachine(interfaceType, usingLegacyDhcp);
+ boolean usingLegacyDhcp, boolean usingBpfOffload) throws Exception {
+ initStateMachine(interfaceType, usingLegacyDhcp, usingBpfOffload);
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
if (upstreamIface != null) {
LinkProperties lp = new LinkProperties();
@@ -204,7 +207,8 @@
when(mDependencies.getIpNeighborMonitor(any(), any(), any()))
.thenReturn(mIpNeighborMonitor);
mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog,
- mNetd, mCallback, false /* usingLegacyDhcp */, mDependencies);
+ mNetd, mCallback, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD,
+ mDependencies);
mIpServer.start();
mLooper.dispatchAll();
verify(mCallback).updateInterfaceState(
@@ -494,7 +498,8 @@
@Test
public void doesNotStartDhcpServerIfDisabled() throws Exception {
- initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, true /* usingLegacyDhcp */);
+ initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, true /* usingLegacyDhcp */,
+ DEFAULT_USING_BPF_OFFLOAD);
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
verify(mDependencies, never()).makeDhcpServer(any(), any(), any());
@@ -577,7 +582,8 @@
@Test
public void addRemoveipv6ForwardingRules() throws Exception {
- initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */);
+ initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */,
+ DEFAULT_USING_BPF_OFFLOAD);
final int myIfindex = TEST_IFACE_PARAMS.index;
final int notMyIfindex = myIfindex - 1;
@@ -678,6 +684,53 @@
reset(mNetd);
}
+ @Test
+ public void enableDisableUsingBpfOffload() throws Exception {
+ final int myIfindex = TEST_IFACE_PARAMS.index;
+ final InetAddress neigh = InetAddresses.parseNumericAddress("2001:db8::1");
+ final MacAddress macA = MacAddress.fromString("00:00:00:00:00:0a");
+ final MacAddress macNull = MacAddress.fromString("00:00:00:00:00:00");
+
+ reset(mNetd);
+
+ // Expect that rules can be only added/removed when the BPF offload config is enabled.
+ // Note that the usingBpfOffload false case is not a realistic test case. Because IP
+ // neighbor monitor doesn't start if BPF offload is disabled, there should have no
+ // neighbor event listening. This is used for testing the protection check just in case.
+ // TODO: Perhaps remove this test once we don't need this check anymore.
+ for (boolean usingBpfOffload : new boolean[]{true, false}) {
+ initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */,
+ usingBpfOffload);
+
+ // A neighbor is added.
+ recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA);
+ if (usingBpfOffload) {
+ verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neigh, macA));
+ } else {
+ verify(mNetd, never()).tetherOffloadRuleAdd(any());
+ }
+ reset(mNetd);
+
+ // A neighbor is deleted.
+ recvDelNeigh(myIfindex, neigh, NUD_STALE, macA);
+ if (usingBpfOffload) {
+ verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neigh, macNull));
+ } else {
+ verify(mNetd, never()).tetherOffloadRuleRemove(any());
+ }
+ reset(mNetd);
+ }
+ }
+
+ @Test
+ public void doesNotStartIpNeighborMonitorIfBpfOffloadDisabled() throws Exception {
+ initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */,
+ false /* usingBpfOffload */);
+
+ // IP neighbor monitor doesn't start if BPF offload is disabled.
+ verify(mIpNeighborMonitor, never()).start();
+ }
+
private void assertDhcpStarted(IpPrefix expectedPrefix) throws Exception {
verify(mDependencies, times(1)).makeDhcpServer(eq(IFACE_NAME), any(), any());
verify(mDhcpServer, timeout(MAKE_DHCPSERVER_TIMEOUT_MS).times(1)).startWithCallbacks(
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
new file mode 100644
index 0000000..f8ff1cb
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.tethering;
+
+import static android.net.util.TetheringUtils.uint16;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.tetheroffload.config.V1_0.IOffloadConfig;
+import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
+import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback;
+import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
+import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
+import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
+import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.NativeHandle;
+import android.os.test.TestLooper;
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class OffloadHardwareInterfaceTest {
+ private static final String RMNET0 = "test_rmnet_data0";
+
+ private final TestLooper mTestLooper = new TestLooper();
+
+ private OffloadHardwareInterface mOffloadHw;
+ private ITetheringOffloadCallback mTetheringOffloadCallback;
+ private OffloadHardwareInterface.ControlCallback mControlCallback;
+
+ @Mock private IOffloadConfig mIOffloadConfig;
+ @Mock private IOffloadControl mIOffloadControl;
+ @Mock private NativeHandle mNativeHandle;
+
+ class MyDependencies extends OffloadHardwareInterface.Dependencies {
+ MyDependencies(SharedLog log) {
+ super(log);
+ }
+
+ @Override
+ public IOffloadConfig getOffloadConfig() {
+ return mIOffloadConfig;
+ }
+
+ @Override
+ public IOffloadControl getOffloadControl() {
+ return mIOffloadControl;
+ }
+
+ @Override
+ public NativeHandle createConntrackSocket(final int groups) {
+ return mNativeHandle;
+ }
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ final SharedLog log = new SharedLog("test");
+ mOffloadHw = new OffloadHardwareInterface(new Handler(mTestLooper.getLooper()), log,
+ new MyDependencies(log));
+ mControlCallback = spy(new OffloadHardwareInterface.ControlCallback());
+ }
+
+ private void startOffloadHardwareInterface() throws Exception {
+ mOffloadHw.initOffloadConfig();
+ mOffloadHw.initOffloadControl(mControlCallback);
+ final ArgumentCaptor<ITetheringOffloadCallback> mOffloadCallbackCaptor =
+ ArgumentCaptor.forClass(ITetheringOffloadCallback.class);
+ verify(mIOffloadControl).initOffload(mOffloadCallbackCaptor.capture(), any());
+ mTetheringOffloadCallback = mOffloadCallbackCaptor.getValue();
+ }
+
+ @Test
+ public void testGetForwardedStats() throws Exception {
+ startOffloadHardwareInterface();
+ final OffloadHardwareInterface.ForwardedStats stats = mOffloadHw.getForwardedStats(RMNET0);
+ verify(mIOffloadControl).getForwardedStats(eq(RMNET0), any());
+ assertNotNull(stats);
+ }
+
+ @Test
+ public void testSetLocalPrefixes() throws Exception {
+ startOffloadHardwareInterface();
+ final ArrayList<String> localPrefixes = new ArrayList<>();
+ localPrefixes.add("127.0.0.0/8");
+ localPrefixes.add("fe80::/64");
+ mOffloadHw.setLocalPrefixes(localPrefixes);
+ verify(mIOffloadControl).setLocalPrefixes(eq(localPrefixes), any());
+ }
+
+ @Test
+ public void testSetDataLimit() throws Exception {
+ startOffloadHardwareInterface();
+ final long limit = 12345;
+ mOffloadHw.setDataLimit(RMNET0, limit);
+ verify(mIOffloadControl).setDataLimit(eq(RMNET0), eq(limit), any());
+ }
+
+ @Test
+ public void testSetUpstreamParameters() throws Exception {
+ startOffloadHardwareInterface();
+ final String v4addr = "192.168.10.1";
+ final String v4gateway = "192.168.10.255";
+ final ArrayList<String> v6gws = new ArrayList<>(0);
+ v6gws.add("2001:db8::1");
+ mOffloadHw.setUpstreamParameters(RMNET0, v4addr, v4gateway, v6gws);
+ verify(mIOffloadControl).setUpstreamParameters(eq(RMNET0), eq(v4addr), eq(v4gateway),
+ eq(v6gws), any());
+
+ final ArgumentCaptor<ArrayList<String>> mArrayListCaptor =
+ ArgumentCaptor.forClass(ArrayList.class);
+ mOffloadHw.setUpstreamParameters(null, null, null, null);
+ verify(mIOffloadControl).setUpstreamParameters(eq(""), eq(""), eq(""),
+ mArrayListCaptor.capture(), any());
+ assertEquals(mArrayListCaptor.getValue().size(), 0);
+ }
+
+ @Test
+ public void testUpdateDownstreamPrefix() throws Exception {
+ startOffloadHardwareInterface();
+ final String ifName = "wlan1";
+ final String prefix = "192.168.43.0/24";
+ mOffloadHw.addDownstreamPrefix(ifName, prefix);
+ verify(mIOffloadControl).addDownstream(eq(ifName), eq(prefix), any());
+
+ mOffloadHw.removeDownstreamPrefix(ifName, prefix);
+ verify(mIOffloadControl).removeDownstream(eq(ifName), eq(prefix), any());
+ }
+
+ @Test
+ public void testTetheringOffloadCallback() throws Exception {
+ startOffloadHardwareInterface();
+
+ mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STARTED);
+ mTestLooper.dispatchAll();
+ verify(mControlCallback).onStarted();
+
+ mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR);
+ mTestLooper.dispatchAll();
+ verify(mControlCallback).onStoppedError();
+
+ mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED);
+ mTestLooper.dispatchAll();
+ verify(mControlCallback).onStoppedUnsupported();
+
+ mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE);
+ mTestLooper.dispatchAll();
+ verify(mControlCallback).onSupportAvailable();
+
+ mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED);
+ mTestLooper.dispatchAll();
+ verify(mControlCallback).onStoppedLimitReached();
+
+ final NatTimeoutUpdate tcpParams = buildNatTimeoutUpdate(NetworkProtocol.TCP);
+ mTetheringOffloadCallback.updateTimeout(tcpParams);
+ mTestLooper.dispatchAll();
+ verify(mControlCallback).onNatTimeoutUpdate(eq(OsConstants.IPPROTO_TCP),
+ eq(tcpParams.src.addr),
+ eq(uint16(tcpParams.src.port)),
+ eq(tcpParams.dst.addr),
+ eq(uint16(tcpParams.dst.port)));
+
+ final NatTimeoutUpdate udpParams = buildNatTimeoutUpdate(NetworkProtocol.UDP);
+ mTetheringOffloadCallback.updateTimeout(udpParams);
+ mTestLooper.dispatchAll();
+ verify(mControlCallback).onNatTimeoutUpdate(eq(OsConstants.IPPROTO_UDP),
+ eq(udpParams.src.addr),
+ eq(uint16(udpParams.src.port)),
+ eq(udpParams.dst.addr),
+ eq(uint16(udpParams.dst.port)));
+ }
+
+ private NatTimeoutUpdate buildNatTimeoutUpdate(final int proto) {
+ final NatTimeoutUpdate params = new NatTimeoutUpdate();
+ params.proto = proto;
+ params.src.addr = "192.168.43.200";
+ params.src.port = 100;
+ params.dst.addr = "172.50.46.169";
+ params.dst.port = 150;
+ return params;
+ }
+}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
index e8ba5b8..fbfa871 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
@@ -127,6 +127,8 @@
.thenReturn(new String[0]);
when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
false);
+ initializeBpfOffloadConfiguration(true, null /* unset */);
+
mHasTelephonyManager = true;
mMockContext = new MockContext(mContext);
mEnableLegacyDhcpServer = false;
@@ -278,6 +280,50 @@
assertFalse(upstreamIterator.hasNext());
}
+ private void initializeBpfOffloadConfiguration(
+ final boolean fromRes, final String fromDevConfig) {
+ when(mResources.getBoolean(R.bool.config_tether_enable_bpf_offload)).thenReturn(fromRes);
+ doReturn(fromDevConfig).when(
+ () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
+ eq(TetheringConfiguration.OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD)));
+ }
+
+ @Test
+ public void testBpfOffloadEnabledByResource() {
+ initializeBpfOffloadConfiguration(true, null /* unset */);
+ final TetheringConfiguration enableByRes =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertTrue(enableByRes.enableBpfOffload);
+ }
+
+ @Test
+ public void testBpfOffloadEnabledByDeviceConfigOverride() {
+ for (boolean res : new boolean[]{true, false}) {
+ initializeBpfOffloadConfiguration(res, "true");
+ final TetheringConfiguration enableByDevConOverride =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertTrue(enableByDevConOverride.enableBpfOffload);
+ }
+ }
+
+ @Test
+ public void testBpfOffloadDisabledByResource() {
+ initializeBpfOffloadConfiguration(false, null /* unset */);
+ final TetheringConfiguration disableByRes =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertFalse(disableByRes.enableBpfOffload);
+ }
+
+ @Test
+ public void testBpfOffloadDisabledByDeviceConfigOverride() {
+ for (boolean res : new boolean[]{true, false}) {
+ initializeBpfOffloadConfiguration(res, "false");
+ final TetheringConfiguration disableByDevConOverride =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertFalse(disableByDevConOverride.enableBpfOffload);
+ }
+ }
+
@Test
public void testNewDhcpServerDisabled() {
when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(