package mdns
import (
"context"
"errors"
"fmt"
"net"
"net/netip"
"sync"
"time"
"github.com/pion/logging"
"golang.org/x/net/dns/dnsmessage"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
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 = 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
}
func Server (
multicastPktConnV4 *ipv4 .PacketConn ,
multicastPktConnV6 *ipv6 .PacketConn ,
config *Config ,
) (*Conn , error ) {
if config == nil {
return nil , errNilConfig
}
loggerFactory := config .LoggerFactory
if loggerFactory == nil {
loggerFactory = logging .NewDefaultLoggerFactory ()
}
log := loggerFactory .NewLogger ("mdns" )
c := &Conn {
queryInterval : defaultQueryInterval ,
log : log ,
closed : make (chan interface {}),
}
c .name = config .Name
if c .name == "" {
c .name = fmt .Sprintf ("%p" , &c )
}
if multicastPktConnV4 == nil && multicastPktConnV6 == nil {
return nil , errNoPacketConn
}
ifaces := config .Interfaces
if ifaces == nil {
var err error
ifaces , err = net .Interfaces ()
if err != nil {
return nil , err
}
}
var unicastPktConnV4 *ipv4 .PacketConn
{
addr4 , err := net .ResolveUDPAddr ("udp4" , "0.0.0.0:0" )
if err != nil {
return nil , err
}
unicastConnV4 , err := net .ListenUDP ("udp4" , addr4 )
if err != nil {
log .Warnf ("[%s] failed to listen on unicast IPv4 %s: %s; will not be able to receive unicast responses on IPv4" , c .name , addr4 , err )
} else {
unicastPktConnV4 = ipv4 .NewPacketConn (unicastConnV4 )
}
}
var unicastPktConnV6 *ipv6 .PacketConn
{
addr6 , err := net .ResolveUDPAddr ("udp6" , "[::]:" )
if err != nil {
return nil , err
}
unicastConnV6 , err := net .ListenUDP ("udp6" , addr6 )
if err != nil {
log .Warnf ("[%s] failed to listen on unicast IPv6 %s: %s; will not be able to receive unicast responses on IPv6" , c .name , addr6 , err )
} else {
unicastPktConnV6 = ipv6 .NewPacketConn (unicastConnV6 )
}
}
mutlicastGroup4 := net .IPv4 (224 , 0 , 0 , 251 )
multicastGroupAddr4 := &net .UDPAddr {IP : mutlicastGroup4 }
mutlicastGroup6 := net .IP {0xff , 0x2 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0xfb }
multicastGroupAddr6 := &net .UDPAddr {IP : mutlicastGroup6 }
inboundBufferSize := 0
joinErrCount := 0
ifacesToUse := make (map [int ]netInterface , len (ifaces ))
for i := range ifaces {
ifc := ifaces [i ]
if !config .IncludeLoopback && ifc .Flags &net .FlagLoopback == net .FlagLoopback {
continue
}
if ifc .Flags &net .FlagUp == 0 {
continue
}
addrs , err := ifc .Addrs ()
if err != nil {
continue
}
var supportsV4 , supportsV6 bool
ifcIPAddrs := make ([]netip .Addr , 0 , len (addrs ))
for _ , addr := range addrs {
var ipToConv net .IP
switch addr := addr .(type ) {
case *net .IPNet :
ipToConv = addr .IP
case *net .IPAddr :
ipToConv = addr .IP
default :
continue
}
ipAddr , ok := netip .AddrFromSlice (ipToConv )
if !ok {
continue
}
if multicastPktConnV4 != nil {
ipAddr = ipAddr .Unmap ()
}
ipAddr = addrWithOptionalZone (ipAddr , ifc .Name )
if ipAddr .Is6 () && !ipAddr .Is4In6 () {
supportsV6 = true
} else {
supportsV4 = true
}
ifcIPAddrs = append (ifcIPAddrs , ipAddr )
}
if !(supportsV4 || supportsV6 ) {
continue
}
var atLeastOneJoin bool
if supportsV4 && multicastPktConnV4 != nil {
if err := multicastPktConnV4 .JoinGroup (&ifc , multicastGroupAddr4 ); err == nil {
atLeastOneJoin = true
}
}
if supportsV6 && multicastPktConnV6 != nil {
if err := multicastPktConnV6 .JoinGroup (&ifc , multicastGroupAddr6 ); err == nil {
atLeastOneJoin = true
}
}
if !atLeastOneJoin {
joinErrCount ++
continue
}
ifacesToUse [ifc .Index ] = netInterface {
Interface : ifc ,
ipAddrs : ifcIPAddrs ,
supportsV4 : supportsV4 ,
supportsV6 : supportsV6 ,
}
if ifc .MTU > inboundBufferSize {
inboundBufferSize = ifc .MTU
}
}
if len (ifacesToUse ) == 0 {
return nil , errNoUsableInterfaces
}
if inboundBufferSize == 0 {
return nil , errNoPositiveMTUFound
}
if inboundBufferSize > maxPacketSize {
inboundBufferSize = maxPacketSize
}
if joinErrCount >= len (ifaces ) {
return nil , errJoiningMulticastGroup
}
dstAddr4 , err := net .ResolveUDPAddr ("udp4" , destinationAddress4 )
if err != nil {
return nil , err
}
dstAddr6 , err := net .ResolveUDPAddr ("udp6" , destinationAddress6 )
if err != nil {
return nil , err
}
var localNames []string
for _ , l := range config .LocalNames {
localNames = append (localNames , l +"." )
}
c .dstAddr4 = dstAddr4
c .dstAddr6 = dstAddr6
c .localNames = localNames
c .ifaces = ifacesToUse
if config .QueryInterval != 0 {
c .queryInterval = config .QueryInterval
}
if multicastPktConnV4 != nil {
if err := multicastPktConnV4 .SetControlMessage (ipv4 .FlagInterface , true ); err != nil {
c .log .Warnf ("[%s] failed to SetControlMessage(ipv4.FlagInterface) on multicast IPv4 PacketConn %v" , c .name , err )
}
if err := multicastPktConnV4 .SetControlMessage (ipv4 .FlagDst , true ); err != nil {
c .log .Warnf ("[%s] failed to SetControlMessage(ipv4.FlagDst) on multicast IPv4 PacketConn %v" , c .name , err )
}
c .multicastPktConnV4 = ipPacketConn4 {c .name , multicastPktConnV4 , log }
}
if multicastPktConnV6 != nil {
if err := multicastPktConnV6 .SetControlMessage (ipv6 .FlagInterface , true ); err != nil {
c .log .Warnf ("[%s] failed to SetControlMessage(ipv6.FlagInterface) on multicast IPv6 PacketConn %v" , c .name , err )
}
if err := multicastPktConnV6 .SetControlMessage (ipv6 .FlagDst , true ); err != nil {
c .log .Warnf ("[%s] failed to SetControlMessage(ipv6.FlagInterface) on multicast IPv6 PacketConn %v" , c .name , err )
}
c .multicastPktConnV6 = ipPacketConn6 {c .name , multicastPktConnV6 , log }
}
if unicastPktConnV4 != nil {
if err := unicastPktConnV4 .SetControlMessage (ipv4 .FlagInterface , true ); err != nil {
c .log .Warnf ("[%s] failed to SetControlMessage(ipv4.FlagInterface) on unicast IPv4 PacketConn %v" , c .name , err )
}
if err := unicastPktConnV4 .SetControlMessage (ipv4 .FlagDst , true ); err != nil {
c .log .Warnf ("[%s] failed to SetControlMessage(ipv4.FlagInterface) on unicast IPv4 PacketConn %v" , c .name , err )
}
c .unicastPktConnV4 = ipPacketConn4 {c .name , unicastPktConnV4 , log }
}
if unicastPktConnV6 != nil {
if err := unicastPktConnV6 .SetControlMessage (ipv6 .FlagInterface , true ); err != nil {
c .log .Warnf ("[%s] failed to SetControlMessage(ipv6.FlagInterface) on unicast IPv6 PacketConn %v" , c .name , err )
}
if err := unicastPktConnV6 .SetControlMessage (ipv6 .FlagDst , true ); err != nil {
c .log .Warnf ("[%s] failed to SetControlMessage(ipv6.FlagInterface) on unicast IPv6 PacketConn %v" , c .name , err )
}
c .unicastPktConnV6 = ipPacketConn6 {c .name , unicastPktConnV6 , log }
}
if config .IncludeLoopback {
if multicastPktConnV4 != nil {
if err := multicastPktConnV4 .SetMulticastLoopback (true ); err != nil {
c .log .Warnf ("[%s] failed to SetMulticastLoopback(true) on multicast IPv4 PacketConn %v; this may cause inefficient network path c.name,communications" , c .name , err )
}
}
if multicastPktConnV6 != nil {
if err := multicastPktConnV6 .SetMulticastLoopback (true ); err != nil {
c .log .Warnf ("[%s] failed to SetMulticastLoopback(true) on multicast IPv6 PacketConn %v; this may cause inefficient network path c.name,communications" , c .name , err )
}
}
if unicastPktConnV4 != nil {
if err := unicastPktConnV4 .SetMulticastLoopback (true ); err != nil {
c .log .Warnf ("[%s] failed to SetMulticastLoopback(true) on unicast IPv4 PacketConn %v; this may cause inefficient network path c.name,communications" , c .name , err )
}
}
if unicastPktConnV6 != nil {
if err := unicastPktConnV6 .SetMulticastLoopback (true ); err != nil {
c .log .Warnf ("[%s] failed to SetMulticastLoopback(true) on unicast IPv6 PacketConn %v; this may cause inefficient network path c.name,communications" , c .name , err )
}
}
}
started := make (chan struct {})
go c .start (started , inboundBufferSize -20 -8 , config )
<-started
return c , nil
}
func (c *Conn ) Close () error {
select {
case <- c .closed :
return nil
default :
}
var errs []error
if c .multicastPktConnV4 != nil {
if err := c .multicastPktConnV4 .Close (); err != nil {
errs = append (errs , err )
}
}
if c .multicastPktConnV6 != nil {
if err := c .multicastPktConnV6 .Close (); err != nil {
errs = append (errs , err )
}
}
if c .unicastPktConnV4 != nil {
if err := c .unicastPktConnV4 .Close (); err != nil {
errs = append (errs , err )
}
}
if c .unicastPktConnV6 != nil {
if err := c .unicastPktConnV6 .Close (); err != nil {
errs = append (errs , err )
}
}
if len (errs ) == 0 {
<-c .closed
return nil
}
rtrn := errFailedToClose
for _ , err := range errs {
rtrn = fmt .Errorf ("%w\n%w" , err , rtrn )
}
return rtrn
}
func (c *Conn ) Query (ctx context .Context , name string ) (dnsmessage .ResourceHeader , net .Addr , error ) {
header , addr , err := c .QueryAddr (ctx , name )
if err != nil {
return header , nil , err
}
return header , &net .IPAddr {
IP : addr .AsSlice (),
Zone : addr .Zone (),
}, nil
}
func (c *Conn ) QueryAddr (ctx context .Context , name string ) (dnsmessage .ResourceHeader , netip .Addr , error ) {
select {
case <- c .closed :
return dnsmessage .ResourceHeader {}, netip .Addr {}, errConnectionClosed
default :
}
nameWithSuffix := name + "."
queryChan := make (chan queryResult , 1 )
query := &query {nameWithSuffix , queryChan }
c .mu .Lock ()
c .queries = append (c .queries , query )
c .mu .Unlock ()
defer func () {
c .mu .Lock ()
defer c .mu .Unlock ()
for i := len (c .queries ) - 1 ; i >= 0 ; i -- {
if c .queries [i ] == query {
c .queries = append (c .queries [:i ], c .queries [i +1 :]...)
}
}
}()
ticker := time .NewTicker (c .queryInterval )
defer ticker .Stop ()
c .sendQuestion (nameWithSuffix )
for {
select {
case <- ticker .C :
c .sendQuestion (nameWithSuffix )
case <- c .closed :
return dnsmessage .ResourceHeader {}, netip .Addr {}, errConnectionClosed
case res := <- queryChan :
return res .answer , res .addr , nil
case <- ctx .Done ():
return dnsmessage .ResourceHeader {}, netip .Addr {}, errContextElapsed
}
}
}
type ipToBytesError struct {
addr netip .Addr
expectedType string
}
func (err ipToBytesError ) Error () string {
return fmt .Sprintf ("ip (%s) is not %s" , err .addr , err .expectedType )
}
func ipv4ToBytes(ipAddr netip .Addr ) ([4 ]byte , error ) {
if !ipAddr .Is4 () {
return [4 ]byte {}, ipToBytesError {ipAddr , "IPv4" }
}
md , err := ipAddr .MarshalBinary ()
if err != nil {
return [4 ]byte {}, err
}
var out [4 ]byte
copy (out [:], md )
return out , nil
}
func ipv6ToBytes(ipAddr netip .Addr ) ([16 ]byte , error ) {
if !ipAddr .Is6 () {
return [16 ]byte {}, ipToBytesError {ipAddr , "IPv6" }
}
md , err := ipAddr .MarshalBinary ()
if err != nil {
return [16 ]byte {}, err
}
var out [16 ]byte
copy (out [:], md )
return out , nil
}
type ipToAddrError struct {
ip []byte
}
func (err ipToAddrError ) Error () string {
return fmt .Sprintf ("failed to convert ip address '%s' to netip.Addr" , err .ip )
}
func interfaceForRemote(remote string ) (*netip .Addr , error ) {
conn , err := net .Dial ("udp" , remote )
if err != nil {
return nil , err
}
localAddr , ok := conn .LocalAddr ().(*net .UDPAddr )
if !ok {
return nil , errFailedCast
}
if err := conn .Close (); err != nil {
return nil , err
}
ipAddr , ok := netip .AddrFromSlice (localAddr .IP )
if !ok {
return nil , ipToAddrError {localAddr .IP }
}
ipAddr = addrWithOptionalZone (ipAddr , localAddr .Zone )
return &ipAddr , nil
}
type writeType byte
const (
writeTypeQuestion writeType = iota
writeTypeAnswer
)
func (c *Conn ) sendQuestion (name string ) {
packedName , err := dnsmessage .NewName (name )
if err != nil {
c .log .Warnf ("[%s] failed to construct mDNS packet %v" , c .name , err )
return
}
msg := dnsmessage .Message {
Header : dnsmessage .Header {},
}
if c .multicastPktConnV4 != nil {
msg .Questions = append (msg .Questions , dnsmessage .Question {
Type : dnsmessage .TypeA ,
Class : dnsmessage .ClassINET | (1 << 15 ),
Name : packedName ,
})
}
if c .multicastPktConnV6 != nil {
msg .Questions = append (msg .Questions , dnsmessage .Question {
Type : dnsmessage .TypeAAAA ,
Class : dnsmessage .ClassINET | (1 << 15 ),
Name : packedName ,
})
}
rawQuery , err := msg .Pack ()
if err != nil {
c .log .Warnf ("[%s] failed to construct mDNS packet %v" , c .name , err )
return
}
c .writeToSocket (-1 , rawQuery , false , false , writeTypeQuestion , nil )
}
func (c *Conn ) writeToSocket (
ifIndex int ,
b []byte ,
hasLoopbackData bool ,
hasIPv6Zone bool ,
wType writeType ,
unicastDst *net .UDPAddr ,
) {
var dst4 , dst6 net .Addr
if wType == writeTypeAnswer {
if unicastDst == nil {
dst4 = c .dstAddr4
dst6 = c .dstAddr6
} else {
if unicastDst .IP .To4 () == nil {
dst6 = unicastDst
} else {
dst4 = unicastDst
}
}
}
if ifIndex != -1 {
if wType == writeTypeQuestion {
c .log .Errorf ("[%s] Unexpected question using specific interface index %d; dropping question" , c .name , ifIndex )
return
}
ifc , ok := c .ifaces [ifIndex ]
if !ok {
c .log .Warnf ("[%s] no interface for %d" , c .name , ifIndex )
return
}
if hasLoopbackData && ifc .Flags &net .FlagLoopback == 0 {
c .log .Debugf ("[%s] interface is not loopback %d" , c .name , ifIndex )
return
}
c .log .Debugf ("[%s] writing answer to IPv4: %v, IPv6: %v" , c .name , dst4 , dst6 )
if ifc .supportsV4 && c .multicastPktConnV4 != nil && dst4 != nil {
if !hasIPv6Zone {
if _ , err := c .multicastPktConnV4 .WriteTo (b , &ifc .Interface , nil , dst4 ); err != nil {
c .log .Warnf ("[%s] failed to send mDNS packet on IPv4 interface %d: %v" , c .name , ifIndex , err )
}
} else {
c .log .Debugf ("[%s] refusing to send mDNS packet with IPv6 zone over IPv4" , c .name )
}
}
if ifc .supportsV6 && c .multicastPktConnV6 != nil && dst6 != nil {
if _ , err := c .multicastPktConnV6 .WriteTo (b , &ifc .Interface , nil , dst6 ); err != nil {
c .log .Warnf ("[%s] failed to send mDNS packet on IPv6 interface %d: %v" , c .name , ifIndex , err )
}
}
return
}
for ifcIdx := range c .ifaces {
ifc := c .ifaces [ifcIdx ]
if hasLoopbackData {
c .log .Debugf ("[%s] Refusing to send loopback data with non-specific interface" , c .name )
continue
}
if wType == writeTypeQuestion {
if c .unicastPktConnV4 == nil && c .unicastPktConnV6 == nil {
c .log .Debugf ("[%s] writing question to multicast IPv4/6 %s" , c .name , c .dstAddr4 )
if ifc .supportsV4 && c .multicastPktConnV4 != nil {
if _ , err := c .multicastPktConnV4 .WriteTo (b , &ifc .Interface , nil , c .dstAddr4 ); err != nil {
c .log .Warnf ("[%s] failed to send mDNS packet (multicast) on IPv4 interface %d: %v" , c .name , ifc .Index , err )
}
}
if ifc .supportsV6 && c .multicastPktConnV6 != nil {
if _ , err := c .multicastPktConnV6 .WriteTo (b , &ifc .Interface , nil , c .dstAddr6 ); err != nil {
c .log .Warnf ("[%s] failed to send mDNS packet (multicast) on IPv6 interface %d: %v" , c .name , ifc .Index , err )
}
}
}
if ifc .supportsV4 && c .unicastPktConnV4 != nil {
c .log .Debugf ("[%s] writing question to unicast IPv4 %s" , c .name , c .dstAddr4 )
if _ , err := c .unicastPktConnV4 .WriteTo (b , &ifc .Interface , nil , c .dstAddr4 ); err != nil {
c .log .Warnf ("[%s] failed to send mDNS packet (unicast) on interface %d: %v" , c .name , ifc .Index , err )
}
}
if ifc .supportsV6 && c .unicastPktConnV6 != nil {
c .log .Debugf ("[%s] writing question to unicast IPv6 %s" , c .name , c .dstAddr6 )
if _ , err := c .unicastPktConnV6 .WriteTo (b , &ifc .Interface , nil , c .dstAddr6 ); err != nil {
c .log .Warnf ("[%s] failed to send mDNS packet (unicast) on interface %d: %v" , c .name , ifc .Index , err )
}
}
} else {
c .log .Debugf ("[%s] writing answer to IPv4: %v, IPv6: %v" , c .name , dst4 , dst6 )
if ifc .supportsV4 && c .multicastPktConnV4 != nil && dst4 != nil {
if !hasIPv6Zone {
if _ , err := c .multicastPktConnV4 .WriteTo (b , &ifc .Interface , nil , dst4 ); err != nil {
c .log .Warnf ("[%s] failed to send mDNS packet (multicast) on IPv4 interface %d: %v" , c .name , ifIndex , err )
}
} else {
c .log .Debugf ("[%s] refusing to send mDNS packet with IPv6 zone over IPv4" , c .name )
}
}
if ifc .supportsV6 && c .multicastPktConnV6 != nil && dst6 != nil {
if _ , err := c .multicastPktConnV6 .WriteTo (b , &ifc .Interface , nil , dst6 ); err != nil {
c .log .Warnf ("[%s] failed to send mDNS packet (multicast) on IPv6 interface %d: %v" , c .name , ifIndex , err )
}
}
}
}
}
func createAnswer(id uint16 , name string , addr netip .Addr ) (dnsmessage .Message , error ) {
packedName , err := dnsmessage .NewName (name )
if err != nil {
return dnsmessage .Message {}, err
}
msg := dnsmessage .Message {
Header : dnsmessage .Header {
ID : id ,
Response : true ,
Authoritative : true ,
},
Answers : []dnsmessage .Resource {
{
Header : dnsmessage .ResourceHeader {
Class : dnsmessage .ClassINET ,
Name : packedName ,
TTL : responseTTL ,
},
},
},
}
if addr .Is4 () {
ipBuf , err := ipv4ToBytes (addr )
if err != nil {
return dnsmessage .Message {}, err
}
msg .Answers [0 ].Header .Type = dnsmessage .TypeA
msg .Answers [0 ].Body = &dnsmessage .AResource {
A : ipBuf ,
}
} else if addr .Is6 () {
ipBuf , err := ipv6ToBytes (addr )
if err != nil {
return dnsmessage .Message {}, err
}
msg .Answers [0 ].Header .Type = dnsmessage .TypeAAAA
msg .Answers [0 ].Body = &dnsmessage .AAAAResource {
AAAA : ipBuf ,
}
}
return msg , nil
}
func (c *Conn ) sendAnswer (queryID uint16 , name string , ifIndex int , result netip .Addr , dst *net .UDPAddr ) {
answer , err := createAnswer (queryID , name , result )
if err != nil {
c .log .Warnf ("[%s] failed to create mDNS answer %v" , c .name , err )
return
}
rawAnswer , err := answer .Pack ()
if err != nil {
c .log .Warnf ("[%s] failed to construct mDNS packet %v" , c .name , err )
return
}
c .writeToSocket (
ifIndex ,
rawAnswer ,
result .IsLoopback (),
result .Is6 () && result .Zone () != "" ,
writeTypeAnswer ,
dst ,
)
}
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 (c ipPacketConn4 ) ReadFrom (b []byte ) (n int , cm *ipControlMessage , src net .Addr , err error ) {
n , cm4 , src , err := c .conn .ReadFrom (b )
if err != nil || cm4 == nil {
return n , nil , src , err
}
return n , &ipControlMessage {IfIndex : cm4 .IfIndex , Dst : cm4 .Dst }, src , err
}
func (c ipPacketConn4 ) WriteTo (b []byte , via *net .Interface , cm *ipControlMessage , dst net .Addr ) (n int , err error ) {
var cm4 *ipv4 .ControlMessage
if cm != nil {
cm4 = &ipv4 .ControlMessage {
IfIndex : cm .IfIndex ,
}
}
if err := c .conn .SetMulticastInterface (via ); err != nil {
c .log .Warnf ("[%s] failed to set multicast interface for %d: %v" , c .name , via .Index , err )
return 0 , err
}
return c .conn .WriteTo (b , cm4 , dst )
}
func (c ipPacketConn4 ) Close () error {
return c .conn .Close ()
}
type ipPacketConn6 struct {
name string
conn *ipv6 .PacketConn
log logging .LeveledLogger
}
func (c ipPacketConn6 ) ReadFrom (b []byte ) (n int , cm *ipControlMessage , src net .Addr , err error ) {
n , cm6 , src , err := c .conn .ReadFrom (b )
if err != nil || cm6 == nil {
return n , nil , src , err
}
return n , &ipControlMessage {IfIndex : cm6 .IfIndex , Dst : cm6 .Dst }, src , err
}
func (c ipPacketConn6 ) WriteTo (b []byte , via *net .Interface , cm *ipControlMessage , dst net .Addr ) (n int , err error ) {
var cm6 *ipv6 .ControlMessage
if cm != nil {
cm6 = &ipv6 .ControlMessage {
IfIndex : cm .IfIndex ,
}
}
if err := c .conn .SetMulticastInterface (via ); err != nil {
c .log .Warnf ("[%s] failed to set multicast interface for %d: %v" , c .name , via .Index , err )
return 0 , err
}
return c .conn .WriteTo (b , cm6 , dst )
}
func (c ipPacketConn6 ) Close () error {
return c .conn .Close ()
}
func (c *Conn ) readLoop (name string , pktConn ipPacketConn , inboundBufferSize int , config *Config ) {
b := make ([]byte , inboundBufferSize )
p := dnsmessage .Parser {}
for {
n , cm , src , err := pktConn .ReadFrom (b )
if err != nil {
if errors .Is (err , net .ErrClosed ) {
return
}
c .log .Warnf ("[%s] failed to ReadFrom %q %v" , c .name , src , err )
continue
}
c .log .Debugf ("[%s] got read on %s from %s" , c .name , name , src )
var ifIndex int
var pktDst net .IP
if cm != nil {
ifIndex = cm .IfIndex
pktDst = cm .Dst
} else {
ifIndex = -1
}
srcAddr , ok := src .(*net .UDPAddr )
if !ok {
c .log .Warnf ("[%s] expected source address %s to be UDP but got %" , c .name , src , src )
continue
}
func () {
header , err := p .Start (b [:n ])
if err != nil {
c .log .Warnf ("[%s] failed to parse mDNS packet %v" , c .name , err )
return
}
for i := 0 ; i <= maxMessageRecords ; i ++ {
q , err := p .Question ()
if errors .Is (err , dnsmessage .ErrSectionDone ) {
break
} else if err != nil {
c .log .Warnf ("[%s] failed to parse mDNS packet %v" , c .name , err )
return
}
if q .Type != dnsmessage .TypeA && q .Type != dnsmessage .TypeAAAA {
continue
}
shouldUnicastResponse := (q .Class &(1 <<15 )) != 0 ||
srcAddr .Port != 5353 ||
(len (pktDst ) != 0 && !(pktDst .Equal (c .dstAddr4 .IP ) ||
pktDst .Equal (c .dstAddr6 .IP )))
var dst *net .UDPAddr
if shouldUnicastResponse {
dst = srcAddr
}
queryWantsV4 := q .Type == dnsmessage .TypeA
for _ , localName := range c .localNames {
if localName == q .Name .String () {
var localAddress *netip .Addr
if config .LocalAddress != nil {
ipAddr , ok := netip .AddrFromSlice (config .LocalAddress )
if !ok {
c .log .Warnf ("[%s] failed to convert config.LocalAddress '%s' to netip.Addr" , c .name , config .LocalAddress )
continue
}
if c .multicastPktConnV4 != nil {
ipAddr = ipAddr .Unmap ()
}
localAddress = &ipAddr
} else {
if ifIndex != -1 {
ifc , ok := c .ifaces [ifIndex ]
if !ok {
c .log .Warnf ("[%s] no interface for %d" , c .name , ifIndex )
return
}
var selectedAddrs []netip .Addr
for _ , addr := range ifc .ipAddrs {
addrCopy := addr
if queryWantsV4 {
if addrCopy .Is4In6 () {
addrCopy = addrCopy .Unmap ()
}
if !addrCopy .Is4 () {
continue
}
} else {
if !addrCopy .Is6 () {
continue
}
if !isSupportedIPv6 (addrCopy , c .multicastPktConnV4 == nil ) {
c .log .Debugf ("[%s] interface %d address not a supported IPv6 address %s" , c .name , ifIndex , &addrCopy )
continue
}
}
selectedAddrs = append (selectedAddrs , addrCopy )
}
if len (selectedAddrs ) == 0 {
c .log .Debugf ("[%s] failed to find suitable IP for interface %d; deriving address from source address c.name,instead" , c .name , ifIndex )
} else {
var choice *netip .Addr
for _ , option := range selectedAddrs {
optCopy := option
if option .Is4 () {
choice = &optCopy
break
}
if choice == nil {
choice = &optCopy
} else if !optCopy .Is4In6 () {
choice = &optCopy
}
if !optCopy .Is4In6 () {
break
}
}
localAddress = choice
}
}
if ifIndex == -1 || localAddress == nil {
localAddress , err = interfaceForRemote (src .String ())
if err != nil {
c .log .Warnf ("[%s] failed to get local interface to communicate with %s: %v" , c .name , src .String (), err )
continue
}
}
}
if queryWantsV4 {
if !localAddress .Is4 () {
c .log .Debugf ("[%s] have IPv6 address %s to respond with but question is for A not c.name,AAAA" , c .name , localAddress )
continue
}
} else {
if !localAddress .Is6 () {
c .log .Debugf ("[%s] have IPv4 address %s to respond with but question is for AAAA not c.name,A" , c .name , localAddress )
continue
}
if !isSupportedIPv6 (*localAddress , c .multicastPktConnV4 == nil ) {
c .log .Debugf ("[%s] got local interface address but not a supported IPv6 address %v" , c .name , localAddress )
continue
}
}
if dst != nil && len (dst .IP ) == net .IPv4len &&
localAddress .Is6 () &&
localAddress .Zone () != "" &&
(localAddress .IsLinkLocalUnicast () || localAddress .IsLinkLocalMulticast ()) {
c .log .Debugf ("[%s] refusing to send link-local address %s to an IPv4 destination %s" , c .name , localAddress , dst )
continue
}
c .log .Debugf ("[%s] sending response for %s on ifc %d of %s to %s" , c .name , q .Name , ifIndex , *localAddress , dst )
c .sendAnswer (header .ID , q .Name .String (), ifIndex , *localAddress , dst )
}
}
}
for i := 0 ; i <= maxMessageRecords ; i ++ {
a , err := p .AnswerHeader ()
if errors .Is (err , dnsmessage .ErrSectionDone ) {
return
}
if err != nil {
c .log .Warnf ("[%s] failed to parse mDNS packet %v" , c .name , err )
return
}
if a .Type != dnsmessage .TypeA && a .Type != dnsmessage .TypeAAAA {
continue
}
c .mu .Lock ()
queries := make ([]*query , len (c .queries ))
copy (queries , c .queries )
c .mu .Unlock ()
var answered []*query
for _ , query := range queries {
queryCopy := query
if queryCopy .nameWithSuffix == a .Name .String () {
addr , err := addrFromAnswerHeader (a , p )
if err != nil {
c .log .Warnf ("[%s] failed to parse mDNS answer %v" , c .name , err )
return
}
resultAddr := *addr
resultAddr = addrWithOptionalZone (resultAddr , srcAddr .Zone )
select {
case queryCopy .queryResultChan <- queryResult {a , resultAddr }:
answered = append (answered , queryCopy )
default :
}
}
}
c .mu .Lock ()
for queryIdx := len (c .queries ) - 1 ; queryIdx >= 0 ; queryIdx -- {
for answerIdx := len (answered ) - 1 ; answerIdx >= 0 ; answerIdx -- {
if c .queries [queryIdx ] == answered [answerIdx ] {
c .queries = append (c .queries [:queryIdx ], c .queries [queryIdx +1 :]...)
answered = append (answered [:answerIdx ], answered [answerIdx +1 :]...)
queryIdx --
break
}
}
}
c .mu .Unlock ()
}
}()
}
}
func (c *Conn ) start (started chan <- struct {}, inboundBufferSize int , config *Config ) {
defer func () {
c .mu .Lock ()
defer c .mu .Unlock ()
close (c .closed )
}()
var numReaders int
readerStarted := make (chan struct {})
readerEnded := make (chan struct {})
if c .multicastPktConnV4 != nil {
numReaders ++
go func () {
defer func () {
readerEnded <- struct {}{}
}()
readerStarted <- struct {}{}
c .readLoop ("multi4" , c .multicastPktConnV4 , inboundBufferSize , config )
}()
}
if c .multicastPktConnV6 != nil {
numReaders ++
go func () {
defer func () {
readerEnded <- struct {}{}
}()
readerStarted <- struct {}{}
c .readLoop ("multi6" , c .multicastPktConnV6 , inboundBufferSize , config )
}()
}
if c .unicastPktConnV4 != nil {
numReaders ++
go func () {
defer func () {
readerEnded <- struct {}{}
}()
readerStarted <- struct {}{}
c .readLoop ("uni4" , c .unicastPktConnV4 , inboundBufferSize , config )
}()
}
if c .unicastPktConnV6 != nil {
numReaders ++
go func () {
defer func () {
readerEnded <- struct {}{}
}()
readerStarted <- struct {}{}
c .readLoop ("uni6" , c .unicastPktConnV6 , inboundBufferSize , config )
}()
}
for i := 0 ; i < numReaders ; i ++ {
<-readerStarted
}
close (started )
for i := 0 ; i < numReaders ; i ++ {
<-readerEnded
}
}
func addrFromAnswerHeader(a dnsmessage .ResourceHeader , p dnsmessage .Parser ) (addr *netip .Addr , err error ) {
if a .Type == dnsmessage .TypeA {
resource , err := p .AResource ()
if err != nil {
return nil , err
}
ipAddr , ok := netip .AddrFromSlice (resource .A [:])
if !ok {
return nil , fmt .Errorf ("failed to convert A record: %w" , ipToAddrError {resource .A [:]})
}
ipAddr = ipAddr .Unmap ()
addr = &ipAddr
} else {
resource , err := p .AAAAResource ()
if err != nil {
return nil , err
}
ipAddr , ok := netip .AddrFromSlice (resource .AAAA [:])
if !ok {
return nil , fmt .Errorf ("failed to convert AAAA record: %w" , ipToAddrError {resource .AAAA [:]})
}
addr = &ipAddr
}
return
}
func isSupportedIPv6(addr netip .Addr , ipv6Only bool ) bool {
if !addr .Is6 () {
return false
}
if !ipv6Only && addr .Is4In6 () {
return false
}
return true
}
func addrWithOptionalZone(addr netip .Addr , zone string ) netip .Addr {
if zone == "" {
return addr
}
if addr .Is6 () && (addr .IsLinkLocalUnicast () || addr .IsLinkLocalMulticast ()) {
return addr .WithZone (zone )
}
return addr
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .