summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
-rw-r--r--tests/integration/src/android/net/ip/IpClientIntegrationTest.java249
-rw-r--r--tests/unit/src/com/android/networkstack/arp/ArpPacketTest.java199
8 files changed, 1051 insertions, 72 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;
diff --git a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
index 1d2bc86..c5f99c5 100644
--- a/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
+++ b/tests/integration/src/android/net/ip/IpClientIntegrationTest.java
@@ -31,6 +31,9 @@ import static android.system.OsConstants.IPPROTO_ICMPV6;
import static android.system.OsConstants.IPPROTO_TCP;
import static com.android.internal.util.BitUtils.uint16;
+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.ETHER_HEADER_LEN;
import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_IPV6;
import static com.android.server.util.NetworkStackConstants.ETHER_TYPE_OFFSET;
@@ -81,6 +84,7 @@ import android.net.NetworkStackIpMemoryStore;
import android.net.TestNetworkInterface;
import android.net.TestNetworkManager;
import android.net.dhcp.DhcpClient;
+import android.net.dhcp.DhcpDeclinePacket;
import android.net.dhcp.DhcpDiscoverPacket;
import android.net.dhcp.DhcpPacket;
import android.net.dhcp.DhcpPacket.ParseException;
@@ -89,6 +93,7 @@ import android.net.ipmemorystore.NetworkAttributes;
import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener;
import android.net.ipmemorystore.Status;
import android.net.shared.ProvisioningConfiguration;
+import android.net.util.InterfaceParams;
import android.net.util.IpUtils;
import android.net.util.NetworkStackUtils;
import android.net.util.PacketReader;
@@ -96,6 +101,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
import android.os.RemoteException;
import android.system.ErrnoException;
import android.system.Os;
@@ -105,6 +111,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.networkstack.arp.ArpPacket;
import com.android.server.NetworkObserverRegistry;
import com.android.server.NetworkStackService.NetworkStackServiceManager;
import com.android.server.connectivity.ipmemorystore.IpMemoryStoreService;
@@ -126,6 +133,7 @@ import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@@ -155,6 +163,7 @@ public class IpClientIntegrationTest {
@Mock private NetworkStackServiceManager mNetworkStackServiceManager;
@Mock private NetworkStackIpMemoryStore mIpMemoryStore;
@Mock private IpMemoryStoreService mIpMemoryStoreService;
+ @Mock private PowerManager.WakeLock mTimeoutWakeLock;
@Spy private INetd mNetd;
@@ -164,6 +173,7 @@ public class IpClientIntegrationTest {
private TapPacketReader mPacketReader;
private IpClient mIpc;
private Dependencies mDependencies;
+ private byte[] mClientMac;
// Ethernet header
private static final int ETH_HEADER_LEN = 14;
@@ -183,7 +193,6 @@ public class IpClientIntegrationTest {
private static final int DHCP_MESSAGE_OP_CODE_OFFSET = DHCP_HEADER_OFFSET + 0;
private static final int DHCP_TRANSACTION_ID_OFFSET = DHCP_HEADER_OFFSET + 4;
private static final int DHCP_OPTION_MAGIC_COOKIE_OFFSET = DHCP_HEADER_OFFSET + 236;
- private static final int DHCP_OPTION_MESSAGE_TYPE_OFFSET = DHCP_OPTION_MAGIC_COOKIE_OFFSET + 4;
private static final Inet4Address SERVER_ADDR =
(Inet4Address) InetAddresses.parseNumericAddress("192.168.1.100");
@@ -198,6 +207,7 @@ public class IpClientIntegrationTest {
private static final String HOSTNAME = "testhostname";
private static final int TEST_DEFAULT_MTU = 1500;
private static final int TEST_MIN_MTU = 1280;
+ private static final byte[] SERVER_MAC = new byte[] { 0x00, 0x1A, 0x11, 0x22, 0x33, 0x44 };
private static class TapPacketReader extends PacketReader {
private final ParcelFileDescriptor mTapFd;
@@ -242,6 +252,7 @@ public class IpClientIntegrationTest {
private class Dependencies extends IpClient.Dependencies {
private boolean mIsDhcpLeaseCacheEnabled;
private boolean mIsDhcpRapidCommitEnabled;
+ private boolean mIsDhcpIpConflictDetectEnabled;
// Can't use SparseIntArray, it doesn't have an easy way to know if a key is not present.
private HashMap<String, Integer> mIntConfigProperties = new HashMap<>();
@@ -253,6 +264,10 @@ public class IpClientIntegrationTest {
mIsDhcpRapidCommitEnabled = enable;
}
+ public void setDhcpIpConflictDetectEnabled(final boolean enable) {
+ mIsDhcpIpConflictDetectEnabled = enable;
+ }
+
@Override
public INetd getNetd(Context context) {
return mNetd;
@@ -269,18 +284,29 @@ public class IpClientIntegrationTest {
NetworkStackIpMemoryStore ipMemoryStore) {
return new DhcpClient.Dependencies(ipMemoryStore) {
@Override
- public boolean getBooleanDeviceConfig(final String nameSpace,
- final String flagName) {
- switch (flagName) {
- case NetworkStackUtils.DHCP_RAPID_COMMIT_ENABLED:
+ public boolean isFeatureEnabled(final Context context, final String name) {
+ switch (name) {
+ case NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION:
return mIsDhcpRapidCommitEnabled;
- case NetworkStackUtils.DHCP_INIT_REBOOT_ENABLED:
+ case NetworkStackUtils.DHCP_INIT_REBOOT_VERSION:
return mIsDhcpLeaseCacheEnabled;
+ case NetworkStackUtils.DHCP_IP_CONFLICT_DETECT_VERSION:
+ return mIsDhcpIpConflictDetectEnabled;
default:
- fail("Invalid experiment flag: " + flagName);
+ fail("Invalid experiment flag: " + name);
return false;
}
}
+
+ @Override
+ public int getIntDeviceConfig(final String name) {
+ return getDeviceConfigPropertyInt(name, 0 /* default value */);
+ }
+
+ @Override
+ public PowerManager.WakeLock getWakeLock(final PowerManager powerManager) {
+ return mTimeoutWakeLock;
+ }
};
}
@@ -311,6 +337,12 @@ public class IpClientIntegrationTest {
.thenReturn(mIpMemoryStoreService);
mDependencies.setDeviceConfigProperty(IpClient.CONFIG_MIN_RDNSS_LIFETIME, 67);
+ mDependencies.setDeviceConfigProperty(DhcpClient.DHCP_RESTART_CONFIG_DELAY, 10);
+ mDependencies.setDeviceConfigProperty(DhcpClient.ARP_FIRST_PROBE_DELAY_MS, 10);
+ mDependencies.setDeviceConfigProperty(DhcpClient.ARP_PROBE_MIN_MS, 10);
+ mDependencies.setDeviceConfigProperty(DhcpClient.ARP_PROBE_MAX_MS, 20);
+ mDependencies.setDeviceConfigProperty(DhcpClient.ARP_FIRST_ANNOUNCE_DELAY_MS, 10);
+ mDependencies.setDeviceConfigProperty(DhcpClient.ARP_ANNOUNCE_INTERVAL_MS, 10);
setUpTapInterface();
setUpIpClient();
@@ -348,6 +380,7 @@ public class IpClientIntegrationTest {
inst.getUiAutomation().dropShellPermissionIdentity();
}
mIfaceName = iface.getInterfaceName();
+ mClientMac = InterfaceParams.getByName(mIfaceName).macAddr.toByteArray();
mPacketReaderThread = new HandlerThread(IpClientIntegrationTest.class.getSimpleName());
mPacketReaderThread.start();
mHandler = mPacketReaderThread.getThreadHandler();
@@ -405,6 +438,14 @@ public class IpClientIntegrationTest {
return true;
}
+ private ArpPacket parseArpPacketOrNull(final byte[] packet) {
+ try {
+ return ArpPacket.parseArpPacket(packet, packet.length);
+ } catch (ArpPacket.ParseException e) {
+ return null;
+ }
+ }
+
private static ByteBuffer buildDhcpOfferPacket(final DhcpPacket packet,
final Integer leaseTimeSec, final short mtu) {
return DhcpPacket.buildOfferPacket(DhcpPacket.ENCAP_L2, packet.getTransactionId(),
@@ -444,9 +485,25 @@ public class IpClientIntegrationTest {
}
}
+ private void sendArpReply(final byte[] clientMac) throws IOException {
+ final ByteBuffer packet = ArpPacket.buildArpPacket(clientMac /* dst */,
+ SERVER_MAC /* src */, INADDR_ANY.getAddress() /* target IP */,
+ clientMac /* target HW address */, CLIENT_ADDR.getAddress() /* sender IP */,
+ (short) ARP_REPLY);
+ sendResponse(packet);
+ }
+
+ private void sendArpProbe() throws IOException {
+ final ByteBuffer packet = ArpPacket.buildArpPacket(DhcpPacket.ETHER_BROADCAST /* dst */,
+ SERVER_MAC /* src */, CLIENT_ADDR.getAddress() /* target IP */,
+ new byte[ETHER_ADDR_LEN] /* target HW address */,
+ INADDR_ANY.getAddress() /* sender IP */, (short) ARP_REQUEST);
+ sendResponse(packet);
+ }
+
private void startIpClientProvisioning(final boolean isDhcpLeaseCacheEnabled,
- final boolean isDhcpRapidCommitEnabled, final boolean isPreconnectionEnabled)
- throws RemoteException {
+ final boolean isDhcpRapidCommitEnabled, final boolean isPreconnectionEnabled,
+ final boolean isDhcpIpConflictDetectEnabled) throws RemoteException {
ProvisioningConfiguration.Builder builder = new ProvisioningConfiguration.Builder()
.withoutIpReachabilityMonitor()
.withoutIPv6();
@@ -454,6 +511,7 @@ public class IpClientIntegrationTest {
mDependencies.setDhcpLeaseCacheEnabled(isDhcpLeaseCacheEnabled);
mDependencies.setDhcpRapidCommitEnabled(isDhcpRapidCommitEnabled);
+ mDependencies.setDhcpIpConflictDetectEnabled(isDhcpIpConflictDetectEnabled);
mIpc.setL2KeyAndGroupHint(TEST_L2KEY, TEST_GROUPHINT);
mIpc.startProvisioning(builder.build());
verify(mCb).setNeighborDiscoveryOffload(true);
@@ -493,9 +551,10 @@ public class IpClientIntegrationTest {
// Helper method to complete DHCP 2-way or 4-way handshake
private void performDhcpHandshake(final boolean isSuccessLease,
final Integer leaseTimeSec, final boolean isDhcpLeaseCacheEnabled,
- final boolean isDhcpRapidCommitEnabled, final int mtu) throws Exception {
+ final boolean isDhcpRapidCommitEnabled, final int mtu,
+ final boolean isDhcpIpConflictDetectEnabled) throws Exception {
startIpClientProvisioning(isDhcpLeaseCacheEnabled, isDhcpRapidCommitEnabled,
- false /* isPreconnectionEnabled */);
+ false /* isPreconnectionEnabled */, isDhcpIpConflictDetectEnabled);
DhcpPacket packet;
while ((packet = getNextDhcpPacket()) != null) {
@@ -538,7 +597,8 @@ public class IpClientIntegrationTest {
return null;
}).when(mIpMemoryStore).retrieveNetworkAttributes(eq(TEST_L2KEY), any());
startIpClientProvisioning(true /* isDhcpLeaseCacheEnabled */,
- false /* isDhcpRapidCommitEnabled */, false /* isPreconnectionEnabled */);
+ false /* isDhcpRapidCommitEnabled */, false /* isPreconnectionEnabled */,
+ false /* isDhcpIpConflictDetectEnabled */);
return getNextDhcpPacket();
}
@@ -563,7 +623,8 @@ public class IpClientIntegrationTest {
if (shouldChangeMtu) mtu = TEST_MIN_MTU;
performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
- true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */, mtu);
+ true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */,
+ mtu, false /* isDhcpIpConflictDetectEnabled */);
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, mtu);
if (shouldChangeMtu) {
@@ -598,7 +659,8 @@ public class IpClientIntegrationTest {
ArgumentCaptor.forClass(InterfaceConfigurationParcel.class);
startIpClientProvisioning(true /* isDhcpLeaseCacheEnabled */,
- isDhcpRapidCommitEnabled, true /* isDhcpPreConnectionEnabled */);
+ isDhcpRapidCommitEnabled, true /* isDhcpPreConnectionEnabled */,
+ false /* isDhcpIpConflictDetectEnabled */);
verify(mCb, timeout(TEST_TIMEOUT_MS).times(1))
.onPreconnectionStart(l2PacketList.capture());
final byte[] payload = l2PacketList.getValue().get(0).payload;
@@ -635,10 +697,85 @@ public class IpClientIntegrationTest {
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
}
+ private ArpPacket getNextArpPacket(final int timeout) throws Exception {
+ byte[] packet;
+ while ((packet = mPacketReader.popPacket(timeout)) != null) {
+ final ArpPacket arpPacket = parseArpPacketOrNull(packet);
+ if (arpPacket != null) return arpPacket;
+ }
+ return null;
+ }
+
+ private ArpPacket getNextArpPacket() throws Exception {
+ final ArpPacket packet = getNextArpPacket(PACKET_TIMEOUT_MS);
+ assertNotNull("No expected ARP packet received on interface within timeout", packet);
+ return packet;
+ }
+
+ private void assertArpPacket(final ArpPacket packet) {
+ assertEquals(packet.opCode, ARP_REQUEST);
+ assertEquals(packet.targetIp, CLIENT_ADDR);
+ assertTrue(Arrays.equals(packet.senderHwAddress.toByteArray(), mClientMac));
+ }
+
+ private void assertArpProbe(final ArpPacket packet) {
+ assertArpPacket(packet);
+ assertEquals(packet.senderIp, INADDR_ANY);
+ }
+
+ private void assertArpAnnounce(final ArpPacket packet) {
+ assertArpPacket(packet);
+ assertEquals(packet.senderIp, CLIENT_ADDR);
+ }
+
+ private void doIpAddressConflictDetectionTest(final boolean causeIpAddressConflict,
+ final boolean isDhcpRapidCommitEnabled, final boolean isDhcpIpConflictDetectEnabled,
+ final boolean shouldResponseArpReply) throws Exception {
+ final long currentTime = System.currentTimeMillis();
+
+ performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
+ true /* isDhcpLeaseCacheEnabled */, isDhcpRapidCommitEnabled,
+ TEST_DEFAULT_MTU, isDhcpIpConflictDetectEnabled);
+
+ // If we receive an ARP packet here, it's guaranteed to be from IP conflict detection,
+ // because at this time the test interface does not have an IP address and therefore
+ // won't send ARP for anything.
+ if (causeIpAddressConflict) {
+ final ArpPacket arpProbe = getNextArpPacket();
+ assertArpProbe(arpProbe);
+
+ if (shouldResponseArpReply) {
+ sendArpReply(mClientMac);
+ } else {
+ sendArpProbe();
+ }
+ final DhcpPacket packet = getNextDhcpPacket();
+ assertTrue(packet instanceof DhcpDeclinePacket);
+ assertEquals(packet.mServerIdentifier, SERVER_ADDR);
+ assertEquals(packet.mRequestedIp, CLIENT_ADDR);
+ assertIpMemoryNeverStoreNetworkAttributes();
+ } else if (isDhcpIpConflictDetectEnabled) {
+ int arpPacketCount = 0;
+ final List<ArpPacket> packetList = new ArrayList<ArpPacket>();
+ // Total sent ARP packets should be 5 (3 ARP Probes + 2 ARP Announcements)
+ ArpPacket packet;
+ while ((packet = getNextArpPacket(TEST_TIMEOUT_MS)) != null) {
+ packetList.add(packet);
+ }
+ assertEquals(5, packetList.size());
+ assertArpProbe(packetList.get(0));
+ assertArpAnnounce(packetList.get(3));
+
+ assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime,
+ TEST_DEFAULT_MTU);
+ }
+ }
+
@Test
public void testDhcpInit() throws Exception {
startIpClientProvisioning(false /* isDhcpLeaseCacheEnabled */,
- false /* isDhcpRapidCommitEnabled */, false /* isPreconnectionEnabled */);
+ false /* isDhcpRapidCommitEnabled */, false /* isPreconnectionEnabled */,
+ false /* isDhcpIpConflictDetectEnabled */);
final DhcpPacket packet = getNextDhcpPacket();
assertTrue(packet instanceof DhcpDiscoverPacket);
}
@@ -648,7 +785,7 @@ public class IpClientIntegrationTest {
final long currentTime = System.currentTimeMillis();
performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */,
- TEST_DEFAULT_MTU);
+ TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
}
@@ -656,7 +793,7 @@ public class IpClientIntegrationTest {
public void testHandleFailureDhcpLease() throws Exception {
performDhcpHandshake(false /* isSuccessLease */, TEST_LEASE_DURATION_S,
true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */,
- TEST_DEFAULT_MTU);
+ TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
assertIpMemoryNeverStoreNetworkAttributes();
}
@@ -665,7 +802,7 @@ public class IpClientIntegrationTest {
final long currentTime = System.currentTimeMillis();
performDhcpHandshake(true /* isSuccessLease */, INFINITE_LEASE,
true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */,
- TEST_DEFAULT_MTU);
+ TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
assertIpMemoryStoreNetworkAttributes(INFINITE_LEASE, currentTime, TEST_DEFAULT_MTU);
}
@@ -674,7 +811,7 @@ public class IpClientIntegrationTest {
final long currentTime = System.currentTimeMillis();
performDhcpHandshake(true /* isSuccessLease */, null /* no lease time */,
true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */,
- TEST_DEFAULT_MTU);
+ TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
assertIpMemoryStoreNetworkAttributes(null, currentTime, TEST_DEFAULT_MTU);
}
@@ -682,7 +819,7 @@ public class IpClientIntegrationTest {
public void testHandleDisableInitRebootState() throws Exception {
performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
false /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */,
- TEST_DEFAULT_MTU);
+ TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
assertIpMemoryNeverStoreNetworkAttributes();
}
@@ -693,7 +830,7 @@ public class IpClientIntegrationTest {
final long currentTime = System.currentTimeMillis();
performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
true /* isDhcpLeaseCacheEnabled */, true /* isDhcpRapidCommitEnabled */,
- TEST_DEFAULT_MTU);
+ TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
}
@@ -756,7 +893,8 @@ public class IpClientIntegrationTest {
@Test
public void testDhcpClientRapidCommitEnabled() throws Exception {
startIpClientProvisioning(true /* isDhcpLeaseCacheEnabled */,
- true /* isDhcpRapidCommitEnabled */, false /* isPreconnectionEnabled */);
+ true /* isDhcpRapidCommitEnabled */, false /* isPreconnectionEnabled */,
+ false /* isDhcpIpConflictDetectEnabled */);
final DhcpPacket packet = getNextDhcpPacket();
assertTrue(packet instanceof DhcpDiscoverPacket);
}
@@ -984,7 +1122,7 @@ public class IpClientIntegrationTest {
final long currentTime = System.currentTimeMillis();
performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */,
- TEST_DEFAULT_MTU);
+ TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
assertIpMemoryStoreNetworkAttributes(TEST_LEASE_DURATION_S, currentTime, TEST_DEFAULT_MTU);
ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class);
@@ -1011,7 +1149,7 @@ public class IpClientIntegrationTest {
// start IpClient again and should enter Clearing State and wait for the message from kernel
performDhcpHandshake(true /* isSuccessLease */, TEST_LEASE_DURATION_S,
true /* isDhcpLeaseCacheEnabled */, false /* isDhcpRapidCommitEnabled */,
- TEST_DEFAULT_MTU);
+ TEST_DEFAULT_MTU, false /* isDhcpIpConflictDetectEnabled */);
verify(mCb, timeout(TEST_TIMEOUT_MS)).onProvisioningSuccess(captor.capture());
lp = captor.getValue();
@@ -1049,4 +1187,67 @@ public class IpClientIntegrationTest {
doIpClientProvisioningWithPreconnectionTest(true /* isDhcpRapidCommitEnabled */,
true /* shouldAbortPreconnection */);
}
+
+ public void testDhcpDecline_conflictByArpReply() throws Exception {
+ doIpAddressConflictDetectionTest(true /* causeIpAddressConflict */,
+ false /* isDhcpRapidCommitEnabled */, true /* isDhcpIpConflictDetectEnabled */,
+ true /* shouldResponseArpReply */);
+ }
+
+ @Test
+ public void testDhcpDecline_conflictByArpProbe() throws Exception {
+ doIpAddressConflictDetectionTest(true /* causeIpAddressConflict */,
+ false /* isDhcpRapidCommitEnabled */, true /* isDhcpIpConflictDetectEnabled */,
+ false /* shouldResponseArpReply */);
+ }
+
+ @Test
+ public void testDhcpDecline_EnableFlagWithoutIpConflict() throws Exception {
+ doIpAddressConflictDetectionTest(false /* causeIpAddressConflict */,
+ false /* isDhcpRapidCommitEnabled */, true /* isDhcpIpConflictDetectEnabled */,
+ false /* shouldResponseArpReply */);
+ }
+
+ @Test
+ public void testDhcpDecline_WithoutIpConflict() throws Exception {
+ doIpAddressConflictDetectionTest(false /* causeIpAddressConflict */,
+ false /* isDhcpRapidCommitEnabled */, false /* isDhcpIpConflictDetectEnabled */,
+ false /* shouldResponseArpReply */);
+ }
+
+ // So far Rapid Commit option has not been built within DHCPACK packet (implemention at
+ // aosp/1092270), hence, client can't deal with DHCPACK in INIT state correctly and drop
+ // the received DHCPACK, which causes the following verify checks fails. After checking in
+ // aosp/1092270, remove all below @Ignore annotations.
+ @Ignore
+ @Test
+ public void testDhcpDecline_WithRapidCommitWithoutIpConflict() throws Exception {
+ doIpAddressConflictDetectionTest(false /* causeIpAddressConflict */,
+ true /* isDhcpRapidCommitEnabled */, false /* isDhcpIpConflictDetectEnabled */,
+ false /* shouldResponseArpReply */);
+ }
+
+ @Ignore
+ @Test
+ public void testDhcpDecline_WithRapidCommitConflictByArpReply() throws Exception {
+ doIpAddressConflictDetectionTest(true /* causeIpAddressConflict */,
+ true /* isDhcpRapidCommitEnabled */, true /* isDhcpIpConflictDetectEnabled */,
+ true /* shouldResponseArpReply */);
+ }
+
+ @Ignore
+ @Test
+ public void testDhcpDecline_WithRapidCommitConflictByArpProbe() throws Exception {
+ doIpAddressConflictDetectionTest(true /* causeIpAddressConflict */,
+ true /* isDhcpRapidCommitEnabled */, true /* isDhcpIpConflictDetectEnabled */,
+ false /* shouldResponseArpReply */);
+ }
+
+ @Ignore
+ @Test
+ public void testDhcpDecline_EnableFlagWithRapidCommitWithoutIpConflict() throws Exception {
+ doIpAddressConflictDetectionTest(false /* causeIpAddressConflict */,
+ true /* isDhcpRapidCommitEnabled */, true /* isDhcpIpConflictDetectEnabled */,
+ false /* shouldResponseArpReply */);
+ }
}
diff --git a/tests/unit/src/com/android/networkstack/arp/ArpPacketTest.java b/tests/unit/src/com/android/networkstack/arp/ArpPacketTest.java
new file mode 100644
index 0000000..18f4e04
--- /dev/null
+++ b/tests/unit/src/com/android/networkstack/arp/ArpPacketTest.java
@@ -0,0 +1,199 @@
+/*
+ * 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 com.android.server.util.NetworkStackConstants.ARP_REQUEST;
+import static com.android.server.util.NetworkStackConstants.ETHER_ADDR_LEN;
+import static com.android.testutils.MiscAssertsKt.assertThrows;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.net.InetAddresses;
+import android.net.MacAddress;
+import android.net.dhcp.DhcpPacket;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class ArpPacketTest {
+
+ private static final Inet4Address TEST_IPV4_ADDR =
+ (Inet4Address) InetAddresses.parseNumericAddress("192.168.1.2");
+ private static final Inet4Address INADDR_ANY =
+ (Inet4Address) InetAddresses.parseNumericAddress("0.0.0.0");
+ private static final byte[] TEST_SENDER_MAC_ADDR = new byte[] {
+ 0x00, 0x1a, 0x11, 0x22, 0x33, 0x33 };
+ private static final byte[] TEST_TARGET_MAC_ADDR = new byte[] {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+ private static final byte[] TEST_ARP_PROBE = new byte[] {
+ // dst mac address
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ // src mac address
+ (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+ // ether type
+ (byte) 0x08, (byte) 0x06,
+ // hardware type
+ (byte) 0x00, (byte) 0x01,
+ // protocol type
+ (byte) 0x08, (byte) 0x00,
+ // hardware address size
+ (byte) 0x06,
+ // protocol address size
+ (byte) 0x04,
+ // opcode
+ (byte) 0x00, (byte) 0x01,
+ // sender mac address
+ (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+ // sender IP address
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ // target mac address
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ // target IP address
+ (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02,
+ };
+
+ private static final byte[] TEST_ARP_ANNOUNCE = new byte[] {
+ // dst mac address
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ // src mac address
+ (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+ // ether type
+ (byte) 0x08, (byte) 0x06,
+ // hardware type
+ (byte) 0x00, (byte) 0x01,
+ // protocol type
+ (byte) 0x08, (byte) 0x00,
+ // hardware address size
+ (byte) 0x06,
+ // protocol address size
+ (byte) 0x04,
+ // opcode
+ (byte) 0x00, (byte) 0x01,
+ // sender mac address
+ (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+ // sender IP address
+ (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02,
+ // target mac address
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ // target IP address
+ (byte) 0xc0, (byte) 0xa8, (byte) 0x01, (byte) 0x02,
+ };
+
+ private static final byte[] TEST_ARP_PROBE_TRUNCATED = new byte[] {
+ // dst mac address
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ // src mac address
+ (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+ // ether type
+ (byte) 0x08, (byte) 0x06,
+ // hardware type
+ (byte) 0x00, (byte) 0x01,
+ // protocol type
+ (byte) 0x08, (byte) 0x00,
+ // hardware address size
+ (byte) 0x06,
+ // protocol address size
+ (byte) 0x04,
+ // opcode
+ (byte) 0x00,
+ };
+
+ private static final byte[] TEST_ARP_PROBE_TRUNCATED_MAC = new byte[] {
+ // dst mac address
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ // src mac address
+ (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x33,
+ // ether type
+ (byte) 0x08, (byte) 0x06,
+ // hardware type
+ (byte) 0x00, (byte) 0x01,
+ // protocol type
+ (byte) 0x08, (byte) 0x00,
+ // hardware address size
+ (byte) 0x06,
+ // protocol address size
+ (byte) 0x04,
+ // opcode
+ (byte) 0x00, (byte) 0x01,
+ // sender mac address
+ (byte) 0x00, (byte) 0x1a, (byte) 0x11, (byte) 0x22, (byte) 0x33,
+ };
+
+ @Test
+ public void testBuildArpProbePacket() throws Exception {
+ final ByteBuffer arpProbe = ArpPacket.buildArpPacket(DhcpPacket.ETHER_BROADCAST,
+ TEST_SENDER_MAC_ADDR, TEST_IPV4_ADDR.getAddress(), new byte[ETHER_ADDR_LEN],
+ INADDR_ANY.getAddress(), (short) ARP_REQUEST);
+ assertArrayEquals(arpProbe.array(), TEST_ARP_PROBE);
+ }
+
+ @Test
+ public void testBuildArpAnnouncePacket() throws Exception {
+ final ByteBuffer arpAnnounce = ArpPacket.buildArpPacket(DhcpPacket.ETHER_BROADCAST,
+ TEST_SENDER_MAC_ADDR, TEST_IPV4_ADDR.getAddress(), new byte[ETHER_ADDR_LEN],
+ TEST_IPV4_ADDR.getAddress(), (short) ARP_REQUEST);
+ assertArrayEquals(arpAnnounce.array(), TEST_ARP_ANNOUNCE);
+ }
+
+ @Test
+ public void testParseArpProbePacket() throws Exception {
+ final ArpPacket packet = ArpPacket.parseArpPacket(TEST_ARP_PROBE, TEST_ARP_PROBE.length);
+ assertEquals(packet.opCode, ARP_REQUEST);
+ assertEquals(packet.senderHwAddress, MacAddress.fromBytes(TEST_SENDER_MAC_ADDR));
+ assertEquals(packet.targetHwAddress, MacAddress.fromBytes(TEST_TARGET_MAC_ADDR));
+ assertEquals(packet.senderIp, INADDR_ANY);
+ assertEquals(packet.targetIp, TEST_IPV4_ADDR);
+ }
+
+ @Test
+ public void testParseArpAnnouncePacket() throws Exception {
+ final ArpPacket packet = ArpPacket.parseArpPacket(TEST_ARP_ANNOUNCE,
+ TEST_ARP_ANNOUNCE.length);
+ assertEquals(packet.opCode, ARP_REQUEST);
+ assertEquals(packet.senderHwAddress, MacAddress.fromBytes(TEST_SENDER_MAC_ADDR));
+ assertEquals(packet.targetHwAddress, MacAddress.fromBytes(TEST_TARGET_MAC_ADDR));
+ assertEquals(packet.senderIp, TEST_IPV4_ADDR);
+ assertEquals(packet.targetIp, TEST_IPV4_ADDR);
+ }
+
+ @Test
+ public void testParseArpPacket_invalidByteBufferParameters() throws Exception {
+ assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket(
+ TEST_ARP_PROBE, 0));
+ }
+
+ @Test
+ public void testParseArpPacket_truncatedPacket() throws Exception {
+ assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket(
+ TEST_ARP_PROBE_TRUNCATED, TEST_ARP_PROBE_TRUNCATED.length));
+ }
+
+ @Test
+ public void testParseArpPacket_truncatedMacAddress() throws Exception {
+ assertThrows(ArpPacket.ParseException.class, () -> ArpPacket.parseArpPacket(
+ TEST_ARP_PROBE_TRUNCATED_MAC, TEST_ARP_PROBE_TRUNCATED.length));
+ }
+}