Line data Source code
1 : /*
2 : * ct6_in2out.c - ip6 connection tracker, inside-to-outside path
3 : *
4 : * Copyright (c) 2019 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 : #include <vlib/vlib.h>
18 : #include <vnet/vnet.h>
19 : #include <vppinfra/error.h>
20 : #include <ct6/ct6.h>
21 :
22 : typedef struct
23 : {
24 : u32 sw_if_index;
25 : u32 next_index;
26 : u32 session_index;
27 : } ct6_in2out_trace_t;
28 :
29 : #ifndef CLIB_MARCH_VARIANT
30 :
31 : /* packet trace format function */
32 : static u8 *
33 0 : format_ct6_in2out_trace (u8 * s, va_list * args)
34 : {
35 0 : CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
36 0 : CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
37 0 : ct6_in2out_trace_t *t = va_arg (*args, ct6_in2out_trace_t *);
38 :
39 0 : s = format (s, "CT6_IN2OUT: sw_if_index %d, next index %d session %d\n",
40 : t->sw_if_index, t->next_index, t->session_index);
41 0 : return s;
42 : }
43 :
44 : vlib_node_registration_t ct6_in2out_node;
45 :
46 : #endif /* CLIB_MARCH_VARIANT */
47 :
48 : #define foreach_ct6_in2out_error \
49 : _(PROCESSED, "ct6 packets processed") \
50 : _(CREATED, "ct6 sessions created") \
51 : _(RECYCLED, "ct6 sessions recycled")
52 :
53 : typedef enum
54 : {
55 : #define _(sym,str) CT6_IN2OUT_ERROR_##sym,
56 : foreach_ct6_in2out_error
57 : #undef _
58 : CT6_IN2OUT_N_ERROR,
59 : } ct6_in2out_error_t;
60 :
61 : #ifndef CLIB_MARCH_VARIANT
62 : static char *ct6_in2out_error_strings[] = {
63 : #define _(sym,string) string,
64 : foreach_ct6_in2out_error
65 : #undef _
66 : };
67 : #endif /* CLIB_MARCH_VARIANT */
68 :
69 : typedef enum
70 : {
71 : CT6_IN2OUT_NEXT_DROP,
72 : CT6_IN2OUT_N_NEXT,
73 : } ct6_next_t;
74 :
75 : #ifndef CLIB_MARCH_VARIANT
76 : ct6_session_t *
77 0 : ct6_create_or_recycle_session (ct6_main_t * cmp,
78 : clib_bihash_kv_48_8_t * kvpp, f64 now,
79 : u32 my_thread_index, u32 * recyclep,
80 : u32 * createp)
81 : {
82 : ct6_session_t *s0;
83 :
84 : /* Empty arena? */
85 0 : if (PREDICT_FALSE (cmp->last_index[my_thread_index] == ~0))
86 0 : goto alloc0;
87 :
88 : /* Look at the least-recently-used session */
89 0 : s0 = pool_elt_at_index (cmp->sessions[my_thread_index],
90 : cmp->last_index[my_thread_index]);
91 :
92 0 : if (CLIB_DEBUG > 0 && s0->expires < now)
93 0 : clib_warning ("session %d expired %.2f time now %.2f",
94 : s0 - cmp->sessions[my_thread_index], s0->expires, now);
95 :
96 0 : if (CLIB_DEBUG > 0 && pool_elts (cmp->sessions[my_thread_index]) >=
97 0 : cmp->max_sessions_per_worker)
98 0 : clib_warning ("recycle session %d have %d max %d",
99 : s0 - cmp->sessions[my_thread_index],
100 : pool_elts (cmp->sessions[my_thread_index]),
101 : cmp->max_sessions_per_worker);
102 :
103 : /* Session expired, or we have as many sessions as is allowed by law? */
104 0 : if ((s0->expires < now) || (pool_elts (cmp->sessions[my_thread_index])
105 0 : >= cmp->max_sessions_per_worker))
106 : {
107 : /* recycle the session */
108 0 : if (clib_bihash_add_del_48_8 (&cmp->session_hash,
109 : (clib_bihash_kv_48_8_t *) s0,
110 : 0 /* is_add */ ) < 0)
111 0 : clib_warning ("session %d not found in hash?",
112 : s0 - cmp->sessions[my_thread_index]);
113 :
114 0 : ct6_lru_remove (cmp, s0);
115 0 : *recyclep += 1;
116 : }
117 : else
118 : {
119 0 : alloc0:
120 : /* Allocate a fresh session */
121 0 : pool_get (cmp->sessions[my_thread_index], s0);
122 0 : *createp += 1;
123 : }
124 :
125 : /* Session setup */
126 0 : memset (s0, 0, sizeof (*s0));
127 0 : clib_memcpy_fast (s0, kvpp, sizeof (ct6_session_key_t));
128 0 : s0->thread_index = my_thread_index;
129 0 : s0->expires = now + cmp->session_timeout_interval;
130 0 : kvpp->value = s0 - cmp->sessions[my_thread_index];
131 0 : clib_bihash_add_del_48_8 (&cmp->session_hash, kvpp, 1 /* is_add */ );
132 0 : ct6_lru_add (cmp, s0, now);
133 0 : return s0;
134 : }
135 : #endif /* CLIB_MARCH_VARIANT */
136 :
137 : always_inline uword
138 0 : ct6_in2out_inline (vlib_main_t * vm,
139 : vlib_node_runtime_t * node, vlib_frame_t * frame,
140 : int is_trace)
141 : {
142 : u32 n_left_from, *from;
143 : vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b;
144 : u16 nexts[VLIB_FRAME_SIZE], *next;
145 0 : ct6_main_t *cmp = &ct6_main;
146 0 : u32 my_thread_index = vm->thread_index;
147 0 : f64 now = vlib_time_now (vm);
148 0 : u32 created = 0;
149 0 : u32 recycled = 0;
150 :
151 0 : from = vlib_frame_vector_args (frame);
152 0 : n_left_from = frame->n_vectors;
153 :
154 0 : vlib_get_buffers (vm, from, bufs, n_left_from);
155 0 : b = bufs;
156 0 : next = nexts;
157 :
158 : #if 0
159 : while (n_left_from >= 4)
160 : {
161 : /* Prefetch next iteration. */
162 : if (PREDICT_TRUE (n_left_from >= 8))
163 : {
164 : vlib_prefetch_buffer_header (b[4], STORE);
165 : vlib_prefetch_buffer_header (b[5], STORE);
166 : vlib_prefetch_buffer_header (b[6], STORE);
167 : vlib_prefetch_buffer_header (b[7], STORE);
168 : clib_prefetch_store (b[4]->data);
169 : clib_prefetch_store (b[5]->data);
170 : clib_prefetch_store (b[6]->data);
171 : clib_prefetch_store (b[7]->data);
172 : }
173 :
174 : /* $$$$ process 4x pkts right here */
175 : next[0] = 0;
176 : next[1] = 0;
177 : next[2] = 0;
178 : next[3] = 0;
179 :
180 : if (is_trace)
181 : {
182 : if (b[0]->flags & VLIB_BUFFER_IS_TRACED)
183 : {
184 : ct6_trace_t *t = vlib_add_trace (vm, node, b[0], sizeof (*t));
185 : t->next_index = next[0];
186 : t->sw_if_index = vnet_buffer (b[0])->sw_if_index[VLIB_RX];
187 : }
188 : if (b[1]->flags & VLIB_BUFFER_IS_TRACED)
189 : {
190 : ct6_trace_t *t = vlib_add_trace (vm, node, b[1], sizeof (*t));
191 : t->next_index = next[1];
192 : t->sw_if_index = vnet_buffer (b[1])->sw_if_index[VLIB_RX];
193 : }
194 : if (b[2]->flags & VLIB_BUFFER_IS_TRACED)
195 : {
196 : ct6_trace_t *t = vlib_add_trace (vm, node, b[2], sizeof (*t));
197 : t->next_index = next[2];
198 : t->sw_if_index = vnet_buffer (b[2])->sw_if_index[VLIB_RX];
199 : }
200 : if (b[3]->flags & VLIB_BUFFER_IS_TRACED)
201 : {
202 : ct6_trace_t *t = vlib_add_trace (vm, node, b[3], sizeof (*t));
203 : t->next_index = next[3];
204 : t->sw_if_index = vnet_buffer (b[3])->sw_if_index[VLIB_RX];
205 : }
206 : }
207 :
208 : b += 4;
209 : next += 4;
210 : n_left_from -= 4;
211 : }
212 : #endif
213 :
214 0 : while (n_left_from > 0)
215 : {
216 : clib_bihash_kv_48_8_t kvp0;
217 : ct6_session_key_t *key0;
218 : ct6_session_t *s0;
219 0 : u32 session_index0 = ~0;
220 : u32 next0, delta0;
221 : ethernet_header_t *e0;
222 :
223 : ip6_header_t *ip0;
224 : udp_header_t *udp0;
225 :
226 : /* $$$ Set to 0 for pg testing */
227 : if (1)
228 : {
229 0 : vnet_feature_next (&next0, b[0]);
230 0 : next[0] = next0;
231 : }
232 : else
233 : next[0] = CT6_IN2OUT_NEXT_DROP;
234 :
235 : /*
236 : * This is an output feature which runs at the last possible
237 : * moment. Assume an ethernet header. Make sure the packet is
238 : * actually ipv6 before we do anything else.
239 : *
240 : * Unfortunately, we have to re-parse the L2 header.
241 : */
242 :
243 0 : e0 = vlib_buffer_get_current (b[0]);
244 0 : delta0 = sizeof (*e0);
245 0 : delta0 += (e0->type == clib_net_to_host_u16 (ETHERNET_TYPE_VLAN))
246 0 : ? 4 : 0;
247 0 : delta0 += (e0->type == clib_net_to_host_u16 (ETHERNET_TYPE_DOT1AD))
248 0 : ? 8 : 0;
249 :
250 0 : if (PREDICT_TRUE (delta0 == sizeof (*e0)))
251 : {
252 0 : if (e0->type != clib_host_to_net_u16 (ETHERNET_TYPE_IP6))
253 0 : goto trace0;
254 : }
255 : else
256 : {
257 0 : u16 *tagged_etype_ptr = vlib_buffer_get_current (b[0]) + delta0 - 2;
258 0 : if (*tagged_etype_ptr != clib_host_to_net_u16 (ETHERNET_TYPE_IP6))
259 0 : goto trace0;
260 : }
261 :
262 0 : ip0 = (ip6_header_t *) (vlib_buffer_get_current (b[0]) + delta0);
263 :
264 : /*
265 : * Pass non-global unicast traffic
266 : */
267 0 : if (PREDICT_FALSE (!ip6_address_is_global_unicast (&ip0->src_address)
268 : ||
269 : !ip6_address_is_global_unicast (&ip0->dst_address)))
270 0 : goto trace0;
271 : /* Pass non-udp, non-tcp traffic */
272 0 : if (PREDICT_FALSE (ip0->protocol != IP_PROTOCOL_TCP &&
273 : ip0->protocol != IP_PROTOCOL_UDP))
274 0 : goto trace0;
275 :
276 0 : udp0 = ip6_next_header (ip0);
277 :
278 : /*
279 : * See if we know about this flow.
280 : * Key set up for the out2in path, the performant case
281 : */
282 0 : key0 = (ct6_session_key_t *) & kvp0;
283 0 : clib_memcpy_fast (&key0->src, &ip0->dst_address,
284 : sizeof (ip6_address_t));
285 0 : clib_memcpy_fast (&key0->dst, &ip0->src_address,
286 : sizeof (ip6_address_t));
287 0 : key0->as_u64[4] = 0;
288 0 : key0->as_u64[5] = 0;
289 0 : key0->sport = udp0->dst_port;
290 0 : key0->dport = udp0->src_port;
291 0 : key0->proto = ip0->protocol;
292 :
293 : /* Need to create a new session? */
294 0 : if (clib_bihash_search_48_8 (&cmp->session_hash, &kvp0, &kvp0) < 0)
295 : {
296 : s0 =
297 0 : ct6_create_or_recycle_session (cmp, &kvp0, now, my_thread_index,
298 : &recycled, &created);
299 0 : session_index0 = kvp0.value;
300 : }
301 : else
302 : {
303 0 : s0 = pool_elt_at_index (cmp->sessions[my_thread_index], kvp0.value);
304 0 : session_index0 = kvp0.value;
305 0 : ct6_update_session_hit (cmp, s0, now);
306 : }
307 :
308 0 : trace0:
309 0 : if (is_trace)
310 : {
311 0 : if (b[0]->flags & VLIB_BUFFER_IS_TRACED)
312 : {
313 : ct6_in2out_trace_t *t =
314 0 : vlib_add_trace (vm, node, b[0], sizeof (*t));
315 0 : t->next_index = next[0];
316 0 : t->sw_if_index = vnet_buffer (b[0])->sw_if_index[VLIB_RX];
317 0 : t->session_index = session_index0;
318 : }
319 : }
320 :
321 0 : b += 1;
322 0 : next += 1;
323 0 : n_left_from -= 1;
324 : }
325 :
326 0 : vlib_buffer_enqueue_to_next (vm, node, from, nexts, frame->n_vectors);
327 :
328 0 : vlib_node_increment_counter (vm, node->node_index,
329 0 : CT6_IN2OUT_ERROR_PROCESSED, frame->n_vectors);
330 0 : vlib_node_increment_counter (vm, node->node_index,
331 : CT6_IN2OUT_ERROR_CREATED, created);
332 0 : vlib_node_increment_counter (vm, node->node_index,
333 : CT6_IN2OUT_ERROR_RECYCLED, recycled);
334 :
335 0 : return frame->n_vectors;
336 : }
337 :
338 2300 : VLIB_NODE_FN (ct6_in2out_node) (vlib_main_t * vm, vlib_node_runtime_t * node,
339 : vlib_frame_t * frame)
340 : {
341 0 : if (PREDICT_FALSE (node->flags & VLIB_NODE_FLAG_TRACE))
342 0 : return ct6_in2out_inline (vm, node, frame, 1 /* is_trace */ );
343 : else
344 0 : return ct6_in2out_inline (vm, node, frame, 0 /* is_trace */ );
345 : }
346 :
347 : /* *INDENT-OFF* */
348 : #ifndef CLIB_MARCH_VARIANT
349 163052 : VLIB_REGISTER_NODE (ct6_in2out_node) =
350 : {
351 : .name = "ct6-in2out",
352 : .vector_size = sizeof (u32),
353 : .format_trace = format_ct6_in2out_trace,
354 : .type = VLIB_NODE_TYPE_INTERNAL,
355 :
356 : .n_errors = ARRAY_LEN(ct6_in2out_error_strings),
357 : .error_strings = ct6_in2out_error_strings,
358 :
359 : .n_next_nodes = CT6_IN2OUT_N_NEXT,
360 :
361 : /* edit / add dispositions here */
362 : .next_nodes = {
363 : [CT6_IN2OUT_NEXT_DROP] = "error-drop",
364 : },
365 : .unformat_buffer = unformat_ethernet_header,
366 : };
367 : #endif /* CLIB_MARCH_VARIANT */
368 : /* *INDENT-ON* */
369 :
370 : /*
371 : * fd.io coding-style-patch-verification: ON
372 : *
373 : * Local Variables:
374 : * eval: (c-set-style "gnu")
375 : * End:
376 : */
|