267 lines
6.0 KiB
Go
267 lines
6.0 KiB
Go
|
package mdns
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"log"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"sync/atomic"
|
||
|
"time"
|
||
|
|
||
|
omd "git.loafle.net/overflow/model/discovery"
|
||
|
omm "git.loafle.net/overflow/model/meta"
|
||
|
omu "git.loafle.net/overflow/model/util"
|
||
|
"git.loafle.net/overflow_scanner/probe/model"
|
||
|
"github.com/grandcat/zeroconf"
|
||
|
)
|
||
|
|
||
|
type serviceMeta struct {
|
||
|
name string
|
||
|
cryptoType string
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
serviceMetaMapping = map[string]*serviceMeta{
|
||
|
"http": &serviceMeta{
|
||
|
name: "HTTP",
|
||
|
cryptoType: "NONE",
|
||
|
},
|
||
|
"printer": &serviceMeta{
|
||
|
name: "PRINTER",
|
||
|
cryptoType: "NONE",
|
||
|
},
|
||
|
"pdl-datastream": &serviceMeta{
|
||
|
name: "PDL-DATASTREAM",
|
||
|
cryptoType: "NONE",
|
||
|
},
|
||
|
"ipp": &serviceMeta{
|
||
|
name: "IPP",
|
||
|
cryptoType: "NONE",
|
||
|
},
|
||
|
"webdav": &serviceMeta{
|
||
|
name: "WEBDAV",
|
||
|
cryptoType: "NONE",
|
||
|
},
|
||
|
"webdavs": &serviceMeta{
|
||
|
name: "WEBDAV",
|
||
|
cryptoType: "TLS",
|
||
|
},
|
||
|
"afpovertcp": &serviceMeta{
|
||
|
name: "AFP",
|
||
|
cryptoType: "NONE",
|
||
|
},
|
||
|
"smb": &serviceMeta{
|
||
|
name: "SMB",
|
||
|
cryptoType: "NONE",
|
||
|
},
|
||
|
"rfb": &serviceMeta{
|
||
|
name: "RFB",
|
||
|
cryptoType: "NONE",
|
||
|
},
|
||
|
}
|
||
|
)
|
||
|
|
||
|
func Scan(discovered model.Discovered) {
|
||
|
serviceEntries, err := browse("_services._dns-sd._udp", "local")
|
||
|
if nil != err {
|
||
|
log.Print("Cannot find service ", err)
|
||
|
}
|
||
|
|
||
|
metaIPTypeEnum := omm.ToMetaIPTypeEnum(discovered.Zone().MetaIPType)
|
||
|
|
||
|
for _, serviceEntry := range serviceEntries {
|
||
|
name := removeDomainName(serviceEntry.Instance, serviceEntry.Domain)
|
||
|
entries, _err := browse(name, serviceEntry.Domain)
|
||
|
if nil != _err {
|
||
|
log.Print("Cannot find entry ", _err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
LOOP:
|
||
|
for _, entry := range entries {
|
||
|
log.Print("serviceEntry ", entry)
|
||
|
|
||
|
name := entry.Instance // HP\ LaserJet\ P1505n
|
||
|
service := entry.Service // _pdl-datastream._tcp
|
||
|
port := entry.Port // 9100
|
||
|
meta := toMeta(entry.Text)
|
||
|
hostName := removeDomainName(entry.HostName, entry.Domain) // NPIFACA9B
|
||
|
serviceName, portType, cryptoType := parseService(service)
|
||
|
|
||
|
var metaPortType *omm.MetaPortType
|
||
|
switch portType {
|
||
|
case "tcp":
|
||
|
metaPortType = omm.ToMetaPortType(omm.MetaPortTypeEnumTCP)
|
||
|
case "udp":
|
||
|
metaPortType = omm.ToMetaPortType(omm.MetaPortTypeEnumUDP)
|
||
|
}
|
||
|
|
||
|
metaCryptoType := omm.ToMetaCryptoType(omm.MetaCryptoTypeEnumUNKNOWN)
|
||
|
switch cryptoType {
|
||
|
case "NONE":
|
||
|
metaCryptoType = omm.ToMetaCryptoType(omm.MetaCryptoTypeEnumNONE)
|
||
|
case "TLS":
|
||
|
metaCryptoType = omm.ToMetaCryptoType(omm.MetaCryptoTypeEnumTLS)
|
||
|
}
|
||
|
|
||
|
switch metaIPTypeEnum {
|
||
|
case omm.MetaIPTypeEnumV4:
|
||
|
for _, ipv4 := range entry.AddrIPv4 {
|
||
|
h := discovered.AddHost(&omd.Host{
|
||
|
MetaIPType: omm.ToMetaIPType(metaIPTypeEnum),
|
||
|
Name: hostName,
|
||
|
Address: ipv4.String(),
|
||
|
Meta: meta,
|
||
|
Zone: discovered.Zone(),
|
||
|
DiscoveredDate: omu.NowPtr(),
|
||
|
})
|
||
|
|
||
|
if 1 > port {
|
||
|
continue LOOP
|
||
|
}
|
||
|
|
||
|
p := discovered.AddPort(&omd.Port{
|
||
|
MetaPortType: metaPortType,
|
||
|
PortNumber: json.Number(strconv.Itoa(port)),
|
||
|
Meta: meta,
|
||
|
Host: h,
|
||
|
})
|
||
|
|
||
|
discovered.AddService(&omd.Service{
|
||
|
MetaCryptoType: metaCryptoType,
|
||
|
Key: serviceName,
|
||
|
Name: name,
|
||
|
Port: p,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
case omm.MetaIPTypeEnumV6:
|
||
|
for _, ipv6 := range entry.AddrIPv6 {
|
||
|
h := discovered.AddHost(&omd.Host{
|
||
|
MetaIPType: omm.ToMetaIPType(metaIPTypeEnum),
|
||
|
Name: hostName,
|
||
|
Address: ipv6.String(),
|
||
|
Meta: meta,
|
||
|
Zone: discovered.Zone(),
|
||
|
DiscoveredDate: omu.NowPtr(),
|
||
|
})
|
||
|
|
||
|
if 1 > port {
|
||
|
continue LOOP
|
||
|
}
|
||
|
|
||
|
p := discovered.AddPort(&omd.Port{
|
||
|
MetaPortType: metaPortType,
|
||
|
PortNumber: json.Number(strconv.Itoa(port)),
|
||
|
Meta: meta,
|
||
|
Host: h,
|
||
|
})
|
||
|
|
||
|
discovered.AddService(&omd.Service{
|
||
|
MetaCryptoType: metaCryptoType,
|
||
|
Key: serviceName,
|
||
|
Name: name,
|
||
|
Port: p,
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func removeDomainName(instance string, domain string) (service string) {
|
||
|
_instance := strings.TrimRight(instance, ".")
|
||
|
return strings.TrimRight(_instance, fmt.Sprintf(".%s", domain))
|
||
|
}
|
||
|
|
||
|
func toMeta(text []string) map[string]string {
|
||
|
if nil == text || 0 == len(text) {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
meta := make(map[string]string)
|
||
|
for _, v := range text {
|
||
|
ss := strings.SplitN(v, "=", 2)
|
||
|
if 2 != len(ss) {
|
||
|
continue
|
||
|
}
|
||
|
if nil != ss {
|
||
|
meta[ss[0]] = ss[1]
|
||
|
}
|
||
|
}
|
||
|
return meta
|
||
|
}
|
||
|
|
||
|
func parseService(service string) (name string, portType string, cryptoType string) {
|
||
|
ss := strings.SplitN(service, ".", 2)
|
||
|
if nil == ss {
|
||
|
return "UNKNOWN", "UNKNOWN", "UNKNOWN"
|
||
|
}
|
||
|
|
||
|
_name := strings.TrimLeft(ss[0], "_")
|
||
|
_portType := strings.TrimLeft(ss[1], "_")
|
||
|
|
||
|
m, ok := serviceMetaMapping[_name]
|
||
|
if !ok {
|
||
|
return "UNKNOWN", "UNKNOWN", "UNKNOWN"
|
||
|
}
|
||
|
|
||
|
name = m.name
|
||
|
portType = _portType
|
||
|
cryptoType = m.cryptoType
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func browse(service string, domain string) ([]*zeroconf.ServiceEntry, error) {
|
||
|
resolver, err := zeroconf.NewResolver(nil)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("Failed to initialize resolver %v", err)
|
||
|
}
|
||
|
|
||
|
entryChan := make(chan *zeroconf.ServiceEntry)
|
||
|
timerStopped := make(chan struct{})
|
||
|
|
||
|
serviceEntries := make([]*zeroconf.ServiceEntry, 0)
|
||
|
|
||
|
go func(_entryChan <-chan *zeroconf.ServiceEntry) {
|
||
|
var delay atomic.Value
|
||
|
delay.Store(false)
|
||
|
ticker := time.NewTicker(time.Second * 1)
|
||
|
|
||
|
for {
|
||
|
select {
|
||
|
case entry, ok := <-_entryChan:
|
||
|
if !ok {
|
||
|
return
|
||
|
}
|
||
|
delay.Store(true)
|
||
|
serviceEntries = append(serviceEntries, entry)
|
||
|
case <-ticker.C:
|
||
|
if false == delay.Load().(bool) {
|
||
|
ticker.Stop()
|
||
|
timerStopped <- struct{}{}
|
||
|
return
|
||
|
}
|
||
|
delay.Store(false)
|
||
|
}
|
||
|
}
|
||
|
}(entryChan)
|
||
|
|
||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(30))
|
||
|
defer cancel()
|
||
|
err = resolver.Browse(ctx, service, domain, entryChan)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("Failed to browse %v", err)
|
||
|
}
|
||
|
|
||
|
select {
|
||
|
case <-ctx.Done():
|
||
|
case <-timerStopped:
|
||
|
}
|
||
|
|
||
|
return serviceEntries, nil
|
||
|
}
|