Initial commit of vpp code.
[vpp.git] / vnet / vnet / l2 / l2_learn.c
1 /*
2  * l2_learn.c : layer 2 learning using l2fib
3  *
4  * Copyright (c) 2013 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 <vlib/vlib.h>
19 #include <vnet/vnet.h>
20 #include <vnet/pg/pg.h>
21 #include <vnet/ethernet/ethernet.h>
22 #include <vlib/cli.h>
23
24 #include <vnet/l2/l2_input.h>
25 #include <vnet/l2/feat_bitmap.h>
26 #include <vnet/l2/l2_fib.h>
27 #include <vnet/l2/l2_learn.h>
28
29 #include <vppinfra/error.h>
30 #include <vppinfra/hash.h>
31
32 /*
33  * Ethernet bridge learning
34  *
35  * Populate the mac table with entries mapping the packet's source mac + bridge
36  * domain ID to the input sw_if_index. 
37  *
38  * Note that learning and forwarding are separate graph nodes. This means that
39  * for a set of packets, all learning is performed first, then all nodes are
40  * forwarded. The forwarding is done based on the end-state of the mac table,
41  * instead of the state after each packet. Thus the forwarding results could
42  * differ in certain cases (mac move tests), but this not expected to cause
43  * problems in real-world networks. It is much simpler to separate learning
44  * and forwarding into separate nodes.
45  */
46
47
48 typedef struct {
49   u8 src[6];
50   u8 dst[6];
51   u32 sw_if_index;
52   u16 bd_index;
53 } l2learn_trace_t;
54
55
56 /* packet trace format function */
57 static u8 * format_l2learn_trace (u8 * s, va_list * args)
58 {
59   CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
60   CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
61   l2learn_trace_t * t = va_arg (*args, l2learn_trace_t *);
62   
63   s = format (s, "l2-learn: sw_if_index %d dst %U src %U bd_index %d",
64               t->sw_if_index, 
65               format_ethernet_address, t->dst,
66               format_ethernet_address, t->src,
67               t->bd_index);
68   return s;
69 }
70
71 static vlib_node_registration_t l2learn_node;
72
73 #define foreach_l2learn_error                           \
74 _(L2LEARN,           "L2 learn packets")                \
75 _(MISS,              "L2 learn misses")                 \
76 _(MAC_MOVE,          "L2 mac moves")                    \
77 _(MAC_MOVE_VIOLATE,  "L2 mac move violations")          \
78 _(LIMIT,             "L2 not learned due to limit")     \
79 _(HIT,               "L2 learn hits")                   \
80 _(FILTER_DROP,       "L2 filter mac drops")     
81
82 typedef enum {
83 #define _(sym,str) L2LEARN_ERROR_##sym,
84   foreach_l2learn_error
85 #undef _
86   L2LEARN_N_ERROR,
87 } l2learn_error_t;
88
89 static char * l2learn_error_strings[] = {
90 #define _(sym,string) string,
91   foreach_l2learn_error
92 #undef _
93 };
94
95 typedef enum {  
96   L2LEARN_NEXT_L2FWD,
97   L2LEARN_NEXT_DROP,
98   L2LEARN_N_NEXT,
99 } l2learn_next_t;
100
101
102 // Perform learning on one packet based on the mac table lookup result
103
104 static_always_inline void
105 l2learn_process (vlib_node_runtime_t * node,
106                  l2learn_main_t * msm,
107                  u64 * counter_base,
108                  vlib_buffer_t * b0,
109                  u32 sw_if_index0,
110                  l2fib_entry_key_t * key0,
111                  l2fib_entry_key_t * cached_key,
112                  u32 * bucket0,
113                  l2fib_entry_result_t * result0,
114                  u32 * next0)
115 {
116   u32 feature_bitmap;
117
118   // Set up the default next node (typically L2FWD)
119
120   // Remove ourself from the feature bitmap
121   feature_bitmap = vnet_buffer(b0)->l2.feature_bitmap & ~L2INPUT_FEAT_LEARN;
122
123   // Save for next feature graph nodes
124   vnet_buffer(b0)->l2.feature_bitmap = feature_bitmap;
125
126   // Determine the next node
127   *next0 = feat_bitmap_get_next_node_index(msm->feat_next_node_index,
128                                            feature_bitmap);
129
130   // Check mac table lookup result
131
132   if (PREDICT_TRUE (result0->fields.sw_if_index == sw_if_index0)) {
133     // The entry was in the table, and the sw_if_index matched, the normal case 
134
135     // TODO: for dataplane learning and aging, do this:
136     //       if refresh=0 and not a static mac, set refresh=1
137     counter_base[L2LEARN_ERROR_HIT] += 1;
138
139   } else if (result0->raw == ~0) {  
140
141     // The entry was not in table, so add it 
142
143     counter_base[L2LEARN_ERROR_MISS] += 1;
144
145     if (msm->global_learn_count == msm->global_learn_limit) {
146       // Global limit reached. Do not learn the mac but forward the packet.
147       // In the future, limits could also be per-interface or bridge-domain.
148       counter_base[L2LEARN_ERROR_LIMIT] += 1;
149       goto done;
150
151     } else {
152       BVT(clib_bihash_kv) kv;
153       // It is ok to learn
154
155       result0->raw = 0; // clear all fields
156       result0->fields.sw_if_index = sw_if_index0;
157       // TODO: set timestamp in entry to clock for dataplane aging
158       kv.key = key0->raw;
159       kv.value = result0->raw;
160
161       BV(clib_bihash_add_del) (msm->mac_table, &kv, 1 /* is_add */);
162
163       cached_key->raw = ~0;  // invalidate the cache
164       msm->global_learn_count++;
165     }
166
167   } else {
168
169     // The entry was in the table, but with the wrong sw_if_index mapping (mac move)
170     counter_base[L2LEARN_ERROR_MAC_MOVE] += 1;
171
172     if (result0->fields.static_mac) {
173       // Don't overwrite a static mac
174       // TODO: Check violation policy. For now drop the packet
175       b0->error = node->errors[L2LEARN_ERROR_MAC_MOVE_VIOLATE];
176       *next0 = L2LEARN_NEXT_DROP;
177     } else {
178       // Update the entry
179       // TODO: may want to rate limit mac moves
180       // TODO: check global/bridge domain/interface learn limits
181       BVT(clib_bihash_kv) kv;
182
183       result0->raw = 0; // clear all fields
184       result0->fields.sw_if_index = sw_if_index0;
185  
186       kv.key = key0->raw;
187       kv.value = result0->raw;
188
189       cached_key->raw = ~0;  // invalidate the cache
190
191       BV(clib_bihash_add_del) (msm->mac_table, &kv, 1 /* is_add */);
192     }
193   }
194
195   if (result0->fields.filter) {
196     // drop packet because lookup matched a filter mac entry
197
198     if (*next0 != L2LEARN_NEXT_DROP) {
199       // if we're not already dropping the packet, do it now
200       b0->error = node->errors[L2LEARN_ERROR_FILTER_DROP];
201       *next0 = L2LEARN_NEXT_DROP;
202     }
203   }
204
205 done:
206   return;
207 }
208
209
210 static uword
211 l2learn_node_fn (vlib_main_t * vm,
212                   vlib_node_runtime_t * node,
213                   vlib_frame_t * frame)
214 {
215   u32 n_left_from, * from, * to_next;
216   l2learn_next_t next_index;
217   l2learn_main_t * msm = &l2learn_main;
218   vlib_node_t *n = vlib_get_node (vm, l2learn_node.index);
219   u32 node_counter_base_index = n->error_heap_index;
220   vlib_error_main_t * em = &vm->error_main;
221   l2fib_entry_key_t cached_key;
222   l2fib_entry_result_t cached_result;
223
224   from = vlib_frame_vector_args (frame);
225   n_left_from = frame->n_vectors; /* number of packets to process */
226   next_index = node->cached_next_index;
227  
228   // Clear the one-entry cache in case mac table was updated
229   cached_key.raw = ~0; 
230   cached_result.raw = ~0;       /* warning be gone */
231
232   while (n_left_from > 0)
233     {
234       u32 n_left_to_next;
235
236       /* get space to enqueue frame to graph node "next_index" */
237       vlib_get_next_frame (vm, node, next_index,
238                            to_next, n_left_to_next);
239
240       while (n_left_from >= 4 && n_left_to_next >= 2)
241         {
242           u32 bi0, bi1;
243           vlib_buffer_t * b0, * b1;
244           u32 next0, next1;
245           u32 sw_if_index0, sw_if_index1;
246           ethernet_header_t * h0, * h1;
247           l2fib_entry_key_t key0, key1;
248           l2fib_entry_result_t result0, result1;
249           u32 bucket0, bucket1;
250           
251           /* Prefetch next iteration. */
252           {
253             vlib_buffer_t * p2, * p3;
254             
255             p2 = vlib_get_buffer (vm, from[2]);
256             p3 = vlib_get_buffer (vm, from[3]);
257             
258             vlib_prefetch_buffer_header (p2, LOAD);
259             vlib_prefetch_buffer_header (p3, LOAD);
260
261             CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, STORE);
262             CLIB_PREFETCH (p3->data, CLIB_CACHE_LINE_BYTES, STORE);
263           }
264
265           /* speculatively enqueue b0 and b1 to the current next frame */
266           /* bi is "buffer index", b is pointer to the buffer */
267           to_next[0] = bi0 = from[0];
268           to_next[1] = bi1 = from[1];
269           from += 2;
270           to_next += 2;
271           n_left_from -= 2;
272           n_left_to_next -= 2;
273
274           b0 = vlib_get_buffer (vm, bi0);
275           b1 = vlib_get_buffer (vm, bi1);
276  
277           /* RX interface handles */
278           sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX];
279           sw_if_index1 = vnet_buffer(b1)->sw_if_index[VLIB_RX];
280
281           /* Process 2 x pkts */
282
283           h0 = vlib_buffer_get_current (b0);
284           h1 = vlib_buffer_get_current (b1);
285
286           if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE)))
287             {
288               if (b0->flags & VLIB_BUFFER_IS_TRACED) 
289                 {
290                   l2learn_trace_t *t = 
291                     vlib_add_trace (vm, node, b0, sizeof (*t));
292                   t->sw_if_index = sw_if_index0;
293                   t->bd_index = vnet_buffer(b0)->l2.bd_index;
294                   memcpy(t->src, h0->src_address, 6);
295                   memcpy(t->dst, h0->dst_address, 6);
296                 }
297               if (b1->flags & VLIB_BUFFER_IS_TRACED) 
298                 {
299                   l2learn_trace_t *t = 
300                     vlib_add_trace (vm, node, b1, sizeof (*t));
301                   t->sw_if_index = sw_if_index1;
302                   t->bd_index = vnet_buffer(b1)->l2.bd_index;
303                   memcpy(t->src, h1->src_address, 6);
304                   memcpy(t->dst, h1->dst_address, 6);
305                 }
306             }
307
308             /* process 2 pkts */
309             em->counters[node_counter_base_index + L2LEARN_ERROR_L2LEARN] += 2;
310
311             l2fib_lookup_2 (msm->mac_table, &cached_key, &cached_result,
312                             h0->src_address, 
313                             h1->src_address, 
314                             vnet_buffer(b0)->l2.bd_index,
315                             vnet_buffer(b1)->l2.bd_index,
316                             &key0,
317                             &key1,
318                             &bucket0,
319                             &bucket1,
320                             &result0,
321                             &result1);
322
323             l2learn_process (node, msm, &em->counters[node_counter_base_index],
324                              b0, sw_if_index0, &key0, &cached_key,
325                              &bucket0, &result0, &next0);
326
327             l2learn_process (node, msm, &em->counters[node_counter_base_index],
328                              b1, sw_if_index1, &key1, &cached_key,
329                              &bucket1, &result1, &next1);
330
331             /* verify speculative enqueues, maybe switch current next frame */
332             /* if next0==next1==next_index then nothing special needs to be done */
333             vlib_validate_buffer_enqueue_x2 (vm, node, next_index,
334                                              to_next, n_left_to_next,
335                                              bi0, bi1, next0, next1);
336         }
337       
338       while (n_left_from > 0 && n_left_to_next > 0)
339         {
340           u32 bi0;
341           vlib_buffer_t * b0;
342           u32 next0;
343           u32 sw_if_index0;
344           ethernet_header_t * h0;
345           l2fib_entry_key_t key0;
346           l2fib_entry_result_t result0;
347           u32 bucket0;
348
349           /* speculatively enqueue b0 to the current next frame */
350           bi0 = from[0];
351           to_next[0] = bi0;
352           from += 1;
353           to_next += 1;
354           n_left_from -= 1;
355           n_left_to_next -= 1;
356
357           b0 = vlib_get_buffer (vm, bi0);
358
359           sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX];
360  
361           h0 = vlib_buffer_get_current (b0);
362
363           if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) 
364                             && (b0->flags & VLIB_BUFFER_IS_TRACED))) {
365             l2learn_trace_t *t = 
366                vlib_add_trace (vm, node, b0, sizeof (*t));
367             t->sw_if_index = sw_if_index0;
368             t->bd_index = vnet_buffer(b0)->l2.bd_index;
369             memcpy(t->src, h0->src_address, 6);
370             memcpy(t->dst, h0->dst_address, 6);
371             }
372
373           /* process 1 pkt */
374           em->counters[node_counter_base_index + L2LEARN_ERROR_L2LEARN] += 1;
375
376           l2fib_lookup_1 (msm->mac_table, &cached_key, &cached_result, 
377                           h0->src_address, vnet_buffer(b0)->l2.bd_index, 
378                           &key0,
379                           &bucket0,
380                           &result0);
381
382           l2learn_process (node, msm, &em->counters[node_counter_base_index],
383                            b0, sw_if_index0, &key0, &cached_key,
384                            &bucket0, &result0, &next0);
385
386           /* verify speculative enqueue, maybe switch current next frame */
387           vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
388                                            to_next, n_left_to_next,
389                                            bi0, next0);
390         }
391
392       vlib_put_next_frame (vm, node, next_index, n_left_to_next);
393     }
394
395   return frame->n_vectors;
396 }
397
398
399 VLIB_REGISTER_NODE (l2learn_node,static) = {
400   .function = l2learn_node_fn,
401   .name = "l2-learn",
402   .vector_size = sizeof (u32),
403   .format_trace = format_l2learn_trace,
404   .type = VLIB_NODE_TYPE_INTERNAL,
405   
406   .n_errors = ARRAY_LEN(l2learn_error_strings),
407   .error_strings = l2learn_error_strings,
408
409   .n_next_nodes = L2LEARN_N_NEXT,
410
411   /* edit / add dispositions here */
412   .next_nodes = {
413         [L2LEARN_NEXT_DROP] = "error-drop",
414         [L2LEARN_NEXT_L2FWD] = "l2-fwd",
415   },
416 };
417
418
419 clib_error_t *l2learn_init (vlib_main_t *vm)
420 {
421   l2learn_main_t * mp = &l2learn_main;
422     
423   mp->vlib_main = vm;
424   mp->vnet_main = vnet_get_main();
425
426   // Initialize the feature next-node indexes
427   feat_bitmap_init_next_nodes(vm,
428                               l2learn_node.index,
429                               L2INPUT_N_FEAT,
430                               l2input_get_feat_names(),
431                               mp->feat_next_node_index);
432
433   /* init the hash table ptr */
434   mp->mac_table = get_mac_table();
435
436   // Set the default number of dynamically learned macs to the number 
437   // of buckets.
438   mp->global_learn_limit = L2FIB_NUM_BUCKETS * 16;
439
440   return 0;
441 }
442
443 VLIB_INIT_FUNCTION (l2learn_init);
444
445
446 // set subinterface learn enable/disable
447 // The CLI format is:
448 //    set interface l2 learn <interface> [disable]
449 static clib_error_t *
450 int_learn (vlib_main_t * vm,
451            unformat_input_t * input,
452            vlib_cli_command_t * cmd)
453 {
454   vnet_main_t * vnm = vnet_get_main();
455   clib_error_t * error = 0;
456   u32 sw_if_index;
457   u32 enable;
458
459   if (! unformat_user (input, unformat_vnet_sw_interface, vnm, &sw_if_index))
460     {
461       error = clib_error_return (0, "unknown interface `%U'",
462                                  format_unformat_error, input);
463       goto done;
464     }
465
466   enable = 1;
467   if (unformat (input, "disable")) {
468     enable = 0;
469   }
470
471   // set the interface flag
472   l2input_intf_bitmap_enable(sw_if_index, L2INPUT_FEAT_LEARN, enable);
473
474  done:
475   return error;
476 }
477
478 VLIB_CLI_COMMAND (int_learn_cli, static) = {
479   .path = "set interface l2 learn",
480   .short_help = "set interface l2 learn <interface> [disable]",
481   .function = int_learn,
482 };
483
484
485 static clib_error_t *
486 l2learn_config (vlib_main_t * vm, unformat_input_t * input)
487 {
488   l2learn_main_t *mp = &l2learn_main;
489
490   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
491     {
492       if (unformat (input, "limit %d", &mp->global_learn_limit))
493         ;
494
495       else
496         return clib_error_return (0, "unknown input `%U'",
497                                   format_unformat_error, input);
498     }
499
500   return 0;
501 }
502
503 VLIB_CONFIG_FUNCTION (l2learn_config, "l2learn");
504