aboutsummaryrefslogtreecommitdiffstats
path: root/src/vnet/cop/ip6_whitelist.c
blob: 7cf2368e798e2dbcfafc60bd6a17a3134baf549d (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
/*
 * Copyright (c) 2016 Cisco and/or its affiliates.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <vnet/cop/cop.h>
#include <vnet/fib/ip6_fib.h>
#include <vnet/dpo/load_balance.h>

typedef struct {
  u32 next_index;
  u32 sw_if_index;
} ip6_cop_whitelist_trace_t;

/* packet trace format function */
static u8 * format_ip6_cop_whitelist_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 *);
  ip6_cop_whitelist_trace_t * t = va_arg (*args, ip6_cop_whitelist_trace_t *);
  
  s = format (s, "IP6_COP_WHITELIST: sw_if_index %d, next index %d",
              t->sw_if_index, t->next_index);
  return s;
}

#define foreach_ip6_cop_whitelist_error                         \
_(DROPPED, "ip6 cop whitelist packets dropped")

typedef enum {
#define _(sym,str) IP6_COP_WHITELIST_ERROR_##sym,
  foreach_ip6_cop_whitelist_error
#undef _
  IP6_COP_WHITELIST_N_ERROR,
} ip6_cop_whitelist_error_t;

static char * ip6_cop_whitelist_error_strings[] = {
#define _(sym,string) string,
  foreach_ip6_cop_whitelist_error
#undef _
};

VLIB_NODE_FN (ip6_cop_whitelist_node) (vlib_main_t * vm,
		  vlib_node_runtime_t * node,
		  vlib_frame_t * frame)
{
  u32 n_left_from, * from, * to_next;
  cop_feature_type_t next_index;
  cop_main_t *cm = &cop_main;
  ip6_main_t * im6 = &ip6_main;
  vlib_combined_counter_main_t * vcm = &load_balance_main.lbm_via_counters;
  u32 thread_index = vm->thread_index;

  from = vlib_frame_vector_args (frame);
  n_left_from = frame->n_vectors;
  next_index = node->cached_next_index;

  while (n_left_from > 0)
    {
      u32 n_left_to_next;

      vlib_get_next_frame (vm, node, next_index,
			   to_next, n_left_to_next);

      while (n_left_from >= 4 && n_left_to_next >= 2)
	{
          u32 bi0, bi1;
          vlib_buffer_t * b0, * b1;
          u32 next0, next1;
          u32 sw_if_index0, sw_if_index1;
          ip6_header_t * ip0, * ip1;
          cop_config_main_t * ccm0, * ccm1;
          cop_config_data_t * c0, * c1;
          u32 lb_index0, lb_index1;
          const load_balance_t * lb0, *lb1;
          const dpo_id_t *dpo0, *dpo1;
         
	  /* Prefetch next iteration. */
	  {
	    vlib_buffer_t * p2, * p3;
            
	    p2 = vlib_get_buffer (vm, from[2]);
	    p3 = vlib_get_buffer (vm, from[3]);
            
	    vlib_prefetch_buffer_header (p2, LOAD);
	    vlib_prefetch_buffer_header (p3, LOAD);

	    CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, STORE);
	    CLIB_PREFETCH (p3->data, CLIB_CACHE_LINE_BYTES, STORE);
	  }

          /* speculatively enqueue b0 and b1 to the current next frame */
	  to_next[0] = bi0 = from[0];
	  to_next[1] = bi1 = from[1];
	  from += 2;
	  to_next += 2;
	  n_left_from -= 2;
	  n_left_to_next -= 2;

	  b0 = vlib_get_buffer (vm, bi0);
          sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX];

	  ip0 = vlib_buffer_get_current (b0);

	  ccm0 = cm->cop_config_mains + VNET_COP_IP6;

	  c0 = vnet_get_config_data 
              (&ccm0->config_main,
               &vnet_buffer (b0)->cop.current_config_index,
               &next0,
               sizeof (c0[0]));

          lb_index0 = ip6_fib_table_fwding_lookup (im6, c0->fib_index, 
						    &ip0->src_address);
	  lb0 = load_balance_get (lb_index0);
          dpo0 = load_balance_get_bucket_i(lb0, 0);

          if (PREDICT_FALSE(dpo0->dpoi_type != DPO_RECEIVE))
            {
              b0->error = node->errors[IP6_COP_WHITELIST_ERROR_DROPPED];
              next0 = RX_COP_DROP;
            }

	  b1 = vlib_get_buffer (vm, bi1);
          sw_if_index1 = vnet_buffer(b1)->sw_if_index[VLIB_RX];

	  ip1 = vlib_buffer_get_current (b1);

	  ccm1 = cm->cop_config_mains + VNET_COP_IP6;

	  c1 = vnet_get_config_data 
              (&ccm1->config_main,
               &vnet_buffer (b1)->cop.current_config_index,
               &next1,
               sizeof (c1[0]));

          lb_index1 = ip6_fib_table_fwding_lookup (im6, c1->fib_index, 
						    &ip1->src_address);

	  lb1 = load_balance_get (lb_index1);
          dpo1 = load_balance_get_bucket_i(lb1, 0);

          vlib_increment_combined_counter 
              (vcm, thread_index, lb_index0, 1,
               vlib_buffer_length_in_chain (vm, b0) 
               + sizeof(ethernet_header_t));

          vlib_increment_combined_counter 
              (vcm, thread_index, lb_index1, 1,
               vlib_buffer_length_in_chain (vm, b1)
               + sizeof(ethernet_header_t));

          if (PREDICT_FALSE(dpo1->dpoi_type != DPO_RECEIVE))
            {
              b1->error = node->errors[IP6_COP_WHITELIST_ERROR_DROPPED];
              next1 = RX_COP_DROP;
            }

          if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) 
                            && (b0->flags & VLIB_BUFFER_IS_TRACED))) 
            {
              ip6_cop_whitelist_trace_t *t = 
                 vlib_add_trace (vm, node, b0, sizeof (*t));
              t->sw_if_index = sw_if_index0;
              t->next_index = next0;
            }

          if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) 
                            && (b1->flags & VLIB_BUFFER_IS_TRACED))) 
            {
              ip6_cop_whitelist_trace_t *t = 
                 vlib_add_trace (vm, node, b1, sizeof (*t));
              t->sw_if_index = sw_if_index1;
              t->next_index = next1;
            }

          /* verify speculative enqueues, maybe switch current next frame */
          vlib_validate_buffer_enqueue_x2 (vm, node, next_index,
                                           to_next, n_left_to_next,
                                           bi0, bi1, next0, next1);
        }

      while (n_left_from > 0 && n_left_to_next > 0)
	{
          u32 bi0;
	  vlib_buffer_t * b0;
          u32 next0;
          u32 sw_if_index0;
          ip6_header_t * ip0;
          cop_config_main_t *ccm0;
          cop_config_data_t *c0;
          u32 lb_index0;
          const load_balance_t * lb0;
          const dpo_id_t *dpo0;

          /* speculatively enqueue b0 to the current next frame */
	  bi0 = from[0];
	  to_next[0] = bi0;
	  from += 1;
	  to_next += 1;
	  n_left_from -= 1;
	  n_left_to_next -= 1;

	  b0 = vlib_get_buffer (vm, bi0);
          sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX];

	  ip0 = vlib_buffer_get_current (b0);

	  ccm0 = cm->cop_config_mains + VNET_COP_IP6;

	  c0 = vnet_get_config_data 
              (&ccm0->config_main,
               &vnet_buffer (b0)->cop.current_config_index,
               &next0,
               sizeof (c0[0]));

          lb_index0 = ip6_fib_table_fwding_lookup (im6, c0->fib_index, 
						    &ip0->src_address);

	  lb0 = load_balance_get (lb_index0);
          dpo0 = load_balance_get_bucket_i(lb0, 0);

          vlib_increment_combined_counter 
              (vcm, thread_index, lb_index0, 1,
               vlib_buffer_length_in_chain (vm, b0) 
               + sizeof(ethernet_header_t));

          if (PREDICT_FALSE(dpo0->dpoi_type != DPO_RECEIVE))
            {
              b0->error = node->errors[IP6_COP_WHITELIST_ERROR_DROPPED];
              next0 = RX_COP_DROP;
            }

          if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) 
                            && (b0->flags & VLIB_BUFFER_IS_TRACED))) 
            {
              ip6_cop_whitelist_trace_t *t = 
                 vlib_add_trace (vm, node, b0, sizeof (*t));
              t->sw_if_index = sw_if_index0;
              t->next_index = next0;
            }
            
          /* verify speculative enqueue, maybe switch current next frame */
	  vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
					   to_next, n_left_to_next,
					   bi0, next0);
	}

      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
    }
  return frame->n_vectors;
}

VLIB_REGISTER_NODE (ip6_cop_whitelist_node) = {
  .name = "ip6-cop-whitelist",
  .vector_size = sizeof (u32),
  .format_trace = format_ip6_cop_whitelist_trace,
  .type = VLIB_NODE_TYPE_INTERNAL,
  
  .n_errors = ARRAY_LEN(ip6_cop_whitelist_error_strings),
  .error_strings = ip6_cop_whitelist_error_strings,

  .n_next_nodes = COP_RX_N_FEATURES,

  /* edit / add dispositions here */
  .next_nodes = {
    [IP4_RX_COP_WHITELIST] = "ip4-cop-whitelist",
    [IP6_RX_COP_WHITELIST] = "ip6-cop-whitelist",
    [DEFAULT_RX_COP_WHITELIST] = "default-cop-whitelist",
    [IP4_RX_COP_INPUT] = "ip4-input",
    [IP6_RX_COP_INPUT] = "ip6-input",
    [DEFAULT_RX_COP_INPUT] = "ethernet-input",
    [RX_COP_DROP] = "error-drop",
  },
};

static clib_error_t *
ip6_whitelist_init (vlib_main_t * vm)
{
  return 0;
}

VLIB_INIT_FUNCTION (ip6_whitelist_init);
;: self.fhc, } ) self._test.registry.register(self, self._test.logger) self.id = r.id return self def remove_vpp_config(self): assert self.id is not None self._test.vapi.cnat_translation_del(id=self.id) return self def query_vpp_config(self): for t in self._test.vapi.cnat_translation_dump(): if self.id == t.translation.id: return t.translation return None class CnatTestContext(object): """ Usage : ctx = CnatTestContext(self, TCP, is_v6=True) # send pg0.remote[0]:1234 -> pg1.remote[0]:6661 ctx.cnat_send(self.pg0, 0, 1234, self.pg1, 0, 6661) # We expect this to be NATed as # pg2.remote[0]:<anyport> -> pg1.remote[0]:6661 ctx.cnat_expect(self.pg2, 0, None, self.pg1, 0, 6661) # After running cnat_expect, we can send back the received packet # and expect it be 'unnated' so that we get the original packet ctx.cnat_send_return().cnat_expect_return() # same thing for ICMP errors ctx.cnat_send_icmp_return_error().cnat_expect_icmp_error_return() """ def __init__(self, test, L4PROTO, is_v6): self.L4PROTO = L4PROTO self.is_v6 = is_v6 self._test = test def get_ip46(self, obj): if self.is_v6: return obj.ip6 return obj.ip4 @property def IP46(self): return IPv6 if self.is_v6 else IP def cnat_send( self, src_pg, src_id, src_port, dst_pg, dst_id, dst_port, no_replies=False ): if isinstance(src_id, int): self.src_addr = self.get_ip46(src_pg.remote_hosts[src_id]) else: self.dst_addr = src_id if isinstance(dst_id, int): self.dst_addr = self.get_ip46(dst_pg.remote_hosts[dst_id]) else: self.dst_addr = dst_id self.src_port = src_port # also ICMP id self.dst_port = dst_port # also ICMP type if self.L4PROTO in [TCP, UDP]: l4 = self.L4PROTO(sport=self.src_port, dport=self.dst_port) elif self.L4PROTO in [ICMP] and not self.is_v6: l4 = self.L4PROTO(id=self.src_port, type=self.dst_port) elif self.L4PROTO in [ICMP] and self.is_v6: l4 = ICMPv6EchoRequest(id=self.src_port) p1 = ( Ether(src=src_pg.remote_mac, dst=src_pg.local_mac) / self.IP46(src=self.src_addr, dst=self.dst_addr) / l4 / Raw() ) if no_replies: self._test.send_and_assert_no_replies(src_pg, p1 * N_PKTS, dst_pg) else: self.rxs = self._test.send_and_expect(src_pg, p1 * N_PKTS, dst_pg) self.expected_src_pg = src_pg self.expected_dst_pg = dst_pg return self def cnat_expect(self, src_pg, src_id, src_port, dst_pg, dst_id, dst_port): if isinstance(src_id, int): self.expect_src_addr = self.get_ip46(src_pg.remote_hosts[src_id]) else: self.expect_src_addr = src_id if isinstance(dst_id, int): self.expect_dst_addr = self.get_ip46(dst_pg.remote_hosts[dst_id]) else: self.expect_dst_addr = dst_id self.expect_src_port = src_port self.expect_dst_port = dst_port if self.expect_src_port is None: if self.L4PROTO in [TCP, UDP]: self.expect_src_port = self.rxs[0][self.L4PROTO].sport elif self.L4PROTO in [ICMP] and not self.is_v6: self.expect_src_port = self.rxs[0][self.L4PROTO].id elif self.L4PROTO in [ICMP] and self.is_v6: self.expect_src_port = self.rxs[0][ICMPv6EchoRequest].id for rx in self.rxs: self._test.assert_packet_checksums_valid(rx) self._test.assertEqual(rx[self.IP46].dst, self.expect_dst_addr) self._test.assertEqual(rx[self.IP46].src, self.expect_src_addr) if self.L4PROTO in [TCP, UDP]: self._test.assertEqual(rx[self.L4PROTO].dport, self.expect_dst_port) self._test.assertEqual(rx[self.L4PROTO].sport, self.expect_src_port) elif self.L4PROTO in [ICMP] and not self.is_v6: self._test.assertEqual(rx[self.L4PROTO].type, self.expect_dst_port) self._test.assertEqual(rx[self.L4PROTO].id, self.expect_src_port) elif self.L4PROTO in [ICMP] and self.is_v6: self._test.assertEqual(rx[ICMPv6EchoRequest].id, self.expect_src_port) return self def cnat_send_return(self): """This sends the return traffic""" if self.L4PROTO in [TCP, UDP]: l4 = self.L4PROTO(sport=self.expect_dst_port, dport=self.expect_src_port) elif self.L4PROTO in [ICMP] and not self.is_v6: # icmp type 0 if echo reply l4 = self.L4PROTO(id=self.expect_src_port, type=0) elif self.L4PROTO in [ICMP] and self.is_v6: l4 = ICMPv6EchoReply(id=self.expect_src_port) src_mac = self.expected_dst_pg.remote_mac p1 = ( Ether(src=src_mac, dst=self.expected_dst_pg.local_mac) / self.IP46(src=self.expect_dst_addr, dst=self.expect_src_addr) / l4 / Raw() ) self.return_rxs = self._test.send_and_expect( self.expected_dst_pg, p1 * N_PKTS, self.expected_src_pg ) return self def cnat_expect_return(self): for rx in self.return_rxs: self._test.assert_packet_checksums_valid(rx) self._test.assertEqual(rx[self.IP46].dst, self.src_addr) self._test.assertEqual(rx[self.IP46].src, self.dst_addr) if self.L4PROTO in [TCP, UDP]: self._test.assertEqual(rx[self.L4PROTO].dport, self.src_port) self._test.assertEqual(rx[self.L4PROTO].sport, self.dst_port) elif self.L4PROTO in [ICMP] and not self.is_v6: # icmp type 0 if echo reply self._test.assertEqual(rx[self.L4PROTO].type, 0) self._test.assertEqual(rx[self.L4PROTO].id, self.src_port) elif self.L4PROTO in [ICMP] and self.is_v6: self._test.assertEqual(rx[ICMPv6EchoReply].id, self.src_port) return self def cnat_send_icmp_return_error(self): """ This called after cnat_expect will send an icmp error on the reverse path """ ICMPelem = ICMPv6DestUnreach(code=1) if self.is_v6 else ICMP(type=11) InnerIP = self.rxs[0][self.IP46] p1 = ( Ether( src=self.expected_dst_pg.remote_mac, dst=self.expected_dst_pg.local_mac ) / self.IP46(src=self.expect_dst_addr, dst=self.expect_src_addr) / ICMPelem / InnerIP ) self.return_rxs = self._test.send_and_expect( self.expected_dst_pg, p1 * N_PKTS, self.expected_src_pg ) return self def cnat_expect_icmp_error_return(self): ICMP46 = ICMPv6DestUnreach if self.is_v6 else ICMP IP46err = IPerror6 if self.is_v6 else IPerror L4err = TCPerror if self.L4PROTO is TCP else UDPerror for rx in self.return_rxs: self._test.assert_packet_checksums_valid(rx) self._test.assertEqual(rx[self.IP46].dst, self.src_addr) self._test.assertEqual(rx[self.IP46].src, self.dst_addr) self._test.assertEqual(rx[ICMP46][IP46err].src, self.src_addr) self._test.assertEqual(rx[ICMP46][IP46err].dst, self.dst_addr) self._test.assertEqual(rx[ICMP46][IP46err][L4err].sport, self.src_port) self._test.assertEqual(rx[ICMP46][IP46err][L4err].dport, self.dst_port) return self # ------------------------------------------------------------------- # ------------------------------------------------------------------- # ------------------------------------------------------------------- # ------------------------------------------------------------------- class TestCNatTranslation(CnatCommonTestCase): """CNat Translation""" @classmethod def setUpClass(cls): super(TestCNatTranslation, cls).setUpClass() @classmethod def tearDownClass(cls): super(TestCNatTranslation, cls).tearDownClass() def setUp(self): super(TestCNatTranslation, self).setUp() self.create_pg_interfaces(range(3)) self.pg0.generate_remote_hosts(N_REMOTE_HOSTS) self.pg1.generate_remote_hosts(N_REMOTE_HOSTS) for i in self.pg_interfaces: i.admin_up() i.config_ip4() i.resolve_arp() i.config_ip6() i.resolve_ndp() i.configure_ipv4_neighbors() i.configure_ipv6_neighbors() def tearDown(self): for translation in self.translations: translation.remove_vpp_config() self.vapi.cnat_session_purge() self.assertFalse(self.vapi.cnat_session_dump()) for i in self.pg_interfaces: i.unconfig_ip4() i.unconfig_ip6() i.admin_down() super(TestCNatTranslation, self).tearDown() def cnat_fhc_translation(self): """CNat Translation""" self.logger.info(self.vapi.cli("sh cnat client")) self.logger.info(self.vapi.cli("sh cnat translation")) for nbr, translation in enumerate(self.mbtranslations): vip = translation.vip # # Flows to the VIP with same ips and different source ports are loadbalanced identically # in both cases of flow hash 0x03 (src ip and dst ip) and 0x08 (dst port) # ctx = CnatTestContext(self, translation.iproto, vip.is_v6) for src_pgi, sport in product(range(N_REMOTE_HOSTS), [1234, 1233]): # from client to vip ctx.cnat_send(self.pg0, src_pgi, sport, self.pg1, vip.ip, vip.port) dport1 = ctx.rxs[0][ctx.L4PROTO].dport ctx._test.assertIn( dport1, [translation.paths[0][DST].port, translation.paths[1][DST].port], ) ctx.cnat_expect(self.pg0, src_pgi, sport, self.pg1, nbr, dport1) ctx.cnat_send( self.pg0, src_pgi, sport + 122, self.pg1, vip.ip, vip.port ) dport2 = ctx.rxs[0][ctx.L4PROTO].dport ctx._test.assertIn( dport2, [translation.paths[0][DST].port, translation.paths[1][DST].port], ) ctx.cnat_expect(self.pg0, src_pgi, sport + 122, self.pg1, nbr, dport2) ctx._test.assertEqual(dport1, dport2) def cnat_translation(self): """CNat Translation""" self.logger.info(self.vapi.cli("sh cnat client")) self.logger.info(self.vapi.cli("sh cnat translation")) for nbr, translation in enumerate(self.translations): vip = translation.vip # # Test Flows to the VIP # ctx = CnatTestContext(self, translation.iproto, vip.is_v6) for src_pgi, sport in product(range(N_REMOTE_HOSTS), [1234, 1233]): # from client to vip ctx.cnat_send(self.pg0, src_pgi, sport, self.pg1, vip.ip, vip.port) dst_port = translation.paths[0][DST].port ctx.cnat_expect(self.pg0, src_pgi, sport, self.pg1, nbr, dst_port) # from vip to client ctx.cnat_send_return().cnat_expect_return() # # packets to the VIP that do not match a # translation are dropped # ctx.cnat_send( self.pg0, src_pgi, sport, self.pg1, vip.ip, 6666, no_replies=True ) # # packets from the VIP that do not match a # session are forwarded # ctx.cnat_send(self.pg1, nbr, 6666, self.pg0, src_pgi, sport) ctx.cnat_expect(self.pg1, nbr, 6666, self.pg0, src_pgi, sport) # # modify the translation to use a different backend # old_dst_port = translation.paths[0][DST].port translation.paths[0][DST].udpate( pg=self.pg2, pgi=0, port=5000, is_v6=vip.is_v6 ) translation.add_vpp_config() # # existing flows follow the old path # for src_pgi in range(N_REMOTE_HOSTS): for sport in [1234, 1233]: # from client to vip ctx.cnat_send(self.pg0, src_pgi, sport, self.pg1, vip.ip, vip.port) ctx.cnat_expect( self.pg0, src_pgi, sport, self.pg1, nbr, old_dst_port ) # from vip to client ctx.cnat_send_return().cnat_expect_return() # # new flows go to the new backend # for src_pgi in range(N_REMOTE_HOSTS): ctx.cnat_send(self.pg0, src_pgi, 9999, self.pg2, vip.ip, vip.port) ctx.cnat_expect(self.pg0, src_pgi, 9999, self.pg2, 0, 5000) self.logger.info(self.vapi.cli("sh cnat session verbose")) # # turn the scanner back on and wait until the sessions # all disapper # self.vapi.cli("test cnat scanner on") self.virtual_sleep(2) sessions = self.vapi.cnat_session_dump() self.assertEqual(len(sessions), 0) self.vapi.cli("test cnat scanner off") # # load some flows again and purge # for translation in self.translations: vip = translation.vip ctx = CnatTestContext(self, translation.iproto, vip.is_v6) for src_pgi in range(N_REMOTE_HOSTS): for sport in [1234, 1233]: # from client to vip ctx.cnat_send(self.pg0, src_pgi, sport, self.pg2, vip.ip, vip.port) ctx.cnat_expect(self.pg0, src_pgi, sport, self.pg2, 0, 5000) def _test_icmp(self): # # Testing ICMP # for nbr, translation in enumerate(self.translations): vip = translation.vip ctx = CnatTestContext(self, translation.iproto, vip.is_v6) # # NATing ICMP errors # ctx.cnat_send(self.pg0, 0, 1234, self.pg1, vip.ip, vip.port) dst_port = translation.paths[0][DST].port ctx.cnat_expect(self.pg0, 0, 1234, self.pg1, nbr, dst_port) ctx.cnat_send_icmp_return_error().cnat_expect_icmp_error_return() # # ICMP errors with no VIP associated should not be # modified # ctx.cnat_send(self.pg0, 0, 1234, self.pg2, 0, vip.port) dst_port = translation.paths[0][DST].port ctx.cnat_expect(self.pg0, 0, 1234, self.pg2, 0, vip.port) ctx.cnat_send_icmp_return_error().cnat_expect_icmp_error_return() def _make_multi_backend_translations(self): self.translations = [] self.mbtranslations = [] self.mbtranslations.append( Translation( self, TCP, Endpoint(ip="30.0.0.5", port=5555, is_v6=False), [ ( Endpoint(is_v6=False), Endpoint(pg=self.pg1, pgi=0, port=4001, is_v6=False), ), ( Endpoint(is_v6=False), Endpoint(pg=self.pg1, pgi=0, port=4005, is_v6=False), ), ], 0x03, # hash only on dst ip and src ip ).add_vpp_config() ) self.mbtranslations.append( Translation( self, TCP, Endpoint(ip="30.0.0.6", port=5555, is_v6=False), [ ( Endpoint(is_v6=False), Endpoint(pg=self.pg1, pgi=1, port=4006, is_v6=False), ), ( Endpoint(is_v6=False), Endpoint(pg=self.pg1, pgi=1, port=4007, is_v6=False), ), ], 0x08, # hash only on dst port ).add_vpp_config() ) def _make_translations_v4(self): self.translations = [] self.translations.append( Translation( self, TCP, Endpoint(ip="30.0.0.1", port=5555, is_v6=False), [ ( Endpoint(is_v6=False), Endpoint(pg=self.pg1, pgi=0, port=4001, is_v6=False), ) ], 0x9F, ).add_vpp_config() ) self.translations.append( Translation( self, TCP, Endpoint(ip="30.0.0.2", port=5554, is_v6=False), [ ( Endpoint(is_v6=False), Endpoint(pg=self.pg1, pgi=1, port=4002, is_v6=False), ) ], 0x9F, ).add_vpp_config() ) self.translations.append( Translation( self, UDP, Endpoint(ip="30.0.0.2", port=5553, is_v6=False), [ ( Endpoint(is_v6=False), Endpoint(pg=self.pg1, pgi=2, port=4003, is_v6=False), ) ], 0x9F, ).add_vpp_config() ) def _make_translations_v6(self): self.translations = [] self.translations.append( Translation( self, TCP, Endpoint(ip="30::1", port=5555, is_v6=True), [ ( Endpoint(is_v6=True), Endpoint(pg=self.pg1, pgi=0, port=4001, is_v6=True), ) ], 0x9F, ).add_vpp_config() ) self.translations.append( Translation( self, TCP, Endpoint(ip="30::2", port=5554, is_v6=True), [ ( Endpoint(is_v6=True), Endpoint(pg=self.pg1, pgi=1, port=4002, is_v6=True), ) ], 0x9F, ).add_vpp_config() ) self.translations.append( Translation( self, UDP, Endpoint(ip="30::2", port=5553, is_v6=True), [ ( Endpoint(is_v6=True), Endpoint(pg=self.pg1, pgi=2, port=4003, is_v6=True), ) ], 0x9F, ).add_vpp_config() ) def test_icmp4(self): # """ CNat Translation icmp v4 """ self._make_translations_v4() self._test_icmp() def test_icmp6(self): # """ CNat Translation icmp v6 """ self._make_translations_v6() self._test_icmp() def test_cnat6(self): # """ CNat Translation ipv6 """ self._make_translations_v6() self.cnat_translation() def test_cnat4(self): # """ CNat Translation ipv4 """ self._make_translations_v4() self.cnat_translation() def test_cnat_fhc(self): # """ CNat Translation flow hash config """ self._make_multi_backend_translations() self.cnat_fhc_translation() class TestCNatSourceNAT(CnatCommonTestCase): """CNat Source NAT""" @classmethod def setUpClass(cls): super(TestCNatSourceNAT, cls).setUpClass() @classmethod def tearDownClass(cls): super(TestCNatSourceNAT, cls).tearDownClass() def _enable_disable_snat(self, is_enable=True): self.vapi.cnat_set_snat_addresses( snat_ip4=self.pg2.remote_hosts[0].ip4, snat_ip6=self.pg2.remote_hosts[0].ip6, sw_if_index=INVALID_INDEX, ) self.vapi.feature_enable_disable( enable=1 if is_enable else 0, arc_name="ip6-unicast", feature_name="cnat-snat-ip6", sw_if_index=self.pg0.sw_if_index, ) self.vapi.feature_enable_disable( enable=1 if is_enable else 0, arc_name="ip4-unicast", feature_name="cnat-snat-ip4", sw_if_index=self.pg0.sw_if_index, ) policie_tbls = VppEnum.vl_api_cnat_snat_policy_table_t self.vapi.cnat_set_snat_policy( policy=VppEnum.vl_api_cnat_snat_policies_t.CNAT_POLICY_IF_PFX ) for i in self.pg_interfaces: self.vapi.cnat_snat_policy_add_del_if( sw_if_index=i.sw_if_index, is_add=1 if is_enable else 0, table=policie_tbls.CNAT_POLICY_INCLUDE_V6, ) self.vapi.cnat_snat_policy_add_del_if( sw_if_index=i.sw_if_index, is_add=1 if is_enable else 0, table=policie_tbls.CNAT_POLICY_INCLUDE_V4, ) def setUp(self): super(TestCNatSourceNAT, self).setUp() self.create_pg_interfaces(range(3)) self.pg1.generate_remote_hosts(2) for i in self.pg_interfaces: i.admin_up() i.config_ip4() i.resolve_arp() i.config_ip6() i.resolve_ndp() i.configure_ipv6_neighbors() i.configure_ipv4_neighbors() self._enable_disable_snat(is_enable=True) def tearDown(self): self._enable_disable_snat(is_enable=True) self.vapi.cnat_session_purge() for i in self.pg_interfaces: i.unconfig_ip4() i.unconfig_ip6() i.admin_down() super(TestCNatSourceNAT, self).tearDown() def test_snat_v6(self): # """ CNat Source Nat v6 """ self.sourcenat_test_tcp_udp_conf(TCP, is_v6=True) self.sourcenat_test_tcp_udp_conf(UDP, is_v6=True) self.sourcenat_test_icmp_echo_conf(is_v6=True) def test_snat_v4(self): # """ CNat Source Nat v4 """ self.sourcenat_test_tcp_udp_conf(TCP) self.sourcenat_test_tcp_udp_conf(UDP) self.sourcenat_test_icmp_echo_conf() def sourcenat_test_icmp_echo_conf(self, is_v6=False): ctx = CnatTestContext(self, ICMP, is_v6=is_v6) # 8 is ICMP type echo (v4 only) ctx.cnat_send(self.pg0, 0, 0xFEED, self.pg1, 0, 8) ctx.cnat_expect(self.pg2, 0, None, self.pg1, 0, 8) ctx.cnat_send_return().cnat_expect_return() def sourcenat_test_tcp_udp_conf(self, L4PROTO, is_v6=False): ctx = CnatTestContext(self, L4PROTO, is_v6) # we should source NAT ctx.cnat_send(self.pg0, 0, 1234, self.pg1, 0, 6661) ctx.cnat_expect(self.pg2, 0, None, self.pg1, 0, 6661) ctx.cnat_send_return().cnat_expect_return() # exclude dst address of pg1.1 from snat if is_v6: exclude_prefix = ip_network( "%s/100" % self.pg1.remote_hosts[1].ip6, strict=False ) else: exclude_prefix = ip_network( "%s/16" % self.pg1.remote_hosts[1].ip4, strict=False ) # add remote host to exclude list self.vapi.cnat_snat_policy_add_del_exclude_pfx(prefix=exclude_prefix, is_add=1) # We should not source NAT the id=1 ctx.cnat_send(self.pg0, 0, 1234, self.pg1, 1, 6661) ctx.cnat_expect(self.pg0, 0, 1234, self.pg1, 1, 6661) ctx.cnat_send_return().cnat_expect_return() # But we should source NAT the id=0 ctx.cnat_send(self.pg0, 0, 1234, self.pg1, 0, 6661) ctx.cnat_expect(self.pg2, 0, None, self.pg1, 0, 6661) ctx.cnat_send_return().cnat_expect_return() # remove remote host from exclude list self.vapi.cnat_snat_policy_add_del_exclude_pfx(prefix=exclude_prefix, is_add=0) self.vapi.cnat_session_purge() # We should source NAT again ctx.cnat_send(self.pg0, 0, 1234, self.pg1, 1, 6661) ctx.cnat_expect(self.pg2, 0, None, self.pg1, 1, 6661) ctx.cnat_send_return().cnat_expect_return() # test return ICMP error nating ctx.cnat_send(self.pg0, 0, 1234, self.pg1, 1, 6661) ctx.cnat_expect(self.pg2, 0, None, self.pg1, 1, 6661) ctx.cnat_send_icmp_return_error().cnat_expect_icmp_error_return() self.vapi.cnat_session_purge() class TestCNatDHCP(CnatCommonTestCase): """CNat Translation""" @classmethod def setUpClass(cls): super(TestCNatDHCP, cls).setUpClass() @classmethod def tearDownClass(cls): super(TestCNatDHCP, cls).tearDownClass() def tearDown(self): for i in self.pg_interfaces: i.admin_down() super(TestCNatDHCP, self).tearDown() def make_addr(self, sw_if_index, addr_id, is_v6): if is_v6: return "fd01:%x::%u" % (sw_if_index, addr_id + 1) return "172.16.%u.%u" % (sw_if_index, addr_id) def make_prefix(self, sw_if_index, addr_id, is_v6): if is_v6: return "%s/128" % self.make_addr(sw_if_index, addr_id, is_v6) return "%s/32" % self.make_addr(sw_if_index, addr_id, is_v6) def check_resolved(self, tr, addr_id, is_v6=False): qt = tr.query_vpp_config() self.assertEqual( str(qt.vip.addr), self.make_addr(tr.vip.sw_if_index, addr_id, is_v6) ) self.assertEqual(len(qt.paths), len(tr.paths)) for path_tr, path_qt in zip(tr.paths, qt.paths): src_qt = path_qt.src_ep dst_qt = path_qt.dst_ep src_tr, dst_tr = path_tr self.assertEqual( str(src_qt.addr), self.make_addr(src_tr.sw_if_index, addr_id, is_v6) ) self.assertEqual( str(dst_qt.addr), self.make_addr(dst_tr.sw_if_index, addr_id, is_v6) ) def add_del_address(self, pg, addr_id, is_add=True, is_v6=False): self.vapi.sw_interface_add_del_address( sw_if_index=pg.sw_if_index, prefix=self.make_prefix(pg.sw_if_index, addr_id, is_v6), is_add=1 if is_add else 0, ) def _test_dhcp_v46(self, is_v6): self.create_pg_interfaces(range(4)) for i in self.pg_interfaces: i.admin_up() paths = [ (Endpoint(pg=self.pg1, is_v6=is_v6), Endpoint(pg=self.pg2, is_v6=is_v6)), (Endpoint(pg=self.pg1, is_v6=is_v6), Endpoint(pg=self.pg3, is_v6=is_v6)), ] ep = Endpoint(pg=self.pg0, is_v6=is_v6) t = Translation(self, TCP, ep, paths, 0x9F).add_vpp_config() # Add an address on every interface # and check it is reflected in the cnat config for pg in self.pg_interfaces: self.add_del_address(pg, addr_id=0, is_add=True, is_v6=is_v6) self.check_resolved(t, addr_id=0, is_v6=is_v6) # Add a new address on every interface, remove the old one # and check it is reflected in the cnat config for pg in self.pg_interfaces: self.add_del_address(pg, addr_id=1, is_add=True, is_v6=is_v6) self.add_del_address(pg, addr_id=0, is_add=False, is_v6=is_v6) self.check_resolved(t, addr_id=1, is_v6=is_v6) # remove the configuration for pg in self.pg_interfaces: self.add_del_address(pg, addr_id=1, is_add=False, is_v6=is_v6) t.remove_vpp_config() def test_dhcp_v4(self): self._test_dhcp_v46(False) def test_dhcp_v6(self): self._test_dhcp_v46(True) def test_dhcp_snat(self): self.create_pg_interfaces(range(1)) for i in self.pg_interfaces: i.admin_up() self.vapi.cnat_set_snat_addresses(sw_if_index=self.pg0.sw_if_index) # Add an address on every interface # and check it is reflected in the cnat config for pg in self.pg_interfaces: self.add_del_address(pg, addr_id=0, is_add=True, is_v6=False) self.add_del_address(pg, addr_id=0, is_add=True, is_v6=True) r = self.vapi.cnat_get_snat_addresses() self.assertEqual( str(r.snat_ip4), self.make_addr(self.pg0.sw_if_index, addr_id=0, is_v6=False), ) self.assertEqual( str(r.snat_ip6), self.make_addr(self.pg0.sw_if_index, addr_id=0, is_v6=True) ) # Add a new address on every interface, remove the old one # and check it is reflected in the cnat config for pg in self.pg_interfaces: self.add_del_address(pg, addr_id=1, is_add=True, is_v6=False) self.add_del_address(pg, addr_id=1, is_add=True, is_v6=True) self.add_del_address(pg, addr_id=0, is_add=False, is_v6=False) self.add_del_address(pg, addr_id=0, is_add=False, is_v6=True) r = self.vapi.cnat_get_snat_addresses() self.assertEqual( str(r.snat_ip4), self.make_addr(self.pg0.sw_if_index, addr_id=1, is_v6=False), ) self.assertEqual( str(r.snat_ip6), self.make_addr(self.pg0.sw_if_index, addr_id=1, is_v6=True) ) # remove the configuration for pg in self.pg_interfaces: self.add_del_address(pg, addr_id=1, is_add=False, is_v6=False) self.add_del_address(pg, addr_id=1, is_add=False, is_v6=True) self.vapi.cnat_set_snat_addresses(sw_if_index=INVALID_INDEX) if __name__ == "__main__": unittest.main(testRunner=VppTestRunner)