blob: f3f92b37930a8ad72de7be8891b2640714c123de [file] [log] [blame]
/*
* Copyright (C) 2023 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.dhcp6;
import static android.net.dhcp6.Dhcp6Packet.PrefixDelegation;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static android.system.OsConstants.AF_INET6;
import static android.system.OsConstants.IFA_F_NODAD;
import static android.system.OsConstants.IPPROTO_UDP;
import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
import static android.system.OsConstants.SOCK_DGRAM;
import static android.system.OsConstants.SOCK_NONBLOCK;
import static com.android.net.module.util.NetworkStackConstants.ALL_DHCP_RELAY_AGENTS_AND_SERVERS;
import static com.android.net.module.util.NetworkStackConstants.DHCP6_CLIENT_PORT;
import static com.android.net.module.util.NetworkStackConstants.DHCP6_SERVER_PORT;
import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ANY;
import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH;
import static com.android.networkstack.apishim.ConstantsShim.IFA_F_MANAGETEMPADDR;
import static com.android.networkstack.apishim.ConstantsShim.IFA_F_NOPREFIXROUTE;
import static com.android.networkstack.util.NetworkStackUtils.createInet6AddressFromEui64;
import static com.android.networkstack.util.NetworkStackUtils.macAddressToEui64;
import android.content.Context;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.ip.IpClient;
import android.net.util.SocketUtils;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.util.HexDump;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.internal.util.WakeupMessage;
import com.android.net.module.util.DeviceConfigUtils;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.PacketReader;
import com.android.net.module.util.netlink.NetlinkUtils;
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.Inet6Address;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Random;
/**
* A DHCPv6 client.
*
* So far only support IA_PD (prefix delegation), not for IA_NA/IA_TA yet.
*
* @hide
*/
public class Dhcp6Client extends StateMachine {
private static final String TAG = Dhcp6Client.class.getSimpleName();
private static final boolean DBG = true;
// Dhcp6Client shares the same handler with IpClient, define the base command range for
// both public and private messages used in Dhcp6Client, to avoid commands overlap.
// Public messages.
private static final int PUBLIC_BASE = IpClient.DHCP6CLIENT_CMD_BASE;
// Commands from controller to start/stop DHCPv6
public static final int CMD_START_DHCP6 = PUBLIC_BASE + 1;
public static final int CMD_STOP_DHCP6 = PUBLIC_BASE + 2;
// Notification from DHCPv6 state machine post DHCPv6 discovery/renewal. Indicates
// success/failure
public static final int CMD_DHCP6_RESULT = PUBLIC_BASE + 3;
// Message.arg1 arguments to CMD_DHCP6_RESULT notification
public static final int DHCP6_PD_SUCCESS = 1;
public static final int DHCP6_PD_PREFIX_EXPIRED = 2;
public static final int DHCP6_PD_PREFIX_CHANGED = 3;
// Notification from DHCPv6 state machine before quitting
public static final int CMD_ON_QUIT = PUBLIC_BASE + 4;
// Internal messages.
private static final int PRIVATE_BASE = IpClient.DHCP6CLIENT_CMD_BASE + 100;
private static final int CMD_RECEIVED_PACKET = PRIVATE_BASE + 1;
private static final int CMD_KICK = PRIVATE_BASE + 2;
private static final int CMD_DHCP6_PD_RENEW = PRIVATE_BASE + 3;
private static final int CMD_DHCP6_PD_REBIND = PRIVATE_BASE + 4;
private static final int CMD_DHCP6_PD_EXPIRE = PRIVATE_BASE + 5;
// Timers and timeouts.
// TODO: comply with RFC8415 section 15(Reliability of Client-Initiated Message Exchanges)
private static final int SECONDS = 1000;
private static final int FIRST_TIMEOUT_MS = 1 * SECONDS;
private static final int MAX_TIMEOUT_MS = 512 * SECONDS;
// Per rfc8415#section-12, the IAID MUST be consistent across restarts.
// Since currently only one IAID is supported, a well-known value can be used (0).
private static final int IAID = 0;
private int mTransId;
private long mTransStartMillis;
@Nullable private PrefixDelegation mAdvertise;
@Nullable private PrefixDelegation mReply;
@Nullable private byte[] mServerDuid;
// State variables.
@NonNull private final Dependencies mDependencies;
@NonNull private final Context mContext;
@NonNull private final Random mRandom;
@NonNull private final StateMachine mController;
@NonNull private final WakeupMessage mKickAlarm;
@NonNull private final WakeupMessage mRenewAlarm;
@NonNull private final WakeupMessage mRebindAlarm;
@NonNull private final WakeupMessage mExpiryAlarm;
@NonNull private final InterfaceParams mIface;
@NonNull private final Dhcp6PacketHandler mDhcp6PacketHandler;
@NonNull private final byte[] mClientDuid;
// States.
private State mStoppedState = new StoppedState();
private State mStartedState = new StartedState();
private State mSolicitState = new SolicitState();
private State mRequestState = new RequestState();
private State mHaveLeaseState = new HaveLeaseState();
private State mBoundState = new BoundState();
private State mRenewState = new RenewState();
private State mRebindState = new RebindState();
/**
* Encapsulates Dhcp6Client depencencies that's used for unit testing and
* integration testing.
*/
public static class Dependencies {
/**
* Read an integer DeviceConfig property.
*/
public int getDeviceConfigPropertyInt(String name, int defaultValue) {
return DeviceConfigUtils.getDeviceConfigPropertyInt(NAMESPACE_CONNECTIVITY, name,
defaultValue);
}
}
private WakeupMessage makeWakeupMessage(String cmdName, int cmd) {
cmdName = Dhcp6Client.class.getSimpleName() + "." + mIface.name + "." + cmdName;
return new WakeupMessage(mContext, getHandler(), cmdName, cmd);
}
private Dhcp6Client(@NonNull final Context context, @NonNull final StateMachine controller,
@NonNull final InterfaceParams iface, @NonNull final Dependencies deps) {
super(TAG, controller.getHandler());
mDependencies = deps;
mContext = context;
mController = controller;
mIface = iface;
mClientDuid = Dhcp6Packet.createClientDuid(iface.macAddr);
mDhcp6PacketHandler = new Dhcp6PacketHandler(getHandler());
addState(mStoppedState);
addState(mStartedState); {
addState(mSolicitState, mStartedState);
addState(mRequestState, mStartedState);
addState(mHaveLeaseState, mStartedState); {
addState(mBoundState, mHaveLeaseState);
addState(mRenewState, mHaveLeaseState);
addState(mRebindState, mHaveLeaseState);
}
}
setInitialState(mStoppedState);
mRandom = new Random();
// Used to schedule packet retransmissions.
mKickAlarm = makeWakeupMessage("KICK", CMD_KICK);
// Used to schedule DHCP reacquisition.
mRenewAlarm = makeWakeupMessage("RENEW", CMD_DHCP6_PD_RENEW);
mRebindAlarm = makeWakeupMessage("REBIND", CMD_DHCP6_PD_REBIND);
mExpiryAlarm = makeWakeupMessage("EXPIRY", CMD_DHCP6_PD_EXPIRE);
}
/**
* Make a Dhcp6Client instance.
*/
public static Dhcp6Client makeDhcp6Client(@NonNull final Context context,
@NonNull final StateMachine controller, @NonNull final InterfaceParams ifParams,
@NonNull final Dependencies deps) {
final Dhcp6Client client = new Dhcp6Client(context, controller, ifParams, deps);
client.start();
return client;
}
/**
* Quit the Dhcp6 StateMachine.
*
* @hide
*/
public void doQuit() {
Log.d(TAG, "doQuit");
quit();
}
@Override
protected void onQuitting() {
Log.d(TAG, "onQuitting");
mController.sendMessage(CMD_ON_QUIT);
}
/**
* Retransmits packets using jittered exponential backoff with an optional timeout. Packet
* transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. Kicks are
* cancelled when leaving the state.
*
* Concrete subclasses must implement sendPacket, which is called when the alarm fires and a
* packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET
* sent by the receive thread.
*
* TODO: deduplicate with the similar code in DhcpClient.java
*/
abstract class PacketRetransmittingState extends State {
private int mTimer;
@Override
public void enter() {
super.enter();
mTimer = FIRST_TIMEOUT_MS;
sendMessage(CMD_KICK);
}
@Override
public boolean processMessage(Message message) {
if (super.processMessage(message) == HANDLED) {
return HANDLED;
}
switch (message.what) {
case CMD_KICK:
sendPacket();
scheduleKick();
return HANDLED;
case CMD_RECEIVED_PACKET:
receivePacket((Dhcp6Packet) message.obj);
return HANDLED;
default:
return NOT_HANDLED;
}
}
@Override
public void exit() {
super.exit();
mKickAlarm.cancel();
}
protected abstract boolean sendPacket();
protected abstract void receivePacket(Dhcp6Packet packet);
protected int jitterTimer(int baseTimer) {
int maxJitter = baseTimer / 10;
int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter;
return baseTimer + jitter;
}
/**
* Per RFC8415 section 15, each of the computations of a new RT includes a randomization
* factor (RAND), which is a random number chosen with a uniform distribution between -0.1
* and +0.1.
*/
private double rand() {
return mRandom.nextDouble() / 5 - 0.1;
}
protected void scheduleKick() {
long now = SystemClock.elapsedRealtime();
long timeout = jitterTimer(mTimer);
long alarmTime = now + timeout;
mKickAlarm.schedule(alarmTime);
mTimer *= 2;
if (mTimer > MAX_TIMEOUT_MS) {
mTimer = MAX_TIMEOUT_MS;
}
}
}
private void scheduleLeaseTimers() {
// TODO: validate t1, t2, valid and preferred lifetimes before the timers are scheduled to
// prevent packet storms due to low timeouts.
int renewTimeout = mReply.t1;
int rebindTimeout = mReply.t2;
final long expirationTimeout = mReply.ipo.valid;
// rfc8415#section-14.2: if t1 and / or t2 are 0, the client chooses an appropriate value.
// rfc8415#section-21.21: Recommended values for T1 and T2 are 0.5 and 0.8 times the
// shortest preferred lifetime of the prefixes in the IA_PD that the server is willing to
// extend, respectively.
if (renewTimeout == 0) {
renewTimeout = (int) (mReply.ipo.preferred * 0.5);
}
if (rebindTimeout == 0) {
rebindTimeout = (int) (mReply.ipo.preferred * 0.8);
}
// Note: message validation asserts that the received t1 <= t2 if both t1 > 0 and t2 > 0.
// However, if t1 or t2 are 0, it is possible for renewTimeout to become larger than
// rebindTimeout (and similarly, rebindTimeout to become larger than expirationTimeout).
// For example: t1 = 0, t2 = 40, valid lft = 100 results in renewTimeout = 50, and
// rebindTimeout = 40. Hence, their correct order must be asserted below.
// If timeouts happen to coincide or are out of order, the former (in respect to the
// specified provisioning lifecycle) can be skipped. This also takes care of the case where
// the server sets t1 == t2 == valid lft, which indicates that the IA cannot be renewed, so
// there is no point in trying.
if (renewTimeout >= rebindTimeout) {
// skip RENEW
renewTimeout = 0;
}
if (rebindTimeout >= expirationTimeout) {
// skip REBIND
rebindTimeout = 0;
}
final long now = SystemClock.elapsedRealtime();
if (renewTimeout > 0) {
mRenewAlarm.schedule(now + renewTimeout * (long) SECONDS);
Log.d(TAG, "Scheduling IA_PD renewal in " + renewTimeout + "s");
}
if (rebindTimeout > 0) {
mRebindAlarm.schedule(now + rebindTimeout * (long) SECONDS);
Log.d(TAG, "Scheduling IA_PD rebind in " + rebindTimeout + "s");
}
mExpiryAlarm.schedule(now + expirationTimeout * (long) SECONDS);
Log.d(TAG, "Scheduling IA_PD expiry in " + expirationTimeout + "s");
}
private void notifyPrefixDelegation(int result, @Nullable final PrefixDelegation pd) {
mController.sendMessage(CMD_DHCP6_RESULT, result, 0, pd);
}
private void clearDhcp6State() {
mAdvertise = null;
mReply = null;
mServerDuid = null;
}
private void startNewTransaction() {
mTransId = mRandom.nextInt() & 0xffffff;
mTransStartMillis = SystemClock.elapsedRealtime();
}
private long getElapsedTimeMs() {
return SystemClock.elapsedRealtime() - mTransStartMillis;
}
@SuppressWarnings("ByteBufferBackingArray")
private boolean sendSolicitPacket(final ByteBuffer iapd) {
final ByteBuffer packet = Dhcp6Packet.buildSolicitPacket(mTransId,
getElapsedTimeMs(), iapd.array(), mClientDuid, true /* rapidCommit */);
return transmitPacket(packet, "solicit");
}
@SuppressWarnings("ByteBufferBackingArray")
private boolean sendRequestPacket(final ByteBuffer iapd) {
final ByteBuffer packet = Dhcp6Packet.buildRequestPacket(mTransId, getElapsedTimeMs(),
iapd.array(), mClientDuid, mServerDuid);
return transmitPacket(packet, "request");
}
@SuppressWarnings("ByteBufferBackingArray")
private boolean sendRenewPacket(final ByteBuffer iapd) {
final ByteBuffer packet = Dhcp6Packet.buildRenewPacket(mTransId, getElapsedTimeMs(),
iapd.array(), mClientDuid, mServerDuid);
return transmitPacket(packet, "renew");
}
@SuppressWarnings("ByteBufferBackingArray")
private boolean sendRebindPacket(final ByteBuffer iapd) {
final ByteBuffer packet = Dhcp6Packet.buildRebindPacket(mTransId, getElapsedTimeMs(),
iapd.array(), mClientDuid);
return transmitPacket(packet, "rebind");
}
private ByteBuffer buildEmptyIaPdOption() {
return Dhcp6Packet.buildIaPdOption(IAID, 0 /* t1 */, 0 /* t2 */, 0 /* preferred */,
0 /* valid */, new byte[16] /* empty prefix */, (byte) RFC7421_PREFIX_LENGTH);
}
private ByteBuffer buildIaPdOption(@NonNull final PrefixDelegation pd) {
return Dhcp6Packet.buildIaPdOption(pd.iaid, pd.t1, pd.t2, pd.ipo.preferred, pd.ipo.valid,
pd.ipo.prefix, pd.ipo.prefixLen);
}
/**
* Parent state at which client does initialization of interface and packet handler, also
* processes the CMD_STOP_DHCP6 command in this state which child states don't handle.
*/
class StartedState extends State {
@Override
public void enter() {
clearDhcp6State();
if (mDhcp6PacketHandler.start()) return;
Log.e(TAG, "Fail to start DHCPv6 Packet Handler");
// We cannot call transitionTo because a transition is still in progress.
// Instead, ensure that we process CMD_STOP_DHCP6 as soon as the transition is complete.
deferMessage(obtainMessage(CMD_STOP_DHCP6));
}
@Override
public void exit() {
mDhcp6PacketHandler.stop();
if (DBG) Log.d(TAG, "DHCPv6 Packet Handler stopped");
clearDhcp6State();
}
@Override
public boolean processMessage(Message message) {
super.processMessage(message);
switch (message.what) {
case CMD_STOP_DHCP6:
transitionTo(mStoppedState);
return HANDLED;
default:
return NOT_HANDLED;
}
}
}
/**
* Initial state of DHCPv6 state machine.
*/
class StoppedState extends State {
@Override
public boolean processMessage(Message message) {
switch (message.what) {
case CMD_START_DHCP6:
// TODO: store the delegated prefix in IpMemoryStore and start in REBIND instead
// of SOLICIT if there is already a valid prefix on this network.
transitionTo(mSolicitState);
return HANDLED;
default:
return NOT_HANDLED;
}
}
}
/**
* Client (re)transmits a Solicit message to locate DHCPv6 servers and processes the Advertise
* message in this state.
*
* Note: Not implement DHCPv6 server selection, always request the first Advertise we receive.
*/
class SolicitState extends PacketRetransmittingState {
@Override
public void enter() {
super.enter();
startNewTransaction();
}
@Override
protected boolean sendPacket() {
return sendSolicitPacket(buildEmptyIaPdOption());
}
// TODO: support multiple prefixes.
@Override
protected void receivePacket(Dhcp6Packet packet) {
if (!packet.isValid(mTransId, mClientDuid)) return;
if (packet instanceof Dhcp6AdvertisePacket) {
mAdvertise = packet.mPrefixDelegation;
if (mAdvertise != null && mAdvertise.iaid == IAID) {
Log.d(TAG, "Get prefix delegation option from Advertise: " + mAdvertise);
mServerDuid = packet.mServerDuid;
transitionTo(mRequestState);
}
} else if (packet instanceof Dhcp6ReplyPacket) {
if (!packet.mRapidCommit) {
Log.e(TAG, "Server responded to SOLICIT with REPLY without rapid commit option"
+ ", ignoring");
return;
}
final PrefixDelegation pd = packet.mPrefixDelegation;
if (pd != null && pd.iaid == IAID) {
Log.d(TAG, "Get prefix delegation option from RapidCommit Reply: " + pd);
mReply = pd;
mServerDuid = packet.mServerDuid;
transitionTo(mBoundState);
}
}
}
}
/**
* Client (re)transmits a Request message to request configuration from a specific server and
* process the Reply message in this state.
*/
class RequestState extends PacketRetransmittingState {
@Override
protected boolean sendPacket() {
return sendRequestPacket(buildIaPdOption(mAdvertise));
}
@Override
protected void receivePacket(Dhcp6Packet packet) {
if (!(packet instanceof Dhcp6ReplyPacket)) return;
if (!packet.isValid(mTransId, mClientDuid)) return;
final PrefixDelegation pd = packet.mPrefixDelegation;
if (pd != null && pd.iaid == IAID) {
Log.d(TAG, "Get prefix delegation option from Reply: " + pd);
mReply = pd;
transitionTo(mBoundState);
}
}
}
/**
* Parent state of other states at which client has already obtained the lease from server.
*/
class HaveLeaseState extends State {
@Override
public boolean processMessage(Message message) {
switch (message.what) {
case CMD_DHCP6_PD_EXPIRE:
notifyPrefixDelegation(DHCP6_PD_PREFIX_EXPIRED, null);
transitionTo(mSolicitState);
return HANDLED;
default:
return NOT_HANDLED;
}
}
@Override
public void exit() {
// Clear any extant alarms.
mRenewAlarm.cancel();
mRebindAlarm.cancel();
mExpiryAlarm.cancel();
clearDhcp6State();
}
}
/**
* Client has already obtained the lease(e.g. IA_PD option) from server and stays in Bound
* state until T1 expires, and then transition to Renew state to extend the lease duration.
*/
class BoundState extends State {
@Override
public void enter() {
super.enter();
scheduleLeaseTimers();
// TODO: roll back to SOLICIT state after a delay if something wrong happens
// instead of returning directly.
if (!Dhcp6Packet.hasValidPrefixDelegation(mReply)) {
Log.e(TAG, "Invalid prefix delegatioin " + mReply);
return;
}
// Configure the IPv6 addresses based on the delegated prefix on the interface.
// We've checked that delegated prefix is valid upon receiving the response
// from DHCPv6 server, and the server may assign a prefix with length less
// than 64. So for SLAAC use case we always set the prefix length to 64 even
// if the delegated prefix length is less than 64.
final IpPrefix prefix;
try {
prefix = new IpPrefix(Inet6Address.getByAddress(mReply.ipo.prefix),
RFC7421_PREFIX_LENGTH);
} catch (UnknownHostException e) {
Log.wtf(TAG, "Invalid delegated prefix "
+ HexDump.toHexString(mReply.ipo.prefix));
return;
}
// Create an IPv6 address from the interface mac address with IFA_F_MANAGETEMPADDR
// flag, kernel will create another privacy IPv6 address on behalf of user space.
// We don't need to remember IPv6 addresses that need to extend the lifetime every
// time it enters BoundState.
final Inet6Address address = createInet6AddressFromEui64(prefix,
macAddressToEui64(mIface.macAddr));
final int flags = IFA_F_NOPREFIXROUTE | IFA_F_MANAGETEMPADDR | IFA_F_NODAD;
final long now = SystemClock.elapsedRealtime();
final long deprecationTime = now + mReply.ipo.preferred;
final long expirationTime = now + mReply.ipo.valid;
final LinkAddress la = new LinkAddress(address, RFC7421_PREFIX_LENGTH, flags,
RT_SCOPE_UNIVERSE /* scope */, deprecationTime, expirationTime);
if (!la.isGlobalPreferred()) {
Log.e(TAG, la + " is not a global IPv6 address, ignoring");
return;
}
if (!NetlinkUtils.sendRtmNewAddressRequest(mIface.index, address,
(short) RFC7421_PREFIX_LENGTH,
flags, (byte) RT_SCOPE_UNIVERSE /* scope */,
mReply.ipo.preferred, mReply.ipo.valid)) {
Log.e(TAG, "Failed to set IPv6 address " + address.getHostAddress()
+ "%" + mIface.index);
return;
}
notifyPrefixDelegation(DHCP6_PD_SUCCESS, mReply);
}
@Override
public boolean processMessage(Message message) {
super.processMessage(message);
switch (message.what) {
case CMD_DHCP6_PD_RENEW:
transitionTo(mRenewState);
return HANDLED;
default:
return NOT_HANDLED;
}
}
}
abstract class ReacquireState extends PacketRetransmittingState {
@Override
public void enter() {
super.enter();
startNewTransaction();
}
@Override
protected void receivePacket(Dhcp6Packet packet) {
if (!(packet instanceof Dhcp6ReplyPacket)) return;
if (!packet.isValid(mTransId, mClientDuid)) return;
final PrefixDelegation pd = packet.mPrefixDelegation;
if (pd != null) {
if (pd.iaid != IAID
|| !(Arrays.equals(pd.ipo.prefix, mReply.ipo.prefix)
&& pd.ipo.prefixLen == mReply.ipo.prefixLen)) {
Log.i(TAG, "Renewal prefix " + HexDump.toHexString(pd.ipo.prefix)
+ " does not match current prefix "
+ HexDump.toHexString(mReply.ipo.prefix));
notifyPrefixDelegation(DHCP6_PD_PREFIX_CHANGED, null);
transitionTo(mSolicitState);
return;
}
mReply = pd;
mServerDuid = packet.mServerDuid;
// Once the delegated prefix gets refreshed successfully we have to extend the
// preferred lifetime and valid lifetime of global IPv6 addresses, otherwise
// these addresses will become depreacated finally and then provisioning failure
// happens. So we transit to mBoundState to update the address with refreshed
// preferred and valid lifetime via sending RTM_NEWADDR message, going back to
// Bound state after a success update.
transitionTo(mBoundState);
}
}
}
/**
* Client enters Renew state when T1 expires and (re)transmits Renew message to the
* server that originally provided the client's leases and configuration parameters to
* extend the lifetimes on the leases assigned to the client.
*/
class RenewState extends ReacquireState {
@Override
public boolean processMessage(Message message) {
if (super.processMessage(message) == HANDLED) {
return HANDLED;
}
switch (message.what) {
case CMD_DHCP6_PD_REBIND:
transitionTo(mRebindState);
return HANDLED;
default:
return NOT_HANDLED;
}
}
@Override
protected boolean sendPacket() {
return sendRenewPacket(buildIaPdOption(mReply));
}
}
/**
* Client enters Rebind state when T2 expires and (re)transmits Rebind message to any
* available server to extend the lifetimes on the leases assigned to the client and to
* update other configuration parameters.
*/
class RebindState extends ReacquireState {
@Override
protected boolean sendPacket() {
return sendRebindPacket(buildIaPdOption(mReply));
}
}
private class Dhcp6PacketHandler extends PacketReader {
private FileDescriptor mUdpSock;
Dhcp6PacketHandler(Handler handler) {
super(handler);
}
@Override
protected void handlePacket(byte[] recvbuf, int length) {
try {
final Dhcp6Packet packet = Dhcp6Packet.decode(recvbuf, length);
if (DBG) Log.d(TAG, "Received packet: " + packet);
sendMessage(CMD_RECEIVED_PACKET, packet);
} catch (Dhcp6Packet.ParseException e) {
Log.e(TAG, "Can't parse DHCPv6 packet: " + e.getMessage());
}
}
@Override
protected FileDescriptor createFd() {
try {
mUdpSock = Os.socket(AF_INET6, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP);
SocketUtils.bindSocketToInterface(mUdpSock, mIface.name);
Os.bind(mUdpSock, IPV6_ADDR_ANY, DHCP6_CLIENT_PORT);
} catch (SocketException | ErrnoException e) {
Log.e(TAG, "Error creating udp socket", e);
closeFd(mUdpSock);
mUdpSock = null;
return null;
}
return mUdpSock;
}
@Override
protected int readPacket(FileDescriptor fd, byte[] packetBuffer) throws Exception {
try {
return Os.read(fd, packetBuffer, 0, packetBuffer.length);
} catch (IOException | ErrnoException e) {
Log.e(TAG, "Fail to read packet");
throw e;
}
}
public int transmitPacket(final ByteBuffer buf) throws ErrnoException, SocketException {
int ret = Os.sendto(mUdpSock, buf.array(), 0 /* byteOffset */,
buf.limit() /* byteCount */, 0 /* flags */, ALL_DHCP_RELAY_AGENTS_AND_SERVERS,
DHCP6_SERVER_PORT);
return ret;
}
}
@SuppressWarnings("ByteBufferBackingArray")
private boolean transmitPacket(@NonNull final ByteBuffer buf,
@NonNull final String description) {
try {
if (DBG) {
Log.d(TAG, "Multicasting " + description + " to ff02::1:2" + " packet raw data: "
+ HexDump.toHexString(buf.array(), 0, buf.limit()));
}
mDhcp6PacketHandler.transmitPacket(buf);
} catch (ErrnoException | IOException e) {
Log.e(TAG, "Can't send packet: ", e);
return false;
}
return true;
}
}