/* * Copyright (c) 2016 Cisco and/or its affiliates. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include typedef struct { u32 next_index; u32 sw_if_index; u32 trace_flags; u32 session_tables[2]; u32 session_nexts[2]; u8 l4_proto; } l2sess_trace_t; /* packet trace format function */ #define _(node_name, node_var, is_out, is_ip6, is_track) \ static u8 * format_## node_var ##_trace (u8 * s, va_list * args) \ { \ CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); \ CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); \ l2sess_trace_t * t = va_arg (*args, l2sess_trace_t *); \ \ s = format (s, node_name ": sw_if_index %d, next index %d trace_flags %08x L4 proto %d\n" \ " tables [ %d, %d ] nexts [ %d, %d ]", \ t->sw_if_index, t->next_index, t->trace_flags, t->l4_proto, \ t->session_tables[0], t->session_tables[1], \ t->session_nexts[0], t->session_nexts[1]); \ return s; \ } foreach_l2sess_node #undef _ #define foreach_l2sess_error \ _(SWAPPED, "Mac swap packets processed") typedef enum { #define _(sym,str) L2SESS_ERROR_##sym, foreach_l2sess_error #undef _ L2SESS_N_ERROR, } l2sess_error_t; static char *l2sess_error_strings[] = { #define _(sym,string) string, foreach_l2sess_error #undef _ }; typedef enum { L2SESS_NEXT_DROP, L2SESS_N_NEXT, } l2sess_next_t; u8 l2sess_get_l4_proto (vlib_buffer_t * b0, int node_is_ip6) { u8 proto; int proto_offset; if (node_is_ip6) { proto_offset = 20; } else { proto_offset = 23; } proto = *((u8 *) vlib_buffer_get_current (b0) + proto_offset); return proto; } u8 l2sess_get_tcp_flags (vlib_buffer_t * b0, int node_is_ip6) { u8 flags; int flags_offset; if (node_is_ip6) { flags_offset = 14 + 40 + 13; /* FIXME: no extension headers assumed */ } else { flags_offset = 14 + 20 + 13; } flags = *((u8 *) vlib_buffer_get_current (b0) + flags_offset); return flags; } static inline int l4_tcp_or_udp (u8 proto) { return ((proto == 6) || (proto == 17)); } void l2sess_get_session_tables (l2sess_main_t * sm, u32 sw_if_index, int node_is_out, int node_is_ip6, u8 l4_proto, u32 * session_tables) { /* * Based on the direction, l3 and l4 protocol, fill a u32[2] array: * [0] is index for the "direct match" path, [1] is for "mirrored match". * Store the indices of the tables to add the session to in session_tables[] */ l2_output_classify_main_t *l2om = &l2_output_classify_main; l2_input_classify_main_t *l2im = &l2_input_classify_main; u32 output_table_index; u32 input_table_index; if (!l4_tcp_or_udp (l4_proto)) { return; } if (node_is_ip6) { vec_validate_init_empty (l2im-> classify_table_index_by_sw_if_index [L2_INPUT_CLASSIFY_TABLE_IP6], sw_if_index, ~0); input_table_index = l2im-> classify_table_index_by_sw_if_index[L2_INPUT_CLASSIFY_TABLE_IP6] [sw_if_index]; vec_validate_init_empty (l2om-> classify_table_index_by_sw_if_index [L2_OUTPUT_CLASSIFY_TABLE_IP6], sw_if_index, ~0); output_table_index = l2om-> classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_IP6] [sw_if_index]; } else { vec_validate_init_empty (l2im-> classify_table_index_by_sw_if_index [L2_INPUT_CLASSIFY_TABLE_IP4], sw_if_index, ~0); input_table_index = l2im-> classify_table_index_by_sw_if_index[L2_INPUT_CLASSIFY_TABLE_IP4] [sw_if_index]; vec_validate_init_empty (l2om-> classify_table_index_by_sw_if_index [L2_OUTPUT_CLASSIFY_TABLE_IP4], sw_if_index, ~0); output_table_index = l2om-> classify_table_index_by_sw_if_index[L2_OUTPUT_CLASSIFY_TABLE_IP4] [sw_if_index]; } if (node_is_out) { session_tables[0] = output_table_index; session_tables[1] = input_table_index; } else { session_tables[0] = input_table_index; session_tables[1] = output_table_index; } } void l2sess_get_session_nexts (l2sess_main_t * sm, u32 sw_if_index, int node_is_out, int node_is_ip6, u8 l4_proto, u32 * session_nexts) { /* * Based on the direction, l3 and l4 protocol, fill a u32[2] array: * [0] is the index for the "direct match" path, [1] is for "mirrored match". * Store the match_next_index in session_nexts[] for a new session entry which is being added to session tables. */ u32 input_node_index; u32 output_node_index; if (!l4_tcp_or_udp (l4_proto)) { return; } input_node_index = sm->next_slot_track_node_by_is_ip6_is_out[node_is_ip6][0]; output_node_index = sm->next_slot_track_node_by_is_ip6_is_out[node_is_ip6][1]; if (node_is_out) { session_nexts[0] = output_node_index; session_nexts[1] = input_node_index; } else { session_nexts[0] = input_node_index; session_nexts[1] = output_node_index; } } static inline void swap_bytes (vlib_buffer_t * b0, int off_a, int off_b, int nbytes) { u8 tmp; u8 *pa = vlib_buffer_get_current (b0) + off_a; u8 *pb = vlib_buffer_get_current (b0) + off_b; while (nbytes--) { tmp = *pa; *pa++ = *pb; *pb++ = tmp; } } /* * This quite pro[bv]ably is a terrible idea performance wise. Moreso doing it twice. * Would having a long (ish) chunk of memory work better for this ? * We will see when we get to the performance of this. */ void l2sess_flip_l3l4_fields (vlib_buffer_t * b0, int node_is_ip6, u8 l4_proto) { if (!l4_tcp_or_udp (l4_proto)) { return; } if (node_is_ip6) { swap_bytes (b0, 22, 38, 16); /* L3 */ swap_bytes (b0, 54, 56, 2); /* L4 (when no EH!) */ } else { swap_bytes (b0, 26, 30, 4); /* L3 */ swap_bytes (b0, 34, 36, 2); /* L4 */ } } void l2sess_add_session (vlib_buffer_t * b0, int node_is_out, int node_is_ip6, u32 session_table, u32 session_match_next, u32 opaque_index) { vnet_classify_main_t *cm = &vnet_classify_main; u32 action = 0; u32 metadata = 0; #ifdef DEBUG_SESSIONS printf ("Adding session to table %d with next %d\n", session_table, session_match_next); #endif vnet_classify_add_del_session (cm, session_table, vlib_buffer_get_current (b0), session_match_next, opaque_index, 0, action, metadata, 1); } static void * get_ptr_to_offset (vlib_buffer_t * b0, int offset) { u8 *p = vlib_buffer_get_current (b0) + offset; return p; } /* * FIXME: Hardcoded offsets are ugly, although if casting to structs one * would need to take care about alignment.. So let's for now be naive and simple. */ void session_store_ip4_l3l4_info (vlib_buffer_t * b0, l2s_session_t * sess, int node_is_out) { clib_memcpy (&sess->side[1 - node_is_out].addr.ip4, get_ptr_to_offset (b0, 26), 4); clib_memcpy (&sess->side[node_is_out].addr.ip4, get_ptr_to_offset (b0, 30), 4); sess->side[1 - node_is_out].port = ntohs (*(u16 *) get_ptr_to_offset (b0, 34)); sess->side[node_is_out].port = ntohs (*(u16 *) get_ptr_to_offset (b0, 36)); } void session_store_ip6_l3l4_info (vlib_buffer_t * b0, l2s_session_t * sess, int node_is_out) { clib_memcpy (&sess->side[1 - node_is_out].addr.ip6, get_ptr_to_offset (b0, 22), 16); clib_memcpy (&sess->side[node_is_out].addr.ip4, get_ptr_to_offset (b0, 38), 16); sess->side[1 - node_is_out].port = ntohs (*(u16 *) get_ptr_to_offset (b0, 54)); sess->side[node_is_out].port = ntohs (*(u16 *) get_ptr_to_offset (b0, 56)); } static void build_match_from_session (l2sess_main_t * sm, u8 * match, l2s_session_t * sess, int is_out) { if (sess->is_ip6) { match[20] = sess->l4_proto; clib_memcpy (&match[22], &sess->side[1 - is_out].addr.ip6, 16); clib_memcpy (&match[38], &sess->side[is_out].addr.ip4, 16); *(u16 *) & match[54] = htons (sess->side[1 - is_out].port); *(u16 *) & match[56] = htons (sess->side[is_out].port); } else { match[23] = sess->l4_proto; clib_memcpy (&match[26], &sess->side[1 - is_out].addr.ip6, 4); clib_memcpy (&match[30], &sess->side[is_out].addr.ip4, 4); *(u16 *) & match[34] = htons (sess->side[1 - is_out].port); *(u16 *) & match[36] = htons (sess->side[is_out].port); } } static void delete_session (l2sess_main_t * sm, u32 sw_if_index, u32 session_index) { vnet_classify_main_t *cm = &vnet_classify_main; u8 match[5 * 16]; /* For building the mock of the packet to delete the classifier session */ u32 session_tables[2] = { ~0, ~0 }; l2s_session_t *sess = sm->sessions + session_index; if (pool_is_free (sm->sessions, sess)) { sm->counter_attempted_delete_free_session++; return; } l2sess_get_session_tables (sm, sw_if_index, 0, sess->is_ip6, sess->l4_proto, session_tables); if (session_tables[1] != ~0) { build_match_from_session (sm, match, sess, 1); vnet_classify_add_del_session (cm, session_tables[1], match, 0, 0, 0, 0, 0, 0); } if (session_tables[1] != ~0) { build_match_from_session (sm, match, sess, 1); vnet_classify_add_del_session (cm, session_tables[1], match, 0, 0, 0, 0, 0, 0); } pool_put (sm->sessions, sess); } static void udp_session_account_buffer (vlib_buffer_t * b0, l2s_session_t * s, int which_side, u64 now) { l2s_session_side_t *ss = &s->side[which_side]; ss->active_time = now; ss->n_packets++; ss->n_bytes += b0->current_data + b0->current_length; } static inline u64 udp_session_get_timeout (l2sess_main_t * sm, l2s_session_t * sess, u64 now) { return (sm->udp_session_idle_timeout); } static void tcp_session_account_buffer (vlib_buffer_t * b0, l2s_session_t * s, int which_side, u64 now) { l2s_session_side_t *ss = &s->side[which_side]; ss->active_time = now; ss->n_packets++; ss->n_bytes += b0->current_data + b0->current_length; /* Very very lightweight TCP state tracking: just record which flags were seen */ s->tcp_flags_seen |= l2sess_get_tcp_flags (b0, s->is_ip6) << (8 * which_side); } /* * Since we are tracking for the purposes of timing the sessions out, * we mostly care about two states: established (maximize the idle timeouts) * and transient (halfopen/halfclosed/reset) - we need to have a reasonably short timeout to * quickly get rid of sessions but not short enough to violate the TCP specs. */ static inline u64 tcp_session_get_timeout (l2sess_main_t * sm, l2s_session_t * sess, u64 now) { /* seen both SYNs and ACKs but not FINs means we are in establshed state */ u16 masked_flags = sess->tcp_flags_seen & ((TCP_FLAGS_RSTFINACKSYN << 8) + TCP_FLAGS_RSTFINACKSYN); if (((TCP_FLAGS_ACKSYN << 8) + TCP_FLAGS_ACKSYN) == masked_flags) { return (sm->tcp_session_idle_timeout); } else { return (sm->tcp_session_transient_timeout); } } static inline u64 session_get_timeout (l2sess_main_t * sm, l2s_session_t * sess, u64 now) { u64 timeout; switch (sess->l4_proto) { case 6: timeout = tcp_session_get_timeout (sm, sess, now); break; case 17: timeout = udp_session_get_timeout (sm, sess, now); break; default: timeout = 0; } return timeout; } static inline u64 get_session_last_active_time(l2s_session_t * sess) { u64 last_active = sess->side[0].active_time > sess->side[1].active_time ? sess->side[0].active_time : sess->side[1]. active_time; return last_active; } static int session_is_alive (l2sess_main_t * sm, l2s_session_t * sess, u64 now, u64 *last_active_cache) { u64 last_active = get_session_last_active_time(sess); u64 timeout = session_get_timeout (sm, sess, now); int is_alive = ((now - last_active) < timeout); if (last_active_cache) *last_active_cache = last_active; return is_alive; } static void check_idle_sessions (l2sess_main_t * sm, u32 sw_if_index, u64 now) { sm->timer_wheel_next_expiring_time = 0; sm->data_from_advancing_timing_wheel = timing_wheel_advance (&sm->timing_wheel, now, sm->data_from_advancing_timing_wheel, &sm->timer_wheel_next_expiring_time); #ifdef DEBUG_SESSIONS_VERBOSE { clib_time_t *ct = &sm->vlib_main->clib_time; f64 ctime; ctime = now * ct->seconds_per_clock; clib_warning ("Now : %U", format_time_interval, "h:m:s:u", ctime); ctime = sm->timer_wheel_next_expiring_time * ct->seconds_per_clock; clib_warning ("Next expire: %U", format_time_interval, "h:m:s:u", ctime); clib_warning ("Expired items: %d", (int) vec_len (sm->data_from_advancing_timing_wheel)); } #endif sm->timer_wheel_next_expiring_time = now + sm->timer_wheel_tick; if (PREDICT_FALSE ( 0 == sm->data_from_advancing_timing_wheel )) { return; } if (PREDICT_FALSE (_vec_len (sm->data_from_advancing_timing_wheel) > 0)) { uword i; for (i = 0; i < _vec_len (sm->data_from_advancing_timing_wheel); i++) { u32 session_index = sm->data_from_advancing_timing_wheel[i]; if (!pool_is_free_index (sm->sessions, session_index)) { l2s_session_t *sess = sm->sessions + session_index; u64 last_active; if (session_is_alive (sm, sess, now, &last_active)) { #ifdef DEBUG_SESSIONS clib_warning ("Restarting timer for session %d", (int) session_index); #endif /* Pretend we did this in the past, at last_active moment */ timing_wheel_insert (&sm->timing_wheel, last_active + session_get_timeout (sm, sess, last_active), session_index); } else { #ifdef DEBUG_SESSIONS clib_warning ("Deleting session %d", (int) session_index); #endif delete_session (sm, sw_if_index, session_index); } } } _vec_len (sm->data_from_advancing_timing_wheel) = 0; } } static uword l2sess_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame, int node_is_out, int node_is_ip6, int node_is_track, u32 *feat_next_node_index) { u32 n_left_from, *from, *to_next; l2sess_next_t next_index; u32 pkts_swapped = 0; u32 feature_bitmap0; u32 trace_flags0; l2sess_main_t *sm = &l2sess_main; from = vlib_frame_vector_args (frame); n_left_from = frame->n_vectors; next_index = node->cached_next_index; while (n_left_from > 0) { u32 n_left_to_next; vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); /* Only a single loop for now for simplicity */ while (n_left_from > 0 && n_left_to_next > 0) { u32 bi0; vlib_buffer_t *b0; u32 next0 = L2SESS_NEXT_DROP; u32 sw_if_index0; //ethernet_header_t *en0; /* speculatively enqueue b0 to the current next frame */ bi0 = from[0]; to_next[0] = bi0; from += 1; to_next += 1; n_left_from -= 1; n_left_to_next -= 1; b0 = vlib_get_buffer (vm, bi0); //en0 = vlib_buffer_get_current (b0); /* * node_is_out : 1 = is output, 0 = is input * node_is_ip6 : 1 = is ip6, 0 = is ip4 * node_is_track : 1 = is a state tracking node, 0 - is a session addition node * * The below code adjust the behavior according to these parameters. */ { u32 session_tables[2] = { ~0, ~0 }; u32 session_nexts[2] = { ~0, ~0 }; u8 l4_proto; u64 now = clib_cpu_time_now (); trace_flags0 = 0; if (node_is_out) { sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_TX]; } else { sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX]; } /* potentially also remove the nodes here */ feature_bitmap0 = vnet_buffer (b0)->l2.feature_bitmap; if (node_is_track) { u32 sess_index = vnet_buffer (b0)->l2_classify.opaque_index; l2s_session_t *sess = sm->sessions + sess_index; l4_proto = sess->l4_proto; if (session_is_alive (sm, sess, now, 0)) { if (6 == l4_proto) { tcp_session_account_buffer (b0, sess, node_is_out, now); } else { udp_session_account_buffer (b0, sess, node_is_out, now); } } else { timing_wheel_delete (&sm->timing_wheel, sess_index); delete_session (sm, sw_if_index0, sess_index); /* FIXME: drop the packet that hit the obsolete node, for now. We really ought to recycle it. */ next0 = 0; } } else { /* * "-add" node: take l2opaque which arrived to us, and deduce * the tables out of that. ~0 means the topmost classifier table * applied for this AF on the RX(for input)/TX(for output)) sw_if_index. * Also add the mirrored session to the paired table. */ l2s_session_t *sess; u32 sess_index; l4_proto = l2sess_get_l4_proto (b0, node_is_ip6); pool_get (sm->sessions, sess); sess_index = sess - sm->sessions; sess->create_time = now; sess->side[node_is_out].active_time = now; sess->side[1 - node_is_out].active_time = now; sess->l4_proto = l4_proto; sess->is_ip6 = node_is_ip6; if (node_is_ip6) { session_store_ip6_l3l4_info (b0, sess, node_is_out); } else { session_store_ip4_l3l4_info (b0, sess, node_is_out); } l2sess_get_session_tables (sm, sw_if_index0, node_is_out, node_is_ip6, l4_proto, session_tables); l2sess_get_session_nexts (sm, sw_if_index0, node_is_out, node_is_ip6, l4_proto, session_nexts); l2sess_flip_l3l4_fields (b0, node_is_ip6, l4_proto); if (session_tables[1] != ~0) { l2sess_add_session (b0, node_is_out, node_is_ip6, session_tables[1], session_nexts[1], sess_index); } l2sess_flip_l3l4_fields (b0, node_is_ip6, l4_proto); if (session_tables[0] != ~0) { l2sess_add_session (b0, node_is_out, node_is_ip6, session_tables[0], session_nexts[0], sess_index); } if (6 == sess->l4_proto) { tcp_session_account_buffer (b0, sess, node_is_out, now); } else { udp_session_account_buffer (b0, sess, node_is_out, now); } timing_wheel_insert (&sm->timing_wheel, now + session_get_timeout (sm, sess, now), sess_index); } if (now >= sm->timer_wheel_next_expiring_time) { check_idle_sessions (sm, sw_if_index0, now); } next0 = feat_bitmap_get_next_node_index (feat_next_node_index, feature_bitmap0); if (next0 >= node->n_next_nodes) { trace_flags0 |= 1; } if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE) && (b0->flags & VLIB_BUFFER_IS_TRACED))) { l2sess_trace_t *t = vlib_add_trace (vm, node, b0, sizeof (*t)); t->sw_if_index = sw_if_index0; t->next_index = next0; t->trace_flags = trace_flags0; t->l4_proto = l4_proto; t->session_tables[0] = session_tables[0]; t->session_tables[1] = session_tables[1]; t->session_nexts[0] = session_nexts[0]; t->session_nexts[1] = session_nexts[1]; } } pkts_swapped += 1; if (next0 >= node->n_next_nodes) { next0 = 0; } /* verify speculative enqueue, maybe switch current next frame */ vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, n_left_to_next, bi0, next0); } vlib_put_next_frame (vm, node, next_index, n_left_to_next); } vlib_node_increment_counter (vm, node->node_index, L2SESS_ERROR_SWAPPED, pkts_swapped); return frame->n_vectors; } #define _(node_name, node_var, is_out, is_ip6, is_track) \ static uword \ node_var ## node_fn (vlib_main_t * vm, \ vlib_node_runtime_t * node, \ vlib_frame_t * frame) \ { \ l2sess_main_t *sm = &l2sess_main; \ return l2sess_node_fn(vm, node, frame, \ is_out, is_ip6, is_track, \ sm->node_var ## _feat_next_node_index); \ } \ VLIB_REGISTER_NODE (node_var) = { \ .function = node_var ## node_fn, \ .name = node_name, \ .vector_size = sizeof (u32), \ .format_trace = format_ ## node_var ## _trace, \ .type = VLIB_NODE_TYPE_INTERNAL, \ \ .n_errors = ARRAY_LEN(l2sess_error_strings), \ .error_strings = l2sess_error_strings, \ \ .n_next_nodes = L2SESS_N_NEXT, \ .next_nodes = { \ [L2SESS_NEXT_DROP] = "error-drop", \ }, \ }; foreach_l2sess_node #undef _