packet_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. package iputil
  2. import (
  3. "encoding/binary"
  4. "net"
  5. "testing"
  6. "github.com/stretchr/testify/assert"
  7. "golang.org/x/net/ipv4"
  8. "golang.org/x/net/ipv6"
  9. )
  10. func Test_CreateRejectPacket(t *testing.T) {
  11. h := ipv4.Header{
  12. Len: 20,
  13. Src: net.IPv4(10, 0, 0, 1),
  14. Dst: net.IPv4(10, 0, 0, 2),
  15. Protocol: 1, // ICMP
  16. }
  17. b, err := h.Marshal()
  18. if err != nil {
  19. t.Fatalf("h.Marhshal: %v", err)
  20. }
  21. b = append(b, []byte{0, 3, 0, 4}...)
  22. expectedLen := ipv4.HeaderLen + 8 + h.Len + 4
  23. out := make([]byte, expectedLen)
  24. rejectPacket := CreateRejectPacket(b, out)
  25. assert.NotNil(t, rejectPacket)
  26. assert.Len(t, rejectPacket, expectedLen)
  27. // ICMP with max header len
  28. h = ipv4.Header{
  29. Len: 60,
  30. Src: net.IPv4(10, 0, 0, 1),
  31. Dst: net.IPv4(10, 0, 0, 2),
  32. Protocol: 1, // ICMP
  33. Options: make([]byte, 40),
  34. }
  35. b, err = h.Marshal()
  36. if err != nil {
  37. t.Fatalf("h.Marhshal: %v", err)
  38. }
  39. b = append(b, []byte{0, 3, 0, 4, 0, 0, 0, 0}...)
  40. expectedLen = maxIPv4RejectPacketSize
  41. out = make([]byte, MaxRejectPacketSize)
  42. rejectPacket = CreateRejectPacket(b, out)
  43. assert.NotNil(t, rejectPacket)
  44. assert.Len(t, rejectPacket, expectedLen)
  45. // TCP with max header len
  46. h = ipv4.Header{
  47. Len: 60,
  48. Src: net.IPv4(10, 0, 0, 1),
  49. Dst: net.IPv4(10, 0, 0, 2),
  50. Protocol: 6, // TCP
  51. Options: make([]byte, 40),
  52. }
  53. b, err = h.Marshal()
  54. if err != nil {
  55. t.Fatalf("h.Marhshal: %v", err)
  56. }
  57. b = append(b, []byte{0, 3, 0, 4}...)
  58. b = append(b, make([]byte, 16)...)
  59. expectedLen = ipv4.HeaderLen + 20
  60. out = make([]byte, expectedLen)
  61. rejectPacket = CreateRejectPacket(b, out)
  62. assert.NotNil(t, rejectPacket)
  63. assert.Len(t, rejectPacket, expectedLen)
  64. }
  65. func Test_CreateRejectPacket_NoFragment(t *testing.T) {
  66. out := make([]byte, MaxRejectPacketSize)
  67. // IPv4: non-zero fragment offset should not generate reject packet
  68. h := ipv4.Header{
  69. Len: 20,
  70. Src: net.IPv4(10, 0, 0, 1),
  71. Dst: net.IPv4(10, 0, 0, 2),
  72. Protocol: 17, // UDP
  73. }
  74. b, err := h.Marshal()
  75. if err != nil {
  76. t.Fatalf("h.Marshal: %v", err)
  77. }
  78. b = append(b, make([]byte, 8)...)
  79. // Set fragment offset to non-zero (byte 6-7, offset in 8-byte units)
  80. b[6] = 0x00
  81. b[7] = 0x01
  82. assert.Nil(t, CreateRejectPacket(b, out))
  83. // MF flag with zero offset (first fragment) should still generate reject
  84. b[6] = 0x20 // MF flag set
  85. b[7] = 0x00
  86. assert.NotNil(t, CreateRejectPacket(b, out))
  87. // Non-fragment should still generate reject packet
  88. b[6] = 0x00
  89. b[7] = 0x00
  90. assert.NotNil(t, CreateRejectPacket(b, out))
  91. // DF flag only (not a fragment) should still generate reject packet
  92. b[6] = 0x40
  93. b[7] = 0x00
  94. assert.NotNil(t, CreateRejectPacket(b, out))
  95. }
  96. func Test_CreateRejectPacketIPv6_NoFragment(t *testing.T) {
  97. src := net.ParseIP("fd00::1")
  98. dst := net.ParseIP("fd00::2")
  99. out := make([]byte, MaxRejectPacketSize)
  100. // IPv6 with Fragment header and non-zero offset should not generate reject
  101. fragHeader := []byte{
  102. 17, // next header: UDP
  103. 0, // reserved
  104. 0, 9, // fragment offset=1 (shifted left 3), M=1
  105. 0, 0, 0, 1, // identification
  106. }
  107. udpPayload := make([]byte, 8)
  108. payload := append(fragHeader, udpPayload...)
  109. packet := makeIPv6Packet(src, dst, 44, payload) // next header 44 = Fragment
  110. assert.Nil(t, CreateRejectPacket(packet, out))
  111. // Fragment header with zero offset (first fragment) should still generate reject
  112. fragHeader[2] = 0
  113. fragHeader[3] = 1 // offset=0, M=1
  114. payload = append(fragHeader, udpPayload...)
  115. packet = makeIPv6Packet(src, dst, 44, payload)
  116. assert.NotNil(t, CreateRejectPacket(packet, out))
  117. }
  118. func Test_CreateRejectPacket_NoICMPError(t *testing.T) {
  119. out := make([]byte, MaxRejectPacketSize)
  120. // ICMP error types should not generate reject packets
  121. icmpErrorTypes := []byte{3, 4, 5, 11, 12}
  122. for _, icmpType := range icmpErrorTypes {
  123. h := ipv4.Header{
  124. Len: 20,
  125. Src: net.IPv4(10, 0, 0, 1),
  126. Dst: net.IPv4(10, 0, 0, 2),
  127. Protocol: 1, // ICMP
  128. }
  129. b, err := h.Marshal()
  130. if err != nil {
  131. t.Fatalf("h.Marshal: %v", err)
  132. }
  133. b = append(b, icmpType, 0, 0, 0, 0, 0, 0, 0)
  134. rejectPacket := CreateRejectPacket(b, out)
  135. assert.Nil(t, rejectPacket, "ICMP type %d should not generate a reject packet", icmpType)
  136. }
  137. // ICMP non-error types should still generate reject packets
  138. icmpNonErrorTypes := []byte{0, 8, 13, 14}
  139. for _, icmpType := range icmpNonErrorTypes {
  140. h := ipv4.Header{
  141. Len: 20,
  142. Src: net.IPv4(10, 0, 0, 1),
  143. Dst: net.IPv4(10, 0, 0, 2),
  144. Protocol: 1, // ICMP
  145. }
  146. b, err := h.Marshal()
  147. if err != nil {
  148. t.Fatalf("h.Marshal: %v", err)
  149. }
  150. b = append(b, icmpType, 0, 0, 0, 0, 0, 0, 0)
  151. rejectPacket := CreateRejectPacket(b, out)
  152. assert.NotNil(t, rejectPacket, "ICMP type %d should generate a reject packet", icmpType)
  153. }
  154. }
  155. func makeIPv6Packet(src, dst net.IP, nextHeader uint8, payload []byte) []byte {
  156. b := make([]byte, ipv6.HeaderLen+len(payload))
  157. b[0] = ipv6.Version << 4
  158. binary.BigEndian.PutUint16(b[4:], uint16(len(payload)))
  159. b[6] = nextHeader
  160. b[7] = 64
  161. copy(b[8:24], src.To16())
  162. copy(b[24:40], dst.To16())
  163. copy(b[ipv6.HeaderLen:], payload)
  164. return b
  165. }
  166. func Test_CreateRejectPacketIPv6_ICMP(t *testing.T) {
  167. src := net.ParseIP("fd00::1")
  168. dst := net.ParseIP("fd00::2")
  169. // Small UDP packet: entire original included in body
  170. udpPayload := make([]byte, 20)
  171. udpPayload[0] = 0x00 // src port high
  172. udpPayload[1] = 0x50 // src port low (80)
  173. udpPayload[2] = 0x01 // dst port high
  174. udpPayload[3] = 0xBB // dst port low (443)
  175. packet := makeIPv6Packet(src, dst, 17, udpPayload)
  176. out := make([]byte, MaxRejectPacketSize)
  177. rejectPacket := CreateRejectPacket(packet, out)
  178. assert.NotNil(t, rejectPacket)
  179. // Small packet fits entirely: 40 (ipv6 hdr) + 8 (icmpv6 hdr) + 60 (original)
  180. expectedLen := ipv6.HeaderLen + 8 + len(packet)
  181. assert.Len(t, rejectPacket, expectedLen)
  182. // Verify version
  183. assert.Equal(t, byte(ipv6.Version<<4), rejectPacket[0]&0xf0)
  184. // Verify next header is ICMPv6 (58)
  185. assert.Equal(t, byte(58), rejectPacket[6])
  186. // Verify src/dst are swapped
  187. assert.Equal(t, dst.To16(), net.IP(rejectPacket[8:24]))
  188. assert.Equal(t, src.To16(), net.IP(rejectPacket[24:40]))
  189. // Verify ICMPv6 type=1 (Dest Unreachable), code=1 (Administratively prohibited)
  190. assert.Equal(t, byte(1), rejectPacket[ipv6.HeaderLen])
  191. assert.Equal(t, byte(1), rejectPacket[ipv6.HeaderLen+1])
  192. // Verify entire original packet is included in body
  193. assert.Equal(t, packet, rejectPacket[ipv6.HeaderLen+8:])
  194. // Large packet: body is truncated to 1000 bytes
  195. largePkt := makeIPv6Packet(src, dst, 17, make([]byte, 1200))
  196. rejectPacket = CreateRejectPacket(largePkt, out)
  197. assert.NotNil(t, rejectPacket)
  198. assert.Len(t, rejectPacket, ipv6.HeaderLen+8+1000)
  199. assert.Equal(t, largePkt[:1000], rejectPacket[ipv6.HeaderLen+8:])
  200. }
  201. func Test_CreateRejectPacketIPv6_TCP(t *testing.T) {
  202. src := net.ParseIP("fd00::1")
  203. dst := net.ParseIP("fd00::2")
  204. // TCP SYN packet (next header 6)
  205. tcpPayload := make([]byte, 20)
  206. tcpPayload[0] = 0x00 // src port high
  207. tcpPayload[1] = 0x50 // src port low (80)
  208. tcpPayload[2] = 0x01 // dst port high
  209. tcpPayload[3] = 0xBB // dst port low (443)
  210. binary.BigEndian.PutUint32(tcpPayload[4:], 1000) // seq
  211. binary.BigEndian.PutUint32(tcpPayload[8:], 0) // ack seq
  212. tcpPayload[12] = (20 >> 2) << 4 // data offset
  213. tcpPayload[13] = 0b00000010 // SYN flag
  214. packet := makeIPv6Packet(src, dst, 6, tcpPayload)
  215. out := make([]byte, MaxRejectPacketSize)
  216. rejectPacket := CreateRejectPacket(packet, out)
  217. assert.NotNil(t, rejectPacket)
  218. // Expected: 40 (ipv6 hdr) + 20 (tcp RST)
  219. expectedLen := ipv6.HeaderLen + 20
  220. assert.Len(t, rejectPacket, expectedLen)
  221. // Verify version
  222. assert.Equal(t, byte(ipv6.Version<<4), rejectPacket[0]&0xf0)
  223. // Verify next header is TCP (6)
  224. assert.Equal(t, byte(6), rejectPacket[6])
  225. // Verify src/dst are swapped
  226. assert.Equal(t, dst.To16(), net.IP(rejectPacket[8:24]))
  227. assert.Equal(t, src.To16(), net.IP(rejectPacket[24:40]))
  228. // Verify ports are swapped
  229. tcpOut := rejectPacket[ipv6.HeaderLen:]
  230. assert.Equal(t, uint16(443), binary.BigEndian.Uint16(tcpOut[0:2]))
  231. assert.Equal(t, uint16(80), binary.BigEndian.Uint16(tcpOut[2:4]))
  232. // RST+ACK flags (since input was SYN without ACK)
  233. assert.Equal(t, byte(0b00010100), tcpOut[13])
  234. // ack_seq = original seq (1000) + SYN (1) + FIN (0) + segment data (0)
  235. assert.Equal(t, uint32(1001), binary.BigEndian.Uint32(tcpOut[8:]))
  236. }
  237. func Test_CreateRejectPacketIPv6_TCPWithACK(t *testing.T) {
  238. src := net.ParseIP("fd00::1")
  239. dst := net.ParseIP("fd00::2")
  240. // TCP packet with ACK set
  241. tcpPayload := make([]byte, 20)
  242. tcpPayload[0] = 0x00
  243. tcpPayload[1] = 0x50
  244. tcpPayload[2] = 0x01
  245. tcpPayload[3] = 0xBB
  246. binary.BigEndian.PutUint32(tcpPayload[4:], 1000) // seq
  247. binary.BigEndian.PutUint32(tcpPayload[8:], 2000) // ack seq
  248. tcpPayload[12] = (20 >> 2) << 4 // data offset
  249. tcpPayload[13] = 0b00010000 // ACK flag
  250. packet := makeIPv6Packet(src, dst, 6, tcpPayload)
  251. out := make([]byte, MaxRejectPacketSize)
  252. rejectPacket := CreateRejectPacket(packet, out)
  253. assert.NotNil(t, rejectPacket)
  254. tcpOut := rejectPacket[ipv6.HeaderLen:]
  255. // RST only (no ACK) since input had ACK
  256. assert.Equal(t, byte(0b00000100), tcpOut[13])
  257. // seq = original ack_seq
  258. assert.Equal(t, uint32(2000), binary.BigEndian.Uint32(tcpOut[4:]))
  259. }
  260. func Test_CreateRejectPacketIPv6_NoICMPError(t *testing.T) {
  261. src := net.ParseIP("fd00::1")
  262. dst := net.ParseIP("fd00::2")
  263. out := make([]byte, MaxRejectPacketSize)
  264. // ICMPv6 error types (1-4) should not generate reject packets
  265. for icmpType := byte(1); icmpType <= 4; icmpType++ {
  266. payload := make([]byte, 8)
  267. payload[0] = icmpType
  268. packet := makeIPv6Packet(src, dst, 58, payload)
  269. rejectPacket := CreateRejectPacket(packet, out)
  270. assert.Nil(t, rejectPacket, "ICMPv6 type %d should not generate a reject packet", icmpType)
  271. }
  272. // ICMPv6 non-error types should still generate reject packets
  273. nonErrorTypes := []byte{128, 129, 133, 134}
  274. for _, icmpType := range nonErrorTypes {
  275. payload := make([]byte, 8)
  276. payload[0] = icmpType
  277. packet := makeIPv6Packet(src, dst, 58, payload)
  278. rejectPacket := CreateRejectPacket(packet, out)
  279. assert.NotNil(t, rejectPacket, "ICMPv6 type %d should generate a reject packet", icmpType)
  280. }
  281. }
  282. func Test_CreateRejectPacketIPv6_TooShort(t *testing.T) {
  283. // Packet too short to be valid IPv6
  284. out := make([]byte, MaxRejectPacketSize)
  285. assert.Nil(t, CreateRejectPacket([]byte{0x60}, out))
  286. assert.Nil(t, CreateRejectPacket(make([]byte, 39), out))
  287. }
  288. func Test_CreateRejectPacketIPv6_ExtensionHeaders(t *testing.T) {
  289. src := net.ParseIP("fd00::1")
  290. dst := net.ParseIP("fd00::2")
  291. // IPv6 + Hop-by-Hop extension header + TCP
  292. hopByHop := []byte{
  293. 6, // next header: TCP
  294. 0, // length (8 bytes total)
  295. 0, 0, // padding
  296. 0, 0, 0, 0,
  297. }
  298. tcpPayload := make([]byte, 20)
  299. tcpPayload[0] = 0x00
  300. tcpPayload[1] = 0x50
  301. tcpPayload[2] = 0x01
  302. tcpPayload[3] = 0xBB
  303. binary.BigEndian.PutUint32(tcpPayload[4:], 1000)
  304. binary.BigEndian.PutUint32(tcpPayload[8:], 2000)
  305. tcpPayload[12] = (20 >> 2) << 4
  306. tcpPayload[13] = 0b00010000 // ACK
  307. payload := append(hopByHop, tcpPayload...)
  308. packet := makeIPv6Packet(src, dst, 0, payload) // next header 0 = Hop-by-Hop
  309. out := make([]byte, MaxRejectPacketSize)
  310. rejectPacket := CreateRejectPacket(packet, out)
  311. assert.NotNil(t, rejectPacket)
  312. // Should produce TCP RST
  313. expectedLen := ipv6.HeaderLen + 20
  314. assert.Len(t, rejectPacket, expectedLen)
  315. assert.Equal(t, byte(6), rejectPacket[6]) // next header is TCP
  316. tcpOut := rejectPacket[ipv6.HeaderLen:]
  317. assert.Equal(t, byte(0b00000100), tcpOut[13]) // RST only
  318. }
  319. func TestCreateICMPEchoResponse_IPv4(t *testing.T) {
  320. // Build a simple IPv4 ICMP Echo Request
  321. packet := make([]byte, 28)
  322. packet[0] = 0x45 // version 4, IHL 5
  323. binary.BigEndian.PutUint16(packet[2:], uint16(28)) // total length
  324. packet[8] = 64 // TTL
  325. packet[9] = 1 // protocol ICMP
  326. copy(packet[12:16], net.IPv4(10, 0, 0, 1).To4()) // src
  327. copy(packet[16:20], net.IPv4(10, 0, 0, 2).To4()) // dst
  328. packet[20] = 8 // ICMP Echo Request
  329. out := make([]byte, len(packet))
  330. result := CreateICMPEchoResponse(packet, out)
  331. assert.NotNil(t, result)
  332. assert.Equal(t, byte(0x45), result[0])
  333. // src/dst swapped
  334. assert.Equal(t, net.IPv4(10, 0, 0, 2).To4(), net.IP(result[12:16]))
  335. assert.Equal(t, net.IPv4(10, 0, 0, 1).To4(), net.IP(result[16:20]))
  336. // ICMP Echo Reply
  337. assert.Equal(t, byte(0), result[20])
  338. }
  339. func TestCreateICMPEchoResponse_IPv6(t *testing.T) {
  340. src := net.ParseIP("fd00::1").To16()
  341. dst := net.ParseIP("fd00::2").To16()
  342. // Build an IPv6 ICMPv6 Echo Request packet
  343. // IPv6 header (40 bytes) + ICMPv6 (8 bytes)
  344. packet := make([]byte, 48)
  345. packet[0] = 0x60 // version 6
  346. payloadLen := uint16(8) // ICMPv6 header only
  347. binary.BigEndian.PutUint16(packet[4:], payloadLen)
  348. packet[6] = 58 // Next Header: ICMPv6
  349. packet[7] = 64 // Hop Limit
  350. copy(packet[8:24], src) // src address
  351. copy(packet[24:40], dst) // dst address
  352. // ICMPv6 Echo Request
  353. icmp := packet[40:]
  354. icmp[0] = 128 // type: Echo Request
  355. icmp[1] = 0 // code
  356. binary.BigEndian.PutUint16(icmp[4:], 1) // identifier
  357. binary.BigEndian.PutUint16(icmp[6:], 1) // sequence number
  358. // Compute correct checksum for the request
  359. csum := ipv6PseudoheaderChecksum(src, dst, 58, uint32(payloadLen))
  360. binary.BigEndian.PutUint16(icmp[2:], tcpipChecksum(icmp, csum))
  361. out := make([]byte, len(packet))
  362. result := CreateICMPEchoResponse(packet, out)
  363. assert.NotNil(t, result)
  364. // Version should still be 6
  365. assert.Equal(t, byte(6), result[0]>>4)
  366. // src/dst swapped
  367. assert.Equal(t, dst, net.IP(result[8:24]))
  368. assert.Equal(t, src, net.IP(result[24:40]))
  369. // ICMPv6 Echo Reply type
  370. assert.Equal(t, byte(129), result[40])
  371. // Verify checksum is valid (tcpipChecksum returns 0 when data+checksum is correct)
  372. respIcmp := result[40:]
  373. verifyCsum := ipv6PseudoheaderChecksum(result[8:24], result[24:40], 58, uint32(payloadLen))
  374. assert.Equal(t, uint16(0), tcpipChecksum(respIcmp, verifyCsum))
  375. }
  376. func TestCreateICMPEchoResponse_IPv6_NotEchoRequest(t *testing.T) {
  377. src := net.ParseIP("fd00::1").To16()
  378. dst := net.ParseIP("fd00::2").To16()
  379. packet := make([]byte, 48)
  380. packet[0] = 0x60
  381. binary.BigEndian.PutUint16(packet[4:], 8)
  382. packet[6] = 58
  383. packet[7] = 64
  384. copy(packet[8:24], src)
  385. copy(packet[24:40], dst)
  386. // ICMPv6 type 1 (Destination Unreachable) - not Echo Request
  387. packet[40] = 1
  388. out := make([]byte, len(packet))
  389. result := CreateICMPEchoResponse(packet, out)
  390. assert.Nil(t, result)
  391. }
  392. func TestCreateICMPEchoResponse_IPv6_NotICMPv6(t *testing.T) {
  393. src := net.ParseIP("fd00::1").To16()
  394. dst := net.ParseIP("fd00::2").To16()
  395. packet := make([]byte, 48)
  396. packet[0] = 0x60
  397. binary.BigEndian.PutUint16(packet[4:], 8)
  398. packet[6] = 6 // TCP, not ICMPv6
  399. packet[7] = 64
  400. copy(packet[8:24], src)
  401. copy(packet[24:40], dst)
  402. out := make([]byte, len(packet))
  403. result := CreateICMPEchoResponse(packet, out)
  404. assert.Nil(t, result)
  405. }