Merge "[NFCT.TETHER.2] Migrate tetherOffloadRuleAdd from netd to mainline"
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index ef9777d..7427481 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -62,10 +62,7 @@
"com.android.tethering",
],
min_sdk_version: "30",
- include_dirs: [
- // TODO: use the libbpf_android_headers instead of just including the header files.
- "system/bpf/libbpf_android/include/",
- ],
+ header_libs: ["bpf_syscall_wrappers"],
srcs: [
"jni/*.cpp",
],
diff --git a/Tethering/jni/com_android_networkstack_tethering_BpfMap.cpp b/Tethering/jni/com_android_networkstack_tethering_BpfMap.cpp
index 64f7dcf..eadc210 100644
--- a/Tethering/jni/com_android_networkstack_tethering_BpfMap.cpp
+++ b/Tethering/jni/com_android_networkstack_tethering_BpfMap.cpp
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-#include <linux/bpf.h>
#include <errno.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
@@ -24,7 +23,7 @@
#include "nativehelper/scoped_utf_chars.h"
#define BPF_FD_JUST_USE_INT
-#include "bpf/BpfUtils.h"
+#include "BpfSyscallWrappers.h"
namespace android {
diff --git a/Tethering/res/values/config.xml b/Tethering/res/values/config.xml
index 5f8d299..4391006 100644
--- a/Tethering/res/values/config.xml
+++ b/Tethering/res/values/config.xml
@@ -40,6 +40,8 @@
<string-array translatable="false" name="config_tether_wifi_regexs">
<item>"wlan\\d"</item>
<item>"softap\\d"</item>
+ <item>"ap_br_wlan\\d"</item>
+ <item>"ap_br_softap\\d"</item>
</string-array>
<!-- List of regexpressions describing the interface (if any) that represent tetherable
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 803c9d8..69d90aa 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -126,7 +126,7 @@
@RunWith(AndroidJUnit4::class)
class NetworkAgentTest {
@Rule @JvmField
- val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.Q)
+ val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.R)
private val LOCAL_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1")
private val REMOTE_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.2")
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.java b/tests/cts/net/src/android/net/cts/NsdManagerTest.java
new file mode 100644
index 0000000..2bcfdc3
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.java
@@ -0,0 +1,594 @@
+/*
+ * Copyright (C) 2012 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 android.net.cts;
+
+import android.content.Context;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.util.Arrays;
+import java.util.Random;
+import java.util.List;
+import java.util.ArrayList;
+
+@AppModeFull(reason = "Socket cannot bind in instant app mode")
+public class NsdManagerTest extends AndroidTestCase {
+
+ private static final String TAG = "NsdManagerTest";
+ private static final String SERVICE_TYPE = "_nmt._tcp";
+ private static final int TIMEOUT = 2000;
+
+ private static final boolean DBG = false;
+
+ NsdManager mNsdManager;
+
+ NsdManager.RegistrationListener mRegistrationListener;
+ NsdManager.DiscoveryListener mDiscoveryListener;
+ NsdManager.ResolveListener mResolveListener;
+ private NsdServiceInfo mResolvedService;
+
+ public NsdManagerTest() {
+ initRegistrationListener();
+ initDiscoveryListener();
+ initResolveListener();
+ }
+
+ private void initRegistrationListener() {
+ mRegistrationListener = new NsdManager.RegistrationListener() {
+ @Override
+ public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
+ setEvent("onRegistrationFailed", errorCode);
+ }
+
+ @Override
+ public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
+ setEvent("onUnregistrationFailed", errorCode);
+ }
+
+ @Override
+ public void onServiceRegistered(NsdServiceInfo serviceInfo) {
+ setEvent("onServiceRegistered", serviceInfo);
+ }
+
+ @Override
+ public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
+ setEvent("onServiceUnregistered", serviceInfo);
+ }
+ };
+ }
+
+ private void initDiscoveryListener() {
+ mDiscoveryListener = new NsdManager.DiscoveryListener() {
+ @Override
+ public void onStartDiscoveryFailed(String serviceType, int errorCode) {
+ setEvent("onStartDiscoveryFailed", errorCode);
+ }
+
+ @Override
+ public void onStopDiscoveryFailed(String serviceType, int errorCode) {
+ setEvent("onStopDiscoveryFailed", errorCode);
+ }
+
+ @Override
+ public void onDiscoveryStarted(String serviceType) {
+ NsdServiceInfo info = new NsdServiceInfo();
+ info.setServiceType(serviceType);
+ setEvent("onDiscoveryStarted", info);
+ }
+
+ @Override
+ public void onDiscoveryStopped(String serviceType) {
+ NsdServiceInfo info = new NsdServiceInfo();
+ info.setServiceType(serviceType);
+ setEvent("onDiscoveryStopped", info);
+ }
+
+ @Override
+ public void onServiceFound(NsdServiceInfo serviceInfo) {
+ setEvent("onServiceFound", serviceInfo);
+ }
+
+ @Override
+ public void onServiceLost(NsdServiceInfo serviceInfo) {
+ setEvent("onServiceLost", serviceInfo);
+ }
+ };
+ }
+
+ private void initResolveListener() {
+ mResolveListener = new NsdManager.ResolveListener() {
+ @Override
+ public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
+ setEvent("onResolveFailed", errorCode);
+ }
+
+ @Override
+ public void onServiceResolved(NsdServiceInfo serviceInfo) {
+ mResolvedService = serviceInfo;
+ setEvent("onServiceResolved", serviceInfo);
+ }
+ };
+ }
+
+
+
+ private final class EventData {
+ EventData(String callbackName, NsdServiceInfo info) {
+ mCallbackName = callbackName;
+ mSucceeded = true;
+ mErrorCode = 0;
+ mInfo = info;
+ }
+ EventData(String callbackName, int errorCode) {
+ mCallbackName = callbackName;
+ mSucceeded = false;
+ mErrorCode = errorCode;
+ mInfo = null;
+ }
+ private final String mCallbackName;
+ private final boolean mSucceeded;
+ private final int mErrorCode;
+ private final NsdServiceInfo mInfo;
+ }
+
+ private final List<EventData> mEventCache = new ArrayList<EventData>();
+
+ private void setEvent(String callbackName, int errorCode) {
+ if (DBG) Log.d(TAG, callbackName + " failed with " + String.valueOf(errorCode));
+ EventData eventData = new EventData(callbackName, errorCode);
+ synchronized (mEventCache) {
+ mEventCache.add(eventData);
+ mEventCache.notify();
+ }
+ }
+
+ private void setEvent(String callbackName, NsdServiceInfo info) {
+ if (DBG) Log.d(TAG, "Received event " + callbackName + " for " + info.getServiceName());
+ EventData eventData = new EventData(callbackName, info);
+ synchronized (mEventCache) {
+ mEventCache.add(eventData);
+ mEventCache.notify();
+ }
+ }
+
+ void clearEventCache() {
+ synchronized(mEventCache) {
+ mEventCache.clear();
+ }
+ }
+
+ int eventCacheSize() {
+ synchronized(mEventCache) {
+ return mEventCache.size();
+ }
+ }
+
+ private int mWaitId = 0;
+ private EventData waitForCallback(String callbackName) {
+
+ synchronized(mEventCache) {
+
+ mWaitId ++;
+ if (DBG) Log.d(TAG, "Waiting for " + callbackName + ", id=" + String.valueOf(mWaitId));
+
+ try {
+ long startTime = android.os.SystemClock.uptimeMillis();
+ long elapsedTime = 0;
+ int index = 0;
+ while (elapsedTime < TIMEOUT ) {
+ // first check if we've received that event
+ for (; index < mEventCache.size(); index++) {
+ EventData e = mEventCache.get(index);
+ if (e.mCallbackName.equals(callbackName)) {
+ if (DBG) Log.d(TAG, "exiting wait id=" + String.valueOf(mWaitId));
+ return e;
+ }
+ }
+
+ // Not yet received, just wait
+ mEventCache.wait(TIMEOUT - elapsedTime);
+ elapsedTime = android.os.SystemClock.uptimeMillis() - startTime;
+ }
+ // we exited the loop because of TIMEOUT; fail the call
+ if (DBG) Log.d(TAG, "timed out waiting id=" + String.valueOf(mWaitId));
+ return null;
+ } catch (InterruptedException e) {
+ return null; // wait timed out!
+ }
+ }
+ }
+
+ private EventData waitForNewEvents() throws InterruptedException {
+ if (DBG) Log.d(TAG, "Waiting for a bit, id=" + String.valueOf(mWaitId));
+
+ long startTime = android.os.SystemClock.uptimeMillis();
+ long elapsedTime = 0;
+ synchronized (mEventCache) {
+ int index = mEventCache.size();
+ while (elapsedTime < TIMEOUT ) {
+ // first check if we've received that event
+ for (; index < mEventCache.size(); index++) {
+ EventData e = mEventCache.get(index);
+ return e;
+ }
+
+ // Not yet received, just wait
+ mEventCache.wait(TIMEOUT - elapsedTime);
+ elapsedTime = android.os.SystemClock.uptimeMillis() - startTime;
+ }
+ }
+
+ return null;
+ }
+
+ private String mServiceName;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ if (DBG) Log.d(TAG, "Setup test ...");
+ mNsdManager = (NsdManager) getContext().getSystemService(Context.NSD_SERVICE);
+
+ Random rand = new Random();
+ mServiceName = new String("NsdTest");
+ for (int i = 0; i < 4; i++) {
+ mServiceName = mServiceName + String.valueOf(rand.nextInt(10));
+ }
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ if (DBG) Log.d(TAG, "Tear down test ...");
+ super.tearDown();
+ }
+
+ public void testNDSManager() throws Exception {
+ EventData lastEvent = null;
+
+ if (DBG) Log.d(TAG, "Starting test ...");
+
+ NsdServiceInfo si = new NsdServiceInfo();
+ si.setServiceType(SERVICE_TYPE);
+ si.setServiceName(mServiceName);
+
+ byte testByteArray[] = new byte[] {-128, 127, 2, 1, 0, 1, 2};
+ String String256 = "1_________2_________3_________4_________5_________6_________" +
+ "7_________8_________9_________10________11________12________13________" +
+ "14________15________16________17________18________19________20________" +
+ "21________22________23________24________25________123456";
+
+ // Illegal attributes
+ try {
+ si.setAttribute(null, (String) null);
+ fail("Could set null key");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ si.setAttribute("", (String) null);
+ fail("Could set empty key");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ si.setAttribute(String256, (String) null);
+ fail("Could set key with 255 characters");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ si.setAttribute("key", String256.substring(3));
+ fail("Could set key+value combination with more than 255 characters");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ si.setAttribute("key", String256.substring(4));
+ fail("Could set key+value combination with 255 characters");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ si.setAttribute(new String(new byte[]{0x19}), (String) null);
+ fail("Could set key with invalid character");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ si.setAttribute("=", (String) null);
+ fail("Could set key with invalid character");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ si.setAttribute(new String(new byte[]{0x7F}), (String) null);
+ fail("Could set key with invalid character");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ // Allowed attributes
+ si.setAttribute("booleanAttr", (String) null);
+ si.setAttribute("keyValueAttr", "value");
+ si.setAttribute("keyEqualsAttr", "=");
+ si.setAttribute(" whiteSpaceKeyValueAttr ", " value ");
+ si.setAttribute("binaryDataAttr", testByteArray);
+ si.setAttribute("nullBinaryDataAttr", (byte[]) null);
+ si.setAttribute("emptyBinaryDataAttr", new byte[]{});
+ si.setAttribute("longkey", String256.substring(9));
+
+ ServerSocket socket;
+ int localPort;
+
+ try {
+ socket = new ServerSocket(0);
+ localPort = socket.getLocalPort();
+ si.setPort(localPort);
+ } catch (IOException e) {
+ if (DBG) Log.d(TAG, "Could not open a local socket");
+ assertTrue(false);
+ return;
+ }
+
+ if (DBG) Log.d(TAG, "Port = " + String.valueOf(localPort));
+
+ clearEventCache();
+
+ mNsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
+ lastEvent = waitForCallback("onServiceRegistered"); // id = 1
+ assertTrue(lastEvent != null);
+ assertTrue(lastEvent.mSucceeded);
+ assertTrue(eventCacheSize() == 1);
+
+ // We may not always get the name that we tried to register;
+ // This events tells us the name that was registered.
+ String registeredName = lastEvent.mInfo.getServiceName();
+ si.setServiceName(registeredName);
+
+ clearEventCache();
+
+ mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
+ mDiscoveryListener);
+
+ // Expect discovery started
+ lastEvent = waitForCallback("onDiscoveryStarted"); // id = 2
+
+ assertTrue(lastEvent != null);
+ assertTrue(lastEvent.mSucceeded);
+
+ // Remove this event, so accounting becomes easier later
+ synchronized (mEventCache) {
+ mEventCache.remove(lastEvent);
+ }
+
+ // Expect a service record to be discovered (and filter the ones
+ // that are unrelated to this test)
+ boolean found = false;
+ for (int i = 0; i < 32; i++) {
+
+ lastEvent = waitForCallback("onServiceFound"); // id = 3
+ if (lastEvent == null) {
+ // no more onServiceFound events are being reported!
+ break;
+ }
+
+ assertTrue(lastEvent.mSucceeded);
+
+ if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
+ lastEvent.mInfo.getServiceName());
+
+ if (lastEvent.mInfo.getServiceName().equals(registeredName)) {
+ // Save it, as it will get overwritten with new serviceFound events
+ si = lastEvent.mInfo;
+ found = true;
+ }
+
+ // Remove this event from the event cache, so it won't be found by subsequent
+ // calls to waitForCallback
+ synchronized (mEventCache) {
+ mEventCache.remove(lastEvent);
+ }
+ }
+
+ assertTrue(found);
+
+ // We've removed all serviceFound events, and we've removed the discoveryStarted
+ // event as well, so now the event cache should be empty!
+ assertTrue(eventCacheSize() == 0);
+
+ // Resolve the service
+ clearEventCache();
+ mNsdManager.resolveService(si, mResolveListener);
+ lastEvent = waitForCallback("onServiceResolved"); // id = 4
+
+ assertNotNull(mResolvedService);
+
+ // Check Txt attributes
+ assertEquals(8, mResolvedService.getAttributes().size());
+ assertTrue(mResolvedService.getAttributes().containsKey("booleanAttr"));
+ assertNull(mResolvedService.getAttributes().get("booleanAttr"));
+ assertEquals("value", new String(mResolvedService.getAttributes().get("keyValueAttr")));
+ assertEquals("=", new String(mResolvedService.getAttributes().get("keyEqualsAttr")));
+ assertEquals(" value ", new String(mResolvedService.getAttributes()
+ .get(" whiteSpaceKeyValueAttr ")));
+ assertEquals(String256.substring(9), new String(mResolvedService.getAttributes()
+ .get("longkey")));
+ assertTrue(Arrays.equals(testByteArray,
+ mResolvedService.getAttributes().get("binaryDataAttr")));
+ assertTrue(mResolvedService.getAttributes().containsKey("nullBinaryDataAttr"));
+ assertNull(mResolvedService.getAttributes().get("nullBinaryDataAttr"));
+ assertTrue(mResolvedService.getAttributes().containsKey("emptyBinaryDataAttr"));
+ assertNull(mResolvedService.getAttributes().get("emptyBinaryDataAttr"));
+
+ assertTrue(lastEvent != null);
+ assertTrue(lastEvent.mSucceeded);
+
+ if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": Port = " +
+ String.valueOf(lastEvent.mInfo.getPort()));
+
+ assertTrue(lastEvent.mInfo.getPort() == localPort);
+ assertTrue(eventCacheSize() == 1);
+
+ checkForAdditionalEvents();
+ clearEventCache();
+
+ // Unregister the service
+ mNsdManager.unregisterService(mRegistrationListener);
+ lastEvent = waitForCallback("onServiceUnregistered"); // id = 5
+
+ assertTrue(lastEvent != null);
+ assertTrue(lastEvent.mSucceeded);
+
+ // Expect a callback for service lost
+ lastEvent = waitForCallback("onServiceLost"); // id = 6
+
+ assertTrue(lastEvent != null);
+ assertTrue(lastEvent.mInfo.getServiceName().equals(registeredName));
+
+ // Register service again to see if we discover it
+ checkForAdditionalEvents();
+ clearEventCache();
+
+ si = new NsdServiceInfo();
+ si.setServiceType(SERVICE_TYPE);
+ si.setServiceName(mServiceName);
+ si.setPort(localPort);
+
+ // Create a new registration listener and register same service again
+ initRegistrationListener();
+
+ mNsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
+
+ lastEvent = waitForCallback("onServiceRegistered"); // id = 7
+
+ assertTrue(lastEvent != null);
+ assertTrue(lastEvent.mSucceeded);
+
+ registeredName = lastEvent.mInfo.getServiceName();
+
+ // Expect a record to be discovered
+ // Expect a service record to be discovered (and filter the ones
+ // that are unrelated to this test)
+ found = false;
+ for (int i = 0; i < 32; i++) {
+
+ lastEvent = waitForCallback("onServiceFound"); // id = 8
+ if (lastEvent == null) {
+ // no more onServiceFound events are being reported!
+ break;
+ }
+
+ assertTrue(lastEvent.mSucceeded);
+
+ if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
+ lastEvent.mInfo.getServiceName());
+
+ if (lastEvent.mInfo.getServiceName().equals(registeredName)) {
+ // Save it, as it will get overwritten with new serviceFound events
+ si = lastEvent.mInfo;
+ found = true;
+ }
+
+ // Remove this event from the event cache, so it won't be found by subsequent
+ // calls to waitForCallback
+ synchronized (mEventCache) {
+ mEventCache.remove(lastEvent);
+ }
+ }
+
+ assertTrue(found);
+
+ // Resolve the service
+ clearEventCache();
+ mNsdManager.resolveService(si, mResolveListener);
+ lastEvent = waitForCallback("onServiceResolved"); // id = 9
+
+ assertTrue(lastEvent != null);
+ assertTrue(lastEvent.mSucceeded);
+
+ if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
+ lastEvent.mInfo.getServiceName());
+
+ assertTrue(lastEvent.mInfo.getServiceName().equals(registeredName));
+
+ assertNotNull(mResolvedService);
+
+ // Check that we don't have any TXT records
+ assertEquals(0, mResolvedService.getAttributes().size());
+
+ checkForAdditionalEvents();
+ clearEventCache();
+
+ mNsdManager.stopServiceDiscovery(mDiscoveryListener);
+ lastEvent = waitForCallback("onDiscoveryStopped"); // id = 10
+ assertTrue(lastEvent != null);
+ assertTrue(lastEvent.mSucceeded);
+ assertTrue(checkCacheSize(1));
+
+ checkForAdditionalEvents();
+ clearEventCache();
+
+ mNsdManager.unregisterService(mRegistrationListener);
+
+ lastEvent = waitForCallback("onServiceUnregistered"); // id = 11
+ assertTrue(lastEvent != null);
+ assertTrue(lastEvent.mSucceeded);
+ assertTrue(checkCacheSize(1));
+ }
+
+ boolean checkCacheSize(int size) {
+ synchronized (mEventCache) {
+ int cacheSize = mEventCache.size();
+ if (cacheSize != size) {
+ Log.d(TAG, "id = " + mWaitId + ": event cache size = " + cacheSize);
+ for (int i = 0; i < cacheSize; i++) {
+ EventData e = mEventCache.get(i);
+ String sname = (e.mInfo != null) ? "(" + e.mInfo.getServiceName() + ")" : "";
+ Log.d(TAG, "eventName is " + e.mCallbackName + sname);
+ }
+ }
+ return (cacheSize == size);
+ }
+ }
+
+ boolean checkForAdditionalEvents() {
+ try {
+ EventData e = waitForNewEvents();
+ if (e != null) {
+ String sname = (e.mInfo != null) ? "(" + e.mInfo.getServiceName() + ")" : "";
+ Log.d(TAG, "ignoring unexpected event " + e.mCallbackName + sname);
+ }
+ return (e == null);
+ }
+ catch (InterruptedException ex) {
+ return false;
+ }
+ }
+}
+