summaryrefslogtreecommitdiffstats
path: root/src/plugins/npt66/npt66_node.c
blob: f74f91439988d39cbef2df2c4cd03abae17f03d6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
// SPDX-License-Identifier: Apache-2.0
// Copyright(c) 2023 Cisco Systems, Inc.

// This file contains the implementation of the NPT66 node.
// RFC6296: IPv6-to-IPv6 Network Prefix Translation (NPTv6)

#include <vnet/ip/ip.h>
#include <vnet/ip/ip6.h>
#include <vnet/ip/ip6_packet.h>

#include <npt66/npt66.h>
#include <npt66/npt66.api_enum.h>

typedef struct
{
  u32 pool_index;
  ip6_address_t internal;
  ip6_address_t external;
} npt66_trace_t;

static inline u8 *
format_npt66_trace (u8 *s, va_list *args)
{
  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
  CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
  npt66_trace_t *t = va_arg (*args, npt66_trace_t *);

  if (t->pool_index != ~0)
    s = format (s, "npt66: index %d internal: %U external: %U\n",
		t->pool_index, format_ip6_address, &t->internal,
		format_ip6_address, &t->external);
  else
    s = format (s, "npt66: index %d (binding not found)\n", t->pool_index);
  return s;
}

/* NPT66 next-nodes */
typedef enum
{
  NPT66_NEXT_DROP,
  NPT66_N_NEXT
} npt66_next_t;

static ip6_address_t
ip6_prefix_copy (ip6_address_t dest, ip6_address_t src, int plen)
{
  int bytes_to_copy = plen / 8;
  int residual_bits = plen % 8;

  // Copy full bytes
  for (int i = 0; i < bytes_to_copy; i++)
    {
      dest.as_u8[i] = src.as_u8[i];
    }

  // Handle the residual bits, if any
  if (residual_bits)
    {
      uint8_t mask = 0xFF << (8 - residual_bits);
      dest.as_u8[bytes_to_copy] = (dest.as_u8[bytes_to_copy] & ~mask) |
				  (src.as_u8[bytes_to_copy] & mask);
    }
  return dest;
}
static int
ip6_prefix_cmp (ip6_address_t a, ip6_address_t b, int plen)
{
  int bytes_to_compare = plen / 8;
  int residual_bits = plen % 8;

  // Compare full bytes
  for (int i = 0; i < bytes_to_compare; i++)
    {
      if (a.as_u8[i] != b.as_u8[i])
	{
	  return 0; // prefixes are not identical
	}
    }

  // Compare the residual bits, if any
  if (residual_bits)
    {
      uint8_t mask = 0xFF << (8 - residual_bits);
      if ((a.as_u8[bytes_to_compare] & mask) !=
	  (b.as_u8[bytes_to_compare] & mask))
	{
	  return 0; // prefixes are not identical
	}
    }
  return 1; // prefixes are identical
}

static int
npt66_adjust_checksum (int plen, bool add, ip_csum_t delta,
		       ip6_address_t *address)
{
  if (plen <= 48)
    {
      // TODO: Check for 0xFFFF
      if (address->as_u16[3] == 0xffff)
	return -1;
      address->as_u16[3] = add ? ip_csum_add_even (address->as_u16[3], delta) :
				       ip_csum_sub_even (address->as_u16[3], delta);
    }
  else
    {
      /* For prefixes longer than 48 find a 16-bit word in the interface id */
      for (int i = 4; i < 8; i++)
	{
	  if (address->as_u16[i] == 0xffff)
	    continue;
	  address->as_u16[i] = add ?
				       ip_csum_add_even (address->as_u16[i], delta) :
				       ip_csum_sub_even (address->as_u16[i], delta);
	  break;
	}
    }
  return 0;
}

static int
npt66_translate (ip6_header_t *ip, npt66_binding_t *binding, int dir)
{
  int rv = 0;
  if (dir == VLIB_TX)
    {
      if (!ip6_prefix_cmp (ip->src_address, binding->internal,
			   binding->internal_plen))
	{
	  clib_warning (
	    "npt66_translate: src address is not internal (%U -> %U)",
	    format_ip6_address, &ip->src_address, format_ip6_address,
	    &ip->dst_address);
	  goto done;
	}
      ip->src_address = ip6_prefix_copy (ip->src_address, binding->external,
					 binding->external_plen);
      /* Checksum neutrality */
      rv = npt66_adjust_checksum (binding->internal_plen, false,
				  binding->delta, &ip->src_address);
    }
  else
    {
      if (!ip6_prefix_cmp (ip->dst_address, binding->external,
			   binding->external_plen))
	{
	  clib_warning (
	    "npt66_translate: dst address is not external (%U -> %U)",
	    format_ip6_address, &ip->src_address, format_ip6_address,
	    &ip->dst_address);
	  goto done;
	}
      ip->dst_address = ip6_prefix_copy (ip->dst_address, binding->internal,
					 binding->internal_plen);
      rv = npt66_adjust_checksum (binding->internal_plen, true, binding->delta,
				  &ip->dst_address);
    }
done:
  return rv;
}

static int
npt66_icmp6_translate (vlib_buffer_t *b, ip6_header_t *outer_ip,
		       icmp46_header_t *icmp, npt66_binding_t *binding,
		       int dir)
{
  ip6_header_t *ip = (ip6_header_t *) (icmp + 2);
  int rv = 0;
  vlib_main_t *vm = vlib_get_main ();

  if (clib_net_to_host_u16 (outer_ip->payload_length) <
      sizeof (icmp46_header_t) + 4 + sizeof (ip6_header_t))
    {
      clib_warning ("ICMP6 payload too short");
      return -1;
    }

  // Validate checksums
  int bogus_length;
  u16 sum16;
  sum16 = ip6_tcp_udp_icmp_compute_checksum (vm, b, outer_ip, &bogus_length);
  if (sum16 != 0 && sum16 != 0xffff)
    {
      clib_warning ("ICMP6 checksum failed");
      return -1;
    }
  if (dir == VLIB_RX)
    {
      if (!ip6_prefix_cmp (ip->src_address, binding->external,
			   binding->external_plen))
	{
	  clib_warning (
	    "npt66_icmp6_translate: src address is not internal (%U -> %U)",
	    format_ip6_address, &ip->src_address, format_ip6_address,
	    &ip->dst_address);
	  goto done;
	}
      ip->src_address = ip6_prefix_copy (ip->src_address, binding->internal,
					 binding->internal_plen);
      /* Checksum neutrality */
      rv = npt66_adjust_checksum (binding->internal_plen, true, binding->delta,
				  &ip->src_address);
    }
  else
    {
      if (!ip6_prefix_cmp (ip->dst_address, binding->external,
			   binding->external_plen))
	{
	  clib_warning (
	    "npt66_icmp6_translate: dst address is not external (%U -> %U)",
	    format_ip6_address, &ip->src_address, format_ip6_address,
	    &ip->dst_address);
	  goto done;
	}
      ip->dst_address = ip6_prefix_copy (ip->dst_address, binding->internal,
					 binding->internal_plen);
      rv = npt66_adjust_checksum (binding->internal_plen, false,
				  binding->delta, &ip->dst_address);
    }
done:

  return rv;
}

/*
 * Lookup the packet tuple in the flow cache, given the lookup mask.
 * If a binding is found, rewrite the packet according to instructions,
 * otherwise follow configured default action (forward, punt or drop)
 */
// TODO: Make use of SVR configurable
static_always_inline uword
npt66_node_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
		   vlib_frame_t *frame, int dir)
{
  npt66_main_t *nm = &npt66_main;
  u32 n_left_from, *from;
  u16 nexts[VLIB_FRAME_SIZE] = { 0 }, *next = nexts;
  u32 pool_indicies[VLIB_FRAME_SIZE], *pi = pool_indicies;
  vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b = bufs;
  ip6_header_t *ip;

  from = vlib_frame_vector_args (frame);
  n_left_from = frame->n_vectors;
  vlib_get_buffers (vm, from, b, n_left_from);
  npt66_binding_t *binding;

  /* Stage 1: build vector of flow hash (based on lookup mask) */
  while (n_left_from > 0)
    {
      u32 sw_if_index = vnet_buffer (b[0])->sw_if_index[dir];
      u32 iph_offset =
	dir == VLIB_TX ? vnet_buffer (b[0])->ip.save_rewrite_length : 0;
      ip = (ip6_header_t *) (vlib_buffer_get_current (b[0]) + iph_offset);
      binding = npt66_interface_by_sw_if_index (sw_if_index);
      ASSERT (binding);
      *pi = binding - nm->bindings;

      /* By default pass packet to next node in the feature chain */
      vnet_feature_next_u16 (next, b[0]);
      int rv;
      icmp46_header_t *icmp = (icmp46_header_t *) (ip + 1);
      if (ip->protocol == IP_PROTOCOL_ICMP6 && icmp->type < 128)
	{
	  rv = npt66_icmp6_translate (b[0], ip, icmp, binding, dir);
	  if (rv < 0)
	    {
	      clib_warning ("ICMP6 npt66_translate failed");
	      *next = NPT66_NEXT_DROP;
	      goto next;
	    }
	}
      rv = npt66_translate (ip, binding, dir);

      if (rv < 0)
	{
	  vlib_node_increment_counter (vm, node->node_index,
				       NPT66_ERROR_TRANSLATION, 1);
	  *next = NPT66_NEXT_DROP;
	  goto next;
	}
      else if (dir == VLIB_TX)
	vlib_node_increment_counter (vm, node->node_index, NPT66_ERROR_TX, 1);
      else
	vlib_node_increment_counter (vm, node->node_index, NPT66_ERROR_RX, 1);

    next:
      next += 1;
      n_left_from -= 1;
      b += 1;
      pi += 1;
    }

  /* Packet trace */
  if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)))
    {
      u32 i;
      b = bufs;
      pi = pool_indicies;

      for (i = 0; i < frame->n_vectors; i++)
	{
	  if (b[0]->flags & VLIB_BUFFER_IS_TRACED)
	    {
	      npt66_trace_t *t = vlib_add_trace (vm, node, b[0], sizeof (*t));
	      if (*pi != ~0)
		{
		  if (!pool_is_free_index (nm->bindings, *pi))
		    {
		      npt66_binding_t *tr =
			pool_elt_at_index (nm->bindings, *pi);
		      t->internal = tr->internal;
		      t->external = tr->external;
		    }
		}
	      t->pool_index = *pi;

	      b += 1;
	      pi += 1;
	    }
	  else
	    break;
	}
    }
  vlib_buffer_enqueue_to_next (vm, node, from, nexts, frame->n_vectors);

  return frame->n_vectors;
}

VLIB_NODE_FN (npt66_input_node)
(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
{
  return npt66_node_inline (vm, node, frame, VLIB_RX);
}
VLIB_NODE_FN (npt66_output_node)
(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
{
  return npt66_node_inline (vm, node, frame, VLIB_TX);
}

VLIB_REGISTER_NODE(npt66_input_node) = {
    .name = "npt66-input",
    .vector_size = sizeof(u32),
    .format_trace = format_npt66_trace,
    .type = VLIB_NODE_TYPE_INTERNAL,
    .n_errors = NPT66_N_ERROR,
    .error_counters = npt66_error_counters,
    .n_next_nodes = NPT66_N_NEXT,
    .next_nodes =
        {
            [NPT66_NEXT_DROP] = "error-drop",
        },
};

VLIB_REGISTER_NODE (npt66_output_node) = {
  .name = "npt66-output",
  .vector_size = sizeof (u32),
  .format_trace = format_npt66_trace,
  .type = VLIB_NODE_TYPE_INTERNAL,
  .n_errors = NPT66_N_ERROR,
  .error_counters = npt66_error_counters,
  .sibling_of = "npt66-input",
};

/* Hook up features */
VNET_FEATURE_INIT (npt66_input, static) = {
  .arc_name = "ip6-unicast",
  .node_name = "npt66-input",
};
VNET_FEATURE_INIT (npt66_output, static) = {
  .arc_name = "ip6-output",
  .node_name = "npt66-output",
};