cnat: Ip ICMP error support
[vpp.git] / src / plugins / cnat / cnat_client.c
1 /*
2  * Copyright (c) 2020 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 #include <vnet/fib/fib_table.h>
17 #include <vnet/dpo/drop_dpo.h>
18
19 #include <cnat/cnat_client.h>
20 #include <cnat/cnat_translation.h>
21
22 cnat_client_t *cnat_client_pool;
23
24 cnat_client_db_t cnat_client_db;
25
26 dpo_type_t cnat_client_dpo;
27
28 static_always_inline u8
29 cnat_client_is_clone (cnat_client_t * cc)
30 {
31   return (FIB_NODE_INDEX_INVALID == cc->cc_fei);
32 }
33
34 static void
35 cnat_client_db_remove (cnat_client_t * cc)
36 {
37   if (ip_addr_version (&cc->cc_ip) == AF_IP4)
38     hash_unset (cnat_client_db.crd_cip4, ip_addr_v4 (&cc->cc_ip).as_u32);
39   else
40     hash_unset_mem_free (&cnat_client_db.crd_cip6, &ip_addr_v6 (&cc->cc_ip));
41 }
42
43 static void
44 cnat_client_destroy (cnat_client_t * cc)
45 {
46   ASSERT (!cnat_client_is_clone (cc));
47   if (!(cc->flags & CNAT_FLAG_EXCLUSIVE))
48     {
49       ASSERT (fib_entry_is_sourced (cc->cc_fei, cnat_fib_source));
50       fib_table_entry_delete_index (cc->cc_fei, cnat_fib_source);
51       ASSERT (!fib_entry_is_sourced (cc->cc_fei, cnat_fib_source));
52     }
53   cnat_client_db_remove (cc);
54   dpo_reset (&cc->cc_parent);
55   pool_put (cnat_client_pool, cc);
56 }
57
58 void
59 cnat_client_free_by_ip (ip46_address_t * ip, u8 af)
60 {
61   cnat_client_t *cc;
62   cc = (AF_IP4 == af ?
63         cnat_client_ip4_find (&ip->ip4) : cnat_client_ip6_find (&ip->ip6));
64   ASSERT (NULL != cc);
65
66   if ((0 == cnat_client_uncnt_session (cc))
67       && (cc->flags & CNAT_FLAG_EXPIRES) && (0 == cc->tr_refcnt))
68     cnat_client_destroy (cc);
69 }
70
71 void
72 cnat_client_throttle_pool_process ()
73 {
74   /* This processes ips stored in the throttle pool
75      to update session refcounts
76      and should be called before cnat_client_free_by_ip */
77   vlib_thread_main_t *tm = vlib_get_thread_main ();
78   cnat_client_t *cc;
79   int nthreads;
80   u32 *del_vec = NULL, *ai;
81   ip_address_t *addr;
82   nthreads = tm->n_threads + 1;
83   for (int i = 0; i < nthreads; i++)
84     {
85       vec_reset_length (del_vec);
86       clib_spinlock_lock (&cnat_client_db.throttle_pool_lock[i]);
87       /* *INDENT-OFF* */
88       pool_foreach(addr, cnat_client_db.throttle_pool[i], ({
89         cc = (AF_IP4 == addr->version ?
90               cnat_client_ip4_find (&ip_addr_v4(addr)) :
91               cnat_client_ip6_find (&ip_addr_v6(addr)));
92         /* Client might not already be created */
93         if (NULL != cc)
94           {
95             cnat_client_cnt_session (cc);
96             vec_add1(del_vec, addr - cnat_client_db.throttle_pool[i]);
97           }
98       }));
99       /* *INDENT-ON* */
100       vec_foreach (ai, del_vec)
101       {
102         addr = pool_elt_at_index (cnat_client_db.throttle_pool[i], *ai);
103         pool_put (cnat_client_db.throttle_pool[i], addr);
104       }
105       clib_spinlock_unlock (&cnat_client_db.throttle_pool_lock[i]);
106     }
107 }
108
109 void
110 cnat_client_translation_added (index_t cci)
111 {
112   cnat_client_t *cc;
113   cc = cnat_client_get (cci);
114   ASSERT (!(cc->flags & CNAT_FLAG_EXPIRES));
115   cc->tr_refcnt++;
116 }
117
118 void
119 cnat_client_translation_deleted (index_t cci)
120 {
121   cnat_client_t *cc;
122
123   cc = cnat_client_get (cci);
124   ASSERT (!(cc->flags & CNAT_FLAG_EXPIRES));
125   cc->tr_refcnt--;
126
127   if (0 == cc->tr_refcnt && 0 == cc->session_refcnt)
128     cnat_client_destroy (cc);
129 }
130
131 static void
132 cnat_client_db_add (cnat_client_t * cc)
133 {
134   index_t cci;
135
136   cci = cc - cnat_client_pool;
137
138   if (ip_addr_version (&cc->cc_ip) == AF_IP4)
139     hash_set (cnat_client_db.crd_cip4, ip_addr_v4 (&cc->cc_ip).as_u32, cci);
140   else
141     hash_set_mem_alloc (&cnat_client_db.crd_cip6,
142                         &ip_addr_v6 (&cc->cc_ip), cci);
143 }
144
145
146 index_t
147 cnat_client_add (const ip_address_t * ip, u8 flags)
148 {
149   cnat_client_t *cc;
150   dpo_id_t tmp = DPO_INVALID;
151   fib_node_index_t fei;
152   dpo_proto_t dproto;
153   fib_prefix_t pfx;
154   index_t cci;
155   u32 fib_flags;
156
157   /* check again if we need this client */
158   cc = (AF_IP4 == ip->version ?
159         cnat_client_ip4_find (&ip->ip.ip4) :
160         cnat_client_ip6_find (&ip->ip.ip6));
161
162   if (NULL != cc)
163     return (cc - cnat_client_pool);
164
165
166   pool_get_aligned (cnat_client_pool, cc, CLIB_CACHE_LINE_BYTES);
167   cc->cc_locks = 1;
168   cci = cc - cnat_client_pool;
169   cc->parent_cci = cci;
170   cc->flags = flags;
171   cc->tr_refcnt = 0;
172   cc->session_refcnt = 0;
173
174   ip_address_copy (&cc->cc_ip, ip);
175   cnat_client_db_add (cc);
176
177   ip_address_to_fib_prefix (&cc->cc_ip, &pfx);
178
179   dproto = fib_proto_to_dpo (pfx.fp_proto);
180   dpo_set (&tmp, cnat_client_dpo, dproto, cci);
181   dpo_stack (cnat_client_dpo, dproto, &cc->cc_parent, drop_dpo_get (dproto));
182
183   fib_flags = FIB_ENTRY_FLAG_LOOSE_URPF_EXEMPT;
184   fib_flags |= (flags & CNAT_FLAG_EXCLUSIVE) ?
185     FIB_ENTRY_FLAG_EXCLUSIVE : FIB_ENTRY_FLAG_INTERPOSE;
186
187   fei = fib_table_entry_special_dpo_add (CNAT_FIB_TABLE,
188                                          &pfx, cnat_fib_source, fib_flags,
189                                          &tmp);
190
191   cc = pool_elt_at_index (cnat_client_pool, cci);
192   cc->cc_fei = fei;
193
194   return (cci);
195 }
196
197 void
198 cnat_client_learn (const cnat_learn_arg_t * l)
199 {
200   /* RPC call to add a client from the dataplane */
201   index_t cci;
202   cnat_client_t *cc;
203   cci = cnat_client_add (&l->addr, CNAT_FLAG_EXPIRES);
204   cc = pool_elt_at_index (cnat_client_pool, cci);
205   cnat_client_cnt_session (cc);
206   /* Process throttled calls if any */
207   cnat_client_throttle_pool_process ();
208 }
209
210 /**
211  * Interpose a policy DPO
212  */
213 static void
214 cnat_client_dpo_interpose (const dpo_id_t * original,
215                            const dpo_id_t * parent, dpo_id_t * clone)
216 {
217   cnat_client_t *cc, *cc_clone;
218
219   pool_get_zero (cnat_client_pool, cc_clone);
220   cc = cnat_client_get (original->dpoi_index);
221
222   cc_clone->cc_fei = FIB_NODE_INDEX_INVALID;
223   cc_clone->parent_cci = cc->parent_cci;
224   cc_clone->flags = cc->flags;
225   ip_address_copy (&cc_clone->cc_ip, &cc->cc_ip);
226
227   /* stack the clone on the FIB provided parent */
228   dpo_stack (cnat_client_dpo, original->dpoi_proto, &cc_clone->cc_parent,
229              parent);
230
231   /* return the clone */
232   dpo_set (clone,
233            cnat_client_dpo,
234            original->dpoi_proto, cc_clone - cnat_client_pool);
235 }
236
237 int
238 cnat_client_purge (void)
239 {
240   vlib_thread_main_t *tm = vlib_get_thread_main ();
241   int nthreads;
242   nthreads = tm->n_threads + 1;
243   ASSERT (0 == hash_elts (cnat_client_db.crd_cip6));
244   ASSERT (0 == hash_elts (cnat_client_db.crd_cip4));
245   ASSERT (0 == pool_elts (cnat_client_pool));
246   for (int i = 0; i < nthreads; i++)
247     {
248       ASSERT (0 == pool_elts (cnat_client_db.throttle_pool[i]));
249     }
250   return (0);
251 }
252
253 u8 *
254 format_cnat_client (u8 * s, va_list * args)
255 {
256   index_t cci = va_arg (*args, index_t);
257   u32 indent = va_arg (*args, u32);
258
259   cnat_client_t *cc = pool_elt_at_index (cnat_client_pool, cci);
260
261   s = format (s, "[%d] cnat-client:[%U] tr:%d sess:%d", cci,
262               format_ip_address, &cc->cc_ip,
263               cc->tr_refcnt, cc->session_refcnt);
264   if (cc->flags & CNAT_FLAG_EXPIRES)
265     s = format (s, " expires");
266
267   if (cc->flags & CNAT_FLAG_EXCLUSIVE)
268     s = format (s, " exclusive");
269
270   if (cnat_client_is_clone (cc))
271     s = format (s, "\n%Uclone of [%d]\n%U%U",
272                 format_white_space, indent + 2, cc->parent_cci,
273                 format_white_space, indent + 2,
274                 format_dpo_id, &cc->cc_parent, indent + 4);
275
276   return (s);
277 }
278
279
280 static clib_error_t *
281 cnat_client_show (vlib_main_t * vm,
282                   unformat_input_t * input, vlib_cli_command_t * cmd)
283 {
284   index_t cci;
285
286   cci = INDEX_INVALID;
287
288   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
289     {
290       if (unformat (input, "%d", &cci))
291         ;
292       else
293         return (clib_error_return (0, "unknown input '%U'",
294                                    format_unformat_error, input));
295     }
296
297   if (INDEX_INVALID == cci)
298     {
299       /* *INDENT-OFF* */
300       pool_foreach_index(cci, cnat_client_pool, ({
301         vlib_cli_output(vm, "%U", format_cnat_client, cci, 0);
302       }))
303       /* *INDENT-ON* */
304
305       vlib_cli_output (vm, "%d clients", pool_elts (cnat_client_pool));
306       vlib_cli_output (vm, "%d timestamps", pool_elts (cnat_timestamps));
307     }
308   else
309     {
310       vlib_cli_output (vm, "Invalid policy ID:%d", cci);
311     }
312
313   return (NULL);
314 }
315
316 /* *INDENT-OFF* */
317 VLIB_CLI_COMMAND (cnat_client_show_cmd_node, static) = {
318   .path = "show cnat client",
319   .function = cnat_client_show,
320   .short_help = "show cnat client",
321   .is_mp_safe = 1,
322 };
323 /* *INDENT-ON* */
324
325 const static char *const cnat_client_dpo_ip4_nodes[] = {
326   "ip4-cnat-tx",
327   NULL,
328 };
329
330 const static char *const cnat_client_dpo_ip6_nodes[] = {
331   "ip6-cnat-tx",
332   NULL,
333 };
334
335 const static char *const *const cnat_client_dpo_nodes[DPO_PROTO_NUM] = {
336   [DPO_PROTO_IP4] = cnat_client_dpo_ip4_nodes,
337   [DPO_PROTO_IP6] = cnat_client_dpo_ip6_nodes,
338 };
339
340 static void
341 cnat_client_dpo_lock (dpo_id_t * dpo)
342 {
343   cnat_client_t *cc;
344
345   cc = cnat_client_get (dpo->dpoi_index);
346
347   cc->cc_locks++;
348 }
349
350 static void
351 cnat_client_dpo_unlock (dpo_id_t * dpo)
352 {
353   cnat_client_t *cc;
354
355   cc = cnat_client_get (dpo->dpoi_index);
356
357   cc->cc_locks--;
358
359   if (0 == cc->cc_locks)
360     {
361       ASSERT (cnat_client_is_clone (cc));
362       pool_put (cnat_client_pool, cc);
363     }
364 }
365
366 u8 *
367 format_cnat_client_dpo (u8 * s, va_list * ap)
368 {
369   index_t cci = va_arg (*ap, index_t);
370   u32 indent = va_arg (*ap, u32);
371
372   s = format (s, "%U", format_cnat_client, cci, indent);
373
374   return (s);
375 }
376
377 const static dpo_vft_t cnat_client_dpo_vft = {
378   .dv_lock = cnat_client_dpo_lock,
379   .dv_unlock = cnat_client_dpo_unlock,
380   .dv_format = format_cnat_client_dpo,
381   .dv_mk_interpose = cnat_client_dpo_interpose,
382 };
383
384 static clib_error_t *
385 cnat_client_init (vlib_main_t * vm)
386 {
387   vlib_thread_main_t *tm = vlib_get_thread_main ();
388   int nthreads = tm->n_threads + 1;
389   int i;
390   cnat_client_dpo = dpo_register_new_type (&cnat_client_dpo_vft,
391                                            cnat_client_dpo_nodes);
392
393   cnat_client_db.crd_cip6 = hash_create_mem (0,
394                                              sizeof (ip6_address_t),
395                                              sizeof (uword));
396
397   vec_validate (cnat_client_db.throttle_pool, nthreads);
398   vec_validate (cnat_client_db.throttle_pool_lock, nthreads);
399   for (i = 0; i < nthreads; i++)
400     clib_spinlock_init (&cnat_client_db.throttle_pool_lock[i]);
401
402   return (NULL);
403 }
404
405 VLIB_INIT_FUNCTION (cnat_client_init);
406
407 /*
408  * fd.io coding-style-patch-verification: ON
409  *
410  * Local Variables:
411  * eval: (c-set-style "gnu")
412  * End:
413  */