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