// The initial retry timeout for DHCP is 4000 milliseconds staticconst uint32_t kInitialTimeout = 4000; // The maximum retry timeout for DHCP is 64000 milliseconds staticconst uint32_t kMaxTimeout = 64000; // A specific value that indicates that no timeout should happen and that // the state machine should immediately transition to the next state staticconst uint32_t kNoTimeout = 0;
// The number of milliseconds that the timeout should vary (up or down) from the // base timeout. DHCP requires a -1 to +1 second variation in timeouts. staticconstint kTimeoutSpan = 1000;
Result DhcpClient::init(constchar* interfaceName) {
Result res = mInterface.init(interfaceName); if (!res) { return res;
}
res = mRouter.init(); if (!res) { return res;
}
res = mSocket.open(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); if (!res) { return res;
}
res = mSocket.bindRaw(mInterface.getIndex()); if (!res) { return res;
} return Result::success();
}
Result DhcpClient::run() { // Block all signals while we're running. This way we don't have to deal // with things like EINTR. waitAndReceive then uses ppoll to set the // original mask while polling. This way polling can be interrupted but // socket writing, reading and ioctl remain interrupt free. If a signal // arrives while we're blocking it will be placed in the signal queue // and handled once ppoll sets the original mask. This way no signals are // lost.
sigset_t blockMask, originalMask; int status = ::sigfillset(&blockMask); if (status != 0) { return Result::error("Unable to fill signal set: %s", strerror(errno));
}
status = ::sigprocmask(SIG_SETMASK, &blockMask, &originalMask); if (status != 0) { return Result::error("Unable to set signal mask: %s", strerror(errno));
}
for (;;) { // Before waiting, polling or receiving we check the current state and // see what we should do next. This may result in polling but could // also lead to instant state changes without any polling. The new state // will then be evaluated instead, most likely leading to polling. switch (mState) { case State::Init: // The starting state. This is the state the client is in when // it first starts. It's also the state that the client returns // to when things go wrong in other states.
setNextState(State::Selecting); break; case State::Selecting: // In the selecting state the client attempts to find DHCP // servers on the network. The client remains in this state // until a suitable server responds.
sendDhcpDiscover();
increaseTimeout(); break; case State::Requesting: // In the requesting state the client has found a suitable // server. The next step is to send a request directly to that // server. if (mNextTimeout >= kMaxTimeout) { // We've tried to request a bunch of times, start over
setNextState(State::Init);
} else {
sendDhcpRequest(mServerAddress);
increaseTimeout();
} break; case State::Bound: // The client enters the bound state when the server has // accepted and acknowledged a request and given us a lease. At // this point the client will wait until the lease is close to // expiring and then it will try to renew the lease. if (mT1.expired()) { // Lease expired, renew lease
setNextState(State::Renewing);
} else { // Spurious wake-up, continue waiting. Do not fuzz the // timeout with a random offset. Doing so can cause wakeups // before the timer has expired causing unnecessary // processing. Even worse it can cause the timer to expire // after the lease has ended.
mNextTimeout = mT1.remainingMillis();
mFuzzNextTimeout = false;
} break; case State::Renewing: // In the renewing state the client is sending a request for the // same address it had was previously bound to. If the second // timer expires when in this state the client will attempt to // do a full rebind. if (mT2.expired()) { // Timeout while renewing, move to rebinding
setNextState(State::Rebinding);
} else {
sendDhcpRequest(mServerAddress);
increaseTimeout();
} break; case State::Rebinding: // The client was unable to renew the lease and moved to the // rebinding state. In this state the client sends a request for // the same address it had before to the broadcast address. This // means that any DHCP server on the network is free to respond. // After attempting this a few times the client will give up and // move to the Init state to try to find a new DHCP server. if (mNextTimeout >= kMaxTimeout) { // We've tried to rebind a bunch of times, start over
setNextState(State::Init);
} else { // Broadcast a request
sendDhcpRequest(INADDR_BROADCAST);
increaseTimeout();
} break; default: break;
} // The proper action for the current state has been taken, perform any // polling and/or waiting needed.
waitAndReceive(originalMask);
}
constchar* DhcpClient::stateToStr(State state) { switch (state) { case State::Init: return"Init"; case State::Selecting: return"Selecting"; case State::Requesting: return"Requesting"; case State::Bound: return"Bound"; case State::Renewing: return"Renewing"; case State::Rebinding: return"Rebinding";
} return"<unknown>";
}
void DhcpClient::waitAndReceive(const sigset_t& pollSignalMask) { if (mNextTimeout == kNoTimeout) { // If there is no timeout the state machine has indicated that it wants // an immediate transition to another state. Do nothing. return;
}
// Poll for any incoming traffic with the calculated timeout. While // polling the original signal mask is set so that the polling can be // interrupted. int res = ::ppoll(&fds, 1, &ts, &pollSignalMask); if (res == 0) { // Timeout, return to let the caller evaluate return;
} elseif (res > 0) { // Something to read
Message msg; if (receiveDhcpMessage(&msg)) { // We received a DHCP message, check if it's of interest
uint8_t msgType = msg.type(); switch (mState) { case State::Selecting: if (msgType == DHCPOFFER) { // Received an offer, move to the Requesting state // to request it.
mServerAddress = msg.serverId();
mRequestAddress = msg.dhcpData.yiaddr;
setNextState(State::Requesting); return;
} break; case State::Requesting: case State::Renewing: case State::Rebinding: // All of these states have sent a DHCP request and are // now waiting for an ACK so the behavior is the same. if (msgType == DHCPACK) { // Request approved if (configureDhcp(msg)) { // Successfully configured DHCP, move to Bound
setNextState(State::Bound); return;
} // Unable to configure DHCP, keep sending requests. // This may not fix the issue but eventually it will // allow for a full timeout which will lead to a // move to the Init state. This might still not fix // the issue but at least the client keeps trying.
} elseif (msgType == DHCPNAK) { // Request denied, halt network and start over
haltNetwork();
setNextState(State::Init); return;
} break; default: // For the other states the client is not expecting any // network messages so we ignore those messages. break;
}
}
} else { // An error occurred in polling, don't do anything here. The client // should keep going anyway to try to acquire a lease in the future // if things start working again.
} // If we reach this point we received something that's not a DHCP, // message, we timed out, or an error occurred. Go again with whatever // time remains.
uint64_t currentTime = now();
uint64_t end = startedAt + timeout; if (currentTime >= end) { // We're done anyway, return and let caller evaluate return;
} // Wait whatever the remaining time is
timeout = end - currentTime;
}
}
bool DhcpClient::configureDhcp(const Message& msg) {
size_t optsSize = msg.optionsSize(); if (optsSize < 4) { // Message is too small if (kDebug) ALOGD("Opts size too small %d", static_cast<int>(optsSize)); returnfalse;
}
const uint8_t* options = msg.dhcpData.options;
memset(&mDhcpInfo, 0, sizeof(mDhcpInfo));
// Inspect all options in the message to try to find the ones we want for (size_t i = 4; i + 1 < optsSize;) {
uint8_t optCode = options[i];
uint8_t optLength = options[i + 1]; if (optCode == OPT_END) { break;
}
if (options + optLength + i >= msg.end()) { // Invalid option length, drop it if (kDebug)
ALOGD("Invalid opt length %d for opt %d", static_cast<int>(optLength), static_cast<int>(optCode)); returnfalse;
} const uint8_t* opt = options + i + 2; switch (optCode) { case OPT_LEASE_TIME: if (optLength == 4) {
mDhcpInfo.leaseTime = ntohl(*reinterpret_cast<const uint32_t*>(opt));
} break; case OPT_T1: if (optLength == 4) {
mDhcpInfo.t1 = ntohl(*reinterpret_cast<const uint32_t*>(opt));
} break; case OPT_T2: if (optLength == 4) {
mDhcpInfo.t2 = ntohl(*reinterpret_cast<const uint32_t*>(opt));
} break; case OPT_SUBNET_MASK: if (optLength == 4) {
mDhcpInfo.subnetMask = *reinterpret_cast<const in_addr_t*>(opt);
} break; case OPT_GATEWAY: if (optLength >= 4) {
mDhcpInfo.gateway = *reinterpret_cast<const in_addr_t*>(opt);
} break; case OPT_MTU: if (optLength == 2) {
mDhcpInfo.mtu = ntohs(*reinterpret_cast<const uint16_t*>(opt));
} break; case OPT_DNS: if (optLength >= 4) {
mDhcpInfo.dns[0] = *reinterpret_cast<const in_addr_t*>(opt);
} if (optLength >= 8) {
mDhcpInfo.dns[1] = *reinterpret_cast<const in_addr_t*>(opt + 4);
} if (optLength >= 12) {
mDhcpInfo.dns[2] = *reinterpret_cast<const in_addr_t*>(opt + 8);
} if (optLength >= 16) {
mDhcpInfo.dns[3] = *reinterpret_cast<const in_addr_t*>(opt + 12);
} break; case OPT_SERVER_ID: if (optLength == 4) {
mDhcpInfo.serverId = *reinterpret_cast<const in_addr_t*>(opt);
} break; default: break;
}
i += 2 + optLength;
}
mDhcpInfo.offeredAddress = msg.dhcpData.yiaddr;
if (mDhcpInfo.leaseTime == 0) { // We didn't get a lease time, ignore this offer returnfalse;
} // If there is no T1 or T2 timer given then we create an estimate as // suggested for servers in RFC 2131.
uint32_t t1 = mDhcpInfo.t1, t2 = mDhcpInfo.t2;
mT1.expireSeconds(t1 > 0 ? t1 : (mDhcpInfo.leaseTime / 2));
mT2.expireSeconds(t2 > 0 ? t2 : ((mDhcpInfo.leaseTime * 7) / 8));
Result res = mInterface.bringUp(); if (!res) {
ALOGE("Could not configure DHCP: %s", res.c_str()); returnfalse;
}
if (mDhcpInfo.mtu != 0) {
res = mInterface.setMtu(mDhcpInfo.mtu); if (!res) { // Consider this non-fatal, the system will not perform at its best // but should still work.
ALOGE("Could not configure DHCP: %s", res.c_str());
}
}
char propName[64];
snprintf(propName, sizeof(propName), "vendor.net.%s.gw", mInterface.getName().c_str()); if (property_set(propName, addrToStr(mDhcpInfo.gateway).c_str()) != 0) {
ALOGE("Failed to set %s: %s", propName, strerror(errno));
}
int numDnsEntries = sizeof(mDhcpInfo.dns) / sizeof(mDhcpInfo.dns[0]); for (int i = 0; i < numDnsEntries; ++i) {
snprintf(propName, sizeof(propName), "vendor.net.%s.dns%d", mInterface.getName().c_str(),
i + 1); if (mDhcpInfo.dns[i] != 0) { if (property_set(propName, addrToStr(mDhcpInfo.dns[i]).c_str()) != 0) {
ALOGE("Failed to set %s: %s", propName, strerror(errno));
}
} else { // Clear out any previous value here in case it was set if (property_set(propName, "") != 0) {
ALOGE("Failed to clear %s: %s", propName, strerror(errno));
}
}
}
res = mInterface.setAddress(mDhcpInfo.offeredAddress, mDhcpInfo.subnetMask); if (!res) {
ALOGE("Could not configure DHCP: %s", res.c_str()); returnfalse;
}
if ((mOptions & static_cast<uint32_t>(ClientOption::NoGateway)) == 0) {
res = mRouter.setDefaultGateway(mDhcpInfo.gateway, mInterface.getIndex()); if (!res) {
ALOGE("Could not configure DHCP: %s", res.c_str()); returnfalse;
}
} returntrue;
}
void DhcpClient::haltNetwork() {
Result res = mInterface.setAddress(0, 0); if (!res) {
ALOGE("Could not halt network: %s", res.c_str());
}
res = mInterface.bringDown(); if (!res) {
ALOGE("Could not halt network: %s", res.c_str());
}
}
bool DhcpClient::receiveDhcpMessage(Message* msg) { bool isValid = false;
Result res = mSocket.receiveRawUdp(PORT_BOOTP_CLIENT, msg, &isValid); if (!res) { if (kDebug) ALOGD("Discarding message: %s", res.c_str()); returnfalse;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.