| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- package discovery
- import (
- "context"
- "encoding/json"
- "fmt"
- "net"
- "strings"
- "nettool/internal/config"
- "nettool/internal/deviceinfo"
- "nettool/internal/logger"
- "nettool/internal/model"
- )
- type Server struct {
- cfg config.Config
- log *logger.Logger
- deviceSvc *deviceinfo.Service
- }
- func New(cfg config.Config, log *logger.Logger, deviceSvc *deviceinfo.Service) *Server {
- return &Server{cfg: cfg, log: log, deviceSvc: deviceSvc}
- }
- func (s *Server) Run(ctx context.Context) error {
- addr, err := net.ResolveUDPAddr("udp4", fmt.Sprintf("%s:%d", s.cfg.UDPHost, s.cfg.UDPPort))
- if err != nil {
- return err
- }
- conn, err := net.ListenUDP("udp4", addr)
- if err != nil {
- return err
- }
- defer conn.Close()
- go func() {
- <-ctx.Done()
- _ = conn.Close()
- }()
- s.log.Info("udp discovery listening", "addr", conn.LocalAddr().String())
- if err := enableLocalAddrControl(conn); err != nil {
- s.log.Warn("udp discovery local interface detection is unavailable", "error", err.Error())
- }
- buf := make([]byte, 2048)
- for {
- n, remote, packetInfo, err := readFromUDP(conn, buf)
- if err != nil {
- if ctx.Err() != nil {
- return nil
- }
- return err
- }
- var req model.DiscoverRequest
- if err := json.Unmarshal(buf[:n], &req); err != nil || req.MessageType != "discover" {
- continue
- }
- lan2IP, mac := s.maintenanceEndpoint(packetInfo)
- if lan2IP == "" {
- s.log.Warn("skip discovery response because no 169.254 maintenance address was found")
- continue
- }
- device := s.deviceSvc.Get()
- resp := model.DiscoverResponse{
- ProtocolVersion: 1,
- MessageType: "discover_response",
- RequestID: req.RequestID,
- DeviceID: device.DeviceID,
- Hostname: device.Hostname,
- ServerVersion: device.ServerVersion,
- MAC: mac,
- LAN2IP: lan2IP,
- HTTPPort: s.cfg.HTTPPort,
- AuthRequired: true,
- }
- payload, _ := json.Marshal(resp)
- responseSourceIP := findResponseSourceIP(packetInfo.ifIndex, remote.IP)
- responseTarget := remote
- broadcastResponse := false
- if responseSourceIP == nil && packetInfo.ifIndex > 0 {
- responseSourceIP = net.ParseIP(lan2IP)
- responseTarget = broadcastResponseTarget(remote)
- broadcastResponse = true
- }
- if _, err := writeToUDP(conn, payload, responseTarget, packetInfo, responseSourceIP); err != nil {
- s.log.Warn("failed to send udp discovery response", "remote", remote.String(), "target", responseTarget.String(), "source_ip", responseSourceIP.String(), "maintenance_ip", lan2IP, "interface_index", packetInfo.ifIndex, "broadcast", broadcastResponse, "error", err.Error())
- }
- }
- }
- func broadcastResponseTarget(remote *net.UDPAddr) *net.UDPAddr {
- return &net.UDPAddr{IP: net.IPv4bcast, Port: remote.Port}
- }
- type udpPacketInfo struct {
- localIP net.IP
- ifIndex int
- }
- func (s *Server) maintenanceEndpoint(packetInfo udpPacketInfo) (string, string) {
- if packetInfo.ifIndex > 0 {
- lan2IP, mac := findLinkLocalEndpointByInterfaceIndex(packetInfo.ifIndex)
- if lan2IP != "" {
- return lan2IP, mac
- }
- return "", ""
- }
- if packetInfo.localIP != nil {
- lan2IP, mac := findLinkLocalEndpointByInterfaceIP(packetInfo.localIP.String())
- if lan2IP != "" {
- return lan2IP, mac
- }
- }
- if s.cfg.MaintenanceIP != "" {
- mac := findMACByIP(s.cfg.MaintenanceIP)
- if mac != "" {
- return s.cfg.MaintenanceIP, mac
- }
- }
- return findFirstLinkLocalEndpoint()
- }
- func findResponseSourceIP(interfaceIndex int, remoteIP net.IP) net.IP {
- if interfaceIndex <= 0 {
- return nil
- }
- iface, err := net.InterfaceByIndex(interfaceIndex)
- if err != nil {
- return nil
- }
- addresses, err := iface.Addrs()
- if err != nil {
- return nil
- }
- return bestSourceIPForRemote(addresses, remoteIP)
- }
- func bestSourceIPForRemote(addresses []net.Addr, remoteIP net.IP) net.IP {
- remoteIPv4 := remoteIP.To4()
- if remoteIPv4 == nil {
- return nil
- }
- var best net.IP
- bestPrefix := -1
- for _, address := range addresses {
- ipNet, ok := address.(*net.IPNet)
- if !ok || ipNet.IP.To4() == nil || !ipNet.Contains(remoteIPv4) {
- continue
- }
- prefix, bits := ipNet.Mask.Size()
- if bits != 32 || prefix <= bestPrefix {
- continue
- }
- best = ipNet.IP.To4()
- bestPrefix = prefix
- }
- return best
- }
- func findLinkLocalEndpointByInterfaceIndex(index int) (string, string) {
- iface, err := net.InterfaceByIndex(index)
- if err != nil {
- return "", ""
- }
- return findLinkLocalEndpointOnInterface(*iface)
- }
- func findLinkLocalEndpointByInterfaceIP(ip string) (string, string) {
- ifaces, err := net.Interfaces()
- if err != nil {
- return "", ""
- }
- for _, iface := range ifaces {
- lan2IP, mac := findLinkLocalEndpointOnInterface(iface)
- if lan2IP == ip {
- return lan2IP, mac
- }
- }
- return "", ""
- }
- func findFirstLinkLocalEndpoint() (string, string) {
- ifaces, err := net.Interfaces()
- if err != nil {
- return "", ""
- }
- for _, iface := range ifaces {
- lan2IP, mac := findLinkLocalEndpointOnInterface(iface)
- if lan2IP != "" {
- return lan2IP, mac
- }
- }
- return "", ""
- }
- func findLinkLocalEndpointOnInterface(iface net.Interface) (string, string) {
- if iface.Flags&net.FlagLoopback != 0 || iface.Flags&net.FlagUp == 0 || len(iface.HardwareAddr) == 0 {
- return "", ""
- }
- addrs, err := iface.Addrs()
- if err != nil {
- return "", ""
- }
- for _, addr := range addrs {
- current := ipv4FromAddr(addr)
- if current == nil || !strings.HasPrefix(current.String(), "169.254.") {
- continue
- }
- return current.String(), iface.HardwareAddr.String()
- }
- return "", ""
- }
- func findMACByIP(ip string) string {
- ifaces, err := net.Interfaces()
- if err != nil {
- return ""
- }
- var fallback string
- for _, iface := range ifaces {
- if iface.Flags&net.FlagLoopback != 0 || len(iface.HardwareAddr) == 0 {
- continue
- }
- addrs, err := iface.Addrs()
- if err != nil {
- continue
- }
- for _, addr := range addrs {
- current := ipv4FromAddr(addr)
- if current == nil {
- continue
- }
- if current.String() == ip {
- return iface.HardwareAddr.String()
- }
- if fallback == "" && strings.HasPrefix(current.String(), "169.254.") {
- fallback = iface.HardwareAddr.String()
- }
- }
- }
- return fallback
- }
- func ipv4FromAddr(addr net.Addr) net.IP {
- var current net.IP
- switch value := addr.(type) {
- case *net.IPNet:
- current = value.IP
- case *net.IPAddr:
- current = value.IP
- }
- if current == nil {
- return nil
- }
- return current.To4()
- }
|