nat: Include platform specific headers on FreeBSD
[vpp.git] / src / plugins / nat / pnat / pnat.c
1 /*
2  * Copyright (c) 2021 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 "pnat.h"
17 #include <arpa/inet.h>
18 #include <stdbool.h>
19 #include <vlib/vlib.h>
20 #include <vnet/feature/feature.h>
21 #include <vnet/fib/fib_table.h>
22 #include <vnet/ip/format.h>
23 #include <vnet/ip/ip4.h>
24 #include <vnet/ip/ip4_packet.h>
25 #include <vnet/ip/reass/ip4_sv_reass.h>
26 #include <vppinfra/clib_error.h>
27
28 /*
29  * This is the main control plane part of the PNAT (Policy 1:1 NAT) feature.
30  */
31
32 pnat_main_t pnat_main;
33
34 /*
35  * Do a lookup in the interface vector (interface_by_sw_if_index)
36  * and return pool entry.
37  */
38 pnat_interface_t *pnat_interface_by_sw_if_index(u32 sw_if_index) {
39     pnat_main_t *pm = &pnat_main;
40
41     if (!pm->interface_by_sw_if_index ||
42         sw_if_index > (vec_len(pm->interface_by_sw_if_index) - 1))
43         return 0;
44     u32 index = pm->interface_by_sw_if_index[sw_if_index];
45     if (index == ~0)
46         return 0;
47     if (pool_is_free_index(pm->interfaces, index))
48         return 0;
49     return pool_elt_at_index(pm->interfaces, index);
50 }
51
52 static pnat_mask_fast_t pnat_mask2fast(pnat_mask_t lookup_mask) {
53     pnat_mask_fast_t m = {0};
54
55     if (lookup_mask & PNAT_SA)
56         m.as_u64[0] = 0xffffffff00000000;
57     if (lookup_mask & PNAT_DA)
58         m.as_u64[0] |= 0x00000000ffffffff;
59     m.as_u64[1] = 0x00ffffff00000000;
60     if (lookup_mask & PNAT_PROTO)
61         m.as_u64[1] |= 0xff00000000000000;
62     if (lookup_mask & PNAT_SPORT)
63         m.as_u64[1] |= 0x00000000ffff0000;
64     if (lookup_mask & PNAT_DPORT)
65         m.as_u64[1] |= 0x000000000000ffff;
66     return m;
67 }
68
69 /*
70  * Create new PNAT interface object and register the pnat feature in the
71  * corresponding feature chain.
72  * Also enable shallow virtual reassembly, to ensure that we have
73  * L4 ports available for all packets we receive.
74  */
75 static clib_error_t *pnat_enable_interface(u32 sw_if_index,
76                                            pnat_attachment_point_t attachment,
77                                            pnat_mask_t mask) {
78     pnat_main_t *pm = &pnat_main;
79     pnat_interface_t *interface = pnat_interface_by_sw_if_index(sw_if_index);
80
81     if (!interface) {
82         pool_get_zero(pm->interfaces, interface);
83         interface->sw_if_index = sw_if_index;
84         vec_validate_init_empty(pm->interface_by_sw_if_index, sw_if_index, ~0);
85         pm->interface_by_sw_if_index[sw_if_index] = interface - pm->interfaces;
86     }
87
88     char *nodename;
89     char *arcname;
90     bool input = false;
91     switch (attachment) {
92     case PNAT_IP4_INPUT:
93         nodename = "pnat-input";
94         arcname = "ip4-unicast";
95         input = true;
96         break;
97
98     case PNAT_IP4_OUTPUT:
99         nodename = "pnat-output";
100         arcname = "ip4-output";
101         break;
102     default:
103         return clib_error_return(0, "Unknown attachment point %u %u",
104                                  sw_if_index, attachment);
105     }
106
107     if (!interface->enabled[attachment]) {
108         if (vnet_feature_enable_disable(arcname, nodename, sw_if_index, 1, 0,
109                                         0) != 0)
110             return clib_error_return(0, "PNAT feature enable failed on %u",
111                                      sw_if_index);
112
113         if (input) {
114             /* TODO: Make shallow virtual reassembly configurable */
115             if (ip4_sv_reass_enable_disable_with_refcnt(sw_if_index, 1) != 0)
116                 return clib_error_return(0, "PNAT SVR enable failed on %u",
117                                          sw_if_index);
118
119         } else {
120             if (ip4_sv_reass_output_enable_disable_with_refcnt(sw_if_index,
121                                                                1) != 0)
122                 return clib_error_return(0, "PNAT SVR enable failed on %u",
123                                          sw_if_index);
124         }
125
126         interface->lookup_mask[attachment] = mask;
127         interface->lookup_mask_fast[attachment] = pnat_mask2fast(mask);
128         interface->enabled[attachment] = true;
129
130     } else {
131         pnat_mask_t current_mask = interface->lookup_mask[attachment];
132         if (current_mask != mask) {
133             return clib_error_return(0,
134                                      "PNAT lookup mask must be consistent per "
135                                      "interface/direction %u",
136                                      sw_if_index);
137         }
138     }
139
140     interface->refcount++;
141
142     return 0;
143 }
144
145 /*
146  * Delete interface object when no rules reference the interface.
147  */
148 static int pnat_disable_interface(u32 sw_if_index,
149                                   pnat_attachment_point_t attachment) {
150     pnat_main_t *pm = &pnat_main;
151     pnat_interface_t *interface = pnat_interface_by_sw_if_index(sw_if_index);
152
153     if (!interface)
154         return 0;
155     if (interface->refcount == 0)
156         return 0;
157
158     if (interface->enabled[attachment] && attachment == PNAT_IP4_INPUT) {
159         if (ip4_sv_reass_enable_disable_with_refcnt(sw_if_index, 0) != 0)
160             return -1;
161         if (vnet_feature_enable_disable("ip4-unicast", "pnat-input",
162                                         sw_if_index, 0, 0, 0) != 0)
163             return -1;
164     }
165     if (interface->enabled[attachment] && attachment == PNAT_IP4_OUTPUT) {
166         if (ip4_sv_reass_output_enable_disable_with_refcnt(sw_if_index, 0) != 0)
167             return -1;
168         if (vnet_feature_enable_disable("ip4-output", "pnat-output",
169                                         sw_if_index, 0, 0, 0) != 0)
170             return -1;
171     }
172
173     interface->lookup_mask[attachment] = 0;
174     interface->enabled[attachment] = false;
175
176     interface->refcount--;
177     if (interface->refcount == 0) {
178         pm->interface_by_sw_if_index[sw_if_index] = ~0;
179         pool_put(pm->interfaces, interface);
180     }
181     return 0;
182 }
183
184 /*
185  * From a 5-tuple (with mask) calculate the key used in the flow cache lookup.
186  */
187 static inline void pnat_calc_key_from_5tuple(u32 sw_if_index,
188                                              pnat_attachment_point_t attachment,
189                                              pnat_match_tuple_t *match,
190                                              clib_bihash_kv_16_8_t *kv) {
191     pnat_mask_fast_t mask = pnat_mask2fast(match->mask);
192     ip4_address_t src, dst;
193     clib_memcpy(&src, &match->src, 4);
194     clib_memcpy(&dst, &match->dst, 4);
195     pnat_calc_key(sw_if_index, attachment, src, dst, match->proto,
196                   htons(match->sport), htons(match->dport), mask, kv);
197 }
198
199 /*
200  * Map between the 5-tuple mask and the instruction set of the rewrite node.
201  */
202 pnat_instructions_t pnat_instructions_from_mask(pnat_mask_t m) {
203     pnat_instructions_t i = 0;
204
205     if (m & PNAT_SA)
206         i |= PNAT_INSTR_SOURCE_ADDRESS;
207     if (m & PNAT_DA)
208         i |= PNAT_INSTR_DESTINATION_ADDRESS;
209     if (m & PNAT_SPORT)
210         i |= PNAT_INSTR_SOURCE_PORT;
211     if (m & PNAT_DPORT)
212         i |= PNAT_INSTR_DESTINATION_PORT;
213     if (m & PNAT_COPY_BYTE)
214         i |= PNAT_INSTR_COPY_BYTE;
215     if (m & PNAT_CLEAR_BYTE)
216         i |= PNAT_INSTR_CLEAR_BYTE;
217     return i;
218 }
219
220 /*
221  * "Init" the PNAT datastructures. Called upon first creation of a PNAT rule.
222  * TODO: Make number of buckets configurable.
223  */
224 static void pnat_enable(void) {
225     pnat_main_t *pm = &pnat_main;
226     if (pm->enabled)
227         return;
228
229     /* Create new flow cache table */
230     clib_bihash_init_16_8(&pm->flowhash, "PNAT flow hash",
231                           PNAT_FLOW_HASH_BUCKETS, 0);
232
233     pm->enabled = true;
234 }
235 static void pnat_disable(void) {
236     pnat_main_t *pm = &pnat_main;
237
238     if (!pm->enabled)
239         return;
240     if (pool_elts(pm->translations))
241         return;
242
243     /* Delete flow cache table */
244     clib_bihash_free_16_8(&pm->flowhash);
245
246     pm->enabled = false;
247 }
248
249 /*
250  * Ensure that a new rule lookup mask matches what's installed on interface
251  */
252 static int pnat_interface_check_mask(u32 sw_if_index,
253                                      pnat_attachment_point_t attachment,
254                                      pnat_mask_t mask) {
255     pnat_interface_t *interface = pnat_interface_by_sw_if_index(sw_if_index);
256     if (!interface)
257         return 0;
258     if (!interface->enabled[attachment])
259         return 0;
260     if (interface->lookup_mask[attachment] != mask)
261         return -1;
262
263     return 0;
264 }
265
266 /*
267  * Add a binding to the binding table.
268  * Returns 0 on success.
269  *  -1: Invalid mask
270  *  -2: Only matches ports for UDP or TCP
271  *
272  */
273 int pnat_binding_add(pnat_match_tuple_t *match, pnat_rewrite_tuple_t *rewrite,
274                      u32 *index) {
275     pnat_main_t *pm = &pnat_main;
276
277     *index = -1;
278
279     /* If we aren't matching or rewriting, why are we here? */
280     if (match->mask == 0 || rewrite->mask == 0)
281         return -1;
282
283     /* Check if protocol is set if ports are set */
284     if ((match->dport || match->sport) &&
285         (match->proto != IP_API_PROTO_UDP && match->proto != IP_API_PROTO_TCP))
286         return -2;
287
288     /* Create pool entry */
289     pnat_translation_t *t;
290     pool_get_zero(pm->translations, t);
291     memcpy(&t->post_da, &rewrite->dst, 4);
292     memcpy(&t->post_sa, &rewrite->src, 4);
293     t->post_sp = rewrite->sport;
294     t->post_dp = rewrite->dport;
295     t->from_offset = rewrite->from_offset;
296     t->to_offset = rewrite->to_offset;
297     t->clear_offset = rewrite->clear_offset;
298     t->instructions = pnat_instructions_from_mask(rewrite->mask);
299
300     /* These are only used for show commands and trace */
301     t->match = *match;
302     t->rewrite = *rewrite;
303
304     *index = t - pm->translations;
305
306     return 0;
307 }
308
309 /*
310  * Looks a match flow in the flow cache, returns the index  in the binding table
311  */
312 u32 pnat_flow_lookup(u32 sw_if_index, pnat_attachment_point_t attachment,
313                      pnat_match_tuple_t *match) {
314     pnat_main_t *pm = &pnat_main;
315     clib_bihash_kv_16_8_t kv, value;
316     pnat_calc_key_from_5tuple(sw_if_index, attachment, match, &kv);
317     if (clib_bihash_search_16_8(&pm->flowhash, &kv, &value) == 0) {
318         return value.value;
319     }
320     return ~0;
321 }
322
323 /*
324  * Attach a binding to an interface / direction.
325  * Returns 0 on success.
326  * -1: Binding does not exist
327  * -2: Interface mask does not match
328  * -3: Existing match entry in flow table
329  * -4: Adding flow table entry failed
330  */
331 int pnat_binding_attach(u32 sw_if_index, pnat_attachment_point_t attachment,
332                         u32 binding_index) {
333     pnat_main_t *pm = &pnat_main;
334
335     if (!pm->translations ||
336         pool_is_free_index(pm->translations, binding_index))
337         return -1;
338
339     pnat_translation_t *t = pool_elt_at_index(pm->translations, binding_index);
340
341     if (pnat_interface_check_mask(sw_if_index, attachment, t->match.mask) != 0)
342         return -2;
343
344     pnat_enable();
345
346     /* Verify non-duplicate */
347     clib_bihash_kv_16_8_t kv, value;
348     pnat_calc_key_from_5tuple(sw_if_index, attachment, &t->match, &kv);
349     if (clib_bihash_search_16_8(&pm->flowhash, &kv, &value) == 0) {
350         return -3;
351     }
352
353     /* Create flow cache */
354     kv.value = binding_index;
355     if (clib_bihash_add_del_16_8(&pm->flowhash, &kv, 1)) {
356         pool_put(pm->translations, t);
357         return -4;
358     }
359
360     /* Register interface */
361     pnat_enable_interface(sw_if_index, attachment, t->match.mask);
362
363     return 0;
364 }
365
366 int pnat_binding_detach(u32 sw_if_index, pnat_attachment_point_t attachment,
367                         u32 binding_index) {
368     pnat_main_t *pm = &pnat_main;
369
370     if (!pm->translations ||
371         pool_is_free_index(pm->translations, binding_index))
372         return -1;
373
374     pnat_translation_t *t = pool_elt_at_index(pm->translations, binding_index);
375
376     /* Verify non-duplicate */
377     clib_bihash_kv_16_8_t kv;
378     pnat_calc_key_from_5tuple(sw_if_index, attachment, &t->match, &kv);
379     if (clib_bihash_add_del_16_8(&pm->flowhash, &kv, 0)) {
380         return -2;
381     }
382
383     /* Deregister interface */
384     pnat_disable_interface(sw_if_index, attachment);
385
386     pnat_disable();
387
388     return 0;
389 }
390
391 /*
392  * Delete a translation using the index returned from pnat_add_translation.
393  */
394 int pnat_binding_del(u32 index) {
395     pnat_main_t *pm = &pnat_main;
396
397     if (pool_is_free_index(pm->translations, index)) {
398         clib_warning("Binding delete: translation does not exist: %d", index);
399         return -1;
400     }
401
402     pnat_translation_t *t = pool_elt_at_index(pm->translations, index);
403     pool_put(pm->translations, t);
404
405     return 0;
406 }