diff options
| author | 2019-01-23 08:40:50 -0800 | |
|---|---|---|
| committer | 2019-01-23 08:40:50 -0800 | |
| commit | 9a13aa9ffd7d9eeee0bfd7bf04b249a4331c7b88 (patch) | |
| tree | 927086da5f13338b399da001f6f5735733e75075 | |
| parent | 02c75cd029211127fc0a4231f4fcdbed4d9870a8 (diff) | |
| parent | 9026fb46ccfe61385ef7e07940ca4111f39b1b99 (diff) | |
Merge changes I1250730c,I7d00848c am: e693d49c0a
am: 9026fb46cc
Change-Id: I31c9f6c11d0f52a34f68151fc2baca1921dcadad
52 files changed, 3411 insertions, 2903 deletions
diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl index b7af37474307..f0fe92eb8641 100644 --- a/core/java/android/net/INetworkManagementEventObserver.aidl +++ b/core/java/android/net/INetworkManagementEventObserver.aidl @@ -24,7 +24,7 @@ import android.net.RouteInfo; * * @hide */ -interface INetworkManagementEventObserver { +oneway interface INetworkManagementEventObserver { /** * Interface configuration status has changed. * diff --git a/core/java/android/net/INetworkStackConnector.aidl b/core/java/android/net/INetworkStackConnector.aidl index 2df8ab7ec198..8b64f1c7c45a 100644 --- a/core/java/android/net/INetworkStackConnector.aidl +++ b/core/java/android/net/INetworkStackConnector.aidl @@ -18,10 +18,12 @@ package android.net; import android.net.INetworkMonitorCallbacks; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpServerCallbacks; +import android.net.ip.IIpClientCallbacks; /** @hide */ oneway interface INetworkStackConnector { void makeDhcpServer(in String ifName, in DhcpServingParamsParcel params, in IDhcpServerCallbacks cb); void makeNetworkMonitor(int netId, String name, in INetworkMonitorCallbacks cb); + void makeIpClient(in String ifName, in IIpClientCallbacks callbacks); }
\ No newline at end of file diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java index af043eeccde2..d277034650a1 100644 --- a/core/java/android/net/NetworkStack.java +++ b/core/java/android/net/NetworkStack.java @@ -27,6 +27,7 @@ import android.content.Intent; import android.content.ServiceConnection; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpServerCallbacks; +import android.net.ip.IIpClientCallbacks; import android.os.Binder; import android.os.IBinder; import android.os.Process; @@ -84,6 +85,21 @@ public class NetworkStack { } /** + * Create an IpClient on the specified interface. + * + * <p>The IpClient will be returned asynchronously through the provided callbacks. + */ + public void makeIpClient(String ifName, IIpClientCallbacks cb) { + requestConnector(connector -> { + try { + connector.makeIpClient(ifName, cb); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + }); + } + + /** * Create a NetworkMonitor. * * <p>The INetworkMonitor will be returned asynchronously through the provided callbacks. diff --git a/services/net/java/android/net/util/MultinetworkPolicyTracker.java b/core/java/android/net/util/MultinetworkPolicyTracker.java index 30c5cd98b719..30c5cd98b719 100644 --- a/services/net/java/android/net/util/MultinetworkPolicyTracker.java +++ b/core/java/android/net/util/MultinetworkPolicyTracker.java diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp index 2f7d599c68bf..a2da0a079550 100644 --- a/packages/NetworkStack/Android.bp +++ b/packages/NetworkStack/Android.bp @@ -24,7 +24,7 @@ java_library { ":services-networkstack-shared-srcs", ], static_libs: [ - "dhcp-packet-lib", + "services-netlink-lib", ] } diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml index 7f8bb93ae023..5ab833bda66d 100644 --- a/packages/NetworkStack/AndroidManifest.xml +++ b/packages/NetworkStack/AndroidManifest.xml @@ -28,6 +28,7 @@ <!-- Launch captive portal app as specific user --> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.NETWORK_STACK" /> + <uses-permission android:name="android.permission.WAKE_LOCK" /> <application android:label="NetworkStack" android:defaultToDeviceProtectedStorage="true" diff --git a/services/net/java/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java index 494395285f5b..50c4dfc8d700 100644 --- a/services/net/java/android/net/apf/ApfFilter.java +++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java @@ -16,10 +16,6 @@ package android.net.apf; -import static android.net.util.NetworkConstants.ICMPV6_ECHO_REQUEST_TYPE; -import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT; -import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT; -import static android.net.util.NetworkConstants.ICMPV6_ROUTER_SOLICITATION; import static android.net.util.SocketUtils.makePacketSocketAddress; import static android.system.OsConstants.AF_PACKET; import static android.system.OsConstants.ARPHRD_ETHER; @@ -35,6 +31,10 @@ import static com.android.internal.util.BitUtils.getUint16; import static com.android.internal.util.BitUtils.getUint32; import static com.android.internal.util.BitUtils.getUint8; import static com.android.internal.util.BitUtils.uint32; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE; +import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION; import android.annotation.Nullable; import android.content.BroadcastReceiver; @@ -46,7 +46,7 @@ import android.net.LinkProperties; import android.net.NetworkUtils; import android.net.apf.ApfGenerator.IllegalInstructionException; import android.net.apf.ApfGenerator.Register; -import android.net.ip.IpClientCallbacks; +import android.net.ip.IpClient.IpClientCallbacksWrapper; import android.net.metrics.ApfProgramEvent; import android.net.metrics.ApfStats; import android.net.metrics.IpConnectivityLog; @@ -337,7 +337,7 @@ public class ApfFilter { private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20; private final ApfCapabilities mApfCapabilities; - private final IpClientCallbacks mIpClientCallback; + private final IpClientCallbacksWrapper mIpClientCallback; private final InterfaceParams mInterfaceParams; private final IpConnectivityLog mMetricsLog; @@ -378,7 +378,7 @@ public class ApfFilter { @VisibleForTesting ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams, - IpClientCallbacks ipClientCallback, IpConnectivityLog log) { + IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log) { mApfCapabilities = config.apfCapabilities; mIpClientCallback = ipClientCallback; mInterfaceParams = ifParams; @@ -1420,7 +1420,7 @@ public class ApfFilter { * filtering using APF programs. */ public static ApfFilter maybeCreate(Context context, ApfConfiguration config, - InterfaceParams ifParams, IpClientCallbacks ipClientCallback) { + InterfaceParams ifParams, IpClientCallbacksWrapper ipClientCallback) { if (context == null || config == null || ifParams == null) return null; ApfCapabilities apfCapabilities = config.apfCapabilities; if (apfCapabilities == null) return null; diff --git a/services/net/java/android/net/apf/ApfGenerator.java b/packages/NetworkStack/src/android/net/apf/ApfGenerator.java index 87a1b5ea8b4d..87a1b5ea8b4d 100644 --- a/services/net/java/android/net/apf/ApfGenerator.java +++ b/packages/NetworkStack/src/android/net/apf/ApfGenerator.java diff --git a/services/net/java/android/net/dhcp/DhcpAckPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpAckPacket.java index b2eb4e21b591..b2eb4e21b591 100644 --- a/services/net/java/android/net/dhcp/DhcpAckPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpAckPacket.java diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java new file mode 100644 index 000000000000..04ac9a301813 --- /dev/null +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java @@ -0,0 +1,1052 @@ +/* + * Copyright (C) 2015 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; + +import static android.net.dhcp.DhcpPacket.DHCP_BROADCAST_ADDRESS; +import static android.net.dhcp.DhcpPacket.DHCP_DNS_SERVER; +import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_NAME; +import static android.net.dhcp.DhcpPacket.DHCP_LEASE_TIME; +import static android.net.dhcp.DhcpPacket.DHCP_MTU; +import static android.net.dhcp.DhcpPacket.DHCP_REBINDING_TIME; +import static android.net.dhcp.DhcpPacket.DHCP_RENEWAL_TIME; +import static android.net.dhcp.DhcpPacket.DHCP_ROUTER; +import static android.net.dhcp.DhcpPacket.DHCP_SUBNET_MASK; +import static android.net.dhcp.DhcpPacket.DHCP_VENDOR_INFO; +import static android.net.dhcp.DhcpPacket.INADDR_ANY; +import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST; +import static android.net.util.SocketUtils.makePacketSocketAddress; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_PACKET; +import static android.system.OsConstants.ETH_P_IP; +import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.SOCK_DGRAM; +import static android.system.OsConstants.SOCK_RAW; +import static android.system.OsConstants.SOL_SOCKET; +import static android.system.OsConstants.SO_BROADCAST; +import static android.system.OsConstants.SO_RCVBUF; +import static android.system.OsConstants.SO_REUSEADDR; + +import android.content.Context; +import android.net.DhcpResults; +import android.net.NetworkUtils; +import android.net.TrafficStats; +import android.net.ip.IpClient; +import android.net.metrics.DhcpClientEvent; +import android.net.metrics.DhcpErrorEvent; +import android.net.metrics.IpConnectivityLog; +import android.net.util.InterfaceParams; +import android.net.util.SocketUtils; +import android.os.Message; +import android.os.SystemClock; +import android.system.ErrnoException; +import android.system.Os; +import android.util.EventLog; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.util.HexDump; +import com.android.internal.util.MessageUtils; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import com.android.internal.util.WakeupMessage; + +import libcore.io.IoBridge; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.net.Inet4Address; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Random; + +/** + * A DHCPv4 client. + * + * Written to behave similarly to the DhcpStateMachine + dhcpcd 5.5.6 combination used in Android + * 5.1 and below, as configured on Nexus 6. The interface is the same as DhcpStateMachine. + * + * TODO: + * + * - Exponential backoff when receiving NAKs (not specified by the RFC, but current behaviour). + * - Support persisting lease state and support INIT-REBOOT. Android 5.1 does this, but it does not + * do so correctly: instead of requesting the lease last obtained on a particular network (e.g., a + * given SSID), it requests the last-leased IP address on the same interface, causing a delay if + * the server NAKs or a timeout if it doesn't. + * + * Known differences from current behaviour: + * + * - Does not request the "static routes" option. + * - Does not support BOOTP servers. DHCP has been around since 1993, should be everywhere now. + * - Requests the "broadcast" option, but does nothing with it. + * - Rejects invalid subnet masks such as 255.255.255.1 (current code treats that as 255.255.255.0). + * + * @hide + */ +public class DhcpClient extends StateMachine { + + private static final String TAG = "DhcpClient"; + private static final boolean DBG = true; + private static final boolean STATE_DBG = false; + private static final boolean MSG_DBG = false; + private static final boolean PACKET_DBG = false; + + // Timers and timeouts. + private static final int SECONDS = 1000; + private static final int FIRST_TIMEOUT_MS = 2 * SECONDS; + private static final int MAX_TIMEOUT_MS = 128 * SECONDS; + + // This is not strictly needed, since the client is asynchronous and implements exponential + // backoff. It's maintained for backwards compatibility with the previous DHCP code, which was + // a blocking operation with a 30-second timeout. We pick 36 seconds so we can send packets at + // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter. + private static final int DHCP_TIMEOUT_MS = 36 * SECONDS; + + // DhcpClient uses IpClient's handler. + private static final int PUBLIC_BASE = IpClient.DHCPCLIENT_CMD_BASE; + + /* Commands from controller to start/stop DHCP */ + public static final int CMD_START_DHCP = PUBLIC_BASE + 1; + public static final int CMD_STOP_DHCP = PUBLIC_BASE + 2; + + /* Notification from DHCP state machine prior to DHCP discovery/renewal */ + public static final int CMD_PRE_DHCP_ACTION = PUBLIC_BASE + 3; + /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates + * success/failure */ + public static final int CMD_POST_DHCP_ACTION = PUBLIC_BASE + 4; + /* Notification from DHCP state machine before quitting */ + public static final int CMD_ON_QUIT = PUBLIC_BASE + 5; + + /* Command from controller to indicate DHCP discovery/renewal can continue + * after pre DHCP action is complete */ + public static final int CMD_PRE_DHCP_ACTION_COMPLETE = PUBLIC_BASE + 6; + + /* Command and event notification to/from IpManager requesting the setting + * (or clearing) of an IPv4 LinkAddress. + */ + public static final int CMD_CLEAR_LINKADDRESS = PUBLIC_BASE + 7; + public static final int CMD_CONFIGURE_LINKADDRESS = PUBLIC_BASE + 8; + public static final int EVENT_LINKADDRESS_CONFIGURED = PUBLIC_BASE + 9; + + /* Message.arg1 arguments to CMD_POST_DHCP_ACTION notification */ + public static final int DHCP_SUCCESS = 1; + public static final int DHCP_FAILURE = 2; + + // Internal messages. + private static final int PRIVATE_BASE = IpClient.DHCPCLIENT_CMD_BASE + 100; + private static final int CMD_KICK = PRIVATE_BASE + 1; + private static final int CMD_RECEIVED_PACKET = PRIVATE_BASE + 2; + private static final int CMD_TIMEOUT = PRIVATE_BASE + 3; + private static final int CMD_RENEW_DHCP = PRIVATE_BASE + 4; + private static final int CMD_REBIND_DHCP = PRIVATE_BASE + 5; + private static final int CMD_EXPIRE_DHCP = PRIVATE_BASE + 6; + + // For message logging. + private static final Class[] sMessageClasses = { DhcpClient.class }; + private static final SparseArray<String> sMessageNames = + MessageUtils.findMessageNames(sMessageClasses); + + // DHCP parameters that we request. + /* package */ static final byte[] REQUESTED_PARAMS = new byte[] { + DHCP_SUBNET_MASK, + DHCP_ROUTER, + DHCP_DNS_SERVER, + DHCP_DOMAIN_NAME, + DHCP_MTU, + DHCP_BROADCAST_ADDRESS, // TODO: currently ignored. + DHCP_LEASE_TIME, + DHCP_RENEWAL_TIME, + DHCP_REBINDING_TIME, + DHCP_VENDOR_INFO, + }; + + // DHCP flag that means "yes, we support unicast." + private static final boolean DO_UNICAST = false; + + // System services / libraries we use. + private final Context mContext; + private final Random mRandom; + private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); + + // Sockets. + // - We use a packet socket to receive, because servers send us packets bound for IP addresses + // which we have not yet configured, and the kernel protocol stack drops these. + // - We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can + // be off-link as well as on-link). + private FileDescriptor mPacketSock; + private FileDescriptor mUdpSock; + private ReceiveThread mReceiveThread; + + // State variables. + private final StateMachine mController; + private final WakeupMessage mKickAlarm; + private final WakeupMessage mTimeoutAlarm; + private final WakeupMessage mRenewAlarm; + private final WakeupMessage mRebindAlarm; + private final WakeupMessage mExpiryAlarm; + private final String mIfaceName; + + private boolean mRegisteredForPreDhcpNotification; + private InterfaceParams mIface; + // TODO: MacAddress-ify more of this class hierarchy. + private byte[] mHwAddr; + private SocketAddress mInterfaceBroadcastAddr; + private int mTransactionId; + private long mTransactionStartMillis; + private DhcpResults mDhcpLease; + private long mDhcpLeaseExpiry; + private DhcpResults mOffer; + + // Milliseconds SystemClock timestamps used to record transition times to DhcpBoundState. + private long mLastInitEnterTime; + private long mLastBoundExitTime; + + // States. + private State mStoppedState = new StoppedState(); + private State mDhcpState = new DhcpState(); + private State mDhcpInitState = new DhcpInitState(); + private State mDhcpSelectingState = new DhcpSelectingState(); + private State mDhcpRequestingState = new DhcpRequestingState(); + private State mDhcpHaveLeaseState = new DhcpHaveLeaseState(); + private State mConfiguringInterfaceState = new ConfiguringInterfaceState(); + private State mDhcpBoundState = new DhcpBoundState(); + private State mDhcpRenewingState = new DhcpRenewingState(); + private State mDhcpRebindingState = new DhcpRebindingState(); + private State mDhcpInitRebootState = new DhcpInitRebootState(); + private State mDhcpRebootingState = new DhcpRebootingState(); + private State mWaitBeforeStartState = new WaitBeforeStartState(mDhcpInitState); + private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(mDhcpRenewingState); + + private WakeupMessage makeWakeupMessage(String cmdName, int cmd) { + cmdName = DhcpClient.class.getSimpleName() + "." + mIfaceName + "." + cmdName; + return new WakeupMessage(mContext, getHandler(), cmdName, cmd); + } + + // TODO: Take an InterfaceParams instance instead of an interface name String. + private DhcpClient(Context context, StateMachine controller, String iface) { + super(TAG, controller.getHandler()); + + mContext = context; + mController = controller; + mIfaceName = iface; + + addState(mStoppedState); + addState(mDhcpState); + addState(mDhcpInitState, mDhcpState); + addState(mWaitBeforeStartState, mDhcpState); + addState(mDhcpSelectingState, mDhcpState); + addState(mDhcpRequestingState, mDhcpState); + addState(mDhcpHaveLeaseState, mDhcpState); + addState(mConfiguringInterfaceState, mDhcpHaveLeaseState); + addState(mDhcpBoundState, mDhcpHaveLeaseState); + addState(mWaitBeforeRenewalState, mDhcpHaveLeaseState); + addState(mDhcpRenewingState, mDhcpHaveLeaseState); + addState(mDhcpRebindingState, mDhcpHaveLeaseState); + addState(mDhcpInitRebootState, mDhcpState); + addState(mDhcpRebootingState, mDhcpState); + + setInitialState(mStoppedState); + + mRandom = new Random(); + + // Used to schedule packet retransmissions. + mKickAlarm = makeWakeupMessage("KICK", CMD_KICK); + // Used to time out PacketRetransmittingStates. + mTimeoutAlarm = makeWakeupMessage("TIMEOUT", CMD_TIMEOUT); + // Used to schedule DHCP reacquisition. + mRenewAlarm = makeWakeupMessage("RENEW", CMD_RENEW_DHCP); + mRebindAlarm = makeWakeupMessage("REBIND", CMD_REBIND_DHCP); + mExpiryAlarm = makeWakeupMessage("EXPIRY", CMD_EXPIRE_DHCP); + } + + public void registerForPreDhcpNotification() { + mRegisteredForPreDhcpNotification = true; + } + + public static DhcpClient makeDhcpClient( + Context context, StateMachine controller, InterfaceParams ifParams) { + DhcpClient client = new DhcpClient(context, controller, ifParams.name); + client.mIface = ifParams; + client.start(); + return client; + } + + private boolean initInterface() { + if (mIface == null) mIface = InterfaceParams.getByName(mIfaceName); + if (mIface == null) { + Log.e(TAG, "Can't determine InterfaceParams for " + mIfaceName); + return false; + } + + mHwAddr = mIface.macAddr.toByteArray(); + mInterfaceBroadcastAddr = makePacketSocketAddress(mIface.index, DhcpPacket.ETHER_BROADCAST); + return true; + } + + private void startNewTransaction() { + mTransactionId = mRandom.nextInt(); + mTransactionStartMillis = SystemClock.elapsedRealtime(); + } + + private boolean initSockets() { + return initPacketSocket() && initUdpSocket(); + } + + private boolean initPacketSocket() { + try { + mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP); + SocketAddress addr = makePacketSocketAddress((short) ETH_P_IP, mIface.index); + Os.bind(mPacketSock, addr); + NetworkUtils.attachDhcpFilter(mPacketSock); + } catch(SocketException|ErrnoException e) { + Log.e(TAG, "Error creating packet socket", e); + return false; + } + return true; + } + + private boolean initUdpSocket() { + final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_DHCP); + try { + mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + SocketUtils.bindSocketToInterface(mUdpSock, mIfaceName); + Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1); + Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1); + Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0); + Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT); + } catch(SocketException|ErrnoException e) { + Log.e(TAG, "Error creating UDP socket", e); + return false; + } finally { + TrafficStats.setThreadStatsTag(oldTag); + } + return true; + } + + private boolean connectUdpSock(Inet4Address to) { + try { + Os.connect(mUdpSock, to, DhcpPacket.DHCP_SERVER); + return true; + } catch (SocketException|ErrnoException e) { + Log.e(TAG, "Error connecting UDP socket", e); + return false; + } + } + + private static void closeQuietly(FileDescriptor fd) { + try { + IoBridge.closeAndSignalBlockedThreads(fd); + } catch (IOException ignored) {} + } + + private void closeSockets() { + closeQuietly(mUdpSock); + closeQuietly(mPacketSock); + } + + class ReceiveThread extends Thread { + + private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH]; + private volatile boolean mStopped = false; + + public void halt() { + mStopped = true; + closeSockets(); // Interrupts the read() call the thread is blocked in. + } + + @Override + public void run() { + if (DBG) Log.d(TAG, "Receive thread started"); + while (!mStopped) { + int length = 0; // Or compiler can't tell it's initialized if a parse error occurs. + try { + length = Os.read(mPacketSock, mPacket, 0, mPacket.length); + DhcpPacket packet = null; + packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2); + if (DBG) Log.d(TAG, "Received packet: " + packet); + sendMessage(CMD_RECEIVED_PACKET, packet); + } catch (IOException|ErrnoException e) { + if (!mStopped) { + Log.e(TAG, "Read error", e); + logError(DhcpErrorEvent.RECEIVE_ERROR); + } + } catch (DhcpPacket.ParseException e) { + Log.e(TAG, "Can't parse packet: " + e.getMessage()); + if (PACKET_DBG) { + Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length)); + } + if (e.errorCode == DhcpErrorEvent.DHCP_NO_COOKIE) { + int snetTagId = 0x534e4554; + String bugId = "31850211"; + int uid = -1; + String data = DhcpPacket.ParseException.class.getName(); + EventLog.writeEvent(snetTagId, bugId, uid, data); + } + logError(e.errorCode); + } + } + if (DBG) Log.d(TAG, "Receive thread stopped"); + } + } + + private short getSecs() { + return (short) ((SystemClock.elapsedRealtime() - mTransactionStartMillis) / 1000); + } + + private boolean transmitPacket(ByteBuffer buf, String description, int encap, Inet4Address to) { + try { + if (encap == DhcpPacket.ENCAP_L2) { + if (DBG) Log.d(TAG, "Broadcasting " + description); + Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr); + } else if (encap == DhcpPacket.ENCAP_BOOTP && to.equals(INADDR_BROADCAST)) { + if (DBG) Log.d(TAG, "Broadcasting " + description); + // We only send L3-encapped broadcasts in DhcpRebindingState, + // where we have an IP address and an unconnected UDP socket. + // + // N.B.: We only need this codepath because DhcpRequestPacket + // hardcodes the source IP address to 0.0.0.0. We could reuse + // the packet socket if this ever changes. + Os.sendto(mUdpSock, buf, 0, to, DhcpPacket.DHCP_SERVER); + } else { + // It's safe to call getpeername here, because we only send unicast packets if we + // have an IP address, and we connect the UDP socket in DhcpBoundState#enter. + if (DBG) Log.d(TAG, String.format("Unicasting %s to %s", + description, Os.getpeername(mUdpSock))); + Os.write(mUdpSock, buf); + } + } catch(ErrnoException|IOException e) { + Log.e(TAG, "Can't send packet: ", e); + return false; + } + return true; + } + + private boolean sendDiscoverPacket() { + ByteBuffer packet = DhcpPacket.buildDiscoverPacket( + DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr, + DO_UNICAST, REQUESTED_PARAMS); + return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST); + } + + private boolean sendRequestPacket( + Inet4Address clientAddress, Inet4Address requestedAddress, + Inet4Address serverAddress, Inet4Address to) { + // TODO: should we use the transaction ID from the server? + final int encap = INADDR_ANY.equals(clientAddress) + ? DhcpPacket.ENCAP_L2 : DhcpPacket.ENCAP_BOOTP; + + ByteBuffer packet = DhcpPacket.buildRequestPacket( + encap, mTransactionId, getSecs(), clientAddress, + DO_UNICAST, mHwAddr, requestedAddress, + serverAddress, REQUESTED_PARAMS, null); + String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null; + String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() + + " request=" + requestedAddress.getHostAddress() + + " serverid=" + serverStr; + return transmitPacket(packet, description, encap, to); + } + + private void scheduleLeaseTimers() { + if (mDhcpLeaseExpiry == 0) { + Log.d(TAG, "Infinite lease, no timer scheduling needed"); + return; + } + + final long now = SystemClock.elapsedRealtime(); + + // TODO: consider getting the renew and rebind timers from T1 and T2. + // See also: + // https://tools.ietf.org/html/rfc2131#section-4.4.5 + // https://tools.ietf.org/html/rfc1533#section-9.9 + // https://tools.ietf.org/html/rfc1533#section-9.10 + final long remainingDelay = mDhcpLeaseExpiry - now; + final long renewDelay = remainingDelay / 2; + final long rebindDelay = remainingDelay * 7 / 8; + mRenewAlarm.schedule(now + renewDelay); + mRebindAlarm.schedule(now + rebindDelay); + mExpiryAlarm.schedule(now + remainingDelay); + Log.d(TAG, "Scheduling renewal in " + (renewDelay / 1000) + "s"); + Log.d(TAG, "Scheduling rebind in " + (rebindDelay / 1000) + "s"); + Log.d(TAG, "Scheduling expiry in " + (remainingDelay / 1000) + "s"); + } + + private void notifySuccess() { + mController.sendMessage( + CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease)); + } + + private void notifyFailure() { + mController.sendMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0, null); + } + + private void acceptDhcpResults(DhcpResults results, String msg) { + mDhcpLease = results; + mOffer = null; + Log.d(TAG, msg + " lease: " + mDhcpLease); + notifySuccess(); + } + + private void clearDhcpState() { + mDhcpLease = null; + mDhcpLeaseExpiry = 0; + mOffer = null; + } + + /** + * Quit the DhcpStateMachine. + * + * @hide + */ + public void doQuit() { + Log.d(TAG, "doQuit"); + quit(); + } + + @Override + protected void onQuitting() { + Log.d(TAG, "onQuitting"); + mController.sendMessage(CMD_ON_QUIT); + } + + abstract class LoggingState extends State { + private long mEnterTimeMs; + + @Override + public void enter() { + if (STATE_DBG) Log.d(TAG, "Entering state " + getName()); + mEnterTimeMs = SystemClock.elapsedRealtime(); + } + + @Override + public void exit() { + long durationMs = SystemClock.elapsedRealtime() - mEnterTimeMs; + logState(getName(), (int) durationMs); + } + + private String messageName(int what) { + return sMessageNames.get(what, Integer.toString(what)); + } + + private String messageToString(Message message) { + long now = SystemClock.uptimeMillis(); + return new StringBuilder(" ") + .append(message.getWhen() - now) + .append(messageName(message.what)) + .append(" ").append(message.arg1) + .append(" ").append(message.arg2) + .append(" ").append(message.obj) + .toString(); + } + + @Override + public boolean processMessage(Message message) { + if (MSG_DBG) { + Log.d(TAG, getName() + messageToString(message)); + } + return NOT_HANDLED; + } + + @Override + public String getName() { + // All DhcpClient's states are inner classes with a well defined name. + // Use getSimpleName() and avoid super's getName() creating new String instances. + return getClass().getSimpleName(); + } + } + + // Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with + // CMD_PRE_DHCP_ACTION_COMPLETE, and then transitions to mOtherState. + abstract class WaitBeforeOtherState extends LoggingState { + protected State mOtherState; + + @Override + public void enter() { + super.enter(); + mController.sendMessage(CMD_PRE_DHCP_ACTION); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case CMD_PRE_DHCP_ACTION_COMPLETE: + transitionTo(mOtherState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + class StoppedState extends State { + @Override + public boolean processMessage(Message message) { + switch (message.what) { + case CMD_START_DHCP: + if (mRegisteredForPreDhcpNotification) { + transitionTo(mWaitBeforeStartState); + } else { + transitionTo(mDhcpInitState); + } + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + class WaitBeforeStartState extends WaitBeforeOtherState { + public WaitBeforeStartState(State otherState) { + super(); + mOtherState = otherState; + } + } + + class WaitBeforeRenewalState extends WaitBeforeOtherState { + public WaitBeforeRenewalState(State otherState) { + super(); + mOtherState = otherState; + } + } + + class DhcpState extends State { + @Override + public void enter() { + clearDhcpState(); + if (initInterface() && initSockets()) { + mReceiveThread = new ReceiveThread(); + mReceiveThread.start(); + } else { + notifyFailure(); + transitionTo(mStoppedState); + } + } + + @Override + public void exit() { + if (mReceiveThread != null) { + mReceiveThread.halt(); // Also closes sockets. + mReceiveThread = null; + } + clearDhcpState(); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case CMD_STOP_DHCP: + transitionTo(mStoppedState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + public boolean isValidPacket(DhcpPacket packet) { + // TODO: check checksum. + int xid = packet.getTransactionId(); + if (xid != mTransactionId) { + Log.d(TAG, "Unexpected transaction ID " + xid + ", expected " + mTransactionId); + return false; + } + if (!Arrays.equals(packet.getClientMac(), mHwAddr)) { + Log.d(TAG, "MAC addr mismatch: got " + + HexDump.toHexString(packet.getClientMac()) + ", expected " + + HexDump.toHexString(packet.getClientMac())); + return false; + } + return true; + } + + public void setDhcpLeaseExpiry(DhcpPacket packet) { + long leaseTimeMillis = packet.getLeaseTimeMillis(); + mDhcpLeaseExpiry = + (leaseTimeMillis > 0) ? SystemClock.elapsedRealtime() + leaseTimeMillis : 0; + } + + /** + * Retransmits packets using jittered exponential backoff with an optional timeout. Packet + * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. If a subclass + * sets mTimeout to a positive value, then timeout() is called by an AlarmManager alarm mTimeout + * milliseconds after entering the state. Kicks and timeouts are cancelled when leaving the + * state. + * + * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a + * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET + * sent by the receive thread. They may also set mTimeout and implement timeout. + */ + abstract class PacketRetransmittingState extends LoggingState { + + private int mTimer; + protected int mTimeout = 0; + + @Override + public void enter() { + super.enter(); + initTimer(); + maybeInitTimeout(); + sendMessage(CMD_KICK); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case CMD_KICK: + sendPacket(); + scheduleKick(); + return HANDLED; + case CMD_RECEIVED_PACKET: + receivePacket((DhcpPacket) message.obj); + return HANDLED; + case CMD_TIMEOUT: + timeout(); + return HANDLED; + default: + return NOT_HANDLED; + } + } + + @Override + public void exit() { + super.exit(); + mKickAlarm.cancel(); + mTimeoutAlarm.cancel(); + } + + abstract protected boolean sendPacket(); + abstract protected void receivePacket(DhcpPacket packet); + protected void timeout() {} + + protected void initTimer() { + mTimer = FIRST_TIMEOUT_MS; + } + + protected int jitterTimer(int baseTimer) { + int maxJitter = baseTimer / 10; + int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter; + return baseTimer + jitter; + } + + protected void scheduleKick() { + long now = SystemClock.elapsedRealtime(); + long timeout = jitterTimer(mTimer); + long alarmTime = now + timeout; + mKickAlarm.schedule(alarmTime); + mTimer *= 2; + if (mTimer > MAX_TIMEOUT_MS) { + mTimer = MAX_TIMEOUT_MS; + } + } + + protected void maybeInitTimeout() { + if (mTimeout > 0) { + long alarmTime = SystemClock.elapsedRealtime() + mTimeout; + mTimeoutAlarm.schedule(alarmTime); + } + } + } + + class DhcpInitState extends PacketRetransmittingState { + public DhcpInitState() { + super(); + } + + @Override + public void enter() { + super.enter(); + startNewTransaction(); + mLastInitEnterTime = SystemClock.elapsedRealtime(); + } + + protected boolean sendPacket() { + return sendDiscoverPacket(); + } + + protected void receivePacket(DhcpPacket packet) { + if (!isValidPacket(packet)) return; + if (!(packet instanceof DhcpOfferPacket)) return; + mOffer = packet.toDhcpResults(); + if (mOffer != null) { + Log.d(TAG, "Got pending lease: " + mOffer); + transitionTo(mDhcpRequestingState); + } + } + } + + // Not implemented. We request the first offer we receive. + class DhcpSelectingState extends LoggingState { + } + + class DhcpRequestingState extends PacketRetransmittingState { + public DhcpRequestingState() { + mTimeout = DHCP_TIMEOUT_MS / 2; + } + + protected boolean sendPacket() { + return sendRequestPacket( + INADDR_ANY, // ciaddr + (Inet4Address) mOffer.ipAddress.getAddress(), // DHCP_REQUESTED_IP + (Inet4Address) mOffer.serverAddress, // DHCP_SERVER_IDENTIFIER + INADDR_BROADCAST); // packet destination address + } + + protected void receivePacket(DhcpPacket packet) { + if (!isValidPacket(packet)) return; + if ((packet instanceof DhcpAckPacket)) { + DhcpResults results = packet.toDhcpResults(); + if (results != null) { + setDhcpLeaseExpiry(packet); + acceptDhcpResults(results, "Confirmed"); + transitionTo(mConfiguringInterfaceState); + } + } else if (packet instanceof DhcpNakPacket) { + // TODO: Wait a while before returning into INIT state. + Log.d(TAG, "Received NAK, returning to INIT"); + mOffer = null; + transitionTo(mDhcpInitState); + } + } + + @Override + protected void timeout() { + // After sending REQUESTs unsuccessfully for a while, go back to init. + transitionTo(mDhcpInitState); + } + } + + class DhcpHaveLeaseState extends State { + @Override + public boolean processMessage(Message message) { + switch (message.what) { + case CMD_EXPIRE_DHCP: + Log.d(TAG, "Lease expired!"); + notifyFailure(); + transitionTo(mDhcpInitState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + + @Override + public void exit() { + // Clear any extant alarms. + mRenewAlarm.cancel(); + mRebindAlarm.cancel(); + mExpiryAlarm.cancel(); + clearDhcpState(); + // Tell IpManager to clear the IPv4 address. There is no need to + // wait for confirmation since any subsequent packets are sent from + // INADDR_ANY anyway (DISCOVER, REQUEST). + mController.sendMessage(CMD_CLEAR_LINKADDRESS); + } + } + + class ConfiguringInterfaceState extends LoggingState { + @Override + public void enter() { + super.enter(); + mController.sendMessage(CMD_CONFIGURE_LINKADDRESS, mDhcpLease.ipAddress); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case EVENT_LINKADDRESS_CONFIGURED: + transitionTo(mDhcpBoundState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + } + + class DhcpBoundState extends LoggingState { + @Override + public void enter() { + super.enter(); + if (mDhcpLease.serverAddress != null && !connectUdpSock(mDhcpLease.serverAddress)) { + // There's likely no point in going into DhcpInitState here, we'll probably + // just repeat the transaction, get the same IP address as before, and fail. + // + // NOTE: It is observed that connectUdpSock() basically never fails, due to + // SO_BINDTODEVICE. Examining the local socket address shows it will happily + // return an IPv4 address from another interface, or even return "0.0.0.0". + // + // TODO: Consider deleting this check, following testing on several kernels. + notifyFailure(); + transitionTo(mStoppedState); + } + + scheduleLeaseTimers(); + logTimeToBoundState(); + } + + @Override + public void exit() { + super.exit(); + mLastBoundExitTime = SystemClock.elapsedRealtime(); + } + + @Override + public boolean processMessage(Message message) { + super.processMessage(message); + switch (message.what) { + case CMD_RENEW_DHCP: + if (mRegisteredForPreDhcpNotification) { + transitionTo(mWaitBeforeRenewalState); + } else { + transitionTo(mDhcpRenewingState); + } + return HANDLED; + default: + return NOT_HANDLED; + } + } + + private void logTimeToBoundState() { + long now = SystemClock.elapsedRealtime(); + if (mLastBoundExitTime > mLastInitEnterTime) { + logState(DhcpClientEvent.RENEWING_BOUND, (int)(now - mLastBoundExitTime)); + } else { + logState(DhcpClientEvent.INITIAL_BOUND, (int)(now - mLastInitEnterTime)); + } + } + } + + abstract class DhcpReacquiringState extends PacketRetransmittingState { + protected String mLeaseMsg; + + @Override + public void enter() { + super.enter(); + startNewTransaction(); + } + + abstract protected Inet4Address packetDestination(); + + protected boolean sendPacket() { + return sendRequestPacket( + (Inet4Address) mDhcpLease.ipAddress.getAddress(), // ciaddr + INADDR_ANY, // DHCP_REQUESTED_IP + null, // DHCP_SERVER_IDENTIFIER + packetDestination()); // packet destination address + } + + protected void receivePacket(DhcpPacket packet) { + if (!isValidPacket(packet)) return; + if ((packet instanceof DhcpAckPacket)) { + final DhcpResults results = packet.toDhcpResults(); + if (results != null) { + if (!mDhcpLease.ipAddress.equals(results.ipAddress)) { + Log.d(TAG, "Renewed lease not for our current IP address!"); + notifyFailure(); + transitionTo(mDhcpInitState); + } + setDhcpLeaseExpiry(packet); + // Updating our notion of DhcpResults here only causes the + // DNS servers and routes to be updated in LinkProperties + // in IpManager and by any overridden relevant handlers of + // the registered IpManager.Callback. IP address changes + // are not supported here. + acceptDhcpResults(results, mLeaseMsg); + transitionTo(mDhcpBoundState); + } + } else if (packet instanceof DhcpNakPacket) { + Log.d(TAG, "Received NAK, returning to INIT"); + notifyFailure(); + transitionTo(mDhcpInitState); + } + } + } + + class DhcpRenewingState extends DhcpReacquiringState { + public DhcpRenewingState() { + mLeaseMsg = "Renewed"; + } + + @Override + public boolean processMessage(Message message) { + if (super.processMessage(message) == HANDLED) { + return HANDLED; + } + + switch (message.what) { + case CMD_REBIND_DHCP: + transitionTo(mDhcpRebindingState); + return HANDLED; + default: + return NOT_HANDLED; + } + } + + @Override + protected Inet4Address packetDestination() { + // Not specifying a SERVER_IDENTIFIER option is a violation of RFC 2131, but... + // http://b/25343517 . Try to make things work anyway by using broadcast renews. + return (mDhcpLease.serverAddress != null) ? + mDhcpLease.serverAddress : INADDR_BROADCAST; + } + } + + class DhcpRebindingState extends DhcpReacquiringState { + public DhcpRebindingState() { + mLeaseMsg = "Rebound"; + } + + @Override + public void enter() { + super.enter(); + + // We need to broadcast and possibly reconnect the socket to a + // completely different server. + closeQuietly(mUdpSock); + if (!initUdpSocket()) { + Log.e(TAG, "Failed to recreate UDP socket"); + transitionTo(mDhcpInitState); + } + } + + @Override + protected Inet4Address packetDestination() { + return INADDR_BROADCAST; + } + } + + class DhcpInitRebootState extends LoggingState { + } + + class DhcpRebootingState extends LoggingState { + } + + private void logError(int errorCode) { + mMetricsLog.log(mIfaceName, new DhcpErrorEvent(errorCode)); + } + + private void logState(String name, int durationMs) { + final DhcpClientEvent event = new DhcpClientEvent.Builder() + .setMsg(name) + .setDurationMs(durationMs) + .build(); + mMetricsLog.log(mIfaceName, event); + } +} diff --git a/services/net/java/android/net/dhcp/DhcpDeclinePacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpDeclinePacket.java index 7ecdea7b89da..7ecdea7b89da 100644 --- a/services/net/java/android/net/dhcp/DhcpDeclinePacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpDeclinePacket.java diff --git a/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpDiscoverPacket.java index 11f2b6118e24..11f2b6118e24 100644 --- a/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpDiscoverPacket.java diff --git a/services/net/java/android/net/dhcp/DhcpInformPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpInformPacket.java index 7a83466c6e05..7a83466c6e05 100644 --- a/services/net/java/android/net/dhcp/DhcpInformPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpInformPacket.java diff --git a/services/net/java/android/net/dhcp/DhcpNakPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpNakPacket.java index 1da0b7300559..1da0b7300559 100644 --- a/services/net/java/android/net/dhcp/DhcpNakPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpNakPacket.java diff --git a/services/net/java/android/net/dhcp/DhcpOfferPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpOfferPacket.java index 0eba77e4a682..0eba77e4a682 100644 --- a/services/net/java/android/net/dhcp/DhcpOfferPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpOfferPacket.java diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java index ce8b7e78d0f8..ce8b7e78d0f8 100644 --- a/services/net/java/android/net/dhcp/DhcpPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java diff --git a/services/net/java/android/net/dhcp/DhcpReleasePacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpReleasePacket.java index 39583032c20d..39583032c20d 100644 --- a/services/net/java/android/net/dhcp/DhcpReleasePacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpReleasePacket.java diff --git a/services/net/java/android/net/dhcp/DhcpRequestPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpRequestPacket.java index 231d04576c28..231d04576c28 100644 --- a/services/net/java/android/net/dhcp/DhcpRequestPacket.java +++ b/packages/NetworkStack/src/android/net/dhcp/DhcpRequestPacket.java diff --git a/services/net/java/android/net/ip/ConnectivityPacketTracker.java b/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java index 385dd52e4576..385dd52e4576 100644 --- a/services/net/java/android/net/ip/ConnectivityPacketTracker.java +++ b/packages/NetworkStack/src/android/net/ip/ConnectivityPacketTracker.java diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java new file mode 100644 index 000000000000..ad7f85d0a30a --- /dev/null +++ b/packages/NetworkStack/src/android/net/ip/IpClient.java @@ -0,0 +1,1691 @@ +/* + * 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.ip; + +import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable; +import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable; +import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable; + +import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.DhcpResults; +import android.net.INetd; +import android.net.IpPrefix; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.Network; +import android.net.ProvisioningConfigurationParcelable; +import android.net.ProxyInfo; +import android.net.ProxyInfoParcelable; +import android.net.RouteInfo; +import android.net.apf.ApfCapabilities; +import android.net.apf.ApfFilter; +import android.net.dhcp.DhcpClient; +import android.net.ip.IIpClientCallbacks; +import android.net.metrics.IpConnectivityLog; +import android.net.metrics.IpManagerEvent; +import android.net.shared.InitialConfiguration; +import android.net.shared.NetdService; +import android.net.shared.ProvisioningConfiguration; +import android.net.util.InterfaceParams; +import android.net.util.SharedLog; +import android.os.ConditionVariable; +import android.os.INetworkManagementService; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.text.TextUtils; +import android.util.LocalLog; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IState; +import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.MessageUtils; +import com.android.internal.util.Preconditions; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import com.android.internal.util.WakeupMessage; +import com.android.server.net.NetlinkTracker; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.function.Predicate; +import java.util.stream.Collectors; + + +/** + * IpClient + * + * This class provides the interface to IP-layer provisioning and maintenance + * functionality that can be used by transport layers like Wi-Fi, Ethernet, + * et cetera. + * + * [ Lifetime ] + * IpClient is designed to be instantiated as soon as the interface name is + * known and can be as long-lived as the class containing it (i.e. declaring + * it "private final" is okay). + * + * @hide + */ +public class IpClient extends StateMachine { + private static final boolean DBG = false; + + // For message logging. + private static final Class[] sMessageClasses = { IpClient.class, DhcpClient.class }; + private static final SparseArray<String> sWhatToString = + MessageUtils.findMessageNames(sMessageClasses); + // Two static concurrent hashmaps of interface name to logging classes. + // One holds StateMachine logs and the other connectivity packet logs. + private static final ConcurrentHashMap<String, SharedLog> sSmLogs = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap<String, LocalLog> sPktLogs = new ConcurrentHashMap<>(); + + /** + * Dump all state machine and connectivity packet logs to the specified writer. + * @param skippedIfaces Interfaces for which logs should not be dumped. + */ + public static void dumpAllLogs(PrintWriter writer, Set<String> skippedIfaces) { + for (String ifname : sSmLogs.keySet()) { + if (skippedIfaces.contains(ifname)) continue; + + writer.println(String.format("--- BEGIN %s ---", ifname)); + + final SharedLog smLog = sSmLogs.get(ifname); + if (smLog != null) { + writer.println("State machine log:"); + smLog.dump(null, writer, null); + } + + writer.println(""); + + final LocalLog pktLog = sPktLogs.get(ifname); + if (pktLog != null) { + writer.println("Connectivity packet log:"); + pktLog.readOnlyLocalLog().dump(null, writer, null); + } + + writer.println(String.format("--- END %s ---", ifname)); + } + } + + // Use a wrapper class to log in order to ensure complete and detailed + // logging. This method is lighter weight than annotations/reflection + // and has the following benefits: + // + // - No invoked method can be forgotten. + // Any new method added to IpClient.Callback must be overridden + // here or it will never be called. + // + // - No invoking call site can be forgotten. + // Centralized logging in this way means call sites don't need to + // remember to log, and therefore no call site can be forgotten. + // + // - No variation in log format among call sites. + // Encourages logging of any available arguments, and all call sites + // are necessarily logged identically. + // + // NOTE: Log first because passed objects may or may not be thread-safe and + // once passed on to the callback they may be modified by another thread. + // + // TODO: Find an lighter weight approach. + public static class IpClientCallbacksWrapper { + private static final String PREFIX = "INVOKE "; + private final IIpClientCallbacks mCallback; + private final SharedLog mLog; + + @VisibleForTesting + protected IpClientCallbacksWrapper(IIpClientCallbacks callback, SharedLog log) { + mCallback = callback; + mLog = log; + } + + private void log(String msg) { + mLog.log(PREFIX + msg); + } + + private void log(String msg, Throwable e) { + mLog.e(PREFIX + msg, e); + } + + public void onPreDhcpAction() { + log("onPreDhcpAction()"); + try { + mCallback.onPreDhcpAction(); + } catch (RemoteException e) { + log("Failed to call onPreDhcpAction", e); + } + } + + public void onPostDhcpAction() { + log("onPostDhcpAction()"); + try { + mCallback.onPostDhcpAction(); + } catch (RemoteException e) { + log("Failed to call onPostDhcpAction", e); + } + } + + public void onNewDhcpResults(DhcpResults dhcpResults) { + log("onNewDhcpResults({" + dhcpResults + "})"); + try { + mCallback.onNewDhcpResults(toStableParcelable(dhcpResults)); + } catch (RemoteException e) { + log("Failed to call onNewDhcpResults", e); + } + } + + public void onProvisioningSuccess(LinkProperties newLp) { + log("onProvisioningSuccess({" + newLp + "})"); + try { + mCallback.onProvisioningSuccess(toStableParcelable(newLp)); + } catch (RemoteException e) { + log("Failed to call onProvisioningSuccess", e); + } + } + + public void onProvisioningFailure(LinkProperties newLp) { + log("onProvisioningFailure({" + newLp + "})"); + try { + mCallback.onProvisioningFailure(toStableParcelable(newLp)); + } catch (RemoteException e) { + log("Failed to call onProvisioningFailure", e); + } + } + + public void onLinkPropertiesChange(LinkProperties newLp) { + log("onLinkPropertiesChange({" + newLp + "})"); + try { + mCallback.onLinkPropertiesChange(toStableParcelable(newLp)); + } catch (RemoteException e) { + log("Failed to call onLinkPropertiesChange", e); + } + } + + public void onReachabilityLost(String logMsg) { + log("onReachabilityLost(" + logMsg + ")"); + try { + mCallback.onReachabilityLost(logMsg); + } catch (RemoteException e) { + log("Failed to call onReachabilityLost", e); + } + } + + public void onQuit() { + log("onQuit()"); + try { + mCallback.onQuit(); + } catch (RemoteException e) { + log("Failed to call onQuit", e); + } + } + + public void installPacketFilter(byte[] filter) { + log("installPacketFilter(byte[" + filter.length + "])"); + try { + mCallback.installPacketFilter(filter); + } catch (RemoteException e) { + log("Failed to call installPacketFilter", e); + } + } + + public void startReadPacketFilter() { + log("startReadPacketFilter()"); + try { + mCallback.startReadPacketFilter(); + } catch (RemoteException e) { + log("Failed to call startReadPacketFilter", e); + } + } + + public void setFallbackMulticastFilter(boolean enabled) { + log("setFallbackMulticastFilter(" + enabled + ")"); + try { + mCallback.setFallbackMulticastFilter(enabled); + } catch (RemoteException e) { + log("Failed to call setFallbackMulticastFilter", e); + } + } + + public void setNeighborDiscoveryOffload(boolean enable) { + log("setNeighborDiscoveryOffload(" + enable + ")"); + try { + mCallback.setNeighborDiscoveryOffload(enable); + } catch (RemoteException e) { + log("Failed to call setNeighborDiscoveryOffload", e); + } + } + } + + public static final String DUMP_ARG_CONFIRM = "confirm"; + + private static final int CMD_TERMINATE_AFTER_STOP = 1; + private static final int CMD_STOP = 2; + private static final int CMD_START = 3; + private static final int CMD_CONFIRM = 4; + private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 5; + // Triggered by NetlinkTracker to communicate netlink events. + private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6; + private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 7; + private static final int CMD_UPDATE_HTTP_PROXY = 8; + private static final int CMD_SET_MULTICAST_FILTER = 9; + private static final int EVENT_PROVISIONING_TIMEOUT = 10; + private static final int EVENT_DHCPACTION_TIMEOUT = 11; + private static final int EVENT_READ_PACKET_FILTER_COMPLETE = 12; + + // Internal commands to use instead of trying to call transitionTo() inside + // a given State's enter() method. Calling transitionTo() from enter/exit + // encounters a Log.wtf() that can cause trouble on eng builds. + private static final int CMD_JUMP_STARTED_TO_RUNNING = 100; + private static final int CMD_JUMP_RUNNING_TO_STOPPING = 101; + private static final int CMD_JUMP_STOPPING_TO_STOPPED = 102; + + // IpClient shares a handler with DhcpClient: commands must not overlap + public static final int DHCPCLIENT_CMD_BASE = 1000; + + private static final int MAX_LOG_RECORDS = 500; + private static final int MAX_PACKET_RECORDS = 100; + + private static final boolean NO_CALLBACKS = false; + private static final boolean SEND_CALLBACKS = true; + + // This must match the interface prefix in clatd.c. + // TODO: Revert this hack once IpClient and Nat464Xlat work in concert. + private static final String CLAT_PREFIX = "v4-"; + + private static final int IMMEDIATE_FAILURE_DURATION = 0; + + private static final int PROV_CHANGE_STILL_NOT_PROVISIONED = 1; + private static final int PROV_CHANGE_LOST_PROVISIONING = 2; + private static final int PROV_CHANGE_GAINED_PROVISIONING = 3; + private static final int PROV_CHANGE_STILL_PROVISIONED = 4; + + private final State mStoppedState = new StoppedState(); + private final State mStoppingState = new StoppingState(); + private final State mStartedState = new StartedState(); + private final State mRunningState = new RunningState(); + + private final String mTag; + private final Context mContext; + private final String mInterfaceName; + private final String mClatInterfaceName; + @VisibleForTesting + protected final IpClientCallbacksWrapper mCallback; + private final Dependencies mDependencies; + private final CountDownLatch mShutdownLatch; + private final ConnectivityManager mCm; + private final INetworkManagementService mNwService; + private final NetlinkTracker mNetlinkTracker; + private final WakeupMessage mProvisioningTimeoutAlarm; + private final WakeupMessage mDhcpActionTimeoutAlarm; + private final SharedLog mLog; + private final LocalLog mConnectivityPacketLog; + private final MessageHandlingLogger mMsgStateLogger; + private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); + private final InterfaceController mInterfaceCtrl; + + private InterfaceParams mInterfaceParams; + + /** + * Non-final member variables accessed only from within our StateMachine. + */ + private LinkProperties mLinkProperties; + private android.net.shared.ProvisioningConfiguration mConfiguration; + private IpReachabilityMonitor mIpReachabilityMonitor; + private DhcpClient mDhcpClient; + private DhcpResults mDhcpResults; + private String mTcpBufferSizes; + private ProxyInfo mHttpProxy; + private ApfFilter mApfFilter; + private boolean mMulticastFiltering; + private long mStartTimeMillis; + + /** + * Reading the snapshot is an asynchronous operation initiated by invoking + * Callback.startReadPacketFilter() and completed when the WiFi Service responds with an + * EVENT_READ_PACKET_FILTER_COMPLETE message. The mApfDataSnapshotComplete condition variable + * signals when a new snapshot is ready. + */ + private final ConditionVariable mApfDataSnapshotComplete = new ConditionVariable(); + + public static class Dependencies { + public INetworkManagementService getNMS() { + return INetworkManagementService.Stub.asInterface( + ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); + } + + public INetd getNetd() { + return NetdService.getInstance(); + } + + /** + * Get interface parameters for the specified interface. + */ + public InterfaceParams getInterfaceParams(String ifname) { + return InterfaceParams.getByName(ifname); + } + } + + public IpClient(Context context, String ifName, IIpClientCallbacks callback) { + this(context, ifName, callback, new Dependencies()); + } + + /** + * An expanded constructor, useful for dependency injection. + * TODO: migrate all test users to mock IpClient directly and remove this ctor. + */ + public IpClient(Context context, String ifName, IIpClientCallbacks callback, + INetworkManagementService nwService) { + this(context, ifName, callback, new Dependencies() { + @Override + public INetworkManagementService getNMS() { + return nwService; + } + }); + } + + @VisibleForTesting + IpClient(Context context, String ifName, IIpClientCallbacks callback, Dependencies deps) { + super(IpClient.class.getSimpleName() + "." + ifName); + Preconditions.checkNotNull(ifName); + Preconditions.checkNotNull(callback); + + mTag = getName(); + + mContext = context; + mInterfaceName = ifName; + mClatInterfaceName = CLAT_PREFIX + ifName; + mDependencies = deps; + mShutdownLatch = new CountDownLatch(1); + mCm = mContext.getSystemService(ConnectivityManager.class); + mNwService = deps.getNMS(); + + sSmLogs.putIfAbsent(mInterfaceName, new SharedLog(MAX_LOG_RECORDS, mTag)); + mLog = sSmLogs.get(mInterfaceName); + sPktLogs.putIfAbsent(mInterfaceName, new LocalLog(MAX_PACKET_RECORDS)); + mConnectivityPacketLog = sPktLogs.get(mInterfaceName); + mMsgStateLogger = new MessageHandlingLogger(); + mCallback = new IpClientCallbacksWrapper(callback, mLog); + + // TODO: Consider creating, constructing, and passing in some kind of + // InterfaceController.Dependencies class. + mInterfaceCtrl = new InterfaceController(mInterfaceName, deps.getNetd(), mLog); + + mNetlinkTracker = new NetlinkTracker( + mInterfaceName, + new NetlinkTracker.Callback() { + @Override + public void update() { + sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED); + } + }) { + @Override + public void interfaceAdded(String iface) { + super.interfaceAdded(iface); + if (mClatInterfaceName.equals(iface)) { + mCallback.setNeighborDiscoveryOffload(false); + } else if (!mInterfaceName.equals(iface)) { + return; + } + + final String msg = "interfaceAdded(" + iface + ")"; + logMsg(msg); + } + + @Override + public void interfaceRemoved(String iface) { + super.interfaceRemoved(iface); + // TODO: Also observe mInterfaceName going down and take some + // kind of appropriate action. + if (mClatInterfaceName.equals(iface)) { + // TODO: consider sending a message to the IpClient main + // StateMachine thread, in case "NDO enabled" state becomes + // tied to more things that 464xlat operation. + mCallback.setNeighborDiscoveryOffload(true); + } else if (!mInterfaceName.equals(iface)) { + return; + } + + final String msg = "interfaceRemoved(" + iface + ")"; + logMsg(msg); + } + + private void logMsg(String msg) { + Log.d(mTag, msg); + getHandler().post(() -> mLog.log("OBSERVED " + msg)); + } + }; + + mLinkProperties = new LinkProperties(); + mLinkProperties.setInterfaceName(mInterfaceName); + + mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(), + mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT); + mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(), + mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT); + + // Anything the StateMachine may access must have been instantiated + // before this point. + configureAndStartStateMachine(); + + // Anything that may send messages to the StateMachine must only be + // configured to do so after the StateMachine has started (above). + startStateMachineUpdaters(); + } + + /** + * Make a IIpClient connector to communicate with this IpClient. + */ + public IIpClient makeConnector() { + return new IpClientConnector(); + } + + class IpClientConnector extends IIpClient.Stub { + @Override + public void completedPreDhcpAction() { + checkNetworkStackCallingPermission(); + IpClient.this.completedPreDhcpAction(); + } + @Override + public void confirmConfiguration() { + checkNetworkStackCallingPermission(); + IpClient.this.confirmConfiguration(); + } + @Override + public void readPacketFilterComplete(byte[] data) { + checkNetworkStackCallingPermission(); + IpClient.this.readPacketFilterComplete(data); + } + @Override + public void shutdown() { + checkNetworkStackCallingPermission(); + IpClient.this.shutdown(); + } + @Override + public void startProvisioning(ProvisioningConfigurationParcelable req) { + checkNetworkStackCallingPermission(); + IpClient.this.startProvisioning(ProvisioningConfiguration.fromStableParcelable(req)); + } + @Override + public void stop() { + checkNetworkStackCallingPermission(); + IpClient.this.stop(); + } + @Override + public void setTcpBufferSizes(String tcpBufferSizes) { + checkNetworkStackCallingPermission(); + IpClient.this.setTcpBufferSizes(tcpBufferSizes); + } + @Override + public void setHttpProxy(ProxyInfoParcelable proxyInfo) { + checkNetworkStackCallingPermission(); + IpClient.this.setHttpProxy(fromStableParcelable(proxyInfo)); + } + @Override + public void setMulticastFilter(boolean enabled) { + checkNetworkStackCallingPermission(); + IpClient.this.setMulticastFilter(enabled); + } + } + + public String getInterfaceName() { + return mInterfaceName; + } + + private void configureAndStartStateMachine() { + // CHECKSTYLE:OFF IndentationCheck + addState(mStoppedState); + addState(mStartedState); + addState(mRunningState, mStartedState); + addState(mStoppingState); + // CHECKSTYLE:ON IndentationCheck + + setInitialState(mStoppedState); + + super.start(); + } + + private void startStateMachineUpdaters() { + try { + mNwService.registerObserver(mNetlinkTracker); + } catch (RemoteException e) { + logError("Couldn't register NetlinkTracker: %s", e); + } + } + + private void stopStateMachineUpdaters() { + try { + mNwService.unregisterObserver(mNetlinkTracker); + } catch (RemoteException e) { + logError("Couldn't unregister NetlinkTracker: %s", e); + } + } + + @Override + protected void onQuitting() { + mCallback.onQuit(); + mShutdownLatch.countDown(); + } + + /** + * Shut down this IpClient instance altogether. + */ + public void shutdown() { + stop(); + sendMessage(CMD_TERMINATE_AFTER_STOP); + } + + /** + * Start provisioning with the provided parameters. + */ + public void startProvisioning(ProvisioningConfiguration req) { + if (!req.isValid()) { + doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); + return; + } + + mInterfaceParams = mDependencies.getInterfaceParams(mInterfaceName); + if (mInterfaceParams == null) { + logError("Failed to find InterfaceParams for " + mInterfaceName); + doImmediateProvisioningFailure(IpManagerEvent.ERROR_INTERFACE_NOT_FOUND); + return; + } + + mCallback.setNeighborDiscoveryOffload(true); + sendMessage(CMD_START, new android.net.shared.ProvisioningConfiguration(req)); + } + + /** + * Stop this IpClient. + * + * <p>This does not shut down the StateMachine itself, which is handled by {@link #shutdown()}. + */ + public void stop() { + sendMessage(CMD_STOP); + } + + /** + * Confirm the provisioning configuration. + */ + public void confirmConfiguration() { + sendMessage(CMD_CONFIRM); + } + + /** + * For clients using {@link ProvisioningConfiguration.Builder#withPreDhcpAction()}, must be + * called after {@link IIpClientCallbacks#onPreDhcpAction} to indicate that DHCP is clear to + * proceed. + */ + public void completedPreDhcpAction() { + sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE); + } + + /** + * Indicate that packet filter read is complete. + */ + public void readPacketFilterComplete(byte[] data) { + sendMessage(EVENT_READ_PACKET_FILTER_COMPLETE, data); + } + + /** + * Set the TCP buffer sizes to use. + * + * This may be called, repeatedly, at any time before or after a call to + * #startProvisioning(). The setting is cleared upon calling #stop(). + */ + public void setTcpBufferSizes(String tcpBufferSizes) { + sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes); + } + + /** + * Set the HTTP Proxy configuration to use. + * + * This may be called, repeatedly, at any time before or after a call to + * #startProvisioning(). The setting is cleared upon calling #stop(). + */ + public void setHttpProxy(ProxyInfo proxyInfo) { + sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo); + } + + /** + * Enable or disable the multicast filter. Attempts to use APF to accomplish the filtering, + * if not, Callback.setFallbackMulticastFilter() is called. + */ + public void setMulticastFilter(boolean enabled) { + sendMessage(CMD_SET_MULTICAST_FILTER, enabled); + } + + /** + * Dump logs of this IpClient. + */ + public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) { + // Execute confirmConfiguration() and take no further action. + confirmConfiguration(); + return; + } + + // Thread-unsafe access to mApfFilter but just used for debugging. + final ApfFilter apfFilter = mApfFilter; + final android.net.shared.ProvisioningConfiguration provisioningConfig = mConfiguration; + final ApfCapabilities apfCapabilities = (provisioningConfig != null) + ? provisioningConfig.mApfCapabilities : null; + + IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); + pw.println(mTag + " APF dump:"); + pw.increaseIndent(); + if (apfFilter != null) { + if (apfCapabilities.hasDataAccess()) { + // Request a new snapshot, then wait for it. + mApfDataSnapshotComplete.close(); + mCallback.startReadPacketFilter(); + if (!mApfDataSnapshotComplete.block(1000)) { + pw.print("TIMEOUT: DUMPING STALE APF SNAPSHOT"); + } + } + apfFilter.dump(pw); + + } else { + pw.print("No active ApfFilter; "); + if (provisioningConfig == null) { + pw.println("IpClient not yet started."); + } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) { + pw.println("Hardware does not support APF."); + } else { + pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities); + } + } + pw.decreaseIndent(); + pw.println(); + pw.println(mTag + " current ProvisioningConfiguration:"); + pw.increaseIndent(); + pw.println(Objects.toString(provisioningConfig, "N/A")); + pw.decreaseIndent(); + + final IpReachabilityMonitor iprm = mIpReachabilityMonitor; + if (iprm != null) { + pw.println(); + pw.println(mTag + " current IpReachabilityMonitor state:"); + pw.increaseIndent(); + iprm.dump(pw); + pw.decreaseIndent(); + } + + pw.println(); + pw.println(mTag + " StateMachine dump:"); + pw.increaseIndent(); + mLog.dump(fd, pw, args); + pw.decreaseIndent(); + + pw.println(); + pw.println(mTag + " connectivity packet log:"); + pw.println(); + pw.println("Debug with python and scapy via:"); + pw.println("shell$ python"); + pw.println(">>> from scapy import all as scapy"); + pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()"); + pw.println(); + + pw.increaseIndent(); + mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args); + pw.decreaseIndent(); + } + + + /** + * Internals. + */ + + @Override + protected String getWhatToString(int what) { + return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what)); + } + + @Override + protected String getLogRecString(Message msg) { + final String logLine = String.format( + "%s/%d %d %d %s [%s]", + mInterfaceName, (mInterfaceParams == null) ? -1 : mInterfaceParams.index, + msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger); + + final String richerLogLine = getWhatToString(msg.what) + " " + logLine; + mLog.log(richerLogLine); + if (DBG) { + Log.d(mTag, richerLogLine); + } + + mMsgStateLogger.reset(); + return logLine; + } + + @Override + protected boolean recordLogRec(Message msg) { + // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy, + // and we already log any LinkProperties change that results in an + // invocation of IpClient.Callback#onLinkPropertiesChange(). + final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED); + if (!shouldLog) { + mMsgStateLogger.reset(); + } + return shouldLog; + } + + private void logError(String fmt, Object... args) { + final String msg = "ERROR " + String.format(fmt, args); + Log.e(mTag, msg); + mLog.log(msg); + } + + // This needs to be called with care to ensure that our LinkProperties + // are in sync with the actual LinkProperties of the interface. For example, + // we should only call this if we know for sure that there are no IP addresses + // assigned to the interface, etc. + private void resetLinkProperties() { + mNetlinkTracker.clearLinkProperties(); + mConfiguration = null; + mDhcpResults = null; + mTcpBufferSizes = ""; + mHttpProxy = null; + + mLinkProperties = new LinkProperties(); + mLinkProperties.setInterfaceName(mInterfaceName); + } + + private void recordMetric(final int type) { + // We may record error metrics prior to starting. + // Map this to IMMEDIATE_FAILURE_DURATION. + final long duration = (mStartTimeMillis > 0) + ? (SystemClock.elapsedRealtime() - mStartTimeMillis) + : IMMEDIATE_FAILURE_DURATION; + mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration)); + } + + // For now: use WifiStateMachine's historical notion of provisioned. + @VisibleForTesting + static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) { + // For historical reasons, we should connect even if all we have is + // an IPv4 address and nothing else. + if (lp.hasIPv4Address() || lp.isProvisioned()) { + return true; + } + if (config == null) { + return false; + } + + // When an InitialConfiguration is specified, ignore any difference with previous + // properties and instead check if properties observed match the desired properties. + return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes()); + } + + // TODO: Investigate folding all this into the existing static function + // LinkProperties.compareProvisioning() or some other single function that + // takes two LinkProperties objects and returns a ProvisioningChange + // object that is a correct and complete assessment of what changed, taking + // account of the asymmetries described in the comments in this function. + // Then switch to using it everywhere (IpReachabilityMonitor, etc.). + private int compareProvisioning(LinkProperties oldLp, LinkProperties newLp) { + int delta; + InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null; + final boolean wasProvisioned = isProvisioned(oldLp, config); + final boolean isProvisioned = isProvisioned(newLp, config); + + if (!wasProvisioned && isProvisioned) { + delta = PROV_CHANGE_GAINED_PROVISIONING; + } else if (wasProvisioned && isProvisioned) { + delta = PROV_CHANGE_STILL_PROVISIONED; + } else if (!wasProvisioned && !isProvisioned) { + delta = PROV_CHANGE_STILL_NOT_PROVISIONED; + } else { + // (wasProvisioned && !isProvisioned) + // + // Note that this is true even if we lose a configuration element + // (e.g., a default gateway) that would not be required to advance + // into provisioned state. This is intended: if we have a default + // router and we lose it, that's a sure sign of a problem, but if + // we connect to a network with no IPv4 DNS servers, we consider + // that to be a network without DNS servers and connect anyway. + // + // See the comment below. + delta = PROV_CHANGE_LOST_PROVISIONING; + } + + final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned(); + final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address(); + final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute(); + + // If bad wifi avoidance is disabled, then ignore IPv6 loss of + // provisioning. Otherwise, when a hotspot that loses Internet + // access sends out a 0-lifetime RA to its clients, the clients + // will disconnect and then reconnect, avoiding the bad hotspot, + // instead of getting stuck on the bad hotspot. http://b/31827713 . + // + // This is incorrect because if the hotspot then regains Internet + // access with a different prefix, TCP connections on the + // deprecated addresses will remain stuck. + // + // Note that we can still be disconnected by IpReachabilityMonitor + // if the IPv6 default gateway (but not the IPv6 DNS servers; see + // accompanying code in IpReachabilityMonitor) is unreachable. + final boolean ignoreIPv6ProvisioningLoss = + mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker + && mCm.getAvoidBadWifi(); + + // Additionally: + // + // Partial configurations (e.g., only an IPv4 address with no DNS + // servers and no default route) are accepted as long as DHCPv4 + // succeeds. On such a network, isProvisioned() will always return + // false, because the configuration is not complete, but we want to + // connect anyway. It might be a disconnected network such as a + // Chromecast or a wireless printer, for example. + // + // Because on such a network isProvisioned() will always return false, + // delta will never be LOST_PROVISIONING. So check for loss of + // provisioning here too. + if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) { + delta = PROV_CHANGE_LOST_PROVISIONING; + } + + // Additionally: + // + // If the previous link properties had a global IPv6 address and an + // IPv6 default route then also consider the loss of that default route + // to be a loss of provisioning. See b/27962810. + if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) { + delta = PROV_CHANGE_LOST_PROVISIONING; + } + + return delta; + } + + private void dispatchCallback(int delta, LinkProperties newLp) { + switch (delta) { + case PROV_CHANGE_GAINED_PROVISIONING: + if (DBG) { + Log.d(mTag, "onProvisioningSuccess()"); + } + recordMetric(IpManagerEvent.PROVISIONING_OK); + mCallback.onProvisioningSuccess(newLp); + break; + + case PROV_CHANGE_LOST_PROVISIONING: + if (DBG) { + Log.d(mTag, "onProvisioningFailure()"); + } + recordMetric(IpManagerEvent.PROVISIONING_FAIL); + mCallback.onProvisioningFailure(newLp); + break; + + default: + if (DBG) { + Log.d(mTag, "onLinkPropertiesChange()"); + } + mCallback.onLinkPropertiesChange(newLp); + break; + } + } + + // Updates all IpClient-related state concerned with LinkProperties. + // Returns a ProvisioningChange for possibly notifying other interested + // parties that are not fronted by IpClient. + private int setLinkProperties(LinkProperties newLp) { + if (mApfFilter != null) { + mApfFilter.setLinkProperties(newLp); + } + if (mIpReachabilityMonitor != null) { + mIpReachabilityMonitor.updateLinkProperties(newLp); + } + + int delta = compareProvisioning(mLinkProperties, newLp); + mLinkProperties = new LinkProperties(newLp); + + if (delta == PROV_CHANGE_GAINED_PROVISIONING) { + // TODO: Add a proper ProvisionedState and cancel the alarm in + // its enter() method. + mProvisioningTimeoutAlarm.cancel(); + } + + return delta; + } + + private LinkProperties assembleLinkProperties() { + // [1] Create a new LinkProperties object to populate. + LinkProperties newLp = new LinkProperties(); + newLp.setInterfaceName(mInterfaceName); + + // [2] Pull in data from netlink: + // - IPv4 addresses + // - IPv6 addresses + // - IPv6 routes + // - IPv6 DNS servers + // + // N.B.: this is fundamentally race-prone and should be fixed by + // changing NetlinkTracker from a hybrid edge/level model to an + // edge-only model, or by giving IpClient its own netlink socket(s) + // so as to track all required information directly. + LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties(); + newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses()); + for (RouteInfo route : netlinkLinkProperties.getRoutes()) { + newLp.addRoute(route); + } + addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers()); + + // [3] Add in data from DHCPv4, if available. + // + // mDhcpResults is never shared with any other owner so we don't have + // to worry about concurrent modification. + if (mDhcpResults != null) { + for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) { + newLp.addRoute(route); + } + addAllReachableDnsServers(newLp, mDhcpResults.dnsServers); + newLp.setDomains(mDhcpResults.domains); + + if (mDhcpResults.mtu != 0) { + newLp.setMtu(mDhcpResults.mtu); + } + } + + // [4] Add in TCP buffer sizes and HTTP Proxy config, if available. + if (!TextUtils.isEmpty(mTcpBufferSizes)) { + newLp.setTcpBufferSizes(mTcpBufferSizes); + } + if (mHttpProxy != null) { + newLp.setHttpProxy(mHttpProxy); + } + + // [5] Add data from InitialConfiguration + if (mConfiguration != null && mConfiguration.mInitialConfig != null) { + InitialConfiguration config = mConfiguration.mInitialConfig; + // Add InitialConfiguration routes and dns server addresses once all addresses + // specified in the InitialConfiguration have been observed with Netlink. + if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) { + for (IpPrefix prefix : config.directlyConnectedRoutes) { + newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName)); + } + } + addAllReachableDnsServers(newLp, config.dnsServers); + } + final LinkProperties oldLp = mLinkProperties; + if (DBG) { + Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s", + netlinkLinkProperties, newLp, oldLp)); + } + + // TODO: also learn via netlink routes specified by an InitialConfiguration and specified + // from a static IP v4 config instead of manually patching them in in steps [3] and [5]. + return newLp; + } + + private static void addAllReachableDnsServers( + LinkProperties lp, Iterable<InetAddress> dnses) { + // TODO: Investigate deleting this reachability check. We should be + // able to pass everything down to netd and let netd do evaluation + // and RFC6724-style sorting. + for (InetAddress dns : dnses) { + if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) { + lp.addDnsServer(dns); + } + } + } + + // Returns false if we have lost provisioning, true otherwise. + private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) { + final LinkProperties newLp = assembleLinkProperties(); + if (Objects.equals(newLp, mLinkProperties)) { + return true; + } + final int delta = setLinkProperties(newLp); + if (sendCallbacks) { + dispatchCallback(delta, newLp); + } + return (delta != PROV_CHANGE_LOST_PROVISIONING); + } + + private void handleIPv4Success(DhcpResults dhcpResults) { + mDhcpResults = new DhcpResults(dhcpResults); + final LinkProperties newLp = assembleLinkProperties(); + final int delta = setLinkProperties(newLp); + + if (DBG) { + Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")"); + } + mCallback.onNewDhcpResults(dhcpResults); + dispatchCallback(delta, newLp); + } + + private void handleIPv4Failure() { + // TODO: Investigate deleting this clearIPv4Address() call. + // + // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances + // that could trigger a call to this function. If we missed handling + // that message in StartedState for some reason we would still clear + // any addresses upon entry to StoppedState. + mInterfaceCtrl.clearIPv4Address(); + mDhcpResults = null; + if (DBG) { + Log.d(mTag, "onNewDhcpResults(null)"); + } + mCallback.onNewDhcpResults(null); + + handleProvisioningFailure(); + } + + private void handleProvisioningFailure() { + final LinkProperties newLp = assembleLinkProperties(); + int delta = setLinkProperties(newLp); + // If we've gotten here and we're still not provisioned treat that as + // a total loss of provisioning. + // + // Either (a) static IP configuration failed or (b) DHCPv4 failed AND + // there was no usable IPv6 obtained before a non-zero provisioning + // timeout expired. + // + // Regardless: GAME OVER. + if (delta == PROV_CHANGE_STILL_NOT_PROVISIONED) { + delta = PROV_CHANGE_LOST_PROVISIONING; + } + + dispatchCallback(delta, newLp); + if (delta == PROV_CHANGE_LOST_PROVISIONING) { + transitionTo(mStoppingState); + } + } + + private void doImmediateProvisioningFailure(int failureType) { + logError("onProvisioningFailure(): %s", failureType); + recordMetric(failureType); + mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties)); + } + + private boolean startIPv4() { + // If we have a StaticIpConfiguration attempt to apply it and + // handle the result accordingly. + if (mConfiguration.mStaticIpConfig != null) { + if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) { + handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig)); + } else { + return false; + } + } else { + // Start DHCPv4. + mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceParams); + mDhcpClient.registerForPreDhcpNotification(); + mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP); + } + + return true; + } + + private boolean startIPv6() { + return mInterfaceCtrl.setIPv6PrivacyExtensions(true) + && mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) + && mInterfaceCtrl.enableIPv6(); + } + + private boolean applyInitialConfig(InitialConfiguration config) { + // TODO: also support specifying a static IPv4 configuration in InitialConfiguration. + for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) { + if (!mInterfaceCtrl.addAddress(addr)) return false; + } + + return true; + } + + private boolean startIpReachabilityMonitor() { + try { + // TODO: Fetch these parameters from settings, and install a + // settings observer to watch for update and re-program these + // parameters (Q: is this level of dynamic updatability really + // necessary or does reading from settings at startup suffice?). + final int numSolicits = 5; + final int interSolicitIntervalMs = 750; + setNeighborParameters(mDependencies.getNetd(), mInterfaceName, + numSolicits, interSolicitIntervalMs); + } catch (Exception e) { + mLog.e("Failed to adjust neighbor parameters", e); + // Carry on using the system defaults (currently: 3, 1000); + } + + try { + mIpReachabilityMonitor = new IpReachabilityMonitor( + mContext, + mInterfaceParams, + getHandler(), + mLog, + new IpReachabilityMonitor.Callback() { + @Override + public void notifyLost(InetAddress ip, String logMsg) { + mCallback.onReachabilityLost(logMsg); + } + }, + mConfiguration.mUsingMultinetworkPolicyTracker); + } catch (IllegalArgumentException iae) { + // Failed to start IpReachabilityMonitor. Log it and call + // onProvisioningFailure() immediately. + // + // See http://b/31038971. + logError("IpReachabilityMonitor failure: %s", iae); + mIpReachabilityMonitor = null; + } + + return (mIpReachabilityMonitor != null); + } + + private void stopAllIP() { + // We don't need to worry about routes, just addresses, because: + // - disableIpv6() will clear autoconf IPv6 routes as well, and + // - we don't get IPv4 routes from netlink + // so we neither react to nor need to wait for changes in either. + + mInterfaceCtrl.disableIPv6(); + mInterfaceCtrl.clearAllAddresses(); + } + + class StoppedState extends State { + @Override + public void enter() { + stopAllIP(); + + resetLinkProperties(); + if (mStartTimeMillis > 0) { + // Completed a life-cycle; send a final empty LinkProperties + // (cleared in resetLinkProperties() above) and record an event. + mCallback.onLinkPropertiesChange(new LinkProperties(mLinkProperties)); + recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE); + mStartTimeMillis = 0; + } + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_TERMINATE_AFTER_STOP: + stopStateMachineUpdaters(); + quit(); + break; + + case CMD_STOP: + break; + + case CMD_START: + mConfiguration = (android.net.shared.ProvisioningConfiguration) msg.obj; + transitionTo(mStartedState); + break; + + case EVENT_NETLINK_LINKPROPERTIES_CHANGED: + handleLinkPropertiesUpdate(NO_CALLBACKS); + break; + + case CMD_UPDATE_TCP_BUFFER_SIZES: + mTcpBufferSizes = (String) msg.obj; + handleLinkPropertiesUpdate(NO_CALLBACKS); + break; + + case CMD_UPDATE_HTTP_PROXY: + mHttpProxy = (ProxyInfo) msg.obj; + handleLinkPropertiesUpdate(NO_CALLBACKS); + break; + + case CMD_SET_MULTICAST_FILTER: + mMulticastFiltering = (boolean) msg.obj; + break; + + case DhcpClient.CMD_ON_QUIT: + // Everything is already stopped. + logError("Unexpected CMD_ON_QUIT (already stopped)."); + break; + + default: + return NOT_HANDLED; + } + + mMsgStateLogger.handled(this, getCurrentState()); + return HANDLED; + } + } + + class StoppingState extends State { + @Override + public void enter() { + if (mDhcpClient == null) { + // There's no DHCPv4 for which to wait; proceed to stopped. + deferMessage(obtainMessage(CMD_JUMP_STOPPING_TO_STOPPED)); + } + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_JUMP_STOPPING_TO_STOPPED: + transitionTo(mStoppedState); + break; + + case CMD_STOP: + break; + + case DhcpClient.CMD_CLEAR_LINKADDRESS: + mInterfaceCtrl.clearIPv4Address(); + break; + + case DhcpClient.CMD_ON_QUIT: + mDhcpClient = null; + transitionTo(mStoppedState); + break; + + default: + deferMessage(msg); + } + + mMsgStateLogger.handled(this, getCurrentState()); + return HANDLED; + } + } + + class StartedState extends State { + @Override + public void enter() { + mStartTimeMillis = SystemClock.elapsedRealtime(); + + if (mConfiguration.mProvisioningTimeoutMs > 0) { + final long alarmTime = SystemClock.elapsedRealtime() + + mConfiguration.mProvisioningTimeoutMs; + mProvisioningTimeoutAlarm.schedule(alarmTime); + } + + if (readyToProceed()) { + deferMessage(obtainMessage(CMD_JUMP_STARTED_TO_RUNNING)); + } else { + // Clear all IPv4 and IPv6 before proceeding to RunningState. + // Clean up any leftover state from an abnormal exit from + // tethering or during an IpClient restart. + stopAllIP(); + } + } + + @Override + public void exit() { + mProvisioningTimeoutAlarm.cancel(); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_JUMP_STARTED_TO_RUNNING: + transitionTo(mRunningState); + break; + + case CMD_STOP: + transitionTo(mStoppingState); + break; + + case EVENT_NETLINK_LINKPROPERTIES_CHANGED: + handleLinkPropertiesUpdate(NO_CALLBACKS); + if (readyToProceed()) { + transitionTo(mRunningState); + } + break; + + case EVENT_PROVISIONING_TIMEOUT: + handleProvisioningFailure(); + break; + + default: + // It's safe to process messages out of order because the + // only message that can both + // a) be received at this time and + // b) affect provisioning state + // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above). + deferMessage(msg); + } + + mMsgStateLogger.handled(this, getCurrentState()); + return HANDLED; + } + + private boolean readyToProceed() { + return (!mLinkProperties.hasIPv4Address() && !mLinkProperties.hasGlobalIPv6Address()); + } + } + + class RunningState extends State { + private ConnectivityPacketTracker mPacketTracker; + private boolean mDhcpActionInFlight; + + @Override + public void enter() { + ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration(); + apfConfig.apfCapabilities = mConfiguration.mApfCapabilities; + apfConfig.multicastFilter = mMulticastFiltering; + // Get the Configuration for ApfFilter from Context + apfConfig.ieee802_3Filter = + mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames); + apfConfig.ethTypeBlackList = + mContext.getResources().getIntArray(R.array.config_apfEthTypeBlackList); + mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback); + // TODO: investigate the effects of any multicast filtering racing/interfering with the + // rest of this IP configuration startup. + if (mApfFilter == null) { + mCallback.setFallbackMulticastFilter(mMulticastFiltering); + } + + mPacketTracker = createPacketTracker(); + if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName); + + if (mConfiguration.mEnableIPv6 && !startIPv6()) { + doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6); + enqueueJumpToStoppingState(); + return; + } + + if (mConfiguration.mEnableIPv4 && !startIPv4()) { + doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4); + enqueueJumpToStoppingState(); + return; + } + + final InitialConfiguration config = mConfiguration.mInitialConfig; + if ((config != null) && !applyInitialConfig(config)) { + // TODO introduce a new IpManagerEvent constant to distinguish this error case. + doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); + enqueueJumpToStoppingState(); + return; + } + + if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) { + doImmediateProvisioningFailure( + IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR); + enqueueJumpToStoppingState(); + return; + } + } + + @Override + public void exit() { + stopDhcpAction(); + + if (mIpReachabilityMonitor != null) { + mIpReachabilityMonitor.stop(); + mIpReachabilityMonitor = null; + } + + if (mDhcpClient != null) { + mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP); + mDhcpClient.doQuit(); + } + + if (mPacketTracker != null) { + mPacketTracker.stop(); + mPacketTracker = null; + } + + if (mApfFilter != null) { + mApfFilter.shutdown(); + mApfFilter = null; + } + + resetLinkProperties(); + } + + private void enqueueJumpToStoppingState() { + deferMessage(obtainMessage(CMD_JUMP_RUNNING_TO_STOPPING)); + } + + private ConnectivityPacketTracker createPacketTracker() { + try { + return new ConnectivityPacketTracker( + getHandler(), mInterfaceParams, mConnectivityPacketLog); + } catch (IllegalArgumentException e) { + return null; + } + } + + private void ensureDhcpAction() { + if (!mDhcpActionInFlight) { + mCallback.onPreDhcpAction(); + mDhcpActionInFlight = true; + final long alarmTime = SystemClock.elapsedRealtime() + + mConfiguration.mRequestedPreDhcpActionMs; + mDhcpActionTimeoutAlarm.schedule(alarmTime); + } + } + + private void stopDhcpAction() { + mDhcpActionTimeoutAlarm.cancel(); + if (mDhcpActionInFlight) { + mCallback.onPostDhcpAction(); + mDhcpActionInFlight = false; + } + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_JUMP_RUNNING_TO_STOPPING: + case CMD_STOP: + transitionTo(mStoppingState); + break; + + case CMD_START: + logError("ALERT: START received in StartedState. Please fix caller."); + break; + + case CMD_CONFIRM: + // TODO: Possibly introduce a second type of confirmation + // that both probes (a) on-link neighbors and (b) does + // a DHCPv4 RENEW. We used to do this on Wi-Fi framework + // roams. + if (mIpReachabilityMonitor != null) { + mIpReachabilityMonitor.probeAll(); + } + break; + + case EVENT_PRE_DHCP_ACTION_COMPLETE: + // It's possible to reach here if, for example, someone + // calls completedPreDhcpAction() after provisioning with + // a static IP configuration. + if (mDhcpClient != null) { + mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE); + } + break; + + case EVENT_NETLINK_LINKPROPERTIES_CHANGED: + if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) { + transitionTo(mStoppingState); + } + break; + + case CMD_UPDATE_TCP_BUFFER_SIZES: + mTcpBufferSizes = (String) msg.obj; + // This cannot possibly change provisioning state. + handleLinkPropertiesUpdate(SEND_CALLBACKS); + break; + + case CMD_UPDATE_HTTP_PROXY: + mHttpProxy = (ProxyInfo) msg.obj; + // This cannot possibly change provisioning state. + handleLinkPropertiesUpdate(SEND_CALLBACKS); + break; + + case CMD_SET_MULTICAST_FILTER: { + mMulticastFiltering = (boolean) msg.obj; + if (mApfFilter != null) { + mApfFilter.setMulticastFilter(mMulticastFiltering); + } else { + mCallback.setFallbackMulticastFilter(mMulticastFiltering); + } + break; + } + + case EVENT_READ_PACKET_FILTER_COMPLETE: { + if (mApfFilter != null) { + mApfFilter.setDataSnapshot((byte[]) msg.obj); + } + mApfDataSnapshotComplete.open(); + break; + } + + case EVENT_DHCPACTION_TIMEOUT: + stopDhcpAction(); + break; + + case DhcpClient.CMD_PRE_DHCP_ACTION: + if (mConfiguration.mRequestedPreDhcpActionMs > 0) { + ensureDhcpAction(); + } else { + sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE); + } + break; + + case DhcpClient.CMD_CLEAR_LINKADDRESS: + mInterfaceCtrl.clearIPv4Address(); + break; + + case DhcpClient.CMD_CONFIGURE_LINKADDRESS: { + final LinkAddress ipAddress = (LinkAddress) msg.obj; + if (mInterfaceCtrl.setIPv4Address(ipAddress)) { + mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED); + } else { + logError("Failed to set IPv4 address."); + dispatchCallback(PROV_CHANGE_LOST_PROVISIONING, + new LinkProperties(mLinkProperties)); + transitionTo(mStoppingState); + } + break; + } + + // This message is only received when: + // + // a) initial address acquisition succeeds, + // b) renew succeeds or is NAK'd, + // c) rebind succeeds or is NAK'd, or + // c) the lease expires, + // + // but never when initial address acquisition fails. The latter + // condition is now governed by the provisioning timeout. + case DhcpClient.CMD_POST_DHCP_ACTION: + stopDhcpAction(); + + switch (msg.arg1) { + case DhcpClient.DHCP_SUCCESS: + handleIPv4Success((DhcpResults) msg.obj); + break; + case DhcpClient.DHCP_FAILURE: + handleIPv4Failure(); + break; + default: + logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1); + } + break; + + case DhcpClient.CMD_ON_QUIT: + // DHCPv4 quit early for some reason. + logError("Unexpected CMD_ON_QUIT."); + mDhcpClient = null; + break; + + default: + return NOT_HANDLED; + } + + mMsgStateLogger.handled(this, getCurrentState()); + return HANDLED; + } + } + + private static class MessageHandlingLogger { + public String processedInState; + public String receivedInState; + + public void reset() { + processedInState = null; + receivedInState = null; + } + + public void handled(State processedIn, IState receivedIn) { + processedInState = processedIn.getClass().getSimpleName(); + receivedInState = receivedIn.getName(); + } + + public String toString() { + return String.format("rcvd_in=%s, proc_in=%s", + receivedInState, processedInState); + } + } + + private static void setNeighborParameters( + INetd netd, String ifName, int numSolicits, int interSolicitIntervalMs) + throws RemoteException, IllegalArgumentException { + Preconditions.checkNotNull(netd); + Preconditions.checkArgument(!TextUtils.isEmpty(ifName)); + Preconditions.checkArgument(numSolicits > 0); + Preconditions.checkArgument(interSolicitIntervalMs > 0); + + for (int family : new Integer[]{INetd.IPV4, INetd.IPV6}) { + netd.setProcSysNet(family, INetd.NEIGH, ifName, "retrans_time_ms", + Integer.toString(interSolicitIntervalMs)); + netd.setProcSysNet(family, INetd.NEIGH, ifName, "ucast_solicit", + Integer.toString(numSolicits)); + } + } + + // TODO: extract out into CollectionUtils. + static <T> boolean any(Iterable<T> coll, Predicate<T> fn) { + for (T t : coll) { + if (fn.test(t)) { + return true; + } + } + return false; + } + + static <T> boolean all(Iterable<T> coll, Predicate<T> fn) { + return !any(coll, not(fn)); + } + + static <T> Predicate<T> not(Predicate<T> fn) { + return (t) -> !fn.test(t); + } + + static <T> String join(String delimiter, Collection<T> coll) { + return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter)); + } + + static <T> T find(Iterable<T> coll, Predicate<T> fn) { + for (T t: coll) { + if (fn.test(t)) { + return t; + } + } + return null; + } + + static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) { + return coll.stream().filter(fn).collect(Collectors.toList()); + } +} diff --git a/services/net/java/android/net/ip/IpNeighborMonitor.java b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java index eb993a4243a9..eb993a4243a9 100644 --- a/services/net/java/android/net/ip/IpNeighborMonitor.java +++ b/packages/NetworkStack/src/android/net/ip/IpNeighborMonitor.java diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java index 761db6822fb4..761db6822fb4 100644 --- a/services/net/java/android/net/ip/IpReachabilityMonitor.java +++ b/packages/NetworkStack/src/android/net/ip/IpReachabilityMonitor.java diff --git a/services/net/java/android/net/util/ConnectivityPacketSummary.java b/packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java index ec833b07e806..08c3f60eff3d 100644 --- a/services/net/java/android/net/util/ConnectivityPacketSummary.java +++ b/packages/NetworkStack/src/android/net/util/ConnectivityPacketSummary.java @@ -16,47 +16,46 @@ package android.net.util; -import static android.net.util.NetworkConstants.ARP_HWTYPE_ETHER; -import static android.net.util.NetworkConstants.ARP_PAYLOAD_LEN; -import static android.net.util.NetworkConstants.ARP_REPLY; -import static android.net.util.NetworkConstants.ARP_REQUEST; -import static android.net.util.NetworkConstants.DHCP4_CLIENT_PORT; -import static android.net.util.NetworkConstants.ETHER_ADDR_LEN; -import static android.net.util.NetworkConstants.ETHER_DST_ADDR_OFFSET; -import static android.net.util.NetworkConstants.ETHER_HEADER_LEN; -import static android.net.util.NetworkConstants.ETHER_SRC_ADDR_OFFSET; -import static android.net.util.NetworkConstants.ETHER_TYPE_ARP; -import static android.net.util.NetworkConstants.ETHER_TYPE_IPV4; -import static android.net.util.NetworkConstants.ETHER_TYPE_IPV6; -import static android.net.util.NetworkConstants.ETHER_TYPE_OFFSET; -import static android.net.util.NetworkConstants.ICMPV6_HEADER_MIN_LEN; -import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR; -import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_MIN_LENGTH; -import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_MTU; -import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_SLLA; -import static android.net.util.NetworkConstants.ICMPV6_ND_OPTION_TLLA; -import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT; -import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_SOLICITATION; -import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT; -import static android.net.util.NetworkConstants.ICMPV6_ROUTER_SOLICITATION; -import static android.net.util.NetworkConstants.IPV4_ADDR_LEN; -import static android.net.util.NetworkConstants.IPV4_DST_ADDR_OFFSET; -import static android.net.util.NetworkConstants.IPV4_FLAGS_OFFSET; -import static android.net.util.NetworkConstants.IPV4_FRAGMENT_MASK; -import static android.net.util.NetworkConstants.IPV4_HEADER_MIN_LEN; -import static android.net.util.NetworkConstants.IPV4_IHL_MASK; -import static android.net.util.NetworkConstants.IPV4_PROTOCOL_OFFSET; -import static android.net.util.NetworkConstants.IPV4_SRC_ADDR_OFFSET; -import static android.net.util.NetworkConstants.IPV6_ADDR_LEN; -import static android.net.util.NetworkConstants.IPV6_HEADER_LEN; -import static android.net.util.NetworkConstants.IPV6_PROTOCOL_OFFSET; -import static android.net.util.NetworkConstants.IPV6_SRC_ADDR_OFFSET; -import static android.net.util.NetworkConstants.UDP_HEADER_LEN; -import static android.net.util.NetworkConstants.asString; -import static android.net.util.NetworkConstants.asUint; import static android.system.OsConstants.IPPROTO_ICMPV6; import static android.system.OsConstants.IPPROTO_UDP; +import static com.android.server.util.NetworkStackConstants.ARP_HWTYPE_ETHER; +import static com.android.server.util.NetworkStackConstants.ARP_PAYLOAD_LEN; +import static com.android.server.util.NetworkStackConstants.ARP_REPLY; +import static com.android.server.util.NetworkStackConstants.ARP_REQUEST; +import static com.android.server.util.NetworkStackConstants.DHCP4_CLIENT_PORT; +import static com.android.server.util.NetworkStackConstants.ETHER_ADDR_LEN; +import static com.android.server.util.NetworkStackConstants.ETHER_DST_ADDR_OFFSET; +import static com.android.server.util.NetworkStackConstants.ETHER_HEADER_LEN; +import static com.android.server.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET; +import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_ARP; +import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_IPV4; +import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_IPV6; +import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_OFFSET; +import static com.android.server.util.NetworkStackConstants.ICMPV6_HEADER_MIN_LEN; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_MIN_LENGTH; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_MTU; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA; +import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT; +import static com.android.server.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION; +import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_LEN; +import static com.android.server.util.NetworkStackConstants.IPV4_DST_ADDR_OFFSET; +import static com.android.server.util.NetworkStackConstants.IPV4_FLAGS_OFFSET; +import static com.android.server.util.NetworkStackConstants.IPV4_FRAGMENT_MASK; +import static com.android.server.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN; +import static com.android.server.util.NetworkStackConstants.IPV4_IHL_MASK; +import static com.android.server.util.NetworkStackConstants.IPV4_PROTOCOL_OFFSET; +import static com.android.server.util.NetworkStackConstants.IPV4_SRC_ADDR_OFFSET; +import static com.android.server.util.NetworkStackConstants.IPV6_ADDR_LEN; +import static com.android.server.util.NetworkStackConstants.IPV6_HEADER_LEN; +import static com.android.server.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET; +import static com.android.server.util.NetworkStackConstants.IPV6_SRC_ADDR_OFFSET; +import static com.android.server.util.NetworkStackConstants.UDP_HEADER_LEN; + import android.net.MacAddress; import android.net.dhcp.DhcpPacket; @@ -412,4 +411,25 @@ public class ConnectivityPacketSummary { final String MAC48_FORMAT = "%02x:%02x:%02x:%02x:%02x:%02x"; return String.format(MAC48_FORMAT, printableBytes); } + + /** + * Convenience method to convert an int to a String. + */ + public static String asString(int i) { + return Integer.toString(i); + } + + /** + * Convenience method to read a byte as an unsigned int. + */ + public static int asUint(byte b) { + return (b & 0xff); + } + + /** + * Convenience method to read a short as an unsigned int. + */ + public static int asUint(short s) { + return (s & 0xffff); + } } diff --git a/services/net/java/android/net/util/FdEventsReader.java b/packages/NetworkStack/src/android/net/util/FdEventsReader.java index 8bbf449f6374..8bbf449f6374 100644 --- a/services/net/java/android/net/util/FdEventsReader.java +++ b/packages/NetworkStack/src/android/net/util/FdEventsReader.java diff --git a/services/net/java/android/net/util/PacketReader.java b/packages/NetworkStack/src/android/net/util/PacketReader.java index 4aec6b6753a6..4aec6b6753a6 100644 --- a/services/net/java/android/net/util/PacketReader.java +++ b/packages/NetworkStack/src/android/net/util/PacketReader.java diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java index cca71e759bdd..4080ddf9e73f 100644 --- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java +++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java @@ -39,6 +39,8 @@ import android.net.dhcp.DhcpServer; import android.net.dhcp.DhcpServingParams; import android.net.dhcp.DhcpServingParamsParcel; import android.net.dhcp.IDhcpServerCallbacks; +import android.net.ip.IIpClientCallbacks; +import android.net.ip.IpClient; import android.net.shared.PrivateDnsConfig; import android.net.util.SharedLog; import android.os.IBinder; @@ -50,7 +52,11 @@ import com.android.server.connectivity.NetworkMonitor; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.ref.WeakReference; import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; /** * Android service used to start the network stack when bound to via an intent. @@ -80,6 +86,8 @@ public class NetworkStackService extends Service { private static final int NUM_VALIDATION_LOG_LINES = 20; private final Context mContext; private final ConnectivityManager mCm; + @GuardedBy("mIpClients") + private final ArrayList<WeakReference<IpClient>> mIpClients = new ArrayList<>(); private static final int MAX_VALIDATION_LOGS = 10; @GuardedBy("mValidationLogs") @@ -138,6 +146,24 @@ public class NetworkStackService extends Service { } @Override + public void makeIpClient(String ifName, IIpClientCallbacks cb) throws RemoteException { + final IpClient ipClient = new IpClient(mContext, ifName, cb); + + synchronized (mIpClients) { + final Iterator<WeakReference<IpClient>> it = mIpClients.iterator(); + while (it.hasNext()) { + final IpClient ipc = it.next().get(); + if (ipc == null) { + it.remove(); + } + } + mIpClients.add(new WeakReference<>(ipClient)); + } + + cb.onIpClientCreated(ipClient.makeConnector()); + } + + @Override protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout, @Nullable String[] args) { checkDumpPermission(); @@ -145,6 +171,33 @@ public class NetworkStackService extends Service { pw.println("NetworkStack logs:"); mLog.dump(fd, pw, args); + // Dump full IpClient logs for non-GCed clients + pw.println(); + pw.println("Recently active IpClient logs:"); + final ArrayList<IpClient> ipClients = new ArrayList<>(); + final HashSet<String> dumpedIpClientIfaces = new HashSet<>(); + synchronized (mIpClients) { + for (WeakReference<IpClient> ipcRef : mIpClients) { + final IpClient ipc = ipcRef.get(); + if (ipc != null) { + ipClients.add(ipc); + } + } + } + + for (IpClient ipc : ipClients) { + pw.println(ipc.getName()); + pw.increaseIndent(); + ipc.dump(fd, pw, args); + pw.decreaseIndent(); + dumpedIpClientIfaces.add(ipc.getInterfaceName()); + } + + // State machine and connectivity metrics logs are kept for GCed IpClients + pw.println(); + pw.println("Other IpClient logs:"); + IpClient.dumpAllLogs(fout, dumpedIpClientIfaces); + pw.println(); pw.println("Validation logs (most recent first):"); synchronized (mValidationLogs) { diff --git a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java index bb5900c53e52..eedaf30c055f 100644 --- a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java +++ b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java @@ -32,12 +32,103 @@ public final class NetworkStackConstants { public static final int IPV4_MAX_MTU = 65_535; /** + * Ethernet constants. + * + * See also: + * - https://tools.ietf.org/html/rfc894 + * - https://tools.ietf.org/html/rfc2464 + * - https://tools.ietf.org/html/rfc7042 + * - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml + * - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml + */ + public static final int ETHER_DST_ADDR_OFFSET = 0; + public static final int ETHER_SRC_ADDR_OFFSET = 6; + public static final int ETHER_ADDR_LEN = 6; + public static final int ETHER_TYPE_OFFSET = 12; + public static final int ETHER_TYPE_LENGTH = 2; + public static final int ETHER_TYPE_ARP = 0x0806; + public static final int ETHER_TYPE_IPV4 = 0x0800; + public static final int ETHER_TYPE_IPV6 = 0x86dd; + public static final int ETHER_HEADER_LEN = 14; + + /** + * ARP constants. + * + * See also: + * - https://tools.ietf.org/html/rfc826 + * - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml + */ + public static final int ARP_PAYLOAD_LEN = 28; // For Ethernet+IPv4. + public static final int ARP_REQUEST = 1; + public static final int ARP_REPLY = 2; + public static final int ARP_HWTYPE_RESERVED_LO = 0; + public static final int ARP_HWTYPE_ETHER = 1; + public static final int ARP_HWTYPE_RESERVED_HI = 0xffff; + + /** + * IPv4 constants. + * + * See also: + * - https://tools.ietf.org/html/rfc791 + */ + public static final int IPV4_HEADER_MIN_LEN = 20; + public static final int IPV4_IHL_MASK = 0xf; + public static final int IPV4_FLAGS_OFFSET = 6; + public static final int IPV4_FRAGMENT_MASK = 0x1fff; + public static final int IPV4_PROTOCOL_OFFSET = 9; + public static final int IPV4_SRC_ADDR_OFFSET = 12; + public static final int IPV4_DST_ADDR_OFFSET = 16; + public static final int IPV4_ADDR_LEN = 4; + + /** + * IPv6 constants. + * + * See also: + * - https://tools.ietf.org/html/rfc2460 + */ + public static final int IPV6_ADDR_LEN = 16; + public static final int IPV6_HEADER_LEN = 40; + public static final int IPV6_PROTOCOL_OFFSET = 6; + public static final int IPV6_SRC_ADDR_OFFSET = 8; + public static final int IPV6_DST_ADDR_OFFSET = 24; + + /** + * ICMPv6 constants. + * + * See also: + * - https://tools.ietf.org/html/rfc4443 + * - https://tools.ietf.org/html/rfc4861 + */ + public static final int ICMPV6_HEADER_MIN_LEN = 4; + public static final int ICMPV6_ECHO_REPLY_TYPE = 129; + public static final int ICMPV6_ECHO_REQUEST_TYPE = 128; + public static final int ICMPV6_ROUTER_SOLICITATION = 133; + public static final int ICMPV6_ROUTER_ADVERTISEMENT = 134; + public static final int ICMPV6_NEIGHBOR_SOLICITATION = 135; + public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136; + public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8; + public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8; + public static final int ICMPV6_ND_OPTION_SLLA = 1; + public static final int ICMPV6_ND_OPTION_TLLA = 2; + public static final int ICMPV6_ND_OPTION_MTU = 5; + + /** + * UDP constants. + * + * See also: + * - https://tools.ietf.org/html/rfc768 + */ + public static final int UDP_HEADER_LEN = 8; + + + /** * DHCP constants. * * See also: * - https://tools.ietf.org/html/rfc2131 */ public static final int INFINITE_LEASE = 0xffffffff; + public static final int DHCP4_CLIENT_PORT = 68; private NetworkStackConstants() { throw new UnsupportedOperationException("This class is not to be instantiated"); diff --git a/packages/NetworkStack/tests/Android.bp b/packages/NetworkStack/tests/Android.bp index bd7ff2a75703..45fa2dc2f383 100644 --- a/packages/NetworkStack/tests/Android.bp +++ b/packages/NetworkStack/tests/Android.bp @@ -16,9 +16,12 @@ android_test { name: "NetworkStackTests", + certificate: "platform", srcs: ["src/**/*.java"], + resource_dirs: ["res"], static_libs: [ "android-support-test", + "frameworks-base-testutils", "mockito-target-extended-minus-junit4", "NetworkStackLib", "testables", @@ -26,10 +29,70 @@ android_test { libs: [ "android.test.runner", "android.test.base", + "android.test.mock", ], jni_libs: [ // For mockito extended "libdexmakerjvmtiagent", "libstaticjvmtiagent", - ] -}
\ No newline at end of file + // For ApfTest + "libartbase", + "libbacktrace", + "libbase", + "libbinder", + "libbinderthreadstate", + "libc++", + "libcrypto", + "libcutils", + "libdexfile", + "libhidl-gen-utils", + "libhidlbase", + "libhidltransport", + "libhwbinder", + "liblog", + "liblzma", + "libnativehelper", + "libnetworkstacktestsjni", + "libpackagelistparser", + "libpcre2", + "libprocessgroup", + "libselinux", + "libui", + "libutils", + "libvintf", + "libvndksupport", + "libtinyxml2", + "libunwindstack", + "libutilscallstack", + "libziparchive", + "libz", + "netd_aidl_interface-cpp", + ], +} + +cc_library_shared { + name: "libnetworkstacktestsjni", + srcs: [ + "jni/**/*.cpp" + ], + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + ], + include_dirs: [ + "hardware/google/apf", + ], + shared_libs: [ + "libbinder", + "liblog", + "libcutils", + "libnativehelper", + "netd_aidl_interface-cpp", + ], + static_libs: [ + "libapf", + "libpcap", + ], + +} diff --git a/packages/NetworkStack/tests/AndroidManifest.xml b/packages/NetworkStack/tests/AndroidManifest.xml index 8b8474f57e28..9cb2c21cc399 100644 --- a/packages/NetworkStack/tests/AndroidManifest.xml +++ b/packages/NetworkStack/tests/AndroidManifest.xml @@ -15,6 +15,35 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.server.networkstack.tests"> + + <uses-permission android:name="android.permission.READ_LOGS" /> + <uses-permission android:name="android.permission.WRITE_SETTINGS" /> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + <uses-permission android:name="android.permission.READ_PHONE_STATE" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.BROADCAST_STICKY" /> + <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" /> + <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" /> + <uses-permission android:name="android.permission.WAKE_LOCK" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> + <uses-permission android:name="android.permission.REAL_GET_TASKS" /> + <uses-permission android:name="android.permission.GET_DETAILED_TASKS" /> + <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" /> + <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" /> + <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" /> + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> + <uses-permission android:name="android.permission.MANAGE_USERS" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" /> + <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> + <uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" /> + <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" /> + <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" /> + <uses-permission android:name="android.permission.INSTALL_PACKAGES" /> + <uses-permission android:name="android.permission.NETWORK_STACK" /> + <application android:debuggable="true"> <uses-library android:name="android.test.runner" /> </application> diff --git a/tests/net/jni/apf_jni.cpp b/packages/NetworkStack/tests/jni/apf_jni.cpp index 4222adf9e06b..4222adf9e06b 100644 --- a/tests/net/jni/apf_jni.cpp +++ b/packages/NetworkStack/tests/jni/apf_jni.cpp diff --git a/tests/net/res/raw/apf.pcap b/packages/NetworkStack/tests/res/raw/apf.pcap Binary files differindex 963165f19f73..963165f19f73 100644 --- a/tests/net/res/raw/apf.pcap +++ b/packages/NetworkStack/tests/res/raw/apf.pcap diff --git a/tests/net/res/raw/apfPcap.pcap b/packages/NetworkStack/tests/res/raw/apfPcap.pcap Binary files differindex 6f69c4add0f8..6f69c4add0f8 100644 --- a/tests/net/res/raw/apfPcap.pcap +++ b/packages/NetworkStack/tests/res/raw/apfPcap.pcap diff --git a/tests/net/java/android/net/apf/ApfTest.java b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java index 3c3e7ce3b12a..f76e41217c2a 100644 --- a/tests/net/java/android/net/apf/ApfTest.java +++ b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java @@ -16,8 +16,6 @@ package android.net.apf; -import static android.net.util.NetworkConstants.ICMPV6_ECHO_REQUEST_TYPE; -import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT; import static android.system.OsConstants.AF_UNIX; import static android.system.OsConstants.ARPHRD_ETHER; import static android.system.OsConstants.ETH_P_ARP; @@ -28,12 +26,14 @@ import static android.system.OsConstants.IPPROTO_UDP; import static android.system.OsConstants.SOCK_STREAM; import static com.android.internal.util.BitUtils.bytesToBEInt; +import static com.android.server.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import android.content.Context; @@ -42,10 +42,14 @@ import android.net.LinkProperties; import android.net.apf.ApfFilter.ApfConfiguration; import android.net.apf.ApfGenerator.IllegalInstructionException; import android.net.apf.ApfGenerator.Register; +import android.net.ip.IIpClientCallbacks; +import android.net.ip.IpClient; +import android.net.ip.IpClient.IpClientCallbacksWrapper; import android.net.ip.IpClientCallbacks; import android.net.metrics.IpConnectivityLog; import android.net.metrics.RaEvent; import android.net.util.InterfaceParams; +import android.net.util.SharedLog; import android.os.ConditionVariable; import android.os.Parcelable; import android.os.SystemClock; @@ -57,8 +61,9 @@ import android.system.Os; import android.text.format.DateUtils; import android.util.Log; -import com.android.frameworks.tests.net.R; import com.android.internal.util.HexDump; +import com.android.server.networkstack.tests.R; +import com.android.server.util.NetworkStackConstants; import libcore.io.IoUtils; import libcore.io.Streams; @@ -100,7 +105,7 @@ public class ApfTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); // Load up native shared library containing APF interpreter exposed via JNI. - System.loadLibrary("frameworksnettestsjni"); + System.loadLibrary("networkstacktestsjni"); } private static final String TAG = "ApfTest"; @@ -915,10 +920,14 @@ public class ApfTest { HexDump.toHexString(data, false), result); } - private class MockIpClientCallback extends IpClientCallbacks { + private class MockIpClientCallback extends IpClientCallbacksWrapper { private final ConditionVariable mGotApfProgram = new ConditionVariable(); private byte[] mLastApfProgram; + MockIpClientCallback() { + super(mock(IIpClientCallbacks.class), mock(SharedLog.class)); + } + @Override public void installPacketFilter(byte[] filter) { mLastApfProgram = filter; @@ -946,7 +955,7 @@ public class ApfTest { private final long mFixedTimeMs = SystemClock.elapsedRealtime(); public TestApfFilter(Context context, ApfConfiguration config, - IpClientCallbacks ipClientCallback, IpConnectivityLog log) throws Exception { + IpClientCallbacksWrapper ipClientCallback, IpConnectivityLog log) throws Exception { super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, log); } @@ -1075,8 +1084,8 @@ public class ApfTest { private static final byte[] IPV4_ANY_HOST_ADDR = {0, 0, 0, 0}; // Helper to initialize a default apfFilter. - private ApfFilter setupApfFilter(IpClientCallbacks ipClientCallback, ApfConfiguration config) - throws Exception { + private ApfFilter setupApfFilter( + IpClientCallbacksWrapper ipClientCallback, ApfConfiguration config) throws Exception { LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19); LinkProperties lp = new LinkProperties(); lp.addLinkAddress(link); @@ -1294,7 +1303,7 @@ public class ApfTest { // However, we should still let through all other ICMPv6 types. ByteBuffer raPacket = ByteBuffer.wrap(packet.array().clone()); - raPacket.put(ICMP6_TYPE_OFFSET, (byte)ICMPV6_ROUTER_ADVERTISEMENT); + raPacket.put(ICMP6_TYPE_OFFSET, (byte) NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT); assertPass(ipClientCallback.getApfProgram(), raPacket.array()); // Now wake up from doze mode to ensure that we no longer drop the packets. diff --git a/tests/net/java/android/net/apf/Bpf2Apf.java b/packages/NetworkStack/tests/src/android/net/apf/Bpf2Apf.java index 5d57cde22fb1..5d57cde22fb1 100644 --- a/tests/net/java/android/net/apf/Bpf2Apf.java +++ b/packages/NetworkStack/tests/src/android/net/apf/Bpf2Apf.java diff --git a/tests/net/java/android/net/dhcp/DhcpPacketTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java index a592809618e6..a592809618e6 100644 --- a/tests/net/java/android/net/dhcp/DhcpPacketTest.java +++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java diff --git a/tests/net/java/android/net/ip/IpClientTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java index 5110ce1830b3..f21809fdbc1c 100644 --- a/tests/net/java/android/net/ip/IpClientTest.java +++ b/packages/NetworkStack/tests/src/android/net/ip/IpClientTest.java @@ -16,10 +16,13 @@ package android.net.ip; +import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.eq; @@ -87,7 +90,7 @@ public class IpClientTest { @Mock private INetworkManagementService mNMService; @Mock private INetd mNetd; @Mock private Resources mResources; - @Mock private IpClientCallbacks mCb; + @Mock private IIpClientCallbacks mCb; @Mock private AlarmManager mAlarm; @Mock private IpClient.Dependencies mDependecies; private MockContentResolver mContentResolver; @@ -209,7 +212,8 @@ public class IpClientTest { verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false); verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface); verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)) - .onLinkPropertiesChange(eq(makeEmptyLinkProperties(iface))); + .onLinkPropertiesChange(argThat( + lp -> fromStableParcelable(lp).equals(makeEmptyLinkProperties(iface)))); } @Test @@ -254,13 +258,15 @@ public class IpClientTest { mObserver.addressUpdated(iface, new LinkAddress(addresses[lastAddr])); LinkProperties want = linkproperties(links(addresses), routes(prefixes)); want.setInterfaceName(iface); - verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(eq(want)); + verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(argThat( + lp -> fromStableParcelable(lp).equals(want))); ipc.shutdown(); verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false); verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface); verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)) - .onLinkPropertiesChange(eq(makeEmptyLinkProperties(iface))); + .onLinkPropertiesChange(argThat( + lp -> fromStableParcelable(lp).equals(makeEmptyLinkProperties(iface)))); } @Test @@ -492,11 +498,11 @@ public class IpClientTest { List<String> list3 = Arrays.asList("bar", "baz"); List<String> list4 = Arrays.asList("foo", "bar", "baz"); - assertTrue(IpClient.all(list1, (x) -> false)); - assertFalse(IpClient.all(list2, (x) -> false)); - assertTrue(IpClient.all(list3, (x) -> true)); - assertTrue(IpClient.all(list2, (x) -> x.charAt(0) == 'f')); - assertFalse(IpClient.all(list4, (x) -> x.charAt(0) == 'f')); + assertTrue(InitialConfiguration.all(list1, (x) -> false)); + assertFalse(InitialConfiguration.all(list2, (x) -> false)); + assertTrue(InitialConfiguration.all(list3, (x) -> true)); + assertTrue(InitialConfiguration.all(list2, (x) -> x.charAt(0) == 'f')); + assertFalse(InitialConfiguration.all(list4, (x) -> x.charAt(0) == 'f')); } @Test @@ -506,11 +512,11 @@ public class IpClientTest { List<String> list3 = Arrays.asList("bar", "baz"); List<String> list4 = Arrays.asList("foo", "bar", "baz"); - assertFalse(IpClient.any(list1, (x) -> true)); - assertTrue(IpClient.any(list2, (x) -> true)); - assertTrue(IpClient.any(list2, (x) -> x.charAt(0) == 'f')); - assertFalse(IpClient.any(list3, (x) -> x.charAt(0) == 'f')); - assertTrue(IpClient.any(list4, (x) -> x.charAt(0) == 'f')); + assertFalse(InitialConfiguration.any(list1, (x) -> true)); + assertTrue(InitialConfiguration.any(list2, (x) -> true)); + assertTrue(InitialConfiguration.any(list2, (x) -> x.charAt(0) == 'f')); + assertFalse(InitialConfiguration.any(list3, (x) -> x.charAt(0) == 'f')); + assertTrue(InitialConfiguration.any(list4, (x) -> x.charAt(0) == 'f')); } @Test diff --git a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java b/packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java index e3b5ddf6f4cf..e3b5ddf6f4cf 100644 --- a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java +++ b/packages/NetworkStack/tests/src/android/net/ip/IpReachabilityMonitorTest.java diff --git a/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java b/packages/NetworkStack/tests/src/android/net/util/ConnectivityPacketSummaryTest.java index dfaf52a953c7..dfaf52a953c7 100644 --- a/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java +++ b/packages/NetworkStack/tests/src/android/net/util/ConnectivityPacketSummaryTest.java diff --git a/tests/net/java/android/net/util/PacketReaderTest.java b/packages/NetworkStack/tests/src/android/net/util/PacketReaderTest.java index dced7435ee74..dced7435ee74 100644 --- a/tests/net/java/android/net/util/PacketReaderTest.java +++ b/packages/NetworkStack/tests/src/android/net/util/PacketReaderTest.java diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 38594efff349..aa74600cc527 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -100,7 +100,7 @@ import android.net.netlink.InetDiagMessage; import android.net.shared.NetworkMonitorUtils; import android.net.shared.PrivateDnsConfig; import android.net.util.MultinetworkPolicyTracker; -import android.net.util.NetdService; +import android.net.shared.NetdService; import android.os.Binder; import android.os.Build; import android.os.Bundle; diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index 126bf6556538..371276fbd2ae 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -44,7 +44,7 @@ import android.net.LinkAddress; import android.net.Network; import android.net.NetworkUtils; import android.net.TrafficStats; -import android.net.util.NetdService; +import android.net.shared.NetdService; import android.os.Binder; import android.os.IBinder; import android.os.ParcelFileDescriptor; diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index abc6ec7e5a3b..7f61dd120c7f 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -62,7 +62,7 @@ import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.TetherStatsParcel; import android.net.UidRange; -import android.net.util.NetdService; +import android.net.shared.NetdService; import android.os.BatteryStats; import android.os.Binder; import android.os.Handler; diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java index ac745985417e..79b56c6027f8 100644 --- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java +++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java @@ -20,7 +20,6 @@ import android.content.Context; import android.net.ConnectivityMetricsEvent; import android.net.IIpConnectivityMetrics; import android.net.INetdEventCallback; -import android.net.ip.IpClient; import android.net.metrics.ApfProgramEvent; import android.net.metrics.IpConnectivityLog; import android.os.Binder; @@ -270,8 +269,6 @@ final public class IpConnectivityMetrics extends SystemService { // Dump the rolling buffer of metrics event and pretty print events using a human readable // format. Also print network dns/connect statistics and default network event time series. static final String CMD_LIST = "list"; - // Dump all IpClient logs ("ipclient"). - static final String CMD_IPCLIENT = IpClient.DUMP_ARG; // By default any other argument will fall into the default case which is the equivalent // of calling both the "list" and "ipclient" commands. This includes most notably bug // reports collected by dumpsys.cpp with the "-a" argument. @@ -295,20 +292,9 @@ final public class IpConnectivityMetrics extends SystemService { case CMD_PROTO: cmdListAsProto(pw); return; - case CMD_IPCLIENT: { - final String[] ipclientArgs = ((args != null) && (args.length > 1)) - ? Arrays.copyOfRange(args, 1, args.length) - : null; - IpClient.dumpAllLogs(pw, ipclientArgs); - return; - } case CMD_LIST: - cmdList(pw); - return; default: cmdList(pw); - pw.println(""); - IpClient.dumpAllLogs(pw, null); return; } } diff --git a/services/net/Android.bp b/services/net/Android.bp index 3b4d6a75591f..30c7de57b73e 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -3,18 +3,18 @@ java_library_static { 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", - ] -} - filegroup { name: "services-networkstack-shared-srcs", srcs: [ - "java/android/net/util/FdEventsReader.java", // TODO: move to NetworkStack with IpClient + "java/android/net/ip/InterfaceController.java", // TODO: move to NetworkStack with tethering + "java/android/net/util/InterfaceParams.java", // TODO: move to NetworkStack with IpServer "java/android/net/shared/*.java", + ], +} + +java_library { + name: "services-netlink-lib", + srcs: [ + "java/android/net/netlink/*.java", ] } diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java index 04ac9a301813..cddb91f65d0f 100644 --- a/services/net/java/android/net/dhcp/DhcpClient.java +++ b/services/net/java/android/net/dhcp/DhcpClient.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,1037 +16,14 @@ package android.net.dhcp; -import static android.net.dhcp.DhcpPacket.DHCP_BROADCAST_ADDRESS; -import static android.net.dhcp.DhcpPacket.DHCP_DNS_SERVER; -import static android.net.dhcp.DhcpPacket.DHCP_DOMAIN_NAME; -import static android.net.dhcp.DhcpPacket.DHCP_LEASE_TIME; -import static android.net.dhcp.DhcpPacket.DHCP_MTU; -import static android.net.dhcp.DhcpPacket.DHCP_REBINDING_TIME; -import static android.net.dhcp.DhcpPacket.DHCP_RENEWAL_TIME; -import static android.net.dhcp.DhcpPacket.DHCP_ROUTER; -import static android.net.dhcp.DhcpPacket.DHCP_SUBNET_MASK; -import static android.net.dhcp.DhcpPacket.DHCP_VENDOR_INFO; -import static android.net.dhcp.DhcpPacket.INADDR_ANY; -import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST; -import static android.net.util.SocketUtils.makePacketSocketAddress; -import static android.system.OsConstants.AF_INET; -import static android.system.OsConstants.AF_PACKET; -import static android.system.OsConstants.ETH_P_IP; -import static android.system.OsConstants.IPPROTO_UDP; -import static android.system.OsConstants.SOCK_DGRAM; -import static android.system.OsConstants.SOCK_RAW; -import static android.system.OsConstants.SOL_SOCKET; -import static android.system.OsConstants.SO_BROADCAST; -import static android.system.OsConstants.SO_RCVBUF; -import static android.system.OsConstants.SO_REUSEADDR; - -import android.content.Context; -import android.net.DhcpResults; -import android.net.NetworkUtils; -import android.net.TrafficStats; -import android.net.ip.IpClient; -import android.net.metrics.DhcpClientEvent; -import android.net.metrics.DhcpErrorEvent; -import android.net.metrics.IpConnectivityLog; -import android.net.util.InterfaceParams; -import android.net.util.SocketUtils; -import android.os.Message; -import android.os.SystemClock; -import android.system.ErrnoException; -import android.system.Os; -import android.util.EventLog; -import android.util.Log; -import android.util.SparseArray; - -import com.android.internal.util.HexDump; -import com.android.internal.util.MessageUtils; -import com.android.internal.util.State; -import com.android.internal.util.StateMachine; -import com.android.internal.util.WakeupMessage; - -import libcore.io.IoBridge; - -import java.io.FileDescriptor; -import java.io.IOException; -import java.net.Inet4Address; -import java.net.SocketAddress; -import java.net.SocketException; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.Random; - /** - * A DHCPv4 client. - * - * Written to behave similarly to the DhcpStateMachine + dhcpcd 5.5.6 combination used in Android - * 5.1 and below, as configured on Nexus 6. The interface is the same as DhcpStateMachine. - * - * TODO: - * - * - Exponential backoff when receiving NAKs (not specified by the RFC, but current behaviour). - * - Support persisting lease state and support INIT-REBOOT. Android 5.1 does this, but it does not - * do so correctly: instead of requesting the lease last obtained on a particular network (e.g., a - * given SSID), it requests the last-leased IP address on the same interface, causing a delay if - * the server NAKs or a timeout if it doesn't. - * - * Known differences from current behaviour: - * - * - Does not request the "static routes" option. - * - Does not support BOOTP servers. DHCP has been around since 1993, should be everywhere now. - * - Requests the "broadcast" option, but does nothing with it. - * - Rejects invalid subnet masks such as 255.255.255.1 (current code treats that as 255.255.255.0). - * - * @hide + * TODO: remove this class after migrating clients. */ -public class DhcpClient extends StateMachine { - - private static final String TAG = "DhcpClient"; - private static final boolean DBG = true; - private static final boolean STATE_DBG = false; - private static final boolean MSG_DBG = false; - private static final boolean PACKET_DBG = false; - - // Timers and timeouts. - private static final int SECONDS = 1000; - private static final int FIRST_TIMEOUT_MS = 2 * SECONDS; - private static final int MAX_TIMEOUT_MS = 128 * SECONDS; - - // This is not strictly needed, since the client is asynchronous and implements exponential - // backoff. It's maintained for backwards compatibility with the previous DHCP code, which was - // a blocking operation with a 30-second timeout. We pick 36 seconds so we can send packets at - // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter. - private static final int DHCP_TIMEOUT_MS = 36 * SECONDS; - - // DhcpClient uses IpClient's handler. - private static final int PUBLIC_BASE = IpClient.DHCPCLIENT_CMD_BASE; - - /* Commands from controller to start/stop DHCP */ - public static final int CMD_START_DHCP = PUBLIC_BASE + 1; - public static final int CMD_STOP_DHCP = PUBLIC_BASE + 2; +public class DhcpClient { + public static final int CMD_PRE_DHCP_ACTION = 1003; + public static final int CMD_POST_DHCP_ACTION = 1004; + public static final int CMD_PRE_DHCP_ACTION_COMPLETE = 1006; - /* Notification from DHCP state machine prior to DHCP discovery/renewal */ - public static final int CMD_PRE_DHCP_ACTION = PUBLIC_BASE + 3; - /* Notification from DHCP state machine post DHCP discovery/renewal. Indicates - * success/failure */ - public static final int CMD_POST_DHCP_ACTION = PUBLIC_BASE + 4; - /* Notification from DHCP state machine before quitting */ - public static final int CMD_ON_QUIT = PUBLIC_BASE + 5; - - /* Command from controller to indicate DHCP discovery/renewal can continue - * after pre DHCP action is complete */ - public static final int CMD_PRE_DHCP_ACTION_COMPLETE = PUBLIC_BASE + 6; - - /* Command and event notification to/from IpManager requesting the setting - * (or clearing) of an IPv4 LinkAddress. - */ - public static final int CMD_CLEAR_LINKADDRESS = PUBLIC_BASE + 7; - public static final int CMD_CONFIGURE_LINKADDRESS = PUBLIC_BASE + 8; - public static final int EVENT_LINKADDRESS_CONFIGURED = PUBLIC_BASE + 9; - - /* Message.arg1 arguments to CMD_POST_DHCP_ACTION notification */ public static final int DHCP_SUCCESS = 1; public static final int DHCP_FAILURE = 2; - - // Internal messages. - private static final int PRIVATE_BASE = IpClient.DHCPCLIENT_CMD_BASE + 100; - private static final int CMD_KICK = PRIVATE_BASE + 1; - private static final int CMD_RECEIVED_PACKET = PRIVATE_BASE + 2; - private static final int CMD_TIMEOUT = PRIVATE_BASE + 3; - private static final int CMD_RENEW_DHCP = PRIVATE_BASE + 4; - private static final int CMD_REBIND_DHCP = PRIVATE_BASE + 5; - private static final int CMD_EXPIRE_DHCP = PRIVATE_BASE + 6; - - // For message logging. - private static final Class[] sMessageClasses = { DhcpClient.class }; - private static final SparseArray<String> sMessageNames = - MessageUtils.findMessageNames(sMessageClasses); - - // DHCP parameters that we request. - /* package */ static final byte[] REQUESTED_PARAMS = new byte[] { - DHCP_SUBNET_MASK, - DHCP_ROUTER, - DHCP_DNS_SERVER, - DHCP_DOMAIN_NAME, - DHCP_MTU, - DHCP_BROADCAST_ADDRESS, // TODO: currently ignored. - DHCP_LEASE_TIME, - DHCP_RENEWAL_TIME, - DHCP_REBINDING_TIME, - DHCP_VENDOR_INFO, - }; - - // DHCP flag that means "yes, we support unicast." - private static final boolean DO_UNICAST = false; - - // System services / libraries we use. - private final Context mContext; - private final Random mRandom; - private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); - - // Sockets. - // - We use a packet socket to receive, because servers send us packets bound for IP addresses - // which we have not yet configured, and the kernel protocol stack drops these. - // - We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can - // be off-link as well as on-link). - private FileDescriptor mPacketSock; - private FileDescriptor mUdpSock; - private ReceiveThread mReceiveThread; - - // State variables. - private final StateMachine mController; - private final WakeupMessage mKickAlarm; - private final WakeupMessage mTimeoutAlarm; - private final WakeupMessage mRenewAlarm; - private final WakeupMessage mRebindAlarm; - private final WakeupMessage mExpiryAlarm; - private final String mIfaceName; - - private boolean mRegisteredForPreDhcpNotification; - private InterfaceParams mIface; - // TODO: MacAddress-ify more of this class hierarchy. - private byte[] mHwAddr; - private SocketAddress mInterfaceBroadcastAddr; - private int mTransactionId; - private long mTransactionStartMillis; - private DhcpResults mDhcpLease; - private long mDhcpLeaseExpiry; - private DhcpResults mOffer; - - // Milliseconds SystemClock timestamps used to record transition times to DhcpBoundState. - private long mLastInitEnterTime; - private long mLastBoundExitTime; - - // States. - private State mStoppedState = new StoppedState(); - private State mDhcpState = new DhcpState(); - private State mDhcpInitState = new DhcpInitState(); - private State mDhcpSelectingState = new DhcpSelectingState(); - private State mDhcpRequestingState = new DhcpRequestingState(); - private State mDhcpHaveLeaseState = new DhcpHaveLeaseState(); - private State mConfiguringInterfaceState = new ConfiguringInterfaceState(); - private State mDhcpBoundState = new DhcpBoundState(); - private State mDhcpRenewingState = new DhcpRenewingState(); - private State mDhcpRebindingState = new DhcpRebindingState(); - private State mDhcpInitRebootState = new DhcpInitRebootState(); - private State mDhcpRebootingState = new DhcpRebootingState(); - private State mWaitBeforeStartState = new WaitBeforeStartState(mDhcpInitState); - private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(mDhcpRenewingState); - - private WakeupMessage makeWakeupMessage(String cmdName, int cmd) { - cmdName = DhcpClient.class.getSimpleName() + "." + mIfaceName + "." + cmdName; - return new WakeupMessage(mContext, getHandler(), cmdName, cmd); - } - - // TODO: Take an InterfaceParams instance instead of an interface name String. - private DhcpClient(Context context, StateMachine controller, String iface) { - super(TAG, controller.getHandler()); - - mContext = context; - mController = controller; - mIfaceName = iface; - - addState(mStoppedState); - addState(mDhcpState); - addState(mDhcpInitState, mDhcpState); - addState(mWaitBeforeStartState, mDhcpState); - addState(mDhcpSelectingState, mDhcpState); - addState(mDhcpRequestingState, mDhcpState); - addState(mDhcpHaveLeaseState, mDhcpState); - addState(mConfiguringInterfaceState, mDhcpHaveLeaseState); - addState(mDhcpBoundState, mDhcpHaveLeaseState); - addState(mWaitBeforeRenewalState, mDhcpHaveLeaseState); - addState(mDhcpRenewingState, mDhcpHaveLeaseState); - addState(mDhcpRebindingState, mDhcpHaveLeaseState); - addState(mDhcpInitRebootState, mDhcpState); - addState(mDhcpRebootingState, mDhcpState); - - setInitialState(mStoppedState); - - mRandom = new Random(); - - // Used to schedule packet retransmissions. - mKickAlarm = makeWakeupMessage("KICK", CMD_KICK); - // Used to time out PacketRetransmittingStates. - mTimeoutAlarm = makeWakeupMessage("TIMEOUT", CMD_TIMEOUT); - // Used to schedule DHCP reacquisition. - mRenewAlarm = makeWakeupMessage("RENEW", CMD_RENEW_DHCP); - mRebindAlarm = makeWakeupMessage("REBIND", CMD_REBIND_DHCP); - mExpiryAlarm = makeWakeupMessage("EXPIRY", CMD_EXPIRE_DHCP); - } - - public void registerForPreDhcpNotification() { - mRegisteredForPreDhcpNotification = true; - } - - public static DhcpClient makeDhcpClient( - Context context, StateMachine controller, InterfaceParams ifParams) { - DhcpClient client = new DhcpClient(context, controller, ifParams.name); - client.mIface = ifParams; - client.start(); - return client; - } - - private boolean initInterface() { - if (mIface == null) mIface = InterfaceParams.getByName(mIfaceName); - if (mIface == null) { - Log.e(TAG, "Can't determine InterfaceParams for " + mIfaceName); - return false; - } - - mHwAddr = mIface.macAddr.toByteArray(); - mInterfaceBroadcastAddr = makePacketSocketAddress(mIface.index, DhcpPacket.ETHER_BROADCAST); - return true; - } - - private void startNewTransaction() { - mTransactionId = mRandom.nextInt(); - mTransactionStartMillis = SystemClock.elapsedRealtime(); - } - - private boolean initSockets() { - return initPacketSocket() && initUdpSocket(); - } - - private boolean initPacketSocket() { - try { - mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP); - SocketAddress addr = makePacketSocketAddress((short) ETH_P_IP, mIface.index); - Os.bind(mPacketSock, addr); - NetworkUtils.attachDhcpFilter(mPacketSock); - } catch(SocketException|ErrnoException e) { - Log.e(TAG, "Error creating packet socket", e); - return false; - } - return true; - } - - private boolean initUdpSocket() { - final int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_DHCP); - try { - mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - SocketUtils.bindSocketToInterface(mUdpSock, mIfaceName); - Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1); - Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1); - Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_RCVBUF, 0); - Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT); - } catch(SocketException|ErrnoException e) { - Log.e(TAG, "Error creating UDP socket", e); - return false; - } finally { - TrafficStats.setThreadStatsTag(oldTag); - } - return true; - } - - private boolean connectUdpSock(Inet4Address to) { - try { - Os.connect(mUdpSock, to, DhcpPacket.DHCP_SERVER); - return true; - } catch (SocketException|ErrnoException e) { - Log.e(TAG, "Error connecting UDP socket", e); - return false; - } - } - - private static void closeQuietly(FileDescriptor fd) { - try { - IoBridge.closeAndSignalBlockedThreads(fd); - } catch (IOException ignored) {} - } - - private void closeSockets() { - closeQuietly(mUdpSock); - closeQuietly(mPacketSock); - } - - class ReceiveThread extends Thread { - - private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH]; - private volatile boolean mStopped = false; - - public void halt() { - mStopped = true; - closeSockets(); // Interrupts the read() call the thread is blocked in. - } - - @Override - public void run() { - if (DBG) Log.d(TAG, "Receive thread started"); - while (!mStopped) { - int length = 0; // Or compiler can't tell it's initialized if a parse error occurs. - try { - length = Os.read(mPacketSock, mPacket, 0, mPacket.length); - DhcpPacket packet = null; - packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2); - if (DBG) Log.d(TAG, "Received packet: " + packet); - sendMessage(CMD_RECEIVED_PACKET, packet); - } catch (IOException|ErrnoException e) { - if (!mStopped) { - Log.e(TAG, "Read error", e); - logError(DhcpErrorEvent.RECEIVE_ERROR); - } - } catch (DhcpPacket.ParseException e) { - Log.e(TAG, "Can't parse packet: " + e.getMessage()); - if (PACKET_DBG) { - Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length)); - } - if (e.errorCode == DhcpErrorEvent.DHCP_NO_COOKIE) { - int snetTagId = 0x534e4554; - String bugId = "31850211"; - int uid = -1; - String data = DhcpPacket.ParseException.class.getName(); - EventLog.writeEvent(snetTagId, bugId, uid, data); - } - logError(e.errorCode); - } - } - if (DBG) Log.d(TAG, "Receive thread stopped"); - } - } - - private short getSecs() { - return (short) ((SystemClock.elapsedRealtime() - mTransactionStartMillis) / 1000); - } - - private boolean transmitPacket(ByteBuffer buf, String description, int encap, Inet4Address to) { - try { - if (encap == DhcpPacket.ENCAP_L2) { - if (DBG) Log.d(TAG, "Broadcasting " + description); - Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr); - } else if (encap == DhcpPacket.ENCAP_BOOTP && to.equals(INADDR_BROADCAST)) { - if (DBG) Log.d(TAG, "Broadcasting " + description); - // We only send L3-encapped broadcasts in DhcpRebindingState, - // where we have an IP address and an unconnected UDP socket. - // - // N.B.: We only need this codepath because DhcpRequestPacket - // hardcodes the source IP address to 0.0.0.0. We could reuse - // the packet socket if this ever changes. - Os.sendto(mUdpSock, buf, 0, to, DhcpPacket.DHCP_SERVER); - } else { - // It's safe to call getpeername here, because we only send unicast packets if we - // have an IP address, and we connect the UDP socket in DhcpBoundState#enter. - if (DBG) Log.d(TAG, String.format("Unicasting %s to %s", - description, Os.getpeername(mUdpSock))); - Os.write(mUdpSock, buf); - } - } catch(ErrnoException|IOException e) { - Log.e(TAG, "Can't send packet: ", e); - return false; - } - return true; - } - - private boolean sendDiscoverPacket() { - ByteBuffer packet = DhcpPacket.buildDiscoverPacket( - DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr, - DO_UNICAST, REQUESTED_PARAMS); - return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST); - } - - private boolean sendRequestPacket( - Inet4Address clientAddress, Inet4Address requestedAddress, - Inet4Address serverAddress, Inet4Address to) { - // TODO: should we use the transaction ID from the server? - final int encap = INADDR_ANY.equals(clientAddress) - ? DhcpPacket.ENCAP_L2 : DhcpPacket.ENCAP_BOOTP; - - ByteBuffer packet = DhcpPacket.buildRequestPacket( - encap, mTransactionId, getSecs(), clientAddress, - DO_UNICAST, mHwAddr, requestedAddress, - serverAddress, REQUESTED_PARAMS, null); - String serverStr = (serverAddress != null) ? serverAddress.getHostAddress() : null; - String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() + - " request=" + requestedAddress.getHostAddress() + - " serverid=" + serverStr; - return transmitPacket(packet, description, encap, to); - } - - private void scheduleLeaseTimers() { - if (mDhcpLeaseExpiry == 0) { - Log.d(TAG, "Infinite lease, no timer scheduling needed"); - return; - } - - final long now = SystemClock.elapsedRealtime(); - - // TODO: consider getting the renew and rebind timers from T1 and T2. - // See also: - // https://tools.ietf.org/html/rfc2131#section-4.4.5 - // https://tools.ietf.org/html/rfc1533#section-9.9 - // https://tools.ietf.org/html/rfc1533#section-9.10 - final long remainingDelay = mDhcpLeaseExpiry - now; - final long renewDelay = remainingDelay / 2; - final long rebindDelay = remainingDelay * 7 / 8; - mRenewAlarm.schedule(now + renewDelay); - mRebindAlarm.schedule(now + rebindDelay); - mExpiryAlarm.schedule(now + remainingDelay); - Log.d(TAG, "Scheduling renewal in " + (renewDelay / 1000) + "s"); - Log.d(TAG, "Scheduling rebind in " + (rebindDelay / 1000) + "s"); - Log.d(TAG, "Scheduling expiry in " + (remainingDelay / 1000) + "s"); - } - - private void notifySuccess() { - mController.sendMessage( - CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease)); - } - - private void notifyFailure() { - mController.sendMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0, null); - } - - private void acceptDhcpResults(DhcpResults results, String msg) { - mDhcpLease = results; - mOffer = null; - Log.d(TAG, msg + " lease: " + mDhcpLease); - notifySuccess(); - } - - private void clearDhcpState() { - mDhcpLease = null; - mDhcpLeaseExpiry = 0; - mOffer = null; - } - - /** - * Quit the DhcpStateMachine. - * - * @hide - */ - public void doQuit() { - Log.d(TAG, "doQuit"); - quit(); - } - - @Override - protected void onQuitting() { - Log.d(TAG, "onQuitting"); - mController.sendMessage(CMD_ON_QUIT); - } - - abstract class LoggingState extends State { - private long mEnterTimeMs; - - @Override - public void enter() { - if (STATE_DBG) Log.d(TAG, "Entering state " + getName()); - mEnterTimeMs = SystemClock.elapsedRealtime(); - } - - @Override - public void exit() { - long durationMs = SystemClock.elapsedRealtime() - mEnterTimeMs; - logState(getName(), (int) durationMs); - } - - private String messageName(int what) { - return sMessageNames.get(what, Integer.toString(what)); - } - - private String messageToString(Message message) { - long now = SystemClock.uptimeMillis(); - return new StringBuilder(" ") - .append(message.getWhen() - now) - .append(messageName(message.what)) - .append(" ").append(message.arg1) - .append(" ").append(message.arg2) - .append(" ").append(message.obj) - .toString(); - } - - @Override - public boolean processMessage(Message message) { - if (MSG_DBG) { - Log.d(TAG, getName() + messageToString(message)); - } - return NOT_HANDLED; - } - - @Override - public String getName() { - // All DhcpClient's states are inner classes with a well defined name. - // Use getSimpleName() and avoid super's getName() creating new String instances. - return getClass().getSimpleName(); - } - } - - // Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with - // CMD_PRE_DHCP_ACTION_COMPLETE, and then transitions to mOtherState. - abstract class WaitBeforeOtherState extends LoggingState { - protected State mOtherState; - - @Override - public void enter() { - super.enter(); - mController.sendMessage(CMD_PRE_DHCP_ACTION); - } - - @Override - public boolean processMessage(Message message) { - super.processMessage(message); - switch (message.what) { - case CMD_PRE_DHCP_ACTION_COMPLETE: - transitionTo(mOtherState); - return HANDLED; - default: - return NOT_HANDLED; - } - } - } - - class StoppedState extends State { - @Override - public boolean processMessage(Message message) { - switch (message.what) { - case CMD_START_DHCP: - if (mRegisteredForPreDhcpNotification) { - transitionTo(mWaitBeforeStartState); - } else { - transitionTo(mDhcpInitState); - } - return HANDLED; - default: - return NOT_HANDLED; - } - } - } - - class WaitBeforeStartState extends WaitBeforeOtherState { - public WaitBeforeStartState(State otherState) { - super(); - mOtherState = otherState; - } - } - - class WaitBeforeRenewalState extends WaitBeforeOtherState { - public WaitBeforeRenewalState(State otherState) { - super(); - mOtherState = otherState; - } - } - - class DhcpState extends State { - @Override - public void enter() { - clearDhcpState(); - if (initInterface() && initSockets()) { - mReceiveThread = new ReceiveThread(); - mReceiveThread.start(); - } else { - notifyFailure(); - transitionTo(mStoppedState); - } - } - - @Override - public void exit() { - if (mReceiveThread != null) { - mReceiveThread.halt(); // Also closes sockets. - mReceiveThread = null; - } - clearDhcpState(); - } - - @Override - public boolean processMessage(Message message) { - super.processMessage(message); - switch (message.what) { - case CMD_STOP_DHCP: - transitionTo(mStoppedState); - return HANDLED; - default: - return NOT_HANDLED; - } - } - } - - public boolean isValidPacket(DhcpPacket packet) { - // TODO: check checksum. - int xid = packet.getTransactionId(); - if (xid != mTransactionId) { - Log.d(TAG, "Unexpected transaction ID " + xid + ", expected " + mTransactionId); - return false; - } - if (!Arrays.equals(packet.getClientMac(), mHwAddr)) { - Log.d(TAG, "MAC addr mismatch: got " + - HexDump.toHexString(packet.getClientMac()) + ", expected " + - HexDump.toHexString(packet.getClientMac())); - return false; - } - return true; - } - - public void setDhcpLeaseExpiry(DhcpPacket packet) { - long leaseTimeMillis = packet.getLeaseTimeMillis(); - mDhcpLeaseExpiry = - (leaseTimeMillis > 0) ? SystemClock.elapsedRealtime() + leaseTimeMillis : 0; - } - - /** - * Retransmits packets using jittered exponential backoff with an optional timeout. Packet - * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. If a subclass - * sets mTimeout to a positive value, then timeout() is called by an AlarmManager alarm mTimeout - * milliseconds after entering the state. Kicks and timeouts are cancelled when leaving the - * state. - * - * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a - * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET - * sent by the receive thread. They may also set mTimeout and implement timeout. - */ - abstract class PacketRetransmittingState extends LoggingState { - - private int mTimer; - protected int mTimeout = 0; - - @Override - public void enter() { - super.enter(); - initTimer(); - maybeInitTimeout(); - sendMessage(CMD_KICK); - } - - @Override - public boolean processMessage(Message message) { - super.processMessage(message); - switch (message.what) { - case CMD_KICK: - sendPacket(); - scheduleKick(); - return HANDLED; - case CMD_RECEIVED_PACKET: - receivePacket((DhcpPacket) message.obj); - return HANDLED; - case CMD_TIMEOUT: - timeout(); - return HANDLED; - default: - return NOT_HANDLED; - } - } - - @Override - public void exit() { - super.exit(); - mKickAlarm.cancel(); - mTimeoutAlarm.cancel(); - } - - abstract protected boolean sendPacket(); - abstract protected void receivePacket(DhcpPacket packet); - protected void timeout() {} - - protected void initTimer() { - mTimer = FIRST_TIMEOUT_MS; - } - - protected int jitterTimer(int baseTimer) { - int maxJitter = baseTimer / 10; - int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter; - return baseTimer + jitter; - } - - protected void scheduleKick() { - long now = SystemClock.elapsedRealtime(); - long timeout = jitterTimer(mTimer); - long alarmTime = now + timeout; - mKickAlarm.schedule(alarmTime); - mTimer *= 2; - if (mTimer > MAX_TIMEOUT_MS) { - mTimer = MAX_TIMEOUT_MS; - } - } - - protected void maybeInitTimeout() { - if (mTimeout > 0) { - long alarmTime = SystemClock.elapsedRealtime() + mTimeout; - mTimeoutAlarm.schedule(alarmTime); - } - } - } - - class DhcpInitState extends PacketRetransmittingState { - public DhcpInitState() { - super(); - } - - @Override - public void enter() { - super.enter(); - startNewTransaction(); - mLastInitEnterTime = SystemClock.elapsedRealtime(); - } - - protected boolean sendPacket() { - return sendDiscoverPacket(); - } - - protected void receivePacket(DhcpPacket packet) { - if (!isValidPacket(packet)) return; - if (!(packet instanceof DhcpOfferPacket)) return; - mOffer = packet.toDhcpResults(); - if (mOffer != null) { - Log.d(TAG, "Got pending lease: " + mOffer); - transitionTo(mDhcpRequestingState); - } - } - } - - // Not implemented. We request the first offer we receive. - class DhcpSelectingState extends LoggingState { - } - - class DhcpRequestingState extends PacketRetransmittingState { - public DhcpRequestingState() { - mTimeout = DHCP_TIMEOUT_MS / 2; - } - - protected boolean sendPacket() { - return sendRequestPacket( - INADDR_ANY, // ciaddr - (Inet4Address) mOffer.ipAddress.getAddress(), // DHCP_REQUESTED_IP - (Inet4Address) mOffer.serverAddress, // DHCP_SERVER_IDENTIFIER - INADDR_BROADCAST); // packet destination address - } - - protected void receivePacket(DhcpPacket packet) { - if (!isValidPacket(packet)) return; - if ((packet instanceof DhcpAckPacket)) { - DhcpResults results = packet.toDhcpResults(); - if (results != null) { - setDhcpLeaseExpiry(packet); - acceptDhcpResults(results, "Confirmed"); - transitionTo(mConfiguringInterfaceState); - } - } else if (packet instanceof DhcpNakPacket) { - // TODO: Wait a while before returning into INIT state. - Log.d(TAG, "Received NAK, returning to INIT"); - mOffer = null; - transitionTo(mDhcpInitState); - } - } - - @Override - protected void timeout() { - // After sending REQUESTs unsuccessfully for a while, go back to init. - transitionTo(mDhcpInitState); - } - } - - class DhcpHaveLeaseState extends State { - @Override - public boolean processMessage(Message message) { - switch (message.what) { - case CMD_EXPIRE_DHCP: - Log.d(TAG, "Lease expired!"); - notifyFailure(); - transitionTo(mDhcpInitState); - return HANDLED; - default: - return NOT_HANDLED; - } - } - - @Override - public void exit() { - // Clear any extant alarms. - mRenewAlarm.cancel(); - mRebindAlarm.cancel(); - mExpiryAlarm.cancel(); - clearDhcpState(); - // Tell IpManager to clear the IPv4 address. There is no need to - // wait for confirmation since any subsequent packets are sent from - // INADDR_ANY anyway (DISCOVER, REQUEST). - mController.sendMessage(CMD_CLEAR_LINKADDRESS); - } - } - - class ConfiguringInterfaceState extends LoggingState { - @Override - public void enter() { - super.enter(); - mController.sendMessage(CMD_CONFIGURE_LINKADDRESS, mDhcpLease.ipAddress); - } - - @Override - public boolean processMessage(Message message) { - super.processMessage(message); - switch (message.what) { - case EVENT_LINKADDRESS_CONFIGURED: - transitionTo(mDhcpBoundState); - return HANDLED; - default: - return NOT_HANDLED; - } - } - } - - class DhcpBoundState extends LoggingState { - @Override - public void enter() { - super.enter(); - if (mDhcpLease.serverAddress != null && !connectUdpSock(mDhcpLease.serverAddress)) { - // There's likely no point in going into DhcpInitState here, we'll probably - // just repeat the transaction, get the same IP address as before, and fail. - // - // NOTE: It is observed that connectUdpSock() basically never fails, due to - // SO_BINDTODEVICE. Examining the local socket address shows it will happily - // return an IPv4 address from another interface, or even return "0.0.0.0". - // - // TODO: Consider deleting this check, following testing on several kernels. - notifyFailure(); - transitionTo(mStoppedState); - } - - scheduleLeaseTimers(); - logTimeToBoundState(); - } - - @Override - public void exit() { - super.exit(); - mLastBoundExitTime = SystemClock.elapsedRealtime(); - } - - @Override - public boolean processMessage(Message message) { - super.processMessage(message); - switch (message.what) { - case CMD_RENEW_DHCP: - if (mRegisteredForPreDhcpNotification) { - transitionTo(mWaitBeforeRenewalState); - } else { - transitionTo(mDhcpRenewingState); - } - return HANDLED; - default: - return NOT_HANDLED; - } - } - - private void logTimeToBoundState() { - long now = SystemClock.elapsedRealtime(); - if (mLastBoundExitTime > mLastInitEnterTime) { - logState(DhcpClientEvent.RENEWING_BOUND, (int)(now - mLastBoundExitTime)); - } else { - logState(DhcpClientEvent.INITIAL_BOUND, (int)(now - mLastInitEnterTime)); - } - } - } - - abstract class DhcpReacquiringState extends PacketRetransmittingState { - protected String mLeaseMsg; - - @Override - public void enter() { - super.enter(); - startNewTransaction(); - } - - abstract protected Inet4Address packetDestination(); - - protected boolean sendPacket() { - return sendRequestPacket( - (Inet4Address) mDhcpLease.ipAddress.getAddress(), // ciaddr - INADDR_ANY, // DHCP_REQUESTED_IP - null, // DHCP_SERVER_IDENTIFIER - packetDestination()); // packet destination address - } - - protected void receivePacket(DhcpPacket packet) { - if (!isValidPacket(packet)) return; - if ((packet instanceof DhcpAckPacket)) { - final DhcpResults results = packet.toDhcpResults(); - if (results != null) { - if (!mDhcpLease.ipAddress.equals(results.ipAddress)) { - Log.d(TAG, "Renewed lease not for our current IP address!"); - notifyFailure(); - transitionTo(mDhcpInitState); - } - setDhcpLeaseExpiry(packet); - // Updating our notion of DhcpResults here only causes the - // DNS servers and routes to be updated in LinkProperties - // in IpManager and by any overridden relevant handlers of - // the registered IpManager.Callback. IP address changes - // are not supported here. - acceptDhcpResults(results, mLeaseMsg); - transitionTo(mDhcpBoundState); - } - } else if (packet instanceof DhcpNakPacket) { - Log.d(TAG, "Received NAK, returning to INIT"); - notifyFailure(); - transitionTo(mDhcpInitState); - } - } - } - - class DhcpRenewingState extends DhcpReacquiringState { - public DhcpRenewingState() { - mLeaseMsg = "Renewed"; - } - - @Override - public boolean processMessage(Message message) { - if (super.processMessage(message) == HANDLED) { - return HANDLED; - } - - switch (message.what) { - case CMD_REBIND_DHCP: - transitionTo(mDhcpRebindingState); - return HANDLED; - default: - return NOT_HANDLED; - } - } - - @Override - protected Inet4Address packetDestination() { - // Not specifying a SERVER_IDENTIFIER option is a violation of RFC 2131, but... - // http://b/25343517 . Try to make things work anyway by using broadcast renews. - return (mDhcpLease.serverAddress != null) ? - mDhcpLease.serverAddress : INADDR_BROADCAST; - } - } - - class DhcpRebindingState extends DhcpReacquiringState { - public DhcpRebindingState() { - mLeaseMsg = "Rebound"; - } - - @Override - public void enter() { - super.enter(); - - // We need to broadcast and possibly reconnect the socket to a - // completely different server. - closeQuietly(mUdpSock); - if (!initUdpSocket()) { - Log.e(TAG, "Failed to recreate UDP socket"); - transitionTo(mDhcpInitState); - } - } - - @Override - protected Inet4Address packetDestination() { - return INADDR_BROADCAST; - } - } - - class DhcpInitRebootState extends LoggingState { - } - - class DhcpRebootingState extends LoggingState { - } - - private void logError(int errorCode) { - mMetricsLog.log(mIfaceName, new DhcpErrorEvent(errorCode)); - } - - private void logState(String name, int durationMs) { - final DhcpClientEvent event = new DhcpClientEvent.Builder() - .setMsg(name) - .setDurationMs(durationMs) - .build(); - mMetricsLog.log(mIfaceName, event); - } } diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java index 233b86f5f8b2..a61c2efd64da 100644 --- a/services/net/java/android/net/ip/IpClient.java +++ b/services/net/java/android/net/ip/IpClient.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,225 +16,209 @@ package android.net.ip; -import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable; +import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable; import android.content.Context; -import android.net.ConnectivityManager; -import android.net.DhcpResults; -import android.net.INetd; -import android.net.IpPrefix; -import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; -import android.net.ProvisioningConfigurationParcelable; import android.net.ProxyInfo; -import android.net.ProxyInfoParcelable; -import android.net.RouteInfo; import android.net.StaticIpConfiguration; import android.net.apf.ApfCapabilities; -import android.net.apf.ApfFilter; -import android.net.dhcp.DhcpClient; -import android.net.metrics.IpConnectivityLog; -import android.net.metrics.IpManagerEvent; -import android.net.shared.InitialConfiguration; -import android.net.util.InterfaceParams; -import android.net.util.NetdService; -import android.net.util.SharedLog; import android.os.ConditionVariable; import android.os.INetworkManagementService; -import android.os.Message; import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.SystemClock; -import android.text.TextUtils; -import android.util.LocalLog; import android.util.Log; -import android.util.SparseArray; - -import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; -import com.android.internal.util.IState; -import com.android.internal.util.IndentingPrintWriter; -import com.android.internal.util.MessageUtils; -import com.android.internal.util.Preconditions; -import com.android.internal.util.State; -import com.android.internal.util.StateMachine; -import com.android.internal.util.WakeupMessage; -import com.android.server.net.NetlinkTracker; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.net.InetAddress; -import java.util.Collection; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CountDownLatch; -import java.util.function.Predicate; -import java.util.stream.Collectors; - /** - * IpClient - * - * This class provides the interface to IP-layer provisioning and maintenance - * functionality that can be used by transport layers like Wi-Fi, Ethernet, - * et cetera. - * - * [ Lifetime ] - * IpClient is designed to be instantiated as soon as the interface name is - * known and can be as long-lived as the class containing it (i.e. declaring - * it "private final" is okay). - * + * Proxy for the IpClient in the NetworkStack. To be removed once clients are migrated. * @hide */ -public class IpClient extends StateMachine { - private static final boolean DBG = false; +public class IpClient { + private static final String TAG = IpClient.class.getSimpleName(); + private static final int IPCLIENT_BLOCK_TIMEOUT_MS = 10_000; - // For message logging. - private static final Class[] sMessageClasses = { IpClient.class, DhcpClient.class }; - private static final SparseArray<String> sWhatToString = - MessageUtils.findMessageNames(sMessageClasses); - // Two static concurrent hashmaps of interface name to logging classes. - // One holds StateMachine logs and the other connectivity packet logs. - private static final ConcurrentHashMap<String, SharedLog> sSmLogs = new ConcurrentHashMap<>(); - private static final ConcurrentHashMap<String, LocalLog> sPktLogs = new ConcurrentHashMap<>(); - - // If |args| is non-empty, assume it's a list of interface names for which - // we should print IpClient logs (filter out all others). - public static void dumpAllLogs(PrintWriter writer, String[] args) { - for (String ifname : sSmLogs.keySet()) { - if (!ArrayUtils.isEmpty(args) && !ArrayUtils.contains(args, ifname)) continue; - - writer.println(String.format("--- BEGIN %s ---", ifname)); - - final SharedLog smLog = sSmLogs.get(ifname); - if (smLog != null) { - writer.println("State machine log:"); - smLog.dump(null, writer, null); - } - - writer.println(""); + public static final String DUMP_ARG = "ipclient"; - final LocalLog pktLog = sPktLogs.get(ifname); - if (pktLog != null) { - writer.println("Connectivity packet log:"); - pktLog.readOnlyLocalLog().dump(null, writer, null); - } + private final ConditionVariable mIpClientCv; + private final ConditionVariable mShutdownCv; - writer.println(String.format("--- END %s ---", ifname)); - } - } + private volatile IIpClient mIpClient; /** - * TODO: remove after migrating clients to use IpClientCallbacks directly * @see IpClientCallbacks */ public static class Callback extends IpClientCallbacks {} /** - * TODO: remove once clients are migrated to IpClientUtil.WaitForProvisioningCallback - * @see IpClientUtil.WaitForProvisioningCallbacks + * IpClient callback that allows clients to block until provisioning is complete. */ - public static class WaitForProvisioningCallback - extends IpClientUtil.WaitForProvisioningCallbacks {} - - // Use a wrapper class to log in order to ensure complete and detailed - // logging. This method is lighter weight than annotations/reflection - // and has the following benefits: - // - // - No invoked method can be forgotten. - // Any new method added to IpClient.Callback must be overridden - // here or it will never be called. - // - // - No invoking call site can be forgotten. - // Centralized logging in this way means call sites don't need to - // remember to log, and therefore no call site can be forgotten. - // - // - No variation in log format among call sites. - // Encourages logging of any available arguments, and all call sites - // are necessarily logged identically. - // - // NOTE: Log first because passed objects may or may not be thread-safe and - // once passed on to the callback they may be modified by another thread. - // - // TODO: Find an lighter weight approach. - private class LoggingCallbackWrapper extends IpClientCallbacks { - private static final String PREFIX = "INVOKE "; - private final IpClientCallbacks mCallback; - - LoggingCallbackWrapper(IpClientCallbacks callback) { - mCallback = (callback != null) ? callback : new IpClientCallbacks(); - } + public static class WaitForProvisioningCallback extends Callback { + private final ConditionVariable mCV = new ConditionVariable(); + private LinkProperties mCallbackLinkProperties; - private void log(String msg) { - mLog.log(PREFIX + msg); + /** + * Block until either {@link #onProvisioningSuccess(LinkProperties)} or + * {@link #onProvisioningFailure(LinkProperties)} is called. + */ + public LinkProperties waitForProvisioning() { + mCV.block(); + return mCallbackLinkProperties; } @Override - public void onPreDhcpAction() { - log("onPreDhcpAction()"); - mCallback.onPreDhcpAction(); - } - @Override - public void onPostDhcpAction() { - log("onPostDhcpAction()"); - mCallback.onPostDhcpAction(); - } - @Override - public void onNewDhcpResults(DhcpResults dhcpResults) { - log("onNewDhcpResults({" + dhcpResults + "})"); - mCallback.onNewDhcpResults(dhcpResults); - } - @Override public void onProvisioningSuccess(LinkProperties newLp) { - log("onProvisioningSuccess({" + newLp + "})"); - mCallback.onProvisioningSuccess(newLp); + mCallbackLinkProperties = newLp; + mCV.open(); } + @Override public void onProvisioningFailure(LinkProperties newLp) { - log("onProvisioningFailure({" + newLp + "})"); - mCallback.onProvisioningFailure(newLp); + mCallbackLinkProperties = null; + mCV.open(); } - @Override - public void onLinkPropertiesChange(LinkProperties newLp) { - log("onLinkPropertiesChange({" + newLp + "})"); - mCallback.onLinkPropertiesChange(newLp); + } + + private class CallbackImpl extends IpClientUtil.IpClientCallbacksProxy { + /** + * Create a new IpClientCallbacksProxy. + */ + CallbackImpl(IpClientCallbacks cb) { + super(cb); } + @Override - public void onReachabilityLost(String logMsg) { - log("onReachabilityLost(" + logMsg + ")"); - mCallback.onReachabilityLost(logMsg); + public void onIpClientCreated(IIpClient ipClient) { + mIpClient = ipClient; + mIpClientCv.open(); + super.onIpClientCreated(ipClient); } + @Override public void onQuit() { - log("onQuit()"); - mCallback.onQuit(); - } - @Override - public void installPacketFilter(byte[] filter) { - log("installPacketFilter(byte[" + filter.length + "])"); - mCallback.installPacketFilter(filter); - } - @Override - public void startReadPacketFilter() { - log("startReadPacketFilter()"); - mCallback.startReadPacketFilter(); - } - @Override - public void setFallbackMulticastFilter(boolean enabled) { - log("setFallbackMulticastFilter(" + enabled + ")"); - mCallback.setFallbackMulticastFilter(enabled); + mShutdownCv.open(); + super.onQuit(); } - @Override - public void setNeighborDiscoveryOffload(boolean enable) { - log("setNeighborDiscoveryOffload(" + enable + ")"); - mCallback.setNeighborDiscoveryOffload(enable); + } + + /** + * Create a new IpClient. + */ + public IpClient(Context context, String iface, Callback callback) { + mIpClientCv = new ConditionVariable(false); + mShutdownCv = new ConditionVariable(false); + + IpClientUtil.makeIpClient(context, iface, new CallbackImpl(callback)); + } + + /** + * @see IpClient#IpClient(Context, String, IpClient.Callback) + */ + public IpClient(Context context, String iface, Callback callback, + INetworkManagementService nms) { + this(context, iface, callback); + } + + private interface IpClientAction { + void useIpClient(IIpClient ipClient) throws RemoteException; + } + + private void doWithIpClient(IpClientAction action) { + mIpClientCv.block(IPCLIENT_BLOCK_TIMEOUT_MS); + try { + action.useIpClient(mIpClient); + } catch (RemoteException e) { + Log.e(TAG, "Error communicating with IpClient", e); } } /** + * Notify IpClient that PreDhcpAction is completed. + */ + public void completedPreDhcpAction() { + doWithIpClient(c -> c.completedPreDhcpAction()); + } + + /** + * Confirm the provisioning configuration. + */ + public void confirmConfiguration() { + doWithIpClient(c -> c.confirmConfiguration()); + } + + /** + * Notify IpClient that packet filter read is complete. + */ + public void readPacketFilterComplete(byte[] data) { + doWithIpClient(c -> c.readPacketFilterComplete(data)); + } + + /** + * Shutdown the IpClient altogether. + */ + public void shutdown() { + doWithIpClient(c -> c.shutdown()); + } + + /** + * Start the IpClient provisioning. + */ + public void startProvisioning(ProvisioningConfiguration config) { + doWithIpClient(c -> c.startProvisioning(config.toStableParcelable())); + } + + /** + * Stop the IpClient. + */ + public void stop() { + doWithIpClient(c -> c.stop()); + } + + /** + * Set the IpClient TCP buffer sizes. + */ + public void setTcpBufferSizes(String tcpBufferSizes) { + doWithIpClient(c -> c.setTcpBufferSizes(tcpBufferSizes)); + } + + /** + * Set the IpClient HTTP proxy. + */ + public void setHttpProxy(ProxyInfo proxyInfo) { + doWithIpClient(c -> c.setHttpProxy(toStableParcelable(proxyInfo))); + } + + /** + * Set the IpClient multicast filter. + */ + public void setMulticastFilter(boolean enabled) { + doWithIpClient(c -> c.setMulticastFilter(enabled)); + } + + /** + * Dump IpClient logs. + */ + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + doWithIpClient(c -> IpClientUtil.dumpIpClient(c, fd, pw, args)); + } + + /** + * Block until IpClient shutdown. + */ + public void awaitShutdown() { + mShutdownCv.block(IPCLIENT_BLOCK_TIMEOUT_MS); + } + + /** + * Create a new ProvisioningConfiguration. + */ + public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() { + return new ProvisioningConfiguration.Builder(); + } + + /** * TODO: remove after migrating clients to use the shared configuration class directly. * @see android.net.shared.ProvisioningConfiguration */ @@ -333,1430 +317,4 @@ public class IpClient extends StateMachine { } } } - - public static final String DUMP_ARG = "ipclient"; - public static final String DUMP_ARG_CONFIRM = "confirm"; - - private static final int CMD_TERMINATE_AFTER_STOP = 1; - private static final int CMD_STOP = 2; - private static final int CMD_START = 3; - private static final int CMD_CONFIRM = 4; - private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 5; - // Triggered by NetlinkTracker to communicate netlink events. - private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6; - private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 7; - private static final int CMD_UPDATE_HTTP_PROXY = 8; - private static final int CMD_SET_MULTICAST_FILTER = 9; - private static final int EVENT_PROVISIONING_TIMEOUT = 10; - private static final int EVENT_DHCPACTION_TIMEOUT = 11; - private static final int EVENT_READ_PACKET_FILTER_COMPLETE = 12; - - // Internal commands to use instead of trying to call transitionTo() inside - // a given State's enter() method. Calling transitionTo() from enter/exit - // encounters a Log.wtf() that can cause trouble on eng builds. - private static final int CMD_JUMP_STARTED_TO_RUNNING = 100; - private static final int CMD_JUMP_RUNNING_TO_STOPPING = 101; - private static final int CMD_JUMP_STOPPING_TO_STOPPED = 102; - - // IpClient shares a handler with DhcpClient: commands must not overlap - public static final int DHCPCLIENT_CMD_BASE = 1000; - - private static final int MAX_LOG_RECORDS = 500; - private static final int MAX_PACKET_RECORDS = 100; - - private static final boolean NO_CALLBACKS = false; - private static final boolean SEND_CALLBACKS = true; - - // This must match the interface prefix in clatd.c. - // TODO: Revert this hack once IpClient and Nat464Xlat work in concert. - private static final String CLAT_PREFIX = "v4-"; - - private static final int IMMEDIATE_FAILURE_DURATION = 0; - - private static final int PROV_CHANGE_STILL_NOT_PROVISIONED = 1; - private static final int PROV_CHANGE_LOST_PROVISIONING = 2; - private static final int PROV_CHANGE_GAINED_PROVISIONING = 3; - private static final int PROV_CHANGE_STILL_PROVISIONED = 4; - - private final State mStoppedState = new StoppedState(); - private final State mStoppingState = new StoppingState(); - private final State mStartedState = new StartedState(); - private final State mRunningState = new RunningState(); - - private final String mTag; - private final Context mContext; - private final String mInterfaceName; - private final String mClatInterfaceName; - @VisibleForTesting - protected final IpClientCallbacks mCallback; - private final Dependencies mDependencies; - private final CountDownLatch mShutdownLatch; - private final ConnectivityManager mCm; - private final INetworkManagementService mNwService; - private final NetlinkTracker mNetlinkTracker; - private final WakeupMessage mProvisioningTimeoutAlarm; - private final WakeupMessage mDhcpActionTimeoutAlarm; - private final SharedLog mLog; - private final LocalLog mConnectivityPacketLog; - private final MessageHandlingLogger mMsgStateLogger; - private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); - private final InterfaceController mInterfaceCtrl; - - private InterfaceParams mInterfaceParams; - - /** - * Non-final member variables accessed only from within our StateMachine. - */ - private LinkProperties mLinkProperties; - private android.net.shared.ProvisioningConfiguration mConfiguration; - private IpReachabilityMonitor mIpReachabilityMonitor; - private DhcpClient mDhcpClient; - private DhcpResults mDhcpResults; - private String mTcpBufferSizes; - private ProxyInfo mHttpProxy; - private ApfFilter mApfFilter; - private boolean mMulticastFiltering; - private long mStartTimeMillis; - - /** - * Reading the snapshot is an asynchronous operation initiated by invoking - * Callback.startReadPacketFilter() and completed when the WiFi Service responds with an - * EVENT_READ_PACKET_FILTER_COMPLETE message. The mApfDataSnapshotComplete condition variable - * signals when a new snapshot is ready. - */ - private final ConditionVariable mApfDataSnapshotComplete = new ConditionVariable(); - - public static class Dependencies { - public INetworkManagementService getNMS() { - return INetworkManagementService.Stub.asInterface( - ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); - } - - public INetd getNetd() { - return NetdService.getInstance(); - } - - /** - * Get interface parameters for the specified interface. - */ - public InterfaceParams getInterfaceParams(String ifname) { - return InterfaceParams.getByName(ifname); - } - } - - public IpClient(Context context, String ifName, IpClientCallbacks callback) { - this(context, ifName, callback, new Dependencies()); - } - - /** - * An expanded constructor, useful for dependency injection. - * TODO: migrate all test users to mock IpClient directly and remove this ctor. - */ - public IpClient(Context context, String ifName, IpClientCallbacks callback, - INetworkManagementService nwService) { - this(context, ifName, callback, new Dependencies() { - @Override - public INetworkManagementService getNMS() { - return nwService; - } - }); - } - - @VisibleForTesting - IpClient(Context context, String ifName, IpClientCallbacks callback, Dependencies deps) { - super(IpClient.class.getSimpleName() + "." + ifName); - Preconditions.checkNotNull(ifName); - Preconditions.checkNotNull(callback); - - mTag = getName(); - - mContext = context; - mInterfaceName = ifName; - mClatInterfaceName = CLAT_PREFIX + ifName; - mCallback = new LoggingCallbackWrapper(callback); - mDependencies = deps; - mShutdownLatch = new CountDownLatch(1); - mCm = mContext.getSystemService(ConnectivityManager.class); - mNwService = deps.getNMS(); - - sSmLogs.putIfAbsent(mInterfaceName, new SharedLog(MAX_LOG_RECORDS, mTag)); - mLog = sSmLogs.get(mInterfaceName); - sPktLogs.putIfAbsent(mInterfaceName, new LocalLog(MAX_PACKET_RECORDS)); - mConnectivityPacketLog = sPktLogs.get(mInterfaceName); - mMsgStateLogger = new MessageHandlingLogger(); - - // TODO: Consider creating, constructing, and passing in some kind of - // InterfaceController.Dependencies class. - mInterfaceCtrl = new InterfaceController(mInterfaceName, deps.getNetd(), mLog); - - mNetlinkTracker = new NetlinkTracker( - mInterfaceName, - new NetlinkTracker.Callback() { - @Override - public void update() { - sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED); - } - }) { - @Override - public void interfaceAdded(String iface) { - super.interfaceAdded(iface); - if (mClatInterfaceName.equals(iface)) { - mCallback.setNeighborDiscoveryOffload(false); - } else if (!mInterfaceName.equals(iface)) { - return; - } - - final String msg = "interfaceAdded(" + iface + ")"; - logMsg(msg); - } - - @Override - public void interfaceRemoved(String iface) { - super.interfaceRemoved(iface); - // TODO: Also observe mInterfaceName going down and take some - // kind of appropriate action. - if (mClatInterfaceName.equals(iface)) { - // TODO: consider sending a message to the IpClient main - // StateMachine thread, in case "NDO enabled" state becomes - // tied to more things that 464xlat operation. - mCallback.setNeighborDiscoveryOffload(true); - } else if (!mInterfaceName.equals(iface)) { - return; - } - - final String msg = "interfaceRemoved(" + iface + ")"; - logMsg(msg); - } - - private void logMsg(String msg) { - Log.d(mTag, msg); - getHandler().post(() -> mLog.log("OBSERVED " + msg)); - } - }; - - mLinkProperties = new LinkProperties(); - mLinkProperties.setInterfaceName(mInterfaceName); - - mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(), - mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT); - mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(), - mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT); - - // Anything the StateMachine may access must have been instantiated - // before this point. - configureAndStartStateMachine(); - - // Anything that may send messages to the StateMachine must only be - // configured to do so after the StateMachine has started (above). - startStateMachineUpdaters(); - } - - /** - * Make a IIpClient connector to communicate with this IpClient. - */ - public IIpClient makeConnector() { - return new IpClientConnector(); - } - - class IpClientConnector extends IIpClient.Stub { - @Override - public void completedPreDhcpAction() { - IpClient.this.completedPreDhcpAction(); - } - @Override - public void confirmConfiguration() { - IpClient.this.confirmConfiguration(); - } - @Override - public void readPacketFilterComplete(byte[] data) { - IpClient.this.readPacketFilterComplete(data); - } - @Override - public void shutdown() { - IpClient.this.shutdown(); - } - @Override - public void startProvisioning(ProvisioningConfigurationParcelable req) { - IpClient.this.startProvisioning( - android.net.shared.ProvisioningConfiguration.fromStableParcelable(req)); - } - @Override - public void stop() { - IpClient.this.stop(); - } - @Override - public void setTcpBufferSizes(String tcpBufferSizes) { - IpClient.this.setTcpBufferSizes(tcpBufferSizes); - } - @Override - public void setHttpProxy(ProxyInfoParcelable proxyInfo) { - IpClient.this.setHttpProxy(fromStableParcelable(proxyInfo)); - } - @Override - public void setMulticastFilter(boolean enabled) { - IpClient.this.setMulticastFilter(enabled); - } - // TODO: remove and have IpClient logs dumped in NetworkStack dumpsys - public void dumpIpClientLogs(FileDescriptor fd, PrintWriter pw, String[] args) { - IpClient.this.dump(fd, pw, args); - } - } - - private void configureAndStartStateMachine() { - // CHECKSTYLE:OFF IndentationCheck - addState(mStoppedState); - addState(mStartedState); - addState(mRunningState, mStartedState); - addState(mStoppingState); - // CHECKSTYLE:ON IndentationCheck - - setInitialState(mStoppedState); - - super.start(); - } - - private void startStateMachineUpdaters() { - try { - mNwService.registerObserver(mNetlinkTracker); - } catch (RemoteException e) { - logError("Couldn't register NetlinkTracker: %s", e); - } - } - - private void stopStateMachineUpdaters() { - try { - mNwService.unregisterObserver(mNetlinkTracker); - } catch (RemoteException e) { - logError("Couldn't unregister NetlinkTracker: %s", e); - } - } - - @Override - protected void onQuitting() { - mCallback.onQuit(); - mShutdownLatch.countDown(); - } - - /** - * Shut down this IpClient instance altogether. - */ - public void shutdown() { - stop(); - sendMessage(CMD_TERMINATE_AFTER_STOP); - } - - // In order to avoid deadlock, this method MUST NOT be called on the - // IpClient instance's thread. This prohibition includes code executed by - // when methods on the passed-in IpClient.Callback instance are called. - public void awaitShutdown() { - try { - mShutdownLatch.await(); - } catch (InterruptedException e) { - mLog.e("Interrupted while awaiting shutdown: " + e); - } - } - - public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() { - return new ProvisioningConfiguration.Builder(); - } - - /** - * Start provisioning with the provided parameters. - */ - public void startProvisioning(android.net.shared.ProvisioningConfiguration req) { - if (!req.isValid()) { - doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); - return; - } - - mInterfaceParams = mDependencies.getInterfaceParams(mInterfaceName); - if (mInterfaceParams == null) { - logError("Failed to find InterfaceParams for " + mInterfaceName); - doImmediateProvisioningFailure(IpManagerEvent.ERROR_INTERFACE_NOT_FOUND); - return; - } - - mCallback.setNeighborDiscoveryOffload(true); - sendMessage(CMD_START, new android.net.shared.ProvisioningConfiguration(req)); - } - - // TODO: Delete this. - public void startProvisioning(StaticIpConfiguration staticIpConfig) { - startProvisioning(buildProvisioningConfiguration() - .withStaticConfiguration(staticIpConfig) - .build()); - } - - public void startProvisioning() { - startProvisioning(new android.net.shared.ProvisioningConfiguration()); - } - - /** - * Stop this IpClient. - * - * <p>This does not shut down the StateMachine itself, which is handled by {@link #shutdown()}. - */ - public void stop() { - sendMessage(CMD_STOP); - } - - /** - * Confirm the provisioning configuration. - */ - public void confirmConfiguration() { - sendMessage(CMD_CONFIRM); - } - - /** - * For clients using {@link ProvisioningConfiguration.Builder#withPreDhcpAction()}, must be - * called after {@link IIpClientCallbacks#onPreDhcpAction} to indicate that DHCP is clear to - * proceed. - */ - public void completedPreDhcpAction() { - sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE); - } - - /** - * Indicate that packet filter read is complete. - */ - public void readPacketFilterComplete(byte[] data) { - sendMessage(EVENT_READ_PACKET_FILTER_COMPLETE, data); - } - - /** - * Set the TCP buffer sizes to use. - * - * This may be called, repeatedly, at any time before or after a call to - * #startProvisioning(). The setting is cleared upon calling #stop(). - */ - public void setTcpBufferSizes(String tcpBufferSizes) { - sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes); - } - - /** - * Set the HTTP Proxy configuration to use. - * - * This may be called, repeatedly, at any time before or after a call to - * #startProvisioning(). The setting is cleared upon calling #stop(). - */ - public void setHttpProxy(ProxyInfo proxyInfo) { - sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo); - } - - /** - * Enable or disable the multicast filter. Attempts to use APF to accomplish the filtering, - * if not, Callback.setFallbackMulticastFilter() is called. - */ - public void setMulticastFilter(boolean enabled) { - sendMessage(CMD_SET_MULTICAST_FILTER, enabled); - } - - /** - * Dump logs of this IpClient. - */ - public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { - if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) { - // Execute confirmConfiguration() and take no further action. - confirmConfiguration(); - return; - } - - // Thread-unsafe access to mApfFilter but just used for debugging. - final ApfFilter apfFilter = mApfFilter; - final android.net.shared.ProvisioningConfiguration provisioningConfig = mConfiguration; - final ApfCapabilities apfCapabilities = (provisioningConfig != null) - ? provisioningConfig.mApfCapabilities : null; - - IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); - pw.println(mTag + " APF dump:"); - pw.increaseIndent(); - if (apfFilter != null) { - if (apfCapabilities.hasDataAccess()) { - // Request a new snapshot, then wait for it. - mApfDataSnapshotComplete.close(); - mCallback.startReadPacketFilter(); - if (!mApfDataSnapshotComplete.block(1000)) { - pw.print("TIMEOUT: DUMPING STALE APF SNAPSHOT"); - } - } - apfFilter.dump(pw); - - } else { - pw.print("No active ApfFilter; "); - if (provisioningConfig == null) { - pw.println("IpClient not yet started."); - } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) { - pw.println("Hardware does not support APF."); - } else { - pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities); - } - } - pw.decreaseIndent(); - pw.println(); - pw.println(mTag + " current ProvisioningConfiguration:"); - pw.increaseIndent(); - pw.println(Objects.toString(provisioningConfig, "N/A")); - pw.decreaseIndent(); - - final IpReachabilityMonitor iprm = mIpReachabilityMonitor; - if (iprm != null) { - pw.println(); - pw.println(mTag + " current IpReachabilityMonitor state:"); - pw.increaseIndent(); - iprm.dump(pw); - pw.decreaseIndent(); - } - - pw.println(); - pw.println(mTag + " StateMachine dump:"); - pw.increaseIndent(); - mLog.dump(fd, pw, args); - pw.decreaseIndent(); - - pw.println(); - pw.println(mTag + " connectivity packet log:"); - pw.println(); - pw.println("Debug with python and scapy via:"); - pw.println("shell$ python"); - pw.println(">>> from scapy import all as scapy"); - pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()"); - pw.println(); - - pw.increaseIndent(); - mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args); - pw.decreaseIndent(); - } - - - /** - * Internals. - */ - - @Override - protected String getWhatToString(int what) { - return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what)); - } - - @Override - protected String getLogRecString(Message msg) { - final String logLine = String.format( - "%s/%d %d %d %s [%s]", - mInterfaceName, (mInterfaceParams == null) ? -1 : mInterfaceParams.index, - msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger); - - final String richerLogLine = getWhatToString(msg.what) + " " + logLine; - mLog.log(richerLogLine); - if (DBG) { - Log.d(mTag, richerLogLine); - } - - mMsgStateLogger.reset(); - return logLine; - } - - @Override - protected boolean recordLogRec(Message msg) { - // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy, - // and we already log any LinkProperties change that results in an - // invocation of IpClient.Callback#onLinkPropertiesChange(). - final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED); - if (!shouldLog) { - mMsgStateLogger.reset(); - } - return shouldLog; - } - - private void logError(String fmt, Object... args) { - final String msg = "ERROR " + String.format(fmt, args); - Log.e(mTag, msg); - mLog.log(msg); - } - - // This needs to be called with care to ensure that our LinkProperties - // are in sync with the actual LinkProperties of the interface. For example, - // we should only call this if we know for sure that there are no IP addresses - // assigned to the interface, etc. - private void resetLinkProperties() { - mNetlinkTracker.clearLinkProperties(); - mConfiguration = null; - mDhcpResults = null; - mTcpBufferSizes = ""; - mHttpProxy = null; - - mLinkProperties = new LinkProperties(); - mLinkProperties.setInterfaceName(mInterfaceName); - } - - private void recordMetric(final int type) { - // We may record error metrics prior to starting. - // Map this to IMMEDIATE_FAILURE_DURATION. - final long duration = (mStartTimeMillis > 0) - ? (SystemClock.elapsedRealtime() - mStartTimeMillis) - : IMMEDIATE_FAILURE_DURATION; - mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration)); - } - - // For now: use WifiStateMachine's historical notion of provisioned. - @VisibleForTesting - static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) { - // For historical reasons, we should connect even if all we have is - // an IPv4 address and nothing else. - if (lp.hasIPv4Address() || lp.isProvisioned()) { - return true; - } - if (config == null) { - return false; - } - - // When an InitialConfiguration is specified, ignore any difference with previous - // properties and instead check if properties observed match the desired properties. - return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes()); - } - - // TODO: Investigate folding all this into the existing static function - // LinkProperties.compareProvisioning() or some other single function that - // takes two LinkProperties objects and returns a ProvisioningChange - // object that is a correct and complete assessment of what changed, taking - // account of the asymmetries described in the comments in this function. - // Then switch to using it everywhere (IpReachabilityMonitor, etc.). - private int compareProvisioning(LinkProperties oldLp, LinkProperties newLp) { - int delta; - InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null; - final boolean wasProvisioned = isProvisioned(oldLp, config); - final boolean isProvisioned = isProvisioned(newLp, config); - - if (!wasProvisioned && isProvisioned) { - delta = PROV_CHANGE_GAINED_PROVISIONING; - } else if (wasProvisioned && isProvisioned) { - delta = PROV_CHANGE_STILL_PROVISIONED; - } else if (!wasProvisioned && !isProvisioned) { - delta = PROV_CHANGE_STILL_NOT_PROVISIONED; - } else { - // (wasProvisioned && !isProvisioned) - // - // Note that this is true even if we lose a configuration element - // (e.g., a default gateway) that would not be required to advance - // into provisioned state. This is intended: if we have a default - // router and we lose it, that's a sure sign of a problem, but if - // we connect to a network with no IPv4 DNS servers, we consider - // that to be a network without DNS servers and connect anyway. - // - // See the comment below. - delta = PROV_CHANGE_LOST_PROVISIONING; - } - - final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned(); - final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address(); - final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute(); - - // If bad wifi avoidance is disabled, then ignore IPv6 loss of - // provisioning. Otherwise, when a hotspot that loses Internet - // access sends out a 0-lifetime RA to its clients, the clients - // will disconnect and then reconnect, avoiding the bad hotspot, - // instead of getting stuck on the bad hotspot. http://b/31827713 . - // - // This is incorrect because if the hotspot then regains Internet - // access with a different prefix, TCP connections on the - // deprecated addresses will remain stuck. - // - // Note that we can still be disconnected by IpReachabilityMonitor - // if the IPv6 default gateway (but not the IPv6 DNS servers; see - // accompanying code in IpReachabilityMonitor) is unreachable. - final boolean ignoreIPv6ProvisioningLoss = - mConfiguration != null && mConfiguration.mUsingMultinetworkPolicyTracker - && mCm.getAvoidBadWifi(); - - // Additionally: - // - // Partial configurations (e.g., only an IPv4 address with no DNS - // servers and no default route) are accepted as long as DHCPv4 - // succeeds. On such a network, isProvisioned() will always return - // false, because the configuration is not complete, but we want to - // connect anyway. It might be a disconnected network such as a - // Chromecast or a wireless printer, for example. - // - // Because on such a network isProvisioned() will always return false, - // delta will never be LOST_PROVISIONING. So check for loss of - // provisioning here too. - if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) { - delta = PROV_CHANGE_LOST_PROVISIONING; - } - - // Additionally: - // - // If the previous link properties had a global IPv6 address and an - // IPv6 default route then also consider the loss of that default route - // to be a loss of provisioning. See b/27962810. - if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) { - delta = PROV_CHANGE_LOST_PROVISIONING; - } - - return delta; - } - - private void dispatchCallback(int delta, LinkProperties newLp) { - switch (delta) { - case PROV_CHANGE_GAINED_PROVISIONING: - if (DBG) { - Log.d(mTag, "onProvisioningSuccess()"); - } - recordMetric(IpManagerEvent.PROVISIONING_OK); - mCallback.onProvisioningSuccess(newLp); - break; - - case PROV_CHANGE_LOST_PROVISIONING: - if (DBG) { - Log.d(mTag, "onProvisioningFailure()"); - } - recordMetric(IpManagerEvent.PROVISIONING_FAIL); - mCallback.onProvisioningFailure(newLp); - break; - - default: - if (DBG) { - Log.d(mTag, "onLinkPropertiesChange()"); - } - mCallback.onLinkPropertiesChange(newLp); - break; - } - } - - // Updates all IpClient-related state concerned with LinkProperties. - // Returns a ProvisioningChange for possibly notifying other interested - // parties that are not fronted by IpClient. - private int setLinkProperties(LinkProperties newLp) { - if (mApfFilter != null) { - mApfFilter.setLinkProperties(newLp); - } - if (mIpReachabilityMonitor != null) { - mIpReachabilityMonitor.updateLinkProperties(newLp); - } - - int delta = compareProvisioning(mLinkProperties, newLp); - mLinkProperties = new LinkProperties(newLp); - - if (delta == PROV_CHANGE_GAINED_PROVISIONING) { - // TODO: Add a proper ProvisionedState and cancel the alarm in - // its enter() method. - mProvisioningTimeoutAlarm.cancel(); - } - - return delta; - } - - private LinkProperties assembleLinkProperties() { - // [1] Create a new LinkProperties object to populate. - LinkProperties newLp = new LinkProperties(); - newLp.setInterfaceName(mInterfaceName); - - // [2] Pull in data from netlink: - // - IPv4 addresses - // - IPv6 addresses - // - IPv6 routes - // - IPv6 DNS servers - // - // N.B.: this is fundamentally race-prone and should be fixed by - // changing NetlinkTracker from a hybrid edge/level model to an - // edge-only model, or by giving IpClient its own netlink socket(s) - // so as to track all required information directly. - LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties(); - newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses()); - for (RouteInfo route : netlinkLinkProperties.getRoutes()) { - newLp.addRoute(route); - } - addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers()); - - // [3] Add in data from DHCPv4, if available. - // - // mDhcpResults is never shared with any other owner so we don't have - // to worry about concurrent modification. - if (mDhcpResults != null) { - for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) { - newLp.addRoute(route); - } - addAllReachableDnsServers(newLp, mDhcpResults.dnsServers); - newLp.setDomains(mDhcpResults.domains); - - if (mDhcpResults.mtu != 0) { - newLp.setMtu(mDhcpResults.mtu); - } - } - - // [4] Add in TCP buffer sizes and HTTP Proxy config, if available. - if (!TextUtils.isEmpty(mTcpBufferSizes)) { - newLp.setTcpBufferSizes(mTcpBufferSizes); - } - if (mHttpProxy != null) { - newLp.setHttpProxy(mHttpProxy); - } - - // [5] Add data from InitialConfiguration - if (mConfiguration != null && mConfiguration.mInitialConfig != null) { - InitialConfiguration config = mConfiguration.mInitialConfig; - // Add InitialConfiguration routes and dns server addresses once all addresses - // specified in the InitialConfiguration have been observed with Netlink. - if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) { - for (IpPrefix prefix : config.directlyConnectedRoutes) { - newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName)); - } - } - addAllReachableDnsServers(newLp, config.dnsServers); - } - final LinkProperties oldLp = mLinkProperties; - if (DBG) { - Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s", - netlinkLinkProperties, newLp, oldLp)); - } - - // TODO: also learn via netlink routes specified by an InitialConfiguration and specified - // from a static IP v4 config instead of manually patching them in in steps [3] and [5]. - return newLp; - } - - private static void addAllReachableDnsServers( - LinkProperties lp, Iterable<InetAddress> dnses) { - // TODO: Investigate deleting this reachability check. We should be - // able to pass everything down to netd and let netd do evaluation - // and RFC6724-style sorting. - for (InetAddress dns : dnses) { - if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) { - lp.addDnsServer(dns); - } - } - } - - // Returns false if we have lost provisioning, true otherwise. - private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) { - final LinkProperties newLp = assembleLinkProperties(); - if (Objects.equals(newLp, mLinkProperties)) { - return true; - } - final int delta = setLinkProperties(newLp); - if (sendCallbacks) { - dispatchCallback(delta, newLp); - } - return (delta != PROV_CHANGE_LOST_PROVISIONING); - } - - private void handleIPv4Success(DhcpResults dhcpResults) { - mDhcpResults = new DhcpResults(dhcpResults); - final LinkProperties newLp = assembleLinkProperties(); - final int delta = setLinkProperties(newLp); - - if (DBG) { - Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")"); - } - mCallback.onNewDhcpResults(dhcpResults); - dispatchCallback(delta, newLp); - } - - private void handleIPv4Failure() { - // TODO: Investigate deleting this clearIPv4Address() call. - // - // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances - // that could trigger a call to this function. If we missed handling - // that message in StartedState for some reason we would still clear - // any addresses upon entry to StoppedState. - mInterfaceCtrl.clearIPv4Address(); - mDhcpResults = null; - if (DBG) { - Log.d(mTag, "onNewDhcpResults(null)"); - } - mCallback.onNewDhcpResults(null); - - handleProvisioningFailure(); - } - - private void handleProvisioningFailure() { - final LinkProperties newLp = assembleLinkProperties(); - int delta = setLinkProperties(newLp); - // If we've gotten here and we're still not provisioned treat that as - // a total loss of provisioning. - // - // Either (a) static IP configuration failed or (b) DHCPv4 failed AND - // there was no usable IPv6 obtained before a non-zero provisioning - // timeout expired. - // - // Regardless: GAME OVER. - if (delta == PROV_CHANGE_STILL_NOT_PROVISIONED) { - delta = PROV_CHANGE_LOST_PROVISIONING; - } - - dispatchCallback(delta, newLp); - if (delta == PROV_CHANGE_LOST_PROVISIONING) { - transitionTo(mStoppingState); - } - } - - private void doImmediateProvisioningFailure(int failureType) { - logError("onProvisioningFailure(): %s", failureType); - recordMetric(failureType); - mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties)); - } - - private boolean startIPv4() { - // If we have a StaticIpConfiguration attempt to apply it and - // handle the result accordingly. - if (mConfiguration.mStaticIpConfig != null) { - if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) { - handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig)); - } else { - return false; - } - } else { - // Start DHCPv4. - mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceParams); - mDhcpClient.registerForPreDhcpNotification(); - mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP); - } - - return true; - } - - private boolean startIPv6() { - return mInterfaceCtrl.setIPv6PrivacyExtensions(true) - && mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) - && mInterfaceCtrl.enableIPv6(); - } - - private boolean applyInitialConfig(InitialConfiguration config) { - // TODO: also support specifying a static IPv4 configuration in InitialConfiguration. - for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) { - if (!mInterfaceCtrl.addAddress(addr)) return false; - } - - return true; - } - - private boolean startIpReachabilityMonitor() { - try { - // TODO: Fetch these parameters from settings, and install a - // settings observer to watch for update and re-program these - // parameters (Q: is this level of dynamic updatability really - // necessary or does reading from settings at startup suffice?). - final int numSolicits = 5; - final int interSolicitIntervalMs = 750; - setNeighborParameters(mDependencies.getNetd(), mInterfaceName, - numSolicits, interSolicitIntervalMs); - } catch (Exception e) { - mLog.e("Failed to adjust neighbor parameters", e); - // Carry on using the system defaults (currently: 3, 1000); - } - - try { - mIpReachabilityMonitor = new IpReachabilityMonitor( - mContext, - mInterfaceParams, - getHandler(), - mLog, - new IpReachabilityMonitor.Callback() { - @Override - public void notifyLost(InetAddress ip, String logMsg) { - mCallback.onReachabilityLost(logMsg); - } - }, - mConfiguration.mUsingMultinetworkPolicyTracker); - } catch (IllegalArgumentException iae) { - // Failed to start IpReachabilityMonitor. Log it and call - // onProvisioningFailure() immediately. - // - // See http://b/31038971. - logError("IpReachabilityMonitor failure: %s", iae); - mIpReachabilityMonitor = null; - } - - return (mIpReachabilityMonitor != null); - } - - private void stopAllIP() { - // We don't need to worry about routes, just addresses, because: - // - disableIpv6() will clear autoconf IPv6 routes as well, and - // - we don't get IPv4 routes from netlink - // so we neither react to nor need to wait for changes in either. - - mInterfaceCtrl.disableIPv6(); - mInterfaceCtrl.clearAllAddresses(); - } - - class StoppedState extends State { - @Override - public void enter() { - stopAllIP(); - - resetLinkProperties(); - if (mStartTimeMillis > 0) { - // Completed a life-cycle; send a final empty LinkProperties - // (cleared in resetLinkProperties() above) and record an event. - mCallback.onLinkPropertiesChange(new LinkProperties(mLinkProperties)); - recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE); - mStartTimeMillis = 0; - } - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_TERMINATE_AFTER_STOP: - stopStateMachineUpdaters(); - quit(); - break; - - case CMD_STOP: - break; - - case CMD_START: - mConfiguration = (android.net.shared.ProvisioningConfiguration) msg.obj; - transitionTo(mStartedState); - break; - - case EVENT_NETLINK_LINKPROPERTIES_CHANGED: - handleLinkPropertiesUpdate(NO_CALLBACKS); - break; - - case CMD_UPDATE_TCP_BUFFER_SIZES: - mTcpBufferSizes = (String) msg.obj; - handleLinkPropertiesUpdate(NO_CALLBACKS); - break; - - case CMD_UPDATE_HTTP_PROXY: - mHttpProxy = (ProxyInfo) msg.obj; - handleLinkPropertiesUpdate(NO_CALLBACKS); - break; - - case CMD_SET_MULTICAST_FILTER: - mMulticastFiltering = (boolean) msg.obj; - break; - - case DhcpClient.CMD_ON_QUIT: - // Everything is already stopped. - logError("Unexpected CMD_ON_QUIT (already stopped)."); - break; - - default: - return NOT_HANDLED; - } - - mMsgStateLogger.handled(this, getCurrentState()); - return HANDLED; - } - } - - class StoppingState extends State { - @Override - public void enter() { - if (mDhcpClient == null) { - // There's no DHCPv4 for which to wait; proceed to stopped. - deferMessage(obtainMessage(CMD_JUMP_STOPPING_TO_STOPPED)); - } - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_JUMP_STOPPING_TO_STOPPED: - transitionTo(mStoppedState); - break; - - case CMD_STOP: - break; - - case DhcpClient.CMD_CLEAR_LINKADDRESS: - mInterfaceCtrl.clearIPv4Address(); - break; - - case DhcpClient.CMD_ON_QUIT: - mDhcpClient = null; - transitionTo(mStoppedState); - break; - - default: - deferMessage(msg); - } - - mMsgStateLogger.handled(this, getCurrentState()); - return HANDLED; - } - } - - class StartedState extends State { - @Override - public void enter() { - mStartTimeMillis = SystemClock.elapsedRealtime(); - - if (mConfiguration.mProvisioningTimeoutMs > 0) { - final long alarmTime = SystemClock.elapsedRealtime() - + mConfiguration.mProvisioningTimeoutMs; - mProvisioningTimeoutAlarm.schedule(alarmTime); - } - - if (readyToProceed()) { - deferMessage(obtainMessage(CMD_JUMP_STARTED_TO_RUNNING)); - } else { - // Clear all IPv4 and IPv6 before proceeding to RunningState. - // Clean up any leftover state from an abnormal exit from - // tethering or during an IpClient restart. - stopAllIP(); - } - } - - @Override - public void exit() { - mProvisioningTimeoutAlarm.cancel(); - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_JUMP_STARTED_TO_RUNNING: - transitionTo(mRunningState); - break; - - case CMD_STOP: - transitionTo(mStoppingState); - break; - - case EVENT_NETLINK_LINKPROPERTIES_CHANGED: - handleLinkPropertiesUpdate(NO_CALLBACKS); - if (readyToProceed()) { - transitionTo(mRunningState); - } - break; - - case EVENT_PROVISIONING_TIMEOUT: - handleProvisioningFailure(); - break; - - default: - // It's safe to process messages out of order because the - // only message that can both - // a) be received at this time and - // b) affect provisioning state - // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above). - deferMessage(msg); - } - - mMsgStateLogger.handled(this, getCurrentState()); - return HANDLED; - } - - private boolean readyToProceed() { - return (!mLinkProperties.hasIPv4Address() && !mLinkProperties.hasGlobalIPv6Address()); - } - } - - class RunningState extends State { - private ConnectivityPacketTracker mPacketTracker; - private boolean mDhcpActionInFlight; - - @Override - public void enter() { - ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration(); - apfConfig.apfCapabilities = mConfiguration.mApfCapabilities; - apfConfig.multicastFilter = mMulticastFiltering; - // Get the Configuration for ApfFilter from Context - apfConfig.ieee802_3Filter = - mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames); - apfConfig.ethTypeBlackList = - mContext.getResources().getIntArray(R.array.config_apfEthTypeBlackList); - mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback); - // TODO: investigate the effects of any multicast filtering racing/interfering with the - // rest of this IP configuration startup. - if (mApfFilter == null) { - mCallback.setFallbackMulticastFilter(mMulticastFiltering); - } - - mPacketTracker = createPacketTracker(); - if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName); - - if (mConfiguration.mEnableIPv6 && !startIPv6()) { - doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6); - enqueueJumpToStoppingState(); - return; - } - - if (mConfiguration.mEnableIPv4 && !startIPv4()) { - doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4); - enqueueJumpToStoppingState(); - return; - } - - final InitialConfiguration config = mConfiguration.mInitialConfig; - if ((config != null) && !applyInitialConfig(config)) { - // TODO introduce a new IpManagerEvent constant to distinguish this error case. - doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); - enqueueJumpToStoppingState(); - return; - } - - if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) { - doImmediateProvisioningFailure( - IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR); - enqueueJumpToStoppingState(); - return; - } - } - - @Override - public void exit() { - stopDhcpAction(); - - if (mIpReachabilityMonitor != null) { - mIpReachabilityMonitor.stop(); - mIpReachabilityMonitor = null; - } - - if (mDhcpClient != null) { - mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP); - mDhcpClient.doQuit(); - } - - if (mPacketTracker != null) { - mPacketTracker.stop(); - mPacketTracker = null; - } - - if (mApfFilter != null) { - mApfFilter.shutdown(); - mApfFilter = null; - } - - resetLinkProperties(); - } - - private void enqueueJumpToStoppingState() { - deferMessage(obtainMessage(CMD_JUMP_RUNNING_TO_STOPPING)); - } - - private ConnectivityPacketTracker createPacketTracker() { - try { - return new ConnectivityPacketTracker( - getHandler(), mInterfaceParams, mConnectivityPacketLog); - } catch (IllegalArgumentException e) { - return null; - } - } - - private void ensureDhcpAction() { - if (!mDhcpActionInFlight) { - mCallback.onPreDhcpAction(); - mDhcpActionInFlight = true; - final long alarmTime = SystemClock.elapsedRealtime() - + mConfiguration.mRequestedPreDhcpActionMs; - mDhcpActionTimeoutAlarm.schedule(alarmTime); - } - } - - private void stopDhcpAction() { - mDhcpActionTimeoutAlarm.cancel(); - if (mDhcpActionInFlight) { - mCallback.onPostDhcpAction(); - mDhcpActionInFlight = false; - } - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_JUMP_RUNNING_TO_STOPPING: - case CMD_STOP: - transitionTo(mStoppingState); - break; - - case CMD_START: - logError("ALERT: START received in StartedState. Please fix caller."); - break; - - case CMD_CONFIRM: - // TODO: Possibly introduce a second type of confirmation - // that both probes (a) on-link neighbors and (b) does - // a DHCPv4 RENEW. We used to do this on Wi-Fi framework - // roams. - if (mIpReachabilityMonitor != null) { - mIpReachabilityMonitor.probeAll(); - } - break; - - case EVENT_PRE_DHCP_ACTION_COMPLETE: - // It's possible to reach here if, for example, someone - // calls completedPreDhcpAction() after provisioning with - // a static IP configuration. - if (mDhcpClient != null) { - mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE); - } - break; - - case EVENT_NETLINK_LINKPROPERTIES_CHANGED: - if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) { - transitionTo(mStoppingState); - } - break; - - case CMD_UPDATE_TCP_BUFFER_SIZES: - mTcpBufferSizes = (String) msg.obj; - // This cannot possibly change provisioning state. - handleLinkPropertiesUpdate(SEND_CALLBACKS); - break; - - case CMD_UPDATE_HTTP_PROXY: - mHttpProxy = (ProxyInfo) msg.obj; - // This cannot possibly change provisioning state. - handleLinkPropertiesUpdate(SEND_CALLBACKS); - break; - - case CMD_SET_MULTICAST_FILTER: { - mMulticastFiltering = (boolean) msg.obj; - if (mApfFilter != null) { - mApfFilter.setMulticastFilter(mMulticastFiltering); - } else { - mCallback.setFallbackMulticastFilter(mMulticastFiltering); - } - break; - } - - case EVENT_READ_PACKET_FILTER_COMPLETE: { - if (mApfFilter != null) { - mApfFilter.setDataSnapshot((byte[]) msg.obj); - } - mApfDataSnapshotComplete.open(); - break; - } - - case EVENT_DHCPACTION_TIMEOUT: - stopDhcpAction(); - break; - - case DhcpClient.CMD_PRE_DHCP_ACTION: - if (mConfiguration.mRequestedPreDhcpActionMs > 0) { - ensureDhcpAction(); - } else { - sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE); - } - break; - - case DhcpClient.CMD_CLEAR_LINKADDRESS: - mInterfaceCtrl.clearIPv4Address(); - break; - - case DhcpClient.CMD_CONFIGURE_LINKADDRESS: { - final LinkAddress ipAddress = (LinkAddress) msg.obj; - if (mInterfaceCtrl.setIPv4Address(ipAddress)) { - mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED); - } else { - logError("Failed to set IPv4 address."); - dispatchCallback(PROV_CHANGE_LOST_PROVISIONING, - new LinkProperties(mLinkProperties)); - transitionTo(mStoppingState); - } - break; - } - - // This message is only received when: - // - // a) initial address acquisition succeeds, - // b) renew succeeds or is NAK'd, - // c) rebind succeeds or is NAK'd, or - // c) the lease expires, - // - // but never when initial address acquisition fails. The latter - // condition is now governed by the provisioning timeout. - case DhcpClient.CMD_POST_DHCP_ACTION: - stopDhcpAction(); - - switch (msg.arg1) { - case DhcpClient.DHCP_SUCCESS: - handleIPv4Success((DhcpResults) msg.obj); - break; - case DhcpClient.DHCP_FAILURE: - handleIPv4Failure(); - break; - default: - logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1); - } - break; - - case DhcpClient.CMD_ON_QUIT: - // DHCPv4 quit early for some reason. - logError("Unexpected CMD_ON_QUIT."); - mDhcpClient = null; - break; - - default: - return NOT_HANDLED; - } - - mMsgStateLogger.handled(this, getCurrentState()); - return HANDLED; - } - } - - private static class MessageHandlingLogger { - public String processedInState; - public String receivedInState; - - public void reset() { - processedInState = null; - receivedInState = null; - } - - public void handled(State processedIn, IState receivedIn) { - processedInState = processedIn.getClass().getSimpleName(); - receivedInState = receivedIn.getName(); - } - - public String toString() { - return String.format("rcvd_in=%s, proc_in=%s", - receivedInState, processedInState); - } - } - - private static void setNeighborParameters( - INetd netd, String ifName, int numSolicits, int interSolicitIntervalMs) - throws RemoteException, IllegalArgumentException { - Preconditions.checkNotNull(netd); - Preconditions.checkArgument(!TextUtils.isEmpty(ifName)); - Preconditions.checkArgument(numSolicits > 0); - Preconditions.checkArgument(interSolicitIntervalMs > 0); - - for (int family : new Integer[]{INetd.IPV4, INetd.IPV6}) { - netd.setProcSysNet(family, INetd.NEIGH, ifName, "retrans_time_ms", - Integer.toString(interSolicitIntervalMs)); - netd.setProcSysNet(family, INetd.NEIGH, ifName, "ucast_solicit", - Integer.toString(numSolicits)); - } - } - - // TODO: extract out into CollectionUtils. - static <T> boolean any(Iterable<T> coll, Predicate<T> fn) { - for (T t : coll) { - if (fn.test(t)) { - return true; - } - } - return false; - } - - static <T> boolean all(Iterable<T> coll, Predicate<T> fn) { - return !any(coll, not(fn)); - } - - static <T> Predicate<T> not(Predicate<T> fn) { - return (t) -> !fn.test(t); - } - - static <T> String join(String delimiter, Collection<T> coll) { - return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter)); - } - - static <T> T find(Iterable<T> coll, Predicate<T> fn) { - for (T t: coll) { - if (fn.test(t)) { - return t; - } - } - return null; - } - - static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) { - return coll.stream().filter(fn).collect(Collectors.toList()); - } } diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java index 0aec10149b23..2a2a67a92a86 100644 --- a/services/net/java/android/net/ip/IpClientUtil.java +++ b/services/net/java/android/net/ip/IpClientUtil.java @@ -16,8 +16,15 @@ package android.net.ip; +import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcelable; +import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable; + import android.content.Context; +import android.net.DhcpResultsParcelable; import android.net.LinkProperties; +import android.net.LinkPropertiesParcelable; +import android.net.NetworkStack; +import android.net.ip.IIpClientCallbacks; import android.os.ConditionVariable; import java.io.FileDescriptor; @@ -31,8 +38,8 @@ import java.io.PrintWriter; * @hide */ public class IpClientUtil { - // TODO: remove once IpClient dumps are moved to NetworkStack and callers don't need this arg - public static final String DUMP_ARG = IpClient.DUMP_ARG; + // TODO: remove with its callers + public static final String DUMP_ARG = "ipclient"; /** * Subclass of {@link IpClientCallbacks} allowing clients to block until provisioning is @@ -69,24 +76,129 @@ public class IpClientUtil { * * <p>This is a convenience method to allow clients to use {@link IpClientCallbacks} instead of * {@link IIpClientCallbacks}. + * @see {@link NetworkStack#makeIpClient(String, IIpClientCallbacks)} */ public static void makeIpClient(Context context, String ifName, IpClientCallbacks callback) { - // TODO: request IpClient asynchronously from NetworkStack. - final IpClient ipClient = new IpClient(context, ifName, callback); - callback.onIpClientCreated(ipClient.makeConnector()); + context.getSystemService(NetworkStack.class) + .makeIpClient(ifName, new IpClientCallbacksProxy(callback)); + } + + /** + * Create a new IpClient. + * + * <p>This is a convenience method to allow clients to use {@link IpClientCallbacksProxy} + * instead of {@link IIpClientCallbacks}. + * @see {@link NetworkStack#makeIpClient(String, IIpClientCallbacks)} + */ + public static void makeIpClient( + Context context, String ifName, IpClientCallbacksProxy callback) { + context.getSystemService(NetworkStack.class) + .makeIpClient(ifName, callback); + } + + /** + * Wrapper to relay calls from {@link IIpClientCallbacks} to {@link IpClientCallbacks}. + */ + public static class IpClientCallbacksProxy extends IIpClientCallbacks.Stub { + protected final IpClientCallbacks mCb; + + /** + * Create a new IpClientCallbacksProxy. + */ + public IpClientCallbacksProxy(IpClientCallbacks cb) { + mCb = cb; + } + + @Override + public void onIpClientCreated(IIpClient ipClient) { + mCb.onIpClientCreated(ipClient); + } + + @Override + public void onPreDhcpAction() { + mCb.onPreDhcpAction(); + } + + @Override + public void onPostDhcpAction() { + mCb.onPostDhcpAction(); + } + + // This is purely advisory and not an indication of provisioning + // success or failure. This is only here for callers that want to + // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress). + // DHCPv4 or static IPv4 configuration failure or success can be + // determined by whether or not the passed-in DhcpResults object is + // null or not. + @Override + public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) { + mCb.onNewDhcpResults(fromStableParcelable(dhcpResults)); + } + + @Override + public void onProvisioningSuccess(LinkPropertiesParcelable newLp) { + mCb.onProvisioningSuccess(fromStableParcelable(newLp)); + } + @Override + public void onProvisioningFailure(LinkPropertiesParcelable newLp) { + mCb.onProvisioningFailure(fromStableParcelable(newLp)); + } + + // Invoked on LinkProperties changes. + @Override + public void onLinkPropertiesChange(LinkPropertiesParcelable newLp) { + mCb.onLinkPropertiesChange(fromStableParcelable(newLp)); + } + + // Called when the internal IpReachabilityMonitor (if enabled) has + // detected the loss of a critical number of required neighbors. + @Override + public void onReachabilityLost(String logMsg) { + mCb.onReachabilityLost(logMsg); + } + + // Called when the IpClient state machine terminates. + @Override + public void onQuit() { + mCb.onQuit(); + } + + // Install an APF program to filter incoming packets. + @Override + public void installPacketFilter(byte[] filter) { + mCb.installPacketFilter(filter); + } + + // Asynchronously read back the APF program & data buffer from the wifi driver. + // Due to Wifi HAL limitations, the current implementation only supports dumping the entire + // buffer. In response to this request, the driver returns the data buffer asynchronously + // by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message. + @Override + public void startReadPacketFilter() { + mCb.startReadPacketFilter(); + } + + // If multicast filtering cannot be accomplished with APF, this function will be called to + // actuate multicast filtering using another means. + @Override + public void setFallbackMulticastFilter(boolean enabled) { + mCb.setFallbackMulticastFilter(enabled); + } + + // Enabled/disable Neighbor Discover offload functionality. This is + // called, for example, whenever 464xlat is being started or stopped. + @Override + public void setNeighborDiscoveryOffload(boolean enable) { + mCb.setNeighborDiscoveryOffload(enable); + } } /** * Dump logs for the specified IpClient. - * TODO: remove logging from this method once IpClient logs are dumped in NetworkStack dumpsys, - * then remove callers and delete. + * TODO: remove callers and delete */ public static void dumpIpClient( IIpClient connector, FileDescriptor fd, PrintWriter pw, String[] args) { - if (!(connector instanceof IpClient.IpClientConnector)) { - pw.println("Invalid connector"); - return; - } - ((IpClient.IpClientConnector) connector).dumpIpClientLogs(fd, pw, args); + pw.println("IpClient logs have moved to dumpsys network_stack"); } } diff --git a/services/net/java/android/net/ip/IpServer.java b/services/net/java/android/net/ip/IpServer.java index 7910c9a69310..f7360f52225f 100644 --- a/services/net/java/android/net/ip/IpServer.java +++ b/services/net/java/android/net/ip/IpServer.java @@ -40,7 +40,7 @@ import android.net.dhcp.IDhcpServer; import android.net.ip.RouterAdvertisementDaemon.RaParams; import android.net.util.InterfaceParams; import android.net.util.InterfaceSet; -import android.net.util.NetdService; +import android.net.shared.NetdService; import android.net.util.SharedLog; import android.os.INetworkManagementService; import android.os.Looper; diff --git a/services/net/java/android/net/util/NetdService.java b/services/net/java/android/net/shared/NetdService.java index 80b2c2705038..be0f5f2d4f34 100644 --- a/services/net/java/android/net/util/NetdService.java +++ b/services/net/java/android/net/shared/NetdService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.net.util; +package android.net.shared; import android.net.INetd; import android.os.RemoteException; diff --git a/services/net/java/android/net/util/InterfaceParams.java b/services/net/java/android/net/util/InterfaceParams.java index 7b060da01a89..f6bb87369cad 100644 --- a/services/net/java/android/net/util/InterfaceParams.java +++ b/services/net/java/android/net/util/InterfaceParams.java @@ -16,9 +16,6 @@ package android.net.util; -import static android.net.util.NetworkConstants.ETHER_MTU; -import static android.net.util.NetworkConstants.IPV6_MIN_MTU; - import static com.android.internal.util.Preconditions.checkArgument; import android.net.MacAddress; @@ -44,6 +41,11 @@ public class InterfaceParams { public final MacAddress macAddr; public final int defaultMtu; + // TODO: move the below to NetworkStackConstants when this class is moved to the NetworkStack. + private static final int ETHER_MTU = 1500; + private static final int IPV6_MIN_MTU = 1280; + + public static InterfaceParams getByName(String name) { final NetworkInterface netif = getNetworkInterfaceByName(name); if (netif == null) return null; diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java index c183b81362dc..ea5ce65f6f79 100644 --- a/services/net/java/android/net/util/NetworkConstants.java +++ b/services/net/java/android/net/util/NetworkConstants.java @@ -28,28 +28,6 @@ package android.net.util; public final class NetworkConstants { private NetworkConstants() { throw new RuntimeException("no instance permitted"); } - /** - * Ethernet constants. - * - * See also: - * - https://tools.ietf.org/html/rfc894 - * - https://tools.ietf.org/html/rfc2464 - * - https://tools.ietf.org/html/rfc7042 - * - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml - * - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml - */ - public static final int ETHER_DST_ADDR_OFFSET = 0; - public static final int ETHER_SRC_ADDR_OFFSET = 6; - public static final int ETHER_ADDR_LEN = 6; - - public static final int ETHER_TYPE_OFFSET = 12; - public static final int ETHER_TYPE_LENGTH = 2; - public static final int ETHER_TYPE_ARP = 0x0806; - public static final int ETHER_TYPE_IPV4 = 0x0800; - public static final int ETHER_TYPE_IPV6 = 0x86dd; - - public static final int ETHER_HEADER_LEN = 14; - public static final byte FF = asByte(0xff); public static final byte[] ETHER_ADDR_BROADCAST = { FF, FF, FF, FF, FF, FF @@ -58,34 +36,12 @@ public final class NetworkConstants { public static final int ETHER_MTU = 1500; /** - * ARP constants. - * - * See also: - * - https://tools.ietf.org/html/rfc826 - * - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml - */ - public static final int ARP_PAYLOAD_LEN = 28; // For Ethernet+IPv4. - public static final int ARP_REQUEST = 1; - public static final int ARP_REPLY = 2; - public static final int ARP_HWTYPE_RESERVED_LO = 0; - public static final int ARP_HWTYPE_ETHER = 1; - public static final int ARP_HWTYPE_RESERVED_HI = 0xffff; - - /** * IPv4 constants. * * See also: * - https://tools.ietf.org/html/rfc791 */ - public static final int IPV4_HEADER_MIN_LEN = 20; - public static final int IPV4_IHL_MASK = 0xf; - public static final int IPV4_FLAGS_OFFSET = 6; - public static final int IPV4_FRAGMENT_MASK = 0x1fff; - public static final int IPV4_PROTOCOL_OFFSET = 9; - public static final int IPV4_SRC_ADDR_OFFSET = 12; - public static final int IPV4_DST_ADDR_OFFSET = 16; public static final int IPV4_ADDR_BITS = 32; - public static final int IPV4_ADDR_LEN = 4; /** * IPv6 constants. @@ -93,15 +49,10 @@ public final class NetworkConstants { * See also: * - https://tools.ietf.org/html/rfc2460 */ - public static final int IPV6_HEADER_LEN = 40; - public static final int IPV6_PROTOCOL_OFFSET = 6; - public static final int IPV6_SRC_ADDR_OFFSET = 8; - public static final int IPV6_DST_ADDR_OFFSET = 24; public static final int IPV6_ADDR_BITS = 128; public static final int IPV6_ADDR_LEN = 16; public static final int IPV6_MIN_MTU = 1280; public static final int RFC7421_PREFIX_LENGTH = 64; - public static final int RFC6177_MIN_PREFIX_LENGTH = 48; /** * ICMP common (v4/v6) constants. @@ -124,45 +75,7 @@ public final class NetworkConstants { * - https://tools.ietf.org/html/rfc792 */ public static final int ICMPV4_ECHO_REQUEST_TYPE = 8; - - /** - * ICMPv6 constants. - * - * See also: - * - https://tools.ietf.org/html/rfc4443 - * - https://tools.ietf.org/html/rfc4861 - */ - public static final int ICMPV6_HEADER_MIN_LEN = 4; public static final int ICMPV6_ECHO_REQUEST_TYPE = 128; - public static final int ICMPV6_ECHO_REPLY_TYPE = 129; - public static final int ICMPV6_ROUTER_SOLICITATION = 133; - public static final int ICMPV6_ROUTER_ADVERTISEMENT = 134; - public static final int ICMPV6_NEIGHBOR_SOLICITATION = 135; - public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136; - - public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8; - public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8; - public static final int ICMPV6_ND_OPTION_SLLA = 1; - public static final int ICMPV6_ND_OPTION_TLLA = 2; - public static final int ICMPV6_ND_OPTION_MTU = 5; - - - /** - * UDP constants. - * - * See also: - * - https://tools.ietf.org/html/rfc768 - */ - public static final int UDP_HEADER_LEN = 8; - - /** - * DHCP(v4) constants. - * - * See also: - * - https://tools.ietf.org/html/rfc2131 - */ - public static final int DHCP4_SERVER_PORT = 67; - public static final int DHCP4_CLIENT_PORT = 68; /** * DNS constants. @@ -176,9 +89,4 @@ public final class NetworkConstants { * Utility functions. */ public static byte asByte(int i) { return (byte) i; } - - public static String asString(int i) { return Integer.toString(i); } - - public static int asUint(byte b) { return (b & 0xff); } - public static int asUint(short s) { return (s & 0xffff); } } diff --git a/tests/net/Android.mk b/tests/net/Android.mk index 685067377166..7e1b4008c473 100644 --- a/tests/net/Android.mk +++ b/tests/net/Android.mk @@ -32,74 +32,6 @@ LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_CERTIFICATE := platform -# These are not normally accessible from apps so they must be explicitly included. -LOCAL_JNI_SHARED_LIBRARIES := \ - android.hidl.token@1.0 \ - libartbase \ - libbacktrace \ - libbase \ - libbinder \ - libbinderthreadstate \ - libc++ \ - libcrypto \ - libcutils \ - libdexfile \ - libframeworksnettestsjni \ - libhidl-gen-utils \ - libhidlbase \ - libhidltransport \ - libhwbinder \ - liblog \ - liblzma \ - libnativehelper \ - libpackagelistparser \ - libpcre2 \ - libprocessgroup \ - libselinux \ - libui \ - libutils \ - libvintf \ - libvndksupport \ - libtinyxml2 \ - libunwindstack \ - libutilscallstack \ - libziparchive \ - libz \ - netd_aidl_interface-cpp - LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk include $(BUILD_PACKAGE) - -######################################################################### -# Build JNI Shared Library -######################################################################### - -LOCAL_PATH:= $(LOCAL_PATH)/jni - -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -LOCAL_CFLAGS := -Wall -Wextra -Werror - -LOCAL_C_INCLUDES := \ - libpcap \ - hardware/google/apf - -LOCAL_SRC_FILES := $(call all-cpp-files-under) - -LOCAL_SHARED_LIBRARIES := \ - libbinder \ - liblog \ - libcutils \ - libnativehelper \ - netd_aidl_interface-cpp - -LOCAL_STATIC_LIBRARIES := \ - libpcap \ - libapf - -LOCAL_MODULE := libframeworksnettestsjni - -include $(BUILD_SHARED_LIBRARY) |