129 lines
4.2 KiB
Python
129 lines
4.2 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import socket
|
||
|
|
import time
|
||
|
|
from dataclasses import dataclass, field
|
||
|
|
from typing import Mapping
|
||
|
|
|
||
|
|
|
||
|
|
_STATIC_ENDPOINT_IPS: dict[str, tuple[str, ...]] = {
|
||
|
|
"open-api.bingx.com": (
|
||
|
|
"18.66.122.103",
|
||
|
|
"18.66.122.71",
|
||
|
|
"18.66.122.3",
|
||
|
|
"18.66.122.36",
|
||
|
|
"2600:9000:2250:4800:1a:4f2d:b440:93a1",
|
||
|
|
"2600:9000:2250:c400:1a:4f2d:b440:93a1",
|
||
|
|
"2600:9000:2250:6c00:1a:4f2d:b440:93a1",
|
||
|
|
"2600:9000:2250:7200:1a:4f2d:b440:93a1",
|
||
|
|
"2600:9000:2250:4600:1a:4f2d:b440:93a1",
|
||
|
|
"2600:9000:2250:ee00:1a:4f2d:b440:93a1",
|
||
|
|
"2600:9000:2250:fc00:1a:4f2d:b440:93a1",
|
||
|
|
"2600:9000:2250:6800:1a:4f2d:b440:93a1",
|
||
|
|
),
|
||
|
|
"open-api-vst.bingx.com": (
|
||
|
|
"18.165.122.31",
|
||
|
|
"18.165.122.22",
|
||
|
|
"18.165.122.47",
|
||
|
|
"18.165.122.63",
|
||
|
|
"2600:9000:2375:a200:14:7788:7980:93a1",
|
||
|
|
"2600:9000:2375:9000:14:7788:7980:93a1",
|
||
|
|
"2600:9000:2375:2c00:14:7788:7980:93a1",
|
||
|
|
"2600:9000:2375:7800:14:7788:7980:93a1",
|
||
|
|
"2600:9000:2375:f600:14:7788:7980:93a1",
|
||
|
|
"2600:9000:2375:ce00:14:7788:7980:93a1",
|
||
|
|
"2600:9000:2375:e800:14:7788:7980:93a1",
|
||
|
|
"2600:9000:2375:e200:14:7788:7980:93a1",
|
||
|
|
),
|
||
|
|
"open-api-swap.bingx.com": (
|
||
|
|
"3.164.68.61",
|
||
|
|
"3.164.68.42",
|
||
|
|
"3.164.68.75",
|
||
|
|
"3.164.68.125",
|
||
|
|
"2600:9000:278c:7400:1f:3dec:7600:93a1",
|
||
|
|
"2600:9000:278c:3e00:1f:3dec:7600:93a1",
|
||
|
|
"2600:9000:278c:5800:1f:3dec:7600:93a1",
|
||
|
|
"2600:9000:278c:3400:1f:3dec:7600:93a1",
|
||
|
|
"2600:9000:278c:ce00:1f:3dec:7600:93a1",
|
||
|
|
"2600:9000:278c:9a00:1f:3dec:7600:93a1",
|
||
|
|
"2600:9000:278c:9000:1f:3dec:7600:93a1",
|
||
|
|
"2600:9000:278c:c800:1f:3dec:7600:93a1",
|
||
|
|
),
|
||
|
|
"open-api.bingx.pro": (
|
||
|
|
"2606:4700:4403::ac40:9313",
|
||
|
|
"2a06:98c1:310d::6812:28ed",
|
||
|
|
),
|
||
|
|
"open-api-vst.bingx.pro": (
|
||
|
|
"2a06:98c1:310d::6812:28ed",
|
||
|
|
"2606:4700:4403::ac40:9313",
|
||
|
|
),
|
||
|
|
"open-api-swap.bingx.pro": (
|
||
|
|
"2a06:98c1:310d::6812:28ed",
|
||
|
|
"2606:4700:4403::ac40:9313",
|
||
|
|
),
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass(slots=True)
|
||
|
|
class BingxDnsRecord:
|
||
|
|
hostname: str
|
||
|
|
ips: tuple[str, ...]
|
||
|
|
source: str
|
||
|
|
updated_at_ns: int
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass(slots=True)
|
||
|
|
class BingxDnsFallbackCache:
|
||
|
|
default_ips: Mapping[str, tuple[str, ...]] = field(default_factory=lambda: dict(_STATIC_ENDPOINT_IPS))
|
||
|
|
_records: dict[str, BingxDnsRecord] = field(default_factory=dict, init=False, repr=False)
|
||
|
|
|
||
|
|
def resolve(self, hostname: str) -> tuple[str, ...]:
|
||
|
|
record = self._records.get(hostname)
|
||
|
|
if record is not None:
|
||
|
|
return record.ips
|
||
|
|
return tuple(self.default_ips.get(hostname, ()))
|
||
|
|
|
||
|
|
def record_static(self, hostname: str) -> tuple[str, ...]:
|
||
|
|
ips = self.resolve(hostname)
|
||
|
|
if ips:
|
||
|
|
self._records[hostname] = BingxDnsRecord(
|
||
|
|
hostname=hostname,
|
||
|
|
ips=ips,
|
||
|
|
source="static",
|
||
|
|
updated_at_ns=time.monotonic_ns(),
|
||
|
|
)
|
||
|
|
return ips
|
||
|
|
|
||
|
|
def refresh_from_dns(self, hostname: str) -> tuple[str, ...]:
|
||
|
|
seen: list[str] = []
|
||
|
|
for family in (socket.AF_UNSPEC,):
|
||
|
|
infos = socket.getaddrinfo(hostname, None, family=family, type=socket.SOCK_STREAM)
|
||
|
|
for info in infos:
|
||
|
|
address = info[4][0]
|
||
|
|
if address not in seen:
|
||
|
|
seen.append(address)
|
||
|
|
ips = tuple(seen)
|
||
|
|
if ips:
|
||
|
|
self._records[hostname] = BingxDnsRecord(
|
||
|
|
hostname=hostname,
|
||
|
|
ips=ips,
|
||
|
|
source="dns",
|
||
|
|
updated_at_ns=time.monotonic_ns(),
|
||
|
|
)
|
||
|
|
return ips
|
||
|
|
|
||
|
|
def maybe_refresh_from_dns(self, hostname: str, *, min_interval_secs: int = 300) -> tuple[str, ...] | None:
|
||
|
|
record = self._records.get(hostname)
|
||
|
|
if record is not None:
|
||
|
|
age_secs = (time.monotonic_ns() - record.updated_at_ns) / 1_000_000_000
|
||
|
|
if age_secs < min_interval_secs:
|
||
|
|
return record.ips
|
||
|
|
try:
|
||
|
|
return self.refresh_from_dns(hostname)
|
||
|
|
except Exception:
|
||
|
|
return None
|
||
|
|
|
||
|
|
|
||
|
|
def is_bingx_hostname(hostname: str | None) -> bool:
|
||
|
|
return hostname in _STATIC_ENDPOINT_IPS
|