packet.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. package iputil
  2. import (
  3. "encoding/binary"
  4. "golang.org/x/net/ipv4"
  5. )
  6. func CreateRejectPacket(packet []byte, out []byte) []byte {
  7. // TODO ipv4 only, need to fix when inside supports ipv6
  8. switch packet[9] {
  9. case 6: // tcp
  10. return ipv4CreateRejectTCPPacket(packet, out)
  11. default:
  12. return ipv4CreateRejectICMPPacket(packet, out)
  13. }
  14. }
  15. func ipv4CreateRejectICMPPacket(packet []byte, out []byte) []byte {
  16. ihl := int(packet[0]&0x0f) << 2
  17. // ICMP reply includes header and first 8 bytes of the packet
  18. packetLen := len(packet)
  19. if packetLen > ihl+8 {
  20. packetLen = ihl + 8
  21. }
  22. outLen := ipv4.HeaderLen + 8 + packetLen
  23. out = out[:(outLen)]
  24. ipHdr := out[0:ipv4.HeaderLen]
  25. ipHdr[0] = ipv4.Version<<4 | (ipv4.HeaderLen >> 2) // version, ihl
  26. ipHdr[1] = 0 // DSCP, ECN
  27. binary.BigEndian.PutUint16(ipHdr[2:], uint16(ipv4.HeaderLen+8+packetLen)) // Total Length
  28. ipHdr[4] = 0 // id
  29. ipHdr[5] = 0 // .
  30. ipHdr[6] = 0 // flags, fragment offset
  31. ipHdr[7] = 0 // .
  32. ipHdr[8] = 64 // TTL
  33. ipHdr[9] = 1 // protocol (icmp)
  34. ipHdr[10] = 0 // checksum
  35. ipHdr[11] = 0 // .
  36. // Swap dest / src IPs
  37. copy(ipHdr[12:16], packet[16:20])
  38. copy(ipHdr[16:20], packet[12:16])
  39. // Calculate checksum
  40. binary.BigEndian.PutUint16(ipHdr[10:], tcpipChecksum(ipHdr, 0))
  41. // ICMP Destination Unreachable
  42. icmpOut := out[ipv4.HeaderLen:]
  43. icmpOut[0] = 3 // type (Destination unreachable)
  44. icmpOut[1] = 3 // code (Port unreachable error)
  45. icmpOut[2] = 0 // checksum
  46. icmpOut[3] = 0 // .
  47. icmpOut[4] = 0 // unused
  48. icmpOut[5] = 0 // .
  49. icmpOut[6] = 0 // .
  50. icmpOut[7] = 0 // .
  51. // Copy original IP header and first 8 bytes as body
  52. copy(icmpOut[8:], packet[:packetLen])
  53. // Calculate checksum
  54. binary.BigEndian.PutUint16(icmpOut[2:], tcpipChecksum(icmpOut, 0))
  55. return out
  56. }
  57. func ipv4CreateRejectTCPPacket(packet []byte, out []byte) []byte {
  58. const tcpLen = 20
  59. ihl := int(packet[0]&0x0f) << 2
  60. outLen := ipv4.HeaderLen + tcpLen
  61. out = out[:(outLen)]
  62. ipHdr := out[0:ipv4.HeaderLen]
  63. ipHdr[0] = ipv4.Version<<4 | (ipv4.HeaderLen >> 2) // version, ihl
  64. ipHdr[1] = 0 // DSCP, ECN
  65. binary.BigEndian.PutUint16(ipHdr[2:], uint16(outLen)) // Total Length
  66. ipHdr[4] = 0 // id
  67. ipHdr[5] = 0 // .
  68. ipHdr[6] = 0 // flags, fragment offset
  69. ipHdr[7] = 0 // .
  70. ipHdr[8] = 64 // TTL
  71. ipHdr[9] = 6 // protocol (tcp)
  72. ipHdr[10] = 0 // checksum
  73. ipHdr[11] = 0 // .
  74. // Swap dest / src IPs
  75. copy(ipHdr[12:16], packet[16:20])
  76. copy(ipHdr[16:20], packet[12:16])
  77. // Calculate checksum
  78. binary.BigEndian.PutUint16(ipHdr[10:], tcpipChecksum(ipHdr, 0))
  79. // TCP RST
  80. tcpIn := packet[ihl:]
  81. var ackSeq, seq uint32
  82. outFlags := byte(0b00000100) // RST
  83. // Set seq and ackSeq based on how iptables/netfilter does it in Linux:
  84. // - https://github.com/torvalds/linux/blob/v5.19/net/ipv4/netfilter/nf_reject_ipv4.c#L193-L221
  85. inAck := tcpIn[13]&0b00010000 != 0
  86. if inAck {
  87. seq = binary.BigEndian.Uint32(tcpIn[8:])
  88. } else {
  89. inSyn := uint32((tcpIn[13] & 0b00000010) >> 1)
  90. inFin := uint32(tcpIn[13] & 0b00000001)
  91. // seq from the packet + syn + fin + tcp segment length
  92. ackSeq = binary.BigEndian.Uint32(tcpIn[4:]) + inSyn + inFin + uint32(len(tcpIn)) - uint32(tcpIn[12]>>4)<<2
  93. outFlags |= 0b00010000 // ACK
  94. }
  95. tcpOut := out[ipv4.HeaderLen:]
  96. // Swap dest / src ports
  97. copy(tcpOut[0:2], tcpIn[2:4])
  98. copy(tcpOut[2:4], tcpIn[0:2])
  99. binary.BigEndian.PutUint32(tcpOut[4:], seq)
  100. binary.BigEndian.PutUint32(tcpOut[8:], ackSeq)
  101. tcpOut[12] = (tcpLen >> 2) << 4 // data offset, reserved, NS
  102. tcpOut[13] = outFlags // CWR, ECE, URG, ACK, PSH, RST, SYN, FIN
  103. tcpOut[14] = 0 // window size
  104. tcpOut[15] = 0 // .
  105. tcpOut[16] = 0 // checksum
  106. tcpOut[17] = 0 // .
  107. tcpOut[18] = 0 // URG Pointer
  108. tcpOut[19] = 0 // .
  109. // Calculate checksum
  110. csum := ipv4PseudoheaderChecksum(ipHdr[12:16], ipHdr[16:20], 6, tcpLen)
  111. binary.BigEndian.PutUint16(tcpOut[16:], tcpipChecksum(tcpOut, csum))
  112. return out
  113. }
  114. func CreateICMPEchoResponse(packet, out []byte) []byte {
  115. // Return early if this is not a simple ICMP Echo Request
  116. //TODO: make constants out of these
  117. if !(len(packet) >= 28 && len(packet) <= 9001 && packet[0] == 0x45 && packet[9] == 0x01 && packet[20] == 0x08) {
  118. return nil
  119. }
  120. // We don't support fragmented packets
  121. if packet[7] != 0 || (packet[6]&0x2F != 0) {
  122. return nil
  123. }
  124. out = out[:len(packet)]
  125. copy(out, packet)
  126. // Swap dest / src IPs and recalculate checksum
  127. ipv4 := out[0:20]
  128. copy(ipv4[12:16], packet[16:20])
  129. copy(ipv4[16:20], packet[12:16])
  130. ipv4[10] = 0
  131. ipv4[11] = 0
  132. binary.BigEndian.PutUint16(ipv4[10:], tcpipChecksum(ipv4, 0))
  133. // Change type to ICMP Echo Reply and recalculate checksum
  134. icmp := out[20:]
  135. icmp[0] = 0
  136. icmp[2] = 0
  137. icmp[3] = 0
  138. binary.BigEndian.PutUint16(icmp[2:], tcpipChecksum(icmp, 0))
  139. return out
  140. }
  141. // calculates the TCP/IP checksum defined in rfc1071. The passed-in
  142. // csum is any initial checksum data that's already been computed.
  143. //
  144. // based on:
  145. // - https://github.com/google/gopacket/blob/v1.1.19/layers/tcpip.go#L50-L70
  146. func tcpipChecksum(data []byte, csum uint32) uint16 {
  147. // to handle odd lengths, we loop to length - 1, incrementing by 2, then
  148. // handle the last byte specifically by checking against the original
  149. // length.
  150. length := len(data) - 1
  151. for i := 0; i < length; i += 2 {
  152. // For our test packet, doing this manually is about 25% faster
  153. // (740 ns vs. 1000ns) than doing it by calling binary.BigEndian.Uint16.
  154. csum += uint32(data[i]) << 8
  155. csum += uint32(data[i+1])
  156. }
  157. if len(data)%2 == 1 {
  158. csum += uint32(data[length]) << 8
  159. }
  160. for csum > 0xffff {
  161. csum = (csum >> 16) + (csum & 0xffff)
  162. }
  163. return ^uint16(csum)
  164. }
  165. // based on:
  166. // - https://github.com/google/gopacket/blob/v1.1.19/layers/tcpip.go#L26-L35
  167. func ipv4PseudoheaderChecksum(src, dst []byte, proto, length uint32) (csum uint32) {
  168. csum += (uint32(src[0]) + uint32(src[2])) << 8
  169. csum += uint32(src[1]) + uint32(src[3])
  170. csum += (uint32(dst[0]) + uint32(dst[2])) << 8
  171. csum += uint32(dst[1]) + uint32(dst[3])
  172. csum += proto
  173. csum += length & 0xffff
  174. csum += length >> 16
  175. return csum
  176. }