class Capp

Capp is a GVL-friendly libpcap wrapper library.

To create a packet capture device:

capp = Capp.live

This listens on the default device. You can list devices with ::devices.

To start capture use loop:

capp.loop do |packet|
  # ...
end

loop yields a Capp::Packet object for each captured packet.

To stop capturing packets return (or break) from the loop, or call stop on the Capp instance. You can resume capturing packets by calling loop again after stop.

To set a filter for only udp port 7647 (Rinda::RingFinger packets):

capp.filter = 'udp port 7647'

The format for a filter rule is the same as for tcpdump. See the pcap-filter(7) man page for the filter syntax.

You can use a Queue to capture packets in one thread and process them in another:

require 'capp'
require 'thread'

q = Queue.new

Thread.new do
  while packet = q.deq do
    # ...
  end
end

capp = Capp.live.loop do |packet|
  q.enq packet
end

Constants

ARPHRD_ETHER

ARPHRD_ETHER

Ethernet hardware

ARPHRD_FRELAY

ARPHRD_FRELAY

frame relay hardware

ARPHRD_IEEE1394

ARPHRD_IEEE1394

IEEE1394 (FireWire™) hardware

ARPHRD_IEEE1394_EUI64

ARPHRD_IEEE1394_EUI64

IEEE1394 (FireWire™) hardware with EUI-64 addresses

ARPHRD_IEEE802

ARPHRD_IEEE802

token-ring hardware

ARPOP_INVREPLY

ARPOP_INVREPLY

ARP response identifying peer

ARPOP_INVREQUEST

ARPOP_INVREQUEST

ARP request to identify peer

ARPOP_REPLY

ARPOP_REPLY

ARP response to resolve request

ARPOP_REQUEST

ARPOP_REQUEST

ARP resolve address request

ARPOP_REVREPLY

ARPOP_REVREPLY

ARP response giving protocol address

ARPOP_REVREQUEST

ARPOP_REVREQUEST

ARP request protocol address given hardware address

Address

An address for a Device which is returned by ::devices

DLT_EN10MB

DLT_EN10MB

Ethernet encapsulation.

DLT_NULL

DLT_NULL

BSD loopback encapsulation.

Device

A device which Capp can listen on, returned by ::devices

ETHERTYPE_ARP

ETHERTYPE_ARP

Address Resolution Protocol

ETHERTYPE_IP

ETHERTYPE_IP

IPv4

ETHERTYPE_IPV6

ETHERTYPE_IPV6

IPv6

ETHERTYPE_LOOPBACK

ETHERTYPE_LOOPBACK

Used to test interfaces

ETHERTYPE_PAE

ETHERTYPE_PAE

EAPOL PAE/802.1x

ETHERTYPE_PUP

ETHERTYPE_PUP

PUP protocol

ETHERTYPE_REVARP

ETHERTYPE_REVARP

Reverse Address Resolution Protocol

ETHERTYPE_RSN_PREAUTH

ETHERTYPE_RSN_PREAUTH

802.11i / RSN Pre-Authentication

ETHERTYPE_VLAN

ETHERTYPE_VLAN

IEEE 802.1Q VLAN tagging

TCP_ACK

TCP_ACK

TCP Acknowledged flag

TCP_CWR

TCP_CWR

TCP Congestion Window Reduced flag

TCP_ECE

TCP_ECE

TCP ECN echo flag

TCP_FIN

TCP_FIN

TCP Finish flag

TCP_PUSH

TCP_PUSH

TCP Push flag

TCP_RST

TCP_RST

TCP Reset flag

TCP_SYN

TCP_SYN

TCP Synchronize flag

TCP_URG

TCP_URG

TCP Urgent flag

VERSION

The version of Capp you are using

Attributes

device[R]

Device name packets are being captured from. Only set for live packet captures.

Public Class Methods

default_device_name → string click to toggle source

Returns the default device name

static VALUE
capp_s_default_device_name(VALUE klass)
{
    char errbuf[PCAP_ERRBUF_SIZE];
    char *device;

    *errbuf = '\0';

    device = pcap_lookupdev(errbuf);

    if (device == NULL)
        rb_raise(eCappError, "pcap_create: %s", errbuf);

    if (*errbuf)
        rb_warn("%s", errbuf);

    return rb_usascii_str_new_cstr(device);
}
devices → array click to toggle source

Returns an Array containing the devices and their addresses:

[#<struct Capp::Address
  address="lo0",
  netmask=nil,
  broadcast=
   [#<struct Capp::Address
     address="0:0:0:0:0:0",
     netmask=nil,
     broadcast=nil,
     destination=nil>,
    #<struct Capp::Address
     address="fe80::1%lo0",
     netmask="ffff:ffff:ffff:ffff::",
     broadcast=nil,
     destination=nil>,
    #<struct Capp::Address
     address="127.0.0.1",
     netmask="255.0.0.0",
     broadcast=nil,
     destination=nil>,
    #<struct Capp::Address
     address="::1",
     netmask="ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
     broadcast=nil,
     destination=nil>],
  destination=1>,
  # [...]
]
static VALUE
capp_s_devices(VALUE klass)
{
    VALUE device, devices, dev_args[4];
    char errbuf[PCAP_ERRBUF_SIZE];
    pcap_if_t *iface, *ifaces;

    *errbuf = '\0';

    if (pcap_findalldevs(&ifaces, errbuf))
        rb_raise(eCappError, "pcap_create: %s", errbuf);

    if (*errbuf)
        rb_warn("%s", errbuf);

    devices = rb_ary_new();

    for (iface = ifaces; iface; iface = iface->next) {
        dev_args[0] = rb_usascii_str_new_cstr(iface->name);
        if (iface->description) {
            dev_args[1] = rb_usascii_str_new_cstr(iface->description);
        } else {
            dev_args[1] = Qnil;
        }
        dev_args[2] = capp_addr_to_addresses(iface->addresses);
        dev_args[3] = UINT2NUM(iface->flags);

        device = rb_class_new_instance(4, dev_args, cCappDevice);

        rb_ary_push(devices, device);
    }

    pcap_freealldevs(ifaces);

    return devices;
}
drop_privileges(run_as_user, run_as_directory = nil) click to toggle source

Drops root privileges to the given run_as_user and optionally chroots to run_as_directory. Use this method after creating a packet capture instance to improve security.

Returns true if privileges are dropped, raises a Capp::Error if privileges could not be dropped and returns a false value if there was no need to drop privileges.

You will be able to start and stop packet capture but not create new packet capture instances after dropping privileges.

# File lib/capp.rb, line 100
def self.drop_privileges run_as_user, run_as_directory = nil
  return unless Process.uid.zero? and Process.euid.zero?
  return unless run_as_user or run_as_directory

  raise Capp::Error, 'chroot without dropping root is insecure' if
    run_as_directory and not run_as_user

  require 'etc'

  begin
    pw = if Integer === run_as_user then
           Etc.getpwuid run_as_user
         else
           Etc.getpwnam run_as_user
         end
  rescue ArgumentError => e
    raise Capp::Error, "could not find user #{run_as_user}"
  end

  if run_as_directory then
    begin
      Dir.chroot run_as_directory
      Dir.chdir '/'
    rescue Errno::ENOENT => e
      raise Capp::Error, "could not chroot to #{run_as_directory} " +
                         "or change to chroot directory"
    end
  end

  begin
    Process.gid = pw.gid
    Process.uid = pw.uid
  rescue Errno::EPERM => e
    raise Capp::Error, "unable to drop privileges to #{run_as_user} " +
                       "(#{e.message})"
  end

  true
end
live → capp click to toggle source
live device → capp
live device, capture_length, → capp
live device, capture_length, promiscuous → capp
live device, capture_length, promiscuous, timeout → capp

Creates a Capp instance that will capture packets from a network device.

device is the device to capture packets from. If the device is omitted the default device (::default_device_name) is used.

capture_length is the number of bytes to capture from each packet. If a length is omitted 65535 is used.

promiscuous places the device in promiscuous mode when true, allowing you to see packets not sent directly to or from the device. Promiscuous mode is enabled by default.

The timeout is the number of maximum number of milliseconds that will elapse between receiving a packet and yielding it to the block given to loop. The default timeout is 10 milliseconds. See timeout= for further discussion.

After creating an instance use loop to start capturing packets.

static VALUE
capp_s_open_live(int argc, VALUE *argv, VALUE klass)
{
    VALUE obj, device, snaplen, promiscuous, timeout;
    int promisc = 0;
    char errbuf[PCAP_ERRBUF_SIZE];
    pcap_t *handle;

    rb_scan_args(argc, argv, "04", &device, &snaplen, &promiscuous, &timeout);

    if (!RTEST(device))      device      = capp_s_default_device_name(klass);
    if (!RTEST(snaplen))     snaplen     = INT2NUM(65535);
    if (!RTEST(promiscuous)) promiscuous = Qtrue;
    if (!RTEST(timeout))     timeout     = INT2NUM(10);

    if (RTEST(promiscuous))
        promisc = 1;

    *errbuf = '\0';

    handle = pcap_open_live(StringValueCStr(device), NUM2INT(snaplen),
            promisc, NUM2INT(timeout), errbuf);

    if (NULL == handle)
        rb_raise(eCappError, "pcap_create: %s", errbuf);

    if (*errbuf)
        rb_warn("%s", errbuf);

    obj = Data_Wrap_Struct(klass, NULL, pcap_close, handle);

    rb_ivar_set(obj, id_iv_device, device);

    return obj;
}
offline filename → capp click to toggle source
offline io → capp

Creates an Capp that instance that captures packets from a pcap savefile. A savefile may be loaded from an open file:

open 'savefile' do |io|
  capp = Capp.offline io
  # ...
end

Or a filename:

capp = Capp.offline 'savefile'

After creating an instance use loop to start capturing packets.

NOTE: When you give ::offline a Ruby IO you should avoid buffered reads or writes to the IO due to limitations of libpcap. (Using a pipe and reading from one end and writing to the other is fine, of course.)

static VALUE
capp_s_open_offline(VALUE klass, VALUE file)
{
    VALUE obj;
    char errbuf[PCAP_ERRBUF_SIZE];
    pcap_t *handle;

    *errbuf = '\0';

    if (TYPE(file) == T_FILE) {
        FILE *c_file;
        rb_io_t *fptr;
        int fd;

        GetOpenFile(file, fptr);

        fd = dup(fptr->fd);

        if (-1 == fd)
            rb_sys_fail("dup");

        c_file = fdopen(fd, "r");

        if (NULL == c_file)
            rb_sys_fail("fdopen");

        handle = pcap_fopen_offline(c_file, errbuf);
    } else {
        handle = pcap_open_offline(StringValueCStr(file), errbuf);
    }

    if (NULL == handle) {
        if (RFILE(file))
            rb_raise(eCappError, "pcap_fopen_offline: %s", errbuf);

        rb_raise(eCappError, "pcap_open_offline: %s", errbuf);
    }

    if (*errbuf)
        rb_warn("%s", errbuf);

    obj = Data_Wrap_Struct(klass, NULL, pcap_close, handle);

    rb_ivar_set(obj, id_iv_device, Qnil);

    return obj;
}
open(device_or_file, *args) click to toggle source

Opens device_or_file as an offline device if it is an IO or an existing file. args are ignored (as ::offline does not support any).

Opens device_or_file as a live device otherwise, along with args. See ::live for documentation on the additional arguments.

# File lib/capp.rb, line 147
def self.open device_or_file, *args
  if IO === device_or_file or File.exist? device_or_file then
    offline device_or_file, *args
  else
    live device_or_file, *args
  end
end
pcap_lib_version → libpcap version string click to toggle source

Returns the libpcap version string:

Capp.pcap_lib_version #=> "libpcap version 1.1.1"
static VALUE
capp_s_pcap_lib_version(VALUE klass)
{
    return rb_usascii_str_new_cstr(pcap_lib_version());
}

Public Instance Methods

filter = filter → self click to toggle source

Sets the packet filter to the given filter string. The format is the same format as for tcpdump. Read the pcap-filter(7) man page for documentation on the filter syntax.

static VALUE
capp_set_filter(VALUE self, VALUE filter)
{
    VALUE device;
    pcap_t *handle;
    struct bpf_program program;
    bpf_u_int32 network, netmask = PCAP_NETMASK_UNKNOWN;
    char errbuf[PCAP_ERRBUF_SIZE];
    int res;

    device = rb_ivar_get(self, id_iv_device);

    if (RTEST(device)) {
        *errbuf = '\0';

        res =
            pcap_lookupnet(StringValueCStr(device), &network, &netmask, errbuf);

        if (res == -1)
            rb_raise(eCappError, "%s", errbuf);

        if (*errbuf)
            rb_warn("%s", errbuf);
    }

    GetCapp(self, handle);

    res = pcap_compile(handle, &program, StringValueCStr(filter), 0, netmask);

    if (res)
        rb_raise(eCappError, "%s", pcap_geterr(handle));

    res = pcap_setfilter(handle, &program);

    pcap_freecode(&program);

    if (res)
        rb_raise(eCappError, "%s", pcap_geterr(handle));

    return self;
}
loop { |packet| ... } → self click to toggle source
loop → enumerator

Starts capturing packets. Each packet captured is yielded to the block. Packets are instances of Capp::Packet.

If no block is given an enumerator is returned.

You can stop capturing packets by returning from the block (or using break) or by calling stop on the instance. Packet capture can be restarted later.

static VALUE
capp_loop(VALUE self)
{
    RETURN_ENUMERATOR(self, 0, 0);

    rb_ensure(capp_loop_run, self, capp_loop_end, self);

    return self;
}
promiscuous = boolean click to toggle source

Enables or disables promiscuous mode. When promiscuous mode is enabled packets that were not sent directly to the device will be captured.

static VALUE
capp_set_promisc(VALUE self, VALUE promiscuous)
{
    pcap_t *handle;
    int promisc = RTEST(promiscuous);

    GetCapp(self, handle);

    if (pcap_set_promisc(handle, promisc))
        rb_raise(eCappError, "pcap already activated");

    return promiscuous;
}
savefile_major_version → integer click to toggle source

When called on a capture instance created from a savefile, returns the major version of the savefile. When called on a live capture instance it returns a meaningless value.

static VALUE
capp_savefile_major_version(VALUE self)
{
    pcap_t *handle;

    GetCapp(self, handle);

    return INT2NUM(pcap_major_version(handle));
}
savefile_minor_version → integer click to toggle source

When called on a capture instance created from a savefile, returns the minor version of the savefile. When called on a live capture instance it returns a meaningless value.

static VALUE
capp_savefile_minor_version(VALUE self)
{
    pcap_t *handle;

    GetCapp(self, handle);

    return INT2NUM(pcap_minor_version(handle));
}
savefile_version() click to toggle source

When called on a capture instance created from a savefile, returns the version of the savefile. When called on a live capture instance it returns a meaningless value.

# File lib/capp.rb, line 160
def savefile_version
  "#{savefile_major_version}.#{savefile_minor_version}"
end
snaplen = bytes click to toggle source

Sets the number of bytes captured from each packet.

static VALUE
capp_set_snaplen(VALUE self, VALUE snaplen)
{
    pcap_t *handle;

    GetCapp(self, handle);

    if (pcap_set_snaplen(handle, NUM2INT(snaplen)))
        rb_raise(eCappError, "pcap already activated");

    return snaplen;
}
stats → hash click to toggle source

Retrieves packet capture statistics:

p capp.stats #=> {:drop => 0, :ifdrop => 0, :recv => 123}
static VALUE
capp_stats(VALUE self)
{
    VALUE stats;
    pcap_t *handle;
    struct pcap_stat ps;

    GetCapp(self, handle);

    if (pcap_stats(handle, &ps))
        rb_raise(eCappError, "%s", pcap_geterr(handle));

    stats = rb_hash_new();

    rb_hash_aset(stats, ID2SYM(id_drop),   UINT2NUM(ps.ps_drop));
    rb_hash_aset(stats, ID2SYM(id_ifdrop), UINT2NUM(ps.ps_ifdrop));
    rb_hash_aset(stats, ID2SYM(id_recv),   UINT2NUM(ps.ps_recv));

    return stats;
}
stop → capp click to toggle source

Stops a running loop

static VALUE
capp_stop(VALUE self)
{
    pcap_t *handle;

    GetCapp(self, handle);

    pcap_breakloop(handle);

    return self;
}
timeout = milliseconds click to toggle source

Sets the maximum amount of time in milliseconds that will elapse between receiving a packet and yielding it to loop.

Reducing the timeout will increase responsiveness as pcap_loop(3) must “check in” more frequently while increasing the timeout will reduce responsiveness.

Setting the timeout too low may increase GVL contention when many packets are arriving at once as loop will be waking up frequently to service captured packets.

static VALUE
capp_set_timeout(VALUE self, VALUE milliseconds)
{
    pcap_t *handle;

    GetCapp(self, handle);

    if (pcap_set_timeout(handle, NUM2INT(milliseconds)))
        rb_raise(eCappError, "pcap already activated");

    return milliseconds;
}