summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/net/dhcp/DhcpClient.java410
-rw-r--r--src/android/net/dhcp/DhcpDeclinePacket.java8
-rw-r--r--src/android/net/dhcp/DhcpPacket.java48
-rw-r--r--src/android/net/util/NetworkStackUtils.java15
-rw-r--r--src/com/android/networkstack/arp/ArpPacket.java171
-rw-r--r--src/com/android/server/util/NetworkStackConstants.java23
6 files changed, 627 insertions, 48 deletions
diff --git a/src/android/net/dhcp/DhcpClient.java b/src/android/net/dhcp/DhcpClient.java
index 68f7ab0..e7d53ee 100644
--- a/src/android/net/dhcp/DhcpClient.java
+++ b/src/android/net/dhcp/DhcpClient.java
@@ -29,11 +29,15 @@ 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.dhcp.DhcpPacket.INFINITE_LEASE;
+import static android.net.util.NetworkStackUtils.DHCP_INIT_REBOOT_VERSION;
+import static android.net.util.NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION;
+import static android.net.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION;
import static android.net.util.NetworkStackUtils.closeSocketQuietly;
import static android.net.util.SocketUtils.makePacketSocketAddress;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static android.system.OsConstants.AF_INET;
import static android.system.OsConstants.AF_PACKET;
+import static android.system.OsConstants.ETH_P_ARP;
import static android.system.OsConstants.ETH_P_IP;
import static android.system.OsConstants.IPPROTO_UDP;
import static android.system.OsConstants.SOCK_DGRAM;
@@ -44,7 +48,11 @@ import static android.system.OsConstants.SO_BROADCAST;
import static android.system.OsConstants.SO_RCVBUF;
import static android.system.OsConstants.SO_REUSEADDR;
+import static com.android.server.util.NetworkStackConstants.ARP_REQUEST;
+import static com.android.server.util.NetworkStackConstants.ETHER_ADDR_LEN;
import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_ANY;
+import static com.android.server.util.NetworkStackConstants.IPV4_CONFLICT_ANNOUNCE_NUM;
+import static com.android.server.util.NetworkStackConstants.IPV4_CONFLICT_PROBE_NUM;
import android.content.Context;
import android.net.DhcpResults;
@@ -66,6 +74,7 @@ import android.net.util.PacketReader;
import android.net.util.SocketUtils;
import android.os.Handler;
import android.os.Message;
+import android.os.PowerManager;
import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.Os;
@@ -85,6 +94,7 @@ import com.android.internal.util.TrafficStatsConstants;
import com.android.internal.util.WakeupMessage;
import com.android.networkstack.R;
import com.android.networkstack.apishim.SocketUtilsShimImpl;
+import com.android.networkstack.arp.ArpPacket;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -122,6 +132,7 @@ public class DhcpClient extends StateMachine {
private static final String TAG = "DhcpClient";
private static final boolean DBG = true;
+ private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
private static final boolean STATE_DBG = Log.isLoggable(TAG, Log.DEBUG);
private static final boolean MSG_DBG = Log.isLoggable(TAG, Log.DEBUG);
private static final boolean PACKET_DBG = Log.isLoggable(TAG, Log.DEBUG);
@@ -138,6 +149,43 @@ public class DhcpClient extends StateMachine {
private static final int MAX_TIMEOUT_MS = 128 * SECONDS;
private static final int IPMEMORYSTORE_TIMEOUT_MS = 1 * SECONDS;
+ // The waiting time to restart the DHCP configuration process after broadcasting a
+ // DHCPDECLINE message, (RFC2131 3.1.5 describes client SHOULD wait a minimum of 10
+ // seconds to avoid excessive traffic, but it's too long).
+ @VisibleForTesting
+ public static final String DHCP_RESTART_CONFIG_DELAY = "dhcp_restart_configuration_delay";
+ private static final int DEFAULT_DHCP_RESTART_CONFIG_DELAY_SEC = 1 * SECONDS;
+
+ // Initial random delay before sending first ARP probe.
+ @VisibleForTesting
+ public static final String ARP_FIRST_PROBE_DELAY_MS = "arp_first_probe_delay";
+ private static final int DEFAULT_ARP_FIRST_PROBE_DELAY_MS = 100;
+
+ // Minimum delay until retransmitting the probe. The probe will be retransmitted after a
+ // random number of milliseconds in the range ARP_PROBE_MIN_MS and ARP_PROBE_MAX_MS.
+ @VisibleForTesting
+ public static final String ARP_PROBE_MIN_MS = "arp_probe_min";
+ private static final int DEFAULT_ARP_PROBE_MIN_MS = 100;
+
+ // Maximum delay until retransmitting the probe.
+ @VisibleForTesting
+ public static final String ARP_PROBE_MAX_MS = "arp_probe_max";
+ private static final int DEFAULT_ARP_PROBE_MAX_MS = 300;
+
+ // Initial random delay before sending first ARP Announcement after completing Probe packet
+ // transmission.
+ @VisibleForTesting
+ public static final String ARP_FIRST_ANNOUNCE_DELAY_MS = "arp_first_announce_delay";
+ private static final int DEFAULT_ARP_FIRST_ANNOUNCE_DELAY_MS = 100;
+
+ // Time between retransmitting ARP Announcement packets.
+ @VisibleForTesting
+ public static final String ARP_ANNOUNCE_INTERVAL_MS = "arp_announce_interval";
+ private static final int DEFAULT_ARP_ANNOUNCE_INTERVAL_MS = 100;
+
+ // Max conflict count before configuring interface with declined IP address anyway.
+ private static final int MAX_CONFLICTS_COUNT = 2;
+
// 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
@@ -190,6 +238,9 @@ public class DhcpClient extends StateMachine {
private static final int EVENT_CONFIGURATION_TIMEOUT = PRIVATE_BASE + 7;
private static final int EVENT_CONFIGURATION_OBTAINED = PRIVATE_BASE + 8;
private static final int EVENT_CONFIGURATION_INVALID = PRIVATE_BASE + 9;
+ private static final int EVENT_IP_CONFLICT = PRIVATE_BASE + 10;
+ private static final int CMD_ARP_PROBE = PRIVATE_BASE + 11;
+ private static final int CMD_ARP_ANNOUNCEMENT = PRIVATE_BASE + 12;
// constant to represent this DHCP lease has been expired.
@VisibleForTesting
@@ -247,6 +298,7 @@ public class DhcpClient extends StateMachine {
private DhcpResults mOffer;
private Configuration mConfiguration;
private Inet4Address mLastAssignedIpv4Address;
+ private int mConflictCount;
private long mLastAssignedIpv4AddressExpiry;
private Dependencies mDependencies;
@NonNull
@@ -277,6 +329,8 @@ public class DhcpClient extends StateMachine {
private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(mDhcpRenewingState);
private State mWaitBeforeObtainingConfigurationState =
new WaitBeforeObtainingConfigurationState(mObtainingConfigurationState);
+ private State mIpAddressConflictDetectingState = new IpAddressConflictDetectingState();
+ private State mDhcpDecliningState = new DhcpDecliningState();
private WakeupMessage makeWakeupMessage(String cmdName, int cmd) {
cmdName = DhcpClient.class.getSimpleName() + "." + mIfaceName + "." + cmdName;
@@ -302,11 +356,51 @@ public class DhcpClient extends StateMachine {
}
/**
- * Get the value of DHCP related experiment flags.
+ * Return whether a feature guarded by a feature flag is enabled.
+ * @see NetworkStackUtils#isFeatureEnabled(Context, String, String)
*/
- public boolean getBooleanDeviceConfig(final String nameSpace, final String flagName) {
- return NetworkStackUtils.getDeviceConfigPropertyBoolean(nameSpace, flagName,
- false /* default value */);
+ public boolean isFeatureEnabled(final Context context, final String name) {
+ return NetworkStackUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name);
+ }
+
+ /**
+ * Get the Integer value of relevant DeviceConfig properties of Connectivity namespace.
+ */
+ public int getIntDeviceConfig(final String name) {
+ final int defaultValue;
+ switch (name) {
+ case DHCP_RESTART_CONFIG_DELAY:
+ defaultValue = DEFAULT_DHCP_RESTART_CONFIG_DELAY_SEC;
+ break;
+ case ARP_FIRST_PROBE_DELAY_MS:
+ defaultValue = DEFAULT_ARP_FIRST_PROBE_DELAY_MS;
+ break;
+ case ARP_PROBE_MIN_MS:
+ defaultValue = DEFAULT_ARP_PROBE_MIN_MS;
+ break;
+ case ARP_PROBE_MAX_MS:
+ defaultValue = DEFAULT_ARP_PROBE_MAX_MS;
+ break;
+ case ARP_FIRST_ANNOUNCE_DELAY_MS:
+ defaultValue = DEFAULT_ARP_FIRST_ANNOUNCE_DELAY_MS;
+ break;
+ case ARP_ANNOUNCE_INTERVAL_MS:
+ defaultValue = DEFAULT_ARP_ANNOUNCE_INTERVAL_MS;
+ break;
+ default:
+ Log.e(TAG, "Invalid experiment flag: " + name);
+ return 0;
+ }
+ return NetworkStackUtils.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, name,
+ defaultValue);
+ }
+
+ /**
+ * Get a new wake lock to force CPU keeping awake when transmitting packets or waiting
+ * for timeout.
+ */
+ public PowerManager.WakeLock getWakeLock(final PowerManager powerManager) {
+ return powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
}
}
@@ -331,12 +425,14 @@ public class DhcpClient extends StateMachine {
addState(mObtainingConfigurationState, mDhcpState);
addState(mDhcpSelectingState, mDhcpState);
addState(mDhcpRequestingState, mDhcpState);
+ addState(mIpAddressConflictDetectingState, mDhcpState);
addState(mDhcpHaveLeaseState, mDhcpState);
addState(mConfiguringInterfaceState, mDhcpHaveLeaseState);
addState(mDhcpBoundState, mDhcpHaveLeaseState);
addState(mWaitBeforeRenewalState, mDhcpHaveLeaseState);
addState(mDhcpRenewingState, mDhcpHaveLeaseState);
addState(mDhcpRebindingState, mDhcpHaveLeaseState);
+ addState(mDhcpDecliningState, mDhcpHaveLeaseState);
addState(mDhcpInitRebootState, mDhcpState);
addState(mDhcpRebootingState, mDhcpState);
// CHECKSTYLE:ON IndentationCheck
@@ -370,19 +466,23 @@ public class DhcpClient extends StateMachine {
/**
* check whether or not to support caching the last lease info and INIT-REBOOT state.
- *
*/
public boolean isDhcpLeaseCacheEnabled() {
- return mDependencies.getBooleanDeviceConfig(NAMESPACE_CONNECTIVITY,
- NetworkStackUtils.DHCP_INIT_REBOOT_ENABLED);
+ return mDependencies.isFeatureEnabled(mContext, DHCP_INIT_REBOOT_VERSION);
}
/**
* check whether or not to support DHCP Rapid Commit option.
*/
public boolean isDhcpRapidCommitEnabled() {
- return mDependencies.getBooleanDeviceConfig(NAMESPACE_CONNECTIVITY,
- NetworkStackUtils.DHCP_RAPID_COMMIT_ENABLED);
+ return mDependencies.isFeatureEnabled(mContext, DHCP_RAPID_COMMIT_VERSION);
+ }
+
+ /**
+ * check whether or not to support IP address conflict detection and DHCPDECLINE.
+ */
+ public boolean isDhcpIpConflictDetectEnabled() {
+ return mDependencies.isFeatureEnabled(mContext, DHCP_IP_CONFLICT_DETECT_VERSION);
}
private void confirmDhcpLease(DhcpPacket packet, DhcpResults results) {
@@ -500,7 +600,8 @@ public class DhcpClient extends StateMachine {
public int transmitPacket(final ByteBuffer buf, final SocketAddress socketAddress)
throws ErrnoException, SocketException {
- return Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, socketAddress);
+ return Os.sendto(mPacketSock, buf.array(), 0 /* byteOffset */,
+ buf.limit() /* byteCount */, 0 /* flags */, socketAddress);
}
}
@@ -537,20 +638,20 @@ public class DhcpClient extends StateMachine {
}
private boolean sendDiscoverPacket() {
- ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
+ final ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr,
DO_UNICAST, REQUESTED_PARAMS, isDhcpRapidCommitEnabled());
return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST);
}
private boolean sendRequestPacket(
- Inet4Address clientAddress, Inet4Address requestedAddress,
- Inet4Address serverAddress, Inet4Address to) {
+ final Inet4Address clientAddress, final Inet4Address requestedAddress,
+ final Inet4Address serverAddress, final 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(
+ final ByteBuffer packet = DhcpPacket.buildRequestPacket(
encap, mTransactionId, getSecs(), clientAddress,
DO_UNICAST, mHwAddr, requestedAddress,
serverAddress, REQUESTED_PARAMS, null);
@@ -561,6 +662,14 @@ public class DhcpClient extends StateMachine {
return transmitPacket(packet, description, encap, to);
}
+ private boolean sendDeclinePacket(final Inet4Address requestedAddress,
+ final Inet4Address serverIdentifier) {
+ // Requested IP address and Server Identifier options are mandatory for DHCPDECLINE.
+ final ByteBuffer packet = DhcpPacket.buildDeclinePacket(DhcpPacket.ENCAP_L2,
+ mTransactionId, mHwAddr, requestedAddress, serverIdentifier);
+ return transmitPacket(packet, "DHCPDECLINE", DhcpPacket.ENCAP_L2, INADDR_BROADCAST);
+ }
+
private void scheduleLeaseTimers() {
if (mDhcpLeaseExpiry == 0) {
Log.d(TAG, "Infinite lease, no timer scheduling needed");
@@ -814,6 +923,7 @@ public class DhcpClient extends StateMachine {
@Override
public void enter() {
clearDhcpState();
+ mConflictCount = 0;
if (initInterface() && initUdpSocket()) {
mDhcpPacketHandler = new DhcpPacketHandler(getHandler());
if (mDhcpPacketHandler.start()) return;
@@ -1033,6 +1143,7 @@ public class DhcpClient extends StateMachine {
@Override
public void exit() {
+ super.exit();
removeMessages(EVENT_CONFIGURATION_INVALID);
removeMessages(EVENT_CONFIGURATION_TIMEOUT);
removeMessages(EVENT_CONFIGURATION_OBTAINED);
@@ -1189,7 +1300,8 @@ public class DhcpClient extends StateMachine {
DhcpResults results = packet.toDhcpResults();
if (results != null) {
confirmDhcpLease(packet, results);
- transitionTo(mConfiguringInterfaceState);
+ transitionTo(isDhcpIpConflictDetectEnabled()
+ ? mIpAddressConflictDetectingState : mConfiguringInterfaceState);
}
} else if (packet instanceof DhcpNakPacket) {
// TODO: Wait a while before returning into INIT state.
@@ -1254,6 +1366,238 @@ public class DhcpClient extends StateMachine {
}
}
+ private class IpConflictDetector extends PacketReader {
+ private FileDescriptor mArpSock;
+ private final Inet4Address mTargetIp;
+
+ IpConflictDetector(@NonNull Handler handler, @NonNull Inet4Address ipAddress) {
+ super(handler);
+ mTargetIp = ipAddress;
+ }
+
+ @Override
+ protected void handlePacket(byte[] recvbuf, int length) {
+ try {
+ final ArpPacket packet = ArpPacket.parseArpPacket(recvbuf, length);
+ if (hasIpAddressConflict(packet, mTargetIp)) {
+ sendMessage(EVENT_IP_CONFLICT);
+ }
+ } catch (ArpPacket.ParseException e) {
+ logError("Can't parse ARP packet", e);
+ }
+ }
+
+ @Override
+ protected FileDescriptor createFd() {
+ try {
+ // TODO: attach ARP packet only filter.
+ mArpSock = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0 /* protocol */);
+ SocketAddress addr = makePacketSocketAddress(ETH_P_ARP, mIface.index);
+ Os.bind(mArpSock, addr);
+ return mArpSock;
+ } catch (SocketException | ErrnoException e) {
+ logError("Error creating ARP socket", e);
+ closeFd(mArpSock);
+ mArpSock = null;
+ return null;
+ }
+ }
+
+ @Override
+ protected void logError(@NonNull String msg, @NonNull Exception e) {
+ Log.e(TAG, msg, e);
+ }
+
+ public boolean transmitPacket(@NonNull Inet4Address targetIp,
+ @NonNull Inet4Address senderIp, final byte[] hwAddr,
+ @NonNull SocketAddress sockAddr) {
+ // RFC5227 3. describes both ARP Probes and Announcements use ARP Request packet.
+ final ByteBuffer packet = ArpPacket.buildArpPacket(DhcpPacket.ETHER_BROADCAST, hwAddr,
+ targetIp.getAddress(), new byte[ETHER_ADDR_LEN], senderIp.getAddress(),
+ (short) ARP_REQUEST);
+ try {
+ Os.sendto(mArpSock, packet.array(), 0 /* byteOffset */,
+ packet.limit() /* byteCount */, 0 /* flags */, sockAddr);
+ return true;
+ } catch (ErrnoException | SocketException e) {
+ logError("Can't send ARP packet", e);
+ return false;
+ }
+ }
+ }
+
+ private boolean isArpProbe(@NonNull ArpPacket packet) {
+ return (packet.opCode == ARP_REQUEST && packet.senderIp.equals(INADDR_ANY)
+ && !packet.targetIp.equals(INADDR_ANY));
+ }
+
+ // RFC5227 2.1.1 says, during probing period:
+ // 1. the host receives any ARP packet (Request *or* Reply) on the interface where the
+ // probe is being performed, where the packet's 'sender IP address' is the address
+ // being probed for, then the host MUST treat this address as conflict.
+ // 2. the host receives any ARP Probe where the packet's 'target IP address' is the
+ // address being probed for, and the packet's 'sender hardware address' is not the
+ // hardware address of any of the host's interfaces, then the host SHOULD similarly
+ // treat this as an address conflict.
+ private boolean checkArpSenderIpOrTargetIp(@NonNull ArpPacket packet,
+ @NonNull Inet4Address targetIp) {
+ return ((!packet.senderIp.equals(INADDR_ANY) && packet.senderIp.equals(targetIp))
+ || (isArpProbe(packet) && packet.targetIp.equals(targetIp)));
+ }
+
+ private boolean hasIpAddressConflict(@NonNull ArpPacket packet,
+ @NonNull Inet4Address targetIp) {
+ if (checkArpSenderIpOrTargetIp(packet, targetIp)
+ && !Arrays.equals(packet.senderHwAddress.toByteArray(), mHwAddr)) {
+ if (DBG) {
+ final String senderIpString = packet.senderIp.getHostAddress();
+ final String targetIpString = packet.targetIp.getHostAddress();
+ final MacAddress senderMacAddress = packet.senderHwAddress;
+ final MacAddress hostMacAddress = MacAddress.fromBytes(mHwAddr);
+ Log.d(TAG, "IP address conflict detected:"
+ + (packet.opCode == ARP_REQUEST ? "ARP Request" : "ARP Reply")
+ + " ARP sender MAC: " + senderMacAddress.toString()
+ + " host MAC: " + hostMacAddress.toString()
+ + " ARP sender IP: " + senderIpString
+ + " ARP target IP: " + targetIpString
+ + " host target IP: " + targetIp.getHostAddress());
+ }
+ return true;
+ }
+ return false;
+ }
+
+ class IpAddressConflictDetectingState extends LoggingState {
+ private int mArpProbeCount;
+ private int mArpAnnounceCount;
+ private Inet4Address mTargetIp;
+ private IpConflictDetector mIpConflictDetector;
+ private PowerManager.WakeLock mTimeoutWakeLock;
+
+ private int mArpFirstProbeDelayMs;
+ private int mArpProbeMaxDelayMs;
+ private int mArpProbeMinDelayMs;
+ private int mArpFirstAnnounceDelayMs;
+ private int mArpAnnounceIntervalMs;
+
+ @Override
+ public void enter() {
+ super.enter();
+
+ mArpProbeCount = 0;
+ mArpAnnounceCount = 0;
+
+ // IP address conflict detection occurs after receiving DHCPACK
+ // message every time, i.e. we already get an available lease from
+ // DHCP server, that ensures mDhcpLease should be NonNull, see
+ // {@link DhcpRequestingState#receivePacket} for details.
+ mTargetIp = (Inet4Address) mDhcpLease.ipAddress.getAddress();
+ mIpConflictDetector = new IpConflictDetector(getHandler(), mTargetIp);
+
+ // IpConflictDetector might fail to create the raw socket.
+ if (!mIpConflictDetector.start()) {
+ Log.e(TAG, "Fail to start IP Conflict Detector");
+ transitionTo(mConfiguringInterfaceState);
+ return;
+ }
+
+ // Read the customized parameters from DeviceConfig.
+ mArpFirstProbeDelayMs = mDependencies.getIntDeviceConfig(ARP_FIRST_PROBE_DELAY_MS);
+ mArpProbeMaxDelayMs = mDependencies.getIntDeviceConfig(ARP_PROBE_MAX_MS);
+ mArpProbeMinDelayMs = mDependencies.getIntDeviceConfig(ARP_PROBE_MIN_MS);
+ mArpAnnounceIntervalMs = mDependencies.getIntDeviceConfig(ARP_ANNOUNCE_INTERVAL_MS);
+ mArpFirstAnnounceDelayMs = mDependencies.getIntDeviceConfig(
+ ARP_FIRST_ANNOUNCE_DELAY_MS);
+
+ if (VDBG) {
+ Log.d(TAG, "ARP First Probe delay: " + mArpFirstProbeDelayMs
+ + " ARP Probe Max delay: " + mArpProbeMaxDelayMs
+ + " ARP Probe Min delay: " + mArpProbeMinDelayMs
+ + " ARP First Announce delay: " + mArpFirstAnnounceDelayMs
+ + " ARP Announce interval: " + mArpAnnounceIntervalMs);
+ }
+
+ // Note that when we get here, we're still processing the WakeupMessage that caused
+ // us to transition into this state, and thus the AlarmManager is still holding its
+ // wakelock. That wakelock might expire as soon as this method returns.
+ final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
+ mTimeoutWakeLock = mDependencies.getWakeLock(powerManager);
+ mTimeoutWakeLock.acquire();
+
+ // RFC5227 2.1.1 describes waiting for a random time interval between 0 and
+ // PROBE_WAIT seconds before sending probe packets PROBE_NUM times, this delay
+ // helps avoid hosts send initial probe packet simultaneously upon power on.
+ // Probe packet transmission interval spaces randomly and uniformly between
+ // PROBE_MIN and PROBE_MAX.
+ sendMessageDelayed(CMD_ARP_PROBE, mRandom.nextInt(mArpFirstProbeDelayMs));
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ super.processMessage(message);
+ switch (message.what) {
+ case CMD_ARP_PROBE:
+ // According to RFC5227, wait ANNOUNCE_WAIT seconds after
+ // the last ARP Probe, and no conflicting ARP Reply or ARP
+ // Probe has been received within this period, then host can
+ // determine the desired IP address may be used safely.
+ sendArpProbe();
+ if (++mArpProbeCount < IPV4_CONFLICT_PROBE_NUM) {
+ scheduleProbe();
+ } else {
+ scheduleAnnounce(mArpFirstAnnounceDelayMs);
+ }
+ return HANDLED;
+ case CMD_ARP_ANNOUNCEMENT:
+ sendArpAnnounce();
+ if (++mArpAnnounceCount < IPV4_CONFLICT_ANNOUNCE_NUM) {
+ scheduleAnnounce(mArpAnnounceIntervalMs);
+ } else {
+ transitionTo(mConfiguringInterfaceState);
+ }
+ return HANDLED;
+ case EVENT_IP_CONFLICT:
+ transitionTo(mDhcpDecliningState);
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+
+ // Because the timing parameters used in IP Address detection mechanism are in
+ // milliseconds, WakeupMessage would be too imprecise for small timeouts.
+ private void scheduleProbe() {
+ long timeout = mRandom.nextInt(mArpProbeMaxDelayMs - mArpProbeMinDelayMs)
+ + mArpProbeMinDelayMs;
+ sendMessageDelayed(CMD_ARP_PROBE, timeout);
+ }
+
+ private void scheduleAnnounce(final int timeout) {
+ sendMessageDelayed(CMD_ARP_ANNOUNCEMENT, timeout);
+ }
+
+ @Override
+ public void exit() {
+ super.exit();
+ mTimeoutWakeLock.release();
+ mIpConflictDetector.stop();
+ if (DBG) Log.d(TAG, "IP Conflict Detector stopped");
+ removeMessages(CMD_ARP_PROBE);
+ removeMessages(CMD_ARP_ANNOUNCEMENT);
+ removeMessages(EVENT_IP_CONFLICT);
+ }
+
+ private boolean sendArpProbe() {
+ return mIpConflictDetector.transmitPacket(mTargetIp /* target IP */,
+ INADDR_ANY /* sender IP */, mHwAddr, mInterfaceBroadcastAddr);
+ }
+
+ private boolean sendArpAnnounce() {
+ return mIpConflictDetector.transmitPacket(mTargetIp /* target IP */,
+ mTargetIp /* sender IP */, mHwAddr, mInterfaceBroadcastAddr);
+ }
+ }
+
class DhcpBoundState extends LoggingState {
@Override
public void enter() {
@@ -1421,10 +1765,10 @@ public class DhcpClient extends StateMachine {
@Override
protected boolean sendPacket() {
return sendRequestPacket(
- INADDR_ANY, // ciaddr
- mLastAssignedIpv4Address, // DHCP_REQUESTED_IP
- null, // DHCP_SERVER_IDENTIFIER
- INADDR_BROADCAST); // packet destination address
+ INADDR_ANY, // ciaddr
+ mLastAssignedIpv4Address, // DHCP_REQUESTED_IP
+ null, // DHCP_SERVER_IDENTIFIER
+ INADDR_BROADCAST); // packet destination address
}
@Override
@@ -1437,6 +1781,34 @@ public class DhcpClient extends StateMachine {
class DhcpRebootingState extends LoggingState {
}
+ class DhcpDecliningState extends TimeoutState {
+ @Override
+ public void enter() {
+ // If the host experiences MAX_CONFLICTS or more address conflicts on the
+ // interface, configure interface with this IP address anyway.
+ if (++mConflictCount > MAX_CONFLICTS_COUNT) {
+ transitionTo(mConfiguringInterfaceState);
+ return;
+ }
+
+ mTimeout = mDependencies.getIntDeviceConfig(DHCP_RESTART_CONFIG_DELAY);
+ super.enter();
+ sendPacket();
+ }
+
+ // No need to override processMessage here since this state is
+ // functionally identical to its superclass TimeoutState.
+ protected void timeout() {
+ transitionTo(mDhcpInitState);
+ }
+
+ private boolean sendPacket() {
+ return sendDeclinePacket(
+ (Inet4Address) mDhcpLease.ipAddress.getAddress(), // requested IP
+ (Inet4Address) mDhcpLease.serverAddress); // serverIdentifier
+ }
+ }
+
private void logState(String name, int durationMs) {
final DhcpClientEvent event = new DhcpClientEvent.Builder()
.setMsg(name)
diff --git a/src/android/net/dhcp/DhcpDeclinePacket.java b/src/android/net/dhcp/DhcpDeclinePacket.java
index 2f4f0f6..14a27db 100644
--- a/src/android/net/dhcp/DhcpDeclinePacket.java
+++ b/src/android/net/dhcp/DhcpDeclinePacket.java
@@ -27,9 +27,11 @@ public class DhcpDeclinePacket extends DhcpPacket {
* Generates a DECLINE packet with the specified parameters.
*/
DhcpDeclinePacket(int transId, short secs, Inet4Address clientIp, Inet4Address yourIp,
- Inet4Address nextIp, Inet4Address relayIp,
- byte[] clientMac) {
+ Inet4Address nextIp, Inet4Address relayIp, byte[] clientMac,
+ Inet4Address requestedIpAddress, Inet4Address serverIdentifier) {
super(transId, secs, clientIp, yourIp, nextIp, relayIp, clientMac, false);
+ mRequestedIp = requestedIpAddress;
+ mServerIdentifier = serverIdentifier;
}
public String toString() {
@@ -55,6 +57,8 @@ public class DhcpDeclinePacket extends DhcpPacket {
void finishPacket(ByteBuffer buffer) {
addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_DECLINE);
addTlv(buffer, DHCP_CLIENT_IDENTIFIER, getClientId());
+ addTlv(buffer, DHCP_REQUESTED_IP, mRequestedIp);
+ addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier);
// RFC 2131 says we MUST NOT include our common client TLVs or the parameter request list.
addTlvEnd(buffer);
}
diff --git a/src/android/net/dhcp/DhcpPacket.java b/src/android/net/dhcp/DhcpPacket.java
index efce6a5..5ab769b 100644
--- a/src/android/net/dhcp/DhcpPacket.java
+++ b/src/android/net/dhcp/DhcpPacket.java
@@ -210,7 +210,8 @@ public abstract class DhcpPacket {
* DHCP Optional Type: DHCP Requested IP Address
*/
protected static final byte DHCP_REQUESTED_IP = 50;
- protected Inet4Address mRequestedIp;
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ public Inet4Address mRequestedIp;
/**
* DHCP Optional Type: DHCP Lease Time
@@ -236,7 +237,8 @@ public abstract class DhcpPacket {
* DHCP Optional Type: DHCP Server Identifier
*/
protected static final byte DHCP_SERVER_IDENTIFIER = 54;
- protected Inet4Address mServerIdentifier;
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ public Inet4Address mServerIdentifier;
/**
* DHCP Optional Type: DHCP Parameter List
@@ -1189,8 +1191,8 @@ public abstract class DhcpPacket {
break;
case DHCP_MESSAGE_TYPE_DECLINE:
newPacket = new DhcpDeclinePacket(
- transactionId, secs, clientIp, yourIp, nextIp, relayIp,
- clientMac);
+ transactionId, secs, clientIp, yourIp, nextIp, relayIp, clientMac, requestedIp,
+ serverIdentifier);
break;
case DHCP_MESSAGE_TYPE_ACK:
newPacket = new DhcpAckPacket(
@@ -1339,11 +1341,11 @@ public abstract class DhcpPacket {
* parameters.
*/
public static ByteBuffer buildOfferPacket(int encap, int transactionId,
- boolean broadcast, Inet4Address serverIpAddr, Inet4Address relayIp,
- Inet4Address yourIp, byte[] mac, Integer timeout, Inet4Address netMask,
- Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers,
- Inet4Address dhcpServerIdentifier, String domainName, String hostname, boolean metered,
- short mtu) {
+ boolean broadcast, Inet4Address serverIpAddr, Inet4Address relayIp,
+ Inet4Address yourIp, byte[] mac, Integer timeout, Inet4Address netMask,
+ Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers,
+ Inet4Address dhcpServerIdentifier, String domainName, String hostname, boolean metered,
+ short mtu) {
DhcpPacket pkt = new DhcpOfferPacket(
transactionId, (short) 0, broadcast, serverIpAddr, relayIp,
INADDR_ANY /* clientIp */, yourIp, mac);
@@ -1366,11 +1368,11 @@ public abstract class DhcpPacket {
* Builds a DHCP-ACK packet from the required specified parameters.
*/
public static ByteBuffer buildAckPacket(int encap, int transactionId,
- boolean broadcast, Inet4Address serverIpAddr, Inet4Address relayIp, Inet4Address yourIp,
- Inet4Address requestClientIp, byte[] mac, Integer timeout, Inet4Address netMask,
- Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers,
- Inet4Address dhcpServerIdentifier, String domainName, String hostname, boolean metered,
- short mtu) {
+ boolean broadcast, Inet4Address serverIpAddr, Inet4Address relayIp, Inet4Address yourIp,
+ Inet4Address requestClientIp, byte[] mac, Integer timeout, Inet4Address netMask,
+ Inet4Address bcAddr, List<Inet4Address> gateways, List<Inet4Address> dnsServers,
+ Inet4Address dhcpServerIdentifier, String domainName, String hostname, boolean metered,
+ short mtu) {
DhcpPacket pkt = new DhcpAckPacket(
transactionId, (short) 0, broadcast, serverIpAddr, relayIp, requestClientIp, yourIp,
mac);
@@ -1405,9 +1407,9 @@ public abstract class DhcpPacket {
* Builds a DHCP-REQUEST packet from the required specified parameters.
*/
public static ByteBuffer buildRequestPacket(int encap,
- int transactionId, short secs, Inet4Address clientIp, boolean broadcast,
- byte[] clientMac, Inet4Address requestedIpAddress,
- Inet4Address serverIdentifier, byte[] requestedParams, String hostName) {
+ int transactionId, short secs, Inet4Address clientIp, boolean broadcast,
+ byte[] clientMac, Inet4Address requestedIpAddress,
+ Inet4Address serverIdentifier, byte[] requestedParams, String hostName) {
DhcpPacket pkt = new DhcpRequestPacket(transactionId, secs, clientIp,
INADDR_ANY /* relayIp */, clientMac, broadcast);
pkt.mRequestedIp = requestedIpAddress;
@@ -1417,4 +1419,16 @@ public abstract class DhcpPacket {
ByteBuffer result = pkt.buildPacket(encap, DHCP_SERVER, DHCP_CLIENT);
return result;
}
+
+ /**
+ * Builds a DHCP-DECLINE packet from the required specified parameters.
+ */
+ public static ByteBuffer buildDeclinePacket(int encap, int transactionId, byte[] clientMac,
+ Inet4Address requestedIpAddress, Inet4Address serverIdentifier) {
+ DhcpPacket pkt = new DhcpDeclinePacket(transactionId, (short) 0 /* secs */,
+ INADDR_ANY /* clientIp */, INADDR_ANY /* yourIp */, INADDR_ANY /* nextIp */,
+ INADDR_ANY /* relayIp */, clientMac, requestedIpAddress, serverIdentifier);
+ ByteBuffer result = pkt.buildPacket(encap, DHCP_SERVER, DHCP_CLIENT);
+ return result;
+ }
}
diff --git a/src/android/net/util/NetworkStackUtils.java b/src/android/net/util/NetworkStackUtils.java
index b2deefd..993f3cb 100644
--- a/src/android/net/util/NetworkStackUtils.java
+++ b/src/android/net/util/NetworkStackUtils.java
@@ -121,6 +121,21 @@ public class NetworkStackUtils {
*/
public static final String DHCP_RAPID_COMMIT_ENABLED = "dhcp_rapid_commit_enabled";
+ /**
+ * Minimum module version at which to enable the DHCP INIT-REBOOT state.
+ */
+ public static final String DHCP_INIT_REBOOT_VERSION = "dhcp_init_reboot_version";
+
+ /**
+ * Minimum module version at which to enable the DHCP Rapid Commit option.
+ */
+ public static final String DHCP_RAPID_COMMIT_VERSION = "dhcp_rapid_commit_version";
+
+ /**
+ * Minimum module version at which to enable the IP address conflict detection feature.
+ */
+ public static final String DHCP_IP_CONFLICT_DETECT_VERSION = "dhcp_ip_conflict_detect_version";
+
static {
System.loadLibrary("networkstackutilsjni");
}
diff --git a/src/com/android/networkstack/arp/ArpPacket.java b/src/com/android/networkstack/arp/ArpPacket.java
new file mode 100644
index 0000000..b84ff2b
--- /dev/null
+++ b/src/com/android/networkstack/arp/ArpPacket.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.arp;
+
+import static android.system.OsConstants.ETH_P_ARP;
+import static android.system.OsConstants.ETH_P_IP;
+
+import static com.android.server.util.NetworkStackConstants.ARP_ETHER_IPV4_LEN;
+import static com.android.server.util.NetworkStackConstants.ARP_HWTYPE_ETHER;
+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.ETHER_ADDR_LEN;
+import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_LEN;
+
+import android.net.MacAddress;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * Defines basic data and operations needed to build and parse packets for the
+ * ARP protocol.
+ *
+ * @hide
+ */
+public class ArpPacket {
+ private static final String TAG = "ArpPacket";
+
+ public final short opCode;
+ public final Inet4Address senderIp;
+ public final Inet4Address targetIp;
+ public final MacAddress senderHwAddress;
+ public final MacAddress targetHwAddress;
+
+ ArpPacket(short opCode, MacAddress senderHwAddress, Inet4Address senderIp,
+ MacAddress targetHwAddress, Inet4Address targetIp) {
+ this.opCode = opCode;
+ this.senderHwAddress = senderHwAddress;
+ this.senderIp = senderIp;
+ this.targetHwAddress = targetHwAddress;
+ this.targetIp = targetIp;
+ }
+
+ /**
+ * Build an ARP packet from the required specified parameters.
+ */
+ @VisibleForTesting
+ public static ByteBuffer buildArpPacket(final byte[] dstMac, final byte[] srcMac,
+ final byte[] targetIp, final byte[] targetHwAddress, byte[] senderIp,
+ final short opCode) {
+ final ByteBuffer buf = ByteBuffer.allocate(ARP_ETHER_IPV4_LEN);
+
+ // Ether header
+ buf.put(dstMac);
+ buf.put(srcMac);
+ buf.putShort((short) ETH_P_ARP);
+
+ // ARP header
+ buf.putShort((short) ARP_HWTYPE_ETHER); // hrd
+ buf.putShort((short) ETH_P_IP); // pro
+ buf.put((byte) ETHER_ADDR_LEN); // hln
+ buf.put((byte) IPV4_ADDR_LEN); // pln
+ buf.putShort(opCode); // op
+ buf.put(srcMac); // sha
+ buf.put(senderIp); // spa
+ buf.put(targetHwAddress); // tha
+ buf.put(targetIp); // tpa
+ buf.flip();
+ return buf;
+ }
+
+ /**
+ * Parse an ARP packet from an ByteBuffer object.
+ */
+ @VisibleForTesting
+ public static ArpPacket parseArpPacket(final byte[] recvbuf, final int length)
+ throws ParseException {
+ try {
+ if (length < ARP_ETHER_IPV4_LEN || recvbuf.length < length) {
+ throw new ParseException("Invalid packet length: " + length);
+ }
+
+ final ByteBuffer buffer = ByteBuffer.wrap(recvbuf, 0, length);
+ byte[] l2dst = new byte[ETHER_ADDR_LEN];
+ byte[] l2src = new byte[ETHER_ADDR_LEN];
+ buffer.get(l2dst);
+ buffer.get(l2src);
+
+ final short etherType = buffer.getShort();
+ if (etherType != ETH_P_ARP) {
+ throw new ParseException("Incorrect Ether Type: " + etherType);
+ }
+
+ final short hwType = buffer.getShort();
+ if (hwType != ARP_HWTYPE_ETHER) {
+ throw new ParseException("Incorrect HW Type: " + hwType);
+ }
+
+ final short protoType = buffer.getShort();
+ if (protoType != ETH_P_IP) {
+ throw new ParseException("Incorrect Protocol Type: " + protoType);
+ }
+
+ final byte hwAddrLength = buffer.get();
+ if (hwAddrLength != ETHER_ADDR_LEN) {
+ throw new ParseException("Incorrect HW address length: " + hwAddrLength);
+ }
+
+ final byte ipAddrLength = buffer.get();
+ if (ipAddrLength != IPV4_ADDR_LEN) {
+ throw new ParseException("Incorrect Protocol address length: " + ipAddrLength);
+ }
+
+ final short opCode = buffer.getShort();
+ if (opCode != ARP_REQUEST && opCode != ARP_REPLY) {
+ throw new ParseException("Incorrect opCode: " + opCode);
+ }
+
+ byte[] senderHwAddress = new byte[ETHER_ADDR_LEN];
+ byte[] senderIp = new byte[IPV4_ADDR_LEN];
+ buffer.get(senderHwAddress);
+ buffer.get(senderIp);
+
+ byte[] targetHwAddress = new byte[ETHER_ADDR_LEN];
+ byte[] targetIp = new byte[IPV4_ADDR_LEN];
+ buffer.get(targetHwAddress);
+ buffer.get(targetIp);
+
+ return new ArpPacket(opCode, MacAddress.fromBytes(senderHwAddress),
+ (Inet4Address) InetAddress.getByAddress(senderIp),
+ MacAddress.fromBytes(targetHwAddress),
+ (Inet4Address) InetAddress.getByAddress(targetIp));
+ } catch (IndexOutOfBoundsException e) {
+ throw new ParseException("Invalid index when wrapping a byte array into a buffer");
+ } catch (BufferUnderflowException e) {
+ throw new ParseException("Invalid buffer position");
+ } catch (IllegalArgumentException e) {
+ throw new ParseException("Invalid MAC address representation");
+ } catch (UnknownHostException e) {
+ throw new ParseException("Invalid IP address of Host");
+ }
+ }
+
+ /**
+ * Thrown when parsing ARP packet failed.
+ */
+ public static class ParseException extends Exception {
+ ParseException(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/src/com/android/server/util/NetworkStackConstants.java b/src/com/android/server/util/NetworkStackConstants.java
index c011b6a..36bac27 100644
--- a/src/com/android/server/util/NetworkStackConstants.java
+++ b/src/com/android/server/util/NetworkStackConstants.java
@@ -26,16 +26,6 @@ import java.net.Inet4Address;
public final class NetworkStackConstants {
/**
- * IPv4 constants.
- *
- * See also:
- * - https://tools.ietf.org/html/rfc791
- */
- public static final int IPV4_ADDR_BITS = 32;
- public static final int IPV4_MIN_MTU = 68;
- public static final int IPV4_MAX_MTU = 65_535;
-
- /**
* Ethernet constants.
*
* See also:
@@ -64,6 +54,7 @@ public final class NetworkStackConstants {
* - 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_ETHER_IPV4_LEN = ARP_PAYLOAD_LEN + ETHER_HEADER_LEN;
public static final int ARP_REQUEST = 1;
public static final int ARP_REPLY = 2;
public static final int ARP_HWTYPE_RESERVED_LO = 0;
@@ -71,11 +62,23 @@ public final class NetworkStackConstants {
public static final int ARP_HWTYPE_RESERVED_HI = 0xffff;
/**
+ * IPv4 Address Conflict Detection constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc5227
+ */
+ public static final int IPV4_CONFLICT_PROBE_NUM = 3;
+ public static final int IPV4_CONFLICT_ANNOUNCE_NUM = 2;
+
+ /**
* IPv4 constants.
*
* See also:
* - https://tools.ietf.org/html/rfc791
*/
+ public static final int IPV4_ADDR_BITS = 32;
+ public static final int IPV4_MIN_MTU = 68;
+ public static final int IPV4_MAX_MTU = 65_535;
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;