如何用Python拦截浏览器流量并实现重定向与目标主机名获取?
实现网络请求拦截与重定向(含主机名获取)
要实现类似虚拟局域网的请求拦截重定向,核心难点是准确获取目标主机名,不同网络层级的实现方式差异很大,以下是分场景的可行方案:
一、DNS劫持(最直接获取主机名)
这是获取目标主机名最可靠的方式——浏览器发起请求前会先做DNS解析,拦截这个阶段的请求可以直接拿到域名,同时直接返回指定IP实现重定向,无需处理后续数据包。
实现方式
- 本地DNS服务器:运行轻量DNS服务,将系统DNS设置指向本地,拦截特定域名的解析请求并返回目标IP,其他请求转发到公共DNS。
- 系统调用钩子:通过钩子劫持系统的DNS解析函数(如Windows的
getaddrinfo、Linux的gethostbyname),直接修改解析结果。
代码示例(Python本地DNS服务器)
from dnslib import DNSRecord, RR, QTYPE, A import socket REDIRECT_MAP = { "example.com": "192.168.1.100", "test.example.org": "10.0.0.5" } DEFAULT_DNS = ("8.8.8.8", 53) def handle_dns_query(data, addr, sock): req = DNSRecord.parse(data) qname = str(req.q.qname)[:-1] # 去除域名末尾的点 resp = req.reply() if qname in REDIRECT_MAP: # 返回指定IP resp.add_answer(RR(rname=req.q.qname, rdata=A(REDIRECT_MAP[qname]), ttl=300)) else: # 转发到公共DNS forward_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) forward_sock.sendto(data, DEFAULT_DNS) forward_data, _ = forward_sock.recvfrom(1024) resp = DNSRecord.parse(forward_data) sock.sendto(resp.pack(), addr) def run_dns_server(): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind(("0.0.0.0", 53)) print("DNS拦截服务启动: 0.0.0.0:53") while True: data, addr = sock.recvfrom(1024) handle_dns_query(data, addr, sock) if __name__ == "__main__": run_dns_server()
二、应用层代理(HTTP/HTTPS请求拦截)
如果需要在请求发起后基于应用层信息(如路径、Header)判断,可使用应用层代理,直接获取HTTP的Host头或HTTPS的SNI字段(TLS握手时的服务器名称)识别目标主机名,再转发到指定IP。
实现方式
- 透明代理:通过系统工具(Linux的
iptables、Windows的WinDivert)将流量转发到代理端口,代理解析请求后再转发。 - 显式代理:配置浏览器使用你的代理服务,直接处理所有请求。
关键注意事项
- HTTPS请求需要生成自签名CA证书并导入系统信任,否则代理无法解密流量获取SNI或Host头。
- 可借助现成框架(如Python的
mitmproxy)快速实现,无需从零开发。
三、内核级数据包拦截(驱动级效果)
若需要接近驱动级的低延迟拦截,可在网络内核层处理数据包,但IP数据包本身不包含主机名,需结合以下方式:
- 先通过DNS劫持记录「域名-IP」映射表,再在内核层拦截对应IP的流量,修改目标地址。
- 对HTTPS流量,解析TLS握手包中的SNI字段(内核层解析TLS复杂度较高)。
实现方式
- Linux:使用
eBPF或netfilter内核模块,拦截IP数据包并修改目标IP。 - Windows:使用
WFP(Windows Filtering Platform)驱动或WinDivert(用户态但接近驱动级的工具)。
代码示例(Linux eBPF简化版)
#include <linux/bpf.h> #include <bpf/bpf_helpers.h> // 要重定向的原目标IP和新IP(网络字节序) #define OLD_DST_IP 0x0a000001 // 10.0.0.1 #define NEW_DST_IP 0xc0a80164 // 192.168.1.100 SEC("tc") int redirect_target_ip(struct __sk_buff *skb) { __be32 dst_ip = skb->dst_ip; if (dst_ip == OLD_DST_IP) { skb->dst_ip = NEW_DST_IP; } return TC_ACT_OK; } char _license[] SEC("license") = "GPL";
四、方案选择建议
- 优先选DNS劫持:实现简单、性能高,能直接获取准确的主机名,适合大部分场景。
- 若需处理直接访问IP的请求,或需要基于应用层信息判断,选应用层代理。
- 若追求极致性能或系统级拦截效果,选内核级拦截+DNS映射的组合。
内容的提问来源于stack exchange,提问作者chronosirius
相关产品推荐
相关产品推荐

