packet.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. package iputil
  2. import (
  3. "encoding/binary"
  4. "errors"
  5. "golang.org/x/net/ipv4"
  6. "golang.org/x/net/ipv6"
  7. )
  8. // ErrIPv6CouldNotFindPayload is returned when the ipv6 extension header chain is truncated before a terminal
  9. // upper layer protocol is reached.
  10. var ErrIPv6CouldNotFindPayload = errors.New("could not find payload in ipv6 packet")
  11. const (
  12. // MaxIPv4RejectPacketSize is the largest IPv4 reject packet:
  13. // - 20 byte ipv4 header
  14. // - 8 byte icmpv4 header
  15. // - 68 byte body (60 byte max orig ipv4 header + 8 byte orig icmpv4 header)
  16. maxIPv4RejectPacketSize = ipv4.HeaderLen + 8 + 60 + 8
  17. // MaxRejectPacketSize is sized for the largest possible reject packet (IPv6):
  18. // - 40 byte ipv6 header
  19. // - 8 byte icmpv6 header
  20. // - up to 1000 byte body (original packet, possibly truncated. We want to stay
  21. // under the MTU with Nebula overhead included)
  22. maxIPv6RejectPacketSize = ipv6.HeaderLen + 8 + 1000
  23. MaxRejectPacketSize = maxIPv6RejectPacketSize
  24. )
  25. func CreateRejectPacket(packet []byte, out []byte) []byte {
  26. if len(packet) < 1 {
  27. return nil
  28. }
  29. version := int(packet[0] >> 4)
  30. switch version {
  31. case ipv4.Version:
  32. if len(packet) < ipv4.HeaderLen {
  33. return nil
  34. }
  35. // Do not send reject packets for non-first fragments
  36. if packet[6]&0x1f != 0 || packet[7] != 0 {
  37. return nil
  38. }
  39. switch packet[9] {
  40. case 6: // tcp
  41. return ipv4CreateRejectTCPPacket(packet, out)
  42. default:
  43. return ipv4CreateRejectICMPPacket(packet, out)
  44. }
  45. case ipv6.Version:
  46. if len(packet) < ipv6.HeaderLen {
  47. return nil
  48. }
  49. return ipv6CreateRejectPacket(packet, out)
  50. default:
  51. return nil
  52. }
  53. }
  54. func ipv4CreateRejectICMPPacket(packet []byte, out []byte) []byte {
  55. ihl := int(packet[0]&0x0f) << 2
  56. if len(packet) < ihl {
  57. // We need at least this many bytes for this to be a valid packet
  58. return nil
  59. }
  60. // Do not generate ICMP errors in response to ICMP error packets
  61. if packet[9] == 1 && len(packet) > ihl {
  62. icmpType := packet[ihl]
  63. if icmpType == 3 || icmpType == 4 || icmpType == 5 || icmpType == 11 || icmpType == 12 {
  64. return nil
  65. }
  66. }
  67. // ICMP reply includes original header and first 8 bytes of the packet
  68. packetLen := min(len(packet), ihl+8)
  69. outLen := ipv4.HeaderLen + 8 + packetLen
  70. if outLen > cap(out) {
  71. return nil
  72. }
  73. out = out[:outLen]
  74. ipHdr := out[0:ipv4.HeaderLen]
  75. ipHdr[0] = ipv4.Version<<4 | (ipv4.HeaderLen >> 2) // version, ihl
  76. ipHdr[1] = 0 // DSCP, ECN
  77. binary.BigEndian.PutUint16(ipHdr[2:], uint16(outLen)) // Total Length
  78. ipHdr[4] = 0 // id
  79. ipHdr[5] = 0 // .
  80. ipHdr[6] = 0 // flags, fragment offset
  81. ipHdr[7] = 0 // .
  82. ipHdr[8] = 64 // TTL
  83. ipHdr[9] = 1 // protocol (icmp)
  84. ipHdr[10] = 0 // checksum
  85. ipHdr[11] = 0 // .
  86. // Swap dest / src IPs
  87. copy(ipHdr[12:16], packet[16:20])
  88. copy(ipHdr[16:20], packet[12:16])
  89. // Calculate checksum
  90. binary.BigEndian.PutUint16(ipHdr[10:], tcpipChecksum(ipHdr, 0))
  91. // ICMP Destination Unreachable
  92. icmpOut := out[ipv4.HeaderLen:]
  93. icmpOut[0] = 3 // type (Destination unreachable)
  94. icmpOut[1] = 13 // code (Communication administratively prohibited)
  95. icmpOut[2] = 0 // checksum
  96. icmpOut[3] = 0 // .
  97. icmpOut[4] = 0 // unused
  98. icmpOut[5] = 0 // .
  99. icmpOut[6] = 0 // .
  100. icmpOut[7] = 0 // .
  101. // Copy original IP header and first 8 bytes as body
  102. copy(icmpOut[8:], packet[:packetLen])
  103. // Calculate checksum
  104. binary.BigEndian.PutUint16(icmpOut[2:], tcpipChecksum(icmpOut, 0))
  105. return out
  106. }
  107. func ipv4CreateRejectTCPPacket(packet []byte, out []byte) []byte {
  108. const tcpLen = 20
  109. ihl := int(packet[0]&0x0f) << 2
  110. outLen := ipv4.HeaderLen + tcpLen
  111. if len(packet) < ihl+tcpLen {
  112. // We need at least this many bytes for this to be a valid packet
  113. return nil
  114. }
  115. if outLen > cap(out) {
  116. return nil
  117. }
  118. out = out[:outLen]
  119. ipHdr := out[0:ipv4.HeaderLen]
  120. ipHdr[0] = ipv4.Version<<4 | (ipv4.HeaderLen >> 2) // version, ihl
  121. ipHdr[1] = 0 // DSCP, ECN
  122. binary.BigEndian.PutUint16(ipHdr[2:], uint16(outLen)) // Total Length
  123. ipHdr[4] = 0 // id
  124. ipHdr[5] = 0 // .
  125. ipHdr[6] = 0 // flags, fragment offset
  126. ipHdr[7] = 0 // .
  127. ipHdr[8] = 64 // TTL
  128. ipHdr[9] = 6 // protocol (tcp)
  129. ipHdr[10] = 0 // checksum
  130. ipHdr[11] = 0 // .
  131. // Swap dest / src IPs
  132. copy(ipHdr[12:16], packet[16:20])
  133. copy(ipHdr[16:20], packet[12:16])
  134. // Calculate checksum
  135. binary.BigEndian.PutUint16(ipHdr[10:], tcpipChecksum(ipHdr, 0))
  136. // TCP RST
  137. tcpIn := packet[ihl:]
  138. var ackSeq, seq uint32
  139. outFlags := byte(0b00000100) // RST
  140. // Set seq and ackSeq based on how iptables/netfilter does it in Linux:
  141. // - https://github.com/torvalds/linux/blob/v5.19/net/ipv4/netfilter/nf_reject_ipv4.c#L193-L221
  142. inAck := tcpIn[13]&0b00010000 != 0
  143. if inAck {
  144. seq = binary.BigEndian.Uint32(tcpIn[8:])
  145. } else {
  146. inSyn := uint32((tcpIn[13] & 0b00000010) >> 1)
  147. inFin := uint32(tcpIn[13] & 0b00000001)
  148. // seq from the packet + syn + fin + tcp segment length
  149. ackSeq = binary.BigEndian.Uint32(tcpIn[4:]) + inSyn + inFin + uint32(len(tcpIn)) - uint32(tcpIn[12]>>4)<<2
  150. outFlags |= 0b00010000 // ACK
  151. }
  152. tcpOut := out[ipv4.HeaderLen:]
  153. // Swap dest / src ports
  154. copy(tcpOut[0:2], tcpIn[2:4])
  155. copy(tcpOut[2:4], tcpIn[0:2])
  156. binary.BigEndian.PutUint32(tcpOut[4:], seq)
  157. binary.BigEndian.PutUint32(tcpOut[8:], ackSeq)
  158. tcpOut[12] = (tcpLen >> 2) << 4 // data offset, reserved, NS
  159. tcpOut[13] = outFlags // CWR, ECE, URG, ACK, PSH, RST, SYN, FIN
  160. tcpOut[14] = 0 // window size
  161. tcpOut[15] = 0 // .
  162. tcpOut[16] = 0 // checksum
  163. tcpOut[17] = 0 // .
  164. tcpOut[18] = 0 // URG Pointer
  165. tcpOut[19] = 0 // .
  166. // Calculate checksum
  167. csum := ipv4PseudoheaderChecksum(ipHdr[12:16], ipHdr[16:20], 6, tcpLen)
  168. binary.BigEndian.PutUint16(tcpOut[16:], tcpipChecksum(tcpOut, csum))
  169. return out
  170. }
  171. func ipv6CreateRejectPacket(packet []byte, out []byte) []byte {
  172. proto, offset, isFragment, _, err := IPv6FindUpperProtocol(packet)
  173. if err != nil || isFragment {
  174. return nil
  175. }
  176. switch proto {
  177. case 6: // tcp
  178. return ipv6CreateRejectTCPPacket(packet, out, offset)
  179. default:
  180. return ipv6CreateRejectICMPPacket(packet, out, proto, offset)
  181. }
  182. }
  183. func ipv6CreateRejectICMPPacket(packet []byte, out []byte, proto uint8, offset int) []byte {
  184. // Do not generate ICMPv6 errors in response to ICMPv6 error packets
  185. if proto == 58 && len(packet) > offset {
  186. icmpType := packet[offset]
  187. if icmpType >= 1 && icmpType <= 4 {
  188. return nil
  189. }
  190. }
  191. // Include as much of the original packet as possible, up to 1000 bytes,
  192. // so the response fits comfortably within any tunnel MTU.
  193. packetLen := min(len(packet), 1000)
  194. outLen := ipv6.HeaderLen + 8 + packetLen
  195. if outLen > cap(out) {
  196. return nil
  197. }
  198. out = out[:outLen]
  199. // IPv6 header
  200. ipHdr := out[0:ipv6.HeaderLen]
  201. ipHdr[0] = ipv6.Version << 4 // version, traffic class (high bits)
  202. ipHdr[1] = 0 // traffic class (low bits), flow label (high bits)
  203. ipHdr[2] = 0 // flow label
  204. ipHdr[3] = 0 // flow label
  205. payloadLen := uint16(outLen - ipv6.HeaderLen)
  206. binary.BigEndian.PutUint16(ipHdr[4:], payloadLen) // payload length
  207. ipHdr[6] = 58 // next header (ICMPv6)
  208. ipHdr[7] = 64 // hop limit
  209. // Swap dest / src IPs (each 16 bytes, src at 8, dst at 24)
  210. copy(ipHdr[8:24], packet[24:40])
  211. copy(ipHdr[24:40], packet[8:24])
  212. // ICMPv6 Destination Unreachable
  213. icmpOut := out[ipv6.HeaderLen:]
  214. icmpOut[0] = 1 // type (Destination Unreachable)
  215. icmpOut[1] = 1 // code (Communication with destination administratively prohibited)
  216. icmpOut[2] = 0 // checksum
  217. icmpOut[3] = 0 // .
  218. icmpOut[4] = 0 // unused
  219. icmpOut[5] = 0 // .
  220. icmpOut[6] = 0 // .
  221. icmpOut[7] = 0 // .
  222. copy(icmpOut[8:], packet[:packetLen])
  223. // ICMPv6 checksum uses a pseudo-header
  224. csum := ipv6PseudoheaderChecksum(ipHdr[8:24], ipHdr[24:40], 58, uint32(payloadLen))
  225. binary.BigEndian.PutUint16(icmpOut[2:], tcpipChecksum(icmpOut, csum))
  226. return out
  227. }
  228. func ipv6CreateRejectTCPPacket(packet []byte, out []byte, offset int) []byte {
  229. const tcpLen = 20
  230. if len(packet) < offset+tcpLen {
  231. return nil
  232. }
  233. outLen := ipv6.HeaderLen + tcpLen
  234. if outLen > cap(out) {
  235. return nil
  236. }
  237. out = out[:outLen]
  238. // IPv6 header
  239. ipHdr := out[0:ipv6.HeaderLen]
  240. ipHdr[0] = ipv6.Version << 4 // version, traffic class (high bits)
  241. ipHdr[1] = 0 // traffic class (low bits), flow label (high bits)
  242. ipHdr[2] = 0 // flow label
  243. ipHdr[3] = 0 // flow label
  244. binary.BigEndian.PutUint16(ipHdr[4:], tcpLen) // payload length
  245. ipHdr[6] = 6 // next header (TCP)
  246. ipHdr[7] = 64 // hop limit
  247. // Swap dest / src IPs
  248. copy(ipHdr[8:24], packet[24:40])
  249. copy(ipHdr[24:40], packet[8:24])
  250. // TCP RST
  251. tcpIn := packet[offset:]
  252. var ackSeq, seq uint32
  253. outFlags := byte(0b00000100) // RST
  254. inAck := tcpIn[13]&0b00010000 != 0
  255. if inAck {
  256. seq = binary.BigEndian.Uint32(tcpIn[8:])
  257. } else {
  258. inSyn := uint32((tcpIn[13] & 0b00000010) >> 1)
  259. inFin := uint32(tcpIn[13] & 0b00000001)
  260. ackSeq = binary.BigEndian.Uint32(tcpIn[4:]) + inSyn + inFin + uint32(len(tcpIn)) - uint32(tcpIn[12]>>4)<<2
  261. outFlags |= 0b00010000 // ACK
  262. }
  263. tcpOut := out[ipv6.HeaderLen:]
  264. // Swap dest / src ports
  265. copy(tcpOut[0:2], tcpIn[2:4])
  266. copy(tcpOut[2:4], tcpIn[0:2])
  267. binary.BigEndian.PutUint32(tcpOut[4:], seq)
  268. binary.BigEndian.PutUint32(tcpOut[8:], ackSeq)
  269. tcpOut[12] = (tcpLen >> 2) << 4 // data offset, reserved, NS
  270. tcpOut[13] = outFlags // CWR, ECE, URG, ACK, PSH, RST, SYN, FIN
  271. tcpOut[14] = 0 // window size
  272. tcpOut[15] = 0 // .
  273. tcpOut[16] = 0 // checksum
  274. tcpOut[17] = 0 // .
  275. tcpOut[18] = 0 // URG Pointer
  276. tcpOut[19] = 0 // .
  277. // Calculate checksum with IPv6 pseudo-header
  278. csum := ipv6PseudoheaderChecksum(ipHdr[8:24], ipHdr[24:40], 6, tcpLen)
  279. binary.BigEndian.PutUint16(tcpOut[16:], tcpipChecksum(tcpOut, csum))
  280. return out
  281. }
  282. // IPv6FindUpperProtocol walks the ipv6 extension header chain and returns the upper layer protocol, the
  283. // offset it begins at, and whether the packet is a non-first fragment. Only the RFC 8200 and IANA extension
  284. // headers below are walked. Everything else, including Mobility (135), HIP (139), Shim6 (140), experimental
  285. // 253/254, and real upper layer protocols like SCTP or GRE, is terminal. Walking those as extension headers
  286. // is a firewall bypass, so they fail closed. For a non-first fragment the returned protocol is the fragmented
  287. // protocol and offset points at the fragment header, there is no transport header to locate. Returns
  288. // ErrIPv6CouldNotFindPayload if packet is smaller than an ipv6 header or the chain is truncated before a
  289. // terminal protocol is reached.
  290. func IPv6FindUpperProtocol(packet []byte) (nextHeader uint8, offset int, isFragment, anyFragment bool, err error) {
  291. if len(packet) < ipv6.HeaderLen {
  292. return 0, 0, false, false, ErrIPv6CouldNotFindPayload
  293. }
  294. nextHeader = packet[6]
  295. offset = ipv6.HeaderLen
  296. for {
  297. switch nextHeader {
  298. case 0, 43, 60: // Hop-by-Hop, Routing, Destination
  299. if len(packet) < offset+2 {
  300. return nextHeader, offset, isFragment, anyFragment, ErrIPv6CouldNotFindPayload
  301. }
  302. nextHeader = packet[offset]
  303. offset += (int(packet[offset+1]) + 1) << 3
  304. case 44: // Fragment
  305. if len(packet) < offset+8 {
  306. return nextHeader, offset, isFragment, anyFragment, ErrIPv6CouldNotFindPayload
  307. }
  308. anyFragment = true
  309. // Non-first fragments carry no transport header, report the fragmented protocol and stop
  310. if packet[offset+2] != 0 || packet[offset+3]&0xf8 != 0 {
  311. return packet[offset], offset, true, anyFragment, nil
  312. }
  313. nextHeader = packet[offset]
  314. offset += 8
  315. case 51: // AH
  316. if len(packet) < offset+2 {
  317. return nextHeader, offset, isFragment, anyFragment, ErrIPv6CouldNotFindPayload
  318. }
  319. nextHeader = packet[offset]
  320. offset += (int(packet[offset+1]) + 2) << 2
  321. default:
  322. // A prior extension header can declare a length that advances offset past the packet. The terminal
  323. // protocol's header isn't actually here, so treat the chain as truncated rather than classifying it.
  324. if offset > len(packet) {
  325. return nextHeader, offset, isFragment, anyFragment, ErrIPv6CouldNotFindPayload
  326. }
  327. return nextHeader, offset, isFragment, anyFragment, nil
  328. }
  329. }
  330. }
  331. func CreateICMPEchoResponse(packet, out []byte) []byte {
  332. if len(packet) < 1 {
  333. return nil
  334. }
  335. switch packet[0] >> 4 {
  336. case 4:
  337. return createICMPv4EchoResponse(packet, out)
  338. case 6:
  339. return createICMPv6EchoResponse(packet, out)
  340. default:
  341. return nil
  342. }
  343. }
  344. func createICMPv4EchoResponse(packet, out []byte) []byte {
  345. // Return early if this is not a simple ICMP Echo Request
  346. //TODO: make constants out of these
  347. if !(len(packet) >= 28 && len(packet) <= 9001 && packet[0] == 0x45 && packet[9] == 0x01 && packet[20] == 0x08) {
  348. return nil
  349. }
  350. // We don't support fragmented packets
  351. if packet[7] != 0 || (packet[6]&0x2F != 0) {
  352. return nil
  353. }
  354. out = out[:len(packet)]
  355. copy(out, packet)
  356. // Swap dest / src IPs and recalculate checksum
  357. ipv4 := out[0:20]
  358. copy(ipv4[12:16], packet[16:20])
  359. copy(ipv4[16:20], packet[12:16])
  360. ipv4[10] = 0
  361. ipv4[11] = 0
  362. binary.BigEndian.PutUint16(ipv4[10:], tcpipChecksum(ipv4, 0))
  363. // Change type to ICMP Echo Reply and recalculate checksum
  364. icmp := out[20:]
  365. icmp[0] = 0
  366. icmp[2] = 0
  367. icmp[3] = 0
  368. binary.BigEndian.PutUint16(icmp[2:], tcpipChecksum(icmp, 0))
  369. return out
  370. }
  371. func createICMPv6EchoResponse(packet, out []byte) []byte {
  372. // IPv6 header (40 bytes) + ICMPv6 header (8 bytes minimum)
  373. if len(packet) < ipv6.HeaderLen+8 || len(packet) > 9001 {
  374. return nil
  375. }
  376. // Next Header must be ICMPv6 (58)
  377. if packet[6] != 58 {
  378. return nil
  379. }
  380. // ICMPv6 type must be Echo Request (128)
  381. if packet[ipv6.HeaderLen] != 128 {
  382. return nil
  383. }
  384. out = out[:len(packet)]
  385. copy(out, packet)
  386. // Swap src/dst addresses (bytes 8-23 and 24-39)
  387. copy(out[8:24], packet[24:40])
  388. copy(out[24:40], packet[8:24])
  389. // Change ICMPv6 type to Echo Reply (129)
  390. icmp := out[ipv6.HeaderLen:]
  391. icmp[0] = 129
  392. icmp[2] = 0
  393. icmp[3] = 0
  394. // ICMPv6 checksum uses a pseudo-header with src, dst, length, and next header
  395. payloadLen := uint32(len(icmp))
  396. csum := ipv6PseudoheaderChecksum(out[8:24], out[24:40], 58, payloadLen)
  397. binary.BigEndian.PutUint16(icmp[2:], tcpipChecksum(icmp, csum))
  398. return out
  399. }
  400. // calculates the TCP/IP checksum defined in rfc1071. The passed-in
  401. // csum is any initial checksum data that's already been computed.
  402. //
  403. // based on:
  404. // - https://github.com/google/gopacket/blob/v1.1.19/layers/tcpip.go#L50-L70
  405. func tcpipChecksum(data []byte, csum uint32) uint16 {
  406. // to handle odd lengths, we loop to length - 1, incrementing by 2, then
  407. // handle the last byte specifically by checking against the original
  408. // length.
  409. length := len(data) - 1
  410. for i := 0; i < length; i += 2 {
  411. // For our test packet, doing this manually is about 25% faster
  412. // (740 ns vs. 1000ns) than doing it by calling binary.BigEndian.Uint16.
  413. csum += uint32(data[i]) << 8
  414. csum += uint32(data[i+1])
  415. }
  416. if len(data)%2 == 1 {
  417. csum += uint32(data[length]) << 8
  418. }
  419. for csum > 0xffff {
  420. csum = (csum >> 16) + (csum & 0xffff)
  421. }
  422. return ^uint16(csum)
  423. }
  424. // based on:
  425. // - https://github.com/google/gopacket/blob/v1.1.19/layers/tcpip.go#L26-L35
  426. func ipv4PseudoheaderChecksum(src, dst []byte, proto, length uint32) (csum uint32) {
  427. csum += (uint32(src[0]) + uint32(src[2])) << 8
  428. csum += uint32(src[1]) + uint32(src[3])
  429. csum += (uint32(dst[0]) + uint32(dst[2])) << 8
  430. csum += uint32(dst[1]) + uint32(dst[3])
  431. csum += proto
  432. csum += length & 0xffff
  433. csum += length >> 16
  434. return csum
  435. }
  436. // based on:
  437. // - https://github.com/google/gopacket/blob/v1.1.19/layers/tcpip.go#L37-L48
  438. func ipv6PseudoheaderChecksum(src, dst []byte, proto, length uint32) (csum uint32) {
  439. for i := 0; i < 16; i += 2 {
  440. csum += uint32(src[i]) << 8
  441. csum += uint32(src[i+1])
  442. csum += uint32(dst[i]) << 8
  443. csum += uint32(dst[i+1])
  444. }
  445. csum += proto
  446. csum += length & 0xffff
  447. csum += length >> 16
  448. return csum
  449. }