Configurando OpenWRT #2: ProtonVPN

No post anterior, mostrei a primeira coisa que eu faço quando instalo ou atualizo o meu OpenWRT (claro que desconsiderando as configurações de wifi). Após isso, algo que implementei recentemente e que vou manter, foi a integração com a ProtonVPN. Eu pago o plano anual deles (Proton Unlimited) e considero uma das poucas assinaturas que são realmente boas, principalmente pelo SimpleLogin, mas isso é assunto para outro post.

A integração com a ProtonVPN basicamente faz todo o tráfego da rede passar pela VPN. Na verdade, isso não é o ideal e nem necessário em termos de privacidade, ainda tem coisas que é bom acessarmos pela ISP normal, então no próximo post irei mostrar outra configuração para selecionar o quê passar pela VPN.

Em resumo, o que iremos configurar é uma interface Wireguard VPN usando a ProtonVPN como conexão remota.

ProtonVPN

Para configurar, precisamos do .conf wireguard da Proton. Eles fornecem a chave privada necessária para a conexão com eles funcionar. Essa parte podem seguir o tutorial deles: https://protonvpn.com/support/wireguard-configurations. Eu selecionei apenas a opção VPN Accelerator.

Após gerar o arquivo, cole o conteúdo para um arquivo dentro do roteador, pode ser em /root/proton.conf.

Configuração

O primeiro passo para configurar o OpenWRT é instalar os pacotes:

apk update
apk add kmod-wireguard wireguard-tools

Vamos criar backups das configurações atuais caso dê algum problema, sempre é bom se precaver. O restore seria apenas fazer a cópia inversa.

cp /etc/config/network "/etc/config/network.bak.$(date +%s)"
cp /etc/config/firewall "/etc/config/firewall.bak.$(date +%s)"

O código abaixo faz o parsing do arquivo para usarmos como variáveis no restante dos comandos:

conf_get() {
  grep -m1 "^[[:space:]]*$1[[:space:]]*=" "$WG_CONF" \
    | cut -d= -f2- \
    | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
}

PRIVATE_KEY="$(conf_get PrivateKey)"
ADDRESSES="$(conf_get Address)"
PEER_PUBLIC_KEY="$(conf_get PublicKey)"
ALLOWED_IPS="$(conf_get AllowedIPs)"
ENDPOINT="$(conf_get Endpoint)"
KEEPALIVE="$(conf_get PersistentKeepalive || true)"
ENDPOINT_HOST="${ENDPOINT%:*}"
ENDPOINT_PORT="${ENDPOINT##*:}"
ENDPOINT_HOST="$(echo "$ENDPOINT_HOST" | sed 's/^\[//; s/\]$//')"
KEEPALIVE="${KEEPALIVE:-25}"

Interface

O openwrt faz a conexão através da interface, precisamos criá-la configurando com a chave privada da Proton. O nome proton0 pode ser substituído.

uci -q delete network.proton0 || true
uci set network.proton0="interface"
uci set network.proton0.proto="wireguard"
uci set network.proton0.private_key="$PRIVATE_KEY"
uci set network.proton0.mtu="1420"

echo "$ADDRESSES" | tr ',' '\n' | while read addr; do
  addr="$(echo "$addr" | tr -d ' ')"
  [ -n "$addr" ] && uci add_list network.proton0.addresses="$addr"
done

Agora precisamos criar o peer wireguard usado pela interface com a chave publica e com o endpoint:

uci -q delete network.wgproton || true
uci set network.wgproton="wireguard_proton0"
uci set network.wgproton.public_key="$PEER_PUBLIC_KEY"
uci set network.wgproton.endpoint_host="$ENDPOINT_HOST"
uci set network.wgproton.endpoint_port="$ENDPOINT_PORT"
uci set network.wgproton.persistent_keepalive="$KEEPALIVE"
echo "$ALLOWED_IPS" | tr ',' '\n' | while read ip; do
  ip="$(echo "$ip" | tr -d ' ')"
  [ -n "$ip" ] && uci add_list network.wgproton.allowed_ips="$ip"
done

O comando abaixo é o mais importante. Não queremos que todo o tráfego da rede passe pela VPN. Caso coloque 1, o OpenWRT irá configurar para que essa seja a rota padrão para toda a rede, o que não é o que queremos. Queremos apenas parte do tráfego passando por este túnel. Então, deixe como falso (0):

uci set network.wgproton.route_allowed_ips="0"

Agora podemos salvar:

uci commit network

Firewall

A conexão agora já está funcionando, mas ainda resta configurar o firewall, pois o OpenWRT precisa saber que a LAN pode encaminhar o tráfego para a interface proton0. Então, precisamos criar a zona (nomeada aqui vpn) e e integrá-la com a interface proton0.

O comando masq=1 ativa o NAT para que os IPs dos clientes ao chegar no lado da Proton sejam substituídos para IPs contidos na subnet da proton, e não permaneçam com o IP da sua rede interna.

uci -q delete firewall.vpn || true
uci set firewall.vpn="zone"
uci set firewall.vpn.name="vpn"
uci set firewall.vpn.input="REJECT"
uci set firewall.vpn.output="ACCEPT"
uci set firewall.vpn.forward="REJECT"
uci set firewall.vpn.masq="1"
uci set firewall.vpn.mtu_fix="1"
uci add_list firewall.vpn.network="proton0"

O bloco abaixo permite que clientes da zona lan encaminhem dados para a zona vpn:

uci -q delete firewall.lan_vpn || true
uci set firewall.lan_vpn="forwarding"
uci set firewall.lan_vpn.src="lan"
uci set firewall.lan_vpn.dest="vpn"

Agora está tudo pronto e podemos reiniciar os serviços para ativá-los:

uci commit firewall
/etc/init.d/network reload
/etc/init.d/firewall restart

Verificação final

Vamos verificar se deu tudo certo. Primeiramente, cheque se a interface subiu com o comando abaixo. Se o campo up estiver true, é porque a interface subiu como esperado:

ifstatus proton0

ifstatus

O comando abaixo mostra o status do handshake wireguard com a Proton. Se mostrar a linha de latest handshake é porque a conexão está funcionando.

wg show proton0

wgshow

Conclusão

Com isso, foi configurado a integração com a ProtonVPN e preparamos o ambiente para usarmos PBR no próximo post. Abaixo coloco todos os comandos num script. Deixo aqui até mesmo para eu copiar nas próximas vezes que eu precisar usar. Assim, eu só executo ele e configura tudo:

./setup-proton.sh
#!/bin/sh
set -eu

WG_CONF="${1:-/root/proton.conf}"

if [ ! -f "$WG_CONF" ]; then
  echo "Config not found: $WG_CONF"
  echo "Usage: $0 /path/to/proton.conf"
  exit 1
fi

conf_get() {
  grep -m1 "^[[:space:]]*$1[[:space:]]*=" "$WG_CONF" \
    | cut -d= -f2- \
    | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
}

echo "Installing WireGuard packages..."
apk update
apk add kmod-wireguard wireguard-tools

echo "Backing up configs..."
cp /etc/config/network "/etc/config/network.bak.$(date +%s)"
cp /etc/config/firewall "/etc/config/firewall.bak.$(date +%s)"

PRIVATE_KEY="$(conf_get PrivateKey)"
ADDRESSES="$(conf_get Address)"
PEER_PUBLIC_KEY="$(conf_get PublicKey)"
ALLOWED_IPS="$(conf_get AllowedIPs)"
ENDPOINT="$(conf_get Endpoint)"
KEEPALIVE="$(conf_get PersistentKeepalive || true)"

ENDPOINT_HOST="${ENDPOINT%:*}"
ENDPOINT_PORT="${ENDPOINT##*:}"
ENDPOINT_HOST="$(echo "$ENDPOINT_HOST" | sed 's/^\[//; s/\]$//')"
KEEPALIVE="${KEEPALIVE:-25}"

if [ -z "$PRIVATE_KEY" ] || [ -z "$ADDRESSES" ] || [ -z "$PEER_PUBLIC_KEY" ] || [ -z "$ALLOWED_IPS" ] || [ -z "$ENDPOINT" ]; then
  echo "Failed to parse required values from $WG_CONF"
  exit 1
fi

echo "Configuring network interface proton0..."

uci -q delete network.proton0 || true
uci set network.proton0="interface"
uci set network.proton0.proto="wireguard"
uci set network.proton0.private_key="$PRIVATE_KEY"
uci set network.proton0.mtu="1420"

echo "$ADDRESSES" | tr ',' '\n' | while read addr; do
  addr="$(echo "$addr" | tr -d ' ')"
  [ -n "$addr" ] && uci add_list network.proton0.addresses="$addr"
done

uci -q delete network.wgproton || true
uci set network.wgproton="wireguard_proton0"
uci set network.wgproton.public_key="$PEER_PUBLIC_KEY"
uci set network.wgproton.endpoint_host="$ENDPOINT_HOST"
uci set network.wgproton.endpoint_port="$ENDPOINT_PORT"
uci set network.wgproton.persistent_keepalive="$KEEPALIVE"

# Important for PBR:
# AllowedIPs permits the tunnel to carry full traffic,
# but route_allowed_ips=0 prevents OpenWrt from making it the default route.
uci set network.wgproton.route_allowed_ips="0"

echo "$ALLOWED_IPS" | tr ',' '\n' | while read ip; do
  ip="$(echo "$ip" | tr -d ' ')"
  [ -n "$ip" ] && uci add_list network.wgproton.allowed_ips="$ip"
done

uci commit network

echo "Configuring firewall zone for proton0..."

uci -q delete firewall.vpn || true
uci set firewall.vpn="zone"
uci set firewall.vpn.name="vpn"
uci set firewall.vpn.input="REJECT"
uci set firewall.vpn.output="ACCEPT"
uci set firewall.vpn.forward="REJECT"
uci set firewall.vpn.masq="1"
uci set firewall.vpn.mtu_fix="1"
uci add_list firewall.vpn.network="proton0"

uci -q delete firewall.lan_vpn || true
uci set firewall.lan_vpn="forwarding"
uci set firewall.lan_vpn.src="lan"
uci set firewall.lan_vpn.dest="vpn"

uci commit firewall

echo "Applying changes..."
/etc/init.d/network reload
/etc/init.d/firewall restart

echo "Done."
echo
echo "Check tunnel status with:"
echo "  wg show"
echo "  ifstatus proton0"
echo
echo "PBR note:"
echo "  Use interface proton0 in your PBR policies."
echo "  This script did not change WAN DNS."
echo "  This script did not remove lan->wan forwarding."