作法:用 WS-Discovery 在 239.255.255.250:3702 發 Probe,蒐集回覆的 XAddrs,再抽出 IP。下例為純 Python3,無外部套件。
#!/usr/bin/env python3
# scan_onvif.py
import socket, uuid, time, re, xml.etree.ElementTree as ET
MCAST_GRP = ('239.255.255.250', 3702)
TIMEOUT_S = 3.0 # 掃描等待時間
IFACE_IP = '' # 綁定本機介面IP;留空自動
PROBE = f'''<?xml version="1.0" encoding="utf-8"?>
<e:Envelope xmlns:e="http://www.w3.org/2003/05/soap-envelope"
xmlns:w="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery"
xmlns:dn="http://www.onvif.org/ver10/network/wsdl">
<e:Header>
<w:MessageID>uuid:{uuid.uuid4()}</w:MessageID>
<w:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</w:To>
<w:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</w:Action>
</e:Header>
<e:Body>
<d:Probe>
<d:Types>dn:NetworkVideoTransmitter</d:Types>
<d:Scopes/>
</d:Probe>
</e:Body>
</e:Envelope>'''.encode('utf-8')
def parse_xaddrs(xml_bytes):
try:
# 去掉可能的垃圾字元
m = re.search(b'<\\?xml|<e:Envelope', xml_bytes)
if m: xml_bytes = xml_bytes[m.start():]
ns = {
'e': 'http://www.w3.org/2003/05/soap-envelope',
'd': 'http://schemas.xmlsoap.org/ws/2005/04/discovery',
'a': 'http://schemas.xmlsoap.org/ws/2004/08/addressing',
}
root = ET.fromstring(xml_bytes)
for x in root.iter():
if x.tag.endswith('XAddrs'):
return x.text or ''
except Exception:
pass
return ''
def extract_ips(xaddrs):
ips = set()
for url in re.findall(r'https?://([^/]+)', xaddrs):
host = url.split(':')[0]
if re.match(r'^\d{1,3}(\.\d{1,3}){3}$', host):
ips.add(host)
return ips
def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.settimeout(0.5)
# 允許廣播與多播
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
if IFACE_IP:
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(IFACE_IP))
sock.bind((IFACE_IP, 0))
# 發送多播 Probe 多次提高命中
for _ in range(3):
sock.sendto(PROBE, MCAST_GRP)
found = {}
t0 = time.time()
while time.time() - t0 < TIMEOUT_S:
try:
data, (addr, port) = sock.recvfrom(65535)
xaddrs = parse_xaddrs(data)
ips = extract_ips(xaddrs) or {addr}
for ip in ips:
if ip not in found:
found[ip] = {'from': addr, 'xaddrs': xaddrs}
except socket.timeout:
continue
except Exception:
continue
if not found:
print('未發現 ONVIF 設備')
return
print('發現的 ONVIF 設備:')
for i, (ip, meta) in enumerate(sorted(found.items()), 1):
print(f'{i:02d}. IP={ip} reply_from={meta["from"]}')
if meta['xaddrs']:
print(f' XAddrs: {meta["xaddrs"]}')
else:
print(' XAddrs: (無)')
if __name__ == '__main__':
main()
使用:
python3 scan_onvif.py
輸出:列出每台裝置的 IP 與回覆的 XAddrs。
若你要進一步拿 RTSP URL,需對每台 IP 執行 ONVIF GetProfiles
/GetStreamUri
(多數設備需要帳密)。
需要我補一支後續查詢的腳本(輸入 IP/帳密 → 列出 RTSP)嗎?
沒有留言:
張貼留言