本指南介绍如何在 OpenWrt (WR30U) 下通过 macvlan 批量创建虚拟网卡,实现单线多拨叠加带宽,并配合自动化脚本完成多账号认证。

一、 刷入 OpenWrt 固件

1. 开启 Telnet 与 SSH

由于 WR30U 是运营商定制版,通常需要先通过小米官方修复工具或特定漏洞开启 Telnet。

获取权限: 使用 Bilibili 或 GitHub 上常见的 “WR30U 开启 Telnet 工具”。

登录: 开启后通过终端执行 telnet 192.168.31.1,用户名为 root,密码根据 SN 计算或为空。

固化 SSH: 进入后修改 nvram 参数以永久开启 SSH 权限,并修改 root 密码。

2. 刷入 uboot (不死固件)

刷入 uboot 是为了防止以后折腾固件导致“变砖”,建议选择 Hanwckf 的 MT7981 版 uboot。

传输文件: 使用 SCP 将 uboot.bin 上传至路由器的 /tmp 目录。

写入: 执行以下命令(注意:分区名称务必核对为 FIP):

# 确认分区
cat /proc/mtd
# 写入 uboot
mtd write /tmp/mt7981_wr30u-fip-fixed-parts.bin FIP

3. 进入 uboot 刷入系统

物理操作: 断开电源,按住 Reset 键不放,插电,约 5-10 秒后灯闪烁。

访问界面: 电脑网线连接 LAN 口,手动设置 IP 为 192.168.1.2,浏览器访问 192.168.1.1。

上传固件: 在 uboot 界面选择你的 OpenWrt .bin 固件(选择内置了 mwan3 和 macvlan 插件的编译版本),点击 Flash。

重启: 进度条走完后,路由器会自动重启进入 OpenWrt 界面(通常为 192.168.1.1)。

二、 网络基础配置

为了确保脚本能准确识别接口,虚拟网卡与接口名称必须严格遵循 veth{ID}vwan{ID} 的命名格式。

1. 批量创建虚拟网卡 (Device)

直接编辑 /etc/config/network,为每个拨号实例添加以下配置(MAC 地址需随机且唯一):

config device 'veth0'
    option name 'veth0'
    option ifname 'wan'      # 物理 WAN 口名称
    option type 'macvlan'
    option macaddr '76:6F:7C:36:E0:aa'

config device 'veth1'
    option name 'veth1'
    option ifname 'wan'
    option type 'macvlan'
    option macaddr '76:6F:7C:36:E0:bb'

2. 批量创建逻辑接口 (Interface)

/etc/config/network 中继续添加接口定义。注意:每个接口的 metric 必须唯一,否则负载均衡无法生效。

config interface 'vwan0'
    option proto 'dhcp'
    option ifname 'veth0'
    option metric '10'

config interface 'vwan1'
    option proto 'dhcp'
    option ifname 'veth1'
    option metric '20'

修改完成后,执行 /etc/init.d/network restart 重启网络服务。


三、 自动化认证脚本部署

脚本将根据配置文件中的 veth_id 自动寻找对应的虚拟网卡 (veth) 和逻辑接口 (vwan)。

1. 创建配置文件 (/usr/bin/xxxx_login/xxxx_login.conf)

确保 veth_id 与上述网络配置中的后缀一致。

{
  "login_url": "http://192.168.200.2:801/eportal/",
  "accounts": [
    {
      "veth_id": "0",
      "is_pc": "0",
      "username": "统一ID",
      "company": "cmcc",
      "password": "your_password"
    },
    {
      "veth_id": "1",
      "is_pc": "1",
      "username": "统一ID",
      "company": "cmcc",
      "password": "your_password"
    }
  ]
}

2. 认证脚本逻辑说明

脚本核心逻辑通过 --interface 参数强制 curl 走指定的虚拟网卡,从而绕过 OpenWrt 默认路由,实现对特定物理线路的认证。

脚本存储路径: /usr/bin/xxxx_login/xxxx_login.sh (需 chmod +x)


四、 设置定时任务 (Cron)

根据您的要求,将自动认证任务设置为每天每 6 小时执行一次(0点、6点、12点、18点),以防止学校系统强制下线。

  1. 在终端输入 crontab -e 或在 Web 界面进入 系统 -> 计划任务

  2. 添加以下配置:

# 每天 0, 6, 12, 18 点整执行认证脚本
0 0,6,12,18 * * * /usr/bin/xxxx_login/xxxx_login.sh

五、 负载均衡 (MWAN3) 关键点

配置好接口后,请在 MWAN3 插件中:

  1. 添加接口:vwan0, vwan1 等加入 MWAN3 接口列表。

  2. 追踪地址: 设置可靠的 ping 测试地址(如 223.5.5.5),用于判断接口是否在线。

  3. 分配权重:Member 设置中,如果带宽相同,将 Weight 均设为 1

  4. 策略: 确保默认策略使用了包含所有 vwan 成员的负载均衡方案。

详细可以自行搜索教程。


附录

xxxx_login.sh

#!/bin/sh

# 依赖检查
command -v jq >/dev/null || { echo "请先安装jq: opkg update && opkg install jq"; exit 1; }
command -v curl >/dev/null || { echo "请先安装curl: opkg update && opkg install curl"; exit 1; }

CONFIG_FILE="/usr/bin/xxxx_login/xxxx_login.conf"
LOG_FILE="/usr/bin/xxxx_login/xxxx_login.log"
MAX_RETRY=3  # 最大重试次数

# 日志记录函数
log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
  logger -t "CampusAuth" "$1"
}

# 获取接口IP(使用设备名)
get_interface_ip() {
  local device=$1
  ip -o -4 addr show dev "$device" | awk '{print $4}' | cut -d'/' -f1
}

# 主认证函数(核心逻辑)
do_auth() {
  local account=$1
  local base_url=$2
  
  # 自动生成设备名和接口名(veth1 <-> vwan1)
  local veth_id=$(echo "$account" | jq -r '.veth_id')
  local device="veth${veth_id}"
  local interface="vwan${veth_id}"
  
  # 初始IP获取
  local ipaddr=$(get_interface_ip "$device")
  [ -z "$ipaddr" ] && {
    log "错误:接口 $interface 无初始IP地址"
    return 1
  }

  # 构造基础请求参数(不含IP)
  local user_account=",$(echo "$account" | jq -r '.is_pc'),$(echo "$account" | jq -r '.username')@$(echo "$account" | jq -r '.company')"

  local base_params="
    c=Portal&
    a=login&
    callback=dr1003&
    login_method=1&
    wlan_user_ipv6=&
    wlan_user_mac=&
    wlan_ac_ip=&
    wlan_ac_name=&
    jsVersion=3.3.3&
    v=10048&
    user_account=$user_account&
    user_password=$(echo "$account" | jq -r '.password')&
    wlan_user_ip=
  "
  base_params=$(echo "$base_params" | tr -d '\n' | tr -d ' ')

  # 重试逻辑
  local retry_count=0
  while [ $retry_count -lt $MAX_RETRY ]; do
    # 每次重试都重新获取当前IP
    ipaddr=$(get_interface_ip "$device")
    [ -z "$ipaddr" ] && {
      log "错误:接口 $interface 无法获取IP"
      return 1
    }

    # 动态更新URL中的IP参数
    local auth_url="$base_url?${base_params}${ipaddr}"
    log "尝试认证接口 $interface (IP: $ipaddr, 重试: $retry_count)"

    # 发送请求
    response=$(curl -sG --connect-timeout 10 --interface "$device" "$auth_url")
    log "认证url: $auth_url"
    
    # 空响应处理
    if [ -z "$response" ]; then
      retry_count=$((retry_count + 1))
      log "第 $retry_count 次空响应,等待重新连接..."
      sleep 3
      continue
    fi

    # 成功响应判断
    if echo "$response" | grep -q -E 'dr1003\(\{"result":"0".*|\{"result":"1".*msg":"\\u8ba4\\u8bc1\\u6210\\u529f'; then
      log "$interface 认证成功 - 响应: $response"
      return 0
    else
      log "$interface 认证失败 - 响应: $response"
      retry_count=$((retry_count + 1))
      sleep 3
    fi
  done
  
  log "错误:接口 $interface 超过最大重试次数 ($MAX_RETRY)"
  return 1
}

# 主流程
main() {
  log "=== 开始校园网认证任务 ==="
  
  [ ! -f "$CONFIG_FILE" ] && {
    log "错误:配置文件 $CONFIG_FILE 不存在"
    exit 1
  }

  base_url=$(jq -r '.login_url' "$CONFIG_FILE")
  [ -z "$base_url" ] && {
    log "错误:未配置login_url"
    exit 1
  }

  jq -c '.accounts[]' "$CONFIG_FILE" | while read -r account; do
    do_auth "$account" "$base_url"
  done

  log "=== 认证任务完成 ==="
  log "==================="
}

main