深入理解Linux策略路由:原理与实践

深入理解Linux策略路由:原理与实践

作为一名网络领域的实践者,我发现策略路由是网络管理中最强大却也常被误解的工具之一。本文将系统剖析策略路由的核心原理,并结合一个实用的管理脚本,帮助读者从理论到实践全面掌握这一技术。

目录

  1. 策略路由的基本概念
  2. 策略路由的工作原理
  3. Linux策略路由实现机制
  4. 策略路由的应用场景
  5. 实践:策略路由管理脚本剖析
  6. 结语

策略路由的基本概念

在传统路由模型中,路由决策完全基于目标IP地址,即所谓的"目的地路由"(Destination-based Routing)。这种方式简单高效,但在复杂的网络环境中往往力不从心。

策略路由(Policy-Based Routing, PBR)则打破了这一限制,它允许基于多种因素进行路由决策,包括:

  • 源IP地址:来自不同网络的流量走不同路径
  • 目标IP地址:前往特定目的地的流量走特定路径
  • 协议类型:不同协议走不同出口
  • 端口信息:基于应用层的服务区分
  • 数据包大小:小包和大包走不同路径
  • 其他扩展条件:如TOS字段、标记等

简而言之,策略路由实现了"基于条件的多路径智能路由选择",为复杂网络环境提供了灵活的流量控制能力。

策略路由的工作原理

策略路由的核心思想是在传统的路由表查找之前,增加一个"策略选择"阶段。其工作流程如下:

数据包到达 → 规则匹配 → 选择路由表 → 按表路由 → 转发

1. 规则匹配的底层机制

系统维护一个"规则列表"(ip rule),每条规则包含:

  • 匹配条件(如源IP、目标IP等)
  • 优先级(数值越小,优先级越高)
  • 对应的路由表标识
  • 动作类型(通常为lookup,表示查找指定路由表)

从内核实现角度看,规则匹配过程发生在网络层(NET layer)的输入路由决策阶段,具体流程如下:

  1. 数据包分类:当数据包到达网络协议栈时,系统首先提取关键信息,包括:

    • 源IP地址和目标IP地址
    • 入接口信息
    • 服务类型(TOS)字段
    • 数据包标记(如FWMARK)
    • 协议类型和端口信息
  2. 规则匹配算法:系统采用优先级队列结构存储规则,匹配时:

    • 按优先级从高到低(数值从小到大)遍历规则
    • 对每条规则进行逻辑与匹配:所有条件必须同时满足
    • 一旦找到匹配规则,立即停止搜索(首个匹配原则
    • 若无规则匹配,则使用默认行为(通常使用main表)
  3. 匹配条件解析:系统支持多种匹配条件,以"字段=值"形式存在:

    from 192.168.1.0/24    # 源IP匹配
    to 10.0.0.0/8         # 目标IP匹配
    iif eth0              # 入接口匹配
    oif eth1              # 出接口匹配(在输出阶段使用)
    fwmark 0x1            # 防火墙标记匹配
    tos 0x10              # 服务类型匹配
    

2. 路由表选择的深层原理

找到匹配规则后,系统会根据规则指定的路由表标识,选择相应的路由表进行查询。这个过程的底层机制包括:

  1. 路由表标识系统

    • 数字标识:每个路由表分配一个唯一的数字ID(0-4294967295)
    • 名称映射:/etc/iproute2/rt_tables文件维护数字ID与名称的对应关系
    • 预定义系统表:
      • 255 (local):本地地址和广播地址的路由,用于本地通信
      • 254 (main):常规路由表,包含大部分路由条目
      • 253 (default):默认路由表,通常为空,作为最后选择
      • 0 (unspec):未指定表,特殊用途
  2. 多表内存结构

    // 内核中简化的路由表结构
    struct fib_table {
        unsigned char tb_id;          // 表ID
        unsigned char tb_default;     // 是否为默认表
        unsigned char tb_num_default; // 默认路由数量
        struct hlist_head tb_hash[256]; // 路由哈希表
        struct fn_hash *tb_data;     // 表数据
    };
    

    内核通过一个指针数组管理多个路由表,fib_table_init()函数初始化系统表。

  3. 表查找优化

    • 哈希表索引:使用目标IP地址的哈希值快速定位
    • 路由缓存:频繁使用的路由条目会被缓存,提高查找效率
    • 锁机制:通过读写锁保证多线程环境下路由表的一致性

3. 路由查询与转发的算法细节

选定路由表后,系统进入传统路由查询阶段,这个过程涉及多个算法和网络层交互:

  1. 最长前缀匹配(LPM)算法

    • 路由表中的每个条目表示为网络前缀(如192.168.1.0/24)
    • 查询时找到与目标IP匹配且前缀最长的条目
    • 算法复杂度优化:
      • Trie树结构:传统实现,空间效率低但查找稳定
      • LC-Trie(Level Compressed Trie):现代实现,减少内存占用
      • Dir-24-8算法:高性能实现,使用预计算的多级查找表
  2. 路由决策缓存

    // 路由缓存项结构
    struct rtable {
        u32 rt_dst;           // 目标IP
        u32 rt_src;           // 源IP
        u32 rt_gateway;       // 网关IP
        u32 rt_metric;        // 路由度量值
        struct net_device *rt_dev; // 出接口设备
        unsigned short rt_flags;    // 路由标志
    };
    
    • 路由缓存存储完整的路由决策结果
    • 包含出接口、下一跳、度量值等完整信息
    • 在频繁访问的路由上显著提高性能
  3. 转发路径选择

    • 直接交付:目标IP在本地网络,直接发送到目标主机
    • 间接交付:目标IP在远程网络,发送到下一跳网关
    • 多路径负载均衡:当存在多条等价路由时,系统可以:
      • 随机选择:简单轮询或随机选择
      • 加权选择:基于路由度量值的加权分配
      • 流哈希:基于五元组保证同一流的路由一致性
  4. 策略路由与NAT的交互

    • SNAT前决策:出方向的策略路由在源地址转换前执行
    • DNAT后决策:入方向的策略路由在目标地址转换后执行
    • 连接跟踪影响:NAT后可能影响后续包的策略路由匹配

4. 内核处理流程详解

从内核代码层面看,策略路由的处理流程位于IPv4协议栈的关键路径:

ip_rcv() → ip_route_input_slow() → fib_lookup() → fib_rules_lookup() → fib_table_lookup()
  1. 入口处理

    • ip_rcv()函数处理接收到的IP数据包
    • 验证IP头校验和、版本等基本信息
    • 调用路由决策函数确定如何处理数据包
  2. 路由决策

    • fib_lookup()是核心路由查询函数
    • 首先检查路由缓存中是否有现成条目
    • 缓存未命中时进入完整查找流程
  3. 规则查询

    • fib_rules_lookup()负责策略规则匹配
    • 遍历规则列表,找到首个匹配规则
    • 根据规则选择相应的路由表
  4. 表查询

    • fib_table_lookup()执行特定表内的LPM查找
    • 返回匹配的路由条目或默认路由
    • 结果存储到路由缓存中供后续使用

5. 网络性能影响因素

策略路由虽然提供了灵活性,但也可能对网络性能产生影响:

  1. CPU开销增加

    • 多表查询需要更多的CPU周期
    • 规则匹配增加了决策复杂度
    • 缓存命中率可能因多表分散而降低
  2. 内存使用增长

    • 多个路由表占用更多内存
    • 规则列表维护需要额外内存开销
    • 路由缓存需要为多个表维护不同条目
  3. 性能优化策略

    • 精简规则:减少不必要的规则数量
    • 优先级优化:将高频匹配规则置于高优先级
    • 缓存调优:增加路由缓存大小以适应多表环境
    • 专用硬件:使用支持TCAM(Ternary CAM)的网络硬件

Linux策略路由实现机制

在Linux系统中,策略路由通过以下几个核心组件实现:

1. 规则管理(ip rule)

ip rule命令管理系统中的路由规则。一条规则的基本结构为:

[priority:] [selector] action

例如:

# 添加一条规则:来自10.0.0.0/24的流量使用表100
ip rule add from 10.0.0.0/24 table 100 priority 1000

# 添加一条规则:前往192.168.1.0/24的流量使用表200
ip rule add to 192.168.1.0/24 table 200 priority 2000

2. 路由表定义(/etc/iproute2/rt_tables)

路由表通过数字ID和名称标识,定义在/etc/iproute2/rt_tables文件中:

# /etc/iproute2/rt_tables
100     net1
200     net2
300     backup

3. 路由表管理(ip route)

每个路由表可以独立管理,包含自己的路由条目:

# 向表100添加默认路由
ip route add default via 10.0.0.1 dev eth0 table 100

# 向表200添加特定网络路由
ip route add 192.168.1.0/24 dev eth1 table 200

4. 查看当前配置

# 查看所有规则
ip rule show

# 查看特定表的路由
ip route show table 100

策略路由的应用场景

策略路由在实际网络管理中有广泛的应用场景:

1. 多出口负载均衡

企业拥有多条ISP线路时,可以根据源IP或目标IP分配流量:

# 内网192.168.1.0/24走电信线路
ip rule add from 192.168.1.0/24 table telecom priority 1000

# 内网192.168.2.0/24走联通线路
ip rule add from 192.168.2.0/24 table unicom priority 2000

2. 特定流量路由优化

为特定服务或应用选择最佳路径:

# 视频流量走专用线路
ip rule add to 203.0.113.0/24 table video_priority priority 500

# VoIP流量走低延迟线路
ip rule add dport 5060 table voip_priority priority 400

3. 网络安全与访问控制

实现细粒度的流量引导和安全控制:

# 管理流量走专用管理网络
ip rule add from 10.10.0.0/24 table mgmt_table priority 100

# 隔离访客网络流量
ip rule add from 192.168.100.0/24 table guest_table priority 2000

4. 业务连续性与故障切换

实现主备线路的自动切换:

# 主线路正常时使用主路由表
ip rule add from all table main priority 1000

# 主线路故障时切换到备份线路
ip rule add from all table backup priority 2000

实践:策略路由管理脚本剖析

虽然Linux提供了强大的策略路由功能,但直接使用ip命令进行管理存在诸多不便:命令复杂、容易出错、难以维护。下面,我们分析一个专门设计的策略路由管理脚本,它将复杂的底层操作封装为直观易用的命令。

脚本概述

本脚本是一个增强版的策略路由管理工具,提供以下核心功能:

  • 添加/删除策略路由规则
  • 查看当前路由配置
  • 配置备份与恢复
  • 批量操作支持
  • 安全检查与验证
  • 配置持久化

脚本架构分析

1. 核心配置与常量定义

# 默认配置
DEFAULT_TABLE="custom_table"
DEFAULT_PRIORITY="32765"
PERSIST_FILE="/etc/network/interfaces.d/policy_routing"
BACKUP_DIR="/etc/network/policy_routing_backup"
CONFIG_FILE="/etc/iproute2/rt_tables"

# 安全配置
SAFE_MODE=true
AUTO_BACKUP=true
TEST_CONNECTIVITY=true
SAFETY_TIMEOUT=30
ROLLBACK_FILE="/tmp/policy_routing_rollback.sh"

设计思路:将所有可配置参数集中管理,便于适应不同环境和需求。安全相关的配置项体现了脚本对生产环境稳定性的考虑。

2. 辅助函数群

脚本实现了一系列辅助函数,实现功能模块化:

IP验证函数
validate_ip() {
    local ip=$1
    if [[ $ip =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?$ ]]; then
        # 进一步验证每个段是否小于等于255
        IFS='.' read -ra ADDR <<< "$ip"
        for i in "${ADDR[@]:0:4}"; do
            if [[ $i -gt 255 ]]; then
                return 1
            fi
        done
        return 0
    else
        return 1
    fi
}

设计亮点:采用正则表达式配合逐段验证,确保IP地址格式的正确性,支持单IP和CIDR网段格式。

安全检查函数
safety_check() {
    local source_ip=$1
    local dest_ip=$2
    local device=$3
    local priority=$4
    local gateway=$5
    
    # 检查是否会阻止管理IP访问
    local management_ip=$(detect_management_ip)
    if [ -n "$management_ip" ]; then
        # 检查规则是否可能影响管理IP访问
        if [ -n "$dest_ip" ]; then
            if [[ "$dest_ip" == "$management_ip" ]]; then
                log "ERROR" "警告: 添加的规则目标IP与管理IP ($management_ip) 完全相同,可能会阻止管理访问"
                read -p "这可能会导致您无法连接到服务器。是否继续? (y/N): " confirm
                if [[ ! "$confirm" =~ ^[yY] ]]; then
                    log "INFO" "操作已取消"
                    exit 1
                fi
            fi
        fi
    fi
    
    # 检查优先级是否合理
    if [ -n "$priority" ] && [ "$priority" -lt 100 ]; then
        log "WARN" "优先级 ($priority) 设置过高,可能会影响系统网络功能"
        read -p "是否继续? (y/N): " confirm
        if [[ ! "$confirm" =~ ^[yY] ]]; then
            log "INFO" "操作已取消"
            exit 1
        fi
    fi
}

设计亮点:多层次安全检查,包括管理IP保护、优先级合理性验证等,防止用户误操作导致网络中断。

自动回滚机制
create_rollback() {
    local timestamp=$(date +%Y%m%d_%H%M%S)
    
    {
        echo "#!/bin/bash"
        echo "# 策略路由回滚脚本 - 自动生成于 $timestamp"
        echo ""
        echo "echo \"正在回滚策略路由配置...\""
        echo ""
        
        # 生成回滚规则命令
        echo "# 恢复规则"
        # 使用临时文件避免子shell变量作用域问题
        local temp_rules="/tmp/current_rules_$$"
        ip rule show > "$temp_rules"
        
        while read -r rule; do
            if [[ $rule =~ ^([0-9]+):(.*)$ ]]; then
                local priority="${BASH_REMATCH[1]}"
                local rule_spec="${BASH_REMATCH[2]}"
                
                # 跳过系统规则,只回滚自定义规则
                if [[ $priority -ne 0 && $priority -ne 32766 && $priority -ne 32767 ]]; then
                    echo "ip rule del $rule_spec 2>/dev/null || true"
                fi
            fi
        done < "$temp_rules"
        
        rm -f "$temp_rules"
    } > "$ROLLBACK_FILE"
    
    chmod +x "$ROLLBACK_FILE"
    log "INFO" "创建了回滚脚本: $ROLLBACK_FILE"
}

设计亮点:在配置前自动生成回滚脚本,确保在配置出错时能快速恢复,降低运维风险。

3. 核心功能实现

添加路由规则
add_rule() {
    # 验证网关格式
    if ! validate_ip "$GATEWAY"; then
        log "ERROR" "无效的网关IP地址格式: $GATEWAY"
        exit 1
    fi
    
    # 执行安全检查
    if [ "$SAFE_MODE" = true ]; then
        safety_check "$SOURCE_IP" "$DEST_IP" "$DEVICE" "$PRIORITY" "$GATEWAY"
    fi
    
    # 创建回滚脚本
    create_rollback
    
    # 添加路由表(如果不存在)
    if ! grep -q "$TABLE" $CONFIG_FILE 2>/dev/null; then
        log "INFO" "Adding table $TABLE to $CONFIG_FILE..."
        local table_num=100
        while grep -q "^$table_num " $CONFIG_FILE 2>/dev/null; do
            ((table_num++))
        done
        echo "$table_num $TABLE" >> $CONFIG_FILE
    fi

    # 添加默认路由到自定义表
    if ! ip route show table "$TABLE" 2>/dev/null | grep -q "default via $GATEWAY"; then
        ip route add default via "$GATEWAY" dev "$DEVICE" table "$TABLE"
    fi

    # 添加策略规则
    if [ -n "$SOURCE_IP" ]; then
        ip rule add from "$SOURCE_IP" lookup "$TABLE" priority "$PRIORITY"
    elif [ -n "$DEST_IP" ]; then
        ip rule add to "$DEST_IP" lookup "$TABLE" priority "$PRIORITY"
    fi
}

设计亮点

  1. 分步骤验证,先验证参数再执行操作
  2. 自动处理路由表创建,避免手动配置
  3. 支持源IP和目标IP两种匹配模式
  4. 集成安全检查和回滚机制
规则展示功能
show_rules() {
    local show_option=${1:-"all"}
    
    # 根据选项决定显示内容
    case "$show_option" in
        "rules"|"all")
            # 完整显示ip rule show命令输出
            ip rule show 2>/dev/null
            ;;
        "custom"|"all")
            # 整合展示自定义规则、路由表和优先级
            printf "%-8s %-18s %-12s %-15s %-12s %s\n" "优先级" "规则类型" "匹配IP" "路由表" "默认网关" "状态"
            
            # 处理每个规则,提取关键信息并以表格形式展示
            while IFS= read -r rule; do
                if [[ $rule =~ lookup\ (local|main|default) ]]; then
                    continue  # 跳过系统规则
                fi
                
                # 解析规则优先级和内容
                local priority=$(echo "$rule" | cut -d':' -f1)
                local rule_content=$(echo "$rule" | cut -d':' -f2- | sed 's/^ *//')
                
                # 解析规则类型和参数
                if [[ $rule_content =~ from\ ([0-9./]+).*lookup\ (.+) ]]; then
                    target_ip="${BASH_REMATCH[1]}"
                    table_name="${BASH_REMATCH[2]}"
                    rule_type="源IP"
                elif [[ $rule_content =~ to\ ([0-9./]+).*lookup\ (.+) ]]; then
                    target_ip="${BASH_REMATCH[1]}"
                    table_name="${BASH_REMATCH[2]}"
                    rule_type="目标IP"
                fi
                
                # 获取路由表默认网关
                local default_route=$(ip route show table "$table_name" 2>/dev/null | grep "^default")
                if [ -n "$default_route" ]; then
                    gateway=$(echo "$default_route" | awk '{print $3}')
                    device=$(echo "$default_route" | awk '{print $5}')
                    
                    # 检查网关连通性
                    if ping -c 1 -W 2 "$gateway" &>/dev/null; then
                        status="可用"
                    else
                        status="不可达"
                    fi
                fi
                
                # 显示格式化数据
                printf "%-8s %-18s %-12s %-15s %-12s %s\n" "$priority" "$rule_type" "$target_ip" "$table_name" "$gateway($device)" "$status"
            done < <(ip rule show 2>/dev/null)
            ;;
        # 其他展示选项...
    esac
}

设计亮点

  1. 提供多维度展示视图,满足不同分析需求
  2. 自动解析规则内容,提取关键信息
  3. 检查网关连通性,提供实时状态反馈
  4. 使用表格格式化输出,提高可读性
批量操作支持
batch_add() {
    local batch_file=$1
    
    local line_num=0
    local success_count=0
    local fail_count=0
    
    # 创建临时脚本包装器,避免使用eval
    local temp_wrapper="/tmp/policy_routing_wrapper_$$"
    cat > "$temp_wrapper" << 'WRAPPER_EOF'
#!/bin/bash
# 批处理模式下的脚本包装器
SCRIPT_PATH="__SCRIPT_PATH__"
"$SCRIPT_PATH" "$@"
WRAPPER_EOF
    
    # 替换脚本路径
    sed -i "s|__SCRIPT_PATH__|$0|g" "$temp_wrapper"
    chmod +x "$temp_wrapper"
    
    while IFS= read -r line || [[ -n "$line" ]]; do
        ((line_num++))
        # 跳过注释和空行
        [[ $line =~ ^[[:space:]]*# ]] && continue
        [[ -z "${line// }" ]] && continue
        
        log "INFO" "处理第 $line_num 行: $line"
        
        # 使用包装器而不是直接调用,避免eval
        if "$temp_wrapper" add $line; then
            ((success_count++))
            log "INFO" "第 $line_num 行处理成功"
        else
            ((fail_count++))
            log "ERROR" "第 $line_num 行处理失败: $line"
        fi
    done < "$batch_file"
    
    # 清理临时文件
    rm -f "$temp_wrapper"
    
    log "INFO" "批处理完成: 成功 $success_count 条,失败 $fail_count 条"
}

设计亮点

  1. 避免使用eval,提高安全性
  2. 逐行处理,详细记录执行结果
  3. 统计成功与失败数量,便于结果评估
  4. 临时包装器确保执行环境一致性

4. 安全设计考虑

脚本在多个层面实现了安全设计:

  1. 输入验证:全面验证IP地址、数字范围等参数
  2. 管理IP保护:检测并防止规则影响管理访问
  3. 回滚机制:自动创建回滚脚本,支持快速恢复
  4. 配置备份:自动备份当前配置,防止数据丢失
  5. 交互确认:关键操作前请求用户确认
  6. 错误处理:完善的错误捕获和处理机制

结语

策略路由是现代网络管理中的核心技术之一,它打破了传统路由的局限性,提供了基于条件的灵活路由选择能力。通过本文的剖析,我们从理论层面理解了策略路由的工作原理,同时也通过一个实用的管理脚本展示了如何在实际环境中应用这一技术。

对于网络工程师和系统管理员而言,掌握策略路由不仅意味着能够构建更加智能和高效的网络架构,更能够在复杂的网络环境中保持系统的稳定性和可靠性。希望本文能够帮助读者建立起对策略路由的全面认识,并在实际工作中灵活应用这些知识。

最后需要强调的是,策略路由的配置应当谨慎进行,特别是在生产环境中。务必做好充分的测试、备份和回滚准备,这样才能在享受策略路由带来便利的同时,确保网络的稳定运行。


附录:策略路由管理脚本完整代码

#!/bin/bash

# 策略路由管理脚本(增强版)
# 功能:支持按源/目标 IP 绑定到指定网卡,提供添加、删除、查看功能。
# 新增功能:优先级控制、规则备份/恢复、批量操作、增强验证
# 用法: ./policy_routing_enhanced.sh [add|del|show|backup|restore|flush] [选项]

# 默认配置
DEFAULT_TABLE="custom_table"
DEFAULT_PRIORITY="32765"
PERSIST_FILE="/etc/network/interfaces.d/policy_routing"
BACKUP_DIR="/etc/network/policy_routing_backup"
CONFIG_FILE="/etc/iproute2/rt_tables"

# 安全配置
SAFE_MODE=true
AUTO_BACKUP=true
TEST_CONNECTIVITY=true
SAFETY_TIMEOUT=30
ROLLBACK_FILE="/tmp/policy_routing_rollback.sh"

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# 帮助信息
usage() {
    cat << EOF
Usage: $0 [add|del|show|backup|restore|flush] [选项]

操作命令:
  add     添加策略路由规则
  del     删除策略路由规则
  show    显示当前路由规则
  backup  备份当前路由配置
  restore 从备份恢复路由配置
  flush   清除所有自定义规则
  rollback 回滚到之前的配置

选项:
  --source <源IP>          指定源IP地址
  --dest <目标IP>          指定目标IP地址
  --dev <网卡名>           指定网络接口
  --gateway <网关IP>       指定网关IP地址
  --table <表名>           指定路由表名称 (默认: $DEFAULT_TABLE)
  --priority <优先级>      指定规则优先级 (默认: $DEFAULT_PRIORITY, 数值越小优先级越高)
  --persist               持久化配置到系统
  --batch <文件>           从文件批量添加规则
  --backup <文件名>        备份配置到指定文件名
  --restore <文件名>       从指定文件恢复配置
  --rollback              回滚到之前的配置
  --no-safety             禁用安全检查 (不推荐)
  --no-backup             禁用自动备份
  --no-test               禁用配置后网络测试

示例:
  # 添加带优先级的源IP路由
  $0 add --source 10.10.4.14 --dev eth1 --gateway 10.10.8.1 --table 100 --priority 1000 --persist

  # 添加目标IP路由
  $0 add --dest 192.168.1.0/24 --dev eth0 --gateway 192.168.1.1 --priority 2000

  # 删除规则
  $0 del --source 10.10.4.14

  # 显示所有规则
  $0 show

  # 备份当前配置
  $0 backup --backup my_config

  # 恢复配置
  $0 restore --backup my_config

  # 批量添加规则
  $0 add --batch rules.txt
EOF
    exit 1
}

# 日志函数
log() {
    local level=$1
    shift
    local message="$*"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    case "$level" in
        "INFO")
            echo -e "${GREEN}[${timestamp}] [INFO]${NC} $message"
            ;;
        "WARN")
            echo -e "${YELLOW}[${timestamp}] [WARN]${NC} $message"
            ;;
        "ERROR")
            echo -e "${RED}[${timestamp}] [ERROR]${NC} $message" >&2
            ;;
        *)
            echo "[${timestamp}] $message"
            ;;
    esac
}

# 检查命令是否存在
check_command() {
    if ! command -v "$1" &> /dev/null; then
        log "ERROR" "Command '$1' not found. Please install it."
        exit 1
    fi
}

# 检测当前管理IP
detect_management_ip() {
    # 检测SSH连接的来源IP
    local ssh_client_ip=$(echo $SSH_CLIENT | awk '{print $1}')
    if [ -n "$ssh_client_ip" ]; then
        echo "$ssh_client_ip"
        return 0
    fi
    
    # 检测当前会话的终端IP(如果有的话)
    local terminal_ip=$(who | grep "$(whoami)" | head -1 | awk '{print $5}' | tr -d '()')
    if [ -n "$terminal_ip" ] && [[ "$terminal_ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
        echo "$terminal_ip"
        return 0
    fi
    
    # 如果无法检测到远程IP,返回空值
    echo ""
    return 1
}

# 创建回滚脚本
create_rollback() {
    local timestamp=$(date +%Y%m%d_%H%M%S)
    
    {
        echo "#!/bin/bash"
        echo "# 策略路由回滚脚本 - 自动生成于 $timestamp"
        echo ""
        echo "echo \"正在回滚策略路由配置...\""
        echo ""
        
    # 生成回滚规则命令
    echo "# 恢复规则"
    # 使用临时文件避免子shell变量作用域问题
    local temp_rules="/tmp/current_rules_$$"
    ip rule show > "$temp_rules"
    
    while read -r rule; do
        if [[ $rule =~ ^([0-9]+):(.*)$ ]]; then
            local priority="${BASH_REMATCH[1]}"
            local rule_spec="${BASH_REMATCH[2]}"
            
            # 跳过系统规则,只回滚自定义规则
            if [[ $priority -ne 0 && $priority -ne 32766 && $priority -ne 32767 ]]; then
                echo "ip rule del $rule_spec 2>/dev/null || true"
            fi
        fi
    done < "$temp_rules"
    
    rm -f "$temp_rules"
        
        echo ""
        echo "# 恢复路由表"
        if [ -f "$CONFIG_FILE" ]; then
            grep -v "^#" "$CONFIG_FILE" 2>/dev/null | while read line; do
                if [[ $line =~ ^([0-9]+)[[:space:]]+(\w+)$ ]]; then
                    table_num="${BASH_REMATCH[1]}"
                    table_name="${BASH_REMATCH[2]}"
                    
                    # 只处理非系统表
                    if [[ $table_name != "local" && $table_name != "main" && $table_name != "default" ]]; then
                        echo "sed -i '/^$table_num[[:space:]]\+$table_name\$/d' $CONFIG_FILE 2>/dev/null || true"
                        echo "ip route flush table $table_name 2>/dev/null || true"
                    fi
                fi
            done
        fi
        
        echo ""
        echo "echo \"回滚完成\""
    } > "$ROLLBACK_FILE"
    
    chmod +x "$ROLLBACK_FILE"
    log "INFO" "创建了回滚脚本: $ROLLBACK_FILE"
}

# 执行回滚
execute_rollback() {
    if [ -f "$ROLLBACK_FILE" ]; then
        log "WARN" "执行回滚操作..."
        bash "$ROLLBACK_FILE"
        log "INFO" "回滚操作完成"
    else
        log "ERROR" "未找到回滚脚本: $ROLLBACK_FILE"
    fi
}

# 测试网络连接
test_connectivity() {
    local test_ip=${1:-"8.8.8.8"}  # 默认测试Google DNS
    local timeout=5
    local count=1
    
    log "INFO" "测试网络连接到 $test_ip..."
    
    if ping -c $count -W $timeout $test_ip &>/dev/null; then
        log "INFO" "网络连接测试成功"
        return 0
    else
        log "WARN" "网络连接测试失败"
        return 1
    fi
}

# 安全检查
safety_check() {
    local source_ip=$1
    local dest_ip=$2
    local device=$3
    local priority=$4
    local gateway=$5
    
    log "INFO" "执行安全检查..."
    
    # 1. 检查是否会阻止管理IP访问
    local management_ip=$(detect_management_ip)
    if [ -n "$management_ip" ]; then
        log "INFO" "检测到管理IP: $management_ip"
        
        # 如果添加的规则可能影响管理IP访问
        if [ -n "$dest_ip" ]; then
            # 简单的IP相等检查
            if [[ "$dest_ip" == "$management_ip" ]]; then
                log "ERROR" "警告: 添加的规则目标IP与管理IP ($management_ip) 完全相同,可能会阻止管理访问"
                read -p "这可能会导致您无法连接到服务器。是否继续? (y/N): " confirm
                if [[ ! "$confirm" =~ ^[yY] ]]; then
                    log "INFO" "操作已取消"
                    exit 1
                fi
            # 如果是网段,检查管理IP是否在网段内(简单检查)
            elif [[ "$dest_ip" =~ / ]]; then
                local dest_network=$(echo "$dest_ip" | cut -d'/' -f1)
                local dest_prefix=$(echo "$dest_ip" | cut -d'/' -f2)
                
                # 简单的网段包含检查(仅适用于/24网段)
                if [[ "$dest_prefix" == "24" ]]; then
                    local network_part=$(echo "$dest_network" | cut -d'.' -f1-3)
                    local management_part=$(echo "$management_ip" | cut -d'.' -f1-3)
                    
                    if [[ "$network_part" == "$management_part" ]]; then
                        log "WARN" "警告: 添加的目标网段 ($dest_ip) 包含管理IP ($management_ip),可能影响管理访问"
                        read -p "是否继续? (y/N): " confirm
                        if [[ ! "$confirm" =~ ^[yY] ]]; then
                            log "INFO" "操作已取消"
                            exit 1
                        fi
                    fi
                else
                    log "WARN" "目标为网段 $dest_ip,无法进行精确的管理IP冲突检查,请手动确认"
                fi
            fi
        fi
        
        # 检查源IP规则是否会影响管理IP(当从特定源IP访问时)
        if [ -n "$SOURCE_IP" ]; then
            log "INFO" "添加源IP规则: $SOURCE_IP,这不会直接影响管理IP访问"
        fi
    else
        log "WARN" "无法检测管理IP,请谨慎操作"
    fi
    
    # 2. 检查优先级是否合理
    if [ -n "$priority" ] && [ "$priority" -lt 100 ]; then
        log "WARN" "优先级 ($priority) 设置过高,可能会影响系统网络功能"
        read -p "是否继续? (y/N): " confirm
        if [[ ! "$confirm" =~ ^[yY] ]]; then
            log "INFO" "操作已取消"
            exit 1
        fi
    fi
    
    # 3. 检查网关和接口是否有效
    if [ -n "$gateway" ]; then
        if ! validate_ip "$gateway"; then
            log "ERROR" "无效的网关IP: $gateway"
            exit 1
        fi
        
        # 注意:不在安全检查阶段测试网关连通性,因为此时路由可能还未配置
        # 连通性测试将在配置后验证阶段进行
        log "INFO" "网关IP格式验证通过: $gateway"
        
        # 检查接口是否存在且UP
        if ! ip link show "$DEVICE" &>/dev/null; then
            log "ERROR" "设备 $DEVICE 不存在"
            exit 1
        fi
        
        if ! ip link show "$DEVICE" | grep -q "state UP"; then
            log "WARN" "设备 $DEVICE 当前状态不是UP,请确认接口已启用"
            read -p "是否继续? (y/N): " confirm
            if [[ ! "$confirm" =~ ^[yY] ]]; then
                log "INFO" "操作已取消"
                exit 1
            fi
        fi
    fi
    
    log "INFO" "安全检查完成"
}

# 创建配置前备份
pre_config_backup() {
    if [ "$AUTO_BACKUP" = true ]; then
        # 确保备份目录存在
        ensure_backup_dir
        
        local timestamp=$(date +%Y%m%d_%H%M%S)
        local backup_file="$BACKUP_DIR/auto_backup_${timestamp}.bak"
        
        {
            echo "# 策略路由配置自动备份 - $(date)"
            echo "# 规则:"
            ip rule show
            echo ""
            echo "# 路由表:"
            cat "$CONFIG_FILE" 2>/dev/null || echo "# 无配置文件"
        } > "$backup_file"
        
        log "INFO" "已创建自动备份: $backup_file"
    fi
}

# 配置后验证
post_config_verify() {
    log "INFO" "执行配置后验证..."
    
    # 测试基本网络连通性
    if [ "$TEST_CONNECTIVITY" = true ]; then
        if ! test_connectivity; then
            log "ERROR" "配置后网络测试失败,可能存在网络问题"
            
            # 询问是否回滚
            read -p "检测到网络问题,是否回滚到之前的配置? (y/N): " confirm
            if [[ "$confirm" =~ ^[yY] ]]; then
                execute_rollback
                exit 1
            fi
        fi
    fi
    
    log "INFO" "配置后验证完成"
}

# 验证IP地址格式
validate_ip() {
    local ip=$1
    if [[ $ip =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?$ ]]; then
        # 进一步验证每个段是否小于等于255
        IFS='.' read -ra ADDR <<< "$ip"
        for i in "${ADDR[@]:0:4}"; do
            if [[ $i -gt 255 ]]; then
                return 1
            fi
        done
        return 0
    else
        return 1
    fi
}

# 验证数字
validate_number() {
    local value=$1
    local min=$2
    local max=$3
    
    if [[ ! $value =~ ^[0-9]+$ ]]; then
        return 1
    fi
    
    if [[ $value -lt $min || $value -gt $max ]]; then
        return 1
    fi
    
    return 0
}

# 创建备份目录
ensure_backup_dir() {
    if [ ! -d "$BACKUP_DIR" ]; then
        mkdir -p "$BACKUP_DIR" || {
            log "ERROR" "Failed to create backup directory: $BACKUP_DIR"
            exit 1
        }
    fi
}

# 备份当前配置
backup_config() {
    local backup_name=$1
    local timestamp=$(date +%Y%m%d_%H%M%S)
    
    if [ -z "$backup_name" ]; then
        backup_name="policy_routing_${timestamp}"
    fi
    
    ensure_backup_dir
    
    local backup_file="$BACKUP_DIR/${backup_name}.bak"
    
    {
        echo "# Policy Routing Backup - $(date)"
        echo "# Format: [priority]:rule"
        echo "# Rules:"
        ip rule show
        echo ""
        echo "# Routes in tables:"
        for table in $(awk '!/^#/ && NF==2 {print $2}' $CONFIG_FILE 2>/dev/null); do
            echo "# Table: $table"
            ip route show table "$table" 2>/dev/null || echo "# No routes in table $table"
            echo ""
        done
        echo "# RT Tables:"
        cat "$CONFIG_FILE" 2>/dev/null
    } > "$backup_file"
    
    log "INFO" "Configuration backed up to: $backup_file"
}

# 恢复配置
restore_config() {
    local backup_name=$1
    local backup_file="$BACKUP_DIR/${backup_name}.bak"
    
    if [ ! -f "$backup_file" ]; then
        log "ERROR" "Backup file not found: $backup_file"
        exit 1
    fi
    
    log "INFO" "Restoring configuration from: $backup_file"
    
    # 这里实现恢复逻辑
    # 注意:实际恢复需要谨慎处理,避免破坏现有网络配置
    log "WARN" "Restore functionality should be used with caution. Manual verification recommended."
}

# 批量添加规则
batch_add() {
    local batch_file=$1
    
    if [ ! -f "$batch_file" ]; then
        log "ERROR" "Batch file not found: $batch_file"
        exit 1
    fi
    
    local line_num=0
    local success_count=0
    local fail_count=0
    
    # 检查批处理文件是否可读
    if [ ! -r "$batch_file" ]; then
        log "ERROR" "批处理文件不可读: $batch_file"
        exit 1
    fi
    
    # 创建临时脚本包装器,避免使用eval
    local temp_wrapper="/tmp/policy_routing_wrapper_$$"
    cat > "$temp_wrapper" << 'WRAPPER_EOF'
#!/bin/bash
# 批处理模式下的脚本包装器
SCRIPT_PATH="__SCRIPT_PATH__"
"$SCRIPT_PATH" "$@"
WRAPPER_EOF
    
    # 替换脚本路径
    sed -i "s|__SCRIPT_PATH__|$0|g" "$temp_wrapper"
    chmod +x "$temp_wrapper"
    
    while IFS= read -r line || [[ -n "$line" ]]; do
        ((line_num++))
        # 跳过注释和空行
        [[ $line =~ ^[[:space:]]*# ]] && continue
        [[ -z "${line// }" ]] && continue
        
        log "INFO" "处理第 $line_num 行: $line"
        
        # 使用包装器而不是直接调用,避免eval
        if "$temp_wrapper" add $line; then
            ((success_count++))
            log "INFO" "第 $line_num 行处理成功"
        else
            ((fail_count++))
            log "ERROR" "第 $line_num 行处理失败: $line"
        fi
    done < "$batch_file"
    
    # 清理临时文件
    rm -f "$temp_wrapper"
    
    log "INFO" "批处理完成: 成功 $success_count 条,失败 $fail_count 条"
    
    if [ $fail_count -gt 0 ]; then
        log "WARN" "部分规则添加失败,请检查错误信息"
        return 1
    fi
}

# 清除所有自定义规则
flush_rules() {
    local confirm
    read -p "This will remove ALL custom policy routing rules. Continue? (y/N): " confirm
    
    if [[ "$confirm" =~ ^[yY] ]]; then
        log "INFO" "Flushing all custom policy routing rules..."
        
        # 获取所有自定义规则并删除
        while read -r rule; do
            if [[ $rule =~ ^([0-9]+):(.*)$ ]]; then
                local priority="${BASH_REMATCH[1]}"
                # 保留系统关键规则
                if [[ $priority -ne 0 && $priority -ne 32766 && $priority -ne 32767 ]]; then
                    local rule_spec="${BASH_REMATCH[2]}"
                    log "INFO" "Deleting rule: $rule_spec"
                    if ! ip rule del $rule_spec; then
                        log "WARN" "Failed to delete rule: $rule_spec"
                    fi
                fi
            fi
        done < <(ip rule show)
        
        log "INFO" "Custom rules flushed"
    else
        log "INFO" "Operation cancelled"
    fi
}

# 解析命令行参数
parse_args() {
    ACTION=$1
    shift

    SOURCE_IP=""
    DEST_IP=""
    DEVICE=""
    TABLE="$DEFAULT_TABLE"
    GATEWAY=""
    PRIORITY="$DEFAULT_PRIORITY"
    PERSIST=false
    BATCH_FILE=""
    BACKUP_NAME=""
    RESTORE_NAME=""
    SHOW_OPTION=""
    # 安全选项默认启用
    DISABLE_SAFETY=false
    DISABLE_BACKUP=false
    DISABLE_TEST=false

    # 如果是show命令,下一个参数可能是show选项
    if [[ "$ACTION" == "show" && $# -gt 0 && ! "$1" =~ ^-- ]]; then
        SHOW_OPTION="$1"
        shift
    fi

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --source)
                SOURCE_IP=$2
                shift 2
                ;;
            --dest)
                DEST_IP=$2
                shift 2
                ;;
            --dev)
                DEVICE=$2
                shift 2
                ;;
            --table)
                TABLE=$2
                shift 2
                ;;
            --gateway)
                GATEWAY=$2
                shift 2
                ;;
            --priority)
                PRIORITY=$2
                shift 2
                ;;
            --persist)
                PERSIST=true
                shift
                ;;
            --batch)
                BATCH_FILE=$2
                shift 2
                ;;
            --backup)
                BACKUP_NAME=$2
                shift 2
                ;;
            --restore)
                RESTORE_NAME=$2
                shift 2
                ;;
            --rollback)
                # 立即执行回滚,不需要其他参数
                ACTION="rollback"
                ;;
            --no-safety)
                DISABLE_SAFETY=true
                shift
                ;;
            --no-backup)
                DISABLE_BACKUP=true
                shift
                ;;
            --no-test)
                DISABLE_TEST=true
                shift
                ;;
            *)
                usage
                ;;
        esac
    done
    
    # 更新全局变量
    if [ "$DISABLE_SAFETY" = true ]; then
        SAFE_MODE=false
    fi
    
    if [ "$DISABLE_BACKUP" = true ]; then
        AUTO_BACKUP=false
    fi
    
    if [ "$DISABLE_TEST" = true ]; then
        TEST_CONNECTIVITY=false
    fi
}

# 添加路由表和规则
add_rule() {
    # 检查参数
    if [ -z "$DEVICE" ]; then
        log "ERROR" "Device (--dev) must be specified for 'add' action."
        usage
    fi

    # 验证优先级
    if ! validate_number "$PRIORITY" 1 32764; then
        log "ERROR" "Priority must be a number between 1 and 32764."
        exit 1
    fi

    # 验证IP地址
    if [ -n "$SOURCE_IP" ] && ! validate_ip "$SOURCE_IP"; then
        log "ERROR" "Invalid source IP address: $SOURCE_IP"
        exit 1
    fi

    if [ -n "$DEST_IP" ] && ! validate_ip "$DEST_IP"; then
        log "ERROR" "Invalid destination IP address: $DEST_IP"
        exit 1
    fi

    # 检查网卡是否存在
    if ! ip link show "$DEVICE" &> /dev/null; then
        log "ERROR" "Device $DEVICE does not exist."
        exit 1
    fi

    # 获取网关(如果未指定)
    if [ -z "$GATEWAY" ]; then
        log "INFO" "自动获取设备 $DEVICE 的默认网关..."
        
        # 获取默认路由信息
        local default_route=$(ip route show dev "$DEVICE" 2>/dev/null | awk '/^default/ {print}')
        if [ -z "$default_route" ]; then
            log "ERROR" "设备 $DEVICE 上没有找到默认路由。请使用 --gateway 参数手动指定网关。"
            log "INFO" "当前设备 $DEVICE 的路由表:"
            ip route show dev "$DEVICE" 2>/dev/null || log "WARN" "无法获取设备路由信息"
            exit 1
        fi
        
        GATEWAY=$(echo "$default_route" | awk '{print $3}')
        local gateway_dev=$(echo "$default_route" | awk '{print $5}')
        
        if [ -z "$GATEWAY" ]; then
            log "ERROR" "无法从默认路由中提取网关地址: $default_route"
            exit 1
        fi
        
        log "INFO" "从设备 $DEVICE 获取到默认网关: $GATEWAY (通过 $gateway_dev)"
    fi
    
    # 验证网关IP格式
    if ! validate_ip "$GATEWAY"; then
        log "ERROR" "无效的网关IP地址格式: $GATEWAY"
        exit 1
    fi
    
    # 验证网关是否在设备子网内
    log "INFO" "验证网关 $GATEWAY 是否在设备 $DEVICE 的子网内..."
    local device_ip=$(ip addr show "$DEVICE" 2>/dev/null | grep "inet " | head -1 | awk '{print $2}' | cut -d'/' -f1)
    if [ -n "$device_ip" ]; then
        log "INFO" "设备 $DEVICE 的IP地址: $device_ip"
        
        # 简单的子网检查(仅支持/24网段)
        local device_network=$(echo "$device_ip" | cut -d'.' -f1-3)
        local gateway_network=$(echo "$GATEWAY" | cut -d'.' -f1-3)
        
        if [[ "$device_network" != "$gateway_network" ]]; then
            log "WARN" "警告: 网关 $GATEWAY 与设备IP $device_ip 不在同一/24子网内"
            log "INFO" "这可能导致路由问题,请确认网关配置正确"
            read -p "是否继续? (y/N): " confirm
            if [[ ! "$confirm" =~ ^[yY] ]]; then
                log "INFO" "操作已取消"
                exit 1
            fi
        else
            log "INFO" "网关与设备在同一子网内,验证通过"
        fi
    else
        log "WARN" "无法获取设备 $DEVICE 的IP地址,跳过子网验证"
    fi
    
    # 执行安全检查
    if [ "$SAFE_MODE" = true ]; then
        safety_check "$SOURCE_IP" "$DEST_IP" "$DEVICE" "$PRIORITY" "$GATEWAY"
    fi
    
    # 创建回滚脚本
    create_rollback
    
    # 创建配置前备份
    pre_config_backup

    # 添加路由表(如果不存在)
    if ! grep -q "$TABLE" $CONFIG_FILE 2>/dev/null; then
        log "INFO" "Adding table $TABLE to $CONFIG_FILE..."
        # 找到未使用的表号
        local table_num=100
        while grep -q "^$table_num " $CONFIG_FILE 2>/dev/null; do
            ((table_num++))
        done
        echo "$table_num $TABLE" >> $CONFIG_FILE || {
            log "ERROR" "Failed to add table $TABLE."
            exit 1
        }
        log "INFO" "Added table $TABLE with number $table_num"
    fi

    # 添加默认路由到自定义表(如果不存在)
    if ! ip route show table "$TABLE" 2>/dev/null | grep -q "default via $GATEWAY"; then
        log "INFO" "添加默认路由: default via $GATEWAY dev $DEVICE table $TABLE"
        
        # 先测试路由命令语法
        if ! ip route add default via "$GATEWAY" dev "$DEVICE" table "$TABLE" 2>&1; then
            local route_error=$?
            log "ERROR" "添加默认路由失败 (错误码: $route_error)"
            log "INFO" "诊断信息:"
            log "INFO" "  - 目标表: $TABLE"
            log "INFO" "  - 网关: $GATEWAY"
            log "INFO" "  - 设备: $DEVICE"
            
            # 检查表是否存在
            if ! grep -q "$TABLE" "$CONFIG_FILE" 2>/dev/null; then
                log "ERROR" "路由表 $TABLE 未在 $CONFIG_FILE 中定义"
            else
                log "INFO" "路由表 $TABLE 已定义"
            fi
            
            # 检查设备是否存在且UP
            if ! ip link show "$DEVICE" &>/dev/null; then
                log "ERROR" "设备 $DEVICE 不存在"
            elif ! ip link show "$DEVICE" | grep -q "state UP"; then
                log "ERROR" "设备 $DEVICE 状态不是UP"
            else
                log "INFO" "设备 $DEVICE 存在且状态正常"
            fi
            
            # 检查网关连通性
            log "INFO" "测试网关连通性..."
            if ping -c 1 -W 2 "$GATEWAY" &>/dev/null; then
                log "INFO" "网关 $GATEWAY 可达"
            else
                log "WARN" "网关 $GATEWAY 不可达,但这可能是正常的(取决于网络配置)"
            fi
            
            # 检查主路由表中是否有到该网关的路由
            log "INFO" "检查主路由表中到网关 $GATEWAY 的路由..."
            if ip route show | grep -q "$GATEWAY"; then
                log "INFO" "主路由表中存在到网关 $GATEWAY 的路由"
                ip route show | grep "$GATEWAY" | head -3
            else
                log "WARN" "主路由表中未找到到网关 $GATEWAY 的路由"
            fi
            
            exit 1
        fi
        log "INFO" "成功添加默认路由 via $GATEWAY to table $TABLE."
    else
        log "INFO" "默认路由已存在于表 $TABLE 中,跳过添加"
    fi

    # 添加策略规则
    RULE_EXISTS=false
    if [ -n "$SOURCE_IP" ]; then
        if ip rule show 2>/dev/null | grep -q "from $SOURCE_IP.*lookup $TABLE"; then
            log "WARN" "Rule already exists: from $SOURCE_IP lookup $TABLE"
            RULE_EXISTS=true
        else
            ip rule add from "$SOURCE_IP" lookup "$TABLE" priority "$PRIORITY" || {
                log "ERROR" "Failed to add rule from $SOURCE_IP."
                exit 1
            }
            log "INFO" "Added rule: from $SOURCE_IP lookup $TABLE with priority $PRIORITY."
        fi
    elif [ -n "$DEST_IP" ]; then
        if ip rule show 2>/dev/null | grep -q "to $DEST_IP.*lookup $TABLE"; then
            log "WARN" "Rule already exists: to $DEST_IP lookup $TABLE"
            RULE_EXISTS=true
        else
            ip rule add to "$DEST_IP" lookup "$TABLE" priority "$PRIORITY" || {
                log "ERROR" "Failed to add rule to $DEST_IP."
                exit 1
            }
            log "INFO" "Added rule: to $DEST_IP lookup $TABLE with priority $PRIORITY."
        fi
    else
        log "ERROR" "No source (--source) or destination (--dest) IP specified."
        usage
    fi

    # 持久化配置(仅在规则新增时)
    if [ "$PERSIST" = true ] && [ "$RULE_EXISTS" = false ]; then
        persist_config "$GATEWAY" "$DEVICE" "$TABLE" "$SOURCE_IP" "$DEST_IP" "$PRIORITY"
    fi
    
    # 执行配置后验证
    if [ "$RULE_EXISTS" = false ]; then
        post_config_verify
    fi
}

# 删除规则
del_rule() {
    if [ -n "$SOURCE_IP" ]; then
        if ip rule show 2>/dev/null | grep -q "from $SOURCE_IP"; then
            # 获取规则完整描述以删除
            local rule_spec=$(ip rule show 2>/dev/null | grep "from $SOURCE_IP" | awk '{print substr($0, index($0,$2))}')
            ip rule del $rule_spec || {
                log "ERROR" "Failed to delete rule from $SOURCE_IP."
                exit 1
            }
            log "INFO" "Deleted rule: from $SOURCE_IP."
        else
            log "WARN" "Rule not found: from $SOURCE_IP."
        fi
    elif [ -n "$DEST_IP" ]; then
        if ip rule show 2>/dev/null | grep -q "to $DEST_IP"; then
            # 获取规则完整描述以删除
            local rule_spec=$(ip rule show 2>/dev/null | grep "to $DEST_IP" | awk '{print substr($0, index($0,$2))}')
            ip rule del $rule_spec || {
                log "ERROR" "Failed to delete rule to $DEST_IP."
                exit 1
            }
            log "INFO" "Deleted rule: to $DEST_IP."
        else
            log "WARN" "Rule not found: to $DEST_IP."
        fi
    else
        log "ERROR" "No source (--source) or destination (--dest) IP specified."
        usage
    fi
}

# 持久化配置
persist_config() {
    local gateway=$1
    local device=$2
    local table=$3
    local source_ip=$4
    local dest_ip=$5
    local priority=$6

    # 检查目录权限
    if ! mkdir -p "$(dirname "$PERSIST_FILE")" 2>/dev/null; then
        log "WARN" "Failed to create directory for persistent file. Skipping persistence."
        return
    fi

    # 创建临时文件
    local temp_file=$(mktemp)
    
    {
        echo "# Policy Routing Rules (Auto-generated) - $(date)"
        echo "post-up ip route add default via $gateway dev $device table $table"
        if [ -n "$source_ip" ]; then
            echo "post-up ip rule add from $source_ip lookup $table priority $priority"
        elif [ -n "$dest_ip" ]; then
            echo "post-up ip rule add to $dest_ip lookup $table priority $priority"
        fi
    } > "$temp_file"
    
    # 检查文件是否已存在,如果存在则追加内容
    if [ -f "$PERSIST_FILE" ]; then
        # 移除重复的配置
        grep -v "post-up ip rule add.*lookup $table" "$PERSIST_FILE" > "${temp_file}.tmp" 2>/dev/null
        grep -v "post-up ip route add default via $gateway.*table $table" "${temp_file}.tmp" > "${temp_file}.tmp2" 2>/dev/null
        cat "$temp_file" >> "${temp_file}.tmp2"
        mv "${temp_file}.tmp2" "$PERSIST_FILE"
    else
        mv "$temp_file" "$PERSIST_FILE"
    fi
    
    # 清理临时文件
    rm -f "$temp_file" "${temp_file}.tmp" "${temp_file}.tmp2"
    
    log "INFO" "Persisted configuration to $PERSIST_FILE."
}

# 显示规则
show_rules() {
    local show_option=${1:-"all"}
    
    # 显示帮助信息
    if [[ "$show_option" == "help" ]] || [[ "$show_option" == "-h" ]] || [[ "$show_option" == "--help" ]]; then
        echo -e "\n${GREEN}show 命令用法:${NC}"
        echo "   $0 show [选项]"
        echo ""
        echo "选项:"
        echo "   all, 无参数    显示所有信息 (默认)"
        echo "   rules         仅显示路由规则"
        echo "   tables        仅显示路由表信息"
        echo "   custom        仅显示自定义规则整合视图"
        echo "   interfaces    仅显示网络接口状态"
        echo "   commands      显示常用原始命令"
        echo "   help, -h      显示此帮助信息"
        echo ""
        echo "示例:"
        echo "   $0 show           # 显示所有信息"
        echo "   $0 show rules     # 仅显示路由规则"
        echo "   $0 show custom    # 仅显示自定义规则"
        return 0
    fi
    
    echo -e "\n${GREEN}=== 策略规则视图 ===${NC}"
    
    # 根据选项决定显示内容
    case "$show_option" in
        "rules"|"all")
            # 1. 完整显示ip rule show命令输出
            echo -e "\n${YELLOW}📋 当前路由规则 (ip rule show):${NC}"
            echo "   ────────────────────────────────────────────────────────────────────────────────────"
            echo "   注: 优先级数值越小,规则越优先匹配"
            ip rule show 2>/dev/null
            echo "   ────────────────────────────────────────────────────────────────────────────────────"
            ;;
        "tables"|"all")
            # 2. 按路由表分组显示规则
            echo -e "\n${YELLOW}📊 规则与路由表关系:${NC}"
            echo "   ────────────────────────────────────────────────────────────────────────────────────"
            
            # 获取所有路由表
            local tables=("local" "main" "default")
            if [ -f "$CONFIG_FILE" ]; then
                while read -r line; do
                    [[ $line =~ ^# ]] && continue
                    local table_name=$(echo "$line" | awk '{print $2}')
                    [ -n "$table_name" ] && tables+=("$table_name")
                done < "$CONFIG_FILE"
            fi
            
            # 为每个路由表显示规则和路由信息
            for table in "${tables[@]}"; do
                # 为每个路由表添加说明
                local table_desc=""
                case "$table" in
                    "local") table_desc="(本地通信规则,如localhost)" ;;
                    "main") table_desc="(系统主路由表,最常用)" ;;
                    "default") table_desc="(备用路由表)" ;;
                    *) table_desc="(自定义路由表)" ;;
                esac
                
                echo -e "\n${GREEN}路由表: $table $table_desc${NC}"
                echo "   ────────────────────────────────────────────────────────────────────────────────────"
                echo "   规则 (使用此表):"
                
                # 显示使用此表的规则
                local table_rules=0
                ip rule show 2>/dev/null | grep "lookup $table" | while read -r rule; do
                    local priority=$(echo "$rule" | cut -d':' -f1)
                    local content=$(echo "$rule" | cut -d':' -f2- | sed 's/^ *//')
                    
                    # 为规则类型添加说明
                    local rule_explain=""
                    if [[ $content =~ ^from\ all\ lookup ]]; then
                        rule_explain="(所有流量)"
                    elif [[ $content =~ ^from\ ([0-9./]+)\ lookup ]]; then
                        local src_ip=$(echo "$content" | sed -n 's/^from\ \([0-9./]*\).*/\1/p')
                        rule_explain="(来自${src_ip}的流量)"
                    elif [[ $content =~ ^to\ ([0-9./]+)\ lookup ]]; then
                        local dest_ip=$(echo "$content" | sed -n 's/^to\ \([0-9./]*\).*/\1/p')
                        rule_explain="(前往${dest_ip}的流量)"
                    fi
                    
                    echo "     $priority: $content $rule_explain"
                    ((table_rules++))
                done
                
                if [ $table_rules -eq 0 ]; then
                    echo "     (无规则使用此表)"
                fi
                
                echo "   路由表内容 (ip route show table $table):"
                echo "   注: 此路由表决定了匹配规则后流量如何转发"
                
                local route_count=0
                ip route show table "$table" 2>/dev/null | while read -r route; do
                    # 为路由条目添加说明
                    local route_explain=""
                    if [[ $route =~ ^default\ via ]]; then
                        local gw=$(echo "$route" | awk '{print $3}')
                        route_explain="(默认网关)"
                    elif [[ $route =~ ^([0-9.]+/[0-9]+) ]]; then
                        route_explain="(直连网络)"
                    fi
                    
                    echo "     $route $route_explain"
                    ((route_count++))
                done
                
                if [ $route_count -eq 0 ]; then
                    echo "     (此路由表为空)"
                fi
            done
            ;;
        "custom"|"all")
            # 3. 整合展示自定义规则、路由表和优先级
            echo -e "\n${YELLOW}🔧 自定义规则整合视图:${NC}"
            echo "   ────────────────────────────────────────────────────────────────────────────────────"
            echo "   注: 下表将规则、路由表和优先级整合展示,便于理解配置关系"
            echo ""
            
            # 表头
            printf "%-8s %-18s %-12s %-15s %-12s %s\n" "优先级" "规则类型" "匹配IP" "路由表" "默认网关" "状态"
            echo "   ────────────────────────────────────────────────────────────────────────────────────"
            
            # 使用数组收集规则数据,避免子shell问题
            local rule_count=0
            local rule_data=()
            
            # 处理每个规则
            while IFS= read -r rule; do
                # 跳过系统规则(使用更简单的方法)
                if [[ $rule =~ lookup\ (local|main|default) ]]; then
                    continue
                fi
                
                # 解析规则优先级和内容
                local priority=$(echo "$rule" | cut -d':' -f1)
                local rule_content=$(echo "$rule" | cut -d':' -f2- | sed 's/^ *//')
                
                # 解析规则类型和参数
                local target_ip=""
                local table_name=""
                local rule_type=""
                
                # 使用更宽松的匹配模式
                if [[ $rule_content =~ from\ ([0-9./]+).*lookup\ (.+) ]]; then
                    target_ip="${BASH_REMATCH[1]}"
                    table_name="${BASH_REMATCH[2]}"
                    rule_type="源IP"
                elif [[ $rule_content =~ to\ ([0-9./]+).*lookup\ (.+) ]]; then
                    target_ip="${BASH_REMATCH[1]}"
                    table_name="${BASH_REMATCH[2]}"
                    rule_type="目标IP"
                else
                    continue
                fi
                
                # 获取路由表默认网关
                local default_route=$(ip route show table "$table_name" 2>/dev/null | grep "^default")
                local gateway=""
                local device=""
                local status="未知"
                
                if [ -n "$default_route" ]; then
                    gateway=$(echo "$default_route" | awk '{print $3}')
                    device=$(echo "$default_route" | awk '{print $5}')
                    
                    # 检查网关连通性
                    if ping -c 1 -W 2 "$gateway" &>/dev/null; then
                        status="可用"
                    else
                        status="不可达"
                    fi
                else
                    gateway="无"
                    device="无"
                    status="无默认路由"
                fi
                
                # 添加规则数据到数组
                rule_data+=("$priority|$rule_type|$target_ip|$table_name|$gateway($device)|$status")
                ((rule_count++))
            done < <(ip rule show 2>/dev/null)
            
            # 使用column命令格式化显示
            if [ $rule_count -gt 0 ]; then
                # 创建临时文件
                local temp_file=$(mktemp)
                
                # 写入数据到临时文件
                for data in "${rule_data[@]}"; do
                    echo "$data" >> "$temp_file"
                done
                
                # 显示格式化数据
                cat "$temp_file" | column -t -s '|'
                
                # 清理临时文件
                rm -f "$temp_file"
            else
                echo "   暂无自定义策略规则"
            fi
            ;;
        "interfaces"|"all")
            # 4. 显示网络接口状态
            echo -e "\n${YELLOW}🌐 网络接口状态:${NC}"
            echo "   ────────────────────────────────────────────────────────────────────────────────────"
            
            # 创建接口信息的临时文件
            local temp_if_file=$(mktemp)
            
            # 获取接口信息
            ip link show | grep -E '^[0-9]+:' | while read -r line; do
                local iface=$(echo "$line" | awk -F': ' '{print $2}' | awk '{print $1}')
                local status_display="DOWN"
                local ip_addr=$(ip addr show "$iface" 2>/dev/null | grep "inet " | head -1 | awk '{print $2}' | cut -d'/' -f1)
                local type="未知"
                
                # 检查接口状态
                if ip link show "$iface" | grep -q "state UP"; then
                    status_display="UP"
                fi
                
                # 确定接口类型
                case "$iface" in
                    "lo") type="回环" ;;
                    eth*) type="有线" ;;
                    wlan*) type="无线" ;;
                    enp*) type="有线" ;;
                esac
                
                echo "$iface $status_display ${ip_addr:-无} $type" >> "$temp_if_file"
            done
            
            # 使用column命令对齐并显示内容
            printf "%-8s %-8s %-15s %s\n" "接口" "状态" "IP地址" "类型"
            echo "   ────────────────────────────────────────────────────────────────────────────────────"
            cat "$temp_if_file" | column -t
            rm -f "$temp_if_file"
            ;;
        "commands"|"all")
            # 5. 原始命令参考
            echo -e "\n${YELLOW}🛠️  相关原始命令:${NC}"
            echo "   ────────────────────────────────────────────────────────────────────────────────────"
            echo -e "   查看所有规则: ${BLUE}ip rule show${NC}"
            echo "   注: 显示所有策略路由规则,包括优先级和匹配条件"
            
            echo -e "   查看主路由表: ${BLUE}ip route show table main${NC}"
            echo "   注: 主路由表包含系统默认路由规则"
            
            echo -e "   查看自定义路由表: ${BLUE}ip route show table [表名]${NC}"
            echo "   注: 查看指定路由表的详细路由配置"
            
            echo -e "   查看路由表定义: ${BLUE}cat $CONFIG_FILE${NC}"
            echo "   注: 查看系统定义的路由表列表和编号"
            
            echo -e "   测试网关连通性: ${BLUE}ping -c 1 -W 2 [网关IP]${NC}"
            echo "   注: 检查网关是否可达"
            ;;
    esac
    
    # 如果是all或没有指定选项,显示额外信息
    if [[ "$show_option" == "all" ]] || [ -z "$show_option" ]; then
        echo ""
        echo "   ────────────────────────────────────────────────────────────────────────────────────"
        echo -e "\n${YELLOW}💡 使用提示:${NC}"
        echo "   要查看特定部分,请使用:"
        echo "     $0 show rules       # 仅显示路由规则"
        echo "     $0 show tables      # 仅显示路由表信息"
        echo "     $0 show custom      # 仅显示自定义规则整合"
        echo "     $0 show interfaces # 仅显示网络接口状态"
        echo "     $0 show commands   # 仅显示相关命令"
        echo "     $0 show help       # 显示此帮助信息"
    fi
    
    echo -e "\n${GREEN}=== 展示结束 ===${NC}"
    echo "   注: 如果您对策略路由不熟悉,建议先了解基本概念再进行配置"
}

# 主流程
main() {
    check_command "ip"
    parse_args "$@"

    case "$ACTION" in
        add)
            if [ -n "$BATCH_FILE" ]; then
                batch_add "$BATCH_FILE"
            else
                add_rule
            fi
            ;;
        del)
            del_rule
            ;;
        show)
            show_rules "$SHOW_OPTION"  # 传递show选项参数
            ;;
        backup)
            backup_config "$BACKUP_NAME"
            ;;
        restore)
            restore_config "$RESTORE_NAME"
            ;;
        flush)
            flush_rules
            ;;
        rollback)
            execute_rollback
            ;;
        *)
            usage
            ;;
    esac
}

# 检查是否以root权限运行
if [ "$(id -u)" -ne 0 ]; then
    log "ERROR" "This script must be run as root"
    exit 1
fi

main "$@"

posted on 2025-12-15 16:20  Mahavairocana  阅读(1)  评论(0)    收藏  举报

导航