Skip to content

Instantly share code, notes, and snippets.

@scomper
Created March 21, 2026 10:12
Show Gist options
  • Select an option

  • Save scomper/5cc18b92fef2344e842b4db0296a521d to your computer and use it in GitHub Desktop.

Select an option

Save scomper/5cc18b92fef2344e842b4db0296a521d to your computer and use it in GitHub Desktop.
macOS 网络诊断工具 v2.3 - 支持 Zerotier/Surge/OpenVPN/防火墙检测
#!/bin/bash
#
# 网络诊断工具 v2.3
# 用途: 综合诊断 Zerotier、Surge、OpenVPN 及系统网络状态
# 特点: 充分利用各软件的 CLI 工具,不只依赖网络命令
# 作者: Walter
# 时间: 2026-03-21
#
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
MAGENTA='\033[0;35m'
NC='\033[0m'
# 输出函数
print_header() {
echo ""
echo -e "${CYAN}════════════════════════════════════════════════════════════════${NC}"
echo -e "${CYAN} $1${NC}"
echo -e "${CYAN}════════════════════════════════════════════════════════════════${NC}"
}
print_section() {
echo ""
echo -e "${MAGENTA}▸ $1${NC}"
}
print_ok() {
echo -e "${GREEN}✅ $1${NC}"
}
print_warn() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_error() {
echo -e "${RED}❌ $1${NC}"
}
print_info() {
echo -e "${BLUE}ℹ️ $1${NC}"
}
# 检查命令是否存在
check_command() {
command -v "$1" &> /dev/null
}
# 检查端口是否开放(使用 nc)
check_port() {
local host="$1"
local port="$2"
nc -z -G 1 "$host" "$port" 2>/dev/null || nc -z -w 1 "$host" "$port" 2>/dev/null
}
# 运行诊断
run_diagnosis() {
# ═══════════════════════════════════════════════════════════════
print_header "🌐 系统网络概览"
# ═══════════════════════════════════════════════════════════════
echo ""
echo "📅 诊断时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "🖥️ 主机名: $(hostname -s)"
echo "👤 当前用户: $(whoami)"
# ═══════════════════════════════════════════════════════════════
print_section "📡 网络接口详细信息"
# ═══════════════════════════════════════════════════════════════
echo ""
print_info "物理网络接口"
# 使用 ifconfig 获取所有接口信息
if check_command "ifconfig"; then
# 检测 Wi-Fi 接口(通过 airport 或接口名称)
WIFI_IFACE=""
# 方法1: 通过 airport 工具检测 Wi-Fi 接口
if [ -f "/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport" ]; then
WIFI_IFACE=$(/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I 2>/dev/null | grep "^ *BSSID" | awk '{print $1}')
# airport 输出的是当前连接的 Wi-Fi 信息,如果没有连接则没有 BSSID
fi
# 方法2: 通过接口名称和状态检测(Wi-Fi 通常是 en0 或 en1)
for iface in en0 en1 en2; do
# 检查是否有 "supported media" 包含 "IEEE802.11"(Wi-Fi 特征)
if ifconfig $iface 2>/dev/null | grep -q "IEEE802.11"; then
WIFI_IFACE="$iface"
break
fi
done
# 方法3: 通过 interface type 检测
if [ -z "$WIFI_IFACE" ]; then
# 尝试 networksetup 获取 Wi-Fi 接口名称
if check_command "networksetup"; then
WIFI_IFACE=$(networksetup -listallhardwareports 2>/dev/null | grep -A 1 "Wi-Fi" | grep "Device:" | awk '{print $2}')
fi
fi
# 显示 Wi-Fi 信息
if [ -n "$WIFI_IFACE" ]; then
WIFI_IP=$(ifconfig $WIFI_IFACE 2>/dev/null | grep "inet " | awk '{print $2}')
if [ -n "$WIFI_IP" ]; then
echo ""
echo "📶 Wi-Fi ($WIFI_IFACE):"
WIFI_MASK=$(ifconfig $WIFI_IFACE 2>/dev/null | grep "inet " | awk '{print $4}')
WIFI_MAC=$(ifconfig $WIFI_IFACE 2>/dev/null | grep "ether" | awk '{print $2}')
WIFI_STATUS=$(ifconfig $WIFI_IFACE 2>/dev/null | grep "status" | awk '{print $2}')
[ -n "$WIFI_IP" ] && echo " IP 地址: $WIFI_IP"
[ -n "$WIFI_MASK" ] && echo " 子网掩码: $WIFI_MASK"
[ -n "$WIFI_MAC" ] && echo " MAC 地址: $WIFI_MAC"
[ -n "$WIFI_STATUS" ] && echo " 状态: $WIFI_STATUS"
else
echo ""
echo "📶 Wi-Fi ($WIFI_IFACE): 未连接"
fi
fi
# 检测并显示所有以太网(有线)接口
echo ""
print_info "有线网络接口"
for iface in en0 en1 en2 en3 en4 en5 en6 en7 en8 en9; do
# 跳过已识别的 Wi-Fi 接口
[ "$iface" = "$WIFI_IFACE" ] && continue
ETH_IP=$(ifconfig $iface 2>/dev/null | grep "inet " | awk '{print $2}')
if [ -n "$ETH_IP" ]; then
echo ""
echo "🔌 有线网络 ($iface):"
ETH_MASK=$(ifconfig $iface 2>/dev/null | grep "inet " | awk '{print $4}')
ETH_MAC=$(ifconfig $iface 2>/dev/null | grep "ether" | awk '{print $2}')
ETH_STATUS=$(ifconfig $iface 2>/dev/null | grep "status" | awk '{print $2}')
echo " IP 地址: $ETH_IP"
[ -n "$ETH_MASK" ] && echo " 子网掩码: $ETH_MASK"
[ -n "$ETH_MAC" ] && echo " MAC 地址: $ETH_MAC"
[ -n "$ETH_STATUS" ] && echo " 状态: $ETH_STATUS"
fi
done
# 虚拟接口 (VPN/Zerotier)
echo ""
print_info "虚拟网络接口"
for iface in utun0 utun1 utun2 utun3 utun4 tun0 tun1 feth2110 zt0; do
VT_IP=$(ifconfig $iface 2>/dev/null | grep "inet " | awk '{print $2}')
if [ -n "$VT_IP" ]; then
echo " $iface: $VT_IP"
fi
done
else
print_warn "ifconfig 不可用"
fi
# ═══════════════════════════════════════════════════════════════
print_section "🛣️ 路由表信息"
# ═══════════════════════════════════════════════════════════════
echo ""
print_info "默认路由"
if check_command "netstat"; then
netstat -rn 2>/dev/null | grep "default" | head -3 | while read line; do
echo " $line"
done
elif check_command "route"; then
route -n get default 2>/dev/null | grep -E "gateway|interface" | head -3 | while read line; do
echo " $line"
done
else
print_warn "无法获取路由信息"
fi
# ═══════════════════════════════════════════════════════════════
print_section "🌍 公网 IP 信息"
# ═══════════════════════════════════════════════════════════════
# 通过代理的 IP
echo ""
print_info "通过 Surge 代理的出口 IP"
PROXY_IP=$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null || echo "")
if [ -n "$PROXY_IP" ]; then
echo " IP: $PROXY_IP"
IP_INFO=$(curl -s --max-time 3 "https://ipapi.co/$PROXY_IP/json/" 2>/dev/null || echo "")
if [ -n "$IP_INFO" ]; then
CITY=$(echo "$IP_INFO" | grep -o '"city":"[^"]*"' | cut -d'"' -f4)
COUNTRY=$(echo "$IP_INFO" | grep -o '"country_name":"[^"]*"' | cut -d'"' -f4)
ISP=$(echo "$IP_INFO" | grep -o '"org":"[^"]*"' | cut -d'"' -f4)
[ -n "$CITY" ] && echo " 位置: $CITY, $COUNTRY"
[ -n "$ISP" ] && echo " ISP: $ISP"
fi
fi
# 直连 IP(绕过代理)
echo ""
print_info "直连出口 IP(绕过代理)"
DIRECT_IP=$(curl -s --max-time 5 --noproxy "*" https://icanhazip.com 2>/dev/null || \
curl -s --max-time 5 --noproxy "*" https://api.ip.sb/ip 2>/dev/null || echo "")
if [ -n "$DIRECT_IP" ]; then
echo " IP: $DIRECT_IP"
if echo "$DIRECT_IP" | grep -qE "^223\.70\.|^123\.|^183\.|^221\.|^125\.|^116\.|^111\."; then
echo " 类型: 中国电信/联通 (国内)"
elif echo "$DIRECT_IP" | grep -qE "^100\.64\."; then
echo " 类型: Zerotier 虚拟网络"
fi
else
print_warn "无法获取直连 IP"
fi
# ═══════════════════════════════════════════════════════════════
print_header "🔵 Zerotier 状态详情"
# ═══════════════════════════════════════════════════════════════
echo ""
ZEROTIER_RUNNING=false
if pgrep -f "zerotier" &> /dev/null; then
ZEROTIER_RUNNING=true
fi
if check_command "zerotier-cli" && [ "$ZEROTIER_RUNNING" = true ]; then
print_ok "Zerotier 服务运行中"
print_info "进程信息"
ps aux | grep -E "zerotier" | grep -v grep | awk '{printf " PID: %s, CPU: %s%%, MEM: %s%%\n", $2, $3, $4}' | head -3
print_info "节点状态"
ZT_STATUS=$(zerotier-cli status 2>/dev/null)
echo " $ZT_STATUS"
print_info "已加入的网络"
ZT_NETWORKS=$(zerotier-cli listnetworks 2>/dev/null)
echo "$ZT_NETWORKS" | grep -v "^200" | while read line; do
[ -z "$line" ] && continue
NWID=$(echo "$line" | awk '{print $1}')
NAME=$(echo "$line" | awk '{print $2}')
STATUS=$(echo "$line" | awk '{print $6}')
ZT_IP=$(echo "$line" | grep -oE '100\.64\.[0-9]+\.[0-9]+/[0-9]+' | head -1)
if [ -n "$NWID" ]; then
echo " • 网络: $NWID"
echo " 名称: $NAME"
echo " 状态: $STATUS"
[ -n "$ZT_IP" ] && echo " 分配 IP: $ZT_IP"
fi
done
print_info "连接的 Peers"
PEER_COUNT=$(zerotier-cli listpeers 2>/dev/null | wc -l | tr -d ' ')
echo " 共 $((PEER_COUNT - 1)) 个 peers"
else
if [ "$ZEROTIER_RUNNING" = true ]; then
print_warn "Zerotier 进程运行中但 CLI 不可用"
else
print_error "Zerotier 未运行"
fi
fi
# ═══════════════════════════════════════════════════════════════
print_header "🟠 Surge 代理状态详情"
# ═══════════════════════════════════════════════════════════════
echo ""
SURGE_RUNNING=false
SURGE_PORTS=""
if [ -d "/Applications/Surge.app" ]; then
print_ok "Surge 已安装"
if pgrep -f "Surge.app" &> /dev/null || pgrep -f "surge-mac" &> /dev/null; then
print_ok "Surge 运行中"
SURGE_RUNNING=true
else
print_error "Surge 未运行"
fi
if [ "$SURGE_RUNNING" = true ]; then
print_info "进程信息"
ps aux | grep -E "Surge" | grep -v grep | awk '{printf " PID: %s, CPU: %s%%, MEM: %s%%\n", $2, $3, $4}' | head -3
print_info "代理端口状态"
for port in 6152 6153 6170; do
if check_port 127.0.0.1 $port; then
case $port in
6152) echo " ✅ 6152/tcp - HTTP 代理" && SURGE_PORTS="$SURGE_PORTS 6152";;
6153) echo " ✅ 6153/tcp - SOCKS5 代理" && SURGE_PORTS="$SURGE_PORTS 6153";;
6170) echo " ✅ 6170/tcp - MitM 代理" && SURGE_PORTS="$SURGE_PORTS 6170";;
esac
else
echo " ❌ $port - 未监听"
fi
done
if check_command "surge-cli"; then
print_info "Surge CLI 信息"
surge-cli mode 2>/dev/null | head -1 || echo " 无法获取模式"
surge-cli policies 2>/dev/null | head -5 || echo " 无法获取策略"
else
print_warn "Surge CLI 未安装"
fi
print_info "代理连通性测试"
if echo "$SURGE_PORTS" | grep -q "6152"; then
if curl -s --max-time 5 --proxy http://127.0.0.1:6152 https://www.google.com/generate_204 &> /dev/null; then
print_ok "HTTP 代理工作正常"
else
print_error "HTTP 代理连接失败"
fi
fi
fi
else
print_warn "Surge 未安装"
fi
# ═══════════════════════════════════════════════════════════════
print_header "🟢 OpenVPN 状态详情"
# ═══════════════════════════════════════════════════════════════
echo ""
OPENVPN_RUNNING=false
if pgrep -f "openvpn" &> /dev/null; then
OPENVPN_RUNNING=true
print_ok "OpenVPN 运行中"
print_info "进程详情"
ps aux | grep -E "openvpn" | grep -v grep | head -2
print_info "VPN 接口"
for iface in utun0 utun1 utun2 utun3 tun0 tun1; do
IP=$(ifconfig $iface 2>/dev/null | grep "inet " | awk '{print $2}')
if [ -n "$IP" ]; then
echo " • $iface: $IP"
fi
done
else
print_warn "OpenVPN 未运行"
fi
# ═══════════════════════════════════════════════════════════════
print_header "🔍 网络连通性测试"
# ═══════════════════════════════════════════════════════════════
echo ""
print_info "DNS 解析测试"
DNS_RESULT=$(dig +short google.com 2>/dev/null | head -1)
if [ -n "$DNS_RESULT" ]; then
print_ok "DNS 正常 (google.com → $DNS_RESULT)"
else
print_error "DNS 解析失败"
fi
print_info "HTTP 连通性"
if curl -s --max-time 5 -o /dev/null -w "%{http_code}" https://www.baidu.com | grep -q "200\|301\|302"; then
print_ok "百度连接正常 (国内直连)"
fi
if [ "$SURGE_RUNNING" = true ] && echo "$SURGE_PORTS" | grep -q "6152"; then
if curl -s --max-time 5 --proxy http://127.0.0.1:6152 -o /dev/null -w "%{http_code}" https://www.google.com 2>/dev/null | grep -q "200\|301\|302"; then
print_ok "Google 连接正常 (通过 Surge)"
fi
fi
# ═══════════════════════════════════════════════════════════════
print_header "🔥 防火墙状态"
# ═══════════════════════════════════════════════════════════════
echo ""
if [ -f "/usr/libexec/ApplicationFirewall/socketfilterfw" ]; then
FW_STATE=$(/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate 2>/dev/null)
echo " 应用防火墙: $FW_STATE"
STEALTH=$(/usr/libexec/ApplicationFirewall/socketfilterfw --getstealthmode 2>/dev/null)
echo " Stealth 模式: $STEALTH"
print_info "已允许的应用 (前10个)"
/usr/libexec/ApplicationFirewall/socketfilterfw --listapps 2>/dev/null | head -23 || echo " 无法获取列表"
else
print_warn "无法获取防火墙状态"
fi
# ═══════════════════════════════════════════════════════════════
print_header "📋 诊断摘要"
# ═══════════════════════════════════════════════════════════════
echo ""
ISSUES=()
[ "$ZEROTIER_RUNNING" != true ] && ISSUES+=("⚠️ Zerotier 未运行")
[ "$SURGE_RUNNING" != true ] && ISSUES+=("⚠️ Surge 未运行")
[ "$OPENVPN_RUNNING" != true ] && ISSUES+=("ℹ️ OpenVPN 未运行")
if [ ${#ISSUES[@]} -eq 0 ]; then
print_ok "所有网络组件运行正常"
else
print_warn "发现 ${#ISSUES[@]} 个问题:"
for issue in "${ISSUES[@]}"; do
echo " $issue"
done
fi
echo ""
echo -e "${CYAN}════════════════════════════════════════════════════════════════${NC}"
echo "诊断完成: $(date '+%H:%M:%S')"
echo -e "${CYAN}════════════════════════════════════════════════════════════════${NC}"
}
# 主程序
if [ "$1" = "--save" ]; then
REPORT_FILE="/tmp/openclaw/network-diagnostic-$(date +%Y%m%d-%H%M%S).log"
run_diagnosis > "$REPORT_FILE" 2>&1
cat "$REPORT_FILE"
echo ""
echo -e "${BLUE}ℹ️ 诊断报告已保存至: $REPORT_FILE${NC}"
else
run_diagnosis
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment