packet.go 5.7 KB

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