capp 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+