Skip to content

Commit 7be0de7

Browse files
committed
WHIP: Support ICE handshake by binding.
1. Create UDP URLContext for ICE, DTLS and RTP. 2. Connect to ice-lite server as UDP client. 3. Create, build and send ICE binding request. 4. Read and verify ICE binding response.
1 parent 58821db commit 7be0de7

File tree

1 file changed

+132
-12
lines changed

1 file changed

+132
-12
lines changed

libavformat/rtcenc.c

Lines changed: 132 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@
3232
#include "libavutil/avstring.h"
3333
#include "url.h"
3434
#include "libavutil/random_seed.h"
35+
#include "avio_internal.h"
36+
#include "libavutil/hmac.h"
37+
#include "libavutil/crc.h"
3538

3639
#define MAX_SDP_SIZE 8192
40+
#define MAX_UDP_SIZE 1500
3741

3842
typedef struct RTCContext {
3943
AVClass *av_class;
@@ -70,6 +74,9 @@ typedef struct RTCContext {
7074
int ice_port;
7175
/* The SDP answer received from the WebRTC server. */
7276
char *sdp_answer;
77+
78+
/* The UDP transport is used for delivering ICE, DTLS and SRTP packets. */
79+
URLContext *udp_uc;
7380
} RTCContext;
7481

7582
/**
@@ -97,12 +104,12 @@ static int check_codec(AVFormatContext *s)
97104
if (par->codec_id != AV_CODEC_ID_H264) {
98105
av_log(s, AV_LOG_ERROR, "Unsupported video codec %s by RTC, choose h264\n",
99106
desc ? desc->name : "unknown");
100-
return AVERROR(EINVAL);
107+
return AVERROR_PATCHWELCOME;
101108
}
102109
if ((par->profile & ~FF_PROFILE_H264_CONSTRAINED) != FF_PROFILE_H264_BASELINE) {
103110
av_log(s, AV_LOG_ERROR, "Profile %d of stream %d is not baseline, currently unsupported by RTC\n",
104111
par->profile, i);
105-
return AVERROR(EINVAL);
112+
return AVERROR_PATCHWELCOME;
106113
}
107114
break;
108115
case AVMEDIA_TYPE_AUDIO:
@@ -115,24 +122,24 @@ static int check_codec(AVFormatContext *s)
115122
if (par->codec_id != AV_CODEC_ID_OPUS) {
116123
av_log(s, AV_LOG_ERROR, "Unsupported audio codec %s by RTC, choose opus\n",
117124
desc ? desc->name : "unknown");
118-
return AVERROR(EINVAL);
125+
return AVERROR_PATCHWELCOME;
119126
}
120127

121128
if (par->ch_layout.nb_channels != 2) {
122129
av_log(s, AV_LOG_ERROR, "Unsupported audio channels %d by RTC, choose stereo\n",
123130
par->ch_layout.nb_channels);
124-
return AVERROR(EINVAL);
131+
return AVERROR_PATCHWELCOME;
125132
}
126133

127134
if (par->sample_rate != 48000) {
128135
av_log(s, AV_LOG_ERROR, "Unsupported audio sample rate %d by RTC, choose 48000\n", par->sample_rate);
129-
return AVERROR(EINVAL);
136+
return AVERROR_PATCHWELCOME;
130137
}
131138
break;
132139
default:
133140
av_log(s, AV_LOG_ERROR, "Codec type '%s' for stream %d is not supported by RTC\n",
134141
av_get_media_type_string(par->codec_type), i);
135-
return AVERROR(EINVAL);
142+
return AVERROR_PATCHWELCOME;
136143
}
137144
}
138145

@@ -194,7 +201,7 @@ static int generate_sdp_offer(AVFormatContext *s)
194201
char *tmp = av_mallocz(MAX_SDP_SIZE);
195202
if (!tmp) {
196203
av_log(s, AV_LOG_ERROR, "Failed to alloc answer: %s", s->url);
197-
return AVERROR(EINVAL);
204+
return AVERROR(ENOMEM);
198205
}
199206

200207
if (rtc->sdp_offer) {
@@ -274,7 +281,7 @@ static int generate_sdp_offer(AVFormatContext *s)
274281
rtc->video_ssrc);
275282
if (ret >= MAX_SDP_SIZE) {
276283
av_log(s, AV_LOG_ERROR, "Offer %d exceed max %d, %s", ret, MAX_SDP_SIZE, tmp);
277-
ret = AVERROR(EINVAL);
284+
ret = AVERROR(EIO);
278285
goto end;
279286
}
280287

@@ -338,7 +345,7 @@ static int exchange_sdp(AVFormatContext *s)
338345
char *tmp = av_mallocz(MAX_SDP_SIZE);
339346
if (!tmp) {
340347
av_log(s, AV_LOG_ERROR, "Failed to alloc answer: %s", s->url);
341-
return AVERROR(EINVAL);
348+
return AVERROR(ENOMEM);
342349
}
343350

344351
ret = ffurl_alloc(&whip_uc, s->url, AVIO_FLAG_READ_WRITE, &s->interrupt_callback);
@@ -376,7 +383,7 @@ static int exchange_sdp(AVFormatContext *s)
376383
ret = av_strlcatf(tmp, MAX_SDP_SIZE, "%.*s", ret, buf);
377384
if (ret >= MAX_SDP_SIZE) {
378385
av_log(s, AV_LOG_ERROR, "Answer %d exceed max size %d, %s", ret, MAX_SDP_SIZE, tmp);
379-
ret = AVERROR(EINVAL);
386+
ret = AVERROR(EIO);
380387
goto end;
381388
}
382389
}
@@ -426,14 +433,14 @@ static int parse_answer(AVFormatContext *s)
426433
if (ret != 4) {
427434
av_log(s, AV_LOG_ERROR, "Failed %d to parse line %d %s from %s",
428435
ret, i, line, rtc->sdp_answer);
429-
ret = AVERROR(EINVAL);
436+
ret = AVERROR(EIO);
430437
goto end;
431438
}
432439

433440
if (av_strcasecmp(protocol, "udp")) {
434441
av_log(s, AV_LOG_ERROR, "Protocol %s is not supported by RTC, choose udp, line %d %s of %s",
435442
protocol, i, line, rtc->sdp_answer);
436-
ret = AVERROR(EINVAL);
443+
ret = AVERROR(EIO);
437444
goto end;
438445
}
439446

@@ -453,6 +460,115 @@ static int parse_answer(AVFormatContext *s)
453460
return ret;
454461
}
455462

463+
/**
464+
* Open the UDP transport and complete the ICE handshake.
465+
*
466+
* @return 0 if OK, AVERROR_xxx on error
467+
*/
468+
static int ice_handshake(AVFormatContext *s)
469+
{
470+
int ret, len, crc32;
471+
char url[256], buf[MAX_UDP_SIZE];
472+
AVIOContext *pb = NULL;
473+
AVHMAC *hmac = NULL;
474+
RTCContext *rtc = s->priv_data;
475+
476+
pb = avio_alloc_context(buf, sizeof(buf), AVIO_FLAG_WRITE, NULL, NULL, NULL, NULL);
477+
if (!pb) {
478+
av_log(s, AV_LOG_ERROR, "Failed to alloc AVIOContext for ICE");
479+
ret = AVERROR(ENOMEM);
480+
goto end;
481+
}
482+
483+
hmac = av_hmac_alloc(AV_HMAC_SHA1);
484+
if (!hmac) {
485+
av_log(s, AV_LOG_ERROR, "Failed to alloc AVHMAC for ICE");
486+
ret = AVERROR(ENOMEM);
487+
goto end;
488+
}
489+
490+
/* Build UDP URL and create the UDP context as transport. */
491+
ff_url_join(url, sizeof(url), "udp", NULL, rtc->ice_host, rtc->ice_port, NULL);
492+
ret = ffurl_alloc(&rtc->udp_uc, url, AVIO_FLAG_WRITE | AVIO_FLAG_NONBLOCK, &s->interrupt_callback);
493+
if (ret < 0) {
494+
av_log(s, AV_LOG_ERROR, "Failed to open udp://%s:%d", rtc->ice_host, rtc->ice_port);
495+
goto end;
496+
}
497+
498+
av_opt_set(rtc->udp_uc->priv_data, "connect", "1", 0);
499+
av_opt_set(rtc->udp_uc->priv_data, "fifo_size", "0", 0);
500+
501+
ret = ffurl_connect(rtc->udp_uc, NULL);
502+
if (ret < 0) {
503+
av_log(s, AV_LOG_ERROR, "Failed to connect udp://%s:%d", rtc->ice_host, rtc->ice_port);
504+
goto end;
505+
}
506+
507+
/* Set the transport as READ and WRITE after connected. */
508+
rtc->udp_uc->flags |= AVIO_FLAG_READ;
509+
510+
/* Build and send the STUN binding request. */
511+
/* Write 20 bytes header */
512+
avio_wb16(pb, 0x0001); /* STUN binding request */
513+
avio_wb16(pb, 0); /* length */
514+
avio_wb32(pb, 0x2112A442); /* magic cookie */
515+
avio_wb32(pb, av_get_random_seed()); /* transaction ID */
516+
avio_wb32(pb, av_get_random_seed()); /* transaction ID */
517+
avio_wb32(pb, av_get_random_seed()); /* transaction ID */
518+
/* Write the username attribute */
519+
ret = snprintf(url, sizeof(url), "%s:%s", rtc->ice_ufrag_remote, rtc->ice_ufrag_local);
520+
avio_wb16(pb, 0x0006); /* attribute type username */
521+
avio_wb16(pb, ret); /* size of username */
522+
avio_write(pb, url, ret); /* bytes of username */
523+
ffio_fill(pb, 0, (4 - (ret % 4)) % 4); /* padding */
524+
/* Build and update message integrity */
525+
avio_wb16(pb, 0x0008); /* attribute type message integrity */
526+
avio_wb16(pb, 20); /* size of message integrity */
527+
ffio_fill(pb, 0, 20); /* fill with zero to directly write and skip it */
528+
len = avio_tell(pb);
529+
buf[2] = (len - 20) >> 8;
530+
buf[3] = (len - 20) & 0xFF;
531+
av_hmac_init(hmac, rtc->ice_pwd_local, strlen(rtc->ice_pwd_local));
532+
av_hmac_update(hmac, buf, len - 24);
533+
av_hmac_final(hmac, buf + len - 20, 20);
534+
/* Write the fingerprint attribute */
535+
avio_wb16(pb, 0x8028); /* attribute type fingerprint */
536+
avio_wb16(pb, 4); /* size of fingerprint */
537+
ffio_fill(pb, 0, 4); /* fill with zero to directly write and skip it */
538+
len = avio_tell(pb);
539+
buf[2] = (len - 20) >> 8;
540+
buf[3] = (len - 20) & 0xFF;
541+
crc32 = av_crc(av_crc_get_table(AV_CRC_32_IEEE_LE), 0xFFFFFFFF, buf, len - 8) ^ 0xFFFFFFFF;
542+
avio_skip(pb, -4);
543+
avio_wb32(pb, crc32 ^ 0x5354554E); /* hash message by CRC32 */
544+
545+
ret = ffurl_write(rtc->udp_uc, buf, len);
546+
if (ret < 0) {
547+
av_log(s, AV_LOG_ERROR, "Failed to send STUN binding request, size=%d", len);
548+
goto end;
549+
}
550+
551+
/* Read the STUN binding response. */
552+
ret = ffurl_read(rtc->udp_uc, buf, sizeof(buf));
553+
if (ret < 0) {
554+
av_log(s, AV_LOG_ERROR, "Failed to read STUN binding response");
555+
goto end;
556+
}
557+
558+
if (ret < 2 || buf[0] != 0x01 || buf[1] != 0x01) {
559+
av_log(s, AV_LOG_ERROR, "Invalid STUN binding response, size=%d, type=%02X%02X", ret, buf[0], buf[1]);
560+
ret = AVERROR(EIO);
561+
goto end;
562+
}
563+
av_log(s, AV_LOG_VERBOSE, "ICE STUN ok, url=udp://%s:%d, username=%s:%s, request=%dB, response=%dB\n",
564+
rtc->ice_host, rtc->ice_port, rtc->ice_ufrag_remote, rtc->ice_ufrag_local, len, ret);
565+
566+
end:
567+
avio_context_free(&pb);
568+
av_hmac_free(hmac);
569+
return ret;
570+
}
571+
456572
static av_cold int rtc_init(AVFormatContext *s)
457573
{
458574
int ret;
@@ -469,6 +585,9 @@ static av_cold int rtc_init(AVFormatContext *s)
469585
if ((ret = parse_answer(s)) < 0)
470586
return ret;
471587

588+
if ((ret = ice_handshake(s)) < 0)
589+
return ret;
590+
472591
return 0;
473592
}
474593

@@ -496,6 +615,7 @@ static av_cold void rtc_deinit(AVFormatContext *s)
496615
av_freep(&rtc->ice_pwd_remote);
497616
av_freep(&rtc->ice_protocol);
498617
av_freep(&rtc->ice_host);
618+
ffurl_closep(&rtc->udp_uc);
499619
}
500620

501621
static const AVOption options[] = {

0 commit comments

Comments
 (0)