implement DHCP (not yet bug-free)
Stefan Schuermans

Stefan Schuermans commited on 2012-04-04 22:27:47
Showing 5 changed files, with 544 additions and 4 deletions.

... ...
@@ -2,15 +2,26 @@
2 2
 #include "macros.h"
3 3
 #include "nethelp.h"
4 4
 
5
-// MAC address
5
+/// MAC address
6 6
 /* extern */ struct config_mac config_mac = {
7 7
   .mac = { 0x02, 0x4D, 0x49, 0x50, 0x53, 0x01 }
8 8
 };
9 9
 
10
-// IP configuration
10
+/**
11
+ * @brief IP configuration
12
+ *
13
+ * fill with meaningful data for static configuration
14
+ * or fill with zeros for DHCP
15
+ */
11 16
 /* extern */ struct config_ip config_ip = {
17
+#if 0
12 18
   .ip = { 192, 168, 0, 89 },
13 19
   .mask = { 255, 255, 255, 0 },
14 20
   .gw = { 192, 168, 0, 1 }
21
+#else
22
+  .ip = { 0, 0, 0, 0 },
23
+  .mask = { 0, 0, 0, 0 },
24
+  .gw = { 0, 0, 0, 0 }
25
+#endif
15 26
 };
16 27
 
... ...
@@ -0,0 +1,476 @@
1
+#include "config.h"
2
+#include "checksum.h"
3
+#include "dhcp.h"
4
+#include "ethernet.h"
5
+#include "ip.h"
6
+#include "macros.h"
7
+#include "memcpy.h"
8
+#include "nethelp.h"
9
+#include "random.h"
10
+#include "udp.h"
11
+
12
+/// configuration
13
+//@{
14
+/// avarage timeout after which to retry DHCP action
15
+static const unsigned char dhcp_retry_secs_max = 10;
16
+/// maximum number of retries before aborting DHCP action
17
+static const unsigned char dhcp_retries_max = 5;
18
+/// when to ask to extend lease
19
+static const unsigned long dhcp_lease_rest_min = 300;
20
+/// to renw lease when only this fraction of lease is left
21
+static const unsigned char dhcp_lease_renew_fraction = 8;
22
+//@}
23
+
24
+/// DHCP tick counter to get full seconds
25
+static unsigned char dhcp_ticks = 0;
26
+
27
+/// time of current DHCP action
28
+//@{
29
+static unsigned char dhcp_in_progress = 0; ///< if DHCP action is in progress
30
+static unsigned char dhcp_retries = 0; ///< number of retries left
31
+static unsigned char dhcp_retry_secs = 0; ///< rest of time for current retry
32
+//@}
33
+
34
+/// current x_id
35
+//@{
36
+static unsigned char dhcp_have_x_id = 0; ///< if an x_id exists at the moment
37
+static unsigned long dhcp_x_id = 0; ///< the current x_id data
38
+static unsigned short dhcp_x_id_secs = 0; ///< time of x_id
39
+//@}
40
+
41
+/// DHCP server and lease time
42
+//@{
43
+static unsigned char dhcp_active = 0; ///< if own IP is leased from DHCP
44
+static unsigned char dhcp_server[4]; /**< IP address of DHCP server
45
+                                          own IP is leased from */
46
+static unsigned long dhcp_lease_time = 0; ///< total lease time
47
+static unsigned long dhcp_lease_rest = 0; ///< rest of lease time
48
+//@}
49
+
50
+/**
51
+ * @brief send a DHCP packet
52
+ * @param[in] ptr pointer to data of packet
53
+ * @param[in] sz size of packet
54
+ *
55
+ * ptr must point to a dhcp_packet
56
+ * with ip_hdr.dest, dhcp_hdr.x_id, dhcp_hdr.secs, dhcp_hdr.flags
57
+ * and dhcp_hdr.yi_addr already initialized
58
+ */
59
+static void dhcp_send(void *ptr, unsigned int sz)
60
+{
61
+  struct dhcp_packet *p_dhcp_pack;
62
+
63
+  // packet too short
64
+  if (sz < sizeof(struct dhcp_packet))
65
+    return;
66
+
67
+  p_dhcp_pack = ptr;
68
+
69
+  // we are DHCP client
70
+  // - source port must be 68
71
+  // - destination port must be 67
72
+  p_dhcp_pack->udp_hdr.src_port = htons(68);
73
+  p_dhcp_pack->udp_hdr.dest_port = htons(67);
74
+  // set up DHCP header fields as client
75
+  p_dhcp_pack->dhcp_hdr.op = 1;
76
+  p_dhcp_pack->dhcp_hdr.h_type = 1;
77
+  p_dhcp_pack->dhcp_hdr.h_len = 6;
78
+  p_dhcp_pack->dhcp_hdr.h_ops = 0;
79
+  memset(p_dhcp_pack->dhcp_hdr.ci_addr, 0, 4);
80
+  memset(p_dhcp_pack->dhcp_hdr.si_addr, 0, 4);
81
+  memset(p_dhcp_pack->dhcp_hdr.gi_addr, 0, 4);
82
+  mac_cpy(p_dhcp_pack->dhcp_hdr.ch_addr, config_mac.mac);
83
+  memset(p_dhcp_pack->dhcp_hdr.ch_addr + 6, 0, 10);
84
+  memset(p_dhcp_pack->dhcp_hdr.s_name, 0, 64);
85
+  memset(p_dhcp_pack->dhcp_hdr.file, 0, 128);
86
+  p_dhcp_pack->dhcp_hdr.m_cookie = htonl(0x63825363);
87
+
88
+  // send DHCP packet
89
+  udp_send(ptr, sz);
90
+}
91
+
92
+/// send a DHCP discover packet
93
+static void dhcp_discover(void)
94
+{
95
+  struct {
96
+    struct dhcp_packet dhcp_pack;
97
+    unsigned char opt_type[3];
98
+    unsigned char opt_end[2];
99
+  } __attribute__((packed)) dhcp_discover;
100
+
101
+  ip_cpy(dhcp_discover.dhcp_pack.ip_hdr.dest, "\xFF\xFF\xFF\xFF"); // broadcast
102
+  dhcp_discover.dhcp_pack.dhcp_hdr.x_id = htonl(dhcp_x_id);
103
+  dhcp_discover.dhcp_pack.dhcp_hdr.secs = htons(dhcp_x_id_secs); // x_id time
104
+  dhcp_discover.dhcp_pack.dhcp_hdr.flags = htons(0x8000); // broadcast
105
+  memset(dhcp_discover.dhcp_pack.dhcp_hdr.yi_addr, 0, 4);
106
+  dhcp_discover.opt_type[0] = 0x35; // DHCP discover
107
+  dhcp_discover.opt_type[1] = 0x01;
108
+  dhcp_discover.opt_type[2] = 0x01;
109
+  dhcp_discover.opt_end[0] = 0xFF; // end of options
110
+  dhcp_discover.opt_end[1] = 0x00;
111
+
112
+  dhcp_send(&dhcp_discover, sizeof(dhcp_discover));
113
+}
114
+
115
+/// send a DHCP request packet
116
+static void dhcp_request(void)
117
+{
118
+  struct {
119
+    struct dhcp_packet dhcp_pack;
120
+    unsigned char opt_type[3];
121
+    unsigned char opt_req[6];
122
+    unsigned char opt_server[6];
123
+    unsigned char opt_end[2];
124
+  } __attribute__((packed)) dhcp_request;
125
+
126
+  ip_cpy(dhcp_request.dhcp_pack.ip_hdr.dest, dhcp_server); // to DHCP server
127
+  dhcp_request.dhcp_pack.dhcp_hdr.x_id = htonl(dhcp_x_id);
128
+  dhcp_request.dhcp_pack.dhcp_hdr.secs = htons(dhcp_x_id_secs); // x_id time
129
+  dhcp_request.dhcp_pack.dhcp_hdr.flags = htons(0x0000);
130
+  ip_cpy(dhcp_request.dhcp_pack.dhcp_hdr.yi_addr, config_ip.ip);
131
+  dhcp_request.opt_type[0] = 0x35; // DHCP request
132
+  dhcp_request.opt_type[1] = 0x01;
133
+  dhcp_request.opt_type[2] = 0x03;
134
+  dhcp_request.opt_req[0] = 0x32; // requested IP
135
+  dhcp_request.opt_req[1] = 0x04;
136
+  ip_cpy(&dhcp_request.opt_req[2], config_ip.ip);
137
+  dhcp_request.opt_server[0] = 0x36; // DHCP server
138
+  dhcp_request.opt_server[1] = 0x04;
139
+  ip_cpy(&dhcp_request.opt_server[2], dhcp_server);
140
+  dhcp_request.opt_end[0] = 0xFF; // end of options
141
+  dhcp_request.opt_end[1] = 0x00;
142
+
143
+  dhcp_send(&dhcp_request, sizeof(dhcp_request));
144
+}
145
+
146
+/// abort DHCP action
147
+static void dhcp_abort(void)
148
+{
149
+  // reset DHCP action status
150
+  dhcp_in_progress = 0;
151
+  dhcp_retries = 0;
152
+  dhcp_retry_secs = 0;
153
+
154
+  // forget x_id
155
+  dhcp_have_x_id = 0;
156
+  dhcp_x_id = 0;
157
+  dhcp_x_id_secs = 0;
158
+
159
+  // give up IP address if it was leased
160
+  if (dhcp_active) {
161
+    ip_cpy(config_ip.ip, "\0\0\0\0");
162
+    ip_cpy(config_ip.mask, "\0\0\0\0");
163
+    ip_cpy(config_ip.gw, "\0\0\0\0");
164
+    dhcp_active = 0;
165
+    dhcp_lease_time = 0;
166
+    dhcp_lease_rest = 0;
167
+  }
168
+}
169
+
170
+/// retry DHCP action
171
+static void dhcp_retry(void)
172
+{
173
+  // DHCP lease not active ---> re-send DHCP discover
174
+  if (! dhcp_active) {
175
+    // get time for next try
176
+    random_get_data(&dhcp_retry_secs, sizeof(dhcp_retry_secs));
177
+    dhcp_retry_secs = dhcp_retry_secs % dhcp_retry_secs_max +
178
+                      dhcp_retry_secs_max / 2;
179
+    // re-send DHCP discover
180
+    dhcp_discover();
181
+  }
182
+
183
+  // DHCP lease active and lease time almost over ---> re-send DHCP request
184
+  if (dhcp_active && (dhcp_lease_rest < dhcp_lease_rest_min ||
185
+                      dhcp_lease_rest < dhcp_lease_time /
186
+                                        dhcp_lease_renew_fraction)) {
187
+    // get time for next try
188
+    random_get_data(&dhcp_retry_secs, sizeof(dhcp_retry_secs));
189
+    dhcp_retry_secs = dhcp_retry_secs % dhcp_retry_secs_max +
190
+                      dhcp_retry_secs_max / 2;
191
+    // re-send DHCP request
192
+    dhcp_request();
193
+  }
194
+}
195
+
196
+/// start new DHCP action
197
+static void dhcp_start(void)
198
+{
199
+  // DHCP lease not active ---> DHCP discover
200
+  if (! dhcp_active) {
201
+    // DHCP operation starts
202
+    dhcp_in_progress = 1;
203
+    dhcp_retries = dhcp_retries_max;
204
+    random_get_data(&dhcp_retry_secs, sizeof(dhcp_retry_secs));
205
+    dhcp_retry_secs = dhcp_retry_secs % dhcp_retry_secs_max +
206
+                      dhcp_retry_secs_max / 2;
207
+    // get new x_id
208
+    random_get_data(&dhcp_x_id, sizeof(dhcp_x_id));
209
+    dhcp_x_id_secs++;
210
+    dhcp_have_x_id = 1;
211
+    // send DHCP discover
212
+    dhcp_discover();
213
+  }
214
+
215
+  // DHCP lease active and lease time almost over ---> DHCP request
216
+  if (dhcp_active && (dhcp_lease_rest < dhcp_lease_rest_min ||
217
+                      dhcp_lease_rest < dhcp_lease_time /
218
+                                        dhcp_lease_renew_fraction)) {
219
+    // DHCP operation starts
220
+    dhcp_in_progress = 1;
221
+    dhcp_retries = dhcp_retries_max;
222
+    random_get_data(&dhcp_retry_secs, sizeof(dhcp_retry_secs));
223
+    dhcp_retry_secs = dhcp_retry_secs % dhcp_retry_secs_max +
224
+                      dhcp_retry_secs_max / 2;
225
+    // get new x_id if none available
226
+    if (! dhcp_have_x_id) {
227
+      random_get_data(&dhcp_x_id, sizeof(dhcp_x_id));
228
+      dhcp_x_id_secs++;
229
+      dhcp_have_x_id = 1;
230
+    }
231
+    // send DHCP request
232
+    dhcp_request();
233
+  }
234
+}
235
+
236
+/// tick procedure - every 1000ms
237
+static void dhcp_tick1000(void)
238
+{
239
+  // DHCP operation in progress
240
+  if (dhcp_in_progress) {
241
+
242
+    // count down time of current try of current action
243
+    dhcp_retry_secs--;
244
+    if (dhcp_retry_secs <= 0) {
245
+
246
+      // count down retries
247
+      dhcp_retries--;
248
+
249
+      // no more retries left ---> abort, retry otherwise
250
+      if (dhcp_retries <= 0)
251
+        dhcp_abort();
252
+      else
253
+        dhcp_retry();
254
+
255
+    }
256
+
257
+  }
258
+
259
+  // increase time of x_id
260
+  dhcp_x_id_secs++;
261
+
262
+  // dcrease remaining lease time
263
+  dhcp_lease_rest--;
264
+  // lease timed out ---> abort DHCP (will also invalidate IP)
265
+  if (dhcp_lease_rest <= 0)
266
+    dhcp_abort();
267
+}
268
+
269
+/// tick procedure - call every 200ms
270
+void dhcp_tick200(void)
271
+{
272
+  // get 1 second interval
273
+  dhcp_ticks++;
274
+  if (dhcp_ticks >= 5) {
275
+    dhcp_ticks = 0;
276
+    dhcp_tick1000();
277
+  }
278
+
279
+  // no DHCP operation in progress
280
+  if (! dhcp_in_progress) {  
281
+    // DHCP lease active or no IP address ---> DHCP allowed (i.e. no static IP)
282
+    if (dhcp_active || ip_eq(config_ip.ip, "\0\0\0\0")) {
283
+      // start new DHCP action
284
+      dhcp_start();
285
+    }
286
+  }
287
+}
288
+
289
+/**
290
+ * @brief process a received DHCP offer
291
+ * @param[in] addr assigned IP address
292
+ * @param[in] mask assigned subnet mask
293
+ * @param[in] gateway assigned gateway IP address
294
+ * @param[in] time lease time in seconds
295
+ * @param[in] DHCP server IP address
296
+ */
297
+static void dhcp_offer(unsigned char addr[4], unsigned char mask[4],
298
+                       unsigned char gateway[4], unsigned long time,
299
+                       unsigned char server[4])
300
+{
301
+  // use this IP address, mask and gateway
302
+  ip_cpy(config_ip.ip, addr);
303
+  ip_cpy(config_ip.mask, mask);
304
+  ip_cpy(config_ip.gw, gateway);
305
+
306
+  // store DHCP server and lease time
307
+  dhcp_active = 1;
308
+  ip_cpy(dhcp_server, server);
309
+  dhcp_lease_time = time;
310
+  dhcp_lease_rest = dhcp_lease_rest_min; // schedule DHCP request
311
+
312
+  // DHCP action finished
313
+  dhcp_in_progress = 0;
314
+  dhcp_retries = 0;
315
+  dhcp_retry_secs = 0;
316
+}
317
+
318
+/**
319
+ * @brief process a received DHCP ack
320
+ * @param[in] addr assigned IP address
321
+ * @param[in] mask assigned subnet mask
322
+ * @param[in] gateway assigned gateway IP address
323
+ * @param[in] time lease time in seconds
324
+ * @param[in] DHCP server IP address
325
+ */
326
+static void dhcp_ack(unsigned char addr[4], unsigned char mask[4],
327
+                     unsigned char gateway[4], unsigned long time,
328
+                     unsigned char server[4])
329
+{
330
+  // check if IP address, mask and gateway match
331
+  if (! ip_eq(config_ip.ip, addr) ||
332
+      ! ip_eq(config_ip.mask, mask) ||
333
+      ! ip_eq(config_ip.gw, gateway))
334
+  {
335
+    // mismatch ---> something is wrong, abort
336
+    dhcp_abort();
337
+    return;
338
+  }
339
+
340
+  // store DHCP server and lease time
341
+  dhcp_active = 1;
342
+  ip_cpy(dhcp_server, server);
343
+  dhcp_lease_time = time;
344
+  dhcp_lease_rest = time;
345
+
346
+  // DHCP action finished
347
+  dhcp_in_progress = 0;
348
+  dhcp_retries = 0;
349
+  dhcp_retry_secs = 0;
350
+
351
+  // forget x_id
352
+  dhcp_have_x_id = 0;
353
+  dhcp_x_id = 0;
354
+  dhcp_x_id_secs = 0;
355
+}
356
+
357
+/**
358
+ * @brief process a received DHCP packet
359
+ * @param[in] ptr pointer to data of packet
360
+ * @param[in] sz size of packet
361
+ */
362
+void dhcp_recv(void *ptr, unsigned int sz)
363
+{
364
+  struct dhcp_packet *p_dhcp_pack;
365
+  unsigned char *opt_ptr, tag, len;
366
+  unsigned short opt_len;
367
+  unsigned char type;
368
+  unsigned char have_mask, mask[4], have_gateway, gateway[4];
369
+  unsigned char have_time, have_server, server[4];
370
+  unsigned long time;
371
+
372
+  // packet too short
373
+  if (sz < sizeof(struct dhcp_packet))
374
+    return;
375
+
376
+  p_dhcp_pack = ptr;
377
+
378
+  // we are DHCP client
379
+  // - source port must be 67
380
+  // - destination port must be 68
381
+  if (p_dhcp_pack->udp_hdr.src_port != htons(67) ||
382
+      p_dhcp_pack->udp_hdr.dest_port != htons(68))
383
+    return;
384
+  // check DHCP fields
385
+  if (p_dhcp_pack->dhcp_hdr.op != 2 ||
386
+      p_dhcp_pack->dhcp_hdr.h_type != 1 ||
387
+      p_dhcp_pack->dhcp_hdr.h_len != 6 ||
388
+      p_dhcp_pack->dhcp_hdr.h_ops != 0 ||
389
+      p_dhcp_pack->dhcp_hdr.m_cookie != htonl(0x63825363))
390
+    return;
391
+
392
+  // check x_id
393
+  if (! dhcp_have_x_id || ntohl(p_dhcp_pack->dhcp_hdr.x_id) != dhcp_x_id)
394
+    return;
395
+
396
+  // get options
397
+  opt_ptr = ptr + sizeof(struct dhcp_packet);
398
+  opt_len = sz - sizeof(struct dhcp_packet);
399
+
400
+  // parse options
401
+  type = 0;
402
+  have_mask = 0;
403
+  have_gateway = 0;
404
+  time = 0;
405
+  have_time = 0;
406
+  have_server = 0;
407
+  while (opt_len > 2) {
408
+
409
+    // get tag and length
410
+    tag = *opt_ptr++;
411
+    len = *opt_ptr++;
412
+    if (opt_len < 2 + len)
413
+      break;
414
+
415
+    // get option
416
+    switch (tag) {
417
+      // type
418
+      case 0x35:
419
+        if (len >= 1)
420
+          type = opt_ptr[0];
421
+        break;
422
+      // subnet mask
423
+      case 0x01:
424
+        if (len >= 4)
425
+          memcpy(mask, opt_ptr, 4);
426
+          have_mask = 1;
427
+        break;
428
+      // gateway
429
+      case 0x03:
430
+        if (len >= 4) {
431
+          memcpy(gateway, opt_ptr, 4);
432
+          have_gateway = 1;
433
+        }
434
+        break;
435
+      // lease time
436
+      case 0x33:
437
+        if (len >= 4) {
438
+          time = ntohl(*(unsigned long *)opt_ptr);
439
+          have_time = 1;
440
+        }
441
+        break;
442
+      // DHCP server
443
+      case 0x36:
444
+        if (len >= 4) {
445
+          memcpy(server, opt_ptr, 4);
446
+          have_server = 1;
447
+        }
448
+        break;
449
+    }
450
+
451
+    // skip option
452
+    opt_ptr += len;
453
+    opt_len -= len;
454
+  }
455
+
456
+  // fill in server from source address if not present
457
+  if (! have_server) {
458
+    ip_cpy(server, p_dhcp_pack->ip_hdr.src);
459
+    have_server = 1;
460
+  }
461
+
462
+  // process DHCP response
463
+  switch (type) {
464
+    // DHCP offer
465
+    case 2:
466
+      if (have_mask && have_gateway && have_time && have_server)
467
+        dhcp_offer(p_dhcp_pack->dhcp_hdr.yi_addr, mask, gateway, time, server);
468
+      break;
469
+    // DHCP ack
470
+    case 5:
471
+      if (have_mask && have_gateway && have_time && have_server)
472
+        dhcp_ack(p_dhcp_pack->dhcp_hdr.yi_addr, mask, gateway, time, server);
473
+      break;
474
+  }
475
+}
476
+
... ...
@@ -0,0 +1,48 @@
1
+#ifndef DHCP_H
2
+#define DHCP_H
3
+
4
+#include "ethernet.h"
5
+#include "ip.h"
6
+#include "udp.h"
7
+
8
+/// header of DHCP packet
9
+struct dhcp_header
10
+{
11
+  unsigned char op;
12
+  unsigned char h_type;
13
+  unsigned char h_len;
14
+  unsigned char h_ops;
15
+  unsigned long x_id;
16
+  unsigned short secs;
17
+  unsigned short flags;
18
+  unsigned char ci_addr[4];
19
+  unsigned char yi_addr[4];
20
+  unsigned char si_addr[4];
21
+  unsigned char gi_addr[4];
22
+  unsigned char ch_addr[16];
23
+  unsigned char s_name[64];
24
+  unsigned char file[128];
25
+  unsigned long m_cookie;
26
+} __attribute__((packed));
27
+
28
+/// DHCP packet
29
+struct dhcp_packet
30
+{
31
+  struct ethernet_header eth_hdr;
32
+  struct ip_header ip_hdr;
33
+  struct udp_header udp_hdr;
34
+  struct dhcp_header dhcp_hdr;
35
+} __attribute__((packed));
36
+
37
+/// tick procedure - call every 200ms
38
+void dhcp_tick200(void);
39
+
40
+/**
41
+ * @brief process a received DHCP packet
42
+ * @param[in] ptr pointer to data of packet
43
+ * @param[in] sz size of packet
44
+ */
45
+void dhcp_recv(void *ptr, unsigned int sz);
46
+
47
+#endif // #ifdef DHCP_H
48
+
... ...
@@ -1,6 +1,7 @@
1 1
 #include "config.h"
2 2
 #include "checksum.h"
3 3
 #include "ethernet.h"
4
+#include "dhcp.h"
4 5
 #include "ip.h"
5 6
 #include "macros.h"
6 7
 #include "nethelp.h"
... ...
@@ -102,6 +103,10 @@ void udp_recv(void *ptr, unsigned int sz)
102 103
     case 7:
103 104
       udp_echo_recv(ptr, sz);
104 105
       break;
106
+    // DHCP
107
+    case 68:
108
+      dhcp_recv(ptr, sz);
109
+      break;
105 110
   }
106 111
 }
107 112
 
... ...
@@ -111,7 +116,7 @@ void udp_recv(void *ptr, unsigned int sz)
111 116
  * @param[in] sz size of packet
112 117
  *
113 118
  * ptr must point to a udp_packet
114
- * with ip_hdr.proto and udp_hdr.src_port, udp_hdr.dest_port, ip_hdr.dest
119
+ * with ip_hdr.proto ip_hdr.dest, udp_hdr.src_port and udp_hdr.dest_port
115 120
  * already initialized
116 121
  */
117 122
 void udp_send(void *ptr, unsigned int sz)
... ...
@@ -37,7 +37,7 @@ void udp_recv(void *ptr, unsigned int sz);
37 37
  * @param[in] sz size of packet
38 38
  *
39 39
  * ptr must point to a udp_packet
40
- * with ip_hdr.proto and udp_hdr.src_port, udp_hdr.dest_port, ip_hdr.dest
40
+ * with ip_hdr.proto ip_hdr.dest, udp_hdr.src_port and udp_hdr.dest_port
41 41
  * already initialized
42 42
  */
43 43
 void udp_send(void *ptr, unsigned int sz);
44 44