书接上文《LwIP分析之在Linux上运行LwIP的第二种方法》,第二种运行LwIP协议栈的方法,我们借用Linux TAP/TUN虚拟网卡,来联通本机Linux TCP/IP协议栈和LwIP TCP/IP协议栈,然后通过设置路由转发规则,可以达到我们在本机使用curl作为客户端,向运行在LwIP协议栈上的应用层httpd服务端发起请求,而httpd处理完http请求之后,又可以通过LwIP协议栈和TAP/TUN虚拟网卡,将数据返回给我们Linux协议栈上的curl程序。
但它的弊端是只能在本机上进行连通性测试,我们无法在其他主机上,向这个LwIP上的httpd发送请求。我们借助 Linux Bridge 来解决这个问题,也就是在Linux上运行LwIP的第三种方法。
方法三的基本原理
该方法,我们直接使用LwIP官方提供的contrib示例代码,contrib针对Linux的移植文件目录是lwip-STABLE-2_2_0_RELEASE/contrib/ports/unix。
- 该方法的核心原理是,我们引入Linux Bridge,让Linux协议栈、Linux主机物理网卡、TAP/TUN虚拟网卡、LwIP协议栈、LwIP模拟网卡,都加入到这个Linux Bridge虚拟交换机中。然后将物理网卡的IP转交给Linux Bridge,让其来承担路由以及数据发送的工作。
- 如果Linux物理网卡的IP地址是 192.168.0.62,我们选择一个没有使用的真实局域网地址,比如192.168.0.200,配置给LwIP模拟网卡。在网桥的作用下,其他主机也可以通过局域网直接访问192.168.0.200。
- 其他主机的浏览器访问 http://192.168.0.200 时,通过arp协议可以获得192.168.0.200的MAC地址(该过程比较有意思,请读者自行分析),然后将数据发送到局域网中。
- 这个时候,我们Linux主机的物理网卡捕捉到该数据帧,将该数据包转给Linux Bridge,而Linux Bridge就如同一个交换机一般,它根据MAC转发表,将该数据包转发给 tap0,tap0 又给了 LwIP的模拟网卡。LwIP模拟网卡获取到数据,然后将数据给了LwIP协议栈,然后在LwIP的协议栈中逐层流转和处理。
- LwIP协议栈的数据链路层、网络层、传输层依次处理之后,到达应用层的httpd功能服务模块,httpd分析http请求处理之后,根据http请求,原路返回一段 html 文件。
其请求数据流转图,可使用下图描述:
Linux Bridge 的配置方法
第一步:Linux主机的物理网卡是ens33,它配置IP地址 192.168.0.62,这个IP可以被同局域网的其他主机访问。请记住你的主机IP,因为后面我们会将这个IP配置给Linux Bridge。
我们看下在配置网桥之前的网卡列表和路由表:
目前有一个实体网卡ens33,IP地址为 192.168.0.62,默认网关是 192.168.0.1。
第二步:将如下内容保存为 settapif.sh 文件,请注意替换你的主机IP地址以及默认网关IP地址。
1 |
|
对上述脚本简单的说明:
sudo ip link add name br0 type bridge
和sudo ip link set dev br0 up
创建名为 br0 的网桥,并启动它。
sudo ip tuntap add dev tap0 mode tap user whoami
和sudo ip link set tap0 up
创建名为 tap0 的虚拟网卡,并启动它。
sudo ip link set tap0 master br0
将 tap0 连接到 br0 上,通俗将 就是将 tap0 这个虚拟网卡原来连接Linux协议栈的一端,现在改连接到 br0 这个虚拟交换机的某个端口上,不再对接Linux协议栈。而 tap0 的还有一端,还是连接LwIP协议栈的模拟网卡上。
sudo ip address add 192.168.0.62/24 dev br0
将原来物理网卡ens33的IP地址让给网桥,让网桥发挥网络接入功能。
sudo ip route append default via 192.168.0.1 dev br0
将原来物理网卡ens33的默认网关地址,原模原样的配置给网桥。
sudo ip link set ens33 master br0
将 ens33 连接到 br0 上,通俗将 ens33 这个物理网卡原来连接Linux协议栈的一端,现在改连接到 br0 这个虚拟交换机的某个端口上,不再对接Linux协议栈。而 ens33 的还有一端,还是保持对接原物理网线。
sudo ip address del 192.168.0.62/24 dev ens33
进行完上述操作之后,其实原ens33物理网卡的IP已经失效,有没有已经无所谓,这里我们将其删除,以免引起误解。
第三步:我们打开仿真终端,将其放到服务器上的任意目录,在shell中使用如下命令使用这个配置脚本:
1 | $ sudo ./settapif.sh |
我们再看在配置网桥之后的网卡列表和路由表:
简单来说,有这么几个变化:
- 增加了网桥 br0,该网桥拥有原物理网卡 ens33 的IP地址等信息。
- 原 ens33 失去了IP地址,退化为虚拟网线。
- 增加了虚拟网卡 tap0。
- 路由表发生了变化,因为 ens33 失去了IP,所以Linux协议栈的路由功能由 br0 来承担。
至此,网桥、虚拟网卡和相应路由表已经准备妥当,我们下面开始编译和运行LwIP协议栈。
注意:如果你是在虚拟机上进行测试,请使用VMWare,在VirtualBox上该运行LwIP的方法行不通!
修改源代码相关配置
首先,先下载源代码并解压,使用如下命令:
1 | $ wget https://github.com/lwip-tcpip/lwip/archive/refs/tags/STABLE-2_2_0_RELEASE.tar.gz |
第一步:创建配置文件 lwipcfg.h。
我们在 lwip-STABLE-2_2_0_RELEASE/contrib/examples/example_app 目录下创建配置文件 lwipcfg.h ,本文复制同目录的 lwipcfg.h.example 文件。使用下面的命令:
1
2
3$ cd lwip-STABLE-2_2_0_RELEASE/contrib/examples/example_app
$
$ cp lwipcfg.h.example lwipcfg.h
创建过程如下图所示:
第二步:修改配置文件 lwipcfg.h
第一处源文件内容:
1 | /* If these 2 are not defined, the corresponding config setting is used */ |
第一处修改后内容:
1 | /* If these 2 are not defined, the corresponding config setting is used */ |
目的是禁止使用DHCP和AUTOIP,服务使用我们自己指定的IP。
第二处源文件内容:
1 |
第二处修改后内容:
1 |
我们在本地局域网192.168.0.0/24网段上找一个类似 192.168.0.62 并且当前还未被占用的局域网真实IP地址比如 192.168.0.200 ,赋值给LwIP协议栈的模拟网卡。
第三处源文件内容:
1 |
第三处修改后内容:
1 |
目的是打开httpd服务,然后我们可以使用curl访问LwIP协议栈上的http服务。如果要测试其他功能,方法类似。
经过创建和修改 lwipcfg.h 配置文件之后,修改前和修改后的对比如下图所示:
大家也可以直接下载查看我配置的 lwipcfg.h 。
编译LwIP
配置文件修改完成之后,进入 lwip-STABLE-2_2_0_RELEASE/contrib/ports/unix/example_app 目录,开始编译LwIP。
1 | $ cd lwip-STABLE-2_2_0_RELEASE/contrib/ports/unix/example_app |
编译过程如下图所示:
编译完成之后,本地生成 example_app 和 makefsdata 两个可执行程序,其中 example_app 就是我们需要的测试程序。
运行LwIP
第一步:使用root权限,使用如下命令启动 example_app 服务端程序:
1 | $ export PRECONFIGURED_TAPIF=tap0 ; sudo -E ./example_app |
启动过程和结果如下图所示:
export PRECONFIGURED_TAPIF=tap0 做什么用?
- 前文讲过,测试程序 contrib\ports\unix\port\netif\tapif.c 的 low_level_init 函数会调用 ifconfig 对 tap0 配置IP。
- 而代码中配置的IP,是网关地址 192.168.0.1!可192.168.0.1是局域网已有的主机,而且是我们的默认路由地址。试想,如果把它给了tap0,必定在局域网上产生冲突,原本正常发给网关的数据,有可能会随机发给tap0,会引起局域网内的其他主机上网异常。
- 其实在给 tap0 配置IP之前代码中有个判断逻辑,它先读取环境变量 PRECONFIGURED_TAPIF ,如果有这个环境变量说明网卡已经配置过,程序会忽略再次使用 ifconfig 配置网卡。
- 所以我们启动程序时,先设置 PRECONFIGURED_TAPIF 这个环境变量,并且使用 sudo -E 来启动 example_app,这样 example_app 才能正确读取到这个环境变量,才能不去配置IP,从而避免上述问题。
第二步:在其他主机,比如Windows主机上打开浏览器,然后在浏览器中访问 http://192.168.0.200 然后会看到如下图:
好了,到此我们总结下,基于第二种方法,改进使用 Linux Bridge 解决了其他主机不能访问测试服务的问题。但很明显该方法的弊端就是使用繁琐,在运行 example_app 之前还需要设置网卡,所以我还是喜欢使用第二种方法。