pcapr-local 0.1.10
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.
- data/.document +5 -0
- data/LICENSE.txt +20 -0
- data/README.md +64 -0
- data/Rakefile +57 -0
- data/VERSION +1 -0
- data/bin/pcap2par +49 -0
- data/bin/startpcapr +40 -0
- data/bin/stoppcapr +33 -0
- data/bin/xtractr +5 -0
- data/lib/environment.rb +106 -0
- data/lib/exe/xtractr +0 -0
- data/lib/mu/pcap.rb +110 -0
- data/lib/mu/pcap/ethernet.rb +148 -0
- data/lib/mu/pcap/header.rb +75 -0
- data/lib/mu/pcap/io_pair.rb +67 -0
- data/lib/mu/pcap/io_wrapper.rb +76 -0
- data/lib/mu/pcap/ip.rb +61 -0
- data/lib/mu/pcap/ipv4.rb +257 -0
- data/lib/mu/pcap/ipv6.rb +148 -0
- data/lib/mu/pcap/packet.rb +104 -0
- data/lib/mu/pcap/pkthdr.rb +155 -0
- data/lib/mu/pcap/reader.rb +61 -0
- data/lib/mu/pcap/reader/http_family.rb +170 -0
- data/lib/mu/pcap/sctp.rb +367 -0
- data/lib/mu/pcap/sctp/chunk.rb +123 -0
- data/lib/mu/pcap/sctp/chunk/data.rb +134 -0
- data/lib/mu/pcap/sctp/chunk/init.rb +100 -0
- data/lib/mu/pcap/sctp/chunk/init_ack.rb +68 -0
- data/lib/mu/pcap/sctp/parameter.rb +110 -0
- data/lib/mu/pcap/sctp/parameter/ip_address.rb +48 -0
- data/lib/mu/pcap/stream_packetizer.rb +72 -0
- data/lib/mu/pcap/tcp.rb +505 -0
- data/lib/mu/pcap/udp.rb +69 -0
- data/lib/mu/scenario/pcap.rb +164 -0
- data/lib/mu/scenario/pcap/fields.rb +50 -0
- data/lib/mu/scenario/pcap/rtp.rb +71 -0
- data/lib/pcapr_local.rb +159 -0
- data/lib/pcapr_local/config.rb +336 -0
- data/lib/pcapr_local/db.rb +197 -0
- data/lib/pcapr_local/scanner.rb +250 -0
- data/lib/pcapr_local/server.rb +178 -0
- data/lib/pcapr_local/www/favicon.ico +0 -0
- data/lib/pcapr_local/www/favicon.png +0 -0
- data/lib/pcapr_local/www/home/index.html +138 -0
- data/lib/pcapr_local/www/static/image/16x16/Cancel.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Cancel.png.1 +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Download.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Folder3.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Full Size.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Minus.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Plus.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/Search.png +0 -0
- data/lib/pcapr_local/www/static/image/16x16/User.png +0 -0
- data/lib/pcapr_local/www/static/image/48x48/Phone.png +0 -0
- data/lib/pcapr_local/www/static/image/48x48/Video.png +0 -0
- data/lib/pcapr_local/www/static/image/bar-orange.gif +0 -0
- data/lib/pcapr_local/www/static/image/beta.png +0 -0
- data/lib/pcapr_local/www/static/image/bg.png +0 -0
- data/lib/pcapr_local/www/static/image/blockquote.png +0 -0
- data/lib/pcapr_local/www/static/image/body-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/body-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl1-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl1-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl1-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl2-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl2-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl2-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl3-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl3-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl3-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl4-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl4-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl4-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl5-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl6-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl7-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-hl8-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/body-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/bottom-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/bottom-l.png +0 -0
- data/lib/pcapr_local/www/static/image/bottom-r.png +0 -0
- data/lib/pcapr_local/www/static/image/btn-search.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-1.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-2.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-3.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-4.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-5.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-6.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-7.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-hl1.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-hl2.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-hl3.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-hl4.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-pathway.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-section1.png +0 -0
- data/lib/pcapr_local/www/static/image/bullet-section2.png +0 -0
- data/lib/pcapr_local/www/static/image/collapsed.gif +0 -0
- data/lib/pcapr_local/www/static/image/crosslink.png +0 -0
- data/lib/pcapr_local/www/static/image/expanded.gif +0 -0
- data/lib/pcapr_local/www/static/image/favicon.ico +0 -0
- data/lib/pcapr_local/www/static/image/favicon.png +0 -0
- data/lib/pcapr_local/www/static/image/icon-author.png +0 -0
- data/lib/pcapr_local/www/static/image/icon-created.png +0 -0
- data/lib/pcapr_local/www/static/image/p-expand.gif +0 -0
- data/lib/pcapr_local/www/static/image/pcapr-logo.png +0 -0
- data/lib/pcapr_local/www/static/image/powered-by.png +0 -0
- data/lib/pcapr_local/www/static/image/section1-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/section1-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/section1-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/section2-bg.png +0 -0
- data/lib/pcapr_local/www/static/image/section2-h3.png +0 -0
- data/lib/pcapr_local/www/static/image/section2-readmore.png +0 -0
- data/lib/pcapr_local/www/static/image/status-alert.png +0 -0
- data/lib/pcapr_local/www/static/image/status-download.png +0 -0
- data/lib/pcapr_local/www/static/image/status-info.png +0 -0
- data/lib/pcapr_local/www/static/image/status-note.png +0 -0
- data/lib/pcapr_local/www/static/image/tab-round.png +0 -0
- data/lib/pcapr_local/www/static/image/throbber.gif +0 -0
- data/lib/pcapr_local/www/static/image/user.jpg +0 -0
- data/lib/pcapr_local/www/static/script/closet/async.js +421 -0
- data/lib/pcapr_local/www/static/script/closet/closet.api.js +241 -0
- data/lib/pcapr_local/www/static/script/closet/closet.folders.js +94 -0
- data/lib/pcapr_local/www/static/script/closet/closet.js +187 -0
- data/lib/pcapr_local/www/static/script/closet/closet.mr.js +219 -0
- data/lib/pcapr_local/www/static/script/closet/closet.options.js +359 -0
- data/lib/pcapr_local/www/static/script/closet/closet.quantity.js +73 -0
- data/lib/pcapr_local/www/static/script/closet/closet.render.js +205 -0
- data/lib/pcapr_local/www/static/script/closet/closet.report.js +86 -0
- data/lib/pcapr_local/www/static/script/closet/closet.reports.http.js +135 -0
- data/lib/pcapr_local/www/static/script/closet/closet.reports.overview.js +163 -0
- data/lib/pcapr_local/www/static/script/closet/closet.reports.sip.js +159 -0
- data/lib/pcapr_local/www/static/script/closet/closet.reports.tcp.js +72 -0
- data/lib/pcapr_local/www/static/script/closet/closet.reports.visualize.js +263 -0
- data/lib/pcapr_local/www/static/script/closet/closet.util.js +40 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery-1.4.2.min.js +154 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery-ui.js +10921 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.flot.js +2123 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.flot.selection.js +184 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.flot.stack.js +184 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.form.js +643 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.jsonp.min.js +3 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.menu.js +142 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.suggest.js +308 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.ui.core.js +203 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.ui.slider.js +629 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.ui.sortable.js +1055 -0
- data/lib/pcapr_local/www/static/script/jquery/jquery.ui.widget.js +236 -0
- data/lib/pcapr_local/www/static/script/json2.js +481 -0
- data/lib/pcapr_local/www/static/script/sammy/plugins/sammy.cache.js +115 -0
- data/lib/pcapr_local/www/static/script/sammy/plugins/sammy.template.js +117 -0
- data/lib/pcapr_local/www/static/script/sammy/sammy.js +1696 -0
- data/lib/pcapr_local/www/static/script/tipsy/jquery.tipsy.js +104 -0
- data/lib/pcapr_local/www/static/style/c3p0.css +116 -0
- data/lib/pcapr_local/www/static/style/jquery.suggest.css +27 -0
- data/lib/pcapr_local/www/static/style/page.css +1113 -0
- data/lib/pcapr_local/www/static/style/tipsy.css +7 -0
- data/lib/pcapr_local/www/templates/browse.services.template +10 -0
- data/lib/pcapr_local/www/templates/browse.template +77 -0
- data/lib/pcapr_local/www/templates/flows.template +38 -0
- data/lib/pcapr_local/www/templates/pcap.template +63 -0
- data/lib/pcapr_local/www/templates/sip.calls.template +35 -0
- data/lib/pcapr_local/www/templates/statistics.template +6 -0
- data/lib/pcapr_local/xtractr.rb +179 -0
- data/lib/pcapr_local/xtractr/instance.rb +172 -0
- data/pcapr-local.gemspec +297 -0
- data/test/mu/pcap/reader/tc_http_family.rb +251 -0
- data/test/mu/pcap/tc_ethernet.rb +71 -0
- data/test/mu/pcap/tc_header.rb +56 -0
- data/test/mu/pcap/tc_ipv4.rb +103 -0
- data/test/mu/pcap/tc_ipv6.rb +83 -0
- data/test/mu/pcap/tc_packet.rb +44 -0
- data/test/mu/pcap/tc_pair.rb +58 -0
- data/test/mu/pcap/tc_pkthdr.rb +33 -0
- data/test/mu/pcap/tc_reader.rb +76 -0
- data/test/mu/pcap/tc_tcp.rb +426 -0
- data/test/mu/pcap/tc_udp.rb +33 -0
- data/test/mu/pcap/tc_wrapper.rb +80 -0
- data/test/mu/scenario/pcap/tc_fields.rb +67 -0
- data/test/mu/scenario/pcap/tc_rtp.rb +135 -0
- data/test/mu/scenario/sip_signalled_call_1.pcap +0 -0
- data/test/mu/scenario/tc_pcap.rb +190 -0
- data/test/mu/scenario/test_data/arp.pcap +0 -0
- data/test/mu/scenario/test_data/dns.pcap +0 -0
- data/test/mu/scenario/test_data/http-v6.pcap +0 -0
- data/test/mu/scenario/test_data/http.pcap +0 -0
- data/test/mu/scenario/test_data/http_chunked.pcap +0 -0
- data/test/mu/scenario/test_data/http_deflate.pcap +0 -0
- data/test/mu/scenario/test_data/httpauth3.pcap +0 -0
- data/test/mu/scenario/test_data/icmp.pcap +0 -0
- data/test/mu/scenario/test_data/sip_signalled_call_1.pcap +0 -0
- data/test/mu/tc_pcap.rb +39 -0
- data/test/mu/testcase.rb +86 -0
- data/test/pcapr_local/arp.pcap +0 -0
- data/test/pcapr_local/data.js +3 -0
- data/test/pcapr_local/http_chunked.pcap +0 -0
- data/test/pcapr_local/tc_api.rb +181 -0
- data/test/pcapr_local/test.tgz +0 -0
- data/test/pcapr_local/test_scanner.rb +241 -0
- data/test/pcapr_local/test_xtractr.rb +219 -0
- data/test/pcapr_local/testcase.rb +107 -0
- data/test/test_export_to_scenario.sh +25 -0
- data/test/test_pcapr_local.rb +29 -0
- metadata +450 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# http://www.mudynamics.com
|
|
2
|
+
# http://labs.mudynamics.com
|
|
3
|
+
# http://www.pcapr.net
|
|
4
|
+
|
|
5
|
+
module Mu
|
|
6
|
+
class Pcap
|
|
7
|
+
|
|
8
|
+
class Ethernet < Packet
|
|
9
|
+
attr_accessor :src, :dst, :type
|
|
10
|
+
|
|
11
|
+
ETHERTYPE_IP = 0x0800
|
|
12
|
+
ETHERTYPE_IP6 = 0x86dd
|
|
13
|
+
ETHERTYPE_ARP = 0x0806
|
|
14
|
+
ETHERTYPE_PPPOE_SESSION = 0x8864
|
|
15
|
+
ETHERTYPE_802_1Q = 0X8100
|
|
16
|
+
|
|
17
|
+
PPP_IP = 0x0021
|
|
18
|
+
PPP_IPV6 = 0x0057
|
|
19
|
+
|
|
20
|
+
def initialize src=nil, dst=nil, type=0
|
|
21
|
+
super()
|
|
22
|
+
@src = src
|
|
23
|
+
@dst = dst
|
|
24
|
+
@type = type
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def flow_id
|
|
28
|
+
if not @payload or @payload.is_a? String
|
|
29
|
+
return [:ethernet, @src, @dst, @type]
|
|
30
|
+
else
|
|
31
|
+
return @payload.flow_id
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
FMT_MAC = "C6"
|
|
36
|
+
FMT_n = 'n'
|
|
37
|
+
MAC_TEMPLATE = '%02x:%02x:%02x:%02x:%02x:%02x'
|
|
38
|
+
def self.from_bytes bytes
|
|
39
|
+
if bytes.length < 14
|
|
40
|
+
raise ParseError, "Truncated Ethernet header: expected 14 bytes, got #{bytes.length} bytes"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
dst = bytes.slice!(0,6).unpack FMT_MAC
|
|
44
|
+
dst = MAC_TEMPLATE % dst
|
|
45
|
+
src = bytes.slice!(0,6).unpack FMT_MAC
|
|
46
|
+
src = MAC_TEMPLATE % src
|
|
47
|
+
type = bytes.slice!(0,2).unpack(FMT_n)[0]
|
|
48
|
+
while (type == ETHERTYPE_802_1Q)
|
|
49
|
+
# Skip 4 bytes for 802.1q vlan tag field
|
|
50
|
+
bytes.slice!(0,2)
|
|
51
|
+
type = bytes.slice!(0,2).unpack(FMT_n)[0]
|
|
52
|
+
end
|
|
53
|
+
ethernet = Ethernet.new src, dst, type
|
|
54
|
+
ethernet.payload = bytes
|
|
55
|
+
ethernet.payload_raw = bytes
|
|
56
|
+
begin
|
|
57
|
+
case type
|
|
58
|
+
when ETHERTYPE_IP
|
|
59
|
+
ethernet.payload = IPv4.from_bytes bytes
|
|
60
|
+
when ETHERTYPE_IP6
|
|
61
|
+
ethernet.payload = IPv6.from_bytes bytes
|
|
62
|
+
when ETHERTYPE_PPPOE_SESSION
|
|
63
|
+
# Remove PPPoE/PPP session layer
|
|
64
|
+
ethernet.payload = bytes
|
|
65
|
+
ethernet.remove_pppoe!
|
|
66
|
+
else
|
|
67
|
+
ethernet.payload = bytes
|
|
68
|
+
end
|
|
69
|
+
rescue ParseError => e
|
|
70
|
+
Pcap.warning e
|
|
71
|
+
end
|
|
72
|
+
return ethernet
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def ip?
|
|
76
|
+
return payload.is_a?(IP)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
ADDR_TO_BYTES = {}
|
|
80
|
+
FMT_HEADER = 'a6a6n'
|
|
81
|
+
def write io
|
|
82
|
+
dst_mac = ADDR_TO_BYTES[@dst] ||= @dst.split(':').inject('') {|m, b| m << b.to_i(16).chr}
|
|
83
|
+
src_mac = ADDR_TO_BYTES[@src] ||= @src.split(':').inject('') {|m, b| m << b.to_i(16).chr}
|
|
84
|
+
bytes = [dst_mac, src_mac, @type].pack(FMT_HEADER)
|
|
85
|
+
io.write bytes
|
|
86
|
+
if @payload.is_a? String
|
|
87
|
+
io.write @payload
|
|
88
|
+
else
|
|
89
|
+
@payload.write io
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Remove the PPPoE and PPP headers. PPPoE is documented in RFC 2516.
|
|
94
|
+
def remove_pppoe!
|
|
95
|
+
bytes = self.payload_raw
|
|
96
|
+
|
|
97
|
+
# Remove PPPoE header
|
|
98
|
+
Pcap.assert bytes.length >= 6, 'Truncated PPPoE header: ' +
|
|
99
|
+
"expected at least 6 bytes, got #{bytes.length} bytes"
|
|
100
|
+
version_type, code, session_id, length = bytes.unpack 'CCnn'
|
|
101
|
+
version = version_type >> 4 & 0b1111
|
|
102
|
+
type = version_type & 0b1111
|
|
103
|
+
Pcap.assert version == 1, "Unknown PPPoE version: #{version}"
|
|
104
|
+
Pcap.assert type == 1, "Unknown PPPoE type: #{type}"
|
|
105
|
+
Pcap.assert code == 0, "Unknown PPPoE code: #{code}"
|
|
106
|
+
bytes = bytes[6..-1]
|
|
107
|
+
Pcap.assert bytes.length >= length, 'Truncated PPoE packet: ' +
|
|
108
|
+
"expected #{length} bytes, got #{bytes.length} bytes"
|
|
109
|
+
|
|
110
|
+
# Remove PPP header
|
|
111
|
+
Pcap.assert bytes.length >= 2, 'Truncated PPP packet: ' +
|
|
112
|
+
"expected at least bytes, got #{bytes.length} bytes"
|
|
113
|
+
protocol_id, = bytes.unpack 'n'
|
|
114
|
+
bytes = bytes[2..-1]
|
|
115
|
+
case protocol_id
|
|
116
|
+
when PPP_IP
|
|
117
|
+
self.payload = IPv4.from_bytes bytes
|
|
118
|
+
self.payload_raw = bytes
|
|
119
|
+
self.type = ETHERTYPE_IP
|
|
120
|
+
when PPP_IPV6
|
|
121
|
+
self.payload = IPv6.from_bytes bytes
|
|
122
|
+
self.payload_raw = bytes
|
|
123
|
+
self.type = ETHERTYPE_IP6
|
|
124
|
+
else
|
|
125
|
+
# Failed. Don't update payload or type.
|
|
126
|
+
raise ParseError, "Unknown PPP protocol: 0x#{'%04x' % protocol_id}"
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def to_s
|
|
131
|
+
if @payload.is_a? String
|
|
132
|
+
payload = @payload.inspect
|
|
133
|
+
else
|
|
134
|
+
payload = @payload.to_s
|
|
135
|
+
end
|
|
136
|
+
return "ethernet(%s, %s, %d, %s)" % [@src, @dst, @type, payload]
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def == other
|
|
140
|
+
return super &&
|
|
141
|
+
self.src == other.src &&
|
|
142
|
+
self.dst == other.dst &&
|
|
143
|
+
self.type == other.type
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# http://www.mudynamics.com
|
|
2
|
+
# http://labs.mudynamics.com
|
|
3
|
+
# http://www.pcapr.net
|
|
4
|
+
|
|
5
|
+
module Mu
|
|
6
|
+
class Pcap
|
|
7
|
+
|
|
8
|
+
class Header
|
|
9
|
+
attr_accessor :magic, :version_major, :version_minor, :thiszone, :sigfigs,
|
|
10
|
+
:snaplen, :linktype
|
|
11
|
+
|
|
12
|
+
BIG_ENDIAN_FORMAT = 'nnNNNN'
|
|
13
|
+
LITTLE_ENDIAN_FORMAT = 'vvVVVV'
|
|
14
|
+
|
|
15
|
+
UNSUPPORTED_FORMATS = {
|
|
16
|
+
0x474D4255 => "NetMon", # "GMBU"
|
|
17
|
+
0x5452534E => "NA Sniffer (DOS)" # Starts with "TRSNIFF data"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
def initialize
|
|
21
|
+
@magic = BIG_ENDIAN
|
|
22
|
+
@version_major = 2
|
|
23
|
+
@version_minor = 4
|
|
24
|
+
@thiszone = 0
|
|
25
|
+
@sigfigs = 0
|
|
26
|
+
@snaplen = 1500
|
|
27
|
+
@linktype = DLT_NULL
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.read ios
|
|
31
|
+
header = Header.new
|
|
32
|
+
bytes = ios.read 24
|
|
33
|
+
Pcap.assert bytes, 'PCAP header missing'
|
|
34
|
+
Pcap.assert bytes.length == 24, 'Truncated PCAP header: ' +
|
|
35
|
+
"expected 24 bytes, got #{bytes.length} bytes"
|
|
36
|
+
header.magic, _ = bytes[0, 4].unpack 'N'
|
|
37
|
+
if header.magic == BIG_ENDIAN
|
|
38
|
+
format = BIG_ENDIAN_FORMAT
|
|
39
|
+
elsif header.magic == LITTLE_ENDIAN
|
|
40
|
+
format = LITTLE_ENDIAN_FORMAT
|
|
41
|
+
else
|
|
42
|
+
format = UNSUPPORTED_FORMATS[header.magic]
|
|
43
|
+
if format.nil?
|
|
44
|
+
err = "Unsupported packet capture format. "
|
|
45
|
+
else
|
|
46
|
+
err = "#{format} capture files are not supported. "
|
|
47
|
+
end
|
|
48
|
+
raise ParseError, err
|
|
49
|
+
end
|
|
50
|
+
header.version_major, header.version_minor, header.thiszone,
|
|
51
|
+
header.sigfigs, header.snaplen, header.linktype =
|
|
52
|
+
bytes[4..-1].unpack format
|
|
53
|
+
return header
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def write io
|
|
57
|
+
bytes = [BIG_ENDIAN, @version_major, @version_minor, @thiszone,
|
|
58
|
+
@sigfigs, @snaplen, DLT_EN10MB].pack('N' + BIG_ENDIAN_FORMAT)
|
|
59
|
+
io.write bytes
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def == other
|
|
63
|
+
return self.class == other.class &&
|
|
64
|
+
self.magic == other.magic &&
|
|
65
|
+
self.version_major == other.version_major &&
|
|
66
|
+
self.version_minor == other.version_minor &&
|
|
67
|
+
self.thiszone == other.thiszone &&
|
|
68
|
+
self.sigfigs == other.sigfigs &&
|
|
69
|
+
self.snaplen == other.snaplen &&
|
|
70
|
+
self.linktype == other.linktype
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# http://www.mudynamics.com
|
|
2
|
+
# http://labs.mudynamics.com
|
|
3
|
+
# http://www.pcapr.net
|
|
4
|
+
|
|
5
|
+
module Mu
|
|
6
|
+
class Pcap
|
|
7
|
+
|
|
8
|
+
# For emulating of a pair of connected sockets. Bytes written
|
|
9
|
+
# with #write to one side are returned by a subsequent #read on
|
|
10
|
+
# the other side.
|
|
11
|
+
#
|
|
12
|
+
# Use Pair.stream_pair to get a pair with stream semantics.
|
|
13
|
+
# Use Pair.packet_pair to get a pair with packet semantics.
|
|
14
|
+
class IOPair
|
|
15
|
+
attr_reader :read_queue
|
|
16
|
+
attr_accessor :other
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
raise NotImplementedError
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.stream_pair
|
|
23
|
+
io1 = Stream.new
|
|
24
|
+
io2 = Stream.new
|
|
25
|
+
io1.other = io2
|
|
26
|
+
io2.other = io1
|
|
27
|
+
return io1, io2
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.packet_pair
|
|
31
|
+
io1 = Packet.new
|
|
32
|
+
io2 = Packet.new
|
|
33
|
+
io1.other = io2
|
|
34
|
+
io2.other = io1
|
|
35
|
+
return io1, io2
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def write bytes
|
|
39
|
+
@other.read_queue << bytes
|
|
40
|
+
bytes.size
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class Stream < IOPair
|
|
44
|
+
def initialize
|
|
45
|
+
@read_queue = ""
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def read n=nil
|
|
49
|
+
n ||= @read_queue.size
|
|
50
|
+
@read_queue.slice!(0,n)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class Packet < IOPair
|
|
55
|
+
def initialize
|
|
56
|
+
@read_queue = []
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def read
|
|
60
|
+
@read_queue.shift
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# http://www.mudynamics.com
|
|
2
|
+
# http://labs.mudynamics.com
|
|
3
|
+
# http://www.pcapr.net
|
|
4
|
+
|
|
5
|
+
require 'mu/pcap/io_pair'
|
|
6
|
+
|
|
7
|
+
module Mu
|
|
8
|
+
class Pcap
|
|
9
|
+
class IOWrapper
|
|
10
|
+
attr_reader :ios, :unread, :state
|
|
11
|
+
|
|
12
|
+
def initialize ios, reader
|
|
13
|
+
@ios = ios
|
|
14
|
+
@reader = reader
|
|
15
|
+
# parse state for reader
|
|
16
|
+
@state = {}
|
|
17
|
+
# read off of underlying io but not yet processed by @reader
|
|
18
|
+
@unread = ""
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Impose upper limit to protect against memory exhaustion.
|
|
22
|
+
MAX_RECEIVE_SIZE = 1048576 # 1MB
|
|
23
|
+
|
|
24
|
+
# Returns next higher level protocol message.
|
|
25
|
+
def read
|
|
26
|
+
until message = @reader.read_message!(@unread, @state)
|
|
27
|
+
bytes = @ios.read
|
|
28
|
+
if bytes and not bytes.empty?
|
|
29
|
+
@unread << bytes
|
|
30
|
+
else
|
|
31
|
+
return nil
|
|
32
|
+
end
|
|
33
|
+
if @unread.size > MAX_RECEIVE_SIZE
|
|
34
|
+
raise "Maximum message size (#{MAX_RECEIVE_SIZE}) exceeded"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
return message
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Parser may need to see requests to understand responses.
|
|
42
|
+
def record_write bytes
|
|
43
|
+
@reader.record_write bytes, @state
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def write bytes, *args
|
|
47
|
+
w = @ios.write bytes, *args
|
|
48
|
+
record_write bytes
|
|
49
|
+
w
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def write_to bytes, *args
|
|
53
|
+
w = @ios.write_to bytes, *args
|
|
54
|
+
record_write bytes
|
|
55
|
+
w
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def open
|
|
59
|
+
if block_given?
|
|
60
|
+
@ios.open { yield }
|
|
61
|
+
else
|
|
62
|
+
@ios.open
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def open?
|
|
67
|
+
@ios.open?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def close
|
|
71
|
+
@ios.close
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
data/lib/mu/pcap/ip.rb
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# http://www.mudynamics.com
|
|
2
|
+
# http://labs.mudynamics.com
|
|
3
|
+
# http://www.pcapr.net
|
|
4
|
+
|
|
5
|
+
module Mu
|
|
6
|
+
class Pcap
|
|
7
|
+
|
|
8
|
+
class IP < Packet
|
|
9
|
+
IPPROTO_TCP = 6
|
|
10
|
+
IPPROTO_UDP = 17
|
|
11
|
+
IPPROTO_HOPOPTS = 0
|
|
12
|
+
IPPROTO_ROUTING = 43
|
|
13
|
+
IPPROTO_FRAGMENT = 44
|
|
14
|
+
IPPROTO_AH = 51
|
|
15
|
+
IPPROTO_NONE = 59
|
|
16
|
+
IPPROTO_DSTOPTS = 60
|
|
17
|
+
IPPROTO_SCTP = 132
|
|
18
|
+
|
|
19
|
+
attr_accessor :src, :dst
|
|
20
|
+
|
|
21
|
+
def initialize src=nil, dst=nil
|
|
22
|
+
super()
|
|
23
|
+
@src = src
|
|
24
|
+
@dst = dst
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def v4?
|
|
28
|
+
return false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def v6?
|
|
32
|
+
return false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def proto
|
|
36
|
+
raise NotImplementedError
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def pseudo_header payload_length
|
|
40
|
+
raise NotImplementedError
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def == other
|
|
44
|
+
return super &&
|
|
45
|
+
self.src == other.src &&
|
|
46
|
+
self.dst == other.dst
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.checksum bytes
|
|
50
|
+
if bytes.size & 1 == 1
|
|
51
|
+
bytes = bytes + "\0"
|
|
52
|
+
end
|
|
53
|
+
sum = 0
|
|
54
|
+
bytes.unpack("n*").each {|n| sum += n }
|
|
55
|
+
sum = (sum & 0xffff) + (sum >> 16 & 0xffff)
|
|
56
|
+
~sum & 0xffff
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
end
|
data/lib/mu/pcap/ipv4.rb
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# http://www.mudynamics.com
|
|
2
|
+
# http://labs.mudynamics.com
|
|
3
|
+
# http://www.pcapr.net
|
|
4
|
+
|
|
5
|
+
require 'ipaddr'
|
|
6
|
+
|
|
7
|
+
module Mu
|
|
8
|
+
class Pcap
|
|
9
|
+
|
|
10
|
+
class IPv4 < IP
|
|
11
|
+
IP_RF = 0x8000 # Reserved
|
|
12
|
+
IP_DF = 0x4000 # Don't fragment
|
|
13
|
+
IP_MF = 0x2000 # More fragments
|
|
14
|
+
IP_OFFMASK = 0x1fff
|
|
15
|
+
|
|
16
|
+
FMT_HEADER = 'CCnnnCCna4a4'
|
|
17
|
+
|
|
18
|
+
attr_accessor :ip_id, :offset, :ttl, :proto, :src, :dst, :dscp
|
|
19
|
+
|
|
20
|
+
def initialize src=nil, dst=nil, ip_id=0, offset=0, ttl=64, proto=0, dscp=0
|
|
21
|
+
super()
|
|
22
|
+
@ip_id = ip_id
|
|
23
|
+
@offset = offset
|
|
24
|
+
@ttl = ttl
|
|
25
|
+
@proto = proto
|
|
26
|
+
@src = src
|
|
27
|
+
@dst = dst
|
|
28
|
+
@dscp = dscp
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def v4?
|
|
32
|
+
return true
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def flow_id
|
|
36
|
+
if not @payload or @payload.is_a? String
|
|
37
|
+
return [:ipv4, @proto, @src, @dst]
|
|
38
|
+
else
|
|
39
|
+
return [:ipv4, @src, @dst, @payload.flow_id]
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
NTOP = {} # Network to human cache
|
|
44
|
+
HTON = {} # Human to network cache
|
|
45
|
+
|
|
46
|
+
def self.from_bytes bytes
|
|
47
|
+
bytes.length >= 20 or
|
|
48
|
+
raise ParseError, "Truncated IPv4 header: expected at least 20 bytes, got #{bytes.length} bytes"
|
|
49
|
+
|
|
50
|
+
vhl, tos, length, id, offset, ttl, proto, checksum, src, dst = bytes[0,20].unpack FMT_HEADER
|
|
51
|
+
version = vhl >> 4
|
|
52
|
+
hl = (vhl & 0b1111) * 4
|
|
53
|
+
|
|
54
|
+
version == 4 or
|
|
55
|
+
raise ParseError, "Wrong IPv4 version: got (#{version})"
|
|
56
|
+
hl >= 20 or
|
|
57
|
+
raise ParseError, "Bad IPv4 header length: expected at least 20 bytes raise ParseError, got #{hl} bytes"
|
|
58
|
+
bytes.length >= hl or
|
|
59
|
+
raise ParseError, "Truncated IPv4 header: expected #{hl} bytes raise ParseError, got #{bytes.length} bytes"
|
|
60
|
+
length >= 20 or
|
|
61
|
+
raise ParseError, "Bad IPv4 packet length: expected at least 20 bytes raise ParseError, got #{length} bytes"
|
|
62
|
+
bytes.length >= length or
|
|
63
|
+
raise ParseError, "Truncated IPv4 packet: expected #{length} bytes raise ParseError, got #{bytes.length} bytes"
|
|
64
|
+
|
|
65
|
+
if hl != 20
|
|
66
|
+
IPv4.check_options bytes[20, hl-20]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
src = NTOP[src] ||= IPAddr.ntop(src)
|
|
70
|
+
dst = NTOP[dst] ||= IPAddr.ntop(dst)
|
|
71
|
+
dscp = tos >> 2
|
|
72
|
+
ipv4 = IPv4.new(src, dst, id, offset, ttl, proto, dscp)
|
|
73
|
+
ipv4.payload_raw = bytes[hl..-1]
|
|
74
|
+
|
|
75
|
+
payload = bytes[hl...length]
|
|
76
|
+
if offset & (IP_OFFMASK | IP_MF) == 0
|
|
77
|
+
begin
|
|
78
|
+
case proto
|
|
79
|
+
when IPPROTO_TCP
|
|
80
|
+
ipv4.payload = TCP.from_bytes payload
|
|
81
|
+
when IPPROTO_UDP
|
|
82
|
+
ipv4.payload = UDP.from_bytes payload
|
|
83
|
+
when IPPROTO_SCTP
|
|
84
|
+
ipv4.payload = SCTP.from_bytes payload
|
|
85
|
+
else
|
|
86
|
+
ipv4.payload = payload
|
|
87
|
+
end
|
|
88
|
+
rescue ParseError => e
|
|
89
|
+
Pcap.warning e
|
|
90
|
+
end
|
|
91
|
+
else
|
|
92
|
+
ipv4.payload = payload
|
|
93
|
+
end
|
|
94
|
+
return ipv4
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def write io
|
|
98
|
+
if @payload.is_a? String
|
|
99
|
+
payload = @payload
|
|
100
|
+
else
|
|
101
|
+
string_io = StringIO.new
|
|
102
|
+
@payload.write string_io, self
|
|
103
|
+
payload = string_io.string
|
|
104
|
+
end
|
|
105
|
+
length = 20 + payload.length
|
|
106
|
+
if length > 65535
|
|
107
|
+
Pcap.warning "IPv4 payload is too large"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
src = HTON[@src] ||= IPAddr.new(@src).hton
|
|
111
|
+
dst = HTON[@dst] ||= IPAddr.new(@dst).hton
|
|
112
|
+
fields = [0x45, @dscp << 2, length, @ip_id, @offset, @ttl, @proto, 0, src, dst]
|
|
113
|
+
header = fields.pack(FMT_HEADER)
|
|
114
|
+
fields[7] = IP.checksum(header)
|
|
115
|
+
header = fields.pack(FMT_HEADER)
|
|
116
|
+
io.write header
|
|
117
|
+
io.write payload
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
FMT_PSEUDO_HEADER = 'a4a4CCn'
|
|
121
|
+
def pseudo_header payload_length
|
|
122
|
+
src = HTON[@src] ||= IPAddr.new(@src).hton
|
|
123
|
+
dst = HTON[@dst] ||= IPAddr.new(@dst).hton
|
|
124
|
+
return [src, dst, 0, @proto, payload_length].pack(FMT_PSEUDO_HEADER)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def fragment?
|
|
128
|
+
return (@offset & (IP_OFFMASK | IP_MF) != 0)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Check that IP or TCP options are valid. Do nothing if they are valid.
|
|
132
|
+
# Both IP and TCP options are 8-bit TLVs with an inclusive length. Both
|
|
133
|
+
# have one byte options 0 and 1.
|
|
134
|
+
def self.check_options options, label='IPv4'
|
|
135
|
+
while not options.empty?
|
|
136
|
+
type = options.slice!(0, 1)[0].ord
|
|
137
|
+
if type == 0 or type == 1
|
|
138
|
+
next
|
|
139
|
+
end
|
|
140
|
+
Pcap.assert !options.empty?,
|
|
141
|
+
"#{label} option #{type} is missing the length field"
|
|
142
|
+
length = options.slice!(0, 1)[0].ord
|
|
143
|
+
Pcap.assert length >= 2,
|
|
144
|
+
"#{label} option #{type} has invalid length: #{length}"
|
|
145
|
+
Pcap.assert length - 2 <= options.length,
|
|
146
|
+
"#{label} option #{type} has truncated data"
|
|
147
|
+
options.slice! 0, length - 2
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
ReassembleState = ::Struct.new :packets, :bytes, :mf, :overlap
|
|
152
|
+
|
|
153
|
+
# Reassemble fragmented IPv4 packets
|
|
154
|
+
def self.reassemble packets
|
|
155
|
+
reassembled_packets = []
|
|
156
|
+
flow_id_to_state = {}
|
|
157
|
+
packets.each do |packet|
|
|
158
|
+
if not packet.is_a?(Ethernet) or not packet.payload.is_a?(IPv4)
|
|
159
|
+
# Ignore non-IPv4 packet
|
|
160
|
+
elsif not packet.payload.fragment?
|
|
161
|
+
# Ignore non-fragments
|
|
162
|
+
else
|
|
163
|
+
# Get reassembly state
|
|
164
|
+
ip = packet.payload
|
|
165
|
+
flow_id = [ip.ip_id, ip.proto, ip.src, ip.dst]
|
|
166
|
+
state = flow_id_to_state[flow_id]
|
|
167
|
+
if not state
|
|
168
|
+
state = ReassembleState.new [], [], true, false
|
|
169
|
+
flow_id_to_state[flow_id] = state
|
|
170
|
+
end
|
|
171
|
+
state.packets << packet
|
|
172
|
+
|
|
173
|
+
# Clear the more-fragments flag if no more fragments
|
|
174
|
+
if ip.offset & IP_MF == 0
|
|
175
|
+
state.mf = false
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Add the bytes
|
|
179
|
+
start = (ip.offset & IP_OFFMASK) * 8
|
|
180
|
+
finish = start + ip.payload.length
|
|
181
|
+
state.bytes.fill nil, start, finish - start
|
|
182
|
+
start.upto(finish-1) do |i|
|
|
183
|
+
if not state.bytes[i]
|
|
184
|
+
byte = ip.payload[i - start].chr
|
|
185
|
+
state.bytes[i] = byte
|
|
186
|
+
elsif not state.overlap
|
|
187
|
+
name = "%s:%s:%d" % [ip.src, ip.dst, ip.proto]
|
|
188
|
+
Pcap.warning \
|
|
189
|
+
"IPv4 flow #{name} contains overlapping fragements"
|
|
190
|
+
state.overlap = true
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# We're done if we've received a fragment without the
|
|
195
|
+
# more-fragments flag and all the bytes in the buffer have been
|
|
196
|
+
# set.
|
|
197
|
+
if not state.mf and state.bytes.all?
|
|
198
|
+
# Remove fragments from reassembled_packets
|
|
199
|
+
state.packets.each do |packet|
|
|
200
|
+
reassembled_packets.delete_if do |reassembled_packet|
|
|
201
|
+
packet.object_id == reassembled_packet.object_id
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
# Remove state
|
|
205
|
+
flow_id_to_state.delete flow_id
|
|
206
|
+
# Create new packet
|
|
207
|
+
packet = state.packets[0].deepdup
|
|
208
|
+
ipv4 = packet.payload
|
|
209
|
+
ipv4.offset = 0
|
|
210
|
+
ipv4.payload = state.bytes.join
|
|
211
|
+
# Decode
|
|
212
|
+
begin
|
|
213
|
+
case ipv4.proto
|
|
214
|
+
when IPPROTO_TCP
|
|
215
|
+
ipv4.payload = TCP.from_bytes ipv4.payload
|
|
216
|
+
when IPPROTO_UDP
|
|
217
|
+
ipv4.payload = UDP.from_bytes ipv4.payload
|
|
218
|
+
when IPPROTO_SCTP
|
|
219
|
+
ipv4.payload = SCTP.from_bytes ipv4.payload
|
|
220
|
+
end
|
|
221
|
+
rescue ParseError => e
|
|
222
|
+
Pcap.warning e
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
reassembled_packets << packet
|
|
227
|
+
end
|
|
228
|
+
if !flow_id_to_state.empty?
|
|
229
|
+
Pcap.warning \
|
|
230
|
+
"#{flow_id_to_state.length} flow(s) have IPv4 fragments " \
|
|
231
|
+
"that can't be reassembled"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
return reassembled_packets
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def to_s
|
|
238
|
+
if @payload.is_a? String
|
|
239
|
+
payload = @payload.inspect
|
|
240
|
+
else
|
|
241
|
+
payload = @payload.to_s
|
|
242
|
+
end
|
|
243
|
+
return "ipv4(%d, %s, %s, %s)" % [@proto, @src, @dst, payload]
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def == other
|
|
247
|
+
return super &&
|
|
248
|
+
self.proto == other.proto &&
|
|
249
|
+
self.ip_id == other.ip_id &&
|
|
250
|
+
self.offset == other.offset &&
|
|
251
|
+
self.ttl == other.ttl &&
|
|
252
|
+
self.dscp == other.dscp
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
end
|
|
257
|
+
end
|