iOS小火箭(Shadowrocket)+ WireGuard 实现代理和内网穿透

iOS 上实现同时使用代理和内网穿透的需求其实很常见,尤其是当你需要在外访问家里设备(如 NAS)时。 但是很多的工具(例如:tailscale,WireGuard)基本上只能满足其中一个需求,要么是全局代理,要么是内网穿透,很难同时兼顾,它们基本上都是靠VPNExtension,iOS中只能开一个。 小火箭(Shadowrocket)是 iOS 平台上一款相当不错的代理工具,协议支持全面,价格也很合适。这里我记录一下使用它实现 代理 + 内网穿透 的完整部署步骤,最终实现 “出国”+“回家” 两个功能。 内网穿透,我这里选择的协议是WireGuard。

需求分析

代理部分没什么特别的,直接导入订阅即可。这里主要记录 WireGuard 的搭建过程。

我的需求很简单:随时随地能连上家里的 NAS。我的 NAS 是群晖 DS218+,系统内核比较老,不支持原生 WireGuard(Linux 5.6 之后的内核才内置支持),懒得折腾内核升级,所以我在内网部署了一台虚拟机作为跳板机来连接它。

WireGuard 需要一台有公网 IP 的云主机作为中转节点。如果不想自己搞,可以试试 Tailscale(底层也是基于 WireGuard 的)。

网络拓扑

我们的网络结构很简单,就是一条线:

phone → server(Ubuntu 24.04) → jump(Ubuntu 24.04) → NAS

当然,你也可以直接把 WireGuard 装在 NAS 里省去跳板机。不过用跳板机有个额外的好处:可以访问局域网内的所有设备,不用每台都单独配置。

一、配置 Server(云主机)

24.04 的内核版本已经原生支持 WireGuard,直接安装即可:

sudo apt install wireguard -y

如果你的系统内核较旧不支持,可以尝试升级:

sudo apt update
sudo apt install --install-recommends linux-generic
sudo reboot

生成密钥对

安装完成后,我们把三台机器(phone、server、jump)的密钥对一次性都生成好:

wg genkey | tee phone_private.key | wg pubkey > phone_public.key
wg genkey | tee server_private.key | wg pubkey > server_public.key
wg genkey | tee jump_private.key | wg pubkey > jump_public.key

编写 Server 配置

sudo vim /etc/wireguard/wg0.conf

[Interface]
# 填入 server_private.key 的内容
PrivateKey = <server_private.key>
# 服务端在隧道内的虚拟 IP
Address = 10.7.0.1/24
# 建议用一个 50000 以上的随机 UDP 端口
ListenPort = 51280

# 核心:开启流量转发(网卡名通过 ip addr 查看,常见为 eth0 / ens3 等)
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

# --- Peer: 手机 ---
[Peer]
PublicKey = <phone_public.key>
AllowedIPs = 10.7.0.2/32

# --- Peer: Jump 跳板机 ---
[Peer]
PublicKey = <jump_public.key>
AllowedIPs = 10.7.0.3/32,192.168.1.0/24,192.168.2.0/24

整体 IP 分配如下:

phone(10.7.0.2) → server(10.7.0.1) → jump(10.7.0.3) → NAS(192.168.1.x)

文章末尾附有一键部署脚本,可以自动完成安装、生成密钥和配置文件。

启动服务

sudo wg-quick up wg0

Tips:

  • 关闭:sudo wg-quick down wg0
  • 推荐使用 systemctl 管理:sudo systemctl start [email protected]
  • 开机自启:sudo systemctl enable [email protected]
  • 如果遇到 wg-quick: 'wg0' already exists 错误,先 sudo wg-quick down wg0 再重新启动

这样 Server 就配置完成了。

二、配置 Jump(跳板机)

Jump 的安装方式和 Server 一样:

sudo apt install wireguard -y

sudo vim /etc/wireguard/wg0.conf

[Interface]
PrivateKey = <jump_private.key>
Address = 10.7.0.3/24
MTU = 1420

PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o ens3 -j MASQUERADE

[Peer]
PublicKey = <server_public.key>
Endpoint = <你的云主机公网IP>:51280
AllowedIPs = 10.7.0.0/24
PersistentKeepalive = 25

启动:

sudo wg-quick up wg0

验证连通性

在 Server 上执行 sudo wg show,留意 latest handshake 字段:

  allowed ips: 10.7.0.3/32, 192.168.1.0/24, 192.168.2.0/24
  latest handshake: 32 seconds ago
  transfer: 8.30 MiB received, 515.13 KiB sent

也可以直接 ping 10.7.0.3,应该是可以 ping 通的。在 Jump 上执行 sudo wg show 也能看到类似的结果。

连不通?排查清单:

  1. 检查内核转发cat /proc/sys/net/ipv4/ip_forward 应返回 1。如果不是,临时开启:sudo sysctl -w net.ipv4.ip_forward=1;永久生效:编辑 /etc/sysctl.conf 添加 net.ipv4.ip_forward = 1,然后执行 sudo sysctl -p
  2. 检查防火墙/安全组:确认云主机的安全组或防火墙已放行配置的 UDP 端口(如 51280)

三、配置小火箭(Phone 端)

小火箭提供图形界面,按照提示添加 WireGuard 配置即可:

配置分流规则(重要!)

这一步是关键。需要在小火箭的分流规则中添加以下 CIDR,确保访问这些网段时走 WireGuard 隧道:

  • 10.7.0.0/24 — WireGuard 隧道网段
  • 192.168.1.0/24 — 家庭局域网网段(按实际情况添加)
image-20260210191010607

这样配置完成后,小火箭会自动根据目标 IP 判断是走代理还是走 WireGuard 隧道,实现"出国"和"回家"两不误。

附录:一键部署脚本

这个脚本在 Server(云主机) 上运行,会自动完成 WireGuard 的安装检测、内核检查、密钥生成和配置文件创建。

#!/usr/bin/env bash
#
# WireGuard 一键部署脚本
# 在 Server(云主机)上运行,在当前目录生成 server / phone / jump 三份配置
# 用法: sudo bash wireguard_setup.sh
#

set -euo pipefail

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

info() { echo -e "${GREEN}[INFO]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() {
  echo -e "${RED}[ERROR]${NC} $*"
  exit 1
}

# ======================== 权限检查 ========================
if [[ $EUID -ne 0 ]]; then
  error "请使用 root 权限运行此脚本: sudo bash $0"
fi

# ======================== 可配置参数 ========================
WG_PORT=${WG_PORT:-51280}
WG_SUBNET="10.7.0"
SERVER_IP="${WG_SUBNET}.1"
PHONE_IP="${WG_SUBNET}.2"
JUMP_IP="${WG_SUBNET}.3"
LAN_SUBNETS=${LAN_SUBNETS:-"192.168.1.0/24,192.168.2.0/24"}
WG_DIR="."
KEY_DIR="${WG_DIR}/keys"

echo ""
echo -e "${CYAN}======================================${NC}"
echo -e "${CYAN}   WireGuard 配置生成${NC}"
echo -e "${CYAN}======================================${NC}"
echo ""

# ======================== 1. 检测内核是否支持 WireGuard ========================
info "检测内核版本..."
KERNEL_VERSION=$(uname -r)
KERNEL_MAJOR=$(echo "$KERNEL_VERSION" | cut -d. -f1)
KERNEL_MINOR=$(echo "$KERNEL_VERSION" | cut -d. -f2)

if [[ $KERNEL_MAJOR -gt 5 ]] || { [[ $KERNEL_MAJOR -eq 5 ]] && [[ $KERNEL_MINOR -ge 6 ]]; }; then
  info "内核版本 ${KERNEL_VERSION},原生支持 WireGuard ✓"
else
  warn "内核版本 ${KERNEL_VERSION},可能不原生支持 WireGuard(需要 5.6+)"
  warn "将尝试通过 DKMS 模块安装,如果失败请先升级内核:"
  warn "  sudo apt update && sudo apt install --install-recommends linux-generic && sudo reboot"
fi

# 检查内核模块是否可用
if modprobe wireguard 2>/dev/null; then
  info "WireGuard 内核模块加载成功 ✓"
else
  warn "WireGuard 内核模块暂未加载,安装后会自动处理"
fi

# ======================== 2. 检测并安装 WireGuard ========================
if command -v wg &>/dev/null; then
  info "WireGuard 已安装,版本: $(wg --version) ✓"
else
  info "WireGuard 未安装,正在安装..."
  apt update -qq
  apt install -y wireguard wireguard-tools
  if command -v wg &>/dev/null; then
    info "WireGuard 安装成功 ✓"
  else
    error "WireGuard 安装失败,请检查系统环境"
  fi
fi

# ======================== 3. 开启内核转发 ========================
info "检查 IP 转发..."
if [[ $(cat /proc/sys/net/ipv4/ip_forward) -eq 1 ]]; then
  info "IPv4 转发已开启 ✓"
else
  warn "IPv4 转发未开启,正在开启..."
  sysctl -w net.ipv4.ip_forward=1 >/dev/null
  # 持久化
  if grep -q "^net.ipv4.ip_forward" /etc/sysctl.conf; then
    sed -i 's/^net.ipv4.ip_forward.*/net.ipv4.ip_forward = 1/' /etc/sysctl.conf
  else
    echo "net.ipv4.ip_forward = 1" >>/etc/sysctl.conf
  fi
  sysctl -p >/dev/null 2>&1
  info "IPv4 转发已开启并持久化 ✓"
fi

# ======================== 4. 自动检测网卡名称 ========================
info "自动检测默认网卡..."
DEFAULT_IFACE=$(ip route show default | awk '/default/ {print $5}' | head -n1)
if [[ -z "$DEFAULT_IFACE" ]]; then
  # fallback: 尝试从 ip link 获取第一个非 lo 的网卡
  DEFAULT_IFACE=$(ip -o link show | awk -F': ' '{print $2}' | grep -v lo | head -n1)
fi

if [[ -z "$DEFAULT_IFACE" ]]; then
  error "无法自动检测网卡名称,请手动指定"
fi
info "检测到默认网卡: ${CYAN}${DEFAULT_IFACE}${NC} ✓"

# ======================== 5. 生成三组密钥对 ========================
info "生成密钥对..."
mkdir -p "$KEY_DIR"
chmod 700 "$KEY_DIR"

generate_keypair() {
  local name=$1
  local priv_file="${KEY_DIR}/${name}_private.key"
  local pub_file="${KEY_DIR}/${name}_public.key"

  if [[ -f "$priv_file" && -f "$pub_file" ]]; then
    warn "${name} 密钥对已存在,跳过生成"
  else
    wg genkey | tee "$priv_file" | wg pubkey >"$pub_file"
    chmod 600 "$priv_file"
    info "${name} 密钥对已生成 ✓"
  fi
}

generate_keypair "server"
generate_keypair "phone"
generate_keypair "jump"

# 读取密钥内容
SERVER_PRIV=$(cat "${KEY_DIR}/server_private.key")
SERVER_PUB=$(cat "${KEY_DIR}/server_public.key")
PHONE_PRIV=$(cat "${KEY_DIR}/phone_private.key")
PHONE_PUB=$(cat "${KEY_DIR}/phone_public.key")
JUMP_PRIV=$(cat "${KEY_DIR}/jump_private.key")
JUMP_PUB=$(cat "${KEY_DIR}/jump_public.key")

# ======================== 6. 获取服务器公网 IP ========================
info "获取服务器公网 IP..."
SERVER_PUBLIC_IP=$(curl -s4 ifconfig.me || curl -s4 icanhazip.com || curl -s4 ip.sb || echo "")
if [[ -z "$SERVER_PUBLIC_IP" ]]; then
  warn "无法自动获取公网 IP,配置文件中将使用占位符 <YOUR_SERVER_PUBLIC_IP>"
  SERVER_PUBLIC_IP="<YOUR_SERVER_PUBLIC_IP>"
else
  info "公网 IP: ${CYAN}${SERVER_PUBLIC_IP}${NC} ✓"
fi

# ======================== 7. 生成 Server 配置文件 ========================
info "生成 Server 配置文件..."

cat >"${WG_DIR}/wg0.conf" <<EOF
[Interface]
PrivateKey = ${SERVER_PRIV}
Address = ${SERVER_IP}/24
ListenPort = ${WG_PORT}

PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o ${DEFAULT_IFACE} -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o ${DEFAULT_IFACE} -j MASQUERADE

# --- Peer: Phone ---
[Peer]
PublicKey = ${PHONE_PUB}
AllowedIPs = ${PHONE_IP}/32

# --- Peer: Jump ---
[Peer]
PublicKey = ${JUMP_PUB}
AllowedIPs = ${JUMP_IP}/32,${LAN_SUBNETS}
EOF

chmod 600 "${WG_DIR}/wg0.conf"
info "Server 配置已写入 ${WG_DIR}/wg0.conf ✓"

# ======================== 8. 生成 Phone 配置文件(供导入小火箭) ========================
info "生成 Phone 配置文件..."

cat >"${WG_DIR}/phone.conf" <<EOF
[Interface]
PrivateKey = ${PHONE_PRIV}
Address = ${PHONE_IP}/24
DNS = 1.1.1.1, 8.8.8.8

[Peer]
PublicKey = ${SERVER_PUB}
Endpoint = ${SERVER_PUBLIC_IP}:${WG_PORT}
AllowedIPs = ${WG_SUBNET}.0/24,${LAN_SUBNETS}
PersistentKeepalive = 25
EOF

chmod 600 "${WG_DIR}/phone.conf"
info "Phone 配置已写入 ${WG_DIR}/phone.conf ✓"

# ======================== 9. 生成 Jump 配置文件 ========================
info "生成 Jump 配置文件..."

cat >"${WG_DIR}/jump.conf" <<EOF
[Interface]
PrivateKey = ${JUMP_PRIV}
Address = ${JUMP_IP}/24
MTU = 1420

# 注意:如果跳板机的网卡名不同,请手动修改下面的网卡名
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o ${DEFAULT_IFACE} -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o ${DEFAULT_IFACE} -j MASQUERADE

[Peer]
PublicKey = ${SERVER_PUB}
Endpoint = ${SERVER_PUBLIC_IP}:${WG_PORT}
AllowedIPs = ${WG_SUBNET}.0/24
PersistentKeepalive = 25
EOF

chmod 600 "${WG_DIR}/jump.conf"
info "Jump 配置已写入 ${WG_DIR}/jump.conf ✓"

# ======================== 10. 输出汇总信息 ========================
echo ""
echo -e "${CYAN}======================================${NC}"
echo -e "${CYAN}   部署完成!配置汇总${NC}"
echo -e "${CYAN}======================================${NC}"
echo ""
echo -e "  默认网卡:     ${GREEN}${DEFAULT_IFACE}${NC}"
echo -e "  监听端口:     ${GREEN}${WG_PORT}/UDP${NC}"
echo -e "  隧道网段:     ${GREEN}${WG_SUBNET}.0/24${NC}"
echo -e "  公网 IP:      ${GREEN}${SERVER_PUBLIC_IP}${NC}"
echo ""
echo -e "  ${CYAN}[IP 分配]${NC}"
echo -e "  Server:       ${GREEN}${SERVER_IP}${NC}"
echo -e "  Phone:        ${GREEN}${PHONE_IP}${NC}"
echo -e "  Jump:         ${GREEN}${JUMP_IP}${NC}"
echo ""
echo -e "  ${CYAN}[配置文件位置]${NC}"
echo -e "  Server:       ${GREEN}${WG_DIR}/wg0.conf${NC}       ← 本机使用"
echo -e "  Phone:        ${GREEN}${WG_DIR}/phone.conf${NC}     ← 导入小火箭"
echo -e "  Jump:         ${GREEN}${WG_DIR}/jump.conf${NC}      ← 拷贝到跳板机"
echo ""
echo -e "  ${CYAN}[密钥文件位置]${NC}"
echo -e "  ${GREEN}${KEY_DIR}/${NC}"
echo ""
echo -e "  ${CYAN}[下一步操作]${NC}"
echo -e "  1. 启动 Server:  ${YELLOW}sudo wg-quick up wg0${NC}"
echo -e "  2. 开机自启:     ${YELLOW}sudo systemctl enable [email protected]${NC}"
echo -e "  3. 将 ${GREEN}jump.conf${NC} 拷贝到跳板机的 ${GREEN}/etc/wireguard/wg0.conf${NC}"
echo -e "     注意修改配置中的网卡名为跳板机实际的网卡名"
echo -e "  4. 根据 ${GREEN}phone.conf${NC} 的内容到小火箭中配置WireGuard"
echo -e "  5. 在小火箭中添加分流规则: ${YELLOW}${WG_SUBNET}.0/24${NC}${YELLOW}${LAN_SUBNETS}${NC}"
echo ""
echo -e "${CYAN}======================================${NC}"
echo -e "${GREEN}  记得在云主机安全组中放行 UDP ${WG_PORT} 端口!${NC}"
echo -e "${CYAN}======================================${NC}"
echo ""

脚本支持通过环境变量自定义参数,例如:

# 自定义端口和局域网网段
sudo WG_PORT=51820 LAN_SUBNETS="192.168.1.0/24,192.168.50.0/24" bash wireguard_setup.sh