Skip to content

Instantly share code, notes, and snippets.

@pondahai
Last active May 6, 2025 12:52
Show Gist options
  • Save pondahai/e3d314f6e74454ae5de3932ed1aee616 to your computer and use it in GitHub Desktop.
Save pondahai/e3d314f6e74454ae5de3932ed1aee616 to your computer and use it in GitHub Desktop.
from MAC addr search IP
import subprocess
import re
import sys
import ipaddress
import netifaces
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
from typing import List, Tuple
def get_all_networks() -> List[Tuple[str, str]]:
"""獲取所有非回環的 IPv4 網路介面及其網段"""
networks = []
try:
for iface in netifaces.interfaces():
addrs = netifaces.ifaddresses(iface)
if netifaces.AF_INET not in addrs:
continue
for addr_info in addrs[netifaces.AF_INET]:
ip = addr_info.get('addr', '')
netmask = addr_info.get('netmask', '')
if not ip or ip.startswith('127.'):
continue
try:
network = ipaddress.IPv4Network(f"{ip}/{netmask}", strict=False)
networks.append((iface, str(network)))
except ValueError as e:
print(f"介面 {iface} 網段計算錯誤: {e}", file=sys.stderr)
except Exception as e:
print(f"獲取網路介面失敗: {e}", file=sys.stderr)
return networks
def select_network(networks: List[Tuple[str, str]]) -> str:
"""讓使用者選擇要掃描的網段"""
if not networks:
print("警告: 無法自動檢測網段,使用預設值 192.168.1.0/24", file=sys.stderr)
return '192.168.1.0/24'
if len(networks) == 1:
return networks[0][1]
print("\n檢測到多個網路介面:")
for i, (iface, network) in enumerate(networks, 1):
print(f"{i}. 介面 {iface} - 網段 {network}")
print(f"{len(networks)+1}. 掃描所有以上網段")
print(f"{len(networks)+2}. 手動輸入網段")
while True:
try:
choice = input("請選擇要掃描的網段 (編號): ").strip()
if not choice:
return networks[0][1]
choice = int(choice)
if 1 <= choice <= len(networks):
return networks[choice-1][1]
elif choice == len(networks)+1:
return 'multiple'
elif choice == len(networks)+2:
manual_input = input("請手動輸入要掃描的網段 (如 192.168.1.0/24): ").strip()
try:
# 驗證輸入的網段是否有效
ipaddress.IPv4Network(manual_input)
return manual_input
except ValueError:
print("無效的網段格式,請重新輸入", file=sys.stderr)
continue
except (ValueError, IndexError):
print("無效輸入,請重新選擇", file=sys.stderr)
def normalize_mac(mac_input: str) -> str:
"""將不同格式的 MAC 地址轉換為統一格式"""
mac_clean = re.sub(r'[^a-fA-F0-9]', '', mac_input.lower())
return mac_clean
def ping_host(ip: str) -> str:
"""Ping 單個主機"""
param = '-n' if sys.platform.lower() == 'win32' else '-c'
timeout = '-w' if sys.platform.lower() == 'win32' else '-W'
try:
subprocess.run(
['ping', param, '1', timeout, '500', str(ip)],
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
return str(ip)
except:
return None
def scan_network(network_cidr: str) -> List[str]:
"""主動掃描整個網段"""
print(f"\n正在掃描網段 {network_cidr}...")
try:
network = ipaddress.ip_network(network_cidr)
except ValueError as e:
print(f"無效的網段: {e}", file=sys.stderr)
return []
live_hosts = []
with ThreadPoolExecutor(max_workers=50) as executor:
futures = [executor.submit(ping_host, str(host)) for host in network.hosts()]
for future in futures:
result = future.result()
if result:
live_hosts.append(result)
print(f"掃描完成,找到 {len(live_hosts)} 個活動主機")
return live_hosts
def get_arp_table(use_cache: bool = True) -> List[Tuple[str, str]]:
"""獲取 ARP 表,可選使用快取"""
if use_cache and hasattr(get_arp_table, 'cache') and \
(datetime.now() - get_arp_table.last_updated).seconds < 300:
return get_arp_table.cache
try:
result = subprocess.run(['arp', '-a'], capture_output=True, text=True)
arp_output = result.stdout
except Exception as e:
print(f"執行 arp 命令時出錯: {e}", file=sys.stderr)
return []
arp_entries = []
for line in arp_output.split('\n'):
line = line.strip()
if not line:
continue
# Windows 格式
win_match = re.match(r'^([\d.]+)\s+([0-9a-fA-F-]+)\s+', line)
if win_match:
ip = win_match.group(1)
mac = normalize_mac(win_match.group(2))
arp_entries.append((ip, mac))
continue
# Unix 格式
unix_match = re.match(r'.*\(([\d.]+)\)\s+at\s+([0-9a-fA-F:]+)\s+', line)
if unix_match:
ip = unix_match.group(1)
mac = normalize_mac(unix_match.group(2))
arp_entries.append((ip, mac))
# 更新快取
get_arp_table.cache = arp_entries
get_arp_table.last_updated = datetime.now()
return arp_entries
def search_mac(
mac_part: str,
networks: List[str],
scan_type: str = 'auto'
) -> List[Tuple[str, str]]:
"""搜尋 MAC 地址"""
mac_part = normalize_mac(mac_part)
matches = []
arp_table = []
# 決定掃描方式
if scan_type == 'arp' or (scan_type == 'auto' and len(mac_part) >= 6):
arp_table = get_arp_table()
matches.extend([(ip, mac) for ip, mac in arp_table if mac_part in mac])
if (not matches or scan_type == 'full') and networks:
# 完整掃描模式
if networks == ['multiple']:
# 特殊情況:掃描所有網段
all_networks = get_all_networks()
networks = [net[1] for net in all_networks] if all_networks else ['192.168.1.0/24']
for network in networks:
live_ips = scan_network(network)
arp_table = get_arp_table(use_cache=False) # 強制更新 ARP 表
matches.extend([(ip, mac) for ip, mac in arp_table if mac_part in mac])
# 去重
unique_matches = []
seen = set()
for ip, mac in matches:
if (ip, mac) not in seen:
seen.add((ip, mac))
unique_matches.append((ip, mac))
return unique_matches
def get_vendor(mac_prefix: str) -> str:
"""獲取 MAC 地址廠商資訊"""
if not mac_prefix or len(mac_prefix) < 6:
return "Unknown"
if not hasattr(get_vendor, 'cache'):
get_vendor.cache = {}
if mac_prefix in get_vendor.cache:
return get_vendor.cache[mac_prefix]
try:
import requests
url = f"https://api.macvendors.com/{mac_prefix[:6]}"
response = requests.get(url, timeout=2)
vendor = response.text if response.status_code == 200 else "Unknown"
get_vendor.cache[mac_prefix] = vendor
return vendor
except:
return "Unknown"
def main():
print("MAC 地址搜尋工具 (多網段增強版)")
print("=" * 50)
# 獲取所有網路介面資訊
networks_info = get_all_networks()
selected_network = select_network(networks_info)
# 處理網段選擇
networks_to_scan = []
if selected_network == 'multiple':
networks_to_scan = ['multiple']
print("\n將掃描所有檢測到的網段")
else:
networks_to_scan = [selected_network]
print(f"\n選擇掃描網段: {selected_network}")
# 獲取輸入
if len(sys.argv) < 2:
print("\n請輸入要搜尋的 MAC 地址(完整或部分)")
print("支援格式範例: b3e0, b3:e0, b3-e0, B3-E0-12-34")
print("掃描模式選項:")
print(" fast - 僅檢查 ARP 快取 (預設)")
print(" full - 完整掃描整個網段")
while True:
mac_input = input("\n輸入 MAC 地址 (或 q 退出): ").strip()
if mac_input.lower() == 'q':
return
if mac_input:
break
print("錯誤: 輸入不能為空", file=sys.stderr)
scan_type = input("選擇掃描模式 [fast/full] (預設 fast): ").strip().lower()
scan_type = scan_type if scan_type in ('fast', 'full') else 'fast'
else:
mac_input = sys.argv[1]
scan_type = 'fast' if len(sys.argv) < 3 else sys.argv[2].lower()
print(f"\n搜尋模式: {'完整掃描' if scan_type == 'full' else '快速掃描'}")
print(f"搜尋目標: {mac_input}")
# 執行搜尋
matches = search_mac(mac_input, networks_to_scan, scan_type)
# 顯示結果
if matches:
print("\n找到匹配的裝置:")
print("-" * 60)
print(f"{'IP地址':<15} | {'MAC地址':<17} | {'廠商資訊'}")
print("-" * 60)
for ip, mac in matches:
formatted_mac = ':'.join([mac[i:i+2] for i in range(0, len(mac), 2)])
vendor = get_vendor(mac[:6]) if len(mac) >= 6 else "Unknown"
print(f"{ip:<15} | {formatted_mac:<17} | {vendor}")
else:
print(f"\n沒有找到 MAC 地址包含 '{mac_input}' 的裝置")
if __name__ == "__main__":
# 檢查必要依賴
try:
import netifaces
except ImportError:
print("錯誤: 需要 netifaces 庫,請先執行: pip install netifaces", file=sys.stderr)
sys.exit(1)
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment