VPP-651: Ensure sw_if_index to node mapping for L2 output path is only done via l2out...
[vpp.git] / src / plugins / acl / l2sess_node.c
1 /*
2  * Copyright (c) 2016 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 <netinet/in.h>
16 #include <vlib/vlib.h>
17 #include <vnet/vnet.h>
18 #include <vnet/pg/pg.h>
19 #include <vppinfra/error.h>
20 #include <acl/l2sess.h>
21 #include <vnet/l2/l2_classify.h>
22
23
24 typedef struct
25 {
26   u32 next_index;
27   u32 sw_if_index;
28   u32 trace_flags;
29   u32 session_tables[2];
30   u32 session_nexts[2];
31   u8 l4_proto;
32 } l2sess_trace_t;
33
34 /* packet trace format function */
35
36 #define _(node_name, node_var, is_out, is_ip6, is_track) \
37 static u8 * format_## node_var ##_trace (u8 * s, va_list * args)      \
38 {  \
39   CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); \
40   CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); \
41   l2sess_trace_t * t = va_arg (*args, l2sess_trace_t *); \
42  \
43   s = format (s, node_name ": sw_if_index %d, next index %d trace_flags %08x L4 proto %d\n" \
44                            "                 tables [ %d, %d ] nexts [ %d, %d ]", \
45               t->sw_if_index, t->next_index, t->trace_flags, t->l4_proto, \
46               t->session_tables[0], t->session_tables[1], \
47               t->session_nexts[0], t->session_nexts[1]); \
48   return s; \
49 }
50 foreach_l2sess_node
51 #undef _
52 #define foreach_l2sess_error \
53 _(SWAPPED, "Mac swap packets processed")
54   typedef enum
55 {
56 #define _(sym,str) L2SESS_ERROR_##sym,
57   foreach_l2sess_error
58 #undef _
59     L2SESS_N_ERROR,
60 } l2sess_error_t;
61
62 static char *l2sess_error_strings[] = {
63 #define _(sym,string) string,
64   foreach_l2sess_error
65 #undef _
66 };
67
68 typedef enum
69 {
70   L2SESS_NEXT_DROP,
71   L2SESS_N_NEXT,
72 } l2sess_next_t;
73
74 u8
75 l2sess_get_l4_proto (vlib_buffer_t * b0, int node_is_ip6)
76 {
77   u8 proto;
78   int proto_offset;
79   if (node_is_ip6)
80     {
81       proto_offset = 20;
82     }
83   else
84     {
85       proto_offset = 23;
86     }
87   proto = *((u8 *) vlib_buffer_get_current (b0) + proto_offset);
88   return proto;
89 }
90
91
92 u8
93 l2sess_get_tcp_flags (vlib_buffer_t * b0, int node_is_ip6)
94 {
95   u8 flags;
96   int flags_offset;
97   if (node_is_ip6)
98     {
99       flags_offset = 14 + 40 + 13;      /* FIXME: no extension headers assumed */
100     }
101   else
102     {
103       flags_offset = 14 + 20 + 13;
104     }
105   flags = *((u8 *) vlib_buffer_get_current (b0) + flags_offset);
106   return flags;
107 }
108
109 static inline int
110 l4_tcp_or_udp (u8 proto)
111 {
112   return ((proto == 6) || (proto == 17));
113 }
114
115 void
116 l2sess_get_session_tables (l2sess_main_t * sm, u32 sw_if_index,
117                            int node_is_out, int node_is_ip6, u8 l4_proto,
118                            u32 * session_tables)
119 {
120 /*
121  * Based on the direction, l3 and l4 protocol, fill a u32[2] array:
122  * [0] is index for the "direct match" path, [1] is for "mirrored match".
123  * Store the indices of the tables to add the session to in session_tables[]
124  */
125   l2_output_classify_main_t *l2om = &l2_output_classify_main;
126   l2_input_classify_main_t *l2im = &l2_input_classify_main;
127
128   u32 output_table_index;
129   u32 input_table_index;
130
131   if (!l4_tcp_or_udp (l4_proto))
132     {
133       return;
134     }
135
136   if (node_is_ip6)
137     {
138       vec_validate_init_empty (l2im->
139                                classify_table_index_by_sw_if_index
140                                [L2_INPUT_CLASSIFY_TABLE_IP6], sw_if_index,
141                                ~0);
142       input_table_index =
143         l2im->
144         classify_table_index_by_sw_if_index[L2_INPUT_CLASSIFY_TABLE_IP6]
145         [sw_if_index];
146       vec_validate_init_empty (l2om->
147                                classify_table_index_by_sw_if_index
148                                [L2_OUTPUT_CLASSIFY_TABLE_IP6], sw_if_index,
149                                ~0);
150       output_table_index =
151         l2om->
152         classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_IP6]
153         [sw_if_index];
154     }
155   else
156     {
157       vec_validate_init_empty (l2im->
158                                classify_table_index_by_sw_if_index
159                                [L2_INPUT_CLASSIFY_TABLE_IP4], sw_if_index,
160                                ~0);
161       input_table_index =
162         l2im->
163         classify_table_index_by_sw_if_index[L2_INPUT_CLASSIFY_TABLE_IP4]
164         [sw_if_index];
165       vec_validate_init_empty (l2om->
166                                classify_table_index_by_sw_if_index
167                                [L2_OUTPUT_CLASSIFY_TABLE_IP4], sw_if_index,
168                                ~0);
169       output_table_index =
170         l2om->
171         classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_IP4]
172         [sw_if_index];
173     }
174
175   if (node_is_out)
176     {
177       session_tables[0] = output_table_index;
178       session_tables[1] = input_table_index;
179     }
180   else
181     {
182       session_tables[0] = input_table_index;
183       session_tables[1] = output_table_index;
184     }
185 }
186
187 void
188 l2sess_get_session_nexts (l2sess_main_t * sm, u32 sw_if_index,
189                           int node_is_out, int node_is_ip6, u8 l4_proto,
190                           u32 * session_nexts)
191 {
192 /*
193  * Based on the direction, l3 and l4 protocol, fill a u32[2] array:
194  * [0] is the index for the "direct match" path, [1] is for "mirrored match".
195  * Store the match_next_index in session_nexts[] for a new session entry which is being added to session tables.
196  */
197   u32 input_node_index;
198   u32 output_node_index;
199
200   if (!l4_tcp_or_udp (l4_proto))
201     {
202       return;
203     }
204
205   input_node_index =
206     sm->next_slot_track_node_by_is_ip6_is_out[node_is_ip6][0];
207   output_node_index =
208     sm->next_slot_track_node_by_is_ip6_is_out[node_is_ip6][1];
209
210   if (node_is_out)
211     {
212       session_nexts[0] = output_node_index;
213       session_nexts[1] = input_node_index;
214     }
215   else
216     {
217       session_nexts[0] = input_node_index;
218       session_nexts[1] = output_node_index;
219     }
220 }
221
222
223 static inline void
224 swap_bytes (vlib_buffer_t * b0, int off_a, int off_b, int nbytes)
225 {
226   u8 tmp;
227   u8 *pa = vlib_buffer_get_current (b0) + off_a;
228   u8 *pb = vlib_buffer_get_current (b0) + off_b;
229   while (nbytes--)
230     {
231       tmp = *pa;
232       *pa++ = *pb;
233       *pb++ = tmp;
234     }
235 }
236
237 /*
238  * This quite pro[bv]ably is a terrible idea performance wise. Moreso doing it twice.
239  * Would having a long (ish) chunk of memory work better for this ?
240  * We will see when we get to the performance of this.
241  */
242 void
243 l2sess_flip_l3l4_fields (vlib_buffer_t * b0, int node_is_ip6, u8 l4_proto)
244 {
245   if (!l4_tcp_or_udp (l4_proto))
246     {
247       return;
248     }
249   if (node_is_ip6)
250     {
251       swap_bytes (b0, 22, 38, 16);      /* L3 */
252       swap_bytes (b0, 54, 56, 2);       /* L4 (when no EH!) */
253     }
254   else
255     {
256       swap_bytes (b0, 26, 30, 4);       /* L3 */
257       swap_bytes (b0, 34, 36, 2);       /* L4 */
258     }
259 }
260
261 void
262 l2sess_add_session (vlib_buffer_t * b0, int node_is_out, int node_is_ip6,
263                     u32 session_table, u32 session_match_next,
264                     u32 opaque_index)
265 {
266   vnet_classify_main_t *cm = &vnet_classify_main;
267   u32 action = 0;
268   u32 metadata = 0;
269
270 #ifdef DEBUG_SESSIONS
271   printf ("Adding session to table %d with next %d\n", session_table,
272           session_match_next);
273 #endif
274   vnet_classify_add_del_session (cm, session_table,
275                                  vlib_buffer_get_current (b0),
276                                  session_match_next, opaque_index, 0, action,
277                                  metadata, 1);
278 }
279
280
281
282 static void *
283 get_ptr_to_offset (vlib_buffer_t * b0, int offset)
284 {
285   u8 *p = vlib_buffer_get_current (b0) + offset;
286   return p;
287 }
288
289
290 /*
291  * FIXME: Hardcoded offsets are ugly, although if casting to structs one
292  * would need to take care about alignment.. So let's for now be naive and simple.
293  */
294
295 void
296 session_store_ip4_l3l4_info (vlib_buffer_t * b0, l2s_session_t * sess,
297                              int node_is_out)
298 {
299   clib_memcpy (&sess->side[1 - node_is_out].addr.ip4,
300                get_ptr_to_offset (b0, 26), 4);
301   clib_memcpy (&sess->side[node_is_out].addr.ip4, get_ptr_to_offset (b0, 30),
302                4);
303   sess->side[1 - node_is_out].port =
304     ntohs (*(u16 *) get_ptr_to_offset (b0, 34));
305   sess->side[node_is_out].port = ntohs (*(u16 *) get_ptr_to_offset (b0, 36));
306 }
307
308 void
309 session_store_ip6_l3l4_info (vlib_buffer_t * b0, l2s_session_t * sess,
310                              int node_is_out)
311 {
312   clib_memcpy (&sess->side[1 - node_is_out].addr.ip6,
313                get_ptr_to_offset (b0, 22), 16);
314   clib_memcpy (&sess->side[node_is_out].addr.ip4, get_ptr_to_offset (b0, 38),
315                16);
316   sess->side[1 - node_is_out].port =
317     ntohs (*(u16 *) get_ptr_to_offset (b0, 54));
318   sess->side[node_is_out].port = ntohs (*(u16 *) get_ptr_to_offset (b0, 56));
319 }
320
321 static void
322 build_match_from_session (l2sess_main_t * sm, u8 * match,
323                           l2s_session_t * sess, int is_out)
324 {
325   if (sess->is_ip6)
326     {
327       match[20] = sess->l4_proto;
328       clib_memcpy (&match[22], &sess->side[1 - is_out].addr.ip6, 16);
329       clib_memcpy (&match[38], &sess->side[is_out].addr.ip4, 16);
330       *(u16 *) & match[54] = htons (sess->side[1 - is_out].port);
331       *(u16 *) & match[56] = htons (sess->side[is_out].port);
332     }
333   else
334     {
335       match[23] = sess->l4_proto;
336       clib_memcpy (&match[26], &sess->side[1 - is_out].addr.ip6, 4);
337       clib_memcpy (&match[30], &sess->side[is_out].addr.ip4, 4);
338       *(u16 *) & match[34] = htons (sess->side[1 - is_out].port);
339       *(u16 *) & match[36] = htons (sess->side[is_out].port);
340     }
341 }
342
343 static void
344 delete_session (l2sess_main_t * sm, u32 sw_if_index, u32 session_index)
345 {
346   vnet_classify_main_t *cm = &vnet_classify_main;
347   u8 match[5 * 16];             /* For building the mock of the packet to delete the classifier session */
348   u32 session_tables[2] = { ~0, ~0 };
349   l2s_session_t *sess = sm->sessions + session_index;
350   if (pool_is_free (sm->sessions, sess))
351     {
352       sm->counter_attempted_delete_free_session++;
353       return;
354     }
355   l2sess_get_session_tables (sm, sw_if_index, 0, sess->is_ip6, sess->l4_proto,
356                              session_tables);
357   if (session_tables[1] != ~0)
358     {
359       build_match_from_session (sm, match, sess, 1);
360       vnet_classify_add_del_session (cm, session_tables[1], match, 0, 0, 0, 0,
361                                      0, 0);
362     }
363   if (session_tables[1] != ~0)
364     {
365       build_match_from_session (sm, match, sess, 1);
366       vnet_classify_add_del_session (cm, session_tables[1], match, 0, 0, 0, 0,
367                                      0, 0);
368     }
369   pool_put (sm->sessions, sess);
370 }
371
372 static void
373 udp_session_account_buffer (vlib_buffer_t * b0, l2s_session_t * s,
374                             int which_side, u64 now)
375 {
376   l2s_session_side_t *ss = &s->side[which_side];
377   ss->active_time = now;
378   ss->n_packets++;
379   ss->n_bytes += b0->current_data + b0->current_length;
380 }
381
382 static inline u64
383 udp_session_get_timeout (l2sess_main_t * sm, l2s_session_t * sess, u64 now)
384 {
385   return (sm->udp_session_idle_timeout);
386 }
387
388 static void
389 tcp_session_account_buffer (vlib_buffer_t * b0, l2s_session_t * s,
390                             int which_side, u64 now)
391 {
392   l2s_session_side_t *ss = &s->side[which_side];
393   ss->active_time = now;
394   ss->n_packets++;
395   ss->n_bytes += b0->current_data + b0->current_length;
396   /* Very very lightweight TCP state tracking: just record which flags were seen */
397   s->tcp_flags_seen |=
398     l2sess_get_tcp_flags (b0, s->is_ip6) << (8 * which_side);
399 }
400
401 /*
402  * Since we are tracking for the purposes of timing the sessions out,
403  * we mostly care about two states: established (maximize the idle timeouts)
404  * and transient (halfopen/halfclosed/reset) - we need to have a reasonably short timeout to
405  * quickly get rid of sessions but not short enough to violate the TCP specs.
406  */
407
408 static inline u64
409 tcp_session_get_timeout (l2sess_main_t * sm, l2s_session_t * sess, u64 now)
410 {
411   /* seen both SYNs and ACKs but not FINs means we are in establshed state */
412   u16 masked_flags =
413     sess->tcp_flags_seen & ((TCP_FLAGS_RSTFINACKSYN << 8) +
414                             TCP_FLAGS_RSTFINACKSYN);
415   if (((TCP_FLAGS_ACKSYN << 8) + TCP_FLAGS_ACKSYN) == masked_flags)
416     {
417       return (sm->tcp_session_idle_timeout);
418     }
419   else
420     {
421       return (sm->tcp_session_transient_timeout);
422     }
423 }
424
425 static inline u64
426 session_get_timeout (l2sess_main_t * sm, l2s_session_t * sess, u64 now)
427 {
428   u64 timeout;
429
430   switch (sess->l4_proto)
431     {
432     case 6:
433       timeout = tcp_session_get_timeout (sm, sess, now);
434       break;
435     case 17:
436       timeout = udp_session_get_timeout (sm, sess, now);
437       break;
438     default:
439       timeout = 0;
440     }
441
442   return timeout;
443 }
444
445 static inline u64
446 get_session_last_active_time(l2s_session_t * sess)
447 {
448   u64 last_active =
449     sess->side[0].active_time >
450     sess->side[1].active_time ? sess->side[0].active_time : sess->side[1].
451     active_time;
452   return last_active;
453 }
454
455 static int
456 session_is_alive (l2sess_main_t * sm, l2s_session_t * sess, u64 now, u64 *last_active_cache)
457 {
458   u64 last_active = get_session_last_active_time(sess);
459   u64 timeout = session_get_timeout (sm, sess, now);
460   int is_alive = ((now - last_active) < timeout);
461   if (last_active_cache)
462     *last_active_cache = last_active;
463   return is_alive;
464 }
465
466 static void
467 check_idle_sessions (l2sess_main_t * sm, u32 sw_if_index, u64 now)
468 {
469   sm->timer_wheel_next_expiring_time = 0;
470   sm->data_from_advancing_timing_wheel
471     =
472     timing_wheel_advance (&sm->timing_wheel, now,
473                           sm->data_from_advancing_timing_wheel,
474                           &sm->timer_wheel_next_expiring_time);
475 #ifdef DEBUG_SESSIONS_VERBOSE
476   {
477     clib_time_t *ct = &sm->vlib_main->clib_time;
478     f64 ctime;
479     ctime = now * ct->seconds_per_clock;
480     clib_warning ("Now        : %U", format_time_interval, "h:m:s:u", ctime);
481     ctime = sm->timer_wheel_next_expiring_time * ct->seconds_per_clock;
482     clib_warning ("Next expire: %U", format_time_interval, "h:m:s:u", ctime);
483     clib_warning ("Expired items: %d",
484                   (int) vec_len (sm->data_from_advancing_timing_wheel));
485   }
486 #endif
487
488   sm->timer_wheel_next_expiring_time = now + sm->timer_wheel_tick;
489   if (PREDICT_FALSE ( 0 == sm->data_from_advancing_timing_wheel )) {
490     return;
491   }
492
493   if (PREDICT_FALSE (_vec_len (sm->data_from_advancing_timing_wheel) > 0))
494     {
495       uword i;
496       for (i = 0; i < _vec_len (sm->data_from_advancing_timing_wheel); i++)
497         {
498           u32 session_index = sm->data_from_advancing_timing_wheel[i];
499           if (!pool_is_free_index (sm->sessions, session_index))
500             {
501               l2s_session_t *sess = sm->sessions + session_index;
502               u64 last_active;
503               if (session_is_alive (sm, sess, now, &last_active))
504                 {
505 #ifdef DEBUG_SESSIONS
506               clib_warning ("Restarting timer for session %d", (int) session_index);
507 #endif
508                     /* Pretend we did this in the past, at last_active moment */
509                     timing_wheel_insert (&sm->timing_wheel,
510                                          last_active + session_get_timeout (sm, sess,
511                                                                     last_active),
512                                          session_index);
513                 }
514               else
515                 {
516 #ifdef DEBUG_SESSIONS
517               clib_warning ("Deleting session %d", (int) session_index);
518 #endif
519               delete_session (sm, sw_if_index, session_index);
520                 }
521             }
522         }
523       _vec_len (sm->data_from_advancing_timing_wheel) = 0;
524     }
525 }
526
527 static uword
528 l2sess_node_fn (vlib_main_t * vm,
529                 vlib_node_runtime_t * node, vlib_frame_t * frame,
530                 int node_is_out, int node_is_ip6, int node_is_track,
531                 u32 *feat_next_node_index)
532 {
533   u32 n_left_from, *from, *to_next;
534   l2sess_next_t next_index;
535   u32 pkts_swapped = 0;
536   u32 feature_bitmap0;
537   u32 trace_flags0;
538
539   l2sess_main_t *sm = &l2sess_main;
540
541   from = vlib_frame_vector_args (frame);
542   n_left_from = frame->n_vectors;
543   next_index = node->cached_next_index;
544
545   while (n_left_from > 0)
546     {
547       u32 n_left_to_next;
548
549       vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
550
551       /* Only a single loop for now for simplicity */
552
553       while (n_left_from > 0 && n_left_to_next > 0)
554         {
555           u32 bi0;
556           vlib_buffer_t *b0;
557           u32 next0 = L2SESS_NEXT_DROP;
558           u32 sw_if_index0;
559           //ethernet_header_t *en0;
560
561           /* speculatively enqueue b0 to the current next frame */
562           bi0 = from[0];
563           to_next[0] = bi0;
564           from += 1;
565           to_next += 1;
566           n_left_from -= 1;
567           n_left_to_next -= 1;
568
569           b0 = vlib_get_buffer (vm, bi0);
570           //en0 = vlib_buffer_get_current (b0);
571
572 /*
573  *     node_is_out : 1 = is output, 0 = is input
574  *     node_is_ip6 : 1 = is ip6, 0 = is ip4
575  *     node_is_track : 1 = is a state tracking node, 0 - is a session addition node
576  *
577  *     The below code adjust the behavior according to these parameters.
578  */
579           {
580             u32 session_tables[2] = { ~0, ~0 };
581             u32 session_nexts[2] = { ~0, ~0 };
582             u8 l4_proto;
583             u64 now = clib_cpu_time_now ();
584
585             trace_flags0 = 0;
586             if (node_is_out)
587               {
588                 sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_TX];
589               }
590             else
591               {
592                 sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
593               }
594             /* potentially also remove the nodes here */
595             feature_bitmap0 = vnet_buffer (b0)->l2.feature_bitmap;
596
597             if (node_is_track)
598               {
599                 u32 sess_index = vnet_buffer (b0)->l2_classify.opaque_index;
600                 l2s_session_t *sess = sm->sessions + sess_index;
601                 l4_proto = sess->l4_proto;
602
603                 if (session_is_alive (sm, sess, now, 0))
604                   {
605                     if (6 == l4_proto)
606                       {
607                         tcp_session_account_buffer (b0, sess, node_is_out,
608                                                     now);
609                       }
610                     else
611                       {
612                         udp_session_account_buffer (b0, sess, node_is_out,
613                                                     now);
614                       }
615                   }
616                 else
617                   {
618                     timing_wheel_delete (&sm->timing_wheel, sess_index);
619                     delete_session (sm, sw_if_index0, sess_index);
620                     /* FIXME: drop the packet that hit the obsolete node, for now. We really ought to recycle it. */
621                     next0 = 0;
622                   }
623               }
624             else
625               {
626                 /*
627                  * "-add" node: take l2opaque which arrived to us, and deduce
628                  * the tables out of that. ~0 means the topmost classifier table
629                  * applied for this AF on the RX(for input)/TX(for output)) sw_if_index.
630                  * Also add the mirrored session to the paired table.
631                  */
632                 l2s_session_t *sess;
633                 u32 sess_index;
634
635                 l4_proto = l2sess_get_l4_proto (b0, node_is_ip6);
636
637                 pool_get (sm->sessions, sess);
638                 sess_index = sess - sm->sessions;
639                 sess->create_time = now;
640                 sess->side[node_is_out].active_time = now;
641                 sess->side[1 - node_is_out].active_time = now;
642                 sess->l4_proto = l4_proto;
643                 sess->is_ip6 = node_is_ip6;
644                 if (node_is_ip6)
645                   {
646                     session_store_ip6_l3l4_info (b0, sess, node_is_out);
647                   }
648                 else
649                   {
650                     session_store_ip4_l3l4_info (b0, sess, node_is_out);
651                   }
652
653                 l2sess_get_session_tables (sm, sw_if_index0, node_is_out,
654                                            node_is_ip6, l4_proto,
655                                            session_tables);
656                 l2sess_get_session_nexts (sm, sw_if_index0, node_is_out,
657                                           node_is_ip6, l4_proto,
658                                           session_nexts);
659                 l2sess_flip_l3l4_fields (b0, node_is_ip6, l4_proto);
660                 if (session_tables[1] != ~0)
661                   {
662                     l2sess_add_session (b0, node_is_out, node_is_ip6,
663                                         session_tables[1], session_nexts[1],
664                                         sess_index);
665                   }
666                 l2sess_flip_l3l4_fields (b0, node_is_ip6, l4_proto);
667                 if (session_tables[0] != ~0)
668                   {
669                     l2sess_add_session (b0, node_is_out, node_is_ip6,
670                                         session_tables[0], session_nexts[0],
671                                         sess_index);
672                   }
673                 if (6 == sess->l4_proto)
674                   {
675                     tcp_session_account_buffer (b0, sess, node_is_out, now);
676                   }
677                 else
678                   {
679                     udp_session_account_buffer (b0, sess, node_is_out, now);
680                   }
681                 timing_wheel_insert (&sm->timing_wheel,
682                                      now + session_get_timeout (sm, sess,
683                                                                 now),
684                                      sess_index);
685               }
686
687             if (now >= sm->timer_wheel_next_expiring_time)
688               {
689                 check_idle_sessions (sm, sw_if_index0, now);
690               }
691
692             next0 = feat_bitmap_get_next_node_index (feat_next_node_index,
693                                                    feature_bitmap0);
694
695             if (next0 >= node->n_next_nodes)
696               {
697                 trace_flags0 |= 1;
698               }
699
700             if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)
701                                && (b0->flags & VLIB_BUFFER_IS_TRACED)))
702               {
703                 l2sess_trace_t *t =
704                   vlib_add_trace (vm, node, b0, sizeof (*t));
705                 t->sw_if_index = sw_if_index0;
706                 t->next_index = next0;
707                 t->trace_flags = trace_flags0;
708                 t->l4_proto = l4_proto;
709                 t->session_tables[0] = session_tables[0];
710                 t->session_tables[1] = session_tables[1];
711                 t->session_nexts[0] = session_nexts[0];
712                 t->session_nexts[1] = session_nexts[1];
713               }
714
715           }
716           pkts_swapped += 1;
717           if (next0 >= node->n_next_nodes)
718             {
719               next0 = 0;
720             }
721
722           /* verify speculative enqueue, maybe switch current next frame */
723           vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
724                                            to_next, n_left_to_next,
725                                            bi0, next0);
726         }
727
728       vlib_put_next_frame (vm, node, next_index, n_left_to_next);
729     }
730   vlib_node_increment_counter (vm, node->node_index,
731                                L2SESS_ERROR_SWAPPED, pkts_swapped);
732   return frame->n_vectors;
733 }
734
735
736 #define _(node_name, node_var, is_out, is_ip6, is_track) \
737 static uword                                             \
738 node_var ## node_fn (vlib_main_t * vm,                   \
739                   vlib_node_runtime_t * node,            \
740                   vlib_frame_t * frame)                  \
741 {                                                        \
742   l2sess_main_t *sm = &l2sess_main;                      \
743   return l2sess_node_fn(vm, node, frame,                 \
744                         is_out, is_ip6, is_track,        \
745                         sm->node_var ## _feat_next_node_index);  \
746 }                                                        \
747 VLIB_REGISTER_NODE (node_var) = {                        \
748   .function = node_var ## node_fn,                       \
749   .name = node_name,                                     \
750   .vector_size = sizeof (u32),                           \
751   .format_trace = format_ ## node_var ## _trace,         \
752   .type = VLIB_NODE_TYPE_INTERNAL,                       \
753                                                          \
754   .n_errors = ARRAY_LEN(l2sess_error_strings),           \
755   .error_strings = l2sess_error_strings,                 \
756                                                          \
757   .n_next_nodes = L2SESS_N_NEXT,                         \
758   .next_nodes = {                                        \
759         [L2SESS_NEXT_DROP] = "error-drop",               \
760   },                                                     \
761 };
762 foreach_l2sess_node
763 #undef _