Skip to content

issue: 4043231 Fix keepalive abort to honor TCP_USER_TIMEOUT - #590

Open
tomerdbz wants to merge 1 commit into
Mellanox:vNextfrom
tomerdbz:4043231_timers
Open

issue: 4043231 Fix keepalive abort to honor TCP_USER_TIMEOUT#590
tomerdbz wants to merge 1 commit into
Mellanox:vNextfrom
tomerdbz:4043231_timers

Conversation

@tomerdbz

Copy link
Copy Markdown
Collaborator

Description

When TCP_USER_TIMEOUT is set on a keepalive connection, the abort decision should be based on elapsed time since last received data, not on the keepalive probe count (TCP_KEEPCNT). This matches the Linux tcp_keepalive_timer() behavior where TCP_USER_TIMEOUT overrides TCP_KEEPCNT.

Previously, the keepalive abort always used keep_cnt * keep_intvl, ignoring TCP_USER_TIMEOUT entirely. The separate tcp_user_timeout_ occured() check could eventually fire but measured from the wrong reference point (first probe sent, not last data received), making the effective timeout keep_idle + user_timeout instead of just user_timeout.

Enable LWIP_TCP_KEEPALIVE so that per-socket TCP_KEEPINTVL and TCP_KEEPCNT values are honored instead of using fixed defaults.

What

Fix keepalive abort to honor TCP_USER_TIMEOUT and enable per-socket TCP_KEEPINTVL/TCP_KEEPCNT.

Why ?

issue: 4043231 - When TCP_USER_TIMEOUT is set on a keepalive connection, XLIO aborts based on
probe count (TCP_KEEPCNT) instead of elapsed time.

This differs from Linux, where
TCP_USER_TIMEOUT overrides TCP_KEEPCNT and the connection drops when elapsed time since last
received data exceeds the user timeout.

Additionally, TCP_KEEPINTVL and TCP_KEEPCNT socket
options were silently ignored (LWIP_TCP_KEEPALIVE was compiled out).

How ?

Modify the keepalive abort block in tcp_slowtmr() to branch on user_timeout_ms:

  • When set: abort when elapsed-since-last-rx >= user_timeout AND at least one probe was sent
    (matches Linux tcp_keepalive_timer() logic).
  • When not set: abort when probe count exhausted (existing behavior, now using per-socket values).
    Enable LWIP_TCP_KEEPALIVE=1 so per-socket keep_intvl and keep_cnt fields (already initialized
    from /proc/sys/net/ipv4/tcp_keepalive_* in the constructor) are used by the timer and exposed
    via setsockopt/getsockopt.

Change type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Tests
  • Other

Check list

  • Code follows the style de facto guidelines of this project
  • Comments have been inserted in hard to understand places
  • Documentation has been updated (if necessary)
  • Test has been added (if possible)

@tomerdbz
tomerdbz requested a review from pasis March 24, 2026 11:17
@tomerdbz

Copy link
Copy Markdown
Collaborator Author

VMA analyzer:
🔍 libvma Relevance Analyzer
────────────────────────────────────────
📁 Analyzing 2 file(s)...
⏳ Connecting to GitHub... done

[1/2] lwip/opt.h... ✅ relevant
[2/2] lwip/tcp.c (2 hunks)... ✅ relevant

────────────────────────────────────────
📋 Summary
────────────────────────────────────────

✅ 2 change(s) MAY BE RELEVANT to libvma:
• src/core/lwip/opt.h
→ src/vma/lwip/opt.h
libvma has the same feature/logic that was changed in libxlio, which is the definition of LWIP_TCP_KEEPALIVE. The change in libxlio from 0 to 1 is relevant to libvma as it enables TCP keepalive functionality.
• src/core/lwip/tcp.c
→ src/vma/lwip/tcp.c
libvma has the same feature/logic for TCP keepalive that was changed in libxlio, and the change appears to be a bug fix or improvement to the keepalive timer logic.

TODO - open a task for libvma

@greptile-apps

greptile-apps Bot commented Mar 24, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes keepalive behavior in XLIO's lwIP TCP stack so that TCP_USER_TIMEOUT is honored by the keepalive abort path, and enables per-socket TCP_KEEPINTVL/TCP_KEEPCNT by removing the LWIP_TCP_KEEPALIVE=0 compile-out. Previously the abort was always probe-count-based and TCP_KEEPINTVL/TCP_KEEPCNT socket options were silently ignored.

  • Adds last_progress_tmr to struct tcp_pcb, seeded at connection establishment and updated only on real forward progress, mirroring Linux's lrcvtime/rcv_tstamp.
  • Replaces the monolithic abort check with a branch: user_timeout path uses ut_elapsed from last_progress_tmr; fallback uses ka_elapsed and probe count.
  • Removes LWIP_TCP_KEEPALIVE guards so keep_intvl/keep_cnt fields are always present and exposed via setsockopt/getsockopt.

Confidence Score: 4/5

The core keepalive timer logic is sound and last_progress_tmr anchor points are correctly placed, but the setsockopt handlers for the newly enabled TCP_KEEPINTVL and TCP_KEEPCNT options do not enforce the upper-bound limits that the newly added negative test cases exercise, so those tests will fail as written.

The abort-path algorithm is correctly implemented: last_progress_tmr is seeded at connection establishment and updated only on genuine forward progress, the user_timeout branch measures elapsed time from the right reference point, and the keep_cnt-based fallback matches previous behavior. However, the setsockopt handlers for TCP_KEEPINTVL and TCP_KEEPCNT reject only non-positive values and do not reject values above INT16_MAX seconds and INT8_MAX respectively, meaning the negative test cases added in this PR will fail.

src/core/sock/sockinfo_tcp.cpp (TCP_KEEPINTVL and TCP_KEEPCNT setsockopt handlers) and tests/gtest/tcp/tcp_sockopt.cc (negative test cases for the upper bounds of those two options).

Important Files Changed

Filename Overview
src/core/lwip/tcp.c Core keepalive timer rewrite: adds user_timeout branch using last_progress_tmr for abort, retains ka_elapsed-based probe-send path, adds UINT8_MAX cap on keep_cnt_sent, and anchors ticks_since_data_sent for zero-window probes at the call site.
src/core/lwip/tcp.h Adds last_progress_tmr field to struct tcp_pcb and unconditionally exposes keep_intvl/keep_cnt by removing LWIP_TCP_KEEPALIVE guards.
src/core/lwip/tcp_in.c Correctly anchors last_progress_tmr at three points: in-sequence data received, lastack advancement, and both active/passive ESTABLISHED transitions.
src/core/lwip/tcp_out.c Removes incorrect ticks_since_data_sent anchoring from tcp_keepalive(); the zero-window/persist caller now anchors it explicitly.
src/core/lwip/tcp_impl.h Adds TCP_USER_TIMEOUT_MAX_MS (RFC 5482 maximum) and removes TCP_MAXIDLE; removes LWIP_TCP_KEEPALIVE default-0 definition.
src/core/sock/sockinfo_tcp.cpp Adds optlen guard and RFC 5482 upper-bound check for TCP_USER_TIMEOUT; enables TCP_KEEPINTVL/TCP_KEEPCNT paths. Upper-bound validation for TCP_KEEPINTVL and TCP_KEEPCNT is absent, so negative test cases will fail.
src/core/lwip/opt.h Removes LWIP_TCP_KEEPALIVE=0 default definition so the feature is unconditionally compiled in.
tests/gtest/tcp/tcp_sockopt.cc Removes LWIP_TCP_KEEPALIVE guards; negative tests for TCP_KEEPINTVL > INT16_MAX and TCP_KEEPCNT > INT8_MAX are now compiled in but the corresponding setsockopt validation is absent.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[tcp_slowtmr tick] --> B{SOF_KEEPALIVE set and ESTABLISHED?}
    B -- No --> Z[Done]
    B -- Yes --> C[ka_elapsed = tcp_ticks - pcb->tmr
ut_elapsed = tcp_ticks - last_progress_tmr]
    C --> D{user_timeout_ms != 0?}
    D -- Yes --> E{ut_elapsed >= user_timeout_ms AND >= keep_idle?}
    D -- No --> F{ka_elapsed > keep_idle + keep_cnt x keep_intvl?}
    E -- Yes --> G[ka_abort = true]
    E -- No --> H[ka_abort = false]
    F -- Yes --> G
    F -- No --> H
    G --> I[Abort: pcb_remove++ pcb_reset++]
    H --> J{ka_elapsed > keep_idle + keep_cnt_sent x keep_intvl?}
    J -- Yes --> K[Send probe, keep_cnt_sent++ up to UINT8_MAX]
    J -- No --> Z
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[tcp_slowtmr tick] --> B{SOF_KEEPALIVE set and ESTABLISHED?}
    B -- No --> Z[Done]
    B -- Yes --> C[ka_elapsed = tcp_ticks - pcb->tmr
ut_elapsed = tcp_ticks - last_progress_tmr]
    C --> D{user_timeout_ms != 0?}
    D -- Yes --> E{ut_elapsed >= user_timeout_ms AND >= keep_idle?}
    D -- No --> F{ka_elapsed > keep_idle + keep_cnt x keep_intvl?}
    E -- Yes --> G[ka_abort = true]
    E -- No --> H[ka_abort = false]
    F -- Yes --> G
    F -- No --> H
    G --> I[Abort: pcb_remove++ pcb_reset++]
    H --> J{ka_elapsed > keep_idle + keep_cnt_sent x keep_intvl?}
    J -- Yes --> K[Send probe, keep_cnt_sent++ up to UINT8_MAX]
    J -- No --> Z
Loading

Reviews (10): Last reviewed commit: "issue: 4043231 Fix keepalive abort to ho..." | Re-trigger Greptile

Comment thread src/core/lwip/tcp.c Outdated
Comment thread src/core/lwip/tcp.c Outdated
Comment thread src/core/lwip/opt.h Outdated
*/
#ifndef LWIP_TCP_KEEPALIVE
#define LWIP_TCP_KEEPALIVE 0
#define LWIP_TCP_KEEPALIVE 1

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

you keep ability to toggle LWIP_TCP_KEEPALIVE , however, you remove build-time branching in 2 places below. So when you disable keepalive here the compilation will fail.
Either make proper compilation in all cases or remove the LWIP_TCP_KEEPALIVE entirely from the code.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

removed LWIP_TCP_KEEPALIVE

@tomerdbz

tomerdbz commented Apr 6, 2026

Copy link
Copy Markdown
Collaborator Author

libvma Relevance Analyzer
────────────────────────────────────────
📁 Analyzing 3 file(s)...
⏳ Connecting to GitHub... done

[1/3] lwip/tcp.c (2 hunks)... ✅ relevant
[2/3] lwip/tcp_impl.h... ✅ relevant
[3/3] sock/sockinfo_tcp.cpp... ✅ relevant

────────────────────────────────────────
📋 Summary
────────────────────────────────────────

✅ 3 change(s) MAY BE RELEVANT to libvma:
• src/core/lwip/tcp.c
→ src/vma/lwip/tcp.c
libvma has the same feature/logic that was changed in libxlio, which is related to calculating user_timeout_ticks. The change in libxlio seems to be a bug fix or improvement, making the calculation more accurate. libvma has not diverged significantly in this area, making the patch applicable.
• src/core/lwip/tcp_impl.h
→ src/vma/lwip/tcp_impl.h
libvma has the same feature/logic that was changed in libxlio, specifically the definition of TCP timeouts. The addition of TCP_USER_TIMEOUT_MAX_MS in libxlio is relevant to libvma as it provides a maximum user timeout value as defined in RFC 5482.
• src/core/sock/sockinfo_tcp.cpp
→ src/vma/sock/sockinfo_tcp.cpp
libvma has the same feature/logic for handling TCP user timeouts, and the libxlio change is a relevant bug fix to prevent exceeding the maximum allowed value.

Consider applying these changes to libvma as well.

Comment thread src/core/sock/sockinfo_tcp.cpp Outdated
@tomerdbz
tomerdbz force-pushed the 4043231_timers branch 2 times, most recently from 676c497 to 04af23a Compare April 6, 2026 08:17
Comment thread src/core/sock/sockinfo_tcp.cpp Outdated
Comment on lines +4709 to +4711
si_tcp_logwarn(
"TCP_USER_TIMEOUT value %u exceeds RFC 5482 maximum, clamping to %u",
user_timeout_ms, TCP_USER_TIMEOUT_MAX_MS);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

WARN level of logs will likely be printed out. This can lead to a flood in CPS scenario.
Linux kernel returns EINVAL when the value is <0 when casted to signed int. Probably we can return EINVAL instead of reducing the value?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

fixed

Comment thread src/core/lwip/tcp.c Outdated
Comment on lines 730 to 736
} else if (ka_elapsed > (pcb->keep_idle + pcb->keep_cnt_sent * pcb->keep_intvl) /
slow_tmr_interval) {
tcp_keepalive(pcb);
pcb->keep_cnt_sent++;
if (pcb->keep_cnt_sent < UINT8_MAX) {
pcb->keep_cnt_sent++;
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

does it mean that when we send 255 keepalives we start sending them every timer tick regardless of the keepalive parameters? What if we configure TCP_USER_TIMEOUT to that high allowed value, can this counter reach the 8-bit limit?

@tomerdbz tomerdbz Apr 13, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

as discussed privately see this:
image

@coderabbitai

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Enterprise

Run ID: 282adf88-d974-4888-a189-e0b7a3dece37

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@tomerdbz

tomerdbz commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

bot:retest

@tomerdbz
tomerdbz force-pushed the 4043231_timers branch 2 times, most recently from 35a58e6 to d739cad Compare July 8, 2026 06:33
@tomerdbz

tomerdbz commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator Author

Lab RED-GREEN evidence (issue 4043231)

  • libxlio commit: d739cad4 (rebased on vNext) - keepalive abort anchored on new pcb->last_progress_tmr (advanced only on real forward progress), so keepalive-probe replies no longer restart the TCP_USER_TIMEOUT clock.
  • Lab: dor03 <-> dor04, offloaded ConnectX (mlx5), 100.123.45.0/24, 400G, MTU 1500.
  • OFED: OFED-internal-26.01-0.6.4.
  • Workload: idle-keepalive repro - SO_KEEPALIVE, TCP_KEEPIDLE=8s, TCP_KEEPINTVL=2s, TCP_KEEPCNT=4, TCP_USER_TIMEOUT=12s; client sends 1 byte (ACKed), goes idle, peer is then blackholed (link down); measure time-to-abort from last data.
  • Result (offload-confirmed, both ends XLIO):
    Build abort time
    Kernel (no XLIO) - correct reference 12.1s
    Pre-fix (bug) ~20.6s (= keep_idle + user_timeout)
    This fix 12.1s / 12.1s (two trials) = honors TCP_USER_TIMEOUT
  • Local gate: container build clean; tests/unit_tests 197/197 pass.
  • Risk areas: TCP slow-timer / keepalive path; persist/zero-window re-anchors ticks_since_data_sent explicitly; the user_timeout == 0 (keep_cnt-based) path is unchanged.

Comment on lines 4687 to 4707
@@ -4697,7 +4706,6 @@ int sockinfo_tcp::tcp_setsockopt(int __level, int __optname, __const void *__opt
m_pcb.keep_cnt = keep_cnt;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Missing upper-bound validation for TCP_KEEPINTVL and TCP_KEEPCNT

The handlers for TCP_KEEPINTVL and TCP_KEEPCNT now always execute (guards removed), but neither enforces the upper-bound limits that Linux imposes and that the newly-enabled negative tests document: TCP_KEEPINTVL must be ≤ INT16_MAX (32 767) seconds, and TCP_KEEPCNT must be ≤ INT8_MAX (127). Both setsockopt handlers only reject non-positive values; they accept 32 768 s or 128 probes respectively, causing the tcp_setsockopt_negative tests added by this PR to fail. For TCP_KEEPINTVL, a value of 32 768 s × 1000 also silently overflows u32_t in pcb->keep_intvl.

When TCP_USER_TIMEOUT is set on a keepalive connection, the abort must be
measured from the last real data progress - matching Linux
tcp_keepalive_timer(), which uses rcv_tstamp/lrcvtime - not from a
reference that a bare keepalive-probe reply advances. Two independent
paths otherwise anchored the timeout on the first keepalive probe, so an
idle connection aborted at keep_idle + user_timeout (~20s) instead of
user_timeout (~12s):

  Path A - tcp_user_timeout_occured() reads pcb->ticks_since_data_sent,
  which tcp_keepalive() set to 0 on the first probe, so an otherwise-idle
  connection started that counter at keep_idle.

  Path B - the SOF_KEEPALIVE block measured elapsed from pcb->tmr, which
  tcp_in.c resets on every received segment, including the reply to the
  first keepalive probe.

Fix:
  - Add pcb->last_progress_tmr, set to tcp_ticks only on real forward
    progress (connection establishment, in-sequence data received, new
    data acknowledged); never advanced by keepalive-probe replies.
  - Path B measures user_timeout from last_progress_tmr, with a keep_idle
    guard so a probe is attempted before aborting an idle connection when
    user_timeout < keep_idle.
  - tcp_keepalive() no longer anchors ticks_since_data_sent (Path A); the
    zero-window/persist caller in tcp_slowtmr() anchors it explicitly (it
    has real pending data), preserving persist + user_timeout behavior.

The keep_cnt-based path (user_timeout == 0) is unchanged. Enable
LWIP_TCP_KEEPALIVE so per-socket TCP_KEEPINTVL/TCP_KEEPCNT are honored.

Signed-off-by: Tomer Cabouly <tcabouly@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants