compression.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. package aerotun
  2. import (
  3. "encoding/binary"
  4. "github.com/pierrec/lz4/v4"
  5. )
  6. // isEncryptedIPPacket inspects the packet's IP and transport headers to determine
  7. // if the payload is already encrypted or part of a protocol with high entropy
  8. // that cannot be compressed with LZ4.
  9. func isEncryptedIPPacket(b []byte) bool {
  10. if len(b) < 20 {
  11. return false
  12. }
  13. ver := b[0] >> 4
  14. if ver == 4 {
  15. ihl := int(b[0]&0x0f) * 4
  16. if len(b) < ihl {
  17. return false
  18. }
  19. proto := b[9]
  20. if proto == 50 { // IPsec ESP
  21. return true
  22. }
  23. if proto == 6 || proto == 17 { // TCP or UDP
  24. if len(b) < ihl+4 {
  25. return false
  26. }
  27. srcPort := binary.BigEndian.Uint16(b[ihl : ihl+2])
  28. dstPort := binary.BigEndian.Uint16(b[ihl+2 : ihl+4])
  29. return isEncryptedPort(srcPort) || isEncryptedPort(dstPort)
  30. }
  31. } else if ver == 6 {
  32. if len(b) < 40 {
  33. return false
  34. }
  35. next := b[6]
  36. if next == 50 { // IPsec ESP
  37. return true
  38. }
  39. if next == 6 || next == 17 { // TCP or UDP
  40. if len(b) < 44 {
  41. return false
  42. }
  43. srcPort := binary.BigEndian.Uint16(b[40:42])
  44. dstPort := binary.BigEndian.Uint16(b[42:44])
  45. return isEncryptedPort(srcPort) || isEncryptedPort(dstPort)
  46. }
  47. }
  48. return false
  49. }
  50. // isEncryptedPort checks whether the given TCP/UDP port is a well-known
  51. // encrypted or high-entropy transport protocol.
  52. func isEncryptedPort(port uint16) bool {
  53. switch port {
  54. case 22, // SSH / SFTP
  55. 443, // HTTPS, HTTP/3 (QUIC)
  56. 465, // SMTPS
  57. 500, // IKE (IPsec)
  58. 853, // DNS-over-TLS (DoT), DNS-over-QUIC (DoQ)
  59. 993, // IMAPS
  60. 995, // POP3S
  61. 1194, // OpenVPN
  62. 4500, // IPsec NAT-Traversal
  63. 8443, // Alternative HTTPS
  64. 51820, // WireGuard
  65. 51830: // AeroTUN encapsulated
  66. return true
  67. default:
  68. return false
  69. }
  70. }
  71. // adaptiveCompressor manages block compression with fast port bypass and
  72. // adaptive failure backoff, preventing CPU starvation from uncompressible streams.
  73. type adaptiveCompressor struct {
  74. compBuf []byte
  75. skipCount int
  76. failCount int
  77. }
  78. func newAdaptiveCompressor(mtu int) *adaptiveCompressor {
  79. if mtu <= 0 {
  80. mtu = DefaultMTU
  81. }
  82. return &adaptiveCompressor{
  83. compBuf: make([]byte, lz4.CompressBlockBound(mtu)),
  84. }
  85. }
  86. // Compress attempts to compress the payload using LZ4. It returns the compressed
  87. // slice and true if compression succeeded and saved at least 16 bytes.
  88. // If the packet is unsuitable, encrypted, or during adaptive backoff, it returns
  89. // the original payload and false without wasting CPU cycles.
  90. func (c *adaptiveCompressor) Compress(payload []byte) ([]byte, bool) {
  91. // Skip small packets (TCP ACKs, keepalives, small DNS queries)
  92. if len(payload) < 128 {
  93. return payload, false
  94. }
  95. // Fast bypass: skip known encrypted protocols instantly
  96. if isEncryptedIPPacket(payload) {
  97. return payload, false
  98. }
  99. // Adaptive backoff: if consecutive attempts recently failed, skip without invoking LZ4
  100. if c.skipCount > 0 {
  101. c.skipCount--
  102. return payload, false
  103. }
  104. n, err := lz4.CompressBlock(payload, c.compBuf, nil)
  105. if err == nil && n > 0 && n < len(payload)-16 {
  106. c.failCount = 0
  107. return c.compBuf[:n], true
  108. }
  109. // Failed to save at least 16 bytes: record failure
  110. c.failCount++
  111. if c.failCount >= 16 {
  112. // Incompressible stream detected: back off for the next 256 packets before probing again
  113. c.skipCount = 256
  114. c.failCount = 0
  115. }
  116. return payload, false
  117. }