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