基于Arduino框架的ESP系列NAPT扩展器:URL监控与拦截实现
ESP8266/ESP32 NAT中继:URL监控与IP/域名/URL拦截实现(Arduino框架)
核心思路
原RangeExtender-NAPT基于lwIP实现NAT转发,要实现监控和拦截,需通过lwIP数据包钩子捕获AP侧的出站流量,解析HTTP请求获取URL,匹配黑名单后截断连接或丢弃数据包。针对HTTPS流量,因加密无法解析URL,仅能拦截IP或域名(需配合DNS拦截或IP匹配)。
步骤1:修改原NAPT代码,添加流量捕获钩子
在原RangeExtender-NAPT代码基础上,添加lwIP的数据包处理逻辑,实现监控与拦截:
头文件引入
在代码顶部添加:
#include <lwip/ip.h> #include <lwip/tcp.h> #include <lwip/pbuf.h> #include <lwip/inet.h> #include <string.h>
定义黑名单与全局变量
// 黑名单配置:可按需添加IP、域名、URL const char* blockedIPs[] = {"192.168.1.100", "8.8.8.8"}; const char* blockedDomains[] = {"example.com", "google.com"}; const char* blockedURLs[] = {"/admin", "/login"}; const int blockedIPCount = sizeof(blockedIPs)/sizeof(blockedIPs[0]); const int blockedDomainCount = sizeof(blockedDomains)/sizeof(blockedDomains[0]); const int blockedURLCount = sizeof(blockedURLs)/sizeof(blockedURLs[0]); // 存储当前解析的HTTP请求信息 char currentHost[64] = {0}; char currentURL[128] = {0};
实现HTTP请求解析函数
bool parseHttpRequest(pbuf* p, ip_addr_t* destIP) { if (p->len < 40) return false; // 跳过过小的数据包 char* payload = (char*)p->payload; // 匹配HTTP GET/POST请求行 if (strstr(payload, "GET ") || strstr(payload, "POST ")) { // 提取请求URL(从请求方法到HTTP协议标识之间的部分) char* start = strstr(payload, " ") + 1; char* end = strstr(start, " HTTP/"); if (start && end) { int urlLen = end - start; if (urlLen < sizeof(currentURL)) { strncpy(currentURL, start, urlLen); currentURL[urlLen] = '\0'; } } // 提取Host头 char* hostStart = strstr(payload, "Host: "); if (hostStart) { hostStart += 6; char* hostEnd = strstr(hostStart, "\r\n"); if (hostEnd) { int hostLen = hostEnd - hostStart; if (hostLen < sizeof(currentHost)) { strncpy(currentHost, hostStart, hostLen); currentHost[hostLen] = '\0'; } } } // 打印监控日志 Serial.print("监控请求:"); Serial.print(currentHost); Serial.print(currentURL); Serial.print(" -> 目标IP: "); Serial.println(inet_ntoa(*destIP)); return true; } return false; }
实现拦截判断逻辑
bool shouldBlock(ip_addr_t* destIP, const char* host, const char* url) { // 1. 拦截指定IP char destIPStr[16]; strcpy(destIPStr, inet_ntoa(*destIP)); for (int i=0; i<blockedIPCount; i++) { if (strcmp(destIPStr, blockedIPs[i]) == 0) { Serial.print("拦截IP:"); Serial.println(destIPStr); return true; } } // 2. 拦截指定域名 if (strlen(host) > 0) { for (int i=0; i<blockedDomainCount; i++) { if (strstr(host, blockedDomains[i]) != NULL) { Serial.print("拦截域名:"); Serial.println(host); return true; } } } // 3. 拦截指定URL路径 if (strlen(url) > 0) { for (int i=0; i<blockedURLCount; i++) { if (strstr(url, blockedURLs[i]) != NULL) { Serial.print("拦截URL:"); Serial.println(url); return true; } } } return false; }
注册lwIP数据包钩子
在setup()函数中原NAPT初始化完成后,添加钩子注册:
// 注册IP输入钩子,捕获AP侧出站流量 ip_input_hook_register([](pbuf* p, netif* inp) -> err_t { // 仅处理AP接口的流量 if (inp != WiFiAP.getNetworkInterface()) return ERR_OK; ip_hdr* ipHeader = (ip_hdr*)p->payload; if (ipHeader->proto == IP_PROTO_TCP) { // 跳过IP头,获取TCP头 pbuf_remove_header(p, sizeof(ip_hdr)); tcp_hdr* tcpHeader = (tcp_hdr*)p->payload; // 仅处理HTTP(80端口)出站请求 if (htons(tcpHeader->dest) == 80) { ip_addr_t destIP; IP4_ADDR(&destIP, ipHeader->dest.addr[0], ipHeader->dest.addr[1], ipHeader->dest.addr[2], ipHeader->dest.addr[3]); // 解析HTTP请求并判断是否拦截 if (parseHttpRequest(p, &destIP) && shouldBlock(&destIP, currentHost, currentURL)) { // 发送TCP重置包断开连接 tcp_reset(ipHeader->src, ipHeader->dest, tcpHeader->src, tcpHeader->dest); pbuf_free(p); return ERR_ABRT; // 丢弃数据包 } } // 恢复IP头,继续正常转发 pbuf_add_header(p, sizeof(ip_hdr)); } return ERR_OK; });
步骤2:HTTPS流量拦截补充
对于HTTPS(443端口),因流量加密无法解析URL,可通过DNS拦截实现域名拦截:
// 注册DNS处理钩子 dns_setserver(0, &dnsServerIP); // 先设置默认DNS服务器 dns_set_callback([](const char* name, ip_addr_t* ipaddr) -> bool { // 检查域名是否在黑名单中 for (int i=0; i<blockedDomainCount; i++) { if (strcmp(name, blockedDomains[i]) == 0) { Serial.print("拦截DNS请求:"); Serial.println(name); // 返回无效IP阻止解析 IP4_ADDR(ipaddr, 0, 0, 0, 0); return true; // 拦截该DNS请求 } } return false; // 正常转发请求 });
兼容性说明
- ESP8266:直接使用上述代码,基于Arduino ESP8266核心编译即可。
- ESP32/ESP32-S2/ESP32-S3:需将
WiFiAP.getNetworkInterface()替换为WiFi.softAPgetNetworkInterface(),其余逻辑通用,基于Arduino ESP32核心编译。
注意事项
- 上述代码仅处理HTTP(80端口)的URL监控与拦截,HTTPS需依赖DNS拦截或IP拦截。
- 黑名单配置可按需动态调整,如需动态添加可改用动态数组或字符串存储。
- 数据包解析逻辑仅处理标准HTTP请求,复杂请求(如分块编码)需完善解析逻辑。
内容的提问来源于stack exchange,提问作者Tirtharaj Sinha
相关产品推荐
相关产品推荐

