tunnel.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. package aerotun
  2. import (
  3. "crypto/cipher"
  4. "crypto/rand"
  5. "crypto/sha256"
  6. "encoding/binary"
  7. "fmt"
  8. "log"
  9. "net"
  10. "os/exec"
  11. "sync"
  12. "sync/atomic"
  13. "time"
  14. "github.com/pierrec/lz4/v4"
  15. "golang.org/x/crypto/chacha20poly1305"
  16. )
  17. type TunnelRunner struct {
  18. mu sync.RWMutex
  19. cfg TunnelInstance
  20. dev VirtualDevice
  21. udpConn *net.UDPConn
  22. aead cipher.AEAD
  23. remoteAddr *net.UDPAddr
  24. txCounter uint64
  25. rxMaxCounter uint64
  26. replayWindow uint64
  27. stopChan chan struct{}
  28. closed bool
  29. // Metrics
  30. rxBytes uint64
  31. txBytes uint64
  32. rawBytesIn uint64
  33. rawBytesOut uint64
  34. wireBytesIn uint64
  35. wireBytesOut uint64
  36. savedBytes uint64
  37. packetsRx uint64
  38. packetsTx uint64
  39. compressedPackets uint64
  40. rawPackets uint64
  41. lastHandshake time.Time
  42. }
  43. func NewTunnelRunner(cfg TunnelInstance) (*TunnelRunner, error) {
  44. // 1. Derive 32-byte ChaCha20-Poly1305 key from PSK
  45. if cfg.PSK == "" {
  46. return nil, fmt.Errorf("pre-shared key (PSK) is required")
  47. }
  48. key := sha256.Sum256([]byte(cfg.PSK))
  49. aead, err := chacha20poly1305.New(key[:])
  50. if err != nil {
  51. return nil, fmt.Errorf("failed to initialize ChaCha20-Poly1305: %w", err)
  52. }
  53. // 2. Create Virtual Network Device (TUN or VETH fallback)
  54. dev, err := CreateDevice(cfg.Name, cfg.MTU, cfg.LocalAddress)
  55. if err != nil {
  56. return nil, fmt.Errorf("failed to create virtual network device %s: %w", cfg.Name, err)
  57. }
  58. // 3. Resolve UDP socket / remote address
  59. var localUDPAddr *net.UDPAddr
  60. if cfg.ListenPort > 0 {
  61. localUDPAddr = &net.UDPAddr{Port: cfg.ListenPort}
  62. } else if stringsEqual(cfg.Mode, "server") {
  63. localUDPAddr = &net.UDPAddr{Port: DefaultPort}
  64. }
  65. udpConn, err := net.ListenUDP("udp", localUDPAddr)
  66. if err != nil {
  67. _ = dev.Close()
  68. return nil, fmt.Errorf("failed to bind UDP socket on port %d: %w", cfg.ListenPort, err)
  69. }
  70. var remoteAddr *net.UDPAddr
  71. if cfg.RemoteEndpoint != "" {
  72. resolved, err := net.ResolveUDPAddr("udp", cfg.RemoteEndpoint)
  73. if err == nil {
  74. remoteAddr = resolved
  75. }
  76. }
  77. runner := &TunnelRunner{
  78. cfg: cfg,
  79. dev: dev,
  80. udpConn: udpConn,
  81. aead: aead,
  82. remoteAddr: remoteAddr,
  83. stopChan: make(chan struct{}),
  84. }
  85. return runner, nil
  86. }
  87. func (t *TunnelRunner) Start() {
  88. go t.txLoop()
  89. go t.rxLoop()
  90. go t.keepaliveLoop()
  91. if t.cfg.DefaultRoute {
  92. _ = exec.Command("ip", "route", "replace", "default", "dev", t.cfg.Name).Run()
  93. }
  94. log.Printf("[AeroTUN] Tunnel %s started in %s mode (listen port %d, MTU %d)", t.cfg.Name, t.cfg.Mode, t.cfg.ListenPort, t.cfg.MTU)
  95. }
  96. func (t *TunnelRunner) Stop() {
  97. t.mu.Lock()
  98. if t.closed {
  99. t.mu.Unlock()
  100. return
  101. }
  102. t.closed = true
  103. close(t.stopChan)
  104. t.mu.Unlock()
  105. _ = t.udpConn.Close()
  106. _ = t.dev.Close()
  107. log.Printf("[AeroTUN] Tunnel %s stopped", t.cfg.Name)
  108. }
  109. // txLoop: Reads raw IP packets from TUN, compresses with LZ4, encrypts with ChaCha20-Poly1305, sends via UDP
  110. func (t *TunnelRunner) txLoop() {
  111. tunBuf := make([]byte, 2048)
  112. lz4Buf := make([]byte, 2048)
  113. udpPacket := make([]byte, 2048)
  114. for {
  115. select {
  116. case <-t.stopChan:
  117. return
  118. default:
  119. }
  120. n, err := t.dev.Read(tunBuf)
  121. if err != nil {
  122. select {
  123. case <-t.stopChan:
  124. return
  125. default:
  126. time.Sleep(10 * time.Millisecond)
  127. continue
  128. }
  129. }
  130. if n == 0 {
  131. continue
  132. }
  133. rawIP := tunBuf[:n]
  134. atomic.AddUint64(&t.rawBytesOut, uint64(n))
  135. atomic.AddUint64(&t.txBytes, uint64(n))
  136. atomic.AddUint64(&t.packetsTx, 1)
  137. // 1. Adaptive LZ4 Compression
  138. var flags byte = 0
  139. payload := rawIP
  140. if t.cfg.Compression && n > 40 {
  141. compressedSize, err := lz4.CompressBlock(rawIP, lz4Buf, nil)
  142. // Only compress if saving at least 16 bytes
  143. if err == nil && compressedSize > 0 && compressedSize < n-16 {
  144. flags |= FlagCompressedLZ4
  145. payload = lz4Buf[:compressedSize]
  146. saved := uint64(n - compressedSize)
  147. atomic.AddUint64(&t.savedBytes, saved)
  148. atomic.AddUint64(&t.compressedPackets, 1)
  149. } else {
  150. atomic.AddUint64(&t.rawPackets, 1)
  151. }
  152. } else {
  153. atomic.AddUint64(&t.rawPackets, 1)
  154. }
  155. // 2. Build 12-byte Authenticated Header:
  156. // [Magic (2B)][Version (1B)][Flags (1B)][Counter (8B)]
  157. counter := atomic.AddUint64(&t.txCounter, 1)
  158. binary.BigEndian.PutUint16(udpPacket[0:2], MagicHeader)
  159. udpPacket[2] = ProtocolVersion
  160. udpPacket[3] = flags
  161. binary.BigEndian.PutUint64(udpPacket[4:12], counter)
  162. // 3. Construct 12-byte Nonce
  163. var nonce [12]byte
  164. copy(nonce[0:4], udpPacket[0:4])
  165. binary.BigEndian.PutUint64(nonce[4:12], counter)
  166. // 4. AEAD Seal: authenticate header [0:12] as AAD, encrypt payload
  167. encrypted := t.aead.Seal(udpPacket[:12], nonce[:], payload, udpPacket[:12])
  168. // 5. Transmit UDP packet
  169. t.mu.RLock()
  170. target := t.remoteAddr
  171. t.mu.RUnlock()
  172. if target != nil {
  173. _, _ = t.udpConn.WriteToUDP(encrypted, target)
  174. atomic.AddUint64(&t.wireBytesOut, uint64(len(encrypted)))
  175. }
  176. }
  177. }
  178. // rxLoop: Reads UDP datagrams, verifies AEAD & anti-replay, decompresses LZ4, writes raw IP to TUN
  179. func (t *TunnelRunner) rxLoop() {
  180. udpBuf := make([]byte, 2048)
  181. decompBuf := make([]byte, 2048)
  182. for {
  183. select {
  184. case <-t.stopChan:
  185. return
  186. default:
  187. }
  188. n, srcAddr, err := t.udpConn.ReadFromUDP(udpBuf)
  189. if err != nil {
  190. select {
  191. case <-t.stopChan:
  192. return
  193. default:
  194. time.Sleep(10 * time.Millisecond)
  195. continue
  196. }
  197. }
  198. if n < 28 { // 12-byte header + 16-byte Poly1305 tag minimum
  199. continue
  200. }
  201. packet := udpBuf[:n]
  202. atomic.AddUint64(&t.wireBytesIn, uint64(n))
  203. // 1. Verify Magic Header & Protocol Version
  204. magic := binary.BigEndian.Uint16(packet[0:2])
  205. if magic != MagicHeader || packet[2] != ProtocolVersion {
  206. continue
  207. }
  208. flags := packet[3]
  209. counter := binary.BigEndian.Uint64(packet[4:12])
  210. // 2. Anti-Replay Sliding Window Check
  211. if !t.checkAndUpdateReplay(counter) {
  212. continue // Drop replayed packet
  213. }
  214. // 3. Construct 12-byte Nonce & Verify AEAD
  215. var nonce [12]byte
  216. copy(nonce[0:4], packet[0:4])
  217. binary.BigEndian.PutUint64(nonce[4:12], counter)
  218. decrypted, err := t.aead.Open(nil, nonce[:], packet[12:], packet[:12])
  219. if err != nil {
  220. continue // Authentication failed
  221. }
  222. // 4. Dynamic Roaming: Learn remote peer address in server mode
  223. t.mu.Lock()
  224. if t.remoteAddr == nil || (t.cfg.Mode == "server" && !udpAddrsEqual(t.remoteAddr, srcAddr)) {
  225. t.remoteAddr = srcAddr
  226. t.lastHandshake = time.Now()
  227. }
  228. t.lastHandshake = time.Now()
  229. t.mu.Unlock()
  230. // 5. Handle Keepalive
  231. if flags&FlagKeepalive != 0 {
  232. continue
  233. }
  234. // 6. LZ4 Decompress if flagged
  235. var ipPayload []byte
  236. if flags&FlagCompressedLZ4 != 0 {
  237. uncompressedSize, err := lz4.UncompressBlock(decrypted, decompBuf)
  238. if err != nil || uncompressedSize == 0 {
  239. continue
  240. }
  241. ipPayload = decompBuf[:uncompressedSize]
  242. atomic.AddUint64(&t.rawBytesIn, uint64(uncompressedSize))
  243. } else {
  244. ipPayload = decrypted
  245. atomic.AddUint64(&t.rawBytesIn, uint64(len(decrypted)))
  246. }
  247. // 7. Inject IP packet into TUN device
  248. _, _ = t.dev.Write(ipPayload)
  249. atomic.AddUint64(&t.rxBytes, uint64(len(ipPayload)))
  250. atomic.AddUint64(&t.packetsRx, 1)
  251. }
  252. }
  253. func (t *TunnelRunner) keepaliveLoop() {
  254. ticker := time.NewTicker(20 * time.Second)
  255. defer ticker.Stop()
  256. keepaliveBuf := make([]byte, 32)
  257. for {
  258. select {
  259. case <-t.stopChan:
  260. return
  261. case <-ticker.C:
  262. t.mu.RLock()
  263. target := t.remoteAddr
  264. t.mu.RUnlock()
  265. if target == nil {
  266. continue
  267. }
  268. counter := atomic.AddUint64(&t.txCounter, 1)
  269. binary.BigEndian.PutUint16(keepaliveBuf[0:2], MagicHeader)
  270. keepaliveBuf[2] = ProtocolVersion
  271. keepaliveBuf[3] = FlagKeepalive
  272. binary.BigEndian.PutUint64(keepaliveBuf[4:12], counter)
  273. var nonce [12]byte
  274. copy(nonce[0:4], keepaliveBuf[0:4])
  275. binary.BigEndian.PutUint64(nonce[4:12], counter)
  276. encrypted := t.aead.Seal(keepaliveBuf[:12], nonce[:], nil, keepaliveBuf[:12])
  277. _, _ = t.udpConn.WriteToUDP(encrypted, target)
  278. }
  279. }
  280. }
  281. // 64-packet Sliding Window Anti-Replay algorithm
  282. func (t *TunnelRunner) checkAndUpdateReplay(counter uint64) bool {
  283. t.mu.Lock()
  284. defer t.mu.Unlock()
  285. if counter == 0 {
  286. return false
  287. }
  288. if counter > t.rxMaxCounter {
  289. diff := counter - t.rxMaxCounter
  290. if diff >= 64 {
  291. t.replayWindow = 1
  292. } else {
  293. t.replayWindow = (t.replayWindow << diff) | 1
  294. }
  295. t.rxMaxCounter = counter
  296. return true
  297. }
  298. diff := t.rxMaxCounter - counter
  299. if diff >= 64 {
  300. return false // Packet is too old
  301. }
  302. mask := uint64(1) << diff
  303. if (t.replayWindow & mask) != 0 {
  304. return false // Duplicate packet!
  305. }
  306. t.replayWindow |= mask
  307. return true
  308. }
  309. func (t *TunnelRunner) GetTelemetry() TunnelInstance {
  310. t.mu.RLock()
  311. peerStr := ""
  312. if t.remoteAddr != nil {
  313. peerStr = t.remoteAddr.String()
  314. }
  315. lastHandshakeStr := "Never"
  316. if !t.lastHandshake.IsZero() {
  317. lastHandshakeStr = time.Since(t.lastHandshake).Truncate(time.Second).String() + " ago"
  318. }
  319. t.mu.RUnlock()
  320. inst := t.cfg
  321. inst.Running = !t.closed
  322. inst.ActivePeerEndpoint = peerStr
  323. inst.LastHandshake = lastHandshakeStr
  324. inst.RxBytes = atomic.LoadUint64(&t.rxBytes)
  325. inst.TxBytes = atomic.LoadUint64(&t.txBytes)
  326. inst.RawBytesIn = atomic.LoadUint64(&t.rawBytesIn)
  327. inst.RawBytesOut = atomic.LoadUint64(&t.rawBytesOut)
  328. inst.WireBytesIn = atomic.LoadUint64(&t.wireBytesIn)
  329. inst.WireBytesOut = atomic.LoadUint64(&t.wireBytesOut)
  330. inst.SavedBytes = atomic.LoadUint64(&t.savedBytes)
  331. inst.PacketsRx = atomic.LoadUint64(&t.packetsRx)
  332. inst.PacketsTx = atomic.LoadUint64(&t.packetsTx)
  333. inst.CompressedPackets = atomic.LoadUint64(&t.compressedPackets)
  334. inst.RawPackets = atomic.LoadUint64(&t.rawPackets)
  335. // Calculate compression ratio
  336. if inst.RawBytesOut > 0 && inst.WireBytesOut > 0 {
  337. ratio := float64(inst.RawBytesOut) / float64(inst.WireBytesOut)
  338. if ratio > 1.0 {
  339. savedPct := (1.0 - (float64(inst.WireBytesOut) / float64(inst.RawBytesOut))) * 100.0
  340. inst.CompressionRatio = fmt.Sprintf("%.2fx (%.1f%% saved)", ratio, savedPct)
  341. } else {
  342. inst.CompressionRatio = "1.00x (0% saved)"
  343. }
  344. } else {
  345. inst.CompressionRatio = "1.00x (0% saved)"
  346. }
  347. return inst
  348. }
  349. func GenerateSecureKey() (string, error) {
  350. key := make([]byte, 32)
  351. if _, err := rand.Read(key); err != nil {
  352. return "", err
  353. }
  354. return fmt.Sprintf("%x", key), nil
  355. }
  356. func udpAddrsEqual(a, b *net.UDPAddr) bool {
  357. if a == nil || b == nil {
  358. return a == b
  359. }
  360. return a.IP.Equal(b.IP) && a.Port == b.Port
  361. }
  362. func stringsEqual(a, b string) bool {
  363. return len(a) == len(b) && (a == b || (len(a) > 0 && (a[0]|0x20) == (b[0]|0x20)))
  364. }