multicast.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. // File: pkg/discovery/multicast.go
  2. package discovery
  3. import (
  4. "context"
  5. "fmt"
  6. "net"
  7. "runtime"
  8. "sync"
  9. "sync/atomic"
  10. "TengTuToolbox/localsend/logging"
  11. "TengTuToolbox/localsend/model"
  12. "TengTuToolbox/localsend/network"
  13. )
  14. // MulticastDiscovery implements UDP multicast-based device discovery
  15. type MulticastDiscovery struct {
  16. config *MulticastConfig
  17. dto model.MulticastDto
  18. devices map[string]*model.Device
  19. devicesMutex sync.RWMutex
  20. handlers []func(*model.Device)
  21. handlersMu sync.RWMutex
  22. conns []net.PacketConn
  23. connsMu sync.Mutex
  24. closed atomic.Bool
  25. peerCache *PeerCache
  26. logger *logging.Logger
  27. }
  28. // NewMulticastDiscovery creates a new multicast discovery instance
  29. func NewMulticastDiscovery(config *MulticastConfig, dto model.MulticastDto, logger *logging.Logger) *MulticastDiscovery {
  30. if config == nil {
  31. config = DefaultMulticastConfig()
  32. }
  33. if logger == nil {
  34. logger = logging.NewQuiet()
  35. }
  36. return &MulticastDiscovery{
  37. config: config,
  38. dto: dto,
  39. devices: make(map[string]*model.Device),
  40. logger: logger,
  41. }
  42. }
  43. // AddDeviceHandler adds a handler function that will be called when a device is discovered
  44. func (md *MulticastDiscovery) AddDeviceHandler(handler func(*model.Device)) {
  45. md.handlersMu.Lock()
  46. defer md.handlersMu.Unlock()
  47. md.handlers = append(md.handlers, handler)
  48. }
  49. // StartListening starts listening for multicast announcements on all suitable interfaces.
  50. // If InterfaceName is set in config, only that interface is used.
  51. func (md *MulticastDiscovery) StartListening(ctx context.Context) error {
  52. md.closed.Store(false)
  53. md.connsMu.Lock()
  54. if len(md.conns) > 0 {
  55. md.connsMu.Unlock()
  56. return fmt.Errorf("already listening")
  57. }
  58. md.connsMu.Unlock()
  59. addr, err := net.ResolveUDPAddr("udp4", md.config.MulticastAddr)
  60. if err != nil {
  61. return fmt.Errorf("failed to resolve multicast address: %w", err)
  62. }
  63. var targetIfaces []net.Interface
  64. if md.config.InterfaceName != "" {
  65. iface, err := network.InterfaceByName(md.config.InterfaceName)
  66. if err != nil {
  67. return fmt.Errorf("multicast interface '%s' not found: %w", md.config.InterfaceName, err)
  68. }
  69. if (iface.Flags & net.FlagUp) == 0 {
  70. return fmt.Errorf("multicast interface '%s' is down", md.config.InterfaceName)
  71. }
  72. if (iface.Flags & net.FlagMulticast) == 0 {
  73. return fmt.Errorf("multicast interface '%s' does not support multicast", md.config.InterfaceName)
  74. }
  75. targetIfaces = append(targetIfaces, *iface)
  76. } else {
  77. allIfaces, err := network.EnumerateInterfaces()
  78. if err != nil {
  79. if runtime.GOOS != "android" {
  80. return fmt.Errorf("failed to list network interfaces: %w", err)
  81. }
  82. // Android/Termux where netlink is blocked by SELinux.
  83. md.logger.Warnf("Failed to list network interfaces (%v); falling back to the default multicast interface", err)
  84. return md.listenOnDefaultInterface(ctx, addr)
  85. }
  86. for _, iface := range allIfaces {
  87. if (iface.Flags&net.FlagUp) == 0 || (iface.Flags&net.FlagMulticast) == 0 {
  88. continue
  89. }
  90. targetIfaces = append(targetIfaces, iface)
  91. }
  92. }
  93. if len(targetIfaces) == 0 {
  94. if runtime.GOOS != "android" {
  95. return fmt.Errorf("no suitable multicast interface found")
  96. }
  97. // No usable multicast interface (e.g. Android on cellular, where the
  98. // only visible interfaces don't support multicast). Fall back to the
  99. // kernel's default multicast interface, which the sandbox permits.
  100. md.logger.Warnf("No suitable multicast interface found; falling back to the default multicast interface")
  101. return md.listenOnDefaultInterface(ctx, addr)
  102. }
  103. for _, iface := range targetIfaces {
  104. conn, err := net.ListenMulticastUDP("udp4", &iface, addr)
  105. if err != nil {
  106. md.logger.Warnf("Failed to listen on interface %s: %v", iface.Name, err)
  107. continue
  108. }
  109. conn.SetReadBuffer(2048)
  110. md.connsMu.Lock()
  111. md.conns = append(md.conns, conn)
  112. md.connsMu.Unlock()
  113. go md.listenLoop(ctx, conn)
  114. md.logger.Debugf("Multicast discovery listening on %s (interface: %s)", md.config.MulticastAddr, iface.Name)
  115. }
  116. md.connsMu.Lock()
  117. listening := len(md.conns)
  118. md.connsMu.Unlock()
  119. if listening == 0 {
  120. return fmt.Errorf("failed to listen on any multicast interface")
  121. }
  122. return nil
  123. }
  124. // listenOnDefaultInterface binds a single multicast listener on the kernel's
  125. // default multicast interface (index 0). This is the only working option on
  126. // Android/Termux, where netlink-based interface enumeration is blocked and the
  127. // visible cellular interfaces reject multicast membership (ENODEV).
  128. func (md *MulticastDiscovery) listenOnDefaultInterface(ctx context.Context, addr *net.UDPAddr) error {
  129. conn, err := net.ListenMulticastUDP("udp4", nil, addr)
  130. if err != nil {
  131. return fmt.Errorf("failed to listen on default multicast interface: %w", err)
  132. }
  133. _ = conn.SetReadBuffer(2048)
  134. md.connsMu.Lock()
  135. md.conns = append(md.conns, conn)
  136. md.connsMu.Unlock()
  137. go md.listenLoop(ctx, conn)
  138. md.logger.Debugf("Multicast discovery listening on %s (default interface)", md.config.MulticastAddr)
  139. return nil
  140. }
  141. // Stop stops the multicast discovery and closes all listeners.
  142. func (md *MulticastDiscovery) Stop() {
  143. md.closed.Store(true)
  144. md.connsMu.Lock()
  145. for _, conn := range md.conns {
  146. conn.Close()
  147. }
  148. md.conns = nil
  149. md.connsMu.Unlock()
  150. }
  151. func (md *MulticastDiscovery) updateDevice(device *model.Device) {
  152. md.devicesMutex.Lock()
  153. md.devices[device.Fingerprint] = device
  154. md.devicesMutex.Unlock()
  155. if md.peerCache != nil {
  156. md.peerCache.Save(device)
  157. }
  158. md.handlersMu.RLock()
  159. handlers := make([]func(*model.Device), len(md.handlers))
  160. copy(handlers, md.handlers)
  161. md.handlersMu.RUnlock()
  162. for _, handler := range handlers {
  163. go handler(device)
  164. }
  165. }
  166. func (md *MulticastDiscovery) GetDevices() []*model.Device {
  167. md.devicesMutex.RLock()
  168. defer md.devicesMutex.RUnlock()
  169. devices := make([]*model.Device, 0, len(md.devices))
  170. for _, device := range md.devices {
  171. devices = append(devices, device)
  172. }
  173. return devices
  174. }
  175. func (md *MulticastDiscovery) SetDto(dto model.MulticastDto) {
  176. md.dto = dto
  177. }
  178. func (md *MulticastDiscovery) SetPeerCache(cache *PeerCache) {
  179. md.peerCache = cache
  180. }
  181. func getShortFingerprint(fingerprint string) string {
  182. if len(fingerprint) > 8 {
  183. return fingerprint[:8] + "..."
  184. }
  185. return fingerprint
  186. }