Commit 712fe222 authored by Leigh Stoller's avatar Leigh Stoller

Commit the static routing support. Invoked from tbprerun, after the

parser runs. The staticroutes script is a wrapper for Chad's route
solver. The network optimization is currently turned off; use -t to
turn it on, until I know if its correct.

Note that Chad gets credit for routecalc.cc; I'm just committing the
file for him, with a couple of trivial changes that I made.
parent 02950747
......@@ -1221,7 +1221,7 @@ outfiles="$outfiles Makeconf GNUmakefile \
tbsetup/webmkgroup tbsetup/mkgroup \
tbsetup/webmkacct tbsetup/mkacct tbsetup/eventsys_control \
tbsetup/webmkproj tbsetup/mkproj tbsetup/libtestbed.pm \
tbsetup/portstats tbsetup/vnode_setup \
tbsetup/portstats tbsetup/vnode_setup tbsetup/staticroutes \
tbsetup/console_setup.proxy tbsetup/exports_setup.proxy \
tip/GNUmakefile \
tmcd/GNUmakefile tmcd/freebsd/GNUmakefile tmcd/linux/GNUmakefile \
......
......@@ -293,7 +293,7 @@ outfiles="$outfiles Makeconf GNUmakefile \
tbsetup/webmkgroup tbsetup/mkgroup \
tbsetup/webmkacct tbsetup/mkacct tbsetup/eventsys_control \
tbsetup/webmkproj tbsetup/mkproj tbsetup/libtestbed.pm \
tbsetup/portstats tbsetup/vnode_setup \
tbsetup/portstats tbsetup/vnode_setup tbsetup/staticroutes \
tbsetup/console_setup.proxy tbsetup/exports_setup.proxy \
tip/GNUmakefile \
tmcd/GNUmakefile tmcd/freebsd/GNUmakefile tmcd/linux/GNUmakefile \
......
......@@ -31,7 +31,7 @@ LIBEXEC_STUFF = rmproj rmacct-ctrl wanlinksolve wanlinkinfo \
assign_wrapper ptopgen webnodeupdate \
webrmgroup webswapexp webnodecontrol \
webmkgroup webmkacct websetgroups webmkproj \
spewlogfile
spewlogfile staticroutes routecalc
LIB_STUFF = libtbsetup.pm exitonwarn.pm libtestbed.pm snmpit_intel.pm \
snmpit_cisco.pm snmpit_lib.pm snmpit_apc.pm power_rpc27.pm \
......@@ -47,8 +47,11 @@ include $(TESTBED_SRCDIR)/GNUmakerules
CXXFLAGS += -Wall -O3
wanlinksolve: wanlinksolve.o
${CXX} wanlinksolve.o -o wanlinksolve ${LIBS} -lm -lstdc++ ${LDFLAGS}
wanlinksolve: wanlinksolve.cc
${CXX} $< -o $@ ${LIBS} -lm -lstdc++ ${LDFLAGS}
routecalc: routecalc.cc
${CXX} $< -o $@ ${LIBS} -lm -lstdc++ ${LDFLAGS}
.PHONY: checkpass ns2ir
checkpass:
......
......@@ -503,7 +503,7 @@ Simulator instproc at {time eventstring} {
}
#
# Routing control. Right now, we do not support much at all.
# Routing control.
#
Simulator instproc rtproto {type args} {
var_import ::GLOBALS::default_ip_routing_type
......@@ -516,6 +516,8 @@ Simulator instproc rtproto {type args} {
set default_ip_routing_type "ospf"
} elseif {($type == "Manual")} {
set default_ip_routing_type "manual"
} elseif {($type == "Static")} {
set default_ip_routing_type "static"
} else {
punsup "rtproto: unsupported routing protocol ignored: $type"
return
......
/*
* routing.cc
*
* routes traffic between nodes (with weighted edges)
* in the same manner as ns-2.
*
* crb May 7 2002
*
* This file largely based on code which was borrowed from ns-2.
* As such, the following copyright applies to portions of this code:
*
* Copyright (c) 1991-1994 Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and the Network Research Group at
* Lawrence Berkeley Laboratory.
* 4. Neither the name of the University nor of the Laboratory may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* Routing code for general topologies based on min-cost routing algorithm in
* Bertsekas' book. Written originally by S. Keshav, 7/18/88
* (his work covered by identical UC Copyright)
*/
#include <stdlib.h>
#include <stdio.h>
#include <list>
#include <algorithm>
using namespace std;
// Forward decls
void routing_init();
void routing_insert( int sid, int tid, double cost, void * etc = NULL );
void routing_calc();
int routing_lookup( int sid, int did, void ** etcReturn = NULL );
// bet ya didnt know that 0x3fff == infinity...
#define INFINITY 0x3fff
// for some bizarre reason, cost is a double, though it gets added to ints..
struct adj_entry {
double cost;
void * etc;
};
struct route_entry {
int next_hop;
void * etc;
};
static int size;
static adj_entry * adj;
static route_entry * route;
static inline adj_entry & ADJ_REF( int s, int d ) { return adj[ s * size + d ]; }
static inline route_entry & ROUTE_REF( int s, int d ) { return route[ s * size + d ]; }
#define ADJ(i, j) ADJ_REF( i, j ).cost
#define ADJ_ENTRY(i, j) ADJ_REF( i, j ).etc
#define ROUTE(i, j) ROUTE_REF( i, j ).next_hop
#define ROUTE_ENTRY(i, j) ROUTE_REF( i, j ).etc
struct queued_adj_entry {
int sid, tid;
adj_entry adj;
};
static list< queued_adj_entry > * queued_adj_entries;
void routing_init()
{
size = 0;
adj = NULL;
route = NULL;
queued_adj_entries = new list< queued_adj_entry >();
}
// rather than inserting entries directly, and constantly growing
// the NxN array, I queue entries up until routes are calculated,
// at which time, a correctly sized array is allocated, and
// the entries are loaded in.
void routing_insert( int sid, int tid, double cost, void * etc )
{
sid++;
tid++;
queued_adj_entry e;
e.sid = sid;
e.tid = tid;
e.adj.cost = cost;
e.adj.etc = etc;
queued_adj_entries->push_back( e );
if ((sid + 1) > size) { size = sid + 1; }
if ((tid + 1) > size) { size = tid + 1; }
}
static inline void add_to_adj( queued_adj_entry & qe )
{
// printf("Adding %i -> %i.\n", qe.sid, qe.tid );
ADJ_REF( qe.sid, qe.tid ) = qe.adj;
}
static void compute_routes();
void routing_calc()
{
if (adj) { delete[] adj; }
adj = new adj_entry[ size * size ];
for (int i = 0; i < (size * size); i++) {
adj[i].cost = INFINITY;
adj[i].etc = NULL;
}
for_each( queued_adj_entries->begin(), queued_adj_entries->end(), add_to_adj );
delete queued_adj_entries;
queued_adj_entries = new list< queued_adj_entry >();
if (route) { delete[] route; }
route = new route_entry[size * size];
memset((char *)route, 0, size * size * sizeof(route_entry));
compute_routes();
}
// The actual algorithm. Joy.
static void compute_routes()
{
int n = size;
int* parent = new int[n];
double* hopcnt = new double[n];
/* do for all the sources */
int k;
for (k = 1; k < n; ++k) {
int v;
for (v = 0; v < n; v++)
parent[v] = v;
/* set the route for all neighbours first */
for (v = 1; v < n; ++v) {
if (parent[v] != k) {
hopcnt[v] = ADJ(k, v);
if (hopcnt[v] != INFINITY) {
ROUTE(k, v) = v;
ROUTE_ENTRY(k, v) = ADJ_ENTRY(k, v);
}
}
}
for (v = 1; v < n; ++v) {
/*
* w is the node that is the nearest to the subtree
* that has been routed
*/
int o = 0;
/* XXX */
hopcnt[0] = INFINITY;
int w;
for (w = 1; w < n; w++)
if (parent[w] != k && hopcnt[w] < hopcnt[o])
o = w;
parent[o] = k;
/*
* update distance counts for the nodes that are
* adjacent to o
*/
if (o == 0)
continue;
for (w = 1; w < n; w++) {
if (parent[w] != k &&
hopcnt[o] + ADJ(o, w) < hopcnt[w]) {
ROUTE(k, w) = ROUTE(k, o);
ROUTE_ENTRY(k, w) =
ROUTE_ENTRY(k, o);
hopcnt[w] = hopcnt[o] + ADJ(o, w);
}
}
}
}
/*
* The route to yourself is yourself.
*/
for (k = 1; k < n; ++k) {
ROUTE(k, k) = k;
ROUTE_ENTRY(k, k) = 0; // This should not matter
}
delete[] hopcnt;
delete[] parent;
}
int routing_lookup(int sid, int did, void ** etcReturn) {
int src = sid+1;
int dst = did+1;
if (route == 0) {
printf("routes not yet computed\n");
return (-1);
}
if (src >= size || dst >= size) {
printf("node out of range\n");
return (-2);
}
if (etcReturn) {
*etcReturn = ROUTE_ENTRY( src, dst );
}
return ROUTE(src, dst) - 1;
}
void routing_printall()
{
int s;
if (route == 0) {
printf("routes not yet computed\n");
return;
}
for (s = 1; s < size; s++) {
int d;
for (d = 1; d < size; d++) {
// Do not print routes for adj or self.
if ( s != d &&
ROUTE( s, d ) > 0 ) {
printf("route %i to %i via %i\n", s - 1, d - 1, ROUTE( s, d ) - 1 );
}
}
}
}
int main()
{
routing_init();
while(1) {
char line[1024];
fgets( line, 1024, stdin );
if (line[0] == 'i' || line[0] == 'I') {
char foo;
int s,t;
float cost = 1.0f; // 1 is ns's default, as well.
sscanf( line, "%c %i %i %f", &foo, &s, &t, &cost );
routing_insert( s, t, cost );
if (line[0] == 'I') { routing_insert( t, s, cost ); }
} else if (line[0] == 'c') {
routing_calc();
} else if (line[0] == 'C') {
routing_calc();
routing_printall();
break;
} else if (line[0] == 'l') {
char foo;
int s,t;
sscanf( line, "%c %i %i", &foo, &s, &t );
printf( "result: %i\n", routing_lookup(s,t) );
} else if (line[0] == 'p') {
routing_printall();
} else if (line[0] == 'q') {
break;
} else if (line[0] == 'h') {
printf( "'i <s> <t> <c>' - inserts adj from s to t, cost c\n"
"'I <s> <t> <c>' - as above, but inserts adj from t to s, too\n"
"'c' - calculates routes\n"
"'C' - calculates routes, prints all, and quits\n"
"'l <s> <t>' - looks up calculated route from s to t\n"
"'p' - prints all routes\n"
"'q' - quits\n" );
} else {
printf( "Unknown command. 'h' for help.\n" );
}
}
exit(0);
}
#!/usr/bin/perl -wT
use English;
use Getopt::Std;
use Socket;
use IO::Handle; # thousands of lines just for autoflush :-(
#
# usage: staticroutes <pid> <eid>
#
sub usage()
{
print("Usage: staticroutes [-n] [-d] [-f] [-t] <pid> <eid>\n".
" Use -n to print routes, but leave the DB alone.\n".
" Use -d to turn on debugging output.\n".
" Use -t to optimize network routes.\n".
" Use -f to force route calculation; ignore 'static' flag.\n");
exit(-1);
}
my $optlist = "dnft";
#
# Configure variables
#
my $TB = "@prefix@";
my $NETMASK = "255.255.255.0";
my $debug = 0;
my $routecalc = "$TB/libexec/routecalc";
my $impotent = 0;
my $force = 0;
my $optimize = 0;
#
# Testbed Support libraries
#
use lib "@prefix@/lib";
use libdb;
use libtestbed;
# un-taint path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
#
# Turn off line buffering on output
#
$| = 1;
#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
%options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (@ARGV != 2) {
usage();
}
if (defined($options{"d"})) {
$debug = 1;
}
if (defined($options{"n"})) {
$impotent = 1;
}
if (defined($options{"f"})) {
$force = 1;
}
if (defined($options{"t"})) {
$optimize = 1;
}
my $pid = $ARGV[0];
my $eid = $ARGV[1];
#
# Untaint args.
#
if ($pid =~ /^([-\@\w]+)$/) {
$pid = $1;
}
else {
die("Bad data in pid: $pid.");
}
if ($eid =~ /^([-\@\w]+)$/) {
$eid = $1;
}
else {
die("Bad data in eid: $eid.");
}
#
# Get the list of nodes.
#
my $query_result =
DBQueryFatal("select vname,ips,routertype from virt_nodes ".
"where pid='$pid' and eid='$eid'");
if (!$query_result->numrows) {
warn("*** $0:\n".
" No nodes in experiment $pid/$eid!\n");
exit(0);
}
# A Flag.
my $dostatic = 0;
# A map of ips for each node, indexed by vname:port.
my %ips = ();
# A map (and reverse map) of node (vname) to unique index.
my %map = ();
my %rmap = ();
my $index = 0;
# A map of lans, where each is a list of the members, indexed by lan.
my %lans = ();
# A reverse map (sorta). Map a pair of links back to the lan.
my %rlans = ();
# The costs, indexed by lan This is kinda bogus, but since the cost
# is the same for each member of a lan, no big deal.
my %costs = ();
#
# Convert list of nodes into a map of $vname:$port to IP. We will need the
# IPs later when filling in the virt_routes table.
#
while (my ($vname,$ips,$routertype) = $query_result->fetchrow_array) {
if (!$force && $routertype ne "static") {
next;
}
$dostatic++;
foreach my $ipinfo (split(" ", $ips)) {
my ($port,$ip) = split(":", $ipinfo);
$ips{"$vname:$port"} = $ip;
if (! defined($map{$vname})) {
$map{$vname} = $index;
$rmap{$index} = $vname;
$index++;
}
}
}
if (! $dostatic) {
warn("No nodes requested static routing in $pid/$eid. This is okay!\n");
exit(0);
}
#
# Now get the lans.
#
$query_result =
DBQueryFatal("select vname,member,cost " .
"from virt_lans where pid='$pid' and eid='$eid'");
if (!$query_result->numrows) {
warn("*** $0:\n".
" No links or lans in experiment $pid/$eid!\n");
exit(0);
}
while (my ($vname,$member,$cost) = $query_result->fetchrow_array) {
if (! defined($lans{$vname})) {
$lans{$vname} = [];
}
push(@{$lans{$vname}},$member);
$costs{$vname} = $cost;
}
#
# We use perl IPC goo to create a child we can both write to and read from
# (normal perl I/O provides just unidirectional I/O to a process).
#
if (! socketpair(CHILD, PARENT, AF_UNIX, SOCK_STREAM, PF_UNSPEC)) {
die("*** $0:\n".
" socketpair failed: $!\n");
}
CHILD->autoflush(1);
PARENT->autoflush(1);
my $childpid = fork();
if (! $childpid) {
close CHILD;
#
# Dup our descriptors to the parent, and exec the program.
# The parent then talks to it read/write.
#
open(STDIN, "<&PARENT") || die "Can't redirect stdin";
open(STDOUT, ">&PARENT") || die "Can't redirect stdout";
open(STDERR, ">&PARENT") || die "Can't redirect stderr";
exec($routecalc);
die("*** $0:\n".
" exec $routecalc failed: $!\n");
}
close PARENT;
#
# Okay, now send adjacency info for all the nodes on each lan to the child.
#
foreach my $lan (keys(%lans)) {
my @members = @{$lans{$lan}};
foreach my $member1 (@members) {
foreach my $member2 (@members) {
if ($member1 ne $member2) {
my ($node1,$port1) = split(":", $member1);
my ($node2,$port2) = split(":", $member2);
my $id1 = $map{$node1};
my $id2 = $map{$node2};
# Reverse mapping for later.
$rlans{"$node1:$node2"} = $lan;
if ($debug) {
print STDERR "$member1($id1) $member2($id2)\n";
}
print CHILD "i $id1 $id2 $costs{$lan}\n";
}
}
}
}
# Tell the child to print the routes.
print CHILD "C\n";
#
# Read back the results from the child.
#
my @results = ();
while (<CHILD>) {
if ($debug) {
print "$_";
}
push(@results, $_);
}
# Tell the child to quit. Then wait for it.
print CHILD "q\n";
waitpid($childpid, 0);
if ($?) {
die("*** $0:\n".
" $routecalc failed with status: $?\n");
}
#
# Parse the routes. Need to map the indices back to nodes, and then form a
# matrix. Why, well you will see in a bit ...
#
my %routes = ();
my %netroutes = ();
foreach my $result (@results) {
my ($src, $dst, $hop);
if ($result =~ /route (\d+) to (\d+) via (\d+)/) {
$src = $rmap{$1};
$dst = $rmap{$2};
$hop = $rmap{$3};
}
else {
die("*** $0:\n".
" $routecalc returned a bad line: '$result'\n");
}
if ($debug) {
print "src:$src dst:$dst hop:$hop\n";
}
$routes{"$src:$dst"} = $hop;
}
#
# Now it gets fun. We have to go through each src:dst pair in the route
# list. The problem is that the dst could be multihomed and multiple hops
# away, and so we have no idea what interface (IP) we are actually routing
# to. We have to know the IP, or else we won't be able to insert the route!
# So, we use the matrix to recurse through and find our way to the
# destination. When we get to the last hop, we can use the src:dst of that
# last hop to index into rlans, which gives the full lanlink list. Then we
# search that to find the IP.
#
foreach my $route (keys(%routes)) {
my ($src,$dst) = split(":", $route);
my $hop = $routes{$route};
my $type = 'host';
#
# If directly connected, skip.
#
if ($dst eq $hop) {
next;
}
#
# This is the lan that contains the src and hop. (the link).
#
my $srclan = $rlans{"$src:$hop"};
#
# Now we need to find the destination lan (link) by stepping through
# each hop till we get to it.
#
my $nhop = $hop;
while (1) {
# Next hop to destination.
if (!defined($routes{"$nhop:$dst"})) {
die("*** $0:\n".
" Failed to find route: $nhop:$dst!\n");
}
my $nnhop = $routes{"$nhop:$dst"};
# Last hop to destination.
if ($nnhop eq $dst) {
last;
}
$nhop = $nnhop;
}
my $dstlan = $rlans{"$nhop:$dst"};
if ($debug) {
print "$src --> $dst : $nhop:$dst\n";
}
#
# The members of the lan are used to map back to the IPs of the
# interfaces (since we need IPs to insert route entries).
#
my @srcmembers = @{$lans{$srclan}};
my @dstmembers = @{$lans{$dstlan}};
# Want to find these.
my ($srcip,$dstip,$hopip);
#
# Scan dstmembers till we find the dst. This is the IP we want.
#
foreach my $member (@dstmembers) {
my ($node,$port) = split(":", $member);
if ($node eq $dst) {
$dstip = $ips{$member};
last;
}
}
#
# If the dst is a real lan (>2 members) we want to create a net route
# and avoid a per-host entry for each pair. Convert the route, but
# remember we did it so we can skip similar routes in the future.
#
if ($optimize && @dstmembers > 2) {
my $newip = inet_ntoa(inet_aton($dstip) & inet_aton($NETMASK));
if (defined($netroutes{"$src:$newip"})) {
if ($netroutes{"$src:$newip"} ne $hop) {
die("*** $0:\n".
" network route mismatch: $src:$dst:$hop!\n");
}
next;
}
$netroutes{"$src:$newip"} = $hop;
$type = "net";
$dstip = $newip;
}
#
# Scan srcmembers to find the IPs for the src and hop.
#
foreach my $member (@srcmembers) {
my ($node,$port) = split(":", $member);
if ($node eq $src) {
$srcip = $ips{$member};
next;
}
if ($node eq $hop) {
$hopip = $ips{$member};
next;
}
}
if ($debug || $impotent) {
printf("%s: %-23s --> %-23s - %s\n",
($type eq "net" ? "n" : "h"),
"$src:$srcip", "$dst:$dstip", "$hop:$hopip");
}
if (! $impotent) {
DBQueryFatal("insert into virt_routes ".
" (pid,eid,vname,dst,nexthop,dst_type) ".