manager.go 12 KB

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