Merge changes I2cea553a,Id8d3dcf6,I19e68e88,I35598935,Idd7dc369, ...
am: e01b4ce6c7
Change-Id: I1800eb214d501e76b8705a27ca817000d009736e
This commit is contained in:
@@ -22,6 +22,7 @@ import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
@@ -130,6 +131,17 @@ public class NetworkUtils {
|
||||
*/
|
||||
public native static boolean queryUserAccess(int uid, int netId);
|
||||
|
||||
/**
|
||||
* Add an entry into the ARP cache.
|
||||
*/
|
||||
public static void addArpEntry(Inet4Address ipv4Addr, MacAddress ethAddr, String ifname,
|
||||
FileDescriptor fd) throws IOException {
|
||||
addArpEntry(ethAddr.toByteArray(), ipv4Addr.getAddress(), ifname, fd);
|
||||
}
|
||||
|
||||
private static native void addArpEntry(byte[] ethAddr, byte[] netAddr, String ifname,
|
||||
FileDescriptor fd) throws IOException;
|
||||
|
||||
/**
|
||||
* @see #intToInet4AddressHTL(int)
|
||||
* @deprecated Use either {@link #intToInet4AddressHTH(int)}
|
||||
@@ -149,7 +161,7 @@ public class NetworkUtils {
|
||||
* @param hostAddress an int coding for an IPv4 address, where higher-order int byte is
|
||||
* lower-order IPv4 address byte
|
||||
*/
|
||||
public static InetAddress intToInet4AddressHTL(int hostAddress) {
|
||||
public static Inet4Address intToInet4AddressHTL(int hostAddress) {
|
||||
return intToInet4AddressHTH(Integer.reverseBytes(hostAddress));
|
||||
}
|
||||
|
||||
@@ -157,14 +169,14 @@ public class NetworkUtils {
|
||||
* Convert a IPv4 address from an integer to an InetAddress (0x01020304 -> 1.2.3.4)
|
||||
* @param hostAddress an int coding for an IPv4 address
|
||||
*/
|
||||
public static InetAddress intToInet4AddressHTH(int hostAddress) {
|
||||
public static Inet4Address intToInet4AddressHTH(int hostAddress) {
|
||||
byte[] addressBytes = { (byte) (0xff & (hostAddress >> 24)),
|
||||
(byte) (0xff & (hostAddress >> 16)),
|
||||
(byte) (0xff & (hostAddress >> 8)),
|
||||
(byte) (0xff & hostAddress) };
|
||||
|
||||
try {
|
||||
return InetAddress.getByAddress(addressBytes);
|
||||
return (Inet4Address) InetAddress.getByAddress(addressBytes);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
@@ -396,6 +408,28 @@ public class NetworkUtils {
|
||||
return new Pair<InetAddress, Integer>(address, prefixLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a prefix mask as Inet4Address for a given prefix length.
|
||||
*
|
||||
* <p>For example 20 -> 255.255.240.0
|
||||
*/
|
||||
public static Inet4Address getPrefixMaskAsInet4Address(int prefixLength)
|
||||
throws IllegalArgumentException {
|
||||
return intToInet4AddressHTH(prefixLengthToV4NetmaskIntHTH(prefixLength));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the broadcast address for a given prefix.
|
||||
*
|
||||
* <p>For example 192.168.0.1/24 -> 192.168.0.255
|
||||
*/
|
||||
public static Inet4Address getBroadcastAddress(Inet4Address addr, int prefixLength)
|
||||
throws IllegalArgumentException {
|
||||
final int intBroadcastAddr = inet4AddressToIntHTH(addr)
|
||||
| ~prefixLengthToV4NetmaskIntHTH(prefixLength);
|
||||
return intToInet4AddressHTH(intBroadcastAddr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if IP address type is consistent between two InetAddress.
|
||||
* @return true if both are the same type. False otherwise.
|
||||
|
||||
@@ -323,6 +323,55 @@ static jboolean android_net_utils_queryUserAccess(JNIEnv *env, jobject thiz, jin
|
||||
return (jboolean) !queryUserAccess(uid, netId);
|
||||
}
|
||||
|
||||
static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst)
|
||||
{
|
||||
if (env->GetArrayLength(addr) != len) {
|
||||
return false;
|
||||
}
|
||||
env->GetByteArrayRegion(addr, 0, len, reinterpret_cast<jbyte*>(dst));
|
||||
return true;
|
||||
}
|
||||
|
||||
static void android_net_utils_addArpEntry(JNIEnv *env, jobject thiz, jbyteArray ethAddr,
|
||||
jbyteArray ipv4Addr, jstring ifname, jobject javaFd)
|
||||
{
|
||||
struct arpreq req = {};
|
||||
struct sockaddr_in& netAddrStruct = *reinterpret_cast<sockaddr_in*>(&req.arp_pa);
|
||||
struct sockaddr& ethAddrStruct = req.arp_ha;
|
||||
|
||||
ethAddrStruct.sa_family = ARPHRD_ETHER;
|
||||
if (!checkLenAndCopy(env, ethAddr, ETH_ALEN, ethAddrStruct.sa_data)) {
|
||||
jniThrowException(env, "java/io/IOException", "Invalid ethAddr length");
|
||||
return;
|
||||
}
|
||||
|
||||
netAddrStruct.sin_family = AF_INET;
|
||||
if (!checkLenAndCopy(env, ipv4Addr, sizeof(in_addr), &netAddrStruct.sin_addr)) {
|
||||
jniThrowException(env, "java/io/IOException", "Invalid ipv4Addr length");
|
||||
return;
|
||||
}
|
||||
|
||||
int ifLen = env->GetStringLength(ifname);
|
||||
// IFNAMSIZ includes the terminating NULL character
|
||||
if (ifLen >= IFNAMSIZ) {
|
||||
jniThrowException(env, "java/io/IOException", "ifname too long");
|
||||
return;
|
||||
}
|
||||
env->GetStringUTFRegion(ifname, 0, ifLen, req.arp_dev);
|
||||
|
||||
req.arp_flags = ATF_COM; // Completed entry (ha valid)
|
||||
int fd = jniGetFDFromFileDescriptor(env, javaFd);
|
||||
if (fd < 0) {
|
||||
jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
|
||||
return;
|
||||
}
|
||||
// See also: man 7 arp
|
||||
if (ioctl(fd, SIOCSARP, &req)) {
|
||||
jniThrowExceptionFmt(env, "java/io/IOException", "ioctl error: %s", strerror(errno));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
@@ -337,6 +386,7 @@ static const JNINativeMethod gNetworkUtilMethods[] = {
|
||||
{ "bindSocketToNetwork", "(II)I", (void*) android_net_utils_bindSocketToNetwork },
|
||||
{ "protectFromVpn", "(I)Z", (void*)android_net_utils_protectFromVpn },
|
||||
{ "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
|
||||
{ "addArpEntry", "([B[BLjava/lang/String;Ljava/io/FileDescriptor;)V", (void*) android_net_utils_addArpEntry },
|
||||
{ "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter },
|
||||
{ "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter },
|
||||
{ "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter },
|
||||
|
||||
@@ -23,11 +23,18 @@ import java.nio.ByteBuffer;
|
||||
* This class implements the DHCP-DISCOVER packet.
|
||||
*/
|
||||
class DhcpDiscoverPacket extends DhcpPacket {
|
||||
/**
|
||||
* The IP address of the client which sent this packet.
|
||||
*/
|
||||
final Inet4Address mSrcIp;
|
||||
|
||||
/**
|
||||
* Generates a DISCOVER packet with the specified parameters.
|
||||
*/
|
||||
DhcpDiscoverPacket(int transId, short secs, byte[] clientMac, boolean broadcast) {
|
||||
super(transId, secs, INADDR_ANY, INADDR_ANY, INADDR_ANY, INADDR_ANY, clientMac, broadcast);
|
||||
DhcpDiscoverPacket(int transId, short secs, Inet4Address relayIp, byte[] clientMac,
|
||||
boolean broadcast, Inet4Address srcIp) {
|
||||
super(transId, secs, INADDR_ANY, INADDR_ANY, INADDR_ANY, relayIp, clientMac, broadcast);
|
||||
mSrcIp = srcIp;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
@@ -41,8 +48,8 @@ class DhcpDiscoverPacket extends DhcpPacket {
|
||||
*/
|
||||
public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
|
||||
ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
|
||||
fillInPacket(encap, INADDR_BROADCAST, INADDR_ANY, destUdp,
|
||||
srcUdp, result, DHCP_BOOTREQUEST, mBroadcast);
|
||||
fillInPacket(encap, INADDR_BROADCAST, mSrcIp, destUdp, srcUdp, result, DHCP_BOOTREQUEST,
|
||||
mBroadcast);
|
||||
result.flip();
|
||||
return result;
|
||||
}
|
||||
|
||||
138
services/net/java/android/net/dhcp/DhcpLease.java
Normal file
138
services/net/java/android/net/dhcp/DhcpLease.java
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.dhcp;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.net.MacAddress;
|
||||
import android.os.SystemClock;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.android.internal.util.HexDump;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An IPv4 address assignment done through DHCPv4.
|
||||
* @hide
|
||||
*/
|
||||
public class DhcpLease {
|
||||
public static final long EXPIRATION_NEVER = Long.MAX_VALUE;
|
||||
public static final String HOSTNAME_NONE = null;
|
||||
|
||||
@Nullable
|
||||
private final byte[] mClientId;
|
||||
@NonNull
|
||||
private final MacAddress mHwAddr;
|
||||
@NonNull
|
||||
private final Inet4Address mNetAddr;
|
||||
/**
|
||||
* Expiration time for the lease, to compare with {@link SystemClock#elapsedRealtime()}.
|
||||
*/
|
||||
private final long mExpTime;
|
||||
@Nullable
|
||||
private final String mHostname;
|
||||
|
||||
public DhcpLease(@Nullable byte[] clientId, @NonNull MacAddress hwAddr,
|
||||
@NonNull Inet4Address netAddr, long expTime, @Nullable String hostname) {
|
||||
mClientId = (clientId == null ? null : Arrays.copyOf(clientId, clientId.length));
|
||||
mHwAddr = hwAddr;
|
||||
mNetAddr = netAddr;
|
||||
mExpTime = expTime;
|
||||
mHostname = hostname;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public byte[] getClientId() {
|
||||
if (mClientId == null) {
|
||||
return null;
|
||||
}
|
||||
return Arrays.copyOf(mClientId, mClientId.length);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public MacAddress getHwAddr() {
|
||||
return mHwAddr;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getHostname() {
|
||||
return mHostname;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Inet4Address getNetAddr() {
|
||||
return mNetAddr;
|
||||
}
|
||||
|
||||
public long getExpTime() {
|
||||
return mExpTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push back the expiration time of this lease. If the provided time is sooner than the original
|
||||
* expiration time, the lease time will not be updated.
|
||||
*
|
||||
* <p>The lease hostname is updated with the provided one if set.
|
||||
* @return A {@link DhcpLease} with expiration time set to max(expTime, currentExpTime)
|
||||
*/
|
||||
public DhcpLease renewedLease(long expTime, @Nullable String hostname) {
|
||||
return new DhcpLease(mClientId, mHwAddr, mNetAddr, Math.max(expTime, mExpTime),
|
||||
(hostname == null ? mHostname : hostname));
|
||||
}
|
||||
|
||||
public boolean matchesClient(@Nullable byte[] clientId, @NonNull MacAddress hwAddr) {
|
||||
if (mClientId != null) {
|
||||
return Arrays.equals(mClientId, clientId);
|
||||
} else {
|
||||
return clientId == null && mHwAddr.equals(hwAddr);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof DhcpLease)) {
|
||||
return false;
|
||||
}
|
||||
final DhcpLease other = (DhcpLease)obj;
|
||||
return Arrays.equals(mClientId, other.mClientId)
|
||||
&& mHwAddr.equals(other.mHwAddr)
|
||||
&& mNetAddr.equals(other.mNetAddr)
|
||||
&& mExpTime == other.mExpTime
|
||||
&& TextUtils.equals(mHostname, other.mHostname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mClientId, mHwAddr, mNetAddr, mHostname, mExpTime);
|
||||
}
|
||||
|
||||
static String clientIdToString(byte[] bytes) {
|
||||
if (bytes == null) {
|
||||
return "null";
|
||||
}
|
||||
return HexDump.toHexString(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("clientId: %s, hwAddr: %s, netAddr: %s, expTime: %d, hostname: %s",
|
||||
clientIdToString(mClientId), mHwAddr.toString(), mNetAddr, mExpTime, mHostname);
|
||||
}
|
||||
}
|
||||
538
services/net/java/android/net/dhcp/DhcpLeaseRepository.java
Normal file
538
services/net/java/android/net/dhcp/DhcpLeaseRepository.java
Normal file
@@ -0,0 +1,538 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.dhcp;
|
||||
|
||||
import static android.net.NetworkUtils.inet4AddressToIntHTH;
|
||||
import static android.net.NetworkUtils.intToInet4AddressHTH;
|
||||
import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTH;
|
||||
import static android.net.dhcp.DhcpLease.EXPIRATION_NEVER;
|
||||
import static android.net.util.NetworkConstants.IPV4_ADDR_BITS;
|
||||
|
||||
import static java.lang.Math.min;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.net.IpPrefix;
|
||||
import android.net.MacAddress;
|
||||
import android.net.util.SharedLog;
|
||||
import android.os.SystemClock;
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* A repository managing IPv4 address assignments through DHCPv4.
|
||||
*
|
||||
* <p>This class is not thread-safe. All public methods should be called on a common thread or
|
||||
* use some synchronization mechanism.
|
||||
*
|
||||
* <p>Methods are optimized for a small number of allocated leases, assuming that most of the time
|
||||
* only 2~10 addresses will be allocated, which is the common case. Managing a large number of
|
||||
* addresses is supported but will be slower: some operations have complexity in O(num_leases).
|
||||
* @hide
|
||||
*/
|
||||
class DhcpLeaseRepository {
|
||||
public static final byte[] CLIENTID_UNSPEC = null;
|
||||
public static final Inet4Address INETADDR_UNSPEC = null;
|
||||
|
||||
@NonNull
|
||||
private final SharedLog mLog;
|
||||
@NonNull
|
||||
private final Clock mClock;
|
||||
|
||||
@NonNull
|
||||
private IpPrefix mPrefix;
|
||||
@NonNull
|
||||
private Set<Inet4Address> mReservedAddrs;
|
||||
private int mSubnetAddr;
|
||||
private int mSubnetMask;
|
||||
private int mNumAddresses;
|
||||
private long mLeaseTimeMs;
|
||||
|
||||
public static class Clock {
|
||||
/**
|
||||
* @see SystemClock#elapsedRealtime()
|
||||
*/
|
||||
public long elapsedRealtime() {
|
||||
return SystemClock.elapsedRealtime();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Next timestamp when committed or declined leases should be checked for expired ones. This
|
||||
* will always be lower than or equal to the time for the first lease to expire: it's OK not to
|
||||
* update this when removing entries, but it must always be updated when adding/updating.
|
||||
*/
|
||||
private long mNextExpirationCheck = EXPIRATION_NEVER;
|
||||
|
||||
static class DhcpLeaseException extends Exception {
|
||||
DhcpLeaseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
static class OutOfAddressesException extends DhcpLeaseException {
|
||||
OutOfAddressesException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
static class InvalidAddressException extends DhcpLeaseException {
|
||||
InvalidAddressException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Leases by IP address
|
||||
*/
|
||||
private final ArrayMap<Inet4Address, DhcpLease> mCommittedLeases = new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* Map address -> expiration timestamp in ms. Addresses are guaranteed to be valid as defined
|
||||
* by {@link #isValidAddress(Inet4Address)}, but are not necessarily otherwise available for
|
||||
* assignment.
|
||||
*/
|
||||
private final LinkedHashMap<Inet4Address, Long> mDeclinedAddrs = new LinkedHashMap<>();
|
||||
|
||||
public DhcpLeaseRepository(@NonNull IpPrefix prefix, @NonNull Set<Inet4Address> reservedAddrs,
|
||||
long leaseTimeMs, @NonNull SharedLog log, @NonNull Clock clock) {
|
||||
updateParams(prefix, reservedAddrs, leaseTimeMs);
|
||||
mLog = log;
|
||||
mClock = clock;
|
||||
}
|
||||
|
||||
public void updateParams(@NonNull IpPrefix prefix, @NonNull Set<Inet4Address> reservedAddrs,
|
||||
long leaseTimeMs) {
|
||||
mPrefix = prefix;
|
||||
mReservedAddrs = Collections.unmodifiableSet(new HashSet<>(reservedAddrs));
|
||||
mSubnetMask = prefixLengthToV4NetmaskIntHTH(prefix.getPrefixLength());
|
||||
mSubnetAddr = inet4AddressToIntHTH((Inet4Address) prefix.getAddress()) & mSubnetMask;
|
||||
mNumAddresses = 1 << (IPV4_ADDR_BITS - prefix.getPrefixLength());
|
||||
mLeaseTimeMs = leaseTimeMs;
|
||||
|
||||
cleanMap(mCommittedLeases);
|
||||
cleanMap(mDeclinedAddrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* From a map keyed by {@link Inet4Address}, remove entries where the key is invalid (as
|
||||
* specified by {@link #isValidAddress(Inet4Address)}), or is a reserved address.
|
||||
*/
|
||||
private <T> void cleanMap(Map<Inet4Address, T> map) {
|
||||
final Iterator<Entry<Inet4Address, T>> it = map.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
final Inet4Address addr = it.next().getKey();
|
||||
if (!isValidAddress(addr) || mReservedAddrs.contains(addr)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a DHCP offer, to reply to a DHCPDISCOVER. Follows RFC2131 #4.3.1.
|
||||
*
|
||||
* @param clientId Client identifier option if specified, or {@link #CLIENTID_UNSPEC}
|
||||
* @param relayAddr Internet address of the relay (giaddr), can be {@link Inet4Address#ANY}
|
||||
* @param reqAddr Requested address by the client (option 50), or {@link #INETADDR_UNSPEC}
|
||||
* @param hostname Client-provided hostname, or {@link DhcpLease#HOSTNAME_NONE}
|
||||
* @throws OutOfAddressesException The server does not have any available address
|
||||
* @throws InvalidAddressException The lease was requested from an unsupported subnet
|
||||
*/
|
||||
@NonNull
|
||||
public DhcpLease getOffer(@Nullable byte[] clientId, @NonNull MacAddress hwAddr,
|
||||
@NonNull Inet4Address relayAddr,
|
||||
@Nullable Inet4Address reqAddr, @Nullable String hostname)
|
||||
throws OutOfAddressesException, InvalidAddressException {
|
||||
final long currentTime = mClock.elapsedRealtime();
|
||||
final long expTime = currentTime + mLeaseTimeMs;
|
||||
|
||||
removeExpiredLeases(currentTime);
|
||||
|
||||
// As per #4.3.1, addresses are assigned based on the relay address if present. This
|
||||
// implementation only assigns addresses if the relayAddr is inside our configured subnet.
|
||||
// This also applies when the client requested a specific address for consistency between
|
||||
// requests, and with older behavior.
|
||||
if (isIpAddrOutsidePrefix(mPrefix, relayAddr)) {
|
||||
throw new InvalidAddressException("Lease requested by relay from outside of subnet");
|
||||
}
|
||||
|
||||
final DhcpLease currentLease = findByClient(clientId, hwAddr);
|
||||
final DhcpLease newLease;
|
||||
if (currentLease != null) {
|
||||
newLease = currentLease.renewedLease(expTime, hostname);
|
||||
mLog.log("Offering extended lease " + newLease);
|
||||
// Do not update lease time in the map: the offer is not committed yet.
|
||||
} else if (reqAddr != null && isValidAddress(reqAddr) && isAvailable(reqAddr)) {
|
||||
newLease = new DhcpLease(clientId, hwAddr, reqAddr, expTime, hostname);
|
||||
mLog.log("Offering requested lease " + newLease);
|
||||
} else {
|
||||
newLease = makeNewOffer(clientId, hwAddr, expTime, hostname);
|
||||
mLog.log("Offering new generated lease " + newLease);
|
||||
}
|
||||
return newLease;
|
||||
}
|
||||
|
||||
private static boolean isIpAddrOutsidePrefix(IpPrefix prefix, Inet4Address addr) {
|
||||
return addr != null && !addr.equals(Inet4Address.ANY) && !prefix.contains(addr);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DhcpLease findByClient(@Nullable byte[] clientId, @NonNull MacAddress hwAddr) {
|
||||
for (DhcpLease lease : mCommittedLeases.values()) {
|
||||
if (lease.matchesClient(clientId, hwAddr)) {
|
||||
return lease;
|
||||
}
|
||||
}
|
||||
|
||||
// Note this differs from dnsmasq behavior, which would match by hwAddr if clientId was
|
||||
// given but no lease keyed on clientId matched. This would prevent one interface from
|
||||
// obtaining multiple leases with different clientId.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a lease conformant to a client DHCPREQUEST or renew the client's existing lease,
|
||||
* commit it to the repository and return it.
|
||||
*
|
||||
* <p>This method always succeeds and commits the lease if it does not throw, and has no side
|
||||
* effects if it throws.
|
||||
*
|
||||
* @param clientId Client identifier option if specified, or {@link #CLIENTID_UNSPEC}
|
||||
* @param reqAddr Requested address by the client (option 50), or {@link #INETADDR_UNSPEC}
|
||||
* @param sidSet Whether the server identifier was set in the request
|
||||
* @return The newly created or renewed lease
|
||||
* @throws InvalidAddressException The client provided an address that conflicts with its
|
||||
* current configuration, or other committed/reserved leases.
|
||||
*/
|
||||
@NonNull
|
||||
public DhcpLease requestLease(@Nullable byte[] clientId, @NonNull MacAddress hwAddr,
|
||||
@NonNull Inet4Address clientAddr, @Nullable Inet4Address reqAddr, boolean sidSet,
|
||||
@Nullable String hostname) throws InvalidAddressException {
|
||||
final long currentTime = mClock.elapsedRealtime();
|
||||
removeExpiredLeases(currentTime);
|
||||
final DhcpLease assignedLease = findByClient(clientId, hwAddr);
|
||||
|
||||
final Inet4Address leaseAddr = reqAddr != null ? reqAddr : clientAddr;
|
||||
if (assignedLease != null) {
|
||||
if (sidSet && reqAddr != null) {
|
||||
// Client in SELECTING state; remove any current lease before creating a new one.
|
||||
mCommittedLeases.remove(assignedLease.getNetAddr());
|
||||
} else if (!assignedLease.getNetAddr().equals(leaseAddr)) {
|
||||
// reqAddr null (RENEWING/REBINDING): client renewing its own lease for clientAddr.
|
||||
// reqAddr set with sid not set (INIT-REBOOT): client verifying configuration.
|
||||
// In both cases, throw if clientAddr or reqAddr does not match the known lease.
|
||||
throw new InvalidAddressException("Incorrect address for client in " +
|
||||
(reqAddr != null ? "INIT-REBOOT" : "RENEWING/REBINDING"));
|
||||
}
|
||||
}
|
||||
|
||||
// In the init-reboot case, RFC2131 #4.3.2 says that the server must not reply if
|
||||
// assignedLease == null, but dnsmasq will let the client use the requested address if
|
||||
// available, when configured with --dhcp-authoritative. This is preferable to avoid issues
|
||||
// if the server lost the lease DB: the client would not get a reply because the server
|
||||
// does not know their lease.
|
||||
// Similarly in RENEWING/REBINDING state, create a lease when possible if the
|
||||
// client-provided lease is unknown.
|
||||
final DhcpLease lease =
|
||||
checkClientAndMakeLease(clientId, hwAddr, leaseAddr, hostname, currentTime);
|
||||
mLog.logf("DHCPREQUEST assignedLease %s, reqAddr=%s, sidSet=%s: created/renewed lease %s",
|
||||
assignedLease, reqAddr, sidSet, lease);
|
||||
return lease;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the client can request the specified address, make or renew the lease if yes, and
|
||||
* commit it.
|
||||
*
|
||||
* <p>This method always succeeds and returns the lease if it does not throw, and has no
|
||||
* side-effect if it throws.
|
||||
*
|
||||
* @return The newly created or renewed, committed lease
|
||||
* @throws InvalidAddressException The client provided an address that conflicts with its
|
||||
* current configuration, or other committed/reserved leases.
|
||||
*/
|
||||
private DhcpLease checkClientAndMakeLease(@Nullable byte[] clientId, @NonNull MacAddress hwAddr,
|
||||
@NonNull Inet4Address addr, @Nullable String hostname, long currentTime)
|
||||
throws InvalidAddressException {
|
||||
final long expTime = currentTime + mLeaseTimeMs;
|
||||
final DhcpLease currentLease = mCommittedLeases.getOrDefault(addr, null);
|
||||
if (currentLease != null && !currentLease.matchesClient(clientId, hwAddr)) {
|
||||
throw new InvalidAddressException("Address in use");
|
||||
}
|
||||
|
||||
final DhcpLease lease;
|
||||
if (currentLease == null) {
|
||||
if (isValidAddress(addr) && !mReservedAddrs.contains(addr)) {
|
||||
lease = new DhcpLease(clientId, hwAddr, addr, expTime, hostname);
|
||||
} else {
|
||||
throw new InvalidAddressException("Lease not found and address unavailable");
|
||||
}
|
||||
} else {
|
||||
lease = currentLease.renewedLease(expTime, hostname);
|
||||
}
|
||||
commitLease(lease);
|
||||
return lease;
|
||||
}
|
||||
|
||||
private void commitLease(@NonNull DhcpLease lease) {
|
||||
mCommittedLeases.put(lease.getNetAddr(), lease);
|
||||
maybeUpdateEarliestExpiration(lease.getExpTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a committed lease from the repository.
|
||||
*
|
||||
* @return true if a lease matching parameters was found.
|
||||
*/
|
||||
public boolean releaseLease(@Nullable byte[] clientId, @NonNull MacAddress hwAddr,
|
||||
@NonNull Inet4Address addr) {
|
||||
final DhcpLease currentLease = mCommittedLeases.getOrDefault(addr, null);
|
||||
if (currentLease == null) {
|
||||
mLog.w("Could not release unknown lease for " + addr);
|
||||
return false;
|
||||
}
|
||||
if (currentLease.matchesClient(clientId, hwAddr)) {
|
||||
mCommittedLeases.remove(addr);
|
||||
mLog.log("Released lease " + currentLease);
|
||||
return true;
|
||||
}
|
||||
mLog.w(String.format("Not releasing lease %s: does not match client (cid %s, hwAddr %s)",
|
||||
currentLease, DhcpLease.clientIdToString(clientId), hwAddr));
|
||||
return false;
|
||||
}
|
||||
|
||||
public void markLeaseDeclined(@NonNull Inet4Address addr) {
|
||||
if (mDeclinedAddrs.containsKey(addr) || !isValidAddress(addr)) {
|
||||
mLog.logf("Not marking %s as declined: already declined or not assignable", addr);
|
||||
return;
|
||||
}
|
||||
final long expTime = mClock.elapsedRealtime() + mLeaseTimeMs;
|
||||
mDeclinedAddrs.put(addr, expTime);
|
||||
mLog.logf("Marked %s as declined expiring %d", addr, expTime);
|
||||
maybeUpdateEarliestExpiration(expTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of currently valid committed leases in the repository.
|
||||
*/
|
||||
@NonNull
|
||||
public List<DhcpLease> getCommittedLeases() {
|
||||
removeExpiredLeases(mClock.elapsedRealtime());
|
||||
return new ArrayList<>(mCommittedLeases.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of addresses that have been marked as declined in the repository.
|
||||
*/
|
||||
@NonNull
|
||||
public Set<Inet4Address> getDeclinedAddresses() {
|
||||
removeExpiredLeases(mClock.elapsedRealtime());
|
||||
return new HashSet<>(mDeclinedAddrs.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the expiration time of a new committed lease or declined address, update
|
||||
* {@link #mNextExpirationCheck} so it stays lower than or equal to the time for the first lease
|
||||
* to expire.
|
||||
*/
|
||||
private void maybeUpdateEarliestExpiration(long expTime) {
|
||||
if (expTime < mNextExpirationCheck) {
|
||||
mNextExpirationCheck = expTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove expired entries from a map keyed by {@link Inet4Address}.
|
||||
*
|
||||
* @param tag Type of lease in the map, for logging
|
||||
* @param getExpTime Functor returning the expiration time for an object in the map.
|
||||
* Must not return null.
|
||||
* @return The lowest expiration time among entries remaining in the map
|
||||
*/
|
||||
private <T> long removeExpired(long currentTime, @NonNull Map<Inet4Address, T> map,
|
||||
@NonNull String tag, @NonNull Function<T, Long> getExpTime) {
|
||||
final Iterator<Entry<Inet4Address, T>> it = map.entrySet().iterator();
|
||||
long firstExpiration = EXPIRATION_NEVER;
|
||||
while (it.hasNext()) {
|
||||
final Entry<Inet4Address, T> lease = it.next();
|
||||
final long expTime = getExpTime.apply(lease.getValue());
|
||||
if (expTime <= currentTime) {
|
||||
mLog.logf("Removing expired %s lease for %s (expTime=%s, currentTime=%s)",
|
||||
tag, lease.getKey(), expTime, currentTime);
|
||||
it.remove();
|
||||
} else {
|
||||
firstExpiration = min(firstExpiration, expTime);
|
||||
}
|
||||
}
|
||||
return firstExpiration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Go through committed and declined leases and remove the expired ones.
|
||||
*/
|
||||
private void removeExpiredLeases(long currentTime) {
|
||||
if (currentTime < mNextExpirationCheck) {
|
||||
return;
|
||||
}
|
||||
|
||||
final long commExp = removeExpired(
|
||||
currentTime, mCommittedLeases, "committed", DhcpLease::getExpTime);
|
||||
final long declExp = removeExpired(
|
||||
currentTime, mDeclinedAddrs, "declined", Function.identity());
|
||||
|
||||
mNextExpirationCheck = min(commExp, declExp);
|
||||
}
|
||||
|
||||
private boolean isAvailable(@NonNull Inet4Address addr) {
|
||||
return !mReservedAddrs.contains(addr) && !mCommittedLeases.containsKey(addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the 0-based index of an address in the subnet.
|
||||
*
|
||||
* <p>Given ordering of addresses 5.6.7.8 < 5.6.7.9 < 5.6.8.0, the index on a subnet is defined
|
||||
* so that the first address is 0, the second 1, etc. For example on a /16, 192.168.0.0 -> 0,
|
||||
* 192.168.0.1 -> 1, 192.168.1.0 -> 256
|
||||
*
|
||||
*/
|
||||
private int getAddrIndex(int addr) {
|
||||
return addr & ~mSubnetMask;
|
||||
}
|
||||
|
||||
private int getAddrByIndex(int index) {
|
||||
return mSubnetAddr | index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a valid address starting from the supplied one.
|
||||
*
|
||||
* <p>This only checks that the address is numerically valid for assignment, not whether it is
|
||||
* already in use. The return value is always inside the configured prefix, even if the supplied
|
||||
* address is not.
|
||||
*
|
||||
* <p>If the provided address is valid, it is returned as-is. Otherwise, the next valid
|
||||
* address (with the ordering in {@link #getAddrIndex(int)}) is returned.
|
||||
*/
|
||||
private int getValidAddress(int addr) {
|
||||
final int lastByteMask = 0xff;
|
||||
int addrIndex = getAddrIndex(addr); // 0-based index of the address in the subnet
|
||||
|
||||
// Some OSes do not handle addresses in .255 or .0 correctly: avoid those.
|
||||
final int lastByte = getAddrByIndex(addrIndex) & lastByteMask;
|
||||
if (lastByte == lastByteMask) {
|
||||
// Avoid .255 address, and .0 address that follows
|
||||
addrIndex = (addrIndex + 2) % mNumAddresses;
|
||||
} else if (lastByte == 0) {
|
||||
// Avoid .0 address
|
||||
addrIndex = (addrIndex + 1) % mNumAddresses;
|
||||
}
|
||||
|
||||
// Do not use first or last address of range
|
||||
if (addrIndex == 0 || addrIndex == mNumAddresses - 1) {
|
||||
// Always valid and not end of range since prefixLength is at most 30 in serving params
|
||||
addrIndex = 1;
|
||||
}
|
||||
return getAddrByIndex(addrIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the address is in the configured subnet and part of the assignable range.
|
||||
*/
|
||||
private boolean isValidAddress(Inet4Address addr) {
|
||||
final int intAddr = inet4AddressToIntHTH(addr);
|
||||
return getValidAddress(intAddr) == intAddr;
|
||||
}
|
||||
|
||||
private int getNextAddress(int addr) {
|
||||
final int addrIndex = getAddrIndex(addr);
|
||||
final int nextAddress = getAddrByIndex((addrIndex + 1) % mNumAddresses);
|
||||
return getValidAddress(nextAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a first candidate address for a client by hashing the hardware address.
|
||||
*
|
||||
* <p>This will be a valid address as checked by {@link #getValidAddress(int)}, but may be
|
||||
* in use.
|
||||
*
|
||||
* @return An IPv4 address encoded as 32-bit int
|
||||
*/
|
||||
private int getFirstClientAddress(MacAddress hwAddr) {
|
||||
// This follows dnsmasq behavior. Advantages are: clients will often get the same
|
||||
// offers for different DISCOVER even if the lease was not yet accepted or has expired,
|
||||
// and address generation will generally not need to loop through many allocated addresses
|
||||
// until it finds a free one.
|
||||
int hash = 0;
|
||||
for (byte b : hwAddr.toByteArray()) {
|
||||
hash += b + (b << 8) + (b << 16);
|
||||
}
|
||||
// This implementation will not always result in the same IPs as dnsmasq would give out in
|
||||
// Android <= P, because it includes invalid and reserved addresses in mNumAddresses while
|
||||
// the configured ranges for dnsmasq did not.
|
||||
final int addrIndex = hash % mNumAddresses;
|
||||
return getValidAddress(getAddrByIndex(addrIndex));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a lease that can be offered to respond to a client DISCOVER.
|
||||
*
|
||||
* <p>This method always succeeds and returns the lease if it does not throw. If no non-declined
|
||||
* address is available, it will try to offer the oldest declined address if valid.
|
||||
*
|
||||
* @throws OutOfAddressesException The server has no address left to offer
|
||||
*/
|
||||
private DhcpLease makeNewOffer(@Nullable byte[] clientId, @NonNull MacAddress hwAddr,
|
||||
long expTime, @Nullable String hostname) throws OutOfAddressesException {
|
||||
int intAddr = getFirstClientAddress(hwAddr);
|
||||
// Loop until a free address is found, or there are no more addresses.
|
||||
// There is slightly less than this many usable addresses, but some extra looping is OK
|
||||
for (int i = 0; i < mNumAddresses; i++) {
|
||||
final Inet4Address addr = intToInet4AddressHTH(intAddr);
|
||||
if (isAvailable(addr) && !mDeclinedAddrs.containsKey(addr)) {
|
||||
return new DhcpLease(clientId, hwAddr, addr, expTime, hostname);
|
||||
}
|
||||
intAddr = getNextAddress(intAddr);
|
||||
}
|
||||
|
||||
// Try freeing DECLINEd addresses if out of addresses.
|
||||
final Iterator<Inet4Address> it = mDeclinedAddrs.keySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
final Inet4Address addr = it.next();
|
||||
it.remove();
|
||||
mLog.logf("Out of addresses in address pool: dropped declined addr %s", addr);
|
||||
// isValidAddress() is always verified for entries in mDeclinedAddrs.
|
||||
// However declined addresses may have been requested (typically by the machine that was
|
||||
// already using the address) after being declined.
|
||||
if (isAvailable(addr)) {
|
||||
return new DhcpLease(clientId, hwAddr, addr, expTime, hostname);
|
||||
}
|
||||
}
|
||||
|
||||
throw new OutOfAddressesException("No address available for offer");
|
||||
}
|
||||
}
|
||||
@@ -26,11 +26,9 @@ class DhcpNakPacket extends DhcpPacket {
|
||||
/**
|
||||
* Generates a NAK packet with the specified parameters.
|
||||
*/
|
||||
DhcpNakPacket(int transId, short secs, Inet4Address clientIp, Inet4Address yourIp,
|
||||
Inet4Address nextIp, Inet4Address relayIp,
|
||||
byte[] clientMac) {
|
||||
super(transId, secs, INADDR_ANY, INADDR_ANY, nextIp, relayIp,
|
||||
clientMac, false);
|
||||
DhcpNakPacket(int transId, short secs, Inet4Address nextIp, Inet4Address relayIp,
|
||||
byte[] clientMac, boolean broadcast) {
|
||||
super(transId, secs, INADDR_ANY, INADDR_ANY, nextIp, relayIp, clientMac, broadcast);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
@@ -43,11 +41,11 @@ class DhcpNakPacket extends DhcpPacket {
|
||||
*/
|
||||
public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
|
||||
ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
|
||||
Inet4Address destIp = mClientIp;
|
||||
Inet4Address srcIp = mYourIp;
|
||||
// Constructor does not set values for layers <= 3: use empty values
|
||||
Inet4Address destIp = INADDR_ANY;
|
||||
Inet4Address srcIp = INADDR_ANY;
|
||||
|
||||
fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result,
|
||||
DHCP_BOOTREPLY, mBroadcast);
|
||||
fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result, DHCP_BOOTREPLY, mBroadcast);
|
||||
result.flip();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package android.net.dhcp;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.net.DhcpResults;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.NetworkUtils;
|
||||
@@ -204,6 +205,7 @@ public abstract class DhcpPacket {
|
||||
protected static final byte DHCP_MESSAGE_TYPE_DECLINE = 4;
|
||||
protected static final byte DHCP_MESSAGE_TYPE_ACK = 5;
|
||||
protected static final byte DHCP_MESSAGE_TYPE_NAK = 6;
|
||||
protected static final byte DHCP_MESSAGE_TYPE_RELEASE = 7;
|
||||
protected static final byte DHCP_MESSAGE_TYPE_INFORM = 8;
|
||||
|
||||
/**
|
||||
@@ -252,6 +254,7 @@ public abstract class DhcpPacket {
|
||||
* DHCP Optional Type: DHCP Client Identifier
|
||||
*/
|
||||
protected static final byte DHCP_CLIENT_IDENTIFIER = 61;
|
||||
protected byte[] mClientId;
|
||||
|
||||
/**
|
||||
* DHCP zero-length option code: pad
|
||||
@@ -281,7 +284,7 @@ public abstract class DhcpPacket {
|
||||
protected final Inet4Address mClientIp;
|
||||
protected final Inet4Address mYourIp;
|
||||
private final Inet4Address mNextIp;
|
||||
private final Inet4Address mRelayIp;
|
||||
protected final Inet4Address mRelayIp;
|
||||
|
||||
/**
|
||||
* Does the client request a broadcast response?
|
||||
@@ -338,13 +341,28 @@ public abstract class DhcpPacket {
|
||||
return mClientMac;
|
||||
}
|
||||
|
||||
// TODO: refactor DhcpClient to set clientId when constructing packets and remove
|
||||
// hasExplicitClientId logic
|
||||
/**
|
||||
* Returns the client ID. This follows RFC 2132 and is based on the hardware address.
|
||||
* Returns whether a client ID was set in the options for this packet.
|
||||
*/
|
||||
public boolean hasExplicitClientId() {
|
||||
return mClientId != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client ID. If not set explicitly, this follows RFC 2132 and creates a client ID
|
||||
* based on the hardware address.
|
||||
*/
|
||||
public byte[] getClientId() {
|
||||
byte[] clientId = new byte[mClientMac.length + 1];
|
||||
clientId[0] = CLIENT_ID_ETHER;
|
||||
System.arraycopy(mClientMac, 0, clientId, 1, mClientMac.length);
|
||||
final byte[] clientId;
|
||||
if (hasExplicitClientId()) {
|
||||
clientId = Arrays.copyOf(mClientId, mClientId.length);
|
||||
} else {
|
||||
clientId = new byte[mClientMac.length + 1];
|
||||
clientId[0] = CLIENT_ID_ETHER;
|
||||
System.arraycopy(mClientMac, 0, clientId, 1, mClientMac.length);
|
||||
}
|
||||
return clientId;
|
||||
}
|
||||
|
||||
@@ -531,8 +549,10 @@ public abstract class DhcpPacket {
|
||||
|
||||
/**
|
||||
* Adds an optional parameter containing an array of bytes.
|
||||
*
|
||||
* <p>This method is a no-op if the payload argument is null.
|
||||
*/
|
||||
protected static void addTlv(ByteBuffer buf, byte type, byte[] payload) {
|
||||
protected static void addTlv(ByteBuffer buf, byte type, @Nullable byte[] payload) {
|
||||
if (payload != null) {
|
||||
if (payload.length > MAX_OPTION_LEN) {
|
||||
throw new IllegalArgumentException("DHCP option too long: "
|
||||
@@ -546,8 +566,10 @@ public abstract class DhcpPacket {
|
||||
|
||||
/**
|
||||
* Adds an optional parameter containing an IP address.
|
||||
*
|
||||
* <p>This method is a no-op if the address argument is null.
|
||||
*/
|
||||
protected static void addTlv(ByteBuffer buf, byte type, Inet4Address addr) {
|
||||
protected static void addTlv(ByteBuffer buf, byte type, @Nullable Inet4Address addr) {
|
||||
if (addr != null) {
|
||||
addTlv(buf, type, addr.getAddress());
|
||||
}
|
||||
@@ -555,8 +577,10 @@ public abstract class DhcpPacket {
|
||||
|
||||
/**
|
||||
* Adds an optional parameter containing a list of IP addresses.
|
||||
*
|
||||
* <p>This method is a no-op if the addresses argument is null or empty.
|
||||
*/
|
||||
protected static void addTlv(ByteBuffer buf, byte type, List<Inet4Address> addrs) {
|
||||
protected static void addTlv(ByteBuffer buf, byte type, @Nullable List<Inet4Address> addrs) {
|
||||
if (addrs == null || addrs.size() == 0) return;
|
||||
|
||||
int optionLen = 4 * addrs.size();
|
||||
@@ -574,9 +598,11 @@ public abstract class DhcpPacket {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an optional parameter containing a short integer
|
||||
* Adds an optional parameter containing a short integer.
|
||||
*
|
||||
* <p>This method is a no-op if the value argument is null.
|
||||
*/
|
||||
protected static void addTlv(ByteBuffer buf, byte type, Short value) {
|
||||
protected static void addTlv(ByteBuffer buf, byte type, @Nullable Short value) {
|
||||
if (value != null) {
|
||||
buf.put(type);
|
||||
buf.put((byte) 2);
|
||||
@@ -585,9 +611,11 @@ public abstract class DhcpPacket {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an optional parameter containing a simple integer
|
||||
* Adds an optional parameter containing a simple integer.
|
||||
*
|
||||
* <p>This method is a no-op if the value argument is null.
|
||||
*/
|
||||
protected static void addTlv(ByteBuffer buf, byte type, Integer value) {
|
||||
protected static void addTlv(ByteBuffer buf, byte type, @Nullable Integer value) {
|
||||
if (value != null) {
|
||||
buf.put(type);
|
||||
buf.put((byte) 4);
|
||||
@@ -597,12 +625,16 @@ public abstract class DhcpPacket {
|
||||
|
||||
/**
|
||||
* Adds an optional parameter containing an ASCII string.
|
||||
*
|
||||
* <p>This method is a no-op if the string argument is null.
|
||||
*/
|
||||
protected static void addTlv(ByteBuffer buf, byte type, String str) {
|
||||
try {
|
||||
addTlv(buf, type, str.getBytes("US-ASCII"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalArgumentException("String is not US-ASCII: " + str);
|
||||
protected static void addTlv(ByteBuffer buf, byte type, @Nullable String str) {
|
||||
if (str != null) {
|
||||
try {
|
||||
addTlv(buf, type, str.getBytes("US-ASCII"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalArgumentException("String is not US-ASCII: " + str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -740,6 +772,7 @@ public abstract class DhcpPacket {
|
||||
Inet4Address nextIp;
|
||||
Inet4Address relayIp;
|
||||
byte[] clientMac;
|
||||
byte[] clientId = null;
|
||||
List<Inet4Address> dnsServers = new ArrayList<>();
|
||||
List<Inet4Address> gateways = new ArrayList<>(); // aka router
|
||||
Inet4Address serverIdentifier = null;
|
||||
@@ -1038,8 +1071,8 @@ public abstract class DhcpPacket {
|
||||
throw new ParseException(DhcpErrorEvent.DHCP_NO_MSG_TYPE,
|
||||
"No DHCP message type option");
|
||||
case DHCP_MESSAGE_TYPE_DISCOVER:
|
||||
newPacket = new DhcpDiscoverPacket(
|
||||
transactionId, secs, clientMac, broadcast);
|
||||
newPacket = new DhcpDiscoverPacket(transactionId, secs, relayIp, clientMac,
|
||||
broadcast, ipSrc);
|
||||
break;
|
||||
case DHCP_MESSAGE_TYPE_OFFER:
|
||||
newPacket = new DhcpOfferPacket(
|
||||
@@ -1047,7 +1080,7 @@ public abstract class DhcpPacket {
|
||||
break;
|
||||
case DHCP_MESSAGE_TYPE_REQUEST:
|
||||
newPacket = new DhcpRequestPacket(
|
||||
transactionId, secs, clientIp, clientMac, broadcast);
|
||||
transactionId, secs, clientIp, relayIp, clientMac, broadcast);
|
||||
break;
|
||||
case DHCP_MESSAGE_TYPE_DECLINE:
|
||||
newPacket = new DhcpDeclinePacket(
|
||||
@@ -1060,8 +1093,15 @@ public abstract class DhcpPacket {
|
||||
break;
|
||||
case DHCP_MESSAGE_TYPE_NAK:
|
||||
newPacket = new DhcpNakPacket(
|
||||
transactionId, secs, clientIp, yourIp, nextIp, relayIp,
|
||||
clientMac);
|
||||
transactionId, secs, nextIp, relayIp, clientMac, broadcast);
|
||||
break;
|
||||
case DHCP_MESSAGE_TYPE_RELEASE:
|
||||
if (serverIdentifier == null) {
|
||||
throw new ParseException(DhcpErrorEvent.MISC_ERROR,
|
||||
"DHCPRELEASE without server identifier");
|
||||
}
|
||||
newPacket = new DhcpReleasePacket(
|
||||
transactionId, serverIdentifier, clientIp, relayIp, clientMac);
|
||||
break;
|
||||
case DHCP_MESSAGE_TYPE_INFORM:
|
||||
newPacket = new DhcpInformPacket(
|
||||
@@ -1074,6 +1114,7 @@ public abstract class DhcpPacket {
|
||||
}
|
||||
|
||||
newPacket.mBroadcastAddress = bcAddr;
|
||||
newPacket.mClientId = clientId;
|
||||
newPacket.mDnsServers = dnsServers;
|
||||
newPacket.mDomainName = domainName;
|
||||
newPacket.mGateways = gateways;
|
||||
@@ -1173,8 +1214,8 @@ public abstract class DhcpPacket {
|
||||
*/
|
||||
public static ByteBuffer buildDiscoverPacket(int encap, int transactionId,
|
||||
short secs, byte[] clientMac, boolean broadcast, byte[] expectedParams) {
|
||||
DhcpPacket pkt = new DhcpDiscoverPacket(
|
||||
transactionId, secs, clientMac, broadcast);
|
||||
DhcpPacket pkt = new DhcpDiscoverPacket(transactionId, secs, INADDR_ANY /* relayIp */,
|
||||
clientMac, broadcast, INADDR_ANY /* srcIp */);
|
||||
pkt.mRequestedParams = expectedParams;
|
||||
return pkt.buildPacket(encap, DHCP_SERVER, DHCP_CLIENT);
|
||||
}
|
||||
@@ -1223,12 +1264,11 @@ public abstract class DhcpPacket {
|
||||
/**
|
||||
* Builds a DHCP-NAK packet from the required specified parameters.
|
||||
*/
|
||||
public static ByteBuffer buildNakPacket(int encap, int transactionId,
|
||||
Inet4Address serverIpAddr, Inet4Address clientIpAddr, byte[] mac) {
|
||||
DhcpPacket pkt = new DhcpNakPacket(transactionId, (short) 0, clientIpAddr,
|
||||
serverIpAddr, serverIpAddr, serverIpAddr, mac);
|
||||
pkt.mMessage = "requested address not available";
|
||||
pkt.mRequestedIp = clientIpAddr;
|
||||
public static ByteBuffer buildNakPacket(int encap, int transactionId, Inet4Address serverIpAddr,
|
||||
byte[] mac, boolean broadcast, String message) {
|
||||
DhcpPacket pkt = new DhcpNakPacket(
|
||||
transactionId, (short) 0, serverIpAddr, serverIpAddr, mac, broadcast);
|
||||
pkt.mMessage = message;
|
||||
return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER);
|
||||
}
|
||||
|
||||
@@ -1240,7 +1280,7 @@ public abstract class DhcpPacket {
|
||||
byte[] clientMac, Inet4Address requestedIpAddress,
|
||||
Inet4Address serverIdentifier, byte[] requestedParams, String hostName) {
|
||||
DhcpPacket pkt = new DhcpRequestPacket(transactionId, secs, clientIp,
|
||||
clientMac, broadcast);
|
||||
INADDR_ANY /* relayIp */, clientMac, broadcast);
|
||||
pkt.mRequestedIp = requestedIpAddress;
|
||||
pkt.mServerIdentifier = serverIdentifier;
|
||||
pkt.mHostName = hostName;
|
||||
|
||||
84
services/net/java/android/net/dhcp/DhcpPacketListener.java
Normal file
84
services/net/java/android/net/dhcp/DhcpPacketListener.java
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.dhcp;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.net.util.FdEventsReader;
|
||||
import android.net.util.PacketReader;
|
||||
import android.os.Handler;
|
||||
import android.system.Os;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* A {@link FdEventsReader} to receive and parse {@link DhcpPacket}.
|
||||
* @hide
|
||||
*/
|
||||
abstract class DhcpPacketListener extends FdEventsReader<DhcpPacketListener.Payload> {
|
||||
static final class Payload {
|
||||
final byte[] bytes = new byte[DhcpPacket.MAX_LENGTH];
|
||||
Inet4Address srcAddr;
|
||||
}
|
||||
|
||||
public DhcpPacketListener(Handler handler) {
|
||||
super(handler, new Payload());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int recvBufSize(Payload buffer) {
|
||||
return buffer.bytes.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void handlePacket(Payload recvbuf, int length) {
|
||||
if (recvbuf.srcAddr == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final DhcpPacket packet = DhcpPacket.decodeFullPacket(recvbuf.bytes, length,
|
||||
DhcpPacket.ENCAP_BOOTP);
|
||||
onReceive(packet, recvbuf.srcAddr);
|
||||
} catch (DhcpPacket.ParseException e) {
|
||||
logParseError(recvbuf.bytes, length, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int readPacket(FileDescriptor fd, Payload packetBuffer) throws Exception {
|
||||
final InetSocketAddress addr = new InetSocketAddress();
|
||||
final int read = Os.recvfrom(
|
||||
fd, packetBuffer.bytes, 0, packetBuffer.bytes.length, 0 /* flags */, addr);
|
||||
|
||||
// Buffers with null srcAddr will be dropped in handlePacket()
|
||||
packetBuffer.srcAddr = inet4AddrOrNull(addr);
|
||||
return read;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Inet4Address inet4AddrOrNull(InetSocketAddress addr) {
|
||||
return addr.getAddress() instanceof Inet4Address
|
||||
? (Inet4Address) addr.getAddress()
|
||||
: null;
|
||||
}
|
||||
|
||||
protected abstract void onReceive(DhcpPacket packet, Inet4Address srcAddr);
|
||||
protected abstract void logParseError(byte[] packet, int length, DhcpPacket.ParseException e);
|
||||
}
|
||||
58
services/net/java/android/net/dhcp/DhcpReleasePacket.java
Normal file
58
services/net/java/android/net/dhcp/DhcpReleasePacket.java
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.dhcp;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Implements DHCP-RELEASE
|
||||
*/
|
||||
class DhcpReleasePacket extends DhcpPacket {
|
||||
|
||||
final Inet4Address mClientAddr;
|
||||
|
||||
/**
|
||||
* Generates a RELEASE packet with the specified parameters.
|
||||
*/
|
||||
public DhcpReleasePacket(int transId, Inet4Address serverId, Inet4Address clientAddr,
|
||||
Inet4Address relayIp, byte[] clientMac) {
|
||||
super(transId, (short)0, clientAddr, INADDR_ANY /* yourIp */, INADDR_ANY /* nextIp */,
|
||||
relayIp, clientMac, false /* broadcast */);
|
||||
mServerIdentifier = serverId;
|
||||
mClientAddr = clientAddr;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
|
||||
ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
|
||||
fillInPacket(encap, mServerIdentifier /* destIp */, mClientIp /* srcIp */, destUdp, srcUdp,
|
||||
result, DHCP_BOOTREPLY, mBroadcast);
|
||||
result.flip();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
void finishPacket(ByteBuffer buffer) {
|
||||
addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_RELEASE);
|
||||
addTlv(buffer, DHCP_CLIENT_IDENTIFIER, getClientId());
|
||||
addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier);
|
||||
addCommonClientTlvs(buffer);
|
||||
addTlvEnd(buffer);
|
||||
}
|
||||
}
|
||||
@@ -28,9 +28,9 @@ class DhcpRequestPacket extends DhcpPacket {
|
||||
/**
|
||||
* Generates a REQUEST packet with the specified parameters.
|
||||
*/
|
||||
DhcpRequestPacket(int transId, short secs, Inet4Address clientIp, byte[] clientMac,
|
||||
boolean broadcast) {
|
||||
super(transId, secs, clientIp, INADDR_ANY, INADDR_ANY, INADDR_ANY, clientMac, broadcast);
|
||||
DhcpRequestPacket(int transId, short secs, Inet4Address clientIp, Inet4Address relayIp,
|
||||
byte[] clientMac, boolean broadcast) {
|
||||
super(transId, secs, clientIp, INADDR_ANY, INADDR_ANY, relayIp, clientMac, broadcast);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
||||
262
services/net/java/android/net/dhcp/DhcpServingParams.java
Normal file
262
services/net/java/android/net/dhcp/DhcpServingParams.java
Normal file
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.dhcp;
|
||||
|
||||
import static android.net.NetworkUtils.getPrefixMaskAsInet4Address;
|
||||
import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
|
||||
import static android.net.util.NetworkConstants.IPV4_MAX_MTU;
|
||||
import static android.net.util.NetworkConstants.IPV4_MIN_MTU;
|
||||
|
||||
import static java.lang.Integer.toUnsignedLong;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.net.IpPrefix;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.NetworkUtils;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Parameters used by the DhcpServer to serve requests.
|
||||
*
|
||||
* <p>Instances are immutable. Use {@link DhcpServingParams.Builder} to instantiate.
|
||||
* @hide
|
||||
*/
|
||||
public class DhcpServingParams {
|
||||
public static final int MTU_UNSET = 0;
|
||||
public static final int MIN_PREFIX_LENGTH = 16;
|
||||
public static final int MAX_PREFIX_LENGTH = 30;
|
||||
|
||||
/** Server inet address and prefix to serve */
|
||||
@NonNull
|
||||
public final LinkAddress serverAddr;
|
||||
|
||||
/**
|
||||
* Default routers to be advertised to DHCP clients. May be empty.
|
||||
* This set is provided by {@link DhcpServingParams.Builder} and is immutable.
|
||||
*/
|
||||
@NonNull
|
||||
public final Set<Inet4Address> defaultRouters;
|
||||
|
||||
/**
|
||||
* DNS servers to be advertised to DHCP clients. May be empty.
|
||||
* This set is provided by {@link DhcpServingParams.Builder} and is immutable.
|
||||
*/
|
||||
@NonNull
|
||||
public final Set<Inet4Address> dnsServers;
|
||||
|
||||
/**
|
||||
* Excluded addresses that the DHCP server is not allowed to assign to clients.
|
||||
* This set is provided by {@link DhcpServingParams.Builder} and is immutable.
|
||||
*/
|
||||
@NonNull
|
||||
public final Set<Inet4Address> excludedAddrs;
|
||||
|
||||
// DHCP uses uint32. Use long for clearer code, and check range when building.
|
||||
public final long dhcpLeaseTimeSecs;
|
||||
public final int linkMtu;
|
||||
|
||||
/**
|
||||
* Checked exception thrown when some parameters used to build {@link DhcpServingParams} are
|
||||
* missing or invalid.
|
||||
*/
|
||||
public static class InvalidParameterException extends Exception {
|
||||
public InvalidParameterException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
private DhcpServingParams(@NonNull LinkAddress serverAddr,
|
||||
@NonNull Set<Inet4Address> defaultRouters,
|
||||
@NonNull Set<Inet4Address> dnsServers, @NonNull Set<Inet4Address> excludedAddrs,
|
||||
long dhcpLeaseTimeSecs, int linkMtu) {
|
||||
this.serverAddr = serverAddr;
|
||||
this.defaultRouters = defaultRouters;
|
||||
this.dnsServers = dnsServers;
|
||||
this.excludedAddrs = excludedAddrs;
|
||||
this.dhcpLeaseTimeSecs = dhcpLeaseTimeSecs;
|
||||
this.linkMtu = linkMtu;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Inet4Address getServerInet4Addr() {
|
||||
return (Inet4Address) serverAddr.getAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the served prefix mask as an IPv4 address.
|
||||
*
|
||||
* <p>For example, if the served prefix is 192.168.42.0/24, this will return 255.255.255.0.
|
||||
*/
|
||||
@NonNull
|
||||
public Inet4Address getPrefixMaskAsAddress() {
|
||||
return getPrefixMaskAsInet4Address(serverAddr.getPrefixLength());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the server broadcast address.
|
||||
*
|
||||
* <p>For example, if the server {@link LinkAddress} is 192.168.42.1/24, this will return
|
||||
* 192.168.42.255.
|
||||
*/
|
||||
@NonNull
|
||||
public Inet4Address getBroadcastAddress() {
|
||||
return NetworkUtils.getBroadcastAddress(getServerInet4Addr(), serverAddr.getPrefixLength());
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class to create new instances of {@link DhcpServingParams} while checking validity
|
||||
* of the parameters.
|
||||
*/
|
||||
public static class Builder {
|
||||
private LinkAddress serverAddr;
|
||||
private Set<Inet4Address> defaultRouters;
|
||||
private Set<Inet4Address> dnsServers;
|
||||
private Set<Inet4Address> excludedAddrs;
|
||||
private long dhcpLeaseTimeSecs;
|
||||
private int linkMtu = MTU_UNSET;
|
||||
|
||||
/**
|
||||
* Set the server address and served prefix for the DHCP server.
|
||||
*
|
||||
* <p>This parameter is required.
|
||||
*/
|
||||
public Builder setServerAddr(@NonNull LinkAddress serverAddr) {
|
||||
this.serverAddr = serverAddr;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default routers to be advertised to DHCP clients.
|
||||
*
|
||||
* <p>Each router must be inside the served prefix. This may be an empty set, but it must
|
||||
* always be set explicitly before building the {@link DhcpServingParams}.
|
||||
*/
|
||||
public Builder setDefaultRouters(@NonNull Set<Inet4Address> defaultRouters) {
|
||||
this.defaultRouters = defaultRouters;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DNS servers to be advertised to DHCP clients.
|
||||
*
|
||||
* <p>This may be an empty set, but it must always be set explicitly before building the
|
||||
* {@link DhcpServingParams}.
|
||||
*/
|
||||
public Builder setDnsServers(@NonNull Set<Inet4Address> dnsServers) {
|
||||
this.dnsServers = dnsServers;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set excluded addresses that the DHCP server is not allowed to assign to clients.
|
||||
*
|
||||
* <p>This parameter is optional. DNS servers and default routers are always excluded
|
||||
* and do not need to be set here.
|
||||
*/
|
||||
public Builder setExcludedAddrs(@NonNull Set<Inet4Address> excludedAddrs) {
|
||||
this.excludedAddrs = excludedAddrs;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the lease time for leases assigned by the DHCP server.
|
||||
*
|
||||
* <p>This parameter is required.
|
||||
*/
|
||||
public Builder setDhcpLeaseTimeSecs(long dhcpLeaseTimeSecs) {
|
||||
this.dhcpLeaseTimeSecs = dhcpLeaseTimeSecs;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the link MTU to be advertised to DHCP clients.
|
||||
*
|
||||
* <p>If set to {@link #MTU_UNSET}, no MTU will be advertised to clients. This parameter
|
||||
* is optional and defaults to {@link #MTU_UNSET}.
|
||||
*/
|
||||
public Builder setLinkMtu(int linkMtu) {
|
||||
this.linkMtu = linkMtu;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link DhcpServingParams} instance based on parameters set in the builder.
|
||||
*
|
||||
* <p>This method has no side-effects. If it does not throw, a valid
|
||||
* {@link DhcpServingParams} is returned.
|
||||
* @return The constructed parameters.
|
||||
* @throws InvalidParameterException At least one parameter is missing or invalid.
|
||||
*/
|
||||
@NonNull
|
||||
public DhcpServingParams build() throws InvalidParameterException {
|
||||
if (serverAddr == null) {
|
||||
throw new InvalidParameterException("Missing serverAddr");
|
||||
}
|
||||
if (defaultRouters == null) {
|
||||
throw new InvalidParameterException("Missing defaultRouters");
|
||||
}
|
||||
if (dnsServers == null) {
|
||||
// Empty set is OK, but enforce explicitly setting it
|
||||
throw new InvalidParameterException("Missing dnsServers");
|
||||
}
|
||||
if (dhcpLeaseTimeSecs <= 0 || dhcpLeaseTimeSecs > toUnsignedLong(INFINITE_LEASE)) {
|
||||
throw new InvalidParameterException("Invalid lease time: " + dhcpLeaseTimeSecs);
|
||||
}
|
||||
if (linkMtu != MTU_UNSET && (linkMtu < IPV4_MIN_MTU || linkMtu > IPV4_MAX_MTU)) {
|
||||
throw new InvalidParameterException("Invalid link MTU: " + linkMtu);
|
||||
}
|
||||
if (!serverAddr.isIPv4()) {
|
||||
throw new InvalidParameterException("serverAddr must be IPv4");
|
||||
}
|
||||
if (serverAddr.getPrefixLength() < MIN_PREFIX_LENGTH
|
||||
|| serverAddr.getPrefixLength() > MAX_PREFIX_LENGTH) {
|
||||
throw new InvalidParameterException("Prefix length is not in supported range");
|
||||
}
|
||||
|
||||
final IpPrefix prefix = makeIpPrefix(serverAddr);
|
||||
for (Inet4Address addr : defaultRouters) {
|
||||
if (!prefix.contains(addr)) {
|
||||
throw new InvalidParameterException(String.format(
|
||||
"Default router %s is not in server prefix %s", addr, serverAddr));
|
||||
}
|
||||
}
|
||||
|
||||
final Set<Inet4Address> excl = new HashSet<>();
|
||||
if (excludedAddrs != null) {
|
||||
excl.addAll(excludedAddrs);
|
||||
}
|
||||
excl.add((Inet4Address) serverAddr.getAddress());
|
||||
excl.addAll(defaultRouters);
|
||||
excl.addAll(dnsServers);
|
||||
|
||||
return new DhcpServingParams(serverAddr,
|
||||
Collections.unmodifiableSet(new HashSet<>(defaultRouters)),
|
||||
Collections.unmodifiableSet(new HashSet<>(dnsServers)),
|
||||
Collections.unmodifiableSet(excl),
|
||||
dhcpLeaseTimeSecs, linkMtu);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
static IpPrefix makeIpPrefix(@NonNull LinkAddress addr) {
|
||||
return new IpPrefix(addr.getAddress(), addr.getPrefixLength());
|
||||
}
|
||||
}
|
||||
254
services/net/java/android/net/util/FdEventsReader.java
Normal file
254
services/net/java/android/net/util/FdEventsReader.java
Normal file
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.util;
|
||||
|
||||
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
|
||||
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.MessageQueue;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.OsConstants;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
|
||||
|
||||
/**
|
||||
* This class encapsulates the mechanics of registering a file descriptor
|
||||
* with a thread's Looper and handling read events (and errors).
|
||||
*
|
||||
* Subclasses MUST implement createFd() and SHOULD override handlePacket(). They MAY override
|
||||
* onStop() and onStart().
|
||||
*
|
||||
* Subclasses can expect a call life-cycle like the following:
|
||||
*
|
||||
* [1] when a client calls start(), createFd() is called, followed by the onStart() hook if all
|
||||
* goes well. Implementations may override onStart() for additional initialization.
|
||||
*
|
||||
* [2] yield, waiting for read event or error notification:
|
||||
*
|
||||
* [a] readPacket() && handlePacket()
|
||||
*
|
||||
* [b] if (no error):
|
||||
* goto 2
|
||||
* else:
|
||||
* goto 3
|
||||
*
|
||||
* [3] when a client calls stop(), the onStop() hook is called (unless already stopped or never
|
||||
* started). Implementations may override onStop() for additional cleanup.
|
||||
*
|
||||
* The packet receive buffer is recycled on every read call, so subclasses
|
||||
* should make any copies they would like inside their handlePacket()
|
||||
* implementation.
|
||||
*
|
||||
* All public methods MUST only be called from the same thread with which
|
||||
* the Handler constructor argument is associated.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public abstract class FdEventsReader<BufferType> {
|
||||
private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
|
||||
private static final int UNREGISTER_THIS_FD = 0;
|
||||
|
||||
@NonNull
|
||||
private final Handler mHandler;
|
||||
@NonNull
|
||||
private final MessageQueue mQueue;
|
||||
@NonNull
|
||||
private final BufferType mBuffer;
|
||||
@Nullable
|
||||
private FileDescriptor mFd;
|
||||
private long mPacketsReceived;
|
||||
|
||||
protected static void closeFd(FileDescriptor fd) {
|
||||
IoUtils.closeQuietly(fd);
|
||||
}
|
||||
|
||||
protected FdEventsReader(@NonNull Handler h, @NonNull BufferType buffer) {
|
||||
mHandler = h;
|
||||
mQueue = mHandler.getLooper().getQueue();
|
||||
mBuffer = buffer;
|
||||
}
|
||||
|
||||
public final void start() {
|
||||
if (onCorrectThread()) {
|
||||
createAndRegisterFd();
|
||||
} else {
|
||||
mHandler.post(() -> {
|
||||
logError("start() called from off-thread", null);
|
||||
createAndRegisterFd();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public final void stop() {
|
||||
if (onCorrectThread()) {
|
||||
unregisterAndDestroyFd();
|
||||
} else {
|
||||
mHandler.post(() -> {
|
||||
logError("stop() called from off-thread", null);
|
||||
unregisterAndDestroyFd();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Handler getHandler() { return mHandler; }
|
||||
|
||||
protected abstract int recvBufSize(@NonNull BufferType buffer);
|
||||
|
||||
public int recvBufSize() { return recvBufSize(mBuffer); }
|
||||
|
||||
/**
|
||||
* Get the number of successful calls to {@link #readPacket(FileDescriptor, Object)}.
|
||||
*
|
||||
* <p>A call was successful if {@link #readPacket(FileDescriptor, Object)} returned a value > 0.
|
||||
*/
|
||||
public final long numPacketsReceived() { return mPacketsReceived; }
|
||||
|
||||
/**
|
||||
* Subclasses MUST create the listening socket here, including setting
|
||||
* all desired socket options, interface or address/port binding, etc.
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract FileDescriptor createFd();
|
||||
|
||||
/**
|
||||
* Implementations MUST return the bytes read or throw an Exception.
|
||||
*
|
||||
* <p>The caller may throw a {@link ErrnoException} with {@link OsConstants#EAGAIN} or
|
||||
* {@link OsConstants#EINTR}, in which case {@link FdEventsReader} will ignore the buffer
|
||||
* contents and respectively wait for further input or retry the read immediately. For all other
|
||||
* exceptions, the {@link FdEventsReader} will be stopped with no more interactions with this
|
||||
* method.
|
||||
*/
|
||||
protected abstract int readPacket(@NonNull FileDescriptor fd, @NonNull BufferType buffer)
|
||||
throws Exception;
|
||||
|
||||
/**
|
||||
* Called by the main loop for every packet. Any desired copies of
|
||||
* |recvbuf| should be made in here, as the underlying byte array is
|
||||
* reused across all reads.
|
||||
*/
|
||||
protected void handlePacket(@NonNull BufferType recvbuf, int length) {}
|
||||
|
||||
/**
|
||||
* Called by the main loop to log errors. In some cases |e| may be null.
|
||||
*/
|
||||
protected void logError(@NonNull String msg, @Nullable Exception e) {}
|
||||
|
||||
/**
|
||||
* Called by start(), if successful, just prior to returning.
|
||||
*/
|
||||
protected void onStart() {}
|
||||
|
||||
/**
|
||||
* Called by stop() just prior to returning.
|
||||
*/
|
||||
protected void onStop() {}
|
||||
|
||||
private void createAndRegisterFd() {
|
||||
if (mFd != null) return;
|
||||
|
||||
try {
|
||||
mFd = createFd();
|
||||
if (mFd != null) {
|
||||
// Force the socket to be non-blocking.
|
||||
IoUtils.setBlocking(mFd, false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logError("Failed to create socket: ", e);
|
||||
closeFd(mFd);
|
||||
mFd = null;
|
||||
}
|
||||
|
||||
if (mFd == null) return;
|
||||
|
||||
mQueue.addOnFileDescriptorEventListener(
|
||||
mFd,
|
||||
FD_EVENTS,
|
||||
(fd, events) -> {
|
||||
// Always call handleInput() so read/recvfrom are given
|
||||
// a proper chance to encounter a meaningful errno and
|
||||
// perhaps log a useful error message.
|
||||
if (!isRunning() || !handleInput()) {
|
||||
unregisterAndDestroyFd();
|
||||
return UNREGISTER_THIS_FD;
|
||||
}
|
||||
return FD_EVENTS;
|
||||
});
|
||||
onStart();
|
||||
}
|
||||
|
||||
private boolean isRunning() { return (mFd != null) && mFd.valid(); }
|
||||
|
||||
// Keep trying to read until we get EAGAIN/EWOULDBLOCK or some fatal error.
|
||||
private boolean handleInput() {
|
||||
while (isRunning()) {
|
||||
final int bytesRead;
|
||||
|
||||
try {
|
||||
bytesRead = readPacket(mFd, mBuffer);
|
||||
if (bytesRead < 1) {
|
||||
if (isRunning()) logError("Socket closed, exiting", null);
|
||||
break;
|
||||
}
|
||||
mPacketsReceived++;
|
||||
} catch (ErrnoException e) {
|
||||
if (e.errno == OsConstants.EAGAIN) {
|
||||
// We've read everything there is to read this time around.
|
||||
return true;
|
||||
} else if (e.errno == OsConstants.EINTR) {
|
||||
continue;
|
||||
} else {
|
||||
if (isRunning()) logError("readPacket error: ", e);
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (isRunning()) logError("readPacket error: ", e);
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
handlePacket(mBuffer, bytesRead);
|
||||
} catch (Exception e) {
|
||||
logError("handlePacket error: ", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void unregisterAndDestroyFd() {
|
||||
if (mFd == null) return;
|
||||
|
||||
mQueue.removeOnFileDescriptorEventListener(mFd);
|
||||
closeFd(mFd);
|
||||
mFd = null;
|
||||
onStop();
|
||||
}
|
||||
|
||||
private boolean onCorrectThread() {
|
||||
return (mHandler.getLooper() == Looper.myLooper());
|
||||
}
|
||||
}
|
||||
@@ -77,10 +77,12 @@ public final class NetworkConstants {
|
||||
/**
|
||||
* IPv4 constants.
|
||||
*
|
||||
* See als:
|
||||
* See also:
|
||||
* - https://tools.ietf.org/html/rfc791
|
||||
*/
|
||||
public static final int IPV4_HEADER_MIN_LEN = 20;
|
||||
public static final int IPV4_MIN_MTU = 68;
|
||||
public static final int IPV4_MAX_MTU = 65_535;
|
||||
public static final int IPV4_IHL_MASK = 0xf;
|
||||
public static final int IPV4_FLAGS_OFFSET = 6;
|
||||
public static final int IPV4_FRAGMENT_MASK = 0x1fff;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,236 +16,46 @@
|
||||
|
||||
package android.net.util;
|
||||
|
||||
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
|
||||
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.MessageQueue;
|
||||
import android.os.MessageQueue.OnFileDescriptorEventListener;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.system.OsConstants;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* This class encapsulates the mechanics of registering a file descriptor
|
||||
* with a thread's Looper and handling read events (and errors).
|
||||
*
|
||||
* Subclasses MUST implement createFd() and SHOULD override handlePacket().
|
||||
|
||||
* Subclasses can expect a call life-cycle like the following:
|
||||
*
|
||||
* [1] start() calls createFd() and (if all goes well) onStart()
|
||||
*
|
||||
* [2] yield, waiting for read event or error notification:
|
||||
*
|
||||
* [a] readPacket() && handlePacket()
|
||||
*
|
||||
* [b] if (no error):
|
||||
* goto 2
|
||||
* else:
|
||||
* goto 3
|
||||
*
|
||||
* [3] stop() calls onStop() if not previously stopped
|
||||
*
|
||||
* The packet receive buffer is recycled on every read call, so subclasses
|
||||
* should make any copies they would like inside their handlePacket()
|
||||
* implementation.
|
||||
*
|
||||
* All public methods MUST only be called from the same thread with which
|
||||
* the Handler constructor argument is associated.
|
||||
* Specialization of {@link FdEventsReader} that reads packets into a byte array.
|
||||
*
|
||||
* TODO: rename this class to something more correctly descriptive (something
|
||||
* like [or less horrible than] FdReadEventsHandler?).
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public abstract class PacketReader {
|
||||
private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
|
||||
private static final int UNREGISTER_THIS_FD = 0;
|
||||
public abstract class PacketReader extends FdEventsReader<byte[]> {
|
||||
|
||||
public static final int DEFAULT_RECV_BUF_SIZE = 2 * 1024;
|
||||
|
||||
private final Handler mHandler;
|
||||
private final MessageQueue mQueue;
|
||||
private final byte[] mPacket;
|
||||
private FileDescriptor mFd;
|
||||
private long mPacketsReceived;
|
||||
|
||||
protected static void closeFd(FileDescriptor fd) {
|
||||
IoUtils.closeQuietly(fd);
|
||||
}
|
||||
|
||||
protected PacketReader(Handler h) {
|
||||
this(h, DEFAULT_RECV_BUF_SIZE);
|
||||
}
|
||||
|
||||
protected PacketReader(Handler h, int recvbufsize) {
|
||||
mHandler = h;
|
||||
mQueue = mHandler.getLooper().getQueue();
|
||||
mPacket = new byte[Math.max(recvbufsize, DEFAULT_RECV_BUF_SIZE)];
|
||||
protected PacketReader(Handler h, int recvBufSize) {
|
||||
super(h, new byte[max(recvBufSize, DEFAULT_RECV_BUF_SIZE)]);
|
||||
}
|
||||
|
||||
public final void start() {
|
||||
if (onCorrectThread()) {
|
||||
createAndRegisterFd();
|
||||
} else {
|
||||
mHandler.post(() -> {
|
||||
logError("start() called from off-thread", null);
|
||||
createAndRegisterFd();
|
||||
});
|
||||
}
|
||||
@Override
|
||||
protected final int recvBufSize(byte[] buffer) {
|
||||
return buffer.length;
|
||||
}
|
||||
|
||||
public final void stop() {
|
||||
if (onCorrectThread()) {
|
||||
unregisterAndDestroyFd();
|
||||
} else {
|
||||
mHandler.post(() -> {
|
||||
logError("stop() called from off-thread", null);
|
||||
unregisterAndDestroyFd();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public Handler getHandler() { return mHandler; }
|
||||
|
||||
public final int recvBufSize() { return mPacket.length; }
|
||||
|
||||
public final long numPacketsReceived() { return mPacketsReceived; }
|
||||
|
||||
/**
|
||||
* Subclasses MUST create the listening socket here, including setting
|
||||
* all desired socket options, interface or address/port binding, etc.
|
||||
*/
|
||||
protected abstract FileDescriptor createFd();
|
||||
|
||||
/**
|
||||
* Subclasses MAY override this to change the default read() implementation
|
||||
* in favour of, say, recvfrom().
|
||||
*
|
||||
* Implementations MUST return the bytes read or throw an Exception.
|
||||
*/
|
||||
@Override
|
||||
protected int readPacket(FileDescriptor fd, byte[] packetBuffer) throws Exception {
|
||||
return Os.read(fd, packetBuffer, 0, packetBuffer.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the main loop for every packet. Any desired copies of
|
||||
* |recvbuf| should be made in here, as the underlying byte array is
|
||||
* reused across all reads.
|
||||
*/
|
||||
protected void handlePacket(byte[] recvbuf, int length) {}
|
||||
|
||||
/**
|
||||
* Called by the main loop to log errors. In some cases |e| may be null.
|
||||
*/
|
||||
protected void logError(String msg, Exception e) {}
|
||||
|
||||
/**
|
||||
* Called by start(), if successful, just prior to returning.
|
||||
*/
|
||||
protected void onStart() {}
|
||||
|
||||
/**
|
||||
* Called by stop() just prior to returning.
|
||||
*/
|
||||
protected void onStop() {}
|
||||
|
||||
private void createAndRegisterFd() {
|
||||
if (mFd != null) return;
|
||||
|
||||
try {
|
||||
mFd = createFd();
|
||||
if (mFd != null) {
|
||||
// Force the socket to be non-blocking.
|
||||
IoUtils.setBlocking(mFd, false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logError("Failed to create socket: ", e);
|
||||
closeFd(mFd);
|
||||
mFd = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mFd == null) return;
|
||||
|
||||
mQueue.addOnFileDescriptorEventListener(
|
||||
mFd,
|
||||
FD_EVENTS,
|
||||
new OnFileDescriptorEventListener() {
|
||||
@Override
|
||||
public int onFileDescriptorEvents(FileDescriptor fd, int events) {
|
||||
// Always call handleInput() so read/recvfrom are given
|
||||
// a proper chance to encounter a meaningful errno and
|
||||
// perhaps log a useful error message.
|
||||
if (!isRunning() || !handleInput()) {
|
||||
unregisterAndDestroyFd();
|
||||
return UNREGISTER_THIS_FD;
|
||||
}
|
||||
return FD_EVENTS;
|
||||
}
|
||||
});
|
||||
onStart();
|
||||
}
|
||||
|
||||
private boolean isRunning() { return (mFd != null) && mFd.valid(); }
|
||||
|
||||
// Keep trying to read until we get EAGAIN/EWOULDBLOCK or some fatal error.
|
||||
private boolean handleInput() {
|
||||
while (isRunning()) {
|
||||
final int bytesRead;
|
||||
|
||||
try {
|
||||
bytesRead = readPacket(mFd, mPacket);
|
||||
if (bytesRead < 1) {
|
||||
if (isRunning()) logError("Socket closed, exiting", null);
|
||||
break;
|
||||
}
|
||||
mPacketsReceived++;
|
||||
} catch (ErrnoException e) {
|
||||
if (e.errno == OsConstants.EAGAIN) {
|
||||
// We've read everything there is to read this time around.
|
||||
return true;
|
||||
} else if (e.errno == OsConstants.EINTR) {
|
||||
continue;
|
||||
} else {
|
||||
if (isRunning()) logError("readPacket error: ", e);
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (isRunning()) logError("readPacket error: ", e);
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
handlePacket(mPacket, bytesRead);
|
||||
} catch (Exception e) {
|
||||
logError("handlePacket error: ", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void unregisterAndDestroyFd() {
|
||||
if (mFd == null) return;
|
||||
|
||||
mQueue.removeOnFileDescriptorEventListener(mFd);
|
||||
closeFd(mFd);
|
||||
mFd = null;
|
||||
onStop();
|
||||
}
|
||||
|
||||
private boolean onCorrectThread() {
|
||||
return (mHandler.getLooper() == Looper.myLooper());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.net.util;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.util.LocalLog;
|
||||
import android.util.Log;
|
||||
@@ -90,6 +91,13 @@ public class SharedLog {
|
||||
Log.e(mTag, record(Category.ERROR, msg));
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an error due to an exception, with the exception stacktrace.
|
||||
*/
|
||||
public void e(@NonNull String msg, @NonNull Throwable e) {
|
||||
Log.e(mTag, record(Category.ERROR, msg + ": " + e.getMessage()), e);
|
||||
}
|
||||
|
||||
public void i(String msg) {
|
||||
Log.i(mTag, record(Category.NONE, msg));
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ import static android.net.NetworkUtils.intToInet4AddressHTL;
|
||||
import static android.net.NetworkUtils.netmaskToPrefixLength;
|
||||
import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTH;
|
||||
import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTL;
|
||||
import static android.net.NetworkUtils.getBroadcastAddress;
|
||||
import static android.net.NetworkUtils.getPrefixMaskAsInet4Address;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
@@ -125,7 +127,6 @@ public class NetworkUtilsTest {
|
||||
assertInvalidNetworkMask(IPv4Address("255.255.0.255"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPrefixLengthToV4NetmaskIntHTL() {
|
||||
assertEquals(0, prefixLengthToV4NetmaskIntHTL(0));
|
||||
@@ -266,4 +267,44 @@ public class NetworkUtilsTest {
|
||||
assertEquals(BigInteger.valueOf(7l - 4 + 4 + 16 + 65536),
|
||||
NetworkUtils.routedIPv6AddressCount(set));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPrefixMaskAsAddress() {
|
||||
assertEquals("255.255.240.0", getPrefixMaskAsInet4Address(20).getHostAddress());
|
||||
assertEquals("255.0.0.0", getPrefixMaskAsInet4Address(8).getHostAddress());
|
||||
assertEquals("0.0.0.0", getPrefixMaskAsInet4Address(0).getHostAddress());
|
||||
assertEquals("255.255.255.255", getPrefixMaskAsInet4Address(32).getHostAddress());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testGetPrefixMaskAsAddress_PrefixTooLarge() {
|
||||
getPrefixMaskAsInet4Address(33);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testGetPrefixMaskAsAddress_NegativePrefix() {
|
||||
getPrefixMaskAsInet4Address(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBroadcastAddress() {
|
||||
assertEquals("192.168.15.255",
|
||||
getBroadcastAddress(IPv4Address("192.168.0.123"), 20).getHostAddress());
|
||||
assertEquals("192.255.255.255",
|
||||
getBroadcastAddress(IPv4Address("192.168.0.123"), 8).getHostAddress());
|
||||
assertEquals("192.168.0.123",
|
||||
getBroadcastAddress(IPv4Address("192.168.0.123"), 32).getHostAddress());
|
||||
assertEquals("255.255.255.255",
|
||||
getBroadcastAddress(IPv4Address("192.168.0.123"), 0).getHostAddress());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testGetBroadcastAddress_PrefixTooLarge() {
|
||||
getBroadcastAddress(IPv4Address("192.168.0.123"), 33);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testGetBroadcastAddress_NegativePrefix() {
|
||||
getBroadcastAddress(IPv4Address("192.168.0.123"), -1);
|
||||
}
|
||||
}
|
||||
|
||||
519
tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java
Normal file
519
tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java
Normal file
@@ -0,0 +1,519 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.dhcp;
|
||||
|
||||
import static android.net.dhcp.DhcpLease.HOSTNAME_NONE;
|
||||
import static android.net.dhcp.DhcpLeaseRepository.CLIENTID_UNSPEC;
|
||||
import static android.net.dhcp.DhcpLeaseRepository.INETADDR_UNSPEC;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.net.InetAddress.parseNumericAddress;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.net.IpPrefix;
|
||||
import android.net.MacAddress;
|
||||
import android.net.dhcp.DhcpLeaseRepository.Clock;
|
||||
import android.net.util.SharedLog;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class DhcpLeaseRepositoryTest {
|
||||
private static final Inet4Address INET4_ANY = (Inet4Address) Inet4Address.ANY;
|
||||
private static final Inet4Address TEST_DEF_ROUTER = parseAddr4("192.168.42.247");
|
||||
private static final Inet4Address TEST_SERVER_ADDR = parseAddr4("192.168.42.241");
|
||||
private static final Inet4Address TEST_RESERVED_ADDR = parseAddr4("192.168.42.243");
|
||||
private static final MacAddress TEST_MAC_1 = MacAddress.fromBytes(
|
||||
new byte[] { 5, 4, 3, 2, 1, 0 });
|
||||
private static final MacAddress TEST_MAC_2 = MacAddress.fromBytes(
|
||||
new byte[] { 0, 1, 2, 3, 4, 5 });
|
||||
private static final MacAddress TEST_MAC_3 = MacAddress.fromBytes(
|
||||
new byte[] { 0, 1, 2, 3, 4, 6 });
|
||||
private static final Inet4Address TEST_INETADDR_1 = parseAddr4("192.168.42.248");
|
||||
private static final Inet4Address TEST_INETADDR_2 = parseAddr4("192.168.42.249");
|
||||
private static final String TEST_HOSTNAME_1 = "hostname1";
|
||||
private static final String TEST_HOSTNAME_2 = "hostname2";
|
||||
private static final IpPrefix TEST_IP_PREFIX = new IpPrefix(TEST_SERVER_ADDR, 22);
|
||||
private static final long TEST_TIME = 100L;
|
||||
private static final int TEST_LEASE_TIME_MS = 3_600_000;
|
||||
private static final Set<Inet4Address> TEST_EXCL_SET =
|
||||
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
|
||||
TEST_SERVER_ADDR, TEST_DEF_ROUTER, TEST_RESERVED_ADDR)));
|
||||
|
||||
@NonNull
|
||||
private SharedLog mLog;
|
||||
@NonNull @Mock
|
||||
private Clock mClock;
|
||||
@NonNull
|
||||
private DhcpLeaseRepository mRepo;
|
||||
|
||||
private static Inet4Address parseAddr4(String inet4Addr) {
|
||||
return (Inet4Address) parseNumericAddress(inet4Addr);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mLog = new SharedLog("DhcpLeaseRepositoryTest");
|
||||
when(mClock.elapsedRealtime()).thenReturn(TEST_TIME);
|
||||
mRepo = new DhcpLeaseRepository(
|
||||
TEST_IP_PREFIX, TEST_EXCL_SET, TEST_LEASE_TIME_MS, mLog, mClock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a number of addresses through offer/request. Useful to test address exhaustion.
|
||||
* @param nAddr Number of addresses to request.
|
||||
*/
|
||||
private void requestAddresses(byte nAddr) throws Exception {
|
||||
final HashSet<Inet4Address> addrs = new HashSet<>();
|
||||
byte[] hwAddrBytes = new byte[] { 8, 4, 3, 2, 1, 0 };
|
||||
for (byte i = 0; i < nAddr; i++) {
|
||||
hwAddrBytes[5] = i;
|
||||
MacAddress newMac = MacAddress.fromBytes(hwAddrBytes);
|
||||
final String hostname = "host_" + i;
|
||||
final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, newMac,
|
||||
INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, hostname);
|
||||
|
||||
assertNotNull(lease);
|
||||
assertEquals(newMac, lease.getHwAddr());
|
||||
assertEquals(hostname, lease.getHostname());
|
||||
assertTrue(format("Duplicate address allocated: %s in %s", lease.getNetAddr(), addrs),
|
||||
addrs.add(lease.getNetAddr()));
|
||||
|
||||
mRepo.requestLease(null, newMac, null, lease.getNetAddr(), true, hostname);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddressExhaustion() throws Exception {
|
||||
// Use a /28 to quickly run out of addresses
|
||||
mRepo.updateParams(new IpPrefix(TEST_SERVER_ADDR, 28), TEST_EXCL_SET, TEST_LEASE_TIME_MS);
|
||||
|
||||
// /28 should have 16 addresses, 14 w/o the first/last, 11 w/o excluded addresses
|
||||
requestAddresses((byte)11);
|
||||
|
||||
try {
|
||||
mRepo.getOffer(null, TEST_MAC_2,
|
||||
null /* relayAddr */, null /* reqAddr */, HOSTNAME_NONE);
|
||||
fail("Should be out of addresses");
|
||||
} catch (DhcpLeaseRepository.OutOfAddressesException e) {
|
||||
// Expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateParams_LeaseCleanup() throws Exception {
|
||||
// Inside /28:
|
||||
final Inet4Address reqAddrIn28 = parseAddr4("192.168.42.242");
|
||||
final Inet4Address declinedAddrIn28 = parseAddr4("192.168.42.245");
|
||||
|
||||
// Inside /28, but not available there (first address of the range)
|
||||
final Inet4Address declinedFirstAddrIn28 = parseAddr4("192.168.42.240");
|
||||
|
||||
final DhcpLease reqAddrIn28Lease = mRepo.requestLease(
|
||||
CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, reqAddrIn28, false, HOSTNAME_NONE);
|
||||
mRepo.markLeaseDeclined(declinedAddrIn28);
|
||||
mRepo.markLeaseDeclined(declinedFirstAddrIn28);
|
||||
|
||||
// Inside /22, but outside /28:
|
||||
final Inet4Address reqAddrIn22 = parseAddr4("192.168.42.3");
|
||||
final Inet4Address declinedAddrIn22 = parseAddr4("192.168.42.4");
|
||||
|
||||
final DhcpLease reqAddrIn22Lease = mRepo.requestLease(
|
||||
CLIENTID_UNSPEC, TEST_MAC_3, INET4_ANY, reqAddrIn22, false, HOSTNAME_NONE);
|
||||
mRepo.markLeaseDeclined(declinedAddrIn22);
|
||||
|
||||
// Address that will be reserved in the updateParams call below
|
||||
final Inet4Address reservedAddr = parseAddr4("192.168.42.244");
|
||||
final DhcpLease reservedAddrLease = mRepo.requestLease(
|
||||
CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY, reservedAddr, false, HOSTNAME_NONE);
|
||||
|
||||
// Update from /22 to /28 and add another reserved address
|
||||
Set<Inet4Address> newReserved = new HashSet<>(TEST_EXCL_SET);
|
||||
newReserved.add(reservedAddr);
|
||||
mRepo.updateParams(new IpPrefix(TEST_SERVER_ADDR, 28), newReserved, TEST_LEASE_TIME_MS);
|
||||
|
||||
assertHasLease(reqAddrIn28Lease);
|
||||
assertDeclined(declinedAddrIn28);
|
||||
|
||||
assertNotDeclined(declinedFirstAddrIn28);
|
||||
|
||||
assertNoLease(reqAddrIn22Lease);
|
||||
assertNotDeclined(declinedAddrIn22);
|
||||
|
||||
assertNoLease(reservedAddrLease);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOffer_StableAddress() throws Exception {
|
||||
for (final MacAddress macAddr : new MacAddress[] { TEST_MAC_1, TEST_MAC_2, TEST_MAC_3 }) {
|
||||
final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, macAddr,
|
||||
INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
|
||||
|
||||
// Same lease is offered twice
|
||||
final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, macAddr,
|
||||
INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
|
||||
assertEquals(lease, newLease);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateParams_UsesNewPrefix() throws Exception {
|
||||
final IpPrefix newPrefix = new IpPrefix(parseAddr4("192.168.123.0"), 24);
|
||||
mRepo.updateParams(newPrefix, TEST_EXCL_SET, TEST_LEASE_TIME_MS);
|
||||
|
||||
DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
|
||||
INETADDR_UNSPEC, INETADDR_UNSPEC, HOSTNAME_NONE);
|
||||
assertTrue(newPrefix.contains(lease.getNetAddr()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOffer_ExistingLease() throws Exception {
|
||||
mRepo.requestLease(
|
||||
CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, TEST_INETADDR_1, false, TEST_HOSTNAME_1);
|
||||
|
||||
DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
|
||||
INETADDR_UNSPEC, INETADDR_UNSPEC, HOSTNAME_NONE);
|
||||
assertEquals(TEST_INETADDR_1, offer.getNetAddr());
|
||||
assertEquals(TEST_HOSTNAME_1, offer.getHostname());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOffer_ClientIdHasExistingLease() throws Exception {
|
||||
final byte[] clientId = new byte[] { 1, 2 };
|
||||
mRepo.requestLease(clientId, TEST_MAC_1, INET4_ANY, TEST_INETADDR_1, false,
|
||||
TEST_HOSTNAME_1);
|
||||
|
||||
// Different MAC, but same clientId
|
||||
DhcpLease offer = mRepo.getOffer(clientId, TEST_MAC_2,
|
||||
INETADDR_UNSPEC, INETADDR_UNSPEC, HOSTNAME_NONE);
|
||||
assertEquals(TEST_INETADDR_1, offer.getNetAddr());
|
||||
assertEquals(TEST_HOSTNAME_1, offer.getHostname());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOffer_DifferentClientId() throws Exception {
|
||||
final byte[] clientId1 = new byte[] { 1, 2 };
|
||||
final byte[] clientId2 = new byte[] { 3, 4 };
|
||||
mRepo.requestLease(clientId1, TEST_MAC_1, INET4_ANY, TEST_INETADDR_1, false,
|
||||
TEST_HOSTNAME_1);
|
||||
|
||||
// Same MAC, different client ID
|
||||
DhcpLease offer = mRepo.getOffer(clientId2, TEST_MAC_1,
|
||||
INETADDR_UNSPEC, INETADDR_UNSPEC, HOSTNAME_NONE);
|
||||
// Obtains a different address
|
||||
assertNotEquals(TEST_INETADDR_1, offer.getNetAddr());
|
||||
assertEquals(HOSTNAME_NONE, offer.getHostname());
|
||||
assertEquals(TEST_MAC_1, offer.getHwAddr());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOffer_RequestedAddress() throws Exception {
|
||||
DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY,
|
||||
TEST_INETADDR_1, TEST_HOSTNAME_1);
|
||||
assertEquals(TEST_INETADDR_1, offer.getNetAddr());
|
||||
assertEquals(TEST_HOSTNAME_1, offer.getHostname());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOffer_RequestedAddressInUse() throws Exception {
|
||||
mRepo.requestLease(
|
||||
CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, TEST_INETADDR_1, false, HOSTNAME_NONE);
|
||||
DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY,
|
||||
TEST_INETADDR_1, HOSTNAME_NONE);
|
||||
assertNotEquals(TEST_INETADDR_1, offer.getNetAddr());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOffer_RequestedAddressReserved() throws Exception {
|
||||
DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY,
|
||||
TEST_RESERVED_ADDR, HOSTNAME_NONE);
|
||||
assertNotEquals(TEST_RESERVED_ADDR, offer.getNetAddr());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOffer_RequestedAddressInvalid() throws Exception {
|
||||
final Inet4Address invalidAddr = parseAddr4("192.168.42.0");
|
||||
DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY,
|
||||
invalidAddr, HOSTNAME_NONE);
|
||||
assertNotEquals(invalidAddr, offer.getNetAddr());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOffer_RequestedAddressOutsideSubnet() throws Exception {
|
||||
final Inet4Address invalidAddr = parseAddr4("192.168.254.2");
|
||||
DhcpLease offer = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY,
|
||||
invalidAddr, HOSTNAME_NONE);
|
||||
assertNotEquals(invalidAddr, offer.getNetAddr());
|
||||
}
|
||||
|
||||
@Test(expected = DhcpLeaseRepository.InvalidAddressException.class)
|
||||
public void testGetOffer_RelayInInvalidSubnet() throws Exception {
|
||||
mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
|
||||
parseAddr4("192.168.254.2") /* relayAddr */, INETADDR_UNSPEC, HOSTNAME_NONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestLease_SelectingTwice() throws Exception {
|
||||
DhcpLease lease1 = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY,
|
||||
TEST_INETADDR_1, true /* sidSet */, TEST_HOSTNAME_1);
|
||||
|
||||
// Second request from same client for a different address
|
||||
DhcpLease lease2 = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY,
|
||||
TEST_INETADDR_2, true /* sidSet */, TEST_HOSTNAME_2);
|
||||
|
||||
assertEquals(TEST_INETADDR_1, lease1.getNetAddr());
|
||||
assertEquals(TEST_HOSTNAME_1, lease1.getHostname());
|
||||
|
||||
assertEquals(TEST_INETADDR_2, lease2.getNetAddr());
|
||||
assertEquals(TEST_HOSTNAME_2, lease2.getHostname());
|
||||
|
||||
// First address freed when client requested a different one: another client can request it
|
||||
DhcpLease lease3 = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY,
|
||||
TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE);
|
||||
assertEquals(TEST_INETADDR_1, lease3.getNetAddr());
|
||||
}
|
||||
|
||||
@Test(expected = DhcpLeaseRepository.InvalidAddressException.class)
|
||||
public void testRequestLease_SelectingInvalid() throws Exception {
|
||||
mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY,
|
||||
parseAddr4("192.168.254.5"), true /* sidSet */, HOSTNAME_NONE);
|
||||
}
|
||||
|
||||
@Test(expected = DhcpLeaseRepository.InvalidAddressException.class)
|
||||
public void testRequestLease_SelectingInUse() throws Exception {
|
||||
mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY,
|
||||
TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE);
|
||||
mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY,
|
||||
TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE);
|
||||
}
|
||||
|
||||
@Test(expected = DhcpLeaseRepository.InvalidAddressException.class)
|
||||
public void testRequestLease_SelectingReserved() throws Exception {
|
||||
mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY,
|
||||
TEST_RESERVED_ADDR, true /* sidSet */, HOSTNAME_NONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestLease_InitReboot() throws Exception {
|
||||
// Request address once
|
||||
mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY,
|
||||
TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE);
|
||||
|
||||
final long newTime = TEST_TIME + 100;
|
||||
when(mClock.elapsedRealtime()).thenReturn(newTime);
|
||||
|
||||
// init-reboot (sidSet == false): verify configuration
|
||||
DhcpLease lease = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY,
|
||||
TEST_INETADDR_1, false, HOSTNAME_NONE);
|
||||
assertEquals(TEST_INETADDR_1, lease.getNetAddr());
|
||||
assertEquals(newTime + TEST_LEASE_TIME_MS, lease.getExpTime());
|
||||
}
|
||||
|
||||
@Test(expected = DhcpLeaseRepository.InvalidAddressException.class)
|
||||
public void testRequestLease_InitRebootWrongAddr() throws Exception {
|
||||
// Request address once
|
||||
mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY,
|
||||
TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE);
|
||||
// init-reboot with different requested address
|
||||
mRepo.requestLease(
|
||||
CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, TEST_INETADDR_2, false, HOSTNAME_NONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestLease_InitRebootUnknownAddr() throws Exception {
|
||||
// init-reboot with unknown requested address
|
||||
DhcpLease lease = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY,
|
||||
TEST_INETADDR_2, false, HOSTNAME_NONE);
|
||||
// RFC2131 says we should not reply to accommodate other servers, but since we are
|
||||
// authoritative we allow creating the lease to avoid issues with lost lease DB (same as
|
||||
// dnsmasq behavior)
|
||||
assertEquals(TEST_INETADDR_2, lease.getNetAddr());
|
||||
}
|
||||
|
||||
@Test(expected = DhcpLeaseRepository.InvalidAddressException.class)
|
||||
public void testRequestLease_InitRebootWrongSubnet() throws Exception {
|
||||
mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY,
|
||||
parseAddr4("192.168.254.2"), false /* sidSet */, HOSTNAME_NONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestLease_Renewing() throws Exception {
|
||||
mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1,
|
||||
INET4_ANY /* clientAddr */, TEST_INETADDR_1 /* reqAddr */, true, HOSTNAME_NONE);
|
||||
|
||||
final long newTime = TEST_TIME + 100;
|
||||
when(mClock.elapsedRealtime()).thenReturn(newTime);
|
||||
|
||||
// Renewing: clientAddr filled in, no reqAddr
|
||||
DhcpLease lease = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1,
|
||||
TEST_INETADDR_1 /* clientAddr */, INETADDR_UNSPEC /* reqAddr */, false,
|
||||
HOSTNAME_NONE);
|
||||
|
||||
assertEquals(TEST_INETADDR_1, lease.getNetAddr());
|
||||
assertEquals(newTime + TEST_LEASE_TIME_MS, lease.getExpTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestLease_RenewingUnknownAddr() throws Exception {
|
||||
final long newTime = TEST_TIME + 100;
|
||||
when(mClock.elapsedRealtime()).thenReturn(newTime);
|
||||
DhcpLease lease = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1,
|
||||
TEST_INETADDR_1 /* clientAddr */, INETADDR_UNSPEC /* reqAddr */, false,
|
||||
HOSTNAME_NONE);
|
||||
// Allows renewing an unknown address if available
|
||||
assertEquals(TEST_INETADDR_1, lease.getNetAddr());
|
||||
assertEquals(newTime + TEST_LEASE_TIME_MS, lease.getExpTime());
|
||||
}
|
||||
|
||||
@Test(expected = DhcpLeaseRepository.InvalidAddressException.class)
|
||||
public void testRequestLease_RenewingAddrInUse() throws Exception {
|
||||
mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_2,
|
||||
INET4_ANY /* clientAddr */, TEST_INETADDR_1 /* reqAddr */, true, HOSTNAME_NONE);
|
||||
mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1,
|
||||
TEST_INETADDR_1 /* clientAddr */, INETADDR_UNSPEC /* reqAddr */, false,
|
||||
HOSTNAME_NONE);
|
||||
}
|
||||
|
||||
@Test(expected = DhcpLeaseRepository.InvalidAddressException.class)
|
||||
public void testRequestLease_RenewingInvalidAddr() throws Exception {
|
||||
mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, parseAddr4("192.168.254.2") /* clientAddr */,
|
||||
INETADDR_UNSPEC /* reqAddr */, false, HOSTNAME_NONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReleaseLease() throws Exception {
|
||||
DhcpLease lease1 = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY,
|
||||
TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE);
|
||||
|
||||
assertHasLease(lease1);
|
||||
assertTrue(mRepo.releaseLease(CLIENTID_UNSPEC, TEST_MAC_1, TEST_INETADDR_1));
|
||||
assertNoLease(lease1);
|
||||
|
||||
DhcpLease lease2 = mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY,
|
||||
TEST_INETADDR_1, true /* sidSet */, HOSTNAME_NONE);
|
||||
|
||||
assertEquals(TEST_INETADDR_1, lease2.getNetAddr());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReleaseLease_UnknownLease() {
|
||||
assertFalse(mRepo.releaseLease(CLIENTID_UNSPEC, TEST_MAC_1, TEST_INETADDR_1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReleaseLease_StableOffer() throws Exception {
|
||||
for (MacAddress mac : new MacAddress[] { TEST_MAC_1, TEST_MAC_2, TEST_MAC_3 }) {
|
||||
final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, mac,
|
||||
INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
|
||||
mRepo.requestLease(
|
||||
CLIENTID_UNSPEC, mac, INET4_ANY, lease.getNetAddr(), true,
|
||||
HOSTNAME_NONE);
|
||||
mRepo.releaseLease(CLIENTID_UNSPEC, mac, lease.getNetAddr());
|
||||
|
||||
// Same lease is offered after it was released
|
||||
final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, mac,
|
||||
INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
|
||||
assertEquals(lease.getNetAddr(), newLease.getNetAddr());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMarkLeaseDeclined() throws Exception {
|
||||
final DhcpLease lease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
|
||||
INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
|
||||
|
||||
mRepo.markLeaseDeclined(lease.getNetAddr());
|
||||
|
||||
// Same lease is not offered again
|
||||
final DhcpLease newLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
|
||||
INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
|
||||
assertNotEquals(lease.getNetAddr(), newLease.getNetAddr());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMarkLeaseDeclined_UsedIfOutOfAddresses() throws Exception {
|
||||
// Use a /28 to quickly run out of addresses
|
||||
mRepo.updateParams(new IpPrefix(TEST_SERVER_ADDR, 28), TEST_EXCL_SET, TEST_LEASE_TIME_MS);
|
||||
|
||||
mRepo.markLeaseDeclined(TEST_INETADDR_1);
|
||||
mRepo.markLeaseDeclined(TEST_INETADDR_2);
|
||||
|
||||
// /28 should have 16 addresses, 14 w/o the first/last, 11 w/o excluded addresses
|
||||
requestAddresses((byte)9);
|
||||
|
||||
// Last 2 addresses: addresses marked declined should be used
|
||||
final DhcpLease firstLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_1,
|
||||
INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_1);
|
||||
mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_1, INET4_ANY, firstLease.getNetAddr(), true,
|
||||
HOSTNAME_NONE);
|
||||
|
||||
final DhcpLease secondLease = mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_2,
|
||||
INETADDR_UNSPEC /* relayAddr */, INETADDR_UNSPEC /* reqAddr */, TEST_HOSTNAME_2);
|
||||
mRepo.requestLease(CLIENTID_UNSPEC, TEST_MAC_2, INET4_ANY, secondLease.getNetAddr(), true,
|
||||
HOSTNAME_NONE);
|
||||
|
||||
// Now out of addresses
|
||||
try {
|
||||
mRepo.getOffer(CLIENTID_UNSPEC, TEST_MAC_3, INETADDR_UNSPEC /* relayAddr */,
|
||||
INETADDR_UNSPEC /* reqAddr */, HOSTNAME_NONE);
|
||||
fail("Repository should be out of addresses and throw");
|
||||
} catch (DhcpLeaseRepository.OutOfAddressesException e) { /* expected */ }
|
||||
|
||||
assertEquals(TEST_INETADDR_1, firstLease.getNetAddr());
|
||||
assertEquals(TEST_HOSTNAME_1, firstLease.getHostname());
|
||||
assertEquals(TEST_INETADDR_2, secondLease.getNetAddr());
|
||||
assertEquals(TEST_HOSTNAME_2, secondLease.getHostname());
|
||||
}
|
||||
|
||||
private void assertNoLease(DhcpLease lease) {
|
||||
assertFalse("Leases contain " + lease, mRepo.getCommittedLeases().contains(lease));
|
||||
}
|
||||
|
||||
private void assertHasLease(DhcpLease lease) {
|
||||
assertTrue("Leases do not contain " + lease, mRepo.getCommittedLeases().contains(lease));
|
||||
}
|
||||
|
||||
private void assertNotDeclined(Inet4Address addr) {
|
||||
assertFalse("Address is declined: " + addr, mRepo.getDeclinedAddresses().contains(addr));
|
||||
}
|
||||
|
||||
private void assertDeclined(Inet4Address addr) {
|
||||
assertTrue("Address is not declined: " + addr, mRepo.getDeclinedAddresses().contains(addr));
|
||||
}
|
||||
}
|
||||
177
tests/net/java/android/net/dhcp/DhcpServingParamsTest.java
Normal file
177
tests/net/java/android/net/dhcp/DhcpServingParamsTest.java
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.net.dhcp;
|
||||
|
||||
import static android.net.dhcp.DhcpServingParams.MTU_UNSET;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import static java.net.InetAddress.parseNumericAddress;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.dhcp.DhcpServingParams.InvalidParameterException;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class DhcpServingParamsTest {
|
||||
@NonNull
|
||||
private DhcpServingParams.Builder mBuilder;
|
||||
|
||||
private static final Set<Inet4Address> TEST_DEFAULT_ROUTERS = new HashSet<>(
|
||||
Arrays.asList(parseAddr("192.168.0.123"), parseAddr("192.168.0.124")));
|
||||
private static final long TEST_LEASE_TIME_SECS = 3600L;
|
||||
private static final Set<Inet4Address> TEST_DNS_SERVERS = new HashSet<>(
|
||||
Arrays.asList(parseAddr("192.168.0.126"), parseAddr("192.168.0.127")));
|
||||
private static final Inet4Address TEST_SERVER_ADDR = parseAddr("192.168.0.2");
|
||||
private static final LinkAddress TEST_LINKADDR = new LinkAddress(TEST_SERVER_ADDR, 20);
|
||||
private static final int TEST_MTU = 1500;
|
||||
private static final Set<Inet4Address> TEST_EXCLUDED_ADDRS = new HashSet<>(
|
||||
Arrays.asList(parseAddr("192.168.0.200"), parseAddr("192.168.0.201")));
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mBuilder = new DhcpServingParams.Builder()
|
||||
.setDefaultRouters(TEST_DEFAULT_ROUTERS)
|
||||
.setDhcpLeaseTimeSecs(TEST_LEASE_TIME_SECS)
|
||||
.setDnsServers(TEST_DNS_SERVERS)
|
||||
.setServerAddr(TEST_LINKADDR)
|
||||
.setLinkMtu(TEST_MTU)
|
||||
.setExcludedAddrs(TEST_EXCLUDED_ADDRS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuild_Immutable() throws InvalidParameterException {
|
||||
final Set<Inet4Address> routers = new HashSet<>(TEST_DEFAULT_ROUTERS);
|
||||
final Set<Inet4Address> dnsServers = new HashSet<>(TEST_DNS_SERVERS);
|
||||
final Set<Inet4Address> excludedAddrs = new HashSet<>(TEST_EXCLUDED_ADDRS);
|
||||
|
||||
final DhcpServingParams params = mBuilder
|
||||
.setDefaultRouters(routers)
|
||||
.setDnsServers(dnsServers)
|
||||
.setExcludedAddrs(excludedAddrs)
|
||||
.build();
|
||||
|
||||
// Modifications to source objects should not affect builder or final parameters
|
||||
final Inet4Address addedAddr = parseAddr("192.168.0.223");
|
||||
routers.add(addedAddr);
|
||||
dnsServers.add(addedAddr);
|
||||
excludedAddrs.add(addedAddr);
|
||||
|
||||
assertEquals(TEST_DEFAULT_ROUTERS, params.defaultRouters);
|
||||
assertEquals(TEST_LEASE_TIME_SECS, params.dhcpLeaseTimeSecs);
|
||||
assertEquals(TEST_DNS_SERVERS, params.dnsServers);
|
||||
assertEquals(TEST_LINKADDR, params.serverAddr);
|
||||
assertEquals(TEST_MTU, params.linkMtu);
|
||||
|
||||
assertContains(params.excludedAddrs, TEST_EXCLUDED_ADDRS);
|
||||
assertContains(params.excludedAddrs, TEST_DEFAULT_ROUTERS);
|
||||
assertContains(params.excludedAddrs, TEST_DNS_SERVERS);
|
||||
assertContains(params.excludedAddrs, TEST_SERVER_ADDR);
|
||||
|
||||
assertFalse("excludedAddrs should not contain " + addedAddr,
|
||||
params.excludedAddrs.contains(addedAddr));
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterException.class)
|
||||
public void testBuild_NegativeLeaseTime() throws InvalidParameterException {
|
||||
mBuilder.setDhcpLeaseTimeSecs(-1).build();
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterException.class)
|
||||
public void testBuild_LeaseTimeTooLarge() throws InvalidParameterException {
|
||||
// Set lease time larger than max value for uint32
|
||||
mBuilder.setDhcpLeaseTimeSecs(1L << 32).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuild_InfiniteLeaseTime() throws InvalidParameterException {
|
||||
final long infiniteLeaseTime = 0xffffffffL;
|
||||
final DhcpServingParams params = mBuilder
|
||||
.setDhcpLeaseTimeSecs(infiniteLeaseTime).build();
|
||||
assertEquals(infiniteLeaseTime, params.dhcpLeaseTimeSecs);
|
||||
assertTrue(params.dhcpLeaseTimeSecs > 0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuild_UnsetMtu() throws InvalidParameterException {
|
||||
final DhcpServingParams params = mBuilder.setLinkMtu(MTU_UNSET).build();
|
||||
assertEquals(MTU_UNSET, params.linkMtu);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterException.class)
|
||||
public void testBuild_MtuTooSmall() throws InvalidParameterException {
|
||||
mBuilder.setLinkMtu(20).build();
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterException.class)
|
||||
public void testBuild_MtuTooLarge() throws InvalidParameterException {
|
||||
mBuilder.setLinkMtu(65_536).build();
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterException.class)
|
||||
public void testBuild_IPv6Addr() throws InvalidParameterException {
|
||||
mBuilder.setServerAddr(new LinkAddress(parseNumericAddress("fe80::1111"), 120)).build();
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterException.class)
|
||||
public void testBuild_PrefixTooLarge() throws InvalidParameterException {
|
||||
mBuilder.setServerAddr(new LinkAddress(TEST_SERVER_ADDR, 15)).build();
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterException.class)
|
||||
public void testBuild_PrefixTooSmall() throws InvalidParameterException {
|
||||
mBuilder.setDefaultRouters(Collections.singleton(parseAddr("192.168.0.254")))
|
||||
.setServerAddr(new LinkAddress(TEST_SERVER_ADDR, 31))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterException.class)
|
||||
public void testBuild_RouterNotInPrefix() throws InvalidParameterException {
|
||||
mBuilder.setDefaultRouters(Collections.singleton(parseAddr("192.168.254.254"))).build();
|
||||
}
|
||||
|
||||
private static <T> void assertContains(@NonNull Set<T> set, @NonNull Set<T> subset) {
|
||||
for (final T elem : subset) {
|
||||
assertContains(set, elem);
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> void assertContains(@NonNull Set<T> set, @Nullable T elem) {
|
||||
assertTrue("Set does not contain " + elem, set.contains(elem));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static Inet4Address parseAddr(@NonNull String inet4Addr) {
|
||||
return (Inet4Address) parseNumericAddress(inet4Addr);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user