// -*- 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/rip/test_outputs.cc,v 1.29 2007/02/16 22:47:16 pavlin Exp $"
#include <set>
#include "rip_module.h"
#include "libxorp/xlog.h"
#include "libxorp/c_format.hh"
#include "libxorp/eventloop.hh"
#include "libxorp/ipv4.hh"
#include "libxorp/ipv6.hh"
#include "libxorp/ipnet.hh"
#include "auth.hh"
#include "output_table.hh"
#include "output_updates.hh"
#include "port.hh"
#include "peer.hh"
#include "packet_queue.hh"
#include "route_db.hh"
#include "system.hh"
#include "update_queue.hh"
#include "test_utils.hh"
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
///////////////////////////////////////////////////////////////////////////////
//
// This test comprises of a RIP system with 2 ports injecting routes. One of
// the ports has an associated Output class instance that generates packets
// containing the routes. We test both the update packets and the unsolicted
// response packets.
//
// +--- System ----+
// TestPort OtherPort
// Routes ------> TestPeer OtherPeer <------ Routes
// |
// V
// response packets (inspected)
//
// We look at the routes in the response packets and compare them against
// what we'd expect to see against differing horizon policies.
//
///////////////////////////////////////////////////////////////////////////////
//
// Generic Constants
//
static const char *program_name = "test_output_update";
static const char *program_description = "Test RIP Port processing";
static const char *program_version_id = "0.1";
static const char *program_date = "August, 2003";
static const char *program_copyright = "See file LICENSE.XORP";
static const char *program_return_value = "0 on success, 1 if test error, "
"2 if internal error";
// ----------------------------------------------------------------------------
// Type specific helpers
template <typename A>
struct DefaultPeer {
static A get();
};
template <typename A>
struct OtherPeer {
static A get();
};
template <>
IPv4 DefaultPeer<IPv4>::get() { return IPv4("10.0.0.1"); }
template <>
IPv4 OtherPeer<IPv4>::get() { return IPv4("192.168.0.1"); }
template <>
IPv6 DefaultPeer<IPv6>::get() { return IPv6("10::1"); }
template <>
IPv6 OtherPeer<IPv6>::get() { return IPv6("1920:1680::1"); }
// ----------------------------------------------------------------------------
// Spoof Port that supports just a single Peer
//
template <typename A>
class SpoofPort : public Port<A> {
public:
SpoofPort(PortManagerBase<A>& pm, A addr) : Port<A>(pm)
{
this->_peers.push_back(new Peer<A>(*this, addr));
// verbose_log("Constructing SpoofPort instance\n");
}
~SpoofPort()
{
// verbose_log("Destructing SpoofPort instance\n");
while (this->_peers.empty() == false) {
delete this->_peers.front();
this->_peers.pop_front();
}
}
};
// ----------------------------------------------------------------------------
// BlockedPortIO
//
// This is a port IO class that reports it's always pending so packets
// build up in the packet queue and never leave...
template <typename A>
class BlockedPortIO : public PortIOBase<A>
{
public:
typedef A Addr;
typedef PortIOUserBase<A> PortIOUser;
public:
BlockedPortIO(PortIOUserBase<A>& user)
: PortIOBase<A>(user, "if0", "vif0", A(DefaultPeer<A>::get()))
{
}
/**
* Called by RIP Port instance.
*/
bool send(const Addr&,
uint16_t,
const vector<uint8_t>&)
{
XLOG_FATAL("Called send inappropriately");
return true;
}
bool pending() const
{
return true;
}
private:
};
// ----------------------------------------------------------------------------
// Spoof Port Manager instance support a single Spoof Port which in turn
// contains a single Peer.
//
template <typename A>
class SpoofPortManager : public PortManagerBase<A> {
public:
SpoofPortManager(System<A>& s, const IfMgrIfTree& iftree)
: PortManagerBase<A>(s, iftree)
{
this->_ports.push_back(new SpoofPort<A>(*this, DefaultPeer<A>::get()));
this->_ports.push_back(new SpoofPort<A>(*this, OtherPeer<A>::get()));
}
~SpoofPortManager()
{
while (!this->_ports.empty()) {
delete this->_ports.front();
this->_ports.pop_front();
}
}
Port<A>* test_port()
{
return this->_ports.front();
}
const Port<A>* test_port() const
{
return this->_ports.front();
}
Port<A>* other_port()
{
return this->_ports.back();
}
const Port<A>* other_port() const
{
return this->_ports.back();
}
Peer<A>* test_peer()
{
return test_port()->peers().front();
}
const Peer<A>* test_peer() const
{
return test_port()->peers().front();
}
Peer<A>* other_peer()
{
return other_port()->peers().front();
}
const Peer<A>* other_peer() const
{
return other_port()->peers().front();
}
};
// ----------------------------------------------------------------------------
// Response Reader - Really needed for IPv6, but IPv4 is trivial
template <typename A>
struct ResponseReader {
ResponseReader(const RipPacket<A>* rp) : _rp(rp), _pe(0)
{
RipPacketHeader rph(_rp->header_ptr());
if (rph.command() != RipPacketHeader::RESPONSE) {
verbose_log("Bad packet!\n");
_pe = ~0U;
}
}
bool get(IPNet<A>& n, A& nh, uint32_t& cost, uint32_t& tag);
inline uint32_t packet_entry() const { return _pe; }
protected:
const RipPacket<A>* _rp;
uint32_t _pe; // Current packet entry
A _nh6; // IPv6 only next hop
};
template <>
bool
ResponseReader<IPv4>::get(IPNet<IPv4>& n,
IPv4& nh,
uint32_t& cost,
uint32_t& tag)
{
const uint8_t* pre_ptr = _rp->route_entry_ptr(_pe);
if (pre_ptr == NULL)
return false;
const PacketRouteEntry<IPv4> pre(pre_ptr);
n = pre.net();
nh = pre.nexthop();
cost = pre.metric();
tag = pre.tag();
_pe++;
return true;
}
template <>
bool
ResponseReader<IPv6>::get(IPNet<IPv6>& n,
IPv6& nh,
uint32_t& cost,
uint32_t& tag)
{
for (;;) {
const uint8_t* pre_ptr = _rp->route_entry_ptr(_pe);
if (pre_ptr == NULL)
return false;
const PacketRouteEntry<IPv6> pre(pre_ptr);
if (pre.is_nexthop()) {
_nh6 = pre.nexthop();
_pe++;
continue;
}
nh = _nh6;
n = pre.net();
cost = pre.metric();
tag = pre.tag();
_pe++;
return true;
}
}
//-----------------------------------------------------------------------------
// Horizon checkers
template <typename A>
class HorizonValidatorBase
{
public:
HorizonValidatorBase(const set<IPNet<A> >& tpn, const set<IPNet<A> >& opn)
: _total_routes_seen(0), _test_peer_routes_seen(0),
_tpn(tpn), _opn(opn)
{}
virtual ~HorizonValidatorBase() {}
/* Check an individual response packet is valid */
virtual bool valid_response(const RipPacket<A>* p) = 0;
/* Final check responses valid */
virtual bool valid_in_sum() const = 0;
inline uint32_t total_routes_seen() const
{
return _total_routes_seen;
}
inline uint32_t test_peer_routes_seen() const
{
return _test_peer_routes_seen;
}
protected:
uint32_t _total_routes_seen;
uint32_t _test_peer_routes_seen;
const set<IPNet<A> >& _tpn;
const set<IPNet<A> >& _opn;
};
template <typename A>
class NoHorizonValidator : public HorizonValidatorBase<A> {
public:
NoHorizonValidator(const set<IPNet<A> >& tpn, const set<IPNet<A> >& opn)
: HorizonValidatorBase<A>(tpn, opn)
{}
bool valid_response(const RipPacket<A>* p)
{
IPNet<A> n;
A nh;
uint32_t cost;
uint32_t tag;
ResponseReader<A> rr(p);
while (rr.get(n, nh, cost, tag) == true) {
this->_total_routes_seen++;
if (this->_tpn.find(n) != this->_tpn.end()) {
this->_test_peer_routes_seen++;
} else if (this->_opn.find(n) != this->_opn.end()) {
// No-op
} else {
// Not a test peer net and not an other peer net
// ==> it's bogus
verbose_log("Failed Processing entry %u / %u %s cost %u\n",
XORP_UINT_CAST(rr.packet_entry()),
XORP_UINT_CAST(p->max_entries()),
n.str().c_str(),
XORP_UINT_CAST(cost));
return false;
}
}
return true;
}
bool valid_in_sum() const
{
if (this->test_peer_routes_seen() != this->_tpn.size()) {
verbose_log("Test routes seen (%u) does not match expected (%u)\n",
XORP_UINT_CAST(this->test_peer_routes_seen()),
XORP_UINT_CAST(this->_tpn.size()));
return false;
}
verbose_log("total routes seen %u, test peer routes seen = %u\n",
XORP_UINT_CAST(this->total_routes_seen()),
XORP_UINT_CAST(this->test_peer_routes_seen()));
return this->test_peer_routes_seen() * 2 == this->total_routes_seen();
}
};
template <typename A>
class SplitHorizonValidator : public HorizonValidatorBase<A> {
public:
SplitHorizonValidator(const set<IPNet<A> >& tpn, const set<IPNet<A> >& opn)
: HorizonValidatorBase<A>(tpn, opn)
{}
bool valid_response(const RipPacket<A>* p)
{
IPNet<A> n;
A nh;
uint32_t cost;
uint32_t tag;
ResponseReader<A> rr(p);
while (rr.get(n, nh, cost, tag) == true) {
this->_total_routes_seen++;
if (this->_opn.find(n) == this->_opn.end()) {
verbose_log("Saw own or alien route with split horizon\n");
// ==> it's bogus
verbose_log("Failed Processing entry %u / %u %s cost %u\n",
XORP_UINT_CAST(rr.packet_entry()),
XORP_UINT_CAST(p->max_entries()),
n.str().c_str(),
XORP_UINT_CAST(cost));
return false;
}
}
return true;
}
bool valid_in_sum() const
{
if (this->test_peer_routes_seen() != 0) {
verbose_log("Test peer routes seen (%u) does not match expected "
"(%u)\n",
XORP_UINT_CAST(this->test_peer_routes_seen()),
XORP_UINT_CAST(0));
return false;
}
verbose_log("total routes seen %u, test peer routes seen = %u\n",
XORP_UINT_CAST(this->total_routes_seen()),
XORP_UINT_CAST(this->test_peer_routes_seen()));
return this->total_routes_seen() == (uint32_t)this->_opn.size();
}
};
template <typename A>
class PoisonReverseValidator : public HorizonValidatorBase<A> {
public:
PoisonReverseValidator(const set<IPNet<A> >& tpn,
const set<IPNet<A> >& opn)
: HorizonValidatorBase<A>(tpn, opn)
{}
bool valid_response(const RipPacket<A>* p)
{
IPNet<A> n;
A nh;
uint32_t cost;
uint32_t tag;
ResponseReader<A> rr(p);
while (rr.get(n, nh, cost, tag) == true) {
this->_total_routes_seen++;
if (this->_tpn.find(n) != this->_tpn.end()
&& cost == RIP_INFINITY) {
this->_test_peer_routes_seen++;
} else if (this->_opn.find(n) != this->_opn.end()) {
// No-op
} else {
// Not a test peer net and not an other peer net
// ==> it's bogus
verbose_log("Failed Processing entry %u / %u %s cost %u\n",
XORP_UINT_CAST(rr.packet_entry()),
XORP_UINT_CAST(p->max_entries()),
n.str().c_str(),
XORP_UINT_CAST(cost));
return false;
}
}
return true;
}
bool valid_in_sum() const
{
if (this->test_peer_routes_seen() != this->_tpn.size()) {
verbose_log("Test routes seen (%u) does not match expected (%u)\n",
XORP_UINT_CAST(this->test_peer_routes_seen()),
XORP_UINT_CAST(this->_tpn.size()));
return false;
}
verbose_log("total routes seen %u, test peer routes seen = %u\n",
XORP_UINT_CAST(this->total_routes_seen()),
XORP_UINT_CAST(this->test_peer_routes_seen()));
return this->test_peer_routes_seen() * 2 == this->total_routes_seen();
}
};
// ----------------------------------------------------------------------------
// OutputTester
//
// This is a bit nasty, the OutputClass is either OutputUpdates or OutputTable
// class. These classes have the same methods and so it seems a waste to
// write this code out twice. OutputClass is only referenced in one location
// so it's not rocket science to comprehend this.
//
static const IfMgrIfTree ift_dummy = IfMgrIfTree();
template <typename A, typename OutputClass>
class OutputTester
{
public:
OutputTester(const set<IPNet<A> >& test_peer_nets,
const set<IPNet<A> >& other_peer_nets)
: _e(), _rip_system(_e), _pm(_rip_system, ift_dummy),
_tpn(test_peer_nets), _opn(other_peer_nets)
{
_pm.test_port()->constants().set_expiry_secs(10);
_pm.test_port()->constants().set_deletion_secs(5);
_pm.test_port()->set_advertise_default_route(false);
_pm.other_port()->constants().set_expiry_secs(10);
_pm.other_port()->constants().set_deletion_secs(5);
_pm.test_port()->set_io_handler(new BlockedPortIO<A>(*_pm.test_port()),
true);
_pm.other_port()->set_io_handler(
new BlockedPortIO<A>(*_pm.other_port()), true);
}
~OutputTester()
{
RouteDB<A>& rdb = _rip_system.route_db();
rdb.flush_routes();
}
int
run_test(RipHorizon horizon, HorizonValidatorBase<A>& validator)
{
_pm.test_port()->set_horizon(horizon);
RouteDB<A>& rdb = _rip_system.route_db();
PacketQueue<A> op_out; // Output pkt qu.
OutputClass ou(_e, *_pm.test_port(), op_out, rdb); // Output pkt gen
ou.start();
verbose_log("Injecting routes from test peer.\n");
for (typename set<IPNet<A> >::const_iterator n = _tpn.begin();
n != _tpn.end(); n++) {
RouteEntryOrigin<A>* reo = _pm.test_peer();
if (rdb.update_route(*n, A::ZERO(), 5u, 0u, reo, PolicyTags(),
false) == false) {
verbose_log("Failed to add route for %s\n",
n->str().c_str());
return 1;
}
}
verbose_log("Injecting routes from other peer.\n");
for (typename set<IPNet<A> >::const_iterator n = this->_opn.begin();
n != this->_opn.end(); n++) {
RouteEntryOrigin<A>* reo = _pm.other_peer();
if (rdb.update_route(*n, A::ZERO(), 5u, 0u, reo, PolicyTags(),
false) == false) {
verbose_log("Failed to add route for %s\n",
n->str().c_str());
return 1;
}
}
bool timeout = false;
XorpTimer tot = _e.set_flag_after_ms(10000, &timeout);
ou.start();
while (ou.running() && timeout == false) {
_e.run();
}
verbose_log("%u bytes buffered in packet queue.\n",
XORP_UINT_CAST(op_out.buffered_bytes()));
if (timeout) {
verbose_log("Timed out!\n");
return 1;
}
uint32_t cnt = 0;
while (op_out.empty() == false) {
if (validator.valid_response(op_out.head()) == false) {
verbose_log("Failed on packet validation.\n");
return 1;
}
op_out.pop_head();
cnt++;
}
if (validator.valid_in_sum() == false) {
verbose_log("Not valid in sum.\n");
return 1;
}
return 0;
}
protected:
EventLoop _e;
System<A> _rip_system;
SpoofPortManager<A> _pm;
const set<IPNet<A> >& _tpn;
const set<IPNet<A> >& _opn;
};
/**
* Print program info to output stream.
*
* @param stream the output stream the print the program info to.
*/
static void
print_program_info(FILE *stream)
{
fprintf(stream, "Name: %s\n", program_name);
fprintf(stream, "Description: %s\n", program_description);
fprintf(stream, "Version: %s\n", program_version_id);
fprintf(stream, "Date: %s\n", program_date);
fprintf(stream, "Copyright: %s\n", program_copyright);
fprintf(stream, "Return: %s\n", program_return_value);
}
/*
* Print program usage information to the stderr.
*
* @param progname the name of the program.
*/
static void
usage(const char* progname)
{
print_program_info(stderr);
fprintf(stderr, "usage: %s [-v] [-h]\n", progname);
fprintf(stderr, " -h : usage (this message)\n");
fprintf(stderr, " -v : verbose output\n");
}
// ----------------------------------------------------------------------------
// Injected Network state
template <typename A>
int
run_all_test_cases()
{
int rval = 0;
static const uint32_t n_routes = 577;
// Make one large collection of unique nets
set<IPNet<A> > all_nets;
make_nets<A>(all_nets, 2 * n_routes);
set<IPNet<A> > tpn; // networks associated with peer under test
set<IPNet<A> > opn; // networks associated with other peer.
// Split large collection into nets for tpn and opn
for_each(all_nets.begin(), all_nets.end(), SplitNets<A>(tpn, opn));
//
// OutputUpdates class tests
//
{
verbose_log("=== IPv%u No Horizon updates test ===\n",
XORP_UINT_CAST(A::ip_version()));
OutputTester<A, OutputUpdates<A> > tester(tpn, opn);
NoHorizonValidator<A> nohv(tpn, opn);
rval |= tester.run_test(NONE, nohv);
if (rval)
return rval;
}
{
verbose_log("=== IPv%u Split Horizon updates test ===\n",
XORP_UINT_CAST(A::ip_version()));
OutputTester<A, OutputUpdates<A> > tester(tpn, opn);
SplitHorizonValidator<A> shv(tpn, opn);
rval |= tester.run_test(SPLIT, shv);
if (rval)
return rval;
}
{
verbose_log("=== IPv%u Split Horizon Poison Reverse test ===\n",
XORP_UINT_CAST(A::ip_version()));
OutputTester<A, OutputUpdates<A> > tester(tpn, opn);
PoisonReverseValidator<A> prv(tpn, opn);
rval |= tester.run_test(SPLIT_POISON_REVERSE, prv);
if (rval)
return rval;
}
//
// OutputTable class tests
//
{
verbose_log("=== IPv%u No Horizon table test ===\n",
XORP_UINT_CAST(A::ip_version()));
OutputTester<A, OutputTable<A> > tester(tpn, opn);
NoHorizonValidator<A> nohv(tpn, opn);
rval |= tester.run_test(NONE, nohv);
if (rval)
return rval;
}
{
verbose_log("=== IPv%u Split Horizon table test ===\n",
XORP_UINT_CAST(A::ip_version()));
OutputTester<A, OutputTable<A> > tester(tpn, opn);
SplitHorizonValidator<A> shv(tpn, opn);
rval |= tester.run_test(SPLIT, shv);
if (rval)
return rval;
}
{
verbose_log("=== IPv%u Split Horizon Poison Reverse table test ===\n",
XORP_UINT_CAST(A::ip_version()));
OutputTester<A, OutputTable<A> > tester(tpn, opn);
PoisonReverseValidator<A> prv(tpn, opn);
rval |= tester.run_test(SPLIT_POISON_REVERSE, prv);
}
return rval;
}
int
main(int argc, char* const argv[])
{
//
// 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 ch;
while ((ch = getopt(argc, argv, "hv")) != -1) {
switch (ch) {
case 'v':
set_verbose(true);
break;
case 'h':
case '?':
default:
usage(argv[0]);
xlog_stop();
xlog_exit();
if (ch == 'h')
return (0);
else
return (1);
}
}
argc -= optind;
argv += optind;
int rval = 0;
XorpUnexpectedHandler x(xorp_unexpected_handler);
try {
rval |= run_all_test_cases<IPv4>();
rval |= run_all_test_cases<IPv6>();
} catch (...) {
// Internal error
xorp_print_standard_exceptions();
rval = 2;
}
//
// Gracefully stop and exit xlog
//
xlog_stop();
xlog_exit();
return rval;
}
syntax highlighted by Code2HTML, v. 0.9.1