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.
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. For now, the script allows to attach to real network adapters and tap interfaces, and to communicate through UDP "links" as known from dynamips and qemu.
Contents |
[edit] 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::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.
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
Other dependencies may arise if you do not have tunctl or brctl commands. You can resolve this with the following:
sudo apt-get install uml-utilities bridge-utils
[edit] Usage
[edit] NETMAP
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.
[edit] Mode of operation / interfaces
You must specify where the frames should be forwarded to/received from. The script can operate in either of the 3 modes:
- PCAP (-i), attaching to real network interfaces
- TAP (-t), for a Layer 2 point-to-point connection to a tapX interface
- UDP (-u), sending and receving frames over UDP links as seen in dynamips and qemu
At least pcap and tap mode require super user privileges, so run the script with sudo.
[edit] PCAP mode
PCAP mode is the preferred method when dealing with traffic from real network interfaces. Just specify the real network interface with the -i option. iou2net.pl will put this interface into promiscuous mode.
$ sudo ./iou2net.pl -i eth0 -p 20
[edit] TAP mode
This is a point-to-point interface type, usually found in Linux and *BSD (tap is for L2, tun is for L3 connections). Its useful especially in bridging configurations. Specify the tap interface with the -t option (full name, like "tap3"). If the tap interface does not exist already, the script will create it for you.
$ sudo ./iou2net.pl -t tap0 -p 99
In any case, you are responsible to bring the tap interface "up". If desired, you also need to deal with further bridging configuration.
For example, the following setup allows IOU to talk to the local machine and to communicate with the real network attached to eth0. A bridge br0 is created, bridging traffic between eth0 and tap0. Traffic from the host itself lands on br0 and gets forwarded to the bridge member interfaces accordingly.
tunctl -t tap0 ifconfig eth0 0.0.0.0 promisc up ifconfig tap0 0.0.0.0 promisc up brctl addbr br0 brctl stp br0 off brctl setfd br0 1 brctl sethello br0 1 brctl addif br0 eth0 brctl addif br0 tap0 ifconfig br0 <your host ip settings> (launch IOU) iou2net.pl -t tap0 -p 111
[edit] UDP mode
For communication with other programs that provide UDP connectors (like dynamips NIO_udp), use this mode. The argument -u requires a port mapping format. It can be just "source-port:remote-port" for links that are local to the host that runs both applications.
It also can be of "source-port:remote-host:remote-port", if communication between applications on different hosts, across a network, is desired. remote-host must be an IP address or a resolveable hostname/FQDN.
$ sudo ./iou2net.pl -u 23001:192.0.2.20:23000 -p 12
Note that the port scheme is always as seen by IOU. In the remote application, you have to specify the ports in reverse meaning - spt:23000 and dpt:23001 from the example above.
[edit] Other Options
[edit] Packet Capture
With -f <capture file>, the script will write any frame received by IOU and the external connector to a capture file. This file can be opened by wireshark/tshark, even if the script is still running. If the file already exists, it will be overwritten without any warning.
[edit] Logging and Debugging
Mainly for troubleshooting purposes, the switches -v and -d will turn on chitchat mode. -v will provide some information during the setup phase of the script. When setup is done and the script is ready to do frame forwarding, no further messages will appear.
-d will do the same as -v, but print the frame header of any received and sent frame during runtime.
[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).
- The script focusses on Ethernet connectivity. Bridging together Serial interfaces works for udp links. The packet capture and the debug output are not useable without additional translation, because the interpretation of the header format is not aware of serial encapsulation protocols.
- With the current implementation, TAP mode will not work on *BSD
- 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 -u 12001:12000 -p 22 & [...]
- 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
- using a bridge configuration on Linux with tap interfaces, to talk to the host that serves IOU
- communication between dynamips and IOU running at different hosts, with UDP connectors
[edit] IOU communication
[edit] General
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/netio<uid>", with a numeric filename that corresponds to <instance ID>. You can sniff this traffic with strace, like
$ strace -e sendto,recvfrom -xx -o capturefile.txt <your IOU command line>
[edit] IOU header
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 0x0100, 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, the mode of operation is determined and sender/receiver logic is invoked.
For every frame sent by the source IOU instance, we strip off the first 8 bytes (the IOU header), and transmit the frame.
When receiving a frame from the external network, the precomputed IOU header will be prepended, and the resulting frame is sent to the socket of the source IOU instance.
[edit] Interface MAC addresses
IOU uses self-gererated MAC addresses for its interfaces. If an interface maps only to other IOU instances at the local host, it will have an OID of AA:BB:CC. Whether communication is internal or not is determined through the mappings in the NETMAP file. Any mapping that does not have a "@..." suffix is considered internal.
For interfaces that require external communication - mappings that have a "@..." suffix in the NETMAP file - a different interface MAC address is generated by IOU. The format is documented in the script. The script caclulates the MAC address of the source IOU instance and its interface, to generate a capture filter in PCAP mode. Because the real network interface card is put into promiscuous mode, this filter ensures that only useful traffic is seen by IOU (unicasts destined to its MAC, broadcasts, multicasts).
This MAC addresses generation has another implication. If you run the same IOU instance ID at different hosts, and you bridge the same interfaces together, you end up with duplicated MAC addresses (assuming both users running IOU use the same UID). Even if you use different IOU interfaces for external bridging, you may end up with odd results, because IOU may consider the MAC from the remote instance to be local (same IOU instance ID). Therefore, make IOU instance IDs unique across all your installations.
The script assumes a router IOU image. If using IOUL2, the low nible of the high byte is '2' instead of 'E' (for routed interfaces). For switched interfaces, no filter should be set. As of version 0.5, the capture filter hopefully respects l2iou interface MAC addresses. l2iou either uses Ethernet MAC OID of 02:<UID> or AA:BB:CC. Please see discussion page for detailed information. For communication toward non-IOU devices behind a l2iou instance, you can turn the capture filter completely off by using the -m switch (without any argument).
[edit] Script
#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Long;
use Net::Pcap;
use IO::Select;
use IO::Socket;
use IO::File;
use Time::HiRes qw(gettimeofday);
my $version = "v0.5";
my $version_date = "20-Sep-2011";
###################################################################################
# CHANGES
# =======
#
# v0.5, 20-Sep-2011
# -----------------
# - changed capture filter to allow for l2iou MAC addresses (more generic approach)
# - added -m option to bypass MAC filtering completely or to allow to specify own
# MAC address
#
# v0.4, 22-Apr-2011
# -----------------
# - for frame read, switched from fork to select
# - added bridging via udp links and tap interfaces
# - encoding issues hopefully fixed
# - more sane construction of IOU header and MAC address
# - verbose output
# - packet trace output (MAC headers)
# - writing traffic to pcap file
#
# 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)
usage:
iou2net.pl [-vd] [-f capture file] [-i interface] [-n netmapfile]
[-p instance ID] [-t interface] [-u portmap]
NOTE: -> You _must_ launch IOU before starting this script.
-> -i, -t or -u, and -p are required as a bare minimum.
-> Most operation require super user privileges; use sudo or run as
root.
-v
Optional, provides verbose output
-d
Optional, provides debug output (verbose + prints frame headers)
-f
optional, write frames to a capture file that can be opened with
wireshark
-i interface (PCAP mode)
Specify the interface you want to bridge to. This makes the script
to run in PCAP mode.
-n NETMAP file
Optional. Per default, the script tries to open ./NETMAP. If you
want to use a NETMAP file located elsewhere, use this argument.
-p instance ID
IOU requires a pseudo instance. When bridging your IOU router
interface, specify an unused ID as the target in your NETMAP file,
like
1:2/1\@hostname 666:1/0\@hostname
666 is the pseudo IOU instance ID, hostname is the host where IOU
and the script runs at. When starting the script, use -p 666 then.
After launching the IOU router instance #1, use interface 2/1 for
external connectivity. The Interface 1/0 at the pseudo instance
does not have any practical meaning for router configuration.
-t interface (TAP mode)
Specify the tap interface you want to attach to. This makes the script
to run in TAP mode. If the interface does not exist, the script will
create it, otherwise it will attach to it. You are responsible to
have the interface in an "up" state and for any additional bridging
that may be required.
-u portmap (UDP mode)
Will establish communication through UDP links (dynamips, qemu).
Portmap has the following format:
(1) source-port:dest-port or
(2) source-port:remote_host:dest-port
The first variant is used for UDP communication at the local host
only (target runs at the same host).
The second variant allows to communicate with a target that runs at
a remote system. <remote_host> must be an IP address or a resolveable
hostname/FQDN.
Port numbers are always from a local (IOU) perspective, therefore
they are the reverse of what gets defined at the target system.
-m <MAC address> (PCAP mode only)
If <MAC address> is supplied, this address is used to build the
capture filter. If <MAC address> is not specified, no capture filter
will applied. This option should only be used for testing/debugging,
because the default capture filter should work for any l3/l2iou
instance out of the box.
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 $verbose;
my $debug;
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 $iou_pseudo_sock;
my $iou_router_sock;
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 $select_handle;
my $pcap;
my $pcap_filter;
my $udp_conn;
my $udp_shost;
my $udp_dhost;
my $udp_spt;
my $udp_dpt;
my $udp_listener;
my $tap;
my $tap_handle;
my $cap_file;
my $cap_handle;
my $cap_dumper;
my $user_mac;
GetOptions(
'help' => sub { print "$help"; exit(0); },
'v+' => \$verbose,
'd+' => \$debug,
'i=s' => \$iface,
'n=s' => \$netmap_file,
'p=i' => \$pseudo_instance,
'u=s' => \$udp_conn,
't=s' => \$tap,
'f=s' => \$cap_file,
'm:s' => \$user_mac
);
print "iou2net.pl, Version $version, $version_date.\n";
die "\nPlease provide -i, -t or -u, and -p!\n$help"
unless ( ( $iface || $udp_conn || $tap ) && $pseudo_instance );
$verbose = 1 if $debug;
# 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";
print "UID: $uid\n" if $verbose;
print "Socket base directory: $socket_base\n" if $verbose;
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);
print "Found valid mapping line in NETMAP: $inputline\n" if $verbose;
# 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);
print
"Using pseudoinstance $pseudo_instance, interface $pseudo_instance_interface_major/$pseudo_instance_interface_minor\n"
if $verbose;
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
$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";
# availability to read shall be queried through select()
$select_handle = IO::Select->new();
$select_handle->add($iou_pseudo_sock);
# allow anyone to read and write
chmod 0666, "$socket_base/$pseudo_instance";
print "Created pseudo IOU socket at $socket_base/$pseudo_instance\n"
if $verbose;
# attach to real IOU instance
$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";
print "Attached to real IOU socket at $socket_base/$iou_instance\n" if $verbose;
# 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 = pack( "nnCCH4",
$iou_instance,
$pseudo_instance,
( $iou_interface_minor << 4 ) | $iou_interface_major,
( $pseudo_instance_interface_minor << 4 ) |
$pseudo_instance_interface_major,
"0100" );
print "Precomputed IOU Header: ", unpack( "H*", $iou_header ), "\n" if $verbose;
# provide a clean exit
$SIG{INT} = \&caught_sigint;
# Open capture file
if ( defined $cap_file ) {
$cap_handle = Net::Pcap::pcap_open_dead( DLT_EN10MB, 1500 );
$cap_dumper = Net::Pcap::pcap_dump_open( $cap_handle, $cap_file )
or die "Cant open capture file: $!";
print "Opened file $cap_file for packet dump.\n" if $verbose;
}
# Determine Mode and setup sender and receiver logic
if ( defined $iface ) {
print "Working in pcap mode.\n" if $verbose;
# bind to network interface, promiscuous mode
$pcap = Net::Pcap::open_live( $iface, 1522, 1, 1, \$err );
die "pcap: can't open device $iface: $err (are you root?)\n"
unless ( defined $pcap );
# construction of IOU MAC address for external connectivity (L3IOU)
# 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;
my $macstring;
if ( defined $user_mac ) {
if ($user_mac) {
$macstring = $user_mac;
}
else {
$macstring = "";
}
}
else {
$macstring = pack( "CH6CC",
( ( $iou_instance >> 7 & 6 ) << 8 ) + 0xE,
unpack( "xH6", pack( "N", 0xFF000000 ^ $uid ) ),
$iou_instance & 0xFF,
( $iou_interface_minor << 4 ) | $iou_interface_major );
$macstring = uc( join( ":", unpack( "(H2)*", $macstring ) ) );
}
if ($macstring) {
print "Using MAC $macstring.\n" if $verbose;
# build a capture filter for IOU interface MAC address
# this will match only what is destined to $macstring, plus multicasts
# and broadcoasts
# for L2IOU, traffic destined to OIDs 02:<UID>:<UID> and AA:BB:CC is
# included in the filter, too
Net::Pcap::compile(
$pcap, \$pcap_filter,
'(ether[0] & 1 = 1) or
(ether dst ' . $macstring . ') or
(ether[0] = 0x02 and ether[1:2] = 0x'
. unpack( "H4", pack( "n", $uid ) ) . ') or
(ether[0] = 0xaa and ether[1:2] = 0xbbcc)',
0, 0xFFFFFFFF
) && die 'Unable to compile capture filter';
Net::Pcap::setfilter( $pcap, $pcap_filter )
&& die 'Unable to assign capture filter';
print "Capture filter set: (ether[0] & 1 = 1) or (ether dst '"
. $macstring . "') or
(ether[0] = 0x02 and ether[1:2] = 0x"
. unpack( "H4", pack( "n", $uid ) ) . ") or
(ether[0] = 0xaa and ether[1:2] = 0xbbcc)\n"
if $verbose;
}
else {
print "No capture filter set (empty -m option)\n" if $verbose;
}
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";
while (1) {
if ( grep { $_ eq $iou_pseudo_sock } $select_handle->can_read(0.001) ) {
# IOU frame received via pseudo ID socket
$iou_pseudo_sock->recv( $iou_recv_data, 1522 );
log_iou_frame( "R:I->P", $iou_recv_data ) if $debug;
$iou_recv_data = unpack( "x8a*", $iou_recv_data );
# send IOU generated frame to real network
Net::Pcap::sendpacket( $pcap, $iou_recv_data );
write_pcap_dump($iou_recv_data) if $cap_dumper;
log_frame( "S:I->P", $iou_recv_data ) if $debug;
}
else {
my %pcap_hdr;
my $return =
Net::Pcap::pcap_next_ex( $pcap, \%pcap_hdr, \$pcap_recv_data );
if ( $return eq 1 ) {
write_pcap_dump($pcap_recv_data) if $cap_dumper;
log_frame( "R:P->I", $pcap_recv_data ) if $debug;
# add IOU header in front of the received frame
# and send frame to IOU socket
$iou_router_sock->send(
pack( "a*a*", $iou_header, $pcap_recv_data ) );
log_iou_frame( "S:P->I",
pack( "a*a*", $iou_header, $pcap_recv_data ) )
if $debug;
}
}
}
}
elsif ( defined $udp_conn ) {
# accept localport:remotehost:remoteport, or localport:remoteport
if ( $udp_conn =~ m/^\d+:\d+$/ ) {
( $udp_spt, $udp_dpt ) = split( /:/, $udp_conn );
$udp_shost = $udp_dhost = "127.0.0.1";
}
elsif ( $udp_conn =~ m/^\d+:[\w\.]+:\d+$/ ) {
( $udp_spt, $udp_dhost, $udp_dpt ) = split( /:/, $udp_conn );
$udp_shost = "";
}
else {
die "UDP port format doesnt match";
}
print "Working in UDP mode.\n" if $verbose;
# bind to udp port
$udp_listener = IO::Socket::INET->new(
Proto => "udp",
LocalPort => $udp_spt,
LocalAddr => $udp_shost,
PeerPort => $udp_dpt,
PeerAddr => $udp_dhost
) or die "Can't bind to UDP port.\n";
print
"Forwarding frames between UDP ports local:$udp_spt, $udp_dhost:$udp_dpt and IOU instance $iou_instance, int $iou_interface_major/$iou_interface_minor - press ^C to exit\n";
$select_handle->add($udp_listener);
while (1) {
my ($readable) =
IO::Select->select( $select_handle, undef, undef, 0.001 );
foreach my $socket (@$readable) {
if ( $socket == $iou_pseudo_sock ) {
# IOU frame received via pseudo ID socket
$iou_pseudo_sock->recv( $iou_recv_data, 1580 );
log_iou_frame( "R:I->U", $iou_recv_data ) if $debug;
$iou_recv_data = unpack( "x8a*", $iou_recv_data );
# send IOU generated frame via udp
$udp_listener->send($iou_recv_data);
write_pcap_dump($iou_recv_data) if $cap_dumper;
log_frame( "S:I->U", $iou_recv_data ) if $debug;
}
else {
$udp_listener->recv( $iou_recv_data, 1580 );
write_pcap_dump($iou_recv_data) if $cap_dumper;
log_frame( "R:U->I", $iou_recv_data ) if $debug;
$iou_router_sock->send(
pack( "a*a*", $iou_header, $iou_recv_data ) );
log_iou_frame( "S:U->I",
pack( "a*a*", $iou_header, $iou_recv_data ) )
if $debug;
}
}
}
}
elsif ( defined $tap ) {
print "Working in TAP mode.\n" if $verbose;
# get file handle
$tap_handle = IO::File->new( "/dev/net/tun", O_RDWR )
or die "Cannot open /dev/net/tun";
# make it tap (not tun)
my $ifr = pack( 'Z16s', $tap, 0x1002 );
ioctl $tap_handle, 0x400454ca, $ifr
or die "Can't ioctl() on device $tap: $!";
print
"Forwarding frames between TAP interface $tap and IOU instance $iou_instance, int $iou_interface_major/$iou_interface_minor - press ^C to exit\n";
$select_handle->add($tap_handle);
while (1) {
my ($readable) =
IO::Select->select( $select_handle, undef, undef, 0.001 );
foreach my $socket (@$readable) {
if ( $socket == $iou_pseudo_sock ) {
# IOU frame received via pseudo ID socket
$iou_pseudo_sock->recv( $iou_recv_data, 1522 );
log_iou_frame( "R:I->T", $iou_recv_data ) if $debug;
$iou_recv_data = unpack( "x8a*", $iou_recv_data );
# send IOU generated frame via udp
$tap_handle->syswrite($iou_recv_data);
write_pcap_dump($iou_recv_data) if $cap_dumper;
log_frame( "S:I->T", $iou_recv_data ) if $debug;
}
else {
$tap_handle->sysread( $iou_recv_data, 1522 );
write_pcap_dump($iou_recv_data) if $cap_dumper;
log_frame( "R:T->I", $iou_recv_data ) if $debug;
$iou_router_sock->send(
pack( "a*a*", $iou_header, $iou_recv_data ) );
log_iou_frame( "S:T->I",
pack( "a*a*", $iou_header, $iou_recv_data ) )
if $debug;
}
}
}
}
else {
# catchall, we really shouldnt land here
print "No valid mode of operation selected.\n\n$help";
caught_sigint();
}
sub caught_sigint {
print "\n...stopped.\n";
print "Cleaning up.\n";
$select_handle->remove( $select_handle->handles );
if ( defined $pcap ) {
Net::Pcap::breakloop($pcap);
Net::Pcap::close($pcap);
print "Closed pcap receiver loop.\n" if $verbose;
}
if ( defined $udp_listener ) {
$udp_listener->close;
print "Closed udp listener.\n" if $verbose;
}
if ($tap_handle) {
$tap_handle->close;
print "Closed tap handle.\n" if $verbose;
}
if ($cap_handle) {
Net::Pcap::pcap_dump_flush($cap_dumper);
Net::Pcap::pcap_dump_close($cap_dumper);
print "Closed dump file.\n" if $verbose;
}
$iou_pseudo_sock->close;
$iou_router_sock->close;
exit(0);
}
sub log_frame {
my ( $direction, $frame ) = @_;
return if ( length($frame) < 14 );
# Print direction, source mac, destination mac and ethertype
print "$direction S ",
join( ":", unpack( "x6(H2)6", $frame ) ),
" D ",
join( ":", unpack( "(H2)6", $frame ) ),
" T ",
unpack( "x12H4", $frame ),
"\n";
}
sub log_iou_frame {
my ( $direction, $frame ) = @_;
return if ( length($frame) < 22 );
# Print direction, IOU header, source mac, destination mac and ethertype
print "$direction IOU ",
unpack( "H16", $frame ),
" S ",
join( ":", unpack( "x14(H2)6", $frame ) ),
" D ",
join( ":", unpack( "x8(H2)6", $frame ) ),
" T ",
unpack( "x20H4", $frame ),
"\n";
}
sub write_pcap_dump {
my $frame = shift @_;
my %header;
$header{len} = $header{caplen} = length($frame);
( $header{tv_sec}, $header{tv_usec} ) = gettimeofday();
Net::Pcap::pcap_dump( $cap_dumper, \%header, $frame );
Net::Pcap::pcap_dump_flush($cap_dumper);
}