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}"
# --- New helper: get public IP with rate-limiting fallbacks ---
_get_public_ip() {
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}"
}
# Global variable used by WireGuard bring-up helper
output=""
if bashio::config.true 'silent'; then
sed -i 's|/proc/1/fd/1 hassio;|off;|g' /etc/nginx/nginx.conf
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
# Start leak monitor for OpenVPN in the background
_vpn_monitor_public_ip "OpenVPN" &
exec /usr/sbin/openvpn \
--config /config/openvpn/config.ovpn \
--script-security 2 \
@@ -55,6 +166,7 @@ else
local legacy_bin_dir="${WIREGUARD_STATE_DIR}/iptables-legacy-bin"
mkdir -p "${legacy_bin_dir}"
local cmd
for cmd in iptables iptables-save iptables-restore ip6tables ip6tables-save ip6tables-restore; do
if command -v "${cmd}-legacy" >/dev/null 2>&1; then
ln -sf "$(command -v "${cmd}-legacy")" "${legacy_bin_dir}/${cmd}"
@@ -68,13 +180,11 @@ else
_wireguard_up_with_iptables_fallback() {
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
fi
@@ -82,8 +192,7 @@ else
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
_wireguard_prepare_iptables_legacy
output=$(wg-quick up "${config_path}" 2>&1)
status=$?
output="$(wg-quick up "${config_path}" 2>&1)" || status=$?
else
bashio::log.warning 'iptables errors detected but iptables-legacy binaries are unavailable in the image.'
status=1
@@ -98,9 +207,10 @@ else
bashio::log.warning "First attempt output:${bashio::constants.LF}${output}"
ipv4_config="${WIREGUARD_STATE_DIR}/${wireguard_interface}-ipv4.conf"
echo -n > "${ipv4_config}"
: > "${ipv4_config}"
chmod 600 "${ipv4_config}" 2>/dev/null || true
local line endpoint endpoint_host endpoint_port
while IFS= read -r line; do
if [[ "${line}" =~ ^Endpoint ]]; then
endpoint="${line#Endpoint = }"
@@ -140,14 +250,6 @@ else
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
if command -v resolvconf >/dev/null 2>&1; then
bashio::log.info 'Refreshing DNS resolver configuration via resolvconf -u.'
@@ -157,6 +259,9 @@ else
else
bashio::log.debug 'resolvconf not found in PATH; skipping DNS refresh.'
fi
# Start VPN leak monitor for WireGuard in background
_vpn_monitor_public_ip "WireGuard" &
fi
if bashio::config.true 'silent'; then