Skip to content

Commit e66b0fb

Browse files
committed
Implement ICE lite support and consent freshness
Signed-off-by: Sergio Garcia Murillo <[email protected]>
1 parent 86490ff commit e66b0fb

File tree

1 file changed

+67
-3
lines changed

1 file changed

+67
-3
lines changed

libavformat/whip.c

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
#include "network.h"
4343
#include "srtp.h"
4444
#include "tls.h"
45-
45+
#include <errno.h>
4646
/**
4747
* Maximum size limit of a Session Description Protocol (SDP),
4848
* be it an offer or answer.
@@ -143,6 +143,12 @@
143143
#define WHIP_RTCP_PT_START 192
144144
#define WHIP_RTCP_PT_END 223
145145

146+
/**
147+
* Consent-freshness constants ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
148+
*/
149+
#define WHIP_CONSENT_DEF_INTERVAL 15000 /* ms – RFC 7675 default */
150+
#define WHIP_CONSENT_MAX_FAILURES 3
151+
146152
/**
147153
* In the case of ICE-LITE, these fields are not used; instead, they are defined
148154
* as constant values.
@@ -156,9 +162,11 @@
156162
/* STUN Attribute, comprehension-required range (0x0000-0x7FFF) */
157163
enum STUNAttr {
158164
STUN_ATTR_USERNAME = 0x0006, /// shared secret response/bind request
165+
STUN_ATTR_PRIORITY = 0x0024, /// ICE controlling/controlled
159166
STUN_ATTR_USE_CANDIDATE = 0x0025, /// bind request
160167
STUN_ATTR_MESSAGE_INTEGRITY = 0x0008, /// bind request/response
161168
STUN_ATTR_FINGERPRINT = 0x8028, /// rfc5389
169+
STUN_ATTR_ICE_CONTROLLING = 0x802A, /// full agent talking to ice-lite
162170
};
163171

164172
enum WHIPState {
@@ -303,6 +311,15 @@ typedef struct WHIPContext {
303311
/* The certificate and private key used for DTLS handshake. */
304312
char* cert_file;
305313
char* key_file;
314+
315+
/* Consent-freshness state */
316+
int consent_interval;
317+
int64_t last_consent_tx;
318+
int consent_failures;
319+
320+
/* ICE-lite support */
321+
int ice_lite_remote;
322+
uint64_t ice_tie_breaker; /* random 64-bit, for ICE-CONTROLLING */
306323
} WHIPContext;
307324

308325
/**
@@ -412,6 +429,15 @@ static av_cold int initialize(AVFormatContext *s)
412429
seed = av_get_random_seed();
413430
av_lfg_init(&whip->rnd, seed);
414431

432+
/* 64-bit tie-breaker for ICE-CONTROLLING (RFC 8445 6.1.1) */
433+
whip->ice_tie_breaker = ((uint64_t)av_lfg_get(&whip->rnd) << 32) | (uint64_t)av_lfg_get(&whip->rnd);
434+
435+
/* Initialise consent-freshness timers */
436+
if (whip->consent_interval <= 0)
437+
whip->consent_interval = WHIP_CONSENT_DEF_INTERVAL;
438+
whip->last_consent_tx = av_gettime();
439+
whip->consent_failures = 0;
440+
415441
if (whip->pkt_size < ideal_pkt_size)
416442
av_log(whip, AV_LOG_WARNING, "WHIP: pkt_size=%d(<%d) is too small, may cause packet loss\n",
417443
whip->pkt_size, ideal_pkt_size);
@@ -894,6 +920,8 @@ static int parse_answer(AVFormatContext *s)
894920
goto end;
895921
}
896922
}
923+
} else if (av_strstart(line, "a=ice-lite", NULL)) {
924+
whip->ice_lite_remote = 1;
897925
}
898926
}
899927

@@ -985,6 +1013,22 @@ static int ice_create_request(AVFormatContext *s, uint8_t *buf, int buf_size, in
9851013
avio_wb16(pb, STUN_ATTR_USE_CANDIDATE); /* attribute type use-candidate */
9861014
avio_wb16(pb, 0); /* size of use-candidate */
9871015

1016+
/**
1017+
* For ICE-lite peers we are *always* the controlling agent (RFC 8445 6.1.3.1).
1018+
* Add PRIORITY + ICE-CONTROLLING attributes.
1019+
*/
1020+
if (whip->ice_lite_remote) {
1021+
/* we are controlling, use host-candidate priority 126 << 24 | 65535 << 8 | 255 = 2130706431 */
1022+
avio_wb16(pb, STUN_ATTR_PRIORITY);
1023+
avio_wb16(pb, 4);
1024+
avio_wb32(pb, 2130706431);
1025+
1026+
avio_wb16(pb, STUN_ATTR_ICE_CONTROLLING);
1027+
avio_wb16(pb, 8);
1028+
avio_wb32(pb, (uint32_t)(whip->ice_tie_breaker >> 32));
1029+
avio_wb32(pb, (uint32_t)(whip->ice_tie_breaker & 0xffffffff));
1030+
}
1031+
9881032
/* Build and update message integrity */
9891033
avio_wb16(pb, STUN_ATTR_MESSAGE_INTEGRITY); /* attribute type message integrity */
9901034
avio_wb16(pb, 20); /* size of message integrity */
@@ -1775,14 +1819,27 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt)
17751819
AVStream *st = s->streams[pkt->stream_index];
17761820
AVFormatContext *rtp_ctx = st->priv_data;
17771821

1778-
/* TODO: Send binding request every 1s as WebRTC heartbeat. */
1822+
/* Periodic consent-freshness STUN Binding Request */
1823+
int64_t now = av_gettime();
1824+
if (now - whip->last_consent_tx >= (int64_t)whip->consent_interval * 1000) {
1825+
int req_sz;
1826+
if (ice_create_request(s, whip->buf, sizeof(whip->buf), &req_sz) >= 0 && ffurl_write(whip->udp, whip->buf, req_sz) == req_sz) {
1827+
whip->consent_failures++;
1828+
whip->last_consent_tx = now;
1829+
av_log(whip, AV_LOG_VERBOSE, "WHIP: consent-freshness request %d sent\n", whip->consent_failures);
1830+
}
1831+
}
17791832

17801833
/**
17811834
* Receive packets from the server such as ICE binding requests, DTLS messages,
17821835
* and RTCP like PLI requests, then respond to them.
17831836
*/
17841837
ret = ffurl_read(whip->udp, whip->buf, sizeof(whip->buf));
17851838
if (ret > 0) {
1839+
if (ice_is_binding_response(whip->buf, ret)) {
1840+
whip->consent_failures = 0;
1841+
av_log(whip, AV_LOG_VERBOSE, "WHIP: consent-freshness response received, counter reset\n");
1842+
}
17861843
if (is_dtls_packet(whip->buf, ret)) {
17871844
if ((ret = ffurl_write(whip->dtls_uc, whip->buf, ret)) < 0) {
17881845
av_log(whip, AV_LOG_ERROR, "WHIP: Failed to handle DTLS message\n");
@@ -1793,6 +1850,12 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt)
17931850
av_log(whip, AV_LOG_ERROR, "WHIP: Failed to read from UDP socket\n");
17941851
goto end;
17951852
}
1853+
/* Check consent freshness consecutive failures */
1854+
if (whip->consent_failures >= WHIP_CONSENT_MAX_FAILURES) {
1855+
av_log(whip, AV_LOG_ERROR, "WHIP: No consent-freshness response after %d attempts, closing\n", WHIP_CONSENT_MAX_FAILURES);
1856+
ret = AVERROR(EHOSTUNREACH);
1857+
goto end;
1858+
}
17961859

17971860
if (whip->h264_annexb_insert_sps_pps && st->codecpar->codec_id == AV_CODEC_ID_H264) {
17981861
if ((ret = h264_annexb_insert_sps_pps(s, pkt)) < 0) {
@@ -1891,7 +1954,8 @@ static const AVOption options[] = {
18911954
{ "pkt_size", "The maximum size, in bytes, of RTP packets that send out", OFFSET(pkt_size), AV_OPT_TYPE_INT, { .i64 = 1200 }, -1, INT_MAX, DEC },
18921955
{ "authorization", "The optional Bearer token for WHIP Authorization", OFFSET(authorization), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, DEC },
18931956
{ "cert_file", "The optional certificate file path for DTLS", OFFSET(cert_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, DEC },
1894-
{ "key_file", "The optional private key file path for DTLS", OFFSET(key_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, DEC },
1957+
{ "key_file", "The optional private key file path for DTLS", OFFSET(key_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, DEC },
1958+
{ "consent_interval", "STUN consent refresh interval in ms (RFC 7675)", OFFSET(consent_interval), AV_OPT_TYPE_INT, { .i64 = WHIP_CONSENT_DEF_INTERVAL }, 5000, 30000, DEC },
18951959
{ NULL },
18961960
};
18971961

0 commit comments

Comments
 (0)