// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package mdns

import (
	
	
	
	
	
	
	

	
	
	
	
)

// Conn represents a mDNS Server
type Conn struct {
	mu   sync.RWMutex
	name string
	log  logging.LeveledLogger

	multicastPktConnV4 ipPacketConn
	multicastPktConnV6 ipPacketConn
	dstAddr4           *net.UDPAddr
	dstAddr6           *net.UDPAddr

	unicastPktConnV4 ipPacketConn
	unicastPktConnV6 ipPacketConn

	queryInterval time.Duration
	localNames    []string
	queries       []*query
	ifaces        map[int]netInterface

	closed chan interface{}
}

type query struct {
	nameWithSuffix  string
	queryResultChan chan queryResult
}

type queryResult struct {
	answer dnsmessage.ResourceHeader
	addr   netip.Addr
}

const (
	defaultQueryInterval = time.Second
	destinationAddress4  = "224.0.0.251:5353"
	destinationAddress6  = "[FF02::FB]:5353"
	maxMessageRecords    = 3
	responseTTL          = 120
	// maxPacketSize is the maximum size of a mdns packet.
	// From RFC 6762:
	// Even when fragmentation is used, a Multicast DNS packet, including IP
	// and UDP headers, MUST NOT exceed 9000 bytes.
	// https://datatracker.ietf.org/doc/html/rfc6762#section-17
	maxPacketSize = 9000
)

var (
	errNoPositiveMTUFound = errors.New("no positive MTU found")
	errNoPacketConn       = errors.New("must supply at least a multicast IPv4 or IPv6 PacketConn")
	errNoUsableInterfaces = errors.New("no usable interfaces found for mDNS")
	errFailedToClose      = errors.New("failed to close mDNS Conn")
)

type netInterface struct {
	net.Interface
	ipAddrs    []netip.Addr
	supportsV4 bool
	supportsV6 bool
}

// Server establishes a mDNS connection over an existing conn.
// Either one or both of the multicast packet conns should be provided.
// The presence of each IP type of PacketConn will dictate what kinds
// of questions are sent for queries. That is, if an ipv6.PacketConn is
// provided, then AAAA questions will be sent. A questions will only be
// sent if an ipv4.PacketConn is also provided. In the future, we may
// add a QueryAddr method that allows specifying this more clearly.
//
//nolint:gocognit
func (
	 *ipv4.PacketConn,
	 *ipv6.PacketConn,
	 *Config,
) (*Conn, error) {
	if  == nil {
		return nil, errNilConfig
	}
	 := .LoggerFactory
	if  == nil {
		 = logging.NewDefaultLoggerFactory()
	}
	 := .NewLogger("mdns")

	 := &Conn{
		queryInterval: defaultQueryInterval,
		log:           ,
		closed:        make(chan interface{}),
	}
	.name = .Name
	if .name == "" {
		.name = fmt.Sprintf("%p", &)
	}

	if  == nil &&  == nil {
		return nil, errNoPacketConn
	}

	 := .Interfaces
	if  == nil {
		var  error
		,  = net.Interfaces()
		if  != nil {
			return nil, 
		}
	}

	var  *ipv4.PacketConn
	{
		,  := net.ResolveUDPAddr("udp4", "0.0.0.0:0")
		if  != nil {
			return nil, 
		}

		,  := net.ListenUDP("udp4", )
		if  != nil {
			.Warnf("[%s] failed to listen on unicast IPv4 %s: %s; will not be able to receive unicast responses on IPv4", .name, , )
		} else {
			 = ipv4.NewPacketConn()
		}
	}

	var  *ipv6.PacketConn
	{
		,  := net.ResolveUDPAddr("udp6", "[::]:")
		if  != nil {
			return nil, 
		}

		,  := net.ListenUDP("udp6", )
		if  != nil {
			.Warnf("[%s] failed to listen on unicast IPv6 %s: %s; will not be able to receive unicast responses on IPv6", .name, , )
		} else {
			 = ipv6.NewPacketConn()
		}
	}

	 := net.IPv4(224, 0, 0, 251)
	 := &net.UDPAddr{IP: }

	// FF02::FB
	 := net.IP{0xff, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfb}
	 := &net.UDPAddr{IP: }

	 := 0
	 := 0
	 := make(map[int]netInterface, len())
	for  := range  {
		 := []
		if !.IncludeLoopback && .Flags&net.FlagLoopback == net.FlagLoopback {
			continue
		}
		if .Flags&net.FlagUp == 0 {
			continue
		}

		,  := .Addrs()
		if  != nil {
			continue
		}
		var ,  bool
		 := make([]netip.Addr, 0, len())
		for ,  := range  {
			var  net.IP
			switch addr := .(type) {
			case *net.IPNet:
				 = .IP
			case *net.IPAddr:
				 = .IP
			default:
				continue
			}

			,  := netip.AddrFromSlice()
			if ! {
				continue
			}
			if  != nil {
				// don't want mapping since we also support IPv4/A
				 = .Unmap()
			}
			 = addrWithOptionalZone(, .Name)

			if .Is6() && !.Is4In6() {
				 = true
			} else {
				// we'll claim we support v4 but defer if we send it or not
				// based on IPv4-to-IPv6 mapping rules later (search for Is4In6 below)
				 = true
			}
			 = append(, )
		}
		if !( || ) {
			continue
		}

		var  bool
		if  &&  != nil {
			if  := .JoinGroup(&, );  == nil {
				 = true
			}
		}
		if  &&  != nil {
			if  := .JoinGroup(&, );  == nil {
				 = true
			}
		}
		if ! {
			++
			continue
		}

		[.Index] = netInterface{
			Interface:  ,
			ipAddrs:    ,
			supportsV4: ,
			supportsV6: ,
		}
		if .MTU >  {
			 = .MTU
		}
	}

	if len() == 0 {
		return nil, errNoUsableInterfaces
	}
	if  == 0 {
		return nil, errNoPositiveMTUFound
	}
	if  > maxPacketSize {
		 = maxPacketSize
	}
	if  >= len() {
		return nil, errJoiningMulticastGroup
	}

	,  := net.ResolveUDPAddr("udp4", destinationAddress4)
	if  != nil {
		return nil, 
	}

	,  := net.ResolveUDPAddr("udp6", destinationAddress6)
	if  != nil {
		return nil, 
	}

	var  []string
	for ,  := range .LocalNames {
		 = append(, +".")
	}

	.dstAddr4 = 
	.dstAddr6 = 
	.localNames = 
	.ifaces = 

	if .QueryInterval != 0 {
		.queryInterval = .QueryInterval
	}

	if  != nil {
		if  := .SetControlMessage(ipv4.FlagInterface, true);  != nil {
			.log.Warnf("[%s] failed to SetControlMessage(ipv4.FlagInterface) on multicast IPv4 PacketConn %v", .name, )
		}
		if  := .SetControlMessage(ipv4.FlagDst, true);  != nil {
			.log.Warnf("[%s] failed to SetControlMessage(ipv4.FlagDst) on multicast IPv4 PacketConn %v", .name, )
		}
		.multicastPktConnV4 = ipPacketConn4{.name, , }
	}
	if  != nil {
		if  := .SetControlMessage(ipv6.FlagInterface, true);  != nil {
			.log.Warnf("[%s] failed to SetControlMessage(ipv6.FlagInterface) on multicast IPv6 PacketConn %v", .name, )
		}
		if  := .SetControlMessage(ipv6.FlagDst, true);  != nil {
			.log.Warnf("[%s] failed to SetControlMessage(ipv6.FlagInterface) on multicast IPv6 PacketConn %v", .name, )
		}
		.multicastPktConnV6 = ipPacketConn6{.name, , }
	}
	if  != nil {
		if  := .SetControlMessage(ipv4.FlagInterface, true);  != nil {
			.log.Warnf("[%s] failed to SetControlMessage(ipv4.FlagInterface) on unicast IPv4 PacketConn %v", .name, )
		}
		if  := .SetControlMessage(ipv4.FlagDst, true);  != nil {
			.log.Warnf("[%s] failed to SetControlMessage(ipv4.FlagInterface) on unicast IPv4 PacketConn %v", .name, )
		}
		.unicastPktConnV4 = ipPacketConn4{.name, , }
	}
	if  != nil {
		if  := .SetControlMessage(ipv6.FlagInterface, true);  != nil {
			.log.Warnf("[%s] failed to SetControlMessage(ipv6.FlagInterface) on unicast IPv6 PacketConn %v", .name, )
		}
		if  := .SetControlMessage(ipv6.FlagDst, true);  != nil {
			.log.Warnf("[%s] failed to SetControlMessage(ipv6.FlagInterface) on unicast IPv6 PacketConn %v", .name, )
		}
		.unicastPktConnV6 = ipPacketConn6{.name, , }
	}

	if .IncludeLoopback {
		// this is an efficient way for us to send ourselves a message faster instead of it going
		// further out into the network stack.
		if  != nil {
			if  := .SetMulticastLoopback(true);  != nil {
				.log.Warnf("[%s] failed to SetMulticastLoopback(true) on multicast IPv4 PacketConn %v; this may cause inefficient network path c.name,communications", .name, )
			}
		}
		if  != nil {
			if  := .SetMulticastLoopback(true);  != nil {
				.log.Warnf("[%s] failed to SetMulticastLoopback(true) on multicast IPv6 PacketConn %v; this may cause inefficient network path c.name,communications", .name, )
			}
		}
		if  != nil {
			if  := .SetMulticastLoopback(true);  != nil {
				.log.Warnf("[%s] failed to SetMulticastLoopback(true) on unicast IPv4 PacketConn %v; this may cause inefficient network path c.name,communications", .name, )
			}
		}
		if  != nil {
			if  := .SetMulticastLoopback(true);  != nil {
				.log.Warnf("[%s] failed to SetMulticastLoopback(true) on unicast IPv6 PacketConn %v; this may cause inefficient network path c.name,communications", .name, )
			}
		}
	}

	// https://www.rfc-editor.org/rfc/rfc6762.html#section-17
	// Multicast DNS messages carried by UDP may be up to the IP MTU of the
	// physical interface, less the space required for the IP header (20
	// bytes for IPv4; 40 bytes for IPv6) and the UDP header (8 bytes).
	 := make(chan struct{})
	go .start(, -20-8, )
	<-
	return , nil
}

// Close closes the mDNS Conn
func ( *Conn) () error {
	select {
	case <-.closed:
		return nil
	default:
	}

	// Once on go1.20, can use errors.Join
	var  []error
	if .multicastPktConnV4 != nil {
		if  := .multicastPktConnV4.Close();  != nil {
			 = append(, )
		}
	}

	if .multicastPktConnV6 != nil {
		if  := .multicastPktConnV6.Close();  != nil {
			 = append(, )
		}
	}

	if .unicastPktConnV4 != nil {
		if  := .unicastPktConnV4.Close();  != nil {
			 = append(, )
		}
	}

	if .unicastPktConnV6 != nil {
		if  := .unicastPktConnV6.Close();  != nil {
			 = append(, )
		}
	}

	if len() == 0 {
		<-.closed
		return nil
	}

	 := errFailedToClose
	for ,  := range  {
		 = fmt.Errorf("%w\n%w", , )
	}
	return 
}

// Query sends mDNS Queries for the following name until
// either the Context is canceled/expires or we get a result
//
// Deprecated: Use QueryAddr instead as it supports the easier to use netip.Addr.
func ( *Conn) ( context.Context,  string) (dnsmessage.ResourceHeader, net.Addr, error) {
	, ,  := .QueryAddr(, )
	if  != nil {
		return , nil, 
	}
	return , &net.IPAddr{
		IP:   .AsSlice(),
		Zone: .Zone(),
	}, nil
}

// QueryAddr sends mDNS Queries for the following name until
// either the Context is canceled/expires or we get a result
func ( *Conn) ( context.Context,  string) (dnsmessage.ResourceHeader, netip.Addr, error) {
	select {
	case <-.closed:
		return dnsmessage.ResourceHeader{}, netip.Addr{}, errConnectionClosed
	default:
	}

	 :=  + "."

	 := make(chan queryResult, 1)
	 := &query{, }
	.mu.Lock()
	.queries = append(.queries, )
	.mu.Unlock()

	defer func() {
		.mu.Lock()
		defer .mu.Unlock()
		for  := len(.queries) - 1;  >= 0; -- {
			if .queries[] ==  {
				.queries = append(.queries[:], .queries[+1:]...)
			}
		}
	}()

	 := time.NewTicker(.queryInterval)
	defer .Stop()

	.sendQuestion()
	for {
		select {
		case <-.C:
			.sendQuestion()
		case <-.closed:
			return dnsmessage.ResourceHeader{}, netip.Addr{}, errConnectionClosed
		case  := <-:
			// Given https://datatracker.ietf.org/doc/html/draft-ietf-mmusic-mdns-ice-candidates#section-3.2.2-2
			// An ICE agent SHOULD ignore candidates where the hostname resolution returns more than one IP address.
			//
			// We will take the first we receive which could result in a race between two suitable addresses where
			// one is better than the other (e.g. localhost vs LAN).
			return .answer, .addr, nil
		case <-.Done():
			return dnsmessage.ResourceHeader{}, netip.Addr{}, errContextElapsed
		}
	}
}

type ipToBytesError struct {
	addr         netip.Addr
	expectedType string
}

func ( ipToBytesError) () string {
	return fmt.Sprintf("ip (%s) is not %s", .addr, .expectedType)
}

// assumes ipv4-to-ipv6 mapping has been checked
func ipv4ToBytes( netip.Addr) ([4]byte, error) {
	if !.Is4() {
		return [4]byte{}, ipToBytesError{, "IPv4"}
	}

	,  := .MarshalBinary()
	if  != nil {
		return [4]byte{}, 
	}

	// net.IPs are stored in big endian / network byte order
	var  [4]byte
	copy([:], )
	return , nil
}

// assumes ipv4-to-ipv6 mapping has been checked
func ipv6ToBytes( netip.Addr) ([16]byte, error) {
	if !.Is6() {
		return [16]byte{}, ipToBytesError{, "IPv6"}
	}
	,  := .MarshalBinary()
	if  != nil {
		return [16]byte{}, 
	}

	// net.IPs are stored in big endian / network byte order
	var  [16]byte
	copy([:], )
	return , nil
}

type ipToAddrError struct {
	ip []byte
}

func ( ipToAddrError) () string {
	return fmt.Sprintf("failed to convert ip address '%s' to netip.Addr", .ip)
}

func interfaceForRemote( string) (*netip.Addr, error) {
	,  := net.Dial("udp", )
	if  != nil {
		return nil, 
	}

	,  := .LocalAddr().(*net.UDPAddr)
	if ! {
		return nil, errFailedCast
	}

	if  := .Close();  != nil {
		return nil, 
	}

	,  := netip.AddrFromSlice(.IP)
	if ! {
		return nil, ipToAddrError{.IP}
	}
	 = addrWithOptionalZone(, .Zone)
	return &, nil
}

type writeType byte

const (
	writeTypeQuestion writeType = iota
	writeTypeAnswer
)

func ( *Conn) ( string) {
	,  := dnsmessage.NewName()
	if  != nil {
		.log.Warnf("[%s] failed to construct mDNS packet %v", .name, )
		return
	}

	// https://datatracker.ietf.org/doc/html/draft-ietf-rtcweb-mdns-ice-candidates-04#section-3.2.1
	//
	// 2.  Otherwise, resolve the candidate using mDNS.  The ICE agent
	//     SHOULD set the unicast-response bit of the corresponding mDNS
	//     query message; this minimizes multicast traffic, as the response
	//     is probably only useful to the querying node.
	//
	// 18.12.  Repurposing of Top Bit of qclass in Question Section
	//
	// In the Question Section of a Multicast DNS query, the top bit of the
	// qclass field is used to indicate that unicast responses are preferred
	// for this particular question.  (See Section 5.4.)
	//
	// We'll follow this up sending on our unicast based packet connections so that we can
	// get a unicast response back.
	 := dnsmessage.Message{
		Header: dnsmessage.Header{},
	}

	// limit what we ask for based on what IPv is available. In the future,
	// this could be an option since there's no reason you cannot get an
	// A record on an IPv6 sourced question and vice versa.
	if .multicastPktConnV4 != nil {
		.Questions = append(.Questions, dnsmessage.Question{
			Type:  dnsmessage.TypeA,
			Class: dnsmessage.ClassINET | (1 << 15),
			Name:  ,
		})
	}
	if .multicastPktConnV6 != nil {
		.Questions = append(.Questions, dnsmessage.Question{
			Type:  dnsmessage.TypeAAAA,
			Class: dnsmessage.ClassINET | (1 << 15),
			Name:  ,
		})
	}

	,  := .Pack()
	if  != nil {
		.log.Warnf("[%s] failed to construct mDNS packet %v", .name, )
		return
	}

	.writeToSocket(-1, , false, false, writeTypeQuestion, nil)
}

//nolint:gocognit
func ( *Conn) (
	 int,
	 []byte,
	 bool,
	 bool,
	 writeType,
	 *net.UDPAddr,
) {
	var ,  net.Addr
	if  == writeTypeAnswer {
		if  == nil {
			 = .dstAddr4
			 = .dstAddr6
		} else {
			if .IP.To4() == nil {
				 = 
			} else {
				 = 
			}
		}
	}

	if  != -1 {
		if  == writeTypeQuestion {
			.log.Errorf("[%s] Unexpected question using specific interface index %d; dropping question", .name, )
			return
		}

		,  := .ifaces[]
		if ! {
			.log.Warnf("[%s] no interface for %d", .name, )
			return
		}
		if  && .Flags&net.FlagLoopback == 0 {
			// avoid accidentally tricking the destination that itself is the same as us
			.log.Debugf("[%s] interface is not loopback %d", .name, )
			return
		}

		.log.Debugf("[%s] writing answer to IPv4: %v, IPv6: %v", .name, , )

		if .supportsV4 && .multicastPktConnV4 != nil &&  != nil {
			if ! {
				if ,  := .multicastPktConnV4.WriteTo(, &.Interface, nil, );  != nil {
					.log.Warnf("[%s] failed to send mDNS packet on IPv4 interface %d: %v", .name, , )
				}
			} else {
				.log.Debugf("[%s] refusing to send mDNS packet with IPv6 zone over IPv4", .name)
			}
		}
		if .supportsV6 && .multicastPktConnV6 != nil &&  != nil {
			if ,  := .multicastPktConnV6.WriteTo(, &.Interface, nil, );  != nil {
				.log.Warnf("[%s] failed to send mDNS packet on IPv6 interface %d: %v", .name, , )
			}
		}

		return
	}
	for  := range .ifaces {
		 := .ifaces[]
		if  {
			.log.Debugf("[%s] Refusing to send loopback data with non-specific interface", .name)
			continue
		}

		if  == writeTypeQuestion {
			// we'll write via unicast if we can in case the responder chooses to respond to the address the request
			// came from (i.e. not respecting unicast-response bit). If we were to use the multicast packet
			// conn here, we'd be writing from a specific multicast address which won't be able to receive unicast
			// traffic (it only works when listening on 0.0.0.0/[::]).
			if .unicastPktConnV4 == nil && .unicastPktConnV6 == nil {
				.log.Debugf("[%s] writing question to multicast IPv4/6 %s", .name, .dstAddr4)
				if .supportsV4 && .multicastPktConnV4 != nil {
					if ,  := .multicastPktConnV4.WriteTo(, &.Interface, nil, .dstAddr4);  != nil {
						.log.Warnf("[%s] failed to send mDNS packet (multicast) on IPv4 interface %d: %v", .name, .Index, )
					}
				}
				if .supportsV6 && .multicastPktConnV6 != nil {
					if ,  := .multicastPktConnV6.WriteTo(, &.Interface, nil, .dstAddr6);  != nil {
						.log.Warnf("[%s] failed to send mDNS packet (multicast) on IPv6 interface %d: %v", .name, .Index, )
					}
				}
			}
			if .supportsV4 && .unicastPktConnV4 != nil {
				.log.Debugf("[%s] writing question to unicast IPv4 %s", .name, .dstAddr4)
				if ,  := .unicastPktConnV4.WriteTo(, &.Interface, nil, .dstAddr4);  != nil {
					.log.Warnf("[%s] failed to send mDNS packet (unicast) on interface %d: %v", .name, .Index, )
				}
			}
			if .supportsV6 && .unicastPktConnV6 != nil {
				.log.Debugf("[%s] writing question to unicast IPv6 %s", .name, .dstAddr6)
				if ,  := .unicastPktConnV6.WriteTo(, &.Interface, nil, .dstAddr6);  != nil {
					.log.Warnf("[%s] failed to send mDNS packet (unicast) on interface %d: %v", .name, .Index, )
				}
			}
		} else {
			.log.Debugf("[%s] writing answer to IPv4: %v, IPv6: %v", .name, , )

			if .supportsV4 && .multicastPktConnV4 != nil &&  != nil {
				if ! {
					if ,  := .multicastPktConnV4.WriteTo(, &.Interface, nil, );  != nil {
						.log.Warnf("[%s] failed to send mDNS packet (multicast) on IPv4 interface %d: %v", .name, , )
					}
				} else {
					.log.Debugf("[%s] refusing to send mDNS packet with IPv6 zone over IPv4", .name)
				}
			}
			if .supportsV6 && .multicastPktConnV6 != nil &&  != nil {
				if ,  := .multicastPktConnV6.WriteTo(, &.Interface, nil, );  != nil {
					.log.Warnf("[%s] failed to send mDNS packet (multicast) on IPv6 interface %d: %v", .name, , )
				}
			}
		}
	}
}

func createAnswer( uint16,  string,  netip.Addr) (dnsmessage.Message, error) {
	,  := dnsmessage.NewName()
	if  != nil {
		return dnsmessage.Message{}, 
	}

	 := dnsmessage.Message{
		Header: dnsmessage.Header{
			ID:            ,
			Response:      true,
			Authoritative: true,
		},
		Answers: []dnsmessage.Resource{
			{
				Header: dnsmessage.ResourceHeader{
					Class: dnsmessage.ClassINET,
					Name:  ,
					TTL:   responseTTL,
				},
			},
		},
	}

	if .Is4() {
		,  := ipv4ToBytes()
		if  != nil {
			return dnsmessage.Message{}, 
		}
		.Answers[0].Header.Type = dnsmessage.TypeA
		.Answers[0].Body = &dnsmessage.AResource{
			A: ,
		}
	} else if .Is6() {
		// we will lose the zone here, but the receiver can reconstruct it
		,  := ipv6ToBytes()
		if  != nil {
			return dnsmessage.Message{}, 
		}
		.Answers[0].Header.Type = dnsmessage.TypeAAAA
		.Answers[0].Body = &dnsmessage.AAAAResource{
			AAAA: ,
		}
	}

	return , nil
}

func ( *Conn) ( uint16,  string,  int,  netip.Addr,  *net.UDPAddr) {
	,  := createAnswer(, , )
	if  != nil {
		.log.Warnf("[%s] failed to create mDNS answer %v", .name, )
		return
	}

	,  := .Pack()
	if  != nil {
		.log.Warnf("[%s] failed to construct mDNS packet %v", .name, )
		return
	}

	.writeToSocket(
		,
		,
		.IsLoopback(),
		.Is6() && .Zone() != "",
		writeTypeAnswer,
		,
	)
}

type ipControlMessage struct {
	IfIndex int
	Dst     net.IP
}

type ipPacketConn interface {
	ReadFrom(b []byte) (n int, cm *ipControlMessage, src net.Addr, err error)
	WriteTo(b []byte, via *net.Interface, cm *ipControlMessage, dst net.Addr) (n int, err error)
	Close() error
}

type ipPacketConn4 struct {
	name string
	conn *ipv4.PacketConn
	log  logging.LeveledLogger
}

func ( ipPacketConn4) ( []byte) ( int,  *ipControlMessage,  net.Addr,  error) {
	, , ,  := .conn.ReadFrom()
	if  != nil ||  == nil {
		return , nil, , 
	}
	return , &ipControlMessage{IfIndex: .IfIndex, Dst: .Dst}, , 
}

func ( ipPacketConn4) ( []byte,  *net.Interface,  *ipControlMessage,  net.Addr) ( int,  error) {
	var  *ipv4.ControlMessage
	if  != nil {
		 = &ipv4.ControlMessage{
			IfIndex: .IfIndex,
		}
	}
	if  := .conn.SetMulticastInterface();  != nil {
		.log.Warnf("[%s] failed to set multicast interface for %d: %v", .name, .Index, )
		return 0, 
	}
	return .conn.WriteTo(, , )
}

func ( ipPacketConn4) () error {
	return .conn.Close()
}

type ipPacketConn6 struct {
	name string
	conn *ipv6.PacketConn
	log  logging.LeveledLogger
}

func ( ipPacketConn6) ( []byte) ( int,  *ipControlMessage,  net.Addr,  error) {
	, , ,  := .conn.ReadFrom()
	if  != nil ||  == nil {
		return , nil, , 
	}
	return , &ipControlMessage{IfIndex: .IfIndex, Dst: .Dst}, , 
}

func ( ipPacketConn6) ( []byte,  *net.Interface,  *ipControlMessage,  net.Addr) ( int,  error) {
	var  *ipv6.ControlMessage
	if  != nil {
		 = &ipv6.ControlMessage{
			IfIndex: .IfIndex,
		}
	}
	if  := .conn.SetMulticastInterface();  != nil {
		.log.Warnf("[%s] failed to set multicast interface for %d: %v", .name, .Index, )
		return 0, 
	}
	return .conn.WriteTo(, , )
}

func ( ipPacketConn6) () error {
	return .conn.Close()
}

func ( *Conn) ( string,  ipPacketConn,  int,  *Config) { //nolint:gocognit
	 := make([]byte, )
	 := dnsmessage.Parser{}

	for {
		, , ,  := .ReadFrom()
		if  != nil {
			if errors.Is(, net.ErrClosed) {
				return
			}
			.log.Warnf("[%s] failed to ReadFrom %q %v", .name, , )
			continue
		}
		.log.Debugf("[%s] got read on %s from %s", .name, , )

		var  int
		var  net.IP
		if  != nil {
			 = .IfIndex
			 = .Dst
		} else {
			 = -1
		}
		,  := .(*net.UDPAddr)
		if ! {
			.log.Warnf("[%s] expected source address %s to be UDP but got %", .name, , )
			continue
		}

		func() {
			,  := .Start([:])
			if  != nil {
				.log.Warnf("[%s] failed to parse mDNS packet %v", .name, )
				return
			}

			for  := 0;  <= maxMessageRecords; ++ {
				,  := .Question()
				if errors.Is(, dnsmessage.ErrSectionDone) {
					break
				} else if  != nil {
					.log.Warnf("[%s] failed to parse mDNS packet %v", .name, )
					return
				}

				if .Type != dnsmessage.TypeA && .Type != dnsmessage.TypeAAAA {
					continue
				}

				// https://datatracker.ietf.org/doc/html/rfc6762#section-6
				// The destination UDP port in all Multicast DNS responses MUST be 5353,
				// and the destination address MUST be the mDNS IPv4 link-local
				// multicast address 224.0.0.251 or its IPv6 equivalent FF02::FB, except
				// when generating a reply to a query that explicitly requested a
				// unicast response
				 := (.Class&(1<<15)) != 0 || // via the unicast-response bit
					.Port != 5353 || // by virtue of being a legacy query (Section 6.7), or
					(len() != 0 && !(.Equal(.dstAddr4.IP) || // by virtue of being a direct unicast query
						.Equal(.dstAddr6.IP)))
				var  *net.UDPAddr
				if  {
					 = 
				}

				 := .Type == dnsmessage.TypeA

				for ,  := range .localNames {
					if  == .Name.String() {
						var  *netip.Addr
						if .LocalAddress != nil {
							// this means the LocalAddress does not support link-local since
							// we have no zone to set here.
							,  := netip.AddrFromSlice(.LocalAddress)
							if ! {
								.log.Warnf("[%s] failed to convert config.LocalAddress '%s' to netip.Addr", .name, .LocalAddress)
								continue
							}
							if .multicastPktConnV4 != nil {
								// don't want mapping since we also support IPv4/A
								 = .Unmap()
							}
							 = &
						} else {
							// prefer the address of the interface if we know its index, but otherwise
							// derive it from the address we read from. We do this because even if
							// multicast loopback is in use or we send from a loopback interface,
							// there are still cases where the IP packet will contain the wrong
							// source IP (e.g. a LAN interface).
							// For example, we can have a packet that has:
							// Source: 192.168.65.3
							// Destination: 224.0.0.251
							// Interface Index: 1
							// Interface Addresses @ 1: [127.0.0.1/8 ::1/128]
							if  != -1 {
								,  := .ifaces[]
								if ! {
									.log.Warnf("[%s] no interface for %d", .name, )
									return
								}
								var  []netip.Addr
								for ,  := range .ipAddrs {
									 := 

									// match up respective IP types based on question
									if  {
										if .Is4In6() {
											// we may allow 4-in-6, but the question wants an A record
											 = .Unmap()
										}
										if !.Is4() {
											continue
										}
									} else { // queryWantsV6
										if !.Is6() {
											continue
										}
										if !isSupportedIPv6(, .multicastPktConnV4 == nil) {
											.log.Debugf("[%s] interface %d address not a supported IPv6 address %s", .name, , &)
											continue
										}
									}

									 = append(, )
								}
								if len() == 0 {
									.log.Debugf("[%s] failed to find suitable IP for interface %d; deriving address from source address c.name,instead", .name, )
								} else {
									// choose the best match
									var  *netip.Addr
									for ,  := range  {
										 := 
										if .Is4() {
											// select first
											 = &
											break
										}
										// we're okay with 4In6 for now but ideally we get a an actual IPv6.
										// Maybe in the future we never want this but it does look like Docker
										// can route IPv4 over IPv6.
										if  == nil {
											 = &
										} else if !.Is4In6() {
											 = &
										}
										if !.Is4In6() {
											break
										}
										// otherwise keep searching for an actual IPv6
									}
									 = 
								}
							}
							if  == -1 ||  == nil {
								,  = interfaceForRemote(.String())
								if  != nil {
									.log.Warnf("[%s] failed to get local interface to communicate with %s: %v", .name, .String(), )
									continue
								}
							}
						}
						if  {
							if !.Is4() {
								.log.Debugf("[%s] have IPv6 address %s to respond with but question is for A not c.name,AAAA", .name, )
								continue
							}
						} else {
							if !.Is6() {
								.log.Debugf("[%s] have IPv4 address %s to respond with but question is for AAAA not c.name,A", .name, )
								continue
							}
							if !isSupportedIPv6(*, .multicastPktConnV4 == nil) {
								.log.Debugf("[%s] got local interface address but not a supported IPv6 address %v", .name, )
								continue
							}
						}

						if  != nil && len(.IP) == net.IPv4len &&
							.Is6() &&
							.Zone() != "" &&
							(.IsLinkLocalUnicast() || .IsLinkLocalMulticast()) {
							// This case happens when multicast v4 picks up an AAAA question that has a zone
							// in the address. Since we cannot send this zone over DNS (it's meaningless),
							// the other side can only infer this via the response interface on the other
							// side (some IPv6 interface).
							.log.Debugf("[%s] refusing to send link-local address %s to an IPv4 destination %s", .name, , )
							continue
						}
						.log.Debugf("[%s] sending response for %s on ifc %d of %s to %s", .name, .Name, , *, )
						.sendAnswer(.ID, .Name.String(), , *, )
					}
				}
			}

			for  := 0;  <= maxMessageRecords; ++ {
				,  := .AnswerHeader()
				if errors.Is(, dnsmessage.ErrSectionDone) {
					return
				}
				if  != nil {
					.log.Warnf("[%s] failed to parse mDNS packet %v", .name, )
					return
				}

				if .Type != dnsmessage.TypeA && .Type != dnsmessage.TypeAAAA {
					continue
				}

				.mu.Lock()
				 := make([]*query, len(.queries))
				copy(, .queries)
				.mu.Unlock()

				var  []*query
				for ,  := range  {
					 := 
					if .nameWithSuffix == .Name.String() {
						,  := addrFromAnswerHeader(, )
						if  != nil {
							.log.Warnf("[%s] failed to parse mDNS answer %v", .name, )
							return
						}

						 := *
						// DNS records don't contain IPv6 zones.
						// We're trusting that since we're on the same link, that we will only
						// be sent link-local addresses from that source's interface's address.
						// If it's not present, we're out of luck since we cannot rely on the
						// interface zone to be the same as the source's.
						 = addrWithOptionalZone(, .Zone)

						select {
						case .queryResultChan <- queryResult{, }:
							 = append(, )
						default:
						}
					}
				}

				.mu.Lock()
				for  := len(.queries) - 1;  >= 0; -- {
					for  := len() - 1;  >= 0; -- {
						if .queries[] == [] {
							.queries = append(.queries[:], .queries[+1:]...)
							 = append([:], [+1:]...)
							--
							break
						}
					}
				}
				.mu.Unlock()
			}
		}()
	}
}

func ( *Conn) ( chan<- struct{},  int,  *Config) {
	defer func() {
		.mu.Lock()
		defer .mu.Unlock()
		close(.closed)
	}()

	var  int
	 := make(chan struct{})
	 := make(chan struct{})

	if .multicastPktConnV4 != nil {
		++
		go func() {
			defer func() {
				 <- struct{}{}
			}()
			 <- struct{}{}
			.readLoop("multi4", .multicastPktConnV4, , )
		}()
	}
	if .multicastPktConnV6 != nil {
		++
		go func() {
			defer func() {
				 <- struct{}{}
			}()
			 <- struct{}{}
			.readLoop("multi6", .multicastPktConnV6, , )
		}()
	}
	if .unicastPktConnV4 != nil {
		++
		go func() {
			defer func() {
				 <- struct{}{}
			}()
			 <- struct{}{}
			.readLoop("uni4", .unicastPktConnV4, , )
		}()
	}
	if .unicastPktConnV6 != nil {
		++
		go func() {
			defer func() {
				 <- struct{}{}
			}()
			 <- struct{}{}
			.readLoop("uni6", .unicastPktConnV6, , )
		}()
	}
	for  := 0;  < ; ++ {
		<-
	}
	close()
	for  := 0;  < ; ++ {
		<-
	}
}

func addrFromAnswerHeader( dnsmessage.ResourceHeader,  dnsmessage.Parser) ( *netip.Addr,  error) {
	if .Type == dnsmessage.TypeA {
		,  := .AResource()
		if  != nil {
			return nil, 
		}
		,  := netip.AddrFromSlice(.A[:])
		if ! {
			return nil, fmt.Errorf("failed to convert A record: %w", ipToAddrError{.A[:]})
		}
		 = .Unmap() // do not want 4-in-6
		 = &
	} else {
		,  := .AAAAResource()
		if  != nil {
			return nil, 
		}
		,  := netip.AddrFromSlice(.AAAA[:])
		if ! {
			return nil, fmt.Errorf("failed to convert AAAA record: %w", ipToAddrError{.AAAA[:]})
		}
		 = &
	}

	return
}

func isSupportedIPv6( netip.Addr,  bool) bool {
	if !.Is6() {
		return false
	}
	// IPv4-mapped-IPv6 addresses cannot be connected to unless
	// unmapped.
	if ! && .Is4In6() {
		return false
	}
	return true
}

func addrWithOptionalZone( netip.Addr,  string) netip.Addr {
	if  == "" {
		return 
	}
	if .Is6() && (.IsLinkLocalUnicast() || .IsLinkLocalMulticast()) {
		return .WithZone()
	}
	return 
}