go-micro.dev/v5@v5.12.0/util/mdns/server.go (about) 1 package mdns 2 3 import ( 4 "fmt" 5 "math/rand" 6 "net" 7 "sync" 8 "sync/atomic" 9 "time" 10 11 "github.com/miekg/dns" 12 log "go-micro.dev/v5/logger" 13 "golang.org/x/net/ipv4" 14 "golang.org/x/net/ipv6" 15 ) 16 17 var ( 18 mdnsGroupIPv4 = net.ParseIP("224.0.0.251") 19 mdnsGroupIPv6 = net.ParseIP("ff02::fb") 20 21 // mDNS wildcard addresses. 22 mdnsWildcardAddrIPv4 = &net.UDPAddr{ 23 IP: net.ParseIP("224.0.0.0"), 24 Port: 5353, 25 } 26 mdnsWildcardAddrIPv6 = &net.UDPAddr{ 27 IP: net.ParseIP("ff02::"), 28 Port: 5353, 29 } 30 31 // mDNS endpoint addresses. 32 ipv4Addr = &net.UDPAddr{ 33 IP: mdnsGroupIPv4, 34 Port: 5353, 35 } 36 ipv6Addr = &net.UDPAddr{ 37 IP: mdnsGroupIPv6, 38 Port: 5353, 39 } 40 ) 41 42 // GetMachineIP is a func which returns the outbound IP of this machine. 43 // Used by the server to determine whether to attempt send the response on a local address. 44 type GetMachineIP func() net.IP 45 46 // Config is used to configure the mDNS server. 47 type Config struct { 48 // Zone must be provided to support responding to queries 49 Zone Zone 50 51 // Iface if provided binds the multicast listener to the given 52 // interface. If not provided, the system default multicase interface 53 // is used. 54 Iface *net.Interface 55 56 // GetMachineIP is a function to return the IP of the local machine 57 GetMachineIP GetMachineIP 58 59 // Port If it is not 0, replace the port 5353 with this port number. 60 Port int 61 62 // LocalhostChecking if enabled asks the server to also send responses to 0.0.0.0 if the target IP 63 // is this host (as defined by GetMachineIP). Useful in case machine is on a VPN which blocks comms on non standard ports 64 LocalhostChecking bool 65 } 66 67 // Server is an mDNS server used to listen for mDNS queries and respond if we 68 // have a matching local record. 69 type Server struct { 70 config *Config 71 72 ipv4List *net.UDPConn 73 ipv6List *net.UDPConn 74 75 shutdownCh chan struct{} 76 77 outboundIP net.IP 78 wg sync.WaitGroup 79 80 shutdownLock sync.Mutex 81 82 shutdown bool 83 } 84 85 // NewServer is used to create a new mDNS server from a config. 86 func NewServer(config *Config) (*Server, error) { 87 setCustomPort(config.Port) 88 89 // Create the listeners 90 // Create wildcard connections (because :5353 can be already taken by other apps) 91 ipv4List, _ := net.ListenUDP("udp4", mdnsWildcardAddrIPv4) 92 ipv6List, _ := net.ListenUDP("udp6", mdnsWildcardAddrIPv6) 93 if ipv4List == nil && ipv6List == nil { 94 return nil, fmt.Errorf("[ERR] mdns: Failed to bind to any udp port!") 95 } 96 97 if ipv4List == nil { 98 ipv4List = &net.UDPConn{} 99 } 100 if ipv6List == nil { 101 ipv6List = &net.UDPConn{} 102 } 103 104 // Join multicast groups to receive announcements 105 p1 := ipv4.NewPacketConn(ipv4List) 106 p2 := ipv6.NewPacketConn(ipv6List) 107 p1.SetMulticastLoopback(true) 108 p2.SetMulticastLoopback(true) 109 110 if config.Iface != nil { 111 if err := p1.JoinGroup(config.Iface, &net.UDPAddr{IP: mdnsGroupIPv4}); err != nil { 112 return nil, err 113 } 114 if err := p2.JoinGroup(config.Iface, &net.UDPAddr{IP: mdnsGroupIPv6}); err != nil { 115 return nil, err 116 } 117 } else { 118 ifaces, err := net.Interfaces() 119 if err != nil { 120 return nil, err 121 } 122 errCount1, errCount2 := 0, 0 123 for _, iface := range ifaces { 124 if err := p1.JoinGroup(&iface, &net.UDPAddr{IP: mdnsGroupIPv4}); err != nil { 125 errCount1++ 126 } 127 if err := p2.JoinGroup(&iface, &net.UDPAddr{IP: mdnsGroupIPv6}); err != nil { 128 errCount2++ 129 } 130 } 131 if len(ifaces) == errCount1 && len(ifaces) == errCount2 { 132 return nil, fmt.Errorf("Failed to join multicast group on all interfaces!") 133 } 134 } 135 136 ipFunc := getOutboundIP 137 if config.GetMachineIP != nil { 138 ipFunc = config.GetMachineIP 139 } 140 141 s := &Server{ 142 config: config, 143 ipv4List: ipv4List, 144 ipv6List: ipv6List, 145 shutdownCh: make(chan struct{}), 146 outboundIP: ipFunc(), 147 } 148 149 go s.recv(s.ipv4List) 150 go s.recv(s.ipv6List) 151 152 s.wg.Add(1) 153 go s.probe() 154 155 return s, nil 156 } 157 158 // Shutdown is used to shutdown the listener. 159 func (s *Server) Shutdown() error { 160 s.shutdownLock.Lock() 161 defer s.shutdownLock.Unlock() 162 163 if s.shutdown { 164 return nil 165 } 166 167 s.shutdown = true 168 close(s.shutdownCh) 169 s.unregister() 170 171 if s.ipv4List != nil { 172 s.ipv4List.Close() 173 } 174 if s.ipv6List != nil { 175 s.ipv6List.Close() 176 } 177 178 s.wg.Wait() 179 return nil 180 } 181 182 // recv is a long running routine to receive packets from an interface. 183 func (s *Server) recv(c *net.UDPConn) { 184 if c == nil { 185 return 186 } 187 buf := make([]byte, 65536) 188 for { 189 s.shutdownLock.Lock() 190 if s.shutdown { 191 s.shutdownLock.Unlock() 192 return 193 } 194 s.shutdownLock.Unlock() 195 n, from, err := c.ReadFrom(buf) 196 if err != nil { 197 continue 198 } 199 if err := s.parsePacket(buf[:n], from); err != nil { 200 log.Errorf("[ERR] mdns: Failed to handle query: %v", err) 201 } 202 } 203 } 204 205 // parsePacket is used to parse an incoming packet. 206 func (s *Server) parsePacket(packet []byte, from net.Addr) error { 207 var msg dns.Msg 208 if err := msg.Unpack(packet); err != nil { 209 log.Errorf("[ERR] mdns: Failed to unpack packet: %v", err) 210 return err 211 } 212 // TODO: This is a bit of a hack 213 // We decided to ignore some mDNS answers for the time being 214 // See: https://tools.ietf.org/html/rfc6762#section-7.2 215 msg.Truncated = false 216 return s.handleQuery(&msg, from) 217 } 218 219 // handleQuery is used to handle an incoming query. 220 func (s *Server) handleQuery(query *dns.Msg, from net.Addr) error { 221 if query.Opcode != dns.OpcodeQuery { 222 // "In both multicast query and multicast response messages, the OPCODE MUST 223 // be zero on transmission (only standard queries are currently supported 224 // over multicast). Multicast DNS messages received with an OPCODE other 225 // than zero MUST be silently ignored." Note: OpcodeQuery == 0 226 return fmt.Errorf("mdns: received query with non-zero Opcode %v: %v", query.Opcode, *query) 227 } 228 if query.Rcode != 0 { 229 // "In both multicast query and multicast response messages, the Response 230 // Code MUST be zero on transmission. Multicast DNS messages received with 231 // non-zero Response Codes MUST be silently ignored." 232 return fmt.Errorf("mdns: received query with non-zero Rcode %v: %v", query.Rcode, *query) 233 } 234 235 // TODO(reddaly): Handle "TC (Truncated) Bit": 236 // In query messages, if the TC bit is set, it means that additional 237 // Known-Answer records may be following shortly. A responder SHOULD 238 // record this fact, and wait for those additional Known-Answer records, 239 // before deciding whether to respond. If the TC bit is clear, it means 240 // that the querying host has no additional Known Answers. 241 if query.Truncated { 242 return fmt.Errorf("[ERR] mdns: support for DNS requests with high truncated bit not implemented: %v", *query) 243 } 244 245 var unicastAnswer, multicastAnswer []dns.RR 246 247 // Handle each question 248 for _, q := range query.Question { 249 mrecs, urecs := s.handleQuestion(q) 250 multicastAnswer = append(multicastAnswer, mrecs...) 251 unicastAnswer = append(unicastAnswer, urecs...) 252 } 253 254 // See section 18 of RFC 6762 for rules about DNS headers. 255 resp := func(unicast bool) *dns.Msg { 256 // 18.1: ID (Query Identifier) 257 // 0 for multicast response, query.Id for unicast response 258 id := uint16(0) 259 if unicast { 260 id = query.Id 261 } 262 263 var answer []dns.RR 264 if unicast { 265 answer = unicastAnswer 266 } else { 267 answer = multicastAnswer 268 } 269 if len(answer) == 0 { 270 return nil 271 } 272 273 return &dns.Msg{ 274 MsgHdr: dns.MsgHdr{ 275 Id: id, 276 277 // 18.2: QR (Query/Response) Bit - must be set to 1 in response. 278 Response: true, 279 280 // 18.3: OPCODE - must be zero in response (OpcodeQuery == 0) 281 Opcode: dns.OpcodeQuery, 282 283 // 18.4: AA (Authoritative Answer) Bit - must be set to 1 284 Authoritative: true, 285 286 // The following fields must all be set to 0: 287 // 18.5: TC (TRUNCATED) Bit 288 // 18.6: RD (Recursion Desired) Bit 289 // 18.7: RA (Recursion Available) Bit 290 // 18.8: Z (Zero) Bit 291 // 18.9: AD (Authentic Data) Bit 292 // 18.10: CD (Checking Disabled) Bit 293 // 18.11: RCODE (Response Code) 294 }, 295 // 18.12 pertains to questions (handled by handleQuestion) 296 // 18.13 pertains to resource records (handled by handleQuestion) 297 298 // 18.14: Name Compression - responses should be compressed (though see 299 // caveats in the RFC), so set the Compress bit (part of the dns library 300 // API, not part of the DNS packet) to true. 301 Compress: true, 302 Question: query.Question, 303 Answer: answer, 304 } 305 } 306 307 if mresp := resp(false); mresp != nil { 308 if err := s.sendResponse(mresp, from); err != nil { 309 return fmt.Errorf("mdns: error sending multicast response: %v", err) 310 } 311 } 312 if uresp := resp(true); uresp != nil { 313 if err := s.sendResponse(uresp, from); err != nil { 314 return fmt.Errorf("mdns: error sending unicast response: %v", err) 315 } 316 } 317 return nil 318 } 319 320 // handleQuestion is used to handle an incoming question 321 // 322 // The response to a question may be transmitted over multicast, unicast, or 323 // both. The return values are DNS records for each transmission type. 324 func (s *Server) handleQuestion(q dns.Question) (multicastRecs, unicastRecs []dns.RR) { 325 records := s.config.Zone.Records(q) 326 if len(records) == 0 { 327 return nil, nil 328 } 329 330 // Handle unicast and multicast responses. 331 // TODO(reddaly): The decision about sending over unicast vs. multicast is not 332 // yet fully compliant with RFC 6762. For example, the unicast bit should be 333 // ignored if the records in question are close to TTL expiration. For now, 334 // we just use the unicast bit to make the decision, as per the spec: 335 // RFC 6762, section 18.12. Repurposing of Top Bit of qclass in Question 336 // Section 337 // 338 // In the Question Section of a Multicast DNS query, the top bit of the 339 // qclass field is used to indicate that unicast responses are preferred 340 // for this particular question. (See Section 5.4.) 341 if q.Qclass&(1<<15) != 0 { 342 return nil, records 343 } 344 return records, nil 345 } 346 347 func (s *Server) probe() { 348 defer s.wg.Done() 349 350 sd, ok := s.config.Zone.(*MDNSService) 351 if !ok { 352 return 353 } 354 355 name := fmt.Sprintf("%s.%s.%s.", sd.Instance, trimDot(sd.Service), trimDot(sd.Domain)) 356 357 q := new(dns.Msg) 358 q.SetQuestion(name, dns.TypePTR) 359 q.RecursionDesired = false 360 361 srv := &dns.SRV{ 362 Hdr: dns.RR_Header{ 363 Name: name, 364 Rrtype: dns.TypeSRV, 365 Class: dns.ClassINET, 366 Ttl: defaultTTL, 367 }, 368 Priority: 0, 369 Weight: 0, 370 Port: uint16(sd.Port), 371 Target: sd.HostName, 372 } 373 txt := &dns.TXT{ 374 Hdr: dns.RR_Header{ 375 Name: name, 376 Rrtype: dns.TypeTXT, 377 Class: dns.ClassINET, 378 Ttl: defaultTTL, 379 }, 380 Txt: sd.TXT, 381 } 382 q.Ns = []dns.RR{srv, txt} 383 384 randomizer := rand.New(rand.NewSource(time.Now().UnixNano())) 385 386 for i := 0; i < 3; i++ { 387 if err := s.SendMulticast(q); err != nil { 388 log.Errorf("[ERR] mdns: failed to send probe:", err.Error()) 389 } 390 time.Sleep(time.Duration(randomizer.Intn(250)) * time.Millisecond) 391 } 392 393 resp := new(dns.Msg) 394 resp.MsgHdr.Response = true 395 396 // set for query 397 q.SetQuestion(name, dns.TypeANY) 398 399 resp.Answer = append(resp.Answer, s.config.Zone.Records(q.Question[0])...) 400 401 // reset 402 q.SetQuestion(name, dns.TypePTR) 403 404 // From RFC6762 405 // The Multicast DNS responder MUST send at least two unsolicited 406 // responses, one second apart. To provide increased robustness against 407 // packet loss, a responder MAY send up to eight unsolicited responses, 408 // provided that the interval between unsolicited responses increases by 409 // at least a factor of two with every response sent. 410 timeout := 1 * time.Second 411 timer := time.NewTimer(timeout) 412 for i := 0; i < 3; i++ { 413 if err := s.SendMulticast(resp); err != nil { 414 log.Errorf("[ERR] mdns: failed to send announcement:", err.Error()) 415 } 416 select { 417 case <-timer.C: 418 timeout *= 2 419 timer.Reset(timeout) 420 case <-s.shutdownCh: 421 timer.Stop() 422 return 423 } 424 } 425 } 426 427 // SendMulticast us used to send a multicast response packet. 428 func (s *Server) SendMulticast(msg *dns.Msg) error { 429 buf, err := msg.Pack() 430 if err != nil { 431 return err 432 } 433 if s.ipv4List != nil { 434 s.ipv4List.WriteToUDP(buf, ipv4Addr) 435 } 436 if s.ipv6List != nil { 437 s.ipv6List.WriteToUDP(buf, ipv6Addr) 438 } 439 return nil 440 } 441 442 // sendResponse is used to send a response packet. 443 func (s *Server) sendResponse(resp *dns.Msg, from net.Addr) error { 444 // TODO(reddaly): Respect the unicast argument, and allow sending responses 445 // over multicast. 446 buf, err := resp.Pack() 447 if err != nil { 448 return err 449 } 450 451 // Determine the socket to send from 452 addr := from.(*net.UDPAddr) 453 conn := s.ipv4List 454 backupTarget := net.IPv4zero 455 456 if addr.IP.To4() == nil { 457 conn = s.ipv6List 458 backupTarget = net.IPv6zero 459 } 460 _, err = conn.WriteToUDP(buf, addr) 461 // If the address we're responding to is this machine then we can also attempt sending on 0.0.0.0 462 // This covers the case where this machine is using a VPN and certain ports are blocked so the response never gets there 463 // Sending two responses is OK 464 if s.config.LocalhostChecking && addr.IP.Equal(s.outboundIP) { 465 // ignore any errors, this is best efforts 466 conn.WriteToUDP(buf, &net.UDPAddr{IP: backupTarget, Port: addr.Port}) 467 } 468 return err 469 } 470 471 func (s *Server) unregister() error { 472 sd, ok := s.config.Zone.(*MDNSService) 473 if !ok { 474 return nil 475 } 476 477 atomic.StoreUint32(&sd.TTL, 0) 478 name := fmt.Sprintf("%s.%s.%s.", sd.Instance, trimDot(sd.Service), trimDot(sd.Domain)) 479 480 q := new(dns.Msg) 481 q.SetQuestion(name, dns.TypeANY) 482 483 resp := new(dns.Msg) 484 resp.MsgHdr.Response = true 485 resp.Answer = append(resp.Answer, s.config.Zone.Records(q.Question[0])...) 486 487 return s.SendMulticast(resp) 488 } 489 490 func setCustomPort(port int) { 491 if port != 0 { 492 if mdnsWildcardAddrIPv4.Port != port { 493 mdnsWildcardAddrIPv4.Port = port 494 } 495 if mdnsWildcardAddrIPv6.Port != port { 496 mdnsWildcardAddrIPv6.Port = port 497 } 498 if ipv4Addr.Port != port { 499 ipv4Addr.Port = port 500 } 501 if ipv6Addr.Port != port { 502 ipv6Addr.Port = port 503 } 504 } 505 } 506 507 func getOutboundIP() net.IP { 508 conn, err := net.Dial("udp", "8.8.8.8:80") 509 if err != nil { 510 // no net connectivity maybe so fallback 511 return nil 512 } 513 defer conn.Close() 514 515 localAddr := conn.LocalAddr().(*net.UDPAddr) 516 517 return localAddr.IP 518 }