map: Prevent IPv4 prefix spoofing during IPv6 -> IPv4

Prevent malicious packets with spoofed embedded IPv4 addresses
by limiting the IPv6 ingress packets to known MAP-T domains.
Drop spoofed packets.

Add several tests that ensure spoofing isn't allowed.

Type: fix
Fixes: fc7344f9be

Change-Id: I80a5dd10d5fe7492e3a1b04de389d649a78065e2
Signed-off-by: Jon Loeliger <jdl@netgate.com>
This commit is contained in:
Jon Loeliger
2020-01-28 07:30:28 -06:00
committed by Ole Trøan
parent 56817e2c48
commit 65866f03d9
2 changed files with 279 additions and 2 deletions

View File

@ -66,7 +66,7 @@ ip6_to_ip4_set_icmp_cb (ip6_header_t * ip6, ip4_header_t * ip4, void *arg)
// Security check
// Note that this prevents an intermediate IPv6 router from answering
// the request.
ip4_sadr = map_get_ip4 (&ip6->src_address, ctx->d->flags);
ip4_sadr = map_get_ip4 (&ip6->src_address, ctx->d->ip6_src_len);
if (ip6->src_address.as_u64[0] !=
map_get_pfx_net (ctx->d, ip4_sadr, ctx->sender_port)
|| ip6->src_address.as_u64[1] != map_get_sfx_net (ctx->d, ip4_sadr,
@ -88,7 +88,7 @@ ip6_to_ip4_set_inner_icmp_cb (ip6_header_t * ip6, ip4_header_t * ip4,
u32 inner_ip4_dadr;
//Security check of inner packet
inner_ip4_dadr = map_get_ip4 (&ip6->dst_address, ctx->d->flags);
inner_ip4_dadr = map_get_ip4 (&ip6->dst_address, ctx->d->ip6_src_len);
if (ip6->dst_address.as_u64[0] !=
map_get_pfx_net (ctx->d, inner_ip4_dadr, ctx->sender_port)
|| ip6->dst_address.as_u64[1] != map_get_sfx_net (ctx->d,
@ -305,6 +305,8 @@ ip6_map_t_fragmented (vlib_main_t * vm,
/*
* Translate IPv6 UDP/TCP packet to IPv4.
* Returns 0 on success.
* Returns a non-zero error code on error.
*/
always_inline int
map_ip6_to_ip4_tcp_udp (vlib_main_t * vm, vlib_buffer_t * p,
@ -370,6 +372,16 @@ map_ip6_to_ip4_tcp_udp (vlib_main_t * vm, vlib_buffer_t * p,
ip4->dst_address.as_u32 = vnet_buffer (p)->map_t.v6.daddr;
ip4->src_address.as_u32 = vnet_buffer (p)->map_t.v6.saddr;
/*
* Drop spoofed packets that from a known domain source.
*/
u32 map_domain_index = -1;
u8 error = 0;
ip4_map_get_domain (&ip4->src_address, &map_domain_index, &error);
if (error)
return error;
ip4->ip_version_and_header_length =
IP4_VERSION_AND_HEADER_LENGTH_NO_OPTIONS;
ip4->tos = ip6_translate_tos (ip6->ip_version_traffic_class_and_flow_label);

View File

@ -0,0 +1,265 @@
#!/usr/bin/env python3
import ipaddress
import unittest
from framework import VppTestCase, VppTestRunner
from vpp_ip import DpoProto
from vpp_ip_route import VppIpRoute, VppRoutePath
from util import fragment_rfc791, fragment_rfc8200
import scapy.compat
from scapy.layers.l2 import Ether
from scapy.packet import Raw
from scapy.layers.inet import IP, UDP, ICMP, TCP
from scapy.layers.inet6 import IPv6, ICMPv6TimeExceeded, IPv6ExtHdrFragment
from scapy.layers.inet6 import ICMPv6EchoRequest, ICMPv6EchoReply
class TestMAPBR(VppTestCase):
""" MAP-T Test Cases """
@classmethod
def setUpClass(cls):
super(TestMAPBR, cls).setUpClass()
@classmethod
def tearDownClass(cls):
super(TestMAPBR, cls).tearDownClass()
def setUp(self):
super(TestMAPBR, self).setUp()
#
# Create 2 pg interfaces.
# pg0 is IPv4
# pg1 is IPv6
#
self.create_pg_interfaces(range(2))
self.pg0.admin_up()
self.pg0.config_ip4()
self.pg1.generate_remote_hosts(20)
self.pg1.configure_ipv4_neighbors()
self.pg0.resolve_arp()
self.pg1.admin_up()
self.pg1.config_ip6()
self.pg1.generate_remote_hosts(20)
self.pg1.configure_ipv6_neighbors()
#
# BR configuration parameters used for all test.
#
self.ip4_prefix = '198.18.0.0/24'
self.ip6_prefix = '2001:db8:f0::/48'
self.ip6_src = '2001:db8:ffff:ff00::/64'
self.ea_bits_len = 12
self.psid_offset = 6
self.psid_length = 4
self.mtu = 1500
self.tag = 'MAP-T BR'
self.ipv4_internet_address = self.pg0.remote_ip4
self.ipv4_map_address = "198.18.0.12"
self.ipv4_udp_or_tcp_internet_port = 65000
self.ipv4_udp_or_tcp_map_port = 16606
self.ipv6_cpe_address = "2001:db8:f0:c30:0:c612:c:3" # 198.18.0.12
self.ipv6_spoof_address = "2001:db8:f0:c30:0:c612:1c:3" # 198.18.0.28
self.ipv6_spoof_prefix = "2001:db8:f0:c30:0:a00:c:3" # 10.0.0.12
self.ipv6_spoof_psid = "2001:db8:f0:c30:0:c612:c:4" # 4
self.ipv6_spoof_subnet = "2001:db8:f1:c30:0:c612:c:3" # f1
self.ipv6_udp_or_tcp_internet_port = 65000
self.ipv6_udp_or_tcp_map_port = 16606
self.ipv6_udp_or_tcp_spoof_port = 16862
self.ipv6_map_address = (
"2001:db8:ffff:ff00:ac:1001:200:0") # 176.16.1.2
self.ipv6_map_same_rule_diff_addr = (
"2001:db8:ffff:ff00:c6:1200:10:0") # 198.18.0.16
self.map_br_prefix = "2001:db8:f0::"
self.map_br_prefix_len = 48
self.psid_number = 3
#
# Add an IPv6 route to the MAP-BR.
#
map_route = VppIpRoute(self,
self.map_br_prefix,
self.map_br_prefix_len,
[VppRoutePath(self.pg1.remote_ip6,
self.pg1.sw_if_index)])
map_route.add_vpp_config()
ip4_map_route = VppIpRoute(self,
"198.18.0.0",
24,
[VppRoutePath(self.pg1.remote_ip4,
self.pg1.sw_if_index)])
ip4_map_route.add_vpp_config()
#
# Add a MAP BR domain that maps from pg0 to pg1.
#
self.vapi.map_add_domain(ip4_prefix=self.ip4_prefix,
ip6_prefix=self.ip6_prefix,
ip6_src=self.ip6_src,
ea_bits_len=self.ea_bits_len,
psid_offset=self.psid_offset,
psid_length=self.psid_length,
mtu=self.mtu,
tag=self.tag)
#
# Set BR parameters.
#
self.vapi.map_param_set_fragmentation(inner=1, ignore_df=0)
self.vapi.map_param_set_fragmentation(inner=0, ignore_df=0)
self.vapi.map_param_set_icmp(ip4_err_relay_src=self.pg0.local_ip4)
self.vapi.map_param_set_traffic_class(copy=1)
#
# Enable MAP-T on interfaces.
#
self.vapi.map_if_enable_disable(is_enable=1,
sw_if_index=self.pg0.sw_if_index,
is_translation=1)
self.vapi.map_if_enable_disable(is_enable=1,
sw_if_index=self.pg1.sw_if_index,
is_translation=1)
self.vapi.map_if_enable_disable(is_enable=1,
sw_if_index=self.pg1.sw_if_index,
is_translation=1)
def tearDown(self):
super(TestMAPBR, self).tearDown()
for i in self.pg_interfaces:
i.unconfig_ip4()
i.unconfig_ip6()
i.admin_down()
#
# Spoofed IPv4 Source Address v6 -> v4 direction
# Send a packet with a wrong IPv4 address embedded in bits 72-103.
# The BR should either drop the packet, or rewrite the spoofed
# source IPv4 as the actual source IPv4 address.
# The BR really should drop the packet.
#
def test_map_t_spoof_ipv4_src_addr_ip6_to_ip4(self):
""" MAP-T spoof ipv4 src addr IPv6 -> IPv4 """
eth = Ether(src=self.pg1.remote_mac,
dst=self.pg1.local_mac)
ip = IPv6(src=self.ipv6_spoof_address,
dst=self.ipv6_map_address)
udp = UDP(sport=self.ipv6_udp_or_tcp_map_port,
dport=self.ipv6_udp_or_tcp_internet_port)
payload = "a" * 82
tx_pkt = eth / ip / udp / payload
self.pg_send(self.pg1, tx_pkt * 1)
self.pg0.get_capture(0, timeout=1)
self.pg0.assert_nothing_captured("Should drop IPv4 spoof address")
#
# Spoofed IPv4 Source Prefix v6 -> v4 direction
# Send a packet with a wrong IPv4 prefix embedded in bits 72-103.
# The BR should either drop the packet, or rewrite the source IPv4
# to the prefix that matches the source IPv4 address.
#
def test_map_t_spoof_ipv4_src_prefix_ip6_to_ip4(self):
""" MAP-T spoof ipv4 src prefix IPv6 -> IPv4 """
eth = Ether(src=self.pg1.remote_mac,
dst=self.pg1.local_mac)
ip = IPv6(src=self.ipv6_spoof_prefix,
dst=self.ipv6_map_address)
udp = UDP(sport=self.ipv6_udp_or_tcp_map_port,
dport=self.ipv6_udp_or_tcp_internet_port)
payload = "a" * 82
tx_pkt = eth / ip / udp / payload
self.pg_send(self.pg1, tx_pkt * 1)
self.pg0.get_capture(0, timeout=1)
self.pg0.assert_nothing_captured("Should drop IPv4 spoof prefix")
#
# Spoofed IPv6 PSID v6 -> v4 direction
# Send a packet with a wrong IPv6 port PSID
# The BR should drop the packet.
#
def test_map_t_spoof_psid_ip6_to_ip4(self):
""" MAP-T spoof psid IPv6 -> IPv4 """
eth = Ether(src=self.pg1.remote_mac,
dst=self.pg1.local_mac)
ip = IPv6(src=self.ipv6_spoof_psid,
dst=self.ipv6_map_address)
udp = UDP(sport=self.ipv6_udp_or_tcp_map_port,
dport=self.ipv6_udp_or_tcp_internet_port)
payload = "a" * 82
tx_pkt = eth / ip / udp / payload
self.pg_send(self.pg1, tx_pkt * 1)
self.pg0.get_capture(0, timeout=1)
self.pg0.assert_nothing_captured("Should drop IPv6 spoof PSID")
#
# Spoofed IPv6 subnet field v6 -> v4 direction
# Send a packet with a wrong IPv6 subnet as "2001:db8:f1"
# The BR should drop the packet.
#
def test_map_t_spoof_subnet_ip6_to_ip4(self):
""" MAP-T spoof subnet IPv6 -> IPv4 """
eth = Ether(src=self.pg1.remote_mac,
dst=self.pg1.local_mac)
ip = IPv6(src=self.ipv6_spoof_subnet,
dst=self.ipv6_map_address)
udp = UDP(sport=self.ipv6_udp_or_tcp_map_port,
dport=self.ipv6_udp_or_tcp_internet_port)
payload = "a" * 82
tx_pkt = eth / ip / udp / payload
self.pg_send(self.pg1, tx_pkt * 1)
self.pg0.get_capture(0, timeout=1)
self.pg0.assert_nothing_captured("Should drop IPv6 spoof subnet")
#
# Spoofed IPv6 port PSID v6 -> v4 direction
# Send a packet with a wrong IPv6 port PSID
# The BR should drop the packet.
#
def test_map_t_spoof_port_psid_ip6_to_ip4(self):
""" MAP-T spoof port psid IPv6 -> IPv4 """
eth = Ether(src=self.pg1.remote_mac,
dst=self.pg1.local_mac)
ip = IPv6(src=self.ipv6_cpe_address,
dst=self.ipv6_map_address)
udp = UDP(sport=self.ipv6_udp_or_tcp_spoof_port,
dport=self.ipv6_udp_or_tcp_internet_port)
payload = "a" * 82
tx_pkt = eth / ip / udp / payload
self.pg_send(self.pg1, tx_pkt * 1)
self.pg0.get_capture(0, timeout=1)
self.pg0.assert_nothing_captured("Should drop IPv6 spoof port PSID")
if __name__ == '__main__':
unittest.main(testRunner=VppTestRunner)