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
Ethernet hardware
frame relay hardware
IEEE1394 (FireWire™) hardware
IEEE1394 (FireWire™) hardware with EUI-64 addresses
token-ring hardware
ARP response identifying peer
ARP request to identify peer
ARP response to resolve request
ARP resolve address request
ARP response giving protocol address
ARP request protocol address given hardware address
Ethernet encapsulation.
BSD loopback encapsulation.
Address Resolution Protocol
IPv4
IPv6
Used to test interfaces
EAPOL PAE/802.1x
PUP protocol
Reverse Address Resolution Protocol
802.11i / RSN Pre-Authentication
IEEE 802.1Q VLAN tagging
TCP Acknowledged flag
TCP Congestion Window Reduced flag
TCP ECN echo flag
TCP Finish flag
TCP Push flag
TCP Reset flag
TCP Synchronize flag
TCP Urgent flag
The version of Capp you are using
Device name packets are being captured from. Only set for live packet captures.
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); }
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; }
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
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; }
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; }
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
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()); }
Returns datalink used for capturing packets.
Capp.live.datalink #=> ["EN10MB"]
static VALUE capp_datalink(VALUE self) { const char *datalink_name; int dlt; pcap_t *handle; GetCapp(self, handle); dlt = pcap_datalink(handle); datalink_name = pcap_datalink_val_to_name(dlt); return rb_usascii_str_new_cstr(datalink_name); }
Sets the link-layer header type to be used by the capture instance to the
given datalink_name
. You can see the supported datalink names
by calling datalinks on the
capture instance.
Note that most possible datalink types do not have full support in Capp. You may receive the raw packet without any further extraction of packet fields.
static VALUE capp_set_datalink(VALUE self, VALUE datalink) { int dlt; pcap_t *handle; const char *datalink_name = StringValueCStr(datalink); GetCapp(self, handle); dlt = pcap_datalink_name_to_val(datalink_name); if (-1 == dlt) rb_raise(eCappError, "unrecognized datalink name %s", datalink_name); if (pcap_set_datalink(handle, dlt)) rb_raise(eCappError, "%s", pcap_geterr(handle)); return datalink; }
Returns the supported datalinks for this capture instance:
p Capp.live.datalinks #=> ["EN10MB", "PPI", "IEEE802_11_RADIO", "IEEE802_11", "IEEE802_11_RADIO_AVS", "RAW"]
These can be used to change the datalink used to capture packets by using datalink=
static VALUE capp_datalinks(VALUE self) { int *dlt_buf; pcap_t *handle; VALUE datalink_ary; int i, datalink_count; GetCapp(self, handle); datalink_count = pcap_list_datalinks(handle, &dlt_buf); if (datalink_count == -1) rb_raise(eCappError, "%s", pcap_geterr(handle)); datalink_ary = rb_ary_new2(datalink_count); for (i = 0; i < datalink_count; i++) { const char *datalink_name_cstr = pcap_datalink_val_to_name(dlt_buf[i]); VALUE datalink_name = rb_usascii_str_new_cstr(datalink_name_cstr); rb_ary_push(datalink_ary, datalink_name); } pcap_free_datalinks(dlt_buf); return datalink_ary; }
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; }
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; }
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; }
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)); }
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)); }
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
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; }
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; }
Stops a running loop
static VALUE capp_stop(VALUE self) { pcap_t *handle; GetCapp(self, handle); pcap_breakloop(handle); return self; }
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; }