Merge "Move DhcpServer to NetworkStack app"

This commit is contained in:
Remi NGUYEN VAN
2019-01-09 10:42:48 +00:00
committed by Gerrit Code Review
34 changed files with 1027 additions and 163 deletions

View File

@@ -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",
}

View File

@@ -2485,6 +2485,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

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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,9 +101,13 @@ 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
@@ -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);

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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");
}
}

View File

@@ -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");
}
}

View File

@@ -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",
]
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;

View File

@@ -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
@@ -164,13 +177,6 @@ public class DhcpServerTest {
verify(mPacketListener, times(1)).start();
}
@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

View File

@@ -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();
}

View File

@@ -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));
}
}
}

View File

@@ -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();
}

View File

@@ -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);
}
/**

View File

@@ -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",
]
}

View File

@@ -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.

View File

@@ -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;
}
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
mDhcpServer = mDeps.makeDhcpServer(getHandler().getLooper(), mIfaceName, params,
mLog.forSubComponent("DHCP"));
mDhcpServer.start();
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();
}
}
}

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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());

View File

@@ -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