mirror of
https://github.com/xcat2/confluent.git
synced 2024-11-21 17:11:58 +00:00
Add MegaRAC discovery support for recent MegaRAC
Create a generic redfish discovery and a MegaRAC specific variant. This should open the door for more generic common base redfish discovery for vaguely compatible implementations. For now, MegaRAC only overrides the default username and password (which is undefined in the redfish spec). Also, have SSDP recognize the variant, and tolerate odd nonsense like SSDP replies coming from all manner of odd port numbers (no way to make a sane firewall rule to capture that odd behavior, but at application level we have a chance).
This commit is contained in:
parent
2c2fe08d66
commit
1edfeba076
@ -75,6 +75,7 @@ import confluent.discovery.handlers.tsm as tsm
|
||||
import confluent.discovery.handlers.pxe as pxeh
|
||||
import confluent.discovery.handlers.smm as smm
|
||||
import confluent.discovery.handlers.xcc as xcc
|
||||
import confluent.discovery.handlers.megarac as megarac
|
||||
import confluent.exceptions as exc
|
||||
import confluent.log as log
|
||||
import confluent.messages as msg
|
||||
@ -113,6 +114,7 @@ nodehandlers = {
|
||||
'service:lenovo-smm': smm,
|
||||
'service:lenovo-smm2': smm,
|
||||
'lenovo-xcc': xcc,
|
||||
'megarac-bmc': megarac,
|
||||
'service:management-hardware.IBM:integrated-management-module2': imm,
|
||||
'pxe-client': pxeh,
|
||||
'onie-switch': None,
|
||||
@ -132,6 +134,7 @@ servicenames = {
|
||||
'service:lenovo-smm2': 'lenovo-smm2',
|
||||
'affluent-switch': 'affluent-switch',
|
||||
'lenovo-xcc': 'lenovo-xcc',
|
||||
'megarac-bmc': 'megarac-bmc',
|
||||
#'openbmc': 'openbmc',
|
||||
'service:management-hardware.IBM:integrated-management-module2': 'lenovo-imm2',
|
||||
'service:io-device.Lenovo:management-module': 'lenovo-switch',
|
||||
@ -147,6 +150,7 @@ servicebyname = {
|
||||
'lenovo-smm2': 'service:lenovo-smm2',
|
||||
'affluent-switch': 'affluent-switch',
|
||||
'lenovo-xcc': 'lenovo-xcc',
|
||||
'megarac-bmc': 'megarac-bmc',
|
||||
'lenovo-imm2': 'service:management-hardware.IBM:integrated-management-module2',
|
||||
'lenovo-switch': 'service:io-device.Lenovo:management-module',
|
||||
'thinkagile-storage': 'service:thinkagile-storagebmc',
|
||||
@ -452,7 +456,7 @@ def iterate_addrs(addrs, countonly=False):
|
||||
yield 1
|
||||
return
|
||||
yield addrs
|
||||
|
||||
|
||||
def _parameterize_path(pathcomponents):
|
||||
listrequested = False
|
||||
childcoll = True
|
||||
@ -542,7 +546,7 @@ async def handle_api_request(configmanager, inputdata, operation, pathcomponents
|
||||
if len(pathcomponents) > 2:
|
||||
raise Exception('TODO')
|
||||
currsubs = get_subscriptions()
|
||||
return [msg.ChildCollection(x) for x in currsubs]
|
||||
return [msg.ChildCollection(x) for x in currsubs]
|
||||
elif operation == 'retrieve':
|
||||
return handle_read_api_request(pathcomponents)
|
||||
elif (operation in ('update', 'create') and
|
||||
|
51
confluent_server/confluent/discovery/handlers/megarac.py
Normal file
51
confluent_server/confluent/discovery/handlers/megarac.py
Normal file
@ -0,0 +1,51 @@
|
||||
# Copyright 2024 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import confluent.discovery.handlers.redfishbmc as redfishbmc
|
||||
import eventlet.support.greendns
|
||||
|
||||
|
||||
getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
|
||||
|
||||
class NodeHandler(redfishbmc.NodeHandler):
|
||||
|
||||
def get_firmware_default_account_info(self):
|
||||
return ('admin', 'admin')
|
||||
|
||||
|
||||
def remote_nodecfg(nodename, cfm):
|
||||
cfg = cfm.get_node_attributes(
|
||||
nodename, 'hardwaremanagement.manager')
|
||||
ipaddr = cfg.get(nodename, {}).get('hardwaremanagement.manager', {}).get(
|
||||
'value', None)
|
||||
ipaddr = ipaddr.split('/', 1)[0]
|
||||
ipaddr = getaddrinfo(ipaddr, 0)[0][-1]
|
||||
if not ipaddr:
|
||||
raise Exception('Cannot remote configure a system without known '
|
||||
'address')
|
||||
info = {'addresses': [ipaddr]}
|
||||
nh = NodeHandler(info, cfm)
|
||||
nh.config(nodename)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import confluent.config.configmanager as cfm
|
||||
c = cfm.ConfigManager(None)
|
||||
import sys
|
||||
info = {'addresses': [[sys.argv[1]]]}
|
||||
print(repr(info))
|
||||
testr = NodeHandler(info, c)
|
||||
testr.config(sys.argv[2])
|
||||
|
269
confluent_server/confluent/discovery/handlers/redfishbmc.py
Normal file
269
confluent_server/confluent/discovery/handlers/redfishbmc.py
Normal file
@ -0,0 +1,269 @@
|
||||
# Copyright 2024 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import confluent.discovery.handlers.generic as generic
|
||||
import confluent.exceptions as exc
|
||||
import confluent.netutil as netutil
|
||||
import confluent.util as util
|
||||
import eventlet
|
||||
import eventlet.support.greendns
|
||||
import json
|
||||
try:
|
||||
from urllib import urlencode
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode
|
||||
|
||||
getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
|
||||
def get_host_interface_urls(wc, mginfo):
|
||||
returls = []
|
||||
hifurl = mginfo.get('HostInterfaces', {}).get('@odata.id', None)
|
||||
if not hifurl:
|
||||
return None
|
||||
hifinfo = wc.grab_json_response(hifurl)
|
||||
hifurls = hifinfo.get('Members', [])
|
||||
for hifurl in hifurls:
|
||||
hifurl = hifurl['@odata.id']
|
||||
hifinfo = wc.grab_json_response(hifurl)
|
||||
acturl = hifinfo.get('ManagerEthernetInterface', {}).get('@odata.id', None)
|
||||
if acturl:
|
||||
returls.append(acturl)
|
||||
return returls
|
||||
|
||||
|
||||
class NodeHandler(generic.NodeHandler):
|
||||
devname = 'BMC'
|
||||
|
||||
def __init__(self, info, configmanager):
|
||||
self.trieddefault = None
|
||||
self.targuser = None
|
||||
self.curruser = None
|
||||
self.currpass = None
|
||||
self.targpass = None
|
||||
self.nodename = None
|
||||
self.csrftok = None
|
||||
self.channel = None
|
||||
self.atdefault = True
|
||||
super(NodeHandler, self).__init__(info, configmanager)
|
||||
|
||||
def get_firmware_default_account_info(self):
|
||||
raise Exception('This must be subclassed')
|
||||
|
||||
def scan(self):
|
||||
c = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
|
||||
i = c.grab_json_response('/redfish/v1/')
|
||||
uuid = i.get('UUID', None)
|
||||
if uuid:
|
||||
self.info['uuid'] = uuid.lower()
|
||||
|
||||
def validate_cert(self, certificate):
|
||||
# broadly speaking, merely checks consistency moment to moment,
|
||||
# but if https_cert gets stricter, this check means something
|
||||
fprint = util.get_fingerprint(self.https_cert)
|
||||
return util.cert_matches(fprint, certificate)
|
||||
|
||||
def _get_wc(self):
|
||||
defuser, defpass = self.get_firmware_default_account_info()
|
||||
wc = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
|
||||
wc.set_basic_credentials(defuser, defpass)
|
||||
wc.set_header('Content-Type', 'application/json')
|
||||
authmode = 0
|
||||
if not self.trieddefault:
|
||||
rsp, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
if status == 403:
|
||||
self.trieddefault = True
|
||||
chgurl = None
|
||||
rsp = json.loads(rsp)
|
||||
currerr = rsp.get('error', {})
|
||||
ecode = currerr.get('code', None)
|
||||
if ecode.endswith('PasswordChangeRequired'):
|
||||
for einfo in currerr.get('@Message.ExtendedInfo', []):
|
||||
if einfo.get('MessageId', None).endswith('PasswordChangeRequired'):
|
||||
for msgarg in einfo.get('MessageArgs'):
|
||||
chgurl = msgarg
|
||||
break
|
||||
if chgurl:
|
||||
if self.targpass == defpass:
|
||||
raise Exception("Must specify a non-default password to onboard this BMC")
|
||||
wc.set_header('If-Match', '*')
|
||||
cpr = wc.grab_json_response_with_status(chgurl, {'Password': self.targpass}, method='PATCH')
|
||||
if cpr[1] >= 200 and cpr[1] < 300:
|
||||
self.curruser = defuser
|
||||
self.currpass = self.targpass
|
||||
wc.set_basic_credentials(self.curruser, self.currpass)
|
||||
_, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
tries = 10
|
||||
while status >= 300 and tries:
|
||||
eventlet.sleep(1)
|
||||
_, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
return wc
|
||||
|
||||
if status > 400:
|
||||
self.trieddefault = True
|
||||
if status == 401:
|
||||
wc.set_basic_credentials(self.DEFAULT_USER, self.targpass)
|
||||
rsp, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
if status == 200: # Default user still, but targpass
|
||||
self.currpass = self.targpass
|
||||
self.curruser = defuser
|
||||
return wc
|
||||
elif self.targuser != defuser:
|
||||
wc.set_basic_credentials(self.targuser, self.targpass)
|
||||
rsp, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
if status != 200:
|
||||
raise Exception("Target BMC does not recognize firmware default credentials nor the confluent stored credential")
|
||||
else:
|
||||
self.curruser = defuser
|
||||
self.currpass = defpass
|
||||
return wc
|
||||
if self.curruser:
|
||||
wc.set_basic_credentials(self.curruser, self.currpass)
|
||||
rsp, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
if status != 200:
|
||||
return None
|
||||
return wc
|
||||
wc.set_basic_credentials(self.targuser, self.targpass)
|
||||
rsp, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
if status != 200:
|
||||
return None
|
||||
self.curruser = self.targuser
|
||||
self.currpass = self.targpass
|
||||
return wc
|
||||
|
||||
def config(self, nodename):
|
||||
self.nodename = nodename
|
||||
creds = self.configmanager.get_node_attributes(
|
||||
nodename, ['secret.hardwaremanagementuser',
|
||||
'secret.hardwaremanagementpassword',
|
||||
'hardwaremanagement.manager', 'hardwaremanagement.method', 'console.method'],
|
||||
True)
|
||||
cd = creds.get(nodename, {})
|
||||
defuser, defpass = self.get_firmware_default_account_info()
|
||||
user, passwd, _ = self.get_node_credentials(
|
||||
nodename, creds, defuser, defpass)
|
||||
user = util.stringify(user)
|
||||
passwd = util.stringify(passwd)
|
||||
self.targuser = user
|
||||
self.targpass = passwd
|
||||
wc = self._get_wc()
|
||||
srvroot, status = wc.grab_json_response_with_status('/redfish/v1/')
|
||||
curruserinfo = {}
|
||||
authupdate = {}
|
||||
wc.set_header('Content-Type', 'application/json')
|
||||
if user != self.curruser:
|
||||
authupdate['UserName'] = user
|
||||
if passwd != self.currpass:
|
||||
authupdate['Password'] = passwd
|
||||
if authupdate:
|
||||
targaccturl = None
|
||||
asrv = srvroot.get('AccountService', {}).get('@odata.id')
|
||||
rsp, status = wc.grab_json_response_with_status(asrv)
|
||||
accts = rsp.get('Accounts', {}).get('@odata.id')
|
||||
rsp, status = wc.grab_json_response_with_status(accts)
|
||||
accts = rsp.get('Members', [])
|
||||
for accturl in accts:
|
||||
accturl = accturl.get('@odata.id', '')
|
||||
if accturl:
|
||||
rsp, status = wc.grab_json_response_with_status(accturl)
|
||||
if rsp.get('UserName', None) == self.curruser:
|
||||
targaccturl = accturl
|
||||
break
|
||||
else:
|
||||
raise Exception("Unable to identify Account URL to modify on this BMC")
|
||||
rsp, status = wc.grab_json_response_with_status(targaccturl, authupdate, method='PATCH')
|
||||
if status >= 300:
|
||||
raise Exception("Failed attempting to update credentials on BMC")
|
||||
wc.set_basic_credentials(user, passwd)
|
||||
_, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
tries = 10
|
||||
while tries and status >= 300:
|
||||
tries -= 1
|
||||
eventlet.sleep(1.0)
|
||||
_, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
if ('hardwaremanagement.manager' in cd and
|
||||
cd['hardwaremanagement.manager']['value'] and
|
||||
not cd['hardwaremanagement.manager']['value'].startswith(
|
||||
'fe80::')):
|
||||
newip = cd['hardwaremanagement.manager']['value']
|
||||
newip = newip.split('/', 1)[0]
|
||||
newipinfo = getaddrinfo(newip, 0)[0]
|
||||
newip = newipinfo[-1][0]
|
||||
if ':' in newip:
|
||||
raise exc.NotImplementedException('IPv6 remote config TODO')
|
||||
mgrs = srvroot['Managers']['@odata.id']
|
||||
rsp = wc.grab_json_response(mgrs)
|
||||
if len(rsp['Members']) != 1:
|
||||
raise Exception("Can not handle multiple Managers")
|
||||
mgrurl = rsp['Members'][0]['@odata.id']
|
||||
mginfo = wc.grab_json_response(mgrurl)
|
||||
hifurls = get_host_interface_urls(wc, mginfo)
|
||||
mgtnicinfo = mginfo['EthernetInterfaces']['@odata.id']
|
||||
mgtnicinfo = wc.grab_json_response(mgtnicinfo)
|
||||
mgtnics = [x['@odata.id'] for x in mgtnicinfo.get('Members', [])]
|
||||
actualnics = []
|
||||
for candnic in mgtnics:
|
||||
if candnic in hifurls:
|
||||
continue
|
||||
actualnics.append(candnic)
|
||||
if len(actualnics) != 1:
|
||||
raise Exception("Multi-interface BMCs are not supported currently")
|
||||
currnet = wc.grab_json_response(actualnics[0])
|
||||
netconfig = netutil.get_nic_config(self.configmanager, nodename, ip=newip)
|
||||
newconfig = {
|
||||
"Address": newip,
|
||||
"SubnetMask": netutil.cidr_to_mask(netconfig['prefix']),
|
||||
}
|
||||
newgw = netconfig['ipv4_gateway']
|
||||
if newgw:
|
||||
newconfig['Gateway'] = newgw
|
||||
else:
|
||||
newconfig['Gateway'] = newip # required property, set to self just to have a value
|
||||
for net in currnet.get("IPv4Addresses", []):
|
||||
if net["Address"] == newip and net["SubnetMask"] == newconfig['SubnetMask'] and (not newgw or newconfig['Gateway'] == newgw):
|
||||
break
|
||||
else:
|
||||
wc.set_header('If-Match', '*')
|
||||
rsp, status = wc.grab_json_response_with_status(actualnics[0], {'IPv4StaticAddresses': [newconfig]}, method='PATCH')
|
||||
elif self.ipaddr.startswith('fe80::'):
|
||||
self.configmanager.set_node_attributes(
|
||||
{nodename: {'hardwaremanagement.manager': self.ipaddr}})
|
||||
else:
|
||||
raise exc.TargetEndpointUnreachable(
|
||||
'hardwaremanagement.manager must be set to desired address (No IPv6 Link Local detected)')
|
||||
|
||||
|
||||
def remote_nodecfg(nodename, cfm):
|
||||
cfg = cfm.get_node_attributes(
|
||||
nodename, 'hardwaremanagement.manager')
|
||||
ipaddr = cfg.get(nodename, {}).get('hardwaremanagement.manager', {}).get(
|
||||
'value', None)
|
||||
ipaddr = ipaddr.split('/', 1)[0]
|
||||
ipaddr = getaddrinfo(ipaddr, 0)[0][-1]
|
||||
if not ipaddr:
|
||||
raise Exception('Cannot remote configure a system without known '
|
||||
'address')
|
||||
info = {'addresses': [ipaddr]}
|
||||
nh = NodeHandler(info, cfm)
|
||||
nh.config(nodename)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import confluent.config.configmanager as cfm
|
||||
c = cfm.ConfigManager(None)
|
||||
import sys
|
||||
info = {'addresses': [[sys.argv[1]]] }
|
||||
print(repr(info))
|
||||
testr = NodeHandler(info, c)
|
||||
testr.config(sys.argv[2])
|
@ -58,6 +58,7 @@ async def active_scan(handler, protocol=None):
|
||||
known_peers = set([])
|
||||
async for scanned in scan(['urn:dmtf-org:service:redfish-rest:1', 'urn::service:affluent']):
|
||||
for addr in scanned['addresses']:
|
||||
addr = addr[0:1] + addr[2:]
|
||||
if addr in known_peers:
|
||||
break
|
||||
hwaddr = neighutil.get_hwaddr(addr[0])
|
||||
@ -77,13 +78,20 @@ async def scan(services, target=None):
|
||||
|
||||
|
||||
def _process_snoop(peer, rsp, mac, known_peers, newmacs, peerbymacaddress, byehandler, machandlers, handler):
|
||||
if mac in peerbymacaddress and peer not in peerbymacaddress[mac]['addresses']:
|
||||
peerbymacaddress[mac]['addresses'].append(peer)
|
||||
if mac in peerbymacaddress:
|
||||
normpeer = peer[0:1] + peer[2:]
|
||||
for currpeer in peerbymacaddress[mac]['addresses']:
|
||||
currnormpeer = currpeer[0:1] + peer[2:]
|
||||
if currnormpeer == normpeer:
|
||||
break
|
||||
else:
|
||||
peerbymacaddress[mac]['addresses'].append(peer)
|
||||
else:
|
||||
peerdata = {
|
||||
'hwaddr': mac,
|
||||
'addresses': [peer],
|
||||
}
|
||||
targurl = None
|
||||
for headline in rsp[1:]:
|
||||
if not headline:
|
||||
continue
|
||||
@ -103,12 +111,19 @@ def _process_snoop(peer, rsp, mac, known_peers, newmacs, peerbymacaddress, byeha
|
||||
if not value.endswith('/redfish/v1/'):
|
||||
return
|
||||
elif header == 'LOCATION':
|
||||
if not value.endswith('/DeviceDescription.json'):
|
||||
if '/eth' in value and value.endswith('.xml'):
|
||||
targurl = '/redfish/v1/'
|
||||
targtype = 'megarac-bmc'
|
||||
continue # MegaRAC redfish
|
||||
elif value.endswith('/DeviceDescription.json'):
|
||||
targurl = '/DeviceDescription.json'
|
||||
targtype = 'megarac-bmc'
|
||||
else:
|
||||
return
|
||||
if handler:
|
||||
util.spawn(check_fish_handler(handler, peerdata, known_peers, newmacs, peerbymacaddress, machandlers, mac, peer))
|
||||
util.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):
|
||||
async def check_fish_handler(handler, peerdata, known_peers, newmacs, peerbymacaddress, machandlers, mac, peer, targurl, targtype):
|
||||
retdata = await check_fish(('/DeviceDescription.json', peerdata))
|
||||
if retdata:
|
||||
known_peers.add(peer)
|
||||
@ -344,7 +359,7 @@ async def _find_service(service, target):
|
||||
host = '[{0}]'.format(host)
|
||||
msg = smsg.format(host, service)
|
||||
if not isinstance(msg, bytes):
|
||||
msg = msg.encode('utf8')
|
||||
msg = msg.encode('utf8')
|
||||
net6.sendto(msg, addr[4])
|
||||
else:
|
||||
net4.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
@ -436,7 +451,11 @@ async def _find_service(service, target):
|
||||
if '/redfish/v1/' not in peerdata[nid].get('urls', ()) and '/redfish/v1' not in peerdata[nid].get('urls', ()):
|
||||
continue
|
||||
if '/DeviceDescription.json' in peerdata[nid]['urls']:
|
||||
pooltargs.append(('/DeviceDescription.json', peerdata[nid]))
|
||||
pooltargs.append(('/DeviceDescription.json', peerdata[nid], 'lenovo-xcc'))
|
||||
else:
|
||||
for targurl in peerdata[nid]['urls']:
|
||||
if '/eth' in targurl and targurl.endswith('.xml'):
|
||||
pooltargs.append(('/redfish/v1/', peerdata[nid], 'megarac-bmc'))
|
||||
# For now, don't interrogate generic redfish bmcs
|
||||
# This is due to a need to deduplicate from some supported SLP
|
||||
# targets (IMM, TSM, others)
|
||||
@ -458,7 +477,7 @@ async def _find_service(service, target):
|
||||
async def check_fish(urldata, port=443, verifycallback=None):
|
||||
if not verifycallback:
|
||||
verifycallback = lambda x: True
|
||||
url, data = urldata
|
||||
url, data, targtype = urldata
|
||||
try:
|
||||
wc = webclient.WebConnection(_get_svrip(data), port, verifycallback=verifycallback)
|
||||
peerinfo = await wc.grab_json_response(url)
|
||||
@ -480,7 +499,7 @@ async def check_fish(urldata, port=443, verifycallback=None):
|
||||
peerinfo = await wc.grab_json_response('/redfish/v1/')
|
||||
if url == '/redfish/v1/':
|
||||
if 'UUID' in peerinfo:
|
||||
data['services'] = ['service:redfish-bmc']
|
||||
data['services'] = [targtype]
|
||||
data['uuid'] = peerinfo['UUID'].lower()
|
||||
return data
|
||||
return None
|
||||
@ -499,7 +518,12 @@ def _parse_ssdp(peer, rsp, peerdata):
|
||||
if code == b'200':
|
||||
if nid in peerdata:
|
||||
peerdatum = peerdata[nid]
|
||||
if peer not in peerdatum['addresses']:
|
||||
normpeer = peer[0:1] + peer[2:]
|
||||
for currpeer in peerdatum['addresses']:
|
||||
currnormpeer = currpeer[0:1] + peer[2:]
|
||||
if currnormpeer == normpeer:
|
||||
break
|
||||
else:
|
||||
peerdatum['addresses'].append(peer)
|
||||
else:
|
||||
peerdatum = {
|
||||
@ -534,5 +558,7 @@ def _parse_ssdp(peer, rsp, peerdata):
|
||||
|
||||
if __name__ == '__main__':
|
||||
def printit(rsp):
|
||||
print(repr(rsp))
|
||||
pass # print(repr(rsp))
|
||||
active_scan(printit)
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user