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