acl-plugin: refactor to introduce multiarch dataplane functions
[vpp.git] / src / plugins / acl / session_inlines.h
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
16
17 /* ICMPv4 invert type for stateful ACL */
18 static const u8 icmp4_invmap[] = {
19   [ICMP4_echo_reply] = ICMP4_echo_request + 1,
20   [ICMP4_timestamp_reply] = ICMP4_timestamp_request + 1,
21   [ICMP4_information_reply] = ICMP4_information_request + 1,
22   [ICMP4_address_mask_reply] = ICMP4_address_mask_request + 1
23 };
24
25 /* Supported ICMPv4 messages for session creation */
26 static const u8 icmp4_valid_new[] = {
27   [ICMP4_echo_request] = 1,
28   [ICMP4_timestamp_request] = 1,
29   [ICMP4_information_request] = 1,
30   [ICMP4_address_mask_request] = 1
31 };
32
33 /* ICMPv6 invert type for stateful ACL */
34 static const u8 icmp6_invmap[] = {
35   [ICMP6_echo_reply - 128] = ICMP6_echo_request + 1,
36   [ICMP6_node_information_response - 128] = ICMP6_node_information_request + 1
37 };
38
39 /* Supported ICMPv6 messages for session creation */
40 static const u8 icmp6_valid_new[] = {
41   [ICMP6_echo_request - 128] = 1,
42   [ICMP6_node_information_request - 128] = 1
43 };
44
45 /* IP4 and IP6 protocol numbers of ICMP */
46 static u8 icmp_protos[] = { IP_PROTOCOL_ICMP, IP_PROTOCOL_ICMP6 };
47
48
49
50 always_inline int
51 acl_fa_ifc_has_sessions (acl_main_t * am, int sw_if_index0)
52 {
53   return am->fa_sessions_hash_is_initialized;
54 }
55
56 always_inline int
57 acl_fa_ifc_has_in_acl (acl_main_t * am, int sw_if_index0)
58 {
59   int it_has = clib_bitmap_get (am->fa_in_acl_on_sw_if_index, sw_if_index0);
60   return it_has;
61 }
62
63 always_inline int
64 acl_fa_ifc_has_out_acl (acl_main_t * am, int sw_if_index0)
65 {
66   int it_has = clib_bitmap_get (am->fa_out_acl_on_sw_if_index, sw_if_index0);
67   return it_has;
68 }
69
70 /* Session keys match the packets received, and mirror the packets sent */
71 always_inline u32
72 acl_make_5tuple_session_key (acl_main_t * am, int is_input, int is_ip6,
73                              u32 sw_if_index, fa_5tuple_t * p5tuple_pkt,
74                              fa_5tuple_t * p5tuple_sess)
75 {
76   int src_index = is_input ? 0 : 1;
77   int dst_index = is_input ? 1 : 0;
78   u32 valid_new_sess = 1;
79   p5tuple_sess->addr[src_index] = p5tuple_pkt->addr[0];
80   p5tuple_sess->addr[dst_index] = p5tuple_pkt->addr[1];
81   p5tuple_sess->l4.as_u64 = p5tuple_pkt->l4.as_u64;
82
83   if (PREDICT_TRUE (p5tuple_pkt->l4.proto != icmp_protos[is_ip6]))
84     {
85       p5tuple_sess->l4.port[src_index] = p5tuple_pkt->l4.port[0];
86       p5tuple_sess->l4.port[dst_index] = p5tuple_pkt->l4.port[1];
87     }
88   else
89     {
90       static const u8 *icmp_invmap[] = { icmp4_invmap, icmp6_invmap };
91       static const u8 *icmp_valid_new[] =
92         { icmp4_valid_new, icmp6_valid_new };
93       static const u8 icmp_invmap_size[] = { sizeof (icmp4_invmap),
94         sizeof (icmp6_invmap)
95       };
96       static const u8 icmp_valid_new_size[] = { sizeof (icmp4_valid_new),
97         sizeof (icmp6_valid_new)
98       };
99       int type =
100         is_ip6 ? p5tuple_pkt->l4.port[0] - 128 : p5tuple_pkt->l4.port[0];
101
102       p5tuple_sess->l4.port[0] = p5tuple_pkt->l4.port[0];
103       p5tuple_sess->l4.port[1] = p5tuple_pkt->l4.port[1];
104
105       /*
106        * Invert ICMP type for valid icmp_invmap messages:
107        *  1) input node with outbound ACL interface
108        *  2) output node with inbound ACL interface
109        *
110        */
111       if ((is_input && acl_fa_ifc_has_out_acl (am, sw_if_index)) ||
112           (!is_input && acl_fa_ifc_has_in_acl (am, sw_if_index)))
113         {
114           if (type >= 0 &&
115               type <= icmp_invmap_size[is_ip6] && icmp_invmap[is_ip6][type])
116             {
117               p5tuple_sess->l4.port[0] = icmp_invmap[is_ip6][type] - 1;
118             }
119         }
120
121       /*
122        * ONLY ICMP messages defined in icmp4_valid_new/icmp6_valid_new table
123        * are allowed to create stateful ACL.
124        * The other messages will be forwarded without creating a reflexive ACL.
125        */
126       if (type < 0 ||
127           type > icmp_valid_new_size[is_ip6] || !icmp_valid_new[is_ip6][type])
128         {
129           valid_new_sess = 0;
130         }
131     }
132
133   return valid_new_sess;
134 }
135
136 always_inline int
137 fa_session_get_timeout_type (acl_main_t * am, fa_session_t * sess)
138 {
139   /* seen both SYNs and ACKs but not FINs means we are in establshed state */
140   u16 masked_flags =
141     sess->tcp_flags_seen.as_u16 & ((TCP_FLAGS_RSTFINACKSYN << 8) +
142                                    TCP_FLAGS_RSTFINACKSYN);
143   switch (sess->info.l4.proto)
144     {
145     case IPPROTO_TCP:
146       if (((TCP_FLAGS_ACKSYN << 8) + TCP_FLAGS_ACKSYN) == masked_flags)
147         {
148           return ACL_TIMEOUT_TCP_IDLE;
149         }
150       else
151         {
152           return ACL_TIMEOUT_TCP_TRANSIENT;
153         }
154       break;
155     case IPPROTO_UDP:
156       return ACL_TIMEOUT_UDP_IDLE;
157       break;
158     default:
159       return ACL_TIMEOUT_UDP_IDLE;
160     }
161 }
162
163 /*
164  * Get the idle timeout of a session.
165  */
166
167 always_inline u64
168 fa_session_get_timeout (acl_main_t * am, fa_session_t * sess)
169 {
170   u64 timeout = am->vlib_main->clib_time.clocks_per_second;
171   int timeout_type = fa_session_get_timeout_type (am, sess);
172   timeout *= am->session_timeout_sec[timeout_type];
173   return timeout;
174 }
175
176
177
178 always_inline fa_session_t *
179 get_session_ptr (acl_main_t * am, u16 thread_index, u32 session_index)
180 {
181   acl_fa_per_worker_data_t *pw = &am->per_worker_data[thread_index];
182   fa_session_t *sess = pool_is_free_index (pw->fa_sessions_pool,
183                                            session_index) ? 0 :
184     pool_elt_at_index (pw->fa_sessions_pool,
185                        session_index);
186   return sess;
187 }
188
189 always_inline int
190 is_valid_session_ptr (acl_main_t * am, u16 thread_index, fa_session_t * sess)
191 {
192   acl_fa_per_worker_data_t *pw = &am->per_worker_data[thread_index];
193   return ((sess != 0)
194           && ((sess - pw->fa_sessions_pool) <
195               pool_len (pw->fa_sessions_pool)));
196 }
197
198 always_inline void
199 acl_fa_conn_list_add_session (acl_main_t * am, fa_full_session_id_t sess_id,
200                               u64 now)
201 {
202   fa_session_t *sess =
203     get_session_ptr (am, sess_id.thread_index, sess_id.session_index);
204   u8 list_id = fa_session_get_timeout_type (am, sess);
205   uword thread_index = os_get_thread_index ();
206   acl_fa_per_worker_data_t *pw = &am->per_worker_data[thread_index];
207   /* the retrieved session thread index must be necessarily the same as the one in the key */
208   ASSERT (sess->thread_index == sess_id.thread_index);
209   /* the retrieved session thread index must be the same as current thread */
210   ASSERT (sess->thread_index == thread_index);
211   sess->link_enqueue_time = now;
212   sess->link_list_id = list_id;
213   sess->link_next_idx = ~0;
214   sess->link_prev_idx = pw->fa_conn_list_tail[list_id];
215   if (~0 != pw->fa_conn_list_tail[list_id])
216     {
217       fa_session_t *prev_sess =
218         get_session_ptr (am, thread_index, pw->fa_conn_list_tail[list_id]);
219       prev_sess->link_next_idx = sess_id.session_index;
220       /* We should never try to link with a session on another thread */
221       ASSERT (prev_sess->thread_index == sess->thread_index);
222     }
223   pw->fa_conn_list_tail[list_id] = sess_id.session_index;
224
225 #ifdef FA_NODE_VERBOSE_DEBUG
226   clib_warning
227     ("FA-SESSION-DEBUG: add session id %d on thread %d sw_if_index %d",
228      sess_id.session_index, thread_index, sess->sw_if_index);
229 #endif
230   pw->serviced_sw_if_index_bitmap =
231     clib_bitmap_set (pw->serviced_sw_if_index_bitmap, sess->sw_if_index, 1);
232
233   if (~0 == pw->fa_conn_list_head[list_id])
234     {
235       pw->fa_conn_list_head[list_id] = sess_id.session_index;
236     }
237 }
238
239 static int
240 acl_fa_conn_list_delete_session (acl_main_t * am,
241                                  fa_full_session_id_t sess_id)
242 {
243   uword thread_index = os_get_thread_index ();
244   acl_fa_per_worker_data_t *pw = &am->per_worker_data[thread_index];
245   if (thread_index != sess_id.thread_index)
246     {
247       /* If another thread attempts to delete the session, fail it. */
248 #ifdef FA_NODE_VERBOSE_DEBUG
249       clib_warning ("thread id in key %d != curr thread index, not deleting");
250 #endif
251       return 0;
252     }
253   fa_session_t *sess =
254     get_session_ptr (am, sess_id.thread_index, sess_id.session_index);
255   /* we should never try to delete the session with another thread index */
256   ASSERT (sess->thread_index == thread_index);
257   if (~0 != sess->link_prev_idx)
258     {
259       fa_session_t *prev_sess =
260         get_session_ptr (am, thread_index, sess->link_prev_idx);
261       /* the previous session must be in the same list as this one */
262       ASSERT (prev_sess->link_list_id == sess->link_list_id);
263       prev_sess->link_next_idx = sess->link_next_idx;
264     }
265   if (~0 != sess->link_next_idx)
266     {
267       fa_session_t *next_sess =
268         get_session_ptr (am, thread_index, sess->link_next_idx);
269       /* The next session must be in the same list as the one we are deleting */
270       ASSERT (next_sess->link_list_id == sess->link_list_id);
271       next_sess->link_prev_idx = sess->link_prev_idx;
272     }
273   if (pw->fa_conn_list_head[sess->link_list_id] == sess_id.session_index)
274     {
275       pw->fa_conn_list_head[sess->link_list_id] = sess->link_next_idx;
276     }
277   if (pw->fa_conn_list_tail[sess->link_list_id] == sess_id.session_index)
278     {
279       pw->fa_conn_list_tail[sess->link_list_id] = sess->link_prev_idx;
280     }
281   return 1;
282 }
283
284 always_inline int
285 acl_fa_restart_timer_for_session (acl_main_t * am, u64 now,
286                                   fa_full_session_id_t sess_id)
287 {
288   if (acl_fa_conn_list_delete_session (am, sess_id))
289     {
290       acl_fa_conn_list_add_session (am, sess_id, now);
291       return 1;
292     }
293   else
294     {
295       /*
296        * Our thread does not own this connection, so we can not delete
297        * The session. To avoid the complicated signaling, we simply
298        * pick the list waiting time to be the shortest of the timeouts.
299        * This way we do not have to do anything special, and let
300        * the regular requeue check take care of everything.
301        */
302       return 0;
303     }
304 }
305
306
307 always_inline u8
308 acl_fa_track_session (acl_main_t * am, int is_input, u32 sw_if_index, u64 now,
309                       fa_session_t * sess, fa_5tuple_t * pkt_5tuple)
310 {
311   sess->last_active_time = now;
312   if (pkt_5tuple->pkt.tcp_flags_valid)
313     {
314       sess->tcp_flags_seen.as_u8[is_input] |= pkt_5tuple->pkt.tcp_flags;
315     }
316   return 3;
317 }
318
319
320 always_inline void
321 acl_fa_delete_session (acl_main_t * am, u32 sw_if_index,
322                        fa_full_session_id_t sess_id)
323 {
324   void *oldheap = clib_mem_set_heap (am->acl_mheap);
325   fa_session_t *sess =
326     get_session_ptr (am, sess_id.thread_index, sess_id.session_index);
327   ASSERT (sess->thread_index == os_get_thread_index ());
328   clib_bihash_add_del_40_8 (&am->fa_sessions_hash, &sess->info.kv, 0);
329   acl_fa_per_worker_data_t *pw = &am->per_worker_data[sess_id.thread_index];
330   pool_put_index (pw->fa_sessions_pool, sess_id.session_index);
331   /* Deleting from timer structures not needed,
332      as the caller must have dealt with the timers. */
333   vec_validate (pw->fa_session_dels_by_sw_if_index, sw_if_index);
334   clib_mem_set_heap (oldheap);
335   pw->fa_session_dels_by_sw_if_index[sw_if_index]++;
336   clib_smp_atomic_add (&am->fa_session_total_dels, 1);
337 }
338
339 always_inline int
340 acl_fa_can_add_session (acl_main_t * am, int is_input, u32 sw_if_index)
341 {
342   u64 curr_sess_count;
343   curr_sess_count = am->fa_session_total_adds - am->fa_session_total_dels;
344   return (curr_sess_count < am->fa_conn_table_max_entries);
345 }
346
347
348 always_inline void
349 acl_fa_try_recycle_session (acl_main_t * am, int is_input, u16 thread_index,
350                             u32 sw_if_index)
351 {
352   /* try to recycle a TCP transient session */
353   acl_fa_per_worker_data_t *pw = &am->per_worker_data[thread_index];
354   u8 timeout_type = ACL_TIMEOUT_TCP_TRANSIENT;
355   fa_full_session_id_t sess_id;
356   sess_id.session_index = pw->fa_conn_list_head[timeout_type];
357   if (~0 != sess_id.session_index)
358     {
359       sess_id.thread_index = thread_index;
360       acl_fa_conn_list_delete_session (am, sess_id);
361       acl_fa_delete_session (am, sw_if_index, sess_id);
362     }
363 }
364
365 always_inline fa_session_t *
366 acl_fa_add_session (acl_main_t * am, int is_input, u32 sw_if_index, u64 now,
367                     fa_5tuple_t * p5tuple, u16 current_policy_epoch)
368 {
369   clib_bihash_kv_40_8_t *pkv = &p5tuple->kv;
370   clib_bihash_kv_40_8_t kv;
371   fa_full_session_id_t f_sess_id;
372   uword thread_index = os_get_thread_index ();
373   void *oldheap = clib_mem_set_heap (am->acl_mheap);
374   acl_fa_per_worker_data_t *pw = &am->per_worker_data[thread_index];
375
376   f_sess_id.thread_index = thread_index;
377   fa_session_t *sess;
378
379   pool_get_aligned (pw->fa_sessions_pool, sess, CLIB_CACHE_LINE_BYTES);
380   f_sess_id.session_index = sess - pw->fa_sessions_pool;
381   f_sess_id.intf_policy_epoch = current_policy_epoch;
382
383   kv.key[0] = pkv->key[0];
384   kv.key[1] = pkv->key[1];
385   kv.key[2] = pkv->key[2];
386   kv.key[3] = pkv->key[3];
387   kv.key[4] = pkv->key[4];
388   kv.value = f_sess_id.as_u64;
389
390   memcpy (sess, pkv, sizeof (pkv->key));
391   sess->last_active_time = now;
392   sess->sw_if_index = sw_if_index;
393   sess->tcp_flags_seen.as_u16 = 0;
394   sess->thread_index = thread_index;
395   sess->link_list_id = ~0;
396   sess->link_prev_idx = ~0;
397   sess->link_next_idx = ~0;
398
399
400
401   ASSERT (am->fa_sessions_hash_is_initialized == 1);
402   clib_bihash_add_del_40_8 (&am->fa_sessions_hash, &kv, 1);
403   acl_fa_conn_list_add_session (am, f_sess_id, now);
404
405   vec_validate (pw->fa_session_adds_by_sw_if_index, sw_if_index);
406   clib_mem_set_heap (oldheap);
407   pw->fa_session_adds_by_sw_if_index[sw_if_index]++;
408   clib_smp_atomic_add (&am->fa_session_total_adds, 1);
409   return sess;
410 }
411
412 always_inline int
413 acl_fa_find_session (acl_main_t * am, u32 sw_if_index0, fa_5tuple_t * p5tuple,
414                      clib_bihash_kv_40_8_t * pvalue_sess)
415 {
416   return (clib_bihash_search_40_8
417           (&am->fa_sessions_hash, &p5tuple->kv, pvalue_sess) == 0);
418 }
419
420 /*
421  * fd.io coding-style-patch-verification: ON
422  *
423  * Local Variables:
424  * eval: (c-set-style "gnu")
425  * End:
426  */