// -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*-

// Copyright (c) 2006-2007 International Computer Science Institute
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software")
// to deal in the Software without restriction, subject to the conditions
// listed in the XORP LICENSE file. These conditions include: you must
// preserve this copyright notice, and you cannot mention the copyright
// holders in advertising related to the Software without their permission.
// The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
// notice is a summary of the XORP LICENSE file; the license in that file is
// legally binding.

#ident "$XORP: xorp/libproto/packet.cc,v 1.4 2007/02/16 22:46:02 pavlin Exp $"


//
// Packet related manipulation tools
//

#include "libproto_module.h"
#include "libxorp/xorp.h"
#include "libxorp/xlog.h"

#include "checksum.h"
#include "packet.hh"


int
IpHeader4::fragment(size_t mtu, list<vector<uint8_t> >& fragments,
		    bool do_checksum, string& error_msg) const
{
    const IpHeader4& orig_ip4 = *this;
    size_t datalen = orig_ip4.ip_len();

    //
    // If the data packet is small enough, then don't fragment it
    //
    if (datalen <= mtu) {
	return (XORP_OK);
    }

    //
    // Fragment the inner packet, then encapsulate and send each fragment
    //
    vector<uint8_t> frag_buf(datalen);	// The buffer for the fragments
    IpHeader4Writer frag_ip4(&frag_buf[0]);
    size_t frag_optlen = 0;		// The optlen to copy into fragments
    size_t frag_ip_hl;

    if (orig_ip4.ip_off() & IpHeader4::FRAGMENT_FLAGS_IP_DF) {
	// Fragmentation is forbidded
	error_msg = c_format("Cannot fragment encapsulated IP packet "
			     "from %s to %s: "
			     "fragmentation not allowed",
			     cstring(orig_ip4.ip_src()),
			     cstring(orig_ip4.ip_dst()));
	XLOG_WARNING("%s", error_msg.c_str());
	return (XORP_ERROR);
    }
    if (((mtu - (orig_ip4.ip_header_len())) & ~7) < 8) {
	//
	// Fragmentation is possible only if we can put at least
	// 8 octets per fragment (except the last one).
	//
	error_msg = c_format("Cannot fragment encapsulated IP packet "
			     "from %s to %s: "
			     "cannot send fragment with size less than 8 octets",
			     cstring(orig_ip4.ip_src()),
			     cstring(orig_ip4.ip_dst()));
	XLOG_WARNING("%s", error_msg.c_str());
	return (XORP_ERROR);
    }

    //
    // Copy the IP header and the options that should be copied during
    // fragmentation.
    // XXX: the code below is taken from FreeBSD's ip_optcopy()
    // in netinet/ip_output.c
    //
    memcpy(&frag_buf[0], _data, IpHeader4::SIZE);
    {
	register const u_char *cp;
	register u_char *dp;
	int opt, optlen, cnt;

	cp = (const u_char *)(orig_ip4.data() + orig_ip4.size());
	dp = (u_char *)(frag_ip4.data() + frag_ip4.size());
	cnt = orig_ip4.ip_header_len() - IpHeader4::SIZE;
	for (; cnt > 0; cnt -= optlen, cp += optlen) {
	    opt = cp[0];
	    if (opt == IpHeader4::OPTIONS_IPOPT_EOL)
		break;
	    if (opt == IpHeader4::OPTIONS_IPOPT_NOP) {
		// Preserve for IP mcast tunnel's LSRR alignment.
		*dp++ = IpHeader4::OPTIONS_IPOPT_NOP;
		optlen = 1;
		continue;
	    }

	    //
	    // Check for bogus lengths
	    //
	    if ((size_t)cnt < IpHeader4::OPTIONS_IPOPT_OLEN + sizeof(*cp)) {
		error_msg = c_format("Cannot fragment encapsulated IP "
				     "packet from %s to %s: "
				     "malformed IPv4 option",
				     cstring(orig_ip4.ip_src()),
				     cstring(orig_ip4.ip_dst()));
		XLOG_WARNING("%s", error_msg.c_str());
		return (XORP_ERROR);
	    }
	    optlen = cp[IpHeader4::OPTIONS_IPOPT_OLEN];
	    if (((size_t)optlen < IpHeader4::OPTIONS_IPOPT_OLEN + sizeof(*cp))
		|| (optlen > cnt)) {
		error_msg = c_format("Cannot fragment encapsulated IP "
				     "packet from %s to %s: "
				     "malformed IPv4 option",
				     cstring(orig_ip4.ip_src()),
				     cstring(orig_ip4.ip_dst()));
		XLOG_WARNING("%s", error_msg.c_str());
		return (XORP_ERROR);
	    }

	    if (optlen > cnt)
		optlen = cnt;
	    if (IpHeader4::OPTIONS_COPIED_FLAG & opt) {
		memcpy(dp, cp, optlen);
		dp += optlen;
	    }
	}
	for (optlen = dp - (u_char *)(frag_ip4.data() + frag_ip4.size());
	     optlen & 0x3; optlen++) {
	    *dp++ = IpHeader4::OPTIONS_IPOPT_EOL;
	}
	frag_optlen = optlen;	// return (optlen);
    }
    // The header length with the remaining IP options
    frag_ip_hl = IpHeader4::SIZE + frag_optlen;
    frag_ip4.set_ip_header_len(frag_ip_hl);

    //
    // Do the fragmentation
    //
    size_t data_start = 0;
    size_t data_end = datalen;
    // Send the first segment with all the options
    {
	vector<uint8_t> first_frag(mtu);
	IpHeader4Writer first_ip(&first_frag[0]);
	size_t first_ip_hl = orig_ip4.ip_header_len();
	size_t nfb = (mtu - first_ip_hl) / 8;
	size_t first_frag_len = first_ip_hl + nfb*8;

	first_frag.resize(first_frag_len);
	memcpy(&first_frag[0], _data, first_frag_len);

	// Correct the IP header
	first_ip.set_ip_off(first_ip.ip_off() | IpHeader4::FRAGMENT_FLAGS_IP_MF);
	first_ip.set_ip_len(first_frag_len);
	first_ip.set_ip_sum(0);
	if (do_checksum) {
	    first_ip.set_ip_sum(ntohs(inet_checksum(first_ip.data(),
						    first_ip_hl)));
	}

	// Add the first fragment
	fragments.push_back(first_frag);

	data_start += first_frag_len;
    }
    
    //
    // Create the remaining of the fragments
    //
    while (data_start < data_end) {
	size_t nfb = (mtu - frag_ip_hl) / 8;
	size_t frag_len = frag_ip_hl + nfb*8;
	size_t frag_data_len = nfb*8;
	bool is_last_fragment = false;

	// Compute the fragment length
	if (data_end - data_start <= frag_data_len) {
	    frag_data_len = data_end - data_start;
	    frag_len = frag_ip_hl + frag_data_len;
	    is_last_fragment = true;
	}

	// Copy the data
	frag_buf.resize(frag_len);
	memcpy(&frag_buf[0] + frag_ip_hl, _data + data_start, frag_data_len);

	// The IP packet total length
	frag_ip4.set_ip_len(frag_len);

	// The IP fragment flags and offset
	{
	    unsigned short ip_off_field = orig_ip4.ip_off();
	    unsigned short frag_ip_off_flags = ip_off_field & ~IpHeader4::FRAGMENT_OFFSET_MASK;
	    unsigned short frag_ip_off = (ip_off_field & IpHeader4::FRAGMENT_OFFSET_MASK);

	    if (! is_last_fragment)
		frag_ip_off_flags |= IpHeader4::FRAGMENT_FLAGS_IP_MF;
	    
	    frag_ip_off += (data_start - orig_ip4.ip_header_len()) / 8;	// XXX
	    frag_ip4.set_ip_off(frag_ip_off_flags | frag_ip_off);
	}

	frag_ip4.set_ip_sum(0);
	if (do_checksum) {
	    frag_ip4.set_ip_sum(ntohs(inet_checksum(frag_ip4.data(),
						    frag_ip_hl)));
	}

	// Add the fragment
	fragments.push_back(frag_buf);

	data_start += frag_data_len;
    }

    return (XORP_OK);
}


syntax highlighted by Code2HTML, v. 0.9.1