安装与配置 OpenConnect (ocserv) 服务端
为了远程访问家中的 NAS 与内网设备,之前主要使用 WireGuard 与 OpenVPN。近期因运营商 QoS 收紧,两者陆续被限制在 3Mbps 左右。考虑到 OpenConnect 协议兼容 Cisco AnyConnect / Secure Client,常用于企业办公场景,实测目前未受限速影响,因此在 OpenWrt 上部署了 ocserv 服务端。
安装
在 Web 界面进入 系统 -> 软件包,搜索并安装 luci-app-ocserv(会自动安装依赖 ocserv);或直接在终端中安装:
opkg update
opkg install ocserv luci-app-ocserv
基础配置
Web 界面基础设置
进入 VPN -> OpenConnect,进行以下基础配置:
- 启用服务:勾选
- VPN IPv4 配置:填写一个与现有内网不冲突的网段(如
192.168.100.1) - DNS 服务器:填写当前路由器 IP
- (可选)路由表:若只需访问内网,填入内网网段(如
192.168.1.0/24);留空则默认全局代理。注:该选项取决于客户端是否支持与遵循服务端推送的路由。

防火墙配置
由于 OpenWrt 24/25 的 Web 界面限制设备名称输入 + 通配符,建议直接通过 UCI 命令行配置防火墙:
# 1. 创建 vpn 区域并绑定设备 vpns+
uci add firewall zone
uci set firewall.@zone[-1].name='ocserv'
uci set firewall.@zone[-1].input='ACCEPT'
uci set firewall.@zone[-1].output='ACCEPT'
uci set firewall.@zone[-1].forward='ACCEPT'
uci set firewall.@zone[-1].masq='1'
uci set firewall.@zone[-1].mtu_fix='1'
uci add_list firewall.@zone[-1].device='vpns+'
# 2. 允许 vpn 转发到 wan(外网)和 lan(内网)
uci add firewall forwarding
uci set firewall.@forwarding[-1].src='ocserv'
uci set firewall.@forwarding[-1].dest='wan'
uci add firewall forwarding
uci set firewall.@forwarding[-1].src='ocserv'
uci set firewall.@forwarding[-1].dest='lan'
# 3. 放行 WAN 口的 ocserv 端口(默认 443 TCP/UDP)
uci add firewall rule
uci set firewall.@rule[-1].name='Allow-ocserv'
uci set firewall.@rule[-1].src='wan'
uci set firewall.@rule[-1].proto='tcp udp'
uci set firewall.@rule[-1].dest_port='4443'
uci set firewall.@rule[-1].target='ACCEPT'
# 4. 提交并重启防火墙
uci commit firewall
/etc/init.d/firewall restart
说明:使用vpns+是因为客户端连接后会动态生成vpns0、vpns1等虚拟网卡接口,+号用于通配所有连接。


为什么要使用 vpns+ 这个设备呢,因为客户端连接后会动态生成名为 vpns0、vpns1的虚拟网卡接口。类似下图

认证方式
方式一:用户名与密码认证(默认)
在 Web 界面或 /etc/config/ocserv 中添加用户即可。
常见排错:提示密码错误
检查 /var/etc/ocpasswd。若生成的密码为明文,属于部分版本的 LuCI 保存 Bug,导致认证失败。此时需手动生成加密哈希:
# 生成 SHA-512 哈希
openssl passwd -6 '<password>'
# 或直接使用 ocpasswd 命令行工具
# 再到生成的 /etc/ocserv/ocpasswd 文件中查看密码
ocpasswd -c /etc/ocserv/ocpasswd <username>
界面上修改或者直接在 /etc/config/ocserv文件中进行修改
config ocservusers
option name '<username>'
option password '<encoded-password-hash>'
方式二:客户端证书 p12 认证(免密登录)
推荐使用证书认证,方便一键连接。
1. 创建证书生成脚本 generate_openconnect_certs.sh
#!/bin/sh
# ==============================================================================
# ocserv Passwordless Certificate Management Script
# Purpose: Initialize private CA, auto-copy CA to /etc/ocserv/ssl, generate .p12
# Compatible with: OpenWrt (ash/sh) & standard Linux environments (bash)
# ==============================================================================
set -eu
# Resolve base directory relative to the script location
BASE_DIR="$(cd "$(dirname "$0")" && pwd)/vpn-certs"
CA_DIR="${BASE_DIR}/ca"
CLIENTS_DIR="${BASE_DIR}/clients"
CA_KEY="${CA_DIR}/vpn-ca-key.pem"
CA_CERT="${CA_DIR}/vpn-ca-cert.pem"
# Target destination in OpenWrt
OCSERV_SSL_DIR="/etc/ocserv/ssl"
# Validity in days
CA_DAYS=3650
CLIENT_DAYS=1825
# ANSI Color output
GREEN='\033[0;32m'
CYAN='\033[0;36m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
log_info() { printf "${CYAN}[INFO]${NC} %s\n" "$1"; }
log_succ() { printf "${GREEN}[SUCCESS]${NC} %s\n" "$1"; }
log_warn() { printf "${YELLOW}[WARN]${NC} %s\n" "$1"; }
log_err() { printf "${RED}[ERROR]${NC} %s\n" "$1"; }
# 1. Initialize dedicated client-signing CA
init_ca() {
if [ -f "$CA_KEY" ] && [ -f "$CA_CERT" ]; then
log_warn "CA already exists at: ${CA_DIR}."
else
mkdir -p "$CA_DIR" "$CLIENTS_DIR"
chmod 700 "$CA_DIR"
log_info "Generating VPN private CA key and certificate..."
openssl genrsa -out "$CA_KEY" 4096 2>/dev/null
chmod 400 "$CA_KEY"
openssl req -x509 -new -nodes -key "$CA_KEY" -days "$CA_DAYS" -out "$CA_CERT" \
-subj "/CN=My-VPN-Client-CA/O=VPN-Infrastructure" 2>/dev/null
log_succ "CA initialized successfully!"
fi
# Ensure target SSL directory exists and copy CA cert
mkdir -p "$OCSERV_SSL_DIR"
cp -f "$CA_CERT" "${OCSERV_SSL_DIR}/ca.pem"
log_succ "Copied CA certificate to: ${OCSERV_SSL_DIR}/ca.pem"
echo ""
log_info "Setup Summary:"
echo " - Local CA Cert : ${CA_CERT}"
echo " - ocserv CA Cert: ${OCSERV_SSL_DIR}/ca.pem"
echo " - Configure ocserv.conf with: ca-cert = ${OCSERV_SSL_DIR}/ca.pem"
}
# 2. Issue client certificate & export .p12
create_client() {
username="${1:-}"
p12_pass="${2:-}"
if [ -z "$username" ]; then
log_err "Error: Username is required!"
echo "Usage: $0 client <username> [p12_export_password]"
exit 1
fi
if [ ! -f "$CA_CERT" ] || [ ! -f "$CA_KEY" ]; then
log_err "CA certificates not found. Please run '$0 init' first."
exit 1
fi
user_dir="${CLIENTS_DIR}/${username}"
mkdir -p "$user_dir"
user_key="${user_dir}/${username}-key.pem"
user_csr="${user_dir}/${username}.csr"
user_cert="${user_dir}/${username}-cert.pem"
user_p12="${user_dir}/${username}.p12"
ext_cnf="${user_dir}/client-ext.cnf"
log_info "Generating client private key and CSR for user [${username}]..."
openssl genrsa -out "$user_key" 2048 2>/dev/null
# CN matches the username, which ocserv extracts via cert-user-oid
openssl req -new -key "$user_key" -out "$user_csr" \
-subj "/CN=${username}/O=VPN-Client-Users" 2>/dev/null
# Client certificate extensions (clientAuth)
cat <<EOF > "$ext_cnf"
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
EOF
log_info "Signing client certificate with CA (${CLIENT_DAYS} days validity)..."
openssl x509 -req -in "$user_csr" -CA "$CA_CERT" -CAkey "$CA_KEY" \
-CAcreateserial -out "$user_cert" -days "$CLIENT_DAYS" -extfile "$ext_cnf" 2>/dev/null
log_info "Exporting to PKCS#12 (.p12) bundle..."
if [ -n "$p12_pass" ]; then
openssl pkcs12 -export -inkey "$user_key" -in "$user_cert" \
-certfile "$CA_CERT" -name "$username" -out "$user_p12" -passout "pass:${p12_pass}" 2>/dev/null
else
openssl pkcs12 -export -inkey "$user_key" -in "$user_cert" \
-certfile "$CA_CERT" -name "$username" -out "$user_p12" -passout "pass:" 2>/dev/null
fi
rm -f "$user_csr" "$ext_cnf"
echo ""
log_succ "Certificate bundle for [${username}] generated successfully!"
echo "=================================================="
printf " Client Package: ${GREEN}%s${NC}\n" "${user_p12}"
if [ -n "$p12_pass" ]; then
printf " .p12 Password: ${YELLOW}%s${NC}\n" "${p12_pass}"
else
printf " .p12 Password: ${YELLOW}(None / Empty)${NC}\n"
fi
echo "=================================================="
echo "Instructions:"
echo "1. Transfer '${username}.p12' to your client device (iOS/Android/macOS/Windows)."
echo "2. Import the certificate into the system keychain or AnyConnect app."
echo "3. Connect using AnyConnect without entering any username or password."
}
# 3. Entrypoint
action="${1:-}"
case "$action" in
init)
init_ca
;;
client)
create_client "${2:-}" "${3:-}"
;;
*)
echo "=================================================="
echo " ocserv Passwordless Certificate Manager"
echo "=================================================="
echo "Usage:"
echo " $0 init - Initialize CA & auto-copy ca.pem to /etc/ocserv/ssl"
echo " $0 client <user> [pass] - Issue a client certificate bundle (.p12)"
echo ""
echo "Examples:"
echo " $0 init"
echo " $0 client alice"
echo " $0 client bob MySecretPassword"
echo "=================================================="
exit 1
;;
esac
2. 生成证书
chmod +x generate_openconnect_certs.sh
./generate_openconnect_certs.sh init
./generate_openconnect_certs.sh client <name> <password>
提示:建议设置密码,部分 AnyConnect 客户端在导入无密码的.p12时会报错。生成的证书位于~/vpn-certs/clients/<username>/<username>.p12。
3. 修改服务端模板配置
修改 /etc/ocserv/ocserv.conf.template 文件的以下内容
auth = "certificate"
# 以下两个 SSL 证书文件在 /etc/ocserv 下也有默认的,这两个是因为有签署的正式证书
server-cert = /etc/ocserv/ssl/cert.pem
server-key = /etc/ocserv/ssl/key.pem
ca-cert = /etc/ocserv/ssl/ca.pem
cert-user-oid = 2.5.4.3
重启服务:
/etc/init.d/ocserv restart
4. 客户端配置 (Cisco Secure Client / AnyConnect)
- 将生成的
.p12文件传输至客户端设备。 - 打开 Cisco Secure Client,进入 诊断 (Diagnostics) -> 证书 (Certificate) 导入证书。
macOS 和 iOS 注意事项: 双击.p12文件会直接导入到系统钥匙串(Keychains),Cisco Secure Client 无法直接读取。 解决方法:在访达(Finder)中右键点击.p12文件 -> 共享 (Share) -> 选择 Cisco Secure Client 进行导入。
