mactime: remove api boilerplate
[vpp.git] / src / plugins / mactime / mactime.c
1 /*
2  * mactime.c - time-based src mac address filtration
3  *
4  * Copyright (c) 2018 Cisco and/or its affiliates.
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at:
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #include <vnet/vnet.h>
19 #include <vnet/plugin/plugin.h>
20 #include <mactime/mactime.h>
21
22 #include <vlibapi/api.h>
23 #include <vlibmemory/api.h>
24 #include <vpp/app/version.h>
25
26 /* define message IDs */
27 #include <mactime/mactime.api_enum.h>
28 #include <mactime/mactime.api_types.h>
29
30 #define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__)
31
32 #define REPLY_MSG_ID_BASE mm->msg_id_base
33 #include <vlibapi/api_helper_macros.h>
34
35 mactime_main_t mactime_main;
36
37 /** \file time-base src-mac filter device-input feature arc implementation
38  */
39
40 static void
41 feature_init (mactime_main_t * mm)
42 {
43   if (mm->feature_initialized == 0)
44     {
45       /* Create the lookup table */
46       clib_bihash_init_8_8 (&mm->lookup_table, "mactime lookup table",
47                             mm->lookup_table_num_buckets,
48                             mm->lookup_table_memory_size);
49       clib_timebase_init (&mm->timebase, mm->timezone_offset,
50                           CLIB_TIMEBASE_DAYLIGHT_USA);
51       mm->allow_counters.name = "allow";
52       mm->allow_counters.stat_segment_name = "/mactime/allow";
53       mm->drop_counters.name = "drop";
54       mm->drop_counters.stat_segment_name = "/mactime/drop";
55       mm->feature_initialized = 1;
56     }
57 }
58
59 /** Action function shared between message handler and debug CLI
60 */
61 int
62 mactime_enable_disable (mactime_main_t * mm, u32 sw_if_index,
63                         int enable_disable)
64 {
65   vnet_sw_interface_t *sw;
66   int rv = 0;
67
68   feature_init (mm);
69
70   /* Utterly wrong? */
71   if (pool_is_free_index (mm->vnet_main->interface_main.sw_interfaces,
72                           sw_if_index))
73     return VNET_API_ERROR_INVALID_SW_IF_INDEX;
74
75   /* Not a physical port? */
76   sw = vnet_get_sw_interface (mm->vnet_main, sw_if_index);
77   if (sw->type != VNET_SW_INTERFACE_TYPE_HARDWARE)
78     return VNET_API_ERROR_INVALID_SW_IF_INDEX;
79
80   vnet_feature_enable_disable ("device-input", "mactime",
81                                sw_if_index, enable_disable, 0, 0);
82   vnet_feature_enable_disable ("interface-output", "mactime-tx",
83                                sw_if_index, enable_disable, 0, 0);
84   return rv;
85 }
86
87 static clib_error_t *
88 mactime_enable_disable_command_fn (vlib_main_t * vm,
89                                    unformat_input_t * input,
90                                    vlib_cli_command_t * cmd)
91 {
92   mactime_main_t *mm = &mactime_main;
93   u32 sw_if_index = ~0;
94   int enable_disable = 1;
95
96   int rv;
97
98   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
99     {
100       if (unformat (input, "disable"))
101         enable_disable = 0;
102       else if (unformat (input, "%U", unformat_vnet_sw_interface,
103                          mm->vnet_main, &sw_if_index))
104         ;
105       else if (unformat (input, "sw_if_index %d", &sw_if_index))
106         ;
107       else
108         break;
109     }
110
111   if (sw_if_index == ~0)
112     return clib_error_return (0, "Please specify an interface...");
113
114   rv = mactime_enable_disable (mm, sw_if_index, enable_disable);
115
116   switch (rv)
117     {
118     case 0:
119       break;
120
121     case VNET_API_ERROR_INVALID_SW_IF_INDEX:
122       return clib_error_return
123         (0, "Invalid interface, only works on physical ports");
124       break;
125
126     default:
127       return clib_error_return (0, "mactime_enable_disable returned %d", rv);
128     }
129   return 0;
130 }
131
132 /* *INDENT-OFF* */
133 VLIB_CLI_COMMAND (mactime_enable_disable_command, static) =
134 {
135   .path = "mactime enable-disable",
136   .short_help =
137   "mactime enable-disable <interface-name> [disable]",
138   .function = mactime_enable_disable_command_fn,
139 };
140 /* *INDENT-ON* */
141
142
143 /** Enable / disable time-base src mac filtration on an interface
144  */
145
146 static void vl_api_mactime_enable_disable_t_handler
147   (vl_api_mactime_enable_disable_t * mp)
148 {
149   vl_api_mactime_enable_disable_reply_t *rmp;
150   mactime_main_t *mm = &mactime_main;
151   int rv;
152
153   VALIDATE_SW_IF_INDEX (mp);
154
155   rv = mactime_enable_disable (mm, ntohl (mp->sw_if_index),
156                                (int) (mp->enable_disable));
157   BAD_SW_IF_INDEX_LABEL;
158   REPLY_MACRO (VL_API_MACTIME_ENABLE_DISABLE_REPLY);
159 }
160
161 /** Create a lookup table entry for the indicated mac address
162  */
163 void
164 mactime_send_create_entry_message (u8 * mac_address)
165 {
166   mactime_main_t *mm = &mactime_main;
167   api_main_t *am;
168   vl_shmem_hdr_t *shmem_hdr;
169   u8 *name;
170   vl_api_mactime_add_del_range_t *mp;
171
172   am = &api_main;
173   shmem_hdr = am->shmem_hdr;
174   mp = vl_msg_api_alloc_as_if_client (sizeof (*mp));
175   clib_memset (mp, 0, sizeof (*mp));
176   mp->_vl_msg_id = ntohs (VL_API_MACTIME_ADD_DEL_RANGE + mm->msg_id_base);
177   name = format (0, "mac-%U", format_mac_address, mac_address);
178
179   memcpy (mp->device_name, name, vec_len (name));
180   memcpy (mp->mac_address, mac_address, sizeof (mp->mac_address));
181   /* $$$ config: create allow / drop / range */
182   mp->allow = 1;
183   mp->is_add = 1;
184   vl_msg_api_send_shmem (shmem_hdr->vl_input_queue, (u8 *) & mp);
185 }
186
187 /** Add or delete static / dynamic accept/drop configuration for a src mac
188  */
189
190 static void vl_api_mactime_add_del_range_t_handler
191   (vl_api_mactime_add_del_range_t * mp)
192 {
193   mactime_main_t *mm = &mactime_main;
194   vl_api_mactime_add_del_range_reply_t *rmp;
195   mactime_device_t *dp;
196   clib_bihash_kv_8_8_t kv;
197   int found = 1;
198   clib_bihash_8_8_t *lut = &mm->lookup_table;
199   u64 data_quota;
200   int i, rv = 0;
201
202   feature_init (mm);
203
204   data_quota = clib_net_to_host_u64 (mp->data_quota);
205
206   clib_memset (&kv, 0, sizeof (kv));
207   memcpy (&kv.key, mp->mac_address, sizeof (mp->mac_address));
208
209   /* See if we have a lookup table entry for this src mac address */
210   if (clib_bihash_search_8_8 (lut, &kv, &kv) < 0)
211     found = 0;
212
213   /* Add an entry? */
214   if (mp->is_add)
215     {
216       /* Create the device entry? */
217       if (found == 0)
218         {
219           pool_get (mm->devices, dp);
220           clib_memset (dp, 0, sizeof (*dp));
221           vlib_validate_combined_counter (&mm->allow_counters,
222                                           dp - mm->devices);
223           vlib_zero_combined_counter (&mm->allow_counters, dp - mm->devices);
224           vlib_validate_combined_counter (&mm->drop_counters,
225                                           dp - mm->devices);
226           vlib_zero_combined_counter (&mm->drop_counters, dp - mm->devices);
227           mp->device_name[ARRAY_LEN (mp->device_name) - 1] = 0;
228           dp->device_name = format (0, "%s%c", mp->device_name, 0);
229           memcpy (dp->mac_address, mp->mac_address, sizeof (mp->mac_address));
230           for (i = 0; i < clib_net_to_host_u32 (mp->count); i++)
231             {
232               clib_timebase_range_t _r, *r = &_r;
233               r->start = mp->ranges[i].start;
234               r->end = mp->ranges[i].end;
235               vec_add1 (dp->ranges, r[0]);
236             }
237           /* If we found some time ranges */
238           if (i)
239             {
240               /* Set allow/drop based on msg flags */
241               if (mp->drop)
242                 dp->flags = MACTIME_DEVICE_FLAG_DYNAMIC_DROP;
243               if (mp->allow)
244                 dp->flags = MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW;
245               if (mp->allow_quota)
246                 dp->flags = MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW_QUOTA;
247             }
248           else
249             {
250               /* no ranges, it's a static allow/drop */
251               if (mp->drop)
252                 dp->flags = MACTIME_DEVICE_FLAG_STATIC_DROP;
253               if (mp->allow)
254                 dp->flags = MACTIME_DEVICE_FLAG_STATIC_ALLOW;
255             }
256           if (mp->no_udp_10001)
257             dp->flags |= MACTIME_DEVICE_FLAG_DROP_UDP_10001;
258
259           dp->data_quota = data_quota;
260
261           /* Add the hash table entry */
262           kv.value = dp - mm->devices;
263           clib_bihash_add_del_8_8 (lut, &kv, 1 /* is_add */ );
264         }
265       else                      /* add more ranges, flags, etc. */
266         {
267           dp = pool_elt_at_index (mm->devices, kv.value);
268
269           for (i = 0; i < clib_net_to_host_u32 (mp->count); i++)
270             {
271               clib_timebase_range_t _r, *r = &_r;
272               r->start = mp->ranges[i].start;
273               r->end = mp->ranges[i].end;
274               vec_add1 (dp->ranges, r[0]);
275             }
276
277           if (vec_len (dp->ranges))
278             {
279               /* Set allow/drop based on msg flags */
280               if (mp->drop)
281                 dp->flags = MACTIME_DEVICE_FLAG_DYNAMIC_DROP;
282               if (mp->allow)
283                 dp->flags = MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW;
284               if (mp->allow_quota)
285                 dp->flags = MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW_QUOTA;
286             }
287           else
288             {
289               /* no ranges, it's a static allow/drop */
290               if (mp->drop)
291                 dp->flags = MACTIME_DEVICE_FLAG_STATIC_DROP;
292               if (mp->allow)
293                 dp->flags = MACTIME_DEVICE_FLAG_STATIC_ALLOW;
294             }
295           if (mp->no_udp_10001)
296             dp->flags |= MACTIME_DEVICE_FLAG_DROP_UDP_10001;
297
298           dp->data_quota = data_quota;
299         }
300     }
301   else                          /* delete case */
302     {
303       if (found == 0)
304         {
305           rv = VNET_API_ERROR_NO_SUCH_ENTRY;
306           goto reply;
307         }
308
309       /* find the device entry */
310       dp = pool_elt_at_index (mm->devices, kv.value);
311
312       /* Remove it from the lookup table */
313       clib_bihash_add_del_8_8 (lut, &kv, 0 /* is_add */ );
314       vec_free (dp->ranges);
315       pool_put (mm->devices, dp);
316     }
317
318 reply:
319   REPLY_MACRO (VL_API_MACTIME_ADD_DEL_RANGE_REPLY);
320 }
321
322 #include <mactime/mactime.api.c>
323 static clib_error_t *
324 mactime_init (vlib_main_t * vm)
325 {
326   mactime_main_t *mm = &mactime_main;
327
328   mm->vlib_main = vm;
329   mm->vnet_main = vnet_get_main ();
330
331   /* Ask for a correctly-sized block of API message decode slots */
332   mm->msg_id_base = setup_message_id_table ();
333
334   mm->lookup_table_num_buckets = MACTIME_NUM_BUCKETS;
335   mm->lookup_table_memory_size = MACTIME_MEMORY_SIZE;
336   mm->timezone_offset = -5;     /* US EST / EDT */
337   return 0;
338 }
339
340 VLIB_INIT_FUNCTION (mactime_init);
341
342 static clib_error_t *
343 mactime_config (vlib_main_t * vm, unformat_input_t * input)
344 {
345   mactime_main_t *mm = &mactime_main;
346
347   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
348     {
349       if (unformat (input, "lookup-table-buckets %u",
350                     &mm->lookup_table_num_buckets))
351         ;
352       else if (unformat (input, "lookup-table-memory %U",
353                          unformat_memory_size, &mm->lookup_table_memory_size))
354         ;
355       else if (unformat (input, "timezone_offset %d", &mm->timezone_offset))
356         ;
357       else
358         {
359           return clib_error_return (0, "unknown input '%U'",
360                                     format_unformat_error, input);
361         }
362     }
363   return 0;
364 }
365
366 VLIB_CONFIG_FUNCTION (mactime_config, "mactime");
367
368 /* *INDENT-OFF* */
369 VNET_FEATURE_INIT (mactime, static) =
370 {
371   .arc_name = "device-input",
372   .node_name = "mactime",
373   .runs_before = VNET_FEATURES ("ethernet-input"),
374 };
375 /* *INDENT-ON */
376
377 /* *INDENT-OFF* */
378 VNET_FEATURE_INIT (mactime_tx, static) =
379 {
380   .arc_name = "interface-output",
381   .node_name = "mactime-tx",
382   .runs_before = VNET_FEATURES ("interface-tx"),
383 };
384 /* *INDENT-ON */
385
386 /* *INDENT-OFF* */
387 VLIB_PLUGIN_REGISTER () =
388 {
389   .version = VPP_BUILD_VER,
390   .description = "Time-based MAC Source Address Filter",
391 };
392 /* *INDENT-ON* */
393
394 u8 *
395 format_bytes_with_width (u8 * s, va_list * va)
396 {
397   uword nbytes = va_arg (*va, u64);
398   int width = va_arg (*va, int);
399   f64 nbytes_f64;
400   u8 *fmt;
401   char *suffix = "";
402
403   if (width > 0)
404     fmt = format (0, "%%%d.3f%%s%c", width, 0);
405   else
406     fmt = format (0, "%%.3f%%s%c", 0);
407
408   if (nbytes > (1024ULL * 1024ULL * 1024ULL))
409     {
410       nbytes_f64 = ((f64) nbytes) / (1024.0 * 1024.0 * 1024.0);
411       suffix = "G";
412     }
413   else if (nbytes > (1024ULL * 1024ULL))
414     {
415       nbytes_f64 = ((f64) nbytes) / (1024.0 * 1024.0);
416       suffix = "M";
417     }
418   else if (nbytes > 1024ULL)
419     {
420       nbytes_f64 = ((f64) nbytes) / (1024.0);
421       suffix = "K";
422     }
423   else
424     {
425       nbytes_f64 = (f64) nbytes;
426       suffix = "B";
427     }
428
429   s = format (s, (char *) fmt, nbytes_f64, suffix);
430   vec_free (fmt);
431   return s;
432 }
433
434 static clib_error_t *
435 show_mactime_command_fn (vlib_main_t * vm,
436                          unformat_input_t * input, vlib_cli_command_t * cmd)
437 {
438   mactime_main_t *mm = &mactime_main;
439   mactime_device_t *dp;
440   u8 *macstring = 0;
441   char *status_string;
442   u32 *pool_indices = 0;
443   int verbose = 0;
444   int current_status = 99;
445   int i, j;
446   f64 now;
447   vlib_counter_t allow, drop;
448   ethernet_arp_ip4_entry_t *n, *pool;
449
450   vec_reset_length (mm->arp_cache_copy);
451   pool = ip4_neighbors_pool ();
452
453   /* *INDENT-OFF* */
454   pool_foreach (n, pool,
455   ({
456     vec_add1 (mm->arp_cache_copy, n[0]);
457   }));
458   /* *INDENT-ON* */
459
460   now = clib_timebase_now (&mm->timebase);
461
462   if (PREDICT_FALSE ((now - mm->sunday_midnight) > 86400.0 * 7.0))
463     mm->sunday_midnight = clib_timebase_find_sunday_midnight (now);
464
465   if (unformat (input, "verbose %d", &verbose))
466     ;
467
468   if (unformat (input, "verbose"))
469     verbose = 1;
470
471   if (verbose)
472     vlib_cli_output (vm, "Time now: %U", format_clib_timebase_time, now);
473
474   /* *INDENT-OFF* */
475   pool_foreach (dp, mm->devices,
476   ({
477     vec_add1 (pool_indices, dp - mm->devices);
478   }));
479   /* *INDENT-ON* */
480
481   vlib_cli_output (vm, "%-15s %18s %14s %10s %11s %13s",
482                    "Device Name", "Addresses", "Status",
483                    "AllowPkt", "AllowByte", "DropPkt");
484
485   for (i = 0; i < vec_len (pool_indices); i++)
486     {
487       dp = pool_elt_at_index (mm->devices, pool_indices[i]);
488
489       /* Check dynamic ranges */
490       for (j = 0; j < vec_len (dp->ranges); j++)
491         {
492           clib_timebase_range_t *r = dp->ranges + j;
493           f64 start0, end0;
494
495           start0 = r->start + mm->sunday_midnight;
496           end0 = r->end + mm->sunday_midnight;
497           if (verbose > 1)
498             vlib_cli_output (vm, "  Range %d: %U - %U", j,
499                              format_clib_timebase_time, start0,
500                              format_clib_timebase_time, end0);
501
502           if (now >= start0 && now <= end0)
503             {
504               if (dp->flags & MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW)
505                 current_status = 3;
506               else if (dp->flags & MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW_QUOTA)
507                 current_status = 5;
508               else
509                 current_status = 2;
510               if (verbose)
511                 {
512                   vlib_cli_output (vm, "  Time in range %d:", j);
513                   vlib_cli_output (vm, "     %U - %U",
514                                    format_clib_timebase_time, start0,
515                                    format_clib_timebase_time, end0);
516                 }
517               goto print;
518             }
519         }
520       if (verbose && j)
521         vlib_cli_output (vm, "  No range match.");
522       if (dp->flags & MACTIME_DEVICE_FLAG_STATIC_DROP)
523         current_status = 0;
524       if (dp->flags & MACTIME_DEVICE_FLAG_STATIC_ALLOW)
525         current_status = 1;
526       if (dp->flags & MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW)
527         current_status = 2;
528       if (dp->flags & MACTIME_DEVICE_FLAG_DYNAMIC_DROP)
529         current_status = 3;
530       if (dp->flags & MACTIME_DEVICE_FLAG_DYNAMIC_ALLOW_QUOTA)
531         current_status = 4;
532
533     print:
534       vec_reset_length (macstring);
535       macstring = format (0, "%U", format_mac_address, dp->mac_address);
536       switch (current_status)
537         {
538         case 0:
539           status_string = "static drop";
540           break;
541         case 1:
542           status_string = "static allow";
543           break;
544         case 2:
545           status_string = "dynamic drop";
546           break;
547         case 3:
548           status_string = "dynamic allow";
549           break;
550         case 4:
551           status_string = "d-quota inact";
552           break;
553         case 5:
554           status_string = "d-quota activ";
555           break;
556         default:
557           status_string = "code bug!";
558           break;
559         }
560       vlib_get_combined_counter (&mm->allow_counters, dp - mm->devices,
561                                  &allow);
562       vlib_get_combined_counter (&mm->drop_counters, dp - mm->devices, &drop);
563       vlib_cli_output (vm, "%-15s %18s %14s %10lld %U %13lld",
564                        dp->device_name, macstring, status_string,
565                        allow.packets, format_bytes_with_width, allow.bytes,
566                        10, drop.packets);
567       if (dp->data_quota > 0)
568         vlib_cli_output (vm, "%-54s %s%U %s%U", " ", "Quota ",
569                          format_bytes_with_width, dp->data_quota, 10,
570                          "Use ", format_bytes_with_width,
571                          dp->data_used_in_range, 8);
572       /* This is really only good for small N... */
573       for (j = 0; j < vec_len (mm->arp_cache_copy); j++)
574         {
575           n = mm->arp_cache_copy + j;
576           if (!memcmp (dp->mac_address, n->mac.bytes, sizeof (n->mac)))
577             {
578               vlib_cli_output (vm, "%17s%U", " ", format_ip4_address,
579                                &n->ip4_address);
580             }
581         }
582     }
583   vec_free (macstring);
584   vec_free (pool_indices);
585
586   return 0;
587 }
588
589 /* *INDENT-OFF* */
590 VLIB_CLI_COMMAND (show_mactime_command, static) =
591 {
592   .path = "show mactime",
593   .short_help = "show mactime [verbose]",
594   .function = show_mactime_command_fn,
595 };
596 /* *INDENT-ON* */
597
598 static clib_error_t *
599 clear_mactime_command_fn (vlib_main_t * vm,
600                           unformat_input_t * input, vlib_cli_command_t * cmd)
601 {
602   mactime_main_t *mm = &mactime_main;
603
604   if (mm->feature_initialized == 0)
605     return clib_error_return (0, "feature not enabled");
606
607   vlib_clear_combined_counters (&mm->allow_counters);
608   vlib_clear_combined_counters (&mm->drop_counters);
609   vlib_cli_output (vm, "Mactime counters cleared...");
610   return 0;
611 }
612
613 /* *INDENT-OFF* */
614 VLIB_CLI_COMMAND (clear_mactime_command, static) =
615 {
616   .path = "clear mactime",
617   .short_help = "clear mactime counters",
618   .function = clear_mactime_command_fn,
619 };
620 /* *INDENT-ON* */
621
622
623
624 /*
625  * fd.io coding-style-patch-verification: ON
626  *
627  * Local Variables:
628  * eval: (c-set-style "gnu")
629  * End:
630  */