diff --git a/confluent_client/bin/nodediscover b/confluent_client/bin/nodediscover index cc2ffee6..8504a501 100755 --- a/confluent_client/bin/nodediscover +++ b/confluent_client/bin/nodediscover @@ -371,8 +371,8 @@ async def assign_discovery(options, session, needid=True): if exitcode: sys.exit(exitcode) -async def blocking_scan(session): - list([x async for x in session.update('/discovery/rescan', {'rescan': 'start'})]) +async def blocking_scan(session, aggressive=False): + list([x async for x in session.update('/discovery/rescan', {'rescan': 'aggressive' if aggressive else 'start'})]) while(list([x async for x in session.read('/discovery/rescan')])[0].get('scanning', False)): await asyncio.sleep(0.5) list([x async for x in session.update('/networking/macs/rescan', {'rescan': 'start'})]) @@ -388,6 +388,9 @@ async def main(): # flush to clear old data out? (e.g. no good way to age pxe data) # also delete discovery datum... more targeted # defect: -t lenovo-imm returns all + parser.add_option('-a', '--aggresive', dest='aggressive', + help='Use aggressive scanning and fingerprinting that is slower and more intrusive', + action='store_true') parser.add_option('-m', '--model', dest='model', help='Operate with nodes matching the specified model ' 'number', metavar='MODEL') @@ -443,7 +446,7 @@ async def main(): if args[0] == 'unsubscribe': await subscribe_discovery(options, session, False, args[1]) if args[0] == 'rescan': - await blocking_scan(session) + await blocking_scan(session, aggressive=options.aggressive) print("Rescan complete") diff --git a/confluent_client/doc/man/nodediscover.ronn b/confluent_client/doc/man/nodediscover.ronn index 5750c6c8..8d5a96d1 100644 --- a/confluent_client/doc/man/nodediscover.ronn +++ b/confluent_client/doc/man/nodediscover.ronn @@ -75,6 +75,9 @@ the nodes. ## OPTIONS +* `-a`, `--aggressive`: + Perform more aggressive scanning and fingerprinting. This will probe as many addresses as it can find with ssh and https connections. By default more targeted measures are used + that will only tend to probe things that are easily detectable as devices implementing explicit mechanisms for detection and identification. * `-m MODEL`, `--model=MODEL`: Operate with nodes matching the specified model number * `-s SERIAL`, `--serial=SERIAL`: diff --git a/confluent_server/confluent/discovery/core.py b/confluent_server/confluent/discovery/core.py index 643ae35a..f6cd6d8a 100644 --- a/confluent_server/confluent/discovery/core.py +++ b/confluent_server/confluent/discovery/core.py @@ -95,6 +95,8 @@ import socket import socket as nsocket import aiohmi.util.webclient as webclient +from confluent import netutil, neighutil + autosensors = set() scanner = None @@ -574,9 +576,13 @@ async def handle_api_request(configmanager, inputdata, operation, pathcomponents return handle_read_api_request(pathcomponents) elif (operation in ('update', 'create') and pathcomponents == ['discovery', 'rescan']): - if inputdata != {'rescan': 'start'}: + rescanmode = inputdata.get('rescan', None) + if rescanmode == 'aggressive': + await rescan(aggressive=True) + elif rescanmode == 'start': + await rescan() + else: raise exc.InvalidArgumentException() - await rescan() return (msg.KeyValueData({'rescan': 'started'}),) elif operation in ('update', 'create') and pathcomponents[:2] == ['discovery', 'subscriptions']: target = pathcomponents[2] @@ -1669,13 +1675,13 @@ async def _periodic_recheck(configmanager): configmanager) -async def rescan(): +async def rescan(aggressive=False): _map_unique_ids() global scanner if scanner: return else: - scanner = tasks.spawn_task(blocking_scan()) + scanner = tasks.spawn_task(blocking_scan(aggressive)) await remotescan() async def remotescan(): @@ -1688,15 +1694,40 @@ async def remotescan(): log.log({'error': 'Unexpected problem asking {} for discovery notifications'.format(remagent)}) -async def blocking_scan(): +async def blocking_scan(aggressive=False): global scanner + if aggressive: + pingscan = tasks.spawn_task(netutil.ping_everywhere()) slpscan = tasks.spawn_task(slp.active_scan(safe_detected, slp)) ssdpscan = tasks.spawn_task(ssdp.active_scan(safe_detected, ssdp)) await slpscan await ssdpscan - #ssdpscan.wait() + if aggressive: + pingscan_result = await pingscan + else: + pingscan_result = {} + gencheckers = [] + for iface in pingscan_result: + for ipa in pingscan_result[iface]: + hwaddr = await neighutil.get_hwaddr(ipa) + if not hwaddr: + continue + if hwaddr in known_info: + continue + gencheckers.append(generic_eval(ipa)) + if gencheckers: + await asyncio.gather(*gencheckers, return_exceptions=True) scanner = None + +async def generic_eval(address): + peerdata = {'addresses': [(address, 443)]} + resdata = await ssdp.check_fish(('/redfish/v1/', peerdata)) + if not resdata: + return None + safe_detected(resdata) + + def start_detection(): global attribwatcher global rechecker diff --git a/confluent_server/confluent/discovery/protocols/ssdp.py b/confluent_server/confluent/discovery/protocols/ssdp.py index da07ebbc..77dc8eea 100644 --- a/confluent_server/confluent/discovery/protocols/ssdp.py +++ b/confluent_server/confluent/discovery/protocols/ssdp.py @@ -127,7 +127,10 @@ def _process_snoop(peer, rsp, mac, known_peers, newmacs, peerbymacaddress, byeha tasks.spawn(check_fish_handler(handler, peerdata, known_peers, newmacs, peerbymacaddress, machandlers, mac, peer, targurl, targtype)) async def check_fish_handler(handler, peerdata, known_peers, newmacs, peerbymacaddress, machandlers, mac, peer, targurl, targtype): - retdata = await check_fish((targurl, peerdata, targtype)) + try: + retdata = await check_fish((targurl, peerdata, targtype)) + except Exception: + return if retdata: known_peers.add(peer) newmacs.add(mac) @@ -533,7 +536,7 @@ async def check_fish(urldata, port=443, verifycallback=None): url, data = urldata targtype = 'service:redfish-bmc' try: - wc = webclient.WebConnection(_get_svrip(data), port, verifycallback=verifycallback) + wc = webclient.WebConnection(_get_svrip(data), port, verifycallback=verifycallback, timeout=3) peerinfo = await wc.grab_json_response(url, headers={'Accept': 'application/json', 'Host': 'credible-bmc'}) except socket.error: return None @@ -588,6 +591,8 @@ async def check_fish(urldata, port=443, verifycallback=None): else: data['services'] = [targtype] data['uuid'] = peerinfo['UUID'].lower() + if len(data['uuid']) == 32: # add hyphens to match standard UUID format + data['uuid'] = '-'.join([data['uuid'][:8], data['uuid'][8:12], data['uuid'][12:16], data['uuid'][16:20], data['uuid'][20:]]) return data return None