vxlan.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. package vpn
  2. import (
  3. "crypto/rand"
  4. "encoding/json"
  5. "fmt"
  6. "net"
  7. "os"
  8. "os/exec"
  9. "path/filepath"
  10. "sort"
  11. "strconv"
  12. "strings"
  13. "sync"
  14. "time"
  15. )
  16. const DefaultVXLANStorePath = "/etc/debtik/vpn/vxlan.json"
  17. // VXLANVtep represents a remote VTEP (Virtual Tunnel Endpoint) for a VXLAN interface.
  18. type VXLANVtep struct {
  19. ID string `json:"id"`
  20. Interface string `json:"interface"` // Name of the parent VXLAN interface
  21. RemoteIP string `json:"remote_ip"` // Unicast IPv4 address of remote VTEP
  22. Port int `json:"port,omitempty"` // UDP port, defaults to parent VXLAN port
  23. Comment string `json:"comment,omitempty"`
  24. }
  25. // VXLANInstance represents a Linux/RouterOS VXLAN network interface.
  26. type VXLANInstance struct {
  27. ID string `json:"id"`
  28. Name string `json:"name"` // Interface name (e.g. "vxlan1", "vxlan100")
  29. VNI int `json:"vni"` // VXLAN Network Identifier (1 - 16777215)
  30. Port int `json:"port"` // UDP Destination Port (RFC 7348 standard: 4789)
  31. MTU int `json:"mtu"` // Interface MTU (default 1500)
  32. MACAddress string `json:"mac_address,omitempty"` // Hardware MAC address
  33. IPAddress string `json:"ip_address,omitempty"` // Overlay IP/CIDR (e.g. "10.200.0.1/24")
  34. ParentInterface string `json:"parent_interface,omitempty"` // Underlay interface (e.g. "eth0")
  35. LocalAddress string `json:"local_address,omitempty"` // VTEP local source IP
  36. Group string `json:"group,omitempty"` // Multicast group (e.g. "239.1.1.1")
  37. RemoteAddress string `json:"remote_address,omitempty"` // Point-to-point remote unicast IP
  38. Learning bool `json:"learning"` // MAC address learning (default: true)
  39. Comment string `json:"comment,omitempty"`
  40. Disabled bool `json:"disabled"`
  41. Running bool `json:"running"` // True if link is created and active in kernel
  42. VTEPs []*VXLANVtep `json:"vteps,omitempty"`
  43. RxPackets uint64 `json:"rx_packets"`
  44. TxPackets uint64 `json:"tx_packets"`
  45. RxBytes uint64 `json:"rx_bytes"`
  46. TxBytes uint64 `json:"tx_bytes"`
  47. RxHuman string `json:"rx_human,omitempty"`
  48. TxHuman string `json:"tx_human,omitempty"`
  49. LastError string `json:"last_error,omitempty"`
  50. }
  51. // VXLANManager manages persistent VXLAN interfaces and kernel links.
  52. type VXLANManager struct {
  53. mu sync.RWMutex
  54. storePath string
  55. tunnels map[string]*VXLANInstance
  56. }
  57. // NewVXLANManager creates a new manager with default or customized storage path.
  58. func NewVXLANManager(customPath ...string) *VXLANManager {
  59. store := DefaultVXLANStorePath
  60. if len(customPath) > 0 && customPath[0] != "" {
  61. store = customPath[0]
  62. }
  63. mgr := &VXLANManager{
  64. storePath: store,
  65. tunnels: make(map[string]*VXLANInstance),
  66. }
  67. mgr.load()
  68. mgr.SyncAll()
  69. return mgr
  70. }
  71. func (m *VXLANManager) load() {
  72. m.mu.Lock()
  73. defer m.mu.Unlock()
  74. data, err := os.ReadFile(m.storePath)
  75. if err != nil {
  76. return
  77. }
  78. var list []*VXLANInstance
  79. if err := json.Unmarshal(data, &list); err == nil {
  80. for _, t := range list {
  81. m.tunnels[t.ID] = t
  82. }
  83. }
  84. }
  85. func (m *VXLANManager) save() error {
  86. _ = os.MkdirAll(filepath.Dir(m.storePath), 0755)
  87. list := make([]*VXLANInstance, 0, len(m.tunnels))
  88. for _, t := range m.tunnels {
  89. list = append(list, t)
  90. }
  91. data, err := json.MarshalIndent(list, "", " ")
  92. if err != nil {
  93. return err
  94. }
  95. return os.WriteFile(m.storePath, data, 0644)
  96. }
  97. // List returns all configured VXLAN tunnels with updated statistics.
  98. func (m *VXLANManager) List() []*VXLANInstance {
  99. m.mu.Lock()
  100. defer m.mu.Unlock()
  101. list := make([]*VXLANInstance, 0, len(m.tunnels))
  102. for _, t := range m.tunnels {
  103. m.updateStats(t)
  104. list = append(list, t)
  105. }
  106. sort.Slice(list, func(i, j int) bool {
  107. if list[i].VNI != list[j].VNI {
  108. return list[i].VNI < list[j].VNI
  109. }
  110. return list[i].Name < list[j].Name
  111. })
  112. return list
  113. }
  114. // Get returns a single VXLAN instance by ID or interface name.
  115. func (m *VXLANManager) Get(idOrName string) (*VXLANInstance, bool) {
  116. m.mu.Lock()
  117. defer m.mu.Unlock()
  118. if t, ok := m.tunnels[idOrName]; ok {
  119. m.updateStats(t)
  120. return t, true
  121. }
  122. for _, t := range m.tunnels {
  123. if t.Name == idOrName {
  124. m.updateStats(t)
  125. return t, true
  126. }
  127. }
  128. return nil, false
  129. }
  130. // Save validates, stores, and synchronizes a VXLAN tunnel configuration into the kernel.
  131. func (m *VXLANManager) Save(inst *VXLANInstance) error {
  132. m.mu.Lock()
  133. defer m.mu.Unlock()
  134. if inst.VNI <= 0 || inst.VNI > 16777215 {
  135. return fmt.Errorf("vni must be between 1 and 16777215")
  136. }
  137. if inst.Name == "" {
  138. inst.Name = fmt.Sprintf("vxlan%d", inst.VNI)
  139. }
  140. if inst.Port <= 0 {
  141. inst.Port = 4789 // RFC 7348 standard
  142. }
  143. if inst.MTU <= 0 {
  144. inst.MTU = 1500
  145. }
  146. if inst.ID == "" {
  147. inst.ID = fmt.Sprintf("vxlan-%d", time.Now().UnixNano())
  148. }
  149. if inst.MACAddress == "" {
  150. inst.MACAddress = generateVXLANMAC()
  151. }
  152. // Check for name or VNI collisions with existing different instances
  153. for _, existing := range m.tunnels {
  154. if existing.ID != inst.ID {
  155. if existing.Name == inst.Name {
  156. return fmt.Errorf("interface name %q already in use", inst.Name)
  157. }
  158. if existing.VNI == inst.VNI && existing.Port == inst.Port {
  159. return fmt.Errorf("vni %d on port %d already exists", inst.VNI, inst.Port)
  160. }
  161. }
  162. }
  163. // Sync link in kernel
  164. if err := m.syncKernelLink(inst); err != nil {
  165. return err
  166. }
  167. m.tunnels[inst.ID] = inst
  168. return m.save()
  169. }
  170. // SetState enables or disables a VXLAN interface.
  171. func (m *VXLANManager) SetState(idOrName string, disabled bool) error {
  172. m.mu.Lock()
  173. defer m.mu.Unlock()
  174. inst := m.findLocked(idOrName)
  175. if inst == nil {
  176. return fmt.Errorf("vxlan interface %q not found", idOrName)
  177. }
  178. inst.Disabled = disabled
  179. if err := m.syncKernelLink(inst); err != nil {
  180. return err
  181. }
  182. return m.save()
  183. }
  184. // Delete removes a VXLAN interface from the kernel and persistent store.
  185. func (m *VXLANManager) Delete(idOrName string) error {
  186. m.mu.Lock()
  187. defer m.mu.Unlock()
  188. inst := m.findLocked(idOrName)
  189. if inst == nil {
  190. return fmt.Errorf("vxlan interface %q not found", idOrName)
  191. }
  192. // Delete kernel link
  193. _ = exec.Command("ip", "link", "del", "dev", inst.Name).Run()
  194. delete(m.tunnels, inst.ID)
  195. return m.save()
  196. }
  197. // AddVtep adds a remote VTEP peer to a VXLAN interface.
  198. func (m *VXLANManager) AddVtep(vxlanIDOrName string, vtep *VXLANVtep) error {
  199. m.mu.Lock()
  200. defer m.mu.Unlock()
  201. inst := m.findLocked(vxlanIDOrName)
  202. if inst == nil {
  203. return fmt.Errorf("vxlan interface %q not found", vxlanIDOrName)
  204. }
  205. if vtep.RemoteIP == "" {
  206. return fmt.Errorf("remote_ip is required")
  207. }
  208. if net.ParseIP(vtep.RemoteIP) == nil {
  209. return fmt.Errorf("invalid remote IP %q", vtep.RemoteIP)
  210. }
  211. if vtep.Port <= 0 {
  212. vtep.Port = inst.Port
  213. }
  214. if vtep.ID == "" {
  215. vtep.ID = fmt.Sprintf("vtep-%d", time.Now().UnixNano())
  216. }
  217. vtep.Interface = inst.Name
  218. // Check duplicate remote IP
  219. for _, existing := range inst.VTEPs {
  220. if existing.RemoteIP == vtep.RemoteIP {
  221. return fmt.Errorf("vtep with remote-ip %s already exists for %s", vtep.RemoteIP, inst.Name)
  222. }
  223. }
  224. inst.VTEPs = append(inst.VTEPs, vtep)
  225. // Apply bridge fdb flood entry in kernel
  226. if !inst.Disabled {
  227. m.applyVtepKernel(inst.Name, vtep)
  228. }
  229. return m.save()
  230. }
  231. // DeleteVtep removes a remote VTEP peer from a VXLAN interface.
  232. func (m *VXLANManager) DeleteVtep(vxlanIDOrName, vtepIDOrIP string) error {
  233. m.mu.Lock()
  234. defer m.mu.Unlock()
  235. inst := m.findLocked(vxlanIDOrName)
  236. if inst == nil {
  237. return fmt.Errorf("vxlan interface %q not found", vxlanIDOrName)
  238. }
  239. idx := -1
  240. var targetVtep *VXLANVtep
  241. for i, v := range inst.VTEPs {
  242. if v.ID == vtepIDOrIP || v.RemoteIP == vtepIDOrIP {
  243. idx = i
  244. targetVtep = v
  245. break
  246. }
  247. }
  248. if idx == -1 {
  249. return fmt.Errorf("vtep %q not found on interface %s", vtepIDOrIP, inst.Name)
  250. }
  251. if !inst.Disabled && targetVtep != nil {
  252. m.removeVtepKernel(inst.Name, targetVtep)
  253. }
  254. inst.VTEPs = append(inst.VTEPs[:idx], inst.VTEPs[idx+1:]...)
  255. return m.save()
  256. }
  257. // ListVteps returns a slice of all configured VTEPs across interfaces.
  258. func (m *VXLANManager) ListVteps(filterIface string) []*VXLANVtep {
  259. m.mu.RLock()
  260. defer m.mu.RUnlock()
  261. var list []*VXLANVtep
  262. for _, inst := range m.tunnels {
  263. if filterIface != "" && inst.Name != filterIface && inst.ID != filterIface {
  264. continue
  265. }
  266. for _, v := range inst.VTEPs {
  267. list = append(list, v)
  268. }
  269. }
  270. sort.Slice(list, func(i, j int) bool {
  271. if list[i].Interface != list[j].Interface {
  272. return list[i].Interface < list[j].Interface
  273. }
  274. return list[i].RemoteIP < list[j].RemoteIP
  275. })
  276. return list
  277. }
  278. // SyncAll reconciles all stored VXLAN interfaces into the kernel.
  279. func (m *VXLANManager) SyncAll() {
  280. m.mu.Lock()
  281. defer m.mu.Unlock()
  282. for _, inst := range m.tunnels {
  283. _ = m.syncKernelLink(inst)
  284. }
  285. }
  286. // RenameInterface updates parent underlay references when a link is renamed.
  287. func (m *VXLANManager) RenameInterface(oldName, newName string) {
  288. m.mu.Lock()
  289. defer m.mu.Unlock()
  290. changed := false
  291. for _, inst := range m.tunnels {
  292. if inst.ParentInterface == oldName {
  293. inst.ParentInterface = newName
  294. changed = true
  295. }
  296. }
  297. if changed {
  298. _ = m.save()
  299. }
  300. }
  301. func (m *VXLANManager) findLocked(idOrName string) *VXLANInstance {
  302. if t, ok := m.tunnels[idOrName]; ok {
  303. return t
  304. }
  305. for _, t := range m.tunnels {
  306. if t.Name == idOrName {
  307. return t
  308. }
  309. }
  310. return nil
  311. }
  312. func (m *VXLANManager) syncKernelLink(inst *VXLANInstance) error {
  313. // Remove existing link if present
  314. _ = exec.Command("ip", "link", "del", "dev", inst.Name).Run()
  315. if inst.Disabled {
  316. inst.Running = false
  317. return nil
  318. }
  319. args := []string{"link", "add", "dev", inst.Name, "type", "vxlan", "id", strconv.Itoa(inst.VNI)}
  320. dstPort := inst.Port
  321. if dstPort <= 0 {
  322. dstPort = 4789
  323. }
  324. args = append(args, "dstport", strconv.Itoa(dstPort))
  325. if inst.LocalAddress != "" {
  326. args = append(args, "local", inst.LocalAddress)
  327. }
  328. if inst.RemoteAddress != "" {
  329. args = append(args, "remote", inst.RemoteAddress)
  330. }
  331. if inst.Group != "" {
  332. args = append(args, "group", inst.Group)
  333. }
  334. if inst.ParentInterface != "" {
  335. args = append(args, "dev", inst.ParentInterface)
  336. }
  337. if !inst.Learning {
  338. args = append(args, "nolearning")
  339. }
  340. args = append(args, "ttl", "auto", "ageing", "300")
  341. out, err := exec.Command("ip", args...).CombinedOutput()
  342. if err != nil {
  343. inst.LastError = string(out)
  344. inst.Running = false
  345. return fmt.Errorf("failed to create vxlan %s: %s (%w)", inst.Name, strings.TrimSpace(string(out)), err)
  346. }
  347. if inst.MACAddress != "" {
  348. _ = exec.Command("ip", "link", "set", "dev", inst.Name, "address", inst.MACAddress).Run()
  349. }
  350. mtu := inst.MTU
  351. if mtu <= 0 {
  352. mtu = 1500
  353. }
  354. _ = exec.Command("ip", "link", "set", "dev", inst.Name, "mtu", strconv.Itoa(mtu)).Run()
  355. if out, err := exec.Command("ip", "link", "set", "dev", inst.Name, "up").CombinedOutput(); err != nil {
  356. inst.LastError = string(out)
  357. } else {
  358. inst.Running = true
  359. inst.LastError = ""
  360. }
  361. if inst.IPAddress != "" {
  362. _ = exec.Command("ip", "addr", "add", inst.IPAddress, "dev", inst.Name).Run()
  363. }
  364. for _, vtep := range inst.VTEPs {
  365. m.applyVtepKernel(inst.Name, vtep)
  366. }
  367. return nil
  368. }
  369. func (m *VXLANManager) applyVtepKernel(iface string, vtep *VXLANVtep) {
  370. if vtep.RemoteIP == "" {
  371. return
  372. }
  373. args := []string{"fdb", "append", "00:00:00:00:00:00", "dev", iface, "dst", vtep.RemoteIP}
  374. if vtep.Port > 0 && vtep.Port != 4789 {
  375. args = append(args, "port", strconv.Itoa(vtep.Port))
  376. }
  377. _ = exec.Command("bridge", args...).Run()
  378. }
  379. func (m *VXLANManager) removeVtepKernel(iface string, vtep *VXLANVtep) {
  380. if vtep.RemoteIP == "" {
  381. return
  382. }
  383. args := []string{"fdb", "del", "00:00:00:00:00:00", "dev", iface, "dst", vtep.RemoteIP}
  384. _ = exec.Command("bridge", args...).Run()
  385. }
  386. func (m *VXLANManager) updateStats(inst *VXLANInstance) {
  387. if inst.Name == "" {
  388. return
  389. }
  390. rxBytes, _ := readUint64File(fmt.Sprintf("/sys/class/net/%s/statistics/rx_bytes", inst.Name))
  391. txBytes, _ := readUint64File(fmt.Sprintf("/sys/class/net/%s/statistics/tx_bytes", inst.Name))
  392. rxPackets, _ := readUint64File(fmt.Sprintf("/sys/class/net/%s/statistics/rx_packets", inst.Name))
  393. txPackets, _ := readUint64File(fmt.Sprintf("/sys/class/net/%s/statistics/tx_packets", inst.Name))
  394. operstate, _ := os.ReadFile(fmt.Sprintf("/sys/class/net/%s/operstate", inst.Name))
  395. inst.RxBytes = rxBytes
  396. inst.TxBytes = txBytes
  397. inst.RxPackets = rxPackets
  398. inst.TxPackets = txPackets
  399. inst.RxHuman = formatBytes(rxBytes)
  400. inst.TxHuman = formatBytes(txBytes)
  401. if !inst.Disabled {
  402. stateStr := strings.TrimSpace(string(operstate))
  403. inst.Running = (stateStr == "up" || stateStr == "unknown")
  404. } else {
  405. inst.Running = false
  406. }
  407. }
  408. func readUint64File(path string) (uint64, error) {
  409. data, err := os.ReadFile(path)
  410. if err != nil {
  411. return 0, err
  412. }
  413. return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
  414. }
  415. func generateVXLANMAC() string {
  416. buf := make([]byte, 3)
  417. _, _ = rand.Read(buf)
  418. return fmt.Sprintf("02:58:%02x:%02x:%02x:%02x", buf[0], buf[1], buf[2], time.Now().UnixNano()%256)
  419. }