// -*- mode: c++; c-basic-offset: 4 -*-
/*
 * fromdevice.{cc,hh} -- element reads packets live from network via pcap
 * Douglas S. J. De Couto, Eddie Kohler, John Jannotti
 *
 * Copyright (c) 1999-2000 Massachusetts Institute of Technology
 * Copyright (c) 2001 International Computer Science Institute
 * Copyright (c) 2005 Regents of the University of California
 *
 * 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>
#include "fromdevice.hh"
#include <click/error.hh>
#include <click/confparse.hh>
#include <click/glue.hh>
#include <click/packet_anno.hh>
#include <click/standard/scheduleinfo.hh>
#include <unistd.h>
#include <fcntl.h>

#ifndef __sun
#include <sys/ioctl.h>
#else
#include <sys/ioccom.h>
#endif

#if FROMDEVICE_LINUX
# include <sys/socket.h>
# include <net/if.h>
# include <features.h>
# if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1
#  include <netpacket/packet.h>
#  include <net/ethernet.h>
# else
#  include <net/if_packet.h>
#  include <linux/if_packet.h>
#  include <linux/if_ether.h>
# endif
#endif

#include "fakepcap.hh"

CLICK_DECLS

FromDevice::FromDevice()
    : 
#if FROMDEVICE_LINUX
      _linux_fd(-1),
#endif
#if FROMDEVICE_PCAP
      _pcap(0), _pcap_task(this), _pcap_complaints(0),
#endif
      _promisc(0), _snaplen(0)
{
}

FromDevice::~FromDevice()
{
}

int
FromDevice::configure(Vector<String> &conf, ErrorHandler *errh)
{
    bool promisc = false, outbound = false, sniffer = true;
    _snaplen = 2046;
    _force_ip = false;
    String bpf_filter, capture;
    if (cp_va_parse(conf, this, errh,
		    cpString, "interface name", &_ifname,
		    cpOptional,
		    cpBool, "be promiscuous?", &promisc,
		    cpUnsigned, "maximum packet length", &_snaplen,
		    cpKeywords,
		    "SNIFFER", cpBool, "act as sniffer?", &sniffer,
		    "PROMISC", cpBool, "be promiscuous?", &promisc,
		    "SNAPLEN", cpUnsigned, "maximum packet length", &_snaplen,
		    "FORCE_IP", cpBool, "force IP packets?", &_force_ip,
		    "CAPTURE", cpWord, "capture method", &capture,
		    "BPF_FILTER", cpString, "BPF filter", &bpf_filter,
		    "OUTBOUND", cpBool, "emit outbound packets?", &outbound,
		    cpEnd) < 0)
	return -1;
    if (_snaplen > 8190 || _snaplen < 14)
	return errh->error("maximum packet length out of range");
    
#if FROMDEVICE_PCAP
    _bpf_filter = bpf_filter;
#endif

    // set _capture
    if (capture == "") {
#if FROMDEVICE_PCAP && FROMDEVICE_LINUX
	_capture = (bpf_filter ? CAPTURE_PCAP : CAPTURE_LINUX);
#elif FROMDEVICE_LINUX
	_capture = CAPTURE_LINUX;
#elif FROMDEVICE_PCAP
	_capture = CAPTURE_PCAP;
#else
	return errh->error("this platform does not support any capture method");
#endif
    }
#if FROMDEVICE_LINUX
    else if (capture == "LINUX")
	_capture = CAPTURE_LINUX;
#endif
#if FROMDEVICE_PCAP
    else if (capture == "PCAP")
	_capture = CAPTURE_PCAP;
#endif
    else
	return errh->error("capture method '%s' not supported", capture.c_str());
    
    if (bpf_filter && _capture != CAPTURE_PCAP)
	errh->warning("not using PCAP capture method, BPF filter ignored");
    
    if (!sniffer)
	return errh->error("SNIFFER must be set to true for now");
    _promisc = promisc;
    _outbound = outbound;
    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.c_str(), strerror(errno));

    // get interface index
    struct ifreq ifr;
    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, ifname.c_str(), sizeof(ifr.ifr_name));
    int res = ioctl(fd, SIOCGIFINDEX, &ifr);
    if (res != 0) {
	close(fd);
	return errh->error("%s: SIOCGIFINDEX: %s", ifname.c_str(), 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.c_str(), 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.c_str(), sizeof(ifr.ifr_name));
    if (ioctl(fd, SIOCGIFFLAGS, &ifr) != 0)
	return -2;
    int was_promisc = (ifr.ifr_flags & IFF_PROMISC ? 1 : 0);

    // set or reset promiscuous flag
#ifdef SOL_PACKET
    if (ioctl(fd, SIOCGIFINDEX, &ifr) != 0)
	return -2;
    struct packet_mreq mr;
    memset(&mr, 0, sizeof(mr));
    mr.mr_ifindex = ifr.ifr_ifindex;
    mr.mr_type = (promisc ? PACKET_MR_PROMISC : PACKET_MR_ALLMULTI);
    if (setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mr, sizeof(mr)) < 0)
	return -3;
#else
    if (was_promisc != promisc) {
	ifr.ifr_flags = (promisc ? ifr.ifr_flags | IFF_PROMISC : ifr.ifr_flags & ~IFF_PROMISC);
	if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0)
	    return -3;
    }
#endif

    return was_promisc;
}
#endif /* FROMDEVICE_LINUX */

int
FromDevice::initialize(ErrorHandler *errh)
{
    if (!_ifname)
	return errh->error("interface not set");

#if FROMDEVICE_PCAP
    if (_capture == CAPTURE_PCAP) {
	assert(!_pcap);
	char *ifname = _ifname.mutable_c_str();
	char ebuf[PCAP_ERRBUF_SIZE];
	_pcap = pcap_open_live(ifname, _snaplen, _promisc,
			       1,     /* timeout: don't wait for packets */
			       ebuf);
	// Note: pcap error buffer will contain the interface name
	if (!_pcap)
	    return errh->error("%s", ebuf);

	// nonblocking I/O on the packet socket so we can poll
	int pcap_fd = fd();
# if HAVE_PCAP_SETNONBLOCK
	if (pcap_setnonblock(_pcap, 1, ebuf) < 0)
	    errh->warning("pcap_setnonblock: %s", ebuf);
# else
	if (fcntl(pcap_fd, F_SETFL, O_NONBLOCK) < 0)
	    errh->warning("setting nonblocking: %s", strerror(errno));
# endif

# ifdef BIOCSSEESENT
	{
	    int r, accept = _outbound;
	    if ((r = ioctl(pcap_fd, BIOCSSEESENT, &accept)) == -1)
		return errh->error("%s: BIOCSSEESENT: %s", ifname, strerror(errno));
	    else if (r != 0)
		errh->warning("%s: BIOCSSEESENT returns %d", ifname, r);
	}
# endif

# if defined(BIOCIMMEDIATE) && !defined(__sun) // pcap/bpf ioctl, not in DLPI/bufmod
	{
	    int r, yes = 1;
	    if ((r = ioctl(pcap_fd, BIOCIMMEDIATE, &yes)) == -1)
		return errh->error("%s: BIOCIMMEDIATE: %s", ifname, strerror(errno));
	    else if (r != 0)
		errh->warning("%s: BIOCIMMEDIATE returns %d", ifname, r);
	}
# endif

	bpf_u_int32 netmask;
	bpf_u_int32 localnet;
	if (pcap_lookupnet(ifname, &localnet, &netmask, ebuf) < 0)
	    errh->warning("%s", 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
	// will pick up any packets.

	// 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));
	if (pcap_setfilter(_pcap, &fcode) < 0)
	    return errh->error("%s: %s", ifname, pcap_geterr(_pcap));

	add_select(pcap_fd, SELECT_READ);

	_datalink = pcap_datalink(_pcap);
	if (_force_ip && !fake_pcap_dlt_force_ipable(_datalink))
	    errh->warning("%s: strange data link type %d, FORCE_IP will not work", ifname, _datalink);

	ScheduleInfo::initialize_task(this, &_pcap_task, false, errh);
    }
#endif

#if FROMDEVICE_LINUX
    if (_capture == CAPTURE_LINUX) {
	_linux_fd = open_packet_socket(_ifname, errh);
	if (_linux_fd < 0)
	    return -1;

	int promisc_ok = set_promiscuous(_linux_fd, _ifname, _promisc);
	if (promisc_ok < 0) {
	    if (_promisc)
		errh->warning("cannot set promiscuous mode");
	    _was_promisc = -1;
	} else
	    _was_promisc = promisc_ok;

	add_select(_linux_fd, SELECT_READ);

	_datalink = FAKE_DLT_EN10MB;
    }
#endif
    
    return 0;
}

void
FromDevice::cleanup(CleanupStage)
{
#if FROMDEVICE_LINUX
    if (_linux_fd >= 0) {
	if (_was_promisc >= 0)
	    set_promiscuous(_linux_fd, _ifname, _was_promisc);
	close(_linux_fd);
	_linux_fd = -1;
    }
#endif
#if FROMDEVICE_PCAP
    if (_pcap) {
	pcap_close(_pcap);
	_pcap = 0;
    }
#endif
}

#if FROMDEVICE_PCAP
CLICK_ENDDECLS
extern "C" {
void
FromDevice_get_packet(u_char* clientdata,
		      const struct pcap_pkthdr* pkthdr,
		      const u_char* data)
{
    static char bcast_addr[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };

    FromDevice *fd = (FromDevice *) clientdata;
    int length = pkthdr->caplen;
#if defined(__sparc)
    // Packet::make(data,length) allocates new buffer to install
    // DEFAULT_HEADROOM (28 bytes). Thus data winds up on a 4 byte
    // boundary, irrespective of its original alignment. We assume we
    // want a two byte offset from a four byte boundary (DLT_EN10B).
    //
    // Furthermore, note that pcap-dlpi on Solaris uses bufmod by
    // default, hence while pcap-dlpi.pcap_read() is careful to load
    // the initial read from the stream head into a buffer aligned
    // appropriately for the network interface type, (I believe)
    // subsequent packets in the batched read will be copied from the
    // Stream's byte sequence into the pcap-dlpi user-level buffer at
    // arbitrary alignments.
    Packet *p = Packet::make(data - 2, length + 2);
    p->pull(2); 
#else
    Packet *p = Packet::make(data, length);
#endif

    // set packet type annotation
    if (p->data()[0] & 1) {
	if (memcmp(bcast_addr, p->data(), 6) == 0)
	    p->set_packet_type_anno(Packet::BROADCAST);
	else
	    p->set_packet_type_anno(Packet::MULTICAST);
    }

    // set annotations
    p->set_timestamp_anno(Timestamp::make_usec(pkthdr->ts.tv_sec, pkthdr->ts.tv_usec));
    p->set_mac_header(p->data());
    SET_EXTRA_LENGTH_ANNO(p, pkthdr->len - length);

    if (!fd->_force_ip || fake_pcap_force_ip(p, fd->_datalink))
	fd->output(0).push(p);
    else
	p->kill();
}
}
CLICK_DECLS
#endif

void
FromDevice::selected(int)
{
#if FROMDEVICE_PCAP
    if (_capture == CAPTURE_PCAP) {
	// Read and push() at most one packet.
	int r = pcap_dispatch(_pcap, 1, FromDevice_get_packet, (u_char *) this);
	if (r > 0)
	    _pcap_task.reschedule();
	else if (r < 0 && ++_pcap_complaints < 5)
	    ErrorHandler::default_handler()->error("%{element}: %s", this, pcap_geterr(_pcap));
    }
#endif
#if FROMDEVICE_LINUX
    if (_capture == CAPTURE_LINUX) {
	struct sockaddr_ll sa;
	socklen_t fromlen = sizeof(sa);
	// store data offset 2 bytes into the packet, assuming that first 14
	// bytes are ether header, and that we want remaining data to be
	// 4-byte aligned.  this assumes that _packetbuf is 4-byte aligned,
	// and that the buffer allocated by Packet::make is also 4-byte
	// aligned.  Actually, it doesn't matter if the packet is 4-byte
	// aligned; perhaps there is some efficiency aspect?  who cares....
	WritablePacket *p = Packet::make(2, 0, _snaplen, 0);
	int len = recvfrom(_linux_fd, p->data(), p->length(), MSG_TRUNC, (sockaddr *)&sa, &fromlen);
	if (len > 0 && (sa.sll_pkttype != PACKET_OUTGOING || _outbound)) {
	    if (len > _snaplen) {
		assert(p->length() == (uint32_t)_snaplen);
		SET_EXTRA_LENGTH_ANNO(p, len - _snaplen);
	    } else
		p->take(_snaplen - len);
	    p->set_packet_type_anno((Packet::PacketType)sa.sll_pkttype);
	    p->timestamp_anno().set_timeval_ioctl(_linux_fd, SIOCGSTAMP);
	    p->set_mac_header(p->data());
	    if (!_force_ip || fake_pcap_force_ip(p, _datalink))
		output(0).push(p);
	    else
		p->kill();
	} else {
	    p->kill();
	    if (len <= 0 && errno != EAGAIN)
		click_chatter("FromDevice(%s): recvfrom: %s", _ifname.c_str(), strerror(errno));
	}
    }
#endif
}

#if FROMDEVICE_PCAP
bool
FromDevice::run_task()
{
    // Read and push() at most one packet.
    int r = pcap_dispatch(_pcap, 1, FromDevice_get_packet, (u_char *) this);
    if (r > 0)
	_pcap_task.fast_reschedule();
    else if (r < 0 && ++_pcap_complaints < 5)
	ErrorHandler::default_handler()->error("%{element}: %s", this, pcap_geterr(_pcap));
    return r > 0;
}
#endif

void
FromDevice::kernel_drops(bool& known, int& max_drops) const
{
#if FROMDEVICE_LINUX
    // You might be able to do this better by parsing netstat/ifconfig output,
    // but for now, we just give up.
#endif
    known = false, max_drops = -1;
#if FROMDEVICE_PCAP
    if (_capture == CAPTURE_PCAP) {
	struct pcap_stat stats;
	if (pcap_stats(_pcap, &stats) >= 0)
	    known = true, max_drops = stats.ps_drop;
    }
#endif
}

String
FromDevice::read_kernel_drops(Element* e, void*)
{
    FromDevice* fd = static_cast<FromDevice*>(e);
    int max_drops;
    bool known;
    fd->kernel_drops(known, max_drops);
    if (known)
	return String(max_drops);
    else if (max_drops >= 0)
	return "<" + String(max_drops);
    else
	return "??";
}

String
FromDevice::read_encap(Element* e, void*)
{
    FromDevice* fd = static_cast<FromDevice*>(e);
    return String(fake_pcap_unparse_dlt(fd->_datalink));
}

void
FromDevice::add_handlers()
{
    add_read_handler("kernel_drops", read_kernel_drops, 0);
    add_read_handler("encap", read_encap, 0);
}

CLICK_ENDDECLS
ELEMENT_REQUIRES(userlevel FakePcap)
EXPORT_ELEMENT(FromDevice)


syntax highlighted by Code2HTML, v. 0.9.1