Skip to content

haproxy: T8931: Improve WebSocket support for HAProxy#5226

Open
indrajitr wants to merge 3 commits into
vyos:rollingfrom
indrajitr:haproxy-websocket
Open

haproxy: T8931: Improve WebSocket support for HAProxy#5226
indrajitr wants to merge 3 commits into
vyos:rollingfrom
indrajitr:haproxy-websocket

Conversation

@indrajitr

Copy link
Copy Markdown
Contributor

Change summary

Improve WebSocket support in HAProxy by allowing control for two configuration parameters:

  • Support for timeout.tunnel in default section with option to override in backend section
  • Enable option http-server-close in backend section

Generally, WebSocket support in HAProxy in VyOS decent. Ability to adjust these couple of parameters provide additional control on WebSocket behavior.

For details of the parameters, see:

Also, see general guidelines of setting up WebSocket in HAProxy here.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Code style update (formatting, renaming)
  • Refactoring (no functional changes)
  • Migration from an old Vyatta component to vyos-1x, please link to related PR inside obsoleted component
  • Other (please describe):

Related Task(s)

https://vyos.dev/T8931

Related PR(s)

How to test / Smoketest result

  1. Set the maximum inactivity time on the client and server side for tunnels (default: 300)

    a. default section:

    # set load-balancing haproxy timeout tunnel 1800
    

    b. backend section override:

    # set load-balancing haproxy backend back-01 timeout tunnel 3600
    
  2. Set Option http-server-close in backend section:

    # set load-balancing haproxy backend back-01 http-server-close
    

Smoketest result:

vyos@v26:~$ python3 /usr/libexec/vyos/tests/smoke/cli/test_load-balancing_haproxy.py 
test_reverse_proxy_backend_http_check (__main__.TestLoadBalancingReverseProxy.test_reverse_proxy_backend_http_check) ... ok
test_reverse_proxy_backend_ssl_no_verify (__main__.TestLoadBalancingReverseProxy.test_reverse_proxy_backend_ssl_no_verify) ... ok
test_reverse_proxy_backend_websocket (__main__.TestLoadBalancingReverseProxy.test_reverse_proxy_backend_websocket) ... ok
test_reverse_proxy_ca_not_exists (__main__.TestLoadBalancingReverseProxy.test_reverse_proxy_ca_not_exists) ... ok
test_reverse_proxy_cert_not_exists (__main__.TestLoadBalancingReverseProxy.test_reverse_proxy_cert_not_exists) ... ok
test_reverse_proxy_domain (__main__.TestLoadBalancingReverseProxy.test_reverse_proxy_domain) ... ok
test_reverse_proxy_http_compression (__main__.TestLoadBalancingReverseProxy.test_reverse_proxy_http_compression) ... ok
test_reverse_proxy_http_redirect (__main__.TestLoadBalancingReverseProxy.test_reverse_proxy_http_redirect) ... ok
test_reverse_proxy_http_response_headers (__main__.TestLoadBalancingReverseProxy.test_reverse_proxy_http_response_headers) ... ok
test_reverse_proxy_listen_address_no_port_conflict (__main__.TestLoadBalancingReverseProxy.test_reverse_proxy_listen_address_no_port_conflict) ... ok
test_reverse_proxy_logging (__main__.TestLoadBalancingReverseProxy.test_reverse_proxy_logging) ... ok
test_reverse_proxy_tcp_health_checks (__main__.TestLoadBalancingReverseProxy.test_reverse_proxy_tcp_health_checks) ... ok
test_reverse_proxy_tcp_health_checks_custom_port (__main__.TestLoadBalancingReverseProxy.test_reverse_proxy_tcp_health_checks_custom_port) ... ok
test_reverse_proxy_tcp_mode (__main__.TestLoadBalancingReverseProxy.test_reverse_proxy_tcp_mode) ... ok
test_reverse_proxy_timeout (__main__.TestLoadBalancingReverseProxy.test_reverse_proxy_timeout) ... ok

----------------------------------------------------------------------
Ran 15 tests in 833.048s

OK

Checklist:

  • I have read the CONTRIBUTING document
  • I have linked this PR to one or more Phabricator Task(s)
  • I have run the components SMOKETESTS if applicable
  • My commit headlines contain a valid Task id
  • My change requires a change to the documentation
  • I have updated the documentation accordingly

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 5c4d8ec9-5825-4ff0-b53f-29bf034a29ea

📥 Commits

Reviewing files that changed from the base of the PR and between 79b68b0 and 49e04f8.

📒 Files selected for processing (6)
  • data/templates/load-balancing/haproxy.cfg.j2
  • interface-definitions/include/haproxy/timeout-tunnel.xml.i
  • interface-definitions/include/haproxy/timeout.xml.i
  • interface-definitions/load-balancing_haproxy.xml.in
  • smoketest/scripts/cli/test_load-balancing_haproxy.py
  • src/conf_mode/load-balancing_haproxy.py
✅ Files skipped from review due to trivial changes (1)
  • interface-definitions/include/haproxy/timeout.xml.i
🚧 Files skipped from review as they are similar to previous changes (4)
  • interface-definitions/include/haproxy/timeout-tunnel.xml.i
  • data/templates/load-balancing/haproxy.cfg.j2
  • interface-definitions/load-balancing_haproxy.xml.in
  • smoketest/scripts/cli/test_load-balancing_haproxy.py
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build_iso
  • GitHub Check: Mergify Merge Protections
  • GitHub Check: Summary
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Python code must use Python 3.11 or higher

Files:

  • src/conf_mode/load-balancing_haproxy.py
src/conf_mode/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Conf-mode entry-point scripts must be named after CLI components and located in src/conf_mode/

Files:

  • src/conf_mode/load-balancing_haproxy.py
{**/*.py,data/templates/**/*}

📄 CodeRabbit inference engine (AGENTS.md)

Linting must use ruff 0.6.4, darker, pylint W0611, and Jinja2 lint as enforced by vyos/.github reusables

Files:

  • src/conf_mode/load-balancing_haproxy.py
🔍 Remote MCP Context7

Additional factual context for PR review (concise)

  • timeout tunnel: HAProxy config directive that "sets the maximum inactivity time on both client and server sides for tunnels" and "supersedes client and server timeouts once a connection becomes a tunnel, such as with WebSocket or CONNECT requests." Example usage: timeout tunnel 1m and recommended pairing with timeout client-fin for certain network conditions.

  • option http-server-close: HAProxy option that enables HTTP/1.x server-side connection closing while keeping client-side keep-alive/pipelining support. Applicable in defaults, frontend, listen, and backend sections; can be negated with no option http-server-close. Example usage appears under defaults http in documentation snippets.

Tools / sources used:

  • Resolved HAProxy library ID: /haproxy/haproxy.
  • Queried HAProxy configuration documentation for timeout tunnel and option http-server-close.
🔇 Additional comments (1)
src/conf_mode/load-balancing_haproxy.py (1)

152-157: LGTM!


📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added HAProxy tunnel timeout (global and per-backend, default 300s; range 1–3600s).
    • Added HTTP server-side connection closing option for HAProxy backends.
  • Bug Fixes / Validation

    • Enforced that the HTTP server-close option is only valid for HTTP-mode backends.
  • Tests

    • Added/updated smoketests to verify tunnel timeouts, http-server-close behavior, and related config rendering.

Walkthrough

Adds a tunnel timeout leaf and a valueless http-server-close backend leaf; conditionally renders timeout tunnel in defaults and per-backend and option http-server-close in HTTP backends; enforces backend-mode validation and adds tests.

Changes

HAProxy tunnel timeout and server-close option support

Layer / File(s) Summary
Configuration schema for tunnel timeout and http-server-close
interface-definitions/include/haproxy/timeout-tunnel.xml.i (lines 1-14), interface-definitions/include/haproxy/timeout.xml.i (line 10), interface-definitions/load-balancing_haproxy.xml.in (lines 228-233, 408-411)
Adds tunnel leaf (u32:1-3600, default 300), includes it into timeout children, and adds valueless http-server-close backend leaf.
Template conditionals for tunnel timeout and http-server-close
data/templates/load-balancing/haproxy.cfg.j2 (lines 45, 209-211, 292-294)
Renders timeout tunnel {{ ... }}s in defaults when global_parameters.timeout.tunnel is set; in backends emits option http-server-close when back_config.http_server_close is set and timeout tunnel when back_config.timeout.tunnel is set.
Runtime validation in conf mode
src/conf_mode/load-balancing_haproxy.py (lines 152-157)
verify(lb) now raises ConfigError if http_server_close is configured while backend mode != http.
WebSocket backend test and extended timeout test
smoketest/scripts/cli/test_load-balancing_haproxy.py (lines 338-365, 650-655, 666, 678, 689)
Adds test_reverse_proxy_backend_websocket() asserting option http-server-close, timeout tunnel <t>s, forwarded headers, and ssl verify none; extends test_reverse_proxy_timeout() to assert default and custom timeout tunnel rendering.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title accurately summarizes the main change: adding WebSocket support features to HAProxy configuration.
Description check ✅ Passed Description clearly explains the two features added (timeout tunnel and http-server-close), links relevant HAProxy documentation, includes testing methodology, and references task T8931.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented May 27, 2026

Copy link
Copy Markdown

👍
No issues in PR Title / Commit Title

@github-actions

github-actions Bot commented May 27, 2026

Copy link
Copy Markdown

✅ No typos found in changed files.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@interface-definitions/load-balancing_haproxy.xml.in`:
- Around line 228-233: The backend leaf "http-server-close" can be set for TCP
backends producing invalid HAProxy config; fix by adding a mode guard: either
update the schema or (recommended) add validation in verify() inside
src/conf_mode/load-balancing_haproxy.py to reject/raise when
back_config.http_server_close is true while back_config.mode != "http" (use the
same error type/format as existing HTTP-only checks like
health-check/http-response-headers), and/or update
data/templates/load-balancing/haproxy.cfg.j2 to only render "option
http-server-close" when back_config.mode == "http" (ensure
back_config.http_server_close remains the trigger but is combined with a mode
check).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 09e9c567-777b-43ab-acc4-70e2b50d0262

📥 Commits

Reviewing files that changed from the base of the PR and between 238d835 and 79b68b0.

📒 Files selected for processing (5)
  • data/templates/load-balancing/haproxy.cfg.j2
  • interface-definitions/include/haproxy/timeout-tunnel.xml.i
  • interface-definitions/include/haproxy/timeout.xml.i
  • interface-definitions/load-balancing_haproxy.xml.in
  • smoketest/scripts/cli/test_load-balancing_haproxy.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build_iso
  • GitHub Check: Mergify Merge Protections
  • GitHub Check: Summary
🧰 Additional context used
📓 Path-based instructions (4)
data/templates/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Jinja2 templates should be stored as discrete files under data/templates/ rather than as inline Python strings

Files:

  • data/templates/load-balancing/haproxy.cfg.j2
{**/*.py,data/templates/**/*}

📄 CodeRabbit inference engine (AGENTS.md)

Linting must use ruff 0.6.4, darker, pylint W0611, and Jinja2 lint as enforced by vyos/.github reusables

Files:

  • data/templates/load-balancing/haproxy.cfg.j2
  • smoketest/scripts/cli/test_load-balancing_haproxy.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Python code must use Python 3.11 or higher

Files:

  • smoketest/scripts/cli/test_load-balancing_haproxy.py
smoketest/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Smoketest scripts must be nose2 compatible and located in smoketest/

Files:

  • smoketest/scripts/cli/test_load-balancing_haproxy.py
🧠 Learnings (2)
📚 Learning: 2026-05-26T06:03:59.703Z
Learnt from: c-po
Repo: vyos/vyos-1x PR: 5109
File: smoketest/scripts/cli/test_service_https.py:206-207
Timestamp: 2026-05-26T06:03:59.703Z
Learning: In VyOS smoketests that verify processes running inside a VRF using iproute2, remember that `ip vrf pids <vrf>` outputs one entry per line as `<pid> <process_name>` (e.g., `300431 nginx`), not PIDs alone. Therefore, assertions should check for the presence of the expected process name in the command output (e.g., `assertIn(PROCESS_NAME, cmd(f'ip vrf pids {vrf}'))`) rather than trying to match PID-only output.

Applied to files:

  • smoketest/scripts/cli/test_load-balancing_haproxy.py
📚 Learning: 2026-05-26T06:04:29.163Z
Learnt from: c-po
Repo: vyos/vyos-1x PR: 5109
File: smoketest/scripts/cli/test_service_https.py:118-120
Timestamp: 2026-05-26T06:04:29.163Z
Learning: In VyOS smoketest scripts under `smoketest/scripts/cli/`, it is intentional to call `self.cli_delete(['vrf'])` in both `setUpClass` and `tearDown` to wipe the entire VRF subtree and ensure a clean slate. During code review, do not recommend narrowing the delete to specific VRF identifiers or name subsets (e.g., `['vrf', 'name', 'mgmt']`)—the broad teardown behavior is the established project-wide pattern for these tests.

Applied to files:

  • smoketest/scripts/cli/test_load-balancing_haproxy.py
🔍 Remote MCP Context7

Perfect! I've found detailed documentation about both features. Let me compile the findings:

Summary of Additional Context for PR Review

Based on HAProxy documentation research, here is the relevant context for reviewing this pull request:

timeout tunnel Directive

The timeout tunnel directive sets the maximum inactivity time on both client and server sides for tunnels. This timeout supersedes client and server timeouts once a connection becomes a tunnel, such as with WebSocket or CONNECT requests.

The timeout tunnel setting specifies the inactivity time for bidirectional connections that remain inactive in both directions and applies to upgraded connections like WebSocket or after the first response if no keepalive/close option is specified. It is recommended to set timeout client-fin alongside timeout tunnel to handle situations where a client might disappear without acknowledging a close, which can occur on lossy networks with firewalls.

The PR sets a default value of 300 seconds (5 minutes), which aligns with typical WebSocket inactivity timeout requirements.

option http-server-close Directive

The option http-server-close option enables HTTP/1.x connection closing on the server side while maintaining client-side keep-alive. This option can reduce latency for clients on slow networks and improve server resource utilization by enabling faster session reuse.

WebSocket Configuration Best Practices

The PR's implementation aligns with documented HAProxy best practices. A real-world example shows WebSocket backends configured with balance source, timeout tunnel 3600s, and server health checks, demonstrating standard WebSocket proxying patterns.

Validation Notes

  • The PR's constraint of 1–3600 seconds for the tunnel timeout (as defined in timeout-tunnel.xml.i) represents reasonable bounds for WebSocket connections (minimum 1 second, maximum 1 hour).
  • The configurable nature of timeout tunnel per backend (in addition to global defaults) provides flexibility for different application requirements, which is important since WebSocket inactivity tolerance varies significantly across applications.
  • Both features are standard HAProxy directives with wide adoption.
🔇 Additional comments (6)
data/templates/load-balancing/haproxy.cfg.j2 (2)

180-182: Duplicate of schema-level mode validation issue.


45-45: LGTM!

Also applies to: 292-294

interface-definitions/include/haproxy/timeout-tunnel.xml.i (1)

2-13: LGTM!

interface-definitions/include/haproxy/timeout.xml.i (1)

10-10: LGTM!

interface-definitions/load-balancing_haproxy.xml.in (1)

408-411: LGTM!

smoketest/scripts/cli/test_load-balancing_haproxy.py (1)

338-360: LGTM!

Also applies to: 645-645, 650-650, 661-661, 673-673, 684-684

Comment thread interface-definitions/load-balancing_haproxy.xml.in
@indrajitr indrajitr force-pushed the haproxy-websocket branch from 79b68b0 to 49e04f8 Compare May 27, 2026 22:57
@github-actions

Copy link
Copy Markdown

CI integration ❌ failed!

Details

CI logs

  • CLI Smoketests 👍 passed
  • CLI Smoketests (interfaces only) 👍 passed
  • Config tests 👍 passed
  • RAID1 tests 👍 passed
  • CLI Smoketests VPP ❌ failed
  • Config tests VPP 👍 passed
  • TPM tests 👍 passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds WebSocket-oriented HAProxy tuning by exposing timeout tunnel (in the defaults section with per-backend override) and option http-server-close (per backend), with validation and a new smoketest.

Changes:

  • New CLI nodes: load-balancing haproxy timeout tunnel, backend <name> timeout tunnel, and backend <name> http-server-close; rendered into haproxy.cfg.
  • Conf-mode verification that http-server-close only applies when the backend mode is http.
  • New smoketest test_reverse_proxy_backend_websocket plus tunnel-timeout assertions added to test_reverse_proxy_timeout.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
interface-definitions/include/haproxy/timeout-tunnel.xml.i New include defining the tunnel timeout leaf (1–3600s).
interface-definitions/include/haproxy/timeout.xml.i Pulls the new tunnel timeout into the shared timeout group used by backends.
interface-definitions/load-balancing_haproxy.xml.in Adds http-server-close to backend and the defaults-section tunnel override (default 300).
data/templates/load-balancing/haproxy.cfg.j2 Emits timeout tunnel (defaults + backend) and option http-server-close in HTTP backends.
src/conf_mode/load-balancing_haproxy.py Validates http-server-close requires HTTP mode; reword of an existing error message.
smoketest/scripts/cli/test_load-balancing_haproxy.py New websocket test and tunnel-timeout coverage in the timeout test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

2 participants