dockerfile conflicts resolved
This commit is contained in:
parent
34c52566f5
commit
bccf55c81b
|
|
@ -43,8 +43,8 @@ RUN apt-get update && \
|
||||||
gstreamer1.0-alsa \
|
gstreamer1.0-alsa \
|
||||||
gstreamer1.0-pulseaudio \
|
gstreamer1.0-pulseaudio \
|
||||||
libopencv-dev \
|
libopencv-dev \
|
||||||
|
nmap \
|
||||||
&& \
|
&& \
|
||||||
pip3 install --no-cache-dir onvif_zeep && \
|
|
||||||
groupadd -r developers && \
|
groupadd -r developers && \
|
||||||
useradd -ms /bin/bash -g developers -G sudo dev && \
|
useradd -ms /bin/bash -g developers -G sudo dev && \
|
||||||
groupadd -g 20 dialout || true && \
|
groupadd -g 20 dialout || true && \
|
||||||
|
|
@ -55,7 +55,6 @@ RUN apt-get update && \
|
||||||
usermod -a -G render dev && \
|
usermod -a -G render dev && \
|
||||||
echo "dev ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/dev-nopasswd
|
echo "dev ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/dev-nopasswd
|
||||||
|
|
||||||
|
|
||||||
RUN echo "umask 0002" > /etc/profile.d/99-shared-umask.sh && \
|
RUN echo "umask 0002" > /etc/profile.d/99-shared-umask.sh && \
|
||||||
chmod +x /etc/profile.d/99-shared-umask.sh
|
chmod +x /etc/profile.d/99-shared-umask.sh
|
||||||
COPY . /tmp/build-context
|
COPY . /tmp/build-context
|
||||||
|
|
@ -80,7 +79,7 @@ RUN ldconfig
|
||||||
RUN rm -rf /var/lib/apt/lists/*
|
RUN rm -rf /var/lib/apt/lists/*
|
||||||
COPY piper_models/ /app/piper_models/
|
COPY piper_models/ /app/piper_models/
|
||||||
USER dev
|
USER dev
|
||||||
RUN pip install --no-cache-dir --user -i https://mirrors.aliyun.com/pypi/simple/ piper-tts
|
RUN pip install --no-cache-dir --user -i https://mirrors.aliyun.com/pypi/simple/ piper-tts onvif-zeep python-nmap
|
||||||
RUN echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bash_profile
|
RUN echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bash_profile
|
||||||
USER dev
|
USER dev
|
||||||
CMD ["/bin/bash"]
|
CMD ["/bin/bash"]
|
||||||
|
|
@ -0,0 +1,309 @@
|
||||||
|
import ipaddress
|
||||||
|
import socket
|
||||||
|
import subprocess
|
||||||
|
import threading
|
||||||
|
import concurrent.futures
|
||||||
|
from typing import List, Set, Union, Dict
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
from collections import defaultdict # 导入 defaultdict 以简化camera_lock下的操作
|
||||||
|
|
||||||
|
# --- Global Variables for thread-safe access ---
|
||||||
|
active_ips: Set[str] = set()
|
||||||
|
found_cameras: Dict[str, Dict[str, str]] = defaultdict(dict) # 使用defaultdict,简化内层字典的初始化
|
||||||
|
ip_lock = threading.Lock()
|
||||||
|
camera_lock = threading.Lock()
|
||||||
|
|
||||||
|
# --- Configuration ---
|
||||||
|
MAX_WORKERS = 100 # Number of concurrent threads for scanning
|
||||||
|
COMMON_CAMERA_PORTS = [
|
||||||
|
80, # HTTP (web interface)
|
||||||
|
443, # HTTPS (secure web interface)
|
||||||
|
554, # RTSP (Real Time Streaming Protocol)
|
||||||
|
8000, # Often used by Hikvision (SDK/HTTP)
|
||||||
|
8080, # Alternative HTTP/RTSP
|
||||||
|
8001, # Hikvision stream port
|
||||||
|
37777, # Dahua primary port
|
||||||
|
37778, # Dahua secondary port
|
||||||
|
8002, # Often used for camera APIs or secondary streams
|
||||||
|
# Add more ports if you know specific ones for your camera brands
|
||||||
|
]
|
||||||
|
|
||||||
|
SSH_PORTS = [22] # Potential SSH access for some cameras
|
||||||
|
|
||||||
|
# --- Imports for ONVIF Discovery ---
|
||||||
|
ONVIF_AVAILABLE = False
|
||||||
|
try:
|
||||||
|
import psutil # For getting all network interfaces
|
||||||
|
import onvif # The package 'onvif-zeep' installs the 'onvif' module
|
||||||
|
# Attempt to import specific discovery module first, as it's the intended way.
|
||||||
|
# If this fails, the ONVIF_AVAILABLE flag will be set to False.
|
||||||
|
try:
|
||||||
|
from onvif import discovery
|
||||||
|
_discovery_method = discovery.find_device
|
||||||
|
print("ONVIF: Using onvif.discovery.find_device for discovery.")
|
||||||
|
except (ImportError, AttributeError):
|
||||||
|
# Fallback: check if the top-level 'onvif' module has a discover method
|
||||||
|
if hasattr(onvif, 'discover') and callable(onvif.discover):
|
||||||
|
_discovery_method = onvif.discover
|
||||||
|
print("ONVIF: Using top-level onvif.discover() for discovery.")
|
||||||
|
else:
|
||||||
|
_discovery_method = None
|
||||||
|
print("ONVIF: No suitable ONVIF discovery method found in 'onvif' module.")
|
||||||
|
raise ImportError("No ONVIF discovery method.") # Force into the outer except block
|
||||||
|
|
||||||
|
ONVIF_AVAILABLE = True
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"Warning: Required libraries for full ONVIF functionality could not be imported.")
|
||||||
|
print(f" Error: {e}")
|
||||||
|
print(f" Please ensure 'psutil' and 'onvif-zeep' are installed:")
|
||||||
|
print(f" pip install psutil onvif-zeep")
|
||||||
|
print(f" ONVIF discovery will be skipped.")
|
||||||
|
# If psutil is not available, we can still do single network scanning later
|
||||||
|
try:
|
||||||
|
import psutil
|
||||||
|
except ImportError:
|
||||||
|
psutil = None # Mark psutil as not available
|
||||||
|
_discovery_method = None # Ensure it's None if ONVIF is unavailable
|
||||||
|
|
||||||
|
|
||||||
|
# --- Imports for SSH service detection ---
|
||||||
|
PARAMIKO_AVAILABLE = False
|
||||||
|
try:
|
||||||
|
import paramiko
|
||||||
|
PARAMIKO_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
print("Warning: 'paramiko' not installed. SSH service detection will be skipped. "
|
||||||
|
"Install with 'pip install paramiko' for full functionality.")
|
||||||
|
|
||||||
|
|
||||||
|
def get_local_ip() -> str:
|
||||||
|
"""Gets the local IP address of the machine."""
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
try:
|
||||||
|
# Connect to a dummy address. Doesn't actually send data.
|
||||||
|
s.connect(('10.255.255.255', 1))
|
||||||
|
IP = s.getsockname()[0]
|
||||||
|
except Exception:
|
||||||
|
IP = '127.0.0.1'
|
||||||
|
finally:
|
||||||
|
s.close()
|
||||||
|
return IP
|
||||||
|
|
||||||
|
def get_all_local_networks() -> List[str]:
|
||||||
|
"""
|
||||||
|
Uses psutil to find all active network interfaces and their associated network ranges.
|
||||||
|
Returns a list of network CIDR strings (e.g., '192.168.1.0/24').
|
||||||
|
"""
|
||||||
|
if not psutil:
|
||||||
|
print("Warning: 'psutil' not available. Only scanning the /24 subnet of the local IP address.")
|
||||||
|
local_ip = get_local_ip()
|
||||||
|
if local_ip == '127.0.0.1':
|
||||||
|
return [] # Cannot determine external network from localhost
|
||||||
|
return [str(ipaddress.IPv4Network(f"{local_ip}/24", strict=False))]
|
||||||
|
|
||||||
|
networks = set()
|
||||||
|
try:
|
||||||
|
for interface, snics in psutil.net_if_addrs().items():
|
||||||
|
for snic in snics:
|
||||||
|
if snic.family == socket.AF_INET: # IPv4 address
|
||||||
|
ip_address = snic.address
|
||||||
|
netmask = snic.netmask
|
||||||
|
if ip_address and netmask and ip_address != '127.0.0.1':
|
||||||
|
try:
|
||||||
|
# Calculate the network address using the IP and netmask
|
||||||
|
# ipaddress module can handle this directly from address and netmask
|
||||||
|
network_obj = ipaddress.IPv4Network(f"{ip_address}/{netmask}", strict=False)
|
||||||
|
networks.add(str(network_obj))
|
||||||
|
except ipaddress.AddressValueError as e:
|
||||||
|
print(f"Warning: Could not parse IP address or netmask for {interface}: {ip_address}, {netmask}. Error: {e}")
|
||||||
|
return list(networks)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting network interfaces with psutil: {e}")
|
||||||
|
print("Falling back to scanning only the /24 subnet of the local IP address.")
|
||||||
|
local_ip = get_local_ip()
|
||||||
|
if local_ip == '127.0.0.1':
|
||||||
|
return []
|
||||||
|
return [str(ipaddress.IPv4Network(f"{local_ip}/24", strict=False))]
|
||||||
|
|
||||||
|
def onvif_discovery_task() -> None:
|
||||||
|
"""Performs ONVIF WS-Discovery to find compatible devices."""
|
||||||
|
global ONVIF_AVAILABLE, _discovery_method
|
||||||
|
if not ONVIF_AVAILABLE or _discovery_method is None:
|
||||||
|
print("ONVIF: Skipping discovery due to 'psutil' or 'onvif-zeep' not being available or no suitable discovery method.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("ONVIF: Starting discovery. This may take a few seconds...")
|
||||||
|
try:
|
||||||
|
# Use the determined discovery method
|
||||||
|
discovered_device_xaddrs: List[str] = _discovery_method(timeout=5)
|
||||||
|
|
||||||
|
# Ensure raw_xaddrs is a list before iteration
|
||||||
|
if not isinstance(discovered_device_xaddrs, list):
|
||||||
|
discovered_device_xaddrs = [discovered_device_xaddrs] if discovered_device_xaddrs else []
|
||||||
|
|
||||||
|
discovered_ips_via_onvif = []
|
||||||
|
for xaddr in discovered_device_xaddrs:
|
||||||
|
try:
|
||||||
|
# Extract IP from the XAddr URL
|
||||||
|
parsed_url = urlparse(xaddr)
|
||||||
|
device_ip = parsed_url.hostname
|
||||||
|
if device_ip and device_ip not in discovered_ips_via_onvif:
|
||||||
|
discovered_ips_via_onvif.append(device_ip)
|
||||||
|
except Exception as url_e:
|
||||||
|
print(f"ONVIF: Warning: Could not parse IP from device XAddr '{xaddr}': {url_e}")
|
||||||
|
|
||||||
|
if discovered_ips_via_onvif:
|
||||||
|
print(f"ONVIF: Found {len(discovered_ips_via_onvif)} potential ONVIF devices via WS-Discovery.")
|
||||||
|
for device_ip in discovered_ips_via_onvif:
|
||||||
|
with ip_lock:
|
||||||
|
active_ips.add(device_ip)
|
||||||
|
with camera_lock:
|
||||||
|
found_cameras[device_ip]['ONVIF_Discovery'] = "ONVIF Device (WS-Discovery)"
|
||||||
|
else:
|
||||||
|
print("ONVIF: No ONVIF devices found via WS-Discovery.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ONVIF: Error during ONVIF discovery (using _discovery_method): {e}")
|
||||||
|
|
||||||
|
def check_socket(ip: str, port: int, timeout: float = 0.5) -> bool:
|
||||||
|
"""Attempts to connect to a specific port on an IP address."""
|
||||||
|
try:
|
||||||
|
with socket.create_connection((ip, port), timeout) as sock:
|
||||||
|
sock.shutdown(socket.SHUT_RDWR) # Gracefully close connection
|
||||||
|
return True
|
||||||
|
except (socket.timeout, ConnectionRefusedError, OSError):
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
# print(f"Error checking {ip}:{port}: {e}") # Uncomment for debugging
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_ssh_banner(ip: str, port: int, timeout: float = 0.5) -> Union[str, bool]:
|
||||||
|
"""Attempts to get SSH banner to verify SSH service."""
|
||||||
|
if not PARAMIKO_AVAILABLE:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
transport = paramiko.Transport((ip, port))
|
||||||
|
transport.connect(timeout=timeout)
|
||||||
|
banner = transport.get_banner()
|
||||||
|
transport.close()
|
||||||
|
return banner.strip()
|
||||||
|
except (paramiko.SSHException, socket.error, socket.timeout):
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
# print(f"Error getting SSH banner from {ip}:{port}: {e}") # Uncomment for debugging
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def service_scan_task(ip: str) -> None:
|
||||||
|
"""Scans common camera ports and SSH ports on a given IP and updates found_cameras."""
|
||||||
|
|
||||||
|
# Try ONVIF specific ports (80, 554, 8000, 8080) for detailed service if available
|
||||||
|
for port in COMMON_CAMERA_PORTS:
|
||||||
|
if check_socket(ip, port):
|
||||||
|
with camera_lock:
|
||||||
|
if port == 80:
|
||||||
|
found_cameras[ip]['HTTP'] = f"Open on port {port}"
|
||||||
|
elif port == 443:
|
||||||
|
found_cameras[ip]['HTTPS'] = f"Open on port {port}"
|
||||||
|
elif port == 554:
|
||||||
|
found_cameras[ip]['RTSP'] = f"Open on port {port}"
|
||||||
|
elif port == 8000 or port == 8001: # Common for Hikvision
|
||||||
|
found_cameras[ip]['Hikvision_Service'] = f"Open on port {port}"
|
||||||
|
elif port == 37777 or port == 37778: # Common for Dahua
|
||||||
|
found_cameras[ip]['Dahua_Service'] = f"Open on port {port}"
|
||||||
|
else:
|
||||||
|
found_cameras[ip][f'TCP_{port}'] = f"Open on port {port}"
|
||||||
|
|
||||||
|
for port in SSH_PORTS:
|
||||||
|
banner = check_ssh_banner(ip, port)
|
||||||
|
if banner:
|
||||||
|
with camera_lock:
|
||||||
|
found_cameras[ip]['SSH'] = f"Open on port {port} (Banner: {banner})"
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
start_time = time.time()
|
||||||
|
print("--- Starting Network Camera Discovery on RK3588 ---")
|
||||||
|
|
||||||
|
# Get local IP address (for display)
|
||||||
|
local_ip = get_local_ip()
|
||||||
|
print(f"Local IP Address: {local_ip}")
|
||||||
|
|
||||||
|
# Get all local networks for scanning
|
||||||
|
all_local_networks = get_all_local_networks()
|
||||||
|
if not all_local_networks:
|
||||||
|
print("No local networks detected for scanning. Exiting.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("Detected local networks for scanning:")
|
||||||
|
for net in all_local_networks:
|
||||||
|
print(f" - {net}")
|
||||||
|
# --- EXCLUDE Docker internal networks ---
|
||||||
|
# If running inside a Docker container, often these are internal.
|
||||||
|
# Adjust these prefixes based on your Docker network configuration if different.
|
||||||
|
all_local_networks_filtered = []
|
||||||
|
DOCKER_NETWORK_PREFIXES = ["172.17.", "172.18.", "172.19.", "172.20."] # Add more if your Docker uses other 172.x ranges
|
||||||
|
|
||||||
|
for net_cidr in all_local_networks:
|
||||||
|
is_docker_internal = False
|
||||||
|
for prefix in DOCKER_NETWORK_PREFIXES:
|
||||||
|
if net_cidr.startswith(prefix):
|
||||||
|
is_docker_internal = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not is_docker_internal:
|
||||||
|
all_local_networks_filtered.append(net_cidr)
|
||||||
|
else:
|
||||||
|
print(f" - Excluding Docker internal network: {net_cidr}")
|
||||||
|
if not all_local_networks_filtered:
|
||||||
|
print("No external local networks found for scanning after filtering Docker networks. Exiting.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\nFiltered local networks for scanning:")
|
||||||
|
for net in all_local_networks_filtered:
|
||||||
|
print(f" - {net}")
|
||||||
|
# --- ONVIF Discovery (runs independently) ---
|
||||||
|
onvif_discovery_task()
|
||||||
|
# --- Prepare IPs for Service Scan (without relying on 'ping') ---
|
||||||
|
all_ips_for_service_scan = set()
|
||||||
|
# Use the filtered list of networks
|
||||||
|
for network_str in all_local_networks_filtered:
|
||||||
|
try:
|
||||||
|
network = ipaddress.IPv4Network(network_str, strict=False)
|
||||||
|
for ip_obj in network.hosts():
|
||||||
|
all_ips_for_service_scan.add(str(ip_obj))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error processing network {network_str}: {e}")
|
||||||
|
continue
|
||||||
|
total_ips_for_service_scan = len(all_ips_for_service_scan)
|
||||||
|
print(f"Proceeding directly to service scan of {total_ips_for_service_scan} IPs on common camera ports.")
|
||||||
|
|
||||||
|
# --- Service Scan for common camera ports ---
|
||||||
|
if total_ips_for_service_scan > 0:
|
||||||
|
with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
|
||||||
|
# Map the service scan task to all IPs identified by local networks
|
||||||
|
executor.map(service_scan_task, list(all_ips_for_service_scan))
|
||||||
|
|
||||||
|
# Wait for service scan to complete using concurrent.futures
|
||||||
|
# (executor.map is blocking here, which is fine)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Discovery Results ---
|
||||||
|
print("\n--- Discovery Results ---")
|
||||||
|
if found_cameras:
|
||||||
|
print(f"Found {len(found_cameras)} potential camera devices and services:")
|
||||||
|
for ip, services in found_cameras.items():
|
||||||
|
print(f" IP: {ip}")
|
||||||
|
# Sort services for consistent output
|
||||||
|
for service, details in sorted(services.items()):
|
||||||
|
print(f" - {service}: {details}")
|
||||||
|
else:
|
||||||
|
print("No network cameras or detectable services found based on ONVIF or port scanning.")
|
||||||
|
|
||||||
|
print(f"\n--- Discovery Finished in {time.time() - start_time:.2f} seconds ---")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -1,222 +0,0 @@
|
||||||
import ipaddress
|
|
||||||
import socket
|
|
||||||
import threading
|
|
||||||
import concurrent.futures
|
|
||||||
import time
|
|
||||||
import re
|
|
||||||
from typing import List, Set, Dict, Tuple
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
# ONVIF related imports (install with: pip install onvif_zeep)
|
|
||||||
try:
|
|
||||||
from onvif_zeep import ONVIFCamera
|
|
||||||
from zeep import xsd # Needed for some internal ONVIF type definitions
|
|
||||||
ONVIF_AVAILABLE = True
|
|
||||||
except ImportError:
|
|
||||||
print("Warning: 'onvif_zeep' library not found. ONVIF discovery will be skipped.")
|
|
||||||
print(" To enable ONVIF discovery, install it: pip install onvif_zeep")
|
|
||||||
ONVIF_AVAILABLE = False
|
|
||||||
|
|
||||||
|
|
||||||
# --- Global Data and Locks ---
|
|
||||||
active_ips: Set[str] = set()
|
|
||||||
found_cameras: Dict[str, Set[int]] = {} # IP -> {ports}
|
|
||||||
|
|
||||||
ip_lock = threading.Lock()
|
|
||||||
camera_lock = threading.Lock()
|
|
||||||
|
|
||||||
# Common camera ports to scan
|
|
||||||
COMMON_CAMERA_PORTS: List[int] = [80, 554, 8000, 8080, 8001, 8090, 443]
|
|
||||||
# Extend this list if you know other common ports for your cameras
|
|
||||||
|
|
||||||
|
|
||||||
# --- Network Utility Functions ---
|
|
||||||
|
|
||||||
def get_local_ip() -> str:
|
|
||||||
"""Gets the local IP address by attempting to connect to an external server."""
|
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
||||||
try:
|
|
||||||
# Try to connect to a public DNS server (doesn't actually send data)
|
|
||||||
s.connect(('8.8.8.8', 1))
|
|
||||||
IP = s.getsockname()[0]
|
|
||||||
except Exception:
|
|
||||||
IP = '127.0.0.1'
|
|
||||||
finally:
|
|
||||||
s.close()
|
|
||||||
return IP
|
|
||||||
|
|
||||||
def get_network_range(ip_address: str, subnet_mask: str = '24') -> ipaddress.IPv4Network:
|
|
||||||
"""
|
|
||||||
Given an IP address and subnet mask, returns the IPv4Network object.
|
|
||||||
Assumes /24 for simplicity if subnet mask is not explicitly provided.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
network = ipaddress.IPv4Network(f"{ip_address}/{subnet_mask}", strict=False)
|
|
||||||
return network
|
|
||||||
except ipaddress.AddressValueError:
|
|
||||||
print(f"Error: Invalid IP address or subnet mask: {ip_address}/{subnet_mask}")
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
# --- Discovery Functions ---
|
|
||||||
|
|
||||||
def ping_ip(ip: str, timeout: float = 0.1) -> None:
|
|
||||||
"""
|
|
||||||
Attempts to 'ping' an IP by trying to connect to a common port (e.g., 80 or 554).
|
|
||||||
If connection is established, assumes the host is active.
|
|
||||||
"""
|
|
||||||
for port in [80, 554]: # Just need any open port to consider it "active"
|
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
sock.settimeout(timeout)
|
|
||||||
try:
|
|
||||||
sock.connect((ip, port))
|
|
||||||
with ip_lock:
|
|
||||||
active_ips.add(ip)
|
|
||||||
sock.close()
|
|
||||||
return # Host is active, no need to try other ports
|
|
||||||
except (socket.timeout, ConnectionRefusedError, OSError):
|
|
||||||
pass # Port not open or connection refused
|
|
||||||
except Exception as e:
|
|
||||||
# print(f"Ping error for {ip}:{port}: {e}") # Uncomment for deeper debugging
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
if sock:
|
|
||||||
sock.close()
|
|
||||||
|
|
||||||
def scan_port(ip: str, port: int, timeout: float = 0.1) -> Tuple[str, int, bool]:
|
|
||||||
"""Scans a single port on a given IP address."""
|
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
sock.settimeout(timeout)
|
|
||||||
try:
|
|
||||||
sock.connect((ip, port))
|
|
||||||
return ip, port, True
|
|
||||||
except (socket.timeout, ConnectionRefusedError, OSError):
|
|
||||||
return ip, port, False
|
|
||||||
finally:
|
|
||||||
sock.close()
|
|
||||||
|
|
||||||
|
|
||||||
def onvif_discovery_task() -> None:
|
|
||||||
"""Performs ONVIF WS-Discovery to find compatible devices."""
|
|
||||||
if not ONVIF_AVAILABLE:
|
|
||||||
return
|
|
||||||
|
|
||||||
print("ONVIF: Starting discovery. This may take a few seconds...")
|
|
||||||
try:
|
|
||||||
# discover() sends a UDP multicast probe and listens for responses
|
|
||||||
# discovery_timeout is in seconds
|
|
||||||
devices = ONVIFCamera.discover(discovery_timeout=5)
|
|
||||||
print(f"ONVIF: Found {len(devices)} potential ONVIF devices.")
|
|
||||||
|
|
||||||
for device_url in devices:
|
|
||||||
try:
|
|
||||||
# device_url looks like 'http://192.168.1.108/onvif/device_service'
|
|
||||||
# or 'http://192.168.1.108:8080/onvif/device_service'
|
|
||||||
parsed_url = urlparse(device_url)
|
|
||||||
ip = parsed_url.hostname
|
|
||||||
port = parsed_url.port if parsed_url.port else (80 if parsed_url.scheme == 'http' else 443)
|
|
||||||
|
|
||||||
if ip:
|
|
||||||
with ip_lock:
|
|
||||||
active_ips.add(ip)
|
|
||||||
with camera_lock:
|
|
||||||
if ip not in found_cameras:
|
|
||||||
found_cameras[ip] = set()
|
|
||||||
found_cameras[ip].add(port)
|
|
||||||
print(f"ONVIF: Discovered device at {ip}:{port} (Service URL: {device_url})")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"ONVIF: Error parsing device URL {device_url}: {e}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"ONVIF: Error during discovery: {e}")
|
|
||||||
|
|
||||||
# --- Main Program Logic ---
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("--- Starting Network Camera Discovery on RK3588 ---")
|
|
||||||
|
|
||||||
# 1. Get Local IP and Network Info
|
|
||||||
local_ip = get_local_ip()
|
|
||||||
if local_ip == '127.0.0.1':
|
|
||||||
print("Warning: Could not determine local IP. Using 192.168.1.0/24 as fallback.")
|
|
||||||
network_range = get_network_range('192.168.1.1', '24')
|
|
||||||
else:
|
|
||||||
print(f"Local IP Address: {local_ip}")
|
|
||||||
# Assuming /24 subnet for local network
|
|
||||||
network_range = get_network_range(local_ip, '24')
|
|
||||||
|
|
||||||
print(f"Scanning network range: {network_range}")
|
|
||||||
|
|
||||||
# Start ONVIF discovery in a separate thread
|
|
||||||
onvif_thread = threading.Thread(target=onvif_discovery_task)
|
|
||||||
onvif_thread.start()
|
|
||||||
|
|
||||||
# 2. Ping all IPs in the subnet to find active hosts
|
|
||||||
print(f"\nPinging all IPs in {network_range} to find active hosts...")
|
|
||||||
ip_list = [str(ip) for ip in network_range.hosts()]
|
|
||||||
|
|
||||||
# Use ThreadPoolExecutor for efficient parallel execution
|
|
||||||
ping_timeout = 0.05 # Smaller timeout for faster ping
|
|
||||||
with concurrent.futures.ThreadPoolExecutor(max_workers=100) as executor:
|
|
||||||
for ip in ip_list:
|
|
||||||
executor.submit(ping_ip, ip, ping_timeout)
|
|
||||||
|
|
||||||
# Wait for ping tasks to complete
|
|
||||||
ping_start_time = time.time()
|
|
||||||
while threading.active_count() > 1 and (time.time() - ping_start_time) < 10: # Max 10s for pings
|
|
||||||
time.sleep(0.1)
|
|
||||||
|
|
||||||
with ip_lock:
|
|
||||||
print(f"Found {len(active_ips)} active IPs.")
|
|
||||||
|
|
||||||
# 3. Port scan active IPs
|
|
||||||
print(f"\nScanning active IPs for common camera ports: {COMMON_CAMERA_PORTS}...")
|
|
||||||
|
|
||||||
futures = []
|
|
||||||
with concurrent.futures.ThreadPoolExecutor(max_workers=50) as executor:
|
|
||||||
with ip_lock: # Ensure active_ips is not modified while iterating
|
|
||||||
for ip in list(active_ips): # Iterate over a copy to avoid issues if set changes
|
|
||||||
for port in COMMON_CAMERA_PORTS:
|
|
||||||
futures.append(executor.submit(scan_port, ip, port))
|
|
||||||
|
|
||||||
for future in concurrent.futures.as_completed(futures):
|
|
||||||
ip, port, is_open = future.result()
|
|
||||||
if is_open:
|
|
||||||
with camera_lock:
|
|
||||||
if ip not in found_cameras:
|
|
||||||
found_cameras[ip] = set()
|
|
||||||
found_cameras[ip].add(port)
|
|
||||||
# print(f" Found open port: {ip}:{port}") # Uncomment for verbose output
|
|
||||||
|
|
||||||
# Wait for ONVIF discovery to finish
|
|
||||||
onvif_thread.join()
|
|
||||||
|
|
||||||
# 4. Present Results
|
|
||||||
print("\n--- Discovery Results ---")
|
|
||||||
if not found_cameras:
|
|
||||||
print("No network cameras found based on common ports or ONVIF discovery.")
|
|
||||||
else:
|
|
||||||
print("Potential Network Cameras Found:")
|
|
||||||
for ip, ports in sorted(found_cameras.items()):
|
|
||||||
print(f" IP: {ip}")
|
|
||||||
print(f" Open Ports: {', '.join(map(str, sorted(list(ports))))}")
|
|
||||||
|
|
||||||
# Heuristic to suggest RTSP/Web URLs
|
|
||||||
suggested_urls = []
|
|
||||||
if 554 in ports:
|
|
||||||
suggested_urls.append(f"RTSP (example, user/pass/path needed): rtsp://[user]:[pass]@{ip}:554/stream")
|
|
||||||
if 80 in ports:
|
|
||||||
suggested_urls.append(f"Web Admin: http://{ip}")
|
|
||||||
if 8080 in ports:
|
|
||||||
suggested_urls.append(f"Web Admin: http://{ip}:8080")
|
|
||||||
if 443 in ports:
|
|
||||||
suggested_urls.append(f"Web Admin (HTTPS): https://{ip}")
|
|
||||||
|
|
||||||
for url in suggested_urls:
|
|
||||||
print(f" - {url}")
|
|
||||||
print("-" * 30)
|
|
||||||
|
|
||||||
print("\n--- Discovery Finished ---")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
Loading…
Reference in New Issue