Add PPPoE Plugin
[vpp.git] / src / plugins / pppoe / pppoe.c
1 /*
2  *------------------------------------------------------------------
3  * Copyright (c) 2017 Intel and/or its affiliates.
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at:
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *------------------------------------------------------------------
16  */
17 #include <stdint.h>
18 #include <net/if.h>
19 #include <sys/ioctl.h>
20 #include <inttypes.h>
21
22 #include <vlib/vlib.h>
23 #include <vlib/unix/unix.h>
24 #include <vnet/ethernet/ethernet.h>
25 #include <vnet/fib/fib_entry.h>
26 #include <vnet/fib/fib_table.h>
27 #include <vnet/dpo/dpo.h>
28 #include <vnet/plugin/plugin.h>
29 #include <vpp/app/version.h>
30 #include <vnet/ppp/packet.h>
31 #include <pppoe/pppoe.h>
32
33 #include <vppinfra/hash.h>
34 #include <vppinfra/bihash_template.c>
35
36 pppoe_main_t pppoe_main;
37
38 u8 *
39 format_pppoe_session (u8 * s, va_list * args)
40 {
41   pppoe_session_t *t = va_arg (*args, pppoe_session_t *);
42   pppoe_main_t *pem = &pppoe_main;
43
44   s = format (s, "[%d] sw-if-index %d client-ip %U session-id %d ",
45               t - pem->sessions, t->sw_if_index,
46               format_ip46_address, &t->client_ip, IP46_TYPE_ANY,
47               t->session_id);
48
49   s = format (s, "encap-if-index %d decap-fib-index %d\n",
50               t->encap_if_index, t->decap_fib_index);
51
52   s = format (s, "    local-mac %U  client-mac %U",
53               format_ethernet_address, t->local_mac,
54               format_ethernet_address, t->client_mac);
55
56   return s;
57 }
58
59 static u8 *
60 format_pppoe_name (u8 * s, va_list * args)
61 {
62   u32 dev_instance = va_arg (*args, u32);
63   return format (s, "pppoe_session%d", dev_instance);
64 }
65
66 static uword
67 dummy_interface_tx (vlib_main_t * vm,
68                     vlib_node_runtime_t * node, vlib_frame_t * frame)
69 {
70   clib_warning ("you shouldn't be here, leaking buffers...");
71   return frame->n_vectors;
72 }
73
74 static clib_error_t *
75 pppoe_interface_admin_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags)
76 {
77   u32 hw_flags = (flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP) ?
78     VNET_HW_INTERFACE_FLAG_LINK_UP : 0;
79   vnet_hw_interface_set_flags (vnm, hw_if_index, hw_flags);
80
81   return /* no error */ 0;
82 }
83
84 /* *INDENT-OFF* */
85 VNET_DEVICE_CLASS (pppoe_device_class,static) = {
86   .name = "PPPPOE",
87   .format_device_name = format_pppoe_name,
88   .format_tx_trace = format_pppoe_encap_trace,
89   .tx_function = dummy_interface_tx,
90   .admin_up_down_function = pppoe_interface_admin_up_down,
91 };
92 /* *INDENT-ON* */
93
94 static u8 *
95 format_pppoe_header_with_length (u8 * s, va_list * args)
96 {
97   u32 dev_instance = va_arg (*args, u32);
98   s = format (s, "unimplemented dev %u", dev_instance);
99   return s;
100 }
101
102 /* *INDENT-OFF* */
103 VNET_HW_INTERFACE_CLASS (pppoe_hw_class) =
104 {
105   .name = "PPPPOE",
106   .format_header = format_pppoe_header_with_length,
107   .build_rewrite = default_build_rewrite,
108   .flags = VNET_HW_INTERFACE_CLASS_FLAG_P2P,
109 };
110 /* *INDENT-ON* */
111
112 #define foreach_copy_field                      \
113 _(session_id)                                   \
114 _(encap_if_index)                               \
115 _(decap_fib_index)                              \
116 _(client_ip)
117
118 static void
119 eth_pppoe_rewrite (pppoe_session_t * t, bool is_ip6)
120 {
121   u8 *rw = 0;
122   int len = sizeof (pppoe_header_t) + sizeof (ethernet_header_t);
123
124   vec_validate_aligned (rw, len - 1, CLIB_CACHE_LINE_BYTES);
125
126   ethernet_header_t *eth_hdr = (ethernet_header_t *) rw;
127   clib_memcpy (eth_hdr->dst_address, t->client_mac, 6);
128   clib_memcpy (eth_hdr->src_address, t->local_mac, 6);
129   eth_hdr->type = clib_host_to_net_u16 (ETHERNET_TYPE_PPPOE_SESSION);
130
131   pppoe_header_t *pppoe = (pppoe_header_t *) (eth_hdr + 1);
132   pppoe->ver_type = PPPOE_VER_TYPE;
133   pppoe->code = 0;
134   pppoe->session_id = clib_host_to_net_u16 (t->session_id);
135   pppoe->length = 0;            /* To be filled in at run-time */
136
137   if (!is_ip6)
138     {
139       pppoe->ppp_proto = clib_host_to_net_u16 (PPP_PROTOCOL_ip4);
140     }
141   else
142     {
143       pppoe->ppp_proto = clib_host_to_net_u16 (PPP_PROTOCOL_ip6);
144     }
145
146   t->rewrite = rw;
147   _vec_len (t->rewrite) = len;
148
149   return;
150 }
151
152 static bool
153 pppoe_decap_next_is_valid (pppoe_main_t * pem, u32 is_ip6,
154                            u32 decap_fib_index)
155 {
156   vlib_main_t *vm = pem->vlib_main;
157   u32 input_idx = (!is_ip6) ? ip4_input_node.index : ip6_input_node.index;
158   vlib_node_runtime_t *r = vlib_node_get_runtime (vm, input_idx);
159
160   return decap_fib_index < r->n_next_nodes;
161 }
162
163 int vnet_pppoe_add_del_session
164   (vnet_pppoe_add_del_session_args_t * a, u32 * sw_if_indexp)
165 {
166   pppoe_main_t *pem = &pppoe_main;
167   pppoe_session_t *t = 0;
168   vnet_main_t *vnm = pem->vnet_main;
169   u32 hw_if_index = ~0;
170   u32 sw_if_index = ~0;
171   u32 is_ip6 = a->is_ip6;
172   pppoe_entry_key_t cached_key;
173   pppoe_entry_result_t cached_result;
174   u32 bucket;
175   pppoe_entry_key_t key;
176   pppoe_entry_result_t result;
177   vnet_hw_interface_t *hi;
178   vnet_sw_interface_t *si;
179   fib_prefix_t pfx;
180
181   cached_key.raw = ~0;
182   cached_result.raw = ~0;       /* warning be gone */
183   memset (&pfx, 0, sizeof (pfx));
184
185   if (!is_ip6)
186     {
187       pfx.fp_addr.ip4.as_u32 = a->client_ip.ip4.as_u32;
188       pfx.fp_len = 32;
189       pfx.fp_proto = FIB_PROTOCOL_IP4;
190     }
191   else
192     {
193       pfx.fp_addr.ip6.as_u64[0] = a->client_ip.ip6.as_u64[0];
194       pfx.fp_addr.ip6.as_u64[1] = a->client_ip.ip6.as_u64[1];
195       pfx.fp_len = 128;
196       pfx.fp_proto = FIB_PROTOCOL_IP6;
197     }
198
199   /* Get encap_if_index and local mac address */
200   pppoe_lookup_1 (&pem->session_table, &cached_key, &cached_result,
201                   a->client_mac, clib_host_to_net_u16 (a->session_id),
202                   &key, &bucket, &result);
203   a->encap_if_index = result.fields.sw_if_index;
204
205   if (a->encap_if_index == ~0)
206     return VNET_API_ERROR_INVALID_SW_IF_INDEX;
207
208   si = vnet_get_sw_interface (vnm, a->encap_if_index);
209   hi = vnet_get_hw_interface (vnm, si->hw_if_index);
210
211
212   if (a->is_add)
213     {
214       /* adding a session: session must not already exist */
215       if (result.fields.session_index != ~0)
216         return VNET_API_ERROR_TUNNEL_EXIST;
217
218       /*if not set explicitly, default to ip4 */
219       if (!pppoe_decap_next_is_valid (pem, is_ip6, a->decap_fib_index))
220         return VNET_API_ERROR_INVALID_DECAP_NEXT;
221
222       pool_get_aligned (pem->sessions, t, CLIB_CACHE_LINE_BYTES);
223       memset (t, 0, sizeof (*t));
224
225       clib_memcpy (t->local_mac, hi->hw_address, 6);
226
227       /* copy from arg structure */
228 #define _(x) t->x = a->x;
229       foreach_copy_field;
230 #undef _
231
232       clib_memcpy (t->client_mac, a->client_mac, 6);
233
234       eth_pppoe_rewrite (t, is_ip6);
235
236       /* update pppoe fib with session_index */
237       result.fields.session_index = t - pem->sessions;
238       pppoe_update_1 (&pem->session_table,
239                       a->client_mac, clib_host_to_net_u16 (a->session_id),
240                       &key, &bucket, &result);
241
242       vnet_hw_interface_t *hi;
243       if (vec_len (pem->free_pppoe_session_hw_if_indices) > 0)
244         {
245           vnet_interface_main_t *im = &vnm->interface_main;
246           hw_if_index = pem->free_pppoe_session_hw_if_indices
247             [vec_len (pem->free_pppoe_session_hw_if_indices) - 1];
248           _vec_len (pem->free_pppoe_session_hw_if_indices) -= 1;
249
250           hi = vnet_get_hw_interface (vnm, hw_if_index);
251           hi->dev_instance = t - pem->sessions;
252           hi->hw_instance = hi->dev_instance;
253
254           /* clear old stats of freed session before reuse */
255           sw_if_index = hi->sw_if_index;
256           vnet_interface_counter_lock (im);
257           vlib_zero_combined_counter
258             (&im->combined_sw_if_counters[VNET_INTERFACE_COUNTER_TX],
259              sw_if_index);
260           vlib_zero_combined_counter (&im->combined_sw_if_counters
261                                       [VNET_INTERFACE_COUNTER_RX],
262                                       sw_if_index);
263           vlib_zero_simple_counter (&im->sw_if_counters
264                                     [VNET_INTERFACE_COUNTER_DROP],
265                                     sw_if_index);
266           vnet_interface_counter_unlock (im);
267         }
268       else
269         {
270           hw_if_index = vnet_register_interface
271             (vnm, pppoe_device_class.index, t - pem->sessions,
272              pppoe_hw_class.index, t - pem->sessions);
273           hi = vnet_get_hw_interface (vnm, hw_if_index);
274         }
275
276       t->hw_if_index = hw_if_index;
277       t->sw_if_index = sw_if_index = hi->sw_if_index;
278
279       vec_validate_init_empty (pem->session_index_by_sw_if_index, sw_if_index,
280                                ~0);
281       pem->session_index_by_sw_if_index[sw_if_index] = t - pem->sessions;
282
283       vnet_sw_interface_t *si = vnet_get_sw_interface (vnm, sw_if_index);
284       si->flags &= ~VNET_SW_INTERFACE_FLAG_HIDDEN;
285       vnet_sw_interface_set_flags (vnm, sw_if_index,
286                                    VNET_SW_INTERFACE_FLAG_ADMIN_UP);
287
288       /* Set pppoe session output node */
289       hi->output_node_index = pppoe_encap_node.index;
290
291       /* add reverse route for client ip */
292       fib_table_entry_path_add (a->decap_fib_index, &pfx,
293                                 FIB_SOURCE_PLUGIN_HI, FIB_ENTRY_FLAG_NONE,
294                                 pfx.fp_proto, &pfx.fp_addr, sw_if_index, ~0,
295                                 1, NULL, FIB_ROUTE_PATH_FLAG_NONE);
296
297     }
298   else
299     {
300       /* deleting a session: session must exist */
301       if (result.fields.session_index == ~0)
302         return VNET_API_ERROR_NO_SUCH_ENTRY;
303
304       t = pool_elt_at_index (pem->sessions, result.fields.session_index);
305       sw_if_index = t->sw_if_index;
306
307       vnet_sw_interface_set_flags (vnm, t->sw_if_index, 0 /* down */ );
308       vnet_sw_interface_t *si = vnet_get_sw_interface (vnm, t->sw_if_index);
309       si->flags |= VNET_SW_INTERFACE_FLAG_HIDDEN;
310
311       vec_add1 (pem->free_pppoe_session_hw_if_indices, t->hw_if_index);
312
313       pem->session_index_by_sw_if_index[t->sw_if_index] = ~0;
314
315       /* update pppoe fib with session_inde=~0x */
316       result.fields.session_index = ~0;
317       pppoe_update_1 (&pem->session_table,
318                       a->client_mac, clib_host_to_net_u16 (a->session_id),
319                       &key, &bucket, &result);
320
321
322       /* delete reverse route for client ip */
323       fib_table_entry_path_remove (a->decap_fib_index, &pfx,
324                                    FIB_SOURCE_PLUGIN_HI,
325                                    pfx.fp_proto,
326                                    &pfx.fp_addr,
327                                    sw_if_index, ~0, 1,
328                                    FIB_ROUTE_PATH_FLAG_NONE);
329
330       vec_free (t->rewrite);
331       pool_put (pem->sessions, t);
332     }
333
334   if (sw_if_indexp)
335     *sw_if_indexp = sw_if_index;
336
337   return 0;
338 }
339
340 static clib_error_t *
341 pppoe_add_del_session_command_fn (vlib_main_t * vm,
342                                   unformat_input_t * input,
343                                   vlib_cli_command_t * cmd)
344 {
345   unformat_input_t _line_input, *line_input = &_line_input;
346   u16 session_id = 0;
347   ip46_address_t client_ip;
348   u8 is_add = 1;
349   u8 client_ip_set = 0;
350   u8 ipv4_set = 0;
351   u8 ipv6_set = 0;
352   u32 encap_if_index = 0;
353   u32 decap_fib_index = 0;
354   u8 client_mac[6] = { 0 };
355   u8 client_mac_set = 0;
356   int rv;
357   u32 tmp;
358   vnet_pppoe_add_del_session_args_t _a, *a = &_a;
359   u32 session_sw_if_index;
360   clib_error_t *error = NULL;
361
362   /* Cant "universally zero init" (={0}) due to GCC bug 53119 */
363   memset (&client_ip, 0, sizeof client_ip);
364
365   /* Get a line of input. */
366   if (!unformat_user (input, unformat_line_input, line_input))
367     return 0;
368
369   while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
370     {
371       if (unformat (line_input, "del"))
372         {
373           is_add = 0;
374         }
375       else if (unformat (line_input, "session-id %d", &session_id))
376         ;
377       else if (unformat (line_input, "client-ip %U",
378                          unformat_ip4_address, &client_ip.ip4))
379         {
380           client_ip_set = 1;
381           ipv4_set = 1;
382         }
383       else if (unformat (line_input, "client-ip %U",
384                          unformat_ip6_address, &client_ip.ip6))
385         {
386           client_ip_set = 1;
387           ipv6_set = 1;
388         }
389       else if (unformat (line_input, "decap-vrf-id %d", &tmp))
390         {
391           if (ipv6_set)
392             decap_fib_index = fib_table_find (FIB_PROTOCOL_IP6, tmp);
393           else
394             decap_fib_index = fib_table_find (FIB_PROTOCOL_IP4, tmp);
395
396           if (decap_fib_index == ~0)
397             {
398               error =
399                 clib_error_return (0, "nonexistent decap fib id %d", tmp);
400               goto done;
401             }
402         }
403       else
404         if (unformat
405             (line_input, "client-mac %U", unformat_ethernet_address,
406              client_mac))
407         client_mac_set = 1;
408       else
409         {
410           error = clib_error_return (0, "parse error: '%U'",
411                                      format_unformat_error, line_input);
412           goto done;
413         }
414     }
415
416   if (client_ip_set == 0)
417     {
418       error =
419         clib_error_return (0, "session client ip address not specified");
420       goto done;
421     }
422
423   if (ipv4_set && ipv6_set)
424     {
425       error = clib_error_return (0, "both IPv4 and IPv6 addresses specified");
426       goto done;
427     }
428
429   if (client_mac_set == 0)
430     {
431       error = clib_error_return (0, "session client mac not specified");
432       goto done;
433     }
434
435   memset (a, 0, sizeof (*a));
436
437   a->is_add = is_add;
438   a->is_ip6 = ipv6_set;
439
440 #define _(x) a->x = x;
441   foreach_copy_field;
442 #undef _
443
444   clib_memcpy (a->client_mac, client_mac, 6);
445
446   rv = vnet_pppoe_add_del_session (a, &session_sw_if_index);
447
448   switch (rv)
449     {
450     case 0:
451       if (is_add)
452         vlib_cli_output (vm, "%U\n", format_vnet_sw_if_index_name,
453                          vnet_get_main (), session_sw_if_index);
454       break;
455
456     case VNET_API_ERROR_TUNNEL_EXIST:
457       error = clib_error_return (0, "session already exists...");
458       goto done;
459
460     case VNET_API_ERROR_NO_SUCH_ENTRY:
461       error = clib_error_return (0, "session does not exist...");
462       goto done;
463
464     default:
465       error = clib_error_return
466         (0, "vnet_pppoe_add_del_session returned %d", rv);
467       goto done;
468     }
469
470 done:
471   unformat_free (line_input);
472
473   return error;
474 }
475
476 /*?
477  * Add or delete a PPPPOE Session.
478  *
479  * @cliexpar
480  * Example of how to create a PPPPOE Session:
481  * @cliexcmd{create pppoe session client-ip 10.0.3.1 session-id 13
482  *             client-mac 00:01:02:03:04:05 }
483  * Example of how to delete a PPPPOE Session:
484  * @cliexcmd{create pppoe session client-ip 10.0.3.1 session-id 13
485  *             client-mac 00:01:02:03:04:05 del }
486  ?*/
487 /* *INDENT-OFF* */
488 VLIB_CLI_COMMAND (create_pppoe_session_command, static) = {
489   .path = "create pppoe session",
490   .short_help =
491   "create pppoe session client-ip <client-ip> session-id <nn>"
492   " client-mac <client-mac> [decap-vrf-id <nn>] [del]",
493   .function = pppoe_add_del_session_command_fn,
494 };
495 /* *INDENT-ON* */
496
497 /* *INDENT-OFF* */
498 static clib_error_t *
499 show_pppoe_session_command_fn (vlib_main_t * vm,
500                                unformat_input_t * input,
501                                vlib_cli_command_t * cmd)
502 {
503   pppoe_main_t *pem = &pppoe_main;
504   pppoe_session_t *t;
505
506   if (pool_elts (pem->sessions) == 0)
507     vlib_cli_output (vm, "No pppoe sessions configured...");
508
509   pool_foreach (t, pem->sessions,
510                 ({
511                     vlib_cli_output (vm, "%U",format_pppoe_session, t);
512                 }));
513
514   return 0;
515 }
516 /* *INDENT-ON* */
517
518 /*?
519  * Display all the PPPPOE Session entries.
520  *
521  * @cliexpar
522  * Example of how to display the PPPPOE Session entries:
523  * @cliexstart{show pppoe session}
524  * [0] client-ip 10.0.3.1 session_id 13 encap-if-index 0 decap-vrf-id 13 sw_if_index 5
525  *     local-mac a0:b0:c0:d0:e0:f0 client-mac 00:01:02:03:04:05
526  * @cliexend
527  ?*/
528 /* *INDENT-OFF* */
529 VLIB_CLI_COMMAND (show_pppoe_session_command, static) = {
530     .path = "show pppoe session",
531     .short_help = "show pppoe session",
532     .function = show_pppoe_session_command_fn,
533 };
534 /* *INDENT-ON* */
535
536 /** Display the contents of the PPPoE Fib. */
537 static clib_error_t *
538 show_pppoe_fib_command_fn (vlib_main_t * vm,
539                            unformat_input_t * input, vlib_cli_command_t * cmd)
540 {
541   pppoe_main_t *pem = &pppoe_main;
542   BVT (clib_bihash) * h = &pem->session_table;
543   BVT (clib_bihash_bucket) * b;
544   BVT (clib_bihash_value) * v;
545   pppoe_entry_key_t key;
546   pppoe_entry_result_t result;
547   u32 first_entry = 1;
548   u64 total_entries = 0;
549   int i, j, k;
550   u8 *s = 0;
551
552   for (i = 0; i < h->nbuckets; i++)
553     {
554       b = &h->buckets[i];
555       if (b->offset == 0)
556         continue;
557       v = BV (clib_bihash_get_value) (h, b->offset);
558       for (j = 0; j < (1 << b->log2_pages); j++)
559         {
560           for (k = 0; k < BIHASH_KVP_PER_PAGE; k++)
561             {
562               if (v->kvp[k].key == ~0ULL && v->kvp[k].value == ~0ULL)
563                 continue;
564
565               if (first_entry)
566                 {
567                   first_entry = 0;
568                   vlib_cli_output (vm,
569                                    "%=19s%=12s%=13s%=14s",
570                                    "Mac-Address", "session_id", "sw_if_index",
571                                    "session_index");
572                 }
573
574               key.raw = v->kvp[k].key;
575               result.raw = v->kvp[k].value;
576
577
578               vlib_cli_output (vm,
579                                "%=19U%=12d%=13d%=14d",
580                                format_ethernet_address, key.fields.mac,
581                                clib_net_to_host_u16 (key.fields.session_id),
582                                result.fields.sw_if_index == ~0
583                                ? -1 : result.fields.sw_if_index,
584                                result.fields.session_index == ~0
585                                ? -1 : result.fields.session_index);
586               vec_reset_length (s);
587               total_entries++;
588             }
589           v++;
590         }
591     }
592
593   if (total_entries == 0)
594     vlib_cli_output (vm, "no pppoe fib entries");
595   else
596     vlib_cli_output (vm, "%lld pppoe fib entries", total_entries);
597
598   vec_free (s);
599   return 0;
600 }
601
602 /*?
603  * This command dispays the MAC Address entries of the PPPoE FIB table.
604  * Output can be filtered to just get the number of MAC Addresses or display
605  * each MAC Address.
606  *
607  * @cliexpar
608  * Example of how to display the number of MAC Address entries in the PPPoE
609  * FIB table:
610  * @cliexstart{show pppoe fib}
611  *     Mac Address      session_id      Interface           sw_if_index  session_index
612  *  52:54:00:53:18:33     1          GigabitEthernet0/8/0        2          0
613  *  52:54:00:53:18:55     2          GigabitEthernet0/8/1        3          1
614  * @cliexend
615 ?*/
616 /* *INDENT-OFF* */
617 VLIB_CLI_COMMAND (show_pppoe_fib_command, static) = {
618     .path = "show pppoe fib",
619     .short_help = "show pppoe fib",
620     .function = show_pppoe_fib_command_fn,
621 };
622 /* *INDENT-ON* */
623
624 clib_error_t *
625 pppoe_init (vlib_main_t * vm)
626 {
627   pppoe_main_t *pem = &pppoe_main;
628
629   pem->vnet_main = vnet_get_main ();
630   pem->vlib_main = vm;
631
632   /* Create the hash table  */
633   BV (clib_bihash_init) (&pem->session_table, "pppoe session table",
634                          PPPOE_NUM_BUCKETS, PPPOE_MEMORY_SIZE);
635
636   ethernet_register_input_type (vm, ETHERNET_TYPE_PPPOE_SESSION,
637                                 pppoe_input_node.index);
638
639   ethernet_register_input_type (vm, ETHERNET_TYPE_PPPOE_DISCOVERY,
640                                 pppoe_tap_dispatch_node.index);
641
642   return 0;
643 }
644
645 VLIB_INIT_FUNCTION (pppoe_init);
646
647 /* *INDENT-OFF* */
648 VLIB_PLUGIN_REGISTER () = {
649     .version = VPP_BUILD_VER,
650     .description = "PPPoE",
651 };
652 /* *INDENT-ON* */
653
654 /*
655  * fd.io coding-style-patch-verification: ON
656  *
657  * Local Variables:
658  * eval: (c-set-style "gnu")
659  * End:
660  */