packet_test.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. package iputil
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "net"
  6. "testing"
  7. "github.com/stretchr/testify/assert"
  8. "github.com/stretchr/testify/require"
  9. "golang.org/x/net/ipv4"
  10. "golang.org/x/net/ipv6"
  11. )
  12. func Test_CreateRejectPacket(t *testing.T) {
  13. h := ipv4.Header{
  14. Len: 20,
  15. Src: net.IPv4(10, 0, 0, 1),
  16. Dst: net.IPv4(10, 0, 0, 2),
  17. Protocol: 1, // ICMP
  18. }
  19. b, err := h.Marshal()
  20. if err != nil {
  21. t.Fatalf("h.Marhshal: %v", err)
  22. }
  23. b = append(b, []byte{0, 3, 0, 4}...)
  24. expectedLen := ipv4.HeaderLen + 8 + h.Len + 4
  25. out := make([]byte, expectedLen)
  26. rejectPacket := CreateRejectPacket(b, out)
  27. assert.NotNil(t, rejectPacket)
  28. assert.Len(t, rejectPacket, expectedLen)
  29. // ICMP with max header len
  30. h = ipv4.Header{
  31. Len: 60,
  32. Src: net.IPv4(10, 0, 0, 1),
  33. Dst: net.IPv4(10, 0, 0, 2),
  34. Protocol: 1, // ICMP
  35. Options: make([]byte, 40),
  36. }
  37. b, err = h.Marshal()
  38. if err != nil {
  39. t.Fatalf("h.Marhshal: %v", err)
  40. }
  41. b = append(b, []byte{0, 3, 0, 4, 0, 0, 0, 0}...)
  42. expectedLen = maxIPv4RejectPacketSize
  43. out = make([]byte, MaxRejectPacketSize)
  44. rejectPacket = CreateRejectPacket(b, out)
  45. assert.NotNil(t, rejectPacket)
  46. assert.Len(t, rejectPacket, expectedLen)
  47. // TCP with max header len
  48. h = ipv4.Header{
  49. Len: 60,
  50. Src: net.IPv4(10, 0, 0, 1),
  51. Dst: net.IPv4(10, 0, 0, 2),
  52. Protocol: 6, // TCP
  53. Options: make([]byte, 40),
  54. }
  55. b, err = h.Marshal()
  56. if err != nil {
  57. t.Fatalf("h.Marhshal: %v", err)
  58. }
  59. b = append(b, []byte{0, 3, 0, 4}...)
  60. b = append(b, make([]byte, 16)...)
  61. expectedLen = ipv4.HeaderLen + 20
  62. out = make([]byte, expectedLen)
  63. rejectPacket = CreateRejectPacket(b, out)
  64. assert.NotNil(t, rejectPacket)
  65. assert.Len(t, rejectPacket, expectedLen)
  66. }
  67. func Test_CreateRejectPacket_NoFragment(t *testing.T) {
  68. out := make([]byte, MaxRejectPacketSize)
  69. // IPv4: non-zero fragment offset should not generate reject packet
  70. h := ipv4.Header{
  71. Len: 20,
  72. Src: net.IPv4(10, 0, 0, 1),
  73. Dst: net.IPv4(10, 0, 0, 2),
  74. Protocol: 17, // UDP
  75. }
  76. b, err := h.Marshal()
  77. if err != nil {
  78. t.Fatalf("h.Marshal: %v", err)
  79. }
  80. b = append(b, make([]byte, 8)...)
  81. // Set fragment offset to non-zero (byte 6-7, offset in 8-byte units)
  82. b[6] = 0x00
  83. b[7] = 0x01
  84. assert.Nil(t, CreateRejectPacket(b, out))
  85. // MF flag with zero offset (first fragment) should still generate reject
  86. b[6] = 0x20 // MF flag set
  87. b[7] = 0x00
  88. assert.NotNil(t, CreateRejectPacket(b, out))
  89. // Non-fragment should still generate reject packet
  90. b[6] = 0x00
  91. b[7] = 0x00
  92. assert.NotNil(t, CreateRejectPacket(b, out))
  93. // DF flag only (not a fragment) should still generate reject packet
  94. b[6] = 0x40
  95. b[7] = 0x00
  96. assert.NotNil(t, CreateRejectPacket(b, out))
  97. }
  98. func Test_CreateRejectPacketIPv6_NoFragment(t *testing.T) {
  99. src := net.ParseIP("fd00::1")
  100. dst := net.ParseIP("fd00::2")
  101. out := make([]byte, MaxRejectPacketSize)
  102. // IPv6 with Fragment header and non-zero offset should not generate reject
  103. fragHeader := []byte{
  104. 17, // next header: UDP
  105. 0, // reserved
  106. 0, 9, // fragment offset=1 (shifted left 3), M=1
  107. 0, 0, 0, 1, // identification
  108. }
  109. udpPayload := make([]byte, 8)
  110. payload := append(fragHeader, udpPayload...)
  111. packet := makeIPv6Packet(src, dst, 44, payload) // next header 44 = Fragment
  112. assert.Nil(t, CreateRejectPacket(packet, out))
  113. // Fragment header with zero offset (first fragment) should still generate reject
  114. fragHeader[2] = 0
  115. fragHeader[3] = 1 // offset=0, M=1
  116. payload = append(fragHeader, udpPayload...)
  117. packet = makeIPv6Packet(src, dst, 44, payload)
  118. assert.NotNil(t, CreateRejectPacket(packet, out))
  119. }
  120. func Test_CreateRejectPacket_NoICMPError(t *testing.T) {
  121. out := make([]byte, MaxRejectPacketSize)
  122. // ICMP error types should not generate reject packets
  123. icmpErrorTypes := []byte{3, 4, 5, 11, 12}
  124. for _, icmpType := range icmpErrorTypes {
  125. h := ipv4.Header{
  126. Len: 20,
  127. Src: net.IPv4(10, 0, 0, 1),
  128. Dst: net.IPv4(10, 0, 0, 2),
  129. Protocol: 1, // ICMP
  130. }
  131. b, err := h.Marshal()
  132. if err != nil {
  133. t.Fatalf("h.Marshal: %v", err)
  134. }
  135. b = append(b, icmpType, 0, 0, 0, 0, 0, 0, 0)
  136. rejectPacket := CreateRejectPacket(b, out)
  137. assert.Nil(t, rejectPacket, "ICMP type %d should not generate a reject packet", icmpType)
  138. }
  139. // ICMP non-error types should still generate reject packets
  140. icmpNonErrorTypes := []byte{0, 8, 13, 14}
  141. for _, icmpType := range icmpNonErrorTypes {
  142. h := ipv4.Header{
  143. Len: 20,
  144. Src: net.IPv4(10, 0, 0, 1),
  145. Dst: net.IPv4(10, 0, 0, 2),
  146. Protocol: 1, // ICMP
  147. }
  148. b, err := h.Marshal()
  149. if err != nil {
  150. t.Fatalf("h.Marshal: %v", err)
  151. }
  152. b = append(b, icmpType, 0, 0, 0, 0, 0, 0, 0)
  153. rejectPacket := CreateRejectPacket(b, out)
  154. assert.NotNil(t, rejectPacket, "ICMP type %d should generate a reject packet", icmpType)
  155. }
  156. }
  157. // Test_CreateRejectPacket_RespectsCap ensures it is impossible for
  158. // an oversized ICMPv6 reject to overwrite the neighbor segment's bytes.
  159. func Test_CreateRejectPacket_RespectsCap(t *testing.T) {
  160. src := net.ParseIP("fd00::1")
  161. dst := net.ParseIP("fd00::2")
  162. // Inner IPv6 UDP packet. An ICMPv6 reject copies the whole inner packet
  163. // plus a 48-byte header (40 IPv6 + 8 ICMPv6), so it needs 48 more bytes
  164. // than the inner packet length.
  165. inner := makeIPv6Packet(src, dst, 17, make([]byte, 20))
  166. // The ciphertext scratch reused as the reject buffer is the received
  167. // datagram: 16-byte Nebula header + inner + 16-byte AEAD tag. That is only
  168. // 32 bytes of slack, so a full ICMPv6 reject overruns it by 16 bytes.
  169. const nebulaOverhead = 32
  170. segLen := len(inner) + nebulaOverhead
  171. // Shared backing row laid out as [segment][neighbor's 16-byte Nebula header].
  172. const neighborHdr = 16
  173. sentinel := bytes.Repeat([]byte{0xAB}, neighborHdr)
  174. // Uncapped: the slice's capacity reaches into the neighbor, reproducing
  175. // the overrun that silently drops the neighbor packet.
  176. backing := make([]byte, segLen+neighborHdr)
  177. copy(backing[segLen:], sentinel)
  178. reject := CreateRejectPacket(inner, backing[:segLen])
  179. assert.NotNil(t, reject, "uncapped buffer reaches into the neighbor, so the reject is built")
  180. assert.NotEqual(t, sentinel, backing[segLen:segLen+neighborHdr],
  181. "without the cap the oversized reject overruns into the neighbor segment")
  182. // Capped (the fix): cap==len, so the builder cannot exceed the segment. The
  183. // reject does not fit, so it is refused rather than corrupting the neighbor.
  184. backing = make([]byte, segLen+neighborHdr)
  185. copy(backing[segLen:], sentinel)
  186. reject = CreateRejectPacket(inner, backing[:segLen:segLen])
  187. assert.Nil(t, reject, "capped segment is 16 bytes too small for a full ICMPv6 reject, so it is refused")
  188. assert.Equal(t, sentinel, backing[segLen:segLen+neighborHdr],
  189. "capped segment must leave the neighbor untouched")
  190. }
  191. func makeIPv6Packet(src, dst net.IP, nextHeader uint8, payload []byte) []byte {
  192. b := make([]byte, ipv6.HeaderLen+len(payload))
  193. b[0] = ipv6.Version << 4
  194. binary.BigEndian.PutUint16(b[4:], uint16(len(payload)))
  195. b[6] = nextHeader
  196. b[7] = 64
  197. copy(b[8:24], src.To16())
  198. copy(b[24:40], dst.To16())
  199. copy(b[ipv6.HeaderLen:], payload)
  200. return b
  201. }
  202. func Test_CreateRejectPacketIPv6_ICMP(t *testing.T) {
  203. src := net.ParseIP("fd00::1")
  204. dst := net.ParseIP("fd00::2")
  205. // Small UDP packet: entire original included in body
  206. udpPayload := make([]byte, 20)
  207. udpPayload[0] = 0x00 // src port high
  208. udpPayload[1] = 0x50 // src port low (80)
  209. udpPayload[2] = 0x01 // dst port high
  210. udpPayload[3] = 0xBB // dst port low (443)
  211. packet := makeIPv6Packet(src, dst, 17, udpPayload)
  212. out := make([]byte, MaxRejectPacketSize)
  213. rejectPacket := CreateRejectPacket(packet, out)
  214. assert.NotNil(t, rejectPacket)
  215. // Small packet fits entirely: 40 (ipv6 hdr) + 8 (icmpv6 hdr) + 60 (original)
  216. expectedLen := ipv6.HeaderLen + 8 + len(packet)
  217. assert.Len(t, rejectPacket, expectedLen)
  218. // Verify version
  219. assert.Equal(t, byte(ipv6.Version<<4), rejectPacket[0]&0xf0)
  220. // Verify next header is ICMPv6 (58)
  221. assert.Equal(t, byte(58), rejectPacket[6])
  222. // Verify src/dst are swapped
  223. assert.Equal(t, dst.To16(), net.IP(rejectPacket[8:24]))
  224. assert.Equal(t, src.To16(), net.IP(rejectPacket[24:40]))
  225. // Verify ICMPv6 type=1 (Dest Unreachable), code=1 (Administratively prohibited)
  226. assert.Equal(t, byte(1), rejectPacket[ipv6.HeaderLen])
  227. assert.Equal(t, byte(1), rejectPacket[ipv6.HeaderLen+1])
  228. // Verify entire original packet is included in body
  229. assert.Equal(t, packet, rejectPacket[ipv6.HeaderLen+8:])
  230. // Large packet: body is truncated to 1000 bytes
  231. largePkt := makeIPv6Packet(src, dst, 17, make([]byte, 1200))
  232. rejectPacket = CreateRejectPacket(largePkt, out)
  233. assert.NotNil(t, rejectPacket)
  234. assert.Len(t, rejectPacket, ipv6.HeaderLen+8+1000)
  235. assert.Equal(t, largePkt[:1000], rejectPacket[ipv6.HeaderLen+8:])
  236. }
  237. func Test_CreateRejectPacketIPv6_TCP(t *testing.T) {
  238. src := net.ParseIP("fd00::1")
  239. dst := net.ParseIP("fd00::2")
  240. // TCP SYN packet (next header 6)
  241. tcpPayload := make([]byte, 20)
  242. tcpPayload[0] = 0x00 // src port high
  243. tcpPayload[1] = 0x50 // src port low (80)
  244. tcpPayload[2] = 0x01 // dst port high
  245. tcpPayload[3] = 0xBB // dst port low (443)
  246. binary.BigEndian.PutUint32(tcpPayload[4:], 1000) // seq
  247. binary.BigEndian.PutUint32(tcpPayload[8:], 0) // ack seq
  248. tcpPayload[12] = (20 >> 2) << 4 // data offset
  249. tcpPayload[13] = 0b00000010 // SYN flag
  250. packet := makeIPv6Packet(src, dst, 6, tcpPayload)
  251. out := make([]byte, MaxRejectPacketSize)
  252. rejectPacket := CreateRejectPacket(packet, out)
  253. assert.NotNil(t, rejectPacket)
  254. // Expected: 40 (ipv6 hdr) + 20 (tcp RST)
  255. expectedLen := ipv6.HeaderLen + 20
  256. assert.Len(t, rejectPacket, expectedLen)
  257. // Verify version
  258. assert.Equal(t, byte(ipv6.Version<<4), rejectPacket[0]&0xf0)
  259. // Verify next header is TCP (6)
  260. assert.Equal(t, byte(6), rejectPacket[6])
  261. // Verify src/dst are swapped
  262. assert.Equal(t, dst.To16(), net.IP(rejectPacket[8:24]))
  263. assert.Equal(t, src.To16(), net.IP(rejectPacket[24:40]))
  264. // Verify ports are swapped
  265. tcpOut := rejectPacket[ipv6.HeaderLen:]
  266. assert.Equal(t, uint16(443), binary.BigEndian.Uint16(tcpOut[0:2]))
  267. assert.Equal(t, uint16(80), binary.BigEndian.Uint16(tcpOut[2:4]))
  268. // RST+ACK flags (since input was SYN without ACK)
  269. assert.Equal(t, byte(0b00010100), tcpOut[13])
  270. // ack_seq = original seq (1000) + SYN (1) + FIN (0) + segment data (0)
  271. assert.Equal(t, uint32(1001), binary.BigEndian.Uint32(tcpOut[8:]))
  272. }
  273. func Test_CreateRejectPacketIPv6_TCPWithACK(t *testing.T) {
  274. src := net.ParseIP("fd00::1")
  275. dst := net.ParseIP("fd00::2")
  276. // TCP packet with ACK set
  277. tcpPayload := make([]byte, 20)
  278. tcpPayload[0] = 0x00
  279. tcpPayload[1] = 0x50
  280. tcpPayload[2] = 0x01
  281. tcpPayload[3] = 0xBB
  282. binary.BigEndian.PutUint32(tcpPayload[4:], 1000) // seq
  283. binary.BigEndian.PutUint32(tcpPayload[8:], 2000) // ack seq
  284. tcpPayload[12] = (20 >> 2) << 4 // data offset
  285. tcpPayload[13] = 0b00010000 // ACK flag
  286. packet := makeIPv6Packet(src, dst, 6, tcpPayload)
  287. out := make([]byte, MaxRejectPacketSize)
  288. rejectPacket := CreateRejectPacket(packet, out)
  289. assert.NotNil(t, rejectPacket)
  290. tcpOut := rejectPacket[ipv6.HeaderLen:]
  291. // RST only (no ACK) since input had ACK
  292. assert.Equal(t, byte(0b00000100), tcpOut[13])
  293. // seq = original ack_seq
  294. assert.Equal(t, uint32(2000), binary.BigEndian.Uint32(tcpOut[4:]))
  295. }
  296. func Test_CreateRejectPacketIPv6_NoICMPError(t *testing.T) {
  297. src := net.ParseIP("fd00::1")
  298. dst := net.ParseIP("fd00::2")
  299. out := make([]byte, MaxRejectPacketSize)
  300. // ICMPv6 error types (1-4) should not generate reject packets
  301. for icmpType := byte(1); icmpType <= 4; icmpType++ {
  302. payload := make([]byte, 8)
  303. payload[0] = icmpType
  304. packet := makeIPv6Packet(src, dst, 58, payload)
  305. rejectPacket := CreateRejectPacket(packet, out)
  306. assert.Nil(t, rejectPacket, "ICMPv6 type %d should not generate a reject packet", icmpType)
  307. }
  308. // ICMPv6 non-error types should still generate reject packets
  309. nonErrorTypes := []byte{128, 129, 133, 134}
  310. for _, icmpType := range nonErrorTypes {
  311. payload := make([]byte, 8)
  312. payload[0] = icmpType
  313. packet := makeIPv6Packet(src, dst, 58, payload)
  314. rejectPacket := CreateRejectPacket(packet, out)
  315. assert.NotNil(t, rejectPacket, "ICMPv6 type %d should generate a reject packet", icmpType)
  316. }
  317. }
  318. func Test_CreateRejectPacketIPv6_TooShort(t *testing.T) {
  319. // Packet too short to be valid IPv6
  320. out := make([]byte, MaxRejectPacketSize)
  321. assert.Nil(t, CreateRejectPacket([]byte{0x60}, out))
  322. assert.Nil(t, CreateRejectPacket(make([]byte, 39), out))
  323. }
  324. func Test_CreateRejectPacketIPv6_ExtensionHeaders(t *testing.T) {
  325. src := net.ParseIP("fd00::1")
  326. dst := net.ParseIP("fd00::2")
  327. // IPv6 + Hop-by-Hop extension header + TCP
  328. hopByHop := []byte{
  329. 6, // next header: TCP
  330. 0, // length (8 bytes total)
  331. 0, 0, // padding
  332. 0, 0, 0, 0,
  333. }
  334. tcpPayload := make([]byte, 20)
  335. tcpPayload[0] = 0x00
  336. tcpPayload[1] = 0x50
  337. tcpPayload[2] = 0x01
  338. tcpPayload[3] = 0xBB
  339. binary.BigEndian.PutUint32(tcpPayload[4:], 1000)
  340. binary.BigEndian.PutUint32(tcpPayload[8:], 2000)
  341. tcpPayload[12] = (20 >> 2) << 4
  342. tcpPayload[13] = 0b00010000 // ACK
  343. payload := append(hopByHop, tcpPayload...)
  344. packet := makeIPv6Packet(src, dst, 0, payload) // next header 0 = Hop-by-Hop
  345. out := make([]byte, MaxRejectPacketSize)
  346. rejectPacket := CreateRejectPacket(packet, out)
  347. assert.NotNil(t, rejectPacket)
  348. // Should produce TCP RST
  349. expectedLen := ipv6.HeaderLen + 20
  350. assert.Len(t, rejectPacket, expectedLen)
  351. assert.Equal(t, byte(6), rejectPacket[6]) // next header is TCP
  352. tcpOut := rejectPacket[ipv6.HeaderLen:]
  353. assert.Equal(t, byte(0b00000100), tcpOut[13]) // RST only
  354. }
  355. func TestCreateICMPEchoResponse_IPv4(t *testing.T) {
  356. // Build a simple IPv4 ICMP Echo Request
  357. packet := make([]byte, 28)
  358. packet[0] = 0x45 // version 4, IHL 5
  359. binary.BigEndian.PutUint16(packet[2:], uint16(28)) // total length
  360. packet[8] = 64 // TTL
  361. packet[9] = 1 // protocol ICMP
  362. copy(packet[12:16], net.IPv4(10, 0, 0, 1).To4()) // src
  363. copy(packet[16:20], net.IPv4(10, 0, 0, 2).To4()) // dst
  364. packet[20] = 8 // ICMP Echo Request
  365. out := make([]byte, len(packet))
  366. result := CreateICMPEchoResponse(packet, out)
  367. assert.NotNil(t, result)
  368. assert.Equal(t, byte(0x45), result[0])
  369. // src/dst swapped
  370. assert.Equal(t, net.IPv4(10, 0, 0, 2).To4(), net.IP(result[12:16]))
  371. assert.Equal(t, net.IPv4(10, 0, 0, 1).To4(), net.IP(result[16:20]))
  372. // ICMP Echo Reply
  373. assert.Equal(t, byte(0), result[20])
  374. }
  375. func TestCreateICMPEchoResponse_IPv6(t *testing.T) {
  376. src := net.ParseIP("fd00::1").To16()
  377. dst := net.ParseIP("fd00::2").To16()
  378. // Build an IPv6 ICMPv6 Echo Request packet
  379. // IPv6 header (40 bytes) + ICMPv6 (8 bytes)
  380. packet := make([]byte, 48)
  381. packet[0] = 0x60 // version 6
  382. payloadLen := uint16(8) // ICMPv6 header only
  383. binary.BigEndian.PutUint16(packet[4:], payloadLen)
  384. packet[6] = 58 // Next Header: ICMPv6
  385. packet[7] = 64 // Hop Limit
  386. copy(packet[8:24], src) // src address
  387. copy(packet[24:40], dst) // dst address
  388. // ICMPv6 Echo Request
  389. icmp := packet[40:]
  390. icmp[0] = 128 // type: Echo Request
  391. icmp[1] = 0 // code
  392. binary.BigEndian.PutUint16(icmp[4:], 1) // identifier
  393. binary.BigEndian.PutUint16(icmp[6:], 1) // sequence number
  394. // Compute correct checksum for the request
  395. csum := ipv6PseudoheaderChecksum(src, dst, 58, uint32(payloadLen))
  396. binary.BigEndian.PutUint16(icmp[2:], tcpipChecksum(icmp, csum))
  397. out := make([]byte, len(packet))
  398. result := CreateICMPEchoResponse(packet, out)
  399. assert.NotNil(t, result)
  400. // Version should still be 6
  401. assert.Equal(t, byte(6), result[0]>>4)
  402. // src/dst swapped
  403. assert.Equal(t, dst, net.IP(result[8:24]))
  404. assert.Equal(t, src, net.IP(result[24:40]))
  405. // ICMPv6 Echo Reply type
  406. assert.Equal(t, byte(129), result[40])
  407. // Verify checksum is valid (tcpipChecksum returns 0 when data+checksum is correct)
  408. respIcmp := result[40:]
  409. verifyCsum := ipv6PseudoheaderChecksum(result[8:24], result[24:40], 58, uint32(payloadLen))
  410. assert.Equal(t, uint16(0), tcpipChecksum(respIcmp, verifyCsum))
  411. }
  412. func TestCreateICMPEchoResponse_IPv6_NotEchoRequest(t *testing.T) {
  413. src := net.ParseIP("fd00::1").To16()
  414. dst := net.ParseIP("fd00::2").To16()
  415. packet := make([]byte, 48)
  416. packet[0] = 0x60
  417. binary.BigEndian.PutUint16(packet[4:], 8)
  418. packet[6] = 58
  419. packet[7] = 64
  420. copy(packet[8:24], src)
  421. copy(packet[24:40], dst)
  422. // ICMPv6 type 1 (Destination Unreachable) - not Echo Request
  423. packet[40] = 1
  424. out := make([]byte, len(packet))
  425. result := CreateICMPEchoResponse(packet, out)
  426. assert.Nil(t, result)
  427. }
  428. func TestCreateICMPEchoResponse_IPv6_NotICMPv6(t *testing.T) {
  429. src := net.ParseIP("fd00::1").To16()
  430. dst := net.ParseIP("fd00::2").To16()
  431. packet := make([]byte, 48)
  432. packet[0] = 0x60
  433. binary.BigEndian.PutUint16(packet[4:], 8)
  434. packet[6] = 6 // TCP, not ICMPv6
  435. packet[7] = 64
  436. copy(packet[8:24], src)
  437. copy(packet[24:40], dst)
  438. out := make([]byte, len(packet))
  439. result := CreateICMPEchoResponse(packet, out)
  440. assert.Nil(t, result)
  441. }
  442. func Test_IPv6FindUpperProtocol(t *testing.T) {
  443. src := net.ParseIP("fd00::1")
  444. dst := net.ParseIP("fd00::2")
  445. // 8 byte extension/transport stand-ins, first byte is the next header, second is the length field
  446. extToTCP := []byte{6, 0, 0, 0, 0, 0, 0, 0} // len 0 -> 8 bytes, next = TCP
  447. extToUDP := []byte{17, 0, 0, 0, 0, 0, 0, 0} // len 0 -> 8 bytes, next = UDP
  448. extToRouting := []byte{43, 0, 0, 0, 0, 0, 0, 0} // len 0 -> 8 bytes, next = Routing
  449. ahToUDP := []byte{17, 0, 0, 0, 0, 0, 0, 0} // AH len 0 -> (0+2)<<2 = 8 bytes, next = UDP
  450. firstFragToUDP := []byte{17, 0, 0, 1, 0, 0, 0, 1} // frag offset 0, M=1, next = UDP
  451. nonFirstFrag := []byte{17, 0, 0, 9, 0, 0, 0, 1} // frag offset non-zero, next = UDP
  452. transport := []byte{0, 80, 1, 187, 0, 0, 0, 0} // stand-in bytes, IPv6FindUpperProtocol never reads ports
  453. extToHbH := []byte{0, 0, 0, 0, 0, 0, 0, 0} // len 0 -> 8 bytes, next = Hop-by-Hop
  454. // 8 walked headers spend the budget before the 9th slot's terminal is ever classified. This is the
  455. // report 4040546 shape: without failing closed the walk hands back offset 104, and a caller reads ports
  456. // from the attacker's bytes there.
  457. var budgetChain []byte
  458. for range 7 {
  459. budgetChain = append(budgetChain, extToHbH...)
  460. }
  461. budgetChain = append(budgetChain, extToTCP...)
  462. budgetChain = append(budgetChain, transport...)
  463. tests := []struct {
  464. name string
  465. nextHeader uint8
  466. payload []byte
  467. wantProto uint8
  468. wantOffset int
  469. wantFragment bool
  470. wantAnyFrag bool
  471. wantErr error
  472. }{
  473. {"plain udp", 17, transport, 17, ipv6.HeaderLen, false, false, nil},
  474. {"hop-by-hop then tcp", 0, append(extToTCP, transport...), 6, ipv6.HeaderLen + 8, false, false, nil},
  475. {"routing then tcp", 43, append(extToTCP, transport...), 6, ipv6.HeaderLen + 8, false, false, nil},
  476. {"destination then udp", 60, append(extToUDP, transport...), 17, ipv6.HeaderLen + 8, false, false, nil},
  477. {"hop-by-hop, routing, then tcp", 0, append(append(extToRouting, extToTCP...), transport...), 6, ipv6.HeaderLen + 16, false, false, nil},
  478. {"ah then udp", 51, append(ahToUDP, transport...), 17, ipv6.HeaderLen + 8, false, false, nil},
  479. {"first fragment walks to transport", 44, append(firstFragToUDP, transport...), 17, ipv6.HeaderLen + 8, false, true, nil},
  480. {"non-first fragment stops", 44, append(nonFirstFrag, transport...), 17, ipv6.HeaderLen, true, true, nil},
  481. {"unknown protocol is terminal", 132, transport, 132, ipv6.HeaderLen, false, false, nil}, // SCTP
  482. {"truncated extension header", 0, nil, 0, 0, false, false, ErrIPv6CouldNotFindPayload},
  483. // Destination Options with a declared length (255+1)*8 = 2048 that runs past the 48 byte buffer, next = SCTP
  484. {"extension length past buffer", 0, []byte{132, 255, 0, 0, 0, 0, 0, 0}, 0, 0, false, false, ErrIPv6CouldNotFindPayload},
  485. {"budget exhausted fails closed", 0, budgetChain, 0, 0, false, false, ErrIPv6CouldNotFindPayload},
  486. }
  487. for _, tt := range tests {
  488. t.Run(tt.name, func(t *testing.T) {
  489. packet := makeIPv6Packet(src, dst, tt.nextHeader, tt.payload)
  490. proto, offset, isFragment, anyFragment, err := IPv6FindUpperProtocol(packet)
  491. if tt.wantErr != nil {
  492. require.ErrorIs(t, err, tt.wantErr)
  493. } else {
  494. require.NoError(t, err)
  495. }
  496. assert.Equal(t, tt.wantProto, proto)
  497. assert.Equal(t, tt.wantOffset, offset)
  498. assert.Equal(t, tt.wantFragment, isFragment)
  499. assert.Equal(t, tt.wantAnyFrag, anyFragment)
  500. })
  501. }
  502. // A packet smaller than an ipv6 header must error rather than panic reading byte 6
  503. t.Run("shorter than ipv6 header", func(t *testing.T) {
  504. _, _, _, _, err := IPv6FindUpperProtocol(make([]byte, 6))
  505. assert.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
  506. })
  507. }