Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Implementation of DHCP lease handling #54

Closed
wants to merge 2 commits into from

3 participants

@kmpm

Requires a periodic call to the new method Ethernet.maintain()
http://code.google.com/p/arduino/issues/detail?id=716

@JerrySievert

any possibility of this getting merged in? even when setting a static ip, my host is going offline as soon as a lease expires.

@kmpm

Can you please try it out an get back with your results. With more tests it will be easier to get it in.

@JerrySievert

i had to back down to pre-1.0 for my production hardware, will try to set up another arduino/shield over the weekend with this patch.

@amcewen

Thanks for implementing this. It doesn't seem to have broken anything - I'll report back whether it works in a few days after the lease has expired on the test Arduino I've just set up :-)

I'll also read through the code properly then too, but in the meantime, a few thoughts or items for further discussion that come to mind after a quick look at it...

1) I think it would be useful if Ethernet::maintain() returned a value to let the user know if the IP address had been renewed. Most of the time people would ignore it, but it's possible that a sketch would have read the IP address and so letting them know that it should be re-read is useful to have.

2) Do we need to keep the DhcpClass object around in the Ethernet object all the time? I can see why you've done that, as it keeps the DHCP logic all nicely self-contained, but it also means that any sketch using DHCP will lose around 50 bytes of RAM, which seems a lot when the ideal solution would just store a timeout value. I don't have an obvious solution, but thought I'd start the discussion and then revisit it when I'm doing a more detailed code review.

@JerrySievert

i am seeing much better behavior with this changeset.

@kmpm

Nice to see that it worked.
1) I agree to 100%. Something for next week... Should it differentiate between a renewal and rebind ( new ip ) or just one return value regardless?

2) I was also annoyed with having the Dhcp object around but I couldn't find a clean and nice way of implementing it differently. C/C++ is not my native language so I might miss something obvious. If someone do have a way of fix it then please do it or describe how to do it.

@amcewen

Still haven't had chance to dig into the code, but thought I'd at least report back on my testing so far - haven't actually seen it renew a lease :-/ Obviously that's not a very useful bug report, more of a comment on my investigatons. When I get time to look through the code properly I'll also

1) I don't mind particularly about differentiating between the two, but if it's easy to do then we might as well. If 0 is the "didn't do anything" return then in the trivial case where you don't care which it was out of renewal or rebind, you can just do "if (Ethernet.maintain())"

2) Again, will see if I can come up with something when I run through the code.

@kmpm

I'm closing this pull request and refer to the issue at http://code.google.com/p/arduino/issues/detail?id=716
The work is till in progress, just not in this pull request.

@kmpm kmpm closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 13, 2011
  1. @kmpm

    Implementation of DHCP lease handling

    kmpm authored
    Requires a periodic call to the new method Ethernet.maintain()
    http://code.google.com/p/arduino/issues/detail?id=716
Commits on Dec 15, 2011
  1. @kmpm
This page is out of date. Refresh to see the latest.
View
128 libraries/Ethernet/Dhcp.cpp
@@ -11,13 +11,32 @@
int DhcpClass::beginWithDHCP(uint8_t *mac, unsigned long timeout, unsigned long responseTimeout)
{
- uint8_t dhcp_state = STATE_DHCP_START;
- uint8_t messageType = 0;
-
- // zero out _dhcpMacAddr, _dhcpSubnetMask, _dhcpGatewayIp, _dhcpLocalIp, _dhcpDhcpServerIp, _dhcpDnsServerIp
- memset(_dhcpMacAddr, 0, 26);
+ _dhcpLeaseTime=0;
+ _dhcpT1=0;
+ _dhcpT2=0;
+ _lastCheck=0;
+ _timeout = timeout;
+ _responseTimeout = responseTimeout;
+
+ // zero out _dhcpMacAddr
+ memset(_dhcpMacAddr, 0, 6);
+ reset_DHCP_lease();
memcpy((void*)_dhcpMacAddr, (void*)mac, 6);
+ _dhcp_state = STATE_DHCP_START;
+ return request_DHCP_lease();
+}
+
+void DhcpClass::reset_DHCP_lease(){
+ // zero out _dhcpSubnetMask, _dhcpGatewayIp, _dhcpLocalIp, _dhcpDhcpServerIp, _dhcpDnsServerIp
+ memset(_dhcpLocalIp, 0, 20);
+}
+
+int DhcpClass::request_DHCP_lease(){
+
+ uint8_t messageType = 0;
+
+
// Pick an initial transaction ID
_dhcpTransactionId = random(1UL, 2000UL);
@@ -35,55 +54,75 @@ int DhcpClass::beginWithDHCP(uint8_t *mac, unsigned long timeout, unsigned long
unsigned long startTime = millis();
- while(dhcp_state != STATE_DHCP_LEASED)
+ while(_dhcp_state != STATE_DHCP_LEASED)
{
- if(dhcp_state == STATE_DHCP_START)
+ if(_dhcp_state == STATE_DHCP_START)
{
_dhcpTransactionId++;
send_DHCP_MESSAGE(DHCP_DISCOVER, ((millis() - startTime) / 1000));
- dhcp_state = STATE_DHCP_DISCOVER;
+ _dhcp_state = STATE_DHCP_DISCOVER;
}
- else if(dhcp_state == STATE_DHCP_DISCOVER)
+ else if(_dhcp_state == STATE_DHCP_REREQUEST){
+ _dhcpTransactionId++;
+ send_DHCP_MESSAGE(DHCP_REQUEST, ((millis() - startTime)/1000));
+ _dhcp_state = STATE_DHCP_REQUEST;
+ }
+ else if(_dhcp_state == STATE_DHCP_DISCOVER)
{
uint32_t respId;
- messageType = parseDHCPResponse(responseTimeout, respId);
+ messageType = parseDHCPResponse(_responseTimeout, respId);
if(messageType == DHCP_OFFER)
{
// We'll use the transaction ID that the offer came with,
// rather than the one we were up to
_dhcpTransactionId = respId;
send_DHCP_MESSAGE(DHCP_REQUEST, ((millis() - startTime) / 1000));
- dhcp_state = STATE_DHCP_REQUEST;
+ _dhcp_state = STATE_DHCP_REQUEST;
}
}
- else if(dhcp_state == STATE_DHCP_REQUEST)
+ else if(_dhcp_state == STATE_DHCP_REQUEST)
{
uint32_t respId;
- messageType = parseDHCPResponse(responseTimeout, respId);
+ messageType = parseDHCPResponse(_responseTimeout, respId);
if(messageType == DHCP_ACK)
{
- dhcp_state = STATE_DHCP_LEASED;
+ _dhcp_state = STATE_DHCP_LEASED;
result = 1;
+ //use default lease time if we didn't get it
+ if(_dhcpLeaseTime == 0){
+ _dhcpLeaseTime = DEFAULT_LEASE;
+ }
+ //calculate T1 & T2 if we didn't get it
+ if(_dhcpT1 == 0){
+ //T1 should be 50% of _dhcpLeaseTime
+ _dhcpT1 = _dhcpLeaseTime >> 1;
+ }
+ if(_dhcpT2 == 0){
+ //T2 should be 87.5% (7/8ths) of _dhcpLeaseTime
+ _dhcpT2 = _dhcpT1 << 1;
+ }
+ _renewInSec = _dhcpT1;
+ _rebindInSec = _dhcpT2;
}
else if(messageType == DHCP_NAK)
- dhcp_state = STATE_DHCP_START;
+ _dhcp_state = STATE_DHCP_START;
}
if(messageType == 255)
{
messageType = 0;
- dhcp_state = STATE_DHCP_START;
+ _dhcp_state = STATE_DHCP_START;
}
- if(result != 1 && ((millis() - startTime) > timeout))
+ if(result != 1 && ((millis() - startTime) > _timeout))
break;
}
// We're done with the socket now
_dhcpUdpSocket.stop();
_dhcpTransactionId++;
-
+
return result;
}
@@ -302,8 +341,26 @@ uint8_t DhcpClass::parseDHCPResponse(unsigned long responseTimeout, uint32_t& tr
}
}
break;
-
+
+ case dhcpT1value :
+ opt_len = _dhcpUdpSocket.read();
+ _dhcpUdpSocket.read((uint8_t*)&_dhcpT1, sizeof(_dhcpT1));
+ _dhcpT1 = ntohl(_dhcpT1);
+ break;
+
+ case dhcpT2value :
+ opt_len = _dhcpUdpSocket.read();
+ _dhcpUdpSocket.read((uint8_t*)&_dhcpT2, sizeof(_dhcpT2));
+ _dhcpT2 = ntohl(_dhcpT2);
+ break;
+
case dhcpIPaddrLeaseTime :
+ opt_len = _dhcpUdpSocket.read();
+ _dhcpUdpSocket.read((uint8_t*)&_dhcpLeaseTime, sizeof(_dhcpLeaseTime));
+ _dhcpLeaseTime = ntohl(_dhcpLeaseTime);
+ _renewInSec = _dhcpLeaseTime;
+ break;
+
default :
opt_len = _dhcpUdpSocket.read();
// Skip over the rest of this option
@@ -322,6 +379,39 @@ uint8_t DhcpClass::parseDHCPResponse(unsigned long responseTimeout, uint32_t& tr
return type;
}
+int DhcpClass::checkLease(){
+ unsigned long now = millis();
+ signed long snow = (long)now;
+ int rc=0;
+ if (_lastCheck != 0){
+ if ( snow - (long)_secTimeout >= 0 ){
+ _renewInSec -= 1;
+ _rebindInSec -= 1;
+ _secTimeout = snow + 1000;
+ }
+
+ //if we have a lease but should renew, do it
+ if (_dhcp_state == STATE_DHCP_LEASED && _renewInSec <=0){
+ _dhcp_state = STATE_DHCP_REREQUEST;
+ rc = 1 + request_DHCP_lease();
+ }
+
+ //if we have a lease or is renewing but should bind, do it
+ if( (_dhcp_state == STATE_DHCP_LEASED || _dhcp_state == STATE_DHCP_START) && _rebindInSec <=0){
+ //this should basically restart completely
+ _dhcp_state = STATE_DHCP_START;
+ reset_DHCP_lease();
+ rc = 3 + request_DHCP_lease();
+ }
+ }
+ else{
+ _secTimeout = snow + 1000;
+ }
+
+ _lastCheck = now;
+ return rc;
+}
+
IPAddress DhcpClass::getLocalIp()
{
return IPAddress(_dhcpLocalIp);
View
19 libraries/Ethernet/Dhcp.h
@@ -45,6 +45,13 @@
#define MAX_DHCP_OPT 16
#define HOST_NAME "WIZnet"
+#define DEFAULT_LEASE (900) //default lease time in seconds
+
+#define DHCP_CHECK_NONE (0)
+#define DHCP_CHECK_RENEW_FAIL (1)
+#define DHCP_CHECK_RENEW_OK (2)
+#define DHCP_CHECK_REBIND_FAIL (3)
+#define DHCP_CHECK_REBIND_OK (4)
enum
{
@@ -139,8 +146,19 @@ class DhcpClass {
uint8_t _dhcpGatewayIp[4];
uint8_t _dhcpDhcpServerIp[4];
uint8_t _dhcpDnsServerIp[4];
+ uint32_t _dhcpLeaseTime;
+ uint32_t _dhcpT1, _dhcpT2;
+ signed long _renewInSec;
+ signed long _rebindInSec;
+ signed long _lastCheck;
+ unsigned long _timeout;
+ unsigned long _responseTimeout;
+ unsigned long _secTimeout;
+ uint8_t _dhcp_state;
EthernetUDP _dhcpUdpSocket;
+ int request_DHCP_lease();
+ void reset_DHCP_lease();
void presend_DHCP();
void send_DHCP_MESSAGE(uint8_t, uint16_t);
@@ -153,6 +171,7 @@ class DhcpClass {
IPAddress getDnsServerIp();
int beginWithDHCP(uint8_t *, unsigned long timeout = 60000, unsigned long responseTimeout = 4000);
+ int checkLease();
};
#endif
View
35 libraries/Ethernet/Ethernet.cpp
@@ -10,7 +10,8 @@ uint16_t EthernetClass::_server_port[MAX_SOCK_NUM] = {
int EthernetClass::begin(uint8_t *mac_address)
{
- DhcpClass dhcp;
+ _dhcp = new DhcpClass();
+
// Initialise the basic info
W5100.init();
@@ -18,15 +19,15 @@ int EthernetClass::begin(uint8_t *mac_address)
W5100.setIPAddress(IPAddress(0,0,0,0).raw_address());
// Now try to get our config info from a DHCP server
- int ret = dhcp.beginWithDHCP(mac_address);
+ int ret = _dhcp->beginWithDHCP(mac_address);
if(ret == 1)
{
// We've successfully found a DHCP server and got our configuration info, so set things
// accordingly
- W5100.setIPAddress(dhcp.getLocalIp().raw_address());
- W5100.setGatewayIp(dhcp.getGatewayIp().raw_address());
- W5100.setSubnetMask(dhcp.getSubnetMask().raw_address());
- _dnsServerAddress = dhcp.getDnsServerIp();
+ W5100.setIPAddress(_dhcp->getLocalIp().raw_address());
+ W5100.setGatewayIp(_dhcp->getGatewayIp().raw_address());
+ W5100.setSubnetMask(_dhcp->getSubnetMask().raw_address());
+ _dnsServerAddress = _dhcp->getDnsServerIp();
}
return ret;
@@ -66,6 +67,28 @@ void EthernetClass::begin(uint8_t *mac, IPAddress local_ip, IPAddress dns_server
_dnsServerAddress = dns_server;
}
+void EthernetClass::maintain(){
+ if(_dhcp != NULL){
+ //we have a pointer to dhcp, use it
+ switch (_dhcp->checkLease() ){
+ case DHCP_CHECK_NONE:
+ //nothing done
+ break;
+ case DHCP_CHECK_RENEW_OK:
+ case DHCP_CHECK_REBIND_OK:
+ //we might have got a new IP.
+ W5100.setIPAddress(_dhcp->getLocalIp().raw_address());
+ W5100.setGatewayIp(_dhcp->getGatewayIp().raw_address());
+ W5100.setSubnetMask(_dhcp->getSubnetMask().raw_address());
+ _dnsServerAddress = _dhcp->getDnsServerIp();
+ break;
+ default:
+ //this is actually a error, it will retry though
+ break;
+ }
+ }
+}
+
IPAddress EthernetClass::localIP()
{
IPAddress ret;
View
3  libraries/Ethernet/Ethernet.h
@@ -6,12 +6,14 @@
#include "IPAddress.h"
#include "EthernetClient.h"
#include "EthernetServer.h"
+#include "Dhcp.h"
#define MAX_SOCK_NUM 4
class EthernetClass {
private:
IPAddress _dnsServerAddress;
+ DhcpClass* _dhcp;
public:
static uint8_t _state[MAX_SOCK_NUM];
static uint16_t _server_port[MAX_SOCK_NUM];
@@ -23,6 +25,7 @@ class EthernetClass {
void begin(uint8_t *mac_address, IPAddress local_ip, IPAddress dns_server);
void begin(uint8_t *mac_address, IPAddress local_ip, IPAddress dns_server, IPAddress gateway);
void begin(uint8_t *mac_address, IPAddress local_ip, IPAddress dns_server, IPAddress gateway, IPAddress subnet);
+ void maintain();
IPAddress localIP();
IPAddress subnetMask();
Something went wrong with that request. Please try again.