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

// Copyright (c) 2001-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/fea/test_rawsock4.cc,v 1.22 2007/02/16 22:45:51 pavlin Exp $"

#include "fea_module.h"

#include "libxorp/xorp.h"
#include "libxorp/xlog.h"
#include "libxorp/xorp.h"
#include "libxorp/ipvx.hh"

#include "libcomm/comm_api.h"

#include "libproto/checksum.h"

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_UIO_H
#include <sys/uio.h>
#endif
#ifdef HAVE_NETINET_IN_SYSTM_H
#include <netinet/in_systm.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_NETINET_IP_H
#include <netinet/ip.h>
#endif

#include <iostream>

#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef HAVE_SYSEXITS_H
#include <sysexits.h>
#endif
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif

/* Windows has no 'struct ip', so ship one. */
#ifdef HOST_OS_WINDOWS
#include "libxorp/win_io.h"
#include "ip.h"
#endif

#include "ifconfig.hh"
#include "nexthop_port_mapper.hh"
#include "rawsock4.hh"

/* ------------------------------------------------------------------------- */
/* Verbose output control */

static bool s_verbose = false;

inline bool verbose()           { return s_verbose; }
inline void set_verbose(bool v) { s_verbose = v; }

#define verbose_log(x...)                                                     \
do {                                                                          \
    if (verbose()) {                                                          \
        printf("From %s:%d: ", __FILE__, __LINE__);                           \
        printf(x);                                                            \
    }                                                                         \
} while(0)

/* ------------------------------------------------------------------------- */
/* IcmpEchoHeader */

struct IcmpEchoHeader {
    enum { TYPE_REQUEST = 8, TYPE_REPLY = 0 };

    uint8_t type() const { return _type; }
    uint8_t code() const { return _code; }
    uint16_t id() const { return ntohs(_id); }
    uint16_t seq() const { return ntohs(_seq); }
    uint16_t cksum() const { return _cksum; }

    IcmpEchoHeader(uint8_t ht, uint16_t id, uint16_t seq) :
	_type(ht), _code(0)
    {
	_id = htons(id);
	_seq = htons(seq);
	_cksum = 0;
	_cksum = inet_checksum(&_type, 8);
    }

    IcmpEchoHeader(const uint8_t* buf, size_t bufbytes)
    {
	XLOG_ASSERT(bufbytes >= 8);
	memcpy(this,  buf, 8);
    }

private:
    uint8_t  _type;
    uint8_t  _code;
    uint16_t _cksum;
    uint16_t _id;
    uint16_t _seq;
};

/* ------------------------------------------------------------------------- */
/*
 * Pinger.  Highly simplified "ping" class.
 *
 * This class is a child of FilterRawSocket4 and is able to send and parse
 * ICMP echo request and responses.  The class is clocked off a single
 * timeout timer.  When the timer expires it schedules the sending of
 * the next ICMP echo request.  If the last ICMP packet request has
 * not been answered when the timer expires the bad count is incremented.
 *
 * The pinger stop sending pings when the sum of the good and bad responses
 * equals the constructor argument "count".
 */
class Pinger : public FilterRawSocket4 {
public:
    Pinger(EventLoop& e, const IfTree& iftree, const IPv4& src,
	   const IPv4& dst, size_t count = 3)
	: FilterRawSocket4(e, IPPROTO_ICMP, iftree),
	  _iftree(iftree), _src(src), _dst(dst),
	  _sent(0), _good(0), _bad(0), _count(count)
    {
	_id = getpid();
	_seq = 0;
	_waiting = false;
	_timeout = e.new_oneoff_after_ms(0,
					 callback(this, &Pinger::timeout));
    }

    size_t good() const { return _good; }
    size_t bad() const  { return _bad; }
    size_t sent() const { return _sent; }
    size_t done() const { return _good + _bad == _count; }

protected:

    bool find_outgoing_interface(const IPvX& src, const IPvX& dst,
				 string& ifname, string& vifname) const {
	const IfTreeInterface* iftree_if = NULL;
	const IfTreeVif* iftree_vif = NULL;

	UNUSED(dst);
	ifname = "";
	vifname = "";

	if (find_interface_vif_by_addr(src, iftree_if, iftree_vif) != true)
	    return (false);

	// Interface found
	XLOG_ASSERT(iftree_if != NULL);
	XLOG_ASSERT(iftree_vif != NULL);
	ifname = iftree_if->ifname();
	vifname = iftree_vif->vifname();

	return (true);
    }

    bool send() {
	string ifname, vifname;

	XLOG_ASSERT(_waiting == false);
	if (_sent == _count) {
	    return false;
	}

	if (find_outgoing_interface(IPvX(_src), IPvX(_dst), ifname, vifname)
	    != true) {
	    fprintf(stderr, "Cannot find outgoing interface for destination "
		    "%s\n", _src.str().c_str());
	    exit(EXIT_FAILURE);
	}

	_timeout.schedule_after_ms(1000);
	_seq++;

	IcmpEchoHeader icmp_hdr(IcmpEchoHeader::TYPE_REQUEST, _id, _seq);

	vector<uint8_t> payload(sizeof(IcmpEchoHeader));
	memcpy(&payload[0], &icmp_hdr, sizeof(IcmpEchoHeader));

	verbose_log("Sending id %d seqno %d cksum 0x%04x\n",
		    icmp_hdr.id(), icmp_hdr.seq(), icmp_hdr.cksum());

	string error_msg;
	int32_t ip_ttl = 64;
	int32_t ip_tos = -1;
	if (proto_socket_write(ifname, vifname,
			       _src, _dst, ip_ttl, ip_tos,
			       false, payload, error_msg)
	    
	    != XORP_OK) {
	    fprintf(stderr, "error: %s\n", error_msg.c_str());
	    exit(EXIT_FAILURE);
	}

	_sent++;
	_waiting = true;
	return true;
    }

    void process_recv_data(const string&	if_name,
			   const string&	vif_name,
			   const IPvX&		src_address,
			   const IPvX&		dst_address,
			   int32_t		ip_ttl,
			   int32_t		ip_tos,
			   bool			ip_router_alert,
			   const vector<uint8_t>& ext_headers_type,
			   const vector<vector<uint8_t> >& ext_headers_payload,
			   const vector<uint8_t>& payload)
    {
	UNUSED(if_name);
	UNUSED(vif_name);
	UNUSED(src_address);
	UNUSED(dst_address);
	UNUSED(ip_ttl);
	UNUSED(ip_tos);
	UNUSED(ip_router_alert);
	UNUSED(ext_headers_type);
	UNUSED(ext_headers_payload);

	if (payload.size() < sizeof(IcmpEchoHeader)) {
	    verbose_log("Ignoring small ICMP packet (payload = %u bytes).\n",
			XORP_UINT_CAST(payload.size()));
	    return;
	}

	IcmpEchoHeader eh(&payload[0], payload.size());

	if (eh.id() != _id) {
	    verbose_log("Ignoring packet with id %d (!= %d)\n", eh.id(), _id);
	    return;
	}

	if (eh.seq() != _seq) {
	    verbose_log("Unexpected seqno %d (!= %d)\n", eh.seq(), _seq);
	    return;
	}

	if (eh.code() != 0) {
	    verbose_log("Unexpected code %d (!= %d)\n", eh.code(), 0);
	    return;
	}

	if (eh.type() == IcmpEchoHeader::TYPE_REQUEST) {
	    verbose_log("Ignoring my outbound ICMP packet\n");
	    return;
	} else if (eh.type() != IcmpEchoHeader::TYPE_REPLY) {
	    verbose_log("Ignoring ICMP packet type %d\n", eh.type());
	    return;
	}

	if (_waiting) {
	    printf("Sequence number %d received.\n", _seq);
	    _good++;
	    _waiting = false;
	}
	verbose_log("Receiving id %d, seqno %d\n", eh.id(), eh.seq());
    }

    void timeout() {
	if (_waiting) {
	    printf("Sequence number %d timed out.\n", _seq);
	    _bad++;
	}
	_waiting = false;
	send();
    }

protected:
    const IfTree&	_iftree;
    XorpTimer		_timeout;
    IPv4		_src;
    IPv4		_dst;
    uint16_t		_seq;
    uint16_t		_id;

    bool		_waiting; // waiting for packet

    size_t		_sent;
    size_t		_good;
    size_t		_bad;
    const size_t	_count;
};

/* ------------------------------------------------------------------------- */
/* Utility resolver function */

IPv4
lookup4(const char* addr)
{
    const struct hostent* he = gethostbyname(addr);
    if (he == 0 || he->h_length < 4) {
	fprintf(stderr, "gethostbyname failed: %s %d\n",
#ifdef HAVE_HSTRERROR
		hstrerror(h_errno),
#elif HOST_OS_WINDOWS
		win_strerror(WSAGetLastError()),
#else
		"",
#endif
#ifdef HOST_OS_WINDOWS
		WSAGetLastError()
#else
		h_errno
#endif
	);
	exit(EXIT_FAILURE);
    }
    struct in_addr ia;
    memcpy(&ia, he->h_addr_list[0], sizeof(ia));
    return IPv4(ia.s_addr);
}

/* ------------------------------------------------------------------------- */

#ifndef EX_USAGE
#define EX_USAGE 1
#endif

#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif

static void
usage()
{
    fprintf(stderr, "usage: test_rawsock [-v] [-c count] [dst [src]]\n");
    fprintf(stderr,
	    "pings dst from address src.  Addresses default to localhost\n"
	    );
    exit(EX_USAGE);
}

/* ------------------------------------------------------------------------- */

int
main(int argc, char* const* argv)
{
#ifndef HOST_OS_WINDOWS
    //
    // Root test, can't run if not root.
    //
    if (geteuid() != 0) {
	fprintf(stderr, "%s\n*\n", string(79, '*').c_str());
	fprintf(stderr, "* This program needs root privileges.\n");
	fprintf(stderr, "* Returning success even though not run.\n");
	fprintf(stderr, "*\n%s\n", string(79, '*').c_str());
	// Return 0 because if part of 'make check' we don't want this
	// to count as a failure.
	return EXIT_SUCCESS;
    }
#endif

    comm_init();

    //
    // Initialize and start xlog
    //
    xlog_init(argv[0], NULL);
    xlog_set_verbose(XLOG_VERBOSE_LOW);         // Least verbose messages
    // XXX: verbosity of the error messages temporary increased
    xlog_level_set_verbose(XLOG_LEVEL_ERROR, XLOG_VERBOSE_HIGH);
    xlog_add_default_output();
    xlog_start();

    int count = 3;

    int ch;
    while ((ch = getopt(argc, argv, "c:hv")) != -1) {
	switch (ch) {
	case 'c':
	    count = atoi(optarg);
	    break;
	case 'v':
	    set_verbose(true);
	    break;
	default:
	    usage();
	    break;
	}
    }
    argc -= optind;
    argv += optind;

    if (argc > 2) {
	usage();
    }

    if (count <= 0) count = 3;

    IPv4 dst, src;
    switch (argc) {
    case 0:
	src = IPv4::LOOPBACK();
	dst = IPv4::LOOPBACK();
	break;
    case 1:
	{
	    char hostname[MAXHOSTNAMELEN];
	    if (gethostname(hostname, sizeof(hostname))) {
		fprintf(stderr, "gethostname() failed: %s", strerror(errno));
		exit(EXIT_FAILURE);
	    }
	    hostname[sizeof(hostname) - 1] = '\0';
	    src = lookup4(hostname);
	    dst = lookup4(argv[0]);
	}
	break;
    case 2:
	src = lookup4(argv[1]);
	dst = lookup4(argv[0]);
	break;
    }

    printf("Running \"ping\" from %s to %s\n",
	   src.str().c_str(), dst.str().c_str());

    try {
	EventLoop eventloop;
	string error_msg;

	//
	// Create the interface tree
	//
	NexthopPortMapper nexthop_port_mapper;
	IfConfigUpdateReplicator ifc_repl;
	IfConfigErrorReporter if_err;

	IfConfig ifconfig(eventloop, ifc_repl, if_err, nexthop_port_mapper);
	if (ifconfig.start(error_msg) != XORP_OK) {
	    XLOG_FATAL("Cannot start IfConfig: %s", error_msg.c_str());
	}
	const IfTree& iftree = ifconfig.pull_config();

	Pinger pinger(eventloop, iftree, src, dst, count);
	if (pinger.start(error_msg) != XORP_OK) {
	    fprintf(stderr, "Cannot start pinger: %s", error_msg.c_str());
	    exit(EXIT_FAILURE);
	}
	
	while (pinger.done() == false)
	    eventloop.run();

	printf("Received %u good responses and %u bad responses.\n",
	       XORP_UINT_CAST(pinger.good()), XORP_UINT_CAST(pinger.bad()));
	if (pinger.good() == 0) {
	    exit(EXIT_FAILURE);
	}
    } catch (...) {
	xorp_catch_standard_exceptions();
    }

    //
    // Gracefully stop and exit xlog
    //
    xlog_stop();
    xlog_exit();

    comm_exit();

    return EXIT_SUCCESS;
}


syntax highlighted by Code2HTML, v. 0.9.1