announce.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. package discovery
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "net"
  7. "strings"
  8. "time"
  9. "TengTuToolbox/localsend/model"
  10. "TengTuToolbox/localsend/network"
  11. )
  12. // multicastBurstCount is the number of rapid UDP multicast bursts sent.
  13. const multicastBurstCount = 3
  14. // multicastBurstInterval is the delay between consecutive bursts.
  15. const multicastBurstInterval = 30 * time.Millisecond
  16. // SendDiscoveryAnnouncement sends a multicast announcement
  17. func (md *MulticastDiscovery) SendDiscoveryAnnouncement() error {
  18. announcementDto := md.dto
  19. announcementDto.Announce = true
  20. data, err := json.Marshal(announcementDto)
  21. if err != nil {
  22. return fmt.Errorf("failed to marshal announcement: %w", err)
  23. }
  24. addr, err := net.ResolveUDPAddr("udp4", md.config.MulticastAddr)
  25. if err != nil {
  26. return fmt.Errorf("failed to resolve multicast address: %w", err)
  27. }
  28. var localAddr *net.UDPAddr
  29. if md.config.InterfaceName != "" {
  30. iface, err := network.InterfaceByName(md.config.InterfaceName)
  31. if err == nil {
  32. addrs, err := iface.Addrs()
  33. if err == nil {
  34. for _, a := range addrs {
  35. if ipnet, ok := a.(*net.IPNet); ok && ipnet.IP.To4() != nil {
  36. localAddr = &net.UDPAddr{IP: ipnet.IP}
  37. break
  38. }
  39. }
  40. }
  41. }
  42. }
  43. conn, err := net.DialUDP("udp4", localAddr, addr)
  44. if err != nil {
  45. return fmt.Errorf("failed to create UDP connection: %w", err)
  46. }
  47. defer conn.Close()
  48. for burst := 0; burst < multicastBurstCount; burst++ {
  49. _, err = conn.Write(data)
  50. if err != nil {
  51. return fmt.Errorf("failed to send multicast announcement (burst %d): %w", burst, err)
  52. }
  53. time.Sleep(multicastBurstInterval)
  54. }
  55. md.logger.Debugf("Sent %d multicast announcement bursts as %s (fingerprint: %s) to %s",
  56. multicastBurstCount, md.dto.Alias, getShortFingerprint(md.dto.Fingerprint), md.config.MulticastAddr)
  57. return nil
  58. }
  59. // SendDiscoveryResponse sends a response to a specific address
  60. func (md *MulticastDiscovery) SendDiscoveryResponse(targetAddr *net.UDPAddr, targetDevice *model.Device) error {
  61. // 1. Try HTTP Response first — 向对端回发注册信息,完成"互相发现"。
  62. // 失败时回退到 UDP 组播响应(见下方 fallback)。
  63. if targetDevice != nil {
  64. ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
  65. defer cancel()
  66. scheme := "http"
  67. if targetDevice.Protocol == model.ProtocolTypeHTTPS {
  68. scheme = "https"
  69. }
  70. reg := model.RegisterDto{
  71. Alias: md.dto.Alias,
  72. Version: md.dto.Version,
  73. DeviceModel: md.dto.DeviceModel,
  74. DeviceType: md.dto.DeviceType,
  75. Fingerprint: md.dto.Fingerprint,
  76. Port: md.dto.Port,
  77. Protocol: md.dto.Protocol,
  78. Download: md.dto.Download,
  79. }
  80. _, err := RegisterWithDevice(ctx, net.ParseIP(targetDevice.IP), targetDevice.Port, scheme, reg)
  81. if err == nil {
  82. md.logger.Debugf("Sent discovery response via HTTP to %s:%d", targetDevice.IP, targetDevice.Port)
  83. return nil
  84. }
  85. md.logger.Debugf("HTTP register to %s:%d failed (%v), falling back to UDP multicast", targetDevice.IP, targetDevice.Port, err)
  86. }
  87. // 2. Fallback to UDP — send via multicast so every listener sees the response
  88. responseDto := md.dto
  89. responseDto.Announce = false
  90. data, err := json.Marshal(responseDto)
  91. if err != nil {
  92. return fmt.Errorf("failed to marshal response: %w", err)
  93. }
  94. respAddr, err := net.ResolveUDPAddr("udp4", md.config.MulticastAddr)
  95. if err != nil {
  96. return fmt.Errorf("failed to resolve multicast address: %w", err)
  97. }
  98. conn, err := net.DialUDP("udp4", nil, respAddr)
  99. if err != nil {
  100. return fmt.Errorf("failed to create UDP connection: %w", err)
  101. }
  102. defer conn.Close()
  103. _, err = conn.Write(data)
  104. if err != nil {
  105. return fmt.Errorf("failed to send discovery response: %w", err)
  106. }
  107. md.logger.Debugf("Sent discovery response via multicast to %s", md.config.MulticastAddr)
  108. return nil
  109. }
  110. func (md *MulticastDiscovery) listenLoop(ctx context.Context, conn net.PacketConn) {
  111. buffer := make([]byte, 2048)
  112. for {
  113. select {
  114. case <-ctx.Done():
  115. return
  116. default:
  117. }
  118. if md.closed.Load() {
  119. return
  120. }
  121. if err := conn.SetReadDeadline(time.Now().Add(md.config.ListenTimeout)); err != nil {
  122. md.logger.Warnf("Failed to set read deadline: %v", err)
  123. }
  124. n, addr, err := conn.ReadFrom(buffer)
  125. if err != nil {
  126. if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
  127. continue
  128. }
  129. if strings.Contains(err.Error(), "use of closed network connection") {
  130. return
  131. }
  132. continue
  133. }
  134. if err := md.handlePacket(buffer[:n], addr); err != nil {
  135. md.logger.Warnf("Failed to handle multicast packet: %v", err)
  136. }
  137. }
  138. }
  139. func (md *MulticastDiscovery) handlePacket(data []byte, addr net.Addr) error {
  140. var dto model.MulticastDto
  141. if err := json.Unmarshal(data, &dto); err != nil {
  142. return fmt.Errorf("failed to unmarshal packet: %w", err)
  143. }
  144. if dto.Fingerprint == md.dto.Fingerprint {
  145. return nil
  146. }
  147. udpAddr, ok := addr.(*net.UDPAddr)
  148. if !ok {
  149. return fmt.Errorf("unexpected address type: %T", addr)
  150. }
  151. device := model.FromMulticastDto(dto, udpAddr.IP)
  152. md.logger.Debugf("Discovered raw device via multicast: %s (%s) at %s:%d",
  153. device.Alias, getShortFingerprint(device.Fingerprint), device.IP, device.Port)
  154. md.updateDevice(device)
  155. if dto.Announce {
  156. if err := md.SendDiscoveryResponse(udpAddr, device); err != nil {
  157. md.logger.Warnf("Failed to send discovery response: %v", err)
  158. }
  159. }
  160. return nil
  161. }