IP fragmentation to handle buffer chains.

Change-Id: Iff557f566ebc9ab170d75da1233997d83b8c8a66
Signed-off-by: Ole Troan <ot@cisco.com>
This commit is contained in:
Ole Troan
2018-08-08 22:23:19 +02:00
committed by Dave Barach
parent 3074629b25
commit 4146c65f0d
4 changed files with 169 additions and 82 deletions

View File

@ -46,33 +46,41 @@ format_ip_frag_trace (u8 * s, va_list * args)
static u32 running_fragment_id;
/*
* Limitation: Does follow buffer chains in the packet to fragment,
* but does not generate buffer chains. I.e. a fragment is always
* contained with in a single buffer and limited to the max buffer
* size.
*/
void
ip4_frag_do_fragment (vlib_main_t * vm, u32 pi, u32 ** buffer,
ip4_frag_do_fragment (vlib_main_t * vm, u32 from_bi, u32 ** buffer,
ip_frag_error_t * error)
{
vlib_buffer_t *p;
vlib_buffer_t *from_b;
ip4_header_t *ip4;
u16 mtu, ptr, len, max, rem, offset, ip_frag_id, ip_frag_offset;
u8 *packet, more;
u16 mtu, len, max, rem, offset, ip_frag_id, ip_frag_offset;
u8 *org_from_packet, more;
vec_add1 (*buffer, pi);
p = vlib_get_buffer (vm, pi);
offset = vnet_buffer (p)->ip_frag.header_offset;
mtu = vnet_buffer (p)->ip_frag.mtu;
packet = (u8 *) vlib_buffer_get_current (p);
ip4 = (ip4_header_t *) (packet + offset);
from_b = vlib_get_buffer (vm, from_bi);
offset = vnet_buffer (from_b)->ip_frag.header_offset;
mtu = vnet_buffer (from_b)->ip_frag.mtu;
org_from_packet = vlib_buffer_get_current (from_b);
ip4 = (ip4_header_t *) vlib_buffer_get_current (from_b) + offset;
rem = clib_net_to_host_u16 (ip4->length) - sizeof (*ip4);
ptr = 0;
max = (mtu - sizeof (*ip4) - vnet_buffer (p)->ip_frag.header_offset) & ~0x7;
rem = clib_net_to_host_u16 (ip4->length) - sizeof (ip4_header_t);
max =
(mtu - sizeof (ip4_header_t) -
vnet_buffer (from_b)->ip_frag.header_offset) & ~0x7;
if (rem > (p->current_length - offset - sizeof (*ip4)))
if (rem >
(vlib_buffer_length_in_chain (vm, from_b) - offset -
sizeof (ip4_header_t)))
{
*error = IP_FRAG_ERROR_MALFORMED;
return;
}
if (mtu < sizeof (*ip4))
if (mtu < sizeof (ip4_header_t))
{
*error = IP_FRAG_ERROR_CANT_FRAGMENT_HEADER;
return;
@ -85,12 +93,6 @@ ip4_frag_do_fragment (vlib_main_t * vm, u32 pi, u32 ** buffer,
return;
}
if (p->flags & VLIB_BUFFER_NEXT_PRESENT)
{
*error = IP_FRAG_ERROR_MALFORMED;
return;
}
if (ip4_is_fragment (ip4))
{
ip_frag_id = ip4->fragment_id;
@ -106,84 +108,109 @@ ip4_frag_do_fragment (vlib_main_t * vm, u32 pi, u32 ** buffer,
more = 0;
}
//Do the actual fragmentation
u8 *from_data = (void *) (ip4 + 1);
vlib_buffer_t *org_from_b = from_b;
u16 ptr = 0, fo = 0;
u16 left_in_from_buffer =
from_b->current_length - offset - sizeof (ip4_header_t);
/* Do the actual fragmentation */
while (rem)
{
u32 bi;
vlib_buffer_t *b;
ip4_header_t *fip4;
u32 to_bi;
vlib_buffer_t *to_b;
ip4_header_t *to_ip4;
u8 *to_data;
len =
(rem >
(mtu - sizeof (*ip4) -
vnet_buffer (p)->ip_frag.header_offset)) ? max : rem;
if (ptr == 0)
len = (rem > (mtu - sizeof (ip4_header_t) - offset) ? max : rem);
if (len != rem) /* Last fragment does not need to divisible by 8 */
len &= ~0x7;
if (!vlib_buffer_alloc (vm, &to_bi, 1))
{
bi = pi;
b = p;
fip4 = (ip4_header_t *) (vlib_buffer_get_current (b) + offset);
*error = IP_FRAG_ERROR_MEMORY;
/* XXX: Free already allocated buffers? */
return;
}
vec_add1 (*buffer, to_bi);
to_b = vlib_get_buffer (vm, to_bi);
vnet_buffer (to_b)->sw_if_index[VLIB_RX] =
vnet_buffer (org_from_b)->sw_if_index[VLIB_RX];
vnet_buffer (to_b)->sw_if_index[VLIB_TX] =
vnet_buffer (org_from_b)->sw_if_index[VLIB_TX];
/* Copy adj_index in case DPO based node is sending for the
* fragmentation, the packet would be sent back to the proper
* DPO next node and Index
*/
vnet_buffer (to_b)->ip.adj_index[VLIB_RX] =
vnet_buffer (org_from_b)->ip.adj_index[VLIB_RX];
vnet_buffer (to_b)->ip.adj_index[VLIB_TX] =
vnet_buffer (org_from_b)->ip.adj_index[VLIB_TX];
/* Copy offset and ip4 header */
clib_memcpy (to_b->data, org_from_packet,
offset + sizeof (ip4_header_t));
to_ip4 = vlib_buffer_get_current (to_b) + offset;
to_data = (void *) (to_ip4 + 1);
/* Spin through buffer chain copying data */
// XXX: Make sure we don't overflow source buffer!!!
if (len > left_in_from_buffer)
{
clib_memcpy (to_data, from_data + ptr, left_in_from_buffer);
/* Move buffer */
if (!(from_b->flags & VLIB_BUFFER_NEXT_PRESENT))
{
*error = IP_FRAG_ERROR_MALFORMED;
return;
}
from_b = vlib_get_buffer (vm, from_b->next_buffer);
from_data = (u8 *) vlib_buffer_get_current (from_b);
clib_memcpy (to_data + left_in_from_buffer, from_data,
len - left_in_from_buffer);
ptr = len - left_in_from_buffer;
left_in_from_buffer =
from_b->current_length - (len - left_in_from_buffer);
}
else
{
if (!vlib_buffer_alloc (vm, &bi, 1))
{
*error = IP_FRAG_ERROR_MEMORY;
return;
}
vec_add1 (*buffer, bi);
b = vlib_get_buffer (vm, bi);
vnet_buffer (b)->sw_if_index[VLIB_RX] =
vnet_buffer (p)->sw_if_index[VLIB_RX];
vnet_buffer (b)->sw_if_index[VLIB_TX] =
vnet_buffer (p)->sw_if_index[VLIB_TX];
/* Copy Adj_index in case DPO based node is sending for the fragmentation,
the packet would be sent back to the proper DPO next node and Index */
vnet_buffer (b)->ip.adj_index[VLIB_RX] =
vnet_buffer (p)->ip.adj_index[VLIB_RX];
vnet_buffer (b)->ip.adj_index[VLIB_TX] =
vnet_buffer (p)->ip.adj_index[VLIB_TX];
fip4 = (ip4_header_t *) (vlib_buffer_get_current (b) + offset);
//Copy offset and ip4 header
clib_memcpy (b->data, packet, offset + sizeof (*ip4));
//Copy data
clib_memcpy (((u8 *) (fip4)) + sizeof (*fip4),
packet + offset + sizeof (*fip4) + ptr, len);
clib_memcpy (to_data, from_data + ptr, len);
left_in_from_buffer -= len;
ptr += len;
}
b->current_length = offset + len + sizeof (*fip4);
to_b->current_length = offset + len + sizeof (ip4_header_t);
fip4->fragment_id = ip_frag_id;
fip4->flags_and_fragment_offset =
clib_host_to_net_u16 ((ptr >> 3) + ip_frag_offset);
fip4->flags_and_fragment_offset |=
to_ip4->fragment_id = ip_frag_id;
to_ip4->flags_and_fragment_offset =
clib_host_to_net_u16 ((fo >> 3) + ip_frag_offset);
to_ip4->flags_and_fragment_offset |=
clib_host_to_net_u16 (((len != rem) || more) << 13);
// ((len0 != rem0) || more0) << 13 is optimization for
// ((len0 != rem0) || more0) ? IP4_HEADER_FLAG_MORE_FRAGMENTS : 0
fip4->length = clib_host_to_net_u16 (len + sizeof (*fip4));
fip4->checksum = ip4_header_checksum (fip4);
to_ip4->length = clib_host_to_net_u16 (len + sizeof (ip4_header_t));
to_ip4->checksum = ip4_header_checksum (to_ip4);
if (vnet_buffer (p)->ip_frag.flags & IP_FRAG_FLAG_IP4_HEADER)
if (vnet_buffer (org_from_b)->ip_frag.flags & IP_FRAG_FLAG_IP4_HEADER)
{
//Encapsulating ipv4 header
/* Encapsulating ipv4 header */
ip4_header_t *encap_header4 =
(ip4_header_t *) vlib_buffer_get_current (b);
encap_header4->length = clib_host_to_net_u16 (b->current_length);
(ip4_header_t *) vlib_buffer_get_current (to_b);
encap_header4->length = clib_host_to_net_u16 (to_b->current_length);
encap_header4->checksum = ip4_header_checksum (encap_header4);
}
else if (vnet_buffer (p)->ip_frag.flags & IP_FRAG_FLAG_IP6_HEADER)
else if (vnet_buffer (org_from_b)->
ip_frag.flags & IP_FRAG_FLAG_IP6_HEADER)
{
//Encapsulating ipv6 header
/* Encapsulating ipv6 header */
ip6_header_t *encap_header6 =
(ip6_header_t *) vlib_buffer_get_current (b);
(ip6_header_t *) vlib_buffer_get_current (to_b);
encap_header6->payload_length =
clib_host_to_net_u16 (b->current_length -
clib_host_to_net_u16 (to_b->current_length -
sizeof (*encap_header6));
}
rem -= len;
ptr += len;
fo += len;
}
/* Free original packet chain */
vlib_buffer_free_one (vm, from_bi);
}
void

View File

@ -26,11 +26,12 @@
extern vnet_hw_interface_class_t ipip_hw_interface_class;
#define foreach_ipip_error \
/* Must be first. */ \
_(DECAP_PKTS, "packets decapsulated") \
_(BAD_PROTOCOL, "bad protocol") \
_(NO_TUNNEL, "no tunnel")
#define foreach_ipip_error \
/* Must be first. */ \
_(DECAP_PKTS, "packets decapsulated") \
_(BAD_PROTOCOL, "bad protocol") \
_(NO_TUNNEL, "no tunnel") \
_(FRAGMENTED_PACKET, "fragmented outer packet")
typedef enum
{

View File

@ -108,6 +108,14 @@ ipip_input (vlib_main_t * vm, vlib_node_runtime_t * node,
else
{
ip40 = vlib_buffer_get_current (b0);
/* Check for outer fragmentation */
if (ip40->flags_and_fragment_offset &
clib_host_to_net_u16 (IP4_HEADER_FLAG_MORE_FRAGMENTS))
{
next0 = IPIP_INPUT_NEXT_DROP;
b0->error = node->errors[IPIP_ERROR_FRAGMENTED_PACKET];
goto drop;
}
vlib_buffer_advance (b0, sizeof (*ip40));
ip_set (&src0, &ip40->src_address, true);
ip_set (&dst0, &ip40->dst_address, true);

View File

@ -3,9 +3,11 @@
import unittest
from scapy.layers.inet6 import IPv6, Ether, IP, UDP
from scapy.all import fragment
from framework import VppTestCase, VppTestRunner
from vpp_ip_route import VppIpRoute, VppRoutePath, DpoProto
from socket import AF_INET, AF_INET6, inet_pton
import StringIO
""" Testipip is a subclass of VPPTestCase classes.
@ -14,6 +16,20 @@ IPIP tests.
"""
def reassemble(listoffragments):
buffer = StringIO.StringIO()
first = listoffragments[0]
buffer.seek(20)
for pkt in listoffragments:
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 TestIPIP(VppTestCase):
""" IPIP Test Case """
@ -126,6 +142,38 @@ class TestIPIP(VppTestCase):
for p in rx:
self.validate(p[1], p6_reply)
# Fragmentation / Reassembly and Re-fragmentation
rv = self.vapi.ip_reassembly_enable_disable(
sw_if_index=self.pg1.sw_if_index,
enable_ip4=1)
# Decapsulation
p_ether = Ether(src=self.pg1.remote_mac, dst=self.pg1.local_mac)
p_payload = UDP(sport=1234, dport=1234) / self.payload(3123)
p_ip4 = IP(src="1.2.3.4", dst=self.pg0.remote_ip4)
outer_ip4 = (p_ether / IP(src=self.pg1.remote_ip4,
dst=self.pg0.local_ip4) / p_ip4 / p_payload)
frags = fragment(outer_ip4, 1400)
p4_reply = (p_ip4 / p_payload)
p4_reply.ttl -= 1
self.pg_enable_capture()
self.pg1.add_stream(frags)
self.pg_start()
rx = self.pg0.get_capture(1)
for p in rx:
self.validate(p[1], p4_reply)
# Now try with re-fragmentation
self.vapi.sw_interface_set_mtu(self.pg0.sw_if_index, [576, 0, 0, 0])
self.pg_enable_capture()
self.pg1.add_stream(frags)
self.pg_start()
rx = self.pg0.get_capture(6)
reass_pkt = reassemble(rx)
p4_reply.ttl -= 1
p4_reply.id = 256
self.validate(reass_pkt, p4_reply)
def test_ipip6(self):
""" ip{v4,v6} over ip6 test """
p_ether = Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac)
@ -213,6 +261,9 @@ class TestIPIP(VppTestCase):
sw_if_index = rv.sw_if_index
self.vapi.ipip_del_tunnel(sw_if_index)
def payload(self, len):
return 'x' * len
if __name__ == '__main__':
unittest.main(testRunner=VppTestRunner)