capp 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,34 @@
1
+ require 'mkmf'
2
+
3
+ $CFLAGS << " #{ENV['CFLAGS']}" if ENV['CFLAGS']
4
+ $LIBS << " #{ENV['LIBS']}" if ENV['LIBS']
5
+
6
+ have_library 'pcap' or abort 'missing pcap library'
7
+
8
+ def require_header header
9
+ have_header header or abort "missing #{header}"
10
+ end
11
+
12
+ require_header 'pcap/pcap.h'
13
+
14
+ require_header 'sys/socket.h'
15
+ require_header 'net/ethernet.h'
16
+ require_header 'net/if_arp.h'
17
+ require_header 'netinet/ip.h'
18
+ require_header 'netinet/ip6.h'
19
+ require_header 'netinet/ip_icmp.h'
20
+ require_header 'arpa/inet.h'
21
+
22
+ have_header 'net/if_dl.h'
23
+ have_header 'netinet/ether.h'
24
+
25
+ have_header 'ruby/thread.h'
26
+
27
+ have_macro 'RETURN_ENUMERATOR' or abort 'missing C enumerator support'
28
+
29
+ have_macro 'PCAP_WARNING_TSTAMP_TYPE_NOTSUP'
30
+ have_macro 'PCAP_ERROR_PROMISC_PERM_DENIED'
31
+
32
+ create_header
33
+ create_makefile 'capp/capp'
34
+
@@ -0,0 +1,86 @@
1
+ /*
2
+ * The following items are copied from tcpdump.
3
+ *
4
+ * Redistribution and use in source and binary forms, with or without
5
+ * modification, are permitted provided that the following conditions
6
+ * are met:
7
+ *
8
+ * 1. Redistributions of source code must retain the above copyright
9
+ * notice, this list of conditions and the following disclaimer.
10
+ * 2. Redistributions in binary form must reproduce the above copyright
11
+ * notice, this list of conditions and the following disclaimer in
12
+ * the documentation and/or other materials provided with the
13
+ * distribution.
14
+ * 3. The names of the authors may not be used to endorse or promote
15
+ * products derived from this software without specific prior
16
+ * written permission.
17
+ *
18
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
19
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
20
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
21
+ */
22
+
23
+ typedef u_int32_t tcp_seq;
24
+
25
+ /*
26
+ * TCP header.
27
+ * Per RFC 793, September, 1981.
28
+ */
29
+ struct tcphdr {
30
+ u_int16_t th_sport; /* source port */
31
+ u_int16_t th_dport; /* destination port */
32
+ tcp_seq th_seq; /* sequence number */
33
+ tcp_seq th_ack; /* acknowledgement number */
34
+ u_int8_t th_offx2; /* data offset, rsvd */
35
+ u_int8_t th_flags;
36
+ u_int16_t th_win; /* window */
37
+ u_int16_t th_sum; /* checksum */
38
+ u_int16_t th_urp; /* urgent pointer */
39
+ };
40
+
41
+ #define TH_OFF(th) (((th)->th_offx2 & 0xf0) >> 4)
42
+
43
+ #define TH_FIN 0x01
44
+ #define TH_SYN 0x02
45
+ #define TH_RST 0x04
46
+ #define TH_PUSH 0x08
47
+ #define TH_ACK 0x10
48
+ #define TH_URG 0x20
49
+ #define TH_ECE 0x40
50
+ #define TH_CWR 0x80
51
+
52
+ /*
53
+ * Udp protocol header.
54
+ * Per RFC 768, September, 1981.
55
+ */
56
+ struct udphdr {
57
+ u_int16_t uh_sport; /* source port */
58
+ u_int16_t uh_dport; /* destination port */
59
+ u_int16_t uh_ulen; /* udp length */
60
+ u_int16_t uh_sum; /* udp checksum */
61
+ };
62
+
63
+ /*
64
+ * Byte-swap a 32-bit number.
65
+ * ("htonl()" or "ntohl()" won't work - we want to byte-swap even on
66
+ * big-endian platforms.)
67
+ */
68
+ #define SWAPLONG(y) \
69
+ ((((y)&0xff)<<24) | (((y)&0xff00)<<8) | (((y)&0xff0000)>>8) | (((y)>>24)&0xff))
70
+
71
+ /*
72
+ * BSD AF_ values.
73
+ *
74
+ * Unfortunately, the BSDs don't all use the same value for AF_INET6,
75
+ * so, because we want to be able to read captures from all of the BSDs,
76
+ * we check for all of them.
77
+ */
78
+ #define BSD_AFNUM_INET 2
79
+ #define BSD_AFNUM_NS 6 /* XEROX NS protocols */
80
+ #define BSD_AFNUM_ISO 7
81
+ #define BSD_AFNUM_APPLETALK 16
82
+ #define BSD_AFNUM_IPX 23
83
+ #define BSD_AFNUM_INET6_BSD 24 /* OpenBSD (and probably NetBSD), BSD/OS */
84
+ #define BSD_AFNUM_INET6_FREEBSD 28
85
+ #define BSD_AFNUM_INET6_DARWIN 30
86
+
@@ -0,0 +1,168 @@
1
+ require 'socket'
2
+
3
+ ##
4
+ # Capp is a GVL-friendly libpcap wrapper library.
5
+ #
6
+ # To create a packet capture device:
7
+ #
8
+ # capp = Capp.live
9
+ #
10
+ # This listens on the default device. You can list devices with Capp.devices.
11
+ #
12
+ # To start capture use #loop:
13
+ #
14
+ # capp.loop do |packet|
15
+ # # ...
16
+ # end
17
+ #
18
+ # #loop yields a Capp::Packet object for each captured packet.
19
+ #
20
+ # To stop capturing packets return (or break) from the loop, or call #stop on
21
+ # the Capp instance. You can resume capturing packets by calling #loop again
22
+ # after #stop.
23
+ #
24
+ # To set a filter for only udp port 7647 (Rinda::RingFinger packets):
25
+ #
26
+ # capp.filter = 'udp port 7647'
27
+ #
28
+ # The format for a filter rule is the same as for tcpdump. See the
29
+ # pcap-filter(7) man page for the filter syntax.
30
+ #
31
+ # You can use a Queue to capture packets in one thread and process them in
32
+ # another:
33
+ #
34
+ # require 'capp'
35
+ # require 'thread'
36
+ #
37
+ # q = Queue.new
38
+ #
39
+ # Thread.new do
40
+ # while packet = q.deq do
41
+ # # ...
42
+ # end
43
+ # end
44
+ #
45
+ # capp = Capp.live.loop do |packet|
46
+ # q.enq packet
47
+ # end
48
+
49
+ class Capp
50
+
51
+ ##
52
+ # The version of Capp you are using
53
+
54
+ VERSION = '1.0'
55
+
56
+ ##
57
+ # Error class for Capp errors
58
+
59
+ class Error < RuntimeError
60
+ end
61
+
62
+ ##
63
+ # An address for a Device which is returned by Capp::devices
64
+
65
+ Address = Struct.new :address, :netmask, :broadcast, :destination
66
+
67
+ ##
68
+ # A device which Capp can listen on, returned by Capp::devices
69
+
70
+ Device = Struct.new :name, :description, :addresses, :flags do
71
+
72
+ ##
73
+ # Creates a new packet capture device for this device sending the given
74
+ # +args+ to Capp.open.
75
+
76
+ def open *args
77
+ Capp.open name, *args
78
+ end
79
+
80
+ end
81
+
82
+ ##
83
+ # Device name packets are being captured from. Only set for live packet
84
+ # captures.
85
+
86
+ attr_reader :device
87
+
88
+ ##
89
+ # Drops root privileges to the given +run_as_user+ and optionally chroots to
90
+ # +run_as_directory+. Use this method after creating a packet capture
91
+ # instance to improve security.
92
+ #
93
+ # Returns true if privileges are dropped, raises a Capp::Error if privileges
94
+ # could not be dropped and returns a false value if there was no need to
95
+ # drop privileges.
96
+ #
97
+ # You will be able to start and stop packet capture but not create new
98
+ # packet capture instances after dropping privileges.
99
+
100
+ def self.drop_privileges run_as_user, run_as_directory = nil
101
+ return unless Process.uid.zero? and Process.euid.zero?
102
+ return unless run_as_user or run_as_directory
103
+
104
+ raise Capp::Error, 'chroot without dropping root is insecure' if
105
+ run_as_directory and not run_as_user
106
+
107
+ require 'etc'
108
+
109
+ begin
110
+ pw = if Integer === run_as_user then
111
+ Etc.getpwuid run_as_user
112
+ else
113
+ Etc.getpwnam run_as_user
114
+ end
115
+ rescue ArgumentError => e
116
+ raise Capp::Error, "could not find user #{run_as_user}"
117
+ end
118
+
119
+ if run_as_directory then
120
+ begin
121
+ Dir.chroot run_as_directory
122
+ Dir.chdir '/'
123
+ rescue Errno::ENOENT => e
124
+ raise Capp::Error, "could not chroot to #{run_as_directory} " +
125
+ "or change to chroot directory"
126
+ end
127
+ end
128
+
129
+ begin
130
+ Process.gid = pw.gid
131
+ Process.uid = pw.uid
132
+ rescue Errno::EPERM => e
133
+ raise Capp::Error, "unable to drop privileges to #{run_as_user} " +
134
+ "(#{e.message})"
135
+ end
136
+
137
+ true
138
+ end
139
+
140
+ ##
141
+ # Opens +device_or_file+ as an offline device if it is an IO or an existing
142
+ # file. +args+ are ignored (as ::offline does not support any).
143
+ #
144
+ # Opens +device_or_file+ as a live device otherwise, along with +args+. See
145
+ # ::live for documentation on the additional arguments.
146
+
147
+ def self.open device_or_file, *args
148
+ if IO === device_or_file or File.exist? device_or_file then
149
+ offline device_or_file, *args
150
+ else
151
+ live device_or_file, *args
152
+ end
153
+ end
154
+
155
+ ##
156
+ # When called on a capture instance created from a savefile, returns the
157
+ # version of the savefile. When called on a live capture instance it
158
+ # returns a meaningless value.
159
+
160
+ def savefile_version
161
+ "#{savefile_major_version}.#{savefile_minor_version}"
162
+ end
163
+
164
+ end
165
+
166
+ require 'capp/packet'
167
+ require 'capp/capp'
168
+
@@ -0,0 +1,402 @@
1
+ # coding: BINARY
2
+
3
+ ##
4
+ # Capp::Packet provides convenient extraction of data from packets.
5
+ #
6
+ # Packet objects are automatically created when a packet is read from the
7
+ # opened interface. Unfortunately Capp does not understand every type of
8
+ # packet. If Capp doesn't understand your packet the layer 3 payload can be
9
+ # retrieved from unknown_layer3_header.
10
+ #
11
+ # If Capp doesn't understand your packets you can extract the data by editing
12
+ # capp.c and submitting a patch. See README for the source code location.
13
+ #
14
+ # To look up IP source and destination names Resolv (from the ruby standard
15
+ # library, require 'resolv') to avoid blocking on name lookups in a
16
+ # cross-platform manner.
17
+
18
+ class Capp::Packet
19
+
20
+ ADDRESS_CACHE = {} # :nodoc:
21
+
22
+ ##
23
+ # ARP header. See RFC 826
24
+
25
+ ARPHeader = Struct.new :hardware, :protocol, :operation,
26
+ :sender_hardware_address, :sender_protocol_address,
27
+ :target_hardware_address, :target_protocol_address do
28
+ alias sha sender_hardware_address
29
+ alias spa sender_protocol_address
30
+ alias tha target_hardware_address
31
+ alias tpa target_protocol_address
32
+ end
33
+
34
+ ##
35
+ # 802.3 Ethernet header
36
+
37
+ EthernetHeader = Struct.new :destination, :source, :type
38
+
39
+ ##
40
+ # ICMP header. See RFC 792
41
+
42
+ ICMPHeader = Struct.new :type, :code, :checksum, :data
43
+
44
+ ##
45
+ # IPv4 header. See RFC 791
46
+
47
+ IPv4Header = Struct.new :version, :ihl, :tos, :length,
48
+ :id, :offset,
49
+ :ttl, :protocol, :checksum,
50
+ :source, :destination
51
+
52
+ ##
53
+ # IPv6 header. See RFC 2460
54
+
55
+ IPv6Header = Struct.new :version, :traffic_class, :flow_label,
56
+ :payload_length, :next_header, :hop_limit,
57
+ :source, :destination
58
+
59
+ ##
60
+ # TCP header. See RFC 793
61
+
62
+ TCPHeader = Struct.new :source_port, :destination_port,
63
+ :seq_number, :ack_number,
64
+ :offset, :flags, :window, :checksum, :urgent do
65
+
66
+ alias source source_port
67
+ alias destination destination_port
68
+
69
+ ##
70
+ # Is the acknowledgment flag set?
71
+
72
+ def ack?
73
+ Capp::TCP_ACK == flags & Capp::TCP_ACK
74
+ end
75
+
76
+ ##
77
+ # Is the congestion window reduced flag set?
78
+
79
+ def cwr?
80
+ Capp::TCP_CWR == flags & Capp::TCP_CWR
81
+ end
82
+
83
+ ##
84
+ # Is the explicit congestion notification echo flag set?
85
+
86
+ def ece?
87
+ Capp::TCP_ECE == flags & Capp::TCP_ECE
88
+ end
89
+
90
+ ##
91
+ # Is the no-more-data flag set?
92
+
93
+ def fin?
94
+ Capp::TCP_FIN == flags & Capp::TCP_FIN
95
+ end
96
+
97
+ ##
98
+ # Is the push flag set?
99
+
100
+ def push?
101
+ Capp::TCP_PUSH == flags & Capp::TCP_PUSH
102
+ end
103
+
104
+ ##
105
+ # Is the reset flag set?
106
+
107
+ def rst?
108
+ Capp::TCP_RST == flags & Capp::TCP_RST
109
+ end
110
+
111
+ ##
112
+ # Is the synchronize flag set?
113
+
114
+ def syn?
115
+ Capp::TCP_SYN == flags & Capp::TCP_SYN
116
+ end
117
+
118
+ ##
119
+ # Is the urgent flag set?
120
+
121
+ def urg?
122
+ Capp::TCP_URG == flags & Capp::TCP_URG
123
+ end
124
+
125
+ end
126
+
127
+ ##
128
+ # UDP header. See RFC 768
129
+
130
+ UDPHeader = Struct.new :source_port, :destination_port, :length, :checksum do
131
+ alias source source_port
132
+ alias destination destination_port
133
+ end
134
+
135
+ ##
136
+ # Fake header for an unknown layer 3 protocol. See also
137
+ # Capp::Packet#unknown_layer3_header
138
+
139
+ UnknownLayer3Header = Struct.new :payload_offset
140
+
141
+ ##
142
+ # Length of packet that was captured
143
+
144
+ attr_reader :capture_length
145
+
146
+ ##
147
+ # Captured portion of the entire packet including datalink layer.
148
+
149
+ attr_reader :captured
150
+
151
+ ##
152
+ # The ARP header if this is an ARP packet.
153
+
154
+ attr_reader :arp_header
155
+
156
+ ##
157
+ # The Ethernet header if this is an Ethernet packet.
158
+
159
+ attr_reader :ethernet_header
160
+
161
+ ##
162
+ # Array of protocol names in this packet. This list is ordered from lowest
163
+ # to highest level.
164
+
165
+ attr_reader :protocols
166
+
167
+ ##
168
+ # ICMP header if this is an ICMP (v4) packet.
169
+
170
+ attr_reader :icmp_header
171
+
172
+ ##
173
+ # IPv4 header if this is an IPv4 packet.
174
+
175
+ attr_reader :ipv4_header
176
+
177
+ ##
178
+ # IPv6 header if this is an IPv6 packet.
179
+
180
+ attr_reader :ipv6_header
181
+
182
+ ##
183
+ # Total length of packet including the portion not captured.
184
+
185
+ attr_reader :length
186
+
187
+ ##
188
+ # TCP header if this is a TCP packet.
189
+
190
+ attr_reader :tcp_header
191
+
192
+ ##
193
+ # Packet capture timestamp
194
+
195
+ attr_reader :timestamp
196
+
197
+ ##
198
+ # UDP header if this is a UDP packet.
199
+
200
+ attr_reader :udp_header
201
+
202
+ ##
203
+ # Fake header for unknown layer 3 protocols. The datalink type will
204
+ # indicate the layer 3 protocol. For an Ethernet packet see the
205
+ # ethernet_header for the type, etc. This method only provides the payload
206
+ # offset of the packet content.
207
+
208
+ attr_reader :unknown_layer3_header
209
+
210
+ ##
211
+ # Creates a new packet. Ordinarily this is performed from Capp#loop. The
212
+ # +timestamp+ is the packet capture timestamp, +length+ is the total length
213
+ # of the packet, +capture_length+ is the number of captured bytes from the
214
+ # packet. The +datalink+ is the type of link the packet was captured on.
215
+ # +headers+ is a Hash of parsed headers.
216
+
217
+ def initialize timestamp, length, capture_length, captured, datalink, headers
218
+ @capture_length = capture_length
219
+ @captured = captured
220
+ @datalink = datalink
221
+ @length = length
222
+ @protocols = headers.keys
223
+ @timestamp = timestamp
224
+
225
+ @arp_header = headers[:arp]
226
+ @ethernet_header = headers[:ethernet]
227
+ @icmp_header = headers[:icmp]
228
+ @ipv4_header = headers[:ipv4]
229
+ @ipv6_header = headers[:ipv6]
230
+ @tcp_header = headers[:tcp]
231
+ @udp_header = headers[:udp]
232
+ @unknown_layer3_header = headers[:unknown_layer3]
233
+ end
234
+
235
+ ##
236
+ # Returns the destination of the packet regardless of protocol
237
+ #
238
+ # If a Resolv-compatible +resolver+ is given the name will be looked up.
239
+
240
+ def destination resolver = nil
241
+ destination =
242
+ if ipv4? then
243
+ @ipv4_header
244
+ elsif ipv6? then
245
+ @ipv6_header
246
+ else
247
+ raise NotImplementedError
248
+ end.destination
249
+
250
+ destination = resolve destination, resolver
251
+
252
+ if tcp? then
253
+ destination << ".#{@tcp_header.destination_port}"
254
+ elsif udp? then
255
+ destination << ".#{@udp_header.destination_port}"
256
+ end
257
+
258
+ destination
259
+ end
260
+
261
+ ##
262
+ # Returns the captured bytes with non-printing characters replaced by "."
263
+
264
+ def dump
265
+ @captured.tr "\000-\037\177-\377", "."
266
+ end
267
+
268
+ ##
269
+ # Dumps the captured packet from +offset+ with offsets, hexadecimal output
270
+ # for the bytes and the ASCII content with non-printing characters replaced
271
+ # by "."
272
+
273
+ def hexdump offset = 0
274
+ data = @captured[offset, @capture_length]
275
+
276
+ data.scan(/.{,16}/m).map.with_index do |chunk, index|
277
+ next nil if chunk.empty?
278
+ hex = chunk.unpack('C*').map { |byte| '%02x' % byte }
279
+ dump = chunk.tr "\000-\037\177-\377", "."
280
+
281
+ length = hex.length
282
+ hex.fill ' ', length, 16 - length if length < 16
283
+
284
+ "\t0x%04x: %s%s %s%s %s%s %s%s %s%s %s%s %s%s %s%s %s" % [
285
+ index * 16, *hex, dump
286
+ ]
287
+ end.join "\n"
288
+ end
289
+
290
+ ##
291
+ # The payload of the packet.
292
+ #
293
+ # For example, for a UDP packet captured from an Ethernet interface this is
294
+ # payload after the Ethernet, IP and UDP headers
295
+
296
+ def payload
297
+ @captured[payload_offset, @capture_length]
298
+ end
299
+
300
+ ##
301
+ # The offset into the captured data where the payload starts.
302
+ #
303
+ # Note that this method does not work properly for IPv6 packets with options
304
+ # set, but I have yet to encounter such an example in the wild.
305
+
306
+ def payload_offset
307
+ offset =
308
+ case @datalink
309
+ when Capp::DLT_NULL then
310
+ 4
311
+ when Capp::DLT_EN10MB then
312
+ 14
313
+ end
314
+
315
+ case
316
+ when ipv4? then offset += @ipv4_header.ihl * 4
317
+ when ipv6? then offset += 40
318
+ else raise NotImplementedError
319
+ end
320
+
321
+ case
322
+ when tcp? then offset += @tcp_header.offset * 4
323
+ when udp? then offset += 8
324
+ else raise NotImplementedError
325
+ end
326
+
327
+ offset
328
+ end
329
+
330
+ def resolve address, resolver # :nodoc:
331
+ return address.dup unless resolver
332
+
333
+ if name = ADDRESS_CACHE[address] then
334
+ return name.dup
335
+ end
336
+
337
+ name = resolver.getname address
338
+
339
+ ADDRESS_CACHE[address] = name
340
+
341
+ name.dup
342
+ rescue Resolv::ResolvError
343
+ ADDRESS_CACHE[address] = address
344
+ address.dup
345
+ end
346
+
347
+ ##
348
+ # Returns the source of the packet regardless of protocol.
349
+ #
350
+ # If a Resolv-compatible +resolver+ is given the name will be looked up.
351
+
352
+ def source resolver = nil
353
+ source =
354
+ if ipv4? then
355
+ @ipv4_header
356
+ elsif ipv6? then
357
+ @ipv6_header
358
+ else
359
+ raise NotImplementedError
360
+ end.source.dup
361
+
362
+ source = resolve source, resolver
363
+
364
+ if tcp? then
365
+ source << ".#{@tcp_header.source_port}"
366
+ elsif udp? then
367
+ source << ".#{@udp_header.source_port}"
368
+ end
369
+
370
+ source
371
+ end
372
+
373
+ ##
374
+ # Is this an IPv4 packet?
375
+
376
+ def ipv4?
377
+ @ipv4_header
378
+ end
379
+
380
+ ##
381
+ # Is this an IPv6 packet?
382
+
383
+ def ipv6?
384
+ @ipv6_header
385
+ end
386
+
387
+ ##
388
+ # Is this a TCP packet?
389
+
390
+ def tcp?
391
+ @tcp_header
392
+ end
393
+
394
+ ##
395
+ # Is this a UDP packet?
396
+
397
+ def udp?
398
+ @udp_header
399
+ end
400
+
401
+ end
402
+