From 68624b34278ff4343706de25d83f50776ac274ea Mon Sep 17 00:00:00 2001 From: Boris Grozev Date: Mon, 21 Sep 2015 11:28:49 -0500 Subject: [PATCH] Adds config options to filter out TCP or UDP candidates (for the purpose of forcing one or the other in automated tests). --- modules/xmpp/JingleSessionPC.js | 52 +++++++++++++++- modules/xmpp/SDP.js | 103 +++++++++++++++++++------------- 2 files changed, 114 insertions(+), 41 deletions(-) diff --git a/modules/xmpp/JingleSessionPC.js b/modules/xmpp/JingleSessionPC.js index c5e18ae15..5b4b76f5f 100644 --- a/modules/xmpp/JingleSessionPC.js +++ b/modules/xmpp/JingleSessionPC.js @@ -46,6 +46,9 @@ function JingleSessionPC(me, sid, connection, service) { this.ssrcOwners = {}; this.ssrcVideoTypes = {}; + this.webrtcIceUdpDisable = !!this.service.options.webrtcIceUdpDisable; + this.webrtcIceTcpDisable = !!this.service.options.webrtcIceTcpDisable; + /** * The indicator which determines whether the (local) video has been muted * in response to a user command in contrast to an automatic decision made @@ -58,6 +61,7 @@ function JingleSessionPC(me, sid, connection, service) { // stable and the ice connection state is connected. this.modifySourcesQueue.pause(); } +//XXX this is badly broken... JingleSessionPC.prototype = JingleSession.prototype; JingleSessionPC.prototype.constructor = JingleSessionPC; @@ -97,6 +101,15 @@ JingleSessionPC.prototype.doInitialize = function () { this); this.peerconnection.onicecandidate = function (event) { + var protocol; + if (event && event.candidate) { + protocol = (typeof event.candidate.protocol === 'string') + ? event.candidate.protocol.toLowerCase() : ''; + if ((self.webrtcIceTcpDisable && protocol == 'tcp') || + (self.webrtcIceUdpDisable && protocol == 'udp')) { + return; + } + } self.sendIceCandidate(event.candidate); }; this.peerconnection.onaddstream = function (event) { @@ -227,6 +240,12 @@ JingleSessionPC.prototype.accept = function () { pranswer.sdp = pranswer.sdp.replace('a=inactive', 'a=sendrecv'); } var prsdp = new SDP(pranswer.sdp); + if (self.webrtcIceTcpDisable) { + prsdp.removeTcpCandidates = true; + } + if (self.webrtcIceUdpDisable) { + prsdp.removeUdpCandidates = true; + } var accept = $iq({to: this.peerjid, type: 'set'}) .c('jingle', {xmlns: 'urn:xmpp:jingle:1', @@ -335,6 +354,12 @@ JingleSessionPC.prototype.sendIceCandidate = function (candidate) { initiator: this.initiator, sid: this.sid}); this.localSDP = new SDP(this.peerconnection.localDescription.sdp); + if (self.webrtcIceTcpDisable) { + this.localSDP.removeTcpCandidates = true; + } + if (self.webrtcIceUdpDisable) { + this.localSDP.removeUdpCandidates = true; + } var sendJingle = function (ssrc) { if(!ssrc) ssrc = {}; @@ -533,6 +558,13 @@ JingleSessionPC.prototype.getSsrcOwner = function (ssrc) { JingleSessionPC.prototype.setRemoteDescription = function (elem, desctype) { //logger.log('setting remote description... ', desctype); this.remoteSDP = new SDP(''); + if (self.webrtcIceTcpDisable) { + this.remoteSDP.removeTcpCandidates = true; + } + if (self.webrtcIceUdpDisable) { + this.remoteSDP.removeUdpCandidates = true; + } + this.remoteSDP.fromJingle(elem); this.readSsrcInfo($(elem).find(">content")); if (this.peerconnection.remoteDescription) { @@ -575,6 +607,10 @@ JingleSessionPC.prototype.setRemoteDescription = function (elem, desctype) { ); }; +/** + * Adds remote ICE candidates to this Jingle session. + * @param elem An array of Jingle "content" elements? + */ JingleSessionPC.prototype.addIceCandidate = function (elem) { var self = this; if (this.peerconnection.signalingState == 'closed') { @@ -585,7 +621,7 @@ JingleSessionPC.prototype.addIceCandidate = function (elem) { // create a PRANSWER for setRemoteDescription if (!this.remoteSDP) { var cobbled = 'v=0\r\n' + - 'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME + 'o=- 1923518516 2 IN IP4 0.0.0.0\r\n' +// FIXME 's=-\r\n' + 't=0 0\r\n'; // first, take some things from the local description @@ -670,6 +706,14 @@ JingleSessionPC.prototype.addIceCandidate = function (elem) { // TODO: check ice-pwd and ice-ufrag? $(this).find('transport>candidate').each(function () { var line, candidate; + var protocol = this.getAttribute('protocol'); + protocol = + (typeof protocol === 'string') ? protocol.toLowerCase() : ''; + if ((self.webrtcIceTcpDisable && protocol == 'tcp') || + (self.webrtcIceUdpDisable && protocol == 'udp')) { + return; + } + line = SDPUtil.candidateFromJingle(this); candidate = new RTCIceCandidate({sdpMLineIndex: idx, sdpMid: name, @@ -723,6 +767,12 @@ JingleSessionPC.prototype.createdAnswer = function (sdp, provisional) { initiator: self.initiator, responder: self.responder, sid: self.sid }); + if (self.webrtcIceTcpDisable) { + self.localSDP.removeTcpCandidates = true; + } + if (self.webrtcIceUdpDisable) { + self.localSDP.removeUdpCandidates = true; + } self.localSDP.toJingle( accept, self.initiator == self.me ? 'initiator' : 'responder', diff --git a/modules/xmpp/SDP.js b/modules/xmpp/SDP.js index e5e70f052..636a6b6ef 100644 --- a/modules/xmpp/SDP.js +++ b/modules/xmpp/SDP.js @@ -5,6 +5,18 @@ var SDPUtil = require("./SDPUtil"); // SDP STUFF function SDP(sdp) { + /** + * Whether or not to remove TCP ice candidates when translating from/to jingle. + * @type {boolean} + */ + this.removeTcpCandidates = false; + + /** + * Whether or not to remove UDP ice candidates when translating from/to jingle. + * @type {boolean} + */ + this.removeUdpCandidates = false; + this.media = sdp.split('\r\nm='); for (var i = 1; i < this.media.length; i++) { this.media[i] = 'm=' + this.media[i]; @@ -15,6 +27,7 @@ function SDP(sdp) { this.session = this.media.shift() + '\r\n'; this.raw = this.session + this.media.join(''); } + /** * Returns map of MediaChannel mapped per channel idx. */ @@ -64,18 +77,16 @@ SDP.prototype.getMediaSsrcMap = function() { */ SDP.prototype.containsSSRC = function(ssrc) { var medias = this.getMediaSsrcMap(); - var contains = false; Object.keys(medias).forEach(function(mediaindex){ var media = medias[mediaindex]; //logger.log("Check", channel, ssrc); if(Object.keys(media.ssrcs).indexOf(ssrc) != -1){ - contains = true; + return true; } }); - return contains; + return false; }; - // remove iSAC and CN from SDP SDP.prototype.mangle = function () { var i, j, mline, lines, rtpmap, newdesc; @@ -129,8 +140,8 @@ SDP.prototype.removeMediaLines = function(mediaindex, prefix) { // add content's to a jingle element SDP.prototype.toJingle = function (elem, thecreator, ssrcs) { // logger.log("SSRC" + ssrcs["audio"] + " - " + ssrcs["video"]); - var i, j, k, mline, ssrc, rtpmap, tmp, line, lines; var self = this; + var i, j, k, mline, ssrc, rtpmap, tmp, lines; // new bundle plan if (SDPUtil.find_line(this.session, 'a=group:')) { lines = SDPUtil.find_lines(this.session, 'a=group:'); @@ -155,12 +166,11 @@ SDP.prototype.toJingle = function (elem, thecreator, ssrcs) { if (SDPUtil.find_line(this.media[i], 'a=ssrc:')) { ssrc = SDPUtil.find_line(this.media[i], 'a=ssrc:').substring(7).split(' ')[0]; // take the first } else { - if(ssrcs && ssrcs[mline.media]) - { + if(ssrcs && ssrcs[mline.media]) { ssrc = ssrcs[mline.media]; - } - else + } else { ssrc = false; + } } elem.c('content', {creator: thecreator, name: mline.media}); @@ -170,8 +180,7 @@ SDP.prototype.toJingle = function (elem, thecreator, ssrcs) { elem.attrs({ name: mid }); } - if (SDPUtil.find_line(this.media[i], 'a=rtpmap:').length) - { + if (SDPUtil.find_line(this.media[i], 'a=rtpmap:').length) { elem.c('description', {xmlns: 'urn:xmpp:jingle:apps:rtp:1', media: mline.media }); @@ -188,7 +197,7 @@ SDP.prototype.toJingle = function (elem, thecreator, ssrcs) { elem.c('parameter', tmp[k]).up(); } } - this.RtcpFbToJingle(i, elem, mline.fmt[j]); // XEP-0293 -- map a=rtcp-fb + this.rtcpFbToJingle(i, elem, mline.fmt[j]); // XEP-0293 -- map a=rtcp-fb elem.up(); } @@ -208,7 +217,7 @@ SDP.prototype.toJingle = function (elem, thecreator, ssrcs) { var ssrclines = SDPUtil.find_lines(this.media[i], 'a=ssrc:'); if(ssrclines.length > 0) { ssrclines.forEach(function (line) { - idx = line.indexOf(' '); + var idx = line.indexOf(' '); var linessrc = line.substr(0, idx).substr(7); if (linessrc != ssrc) { elem.up(); @@ -229,9 +238,7 @@ SDP.prototype.toJingle = function (elem, thecreator, ssrcs) { } elem.up(); }); - } - else - { + } else { elem.up(); elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' }); elem.c('parameter'); @@ -265,7 +272,7 @@ SDP.prototype.toJingle = function (elem, thecreator, ssrcs) { // XEP-0339 handle ssrc-group attributes var ssrc_group_lines = SDPUtil.find_lines(this.media[i], 'a=ssrc-group:'); ssrc_group_lines.forEach(function(line) { - idx = line.indexOf(' '); + var idx = line.indexOf(' '); var semantics = line.substr(0, idx).substr(13); var ssrcs = line.substr(14 + semantics.length).split(' '); if (ssrcs.length) { @@ -284,7 +291,7 @@ SDP.prototype.toJingle = function (elem, thecreator, ssrcs) { } // XEP-0293 -- map a=rtcp-fb:* - this.RtcpFbToJingle(i, elem, '*'); + this.rtcpFbToJingle(i, elem, '*'); // XEP-0294 if (SDPUtil.find_line(this.media[i], 'a=extmap:')) { @@ -318,7 +325,7 @@ SDP.prototype.toJingle = function (elem, thecreator, ssrcs) { } // map ice-ufrag/pwd, dtls fingerprint, candidates - this.TransportToJingle(i, elem); + this.transportToJingle(i, elem); if (SDPUtil.find_line(this.media[i], 'a=sendrecv', this.session)) { elem.attrs({senders: 'both'}); @@ -339,25 +346,24 @@ SDP.prototype.toJingle = function (elem, thecreator, ssrcs) { return elem; }; -SDP.prototype.TransportToJingle = function (mediaindex, elem) { - var i = mediaindex; - var tmp; +SDP.prototype.transportToJingle = function (mediaindex, elem) { + var tmp, sctpmap, sctpAttrs, fingerprints; var self = this; elem.c('transport'); // XEP-0343 DTLS/SCTP if (SDPUtil.find_line(this.media[mediaindex], 'a=sctpmap:').length) { - var sctpmap = SDPUtil.find_line( - this.media[i], 'a=sctpmap:', self.session); + sctpmap = SDPUtil.find_line( + this.media[mediaindex], 'a=sctpmap:', self.session); if (sctpmap) { - var sctpAttrs = SDPUtil.parse_sctpmap(sctpmap); + sctpAttrs = SDPUtil.parse_sctpmap(sctpmap); elem.c('sctpmap', { xmlns: 'urn:xmpp:jingle:transports:dtls-sctp:1', number: sctpAttrs[0], /* SCTP port */ - protocol: sctpAttrs[1], /* protocol */ + protocol: sctpAttrs[1] /* protocol */ }); // Optional stream count attribute if (sctpAttrs.length > 2) @@ -366,7 +372,7 @@ SDP.prototype.TransportToJingle = function (mediaindex, elem) { } } // XEP-0320 - var fingerprints = SDPUtil.find_lines(this.media[mediaindex], 'a=fingerprint:', this.session); + fingerprints = SDPUtil.find_lines(this.media[mediaindex], 'a=fingerprint:', this.session); fingerprints.forEach(function(line) { tmp = SDPUtil.parse_fingerprint(line); tmp.xmlns = 'urn:xmpp:jingle:apps:dtls:0'; @@ -387,14 +393,22 @@ SDP.prototype.TransportToJingle = function (mediaindex, elem) { if (SDPUtil.find_line(this.media[mediaindex], 'a=candidate:', this.session)) { // add any a=candidate lines var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=candidate:', this.session); lines.forEach(function (line) { - elem.c('candidate', SDPUtil.candidateToJingle(line)).up(); + var candidate = SDPUtil.candidateToJingle(line); + var protocol = (candidate && + typeof candidate.protocol === 'string') + ? candidate.protocol.toLowerCase() : ''; + if ((self.removeTcpCandidates && protocol === 'tcp') || + (self.removeUdpCandidates && protocol === 'udp')) { + return; + } + elem.c('candidate', candidate).up(); }); } } elem.up(); // end of transport } -SDP.prototype.RtcpFbToJingle = function (mediaindex, elem, payloadtype) { // XEP-0293 +SDP.prototype.rtcpFbToJingle = function (mediaindex, elem, payloadtype) { // XEP-0293 var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=rtcp-fb:' + payloadtype); lines.forEach(function (line) { var tmp = SDPUtil.parse_rtcpfb(line); @@ -411,7 +425,7 @@ SDP.prototype.RtcpFbToJingle = function (mediaindex, elem, payloadtype) { // XEP }); }; -SDP.prototype.RtcpFbFromJingle = function (elem, payloadtype) { // XEP-0293 +SDP.prototype.rtcpFbFromJingle = function (elem, payloadtype) { // XEP-0293 var media = ''; var tmp = elem.find('>rtcp-fb-trr-int[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]'); if (tmp.length) { @@ -438,7 +452,7 @@ SDP.prototype.RtcpFbFromJingle = function (elem, payloadtype) { // XEP-0293 SDP.prototype.fromJingle = function (jingle) { var self = this; this.raw = 'v=0\r\n' + - 'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME + 'o=- 1923518516 2 IN IP4 0.0.0.0\r\n' +// FIXME 's=-\r\n' + 't=0 0\r\n'; // http://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-04#section-8 @@ -494,14 +508,11 @@ SDP.prototype.jingle2media = function (content) { } else { tmp.proto = 'RTP/AVPF'; } - if (!sctp.length) - { + if (!sctp.length) { tmp.fmt = desc.find('payload-type').map( function () { return this.getAttribute('id'); }).get(); media += SDPUtil.build_mline(tmp) + '\r\n'; - } - else - { + } else { media += 'm=application 1 DTLS/SCTP ' + sctp.attr('number') + '\r\n'; media += 'a=sctpmap:' + sctp.attr('number') + ' ' + sctp.attr('protocol'); @@ -572,15 +583,19 @@ SDP.prototype.jingle2media = function (content) { media += SDPUtil.build_rtpmap(this) + '\r\n'; if ($(this).find('>parameter').length) { media += 'a=fmtp:' + this.getAttribute('id') + ' '; - media += $(this).find('parameter').map(function () { return (this.getAttribute('name') ? (this.getAttribute('name') + '=') : '') + this.getAttribute('value'); }).get().join('; '); + media += $(this).find('parameter').map(function () { + return (this.getAttribute('name') + ? (this.getAttribute('name') + '=') : '') + + this.getAttribute('value'); + }).get().join('; '); media += '\r\n'; } // xep-0293 - media += self.RtcpFbFromJingle($(this), this.getAttribute('id')); + media += self.rtcpFbFromJingle($(this), this.getAttribute('id')); }); // xep-0293 - media += self.RtcpFbFromJingle(desc, '*'); + media += self.rtcpFbFromJingle(desc, '*'); // xep-0294 tmp = desc.find('>rtp-hdrext[xmlns="urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"]'); @@ -589,11 +604,19 @@ SDP.prototype.jingle2media = function (content) { }); content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]>candidate').each(function () { + var protocol = this.getAttribute('protocol'); + protocol = (typeof protocol === 'string') ? protocol.toLowerCase(): ''; + + if ((self.removeTcpCandidates && protocol === 'tcp') || + (self.removeUdpCandidates && protocol === 'udp')) { + return; + } + media += SDPUtil.candidateFromJingle(this); }); // XEP-0339 handle ssrc-group attributes - tmp = content.find('description>ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() { + content.find('description>ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() { var semantics = this.getAttribute('semantics'); var ssrcs = $(this).find('>source').map(function() { return this.getAttribute('ssrc');