manager.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. package network
  2. import (
  3. "fmt"
  4. "net"
  5. "strings"
  6. "sync"
  7. "time"
  8. "github.com/vishvananda/netlink"
  9. )
  10. type prevStat struct {
  11. rxBytes uint64
  12. txBytes uint64
  13. time time.Time
  14. }
  15. // Manager handles all Linux network interactions via Netlink.
  16. type Manager struct {
  17. mu sync.Mutex
  18. metadata *MetadataManager
  19. prevStats map[string]prevStat
  20. }
  21. func NewManager(mm *MetadataManager) *Manager {
  22. return &Manager{
  23. metadata: mm,
  24. prevStats: make(map[string]prevStat),
  25. }
  26. }
  27. // IsKernelDummyInterface returns true if link is an internal kernel fallback/dummy device (e.g. gre0, gretap0, erspan0)
  28. func IsKernelDummyInterface(name string) bool {
  29. if strings.HasPrefix(name, "ep_") {
  30. return true
  31. }
  32. switch name {
  33. case "gre0", "gretap0", "erspan0", "sit0", "tunl0", "ip6tnl0", "ip6gre0", "ip6erspan0":
  34. return true
  35. default:
  36. return false
  37. }
  38. }
  39. // ListInterfaces returns all network links on the system.
  40. func (m *Manager) ListInterfaces() ([]InterfaceInfo, error) {
  41. links, err := netlink.LinkList()
  42. if err != nil {
  43. return nil, fmt.Errorf("failed to list links: %w", err)
  44. }
  45. linkMap := make(map[int]netlink.Link)
  46. for _, l := range links {
  47. linkMap[l.Attrs().Index] = l
  48. }
  49. m.mu.Lock()
  50. defer m.mu.Unlock()
  51. now := time.Now()
  52. result := make([]InterfaceInfo, 0)
  53. for _, link := range links {
  54. attrs := link.Attrs()
  55. if IsKernelDummyInterface(attrs.Name) {
  56. continue
  57. }
  58. info := InterfaceInfo{
  59. Index: attrs.Index,
  60. Name: attrs.Name,
  61. Type: link.Type(),
  62. MTU: attrs.MTU,
  63. MAC: attrs.HardwareAddr.String(),
  64. OperState: strings.ToUpper(attrs.OperState.String()),
  65. IPv4: make([]string, 0),
  66. IPv6: make([]string, 0),
  67. Flags: make([]string, 0),
  68. }
  69. // Flags
  70. if attrs.Flags&net.FlagUp != 0 {
  71. info.State = "UP"
  72. } else {
  73. info.State = "DOWN"
  74. }
  75. for _, f := range strings.Split(attrs.Flags.String(), "|") {
  76. if f != "" {
  77. info.Flags = append(info.Flags, f)
  78. }
  79. }
  80. // Check if interface is a VLAN
  81. if vlan, ok := link.(*netlink.Vlan); ok {
  82. info.IsVLAN = true
  83. info.VlanID = vlan.VlanId
  84. info.ParentIndex = vlan.ParentIndex
  85. if parent, exists := linkMap[vlan.ParentIndex]; exists {
  86. info.ParentName = parent.Attrs().Name
  87. }
  88. } else if strings.Contains(attrs.Name, ".") {
  89. // Sub-interface naming fallback (e.g. eth0.10)
  90. parts := strings.Split(attrs.Name, ".")
  91. if len(parts) == 2 {
  92. var id int
  93. if n, _ := fmt.Sscanf(parts[1], "%d", &id); n == 1 {
  94. info.IsVLAN = true
  95. info.VlanID = id
  96. info.ParentName = parts[0]
  97. }
  98. }
  99. }
  100. // Addresses
  101. addrs, err := netlink.AddrList(link, netlink.FAMILY_ALL)
  102. if err == nil {
  103. for _, a := range addrs {
  104. if a.IP.To4() != nil {
  105. info.IPv4 = append(info.IPv4, a.IPNet.String())
  106. } else {
  107. info.IPv6 = append(info.IPv6, a.IPNet.String())
  108. }
  109. }
  110. }
  111. // Metadata
  112. meta := m.metadata.Get(attrs.Name)
  113. info.Role = meta.Role
  114. info.Description = meta.Description
  115. if info.Role == "" {
  116. if attrs.Name == "lo" {
  117. info.Role = "LOOPBACK"
  118. } else if info.IsVLAN {
  119. info.Role = "VLAN"
  120. } else if strings.HasPrefix(attrs.Name, "eth0") || strings.HasPrefix(attrs.Name, "ens") {
  121. info.Role = "WAN/LAN"
  122. }
  123. }
  124. // Statistics & throughput calculation
  125. if attrs.Statistics != nil {
  126. info.Stats = NetStats{
  127. RxBytes: attrs.Statistics.RxBytes,
  128. TxBytes: attrs.Statistics.TxBytes,
  129. RxPackets: attrs.Statistics.RxPackets,
  130. TxPackets: attrs.Statistics.TxPackets,
  131. RxErrors: attrs.Statistics.RxErrors,
  132. TxErrors: attrs.Statistics.TxErrors,
  133. RxDropped: attrs.Statistics.RxDropped,
  134. TxDropped: attrs.Statistics.TxDropped,
  135. }
  136. if prev, ok := m.prevStats[attrs.Name]; ok {
  137. dt := now.Sub(prev.time).Seconds()
  138. if dt > 0.1 && attrs.Statistics.RxBytes >= prev.rxBytes && attrs.Statistics.TxBytes >= prev.txBytes {
  139. rxRate := float64(attrs.Statistics.RxBytes-prev.rxBytes) / dt
  140. txRate := float64(attrs.Statistics.TxBytes-prev.txBytes) / dt
  141. info.Rate = TrafficRate{
  142. RxBytesPerSec: rxRate,
  143. TxBytesPerSec: txRate,
  144. RxHuman: formatBytes(rxRate) + "/s",
  145. TxHuman: formatBytes(txRate) + "/s",
  146. }
  147. }
  148. }
  149. m.prevStats[attrs.Name] = prevStat{
  150. rxBytes: attrs.Statistics.RxBytes,
  151. txBytes: attrs.Statistics.TxBytes,
  152. time: now,
  153. }
  154. }
  155. result = append(result, info)
  156. }
  157. return result, nil
  158. }
  159. // GetInterface returns information for a specific interface.
  160. func (m *Manager) GetInterface(name string) (*InterfaceInfo, error) {
  161. interfaces, err := m.ListInterfaces()
  162. if err != nil {
  163. return nil, err
  164. }
  165. for _, iface := range interfaces {
  166. if iface.Name == name {
  167. return &iface, nil
  168. }
  169. }
  170. return nil, fmt.Errorf("interface %s not found", name)
  171. }
  172. // SetInterfaceState sets the administrative status (UP or DOWN).
  173. func (m *Manager) SetInterfaceState(name string, up bool) error {
  174. link, err := netlink.LinkByName(name)
  175. if err != nil {
  176. return fmt.Errorf("link not found: %w", err)
  177. }
  178. if up {
  179. return netlink.LinkSetUp(link)
  180. }
  181. return netlink.LinkSetDown(link)
  182. }
  183. // SetInterfaceMTU updates the MTU for the specified interface.
  184. func (m *Manager) SetInterfaceMTU(name string, mtu int) error {
  185. if mtu < 68 || mtu > 65535 {
  186. return fmt.Errorf("invalid MTU size: %d", mtu)
  187. }
  188. link, err := netlink.LinkByName(name)
  189. if err != nil {
  190. return fmt.Errorf("link not found: %w", err)
  191. }
  192. return netlink.LinkSetMTU(link, mtu)
  193. }
  194. // AddInterfaceIP adds an IP address with subnet CIDR to an interface.
  195. func (m *Manager) AddInterfaceIP(name string, cidr string) error {
  196. link, err := netlink.LinkByName(name)
  197. if err != nil {
  198. return fmt.Errorf("link not found: %w", err)
  199. }
  200. addr, err := netlink.ParseAddr(cidr)
  201. if err != nil {
  202. return fmt.Errorf("invalid IP/CIDR %q: %w", cidr, err)
  203. }
  204. return netlink.AddrAdd(link, addr)
  205. }
  206. // DeleteInterfaceIP removes an IP address from an interface.
  207. func (m *Manager) DeleteInterfaceIP(name string, cidr string) error {
  208. link, err := netlink.LinkByName(name)
  209. if err != nil {
  210. return fmt.Errorf("link not found: %w", err)
  211. }
  212. addr, err := netlink.ParseAddr(cidr)
  213. if err != nil {
  214. return fmt.Errorf("invalid IP/CIDR %q: %w", cidr, err)
  215. }
  216. return netlink.AddrDel(link, addr)
  217. }
  218. // CreateVLAN creates a new 802.1Q VLAN sub-interface.
  219. func (m *Manager) CreateVLAN(req CreateVLANRequest) (*InterfaceInfo, error) {
  220. if req.VlanID < 1 || req.VlanID > 4094 {
  221. return nil, fmt.Errorf("VLAN ID must be between 1 and 4094, got %d", req.VlanID)
  222. }
  223. parent, err := netlink.LinkByName(req.ParentName)
  224. if err != nil {
  225. return nil, fmt.Errorf("parent interface %q not found: %w", req.ParentName, err)
  226. }
  227. vlanName := req.Name
  228. if vlanName == "" {
  229. vlanName = fmt.Sprintf("%s.%d", req.ParentName, req.VlanID)
  230. }
  231. // Check if already exists
  232. if _, err := netlink.LinkByName(vlanName); err == nil {
  233. return nil, fmt.Errorf("interface %q already exists", vlanName)
  234. }
  235. mtu := req.MTU
  236. if mtu <= 0 {
  237. mtu = parent.Attrs().MTU
  238. if mtu == 0 {
  239. mtu = 1500
  240. }
  241. }
  242. vlanLink := &netlink.Vlan{
  243. LinkAttrs: netlink.LinkAttrs{
  244. Name: vlanName,
  245. ParentIndex: parent.Attrs().Index,
  246. MTU: mtu,
  247. },
  248. VlanId: req.VlanID,
  249. }
  250. if err := netlink.LinkAdd(vlanLink); err != nil {
  251. return nil, fmt.Errorf("failed to create VLAN link: %w", err)
  252. }
  253. // Bring UP
  254. if req.AutoUp {
  255. if err := netlink.LinkSetUp(vlanLink); err != nil {
  256. _ = netlink.LinkDel(vlanLink)
  257. return nil, fmt.Errorf("failed to set VLAN link UP: %w", err)
  258. }
  259. }
  260. // Assign IP if provided
  261. if strings.TrimSpace(req.IPAddress) != "" {
  262. addr, err := netlink.ParseAddr(strings.TrimSpace(req.IPAddress))
  263. if err != nil {
  264. _ = netlink.LinkDel(vlanLink)
  265. return nil, fmt.Errorf("invalid IP address %q: %w", req.IPAddress, err)
  266. }
  267. if err := netlink.AddrAdd(vlanLink, addr); err != nil {
  268. _ = netlink.LinkDel(vlanLink)
  269. return nil, fmt.Errorf("failed to assign IP to VLAN: %w", err)
  270. }
  271. }
  272. // Store metadata
  273. role := req.Role
  274. if role == "" {
  275. role = "VLAN"
  276. }
  277. _ = m.metadata.Set(vlanName, role, req.Description)
  278. return m.GetInterface(vlanName)
  279. }
  280. // DeleteVLAN removes a VLAN interface.
  281. func (m *Manager) DeleteVLAN(name string) error {
  282. link, err := netlink.LinkByName(name)
  283. if err != nil {
  284. return fmt.Errorf("interface %s not found: %w", name, err)
  285. }
  286. if _, ok := link.(*netlink.Vlan); !ok && !strings.Contains(name, ".") {
  287. return fmt.Errorf("interface %s is not recognized as a VLAN interface", name)
  288. }
  289. if err := netlink.LinkDel(link); err != nil {
  290. return fmt.Errorf("failed to delete link: %w", err)
  291. }
  292. _ = m.metadata.Delete(name)
  293. return nil
  294. }
  295. // ListVLANs returns only the VLAN interfaces.
  296. func (m *Manager) ListVLANs() ([]InterfaceInfo, error) {
  297. all, err := m.ListInterfaces()
  298. if err != nil {
  299. return nil, err
  300. }
  301. vlans := make([]InterfaceInfo, 0)
  302. for _, iface := range all {
  303. if iface.IsVLAN {
  304. vlans = append(vlans, iface)
  305. }
  306. }
  307. return vlans, nil
  308. }
  309. func formatBytes(b float64) string {
  310. const unit = 1024
  311. if b < unit {
  312. return fmt.Sprintf("%.0f B", b)
  313. }
  314. div, exp := int64(unit), 0
  315. for n := b / unit; n >= unit; n /= unit {
  316. div *= unit
  317. exp++
  318. }
  319. return fmt.Sprintf("%.1f %cB", b/float64(div), "KMGTPE"[exp])
  320. }