diff --git a/AUTHORS b/AUTHORS index 59e7cca30d0f88264af27a3dc8499f6d4ed4874c..ef2d3d1552df2828da1836be7db023fc1282bbd7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -3,6 +3,10 @@ benjie@lcs.mit.edu polling extensions, Linux kernel patches, stride scheduling, Linux kernel thread, device driver updates +Douglas S. J. DeCouto +decouto@lcs.mit.edu +user level elements + Thomer Gil thomer@lcs.mit.edu dynamic IP routing table diff --git a/doc/element2man.pl b/doc/element2man.pl index bcebd5d064d730fe4ea2ef3f3855b8c65d13fef9..4c546a7cd042bd42beca8b6c128b48916819339d 100755 --- a/doc/element2man.pl +++ b/doc/element2man.pl @@ -12,10 +12,10 @@ # version. For more information, see the `COPYRIGHT' file in the source # distribution. -my(%section_is_array) = ( 'h' => 1, 'a' => 1 ); +my(%section_is_array) = ( 'h' => 1, 'a' => 1, 'page' => 1 ); my $directory; my $section = 'n'; -my(@all_created, %class_name, %processing); +my(@all_outnames, %all_outsections, %class_name, %processing); my(%processing_constants) = ( 'AGNOSTIC' => 'a/a', 'PUSH' => 'h/h', 'PULL' => 'l/l', @@ -42,24 +42,19 @@ my $prologue = <<'EOD;'; EOD; chomp $prologue; -sub nroffize ($;$$$) { - my($t, $embolden, $srelated, $related_source) = @_; +sub nroffize ($;$$) { + my($t, $related, $related_source) = @_; my($i); # embolden & manpageize - if (defined $embolden) { - foreach $i (sort { length($b) <=> length($a) } @$embolden) { - $t =~ s{(^|[^\w@/])$i($|[^\w@/])}{$1<B>$i</B>$2}gs; - } - } - if (defined $srelated) { - foreach $i (@$srelated) { - $t =~ s{(^|[^\w@/])$i($|[^\w@/(])}{$1<\#>$i</\#>$2}gs; + if (defined $related) { + foreach $i (@$related) { + $t =~ s{(^|[^\w@/>])$i($|[^\w@/])}{$1<\#>$i</\#>$2}gs; } } # remove emboldening & manpaging on examples - 1 while ($t =~ s{^= (.*)</?[B\#]>}{= $1}gm); + 1 while ($t =~ s{^= (.*)</?[\#]>}{= $1}gm); $t =~ s/\\/\\\\/g; $t =~ s/^(= )?\./$1\\&./gm; @@ -68,7 +63,14 @@ sub nroffize ($;$$$) { $t =~ s/<i>(.*?)<\/i>/\\fI$1\\fP/ig; $t =~ s/<b>(.*?)<\/b>/\\fB$1\\fP/ig; $t =~ s/<tt>(.*?)<\/tt>/\\f(CW$1\\fP/ig; - $t =~ s/<\#>(.*?)<\/\#>(\S*)\s*/\n.M $1 $related_source->{$1} $2\n/g; + $t =~ s{<\#>(.*?)<\/\#>(\S*)(\s*)}{ + if ($related_source->{$1}) { + "\n.M $1 \"$related_source->{$1}\" $2\n"; + } else { + "\\fB$1\\fP$2$3"; + } + }eg; + $t =~ s{\n\.M (\S+) \"(\S+)\" \(\2\)\n}{\n.M $1 "$2"\n}g; 1 while ($t =~ s/^\.PP\n\.PP\n/.PP\n/gm); $t =~ s/^= (.*\n)/.nf\n$1.fi\n/mg; $t =~ s/^\.fi\n\.nf\n//mg; @@ -100,7 +102,8 @@ sub process_comment ($$) { $x{$1} .= "$2\n"; } } - + + # snarf class names my(@classes, %classes); while ($x{'c'} =~ /^\s*(\w+)\(/mg) { # configuration arguments section push @classes, $1 if !exists $classes{$1}; @@ -114,36 +117,76 @@ sub process_comment ($$) { print STDERR "$filename: no class definitions\n (did you forget `()' in the =c section?)\n"; return; } - my($classes_plural) = (@classes == 1 ? '' : 's'); - my($classes) = join(', ', @classes); + + # output filenames might be specified in 'page' section + my(@outfiles) = @classes; + my(@outsections) = ($section) x @classes; + if ($x{'page'}) { + @outfiles = (); + @outsections = (); + foreach $i (map(split(/\s+/), @{$x{'page'}})) { + if ($i =~ /^(.*)\((.*)\)$/) { + push @outfiles, $1; + push @outsections, $2; + } else { + push @outfiles, $i; + push @outsections, $section; + } + } + } # open new output file if necessary + my($main_outname); if ($directory) { - if (!open(OUT, ">$directory/$classes[0].$section")) { - print STDERR "$directory/$classes[0].$section: $!\n"; + $main_outname = "$directory/$outfiles[0].$outsections[0]"; + if (!open(OUT, ">$main_outname")) { + print STDERR "$main_outname: $!\n"; return; } } - push @all_created, $classes[0]; - + push @all_outfiles, $outfiles[0]; + $all_outsections{$outfiles[0]} = $outsections[0]; + + # front matter + my($classes_text) = join(', ', @classes); + my($oneliner) = ($x{'desc'} ? $x{'desc'} : (@classes == 1 ? "Click element" : "Click elements")); + my($outfiles_text) = join(', ', @outfiles); + print OUT <<"EOD;"; .\\" -*- mode: nroff -*- .\\" Generated by \`element2man.pl' from \`$filename' $prologue -.TH "\U$classes\E" $section "$today" "Click" +.TH "\U$outfiles_text\E" $outsections[0] "$today" "Click" .SH "NAME" -$classes \- Click element$classes_plural +$classes_text \- $oneliner EOD; + # prepare related + my(@related, @srelated, %related_source, %srelated_source); + if ($x{'a'}) { + foreach $i (map(split(/\s+/), @{$x{'a'}})) { + if ($i =~ /^(.*)\((.*)\)$/) { + push @related, $1; + $related_source{$1} = $2; + } else { + push @related, $i; + $related_source{$i} = 'n'; + } + } + } + @srelated = sort { length($b) <=> length($a) } (@related, @classes); + %srelated_source = %related_source; + map(delete $srelated_source{$_}, @classes); + if ($x{'c'}) { print OUT ".SH \"SYNOPSIS\"\n"; while ($x{'c'} =~ /^\s*(\S.*)$/mg) { - print OUT nroffize($1, \@classes), "\n.br\n"; + print OUT nroffize($1, \@srelated), "\n.br\n"; } } - if (@classes == 1 && $processing{$classes}) { - my $p = process_processing($processing{$classes}); + if (@classes == 1 && $processing{$classes[0]}) { + my $p = process_processing($processing{$classes[0]}); if ($p) { print OUT ".SH \"PROCESSING TYPE\"\n"; print OUT nroffize($p), "\n"; @@ -155,33 +198,19 @@ EOD; print OUT nroffize($x{'io'}); } - my(@related, @srelated, %related_source); - if ($x{'a'}) { - foreach $i (map(split(/\s+/), @{$x{'a'}})) { - if ($i =~ /^(.*)\((.*)\)$/) { - push @related, $1; - $related_source{$1} = $2; - } else { - push @related, $i; - $related_source{$i} = 'n'; - } - } - @srelated = sort { length($b) <=> length($a) } @related; - } - if ($x{'d'}) { print OUT ".SH \"DESCRIPTION\"\n"; - print OUT nroffize($x{'d'}, \@classes, \@srelated, \%related_source); + print OUT nroffize($x{'d'}, \@srelated, \%srelated_source); } if ($x{'n'}) { print OUT ".SH \"NOTES\"\n"; - print OUT nroffize($x{'n'}, \@classes, \@srelated, \%related_source); + print OUT nroffize($x{'n'}, \@srelated, \%srelated_source); } if ($x{'e'}) { print OUT ".SH \"EXAMPLES\"\n"; - print OUT nroffize($x{'e'}, \@classes, \@srelated, \%related_source); + print OUT nroffize($x{'e'}, \@srelated, \%srelated_source); } if ($x{'h'} && @{$x{'h'}}) { @@ -206,12 +235,14 @@ EOD; # close output file & make links if appropriate if ($directory) { close OUT; - foreach $i (@classes[1..$#classes]) { - unlink("$directory/$i.$section"); - if (link "$directory/$classes[0].$section", "$directory/$i.$section") { - push @all_created, $i; + for ($i = 1; $i < @outfiles; $i++) { + my($outname) = "$directory/$outfiles[$i].$outsections[$i]"; + unlink($outname); + if (link $main_outname, $outname) { + push @all_outfiles, $outfiles[$i]; + $all_outsections{$outfiles[$i]} = $outsections[$i]; } else { - print STDERR "$directory/$i.$section: $!\n"; + print STDERR "$outname: $!\n"; } } } @@ -323,13 +354,13 @@ This page lists all Click element classes that have manual page documentation. .SH "SEE ALSO" .nh EOD; - @all_created = sort @all_created; - my($last) = pop @all_created; - print OUT map(".M $_ n ,\n", @all_created); - print OUT ".M $last n\n.hy\n"; + @all_outfiles = sort @all_outfiles; + my($last) = pop @all_outfiles; + print OUT map(".M $_ $all_outsections{$_} ,\n", @all_outfiles); + print OUT ".M $last $all_outsections{$last}\n.hy\n"; close OUT if $directory; } -if ($elementlist && @all_created) { +if ($elementlist && @all_outfiles) { make_elementlist(); } diff --git a/elements/ip/ipprint.cc b/elements/ip/ipprint.cc index 3a098cefa4e9ab49a6bd14dbab99ec01329921c4..a5410f776c0d58967c56ec16fd0951f30e95ab81 100644 --- a/elements/ip/ipprint.cc +++ b/elements/ip/ipprint.cc @@ -48,7 +48,7 @@ IPPrint::configure(const Vector<String> &conf, ErrorHandler* errh) if (cp_va_parse(conf, this, errh, cpString, "label", &_label, cpOptional, - cpBool, "print packet contents in hex", &_hex, + cpBool, "print packet contents in hex?", &_hex, cpInteger, "number of bytes to dump", &_bytes, cpEnd) < 0) return -1; diff --git a/elements/linuxmodule/fromdevice.hh b/elements/linuxmodule/fromdevice.hh index b4b0522e5afffe5ed0e09d6254f28fc5b66a6ca4..3f82fb4fdc2beecc7b12377ba70e384f52d02e16 100644 --- a/elements/linuxmodule/fromdevice.hh +++ b/elements/linuxmodule/fromdevice.hh @@ -5,6 +5,11 @@ * =c * FromDevice(DEVNAME) * =d + * + * This manual page describes the Linux kernel module version of the + * FromDevice element. For the user-level element, read the FromDevice.u + * manual page. + * * Intercepts all packets received by the Linux network interface * named DEVNAME and pushes them out output 0. * The packets include the link-level header. @@ -17,12 +22,11 @@ * This is bad for performance. If you care about performance and have a * polling-capable device, use PollDevice instead. * - * This element is only available inside the kernel module. - * * =a PollDevice * =a ToDevice * =a FromLinux - * =a ToLinux */ + * =a ToLinux + * =a FromDevice.u */ #include "anydevice.hh" diff --git a/elements/linuxmodule/todevice.hh b/elements/linuxmodule/todevice.hh index 10fad5a1682800e1cc1556b186130f9d6a948247..1091f76b1e5da3e52903a7ac6682501f2cd22c76 100644 --- a/elements/linuxmodule/todevice.hh +++ b/elements/linuxmodule/todevice.hh @@ -5,6 +5,10 @@ * =c * ToDevice(DEVNAME) * =d + * + * This manual page describes the Linux kernel module version of the ToDevice + * element. For the user-level element, read the ToDevice.u(n) manual page. + * * Sends packets out the Linux network interface named DEVNAME. * * Packets must have a link header. For ethernet, ToDevice @@ -21,13 +25,11 @@ * we depend on the net driver's send operation for synchronization (e.g. * tulip send operation uses a bit lock). * - * This element is only available inside the kernel module. - * * =a FromDevice * =a PollDevice * =a FromLinux * =a ToLinux - */ + * =a ToDevice.u */ #include "anydevice.hh" diff --git a/elements/userlevel/fromdevice.cc b/elements/userlevel/fromdevice.cc new file mode 100644 index 0000000000000000000000000000000000000000..1d6510e3a2cfd066c422fdb768dfb724791a0b5e --- /dev/null +++ b/elements/userlevel/fromdevice.cc @@ -0,0 +1,281 @@ +/* + * fromdevice.{cc,hh} -- element reads packets live from network via pcap + * Douglas S. J. DeCouto, Eddie Kohler, John Jannotti + * + * Copyright (c) 1999-2000 Massachusetts Institute of Technology. + * + * This software is being provided by the copyright holders under the GNU + * General Public License, either version 2 or, at your discretion, any later + * version. For more information, see the `COPYRIGHT' file in the source + * distribution. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include "fromdevice.hh" +#include "todevice.hh" +#include "error.hh" +#include "packet.hh" +#include "confparse.hh" +#include "elements/standard/scheduleinfo.hh" +#include "glue.hh" +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#if FROMDEVICE_LINUX +# include <sys/socket.h> +# include <sys/ioctl.h> +# include <net/if.h> +# include <net/if_packet.h> +# include <features.h> +# if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1 +# include <netpacket/packet.h> +# include <net/ethernet.h> +# else +# include <linux/if_packet.h> +# include <linux/if_ether.h> +# endif +#endif + +FromDevice::FromDevice() + : _promisc(0), _packetbuf_size(0) +{ + add_output(); +#if FROMDEVICE_PCAP + _pcap = 0; +#endif +#if FROMDEVICE_LINUX + _fd = -1; + _packetbuf = 0; +#endif +} + +FromDevice::~FromDevice() +{ + uninitialize(); +} + +FromDevice * +FromDevice::clone() const +{ + return new FromDevice; +} + +int +FromDevice::configure(const Vector<String> &conf, ErrorHandler *errh) +{ + bool promisc = false; + _packetbuf_size = 2048; + if (cp_va_parse(conf, this, errh, + cpString, "interface name", &_ifname, + cpOptional, + cpBool, "be promiscuous?", &promisc, +#if FROMDEVICE_LINUX + cpUnsigned, "maximum packet length", &_packetbuf_size, +#endif + cpEnd) < 0) + return -1; + if (_packetbuf_size > 8192 || _packetbuf_size < 128) + return errh->error("maximum packet length out of range"); + _promisc = promisc; + return 0; +} + +#if FROMDEVICE_LINUX +int +FromDevice::open_packet_socket(String ifname, ErrorHandler *errh) +{ + int fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); + if (fd == -1) + return errh->error("%s: socket: %s", ifname.cc(), strerror(errno)); + + // get interface index + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname.cc(), sizeof(ifr.ifr_name)); + int res = ioctl(fd, SIOCGIFINDEX, &ifr); + if (res != 0) { + close(fd); + return errh->error("%s: SIOCGIFINDEX: %s", ifname.cc(), strerror(errno)); + } + int ifindex = ifr.ifr_ifindex; + + // bind to the specified interface. from packet man page, only + // sll_protocol and sll_ifindex fields are used; also have to set + // sll_family + sockaddr_ll sa; + memset(&sa, 0, sizeof(sa)); + sa.sll_family = AF_PACKET; + sa.sll_protocol = htons(ETH_P_ALL); + sa.sll_ifindex = ifindex; + res = bind(fd, (struct sockaddr *)&sa, sizeof(sa)); + if (res != 0) { + close(fd); + return errh->error("%s: bind: %s", ifname.cc(), strerror(errno)); + } + + // nonblocking I/O on the packet socket so we can poll + fcntl(fd, F_SETFL, O_NONBLOCK); + + return fd; +} + +int +FromDevice::set_promiscuous(int fd, String ifname, bool promisc) +{ + // get interface flags + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname.cc(), sizeof(ifr.ifr_name)); + int res = ioctl(fd, SIOCGIFFLAGS, &ifr); + if (res != 0) + return -2; + int was_promisc = (ifr.ifr_flags & IFF_PROMISC ? 1 : 0); + + // set or reset promiscuous flag + if (was_promisc != promisc) { + ifr.ifr_flags = (promisc ? ifr.ifr_flags | IFF_PROMISC : ifr.ifr_flags & ~IFF_PROMISC); + res = ioctl(fd, SIOCSIFFLAGS, &ifr); + if (res != 0) + return -3; + } + + return was_promisc; +} +#endif + +int +FromDevice::initialize(ErrorHandler *errh) +{ + if (!_ifname) + return errh->error("interface not set"); + + /* + * Later versions of pcap distributed with linux (e.g. the redhat + * linux pcap-0.4-16) want to have a filter installed before they + * will pick up any packets. + */ + +#if FROMDEVICE_PCAP + + assert(!_pcap); + char *ifname = _ifname.mutable_c_str(); + char ebuf[PCAP_ERRBUF_SIZE]; + _pcap = pcap_open_live(ifname, + 12000, /* XXX snaplen */ + _promisc, + 1, /* timeout: don't wait for packets */ + ebuf); + if (!_pcap) + return errh->error("%s: %s", ifname, ebuf); + + // nonblocking I/O on the packet socket so we can poll + int fd = pcap_fileno(_pcap); + fcntl(fd, F_SETFL, O_NONBLOCK); + + bpf_u_int32 netmask; + bpf_u_int32 localnet; + if (pcap_lookupnet(ifname, &localnet, &netmask, ebuf) < 0) { + errh->warning("%s: %s", ifname, ebuf); + } + + struct bpf_program fcode; + /* + * assume we can use 0 pointer for program string and get ``empty'' + * filter program. + */ + if (pcap_compile(_pcap, &fcode, 0, 0, netmask) < 0) { + return errh->error("%s: %s", ifname, pcap_geterr(_pcap)); + } + + if (pcap_setfilter(_pcap, &fcode) < 0) { + return errh->error("%s: %s", ifname, pcap_geterr(_pcap)); + } + +#elif FROMDEVICE_LINUX + + _fd = open_packet_socket(_ifname, errh); + if (_fd < 0) return -1; + + int promisc_ok = set_promiscuous(_fd, _ifname, _promisc); + if (promisc_ok < 0) { + if (_promisc) + errh->warning("cannot set promiscuous mode"); + _was_promisc = -1; + } else + _was_promisc = promisc_ok; + + // create packet buffer + _packetbuf = new unsigned char[_packetbuf_size]; + if (!_packetbuf) { + close(_fd); + return errh->error("out of memory"); + } + +#else + + return errh->error("FromDevice is not supported on this platform"); + +#endif + + ScheduleInfo::join_scheduler(this, errh); + return 0; +} + +void +FromDevice::uninitialize() +{ +#if FROMDEVICE_PCAP + if (_pcap) + pcap_close(_pcap); + _pcap = 0; +#endif +#if FROMDEVICE_LINUX + if (_was_promisc >= 0) + set_promiscuous(_fd, _ifname, _was_promisc); + if (_fd >= 0) + close(_fd); + _fd = -1; + delete[] _packetbuf; + _packetbuf = 0; +#endif +} + +#if FROMDEVICE_PCAP +void +FromDevice::get_packet(u_char* clientdata, + const struct pcap_pkthdr* pkthdr, + const u_char* data) +{ + FromDevice *fd = (FromDevice *) clientdata; + int length = pkthdr->caplen; + Packet* p = Packet::make(data, length); + fd->output(0).push(p); +} +#endif + +void +FromDevice::run_scheduled() +{ +#if FROMDEVICE_PCAP + // Read and push() at most one packet. + pcap_dispatch(_pcap, 1, FromDevice::get_packet, (u_char *) this); +#endif +#if FROMDEVICE_LINUX + struct sockaddr_ll sa; + //memset(&sa, 0, sizeof(sa)); + socklen_t fromlen = sizeof(sa); + int len = recvfrom(_fd, _packetbuf, _packetbuf_size, 0, + (sockaddr *)&sa, &fromlen); + if (len > 0) { + if (sa.sll_pkttype != PACKET_OUTGOING) + output(0).push(Packet::make(_packetbuf, len)); + } else if (errno != EAGAIN) + click_chatter("FromDevice(%s): recvfrom: %s", _ifname.cc(), strerror(errno)); +#endif + reschedule(); +} + +EXPORT_ELEMENT(FromDevice) diff --git a/elements/userlevel/fromdevice.hh b/elements/userlevel/fromdevice.hh new file mode 100644 index 0000000000000000000000000000000000000000..e5c5ef72535232ed9bf3bef93dda22c3a337c41e --- /dev/null +++ b/elements/userlevel/fromdevice.hh @@ -0,0 +1,100 @@ +#ifndef FROMDEVICE_HH +#define FROMDEVICE_HH + +/* + * =page FromDevice.u + * =c + * FromDevice(DEVNAME [, PROMISC? [, MAXPACKETSIZE]]) + * =d + * + * This manual page describes the user-level version of the FromDevice + * element. For the Linux kernel module element, read the FromDevice(n) manual + * page. + * + * Reads packets from the kernel that were received on the network controller + * named DEVNAME. Puts the device in promiscuous mode if PROMISC? (a Boolean) + * is true. PROMISC? defaults to false. On some systems, packets larger than + * MAXPACKETSIZE will be truncated; default MAXPACKETSIZE is 2048 bytes. + * + * The kernel networking code sees all of the packets that FromDevice + * produces; be careful that at most one of Click and the kernel forwards each + * packet. + * + * Under Linux, a FromDevice element will not receive packets sent by a + * ToDevice element for the same device. Under other operating systems, your + * mileage may vary. + * + * =e + * = FromDevice(eth0, 0) -> ... + * + * =a ToDevice.u + * =a FromDump + * =a ToDump + * =a FromDevice */ + +#include "element.hh" + +#ifdef __linux__ +# define FROMDEVICE_LINUX 1 +#elif defined(HAVE_PCAP) +# define FROMDEVICE_PCAP 1 +extern "C" { +# include <pcap.h> +} +#endif + +class FromDevice : public Element { + + String _ifname; + bool _promisc : 1; + int _was_promisc : 2; + int _packetbuf_size; + +#if FROMDEVICE_LINUX + int _fd; + unsigned char *_packetbuf; +#endif +#if FROMDEVICE_PCAP + pcap_t* _pcap; + static void get_packet(u_char *, const struct pcap_pkthdr *, + const u_char *); + int do_select(int waitms); +#endif + + public: + + enum ConfigurePhase { + CONFIGURE_PHASE_FROMDEVICE = CONFIGURE_PHASE_DEFAULT, + CONFIGURE_PHASE_TODEVICE = CONFIGURE_PHASE_FROMDEVICE + 1 + }; + + FromDevice(); + ~FromDevice(); + + const char *class_name() const { return "FromDevice"; } + const char *processing() const { return PUSH; } + + FromDevice *clone() const; + int configure_phase() const { return CONFIGURE_PHASE_FROMDEVICE; } + int configure(const Vector<String> &, ErrorHandler *); + int initialize(ErrorHandler *); + void uninitialize(); + + String ifname() const { return _ifname; } +#if FROMDEVICE_PCAP + pcap_t *pcap() const { return _pcap; } +#endif +#if FROMDEVICE_LINUX + int fd() const { return _fd; } +#endif + + void run_scheduled(); + +#if FROMDEVICE_LINUX + static int open_packet_socket(String, ErrorHandler *); + static int set_promiscuous(int, String, bool); +#endif + +}; + +#endif diff --git a/elements/userlevel/todevice.cc b/elements/userlevel/todevice.cc new file mode 100644 index 0000000000000000000000000000000000000000..d80d75c0283170f3df62482966a62008f295fad4 --- /dev/null +++ b/elements/userlevel/todevice.cc @@ -0,0 +1,182 @@ +/* + * todevice.{cc,hh} -- element writes packets to network via pcap library + * Douglas S. J. DeCouto, Eddie Kohler, John Jannotti + * + * Copyright (c) 1999-2000 Massachusetts Institute of Technology. + * + * This software is being provided by the copyright holders under the GNU + * General Public License, either version 2 or, at your discretion, any later + * version. For more information, see the `COPYRIGHT' file in the source + * distribution. + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include "todevice.hh" +#include "error.hh" +#include "etheraddress.hh" +#include "confparse.hh" +#include "router.hh" +#include "elements/standard/scheduleinfo.hh" + +#include <stdio.h> +#include <assert.h> +#include <unistd.h> +#include <errno.h> + +#if TODEVICE_BSD_DEV_BPF +# include <fcntl.h> +# include <sys/types.h> +# include <sys/socket.h> +# include <sys/ioctl.h> +# include <net/if.h> +#elif TODEVICE_LINUX +# include <sys/socket.h> +# include <sys/ioctl.h> +# include <net/if.h> +# include <net/if_packet.h> +# include <features.h> +# if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1 +# include <netpacket/packet.h> +# else +# include <linux/if_packet.h> +# endif +#endif + +ToDevice::ToDevice() + : Element(1, 0), _fd(-1), _my_fd(false) +{ +#if TODEVICE_BSD_DEV_BPF + _pcap = 0; +#endif +} + +ToDevice::~ToDevice() +{ + uninitialize(); +} + +ToDevice * +ToDevice::clone() const +{ + return new ToDevice; +} + +int +ToDevice::configure(const Vector<String> &conf, ErrorHandler *errh) +{ + if (cp_va_parse(conf, this, errh, + cpString, "interface name", &_ifname, + 0) < 0) + return -1; + if (!_ifname) + return errh->error("interface not set"); + return 0; +} + +int +ToDevice::initialize(ErrorHandler *errh) +{ + _fd = -1; + +#if TODEVICE_BSD_DEV_BPF + + /* pcap_open_live() doesn't open for writing. */ + for (int i = 0; i < 16 && _fd < 0; i++) { + char tmp[64]; + sprintf(tmp, "/dev/bpf%d", i); + _fd = open(tmp, 1); + } + if (_fd < 0) + return(errh->error("can't open a bpf")); + + struct ifreq ifr; + strncpy(ifr.ifr_name, _ifname, sizeof(ifr.ifr_name)); + ifr.ifr_name[sizeof(ifr.ifr_name) - 1] = 0; + if (ioctl(_fd, BIOCSETIF, (caddr_t)&ifr) < 0) + return errh->error("BIOCSETIF %s failed", ifr.ifr_name); + +#elif TODEVICE_LINUX + + // find a FromDevice and reuse its socket if possible + for (int ei = 0; ei < router()->nelements() && _fd < 0; ei++) { + Element *e = router()->element(ei); + ToDevice *td = (ToDevice *)e->cast("FromDevice"); + if (td && td->ifname() == _ifname && td->fd() >= 0) { + _fd = td->fd(); + _my_fd = false; + } + } + + if (_fd < 0) { + _fd = FromDevice::open_packet_socket(_ifname, errh); + _my_fd = true; + } + + if (_fd < 0) return -1; + +#else + + return errh->error("ToDevice is not supported on this platform"); + +#endif + + if (input_is_pull(0)) + ScheduleInfo::join_scheduler(this, errh); + return 0; +} + +void +ToDevice::uninitialize() +{ +#if TODEVICE_BSD_DEV_BPF + if (_pcap) pcap_close(_pcap); + _pcap = 0; +#endif +#if TODEVICE_LINUX + if (_fd >= 0 && _my_fd) close(_fd); + _fd = -1; +#endif + unschedule(); +} + +void +ToDevice::send_packet(Packet *p) +{ + int retval; + const char *syscall; + +#if TODEVICE_WRITE + retval = (write(_fd, p->data(), p->length()) > 0 ? 0 : -1); + syscall = "write"; +#elif TODEVICE_SEND + retval = send(_fd, p->data(), p->length(), 0); + syscall = "send"; +#else + retval = 0; +#endif + + if (retval < 0) + click_chatter("ToDevice(%d) %s: %s", _ifname.cc(), syscall, strerror(errno)); + p->kill(); +} + +void +ToDevice::push(int, Packet *p) +{ + assert(p->length() >= 14); + send_packet(p); +} + +void +ToDevice::run_scheduled() +{ + // XXX reduce tickets when idle + if (Packet *p = input(0).pull()) + send_packet(p); + reschedule(); +} + +ELEMENT_REQUIRES(FromDevice) +EXPORT_ELEMENT(ToDevice) diff --git a/elements/userlevel/todevice.hh b/elements/userlevel/todevice.hh new file mode 100644 index 0000000000000000000000000000000000000000..3f0b6a8806a6da7f7a27f25aaa7e9487ce7b1da3 --- /dev/null +++ b/elements/userlevel/todevice.hh @@ -0,0 +1,93 @@ +#ifndef TODEVICE_HH +#define TODEVICE_HH +#include "element.hh" +#include "string.hh" +#include "elements/userlevel/fromdevice.hh" + +/* + * =page ToDevice.u + * =c + * ToDevice(DEVNAME) + * =d + * + * This manual page describes the user-level version of the ToDevice element. + * For the Linux kernel module element, read the ToDevice(n) manual page. + * + * Pulls packets and sends them out the named device using + * Berkeley Packet Filters (or Linux equivalent). + * + * Packets sent via ToDevice should already have a link-level + * header prepended. This means that ARP processing, + * for example, must already have been done. + * + * Under Linux, a FromDevice element will not receive packets sent by a + * ToDevice element for the same device. Under other operating systems, your + * mileage may vary. + * + * This element is only available at user level. + * + * =a FromDevice.u + * =a FromDump + * =a ToDump + * =a ToDevice */ + +#ifdef HAVE_PCAP +extern "C" { +# include <pcap.h> +} +#else +# include "fakepcap.h" +#endif + +#ifdef HAVE_PCAP +# if defined(__FreeBSD__) || defined(__OpenBSD__) +# define TODEVICE_BSD_DEV_BPF 1 +# define TODEVICE_WRITE 1 +# endif +#endif +#if defined(__linux__) +# define TODEVICE_LINUX 1 +# define TODEVICE_SEND 1 +#endif + +/* + * Write packets to the ethernet via the bpf. + * Expects packets that already have an ether header. + * Can push or pull. + */ + +class ToDevice : public Element { + + String _ifname; + int _fd; + bool _my_fd; + +#if TODEVICE_BSD_DEV_BPF + pcap_t *_pcap; +#endif + + void send_packet(Packet *); + + public: + + ToDevice(); + ~ToDevice(); + + const char *class_name() const { return "ToDevice"; } + const char *processing() const { return AGNOSTIC; } + + ToDevice *clone() const; + int configure_phase() const { return FromDevice::CONFIGURE_PHASE_TODEVICE; } + int configure(const Vector<String> &, ErrorHandler *); + int initialize(ErrorHandler *); + void uninitialize(); + + String ifname() const { return _ifname; } + int fd() const { return _fd; } + + void push(int port, Packet *); + void run_scheduled(); + +}; + +#endif diff --git a/lib/router.cc b/lib/router.cc index ad7398e3878c4dffb9de5c5dba4e3a91b5155fab..44396040e277ac446aa5b850252e344b283088ef 100644 --- a/lib/router.cc +++ b/lib/router.cc @@ -813,7 +813,7 @@ Router::initialize(ErrorHandler *errh) // If there were errors, uninitialize any elements that we initialized // successfully and return -1 (error). Otherwise, we're all set! if (!all_ok) { - errh->error("router could not be initialized"); + errh->error("Router could not be initialized!"); for (int i = 0; i < _elements.size(); i++) if (element_ok[i]) _elements[i]->uninitialize();