为什么要获取MAC,为什么要构造完整报文帧,请换一种方法
#!/usr/bin/env python3
"""
mdns_query_generator_improved.py
不依赖 Scapy,使用原生 socket 构造 mDNS 查询。
- 根据 IP 自动查找网络接口
- 跨平台权限检查(Windows/Linux)
- 多线程并发发送
"""
import argparse
import threading
import time
import random
import sys
import socket
import struct
from struct import pack
# 全局变量
stop_event = threading.Event()
MCAST_ADDR = "224.0.0.251"
MCAST_PORT = 5353
IP_TTL = 255
def is_admin():
"""跨平台检查管理员/root 权限"""
try:
if sys.platform == "win32":
import ctypes
return ctypes.windll.shell32.IsUserAnAdmin() != 0
else:
return hasattr(os, 'geteuid') and os.geteuid() == 0
except Exception:
return False
def get_interface_by_ip_ioctl_linux(ip):
"""Linux 下通过 ioctl(SIOCGIFCONF) 查找接口名"""
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# SIOCGIFCONF: 获取所有接口信息
ifs = fcntl.ioctl(s.fileno(), 0x8912, bytes(4096))
s.close()
# 每个 ifreq 结构体约 40 字节(if_name + sa)
for i in range(0, len(ifs), 40):
data = ifs[i:i+40]
if len(data) < 40:
continue
ifname_bytes = data[:16].split(b'\x00', 1)[0]
ifname = ifname_bytes.decode('utf-8')
addr_data = data[20:24] # sin_addr 在结构体中的偏移
ip_if = socket.inet_ntoa(addr_data)
if ip_if == ip:
return ifname
except Exception as e:
print(f"[ioctl] Error: {e}")
return None
def get_interface_by_ip_netifaces(ip):
"""使用 netifaces 查找接口"""
try:
import netifaces
for iface in netifaces.interfaces():
addrs = netifaces.ifaddresses(iface)
if netifaces.AF_INET in addrs:
for addr_info in addrs[netifaces.AF_INET]:
if addr_info['addr'] == ip:
return iface
except ImportError:
return None
except Exception as e:
print(f"[netifaces] Error: {e}")
return None
return None
def get_interface_by_ip(ip):
"""自动根据 IP 查找对应接口名(优先 netifaces,fallback 到 ioctl)"""
# 方法 1: netifaces
iface = get_interface_by_ip_netifaces(ip)
if iface:
return iface
# 方法 2: Linux ioctl
if sys.platform.startswith("linux"):
iface = get_interface_by_ip_ioctl_linux(ip)
if iface:
return iface
# 方法 3: 尝试用 UDP connect 推断出口接口(启发式)
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 53))
local_ip = s.getsockname()[0]
s.close()
if local_ip == ip:
# 再试一次 ioctl(适用于本机默认路由接口)
return get_interface_by_ip_ioctl_linux(ip)
except Exception:
pass
return None
def mac_str_to_bytes(mac):
"""MAC 字符串转 bytes"""
return bytes(int(b, 16) for b in mac.split(':'))
def ip_str_to_bytes(ip):
"""IPv4 转 bytes"""
return bytes(map(int, ip.split('.')))
def ip_to_int(ip):
"""IP 转整数"""
return sum([int(b) << (8 * (3 - i)) for i, b in enumerate(ip.split('.'))])
def checksum(data):
"""校验和计算"""
if len(data) % 2 == 1:
data += b'\0'
chk = 0
for i in range(0, len(data), 2):
chk += (data[i] << 8) + data[i + 1]
while chk >> 16:
chk = (chk & 0xFFFF) + (chk >> 16)
return ~chk & 0xFFFF
def build_dns_query(qname, qtype=12): # 12 = PTR
def encode_name(name):
result = b''
for part in name.encode().split(b'.'):
result += bytes([len(part)]) + part
return result + b'\0'
header = pack('>HHHHHH', 0, 0x0100, 1, 0, 0, 0)
question = encode_name(qname) + pack('>HH', qtype, 1)
return header + question
def build_udp_packet(sport, dport, data):
length = 8 + len(data)
udp_header = pack('>HHH H', sport, dport, length, 0)
pseudo_header = ip_str_to_bytes(src_ip_global) + ip_str_to_bytes(MCAST_ADDR) + \
pack('>BBH', 0, 17, length)
chk_data = pseudo_header + udp_header[:8] + b'\0\0' + data
csum = checksum(chk_data)
udp_header = pack('>HHH H', sport, dport, length, csum)
return udp_header + data
def build_ip_packet(src, dst, proto, payload):
version_ihl_tos = 0x45
tot_len = 20 + len(payload)
iden = random.randint(0, 0xFFFF)
frag = 0
ttl = IP_TTL
src_addr = ip_str_to_bytes(src)
dst_addr = ip_str_to_bytes(dst)
header = pack('!BBHHHBBHII',
version_ihl_tos, 0, tot_len,
iden, frag, ttl, proto, 0,
ip_to_int(src), ip_to_int(dst))
chk = checksum(header)
header = pack('!BBHHHBBHII',
version_ihl_tos, 0, tot_len,
iden, frag, ttl, proto, chk,
ip_to_int(src), ip_to_int(dst))
return header + payload
# 全局用于保存源 IP
src_ip_global = None
def get_interface_mac(iface_name):
"""获取接口 MAC 地址"""
if sys.platform.startswith("linux"):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', iface_name.encode('utf-8')[:15]))
return bytes(info[18:24])
except Exception as e:
print(f"Failed to get MAC of {iface_name}: {e}")
return None
return None
def sender_thread(thread_id, service, rate, iface_name, src_ip):
global src_ip_global
src_ip_global = src_ip
try:
my_mac = get_interface_mac(iface_name)
if not my_mac:
print(f"[Thread {thread_id}] Cannot get MAC address.")
stop_event.set()
return
except Exception as e:
print(f"[Thread {thread_id}] Interface error: {e}")
stop_event.set()
return
eth_hdr = mac_str_to_bytes("01:00:5e:00:00:fb") + my_mac + pack('!H', 0x0800)
dns_query = build_dns_query(service, qtype=12)
udp_pkt = build_udp_packet(MCAST_PORT, MCAST_PORT, dns_query)
ip_pkt = build_ip_packet(src_ip, MCAST_ADDR, 17, udp_pkt)
packet = eth_hdr + ip_pkt
# 仅 Linux 支持 AF_PACKET 发送
if not sys.platform.startswith("linux"):
print(f"[Thread {thread_id}] Non-Linux platform: simulating send")
interval = 1.0 / rate if rate > 0 else 0
while not stop_event.is_set():
print(f"[SIM] Sending mDNS query from {src_ip} via {iface_name}")
time.sleep(interval)
return
try:
sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(0x0800))
sock.bind((iface_name, 0))
except PermissionError:
print(f"[ERROR] Permission denied. Run as root/administrator.")
stop_event.set()
return
except Exception as e:
print(f"[Socket] Create failed: {e}")
stop_event.set()
return
print(f"[Thread {thread_id}] Started sending on {iface_name}")
interval = 1.0 / rate if rate > 0 else 0
while not stop_event.is_set():
try:
sock.send(packet)
except Exception as e:
print(f"[Send] Failed: {e}")
break
if interval > 0:
time.sleep(interval)
def main():
parser = argparse.ArgumentParser(description="mDNS Query Generator (No Scapy, Auto IFACE)")
parser.add_argument("--service", default="_http._tcp.local.", help="Service type to query (PTR)")
parser.add_argument("--clients", type=int, default=100, help="Number of concurrent threads")
parser.add_argument("--rate", type=float, default=1.0, help="Queries per second per client")
parser.add_argument("--duration", type=int, default=60, help="Duration in seconds")
parser.add_argument("--ip", required=True, help="Source IP address (used to auto-find interface)")
args = parser.parse_args()
# 步骤 1: 检查权限
if not is_admin():
print("[WARNING] May lack permission to send raw packets. Please run as admin/root.")
# 步骤 2: 根据 IP 自动查找接口
iface_name = get_interface_by_ip(args.ip)
if not iface_name:
print(f"[ERROR] Cannot find interface for IP {args.ip}")
print("Available interfaces may not have this IP assigned.")
sys.exit(1)
print(f"[INFO] Found interface '{iface_name}' for IP {args.ip}")
# 步骤 3: 启动多线程发送器
print(f"[INFO] Starting {args.clients} clients, {args.rate} qps/client, duration={args.duration}s")
threads = []
for i in range(args.clients):
t = threading.Thread(
target=sender_thread,
args=(i, args.service, args.rate, iface_name, args.ip),
daemon=True
)
t.start()
threads.append(t)
start_time = time.time()
try:
while time.time() - start_time < args.duration and not stop_event.is_set():
time.sleep(0.5)
except KeyboardInterrupt:
print("\n[INFO] Interrupted by user.")
stop_event.set()
stop_event.set()
print("Waiting for threads to finish...")
for t in threads:
t.join(timeout=2.0)
print("Done.")
if __name__ == "__main__":
# 导入条件依赖
if sys.platform.startswith("linux"):
import fcntl
main()
"python mdns_query.py --ip 192.168.2.2 --clients 10 --rate 2 --duration 30"
最新发布