From 6d451c33d18f9f218fab3faf346bcde93a931d5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sat, 15 Aug 2026 20:38:01 +0400 Subject: [PATCH] =?UTF-8?q?HISTORY:=20=D0=BF=D0=B5=D1=80=D0=B8=D0=BE=D0=B4?= =?UTF-8?q?=20=D1=82=D0=B0=D0=B9=D0=BC=D0=B0=D1=83=D1=82=D0=BE=D0=B2=2032.?= =?UTF-8?q?9=D1=81=20(18=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D0=B2=D0=B0?= =?UTF-8?q?=D0=BB=D0=BE=D0=B2=2032.7-33.0=D1=81)=20=E2=80=94=20=D1=82?= =?UTF-8?q?=D0=B0=D0=B9=D0=BC=D0=B5=D1=80=20=D0=B4=D0=BE=D0=BA=D0=B0=D0=B7?= =?UTF-8?q?=D0=B0=D0=BD;=20=D0=BF=D0=BE=D0=BB=D0=BD=D0=B0=D1=8F=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B0=20=D1=81=D0=BA=D1=80?= =?UTF-8?q?=D0=B8=D0=BF=D1=82=D0=BE=D0=B2=20=D0=B8=20=D0=BA=D1=80=D0=B5?= =?UTF-8?q?=D0=B4=D0=BE=D0=B2=20OK;=20+tcp=5Fmtu=5Fprobe.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/tcp_mtu_probe.py | 90 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 tests/tcp_mtu_probe.py diff --git a/tests/tcp_mtu_probe.py b/tests/tcp_mtu_probe.py new file mode 100644 index 0000000..24c8aeb --- /dev/null +++ b/tests/tcp_mtu_probe.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +"""tcp_mtu_probe.py — бинарный поиск MTU-порога по TCP к шлюзу Nubes. + +Критерий: время до ответа nginx. Тело дошло → мгновенный ответ (<2с). +Сегмент дропнут (MTU/PMTUD) → ответ не приходит (nginx ждёт тело) → считаем +"не прошло" по таймауту FAST_TIMEOUT. Порог ищем делением пополам между +LO и HI, потом уточняем повторными пробами вокруг порога. +""" +import socket +import ssl +import time + +HOST = "185.247.187.151" +PORT = 443 # реальный путь клиентов; 32391 (NodePort) снаружи закрыт +LO = 512 # заведомо проходит +HI = 20000 # заведомо не проходит (14+ сегментов по MSS) +FAST_TIMEOUT = 20 # быстрее полного зависания ~51с +FAST_OK = 2.0 # ответ быстрее этого = прошло + +ctx = ssl.create_default_context() +ctx.check_hostname = False +ctx.verify_mode = ssl.CERT_NONE + + +def probe(n): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(FAST_TIMEOUT) + t0 = time.time() + try: + s.connect((HOST, PORT)) + mss = s.getsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG) + ss = ctx.wrap_socket(s, server_hostname="sqs.containerk8s.dev.nubes.ru") + body = b"x" * n + req = (b"POST / HTTP/1.1\r\n" + b"Host: sqs.containerk8s.dev.nubes.ru\r\n" + b"Content-Type: application/x-www-form-urlencoded\r\n" + b"Content-Length: " + str(n).encode() + b"\r\n" + b"Connection: close\r\n\r\n" + body) + ss.sendall(req) + data = ss.recv(4096) + dt = time.time() - t0 + first = data.split(b"\r\n")[0][:40].decode("latin1", "replace") + return dt, mss, first + except Exception as e: + return time.time() - t0, None, "%s" % type(e).__name__ + finally: + try: + s.close() + except Exception: + pass + + +def passed(n): + dt, mss, resp = probe(n) + ok = dt < FAST_OK + print(" size=%6d: %.2fs mss=%s %s -> %s" % + (n, dt, mss if mss else "-", resp, "OK" if ok else "DROP"), flush=True) + return ok + + +def main(): + # санпроверка границ + print("check LO=%d:" % LO) + if not passed(LO): + print("LO не проходит — границы неверны, выход") + return + print("check HI=%d:" % HI) + if passed(HI): + print("HI тоже проходит мгновенно — порога нет в диапазоне, выход") + return + + lo, hi = LO, HI + while hi - lo > 50: + mid = (lo + hi) // 2 + if passed(mid): + lo = mid + else: + hi = mid + print("\nпорог найден: lo=%d hi=%d (проходит <=lo, дроп >=hi)" % (lo, hi)) + + # уточнение вокруг порога: 3 пробы на каждую из 4 точек рядом + print("\nуточнение (по 3 пробы):") + for n in (lo - 200, lo, (lo + hi) // 2, hi + 200): + res = sum(1 for _ in range(3) if passed(max(n, 1))) + print(" size=%d: %d/3 прошли" % (n, res)) + + +if __name__ == "__main__": + main() +