Class: Capp

Inherits:
Object
  • Object
show all
Defined in:
lib/capp.rb,
ext/capp/capp.c

Overview

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 Capp.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

Defined Under Namespace

Classes: Address, Device, Error, Packet, TestCase

Constant Summary collapse

VERSION =

The version of Capp you are using

'1.0'
ARPHRD_ETHER =

Ethernet hardware

INT2NUM(ARPHRD_ETHER)
ARPHRD_FRELAY =

frame relay hardware

INT2NUM(ARPHRD_FRELAY)
ARPHRD_IEEE1394 =

IEEE1394 (FireWire™) hardware

INT2NUM(ARPHRD_IEEE1394)
ARPHRD_IEEE1394_EUI64 =

IEEE1394 (FireWire™) hardware with EUI-64 addresses

INT2NUM(ARPHRD_IEEE1394_EUI64)
ARPHRD_IEEE802 =

token-ring hardware

INT2NUM(ARPHRD_IEEE802)
ARPOP_INVREPLY =

ARP response identifying peer

INT2NUM(ARPOP_INVREPLY)
ARPOP_INVREQUEST =

ARP request to identify peer

INT2NUM(ARPOP_INVREQUEST)
ARPOP_REPLY =

ARP response to resolve request

INT2NUM(ARPOP_REPLY)
ARPOP_REQUEST =

ARP resolve address request

INT2NUM(ARPOP_REQUEST)
ARPOP_REVREPLY =

ARP response giving protocol address

INT2NUM(ARPOP_REVREPLY)
ARPOP_REVREQUEST =

ARP request protocol address given hardware address

INT2NUM(ARPOP_REVREQUEST)
DLT_NULL =

BSD loopback encapsulation.

INT2NUM(DLT_NULL)
DLT_EN10MB =

Ethernet encapsulation.

INT2NUM(DLT_EN10MB)
ETHERTYPE_ARP =

Address Resolution Protocol

INT2NUM(ETHERTYPE_ARP)
ETHERTYPE_IP =

IPv4

INT2NUM(ETHERTYPE_IP)
ETHERTYPE_IPV6 =

IPv6

INT2NUM(ETHERTYPE_IPV6)
ETHERTYPE_LOOPBACK =

Used to test interfaces

INT2NUM(ETHERTYPE_LOOPBACK)
ETHERTYPE_PUP =

PUP protocol

INT2NUM(ETHERTYPE_PUP)
ETHERTYPE_PAE =

EAPOL PAE/802.1x

INT2NUM(ETHERTYPE_PAE)
ETHERTYPE_REVARP =

Reverse Address Resolution Protocol

INT2NUM(ETHERTYPE_REVARP)
ETHERTYPE_RSN_PREAUTH =

802.11i / RSN Pre-Authentication

INT2NUM(ETHERTYPE_RSN_PREAUTH)
ETHERTYPE_VLAN =

IEEE 802.1Q VLAN tagging

INT2NUM(ETHERTYPE_VLAN)
TCP_ACK =

TCP Acknowledged flag

INT2NUM(TH_ACK)
TCP_CWR =

TCP Congestion Window Reduced flag

INT2NUM(TH_CWR)
TCP_ECE =

TCP ECN echo flag

INT2NUM(TH_ECE)
TCP_FIN =

TCP Finish flag

INT2NUM(TH_FIN)
TCP_PUSH =

TCP Push flag

INT2NUM(TH_PUSH)
TCP_RST =

TCP Reset flag

INT2NUM(TH_RST)
TCP_SYN =

TCP Synchronize flag

INT2NUM(TH_SYN)
TCP_URG =

TCP Urgent flag

INT2NUM(TH_URG)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#deviceObject (readonly)

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



86
87
88
# File 'lib/capp.rb', line 86

def device
  @device
end

Class Method Details

.default_device_nameString

Returns the default device name

Returns:

  • (String)


124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'ext/capp/capp.c', line 124

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);
}

.devicesArray

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>,
  # [...]
]

Returns:

  • (Array)


236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'ext/capp/capp.c', line 236

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) ⇒ Object

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.

Raises:



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# 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

.liveObject .deviceObject .deviceObject .deviceObject .deviceObject

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.



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'ext/capp/capp.c', line 300

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;
}

.filenameObject .ioObject

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 Capp.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.)



359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
# File 'ext/capp/capp.c', line 359

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) ⇒ Object

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.



147
148
149
150
151
152
153
# 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_versionObject

Returns the libpcap version string:

Capp.pcap_lib_version #=> "libpcap version 1.1.1"


416
417
418
419
420
# File 'ext/capp/capp.c', line 416

static VALUE
capp_s_pcap_lib_version(VALUE klass)
{
    return rb_usascii_str_new_cstr(pcap_lib_version());
}

Instance Method Details

Returns datalink used for capturing packets.

Capp.live.datalink #=> ["EN10MB"]


472
473
474
475
476
477
478
479
480
481
482
483
484
485
# File 'ext/capp/capp.c', line 472

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);
}

#datalink=(datalink_name) ⇒ Object

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.



950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
# File 'ext/capp/capp.c', line 950

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=



435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
# File 'ext/capp/capp.c', line 435

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;
}

#filter=(filter) ⇒ self

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.

Returns:

  • (self)


978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
# File 'ext/capp/capp.c', line 978

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 #loopObject

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.

Overloads:

  • #loop {|packet| ... } ⇒ self

    Yields:

    • (packet)

    Returns:

    • (self)


892
893
894
895
896
897
898
899
900
# File 'ext/capp/capp.c', line 892

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) ⇒ Object

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



1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
# File 'ext/capp/capp.c', line 1027

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_versionInteger

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.

Returns:

  • (Integer)


910
911
912
913
914
915
916
917
918
# File 'ext/capp/capp.c', line 910

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

    GetCapp(self, handle);

    return INT2NUM(pcap_major_version(handle));
}

#savefile_minor_versionInteger

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.

Returns:

  • (Integer)


928
929
930
931
932
933
934
935
936
# File 'ext/capp/capp.c', line 928

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

    GetCapp(self, handle);

    return INT2NUM(pcap_minor_version(handle));
}

#savefile_versionObject

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.



160
161
162
# File 'lib/capp.rb', line 160

def savefile_version
  "#{savefile_major_version}.#{savefile_minor_version}"
end

#snaplen=(bytes) ⇒ Object

Sets the number of bytes captured from each packet.



1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
# File 'ext/capp/capp.c', line 1047

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;
}

#statsHash

Retrieves packet capture statistics:

p capp.stats #=> {:drop => 0, :ifdrop => 0, :recv => 123}

Returns:

  • (Hash)


1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
# File 'ext/capp/capp.c', line 1096

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;
}

#stopObject

Stops a running loop



1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
# File 'ext/capp/capp.c', line 1123

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

    GetCapp(self, handle);

    pcap_breakloop(handle);

    return self;
}

#timeout=(milliseconds) ⇒ Object

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.



1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
# File 'ext/capp/capp.c', line 1075

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;
}