ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Mongoose 内置 TCP/IP 协议栈接入 Linux TAP 虚拟网卡:驱动实现与三种组网实战

Mongoose 内置 TCP/IP 协议栈接入 Linux TAP 虚拟网卡:驱动实现与三种组网实战 嵌入式网络通信物联网【免费下载链接】mongooseEmbedded web server, with TCP/IP network stack, MQTT and Websocket项目地址https://gitcode.com/gh_mirrors/mon/mongoose点击查看免费下载导读本指南围绕 Mongoose 内置 TCP/IP 协议栈Mongoose Embedded Network Stack在 Linux TUN/TAP 设备上的接入实战展开示例程序通过标准 socket 接口读写一个 TAP 设备让 Mongoose 的 HTTP/HTTPS、DHCP、SNTP 等网络能力直接跑在一个虚拟网卡之上。文章完整覆盖 TAP 接口的创建、三种典型的宿主机组网方式纯虚拟接口、桥接到局域网、转发与 NAT 伪装以及背后的驱动回调与栈事件原理。读完本文你将掌握如何把 Mongoose 内置协议栈挂到任意能收发以太网帧的字符设备上并能在自己的 Linux 工作站上复现一套完整的嵌入式 Web 服务环境。示例概览为什么用 TAP 接口跑 Mongoosetutorials/tcpip/tap-driver/是一个把 Mongoose 内置 TCP/IP 协议栈 接入 Linux TUN/TAP 设备的完整示例核心思路非常直接Mongoose 内置协议栈只关心能否从底层拿到原始以太网帧——只要实现了收发帧的驱动回调它就能独立完成 ARP、IP、TCP/UDP、DHCP 客户端、DNS 与 SNTP 请求等全套网络工作不依赖操作系统协议栈TUN/TAP 是 Linux 提供的虚拟网络设备其中TAP 工作在二层以太网帧恰好与 Mongoose 内置栈的 L2 接口天然匹配示例使用标准 socket 接口open/read/write/ioctl打开并读写 TAP 设备因此既不需要内核模块也不需要第三方驱动make即可运行。示例程序main.c在启动后会监听两个端口Mongoose version : vMG_VERSION Listening on : http://0.0.0.0/ Listening on : https://0.0.0.0/这里的HTTP_URL/HTTPS_URL由 Makefile 通过编译宏注入默认值为http://0.0.0.0/与https://0.0.0.0/见 net.h 中的回退定义http://0.0.0.0:8000与https://0.0.0.0:8443。也就是说一旦 TAP 接口就绪这台虚拟设备就同时提供明文 HTTP 与 TLS HTTPS 服务。构建配置要点Makefile 中有一段关键编译选项CFLAGS_MONGOOSE -DMG_ENABLE_LINES1 -DMG_ENABLE_TCPIP1 -DMG_ENABLE_SOCKET0 -DMG_ENABLE_TCPIP_DRIVER_INIT0其含义是编译宏作用MG_ENABLE_TCPIP1启用 Mongoose 内置 TCP/IP 协议栈即net_builtin.c实现的那套协议MG_ENABLE_SOCKET0关闭对操作系统 socket API 的依赖网络完全由内置栈接管MG_ENABLE_TCPIP_DRIVER_INIT0禁止栈自动调用驱动init因为本示例自己负责打开 TAP 设备MG_ENABLE_LINES1日志输出附带源码文件名与行号便于调试启动示例时如果不带任何参数它默认打开名为tap0的接口、使用 MAC 地址02:00:01:02:03:77。可用的命令行参数解析逻辑见 main.c参数说明默认值-i ifcname指定要打开的 TAP 接口名称tap0-mac XX:XX:XX:XX:XX:XX指定网卡 MAC 地址02:00:01:02:03:77-v level设置日志级别mg_log_set由MG_LL_*默认值决定接口的创建由示例之外完成见下文 Linux 配置示例只是打开一个已存在的接口接口会随示例退出而消失这正是文档建议先建好接口、再做 DHCP/联网准备的原因。源码级解读TAP 驱动与内置栈的对接方式驱动接口三个回调函数Mongoose 内置栈通过struct mg_tcpip_driver与硬件/虚拟设备解耦其定义在 mongoose.hstruct mg_tcpip_driver { bool (*init)(struct mg_tcpip_if *); // One-time hardware init; return false on failure size_t (*tx)(const void *, size_t, struct mg_tcpip_if *); // Transmit one raw frame; return bytes sent size_t (*rx)(void *buf, size_t len, struct mg_tcpip_if *); // Read one raw frame; return frame size, 0 if none bool (*poll)(struct mg_tcpip_if *, bool); // Check link status; second arg is true once per second };TAP 示例在 main.c 中实现了其中三个init因MG_ENABLE_TCPIP_DRIVER_INIT0被跳过改由主函数自行完成tap_tx调用write()把一帧原始以太网数据写入 TAP 设备文件描述符写入失败时打印MG_ERROR并返回 0tap_rx调用read()从 TAP 设备读取一帧读取后usleep(1)让出 CPU避免轮询把单核跑到 100%注释原文 This is to avoid 100% CPUtap_poll轮询链路状态——return s1 ifp-driver_data ? true : false;即只要每秒一次的周期 tick 到来且驱动数据文件描述符有效就认为链路 UP。随后把这三个回调组装进struct mg_tcpip_driver连同文件描述符一起塞进struct mg_tcpip_ifstruct mg_tcpip_driver driver {.tx tap_tx, .poll tap_poll, .rx tap_rx}; struct mg_tcpip_if mif {.driver driver, .driver_data fd, .enable_req_dns true, .enable_req_sntp true, .fn mif_fn};driver_data指向 TAP 设备的文件描述符驱动回调通过ifp-driver_data取回它——这是 Mongoose 驱动体系的标准做法任何外部硬件W5500、w5100、PHY 等见 src/drivers 目录都遵循同一模式。打开 TAP 设备的细节main.c 展示了 Linux 与 OpenBSD 两种打开方式#ifndef __OpenBSD__ const char *tuntap_device /dev/net/tun; #else const char *tuntap_device /dev/tap0; #endif int fd open(tuntap_device, O_RDWR); ... ifr.ifr_flags IFF_TAP | IFF_NO_PI; // Linux: TAP 模式去掉额外的 4 字节包头 if (ioctl(fd, TUNSETIFF, (void *) ifr) 0) { ... } ... fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); // 非阻塞模式关键点Linux 下统一打开字符设备/dev/net/tun用TUNSETIFFioctl 把 fd 绑定到指定名称的 TAP 接口IFF_TAP表示二层以太网设备IFF_NO_PI去掉 Linux 默认附加的 4 字节包信息头让 Mongoose 收到的就是纯以太网帧随后用O_NONBLOCK设为非阻塞这样tap_rx在无数据时read返回负值驱动返回 0 即可。内置栈初始化与事件回调mg_tcpip_init(mgr, mif)完成栈的初始化。从 src/net_builtin.c 的实现可以看到初始化时的几个自动行为若mif.ip 0未配置静态 IP且 L2 类型不是 PPP/PPPoE则自动开启 DHCP 客户端ifp-enable_dhcp_client true——这就是为什么示例默认依赖 DHCP 分配地址未设置dhcp_name时默认使用主机名mip参与 DHCP 请求未指定recv_queue.size时轮询式驱动有rx回调默认队列大小等于framesizemg_tcpip_init会调用driver-init本示例已通过编译宏禁用改由用户代码打开设备。示例还注册了网络事件回调mif_fnmain.c处理两类事件if (ev MG_TCPIP_EV_STATE_CHANGE) { MG_INFO((State change: %u, *(uint8_t *) ev_data)); } else if (ev MG_TCPIP_EV_DHCP_DNS) { MG_INFO((Got DNS from DHCP: %M, mg_print_ip4, (uint32_t *) ev_data)); } else if (ev MG_TCPIP_EV_DHCP_SNTP) { ... // 构造 udp://sntp-ip:123 的 SNTP 服务器地址 }MG_TCPIP_EV_*事件族在 mongoose.h 中有完整定义本示例用到的有事件ev_data 类型含义MG_TCPIP_EV_STATE_CHANGEuint8_t *指向ifp-stateIPv4 链路状态变化MG_TCPIP_EV_DHCP_DNSuint32_t *DHCP 分配了 DNS 服务器MG_TCPIP_EV_DHCP_SNTPuint32_t *DHCP 分配了 SNTP 服务器链路状态机取值MG_TCPIP_STATE_*见 mongoose.hDOWN(0) →LINK_UP(1) →UP(2) →REQ(3等待 DHCP offer) →IP(4已获 IP、解析网关 MAC) →READY(5完全就绪)。测试代码 test/mip_tap_test.c 正是轮询mif.state MG_TCPIP_STATE_READY来判断协议栈是否完全可用。DHCP 请求参数DNS 与 SNTP示例把enable_req_dns与enable_req_sntp都置为true这意味着 DHCP 客户端会在请求中携带选项 6DNS与选项 42SNTP。对应实现见 src/net_builtin.cDHCP 请求构造时追加*p 6; *p 42;以及 src/net_builtin.c解析 DHCP 响应后触发MG_TCPIP_EV_DHCP_DNS/MG_TCPIP_EV_DHCP_SNTP事件。示例在收到 SNTP 服务器后还会动态拼出udp://ip:123地址供 SNTP 同步时间使用并在 net.c 中通过定时器周期性调用mg_sntp_connect与time.google.com同步。示例自带的 Web 应用示例的业务层在 net.c 中实现它监听 HTTP 与 HTTPS 两个端口mg_http_listen并实现了完整的 REST APIPOST /api/login/POST /api/logout基于用户名口令或 access_token 的登录态管理authenticate从Authorization: Basic或Cookie: access_token中取凭据登录成功通过 HttpOnly Cookie 下发 tokenTLS 连接下 Cookie 还会带Secure属性GET /api/stats/get返回温度、湿度等模拟遥测数据GET /api/events/get分页返回模拟告警事件流每页 20 条最多 400 条GET/POST /api/settings/get|set读取/修改设备设置日志开关、日志级别、亮度、设备名POST /api/firmware/upload通过mg_ota_begin/mg_ota_write/mg_ota_end演示固件在线升级OTA其余路径由mg_http_serve_dir提供静态文件服务工作站上读取web_root目录嵌入式环境则改用mg_fs_packed打包文件系统。这些 API 与 Mongoose 的 HTTP 路由、JSON 解析mg_json_get_*、格式化输出mg_xprintf与%M自定义打印深度绑定可作为在 TAP 网络里验证端到端连通性的现成测试目标。Linux 端准备创建 TAP 接口首先创建并启用一个 TAP 接口示例默认使用tap0$ sudo ip tuntap add dev tap0 mode tap $ sudo ip link set tap0 up注意事项某些系统上示例程序需要超级用户权限才能打开该设备也可以在创建接口时直接授予用户/组访问权限例如sudo ip tuntap add dev tap0 mode tap user yourusername或group yourgroup接口生命周期如果由示例自行创建接口进程退出后接口会随之消失而网络准备工作DHCP 服务器、对外连接通常需要提前就位因此文档强烈建议先手动建好接口、再启动示例——这样接口会一直存在DHCP 服务器等设施可以常驻。组网方式一纯虚拟接口仅宿主机可达这种模式下Mongoose 只能从宿主机访问但后续可以随时叠加转发与 NAT。步骤给 TAP 接口分配 IP$ sudo ip addr add 192.168.0.1/24 dev tap0在该接口上启动一个为192.168.0.0/24子网服务的 DHCP 服务器或者反过来给 Mongoose 配置该子网内的固定地址静态 IP 场景下在struct mg_tcpip_if中设置mif.ip、mif.mask、mif.gw参考 test/mip_tap_test.c 的写法启动示例打开同一个 TAP 接口$ make -C tutorials/tcpip/tap-driver/ clean all如果接口名不是tap0追加ARGS-i ifcname使用实际接口名。拓扑示意[DHCP server for 192.168.0.x] 192.168.0.1 192.168.0.x ┌─────────┐ │ tap0 ├──────────────────── tap-driver └─────────┘由于这种模式下宿主机之外的主机都无法访问该接口DNS 等必要服务需要同样部署在宿主机上并在 Mongoose 侧配置正确DHCP 下发的 DNS 由enable_req_dns自动获取。这是后续所有组网方案的基础形态。组网方式二桥接到局域网如果你已经有虚拟网桥例如用于连接多个虚拟机的br0/virbr0可以把 TAP 接口挂到网桥上Mongoose 将获得整个局域网的访问权同时也能被工作台和网内其他主机访问。准备网桥没有网桥时把物理网卡NIC纳入网桥时IP 地址要从网卡迁移到网桥接口上如果走 DHCPDHCP 客户端也要运行在网桥接口而不是网卡上。示例网卡enp9s0原 IP10.1.0.10/24$ sudo ip link add virbr0 type bridge $ sudo ip link set virbr0 up $ sudo ip addr del 10.1.0.10/24 dev enp9s0 $ sudo ip link set enp9s0 master virbr0 $ sudo ip addr add 10.1.0.10/24 dev virbr0用ifconfig检查并尝试 ping 网内主机验证桥接是否生效。把 TAP 挂到网桥$ sudo ip link set tap0 master virbr0注意此时tap0的状态在示例尚未打开它之前会显示为 DOWNNO-CARRIER这是正常现象$ ip link show master virbr0 2: enp9s0: BROADCAST,MULTICAST,UP,LOWER_UP mtu 1500 qdisc pfifo_fast master virbr0 state UP mode DEFAULT group default qlen 1000 link/ether 30:5a:3a:08:db:90 brd ff:ff:ff:ff:ff:ff 6: tap0: NO-CARRIER,BROADCAST,MULTICAST,UP mtu 1500 qdisc pfifo_fast master virbr0 state DOWN mode DEFAULT group default qlen 1000 link/ether 66:91:e2:5f:d7:ed brd ff:ff:ff:ff:ff:ff启动示例$ make -C tutorials/tcpip/tap-driver/ clean all接口名不同时同样追加ARGS-i ifcname。一切正常的话示例运行后tap0会变为UPLOWER_UP出现$ ip link show master virbr0 2: enp9s0: BROADCAST,MULTICAST,UP,LOWER_UP mtu 1500 qdisc pfifo_fast master virbr0 state UP mode DEFAULT group default qlen 1000 link/ether 30:5a:3a:08:db:90 brd ff:ff:ff:ff:ff:ff 6: tap0: BROADCAST,MULTICAST,UP,LOWER_UP mtu 1500 qdisc pfifo_fast master virbr0 state UP mode DEFAULT group default qlen 1000 link/ether 66:91:e2:5f:d7:ed brd ff:ff:ff:ff:ff:ff拓扑示意10.1.0.10 virbr0 │ ┌──────────────────┴──────────────────┐ │ │ 10.1.0.x │ ┌────────┐ │ │ │ tap0 ├─────────────┼─────── tap-driver │ └────────┘ │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ vnet1 │ │ enp9s0 │ │ vnet2 │ │ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ │ │ │ └──────┼───────────┼───────────┼──────┘ │ │ │ │ Ethernet │ Local LAN 10.1.0.x │ DHCP server, router ▼ Internet桥接模式下无需像方式一那样手动加 IP除非你的局域网里没有 DHCP 服务器——那时才需要给 Mongoose 配一个子网内的固定地址。注意如果宿主机运行着 Docker它引入的防火墙规则可能会破坏桥接Docker 默认的 FORWARD 链策略会拦截桥接流量。另外桥接通常无法用于 Wi-Fi原因见文末附录。组网方式三转发与 NATMasquerade当虚拟接口已有 IP 时可以让 Linux 充当 NAT/转发网关把 Mongoose 的流量转发到另一张接口例如上网用的enp9s0。这种模式下 Mongoose 能访问 Internet工作台也能访问它但局域网内其他主机访问不到如需隔离场景非常合适。配置伪装与转发前提你已经配好了防火墙$ sudo iptables -t nat -A POSTROUTING -o enp9s0 -j MASQUERADE $ echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward注意把enp9s0换成实际的出网接口名。启动示例$ make -C tutorials/tcpip/tap-driver/ clean all接口名不同时追加ARGS-i ifcname。拓扑示意[DHCP server for 192.168.0.x] ┌────────────► 192.168.0.1 192.168.0.x │ forwarding ┌─────────┐ │ masquerading │ tap0 ├──────────────────── tap-driver │ └─────────┘ ▼ Your IP (LAN / Public) ┌─────────┐ │ enp9s0 │ └────┬────┘ │ │ Ethernet [WiFi] │ ▼ Internet变体NIC 本身已在网桥中如果你的物理网卡是网桥的一部分例如你在跑虚拟机则用网桥接口做伪装而不是物理网卡此时 IP 属于网桥而不是网卡$ sudo iptables -t nat -A POSTROUTING -o virbr0 -j MASQUERADE $ echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward记得使用正确的网桥接口名。拓扑示意[DHCP server for 192.168.0.x] 10.1.0.10 ◀──────────────► 192.168.0.1 192.168.0.x virbr0 forwarding ┌─────────┐ │ masquerading │ tap0 ├──────────────────── tap-driver │ └─────────┘ ┌──────────────────┴──────────────────┐ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ vnet1 │ │ enp9s0 │ │ vnet2 │ │ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ │ │ │ └──────┼───────────┼───────────┼──────┘ │ │ │ │ Ethernet │ Local LAN 10.1.0.x │ DHCP server, router ▼ Internet同样地Wi-Fi 场景下桥接通常不可用参见附录。附录为什么 Wi-Fi 上桥接通常行不通由于无线标准的工作方式桥接往往要求能够深入配置接入点/无线路由器AP原因在于地址字段的差异以太网帧只需要 Sender AddressSA与 Destination AddressDA两个 MAC 地址Wi-Fi 额外要求 Transmitter AddressTA与 Receiver AddressRA字段一共 4 个 MAC 地址常见场景STA 到 AP 的帧 TA SAAP 到 STA 的帧 DA RA实际上只用了 3 个地址因此在 AP 上做桥接通常没有问题——AP 只需为 AP→STA 帧改 SA、为 STA→AP 帧改 DA仍然只用 3 个地址但在 STA无线工作站上做桥接时STA→AP 帧的 SA 与 TA 不再相等AP→STA 帧的 DA 与 RA 也不同必须用满 4 个地址——而大多数 STA 与 AP 默认都不支持这种模式。STA 侧可能需要配置工具开启4addr模式AP 侧则类似两台 LAN 桥接的场景需要启用 WDSWireless Distribution System。即便两侧都能配置由于不同厂商实现方式存在兼容性问题仍然无法保证一定可用。因此本文档的三种组网方式中桥接方案默认面向有线网络无线环境请优先考虑方式一纯虚拟接口或方式三转发 NAT。延伸阅读示例入口与 TAP 驱动实现tutorials/tcpip/tap-driver/main.c、tutorials/tcpip/tap-driver/net.c驱动与接口结构体、事件定义mongoose.h内置协议栈核心实现src/net_builtin.cmg_tcpip_init见 src/net_builtin.c官方测试中基于同一 TAP 驱动做协议栈测试与 TCP 丢包重传验证test/mip_tap_test.c同一思路的抓包版驱动示例基于 pcap 读原始帧tutorials/tcpip/pcap-driver更多内置栈驱动的参考实现W5500、W5100、PHY 等src/drivers赞分享嵌入式网络通信物联网【免费下载链接】mongooseEmbedded web server, with TCP/IP network stack, MQTT and Websocket项目地址https://gitcode.com/gh_mirrors/mon/mongoose点击查看免费下载相关推荐RP2040/RP2350 上的 Mongoose 内置 TCP/IP 协议栈基于 PIO 的 RMII 以太网驱动实战RP2040/RP2350 上的 Mongoose 内置 TCP/IP 协议栈基于 PIO 的 RMII 以太网驱动实战 本文围绕 tutorials/rp/嵌入式网络通信物联网Mongoose 内置 TCP/IP 协议栈net_builtin安全审查实战指南攻击面、边界校验与驱动防护Mongoose 内置 TCP/IP 协议栈net_builtin安全审查实战指南攻击面、边界校验与驱动防护 导读 本文面向嵌入式固件开发者与安全工程师嵌入式网络通信物联网RP2040/RP2350 RM2 WiFi 模块跑通 Mongoose 内置 TCP/IP 协议栈从 PIO SPI 驱动到 HTTP 服务器实战RP2040/RP2350 RM2 WiFi 模块跑通 Mongoose 内置 TCP/IP 协议栈从 PIO SPI 驱动到 HTTP 服务器实战 本指嵌入式网络通信物联网创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表