[Raspberry PI CM4] 桥接两个物理网卡实现PN故障模拟
本文介绍如何使用树莓派CM4 + 双网口扩展板 实现PN 故障模拟功能
创建桥接
本章节介绍如何使用nmcli 工具设置桥接,[参考资料](https://www.raspberrypi.com/documentation/computers/configuration.html#setting-up-a-headless-raspberry-pi "参考资料")
- 创建一个桥接
sudo nmcli connection add type bridge con-name 'Bridge' ifname bridge0
- 将网卡添加到桥接配置中
将eth0 和 eth1 网卡分别添加到桥接
sudo nmcli connection add type ethernet slave-type bridge con-name 'Ethernet0' ifname eth0 master bridge0
sudo nmcli connection add type ethernet slave-type bridge con-name 'Ethernet1' ifname eth1 master bridge0
- 设置桥接的IP地址
sudo nmcli connection modify "Bridge" ipv4.method manual ipv4.addresses 192.168.10.50/24
- 启动生效桥接
sudo nmcli connection up Bridge
🍭 配置好后,这些配置重启不会丢失,无需再次配置
安装管理和配置桥接的命令行工具brctl
sudo apt install bridge-utils
查看当前故障模拟规则
sudo tc -s qdisc show dev eth1
清除故障模拟规则
设置新规则之前,需要清除之前设置的规则!
sudo tc qdisc del dev eth1 root
桥接模拟延时抖动
执行以下命令,可模拟eth1网卡延迟+抖动
- PN 周期是4ms的情况下,添加4ms延时,将导致Jitter增大或者单次丢包
sudo tc qdisc add dev eth1 root netem delay 4ms 1ms
- PN 周期是4ms的情况下,添加20ms延时,将导致连续丢包和连接超时断开
sudo tc qdisc add dev eth1 root netem delay 20ms 1ms
- PN 周期是4ms的情况下,添加200ms延时,将导致连接超时断开
sudo tc qdisc add dev eth1 root netem delay 200ms 20ms
桥接模拟丢包
执行以下命令,可模拟eth1网卡丢包(百分比)
sudo tc qdisc add dev eth1 root netem loss 30%
桥接模拟延时+丢包
执行以下命令,可模拟eth1网卡丢包50%
sudo tc qdisc add dev eth1 root netem delay 20ms 5ms loss 30%
在树莓派搭建网页测试环境触发故障模拟
安装flask (轻量级Web框架)
# 创建虚拟环境
python3 -m venv ~/webctrl
# 激活环境
source ~/webctrl/bin/activate
#在虚拟环境中安装 Flask
pip3 install flask
创建网页内容
在用户根目录 /home/wef/ 下创建 web_control.py 文件,并编辑
sudo nano web_control.py
输入以下内容
from flask import Flask, render_template_string, request
import subprocess
app = Flask(__name__)
# HTML页面(内嵌模板)
HTML = '''
<!DOCTYPE html>
<html>
<head>
<title>PN 故障模拟器</title>
</head>
<body>
<h1>树莓派控制面板</h1>
<button onclick="runCommand('start2ms')">抖动</button>
<button onclick="runCommand('start7ms')">丢包</button>
<button onclick="runCommand('start15ms')">掉站</button>
<button onclick="runCommand('start100ms')">频繁掉站</button>
<button onclick="runCommand('stop')">停止</button>
<p id="result"></p>
<script>
function runCommand(cmd) {
fetch('/run?cmd=' + cmd)
.then(response => response.text())
.then(data => {
document.getElementById("result").innerText = data;
});
}
</script>
</body>
</html>
'''
@app.route('/')
def home():
return render_template_string(HTML)
@app.route('/run')
def run_command():
cmd = request.args.get('cmd')
try:
if cmd == 'start2ms':
output = subprocess.check_output("sudo tc qdisc add dev eth1 root netem delay 3ms 1ms", shell=True, text=True)
elif cmd == 'start7ms':
output = subprocess.check_output("sudo tc qdisc add dev eth1 root netem delay 7ms", shell=True, text=True)
elif cmd == 'start15ms':
output = subprocess.check_output("sudo tc qdisc add dev eth1 root netem delay 15ms", shell=True, text=True)
elif cmd == 'start100ms':
output = subprocess.check_output("sudo tc qdisc add dev eth1 root netem delay 100ms", shell=True, text=True)
elif cmd == 'stop':
output = subprocess.check_output("sudo tc qdisc del dev eth1 root", shell=True, text=True)
else:
output = "unknow cmd"
return output
except Exception as e:
return f"error: {str(e)}"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000) # 允许局域网访问
ctrl+x 保存退出
增加文件的执行权限
sudo chmod 777 web_control.py
flask 开机自启动,并执行web_control.py 内容
创建systemd服务:
sudo nano /etc/systemd/system/web_control.service
输入以下内容
[Unit]
Description=Web Control Service
After=network.target
[Service]
ExecStart=/home/wef/webctrl/bin/python3 /home/wef/web_control.py
Restart=always
[Install]
WantedBy=multi-user.target
ctrl+x 保存退出
启用服务
sudo systemctl enable webcontrol.service
sudo systemctl start webcontrol.service
输入树莓派的IP+端口5000,打开如下界面

可以点击相应的按钮,执行不同故障模拟命令