请重新将以下代码按照代码规范进行缩进,然后输出一遍:
import socket
import struct
import argparse
import random
import string
import time
import threading
from collections import defaultdict
" python mdns_responder.py --iface_ip 192.168.0.100 "
MDNS_ADDR = “224.0.0.251”
MDNS_PORT = 5353
class DNSError(Exception):
“”“DNS错误基类”“”
pass
def generate_random_service_id(length=8):
“”“生成8位随机服务ID(大写字母+数字)”“”
return ‘’.join(random.choices(string.ascii_uppercase + string.digits, k=length))
def generate_random_hostname_suffix(length=8):
“”“生成随机主机名后缀(数字)”“”
return ‘’.join(random.choices(string.digits, k=length))
def parse_dns_name(data: bytes, offset: int) -> tuple:
“”"
解析DNS名称(支持压缩指针)
返回: (域名, 新偏移量)
“”"
labels = []
traversed = set()
while offset < len(data): length = data[offset] offset += 1 if length == 0: # 结束标记 break # 处理压缩指针 if length & 0xC0 == 0xC0: if offset >= len(data): raise DNSError("压缩指针不完整") ptr_offset = ((length & 0x3F) << 8) | data[offset] offset += 1 if ptr_offset in traversed: break traversed.add(ptr_offset) if ptr_offset >= len(data): break name_part, _ = parse_dns_name(data, ptr_offset) labels.append(name_part) break # 处理普通标签 end = offset + length if end > len(data): break labels.append(data[offset:end].decode('utf-8', 'ignore')) offset = end return '.'.join(labels), offset
def build_dns_name(name: str, compress_dict: dict, base_offset: int) -> bytes:
“”"
构建DNS名称(支持压缩)
compress_dict: {域名: 偏移量}
base_offset: 当前数据起始偏移
“”"
encoded = b’’
parts = name.split(‘.’)
for i in range(len(parts)): partial = '.'.join(parts[i:]) # 应用压缩 if len(partial) > 3 and partial in compress_dict: ptr = compress_dict[partial] if ptr < 0x4000: encoded += struct.pack('!H', 0xC000 | ptr) return encoded # 添加标签 label = parts[i] if not 1 <= len(label) <= 63: raise DNSError(f"无效标签长度: {len(label)}") encoded += bytes([len(label)]) + label.encode('utf-8') # 注册压缩点 current = '.'.join(parts[i:]) pos = base_offset + len(encoded) - len(label) - 1 if len(current) > 3 and current not in compress_dict: compress_dict[current] = pos encoded += b'\x00' return encoded
def create_service_response(transaction_id: int,
service_type: str,
instance_name: str,
host_name: str,
ip: str,
port: int) -> bytes:
“”"
创建单服务响应包
“”"
compress_dict = {}
parts = []
current_offset = 12 # DNS头部长12字节
# === DNS头部 === flags = 0x8400 # QR=1, AA=1 qdcount = 0 # 无查询部分 ancount = 3 # PTR + SRV + A header = struct.pack("!HHHHHH", transaction_id, flags, qdcount, ancount, 0, 0) parts.append(header) # === PTR记录 === ptr_name = build_dns_name(service_type, compress_dict, current_offset) ptr_data = build_dns_name(instance_name, compress_dict, current_offset + len(ptr_name) + 10) ptr_record = ( ptr_name + struct.pack("!HHIH", 12, 1, 4500, len(ptr_data)) + # TYPE=PTR, CLASS=IN, TTL=120 ptr_data ) parts.append(ptr_record) current_offset += len(ptr_record) # === SRV记录 === srv_name = build_dns_name(instance_name, compress_dict, current_offset) srv_data = struct.pack("!HHH", 0, 0, port) # 优先级, 权重, 端口 srv_data += build_dns_name(host_name, compress_dict, current_offset + len(srv_name) + 10 + len(srv_data)) srv_record = ( srv_name + struct.pack("!HHIH", 33, 1, 120, len(srv_data)) + # TYPE=SRV srv_data ) parts.append(srv_record) current_offset += len(srv_record) # === A记录 === a_name = build_dns_name(host_name, compress_dict, current_offset) a_data = socket.inet_aton(ip) a_record = ( a_name + struct.pack("!HHIH", 1, 1, 120, 4) + # TYPE=A a_data ) parts.append(a_record) return b''.join(parts)
def create_pair_response(transaction_id: int,
airplay_instance: str,
raop_instance: str,
host_name: str,
ip: str) -> bytes:
“”"
创建服务对响应包(AirPlay + RAOP)
“”"
compress_dict = {}
parts = []
current_offset = 12
# === DNS头部 === flags = 0x8400 qdcount = 0 ancount = 5 # 2 PTR + 2 SRV + 1 A header = struct.pack("!HHHHHH", transaction_id, flags, qdcount, ancount, 0, 0) parts.append(header) # === AirPlay PTR === ptr_ap_name = build_dns_name("_airplay._tcp.local", compress_dict, current_offset) ptr_ap_data = build_dns_name(airplay_instance, compress_dict, current_offset + len(ptr_ap_name) + 10) ptr_ap_record = ptr_ap_name + struct.pack("!HHIH", 12, 1, 120, len(ptr_ap_data)) + ptr_ap_data parts.append(ptr_ap_record) current_offset += len(ptr_ap_record) # === AirPlay SRV === srv_ap_name = build_dns_name(airplay_instance, compress_dict, current_offset) srv_ap_data = struct.pack("!HHH", 0, 0, 5000) srv_ap_data += build_dns_name(host_name, compress_dict, current_offset + len(srv_ap_name) + 10 + len(srv_ap_data)) srv_ap_record = srv_ap_name + struct.pack("!HHIH", 33, 1, 120, len(srv_ap_data)) + srv_ap_data parts.append(srv_ap_record) current_offset += len(srv_ap_record) # === RAOP PTR === ptr_raop_name = build_dns_name("_raop._tcp.local", compress_dict, current_offset) ptr_raop_data = build_dns_name(raop_instance, compress_dict, current_offset + len(ptr_raop_name) + 10) ptr_raop_record = ptr_raop_name + struct.pack("!HHIH", 12, 1, 120, len(ptr_raop_data)) + ptr_raop_data parts.append(ptr_raop_record) current_offset += len(ptr_raop_record) # === RAOP SRV === srv_raop_name = build_dns_name(raop_instance, compress_dict, current_offset) srv_raop_data = struct.pack("!HHH", 0, 0, 7000) srv_raop_data += build_dns_name(host_name, compress_dict, current_offset + len(srv_raop_name) + 10 + len(srv_raop_data)) srv_raop_record = srv_raop_name + struct.pack("!HHIH", 33, 1, 120, len(srv_raop_data)) + srv_raop_data parts.append(srv_raop_record) current_offset += len(srv_raop_record) # === 共享A记录 === a_name = build_dns_name(host_name, compress_dict, current_offset) a_data = socket.inet_aton(ip) a_record = a_name + struct.pack("!HHIH", 1, 1, 120, 4) + a_data parts.append(a_record) return b''.join(parts)
class MDNSResponder:
def init(self, iface_ip: str):
“”“mDNS响应器初始化”“”
self.iface_ip = iface_ip
self.services = [] # 所有服务对
self.service_map = defaultdict(list) # 服务类型到实例的映射
self.running = False
# 创建socket self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 多播设置 self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255) self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(iface_ip)) try: self.sock.bind(("", MDNS_PORT)) mreq = struct.pack("!4s4s", socket.inet_aton(MDNS_ADDR), socket.inet_aton(iface_ip)) self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) print(f"[mDNS] Socket bound to {MDNS_ADDR}:{MDNS_PORT}") except Exception as e: raise RuntimeError(f"Socket初始化失败: {e}") def generate_services(self, start_ip: str, count: int): """生成服务对(每对包含AirPlay和RAOP服务)""" base_ip, start_num = start_ip.rsplit('.', 1) start_num = int(start_num) for i in range(count): # 生成递增IP地址 ip = f"{base_ip}.{start_num + i}" # 生成唯一服务ID(8位字符) service_id = generate_random_service_id() # 生成主机名(主机名后缀为8位数字) host_suffix = generate_random_hostname_suffix() host_name = f"host-{host_suffix}.local" # AirPlay实例名 airplay_instance = f"{service_id}._airplay._tcp.local" # RAOP实例名(遵循Apple规范) raop_instance = f"{service_id}@AirPlay._raop._tcp.local" # 添加到服务列表 self.services.append({ "ip": ip, "host_name": host_name, "airplay_instance": airplay_instance, "raop_instance": raop_instance }) # 添加到服务映射 self.service_map["_airplay._tcp.local"].append( (airplay_instance, host_name, ip, 5000) ) self.service_map["_raop._tcp.local"].append( (raop_instance, host_name, ip, 7000) ) print(f"[生成服务] 已创建 {count} 对服务") def start(self): """启动mDNS响应器""" self.running = True print(f"[mDNS响应器] 在 {self.iface_ip} 上启动") while self.running: try: data, addr = self.sock.recvfrom(2048) if len(data) < 12: continue # 处理数据包 self.handle_packet(data, addr) except Exception as e: if self.running: # 防止关闭时的报错 print(f"处理报文错误: {e}") def handle_packet(self, data: bytes, addr: tuple): """处理接收到的mDNS查询(支持多问题查询)""" # 解析头部 transaction_id = struct.unpack("!H", data[0:2])[0] flags = struct.unpack("!H", data[2:4])[0] qdcount = struct.unpack("!H", data[4:6])[0] # 问题数量 # 只处理查询请求 if flags & 0x8000: return offset = 12 # DNS头部后开始解析问题 services_to_respond = set() # 收集需要响应的服务类型 # 循环读取所有问题 for _ in range(qdcount): try: # 解析域名 qname, new_offset = parse_dns_name(data, offset) # 读取类型和类(各2字节) if new_offset + 4 > len(data): break qtype, qclass = struct.unpack("!HH", data[new_offset:new_offset+4]) offset = new_offset + 4 # 只处理PTR查询(qtype=12)且类为IN(qclass=1) if qtype != 12 or qclass != 1: continue service_type = qname.rstrip('.').lower() if service_type in self.service_map: services_to_respond.add(service_type) except DNSError as e: # 跳过当前问题,继续下一个 continue # 对每个需要响应的服务类型,发送响应 for service_type in services_to_respond: print(f"[查询] 来自 {addr[0]} 查询 {service_type}") for instance_info in self.service_map[service_type]: instance_name, host_name, ip, port = instance_info try: response = create_service_response( transaction_id, service_type, instance_name, host_name, ip, port ) self.sock.sendto(response, (MDNS_ADDR, MDNS_PORT)) except Exception as e: print(f"构建响应错误: {e}") def stop(self): """停止响应器""" self.running = False self.sock.close() print("[mDNS响应器] 已停止")
class ServiceAnnouncer:
def init(self, iface_ip: str, services: list):
“”“服务广播器初始化”“”
self.iface_ip = iface_ip
self.services = services
self.running = False
# 创建广播socket self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255) self.sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(iface_ip)) def start(self): """启动服务广播""" self.running = True print("[广播器] 开始周期性广播服务") broadcast_count = 0 while self.running: try: broadcast_count += 1 broadcast_time = time.strftime("%Y-%m-%d %H:%M:%S") print(f"\n[广播 #{broadcast_count}] 开始于 {broadcast_time}") # 广播每个服务对 for idx, service in enumerate(self.services): transaction_id = random.randint(0, 0xFFFF) try: response = create_pair_response( transaction_id, service["airplay_instance"], service["raop_instance"], service["host_name"], service["ip"] ) self.sock.sendto(response, (MDNS_ADDR, MDNS_PORT)) print(f" 已广播服务对 #{idx+1}/{len(self.services)}: {service['ip']}") except Exception as e: print(f" 广播错误: {e}") # 等待下次广播 sleep_interval = 120 # 2分钟 for _ in range(sleep_interval): if not self.running: break time.sleep(1) except Exception as e: print(f"广播错误: {e}") def stop(self): """停止广播""" self.running = False self.sock.close() print("[广播器] 已停止")
def main():
parser = argparse.ArgumentParser(description=“AirPlay & RAOP mDNS 批量响应器”)
parser.add_argument(“–iface_ip”, required=True, help=“网络接口IP”)
parser.add_argument(“–start_ip”, default=“192.168.0.2”, help=“起始IP地址 (默认: 192.168.0.2)”)
parser.add_argument(“–count”, type=int, default=100, help=“服务对数量 (默认: 100)”)
args = parser.parse_args()
# 创建响应器 responder = MDNSResponder(args.iface_ip) # 生成服务 responder.generate_services(args.start_ip, args.count) # 启动响应线程 responder_thread = threading.Thread(target=responder.start, daemon=True) responder_thread.start() print("服务已启动,按Ctrl+C退出...") try: # 主线程等待中断信号 while True: time.sleep(1) except KeyboardInterrupt: print("正在停止...") # 设置停止标志(如果类中有) responder.running = False # 也可以调用stop方法,但注意线程可能阻塞在recvfrom或sleep中 # 所以设置标志后,还需要中断这些阻塞(例如关闭socket) responder.stop() # 等待线程结束(如果有必要) responder_thread.join(timeout=1.0) print("已退出")
if name == “main”:
main()
最新发布