From 45513c90b7498df05cf40b5fd24f0ef192122b1a Mon Sep 17 00:00:00 2001 From: ThearchyHelios Date: Mon, 19 Oct 2026 15:09:10 +0800 Subject: [PATCH] =?UTF-8?q?R-b12=20=E7=9A=84=E5=B0=BE=E5=B7=B4=EF=BC=9Ache?= =?UTF-8?q?ck=5Fpgbouncer=20=E5=88=A4=E5=90=8C=E4=B8=80=E4=B8=AA=E4=B8=BB?= =?UTF-8?q?=E6=9C=BA=E7=AB=AF=E5=8F=A3=E6=97=B6=E6=8A=8A=E4=B8=BB=E6=9C=BA?= =?UTF-8?q?=E5=90=8D=E8=A7=84=E8=8C=83=E5=8C=96=E2=80=94=E2=80=94localhost?= =?UTF-8?q?=E3=80=81127.x.x.x=E3=80=81::1=20=E9=83=BD=E7=AE=97=E6=9C=AC?= =?UTF-8?q?=E6=9C=BA=E5=9B=9E=E7=8E=AF=EF=BC=8CIP=20=E6=8C=89=E5=9C=B0?= =?UTF-8?q?=E5=9D=80=E6=AF=94=EF=BC=88::ffff:10.0.0.5=20=E5=92=8C=2010.0.0?= =?UTF-8?q?.5=20=E7=9B=B8=E5=90=8C=EF=BC=89=EF=BC=8C=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E4=B8=8D=E6=88=90=20IP=20=E7=9A=84=E6=8C=89=E5=AD=97=E9=9D=A2?= =?UTF-8?q?=EF=BC=88=E5=B0=8F=E5=86=99=EF=BC=89=E6=AF=94=E3=80=81=E4=B8=8D?= =?UTF-8?q?=E6=9F=A5=20DNS=E3=80=82=E8=A1=A5=E6=B5=8B=E8=AF=95=EF=BC=9Aloc?= =?UTF-8?q?alhost=20=E5=AF=B9=20127.0.0.1=E3=80=81127.0.0.1=20=E5=AF=B9=20?= =?UTF-8?q?::1=E3=80=81IPv4=20=E6=98=A0=E5=B0=84=E5=9C=B0=E5=9D=80?= =?UTF-8?q?=E5=90=8C=E7=AB=AF=E5=8F=A3=E9=83=BD=E6=8B=92=E7=BB=9D=EF=BC=8C?= =?UTF-8?q?=E6=9C=AC=E6=9C=BA=E5=8F=A6=E4=B8=80=E4=B8=AA=E7=AB=AF=E5=8F=A3?= =?UTF-8?q?=E3=80=81=E5=90=8D=E5=AD=97=E4=B8=8D=E5=90=8C=E7=9A=84=E6=94=BE?= =?UTF-8?q?=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/db/base.py | 18 ++++++++++++++++-- backend/tests/test_db_direct.py | 22 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/backend/app/db/base.py b/backend/app/db/base.py index f3bf306b..80e9cf93 100644 --- a/backend/app/db/base.py +++ b/backend/app/db/base.py @@ -318,9 +318,23 @@ def check_pgbouncer() -> None: def _endpoint(url: str) -> tuple[str, int]: - """地址连的是哪个主机端口(端口不写按 5432)。""" + """地址连的是哪个主机端口(端口不写按 5432)。主机名规范化:localhost、127.x.x.x、::1 都算本机回环;IP 地址按地址比 + (::ffff:10.0.0.5 和 10.0.0.5 相同);解析不成 IP 的按字面(小写)比,不查 DNS。""" + import ipaddress + parsed = make_url(url) - return (parsed.host or "").lower(), int(parsed.port or 5432) + host = (parsed.host or "").strip().strip("[]").lower().rstrip(".") + try: + addr = ipaddress.ip_address(host) + except ValueError: + addr = None + if isinstance(addr, ipaddress.IPv6Address) and addr.ipv4_mapped is not None: + addr = addr.ipv4_mapped + if host == "localhost" or host.endswith(".localhost") or (addr is not None and addr.is_loopback): + host = "loopback" + elif addr is not None: + host = str(addr) + return host, int(parsed.port or 5432) async def get_session() -> AsyncIterator[AsyncSession]: diff --git a/backend/tests/test_db_direct.py b/backend/tests/test_db_direct.py index b7b152f8..13c52dd0 100644 --- a/backend/tests/test_db_direct.py +++ b/backend/tests/test_db_direct.py @@ -109,3 +109,25 @@ def test_copying_checkpoints_goes_through_the_direct_url(monkeypatch, tmp_path): assert seen["direct_url"] == settings.database_direct_url, "目标就是应用库:断点走它的直连地址" assert to_postgres.main([str(tmp_path), "postgresql://u:p@elsewhere.internal:5432/x"]) == 2 assert seen["direct_url"] is None, "目标是别的库:不套用应用库的直连地址" + + +def test_loopback_spellings_count_as_the_same_endpoint(monkeypatch): + """R-b12 的尾巴:localhost、127.0.0.1、::1 是同一台机器——同一个端口时照样拒绝;IP 按地址比,解析不成 IP 的按字面比。""" + from app.db import base + + def norm(url: str) -> str: + return Settings(database_url=url, _env_file=None).database_url + + monkeypatch.setattr(settings, "db_pgbouncer", True) + for app_url, direct in (("postgresql://u:p@localhost:6432/db", "postgresql://u:p@127.0.0.1:6432/db"), + ("postgresql://u:p@127.0.0.1:6432/db", "postgresql://u:p@[::1]:6432/db"), + ("postgresql://u:p@10.0.0.5:6432/db", "postgresql://u:p@[::ffff:10.0.0.5]:6432/db")): + monkeypatch.setattr(settings, "database_url", norm(app_url)) + monkeypatch.setattr(settings, "database_direct_url", norm(direct)) + with pytest.raises(RuntimeError, match="指着同一个地址"): + base.check_pgbouncer() + monkeypatch.setattr(settings, "database_url", norm("postgresql://u:p@localhost:6432/db")) + monkeypatch.setattr(settings, "database_direct_url", norm("postgresql://u:p@127.0.0.1:5432/db")) + base.check_pgbouncer() # 本机另一个端口:放行 + monkeypatch.setattr(settings, "database_direct_url", norm("postgresql://u:p@db.internal:6432/db")) + base.check_pgbouncer() # 名字不同、不查 DNS:按字面算不同