| Server IP : 109.234.162.214 / Your IP : 216.73.216.112 Web Server : Apache System : Linux servd162214.srv.odns.fr 4.18.0-372.26.1.lve.1.el8.x86_64 #1 SMP Fri Sep 16 14:08:19 EDT 2022 x86_64 User : carpe ( 1178) PHP Version : 8.0.30 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/ |
Upload File : |
"""IPEchoAPI - returns real IP address of the host (behind NAT)"""
import asyncio
import functools
import logging
import time
import urllib
from pathlib import Path
from typing import Optional
from async_lru import alru_cache
from defence360agent.api.server import API, APIError
from defence360agent.utils import atomic_rewrite
from defence360agent.utils.validate import IP, IPVersion
logger = logging.getLogger(__name__)
TIMEOUT_FOR_IPECHO_REQUEST = 5 # in seconds
CACHE_TTL_SECONDS = 3 * 60 * 60
CACHE_FILE_PATH = Path("/var/imunify360") / "ipecho_cache"
class IPEchoAPI(API):
"""Make requests to the API for obtain own IP address"""
URL = "/api/ip"
@classmethod
@alru_cache(maxsize=3)
async def get_ip(cls, ip_version: IPVersion = None) -> Optional[str]:
"""Return cached result for resolved IP from echo ip API"""
return await cls.ip_for_version(ip_version)
@classmethod
@functools.lru_cache(maxsize=1)
def server_ip(cls):
"""Return cached result for resolved IP from echo ip API"""
try:
return cls._get_ip()
except Exception as e:
raise APIError from e
@classmethod
async def ip_for_version(
cls, ip_version: IPVersion = None
) -> Optional[str]:
"""Return resolved IP from echo ip API"""
loop = asyncio.get_event_loop()
try:
ip = await asyncio.wait_for(
loop.run_in_executor(None, cls._get_ip),
timeout=TIMEOUT_FOR_IPECHO_REQUEST,
)
if IP.type_of(ip) != ip_version:
raise ValueError("Wrong ip type")
return ip
except (asyncio.TimeoutError, ValueError) as e:
raise APIError from e
@classmethod
def _load_cache(cls) -> Optional[str]:
try:
if not CACHE_FILE_PATH.exists():
return None
mtime = CACHE_FILE_PATH.stat().st_mtime
cache_age = time.time() - mtime
if cache_age < 0:
return None
if cache_age < CACHE_TTL_SECONDS:
ip = CACHE_FILE_PATH.read_text().strip()
return ip
else:
return None
except Exception as e:
logger.error("IPEchoAPI cache read error: %s", e)
return None
@classmethod
def _save_cache(cls, ip: str) -> None:
try:
atomic_rewrite(
CACHE_FILE_PATH,
ip,
backup=False,
permissions=0o644,
)
except Exception as e:
logger.error("IPEchoAPI cache write error: %s", e)
@classmethod
def _get_ip(cls):
"""Get IP from file-based cache or send request to API and process response."""
cached_ip = cls._load_cache()
if cached_ip is not None:
return cached_ip
request = urllib.request.Request(cls._BASE_URL + cls.URL)
response = cls.request(request)
if response.get("status") != "ok":
# time inside sync executor
raise APIError("Unexpected API error")
ip = response.get("ip")
if ip:
cls._save_cache(ip)
return ip