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

Add SRTP for remote publishers #3449

Merged
Merged
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
194 changes: 182 additions & 12 deletions src/plugins/janus_videoroom.c
Original file line number Diff line number Diff line change
Expand Up @@ -1608,6 +1608,8 @@ room-<unique room ID>: {
"mcast" : "<multicast group port for receiving RTP packets, if any>",
"iface" : "<network interface or IP address to bind to, if any (binds to all otherwise)>",
"port" : <local port for receiving all RTP packets; 0 will bind to a random one (default)>,
"srtp_suite" : <length of authentication tag (32 or 80); optional>,
"srtp_crypto" : "<key to use as crypto (base64 encoded key as in SDES); optional>",
"streams" : [
{
"type" : "<type of published stream #1 (audio|video|data)">,
Expand Down Expand Up @@ -1657,6 +1659,8 @@ room-<unique room ID>: {
"secret" : "<password required to edit the room, mandatory if configured in the room>",
"display" : "<new display name for the remote publisher; optional>",
"metadata" : <new valid json object of metadata; optional>,
"srtp_suite" : <length of authentication tag (32 or 80); optional>,
"srtp_crypto" : "<key to use as crypto (base64 encoded key as in SDES); optional>",
"streams" : [
{
// Same syntax as add_remote_publisher: only needs to
Expand Down Expand Up @@ -1713,7 +1717,9 @@ room-<unique room ID>: {
"host" : "<host address to forward the RTP and data packets to>",
"host_family" : "<ipv4|ipv6, if we need to resolve the host address to an IP; by default, whatever we get>",
"port" : <port to forward the packets to>,
"rtcp_port" : <port to contact to receive RTCP feedback from the recipient; optional, and only for RTP streams, not data>
"rtcp_port" : <port to contact to receive RTCP feedback from the recipient; optional, and only for RTP streams, not data>,
"srtp_suite" : <length of authentication tag (32 or 80); optional>,
"srtp_crypto" : "<key to use as crypto (base64 encoded key as in SDES); optional>"
}
\endverbatim
*
Expand Down Expand Up @@ -2194,7 +2200,9 @@ static struct janus_json_parameter publish_remotely_parameters[] = {
{"host", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
{"host_family", JSON_STRING, 0},
{"port", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE | JANUS_JSON_PARAM_REQUIRED},
{"rtcp_port", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE}
{"rtcp_port", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
{"srtp_suite", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
{"srtp_crypto", JSON_STRING, 0}
};
static struct janus_json_parameter unpublish_remotely_parameters[] = {
{"secret", JSON_STRING, 0},
Expand All @@ -2207,13 +2215,17 @@ static struct janus_json_parameter remote_publisher_parameters[] = {
{"iface", JANUS_JSON_STRING, 0},
{"port", JANUS_JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
{"streams", JANUS_JSON_ARRAY, JANUS_JSON_PARAM_REQUIRED},
{"metadata", JSON_OBJECT, 0}
{"metadata", JSON_OBJECT, 0},
{"srtp_suite", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
{"srtp_crypto", JSON_STRING, 0}
};
static struct janus_json_parameter remote_publisher_update_parameters[] = {
{"secret", JSON_STRING, 0},
{"display", JANUS_JSON_STRING, 0},
{"metadata", JSON_OBJECT, 0},
{"streams", JANUS_JSON_ARRAY, JANUS_JSON_PARAM_REQUIRED}
{"streams", JANUS_JSON_ARRAY, JANUS_JSON_PARAM_REQUIRED},
{"srtp_suite", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
{"srtp_crypto", JSON_STRING, 0}
};
static struct janus_json_parameter remote_publisher_stream_parameters[] = {
{"mid", JANUS_JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
Expand Down Expand Up @@ -2498,6 +2510,12 @@ typedef struct janus_videoroom_publisher_stream {
volatile gint need_pli; /* Whether we need to send a PLI later */
volatile gint sending_pli; /* Whether we're currently sending a PLI */
gint64 pli_latest; /* Time of latest sent PLI (to avoid flooding) */
/* Only needed for SRTP support for remote publisher */
gboolean is_srtp;
int srtp_suite;
char *srtp_crypto;
srtp_t srtp_ctx;
srtp_policy_t srtp_policy;
/* Subscriptions to this publisher stream (who's receiving it) */
GSList *subscribers;
janus_mutex subscribers_mutex;
Expand Down Expand Up @@ -2622,11 +2640,15 @@ typedef struct janus_videoroom_remote_recipient {
uint16_t port; /* Port this publisher is being relayed to */
uint16_t rtcp_port; /* RTCP port this publisher is going to latch to */
gboolean rtcp_added; /* Whether we created an RTCP socket for this remotization */
/* Only needed for SRTP support for remote publisher */
int srtp_suite;
char *srtp_crypto;
} janus_videoroom_remote_recipient;
static void janus_videoroom_remote_recipient_free(janus_videoroom_remote_recipient *r) {
if(r) {
g_free(r->remote_id);
g_free(r->host);
g_free(r->srtp_crypto);
g_free(r);
}
}
Expand Down Expand Up @@ -2710,6 +2732,11 @@ static void janus_videoroom_publisher_stream_free(const janus_refcount *ps_ref)
janus_mutex_destroy(&ps->subscribers_mutex);
janus_mutex_destroy(&ps->rid_mutex);
janus_rtp_simulcasting_cleanup(NULL, NULL, ps->rid, NULL);
if(ps->is_srtp) {
g_free(ps->srtp_crypto);
srtp_dealloc(ps->srtp_ctx);
g_free(ps->srtp_policy.key);
}
g_free(ps);
}

Expand Down Expand Up @@ -7120,6 +7147,22 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi
if(error_code != 0)
goto prepare_response;
}
/* We may need to SRTP-encrypt this stream */
int srtp_suite = 0;
const char *srtp_crypto = NULL;
json_t *s_suite = json_object_get(root, "srtp_suite");
json_t *s_crypto = json_object_get(root, "srtp_crypto");
if(s_suite && s_crypto) {
srtp_suite = json_integer_value(s_suite);
if(srtp_suite != 32 && srtp_suite != 80) {
JANUS_LOG(LOG_ERR, "Invalid SRTP suite (%d)\n", srtp_suite);
error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
g_snprintf(error_cause, 512, "Invalid SRTP suite (%d)", srtp_suite);
goto prepare_response;
}
srtp_crypto = json_string_value(s_crypto);
JANUS_LOG(LOG_VERB, "SRTP setting s_suite (%d) and s_crypto (%s) on publish_remotely\n", srtp_suite, srtp_crypto);
}
const char *remote_id = json_string_value(json_object_get(root, "remote_id"));
json_t *pub_id = json_object_get(root, "publisher_id");
json_t *json_host = json_object_get(root, "host");
Expand Down Expand Up @@ -7267,7 +7310,7 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi
f = janus_videoroom_rtp_forwarder_add_helper(publisher, ps,
host, port, -1, 0,
(REMOTE_PUBLISHER_BASE_SSRC + ps->mindex*REMOTE_PUBLISHER_SSRC_STEP),
FALSE, 0, NULL, 0, FALSE, FALSE);
FALSE, srtp_suite, srtp_crypto, 0, FALSE, FALSE);
if(f != NULL)
f->metadata = g_strdup(remote_id);
} else if(ps->type == JANUS_VIDEOROOM_MEDIA_VIDEO) {
Expand All @@ -7276,7 +7319,7 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi
f = janus_videoroom_rtp_forwarder_add_helper(publisher, ps,
host, port, add_rtcp ? rtcp_port : -1, 0,
(REMOTE_PUBLISHER_BASE_SSRC + ps->mindex*REMOTE_PUBLISHER_SSRC_STEP),
FALSE, 0, NULL, 0, TRUE, FALSE);
FALSE, srtp_suite, srtp_crypto, 0, TRUE, FALSE);
if(f != NULL)
f->metadata = g_strdup(remote_id);
if(add_rtcp)
Expand All @@ -7286,15 +7329,15 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi
f = janus_videoroom_rtp_forwarder_add_helper(publisher, ps,
host, port, -1, 0,
(REMOTE_PUBLISHER_BASE_SSRC + ps->mindex*REMOTE_PUBLISHER_SSRC_STEP + 1),
FALSE, 0, NULL, 1, TRUE, FALSE);
FALSE, srtp_suite, srtp_crypto, 1, TRUE, FALSE);
if(f != NULL)
f->metadata = g_strdup(remote_id);
}
if(ps->vssrc[2] || ps->rid[2]) {
f = janus_videoroom_rtp_forwarder_add_helper(publisher, ps,
host, port, -1, 0,
(REMOTE_PUBLISHER_BASE_SSRC + ps->mindex*REMOTE_PUBLISHER_SSRC_STEP + 2),
FALSE, 0, NULL, 2, TRUE, FALSE);
FALSE, srtp_suite, srtp_crypto, 2, TRUE, FALSE);
if(f != NULL)
f->metadata = g_strdup(remote_id);
}
Expand All @@ -7316,6 +7359,8 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi
recipient->port = port;
recipient->rtcp_port = rtcp_port;
recipient->rtcp_added = rtcp_added;
recipient->srtp_suite = srtp_suite;
recipient->srtp_crypto = srtp_crypto ? g_strdup(srtp_crypto) : NULL;
g_hash_table_insert(publisher->remote_recipients, g_strdup(remote_id), recipient);
/* Done */
janus_mutex_unlock(&publisher->rtp_forwarders_mutex);
Expand Down Expand Up @@ -7592,6 +7637,22 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi
}
}
}
/* We may need to SRTP-decrypt this stream */
int srtp_suite = 0;
const char *srtp_crypto = NULL;
json_t *s_suite = json_object_get(root, "srtp_suite");
json_t *s_crypto = json_object_get(root, "srtp_crypto");
if(s_suite && s_crypto) {
srtp_suite = json_integer_value(s_suite);
if(srtp_suite != 32 && srtp_suite != 80) {
JANUS_LOG(LOG_ERR, "Invalid SRTP suite (%d)\n", srtp_suite);
error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
g_snprintf(error_cause, 512, "Invalid SRTP suite (%d)", srtp_suite);
goto prepare_response;
}
srtp_crypto = json_string_value(s_crypto);
JANUS_LOG(LOG_VERB, "SRTP setting s_suite (%d) and s_crypto (%s) on add_remote_publisher\n", srtp_suite, srtp_crypto);
}
if(error_code != 0)
goto prepare_response;
/* Now access the room */
Expand Down Expand Up @@ -7772,6 +7833,45 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi
gboolean disabled = json_is_true(json_object_get(s, "disabled"));
/* Create a publisher stream */
ps = g_malloc0(sizeof(janus_videoroom_publisher_stream));
if(mtype == JANUS_VIDEOROOM_MEDIA_AUDIO || mtype == JANUS_VIDEOROOM_MEDIA_VIDEO) {
/* First of all, let's check if we need to setup an SRTP for remote publisher */
if(srtp_suite > 0 && srtp_crypto != NULL) {
JANUS_LOG(LOG_VERB, "enabling SRTP crypto (%s) for stream.\n", srtp_crypto);
gsize len = 0;
guchar *srtp_crypto_decoded = g_base64_decode(srtp_crypto, &len);
if(len < SRTP_MASTER_LENGTH) {
/* Something went wrong */
g_free(srtp_crypto_decoded);
JANUS_LOG(LOG_ERR, "Invalid SRTP crypto (%s), disabling stream\n", srtp_crypto);
ps->is_srtp = FALSE;
disabled = TRUE;
} else {
/* Set SRTP policy */
srtp_policy_t *policy = &ps->srtp_policy;
srtp_crypto_policy_set_rtp_default(&policy->rtp);
if(srtp_suite == 32) {
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(&policy->rtp);
} else if(srtp_suite == 80) {
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy->rtp);
}
policy->ssrc.type = ssrc_any_inbound;
policy->key = srtp_crypto_decoded;
policy->next = NULL;
/* Create SRTP context */
srtp_err_status_t res = srtp_create(&ps->srtp_ctx, policy);
if(res == srtp_err_status_ok) {
ps->is_srtp = TRUE;
ps->srtp_suite = srtp_suite;
ps->srtp_crypto = g_strdup(srtp_crypto);
} else {
/* Something went wrong... */
JANUS_LOG(LOG_ERR, "Error creating SRTP context: %d (%s), disabling stream\n", res, janus_srtp_error_str(res));
ps->is_srtp = FALSE;
disabled = TRUE;
}
}
}
}
ps->type = mtype;
ps->mindex = mindex;
char mid[5];
Expand Down Expand Up @@ -7974,6 +8074,22 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi
}
}
}
/* We may need to SRTP-decrypt this stream */
int srtp_suite = 0;
const char *srtp_crypto = NULL;
json_t *s_suite = json_object_get(root, "srtp_suite");
json_t *s_crypto = json_object_get(root, "srtp_crypto");
if(s_suite && s_crypto) {
srtp_suite = json_integer_value(s_suite);
if(srtp_suite != 32 && srtp_suite != 80) {
JANUS_LOG(LOG_ERR, "Invalid SRTP suite (%d)\n", srtp_suite);
error_code = JANUS_VIDEOROOM_ERROR_INVALID_ELEMENT;
g_snprintf(error_cause, 512, "Invalid SRTP suite (%d)", srtp_suite);
goto prepare_response;
}
srtp_crypto = json_string_value(s_crypto);
JANUS_LOG(LOG_VERB, "SRTP setting s_suite (%d) and s_crypto (%s) on add_remote_publisher\n", srtp_suite, srtp_crypto);
}
if(error_code != 0)
goto prepare_response;
/* Now access the room */
Expand Down Expand Up @@ -8061,6 +8177,46 @@ static json_t *janus_videoroom_process_synchronous_request(janus_videoroom_sessi
gboolean disabled = json_is_true(json_object_get(s, "disabled"));
/* Create a publisher stream */
ps = g_malloc0(sizeof(janus_videoroom_publisher_stream));
if(mtype == JANUS_VIDEOROOM_MEDIA_AUDIO || mtype == JANUS_VIDEOROOM_MEDIA_VIDEO) {
/* First of all, let's check if we need to setup an SRTP for remote publisher */
if(srtp_suite > 0 && srtp_crypto != NULL) {
JANUS_LOG(LOG_VERB, "Enabling SRTP crypto (%s) for stream\n", srtp_crypto);
gsize len = 0;
guchar *srtp_crypto_decoded = g_base64_decode(srtp_crypto, &len);
if(len < SRTP_MASTER_LENGTH) {
/* Something went wrong */
g_free(srtp_crypto_decoded);
JANUS_LOG(LOG_ERR, "Invalid SRTP crypto (%s), disabling stream\n", srtp_crypto);
disabled = TRUE;
} else {
/* Set SRTP policy */
srtp_policy_t *policy = &ps->srtp_policy;
srtp_crypto_policy_set_rtp_default(&policy->rtp);
if(srtp_suite == 32) {
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(&policy->rtp);
} else if(srtp_suite == 80) {
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy->rtp);
}
policy->ssrc.type = ssrc_any_inbound;
policy->key = srtp_crypto_decoded;
policy->next = NULL;
/* Create SRTP context */
srtp_err_status_t res = srtp_create(&ps->srtp_ctx, policy);
if(res == srtp_err_status_ok) {
ps->is_srtp = TRUE;
ps->srtp_suite = srtp_suite;
ps->srtp_crypto = g_strdup(srtp_crypto);
} else {
/* Something went wrong... */
JANUS_LOG(LOG_ERR, "Error creating SRTP context: %d (%s), disabling stream\n", res, janus_srtp_error_str(res));
ps->is_srtp = FALSE;
disabled = TRUE;
}
}
} else {
JANUS_LOG(LOG_ERR, "SRTP crypto (%d) (%s) not enabled for stream\n", srtp_suite, srtp_crypto);
}
}
ps->type = mtype;
ps->mindex = mindex;
char pmid[5];
Expand Down Expand Up @@ -12918,7 +13074,7 @@ static void *janus_videoroom_handler(void *data) {
f = janus_videoroom_rtp_forwarder_add_helper(participant, ps,
r->host, r->port, -1, 0,
(REMOTE_PUBLISHER_BASE_SSRC + ps->mindex*REMOTE_PUBLISHER_SSRC_STEP),
FALSE, 0, NULL, 0, FALSE, FALSE);
FALSE, r->srtp_suite, r->srtp_crypto, 0, FALSE, FALSE);
if(f != NULL)
f->metadata = g_strdup(r->remote_id);
} else if(ps->type == JANUS_VIDEOROOM_MEDIA_VIDEO) {
Expand All @@ -12927,7 +13083,7 @@ static void *janus_videoroom_handler(void *data) {
f = janus_videoroom_rtp_forwarder_add_helper(participant, ps,
r->host, r->port, add_rtcp ? r->rtcp_port : -1, 0,
(REMOTE_PUBLISHER_BASE_SSRC + ps->mindex*REMOTE_PUBLISHER_SSRC_STEP),
FALSE, 0, NULL, 0, TRUE, FALSE);
FALSE, r->srtp_suite, r->srtp_crypto, 0, TRUE, FALSE);
if(f != NULL)
f->metadata = g_strdup(r->remote_id);
if(add_rtcp)
Expand All @@ -12937,15 +13093,15 @@ static void *janus_videoroom_handler(void *data) {
f = janus_videoroom_rtp_forwarder_add_helper(participant, ps,
r->host, r->port, -1, 0,
(REMOTE_PUBLISHER_BASE_SSRC + ps->mindex*REMOTE_PUBLISHER_SSRC_STEP + 1),
FALSE, 0, NULL, 1, TRUE, FALSE);
FALSE, r->srtp_suite, r->srtp_crypto, 1, TRUE, FALSE);
if(f != NULL)
f->metadata = g_strdup(r->remote_id);
}
if(ps->vssrc[2] || ps->rid[2]) {
f = janus_videoroom_rtp_forwarder_add_helper(participant, ps,
r->host, r->port, -1, 0,
(REMOTE_PUBLISHER_BASE_SSRC + ps->mindex*REMOTE_PUBLISHER_SSRC_STEP + 2),
FALSE, 0, NULL, 2, TRUE, FALSE);
FALSE, r->srtp_suite, r->srtp_crypto, 2, TRUE, FALSE);
if(f != NULL)
f->metadata = g_strdup(r->remote_id);
}
Expand Down Expand Up @@ -13745,6 +13901,20 @@ static void *janus_videoroom_remote_publisher_thread(void *user_data) {
janus_videoroom_incoming_data_internal(publisher->session, publisher, &data);
continue;
}
/* Is this SRTP? */
if(ps->is_srtp) {
int buflen = bytes;
srtp_err_status_t res = srtp_unprotect(ps->srtp_ctx, buffer, &buflen);
if(res != srtp_err_status_ok) {
janus_mutex_unlock(&publisher->streams_mutex);
guint32 timestamp = ntohl(rtp->timestamp);
guint16 seq = ntohs(rtp->seq_number);
JANUS_LOG(LOG_ERR, "[%s] Publisher stream (#%d) SRTP unprotect error: %s (len=%d-->%d, ts=%"SCNu32", seq=%"SCNu16")\n",
publisher->user_id_str, ps->mindex, janus_srtp_error_str(res), bytes, buflen, timestamp, seq);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will flood your logs in case you start getting traffic that's inconsistent with your SRTP context, since it will print a log line for every incoming RTP packet, and there may be a lot of them. In the core we try to limit that by showing an error summary every couple of seconds or so to limit the damage. An alternative might be having some sort of toggle: the first time you get an SRTP error you print the error, and then you don't anymore until you get a proper error (SRTP decoding succeeded) that resets the toggle (but this could still cause log flooding if success/error alternate often).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have any example from core, where you summarize logs? I copied this part from streaming plugin.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found it in ice.c, I'll make the same - write to debug log on every packet and write summary periodically.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's there too then nevermind, I can look into this later on and fix it on both. I just noticed that a few of the nits I mentioned were actually in that code too (e.g., the &policy->rtp no parenthesis thing).

Copy link
Member

@lminiero lminiero Oct 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found it in ice.c, I'll make the same - write to debug log on every packet and write summary periodically.

No need, don't worry: I'll take care of that in both plugins myself so that it's done in a consistent way (I'll have to check if the same happens in SIP and NoSIP plugins too).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think about disabled, maybe we should add new flag for stream? like srtp_init_failed or similar and set it to true and add required checks in logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so, what will be suitable decision for this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that, to keep things simple, besides setting disabled to FALSE it may be enough to simply set the stream is_srtp to FALSE as well. This way, even if a stream disabled is forcibly set to TRUE later on, there will be no SRTP code involved that may find broken SRTP structures: of course audio/video would not work at all, because we'd be relaying encoded packets, but that's to be expected, since configuring that stream failed in the first place. I only want to ensure there's not a crash when that state changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, updated pr

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw it was is_rtp=FALSE by default, I set it to be more explicit.

continue;
}
bytes = buflen;
}
/* Prepare the RTP packet */
pkt.mindex = mindex;
pkt.video = (ps->type == JANUS_VIDEOROOM_MEDIA_VIDEO);
Expand Down
Loading