Cisco IOU:Connect IOU with real or external networks

This is a reblog from the original article at Connect IOU with real networks or dynamips

From Internetworkpro

This guide provides explanations to a script called iou2net.pl (script is at the end of the article), which allows you to attach IOU instances to real network (interfaces). In its functionality, its similar to what the ioulive tool does. For more information about IOU or ioulive, ask your Cisco SE/AM that provided you with your copy of IOU, or check out the [IOU FAQ from evilrouters.net][3].

Why another script that does nearly the same thing as ioulive? While playing around with IOU, i tried to come up with a way to attach IOU instances to dynamips directly. I managed to get this working for single instances, but came to the conclusion that its best to integrate such functionality in dynagen directly. Unfortunately, Im not a good programmer and have no experience with python, nor the time to dig through the dynagen sources and extend the code. This script is a byproduct of this work, where i tried to document the packet format that IOU uses to communicate between instances.

Installation

The program is written in perl and can be copied directly from the source listing (end of the article) to a file. Make the file executeable with

chmod +x ./iou2net.pl

Dependencies

iou2net depends on some perl modules. Most of them should come with your default perl distribution. Best case, the extra installation of Net::Pcap should do the trick to satisfy dependencies. On Ubuntu and Debian systems, this is provided with the “libnet-pcap-perl” package. Of course, beside the perl package, you need libpcap too (not sure if -dev is required).

sudo apt-get install libnet-pcap-perl libpcap0.8

For systems that dont pack the perl module, use CPAN to install it:

perl -MCPAN -e 'install Net::Pcap' 

Now try to start the script, it should start without any module warnings, printing the help screen:

./iou2net.pl

Usage

iou2net will forward frames between a IOU instance and a (real) network adapter. IOU needs a special mapping in its NETMAP file. The interface of the IOU instance you want to forward from has to be listed as a source entry, with a “@” suffix at the end. The destination of the mapping will be a pseudo IOU instance number, with a pseudo interface number. iou2net.pl will use this pseudo IOU instance ID to connect to the real IOU instance, and do its frame relaying.

Example NETMAP file:

10:1/1              11:1/0
10:1/2              12:1/0
10:1/0@iou-test             20:0/0@iou-test

The first two mappings are normal connections between IOU instances at the same host (R10, 1/1 R11, 1/0 and R10, 1/2 R12, 1/0). The last one connects interface 1/0 of R10 to pseudo router (instance) 20, interface 0/0.

You can choose any ID (<1024), as long as its not used by any real IOU instance. Specify this ID with the option “-p” when launching the script. Dont use this arbitrary ID for anything else in your mappings/topology. iou2net will need to read through this NETMAP file, to determine the correct mapping. By default, it looks in the current directory for the file NETMAP. If you want to use a file in a different directory (and/or with a different name), use the “-n” option. Last, you must specify the network interface where the frames should be forwarded to/received from. iou2net.pl will put this interface into promiscuous mode, therefore you must have superuser privileges when starting the script.

$ sudo ./iou2net.pl -i eth0 -p 20
Forwarding frames between interface eth0 and IOU instance 10, int 1/0 -  press ^C to exit

Limitations

  • This is only tested with Linux so far. Im sure it will run at other modern Unices, too. From perl perspective, porting this to windows is a possible, yet a pointless approach, because there is no IO_W_ (afaik).
  • For the NETMAP mapping line that is related with iou2net.pl pseudo ID, you must use id:x/y interface notation and not the “compressed” id:z format. Its just a matter of implementation in the script, im too lazy to add this.
  • There is not much sanity checking. The last occurence of a line that contains the pseudo ID as a destination is used, no matter how many other mappings with this ID exist before in the NETMAP file. These are typos anyway (see next limitation).
  • A single instance of this script will handle one IOU < -> network mapping. If you, for example, have multiple NICs in your systems and therefore want multiple IOU interfaces to be forwarded, you must launch multiple instances of the script. Every mapping must get a unique pseudo ID as the destination, and every instance of the script must be started by adding this unique ID. Example: $ cat NETMAP 10:1/0@iou-test 20:0/0@iou-test 10:1/1@iou-test 21:0/0@iou-test 11:1/0@iou-test 22:0/0@iou-test […]$ sudo ./iou2net.pl -i eth0 -p 20 & […] $ sudo ./iou2net.pl -i eth1 -p 21 & […] $ sudo ./iou2net.pl -i eth2 -p 22 & […]

I didnt test this approach, but it should work. Also, i didnt test the behavior of bridging multiple instances of this script to the same physical interface – please provide feedback.

Run all your IOU instances as the same user, otherwise you end up with different “netio” subdirectories in /tmp, and your IOU instances cannot talk to each other. The script needs to know the real uid of the user, therefore you should invoke iou2net.pl with sudo, from the same user that runs the IOU instances.

What works

I’ve done some quick tests with a local IOU instance, one Ethernet interface bridged to a real network where a c1841 is located:

  • Basic communication at layer 2 and 3 (Ethernet), up to MTU of 1500
  • OSPF adjacency over Broadcast segment (multicast)
  • ISIS adjacency
  • LDP adjacency
  • MPLS encapsulation over Ethernet (@1500 byte [MPLS] MTU, no baby giant/jumbo frame support)
  • IPv6 auto discovery
  • various connectivity tests toward v4 and v6 Internet
  • attaching IOU to dynamips (see below)

IOU communication

In this cha pter, i will outline the packet format and methods IOU uses when communicating outside of an IOU instance. Inter-instance communication is done through UNIX domain sockets. These are created in the subdirectory “/tmp/netio1000″, with a numeric name that corresponds to . So far, i cannot tell whether this directory is the same for anyone, anytime. You can sniff this traffic with strace, like

$ strace -e sendto,recvfrom -xx -o capturefile.txt <your IOU command line>

When sending frames, IOU will submit the entire L2 frame, prepended with a IOU proprietary header. This header is required at the receiving instance, to make the distinction to which local interface the frame is destined to. This is important, because the sockets are per instance (router) and have no way to decide to which internal interface to forward to (whithout extra logic that looks at MAC addresses etc.).

Furthermore, IOU does sanity checks with this header. When receiving a frame, the source information (sending ID, sending interface) is checked against the mappings that were read from the NETMAP file. It is not possible to send frames to an instance with valid destination ID and interface numbers and faked/unknown source information; the source ID and interface numbers have to match also.

The header format is described in the script. I cannot say if the last two bytes are really a delimiter that is always 0x01000, or if these fields serve a different purpose.

The script walks through the NETMAP file and determines the correct mapping, extracts the source IOU instance ID and its interface numbers. As discussed above, this is important for constructing the IOU header. Then it creates a socket $iou_pseudo_sock for out IOU pseudo ID (the destination), the real IOU instance (the source) will send its frames to this socket. This socket will be read- and writeable by anyone, since this script runs as root, where you usually run IOU as a normal user. Furthermore, we bind to the socket of the source IOU instance

($iou_router_sock). The IOU header that is used when sending frames to the real IOU instance is now prebuild, all the required information is available. Next, we bind to the NIC through PCAP routines.

The receiver that handles the traffic direction “IOU->real network” is forked to allow for non-blocking functionality (There is an option to IO::Socket::UNIX, allowing the socket to operate in nonblocking mode, but i found this way more intuitive). For every frame that the source IOU instance sends, we strip off the first 8 bytes (the IOU header), and transmit the frame via the real NIC.

The receiver that handles the direction “real network->IOU” is implemented with a pcap capture loop, where the “logic” is inside the sub recv_loop. Every received frame will be prepended with the precompiled IOU header and send to the socket of the source IOU instance.

Script

This is a reblog from the original article at Connect IOU with real networks or dynamips and probably worth checking there first for any updates.

#!/usr/bin/perl

use strict;
use warnings;
use Getopt::Long;
use Net::Pcap;
use IO::Socket;

my $version = "v0.31";
my $version_date = "28-Jan-2011";

###################################################################################
# CHANGES
# =======
#
# v0.31, 28-Jan-2011
# -----------------
# - MAC address is now in "ether" format (bytes separated with ":") for building
#   the capture filter
#
# v0.3, 27-Jan-2011
# -----------------
# - better capture filter handling, after understanding how IOU generates
#   MAC addresses (related code is still ugly)
# - hostnames with hyphen are now accepted
#
# v0.21, 26-Jan-2011
# -----------------
# - changed socket_base handling after receiving hint that "1000" is the uid
#   that IOU is started with ;-)
#
# v0.2, 24-Jan-2011
# -----------------
# - added pcap filter to allow for better performance on busy nics
#
# v0.1, 23-Jan-2011
# -----------------
# - first release
#
###################################################################################

my $help = < <EOF;

iou2net.pl: bridge between iou and real networks (IOUlive replacement)
Version $version, $version_date.

usage:
iou2net.pl -i <network interface> -n [<iou NETMAP file>] -p </iou><iou pseudo instance ID>

-i <network interface>
The NIC you want to bridge to/from. You need superuser privileges to do that.

-n <iou NETMAP file> (optional)
A NETMAP file is always needed, because the original IOU instance must be determined
by the script. Without this parameter, the script tries to open the NETMAP file from
the current directory. If you want to use a file in a different location, use this
parameter.

-p </iou><iou pseudo instance ID>
IOU requires a pseudo instance for this. When bridging your IOU router interface,
specify an unused ID as the target in your NETMAP file, like

1:2/1@hobel    666:1/0@hobel

666 is the pseudo IOU instance ID, hobel is the host where the IOU and the script
runs at. When starting the script, use 666 then.

CAVEATS: For now, you need to use x/y interface format in the NETMAP file, at least for
the mapping this script requires. Also, for bridging multiple router interfaces, separate
instances of this script must be launched, and you need an unique pseudo IOU ID per
instance.

EOF

my $err;
my $pcap_recv_data;
my $iou_recv_data;
my $iou_header;
my $iface;
my $netmap_file = "./NETMAP";
my $netmap_handle;
my $uid;
my $socket_base;
my $pseudo_instance;
my $pseudo_instance_interface_major;
my $pseudo_instance_interface_minor;
my $iou_instance;
my $iou_interface_major;
my $iou_interface_minor;
my $mac;
my $pcap_filter;

GetOptions( 'help'      =>  sub{ print"$help"; exit(0); },
        'i=s'       =>  $iface,
        'n=s'       =>  $netmap_file,
        'p=i'       =>  $pseudo_instance
);

die "nPlease provide -i and -p!n$help" unless ($iface && $pseudo_instance);       

# socket directory is a directory below $TMPDIR (/tmp), composed of "netio" plus
# uid of the user that runs the iou binary
# since we assume this script gets invoked with sudo by most people:
# try to be smart about getting real UID, $< does not (always?) return real uid when using sudo

$uid = $ENV{SUDO_UID};
$uid = $< unless (defined $uid);        # apparently not started with sudo
$socket_base = "/tmp/netio$uid";

open (netmap_handle, $netmap_file) or die "Can't open netmap file $netmap_filen";

# walk through NETMAP file and try to determine the source IOU instance
while (<netmap_handle>)
{
    # stop when there is a match for our pseudo instance ID as the destination
    next if !($_ =~ m/^d+:d+/d+@[w-]+[ t]+$pseudo_instance:d+/d+@[w-]+(s|t)*$/);
    my $inputline = $_;
        chomp($inputline);

    # we just ignore any hostname statements
    $inputline =~ s/@[w-]+//g;

        my @connline = split (/[ t]+/, $inputline);
        $connline[0] =~ s/(st)*//g;
        $connline[1] =~ s/(st)*//g;
        my @iou_src = split (/:/, $connline[0]);
        my @iou_dst = split (/:/, $connline[1]);
    $iou_instance = $iou_src[0];
    ($iou_interface_major,$iou_interface_minor) = split (///, $iou_src[1]);
    ($pseudo_instance_interface_major,$pseudo_instance_interface_minor) =  split (///, $iou_dst[1]);
}

close (netmap_handle);

die "Could not find any valid mapping for IOU pseudo instance $pseudo_instance in NETMAP file" unless ((defined $iou_instance) && (defined $iou_interface_major) && (defined $iou_interface_minor) && (defined $pseudo_instance_interface_major) && (defined $pseudo_instance_interface_minor));

# unlink socket for IOU pseudo instance
unlink "$socket_base/$pseudo_instance";

# create socket for IOU pseudo instance
my $iou_pseudo_sock = IO::Socket::UNIX->new(Type=>SOCK_DGRAM, Listen=>5, Local=>"$socket_base/$pseudo_instance") or die "Can't create IOU pseudo socketn";
# allow anyone to read and write
chmod 0666, "$socket_base/$pseudo_instance";

# attach to real IOU instance
my $iou_router_sock = IO::Socket::UNIX->new(Type=>SOCK_DGRAM, Peer=>"$socket_base/$iou_instance") or die "Can't connect to IOU socket at $socket_base/$iou_instancen";

# precompute IOU header
# IOU header format
# Pos (byte)    value
# ==============================================================
# 00 - 01   destination (receiving) IOU instance ID
# 02 - 03   source (sending) IOU instance ID
# 04        receiving interface ID
# 05        sending interface ID
# 06 - 07   fixed delimiter, looks like its always 0x01 0x00
#
#               interface ID = <major int number> + (<minor int number> * 16)

$iou_header = sprintf("%04x",$iou_instance) . sprintf ("%04x", $pseudo_instance);
$iou_header .= sprintf ("%02x", ($iou_interface_major + ($iou_interface_minor * 16)));
$iou_header .= sprintf ("%02x", ($pseudo_instance_interface_major + ($pseudo_instance_interface_minor * 16)));
$iou_header .= "0100";
$iou_header = pack("H*", $iou_header);

# bind to network interface, promiscuous mode
my $pcap = Net::Pcap::open_live($iface, 1522, 1, 100, $err);
die "pcap: can't open device $iface: $err (are you root?)n"    if(not defined $pcap);

# receive IOU frame and send to real network
# we fork this, so traffic can be received and processed via pcap in the pcap loop below

my $iou_pseudo_fork = fork();
if ($iou_pseudo_fork == 0)
{
    while (1)
    {
        # IOU frame received via pseudo ID socket
        $iou_pseudo_sock->recv($iou_recv_data,1522);

        # cut off IOU header (first 8 bytes)
        $iou_recv_data =~ s/^.{8}//;

        # send IOU generated frame to real network
        Net::Pcap::sendpacket($pcap,$iou_recv_data);
    }
    exit(0);
}

# provide a clean exit when user sends break
$SIG{INT} = &pcap_sigint;

# construction of IOU MAC address for external connectivity
# Pos (byte)        value
# ==============================================================
# 0 (high nibble)   from IOU instance ID (2 bytes, only 10 bits used),
#                       the two least significant bits from the high byte
#                       are taken and shifted one bit left
# 0 (low nibble)    always 0xE
# 1 - 3         UID of the user that runs the IOU instance
# 4         low byte of the IOU instance ID
# 5         interface ID
#
# for x64 systems, binary math works well, like
# $mac = (((($iou_instance & 0x0300) < < 1 ) << 36 ) + 0xE0000000000 );
# $mac += $uid << 16;
# $mac += ($iou_instance & 0xFF) << 8;
# $mac += ($iou_interface_minor << 4) + $iou_interface_major;
#
# apparently I'm too stupid to deal with Math::BigInt for 32bit system compatibility, so I use string operations

my $macstring;
$macstring = sprintf "%x", (($iou_instance >> 7 & 6));
$macstring .= "e";
$macstring .= sprintf "%06x", ($uid);
$macstring .= sprintf "%02x", (($iou_instance & 0xFF));
$macstring .= sprintf "%02x", (($iou_interface_minor < < 4) + $iou_interface_major);

$macstring = join(":", unpack ("(A2)*", $macstring));

# build a capture filter for IOU interface MAC address
# this will match only what is destined to $macstring, plus multicasts and broadcasts
Net::Pcap::compile($pcap, $pcap_filter, '(ether[0] & 1 = 1) or (ether dst ' . $macstring . ')', 0, 0xFFFFFFFF) && die 'Unable to compile capture filter';
Net::Pcap::setfilter($pcap, $pcap_filter) && die 'Unable to assign capture filter';

print "Forwarding frames between interface $iface and IOU instance $iou_instance, int $iou_interface_major/$iou_interface_minor (MAC: $macstring) -  press ^C to exitn";

# define infinite loop for capturing network traffic
my $loop_exit = Net::Pcap::loop($pcap, -1, &recv_loop, $pcap_recv_data);

sub recv_loop
{
    my($user_data, $hdr, $pkt) = @_;

    # add IOU header in front of the received frame
    my $iou_frame = $iou_header . "$pkt";

    # send frame to IOU socket
    $iou_router_sock->send($iou_frame);

}

sub pcap_sigint
{
    Net::Pcap::breakloop($pcap);
    print "n...stopped by user.n";
    Net::Pcap::close($pcap);
    $iou_pseudo_sock->close;
    $iou_router_sock->close;
    kill 1, $iou_pseudo_fork;
    exit(0);
}</minor></major></iou></network></iou></your></instance>

Other Posts in A Series On The Same Topic

  1. Cisco IOU:Connect IOU with real or external networks (17th April 2011)
  2. Cisco IOU:Scripted Start Multiple Routing with L2IOU, memory (15th April 2011)
  3. Cisco IOU: What can Cisco do for Testing, Validation & the IPv6 challenge ? (15th April 2011)
  4. Cisco IOU: Starting Multiple Routers (15th April 2011)
  5. Cisco IOU: Shutting down the IOU Processes (15th April 2011)
  • http://flyxj.cn flyxj
  • Paul

    Iíve tested the script-??works per≠fectly on a Fedora 14 VM run in VMware fusion. Iíve move from Dynamips/?GNS to IUO, itís excel≠lent.
    Regards

  • [email protected]

    NETMAP file is the directory, and the instance of the router works normally; but I’m getting the following message:

    Could not find any valid mapping for IOU pseudo instance 1 in NETMAP file at ./iou2net.pl line 95.

  • Someurt

    getting the following error when running iou2net:

    isco@cisco:/opt/GNS3/iou$ sudo ./iou2net.pl -u 12000:127.0.0.1:7005 -p 150iou2net.pl, Version v0.4, 22-Apr-2011.Can’t connect to IOU socket at /tmp/netio1000/1
    I do see the folder /tmp/netio1000 created but it does not contain any file. Does anyone know how to fix this?
    thanks

  • Carlos

    Couple of points that may help someone:
    if you are using “sudo bash” to run everything as root, then the script confuses your
    real id and does not find the right netio subdir. Check with -v and see /tmp/netio*.
    Also, the script assumes macs with xE:xx:xx:xx:xx:xx, wich seem to be the ones used by IOU router images, but at least one ioul2 image uses x2:xx:xx:xx:xx:xx and fails. Easy to fix.

  • chi hanson

    Hi :

    when i start the router , click the router but telnet client can not start! just disappear , can you help me that ?