Created
March 21, 2026 10:12
-
-
Save scomper/5cc18b92fef2344e842b4db0296a521d to your computer and use it in GitHub Desktop.
macOS 网络诊断工具 v2.3 - 支持 Zerotier/Surge/OpenVPN/防火墙检测
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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