added 1 changeset to branch 'refs/remotes/pdziepak-github/network' old head: 0000000000000000000000000000000000000000 new head: 6972b91e17f19950fbf994033846a856bff4b796 overview: https://github.com/pdziepak/Haiku/compare/6972b91 ---------------------------------------------------------------------------- 6972b91: servers/net: Make DHCP client more RFC 2131 compliant * The client should enter state REBINDING only when RENEWING times out. * When in RENEWING or REBINDING state do not give up retrying unless the lease expires. * Fix bug sending 2^n DHCP requests at n-th lease renewal. * Use timeout values and renewal/rebinding times suggested by the RFC. * Use different XIDs in subsequent transactions. [ Pawel Dziepak <pdziepak@xxxxxxxxxxx> ] ---------------------------------------------------------------------------- Commit: 6972b91e17f19950fbf994033846a856bff4b796 Author: Pawel Dziepak <pdziepak@xxxxxxxxxxx> Date: Thu Jul 25 01:52:46 2013 UTC ---------------------------------------------------------------------------- 3 files changed, 195 insertions(+), 207 deletions(-) src/servers/net/DHCPClient.cpp | 388 ++++++++++++++++++------------------- src/servers/net/DHCPClient.h | 12 +- src/servers/net/NetServer.cpp | 2 + ---------------------------------------------------------------------------- diff --git a/src/servers/net/DHCPClient.cpp b/src/servers/net/DHCPClient.cpp index a2ae864..850861a 100644 --- a/src/servers/net/DHCPClient.cpp +++ b/src/servers/net/DHCPClient.cpp @@ -16,6 +16,7 @@ #include <NetworkDevice.h> #include <NetworkInterface.h> +#include <algorithm> #include <arpa/inet.h> #include <errno.h> #include <stdio.h> @@ -38,8 +39,10 @@ #define DHCP_CLIENT_PORT 68 #define DHCP_SERVER_PORT 67 -#define DEFAULT_TIMEOUT 2 // secs -#define MAX_TIMEOUT 15 // secs +#define DEFAULT_TIMEOUT 4 // secs +#define MAX_TIMEOUT 64 // secs + +#define MAX_RETRIES 5 enum message_opcode { BOOT_REQUEST = 1, @@ -427,10 +430,7 @@ DHCPClient::DHCPClient(BMessenger target, const char* device) fServer(AF_INET, NULL, DHCP_SERVER_PORT), fLeaseTime(0) { - fStartTime = system_time(); - fTransactionID = (uint32)fStartTime; - - srand(fTransactionID); + fTransactionID = (uint32)system_time() ^ rand(); BNetworkAddress link; BNetworkInterface interface(device); @@ -498,13 +498,22 @@ DHCPClient::Initialize() status_t DHCPClient::_Negotiate(dhcp_state state) { + if (state == BOUND) + return B_OK; + + fStartTime = system_time(); + fTransactionID++; + + char hostName[MAXHOSTNAMELEN]; + if (gethostname(hostName, MAXHOSTNAMELEN) == 0) + fHostName.SetTo(hostName, MAXHOSTNAMELEN); + else + fHostName.Truncate(0); + int socket = ::socket(AF_INET, SOCK_DGRAM, 0); if (socket < 0) return errno; - BNetworkAddress local; - local.SetToWildcard(AF_INET, DHCP_CLIENT_PORT); - // Enable reusing the port. This is needed in case there is more // than 1 interface that needs to be configured. Note that the only reason // this works is because there is code below to bind to a specific @@ -512,17 +521,125 @@ DHCPClient::_Negotiate(dhcp_state state) int option = 1; setsockopt(socket, SOL_SOCKET, SO_REUSEPORT, &option, sizeof(option)); + BNetworkAddress local; + local.SetToWildcard(AF_INET, DHCP_CLIENT_PORT); + + option = 1; + setsockopt(socket, SOL_SOCKET, SO_BROADCAST, &option, sizeof(option)); + if (bind(socket, local, local.Length()) < 0) { close(socket); return errno; } - BNetworkAddress broadcast; - broadcast.SetToBroadcast(AF_INET, DHCP_SERVER_PORT); + bigtime_t previousLeaseTime = fLeaseTime; - option = 1; - setsockopt(socket, SOL_SOCKET, SO_BROADCAST, &option, sizeof(option)); + status_t status = B_OK; + while (state != BOUND) { + status = _StateTransition(socket, state); + if (status != B_OK && (state == SELECTING || state == REBOOTING)) + break; + } + + close(socket); + if (fLeaseTime == 0) + fLeaseTime = previousLeaseTime; + if (fLeaseTime == 0) + fLeaseTime = 60; + + if (fRenewalTime == 0) + fRenewalTime = fLeaseTime / 2; + if (fRebindingTime == 0) + fRebindingTime = fLeaseTime * 7 / 8; + fLeaseTime += fRequestTime; + fRenewalTime += fRequestTime; + fRebindingTime += fRequestTime; + _RestartLease(fRenewalTime); + + fStatus = status; + if (status) + return status; + + // configure interface + BMessage reply; + status = Target().SendMessage(&fConfiguration, &reply); + if (status == B_OK) + status = reply.FindInt32("status", &fStatus); + + // configure resolver + reply.MakeEmpty(); + fResolverConfiguration.AddString("device", Device()); + status = Target().SendMessage(&fResolverConfiguration, &reply); + if (status == B_OK) + status = reply.FindInt32("status", &fStatus); + return status; +} + + +status_t +DHCPClient::_GotMessage(dhcp_state& state, dhcp_message* message) +{ + switch (state) { + case SELECTING: + if (message->Type() == DHCP_OFFER) { + state = REQUESTING; + + fAssignedAddress = message->your_address; + syslog(LOG_INFO, " your_address: %s\n", + _AddressToString(fAssignedAddress).String()); + + fConfiguration.MakeEmpty(); + fConfiguration.AddString("device", Device()); + fConfiguration.AddBool("auto_configured", true); + + BMessage address; + address.AddString("family", "inet"); + address.AddString("address", _AddressToString(fAssignedAddress)); + fResolverConfiguration.MakeEmpty(); + _ParseOptions(*message, address, fResolverConfiguration); + + fConfiguration.AddMessage("address", &address); + return B_OK; + } + + return B_BAD_VALUE; + + case REBOOTING: + case REBINDING: + case RENEWING: + case REQUESTING: + if (message->Type() == DHCP_ACK) { + // TODO: we might want to configure the stuff, don't we? + BMessage address; + fResolverConfiguration.MakeEmpty(); + _ParseOptions(*message, address, fResolverConfiguration); + // TODO: currently, only lease time and DNS is updated this + // way + + // our address request has been acknowledged + state = BOUND; + + return B_OK; + } + + if (message->Type() == DHCP_NACK) { + // server reject our request on previous assigned address + // back to square one... + fAssignedAddress = 0; + state = INIT; + return B_OK; + } + + default: + return B_BAD_VALUE; + } +} + + +status_t +DHCPClient::_StateTransition(int socket, dhcp_state& state) +{ if (state == INIT) { // The local interface does not have an address yet, bind the socket // to the device directly. @@ -532,21 +649,12 @@ DHCPClient::_Negotiate(dhcp_state state) setsockopt(socket, SOL_SOCKET, SO_BINDTODEVICE, &index, sizeof(int)); } - bigtime_t previousLeaseTime = fLeaseTime; - fLeaseTime = 0; - fRenewalTime = 0; - fRebindingTime = 0; + BNetworkAddress broadcast; + broadcast.SetToBroadcast(AF_INET, DHCP_SERVER_PORT); - status_t status = B_ERROR; time_t timeout; uint32 tries; - _ResetTimeout(socket, timeout, tries); - - char hostName[MAXHOSTNAMELEN]; - if (gethostname(hostName, MAXHOSTNAMELEN) == 0) - fHostName.SetTo(hostName, MAXHOSTNAMELEN); - else - fHostName.Truncate(0); + _ResetTimeout(socket, state, timeout, tries); dhcp_message discover(DHCP_DISCOVER); _PrepareMessage(discover, state); @@ -554,34 +662,36 @@ DHCPClient::_Negotiate(dhcp_state state) dhcp_message request(DHCP_REQUEST); _PrepareMessage(request, state); - // send discover/request message - _SendMessage(socket, state == INIT ? discover : request, - state != RENEWING ? broadcast : fServer); - // no need to check the status; in case of an error we'll just send - // the message again - - // receive loop until we've got an offer and acknowledged it + bool skipRequest = false; + dhcp_state originalState = state; + fRequestTime = system_time(); + while (true) { + if (!skipRequest) { + _SendMessage(socket, originalState == INIT ? discover : request, + originalState == RENEWING ? fServer : broadcast); + + if (originalState == INIT) + state = SELECTING; + else if (originalState == INIT_REBOOT) + state = REBOOTING; + } - while (state != BOUND) { char buffer[2048]; struct sockaddr_in from; socklen_t fromLength = sizeof(from); + ssize_t bytesReceived = recvfrom(socket, buffer, sizeof(buffer), 0, (struct sockaddr*)&from, &fromLength); if (bytesReceived < 0 && errno == B_TIMED_OUT) { // depending on the state, we'll just try again - if (!_TimeoutShift(socket, timeout, tries)) { - close(socket); + if (!_TimeoutShift(socket, state, timeout, tries)) return B_TIMED_OUT; - } - - _SendMessage(socket, state == INIT ? discover : request, - state != RENEWING ? broadcast : fServer); + skipRequest = false; continue; - } else if (bytesReceived < 0) - break; + return errno; + skipRequest = true; dhcp_message* message = (dhcp_message*)buffer; if (message->transaction_id != htonl(fTransactionID) || !message->HasOptions() @@ -591,137 +701,15 @@ DHCPClient::_Negotiate(dhcp_state state) continue; } - // advance from startup state - if (state == INIT) - state = SELECTING; - else if (state == INIT_REBOOT) - state = REBOOTING; - syslog(LOG_DEBUG, "%s: Received %s from %s\n", Device(), dhcp_message::TypeToString(message->Type()), _AddressToString(from.sin_addr.s_addr).String()); - switch (message->Type()) { - case DHCP_NONE: - default: - // ignore this message - break; - - case DHCP_OFFER: - { - // first offer wins - if (state != SELECTING) - break; - - // collect interface options - - fAssignedAddress = message->your_address; - syslog(LOG_INFO, " your_address: %s\n", - _AddressToString(fAssignedAddress).String()); - - fConfiguration.MakeEmpty(); - fConfiguration.AddString("device", Device()); - fConfiguration.AddBool("auto_configured", true); - - BMessage address; - address.AddString("family", "inet"); - address.AddString("address", _AddressToString(fAssignedAddress)); - fResolverConfiguration.MakeEmpty(); - _ParseOptions(*message, address, fResolverConfiguration); - - fConfiguration.AddMessage("address", &address); - - // request configuration from the server - - _ResetTimeout(socket, timeout, tries); - state = REQUESTING; - _PrepareMessage(request, state); - - status = _SendMessage(socket, request, broadcast); - // we're sending a broadcast so that all potential offers - // get an answer - break; - } - - case DHCP_ACK: - { - if (state != REQUESTING - && state != REBOOTING - && state != REBINDING - && state != RENEWING) - continue; - - // TODO: we might want to configure the stuff, don't we? - BMessage address; - fResolverConfiguration.MakeEmpty(); - _ParseOptions(*message, address, fResolverConfiguration); - // TODO: currently, only lease time and DNS is updated this - // way - - // our address request has been acknowledged - state = BOUND; - - // configure interface - BMessage reply; - status = Target().SendMessage(&fConfiguration, &reply); - if (status == B_OK) - status = reply.FindInt32("status", &fStatus); - - // configure resolver - reply.MakeEmpty(); - fResolverConfiguration.AddString("device", Device()); - status = Target().SendMessage(&fResolverConfiguration, &reply); - if (status == B_OK) - status = reply.FindInt32("status", &fStatus); - break; - } - - case DHCP_NACK: - if (state != REQUESTING - && state != REBOOTING - && state != REBINDING - && state != RENEWING) - continue; - - if (state == REBOOTING) { - // server reject our request on previous assigned address - // back to square one... - fAssignedAddress = 0; - } - - // try again (maybe we should prefer other servers if this - // happens more than once) - status = _SendMessage(socket, discover, broadcast); - if (status == B_OK) - state = INIT; - break; - } - } - - close(socket); - - if (status == B_OK && fLeaseTime > 0) { - // notify early enough when the lease is - if (fRenewalTime == 0) - fRenewalTime = fLeaseTime * 2/3; - if (fRebindingTime == 0) - fRebindingTime = fLeaseTime * 5/6; - - bigtime_t now = system_time(); - _RestartLease(fRenewalTime); - - fLeaseTime += now; - fRenewalTime += now; - fRebindingTime += now; - // make lease times absolute - } else { - fLeaseTime = previousLeaseTime; - bigtime_t now = system_time(); - fRenewalTime = (fLeaseTime - now) * 2/3 + now; - fRebindingTime = (fLeaseTime - now) * 5/6 + now; + if (_GotMessage(state, message) == B_OK) + break; } - return status; + return B_OK; } @@ -732,7 +720,7 @@ DHCPClient::_RestartLease(bigtime_t leaseTime) return; BMessage lease(kMsgLeaseTime); - fRunner = new BMessageRunner(this, &lease, leaseTime, 1); + fRunner = new BMessageRunner(this, &lease, leaseTime - system_time(), 1); } @@ -899,7 +887,8 @@ DHCPClient::_PrepareMessage(dhcp_message& message, dhcp_state state) void -DHCPClient::_ResetTimeout(int socket, time_t& timeout, uint32& tries) +DHCPClient::_ResetTimeout(int socket, dhcp_state& state, time_t& timeout, + uint32& tries) { timeout = DEFAULT_TIMEOUT; tries = 0; @@ -912,15 +901,36 @@ DHCPClient::_ResetTimeout(int socket, time_t& timeout, uint32& tries) bool -DHCPClient::_TimeoutShift(int socket, time_t& timeout, uint32& tries) +DHCPClient::_TimeoutShift(int socket, dhcp_state& state, time_t& timeout, + uint32& tries) { - timeout += timeout; - if (timeout > MAX_TIMEOUT) { - timeout = DEFAULT_TIMEOUT; + if (state == RENEWING && system_time() > fRebindingTime) { + state = REBINDING; + return false; + } - if (++tries > 2) + if (state == REBINDING && system_time() > fLeaseTime) { + state = INIT; + return false; + } + + tries++; + timeout += timeout; + if (timeout > MAX_TIMEOUT) + timeout = MAX_TIMEOUT; + + if (tries > MAX_RETRIES) { + bigtime_t remaining = 0; + if (state == RENEWING) + remaining = (fRebindingTime - system_time()) / 2 + 1; + else if (state == REBINDING) + remaining = (fLeaseTime - system_time()) / 2 + 1; + else return false; + + timeout = std::max(remaining / 1000000, bigtime_t(60)); } + syslog(LOG_DEBUG, "%s: Timeout shift: %lu secs (try %lu)\n", Device(), timeout, tries); @@ -978,13 +988,12 @@ DHCPClient::_CurrentState() const { bigtime_t now = system_time(); - if (now > fLeaseTime || fStatus < B_OK) + if (now > fLeaseTime || fStatus != B_OK) return INIT; if (now >= fRebindingTime) return REBINDING; if (now >= fRenewalTime) return RENEWING; - return BOUND; } @@ -994,35 +1003,8 @@ DHCPClient::MessageReceived(BMessage* message) { switch (message->what) { case kMsgLeaseTime: - { - dhcp_state state = _CurrentState(); - - bigtime_t next; - if (_Negotiate(state) == B_OK) { - switch (state) { - case RENEWING: - next = fRebindingTime; - break; - case REBINDING: - default: - next = fRenewalTime; - break; - } - } else { - switch (state) { - case RENEWING: - next = (fLeaseTime - fRebindingTime) / 4 + system_time(); - break; - case REBINDING: - default: - next = (fLeaseTime - fRenewalTime) / 4 + system_time(); - break; - } - } - - _RestartLease(next - system_time()); + _Negotiate(_CurrentState()); break; - } default: BHandler::MessageReceived(message); diff --git a/src/servers/net/DHCPClient.h b/src/servers/net/DHCPClient.h index 9cce7e1..b0a02d4 100644 --- a/src/servers/net/DHCPClient.h +++ b/src/servers/net/DHCPClient.h @@ -44,6 +44,9 @@ public: private: status_t _Negotiate(dhcp_state state); + status_t _GotMessage(dhcp_state& state, + dhcp_message* message); + status_t _StateTransition(int socket, dhcp_state& state); void _ParseOptions(dhcp_message& message, BMessage& address, BMessage& resolverConfiguration); @@ -52,10 +55,10 @@ private: status_t _SendMessage(int socket, dhcp_message& message, const BNetworkAddress& address) const; dhcp_state _CurrentState() const; - void _ResetTimeout(int socket, time_t& timeout, - uint32& tries); - bool _TimeoutShift(int socket, time_t& timeout, - uint32& tries); + void _ResetTimeout(int socket, dhcp_state& state, + time_t& timeout, uint32& tries); + bool _TimeoutShift(int socket, dhcp_state& state, + time_t& timeout, uint32& tries); void _RestartLease(bigtime_t lease); static BString _AddressToString(const uint8* data); @@ -71,6 +74,7 @@ private: in_addr_t fAssignedAddress; BNetworkAddress fServer; bigtime_t fStartTime; + bigtime_t fRequestTime; bigtime_t fRenewalTime; bigtime_t fRebindingTime; bigtime_t fLeaseTime; diff --git a/src/servers/net/NetServer.cpp b/src/servers/net/NetServer.cpp index e97f316..de24111 100644 --- a/src/servers/net/NetServer.cpp +++ b/src/servers/net/NetServer.cpp @@ -1388,6 +1388,8 @@ NetServer::_ConvertNetworkFromSettings(BMessage& message) int main(int argc, char** argv) { + srand(system_time()); + status_t status; NetServer server(status); if (status != B_OK) {