Page MenuHomec4science

todevice.u.cc
No OneTemporary

File Metadata

Created
Fri, Jul 4, 05:55

todevice.u.cc

/*
* todevice.{cc,hh} -- element writes packets to network via pcap library
* Douglas S. J. De Couto, Eddie Kohler, John Jannotti
*
* Copyright (c) 1999-2000 Massachusetts Institute of Technology
* Copyright (c) 2005-2008 Regents of the University of California
* Copyright (c) 2011 Meraki, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, subject to the conditions
* listed in the Click LICENSE file. These conditions include: you must
* preserve this copyright notice, and you cannot mention the copyright
* holders in advertising related to the Software without their permission.
* The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
* notice is a summary of the Click LICENSE file; the license in that file is
* legally binding.
*/
#include <click/config.h>
#if HAVE_NET_BPF_H
# include <sys/types.h>
# include <sys/time.h>
# include <net/bpf.h>
# define PCAP_DONT_INCLUDE_PCAP_BPF_H 1
#endif
#include "todevice.hh"
#include <click/error.hh>
#include <click/etheraddress.hh>
#include <click/args.hh>
#include <click/router.hh>
#include <click/standard/scheduleinfo.hh>
#include <click/packet_anno.hh>
#include <click/straccum.hh>
#include <stdio.h>
#include <unistd.h>
#if TODEVICE_ALLOW_DEVBPF
# include <fcntl.h>
# include <sys/types.h>
# include <sys/socket.h>
# include <sys/ioctl.h>
# include <net/if.h>
#endif
#if TODEVICE_ALLOW_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
#if TODEVICE_ALLOW_NETMAP
//# include <sys/mman.h>
#endif
CLICK_DECLS
ToDevice::ToDevice()
: _task(this), _timer(&_task), _q(0), _pulls(0)
{
#if TODEVICE_ALLOW_PCAP
_pcap = 0;
_my_pcap = false;
#endif
#if TODEVICE_ALLOW_LINUX || TODEVICE_ALLOW_DEVBPF || TODEVICE_ALLOW_PCAPFD || TODEVICE_ALLOW_NETMAP
_fd = -1;
_my_fd = false;
#endif
}
ToDevice::~ToDevice()
{
}
int
ToDevice::configure(Vector<String> &conf, ErrorHandler *errh)
{
String method;
_burst = 1;
_debug = false;
_verb_debug = false;
if (Args(conf, this, errh)
.read_mp("DEVNAME", _ifname)
.read("DEBUG", _debug)
.read("VERB_DEBUG", _verb_debug)
.read("METHOD", WordArg(), method)
.read("BURST", _burst)
.complete() < 0)
return -1;
if (!_ifname)
return errh->error("interface not set");
if (_burst <= 0)
return errh->error("bad BURST");
if (method == "") {
#if TODEVICE_ALLOW_PCAP || TODEVICE_ALLOW_PCAPFD || TODEVICE_ALLOW_LINUX || TODEVICE_ALLOW_DEVBPF || TODEVICE_ALLOW_NETMAP
_method = method_default;
#else
return errh->error("cannot send packets on this platform");
#endif
}
#if TODEVICE_ALLOW_PCAP
else if (method == "PCAP")
_method = method_pcap;
#endif
#if TODEVICE_ALLOW_LINUX
else if (method == "LINUX")
_method = method_linux;
#endif
#if TODEVICE_ALLOW_DEVBPF
else if (method == "DEVBPF")
_method = method_devbpf;
#endif
#if TODEVICE_ALLOW_PCAPFD
else if (method == "PCAPFD")
_method = method_pcapfd;
#endif
#if TODEVICE_ALLOW_NETMAP
else if (method == "NETMAP")
_method = method_netmap;
#endif
else
return errh->error("bad METHOD");
return 0;
}
FromDevice *
ToDevice::find_fromdevice() const
{
Router *r = router();
for (int ei = 0; ei < r->nelements(); ++ei) {
FromDevice *fd = (FromDevice *) r->element(ei)->cast("FromDevice");
if (fd && fd->ifname() == _ifname && fd->fd() >= 0)
return fd;
}
return 0;
}
int
ToDevice::initialize(ErrorHandler *errh)
{
_timer.initialize(this);
FromDevice *fd = find_fromdevice();
if (fd && _method == method_default) {
#if FROMDEVICE_ALLOW_NETMAP && TODEVICE_ALLOW_NETMAP
if (fd->netmap())
_method = method_netmap;
#endif
#if FROMDEVICE_ALLOW_PCAP && TODEVICE_ALLOW_PCAP
if (fd->pcap())
_method = method_pcap;
#endif
#if FROMDEVICE_ALLOW_LINUX && TODEVICE_ALLOW_LINUX
if (fd->linux_fd() >= 0)
_method = method_linux;
#endif
}
#if TODEVICE_ALLOW_NETMAP
// first choice is netmap by default
if (_method == method_default || _method == method_netmap) {
if (fd && fd->netmap()) { // fromdevice already open, reuse
_fd = fd->fd();
_netmap = *fd->netmap();
} else {
_fd = _netmap.open(_ifname, _method == method_netmap, errh);
if (_fd >= 0) {
_my_fd = true;
add_select(_fd, SELECT_READ); // NB NOT writable!
} else if (_method == method_netmap)
return -1; // fail
}
if (_fd >= 0) {
_method = method_netmap;
_netmap.initialize_rings_tx(); // no-op
}
}
#endif
#if TODEVICE_ALLOW_PCAP
if (_method == method_default || _method == method_pcap) {
if (fd && fd->pcap())
_pcap = fd->pcap();
else {
_pcap = FromDevice::open_pcap(_ifname, FromDevice::default_snaplen, false, errh);
if (!_pcap)
return -1;
_my_pcap = true;
}
_fd = pcap_fileno(_pcap);
_method = method_pcap;
}
#endif
#if TODEVICE_ALLOW_DEVBPF
if (_method == method_default || _method == method_devbpf) {
/* 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("open /dev/bpf* for write: %s", strerror(errno)));
_my_fd = true;
struct ifreq ifr;
strncpy(ifr.ifr_name, _ifname.c_str(), 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);
# ifdef BIOCSHDRCMPLT
int yes = 1;
if (ioctl(_fd, BIOCSHDRCMPLT, (caddr_t)&yes) < 0)
errh->warning("BIOCSHDRCMPLT %s failed", ifr.ifr_name);
# endif
_method = method_devbpf;
}
#endif
#if TODEVICE_ALLOW_LINUX
if (_method == method_default || _method == method_linux) {
if (fd && fd->linux_fd() >= 0)
_fd = fd->linux_fd();
else {
_fd = FromDevice::open_packet_socket(_ifname, errh);
if (_fd < 0)
return -1;
_my_fd = true;
}
_method = method_linux;
}
#endif
#if TODEVICE_ALLOW_PCAPFD
if (_method == method_default || _method == method_pcapfd) {
FromDevice *fd = find_fromdevice();
if (fd && fd->pcap())
_fd = fd->fd();
else
return errh->error("initialized FromDevice required on this platform");
_method = method_pcapfd;
}
#endif
// check for duplicate writers
void *&used = router()->force_attachment("device_writer_" + _ifname);
if (used)
return errh->error("duplicate writer for device %<%s%>", _ifname.c_str());
used = this;
ScheduleInfo::join_scheduler(this, &_task, errh);
_signal = Notifier::upstream_empty_signal(this, 0, &_task);
return 0;
}
void
ToDevice::cleanup(CleanupStage)
{
#if TODEVICE_ALLOW_PCAP
if (_pcap && _my_pcap)
pcap_close(_pcap);
_pcap = 0;
#endif
#if TODEVICE_ALLOW_NETMAP
if (_fd >= 0 && _my_fd && _method == method_netmap) {
_netmap.close(_fd); // XXX _fd is not really needed
_fd = -1;
}
#endif
#if TODEVICE_ALLOW_LINUX || TODEVICE_ALLOW_DEVBPF || TODEVICE_ALLOW_PCAPFD || TODEVICE_ALLOW_NETMAP
if (_fd >= 0 && _my_fd)
close(_fd);
_fd = -1;
#endif
}
/*
* Linux select marks datagram fd's as writeable when the socket
* buffer has enough space to do a send (sock_writeable() in
* sock.h). BSD select always marks datagram fd's as writeable
* (bpf_poll() in sys/net/bpf.c) This function should behave
* appropriately under both. It makes use of select if it correctly
* tells us when buffers are available, and it schedules a backoff
* timer if buffers are not available.
* --jbicket
*/
int
ToDevice::send_packet(Packet *p)
{
int r = 0;
errno = 0;
#if TODEVICE_ALLOW_NETMAP
if (_method == method_netmap) {
if (_netmap.send_packet(p, noutputs())) { // fail
errno = ENOBUFS;
r = -1;
} else
r = 0;
}
#endif
#if TODEVICE_ALLOW_PCAP
if (_method == method_pcap) {
# if HAVE_PCAP_INJECT
r = pcap_inject(_pcap, p->data(), p->length());
# else
r = pcap_sendpacket(_pcap, p->data(), p->length());
# endif
}
#endif
#if TODEVICE_ALLOW_LINUX
if (_method == method_linux)
r = send(_fd, p->data(), p->length(), 0);
#endif
#if TODEVICE_ALLOW_DEVBPF
if (_method == method_devbpf)
if (write(_fd, p->data(), p->length()) != (ssize_t) p->length())
r = -1;
#endif
#if TODEVICE_ALLOW_PCAPFD
if (_method == method_pcapfd)
if (write(_fd, p->data(), p->length()) != (ssize_t) p->length())
r = -1;
#endif
if (r >= 0) {
if(_verb_debug) click_chatter("[ToDevice %s:%s] send_packet called, success", name().c_str(), Timestamp::now().unparse().c_str());
return 0;
}
else {
if(_verb_debug) click_chatter("[ToDevice %s:%s] send_packet called, error %d", name().c_str(), Timestamp::now().unparse().c_str(), errno ? -errno : -EINVAL);
return errno ? -errno : -EINVAL;
}
}
bool
ToDevice::run_task(Task *)
{
Packet *p = _q;
_q = 0;
int count = 0, r = 0;
if(_verb_debug) click_chatter("[ToDevice %s:%s] run_task called", name().c_str(), Timestamp::now().unparse().c_str());
Timestamp at_run_task = Timestamp::now();
Timestamp pulls;
Timestamp sends;
String out_str;
do {
if (!p) {
++_pulls;
pulls -= Timestamp::now();
if (!(p = input(0).pull())) {
pulls += Timestamp::now();
out_str=", pull is null";
break;
}
pulls += Timestamp::now();
}
sends -= Timestamp::now();
if ((r = send_packet(p)) >= 0) {
sends += Timestamp::now();
_backoff = 0;
checked_output_push(0, p);
++count;
p = 0;
} else {
out_str=", not sent, errno "+String(-r);
sends += Timestamp::now();
break;
}
} while (count < _burst);
if((_burst > 1 && count > 1 && _debug) || _verb_debug) {
Timestamp now = Timestamp::now();
click_chatter("[ToDevice %s:%s] Sent %d packets in a pull in %s milliseconds (pulls %s ms, sends %s ms)%s.", name().c_str(),
now.unparse().c_str(), count, (1000*(now-at_run_task)).unparse().c_str(),
(1000*pulls).unparse().c_str(), (1000*sends).unparse().c_str(), out_str.c_str());
}
if (r == -ENOBUFS || r == -EAGAIN || r == -EDEADLK) {
assert(!_q);
_q = p;
if (!_backoff) {
if(_verb_debug) click_chatter("[ToDevice %s:%s] backing off for first time", name().c_str(), Timestamp::now().unparse().c_str());
_backoff = 1;
add_select(_fd, SELECT_WRITE);
} else {
_timer.schedule_after(Timestamp::make_usec(_backoff));
if (_backoff < 256)
_backoff *= 2;
if (_verb_debug) {
Timestamp now = Timestamp::now();
click_chatter("%p{element} backing off for %d at %p{timestamp}\n", this, _backoff, &now);
}
}
return count > 0;
} else if (r < 0) {
click_chatter("ToDevice(%s at %s): %s", _ifname.c_str(), Timestamp::now().unparse().c_str(), strerror(-r));
checked_output_push(1, p);
}
if (p || _signal)
_task.fast_reschedule();
return count > 0;
}
void
ToDevice::selected(int, int)
{
if(_verb_debug) click_chatter("[ToDevice %s:%s] selected", name().c_str(), Timestamp::now().unparse().c_str());
_task.reschedule();
remove_select(_fd, SELECT_WRITE);
}
String
ToDevice::read_param(Element *e, void *thunk)
{
ToDevice *td = (ToDevice *)e;
switch((uintptr_t) thunk) {
case h_debug:
return String(td->_debug);
case h_signal:
return String(td->_signal);
case h_pulls:
return String(td->_pulls);
case h_q:
return String((bool) td->_q);
default:
return String();
}
}
int
ToDevice::write_param(const String &in_s, Element *e, void *vparam,
ErrorHandler *errh)
{
ToDevice *td = (ToDevice *)e;
String s = cp_uncomment(in_s);
switch ((intptr_t)vparam) {
case h_debug: {
bool debug;
if (!BoolArg().parse(s, debug))
return errh->error("type mismatch");
td->_debug = debug;
break;
}
case h_verb_debug:
{
bool debug;
if (!BoolArg().parse(s, debug))
return errh->error("type mismatch");
td->_verb_debug = debug;
break;
}
}
return 0;
}
int
ToDevice::set_burst_handler(const String &s, Element *e, void *, ErrorHandler *errh)
{
ToDevice* td = static_cast<ToDevice*>(e);
int arg;
if(!cp_integer(s, &arg))
return errh->error("Burst must be an integer");
td->set_burst(arg);
return 0;
}
void
ToDevice::add_handlers()
{
add_task_handlers(&_task);
add_read_handler("debug", read_param, h_debug, Handler::CHECKBOX);
add_read_handler("pulls", read_param, h_pulls);
add_read_handler("signal", read_param, h_signal);
add_read_handler("q", read_param, h_q);
add_write_handler("debug", write_param, h_debug);
add_write_handler("verb_debug", write_param, h_verb_debug);
add_write_handler("burst", set_burst_handler, 0);
}
CLICK_ENDDECLS
ELEMENT_REQUIRES(FromDevice userlevel)
EXPORT_ELEMENT(ToDevice)

Event Timeline