kit-sub: Connection: keep-alive и ALPN http/1.1, как у nginx
На ответах, после которых соединение остаётся открытым, – «Connection: keep-alive», перед закрытием – «Connection: close» (раньше заголовка не было вовсе). На своём TLS (--multi-port) kit-sub соглашается на ALPN http/1.1, как nginx без http2; клиент, предложивший только h2, получает соединение без ALPN. Тесты – в kitsub_http_test.py.
This commit is contained in:
+1
-1
@@ -12,7 +12,7 @@
|
||||
* Исправил `--restore` на сервер с другим IP: ссылки части протоколов (адреса в таблице `hosts` панели и в настройках kit-sub) оставались со старым IP, а проверка панели после запуска могла ложно сообщить «Панель не отвечает», если панель стартует не сразу. А файлы настроек из копии `--restore` теперь читает строго по списку, как охранник на входе: лишнее имя – и копия не принимается, ничего не меняется.
|
||||
* Исправил `kit backup`: из `/root` он падал с «No such file or directory», работал только из `/`. Теперь работает откуда угодно. Если токен API панели в `/etc/x-ui/install-result.env` не подошёл (так бывает после обновления с 1.0), `kit` сам выпускает новый и сохраняет его, а если панель всё же не отвечает – говорит по-человечески (код ответа и вероятная причина), а не «Unsupported HTTP version in response».
|
||||
* Сайт-заглушка теперь у каждого сервера своя: при установке она собирается из двадцати тем и пяти вёрсток со случайным названием, текстами и цветами. Пекарня, переводчик или мастерская по велосипедам – как повезёт. Раньше у многих серверов был один и тот же «облачный» сосед.
|
||||
* `kit-sub` на своём порту (`--multi-port`) отвечает по HTTP/1.1 и с `Server: nginx` без лишнего пробела, а на бессмыслицу вместо запроса – честным `400 Bad Request` со строкой статуса. Как настоящий nginx, а не его двоюродный брат.
|
||||
* `kit-sub` на своём порту (`--multi-port`) отвечает по HTTP/1.1 и с `Server: nginx` без лишнего пробела, а на бессмыслицу вместо запроса – честным `400 Bad Request` со строкой статуса. Ещё он говорит `Connection: keep-alive` и соглашается на `http/1.1` при знакомстве по TLS. Как настоящий nginx, а не его двоюродный брат.
|
||||
* Исправил `kit net masq on`: Hysteria2 после него переставала слушать порт до перезапуска панели, а `kit net fp` и `kit net on` спотыкались о то же. Панель меняет подключение на ходу, а ядро такую правку не принимает. Теперь `kit` проверяет порты после каждой правки и, если что-то закрылось, перезапускает ядро и честно об этом говорит. А `kit check` научился смотреть, запущено ли ядро Xray и слушает ли каждое подключение свой порт, TCP и UDP отдельно: nginx на 443/tcp больше не выдаёт себя за Hysteria2 на 443/udp.
|
||||
* `--restore` на новый IP, где Let's Encrypt не выдал сертификат (лимит или закрытый порт 80), раньше говорил «Готово», а ядро Xray не запускалось: подключения ждали сертификат, которого нет. Теперь я ставлю самоподписанный, закрепляю его отпечаток в ссылках и прямо говорю об этом. А если ядро всё же не поднялось, «Готово» не будет: покажу, где смотреть. Копию с пустой папкой сертификата `--restore` теперь отклоняет сразу, а не на полпути установки.
|
||||
* JSON-подписка снова открывается в приложениях на Xray: там, где у vless не было `encryption`, подписка теперь дописывает `none`.
|
||||
|
||||
+8
-2
@@ -361,11 +361,16 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
def log_message(self, fmt, *args): # без IP клиентов в логах
|
||||
pass
|
||||
|
||||
def send_connection(self):
|
||||
# nginx на ответ с keep-alive пишет «Connection: keep-alive», перед закрытием – «Connection: close».
|
||||
self.send_header("Connection", "close" if self.close_connection else "keep-alive")
|
||||
|
||||
def send_plain(self, code, text=""):
|
||||
body = text.encode()
|
||||
self.send_response(code)
|
||||
self.send_header("Content-Type", "text/plain; charset=utf-8")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.send_connection()
|
||||
self.end_headers()
|
||||
if self.command != "HEAD":
|
||||
self.wfile.write(body)
|
||||
@@ -385,8 +390,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
self.send_response(code)
|
||||
self.send_header("Content-Type", "text/html")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
if self.close_connection:
|
||||
self.send_header("Connection", "close")
|
||||
self.send_connection()
|
||||
self.end_headers()
|
||||
if self.command != "HEAD":
|
||||
self.wfile.write(body)
|
||||
@@ -454,6 +458,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
||||
if v and "\r" not in v and "\n" not in v:
|
||||
self.send_header(k.title(), v)
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.send_connection()
|
||||
self.end_headers()
|
||||
if self.command != "HEAD":
|
||||
self.wfile.write(body)
|
||||
@@ -546,6 +551,7 @@ def main():
|
||||
return
|
||||
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
ctx.minimum_version = ssl.TLSVersion.TLSv1_2
|
||||
ctx.set_alpn_protocols(["http/1.1"]) # как nginx без http2: клиент предложил http/1.1 – соглашаемся
|
||||
ctx.load_cert_chain(cert, key)
|
||||
stamp = [os.path.getmtime(cert)]
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""Ответы kit-sub как у nginx (режим --multi-port): python3 tools/test/kitsub_http_test.py"""
|
||||
import json, os, socket, tempfile, threading, unittest, importlib.util
|
||||
import json, os, shutil, socket, ssl, subprocess, sys, tempfile, threading, time, unittest, importlib.util
|
||||
|
||||
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
tmp = tempfile.mkdtemp()
|
||||
@@ -58,6 +58,53 @@ class Http(unittest.TestCase):
|
||||
r = raw(b"GET / HTTP/9.9\r\n\r\n")
|
||||
self.assertTrue(r.startswith("HTTP/1.1 505"), r[:40])
|
||||
|
||||
def test_connection_header(self):
|
||||
r = raw(b"GET / HTTP/1.1\r\nHost: x\r\n\r\nGET /a HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n")
|
||||
first, second = r.split("HTTP/1.1 404", 2)[1:]
|
||||
self.assertIn("\r\nConnection: keep-alive\r\n", first)
|
||||
self.assertIn("\r\nConnection: close\r\n", second)
|
||||
self.assertEqual(r.count("Connection:"), 2)
|
||||
|
||||
def test_http10_closes(self):
|
||||
r = raw(b"GET / HTTP/1.0\r\n\r\n")
|
||||
self.assertTrue(r.startswith("HTTP/1.1 404"))
|
||||
self.assertIn("\r\nConnection: close\r\n", r)
|
||||
self.assertNotIn("<open>", r)
|
||||
|
||||
|
||||
@unittest.skipUnless(shutil.which("openssl"), "нужен openssl")
|
||||
class Tls(unittest.TestCase):
|
||||
"""Свой TLS (--multi-port): ALPN http/1.1, как у nginx без http2."""
|
||||
def test_alpn(self):
|
||||
d = tempfile.mkdtemp()
|
||||
cert, key = os.path.join(d, "c.pem"), os.path.join(d, "k.pem")
|
||||
subprocess.run(["openssl", "req", "-x509", "-nodes", "-newkey", "ec", "-pkeyopt", "ec_paramgen_curve:prime256v1",
|
||||
"-keyout", key, "-out", cert, "-subj", "/CN=1.2.3.4", "-days", "1"], check=True, capture_output=True)
|
||||
s = socket.socket(); s.bind(("127.0.0.1", 0)); port = s.getsockname()[1]; s.close()
|
||||
cfg = os.path.join(d, "config.json")
|
||||
with open(cfg, "w") as f:
|
||||
json.dump({"listen": "127.0.0.1", "port": port, "path": "/sub/", "upstream": "http://127.0.0.1:1/", "cert": cert, "key": key}, f)
|
||||
env = dict(os.environ, KIT_SUB_CONFIG=cfg, KIT_SUB_RULES=os.path.join(d, "rules.yaml"))
|
||||
env.pop("CREDENTIALS_DIRECTORY", None)
|
||||
p = subprocess.Popen([sys.executable, os.path.join(root, "scripts", "kit-sub.py")], env=env,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
try:
|
||||
ctx = ssl.create_default_context(); ctx.check_hostname = False; ctx.verify_mode = ssl.CERT_NONE
|
||||
for alpn, want in ((["h2", "http/1.1"], "http/1.1"), (["h2"], None)):
|
||||
ctx.set_alpn_protocols(alpn)
|
||||
for _ in range(50):
|
||||
try:
|
||||
c = socket.create_connection(("127.0.0.1", port), timeout=3)
|
||||
break
|
||||
except OSError:
|
||||
time.sleep(0.1)
|
||||
with ctx.wrap_socket(c) as t:
|
||||
self.assertEqual(t.selected_alpn_protocol(), want)
|
||||
t.sendall(b"GET / HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n")
|
||||
self.assertTrue(t.recv(64).startswith(b"HTTP/1.1 404"))
|
||||
finally:
|
||||
p.terminate(); p.wait()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user