cnat: Ip ICMP error support

Type: feature

Add CNAT translation for ICMP 4 & 6 errors
inner packet will be translated according
to existing sessions.

Change-Id: If118751988f44ef96b800878596296d1ab8ab6f8
Signed-off-by: Nathan Skrzypczak <nathan.skrzypczak@gmail.com>
This commit is contained in:
Nathan Skrzypczak
2020-09-08 15:16:08 +02:00
committed by Dave Barach
parent 277d402bee
commit ece39214bc
6 changed files with 557 additions and 165 deletions

View File

@ -61,12 +61,10 @@ cnat_client_free_by_ip (ip46_address_t * ip, u8 af)
cnat_client_t *cc;
cc = (AF_IP4 == af ?
cnat_client_ip4_find (&ip->ip4) : cnat_client_ip6_find (&ip->ip6));
/* This can happen if the translation gets deleted
before the session */
if (NULL == cc)
return;
ASSERT (NULL != cc);
if ((0 == cnat_client_uncnt_session (cc))
&& (cc->flags & CNAT_FLAG_EXPIRES))
&& (cc->flags & CNAT_FLAG_EXPIRES) && (0 == cc->tr_refcnt))
cnat_client_destroy (cc);
}
@ -101,7 +99,6 @@ cnat_client_throttle_pool_process ()
/* *INDENT-ON* */
vec_foreach (ai, del_vec)
{
/* Free session */
addr = pool_elt_at_index (cnat_client_db.throttle_pool[i], *ai);
pool_put (cnat_client_db.throttle_pool[i], addr);
}
@ -127,7 +124,7 @@ cnat_client_translation_deleted (index_t cci)
ASSERT (!(cc->flags & CNAT_FLAG_EXPIRES));
cc->tr_refcnt--;
if (0 == cc->tr_refcnt)
if (0 == cc->tr_refcnt && 0 == cc->session_refcnt)
cnat_client_destroy (cc);
}
@ -171,6 +168,8 @@ cnat_client_add (const ip_address_t * ip, u8 flags)
cci = cc - cnat_client_pool;
cc->parent_cci = cci;
cc->flags = flags;
cc->tr_refcnt = 0;
cc->session_refcnt = 0;
ip_address_copy (&cc->cc_ip, ip);
cnat_client_db_add (cc);
@ -238,9 +237,16 @@ cnat_client_dpo_interpose (const dpo_id_t * original,
int
cnat_client_purge (void)
{
vlib_thread_main_t *tm = vlib_get_thread_main ();
int nthreads;
nthreads = tm->n_threads + 1;
ASSERT (0 == hash_elts (cnat_client_db.crd_cip6));
ASSERT (0 == hash_elts (cnat_client_db.crd_cip4));
ASSERT (0 == pool_elts (cnat_client_pool));
for (int i = 0; i < nthreads; i++)
{
ASSERT (0 == pool_elts (cnat_client_db.throttle_pool[i]));
}
return (0);
}

File diff suppressed because it is too large Load Diff

View File

@ -81,7 +81,8 @@ cnat_snat_inline (vlib_main_t * vm,
vnet_feature_next (&arc_next0, b);
next0 = arc_next0;
if (iproto != IP_PROTOCOL_UDP && iproto != IP_PROTOCOL_TCP)
if (iproto != IP_PROTOCOL_UDP && iproto != IP_PROTOCOL_TCP
&& iproto != IP_PROTOCOL_ICMP && iproto != IP_PROTOCOL_ICMP6)
{
/* Dont translate */
goto trace;

View File

@ -95,7 +95,8 @@ cnat_vip_inline (vlib_main_t * vm,
cc = cnat_client_get (vnet_buffer (b)->ip.adj_index[VLIB_TX]);
if (iproto != IP_PROTOCOL_UDP && iproto != IP_PROTOCOL_TCP)
if (iproto != IP_PROTOCOL_UDP && iproto != IP_PROTOCOL_TCP
&& iproto != IP_PROTOCOL_ICMP && iproto != IP_PROTOCOL_ICMP6)
{
/* Dont translate & follow the fib programming */
next0 = cc->cc_parent.dpoi_next_node;
@ -214,6 +215,7 @@ cnat_vip_inline (vlib_main_t * vm,
}
session->value.cs_lbi = dpo0->dpoi_index;
/* refcnt session in current client */
cnat_client_cnt_session (cc);
cnat_session_create (session, ctx, rsession_flags);
created_session = 1;
@ -222,7 +224,6 @@ cnat_vip_inline (vlib_main_t * vm,
vnet_buffer (b)->ip.adj_index[VLIB_TX] = session->value.cs_lbi;
}
if (AF_IP4 == ctx->af)
cnat_translation_ip4 (session, ip4, udp0);
else

View File

@ -146,6 +146,11 @@ extern u64 cnat_session_scan (vlib_main_t * vm, f64 start_time, int i);
*/
extern int cnat_session_purge (void);
/**
* Free a session & update refcounts
*/
extern void cnat_session_free (cnat_session_t * session);
/*
* fd.io coding-style-patch-verification: ON
*

View File

@ -7,8 +7,11 @@ from vpp_ip import DpoProto
from scapy.packet import Raw
from scapy.layers.l2 import Ether
from scapy.layers.inet import IP, UDP, TCP
from scapy.layers.inet6 import IPv6
from scapy.layers.inet import IP, UDP, TCP, ICMP
from scapy.layers.inet import IPerror, TCPerror, UDPerror, ICMPerror
from scapy.layers.inet6 import IPv6, IPerror6, ICMPv6DestUnreach
import struct
from ipaddress import ip_address, ip_network, \
IPv4Address, IPv6Address, IPv4Network, IPv6Network
@ -39,6 +42,10 @@ class Ep(object):
return {'addr': self.ip,
'port': self.port}
@property
def isV6(self):
return ":" in self.ip
def __str__(self):
return ("%s:%d" % (self.ip, self.port))
@ -180,10 +187,10 @@ class TestCNatTranslation(VppTestCase):
i.admin_down()
super(TestCNatTranslation, self).tearDown()
def cnat_create_translation(self, vip, nbr, isV6=False):
ip_v = "ip6" if isV6 else "ip4"
def cnat_create_translation(self, vip, nbr):
ip_v = "ip6" if vip.isV6 else "ip4"
dep = Ep(getattr(self.pg1.remote_hosts[nbr], ip_v), 4000 + nbr)
sep = Ep("::", 0) if isV6 else Ep("0.0.0.0", 0)
sep = Ep("::", 0) if vip.isV6 else Ep("0.0.0.0", 0)
t1 = VppCNatTranslation(
self, vip.l4p, vip,
[EpTuple(sep, dep), EpTuple(sep, dep)])
@ -341,7 +348,7 @@ class TestCNatTranslation(VppTestCase):
trs = []
for nbr, vip in enumerate(vips):
trs.append(self.cnat_create_translation(vip, nbr, isV6=isV6))
trs.append(self.cnat_create_translation(vip, nbr))
self.logger.info(self.vapi.cli("sh cnat client"))
self.logger.info(self.vapi.cli("sh cnat translation"))
@ -372,8 +379,10 @@ class TestCNatTranslation(VppTestCase):
n_tries += 1
sessions = self.vapi.cnat_session_dump()
self.sleep(2)
print(self.vapi.cli("show cnat session verbose"))
self.assertTrue(n_tries < 100)
self.vapi.cli("test cnat scanner off")
#
# load some flows again and purge
@ -398,6 +407,109 @@ class TestCNatTranslation(VppTestCase):
self.vapi.cnat_session_purge()
self.assertFalse(self.vapi.cnat_session_dump())
def test_icmp(self):
vips = [
Ep("30.0.0.1", 5555),
Ep("30.0.0.2", 5554),
Ep("30.0.0.2", 5553, UDP),
Ep("30::1", 6666),
Ep("30::2", 5553, UDP),
]
sport = 1234
self.pg0.generate_remote_hosts(len(vips))
self.pg0.configure_ipv6_neighbors()
self.pg0.configure_ipv4_neighbors()
self.pg1.generate_remote_hosts(len(vips))
self.pg1.configure_ipv6_neighbors()
self.pg1.configure_ipv4_neighbors()
self.vapi.cli("test cnat scanner off")
trs = []
for nbr, vip in enumerate(vips):
trs.append(self.cnat_create_translation(vip, nbr))
self.logger.info(self.vapi.cli("sh cnat client"))
self.logger.info(self.vapi.cli("sh cnat translation"))
for nbr, vip in enumerate(vips):
if vip.isV6:
client_addr = self.pg0.remote_hosts[0].ip6
remote_addr = self.pg1.remote_hosts[nbr].ip6
remote2_addr = self.pg2.remote_hosts[0].ip6
else:
client_addr = self.pg0.remote_hosts[0].ip4
remote_addr = self.pg1.remote_hosts[nbr].ip4
remote2_addr = self.pg2.remote_hosts[0].ip4
IP46 = IPv6 if vip.isV6 else IP
# from client to vip
p1 = (Ether(dst=self.pg0.local_mac,
src=self.pg0.remote_hosts[0].mac) /
IP46(src=client_addr, dst=vip.ip) /
vip.l4p(sport=sport, dport=vip.port) /
Raw())
rxs = self.send_and_expect(self.pg0,
p1 * N_PKTS,
self.pg1)
for rx in rxs:
self.assert_packet_checksums_valid(rx)
self.assertEqual(rx[IP46].dst, remote_addr)
self.assertEqual(rx[vip.l4p].dport, 4000 + nbr)
self.assertEqual(rx[IP46].src, client_addr)
self.assertEqual(rx[vip.l4p].sport, sport)
InnerIP = rxs[0][IP46]
ICMP46 = ICMPv6DestUnreach if vip.isV6 else ICMP
ICMPelem = ICMPv6DestUnreach(code=1) if vip.isV6 else ICMP(type=11)
# from vip to client, ICMP error
p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
IP46(src=remote_addr, dst=client_addr) /
ICMPelem / InnerIP)
rxs = self.send_and_expect(self.pg1,
p1 * N_PKTS,
self.pg0)
TCPUDPError = TCPerror if vip.l4p == TCP else UDPerror
IP46error = IPerror6 if vip.isV6 else IPerror
for rx in rxs:
self.assert_packet_checksums_valid(rx)
self.assertEqual(rx[IP46].src, vip.ip)
self.assertEqual(rx[ICMP46][IP46error].src, client_addr)
self.assertEqual(rx[ICMP46][IP46error].dst, vip.ip)
self.assertEqual(rx[ICMP46][IP46error]
[TCPUDPError].sport, sport)
self.assertEqual(rx[ICMP46][IP46error]
[TCPUDPError].dport, vip.port)
# from other remote to client, ICMP error
# outside shouldn't be NAT-ed
p1 = (Ether(dst=self.pg2.local_mac, src=self.pg2.remote_mac) /
IP46(src=remote2_addr, dst=client_addr) /
ICMPelem / InnerIP)
rxs = self.send_and_expect(self.pg1,
p1 * N_PKTS,
self.pg0)
TCPUDPError = TCPerror if vip.l4p == TCP else UDPerror
IP46error = IPerror6 if vip.isV6 else IPerror
for rx in rxs:
self.assert_packet_checksums_valid(rx)
self.assertEqual(rx[IP46].src, remote2_addr)
self.assertEqual(rx[ICMP46][IP46error].src, client_addr)
self.assertEqual(rx[ICMP46][IP46error].dst, vip.ip)
self.assertEqual(rx[ICMP46][IP46error]
[TCPUDPError].sport, sport)
self.assertEqual(rx[ICMP46][IP46error]
[TCPUDPError].dport, vip.port)
self.vapi.cnat_session_purge()
def test_cnat6(self):
# """ CNat Translation ipv6 """
vips = [
@ -478,7 +590,7 @@ class TestCNatSourceNAT(VppTestCase):
def cnat_test_sourcenat(self, srcNatAddr, l4p=TCP, isV6=False):
ip_v = "ip6" if isV6 else "ip4"
ip_class = IPv6 if isV6 else IP
IP46 = IPv6 if isV6 else IP
sports = [1234, 1235, 1236]
dports = [6661, 6662, 6663]
@ -493,14 +605,17 @@ class TestCNatSourceNAT(VppTestCase):
t1 = self.cnat_set_snat_address(srcNatAddr, self.pg0, isV6)
for nbr, remote_host in enumerate(self.pg1.remote_hosts):
if isV6:
client_addr = self.pg0.remote_hosts[0].ip6
remote_addr = self.pg1.remote_hosts[nbr].ip6
else:
client_addr = self.pg0.remote_hosts[0].ip4
remote_addr = self.pg1.remote_hosts[nbr].ip4
# from pods to outside network
p1 = (
Ether(
dst=self.pg0.local_mac,
src=self.pg0.remote_hosts[0].mac) /
ip_class(
src=getattr(self.pg0.remote_hosts[0], ip_v),
dst=getattr(remote_host, ip_v)) /
Ether(dst=self.pg0.local_mac,
src=self.pg0.remote_hosts[0].mac) /
IP46(src=client_addr, dst=remote_addr) /
l4p(sport=sports[nbr], dport=dports[nbr]) /
Raw())
@ -510,21 +625,16 @@ class TestCNatSourceNAT(VppTestCase):
self.pg1)
for rx in rxs:
self.assert_packet_checksums_valid(rx)
self.assertEqual(
rx[ip_class].dst,
getattr(remote_host, ip_v))
self.assertEqual(rx[IP46].dst, remote_addr)
self.assertEqual(rx[l4p].dport, dports[nbr])
self.assertEqual(
rx[ip_class].src,
srcNatAddr)
self.assertEqual(rx[IP46].src, srcNatAddr)
sport = rx[l4p].sport
# from outside to pods
p2 = (
Ether(
dst=self.pg1.local_mac,
src=self.pg1.remote_hosts[nbr].mac) /
ip_class(src=getattr(remote_host, ip_v), dst=srcNatAddr) /
Ether(dst=self.pg1.local_mac,
src=self.pg1.remote_hosts[nbr].mac) /
IP46(src=remote_addr, dst=srcNatAddr) /
l4p(sport=dports[nbr], dport=sport) /
Raw())
@ -535,18 +645,14 @@ class TestCNatSourceNAT(VppTestCase):
for rx in rxs:
self.assert_packet_checksums_valid(rx)
self.assertEqual(
rx[ip_class].dst,
getattr(self.pg0.remote_hosts[0], ip_v))
self.assertEqual(rx[IP46].dst, client_addr)
self.assertEqual(rx[l4p].dport, sports[nbr])
self.assertEqual(rx[l4p].sport, dports[nbr])
self.assertEqual(
rx[ip_class].src,
getattr(remote_host, ip_v))
self.assertEqual(rx[IP46].src, remote_addr)
# add remote host to exclude list
subnet_mask = 100 if isV6 else 16
subnet = getattr(remote_host, ip_v) + "/" + str(subnet_mask)
subnet = "%s/%d" % (remote_addr, subnet_mask)
exclude_subnet = ip_network(subnet, strict=False)
t1.cnat_exclude_subnet(exclude_subnet)
@ -558,13 +664,9 @@ class TestCNatSourceNAT(VppTestCase):
self.pg1)
for rx in rxs:
self.assert_packet_checksums_valid(rx)
self.assertEqual(
rx[ip_class].dst,
getattr(remote_host, ip_v))
self.assertEqual(rx[IP46].dst, remote_addr)
self.assertEqual(rx[l4p].dport, dports[nbr])
self.assertEqual(
rx[ip_class].src,
getattr(self.pg0.remote_hosts[0], ip_v))
self.assertEqual(rx[IP46].src, client_addr)
# remove remote host from exclude list
t1.cnat_exclude_subnet(exclude_subnet, isAdd=False)
@ -577,13 +679,9 @@ class TestCNatSourceNAT(VppTestCase):
for rx in rxs:
self.assert_packet_checksums_valid(rx)
self.assertEqual(
rx[ip_class].dst,
getattr(remote_host, ip_v))
self.assertEqual(rx[IP46].dst, remote_addr)
self.assertEqual(rx[l4p].dport, dports[nbr])
self.assertEqual(
rx[ip_class].src,
srcNatAddr)
self.assertEqual(rx[IP46].src, srcNatAddr)
def test_cnat6_sourcenat(self):
# """ CNat Source Nat ipv6 """