2025年9月2日 星期二

掃描內網的所有[ONVIF] 的設備IP

 作法:用 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)嗎?

沒有留言:

張貼留言