Class: Mu::Pcap::SCTP

Inherits:
Packet
  • Object
show all
Defined in:
lib/mu/pcap/sctp.rb,
lib/mu/pcap/sctp/chunk.rb,
lib/mu/pcap/sctp/parameter.rb,
lib/mu/pcap/sctp/chunk/data.rb,
lib/mu/pcap/sctp/chunk/init.rb,
lib/mu/pcap/sctp/chunk/init_ack.rb,
lib/mu/pcap/sctp/parameter/ip_address.rb

Defined Under Namespace

Classes: Chunk, Parameter, ReorderError

Constant Summary collapse

CHUNK_DATA =

SCTP chunk types

0x00
CHUNK_INIT =
0x01
CHUNK_INIT_ACK =
0x02
CHUNK_SACK =
0x03
CHUNK_HEARTBEAT =
0x04
CHUNK_HEARTBEAT_ACK =
0x05
CHUNK_ABORT =
0x06
CHUNK_SHUTDOWN =
0x07
CHUNK_SHUTDOWN_ACK =
0x08
CHUNK_ERROR =
0x09
0x0A
0x0B
CHUNK_ECNE =
0x0C
CHUNK_CWR =
0x0D
CHUNK_SHUTDOWN_COMPLETE =
0x0E
CHUNK_AUTH =
0x0F
CHUNK_ASCONF_ACK =
0x80
CHUNK_PADDING =
0x84
CHUNK_FORWARD_TSN =
0xC0
CHUNK_ASCONF =
0xC1
PARAM_IPV4 =

SCTP parameter types

0x0005
PARAM_IPV6 =
0x0006
0x0007
0x0009
PARAM_HOST_NAME_ADDR =
0x000B
PARAM_SUPPORTED_ADDR_TYPES =
0x000C
PARAM_ECN =
0x8000
PARAM_RANDOM =
0x8002
PARAM_CHUNK_LIST =
0x8003
PARAM_HMAC_ALGORITHM =
0x8004
PARAM_PADDING =
0x8005
PARAM_SUPPORTED_EXTENSIONS =
0x8006
PARAM_FORWARD_TSN =
0xC000
PARAM_SET_PRIMARY_ADDR =
0xC004
PARAM_ADAPTATION_LAYER_INDICATION =
0xC006

Constants inherited from Packet

Packet::IGNORE_UDP_PORTS

Instance Attribute Summary collapse

Attributes inherited from Packet

#payload, #payload_raw

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Packet

#deepdup, isolate_l7, normalize, #payload_bytes, #to_bytes

Constructor Details

#initializeSCTP

Returns a new instance of SCTP.



50
51
52
53
54
55
56
57
58
# File 'lib/mu/pcap/sctp.rb', line 50

def initialize
    super
    
    @src_port   = 0
    @dst_port   = 0
    @verify_tag = 0
    @checksum   = 0
    @payload    = []
end

Instance Attribute Details

#checksumObject

Returns the value of attribute checksum.



9
10
11
# File 'lib/mu/pcap/sctp.rb', line 9

def checksum
  @checksum
end

#dst_portObject

Returns the value of attribute dst_port.



9
10
11
# File 'lib/mu/pcap/sctp.rb', line 9

def dst_port
  @dst_port
end

#src_portObject

Returns the value of attribute src_port.



9
10
11
# File 'lib/mu/pcap/sctp.rb', line 9

def src_port
  @src_port
end

#verify_tagObject

Returns the value of attribute verify_tag.



9
10
11
# File 'lib/mu/pcap/sctp.rb', line 9

def verify_tag
  @verify_tag
end

Class Method Details

.crc32(bytes) ⇒ Object



282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/mu/pcap/sctp.rb', line 282

def self.crc32 bytes
    r = 0xFFFFFFFF
    
    bytes.each_byte do |b|
        r ^= b
        
        8.times do
          r = (r >> 1) ^ (0xEDB88320 * (r & 1))
        end
    end
    
    return r ^ 0xFFFFFFFF
end

.from_bytes(bytes) ⇒ Object

Creates SCTP packet from the payload



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/mu/pcap/sctp.rb', line 69

def self.from_bytes bytes
    # Basic packet validation
    Pcap.assert(bytes.length >= 12,
                "Truncated SCTP header: 12 > #{bytes.length}")
    Pcap.assert(bytes.length >= 16,
                "Truncated SCTP packet: got only #{bytes.length} bytes")
    
    # Read SCTP header
    sport, dport, vtag, cksum = bytes.unpack('nnNN')
    
    # Create SCTP packet and populate SCTP header fields
    sctp            = SCTP.new
    sctp.src_port   = sport
    sctp.dst_port   = dport
    sctp.verify_tag = vtag
    sctp.checksum   = cksum
    
    # Initialize the counter
    length = 12
    
    # Collect the chunks
    while length < bytes.length
        # Parse new chunk from the bytes
        chunk = Chunk.from_bytes(bytes[length..-1])
        
        # Get chunk size with padding
        length += chunk.padded_size
        
        # Add chunk to the list
        sctp << chunk
    end

    # Sync the payload
    sctp.sync_payload
    
    # Return the result
    return sctp
end

.reorder(packets) ⇒ Object

Reorders SCTP packets, if necessary



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/mu/pcap/sctp.rb', line 111

def self.reorder packets
    # Initialize
    tsns              = {}
    init_packets      = {}
    init_ack_packets  = {}
    reordered_packets = []
    
    # Iterate over each packet
    while not packets.empty?
        # Get next packet
        packet = packets.shift
        
        # Do not reorder non-SCTP packets
        if not sctp?(packet)
            reordered_packets << packet
        else
            # Get SCTP portion
            sctp = packet.payload.payload
            
            # Sanity checks and packet filtering/preprocessing
            if 0 == sctp.verify_tag and not sctp.init?
                # Non-Init packet with 0 verify tag
                raise ReorderError, "Non-Init packet with zero verify tag"
            elsif sctp.init_or_ack? and 1 < sctp.chunk_count
                # Init/InitAck packet with more with one chunk
                raise ReorderError, "Init/Ack packet with more than 1 chunk"
            elsif sctp.init?
                # Use checksum to save reverse verify tag in the Init packet
                sctp.checksum = sctp[0].init_tag

                # Save orphaned Init packets until we find the Ack
                init_packets[sctp.reverse_flow_id] = sctp
                
                # Add packet for further processing
                reordered_packets << packet
            elsif sctp.init_ack?
                # Lookup Init packet and construct it's flow it
                init_packet = init_packets.delete(sctp.flow_id)

                # Did we find anything?
                if init_packet
                    # Set verify tag in the Init packet
                    init_packet.verify_tag = sctp[0].init_tag

                    # Set reverse verify tag in the InitAck packet
                    sctp.checksum = init_packet.verify_tag

                    # Re-insert INIT packet keyed by its flow id
                    init_packets[init_packet.flow_id] = init_packet
                else
                    Pcap.warning("Orphaned SCTP INIT_ACK packet")
                end

                # Save InitAck packet
                init_ack_packets[sctp.flow_id] = sctp
                
                # Add packet for further processing
                reordered_packets << packet
            elsif sctp.has_data?
                # SCTP packet with user data; lookup Init or InitAck packet
                init_packet     = init_packets[sctp.flow_id]
                init_ack_packet = init_ack_packets[sctp.flow_id]

                # It should belong to either one flow id or the other
                if init_packet
                    # Set reverse verify tag from Init packet
                    sctp.checksum = init_packet.checksum
                elsif init_ack_packet
                    # Set reverse flow id from InitAck packet
                    sctp.checksum = init_ack_packet.checksum
                else
                    # Orphaned SCTP packet -- not very good
                    Pcap.warning("Orphaned SCTP DATA packet detected")
                end

                # If we have just one chunk we are done
                if 1 == sctp.chunk_count and not tsns.member?(sctp[0].tsn)
                    # Save TSN
                    tsns[sctp[0].tsn] = sctp[0]

                    # sync the payload
                    sctp.sync_payload

                    # Add packet for further processing
                    reordered_packets << packet
                else
                    # Split each data chunk in a separate SCTP packet
                    sctp.chunk_count.times do
                        # Get next chunk
                        chunk = sctp.shift

                        # Is it data?
                        if CHUNK_DATA == chunk.type
                            # Yes, check for duplicate TSNs
                            if not tsns.member?(chunk.tsn)
                                # Not a duplicate; create new SCTP packet
                                packet_new = packet.deepdup

                                # Create new SCTP payload
                                sctp_new = SCTP.new
                                sctp_new.src_port   = sctp.src_port
                                sctp_new.dst_port   = sctp.dst_port
                                sctp_new.verify_tag = sctp.verify_tag
                                sctp_new.checksum   = sctp.checksum

                                # Add the chunk
                                sctp_new << chunk

                                # Add SCTP payload to the new packet
                                packet_new.payload.payload = sctp_new

                                # Save TSN
                                tsns[chunk.tsn] = chunk

                                # Sync the payload
                                sctp_new.sync_payload

                                # Add packet for further processing
                                reordered_packets << packet_new
                            else
                                Pcap.warning("Duplicate chunk: #{chunk.tsn}")
                            end
                        else
                            Pcap.warning("Non-data chunk: #{chunk.type}")
                        end
                    end
                end
            else
                # Other SCTP packet; we are not interested at this time
            end
        end
    end
    
    # Return the result
    return reordered_packets
end

.sctp?(packet) ⇒ Boolean

Returns:

  • (Boolean)


296
297
298
299
300
# File 'lib/mu/pcap/sctp.rb', line 296

def self.sctp? packet
    return packet.is_a?(Ethernet) &&
           packet.payload.is_a?(IP) &&
           packet.payload.payload.is_a?(SCTP)
end

Instance Method Details

#<<(chunk) ⇒ Object



302
303
304
# File 'lib/mu/pcap/sctp.rb', line 302

def << chunk
    @payload << chunk
end

#==(other) ⇒ Object



331
332
333
334
335
336
337
# File 'lib/mu/pcap/sctp.rb', line 331

def == other
    return super                               &&
           self.src_port == other.src_port     &&
           self.dst_port == other.dst_port     &&
           self.verify_tag == other.verify_tag &&
           self.payload.size == other.payload.size
end

#[](index) ⇒ Object



310
311
312
# File 'lib/mu/pcap/sctp.rb', line 310

def [] index
    return @payload[index]
end

#chunk_countObject



314
315
316
# File 'lib/mu/pcap/sctp.rb', line 314

def chunk_count
    return @payload.size
end

#flow_idObject



60
61
62
# File 'lib/mu/pcap/sctp.rb', line 60

def flow_id
    return [:sctp, @src_port, @dst_port, @verify_tag]
end

#has_data?Boolean

Returns:

  • (Boolean)


318
319
320
321
322
# File 'lib/mu/pcap/sctp.rb', line 318

def has_data?
    return @payload.any? do |chunk|
        CHUNK_DATA == chunk.type
    end
end

#init?Boolean

Returns:

  • (Boolean)


339
340
341
342
343
344
345
# File 'lib/mu/pcap/sctp.rb', line 339

def init?
    if CHUNK_INIT == @payload[0].type
        return true
    else
        return false
    end
end

#init_ack?Boolean

Returns:

  • (Boolean)


347
348
349
350
351
352
353
# File 'lib/mu/pcap/sctp.rb', line 347

def init_ack?
    if CHUNK_INIT_ACK == @payload[0].type
        return true
    else
        return false
    end
end

#init_or_ack?Boolean

Returns:

  • (Boolean)


355
356
357
358
359
360
361
# File 'lib/mu/pcap/sctp.rb', line 355

def init_or_ack?
    if CHUNK_INIT == @payload[0].type or CHUNK_INIT_ACK == @payload[0].type
        return true
    else
        return false
    end
end

#reverse_flow_idObject



64
65
66
# File 'lib/mu/pcap/sctp.rb', line 64

def reverse_flow_id
    return [:sctp, @dst_port, @src_port, @checksum]
end

#shiftObject



306
307
308
# File 'lib/mu/pcap/sctp.rb', line 306

def shift
    return @payload.shift
end

#sync_payloadObject



269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/mu/pcap/sctp.rb', line 269

def sync_payload
    # Reset raw bytes
    @payload_raw = ''
    
    # Iterate over each chunk
    @payload.each do |chunk|
        @payload_raw << chunk.payload_raw
    end
    
    # Reset raw payload if it's empty
    @payload_raw = nil if @payload_raw == ''
end

#to_sObject



324
325
326
327
328
329
# File 'lib/mu/pcap/sctp.rb', line 324

def to_s
    return "sctp(%d, %d, %d, %s)" % [@src_port,
                                     @dst_port,
                                     @verify_tag,
                                     @payload.join(", ")]
end

#write(io, ip) ⇒ Object



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/mu/pcap/sctp.rb', line 248

def write io, ip
    # Give a warning if packet size exceeds maximum allowed
    if @payload_raw and @payload_raw.length + 20 > 65535
        Pcap.warning("SCTP payload is too large")
    end
    
    # Calculate CRC32 checksum on the packet; temporarily removed due to a
    # hack that uses checksum to link forward and reverse SCTP flow IDs.
    #header = [@src_port, @dst_port, @verify_tag, 0].pack('nnNN')
    #checksum = SCTP.crc32(header + @payload_raw)
    header = [@src_port, @dst_port, @verify_tag, @checksum].pack('nnNN')
    
    # Write SCTP header followed by each chunk
    io.write(header)
    
    # Write each chunks' data
    @payload.each do |chunk|
        chunk.write(io, ip)
    end
end