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() +