Commit a8031d9f authored by Eddie Kohler's avatar Eddie Kohler

User ToDevice: Support sending packets via pcap.

Luigi Rizzo reports this can be significantly faster on FreeBSD.
Specify the send method via a METHOD keyword.

Simultaneously, update FromDevice to support METHOD.
Signed-off-by: default avatarEddie Kohler <ekohler@gmail.com>
parent fb41a74a
......@@ -34,6 +34,14 @@
of 'madvise', and to 0 if you don't. */
#undef HAVE_DECL_MADVISE
/* Define to 1 if you have the declaration
of 'pcap_inject', and to 0 if you don't. */
#undef HAVE_DECL_PCAP_INJECT
/* Define to 1 if you have the declaration
of 'pcap_sendpacket', and to 0 if you don't. */
#undef HAVE_DECL_PCAP_SENDPACKET
/* Define to 1 if you have the declaration
of 'pcap_setnonblock', and to 0 if you don't. */
#undef HAVE_DECL_PCAP_SETNONBLOCK
......@@ -122,6 +130,12 @@
/* Define if you have -lpcap and pcap.h. */
#undef HAVE_PCAP
/* Define if you have the pcap_inject function. */
#undef HAVE_PCAP_INJECT
/* Define if you have the pcap_sendpacket function. */
#undef HAVE_PCAP_SENDPACKET
/* Define if you have the pcap_setnonblock function. */
#undef HAVE_PCAP_SETNONBLOCK
......
......@@ -9710,7 +9710,29 @@ $as_echo "$ac_cv_bpf_timeval" >&6; }
$as_echo "#define HAVE_BPF_TIMEVAL 1" >>confdefs.h
fi
ac_fn_cxx_check_decl "$LINENO" "pcap_setnonblock" "ac_cv_have_decl_pcap_setnonblock" "#include <pcap.h>
ac_fn_cxx_check_decl "$LINENO" "pcap_inject" "ac_cv_have_decl_pcap_inject" "#include <pcap.h>
"
if test "x$ac_cv_have_decl_pcap_inject" = xyes; then :
ac_have_decl=1
else
ac_have_decl=0
fi
cat >>confdefs.h <<_ACEOF
#define HAVE_DECL_PCAP_INJECT $ac_have_decl
_ACEOF
ac_fn_cxx_check_decl "$LINENO" "pcap_sendpacket" "ac_cv_have_decl_pcap_sendpacket" "#include <pcap.h>
"
if test "x$ac_cv_have_decl_pcap_sendpacket" = xyes; then :
ac_have_decl=1
else
ac_have_decl=0
fi
cat >>confdefs.h <<_ACEOF
#define HAVE_DECL_PCAP_SENDPACKET $ac_have_decl
_ACEOF
ac_fn_cxx_check_decl "$LINENO" "pcap_setnonblock" "ac_cv_have_decl_pcap_setnonblock" "#include <pcap.h>
"
if test "x$ac_cv_have_decl_pcap_setnonblock" = xyes; then :
ac_have_decl=1
......@@ -9869,12 +9891,13 @@ $as_echo "#define HAVE_PCAP 1" >>confdefs.h
saveflags="$LDFLAGS"
LDFLAGS="$saveflags $PCAP_LIBS"
for ac_func in pcap_setnonblock
for ac_func in pcap_inject pcap_sendpacket pcap_setnonblock
do :
ac_fn_c_check_func "$LINENO" "pcap_setnonblock" "ac_cv_func_pcap_setnonblock"
if test "x$ac_cv_func_pcap_setnonblock" = xyes; then :
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
if eval test \"x\$"$as_ac_var"\" = x"yes"; then :
cat >>confdefs.h <<_ACEOF
#define HAVE_PCAP_SETNONBLOCK 1
#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1
_ACEOF
fi
......
......@@ -6,6 +6,7 @@
* Copyright (c) 1999-2000 Massachusetts Institute of Technology
* Copyright (c) 2001 International Computer Science Institute
* Copyright (c) 2005-2007 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"),
......@@ -73,7 +74,7 @@ int
FromDevice::configure(Vector<String> &conf, ErrorHandler *errh)
{
bool promisc = false, outbound = false, sniffer = true;
_snaplen = 2046;
_snaplen = default_snaplen;
_headroom = Packet::default_headroom;
_headroom += (4 - (_headroom + 2) % 4) % 4; // default 4/2 alignment
_force_ip = false;
......@@ -86,7 +87,8 @@ FromDevice::configure(Vector<String> &conf, ErrorHandler *errh)
.read_p("SNAPLEN", _snaplen)
.read("SNIFFER", sniffer)
.read("FORCE_IP", _force_ip)
.read("CAPTURE", WordArg(), capture)
.read("METHOD", WordArg(), capture)
.read("CAPTURE", WordArg(), capture) // deprecated
.read("BPF_FILTER", bpf_filter)
.read("OUTBOUND", outbound)
.read("HEADROOM", _headroom)
......@@ -119,7 +121,7 @@ FromDevice::configure(Vector<String> &conf, ErrorHandler *errh)
#elif FROMDEVICE_PCAP
_capture = CAPTURE_PCAP;
#else
return errh->error("this platform does not support any capture method");
return errh->error("cannot receive packets on this platform");
#endif
}
#if FROMDEVICE_LINUX
......@@ -131,10 +133,10 @@ FromDevice::configure(Vector<String> &conf, ErrorHandler *errh)
_capture = CAPTURE_PCAP;
#endif
else
return errh->error("capture method '%s' not supported", capture.c_str());
return errh->error("bad METHOD");
if (bpf_filter && _capture != CAPTURE_PCAP)
errh->warning("not using PCAP capture method, BPF filter ignored");
errh->warning("not using METHOD PCAP, BPF filter ignored");
_sniffer = sniffer;
_promisc = promisc;
......@@ -215,16 +217,46 @@ FromDevice::set_promiscuous(int fd, String ifname, bool promisc)
#endif /* FROMDEVICE_LINUX */
#if FROMDEVICE_PCAP
String
FromDevice::get_pcap_error(const char *ebuf)
const char *
FromDevice::pcap_error(pcap_t *pcap, const char *ebuf)
{
if ((!ebuf || !ebuf[0]) && _pcap)
ebuf = pcap_geterr(_pcap);
if ((!ebuf || !ebuf[0]) && pcap)
ebuf = pcap_geterr(pcap);
if (!ebuf || !ebuf[0])
return "unknown error";
else
return ebuf;
}
pcap_t *
FromDevice::open_pcap(String ifname, int snaplen, bool promisc,
ErrorHandler *errh)
{
char ebuf[PCAP_ERRBUF_SIZE];
ebuf[0] = 0;
pcap_t *pcap = pcap_open_live(ifname.mutable_c_str(), snaplen, promisc,
1, /* timeout: don't wait for packets */
ebuf);
// Note: pcap error buffer will contain the interface name
if (!pcap) {
errh->error("%s while opening %s", pcap_error(0, ebuf), ifname.c_str());
return 0;
} else if (ebuf[0])
errh->warning("%s", ebuf);
// nonblocking I/O on the packet socket so we can poll
# if HAVE_PCAP_SETNONBLOCK
ebuf[0] = 0;
if (pcap_setnonblock(pcap, 1, ebuf) < 0 || ebuf[0])
errh->warning("pcap_setnonblock: %s", pcap_error(pcap, ebuf));
# else
if (fcntl(pcap_fileno(pcap), F_SETFL, O_NONBLOCK) < 0)
errh->warning("setting nonblocking: %s", strerror(errno));
# endif
return pcap;
}
#endif
int
......@@ -236,28 +268,11 @@ FromDevice::initialize(ErrorHandler *errh)
#if FROMDEVICE_PCAP
if (_capture == CAPTURE_PCAP) {
assert(!_pcap);
char *ifname = _ifname.mutable_c_str();
char ebuf[PCAP_ERRBUF_SIZE];
ebuf[0] = 0;
_pcap = pcap_open_live(ifname, _snaplen, _promisc,
1, /* timeout: don't wait for packets */
ebuf);
// Note: pcap error buffer will contain the interface name
_pcap = open_pcap(_ifname, _snaplen, _promisc, errh);
if (!_pcap)
return errh->error("%s while opening %s", get_pcap_error(ebuf).c_str(), ifname);
else if (ebuf[0])
errh->warning("%s", ebuf);
// nonblocking I/O on the packet socket so we can poll
return 0;
char *ifname = _ifname.mutable_c_str();
int pcap_fd = fd();
# if HAVE_PCAP_SETNONBLOCK
ebuf[0] = 0;
if (pcap_setnonblock(_pcap, 1, ebuf) < 0 || ebuf[0])
errh->warning("pcap_setnonblock: %s", get_pcap_error(ebuf).c_str());
# else
if (fcntl(pcap_fd, F_SETFL, O_NONBLOCK) < 0)
errh->warning("setting nonblocking: %s", strerror(errno));
# endif
# ifdef BIOCSSEESENT
{
......@@ -288,9 +303,10 @@ FromDevice::initialize(ErrorHandler *errh)
bpf_u_int32 netmask;
bpf_u_int32 localnet;
char ebuf[PCAP_ERRBUF_SIZE];
ebuf[0] = 0;
if (pcap_lookupnet(ifname, &localnet, &netmask, ebuf) < 0 || ebuf[0] != 0)
errh->warning("%s", get_pcap_error(ebuf).c_str());
errh->warning("%s", pcap_error(ebuf));
// Later versions of pcap distributed with linux (e.g. the redhat
// linux pcap-0.4-16) want to have a filter installed before they
......@@ -299,9 +315,9 @@ FromDevice::initialize(ErrorHandler *errh)
// compile the BPF filter
struct bpf_program fcode;
if (pcap_compile(_pcap, &fcode, _bpf_filter.mutable_c_str(), 0, netmask) < 0)
return errh->error("%s: %s", ifname, pcap_geterr(_pcap));
return errh->error("%s: %s", ifname, pcap_error(0));
if (pcap_setfilter(_pcap, &fcode) < 0)
return errh->error("%s: %s", ifname, pcap_geterr(_pcap));
return errh->error("%s: %s", ifname, pcap_error(0));
add_select(pcap_fd, SELECT_READ);
......@@ -354,10 +370,9 @@ FromDevice::cleanup(CleanupStage stage)
}
#endif
#if FROMDEVICE_PCAP
if (_pcap) {
if (_pcap)
pcap_close(_pcap);
_pcap = 0;
}
_pcap = 0;
#endif
}
......
......@@ -79,23 +79,22 @@ Defaults to 2046.
Boolean. If true, then output only IP packets. (Any link-level header remains,
but the IP header annotation has been set appropriately.) Default is false.
=item CAPTURE
=item METHOD
Word. Defines the capture method FromDevice will use to read packets from the
kernel. Linux targets generally support PCAP and LINUX; other targets support
only PCAP. Defaults to LINUX on Linux targets (unless you give a BPF_FILTER),
and PCAP elsewhere.
device. Linux targets generally support PCAP and LINUX; other targets support
only PCAP. Defaults to PCAP.
=item BPF_FILTER
String. A BPF filter expression used to select the interesting packets.
Default is the empty string, which means all packets. If CAPTURE is not PCAP,
Default is the empty string, which means all packets. If METHOD is not PCAP,
then any filter expression is ignored with a warning.
=item ENCAP
Word. The encapsulation type the interface should use; see FromDump for
choices. Ignored if CAPTURE is not PCAP.
choices. Ignored if METHOD is not PCAP.
=item OUTBOUND
......@@ -153,6 +152,7 @@ class FromDevice : public Element { public:
const char *port_count() const { return "0/1-2"; }
const char *processing() const { return PUSH; }
enum { default_snaplen = 2046 };
int configure_phase() const { return KernelFilter::CONFIGURE_PHASE_FROMDEVICE; }
int configure(Vector<String> &, ErrorHandler *);
int initialize(ErrorHandler *);
......@@ -164,10 +164,14 @@ class FromDevice : public Element { public:
void selected(int fd, int mask);
#if FROMDEVICE_PCAP
pcap_t *pcap() const { return _pcap; }
bool run_task(Task *);
static const char *pcap_error(pcap_t *pcap, const char *ebuf);
static pcap_t *open_pcap(String ifname, int snaplen, bool promisc, ErrorHandler *errh);
#endif
#if FROMDEVICE_LINUX
int linux_fd() const { return _linux_fd; }
static int open_packet_socket(String, ErrorHandler *);
static int set_promiscuous(int, String, bool);
#endif
......@@ -181,12 +185,14 @@ class FromDevice : public Element { public:
unsigned char *_linux_packetbuf;
#endif
#if FROMDEVICE_PCAP
pcap_t* _pcap;
pcap_t *_pcap;
Task _pcap_task;
int _pcap_complaints;
friend void FromDevice_get_packet(u_char*, const struct pcap_pkthdr*,
const u_char*);
String get_pcap_error(const char *ebuf);
const char *pcap_error(const char *ebuf) {
return pcap_error(_pcap, ebuf);
}
#endif
bool _force_ip;
int _burst;
......
......@@ -4,6 +4,7 @@
*
* 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"),
......@@ -34,13 +35,14 @@
#include <stdio.h>
#include <unistd.h>
#if TODEVICE_BSD_DEV_BPF
#if TODEVICE_ALLOW_DEVBPF
# include <fcntl.h>
# include <sys/types.h>
# include <sys/socket.h>
# include <sys/ioctl.h>
# include <net/if.h>
#elif TODEVICE_LINUX
#endif
#if TODEVICE_ALLOW_LINUX
# include <sys/socket.h>
# include <sys/ioctl.h>
# include <net/if.h>
......@@ -56,10 +58,16 @@
CLICK_DECLS
ToDevice::ToDevice()
: _task(this), _timer(&_task), _fd(-1), _my_fd(false),
_q(0),
_pulls(0)
: _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
_fd = -1;
_my_fd = false;
#endif
}
ToDevice::~ToDevice()
......@@ -69,90 +77,158 @@ ToDevice::~ToDevice()
int
ToDevice::configure(Vector<String> &conf, ErrorHandler *errh)
{
String method;
if (Args(conf, this, errh)
.read_mp("DEVNAME", _ifname)
.read("DEBUG", _debug)
.read("METHOD", WordArg(), method)
.complete() < 0)
return -1;
if (!_ifname)
return errh->error("interface not set");
if (method == "") {
#if TODEVICE_ALLOW_PCAP && TODEVICE_ALLOW_LINUX
_method = method_pcap;
if (FromDevice *fd = find_fromdevice())
if (fd->linux_fd())
_method = method_linux;
#elif TODEVICE_ALLOW_PCAP
_method = method_pcap;
#elif TODEVICE_ALLOW_LINUX
_method = method_linux;
#elif TODEVICE_ALLOW_DEVBPF
_method = method_devbpf;
#elif TODEVICE_ALLOW_PCAPFD
_method = method_pcapfd;
#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
else
return errh->error("bad METHOD");
return 0;
}
FromDevice *
ToDevice::find_fromdevice() const
{
Router *r = router();
for (int ei = 0; ei < r->nelements(); ++ei)
if (FromDevice *fd = (FromDevice *) r->element(ei)->cast("FromDevice"))
return fd;
return 0;
}
int
ToDevice::initialize(ErrorHandler *errh)
{
_timer.initialize(this);
_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("open /dev/bpf* for write: %s", strerror(errno)));
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);
_timer.initialize(this);
#if TODEVICE_ALLOW_PCAP
if (_method == method_pcap) {
FromDevice *fd = find_fromdevice();
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;
}
}
#endif
#if TODEVICE_ALLOW_DEVBPF
if (_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);
int yes = 1;
if (ioctl(_fd, BIOCSHDRCMPLT, (caddr_t)&yes) < 0)
errh->warning("BIOCSHDRCMPLT %s failed", ifr.ifr_name);
# endif
_my_fd = true;
#elif TODEVICE_LINUX || TODEVICE_PCAP
// find a FromDevice and reuse its socket if possible
for (int ei = 0; ei < router()->nelements() && _fd < 0; ei++) {
Element *e = router()->element(ei);
FromDevice *fdev = (FromDevice *)e->cast("FromDevice");
if (fdev && fdev->ifname() == _ifname && fdev->fd() >= 0) {
_fd = fdev->fd();
_my_fd = false;
}
}
if (_fd < 0) {
# if TODEVICE_LINUX
_fd = FromDevice::open_packet_socket(_ifname, errh);
_my_fd = true;
# else
return errh->error("ToDevice requires an initialized FromDevice on this platform");
# endif
}
if (_fd < 0)
return -1;
#else
#endif
return errh->error("ToDevice is not supported on this platform");
#if TODEVICE_ALLOW_LINUX
if (_method == method_linux) {
FromDevice *fd = find_fromdevice();
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;
}
}
#endif
#if TODEVICE_ALLOW_PCAPFD
if (_method == method_pcapfd) {
FromDevice *fd = find_fromdevice();
if (fd && fd->pcap())
_fd = fd->fd();
else
return errh->error("initialized FromDevice required on this platform");
}
#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;
// 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;
ScheduleInfo::join_scheduler(this, &_task, errh);
_signal = Notifier::upstream_empty_signal(this, 0, &_task);
return 0;
}
void
ToDevice::cleanup(CleanupStage)
{
if (_fd >= 0 && _my_fd)
close(_fd);
_fd = -1;
#if TODEVICE_ALLOW_PCAP
if (_pcap && _my_pcap)
pcap_close(_pcap);
_pcap = 0;
#endif
#if TODEVICE_ALLOW_LINUX || TODEVICE_ALLOW_DEVBPF || TODEVICE_ALLOW_PCAPFD
if (_fd >= 0 && _my_fd)
close(_fd);
_fd = -1;
#endif
}
......@@ -166,6 +242,45 @@ ToDevice::cleanup(CleanupStage)
* timer if buffers are not available.
* --jbicket
*/
int
ToDevice::send_packet(Packet *p)
{
int r = 0;
errno = 0;
#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()) != p->length())
r = -1;
#endif
#if TODEVICE_ALLOW_PCAPFD
if (_method == method_pcapfd)
if (write(_fd, p->data(), p->length()) != p->length())
r = -1;
#endif
if (r >= 0)
return 0;
else
return errno ? -errno : -EINVAL;
}
bool
ToDevice::run_task(Task *)
{
......@@ -177,24 +292,13 @@ ToDevice::run_task(Task *)
}
if (p) {
int retval;
const char *syscall;
#if TODEVICE_WRITE
retval = ((uint32_t) write(_fd, p->data(), p->length()) == p->length() ? 0 : -1);
syscall = "write";
#elif TODEVICE_SEND
retval = send(_fd, p->data(), p->length(), 0);
syscall = "send";
#else
retval = 0;
#endif
int r = send_packet(p);
if (retval >= 0) {
if (r >= 0) {
_backoff = 0;
checked_output_push(0, p);
} else if (errno == ENOBUFS || errno == EAGAIN) {
} else if (r == -ENOBUFS || r == -EAGAIN) {
assert(!_q);
_q = p;
......@@ -214,7 +318,7 @@ ToDevice::run_task(Task *)
return false;