interfaces.go 4.6 KB

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