cnat: add support for icmp traceroute
Type: improvement Signed-off-by: Mohsin Kazmi <sykazmi@cisco.com> Change-Id: Ief1e97d03b7a934547add35ac3ed1f93f2499a20
This commit is contained in:
parent
2e67a3f377
commit
f0a126a1eb
@ -334,6 +334,10 @@ cnat_translation_icmp4_error (ip4_header_t *outer_ip4, icmp46_header_t *icmp,
|
||||
cnat_ip4_translate_l4 (ip4, udp, &inner_l4_sum, new_addr, new_port,
|
||||
0 /* flags */);
|
||||
tcp->checksum = ip_csum_fold (inner_l4_sum);
|
||||
|
||||
/* TCP checksum changed */
|
||||
sum = ip_csum_update (sum, inner_l4_old_sum, inner_l4_sum, ip4_header_t,
|
||||
checksum);
|
||||
}
|
||||
else if (ip4->protocol == IP_PROTOCOL_UDP)
|
||||
{
|
||||
@ -341,14 +345,40 @@ cnat_translation_icmp4_error (ip4_header_t *outer_ip4, icmp46_header_t *icmp,
|
||||
cnat_ip4_translate_l4 (ip4, udp, &inner_l4_sum, new_addr, new_port,
|
||||
0 /* flags */);
|
||||
udp->checksum = ip_csum_fold (inner_l4_sum);
|
||||
|
||||
/* UDP checksum changed */
|
||||
sum = ip_csum_update (sum, inner_l4_old_sum, inner_l4_sum, ip4_header_t,
|
||||
checksum);
|
||||
}
|
||||
else if (ip4->protocol == IP_PROTOCOL_ICMP)
|
||||
{
|
||||
icmp46_header_t *icmp = (icmp46_header_t *) udp;
|
||||
if (icmp_type_is_echo (icmp->type))
|
||||
{
|
||||
u16 old_port;
|
||||
cnat_echo_header_t *echo = (cnat_echo_header_t *) (icmp + 1);
|
||||
inner_l4_old_sum = inner_l4_sum = icmp->checksum;
|
||||
|
||||
old_port = echo->identifier;
|
||||
echo->identifier = new_port[VLIB_RX];
|
||||
|
||||
inner_l4_sum = ip_csum_update (
|
||||
inner_l4_sum, old_port, new_port[VLIB_RX], udp_header_t, src_port);
|
||||
|
||||
icmp->checksum = ip_csum_fold (inner_l4_sum);
|
||||
|
||||
sum = ip_csum_update (sum, old_port, new_port[VLIB_RX], udp_header_t,
|
||||
src_port);
|
||||
/* checksum changed */
|
||||
sum = ip_csum_update (sum, inner_l4_old_sum, inner_l4_sum,
|
||||
ip4_header_t, checksum);
|
||||
}
|
||||
old_port[VLIB_TX] = 0;
|
||||
old_port[VLIB_RX] = 0;
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
/* UDP/TCP checksum changed */
|
||||
sum = ip_csum_update (sum, inner_l4_old_sum, inner_l4_sum,
|
||||
ip4_header_t, checksum);
|
||||
|
||||
/* UDP/TCP Ports changed */
|
||||
if (old_port[VLIB_TX] && new_port[VLIB_TX])
|
||||
sum = ip_csum_update (sum, old_port[VLIB_TX], new_port[VLIB_TX],
|
||||
@ -569,6 +599,17 @@ cnat_translation_icmp6_error (ip6_header_t * outer_ip6,
|
||||
cnat_ip6_translate_l4 (ip6, udp, &inner_l4_sum, new_addr, new_port,
|
||||
0 /* oflags */);
|
||||
tcp->checksum = ip_csum_fold (inner_l4_sum);
|
||||
|
||||
/* TCP checksum changed */
|
||||
sum = ip_csum_update (sum, inner_l4_old_sum, inner_l4_sum, ip4_header_t,
|
||||
checksum);
|
||||
|
||||
/* TCP Ports changed */
|
||||
sum = ip_csum_update (sum, old_port[VLIB_TX], new_port[VLIB_TX],
|
||||
tcp_header_t, dst_port);
|
||||
|
||||
sum = ip_csum_update (sum, old_port[VLIB_RX], new_port[VLIB_RX],
|
||||
tcp_header_t, src_port);
|
||||
}
|
||||
else if (ip6->protocol == IP_PROTOCOL_UDP)
|
||||
{
|
||||
@ -576,21 +617,61 @@ cnat_translation_icmp6_error (ip6_header_t * outer_ip6,
|
||||
cnat_ip6_translate_l4 (ip6, udp, &inner_l4_sum, new_addr, new_port,
|
||||
0 /* oflags */);
|
||||
udp->checksum = ip_csum_fold (inner_l4_sum);
|
||||
|
||||
/* UDP checksum changed */
|
||||
sum = ip_csum_update (sum, inner_l4_old_sum, inner_l4_sum, ip4_header_t,
|
||||
checksum);
|
||||
|
||||
/* UDP Ports changed */
|
||||
sum = ip_csum_update (sum, old_port[VLIB_TX], new_port[VLIB_TX],
|
||||
udp_header_t, dst_port);
|
||||
|
||||
sum = ip_csum_update (sum, old_port[VLIB_RX], new_port[VLIB_RX],
|
||||
udp_header_t, src_port);
|
||||
}
|
||||
else if (ip6->protocol == IP_PROTOCOL_ICMP6)
|
||||
{
|
||||
/* Update ICMP6 checksum */
|
||||
icmp46_header_t *inner_icmp = (icmp46_header_t *) udp;
|
||||
inner_l4_old_sum = inner_l4_sum = inner_icmp->checksum;
|
||||
if (icmp6_type_is_echo (inner_icmp->type))
|
||||
{
|
||||
cnat_echo_header_t *echo = (cnat_echo_header_t *) (inner_icmp + 1);
|
||||
u16 old_port = echo->identifier;
|
||||
echo->identifier = new_port[VLIB_RX];
|
||||
inner_l4_sum = ip_csum_update (
|
||||
inner_l4_sum, old_port, new_port[VLIB_RX], udp_header_t, src_port);
|
||||
|
||||
sum = ip_csum_update (sum, old_port, new_port[VLIB_RX], udp_header_t,
|
||||
src_port);
|
||||
}
|
||||
|
||||
inner_l4_sum =
|
||||
ip_csum_add_even (inner_l4_sum, new_addr[VLIB_TX].as_u64[0]);
|
||||
inner_l4_sum =
|
||||
ip_csum_add_even (inner_l4_sum, new_addr[VLIB_TX].as_u64[1]);
|
||||
inner_l4_sum =
|
||||
ip_csum_sub_even (inner_l4_sum, ip6->dst_address.as_u64[0]);
|
||||
inner_l4_sum =
|
||||
ip_csum_sub_even (inner_l4_sum, ip6->dst_address.as_u64[1]);
|
||||
|
||||
inner_l4_sum =
|
||||
ip_csum_add_even (inner_l4_sum, new_addr[VLIB_RX].as_u64[0]);
|
||||
inner_l4_sum =
|
||||
ip_csum_add_even (inner_l4_sum, new_addr[VLIB_RX].as_u64[1]);
|
||||
inner_l4_sum =
|
||||
ip_csum_sub_even (inner_l4_sum, ip6->src_address.as_u64[0]);
|
||||
inner_l4_sum =
|
||||
ip_csum_sub_even (inner_l4_sum, ip6->src_address.as_u64[1]);
|
||||
inner_icmp->checksum = ip_csum_fold (inner_l4_sum);
|
||||
|
||||
/* Update ICMP6 checksum change */
|
||||
sum = ip_csum_update (sum, inner_l4_old_sum, inner_l4_sum, ip4_header_t,
|
||||
checksum);
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
/* UDP/TCP checksum changed */
|
||||
sum = ip_csum_update (sum, inner_l4_old_sum, inner_l4_sum, ip4_header_t,
|
||||
checksum);
|
||||
|
||||
/* UDP/TCP Ports changed */
|
||||
sum = ip_csum_update (sum, old_port[VLIB_TX], new_port[VLIB_TX],
|
||||
udp_header_t, dst_port);
|
||||
|
||||
sum = ip_csum_update (sum, old_port[VLIB_RX], new_port[VLIB_RX],
|
||||
udp_header_t, src_port);
|
||||
|
||||
cnat_ip6_translate_l3 (ip6, new_addr);
|
||||
/* IP src/dst addr changed */
|
||||
sum = ip_csum_add_even (sum, new_addr[VLIB_TX].as_u64[0]);
|
||||
@ -677,15 +758,35 @@ cnat_session_make_key (vlib_buffer_t *b, ip_address_family_t af,
|
||||
if (icmp_type_is_error_message (icmp->type))
|
||||
{
|
||||
ip4 = (ip4_header_t *) (icmp + 2); /* Use inner packet */
|
||||
udp = (udp_header_t *) (ip4 + 1);
|
||||
/* Swap dst & src for search as ICMP payload is reversed */
|
||||
ip46_address_set_ip4 (&session->key.cs_ip[VLIB_RX],
|
||||
&ip4->dst_address);
|
||||
ip46_address_set_ip4 (&session->key.cs_ip[VLIB_TX],
|
||||
&ip4->src_address);
|
||||
session->key.cs_proto = ip4->protocol;
|
||||
session->key.cs_port[VLIB_TX] = udp->src_port;
|
||||
session->key.cs_port[VLIB_RX] = udp->dst_port;
|
||||
if (PREDICT_FALSE (ip4->protocol == IP_PROTOCOL_ICMP))
|
||||
{
|
||||
icmp = (icmp46_header_t *) (ip4 + 1);
|
||||
if (icmp_type_is_echo (icmp->type))
|
||||
{
|
||||
cnat_echo_header_t *echo =
|
||||
(cnat_echo_header_t *) (icmp + 1);
|
||||
ip46_address_set_ip4 (&session->key.cs_ip[VLIB_RX],
|
||||
&ip4->dst_address);
|
||||
ip46_address_set_ip4 (&session->key.cs_ip[VLIB_TX],
|
||||
&ip4->src_address);
|
||||
session->key.cs_proto = ip4->protocol;
|
||||
session->key.cs_port[VLIB_TX] = echo->identifier;
|
||||
session->key.cs_port[VLIB_RX] = echo->identifier;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
udp = (udp_header_t *) (ip4 + 1);
|
||||
/* Swap dst & src for search as ICMP payload is reversed
|
||||
*/
|
||||
ip46_address_set_ip4 (&session->key.cs_ip[VLIB_RX],
|
||||
&ip4->dst_address);
|
||||
ip46_address_set_ip4 (&session->key.cs_ip[VLIB_TX],
|
||||
&ip4->src_address);
|
||||
session->key.cs_proto = ip4->protocol;
|
||||
session->key.cs_port[VLIB_TX] = udp->src_port;
|
||||
session->key.cs_port[VLIB_RX] = udp->dst_port;
|
||||
}
|
||||
}
|
||||
else if (icmp_type_is_echo (icmp->type))
|
||||
{
|
||||
@ -738,15 +839,35 @@ cnat_session_make_key (vlib_buffer_t *b, ip_address_family_t af,
|
||||
if (icmp6_type_is_error_message (icmp->type))
|
||||
{
|
||||
ip6 = (ip6_header_t *) (icmp + 2); /* Use inner packet */
|
||||
udp = (udp_header_t *) (ip6 + 1);
|
||||
/* Swap dst & src for search as ICMP payload is reversed */
|
||||
ip46_address_set_ip6 (&session->key.cs_ip[VLIB_RX],
|
||||
&ip6->dst_address);
|
||||
ip46_address_set_ip6 (&session->key.cs_ip[VLIB_TX],
|
||||
&ip6->src_address);
|
||||
session->key.cs_proto = ip6->protocol;
|
||||
session->key.cs_port[VLIB_TX] = udp->src_port;
|
||||
session->key.cs_port[VLIB_RX] = udp->dst_port;
|
||||
if (PREDICT_FALSE (ip6->protocol == IP_PROTOCOL_ICMP6))
|
||||
{
|
||||
icmp = (icmp46_header_t *) (ip6 + 1);
|
||||
if (icmp6_type_is_echo (icmp->type))
|
||||
{
|
||||
cnat_echo_header_t *echo =
|
||||
(cnat_echo_header_t *) (icmp + 1);
|
||||
ip46_address_set_ip6 (&session->key.cs_ip[VLIB_RX],
|
||||
&ip6->dst_address);
|
||||
ip46_address_set_ip6 (&session->key.cs_ip[VLIB_TX],
|
||||
&ip6->src_address);
|
||||
session->key.cs_proto = ip6->protocol;
|
||||
session->key.cs_port[VLIB_TX] = echo->identifier;
|
||||
session->key.cs_port[VLIB_RX] = echo->identifier;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
udp = (udp_header_t *) (ip6 + 1);
|
||||
/* Swap dst & src for search as ICMP payload is reversed
|
||||
*/
|
||||
ip46_address_set_ip6 (&session->key.cs_ip[VLIB_RX],
|
||||
&ip6->dst_address);
|
||||
ip46_address_set_ip6 (&session->key.cs_ip[VLIB_TX],
|
||||
&ip6->src_address);
|
||||
session->key.cs_proto = ip6->protocol;
|
||||
session->key.cs_port[VLIB_TX] = udp->src_port;
|
||||
session->key.cs_port[VLIB_RX] = udp->dst_port;
|
||||
}
|
||||
}
|
||||
else if (icmp6_type_is_echo (icmp->type))
|
||||
{
|
||||
|
@ -11,9 +11,10 @@ from config import config
|
||||
from scapy.packet import Raw
|
||||
from scapy.layers.l2 import Ether
|
||||
from scapy.layers.inet import IP, UDP, TCP, ICMP
|
||||
from scapy.layers.inet import IPerror, TCPerror, UDPerror
|
||||
from scapy.layers.inet import IPerror, TCPerror, UDPerror, ICMPerror
|
||||
from scapy.layers.inet6 import IPv6, IPerror6, ICMPv6DestUnreach
|
||||
from scapy.layers.inet6 import ICMPv6EchoRequest, ICMPv6EchoReply
|
||||
from scapy.layers.inet6 import ICMPv6TimeExceeded
|
||||
|
||||
from ipaddress import ip_network
|
||||
|
||||
@ -760,12 +761,14 @@ class TestCNatSourceNAT(CnatCommonTestCase):
|
||||
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)
|
||||
self.sourcenat_test_icmp_traceroute_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()
|
||||
self.sourcenat_test_icmp_traceroute_conf()
|
||||
|
||||
def sourcenat_test_icmp_echo_conf(self, is_v6=False):
|
||||
ctx = CnatTestContext(self, ICMP, is_v6=is_v6)
|
||||
@ -774,6 +777,125 @@ class TestCNatSourceNAT(CnatCommonTestCase):
|
||||
ctx.cnat_expect(self.pg2, 0, None, self.pg1, 0, 8)
|
||||
ctx.cnat_send_return().cnat_expect_return()
|
||||
|
||||
def sourcenat_test_icmp_traceroute_conf(self, is_v6=False):
|
||||
# IPv4 ICMP
|
||||
if not is_v6:
|
||||
# Create an ICMP traceroute packet with TTL set to 1.
|
||||
# The CNAT translates the packet, but the NATted packet is dropped
|
||||
# due to the TTL of 1. An ICMP Time Exceeded message is sent
|
||||
# to the source (which is the NATted address).
|
||||
# The packet will be translated once more to the original
|
||||
# source IP address.
|
||||
|
||||
icmp = (
|
||||
Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac)
|
||||
/ IP(
|
||||
ttl=1,
|
||||
src=self.pg0.remote_hosts[0].ip4,
|
||||
dst=self.pg1.remote_hosts[0].ip4,
|
||||
)
|
||||
/ ICMP(id=0xFEED, type=8) # ICMP Type Echo Request
|
||||
/ Raw()
|
||||
)
|
||||
|
||||
self.rxs = self.send_and_expect(self.pg0, icmp, self.pg0)
|
||||
|
||||
for rx in self.rxs:
|
||||
self.assert_packet_checksums_valid(rx)
|
||||
self.assertEqual(rx[IP].dst, self.pg0.remote_hosts[0].ip4)
|
||||
self.assertEqual(rx[IP].src, "172.16.1.1")
|
||||
self.assertEqual(rx[ICMP].type, 11) # ICMP Type 11 (Time Exceeded)
|
||||
self.assertEqual(
|
||||
rx[ICMP].code, 0
|
||||
) # ICMP Code 0 (TTL Zero During Transit)
|
||||
inner = rx[ICMP].payload
|
||||
self.assertEqual(inner[IPerror].src, self.pg0.remote_hosts[0].ip4)
|
||||
self.assertEqual(inner[IPerror].dst, self.pg1.remote_hosts[0].ip4)
|
||||
self.assertEqual(inner[ICMPerror].type, 8) # ICMP Echo Request
|
||||
self.assertEqual(inner[ICMPerror].id, 0xFEED)
|
||||
|
||||
# source ---> NATted Transit ---> Transit 2 ... ---> Transit N ---> Destination
|
||||
# Simulate an ICMP Time Exceeded message arriving at the NATted Transit
|
||||
# from the Transit N-2 node. This occurs because the NATted packet
|
||||
# is dropped due to a TTL of 1.
|
||||
# An ICMP Time Exceeded message is sent back to the source
|
||||
# (initially the NATted address). The CNAT then translates the message
|
||||
# back to the original source IP address.
|
||||
|
||||
# For ICMP based traffic, snat session uses identifier for session key.
|
||||
# snat allocates a new identifier. To hit the snat session from Transit N-2
|
||||
# to NATed Transit, packet should use snat allocated identifier. To get the
|
||||
# snat allocated identifier, echo request will be sent and captured at the
|
||||
# destination, taken out the identifier from the packet and use it to set
|
||||
# the identifier in the ICMP time exceed packet
|
||||
icmp[IP].ttl = 64
|
||||
rxs = self.send_and_expect(self.pg0, icmp, self.pg1)
|
||||
|
||||
icmp_error = (
|
||||
Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac)
|
||||
/ IP(src="172.16.1.1", dst=self.pg2.remote_hosts[0].ip4)
|
||||
/ ICMP(type=11, code=0)
|
||||
/ IPerror(
|
||||
src=self.pg2.remote_hosts[0].ip4, dst=self.pg1.remote_hosts[0].ip4
|
||||
)
|
||||
/ ICMPerror(id=rxs[0][ICMP].id, type=8)
|
||||
/ Raw()
|
||||
)
|
||||
|
||||
self.rxs = self.send_and_expect(self.pg1, icmp_error, self.pg0)
|
||||
for rx in self.rxs:
|
||||
self.assert_packet_checksums_valid(rx)
|
||||
self.assertEqual(rx[IP].dst, self.pg0.remote_hosts[0].ip4)
|
||||
self.assertEqual(rx[IP].src, "172.16.1.1")
|
||||
self.assertEqual(rx[ICMP].type, 11) # ICMP Type 11 (Time Exceeded)
|
||||
self.assertEqual(
|
||||
rx[ICMP].code, 0
|
||||
) # ICMP Code 0 (TTL Zero During Transit)
|
||||
inner = rx[ICMP].payload
|
||||
self.assertEqual(inner[IPerror].src, self.pg0.remote_hosts[0].ip4)
|
||||
self.assertEqual(inner[IPerror].dst, self.pg1.remote_hosts[0].ip4)
|
||||
self.assertEqual(inner[ICMPerror].type, 8) # ICMP Echo Request
|
||||
self.assertEqual(inner[ICMPerror].id, 0xFEED)
|
||||
|
||||
# IPv6 ICMPv6
|
||||
if is_v6:
|
||||
|
||||
# Create an ICMPv6 traceroute packet with Hop Limit set to 1.
|
||||
# The CNAT translates the packet, but the NATted packet is dropped
|
||||
# due to the Hop Limit of 1. An ICMPv6 Time Exceeded message is sent
|
||||
# back to the source (which is the NATted address).
|
||||
# The CNAT translates the message once more to restore
|
||||
# the original source IPv6 address.
|
||||
icmp6 = (
|
||||
Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac)
|
||||
/ IPv6(
|
||||
hlim=1,
|
||||
src=self.pg0.remote_hosts[0].ip6,
|
||||
dst=self.pg1.remote_hosts[0].ip6,
|
||||
)
|
||||
/ ICMPv6EchoRequest(id=0xFEED)
|
||||
/ Raw()
|
||||
)
|
||||
self.rxs = self.send_and_expect(self.pg0, icmp6, self.pg0)
|
||||
|
||||
for rx in self.rxs:
|
||||
self.assert_packet_checksums_valid(rx)
|
||||
self.assertEqual(rx[IPv6].dst, self.pg0.remote_hosts[0].ip6)
|
||||
self.assertEqual(rx[IPv6].src, "fd01:1::1")
|
||||
self.assertEqual(
|
||||
rx[ICMPv6TimeExceeded].type, 3
|
||||
) # ICMPv6 Type 3 (Time Exceeded)
|
||||
self.assertEqual(
|
||||
rx[ICMPv6TimeExceeded].code, 0
|
||||
) # ICMPv6 Code 0 (TTL Zero During Transit)
|
||||
inner = rx[ICMPv6TimeExceeded].payload
|
||||
self.assertEqual(inner[IPerror6].src, self.pg0.remote_hosts[0].ip6)
|
||||
self.assertEqual(inner[IPerror6].dst, self.pg1.remote_hosts[0].ip6)
|
||||
self.assertEqual(
|
||||
inner[ICMPv6EchoRequest].type, 128
|
||||
) # ICMPv6 Echo Request
|
||||
self.assertEqual(inner[ICMPv6EchoRequest].id, 0xFEED)
|
||||
|
||||
def sourcenat_test_tcp_udp_conf(self, L4PROTO, is_v6=False):
|
||||
ctx = CnatTestContext(self, L4PROTO, is_v6)
|
||||
# we should source NAT
|
||||
@ -823,7 +945,7 @@ class TestCNatSourceNAT(CnatCommonTestCase):
|
||||
|
||||
@unittest.skipIf("cnat" in config.excluded_plugins, "Exclude CNAT plugin tests")
|
||||
class TestCNatDHCP(CnatCommonTestCase):
|
||||
"""CNat Translation"""
|
||||
"""CNat DHCP"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
Loading…
x
Reference in New Issue
Block a user