diff --git a/daemon/media_socket.c b/daemon/media_socket.c index 09c88d1cad..b1b5c968ac 100644 --- a/daemon/media_socket.c +++ b/daemon/media_socket.c @@ -1484,11 +1484,18 @@ static const char *kernelize_one(struct rtpengine_target_info *reti, GQueue *out ZERO(stream->kernel_stats_in); - if (proto_is_rtp(media->protocol) && sinks && sinks->length) { + if (proto_is_rtp(media->protocol)) { + reti->rtp = 1; + if (!media->monologue->transcoding) { + if (media->protocol->avpf) + reti->rtcp_fb_fw = 1; + } + } + + if (reti->rtp && sinks && sinks->length) { GList *l; struct rtp_stats *rs; - reti->rtp = 1; // this code is execute only once: list therefore must be empty assert(*payload_types == NULL); *payload_types = g_hash_table_get_values(stream->rtp_stats); diff --git a/daemon/rtcp.c b/daemon/rtcp.c index 62d8bf7666..ff9ed23368 100644 --- a/daemon/rtcp.c +++ b/daemon/rtcp.c @@ -897,7 +897,7 @@ int rtcp_avp2savp(str *s, struct crypto_context *c, struct ssrc_ctx *ssrc_ctx) { int rtcp_savp2avp(str *s, struct crypto_context *c, struct ssrc_ctx *ssrc_ctx) { struct rtcp_packet *rtcp; str payload, to_auth, to_decrypt, auth_tag; - uint32_t idx, *idx_p; + uint32_t idx; char hmac[20]; const char *err; @@ -923,8 +923,8 @@ int rtcp_savp2avp(str *s, struct crypto_context *c, struct ssrc_ctx *ssrc_ctx) { if (to_decrypt.len < sizeof(idx)) goto error; to_decrypt.len -= sizeof(idx); - idx_p = (void *) to_decrypt.s + to_decrypt.len; - idx = ntohl(*idx_p); + memcpy(&idx, to_decrypt.s + to_decrypt.len, sizeof(idx)); + idx = ntohl(idx); crypto_debug_printf(", idx %" PRIu32, idx); diff --git a/kernel-module/xt_RTPENGINE.c b/kernel-module/xt_RTPENGINE.c index 58eedc91b1..3201462e83 100644 --- a/kernel-module/xt_RTPENGINE.c +++ b/kernel-module/xt_RTPENGINE.c @@ -1758,6 +1758,8 @@ static int proc_list_show(struct seq_file *f, void *v) { seq_printf(f, " SSRC-tracking"); if (g->target.do_intercept) seq_printf(f, " intercept"); + if (g->target.rtcp_fb_fw) + seq_printf(f, " forward-RTCP-FB"); seq_printf(f, "\n"); for (i = 0; i < g->target.num_destinations; i++) { @@ -3595,7 +3597,7 @@ static int stream_packet(struct rtpengine_table *t, const struct rtpengine_packe if (!len) /* can't have empty packets */ return -EINVAL; - DBG("received %u bytes of data from userspace\n", len); + DBG("received %zu bytes of data from userspace\n", len); err = -ENOENT; stream = get_stream_lock(NULL, info->stream_idx); @@ -3643,6 +3645,20 @@ static int target_find_ssrc(struct rtpengine_target *g, uint32_t ssrc) { return -2; } +static void parse_rtcp(struct rtp_parsed *rtp, struct sk_buff *skb) { + rtp->ok = 0; + rtp->rtcp = 0; + + if (skb->len < sizeof(struct rtcp_header)) + return; + + rtp->rtcp_header = (void *) skb->data; + rtp->header_len = sizeof(struct rtcp_header); + rtp->payload = skb->data + sizeof(struct rtcp_header); + rtp->payload_len = skb->len - sizeof(struct rtcp_header); + rtp->rtcp = 1; +} + static int table_send_rtcp(struct rtpengine_table *t, const struct rtpengine_send_packet_info *info, size_t len) { struct rtpengine_target *g; @@ -3690,14 +3706,8 @@ static int table_send_rtcp(struct rtpengine_table *t, const struct rtpengine_sen skb_reserve(skb, MAX_HEADER); memcpy(skb_put(skb, len), data, len); - // rudimentarily set up header - memset(&rtp, 0, sizeof(rtp)); - if (len >= sizeof(struct rtcp_header)) { - rtp.rtcp_header = (void *) skb->data; - rtp.header_len = sizeof(struct rtcp_header); - rtp.payload = skb->data + sizeof(struct rtcp_header); - rtp.payload_len = skb->len - sizeof(struct rtcp_header); - rtp.rtcp = 1; + parse_rtcp(&rtp, skb); + if (rtp.rtcp) { ssrc_idx = target_find_ssrc(g, rtp.rtcp_header->ssrc); if (ssrc_idx == -2) { kfree_skb(skb); @@ -4494,6 +4504,61 @@ static int srtp_auth_validate(struct re_crypto_context *c, ok: return 0; } +static int srtcp_auth_validate(struct re_crypto_context *c, + struct rtpengine_srtp *s, struct rtp_parsed *r, + uint64_t *pkt_idx_p) +{ + uint32_t idx; + unsigned char *auth_tag = NULL; + unsigned char hmac[20]; + + if (!c->cipher->decrypt_rtcp) + return 0; + + if (s->rtcp_auth_tag_len) { + // we have an auth tag to verify + if (s->hmac == REH_NULL) + return -1; + if (!c->hmac) + return -1; + if (!c->shash) + return -1; + + // extract auth tag + if (r->payload_len < s->rtcp_auth_tag_len) + return -1; + auth_tag = r->payload + r->payload_len - s->rtcp_auth_tag_len; + r->payload_len -= s->rtcp_auth_tag_len; + } + + // skip MKI + if (r->payload_len < s->mki_len) + return -1; + r->payload_len -= s->mki_len; + + // extract index + if (r->payload_len < sizeof(idx)) + return -1; + memcpy(&idx, r->payload + r->payload_len - sizeof(idx), sizeof(idx)); + idx = ntohl(idx); + + if (auth_tag) { + if (srtcp_hash(hmac, c, s, r, idx)) + return -1; + if (memcmp(auth_tag, hmac, s->rtcp_auth_tag_len)) + return -1; + } + + r->payload_len -= sizeof(idx); + + if ((idx & 0x80000000ULL)) { + *pkt_idx_p = idx & ~0x80000000ULL; + return 1; // decrypt + } + + *pkt_idx_p = idx; + return 0; +} /* XXX shared code */ @@ -4898,6 +4963,37 @@ static inline int is_muxed_rtcp(struct sk_buff *skb) { return 0; return 1; } +static inline int is_rtcp_fb_packet(struct sk_buff *skb) { + unsigned char m_pt; + size_t left = skb->len; + size_t offset = 0; + unsigned int packets = 0; + uint16_t len; + + while (1) { + if (left < 8) // minimum RTCP size + return 0; + m_pt = skb->data[offset + 1]; + // only RTPFB and PSFB + if (m_pt != 205 && m_pt != 206) + return 0; + + // length check + len = (((unsigned char) skb->data[offset + 2]) << 8) + | ((unsigned char) skb->data[offset + 3]); + len++; + len <<= 2; + if (len > left) // invalid + return 0; + + left -= len; + offset += len; + + if (packets++ >= 8) // limit number of compound packets + return 0; + } + return 1; +} static inline int is_stun(struct rtpengine_target *g, unsigned int datalen, unsigned char *skb_data) { uint32_t *u32; if (!g->target.stun) @@ -5195,6 +5291,7 @@ static unsigned int rtpengine46(struct sk_buff *skb, struct rtpengine_table *t, unsigned long flags; unsigned int i; unsigned int start_idx, end_idx; + int is_rtcp; #if (RE_HAS_MEASUREDELAY) uint64_t starttime, endtime, delay; @@ -5228,9 +5325,9 @@ static unsigned int rtpengine46(struct sk_buff *skb, struct rtpengine_table *t, } _r_unlock(&g->outputs_lock, flags); - DBG("target found, src "MIPF" -> dst "MIPF"\n", MIPP(g->target.src_addr), MIPP(g->target.dst_addr)); - DBG("target decrypt hmac and cipher are %s and %s", g->decrypt.hmac->name, - g->decrypt.cipher->name); + DBG("target found, local " MIPF "\n", MIPP(g->target.local)); + DBG("target decrypt RTP hmac and cipher are %s and %s", g->decrypt_rtp.hmac->name, + g->decrypt_rtp.cipher->name); if (is_stun(g, datalen, skb->data)) goto out; @@ -5259,19 +5356,33 @@ static unsigned int rtpengine46(struct sk_buff *skb, struct rtpengine_table *t, // RTP processing rtp.ok = 0; + rtp.rtcp = 0; + is_rtcp = 0; if (g->target.rtp) { if (g->target.rtcp) { if (g->target.rtcp_mux) { if (is_muxed_rtcp(skb)) - goto out; // pass to userspace + is_rtcp = 1; } else - goto out; // RTCP only + is_rtcp = 1; + } + + if (!is_rtcp) { + parse_rtp(&rtp, skb); + if (!rtp.ok && g->target.rtp_only) + goto out; // pass to userspace } + else { + if (g->target.rtcp_fb_fw && is_rtcp_fb_packet(skb)) + ; // forward and then drop + else + goto out; // just pass to userspace - parse_rtp(&rtp, skb); - if (!rtp.ok && g->target.rtp_only) - goto out; // pass to userspace + parse_rtcp(&rtp, skb); + if (!rtp.rtcp) + goto out; + } } if (rtp.ok) { // RTP ok @@ -5312,6 +5423,20 @@ static unsigned int rtpengine46(struct sk_buff *skb, struct rtpengine_table *t, rtp.payload[12], rtp.payload[13], rtp.payload[14], rtp.payload[15], rtp.payload[16], rtp.payload[17], rtp.payload[18], rtp.payload[19]); } + else if (is_rtcp && rtp.rtcp) { + pkt_idx = 0; + err = srtcp_auth_validate(&g->decrypt_rtcp, &g->target.decrypt, &rtp, &pkt_idx); + errstr = "SRTCP authentication tag mismatch"; + if (err == -1) + goto out_error; + if (err == 1) { + // decrypt + errstr = "SRTCP decryption failed"; + if (srtcp_decrypt(&g->decrypt_rtcp, &g->target.decrypt, &rtp, pkt_idx)) + goto out_error; + } + skb_trim(skb, rtp.header_len + rtp.payload_len); + } if (g->target.do_intercept) { DBG("do_intercept is set\n"); @@ -5338,6 +5463,7 @@ static unsigned int rtpengine46(struct sk_buff *skb, struct rtpengine_table *t, for (i = start_idx; i < end_idx; i++) { struct rtpengine_output *o = &g->outputs[i]; + DBG("output src " MIPF " -> dst " MIPF "\n", MIPP(o->output.src_addr), MIPP(o->output.dst_addr)); // do we need a copy? if (i == (end_idx - 1)) { skb2 = skb; // last iteration - use original @@ -5356,7 +5482,8 @@ static unsigned int rtpengine46(struct sk_buff *skb, struct rtpengine_table *t, } // adjust RTP pointers rtp2 = rtp; - rtp2.rtp_header = (void *) (((char *) rtp2.rtp_header) + offset); + if (rtp.rtp_header) + rtp2.rtp_header = (void *) (((char *) rtp2.rtp_header) + offset); rtp2.payload = (void *) (((char *) rtp2.payload) + offset); datalen_out = skb2->len; diff --git a/kernel-module/xt_RTPENGINE.h b/kernel-module/xt_RTPENGINE.h index 01e1eb6c7d..2decbcc5b8 100644 --- a/kernel-module/xt_RTPENGINE.h +++ b/kernel-module/xt_RTPENGINE.h @@ -125,6 +125,7 @@ struct rtpengine_target_info { rtp_only:1, track_ssrc:1, rtcp:1, + rtcp_fb_fw:1, do_intercept:1, pt_filter:1, non_forwarding:1, // empty src/dst addr diff --git a/tests/kernel-module-test.c b/tests/kernel-module-test.c index 83e305103a..f4c4381e74 100644 --- a/tests/kernel-module-test.c +++ b/tests/kernel-module-test.c @@ -14,7 +14,7 @@ #include "xt_RTPENGINE.h" #define NUM_SOCKETS 41 -#define PORT_BASE 37526 +#define PORT_BASE 36000 #define LOCALHOST htonl(0x7f000001) #define LEN(x) (sizeof(x)-1) @@ -42,8 +42,8 @@ .sin_port = htons(PORT_BASE + port), \ .sin_addr = { LOCALHOST }, \ }; \ - ret = sendto(fds[sock], data, LEN(data), 0, (struct sockaddr *) &sin, sizeof(sin)); \ - printf("ret = %i\n", ret); \ + ssize_t ret = sendto(fds[sock], data, LEN(data), 0, (struct sockaddr *) &sin, sizeof(sin)); \ + printf("ret = %zi\n", ret); \ assert(ret == LEN(data)); \ } #define EXP(sock, data) \ @@ -51,9 +51,9 @@ { \ char buf[65535]; \ alarm(1); \ - ret = recv(fds[sock], buf, sizeof(buf), 0); \ + ssize_t ret = recv(fds[sock], buf, sizeof(buf), 0); \ alarm(0); \ - printf("ret = %i\n", ret); \ + printf("ret = %zi, expect = %zi\n", ret, LEN(data)); \ assert(ret == LEN(data)); \ buf[ret] = '\0'; \ printf("data ="); \ @@ -69,9 +69,9 @@ socklen_t sinlen = sizeof(sin); \ char buf[65535]; \ alarm(1); \ - ret = recvfrom(fds[sock], buf, sizeof(buf), 0, (struct sockaddr *) &sin, &sinlen); \ + ssize_t ret = recvfrom(fds[sock], buf, sizeof(buf), 0, (struct sockaddr *) &sin, &sinlen); \ alarm(0); \ - printf("ret = %i\n", ret); \ + printf("ret = %zi, expect = %zi\n", ret, LEN(data)); \ assert(ret == LEN(data)); \ buf[ret] = '\0'; \ printf("data ="); \ @@ -108,7 +108,7 @@ int main(void) { .sin_port = htons(PORT_BASE + i), .sin_addr = { LOCALHOST }, }; - ret = bind(fds[i], (struct sockaddr *) &sin, sizeof(sin)); + int ret = bind(fds[i], (struct sockaddr *) &sin, sizeof(sin)); assert(ret == 0); } @@ -1378,5 +1378,162 @@ int main(void) { ); EXPF(32, "\x81\xc8\x00\x0c\x87\x65\x43\x21\xa8\xf0\x49\x6a\x19\x93\x12\xb7\x15\x08\xaf\xea\x9f\xb8\x07\x51\x0b\x21\xfc\xd2\xcd\x34\x80\x9b\x17\x3d\xfe\xf6\x34\x74\x09\x33\xdb\x77\xb8\xfc\x24\x27\x52\xdf\x47\xe0\xe2\x42\x51\x8b\x99\x56\x3f\x86\x3b\x4f\xe1\x1a\x2e\xc1\xaa\x19\x3f\xae\xae\x0e\xd8\x6d\x0d\xd1\x72\xa4\x80\x00\x00\x01\x86\xe0\x95\x0b\x40\x1f\xa0\x75\x18\x71", 31); + // SRTCP decryption, AES-CM-128 + MSG(REMG_ADD_TARGET, target, + .local = { + .family = AF_INET, + .u = { + .ipv4 = LOCALHOST, + }, + .port = PORT_BASE + 33, + }, + .expected_src = { + .family = AF_INET, + .u = { + .ipv4 = LOCALHOST, + }, + .port = 5555, + }, + .decrypt = { + .cipher = REC_AES_CM_128, + .hmac = REH_HMAC_SHA1, + .master_key_len = 16, + .master_salt_len = 14, + .session_key_len = 16, + .session_salt_len = 14, + .rtp_auth_tag_len = 10, + .rtcp_auth_tag_len = 10, + .master_key = {0xe1, 0xf9, 0x7a, 0x0d, 0x3e, 0x01, 0x8b, 0xe0, + 0xd6, 0x4f, 0xa3, 0x2c, 0x06, 0xde, 0x41, 0x39}, + .master_salt = {0x0e, 0xc6, 0x75, 0xad, 0x49, 0x8a, 0xfe, 0xeb, + 0xb6, 0x96, 0x0b, 0x3a, 0xab, 0xe6}, + }, + .src_mismatch = MSM_IGNORE, + .num_destinations = 1, + .rtp = 1, + .rtcp = 1, + .rtcp_fw = 1, + + .num_payload_types = 1, + .payload_types = { + { + .pt_num = 0xf, + .clock_rate = 8000, + }, + }, + ); + MSG(REMG_ADD_DESTINATION, destination, + .local = { + .family = AF_INET, + .u = { + .ipv4 = LOCALHOST, + }, + .port = PORT_BASE + 33, + }, + .num = 0, + .output = { + .src_addr = { + .family = AF_INET, + .u = { + .ipv4 = LOCALHOST, + }, + .port = PORT_BASE + 34, + }, + .dst_addr = { + .family = AF_INET, + .u = { + .ipv4 = LOCALHOST, + }, + .port = PORT_BASE + 35, + }, + .encrypt = { + .cipher = REC_NULL, + .hmac = REH_NULL, + }, + }, + ); + + SND(40, 33, "\x81\xc8\x00\x0c\x12\x34\x56\x78\x09\x11\x4b\x0c\x97\xba\x5c\x20\x2c\x0c\x52\x0c\xea\x0c\xe6\x5b\x8f\x66\xad\x0d\x0b\x84\xb7\x9e\x0c\x6b\x80\xbc\xb1\x94\xb3\x1a\x0a\x1a\x30\x18\x06\xc7\x14\x91\x5a\xb1\xae\xd1\xee\x23\x64\x54\x82\x65\x27\x9c\x3e\xcd\xdc\x87\xff\x68\x84\x7c\x17\x6d\xd7\xc8\xae\xb6\x4b\x73\x80\x00\x00\x00\x00\x0b\x24\x46\x81\xdd\x28\x6b\xe8\xac"); + EXPF(35, "\x81\xc8\x00\x0c\x12\x34\x56\x78xxxxxxxx\x00\x00\x26\xc0\x00\x00\x00\x25\x00\x00\x18\xdc\x00\x00\x12\x34\x06\x00\x00\x01\x00\x00\x04\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81\xca\x00\x05\x00\x00\x16\x1c\x01\x0cqwertyuiopas\x00\x00", 34); + + // SRTCP decryption, AES-GCM + MSG(REMG_ADD_TARGET, target, + .local = { + .family = AF_INET, + .u = { + .ipv4 = LOCALHOST, + }, + .port = PORT_BASE + 36, + }, + .expected_src = { + .family = AF_INET, + .u = { + .ipv4 = LOCALHOST, + }, + .port = 5555, + }, + .decrypt = { + .cipher = REC_AEAD_AES_GCM_256, + .hmac = REH_NULL, + .master_key_len = 32, + .master_salt_len = 12, + .session_key_len = 32, + .session_salt_len = 12, + .rtp_auth_tag_len = 0, + .master_key = {0x81, 0xa4, 0xe5, 0x86, 0x21, 0x62, 0x6c, 0x57, + 0x9c, 0x5b, 0x8b, 0x2f, 0x1e, 0x27, 0x6a, 0x69, + 0x3c, 0xf2, 0xd5, 0xf6, 0xd0, 0xbc, 0x9a, 0x53, + 0x7c, 0x71, 0xdf, 0x22, 0x95, 0x38, 0x4c, 0xb2}, + .master_salt = {0x33, 0xaa, 0xf1, 0x5f, 0x42, 0x81, 0x10, 0x58, + 0xb0, 0x03, 0x8c, 0x0c}, + }, + .src_mismatch = MSM_IGNORE, + .num_destinations = 1, + .rtp = 1, + .rtcp = 1, + .rtcp_fw = 1, + + .num_payload_types = 1, + .payload_types = { + { + .pt_num = 0xf, + .clock_rate = 8000, + }, + }, + ); + MSG(REMG_ADD_DESTINATION, destination, + .local = { + .family = AF_INET, + .u = { + .ipv4 = LOCALHOST, + }, + .port = PORT_BASE + 36, + }, + .num = 0, + .output = { + .src_addr = { + .family = AF_INET, + .u = { + .ipv4 = LOCALHOST, + }, + .port = PORT_BASE + 37, + }, + .dst_addr = { + .family = AF_INET, + .u = { + .ipv4 = LOCALHOST, + }, + .port = PORT_BASE + 38, + }, + .encrypt = { + .cipher = REC_NULL, + .hmac = REH_NULL, + }, + }, + ); + + SND(40, 36, "\x81\xc8\x00\x0c\x00\x00\x16\x1c\x96\xe5\xb7\xf4\x34\x2e\xed\xfa\x59\xed\x4d\x77\x30\x96\x2a\xb3\x62\x5b\xe9\x4d\x06\xfe\x70\xb2\x9a\x4b\xb9\x27\x14\x78\x64\x15\x0c\xe6\xe6\x0d\xcc\x2f\x7f\x5f\x21\xf3\xfa\x03\x6f\xd2\xc1\xb5\x9c\x12\x76\x1b\x68\xe8\x12\xc8\xa7\x6d\x79\xce\x13\x14\xce\x33\x36\x58\x98\x6f\xe7\x95\xb5\x35\x0c\x25\x92\xbe\x2e\xb3\xb6\x2d\x51\x38\xfb\x09\x80\x00\x00\x00"); + EXPF(38, "\x81\xc8\x00\x0c\x00\x00\x16\x1cxxxxxxxx\x00\x00\x26\xc0\x00\x00\x00\x25\x00\x00\x18\xdc\x00\x00\x12\x34\x06\x00\x00\x01\x00\x00\x04\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81\xca\x00\x05\x00\x00\x16\x1c\x01\x0cqwertyuiopas\x00\x00", 37); + return 0; }