Compare commits

..

4 Commits

Author SHA1 Message Date
Flowseal 4f9edf3072 Version bump 2026-06-27 01:22:19 +03:00
Flowseal 9ff95d1222 ip_timeout implementation 2026-06-27 01:19:27 +03:00
Flowseal 4c19a6cce4 release footer separator 2026-06-26 19:35:25 +03:00
Flowseal bb900e0c9e todo 2026-06-26 19:33:48 +03:00
4 changed files with 30 additions and 18 deletions
+1 -1
View File
@@ -525,7 +525,7 @@ jobs:
tag_name: ${{ github.event.inputs.version }} tag_name: ${{ github.event.inputs.version }}
name: "TG WS Proxy ${{ github.event.inputs.version }}" name: "TG WS Proxy ${{ github.event.inputs.version }}"
body: | body: |
## ---
### [❤️ Поддержать развитие проекта](https://github.com/Flowseal/tg-ws-proxy/blob/main/docs/Funding.md) ### [❤️ Поддержать развитие проекта](https://github.com/Flowseal/tg-ws-proxy/blob/main/docs/Funding.md)
> [!TIP] > [!TIP]
+4 -4
View File
@@ -4,8 +4,8 @@
# http://msdn.microsoft.com/en-us/library/ms646997.aspx # http://msdn.microsoft.com/en-us/library/ms646997.aspx
VSVersionInfo( VSVersionInfo(
ffi=FixedFileInfo( ffi=FixedFileInfo(
filevers=(1, 8, 0, 0), filevers=(1, 8, 1, 0),
prodvers=(1, 8, 0, 0), prodvers=(1, 8, 1, 0),
mask=0x3f, mask=0x3f,
flags=0x0, flags=0x0,
OS=0x40004, OS=0x40004,
@@ -21,12 +21,12 @@ VSVersionInfo(
[ [
StringStruct(u'CompanyName', u'Flowseal'), StringStruct(u'CompanyName', u'Flowseal'),
StringStruct(u'FileDescription', u'Telegram Desktop WebSocket Bridge Proxy'), StringStruct(u'FileDescription', u'Telegram Desktop WebSocket Bridge Proxy'),
StringStruct(u'FileVersion', u'1.8.0.0'), StringStruct(u'FileVersion', u'1.8.1.0'),
StringStruct(u'InternalName', u'TgWsProxy'), StringStruct(u'InternalName', u'TgWsProxy'),
StringStruct(u'LegalCopyright', u'Copyright (c) Flowseal. MIT License.'), StringStruct(u'LegalCopyright', u'Copyright (c) Flowseal. MIT License.'),
StringStruct(u'OriginalFilename', u'TgWsProxy.exe'), StringStruct(u'OriginalFilename', u'TgWsProxy.exe'),
StringStruct(u'ProductName', u'TG WS Proxy'), StringStruct(u'ProductName', u'TG WS Proxy'),
StringStruct(u'ProductVersion', u'1.8.0.0'), StringStruct(u'ProductVersion', u'1.8.1.0'),
] ]
) )
] ]
+1 -1
View File
@@ -1,6 +1,6 @@
from .config import parse_dc_ip_list, proxy_config, coerce_domain_list from .config import parse_dc_ip_list, proxy_config, coerce_domain_list
from .utils import get_link_host, build_github_opener from .utils import get_link_host, build_github_opener
__version__ = "1.8.0" __version__ = "1.8.1"
__all__ = ["__version__", "get_link_host", "proxy_config", "parse_dc_ip_list", "build_github_opener", "coerce_domain_list"] __all__ = ["__version__", "get_link_host", "proxy_config", "parse_dc_ip_list", "build_github_opener", "coerce_domain_list"]
+24 -12
View File
@@ -33,13 +33,15 @@ from ._aes import Cipher, algorithms, modes
log = logging.getLogger('tg-mtproto-proxy') log = logging.getLogger('tg-mtproto-proxy')
DC_FAIL_COOLDOWN = 30.0 IP_FAIL_COOLDOWN = 3600.0
DC_FAIL_COOLDOWN = 60.0
WS_FAIL_TIMEOUT = 2.0 WS_FAIL_TIMEOUT = 2.0
FRONTING_COOLDOWN = 1800.0 FRONTING_COOLDOWN = 1800.0
LISTENER_CHECK_INTERVAL = 5.0 LISTENER_CHECK_INTERVAL = 5.0
LISTENER_RESTART_DELAY = 1.0 LISTENER_RESTART_DELAY = 1.0
ws_blacklist: Set[str] = set() ws_blacklist: Set[str] = set()
dc_fail_until: Dict[str, float] = {} dc_fail_until: Dict[str, float] = {}
ip_fail_until: Dict[str, float] = {}
fronting_until: float = 0.0 fronting_until: float = 0.0
@@ -295,15 +297,24 @@ async def _handle_client(reader, writer, secret: bytes):
dc_key = f'{dc}{"m" if is_media else ""}' dc_key = f'{dc}{"m" if is_media else ""}'
media_tag = " media" if is_media else "" media_tag = " media" if is_media else ""
now = time.monotonic()
target = proxy_config.dc_redirects.get(dc)
is_any_cf_fallback = proxy_config.fallback_cfproxy or proxy_config.cfproxy_worker_domains
# Fallback if DC not in config, if WS blacklisted for this DC/is_media or if connect to ip is timed out
if (dc not in proxy_config.dc_redirects
or dc_key in ws_blacklist
or now < ip_fail_until.get(target, 0) and is_any_cf_fallback):
# Fallback if DC not in config or WS blacklisted for this DC/is_media
if dc not in proxy_config.dc_redirects or dc_key in ws_blacklist:
if dc not in proxy_config.dc_redirects: if dc not in proxy_config.dc_redirects:
log.info("[%s] DC%d not in config -> fallback", log.info("[%s] DC%d not in config -> fallback",
label, dc) label, dc)
else: elif dc_key in ws_blacklist:
log.info("[%s] DC%d%s WS blacklisted -> fallback", log.info("[%s] DC%d%s WS blacklisted -> fallback",
label, dc, media_tag) label, dc, media_tag)
else:
log.info("[%s] DC%d%s WS connect to %s was timed out -> fallback",
label, dc, media_tag, target)
splitter = None splitter = None
try: try:
splitter = MsgSplitter(relay_init, proto_int) splitter = MsgSplitter(relay_init, proto_int)
@@ -318,13 +329,10 @@ async def _handle_client(reader, writer, secret: bytes):
label, dc, media_tag) label, dc, media_tag)
return return
now = time.monotonic() ws_timeout = WS_FAIL_TIMEOUT if now < dc_fail_until.get(dc_key, 0) else 5.0
fail_until = dc_fail_until.get(dc_key, 0)
ws_timeout = WS_FAIL_TIMEOUT if now < fail_until else 10.0
fronting_active = now < fronting_until fronting_active = now < fronting_until
domains = ws_domains(dc, is_media) domains = ws_domains(dc, is_media)
target = proxy_config.dc_redirects[dc]
ws = None ws = None
ws_failed_redirect = False ws_failed_redirect = False
ws_timed_out = False ws_timed_out = False
@@ -340,7 +348,7 @@ async def _handle_client(reader, writer, secret: bytes):
label, dc, media_tag, domains[0]) label, dc, media_tag, domains[0])
try: try:
ws = await RawWebSocket.connect(target, domains[0], ws = await RawWebSocket.connect(target, domains[0],
timeout=10.0, timeout=5.0,
sni="sprinthost.ru") sni="sprinthost.ru")
except Exception as exc: except Exception as exc:
stats.ws_errors += 1 stats.ws_errors += 1
@@ -381,6 +389,7 @@ async def _handle_client(reader, writer, secret: bytes):
ws_timed_out = True ws_timed_out = True
log.warning("[%s] DC%d%s WS connect timed out via %s", log.warning("[%s] DC%d%s WS connect timed out via %s",
label, dc, media_tag, domain) label, dc, media_tag, domain)
break
except Exception as exc: except Exception as exc:
stats.ws_errors += 1 stats.ws_errors += 1
all_redirects = False all_redirects = False
@@ -395,7 +404,7 @@ async def _handle_client(reader, writer, secret: bytes):
label, dc, media_tag, domains[0]) label, dc, media_tag, domains[0])
try: try:
ws = await RawWebSocket.connect(target, domains[0], ws = await RawWebSocket.connect(target, domains[0],
timeout=10.0, timeout=5.0,
sni="sprinthost.ru") sni="sprinthost.ru")
except Exception as exc: except Exception as exc:
stats.ws_errors += 1 stats.ws_errors += 1
@@ -410,6 +419,9 @@ async def _handle_client(reader, writer, secret: bytes):
# WS failed -> fallback # WS failed -> fallback
if ws is None: if ws is None:
if ws_timed_out:
ip_fail_until[target] = now + IP_FAIL_COOLDOWN
if ws_failed_redirect and all_redirects: if ws_failed_redirect and all_redirects:
ws_blacklist.add(dc_key) ws_blacklist.add(dc_key)
log.warning("[%s] DC%d%s blacklisted for WS (all 302)", log.warning("[%s] DC%d%s blacklisted for WS (all 302)",
@@ -418,8 +430,6 @@ async def _handle_client(reader, writer, secret: bytes):
dc_fail_until[dc_key] = now + DC_FAIL_COOLDOWN dc_fail_until[dc_key] = now + DC_FAIL_COOLDOWN
else: else:
dc_fail_until[dc_key] = now + DC_FAIL_COOLDOWN dc_fail_until[dc_key] = now + DC_FAIL_COOLDOWN
# TODO: may be don't try regular WS connection and do fallback instanstly
# instead of waiting for WS_FAIL_TIMEOUT and then fallback
log.info("[%s] DC%d%s WS cooldown for %ds", log.info("[%s] DC%d%s WS cooldown for %ds",
label, dc, media_tag, int(DC_FAIL_COOLDOWN)) label, dc, media_tag, int(DC_FAIL_COOLDOWN))
@@ -438,6 +448,7 @@ async def _handle_client(reader, writer, secret: bytes):
return return
dc_fail_until.pop(dc_key, None) dc_fail_until.pop(dc_key, None)
ip_fail_until.pop(target, None)
stats.connections_ws += 1 stats.connections_ws += 1
splitter = None splitter = None
@@ -491,6 +502,7 @@ async def _run(stop_event: Optional[asyncio.Event] = None):
cf_worker_pool.reset() cf_worker_pool.reset()
ws_blacklist.clear() ws_blacklist.clear()
dc_fail_until.clear() dc_fail_until.clear()
ip_fail_until.clear()
_client_tasks.clear() _client_tasks.clear()
fronting_until = 0.0 fronting_until = 0.0