/* * 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 com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address; import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH; import static com.android.net.module.util.NetworkStackConstants.INFINITE_LEASE; import static com.android.net.module.util.NetworkStackConstants.IPV4_MAX_MTU; import static com.android.net.module.util.NetworkStackConstants.IPV4_MIN_MTU; import static java.lang.Integer.toUnsignedLong; import android.net.IpPrefix; import android.net.LinkAddress; import android.util.ArraySet; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.net.module.util.Inet4AddressUtils; import java.net.Inet4Address; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; /** * Parameters used by the DhcpServer to serve requests. * *

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 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 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 excludedAddrs; // DHCP uses uint32. Use long for clearer code, and check range when building. public final long dhcpLeaseTimeSecs; public final int linkMtu; /** * Indicates whether the DHCP server should send the ANDROID_METERED vendor-specific option. */ public final boolean metered; /** * Client inet address. This will be the only address offered by DhcpServer if set. */ @Nullable public final Inet4Address singleClientAddr; /** * Indicates whether the DHCP server should request a new prefix from IpServer when receiving * DHCPDECLINE message in certain particular link (e.g. there is only one downstream USB * tethering client). If it's false, process DHCPDECLINE message as RFC2131#4.3.3 suggests. */ public final boolean changePrefixOnDecline; /** * 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 defaultRouters, @NonNull Set dnsServers, @NonNull Set excludedAddrs, long dhcpLeaseTimeSecs, int linkMtu, boolean metered, Inet4Address singleClientAddr, boolean changePrefixOnDecline) { this.serverAddr = serverAddr; this.defaultRouters = defaultRouters; this.dnsServers = dnsServers; this.excludedAddrs = excludedAddrs; this.dhcpLeaseTimeSecs = dhcpLeaseTimeSecs; this.linkMtu = linkMtu; this.metered = metered; this.singleClientAddr = singleClientAddr; this.changePrefixOnDecline = changePrefixOnDecline; } /** * Create parameters from a stable AIDL-compatible parcel. * @throws InvalidParameterException The parameters parcelable is null or invalid. */ public static DhcpServingParams fromParcelableObject(@Nullable DhcpServingParamsParcel parcel) throws InvalidParameterException { if (parcel == null) { throw new InvalidParameterException("Null serving parameters"); } final LinkAddress serverAddr = new LinkAddress( intToInet4AddressHTH(parcel.serverAddr), parcel.serverAddrPrefixLength); Inet4Address clientAddr = null; if (parcel.singleClientAddr != 0) { clientAddr = intToInet4AddressHTH(parcel.singleClientAddr); } return new Builder() .setServerAddr(serverAddr) .setDefaultRouters(toInet4AddressSet(parcel.defaultRouters)) .setDnsServers(toInet4AddressSet(parcel.dnsServers)) .setExcludedAddrs(toInet4AddressSet(parcel.excludedAddrs)) .setDhcpLeaseTimeSecs(parcel.dhcpLeaseTimeSecs) .setLinkMtu(parcel.linkMtu) .setMetered(parcel.metered) .setSingleClientAddr(clientAddr) .setChangePrefixOnDecline(parcel.changePrefixOnDecline) .build(); } private static Set toInet4AddressSet(@Nullable int[] addrs) { if (addrs == null) { return new HashSet<>(0); } final HashSet res = new HashSet<>(); for (int addr : addrs) { res.add(intToInet4AddressHTH(addr)); } return res; } @NonNull public Inet4Address getServerInet4Addr() { return (Inet4Address) serverAddr.getAddress(); } /** * Get the served prefix mask as an IPv4 address. * *

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

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 Inet4AddressUtils.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 mServerAddr; private Set mDefaultRouters; private Set mDnsServers; private Set mExcludedAddrs; private long mDhcpLeaseTimeSecs; private int mLinkMtu = MTU_UNSET; private boolean mMetered; private Inet4Address mClientAddr; private boolean mChangePrefixOnDecline; /** * Set the server address and served prefix for the DHCP server. * *

This parameter is required. */ public Builder setServerAddr(@NonNull LinkAddress serverAddr) { this.mServerAddr = serverAddr; return this; } /** * Set the default routers to be advertised to DHCP clients. * *

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 defaultRouters) { this.mDefaultRouters = defaultRouters; return this; } /** * Set the default routers to be advertised to DHCP clients. * *

Each router must be inside the served prefix. This may be an empty list of routers, * but it must always be set explicitly before building the {@link DhcpServingParams}. */ public Builder setDefaultRouters(@NonNull Inet4Address... defaultRouters) { return setDefaultRouters(makeArraySet(defaultRouters)); } /** * Convenience method to build the parameters with no default router. * *

Equivalent to calling {@link #setDefaultRouters(Inet4Address...)} with no address. */ public Builder withNoDefaultRouter() { return setDefaultRouters(); } /** * Set the DNS servers to be advertised to DHCP clients. * *

This may be an empty set, but it must always be set explicitly before building the * {@link DhcpServingParams}. */ public Builder setDnsServers(@NonNull Set dnsServers) { this.mDnsServers = dnsServers; return this; } /** * Set the DNS servers to be advertised to DHCP clients. * *

This may be an empty list of servers, but it must always be set explicitly before * building the {@link DhcpServingParams}. */ public Builder setDnsServers(@NonNull Inet4Address... dnsServers) { return setDnsServers(makeArraySet(dnsServers)); } /** * Convenience method to build the parameters with no DNS server. * *

Equivalent to calling {@link #setDnsServers(Inet4Address...)} with no address. */ public Builder withNoDnsServer() { return setDnsServers(); } /** * Set excluded addresses that the DHCP server is not allowed to assign to clients. * *

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 excludedAddrs) { this.mExcludedAddrs = excludedAddrs; return this; } /** * Set excluded addresses that the DHCP server is not allowed to assign to clients. * *

This parameter is optional. DNS servers and default routers are always excluded * and do not need to be set here. */ public Builder setExcludedAddrs(@NonNull Inet4Address... excludedAddrs) { return setExcludedAddrs(makeArraySet(excludedAddrs)); } /** * Set the lease time for leases assigned by the DHCP server. * *

This parameter is required. */ public Builder setDhcpLeaseTimeSecs(long dhcpLeaseTimeSecs) { this.mDhcpLeaseTimeSecs = dhcpLeaseTimeSecs; return this; } /** * Set the link MTU to be advertised to DHCP clients. * *

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.mLinkMtu = linkMtu; return this; } /** * Set whether the DHCP server should send the ANDROID_METERED vendor-specific option. * *

If not set, the default value is false. */ public Builder setMetered(boolean metered) { this.mMetered = metered; return this; } /** * Set the client address. * *

If not set, the default value is null. */ public Builder setSingleClientAddr(@Nullable Inet4Address clientAddr) { this.mClientAddr = clientAddr; return this; } /** * Set whether the DHCP server should request a new prefix from IpServer when receiving * DHCPDECLINE message in certain particular link. * *

If not set, the default value is false. */ public Builder setChangePrefixOnDecline(boolean changePrefixOnDecline) { this.mChangePrefixOnDecline = changePrefixOnDecline; return this; } /** * Create a new {@link DhcpServingParams} instance based on parameters set in the builder. * *

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 (mServerAddr == null) { throw new InvalidParameterException("Missing serverAddr"); } if (mDefaultRouters == null) { throw new InvalidParameterException("Missing defaultRouters"); } if (mDnsServers == null) { // Empty set is OK, but enforce explicitly setting it throw new InvalidParameterException("Missing dnsServers"); } if (mDhcpLeaseTimeSecs <= 0 || mDhcpLeaseTimeSecs > toUnsignedLong(INFINITE_LEASE)) { throw new InvalidParameterException("Invalid lease time: " + mDhcpLeaseTimeSecs); } if (mLinkMtu != MTU_UNSET && (mLinkMtu < IPV4_MIN_MTU || mLinkMtu > IPV4_MAX_MTU)) { throw new InvalidParameterException("Invalid link MTU: " + mLinkMtu); } if (!mServerAddr.isIpv4()) { throw new InvalidParameterException("serverAddr must be IPv4"); } if (mServerAddr.getPrefixLength() < MIN_PREFIX_LENGTH || mServerAddr.getPrefixLength() > MAX_PREFIX_LENGTH) { throw new InvalidParameterException("Prefix length is not in supported range"); } final IpPrefix prefix = makeIpPrefix(mServerAddr); for (Inet4Address addr : mDefaultRouters) { if (!prefix.contains(addr)) { throw new InvalidParameterException(String.format( "Default router %s is not in server prefix %s", addr, mServerAddr)); } } final Set excl = new HashSet<>(); if (mExcludedAddrs != null) { excl.addAll(mExcludedAddrs); } excl.add((Inet4Address) mServerAddr.getAddress()); excl.addAll(mDefaultRouters); excl.addAll(mDnsServers); return new DhcpServingParams(mServerAddr, Collections.unmodifiableSet(new HashSet<>(mDefaultRouters)), Collections.unmodifiableSet(new HashSet<>(mDnsServers)), Collections.unmodifiableSet(excl), mDhcpLeaseTimeSecs, mLinkMtu, mMetered, mClientAddr, mChangePrefixOnDecline); } } /** * Utility method to create an IpPrefix with the address and prefix length of a LinkAddress. */ @NonNull static IpPrefix makeIpPrefix(@NonNull LinkAddress addr) { return new IpPrefix(addr.getAddress(), addr.getPrefixLength()); } private static ArraySet makeArraySet(T[] elements) { final ArraySet set = new ArraySet<>(elements.length); set.addAll(Arrays.asList(elements)); return set; } }