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