update scan ip with MAC, update UI show history
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import ipaddress
|
||||
@@ -45,6 +46,58 @@ def _ping_one(ip, is_win):
|
||||
return False
|
||||
|
||||
|
||||
def _get_mac_from_arp(ip):
|
||||
"""
|
||||
Lấy MAC address của một IP qua ARP table.
|
||||
|
||||
Chỉ được gọi SAU KHI IP đã phản hồi ping thành công — đảm bảo ARP
|
||||
cache vừa được OS cập nhật với thông tin mới nhất (không bị stale).
|
||||
|
||||
Trả về chuỗi MAC dạng 'AA:BB:CC:DD:EE:FF' hoặc 'N/A' nếu không tra được.
|
||||
"""
|
||||
try:
|
||||
if sys.platform == "win32":
|
||||
# arp -a <ip> → " 192.168.4.5 aa-bb-cc-dd-ee-ff dynamic"
|
||||
r = subprocess.run(
|
||||
["arp", "-a", str(ip)],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
timeout=3,
|
||||
creationflags=_NO_WINDOW
|
||||
)
|
||||
output = r.stdout.decode(errors="ignore")
|
||||
# Dạng Windows: aa-bb-cc-dd-ee-ff
|
||||
match = re.search(
|
||||
r"([0-9a-fA-F]{2}[-:][0-9a-fA-F]{2}[-:][0-9a-fA-F]{2}"
|
||||
r"[-:][0-9a-fA-F]{2}[-:][0-9a-fA-F]{2}[-:][0-9a-fA-F]{2})",
|
||||
output
|
||||
)
|
||||
else:
|
||||
# macOS / Linux: arp -n <ip>
|
||||
# macOS output: "? (192.168.4.5) at aa:bb:cc:dd:ee:ff on en0 ..."
|
||||
# Linux output: "192.168.4.5 ether aa:bb:cc:dd:ee:ff C eth0"
|
||||
r = subprocess.run(
|
||||
["arp", "-n", str(ip)],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
timeout=3
|
||||
)
|
||||
output = r.stdout.decode(errors="ignore")
|
||||
match = re.search(
|
||||
r"([0-9a-fA-F]{2}[:-][0-9a-fA-F]{2}[:-][0-9a-fA-F]{2}"
|
||||
r"[:-][0-9a-fA-F]{2}[:-][0-9a-fA-F]{2}[:-][0-9a-fA-F]{2})",
|
||||
output
|
||||
)
|
||||
|
||||
if match:
|
||||
# Chuẩn hoá sang dấu ':' và chữ hoa
|
||||
mac = match.group(1).replace("-", ":").upper()
|
||||
return mac
|
||||
except Exception:
|
||||
pass
|
||||
return "N/A"
|
||||
|
||||
|
||||
def _ping_sweep(network, progress_cb=None):
|
||||
"""Ping tất cả host trong network đồng thời.
|
||||
Trả về list IP đã phản hồi. Gọi progress_cb(done, total) sau mỗi ping.
|
||||
@@ -83,10 +136,37 @@ def _ping_sweep(network, progress_cb=None):
|
||||
|
||||
|
||||
def scan_network(network, progress_cb=None, stage_cb=None):
|
||||
"""Scan network: chỉ dùng ping để xác định thiết bị online."""
|
||||
"""Scan network: ping → lấy MAC từ ARP (chỉ cho IP đang online).
|
||||
|
||||
Flow:
|
||||
1. Ping sweep — xác định thiết bị online
|
||||
2. MAC lookup — query ARP cho từng IP vừa phản hồi ping (song song)
|
||||
=> ARP cache vừa được OS cập nhật sau ping, không bị stale.
|
||||
"""
|
||||
# ── Stage 1: Ping ──
|
||||
if stage_cb:
|
||||
stage_cb("ping")
|
||||
alive_ips = _ping_sweep(network, progress_cb)
|
||||
|
||||
results = [{"ip": ip_str, "mac": "N/A"} for ip_str in alive_ips]
|
||||
# ── Stage 2: MAC lookup ──
|
||||
if stage_cb:
|
||||
stage_cb("mac")
|
||||
|
||||
results = []
|
||||
if alive_ips:
|
||||
# Chạy song song để tra MAC nhanh hơn
|
||||
mac_workers = min(32, len(alive_ips))
|
||||
macs = {}
|
||||
with ThreadPoolExecutor(max_workers=mac_workers) as executor:
|
||||
future_to_ip = {executor.submit(_get_mac_from_arp, ip): ip for ip in alive_ips}
|
||||
for future in as_completed(future_to_ip):
|
||||
ip = future_to_ip[future]
|
||||
try:
|
||||
macs[ip] = future.result()
|
||||
except Exception:
|
||||
macs[ip] = "N/A"
|
||||
|
||||
for ip_str in alive_ips:
|
||||
results.append({"ip": ip_str, "mac": macs.get(ip_str, "N/A")})
|
||||
|
||||
return sorted(results, key=lambda d: ipaddress.ip_address(d["ip"]))
|
||||
|
||||
Reference in New Issue
Block a user