nat: 1:1 policy NAT
[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] = 0xffffffff00000000;
60     if (lookup_mask & PNAT_SPORT)
61         m.as_u64[1] |= 0x00000000ffff0000;
62     if (lookup_mask & PNAT_DPORT)
63         m.as_u64[1] |= 0x000000000000ffff;
64     return m;
65 }
66
67 /*
68  * Create new PNAT interface object and register the pnat feature in the
69  * corresponding feature chain.
70  * Also enable shallow virtual reassembly, to ensure that we have
71  * L4 ports available for all packets we receive.
72  */
73 static clib_error_t *pnat_enable_interface(u32 sw_if_index,
74                                            pnat_attachment_point_t attachment,
75                                            pnat_mask_t mask) {
76     pnat_main_t *pm = &pnat_main;
77     pnat_interface_t *interface = pnat_interface_by_sw_if_index(sw_if_index);
78
79     if (!interface) {
80         pool_get_zero(pm->interfaces, interface);
81         interface->sw_if_index = sw_if_index;
82         vec_validate_init_empty(pm->interface_by_sw_if_index, sw_if_index, ~0);
83         pm->interface_by_sw_if_index[sw_if_index] = interface - pm->interfaces;
84     }
85
86     char *nodename;
87     char *arcname;
88     bool input = false;
89     switch (attachment) {
90     case PNAT_IP4_INPUT:
91         nodename = "pnat-input";
92         arcname = "ip4-unicast";
93         input = true;
94         break;
95
96     case PNAT_IP4_OUTPUT:
97         nodename = "pnat-output";
98         arcname = "ip4-output";
99         break;
100     default:
101         return clib_error_return(0, "Unknown attachment point %u %u",
102                                  sw_if_index, attachment);
103     }
104
105     if (!interface->enabled[attachment]) {
106         if (vnet_feature_enable_disable(arcname, nodename, sw_if_index, 1, 0,
107                                         0) != 0)
108             return clib_error_return(0, "PNAT feature enable failed on %u",
109                                      sw_if_index);
110
111         if (input) {
112             /* TODO: Make shallow virtual reassembly configurable */
113             ip4_sv_reass_enable_disable_with_refcnt(sw_if_index, 1);
114         } else {
115             ip4_sv_reass_output_enable_disable_with_refcnt(sw_if_index, 1);
116         }
117
118         interface->lookup_mask[attachment] = mask;
119         interface->lookup_mask_fast[attachment] = pnat_mask2fast(mask);
120         interface->enabled[attachment] = true;
121
122     } else {
123         pnat_mask_t current_mask = interface->lookup_mask[attachment];
124         if (current_mask != mask) {
125             return clib_error_return(0,
126                                      "PNAT lookup mask must be consistent per "
127                                      "interface/direction %u",
128                                      sw_if_index);
129         }
130     }
131
132     interface->refcount++;
133
134     return 0;
135 }
136
137 /*
138  * Delete interface object when no rules reference the interface.
139  */
140 static int pnat_disable_interface(u32 sw_if_index,
141                                   pnat_attachment_point_t attachment) {
142     pnat_main_t *pm = &pnat_main;
143     pnat_interface_t *interface = pnat_interface_by_sw_if_index(sw_if_index);
144
145     if (!interface)
146         return 0;
147     if (interface->refcount == 0)
148         return 0;
149
150     if (interface->enabled[attachment] && attachment == PNAT_IP4_INPUT) {
151         ip4_sv_reass_enable_disable_with_refcnt(sw_if_index, 0);
152         if (vnet_feature_enable_disable("ip4-unicast", "pnat-input",
153                                         sw_if_index, 0, 0, 0) != 0)
154             return -1;
155     }
156     if (interface->enabled[attachment] && attachment == PNAT_IP4_OUTPUT) {
157         ip4_sv_reass_output_enable_disable_with_refcnt(sw_if_index, 0);
158         if (vnet_feature_enable_disable("ip4-output", "pnat-output",
159                                         sw_if_index, 0, 0, 0) != 0)
160             return -1;
161     }
162
163     interface->lookup_mask[attachment] = 0;
164     interface->enabled[attachment] = false;
165
166     interface->refcount--;
167     if (interface->refcount == 0) {
168         pm->interface_by_sw_if_index[sw_if_index] = ~0;
169         pool_put(pm->interfaces, interface);
170     }
171     return 0;
172 }
173
174 /*
175  * From a 5-tuple (with mask) calculate the key used in the flow cache lookup.
176  */
177 static inline void pnat_calc_key_from_5tuple(u32 sw_if_index,
178                                              pnat_attachment_point_t attachment,
179                                              pnat_5tuple_t *match,
180                                              clib_bihash_kv_16_8_t *kv) {
181     pnat_mask_fast_t mask = pnat_mask2fast(match->mask);
182     ip4_address_t src, dst;
183     clib_memcpy(&src, &match->src, 4);
184     clib_memcpy(&dst, &match->dst, 4);
185     pnat_calc_key(sw_if_index, attachment, src, dst, match->proto,
186                   htons(match->sport), htons(match->dport), mask, kv);
187 }
188
189 /*
190  * Map between the 5-tuple mask and the instruction set of the rewrite node.
191  */
192 pnat_instructions_t pnat_instructions_from_mask(pnat_mask_t m) {
193     pnat_instructions_t i = 0;
194
195     if (m & PNAT_SA)
196         i |= PNAT_INSTR_SOURCE_ADDRESS;
197     if (m & PNAT_DA)
198         i |= PNAT_INSTR_DESTINATION_ADDRESS;
199     if (m & PNAT_SPORT)
200         i |= PNAT_INSTR_SOURCE_PORT;
201     if (m & PNAT_DPORT)
202         i |= PNAT_INSTR_DESTINATION_PORT;
203     return i;
204 }
205
206 /*
207  * "Init" the PNAT datastructures. Called upon first creation of a PNAT rule.
208  * TODO: Make number of buckets configurable.
209  */
210 static void pnat_enable(void) {
211     pnat_main_t *pm = &pnat_main;
212     if (pm->enabled)
213         return;
214
215     /* Create new flow cache table */
216     clib_bihash_init_16_8(&pm->flowhash, "PNAT flow hash",
217                           PNAT_FLOW_HASH_BUCKETS, 0);
218
219     pm->enabled = true;
220 }
221 static void pnat_disable(void) {
222     pnat_main_t *pm = &pnat_main;
223
224     if (!pm->enabled)
225         return;
226     if (pool_elts(pm->translations))
227         return;
228
229     /* Delete flow cache table */
230     clib_bihash_free_16_8(&pm->flowhash);
231
232     pm->enabled = false;
233 }
234
235 /*
236  * Ensure that a new rule lookup mask matches what's installed on interface
237  */
238 static int pnat_interface_check_mask(u32 sw_if_index,
239                                      pnat_attachment_point_t attachment,
240                                      pnat_mask_t mask) {
241     pnat_interface_t *interface = pnat_interface_by_sw_if_index(sw_if_index);
242     if (!interface)
243         return 0;
244     if (!interface->enabled[attachment])
245         return 0;
246     if (interface->lookup_mask[attachment] != mask)
247         return -1;
248
249     return 0;
250 }
251
252 int pnat_binding_add(pnat_5tuple_t *match, pnat_5tuple_t *rewrite, u32 *index) {
253     pnat_main_t *pm = &pnat_main;
254
255     *index = -1;
256
257     /* If we aren't matching or rewriting, why are we here? */
258     if (match->mask == 0 || rewrite->mask == 0)
259         return -1;
260
261     /* Check if protocol is set if ports are set */
262     if ((match->dport || match->sport) &&
263         (match->proto != IP_API_PROTO_UDP && match->proto != IP_API_PROTO_TCP))
264         return -2;
265
266     /* Create pool entry */
267     pnat_translation_t *t;
268     pool_get_zero(pm->translations, t);
269     memcpy(&t->post_da, &rewrite->dst, 4);
270     memcpy(&t->post_sa, &rewrite->src, 4);
271     t->post_sp = rewrite->sport;
272     t->post_dp = rewrite->dport;
273     t->instructions = pnat_instructions_from_mask(rewrite->mask);
274
275     /* These are only used for show commands and trace */
276     t->match = *match;
277
278     /* Rewrite of protocol is not supported, ignore. */
279     t->rewrite = *rewrite;
280     t->rewrite.proto = 0;
281
282     *index = t - pm->translations;
283
284     return 0;
285 }
286 u32 pnat_flow_lookup(u32 sw_if_index, pnat_attachment_point_t attachment,
287                      pnat_5tuple_t *match) {
288     pnat_main_t *pm = &pnat_main;
289     clib_bihash_kv_16_8_t kv, value;
290     pnat_calc_key_from_5tuple(sw_if_index, attachment, match, &kv);
291     if (clib_bihash_search_16_8(&pm->flowhash, &kv, &value) == 0) {
292         return value.value;
293     }
294     return ~0;
295 }
296
297 int pnat_binding_attach(u32 sw_if_index, pnat_attachment_point_t attachment,
298                         u32 binding_index) {
299     pnat_main_t *pm = &pnat_main;
300
301     if (!pm->translations ||
302         pool_is_free_index(pm->translations, binding_index))
303         return -1;
304
305     pnat_translation_t *t = pool_elt_at_index(pm->translations, binding_index);
306
307     if (pnat_interface_check_mask(sw_if_index, attachment, t->match.mask) != 0)
308         return -2;
309
310     pnat_enable();
311
312     /* Verify non-duplicate */
313     clib_bihash_kv_16_8_t kv, value;
314     pnat_calc_key_from_5tuple(sw_if_index, attachment, &t->match, &kv);
315     if (clib_bihash_search_16_8(&pm->flowhash, &kv, &value) == 0) {
316         return -3;
317     }
318
319     /* Create flow cache */
320     kv.value = binding_index;
321     if (clib_bihash_add_del_16_8(&pm->flowhash, &kv, 1)) {
322         pool_put(pm->translations, t);
323         return -4;
324     }
325
326     /* Register interface */
327     pnat_enable_interface(sw_if_index, attachment, t->match.mask);
328
329     return 0;
330 }
331
332 int pnat_binding_detach(u32 sw_if_index, pnat_attachment_point_t attachment,
333                         u32 binding_index) {
334     pnat_main_t *pm = &pnat_main;
335
336     if (!pm->translations ||
337         pool_is_free_index(pm->translations, binding_index))
338         return -1;
339
340     pnat_translation_t *t = pool_elt_at_index(pm->translations, binding_index);
341
342     /* Verify non-duplicate */
343     clib_bihash_kv_16_8_t kv;
344     pnat_calc_key_from_5tuple(sw_if_index, attachment, &t->match, &kv);
345     if (clib_bihash_add_del_16_8(&pm->flowhash, &kv, 0)) {
346         return -2;
347     }
348
349     /* Deregister interface */
350     pnat_disable_interface(sw_if_index, attachment);
351
352     pnat_disable();
353
354     return 0;
355 }
356
357 /*
358  * Delete a translation using the index returned from pnat_add_translation.
359  */
360 int pnat_binding_del(u32 index) {
361     pnat_main_t *pm = &pnat_main;
362
363     if (pool_is_free_index(pm->translations, index)) {
364         clib_warning("Binding delete: translation does not exist: %d", index);
365         return -1;
366     }
367
368     pnat_translation_t *t = pool_elt_at_index(pm->translations, index);
369     pool_put(pm->translations, t);
370
371     return 0;
372 }