mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-01-10 18:01:03 +01:00
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user