packet.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  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(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. p.sourcePort = binary.BigEndian.Uint16(raw[0:])
  63. p.destinationPort = binary.BigEndian.Uint16(raw[2:])
  64. p.verificationTag = binary.BigEndian.Uint32(raw[4:])
  65. offset := packetHeaderSize
  66. for {
  67. // Exact match, no more chunks
  68. if offset == len(raw) {
  69. break
  70. } else if offset+chunkHeaderSize > len(raw) {
  71. return fmt.Errorf("%w: offset %d remaining %d", ErrParseSCTPChunkNotEnoughData, offset, len(raw))
  72. }
  73. var c chunk
  74. switch chunkType(raw[offset]) {
  75. case ctInit:
  76. c = &chunkInit{}
  77. case ctInitAck:
  78. c = &chunkInitAck{}
  79. case ctAbort:
  80. c = &chunkAbort{}
  81. case ctCookieEcho:
  82. c = &chunkCookieEcho{}
  83. case ctCookieAck:
  84. c = &chunkCookieAck{}
  85. case ctHeartbeat:
  86. c = &chunkHeartbeat{}
  87. case ctPayloadData:
  88. c = &chunkPayloadData{}
  89. case ctSack:
  90. c = &chunkSelectiveAck{}
  91. case ctReconfig:
  92. c = &chunkReconfig{}
  93. case ctForwardTSN:
  94. c = &chunkForwardTSN{}
  95. case ctError:
  96. c = &chunkError{}
  97. case ctShutdown:
  98. c = &chunkShutdown{}
  99. case ctShutdownAck:
  100. c = &chunkShutdownAck{}
  101. case ctShutdownComplete:
  102. c = &chunkShutdownComplete{}
  103. default:
  104. return fmt.Errorf("%w: %s", ErrUnmarshalUnknownChunkType, chunkType(raw[offset]).String())
  105. }
  106. if err := c.unmarshal(raw[offset:]); err != nil {
  107. return err
  108. }
  109. p.chunks = append(p.chunks, c)
  110. chunkValuePadding := getPadding(c.valueLength())
  111. offset += chunkHeaderSize + c.valueLength() + chunkValuePadding
  112. }
  113. theirChecksum := binary.LittleEndian.Uint32(raw[8:])
  114. ourChecksum := generatePacketChecksum(raw)
  115. if theirChecksum != ourChecksum {
  116. return fmt.Errorf("%w: %d ours: %d", ErrChecksumMismatch, theirChecksum, ourChecksum)
  117. }
  118. return nil
  119. }
  120. func (p *packet) marshal() ([]byte, error) {
  121. raw := make([]byte, packetHeaderSize)
  122. // Populate static headers
  123. // 8-12 is Checksum which will be populated when packet is complete
  124. binary.BigEndian.PutUint16(raw[0:], p.sourcePort)
  125. binary.BigEndian.PutUint16(raw[2:], p.destinationPort)
  126. binary.BigEndian.PutUint32(raw[4:], p.verificationTag)
  127. // Populate chunks
  128. for _, c := range p.chunks {
  129. chunkRaw, err := c.marshal()
  130. if err != nil {
  131. return nil, err
  132. }
  133. raw = append(raw, chunkRaw...)
  134. paddingNeeded := getPadding(len(raw))
  135. if paddingNeeded != 0 {
  136. raw = append(raw, make([]byte, paddingNeeded)...)
  137. }
  138. }
  139. // Checksum is already in BigEndian
  140. // Using LittleEndian.PutUint32 stops it from being flipped
  141. binary.LittleEndian.PutUint32(raw[8:], generatePacketChecksum(raw))
  142. return raw, nil
  143. }
  144. func generatePacketChecksum(raw []byte) (sum uint32) {
  145. // Fastest way to do a crc32 without allocating.
  146. sum = crc32.Update(sum, castagnoliTable, raw[0:8])
  147. sum = crc32.Update(sum, castagnoliTable, fourZeroes[:])
  148. sum = crc32.Update(sum, castagnoliTable, raw[12:])
  149. return sum
  150. }
  151. // String makes packet printable
  152. func (p *packet) String() string {
  153. format := `Packet:
  154. sourcePort: %d
  155. destinationPort: %d
  156. verificationTag: %d
  157. `
  158. res := fmt.Sprintf(format,
  159. p.sourcePort,
  160. p.destinationPort,
  161. p.verificationTag,
  162. )
  163. for i, chunk := range p.chunks {
  164. res += fmt.Sprintf("Chunk %d:\n %s", i, chunk)
  165. }
  166. return res
  167. }