c11 safe string handling support
[vpp.git] / src / vnet / dhcp / client.c
1 /*
2  * Copyright (c) 2015 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 #include <vlib/vlib.h>
16 #include <vnet/dhcp/client.h>
17 #include <vnet/dhcp/dhcp_proxy.h>
18 #include <vnet/fib/fib_table.h>
19
20 dhcp_client_main_t dhcp_client_main;
21 static u8 *format_dhcp_client_state (u8 * s, va_list * va);
22 static vlib_node_registration_t dhcp_client_process_node;
23
24 #define foreach_dhcp_sent_packet_stat           \
25 _(DISCOVER, "DHCP discover packets sent")       \
26 _(OFFER, "DHCP offer packets sent")             \
27 _(REQUEST, "DHCP request packets sent")         \
28 _(ACK, "DHCP ack packets sent")
29
30 #define foreach_dhcp_error_counter                                      \
31 _(NOT_FOR_US, "DHCP packets for other hosts, dropped")                  \
32 _(NAK, "DHCP nak packets received")                                     \
33 _(NON_OFFER_DISCOVER, "DHCP non-offer packets in discover state")       \
34 _(ODDBALL, "DHCP non-ack, non-offer packets received")                  \
35 _(BOUND, "DHCP bind success")
36
37 typedef enum
38 {
39 #define _(sym,str) DHCP_STAT_##sym,
40   foreach_dhcp_sent_packet_stat foreach_dhcp_error_counter
41 #undef _
42     DHCP_STAT_UNKNOWN,
43   DHCP_STAT_N_STAT,
44 } sample_error_t;
45
46 static char *dhcp_client_process_stat_strings[] = {
47 #define _(sym,string) string,
48   foreach_dhcp_sent_packet_stat foreach_dhcp_error_counter
49 #undef _
50     "DHCP unknown packets sent",
51 };
52
53
54 static void
55 dhcp_client_acquire_address (dhcp_client_main_t * dcm, dhcp_client_t * c)
56 {
57   /*
58    * Install any/all info gleaned from dhcp, right here
59    */
60   ip4_add_del_interface_address (dcm->vlib_main, c->sw_if_index,
61                                  (void *) &c->leased_address,
62                                  c->subnet_mask_width, 0 /*is_del */ );
63 }
64
65 static void
66 dhcp_client_release_address (dhcp_client_main_t * dcm, dhcp_client_t * c)
67 {
68   /*
69    * Remove any/all info gleaned from dhcp, right here. Caller(s)
70    * have not wiped out the info yet.
71    */
72
73   ip4_add_del_interface_address (dcm->vlib_main, c->sw_if_index,
74                                  (void *) &c->leased_address,
75                                  c->subnet_mask_width, 1 /*is_del */ );
76 }
77
78 static void
79 set_l2_rewrite (dhcp_client_main_t * dcm, dhcp_client_t * c)
80 {
81   /* Acquire the L2 rewrite string for the indicated sw_if_index */
82   c->l2_rewrite = vnet_build_rewrite_for_sw_interface (dcm->vnet_main,
83                                                        c->sw_if_index,
84                                                        VNET_LINK_IP4,
85                                                        0 /* broadcast */ );
86 }
87
88 void vl_api_rpc_call_main_thread (void *fp, u8 * data, u32 data_length);
89
90 static void
91 dhcp_client_proc_callback (uword * client_index)
92 {
93   vlib_main_t *vm = vlib_get_main ();
94   ASSERT (vlib_get_thread_index () == 0);
95   vlib_process_signal_event (vm, dhcp_client_process_node.index,
96                              EVENT_DHCP_CLIENT_WAKEUP, *client_index);
97 }
98
99 static void
100 dhcp_client_addr_callback (dhcp_client_t * c)
101 {
102   dhcp_client_main_t *dcm = &dhcp_client_main;
103
104   /* disable the feature */
105   vnet_feature_enable_disable ("ip4-unicast",
106                                "ip4-dhcp-client-detect",
107                                c->sw_if_index, 0 /* disable */ , 0, 0);
108   c->client_detect_feature_enabled = 0;
109
110   /* if renewing the lease, the address and route have already been added */
111   if (c->state == DHCP_BOUND)
112     return;
113
114   /* add the address to the interface */
115   dhcp_client_acquire_address (dcm, c);
116
117   /*
118    * Configure default IP route:
119    */
120   if (c->router_address.as_u32)
121     {
122       fib_prefix_t all_0s = {
123         .fp_len = 0,
124         .fp_addr.ip4.as_u32 = 0x0,
125         .fp_proto = FIB_PROTOCOL_IP4,
126       };
127       ip46_address_t nh = {
128         .ip4 = c->router_address,
129       };
130
131       /* *INDENT-OFF* */
132       fib_table_entry_path_add (
133         fib_table_get_index_for_sw_if_index (
134           FIB_PROTOCOL_IP4,
135           c->sw_if_index),
136           &all_0s,
137           FIB_SOURCE_DHCP,
138           FIB_ENTRY_FLAG_NONE,
139           DPO_PROTO_IP4,
140           &nh, c->sw_if_index,
141           ~0, 1, NULL,  // no label stack
142           FIB_ROUTE_PATH_FLAG_NONE);
143       /* *INDENT-ON* */
144     }
145
146   /*
147    * Call the user's event callback to report DHCP information
148    */
149   if (c->event_callback)
150     c->event_callback (c->client_index, c);
151 }
152
153 /*
154  * dhcp_client_for_us - server-to-client callback.
155  * Called from proxy_node.c:dhcp_proxy_to_client_input().
156  * This function first decides that the packet in question is
157  * actually for the dhcp client code in case we're also acting as
158  * a dhcp proxy. Ay caramba, what a folly!
159  */
160 int
161 dhcp_client_for_us (u32 bi, vlib_buffer_t * b,
162                     ip4_header_t * ip,
163                     udp_header_t * udp, dhcp_header_t * dhcp)
164 {
165   dhcp_client_main_t *dcm = &dhcp_client_main;
166   vlib_main_t *vm = dcm->vlib_main;
167   dhcp_client_t *c;
168   uword *p;
169   f64 now = vlib_time_now (dcm->vlib_main);
170   u8 dhcp_message_type = 0;
171   dhcp_option_t *o;
172
173   /*
174    * Doing dhcp client on this interface?
175    * Presumably we will always receive dhcp clnt for-us pkts on
176    * the interface that's asking for an address.
177    */
178   p = hash_get (dcm->client_by_sw_if_index,
179                 vnet_buffer (b)->sw_if_index[VLIB_RX]);
180   if (p == 0)
181     return 0;                   /* no */
182
183   c = pool_elt_at_index (dcm->clients, p[0]);
184
185   /* Mixing dhcp relay and dhcp proxy? DGMS... */
186   if (c->state == DHCP_BOUND && c->retry_count == 0)
187     return 0;
188
189   /* Packet not for us? Turf it... */
190   if (memcmp (dhcp->client_hardware_address, c->client_hardware_address,
191               sizeof (c->client_hardware_address)))
192     {
193       vlib_node_increment_counter (vm, dhcp_client_process_node.index,
194                                    DHCP_STAT_NOT_FOR_US, 1);
195       return 0;
196     }
197
198   /* parse through the packet, learn what we can */
199   if (dhcp->your_ip_address.as_u32)
200     c->leased_address.as_u32 = dhcp->your_ip_address.as_u32;
201
202   c->dhcp_server.as_u32 = dhcp->server_ip_address.as_u32;
203
204   o = (dhcp_option_t *) dhcp->options;
205
206   while (o->option != 0xFF /* end of options */  &&
207          (u8 *) o < (b->data + b->current_data + b->current_length))
208     {
209       switch (o->option)
210         {
211         case 53:                /* dhcp message type */
212           dhcp_message_type = o->data[0];
213           break;
214
215         case 51:                /* lease time */
216           {
217             u32 lease_time_in_seconds =
218               clib_host_to_net_u32 (o->data_as_u32[0]);
219             // for debug: lease_time_in_seconds = 20; /*$$$$*/
220             c->lease_expires = now + (f64) lease_time_in_seconds;
221             c->lease_lifetime = lease_time_in_seconds;
222             /* Set a sensible default, in case we don't get opt 58 */
223             c->lease_renewal_interval = lease_time_in_seconds / 2;
224           }
225           break;
226
227         case 58:                /* lease renew time in seconds */
228           {
229             u32 lease_renew_time_in_seconds =
230               clib_host_to_net_u32 (o->data_as_u32[0]);
231             c->lease_renewal_interval = lease_renew_time_in_seconds;
232           }
233           break;
234
235         case 54:                /* dhcp server address */
236           c->dhcp_server.as_u32 = o->data_as_u32[0];
237           break;
238
239         case 1:         /* subnet mask */
240           {
241             u32 subnet_mask = clib_host_to_net_u32 (o->data_as_u32[0]);
242             c->subnet_mask_width = count_set_bits (subnet_mask);
243           }
244           break;
245         case 3:         /* router address */
246           {
247             u32 router_address = o->data_as_u32[0];
248             c->router_address.as_u32 = router_address;
249           }
250           break;
251
252         case 12:                /* hostname */
253           {
254             /* Replace the existing hostname if necessary */
255             vec_free (c->hostname);
256             vec_validate (c->hostname, o->length - 1);
257             clib_memcpy (c->hostname, o->data, o->length);
258           }
259           break;
260
261           /* $$$$ Your message in this space, parse more options */
262         default:
263           break;
264         }
265
266       o = (dhcp_option_t *) (((uword) o) + (o->length + 2));
267     }
268
269   switch (c->state)
270     {
271     case DHCP_DISCOVER:
272       if (dhcp_message_type != DHCP_PACKET_OFFER)
273         {
274           vlib_node_increment_counter (vm, dhcp_client_process_node.index,
275                                        DHCP_STAT_NON_OFFER_DISCOVER, 1);
276           c->next_transmit = now + 5.0;
277           break;
278         }
279
280       /* Received an offer, go send a request */
281       c->state = DHCP_REQUEST;
282       c->retry_count = 0;
283       c->next_transmit = 0;     /* send right now... */
284       /* Poke the client process, which will send the request */
285       uword client_id = c - dcm->clients;
286       vl_api_rpc_call_main_thread (dhcp_client_proc_callback,
287                                    (u8 *) & client_id, sizeof (uword));
288       break;
289
290     case DHCP_BOUND:
291     case DHCP_REQUEST:
292       if (dhcp_message_type == DHCP_PACKET_NAK)
293         {
294           vlib_node_increment_counter (vm, dhcp_client_process_node.index,
295                                        DHCP_STAT_NAK, 1);
296           /* Probably never happens in bound state, but anyhow... */
297           if (c->state == DHCP_BOUND)
298             {
299               ip4_add_del_interface_address (dcm->vlib_main, c->sw_if_index,
300                                              (void *) &c->leased_address,
301                                              c->subnet_mask_width,
302                                              1 /*is_del */ );
303               vnet_feature_enable_disable ("ip4-unicast",
304                                            "ip4-dhcp-client-detect",
305                                            c->sw_if_index, 1 /* enable */ ,
306                                            0, 0);
307               c->client_detect_feature_enabled = 1;
308             }
309           /* Wipe out any memory of the address we had... */
310           c->state = DHCP_DISCOVER;
311           c->next_transmit = now;
312           c->retry_count = 0;
313           c->leased_address.as_u32 = 0;
314           c->subnet_mask_width = 0;
315           c->router_address.as_u32 = 0;
316           c->lease_renewal_interval = 0;
317           c->dhcp_server.as_u32 = 0;
318           break;
319         }
320
321       if (dhcp_message_type != DHCP_PACKET_ACK &&
322           dhcp_message_type != DHCP_PACKET_OFFER)
323         {
324           vlib_node_increment_counter (vm, dhcp_client_process_node.index,
325                                        DHCP_STAT_NON_OFFER_DISCOVER, 1);
326           clib_warning ("sw_if_index %d state %U message type %d",
327                         c->sw_if_index, format_dhcp_client_state,
328                         c->state, dhcp_message_type);
329           c->next_transmit = now + 5.0;
330           break;
331         }
332       /* OK, we own the address (etc), add to the routing table(s) */
333       vl_api_rpc_call_main_thread (dhcp_client_addr_callback,
334                                    (u8 *) c, sizeof (*c));
335
336       c->state = DHCP_BOUND;
337       c->retry_count = 0;
338       c->next_transmit = now + (f64) c->lease_renewal_interval;
339       c->lease_expires = now + (f64) c->lease_lifetime;
340       vlib_node_increment_counter (vm, dhcp_client_process_node.index,
341                                    DHCP_STAT_BOUND, 1);
342       break;
343
344     default:
345       clib_warning ("client %d bogus state %d", c - dcm->clients, c->state);
346       break;
347     }
348
349   /* drop the pkt, return 1 */
350   vlib_buffer_free (vm, &bi, 1);
351   return 1;
352 }
353
354 static void
355 send_dhcp_pkt (dhcp_client_main_t * dcm, dhcp_client_t * c,
356                dhcp_packet_type_t type, int is_broadcast)
357 {
358   vlib_main_t *vm = dcm->vlib_main;
359   vnet_main_t *vnm = dcm->vnet_main;
360   vnet_hw_interface_t *hw = vnet_get_sup_hw_interface (vnm, c->sw_if_index);
361   vnet_sw_interface_t *sup_sw
362     = vnet_get_sup_sw_interface (vnm, c->sw_if_index);
363   vnet_sw_interface_t *sw = vnet_get_sw_interface (vnm, c->sw_if_index);
364   vlib_buffer_t *b;
365   u32 bi;
366   ip4_header_t *ip;
367   udp_header_t *udp;
368   dhcp_header_t *dhcp;
369   u32 *to_next;
370   vlib_frame_t *f;
371   dhcp_option_t *o;
372   u16 udp_length, ip_length;
373   u32 counter_index;
374
375   /* Interface(s) down? */
376   if ((hw->flags & VNET_HW_INTERFACE_FLAG_LINK_UP) == 0)
377     return;
378   if ((sup_sw->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP) == 0)
379     return;
380   if ((sw->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP) == 0)
381     return;
382
383   if (vlib_buffer_alloc (vm, &bi, 1) != 1)
384     {
385       clib_warning ("buffer allocation failure");
386       c->next_transmit = 0;
387       return;
388     }
389
390   /* Build a dhcpv4 pkt from whole cloth */
391   b = vlib_get_buffer (vm, bi);
392
393   ASSERT (b->current_data == 0);
394
395   vnet_buffer (b)->sw_if_index[VLIB_RX] = c->sw_if_index;
396   if (is_broadcast)
397     {
398       f = vlib_get_frame_to_node (vm, hw->output_node_index);
399       vnet_buffer (b)->sw_if_index[VLIB_TX] = c->sw_if_index;
400       clib_memcpy (b->data, c->l2_rewrite, vec_len (c->l2_rewrite));
401       ip = (void *)
402         (((u8 *) vlib_buffer_get_current (b)) + vec_len (c->l2_rewrite));
403     }
404   else
405     {
406       f = vlib_get_frame_to_node (vm, ip4_lookup_node.index);
407       vnet_buffer (b)->sw_if_index[VLIB_TX] = ~0;       /* use interface VRF */
408       ip = vlib_buffer_get_current (b);
409     }
410
411   /* Enqueue the packet right now */
412   to_next = vlib_frame_vector_args (f);
413   to_next[0] = bi;
414   f->n_vectors = 1;
415
416   if (is_broadcast)
417     vlib_put_frame_to_node (vm, hw->output_node_index, f);
418   else
419     vlib_put_frame_to_node (vm, ip4_lookup_node.index, f);
420
421   udp = (udp_header_t *) (ip + 1);
422   dhcp = (dhcp_header_t *) (udp + 1);
423
424   /* $$$ optimize, maybe */
425   clib_memset (ip, 0, sizeof (*ip) + sizeof (*udp) + sizeof (*dhcp));
426
427   ip->ip_version_and_header_length = 0x45;
428   ip->ttl = 128;
429   ip->protocol = IP_PROTOCOL_UDP;
430
431   if (is_broadcast)
432     {
433       /* src = 0.0.0.0, dst = 255.255.255.255 */
434       ip->dst_address.as_u32 = ~0;
435     }
436   else
437     {
438       /* Renewing an active lease, plain old ip4 src/dst */
439       ip->src_address.as_u32 = c->leased_address.as_u32;
440       ip->dst_address.as_u32 = c->dhcp_server.as_u32;
441     }
442
443   udp->src_port = clib_host_to_net_u16 (UDP_DST_PORT_dhcp_to_client);
444   udp->dst_port = clib_host_to_net_u16 (UDP_DST_PORT_dhcp_to_server);
445
446   /* Send the interface MAC address */
447   clib_memcpy (dhcp->client_hardware_address, c->l2_rewrite + 6, 6);
448
449   /* And remember it for rx-packet-for-us checking */
450   clib_memcpy (c->client_hardware_address, dhcp->client_hardware_address,
451                sizeof (c->client_hardware_address));
452
453   /* Lease renewal, set up client_ip_address */
454   if (is_broadcast == 0)
455     dhcp->client_ip_address.as_u32 = c->leased_address.as_u32;
456
457   dhcp->opcode = 1;             /* request, all we send */
458   dhcp->hardware_type = 1;      /* ethernet */
459   dhcp->hardware_address_length = 6;
460   dhcp->transaction_identifier = c->transaction_id;
461   dhcp->flags =
462     clib_host_to_net_u16 (is_broadcast && c->set_broadcast_flag ?
463                           DHCP_FLAG_BROADCAST : 0);
464   dhcp->magic_cookie.as_u32 = DHCP_MAGIC;
465
466   o = (dhcp_option_t *) dhcp->options;
467
468   /* Send option 53, the DHCP message type */
469   o->option = DHCP_PACKET_OPTION_MSG_TYPE;
470   o->length = 1;
471   o->data[0] = type;
472   o = (dhcp_option_t *) (((uword) o) + (o->length + 2));
473
474   /* Send option 57, max msg length */
475   if (0 /* not needed, apparently */ )
476     {
477       o->option = 57;
478       o->length = 2;
479       {
480         u16 *o2 = (u16 *) o->data;
481         *o2 = clib_host_to_net_u16 (1152);
482         o = (dhcp_option_t *) (((uword) o) + (o->length + 2));
483       }
484     }
485
486   /*
487    * If server ip address is available with non-zero value,
488    * option 54 (DHCP Server Identifier) is sent.
489    */
490   if (c->dhcp_server.as_u32)
491     {
492       o->option = 54;
493       o->length = 4;
494       clib_memcpy (o->data, &c->dhcp_server.as_u32, 4);
495       o = (dhcp_option_t *) (((uword) o) + (o->length + 2));
496     }
497
498   /* send option 50, requested IP address */
499   if (c->leased_address.as_u32)
500     {
501       o->option = 50;
502       o->length = 4;
503       clib_memcpy (o->data, &c->leased_address.as_u32, 4);
504       o = (dhcp_option_t *) (((uword) o) + (o->length + 2));
505     }
506
507   /* send option 12, host name */
508   if (vec_len (c->hostname))
509     {
510       o->option = 12;
511       o->length = vec_len (c->hostname);
512       clib_memcpy (o->data, c->hostname, vec_len (c->hostname));
513       o = (dhcp_option_t *) (((uword) o) + (o->length + 2));
514     }
515
516   /* send option 61, client_id */
517   if (vec_len (c->client_identifier))
518     {
519       o->option = 61;
520       o->length = vec_len (c->client_identifier);
521       clib_memcpy (o->data, c->client_identifier,
522                    vec_len (c->client_identifier));
523       o = (dhcp_option_t *) (((uword) o) + (o->length + 2));
524     }
525
526   /* $$ maybe send the client s/w version if anyone cares */
527
528   /*
529    * send option 55, parameter request list
530    * The current list - see below, matches the Linux dhcp client's list
531    * Any specific dhcp server config and/or dhcp server may or may
532    * not yield specific options.
533    */
534   o->option = 55;
535   o->length = vec_len (c->option_55_data);
536   clib_memcpy (o->data, c->option_55_data, vec_len (c->option_55_data));
537   o = (dhcp_option_t *) (((uword) o) + (o->length + 2));
538
539   /* End of list */
540   o->option = 0xff;
541   o->length = 0;
542   o++;
543
544   b->current_length = ((u8 *) o) - b->data;
545
546   /* fix ip length, checksum and udp length */
547   ip_length = vlib_buffer_length_in_chain (vm, b);
548   if (is_broadcast)
549     ip_length -= vec_len (c->l2_rewrite);
550
551   ip->length = clib_host_to_net_u16 (ip_length);
552   ip->checksum = ip4_header_checksum (ip);
553
554   udp_length = ip_length - (sizeof (*ip));
555   udp->length = clib_host_to_net_u16 (udp_length);
556
557   switch (type)
558     {
559 #define _(a,b) case DHCP_PACKET_##a: {counter_index = DHCP_STAT_##a; break;}
560       foreach_dhcp_sent_packet_stat
561 #undef _
562     default:
563       counter_index = DHCP_STAT_UNKNOWN;
564       break;
565     }
566
567   vlib_node_increment_counter (vm, dhcp_client_process_node.index,
568                                counter_index, 1);
569 }
570
571 static int
572 dhcp_discover_state (dhcp_client_main_t * dcm, dhcp_client_t * c, f64 now)
573 {
574   /*
575    * State machine "DISCOVER" state. Send a dhcp discover packet,
576    * eventually back off the retry rate.
577    */
578
579   if (c->client_detect_feature_enabled == 0)
580     {
581       vnet_feature_enable_disable ("ip4-unicast",
582                                    "ip4-dhcp-client-detect",
583                                    c->sw_if_index, 1 /* enable */ , 0, 0);
584       c->client_detect_feature_enabled = 1;
585     }
586
587   send_dhcp_pkt (dcm, c, DHCP_PACKET_DISCOVER, 1 /* is_broadcast */ );
588
589   c->retry_count++;
590   if (c->retry_count > 10)
591     c->next_transmit = now + 5.0;
592   else
593     c->next_transmit = now + 1.0;
594   return 0;
595 }
596
597 static int
598 dhcp_request_state (dhcp_client_main_t * dcm, dhcp_client_t * c, f64 now)
599 {
600   /*
601    * State machine "REQUEST" state. Send a dhcp request packet,
602    * eventually drop back to the discover state.
603    */
604   send_dhcp_pkt (dcm, c, DHCP_PACKET_REQUEST, 1 /* is_broadcast */ );
605
606   c->retry_count++;
607   if (c->retry_count > 7 /* lucky you */ )
608     {
609       c->state = DHCP_DISCOVER;
610       c->next_transmit = now;
611       c->retry_count = 0;
612       return 1;
613     }
614   c->next_transmit = now + 1.0;
615   return 0;
616 }
617
618 static int
619 dhcp_bound_state (dhcp_client_main_t * dcm, dhcp_client_t * c, f64 now)
620 {
621   /*
622    * State machine "BOUND" state. Send a dhcp request packet to renew
623    * the lease.
624    * Eventually, when the lease expires, forget the dhcp data
625    * and go back to the stone age.
626    */
627
628   /*
629    * We disable the client detect feature when we bind a
630    * DHCP address. Turn it back on again on first renew attempt.
631    * Otherwise, if the DHCP server replies we'll never see it.
632    */
633   if (c->client_detect_feature_enabled == 0)
634     {
635       vnet_feature_enable_disable ("ip4-unicast",
636                                    "ip4-dhcp-client-detect",
637                                    c->sw_if_index, 1 /* enable */ , 0, 0);
638       c->client_detect_feature_enabled = 1;
639     }
640
641   send_dhcp_pkt (dcm, c, DHCP_PACKET_REQUEST, 0 /* is_broadcast */ );
642
643   c->retry_count++;
644   if (c->retry_count > 10)
645     c->next_transmit = now + 5.0;
646   else
647     c->next_transmit = now + 1.0;
648
649   if (now > c->lease_expires)
650     {
651       /* Remove the default route */
652       if (c->router_address.as_u32)
653         {
654           fib_prefix_t all_0s = {
655             .fp_len = 0,
656             .fp_addr.ip4.as_u32 = 0x0,
657             .fp_proto = FIB_PROTOCOL_IP4,
658           };
659           ip46_address_t nh = {
660             .ip4 = c->router_address,
661           };
662
663           fib_table_entry_path_remove (fib_table_get_index_for_sw_if_index
664                                        (FIB_PROTOCOL_IP4, c->sw_if_index),
665                                        &all_0s, FIB_SOURCE_DHCP,
666                                        DPO_PROTO_IP4, &nh, c->sw_if_index, ~0,
667                                        1, FIB_ROUTE_PATH_FLAG_NONE);
668         }
669       /* Remove the interface address */
670       dhcp_client_release_address (dcm, c);
671       c->state = DHCP_DISCOVER;
672       c->next_transmit = now;
673       c->retry_count = 0;
674       /* Wipe out any memory of the address we had... */
675       c->leased_address.as_u32 = 0;
676       c->subnet_mask_width = 0;
677       c->router_address.as_u32 = 0;
678       c->lease_renewal_interval = 0;
679       c->dhcp_server.as_u32 = 0;
680       return 1;
681     }
682   return 0;
683 }
684
685 static f64
686 dhcp_client_sm (f64 now, f64 timeout, uword pool_index)
687 {
688   dhcp_client_main_t *dcm = &dhcp_client_main;
689   dhcp_client_t *c;
690
691   /* deleted, pooched, yadda yadda yadda */
692   if (pool_is_free_index (dcm->clients, pool_index))
693     return timeout;
694
695   c = pool_elt_at_index (dcm->clients, pool_index);
696
697   /* Time for us to do something with this client? */
698   if (now < c->next_transmit)
699     return timeout;
700
701 again:
702   switch (c->state)
703     {
704     case DHCP_DISCOVER: /* send a discover */
705       if (dhcp_discover_state (dcm, c, now))
706         goto again;
707       break;
708
709     case DHCP_REQUEST:          /* send a request */
710       if (dhcp_request_state (dcm, c, now))
711         goto again;
712       break;
713
714     case DHCP_BOUND:            /* bound, renew needed? */
715       if (dhcp_bound_state (dcm, c, now))
716         goto again;
717       break;
718
719     default:
720       clib_warning ("dhcp client %d bogus state %d",
721                     c - dcm->clients, c->state);
722       break;
723     }
724
725   if (c->next_transmit < now + timeout)
726     return c->next_transmit - now;
727
728   return timeout;
729 }
730
731 static uword
732 dhcp_client_process (vlib_main_t * vm,
733                      vlib_node_runtime_t * rt, vlib_frame_t * f)
734 {
735   f64 timeout = 100.0;
736   f64 now;
737   uword event_type;
738   uword *event_data = 0;
739   dhcp_client_main_t *dcm = &dhcp_client_main;
740   dhcp_client_t *c;
741   int i;
742
743   while (1)
744     {
745       vlib_process_wait_for_event_or_clock (vm, timeout);
746
747       event_type = vlib_process_get_events (vm, &event_data);
748
749       now = vlib_time_now (vm);
750
751       switch (event_type)
752         {
753         case EVENT_DHCP_CLIENT_WAKEUP:
754           for (i = 0; i < vec_len (event_data); i++)
755             timeout = dhcp_client_sm (now, timeout, event_data[i]);
756           break;
757
758         case ~0:
759           /* *INDENT-OFF* */
760           pool_foreach (c, dcm->clients,
761           ({
762             timeout = dhcp_client_sm (now, timeout,
763                                       (uword) (c - dcm->clients));
764           }));
765           /* *INDENT-ON* */
766           if (pool_elts (dcm->clients) == 0)
767             timeout = 100.0;
768           break;
769         }
770
771       vec_reset_length (event_data);
772     }
773
774   /* NOTREACHED */
775   return 0;
776 }
777
778 /* *INDENT-OFF* */
779 VLIB_REGISTER_NODE (dhcp_client_process_node,static) = {
780     .function = dhcp_client_process,
781     .type = VLIB_NODE_TYPE_PROCESS,
782     .name = "dhcp-client-process",
783     .process_log2_n_stack_bytes = 16,
784     .n_errors = ARRAY_LEN(dhcp_client_process_stat_strings),
785     .error_strings = dhcp_client_process_stat_strings,
786 };
787 /* *INDENT-ON* */
788
789 static u8 *
790 format_dhcp_client_state (u8 * s, va_list * va)
791 {
792   dhcp_client_state_t state = va_arg (*va, dhcp_client_state_t);
793   char *str = "BOGUS!";
794
795   switch (state)
796     {
797 #define _(a)                                    \
798     case a:                                     \
799       str = #a;                                 \
800         break;
801       foreach_dhcp_client_state;
802 #undef _
803     default:
804       break;
805     }
806
807   s = format (s, "%s", str);
808   return s;
809 }
810
811 static u8 *
812 format_dhcp_client (u8 * s, va_list * va)
813 {
814   dhcp_client_main_t *dcm = va_arg (*va, dhcp_client_main_t *);
815   dhcp_client_t *c = va_arg (*va, dhcp_client_t *);
816   int verbose = va_arg (*va, int);
817
818   s = format (s, "[%d] %U state %U ", c - dcm->clients,
819               format_vnet_sw_if_index_name, dcm->vnet_main, c->sw_if_index,
820               format_dhcp_client_state, c->state);
821
822   if (c->leased_address.as_u32)
823     s = format (s, "addr %U/%d gw %U\n",
824                 format_ip4_address, &c->leased_address,
825                 c->subnet_mask_width, format_ip4_address, &c->router_address);
826   else
827     s = format (s, "no address\n");
828
829   if (verbose)
830     {
831       s = format (s, "retry count %d, next xmt %.2f",
832                   c->retry_count, c->next_transmit);
833     }
834   return s;
835 }
836
837 static clib_error_t *
838 show_dhcp_client_command_fn (vlib_main_t * vm,
839                              unformat_input_t * input,
840                              vlib_cli_command_t * cmd)
841 {
842   dhcp_client_main_t *dcm = &dhcp_client_main;
843   dhcp_client_t *c;
844   int verbose = 0;
845   u32 sw_if_index = ~0;
846   uword *p;
847
848   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
849     {
850       if (unformat (input, "intfc %U",
851                     unformat_vnet_sw_interface, dcm->vnet_main, &sw_if_index))
852         ;
853       else if (unformat (input, "verbose"))
854         verbose = 1;
855       else
856         break;
857     }
858
859   if (sw_if_index != ~0)
860     {
861       p = hash_get (dcm->client_by_sw_if_index, sw_if_index);
862       if (p == 0)
863         return clib_error_return (0, "dhcp client not configured");
864       c = pool_elt_at_index (dcm->clients, p[0]);
865       vlib_cli_output (vm, "%U", format_dhcp_client, dcm, c, verbose);
866       return 0;
867     }
868
869   /* *INDENT-OFF* */
870   pool_foreach (c, dcm->clients,
871   ({
872     vlib_cli_output (vm, "%U",
873                      format_dhcp_client, dcm,
874                      c, verbose);
875   }));
876   /* *INDENT-ON* */
877
878   return 0;
879 }
880
881 /* *INDENT-OFF* */
882 VLIB_CLI_COMMAND (show_dhcp_client_command, static) = {
883   .path = "show dhcp client",
884   .short_help = "show dhcp client [intfc <intfc>][verbose]",
885   .function = show_dhcp_client_command_fn,
886 };
887 /* *INDENT-ON* */
888
889
890 int
891 dhcp_client_add_del (dhcp_client_add_del_args_t * a)
892 {
893   dhcp_client_main_t *dcm = &dhcp_client_main;
894   vlib_main_t *vm = dcm->vlib_main;
895   dhcp_client_t *c;
896   uword *p;
897   fib_prefix_t all_0s = {
898     .fp_len = 0,
899     .fp_addr.ip4.as_u32 = 0x0,
900     .fp_proto = FIB_PROTOCOL_IP4,
901   };
902
903   p = hash_get (dcm->client_by_sw_if_index, a->sw_if_index);
904
905   if ((p && a->is_add) || (!p && a->is_add == 0))
906     return VNET_API_ERROR_INVALID_VALUE;
907
908   if (a->is_add)
909     {
910       pool_get (dcm->clients, c);
911       clib_memset (c, 0, sizeof (*c));
912       c->state = DHCP_DISCOVER;
913       c->sw_if_index = a->sw_if_index;
914       c->client_index = a->client_index;
915       c->pid = a->pid;
916       c->event_callback = a->event_callback;
917       c->option_55_data = a->option_55_data;
918       c->hostname = a->hostname;
919       c->client_identifier = a->client_identifier;
920       c->set_broadcast_flag = a->set_broadcast_flag;
921       do
922         {
923           c->transaction_id = random_u32 (&dcm->seed);
924         }
925       while (c->transaction_id == 0);
926       set_l2_rewrite (dcm, c);
927       hash_set (dcm->client_by_sw_if_index, a->sw_if_index, c - dcm->clients);
928
929       /*
930        * In order to accept any OFFER, whether broadcasted or unicasted, we
931        * need to configure the dhcp-client-detect feature as an input feature
932        * so the DHCP OFFER is sent to the ip4-local node. Without this a
933        * broadcasted OFFER hits the 255.255.255.255/32 address and a unicast
934        * hits 0.0.0.0/0 both of which default to drop and the latter may forward
935        * of box - not what we want. Nor to we want to change these route for
936        * all interfaces in this table
937        */
938       vnet_feature_enable_disable ("ip4-unicast",
939                                    "ip4-dhcp-client-detect",
940                                    c->sw_if_index, 1 /* enable */ , 0, 0);
941       c->client_detect_feature_enabled = 1;
942
943       vlib_process_signal_event (vm, dhcp_client_process_node.index,
944                                  EVENT_DHCP_CLIENT_WAKEUP, c - dcm->clients);
945     }
946   else
947     {
948       c = pool_elt_at_index (dcm->clients, p[0]);
949
950       if (c->router_address.as_u32)
951         {
952           ip46_address_t nh = {
953             .ip4 = c->router_address,
954           };
955
956           fib_table_entry_path_remove (fib_table_get_index_for_sw_if_index
957                                        (FIB_PROTOCOL_IP4, c->sw_if_index),
958                                        &all_0s, FIB_SOURCE_DHCP,
959                                        DPO_PROTO_IP4, &nh, c->sw_if_index, ~0,
960                                        1, FIB_ROUTE_PATH_FLAG_NONE);
961         }
962       dhcp_client_release_address (dcm, c);
963
964       vec_free (c->option_55_data);
965       vec_free (c->hostname);
966       vec_free (c->client_identifier);
967       vec_free (c->l2_rewrite);
968       hash_unset (dcm->client_by_sw_if_index, c->sw_if_index);
969       pool_put (dcm->clients, c);
970     }
971   return 0;
972 }
973
974 int
975 dhcp_client_config (u32 is_add,
976                     u32 client_index,
977                     vlib_main_t * vm,
978                     u32 sw_if_index,
979                     u8 * hostname,
980                     u8 * client_id,
981                     dhcp_event_cb_t event_callback,
982                     u8 set_broadcast_flag, u32 pid)
983 {
984   dhcp_client_add_del_args_t _a, *a = &_a;
985   int rv;
986
987   clib_memset (a, 0, sizeof (*a));
988   a->is_add = is_add;
989   a->sw_if_index = sw_if_index;
990   a->client_index = client_index;
991   a->pid = pid;
992   a->event_callback = event_callback;
993   a->set_broadcast_flag = set_broadcast_flag;
994   vec_validate (a->hostname, strlen ((char *) hostname) - 1);
995   strncpy ((char *) a->hostname, (char *) hostname, vec_len (a->hostname));
996   vec_validate (a->client_identifier, strlen ((char *) client_id) - 1);
997   strncpy ((char *) a->client_identifier, (char *) client_id,
998            vec_len (a->client_identifier));
999
1000   /*
1001    * Option 55 request list. These data precisely match
1002    * the Ubuntu dhcp client. YMMV.
1003    */
1004
1005   /* Subnet Mask */
1006   vec_add1 (a->option_55_data, 1);
1007   /* Broadcast address */
1008   vec_add1 (a->option_55_data, 28);
1009   /* time offset */
1010   vec_add1 (a->option_55_data, 2);
1011   /* Router */
1012   vec_add1 (a->option_55_data, 3);
1013   /* Domain Name */
1014   vec_add1 (a->option_55_data, 15);
1015   /* DNS */
1016   vec_add1 (a->option_55_data, 6);
1017   /* Domain search */
1018   vec_add1 (a->option_55_data, 119);
1019   /* Host name */
1020   vec_add1 (a->option_55_data, 12);
1021   /* NetBIOS name server */
1022   vec_add1 (a->option_55_data, 44);
1023   /* NetBIOS Scope */
1024   vec_add1 (a->option_55_data, 47);
1025   /* MTU */
1026   vec_add1 (a->option_55_data, 26);
1027   /* Classless static route */
1028   vec_add1 (a->option_55_data, 121);
1029   /* NTP servers */
1030   vec_add1 (a->option_55_data, 42);
1031
1032   rv = dhcp_client_add_del (a);
1033
1034   switch (rv)
1035     {
1036     case 0:
1037       break;
1038
1039     case VNET_API_ERROR_INVALID_VALUE:
1040
1041       vec_free (a->hostname);
1042       vec_free (a->client_identifier);
1043       vec_free (a->option_55_data);
1044
1045       if (is_add)
1046         clib_warning ("dhcp client already enabled on intf_idx %d",
1047                       sw_if_index);
1048       else
1049         clib_warning ("dhcp client not enabled on on intf_idx %d",
1050                       sw_if_index);
1051       break;
1052
1053     default:
1054       clib_warning ("dhcp_client_add_del returned %d", rv);
1055     }
1056
1057   return rv;
1058 }
1059
1060 void
1061 dhcp_client_walk (dhcp_client_walk_cb_t cb, void *ctx)
1062 {
1063   dhcp_client_main_t *dcm = &dhcp_client_main;
1064   dhcp_client_t *c;
1065
1066   /* *INDENT-OFF* */
1067   pool_foreach (c, dcm->clients,
1068   ({
1069     if (!cb(c, ctx))
1070       break;
1071   }));
1072   /* *INDENT-ON* */
1073
1074 }
1075
1076 static clib_error_t *
1077 dhcp_client_set_command_fn (vlib_main_t * vm,
1078                             unformat_input_t * input,
1079                             vlib_cli_command_t * cmd)
1080 {
1081
1082   dhcp_client_main_t *dcm = &dhcp_client_main;
1083   u32 sw_if_index;
1084   u8 *hostname = 0;
1085   u8 sw_if_index_set = 0;
1086   u8 set_broadcast_flag = 1;
1087   int is_add = 1;
1088   dhcp_client_add_del_args_t _a, *a = &_a;
1089   int rv;
1090
1091   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
1092     {
1093       if (unformat (input, "intfc %U",
1094                     unformat_vnet_sw_interface, dcm->vnet_main, &sw_if_index))
1095         sw_if_index_set = 1;
1096       else if (unformat (input, "hostname %v", &hostname))
1097         ;
1098       else if (unformat (input, "del"))
1099         is_add = 0;
1100       else if (unformat (input, "broadcast", &set_broadcast_flag))
1101         is_add = 0;
1102       else
1103         break;
1104     }
1105
1106   if (sw_if_index_set == 0)
1107     return clib_error_return (0, "interface not specified");
1108
1109   clib_memset (a, 0, sizeof (*a));
1110   a->is_add = is_add;
1111   a->sw_if_index = sw_if_index;
1112   a->hostname = hostname;
1113   a->client_identifier = format (0, "vpe 1.0%c", 0);
1114   a->set_broadcast_flag = set_broadcast_flag;
1115
1116   /*
1117    * Option 55 request list. These data precisely match
1118    * the Ubuntu dhcp client. YMMV.
1119    */
1120
1121   /* Subnet Mask */
1122   vec_add1 (a->option_55_data, 1);
1123   /* Broadcast address */
1124   vec_add1 (a->option_55_data, 28);
1125   /* time offset */
1126   vec_add1 (a->option_55_data, 2);
1127   /* Router */
1128   vec_add1 (a->option_55_data, 3);
1129   /* Domain Name */
1130   vec_add1 (a->option_55_data, 15);
1131   /* DNS */
1132   vec_add1 (a->option_55_data, 6);
1133   /* Domain search */
1134   vec_add1 (a->option_55_data, 119);
1135   /* Host name */
1136   vec_add1 (a->option_55_data, 12);
1137   /* NetBIOS name server */
1138   vec_add1 (a->option_55_data, 44);
1139   /* NetBIOS Scope */
1140   vec_add1 (a->option_55_data, 47);
1141   /* MTU */
1142   vec_add1 (a->option_55_data, 26);
1143   /* Classless static route */
1144   vec_add1 (a->option_55_data, 121);
1145   /* NTP servers */
1146   vec_add1 (a->option_55_data, 42);
1147
1148   rv = dhcp_client_add_del (a);
1149
1150   switch (rv)
1151     {
1152     case 0:
1153       break;
1154
1155     case VNET_API_ERROR_INVALID_VALUE:
1156
1157       vec_free (a->hostname);
1158       vec_free (a->client_identifier);
1159       vec_free (a->option_55_data);
1160       if (is_add)
1161         return clib_error_return (0, "dhcp client already enabled on %U",
1162                                   format_vnet_sw_if_index_name,
1163                                   dcm->vnet_main, sw_if_index);
1164       else
1165         return clib_error_return (0, "dhcp client not enabled on %U",
1166                                   format_vnet_sw_if_index_name,
1167                                   dcm->vnet_main, sw_if_index);
1168       break;
1169
1170     default:
1171       vlib_cli_output (vm, "dhcp_client_add_del returned %d", rv);
1172     }
1173
1174   return 0;
1175 }
1176
1177 /* *INDENT-OFF* */
1178 VLIB_CLI_COMMAND (dhcp_client_set_command, static) = {
1179   .path = "set dhcp client",
1180   .short_help = "set dhcp client [del] intfc <interface> [hostname <name>]",
1181   .function = dhcp_client_set_command_fn,
1182 };
1183 /* *INDENT-ON* */
1184
1185 static clib_error_t *
1186 dhcp_client_init (vlib_main_t * vm)
1187 {
1188   dhcp_client_main_t *dcm = &dhcp_client_main;
1189
1190   dcm->vlib_main = vm;
1191   dcm->vnet_main = vnet_get_main ();
1192   dcm->seed = 0xdeaddabe;
1193   return 0;
1194 }
1195
1196 VLIB_INIT_FUNCTION (dhcp_client_init);
1197
1198 /*
1199  * fd.io coding-style-patch-verification: ON
1200  *
1201  * Local Variables:
1202  * eval: (c-set-style "gnu")
1203  * End:
1204  */