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:
7 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 #include <arpa/inet.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>
29 * This is the main control plane part of the PNAT (Policy 1:1 NAT) feature.
32 pnat_main_t pnat_main;
35 * Do a lookup in the interface vector (interface_by_sw_if_index)
36 * and return pool entry.
38 pnat_interface_t *pnat_interface_by_sw_if_index(u32 sw_if_index) {
39 pnat_main_t *pm = &pnat_main;
41 if (!pm->interface_by_sw_if_index ||
42 sw_if_index > (vec_len(pm->interface_by_sw_if_index) - 1))
44 u32 index = pm->interface_by_sw_if_index[sw_if_index];
47 if (pool_is_free_index(pm->interfaces, index))
49 return pool_elt_at_index(pm->interfaces, index);
52 static pnat_mask_fast_t pnat_mask2fast(pnat_mask_t lookup_mask) {
53 pnat_mask_fast_t m = {0};
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;
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.
75 static clib_error_t *pnat_enable_interface(u32 sw_if_index,
76 pnat_attachment_point_t attachment,
78 pnat_main_t *pm = &pnat_main;
79 pnat_interface_t *interface = pnat_interface_by_sw_if_index(sw_if_index);
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;
93 nodename = "pnat-input";
94 arcname = "ip4-unicast";
99 nodename = "pnat-output";
100 arcname = "ip4-output";
103 return clib_error_return(0, "Unknown attachment point %u %u",
104 sw_if_index, attachment);
107 if (!interface->enabled[attachment]) {
108 if (vnet_feature_enable_disable(arcname, nodename, sw_if_index, 1, 0,
110 return clib_error_return(0, "PNAT feature enable failed on %u",
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",
120 if (ip4_sv_reass_output_enable_disable_with_refcnt(sw_if_index,
122 return clib_error_return(0, "PNAT SVR enable failed on %u",
126 interface->lookup_mask[attachment] = mask;
127 interface->lookup_mask_fast[attachment] = pnat_mask2fast(mask);
128 interface->enabled[attachment] = true;
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",
140 interface->refcount++;
146 * Delete interface object when no rules reference the interface.
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);
155 if (interface->refcount == 0)
158 if (interface->enabled[attachment] && attachment == PNAT_IP4_INPUT) {
159 if (ip4_sv_reass_enable_disable_with_refcnt(sw_if_index, 0) != 0)
161 if (vnet_feature_enable_disable("ip4-unicast", "pnat-input",
162 sw_if_index, 0, 0, 0) != 0)
165 if (interface->enabled[attachment] && attachment == PNAT_IP4_OUTPUT) {
166 if (ip4_sv_reass_output_enable_disable_with_refcnt(sw_if_index, 0) != 0)
168 if (vnet_feature_enable_disable("ip4-output", "pnat-output",
169 sw_if_index, 0, 0, 0) != 0)
173 interface->lookup_mask[attachment] = 0;
174 interface->enabled[attachment] = false;
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);
185 * From a 5-tuple (with mask) calculate the key used in the flow cache lookup.
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);
200 * Map between the 5-tuple mask and the instruction set of the rewrite node.
202 pnat_instructions_t pnat_instructions_from_mask(pnat_mask_t m) {
203 pnat_instructions_t i = 0;
206 i |= PNAT_INSTR_SOURCE_ADDRESS;
208 i |= PNAT_INSTR_DESTINATION_ADDRESS;
210 i |= PNAT_INSTR_SOURCE_PORT;
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;
221 * "Init" the PNAT datastructures. Called upon first creation of a PNAT rule.
222 * TODO: Make number of buckets configurable.
224 static void pnat_enable(void) {
225 pnat_main_t *pm = &pnat_main;
229 /* Create new flow cache table */
230 clib_bihash_init_16_8(&pm->flowhash, "PNAT flow hash",
231 PNAT_FLOW_HASH_BUCKETS, 0);
235 static void pnat_disable(void) {
236 pnat_main_t *pm = &pnat_main;
240 if (pool_elts(pm->translations))
243 /* Delete flow cache table */
244 clib_bihash_free_16_8(&pm->flowhash);
250 * Ensure that a new rule lookup mask matches what's installed on interface
252 static int pnat_interface_check_mask(u32 sw_if_index,
253 pnat_attachment_point_t attachment,
255 pnat_interface_t *interface = pnat_interface_by_sw_if_index(sw_if_index);
258 if (!interface->enabled[attachment])
260 if (interface->lookup_mask[attachment] != mask)
267 * Add a binding to the binding table.
268 * Returns 0 on success.
270 * -2: Only matches ports for UDP or TCP
273 int pnat_binding_add(pnat_match_tuple_t *match, pnat_rewrite_tuple_t *rewrite,
275 pnat_main_t *pm = &pnat_main;
279 /* If we aren't matching or rewriting, why are we here? */
280 if (match->mask == 0 || rewrite->mask == 0)
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))
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);
300 /* These are only used for show commands and trace */
302 t->rewrite = *rewrite;
304 *index = t - pm->translations;
310 * Looks a match flow in the flow cache, returns the index in the binding table
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) {
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
331 int pnat_binding_attach(u32 sw_if_index, pnat_attachment_point_t attachment,
333 pnat_main_t *pm = &pnat_main;
335 if (!pm->translations ||
336 pool_is_free_index(pm->translations, binding_index))
339 pnat_translation_t *t = pool_elt_at_index(pm->translations, binding_index);
341 if (pnat_interface_check_mask(sw_if_index, attachment, t->match.mask) != 0)
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) {
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);
360 /* Register interface */
361 pnat_enable_interface(sw_if_index, attachment, t->match.mask);
366 int pnat_binding_detach(u32 sw_if_index, pnat_attachment_point_t attachment,
368 pnat_main_t *pm = &pnat_main;
370 if (!pm->translations ||
371 pool_is_free_index(pm->translations, binding_index))
374 pnat_translation_t *t = pool_elt_at_index(pm->translations, binding_index);
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)) {
383 /* Deregister interface */
384 pnat_disable_interface(sw_if_index, attachment);
392 * Delete a translation using the index returned from pnat_add_translation.
394 int pnat_binding_del(u32 index) {
395 pnat_main_t *pm = &pnat_main;
397 if (pool_is_free_index(pm->translations, index)) {
398 clib_warning("Binding delete: translation does not exist: %d", index);
402 pnat_translation_t *t = pool_elt_at_index(pm->translations, index);
403 pool_put(pm->translations, t);