You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by gs...@apache.org on 2026/09/01 23:45:22 UTC
(tooling-llmao) branch main updated: Stop wrapping Quart asgi_app with WSGI ProxyFix.
This is an automated email from the ASF dual-hosted git repository.
gstein pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-llmao.git
The following commit(s) were added to refs/heads/main by this push:
new 7777b21 Stop wrapping Quart asgi_app with WSGI ProxyFix.
7777b21 is described below
commit 7777b210171010c21d850271e7a8ea693afff864
Author: Greg Stein <gs...@gmail.com>
AuthorDate: Tue Sep 1 18:45:16 2026 -0500
Stop wrapping Quart asgi_app with WSGI ProxyFix.
Werkzeug ProxyFix is WSGI and 500'd Hypercorn. Read leftmost
X-Forwarded-For in the /vllm/config handler instead, and log when
that header is present.
Co-authored-by: Grok <gr...@x.ai>
---
api.py | 9 ++++++---
docs/vllm-fleet-design.md | 4 ++--
llmao/fleet.py | 14 ++++++++++++++
main.py | 6 ------
tests/test_fleet.py | 10 ++++++++++
5 files changed, 32 insertions(+), 11 deletions(-)
diff --git a/api.py b/api.py
index 5eefd81..b52664a 100644
--- a/api.py
+++ b/api.py
@@ -29,7 +29,7 @@ from asfquart.auth import Requirements as R
from quart import jsonify, request
from llmao.auth import current_identity
-from llmao.fleet import UnknownHost, config_for_host, normalize_peer_ip
+from llmao.fleet import UnknownHost, client_ip, config_for_host
from llmao.seam import AuthzError
APP = asfquart.APP
@@ -117,7 +117,7 @@ def _fleet_key() -> str:
@APP.get("/vllm/config")
@api
async def vllm_config():
- """JSON for the calling box. Bearer fleet key; host from peer IP (ProxyFix)."""
+ """JSON for the calling box. Bearer fleet key; host from peer IP / X-Forwarded-For."""
expected = _fleet_key()
if not expected or expected.startswith("CHANGE_ME"):
raise HttpError(503, "fleet.key is not configured")
@@ -128,7 +128,10 @@ async def vllm_config():
presented = auth[len(prefix):].strip()
if not hmac.compare_digest(presented, expected):
raise HttpError(403, "invalid fleet key")
- host = normalize_peer_ip(request.remote_addr)
+ host = client_ip(
+ remote_addr=request.remote_addr,
+ forwarded_for=request.headers.get("X-Forwarded-For"),
+ )
try:
payload = config_for_host(host, cfg=APP.cfg)
except UnknownHost:
diff --git a/docs/vllm-fleet-design.md b/docs/vllm-fleet-design.md
index f4a5412..61d2029 100644
--- a/docs/vllm-fleet-design.md
+++ b/docs/vllm-fleet-design.md
@@ -58,7 +58,7 @@ flowchart TB
- The real API keys used between LiteLLM and each vLLM server
- Logical **hosts** (models + ports on one GPU box, keyed by public IP)
- Each GPU box receives the **fleet key** on the template. asfquart maps
- `request.remote_addr` (ProxyFix / X-Forwarded-For behind Hypercorn or Apache)
+ leftmost `X-Forwarded-For` if present, else `request.remote_addr` (Apache/Hypercorn in front)
to `fleet.hosts`.
- At box-start, the provider installer fetches JSON for that IP
and installs native process units (Vast: Supervisor). There is no on-disk
@@ -108,7 +108,7 @@ Response: 200 application/json
```
- asfquart validates the fleet key (not OAuth).
-- Looks up `fleet.hosts` by client IP (`request.remote_addr` after ProxyFix).
+- Looks up `fleet.hosts` by client IP (`X-Forwarded-For` or `request.remote_addr`).
- Emits JSON (host, servers: name, HF weights id, host, port, args, API keys).
---
diff --git a/llmao/fleet.py b/llmao/fleet.py
index b09c481..8cdf769 100644
--- a/llmao/fleet.py
+++ b/llmao/fleet.py
@@ -41,6 +41,20 @@ def normalize_peer_ip(addr: str | None) -> str:
return ip
+def client_ip(*, remote_addr: str | None, forwarded_for: str | None) -> str:
+ """Box public IP: leftmost X-Forwarded-For (one reverse proxy), else peer.
+
+ Werkzeug ProxyFix is WSGI and cannot wrap Quart asgi_app (ASGI).
+ """
+ if forwarded_for and forwarded_for.strip():
+ chosen = normalize_peer_ip(forwarded_for.split(",")[0])
+ _LOGGER.info(
+ f"X-Forwarded-For={forwarded_for!r} remote_addr={remote_addr!r} client_ip={chosen}"
+ )
+ return chosen
+ return normalize_peer_ip(remote_addr)
+
+
def parse_host_row(raw: Any, host: str, index: int) -> tuple[str, int, str]:
if not isinstance(raw, (list, tuple)) or len(raw) not in (2, 3):
raise ValueError(
diff --git a/main.py b/main.py
index 9c0eab3..6c8e6f6 100644
--- a/main.py
+++ b/main.py
@@ -49,12 +49,6 @@ def create_app():
force_login=True,
)
- # Hypercorn and/or Apache sit in front; one trusted hop so
- # request.remote_addr is the GPU box public IP (fleet.hosts key).
- from werkzeug.middleware.proxy_fix import ProxyFix
-
- app.asgi_app = ProxyFix(app.asgi_app, x_for=1, x_proto=1)
-
from llmao.auth import make_token_handler
from llmao.litellm_client import LiteLLMBackend
from llmao.seam import Seam
diff --git a/tests/test_fleet.py b/tests/test_fleet.py
index 7234529..b8f2f10 100644
--- a/tests/test_fleet.py
+++ b/tests/test_fleet.py
@@ -7,6 +7,7 @@ from easydict import EasyDict as edict
from llmao.fleet import (
UnknownHost,
+ client_ip,
config_for_host,
normalize_peer_ip,
validate_fleet,
@@ -102,3 +103,12 @@ def test_unknown_model():
def test_normalize_peer_ip():
assert normalize_peer_ip("::ffff:203.0.113.10") == "203.0.113.10"
assert normalize_peer_ip("203.0.113.10") == "203.0.113.10"
+
+
+def test_client_ip_xff_leftmost():
+ assert client_ip(remote_addr="10.0.0.1", forwarded_for="203.0.113.10, 10.0.0.1") == "203.0.113.10"
+
+
+def test_client_ip_no_xff():
+ assert client_ip(remote_addr="203.0.113.10", forwarded_for=None) == "203.0.113.10"
+ assert client_ip(remote_addr="::ffff:203.0.113.10", forwarded_for="") == "203.0.113.10"
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org