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