package session import ( "encoding/json" "fmt" "log" "net" "strconv" "strings" "sync" omd "git.loafle.net/overflow/model/discovery" omm "git.loafle.net/overflow/model/meta" omu "git.loafle.net/overflow/model/util" ouej "git.loafle.net/overflow/util-go/encoding/json" ounc "git.loafle.net/overflow/util-go/net/cidr" "git.loafle.net/overflow_scanner/probe/discovery/types" "git.loafle.net/overflow_scanner/probe/internal/pcap" ) type DiscoverySession interface { InitWithRequest(request types.DiscoveryRequest) error DiscoveryRequest() types.DiscoveryRequest PCapScanner() pcap.PCapScanner Zone() *omd.Zone Host() *omd.Host Port() *omd.Port DiscoveryConfig() *omd.DiscoveryConfig DiscoverHost() *omd.DiscoverHost DiscoverPort() *omd.DiscoverPort DiscoverService() *omd.DiscoverService TargetHosts() []net.IP IsTargetHost(host *omd.Host) bool IsTargetPort(port *omd.Port) bool IsTargetService(service *omd.Service) bool SetDiscoveryDelegator(chan<- interface{}) AddHost(discoveredBy string, host *omd.Host, meta map[string]string) *omd.Host AddPort(discoveredBy string, port *omd.Port, meta map[string]string) *omd.Port AddService(discoveredBy string, service *omd.Service, meta map[string]string) *omd.Service AddServiceUnknown(discoveredBy string, port *omd.Port) *omd.Service DiscoveredHost(address string) *omd.Host DiscoveredAllHosts(includeMac bool) map[string]*omd.Host DiscoveredPort(host *omd.Host, portNumber int) map[string]*omd.Port DiscoveredAllPorts() map[*omd.Host]map[json.Number]map[string]*omd.Port DiscoveredService(port *omd.Port, name string) *omd.Service DiscoveredServices(port *omd.Port) map[string]*omd.Service DiscoveredAllServices() map[*omd.Port]map[string]*omd.Service Shutdown() StopChan() <-chan struct{} } type ofDiscoverySession struct { discoveryRequest types.DiscoveryRequest pCapScanner pcap.PCapScanner zone *omd.Zone host *omd.Host port *omd.Port discoveryConfig *omd.DiscoveryConfig discoverHost *omd.DiscoverHost discoverPort *omd.DiscoverPort discoverService *omd.DiscoverService targetHosts []net.IP targetHostAddresses map[string]bool targetPortNumbers map[int]bool discoveryDelegator chan<- interface{} discoveredMtx sync.RWMutex hosts map[string]*omd.Host includeMachosts map[string]*omd.Host ports map[*omd.Host]map[json.Number]map[string]*omd.Port services map[*omd.Port]map[string]*omd.Service stopChan chan struct{} } func (ds *ofDiscoverySession) init(request types.DiscoveryRequest) { ds.discoveryRequest = request ds.zone = nil ds.host = nil ds.port = nil ds.discoverHost = nil ds.discoverPort = nil ds.discoverService = nil ds.pCapScanner = nil ds.targetHosts = nil ds.targetHostAddresses = nil ds.targetPortNumbers = nil ds.hosts = make(map[string]*omd.Host) ds.includeMachosts = make(map[string]*omd.Host) ds.ports = make(map[*omd.Host]map[json.Number]map[string]*omd.Port) ds.services = make(map[*omd.Port]map[string]*omd.Service) ds.stopChan = make(chan struct{}) } func (ds *ofDiscoverySession) InitWithRequest(request types.DiscoveryRequest) error { ds.init(request) params := request.Params() switch request.RequestType() { case types.DiscoveryRequestTypeHost: if nil == params || 2 != len(params) { return fmt.Errorf("Parameter is not valid") } zone, ok := params[0].(*omd.Zone) if !ok { return fmt.Errorf("Zone of parameter is not valid") } ds.setZone(zone) discoverHost, ok := params[1].(*omd.DiscoverHost) if !ok { return fmt.Errorf("DiscoverHost of parameter is not valid") } if err := ds.setDiscoverHost(discoverHost); nil != err { return err } if nil == discoverHost.DiscoveryConfig { return fmt.Errorf("DiscoveryConfig of parameter is not valid") } ds.discoveryConfig = discoverHost.DiscoveryConfig case types.DiscoveryRequestTypePort: if nil == params || 2 != len(params) { return fmt.Errorf("Parameter is not valid") } host, ok := params[0].(*omd.Host) if !ok { return fmt.Errorf("Host of parameter is not valid") } ds.setHost(host) discoverPort, ok := params[1].(*omd.DiscoverPort) if !ok { return fmt.Errorf("DiscoverPort of parameter is not valid") } if err := ds.setDiscoverPort(discoverPort); nil != err { return err } if nil == discoverPort.DiscoveryConfig { return fmt.Errorf("DiscoveryConfig of parameter is not valid") } ds.discoveryConfig = discoverPort.DiscoveryConfig case types.DiscoveryRequestTypeService: if nil == params || 2 != len(params) { return fmt.Errorf("Parameter is not valid") } port, ok := params[0].(*omd.Port) if !ok { return fmt.Errorf("Port of parameter is not valid") } ds.setPort(port) discoverService, ok := params[1].(*omd.DiscoverService) if !ok { return fmt.Errorf("DiscoverService of parameter is not valid") } if err := ds.setDiscoverService(discoverService); nil != err { return err } if nil == discoverService.DiscoveryConfig { return fmt.Errorf("DiscoveryConfig of parameter is not valid") } ds.discoveryConfig = discoverService.DiscoveryConfig } _pCapScanner := pcap.NewPCapScanner(ds.zone) if err := _pCapScanner.Start(); nil == err { ds.pCapScanner = _pCapScanner log.Print("Privileged mode") } else { log.Print(err) log.Print("Unprivileged mode") } return nil } func (ds *ofDiscoverySession) PCapScanner() pcap.PCapScanner { return ds.pCapScanner } func (ds *ofDiscoverySession) SetDiscoveryDelegator(discoveryDelegator chan<- interface{}) { ds.discoveryDelegator = discoveryDelegator } func (ds *ofDiscoverySession) DiscoveryRequest() types.DiscoveryRequest { return ds.discoveryRequest } func (ds *ofDiscoverySession) Zone() *omd.Zone { return ds.zone } func (ds *ofDiscoverySession) Host() *omd.Host { return ds.host } func (ds *ofDiscoverySession) Port() *omd.Port { return ds.port } func (ds *ofDiscoverySession) DiscoveryConfig() *omd.DiscoveryConfig { return ds.discoveryConfig } func (ds *ofDiscoverySession) DiscoverHost() *omd.DiscoverHost { return ds.discoverHost } func (ds *ofDiscoverySession) DiscoverPort() *omd.DiscoverPort { return ds.discoverPort } func (ds *ofDiscoverySession) DiscoverService() *omd.DiscoverService { return ds.discoverService } func (ds *ofDiscoverySession) TargetHosts() []net.IP { return ds.targetHosts } func (ds *ofDiscoverySession) IsTargetHost(host *omd.Host) bool { if nil == ds.targetHostAddresses { return false } if _, ok := ds.targetHostAddresses[host.Address]; ok { return true } return false } func (ds *ofDiscoverySession) IsTargetPort(port *omd.Port) bool { if nil == ds.targetPortNumbers || 0 == len(ds.targetPortNumbers) { return false } portNumber, err := ouej.NumberToInt(port.PortNumber) if nil != err { return false } if !ds.IsTargetHost(port.Host) { return false } if _, ok := ds.targetPortNumbers[portNumber]; ok { return true } return false } func (ds *ofDiscoverySession) IsTargetService(service *omd.Service) bool { if nil == ds.discoverService { return false } if !ds.IsTargetPort(service.Port) { return false } return true } func (ds *ofDiscoverySession) AddHost(discoveredBy string, host *omd.Host, meta map[string]string) *omd.Host { ds.discoveredMtx.Lock() defer ds.discoveredMtx.Unlock() h, modified := ds.findHost(host) if "" == h.Mac && "" != host.Mac { h.Mac = host.Mac modified = true } if "" == h.OsType && "" != host.OsType { h.OsType = host.OsType modified = true } if omd.DefaultHostType == h.HostType && "" != host.HostType { h.HostType = host.HostType modified = true } if omd.DefaultHostVendor == h.HostVendor && "" != host.HostVendor { h.HostVendor = host.HostVendor modified = true } if omd.DefaultHostModel == h.HostModel && "" != host.HostModel { h.HostModel = host.HostModel modified = true } discoveredBys, discoveredByModified := ds.appendDiscoveredBy(discoveredBy, h.DiscoveredBy) if discoveredByModified { h.DiscoveredBy = discoveredBys modified = true } metas, metaModified := ds.appendMeta(discoveredBy, h.Meta, meta) if metaModified { h.Meta = metas modified = true } h, addtionalModified := ds.addtionalHost(discoveredBy, h) if addtionalModified { modified = true } if modified { ds.delegate(h) } return h } func (ds *ofDiscoverySession) AddPort(discoveredBy string, port *omd.Port, meta map[string]string) *omd.Port { ds.discoveredMtx.Lock() defer ds.discoveredMtx.Unlock() p, modified := ds.findPort(port) discoveredBys, discoveredByModified := ds.appendDiscoveredBy(discoveredBy, p.DiscoveredBy) if discoveredByModified { p.DiscoveredBy = discoveredBys modified = true } metas, metaModified := ds.appendMeta(discoveredBy, p.Meta, meta) if metaModified { p.Meta = metas modified = metaModified } if modified { ds.delegate(p) } return p } func (ds *ofDiscoverySession) AddService(discoveredBy string, service *omd.Service, meta map[string]string) *omd.Service { ds.discoveredMtx.Lock() defer ds.discoveredMtx.Unlock() s, modified := ds.findService(service) if omd.DefaultServiceType == s.ServiceType && "" != service.ServiceType { s.ServiceType = service.ServiceType modified = true } if omd.DefaultServiceVendor == s.ServiceVendor && "" != service.ServiceVendor { s.ServiceVendor = service.ServiceVendor modified = true } if omd.DefaultServiceVersion == s.ServiceVersion && "" != service.ServiceVersion { s.ServiceVersion = service.ServiceVersion modified = true } discoveredBys, discoveredByModified := ds.appendDiscoveredBy(discoveredBy, s.DiscoveredBy) if discoveredByModified { s.DiscoveredBy = discoveredBys modified = true } metas, metaModified := ds.appendMeta(discoveredBy, s.Meta, meta) if metaModified { s.Meta = metas modified = metaModified } s, addtionalModified := ds.addtionalService(discoveredBy, s) if addtionalModified { modified = true } if modified { ds.delegate(s) } return s } func (ds *ofDiscoverySession) AddServiceUnknown(discoveredBy string, port *omd.Port) *omd.Service { if nil == ds.DiscoveredServices(port) { s := omd.NewService( port, omm.ToMetaCryptoType(omm.MetaCryptoTypeEnumNONE), omd.UnknownServiceKey, ) s.Name = "" s.ServiceType = omm.MetaServiceTypeEnumUNKNOWN.String() s.ServiceVendor = "" s.ServiceVersion = "" s.DiscoveredDate = omu.NowPtr() return ds.AddService(discoveredBy, s, nil) } return nil } func (ds *ofDiscoverySession) DiscoveredHost(address string) *omd.Host { ds.discoveredMtx.RLock() defer ds.discoveredMtx.RUnlock() h, ok := ds.hosts[address] if !ok { return nil } return h } func (ds *ofDiscoverySession) DiscoveredAllHosts(includeMac bool) map[string]*omd.Host { if includeMac { return ds.includeMachosts } return ds.hosts } func (ds *ofDiscoverySession) DiscoveredPort(host *omd.Host, portNumber int) map[string]*omd.Port { ds.discoveredMtx.RLock() defer ds.discoveredMtx.RUnlock() h, _ := ds.findHost(host) if nil == h { return nil } hostPorts, ok := ds.ports[h] if !ok { return nil } ports, ok := hostPorts[json.Number(strconv.Itoa(portNumber))] if !ok { return nil } return ports } func (ds *ofDiscoverySession) DiscoveredAllPorts() map[*omd.Host]map[json.Number]map[string]*omd.Port { return ds.ports } func (ds *ofDiscoverySession) DiscoveredService(port *omd.Port, name string) *omd.Service { ds.discoveredMtx.RLock() defer ds.discoveredMtx.RUnlock() p, _ := ds.findPort(port) if nil == p { return nil } services, ok := ds.services[p] if !ok { return nil } s, ok := services[name] if !ok { return nil } return s } func (ds *ofDiscoverySession) DiscoveredServices(port *omd.Port) map[string]*omd.Service { ds.discoveredMtx.RLock() defer ds.discoveredMtx.RUnlock() p, _ := ds.findPort(port) if nil == p { return nil } services, ok := ds.services[p] if !ok { return nil } return services } func (ds *ofDiscoverySession) DiscoveredAllServices() map[*omd.Port]map[string]*omd.Service { return ds.services } func (ds *ofDiscoverySession) StopChan() <-chan struct{} { return ds.stopChan } func (ds *ofDiscoverySession) delegate(data interface{}) { switch v := data.(type) { case *omd.Host: if !ds.IsTargetHost(v) { return } case *omd.Port: if !ds.IsTargetPort(v) { return } case *omd.Service: if !ds.IsTargetService(v) { return } default: return } if nil != ds.discoveryDelegator { ds.discoveryDelegator <- data } } func (ds *ofDiscoverySession) Shutdown() { close(ds.stopChan) } func (ds *ofDiscoverySession) findHost(host *omd.Host) (h *omd.Host, modified bool) { modified = false var ok bool if "" == host.HostType { host.HostType = omd.DefaultHostType } if "" == host.OsType { host.OsType = omd.DefaultOsType } h, ok = ds.hosts[host.Address] if !ok { ds.hosts[host.Address] = host if "" != host.Mac { ds.includeMachosts[host.Address] = host } h = host modified = true } return } func (ds *ofDiscoverySession) findPort(port *omd.Port) (p *omd.Port, modified bool) { modified = false var ok bool h, _ := ds.findHost(port.Host) if nil == h { return } hostPorts, ok := ds.ports[h] if !ok { ds.ports[h] = make(map[json.Number]map[string]*omd.Port) hostPorts = ds.ports[h] } ports, ok := hostPorts[port.PortNumber] if !ok { hostPorts[port.PortNumber] = make(map[string]*omd.Port) ports = hostPorts[port.PortNumber] } p, ok = ports[port.MetaPortType.Key] if !ok { ports[port.MetaPortType.Key] = port p = ports[port.MetaPortType.Key] modified = true } return } func (ds *ofDiscoverySession) findService(service *omd.Service) (s *omd.Service, modified bool) { modified = false var ok bool p, _ := ds.findPort(service.Port) if nil == p { return } services, ok := ds.services[p] if !ok { ds.services[p] = make(map[string]*omd.Service) services = ds.services[p] } else { if _, ok := services[omd.UnknownServiceKey]; ok { delete(services, omd.UnknownServiceKey) } } s, ok = services[service.Key] if !ok { services[service.Key] = service s = services[service.Key] modified = true } return } func (ds *ofDiscoverySession) addtionalHost(discoveredBy string, host *omd.Host) (h *omd.Host, modified bool) { h = host modified = false if nil == host.Meta { return } meta, ok := host.Meta[discoveredBy] if !ok || nil == meta || 0 == len(meta) { return } switch discoveredBy { case "mDNS": _h, _modified := ds.addtionalHostMDNS(h, meta) if _modified { h = _h modified = true } } return } func (ds *ofDiscoverySession) addtionalHostMDNS(host *omd.Host, meta map[string]string) (h *omd.Host, modified bool) { h = host modified = false _vendor, ok := meta["vendor"] if ok || "" != _vendor { h.HostVendor = _vendor modified = true } _model, ok := meta["model"] if ok || "" != _model { h.HostModel = _model if "Synology" == _vendor && strings.Contains(_model, "DS1817+") { h.HostType = "NAS" } modified = true } return } func (ds *ofDiscoverySession) addtionalService(discoveredBy string, service *omd.Service) (s *omd.Service, modified bool) { s = service modified = false if nil == service.Meta { return } meta, ok := service.Meta[discoveredBy] if !ok || nil == meta || 0 == len(meta) { return } switch discoveredBy { case "mDNS": _s, _modified := ds.addtionalServiceMDNS(s, meta) if _modified { s = _s modified = true } } return } func (ds *ofDiscoverySession) addtionalServiceMDNS(service *omd.Service, meta map[string]string) (s *omd.Service, modified bool) { s = service modified = false portNumber, err := ouej.NumberToInt(service.Port.PortNumber) if nil != err { log.Print(err) return } switch portNumber { case 515: if "printer" == service.Key { _h := service.Port.Host _h.HostType = "PRINTER" _h.Name = service.Name ds.delegate(_h) } case 9100: if "pdl-datastream" == service.Key { _h := service.Port.Host _h.HostType = "PRINTER" _h.Name = service.Name ds.delegate(_h) } } return } func (ds *ofDiscoverySession) appendDiscoveredBy(discoveredBy string, oriDiscoveredBy []string) (resultDiscoveredBy []string, modified bool) { modified = false if "" == discoveredBy { resultDiscoveredBy = oriDiscoveredBy return } if nil == oriDiscoveredBy { resultDiscoveredBy = []string{ discoveredBy, } modified = true return } for _, v := range oriDiscoveredBy { if v == discoveredBy { resultDiscoveredBy = oriDiscoveredBy return } } oriDiscoveredBy = append(oriDiscoveredBy, discoveredBy) resultDiscoveredBy = oriDiscoveredBy modified = true return } func (ds *ofDiscoverySession) appendMeta(discoveredBy string, oriMetas map[string]map[string]string, newMeta map[string]string) (resultMetas map[string]map[string]string, modified bool) { modified = false if nil == newMeta || 0 == len(newMeta) { resultMetas = oriMetas return } if nil == oriMetas { resultMetas = map[string]map[string]string{ discoveredBy: newMeta, } modified = true return } oriMeta, ok := oriMetas[discoveredBy] if !ok { oriMetas[discoveredBy] = newMeta resultMetas = oriMetas return } LOOP: for k, v := range newMeta { _v, _ok := oriMeta[k] if !_ok { oriMeta[k] = v modified = true continue LOOP } if v == _v { continue LOOP } oriMeta[k] = fmt.Sprintf("%s|||%s", _v, v) modified = true } resultMetas = oriMetas return } func (ds *ofDiscoverySession) setZone(zone *omd.Zone) { if nil == zone { return } ds.zone = zone } func (ds *ofDiscoverySession) setHost(host *omd.Host) { if nil == host { return } ds.setZone(host.Zone) ds.host = host } func (ds *ofDiscoverySession) setPort(port *omd.Port) { if nil == port { return } ds.setHost(port.Host) ds.port = port } func (ds *ofDiscoverySession) setDiscoverHost(discoverHost *omd.DiscoverHost) error { if nil == discoverHost { return nil } if err := ds.setTargetHosts(ds.zone, discoverHost); nil != err { return err } ds.discoverHost = discoverHost return ds.setDiscoverPort(discoverHost.DiscoverPort) } func (ds *ofDiscoverySession) setDiscoverPort(discoverPort *omd.DiscoverPort) error { if nil == discoverPort { return nil } if err := ds.setTargetPortNumbers(discoverPort); nil != err { return err } ds.discoverPort = discoverPort return ds.setDiscoverService(discoverPort.DiscoverService) } func (ds *ofDiscoverySession) setDiscoverService(discoverService *omd.DiscoverService) error { if nil == discoverService { return nil } ds.discoverService = discoverService return nil } func (ds *ofDiscoverySession) setTargetHosts(zone *omd.Zone, discoverHost *omd.DiscoverHost) error { cr, err := ounc.NewCIDRRanger(zone.Network) if nil != err { return err } var firstIP net.IP if "" != discoverHost.FirstScanRange { firstIP = net.ParseIP(discoverHost.FirstScanRange) if nil == firstIP { return fmt.Errorf("IP(%v) of FirstScanRange host is not valid", firstIP) } } var lastIP net.IP if "" != discoverHost.LastScanRange { lastIP = net.ParseIP(discoverHost.LastScanRange) if nil == lastIP { return fmt.Errorf("IP(%v) of LastScanRange host is not valid", lastIP) } } includeIPs := make([]net.IP, 0) for _, iHost := range discoverHost.IncludeHosts { iIP := net.ParseIP(iHost) if nil == iIP { return fmt.Errorf("IP(%v) of include host is not valid", iHost) } includeIPs = append(includeIPs, iIP) } excludeIPs := make([]net.IP, 0) for _, eHost := range discoverHost.ExcludeHosts { eIP := net.ParseIP(eHost) if nil == eIP { return fmt.Errorf("IP(%v) of exclude host is not valid", eHost) } excludeIPs = append(excludeIPs, eIP) } ranges, err := cr.Ranges(firstIP, lastIP, includeIPs, excludeIPs) if nil != err { return err } ds.targetHosts = ranges ds.targetHostAddresses = make(map[string]bool, len(ranges)) for _, ip := range ranges { ds.targetHostAddresses[ip.String()] = true } return nil } func (ds *ofDiscoverySession) setTargetPortNumbers(discoverPort *omd.DiscoverPort) error { if nil == discoverPort { return nil } ds.targetPortNumbers = make(map[int]bool, 0) LOOP: for portNumber := discoverPort.FirstScanRange; portNumber < discoverPort.LastScanRange; portNumber++ { if nil != discoverPort.ExcludePorts { for _, exPortNumber := range discoverPort.ExcludePorts { if portNumber == exPortNumber { continue LOOP } } } ds.targetPortNumbers[portNumber] = true } return nil } var discoverySessionPool sync.Pool func RetainDiscoverySession() *ofDiscoverySession { v := discoverySessionPool.Get() var ds *ofDiscoverySession if v == nil { ds = &ofDiscoverySession{} } else { ds = v.(*ofDiscoverySession) } return ds } func ReleaseDiscoverySession(ds *ofDiscoverySession) { close(ds.stopChan) if nil != ds.pCapScanner { ds.pCapScanner.Stop() } ds.pCapScanner = nil ds.discoveryRequest = nil ds.zone = nil ds.host = nil ds.port = nil ds.discoveryConfig = nil ds.discoverHost = nil ds.discoverPort = nil ds.discoverService = nil ds.targetHosts = nil ds.targetHostAddresses = nil ds.targetPortNumbers = nil ds.discoveryDelegator = nil ds.includeMachosts = nil ds.hosts = nil ds.ports = nil ds.services = nil discoverySessionPool.Put(ds) }