Skip to content

Commit bb994bf

Browse files
Copilotyunwei37Copilot
authored
Fix curl hanging in XDP load balancer: backend servers reject mismatched Host headers (#198)
* Initial plan * Add simple HTTP server and update docs to fix curl hanging issue Co-authored-by: yunwei37 <34985212+yunwei37@users.noreply.github.com> * Fix code review issues: remove unnecessary shutdown() and fix spacing Co-authored-by: yunwei37 <34985212+yunwei37@users.noreply.github.com> * Add test script to verify HTTP servers handle mismatched Host headers Co-authored-by: yunwei37 <34985212+yunwei37@users.noreply.github.com> * Add comprehensive solution documentation for curl hanging issue Co-authored-by: yunwei37 <34985212+yunwei37@users.noreply.github.com> * Co-authored-by: yunwei37 <34985212+yunwei37@users.noreply.github.com> * Update src/42-xdp-loadbalancer/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: 云微 <1067852565@qq.com> * Update src/42-xdp-loadbalancer/README.zh.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: 云微 <1067852565@qq.com> * Update src/42-xdp-loadbalancer/SOLUTION.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: 云微 <1067852565@qq.com> * Delete src/42-xdp-loadbalancer/__pycache__/simple_http_server.cpython-312.pyc Signed-off-by: 云微 <1067852565@qq.com> * Delete src/42-xdp-loadbalancer/SOLUTION.md Signed-off-by: 云微 <1067852565@qq.com> --------- Signed-off-by: 云微 <1067852565@qq.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: yunwei37 <34985212+yunwei37@users.noreply.github.com> Co-authored-by: 云微 <1067852565@qq.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 8e4e94c commit bb994bf

4 files changed

Lines changed: 184 additions & 5 deletions

File tree

src/42-xdp-loadbalancer/README.md

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -470,9 +470,20 @@ You can test the setup by starting HTTP servers on the two backend namespaces (`
470470

471471
Start servers on `h2` and `h3`:
472472

473+
**Important**: The HTTP servers must bind to `0.0.0.0` so they listen on all local addresses in the namespace and can accept connections arriving via the load balancer. The forwarded HTTP requests will use the load balancer's virtual IP and port in the `Host` header (for example, `Host: 10.0.0.10:8000`), which may differ from the backend's own IP address; for this tutorial we use simple HTTP servers that accept such requests without enforcing strict `Host` header checks.
474+
475+
**Option 1**: Using the provided simple HTTP server (recommended):
476+
477+
```sh
478+
sudo ip netns exec h2 python3 simple_http_server.py &
479+
sudo ip netns exec h3 python3 simple_http_server.py &
480+
```
481+
482+
**Option 2**: Using Python's built-in http.server with explicit binding:
483+
473484
```sh
474-
sudo ip netns exec h2 python3 -m http.server
475-
sudo ip netns exec h3 python3 -m http.server
485+
sudo ip netns exec h2 python3 -m http.server --bind 0.0.0.0 &
486+
sudo ip netns exec h3 python3 -m http.server --bind 0.0.0.0 &
476487
```
477488

478489
Then, send a request to the load balancer IP:
@@ -483,6 +494,8 @@ curl 10.0.0.10:8000
483494

484495
The load balancer will distribute traffic to the backends (`h2` and `h3`) based on the hashing function.
485496

497+
> **Note**: If you experience hanging requests with `curl`, ensure the backend HTTP servers are bound to `0.0.0.0` and accept requests with any Host header. The XDP load balancer operates at Layer 3/4 (IP/TCP) and does not modify HTTP headers, so the Host header in requests will still show `10.0.0.10:8000` even though packets are forwarded to the backend IPs (10.0.0.2 or 10.0.0.3).
498+
486499
### Monitoring with `bpf_printk`
487500

488501
You can monitor the load balancer's activity by checking the `bpf_printk` logs. The BPF program prints diagnostic messages whenever a packet is processed. You can view these logs using:
@@ -507,6 +520,24 @@ Example output:
507520

508521
### Debugging Issues
509522

523+
#### Curl Requests Hanging
524+
525+
If `curl` requests to the load balancer hang and never complete, the most likely cause is that the backend HTTP servers are rejecting requests with mismatched Host headers.
526+
527+
**Problem**: When you run `curl 10.0.0.10:8000`, the HTTP request includes a Host header set to `10.0.0.10:8000`. The XDP load balancer forwards the packet at Layer 3/4 (IP/TCP) to a backend server (10.0.0.2 or 10.0.0.3), but the HTTP headers remain unchanged. If the backend HTTP server validates the Host header and expects it to match its own IP address, it may reject or drop the request.
528+
529+
**Solution**: Ensure backend HTTP servers bind to `0.0.0.0` and accept requests with any Host header:
530+
- Use `python3 simple_http_server.py` (provided in this directory), or
531+
- Use `python3 -m http.server --bind 0.0.0.0`
532+
533+
**Verification**: Check the backend server logs to see if requests are being received. You can also use `tcpdump` in the backend namespace to verify packets are arriving:
534+
535+
```sh
536+
sudo ip netns exec h2 tcpdump -i veth2 -n port 8000
537+
```
538+
539+
#### XDP Packet Forwarding Issues
540+
510541
Some systems may experience packet loss or failure to forward packets due to issues similar to those described in this [blog post](https://fedepaol.github.io/blog/2023/09/11/xdp-ate-my-packets-and-how-i-debugged-it/). You can debug these issues using `bpftrace` to trace XDP errors:
511542

512543
```sh

src/42-xdp-loadbalancer/README.zh.md

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -463,13 +463,25 @@ Press Ctrl+C to exit...
463463

464464
### 测试设置
465465

466-
您可以通过在两个后端命名空间(`h2``h3`)启动 HTTP 服务器并从本地机器向负载均衡器发送请求来测试设置:
466+
您可以通过在两个后端命名空间(`h2``h3`)启动 HTTP 服务器, 并从本地机器向负载均衡器发送请求来测试设置:
467467

468468
`h2``h3` 上启动服务器:
469469

470+
**重要提示(监听地址)**:在本实验环境中,HTTP 服务器应绑定到 `0.0.0.0`,这样它会在该网络命名空间中的所有本地地址上监听,从而能够接受通过后端 IP 转发过来的连接。
471+
472+
**重要提示(Host 头)**:负载均衡器对外暴露的虚拟 IP 地址是 `10.0.0.10`,并以该地址作为目标 IP 转发 HTTP 请求,因此请求中的 Host 头将类似于 `Host: 10.0.0.10:8000`,这与后端命名空间内实际分配给服务器进程的本地 IP 不同。是否接受这样的 Host 头由具体的 HTTP 服务器实现决定,与是否绑定到 `0.0.0.0` 无关;本教程中使用的 `simple_http_server.py``python -m http.server` 默认都会接受这种 Host 头,无需额外配置。
473+
**选项 1**:使用提供的简单 HTTP 服务器(推荐):
474+
475+
```sh
476+
sudo ip netns exec h2 python3 simple_http_server.py &
477+
sudo ip netns exec h3 python3 simple_http_server.py &
478+
```
479+
480+
**选项 2**:使用 Python 内置的 http.server 并显式绑定:
481+
470482
```sh
471-
sudo ip netns exec h2 python3 -m http.server
472-
sudo ip netns exec h3 python3 -m http.server
483+
sudo ip netns exec h2 python3 -m http.server --bind 0.0.0.0 &
484+
sudo ip netns exec h3 python3 -m http.server --bind 0.0.0.0 &
473485
```
474486

475487
然后,向负载均衡器 IP 发送请求:
@@ -480,6 +492,8 @@ curl 10.0.0.10:8000
480492

481493
负载均衡器将根据哈希函数将流量分配到后端服务器(`h2``h3`)。
482494

495+
> **注意**:如果您使用 `curl` 时遇到请求挂起的问题,请确保后端 HTTP 服务器绑定到 `0.0.0.0` 并接受任何 Host 头的请求。XDP 负载均衡器在第 3/4 层(IP/TCP)运行,不会修改 HTTP 头,因此请求中的 Host 头仍将显示 `10.0.0.10:8000`,即使数据包被转发到后端 IP(10.0.0.2 或 10.0.0.3)。
496+
483497
### 使用 `bpf_printk` 进行监控
484498

485499
您可以通过查看 `bpf_printk` 日志来监控负载均衡器的活动。BPF 程序在处理每个数据包时会打印诊断消息。您可以使用以下命令查看这些日志:
@@ -504,6 +518,24 @@ sudo cat /sys/kernel/debug/tracing/trace_pipe
504518

505519
### 调试问题
506520

521+
#### Curl 请求挂起
522+
523+
如果对负载均衡器的 `curl` 请求挂起且永不完成,最可能的原因是后端 HTTP 服务器拒绝了具有不匹配 Host 头的请求。
524+
525+
**问题**:当您运行 `curl 10.0.0.10:8000` 时,HTTP 请求包含设置为 `10.0.0.10:8000` 的 Host 头。XDP 负载均衡器在第 3/4 层(IP/TCP)将数据包转发到后端服务器(10.0.0.2 或 10.0.0.3),但 HTTP 头保持不变。如果后端 HTTP 服务器验证 Host 头并期望它与自己的 IP 地址匹配,它可能会拒绝或丢弃该请求。
526+
527+
**解决方案**:确保后端 HTTP 服务器绑定到 `0.0.0.0` 并接受任何 Host 头的请求:
528+
- 使用 `python3 simple_http_server.py`(在此目录中提供),或
529+
- 使用 `python3 -m http.server --bind 0.0.0.0`
530+
531+
**验证**:检查后端服务器日志以查看是否收到请求。您还可以在后端命名空间中使用 `tcpdump` 验证数据包是否到达:
532+
533+
```sh
534+
sudo ip netns exec h2 tcpdump -i veth2 -n port 8000
535+
```
536+
537+
#### XDP 数据包转发问题
538+
507539
某些系统可能会因为类似于此[博客文章](https://fedepaol.github.io/blog/2023/09/11/xdp-ate-my-packets-and-how-i-debugged-it/)中描述的问题而导致数据包丢失或转发失败。您可以使用 `bpftrace` 跟踪 XDP 错误进行调试:
508540

509541
```sh
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Simple HTTP server that doesn't validate the Host header.
4+
This server is designed to work with load balancers that forward requests
5+
with mismatched Host headers.
6+
"""
7+
8+
import sys
9+
import http.server
10+
import socketserver
11+
12+
class SimpleHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
13+
"""
14+
HTTP request handler that doesn't validate the Host header.
15+
This allows the server to work behind a load balancer.
16+
"""
17+
18+
def version_string(self):
19+
"""Return the server software version string."""
20+
return f'SimpleHTTP/{sys.version.split()[0]}'
21+
22+
def log_message(self, format, *args):
23+
"""Log an arbitrary message."""
24+
# Include the Host header in the log for debugging
25+
host_header = self.headers.get('Host', 'unknown')
26+
sys.stderr.write(f"[{self.log_date_time_string()}] Host: {host_header} - {format % args}\n")
27+
28+
29+
def main():
30+
PORT = 8000
31+
32+
# Allow reuse of address to avoid "Address already in use" errors
33+
socketserver.TCPServer.allow_reuse_address = True
34+
35+
# Bind to 0.0.0.0 to accept connections from any interface
36+
with socketserver.TCPServer(("0.0.0.0", PORT), SimpleHTTPRequestHandler) as httpd:
37+
print(f"Server listening on 0.0.0.0:{PORT}")
38+
print("This server accepts requests with any Host header.")
39+
print("Press Ctrl+C to stop the server.")
40+
try:
41+
httpd.serve_forever()
42+
except KeyboardInterrupt:
43+
print("\nShutting down server...")
44+
45+
46+
if __name__ == "__main__":
47+
main()
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/bin/bash
2+
#
3+
# Test script to verify that the HTTP servers correctly handle
4+
# requests with mismatched Host headers, which is essential for
5+
# the XDP load balancer to work correctly.
6+
#
7+
# This test demonstrates that the fix resolves the curl hanging issue.
8+
9+
set -e
10+
11+
cd "$(dirname "$0")"
12+
13+
echo "=== Testing HTTP Server with Mismatched Host Headers ==="
14+
echo ""
15+
16+
# Test 1: simple_http_server.py (uses port 8000 by default)
17+
echo "Test 1: Testing simple_http_server.py"
18+
echo "Starting simple_http_server.py on default port 8000..."
19+
python3 simple_http_server.py > /tmp/simple_http_test.log 2>&1 &
20+
SERVER_PID=$!
21+
sleep 3
22+
23+
# Test with mismatched Host header
24+
echo "Sending request with Host: 10.0.0.10:8000 to 127.0.0.1:8000..."
25+
RESPONSE=$(curl -s -H "Host: 10.0.0.10:8000" http://127.0.0.1:8000/ 2>&1 | head -5)
26+
if [ -n "$RESPONSE" ]; then
27+
echo "✓ simple_http_server.py successfully handled request with mismatched Host header"
28+
else
29+
echo "✗ simple_http_server.py failed to handle request"
30+
kill $SERVER_PID 2>/dev/null
31+
exit 1
32+
fi
33+
34+
# Check server logs
35+
echo "Server log snippet:"
36+
tail -n 1 /tmp/simple_http_test.log
37+
kill $SERVER_PID 2>/dev/null
38+
sleep 1
39+
echo ""
40+
41+
# Test 2: python -m http.server --bind 0.0.0.0
42+
echo "Test 2: Testing python3 -m http.server --bind 0.0.0.0"
43+
echo "Starting http.server on port 8001..."
44+
python3 -m http.server --bind 0.0.0.0 8001 > /tmp/builtin_http_test.log 2>&1 &
45+
SERVER_PID=$!
46+
sleep 3
47+
48+
# Test with mismatched Host header
49+
echo "Sending request with Host: 10.0.0.10:8001 to 127.0.0.1:8001..."
50+
RESPONSE=$(curl -s -H "Host: 10.0.0.10:8001" http://127.0.0.1:8001/ 2>&1 | head -5)
51+
if [ -n "$RESPONSE" ]; then
52+
echo "✓ http.server --bind 0.0.0.0 successfully handled request with mismatched Host header"
53+
else
54+
echo "✗ http.server --bind 0.0.0.0 failed to handle request"
55+
kill $SERVER_PID 2>/dev/null
56+
exit 1
57+
fi
58+
59+
# Check server logs
60+
echo "Server log snippet:"
61+
tail -n 1 /tmp/builtin_http_test.log
62+
kill $SERVER_PID 2>/dev/null
63+
sleep 1
64+
echo ""
65+
66+
echo "=== All Tests Passed ==="
67+
echo ""
68+
echo "Both HTTP server options correctly handle requests with mismatched Host headers,"
69+
echo "which fixes the curl hanging issue in the XDP load balancer setup."

0 commit comments

Comments
 (0)