eoip.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. package vpn
  2. import (
  3. "crypto/rand"
  4. "encoding/binary"
  5. "encoding/json"
  6. "fmt"
  7. "log"
  8. "net"
  9. "os"
  10. "os/exec"
  11. "path/filepath"
  12. "strconv"
  13. "sync"
  14. "sync/atomic"
  15. "syscall"
  16. "time"
  17. )
  18. // EoIPInstance represents a MikroTik Ethernet-over-IP tunnel instance
  19. type EoIPInstance struct {
  20. ID string `json:"id"`
  21. Name string `json:"name"` // e.g. "Goldix" or "eoip-tunnel1"
  22. TunnelID int `json:"tunnel_id"` // 1 - 65535
  23. RemoteAddress string `json:"remote_address"` // Remote MikroTik IPv4
  24. LocalAddress string `json:"local_address"` // Local router IPv4 (optional)
  25. MACAddress string `json:"mac_address"` // e.g. "02:64:83:6b:46:44"
  26. MTU int `json:"mtu"` // default 1500
  27. ClampMSS bool `json:"clamp_mss"` // Clamp TCP MSS for PMTUD
  28. Comment string `json:"comment,omitempty"`
  29. Disabled bool `json:"disabled"`
  30. Running bool `json:"running"` // MikroTik 'R' flag
  31. RxPackets uint64 `json:"rx_packets"`
  32. TxPackets uint64 `json:"tx_packets"`
  33. RxBytes uint64 `json:"rx_bytes"`
  34. TxBytes uint64 `json:"tx_bytes"`
  35. RxHuman string `json:"rx_human,omitempty"`
  36. TxHuman string `json:"tx_human,omitempty"`
  37. LastError string `json:"last_error,omitempty"`
  38. }
  39. type EoIPManager struct {
  40. mu sync.RWMutex
  41. storePath string
  42. tunnels map[string]*EoIPInstance
  43. workers map[string]*eoipTunnelWorker
  44. }
  45. func NewEoIPManager() *EoIPManager {
  46. mgr := &EoIPManager{
  47. storePath: "/etc/proxmox-router/vpn/eoip.json",
  48. tunnels: make(map[string]*EoIPInstance),
  49. workers: make(map[string]*eoipTunnelWorker),
  50. }
  51. mgr.load()
  52. mgr.syncAll()
  53. return mgr
  54. }
  55. func (m *EoIPManager) load() {
  56. if data, err := os.ReadFile(m.storePath); err == nil {
  57. var list []*EoIPInstance
  58. if err := json.Unmarshal(data, &list); err == nil {
  59. for _, t := range list {
  60. m.tunnels[t.ID] = t
  61. }
  62. }
  63. }
  64. }
  65. func (m *EoIPManager) save() error {
  66. _ = os.MkdirAll(filepath.Dir(m.storePath), 0755)
  67. var list []*EoIPInstance
  68. for _, t := range m.tunnels {
  69. list = append(list, t)
  70. }
  71. data, err := json.MarshalIndent(list, "", " ")
  72. if err != nil {
  73. return err
  74. }
  75. return os.WriteFile(m.storePath, data, 0644)
  76. }
  77. func (m *EoIPManager) List() []*EoIPInstance {
  78. m.mu.Lock()
  79. defer m.mu.Unlock()
  80. list := make([]*EoIPInstance, 0, len(m.tunnels))
  81. for _, t := range m.tunnels {
  82. m.updateStats(t)
  83. list = append(list, t)
  84. }
  85. return list
  86. }
  87. func (m *EoIPManager) Get(id string) (*EoIPInstance, bool) {
  88. m.mu.Lock()
  89. defer m.mu.Unlock()
  90. t, ok := m.tunnels[id]
  91. if ok {
  92. m.updateStats(t)
  93. }
  94. return t, ok
  95. }
  96. func (m *EoIPManager) Save(inst *EoIPInstance) error {
  97. m.mu.Lock()
  98. defer m.mu.Unlock()
  99. if inst.Name == "" {
  100. inst.Name = fmt.Sprintf("eoip-tunnel%d", inst.TunnelID)
  101. }
  102. if inst.TunnelID <= 0 || inst.TunnelID > 65535 {
  103. return fmt.Errorf("tunnel_id must be between 1 and 65535")
  104. }
  105. if inst.RemoteAddress == "" {
  106. return fmt.Errorf("remote_address is required")
  107. }
  108. if inst.MTU <= 0 {
  109. inst.MTU = 1500
  110. }
  111. if inst.MACAddress == "" {
  112. inst.MACAddress = generateMAC()
  113. }
  114. if inst.ID == "" {
  115. inst.ID = fmt.Sprintf("eoip-%d", time.Now().UnixNano())
  116. }
  117. // Stop previous worker if exists
  118. if w, exists := m.workers[inst.ID]; exists {
  119. w.stop()
  120. delete(m.workers, inst.ID)
  121. }
  122. m.tunnels[inst.ID] = inst
  123. _ = m.save()
  124. if !inst.Disabled {
  125. worker, err := newEoIPTunnelWorker(inst)
  126. if err != nil {
  127. inst.LastError = err.Error()
  128. inst.Running = false
  129. return err
  130. }
  131. m.workers[inst.ID] = worker
  132. go worker.start()
  133. } else {
  134. inst.Running = false
  135. }
  136. return nil
  137. }
  138. func (m *EoIPManager) SetState(id string, disabled bool) error {
  139. m.mu.Lock()
  140. defer m.mu.Unlock()
  141. t, ok := m.tunnels[id]
  142. if !ok {
  143. return fmt.Errorf("tunnel not found")
  144. }
  145. t.Disabled = disabled
  146. _ = m.save()
  147. if w, exists := m.workers[id]; exists {
  148. w.stop()
  149. delete(m.workers, id)
  150. }
  151. if disabled {
  152. t.Running = false
  153. return nil
  154. }
  155. worker, err := newEoIPTunnelWorker(t)
  156. if err != nil {
  157. t.LastError = err.Error()
  158. t.Running = false
  159. return err
  160. }
  161. m.workers[id] = worker
  162. go worker.start()
  163. return nil
  164. }
  165. func (m *EoIPManager) Delete(id string) error {
  166. m.mu.Lock()
  167. defer m.mu.Unlock()
  168. if _, ok := m.tunnels[id]; !ok {
  169. return fmt.Errorf("tunnel not found")
  170. }
  171. if w, exists := m.workers[id]; exists {
  172. w.stop()
  173. delete(m.workers, id)
  174. }
  175. delete(m.tunnels, id)
  176. return m.save()
  177. }
  178. func (m *EoIPManager) syncAll() {
  179. m.mu.Lock()
  180. defer m.mu.Unlock()
  181. for id, t := range m.tunnels {
  182. if !t.Disabled {
  183. if w, exists := m.workers[id]; exists {
  184. w.stop()
  185. delete(m.workers, id)
  186. }
  187. worker, err := newEoIPTunnelWorker(t)
  188. if err != nil {
  189. t.LastError = err.Error()
  190. t.Running = false
  191. continue
  192. }
  193. m.workers[id] = worker
  194. go worker.start()
  195. } else {
  196. t.Running = false
  197. }
  198. }
  199. }
  200. func (m *EoIPManager) updateStats(t *EoIPInstance) {
  201. if w, exists := m.workers[t.ID]; exists {
  202. t.Running = w.isRunning()
  203. t.RxBytes = atomic.LoadUint64(&w.rxBytes)
  204. t.RxPackets = atomic.LoadUint64(&w.rxPackets)
  205. t.TxBytes = atomic.LoadUint64(&w.txBytes)
  206. t.TxPackets = atomic.LoadUint64(&w.txPackets)
  207. } else {
  208. t.Running = false
  209. }
  210. t.RxHuman = formatBytes(t.RxBytes)
  211. t.TxHuman = formatBytes(t.TxBytes)
  212. }
  213. // -------------------------------------------------------------
  214. // Native MikroTik EoIP Engine Worker
  215. // -------------------------------------------------------------
  216. type eoipTunnelWorker struct {
  217. inst *EoIPInstance
  218. peerIfName string
  219. peerIfIndex int
  220. greFd int
  221. pktFd int
  222. stopChan chan struct{}
  223. lastRx int64 // unix nano
  224. rxBytes uint64
  225. rxPackets uint64
  226. txBytes uint64
  227. txPackets uint64
  228. remoteAddrInet syscall.SockaddrInet4
  229. }
  230. func newEoIPTunnelWorker(inst *EoIPInstance) (*eoipTunnelWorker, error) {
  231. // Parse Remote IPv4
  232. remoteIP := net.ParseIP(inst.RemoteAddress).To4()
  233. if remoteIP == nil {
  234. return nil, fmt.Errorf("invalid remote address: %s", inst.RemoteAddress)
  235. }
  236. // Internal peer interface name (max 15 chars for Linux ifnames)
  237. peerName := fmt.Sprintf("ep_%s", inst.Name)
  238. if len(peerName) > 15 {
  239. peerName = peerName[:15]
  240. }
  241. // 1. Clean up existing links if present
  242. _ = exec.Command("ip", "link", "del", inst.Name).Run()
  243. _ = exec.Command("ip", "link", "del", peerName).Run()
  244. // 2. Create veth pair: inst.Name <--> peerName
  245. cmdAdd := exec.Command("ip", "link", "add", inst.Name, "type", "veth", "peer", "name", peerName)
  246. if out, err := cmdAdd.CombinedOutput(); err != nil {
  247. return nil, fmt.Errorf("failed to create veth pair: %s - %w", string(out), err)
  248. }
  249. // 3. Set MAC address and MTU
  250. if inst.MACAddress != "" {
  251. _ = exec.Command("ip", "link", "set", inst.Name, "address", inst.MACAddress).Run()
  252. }
  253. mtuStr := strconv.Itoa(inst.MTU)
  254. _ = exec.Command("ip", "link", "set", inst.Name, "mtu", mtuStr).Run()
  255. _ = exec.Command("ip", "link", "set", peerName, "mtu", mtuStr).Run()
  256. // 4. Bring both interfaces UP
  257. _ = exec.Command("ip", "link", "set", inst.Name, "up").Run()
  258. _ = exec.Command("ip", "link", "set", peerName, "up").Run()
  259. // 5. TCP MSS Clamping
  260. if inst.ClampMSS {
  261. _ = exec.Command("iptables", "-t", "mangle", "-A", "FORWARD", "-o", inst.Name, "-p", "tcp", "--tcp-flags", "SYN,RST", "SYN", "-j", "TCPMSS", "--clamp-mss-to-pmtu").Run()
  262. }
  263. // 6. Find interface index of peerName
  264. peerIf, err := net.InterfaceByName(peerName)
  265. if err != nil {
  266. _ = exec.Command("ip", "link", "del", inst.Name).Run()
  267. return nil, fmt.Errorf("cannot find peer interface %s: %w", peerName, err)
  268. }
  269. // 7. Open AF_PACKET raw socket on peer interface
  270. pktFd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, int(htons(syscall.ETH_P_ALL)))
  271. if err != nil {
  272. _ = exec.Command("ip", "link", "del", inst.Name).Run()
  273. return nil, fmt.Errorf("cannot create AF_PACKET socket: %w", err)
  274. }
  275. pktAddr := syscall.SockaddrLinklayer{
  276. Protocol: htons(syscall.ETH_P_ALL),
  277. Ifindex: peerIf.Index,
  278. }
  279. if err := syscall.Bind(pktFd, &pktAddr); err != nil {
  280. syscall.Close(pktFd)
  281. _ = exec.Command("ip", "link", "del", inst.Name).Run()
  282. return nil, fmt.Errorf("cannot bind AF_PACKET socket to %s: %w", peerName, err)
  283. }
  284. // 8. Open raw GRE socket (IPPROTO_GRE = 47)
  285. greFd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_GRE)
  286. if err != nil {
  287. syscall.Close(pktFd)
  288. _ = exec.Command("ip", "link", "del", inst.Name).Run()
  289. return nil, fmt.Errorf("cannot create raw GRE socket: %w", err)
  290. }
  291. // Bind GRE socket to LocalAddress if specified
  292. if inst.LocalAddress != "" {
  293. localIP := net.ParseIP(inst.LocalAddress).To4()
  294. if localIP != nil {
  295. localAddr := &syscall.SockaddrInet4{
  296. Addr: [4]byte{localIP[0], localIP[1], localIP[2], localIP[3]},
  297. }
  298. _ = syscall.Bind(greFd, localAddr)
  299. }
  300. }
  301. w := &eoipTunnelWorker{
  302. inst: inst,
  303. peerIfName: peerName,
  304. peerIfIndex: peerIf.Index,
  305. greFd: greFd,
  306. pktFd: pktFd,
  307. stopChan: make(chan struct{}),
  308. remoteAddrInet: syscall.SockaddrInet4{
  309. Addr: [4]byte{remoteIP[0], remoteIP[1], remoteIP[2], remoteIP[3]},
  310. },
  311. rxBytes: inst.RxBytes,
  312. rxPackets: inst.RxPackets,
  313. txBytes: inst.TxBytes,
  314. txPackets: inst.TxPackets,
  315. }
  316. return w, nil
  317. }
  318. func (w *eoipTunnelWorker) start() {
  319. log.Printf("[EoIP] Started tunnel '%s' (ID: %d, Remote: %s)", w.inst.Name, w.inst.TunnelID, w.inst.RemoteAddress)
  320. // Goroutine 1: Forward packets from Local veth -> Remote MikroTik via GRE
  321. go w.txLoop()
  322. // Goroutine 2: Receive packets from Remote MikroTik via GRE -> Inject to Local veth
  323. go w.rxLoop()
  324. // Goroutine 3: Periodic MikroTik EoIP Keepalive
  325. go w.keepaliveLoop()
  326. }
  327. func (w *eoipTunnelWorker) isRunning() bool {
  328. last := atomic.LoadInt64(&w.lastRx)
  329. if last == 0 {
  330. return false
  331. }
  332. return time.Since(time.Unix(0, last)) < 15*time.Second
  333. }
  334. func (w *eoipTunnelWorker) sendKeepalive() {
  335. // MikroTik EoIP Keepalive packet:
  336. // GRE Flags: 0x2001
  337. // Protocol: 0x6400
  338. // Length: 0x0000
  339. // Tunnel ID: Little-Endian
  340. tid := uint16(w.inst.TunnelID)
  341. pkt := []byte{
  342. 0x20, 0x01,
  343. 0x64, 0x00,
  344. 0x00, 0x00,
  345. byte(tid & 0xff), byte(tid >> 8),
  346. }
  347. _ = syscall.Sendto(w.greFd, pkt, 0, &w.remoteAddrInet)
  348. }
  349. func (w *eoipTunnelWorker) keepaliveLoop() {
  350. ticker := time.NewTicker(5 * time.Second)
  351. defer ticker.Stop()
  352. // Send immediate initial keepalive
  353. w.sendKeepalive()
  354. for {
  355. select {
  356. case <-w.stopChan:
  357. return
  358. case <-ticker.C:
  359. w.sendKeepalive()
  360. }
  361. }
  362. }
  363. func (w *eoipTunnelWorker) txLoop() {
  364. buf := make([]byte, 2048)
  365. tid := uint16(w.inst.TunnelID)
  366. for {
  367. select {
  368. case <-w.stopChan:
  369. return
  370. default:
  371. }
  372. n, from, err := syscall.Recvfrom(w.pktFd, buf, 0)
  373. if err != nil {
  374. select {
  375. case <-w.stopChan:
  376. return
  377. default:
  378. time.Sleep(10 * time.Millisecond)
  379. continue
  380. }
  381. }
  382. // Filter out outgoing packets to prevent reflection
  383. if fromLink, ok := from.(*syscall.SockaddrLinklayer); ok {
  384. if fromLink.Pkttype == syscall.PACKET_OUTGOING {
  385. continue
  386. }
  387. }
  388. // Must have at least minimum Ethernet header length (14 bytes)
  389. if n < 14 {
  390. continue
  391. }
  392. // Construct MikroTik EoIP Header (8 bytes) + Ethernet Frame
  393. // [0..1] = 0x2001 (Flags)
  394. // [2..3] = 0x6400 (Protocol)
  395. // [4..5] = payload length (Big-Endian)
  396. // [6..7] = tunnel ID (Little-Endian)
  397. grePkt := make([]byte, 8+n)
  398. grePkt[0] = 0x20
  399. grePkt[1] = 0x01
  400. grePkt[2] = 0x64
  401. grePkt[3] = 0x00
  402. binary.BigEndian.PutUint16(grePkt[4:6], uint16(n))
  403. binary.LittleEndian.PutUint16(grePkt[6:8], tid)
  404. copy(grePkt[8:], buf[:n])
  405. // Send over raw GRE socket
  406. if err := syscall.Sendto(w.greFd, grePkt, 0, &w.remoteAddrInet); err == nil {
  407. atomic.AddUint64(&w.txPackets, 1)
  408. atomic.AddUint64(&w.txBytes, uint64(n))
  409. }
  410. }
  411. }
  412. func (w *eoipTunnelWorker) rxLoop() {
  413. buf := make([]byte, 4096)
  414. expectedTid := uint16(w.inst.TunnelID)
  415. remoteIP := net.ParseIP(w.inst.RemoteAddress).To4()
  416. injectAddr := syscall.SockaddrLinklayer{
  417. Protocol: htons(syscall.ETH_P_ALL),
  418. Ifindex: w.peerIfIndex,
  419. Halen: 6,
  420. }
  421. for {
  422. select {
  423. case <-w.stopChan:
  424. return
  425. default:
  426. }
  427. n, from, err := syscall.Recvfrom(w.greFd, buf, 0)
  428. if err != nil {
  429. select {
  430. case <-w.stopChan:
  431. return
  432. default:
  433. time.Sleep(10 * time.Millisecond)
  434. continue
  435. }
  436. }
  437. // RAW IPv4 socket Recvfrom returns [IPv4 Header (20+ bytes)] + [GRE Header (8 bytes)] + [Payload]
  438. if n < 28 {
  439. continue
  440. }
  441. // Verify remote sender IP
  442. if fromIP, ok := from.(*syscall.SockaddrInet4); ok {
  443. if fromIP.Addr[0] != remoteIP[0] || fromIP.Addr[1] != remoteIP[1] ||
  444. fromIP.Addr[2] != remoteIP[2] || fromIP.Addr[3] != remoteIP[3] {
  445. continue
  446. }
  447. }
  448. // Calculate IPv4 Header Length
  449. ipHdrLen := int(buf[0]&0x0f) * 4
  450. if n < ipHdrLen+8 {
  451. continue
  452. }
  453. gre := buf[ipHdrLen:n]
  454. // Verify MikroTik EoIP GRE Header
  455. // Flags: 0x2001 (or 0x2000)
  456. // Proto: 0x6400
  457. if gre[0] != 0x20 || (gre[1] != 0x01 && gre[1] != 0x00) {
  458. continue
  459. }
  460. if gre[2] != 0x64 || gre[3] != 0x00 {
  461. continue
  462. }
  463. frameLen := binary.BigEndian.Uint16(gre[4:6])
  464. tunnelID := binary.LittleEndian.Uint16(gre[6:8])
  465. if tunnelID != expectedTid {
  466. continue
  467. }
  468. // Record successful packet reception
  469. now := time.Now().UnixNano()
  470. atomic.StoreInt64(&w.lastRx, now)
  471. if frameLen == 0 {
  472. // Keepalive probe from MikroTik: reply with keepalive
  473. w.sendKeepalive()
  474. continue
  475. }
  476. // Valid Ethernet frame received from MikroTik EoIP tunnel
  477. ethFrame := gre[8:]
  478. if len(ethFrame) < int(frameLen) {
  479. continue
  480. }
  481. payload := ethFrame[:frameLen]
  482. // Inject Ethernet frame into veth peer -> kernel delivers it to user interface (w.inst.Name)
  483. if err := syscall.Sendto(w.pktFd, payload, 0, &injectAddr); err == nil {
  484. atomic.AddUint64(&w.rxPackets, 1)
  485. atomic.AddUint64(&w.rxBytes, uint64(frameLen))
  486. }
  487. }
  488. }
  489. func (w *eoipTunnelWorker) stop() {
  490. close(w.stopChan)
  491. _ = syscall.Close(w.pktFd)
  492. _ = syscall.Close(w.greFd)
  493. // Clean up interfaces & iptables
  494. if w.inst.ClampMSS {
  495. _ = exec.Command("iptables", "-t", "mangle", "-D", "FORWARD", "-o", w.inst.Name, "-p", "tcp", "--tcp-flags", "SYN,RST", "SYN", "-j", "TCPMSS", "--clamp-mss-to-pmtu").Run()
  496. }
  497. _ = exec.Command("ip", "link", "del", w.inst.Name).Run()
  498. log.Printf("[EoIP] Stopped tunnel '%s'", w.inst.Name)
  499. }
  500. func htons(n uint16) uint16 {
  501. return (n<<8)&0xff00 | (n>>8)&0x00ff
  502. }
  503. func generateMAC() string {
  504. buf := make([]byte, 3)
  505. _, _ = rand.Read(buf)
  506. return fmt.Sprintf("02:64:%02x:%02x:%02x:%02x", buf[0], buf[1], buf[2], time.Now().UnixNano()%256)
  507. }
  508. func formatBytes(b uint64) string {
  509. const unit = 1024
  510. if b < unit {
  511. return fmt.Sprintf("%d B", b)
  512. }
  513. div, exp := uint64(unit), 0
  514. for n := b / unit; n >= unit; n /= unit {
  515. div *= unit
  516. exp++
  517. }
  518. return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "KMGTPE"[exp])
  519. }