probe/discovery/protocol/mdns/mdns.go
crusader b3e58106a9 ing
2018-09-17 23:18:49 +09:00

261 lines
5.7 KiB
Go

package mdns
import (
"context"
"fmt"
"log"
"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/discovery/session"
"github.com/grandcat/zeroconf"
)
func Scan(discoverySession session.DiscoverySession) error {
serviceEntries, err := browse("_services._dns-sd._udp", "local")
if nil != err {
log.Print("Cannot find service ", err)
return nil
}
metaIPTypeEnum := omm.ToMetaIPTypeEnum(discoverySession.Zone().MetaIPType)
SERVICE_LOOP:
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)
continue SERVICE_LOOP
}
ENTRY_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 := omd.NewHost(
discoverySession.Zone(),
omm.ToMetaIPType(metaIPTypeEnum),
ipv4.String(),
)
h.Name = hostName
h.DiscoveredDate = omu.NowPtr()
h = discoverySession.AddHost(
omm.ToMetaDiscovererType(omm.MetaDiscovererTypeEnumMDNS),
h,
meta,
)
if 1 > port {
continue ENTRY_LOOP
}
p := omd.NewPort(
h,
metaPortType,
port,
)
p.DiscoveredDate = omu.NowPtr()
p = discoverySession.AddPort(
omm.ToMetaDiscovererType(omm.MetaDiscovererTypeEnumMDNS),
p,
meta,
)
s := omd.NewService(
p,
metaCryptoType,
omm.MetaServiceTypeEnumUNKNOWN.String(),
)
s.Name = serviceName
s.DiscoveredDate = omu.NowPtr()
discoverySession.AddService(
omm.ToMetaDiscovererType(omm.MetaDiscovererTypeEnumMDNS),
s,
meta,
)
}
case omm.MetaIPTypeEnumV6:
for _, ipv6 := range entry.AddrIPv6 {
h := omd.NewHost(
discoverySession.Zone(),
omm.ToMetaIPType(metaIPTypeEnum),
ipv6.String(),
)
h.Name = hostName
h.DiscoveredDate = omu.NowPtr()
h = discoverySession.AddHost(
omm.ToMetaDiscovererType(omm.MetaDiscovererTypeEnumMDNS),
h,
meta,
)
if 1 > port {
continue ENTRY_LOOP
}
p := omd.NewPort(
h,
metaPortType,
port,
)
p.DiscoveredDate = omu.NowPtr()
p = discoverySession.AddPort(
omm.ToMetaDiscovererType(omm.MetaDiscovererTypeEnumMDNS),
p,
meta,
)
s := omd.NewService(
p,
metaCryptoType,
omm.MetaServiceTypeEnumUNKNOWN.String(),
)
s.Name = serviceName
s.DiscoveredDate = omu.NowPtr()
discoverySession.AddService(
omm.ToMetaDiscovererType(omm.MetaDiscovererTypeEnumMDNS),
s,
meta,
)
}
}
}
select {
case <-discoverySession.StopChan():
return nil
default:
}
}
return nil
}
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], "_")
name = _name
portType = _portType
cryptoType = "NONE"
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(3))
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
}