NetworkManager 运维教程 / 第 8 章:Dispatcher 事件钩子
第 8 章:Dispatcher 事件钩子
8.1 Dispatcher 概述
NetworkManager Dispatcher 是一个事件触发系统,当网络状态发生变化时自动执行用户定义的脚本。这是实现网络自动化任务的强大机制。
工作原理
网络事件发生
│
▼
NetworkManager 检测到变化
│
▼
Dispatcher 服务触发
│
▼
按字母顺序执行 /etc/NetworkManager/dispatcher.d/ 下的脚本
│
▼
传递参数:接口名、事件类型、连接名
支持的事件类型
| 事件 | 触发时机 | 说明 |
|---|---|---|
up | 连接激活 | 接口获得 IP 并可用 |
down | 连接断开 | 接口失去连接 |
pre-up | 连接激活前 | IP 配置前触发 |
pre-down | 连接断开前 | 断开前触发 |
dhcp4-change | DHCPv4 租约变化 | 获取新的 IPv4 配置 |
dhcp6-change | DHCPv6 租约变化 | 获取新的 IPv6 配置 |
vpn-pre-up | VPN 连接前 | VPN 隧道建立前 |
vpn-up | VPN 连接后 | VPN 隧道已建立 |
vpn-pre-down | VPN 断开前 | VPN 隧道断开前 |
vpn-down | VPN 断开后 | VPN 隧道已断开 |
hostname | 主机名变化 | 系统主机名变更 |
connectivity-change | 连通性变化 | 网络连通状态改变 |
8.2 创建 Dispatcher 脚本
基本脚本结构
#!/bin/bash
# /etc/NetworkManager/dispatcher.d/99-my-script
# NM Dispatcher 脚本模板
INTERFACE=$1 # 网络接口名(如 eth0, wlan0)
EVENT=$2 # 事件类型(如 up, down)
# 接口状态信息(仅 up/down/dhcp*-change 时可用)
CONNECTION_ID=$(echo "$CONNECTION_ID") # 连接名称
CONNECTION_UUID=$(echo "$CONNECTION_UUID") # 连接 UUID
IP4_ADDRESS=$(echo "$IP4_ADDRESS_0") # IPv4 地址
IP4_GATEWAY=$(echo "$IP4_GATEWAY") # IPv4 网关
case "$EVENT" in
up)
echo "$(date): Interface $INTERFACE connected" >> /var/log/nm-dispatcher.log
;;
down)
echo "$(date): Interface $INTERFACE disconnected" >> /var/log/nm-dispatcher.log
;;
dhcp4-change)
echo "$(date): DHCP lease changed on $INTERFACE" >> /var/log/nm-dispatcher.log
;;
esac
exit 0
脚本规范
# 权限要求
sudo chmod 755 /etc/NetworkManager/dispatcher.d/99-my-script
sudo chown root:root /etc/NetworkManager/dispatcher.d/99-my-script
# 文件命名
# 必须是可执行文件
# 不能以 .bak, .rpmnew, .rpmsave, .swp 结尾
# 按字母顺序执行,建议用数字前缀控制顺序
# 00-49: NM 内部保留
# 50-99: 用户自定义
# 两个执行目录
/etc/NetworkManager/dispatcher.d/pre-up.d/ # 连接前执行
/etc/NetworkManager/dispatcher.d/no-wait.d/ # 不等待完成(异步)
注意:脚本必须由 root 拥有且可执行,否则 NM 会忽略它。
8.3 常用 Dispatcher 脚本示例
脚本 1:网络连接日志
#!/bin/bash
# /etc/NetworkManager/dispatcher.d/50-connection-logger
INTERFACE=$1
EVENT=$2
LOG_FILE="/var/log/nm-connections.log"
if [ -z "$INTERFACE" ] || [ -z "$EVENT" ]; then
exit 0
fi
echo "$(date '+%Y-%m-%d %H:%M:%S') | $EVENT | $INTERFACE | ${CONNECTION_ID:-N/A} | ${IP4_ADDRESS_0:-N/A}" >> "$LOG_FILE"
exit 0
脚本 2:连接到公司网络时自动启动 VPN
#!/bin/bash
# /etc/NetworkManager/dispatcher.d/60-auto-vpn
INTERFACE=$1
EVENT=$2
# 只在特定网络连接时触发
if [ "$EVENT" = "up" ] && [ "$CONNECTION_ID" = "Office-WiFi" ]; then
logger -t nm-dispatcher "Office WiFi detected, starting VPN..."
sleep 3 # 等待网络稳定
nmcli connection up "Corp-VPN" &
fi
exit 0
脚本 3:网络变化时更新防火墙规则
#!/bin/bash
# /etc/NetworkManager/dispatcher.d/55-firewall-update
INTERFACE=$1
EVENT=$2
case "$EVENT" in
up)
# 只对有线网络应用防火墙规则
if [ "$INTERFACE" = "eth0" ]; then
/usr/local/bin/update-firewall.sh "$IP4_ADDRESS_0"
logger -t nm-dispatcher "Firewall updated for $INTERFACE ($IP4_ADDRESS_0)"
fi
;;
down)
if [ "$INTERFACE" = "eth0" ]; then
/usr/local/bin/cleanup-firewall.sh
fi
;;
esac
exit 0
脚本 4:WiFi 切换时调整 DNS
#!/bin/bash
# /etc/NetworkManager/dispatcher.d/52-wifi-dns
INTERFACE=$1
EVENT=$2
if [ "$INTERFACE" != "wlan0" ]; then
exit 0
fi
case "$EVENT" in
up)
if [ "$CONNECTION_ID" = "HomeWiFi" ]; then
# 家庭网络使用 Pi-hole
nmcli connection modify "$CONNECTION_ID" ipv4.dns "192.168.1.5"
logger -t nm-dispatcher "Switched to Pi-hole DNS"
fi
;;
esac
exit 0
脚本 5:连接变化时发送通知
#!/bin/bash
# /etc/NetworkManager/dispatcher.d/60-notify
INTERFACE=$1
EVENT=$2
if [ "$EVENT" = "up" ]; then
# 如果有桌面环境,发送桌面通知
if [ -n "$DISPLAY" ]; then
sudo -u $(logname) DISPLAY=$DISPLAY \
notify-send "网络已连接" "接口: $INTERFACE\n连接: ${CONNECTION_ID:-unknown}\nIP: ${IP4_ADDRESS_0:-unknown}" \
--icon=network-transmit-receive
fi
# 发送消息到 Slack/钉钉(如果有 webhook)
if [ -f /etc/nm-webhook.conf ]; then
WEBHOOK_URL=$(cat /etc/nm-webhook.conf)
curl -s -X POST "$WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "{\"text\": \"Network up: $INTERFACE ($CONNECTION_ID) ${IP4_ADDRESS_0}\"}" &
fi
fi
exit 0
脚本 6:连接时自动挂载 NFS
#!/bin/bash
# /etc/NetworkManager/dispatcher.d/65-nfs-mount
INTERFACE=$1
EVENT=$2
if [ "$EVENT" = "up" ] && [ "$CONNECTION_ID" = "Office-LAN" ]; then
logger -t nm-dispatcher "Office LAN connected, mounting NFS shares..."
sleep 2
mount -a -t nfs4
mount -a -t cifs
elif [ "$EVENT" = "down" ] && [ "$CONNECTION_ID" = "Office-LAN" ]; then
logger -t nm-dispatcher "Office LAN disconnected, unmounting NFS shares..."
umount -a -t nfs4 -f -l
umount -a -t cifs -f -l
fi
exit 0
脚本 7:DHCP 租约变化时更新 dnsmasq
#!/bin/bash
# /etc/NetworkManager/dispatcher.d/53-dnsmasq-update
INTERFACE=$1
EVENT=$2
if [ "$EVENT" = "dhcp4-change" ] && [ "$INTERFACE" = "eth0" ]; then
# 更新 dnsmasq 的上游 DNS
NEW_DNS=$(nmcli -t -f IP4.DNS device show "$INTERFACE" | head -1 | cut -d: -f2)
if [ -n "$NEW_DNS" ]; then
echo "server=$NEW_DNS" > /etc/dnsmasq.d/upstream.conf
systemctl reload dnsmasq
logger -t nm-dispatcher "Updated upstream DNS to $NEW_DNS"
fi
fi
exit 0
8.4 Dispatcher 可用环境变量
Dispatcher 脚本可以使用以下环境变量(并非所有事件都有所有变量):
| 环境变量 | 说明 |
|---|---|
$1 (INTERFACE) | 网络接口名 |
$2 (EVENT) | 事件类型 |
$CONNECTION_UUID | 连接 UUID |
$CONNECTION_ID | 连接名称 |
$CONNECTION_FILENAME | 连接文件路径 |
$CONNECTION_EXTERNAL | 是否外部连接 |
$DEVICE_IFACE | 设备接口名 |
$DEVICE_IP_IFACE | IP 接口名 |
$IP4_ADDRESS_0 | 第一个 IPv4 地址(CIDR) |
$IP4_GATEWAY | IPv4 网关 |
$IP4_NAMESERVERS | IPv4 DNS 服务器(空格分隔) |
$IP4_DOMAINS | IPv4 搜索域 |
$IP6_ADDRESS_0 | 第一个 IPv6 地址 |
$IP6_GATEWAY | IPv6 网关 |
$IP6_NAMESERVERS | IPv6 DNS 服务器 |
# 查看所有环境变量(调试用)
#!/bin/bash
# /etc/NetworkManager/dispatcher.d/99-debug-env
env | sort > /tmp/nm-dispatcher-env-$(date +%s).txt
exit 0
8.5 安全最佳实践
# 1. 脚本必须由 root 拥有
sudo chown root:root /etc/NetworkManager/dispatcher.d/*
# 2. 脚本必须可执行
sudo chmod 755 /etc/NetworkManager/dispatcher.d/*
# 3. 验证输入参数
if [ -z "$INTERFACE" ] || [ -z "$EVENT" ]; then
exit 0
fi
# 4. 使用完整路径
/usr/bin/curl ...
/usr/sbin/iptables ...
# 5. 处理错误
if ! nmcli connection up "VPN" 2>/dev/null; then
logger -t nm-dispatcher "Failed to start VPN"
fi
# 6. 避免长时间运行的操作
# 使用后台执行或 no-wait.d 目录
long_running_task &
# 7. 脚本测试
# 手动模拟调用
INTERFACE=eth0 EVENT=up CONNECTION_ID="Wired" \
/etc/NetworkManager/dispatcher.d/50-my-script eth0 up
8.6 排查 Dispatcher 问题
# 查看 dispatcher 日志
journalctl -u NetworkManager-dispatcher
# 查看脚本执行错误
journalctl -u NetworkManager-dispatcher | grep -i "error\|fail"
# 手动测试脚本
INTERFACE=eth0 EVENT=up \
/etc/NetworkManager/dispatcher.d/50-logger eth0 up
# 检查脚本权限
ls -la /etc/NetworkManager/dispatcher.d/
# 检查 dispatcher 服务状态
systemctl status NetworkManager-dispatcher
# 启用 dispatcher 服务
sudo systemctl enable NetworkManager-dispatcher
sudo systemctl start NetworkManager-dispatcher
# 常见问题:脚本不执行
# 1. 文件不是可执行文件
# 2. 文件不是 root 所有
# 3. 文件以 .bak/.swp 结尾
# 4. 脚本语法错误
# 5. dispatcher 服务未运行
8.7 本章小结
| 要点 | 说明 |
|---|---|
| 目录 | /etc/NetworkManager/dispatcher.d/ |
| 执行顺序 | 按文件名字母顺序 |
| 权限要求 | root 拥有,755 权限 |
| 主要事件 | up, down, pre-up, pre-down, dhcp4-change, vpn-up |
| 环境变量 | $INTERFACE, $EVENT, $CONNECTION_ID, $IP4_ADDRESS_0 等 |
| 异步执行 | 放在 no-wait.d/ 子目录 |
| 日志 | journalctl -u NetworkManager-dispatcher |