1362 Commits

Author SHA1 Message Date
roman 96d7d04084 Update tools/meson.build 2026-06-09 09:48:51 +00:00
roman b6c1f874d6 Add tools/moo-ristsender.c 2026-06-09 09:47:58 +00:00
Sergio Ammirata 8461d46117 Merge branch 'fix/prometheus-unix-socket-reliability' into 'master'
fix(prometheus-exporter): six fixes for flaky --metrics-unixsock scrapes

See merge request rist/librist!346
2026-06-06 01:07:23 +00:00
Sergio Ammirata bae99c6c64 prometheus-exporter: fix flaky unix-socket scrapes
The unix-socket scraper exposed by ristreceiver / ristsender
(activated by --metrics-unixsock) returned empty or stub-only
bodies on a substantial fraction of scrapes in production.
Six separate defects in tools/prometheus-exporter.c contributed:

1. listen(fd, 1) -- a backlog of one is too small for any
   real deployment where Prometheus + an external collector
   may scrape within the same RTT. Replaced with SOMAXCONN.

2. accept() returning EINTR was treated as fatal and broke the
   thread out of the loop, silently disabling the exporter for
   the remainder of the process lifetime. EINTR is now retried.

3. The hot path issued a single unchecked write() with the
   result discarded via (void)!. Partial writes and transient
   EINTR therefore truncated the response, and an early peer
   close raised SIGPIPE on the exporter thread. Replaced with
   a write_all helper that loops on partial writes, retries
   EINTR, and uses MSG_NOSIGNAL where available.

4. shutdown(SHUT_RDWR) immediately before close() can behave
   as an abortive teardown on AF_UNIX SOCK_STREAM under some
   kernels, discarding bytes that write() had just queued.
   Switched to shutdown(SHUT_WR), which is the documented
   "no more writes -- let the receive queue drain" sequence.

5. close() with unread bytes in the receive queue is the
   biggest source of empty-body scrapes in the field. The
   exporter is a dump-on-connect protocol that never reads
   the client's request, but tools like nc / curl / the
   Prometheus scraper itself all send "GET /metrics HTTP/1.0
   \r\n\r\n". On Linux, close() on an AF_UNIX SOCK_STREAM
   with unread input is treated as an abortive teardown: the
   kernel sends RST and discards everything in the transmit
   queue -- including the 10 KB body we just wrote. The
   client sees ECONNRESET / EOF with zero bytes. We now
   shutdown(SHUT_WR) and drain the receive queue with a
   SO_RCVTIMEO-bounded recv loop before close, so the kernel
   does a clean FIN-FIN handshake and the body actually
   reaches the client. Confirmed by a Python probe that
   went from 5/5 ECONNRESET to 5/5 success in the same
   environment.

6. In single-stat-point mode (the default for ristreceiver
   and ristsender) rist_prometheus_stats_format() was zeroing
   container_count after every scrape. Any subsequent scrape
   that arrived before the next 1 Hz stats callback then
   emitted only HELP/TYPE/UNIT preambles with no data points,
   which manifested in the field as metrics flickering and
   downstream consumers parsing NaN / blank bandwidth. The
   ring is now only reset in --metrics-multipoint mode;
   single-point mode keeps the latest sample visible until
   the next callback overwrites it. cleanup_stale_locked()
   still ages stale flows / peers out after 15 s.
2026-06-05 21:02:54 -04:00
Sergio Ammirata dc4b993302 Merge branch 'fix/prometheus-single-stat-point-slot' into 'master'
fix(prometheus-exporter): write to container[0] in single-stat-point mode

See merge request rist/librist!345
2026-06-06 00:48:34 +00:00
Sergio Ammirata 70f5fc7089 fix(prometheus-exporter): write to container[0] in single-stat-point mode
In single-stat-point mode the writer set container_offset = 1 after
each callback, but the format() macro reads only container[0..count-1]
and container_count stays at 1.  container[0] was therefore frozen at
the value from the first callback while every subsequent callback
silently overwrote container[1], which the reader never touches.

Three sites had the same pattern:
- rist_prometheus_handle_client_stats (receiver flow stats)
- rist_prometheus_handle_client_stats (per-receiver-peer stats)
- rist_prometheus_handle_sender_peer_stats

NEWS entry added under 0.2.18 Bug Fixes.
2026-06-05 20:41:48 -04:00
Sergio Ammirata 003270fbba Merge branch 'feature/url-param-profile' into 'master'
feat: tighten ?profile= and ?srp-compat= URL parameters (numeric, library-side)

See merge request rist/librist!344
2026-06-06 00:37:58 +00:00
Sergio Ammirata e21f1e644c fix(url): parse ?srp-compat= as 0|1, not "legacy"
The original librist convention for URL parameters that select from a
small fixed enum is numeric values (aes-type, congestion-control,
timing-mode, reflector, and as of the previous commit profile).
?srp-compat= was added in v0.2.18-rc1 with a symbolic alias ("legacy")
and a permissive parser that mapped any unrecognised value, including
a typo of the alias, to 0 (compliant).  Tightened to numeric-only
before v0.2.18 final.

The parser now accepts only 0 or 1.  Out-of-range, non-numeric, and
trailing-garbage values cause rist_parse_address2 to return non-zero
with stderr output.

Tests
-----
- test/rist/unit/test_url_srp_compat_parse.c (new): two accepted forms
  plus six rejected forms.
- meson test --suite unit:        6/6 OK (was 5/5).
- meson test --suite url-profile: 4/4 OK.
- meson test (full):              49 OK, 11 Expected Fail, 0 unexpected.
2026-06-05 20:35:00 -04:00
Sergio Ammirata 69e308d5a9 fix(url): make ?profile= override the context wire profile in the library
RIST_URL_PARAM_PROFILE has been declared in the public urlparam.h header
since at least the rist2rist merge but the URL parser never honored it
and the field had no library-side effect.  This commit makes the
parameter do what the public header has always advertised: select the
wire profile of the context that owns the peer, from inside the URL
itself.

The override is implemented entirely in the library so every consumer
that already passes URLs through rist_parse_address2 + rist_peer_create
picks it up without application-side changes.

URL syntax
----------
  ?profile=0    Simple
  ?profile=1    Main
  ?profile=2    Advanced

Out-of-range, non-numeric, or trailing-garbage values cause
rist_parse_address2 to return non-zero with stderr output.

ABI
---
RIST_PEER_CONFIG_VERSION bumps from 3 to 4.  Two trailing fields added
to struct rist_peer_config (profile, profile_set).

Tests
-----
- test/rist/unit/test_url_profile_parse.c: parser assertions including
  the rejected forms.
- test/rist/unit/test_url_profile_override.c: three scenarios through
  the public API - override applies, conflicting peer refused, late
  peer refused.
- test/rist/meson.build: four end-to-end tests where rist_*_create is
  called with a profile that differs from the URL ?profile= value;
  both ends must converge on the URL profile for the test to pass.
- meson test --suite unit:        5/5 OK
- meson test --suite url-profile: 4/4 OK
- meson test (full):              48 OK, 11 Expected Fail, 0 unexpected.

NEWS updated under ABI/API and Bug Fixes.
2026-06-05 20:35:00 -04:00
Sergio Ammirata 6377b5c333 Merge branch 'feature/prometheus-avg-buffer-time' into 'master'
Feature/prometheus avg buffer time

See merge request rist/librist!342
2026-06-05 05:05:35 +00:00
Manuel 10ca00d691 Feature/prometheus avg buffer time 2026-06-05 05:05:32 +00:00
Sergio Ammirata 1c34d95cca Merge branch 'fix/issue-217-receiver-flow-bitrate-decay' into 'master'
stats: refresh receiver flow bitrate counters at tick time

Closes #217

See merge request rist/librist!343
2026-06-05 04:58:14 +00:00
Sergio Ammirata 96740d6647 stats: refresh receiver flow bitrate counters at tick time (#217)
The receiver-side flow bitrate fields (`bitrate`, `bitrate_retries`,
`bitrate_rejected`, `bitrate_ts_nulls`) are only updated when a packet
arrives on the corresponding path, because the bw->bitrate field is
recomputed inside rist_calculate_flow_bitrate() and that helper is only
called from the ingest path. As soon as the traffic on a counter stops
(e.g. retries cease), the matching `bitrate_*` field stays frozen at
the last value it was assigned while traffic was flowing.

Add a small rist_refresh_flow_bitrate() helper that recomputes
bw->bitrate with zero new bytes, mirroring what
rist_calculate_bitrate(0, ...) already does for the sender peer stats
path. The helper deliberately skips the per-flow inter-packet-spacing
bookkeeping that lives in rist_calculate_flow_bitrate(), so calling it
from the stats tick does not perturb the IPS counters.

Call the new helper for all four flow bitrate counters at the start of
the JSON/binary stats emission in rist_receiver_flow_statistics(), so
each counter naturally decays to zero when its traffic stops.
2026-06-05 00:50:05 -04:00
Sergio Ammirata 6955cd4c27 Merge branch 'docs/news-prom-exporter-peer-metrics' into 'master'
NEWS: prom-exporter per-peer metrics and counter format fix (follow-up to !340)

See merge request rist/librist!341
2026-06-04 20:18:34 +00:00
Sergio Ammirata 6ea98b9532 NEWS: prom-exporter per-peer metrics and counter format fix
Two 0.2.18 pre-release bullets for the changes that landed in !340.
2026-06-04 16:17:05 -04:00
Sergio Ammirata 168e863a09 Merge branch 'feat/prometheus-receiver-peer-stats' into 'master'
prom-exporter: add per-peer receiver metrics + fix counter timestamp spacing

See merge request rist/librist!340
2026-06-04 20:10:16 +00:00
Sergio Ammirata 251905d8d0 prom-exporter: separate value and timestamp in counter output
PROMETHEUS_COUNTER_PRINT_R wrote the per-sample timestamp using
"%"PRIu64"\n" with no leading separator, so when single_stat_point
is false (the multiple_metric_datapoints=true setup), every counter
row came out as

  rist_client_flow_received_packets_total{...} 62001780601685

i.e. the value and the timestamp glued together. The sibling
PROMETHEUS_GAUGE_PRINT_R macro uses " %"PRIu64"\n" and emits the
correct "<value> <timestamp>" form. OpenMetrics requires the
timestamp to be space-separated from the value, so a scraper
reading these lines either rejects them or stores one large bogus
number.

Add the missing space. Only the multiple-datapoint branch is
affected; the single_stat_point branch omits the timestamp
entirely and is unchanged.
2026-06-04 15:57:56 -04:00
Sergio Ammirata d2148dbd58 prom-exporter: add per-peer receiver metrics
The Prometheus exporter publishes flow-level aggregates for receivers
(rist_client_flow_*) and per-peer breakdowns for senders
(rist_sender_peer_*) but no per-peer view on the receive side, even
though that data is already populated in rist_stats_receiver_flow.peers[].

Add the receiver-side counterpart as rist_receiver_peer_* with labels
{peer_id, flow_id, receiver_id}, mirroring the sender_peer schema:

  rist_receiver_peer_bandwidth_bps              gauge
  rist_receiver_peer_avg_bandwidth_bps          gauge
  rist_receiver_peer_received_data_packets      counter
  rist_receiver_peer_received_bytes             counter
  rist_receiver_peer_received_rtcp_packets      counter
  rist_receiver_peer_sent_rtcp_packets          counter
  rist_receiver_peer_rtt_seconds                gauge
  rist_receiver_peer_avg_rtt_seconds            gauge

Entries auto-register on first sight of a (receiver_id, flow_id,
peer_id) tuple, so callers do not need to wire peer_ids in at peer
creation time. Re-uses the existing stale-entry sweep (15s) and the
same realloc/abort-on-OOM idiom as sender_peers.

No public API change.
2026-06-04 15:29:39 -04:00
Sergio Ammirata eb00ca04e4 Merge branch 'fix-issue-216-msvc-windows' into 'master'
Fix MSVC build errors and ssize_t warning on Windows (#216)

See merge request rist/librist!339
2026-06-04 19:18:07 +00:00
Sergio Ammirata 9ad7840d17 common: guard ssize_t typedefs with _SSIZE_T_DEFINED
Public header <librist/transport.h> (0.2.18-rc1) typedefs ssize_t
on Windows under an _SSIZE_T_DEFINED guard.  common/attributes.h
defines the same type without a guard, so any TU that includes
both pulls in a duplicate definition and trips MSVC C4142
(benign redefinition).  Wrap each of the three Windows code paths
here in the same _SSIZE_T_DEFINED guard so the two headers are
order-independent.  Closes the warning reported in #216.
2026-06-03 08:05:06 -04:00
Sergio Ammirata 900eda94c3 test(srp): add stdatomic_dependency to srp_unit target
The main librist library target already picks up the compat/msvc
<stdatomic.h> shim when MSVC's system header requires
/experimental:c11atomics, but the srp_unit test target compiles
random.c (and friends) without that dependency and so hits
'C atomic support is not enabled' on plain MSVC builds.  Mirror
the library target's deps so the test follows the same code path.
Closes the second build error in #216.
2026-06-03 08:05:06 -04:00
Sergio Ammirata 9194ed9cef test(srp): only include <sched.h> on POSIX
MSVC has no <sched.h> and pulls calloc's prototype in via <stdlib.h>
(transitively, through <cmocka.h>), so the musl-specific shim is
only needed on POSIX targets.  Closes the first build error in #216.
2026-06-03 08:05:06 -04:00
Sergio Ammirata 20c7958a2c Merge branch 'release/v0.2.18-rc1' into 'master'
NEWS, meson: prepare 0.2.18-rc1

See merge request rist/librist!338
v0.2.18-rc1
2026-06-03 02:02:50 +00:00
Sergio Ammirata 6d59e5ba9e NEWS, meson: prepare 0.2.18-rc1 with SRP interop, XP fix, EAGAIN hardening
Bump project version to 0.2.18, API to 4.10.0, ABI to 13:0:9
(soversion 4 unchanged, binary-compatible with 0.2.15/0.2.16/0.2.17).

Headlining the release:
  - srp-compat=legacy URL opt-in restores pre-0.2.16 interop with the
    RFC 5054 PAD-compliant exchange landed in 0.2.16.
  - Windows XP support restored: rist_transport_poll() uses select()
    via the existing poll_win.c shim instead of WSAPoll() (Vista+).
  - rist_peer_recv() accepts both numeric EAGAIN values (11 and 35) so
    custom transport_ops backends in cross-compile environments work.
  - <librist/transport.h> now defines ssize_t on Windows (#214).
  - CI: srp_unit_test now runs via a cmocka wrap subproject, closing
    the silent-skip that hid the SRP fixture drift reported in #215.
2026-06-02 22:00:39 -04:00
Sergio Ammirata 57c58dcda4 Merge branch 'feat/v0.2.18-fixes' into 'master'
v0.2.18: SRP wire-format compat (#215), transport.h ssize_t (#214), --blind-send, CI coverage

Closes #215 and #214

See merge request rist/librist!337
2026-06-02 21:57:51 +00:00
Sergio Ammirata 43eda0aa6f news: document srp-compat=legacy and v0.2.18 ABI bump
- Replace the placeholder "No source or binary changes" ABI/API entry
  with the actual change: RIST_PEER_CONFIG_VERSION 2 -> 3 plus the
  one new field (srp_compat_legacy).  Existing zero-initialised
  peer configs continue to work; the new field defaults to 0.
- Promote the SRP interop note to its own section and document the
  new srp-compat=legacy URL option together with the advisory hint
  emitted on M1/M2 failure.
- Mention the four new end-to-end SRP unit tests under CI / Test
  Coverage alongside the existing fixture-regeneration entry.
2026-06-02 17:52:25 -04:00
Sergio Ammirata 8ed026a350 test(srp): cover both PAD and legacy modes plus cross-mode failures
Adds srp_run_exchange() — a self-contained authenticator+client
handshake on the 2048-bit NG_DEFAULT group using only the public SRP
API, then drives it through four scenarios:

  1. Both ends in PAD mode (default)            -> verify_m1 == 0
  2. Both ends in legacy unpadded mode          -> verify_m1 == 0
  3. Authenticator PAD, client legacy           -> verify_m1 != 0
  4. Authenticator legacy, client PAD           -> verify_m1 != 0

Cases 3 and 4 are the "operator forgot to set srp-compat on one side"
configurations.  They MUST fail at M1 — otherwise the bypass would be
silently leaking through.  The tests deliberately do NOT assert on the
specific cross-mode failure mode, because the SRP-6a identity
  (A * v^u)^b = (B - k*g^x)^(a + u*x)
only holds when both peers use the same k, so no purely-local check
can produce a deterministic cross-mode M1 to compare against.

Also propagates the new legacy_pad parameter through every existing
ctx_create call site so the deterministic fixture tests continue to
exercise the spec-compliant path.
2026-06-02 17:52:25 -04:00
Sergio Ammirata 25256615bf feat(srp): add srp-compat=legacy URL option for pre-0.2.16 interop
0.2.16 made TLS-SRP RFC 5054 / TR-06-2 §6.4 compliant by adding the
PAD operation to the u and k hash inputs.  That change breaks
SRP-authenticated handshakes against peers still running 0.2.15 or
earlier.  The recommended fix is to upgrade both ends, but
mixed-version deployments need a temporary escape hatch.

This change adds an opt-in legacy wire-format mode:

  rist://user:pass@host:port?srp-compat=legacy

Must be set on BOTH ends.  The library logs an explicit RIST_LOG_WARN
banner on startup whenever the legacy mode is active, marking it as
not TR-06-2 / RFC 5054 compliant.  Default behaviour is unchanged.

Failure-side diagnostic
-----------------------
On SRP M1 (authenticator) or M2 (client) verification failure, the
library now emits a one-shot INFO hint pointing at this option as a
possible cause.  The hint is advisory only — the SRP-6a transcript
binds the M1/M2 hashes to whichever wire mode the authenticator used
when constructing B, so there is no purely-local way to distinguish
"wrong password" from "peer is on the other wire format" once the
handshake has reached M1.  Both possible causes are surfaced.

Plumbing
--------
- include/librist/peer.h: bump RIST_PEER_CONFIG_VERSION 2 -> 3, add
  one trailing int (srp_compat_legacy); zero-initialised configs and
  older clients keep the spec-compliant default.
- include/librist/urlparam.h: new RIST_URL_PARAM_SRP_COMPAT.
- src/rist-common.c: parse srp-compat=legacy | 1 into the new field
  (anything else, including the explicit "auto", leaves PAD mode on).
  store_peer_settings() also copies the flag into peer->config so
  rist_enable_eap_srp_2(), which runs after the caller's config may
  have been freed, can still see it.
- src/crypto/srp.{c,h}: new librist_crypto_srp_hash_2_bignum_unpadded()
  helper (the pre-0.2.16 form) and a single srp_hash_uk() dispatch.
  Both authenticator and client SRP contexts grow a bool legacy_pad
  field set at ctx_create time; the four u/k hash sites pick padded
  vs unpadded based on it.
- src/proto/eap.c: eapsrp_ctx grows srp_legacy_pad + a one-shot
  srp_legacy_peer_warned latch; the flag is read from
  peer->config.srp_compat_legacy when enable_eap_srp_2 builds the
  ctx (both authenticator and authenticatee paths), propagated by
  eap_clone_ctx, and passed into both SRP ctx_create calls.

Default behaviour is unchanged: peers without srp-compat=legacy keep
the PAD-compliant exchange.
2026-06-02 17:50:42 -04:00
Sergio Ammirata 63b11b693d news: SRP interop note, ssize_t fix, CI coverage gap closure
Three additions under 0.2.18 in development:
  - SRP interop note explaining why 0.2.16+ does not handshake
    with 0.2.15 (PAD compliance is the reason; symmetric upgrade
    required).
  - Bug-fix entry for the new Windows ssize_t typedef (#214).
  - CI/test coverage entry noting libcmocka-dev install and the
    PAD-compliant test fixture regeneration (#215).
2026-06-02 17:50:42 -04:00
Sergio Ammirata 97a5242572 transport: define ssize_t on Windows in public transport.h
The recvfrom and sendto callbacks in struct rist_transport_ops
return ssize_t.  On Windows that type is not a Win32 standard
type and is not provided by <winsock2.h>, so a downstream Windows
project that #includes <librist/transport.h> fails to compile
with "unknown type name 'ssize_t'".

Library-internal code worked because librist's private
common/attributes.h provides a typedef, but that header is not
installed.  Add a public typedef inside the existing _WIN32 block,
guarded by _SSIZE_T_DEFINED to coexist with MSVC's CRT and other
public Windows headers that already conditionally define it.

Fixes #214.
2026-06-02 17:50:42 -04:00
Sergio Ammirata f8d31daefe test(srp): regenerate fixtures for 2048-bit NG against PAD-compliant exchange
Since 0.2.16 the SRP authenticator zero-pads the u and k hash inputs
to N-length per RFC 5054 §2.6 (and therefore TR-06-2).  The fixtures
in srp_examples.c were captured against the pre-PAD code and have
failed deterministically since 0.2.16 with both gnutls, system
mbedtls 3.6, and bundled mbedtls — see #215.

A separate hardening change also enforced a 1024-bit minimum for
caller-supplied N in librist_crypto_srp_client_ctx_create(), which
caused test_srp_client_ctx_create (and three downstream tests) to
deref a NULL ctx and segfault on the deprecated 512-bit modulus.

Switch the deterministic setup to the 2048-bit RFC 5054 group
(NG_DEFAULT) and re-record every fixture from the current
PAD-compliant exchange.  The DEBUG_USE_EXAMPLE_CONSTANTS hooks in
srp.c still pin a, b, and salt, so the run remains deterministic.
test_srp_client_ctx_create now exercises both the default_ng=true
path and a custom-N path with NG_2048, asserts non-NULL on every
create, and uses the standard sizeof()/mbedtls_mpi_size() byte
widths instead of the obsolete 64-byte buffers.

Fixes #215 (segfaults + hash mismatches).
2026-06-02 17:50:42 -04:00
Sergio Ammirata c236ac295e ci/test: source cmocka via meson wrap so srp_unit_test always builds
srp_unit_test had been silently skipping in test-ubuntu because the
runner image (libplacebo-ubuntu-jammy) ships without libcmocka-dev
and does not give the CI user write access to /var/lib/apt/lists, so
an in-script apt-get install cannot fix it.  The silent skip is how
the SRP fixture drift reported in #215 reached a release tag.

Switch the meson detection from cc.find_library('cmocka', required:
false) to dependency('cmocka', required: false, fallback:
['cmocka', 'cmocka_dep']) and add subprojects/cmocka.wrap pointing
at WrapDB cmocka_1.1.8-1.  When the runner image has cmocka the
system copy is used; otherwise meson builds it as a subproject.
Existing .gitignore rules (/subprojects/* with !*.wrap) keep the
fetched source out of the tree.

srp_unit's link line now picks up bcrypt on Windows because
random.c calls BCryptGenRandom; the main librist target was already
pulling it in via the top-level deps list, but srp_unit links
random.c directly.

Drop the broken apt-get line from the test-ubuntu script and add
retry: 2 to both test jobs to absorb the simple+multicast environmental
flake that surfaces on the shared runner regardless of branch.
2026-06-02 17:49:55 -04:00
Sergio Ammirata 5ecacd25d9 ci: mark test-win64 as allow_failure pending wine runner fix
The shared CI runner used by test-win64 cannot complete the
send/receive tests within meson's default 30s per-test timeout when
the binaries are executed under wine.  Recent pipelines on master
have been showing 38-41 of 55 tests marked TIMEOUT (with 0-3 real
failures) while the same suite passes on test-ubuntu.

The logs show peer authentication completing, packets flowing, NACK
/ retransmit loops operating normally — there are no crashes,
asserts or hangs, only tests being killed at the 30s boundary
because the wine layer adds enough overhead per test that they
cannot reach their target packet count in time.

Mark test-win64 as allow_failure so wine throughput jitter does not
block unrelated MRs.  The job still runs and its log is still
uploaded on failure, so genuine Windows-runtime regressions remain
visible — they just no longer flip the overall pipeline status.

A TODO is left in the file describing the two ways to drop
allow_failure: bump meson's per-test timeout multiplier for this
job, or move it to a faster runner.
2026-06-02 15:26:54 -04:00
Sergio Ammirata d2ce51690d tools: ristsender: add --blind-send for fire-and-forget broadcast mode
By default ristsender couples the input flow to RIST peer state:
multicast input groups are bound but the IGMP join is deferred until
at least one RIST output peer has completed its handshake, and
incoming UDP data is dropped until peer_connected_count > 0.  That
keeps a freshly-started sender from generating multicast traffic the
network has not yet asked for and from buffering data nobody will
ever read, which is the right behaviour for point-to-point RIST.

The peer-handshake gate is a poor fit for broadcast topologies where
the sender is meant to stream regardless of who is (or isn't)
listening — multicast output in simple/main/advanced profile, or
ristsender as a fan-out in front of non-RIST consumers.

Add --blind-send.  When set:

  - the multicast input socket performs udpsocket_join_mcast_group()
    immediately instead of marking it mcast_deferred, and
  - input_udp_recv() writes data to the RIST context unconditionally.

Default behaviour is unchanged when the option is absent.
2026-06-02 14:42:51 -04:00
Sergio Ammirata d1dcd83a08 transport: match canonical EAGAIN errno values defensively in rist_peer_recv
When a custom transport_ops backend returns -1 from recvfrom() to signal
"no data ready", librist's poll loop relies on errno == EAGAIN to treat
the call as transient and silently retry.  In most environments this is
straightforward, but cross-compilation setups can end up with the
EAGAIN macro resolving to a different numeric value than the one the
custom backend actually wrote into errno.

This has been observed concretely with VLC's WebAssembly build, where
librist is compiled by Emscripten as part of VLC's contrib system: the
WebTransport-backed transport_ops returns -1 with errno = EAGAIN (= 11
in musl/Emscripten), but librist's own translation unit ends up with
EAGAIN expanding to 35 due to header search paths from the host
toolchain leaking into the cross build.  The result was the poll loop
mistaking the transient case for a hard error and spamming
"Receive failed: errno=11" until the RIST handshake gave up.

Recognise both canonical EAGAIN errno values (11 on Linux / Emscripten /
musl, 35 on macOS / *BSD / iOS) explicitly when ret == -1.  On any
platform where EAGAIN resolves consistently the existing macro check
already returns first, so this addition is unreachable and behaviour is
unchanged.  It only fires in the pathological cross-compile case where
the macro and the runtime errno disagree, and protects the same set of
errno values that POSIX recvfrom(2) is documented to use for the
non-blocking "no data" path.

The Windows path (_WIN32) is unaffected.
2026-06-02 14:28:02 -04:00
Sergio Ammirata fdeea28907 Merge branch 'fix/transport-poll-xp-compat' into 'master'
fix(transport): use select()-based poll() shim instead of WSAPoll for XP compatibility

See merge request rist/librist!336
2026-06-02 15:58:29 +00:00
Sergio Ammirata 769a05a797 fix(transport): use select()-based poll() shim instead of WSAPoll
librist 0.2.17 introduced a pluggable transport abstraction
(rist_transport_set + companion rist_transport_poll wrapper) that on
Windows fell back to WSAPoll().  WSAPoll is a Vista+ symbol, so adding
it to the librist link surface dropped runtime support for Windows XP
on the rist_transport_poll() code path — a regression compared with
0.2.16, which only ever called select() on Windows (via the
public-domain poll() shim included from contrib/poll_win.c into
src/libevsocket.c since 2019).

Route rist_transport_poll() through that same select()-based shim
instead of WSAPoll() so the pluggable-transport layer preserves XP
compatibility.  The fix mirrors libevsocket.c's pattern: include
contrib/poll_win.c as a translation-unit-private static function on
Windows, then call poll() unconditionally on both platforms.

Also expose <winsock2.h>'s struct pollfd declaration at the prototype
site (transport-private.h) so the prototype and definition agree even
when callers pin _WIN32_WINNT below 0x0600 (e.g. VLC's 3.0.x contrib
system, which targets legacy Windows XP).  The redefinition is
header-only and pulls in zero Vista+ runtime symbols — only the
struct pollfd typedef visibility changes.

After the fix, mingw-w64 cross-built librist.dll has WSAPoll removed
from its WS2_32.dll import table (verified locally with `objdump -p`);
XP runtime support is preserved.

Reported by Steve Lhomme (@robUx4) during review of VLC merge request
videolan/vlc!9297.
2026-06-02 11:36:43 -04:00
Sergio Ammirata e939a9a4e3 Merge branch 'release/v0.2.17-news' into 'master'
release: mark NEWS for v0.2.17 final

See merge request rist/librist!335
v0.2.17
2026-05-31 14:10:56 +00:00
Sergio Ammirata 04634937ac release: mark NEWS for v0.2.17 final 2026-05-31 14:10:56 +00:00
Sergio Ammirata b971cfa074 Merge branch 'fix/mbedtls-ios-availability' into 'master'
fix(mbedtls): suppress -Wunguarded-availability for bundled build

See merge request rist/librist!334
v0.2.17-rc2
2026-05-30 23:17:05 +00:00
Sergio Ammirata f09bac21b7 fix(mbedtls): suppress -Wunguarded-availability for bundled build
mbedtls 3.6 uses clock_gettime(CLOCK_MONOTONIC) in platform_util.c
which requires iOS 10.0+.  When VLC contribs build with
-Werror=partial-availability and a deployment target of iOS 9.0,
this becomes a hard error.

The runtime fallback already handles the case correctly:
clock_gettime returns -1 on older iOS and mbedtls_ms_time() falls
back to time(NULL) * 1000.

Suppress -Wunguarded-availability for the bundled mbedcrypto static
library only, consistent with the six other warning suppressions
already applied to the bundled build.
2026-05-30 19:11:35 -04:00
Sergio Ammirata 943790307b Merge branch 'feat/multicast-stats-enhancements' into 'master'
feat: multicast enhancements, per-byte stats, local port, deferred join

Closes #174, #158, #112, and #85

See merge request rist/librist!333
v0.2.17-rc1
2026-05-30 21:26:52 +00:00
Sergio Ammirata e845679293 build: bump ABI/API versions and NEWS for v0.2.17
ABI: 12:0:8 (soversion 4, current 12, age 8)
API: 4.9.0
RIST_STATS_VERSION: 2
RIST_STATS_JSON_SCHEMA_VERSION: 3
RIST_PEER_CONFIG_VERSION: 2
2026-05-30 16:10:21 -04:00
Sergio Ammirata 5416522330 feat(ristsender): defer multicast join until peer handshake
When ristsender's input URL is a multicast address, the IGMP join is
now deferred until the first RIST peer handshake completes.  This
prevents receiving multicast traffic before the RIST output path is
ready, avoiding packet loss during the initial connection setup.

The socket is opened and bound immediately (so the port is reserved),
but the multicast group join happens in the connection_status_callback
when RIST_CONNECTION_ESTABLISHED fires.  Unicast inputs are unaffected.

Closes #85
2026-05-30 16:10:21 -04:00
Sergio Ammirata ddbf22b67f feat: configurable local port for caller peers
Add ?local-port=N URL parameter to bind caller (non-listening) peers
to a specific local UDP port instead of using an ephemeral port.

When miface is also set, the specified port is included in the
interface bind.  When only local-port is set, the socket binds to
INADDR_ANY (or in6addr_any) on the given port.

If the bind fails, a warning is logged (with WSAGetLastError on
Windows) and the peer falls back to ephemeral port behavior.

Closes #158
2026-05-30 16:10:21 -04:00
Sergio Ammirata 5e45634c35 feat: per-byte statistics for sender and receiver
Add byte-level counters alongside the existing packet counters:

Sender:
- sent_bytes: total bytes sent (payload + overhead)
- retransmitted_bytes: total bytes retransmitted via ARQ

Receiver:
- received_bytes: per-peer and per-flow data bytes received

New fields appended to rist_stats_sender_peer,
rist_stats_receiver_peer, and rist_stats_receiver_flow structs.
JSON stats include sent_bytes, retransmitted_bytes, and
received_bytes fields.

RIST_STATS_VERSION bumped from 1 to 2.
RIST_STATS_JSON_SCHEMA_VERSION bumped from 2 to 3.

Closes #174
2026-05-30 16:10:21 -04:00
Sergio Ammirata ee70af8816 feat: multicast TTL, IGMPv3 source-specific multicast, and IPv6 join
Add configurable multicast TTL via ?ttl=N URL parameter (1-255) and
IGMPv3 Source-Specific Multicast via ?source=IP URL parameter.

IPv6 multicast join was previously unsupported; implement it via
MCAST_JOIN_GROUP and IPV6_JOIN_GROUP.  SSM support uses
IP_ADD_SOURCE_MEMBERSHIP, guarded with #ifdef for portability.

New public API:
- udpsocket_set_mcast_ttl(): set IPv4 TTL or IPv6 hop limit
- udpsocket_open_bind_mcast(): open+bind with TTL and SSM source
- udpsocket_join_mcast_group(): now public, with IPv6 and SSM support

All new error paths use WSAGetLastError() on Windows instead of
strerror(errno) for correct Winsock error reporting.

New fields in rist_peer_config and rist_udp_config: multicast_ttl,
multicast_source.  RIST_PEER_CONFIG_VERSION bumped from 1 to 2.

Closes #112
2026-05-30 16:10:21 -04:00
Sergio Ammirata e9ce818785 Merge branch 'feature/reflector' into 'master'
feat(core): implement native one-to-many single-listener RIST reflector

See merge request rist/librist!321
2026-05-30 15:53:57 +00:00
Sergio Ammirata e2821fca31 feat: add opt-in ?reflector=1 URL parameter, document trade-offs
Gate reflector logic behind an explicit URL parameter so it is not
active on every receiver listener by default.  Add 'reflector' field
to rist_peer_config and RIST_URL_PARAM_REFLECTOR constant.

Document trade-offs vs rist2rist in the API header, NEWS, and URL
help so users understand the limitations:
  - No per-subscriber retry buffer (relies on publisher's buffer)
  - Retransmissions fan out to ALL subscribers (bandwidth x N)
  - Recovery RTT ~ 2x a direct connection
  - No per-subscriber congestion control or statistics

Best suited for low subscriber counts with clean last-mile links.
For high fan-out or lossy last-mile, use rist2rist instead.
2026-05-30 11:51:31 -04:00
RossWang 2717e00948 feat(core): implement native one-to-many single-listener RIST reflector
Add transparent reflector logic to rist_peer_recv that forwards raw
packets between publisher (sender) and subscriber (receiver) children
before decryption.  Publisher/subscriber roles are detected via buffer
negotiation.  Buffer size is proxied from publisher to subscribers.

Log levels for harmless cross-role packets (NACK on receiver, data on
sender) lowered from ERROR/WARN to DEBUG.

Includes test_reflector.c with 3-phase verification: basic reflection,
NACK/retransmission through reflector, and RTT/buffer convergence.
2026-05-30 11:50:49 -04:00