mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-08-10 23:22:29 +02:00
qBittorrent upnp and firewall for VPN
This is implementation of the UPnP port opening for qBittorrent running on VPN. Implementation also includes simple firewall for incoming connections.
This commit is contained in:
@@ -40,6 +40,7 @@ _parse_config() {
|
||||
}
|
||||
|
||||
_parse_dns() {
|
||||
local dns_ip
|
||||
local -a dns_conf=()
|
||||
local -a dns_backup_ipv4=("8.8.8.8" "1.1.1.1")
|
||||
local -a dns_backup_ipv6=("2001:4860:4860::8888" "2606:4700:4700::1111")
|
||||
@@ -155,19 +156,24 @@ _routing_add() {
|
||||
local local_ipv6=$(ip addr show ${config["Interface"]} | grep 'inet6 ' | awk '{print $2}' | cut -d'/' -f1)
|
||||
local ipv4
|
||||
local ipv6
|
||||
local dns_ip
|
||||
|
||||
# add routing rules for local IPs
|
||||
for ipv4 in ${local_ipv4}; do
|
||||
config["IPv4Enabled"]="true"
|
||||
_cmd "ip -4 route add default dev ${config["Interface"]} table ${config["Table"]}" || return 1
|
||||
_cmd "ip -4 rule add priority 1 from ${ipv4} table ${config["Table"]}" || return 1
|
||||
_cmd "ip -4 rule add priority 1 to ${ipv4}/24 table ${config["Table"]}" || return 1
|
||||
done
|
||||
if [ "${config["IPv4Enabled"]}" = "true" ]; then
|
||||
_cmd "ip -4 route add default dev ${config["Interface"]} table ${config["Table"]}" || return 1
|
||||
fi
|
||||
for ipv6 in ${local_ipv6}; do
|
||||
config["IPv6Enabled"]="true"
|
||||
_cmd "ip -6 rule add priority 1 from ${ipv6} table ${config["Table"]}" || return 1
|
||||
_cmd "ip -6 rule add priority 1 to ${ipv6}/64 table ${config["Table"]}" || return 1
|
||||
done
|
||||
if [ "${config["IPv6Enabled"]}" = "true" ]; then
|
||||
_cmd "ip -6 route add default dev ${config["Interface"]} table ${config["Table"]}" || true
|
||||
_cmd "ip -6 route add default dev ${config["Interface"]} table ${config["Table"]}" || return 1
|
||||
fi
|
||||
|
||||
# get valid DNS servers
|
||||
@@ -182,15 +188,11 @@ _routing_add() {
|
||||
#_cmd "ip -6 route add ${dns_ip} dev ${config["Interface"]}" || return 1
|
||||
_cmd "ip -6 rule add priority 1 to ${dns_ip} table ${config["Table"]}" || return 1
|
||||
done
|
||||
|
||||
# Update resolv.conf with VPN DNS servers
|
||||
_resolvconf "update"
|
||||
}
|
||||
|
||||
_routing_del() {
|
||||
bashio::log.info "Removing routing rules for VPN interface ${config["Interface"]}..."
|
||||
|
||||
_resolvconf "reset"
|
||||
while _cmd "ip -4 rule del priority 1 from all table ${config["Table"]} 2>/dev/null"; do :; done
|
||||
while _cmd "ip -4 rule del priority 1 to all table ${config["Table"]} 2>/dev/null"; do :; done
|
||||
while _cmd "ip -4 route del default dev ${config["Interface"]} table ${config["Table"]} 2>/dev/null"; do :; done
|
||||
@@ -199,20 +201,52 @@ _routing_del() {
|
||||
while _cmd "ip -6 route del default dev ${config["Interface"]} table ${config["Table"]} 2>/dev/null"; do :; done
|
||||
}
|
||||
|
||||
# --- Firewall Specific Functions ---
|
||||
|
||||
_firewall_add() {
|
||||
if [ "${config["IPv4Enabled"]}" = "true" ]; then
|
||||
_cmd "iptables -N pnat" || return 1
|
||||
_cmd "iptables -A INPUT -i ${config["Interface"]} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT" || return 1
|
||||
_cmd "iptables -A INPUT -i ${config["Interface"]} -p icmp -j ACCEPT" || return 1
|
||||
_cmd "iptables -A INPUT -i ${config["Interface"]} -j pnat" || return 1
|
||||
_cmd "iptables -A INPUT -i ${config["Interface"]} -j DROP" || return 1
|
||||
fi
|
||||
|
||||
if [ "${config["IPv6Enabled"]}" = "true" ]; then
|
||||
_cmd "ip6tables -N pnat" || return 1
|
||||
_cmd "ip6tables -A INPUT -i ${config["Interface"]} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT" || return 1
|
||||
_cmd "ip6tables -A INPUT -i ${config["Interface"]} -p icmpv6 -j ACCEPT" || return 1
|
||||
_cmd "ip6tables -A INPUT -i ${config["Interface"]} -j pnat" || return 1
|
||||
_cmd "ip6tables -A INPUT -i ${config["Interface"]} -j DROP" || return 1
|
||||
fi
|
||||
}
|
||||
|
||||
_firewall_del() {
|
||||
if [ "${config["IPv4Enabled"]}" = "true" ]; then
|
||||
_cmd "iptables -F INPUT" || true
|
||||
_cmd "iptables -F pnat" || true
|
||||
_cmd "iptables -X pnat" || true
|
||||
fi
|
||||
|
||||
if [ "${config["IPv6Enabled"]}" = "true" ]; then
|
||||
_cmd "ip6tables -F INPUT" || true
|
||||
_cmd "ip6tables -F pnat" || true
|
||||
_cmd "ip6tables -X pnat" || true
|
||||
fi
|
||||
}
|
||||
|
||||
# --- WireGuard Specific Logic ---
|
||||
|
||||
_wg_wait_handshake() {
|
||||
_wireguard_check() {
|
||||
local timeout="${1:-20}"
|
||||
local iface="${config["Interface"]}"
|
||||
local peer_pk="${config["PublicKey"]}"
|
||||
local deadline ts
|
||||
|
||||
deadline=$(( $(date +%s) + timeout ))
|
||||
|
||||
while [ "$(date +%s)" -lt "${deadline}" ]; do
|
||||
ping -I "${iface}" -c1 -W1 1.1.1.1 >/dev/null 2>&1 || true
|
||||
ping -I "${config["Interface"]}" -c1 -W1 1.1.1.1 >/dev/null 2>&1 || true
|
||||
|
||||
ts="$(wg show "${iface}" latest-handshakes 2>/dev/null | awk -v pk="${peer_pk}" '$1==pk{print $2; exit}')"
|
||||
ts="$(wg show "${config["Interface"]}" latest-handshakes 2>/dev/null | awk -v pk="${config["PublicKey"]}" '$1==pk{print $2; exit}')"
|
||||
if [ -n "${ts}" ] && [ "${ts}" -gt 0 ] 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
@@ -220,17 +254,25 @@ _wg_wait_handshake() {
|
||||
done
|
||||
|
||||
bashio::log.error "WireGuard handshake not established after ${timeout}s (latest-handshake=${ts:-0})."
|
||||
wg show "${iface}" 2>&1 | while IFS= read -r l; do bashio::log.error "${l}"; done
|
||||
wg show "${config["Interface"]}" 2>&1 | while IFS= read -r l; do bashio::log.error "${l}"; done
|
||||
return 1
|
||||
}
|
||||
|
||||
_wireguard_up() {
|
||||
local local_ip
|
||||
local -a local_ips=()
|
||||
local -A local_ip_types=()
|
||||
local allowed_ip
|
||||
local -a allowed_ips=()
|
||||
local -A allowed_ip_types=()
|
||||
local key
|
||||
|
||||
bashio::log.warning "This script force Wireguard to ignore any routes and DNS settings."
|
||||
bashio::log.warning "Default route will be inserted into custom routing table: ${config["Table"]}"
|
||||
bashio::log.warning "This routing table will be used for traffic from the VPN interface and to the configured DNS servers."
|
||||
bashio::log.warning "Qbittorrent bittorrent client shall be set to use the VPN interface ${config["Interface"]} only."
|
||||
|
||||
for key in "Interface" "ListenPort" "PrivateKey" "PublicKey" "EndpointIP" "EndpointPort" ; do
|
||||
for key in "Interface" "ListenPort" "PrivateKey" "PublicKey" "EndpointIP" "EndpointPort" "Address"; do
|
||||
if [ ! -v config[$key] ] || [ -z "${config[$key]}" ]; then
|
||||
bashio::log.error "Missing required WireGuard configuration parameter: ${key}"
|
||||
return 1
|
||||
@@ -238,28 +280,51 @@ _wireguard_up() {
|
||||
done
|
||||
|
||||
_cmd "ip link add ${config["Interface"]} type wireguard" || return 1
|
||||
local allowed_ips=""
|
||||
local -a local_ips=()
|
||||
|
||||
mapfile -d ',' -t local_ips < <(echo "${config["Address"]}" | tr -d ' ')
|
||||
for local_ip in ${local_ips[@]}; do
|
||||
local result=0
|
||||
_check_host "${local_ip}" || result=$?
|
||||
if [ "${result}" -eq 1 ]; then
|
||||
allowed_ips="${allowed_ips},0.0.0.0/0"
|
||||
config["IPv4Enabled"]="true"
|
||||
local_ip_types["${local_ip}"]="ipv4"
|
||||
allowed_ip_types["0.0.0.0/0"]="ipv4"
|
||||
_cmd "ip addr add ${local_ip} dev ${config["Interface"]}" || return 1
|
||||
elif [ "${result}" -eq 2 ]; then
|
||||
allowed_ips="${allowed_ips},::/0"
|
||||
config["IPv6Enabled"]="true"
|
||||
local_ip_types["${local_ip}"]="ipv6"
|
||||
_cmd "ip addr add ${local_ip} dev ${config["Interface"]}" || return 1
|
||||
else
|
||||
bashio::log.warning "Ignoring invalid local IP address: ${local_ip}"
|
||||
fi
|
||||
done
|
||||
allowed_ips="${allowed_ips#,}"
|
||||
if [ -z "${allowed_ips}" ]; then
|
||||
bashio::log.error "No valid local IP addresses configured."
|
||||
if [ ${#local_ip_types[@]} -eq 0 ]; then
|
||||
bashio::log.error "No valid local IP addresses configured for WireGuard interface."
|
||||
return 1
|
||||
fi
|
||||
|
||||
mapfile -d ',' -t allowed_ips < <(echo "${config["Address"]}" | tr -d ' ')
|
||||
for allowed_ip in ${allowed_ips[@]}; do
|
||||
local result=0
|
||||
_check_host "${allowed_ip}" || result=$?
|
||||
if [ "${result}" -eq 1 ] && [ "${config["IPv4Enabled"]}" == "true" ]; then
|
||||
allowed_ip_types["${allowed_ip}"]="ipv4"
|
||||
#allowed_ip_types["0.0.0.0/0"]="ipv4"
|
||||
elif [ "${result}" -eq 2 ] && [ "${config["IPv6Enabled"]}" == "true" ]; then
|
||||
allowed_ip_types["${allowed_ip}"]="ipv6"
|
||||
#allowed_ip_types["::/0"]="ipv6"
|
||||
else
|
||||
bashio::log.error "Invalid allowed IP address: ${allowed_ip}"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
if [ ${#allowed_ip_types[@]} -eq 0 ]; then
|
||||
bashio::log.error "No valid allowed IP addresses configured for WireGuard peer."
|
||||
return 1
|
||||
fi
|
||||
printf -v allowed_ips '%s,' "${!allowed_ip_types[@]}"
|
||||
allowed_ips="${allowed_ips%,}"
|
||||
|
||||
_cmd "wg set ${config["Interface"]} listen-port ${config["ListenPort"]} private-key ${config["PrivateKey"]}" || return 1
|
||||
local endpoint="${config["EndpointIP"]}:${config["EndpointPort"]}"
|
||||
if [[ "${config["EndpointIP"]}" == *:* ]]; then
|
||||
@@ -279,18 +344,32 @@ _wireguard_up() {
|
||||
fi
|
||||
|
||||
_cmd "ip link set ${config["Interface"]} up" || return 1
|
||||
_routing_add
|
||||
_wg_wait_handshake 10 || return 1
|
||||
|
||||
# Add routing rules for VPN interface and DNS servers
|
||||
_routing_add || return 1
|
||||
# Add firewall rules for VPN interface
|
||||
_firewall_add || return 1
|
||||
# Update resolv.conf with VPN DNS servers
|
||||
_resolvconf "update" || return 1
|
||||
# Wait for handshake to be established before returning success
|
||||
_wireguard_check 10 || return 1
|
||||
}
|
||||
|
||||
_wireguard_down() {
|
||||
_routing_del
|
||||
# Update resolv.conf to remove VPN DNS servers
|
||||
_resolvconf "reset" || true
|
||||
# Remove routing rules for VPN interface and DNS servers
|
||||
_routing_del || true
|
||||
# Remove firewall rules for VPN interface
|
||||
_firewall_del || true
|
||||
|
||||
_cmd "ip link set ${config["Interface"]} down" 2>/dev/null || true
|
||||
_cmd "ip link del ${config["Interface"]}" 2>/dev/null || true
|
||||
}
|
||||
|
||||
wireguard() {
|
||||
local mode=$1
|
||||
local key
|
||||
local interface
|
||||
local config_file
|
||||
local WIREGUARD_STATE_DIR="/var/run/wireguard"
|
||||
@@ -328,7 +407,7 @@ wireguard() {
|
||||
printf '%s\n' "${config["PrivateKey"]}" > "${WIREGUARD_STATE_DIR}/privatekey"
|
||||
chmod 600 "${WIREGUARD_STATE_DIR}/privatekey" || true
|
||||
config["PrivateKey"]="${WIREGUARD_STATE_DIR}/privatekey"
|
||||
|
||||
|
||||
if [ -n "${config["PresharedKey"]:-}" ]; then
|
||||
printf '%s\n' "${config["PresharedKey"]}" > "${WIREGUARD_STATE_DIR}/presharedkey"
|
||||
chmod 600 "${WIREGUARD_STATE_DIR}/presharedkey" || true
|
||||
@@ -384,6 +463,23 @@ wireguard() {
|
||||
|
||||
# --- OpenVPN Specific Logic ---
|
||||
|
||||
_openvpn_check() {
|
||||
local timeout="${1:-20}"
|
||||
local deadline ts
|
||||
|
||||
deadline=$(( $(date +%s) + timeout ))
|
||||
|
||||
while [ "$(date +%s)" -lt "${deadline}" ]; do
|
||||
if ip link show "${config["Interface"]}" > /dev/null 2>&1 ; then
|
||||
return 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
bashio::log.error "OpenVPN interface ${config["Interface"]} failed to come up after ${timeout}s."
|
||||
return 1
|
||||
}
|
||||
|
||||
_openvpn_up() {
|
||||
bashio::log.warning "This script force OpenvPN to ignore any routes and DNS settings pushed by the server."
|
||||
bashio::log.warning "Default route will be inserted into custom routing table: ${config["Table"]}"
|
||||
@@ -418,21 +514,31 @@ _openvpn_up() {
|
||||
--route-nopull \
|
||||
--route-noexec" || return 1
|
||||
|
||||
#wait for slow OpenVPN interface to come up
|
||||
for i in {1..10}; do
|
||||
if ip link show "${config["Interface"]}" > /dev/null 2>&1 ; then
|
||||
return 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
bashio::log.error "OpenVPN interface ${config["Interface"]} failed to come up."
|
||||
return 1
|
||||
# Wait for the VPN interface to come up
|
||||
_openvpn_check 30 || return 1
|
||||
}
|
||||
|
||||
_openvpn_down() {
|
||||
# Terminate OpenVPN process
|
||||
pkill -f "openvpn --config ${config["ConfigFile"]}" || true
|
||||
_routing_del
|
||||
}
|
||||
|
||||
_openpvn_postup() {
|
||||
# Add routing rules for VPN interface and DNS servers
|
||||
_routing_add || return 1
|
||||
# Add firewall rules for VPN interface
|
||||
_firewall_add || return 1
|
||||
# Update resolv.conf with VPN DNS servers
|
||||
_resolvconf "update" || return 1
|
||||
}
|
||||
|
||||
_openpvn_postdown() {
|
||||
# Update resolv.conf to remove VPN DNS servers
|
||||
_resolvconf "reset" || true
|
||||
# Remove routing rules for VPN interface and DNS servers
|
||||
_routing_del || true
|
||||
# Remove firewall rules for VPN interface
|
||||
_firewall_del || true
|
||||
}
|
||||
|
||||
openvpn() {
|
||||
@@ -480,10 +586,10 @@ openvpn() {
|
||||
bashio::log.info "OpenVPN on interface ${config["Interface"]} is down."
|
||||
bashio::exit.ok 'OpenVPN stopped.'
|
||||
elif [ "${mode}" = "postup" ]; then
|
||||
_routing_add
|
||||
_openpvn_postup
|
||||
bashio::exit.ok 'OpenVPN routes added.'
|
||||
elif [ "${mode}" = "postdown" ]; then
|
||||
_routing_del
|
||||
_openpvn_postdown
|
||||
bashio::exit.ok 'OpenVPN routes deleted.'
|
||||
else
|
||||
bashio::log.error "Invalid OpenVPN mode specified. Use 'up', 'down', 'postup', or 'postdown'."
|
||||
|
||||
Reference in New Issue
Block a user