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