mirror of
https://github.com/Flowseal/tg-ws-proxy.git
synced 2026-05-22 23:41:44 +03:00
cloudflare worker implementation
This commit is contained in:
@@ -48,6 +48,7 @@ tg-ws-proxy [--port PORT] [--host HOST] [--dc-ip DC:IP ...] [-v]
|
|||||||
| `--dc-ip` | `2:149.154.167.220`, `4:149.154.167.220` | Целевой IP для DC (параметр можно указывать несколько раз) |
|
| `--dc-ip` | `2:149.154.167.220`, `4:149.154.167.220` | Целевой IP для DC (параметр можно указывать несколько раз) |
|
||||||
| `--no-cfproxy` | `false` | Отключить попытку [проксирования через Cloudflare](./CfProxy.md) |
|
| `--no-cfproxy` | `false` | Отключить попытку [проксирования через Cloudflare](./CfProxy.md) |
|
||||||
| `--cfproxy-domain` | | Указать свой домен для проксирования через Cloudflare. [Подробнее](./CfProxy.md) |
|
| `--cfproxy-domain` | | Указать свой домен для проксирования через Cloudflare. [Подробнее](./CfProxy.md) |
|
||||||
|
| `--cfproxy-worker-domain` | | Домен Cloudflare Worker [Подробнее](./CfWorker.md) |
|
||||||
| `--cfproxy-priority` | `true` | Пробовать проксировать через Cloudflare перед прямым TCP подключением |
|
| `--cfproxy-priority` | `true` | Пробовать проксировать через Cloudflare перед прямым TCP подключением |
|
||||||
| `--fake-tls-domain` | | Включить маскировку Fake TLS (ee-secret) с указанным SNI-доменом |
|
| `--fake-tls-domain` | | Включить маскировку Fake TLS (ee-secret) с указанным SNI-доменом |
|
||||||
| `--proxy-protocol` | выкл. | Принимать HAProxy PROXY protocol v1 (для работы за nginx/haproxy с `proxy_protocol on`) |
|
| `--proxy-protocol` | выкл. | Принимать HAProxy PROXY protocol v1 (для работы за nginx/haproxy с `proxy_protocol on`) |
|
||||||
|
|||||||
131
docs/CfWorker.md
Normal file
131
docs/CfWorker.md
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
# Cloudflare Worker
|
||||||
|
|
||||||
|
Альтернативный (полностью бесплатный, не нужно покупать домен в отличии от [CfProxy](./CfProxy.md)) способ проксирования.
|
||||||
|
|
||||||
|
Прокси возвращает доступ к тому, что раньше не загружалось (реакции, некоторые стикеры). Если на аккаунте без Premium с данным способом все еще не загружаются фото/видео, оставьте в блоке `DC → IP` только `4:149.154.167.220`
|
||||||
|
|
||||||
|
##
|
||||||
|
|
||||||
|
1. **Добавьте в [zapret](https://github.com/Flowseal/zapret-discord-youtube/) или в любое другое ПО следующие домены:**
|
||||||
|
```
|
||||||
|
cloudflare.com
|
||||||
|
cloudflare.dev
|
||||||
|
workers.dev
|
||||||
|
```
|
||||||
|
2. Создайте аккаунт в [Cloudflare](https://dash.cloudflare.com/) (или войдите в существующий)
|
||||||
|
3. Слева в панели выберите `Compute` → `Workers & Pages`
|
||||||
|
4. Нажмите сверху справа кнопку **`Create application`** → `Start with Hello World!` → `Deploy`
|
||||||
|
5. Сверху справа нажмите кнопку **`Edit code`**, замените код слева на тот, что находится внизу страницы
|
||||||
|
* Если у вас не загружается код, то вы не выполнили первый пункт
|
||||||
|
6. Нажмите сверху справа кнопку **`Deploy`**
|
||||||
|
7. Скопируйте домен из поля справа и укажите его в настройках **Cloudflare Worker** (или через аргумент `--cfproxy-worker-domain`)
|
||||||
|
* Пример домена: `random-symbols-1234.username.workers.dev`
|
||||||
|
|
||||||
|
### Код Worker'а
|
||||||
|
```javascript
|
||||||
|
import { connect } from "cloudflare:sockets";
|
||||||
|
|
||||||
|
const DC_IPS = {
|
||||||
|
"1": "149.154.175.50",
|
||||||
|
"2": "149.154.167.51",
|
||||||
|
"3": "149.154.175.100",
|
||||||
|
"4": "149.154.167.91",
|
||||||
|
"5": "149.154.171.5",
|
||||||
|
"203": "91.105.192.100",
|
||||||
|
};
|
||||||
|
|
||||||
|
function isIpv4(value) {
|
||||||
|
return /^\d{1,3}(\.\d{1,3}){3}$/.test(value || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickDst(url) {
|
||||||
|
const byDst = url.searchParams.get("dst");
|
||||||
|
if (isIpv4(byDst)) {
|
||||||
|
return byDst;
|
||||||
|
}
|
||||||
|
const dc = url.searchParams.get("dc") || "2";
|
||||||
|
return DC_IPS[dc] || DC_IPS["2"];
|
||||||
|
}
|
||||||
|
|
||||||
|
function toBytes(data) {
|
||||||
|
if (data instanceof ArrayBuffer) {
|
||||||
|
return new Uint8Array(data);
|
||||||
|
}
|
||||||
|
if (typeof data === "string") {
|
||||||
|
return new TextEncoder().encode(data);
|
||||||
|
}
|
||||||
|
if (data && typeof data.arrayBuffer === "function") {
|
||||||
|
return data.arrayBuffer().then((ab) => new Uint8Array(ab));
|
||||||
|
}
|
||||||
|
return new Uint8Array();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
async fetch(request) {
|
||||||
|
if ((request.headers.get("Upgrade") || "").toLowerCase() !== "websocket") {
|
||||||
|
return new Response("Expected websocket", { status: 426 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(request.url);
|
||||||
|
if (url.pathname !== "/apiws") {
|
||||||
|
return new Response("Not found", { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const dst = pickDst(url);
|
||||||
|
const pair = new WebSocketPair();
|
||||||
|
const client = pair[0];
|
||||||
|
const server = pair[1];
|
||||||
|
server.accept();
|
||||||
|
|
||||||
|
const socket = connect({ hostname: dst, port: 443 });
|
||||||
|
const tcpReader = socket.readable.getReader();
|
||||||
|
const tcpWriter = socket.writable.getWriter();
|
||||||
|
|
||||||
|
server.addEventListener("message", async (event) => {
|
||||||
|
try {
|
||||||
|
await tcpWriter.write(await toBytes(event.data));
|
||||||
|
} catch {
|
||||||
|
try {
|
||||||
|
server.close(1011, "tcp write failed");
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.addEventListener("close", async () => {
|
||||||
|
try {
|
||||||
|
await tcpWriter.close();
|
||||||
|
} catch {}
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch {}
|
||||||
|
});
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
const { value, done } = await tcpReader.read();
|
||||||
|
if (done) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (value) {
|
||||||
|
server.send(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
server.close();
|
||||||
|
} catch {}
|
||||||
|
try {
|
||||||
|
tcpReader.releaseLock();
|
||||||
|
} catch {}
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return new Response(null, { status: 101, webSocket: client });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
@@ -23,6 +23,7 @@ Tray-приложение хранит данные в:
|
|||||||
"cfproxy": true,
|
"cfproxy": true,
|
||||||
"cfproxy_priority": true,
|
"cfproxy_priority": true,
|
||||||
"cfproxy_user_domain": "",
|
"cfproxy_user_domain": "",
|
||||||
|
"cfproxy_worker_domain": "",
|
||||||
"appearance": "auto"
|
"appearance": "auto"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import struct
|
|||||||
|
|
||||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
from .utils import *
|
from .utils import *
|
||||||
from .stats import stats
|
from .stats import stats
|
||||||
@@ -132,14 +133,24 @@ async def do_fallback(reader, writer, relay_init, label,
|
|||||||
fallback_dst = DC_DEFAULT_IPS.get(dc)
|
fallback_dst = DC_DEFAULT_IPS.get(dc)
|
||||||
use_cf = proxy_config.fallback_cfproxy
|
use_cf = proxy_config.fallback_cfproxy
|
||||||
cf_first = proxy_config.fallback_cfproxy_priority
|
cf_first = proxy_config.fallback_cfproxy_priority
|
||||||
|
worker_domain = proxy_config.cfproxy_worker_domain
|
||||||
|
|
||||||
methods: List[str] = ['tcp']
|
methods: List[str] = ['tcp']
|
||||||
|
|
||||||
if use_cf:
|
if use_cf:
|
||||||
methods.insert(0 if cf_first else 1, 'cf')
|
methods.insert(0 if cf_first else 1, 'cf')
|
||||||
|
if worker_domain:
|
||||||
|
methods.insert(0, 'cf_worker')
|
||||||
|
|
||||||
for method in methods:
|
for method in methods:
|
||||||
if method == 'cf':
|
if method == 'cf_worker' and fallback_dst:
|
||||||
|
ok = await _cfproxy_worker_fallback(
|
||||||
|
reader, writer, relay_init, label, ctx,
|
||||||
|
dc=dc, is_media=is_media, fallback_dst=fallback_dst,
|
||||||
|
splitter=splitter)
|
||||||
|
if ok:
|
||||||
|
return True
|
||||||
|
elif method == 'cf':
|
||||||
ok = await _cfproxy_fallback(
|
ok = await _cfproxy_fallback(
|
||||||
reader, writer, relay_init, label, ctx,
|
reader, writer, relay_init, label, ctx,
|
||||||
dc=dc, is_media=is_media,
|
dc=dc, is_media=is_media,
|
||||||
@@ -157,6 +168,42 @@ async def do_fallback(reader, writer, relay_init, label,
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def _cfproxy_worker_fallback(reader, writer, relay_init, label,
|
||||||
|
ctx: CryptoCtx,
|
||||||
|
dc: int, is_media: bool,
|
||||||
|
fallback_dst: str,
|
||||||
|
splitter=None):
|
||||||
|
media_tag = ' media' if is_media else ''
|
||||||
|
worker_domain = proxy_config.cfproxy_worker_domain
|
||||||
|
if not worker_domain:
|
||||||
|
return False
|
||||||
|
|
||||||
|
query = urlencode({
|
||||||
|
'dst': fallback_dst,
|
||||||
|
'dc': str(dc),
|
||||||
|
'media': '1' if is_media else '0',
|
||||||
|
})
|
||||||
|
path = f'/apiws?{query}'
|
||||||
|
|
||||||
|
log.info("[%s] DC%d%s -> trying CF worker for %s",
|
||||||
|
label, dc, media_tag, fallback_dst)
|
||||||
|
|
||||||
|
try:
|
||||||
|
ws = await RawWebSocket.connect(worker_domain, worker_domain,
|
||||||
|
timeout=10.0, path=path)
|
||||||
|
except Exception as exc:
|
||||||
|
log.warning("[%s] DC%d%s CF worker failed: %s",
|
||||||
|
label, dc, media_tag, repr(exc))
|
||||||
|
return False
|
||||||
|
|
||||||
|
stats.connections_cfproxy += 1
|
||||||
|
await ws.send(relay_init)
|
||||||
|
await bridge_ws_reencrypt(reader, writer, ws, label, ctx,
|
||||||
|
dc=dc, is_media=is_media,
|
||||||
|
splitter=splitter)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def _cfproxy_fallback(reader, writer, relay_init, label,
|
async def _cfproxy_fallback(reader, writer, relay_init, label,
|
||||||
ctx: CryptoCtx,
|
ctx: CryptoCtx,
|
||||||
dc: int, is_media: bool,
|
dc: int, is_media: bool,
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class ProxyConfig:
|
|||||||
fallback_cfproxy: bool = True
|
fallback_cfproxy: bool = True
|
||||||
fallback_cfproxy_priority: bool = True
|
fallback_cfproxy_priority: bool = True
|
||||||
cfproxy_user_domain: str = ''
|
cfproxy_user_domain: str = ''
|
||||||
|
cfproxy_worker_domain: str = ''
|
||||||
fake_tls_domain: str = ''
|
fake_tls_domain: str = ''
|
||||||
proxy_protocol: bool = False
|
proxy_protocol: bool = False
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,8 @@ class RawWebSocket:
|
|||||||
self._closed = False
|
self._closed = False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def connect(host: str, domain: str, timeout: float = 10.0) -> 'RawWebSocket':
|
async def connect(host: str, domain: str, timeout: float = 10.0,
|
||||||
|
path: str = '/apiws') -> 'RawWebSocket':
|
||||||
reader, writer = await asyncio.wait_for(
|
reader, writer = await asyncio.wait_for(
|
||||||
asyncio.open_connection(host, 443, ssl=_ssl_ctx,
|
asyncio.open_connection(host, 443, ssl=_ssl_ctx,
|
||||||
server_hostname=domain),
|
server_hostname=domain),
|
||||||
@@ -89,7 +90,7 @@ class RawWebSocket:
|
|||||||
ws_key = base64.b64encode(os.urandom(16)).decode()
|
ws_key = base64.b64encode(os.urandom(16)).decode()
|
||||||
|
|
||||||
req = (
|
req = (
|
||||||
f'GET /apiws HTTP/1.1\r\n'
|
f'GET {path} HTTP/1.1\r\n'
|
||||||
f'Host: {domain}\r\n'
|
f'Host: {domain}\r\n'
|
||||||
f'Upgrade: websocket\r\n'
|
f'Upgrade: websocket\r\n'
|
||||||
f'Connection: Upgrade\r\n'
|
f'Connection: Upgrade\r\n'
|
||||||
|
|||||||
@@ -590,6 +590,9 @@ async def _run(stop_event: Optional[asyncio.Event] = None):
|
|||||||
prio = 'CF first' if proxy_config.fallback_cfproxy_priority else 'TCP first'
|
prio = 'CF first' if proxy_config.fallback_cfproxy_priority else 'TCP first'
|
||||||
user_domain = "user" if proxy_config.cfproxy_user_domain else "auto"
|
user_domain = "user" if proxy_config.cfproxy_user_domain else "auto"
|
||||||
log.info(" CF proxy: enabled (%s | %s)", prio, user_domain)
|
log.info(" CF proxy: enabled (%s | %s)", prio, user_domain)
|
||||||
|
if proxy_config.cfproxy_worker_domain:
|
||||||
|
log.info(" CF worker: enabled (%s)",
|
||||||
|
proxy_config.cfproxy_worker_domain)
|
||||||
log.info("=" * 60)
|
log.info("=" * 60)
|
||||||
log.info(" Connect:")
|
log.info(" Connect:")
|
||||||
if ftls:
|
if ftls:
|
||||||
@@ -687,6 +690,10 @@ def main():
|
|||||||
ap.add_argument('--cfproxy-domain', type=str, default='',
|
ap.add_argument('--cfproxy-domain', type=str, default='',
|
||||||
metavar='DOMAIN',
|
metavar='DOMAIN',
|
||||||
help='User defined Cloudflare-proxied domain for WS fallback')
|
help='User defined Cloudflare-proxied domain for WS fallback')
|
||||||
|
ap.add_argument('--cfproxy-worker-domain', type=str, default='',
|
||||||
|
metavar='DOMAIN',
|
||||||
|
help='Cloudflare Worker domain for WS fallback '
|
||||||
|
'(tried before other fallback methods)')
|
||||||
ap.add_argument('--no-cfproxy', action='store_true',
|
ap.add_argument('--no-cfproxy', action='store_true',
|
||||||
help='Disable Cloudflare proxy fallback')
|
help='Disable Cloudflare proxy fallback')
|
||||||
ap.add_argument('--cfproxy-priority', type=_parse_bool, default=True,
|
ap.add_argument('--cfproxy-priority', type=_parse_bool, default=True,
|
||||||
@@ -732,6 +739,7 @@ def main():
|
|||||||
proxy_config.fallback_cfproxy = not args.no_cfproxy
|
proxy_config.fallback_cfproxy = not args.no_cfproxy
|
||||||
proxy_config.fallback_cfproxy_priority = args.cfproxy_priority
|
proxy_config.fallback_cfproxy_priority = args.cfproxy_priority
|
||||||
proxy_config.cfproxy_user_domain = args.cfproxy_domain.strip()
|
proxy_config.cfproxy_user_domain = args.cfproxy_domain.strip()
|
||||||
|
proxy_config.cfproxy_worker_domain = args.cfproxy_worker_domain.strip()
|
||||||
proxy_config.fake_tls_domain = args.fake_tls_domain.strip()
|
proxy_config.fake_tls_domain = args.fake_tls_domain.strip()
|
||||||
proxy_config.proxy_protocol = args.proxy_protocol
|
proxy_config.proxy_protocol = args.proxy_protocol
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ _TRAY_DEFAULTS_COMMON: Dict[str, Any] = {
|
|||||||
"cfproxy": True,
|
"cfproxy": True,
|
||||||
"cfproxy_priority": True,
|
"cfproxy_priority": True,
|
||||||
"cfproxy_user_domain": "",
|
"cfproxy_user_domain": "",
|
||||||
|
"cfproxy_worker_domain": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -273,6 +273,7 @@ def apply_proxy_config(cfg: dict) -> bool:
|
|||||||
pc.fallback_cfproxy = cfg.get("cfproxy", DEFAULT_CONFIG["cfproxy"])
|
pc.fallback_cfproxy = cfg.get("cfproxy", DEFAULT_CONFIG["cfproxy"])
|
||||||
pc.fallback_cfproxy_priority = cfg.get("cfproxy_priority", DEFAULT_CONFIG["cfproxy_priority"])
|
pc.fallback_cfproxy_priority = cfg.get("cfproxy_priority", DEFAULT_CONFIG["cfproxy_priority"])
|
||||||
pc.cfproxy_user_domain = cfg.get("cfproxy_user_domain", DEFAULT_CONFIG["cfproxy_user_domain"])
|
pc.cfproxy_user_domain = cfg.get("cfproxy_user_domain", DEFAULT_CONFIG["cfproxy_user_domain"])
|
||||||
|
pc.cfproxy_worker_domain = cfg.get("cfproxy_worker_domain", DEFAULT_CONFIG["cfproxy_worker_domain"])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user