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.
Files changed (203) hide show
  1. data/.document +5 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +64 -0
  4. data/Rakefile +57 -0
  5. data/VERSION +1 -0
  6. data/bin/pcap2par +49 -0
  7. data/bin/startpcapr +40 -0
  8. data/bin/stoppcapr +33 -0
  9. data/bin/xtractr +5 -0
  10. data/lib/environment.rb +106 -0
  11. data/lib/exe/xtractr +0 -0
  12. data/lib/mu/pcap.rb +110 -0
  13. data/lib/mu/pcap/ethernet.rb +148 -0
  14. data/lib/mu/pcap/header.rb +75 -0
  15. data/lib/mu/pcap/io_pair.rb +67 -0
  16. data/lib/mu/pcap/io_wrapper.rb +76 -0
  17. data/lib/mu/pcap/ip.rb +61 -0
  18. data/lib/mu/pcap/ipv4.rb +257 -0
  19. data/lib/mu/pcap/ipv6.rb +148 -0
  20. data/lib/mu/pcap/packet.rb +104 -0
  21. data/lib/mu/pcap/pkthdr.rb +155 -0
  22. data/lib/mu/pcap/reader.rb +61 -0
  23. data/lib/mu/pcap/reader/http_family.rb +170 -0
  24. data/lib/mu/pcap/sctp.rb +367 -0
  25. data/lib/mu/pcap/sctp/chunk.rb +123 -0
  26. data/lib/mu/pcap/sctp/chunk/data.rb +134 -0
  27. data/lib/mu/pcap/sctp/chunk/init.rb +100 -0
  28. data/lib/mu/pcap/sctp/chunk/init_ack.rb +68 -0
  29. data/lib/mu/pcap/sctp/parameter.rb +110 -0
  30. data/lib/mu/pcap/sctp/parameter/ip_address.rb +48 -0
  31. data/lib/mu/pcap/stream_packetizer.rb +72 -0
  32. data/lib/mu/pcap/tcp.rb +505 -0
  33. data/lib/mu/pcap/udp.rb +69 -0
  34. data/lib/mu/scenario/pcap.rb +164 -0
  35. data/lib/mu/scenario/pcap/fields.rb +50 -0
  36. data/lib/mu/scenario/pcap/rtp.rb +71 -0
  37. data/lib/pcapr_local.rb +159 -0
  38. data/lib/pcapr_local/config.rb +336 -0
  39. data/lib/pcapr_local/db.rb +197 -0
  40. data/lib/pcapr_local/scanner.rb +250 -0
  41. data/lib/pcapr_local/server.rb +178 -0
  42. data/lib/pcapr_local/www/favicon.ico +0 -0
  43. data/lib/pcapr_local/www/favicon.png +0 -0
  44. data/lib/pcapr_local/www/home/index.html +138 -0
  45. data/lib/pcapr_local/www/static/image/16x16/Cancel.png +0 -0
  46. data/lib/pcapr_local/www/static/image/16x16/Cancel.png.1 +0 -0
  47. data/lib/pcapr_local/www/static/image/16x16/Download.png +0 -0
  48. data/lib/pcapr_local/www/static/image/16x16/Folder3.png +0 -0
  49. data/lib/pcapr_local/www/static/image/16x16/Full Size.png +0 -0
  50. data/lib/pcapr_local/www/static/image/16x16/Minus.png +0 -0
  51. data/lib/pcapr_local/www/static/image/16x16/Plus.png +0 -0
  52. data/lib/pcapr_local/www/static/image/16x16/Search.png +0 -0
  53. data/lib/pcapr_local/www/static/image/16x16/User.png +0 -0
  54. data/lib/pcapr_local/www/static/image/48x48/Phone.png +0 -0
  55. data/lib/pcapr_local/www/static/image/48x48/Video.png +0 -0
  56. data/lib/pcapr_local/www/static/image/bar-orange.gif +0 -0
  57. data/lib/pcapr_local/www/static/image/beta.png +0 -0
  58. data/lib/pcapr_local/www/static/image/bg.png +0 -0
  59. data/lib/pcapr_local/www/static/image/blockquote.png +0 -0
  60. data/lib/pcapr_local/www/static/image/body-bg.png +0 -0
  61. data/lib/pcapr_local/www/static/image/body-h3.png +0 -0
  62. data/lib/pcapr_local/www/static/image/body-hl1-bg.png +0 -0
  63. data/lib/pcapr_local/www/static/image/body-hl1-h3.png +0 -0
  64. data/lib/pcapr_local/www/static/image/body-hl1-readmore.png +0 -0
  65. data/lib/pcapr_local/www/static/image/body-hl2-bg.png +0 -0
  66. data/lib/pcapr_local/www/static/image/body-hl2-h3.png +0 -0
  67. data/lib/pcapr_local/www/static/image/body-hl2-readmore.png +0 -0
  68. data/lib/pcapr_local/www/static/image/body-hl3-bg.png +0 -0
  69. data/lib/pcapr_local/www/static/image/body-hl3-h3.png +0 -0
  70. data/lib/pcapr_local/www/static/image/body-hl3-readmore.png +0 -0
  71. data/lib/pcapr_local/www/static/image/body-hl4-bg.png +0 -0
  72. data/lib/pcapr_local/www/static/image/body-hl4-h3.png +0 -0
  73. data/lib/pcapr_local/www/static/image/body-hl4-readmore.png +0 -0
  74. data/lib/pcapr_local/www/static/image/body-hl5-h3.png +0 -0
  75. data/lib/pcapr_local/www/static/image/body-hl6-h3.png +0 -0
  76. data/lib/pcapr_local/www/static/image/body-hl7-h3.png +0 -0
  77. data/lib/pcapr_local/www/static/image/body-hl8-h3.png +0 -0
  78. data/lib/pcapr_local/www/static/image/body-readmore.png +0 -0
  79. data/lib/pcapr_local/www/static/image/bottom-bg.png +0 -0
  80. data/lib/pcapr_local/www/static/image/bottom-l.png +0 -0
  81. data/lib/pcapr_local/www/static/image/bottom-r.png +0 -0
  82. data/lib/pcapr_local/www/static/image/btn-search.png +0 -0
  83. data/lib/pcapr_local/www/static/image/bullet-1.png +0 -0
  84. data/lib/pcapr_local/www/static/image/bullet-2.png +0 -0
  85. data/lib/pcapr_local/www/static/image/bullet-3.png +0 -0
  86. data/lib/pcapr_local/www/static/image/bullet-4.png +0 -0
  87. data/lib/pcapr_local/www/static/image/bullet-5.png +0 -0
  88. data/lib/pcapr_local/www/static/image/bullet-6.png +0 -0
  89. data/lib/pcapr_local/www/static/image/bullet-7.png +0 -0
  90. data/lib/pcapr_local/www/static/image/bullet-hl1.png +0 -0
  91. data/lib/pcapr_local/www/static/image/bullet-hl2.png +0 -0
  92. data/lib/pcapr_local/www/static/image/bullet-hl3.png +0 -0
  93. data/lib/pcapr_local/www/static/image/bullet-hl4.png +0 -0
  94. data/lib/pcapr_local/www/static/image/bullet-pathway.png +0 -0
  95. data/lib/pcapr_local/www/static/image/bullet-section1.png +0 -0
  96. data/lib/pcapr_local/www/static/image/bullet-section2.png +0 -0
  97. data/lib/pcapr_local/www/static/image/collapsed.gif +0 -0
  98. data/lib/pcapr_local/www/static/image/crosslink.png +0 -0
  99. data/lib/pcapr_local/www/static/image/expanded.gif +0 -0
  100. data/lib/pcapr_local/www/static/image/favicon.ico +0 -0
  101. data/lib/pcapr_local/www/static/image/favicon.png +0 -0
  102. data/lib/pcapr_local/www/static/image/icon-author.png +0 -0
  103. data/lib/pcapr_local/www/static/image/icon-created.png +0 -0
  104. data/lib/pcapr_local/www/static/image/p-expand.gif +0 -0
  105. data/lib/pcapr_local/www/static/image/pcapr-logo.png +0 -0
  106. data/lib/pcapr_local/www/static/image/powered-by.png +0 -0
  107. data/lib/pcapr_local/www/static/image/section1-bg.png +0 -0
  108. data/lib/pcapr_local/www/static/image/section1-h3.png +0 -0
  109. data/lib/pcapr_local/www/static/image/section1-readmore.png +0 -0
  110. data/lib/pcapr_local/www/static/image/section2-bg.png +0 -0
  111. data/lib/pcapr_local/www/static/image/section2-h3.png +0 -0
  112. data/lib/pcapr_local/www/static/image/section2-readmore.png +0 -0
  113. data/lib/pcapr_local/www/static/image/status-alert.png +0 -0
  114. data/lib/pcapr_local/www/static/image/status-download.png +0 -0
  115. data/lib/pcapr_local/www/static/image/status-info.png +0 -0
  116. data/lib/pcapr_local/www/static/image/status-note.png +0 -0
  117. data/lib/pcapr_local/www/static/image/tab-round.png +0 -0
  118. data/lib/pcapr_local/www/static/image/throbber.gif +0 -0
  119. data/lib/pcapr_local/www/static/image/user.jpg +0 -0
  120. data/lib/pcapr_local/www/static/script/closet/async.js +421 -0
  121. data/lib/pcapr_local/www/static/script/closet/closet.api.js +241 -0
  122. data/lib/pcapr_local/www/static/script/closet/closet.folders.js +94 -0
  123. data/lib/pcapr_local/www/static/script/closet/closet.js +187 -0
  124. data/lib/pcapr_local/www/static/script/closet/closet.mr.js +219 -0
  125. data/lib/pcapr_local/www/static/script/closet/closet.options.js +359 -0
  126. data/lib/pcapr_local/www/static/script/closet/closet.quantity.js +73 -0
  127. data/lib/pcapr_local/www/static/script/closet/closet.render.js +205 -0
  128. data/lib/pcapr_local/www/static/script/closet/closet.report.js +86 -0
  129. data/lib/pcapr_local/www/static/script/closet/closet.reports.http.js +135 -0
  130. data/lib/pcapr_local/www/static/script/closet/closet.reports.overview.js +163 -0
  131. data/lib/pcapr_local/www/static/script/closet/closet.reports.sip.js +159 -0
  132. data/lib/pcapr_local/www/static/script/closet/closet.reports.tcp.js +72 -0
  133. data/lib/pcapr_local/www/static/script/closet/closet.reports.visualize.js +263 -0
  134. data/lib/pcapr_local/www/static/script/closet/closet.util.js +40 -0
  135. data/lib/pcapr_local/www/static/script/jquery/jquery-1.4.2.min.js +154 -0
  136. data/lib/pcapr_local/www/static/script/jquery/jquery-ui.js +10921 -0
  137. data/lib/pcapr_local/www/static/script/jquery/jquery.flot.js +2123 -0
  138. data/lib/pcapr_local/www/static/script/jquery/jquery.flot.selection.js +184 -0
  139. data/lib/pcapr_local/www/static/script/jquery/jquery.flot.stack.js +184 -0
  140. data/lib/pcapr_local/www/static/script/jquery/jquery.form.js +643 -0
  141. data/lib/pcapr_local/www/static/script/jquery/jquery.jsonp.min.js +3 -0
  142. data/lib/pcapr_local/www/static/script/jquery/jquery.menu.js +142 -0
  143. data/lib/pcapr_local/www/static/script/jquery/jquery.suggest.js +308 -0
  144. data/lib/pcapr_local/www/static/script/jquery/jquery.ui.core.js +203 -0
  145. data/lib/pcapr_local/www/static/script/jquery/jquery.ui.slider.js +629 -0
  146. data/lib/pcapr_local/www/static/script/jquery/jquery.ui.sortable.js +1055 -0
  147. data/lib/pcapr_local/www/static/script/jquery/jquery.ui.widget.js +236 -0
  148. data/lib/pcapr_local/www/static/script/json2.js +481 -0
  149. data/lib/pcapr_local/www/static/script/sammy/plugins/sammy.cache.js +115 -0
  150. data/lib/pcapr_local/www/static/script/sammy/plugins/sammy.template.js +117 -0
  151. data/lib/pcapr_local/www/static/script/sammy/sammy.js +1696 -0
  152. data/lib/pcapr_local/www/static/script/tipsy/jquery.tipsy.js +104 -0
  153. data/lib/pcapr_local/www/static/style/c3p0.css +116 -0
  154. data/lib/pcapr_local/www/static/style/jquery.suggest.css +27 -0
  155. data/lib/pcapr_local/www/static/style/page.css +1113 -0
  156. data/lib/pcapr_local/www/static/style/tipsy.css +7 -0
  157. data/lib/pcapr_local/www/templates/browse.services.template +10 -0
  158. data/lib/pcapr_local/www/templates/browse.template +77 -0
  159. data/lib/pcapr_local/www/templates/flows.template +38 -0
  160. data/lib/pcapr_local/www/templates/pcap.template +63 -0
  161. data/lib/pcapr_local/www/templates/sip.calls.template +35 -0
  162. data/lib/pcapr_local/www/templates/statistics.template +6 -0
  163. data/lib/pcapr_local/xtractr.rb +179 -0
  164. data/lib/pcapr_local/xtractr/instance.rb +172 -0
  165. data/pcapr-local.gemspec +297 -0
  166. data/test/mu/pcap/reader/tc_http_family.rb +251 -0
  167. data/test/mu/pcap/tc_ethernet.rb +71 -0
  168. data/test/mu/pcap/tc_header.rb +56 -0
  169. data/test/mu/pcap/tc_ipv4.rb +103 -0
  170. data/test/mu/pcap/tc_ipv6.rb +83 -0
  171. data/test/mu/pcap/tc_packet.rb +44 -0
  172. data/test/mu/pcap/tc_pair.rb +58 -0
  173. data/test/mu/pcap/tc_pkthdr.rb +33 -0
  174. data/test/mu/pcap/tc_reader.rb +76 -0
  175. data/test/mu/pcap/tc_tcp.rb +426 -0
  176. data/test/mu/pcap/tc_udp.rb +33 -0
  177. data/test/mu/pcap/tc_wrapper.rb +80 -0
  178. data/test/mu/scenario/pcap/tc_fields.rb +67 -0
  179. data/test/mu/scenario/pcap/tc_rtp.rb +135 -0
  180. data/test/mu/scenario/sip_signalled_call_1.pcap +0 -0
  181. data/test/mu/scenario/tc_pcap.rb +190 -0
  182. data/test/mu/scenario/test_data/arp.pcap +0 -0
  183. data/test/mu/scenario/test_data/dns.pcap +0 -0
  184. data/test/mu/scenario/test_data/http-v6.pcap +0 -0
  185. data/test/mu/scenario/test_data/http.pcap +0 -0
  186. data/test/mu/scenario/test_data/http_chunked.pcap +0 -0
  187. data/test/mu/scenario/test_data/http_deflate.pcap +0 -0
  188. data/test/mu/scenario/test_data/httpauth3.pcap +0 -0
  189. data/test/mu/scenario/test_data/icmp.pcap +0 -0
  190. data/test/mu/scenario/test_data/sip_signalled_call_1.pcap +0 -0
  191. data/test/mu/tc_pcap.rb +39 -0
  192. data/test/mu/testcase.rb +86 -0
  193. data/test/pcapr_local/arp.pcap +0 -0
  194. data/test/pcapr_local/data.js +3 -0
  195. data/test/pcapr_local/http_chunked.pcap +0 -0
  196. data/test/pcapr_local/tc_api.rb +181 -0
  197. data/test/pcapr_local/test.tgz +0 -0
  198. data/test/pcapr_local/test_scanner.rb +241 -0
  199. data/test/pcapr_local/test_xtractr.rb +219 -0
  200. data/test/pcapr_local/testcase.rb +107 -0
  201. data/test/test_export_to_scenario.sh +25 -0
  202. data/test/test_pcapr_local.rb +29 -0
  203. 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
@@ -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