Support DHCPv4 rebinding state
Bug: 24837343 Change-Id: Id49e1c12ec3b11fedba42bb28348a00cb0b11169
This commit is contained in:
@@ -125,17 +125,18 @@ public class DhcpClient extends StateMachine {
|
||||
public static final int CMD_CONFIGURE_LINKADDRESS = PUBLIC_BASE + 8;
|
||||
public static final int EVENT_LINKADDRESS_CONFIGURED = PUBLIC_BASE + 9;
|
||||
|
||||
/* Message.arg1 arguments to CMD_POST_DHCP notification */
|
||||
/* Message.arg1 arguments to CMD_POST_DHCP_ACTION notification */
|
||||
public static final int DHCP_SUCCESS = 1;
|
||||
public static final int DHCP_FAILURE = 2;
|
||||
|
||||
// Messages.
|
||||
// Internal messages.
|
||||
private static final int PRIVATE_BASE = Protocol.BASE_DHCP + 100;
|
||||
private static final int CMD_KICK = PRIVATE_BASE + 1;
|
||||
private static final int CMD_RECEIVED_PACKET = PRIVATE_BASE + 2;
|
||||
private static final int CMD_TIMEOUT = PRIVATE_BASE + 3;
|
||||
private static final int CMD_RENEW_DHCP = PRIVATE_BASE + 4;
|
||||
private static final int CMD_EXPIRE_DHCP = PRIVATE_BASE + 5;
|
||||
private static final int CMD_REBIND_DHCP = PRIVATE_BASE + 5;
|
||||
private static final int CMD_EXPIRE_DHCP = PRIVATE_BASE + 6;
|
||||
|
||||
// For message logging.
|
||||
private static final Class[] sMessageClasses = { DhcpClient.class };
|
||||
@@ -177,6 +178,7 @@ public class DhcpClient extends StateMachine {
|
||||
private final WakeupMessage mKickAlarm;
|
||||
private final WakeupMessage mTimeoutAlarm;
|
||||
private final WakeupMessage mRenewAlarm;
|
||||
private final WakeupMessage mRebindAlarm;
|
||||
private final WakeupMessage mExpiryAlarm;
|
||||
private final String mIfaceName;
|
||||
|
||||
@@ -241,8 +243,9 @@ public class DhcpClient extends StateMachine {
|
||||
mKickAlarm = makeWakeupMessage("KICK", CMD_KICK);
|
||||
// Used to time out PacketRetransmittingStates.
|
||||
mTimeoutAlarm = makeWakeupMessage("TIMEOUT", CMD_TIMEOUT);
|
||||
// Used to schedule DHCP renews.
|
||||
// Used to schedule DHCP reacquisition.
|
||||
mRenewAlarm = makeWakeupMessage("RENEW", CMD_RENEW_DHCP);
|
||||
mRebindAlarm = makeWakeupMessage("REBIND", CMD_REBIND_DHCP);
|
||||
mExpiryAlarm = makeWakeupMessage("EXPIRY", CMD_EXPIRE_DHCP);
|
||||
}
|
||||
|
||||
@@ -276,6 +279,10 @@ public class DhcpClient extends StateMachine {
|
||||
}
|
||||
|
||||
private boolean initSockets() {
|
||||
return initPacketSocket() && initUdpSocket();
|
||||
}
|
||||
|
||||
private boolean initPacketSocket() {
|
||||
try {
|
||||
mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP);
|
||||
PacketSocketAddress addr = new PacketSocketAddress((short) ETH_P_IP, mIface.getIndex());
|
||||
@@ -285,6 +292,10 @@ public class DhcpClient extends StateMachine {
|
||||
Log.e(TAG, "Error creating packet socket", e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean initUdpSocket() {
|
||||
try {
|
||||
mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1);
|
||||
@@ -363,16 +374,25 @@ public class DhcpClient extends StateMachine {
|
||||
return (short) ((SystemClock.elapsedRealtime() - mTransactionStartMillis) / 1000);
|
||||
}
|
||||
|
||||
private boolean transmitPacket(ByteBuffer buf, String description, Inet4Address to) {
|
||||
private boolean transmitPacket(ByteBuffer buf, String description, int encap, Inet4Address to) {
|
||||
try {
|
||||
if (to.equals(INADDR_BROADCAST)) {
|
||||
if (encap == DhcpPacket.ENCAP_L2) {
|
||||
if (DBG) Log.d(TAG, "Broadcasting " + description);
|
||||
Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr);
|
||||
} else if (encap == DhcpPacket.ENCAP_BOOTP && to.equals(INADDR_BROADCAST)) {
|
||||
if (DBG) Log.d(TAG, "Broadcasting " + description);
|
||||
// We only send L3-encapped broadcasts in DhcpRebindingState,
|
||||
// where we have an IP address and an unconnected UDP socket.
|
||||
//
|
||||
// N.B.: We only need this codepath because DhcpRequestPacket
|
||||
// hardcodes the source IP address to 0.0.0.0. We could reuse
|
||||
// the packet socket if this ever changes.
|
||||
Os.sendto(mUdpSock, buf, 0, to, DhcpPacket.DHCP_SERVER);
|
||||
} else {
|
||||
// It's safe to call getpeername here, because we only send unicast packets if we
|
||||
// have an IP address, and we connect the UDP socket before
|
||||
// ConfiguringInterfaceState#exit.
|
||||
if (DBG) Log.d(TAG, "Unicasting " + description + " to " + Os.getpeername(mUdpSock));
|
||||
// have an IP address, and we connect the UDP socket in DhcpBoundState#enter.
|
||||
if (DBG) Log.d(TAG, String.format("Unicasting %s to %s",
|
||||
description, Os.getpeername(mUdpSock)));
|
||||
Os.write(mUdpSock, buf);
|
||||
}
|
||||
} catch(ErrnoException|IOException e) {
|
||||
@@ -386,14 +406,15 @@ public class DhcpClient extends StateMachine {
|
||||
ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
|
||||
DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr,
|
||||
DO_UNICAST, REQUESTED_PARAMS);
|
||||
return transmitPacket(packet, "DHCPDISCOVER", INADDR_BROADCAST);
|
||||
return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST);
|
||||
}
|
||||
|
||||
private boolean sendRequestPacket(
|
||||
Inet4Address clientAddress, Inet4Address requestedAddress,
|
||||
Inet4Address serverAddress, Inet4Address to) {
|
||||
// TODO: should we use the transaction ID from the server?
|
||||
int encap = to.equals(INADDR_BROADCAST) ? DhcpPacket.ENCAP_L2 : DhcpPacket.ENCAP_BOOTP;
|
||||
final int encap = INADDR_ANY.equals(clientAddress)
|
||||
? DhcpPacket.ENCAP_L2 : DhcpPacket.ENCAP_BOOTP;
|
||||
|
||||
ByteBuffer packet = DhcpPacket.buildRequestPacket(
|
||||
encap, mTransactionId, getSecs(), clientAddress,
|
||||
@@ -403,7 +424,7 @@ public class DhcpClient extends StateMachine {
|
||||
String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() +
|
||||
" request=" + requestedAddress.getHostAddress() +
|
||||
" serverid=" + serverStr;
|
||||
return transmitPacket(packet, description, to);
|
||||
return transmitPacket(packet, description, encap, to);
|
||||
}
|
||||
|
||||
private void scheduleLeaseTimers() {
|
||||
@@ -413,14 +434,21 @@ public class DhcpClient extends StateMachine {
|
||||
}
|
||||
|
||||
final long now = SystemClock.elapsedRealtime();
|
||||
long renewTime = (now + mDhcpLeaseExpiry) / 2;
|
||||
mRenewAlarm.schedule(renewTime);
|
||||
long secondsHence = (renewTime - now) / 1000;
|
||||
Log.d(TAG, "Scheduling renewal in " + secondsHence + "s");
|
||||
|
||||
mExpiryAlarm.schedule(mDhcpLeaseExpiry);
|
||||
secondsHence = (mDhcpLeaseExpiry - now) / 1000;
|
||||
Log.d(TAG, "Scheduling expiry in " + secondsHence + "s");
|
||||
// TODO: consider getting the renew and rebind timers from T1 and T2.
|
||||
// See also:
|
||||
// https://tools.ietf.org/html/rfc2131#section-4.4.5
|
||||
// https://tools.ietf.org/html/rfc1533#section-9.9
|
||||
// https://tools.ietf.org/html/rfc1533#section-9.10
|
||||
final long remainingDelay = mDhcpLeaseExpiry - now;
|
||||
final long renewDelay = remainingDelay / 2;
|
||||
final long rebindDelay = remainingDelay * 7 / 8;
|
||||
mRenewAlarm.schedule(now + renewDelay);
|
||||
mRebindAlarm.schedule(now + rebindDelay);
|
||||
mExpiryAlarm.schedule(now + remainingDelay);
|
||||
Log.d(TAG, "Scheduling renewal in " + (renewDelay / 1000) + "s");
|
||||
Log.d(TAG, "Scheduling rebind in " + (rebindDelay / 1000) + "s");
|
||||
Log.d(TAG, "Scheduling expiry in " + (remainingDelay / 1000) + "s");
|
||||
}
|
||||
|
||||
private void notifySuccess() {
|
||||
@@ -719,7 +747,6 @@ public class DhcpClient extends StateMachine {
|
||||
|
||||
class DhcpRequestingState extends PacketRetransmittingState {
|
||||
public DhcpRequestingState() {
|
||||
super();
|
||||
mTimeout = DHCP_TIMEOUT_MS / 2;
|
||||
}
|
||||
|
||||
@@ -777,7 +804,11 @@ public class DhcpClient extends StateMachine {
|
||||
|
||||
@Override
|
||||
public void exit() {
|
||||
// Clear any extant alarms.
|
||||
mRenewAlarm.cancel();
|
||||
mRebindAlarm.cancel();
|
||||
mExpiryAlarm.cancel();
|
||||
clearDhcpState();
|
||||
// Tell IpManager to clear the IPv4 address. There is no need to
|
||||
// wait for confirmation since any subsequent packets are sent from
|
||||
// INADDR_ANY anyway (DISCOVER, REQUEST).
|
||||
@@ -797,21 +828,7 @@ public class DhcpClient extends StateMachine {
|
||||
super.processMessage(message);
|
||||
switch (message.what) {
|
||||
case EVENT_LINKADDRESS_CONFIGURED:
|
||||
if (mDhcpLease.serverAddress != null &&
|
||||
!connectUdpSock(mDhcpLease.serverAddress)) {
|
||||
// There's likely no point in going into DhcpInitState here, we'll probably
|
||||
// just repeat the transaction, get the same IP address as before, and fail.
|
||||
//
|
||||
// NOTE: It is observed that connectUdpSock() basically never fails, due to
|
||||
// SO_BINDTODEVICE. Examining the local socket address shows it will happily
|
||||
// return an IPv4 address from another interface, or even return "0.0.0.0".
|
||||
//
|
||||
// TODO: Consider deleting this check, following testing on several kernels.
|
||||
notifyFailure();
|
||||
transitionTo(mStoppedState);
|
||||
} else {
|
||||
transitionTo(mDhcpBoundState);
|
||||
}
|
||||
transitionTo(mDhcpBoundState);
|
||||
return HANDLED;
|
||||
default:
|
||||
return NOT_HANDLED;
|
||||
@@ -823,8 +840,19 @@ public class DhcpClient extends StateMachine {
|
||||
@Override
|
||||
public void enter() {
|
||||
super.enter();
|
||||
// TODO: DhcpStateMachine only supported renewing at 50% of the lease time,
|
||||
// and did not support rebinding. Now that the legacy DHCP client is gone, fix this.
|
||||
if (mDhcpLease.serverAddress != null && !connectUdpSock(mDhcpLease.serverAddress)) {
|
||||
// There's likely no point in going into DhcpInitState here, we'll probably
|
||||
// just repeat the transaction, get the same IP address as before, and fail.
|
||||
//
|
||||
// NOTE: It is observed that connectUdpSock() basically never fails, due to
|
||||
// SO_BINDTODEVICE. Examining the local socket address shows it will happily
|
||||
// return an IPv4 address from another interface, or even return "0.0.0.0".
|
||||
//
|
||||
// TODO: Consider deleting this check, following testing on several kernels.
|
||||
notifyFailure();
|
||||
transitionTo(mStoppedState);
|
||||
}
|
||||
|
||||
scheduleLeaseTimers();
|
||||
}
|
||||
|
||||
@@ -843,18 +871,10 @@ public class DhcpClient extends StateMachine {
|
||||
return NOT_HANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit() {
|
||||
mRenewAlarm.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
class DhcpRenewingState extends PacketRetransmittingState {
|
||||
public DhcpRenewingState() {
|
||||
super();
|
||||
mTimeout = DHCP_TIMEOUT_MS;
|
||||
}
|
||||
abstract class DhcpReacquiringState extends PacketRetransmittingState {
|
||||
protected String mLeaseMsg;
|
||||
|
||||
@Override
|
||||
public void enter() {
|
||||
@@ -862,16 +882,14 @@ public class DhcpClient extends StateMachine {
|
||||
startNewTransaction();
|
||||
}
|
||||
|
||||
abstract protected Inet4Address packetDestination();
|
||||
|
||||
protected boolean sendPacket() {
|
||||
// Not specifying a SERVER_IDENTIFIER option is a violation of RFC 2131, but...
|
||||
// http://b/25343517 . Try to make things work anyway by using broadcast renews.
|
||||
Inet4Address to = (mDhcpLease.serverAddress != null) ?
|
||||
mDhcpLease.serverAddress : INADDR_BROADCAST;
|
||||
return sendRequestPacket(
|
||||
(Inet4Address) mDhcpLease.ipAddress.getAddress(), // ciaddr
|
||||
INADDR_ANY, // DHCP_REQUESTED_IP
|
||||
null, // DHCP_SERVER_IDENTIFIER
|
||||
to); // packet destination address
|
||||
packetDestination()); // packet destination address
|
||||
}
|
||||
|
||||
protected void receivePacket(DhcpPacket packet) {
|
||||
@@ -890,7 +908,7 @@ public class DhcpClient extends StateMachine {
|
||||
// in IpManager and by any overridden relevant handlers of
|
||||
// the registered IpManager.Callback. IP address changes
|
||||
// are not supported here.
|
||||
acceptDhcpResults(results, "Renewed");
|
||||
acceptDhcpResults(results, mLeaseMsg);
|
||||
transitionTo(mDhcpBoundState);
|
||||
}
|
||||
} else if (packet instanceof DhcpNakPacket) {
|
||||
@@ -901,8 +919,57 @@ public class DhcpClient extends StateMachine {
|
||||
}
|
||||
}
|
||||
|
||||
// Not implemented--yet. DhcpStateMachine did not implement it either.
|
||||
class DhcpRebindingState extends LoggingState {
|
||||
class DhcpRenewingState extends DhcpReacquiringState {
|
||||
public DhcpRenewingState() {
|
||||
mLeaseMsg = "Renewed";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processMessage(Message message) {
|
||||
if (super.processMessage(message) == HANDLED) {
|
||||
return HANDLED;
|
||||
}
|
||||
|
||||
switch (message.what) {
|
||||
case CMD_REBIND_DHCP:
|
||||
transitionTo(mDhcpRebindingState);
|
||||
return HANDLED;
|
||||
default:
|
||||
return NOT_HANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Inet4Address packetDestination() {
|
||||
// Not specifying a SERVER_IDENTIFIER option is a violation of RFC 2131, but...
|
||||
// http://b/25343517 . Try to make things work anyway by using broadcast renews.
|
||||
return (mDhcpLease.serverAddress != null) ?
|
||||
mDhcpLease.serverAddress : INADDR_BROADCAST;
|
||||
}
|
||||
}
|
||||
|
||||
class DhcpRebindingState extends DhcpReacquiringState {
|
||||
public DhcpRebindingState() {
|
||||
mLeaseMsg = "Rebound";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enter() {
|
||||
super.enter();
|
||||
|
||||
// We need to broadcast and possibly reconnect the socket to a
|
||||
// completely different server.
|
||||
closeQuietly(mUdpSock);
|
||||
if (!initUdpSocket()) {
|
||||
Log.e(TAG, "Failed to recreate UDP socket");
|
||||
transitionTo(mDhcpInitState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Inet4Address packetDestination() {
|
||||
return INADDR_BROADCAST;
|
||||
}
|
||||
}
|
||||
|
||||
class DhcpInitRebootState extends LoggingState {
|
||||
|
||||
@@ -1027,6 +1027,8 @@ public class IpManager extends StateMachine {
|
||||
}
|
||||
|
||||
class StartedState extends State {
|
||||
private boolean mDhcpActionInFlight;
|
||||
|
||||
@Override
|
||||
public void enter() {
|
||||
mStartTimeMillis = SystemClock.elapsedRealtime();
|
||||
@@ -1066,7 +1068,7 @@ public class IpManager extends StateMachine {
|
||||
@Override
|
||||
public void exit() {
|
||||
mProvisioningTimeoutAlarm.cancel();
|
||||
mDhcpActionTimeoutAlarm.cancel();
|
||||
stopDhcpAction();
|
||||
|
||||
if (mIpReachabilityMonitor != null) {
|
||||
mIpReachabilityMonitor.stop();
|
||||
@@ -1086,16 +1088,22 @@ public class IpManager extends StateMachine {
|
||||
resetLinkProperties();
|
||||
}
|
||||
|
||||
private void startDhcpAction() {
|
||||
mCallback.onPreDhcpAction();
|
||||
final long alarmTime = SystemClock.elapsedRealtime() +
|
||||
mConfiguration.mRequestedPreDhcpActionMs;
|
||||
mDhcpActionTimeoutAlarm.schedule(alarmTime);
|
||||
private void ensureDhcpAction() {
|
||||
if (!mDhcpActionInFlight) {
|
||||
mCallback.onPreDhcpAction();
|
||||
mDhcpActionInFlight = true;
|
||||
final long alarmTime = SystemClock.elapsedRealtime() +
|
||||
mConfiguration.mRequestedPreDhcpActionMs;
|
||||
mDhcpActionTimeoutAlarm.schedule(alarmTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void stopDhcpAction() {
|
||||
mDhcpActionTimeoutAlarm.cancel();
|
||||
mCallback.onPostDhcpAction();
|
||||
if (mDhcpActionInFlight) {
|
||||
mCallback.onPostDhcpAction();
|
||||
mDhcpActionInFlight = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1165,9 +1173,8 @@ public class IpManager extends StateMachine {
|
||||
break;
|
||||
|
||||
case DhcpClient.CMD_PRE_DHCP_ACTION:
|
||||
if (VDBG) { Log.d(mTag, "onPreDhcpAction()"); }
|
||||
if (mConfiguration.mRequestedPreDhcpActionMs > 0) {
|
||||
startDhcpAction();
|
||||
ensureDhcpAction();
|
||||
} else {
|
||||
sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
|
||||
}
|
||||
@@ -1193,18 +1200,18 @@ public class IpManager extends StateMachine {
|
||||
// This message is only received when:
|
||||
//
|
||||
// a) initial address acquisition succeeds,
|
||||
// b) renew succeeds,
|
||||
// c) renew fails,
|
||||
// b) renew succeeds or is NAK'd,
|
||||
// c) rebind succeeds or is NAK'd, or
|
||||
// c) the lease expires,
|
||||
//
|
||||
// but never when initial address acquisition fails. The latter
|
||||
// condition is now governed by the provisioning timeout.
|
||||
case DhcpClient.CMD_POST_DHCP_ACTION: {
|
||||
case DhcpClient.CMD_POST_DHCP_ACTION:
|
||||
stopDhcpAction();
|
||||
|
||||
final DhcpResults dhcpResults = (DhcpResults) msg.obj;
|
||||
switch (msg.arg1) {
|
||||
case DhcpClient.DHCP_SUCCESS:
|
||||
handleIPv4Success(dhcpResults);
|
||||
handleIPv4Success((DhcpResults) msg.obj);
|
||||
break;
|
||||
case DhcpClient.DHCP_FAILURE:
|
||||
handleIPv4Failure();
|
||||
@@ -1213,7 +1220,6 @@ public class IpManager extends StateMachine {
|
||||
Log.e(mTag, "Unknown CMD_POST_DHCP_ACTION status:" + msg.arg1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case DhcpClient.CMD_ON_QUIT:
|
||||
// DHCPv4 quit early for some reason.
|
||||
|
||||
Reference in New Issue
Block a user