Merge "[MS65.2] Add unit test of NetworkIdentity#Builder"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 6996ad9..9f2ea35 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -32,6 +32,9 @@
// TODO: move to presubmit when known green.
{
"name": "bpf_existence_test"
+ },
+ {
+ "name": "libclat_test"
}
],
"mainline-presubmit": [
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index c72d3a6..72c83fa 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -63,6 +63,8 @@
],
canned_fs_config: "canned_fs_config",
bpfs: [
+ "clatd.o_mainline",
+ "netd.o_mainline",
"offload.o",
"test.o",
],
@@ -117,8 +119,13 @@
// Additional hidden API flag files to override the defaults. This must only be
// modified by the Soong or platform compat team.
hidden_api: {
- max_target_r_low_priority: ["hiddenapi/hiddenapi-max-target-r-loprio.txt"],
- max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+ max_target_r_low_priority: [
+ "hiddenapi/hiddenapi-max-target-r-loprio.txt",
+ ],
+ max_target_o_low_priority: [
+ "hiddenapi/hiddenapi-max-target-o-low-priority.txt",
+ "hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt",
+ ],
unsupported: ["hiddenapi/hiddenapi-unsupported.txt"],
},
}
diff --git a/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt b/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt
new file mode 100644
index 0000000..88c77f2
--- /dev/null
+++ b/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority-tiramisu.txt
@@ -0,0 +1,87 @@
+Landroid/net/nsd/DnsSdTxtRecord;-><init>()V
+Landroid/net/nsd/DnsSdTxtRecord;-><init>(Landroid/net/nsd/DnsSdTxtRecord;)V
+Landroid/net/nsd/DnsSdTxtRecord;-><init>([B)V
+Landroid/net/nsd/DnsSdTxtRecord;->contains(Ljava/lang/String;)Z
+Landroid/net/nsd/DnsSdTxtRecord;->CREATOR:Landroid/os/Parcelable$Creator;
+Landroid/net/nsd/DnsSdTxtRecord;->get(Ljava/lang/String;)Ljava/lang/String;
+Landroid/net/nsd/DnsSdTxtRecord;->getKey(I)Ljava/lang/String;
+Landroid/net/nsd/DnsSdTxtRecord;->getRawData()[B
+Landroid/net/nsd/DnsSdTxtRecord;->getValue(I)[B
+Landroid/net/nsd/DnsSdTxtRecord;->getValue(Ljava/lang/String;)[B
+Landroid/net/nsd/DnsSdTxtRecord;->getValueAsString(I)Ljava/lang/String;
+Landroid/net/nsd/DnsSdTxtRecord;->insert([B[BI)V
+Landroid/net/nsd/DnsSdTxtRecord;->keyCount()I
+Landroid/net/nsd/DnsSdTxtRecord;->mData:[B
+Landroid/net/nsd/DnsSdTxtRecord;->mSeperator:B
+Landroid/net/nsd/DnsSdTxtRecord;->remove(Ljava/lang/String;)I
+Landroid/net/nsd/DnsSdTxtRecord;->set(Ljava/lang/String;Ljava/lang/String;)V
+Landroid/net/nsd/DnsSdTxtRecord;->size()I
+Landroid/net/nsd/INsdManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
+Landroid/net/nsd/INsdManager$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
+Landroid/net/nsd/INsdManager$Stub$Proxy;->getMessenger()Landroid/os/Messenger;
+Landroid/net/nsd/INsdManager$Stub$Proxy;->mRemote:Landroid/os/IBinder;
+Landroid/net/nsd/INsdManager$Stub$Proxy;->setEnabled(Z)V
+Landroid/net/nsd/INsdManager$Stub;-><init>()V
+Landroid/net/nsd/INsdManager$Stub;->DESCRIPTOR:Ljava/lang/String;
+Landroid/net/nsd/INsdManager$Stub;->TRANSACTION_getMessenger:I
+Landroid/net/nsd/INsdManager$Stub;->TRANSACTION_setEnabled:I
+Landroid/net/nsd/INsdManager;->setEnabled(Z)V
+Landroid/net/nsd/NsdManager;-><init>(Landroid/content/Context;Landroid/net/nsd/INsdManager;)V
+Landroid/net/nsd/NsdManager;->BASE:I
+Landroid/net/nsd/NsdManager;->checkListener(Ljava/lang/Object;)V
+Landroid/net/nsd/NsdManager;->checkProtocol(I)V
+Landroid/net/nsd/NsdManager;->checkServiceInfo(Landroid/net/nsd/NsdServiceInfo;)V
+Landroid/net/nsd/NsdManager;->DBG:Z
+Landroid/net/nsd/NsdManager;->DISABLE:I
+Landroid/net/nsd/NsdManager;->disconnect()V
+Landroid/net/nsd/NsdManager;->DISCOVER_SERVICES:I
+Landroid/net/nsd/NsdManager;->DISCOVER_SERVICES_FAILED:I
+Landroid/net/nsd/NsdManager;->DISCOVER_SERVICES_STARTED:I
+Landroid/net/nsd/NsdManager;->ENABLE:I
+Landroid/net/nsd/NsdManager;->EVENT_NAMES:Landroid/util/SparseArray;
+Landroid/net/nsd/NsdManager;->fatal(Ljava/lang/String;)V
+Landroid/net/nsd/NsdManager;->FIRST_LISTENER_KEY:I
+Landroid/net/nsd/NsdManager;->getListenerKey(Ljava/lang/Object;)I
+Landroid/net/nsd/NsdManager;->getMessenger()Landroid/os/Messenger;
+Landroid/net/nsd/NsdManager;->getNsdServiceInfoType(Landroid/net/nsd/NsdServiceInfo;)Ljava/lang/String;
+Landroid/net/nsd/NsdManager;->init()V
+Landroid/net/nsd/NsdManager;->mAsyncChannel:Lcom/android/internal/util/AsyncChannel;
+Landroid/net/nsd/NsdManager;->mConnected:Ljava/util/concurrent/CountDownLatch;
+Landroid/net/nsd/NsdManager;->mContext:Landroid/content/Context;
+Landroid/net/nsd/NsdManager;->mHandler:Landroid/net/nsd/NsdManager$ServiceHandler;
+Landroid/net/nsd/NsdManager;->mListenerKey:I
+Landroid/net/nsd/NsdManager;->mListenerMap:Landroid/util/SparseArray;
+Landroid/net/nsd/NsdManager;->mMapLock:Ljava/lang/Object;
+Landroid/net/nsd/NsdManager;->mService:Landroid/net/nsd/INsdManager;
+Landroid/net/nsd/NsdManager;->mServiceMap:Landroid/util/SparseArray;
+Landroid/net/nsd/NsdManager;->nameOf(I)Ljava/lang/String;
+Landroid/net/nsd/NsdManager;->NATIVE_DAEMON_EVENT:I
+Landroid/net/nsd/NsdManager;->nextListenerKey()I
+Landroid/net/nsd/NsdManager;->putListener(Ljava/lang/Object;Landroid/net/nsd/NsdServiceInfo;)I
+Landroid/net/nsd/NsdManager;->REGISTER_SERVICE:I
+Landroid/net/nsd/NsdManager;->REGISTER_SERVICE_FAILED:I
+Landroid/net/nsd/NsdManager;->REGISTER_SERVICE_SUCCEEDED:I
+Landroid/net/nsd/NsdManager;->removeListener(I)V
+Landroid/net/nsd/NsdManager;->RESOLVE_SERVICE:I
+Landroid/net/nsd/NsdManager;->RESOLVE_SERVICE_FAILED:I
+Landroid/net/nsd/NsdManager;->RESOLVE_SERVICE_SUCCEEDED:I
+Landroid/net/nsd/NsdManager;->SERVICE_FOUND:I
+Landroid/net/nsd/NsdManager;->SERVICE_LOST:I
+Landroid/net/nsd/NsdManager;->setEnabled(Z)V
+Landroid/net/nsd/NsdManager;->STOP_DISCOVERY:I
+Landroid/net/nsd/NsdManager;->STOP_DISCOVERY_FAILED:I
+Landroid/net/nsd/NsdManager;->STOP_DISCOVERY_SUCCEEDED:I
+Landroid/net/nsd/NsdManager;->TAG:Ljava/lang/String;
+Landroid/net/nsd/NsdManager;->UNREGISTER_SERVICE:I
+Landroid/net/nsd/NsdManager;->UNREGISTER_SERVICE_FAILED:I
+Landroid/net/nsd/NsdManager;->UNREGISTER_SERVICE_SUCCEEDED:I
+Landroid/net/nsd/NsdServiceInfo;-><init>(Ljava/lang/String;Ljava/lang/String;)V
+Landroid/net/nsd/NsdServiceInfo;->getTxtRecord()[B
+Landroid/net/nsd/NsdServiceInfo;->getTxtRecordSize()I
+Landroid/net/nsd/NsdServiceInfo;->mHost:Ljava/net/InetAddress;
+Landroid/net/nsd/NsdServiceInfo;->mPort:I
+Landroid/net/nsd/NsdServiceInfo;->mServiceName:Ljava/lang/String;
+Landroid/net/nsd/NsdServiceInfo;->mServiceType:Ljava/lang/String;
+Landroid/net/nsd/NsdServiceInfo;->mTxtRecord:Landroid/util/ArrayMap;
+Landroid/net/nsd/NsdServiceInfo;->setTxtRecords(Ljava/lang/String;)V
+Landroid/net/nsd/NsdServiceInfo;->TAG:Ljava/lang/String;
diff --git a/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority.txt b/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority.txt
index ea0f61a..1f49d1b 100644
--- a/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority.txt
+++ b/Tethering/apex/hiddenapi/hiddenapi-max-target-o-low-priority.txt
@@ -1163,93 +1163,6 @@
Landroid/net/NetworkWatchlistManager;->reportWatchlistIfNecessary()V
Landroid/net/NetworkWatchlistManager;->SHARED_MEMORY_TAG:Ljava/lang/String;
Landroid/net/NetworkWatchlistManager;->TAG:Ljava/lang/String;
-Landroid/net/nsd/DnsSdTxtRecord;-><init>()V
-Landroid/net/nsd/DnsSdTxtRecord;-><init>(Landroid/net/nsd/DnsSdTxtRecord;)V
-Landroid/net/nsd/DnsSdTxtRecord;-><init>([B)V
-Landroid/net/nsd/DnsSdTxtRecord;->contains(Ljava/lang/String;)Z
-Landroid/net/nsd/DnsSdTxtRecord;->CREATOR:Landroid/os/Parcelable$Creator;
-Landroid/net/nsd/DnsSdTxtRecord;->get(Ljava/lang/String;)Ljava/lang/String;
-Landroid/net/nsd/DnsSdTxtRecord;->getKey(I)Ljava/lang/String;
-Landroid/net/nsd/DnsSdTxtRecord;->getRawData()[B
-Landroid/net/nsd/DnsSdTxtRecord;->getValue(I)[B
-Landroid/net/nsd/DnsSdTxtRecord;->getValue(Ljava/lang/String;)[B
-Landroid/net/nsd/DnsSdTxtRecord;->getValueAsString(I)Ljava/lang/String;
-Landroid/net/nsd/DnsSdTxtRecord;->insert([B[BI)V
-Landroid/net/nsd/DnsSdTxtRecord;->keyCount()I
-Landroid/net/nsd/DnsSdTxtRecord;->mData:[B
-Landroid/net/nsd/DnsSdTxtRecord;->mSeperator:B
-Landroid/net/nsd/DnsSdTxtRecord;->remove(Ljava/lang/String;)I
-Landroid/net/nsd/DnsSdTxtRecord;->set(Ljava/lang/String;Ljava/lang/String;)V
-Landroid/net/nsd/DnsSdTxtRecord;->size()I
-Landroid/net/nsd/INsdManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
-Landroid/net/nsd/INsdManager$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
-Landroid/net/nsd/INsdManager$Stub$Proxy;->getMessenger()Landroid/os/Messenger;
-Landroid/net/nsd/INsdManager$Stub$Proxy;->mRemote:Landroid/os/IBinder;
-Landroid/net/nsd/INsdManager$Stub$Proxy;->setEnabled(Z)V
-Landroid/net/nsd/INsdManager$Stub;-><init>()V
-Landroid/net/nsd/INsdManager$Stub;->DESCRIPTOR:Ljava/lang/String;
-Landroid/net/nsd/INsdManager$Stub;->TRANSACTION_getMessenger:I
-Landroid/net/nsd/INsdManager$Stub;->TRANSACTION_setEnabled:I
-Landroid/net/nsd/INsdManager;->setEnabled(Z)V
-Landroid/net/nsd/NsdManager;-><init>(Landroid/content/Context;Landroid/net/nsd/INsdManager;)V
-Landroid/net/nsd/NsdManager;->BASE:I
-Landroid/net/nsd/NsdManager;->checkListener(Ljava/lang/Object;)V
-Landroid/net/nsd/NsdManager;->checkProtocol(I)V
-Landroid/net/nsd/NsdManager;->checkServiceInfo(Landroid/net/nsd/NsdServiceInfo;)V
-Landroid/net/nsd/NsdManager;->DBG:Z
-Landroid/net/nsd/NsdManager;->DISABLE:I
-Landroid/net/nsd/NsdManager;->disconnect()V
-Landroid/net/nsd/NsdManager;->DISCOVER_SERVICES:I
-Landroid/net/nsd/NsdManager;->DISCOVER_SERVICES_FAILED:I
-Landroid/net/nsd/NsdManager;->DISCOVER_SERVICES_STARTED:I
-Landroid/net/nsd/NsdManager;->ENABLE:I
-Landroid/net/nsd/NsdManager;->EVENT_NAMES:Landroid/util/SparseArray;
-Landroid/net/nsd/NsdManager;->fatal(Ljava/lang/String;)V
-Landroid/net/nsd/NsdManager;->FIRST_LISTENER_KEY:I
-Landroid/net/nsd/NsdManager;->getListenerKey(Ljava/lang/Object;)I
-Landroid/net/nsd/NsdManager;->getMessenger()Landroid/os/Messenger;
-Landroid/net/nsd/NsdManager;->getNsdServiceInfoType(Landroid/net/nsd/NsdServiceInfo;)Ljava/lang/String;
-Landroid/net/nsd/NsdManager;->init()V
-Landroid/net/nsd/NsdManager;->mAsyncChannel:Lcom/android/internal/util/AsyncChannel;
-Landroid/net/nsd/NsdManager;->mConnected:Ljava/util/concurrent/CountDownLatch;
-Landroid/net/nsd/NsdManager;->mContext:Landroid/content/Context;
-Landroid/net/nsd/NsdManager;->mHandler:Landroid/net/nsd/NsdManager$ServiceHandler;
-Landroid/net/nsd/NsdManager;->mListenerKey:I
-Landroid/net/nsd/NsdManager;->mListenerMap:Landroid/util/SparseArray;
-Landroid/net/nsd/NsdManager;->mMapLock:Ljava/lang/Object;
-Landroid/net/nsd/NsdManager;->mService:Landroid/net/nsd/INsdManager;
-Landroid/net/nsd/NsdManager;->mServiceMap:Landroid/util/SparseArray;
-Landroid/net/nsd/NsdManager;->nameOf(I)Ljava/lang/String;
-Landroid/net/nsd/NsdManager;->NATIVE_DAEMON_EVENT:I
-Landroid/net/nsd/NsdManager;->nextListenerKey()I
-Landroid/net/nsd/NsdManager;->putListener(Ljava/lang/Object;Landroid/net/nsd/NsdServiceInfo;)I
-Landroid/net/nsd/NsdManager;->REGISTER_SERVICE:I
-Landroid/net/nsd/NsdManager;->REGISTER_SERVICE_FAILED:I
-Landroid/net/nsd/NsdManager;->REGISTER_SERVICE_SUCCEEDED:I
-Landroid/net/nsd/NsdManager;->removeListener(I)V
-Landroid/net/nsd/NsdManager;->RESOLVE_SERVICE:I
-Landroid/net/nsd/NsdManager;->RESOLVE_SERVICE_FAILED:I
-Landroid/net/nsd/NsdManager;->RESOLVE_SERVICE_SUCCEEDED:I
-Landroid/net/nsd/NsdManager;->SERVICE_FOUND:I
-Landroid/net/nsd/NsdManager;->SERVICE_LOST:I
-Landroid/net/nsd/NsdManager;->setEnabled(Z)V
-Landroid/net/nsd/NsdManager;->STOP_DISCOVERY:I
-Landroid/net/nsd/NsdManager;->STOP_DISCOVERY_FAILED:I
-Landroid/net/nsd/NsdManager;->STOP_DISCOVERY_SUCCEEDED:I
-Landroid/net/nsd/NsdManager;->TAG:Ljava/lang/String;
-Landroid/net/nsd/NsdManager;->UNREGISTER_SERVICE:I
-Landroid/net/nsd/NsdManager;->UNREGISTER_SERVICE_FAILED:I
-Landroid/net/nsd/NsdManager;->UNREGISTER_SERVICE_SUCCEEDED:I
-Landroid/net/nsd/NsdServiceInfo;-><init>(Ljava/lang/String;Ljava/lang/String;)V
-Landroid/net/nsd/NsdServiceInfo;->getTxtRecord()[B
-Landroid/net/nsd/NsdServiceInfo;->getTxtRecordSize()I
-Landroid/net/nsd/NsdServiceInfo;->mHost:Ljava/net/InetAddress;
-Landroid/net/nsd/NsdServiceInfo;->mPort:I
-Landroid/net/nsd/NsdServiceInfo;->mServiceName:Ljava/lang/String;
-Landroid/net/nsd/NsdServiceInfo;->mServiceType:Ljava/lang/String;
-Landroid/net/nsd/NsdServiceInfo;->mTxtRecord:Landroid/util/ArrayMap;
-Landroid/net/nsd/NsdServiceInfo;->setTxtRecords(Ljava/lang/String;)V
-Landroid/net/nsd/NsdServiceInfo;->TAG:Ljava/lang/String;
Landroid/net/ProxyInfo;-><init>(Landroid/net/ProxyInfo;)V
Landroid/net/ProxyInfo;-><init>(Landroid/net/Uri;)V
Landroid/net/ProxyInfo;-><init>(Landroid/net/Uri;I)V
diff --git a/Tethering/proguard.flags b/Tethering/proguard.flags
index f62df7f..6735317 100644
--- a/Tethering/proguard.flags
+++ b/Tethering/proguard.flags
@@ -8,6 +8,10 @@
native <methods>;
}
+-keep class com.android.networkstack.tethering.util.TcUtils {
+ native <methods>;
+}
+
-keepclassmembers public class * extends com.android.networkstack.tethering.util.Struct {
*;
}
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index b4228da..2bb19db 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -614,10 +614,8 @@
return false;
}
- if (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) {
- // BT configures the interface elsewhere: only start DHCP.
- // TODO: make all tethering types behave the same way, and delete the bluetooth
- // code that calls into NetworkManagementService directly.
+ if (shouldNotConfigureBluetoothInterface()) {
+ // Interface was already configured elsewhere, only start DHCP.
return configureDhcp(enabled, mIpv4Address, null /* clientAddress */);
}
@@ -651,12 +649,15 @@
return configureDhcp(enabled, mIpv4Address, mStaticIpv4ClientAddr);
}
+ private boolean shouldNotConfigureBluetoothInterface() {
+ // Before T, bluetooth tethering configures the interface elsewhere.
+ return (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) && !SdkLevel.isAtLeastT();
+ }
+
private LinkAddress requestIpv4Address(final boolean useLastAddress) {
if (mStaticIpv4ServerAddr != null) return mStaticIpv4ServerAddr;
- if (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) {
- return new LinkAddress(BLUETOOTH_IFACE_ADDR);
- }
+ if (shouldNotConfigureBluetoothInterface()) return new LinkAddress(BLUETOOTH_IFACE_ADDR);
return mPrivateAddressCoordinator.requestDownstreamAddress(this, useLastAddress);
}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 55c24d3..db9a64f 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -134,7 +134,12 @@
import com.android.internal.util.MessageUtils;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
+import com.android.networkstack.apishim.common.BluetoothPanShim;
+import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim;
+import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.networkstack.tethering.util.PrefixUtils;
import com.android.networkstack.tethering.util.TetheringUtils;
@@ -265,8 +270,11 @@
private int mOffloadStatus = TETHER_HARDWARE_OFFLOAD_STOPPED;
private EthernetManager.TetheredInterfaceRequest mEthernetIfaceRequest;
+ private TetheredInterfaceRequestShim mBluetoothIfaceRequest;
private String mConfiguredEthernetIface;
+ private String mConfiguredBluetoothIface;
private EthernetCallback mEthernetCallback;
+ private TetheredInterfaceCallbackShim mBluetoothCallback;
private SettingsObserver mSettingsObserver;
private BluetoothPan mBluetoothPan;
private PanServiceListener mBluetoothPanListener;
@@ -533,14 +541,16 @@
}
}
- // This method needs to exist because TETHERING_BLUETOOTH and TETHERING_WIGIG can't use
- // enableIpServing.
+ // This method needs to exist because TETHERING_BLUETOOTH before Android T and TETHERING_WIGIG
+ // can't use enableIpServing.
private void processInterfaceStateChange(final String iface, boolean enabled) {
// Do not listen to USB interface state changes or USB interface add/removes. USB tethering
// is driven only by USB_ACTION broadcasts.
final int type = ifaceNameToType(iface);
if (type == TETHERING_USB || type == TETHERING_NCM) return;
+ if (type == TETHERING_BLUETOOTH && SdkLevel.isAtLeastT()) return;
+
if (enabled) {
ensureIpServerStarted(iface);
} else {
@@ -769,6 +779,9 @@
TETHERING_BLUETOOTH);
}
mPendingPanRequests.clear();
+ mBluetoothIfaceRequest = null;
+ mBluetoothCallback = null;
+ maybeDisableBluetoothIpServing();
});
}
@@ -779,7 +792,11 @@
private void setBluetoothTetheringSettings(@NonNull final BluetoothPan bluetoothPan,
final boolean enable, final IIntResultListener listener) {
- bluetoothPan.setBluetoothTethering(enable);
+ if (SdkLevel.isAtLeastT()) {
+ changeBluetoothTetheringSettings(bluetoothPan, enable);
+ } else {
+ changeBluetoothTetheringSettingsPreT(bluetoothPan, enable);
+ }
// Enabling bluetooth tethering settings can silently fail. Send internal error if the
// result is not expected.
@@ -788,6 +805,68 @@
sendTetherResult(listener, result, TETHERING_BLUETOOTH);
}
+ private void changeBluetoothTetheringSettingsPreT(@NonNull final BluetoothPan bluetoothPan,
+ final boolean enable) {
+ bluetoothPan.setBluetoothTethering(enable);
+ }
+
+ private void changeBluetoothTetheringSettings(@NonNull final BluetoothPan bluetoothPan,
+ final boolean enable) {
+ final BluetoothPanShim panShim = mDeps.getBluetoothPanShim(bluetoothPan);
+ if (enable) {
+ if (mBluetoothIfaceRequest != null) {
+ Log.d(TAG, "Bluetooth tethering settings already enabled");
+ return;
+ }
+
+ mBluetoothCallback = new BluetoothCallback();
+ try {
+ mBluetoothIfaceRequest = panShim.requestTetheredInterface(mExecutor,
+ mBluetoothCallback);
+ } catch (UnsupportedApiLevelException e) {
+ Log.wtf(TAG, "Use unsupported API, " + e);
+ }
+ } else {
+ if (mBluetoothIfaceRequest == null) {
+ Log.d(TAG, "Bluetooth tethering settings already disabled");
+ return;
+ }
+
+ mBluetoothIfaceRequest.release();
+ mBluetoothIfaceRequest = null;
+ mBluetoothCallback = null;
+ // If bluetooth request is released, tethering won't able to receive
+ // onUnavailable callback, explicitly disable bluetooth IpServer manually.
+ maybeDisableBluetoothIpServing();
+ }
+ }
+
+ // BluetoothCallback is only called after T. Before T, PanService would call tether/untether to
+ // notify bluetooth interface status.
+ private class BluetoothCallback implements TetheredInterfaceCallbackShim {
+ @Override
+ public void onAvailable(String iface) {
+ if (this != mBluetoothCallback) return;
+
+ enableIpServing(TETHERING_BLUETOOTH, iface, getRequestedState(TETHERING_BLUETOOTH));
+ mConfiguredBluetoothIface = iface;
+ }
+
+ @Override
+ public void onUnavailable() {
+ if (this != mBluetoothCallback) return;
+
+ maybeDisableBluetoothIpServing();
+ }
+ }
+
+ private void maybeDisableBluetoothIpServing() {
+ if (mConfiguredBluetoothIface == null) return;
+
+ ensureIpServerStopped(mConfiguredBluetoothIface);
+ mConfiguredBluetoothIface = null;
+ }
+
private int setEthernetTethering(final boolean enable) {
final EthernetManager em = (EthernetManager) mContext.getSystemService(
Context.ETHERNET_SERVICE);
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 7df9475..c1a747e 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -18,6 +18,7 @@
import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothPan;
import android.content.Context;
import android.net.INetd;
import android.net.ip.IpServer;
@@ -31,6 +32,8 @@
import androidx.annotation.NonNull;
import com.android.internal.util.StateMachine;
+import com.android.networkstack.apishim.BluetoothPanShimImpl;
+import com.android.networkstack.apishim.common.BluetoothPanShim;
import java.util.ArrayList;
@@ -158,4 +161,13 @@
TetheringConfiguration cfg) {
return new PrivateAddressCoordinator(ctx, cfg);
}
+
+ /**
+ * Get BluetoothPanShim object to enable/disable bluetooth tethering.
+ *
+ * TODO: use BluetoothPan directly when mainline module is built with API 32.
+ */
+ public BluetoothPanShim getBluetoothPanShim(BluetoothPan pan) {
+ return BluetoothPanShimImpl.newInstance(pan);
+ }
}
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 2f2cde0..267c376 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -16,6 +16,7 @@
package android.net.ip;
+import static android.net.INetd.IF_STATE_DOWN;
import static android.net.INetd.IF_STATE_UP;
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.TetheringManager.TETHERING_BLUETOOTH;
@@ -33,6 +34,7 @@
import static android.net.ip.IpServer.STATE_UNAVAILABLE;
import static android.system.OsConstants.ETH_P_IPV6;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELNEIGH;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNEIGH;
@@ -400,11 +402,16 @@
}
@Test
- public void canBeTethered() throws Exception {
+ public void canBeTetheredAsBluetooth() throws Exception {
initStateMachine(TETHERING_BLUETOOTH);
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
- InOrder inOrder = inOrder(mCallback, mNetd);
+ InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
+ if (isAtLeastT()) {
+ inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
+ inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
+ IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
+ }
inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
// One for ipv4 route, one for ipv6 link local route.
@@ -426,7 +433,13 @@
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
- inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
+ // One is ipv4 address clear (set to 0.0.0.0), another is set interface down which only
+ // happen after T. Before T, the interface configuration control in bluetooth side.
+ if (isAtLeastT()) {
+ inOrder.verify(mNetd).interfaceSetCfg(
+ argThat(cfg -> assertContainsFlag(cfg.flags, IF_STATE_DOWN)));
+ }
+ inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> cfg.flags.length == 0));
inOrder.verify(mAddressCoordinator).releaseDownstream(any());
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
@@ -443,7 +456,7 @@
InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
- IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
+ IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
@@ -587,7 +600,8 @@
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
- inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
+ inOrder.verify(mNetd, times(isAtLeastT() ? 2 : 1)).interfaceSetCfg(
+ argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
inOrder.verify(mAddressCoordinator).releaseDownstream(any());
inOrder.verify(mBpfCoordinator).tetherOffloadClientClear(mIpServer);
inOrder.verify(mBpfCoordinator).stopMonitoring(mIpServer);
@@ -683,7 +697,11 @@
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
- assertDhcpStarted(mBluetoothPrefix);
+ if (isAtLeastT()) {
+ assertDhcpStarted(PrefixUtils.asIpPrefix(mTestAddress));
+ } else {
+ assertDhcpStarted(mBluetoothPrefix);
+ }
}
@Test
@@ -1371,7 +1389,6 @@
for (String flag : flags) {
if (flag.equals(match)) return true;
}
- fail("Missing flag: " + match);
return false;
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 40d133a..e4dbc7d 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -62,6 +62,7 @@
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_0;
@@ -81,6 +82,8 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Matchers.anyInt;
@@ -182,6 +185,10 @@
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.net.module.util.CollectionUtils;
+import com.android.networkstack.apishim.common.BluetoothPanShim;
+import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim;
+import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim;
+import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.networkstack.tethering.TestConnectivityManager.TestNetworkAgent;
import com.android.testutils.MiscAsserts;
@@ -261,6 +268,8 @@
@Mock private PackageManager mPackageManager;
@Mock private BluetoothAdapter mBluetoothAdapter;
@Mock private BluetoothPan mBluetoothPan;
+ @Mock private BluetoothPanShim mBluetoothPanShim;
+ @Mock private TetheredInterfaceRequestShim mTetheredInterfaceRequestShim;
private final MockIpServerDependencies mIpServerDependencies =
spy(new MockIpServerDependencies());
@@ -285,6 +294,7 @@
private PrivateAddressCoordinator mPrivateAddressCoordinator;
private SoftApCallback mSoftApCallback;
private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
+ private TetheredInterfaceCallbackShim mTetheredInterfaceCallbackShim;
private TestConnectivityManager mCm;
@@ -483,13 +493,23 @@
return false;
}
-
@Override
public PrivateAddressCoordinator getPrivateAddressCoordinator(Context ctx,
TetheringConfiguration cfg) {
mPrivateAddressCoordinator = super.getPrivateAddressCoordinator(ctx, cfg);
return mPrivateAddressCoordinator;
}
+
+ @Override
+ public BluetoothPanShim getBluetoothPanShim(BluetoothPan pan) {
+ try {
+ when(mBluetoothPanShim.requestTetheredInterface(
+ any(), any())).thenReturn(mTetheredInterfaceRequestShim);
+ } catch (UnsupportedApiLevelException e) {
+ fail("BluetoothPan#requestTetheredInterface is not supported");
+ }
+ return mBluetoothPanShim;
+ }
}
private static LinkProperties buildUpstreamLinkProperties(String interfaceName,
@@ -2557,6 +2577,44 @@
@Test
public void testBluetoothTethering() throws Exception {
+ // Switch to @IgnoreUpTo(Build.VERSION_CODES.S_V2) when it is available for AOSP.
+ assumeTrue(isAtLeastT());
+
+ final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
+ mLooper.dispatchAll();
+ verifySetBluetoothTethering(true /* enable */, true /* bindToPanService */);
+ result.assertHasResult();
+
+ mTetheredInterfaceCallbackShim.onAvailable(TEST_BT_IFNAME);
+ mLooper.dispatchAll();
+ verifyNetdCommandForBtSetup();
+
+ // If PAN disconnect, tethering should also be stopped.
+ mTetheredInterfaceCallbackShim.onUnavailable();
+ mLooper.dispatchAll();
+ verifyNetdCommandForBtTearDown();
+
+ // Tethering could restart if PAN reconnect.
+ mTetheredInterfaceCallbackShim.onAvailable(TEST_BT_IFNAME);
+ mLooper.dispatchAll();
+ verifyNetdCommandForBtSetup();
+
+ // Pretend that bluetooth tethering was disabled.
+ mockBluetoothSettings(true /* bluetoothOn */, false /* tetheringOn */);
+ mTethering.stopTethering(TETHERING_BLUETOOTH);
+ mLooper.dispatchAll();
+ verifySetBluetoothTethering(false /* enable */, false /* bindToPanService */);
+
+ verifyNetdCommandForBtTearDown();
+ }
+
+ @Test
+ public void testBluetoothTetheringBeforeT() throws Exception {
+ // Switch to @IgnoreAfter(Build.VERSION_CODES.S_V2) when it is available for AOSP.
+ assumeFalse(isAtLeastT());
+
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
@@ -2610,12 +2668,17 @@
mTethering.interfaceAdded(TEST_BT_IFNAME);
mLooper.dispatchAll();
- mTethering.interfaceStatusChanged(TEST_BT_IFNAME, false);
- mTethering.interfaceStatusChanged(TEST_BT_IFNAME, true);
- final ResultListener tetherResult = new ResultListener(TETHER_ERROR_NO_ERROR);
- mTethering.tether(TEST_BT_IFNAME, IpServer.STATE_TETHERED, tetherResult);
- mLooper.dispatchAll();
- tetherResult.assertHasResult();
+ if (isAtLeastT()) {
+ mTetheredInterfaceCallbackShim.onAvailable(TEST_BT_IFNAME);
+ mLooper.dispatchAll();
+ } else {
+ mTethering.interfaceStatusChanged(TEST_BT_IFNAME, false);
+ mTethering.interfaceStatusChanged(TEST_BT_IFNAME, true);
+ final ResultListener tetherResult = new ResultListener(TETHER_ERROR_NO_ERROR);
+ mTethering.tether(TEST_BT_IFNAME, IpServer.STATE_TETHERED, tetherResult);
+ mLooper.dispatchAll();
+ tetherResult.assertHasResult();
+ }
verifyNetdCommandForBtSetup();
@@ -2632,6 +2695,10 @@
}
private void verifyNetdCommandForBtSetup() throws Exception {
+ if (isAtLeastT()) {
+ verify(mNetd).interfaceSetCfg(argThat(cfg -> TEST_BT_IFNAME.equals(cfg.ifName)
+ && assertContainsFlag(cfg.flags, INetd.IF_STATE_UP)));
+ }
verify(mNetd).tetherInterfaceAdd(TEST_BT_IFNAME);
verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, TEST_BT_IFNAME);
verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_BT_IFNAME),
@@ -2644,19 +2711,30 @@
reset(mNetd);
}
+ private boolean assertContainsFlag(String[] flags, String match) {
+ for (String flag : flags) {
+ if (flag.equals(match)) return true;
+ }
+ return false;
+ }
+
private void verifyNetdCommandForBtTearDown() throws Exception {
verify(mNetd).tetherApplyDnsInterfaces();
verify(mNetd).tetherInterfaceRemove(TEST_BT_IFNAME);
verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_BT_IFNAME);
- verify(mNetd).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+ // One is ipv4 address clear (set to 0.0.0.0), another is set interface down which only
+ // happen after T. Before T, the interface configuration control in bluetooth side.
+ verify(mNetd, times(isAtLeastT() ? 2 : 1)).interfaceSetCfg(
+ any(InterfaceConfigurationParcel.class));
verify(mNetd).tetherStop();
verify(mNetd).ipfwdDisableForwarding(TETHERING_NAME);
+ reset(mNetd);
}
// If bindToPanService is true, this function would return ServiceListener which could notify
// PanService is connected or disconnected.
private ServiceListener verifySetBluetoothTethering(final boolean enable,
- final boolean bindToPanService) {
+ final boolean bindToPanService) throws Exception {
ServiceListener listener = null;
verify(mBluetoothAdapter).isEnabled();
if (bindToPanService) {
@@ -2671,7 +2749,19 @@
verify(mBluetoothAdapter, never()).getProfileProxy(eq(mServiceContext), any(),
anyInt());
}
- verify(mBluetoothPan).setBluetoothTethering(enable);
+
+ if (isAtLeastT()) {
+ if (enable) {
+ final ArgumentCaptor<TetheredInterfaceCallbackShim> callbackCaptor =
+ ArgumentCaptor.forClass(TetheredInterfaceCallbackShim.class);
+ verify(mBluetoothPanShim).requestTetheredInterface(any(), callbackCaptor.capture());
+ mTetheredInterfaceCallbackShim = callbackCaptor.getValue();
+ } else {
+ verify(mTetheredInterfaceRequestShim).release();
+ }
+ } else {
+ verify(mBluetoothPan).setBluetoothTethering(enable);
+ }
verify(mBluetoothPan).isTetheringOn();
verifyNoMoreInteractions(mBluetoothAdapter, mBluetoothPan);
reset(mBluetoothAdapter, mBluetoothPan);
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index d015ef6..bb9f5ead6 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -71,3 +71,29 @@
"-Werror",
],
}
+
+bpf {
+ name: "clatd.o_mainline",
+ srcs: ["clatd.c"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ include_dirs: [
+ "frameworks/libs/net/common/netd/libnetdutils/include",
+ ],
+ sub_dir: "net_shared",
+}
+
+bpf {
+ name: "netd.o_mainline",
+ srcs: ["netd.c"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ include_dirs: [
+ "frameworks/libs/net/common/netd/libnetdutils/include",
+ ],
+ sub_dir: "net_shared",
+}
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
new file mode 100644
index 0000000..dc646c3
--- /dev/null
+++ b/bpf_progs/clatd.c
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include <linux/bpf.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/pkt_cls.h>
+#include <linux/swab.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+// bionic kernel uapi linux/udp.h header is munged...
+#define __kernel_udphdr udphdr
+#include <linux/udp.h>
+
+#include "bpf_helpers.h"
+#include "bpf_net_helpers.h"
+#include "bpf_shared.h"
+
+// From kernel:include/net/ip.h
+#define IP_DF 0x4000 // Flag: "Don't Fragment"
+
+DEFINE_BPF_MAP_GRW(clat_ingress6_map, HASH, ClatIngress6Key, ClatIngress6Value, 16, AID_SYSTEM)
+
+static inline __always_inline int nat64(struct __sk_buff* skb, bool is_ethernet) {
+ const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
+ void* data = (void*)(long)skb->data;
+ const void* data_end = (void*)(long)skb->data_end;
+ const struct ethhdr* const eth = is_ethernet ? data : NULL; // used iff is_ethernet
+ const struct ipv6hdr* const ip6 = is_ethernet ? (void*)(eth + 1) : data;
+
+ // Require ethernet dst mac address to be our unicast address.
+ if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_PIPE;
+
+ // Must be meta-ethernet IPv6 frame
+ if (skb->protocol != htons(ETH_P_IPV6)) return TC_ACT_PIPE;
+
+ // Must have (ethernet and) ipv6 header
+ if (data + l2_header_size + sizeof(*ip6) > data_end) return TC_ACT_PIPE;
+
+ // Ethertype - if present - must be IPv6
+ if (is_ethernet && (eth->h_proto != htons(ETH_P_IPV6))) return TC_ACT_PIPE;
+
+ // IP version must be 6
+ if (ip6->version != 6) return TC_ACT_PIPE;
+
+ // Maximum IPv6 payload length that can be translated to IPv4
+ if (ntohs(ip6->payload_len) > 0xFFFF - sizeof(struct iphdr)) return TC_ACT_PIPE;
+
+ switch (ip6->nexthdr) {
+ case IPPROTO_TCP: // For TCP & UDP the checksum neutrality of the chosen IPv6
+ case IPPROTO_UDP: // address means there is no need to update their checksums.
+ case IPPROTO_GRE: // We do not need to bother looking at GRE/ESP headers,
+ case IPPROTO_ESP: // since there is never a checksum to update.
+ break;
+
+ default: // do not know how to handle anything else
+ return TC_ACT_PIPE;
+ }
+
+ ClatIngress6Key k = {
+ .iif = skb->ifindex,
+ .pfx96.in6_u.u6_addr32 =
+ {
+ ip6->saddr.in6_u.u6_addr32[0],
+ ip6->saddr.in6_u.u6_addr32[1],
+ ip6->saddr.in6_u.u6_addr32[2],
+ },
+ .local6 = ip6->daddr,
+ };
+
+ ClatIngress6Value* v = bpf_clat_ingress6_map_lookup_elem(&k);
+
+ if (!v) return TC_ACT_PIPE;
+
+ struct ethhdr eth2; // used iff is_ethernet
+ if (is_ethernet) {
+ eth2 = *eth; // Copy over the ethernet header (src/dst mac)
+ eth2.h_proto = htons(ETH_P_IP); // But replace the ethertype
+ }
+
+ struct iphdr ip = {
+ .version = 4, // u4
+ .ihl = sizeof(struct iphdr) / sizeof(__u32), // u4
+ .tos = (ip6->priority << 4) + (ip6->flow_lbl[0] >> 4), // u8
+ .tot_len = htons(ntohs(ip6->payload_len) + sizeof(struct iphdr)), // u16
+ .id = 0, // u16
+ .frag_off = htons(IP_DF), // u16
+ .ttl = ip6->hop_limit, // u8
+ .protocol = ip6->nexthdr, // u8
+ .check = 0, // u16
+ .saddr = ip6->saddr.in6_u.u6_addr32[3], // u32
+ .daddr = v->local4.s_addr, // u32
+ };
+
+ // Calculate the IPv4 one's complement checksum of the IPv4 header.
+ __wsum sum4 = 0;
+ for (int i = 0; i < sizeof(ip) / sizeof(__u16); ++i) {
+ sum4 += ((__u16*)&ip)[i];
+ }
+ // Note that sum4 is guaranteed to be non-zero by virtue of ip.version == 4
+ sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse u32 into range 1 .. 0x1FFFE
+ sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse any potential carry into u16
+ ip.check = (__u16)~sum4; // sum4 cannot be zero, so this is never 0xFFFF
+
+ // Calculate the *negative* IPv6 16-bit one's complement checksum of the IPv6 header.
+ __wsum sum6 = 0;
+ // We'll end up with a non-zero sum due to ip6->version == 6 (which has '0' bits)
+ for (int i = 0; i < sizeof(*ip6) / sizeof(__u16); ++i) {
+ sum6 += ~((__u16*)ip6)[i]; // note the bitwise negation
+ }
+
+ // Note that there is no L4 checksum update: we are relying on the checksum neutrality
+ // of the ipv6 address chosen by netd's ClatdController.
+
+ // Packet mutations begin - point of no return, but if this first modification fails
+ // the packet is probably still pristine, so let clatd handle it.
+ if (bpf_skb_change_proto(skb, htons(ETH_P_IP), 0)) return TC_ACT_PIPE;
+
+ // This takes care of updating the skb->csum field for a CHECKSUM_COMPLETE packet.
+ //
+ // In such a case, skb->csum is a 16-bit one's complement sum of the entire payload,
+ // thus we need to subtract out the ipv6 header's sum, and add in the ipv4 header's sum.
+ // However, by construction of ip.check above the checksum of an ipv4 header is zero.
+ // Thus we only need to subtract the ipv6 header's sum, which is the same as adding
+ // in the sum of the bitwise negation of the ipv6 header.
+ //
+ // bpf_csum_update() always succeeds if the skb is CHECKSUM_COMPLETE and returns an error
+ // (-ENOTSUPP) if it isn't. So we just ignore the return code.
+ //
+ // if (skb->ip_summed == CHECKSUM_COMPLETE)
+ // return (skb->csum = csum_add(skb->csum, csum));
+ // else
+ // return -ENOTSUPP;
+ bpf_csum_update(skb, sum6);
+
+ // bpf_skb_change_proto() invalidates all pointers - reload them.
+ data = (void*)(long)skb->data;
+ data_end = (void*)(long)skb->data_end;
+
+ // I cannot think of any valid way for this error condition to trigger, however I do
+ // believe the explicit check is required to keep the in kernel ebpf verifier happy.
+ if (data + l2_header_size + sizeof(struct iphdr) > data_end) return TC_ACT_SHOT;
+
+ if (is_ethernet) {
+ struct ethhdr* new_eth = data;
+
+ // Copy over the updated ethernet header
+ *new_eth = eth2;
+
+ // Copy over the new ipv4 header.
+ *(struct iphdr*)(new_eth + 1) = ip;
+ } else {
+ // Copy over the new ipv4 header without an ethernet header.
+ *(struct iphdr*)data = ip;
+ }
+
+ // Redirect, possibly back to same interface, so tcpdump sees packet twice.
+ if (v->oif) return bpf_redirect(v->oif, BPF_F_INGRESS);
+
+ // Just let it through, tcpdump will not see IPv4 packet.
+ return TC_ACT_PIPE;
+}
+
+DEFINE_BPF_PROG("schedcls/ingress6/clat_ether", AID_ROOT, AID_SYSTEM, sched_cls_ingress6_clat_ether)
+(struct __sk_buff* skb) {
+ return nat64(skb, true);
+}
+
+DEFINE_BPF_PROG("schedcls/ingress6/clat_rawip", AID_ROOT, AID_SYSTEM, sched_cls_ingress6_clat_rawip)
+(struct __sk_buff* skb) {
+ return nat64(skb, false);
+}
+
+DEFINE_BPF_MAP_GRW(clat_egress4_map, HASH, ClatEgress4Key, ClatEgress4Value, 16, AID_SYSTEM)
+
+DEFINE_BPF_PROG("schedcls/egress4/clat_ether", AID_ROOT, AID_SYSTEM, sched_cls_egress4_clat_ether)
+(struct __sk_buff* skb) {
+ return TC_ACT_PIPE;
+}
+
+DEFINE_BPF_PROG("schedcls/egress4/clat_rawip", AID_ROOT, AID_SYSTEM, sched_cls_egress4_clat_rawip)
+(struct __sk_buff* skb) {
+ void* data = (void*)(long)skb->data;
+ const void* data_end = (void*)(long)skb->data_end;
+ const struct iphdr* const ip4 = data;
+
+ // Must be meta-ethernet IPv4 frame
+ if (skb->protocol != htons(ETH_P_IP)) return TC_ACT_PIPE;
+
+ // Must have ipv4 header
+ if (data + sizeof(*ip4) > data_end) return TC_ACT_PIPE;
+
+ // IP version must be 4
+ if (ip4->version != 4) return TC_ACT_PIPE;
+
+ // We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header
+ if (ip4->ihl != 5) return TC_ACT_PIPE;
+
+ // Calculate the IPv4 one's complement checksum of the IPv4 header.
+ __wsum sum4 = 0;
+ for (int i = 0; i < sizeof(*ip4) / sizeof(__u16); ++i) {
+ sum4 += ((__u16*)ip4)[i];
+ }
+ // Note that sum4 is guaranteed to be non-zero by virtue of ip4->version == 4
+ sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse u32 into range 1 .. 0x1FFFE
+ sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse any potential carry into u16
+ // for a correct checksum we should get *a* zero, but sum4 must be positive, ie 0xFFFF
+ if (sum4 != 0xFFFF) return TC_ACT_PIPE;
+
+ // Minimum IPv4 total length is the size of the header
+ if (ntohs(ip4->tot_len) < sizeof(*ip4)) return TC_ACT_PIPE;
+
+ // We are incapable of dealing with IPv4 fragments
+ if (ip4->frag_off & ~htons(IP_DF)) return TC_ACT_PIPE;
+
+ switch (ip4->protocol) {
+ case IPPROTO_TCP: // For TCP & UDP the checksum neutrality of the chosen IPv6
+ case IPPROTO_GRE: // address means there is no need to update their checksums.
+ case IPPROTO_ESP: // We do not need to bother looking at GRE/ESP headers,
+ break; // since there is never a checksum to update.
+
+ case IPPROTO_UDP: // See above comment, but must also have UDP header...
+ if (data + sizeof(*ip4) + sizeof(struct udphdr) > data_end) return TC_ACT_PIPE;
+ const struct udphdr* uh = (const struct udphdr*)(ip4 + 1);
+ // If IPv4/UDP checksum is 0 then fallback to clatd so it can calculate the
+ // checksum. Otherwise the network or more likely the NAT64 gateway might
+ // drop the packet because in most cases IPv6/UDP packets with a zero checksum
+ // are invalid. See RFC 6935. TODO: calculate checksum via bpf_csum_diff()
+ if (!uh->check) return TC_ACT_PIPE;
+ break;
+
+ default: // do not know how to handle anything else
+ return TC_ACT_PIPE;
+ }
+
+ ClatEgress4Key k = {
+ .iif = skb->ifindex,
+ .local4.s_addr = ip4->saddr,
+ };
+
+ ClatEgress4Value* v = bpf_clat_egress4_map_lookup_elem(&k);
+
+ if (!v) return TC_ACT_PIPE;
+
+ // Translating without redirecting doesn't make sense.
+ if (!v->oif) return TC_ACT_PIPE;
+
+ // This implementation is currently limited to rawip.
+ if (v->oifIsEthernet) return TC_ACT_PIPE;
+
+ struct ipv6hdr ip6 = {
+ .version = 6, // __u8:4
+ .priority = ip4->tos >> 4, // __u8:4
+ .flow_lbl = {(ip4->tos & 0xF) << 4, 0, 0}, // __u8[3]
+ .payload_len = htons(ntohs(ip4->tot_len) - 20), // __be16
+ .nexthdr = ip4->protocol, // __u8
+ .hop_limit = ip4->ttl, // __u8
+ .saddr = v->local6, // struct in6_addr
+ .daddr = v->pfx96, // struct in6_addr
+ };
+ ip6.daddr.in6_u.u6_addr32[3] = ip4->daddr;
+
+ // Calculate the IPv6 16-bit one's complement checksum of the IPv6 header.
+ __wsum sum6 = 0;
+ // We'll end up with a non-zero sum due to ip6.version == 6
+ for (int i = 0; i < sizeof(ip6) / sizeof(__u16); ++i) {
+ sum6 += ((__u16*)&ip6)[i];
+ }
+
+ // Note that there is no L4 checksum update: we are relying on the checksum neutrality
+ // of the ipv6 address chosen by netd's ClatdController.
+
+ // Packet mutations begin - point of no return, but if this first modification fails
+ // the packet is probably still pristine, so let clatd handle it.
+ if (bpf_skb_change_proto(skb, htons(ETH_P_IPV6), 0)) return TC_ACT_PIPE;
+
+ // This takes care of updating the skb->csum field for a CHECKSUM_COMPLETE packet.
+ //
+ // In such a case, skb->csum is a 16-bit one's complement sum of the entire payload,
+ // thus we need to subtract out the ipv4 header's sum, and add in the ipv6 header's sum.
+ // However, we've already verified the ipv4 checksum is correct and thus 0.
+ // Thus we only need to add the ipv6 header's sum.
+ //
+ // bpf_csum_update() always succeeds if the skb is CHECKSUM_COMPLETE and returns an error
+ // (-ENOTSUPP) if it isn't. So we just ignore the return code (see above for more details).
+ bpf_csum_update(skb, sum6);
+
+ // bpf_skb_change_proto() invalidates all pointers - reload them.
+ data = (void*)(long)skb->data;
+ data_end = (void*)(long)skb->data_end;
+
+ // I cannot think of any valid way for this error condition to trigger, however I do
+ // believe the explicit check is required to keep the in kernel ebpf verifier happy.
+ if (data + sizeof(ip6) > data_end) return TC_ACT_SHOT;
+
+ // Copy over the new ipv6 header without an ethernet header.
+ *(struct ipv6hdr*)data = ip6;
+
+ // Redirect to non v4-* interface. Tcpdump only sees packet after this redirect.
+ return bpf_redirect(v->oif, 0 /* this is effectively BPF_F_EGRESS */);
+}
+
+LICENSE("Apache 2.0");
+CRITICAL("netd");
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
new file mode 100644
index 0000000..72ee431
--- /dev/null
+++ b/bpf_progs/netd.c
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include <bpf_helpers.h>
+#include <linux/bpf.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/pkt_cls.h>
+#include <linux/tcp.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include "bpf_net_helpers.h"
+#include "bpf_shared.h"
+
+// This is defined for cgroup bpf filter only.
+#define BPF_DROP_UNLESS_DNS 2
+#define BPF_PASS 1
+#define BPF_DROP 0
+
+// This is used for xt_bpf program only.
+#define BPF_NOMATCH 0
+#define BPF_MATCH 1
+
+#define BPF_EGRESS 0
+#define BPF_INGRESS 1
+
+#define IP_PROTO_OFF offsetof(struct iphdr, protocol)
+#define IPV6_PROTO_OFF offsetof(struct ipv6hdr, nexthdr)
+#define IPPROTO_IHL_OFF 0
+#define TCP_FLAG_OFF 13
+#define RST_OFFSET 2
+
+DEFINE_BPF_MAP_GRW(cookie_tag_map, HASH, uint64_t, UidTagValue, COOKIE_UID_MAP_SIZE,
+ AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(uid_counterset_map, HASH, uint32_t, uint8_t, UID_COUNTERSET_MAP_SIZE,
+ AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(app_uid_stats_map, HASH, uint32_t, StatsValue, APP_STATS_MAP_SIZE,
+ AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(stats_map_A, HASH, StatsKey, StatsValue, STATS_MAP_SIZE, AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(stats_map_B, HASH, StatsKey, StatsValue, STATS_MAP_SIZE, AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(iface_stats_map, HASH, uint32_t, StatsValue, IFACE_STATS_MAP_SIZE,
+ AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(configuration_map, HASH, uint32_t, uint8_t, CONFIGURATION_MAP_SIZE,
+ AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(uid_owner_map, HASH, uint32_t, UidOwnerValue, UID_OWNER_MAP_SIZE,
+ AID_NET_BW_ACCT)
+DEFINE_BPF_MAP_GRW(uid_permission_map, HASH, uint32_t, uint8_t, UID_OWNER_MAP_SIZE, AID_NET_BW_ACCT)
+
+/* never actually used from ebpf */
+DEFINE_BPF_MAP_GRW(iface_index_name_map, HASH, uint32_t, IfaceValue, IFACE_INDEX_NAME_MAP_SIZE,
+ AID_NET_BW_ACCT)
+
+static __always_inline int is_system_uid(uint32_t uid) {
+ return (uid <= MAX_SYSTEM_UID) && (uid >= MIN_SYSTEM_UID);
+}
+
+/*
+ * Note: this blindly assumes an MTU of 1500, and that packets > MTU are always TCP,
+ * and that TCP is using the Linux default settings with TCP timestamp option enabled
+ * which uses 12 TCP option bytes per frame.
+ *
+ * These are not unreasonable assumptions:
+ *
+ * The internet does not really support MTUs greater than 1500, so most TCP traffic will
+ * be at that MTU, or slightly below it (worst case our upwards adjustment is too small).
+ *
+ * The chance our traffic isn't IP at all is basically zero, so the IP overhead correction
+ * is bound to be needed.
+ *
+ * Furthermore, the likelyhood that we're having to deal with GSO (ie. > MTU) packets that
+ * are not IP/TCP is pretty small (few other things are supported by Linux) and worse case
+ * our extra overhead will be slightly off, but probably still better than assuming none.
+ *
+ * Most servers are also Linux and thus support/default to using TCP timestamp option
+ * (and indeed TCP timestamp option comes from RFC 1323 titled "TCP Extensions for High
+ * Performance" which also defined TCP window scaling and are thus absolutely ancient...).
+ *
+ * All together this should be more correct than if we simply ignored GSO frames
+ * (ie. counted them as single packets with no extra overhead)
+ *
+ * Especially since the number of packets is important for any future clat offload correction.
+ * (which adjusts upward by 20 bytes per packet to account for ipv4 -> ipv6 header conversion)
+ */
+#define DEFINE_UPDATE_STATS(the_stats_map, TypeOfKey) \
+ static __always_inline inline void update_##the_stats_map(struct __sk_buff* skb, \
+ int direction, TypeOfKey* key) { \
+ StatsValue* value = bpf_##the_stats_map##_lookup_elem(key); \
+ if (!value) { \
+ StatsValue newValue = {}; \
+ bpf_##the_stats_map##_update_elem(key, &newValue, BPF_NOEXIST); \
+ value = bpf_##the_stats_map##_lookup_elem(key); \
+ } \
+ if (value) { \
+ const int mtu = 1500; \
+ uint64_t packets = 1; \
+ uint64_t bytes = skb->len; \
+ if (bytes > mtu) { \
+ bool is_ipv6 = (skb->protocol == htons(ETH_P_IPV6)); \
+ int ip_overhead = (is_ipv6 ? sizeof(struct ipv6hdr) : sizeof(struct iphdr)); \
+ int tcp_overhead = ip_overhead + sizeof(struct tcphdr) + 12; \
+ int mss = mtu - tcp_overhead; \
+ uint64_t payload = bytes - tcp_overhead; \
+ packets = (payload + mss - 1) / mss; \
+ bytes = tcp_overhead * packets + payload; \
+ } \
+ if (direction == BPF_EGRESS) { \
+ __sync_fetch_and_add(&value->txPackets, packets); \
+ __sync_fetch_and_add(&value->txBytes, bytes); \
+ } else if (direction == BPF_INGRESS) { \
+ __sync_fetch_and_add(&value->rxPackets, packets); \
+ __sync_fetch_and_add(&value->rxBytes, bytes); \
+ } \
+ } \
+ }
+
+DEFINE_UPDATE_STATS(app_uid_stats_map, uint32_t)
+DEFINE_UPDATE_STATS(iface_stats_map, uint32_t)
+DEFINE_UPDATE_STATS(stats_map_A, StatsKey)
+DEFINE_UPDATE_STATS(stats_map_B, StatsKey)
+
+static inline bool skip_owner_match(struct __sk_buff* skb) {
+ int offset = -1;
+ int ret = 0;
+ if (skb->protocol == htons(ETH_P_IP)) {
+ offset = IP_PROTO_OFF;
+ uint8_t proto, ihl;
+ uint8_t flag;
+ ret = bpf_skb_load_bytes(skb, offset, &proto, 1);
+ if (!ret) {
+ if (proto == IPPROTO_ESP) {
+ return true;
+ } else if (proto == IPPROTO_TCP) {
+ ret = bpf_skb_load_bytes(skb, IPPROTO_IHL_OFF, &ihl, 1);
+ ihl = ihl & 0x0F;
+ ret = bpf_skb_load_bytes(skb, ihl * 4 + TCP_FLAG_OFF, &flag, 1);
+ if (ret == 0 && (flag >> RST_OFFSET & 1)) {
+ return true;
+ }
+ }
+ }
+ } else if (skb->protocol == htons(ETH_P_IPV6)) {
+ offset = IPV6_PROTO_OFF;
+ uint8_t proto;
+ ret = bpf_skb_load_bytes(skb, offset, &proto, 1);
+ if (!ret) {
+ if (proto == IPPROTO_ESP) {
+ return true;
+ } else if (proto == IPPROTO_TCP) {
+ uint8_t flag;
+ ret = bpf_skb_load_bytes(skb, sizeof(struct ipv6hdr) + TCP_FLAG_OFF, &flag, 1);
+ if (ret == 0 && (flag >> RST_OFFSET & 1)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+static __always_inline BpfConfig getConfig(uint32_t configKey) {
+ uint32_t mapSettingKey = configKey;
+ BpfConfig* config = bpf_configuration_map_lookup_elem(&mapSettingKey);
+ if (!config) {
+ // Couldn't read configuration entry. Assume everything is disabled.
+ return DEFAULT_CONFIG;
+ }
+ return *config;
+}
+
+static inline int bpf_owner_match(struct __sk_buff* skb, uint32_t uid, int direction) {
+ if (skip_owner_match(skb)) return BPF_PASS;
+
+ if (is_system_uid(uid)) return BPF_PASS;
+
+ BpfConfig enabledRules = getConfig(UID_RULES_CONFIGURATION_KEY);
+
+ UidOwnerValue* uidEntry = bpf_uid_owner_map_lookup_elem(&uid);
+ uint8_t uidRules = uidEntry ? uidEntry->rule : 0;
+ uint32_t allowed_iif = uidEntry ? uidEntry->iif : 0;
+
+ if (enabledRules) {
+ if ((enabledRules & DOZABLE_MATCH) && !(uidRules & DOZABLE_MATCH)) {
+ return BPF_DROP;
+ }
+ if ((enabledRules & STANDBY_MATCH) && (uidRules & STANDBY_MATCH)) {
+ return BPF_DROP;
+ }
+ if ((enabledRules & POWERSAVE_MATCH) && !(uidRules & POWERSAVE_MATCH)) {
+ return BPF_DROP;
+ }
+ if ((enabledRules & RESTRICTED_MATCH) && !(uidRules & RESTRICTED_MATCH)) {
+ return BPF_DROP;
+ }
+ }
+ if (direction == BPF_INGRESS && (uidRules & IIF_MATCH)) {
+ // Drops packets not coming from lo nor the allowlisted interface
+ if (allowed_iif && skb->ifindex != 1 && skb->ifindex != allowed_iif) {
+ return BPF_DROP_UNLESS_DNS;
+ }
+ }
+ return BPF_PASS;
+}
+
+static __always_inline inline void update_stats_with_config(struct __sk_buff* skb, int direction,
+ StatsKey* key, uint8_t selectedMap) {
+ if (selectedMap == SELECT_MAP_A) {
+ update_stats_map_A(skb, direction, key);
+ } else if (selectedMap == SELECT_MAP_B) {
+ update_stats_map_B(skb, direction, key);
+ }
+}
+
+static __always_inline inline int bpf_traffic_account(struct __sk_buff* skb, int direction) {
+ uint32_t sock_uid = bpf_get_socket_uid(skb);
+ uint64_t cookie = bpf_get_socket_cookie(skb);
+ UidTagValue* utag = bpf_cookie_tag_map_lookup_elem(&cookie);
+ uint32_t uid, tag;
+ if (utag) {
+ uid = utag->uid;
+ tag = utag->tag;
+ } else {
+ uid = sock_uid;
+ tag = 0;
+ }
+
+ // Always allow and never count clat traffic. Only the IPv4 traffic on the stacked
+ // interface is accounted for and subject to usage restrictions.
+ // TODO: remove sock_uid check once Nat464Xlat javaland adds the socket tag AID_CLAT for clat.
+ if (sock_uid == AID_CLAT || uid == AID_CLAT) {
+ return BPF_PASS;
+ }
+
+ int match = bpf_owner_match(skb, sock_uid, direction);
+ if ((direction == BPF_EGRESS) && (match == BPF_DROP)) {
+ // If an outbound packet is going to be dropped, we do not count that
+ // traffic.
+ return match;
+ }
+
+// Workaround for secureVPN with VpnIsolation enabled, refer to b/159994981 for details.
+// Keep TAG_SYSTEM_DNS in sync with DnsResolver/include/netd_resolv/resolv.h
+// and TrafficStatsConstants.java
+#define TAG_SYSTEM_DNS 0xFFFFFF82
+ if (tag == TAG_SYSTEM_DNS && uid == AID_DNS) {
+ uid = sock_uid;
+ if (match == BPF_DROP_UNLESS_DNS) match = BPF_PASS;
+ } else {
+ if (match == BPF_DROP_UNLESS_DNS) match = BPF_DROP;
+ }
+
+ StatsKey key = {.uid = uid, .tag = tag, .counterSet = 0, .ifaceIndex = skb->ifindex};
+
+ uint8_t* counterSet = bpf_uid_counterset_map_lookup_elem(&uid);
+ if (counterSet) key.counterSet = (uint32_t)*counterSet;
+
+ uint32_t mapSettingKey = CURRENT_STATS_MAP_CONFIGURATION_KEY;
+ uint8_t* selectedMap = bpf_configuration_map_lookup_elem(&mapSettingKey);
+
+ // Use asm("%0 &= 1" : "+r"(match)) before return match,
+ // to help kernel's bpf verifier, so that it can be 100% certain
+ // that the returned value is always BPF_NOMATCH(0) or BPF_MATCH(1).
+ if (!selectedMap) {
+ asm("%0 &= 1" : "+r"(match));
+ return match;
+ }
+
+ if (key.tag) {
+ update_stats_with_config(skb, direction, &key, *selectedMap);
+ key.tag = 0;
+ }
+
+ update_stats_with_config(skb, direction, &key, *selectedMap);
+ update_app_uid_stats_map(skb, direction, &uid);
+ asm("%0 &= 1" : "+r"(match));
+ return match;
+}
+
+DEFINE_BPF_PROG("cgroupskb/ingress/stats", AID_ROOT, AID_ROOT, bpf_cgroup_ingress)
+(struct __sk_buff* skb) {
+ return bpf_traffic_account(skb, BPF_INGRESS);
+}
+
+DEFINE_BPF_PROG("cgroupskb/egress/stats", AID_ROOT, AID_ROOT, bpf_cgroup_egress)
+(struct __sk_buff* skb) {
+ return bpf_traffic_account(skb, BPF_EGRESS);
+}
+
+DEFINE_BPF_PROG("skfilter/egress/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_egress_prog)
+(struct __sk_buff* skb) {
+ // Clat daemon does not generate new traffic, all its traffic is accounted for already
+ // on the v4-* interfaces (except for the 20 (or 28) extra bytes of IPv6 vs IPv4 overhead,
+ // but that can be corrected for later when merging v4-foo stats into interface foo's).
+ // TODO: remove sock_uid check once Nat464Xlat javaland adds the socket tag AID_CLAT for clat.
+ uint32_t sock_uid = bpf_get_socket_uid(skb);
+ if (sock_uid == AID_CLAT) return BPF_NOMATCH;
+ if (sock_uid == AID_SYSTEM) {
+ uint64_t cookie = bpf_get_socket_cookie(skb);
+ UidTagValue* utag = bpf_cookie_tag_map_lookup_elem(&cookie);
+ if (utag && utag->uid == AID_CLAT) return BPF_NOMATCH;
+ }
+
+ uint32_t key = skb->ifindex;
+ update_iface_stats_map(skb, BPF_EGRESS, &key);
+ return BPF_MATCH;
+}
+
+DEFINE_BPF_PROG("skfilter/ingress/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_ingress_prog)
+(struct __sk_buff* skb) {
+ // Clat daemon traffic is not accounted by virtue of iptables raw prerouting drop rule
+ // (in clat_raw_PREROUTING chain), which triggers before this (in bw_raw_PREROUTING chain).
+ // It will be accounted for on the v4-* clat interface instead.
+ // Keep that in mind when moving this out of iptables xt_bpf and into tc ingress (or xdp).
+
+ uint32_t key = skb->ifindex;
+ update_iface_stats_map(skb, BPF_INGRESS, &key);
+ return BPF_MATCH;
+}
+
+DEFINE_BPF_PROG("schedact/ingress/account", AID_ROOT, AID_NET_ADMIN, tc_bpf_ingress_account_prog)
+(struct __sk_buff* skb) {
+ // Account for ingress traffic before tc drops it.
+ uint32_t key = skb->ifindex;
+ update_iface_stats_map(skb, BPF_INGRESS, &key);
+ return TC_ACT_UNSPEC;
+}
+
+DEFINE_BPF_PROG("skfilter/allowlist/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_allowlist_prog)
+(struct __sk_buff* skb) {
+ uint32_t sock_uid = bpf_get_socket_uid(skb);
+ if (is_system_uid(sock_uid)) return BPF_MATCH;
+
+ // 65534 is the overflow 'nobody' uid, usually this being returned means
+ // that skb->sk is NULL during RX (early decap socket lookup failure),
+ // which commonly happens for incoming packets to an unconnected udp socket.
+ // Additionally bpf_get_socket_cookie() returns 0 if skb->sk is NULL
+ if ((sock_uid == 65534) && !bpf_get_socket_cookie(skb) && is_received_skb(skb))
+ return BPF_MATCH;
+
+ UidOwnerValue* allowlistMatch = bpf_uid_owner_map_lookup_elem(&sock_uid);
+ if (allowlistMatch) return allowlistMatch->rule & HAPPY_BOX_MATCH ? BPF_MATCH : BPF_NOMATCH;
+ return BPF_NOMATCH;
+}
+
+DEFINE_BPF_PROG("skfilter/denylist/xtbpf", AID_ROOT, AID_NET_ADMIN, xt_bpf_denylist_prog)
+(struct __sk_buff* skb) {
+ uint32_t sock_uid = bpf_get_socket_uid(skb);
+ UidOwnerValue* denylistMatch = bpf_uid_owner_map_lookup_elem(&sock_uid);
+ if (denylistMatch) return denylistMatch->rule & PENALTY_BOX_MATCH ? BPF_MATCH : BPF_NOMATCH;
+ return BPF_NOMATCH;
+}
+
+DEFINE_BPF_PROG_KVER("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create,
+ KVER(4, 14, 0))
+(struct bpf_sock* sk) {
+ uint64_t gid_uid = bpf_get_current_uid_gid();
+ /*
+ * A given app is guaranteed to have the same app ID in all the profiles in
+ * which it is installed, and install permission is granted to app for all
+ * user at install time so we only check the appId part of a request uid at
+ * run time. See UserHandle#isSameApp for detail.
+ */
+ uint32_t appId = (gid_uid & 0xffffffff) % PER_USER_RANGE;
+ uint8_t* permissions = bpf_uid_permission_map_lookup_elem(&appId);
+ if (!permissions) {
+ // UID not in map. Default to just INTERNET permission.
+ return 1;
+ }
+
+ // A return value of 1 means allow, everything else means deny.
+ return (*permissions & BPF_PERMISSION_INTERNET) == BPF_PERMISSION_INTERNET;
+}
+
+LICENSE("Apache 2.0");
+CRITICAL("netd");
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index 79bb128..abcfbeb 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -19,11 +19,22 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+java_defaults {
+ name: "enable-framework-connectivity-t-targets",
+ enabled: true,
+}
+// The above defaults can be used to disable framework-connectivity t
+// targets while minimizing merge conflicts in the build rules.
+
+
java_sdk_library {
name: "framework-connectivity-tiramisu",
sdk_version: "module_current",
min_sdk_version: "Tiramisu",
- defaults: ["framework-module-defaults"],
+ defaults: [
+ "framework-module-defaults",
+ "enable-framework-connectivity-t-targets",
+ ],
srcs: [
":framework-connectivity-tiramisu-updatable-sources",
],
diff --git a/service/Android.bp b/service/Android.bp
index 76f9153..e376ff7 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -57,12 +57,18 @@
],
srcs: [
"jni/com_android_server_TestNetworkService.cpp",
+ "jni/com_android_server_connectivity_ClatCoordinator.cpp",
"jni/onload.cpp",
],
stl: "libc++_static",
header_libs: [
"libbase_headers",
],
+ static_libs: [
+ "libclat",
+ "libip_checksum",
+ "libnetjniutils",
+ ],
shared_libs: [
"liblog",
"libnativehelper",
@@ -156,3 +162,11 @@
srcs: ["jarjar-rules.txt"],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
+
+// TODO: This filegroup temporary exposes for NetworkStats. It should be
+// removed right after NetworkStats moves into mainline module.
+filegroup {
+ name: "traffic-controller-utils",
+ srcs: ["src/com/android/server/BpfNetMaps.java"],
+ visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
new file mode 100644
index 0000000..a9d7946
--- /dev/null
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/if_tun.h>
+#include <linux/ioctl.h>
+#include <nativehelper/JNIHelp.h>
+#include <net/if.h>
+
+#include <netjniutils/netjniutils.h>
+
+#include "libclat/clatutils.h"
+#include "nativehelper/scoped_utf_chars.h"
+
+// Sync from system/netd/include/netid_client.h
+#define MARK_UNSET 0u
+
+namespace android {
+static void throwIOException(JNIEnv* env, const char* msg, int error) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "%s: %s", msg, strerror(error));
+}
+
+jstring com_android_server_connectivity_ClatCoordinator_selectIpv4Address(JNIEnv* env,
+ jobject clazz,
+ jstring v4addr,
+ jint prefixlen) {
+ ScopedUtfChars address(env, v4addr);
+ in_addr ip;
+ if (inet_pton(AF_INET, address.c_str(), &ip) != 1) {
+ throwIOException(env, "invalid address", EINVAL);
+ return nullptr;
+ }
+
+ // Pick an IPv4 address.
+ // TODO: this picks the address based on other addresses that are assigned to interfaces, but
+ // the address is only actually assigned to an interface once clatd starts up. So we could end
+ // up with two clatd instances with the same IPv4 address.
+ // Stop doing this and instead pick a free one from the kV4Addr pool.
+ in_addr v4 = {net::clat::selectIpv4Address(ip, prefixlen)};
+ if (v4.s_addr == INADDR_NONE) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "No free IPv4 address in %s/%d",
+ address.c_str(), prefixlen);
+ return nullptr;
+ }
+
+ char addrstr[INET_ADDRSTRLEN];
+ if (!inet_ntop(AF_INET, (void*)&v4, addrstr, sizeof(addrstr))) {
+ throwIOException(env, "invalid address", EADDRNOTAVAIL);
+ return nullptr;
+ }
+ return env->NewStringUTF(addrstr);
+}
+
+// Picks a random interface ID that is checksum neutral with the IPv4 address and the NAT64 prefix.
+jstring com_android_server_connectivity_ClatCoordinator_generateIpv6Address(
+ JNIEnv* env, jobject clazz, jstring ifaceStr, jstring v4Str, jstring prefix64Str) {
+ ScopedUtfChars iface(env, ifaceStr);
+ ScopedUtfChars addr4(env, v4Str);
+ ScopedUtfChars prefix64(env, prefix64Str);
+
+ if (iface.c_str() == nullptr) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid null interface name");
+ return nullptr;
+ }
+
+ in_addr v4;
+ if (inet_pton(AF_INET, addr4.c_str(), &v4) != 1) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid clat v4 address %s",
+ addr4.c_str());
+ return nullptr;
+ }
+
+ in6_addr nat64Prefix;
+ if (inet_pton(AF_INET6, prefix64.c_str(), &nat64Prefix) != 1) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid prefix %s", prefix64.c_str());
+ return nullptr;
+ }
+
+ in6_addr v6;
+ if (net::clat::generateIpv6Address(iface.c_str(), v4, nat64Prefix, &v6)) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "Unable to find global source address on %s for %s", iface.c_str(),
+ prefix64.c_str());
+ return nullptr;
+ }
+
+ char addrstr[INET6_ADDRSTRLEN];
+ if (!inet_ntop(AF_INET6, (void*)&v6, addrstr, sizeof(addrstr))) {
+ throwIOException(env, "invalid address", EADDRNOTAVAIL);
+ return nullptr;
+ }
+ return env->NewStringUTF(addrstr);
+}
+
+static jint com_android_server_connectivity_ClatCoordinator_createTunInterface(JNIEnv* env,
+ jobject clazz,
+ jstring tuniface) {
+ ScopedUtfChars v4interface(env, tuniface);
+
+ // open the tun device in non blocking mode as required by clatd
+ jint fd = open("/dev/net/tun", O_RDWR | O_NONBLOCK | O_CLOEXEC);
+ if (fd == -1) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "open tun device failed (%s)",
+ strerror(errno));
+ return -1;
+ }
+
+ struct ifreq ifr = {
+ .ifr_flags = IFF_TUN,
+ };
+ strlcpy(ifr.ifr_name, v4interface.c_str(), sizeof(ifr.ifr_name));
+
+ if (ioctl(fd, TUNSETIFF, &ifr, sizeof(ifr))) {
+ close(fd);
+ jniThrowExceptionFmt(env, "java/io/IOException", "ioctl(TUNSETIFF) failed (%s)",
+ strerror(errno));
+ return -1;
+ }
+
+ return fd;
+}
+
+static jint com_android_server_connectivity_ClatCoordinator_detectMtu(JNIEnv* env, jobject clazz,
+ jstring platSubnet,
+ jint plat_suffix, jint mark) {
+ ScopedUtfChars platSubnetStr(env, platSubnet);
+
+ in6_addr plat_subnet;
+ if (inet_pton(AF_INET6, platSubnetStr.c_str(), &plat_subnet) != 1) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid plat prefix address %s",
+ platSubnetStr.c_str());
+ return -1;
+ }
+
+ int ret = net::clat::detect_mtu(&plat_subnet, plat_suffix, mark);
+ if (ret < 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "detect mtu failed: %s", strerror(-ret));
+ return -1;
+ }
+
+ return ret;
+}
+
+static jint com_android_server_connectivity_ClatCoordinator_openPacketSocket(JNIEnv* env,
+ jobject clazz) {
+ // Will eventually be bound to htons(ETH_P_IPV6) protocol,
+ // but only after appropriate bpf filter is attached.
+ int sock = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (sock < 0) {
+ throwIOException(env, "packet socket failed", errno);
+ return -1;
+ }
+ return sock;
+}
+
+static jint com_android_server_connectivity_ClatCoordinator_openRawSocket6(JNIEnv* env,
+ jobject clazz,
+ jint mark) {
+ int sock = socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_RAW);
+ if (sock < 0) {
+ throwIOException(env, "raw socket failed", errno);
+ return -1;
+ }
+
+ // TODO: check the mark validation
+ if (mark != MARK_UNSET && setsockopt(sock, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) < 0) {
+ throwIOException(env, "could not set mark on raw socket", errno);
+ close(sock);
+ return -1;
+ }
+
+ return sock;
+}
+
+static void com_android_server_connectivity_ClatCoordinator_addAnycastSetsockopt(
+ JNIEnv* env, jobject clazz, jobject javaFd, jstring addr6, jint ifindex) {
+ int sock = netjniutils::GetNativeFileDescriptor(env, javaFd);
+ if (sock < 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
+ return;
+ }
+
+ ScopedUtfChars addrStr(env, addr6);
+
+ in6_addr addr;
+ if (inet_pton(AF_INET6, addrStr.c_str(), &addr) != 1) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid IPv6 address %s",
+ addrStr.c_str());
+ return;
+ }
+
+ struct ipv6_mreq mreq = {addr, ifindex};
+ int ret = setsockopt(sock, SOL_IPV6, IPV6_JOIN_ANYCAST, &mreq, sizeof(mreq));
+ if (ret) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "setsockopt IPV6_JOIN_ANYCAST failed: %s",
+ strerror(errno));
+ return;
+ }
+}
+
+static void com_android_server_connectivity_ClatCoordinator_configurePacketSocket(
+ JNIEnv* env, jobject clazz, jobject javaFd, jstring addr6, jint ifindex) {
+ ScopedUtfChars addrStr(env, addr6);
+
+ int sock = netjniutils::GetNativeFileDescriptor(env, javaFd);
+ if (sock < 0) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
+ return;
+ }
+
+ in6_addr addr;
+ if (inet_pton(AF_INET6, addrStr.c_str(), &addr) != 1) {
+ jniThrowExceptionFmt(env, "java/io/IOException", "Invalid IPv6 address %s",
+ addrStr.c_str());
+ return;
+ }
+
+ int ret = net::clat::configure_packet_socket(sock, &addr, ifindex);
+ if (ret < 0) {
+ throwIOException(env, "configure packet socket failed", -ret);
+ return;
+ }
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ {"native_selectIpv4Address", "(Ljava/lang/String;I)Ljava/lang/String;",
+ (void*)com_android_server_connectivity_ClatCoordinator_selectIpv4Address},
+ {"native_generateIpv6Address",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
+ (void*)com_android_server_connectivity_ClatCoordinator_generateIpv6Address},
+ {"native_createTunInterface", "(Ljava/lang/String;)I",
+ (void*)com_android_server_connectivity_ClatCoordinator_createTunInterface},
+ {"native_detectMtu", "(Ljava/lang/String;II)I",
+ (void*)com_android_server_connectivity_ClatCoordinator_detectMtu},
+ {"native_openPacketSocket", "()I",
+ (void*)com_android_server_connectivity_ClatCoordinator_openPacketSocket},
+ {"native_openRawSocket6", "(I)I",
+ (void*)com_android_server_connectivity_ClatCoordinator_openRawSocket6},
+ {"native_addAnycastSetsockopt", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V",
+ (void*)com_android_server_connectivity_ClatCoordinator_addAnycastSetsockopt},
+ {"native_configurePacketSocket", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V",
+ (void*)com_android_server_connectivity_ClatCoordinator_configurePacketSocket},
+};
+
+int register_android_server_connectivity_ClatCoordinator(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/server/connectivity/ClatCoordinator",
+ gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/service/jni/onload.cpp b/service/jni/onload.cpp
index 0012879..04d9671 100644
--- a/service/jni/onload.cpp
+++ b/service/jni/onload.cpp
@@ -20,6 +20,7 @@
namespace android {
int register_android_server_TestNetworkService(JNIEnv* env);
+int register_android_server_connectivity_ClatCoordinator(JNIEnv* env);
extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
JNIEnv *env;
@@ -32,6 +33,10 @@
return JNI_ERR;
}
+ if (register_android_server_connectivity_ClatCoordinator(env) < 0) {
+ return JNI_ERR;
+ }
+
return JNI_VERSION_1_6;
}
diff --git a/service/native/libs/libclat/Android.bp b/service/native/libs/libclat/Android.bp
new file mode 100644
index 0000000..8540787
--- /dev/null
+++ b/service/native/libs/libclat/Android.bp
@@ -0,0 +1,49 @@
+// Copyright (C) 2022 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+ name: "libclat",
+ defaults: ["netd_defaults"],
+ srcs: ["clatutils.cpp"],
+ stl: "libc++_static",
+ static_libs: ["libip_checksum"],
+ shared_libs: ["liblog"],
+ export_include_dirs: ["include"],
+ min_sdk_version: "30",
+ apex_available: ["com.android.tethering"],
+}
+
+cc_test {
+ name: "libclat_test",
+ defaults: ["netd_defaults"],
+ test_suites: ["device-tests"],
+ srcs: [
+ "clatutils_test.cpp",
+ ],
+ static_libs: [
+ "libbase",
+ "libclat",
+ "libip_checksum",
+ "libnetd_test_tun_interface",
+ ],
+ shared_libs: [
+ "liblog",
+ "libnetutils",
+ ],
+ require_root: true,
+}
\ No newline at end of file
diff --git a/service/native/libs/libclat/clatutils.cpp b/service/native/libs/libclat/clatutils.cpp
new file mode 100644
index 0000000..4a125ba
--- /dev/null
+++ b/service/native/libs/libclat/clatutils.cpp
@@ -0,0 +1,268 @@
+// Copyright (C) 2022 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.
+
+#define LOG_TAG "clatutils"
+
+#include "libclat/clatutils.h"
+
+#include <errno.h>
+#include <linux/filter.h>
+#include <linux/if_packet.h>
+#include <linux/if_tun.h>
+#include <log/log.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+extern "C" {
+#include "checksum.h"
+}
+
+// Sync from external/android-clat/clatd.h
+#define MAXMTU 65536
+#define PACKETLEN (MAXMTU + sizeof(struct tun_pi))
+
+// Sync from system/netd/include/netid_client.h.
+#define MARK_UNSET 0u
+
+namespace android {
+namespace net {
+namespace clat {
+
+bool isIpv4AddressFree(in_addr_t addr) {
+ int s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (s == -1) {
+ return 0;
+ }
+
+ // Attempt to connect to the address. If the connection succeeds and getsockname returns the
+ // same then the address is already assigned to the system and we can't use it.
+ struct sockaddr_in sin = {
+ .sin_family = AF_INET,
+ .sin_port = htons(53),
+ .sin_addr = {addr},
+ };
+ socklen_t len = sizeof(sin);
+ bool inuse = connect(s, (struct sockaddr*)&sin, sizeof(sin)) == 0 &&
+ getsockname(s, (struct sockaddr*)&sin, &len) == 0 && (size_t)len >= sizeof(sin) &&
+ sin.sin_addr.s_addr == addr;
+
+ close(s);
+ return !inuse;
+}
+
+// Picks a free IPv4 address, starting from ip and trying all addresses in the prefix in order.
+// ip - the IP address from the configuration file
+// prefixlen - the length of the prefix from which addresses may be selected.
+// returns: the IPv4 address, or INADDR_NONE if no addresses were available
+in_addr_t selectIpv4Address(const in_addr ip, int16_t prefixlen) {
+ return selectIpv4AddressInternal(ip, prefixlen, isIpv4AddressFree);
+}
+
+// Only allow testing to use this function directly. Otherwise call selectIpv4Address(ip, pfxlen)
+// which has applied valid isIpv4AddressFree function pointer.
+in_addr_t selectIpv4AddressInternal(const in_addr ip, int16_t prefixlen,
+ isIpv4AddrFreeFn isIpv4AddressFreeFunc) {
+ // Impossible! Only test allows to apply fn.
+ if (isIpv4AddressFreeFunc == nullptr) {
+ return INADDR_NONE;
+ }
+
+ // Don't accept prefixes that are too large because we scan addresses one by one.
+ if (prefixlen < 16 || prefixlen > 32) {
+ return INADDR_NONE;
+ }
+
+ // All these are in host byte order.
+ in_addr_t mask = 0xffffffff >> (32 - prefixlen) << (32 - prefixlen);
+ in_addr_t ipv4 = ntohl(ip.s_addr);
+ in_addr_t first_ipv4 = ipv4;
+ in_addr_t prefix = ipv4 & mask;
+
+ // Pick the first IPv4 address in the pool, wrapping around if necessary.
+ // So, for example, 192.0.0.4 -> 192.0.0.5 -> 192.0.0.6 -> 192.0.0.7 -> 192.0.0.0.
+ do {
+ if (isIpv4AddressFreeFunc(htonl(ipv4))) {
+ return htonl(ipv4);
+ }
+ ipv4 = prefix | ((ipv4 + 1) & ~mask);
+ } while (ipv4 != first_ipv4);
+
+ return INADDR_NONE;
+}
+
+// Alters the bits in the IPv6 address to make them checksum neutral with v4 and nat64Prefix.
+void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Prefix) {
+ // Fill last 8 bytes of IPv6 address with random bits.
+ arc4random_buf(&v6->s6_addr[8], 8);
+
+ // Make the IID checksum-neutral. That is, make it so that:
+ // checksum(Local IPv4 | Remote IPv4) = checksum(Local IPv6 | Remote IPv6)
+ // in other words (because remote IPv6 = NAT64 prefix | Remote IPv4):
+ // checksum(Local IPv4) = checksum(Local IPv6 | NAT64 prefix)
+ // Do this by adjusting the two bytes in the middle of the IID.
+
+ uint16_t middlebytes = (v6->s6_addr[11] << 8) + v6->s6_addr[12];
+
+ uint32_t c1 = ip_checksum_add(0, &v4, sizeof(v4));
+ uint32_t c2 = ip_checksum_add(0, &nat64Prefix, sizeof(nat64Prefix)) +
+ ip_checksum_add(0, v6, sizeof(*v6));
+
+ uint16_t delta = ip_checksum_adjust(middlebytes, c1, c2);
+ v6->s6_addr[11] = delta >> 8;
+ v6->s6_addr[12] = delta & 0xff;
+}
+
+// Picks a random interface ID that is checksum neutral with the IPv4 address and the NAT64 prefix.
+int generateIpv6Address(const char* iface, const in_addr v4, const in6_addr& nat64Prefix,
+ in6_addr* v6) {
+ int s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (s == -1) return -errno;
+
+ if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, iface, strlen(iface) + 1) == -1) {
+ close(s);
+ return -errno;
+ }
+
+ sockaddr_in6 sin6 = {.sin6_family = AF_INET6, .sin6_addr = nat64Prefix};
+ if (connect(s, reinterpret_cast<struct sockaddr*>(&sin6), sizeof(sin6)) == -1) {
+ close(s);
+ return -errno;
+ }
+
+ socklen_t len = sizeof(sin6);
+ if (getsockname(s, reinterpret_cast<struct sockaddr*>(&sin6), &len) == -1) {
+ close(s);
+ return -errno;
+ }
+
+ *v6 = sin6.sin6_addr;
+
+ if (IN6_IS_ADDR_UNSPECIFIED(v6) || IN6_IS_ADDR_LOOPBACK(v6) || IN6_IS_ADDR_LINKLOCAL(v6) ||
+ IN6_IS_ADDR_SITELOCAL(v6) || IN6_IS_ADDR_ULA(v6)) {
+ close(s);
+ return -ENETUNREACH;
+ }
+
+ makeChecksumNeutral(v6, v4, nat64Prefix);
+ close(s);
+
+ return 0;
+}
+
+int detect_mtu(const struct in6_addr* plat_subnet, uint32_t plat_suffix, uint32_t mark) {
+ // Create an IPv6 UDP socket.
+ int s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+ if (s < 0) {
+ int ret = errno;
+ ALOGE("socket(AF_INET6, SOCK_DGRAM, 0) failed: %s", strerror(errno));
+ return -ret;
+ }
+
+ // Socket's mark affects routing decisions (network selection)
+ if ((mark != MARK_UNSET) && setsockopt(s, SOL_SOCKET, SO_MARK, &mark, sizeof(mark))) {
+ int ret = errno;
+ ALOGE("setsockopt(SOL_SOCKET, SO_MARK) failed: %s", strerror(errno));
+ close(s);
+ return -ret;
+ }
+
+ // Try to connect udp socket to plat_subnet(96 bits):plat_suffix(32 bits)
+ struct sockaddr_in6 dst = {
+ .sin6_family = AF_INET6,
+ .sin6_addr = *plat_subnet,
+ };
+ dst.sin6_addr.s6_addr32[3] = plat_suffix;
+ if (connect(s, (struct sockaddr*)&dst, sizeof(dst))) {
+ int ret = errno;
+ ALOGE("connect() failed: %s", strerror(errno));
+ close(s);
+ return -ret;
+ }
+
+ // Fetch the socket's IPv6 mtu - this is effectively fetching mtu from routing table
+ int mtu;
+ socklen_t sz_mtu = sizeof(mtu);
+ if (getsockopt(s, SOL_IPV6, IPV6_MTU, &mtu, &sz_mtu)) {
+ int ret = errno;
+ ALOGE("getsockopt(SOL_IPV6, IPV6_MTU) failed: %s", strerror(errno));
+ close(s);
+ return -ret;
+ }
+ if (sz_mtu != sizeof(mtu)) {
+ ALOGE("getsockopt(SOL_IPV6, IPV6_MTU) returned unexpected size: %d", sz_mtu);
+ close(s);
+ return -EFAULT;
+ }
+ close(s);
+
+ return mtu;
+}
+
+/* function: configure_packet_socket
+ * Binds the packet socket and attaches the receive filter to it.
+ * sock - the socket to configure
+ * addr - the IP address to filter
+ * ifindex - index of interface to add the filter to
+ * returns: 0 on success, -errno on failure
+ */
+int configure_packet_socket(int sock, in6_addr* addr, int ifindex) {
+ uint32_t* ipv6 = addr->s6_addr32;
+
+ // clang-format off
+ struct sock_filter filter_code[] = {
+ // Load the first four bytes of the IPv6 destination address (starts 24 bytes in).
+ // Compare it against the first four bytes of our IPv6 address, in host byte order (BPF loads
+ // are always in host byte order). If it matches, continue with next instruction (JMP 0). If it
+ // doesn't match, jump ahead to statement that returns 0 (ignore packet). Repeat for the other
+ // three words of the IPv6 address, and if they all match, return PACKETLEN (accept packet).
+ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 24),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(ipv6[0]), 0, 7),
+ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 28),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(ipv6[1]), 0, 5),
+ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 32),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(ipv6[2]), 0, 3),
+ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 36),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(ipv6[3]), 0, 1),
+ BPF_STMT(BPF_RET | BPF_K, PACKETLEN),
+ BPF_STMT(BPF_RET | BPF_K, 0),
+ };
+ // clang-format on
+ struct sock_fprog filter = {sizeof(filter_code) / sizeof(filter_code[0]), filter_code};
+
+ if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))) {
+ int res = errno;
+ ALOGE("attach packet filter failed: %s", strerror(errno));
+ return -res;
+ }
+
+ struct sockaddr_ll sll = {
+ .sll_family = AF_PACKET,
+ .sll_protocol = htons(ETH_P_IPV6),
+ .sll_ifindex = ifindex,
+ .sll_pkttype =
+ PACKET_OTHERHOST, // The 464xlat IPv6 address is not assigned to the kernel.
+ };
+ if (bind(sock, (struct sockaddr*)&sll, sizeof(sll))) {
+ int res = errno;
+ ALOGE("binding packet socket: %s", strerror(errno));
+ return -res;
+ }
+
+ return 0;
+}
+
+} // namespace clat
+} // namespace net
+} // namespace android
diff --git a/service/native/libs/libclat/clatutils_test.cpp b/service/native/libs/libclat/clatutils_test.cpp
new file mode 100644
index 0000000..4153e19
--- /dev/null
+++ b/service/native/libs/libclat/clatutils_test.cpp
@@ -0,0 +1,187 @@
+// Copyright (C) 2022 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.
+
+#include "libclat/clatutils.h"
+
+#include <android-base/stringprintf.h>
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+#include <linux/if_packet.h>
+#include <linux/if_tun.h>
+#include "tun_interface.h"
+
+extern "C" {
+#include "checksum.h"
+}
+
+// Default translation parameters.
+static const char kIPv4LocalAddr[] = "192.0.0.4";
+
+namespace android {
+namespace net {
+namespace clat {
+
+using android::net::TunInterface;
+using base::StringPrintf;
+
+class ClatUtils : public ::testing::Test {};
+
+// Mock functions for isIpv4AddressFree.
+bool neverFree(in_addr_t /* addr */) {
+ return 0;
+}
+bool alwaysFree(in_addr_t /* addr */) {
+ return 1;
+}
+bool only2Free(in_addr_t addr) {
+ return (ntohl(addr) & 0xff) == 2;
+}
+bool over6Free(in_addr_t addr) {
+ return (ntohl(addr) & 0xff) >= 6;
+}
+bool only10Free(in_addr_t addr) {
+ return (ntohl(addr) & 0xff) == 10;
+}
+
+// Apply mocked isIpv4AddressFree function for selectIpv4Address test.
+in_addr_t selectIpv4Address(const in_addr ip, int16_t prefixlen,
+ isIpv4AddrFreeFn fn /* mocked function */) {
+ // Call internal function to replace isIpv4AddressFreeFn for testing.
+ return selectIpv4AddressInternal(ip, prefixlen, fn);
+}
+
+TEST_F(ClatUtils, SelectIpv4Address) {
+ struct in_addr addr;
+
+ inet_pton(AF_INET, kIPv4LocalAddr, &addr);
+
+ // If no addresses are free, return INADDR_NONE.
+ EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 29, neverFree));
+ EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 16, neverFree));
+
+ // If the configured address is free, pick that. But a prefix that's too big is invalid.
+ EXPECT_EQ(inet_addr(kIPv4LocalAddr), selectIpv4Address(addr, 29, alwaysFree));
+ EXPECT_EQ(inet_addr(kIPv4LocalAddr), selectIpv4Address(addr, 20, alwaysFree));
+ EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 15, alwaysFree));
+
+ // A prefix length of 32 works, but anything above it is invalid.
+ EXPECT_EQ(inet_addr(kIPv4LocalAddr), selectIpv4Address(addr, 32, alwaysFree));
+ EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 33, alwaysFree));
+
+ // If another address is free, pick it.
+ EXPECT_EQ(inet_addr("192.0.0.6"), selectIpv4Address(addr, 29, over6Free));
+
+ // Check that we wrap around to addresses that are lower than the first address.
+ EXPECT_EQ(inet_addr("192.0.0.2"), selectIpv4Address(addr, 29, only2Free));
+ EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 30, only2Free));
+
+ // If a free address exists outside the prefix, we don't pick it.
+ EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 29, only10Free));
+ EXPECT_EQ(inet_addr("192.0.0.10"), selectIpv4Address(addr, 24, only10Free));
+
+ // Now try using the real function which sees if IP addresses are free using bind().
+ // Assume that the machine running the test has the address 127.0.0.1, but not 8.8.8.8.
+ addr.s_addr = inet_addr("8.8.8.8");
+ EXPECT_EQ(inet_addr("8.8.8.8"), selectIpv4Address(addr, 29));
+
+ addr.s_addr = inet_addr("127.0.0.1");
+ EXPECT_EQ(inet_addr("127.0.0.2"), selectIpv4Address(addr, 29));
+}
+
+TEST_F(ClatUtils, MakeChecksumNeutral) {
+ // We can't test generateIPv6Address here since it requires manipulating routing, which we can't
+ // do without talking to the real netd on the system.
+ uint32_t rand = arc4random_uniform(0xffffffff);
+ uint16_t rand1 = rand & 0xffff;
+ uint16_t rand2 = (rand >> 16) & 0xffff;
+ std::string v6PrefixStr = StringPrintf("2001:db8:%x:%x", rand1, rand2);
+ std::string v6InterfaceAddrStr = StringPrintf("%s::%x:%x", v6PrefixStr.c_str(), rand2, rand1);
+ std::string nat64PrefixStr = StringPrintf("2001:db8:%x:%x::", rand2, rand1);
+
+ in_addr v4 = {inet_addr(kIPv4LocalAddr)};
+ in6_addr v6InterfaceAddr;
+ ASSERT_TRUE(inet_pton(AF_INET6, v6InterfaceAddrStr.c_str(), &v6InterfaceAddr));
+ in6_addr nat64Prefix;
+ ASSERT_TRUE(inet_pton(AF_INET6, nat64PrefixStr.c_str(), &nat64Prefix));
+
+ // Generate a boatload of random IIDs.
+ int onebits = 0;
+ uint64_t prev_iid = 0;
+ for (int i = 0; i < 100000; i++) {
+ in6_addr v6 = v6InterfaceAddr;
+ makeChecksumNeutral(&v6, v4, nat64Prefix);
+
+ // Check the generated IP address is in the same prefix as the interface IPv6 address.
+ EXPECT_EQ(0, memcmp(&v6, &v6InterfaceAddr, 8));
+
+ // Check that consecutive IIDs are not the same.
+ uint64_t iid = *(uint64_t*)(&v6.s6_addr[8]);
+ ASSERT_TRUE(iid != prev_iid)
+ << "Two consecutive random IIDs are the same: " << std::showbase << std::hex << iid
+ << "\n";
+ prev_iid = iid;
+
+ // Check that the IID is checksum-neutral with the NAT64 prefix and the
+ // local prefix.
+ uint16_t c1 = ip_checksum_finish(ip_checksum_add(0, &v4, sizeof(v4)));
+ uint16_t c2 = ip_checksum_finish(ip_checksum_add(0, &nat64Prefix, sizeof(nat64Prefix)) +
+ ip_checksum_add(0, &v6, sizeof(v6)));
+
+ if (c1 != c2) {
+ char v6Str[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, &v6, v6Str, sizeof(v6Str));
+ FAIL() << "Bad IID: " << v6Str << " not checksum-neutral with " << kIPv4LocalAddr
+ << " and " << nat64PrefixStr.c_str() << std::showbase << std::hex
+ << "\n IPv4 checksum: " << c1 << "\n IPv6 checksum: " << c2 << "\n";
+ }
+
+ // Check that IIDs are roughly random and use all the bits by counting the
+ // total number of bits set to 1 in a random sample of 100000 generated IIDs.
+ onebits += __builtin_popcountll(*(uint64_t*)&iid);
+ }
+ EXPECT_LE(3190000, onebits);
+ EXPECT_GE(3210000, onebits);
+}
+
+TEST_F(ClatUtils, DetectMtu) {
+ // ::1 with bottom 32 bits set to 1 is still ::1 which routes via lo with mtu of 64KiB
+ ASSERT_EQ(detect_mtu(&in6addr_loopback, htonl(1), 0 /*MARK_UNSET*/), 65536);
+}
+
+TEST_F(ClatUtils, ConfigurePacketSocket) {
+ // Create an interface for configure_packet_socket to attach socket filter to.
+ TunInterface v6Iface;
+ ASSERT_EQ(0, v6Iface.init());
+
+ int s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IPV6));
+ EXPECT_LE(0, s);
+ struct in6_addr addr6;
+ EXPECT_EQ(1, inet_pton(AF_INET6, "2001:db8::f00", &addr6));
+ EXPECT_EQ(0, configure_packet_socket(s, &addr6, v6Iface.ifindex()));
+
+ // Check that the packet socket is bound to the interface. We can't check the socket filter
+ // because there is no way to fetch it from the kernel.
+ sockaddr_ll sll;
+ socklen_t len = sizeof(sll);
+ ASSERT_EQ(0, getsockname(s, reinterpret_cast<sockaddr*>(&sll), &len));
+ EXPECT_EQ(htons(ETH_P_IPV6), sll.sll_protocol);
+ EXPECT_EQ(sll.sll_ifindex, v6Iface.ifindex());
+
+ close(s);
+ v6Iface.destroy();
+}
+
+} // namespace clat
+} // namespace net
+} // namespace android
diff --git a/service/native/libs/libclat/include/libclat/clatutils.h b/service/native/libs/libclat/include/libclat/clatutils.h
new file mode 100644
index 0000000..812c86e
--- /dev/null
+++ b/service/native/libs/libclat/include/libclat/clatutils.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2022 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.
+
+#pragma once
+#include <netinet/in.h>
+#include <netinet/in6.h>
+
+namespace android {
+namespace net {
+namespace clat {
+
+bool isIpv4AddressFree(in_addr_t addr);
+in_addr_t selectIpv4Address(const in_addr ip, int16_t prefixlen);
+void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Prefix);
+int generateIpv6Address(const char* iface, const in_addr v4, const in6_addr& nat64Prefix,
+ in6_addr* v6);
+int detect_mtu(const struct in6_addr* plat_subnet, uint32_t plat_suffix, uint32_t mark);
+int configure_packet_socket(int sock, in6_addr* addr, int ifindex);
+
+// For testing
+typedef bool (*isIpv4AddrFreeFn)(in_addr_t);
+in_addr_t selectIpv4AddressInternal(const in_addr ip, int16_t prefixlen, isIpv4AddrFreeFn fn);
+
+} // namespace clat
+} // namespace net
+} // namespace android
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
new file mode 100644
index 0000000..bc63eef
--- /dev/null
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2022 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.server;
+
+import android.os.ServiceSpecificException;
+import android.system.Os;
+import android.util.Log;
+
+/**
+ * BpfNetMaps is responsible for providing traffic controller relevant functionality.
+ *
+ * {@hide}
+ */
+public class BpfNetMaps {
+ private static final String TAG = "BpfNetMaps";
+
+ /**
+ * Add naughty app bandwidth rule for specific app
+ *
+ * @param uid uid of target app
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void addNaughtyApp(final int uid) {
+ final int err = native_addNaughtyApp(uid);
+ if (err != 0) {
+ throw new ServiceSpecificException(-err, "Unable to add naughty app: "
+ + Os.strerror(-err));
+ }
+ }
+
+ /**
+ * Remove naughty app bandwidth rule for specific app
+ *
+ * @param uid uid of target app
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void removeNaughtyApp(final int uid) {
+ final int err = native_removeNaughtyApp(uid);
+ if (err != 0) {
+ throw new ServiceSpecificException(-err, "Unable to remove naughty app: "
+ + Os.strerror(-err));
+ }
+ }
+
+ /**
+ * Add nice app bandwidth rule for specific app
+ *
+ * @param uid uid of target app
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void addNiceApp(final int uid) {
+ final int err = native_addNiceApp(uid);
+ if (err != 0) {
+ throw new ServiceSpecificException(-err, "Unable to add nice app: "
+ + Os.strerror(-err));
+ }
+ }
+
+ /**
+ * Remove nice app bandwidth rule for specific app
+ *
+ * @param uid uid of target app
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void removeNiceApp(final int uid) {
+ final int err = native_removeNiceApp(uid);
+ if (err != 0) {
+ throw new ServiceSpecificException(-err, "Unable to remove nice app: "
+ + Os.strerror(-err));
+ }
+ }
+
+ /**
+ * Set target firewall child chain
+ *
+ * @param childChain target chain to enable
+ * @param enable whether to enable or disable child chain.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void setChildChain(final int childChain, final boolean enable) {
+ final int err = native_setChildChain(childChain, enable);
+ if (err != 0) {
+ throw new ServiceSpecificException(-err, "Unable to set child chain: "
+ + Os.strerror(-err));
+ }
+ }
+
+ /**
+ * Replaces the contents of the specified UID-based firewall chain.
+ *
+ * The chain may be an allowlist chain or a denylist chain. A denylist chain contains DROP
+ * rules for the specified UIDs and a RETURN rule at the end. An allowlist chain contains RETURN
+ * rules for the system UID range (0 to {@code UID_APP} - 1), RETURN rules for for the specified
+ * UIDs, and a DROP rule at the end. The chain will be created if it does not exist.
+ *
+ * @param chainName The name of the chain to replace.
+ * @param isAllowlist Whether this is an allowlist or denylist chain.
+ * @param uids The list of UIDs to allow/deny.
+ * @return true if the chain was successfully replaced, false otherwise.
+ */
+ public int replaceUidChain(final String chainName, final boolean isAllowlist,
+ final int[] uids) {
+ final int err = native_replaceUidChain(chainName, isAllowlist, uids);
+ if (err != 0) {
+ Log.e(TAG, "replaceUidChain failed: " + Os.strerror(-err));
+ }
+ return -err;
+ }
+
+ /**
+ * Set firewall rule for uid
+ *
+ * @param childChain target chain
+ * @param uid uid to allow/deny
+ * @param firewallRule either FIREWALL_RULE_ALLOW or FIREWALL_RULE_DENY
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void setUidRule(final int childChain, final int uid,
+ final int firewallRule) {
+ final int err = native_setUidRule(childChain, uid, firewallRule);
+ if (err != 0) {
+ throw new ServiceSpecificException(-err, "Unable to set uid rule: "
+ + Os.strerror(-err));
+ }
+ }
+
+ /**
+ * Add ingress interface filtering rules to a list of UIDs
+ *
+ * For a given uid, once a filtering rule is added, the kernel will only allow packets from the
+ * allowed interface and loopback to be sent to the list of UIDs.
+ *
+ * Calling this method on one or more UIDs with an existing filtering rule but a different
+ * interface name will result in the filtering rule being updated to allow the new interface
+ * instead. Otherwise calling this method will not affect existing rules set on other UIDs.
+ *
+ * @param ifName the name of the interface on which the filtering rules will allow packets to
+ be received.
+ * @param uids an array of UIDs which the filtering rules will be set
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void addUidInterfaceRules(final String ifName, final int[] uids) {
+ final int err = native_addUidInterfaceRules(ifName, uids);
+ if (err != 0) {
+ throw new ServiceSpecificException(-err, "Unable to add uid interface rules: "
+ + Os.strerror(-err));
+ }
+ }
+
+ /**
+ * Remove ingress interface filtering rules from a list of UIDs
+ *
+ * Clear the ingress interface filtering rules from the list of UIDs which were previously set
+ * by addUidInterfaceRules(). Ignore any uid which does not have filtering rule.
+ *
+ * @param uids an array of UIDs from which the filtering rules will be removed
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void removeUidInterfaceRules(final int[] uids) {
+ final int err = native_removeUidInterfaceRules(uids);
+ if (err != 0) {
+ throw new ServiceSpecificException(-err, "Unable to remove uid interface rules: "
+ + Os.strerror(-err));
+ }
+ }
+
+ /**
+ * Request netd to change the current active network stats map.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void swapActiveStatsMap() {
+ final int err = native_swapActiveStatsMap();
+ if (err != 0) {
+ throw new ServiceSpecificException(-err, "Unable to swap active stats map: "
+ + Os.strerror(-err));
+ }
+ }
+
+ /**
+ * Assigns android.permission.INTERNET and/or android.permission.UPDATE_DEVICE_STATS to the uids
+ * specified. Or remove all permissions from the uids.
+ *
+ * @param permission The permission to grant, it could be either PERMISSION_INTERNET and/or
+ * PERMISSION_UPDATE_DEVICE_STATS. If the permission is NO_PERMISSIONS, then
+ * revoke all permissions for the uids.
+ * @param uids uid of users to grant permission
+ */
+ public void setNetPermForUids(final int permission, final int[] uids) {
+ native_setPermissionForUids(permission, uids);
+ }
+
+ /**
+ * Set counter set for uid
+ *
+ * @param counterSet either SET_DEFAULT or SET_FOREGROUND
+ * @param uid uid to foreground/background
+ */
+ public int setCounterSet(final int counterSet, final int uid) {
+ final int err = native_setCounterSet(counterSet, uid);
+ if (err != 0) {
+ Log.e(TAG, "setCounterSet failed: " + Os.strerror(-err));
+ }
+ return -err;
+ }
+
+ /**
+ * Reset Uid stats
+ * @param tag default 0
+ * @param uid given uid to be clear
+ */
+ public int deleteTagData(final int tag, final int uid) {
+ final int err = native_deleteTagData(tag, uid);
+ if (err != 0) {
+ Log.e(TAG, "deleteTagData failed: " + Os.strerror(-err));
+ }
+ return -err;
+ }
+
+ private native int native_addNaughtyApp(int uid);
+ private native int native_removeNaughtyApp(int uid);
+ private native int native_addNiceApp(int uid);
+ private native int native_removeNiceApp(int uid);
+ private native int native_setChildChain(int childChain, boolean enable);
+ private native int native_replaceUidChain(String name, boolean isAllowlist, int[] uids);
+ private native int native_setUidRule(int childChain, int uid, int firewallRule);
+ private native int native_addUidInterfaceRules(String ifName, int[] uids);
+ private native int native_removeUidInterfaceRules(int[] uids);
+ private native int native_swapActiveStatsMap();
+ private native void native_setPermissionForUids(int permission, int[] uids);
+ private native int native_setCounterSet(int counterSet, int uid);
+ private native int native_deleteTagData(int tag, int uid);
+}
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
new file mode 100644
index 0000000..4d243c4
--- /dev/null
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2022 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.server.connectivity;
+
+import static android.net.INetd.IF_STATE_UP;
+import static android.net.INetd.PERMISSION_SYSTEM;
+
+import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.INetd;
+import android.net.InterfaceConfigurationParcel;
+import android.net.IpPrefix;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.InterfaceParams;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+
+/**
+ * This coordinator is responsible for providing clat relevant functionality.
+ *
+ * {@hide}
+ */
+public class ClatCoordinator {
+ private static final String TAG = ClatCoordinator.class.getSimpleName();
+
+ // Sync from external/android-clat/clatd.c
+ // 40 bytes IPv6 header - 20 bytes IPv4 header + 8 bytes fragment header.
+ @VisibleForTesting
+ static final int MTU_DELTA = 28;
+ @VisibleForTesting
+ static final int CLAT_MAX_MTU = 65536;
+
+ // This must match the interface prefix in clatd.c.
+ private static final String CLAT_PREFIX = "v4-";
+
+ // For historical reasons, start with 192.0.0.4, and after that, use all subsequent addresses
+ // in 192.0.0.0/29 (RFC 7335).
+ @VisibleForTesting
+ static final String INIT_V4ADDR_STRING = "192.0.0.4";
+ @VisibleForTesting
+ static final int INIT_V4ADDR_PREFIX_LEN = 29;
+ private static final InetAddress GOOGLE_DNS_4 = InetAddress.parseNumericAddress("8.8.8.8");
+
+ private static final int INVALID_IFINDEX = 0;
+ private static final int INVALID_PID = 0;
+
+ @NonNull
+ private final INetd mNetd;
+ @NonNull
+ private final Dependencies mDeps;
+ @Nullable
+ private String mIface = null;
+ private int mPid = INVALID_PID;
+
+ @VisibleForTesting
+ abstract static class Dependencies {
+ /**
+ * Get netd.
+ */
+ @NonNull
+ public abstract INetd getNetd();
+
+ /**
+ * @see ParcelFileDescriptor#adoptFd(int).
+ */
+ @NonNull
+ public ParcelFileDescriptor adoptFd(int fd) {
+ return ParcelFileDescriptor.adoptFd(fd);
+ }
+
+ /**
+ * Get interface index for a given interface.
+ */
+ public int getInterfaceIndex(String ifName) {
+ final InterfaceParams params = InterfaceParams.getByName(ifName);
+ return params != null ? params.index : INVALID_IFINDEX;
+ }
+
+ /**
+ * Create tun interface for a given interface name.
+ */
+ public int createTunInterface(@NonNull String tuniface) throws IOException {
+ return native_createTunInterface(tuniface);
+ }
+
+ /**
+ * Pick an IPv4 address for clat.
+ */
+ @NonNull
+ public String selectIpv4Address(@NonNull String v4addr, int prefixlen)
+ throws IOException {
+ return native_selectIpv4Address(v4addr, prefixlen);
+ }
+
+ /**
+ * Generate a checksum-neutral IID.
+ */
+ @NonNull
+ public String generateIpv6Address(@NonNull String iface, @NonNull String v4,
+ @NonNull String prefix64) throws IOException {
+ return native_generateIpv6Address(iface, v4, prefix64);
+ }
+
+ /**
+ * Detect MTU.
+ */
+ public int detectMtu(@NonNull String platSubnet, int platSuffix, int mark)
+ throws IOException {
+ return native_detectMtu(platSubnet, platSuffix, mark);
+ }
+
+ /**
+ * Open packet socket.
+ */
+ public int openPacketSocket() throws IOException {
+ return native_openPacketSocket();
+ }
+
+ /**
+ * Open IPv6 raw socket and set SO_MARK.
+ */
+ public int openRawSocket6(int mark) throws IOException {
+ return native_openRawSocket6(mark);
+ }
+
+ /**
+ * Add anycast setsockopt.
+ */
+ public void addAnycastSetsockopt(@NonNull FileDescriptor sock, String v6, int ifindex)
+ throws IOException {
+ native_addAnycastSetsockopt(sock, v6, ifindex);
+ }
+
+ /**
+ * Configure packet socket.
+ */
+ public void configurePacketSocket(@NonNull FileDescriptor sock, String v6, int ifindex)
+ throws IOException {
+ native_configurePacketSocket(sock, v6, ifindex);
+ }
+ }
+
+ @VisibleForTesting
+ static int getFwmark(int netId) {
+ // See union Fwmark in system/netd/include/Fwmark.h
+ return (netId & 0xffff)
+ | 0x1 << 16 // protectedFromVpn: true
+ | 0x1 << 17 // explicitlySelected: true
+ | (PERMISSION_SYSTEM & 0x3) << 18;
+ }
+
+ @VisibleForTesting
+ static int adjustMtu(int mtu) {
+ // clamp to minimum ipv6 mtu - this probably cannot ever trigger
+ if (mtu < IPV6_MIN_MTU) mtu = IPV6_MIN_MTU;
+ // clamp to buffer size
+ if (mtu > CLAT_MAX_MTU) mtu = CLAT_MAX_MTU;
+ // decrease by ipv6(40) + ipv6 fragmentation header(8) vs ipv4(20) overhead of 28 bytes
+ mtu -= MTU_DELTA;
+
+ return mtu;
+ }
+
+ public ClatCoordinator(@NonNull Dependencies deps) {
+ mDeps = deps;
+ mNetd = mDeps.getNetd();
+ }
+
+ /**
+ * Start clatd for a given interface and NAT64 prefix.
+ */
+ public String clatStart(final String iface, final int netId,
+ @NonNull final IpPrefix nat64Prefix)
+ throws IOException {
+ if (nat64Prefix.getPrefixLength() != 96) {
+ throw new IOException("Prefix must be 96 bits long: " + nat64Prefix);
+ }
+
+ // [1] Pick an IPv4 address from 192.0.0.4, 192.0.0.5, 192.0.0.6 ..
+ final String v4;
+ try {
+ v4 = mDeps.selectIpv4Address(INIT_V4ADDR_STRING, INIT_V4ADDR_PREFIX_LEN);
+ } catch (IOException e) {
+ throw new IOException("no IPv4 addresses were available for clat: " + e);
+ }
+
+ // [2] Generate a checksum-neutral IID.
+ final String pfx96 = nat64Prefix.getAddress().getHostAddress();
+ final String v6;
+ try {
+ v6 = mDeps.generateIpv6Address(iface, v4, pfx96);
+ } catch (IOException e) {
+ throw new IOException("no IPv6 addresses were available for clat: " + e);
+ }
+
+ // [3] Open, configure and bring up the tun interface.
+ // Create the v4-... tun interface.
+ final String tunIface = CLAT_PREFIX + iface;
+ final ParcelFileDescriptor tunFd;
+ try {
+ tunFd = mDeps.adoptFd(mDeps.createTunInterface(tunIface));
+ } catch (IOException e) {
+ throw new IOException("Create tun interface " + tunIface + " failed: " + e);
+ }
+
+ // disable IPv6 on it - failing to do so is not a critical error
+ try {
+ mNetd.interfaceSetEnableIPv6(tunIface, false /* enabled */);
+ } catch (RemoteException | ServiceSpecificException e) {
+ Log.e(TAG, "Disable IPv6 on " + tunIface + " failed: " + e);
+ }
+
+ // Detect ipv4 mtu.
+ final Integer fwmark = getFwmark(netId);
+ final int detectedMtu = mDeps.detectMtu(pfx96,
+ ByteBuffer.wrap(GOOGLE_DNS_4.getAddress()).getInt(), fwmark);
+ final int mtu = adjustMtu(detectedMtu);
+ Log.i(TAG, "ipv4 mtu is " + mtu);
+
+ // TODO: add setIptablesDropRule
+
+ // Config tun interface mtu, address and bring up.
+ try {
+ mNetd.interfaceSetMtu(tunIface, mtu);
+ } catch (RemoteException | ServiceSpecificException e) {
+ throw new IOException("Set MTU " + mtu + " on " + tunIface + " failed: " + e);
+ }
+ final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
+ ifConfig.ifName = tunIface;
+ ifConfig.ipv4Addr = v4;
+ ifConfig.prefixLength = 32;
+ ifConfig.hwAddr = "";
+ ifConfig.flags = new String[] {IF_STATE_UP};
+ try {
+ mNetd.interfaceSetCfg(ifConfig);
+ } catch (RemoteException | ServiceSpecificException e) {
+ throw new IOException("Setting IPv4 address to " + ifConfig.ipv4Addr + "/"
+ + ifConfig.prefixLength + " failed on " + ifConfig.ifName + ": " + e);
+ }
+
+ // [4] Open and configure local 464xlat read/write sockets.
+ // Opens a packet socket to receive IPv6 packets in clatd.
+ final ParcelFileDescriptor readSock6;
+ try {
+ // Use a JNI call to get native file descriptor instead of Os.socket() because we would
+ // like to use ParcelFileDescriptor to close file descriptor automatically. But ctor
+ // ParcelFileDescriptor(FileDescriptor fd) is a @hide function. Need to use native file
+ // descriptor to initialize ParcelFileDescriptor object instead.
+ readSock6 = mDeps.adoptFd(mDeps.openPacketSocket());
+ } catch (IOException e) {
+ throw new IOException("Open packet socket failed: " + e);
+ }
+
+ // Opens a raw socket with a given fwmark to send IPv6 packets in clatd.
+ final ParcelFileDescriptor writeSock6;
+ try {
+ // Use a JNI call to get native file descriptor instead of Os.socket(). See above
+ // reason why we use jniOpenPacketSocket6().
+ writeSock6 = mDeps.adoptFd(mDeps.openRawSocket6(fwmark));
+ } catch (IOException e) {
+ throw new IOException("Open raw socket failed: " + e);
+ }
+
+ final int ifaceIndex = mDeps.getInterfaceIndex(iface);
+ if (ifaceIndex == INVALID_IFINDEX) {
+ throw new IOException("Fail to get interface index for interface " + iface);
+ }
+
+ // Start translating packets to the new prefix.
+ try {
+ mDeps.addAnycastSetsockopt(writeSock6.getFileDescriptor(), v6, ifaceIndex);
+ } catch (IOException e) {
+ throw new IOException("add anycast sockopt failed: " + e);
+ }
+
+ // Update our packet socket filter to reflect the new 464xlat IP address.
+ try {
+ mDeps.configurePacketSocket(readSock6.getFileDescriptor(), v6, ifaceIndex);
+ } catch (IOException e) {
+ throw new IOException("configure packet socket failed: " + e);
+ }
+
+ // TODO: start clatd and returns local xlat464 v6 address.
+ return null;
+ }
+
+ private static native String native_selectIpv4Address(String v4addr, int prefixlen)
+ throws IOException;
+ private static native String native_generateIpv6Address(String iface, String v4,
+ String prefix64) throws IOException;
+ private static native int native_createTunInterface(String tuniface) throws IOException;
+ private static native int native_detectMtu(String platSubnet, int platSuffix, int mark)
+ throws IOException;
+ private static native int native_openPacketSocket() throws IOException;
+ private static native int native_openRawSocket6(int mark) throws IOException;
+ private static native void native_addAnycastSetsockopt(FileDescriptor sock, String v6,
+ int ifindex) throws IOException;
+ private static native void native_configurePacketSocket(FileDescriptor sock, String v6,
+ int ifindex) throws IOException;
+}
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index f1897f5..acf04bf 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -122,3 +122,25 @@
"framework-res",
],
}
+
+// Defaults for tests that want to run in mainline-presubmit.
+// Not widely used because many of our tests have AndroidTest.xml files and
+// use the mainline-param config-descriptor metadata in AndroidTest.xml.
+
+// test_mainline_modules is an array of strings. Each element in the array is a list of modules
+// separated by "+". The modules in this list must be in alphabetical order.
+// See SuiteModuleLoader.java.
+// TODO: why are the modules separated by + instead of being separate entries in the array?
+mainline_presubmit_modules = [
+ "CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex",
+]
+
+cc_defaults {
+ name: "connectivity-mainline-presubmit-cc-defaults",
+ test_mainline_modules: mainline_presubmit_modules,
+}
+
+java_defaults {
+ name: "connectivity-mainline-presubmit-java-defaults",
+ test_mainline_modules: mainline_presubmit_modules,
+}
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index 9cc001d..09d36e5 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -57,6 +57,7 @@
import static com.android.modules.utils.build.SdkLevel.isAtLeastR;
import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static com.android.testutils.MiscAsserts.assertEmpty;
import static com.android.testutils.MiscAsserts.assertThrows;
import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
@@ -788,7 +789,7 @@
} catch (IllegalStateException expected) { }
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.S)
+ @Test @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
public void testEnterpriseCapabilitySubLevel() {
final NetworkCapabilities nc1 = new NetworkCapabilities.Builder()
.addCapability(NET_CAPABILITY_ENTERPRISE)
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index 916b566..5778b0d 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -36,6 +36,7 @@
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static com.android.testutils.Cleanup.testAndCleanup;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -71,7 +72,6 @@
import android.net.VpnTransportInfo;
import android.net.cts.util.CtsNetUtils;
import android.net.wifi.WifiManager;
-import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
@@ -830,7 +830,7 @@
.getCaps().getUnderlyingNetworks())));
}
- @Test @IgnoreUpTo(Build.VERSION_CODES.S)
+ @Test @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
public void testChangeUnderlyingNetworks() throws Exception {
assumeTrue(supportedHardware());
assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 81c30b1..f66231d 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -49,6 +49,7 @@
"FrameworksNetCommonTests",
"core-tests-support",
"cts-net-utils",
+ "CtsNetTestsNonUpdatableLib",
"ctstestrunner-axt",
"junit",
"junit-params",
diff --git a/tests/cts/net/src/android/net/cts/DhcpOptionTest.kt b/tests/cts/net/src/android/net/cts/DhcpOptionTest.kt
index 1a62560..555dd87 100644
--- a/tests/cts/net/src/android/net/cts/DhcpOptionTest.kt
+++ b/tests/cts/net/src/android/net/cts/DhcpOptionTest.kt
@@ -16,11 +16,11 @@
package android.net.cts
-import android.os.Build
import android.net.DhcpOption
import androidx.test.filters.SmallTest
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.SC_V2
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
@@ -28,7 +28,7 @@
import org.junit.Test
@SmallTest
-@IgnoreUpTo(Build.VERSION_CODES.S)
+@IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
@RunWith(DevSdkIgnoreRunner::class)
class DhcpOptionTest {
private val DHCP_OPTION_TYPE: Byte = 2
diff --git a/tests/cts/net/src/android/net/cts/DnsResolverTest.java b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
index 4992795..c6fc38f 100644
--- a/tests/cts/net/src/android/net/cts/DnsResolverTest.java
+++ b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
@@ -25,6 +25,8 @@
import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
import static android.system.OsConstants.ETIMEDOUT;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -43,7 +45,6 @@
import android.net.NetworkRequest;
import android.net.ParseException;
import android.net.cts.util.CtsNetUtils;
-import android.os.Build;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
@@ -814,7 +815,7 @@
}
/** Verifies that DnsResolver.DnsException can be subclassed and its constructor re-used. */
- @Test @IgnoreUpTo(Build.VERSION_CODES.S)
+ @Test @IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
public void testDnsExceptionConstructor() throws InterruptedException {
class TestDnsException extends DnsResolver.DnsException {
TestDnsException(int code, @Nullable Throwable cause) {
diff --git a/tests/mts/Android.bp b/tests/mts/Android.bp
index a56f76e..2c44010 100644
--- a/tests/mts/Android.bp
+++ b/tests/mts/Android.bp
@@ -23,6 +23,9 @@
"general-tests",
"mts-tethering",
],
+ defaults: [
+ "connectivity-mainline-presubmit-cc-defaults",
+ ],
require_root: true,
static_libs: [
"libbase",
diff --git a/tests/unit/java/android/net/NetworkStatsAccessTest.java b/tests/unit/java/android/net/NetworkStatsAccessTest.java
index e4fc118..97a93ca 100644
--- a/tests/unit/java/android/net/NetworkStatsAccessTest.java
+++ b/tests/unit/java/android/net/NetworkStatsAccessTest.java
@@ -16,6 +16,8 @@
package android.net;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
@@ -25,7 +27,6 @@
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.os.Build;
import android.telephony.TelephonyManager;
import androidx.test.filters.SmallTest;
@@ -42,7 +43,7 @@
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S)
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
public class NetworkStatsAccessTest {
private static final String TEST_PKG = "com.example.test";
private static final int TEST_PID = 1234;
diff --git a/tests/unit/java/android/net/NetworkStatsCollectionTest.java b/tests/unit/java/android/net/NetworkStatsCollectionTest.java
index 870e88a..2e82986 100644
--- a/tests/unit/java/android/net/NetworkStatsCollectionTest.java
+++ b/tests/unit/java/android/net/NetworkStatsCollectionTest.java
@@ -29,6 +29,7 @@
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static com.android.testutils.MiscAsserts.assertThrows;
import static org.junit.Assert.assertArrayEquals;
@@ -37,7 +38,6 @@
import static org.junit.Assert.fail;
import android.content.res.Resources;
-import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
import android.telephony.SubscriptionPlan;
@@ -79,7 +79,7 @@
*/
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S)
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
public class NetworkStatsCollectionTest {
private static final String TEST_FILE = "test.bin";
diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt
index 177132f..0c3bee3 100644
--- a/tests/unit/java/android/net/NetworkTemplateTest.kt
+++ b/tests/unit/java/android/net/NetworkTemplateTest.kt
@@ -56,6 +56,7 @@
import com.android.net.module.util.NetworkStatsUtils.SUBSCRIBER_ID_MATCH_RULE_EXACT
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.SC_V2
import com.android.testutils.assertParcelSane
import org.junit.Before
import org.junit.Test
@@ -555,7 +556,7 @@
}
}
- @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S)
+ @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
@Test
fun testBuilderMatchRules() {
// Verify unknown match rules cannot construct templates.
@@ -656,7 +657,7 @@
}
}
- @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S)
+ @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
@Test
fun testBuilderWifiNetworkKeys() {
// Verify template builder which generates same template with the given different
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
index 416549c..d993d1f 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -29,6 +29,8 @@
import static android.net.TrafficStats.MB_IN_BYTES;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
@@ -41,7 +43,6 @@
import android.net.NetworkStats;
import android.net.NetworkStatsAccess;
import android.net.NetworkTemplate;
-import android.os.Build;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
@@ -74,7 +75,7 @@
*/
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S)
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
public class NetworkStatsObserversTest {
private static final String TEST_IFACE = "test0";
private static final String TEST_IFACE2 = "test1";
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 3765bf0..5e1699a 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -73,10 +73,8 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -119,7 +117,6 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.net.module.util.LocationPermissionChecker;
import com.android.server.net.NetworkStatsService.AlertObserver;
@@ -996,7 +993,7 @@
}
@Test
- public void testDetailedUidStats() throws Exception {
+ public void testUidStatsForTransport() throws Exception {
// pretend that network comes online
expectDefaultSettings();
NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {buildWifiState()};
@@ -1022,7 +1019,7 @@
.insertEntry(entry3));
mService.incrementOperationCount(UID_RED, 0xF00D, 1);
- NetworkStats stats = mService.getDetailedUidStats(INTERFACES_ALL);
+ NetworkStats stats = mService.getUidStatsForTransport(NetworkCapabilities.TRANSPORT_WIFI);
assertEquals(3, stats.size());
entry1.operations = 1;
@@ -1033,68 +1030,6 @@
}
@Test
- public void testDetailedUidStats_Filtered() throws Exception {
- // pretend that network comes online
- expectDefaultSettings();
-
- final String stackedIface = "stacked-test0";
- final LinkProperties stackedProp = new LinkProperties();
- stackedProp.setInterfaceName(stackedIface);
- final NetworkStateSnapshot wifiState = buildWifiState();
- wifiState.getLinkProperties().addStackedLink(stackedProp);
- NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {wifiState};
-
- expectNetworkStatsSummary(buildEmptyStats());
- expectNetworkStatsUidDetail(buildEmptyStats());
-
- mService.notifyNetworkStatus(NETWORKS_WIFI, states, getActiveIface(states),
- new UnderlyingNetworkInfo[0]);
-
- NetworkStats.Entry uidStats = new NetworkStats.Entry(
- TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L);
- // Stacked on matching interface
- NetworkStats.Entry tetheredStats1 = new NetworkStats.Entry(
- stackedIface, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L);
- TetherStatsParcel tetherStatsParcel1 =
- buildTetherStatsParcel(stackedIface, 1024L, 8L, 512L, 4L, 0);
- // Different interface
- TetherStatsParcel tetherStatsParcel2 =
- buildTetherStatsParcel("otherif", 1024L, 8L, 512L, 4L, 0);
-
- final String[] ifaceFilter = new String[] { TEST_IFACE };
- final String[] augmentedIfaceFilter = new String[] { stackedIface, TEST_IFACE };
- incrementCurrentTime(HOUR_IN_MILLIS);
- expectDefaultSettings();
- expectNetworkStatsSummary(buildEmptyStats());
- when(mStatsFactory.augmentWithStackedInterfaces(eq(ifaceFilter)))
- .thenReturn(augmentedIfaceFilter);
- when(mStatsFactory.readNetworkStatsDetail(eq(UID_ALL), any(), eq(TAG_ALL)))
- .thenReturn(new NetworkStats(getElapsedRealtime(), 1)
- .insertEntry(uidStats));
- final TetherStatsParcel[] tetherStatsParcels = {tetherStatsParcel1, tetherStatsParcel2};
- when(mNetd.tetherGetStats()).thenReturn(tetherStatsParcels);
-
- NetworkStats stats = mService.getDetailedUidStats(ifaceFilter);
-
- // mStatsFactory#readNetworkStatsDetail() has the following invocations:
- // 1) NetworkStatsService#systemReady from #setUp.
- // 2) mService#notifyNetworkStatus in the test above.
- //
- // Additionally, we should have one call from the above call to mService#getDetailedUidStats
- // with the augmented ifaceFilter.
- verify(mStatsFactory, times(2)).readNetworkStatsDetail(UID_ALL, INTERFACES_ALL, TAG_ALL);
- verify(mStatsFactory, times(1)).readNetworkStatsDetail(
- eq(UID_ALL),
- eq(augmentedIfaceFilter),
- eq(TAG_ALL));
- assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), TEST_IFACE));
- assertTrue(ArrayUtils.contains(stats.getUniqueIfaces(), stackedIface));
- assertEquals(2, stats.size());
- assertEquals(uidStats, stats.getValues(0, null));
- assertEquals(tetheredStats1, stats.getValues(1, null));
- }
-
- @Test
public void testForegroundBackground() throws Exception {
// pretend that network comes online
expectDefaultSettings();