1 // DHCP Library v0.3 - April 25, 2009
2 // Author: Jordan Terrell - blog.jordanterrell.com
3 
4 #include "utility/w5100.h"
5 
6 #include <string.h>
7 #include <stdlib.h>
8 #include "Dhcp.h"
9 #include "Arduino.h"
10 #include "utility/util.h"
11 
beginWithDHCP(uint8_t * mac,unsigned long timeout,unsigned long responseTimeout)12 int DhcpClass::beginWithDHCP(uint8_t *mac, unsigned long timeout, unsigned long responseTimeout)
13 {
14     _dhcpLeaseTime=0;
15     _dhcpT1=0;
16     _dhcpT2=0;
17     _timeout = timeout;
18     _responseTimeout = responseTimeout;
19 
20     // zero out _dhcpMacAddr
21     memset(_dhcpMacAddr, 0, 6);
22     reset_DHCP_lease();
23 
24     memcpy((void*)_dhcpMacAddr, (void*)mac, 6);
25     _dhcp_state = STATE_DHCP_START;
26     return request_DHCP_lease();
27 }
28 
reset_DHCP_lease()29 void DhcpClass::reset_DHCP_lease(){
30     // zero out _dhcpSubnetMask, _dhcpGatewayIp, _dhcpLocalIp, _dhcpDhcpServerIp, _dhcpDnsServerIp
31     memset(_dhcpLocalIp, 0, 20);
32 }
33 
34 //return:0 on error, 1 if request is sent and response is received
request_DHCP_lease()35 int DhcpClass::request_DHCP_lease(){
36 
37     uint8_t messageType = 0;
38 
39 
40 
41     // Pick an initial transaction ID
42     _dhcpTransactionId = random(1UL, 2000UL);
43     _dhcpInitialTransactionId = _dhcpTransactionId;
44 
45     _dhcpUdpSocket.stop();
46     if (_dhcpUdpSocket.begin(DHCP_CLIENT_PORT) == 0)
47     {
48       // Couldn't get a socket
49       return 0;
50     }
51 
52     presend_DHCP();
53 
54     int result = 0;
55 
56     unsigned long startTime = millis();
57 
58     while(_dhcp_state != STATE_DHCP_LEASED)
59     {
60         if(_dhcp_state == STATE_DHCP_START)
61         {
62             _dhcpTransactionId++;
63 
64             send_DHCP_MESSAGE(DHCP_DISCOVER, ((millis() - startTime) / 1000));
65             _dhcp_state = STATE_DHCP_DISCOVER;
66         }
67         else if(_dhcp_state == STATE_DHCP_REREQUEST){
68             _dhcpTransactionId++;
69             send_DHCP_MESSAGE(DHCP_REQUEST, ((millis() - startTime)/1000));
70             _dhcp_state = STATE_DHCP_REQUEST;
71         }
72         else if(_dhcp_state == STATE_DHCP_DISCOVER)
73         {
74             uint32_t respId;
75             messageType = parseDHCPResponse(_responseTimeout, respId);
76             if(messageType == DHCP_OFFER)
77             {
78                 // We'll use the transaction ID that the offer came with,
79                 // rather than the one we were up to
80                 _dhcpTransactionId = respId;
81                 send_DHCP_MESSAGE(DHCP_REQUEST, ((millis() - startTime) / 1000));
82                 _dhcp_state = STATE_DHCP_REQUEST;
83             }
84         }
85         else if(_dhcp_state == STATE_DHCP_REQUEST)
86         {
87             uint32_t respId;
88             messageType = parseDHCPResponse(_responseTimeout, respId);
89             if(messageType == DHCP_ACK)
90             {
91                 _dhcp_state = STATE_DHCP_LEASED;
92                 result = 1;
93                 //use default lease time if we didn't get it
94                 if(_dhcpLeaseTime == 0){
95                     _dhcpLeaseTime = DEFAULT_LEASE;
96                 }
97                 // Calculate T1 & T2 if we didn't get it
98                 if(_dhcpT1 == 0){
99                     // T1 should be 50% of _dhcpLeaseTime
100                     _dhcpT1 = _dhcpLeaseTime >> 1;
101                 }
102                 if(_dhcpT2 == 0){
103                     // T2 should be 87.5% (7/8ths) of _dhcpLeaseTime
104                     _dhcpT2 = _dhcpLeaseTime - (_dhcpLeaseTime >> 3);
105                 }
106                 _renewInSec = _dhcpT1;
107                 _rebindInSec = _dhcpT2;
108             }
109             else if(messageType == DHCP_NAK)
110                 _dhcp_state = STATE_DHCP_START;
111         }
112 
113         if(messageType == 255)
114         {
115             messageType = 0;
116             _dhcp_state = STATE_DHCP_START;
117         }
118 
119         if(result != 1 && ((millis() - startTime) > _timeout))
120             break;
121     }
122 
123     // We're done with the socket now
124     _dhcpUdpSocket.stop();
125     _dhcpTransactionId++;
126 
127     _lastCheckLeaseMillis = millis();
128     return result;
129 }
130 
presend_DHCP()131 void DhcpClass::presend_DHCP()
132 {
133 }
134 
send_DHCP_MESSAGE(uint8_t messageType,uint16_t secondsElapsed)135 void DhcpClass::send_DHCP_MESSAGE(uint8_t messageType, uint16_t secondsElapsed)
136 {
137     uint8_t buffer[32];
138     memset(buffer, 0, 32);
139     IPAddress dest_addr( 255, 255, 255, 255 ); // Broadcast address
140 
141     if (-1 == _dhcpUdpSocket.beginPacket(dest_addr, DHCP_SERVER_PORT))
142     {
143         // FIXME Need to return errors
144         return;
145     }
146 
147     buffer[0] = DHCP_BOOTREQUEST;   // op
148     buffer[1] = DHCP_HTYPE10MB;     // htype
149     buffer[2] = DHCP_HLENETHERNET;  // hlen
150     buffer[3] = DHCP_HOPS;          // hops
151 
152     // xid
153     unsigned long xid = htonl(_dhcpTransactionId);
154     memcpy(buffer + 4, &(xid), 4);
155 
156     // 8, 9 - seconds elapsed
157     buffer[8] = ((secondsElapsed & 0xff00) >> 8);
158     buffer[9] = (secondsElapsed & 0x00ff);
159 
160     // flags
161     unsigned short flags = htons(DHCP_FLAGSBROADCAST);
162     memcpy(buffer + 10, &(flags), 2);
163 
164     // ciaddr: already zeroed
165     // yiaddr: already zeroed
166     // siaddr: already zeroed
167     // giaddr: already zeroed
168 
169     //put data in W5100 transmit buffer
170     _dhcpUdpSocket.write(buffer, 28);
171 
172     memset(buffer, 0, 32); // clear local buffer
173 
174     memcpy(buffer, _dhcpMacAddr, 6); // chaddr
175 
176     //put data in W5100 transmit buffer
177     _dhcpUdpSocket.write(buffer, 16);
178 
179     memset(buffer, 0, 32); // clear local buffer
180 
181     // leave zeroed out for sname && file
182     // put in W5100 transmit buffer x 6 (192 bytes)
183 
184     for(int i = 0; i < 6; i++) {
185         _dhcpUdpSocket.write(buffer, 32);
186     }
187 
188     // OPT - Magic Cookie
189     buffer[0] = (uint8_t)((MAGIC_COOKIE >> 24)& 0xFF);
190     buffer[1] = (uint8_t)((MAGIC_COOKIE >> 16)& 0xFF);
191     buffer[2] = (uint8_t)((MAGIC_COOKIE >> 8)& 0xFF);
192     buffer[3] = (uint8_t)(MAGIC_COOKIE& 0xFF);
193 
194     // OPT - message type
195     buffer[4] = dhcpMessageType;
196     buffer[5] = 0x01;
197     buffer[6] = messageType; //DHCP_REQUEST;
198 
199     // OPT - client identifier
200     buffer[7] = dhcpClientIdentifier;
201     buffer[8] = 0x07;
202     buffer[9] = 0x01;
203     memcpy(buffer + 10, _dhcpMacAddr, 6);
204 
205     // OPT - host name
206     buffer[16] = hostName;
207     buffer[17] = strlen(HOST_NAME) + 6; // length of hostname + last 3 bytes of mac address
208     strcpy((char*)&(buffer[18]), HOST_NAME);
209 
210     printByte((char*)&(buffer[24]), _dhcpMacAddr[3]);
211     printByte((char*)&(buffer[26]), _dhcpMacAddr[4]);
212     printByte((char*)&(buffer[28]), _dhcpMacAddr[5]);
213 
214     //put data in W5100 transmit buffer
215     _dhcpUdpSocket.write(buffer, 30);
216 
217     if(messageType == DHCP_REQUEST)
218     {
219         buffer[0] = dhcpRequestedIPaddr;
220         buffer[1] = 0x04;
221         buffer[2] = _dhcpLocalIp[0];
222         buffer[3] = _dhcpLocalIp[1];
223         buffer[4] = _dhcpLocalIp[2];
224         buffer[5] = _dhcpLocalIp[3];
225 
226         buffer[6] = dhcpServerIdentifier;
227         buffer[7] = 0x04;
228         buffer[8] = _dhcpDhcpServerIp[0];
229         buffer[9] = _dhcpDhcpServerIp[1];
230         buffer[10] = _dhcpDhcpServerIp[2];
231         buffer[11] = _dhcpDhcpServerIp[3];
232 
233         //put data in W5100 transmit buffer
234         _dhcpUdpSocket.write(buffer, 12);
235     }
236 
237     buffer[0] = dhcpParamRequest;
238     buffer[1] = 0x06;
239     buffer[2] = subnetMask;
240     buffer[3] = routersOnSubnet;
241     buffer[4] = dns;
242     buffer[5] = domainName;
243     buffer[6] = dhcpT1value;
244     buffer[7] = dhcpT2value;
245     buffer[8] = endOption;
246 
247     //put data in W5100 transmit buffer
248     _dhcpUdpSocket.write(buffer, 9);
249 
250     _dhcpUdpSocket.endPacket();
251 }
252 
parseDHCPResponse(unsigned long responseTimeout,uint32_t & transactionId)253 uint8_t DhcpClass::parseDHCPResponse(unsigned long responseTimeout, uint32_t& transactionId)
254 {
255     uint8_t type = 0;
256     uint8_t opt_len = 0;
257 
258     unsigned long startTime = millis();
259 
260     while(_dhcpUdpSocket.parsePacket() <= 0)
261     {
262         if((millis() - startTime) > responseTimeout)
263         {
264             return 255;
265         }
266         delay(50);
267     }
268     // start reading in the packet
269     RIP_MSG_FIXED fixedMsg;
270     _dhcpUdpSocket.read((uint8_t*)&fixedMsg, sizeof(RIP_MSG_FIXED));
271 
272     if(fixedMsg.op == DHCP_BOOTREPLY && _dhcpUdpSocket.remotePort() == DHCP_SERVER_PORT)
273     {
274         transactionId = ntohl(fixedMsg.xid);
275         if(memcmp(fixedMsg.chaddr, _dhcpMacAddr, 6) != 0 || (transactionId < _dhcpInitialTransactionId) || (transactionId > _dhcpTransactionId))
276         {
277             // Need to read the rest of the packet here regardless
278             _dhcpUdpSocket.flush();
279             return 0;
280         }
281 
282         memcpy(_dhcpLocalIp, fixedMsg.yiaddr, 4);
283 
284         // Skip to the option part
285         // Doing this a byte at a time so we don't have to put a big buffer
286         // on the stack (as we don't have lots of memory lying around)
287         for (int i =0; i < (240 - (int)sizeof(RIP_MSG_FIXED)); i++)
288         {
289             _dhcpUdpSocket.read(); // we don't care about the returned byte
290         }
291 
292         while (_dhcpUdpSocket.available() > 0)
293         {
294             switch (_dhcpUdpSocket.read())
295             {
296                 case endOption :
297                     break;
298 
299                 case padOption :
300                     break;
301 
302                 case dhcpMessageType :
303                     opt_len = _dhcpUdpSocket.read();
304                     type = _dhcpUdpSocket.read();
305                     break;
306 
307                 case subnetMask :
308                     opt_len = _dhcpUdpSocket.read();
309                     _dhcpUdpSocket.read(_dhcpSubnetMask, 4);
310                     break;
311 
312                 case routersOnSubnet :
313                     opt_len = _dhcpUdpSocket.read();
314                     _dhcpUdpSocket.read(_dhcpGatewayIp, 4);
315                     for (int i = 0; i < opt_len-4; i++)
316                     {
317                         _dhcpUdpSocket.read();
318                     }
319                     break;
320 
321                 case dns :
322                     opt_len = _dhcpUdpSocket.read();
323                     _dhcpUdpSocket.read(_dhcpDnsServerIp, 4);
324                     for (int i = 0; i < opt_len-4; i++)
325                     {
326                         _dhcpUdpSocket.read();
327                     }
328                     break;
329 
330                 case dhcpServerIdentifier :
331                     opt_len = _dhcpUdpSocket.read();
332                     if ((_dhcpDhcpServerIp[0] == 0 && _dhcpDhcpServerIp[1] == 0 &&
333                          _dhcpDhcpServerIp[2] == 0 && _dhcpDhcpServerIp[3] == 0) ||
334                         IPAddress(_dhcpDhcpServerIp) == _dhcpUdpSocket.remoteIP())
335                     {
336                         _dhcpUdpSocket.read(_dhcpDhcpServerIp, sizeof(_dhcpDhcpServerIp));
337                     }
338                     else
339                     {
340                         // Skip over the rest of this option
341                         while (opt_len--)
342                         {
343                             _dhcpUdpSocket.read();
344                         }
345                     }
346                     break;
347 
348                 case dhcpT1value :
349                     opt_len = _dhcpUdpSocket.read();
350                     _dhcpUdpSocket.read((uint8_t*)&_dhcpT1, sizeof(_dhcpT1));
351                     _dhcpT1 = ntohl(_dhcpT1);
352                     break;
353 
354                 case dhcpT2value :
355                     opt_len = _dhcpUdpSocket.read();
356                     _dhcpUdpSocket.read((uint8_t*)&_dhcpT2, sizeof(_dhcpT2));
357                     _dhcpT2 = ntohl(_dhcpT2);
358                     break;
359 
360                 case dhcpIPaddrLeaseTime :
361                     opt_len = _dhcpUdpSocket.read();
362                     _dhcpUdpSocket.read((uint8_t*)&_dhcpLeaseTime, sizeof(_dhcpLeaseTime));
363                     _dhcpLeaseTime = ntohl(_dhcpLeaseTime);
364                     _renewInSec = _dhcpLeaseTime;
365                     break;
366 
367                 default :
368                     opt_len = _dhcpUdpSocket.read();
369                     // Skip over the rest of this option
370                     while (opt_len--)
371                     {
372                         _dhcpUdpSocket.read();
373                     }
374                     break;
375             }
376         }
377     }
378 
379     // Need to skip to end of the packet regardless here
380     _dhcpUdpSocket.flush();
381 
382     return type;
383 }
384 
385 
386 /*
387     returns:
388     0/DHCP_CHECK_NONE: nothing happened
389     1/DHCP_CHECK_RENEW_FAIL: renew failed
390     2/DHCP_CHECK_RENEW_OK: renew success
391     3/DHCP_CHECK_REBIND_FAIL: rebind fail
392     4/DHCP_CHECK_REBIND_OK: rebind success
393 */
checkLease()394 int DhcpClass::checkLease(){
395     int rc = DHCP_CHECK_NONE;
396 
397     unsigned long now = millis();
398     unsigned long elapsed = now - _lastCheckLeaseMillis;
399 
400     // if more then one sec passed, reduce the counters accordingly
401     if (elapsed >= 1000) {
402         // set the new timestamps
403         _lastCheckLeaseMillis = now - (elapsed % 1000);
404         elapsed = elapsed / 1000;
405 
406         // decrease the counters by elapsed seconds
407         // we assume that the cycle time (elapsed) is fairly constant
408         // if the remainder is less than cycle time * 2
409         // do it early instead of late
410         if (_renewInSec < elapsed * 2)
411             _renewInSec = 0;
412         else
413             _renewInSec -= elapsed;
414 
415         if (_rebindInSec < elapsed * 2)
416             _rebindInSec = 0;
417         else
418             _rebindInSec -= elapsed;
419     }
420 
421     // if we have a lease but should renew, do it
422     if (_renewInSec == 0 &&_dhcp_state == STATE_DHCP_LEASED) {
423         _dhcp_state = STATE_DHCP_REREQUEST;
424         rc = 1 + request_DHCP_lease();
425     }
426 
427     // if we have a lease or is renewing but should bind, do it
428     if (_rebindInSec == 0 && (_dhcp_state == STATE_DHCP_LEASED || _dhcp_state == STATE_DHCP_START)) {
429         // this should basically restart completely
430         _dhcp_state = STATE_DHCP_START;
431         reset_DHCP_lease();
432         rc = 3 + request_DHCP_lease();
433     }
434     return rc;
435 }
436 
getLocalIp()437 IPAddress DhcpClass::getLocalIp()
438 {
439     return IPAddress(_dhcpLocalIp);
440 }
441 
getSubnetMask()442 IPAddress DhcpClass::getSubnetMask()
443 {
444     return IPAddress(_dhcpSubnetMask);
445 }
446 
getGatewayIp()447 IPAddress DhcpClass::getGatewayIp()
448 {
449     return IPAddress(_dhcpGatewayIp);
450 }
451 
getDhcpServerIp()452 IPAddress DhcpClass::getDhcpServerIp()
453 {
454     return IPAddress(_dhcpDhcpServerIp);
455 }
456 
getDnsServerIp()457 IPAddress DhcpClass::getDnsServerIp()
458 {
459     return IPAddress(_dhcpDnsServerIp);
460 }
461 
printByte(char * buf,uint8_t n)462 void DhcpClass::printByte(char * buf, uint8_t n ) {
463   char *str = &buf[1];
464   buf[0]='0';
465   do {
466     unsigned long m = n;
467     n /= 16;
468     char c = m - 16 * n;
469     *str-- = c < 10 ? c + '0' : c + 'A' - 10;
470   } while(n);
471 }
472