interfaces.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. package interfaces
  2. import (
  3. "net"
  4. "os"
  5. "path/filepath"
  6. "sort"
  7. "strings"
  8. "networktool/internal/config"
  9. "networktool/internal/model"
  10. )
  11. type Service struct {
  12. cfg config.Config
  13. }
  14. func New(cfg config.Config) *Service {
  15. return &Service{cfg: cfg}
  16. }
  17. func (s *Service) List() (model.InterfacesResponse, error) {
  18. items, err := s.listPhysicalInterfaces()
  19. if err != nil {
  20. return model.InterfacesResponse{}, err
  21. }
  22. management := s.detectManagement(items)
  23. for i := range items {
  24. items[i].IsManagement = items[i].SystemName == management
  25. items[i].Name = items[i].SystemName
  26. if items[i].IsManagement {
  27. items[i].Role = "control"
  28. } else {
  29. items[i].Role = "business"
  30. }
  31. }
  32. suggested, requiresSelection := suggestTarget(items, management)
  33. for i := range items {
  34. items[i].IsSuggestedTarget = items[i].SystemName == suggested
  35. }
  36. return model.InterfacesResponse{
  37. ManagementInterface: management,
  38. SuggestedTargetInterface: suggested,
  39. RequiresTargetSelection: requiresSelection,
  40. Interfaces: items,
  41. }, nil
  42. }
  43. func (s *Service) listPhysicalInterfaces() ([]model.NetworkInterface, error) {
  44. ifaces, err := net.Interfaces()
  45. if err != nil {
  46. return nil, err
  47. }
  48. items := make([]model.NetworkInterface, 0)
  49. for _, iface := range ifaces {
  50. if !isPhysicalEthernet(iface.Name) {
  51. continue
  52. }
  53. item := model.NetworkInterface{
  54. SystemName: iface.Name,
  55. MAC: iface.HardwareAddr.String(),
  56. LinkUp: readLinkUp(iface.Name),
  57. Gateway: "",
  58. DNS: []string{},
  59. }
  60. item.IPv4 = readIPv4(iface)
  61. items = append(items, item)
  62. }
  63. sort.Slice(items, func(i, j int) bool { return items[i].SystemName < items[j].SystemName })
  64. return items, nil
  65. }
  66. func (s *Service) detectManagement(items []model.NetworkInterface) string {
  67. for _, item := range items {
  68. for _, addr := range item.IPv4 {
  69. if s.cfg.MaintenanceIP != "" && addr.Address == s.cfg.MaintenanceIP {
  70. return item.SystemName
  71. }
  72. if s.cfg.MaintenanceIP == "" && strings.HasPrefix(addr.Address, "169.254.") {
  73. return item.SystemName
  74. }
  75. }
  76. }
  77. if len(items) == 2 {
  78. return items[1].SystemName
  79. }
  80. if len(items) > 0 {
  81. return items[len(items)-1].SystemName
  82. }
  83. return ""
  84. }
  85. func suggestTarget(items []model.NetworkInterface, management string) (string, bool) {
  86. candidates := make([]string, 0)
  87. for _, item := range items {
  88. if item.SystemName != management {
  89. candidates = append(candidates, item.SystemName)
  90. }
  91. }
  92. if len(candidates) == 1 {
  93. return candidates[0], false
  94. }
  95. return "", len(candidates) > 1
  96. }
  97. func readIPv4(iface net.Interface) []model.IPv4Address {
  98. addrs, err := iface.Addrs()
  99. if err != nil {
  100. return nil
  101. }
  102. result := make([]model.IPv4Address, 0)
  103. for _, addr := range addrs {
  104. ipNet, ok := addr.(*net.IPNet)
  105. if !ok || ipNet.IP.To4() == nil {
  106. continue
  107. }
  108. prefix, _ := ipNet.Mask.Size()
  109. source := "unknown"
  110. if strings.HasPrefix(ipNet.IP.String(), "169.254.") {
  111. source = "static"
  112. }
  113. result = append(result, model.IPv4Address{Address: ipNet.IP.String(), Prefix: prefix, Source: source})
  114. }
  115. return result
  116. }
  117. func isPhysicalEthernet(name string) bool {
  118. if name == "lo" {
  119. return false
  120. }
  121. for _, prefix := range []string{"docker", "br-", "veth", "virbr", "tun", "tap", "wg", "zt", "vmnet"} {
  122. if strings.HasPrefix(name, prefix) {
  123. return false
  124. }
  125. }
  126. devicePath := filepath.Join("/sys/class/net", name, "device")
  127. if _, err := os.Stat(devicePath); err != nil {
  128. return false
  129. }
  130. return true
  131. }
  132. func readLinkUp(name string) bool {
  133. carrierPath := filepath.Join("/sys/class/net", name, "carrier")
  134. data, err := os.ReadFile(carrierPath)
  135. if err == nil {
  136. return strings.TrimSpace(string(data)) == "1"
  137. }
  138. statePath := filepath.Join("/sys/class/net", name, "operstate")
  139. data, err = os.ReadFile(statePath)
  140. if err != nil {
  141. return false
  142. }
  143. return strings.TrimSpace(string(data)) == "up"
  144. }