package session import ( "encoding/json" "fmt" "log" "net" "strconv" "strings" "sync" omd "git.loafle.net/overflow/model/discovery" 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 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 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) map[string]*omd.Service DiscoveredAllServices() map[*omd.Port]map[string]map[string]*omd.Service 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 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]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.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]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") } discoverHost, ok := params[1].(*omd.DiscoverHost) if !ok { return fmt.Errorf("DiscoverHost of parameter is not valid") } if nil == discoverHost.DiscoveryConfig { return fmt.Errorf("DiscoveryConfig of parameter is not valid") } if ts, err := ds.getTargetHosts(zone, discoverHost); nil != err { return err } else { ds.targetHosts = ts } ds.discoveryConfig = discoverHost.DiscoveryConfig ds.setZone(zone) ds.setDiscoverHost(discoverHost) 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") } discoverPort, ok := params[1].(*omd.DiscoverPort) if !ok { return fmt.Errorf("DiscoverPort of parameter is not valid") } if nil == discoverPort.DiscoveryConfig { return fmt.Errorf("DiscoveryConfig of parameter is not valid") } ds.discoveryConfig = discoverPort.DiscoveryConfig ds.setHost(host) ds.setDiscoverPort(discoverPort) 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") } discoverService, ok := params[1].(*omd.DiscoverService) if !ok { return fmt.Errorf("DiscoverService of parameter is not valid") } if nil == discoverService.DiscoveryConfig { return fmt.Errorf("DiscoveryConfig of parameter is not valid") } ds.discoveryConfig = discoverService.DiscoveryConfig ds.setPort(port) ds.setDiscoverService(discoverService) } _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) 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) 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) map[string]*omd.Service { ds.discoveredMtx.RLock() defer ds.discoveredMtx.RUnlock() p, _ := ds.findPort(port) if nil == p { return nil } portServices, ok := ds.services[p] if !ok { return nil } services, ok := portServices[name] if !ok { return nil } return services } func (ds *ofDiscoverySession) DiscoveredAllServices() map[*omd.Port]map[string]map[string]*omd.Service { return ds.services } func (ds *ofDiscoverySession) StopChan() <-chan struct{} { return ds.stopChan } func (ds *ofDiscoverySession) delegate(data interface{}) { if nil != ds.discoveryDelegator { ds.discoveryDelegator <- data } } 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 } portServices, ok := ds.services[p] if !ok { ds.services[p] = make(map[string]map[string]*omd.Service) portServices = ds.services[p] } services, ok := portServices[service.Key] if !ok { portServices[service.Key] = make(map[string]*omd.Service) services = portServices[service.Key] } s, ok = services[service.MetaCryptoType.Key] if !ok { services[service.MetaCryptoType.Key] = service s = services[service.MetaCryptoType.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) { if nil == discoverHost { return } ds.discoverHost = discoverHost ds.setDiscoverPort(discoverHost.DiscoverPort) } func (ds *ofDiscoverySession) setDiscoverPort(discoverPort *omd.DiscoverPort) { if nil == discoverPort { return } ds.discoverPort = discoverPort ds.setDiscoverService(discoverPort.DiscoverService) } func (ds *ofDiscoverySession) setDiscoverService(discoverService *omd.DiscoverService) { if nil == discoverService { return } ds.discoverService = discoverService } func (ds *ofDiscoverySession) getTargetHosts(zone *omd.Zone, discoverHost *omd.DiscoverHost) ([]net.IP, error) { cr, err := ounc.NewCIDRRanger(zone.Network) if nil != err { return nil, err } var firstIP net.IP if "" != discoverHost.FirstScanRange { firstIP = net.ParseIP(discoverHost.FirstScanRange) if nil == firstIP { return nil, 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 nil, 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 nil, 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 nil, 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 nil, err } return ranges, 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.discoveryDelegator = nil ds.includeMachosts = nil ds.hosts = nil ds.ports = nil ds.services = nil discoverySessionPool.Put(ds) }