vppinfra: AArch64 NEON implementation of clib_compare_u16_x64()
[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   VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b);
504
505   ASSERT (b->current_data == 0);
506
507   vnet_buffer (b)->sw_if_index[VLIB_RX] = c->sw_if_index;
508   b->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
509
510   if (is_broadcast)
511     {
512       node_index = ip4_rewrite_node.index;
513       vnet_buffer (b)->ip.adj_index[VLIB_TX] = c->ai_bcast;
514     }
515   else
516     node_index = dcm->ip4_lookup_node_index;
517
518   /* Enqueue the packet right now */
519   f = vlib_get_frame_to_node (vm, node_index);
520   to_next = vlib_frame_vector_args (f);
521   to_next[0] = bi;
522   f->n_vectors = 1;
523   vlib_put_frame_to_node (vm, node_index, f);
524
525   /* build the headers */
526   ip = vlib_buffer_get_current (b);
527   udp = (udp_header_t *) (ip + 1);
528   dhcp = (dhcp_header_t *) (udp + 1);
529
530   /* $$$ optimize, maybe */
531   clib_memset (ip, 0, sizeof (*ip) + sizeof (*udp) + sizeof (*dhcp));
532
533   ip->ip_version_and_header_length = 0x45;
534   ip->ttl = 128;
535   ip->protocol = IP_PROTOCOL_UDP;
536
537   ip->tos = c->dscp;
538
539   if (ip->tos)
540     {
541       /*
542        * Setup the buffer's QoS settings so any QoS marker on the egress
543        * interface, that might set VLAN CoS bits, based on this DSCP setting
544        */
545       vnet_buffer2 (b)->qos.source = QOS_SOURCE_IP;
546       vnet_buffer2 (b)->qos.bits = ip->tos;
547       b->flags |= VNET_BUFFER_F_QOS_DATA_VALID;
548     }
549
550   if (is_broadcast)
551     {
552       /* src = 0.0.0.0, dst = 255.255.255.255 */
553       ip->dst_address.as_u32 = ~0;
554     }
555   else
556     {
557       /* Renewing an active lease, plain old ip4 src/dst */
558       ip->src_address.as_u32 = c->learned.leased_address.as_u32;
559       ip->dst_address.as_u32 = c->learned.dhcp_server.as_u32;
560     }
561
562   udp->src_port = clib_host_to_net_u16 (UDP_DST_PORT_dhcp_to_client);
563   udp->dst_port = clib_host_to_net_u16 (UDP_DST_PORT_dhcp_to_server);
564
565   /* Send the interface MAC address */
566   clib_memcpy (dhcp->client_hardware_address,
567                vnet_sw_interface_get_hw_address (vnm, c->sw_if_index), 6);
568
569   /* And remember it for rx-packet-for-us checking */
570   clib_memcpy (c->client_hardware_address, dhcp->client_hardware_address,
571                sizeof (c->client_hardware_address));
572
573   /* Lease renewal, set up client_ip_address */
574   if (is_broadcast == 0)
575     dhcp->client_ip_address.as_u32 = c->learned.leased_address.as_u32;
576
577   dhcp->opcode = 1;             /* request, all we send */
578   dhcp->hardware_type = 1;      /* ethernet */
579   dhcp->hardware_address_length = 6;
580   dhcp->transaction_identifier = c->transaction_id;
581   dhcp->flags =
582     clib_host_to_net_u16 (is_broadcast && c->set_broadcast_flag ?
583                           DHCP_FLAG_BROADCAST : 0);
584   dhcp->magic_cookie.as_u32 = DHCP_MAGIC;
585
586   o = (dhcp_option_t *) dhcp->options;
587
588   /* Send option 53, the DHCP message type */
589   o->option = DHCP_PACKET_OPTION_MSG_TYPE;
590   o->length = 1;
591   o->data[0] = type;
592   o = (dhcp_option_t *) (((uword) o) + (o->length + 2));
593
594   /* Send option 57, max msg length */
595   if (0 /* not needed, apparently */ )
596     {
597       o->option = 57;
598       o->length = 2;
599       {
600         u16 *o2 = (u16 *) o->data;
601         *o2 = clib_host_to_net_u16 (1152);
602         o = (dhcp_option_t *) (((uword) o) + (o->length + 2));
603       }
604     }
605
606   /*
607    * If server ip address is available with non-zero value,
608    * option 54 (DHCP Server Identifier) is sent.
609    */
610   if (c->learned.dhcp_server.as_u32)
611     {
612       o->option = 54;
613       o->length = 4;
614       clib_memcpy (o->data, &c->learned.dhcp_server.as_u32, 4);
615       o = (dhcp_option_t *) (((uword) o) + (o->length + 2));
616     }
617
618   /* send option 50, requested IP address */
619   if (c->learned.leased_address.as_u32)
620     {
621       o->option = 50;
622       o->length = 4;
623       clib_memcpy (o->data, &c->learned.leased_address.as_u32, 4);
624       o = (dhcp_option_t *) (((uword) o) + (o->length + 2));
625     }
626
627   /* send option 12, host name */
628   if (vec_len (c->hostname))
629     {
630       o->option = 12;
631       o->length = vec_len (c->hostname);
632       clib_memcpy (o->data, c->hostname, vec_len (c->hostname));
633       o = (dhcp_option_t *) (((uword) o) + (o->length + 2));
634     }
635
636   /* send option 61, client_id */
637   if (vec_len (c->client_identifier))
638     {
639       o->option = 61;
640       o->length = vec_len (c->client_identifier) + 1;
641       /* Set type to zero, apparently some dhcp servers care */
642       o->data[0] = 0;
643       clib_memcpy (o->data + 1, c->client_identifier,
644                    vec_len (c->client_identifier));
645       o = (dhcp_option_t *) (((uword) o) + (o->length + 2));
646     }
647
648   /* $$ maybe send the client s/w version if anyone cares */
649
650   /*
651    * send option 55, parameter request list
652    * The current list - see below, matches the Linux dhcp client's list
653    * Any specific dhcp server config and/or dhcp server may or may
654    * not yield specific options.
655    */
656   o->option = 55;
657   o->length = vec_len (c->option_55_data);
658   clib_memcpy (o->data, c->option_55_data, vec_len (c->option_55_data));
659   o = (dhcp_option_t *) (((uword) o) + (o->length + 2));
660
661   /* End of list */
662   o->option = 0xff;
663   o->length = 0;
664   o++;
665
666   b->current_length = ((u8 *) o) - b->data;
667
668   /* fix ip length, checksum and udp length */
669   ip_length = vlib_buffer_length_in_chain (vm, b);
670
671   ip->length = clib_host_to_net_u16 (ip_length);
672   ip->checksum = ip4_header_checksum (ip);
673
674   udp_length = ip_length - (sizeof (*ip));
675   udp->length = clib_host_to_net_u16 (udp_length);
676
677   switch (type)
678     {
679 #define _(a,b) case DHCP_PACKET_##a: {counter_index = DHCP_STAT_##a; break;}
680       foreach_dhcp_sent_packet_stat
681 #undef _
682     default:
683       counter_index = DHCP_STAT_UNKNOWN;
684       break;
685     }
686
687   vlib_node_increment_counter (vm, dhcp_client_process_node.index,
688                                counter_index, 1);
689 }
690
691 static int
692 dhcp_discover_state (dhcp_client_main_t * dcm, dhcp_client_t * c, f64 now)
693 {
694   /*
695    * State machine "DISCOVER" state. Send a dhcp discover packet,
696    * eventually back off the retry rate.
697    */
698   /*
699    * In order to accept any OFFER, whether broadcasted or unicasted, we
700    * need to configure the dhcp-client-detect feature as an input feature
701    * so the DHCP OFFER is sent to the ip4-local node. Without this a
702    * broadcasted OFFER hits the 255.255.255.255/32 address and a unicast
703    * hits 0.0.0.0/0 both of which default to drop and the latter may forward
704    * of box - not what we want. Nor to we want to change these route for
705    * all interfaces in this table
706    */
707   if (c->client_detect_feature_enabled == 0)
708     {
709       vlib_worker_thread_barrier_sync (dcm->vlib_main);
710       vnet_feature_enable_disable ("ip4-unicast",
711                                    "ip4-dhcp-client-detect",
712                                    c->sw_if_index, 1 /* enable */ , 0, 0);
713       vlib_worker_thread_barrier_release (dcm->vlib_main);
714       c->client_detect_feature_enabled = 1;
715     }
716
717   send_dhcp_pkt (dcm, c, DHCP_PACKET_DISCOVER, 1 /* is_broadcast */ );
718
719   c->retry_count++;
720   if (c->retry_count > 10)
721     c->next_transmit = now + 5.0;
722   else
723     c->next_transmit = now + 1.0;
724   return 0;
725 }
726
727 static int
728 dhcp_request_state (dhcp_client_main_t * dcm, dhcp_client_t * c, f64 now)
729 {
730   /*
731    * State machine "REQUEST" state. Send a dhcp request packet,
732    * eventually drop back to the discover state.
733    */
734   DHCP_INFO ("enter request: %U", format_dhcp_client, dcm, c,
735              1 /*verbose */ );
736
737   send_dhcp_pkt (dcm, c, DHCP_PACKET_REQUEST, 1 /* is_broadcast */ );
738
739   c->retry_count++;
740   if (c->retry_count > 7 /* lucky you */ )
741     {
742       c->state = DHCP_DISCOVER;
743       c->next_transmit = now;
744       c->retry_count = 0;
745       return 1;
746     }
747   c->next_transmit = now + 1.0;
748   return 0;
749 }
750
751 static int
752 dhcp_bound_state (dhcp_client_main_t * dcm, dhcp_client_t * c, f64 now)
753 {
754   /*
755    * We disable the client detect feature when we bind a
756    * DHCP address. Turn it back on again on first renew attempt.
757    * Otherwise, if the DHCP server replies we'll never see it.
758    */
759   if (c->client_detect_feature_enabled == 0)
760     {
761       vlib_worker_thread_barrier_sync (dcm->vlib_main);
762       vnet_feature_enable_disable ("ip4-unicast",
763                                    "ip4-dhcp-client-detect",
764                                    c->sw_if_index, 1 /* enable */ , 0, 0);
765       vlib_worker_thread_barrier_release (dcm->vlib_main);
766       c->client_detect_feature_enabled = 1;
767     }
768
769   /*
770    * State machine "BOUND" state. Send a dhcp request packet to renew
771    * the lease.
772    * Eventually, when the lease expires, forget the dhcp data
773    * and go back to the stone age.
774    */
775   if (now > c->lease_expires)
776     {
777       DHCP_INFO ("lease expired: %U", format_dhcp_client, dcm, c,
778                  1 /*verbose */ );
779
780       /* reset all data for the client. do not send any more messages
781        * since the objects to do so have been lost */
782       dhcp_client_reset (dcm, c);
783       return 1;
784     }
785
786   DHCP_INFO ("enter bound: %U", format_dhcp_client, dcm, c, 1 /* verbose */ );
787   send_dhcp_pkt (dcm, c, DHCP_PACKET_REQUEST, 0 /* is_broadcast */ );
788
789   c->retry_count++;
790   if (c->retry_count > 10)
791     c->next_transmit = now + 5.0;
792   else
793     c->next_transmit = now + 1.0;
794
795   return 0;
796 }
797
798 static f64
799 dhcp_client_sm (f64 now, f64 timeout, uword pool_index)
800 {
801   dhcp_client_main_t *dcm = &dhcp_client_main;
802   dhcp_client_t *c;
803
804   /* deleted, pooched, yadda yadda yadda */
805   if (pool_is_free_index (dcm->clients, pool_index))
806     return timeout;
807
808   c = pool_elt_at_index (dcm->clients, pool_index);
809
810   /* Time for us to do something with this client? */
811   if (now < c->next_transmit)
812     return c->next_transmit;
813
814   DHCP_INFO ("sm active session %d", c - dcm->clients);
815
816 again:
817   switch (c->state)
818     {
819     case DHCP_DISCOVER: /* send a discover */
820       if (dhcp_discover_state (dcm, c, now))
821         goto again;
822       break;
823
824     case DHCP_REQUEST:          /* send a request */
825       if (dhcp_request_state (dcm, c, now))
826         goto again;
827       break;
828
829     case DHCP_BOUND:            /* bound, renew needed? */
830       if (dhcp_bound_state (dcm, c, now))
831         goto again;
832       break;
833
834     default:
835       clib_warning ("dhcp client %d bogus state %d",
836                     c - dcm->clients, c->state);
837       break;
838     }
839
840   return c->next_transmit;
841 }
842
843 static uword
844 dhcp_client_process (vlib_main_t * vm,
845                      vlib_node_runtime_t * rt, vlib_frame_t * f)
846 {
847   f64 timeout = 1000.0;
848   f64 next_expire_time, this_next_expire_time;
849   f64 now;
850   uword event_type;
851   uword *event_data = 0;
852   dhcp_client_main_t *dcm = &dhcp_client_main;
853   dhcp_client_t *c;
854   int i;
855
856   while (1)
857     {
858       vlib_process_wait_for_event_or_clock (vm, timeout);
859
860       event_type = vlib_process_get_events (vm, &event_data);
861
862       now = vlib_time_now (vm);
863
864       switch (event_type)
865         {
866         case EVENT_DHCP_CLIENT_WAKEUP:
867           for (i = 0; i < vec_len (event_data); i++)
868             (void) dhcp_client_sm (now, timeout, event_data[i]);
869           /* FALLTHROUGH */
870
871         case ~0:
872           if (pool_elts (dcm->clients))
873             {
874               /* *INDENT-OFF* */
875               next_expire_time = 1e70;
876               pool_foreach (c, dcm->clients)
877                {
878                 this_next_expire_time = dhcp_client_sm
879                   (now, timeout, (uword) (c - dcm->clients));
880                 next_expire_time = this_next_expire_time < next_expire_time ?
881                   this_next_expire_time : next_expire_time;
882               }
883               if (next_expire_time > now)
884                 timeout = next_expire_time - now;
885               else
886                 {
887                   clib_warning ("BUG");
888                   timeout = 1.13;
889                 }
890               /* *INDENT-ON* */
891             }
892           else
893             timeout = 1000.0;
894           break;
895         }
896
897       vec_reset_length (event_data);
898     }
899
900   /* NOTREACHED */
901   return 0;
902 }
903
904 /* *INDENT-OFF* */
905 VLIB_REGISTER_NODE (dhcp_client_process_node,static) = {
906     .function = dhcp_client_process,
907     .type = VLIB_NODE_TYPE_PROCESS,
908     .name = "dhcp-client-process",
909     .process_log2_n_stack_bytes = 16,
910     .n_errors = ARRAY_LEN(dhcp_client_process_stat_strings),
911     .error_strings = dhcp_client_process_stat_strings,
912 };
913 /* *INDENT-ON* */
914
915 static clib_error_t *
916 show_dhcp_client_command_fn (vlib_main_t * vm,
917                              unformat_input_t * input,
918                              vlib_cli_command_t * cmd)
919 {
920   dhcp_client_main_t *dcm = &dhcp_client_main;
921   dhcp_client_t *c;
922   int verbose = 0;
923   u32 sw_if_index = ~0;
924   uword *p;
925
926   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
927     {
928       if (unformat (input, "intfc %U",
929                     unformat_vnet_sw_interface, dcm->vnet_main, &sw_if_index))
930         ;
931       else if (unformat (input, "verbose"))
932         verbose = 1;
933       else
934         break;
935     }
936
937   if (sw_if_index != ~0)
938     {
939       p = hash_get (dcm->client_by_sw_if_index, sw_if_index);
940       if (p == 0)
941         return clib_error_return (0, "dhcp client not configured");
942       c = pool_elt_at_index (dcm->clients, p[0]);
943       vlib_cli_output (vm, "%U", format_dhcp_client, dcm, c, verbose);
944       return 0;
945     }
946
947   /* *INDENT-OFF* */
948   pool_foreach (c, dcm->clients)
949    {
950     vlib_cli_output (vm, "%U",
951                      format_dhcp_client, dcm,
952                      c, verbose);
953   }
954   /* *INDENT-ON* */
955
956   return 0;
957 }
958
959 /* *INDENT-OFF* */
960 VLIB_CLI_COMMAND (show_dhcp_client_command, static) = {
961   .path = "show dhcp client",
962   .short_help = "show dhcp client [intfc <intfc>][verbose]",
963   .function = show_dhcp_client_command_fn,
964 };
965 /* *INDENT-ON* */
966
967
968 int
969 dhcp_client_add_del (dhcp_client_add_del_args_t * a)
970 {
971   dhcp_client_main_t *dcm = &dhcp_client_main;
972   vlib_main_t *vm = dcm->vlib_main;
973   dhcp_client_t *c;
974   uword *p;
975
976   p = hash_get (dcm->client_by_sw_if_index, a->sw_if_index);
977
978   if ((p && a->is_add) || (!p && a->is_add == 0))
979     return VNET_API_ERROR_INVALID_VALUE;
980
981   if (a->is_add)
982     {
983       dhcp_maybe_register_udp_ports (DHCP_PORT_REG_CLIENT);
984       pool_get (dcm->clients, c);
985       clib_memset (c, 0, sizeof (*c));
986       c->state = DHCP_DISCOVER;
987       c->sw_if_index = a->sw_if_index;
988       c->client_index = a->client_index;
989       c->pid = a->pid;
990       c->event_callback = a->event_callback;
991       c->option_55_data = a->option_55_data;
992       c->hostname = a->hostname;
993       c->client_identifier = a->client_identifier;
994       c->set_broadcast_flag = a->set_broadcast_flag;
995       c->dscp = a->dscp;
996       c->ai_bcast = adj_nbr_add_or_lock (FIB_PROTOCOL_IP4,
997                                          VNET_LINK_IP4,
998                                          &ADJ_BCAST_ADDR, c->sw_if_index);
999
1000       do
1001         {
1002           c->transaction_id = random_u32 (&dcm->seed);
1003         }
1004       while (c->transaction_id == 0);
1005
1006       hash_set (dcm->client_by_sw_if_index, a->sw_if_index, c - dcm->clients);
1007
1008       vlib_process_signal_event (vm, dhcp_client_process_node.index,
1009                                  EVENT_DHCP_CLIENT_WAKEUP, c - dcm->clients);
1010
1011       DHCP_INFO ("create: %U", format_dhcp_client, dcm, c, 1 /* verbose */ );
1012     }
1013   else
1014     {
1015       c = pool_elt_at_index (dcm->clients, p[0]);
1016
1017       dhcp_client_reset (dcm, c);
1018
1019       adj_unlock (c->ai_bcast);
1020
1021       vec_free (c->domain_server_address);
1022       vec_free (c->option_55_data);
1023       vec_free (c->hostname);
1024       vec_free (c->client_identifier);
1025       hash_unset (dcm->client_by_sw_if_index, c->sw_if_index);
1026       pool_put (dcm->clients, c);
1027     }
1028   return 0;
1029 }
1030
1031 int
1032 dhcp_client_config (u32 is_add,
1033                     u32 client_index,
1034                     vlib_main_t * vm,
1035                     u32 sw_if_index,
1036                     u8 * hostname,
1037                     u8 * client_id,
1038                     dhcp_event_cb_t event_callback,
1039                     u8 set_broadcast_flag, ip_dscp_t dscp, u32 pid)
1040 {
1041   dhcp_client_add_del_args_t _a, *a = &_a;
1042   int rv;
1043
1044   clib_memset (a, 0, sizeof (*a));
1045   a->is_add = is_add;
1046   a->sw_if_index = sw_if_index;
1047   a->client_index = client_index;
1048   a->pid = pid;
1049   a->event_callback = event_callback;
1050   a->set_broadcast_flag = set_broadcast_flag;
1051   a->dscp = dscp;
1052   vec_validate (a->hostname, strlen ((char *) hostname) - 1);
1053   strncpy ((char *) a->hostname, (char *) hostname, vec_len (a->hostname));
1054   vec_validate (a->client_identifier, strlen ((char *) client_id) - 1);
1055   strncpy ((char *) a->client_identifier, (char *) client_id,
1056            vec_len (a->client_identifier));
1057
1058   /*
1059    * Option 55 request list. These data precisely match
1060    * the Ubuntu dhcp client. YMMV.
1061    */
1062
1063   /* Subnet Mask */
1064   vec_add1 (a->option_55_data, 1);
1065   /* Broadcast address */
1066   vec_add1 (a->option_55_data, 28);
1067   /* time offset */
1068   vec_add1 (a->option_55_data, 2);
1069   /* Router */
1070   vec_add1 (a->option_55_data, 3);
1071   /* Domain Name */
1072   vec_add1 (a->option_55_data, 15);
1073   /* DNS */
1074   vec_add1 (a->option_55_data, 6);
1075   /* Domain search */
1076   vec_add1 (a->option_55_data, 119);
1077   /* Host name */
1078   vec_add1 (a->option_55_data, 12);
1079   /* NetBIOS name server */
1080   vec_add1 (a->option_55_data, 44);
1081   /* NetBIOS Scope */
1082   vec_add1 (a->option_55_data, 47);
1083   /* MTU */
1084   vec_add1 (a->option_55_data, 26);
1085   /* Classless static route */
1086   vec_add1 (a->option_55_data, 121);
1087   /* NTP servers */
1088   vec_add1 (a->option_55_data, 42);
1089
1090   rv = dhcp_client_add_del (a);
1091
1092   switch (rv)
1093     {
1094     case 0:
1095       break;
1096
1097     case VNET_API_ERROR_INVALID_VALUE:
1098
1099       vec_free (a->hostname);
1100       vec_free (a->client_identifier);
1101       vec_free (a->option_55_data);
1102
1103       if (is_add)
1104         DHCP_INFO ("dhcp client already enabled on intf_idx %d", sw_if_index);
1105       else
1106         DHCP_INFO ("not enabled on on intf_idx %d", sw_if_index);
1107       break;
1108
1109     default:
1110       DHCP_INFO ("dhcp_client_add_del returned %d", rv);
1111     }
1112
1113   return rv;
1114 }
1115
1116 void
1117 dhcp_client_walk (dhcp_client_walk_cb_t cb, void *ctx)
1118 {
1119   dhcp_client_main_t *dcm = &dhcp_client_main;
1120   dhcp_client_t *c;
1121
1122   /* *INDENT-OFF* */
1123   pool_foreach (c, dcm->clients)
1124    {
1125     if (!cb(c, ctx))
1126       break;
1127   }
1128   /* *INDENT-ON* */
1129
1130 }
1131
1132 static clib_error_t *
1133 dhcp_client_set_command_fn (vlib_main_t * vm,
1134                             unformat_input_t * input,
1135                             vlib_cli_command_t * cmd)
1136 {
1137
1138   dhcp_client_main_t *dcm = &dhcp_client_main;
1139   u32 sw_if_index;
1140   u8 *hostname = 0;
1141   u8 sw_if_index_set = 0;
1142   u8 set_broadcast_flag = 1;
1143   int is_add = 1;
1144   dhcp_client_add_del_args_t _a, *a = &_a;
1145   int rv;
1146
1147   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
1148     {
1149       if (unformat (input, "intfc %U",
1150                     unformat_vnet_sw_interface, dcm->vnet_main, &sw_if_index))
1151         sw_if_index_set = 1;
1152       else if (unformat (input, "hostname %v", &hostname))
1153         ;
1154       else if (unformat (input, "del"))
1155         is_add = 0;
1156       else if (unformat (input, "broadcast", &set_broadcast_flag))
1157         is_add = 0;
1158       else
1159         break;
1160     }
1161
1162   if (sw_if_index_set == 0)
1163     return clib_error_return (0, "interface not specified");
1164
1165   clib_memset (a, 0, sizeof (*a));
1166   a->is_add = is_add;
1167   a->sw_if_index = sw_if_index;
1168   a->hostname = hostname;
1169   a->client_identifier = format (0, "vpp 1.1%c", 0);
1170   a->set_broadcast_flag = set_broadcast_flag;
1171
1172   /*
1173    * Option 55 request list. These data precisely match
1174    * the Ubuntu dhcp client. YMMV.
1175    */
1176
1177   /* Subnet Mask */
1178   vec_add1 (a->option_55_data, 1);
1179   /* Broadcast address */
1180   vec_add1 (a->option_55_data, 28);
1181   /* time offset */
1182   vec_add1 (a->option_55_data, 2);
1183   /* Router */
1184   vec_add1 (a->option_55_data, 3);
1185   /* Domain Name */
1186   vec_add1 (a->option_55_data, 15);
1187   /* DNS */
1188   vec_add1 (a->option_55_data, 6);
1189   /* Domain search */
1190   vec_add1 (a->option_55_data, 119);
1191   /* Host name */
1192   vec_add1 (a->option_55_data, 12);
1193   /* NetBIOS name server */
1194   vec_add1 (a->option_55_data, 44);
1195   /* NetBIOS Scope */
1196   vec_add1 (a->option_55_data, 47);
1197   /* MTU */
1198   vec_add1 (a->option_55_data, 26);
1199   /* Classless static route */
1200   vec_add1 (a->option_55_data, 121);
1201   /* NTP servers */
1202   vec_add1 (a->option_55_data, 42);
1203
1204   rv = dhcp_client_add_del (a);
1205
1206   switch (rv)
1207     {
1208     case 0:
1209       break;
1210
1211     case VNET_API_ERROR_INVALID_VALUE:
1212
1213       vec_free (a->hostname);
1214       vec_free (a->client_identifier);
1215       vec_free (a->option_55_data);
1216       if (is_add)
1217         return clib_error_return (0, "dhcp client already enabled on %U",
1218                                   format_vnet_sw_if_index_name,
1219                                   dcm->vnet_main, sw_if_index);
1220       else
1221         return clib_error_return (0, "dhcp client not enabled on %U",
1222                                   format_vnet_sw_if_index_name,
1223                                   dcm->vnet_main, sw_if_index);
1224       break;
1225
1226     default:
1227       vlib_cli_output (vm, "dhcp_client_add_del returned %d", rv);
1228     }
1229
1230   return 0;
1231 }
1232
1233 /* *INDENT-OFF* */
1234 VLIB_CLI_COMMAND (dhcp_client_set_command, static) = {
1235   .path = "set dhcp client",
1236   .short_help = "set dhcp client [del] intfc <interface> [hostname <name>]",
1237   .function = dhcp_client_set_command_fn,
1238 };
1239 /* *INDENT-ON* */
1240
1241 static clib_error_t *
1242 dhcp_client_init (vlib_main_t * vm)
1243 {
1244   dhcp_client_main_t *dcm = &dhcp_client_main;
1245   vlib_node_t *ip4_lookup_node;
1246
1247   ip4_lookup_node = vlib_get_node_by_name (vm, (u8 *) "ip4-lookup");
1248
1249   /* Should never happen... */
1250   if (ip4_lookup_node == 0)
1251     return clib_error_return (0, "ip4-lookup node not found");
1252
1253   dcm->ip4_lookup_node_index = ip4_lookup_node->index;
1254   dcm->vlib_main = vm;
1255   dcm->vnet_main = vnet_get_main ();
1256   dcm->seed = (u32) clib_cpu_time_now ();
1257
1258   dhcp_logger = vlib_log_register_class ("dhcp", "client");
1259   DHCP_DBG ("plugin initialized");
1260
1261   return 0;
1262 }
1263
1264 VLIB_INIT_FUNCTION (dhcp_client_init);
1265
1266 /*
1267  * fd.io coding-style-patch-verification: ON
1268  *
1269  * Local Variables:
1270  * eval: (c-set-style "gnu")
1271  * End:
1272  */