summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/net/ip/IpClient.java48
-rwxr-xr-xsrc/com/android/server/connectivity/NetworkMonitor.java79
-rw-r--r--src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java95
-rw-r--r--src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java57
-rw-r--r--src/com/android/server/connectivity/ipmemorystore/StatusAndCount.java29
5 files changed, 254 insertions, 54 deletions
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index f3dcdc8..018d6ab 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -64,7 +64,6 @@ import android.text.TextUtils;
import android.util.LocalLog;
import android.util.Log;
import android.util.Pair;
-import android.util.Patterns;
import android.util.SparseArray;
import androidx.annotation.NonNull;
@@ -86,6 +85,8 @@ import com.android.server.NetworkStackService.NetworkStackServiceManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URL;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@@ -381,7 +382,7 @@ public class IpClient extends StateMachine {
private static final int EVENT_READ_PACKET_FILTER_COMPLETE = 12;
private static final int CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF = 13;
private static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF = 14;
- private static final int CMD_UPDATE_L2KEY_GROUPHINT = 15;
+ private static final int CMD_UPDATE_L2KEY_CLUSTER = 15;
private static final int CMD_COMPLETE_PRECONNECTION = 16;
private static final int CMD_UPDATE_L2INFORMATION = 17;
@@ -477,7 +478,7 @@ public class IpClient extends StateMachine {
private ProxyInfo mHttpProxy;
private ApfFilter mApfFilter;
private String mL2Key; // The L2 key for this network, for writing into the memory store
- private String mGroupHint; // The group hint for this network, for writing into the memory store
+ private String mCluster; // The cluster for this network, for writing into the memory store
private boolean mMulticastFiltering;
private long mStartTimeMillis;
private MacAddress mCurrentBssid;
@@ -686,9 +687,9 @@ public class IpClient extends StateMachine {
IpClient.this.stop();
}
@Override
- public void setL2KeyAndGroupHint(String l2Key, String groupHint) {
+ public void setL2KeyAndGroupHint(String l2Key, String cluster) {
enforceNetworkStackCallingPermission();
- IpClient.this.setL2KeyAndGroupHint(l2Key, groupHint);
+ IpClient.this.setL2KeyAndCluster(l2Key, cluster);
}
@Override
public void setTcpBufferSizes(String tcpBufferSizes) {
@@ -805,7 +806,7 @@ public class IpClient extends StateMachine {
if (req.mLayer2Info != null) {
mL2Key = req.mLayer2Info.mL2Key;
- mGroupHint = req.mLayer2Info.mGroupHint;
+ mCluster = req.mLayer2Info.mCluster;
}
sendMessage(CMD_START, new android.net.shared.ProvisioningConfiguration(req));
}
@@ -853,14 +854,14 @@ public class IpClient extends StateMachine {
}
/**
- * Set the L2 key and group hint for storing info into the memory store.
+ * Set the L2 key and cluster for storing info into the memory store.
*
* This method is only supported on Q devices. For R or above releases,
* caller should call #updateLayer2Information() instead.
*/
- public void setL2KeyAndGroupHint(String l2Key, String groupHint) {
+ public void setL2KeyAndCluster(String l2Key, String cluster) {
if (!ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)) {
- sendMessage(CMD_UPDATE_L2KEY_GROUPHINT, new Pair<>(l2Key, groupHint));
+ sendMessage(CMD_UPDATE_L2KEY_CLUSTER, new Pair<>(l2Key, cluster));
}
}
@@ -917,7 +918,7 @@ public class IpClient extends StateMachine {
}
/**
- * Update the network bssid, L2Key and GroupHint on L2 roaming happened.
+ * Update the network bssid, L2Key and cluster on L2 roaming happened.
*/
public void updateLayer2Information(@NonNull Layer2InformationParcelable info) {
sendMessage(CMD_UPDATE_L2INFORMATION, info);
@@ -1263,9 +1264,9 @@ public class IpClient extends StateMachine {
}
final String capportUrl = mDhcpResults.captivePortalApiUrl;
- // Uri.parse does no syntax check; do a simple regex check to eliminate garbage.
+ // Uri.parse does no syntax check; do a simple check to eliminate garbage.
// If the URL is still incorrect data fetching will fail later, which is fine.
- if (capportUrl != null && Patterns.WEB_URL.matcher(capportUrl).matches()) {
+ if (isParseableUrl(capportUrl)) {
NetworkInformationShimImpl.newInstance()
.setCaptivePortalApiUrl(newLp, Uri.parse(capportUrl));
}
@@ -1303,6 +1304,19 @@ public class IpClient extends StateMachine {
return newLp;
}
+ private static boolean isParseableUrl(String url) {
+ // Verify that a URL has a reasonable format that can be parsed as per the URL constructor.
+ // This does not use Patterns.WEB_URL as that pattern excludes URLs without TLDs, such as on
+ // localhost.
+ if (url == null) return false;
+ try {
+ new URL(url);
+ return true;
+ } catch (MalformedURLException e) {
+ return false;
+ }
+ }
+
private static void addAllReachableDnsServers(
LinkProperties lp, Iterable<InetAddress> dnses) {
// TODO: Investigate deleting this reachability check. We should be
@@ -1545,7 +1559,7 @@ public class IpClient extends StateMachine {
private void handleUpdateL2Information(@NonNull Layer2InformationParcelable info) {
mL2Key = info.l2Key;
- mGroupHint = info.groupHint;
+ mCluster = info.cluster;
if (info.bssid == null || mCurrentBssid == null) {
Log.wtf(mTag, "bssid in the parcelable or current tracked bssid should be non-null");
@@ -1619,10 +1633,10 @@ public class IpClient extends StateMachine {
handleLinkPropertiesUpdate(NO_CALLBACKS);
break;
- case CMD_UPDATE_L2KEY_GROUPHINT: {
+ case CMD_UPDATE_L2KEY_CLUSTER: {
final Pair<String, String> args = (Pair<String, String>) msg.obj;
mL2Key = args.first;
- mGroupHint = args.second;
+ mCluster = args.second;
break;
}
@@ -1824,10 +1838,10 @@ public class IpClient extends StateMachine {
transitionTo(mStoppingState);
break;
- case CMD_UPDATE_L2KEY_GROUPHINT: {
+ case CMD_UPDATE_L2KEY_CLUSTER: {
final Pair<String, String> args = (Pair<String, String>) msg.obj;
mL2Key = args.first;
- mGroupHint = args.second;
+ mCluster = args.second;
// TODO : attributes should be saved to the memory store with
// these new values if they differ from the previous ones.
// If the state machine is in pure StartedState, then the values to input
diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java
index 8a1b0c7..bee3634 100755
--- a/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/src/com/android/server/connectivity/NetworkMonitor.java
@@ -83,6 +83,7 @@ import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static com.android.networkstack.apishim.ConstantsShim.DETECTION_METHOD_DNS_EVENTS;
import static com.android.networkstack.apishim.ConstantsShim.DETECTION_METHOD_TCP_METRICS;
+import static com.android.networkstack.apishim.ConstantsShim.TRANSPORT_TEST;
import static com.android.networkstack.util.DnsUtils.PRIVATE_DNS_PROBE_HOST_SUFFIX;
import static com.android.networkstack.util.DnsUtils.TYPE_ADDRCONFIG;
@@ -387,7 +388,8 @@ public class NetworkMonitor extends StateMachine {
private static final int CMD_BANDWIDTH_CHECK_TIMEOUT = 24;
// Start mReevaluateDelayMs at this value and double.
- private static final int INITIAL_REEVALUATE_DELAY_MS = 1000;
+ @VisibleForTesting
+ static final int INITIAL_REEVALUATE_DELAY_MS = 1000;
private static final int MAX_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
// Default timeout of evaluating network bandwidth.
private static final int DEFAULT_EVALUATING_BANDWIDTH_TIMEOUT_MS = 10_000;
@@ -1434,8 +1436,9 @@ public class NetworkMonitor extends StateMachine {
}
final int token = ++mProbeToken;
+ final EvaluationThreadDeps deps = new EvaluationThreadDeps(mNetworkCapabilities);
mThread = new Thread(() -> sendMessage(obtainMessage(CMD_PROBE_COMPLETE, token, 0,
- isCaptivePortal())));
+ isCaptivePortal(deps))));
mThread.start();
}
@@ -2147,8 +2150,25 @@ public class NetworkMonitor extends StateMachine {
return mCaptivePortalFallbackSpecs[idx];
}
- @VisibleForTesting
- protected CaptivePortalProbeResult isCaptivePortal() {
+ /**
+ * Parameters that can be accessed by the evaluation thread in a thread-safe way.
+ *
+ * Parameters such as LinkProperties and NetworkCapabilities cannot be accessed by the
+ * evaluation thread directly, as they are managed in the state machine thread and not
+ * synchronized. This class provides a copy of the required data that is not modified and can be
+ * used safely by the evaluation thread.
+ */
+ private static class EvaluationThreadDeps {
+ // TODO: add parameters that are accessed in a non-thread-safe way from the evaluation
+ // thread (read from LinkProperties, NetworkCapabilities, useHttps, validationStage)
+ private final boolean mIsTestNetwork;
+
+ EvaluationThreadDeps(NetworkCapabilities nc) {
+ this.mIsTestNetwork = nc.hasTransport(TRANSPORT_TEST);
+ }
+ }
+
+ private CaptivePortalProbeResult isCaptivePortal(EvaluationThreadDeps deps) {
if (!mIsCaptivePortalCheckEnabled) {
validationLog("Validation disabled.");
return CaptivePortalProbeResult.success(CaptivePortalProbeResult.PROBE_UNKNOWN);
@@ -2196,11 +2216,11 @@ public class NetworkMonitor extends StateMachine {
reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result);
} else if (mUseHttps && httpsUrls.length == 1 && httpUrls.length == 1) {
// Probe results are reported inside sendHttpAndHttpsParallelWithFallbackProbes.
- result = sendHttpAndHttpsParallelWithFallbackProbes(
- proxyInfo, httpsUrls[0], httpUrls[0]);
+ result = sendHttpAndHttpsParallelWithFallbackProbes(deps, proxyInfo,
+ httpsUrls[0], httpUrls[0]);
} else if (mUseHttps) {
// Support result aggregation from multiple Urls.
- result = sendMultiParallelHttpAndHttpsProbes(proxyInfo, httpsUrls, httpUrls);
+ result = sendMultiParallelHttpAndHttpsProbes(deps, proxyInfo, httpsUrls, httpUrls);
} else {
result = sendDnsAndHttpProbes(proxyInfo, httpUrls[0], ValidationProbeEvent.PROBE_HTTP);
reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result);
@@ -2482,12 +2502,12 @@ public class NetworkMonitor extends StateMachine {
private final CountDownLatch mLatch;
private final Probe mProbe;
- ProbeThread(CountDownLatch latch, ProxyInfo proxy, URL url, int probeType,
- Uri captivePortalApiUrl) {
+ ProbeThread(CountDownLatch latch, EvaluationThreadDeps deps, ProxyInfo proxy, URL url,
+ int probeType, Uri captivePortalApiUrl) {
mLatch = latch;
mProbe = (probeType == ValidationProbeEvent.PROBE_HTTPS)
- ? new HttpsProbe(proxy, url, captivePortalApiUrl)
- : new HttpProbe(proxy, url, captivePortalApiUrl);
+ ? new HttpsProbe(deps, proxy, url, captivePortalApiUrl)
+ : new HttpProbe(deps, proxy, url, captivePortalApiUrl);
mResult = CaptivePortalProbeResult.failed(probeType);
}
@@ -2512,11 +2532,14 @@ public class NetworkMonitor extends StateMachine {
}
private abstract static class Probe {
+ protected final EvaluationThreadDeps mDeps;
protected final ProxyInfo mProxy;
protected final URL mUrl;
protected final Uri mCaptivePortalApiUrl;
- protected Probe(ProxyInfo proxy, URL url, Uri captivePortalApiUrl) {
+ protected Probe(EvaluationThreadDeps deps, ProxyInfo proxy, URL url,
+ Uri captivePortalApiUrl) {
+ mDeps = deps;
mProxy = proxy;
mUrl = url;
mCaptivePortalApiUrl = captivePortalApiUrl;
@@ -2526,8 +2549,8 @@ public class NetworkMonitor extends StateMachine {
}
final class HttpsProbe extends Probe {
- HttpsProbe(ProxyInfo proxy, URL url, Uri captivePortalApiUrl) {
- super(proxy, url, captivePortalApiUrl);
+ HttpsProbe(EvaluationThreadDeps deps, ProxyInfo proxy, URL url, Uri captivePortalApiUrl) {
+ super(deps, proxy, url, captivePortalApiUrl);
}
@Override
@@ -2537,8 +2560,8 @@ public class NetworkMonitor extends StateMachine {
}
final class HttpProbe extends Probe {
- HttpProbe(ProxyInfo proxy, URL url, Uri captivePortalApiUrl) {
- super(proxy, url, captivePortalApiUrl);
+ HttpProbe(EvaluationThreadDeps deps, ProxyInfo proxy, URL url, Uri captivePortalApiUrl) {
+ super(deps, proxy, url, captivePortalApiUrl);
}
private CaptivePortalDataShim tryCapportApiProbe() {
@@ -2548,7 +2571,12 @@ public class NetworkMonitor extends StateMachine {
final String apiContent;
try {
final URL url = new URL(mCaptivePortalApiUrl.toString());
- if (!"https".equals(url.getProtocol())) {
+ // Protocol must be HTTPS
+ // (as per https://www.ietf.org/id/draft-ietf-capport-api-07.txt, #4).
+ // Only allow HTTP on localhost, for testing.
+ final boolean isTestLocalhostHttp = mDeps.mIsTestNetwork
+ && "localhost".equals(url.getHost()) && "http".equals(url.getProtocol());
+ if (!"https".equals(url.getProtocol()) && !isTestLocalhostHttp) {
validationLog("Invalid captive portal API protocol: " + url.getProtocol());
return null;
}
@@ -2631,8 +2659,9 @@ public class NetworkMonitor extends StateMachine {
&& captivePortalApiUrl == null);
}
- private CaptivePortalProbeResult sendMultiParallelHttpAndHttpsProbes(@NonNull ProxyInfo proxy,
- @NonNull URL[] httpsUrls, @NonNull URL[] httpUrls) {
+ private CaptivePortalProbeResult sendMultiParallelHttpAndHttpsProbes(
+ @NonNull EvaluationThreadDeps deps, @Nullable ProxyInfo proxy, @NonNull URL[] httpsUrls,
+ @NonNull URL[] httpUrls) {
// If multiple URLs are required to ensure the correctness of validation, send parallel
// probes to explore the result in separate probe threads and aggregate those results into
// one as the final result for either HTTP or HTTPS.
@@ -2657,13 +2686,13 @@ public class NetworkMonitor extends StateMachine {
// TODO: Have the capport probe as a different probe for cleanliness.
final URL urlMaybeWithCapport = httpUrls[0];
for (final URL url : httpUrls) {
- futures.add(ecs.submit(() -> new HttpProbe(proxy, url,
+ futures.add(ecs.submit(() -> new HttpProbe(deps, proxy, url,
url.equals(urlMaybeWithCapport) ? capportApiUrl : null).sendProbe()));
}
for (final URL url : httpsUrls) {
- futures.add(
- ecs.submit(() -> new HttpsProbe(proxy, url, capportApiUrl).sendProbe()));
+ futures.add(ecs.submit(() -> new HttpsProbe(deps, proxy, url, capportApiUrl)
+ .sendProbe()));
}
final ArrayList<CaptivePortalProbeResult> completedProbes = new ArrayList<>();
@@ -2759,15 +2788,15 @@ public class NetworkMonitor extends StateMachine {
}
private CaptivePortalProbeResult sendHttpAndHttpsParallelWithFallbackProbes(
- ProxyInfo proxy, URL httpsUrl, URL httpUrl) {
+ EvaluationThreadDeps deps, ProxyInfo proxy, URL httpsUrl, URL httpUrl) {
// Number of probes to wait for. If a probe completes with a conclusive answer
// it shortcuts the latch immediately by forcing the count to 0.
final CountDownLatch latch = new CountDownLatch(2);
final Uri capportApiUrl = getCaptivePortalApiUrl(mLinkProperties);
- final ProbeThread httpsProbe = new ProbeThread(latch, proxy, httpsUrl,
+ final ProbeThread httpsProbe = new ProbeThread(latch, deps, proxy, httpsUrl,
ValidationProbeEvent.PROBE_HTTPS, capportApiUrl);
- final ProbeThread httpProbe = new ProbeThread(latch, proxy, httpUrl,
+ final ProbeThread httpProbe = new ProbeThread(latch, deps, proxy, httpUrl,
ValidationProbeEvent.PROBE_HTTP, capportApiUrl);
try {
diff --git a/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java b/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
index 834aa2d..0b05a5b 100644
--- a/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
+++ b/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
@@ -78,11 +78,17 @@ public class IpMemoryStoreDatabase {
// is used to represent "infinite lease".
public static final String COLTYPE_ASSIGNEDV4ADDRESSEXPIRY = "BIGINT";
- // Please note that the group hint is only a *hint*, hence its name. The client can offer
- // this information to nudge the grouping in the decision it thinks is right, but it can't
- // decide for the memory store what is the same L3 network.
- public static final String COLNAME_GROUPHINT = "groupHint";
- public static final String COLTYPE_GROUPHINT = "TEXT";
+ // An optional cluster representing a notion of group owned by the client. The memory
+ // store uses this as a hint for grouping, but not as an overriding factor. The client
+ // can then use this to find networks belonging to a cluster. An example of this could
+ // be the SSID for WiFi, where same SSID-networks may not be the same L3 networks but
+ // it's still useful for managing networks.
+ // Note that "groupHint" is the legacy name of the column. The column should be renamed
+ // in the database – ALTER TABLE ${NetworkAttributesContract.TABLENAME RENAME} COLUMN
+ // groupHint TO cluster – but this has been postponed to reduce risk as the Mainline
+ // release winter imposes a lot of changes be pushed at the same time in the next release.
+ public static final String COLNAME_CLUSTER = "groupHint";
+ public static final String COLTYPE_CLUSTER = "TEXT";
public static final String COLNAME_DNSADDRESSES = "dnsAddresses";
// Stored in marshalled form as is
@@ -97,7 +103,7 @@ public class IpMemoryStoreDatabase {
+ COLNAME_EXPIRYDATE + " " + COLTYPE_EXPIRYDATE + ", "
+ COLNAME_ASSIGNEDV4ADDRESS + " " + COLTYPE_ASSIGNEDV4ADDRESS + ", "
+ COLNAME_ASSIGNEDV4ADDRESSEXPIRY + " " + COLTYPE_ASSIGNEDV4ADDRESSEXPIRY + ", "
- + COLNAME_GROUPHINT + " " + COLTYPE_GROUPHINT + ", "
+ + COLNAME_CLUSTER + " " + COLTYPE_CLUSTER + ", "
+ COLNAME_DNSADDRESSES + " " + COLTYPE_DNSADDRESSES + ", "
+ COLNAME_MTU + " " + COLTYPE_MTU + ")";
public static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLENAME;
@@ -165,7 +171,7 @@ public class IpMemoryStoreDatabase {
try {
if (oldVersion < 2) {
// upgrade from version 1 to version 2
- // since we starts from version 2, do nothing here
+ // since we start from version 2, do nothing here
}
if (oldVersion < 3) {
@@ -250,8 +256,8 @@ public class IpMemoryStoreDatabase {
values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESSEXPIRY,
attributes.assignedV4AddressExpiry);
}
- if (null != attributes.groupHint) {
- values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint);
+ if (null != attributes.cluster) {
+ values.put(NetworkAttributesContract.COLNAME_CLUSTER, attributes.cluster);
}
if (null != attributes.dnsAddresses) {
values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES,
@@ -299,7 +305,7 @@ public class IpMemoryStoreDatabase {
NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0);
final long assignedV4AddressExpiry = getLong(cursor,
NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESSEXPIRY, 0);
- final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT);
+ final String cluster = getString(cursor, NetworkAttributesContract.COLNAME_CLUSTER);
final byte[] dnsAddressesBlob =
getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES);
final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1);
@@ -309,7 +315,7 @@ public class IpMemoryStoreDatabase {
if (0 != assignedV4AddressExpiry) {
builder.setAssignedV4AddressExpiry(assignedV4AddressExpiry);
}
- builder.setGroupHint(groupHint);
+ builder.setCluster(cluster);
if (null != dnsAddressesBlob) {
builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob));
}
@@ -596,6 +602,64 @@ public class IpMemoryStoreDatabase {
return bestKey;
}
+ /**
+ * Delete a single entry by key.
+ *
+ * If |needWipe| is true, the data will be wiped from disk immediately. Otherwise, it will
+ * only be marked deleted, and overwritten by subsequent writes or reclaimed during the next
+ * maintenance window.
+ * Note that wiping data is a very expensive operation. This is meant for clients that need
+ * this data gone from disk immediately for security reasons. Functionally it makes no
+ * difference at all.
+ */
+ static StatusAndCount delete(@NonNull final SQLiteDatabase db, @NonNull final String l2key,
+ final boolean needWipe) {
+ return deleteEntriesWithColumn(db,
+ NetworkAttributesContract.COLNAME_L2KEY, l2key, needWipe);
+ }
+
+ /**
+ * Delete all entries that have a particular cluster value.
+ *
+ * If |needWipe| is true, the data will be wiped from disk immediately. Otherwise, it will
+ * only be marked deleted, and overwritten by subsequent writes or reclaimed during the next
+ * maintenance window.
+ * Note that wiping data is a very expensive operation. This is meant for clients that need
+ * this data gone from disk immediately for security reasons. Functionally it makes no
+ * difference at all.
+ */
+ static StatusAndCount deleteCluster(@NonNull final SQLiteDatabase db,
+ @NonNull final String cluster, final boolean needWipe) {
+ return deleteEntriesWithColumn(db,
+ NetworkAttributesContract.COLNAME_CLUSTER, cluster, needWipe);
+ }
+
+ // Delete all entries where the given column has the given value.
+ private static StatusAndCount deleteEntriesWithColumn(@NonNull final SQLiteDatabase db,
+ @NonNull final String column, @NonNull final String value, final boolean needWipe) {
+ db.beginTransaction();
+ int deleted = 0;
+ try {
+ deleted = db.delete(NetworkAttributesContract.TABLENAME,
+ column + "= ?", new String[] { value });
+ db.setTransactionSuccessful();
+ } catch (SQLiteException e) {
+ Log.e(TAG, "Could not delete from the memory store", e);
+ // Unclear what might have happened ; deleting records is not supposed to be able
+ // to fail barring a syntax error in the SQL query.
+ return new StatusAndCount(Status.ERROR_UNKNOWN, 0);
+ } finally {
+ db.endTransaction();
+ }
+
+ if (needWipe) {
+ final int vacuumStatus = vacuum(db);
+ // This is a problem for the client : return the failure
+ if (Status.SUCCESS != vacuumStatus) return new StatusAndCount(vacuumStatus, deleted);
+ }
+ return new StatusAndCount(Status.SUCCESS, deleted);
+ }
+
// Drops all records that are expired. Relevance has decayed to zero of these records. Returns
// an int out of Status.{SUCCESS, ERROR_*}
static int dropAllExpiredRecords(@NonNull final SQLiteDatabase db) {
@@ -702,4 +766,13 @@ public class IpMemoryStoreDatabase {
final int columnIndex = cursor.getColumnIndex(columnName);
return (columnIndex >= 0) ? cursor.getLong(columnIndex) : defaultValue;
}
+ private static int vacuum(@NonNull final SQLiteDatabase db) {
+ try {
+ db.execSQL("VACUUM");
+ return Status.SUCCESS;
+ } catch (SQLiteException e) {
+ // Vacuuming may fail from lack of storage, because it makes a copy of the database.
+ return Status.ERROR_STORAGE;
+ }
+ }
}
diff --git a/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java b/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
index cd29e0d..ae9c875 100644
--- a/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
+++ b/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
@@ -33,6 +33,7 @@ import android.net.ipmemorystore.IOnBlobRetrievedListener;
import android.net.ipmemorystore.IOnL2KeyResponseListener;
import android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener;
import android.net.ipmemorystore.IOnSameL3NetworkResponseListener;
+import android.net.ipmemorystore.IOnStatusAndCountListener;
import android.net.ipmemorystore.IOnStatusListener;
import android.net.ipmemorystore.NetworkAttributes;
import android.net.ipmemorystore.NetworkAttributesParcelable;
@@ -116,7 +117,11 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub {
// TODO : investigate replacing this scheme with a scheme where each thread has its own
// instance of the database, as it may be faster. It is likely however that IpMemoryStore
// operations are mostly IO-bound anyway, and additional contention is unlikely to bring
- // benefits. Alternatively, a read-write lock might increase throughput.
+ // benefits. Alternatively, a read-write lock might increase throughput. Also if doing
+ // this work, care must be taken around the privacy-preserving VACUUM operations as
+ // VACUUM will fail if there are other open transactions at the same time, and using
+ // multiple threads will open the possibility of this failure happening, threatening
+ // the privacy guarantees.
mExecutor = Executors.newSingleThreadExecutor();
RegularMaintenanceJobService.schedule(mContext, this);
}
@@ -405,6 +410,56 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub {
}
/**
+ * Delete a single entry.
+ *
+ * @param l2Key The L2 key of the entry to delete.
+ * @param needWipe Whether the data must be wiped from disk immediately for security reasons.
+ * This is very expensive and makes no functional difference ; only pass
+ * true if security requires this data must be removed from disk immediately.
+ * @param listener A listener that will be invoked to inform of the completion of this call,
+ * or null if the client is not interested in learning about success/failure.
+ * returns (through the listener) A status to indicate success and the number of deleted records
+ */
+ public void delete(@NonNull final String l2Key, final boolean needWipe,
+ @Nullable final IOnStatusAndCountListener listener) {
+ mExecutor.execute(() -> {
+ try {
+ final StatusAndCount res = IpMemoryStoreDatabase.delete(mDb, l2Key, needWipe);
+ if (null != listener) listener.onComplete(makeStatus(res.status), res.count);
+ } catch (final RemoteException e) {
+ // Client at the other end died
+ }
+ });
+ }
+
+ /**
+ * Delete all entries in a cluster.
+ *
+ * This method will delete all entries in the memory store that have the cluster attribute
+ * passed as an argument.
+ *
+ * @param cluster The cluster to delete.
+ * @param needWipe Whether the data must be wiped from disk immediately for security reasons.
+ * This is very expensive and makes no functional difference ; only pass
+ * true if security requires this data must be removed from disk immediately.
+ * @param listener A listener that will be invoked to inform of the completion of this call,
+ * or null if the client is not interested in learning about success/failure.
+ * returns (through the listener) A status to indicate success and the number of deleted records
+ */
+ public void deleteCluster(@NonNull final String cluster, final boolean needWipe,
+ @Nullable final IOnStatusAndCountListener listener) {
+ mExecutor.execute(() -> {
+ try {
+ final StatusAndCount res =
+ IpMemoryStoreDatabase.deleteCluster(mDb, cluster, needWipe);
+ if (null != listener) listener.onComplete(makeStatus(res.status), res.count);
+ } catch (final RemoteException e) {
+ // Client at the other end died
+ }
+ });
+ }
+
+ /**
* Wipe the data in IpMemoryStore database upon network factory reset.
*/
@Override
diff --git a/src/com/android/server/connectivity/ipmemorystore/StatusAndCount.java b/src/com/android/server/connectivity/ipmemorystore/StatusAndCount.java
new file mode 100644
index 0000000..2cbe843
--- /dev/null
+++ b/src/com/android/server/connectivity/ipmemorystore/StatusAndCount.java
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+package com.android.server.connectivity.ipmemorystore;
+
+/**
+ * Small data class to wrap a Status and an int.
+ */
+public class StatusAndCount {
+ public final int status;
+ public final int count;
+ public StatusAndCount(final int status, final int count) {
+ this.status = status;
+ this.count = count;
+ }
+}