Line data Source code
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 48 : pnat_interface_t *pnat_interface_by_sw_if_index(u32 sw_if_index) {
39 48 : pnat_main_t *pm = &pnat_main;
40 :
41 94 : if (!pm->interface_by_sw_if_index ||
42 46 : sw_if_index > (vec_len(pm->interface_by_sw_if_index) - 1))
43 4 : return 0;
44 44 : u32 index = pm->interface_by_sw_if_index[sw_if_index];
45 44 : if (index == ~0)
46 18 : return 0;
47 26 : if (pool_is_free_index(pm->interfaces, index))
48 0 : return 0;
49 26 : return pool_elt_at_index(pm->interfaces, index);
50 : }
51 :
52 33 : static pnat_mask_fast_t pnat_mask2fast(pnat_mask_t lookup_mask) {
53 33 : pnat_mask_fast_t m = {0};
54 :
55 33 : if (lookup_mask & PNAT_SA)
56 12 : m.as_u64[0] = 0xffffffff00000000;
57 33 : if (lookup_mask & PNAT_DA)
58 21 : m.as_u64[0] |= 0x00000000ffffffff;
59 33 : m.as_u64[1] = 0x00ffffff00000000;
60 33 : if (lookup_mask & PNAT_PROTO)
61 12 : m.as_u64[1] |= 0xff00000000000000;
62 33 : if (lookup_mask & PNAT_SPORT)
63 0 : m.as_u64[1] |= 0x00000000ffff0000;
64 33 : if (lookup_mask & PNAT_DPORT)
65 18 : m.as_u64[1] |= 0x000000000000ffff;
66 33 : 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 11 : static clib_error_t *pnat_enable_interface(u32 sw_if_index,
76 : pnat_attachment_point_t attachment,
77 : pnat_mask_t mask) {
78 11 : pnat_main_t *pm = &pnat_main;
79 11 : pnat_interface_t *interface = pnat_interface_by_sw_if_index(sw_if_index);
80 :
81 11 : if (!interface) {
82 11 : pool_get_zero(pm->interfaces, interface);
83 11 : interface->sw_if_index = sw_if_index;
84 14 : vec_validate_init_empty(pm->interface_by_sw_if_index, sw_if_index, ~0);
85 11 : pm->interface_by_sw_if_index[sw_if_index] = interface - pm->interfaces;
86 : }
87 :
88 : char *nodename;
89 : char *arcname;
90 11 : bool input = false;
91 11 : switch (attachment) {
92 7 : case PNAT_IP4_INPUT:
93 7 : nodename = "pnat-input";
94 7 : arcname = "ip4-unicast";
95 7 : input = true;
96 7 : break;
97 :
98 4 : case PNAT_IP4_OUTPUT:
99 4 : nodename = "pnat-output";
100 4 : arcname = "ip4-output";
101 4 : break;
102 0 : default:
103 0 : return clib_error_return(0, "Unknown attachment point %u %u",
104 : sw_if_index, attachment);
105 : }
106 :
107 11 : if (!interface->enabled[attachment]) {
108 11 : if (vnet_feature_enable_disable(arcname, nodename, sw_if_index, 1, 0,
109 : 0) != 0)
110 0 : return clib_error_return(0, "PNAT feature enable failed on %u",
111 : sw_if_index);
112 :
113 11 : if (input) {
114 : /* TODO: Make shallow virtual reassembly configurable */
115 7 : if (ip4_sv_reass_enable_disable_with_refcnt(sw_if_index, 1) != 0)
116 0 : return clib_error_return(0, "PNAT SVR enable failed on %u",
117 : sw_if_index);
118 :
119 : } else {
120 4 : if (ip4_sv_reass_output_enable_disable_with_refcnt(sw_if_index,
121 : 1) != 0)
122 0 : return clib_error_return(0, "PNAT SVR enable failed on %u",
123 : sw_if_index);
124 : }
125 :
126 11 : interface->lookup_mask[attachment] = mask;
127 11 : interface->lookup_mask_fast[attachment] = pnat_mask2fast(mask);
128 11 : interface->enabled[attachment] = true;
129 :
130 : } else {
131 0 : pnat_mask_t current_mask = interface->lookup_mask[attachment];
132 0 : if (current_mask != mask) {
133 0 : 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 11 : interface->refcount++;
141 :
142 11 : return 0;
143 : }
144 :
145 : /*
146 : * Delete interface object when no rules reference the interface.
147 : */
148 11 : static int pnat_disable_interface(u32 sw_if_index,
149 : pnat_attachment_point_t attachment) {
150 11 : pnat_main_t *pm = &pnat_main;
151 11 : pnat_interface_t *interface = pnat_interface_by_sw_if_index(sw_if_index);
152 :
153 11 : if (!interface)
154 0 : return 0;
155 11 : if (interface->refcount == 0)
156 0 : return 0;
157 :
158 11 : if (interface->enabled[attachment] && attachment == PNAT_IP4_INPUT) {
159 7 : if (ip4_sv_reass_enable_disable_with_refcnt(sw_if_index, 0) != 0)
160 0 : return -1;
161 7 : if (vnet_feature_enable_disable("ip4-unicast", "pnat-input",
162 : sw_if_index, 0, 0, 0) != 0)
163 0 : return -1;
164 : }
165 11 : if (interface->enabled[attachment] && attachment == PNAT_IP4_OUTPUT) {
166 4 : if (ip4_sv_reass_output_enable_disable_with_refcnt(sw_if_index, 0) != 0)
167 0 : return -1;
168 4 : if (vnet_feature_enable_disable("ip4-output", "pnat-output",
169 : sw_if_index, 0, 0, 0) != 0)
170 0 : return -1;
171 : }
172 :
173 11 : interface->lookup_mask[attachment] = 0;
174 11 : interface->enabled[attachment] = false;
175 :
176 11 : interface->refcount--;
177 11 : if (interface->refcount == 0) {
178 11 : pm->interface_by_sw_if_index[sw_if_index] = ~0;
179 11 : pool_put(pm->interfaces, interface);
180 : }
181 11 : return 0;
182 : }
183 :
184 : /*
185 : * From a 5-tuple (with mask) calculate the key used in the flow cache lookup.
186 : */
187 22 : 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 22 : pnat_mask_fast_t mask = pnat_mask2fast(match->mask);
192 : ip4_address_t src, dst;
193 22 : clib_memcpy(&src, &match->src, 4);
194 22 : clib_memcpy(&dst, &match->dst, 4);
195 22 : pnat_calc_key(sw_if_index, attachment, src, dst, match->proto,
196 22 : htons(match->sport), htons(match->dport), mask, kv);
197 22 : }
198 :
199 : /*
200 : * Map between the 5-tuple mask and the instruction set of the rewrite node.
201 : */
202 11 : pnat_instructions_t pnat_instructions_from_mask(pnat_mask_t m) {
203 11 : pnat_instructions_t i = 0;
204 :
205 11 : if (m & PNAT_SA)
206 5 : i |= PNAT_INSTR_SOURCE_ADDRESS;
207 11 : if (m & PNAT_DA)
208 5 : i |= PNAT_INSTR_DESTINATION_ADDRESS;
209 11 : if (m & PNAT_SPORT)
210 0 : i |= PNAT_INSTR_SOURCE_PORT;
211 11 : if (m & PNAT_DPORT)
212 2 : i |= PNAT_INSTR_DESTINATION_PORT;
213 11 : if (m & PNAT_COPY_BYTE)
214 0 : i |= PNAT_INSTR_COPY_BYTE;
215 11 : if (m & PNAT_CLEAR_BYTE)
216 0 : i |= PNAT_INSTR_CLEAR_BYTE;
217 11 : 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 11 : static void pnat_enable(void) {
225 11 : pnat_main_t *pm = &pnat_main;
226 11 : if (pm->enabled)
227 10 : return;
228 :
229 : /* Create new flow cache table */
230 1 : clib_bihash_init_16_8(&pm->flowhash, "PNAT flow hash",
231 : PNAT_FLOW_HASH_BUCKETS, 0);
232 :
233 1 : pm->enabled = true;
234 : }
235 11 : static void pnat_disable(void) {
236 11 : pnat_main_t *pm = &pnat_main;
237 :
238 11 : if (!pm->enabled)
239 0 : return;
240 11 : if (pool_elts(pm->translations))
241 11 : return;
242 :
243 : /* Delete flow cache table */
244 0 : clib_bihash_free_16_8(&pm->flowhash);
245 :
246 0 : pm->enabled = false;
247 : }
248 :
249 : /*
250 : * Ensure that a new rule lookup mask matches what's installed on interface
251 : */
252 11 : static int pnat_interface_check_mask(u32 sw_if_index,
253 : pnat_attachment_point_t attachment,
254 : pnat_mask_t mask) {
255 11 : pnat_interface_t *interface = pnat_interface_by_sw_if_index(sw_if_index);
256 11 : if (!interface)
257 11 : return 0;
258 0 : if (!interface->enabled[attachment])
259 0 : return 0;
260 0 : if (interface->lookup_mask[attachment] != mask)
261 0 : return -1;
262 :
263 0 : 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 11 : int pnat_binding_add(pnat_match_tuple_t *match, pnat_rewrite_tuple_t *rewrite,
274 : u32 *index) {
275 11 : pnat_main_t *pm = &pnat_main;
276 :
277 11 : *index = -1;
278 :
279 : /* If we aren't matching or rewriting, why are we here? */
280 11 : if (match->mask == 0 || rewrite->mask == 0)
281 0 : return -1;
282 :
283 : /* Check if protocol is set if ports are set */
284 11 : if ((match->dport || match->sport) &&
285 6 : (match->proto != IP_API_PROTO_UDP && match->proto != IP_API_PROTO_TCP))
286 0 : return -2;
287 :
288 : /* Create pool entry */
289 : pnat_translation_t *t;
290 11 : pool_get_zero(pm->translations, t);
291 11 : memcpy(&t->post_da, &rewrite->dst, 4);
292 11 : memcpy(&t->post_sa, &rewrite->src, 4);
293 11 : t->post_sp = rewrite->sport;
294 11 : t->post_dp = rewrite->dport;
295 11 : t->from_offset = rewrite->from_offset;
296 11 : t->to_offset = rewrite->to_offset;
297 11 : t->clear_offset = rewrite->clear_offset;
298 11 : t->instructions = pnat_instructions_from_mask(rewrite->mask);
299 :
300 : /* These are only used for show commands and trace */
301 11 : t->match = *match;
302 11 : t->rewrite = *rewrite;
303 :
304 11 : *index = t - pm->translations;
305 :
306 11 : return 0;
307 : }
308 :
309 : /*
310 : * Looks a match flow in the flow cache, returns the index in the binding table
311 : */
312 0 : u32 pnat_flow_lookup(u32 sw_if_index, pnat_attachment_point_t attachment,
313 : pnat_match_tuple_t *match) {
314 0 : pnat_main_t *pm = &pnat_main;
315 : clib_bihash_kv_16_8_t kv, value;
316 0 : pnat_calc_key_from_5tuple(sw_if_index, attachment, match, &kv);
317 0 : if (clib_bihash_search_16_8(&pm->flowhash, &kv, &value) == 0) {
318 0 : return value.value;
319 : }
320 0 : 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 11 : int pnat_binding_attach(u32 sw_if_index, pnat_attachment_point_t attachment,
332 : u32 binding_index) {
333 11 : pnat_main_t *pm = &pnat_main;
334 :
335 22 : if (!pm->translations ||
336 11 : pool_is_free_index(pm->translations, binding_index))
337 0 : return -1;
338 :
339 11 : pnat_translation_t *t = pool_elt_at_index(pm->translations, binding_index);
340 :
341 11 : if (pnat_interface_check_mask(sw_if_index, attachment, t->match.mask) != 0)
342 0 : return -2;
343 :
344 11 : pnat_enable();
345 :
346 : /* Verify non-duplicate */
347 : clib_bihash_kv_16_8_t kv, value;
348 11 : pnat_calc_key_from_5tuple(sw_if_index, attachment, &t->match, &kv);
349 11 : if (clib_bihash_search_16_8(&pm->flowhash, &kv, &value) == 0) {
350 0 : return -3;
351 : }
352 :
353 : /* Create flow cache */
354 11 : kv.value = binding_index;
355 11 : if (clib_bihash_add_del_16_8(&pm->flowhash, &kv, 1)) {
356 0 : pool_put(pm->translations, t);
357 0 : return -4;
358 : }
359 :
360 : /* Register interface */
361 11 : pnat_enable_interface(sw_if_index, attachment, t->match.mask);
362 :
363 11 : return 0;
364 : }
365 :
366 11 : int pnat_binding_detach(u32 sw_if_index, pnat_attachment_point_t attachment,
367 : u32 binding_index) {
368 11 : pnat_main_t *pm = &pnat_main;
369 :
370 22 : if (!pm->translations ||
371 11 : pool_is_free_index(pm->translations, binding_index))
372 0 : return -1;
373 :
374 11 : 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 11 : pnat_calc_key_from_5tuple(sw_if_index, attachment, &t->match, &kv);
379 11 : if (clib_bihash_add_del_16_8(&pm->flowhash, &kv, 0)) {
380 0 : return -2;
381 : }
382 :
383 : /* Deregister interface */
384 11 : pnat_disable_interface(sw_if_index, attachment);
385 :
386 11 : pnat_disable();
387 :
388 11 : return 0;
389 : }
390 :
391 : /*
392 : * Delete a translation using the index returned from pnat_add_translation.
393 : */
394 11 : int pnat_binding_del(u32 index) {
395 11 : pnat_main_t *pm = &pnat_main;
396 :
397 11 : if (pool_is_free_index(pm->translations, index)) {
398 0 : clib_warning("Binding delete: translation does not exist: %d", index);
399 0 : return -1;
400 : }
401 :
402 11 : pnat_translation_t *t = pool_elt_at_index(pm->translations, index);
403 11 : pool_put(pm->translations, t);
404 :
405 11 : return 0;
406 : }
|