Files
zoraxy/src/mod/mdns/mdns.go
Toby Chui 70adadf129 bug fixes for 2.6.5
+ Added potential fixes for #34 and #39
+ Added better error handling for #43
2023-08-17 14:08:48 +08:00

244 lines
6.3 KiB
Go

package mdns
import (
"context"
"log"
"net"
"strings"
"time"
"github.com/grandcat/zeroconf"
"imuslab.com/zoraxy/mod/utils"
)
type MDNSHost struct {
MDNS *zeroconf.Server
Host *NetworkHost
IfaceOverride *net.Interface
}
type NetworkHost struct {
HostName string
Port int
IPv4 []net.IP
Domain string
Model string
UUID string
Vendor string
BuildVersion string
MacAddr []string
Online bool
}
// Create a new MDNS discoverer, set MacOverride to empty string for using the first NIC discovered
func NewMDNS(config NetworkHost, MacOverride string) (*MDNSHost, error) {
//Get host MAC Address
macAddress, err := getMacAddr()
if err != nil {
return nil, err
}
macAddressBoardcast := ""
if err == nil {
macAddressBoardcast = strings.Join(macAddress, ",")
} else {
log.Println("[mDNS] Unable to get MAC Address: ", err.Error())
}
//Register the mds services
server, err := zeroconf.Register(config.HostName, "_http._tcp", "local.", config.Port, []string{"version_build=" + config.BuildVersion, "vendor=" + config.Vendor, "model=" + config.Model, "uuid=" + config.UUID, "domain=" + config.Domain, "mac_addr=" + macAddressBoardcast}, nil)
if err != nil {
log.Println("[mDNS] Error when registering zeroconf broadcast message", err.Error())
return &MDNSHost{}, err
}
//Discover the iface to override if exists
var overrideIface *net.Interface = nil
if MacOverride != "" {
ifaceIp := ""
ifaces, err := net.Interfaces()
if err != nil {
log.Println("[mDNS] Unable to override iface MAC: " + err.Error() + ". Resuming with default iface")
}
foundMatching := false
for _, iface := range ifaces {
thisIfaceMac := iface.HardwareAddr.String()
thisIfaceMac = strings.ReplaceAll(thisIfaceMac, ":", "-")
MacOverride = strings.ReplaceAll(MacOverride, ":", "-")
if strings.EqualFold(thisIfaceMac, strings.TrimSpace(MacOverride)) {
//This is the correct iface to use
overrideIface = &iface
addrs, err := iface.Addrs()
if err == nil && len(addrs) > 0 {
ifaceIp = addrs[0].String()
}
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip.To4() != nil {
//This NIC have Ipv4 addr
ifaceIp = ip.String()
}
}
foundMatching = true
break
}
}
if !foundMatching {
log.Println("[mDNS] Unable to find the target iface with MAC address: " + MacOverride + ". Resuming with default iface")
} else {
log.Println("[mDNS] Entering force MAC address mode, listening on: " + MacOverride + "(IP address: " + ifaceIp + ")")
}
}
return &MDNSHost{
MDNS: server,
Host: &config,
IfaceOverride: overrideIface,
}, nil
}
func (m *MDNSHost) Close() {
if m != nil {
m.MDNS.Shutdown()
}
}
// Scan with given timeout and domain filter. Use m.Host.Domain for scanning similar typed devices
func (m *MDNSHost) Scan(timeout int, domainFilter string) []*NetworkHost {
// Discover all services on the network (e.g. _workstation._tcp)
var zcoption zeroconf.ClientOption = nil
if m.IfaceOverride != nil {
zcoption = zeroconf.SelectIfaces([]net.Interface{*m.IfaceOverride})
}
resolver, err := zeroconf.NewResolver(zcoption)
if err != nil {
log.Fatalln("Failed to initialize resolver:", err.Error())
}
entries := make(chan *zeroconf.ServiceEntry)
//Create go routine to wait for the resolver
discoveredHost := []*NetworkHost{}
go func(results <-chan *zeroconf.ServiceEntry) {
for entry := range results {
if domainFilter == "" {
//This is a ArOZ Online Host
//Split the required information out of the text element
TEXT := entry.Text
properties := map[string]string{}
for _, v := range TEXT {
kv := strings.Split(v, "=")
if len(kv) == 2 {
properties[kv[0]] = kv[1]
}
}
var macAddrs []string
val, ok := properties["mac_addr"]
if !ok || val == "" {
//No MacAddr found. Target node version too old
macAddrs = []string{}
} else {
macAddrs = strings.Split(properties["mac_addr"], ",")
}
//log.Println(properties)
discoveredHost = append(discoveredHost, &NetworkHost{
HostName: entry.HostName,
Port: entry.Port,
IPv4: entry.AddrIPv4,
Domain: properties["domain"],
Model: properties["model"],
UUID: properties["uuid"],
Vendor: properties["vendor"],
BuildVersion: properties["version_build"],
MacAddr: macAddrs,
Online: true,
})
} else {
if utils.StringInArray(entry.Text, "domain="+domainFilter) {
//This is generic scan request
//Split the required information out of the text element
TEXT := entry.Text
properties := map[string]string{}
for _, v := range TEXT {
kv := strings.Split(v, "=")
if len(kv) == 2 {
properties[kv[0]] = kv[1]
}
}
var macAddrs []string
val, ok := properties["mac_addr"]
if !ok || val == "" {
//No MacAddr found. Target node version too old
macAddrs = []string{}
} else {
macAddrs = strings.Split(properties["mac_addr"], ",")
}
//log.Println(properties)
discoveredHost = append(discoveredHost, &NetworkHost{
HostName: entry.HostName,
Port: entry.Port,
IPv4: entry.AddrIPv4,
Domain: properties["domain"],
Model: properties["model"],
UUID: properties["uuid"],
Vendor: properties["vendor"],
BuildVersion: properties["version_build"],
MacAddr: macAddrs,
Online: true,
})
}
}
}
}(entries)
//Resolve each of the mDNS and pipe it back to the log functions
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(timeout))
defer cancel()
err = resolver.Browse(ctx, "_http._tcp", "local.", entries)
if err != nil {
log.Fatalln("Failed to browse:", err.Error())
}
//Update the master scan record
<-ctx.Done()
return discoveredHost
}
// Get all mac address of all interfaces
func getMacAddr() ([]string, error) {
ifas, err := net.Interfaces()
if err != nil {
return nil, err
}
var as []string
for _, ifa := range ifas {
a := ifa.HardwareAddr.String()
if a != "" {
as = append(as, a)
}
}
return as, nil
}