Enhance VPN leak monitoring and IP fetching methods

Refactor public IP fetching and VPN leak monitoring logic. Introduce new helper functions for fetching public IP and country code with improved error handling and randomization of URL sources. Update WireGuard and OpenVPN integration to include leak monitoring.
This commit is contained in:
Alexandre
2025-11-24 08:45:37 +01:00
committed by GitHub
parent 6e7308f4e6
commit bda1ffedde

View File

@@ -5,23 +5,134 @@ WEBUI_PORT=${WEBUI_PORT:-8080}
export PATH="/usr/local/sbin:/usr/local/bin:${PATH}" export PATH="/usr/local/sbin:/usr/local/bin:${PATH}"
# --- New helper: get public IP with rate-limiting fallbacks --- # Global variable used by WireGuard bring-up helper
_get_public_ip() { output=""
local ip
ip=$(
curl -fsS --max-time 10 https://ifconfig.co/ip \
|| curl -fsS --max-time 10 https://api64.ipify.org \
|| curl -fsS --max-time 10 https://ipecho.net/plain
) || return 1
printf '%s\n' "${ip}"
}
if bashio::config.true 'silent'; then if bashio::config.true 'silent'; then
sed -i 's|/proc/1/fd/1 hassio;|off;|g' /etc/nginx/nginx.conf sed -i 's|/proc/1/fd/1 hassio;|off;|g' /etc/nginx/nginx.conf
fi fi
_fetch_public_ip() {
local resp
local url
local urls=(
"https://icanhazip.com"
"https://ifconfig.me/ip"
"https://api64.ipify.org"
"https://checkip.amazonaws.com"
"https://domains.google.com/checkip"
"https://ipinfo.io/ip"
)
local shuffled_urls
mapfile -t shuffled_urls < <(printf "%s\n" "${urls[@]}" | shuf)
# Loop through the now-randomized list
for url in "${shuffled_urls[@]}"; do
resp=$(curl -fsS --max-time 5 "${url}" 2>/dev/null || true)
resp="${resp//[[:space:]]/}"
# Validation (IPv4 or IPv6 regex)
if [[ "${resp}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || \
[[ "${resp}" =~ ^[0-9a-fA-F:]+$ ]]; then
printf '%s\n' "${resp}"
return 0
fi
done
return 1
}
_fetch_country_code() {
local resp
local url
local urls=(
"https://ipapi.co/country/"
"http://ip-api.com/line/?fields=countryCode"
"https://ifconfig.co/country-iso"
"https://ipinfo.io/country"
"https://www.icloud.com/geo/country_code/"
)
local shuffled_urls_output
shuffled_urls_output=$(printf '%s\n' "${urls[@]}" | shuf)
while IFS= read -r url; do
# Skip empty lines if any
[[ -z "${url}" ]] && continue
# Fetch the response with a 5-second timeout
resp=$(curl -fsS --max-time 5 "${url}" 2>/dev/null || true)
# Clean whitespace/newlines
resp="${resp//[[:space:]]/}"
# Validation: Ensure the response is exactly 2 letters (ISO 3166-1 alpha-2)
if [[ "${resp}" =~ ^[A-Za-z]{2}$ ]]; then
# Convert to uppercase and print
printf '%s\n' "${resp^^}"
return 0
fi
done <<< "${shuffled_urls_output}" # Process the shuffled output
return 1
}
_vpn_monitor_public_ip() {
# Arg1: label ("OpenVPN", "WireGuard", etc.)
local vpn_label="${1:-VPN}"
local current_ip_file="/currentip"
local baseline_ip
local vpn_ip
local country
local interval
local initial_delay
interval=${VPN_LEAK_CHECK_INTERVAL:-300}
initial_delay=${VPN_LEAK_INITIAL_DELAY:-60}
if ! command -v curl >/dev/null 2>&1; then
bashio::log.warning "${vpn_label}: curl not found; VPN leak monitoring disabled."
return 0
fi
if ! bashio::fs.file_exists "${current_ip_file}"; then
bashio::log.warning "${vpn_label}: baseline IP file ${current_ip_file} not found; VPN leak monitoring disabled."
return 0
fi
baseline_ip="$(tr -d '[:space:]' < "${current_ip_file}")"
if [[ -z "${baseline_ip}" ]]; then
bashio::log.warning "${vpn_label}: baseline IP in ${current_ip_file} is empty; VPN leak monitoring disabled."
return 0
fi
bashio::log.info "${vpn_label}: baseline (non-VPN) IP for leak detection: ${baseline_ip}"
bashio::log.debug "${vpn_label}: waiting ${initial_delay}s before first leak check."
sleep "${initial_delay}"
while true; do
vpn_ip="$(_fetch_public_ip || true)"
if [[ -z "${vpn_ip}" ]]; then
bashio::log.warning "${vpn_label}: failed to obtain VPN public IP from all providers (rate limited or unreachable)."
else
if country="$(_fetch_country_code || true)"; then
bashio::log.info "${vpn_label}: public IP: ${vpn_ip} (${country})."
else
bashio::log.info "${vpn_label}: public IP: ${vpn_ip} (country unknown)."
fi
if [[ "${vpn_ip}" == "${baseline_ip}" ]]; then
bashio::log.error "${vpn_label}: VPN leak detected: public IP ${vpn_ip} matches baseline ${baseline_ip}. Stopping add-on."
# Try to terminate the service tree cleanly.
s6-svscanctl -t /var/run/s6/services 2>/dev/null || true
exit 1
fi
fi
sleep "${interval}"
done
}
#
# --- Main logic: OpenVPN / WireGuard / qBittorrent ---
#
if bashio::config.true 'openvpn_enabled'; then if bashio::config.true 'openvpn_enabled'; then
# Start leak monitor for OpenVPN in the background
_vpn_monitor_public_ip "OpenVPN" &
exec /usr/sbin/openvpn \ exec /usr/sbin/openvpn \
--config /config/openvpn/config.ovpn \ --config /config/openvpn/config.ovpn \
--script-security 2 \ --script-security 2 \
@@ -55,6 +166,7 @@ else
local legacy_bin_dir="${WIREGUARD_STATE_DIR}/iptables-legacy-bin" local legacy_bin_dir="${WIREGUARD_STATE_DIR}/iptables-legacy-bin"
mkdir -p "${legacy_bin_dir}" mkdir -p "${legacy_bin_dir}"
local cmd
for cmd in iptables iptables-save iptables-restore ip6tables ip6tables-save ip6tables-restore; do for cmd in iptables iptables-save iptables-restore ip6tables ip6tables-save ip6tables-restore; do
if command -v "${cmd}-legacy" >/dev/null 2>&1; then if command -v "${cmd}-legacy" >/dev/null 2>&1; then
ln -sf "$(command -v "${cmd}-legacy")" "${legacy_bin_dir}/${cmd}" ln -sf "$(command -v "${cmd}-legacy")" "${legacy_bin_dir}/${cmd}"
@@ -68,13 +180,11 @@ else
_wireguard_up_with_iptables_fallback() { _wireguard_up_with_iptables_fallback() {
local config_path="$1" local config_path="$1"
local status local status=0
output="" output="$(wg-quick up "${config_path}" 2>&1)" || status=$?
output=$(wg-quick up "${config_path}" 2>&1)
status=$?
if [ "$status" -eq 0 ]; then if [ "${status}" -eq 0 ]; then
return 0 return 0
fi fi
@@ -82,8 +192,7 @@ else
if command -v iptables-legacy >/dev/null 2>&1 || command -v ip6tables-legacy >/dev/null 2>&1; then if command -v iptables-legacy >/dev/null 2>&1 || command -v ip6tables-legacy >/dev/null 2>&1; then
wg-quick down "${config_path}" >/dev/null 2>&1 || true wg-quick down "${config_path}" >/dev/null 2>&1 || true
_wireguard_prepare_iptables_legacy _wireguard_prepare_iptables_legacy
output=$(wg-quick up "${config_path}" 2>&1) output="$(wg-quick up "${config_path}" 2>&1)" || status=$?
status=$?
else else
bashio::log.warning 'iptables errors detected but iptables-legacy binaries are unavailable in the image.' bashio::log.warning 'iptables errors detected but iptables-legacy binaries are unavailable in the image.'
status=1 status=1
@@ -98,9 +207,10 @@ else
bashio::log.warning "First attempt output:${bashio::constants.LF}${output}" bashio::log.warning "First attempt output:${bashio::constants.LF}${output}"
ipv4_config="${WIREGUARD_STATE_DIR}/${wireguard_interface}-ipv4.conf" ipv4_config="${WIREGUARD_STATE_DIR}/${wireguard_interface}-ipv4.conf"
echo -n > "${ipv4_config}" : > "${ipv4_config}"
chmod 600 "${ipv4_config}" 2>/dev/null || true chmod 600 "${ipv4_config}" 2>/dev/null || true
local line endpoint endpoint_host endpoint_port
while IFS= read -r line; do while IFS= read -r line; do
if [[ "${line}" =~ ^Endpoint ]]; then if [[ "${line}" =~ ^Endpoint ]]; then
endpoint="${line#Endpoint = }" endpoint="${line#Endpoint = }"
@@ -140,14 +250,6 @@ else
bashio::log.info "WireGuard interface ${wireguard_interface} is up." bashio::log.info "WireGuard interface ${wireguard_interface} is up."
# Example usage of the helper: get VPN-protected IP and store it
if vpn_ip="$(_get_public_ip)"; then
echo "${vpn_ip}" > /vpn_ip
bashio::log.info "Detected VPN public IP: ${vpn_ip}"
else
bashio::log.warning 'Unable to determine VPN public IP (all IP services failed).'
fi
# Refresh DNS resolver configuration if resolvconf is present # Refresh DNS resolver configuration if resolvconf is present
if command -v resolvconf >/dev/null 2>&1; then if command -v resolvconf >/dev/null 2>&1; then
bashio::log.info 'Refreshing DNS resolver configuration via resolvconf -u.' bashio::log.info 'Refreshing DNS resolver configuration via resolvconf -u.'
@@ -157,6 +259,9 @@ else
else else
bashio::log.debug 'resolvconf not found in PATH; skipping DNS refresh.' bashio::log.debug 'resolvconf not found in PATH; skipping DNS refresh.'
fi fi
# Start VPN leak monitor for WireGuard in background
_vpn_monitor_public_ip "WireGuard" &
fi fi
if bashio::config.true 'silent'; then if bashio::config.true 'silent'; then