设为首页收藏本站language 语言切换
查看: 4541|回复: 4
收起左侧

[Cisco IOU] Cisco IOU with real networks or dynamips

[复制链接]
发表于 2011-1-29 23:27:45 | 显示全部楼层 |阅读模式
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

[edit] Dependencies

iou2net depends on some perl modules. Most of them should come with your default perl distribution. Best case, the extra installation of Net:cap 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:cap\'

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

./iou2net.pl

[edit] 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 \"@<hostname>\" 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

[edit] 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.

    * using a productive ethernet interface, where you push lots of traffic, isnt a good idea. There is no capture filter implemented, iou2net will happily forward any traffic it sees to IOU and vice versa (promiscuous mode). Capture filter added in v0.2.
    * 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.

[edit] 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)

[edit] IOU communication

In this chapter, 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 <instance ID>. 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.
[edit] thoughts about dynamips integration

It is possible to use this script to connect IOU with dynamips instances. For example, this can be done by utilizing dummy network interfaces that linux provides. Both IOU (with the help of iou2net.pl) and dynamips attach to the interface dummy0:

$ sudo modprobe dummy
$ sudo ifconfig dummy0 up
$ cat NETMAP
[...]
5:0/0@hobel     6:0/0@hobel
[...]

$ (launch IOU with instance ID 5)
$ sudo ./iou2net.pl -i dummy0 -p 6 &
$ sudo dynamips -p 1A-4E -s 1:1:gen_eth:dummy0 images/7200/c7200-123-20.IPPLUS.bin

dynagen has the gen_eth connector too, therefore dynagen powered topologies can be used too. This approach, although working, is not optimal. Since most people run dynagen (or GNS3) anyway, it would be much better to integrate the handling of IOU directly into dynagen. Furthermore, to allow IOU instances to be connected to other \"devices\" directly (like qemu powered olive), the domain socket<->pcap way is not flexible enough.

Between all the emulated platforms, the udp connector seems to be the common denominator. I wrote a \"proof of concept\" that uses udp connectors/encapsulation between IOU and dynamips, basically the code that uses pcap is replaced with udp sockets:

$dyna_socket = IO::Socket::INET->new(Proto=>\"udp\", LocalPort=>\"12001\", LocalAddr=>\"127.0.0.1\", PeerPort=>\"12000\", PeerAddr=>\"127.0.0.1\")

This socket is used as a sender and receiver together with a dynamips (or qemu) udp port mapping, like NIO_udp:12000:127.0.0.1:12001 (dynagen syntax). Frames received by the UDP socket go to the real IOU socket, with the IOU header, frames received from IOU pseudo ID socket get transmitted to the UDP socket, minus the IOU header. The big difference to iou2net is how multiple devices and interfaces will be handled. iou2net is more or less a point to point implementation; using udp connectors with multiple routers and interfaces at both sides requires smart logic that handles multiple sockets. Also, the IOU header is more important, its not enough to strip it off when receiving a IOU frame, the destination information in the header is the key to which UDP socket the frame shall be delivered to. It also makes sense to allocated UDP ports dynamically and create NETMAP files on the fly, based on topology definition that are in your dynagen .net file.

Long story short: i gave up trying to do this in perl; its something that deserves to be integrated directly into dynagen - perhaps in a similar way than qemu/qemuwrapper.
[edit] Script

#!/usr/bin/perl

use strict;
use warnings;
use Getopt:ong;
use Net:cap;
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 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 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_file\\n\";

# 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/(\\s\\t)*//g;
        $connline[1] =~ s/(\\s\\t)*//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 socket\\n\";
# 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_instance\\n\";

# 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:cap:pen_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:cap::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:cap::compile($pcap, \\$pcap_filter, \'(ether[0] & 1 = 1) or (ether dst \' . $macstring . \')\', 0, 0xFFFFFFFF) && die \'Unable to compile capture filter\';
Net:cap::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 exit\\n\";

# define infinite loop for capturing network traffic
my $loop_exit = Net:cap::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:cap::breakloop($pcap);
        print \"\\n...stopped by user.\\n\";
        Net:cap::close($pcap);
        $iou_pseudo_sock->close;
        $iou_router_sock->close;
        kill 1, $iou_pseudo_fork;
        exit(0);
}


该贴已经同步到 goodluck的微博
发表于 2012-1-19 17:44:58 | 显示全部楼层
永远支持论坛!祝越办越好!
沙发 2012-1-19 17:44:58 回复 收起回复
回复 支持 反对

使用道具 举报

发表于 2012-3-6 09:31:37 | 显示全部楼层
看不懂
板凳 2012-3-6 09:31:37 回复 收起回复
回复 支持 反对

使用道具 举报

发表于 2012-8-5 23:04:18 | 显示全部楼层
地板 2012-8-5 23:04:18 回复 收起回复
回复 支持 反对

使用道具 举报

发表于 2012-8-18 17:48:51 | 显示全部楼层
好繁琐啊
5# 2012-8-18 17:48:51 回复 收起回复
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 论坛注册

本版积分规则

QQ|Archiver|手机版|小黑屋|sitemap|鸿鹄论坛 ( 京ICP备14027439号 )  

GMT+8, 2025-2-23 12:53 , Processed in 0.063529 second(s), 21 queries , Redis On.  

  Powered by Discuz!

  © 2001-2025 HH010.COM

快速回复 返回顶部 返回列表