ipsec: Performance improvement of ipsec4_output_node using flow cache
[vpp.git] / src / vnet / ipsec / ipsec_output.c
1 /*
2  * ipsec_output.c : IPSec output node
3  *
4  * Copyright (c) 2015 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 <vnet/vnet.h>
19 #include <vnet/api_errno.h>
20 #include <vnet/ip/ip.h>
21
22 #include <vnet/ipsec/ipsec.h>
23 #include <vnet/ipsec/ipsec_io.h>
24
25 #define foreach_ipsec_output_error                   \
26  _(RX_PKTS, "IPSec pkts received")                   \
27  _(POLICY_DISCARD, "IPSec policy discard")           \
28  _(POLICY_NO_MATCH, "IPSec policy (no match)")       \
29  _(POLICY_PROTECT, "IPSec policy protect")           \
30  _(POLICY_BYPASS, "IPSec policy bypass")             \
31  _(ENCAPS_FAILED, "IPSec encapsulation failed")
32
33 typedef enum
34 {
35 #define _(sym,str) IPSEC_OUTPUT_ERROR_##sym,
36   foreach_ipsec_output_error
37 #undef _
38     IPSEC_DECAP_N_ERROR,
39 } ipsec_output_error_t;
40
41 static char *ipsec_output_error_strings[] = {
42 #define _(sym,string) string,
43   foreach_ipsec_output_error
44 #undef _
45 };
46
47 typedef struct
48 {
49   u32 spd_id;
50   u32 policy_id;
51 } ipsec_output_trace_t;
52
53 /* packet trace format function */
54 static u8 *
55 format_ipsec_output_trace (u8 * s, va_list * args)
56 {
57   CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
58   CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
59   ipsec_output_trace_t *t = va_arg (*args, ipsec_output_trace_t *);
60
61   s = format (s, "spd %u policy %d", t->spd_id, t->policy_id);
62
63   return s;
64 }
65
66 always_inline void
67 ipsec4_out_spd_add_flow_cache_entry (ipsec_main_t *im, u8 pr, u32 la, u32 ra,
68                                      u16 lp, u16 rp, u32 pol_id)
69 {
70   u64 hash;
71   u8 overwrite = 0, stale_overwrite = 0;
72   ipsec4_spd_5tuple_t ip4_5tuple = { .ip4_addr = { (ip4_address_t) la,
73                                                    (ip4_address_t) ra },
74                                      .port = { lp, rp },
75                                      .proto = pr };
76
77   ip4_5tuple.kv_16_8.value = (((u64) pol_id) << 32) | ((u64) im->epoch_count);
78
79   hash = ipsec4_hash_16_8 (&ip4_5tuple.kv_16_8);
80   hash &= (im->ipsec4_out_spd_hash_num_buckets - 1);
81
82   ipsec_spinlock_lock (&im->ipsec4_out_spd_hash_tbl[hash].bucket_lock);
83   /* Check if we are overwriting an existing entry so we know
84   whether to increment the flow cache counter. Since flow
85   cache counter is reset on any policy add/remove, but
86   hash table values are not, we also need to check if the entry
87   we are overwriting is stale or not. If it's a stale entry
88   overwrite, we still want to increment flow cache counter */
89   overwrite = (im->ipsec4_out_spd_hash_tbl[hash].value != 0);
90   /* Check for stale entry by comparing with current epoch count */
91   if (PREDICT_FALSE (overwrite))
92     stale_overwrite =
93       (im->epoch_count !=
94        ((u32) (im->ipsec4_out_spd_hash_tbl[hash].value & 0xFFFFFFFF)));
95   clib_memcpy_fast (&im->ipsec4_out_spd_hash_tbl[hash], &ip4_5tuple.kv_16_8,
96                     sizeof (ip4_5tuple.kv_16_8));
97   ipsec_spinlock_unlock (&im->ipsec4_out_spd_hash_tbl[hash].bucket_lock);
98
99   /* Increment the counter to track active flow cache entries
100     when entering a fresh entry or overwriting a stale one */
101   if (!overwrite || stale_overwrite)
102     clib_atomic_fetch_add_relax (&im->ipsec4_out_spd_flow_cache_entries, 1);
103
104   return;
105 }
106
107 always_inline ipsec_policy_t *
108 ipsec4_out_spd_find_flow_cache_entry (ipsec_main_t *im, u8 pr, u32 la, u32 ra,
109                                       u16 lp, u16 rp)
110 {
111   ipsec_policy_t *p = NULL;
112   ipsec4_hash_kv_16_8_t kv_result;
113   u64 hash;
114
115   if (PREDICT_FALSE ((pr != IP_PROTOCOL_TCP) && (pr != IP_PROTOCOL_UDP) &&
116                      (pr != IP_PROTOCOL_SCTP)))
117     {
118       lp = 0;
119       rp = 0;
120     }
121   ipsec4_spd_5tuple_t ip4_5tuple = { .ip4_addr = { (ip4_address_t) la,
122                                                    (ip4_address_t) ra },
123                                      .port = { lp, rp },
124                                      .proto = pr };
125
126   hash = ipsec4_hash_16_8 (&ip4_5tuple.kv_16_8);
127   hash &= (im->ipsec4_out_spd_hash_num_buckets - 1);
128
129   ipsec_spinlock_lock (&im->ipsec4_out_spd_hash_tbl[hash].bucket_lock);
130   kv_result = im->ipsec4_out_spd_hash_tbl[hash];
131   ipsec_spinlock_unlock (&im->ipsec4_out_spd_hash_tbl[hash].bucket_lock);
132
133   if (ipsec4_hash_key_compare_16_8 ((u64 *) &ip4_5tuple.kv_16_8,
134                                     (u64 *) &kv_result))
135     {
136       if (im->epoch_count == ((u32) (kv_result.value & 0xFFFFFFFF)))
137         {
138           /* Get the policy based on the index */
139           p =
140             pool_elt_at_index (im->policies, ((u32) (kv_result.value >> 32)));
141         }
142     }
143
144   return p;
145 }
146
147 always_inline ipsec_policy_t *
148 ipsec_output_policy_match (ipsec_spd_t *spd, u8 pr, u32 la, u32 ra, u16 lp,
149                            u16 rp, u8 flow_cache_enabled)
150 {
151   ipsec_main_t *im = &ipsec_main;
152   ipsec_policy_t *p;
153   u32 *i;
154
155   if (!spd)
156     return 0;
157
158   vec_foreach (i, spd->policies[IPSEC_SPD_POLICY_IP4_OUTBOUND])
159   {
160     p = pool_elt_at_index (im->policies, *i);
161     if (PREDICT_FALSE (p->protocol && (p->protocol != pr)))
162       continue;
163
164     if (ra < clib_net_to_host_u32 (p->raddr.start.ip4.as_u32))
165       continue;
166
167     if (ra > clib_net_to_host_u32 (p->raddr.stop.ip4.as_u32))
168       continue;
169
170     if (la < clib_net_to_host_u32 (p->laddr.start.ip4.as_u32))
171       continue;
172
173     if (la > clib_net_to_host_u32 (p->laddr.stop.ip4.as_u32))
174       continue;
175
176     if (PREDICT_FALSE ((pr != IP_PROTOCOL_TCP) && (pr != IP_PROTOCOL_UDP) &&
177                        (pr != IP_PROTOCOL_SCTP)))
178       {
179         lp = 0;
180         rp = 0;
181         goto add_flow_cache;
182       }
183
184     if (lp < p->lport.start)
185       continue;
186
187     if (lp > p->lport.stop)
188       continue;
189
190     if (rp < p->rport.start)
191       continue;
192
193     if (rp > p->rport.stop)
194       continue;
195
196   add_flow_cache:
197     if (flow_cache_enabled)
198       {
199         /* Add an Entry in Flow cache */
200         ipsec4_out_spd_add_flow_cache_entry (
201           im, pr, clib_host_to_net_u32 (la), clib_host_to_net_u32 (ra),
202           clib_host_to_net_u16 (lp), clib_host_to_net_u16 (rp), *i);
203       }
204
205     return p;
206   }
207   return 0;
208 }
209
210 always_inline uword
211 ip6_addr_match_range (ip6_address_t * a, ip6_address_t * la,
212                       ip6_address_t * ua)
213 {
214   if ((memcmp (a->as_u64, la->as_u64, 2 * sizeof (u64)) >= 0) &&
215       (memcmp (a->as_u64, ua->as_u64, 2 * sizeof (u64)) <= 0))
216     return 1;
217   return 0;
218 }
219
220 always_inline ipsec_policy_t *
221 ipsec6_output_policy_match (ipsec_spd_t * spd,
222                             ip6_address_t * la,
223                             ip6_address_t * ra, u16 lp, u16 rp, u8 pr)
224 {
225   ipsec_main_t *im = &ipsec_main;
226   ipsec_policy_t *p;
227   u32 *i;
228
229   if (!spd)
230     return 0;
231
232   vec_foreach (i, spd->policies[IPSEC_SPD_POLICY_IP6_OUTBOUND])
233   {
234     p = pool_elt_at_index (im->policies, *i);
235     if (PREDICT_FALSE (p->protocol && (p->protocol != pr)))
236       continue;
237
238     if (!ip6_addr_match_range (ra, &p->raddr.start.ip6, &p->raddr.stop.ip6))
239       continue;
240
241     if (!ip6_addr_match_range (la, &p->laddr.start.ip6, &p->laddr.stop.ip6))
242       continue;
243
244     if (PREDICT_FALSE
245         ((pr != IP_PROTOCOL_TCP) && (pr != IP_PROTOCOL_UDP)
246          && (pr != IP_PROTOCOL_SCTP)))
247       return p;
248
249     if (lp < p->lport.start)
250       continue;
251
252     if (lp > p->lport.stop)
253       continue;
254
255     if (rp < p->rport.start)
256       continue;
257
258     if (rp > p->rport.stop)
259       continue;
260
261     return p;
262   }
263
264   return 0;
265 }
266
267 static inline uword
268 ipsec_output_inline (vlib_main_t * vm, vlib_node_runtime_t * node,
269                      vlib_frame_t * from_frame, int is_ipv6)
270 {
271   ipsec_main_t *im = &ipsec_main;
272
273   u32 *from, *to_next = 0, thread_index;
274   u32 n_left_from, sw_if_index0, last_sw_if_index = (u32) ~ 0;
275   u32 next_node_index = (u32) ~ 0, last_next_node_index = (u32) ~ 0;
276   vlib_frame_t *f = 0;
277   u32 spd_index0 = ~0;
278   ipsec_spd_t *spd0 = 0;
279   int bogus;
280   u64 nc_protect = 0, nc_bypass = 0, nc_discard = 0, nc_nomatch = 0;
281   u8 flow_cache_enabled = im->flow_cache_flag;
282
283   from = vlib_frame_vector_args (from_frame);
284   n_left_from = from_frame->n_vectors;
285   thread_index = vm->thread_index;
286
287   while (n_left_from > 0)
288     {
289       u32 bi0, pi0, bi1;
290       vlib_buffer_t *b0, *b1;
291       ipsec_policy_t *p0 = NULL;
292       ip4_header_t *ip0;
293       ip6_header_t *ip6_0 = 0;
294       udp_header_t *udp0;
295       u32 iph_offset = 0;
296       tcp_header_t *tcp0;
297       u64 bytes0;
298
299       bi0 = from[0];
300       b0 = vlib_get_buffer (vm, bi0);
301       if (n_left_from > 1)
302         {
303           bi1 = from[1];
304           b1 = vlib_get_buffer (vm, bi1);
305           CLIB_PREFETCH (b1, CLIB_CACHE_LINE_BYTES * 2, STORE);
306           vlib_prefetch_buffer_data (b1, LOAD);
307         }
308       sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_TX];
309       iph_offset = vnet_buffer (b0)->ip.save_rewrite_length;
310       ip0 = (ip4_header_t *) ((u8 *) vlib_buffer_get_current (b0)
311                               + iph_offset);
312
313       /* lookup for SPD only if sw_if_index is changed */
314       if (PREDICT_FALSE (last_sw_if_index != sw_if_index0))
315         {
316           uword *p = hash_get (im->spd_index_by_sw_if_index, sw_if_index0);
317           ALWAYS_ASSERT (p);
318           spd_index0 = p[0];
319           spd0 = pool_elt_at_index (im->spds, spd_index0);
320           last_sw_if_index = sw_if_index0;
321         }
322
323       if (is_ipv6)
324         {
325           ip6_0 = (ip6_header_t *) ((u8 *) vlib_buffer_get_current (b0)
326                                     + iph_offset);
327
328           udp0 = ip6_next_header (ip6_0);
329 #if 0
330           clib_warning
331             ("packet received from %U port %u to %U port %u spd_id %u",
332              format_ip6_address, &ip6_0->src_address,
333              clib_net_to_host_u16 (udp0->src_port), format_ip6_address,
334              &ip6_0->dst_address, clib_net_to_host_u16 (udp0->dst_port),
335              spd0->id);
336 #endif
337
338           p0 = ipsec6_output_policy_match (spd0,
339                                            &ip6_0->src_address,
340                                            &ip6_0->dst_address,
341                                            clib_net_to_host_u16
342                                            (udp0->src_port),
343                                            clib_net_to_host_u16
344                                            (udp0->dst_port), ip6_0->protocol);
345         }
346       else
347         {
348           udp0 = (udp_header_t *) ((u8 *) ip0 + ip4_header_bytes (ip0));
349
350 #if 0
351           clib_warning ("packet received from %U to %U port %u",
352                         format_ip4_address, ip0->src_address.as_u8,
353                         format_ip4_address, ip0->dst_address.as_u8,
354                         clib_net_to_host_u16 (udp0->dst_port));
355           clib_warning ("sw_if_index0 %u spd_index0 %u spd_id %u",
356                         sw_if_index0, spd_index0, spd0->id);
357 #endif
358
359           /*
360            * Check whether flow cache is enabled.
361            */
362           if (flow_cache_enabled)
363             {
364               p0 = ipsec4_out_spd_find_flow_cache_entry (
365                 im, ip0->protocol, ip0->src_address.as_u32,
366                 ip0->dst_address.as_u32, udp0->src_port, udp0->dst_port);
367             }
368
369           /* Fall back to linear search if flow cache lookup fails */
370           if (p0 == NULL)
371             {
372               p0 = ipsec_output_policy_match (
373                 spd0, ip0->protocol,
374                 clib_net_to_host_u32 (ip0->src_address.as_u32),
375                 clib_net_to_host_u32 (ip0->dst_address.as_u32),
376                 clib_net_to_host_u16 (udp0->src_port),
377                 clib_net_to_host_u16 (udp0->dst_port), flow_cache_enabled);
378             }
379         }
380       tcp0 = (void *) udp0;
381
382       if (PREDICT_TRUE (p0 != NULL))
383         {
384           pi0 = p0 - im->policies;
385
386           vlib_prefetch_combined_counter (&ipsec_spd_policy_counters,
387                                           thread_index, pi0);
388
389           if (is_ipv6)
390             {
391               bytes0 = clib_net_to_host_u16 (ip6_0->payload_length);
392               bytes0 += sizeof (ip6_header_t);
393             }
394           else
395             {
396               bytes0 = clib_net_to_host_u16 (ip0->length);
397             }
398
399           if (p0->policy == IPSEC_POLICY_ACTION_PROTECT)
400             {
401               ipsec_sa_t *sa = 0;
402               nc_protect++;
403               sa = ipsec_sa_get (p0->sa_index);
404               if (sa->protocol == IPSEC_PROTOCOL_ESP)
405                 if (is_ipv6)
406                   next_node_index = im->esp6_encrypt_node_index;
407                 else
408                   next_node_index = im->esp4_encrypt_node_index;
409               else if (is_ipv6)
410                 next_node_index = im->ah6_encrypt_node_index;
411               else
412                 next_node_index = im->ah4_encrypt_node_index;
413               vnet_buffer (b0)->ipsec.sad_index = p0->sa_index;
414
415               if (PREDICT_FALSE (b0->flags & VNET_BUFFER_F_OFFLOAD))
416                 {
417                   vnet_buffer_oflags_t oflags = vnet_buffer (b0)->oflags;
418
419                   /*
420                    * Clearing offload flags before checksum is computed
421                    * It guarantees the cache hit!
422                    */
423                   vnet_buffer_offload_flags_clear (b0, oflags);
424
425                   if (is_ipv6)
426                     {
427                       if (PREDICT_FALSE (oflags &
428                                          VNET_BUFFER_OFFLOAD_F_TCP_CKSUM))
429                         {
430                           tcp0->checksum = ip6_tcp_udp_icmp_compute_checksum (
431                             vm, b0, ip6_0, &bogus);
432                         }
433                       if (PREDICT_FALSE (oflags &
434                                          VNET_BUFFER_OFFLOAD_F_UDP_CKSUM))
435                         {
436                           udp0->checksum = ip6_tcp_udp_icmp_compute_checksum (
437                             vm, b0, ip6_0, &bogus);
438                         }
439                     }
440                   else
441                     {
442                       if (PREDICT_FALSE (oflags &
443                                          VNET_BUFFER_OFFLOAD_F_IP_CKSUM))
444                         {
445                           ip0->checksum = ip4_header_checksum (ip0);
446                         }
447                       if (PREDICT_FALSE (oflags &
448                                          VNET_BUFFER_OFFLOAD_F_TCP_CKSUM))
449                         {
450                           tcp0->checksum =
451                             ip4_tcp_udp_compute_checksum (vm, b0, ip0);
452                         }
453                       if (PREDICT_FALSE (oflags &
454                                          VNET_BUFFER_OFFLOAD_F_UDP_CKSUM))
455                         {
456                           udp0->checksum =
457                             ip4_tcp_udp_compute_checksum (vm, b0, ip0);
458                         }
459                     }
460                 }
461               vlib_buffer_advance (b0, iph_offset);
462             }
463           else if (p0->policy == IPSEC_POLICY_ACTION_BYPASS)
464             {
465               nc_bypass++;
466               next_node_index = get_next_output_feature_node_index (b0, node);
467             }
468           else
469             {
470               nc_discard++;
471               next_node_index = im->error_drop_node_index;
472             }
473           vlib_increment_combined_counter
474             (&ipsec_spd_policy_counters, thread_index, pi0, 1, bytes0);
475         }
476       else
477         {
478           pi0 = ~0;
479           nc_nomatch++;
480           next_node_index = im->error_drop_node_index;
481         }
482
483       from += 1;
484       n_left_from -= 1;
485
486       if (PREDICT_FALSE ((last_next_node_index != next_node_index) || f == 0))
487         {
488           /* if this is not 1st frame */
489           if (f)
490             vlib_put_frame_to_node (vm, last_next_node_index, f);
491
492           last_next_node_index = next_node_index;
493
494           f = vlib_get_frame_to_node (vm, next_node_index);
495
496           /* frame->frame_flags, copy it from node */
497           /* Copy trace flag from next_frame and from runtime. */
498           f->frame_flags |= node->flags & VLIB_NODE_FLAG_TRACE;
499
500           to_next = vlib_frame_vector_args (f);
501         }
502
503       to_next[0] = bi0;
504       to_next += 1;
505       f->n_vectors++;
506
507       if (PREDICT_FALSE (node->flags & VLIB_NODE_FLAG_TRACE) &&
508           PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
509         {
510           ipsec_output_trace_t *tr =
511             vlib_add_trace (vm, node, b0, sizeof (*tr));
512           if (spd0)
513             tr->spd_id = spd0->id;
514           tr->policy_id = pi0;
515         }
516     }
517
518   vlib_put_frame_to_node (vm, next_node_index, f);
519   vlib_node_increment_counter (vm, node->node_index,
520                                IPSEC_OUTPUT_ERROR_POLICY_PROTECT, nc_protect);
521   vlib_node_increment_counter (vm, node->node_index,
522                                IPSEC_OUTPUT_ERROR_POLICY_BYPASS, nc_bypass);
523   vlib_node_increment_counter (vm, node->node_index,
524                                IPSEC_OUTPUT_ERROR_POLICY_DISCARD, nc_discard);
525   vlib_node_increment_counter (vm, node->node_index,
526                                IPSEC_OUTPUT_ERROR_POLICY_NO_MATCH,
527                                nc_nomatch);
528   return from_frame->n_vectors;
529 }
530
531 VLIB_NODE_FN (ipsec4_output_node) (vlib_main_t * vm,
532                                    vlib_node_runtime_t * node,
533                                    vlib_frame_t * frame)
534 {
535   return ipsec_output_inline (vm, node, frame, 0);
536 }
537
538 /* *INDENT-OFF* */
539 VLIB_REGISTER_NODE (ipsec4_output_node) = {
540   .name = "ipsec4-output-feature",
541   .vector_size = sizeof (u32),
542   .format_trace = format_ipsec_output_trace,
543   .type = VLIB_NODE_TYPE_INTERNAL,
544
545   .n_errors = ARRAY_LEN(ipsec_output_error_strings),
546   .error_strings = ipsec_output_error_strings,
547
548   .n_next_nodes = IPSEC_OUTPUT_N_NEXT,
549   .next_nodes = {
550 #define _(s,n) [IPSEC_OUTPUT_NEXT_##s] = n,
551     foreach_ipsec_output_next
552 #undef _
553   },
554 };
555 /* *INDENT-ON* */
556
557 VLIB_NODE_FN (ipsec6_output_node) (vlib_main_t * vm,
558                                    vlib_node_runtime_t * node,
559                                    vlib_frame_t * frame)
560 {
561   return ipsec_output_inline (vm, node, frame, 1);
562 }
563
564 VLIB_REGISTER_NODE (ipsec6_output_node) = {
565   .name = "ipsec6-output-feature",
566   .vector_size = sizeof (u32),
567   .format_trace = format_ipsec_output_trace,
568   .type = VLIB_NODE_TYPE_INTERNAL,
569
570   .n_errors = ARRAY_LEN(ipsec_output_error_strings),
571   .error_strings = ipsec_output_error_strings,
572
573   .n_next_nodes = IPSEC_OUTPUT_N_NEXT,
574   .next_nodes = {
575 #define _(s,n) [IPSEC_OUTPUT_NEXT_##s] = n,
576     foreach_ipsec_output_next
577 #undef _
578   },
579 };
580