Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ version at the top when it merges.

## Unreleased

- `TCPWINDOWMAX=<bytes>` in an interface file caps the TCP receive window a
socket on that interface settles at, 65,536 to 1,048,576 bytes; unset
leaves the window as it was. The cap never goes below the window the
handshake offered. The key brings the resident `bsdsocket.library` image
to 365,536 bytes; its budget is 366,000 bytes.
- `TCPGROWRTT=<ms>` in an interface file sets the handshake round trip
(1 to 65,535 ms) at or above which a TCP receive window on that interface
grows to its maximum. Unset, every device keeps 10 ms. The key adds 100
Expand Down
11 changes: 11 additions & 0 deletions docs/user/AmiNetXDuo.guide
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,17 @@
this interface only; editing the file takes effect when
the interface is next attached.

@{b}TCPWINDOWMAX@{ub}
The largest TCP receive window, in bytes, 65536 to
1048576, that a socket on this interface settles at once
its handshake is done. It lowers the window chosen from
the packet pool, TCPGROWRTT and RXBUFFER, and never
raises it. Leave it out for no limit. The value applies
to this interface only; editing the file takes effect
when the interface is next attached. SO_RCVBUF does not
change the window: it bounds the socket's receive queue
in packets.

Interface files written for Roadshow usually carry other keywords:
COPYMODE, ALIAS, METRIC, LEASE, DHCPUNICAST, LINKSTATUSCOMMAND,
MULTICAST, POINTTOPOINT, DESTINATION and BROADCASTADDRESS among
Expand Down
8 changes: 8 additions & 0 deletions include/aminetxduo/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ extern "C" {
#define AMI_CFG_WRITEREQUESTS_MAX AMINETXDUO_TX_SLOTS
#define AMI_CFG_TCP_ACK_MAX 65535UL
#define AMI_CFG_TCP_GROW_RTT_MAX 65535UL
/* TCPWINDOWMAX bounds. The SYN advertised min(window, 65535) unscaled; a
cap under it would retract that edge. 65536 >> s << s >= 65535 for every
scale up to 5, and 5 is what 1 MiB (BSD_TCP_WINDOW_MAX) negotiates. */
#define AMI_CFG_TCP_WINDOW_MAX_MIN 65536UL
#define AMI_CFG_TCP_WINDOW_MAX_MAX 1048576UL
#define AMI_CFG_PATH_LEN 128

/* The default domain gets its own cap: SetDefaultDomainName()'s autodoc says
Expand Down Expand Up @@ -207,6 +212,9 @@ typedef struct AmiIfConfig {
/* TCPGROWRTT: handshake round trip in ms at or above which a socket on
this interface grows its receive window. 0 = BSD_TCP_WINDOW_GROW_RTT_MS. */
UWORD tcp_grow_rtt;
/* TCPWINDOWMAX: bytes a socket on this interface settles its receive
window at, at most. 0 = no cap. */
ULONG tcp_window_max;
BYTE priority; /* PRIORITY=, -128..127, 0 unset: the
highest wins a route two
interfaces could carry */
Expand Down
1 change: 1 addition & 0 deletions include/aminetxduo/config_advice.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,6 @@
#define AMI_CFG_ADVICE_FILTER_IS_LOCAL_IPANDARP 51
#define AMI_CFG_ADVICE_TCPACKMAX_IS_ACK_BYTES 52
#define AMI_CFG_ADVICE_TCPGROWRTT_IS_MILLISECONDS 53
#define AMI_CFG_ADVICE_TCPWINDOWMAX_IS_BYTES 54

#endif /* AMINETXDUO_CONFIG_ADVICE_H */
2 changes: 2 additions & 0 deletions include/aminetxduo/sana2.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ ULONG ami_sana2_get_hw_rx_bytes(const AmiSana2If *iface);
/* The interface file's TCPGROWRTT in ms, 0 = not set (bsdsocket_window.h,
BSD_TCP_WINDOW_GROW_RTT_MS). */
ULONG ami_sana2_get_tcp_grow_rtt(const AmiSana2If *iface);
/* The interface file's TCPWINDOWMAX in bytes, 0 = not set (no cap). */
ULONG ami_sana2_get_tcp_window_max(const AmiSana2If *iface);
BOOL ami_sana2_is_online(const AmiSana2If *iface);

/*
Expand Down
2 changes: 2 additions & 0 deletions install/Install-AmiNetXDuo
Original file line number Diff line number Diff line change
Expand Up @@ -2242,6 +2242,8 @@
(append "; Leave it out for this device's default.\n")
(append "; TCPGROWRTT= is the handshake round trip in ms at which a\n")
(append "; TCP receive window grows. Leave it out for 10.\n")
(append "; TCPWINDOWMAX= caps a TCP receive window in bytes, 65536 to\n")
(append "; 1048576. Leave it out for no cap.\n")
(append ";\n")
(append "; An IPv6 build also understands CONFIGURE6 (OFF, LINKLOCAL,\n")
(append "; AUTO, DHCP or STATIC), ADDRESS6 and GATEWAY6. A build\n")
Expand Down
14 changes: 14 additions & 0 deletions src/bsdsocket/bsdsocket_window.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,20 @@ ULONG ami_bsd_tcp_window_settle(ULONG created, ULONG maximum, ULONG bps,
return created;
}

ULONG ami_bsd_tcp_window_chosen(ULONG created, ULONG maximum, ULONG bps,
ULONG rtt_ms, ULONG grow_rtt_ms,
ULONG hw_bytes, ULONG mss, ULONG window_max)
{
ULONG want = ami_bsd_tcp_window_settle(created, maximum, bps, rtt_ms,
grow_rtt_ms);

if (ami_bsd_tcp_window_burst_bound(bps, rtt_ms, grow_rtt_ms))
want = ami_bsd_tcp_window_fit(want, hw_bytes, mss);
if (window_max != 0UL && want > window_max)
want = window_max;
return want;
}

BOOL ami_bsd_tcp_window_burst_bound(ULONG bps, ULONG rtt_ms,
ULONG grow_rtt_ms)
{
Expand Down
12 changes: 12 additions & 0 deletions src/bsdsocket/bsdsocket_window.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,18 @@ ULONG ami_bsd_tcp_window_fit(ULONG window, ULONG hw_bytes, ULONG mss);
BOOL ami_bsd_tcp_window_burst_bound(ULONG bps, ULONG rtt_ms,
ULONG grow_rtt_ms);

/*
* The whole settle decision for one interface: _settle(), then _fit() where
* _burst_bound(), then the interface's TCPWINDOWMAX (`window_max`, 0 = no
* cap, #89). No interface is bps, hw_bytes, grow_rtt_ms and window_max all
* 0. The cap only lowers: the scale negotiated at SYN from `maximum` still
* expresses the result, rounded down to its 1 << scale granularity on the
* wire. Pure arithmetic, host-tested.
*/
ULONG ami_bsd_tcp_window_chosen(ULONG created, ULONG maximum, ULONG bps,
ULONG rtt_ms, ULONG grow_rtt_ms,
ULONG hw_bytes, ULONG mss, ULONG window_max);

#ifndef BSD_TCP_WINDOW_CEILING
#ifdef AMINETXDUO_TCP_WINDOW_SCALING
#define BSD_TCP_WINDOW_CEILING BSD_TCP_WINDOW_MAX
Expand Down
21 changes: 10 additions & 11 deletions src/bsdsocket/socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -204,21 +204,20 @@ VOID bsd_tcp_window_settle(NX_TCP_SOCKET *tcp, ULONG rtt_ms)
AmiSana2If *sana = (nxif != NX_NULL)
? (AmiSana2If *)nxif->nx_interface_additional_link_info
: NULL;
ULONG bps = (sana != NULL) ? ami_sana2_get_bps(sana) : 0UL;
ULONG grow = ami_sana2_get_tcp_grow_rtt(sana); /* 0 = built-in line */
ULONG cur = tcp->nx_tcp_socket_rx_window_default;
ULONG want = ami_bsd_tcp_window_settle(cur, bsd_tcp_window_top(tcp), bps,
rtt_ms, grow);

/* ... and never more than the card behind this interface can hold from
the wire at once (bsdsocket_window.h, ami_bsd_tcp_window_fit), where
the whole window can arrive at once -- a LAN, or a card slower than
the path (ami_bsd_tcp_window_burst_bound). The segment size is the
/* Grown for the path, never more than the card behind this interface
can hold from the wire where the whole window can arrive at once, and
never more than its TCPWINDOWMAX (ami_bsd_tcp_window_chosen). A NULL
interface reads as all zeros: no fit, no cap. The segment size is the
one the handshake settled: nx_tcp_socket_mss is what the application
asked for and stays 0 on an accepted socket. */
if (sana != NULL && ami_bsd_tcp_window_burst_bound(bps, rtt_ms, grow))
want = ami_bsd_tcp_window_fit(want, ami_sana2_get_hw_rx_bytes(sana),
tcp->nx_tcp_socket_connect_mss);
ULONG want = ami_bsd_tcp_window_chosen(cur, bsd_tcp_window_top(tcp),
ami_sana2_get_bps(sana), rtt_ms,
ami_sana2_get_tcp_grow_rtt(sana),
ami_sana2_get_hw_rx_bytes(sana),
tcp->nx_tcp_socket_connect_mss,
ami_sana2_get_tcp_window_max(sana));

if (want == cur)
return;
Expand Down
1 change: 1 addition & 0 deletions src/config/config_advice.c
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ static const char *const ami_cfg_advice_text[] =
"before requesting an ACK, 1 to 65535. Leave it out for the "
"device default; smaller values send more ACKs.",
"TCPGROWRTT is milliseconds, 1 to 65535. Leave it out for 10.",
"TCPWINDOWMAX is bytes, 65536 to 1048576. Leave it out for no cap.",
};

const char *ami_cfg_advice(UWORD code)
Expand Down
18 changes: 18 additions & 0 deletions src/config/config_parse.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ typedef enum
IF_KEY_RXBUFFER,
IF_KEY_TCPACKMAX,
IF_KEY_TCPGROWRTT,
IF_KEY_TCPWINDOWMAX,
IF_KEY_PRIORITY
} IfKey;

Expand Down Expand Up @@ -80,6 +81,7 @@ ami_if_keywords[] =
{ "rxbuffer", IF_KEY_RXBUFFER },
{ "tcpackmax", IF_KEY_TCPACKMAX },
{ "tcpgrowrtt", IF_KEY_TCPGROWRTT },
{ "tcpwindowmax", IF_KEY_TCPWINDOWMAX },
{ "priority", IF_KEY_PRIORITY }, /* Roadshow's, and PRI */
{ "pri", IF_KEY_PRIORITY },

Expand Down Expand Up @@ -797,6 +799,22 @@ LONG ami_cfg_parse_interface(const char *name, char *buf, AmiIfConfig *out)
}
break;

case IF_KEY_TCPWINDOWMAX:
if (ami_cfg_parse_ulong(value, &n) &&
n >= AMI_CFG_TCP_WINDOW_MAX_MIN &&
n <= AMI_CFG_TCP_WINDOW_MAX_MAX)
{
out->tcp_window_max = n;
}
else
{
AMI_WARN("config: %s: bad TCPWINDOWMAX '%s'", out->name, value);
report_bad_value(lineno, AMI_CFG_PROBLEM_WARN,
"TCPWINDOWMAX", value,
AMI_CFG_ADVICE_TCPWINDOWMAX_IS_BYTES);
}
break;

case IF_KEY_IPREQUESTS:
case IF_KEY_ARPREQUESTS:
case IF_KEY_WRITEREQUESTS:
Expand Down
66 changes: 66 additions & 0 deletions src/config/test/test_config.c
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,70 @@ static void test_interface_tcp_grow_rtt(void)
ami_config_set_reporter(NULL, NULL);
}

static void test_interface_tcp_window_max(void)
{
AmiIfConfig iface;
char *buf;

printf("interface: TCPWINDOWMAX is optional, 65536..1048576\n");

buf = dup_text("device = anxwifipi.device\nconfigure = dhcp\n");
CHECK(ami_cfg_parse_interface("wifipi", buf, &iface) == AMI_CFG_OK);
CHECK(iface.tcp_window_max == 0);
free(buf);

buf = dup_text("device = anxwifipi.device\nconfigure = dhcp\n"
"tcpwindowmax = 262144\n");
CHECK(ami_cfg_parse_interface("wifipi", buf, &iface) == AMI_CFG_OK);
CHECK(iface.tcp_window_max == 262144UL);
free(buf);

buf = dup_text("device = genet.device\nconfigure = dhcp\n"
"TCPWINDOWMAX=65536\n");
CHECK(ami_cfg_parse_interface("eth", buf, &iface) == AMI_CFG_OK);
CHECK(iface.tcp_window_max == 65536UL);
free(buf);

buf = dup_text("device = genet.device\nconfigure = dhcp\n"
"TCPWINDOWMAX=1048576\n");
CHECK(ami_cfg_parse_interface("eth", buf, &iface) == AMI_CFG_OK);
CHECK(iface.tcp_window_max == 1048576UL);
free(buf);
}

/* Under the 65536 floor (a retraction of the handshake window), over the
maximum, 0, or not a number: warned with the advice, left unset. */
static void test_interface_tcp_window_max_rejects_bad_values(void)
{
static const char *const bad[] = {
"0", "8192", "65535", "1048577", "4294967296", "big", "256k"
};
AmiIfConfig iface;
char *buf;
char text[96];
ULONG i;

printf("interface: a bad TCPWINDOWMAX is warned and ignored\n");

ami_config_set_reporter(collect, NULL);
ami_cfg_problem_file("DEVS:NetInterfaces/wifipi");
for (i = 0; i < sizeof(bad) / sizeof(bad[0]); i++)
{
seen_count = 0;
snprintf(text, sizeof(text), "device = anxwifipi.device\n"
"configure = dhcp\ntcpwindowmax = %s\n", bad[i]);
buf = dup_text(text);
CHECK(ami_cfg_parse_interface("wifipi", buf, &iface) == AMI_CFG_OK);
CHECK(iface.tcp_window_max == 0);
CHECK(seen_count == 1 && seen[0].line == 3);
CHECK(seen_count == 1 &&
strstr(seen[0].hint,
"TCPWINDOWMAX is bytes, 65536 to 1048576") != NULL);
free(buf);
}
ami_config_set_reporter(NULL, NULL);
}

static void test_interface_priority(void)
{
AmiIfConfig iface;
Expand Down Expand Up @@ -3021,6 +3085,8 @@ int main(int argc, char **argv)
test_request_counts_have_ceilings();
test_interface_tcp_ack_max();
test_interface_tcp_grow_rtt();
test_interface_tcp_window_max();
test_interface_tcp_window_max_rejects_bad_values();
test_interface_priority();
test_interface_ipv6_only();
#ifdef AMINETXDUO_IPV6
Expand Down
6 changes: 6 additions & 0 deletions src/sana2/sana2_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -1321,6 +1321,7 @@ AmiSana2If *ami_sana2_open(const AmiIfConfig *cfg, LONG *err)
* by name. 0 is no cap.
*/
iface->tcp_grow_rtt = cfg->tcp_grow_rtt;
iface->tcp_window_max = cfg->tcp_window_max;
if (cfg->rx_buffer != 0)
iface->hw_rx_bytes = cfg->rx_buffer;
else if ((iface->extension.Accepted & ANXD_S2F_RX_CAPACITY) != 0)
Expand Down Expand Up @@ -1613,6 +1614,11 @@ ULONG ami_sana2_get_tcp_grow_rtt(const AmiSana2If *iface)
return (iface != NULL) ? iface->tcp_grow_rtt : 0;
}

ULONG ami_sana2_get_tcp_window_max(const AmiSana2If *iface)
{
return (iface != NULL) ? iface->tcp_window_max : 0;
}

/*
* What a vendor driver's card holds from the wire, by the driver's name,
* for the drivers whose hardware is known: they cannot be asked. The
Expand Down
2 changes: 2 additions & 0 deletions src/sana2/sana2_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,8 @@ struct AmiSana2If
ULONG hw_rx_bytes;
/* TCPGROWRTT in ms, 0 = BSD_TCP_WINDOW_GROW_RTT_MS (bsdsocket_window.h). */
UWORD tcp_grow_rtt;
/* TCPWINDOWMAX in bytes, 0 = no cap (bsdsocket_window.h). */
ULONG tcp_window_max;

/* The interface file's IPREQUESTS, ARPREQUESTS (0 = the plan decides)
and WRITEREQUESTS (1..AMI_SANA2_TX_SLOTS: how many of tx[] are
Expand Down
6 changes: 4 additions & 2 deletions tests/bsdsocket/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,8 @@ if(NOT CMAKE_CROSSCOMPILING AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
# mcast.c and cmsg.c -- are stubbed; the test's header says what that does
# and does not prove.
add_executable(test_sockopt host/test_sockopt_host.c
"${CMAKE_SOURCE_DIR}/src/bsdsocket/options.c")
"${CMAKE_SOURCE_DIR}/src/bsdsocket/options.c"
"${CMAKE_SOURCE_DIR}/src/bsdsocket/bsdsocket_window.c")

target_include_directories(test_sockopt PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/host/shim"
Expand All @@ -323,7 +324,8 @@ if(NOT CMAKE_CROSSCOMPILING AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
# half -- 0 turns the small-write cork on, getsockopt reads it back -- is
# compiled and checked whatever the option says, rxdirect's reason below.
add_executable(test_sockopt_cork host/test_sockopt_host.c
"${CMAKE_SOURCE_DIR}/src/bsdsocket/options.c")
"${CMAKE_SOURCE_DIR}/src/bsdsocket/options.c"
"${CMAKE_SOURCE_DIR}/src/bsdsocket/bsdsocket_window.c")

target_include_directories(test_sockopt_cork PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/host/shim"
Expand Down
Loading
Loading