Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix incorrect behavior in message mode when too small buffer supplied #2809

Draft
wants to merge 38 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b53a091
[FIX] Calculate correctly max payload size per UDP packet
Mar 1, 2023
a8b9104
Applied verification of the payload size fit. Added tests. Updated do…
Mar 3, 2023
56fdfdb
Withdrawn quotes for option names
Mar 3, 2023
d7f32d0
Fixed links in the socket option table
Mar 3, 2023
70de763
Fixed minimum MSS to 116. Fixed some other bux
Mar 6, 2023
2eb1159
Replaced rand_r with std c++ random
Mar 6, 2023
9f48d3c
Fixed usage of C++14 literals in the test (build failures)
Mar 6, 2023
edbb608
[MAINT] Upgraded CI: ubuntu to version 20.04
Mar 6, 2023
26a7be6
Fixed test logics (printing after closing)
Mar 6, 2023
3d387a2
Attempted fix for a deadlock in test, added some tracking
Mar 6, 2023
03717f3
Merge branch 'dev-upgrade-ci-ubuntu' into dev-fix-ipv6-payloadsize
Mar 6, 2023
3eef592
Added expect and tracking to close socket in ReuseAddr test (Travis p…
Mar 6, 2023
e2ab5f6
Used relaxed signaling for the sake of Travis
Mar 6, 2023
d081e50
Lock debug fix for tests
ethouris Mar 7, 2023
ba5f962
Added timeout for lock-CV to avoid Travis problem
ethouris Mar 7, 2023
320a79b
Attempted more debug for test ipv6 for Travis
ethouris Mar 7, 2023
1b3ccd7
More debug for Travis
ethouris Mar 7, 2023
90b13b1
Fixed test ipv6 to use promise-future for synchronization
Mar 7, 2023
bc4059c
Fixed filtering-out IPv6 tests for Travis
Mar 7, 2023
11574a0
Fixed wrong comment
Mar 9, 2023
fabb85f
Updated with upstream and fixed
Apr 28, 2023
7cc801f
Updated and fixed
Sep 8, 2023
7658145
Fixed a bug introduced during upstream merge
Sep 11, 2023
41a86bf
Fixed tests that should require IPv6 enabled
Sep 11, 2023
45809bd
Pre-refax of change-independent parts for 2677
Sep 18, 2023
c782bf7
Merge branch 'dev-fix-ipv6-payloadsize-prefax' into dev-fix-ipv6-payl…
Sep 18, 2023
848857b
Merge branch 'master' into dev-fix-ipv6-payloadsize
Sep 18, 2023
0c3abe0
A fix from code review
Sep 18, 2023
ce2a043
Apply SOME suggestions from the doc review (others pending)
ethouris Sep 19, 2023
ccfb0b6
Updated and fixed
Sep 19, 2023
422cbed
Merge branch 'master' into dev-fix-ipv6-payloadsize
Sep 19, 2023
fa40417
Some more explanatory comments to enforce checkin
Sep 19, 2023
5c2c876
Apply suggestions from doc review (still pending)
ethouris Sep 19, 2023
0403820
Update doc review (still pending)
ethouris Sep 19, 2023
e02d85a
Apply suggestions from doc review (complete)
ethouris Sep 19, 2023
6074f21
Added extra v6-to-v6 test to check correct payload size
Oct 2, 2023
43d1a9c
Merge branch 'dev-fix-ipv6-payloadsize' into dev-fix-messageapi-error
Oct 6, 2023
9c6e5b5
Added a test that demonstrates the bug
Oct 6, 2023
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
7 changes: 5 additions & 2 deletions apps/srt-file-transmit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ int parse_args(FileTransmitConfig &cfg, int argc, char** argv)
return 2;
}

cfg.chunk_size = stoul(Option<OutString>(params, "1456", o_chunk));
cfg.chunk_size = stoul(Option<OutString>(params, "0", o_chunk));
cfg.skip_flushing = Option<OutBool>(params, false, o_no_flush);
cfg.bw_report = stoi(Option<OutString>(params, "0", o_bwreport));
cfg.stats_report = stoi(Option<OutString>(params, "0", o_statsrep));
Expand Down Expand Up @@ -681,8 +681,11 @@ int main(int argc, char** argv)
//
// Set global config variables
//
if (cfg.chunk_size != SRT_LIVE_MAX_PLSIZE)
if (cfg.chunk_size != 0)
transmit_chunk_size = cfg.chunk_size;
else
transmit_chunk_size = SRT_MAX_PLSIZE_AF_INET;

transmit_stats_writer = SrtStatsWriterFactory(cfg.stats_pf);
transmit_bw_report = cfg.bw_report;
transmit_stats_report = cfg.stats_report;
Expand Down
37 changes: 34 additions & 3 deletions apps/transmitmedia.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ bool g_stats_are_printed_to_stdout = false;
bool transmit_total_stats = false;
unsigned long transmit_bw_report = 0;
unsigned long transmit_stats_report = 0;
unsigned long transmit_chunk_size = SRT_LIVE_MAX_PLSIZE;
unsigned long transmit_chunk_size = SRT_MAX_PLSIZE_AF_INET6;

class FileSource: public Source
{
Expand Down Expand Up @@ -179,6 +179,36 @@ void SrtCommon::InitParameters(string host, map<string,string> par)
m_adapter = host;
}

int fam_to_limit_size = AF_INET6; // take the less one as default

// Try to interpret host and adapter first
sockaddr_any host_sa, adapter_sa;

if (host != "")
{
host_sa = CreateAddr(host);
fam_to_limit_size = host_sa.family();
if (fam_to_limit_size == AF_UNSPEC)
Error("Failed to interpret 'host' spec: " + host);
}

if (adapter != "" && adapter != host)
{
adapter_sa = CreateAddr(adapter);
fam_to_limit_size = adapter_sa.family();

if (fam_to_limit_size == AF_UNSPEC)
Error("Failed to interpret 'adapter' spec: " + adapter);

if (host_sa.family() != AF_UNSPEC && host_sa.family() != adapter_sa.family())
{
Error("Both host and adapter specified and they use different IP versions");
}
}

if (fam_to_limit_size != AF_INET)
fam_to_limit_size = AF_INET6;

if (par.count("tsbpd") && false_names.count(par.at("tsbpd")))
{
m_tsbpdmode = false;
Expand All @@ -195,8 +225,9 @@ void SrtCommon::InitParameters(string host, map<string,string> par)
if ((par.count("transtype") == 0 || par["transtype"] != "file")
&& transmit_chunk_size > SRT_LIVE_DEF_PLSIZE)
{
if (transmit_chunk_size > SRT_LIVE_MAX_PLSIZE)
throw std::runtime_error("Chunk size in live mode exceeds 1456 bytes; this is not supported");
size_t size_limit = (size_t)SRT_MAX_PLSIZE(fam_to_limit_size);
if (transmit_chunk_size > size_limit)
throw std::runtime_error(Sprint("Chunk size in live mode exceeds ", size_limit, " bytes; this is not supported"));

par["payloadsize"] = Sprint(transmit_chunk_size);
}
Expand Down
16 changes: 16 additions & 0 deletions docs/API/API-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ Since SRT v1.5.0.
| [SRT_REJ_GROUP](#SRT_REJ_GROUP) | 1.4.2 | The group type or some group settings are incompatible for both connection parties |
| [SRT_REJ_TIMEOUT](#SRT_REJ_TIMEOUT) | 1.4.2 | The connection wasn't rejected, but it timed out |
| [SRT_REJ_CRYPTO](#SRT_REJ_CRYPTO) | 1.5.2 | The connection was rejected due to an unsupported or mismatching encryption mode |
| [SRT_REJ_SETTINGS](#SRT_REJ_SETTINGS) | 1.6.0 | The connection was rejected because settings on both parties are in collision and cannot negotiate common values |
| <img width=290px height=1px/> | | |

<h4 id="error-codes">Error Codes</h4>
Expand Down Expand Up @@ -3068,6 +3069,21 @@ and above is reserved for "predefined codes" (`SRT_REJC_PREDEFINED` value plus
adopted HTTP codes). Values above `SRT_REJC_USERDEFINED` are freely defined by
the application.

#### SRT_REJ_CRYPTO

Settings for `SRTO_CRYPTOMODE` on both parties are not compatible with one another.
See [`SRTO_CRYPTOMODE`](API-socket-options.md#SRTO_CRYPTOMODE) for details.

#### SRT_REJ_SETTINGS

Settings for various transmission parameters that are supposed to be negotiated
during the handshake (in order to agree upon a common value) are under restrictions
that make finding common values for them impossible. Cases include:

* `SRTO_PAYLOADSIZE`, which is nonzero in live mode, is set to a value that
exceeds the free space in a single packet that results from the value of the
negotiated MSS value


[:arrow_up: &nbsp; Back to List of Functions & Structures](#srt-api-functions)

Expand Down
152 changes: 132 additions & 20 deletions docs/API/API-socket-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -922,20 +922,86 @@ The default value is 0x010000 (SRT v1.0.0).

| OptName | Since | Restrict | Type | Units | Default | Range | Dir | Entity |
| -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ |
| `SRTO_MSS` | | pre-bind | `int32_t` | bytes | 1500 | 76.. | RW | GSD |

Maximum Segment Size. Used for buffer allocation and rate calculation using
packet counter assuming fully filled packets. Each party can set its own MSS
value independently. During a handshake the parties exchange MSS values, and
the lowest is used.

*Generally on the internet MSS is 1500 by default. This is the maximum
size of a UDP packet and can be only decreased, unless you have some unusual
dedicated network settings. MSS is not to be confused with the size of the UDP
payload or SRT payload - this size is the size of the IP packet, including the
UDP and SRT headers*

THe value of `SRTO_MSS` must not exceed `SRTO_UDP_SNDBUF` or `SRTO_UDP_RCVBUF`.
| `SRTO_MSS` | | pre-bind | `int32_t` | bytes | 1500 | 116.. | RW | GSD |

Maximum Segment Size. This value represents the maximum size of a UDP packet
sent by the system. Therefore the value of `SRTO_MSS` must not exceed the
values of `SRTO_UDP_SNDBUF` or `SRTO_UDP_RCVBUF`. It is used for buffer
allocation and rate calculation using a packet counter that assumes fully filled
packets.

This value is a sum of:

* IP header (20 bytes for IPv4, or 32 bytes for IPv6)
* UDP header (8 bytes)
* SRT header (16 bytes)
* remaining space (as the maximum payload size available for a packet)

For the default 1500 the "remaining space" is effectively 1456 for IPv4
and 1444 for IPv6.

Note that the IP version used here is not the domain of the underlying UDP
socket, but the in-transmission IP version. This is effectively IPv4 in the
following cases:

* when the current socket's binding address is of IPv4 domain
* when the peer's address is an IPv6-mapped-IPv4 address

The IPv6 transmission case is assumed only if the peer's address is a true IPv6 address
(not IPv4 mapped). It is then not possible to determine the payload size limit
until the connection is established. SRT operations that must allocate any
resources according to this value prior to connecting will assume IPv4 transmission
because this way, in the worst case, they allocate more space than needed.

This value can be set on both connection parties independently, but after
connection `SRTO_MSS` gets a negotiated value, which is the lesser
of the two. If this effective value is too small for either of the
connection peers, the connection is rejected (or late-rejected on the caller
side).

This value then controls:

* The maximum size of the payload in a single UDP packet ("remaining space").

* The size of the memory space allocated for a single packet in the sender
and receiver buffers. This value is equal to "SRT header" + "remaining space"
in the IPv4 layout case (1472 bytes per packet for MSS=1500). The reason for it
is that some buffer resources are allocated prior to the connection, so this
value must fit both IPv4 and IPv6 for buffer memory allocation.

The default value of 1500 corresponds to the standard MTU size for network devices. It
is recommended that this value be set to the maximum MTU size of
the network device that you will use for the connection.

The recommendations for the value of `SRTO_MSS` differ between file and live modes.

In live mode a single call to the `srt_send*` function may only send data
that fits in one packet. This size is defined by the `SRTO_PAYLOADSIZE`
option (defult: 1316), and it is also the size of the data in a single UDP
packet. To save memory space, you may want then to set `SRTO_MSS` in live mode to
a value for which the "remaining space" matches the `SRTO_PAYLOADSIZE` value (for
the default value of 1316 this will be 1360 for IPv4 and 1372 for IPv6). For security reasons,
this is not done by default: it may potentially lead to the inability to read an incoming UDP
packet if its size is for some reason bigger than the negotiated MSS, which may in turn lead
to unpredictable behaviour and hard-to-detect errors. You should set such a value only if
the peer is trusted (that is, you can be certain that you will never receive an oversized UDP
packet over the link used for the connection). You should also consider the limitations of
`SRTO_PAYLOADSIZE`.

In file mode `SRTO_PAYLOADSIZE` has a special value 0 that means no limit
for sending a single packet, and therefore bigger portions of data are
internally split into smaller portions, each one using the maximum available
"remaining space". The best value of `SRTO_MSS` for this case is then equal to
the current network device's MTU size. Setting a greater value is possible
(maximum for the system API is 65535), but it may lead to packet fragmentation
on the system level. This is highly unwanted in SRT because:

* SRT also performs its own fragmentation, so it would be counter-productive
* It would use more system resources to no advantage
* SRT is unaware of it, so the resulting statistics would be slightly misleading

System-level packet fragmentation cannot be reliably turned off,
so the safest approach is to avoid it by using appropriate parameters.

[Return to list](#list-of-options)

Expand Down Expand Up @@ -1032,7 +1098,7 @@ Cases when negotiation succeeds:
| fec,cols:10 | fec,cols:10,rows:20 | fec,cols:10,rows:20,arq:onreq,layout:even
| fec,layout:staircase | fec,cols:10 | fec,cols:10,rows:1,arq:onreq,layout:staircase

In these cases the configuration is rejected with SRT_REJ_FILTER code:
In these cases the configuration is rejected with `SRT_REJ_FILTER` code:

| Peer A | Peer B | Error reason
|-----------------------|---------------------|--------------------------
Expand Down Expand Up @@ -1089,12 +1155,58 @@ encrypted connection, they have to simply set the same passphrase.
| -------------------- | ----- | -------- | ---------- | ------- | -------- | ------ | --- | ------ |
| `SRTO_PAYLOADSIZE` | 1.3.0 | pre | `int32_t` | bytes | \* | 0.. \* | W | GSD |

Sets the maximum declared size of a single call to sending function in Live
mode. When set to 0, there's no limit for a single sending call.
Sets the mode that determines the limitations on how data is sent, including the maximum
size of payload data sent within a single UDP packet. This option can be only set prior
to connecting, but it can be read also after the connection has been established.

The default value is 1316 in live mode (which is default) and 0 in file mode (when file
mode is set through the `SRTO_TRANSTYPE` option).

In file mode (`SRTO_PAYLOADSIZE` = 0) the call to `srt_send*` is not limited to the size
of a single packet. If necessary, the supplied data will be split into multiple pieces,
each fitting into a single UDP packet. Every data payload (except the last one in the
stream or in the message) will use the maximum space available in a UDP packet,
as determined by `SRTO_MSS` and other settings that may influence this size
(such as [`SRTO_PACKETFILTER`](#SRTO_PACKETFILTER) and
[`SRTO_CRYPTOMODE`](#SRTO_CRYPTOMODE)).

Also when this option is set to 0 prior to connecting, then reading this option
from a connected socket will return the maximum size of the payload that fits
in a single packet according to the current connection parameters.

In live mode (`SRTO_PAYLOADSIZE` > 0) the value defines the maximum size of:

* a single call to a sending function (`srt_send*`)
* the payload supplied in each data packet

as well as the minimum size of the buffer used for the `srt_recv*` call.

This value can be set to a greater value than the default 1316, but the maximum
possible value is limited by the following factors:

* 1500 bytes is the default MSS (see [`SRTO_MSS`](#SRTO_MSS)), including headers, which occupy:
* 20 bytes for IPv4, or 32 bytes for IPv6
* 8 bytes for UDP
* 16 bytes for SRT

This alone gives a limit of 1456 for IPv4 and 1444 for IPv6. This limit may
be further decreased in the following cases:

* 4 bytes reserved for FEC, if you use the built in FEC packet filter (see [`SRTO_PACKETFILTER`](#SRTO_PACKETFILTER))
* 16 bytes reserved for the authentication tag, if you use AES GCM (see [`SRTO_CRYPTOMODE`](#SRTO_CRYPTOMODE))

**WARNING**: The party setting the options will reject a value that is too
large, but note that not every limitation can be checked prior to connection.
This includes:

* the MSS value defined by a peer, which may override the MSS set by an agent
* the in-transmission IP version (see [SRTO_MSS](#SRTO_MSS) for details)

For Live mode: Default value is 1316, but can be increased up to 1456. Note that
with the `SRTO_PACKETFILTER` option additional header space is usually required,
which decreases the maximum possible value for `SRTO_PAYLOADSIZE`.
These values also influence the "remaining space" in the packet to be used for
payload. If during the handshake it turns out that this "remaining space" is
less than the value set for `SRTO_PAYLOADSIZE` (including when it remains with
the default value), the connection will be rejected with the `SRT_REJ_SETTINGS`
code.

For File mode: Default value is 0 and it's recommended not to be changed.

Expand Down
9 changes: 8 additions & 1 deletion srtcore/api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3157,7 +3157,14 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, cons
m.m_pSndQueue = new CSndQueue;
m.m_pSndQueue->init(m.m_pChannel, m.m_pTimer);
m.m_pRcvQueue = new CRcvQueue;
m.m_pRcvQueue->init(128, s->core().maxPayloadSize(), m.m_iIPversion, 1024, m.m_pChannel, m.m_pTimer);

// We can't use maxPayloadSize() because this value isn't valid until the connection is established.
// We need to "think big", that is, allocate a size that would fit both IPv4 and IPv6.
const size_t payload_size = s->core().m_config.iMSS - CPacket::HDR_SIZE - CPacket::udpHeaderSize(AF_INET);

HLOGC(smlog.Debug, log << s->core().CONID() << "updateMux: config rcv queue qsize=" << 128
<< " plsize=" << payload_size << " hsize=" << 1024);
m.m_pRcvQueue->init(128, payload_size, m.m_iIPversion, 1024, m.m_pChannel, m.m_pTimer);

// Rewrite the port here, as it might be only known upon return
// from CChannel::open.
Expand Down
4 changes: 2 additions & 2 deletions srtcore/buffer_tools.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,12 @@ void AvgBufSize::update(const steady_clock::time_point& now, int pkts, int bytes
m_dTimespanMAvg = avg_iir_w<1000, double>(m_dTimespanMAvg, timespan_ms, elapsed_ms);
}

CRateEstimator::CRateEstimator(int /*family*/)
CRateEstimator::CRateEstimator(int family)
: m_iInRatePktsCount(0)
, m_iInRateBytesCount(0)
, m_InRatePeriod(INPUTRATE_FAST_START_US) // 0.5 sec (fast start)
, m_iInRateBps(INPUTRATE_INITIAL_BYTESPS)
, m_iFullHeaderSize(CPacket::UDP_HDR_SIZE + CPacket::HDR_SIZE)
, m_iFullHeaderSize(CPacket::udpHeaderSize(family) + CPacket::HDR_SIZE)
{}

void CRateEstimator::setInputRateSmpPeriod(int period)
Expand Down
Loading
Loading