manager.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. package network
  2. import (
  3. "fmt"
  4. "log"
  5. "net"
  6. "regexp"
  7. "strings"
  8. "sync"
  9. "time"
  10. "github.com/vishvananda/netlink"
  11. )
  12. type prevStat struct {
  13. rxBytes uint64
  14. txBytes uint64
  15. time time.Time
  16. }
  17. // Manager handles all Linux network interactions via Netlink.
  18. type Manager struct {
  19. mu sync.Mutex
  20. metadata *MetadataManager
  21. prevStats map[string]prevStat
  22. }
  23. func NewManager(mm *MetadataManager) *Manager {
  24. mgr := &Manager{
  25. metadata: mm,
  26. prevStats: make(map[string]prevStat),
  27. }
  28. mgr.startIPSyncWorker()
  29. return mgr
  30. }
  31. // syncPersistedIPs ensures that all IP addresses saved in metadata are applied to the kernel interfaces
  32. func (m *Manager) syncPersistedIPs() {
  33. if m.metadata == nil {
  34. return
  35. }
  36. configured := m.metadata.GetAllWithIPs()
  37. for name, ips := range configured {
  38. link, err := netlink.LinkByName(name)
  39. if err != nil {
  40. continue // Interface might not be attached/created yet by Proxmox host
  41. }
  42. // Ensure link is UP if it has configured IPs
  43. if link.Attrs().Flags&net.FlagUp == 0 {
  44. _ = netlink.LinkSetUp(link)
  45. }
  46. currentAddrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
  47. if err != nil {
  48. continue
  49. }
  50. currentMap := make(map[string]bool)
  51. for _, a := range currentAddrs {
  52. currentMap[a.IPNet.String()] = true
  53. }
  54. for _, cidr := range ips {
  55. if !currentMap[cidr] {
  56. addr, err := netlink.ParseAddr(cidr)
  57. if err == nil {
  58. if err := netlink.AddrAdd(link, addr); err == nil {
  59. log.Printf("[NetSync] Restored IP %s on interface %s", cidr, name)
  60. }
  61. }
  62. }
  63. }
  64. }
  65. }
  66. func (m *Manager) startIPSyncWorker() {
  67. go func() {
  68. m.syncPersistedIPs()
  69. ticker := time.NewTicker(3 * time.Second)
  70. defer ticker.Stop()
  71. for range ticker.C {
  72. m.syncPersistedIPs()
  73. }
  74. }()
  75. }
  76. // IsKernelDummyInterface returns true if link is an internal kernel fallback/dummy device (e.g. gre0, gretap0, erspan0)
  77. func IsKernelDummyInterface(name string) bool {
  78. if strings.HasPrefix(name, "ep_") {
  79. return true
  80. }
  81. switch name {
  82. case "gre0", "gretap0", "erspan0", "sit0", "tunl0", "ip6tnl0", "ip6gre0", "ip6erspan0":
  83. return true
  84. default:
  85. return false
  86. }
  87. }
  88. // ListInterfaces returns all network links on the system.
  89. func (m *Manager) ListInterfaces() ([]InterfaceInfo, error) {
  90. links, err := netlink.LinkList()
  91. if err != nil {
  92. return nil, fmt.Errorf("failed to list links: %w", err)
  93. }
  94. linkMap := make(map[int]netlink.Link)
  95. for _, l := range links {
  96. linkMap[l.Attrs().Index] = l
  97. }
  98. m.mu.Lock()
  99. defer m.mu.Unlock()
  100. now := time.Now()
  101. result := make([]InterfaceInfo, 0)
  102. for _, link := range links {
  103. attrs := link.Attrs()
  104. if IsKernelDummyInterface(attrs.Name) {
  105. continue
  106. }
  107. info := InterfaceInfo{
  108. Index: attrs.Index,
  109. Name: attrs.Name,
  110. Type: link.Type(),
  111. MTU: attrs.MTU,
  112. MAC: attrs.HardwareAddr.String(),
  113. OperState: strings.ToUpper(attrs.OperState.String()),
  114. IPv4: make([]string, 0),
  115. IPv6: make([]string, 0),
  116. Flags: make([]string, 0),
  117. }
  118. // Flags
  119. if attrs.Flags&net.FlagUp != 0 {
  120. info.State = "UP"
  121. } else {
  122. info.State = "DOWN"
  123. }
  124. for _, f := range strings.Split(attrs.Flags.String(), "|") {
  125. if f != "" {
  126. info.Flags = append(info.Flags, f)
  127. }
  128. }
  129. // Check if interface is a VLAN
  130. if vlan, ok := link.(*netlink.Vlan); ok {
  131. info.IsVLAN = true
  132. info.VlanID = vlan.VlanId
  133. info.ParentIndex = vlan.ParentIndex
  134. if parent, exists := linkMap[vlan.ParentIndex]; exists {
  135. info.ParentName = parent.Attrs().Name
  136. }
  137. } else if strings.Contains(attrs.Name, ".") {
  138. // Sub-interface naming fallback (e.g. eth0.10)
  139. parts := strings.Split(attrs.Name, ".")
  140. if len(parts) == 2 {
  141. var id int
  142. if n, _ := fmt.Sscanf(parts[1], "%d", &id); n == 1 {
  143. info.IsVLAN = true
  144. info.VlanID = id
  145. info.ParentName = parts[0]
  146. }
  147. }
  148. }
  149. // Addresses
  150. addrs, err := netlink.AddrList(link, netlink.FAMILY_ALL)
  151. if err == nil {
  152. for _, a := range addrs {
  153. if a.IP.To4() != nil {
  154. info.IPv4 = append(info.IPv4, a.IPNet.String())
  155. } else {
  156. info.IPv6 = append(info.IPv6, a.IPNet.String())
  157. }
  158. }
  159. }
  160. // Metadata
  161. meta := m.metadata.Get(attrs.Name)
  162. info.Role = meta.Role
  163. info.Description = meta.Description
  164. if info.Role == "" {
  165. if attrs.Name == "lo" {
  166. info.Role = "LOOPBACK"
  167. } else if info.IsVLAN {
  168. info.Role = "VLAN"
  169. } else if strings.HasPrefix(attrs.Name, "eth0") || strings.HasPrefix(attrs.Name, "ens") {
  170. info.Role = "WAN/LAN"
  171. }
  172. }
  173. // Statistics & throughput calculation
  174. if attrs.Statistics != nil {
  175. info.Stats = NetStats{
  176. RxBytes: attrs.Statistics.RxBytes,
  177. TxBytes: attrs.Statistics.TxBytes,
  178. RxPackets: attrs.Statistics.RxPackets,
  179. TxPackets: attrs.Statistics.TxPackets,
  180. RxErrors: attrs.Statistics.RxErrors,
  181. TxErrors: attrs.Statistics.TxErrors,
  182. RxDropped: attrs.Statistics.RxDropped,
  183. TxDropped: attrs.Statistics.TxDropped,
  184. }
  185. if prev, ok := m.prevStats[attrs.Name]; ok {
  186. dt := now.Sub(prev.time).Seconds()
  187. if dt > 0.1 && attrs.Statistics.RxBytes >= prev.rxBytes && attrs.Statistics.TxBytes >= prev.txBytes {
  188. rxRate := float64(attrs.Statistics.RxBytes-prev.rxBytes) / dt
  189. txRate := float64(attrs.Statistics.TxBytes-prev.txBytes) / dt
  190. info.Rate = TrafficRate{
  191. RxBytesPerSec: rxRate,
  192. TxBytesPerSec: txRate,
  193. RxHuman: formatBytes(rxRate) + "/s",
  194. TxHuman: formatBytes(txRate) + "/s",
  195. }
  196. }
  197. }
  198. m.prevStats[attrs.Name] = prevStat{
  199. rxBytes: attrs.Statistics.RxBytes,
  200. txBytes: attrs.Statistics.TxBytes,
  201. time: now,
  202. }
  203. }
  204. result = append(result, info)
  205. }
  206. return result, nil
  207. }
  208. // GetInterface returns information for a specific interface.
  209. func (m *Manager) GetInterface(name string) (*InterfaceInfo, error) {
  210. interfaces, err := m.ListInterfaces()
  211. if err != nil {
  212. return nil, err
  213. }
  214. for _, iface := range interfaces {
  215. if iface.Name == name {
  216. return &iface, nil
  217. }
  218. }
  219. return nil, fmt.Errorf("interface %s not found", name)
  220. }
  221. // SetInterfaceState sets the administrative status (UP or DOWN).
  222. func (m *Manager) SetInterfaceState(name string, up bool) error {
  223. link, err := netlink.LinkByName(name)
  224. if err != nil {
  225. return fmt.Errorf("link not found: %w", err)
  226. }
  227. if up {
  228. return netlink.LinkSetUp(link)
  229. }
  230. return netlink.LinkSetDown(link)
  231. }
  232. // SetInterfaceMTU updates the MTU for the specified interface.
  233. func (m *Manager) SetInterfaceMTU(name string, mtu int) error {
  234. if mtu < 68 || mtu > 65535 {
  235. return fmt.Errorf("invalid MTU size: %d", mtu)
  236. }
  237. link, err := netlink.LinkByName(name)
  238. if err != nil {
  239. return fmt.Errorf("link not found: %w", err)
  240. }
  241. return netlink.LinkSetMTU(link, mtu)
  242. }
  243. var ifaceNameRegex = regexp.MustCompile(`^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$`)
  244. // RenameInterface safely renames an existing network device using Netlink.
  245. func (m *Manager) RenameInterface(oldName, newName string) (*InterfaceInfo, error) {
  246. oldName = strings.TrimSpace(oldName)
  247. newName = strings.TrimSpace(newName)
  248. if oldName == "" || newName == "" {
  249. return nil, fmt.Errorf("interface name cannot be empty")
  250. }
  251. if oldName == newName {
  252. return m.GetInterface(newName)
  253. }
  254. if oldName == "lo" || newName == "lo" {
  255. return nil, fmt.Errorf("cannot rename the loopback interface")
  256. }
  257. if len(newName) > 15 {
  258. return nil, fmt.Errorf("interface name %q exceeds Linux limit of 15 characters", newName)
  259. }
  260. if !ifaceNameRegex.MatchString(newName) {
  261. return nil, fmt.Errorf("invalid interface name %q: must contain only letters, numbers, underscores, dots, or hyphens", newName)
  262. }
  263. link, err := netlink.LinkByName(oldName)
  264. if err != nil {
  265. return nil, fmt.Errorf("interface %q not found: %w", oldName, err)
  266. }
  267. if _, err := netlink.LinkByName(newName); err == nil {
  268. return nil, fmt.Errorf("interface %q already exists", newName)
  269. }
  270. wasUp := (link.Attrs().Flags & net.FlagUp) != 0
  271. // Linux kernel requires interface to be DOWN to change dev name
  272. if wasUp {
  273. if err := netlink.LinkSetDown(link); err != nil {
  274. return nil, fmt.Errorf("failed to bring interface %q down for renaming: %w", oldName, err)
  275. }
  276. }
  277. if err := netlink.LinkSetName(link, newName); err != nil {
  278. if wasUp {
  279. _ = netlink.LinkSetUp(link)
  280. }
  281. return nil, fmt.Errorf("failed to rename interface %q to %q: %w", oldName, newName, err)
  282. }
  283. // Restore UP state if it was originally UP
  284. if wasUp {
  285. if newLink, err := netlink.LinkByName(newName); err == nil {
  286. _ = netlink.LinkSetUp(newLink)
  287. }
  288. }
  289. // Migrate metadata
  290. _ = m.metadata.Rename(oldName, newName)
  291. // Update traffic rate history
  292. m.mu.Lock()
  293. if prev, ok := m.prevStats[oldName]; ok {
  294. delete(m.prevStats, oldName)
  295. m.prevStats[newName] = prev
  296. }
  297. m.mu.Unlock()
  298. return m.GetInterface(newName)
  299. }
  300. // AddInterfaceIP adds an IP address with subnet CIDR to an interface and persists it.
  301. func (m *Manager) AddInterfaceIP(name string, cidr string) error {
  302. link, err := netlink.LinkByName(name)
  303. if err != nil {
  304. return fmt.Errorf("link not found: %w", err)
  305. }
  306. addr, err := netlink.ParseAddr(cidr)
  307. if err != nil {
  308. return fmt.Errorf("invalid IP/CIDR %q: %w", cidr, err)
  309. }
  310. if err := netlink.AddrAdd(link, addr); err != nil && !strings.Contains(err.Error(), "file exists") {
  311. return err
  312. }
  313. if m.metadata != nil {
  314. _ = m.metadata.AddIP(name, cidr)
  315. }
  316. return nil
  317. }
  318. // DeleteInterfaceIP removes an IP address from an interface and updates persistence.
  319. func (m *Manager) DeleteInterfaceIP(name string, cidr string) error {
  320. link, err := netlink.LinkByName(name)
  321. if err != nil {
  322. return fmt.Errorf("link not found: %w", err)
  323. }
  324. addr, err := netlink.ParseAddr(cidr)
  325. if err != nil {
  326. return fmt.Errorf("invalid IP/CIDR %q: %w", cidr, err)
  327. }
  328. if err := netlink.AddrDel(link, addr); err != nil && !strings.Contains(err.Error(), "cannot assign requested address") {
  329. return err
  330. }
  331. if m.metadata != nil {
  332. _ = m.metadata.RemoveIP(name, cidr)
  333. }
  334. return nil
  335. }
  336. // CreateVLAN creates a new 802.1Q VLAN sub-interface.
  337. func (m *Manager) CreateVLAN(req CreateVLANRequest) (*InterfaceInfo, error) {
  338. if req.VlanID < 1 || req.VlanID > 4094 {
  339. return nil, fmt.Errorf("VLAN ID must be between 1 and 4094, got %d", req.VlanID)
  340. }
  341. parent, err := netlink.LinkByName(req.ParentName)
  342. if err != nil {
  343. return nil, fmt.Errorf("parent interface %q not found: %w", req.ParentName, err)
  344. }
  345. // Validate parent interface supports 802.1Q VLAN sub-interfaces
  346. parentType := strings.ToLower(parent.Type())
  347. parentName := strings.ToLower(parent.Attrs().Name)
  348. if parentType == "wireguard" || strings.HasPrefix(parentName, "wg") ||
  349. parentType == "loopback" || parentName == "lo" ||
  350. parentType == "ppp" || strings.HasPrefix(parentName, "ppp") ||
  351. parentType == "dummy" || parentType == "tun" || parentType == "tap" || parentType == "gre" ||
  352. parent.Attrs().Flags&net.FlagLoopback != 0 || parent.Attrs().Flags&net.FlagPointToPoint != 0 {
  353. return nil, fmt.Errorf("interface %q does not support 802.1Q VLAN sub-interfaces", req.ParentName)
  354. }
  355. vlanName := req.Name
  356. if vlanName == "" {
  357. vlanName = fmt.Sprintf("%s.%d", req.ParentName, req.VlanID)
  358. }
  359. // Check if already exists
  360. if _, err := netlink.LinkByName(vlanName); err == nil {
  361. return nil, fmt.Errorf("interface %q already exists", vlanName)
  362. }
  363. mtu := req.MTU
  364. if mtu <= 0 {
  365. mtu = parent.Attrs().MTU
  366. if mtu == 0 {
  367. mtu = 1500
  368. }
  369. }
  370. vlanLink := &netlink.Vlan{
  371. LinkAttrs: netlink.LinkAttrs{
  372. Name: vlanName,
  373. ParentIndex: parent.Attrs().Index,
  374. MTU: mtu,
  375. },
  376. VlanId: req.VlanID,
  377. }
  378. if err := netlink.LinkAdd(vlanLink); err != nil {
  379. return nil, fmt.Errorf("failed to create VLAN link: %w", err)
  380. }
  381. // Bring UP
  382. if req.AutoUp {
  383. if err := netlink.LinkSetUp(vlanLink); err != nil {
  384. _ = netlink.LinkDel(vlanLink)
  385. return nil, fmt.Errorf("failed to set VLAN link UP: %w", err)
  386. }
  387. }
  388. // Assign IP if provided
  389. if strings.TrimSpace(req.IPAddress) != "" {
  390. addr, err := netlink.ParseAddr(strings.TrimSpace(req.IPAddress))
  391. if err != nil {
  392. _ = netlink.LinkDel(vlanLink)
  393. return nil, fmt.Errorf("invalid IP address %q: %w", req.IPAddress, err)
  394. }
  395. if err := netlink.AddrAdd(vlanLink, addr); err != nil {
  396. _ = netlink.LinkDel(vlanLink)
  397. return nil, fmt.Errorf("failed to assign IP to VLAN: %w", err)
  398. }
  399. }
  400. // Store metadata
  401. role := req.Role
  402. if role == "" {
  403. role = "VLAN"
  404. }
  405. _ = m.metadata.Set(vlanName, role, req.Description)
  406. return m.GetInterface(vlanName)
  407. }
  408. // DeleteVLAN removes a VLAN interface.
  409. func (m *Manager) DeleteVLAN(name string) error {
  410. link, err := netlink.LinkByName(name)
  411. if err != nil {
  412. return fmt.Errorf("interface %s not found: %w", name, err)
  413. }
  414. if _, ok := link.(*netlink.Vlan); !ok && !strings.Contains(name, ".") {
  415. return fmt.Errorf("interface %s is not recognized as a VLAN interface", name)
  416. }
  417. if err := netlink.LinkDel(link); err != nil {
  418. return fmt.Errorf("failed to delete link: %w", err)
  419. }
  420. _ = m.metadata.Delete(name)
  421. return nil
  422. }
  423. // ListVLANs returns only the VLAN interfaces.
  424. func (m *Manager) ListVLANs() ([]InterfaceInfo, error) {
  425. all, err := m.ListInterfaces()
  426. if err != nil {
  427. return nil, err
  428. }
  429. vlans := make([]InterfaceInfo, 0)
  430. for _, iface := range all {
  431. if iface.IsVLAN {
  432. vlans = append(vlans, iface)
  433. }
  434. }
  435. return vlans, nil
  436. }
  437. func formatBytes(b float64) string {
  438. const unit = 1024
  439. if b < unit {
  440. return fmt.Sprintf("%.0f B", b)
  441. }
  442. div, exp := int64(unit), 0
  443. for n := b / unit; n >= unit; n /= unit {
  444. div *= unit
  445. exp++
  446. }
  447. return fmt.Sprintf("%.1f %cB", b/float64(div), "KMGTPE"[exp])
  448. }