You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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核心编译。

注意事项

  1. 上述代码仅处理HTTP(80端口)的URL监控与拦截,HTTPS需依赖DNS拦截或IP拦截。
  2. 黑名单配置可按需动态调整,如需动态添加可改用动态数组或字符串存储。
  3. 数据包解析逻辑仅处理标准HTTP请求,复杂请求(如分块编码)需完善解析逻辑。

内容的提问来源于stack exchange,提问作者Tirtharaj Sinha

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 12:44:51