diff options
author | 2018-12-04 12:13:09 +0900 | |
---|---|---|
committer | 2019-01-09 15:42:16 +0900 | |
commit | 0e3d0923a65d654c57bd0ce24d84a257702e83db (patch) | |
tree | 1e6af187f61e4165899fb74ab139b25f196b5e3f | |
parent | 5dbf0574365254e9b5aee3919b5ee0563a92ba80 (diff) |
Move DhcpServer to NetworkStack app
Test: atest FrameworksNetTests && atest NetworkStackTests
Bug: b/112869080
Change-Id: I96c40e63e9ceb37b67705bdd4d120307e114715b
34 files changed, 1028 insertions, 164 deletions
diff --git a/Android.bp b/Android.bp index 482b19dbd8d7..565f4e05dde4 100644 --- a/Android.bp +++ b/Android.bp @@ -825,7 +825,10 @@ aidl_interface { local_include_dir: "core/java", srcs: [ "core/java/android/net/INetworkStackConnector.aidl", + "core/java/android/net/INetworkStackStatusCallback.aidl", "core/java/android/net/dhcp/DhcpServingParamsParcel.aidl", + "core/java/android/net/dhcp/IDhcpServer.aidl", + "core/java/android/net/dhcp/IDhcpServerCallbacks.aidl", ], api_dir: "aidl/networkstack", } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 61d5a9127743..529bb10afad5 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -2475,6 +2475,8 @@ public class ConnectivityManager { public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; /** {@hide} */ public static final int TETHER_ERROR_PROVISION_FAILED = 11; + /** {@hide} */ + public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; /** * Get a more detailed error code after a Tethering or Untethering diff --git a/core/java/android/net/INetworkStackConnector.aidl b/core/java/android/net/INetworkStackConnector.aidl index 29f882858c05..be0dc07f4b23 100644 --- a/core/java/android/net/INetworkStackConnector.aidl +++ b/core/java/android/net/INetworkStackConnector.aidl @@ -15,7 +15,11 @@ */ package android.net; +import android.net.dhcp.DhcpServingParamsParcel; +import android.net.dhcp.IDhcpServerCallbacks; + /** @hide */ oneway interface INetworkStackConnector { - // TODO: requestDhcpServer(), etc. will go here + void makeDhcpServer(in String ifName, in DhcpServingParamsParcel params, + in IDhcpServerCallbacks cb); }
\ No newline at end of file diff --git a/core/java/android/net/INetworkStackStatusCallback.aidl b/core/java/android/net/INetworkStackStatusCallback.aidl new file mode 100644 index 000000000000..51032d80a172 --- /dev/null +++ b/core/java/android/net/INetworkStackStatusCallback.aidl @@ -0,0 +1,22 @@ +/* + * 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. + */ + +package android.net; + +/** @hide */ +oneway interface INetworkStackStatusCallback { + void onStatusAvailable(int statusCode); +}
\ No newline at end of file diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java index 82a4e31a81dd..d4a0ec632383 100644 --- a/core/java/android/net/NetworkStack.java +++ b/core/java/android/net/NetworkStack.java @@ -25,9 +25,12 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.net.dhcp.DhcpServingParamsParcel; +import android.net.dhcp.IDhcpServerCallbacks; import android.os.Binder; import android.os.IBinder; import android.os.Process; +import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.util.Slog; @@ -58,6 +61,22 @@ public class NetworkStack { public NetworkStack() { } + /** + * Create a DHCP server according to the specified parameters. + * + * <p>The server will be returned asynchronously through the provided callbacks. + */ + public void makeDhcpServer(final String ifName, final DhcpServingParamsParcel params, + final IDhcpServerCallbacks cb) { + requestConnector(connector -> { + try { + connector.makeDhcpServer(ifName, params, cb); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + }); + } + private class NetworkStackConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { diff --git a/core/java/android/net/dhcp/DhcpServerCallbacks.java b/core/java/android/net/dhcp/DhcpServerCallbacks.java new file mode 100644 index 000000000000..bb56876c77f5 --- /dev/null +++ b/core/java/android/net/dhcp/DhcpServerCallbacks.java @@ -0,0 +1,33 @@ +/* + * 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. + */ + +package android.net.dhcp; + +/** + * Convenience wrapper around IDhcpServerCallbacks.Stub that implements getInterfaceVersion(). + * @hide + */ +public abstract class DhcpServerCallbacks extends IDhcpServerCallbacks.Stub { + // TODO: add @Override here once the API is versioned + + /** + * Get the version of the aidl interface implemented by the callbacks. + */ + public int getInterfaceVersion() { + // TODO: return IDhcpServerCallbacks.VERSION; + return 0; + } +} diff --git a/core/java/android/net/dhcp/IDhcpServer.aidl b/core/java/android/net/dhcp/IDhcpServer.aidl new file mode 100644 index 000000000000..559433b13962 --- /dev/null +++ b/core/java/android/net/dhcp/IDhcpServer.aidl @@ -0,0 +1,32 @@ +/** + * 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 perNmissions and + * limitations under the License. + */ + +package android.net.dhcp; + +import android.net.INetworkStackStatusCallback; +import android.net.dhcp.DhcpServingParamsParcel; + +/** @hide */ +oneway interface IDhcpServer { + const int STATUS_UNKNOWN = 0; + const int STATUS_SUCCESS = 1; + const int STATUS_INVALID_ARGUMENT = 2; + const int STATUS_UNKNOWN_ERROR = 3; + + void start(in INetworkStackStatusCallback cb); + void updateParams(in DhcpServingParamsParcel params, in INetworkStackStatusCallback cb); + void stop(in INetworkStackStatusCallback cb); +} diff --git a/core/java/android/net/dhcp/IDhcpServerCallbacks.aidl b/core/java/android/net/dhcp/IDhcpServerCallbacks.aidl new file mode 100644 index 000000000000..7ab4dcdbe584 --- /dev/null +++ b/core/java/android/net/dhcp/IDhcpServerCallbacks.aidl @@ -0,0 +1,24 @@ +/** + * 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 perNmissions and + * limitations under the License. + */ + +package android.net.dhcp; + +import android.net.dhcp.IDhcpServer; + +/** @hide */ +oneway interface IDhcpServerCallbacks { + void onDhcpServerCreated(int statusCode, in IDhcpServer server); +} diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp index 55bb5175c28d..4688848dee0f 100644 --- a/packages/NetworkStack/Android.bp +++ b/packages/NetworkStack/Android.bp @@ -22,6 +22,10 @@ java_library { srcs: [ "src/**/*.java", ], + static_libs: [ + "dhcp-packet-lib", + "frameworks-net-shared-utils", + ] } // Updatable network stack packaged as an application diff --git a/services/net/java/android/net/dhcp/DhcpLease.java b/packages/NetworkStack/src/android/net/dhcp/DhcpLease.java index 6849cfadc22a..6849cfadc22a 100644 --- a/services/net/java/android/net/dhcp/DhcpLease.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpLease.java diff --git a/services/net/java/android/net/dhcp/DhcpLeaseRepository.java b/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java index b3d0512ba447..0d298de4f5f8 100644 --- a/services/net/java/android/net/dhcp/DhcpLeaseRepository.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java @@ -21,7 +21,8 @@ import static android.net.NetworkUtils.intToInet4AddressHTH; import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTH; import static android.net.dhcp.DhcpLease.EXPIRATION_NEVER; import static android.net.dhcp.DhcpLease.inet4AddrToString; -import static android.net.util.NetworkConstants.IPV4_ADDR_BITS; + +import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_BITS; import static java.lang.Math.min; diff --git a/services/net/java/android/net/dhcp/DhcpPacketListener.java b/packages/NetworkStack/src/android/net/dhcp/DhcpPacketListener.java index dce8b619494e..dce8b619494e 100644 --- a/services/net/java/android/net/dhcp/DhcpPacketListener.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpPacketListener.java diff --git a/services/net/java/android/net/dhcp/DhcpServer.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java index 641bba2ed306..14e293694ebd 100644 --- a/services/net/java/android/net/dhcp/DhcpServer.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java @@ -23,7 +23,8 @@ import static android.net.dhcp.DhcpPacket.DHCP_CLIENT; import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME; import static android.net.dhcp.DhcpPacket.DHCP_SERVER; import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP; -import static android.net.dhcp.DhcpPacket.INFINITE_LEASE; +import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT; +import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.IPPROTO_UDP; import static android.system.OsConstants.SOCK_DGRAM; @@ -32,21 +33,28 @@ import static android.system.OsConstants.SO_BINDTODEVICE; import static android.system.OsConstants.SO_BROADCAST; import static android.system.OsConstants.SO_REUSEADDR; +import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE; +import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission; + import static java.lang.Integer.toUnsignedLong; import android.annotation.NonNull; import android.annotation.Nullable; +import android.net.INetworkStackStatusCallback; import android.net.MacAddress; import android.net.NetworkUtils; import android.net.TrafficStats; import android.net.util.SharedLog; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.os.Message; +import android.os.RemoteException; import android.os.SystemClock; import android.system.ErrnoException; import android.system.Os; import android.text.TextUtils; +import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.HexDump; @@ -70,7 +78,7 @@ import java.util.ArrayList; * on the looper asynchronously. * @hide */ -public class DhcpServer { +public class DhcpServer extends IDhcpServer.Stub { private static final String REPO_TAG = "Repository"; // Lease time to transmit to client instead of a negative time in case a lease expired before @@ -82,7 +90,7 @@ public class DhcpServer { private static final int CMD_UPDATE_PARAMS = 3; @NonNull - private final ServerHandler mHandler; + private final HandlerThread mHandlerThread; @NonNull private final String mIfName; @NonNull @@ -93,10 +101,14 @@ public class DhcpServer { private final Dependencies mDeps; @NonNull private final Clock mClock; - @NonNull - private final DhcpPacketListener mPacketListener; @Nullable + private volatile ServerHandler mHandler; + + // Accessed only on the handler thread + @Nullable + private DhcpPacketListener mPacketListener; + @Nullable private FileDescriptor mSocket; @NonNull private DhcpServingParams mServingParams; @@ -156,6 +168,12 @@ public class DhcpServer { */ void addArpEntry(@NonNull Inet4Address ipv4Addr, @NonNull MacAddress ethAddr, @NonNull String ifname, @NonNull FileDescriptor fd) throws IOException; + + /** + * Verify that the caller is allowed to call public methods on DhcpServer. + * @throws SecurityException The caller is not allowed to call public methods on DhcpServer. + */ + void checkCaller() throws SecurityException; } private class DependenciesImpl implements Dependencies { @@ -189,6 +207,11 @@ public class DhcpServer { @NonNull String ifname, @NonNull FileDescriptor fd) throws IOException { NetworkUtils.addArpEntry(ipv4Addr, ethAddr, ifname, fd); } + + @Override + public void checkCaller() { + checkNetworkStackCallingPermission(); + } } private static class MalformedPacketException extends Exception { @@ -197,41 +220,62 @@ public class DhcpServer { } } - public DhcpServer(@NonNull Looper looper, @NonNull String ifName, + public DhcpServer(@NonNull String ifName, @NonNull DhcpServingParams params, @NonNull SharedLog log) { - this(looper, ifName, params, log, null); + this(new HandlerThread(DhcpServer.class.getSimpleName() + "." + ifName), + ifName, params, log, null); } @VisibleForTesting - DhcpServer(@NonNull Looper looper, @NonNull String ifName, + DhcpServer(@NonNull HandlerThread handlerThread, @NonNull String ifName, @NonNull DhcpServingParams params, @NonNull SharedLog log, @Nullable Dependencies deps) { if (deps == null) { deps = new DependenciesImpl(); } - mHandler = new ServerHandler(looper); + mHandlerThread = handlerThread; mIfName = ifName; mServingParams = params; mLog = log; mDeps = deps; mClock = deps.makeClock(); - mPacketListener = deps.makePacketListener(); mLeaseRepo = deps.makeLeaseRepository(mServingParams, mLog, mClock); } /** * Start listening for and responding to packets. + * + * <p>It is not legal to call this method more than once; in particular the server cannot be + * restarted after being stopped. */ - public void start() { - mHandler.sendEmptyMessage(CMD_START_DHCP_SERVER); + @Override + public void start(@Nullable INetworkStackStatusCallback cb) { + mDeps.checkCaller(); + mHandlerThread.start(); + mHandler = new ServerHandler(mHandlerThread.getLooper()); + sendMessage(CMD_START_DHCP_SERVER, cb); } /** * Update serving parameters. All subsequently received requests will be handled with the new * parameters, and current leases that are incompatible with the new parameters are dropped. */ - public void updateParams(@NonNull DhcpServingParams params) { - sendMessage(CMD_UPDATE_PARAMS, params); + @Override + public void updateParams(@Nullable DhcpServingParamsParcel params, + @Nullable INetworkStackStatusCallback cb) throws RemoteException { + mDeps.checkCaller(); + final DhcpServingParams parsedParams; + try { + // throws InvalidParameterException with null params + parsedParams = DhcpServingParams.fromParcelableObject(params); + } catch (DhcpServingParams.InvalidParameterException e) { + mLog.e("Invalid parameters sent to DhcpServer", e); + if (cb != null) { + cb.onStatusAvailable(STATUS_INVALID_ARGUMENT); + } + return; + } + sendMessage(CMD_UPDATE_PARAMS, new Pair<>(parsedParams, cb)); } /** @@ -240,11 +284,17 @@ public class DhcpServer { * <p>As the server is stopped asynchronously, some packets may still be processed shortly after * calling this method. */ - public void stop() { - mHandler.sendEmptyMessage(CMD_STOP_DHCP_SERVER); + @Override + public void stop(@Nullable INetworkStackStatusCallback cb) { + mDeps.checkCaller(); + sendMessage(CMD_STOP_DHCP_SERVER, cb); } private void sendMessage(int what, @Nullable Object obj) { + if (mHandler == null) { + mLog.e("Attempting to send a command to stopped DhcpServer: " + what); + return; + } mHandler.sendMessage(mHandler.obtainMessage(what, obj)); } @@ -255,23 +305,42 @@ public class DhcpServer { @Override public void handleMessage(@NonNull Message msg) { + final INetworkStackStatusCallback cb; switch (msg.what) { case CMD_UPDATE_PARAMS: - final DhcpServingParams params = (DhcpServingParams) msg.obj; + final Pair<DhcpServingParams, INetworkStackStatusCallback> pair = + (Pair<DhcpServingParams, INetworkStackStatusCallback>) msg.obj; + final DhcpServingParams params = pair.first; mServingParams = params; mLeaseRepo.updateParams( DhcpServingParams.makeIpPrefix(mServingParams.serverAddr), params.excludedAddrs, params.dhcpLeaseTimeSecs); + + cb = pair.second; break; case CMD_START_DHCP_SERVER: - // This is a no-op if the listener is already started + mPacketListener = mDeps.makePacketListener(); mPacketListener.start(); + cb = (INetworkStackStatusCallback) msg.obj; break; case CMD_STOP_DHCP_SERVER: - // This is a no-op if the listener was not started - mPacketListener.stop(); + if (mPacketListener != null) { + mPacketListener.stop(); + mPacketListener = null; + } + mHandlerThread.quitSafely(); + cb = (INetworkStackStatusCallback) msg.obj; break; + default: + return; + } + if (cb != null) { + try { + cb.onStatusAvailable(STATUS_SUCCESS); + } catch (RemoteException e) { + mLog.e("Could not send status back to caller", e); + } } } } @@ -572,7 +641,7 @@ public class DhcpServer { return mSocket; } catch (IOException | ErrnoException e) { mLog.e("Error creating UDP socket", e); - DhcpServer.this.stop(); + DhcpServer.this.stop(null); return null; } finally { TrafficStats.setThreadStatsTag(oldTag); diff --git a/services/net/java/android/net/dhcp/DhcpServingParams.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java index 2780814a2f33..f38888aafbd6 100644 --- a/services/net/java/android/net/dhcp/DhcpServingParams.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java @@ -18,9 +18,10 @@ package android.net.dhcp; import static android.net.NetworkUtils.getPrefixMaskAsInet4Address; import static android.net.NetworkUtils.intToInet4AddressHTH; -import static android.net.dhcp.DhcpPacket.INFINITE_LEASE; -import static android.net.util.NetworkConstants.IPV4_MAX_MTU; -import static android.net.util.NetworkConstants.IPV4_MIN_MTU; + +import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE; +import static com.android.server.util.NetworkStackConstants.IPV4_MAX_MTU; +import static com.android.server.util.NetworkStackConstants.IPV4_MIN_MTU; import static java.lang.Integer.toUnsignedLong; @@ -107,9 +108,13 @@ public class DhcpServingParams { /** * Create parameters from a stable AIDL-compatible parcel. + * @throws InvalidParameterException The parameters parcelable is null or invalid. */ - public static DhcpServingParams fromParcelableObject(@NonNull DhcpServingParamsParcel parcel) + public static DhcpServingParams fromParcelableObject(@Nullable DhcpServingParamsParcel parcel) throws InvalidParameterException { + if (parcel == null) { + throw new InvalidParameterException("Null serving parameters"); + } final LinkAddress serverAddr = new LinkAddress( intToInet4AddressHTH(parcel.serverAddr), parcel.serverAddrPrefixLength); diff --git a/packages/NetworkStack/src/android/net/util/SharedLog.java b/packages/NetworkStack/src/android/net/util/SharedLog.java new file mode 100644 index 000000000000..74bc1470293f --- /dev/null +++ b/packages/NetworkStack/src/android/net/util/SharedLog.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.text.TextUtils; +import android.util.LocalLog; +import android.util.Log; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.StringJoiner; + + +/** + * Class to centralize logging functionality for tethering. + * + * All access to class methods other than dump() must be on the same thread. + * + * @hide + */ +public class SharedLog { + private static final int DEFAULT_MAX_RECORDS = 500; + private static final String COMPONENT_DELIMITER = "."; + + private enum Category { + NONE, + ERROR, + MARK, + WARN, + }; + + private final LocalLog mLocalLog; + // The tag to use for output to the system log. This is not output to the + // LocalLog because that would be redundant. + private final String mTag; + // The component (or subcomponent) of a system that is sharing this log. + // This can grow in depth if components call forSubComponent() to obtain + // their SharedLog instance. The tag is not included in the component for + // brevity. + private final String mComponent; + + public SharedLog(String tag) { + this(DEFAULT_MAX_RECORDS, tag); + } + + public SharedLog(int maxRecords, String tag) { + this(new LocalLog(maxRecords), tag, tag); + } + + private SharedLog(LocalLog localLog, String tag, String component) { + mLocalLog = localLog; + mTag = tag; + mComponent = component; + } + + /** + * Create a SharedLog based on this log with an additional component prefix on each logged line. + */ + public SharedLog forSubComponent(String component) { + if (!isRootLogInstance()) { + component = mComponent + COMPONENT_DELIMITER + component; + } + return new SharedLog(mLocalLog, mTag, component); + } + + /** + * Dump the contents of this log. + * + * <p>This method may be called on any thread. + */ + public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + mLocalLog.readOnlyLocalLog().dump(fd, writer, args); + } + + ////// + // Methods that both log an entry and emit it to the system log. + ////// + + /** + * Log an error due to an exception. This does not include the exception stacktrace. + * + * <p>The log entry will be also added to the system log. + * @see #e(String, Throwable) + */ + public void e(Exception e) { + Log.e(mTag, record(Category.ERROR, e.toString())); + } + + /** + * Log an error message. + * + * <p>The log entry will be also added to the system log. + */ + public void e(String msg) { + Log.e(mTag, record(Category.ERROR, msg)); + } + + /** + * Log an error due to an exception, with the exception stacktrace if provided. + * + * <p>The error and exception message appear in the shared log, but the stacktrace is only + * logged in general log output (logcat). The log entry will be also added to the system log. + */ + public void e(@NonNull String msg, @Nullable Throwable exception) { + if (exception == null) { + e(msg); + return; + } + Log.e(mTag, record(Category.ERROR, msg + ": " + exception.getMessage()), exception); + } + + /** + * Log an informational message. + * + * <p>The log entry will be also added to the system log. + */ + public void i(String msg) { + Log.i(mTag, record(Category.NONE, msg)); + } + + /** + * Log a warning message. + * + * <p>The log entry will be also added to the system log. + */ + public void w(String msg) { + Log.w(mTag, record(Category.WARN, msg)); + } + + ////// + // Methods that only log an entry (and do NOT emit to the system log). + ////// + + /** + * Log a general message to be only included in the in-memory log. + * + * <p>The log entry will *not* be added to the system log. + */ + public void log(String msg) { + record(Category.NONE, msg); + } + + /** + * Log a general, formatted message to be only included in the in-memory log. + * + * <p>The log entry will *not* be added to the system log. + * @see String#format(String, Object...) + */ + public void logf(String fmt, Object... args) { + log(String.format(fmt, args)); + } + + /** + * Log a message with MARK level. + * + * <p>The log entry will *not* be added to the system log. + */ + public void mark(String msg) { + record(Category.MARK, msg); + } + + private String record(Category category, String msg) { + final String entry = logLine(category, msg); + mLocalLog.log(entry); + return entry; + } + + private String logLine(Category category, String msg) { + final StringJoiner sj = new StringJoiner(" "); + if (!isRootLogInstance()) sj.add("[" + mComponent + "]"); + if (category != Category.NONE) sj.add(category.toString()); + return sj.add(msg).toString(); + } + + // Check whether this SharedLog instance is nominally the top level in + // a potential hierarchy of shared logs (the root of a tree), + // or is a subcomponent within the hierarchy. + private boolean isRootLogInstance() { + return TextUtils.isEmpty(mComponent) || mComponent.equals(mTag); + } +} diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java index 5afaf586f74d..7fea1e038cee 100644 --- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java +++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java @@ -16,15 +16,24 @@ package com.android.server; -import static android.os.Binder.getCallingUid; +import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT; +import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; +import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR; + +import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Service; import android.content.Intent; import android.net.INetworkStackConnector; +import android.net.dhcp.DhcpServer; +import android.net.dhcp.DhcpServingParams; +import android.net.dhcp.DhcpServingParamsParcel; +import android.net.dhcp.IDhcpServerCallbacks; +import android.net.util.SharedLog; import android.os.IBinder; -import android.os.Process; +import android.os.RemoteException; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -54,21 +63,37 @@ public class NetworkStackService extends Service { } private static class NetworkStackConnector extends INetworkStackConnector.Stub { - // TODO: makeDhcpServer(), etc. will go here. + @NonNull + private final SharedLog mLog = new SharedLog(TAG); + + @Override + public void makeDhcpServer(@NonNull String ifName, @NonNull DhcpServingParamsParcel params, + @NonNull IDhcpServerCallbacks cb) throws RemoteException { + checkNetworkStackCallingPermission(); + final DhcpServer server; + try { + server = new DhcpServer( + ifName, + DhcpServingParams.fromParcelableObject(params), + mLog.forSubComponent(ifName + ".DHCP")); + } catch (DhcpServingParams.InvalidParameterException e) { + mLog.e("Invalid DhcpServingParams", e); + cb.onDhcpServerCreated(STATUS_INVALID_ARGUMENT, null); + return; + } catch (Exception e) { + mLog.e("Unknown error starting DhcpServer", e); + cb.onDhcpServerCreated(STATUS_UNKNOWN_ERROR, null); + return; + } + cb.onDhcpServerCreated(STATUS_SUCCESS, server); + } @Override protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout, @Nullable String[] args) { - checkCaller(); + checkNetworkStackCallingPermission(); fout.println("NetworkStack logs:"); - // TODO: dump logs here - } - } - - private static void checkCaller() { - // TODO: check that the calling PID is the system server. - if (getCallingUid() != Process.SYSTEM_UID && getCallingUid() != Process.ROOT_UID) { - throw new SecurityException("Invalid caller: " + getCallingUid()); + mLog.dump(fd, fout, args); } } } diff --git a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java new file mode 100644 index 000000000000..bb5900c53e52 --- /dev/null +++ b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java @@ -0,0 +1,45 @@ +/* + * 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. + */ + +package com.android.server.util; + +/** + * Network constants used by the network stack. + */ +public final class NetworkStackConstants { + + /** + * IPv4 constants. + * + * See also: + * - https://tools.ietf.org/html/rfc791 + */ + public static final int IPV4_ADDR_BITS = 32; + public static final int IPV4_MIN_MTU = 68; + public static final int IPV4_MAX_MTU = 65_535; + + /** + * DHCP constants. + * + * See also: + * - https://tools.ietf.org/html/rfc2131 + */ + public static final int INFINITE_LEASE = 0xffffffff; + + private NetworkStackConstants() { + throw new UnsupportedOperationException("This class is not to be instantiated"); + } +} diff --git a/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java b/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java new file mode 100644 index 000000000000..733f87393c32 --- /dev/null +++ b/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java @@ -0,0 +1,42 @@ +/* + * 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. + */ + +package com.android.server.util; + +import static android.os.Binder.getCallingUid; + +import android.os.Process; + +/** + * Utility class to check calling permissions on the network stack. + */ +public final class PermissionUtil { + + /** + * Check that the caller is allowed to communicate with the network stack. + * @throws SecurityException The caller is not allowed to communicate with the network stack. + */ + public static void checkNetworkStackCallingPermission() { + // TODO: check that the calling PID is the system server. + if (getCallingUid() != Process.SYSTEM_UID && getCallingUid() != Process.ROOT_UID) { + throw new SecurityException("Invalid caller: " + getCallingUid()); + } + } + + private PermissionUtil() { + throw new UnsupportedOperationException("This class is not to be instantiated"); + } +} diff --git a/packages/NetworkStack/tests/Android.bp b/packages/NetworkStack/tests/Android.bp new file mode 100644 index 000000000000..bd7ff2a75703 --- /dev/null +++ b/packages/NetworkStack/tests/Android.bp @@ -0,0 +1,35 @@ +// +// 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. +// + +android_test { + name: "NetworkStackTests", + srcs: ["src/**/*.java"], + static_libs: [ + "android-support-test", + "mockito-target-extended-minus-junit4", + "NetworkStackLib", + "testables", + ], + libs: [ + "android.test.runner", + "android.test.base", + ], + jni_libs: [ + // For mockito extended + "libdexmakerjvmtiagent", + "libstaticjvmtiagent", + ] +}
\ No newline at end of file diff --git a/packages/NetworkStack/tests/AndroidManifest.xml b/packages/NetworkStack/tests/AndroidManifest.xml new file mode 100644 index 000000000000..8b8474f57e28 --- /dev/null +++ b/packages/NetworkStack/tests/AndroidManifest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.networkstack.tests"> + <application android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.networkstack.tests" + android:label="Networking service tests"> + </instrumentation> +</manifest>
\ No newline at end of file diff --git a/packages/NetworkStack/tests/AndroidTest.xml b/packages/NetworkStack/tests/AndroidTest.xml new file mode 100644 index 000000000000..6b08b57731b7 --- /dev/null +++ b/packages/NetworkStack/tests/AndroidTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Runs Tests for NetworkStack"> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="NetworkStackTests.apk" /> + </target_preparer> + + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="framework-base-presubmit" /> + <option name="test-tag" value="NetworkStackTests" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.server.networkstack.tests" /> + <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration>
\ No newline at end of file diff --git a/tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java index ba0448c98387..51d50d9eb13a 100644 --- a/tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java +++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java @@ -16,6 +16,7 @@ package android.net.dhcp; +import static android.net.InetAddresses.parseNumericAddress; import static android.net.dhcp.DhcpLease.HOSTNAME_NONE; import static android.net.dhcp.DhcpLeaseRepository.CLIENTID_UNSPEC; import static android.net.dhcp.DhcpLeaseRepository.INETADDR_UNSPEC; @@ -29,7 +30,6 @@ import static org.junit.Assert.fail; import static org.mockito.Mockito.when; import static java.lang.String.format; -import static java.net.InetAddress.parseNumericAddress; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/tests/net/java/android/net/dhcp/DhcpServerTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServerTest.java index ab9bd84b05cb..d4c1e2e16731 100644 --- a/tests/net/java/android/net/dhcp/DhcpServerTest.java +++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServerTest.java @@ -16,11 +16,13 @@ package android.net.dhcp; +import static android.net.InetAddresses.parseNumericAddress; import static android.net.dhcp.DhcpPacket.DHCP_CLIENT; import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME; import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP; import static android.net.dhcp.DhcpPacket.INADDR_ANY; import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST; +import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; @@ -33,14 +35,14 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static java.net.InetAddress.parseNumericAddress; - import android.annotation.NonNull; import android.annotation.Nullable; +import android.net.INetworkStackStatusCallback; import android.net.LinkAddress; import android.net.MacAddress; import android.net.dhcp.DhcpLeaseRepository.InvalidAddressException; @@ -48,9 +50,11 @@ import android.net.dhcp.DhcpLeaseRepository.OutOfAddressesException; import android.net.dhcp.DhcpServer.Clock; import android.net.dhcp.DhcpServer.Dependencies; import android.net.util.SharedLog; -import android.os.test.TestLooper; +import android.os.HandlerThread; import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; import org.junit.After; import org.junit.Before; @@ -67,10 +71,10 @@ import java.util.Arrays; import java.util.HashSet; import java.util.Set; -@RunWith(AndroidJUnit4.class) +@RunWith(AndroidTestingRunner.class) @SmallTest +@RunWithLooper public class DhcpServerTest { - private static final String PROP_DEXMAKER_SHARE_CLASSLOADER = "dexmaker.share_classloader"; private static final String TEST_IFACE = "testiface"; private static final Inet4Address TEST_SERVER_ADDR = parseAddr("192.168.0.2"); @@ -113,18 +117,25 @@ public class DhcpServerTest { private ArgumentCaptor<Inet4Address> mResponseDstAddrCaptor; @NonNull - private TestLooper mLooper; + private HandlerThread mHandlerThread; + @NonNull + private TestableLooper mLooper; @NonNull private DhcpServer mServer; @Nullable private String mPrevShareClassloaderProp; + private final INetworkStackStatusCallback mAssertSuccessCallback = + new INetworkStackStatusCallback.Stub() { + @Override + public void onStatusAvailable(int statusCode) { + assertEquals(STATUS_SUCCESS, statusCode); + } + }; + @Before public void setUp() throws Exception { - // Allow mocking package-private classes - mPrevShareClassloaderProp = System.getProperty(PROP_DEXMAKER_SHARE_CLASSLOADER); - System.setProperty(PROP_DEXMAKER_SHARE_CLASSLOADER, "true"); MockitoAnnotations.initMocks(this); when(mDeps.makeLeaseRepository(any(), any(), any())).thenReturn(mRepository); @@ -143,20 +154,22 @@ public class DhcpServerTest { .setExcludedAddrs(TEST_EXCLUDED_ADDRS) .build(); - mLooper = new TestLooper(); - mServer = new DhcpServer(mLooper.getLooper(), TEST_IFACE, servingParams, + mLooper = TestableLooper.get(this); + mHandlerThread = spy(new HandlerThread("TestDhcpServer")); + when(mHandlerThread.getLooper()).thenReturn(mLooper.getLooper()); + mServer = new DhcpServer(mHandlerThread, TEST_IFACE, servingParams, new SharedLog(DhcpServerTest.class.getSimpleName()), mDeps); - mServer.start(); - mLooper.dispatchAll(); + mServer.start(mAssertSuccessCallback); + mLooper.processAllMessages(); } @After - public void tearDown() { - // Calling stop() several times is not an issue - mServer.stop(); - System.setProperty(PROP_DEXMAKER_SHARE_CLASSLOADER, - (mPrevShareClassloaderProp == null ? "" : mPrevShareClassloaderProp)); + public void tearDown() throws Exception { + mServer.stop(mAssertSuccessCallback); + mLooper.processMessages(1); + verify(mPacketListener, times(1)).stop(); + verify(mHandlerThread, times(1)).quitSafely(); } @Test @@ -165,13 +178,6 @@ public class DhcpServerTest { } @Test - public void testStop() throws Exception { - mServer.stop(); - mLooper.dispatchAll(); - verify(mPacketListener, times(1)).stop(); - } - - @Test public void testDiscover() throws Exception { // TODO: refactor packet construction to eliminate unnecessary/confusing/duplicate fields when(mRepository.getOffer(isNull() /* clientId */, eq(TEST_CLIENT_MAC), diff --git a/tests/net/java/android/net/dhcp/DhcpServingParamsTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java index 2ab224667b8a..3ca0564f24d6 100644 --- a/tests/net/java/android/net/dhcp/DhcpServingParamsTest.java +++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java @@ -16,6 +16,7 @@ package android.net.dhcp; +import static android.net.InetAddresses.parseNumericAddress; import static android.net.NetworkUtils.inet4AddressToIntHTH; import static android.net.dhcp.DhcpServingParams.MTU_UNSET; @@ -23,8 +24,6 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; -import static java.net.InetAddress.parseNumericAddress; - import android.annotation.NonNull; import android.annotation.Nullable; import android.net.LinkAddress; @@ -195,6 +194,11 @@ public class DhcpServingParamsTest { assertEquals(7, numFields); } + @Test(expected = InvalidParameterException.class) + public void testFromParcelableObject_NullArgument() throws InvalidParameterException { + DhcpServingParams.fromParcelableObject(null); + } + private static int[] toIntArray(Collection<Inet4Address> addrs) { return addrs.stream().mapToInt(NetworkUtils::inet4AddressToIntHTH).toArray(); } diff --git a/packages/NetworkStack/tests/src/com/android/server/util/SharedLogTest.java b/packages/NetworkStack/tests/src/com/android/server/util/SharedLogTest.java new file mode 100644 index 000000000000..07ad3123bc53 --- /dev/null +++ b/packages/NetworkStack/tests/src/com/android/server/util/SharedLogTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2017 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.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import android.net.util.SharedLog; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class SharedLogTest { + private static final String TIMESTAMP_PATTERN = "\\d{2}:\\d{2}:\\d{2}"; + private static final String TIMESTAMP = "HH:MM:SS"; + + @Test + public void testBasicOperation() { + final SharedLog logTop = new SharedLog("top"); + logTop.mark("first post!"); + + final SharedLog logLevel2a = logTop.forSubComponent("twoA"); + final SharedLog logLevel2b = logTop.forSubComponent("twoB"); + logLevel2b.e("2b or not 2b"); + logLevel2b.e("No exception", null); + logLevel2b.e("Wait, here's one", new Exception("Test")); + logLevel2a.w("second post?"); + + final SharedLog logLevel3 = logLevel2a.forSubComponent("three"); + logTop.log("still logging"); + logLevel3.log("3 >> 2"); + logLevel2a.mark("ok: last post"); + + final String[] expected = { + " - MARK first post!", + " - [twoB] ERROR 2b or not 2b", + " - [twoB] ERROR No exception", + // No stacktrace in shared log, only in logcat + " - [twoB] ERROR Wait, here's one: Test", + " - [twoA] WARN second post?", + " - still logging", + " - [twoA.three] 3 >> 2", + " - [twoA] MARK ok: last post", + }; + // Verify the logs are all there and in the correct order. + verifyLogLines(expected, logTop); + + // In fact, because they all share the same underlying LocalLog, + // every subcomponent SharedLog's dump() is identical. + verifyLogLines(expected, logLevel2a); + verifyLogLines(expected, logLevel2b); + verifyLogLines(expected, logLevel3); + } + + private static void verifyLogLines(String[] expected, SharedLog log) { + final ByteArrayOutputStream ostream = new ByteArrayOutputStream(); + final PrintWriter pw = new PrintWriter(ostream, true); + log.dump(null, pw, null); + + final String dumpOutput = ostream.toString(); + assertTrue(dumpOutput != null); + assertTrue(!"".equals(dumpOutput)); + + final String[] lines = dumpOutput.split("\n"); + assertEquals(expected.length, lines.length); + + for (int i = 0; i < expected.length; i++) { + String got = lines[i]; + String want = expected[i]; + assertTrue(String.format("'%s' did not contain '%s'", got, want), got.endsWith(want)); + assertTrue(String.format("'%s' did not contain a %s timestamp", got, TIMESTAMP), + got.replaceFirst(TIMESTAMP_PATTERN, TIMESTAMP).contains(TIMESTAMP)); + } + } +} diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 9dfdddbea18a..eb5be77e4a33 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -1837,7 +1837,7 @@ public class Tethering extends BaseNetworkObserver { final TetherState tetherState = new TetherState( new IpServer(iface, mLooper, interfaceType, mLog, mNMService, mStatsService, makeControlCallback(), mConfig.enableLegacyDhcpServer, - mDeps.getIpServerDependencies())); + mDeps.getIpServerDependencies(mContext))); mTetherStates.put(iface, tetherState); tetherState.ipServer.start(); } diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java index 7daf71dda73b..a42efe960ff9 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java @@ -60,8 +60,8 @@ public class TetheringDependencies { /** * Get dependencies to be used by IpServer. */ - public IpServer.Dependencies getIpServerDependencies() { - return new IpServer.Dependencies(); + public IpServer.Dependencies getIpServerDependencies(Context context) { + return new IpServer.Dependencies(context); } /** diff --git a/services/net/Android.bp b/services/net/Android.bp index e0ae68f20483..ae697b7f093a 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -2,3 +2,19 @@ java_library_static { name: "services.net", srcs: ["java/**/*.java"], } + +// TODO: move to networking module with DhcpClient and remove lib +java_library { + name: "dhcp-packet-lib", + srcs: [ + "java/android/net/dhcp/*Packet.java", + ] +} + +// TODO: move to networking module with IpNeighborMonitor/ConnectivityPacketTracker and remove lib +java_library { + name: "frameworks-net-shared-utils", + srcs: [ + "java/android/net/util/FdEventsReader.java", + ] +}
\ No newline at end of file diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java index 6ba7d94117b1..ce8b7e78d0f8 100644 --- a/services/net/java/android/net/dhcp/DhcpPacket.java +++ b/services/net/java/android/net/dhcp/DhcpPacket.java @@ -1,8 +1,5 @@ package android.net.dhcp; -import static android.net.util.NetworkConstants.IPV4_MAX_MTU; -import static android.net.util.NetworkConstants.IPV4_MIN_MTU; - import android.annotation.Nullable; import android.net.DhcpResults; import android.net.LinkAddress; @@ -37,6 +34,9 @@ import java.util.List; public abstract class DhcpPacket { protected static final String TAG = "DhcpPacket"; + // TODO: use NetworkStackConstants.IPV4_MIN_MTU once this class is moved to the network stack. + private static final int IPV4_MIN_MTU = 68; + // dhcpcd has a minimum lease of 20 seconds, but DhcpStateMachine would refuse to wake up the // CPU for anything shorter than 5 minutes. For sanity's sake, this must be higher than the // DHCP client timeout. diff --git a/services/net/java/android/net/ip/IpServer.java b/services/net/java/android/net/ip/IpServer.java index 493350d776f3..8b22f68286af 100644 --- a/services/net/java/android/net/ip/IpServer.java +++ b/services/net/java/android/net/ip/IpServer.java @@ -17,20 +17,26 @@ package android.net.ip; import static android.net.NetworkUtils.numericToInetAddress; -import static android.net.util.NetworkConstants.asByte; +import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.util.NetworkConstants.FF; import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH; +import static android.net.util.NetworkConstants.asByte; +import android.content.Context; import android.net.ConnectivityManager; import android.net.INetd; +import android.net.INetworkStackStatusCallback; import android.net.INetworkStatsService; import android.net.InterfaceConfiguration; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.NetworkStack; import android.net.RouteInfo; -import android.net.dhcp.DhcpServer; -import android.net.dhcp.DhcpServingParams; +import android.net.dhcp.DhcpServerCallbacks; +import android.net.dhcp.DhcpServingParamsParcel; +import android.net.dhcp.DhcpServingParamsParcelExt; +import android.net.dhcp.IDhcpServer; import android.net.ip.RouterAdvertisementDaemon.RaParams; import android.net.util.InterfaceParams; import android.net.util.InterfaceSet; @@ -126,6 +132,10 @@ public class IpServer extends StateMachine { } public static class Dependencies { + private final Context mContext; + public Dependencies(Context context) { + mContext = context; + } public RouterAdvertisementDaemon getRouterAdvertisementDaemon(InterfaceParams ifParams) { return new RouterAdvertisementDaemon(ifParams); } @@ -138,9 +148,12 @@ public class IpServer extends StateMachine { return NetdService.getInstance(); } - public DhcpServer makeDhcpServer(Looper looper, String ifName, - DhcpServingParams params, SharedLog log) { - return new DhcpServer(looper, ifName, params, log); + /** + * Create a DhcpServer instance to be used by IpServer. + */ + public void makeDhcpServer(String ifName, DhcpServingParamsParcel params, + DhcpServerCallbacks cb) { + mContext.getSystemService(NetworkStack.class).makeDhcpServer(ifName, params, cb); } } @@ -197,7 +210,10 @@ public class IpServer extends StateMachine { // Advertisements (otherwise, we do not add them to mLinkProperties at all). private LinkProperties mLastIPv6LinkProperties; private RouterAdvertisementDaemon mRaDaemon; - private DhcpServer mDhcpServer; + + // To be accessed only on the handler thread + private int mDhcpServerStartIndex = 0; + private IDhcpServer mDhcpServer; private RaParams mLastRaParams; public IpServer( @@ -252,35 +268,109 @@ public class IpServer extends StateMachine { private boolean startIPv4() { return configureIPv4(true); } + /** + * Convenience wrapper around INetworkStackStatusCallback to run callbacks on the IpServer + * handler. + * + * <p>Different instances of this class can be created for each call to IDhcpServer methods, + * with different implementations of the callback, to differentiate handling of success/error in + * each call. + */ + private abstract class OnHandlerStatusCallback extends INetworkStackStatusCallback.Stub { + @Override + public void onStatusAvailable(int statusCode) { + getHandler().post(() -> callback(statusCode)); + } + + public abstract void callback(int statusCode); + } + + private class DhcpServerCallbacksImpl extends DhcpServerCallbacks { + private final int mStartIndex; + + private DhcpServerCallbacksImpl(int startIndex) { + mStartIndex = startIndex; + } + + @Override + public void onDhcpServerCreated(int statusCode, IDhcpServer server) throws RemoteException { + getHandler().post(() -> { + // We are on the handler thread: mDhcpServerStartIndex can be read safely. + if (mStartIndex != mDhcpServerStartIndex) { + // This start request is obsolete. When the |server| binder token goes out of + // scope, the garbage collector will finalize it, which causes the network stack + // process garbage collector to collect the server itself. + return; + } + + if (statusCode != STATUS_SUCCESS) { + mLog.e("Error obtaining DHCP server: " + statusCode); + handleError(); + return; + } + + mDhcpServer = server; + try { + mDhcpServer.start(new OnHandlerStatusCallback() { + @Override + public void callback(int startStatusCode) { + if (startStatusCode != STATUS_SUCCESS) { + mLog.e("Error starting DHCP server: " + startStatusCode); + handleError(); + } + } + }); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + }); + } + + private void handleError() { + mLastError = ConnectivityManager.TETHER_ERROR_DHCPSERVER_ERROR; + transitionTo(mInitialState); + } + } + private boolean startDhcp(Inet4Address addr, int prefixLen) { if (mUsingLegacyDhcp) { return true; } - final DhcpServingParams params; - try { - params = new DhcpServingParams.Builder() - .setDefaultRouters(addr) - .setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS) - .setDnsServers(addr) - .setServerAddr(new LinkAddress(addr, prefixLen)) - .setMetered(true) - .build(); - // TODO: also advertise link MTU - } catch (DhcpServingParams.InvalidParameterException e) { - Log.e(TAG, "Invalid DHCP parameters", e); - return false; - } - - mDhcpServer = mDeps.makeDhcpServer(getHandler().getLooper(), mIfaceName, params, - mLog.forSubComponent("DHCP")); - mDhcpServer.start(); + final DhcpServingParamsParcel params; + params = new DhcpServingParamsParcelExt() + .setDefaultRouters(addr) + .setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS) + .setDnsServers(addr) + .setServerAddr(new LinkAddress(addr, prefixLen)) + .setMetered(true); + // TODO: also advertise link MTU + + mDhcpServerStartIndex++; + mDeps.makeDhcpServer( + mIfaceName, params, new DhcpServerCallbacksImpl(mDhcpServerStartIndex)); return true; } private void stopDhcp() { + // Make all previous start requests obsolete so servers are not started later + mDhcpServerStartIndex++; + if (mDhcpServer != null) { - mDhcpServer.stop(); - mDhcpServer = null; + try { + mDhcpServer.stop(new OnHandlerStatusCallback() { + @Override + public void callback(int statusCode) { + if (statusCode != STATUS_SUCCESS) { + mLog.e("Error stopping DHCP server: " + statusCode); + mLastError = ConnectivityManager.TETHER_ERROR_DHCPSERVER_ERROR; + // Not much more we can do here + } + } + }); + mDhcpServer = null; + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } } } diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java index 3defe56939f5..c183b81362dc 100644 --- a/services/net/java/android/net/util/NetworkConstants.java +++ b/services/net/java/android/net/util/NetworkConstants.java @@ -16,9 +16,6 @@ package android.net.util; -import java.nio.ByteBuffer; - - /** * Networking protocol constants. * @@ -81,8 +78,6 @@ public final class NetworkConstants { * - https://tools.ietf.org/html/rfc791 */ public static final int IPV4_HEADER_MIN_LEN = 20; - public static final int IPV4_MIN_MTU = 68; - public static final int IPV4_MAX_MTU = 65_535; public static final int IPV4_IHL_MASK = 0xf; public static final int IPV4_FLAGS_OFFSET = 6; public static final int IPV4_FRAGMENT_MASK = 0x1fff; diff --git a/services/net/java/android/net/util/SharedLog.java b/services/net/java/android/net/util/SharedLog.java index 74bc1470293f..8b7b59d20978 100644 --- a/services/net/java/android/net/util/SharedLog.java +++ b/services/net/java/android/net/util/SharedLog.java @@ -32,6 +32,7 @@ import java.util.StringJoiner; * * All access to class methods other than dump() must be on the same thread. * + * TODO: this is a copy of SharedLog in the NetworkStack. Remove after Tethering is migrated. * @hide */ public class SharedLog { diff --git a/tests/net/java/android/net/ip/IpServerTest.java b/tests/net/java/android/net/ip/IpServerTest.java index 017822896610..c3162af1868d 100644 --- a/tests/net/java/android/net/ip/IpServerTest.java +++ b/tests/net/java/android/net/ip/IpServerTest.java @@ -22,20 +22,26 @@ import static android.net.ConnectivityManager.TETHERING_WIFI; import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR; import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR; +import static android.net.NetworkUtils.intToInet4AddressHTH; +import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.ip.IpServer.STATE_AVAILABLE; import static android.net.ip.IpServer.STATE_TETHERED; import static android.net.ip.IpServer.STATE_UNAVAILABLE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -48,8 +54,9 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.MacAddress; import android.net.RouteInfo; -import android.net.dhcp.DhcpServer; -import android.net.dhcp.DhcpServingParams; +import android.net.dhcp.DhcpServingParamsParcel; +import android.net.dhcp.IDhcpServer; +import android.net.dhcp.IDhcpServerCallbacks; import android.net.util.InterfaceParams; import android.net.util.InterfaceSet; import android.net.util.SharedLog; @@ -82,16 +89,18 @@ public class IpServerTest { private static final InterfaceParams TEST_IFACE_PARAMS = new InterfaceParams( IFACE_NAME, 42 /* index */, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */); + private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000; + @Mock private INetworkManagementService mNMService; @Mock private INetworkStatsService mStatsService; @Mock private IpServer.Callback mCallback; @Mock private InterfaceConfiguration mInterfaceConfiguration; @Mock private SharedLog mSharedLog; - @Mock private DhcpServer mDhcpServer; + @Mock private IDhcpServer mDhcpServer; @Mock private RouterAdvertisementDaemon mRaDaemon; @Mock private IpServer.Dependencies mDependencies; - @Captor private ArgumentCaptor<DhcpServingParams> mDhcpParamsCaptor; + @Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor; private final TestLooper mLooper = new TestLooper(); private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor = @@ -112,8 +121,18 @@ public class IpServerTest { mLooper.dispatchAll(); reset(mNMService, mStatsService, mCallback); when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); - when(mDependencies.makeDhcpServer( - any(), any(), mDhcpParamsCaptor.capture(), any())).thenReturn(mDhcpServer); + + doAnswer(inv -> { + final IDhcpServerCallbacks cb = inv.getArgument(2); + new Thread(() -> { + try { + cb.onDhcpServerCreated(STATUS_SUCCESS, mDhcpServer); + } catch (RemoteException e) { + fail(e.getMessage()); + } + }).run(); + return null; + }).when(mDependencies).makeDhcpServer(any(), mDhcpParamsCaptor.capture(), any()); when(mDependencies.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon); when(mDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS); @@ -399,21 +418,20 @@ public class IpServerTest { initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, true /* usingLegacyDhcp */); dispatchTetherConnectionChanged(UPSTREAM_IFACE); - verify(mDependencies, never()).makeDhcpServer(any(), any(), any(), any()); + verify(mDependencies, never()).makeDhcpServer(any(), any(), any()); } - private void assertDhcpStarted(IpPrefix expectedPrefix) { - verify(mDependencies, times(1)).makeDhcpServer( - eq(mLooper.getLooper()), eq(IFACE_NAME), any(), eq(mSharedLog)); - verify(mDhcpServer, times(1)).start(); - final DhcpServingParams params = mDhcpParamsCaptor.getValue(); + private void assertDhcpStarted(IpPrefix expectedPrefix) throws Exception { + verify(mDependencies, times(1)).makeDhcpServer(eq(IFACE_NAME), any(), any()); + verify(mDhcpServer, timeout(MAKE_DHCPSERVER_TIMEOUT_MS).times(1)).start(any()); + final DhcpServingParamsParcel params = mDhcpParamsCaptor.getValue(); // Last address byte is random - assertTrue(expectedPrefix.contains(params.serverAddr.getAddress())); - assertEquals(expectedPrefix.getPrefixLength(), params.serverAddr.getPrefixLength()); - assertEquals(1, params.defaultRouters.size()); - assertEquals(params.serverAddr.getAddress(), params.defaultRouters.iterator().next()); - assertEquals(1, params.dnsServers.size()); - assertEquals(params.serverAddr.getAddress(), params.dnsServers.iterator().next()); + assertTrue(expectedPrefix.contains(intToInet4AddressHTH(params.serverAddr))); + assertEquals(expectedPrefix.getPrefixLength(), params.serverAddrPrefixLength); + assertEquals(1, params.defaultRouters.length); + assertEquals(params.serverAddr, params.defaultRouters[0]); + assertEquals(1, params.dnsServers.length); + assertEquals(params.serverAddr, params.dnsServers[0]); assertEquals(DHCP_LEASE_TIME_SECS, params.dhcpLeaseTimeSecs); } @@ -458,7 +476,7 @@ public class IpServerTest { addr4 = addr; break; } - assertTrue("missing IPv4 address", addr4 != null); + assertNotNull("missing IPv4 address", addr4); // Assert the presence of the associated directly connected route. final RouteInfo directlyConnected = new RouteInfo(addr4, null, lp.getInterfaceName()); diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java index e6b43d286a3d..1ea83c2bbb6b 100644 --- a/tests/net/java/com/android/server/connectivity/TetheringTest.java +++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java @@ -27,6 +27,7 @@ import static android.net.ConnectivityManager.TETHERING_USB; import static android.net.ConnectivityManager.TETHERING_WIFI; import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE; import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE; @@ -37,6 +38,7 @@ import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Matchers.anyInt; @@ -47,6 +49,8 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -74,8 +78,9 @@ import android.net.NetworkInfo; import android.net.NetworkState; import android.net.NetworkUtils; import android.net.RouteInfo; -import android.net.dhcp.DhcpServer; -import android.net.dhcp.DhcpServingParams; +import android.net.dhcp.DhcpServerCallbacks; +import android.net.dhcp.DhcpServingParamsParcel; +import android.net.dhcp.IDhcpServer; import android.net.ip.IpServer; import android.net.ip.RouterAdvertisementDaemon; import android.net.util.InterfaceParams; @@ -86,7 +91,6 @@ import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Handler; import android.os.INetworkManagementService; -import android.os.Looper; import android.os.PersistableBundle; import android.os.RemoteException; import android.os.UserHandle; @@ -129,6 +133,8 @@ public class TetheringTest { private static final String TEST_USB_IFNAME = "test_rndis0"; private static final String TEST_WLAN_IFNAME = "test_wlan0"; + private static final int DHCPSERVER_START_TIMEOUT_MS = 1000; + @Mock private ApplicationInfo mApplicationInfo; @Mock private Context mContext; @Mock private INetworkManagementService mNMService; @@ -143,9 +149,11 @@ public class TetheringTest { @Mock private UpstreamNetworkMonitor mUpstreamNetworkMonitor; @Mock private IPv6TetheringCoordinator mIPv6TetheringCoordinator; @Mock private RouterAdvertisementDaemon mRouterAdvertisementDaemon; - @Mock private DhcpServer mDhcpServer; + @Mock private IDhcpServer mDhcpServer; @Mock private INetd mNetd; + private final MockIpServerDependencies mIpServerDependencies = + spy(new MockIpServerDependencies()); private final MockTetheringDependencies mTetheringDependencies = new MockTetheringDependencies(); @@ -185,6 +193,47 @@ public class TetheringTest { } } + public class MockIpServerDependencies extends IpServer.Dependencies { + MockIpServerDependencies() { + super(null); + } + + @Override + public RouterAdvertisementDaemon getRouterAdvertisementDaemon( + InterfaceParams ifParams) { + return mRouterAdvertisementDaemon; + } + + @Override + public InterfaceParams getInterfaceParams(String ifName) { + assertTrue("Non-mocked interface " + ifName, + ifName.equals(TEST_USB_IFNAME) + || ifName.equals(TEST_WLAN_IFNAME) + || ifName.equals(TEST_MOBILE_IFNAME)); + final String[] ifaces = new String[] { + TEST_USB_IFNAME, TEST_WLAN_IFNAME, TEST_MOBILE_IFNAME }; + return new InterfaceParams(ifName, ArrayUtils.indexOf(ifaces, ifName) + IFINDEX_OFFSET, + MacAddress.ALL_ZEROS_ADDRESS); + } + + @Override + public INetd getNetdService() { + return mNetd; + } + + @Override + public void makeDhcpServer(String ifName, DhcpServingParamsParcel params, + DhcpServerCallbacks cb) { + new Thread(() -> { + try { + cb.onDhcpServerCreated(STATUS_SUCCESS, mDhcpServer); + } catch (RemoteException e) { + fail(e.getMessage()); + } + }).run(); + } + } + public class MockTetheringDependencies extends TetheringDependencies { StateMachine upstreamNetworkMonitorMasterSM; ArrayList<IpServer> ipv6CoordinatorNotifyList; @@ -216,35 +265,8 @@ public class TetheringTest { } @Override - public IpServer.Dependencies getIpServerDependencies() { - return new IpServer.Dependencies() { - @Override - public RouterAdvertisementDaemon getRouterAdvertisementDaemon( - InterfaceParams ifParams) { - return mRouterAdvertisementDaemon; - } - - @Override - public InterfaceParams getInterfaceParams(String ifName) { - final String[] ifaces = new String[] { - TEST_USB_IFNAME, TEST_WLAN_IFNAME, TEST_MOBILE_IFNAME }; - final int index = ArrayUtils.indexOf(ifaces, ifName); - assertTrue("Non-mocked interface: " + ifName, index >= 0); - return new InterfaceParams(ifName, index + IFINDEX_OFFSET, - MacAddress.ALL_ZEROS_ADDRESS); - } - - @Override - public INetd getNetdService() { - return mNetd; - } - - @Override - public DhcpServer makeDhcpServer(Looper looper, String ifName, - DhcpServingParams params, SharedLog log) { - return mDhcpServer; - } - }; + public IpServer.Dependencies getIpServerDependencies(Context context) { + return mIpServerDependencies; } @Override @@ -546,7 +568,7 @@ public class TetheringTest { sendIPv6TetherUpdates(upstreamState); verify(mRouterAdvertisementDaemon, never()).buildNewRa(any(), notNull()); - verify(mDhcpServer, times(1)).start(); + verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any()); } @Test @@ -557,7 +579,7 @@ public class TetheringTest { runUsbTethering(upstreamState); sendIPv6TetherUpdates(upstreamState); - verify(mDhcpServer, never()).start(); + verify(mIpServerDependencies, never()).makeDhcpServer(any(), any(), any()); } @Test @@ -581,7 +603,7 @@ public class TetheringTest { verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); verify(mRouterAdvertisementDaemon, times(1)).start(); - verify(mDhcpServer, times(1)).start(); + verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any()); sendIPv6TetherUpdates(upstreamState); verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull()); @@ -595,7 +617,7 @@ public class TetheringTest { verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME); verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); - verify(mDhcpServer, times(1)).start(); + verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any()); verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME); @@ -612,7 +634,7 @@ public class TetheringTest { runUsbTethering(upstreamState); verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); - verify(mDhcpServer, times(1)).start(); + verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any()); verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); // Then 464xlat comes up @@ -636,7 +658,7 @@ public class TetheringTest { verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); // DHCP not restarted on downstream (still times(1)) - verify(mDhcpServer, times(1)).start(); + verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any()); } @Test |