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