packet.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
  2. // SPDX-License-Identifier: MIT
  3. package sctp
  4. import (
  5. "encoding/binary"
  6. "errors"
  7. "fmt"
  8. "hash/crc32"
  9. )
  10. // Create the crc32 table we'll use for the checksum
  11. var castagnoliTable = crc32.MakeTable(crc32.Castagnoli) // nolint:gochecknoglobals
  12. // Allocate and zero this data once.
  13. // We need to use it for the checksum and don't want to allocate/clear each time.
  14. var fourZeroes [4]byte // nolint:gochecknoglobals
  15. /*
  16. Packet represents an SCTP packet, defined in https://tools.ietf.org/html/rfc4960#section-3
  17. An SCTP packet is composed of a common header and chunks. A chunk
  18. contains either control information or user data.
  19. SCTP Packet Format
  20. 0 1 2 3
  21. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  22. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  23. | Common Header |
  24. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  25. | Chunk #1 |
  26. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  27. | ... |
  28. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  29. | Chunk #n |
  30. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  31. SCTP Common Header Format
  32. 0 1 2 3
  33. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  34. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  35. | Source Value Number | Destination Value Number |
  36. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  37. | Verification Tag |
  38. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  39. | Checksum |
  40. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  41. */
  42. type packet struct {
  43. sourcePort uint16
  44. destinationPort uint16
  45. verificationTag uint32
  46. chunks []chunk
  47. }
  48. const (
  49. packetHeaderSize = 12
  50. )
  51. // SCTP packet errors
  52. var (
  53. ErrPacketRawTooSmall = errors.New("raw is smaller than the minimum length for a SCTP packet")
  54. ErrParseSCTPChunkNotEnoughData = errors.New("unable to parse SCTP chunk, not enough data for complete header")
  55. ErrUnmarshalUnknownChunkType = errors.New("failed to unmarshal, contains unknown chunk type")
  56. ErrChecksumMismatch = errors.New("checksum mismatch theirs")
  57. )
  58. func (p *packet) unmarshal(doChecksum bool, raw []byte) error {
  59. if len(raw) < packetHeaderSize {
  60. return fmt.Errorf("%w: raw only %d bytes, %d is the minimum length", ErrPacketRawTooSmall, len(raw), packetHeaderSize)
  61. }
  62. offset := packetHeaderSize
  63. // Check if doing CRC32c is required.
  64. // Without having SCTP AUTH implemented, this depends only on the type
  65. // og the first chunk.
  66. if offset+chunkHeaderSize <= len(raw) {
  67. switch chunkType(raw[offset]) {
  68. case ctInit, ctCookieEcho:
  69. doChecksum = true
  70. default:
  71. }
  72. }
  73. theirChecksum := binary.LittleEndian.Uint32(raw[8:])
  74. if theirChecksum != 0 || doChecksum {
  75. ourChecksum := generatePacketChecksum(raw)
  76. if theirChecksum != ourChecksum {
  77. return fmt.Errorf("%w: %d ours: %d", ErrChecksumMismatch, theirChecksum, ourChecksum)
  78. }
  79. }
  80. p.sourcePort = binary.BigEndian.Uint16(raw[0:])
  81. p.destinationPort = binary.BigEndian.Uint16(raw[2:])
  82. p.verificationTag = binary.BigEndian.Uint32(raw[4:])
  83. for {
  84. // Exact match, no more chunks
  85. if offset == len(raw) {
  86. break
  87. } else if offset+chunkHeaderSize > len(raw) {
  88. return fmt.Errorf("%w: offset %d remaining %d", ErrParseSCTPChunkNotEnoughData, offset, len(raw))
  89. }
  90. var c chunk
  91. switch chunkType(raw[offset]) {
  92. case ctInit:
  93. c = &chunkInit{}
  94. case ctInitAck:
  95. c = &chunkInitAck{}
  96. case ctAbort:
  97. c = &chunkAbort{}
  98. case ctCookieEcho:
  99. c = &chunkCookieEcho{}
  100. case ctCookieAck:
  101. c = &chunkCookieAck{}
  102. case ctHeartbeat:
  103. c = &chunkHeartbeat{}
  104. case ctPayloadData:
  105. c = &chunkPayloadData{}
  106. case ctSack:
  107. c = &chunkSelectiveAck{}
  108. case ctReconfig:
  109. c = &chunkReconfig{}
  110. case ctForwardTSN:
  111. c = &chunkForwardTSN{}
  112. case ctError:
  113. c = &chunkError{}
  114. case ctShutdown:
  115. c = &chunkShutdown{}
  116. case ctShutdownAck:
  117. c = &chunkShutdownAck{}
  118. case ctShutdownComplete:
  119. c = &chunkShutdownComplete{}
  120. default:
  121. return fmt.Errorf("%w: %s", ErrUnmarshalUnknownChunkType, chunkType(raw[offset]).String())
  122. }
  123. if err := c.unmarshal(raw[offset:]); err != nil {
  124. return err
  125. }
  126. p.chunks = append(p.chunks, c)
  127. chunkValuePadding := getPadding(c.valueLength())
  128. offset += chunkHeaderSize + c.valueLength() + chunkValuePadding
  129. }
  130. return nil
  131. }
  132. func (p *packet) marshal(doChecksum bool) ([]byte, error) {
  133. raw := make([]byte, packetHeaderSize)
  134. // Populate static headers
  135. // 8-12 is Checksum which will be populated when packet is complete
  136. binary.BigEndian.PutUint16(raw[0:], p.sourcePort)
  137. binary.BigEndian.PutUint16(raw[2:], p.destinationPort)
  138. binary.BigEndian.PutUint32(raw[4:], p.verificationTag)
  139. // Populate chunks
  140. for _, c := range p.chunks {
  141. chunkRaw, err := c.marshal()
  142. if err != nil {
  143. return nil, err
  144. }
  145. raw = append(raw, chunkRaw...)
  146. paddingNeeded := getPadding(len(raw))
  147. if paddingNeeded != 0 {
  148. raw = append(raw, make([]byte, paddingNeeded)...)
  149. }
  150. }
  151. if doChecksum {
  152. // golang CRC32C uses reflected input and reflected output, the
  153. // net result of this is to have the bytes flipped compared to
  154. // the non reflected variant that the spec expects.
  155. //
  156. // Use LittleEndian.PutUint32 to avoid flipping the bytes in to
  157. // the spec compliant checksum order
  158. binary.LittleEndian.PutUint32(raw[8:], generatePacketChecksum(raw))
  159. }
  160. return raw, nil
  161. }
  162. func generatePacketChecksum(raw []byte) (sum uint32) {
  163. // Fastest way to do a crc32 without allocating.
  164. sum = crc32.Update(sum, castagnoliTable, raw[0:8])
  165. sum = crc32.Update(sum, castagnoliTable, fourZeroes[:])
  166. sum = crc32.Update(sum, castagnoliTable, raw[12:])
  167. return sum
  168. }
  169. // String makes packet printable
  170. func (p *packet) String() string {
  171. format := `Packet:
  172. sourcePort: %d
  173. destinationPort: %d
  174. verificationTag: %d
  175. `
  176. res := fmt.Sprintf(format,
  177. p.sourcePort,
  178. p.destinationPort,
  179. p.verificationTag,
  180. )
  181. for i, chunk := range p.chunks {
  182. res += fmt.Sprintf("Chunk %d:\n %s", i, chunk)
  183. }
  184. return res
  185. }