Skip to content

[TCP] Missing TCP_NODELAY and socket options on server-accepted connections in modbus_tcp_accept #849

@NeriaIfrah

Description

@NeriaIfrah

modbus_tcp_accept() does not apply the same socket-level options that
_modbus_tcp_connect() applies to client sockets. Specifically, the
accepted client socket is missing:

  1. TCP_NODELAY — Nagle's algorithm remains enabled
  2. IPTOS_LOWDELAY — low-latency TOS marking is absent (non-Windows)
  3. Non-blocking mode via FIONBIO — on platforms without SOCK_NONBLOCK

Root Cause

The existing helper _modbus_tcp_set_ipv4_options() encapsulates all
three settings and is called in the client connect path
(_modbus_tcp_connect), but it is not called in the server accept path
(modbus_tcp_accept).

Observed Asymmetry

Client path (_modbus_tcp_connect):

ctx->s = socket(PF_INET, flags, 0);
// ...
rc = _modbus_tcp_set_ipv4_options(ctx->s);  // TCP_NODELAY + LOWDELAY + FIONBIO

Server path (modbus_tcp_accept):

ctx->s = accept4(*s, (struct sockaddr *) &addr, &addrlen, SOCK_CLOEXEC);
// _modbus_tcp_set_ipv4_options is never called — Nagle remains active

Impact

With Nagle's algorithm active on the server side, if the TCP stack
accumulates outstanding unacknowledged data (e.g., due to partial sends
or back-to-back responses), it will hold subsequent small segments until
an ACK is received from the client. Combined with the client's Delayed
ACK mechanism (up to 40 ms per RFC 1122), this can introduce latency
in response delivery — which is particularly undesirable in Modbus TCP,
where real-time response is a protocol requirement.
Additionally, without IPTOS_LOWDELAY, server-side responses do not
receive low-latency prioritisation in QoS-aware network infrastructure.
On platforms without SOCK_NONBLOCK, the accepted socket also remains
blocking, which differs from the non-blocking mode established for
client connections.

Proposed Fix

Insert a call to _modbus_tcp_set_ipv4_options() in modbus_tcp_accept
immediately after the accepted socket is validated:

if (_modbus_tcp_set_ipv4_options(ctx->s) == -1) {
    if (ctx->debug) {
        fprintf(stderr,
                "ERROR Failed to set IPv4 options on accepted socket %d\n",
                ctx->s);
    }
    close(ctx->s);
    ctx->s = -1;
    return -1;
}

_modbus_tcp_set_ipv4_options already contains all required platform
guards (#ifndef OS_WIN32, #ifndef SOCK_NONBLOCK), so no additional
preprocessor directives are needed at the call site.

The same fix should be applied to modbus_tcp_pi_accept for
consistency with the IPv6 / protocol-independent code path.


Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions