From 8a9c8f1412cb1258340b18a8eb622a835ef3c37b Mon Sep 17 00:00:00 2001 From: Ole Troan Date: Fri, 18 May 2018 11:01:31 +0200 Subject: [PATCH] VPP-1283: IPv4 PMTU missing MTU value in ICMP4 message. Change-Id: I7a4133c59ff45b0744b48e246a049d9f015026fc Signed-off-by: Ole Troan --- src/plugins/igmp/igmp.c | 2 +- src/vnet/ip/icmp4.c | 6 +- src/vnet/ip/ip4_forward.c | 69 ++++++++------- test/test_ip4.py | 2 +- test/test_mtu.py | 212 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 257 insertions(+), 34 deletions(-) create mode 100644 test/test_mtu.py diff --git a/src/plugins/igmp/igmp.c b/src/plugins/igmp/igmp.c index 5ff88e58ec3..6499b5be466 100644 --- a/src/plugins/igmp/igmp.c +++ b/src/plugins/igmp/igmp.c @@ -712,7 +712,7 @@ igmp_timer_process (vlib_main_t * vm, vlib_node_runtime_t * rt, goto next_timer; IGMP_DBG ("time: %f", vlib_time_now (vm)); /* timer expired */ - if (NULL != timer) + if (NULL != timer && timer->func != NULL) timer->func (vm, rt, im, timer); next_timer: timer = igmp_get_next_timer (im); diff --git a/src/vnet/ip/icmp4.c b/src/vnet/ip/icmp4.c index 1fe01e48500..a4808f23271 100644 --- a/src/vnet/ip/icmp4.c +++ b/src/vnet/ip/icmp4.c @@ -513,13 +513,15 @@ ip4_icmp_error (vlib_main_t * vm, b->current_length = 0; } } - p0->current_length = - p0->current_length > 576 ? 576 : p0->current_length; /* Add IP header and ICMPv4 header including a 4 byte data field */ vlib_buffer_advance (p0, -sizeof (ip4_header_t) - sizeof (icmp46_header_t) - 4); + + p0->current_length = + p0->current_length > 576 ? 576 : p0->current_length; + out_ip0 = vlib_buffer_get_current (p0); icmp0 = (icmp46_header_t *) & out_ip0[1]; diff --git a/src/vnet/ip/ip4_forward.c b/src/vnet/ip/ip4_forward.c index ce2d619941c..40fe9dc447e 100644 --- a/src/vnet/ip/ip4_forward.c +++ b/src/vnet/ip/ip4_forward.c @@ -1963,6 +1963,29 @@ typedef enum #define IP4_MCAST_ADDR_MASK 0xffff7f00 #endif +always_inline void +ip4_mtu_check (vlib_buffer_t * b, u16 packet_len, + u16 adj_packet_bytes, bool df, u32 * next, u32 * error) +{ + if (packet_len > adj_packet_bytes) + { + *error = IP4_ERROR_MTU_EXCEEDED; + if (df) + { + icmp4_error_set_vnet_buffer + (b, ICMP4_destination_unreachable, + ICMP4_destination_unreachable_fragmentation_needed_and_dont_fragment_set, + adj_packet_bytes); + *next = IP4_REWRITE_NEXT_ICMP_ERROR; + } + else + { + /* Add support for fragmentation here */ + *next = IP4_REWRITE_NEXT_DROP; + } + } +} + always_inline uword ip4_rewrite_inline (vlib_main_t * vm, vlib_node_runtime_t * node, @@ -2123,26 +2146,16 @@ ip4_rewrite_inline (vlib_main_t * vm, vnet_buffer (p1)->ip.save_rewrite_length = rw_len1; /* Check MTU of outgoing interface. */ - if (vlib_buffer_length_in_chain (vm, p0) > - adj0[0].rewrite_header.max_l3_packet_bytes) - { - error0 = IP4_ERROR_MTU_EXCEEDED; - next0 = IP4_REWRITE_NEXT_ICMP_ERROR; - icmp4_error_set_vnet_buffer - (p0, ICMP4_destination_unreachable, - ICMP4_destination_unreachable_fragmentation_needed_and_dont_fragment_set, - 0); - } - if (vlib_buffer_length_in_chain (vm, p1) > - adj1[0].rewrite_header.max_l3_packet_bytes) - { - error1 = IP4_ERROR_MTU_EXCEEDED; - next1 = IP4_REWRITE_NEXT_ICMP_ERROR; - icmp4_error_set_vnet_buffer - (p1, ICMP4_destination_unreachable, - ICMP4_destination_unreachable_fragmentation_needed_and_dont_fragment_set, - 0); - } + ip4_mtu_check (p0, clib_net_to_host_u16 (ip0->length), + adj0[0].rewrite_header.max_l3_packet_bytes, + ip0->flags_and_fragment_offset & + clib_host_to_net_u16 (IP4_HEADER_FLAG_DONT_FRAGMENT), + &next0, &error0); + ip4_mtu_check (p1, clib_net_to_host_u16 (ip1->length), + adj1[0].rewrite_header.max_l3_packet_bytes, + ip1->flags_and_fragment_offset & + clib_host_to_net_u16 (IP4_HEADER_FLAG_DONT_FRAGMENT), + &next1, &error1); if (is_mcast) { @@ -2326,16 +2339,12 @@ ip4_rewrite_inline (vlib_main_t * vm, vlib_buffer_length_in_chain (vm, p0) + rw_len0); /* Check MTU of outgoing interface. */ - if (vlib_buffer_length_in_chain (vm, p0) > - adj0[0].rewrite_header.max_l3_packet_bytes) - { - error0 = IP4_ERROR_MTU_EXCEEDED; - next0 = IP4_REWRITE_NEXT_ICMP_ERROR; - icmp4_error_set_vnet_buffer - (p0, ICMP4_destination_unreachable, - ICMP4_destination_unreachable_fragmentation_needed_and_dont_fragment_set, - 0); - } + ip4_mtu_check (p0, clib_net_to_host_u16 (ip0->length), + adj0[0].rewrite_header.max_l3_packet_bytes, + ip0->flags_and_fragment_offset & + clib_host_to_net_u16 (IP4_HEADER_FLAG_DONT_FRAGMENT), + &next0, &error0); + if (is_mcast) { error0 = ((adj0[0].rewrite_header.sw_if_index == diff --git a/test/test_ip4.py b/test/test_ip4.py index 1a611251eb5..5b5cf09c86f 100644 --- a/test/test_ip4.py +++ b/test/test_ip4.py @@ -1294,7 +1294,7 @@ class TestIPInput(VppTestCase): dst=self.pg0.local_mac) / IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4, - ttl=10) / + ttl=10, flags='DF') / UDP(sport=1234, dport=1234) / Raw('\xa5' * 2000)) diff --git a/test/test_mtu.py b/test/test_mtu.py new file mode 100644 index 00000000000..abf0c94b268 --- /dev/null +++ b/test/test_mtu.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python +"""IP4 and IP6 MTU functional tests""" + +# +# Add tests for: +# - sub interfaces +# - Verify that adjacencies inherit MTU correctly +# - Verify that sub-interfaces inherit MTU correctly +# - Different types of interfaces? +# +import unittest +from scapy.layers.inet6 import IPv6, Ether, IP, UDP, ICMPv6PacketTooBig +from scapy.layers.inet import ICMP +from framework import VppTestCase, VppTestRunner +from vpp_ip_route import VppIpRoute, VppRoutePath, DpoProto +from socket import AF_INET, AF_INET6, inet_pton +import StringIO + +""" Test_mtu is a subclass of VPPTestCase classes. + MTU tests. +""" + + +def reassemble(listoffragments): + buffer = StringIO.StringIO() + first = listoffragments[0] + buffer.seek(20) + for pkt in listoffragments: + # pkt.show2() + buffer.seek(pkt[IP].frag*8) + buffer.write(pkt[IP].payload) + first.len = len(buffer.getvalue()) + 20 + first.flags = 0 + del(first.chksum) + header = str(first[IP])[:20] + return first[IP].__class__(header + buffer.getvalue()) + + +class TestMTU(VppTestCase): + """ MTU Test Case """ + + @classmethod + def setUpClass(cls): + super(TestMTU, cls).setUpClass() + cls.create_pg_interfaces(range(2)) + cls.interfaces = list(cls.pg_interfaces) + + def setUp(cls): + super(TestMTU, cls).setUp() + for i in cls.interfaces: + i.admin_up() + i.config_ip4() + i.config_ip6() + i.disable_ipv6_ra() + i.resolve_arp() + i.resolve_ndp() + + def tearDown(self): + super(TestMTU, self).tearDown() + if not self.vpp_dead: + for i in self.pg_interfaces: + i.unconfig_ip4() + i.unconfig_ip6() + i.admin_down() + + def validate(self, rx, expected): + self.assertEqual(rx, expected.__class__(str(expected))) + + def validate_bytes(self, rx, expected): + self.assertEqual(rx, expected) + + def payload(self, len): + return 'x' * len + + def get_mtu(self, sw_if_index): + rv = self.vapi.sw_interface_dump() + for i in rv: + if i.sw_if_index == sw_if_index: + return i.link_mtu + return 0 + + def test_ip4_mtu(self): + """ IP4 MTU test """ + + # + # TODO: Link MTU is 216 bytes 'off'. Fix when L3 MTU patches committed + # + mtu_offset = 216 + p_ether = Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + p_ip4 = IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4, + flags='DF') + + # TODO: Re-enable when MTU fixes are committed + current_mtu = self.get_mtu(self.pg1.sw_if_index) + current_mtu -= mtu_offset + + p_payload = UDP(sport=1234, dport=1234) / self.payload( + current_mtu - 20 - 8) + + p4 = p_ether / p_ip4 / p_payload + p4_reply = p_ip4 / p_payload + p4_reply.ttl -= 1 + rx = self.send_and_expect(self.pg0, p4*11, self.pg1) + for p in rx: + self.validate(p[1], p4_reply) + + # MTU + self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, 576 + mtu_offset) + self.assertEqual(576, self.get_mtu(self.pg1.sw_if_index) - mtu_offset) + + # Should fail. Too large MTU + p_icmp4 = ICMP(type='dest-unreach', code='fragmentation-needed', + nexthopmtu=576, chksum=0x2dbb) + icmp4_reply = (IP(src=self.pg0.local_ip4, + dst=self.pg0.remote_ip4, + ttl=254, len=576, id=0) / + p_icmp4 / p_ip4 / p_payload) + icmp4_reply[1].ttl -= 1 + n = icmp4_reply.__class__(str(icmp4_reply)) + s = str(icmp4_reply) + icmp4_reply = s[0:576] + rx = self.send_and_expect(self.pg0, p4*11, self.pg0) + for p in rx: + # p.show2() + # n.show2() + self.validate_bytes(str(p[1]), icmp4_reply) + + ''' + # Now with DF off. Expect fragments. + # First go with 1500 byte packets. + p_payload = UDP(sport=1234, dport=1234) / self.payload( + 1500 - 20 - 8) + p4 = p_ether / p_ip4 / p_payload + p4.flags = 0 + p4_reply = p_ip4 / p_payload + p4_reply.ttl = 62 # check this + p4_reply.flags = 0 + p4_reply.id = 256 + self.pg_enable_capture() + self.pg0.add_stream(p4*1) + self.pg_start() + rx = self.pg1.get_capture(3) + print('RX', len(rx)) + reass_pkt = reassemble(rx) + self.validate(reass_pkt, p4_reply) + ''' + # Now what happens with a 9K frame + ''' + p_payload = UDP(sport=1234, dport=1234) / self.payload( + current_mtu - 20 - 8) + p4 = p_ether / p_ip4 / p_payload + p4.flags = 0 + p4_reply = p_ip4 / p_payload + p4_reply.ttl = 62 # check this + p4_reply.flags = 0 + p4_reply.id = 512 + + self.pg_enable_capture() + self.pg0.add_stream(p4*1) + self.pg_start() + rx = self.pg1.get_capture(16) + reass_pkt = reassemble(rx) + reass_pkt.show2() + p4_reply.show2() + self.validate(reass_pkt, p4_reply) + ''' + # Reset MTU + self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, current_mtu) + + @unittest.skip("Enable when IPv6 fragmentation is added") + def test_ip6_mtu(self): + """ IP6 MTU test """ + + p_ether = Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) + p_ip6 = IPv6(src=self.pg0.remote_ip6, dst=self.pg1.remote_ip6) + + current_mtu = self.get_mtu(self.pg1.sw_if_index) + p_payload = UDP(sport=1234, dport=1234) / self.payload( + current_mtu - 40 - 8) + + p6 = p_ether / p_ip6 / p_payload + p6_reply = p_ip6 / p_payload + p6_reply.hlim -= 1 + rx = self.send_and_expect(self.pg0, p6*9, self.pg1) + for p in rx: + self.validate(p[1], p6_reply) + + # MTU (only checked on encap) + self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, 1280) + self.assertEqual(1280, self.get_mtu(self.pg1.sw_if_index)) + + # Should fail. Too large MTU + p_icmp6 = ICMPv6PacketTooBig(mtu=1280, cksum=0x4c7a) + icmp6_reply = (IPv6(src=self.pg0.local_ip6, + dst=self.pg0.remote_ip6, + hlim=254, plen=1240) / + p_icmp6 / p_ip6 / p_payload) + icmp6_reply[2].hlim -= 1 + n = icmp6_reply.__class__(str(icmp6_reply)) + s = str(icmp6_reply) + icmp6_reply = s[0:1280] + + rx = self.send_and_expect(self.pg0, p6*9, self.pg0) + for p in rx: + self.validate_bytes(str(p[1]), icmp6_reply) + + # Reset MTU + self.vapi.sw_interface_set_mtu(self.pg1.sw_if_index, current_mtu) + + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) -- 2.16.6