woolen_common 0.0.1

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 (86) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +31 -0
  3. data/ext/woolen_common/extconf.rb +26 -0
  4. data/ext/woolen_common/linux.h +3 -0
  5. data/ext/woolen_common/win.c +18 -0
  6. data/ext/woolen_common/win.h +4 -0
  7. data/ext/woolen_common/win/puts_color.c +139 -0
  8. data/ext/woolen_common/win/puts_color.h +5 -0
  9. data/ext/woolen_common/woolen_common.c +20 -0
  10. data/ext/woolen_common/woolen_common.h +7 -0
  11. data/lib/woolen_common.rb +39 -0
  12. data/lib/woolen_common/abstract_middleware/builder.rb +138 -0
  13. data/lib/woolen_common/abstract_middleware/map_cfg_manager.rb +52 -0
  14. data/lib/woolen_common/abstract_middleware/runner.rb +72 -0
  15. data/lib/woolen_common/action_pool_proxy.rb +28 -0
  16. data/lib/woolen_common/actionpool.rb +10 -0
  17. data/lib/woolen_common/actionpool/pool.rb +295 -0
  18. data/lib/woolen_common/actionpool/queue.rb +41 -0
  19. data/lib/woolen_common/actionpool/thread.rb +181 -0
  20. data/lib/woolen_common/addr_helper.rb +93 -0
  21. data/lib/woolen_common/cache.rb +305 -0
  22. data/lib/woolen_common/common_helper.rb +42 -0
  23. data/lib/woolen_common/config_manager.rb +36 -0
  24. data/lib/woolen_common/drb_helper.rb +125 -0
  25. data/lib/woolen_common/ffi/win32_kernel32.rb +86 -0
  26. data/lib/woolen_common/logger.rb +419 -0
  27. data/lib/woolen_common/pcap/mu/fixnum_ext.rb +8 -0
  28. data/lib/woolen_common/pcap/mu/pcap/ethernet.rb +164 -0
  29. data/lib/woolen_common/pcap/mu/pcap/header.rb +76 -0
  30. data/lib/woolen_common/pcap/mu/pcap/io_pair.rb +68 -0
  31. data/lib/woolen_common/pcap/mu/pcap/io_wrapper.rb +77 -0
  32. data/lib/woolen_common/pcap/mu/pcap/ip.rb +62 -0
  33. data/lib/woolen_common/pcap/mu/pcap/ipv4.rb +274 -0
  34. data/lib/woolen_common/pcap/mu/pcap/ipv6.rb +149 -0
  35. data/lib/woolen_common/pcap/mu/pcap/packet.rb +106 -0
  36. data/lib/woolen_common/pcap/mu/pcap/pkthdr.rb +162 -0
  37. data/lib/woolen_common/pcap/mu/pcap/reader.rb +62 -0
  38. data/lib/woolen_common/pcap/mu/pcap/reader/http_family.rb +175 -0
  39. data/lib/woolen_common/pcap/mu/pcap/sctp.rb +369 -0
  40. data/lib/woolen_common/pcap/mu/pcap/sctp/chunk.rb +124 -0
  41. data/lib/woolen_common/pcap/mu/pcap/sctp/chunk/data.rb +135 -0
  42. data/lib/woolen_common/pcap/mu/pcap/sctp/chunk/init.rb +101 -0
  43. data/lib/woolen_common/pcap/mu/pcap/sctp/chunk/init_ack.rb +69 -0
  44. data/lib/woolen_common/pcap/mu/pcap/sctp/parameter.rb +111 -0
  45. data/lib/woolen_common/pcap/mu/pcap/sctp/parameter/ip_address.rb +49 -0
  46. data/lib/woolen_common/pcap/mu/pcap/stream_packetizer.rb +74 -0
  47. data/lib/woolen_common/pcap/mu/pcap/tcp.rb +522 -0
  48. data/lib/woolen_common/pcap/mu/pcap/udp.rb +81 -0
  49. data/lib/woolen_common/pcap/mu/scenario/pcap.rb +175 -0
  50. data/lib/woolen_common/pcap/mu/scenario/pcap/fields.rb +51 -0
  51. data/lib/woolen_common/pcap/mu/scenario/pcap/rtp.rb +72 -0
  52. data/lib/woolen_common/pcap/pcap.rb +115 -0
  53. data/lib/woolen_common/pcap/readme.md +72 -0
  54. data/lib/woolen_common/ruby_ext/blank.rb +126 -0
  55. data/lib/woolen_common/ruby_ext/drb_ext.rb +7 -0
  56. data/lib/woolen_common/ruby_ext/string.rb +116 -0
  57. data/lib/woolen_common/ruby_ext/win32_ole.rb +4 -0
  58. data/lib/woolen_common/ruby_proxy.rb +5 -0
  59. data/lib/woolen_common/ruby_proxy/client.rb +305 -0
  60. data/lib/woolen_common/ruby_proxy/config.rb +44 -0
  61. data/lib/woolen_common/ruby_proxy/exceptions.rb +17 -0
  62. data/lib/woolen_common/ruby_proxy/proxy.rb +157 -0
  63. data/lib/woolen_common/ruby_proxy/proxy_global_set.rb +44 -0
  64. data/lib/woolen_common/ruby_proxy/proxy_load.rb +34 -0
  65. data/lib/woolen_common/ruby_proxy/server.rb +53 -0
  66. data/lib/woolen_common/splib.rb +36 -0
  67. data/lib/woolen_common/splib/Array.rb +33 -0
  68. data/lib/woolen_common/splib/CodeReloader.rb +59 -0
  69. data/lib/woolen_common/splib/Constants.rb +44 -0
  70. data/lib/woolen_common/splib/Conversions.rb +47 -0
  71. data/lib/woolen_common/splib/Exec.rb +132 -0
  72. data/lib/woolen_common/splib/Float.rb +13 -0
  73. data/lib/woolen_common/splib/HumanIdealRandomIterator.rb +40 -0
  74. data/lib/woolen_common/splib/Monitor.rb +214 -0
  75. data/lib/woolen_common/splib/PriorityQueue.rb +110 -0
  76. data/lib/woolen_common/splib/Sleep.rb +10 -0
  77. data/lib/woolen_common/splib/UrlShorteners.rb +48 -0
  78. data/lib/woolen_common/ssh_proxy.rb +146 -0
  79. data/lib/woolen_common/system_helper.rb +123 -0
  80. data/lib/woolen_common/system_monitor.rb +23 -0
  81. data/lib/woolen_common/system_monitor/linux_monitor.rb +250 -0
  82. data/lib/woolen_common/system_monitor/windows_monitor.rb +145 -0
  83. data/lib/woolen_common/type_helper.rb +42 -0
  84. data/lib/woolen_common/ver_ctrl_middle_ware.rb +92 -0
  85. data/lib/woolen_common/version.rb +3 -0
  86. metadata +210 -0
@@ -0,0 +1,162 @@
1
+ # -*- encoding : utf-8 -*-
2
+ # http://www.mudynamics.com
3
+ # http://labs.mudynamics.com
4
+ # http://www.pcapr.net
5
+
6
+ module Mu
7
+ class Pcap
8
+
9
+ class Pkthdr
10
+ attr_accessor :endian, :ts_sec, :ts_usec, :caplen, :len, :pkt, :pkt_raw
11
+
12
+ def initialize endian=BIG_ENDIAN, ts_sec=0, ts_usec=0, caplen=0, len=0, pkt=nil
13
+ @endian = endian
14
+ @ts_sec = ts_sec
15
+ @ts_usec = ts_usec
16
+ @caplen = caplen
17
+ @len = len
18
+ @pkt = pkt
19
+ @pkt_raw = pkt
20
+ end
21
+
22
+ FMT_NNNN = 'NNNN'
23
+ FMT_VVVV = 'VVVV'
24
+
25
+ def self.read io, endian=BIG_ENDIAN
26
+ if endian == BIG_ENDIAN
27
+ format = FMT_NNNN
28
+ elsif endian == LITTLE_ENDIAN
29
+ format = FMT_VVVV
30
+ end
31
+ bytes = io.read 16
32
+ if not bytes
33
+ raise EOFError, 'Missing PCAP packet header'
34
+ end
35
+ if not bytes.length == 16
36
+ raise ParseError, "Truncated PCAP packet header: expected 16 bytes, got #{bytes.length} bytes"
37
+ end
38
+ ts_sec, ts_usec, caplen, len = bytes.unpack format
39
+ pkt = io.read(caplen)
40
+ if not pkt
41
+ raise ParseError, 'Missing PCAP packet header packet'
42
+ end
43
+ if not pkt.length == caplen
44
+ raise ParseError, "Truncated PCAP packet header: expected #{pkthdr.caplen} bytes, got #{pkthdr.pkt.length} bytes"
45
+ end
46
+ pkthdr = Pkthdr.new endian, ts_sec, ts_usec, caplen, len, pkt
47
+ return pkthdr
48
+ end
49
+
50
+ def write io
51
+ if @pkt.is_a? String
52
+ pkt = @pkt
53
+ else
54
+ string_io = StringIO.new
55
+ @pkt.write string_io
56
+ pkt = string_io.string
57
+ end
58
+ len = pkt.length
59
+ bytes = [@ts_sec, @ts_usec, len, len].pack FMT_NNNN
60
+ io.write bytes
61
+ io.write pkt
62
+ end
63
+
64
+ def decode! endian, linktype
65
+ @pkt = case linktype
66
+ when DLT_NULL;
67
+ Pkthdr.decode_null endian, @pkt
68
+ when DLT_EN10MB;
69
+ Pkthdr.decode_en10mb @pkt
70
+ when DLT_RAW;
71
+ raise NotImplementedError
72
+ when DLT_LINUX_SLL;
73
+ Pkthdr.decode_linux_sll @pkt
74
+ else
75
+ raise ParseError, "Unknown PCAP linktype: #{linktype}"
76
+ end
77
+ end
78
+
79
+ # See http://wiki.wireshark.org/NullLoopback
80
+ # and epan/aftypes.h in wireshark code.
81
+ BSD_AF_INET6 = [
82
+ OPENBSD_AF_INET6 = 24,
83
+ FREEBSD_AF_INET6 = 28,
84
+ DARWIN_AF_INET6 = 30
85
+ ]
86
+
87
+ def self.decode_null endian, bytes
88
+ Pcap.assert bytes.length >= 4, 'Truncated PCAP packet header: ' +
89
+ "expected at least 4 bytes, got #{bytes.length} bytes"
90
+ if endian == BIG_ENDIAN
91
+ format = 'N'
92
+ elsif endian == LITTLE_ENDIAN
93
+ format = 'V'
94
+ end
95
+ family = bytes[0, 4].unpack(format)[0]
96
+ bytes = bytes[4..-1]
97
+ ethernet = Ethernet.new
98
+ ethernet.src = '00:01:01:00:00:01'
99
+ ethernet.dst = '00:01:01:00:00:02'
100
+ ethernet.payload = ethernet.payload_raw = bytes
101
+ if family != Socket::AF_INET and family != Socket::AF_INET6 and
102
+ not BSD_AF_INET6.include?(family)
103
+ raise ParseError, "Unknown PCAP packet header family: #{family}"
104
+ end
105
+ begin
106
+ case family
107
+ when Socket::AF_INET
108
+ ethernet.type = Ethernet::ETHERTYPE_IP
109
+ ethernet.payload = IPv4.from_bytes ethernet.payload
110
+ when Socket::AF_INET6, FREEBSD_AF_INET6, OPENBSD_AF_INET6, DARWIN_AF_INET6
111
+ ethernet.type = Ethernet::ETHERTYPE_IP6
112
+ ethernet.payload = IPv6.from_bytes ethernet.payload
113
+ else
114
+ raise NotImplementedError
115
+ end
116
+ rescue ParseError => e
117
+ Pcap.warning e
118
+ end
119
+ return ethernet
120
+ end
121
+
122
+ def self.decode_en10mb bytes
123
+ return Ethernet.from_bytes(bytes)
124
+ end
125
+
126
+ def self.decode_linux_sll bytes
127
+ Pcap.assert bytes.length >= 16, 'Truncated PCAP packet header: ' +
128
+ "expected at least 16 bytes, got #{bytes.length} bytes"
129
+ packet_type, link_type, addr_len = bytes.unpack('nnn')
130
+ type = bytes[14, 2].unpack('n')[0]
131
+ bytes = bytes[16..-1]
132
+ ethernet = Ethernet.new
133
+ ethernet.type = type
134
+ ethernet.src = '00:01:01:00:00:01'
135
+ ethernet.dst = '00:01:01:00:00:02'
136
+ ethernet.payload = ethernet.payload_raw = bytes
137
+ begin
138
+ case type
139
+ when Ethernet::ETHERTYPE_IP
140
+ ethernet.payload = IPv4.from_bytes ethernet.payload
141
+ when Ethernet::ETHERTYPE_IP6
142
+ ethernet.payload = IPv6.from_bytes ethernet.payload
143
+ end
144
+ rescue ParseError => e
145
+ Pcap.warning e
146
+ end
147
+ return ethernet
148
+ end
149
+
150
+ def == other
151
+ return self.class == other.class &&
152
+ self.endian == other.endian &&
153
+ self.ts_sec == other.ts_sec &&
154
+ self.ts_usec == other.ts_usec &&
155
+ self.caplen == other.caplen &&
156
+ self.len == other.len &&
157
+ self.pkt == other.pkt
158
+ end
159
+ end
160
+
161
+ end
162
+ end
@@ -0,0 +1,62 @@
1
+ # -*- encoding : utf-8 -*-
2
+ # http://www.mudynamics.com
3
+ # http://labs.mudynamics.com
4
+ # http://www.pcapr.net
5
+
6
+ module Mu
7
+ class Pcap
8
+
9
+ class Reader
10
+ attr_accessor :pcap2scenario
11
+
12
+ FAMILY_TO_READER = {}
13
+
14
+ # Returns a reader instance of specified family. Returns nil when family is :none.
15
+ def self.reader family
16
+ if family == :none
17
+ return nil
18
+ end
19
+
20
+ if klass = FAMILY_TO_READER[family]
21
+ return klass.new
22
+ end
23
+
24
+ raise ArgumentError, "Unknown protocol family: '#{family}'"
25
+ end
26
+
27
+ # Returns family name
28
+ def family
29
+ raise NotImplementedError
30
+ end
31
+
32
+ # Notify parser of bytes written. Parser may update state
33
+ # to serve as a hint for subsequent reads.
34
+ def record_write bytes, state=nil
35
+ begin
36
+ do_record_write bytes, state
37
+ rescue
38
+ nil
39
+ end
40
+ end
41
+
42
+ # Returns next complete message from byte stream or nil.
43
+ def read_message bytes, state=nil
44
+ read_message! bytes.dup, state
45
+ end
46
+
47
+ # Mutating form of read_message. Removes a complete message
48
+ # from input stream. Returns the message or nil if there.
49
+ # is not a complete message.
50
+ def read_message! bytes, state=nil
51
+ begin
52
+ do_read_message! bytes, state
53
+ rescue
54
+ nil
55
+ end
56
+ end
57
+
58
+ end
59
+ end
60
+ end
61
+
62
+ require 'mu/pcap/reader/http_family'
@@ -0,0 +1,175 @@
1
+ # -*- encoding : utf-8 -*-
2
+ # http://www.mudynamics.com
3
+ # http://labs.mudynamics.com
4
+ # http://www.pcapr.net
5
+
6
+ require 'mu/pcap/reader'
7
+ require 'stringio'
8
+ require 'zlib'
9
+
10
+ module Mu
11
+ class Pcap
12
+ class Reader
13
+
14
+ # Reader for HTTP family of protocols (HTTP/SIP/RTSP).
15
+ # Handles message boundaries and decompressing/dechunking payloads.
16
+ class HttpFamily < Reader
17
+ FAMILY = :http
18
+ FAMILY_TO_READER[FAMILY] = self
19
+ CRLF = "\r\n"
20
+
21
+ def family
22
+ FAMILY
23
+ end
24
+
25
+ def do_record_write bytes, state=nil
26
+ return if not state
27
+ if bytes =~ RE_REQUEST_LINE
28
+ method = $1
29
+ requests = state[:requests] ||= []
30
+ requests << method
31
+ end
32
+ end
33
+
34
+ private :do_record_write
35
+
36
+ RE_CONTENT_ENCODING = /^content-encoding:\s*(gzip|deflate)/i
37
+ RE_CHUNKED = /Transfer-Encoding:\s*chunked/i
38
+ RE_HEADERS_COMPLETE = /.*?\r\n\r\n/m
39
+ # Request line e.g. GET /index.html HTTP/1.1
40
+ RE_REQUEST_LINE = /\A([^ \t\r\n]+)[ \t]+([^ \t\r\n]+)[ \t]+(HTTP|SIP|RTSP)\/[\d.]+.*\r\n/
41
+ # Status line e.g. SIP/2.0 404 Authorization required
42
+ RE_STATUS_LINE = /\A((HTTP|SIP|RTSP)\/[\d.]+[ \t]+(\d+))\b.*\r\n/
43
+
44
+ RE_CONTENT_LENGTH = /^(Content-Length)(:\s*)(\d+)\r\n/i
45
+ RE_CONTENT_LENGTH_SIP = /^(Content-Length|l)(:\s*)(\d+)\r\n/i
46
+
47
+
48
+ def do_read_message! bytes, state=nil
49
+ case bytes
50
+ when RE_REQUEST_LINE
51
+ proto = $3
52
+ when RE_STATUS_LINE
53
+ proto = $2
54
+ status = $3.to_i
55
+ if state
56
+ requests = state[:requests] ||= []
57
+ if requests[0] == "HEAD"
58
+ reply_to_head = true
59
+ end
60
+ if status > 199
61
+ # We have a final response. Forget about request.
62
+ requests.shift
63
+ end
64
+ end
65
+ else
66
+ return nil # Not http family.
67
+ end
68
+
69
+ # Read headers
70
+ if bytes =~ RE_HEADERS_COMPLETE
71
+ headers = $&
72
+ rest = $'
73
+ else
74
+ return nil
75
+ end
76
+ message = [headers]
77
+
78
+ # Read payload.
79
+ if proto == 'SIP'
80
+ re_content_length = RE_CONTENT_LENGTH_SIP
81
+ else
82
+ re_content_length = RE_CONTENT_LENGTH
83
+ end
84
+ if reply_to_head
85
+ length = 0
86
+ elsif headers =~ RE_CHUNKED
87
+ # Read chunks, dechunking in runtime case.
88
+ raw, dechunked = get_chunks(rest)
89
+ if raw
90
+ length = raw.length
91
+ payload = raw
92
+ else
93
+ return nil # Last chunk not received.
94
+ end
95
+ elsif headers =~ re_content_length
96
+ length = $3.to_i
97
+ if rest.length >= length
98
+ payload = rest.slice(0, length)
99
+ else
100
+ return nil # Not enough bytes.
101
+ end
102
+ else
103
+ # XXX. When there is a payload and no content-length
104
+ # header HTTP RFC says to read until connection close.
105
+ length = 0
106
+ end
107
+
108
+ message << payload
109
+
110
+ # Consume message from input bytes.
111
+ message_len = headers.length + length
112
+ if bytes.length >= message_len
113
+ bytes.slice!(0, message_len)
114
+ return message.join
115
+ else
116
+ return nil # Not enough bytes.
117
+ end
118
+ end
119
+
120
+ private :do_read_message!
121
+
122
+ # Returns array containing raw and dechunked payload. Returns nil
123
+ # if payload cannot be completely read.
124
+ RE_CHUNK_SIZE_LINE = /\A([[:xdigit:]]+)\r\n?/
125
+
126
+ def get_chunks bytes
127
+ raw = []
128
+ dechunked = []
129
+ io = StringIO.new bytes
130
+ until io.eof?
131
+ # Read size line
132
+ size_line = io.readline
133
+ raw << size_line
134
+ if size_line =~ RE_CHUNK_SIZE_LINE
135
+ chunk_size = $1.to_i(16)
136
+ else
137
+ # Malformed chunk size line
138
+ $stderr.puts "malformed size line : #{size_line.inspect}"
139
+ return nil
140
+ end
141
+
142
+ # Read chunk data
143
+ chunk = io.read(chunk_size)
144
+ if chunk.size < chunk_size
145
+ # malformed/incomplete
146
+ $stderr.puts "malformed/incomplete #{chunk_size}"
147
+ return nil
148
+ end
149
+ raw << chunk
150
+ dechunked << chunk
151
+ # Get end-of-chunk CRLF
152
+ crlf = io.read(2)
153
+ if crlf == CRLF
154
+ raw << crlf
155
+ else
156
+ # CRLF has not arrived or, if this is the last chunk,
157
+ # we might be looking at the first two bytes of a trailer
158
+ # and we don't support trailers (see rfc 2616 sec3.6.1).
159
+ return nil
160
+ end
161
+
162
+ if chunk_size == 0
163
+ # Done. Return raw and dechunked payloads.
164
+ return raw.join, dechunked.join
165
+ end
166
+ end
167
+
168
+ # EOF w/out reaching last chunk.
169
+ return nil
170
+ end
171
+ end
172
+
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,369 @@
1
+ # -*- encoding : utf-8 -*-
2
+ # http://www.mudynamics.com
3
+ # http://labs.mudynamics.com
4
+ # http://www.pcapr.net
5
+
6
+ module Mu
7
+ class Pcap
8
+
9
+ class SCTP < Packet
10
+ attr_accessor :src_port, :dst_port, :verify_tag, :checksum
11
+
12
+ # SCTP chunk types
13
+ CHUNK_DATA = 0x00
14
+ CHUNK_INIT = 0x01
15
+ CHUNK_INIT_ACK = 0x02
16
+ CHUNK_SACK = 0x03
17
+ CHUNK_HEARTBEAT = 0x04
18
+ CHUNK_HEARTBEAT_ACK = 0x05
19
+ CHUNK_ABORT = 0x06
20
+ CHUNK_SHUTDOWN = 0x07
21
+ CHUNK_SHUTDOWN_ACK = 0x08
22
+ CHUNK_ERROR = 0x09
23
+ CHUNK_COOKIE_ECHO = 0x0A
24
+ CHUNK_COOKIE_ACK = 0x0B
25
+ CHUNK_ECNE = 0x0C
26
+ CHUNK_CWR = 0x0D
27
+ CHUNK_SHUTDOWN_COMPLETE = 0x0E
28
+ CHUNK_AUTH = 0x0F
29
+ CHUNK_ASCONF_ACK = 0x80
30
+ CHUNK_PADDING = 0x84
31
+ CHUNK_FORWARD_TSN = 0xC0
32
+ CHUNK_ASCONF = 0xC1
33
+
34
+ # SCTP parameter types
35
+ PARAM_IPV4 = 0x0005
36
+ PARAM_IPV6 = 0x0006
37
+ PARAM_STATE_COOKIE = 0x0007
38
+ PARAM_COOKIE_PRESERVATIVE = 0x0009
39
+ PARAM_HOST_NAME_ADDR = 0x000B
40
+ PARAM_SUPPORTED_ADDR_TYPES = 0x000C
41
+ PARAM_ECN = 0x8000
42
+ PARAM_RANDOM = 0x8002
43
+ PARAM_CHUNK_LIST = 0x8003
44
+ PARAM_HMAC_ALGORITHM = 0x8004
45
+ PARAM_PADDING = 0x8005
46
+ PARAM_SUPPORTED_EXTENSIONS = 0x8006
47
+ PARAM_FORWARD_TSN = 0xC000
48
+ PARAM_SET_PRIMARY_ADDR = 0xC004
49
+ PARAM_ADAPTATION_LAYER_INDICATION = 0xC006
50
+
51
+ def initialize
52
+ super
53
+
54
+ @src_port = 0
55
+ @dst_port = 0
56
+ @verify_tag = 0
57
+ @checksum = 0
58
+ @payload = []
59
+ end
60
+
61
+ def flow_id
62
+ return [:sctp, @src_port, @dst_port, @verify_tag]
63
+ end
64
+
65
+ def reverse_flow_id
66
+ return [:sctp, @dst_port, @src_port, @checksum]
67
+ end
68
+
69
+ # Creates SCTP packet from the payload
70
+ def self.from_bytes bytes
71
+ # Basic packet validation
72
+ Pcap.assert(bytes.length >= 12,
73
+ "Truncated SCTP header: 12 > #{bytes.length}")
74
+ Pcap.assert(bytes.length >= 16,
75
+ "Truncated SCTP packet: got only #{bytes.length} bytes")
76
+
77
+ # Read SCTP header
78
+ sport, dport, vtag, cksum = bytes.unpack('nnNN')
79
+
80
+ # Create SCTP packet and populate SCTP header fields
81
+ sctp = SCTP.new
82
+ sctp.src_port = sport
83
+ sctp.dst_port = dport
84
+ sctp.verify_tag = vtag
85
+ sctp.checksum = cksum
86
+
87
+ # Initialize the counter
88
+ length = 12
89
+
90
+ # Collect the chunks
91
+ while length < bytes.length
92
+ # Parse new chunk from the bytes
93
+ chunk = Chunk.from_bytes(bytes[length..-1])
94
+
95
+ # Get chunk size with padding
96
+ length += chunk.padded_size
97
+
98
+ # Add chunk to the list
99
+ sctp << chunk
100
+ end
101
+
102
+ # Sync the payload
103
+ sctp.sync_payload
104
+
105
+ # Return the result
106
+ return sctp
107
+ end
108
+
109
+ class ReorderError < StandardError;
110
+ end
111
+
112
+ # Reorders SCTP packets, if necessary
113
+ def self.reorder packets
114
+ # Initialize
115
+ tsns = {}
116
+ init_packets = {}
117
+ init_ack_packets = {}
118
+ reordered_packets = []
119
+
120
+ # Iterate over each packet
121
+ while not packets.empty?
122
+ # Get next packet
123
+ packet = packets.shift
124
+
125
+ # Do not reorder non-SCTP packets
126
+ if not sctp?(packet)
127
+ reordered_packets << packet
128
+ else
129
+ # Get SCTP portion
130
+ sctp = packet.payload.payload
131
+
132
+ # Sanity checks and packet filtering/preprocessing
133
+ if 0 == sctp.verify_tag and not sctp.init?
134
+ # Non-Init packet with 0 verify tag
135
+ raise ReorderError, "Non-Init packet with zero verify tag"
136
+ elsif sctp.init_or_ack? and 1 < sctp.chunk_count
137
+ # Init/InitAck packet with more with one chunk
138
+ raise ReorderError, "Init/Ack packet with more than 1 chunk"
139
+ elsif sctp.init?
140
+ # Use checksum to save reverse verify tag in the Init packet
141
+ sctp.checksum = sctp[0].init_tag
142
+
143
+ # Save orphaned Init packets until we find the Ack
144
+ init_packets[sctp.reverse_flow_id] = sctp
145
+
146
+ # Add packet for further processing
147
+ reordered_packets << packet
148
+ elsif sctp.init_ack?
149
+ # Lookup Init packet and construct it's flow it
150
+ init_packet = init_packets.delete(sctp.flow_id)
151
+
152
+ # Did we find anything?
153
+ if init_packet
154
+ # Set verify tag in the Init packet
155
+ init_packet.verify_tag = sctp[0].init_tag
156
+
157
+ # Set reverse verify tag in the InitAck packet
158
+ sctp.checksum = init_packet.verify_tag
159
+
160
+ # Re-insert INIT packet keyed by its flow id
161
+ init_packets[init_packet.flow_id] = init_packet
162
+ else
163
+ Pcap.warning("Orphaned SCTP INIT_ACK packet")
164
+ end
165
+
166
+ # Save InitAck packet
167
+ init_ack_packets[sctp.flow_id] = sctp
168
+
169
+ # Add packet for further processing
170
+ reordered_packets << packet
171
+ elsif sctp.has_data?
172
+ # SCTP packet with user data; lookup Init or InitAck packet
173
+ init_packet = init_packets[sctp.flow_id]
174
+ init_ack_packet = init_ack_packets[sctp.flow_id]
175
+
176
+ # It should belong to either one flow id or the other
177
+ if init_packet
178
+ # Set reverse verify tag from Init packet
179
+ sctp.checksum = init_packet.checksum
180
+ elsif init_ack_packet
181
+ # Set reverse flow id from InitAck packet
182
+ sctp.checksum = init_ack_packet.checksum
183
+ else
184
+ # Orphaned SCTP packet -- not very good
185
+ Pcap.warning("Orphaned SCTP DATA packet detected")
186
+ end
187
+
188
+ # If we have just one chunk we are done
189
+ if 1 == sctp.chunk_count and not tsns.member?(sctp[0].tsn)
190
+ # Save TSN
191
+ tsns[sctp[0].tsn] = sctp[0]
192
+
193
+ # sync the payload
194
+ sctp.sync_payload
195
+
196
+ # Add packet for further processing
197
+ reordered_packets << packet
198
+ else
199
+ # Split each data chunk in a separate SCTP packet
200
+ sctp.chunk_count.times do
201
+ # Get next chunk
202
+ chunk = sctp.shift
203
+
204
+ # Is it data?
205
+ if CHUNK_DATA == chunk.type
206
+ # Yes, check for duplicate TSNs
207
+ if not tsns.member?(chunk.tsn)
208
+ # Not a duplicate; create new SCTP packet
209
+ packet_new = packet.deepdup
210
+
211
+ # Create new SCTP payload
212
+ sctp_new = SCTP.new
213
+ sctp_new.src_port = sctp.src_port
214
+ sctp_new.dst_port = sctp.dst_port
215
+ sctp_new.verify_tag = sctp.verify_tag
216
+ sctp_new.checksum = sctp.checksum
217
+
218
+ # Add the chunk
219
+ sctp_new << chunk
220
+
221
+ # Add SCTP payload to the new packet
222
+ packet_new.payload.payload = sctp_new
223
+
224
+ # Save TSN
225
+ tsns[chunk.tsn] = chunk
226
+
227
+ # Sync the payload
228
+ sctp_new.sync_payload
229
+
230
+ # Add packet for further processing
231
+ reordered_packets << packet_new
232
+ else
233
+ Pcap.warning("Duplicate chunk: #{chunk.tsn}")
234
+ end
235
+ else
236
+ Pcap.warning("Non-data chunk: #{chunk.type}")
237
+ end
238
+ end
239
+ end
240
+ else
241
+ # Other SCTP packet; we are not interested at this time
242
+ end
243
+ end
244
+ end
245
+
246
+ # Return the result
247
+ return reordered_packets
248
+ end
249
+
250
+ def write io, ip
251
+ # Give a warning if packet size exceeds maximum allowed
252
+ if @payload_raw and @payload_raw.length + 20 > 65535
253
+ Pcap.warning("SCTP payload is too large")
254
+ end
255
+
256
+ # Calculate CRC32 checksum on the packet; temporarily removed due to a
257
+ # hack that uses checksum to link forward and reverse SCTP flow IDs.
258
+ #header = [@src_port, @dst_port, @verify_tag, 0].pack('nnNN')
259
+ #checksum = SCTP.crc32(header + @payload_raw)
260
+ header = [@src_port, @dst_port, @verify_tag, @checksum].pack('nnNN')
261
+
262
+ # Write SCTP header followed by each chunk
263
+ io.write(header)
264
+
265
+ # Write each chunks' data
266
+ @payload.each do |chunk|
267
+ chunk.write(io, ip)
268
+ end
269
+ end
270
+
271
+ def sync_payload
272
+ # Reset raw bytes
273
+ @payload_raw = ''
274
+
275
+ # Iterate over each chunk
276
+ @payload.each do |chunk|
277
+ @payload_raw << chunk.payload_raw
278
+ end
279
+
280
+ # Reset raw payload if it's empty
281
+ @payload_raw = nil if @payload_raw == ''
282
+ end
283
+
284
+ def self.crc32 bytes
285
+ r = 0xFFFFFFFF
286
+
287
+ bytes.each_byte do |b|
288
+ r ^= b
289
+
290
+ 8.times do
291
+ r = (r >> 1) ^ (0xEDB88320 * (r & 1))
292
+ end
293
+ end
294
+
295
+ return r ^ 0xFFFFFFFF
296
+ end
297
+
298
+ def self.sctp? packet
299
+ return packet.is_a?(Ethernet) &&
300
+ packet.payload.is_a?(IP) &&
301
+ packet.payload.payload.is_a?(SCTP)
302
+ end
303
+
304
+ def << chunk
305
+ @payload << chunk
306
+ end
307
+
308
+ def shift
309
+ return @payload.shift
310
+ end
311
+
312
+ def [] index
313
+ return @payload[index]
314
+ end
315
+
316
+ def chunk_count
317
+ return @payload.size
318
+ end
319
+
320
+ def has_data?
321
+ return @payload.any? do |chunk|
322
+ CHUNK_DATA == chunk.type
323
+ end
324
+ end
325
+
326
+ def to_s
327
+ return "sctp(%d, %d, %d, %s)" % [@src_port,
328
+ @dst_port,
329
+ @verify_tag,
330
+ @payload.join(", ")]
331
+ end
332
+
333
+ def == other
334
+ return super &&
335
+ self.src_port == other.src_port &&
336
+ self.dst_port == other.dst_port &&
337
+ self.verify_tag == other.verify_tag &&
338
+ self.payload.size == other.payload.size
339
+ end
340
+
341
+ def init?
342
+ if CHUNK_INIT == @payload[0].type
343
+ return true
344
+ else
345
+ return false
346
+ end
347
+ end
348
+
349
+ def init_ack?
350
+ if CHUNK_INIT_ACK == @payload[0].type
351
+ return true
352
+ else
353
+ return false
354
+ end
355
+ end
356
+
357
+ def init_or_ack?
358
+ if CHUNK_INIT == @payload[0].type or CHUNK_INIT_ACK == @payload[0].type
359
+ return true
360
+ else
361
+ return false
362
+ end
363
+ end
364
+ end # class SCTP
365
+
366
+ end # class Pcap
367
+ end # module Mu
368
+
369
+ require 'mu/pcap/sctp/chunk'