-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
New Janus plugin, NoSIP, for legacy interop without touching signalling #799
Conversation
plugins/janus_nosip.c
Outdated
#define SRTP_MASTER_SALT_LENGTH 14 | ||
#define SRTP_MASTER_LENGTH (SRTP_MASTER_KEY_LENGTH + SRTP_MASTER_SALT_LENGTH) | ||
static const char *janus_nosip_srtp_error[] = | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is also present in the SIP plugin, right? Maybe some srtputils.c module is in order?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that may be a good idea! I'll do that as part of this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did something in that spirit in #804
plugins/janus_nosip.c
Outdated
static void *janus_nosip_relay_thread(void *data); | ||
|
||
|
||
/* Random string helper (in case user doesn't provide opaque info) */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason why this is exposed? Shouldn't Janus always generate some random stuff?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is an info object users can provide in case they need some opaque identifier, but I'm questioning whether it's needed at all, considering you'll never bridge two sessions using this plugin directly... just in case it's needed, and the user doesn't provide any, then the random string kicks in.
plugins/janus_nosip.c
Outdated
} | ||
|
||
|
||
static void janus_nosip_detect_local_ip(char *buf, size_t buflen) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC this is also in some other parts of the code... how about moving it out so any plugin can use it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually we have that already, we recently merged a contribution that provides a whole new set of IP- and interface-related utilities. I've been planning to replace all the parts where we do the same thing with those utils, will do that soon and here too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did that in #803, when it's merged I'll integrate in here too
goto error; | ||
} | ||
gboolean offer = !strcasecmp(msg_sdp_type, "offer"); | ||
if(strstr(msg_sdp, "m=application")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
janus could filter those streams out and generate the answer rejecting the datachannel
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case of an answer you're right, while I guess we'll still need tor reject it for offers. Will do that.
Hey Lorenzo, this is great to see! ❤️ There is one case I'm not sure this covers (just had a quick look): getting the first offer as a "boring" SDP. My impression was that this relies on getting the first offer as a WebRTC SDP, is that the case? If so, please consider the opposite case, so incoming calls from legacy devices can work. Maybe some Again, thanks for doing this, I'm sure many people will benefit from it. Hint: someone could write an OpenSIPS / Kamailio module which uses Janus to "modernize" the SDP when sending an INVITE to a device known to be a WebRTC endpoint... and the other way around :-) |
Thanks for the review! We do handle the "boring" SDP offer, actually. The demo we presented tries to show both cases, in fact, as it creates two different handles, a caller and a callee. For the callee, you'll get a "process(boring offer)" as the first operation, which will trigger the WebRTC offer you can use for the setRemoteDescription. It's probably poorly documented or explained right now, I'll have to improve that. I'll add some examples of the API in action to the PR text so that it's clearer. It would be cool to have that OpenSIPS/Kamailio integration, I'll keep that in mind! I'll also have to try implementing a test page using JsSIP to showcase the signalling layer as well, although I'm not sure JsSIP allows you to provide your own SDP instead of handling JSEP itself: maybe I should ask @jmillan or @ibc 😄 |
cool , thank you. i want to see same idea on videoroom. any plan? |
What do you mean same idea in the VideoRoom? This PR is for 1-1 calls with legacy interop, not multiparty communication. |
VideoRoom: the application handle the signaling, janus just handle the media layer. |
You can already do that wrapping the Janus API and exposing your own API to users, we do that all the time. Here the requirement was different, as the SIP plugin uses SIP internally and we wanted to provide an alternative that didn't. |
plugins/janus_nosip.c
Outdated
if (!janus_is_ip_valid(item->value, &family)) { | ||
JANUS_LOG(LOG_WARN, "Invalid local IP specified: %s, guessing the default...\n", item->value); | ||
} else { | ||
/* Verify that we can actually bind to that address */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this code looks like a general purpose code, it can probably be refactored out into a network utils library.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did that in #803, when it's merged I'll integrate in here too
plugins/janus_nosip.c
Outdated
} | ||
} | ||
if (!local_ip_set) | ||
janus_nosip_detect_local_ip(local_ip, sizeof(local_ip)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in some places you surround single line ifs with {} and in some places you don't, better to be consistent (in my opinion it's better to surround even single lines)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack and agree! I always try to do that but there may be places where I still forget to do that.
} | ||
} | ||
janus_mutex_unlock(&sessions_mutex); | ||
g_usleep(500000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably better to use cond wait/signal instead of sleep.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the way all plugins behave right now, but it has already been dealt with in the referece counters branch, #403, where we don't have that anymore.
plugins/janus_nosip.c
Outdated
return; | ||
} | ||
/* Forward to our NoSIP peer */ | ||
if(video) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the audio and video branches are very similar, maybe there's a way to refactor into a helper method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, which probably applies to other plugins as well. I'll keep that in mind.
const char *request_text = json_string_value(request); | ||
json_t *result = NULL, *localjsep = NULL; | ||
|
||
if(!strcasecmp(request_text, "generate") || !strcasecmp(request_text, "process")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider splinting this into separate "handle" functions - handle_generate, handle_recording, etc.
maybe even go deeper and split those as well - handle_recording_start, handle_recording_stop, handle_generate_answer, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure how that would help? Only semantics?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it'll make the code much more readable. personally, i find it difficult to follow a 500 line function or/and 5 level of nesting, especially one that handles several completely different aspects of the BL. i'm guessing i'm not the only one :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it. In this case, my main point was trying to keep things compact and re-use as much code as possible, but I guess I could at least split the generate and process part in two branches, as that's what we do in almost all plugins anyway (it's rare we group-handle different methods).
plugins/janus_nosip.c
Outdated
if(video) | ||
session->media.video_srtp_suite_in = suite; | ||
else | ||
sessi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will creating a thread for each session scale?
lets say you want to have 10000 audio calls on a server? will 10000 threads work?
i'm guessing the the context switches will be a problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's how the SIP plugin already worked, and I just migrated (as in copied/pasted) it in here as well. I guess we might indeed have a single thread that polls on all the file descriptors for all sessions and handles all incoming traffic accordingly, but that would be a non trivial refactoring as it is. Besides, there's code (still unused) to handle session updates that could keep the thread busy, and I'm afraid this would impact other sessions.
Anyway, your point on threads in general is valid, and applies to the Janus core as well.
why not taking this one step farther and eliminating SDP for signaling altogether? just provide and API to set ip/port and SRTP credentials. |
But that would require the application to then craft its own SDP if that's what it needs. I believe it's easier to just do it that way: if one only needs SRTP credentials and IP/ports it can extract them from the SDP, and preparing an SDP from raw info (e.g., from JavaScript) to pass to the plugin would be easy enough. Besides, it's not just IP/ports or SRTP credentials you have to worry about: there's codecs, payload types, formats, and a whole bunch of stuff you'd need to serialize someway... I really don't want to get into that rat hole 😉 Thanks for the review! |
Not sure if I understand the doubt. I mean, JsSIP generates its SDP offers/answers by calling the PeerConnection API, and JsSIP does accept remote SDPs as usual and then calls Said that... which is the question? :) |
@ibc I wanted to understand whether you can use JsSIP as a SIP stack only, and not let it handle JSEP/media as well. Normally, as you pointed out, JsSIP would generate its own SDPs, but with this plugin I'd like to check if I can provide, let's say, my own SDP (the barebone SDP stuff), use JsSIP as a stack to contact a WebSockets SIP server, and handle media negotiation with Janus as a consequence. |
Ahhh ok ok, so you mean using JsSIP as a Node.js app in server side for pure SIP purposes, right? |
Sure, that too, but even in the browser if you want: think wanting to call (or be called by) somebody you know doesn't support WebRTC from the browser, you'll want an SDP they can digest, and something in the middle to fix the media gap. That's why I thought of JsSIP as a way to implement a simple proof of concept that involves signalling as well, instead of the ugly self-contained demo I have now 😄 |
May you please draw/describe a single flow/architecture with all the pieces (JsSIP web app, Janus, JsSIP-in-server, and legacy SIP phone) so I can completely understand the picture? What I don't understand is: regarding JsSIP in client side (WebRTC web application), should it generate/consume non WebRTC valid SDPs? |
Yeah, I plan to sketch this a little better, as that's indeed lacking in the PR at the moment. It is indeed a bit confusing to grasp!
The WebRTC application would keep on generating/consuming WebRTC SDPs: what changes here is that you rely on the help of the NoSIP plugin to get a stripped down SDP you use in your SIP/SDP offer/answer instead. When you get an SIP/SDP offer/answer from a legacy peer, you pass it to the plugin, and it will turn it into a WebRTC SDP you can consume. The end result is that you exchange WebRTC DTLS/ICE/SRTP with Janus, and Janus (via the NoSIP plugin) exchanges media with the legacy peer using plain RTP (or SDES-SRTP). Basically a media-only gateway. |
I just need to know who you is in the above sentence :)
Probably I should read much more in order to understand how such a communication (between you and the plugin) is. Does the plugin provides a REST/HTTP API so web clients can post a WebRTC/legacy SDP and receive a legacy/WebRTC SDP? Am I getting crazy? |
Yes, it IS confusing! 😄 Let me try to clarify this a bit: let's assume Alice uses JsSIP in her browser, and Bob uses an old SIP softphone that only talks RTP. If Alice wants to call Bob (and somehow she knows Bob still lives in 2005), something like this can happen:
Hope this is clearer! For Alice as a callee the approach would be pretty much the same: she'd get an old SDP, which she can turn in a WebRTC offer from Janus via the NoSIP plugin; her WebRTC answer (which Janus processes to create a PeerConnection with Alice) is turned into an old SDP Alice can send in her 200 OK to Bob. PS: edited to add a 12. point and make this even clearer. |
Hyper clear now! OK, so here is the problem: Currently JsSIP emits some events before sending an INVITE (the app can mangle the SDP in there) but such an event must be synchronous, this is, once the JS listener/callbacks returns the INVITE is sent, so there is no a chance for asynchronous stuff (i.e. making a REST API to NoSIP and getting the boring SDP offer). Same happens when receiving an INVITE or a SIP 200 response. Indeed, the way to go must be removing the WebRTC/PeerConnection dependency from JsSIP and make it a pure SIP signaling library. So, planning it: versatica/JsSIP#427 :) |
Nice, thanks! I'll definitely follow this to play with it when something's available 👍 |
This has been around long enough, and I don't plan to add new features in the short term, so we might just as well merge it in case it's of use to anybody as it is. We can always improve it along the way. |
This is a plugin that has been cooking (at least in mind) for a while. Specifically, people interested in interoperability with the SIP world now have an alternative to the SIP plugin we already provided. More importantly, it's good ol' @saghul's BoringSDP idea brought to life (so kudos to him for suggesting the feature in the first place, at the time), but with a different name 😄
As the name suggests, this new plugin, called NoSIP, doesn't care about signalling at all, and only takes care of bridging the media between two peers as the SIP plugin already did. This means that signalling is entirely up to you: this plugin only deals with taking care of the media level interoperability for you. Of course, you still need to communicate with Janus and this plugin to establish media connectivity, but how you transport the SDPs around between peers is up to you: SIP, IAX, XMPP, pigeon, etc., we don't care.
Copying from the plugin doc, the typical flow is the following:
The same kind of flow also applies if the WebRTC peer using the NoSIP plugin is the callee: in that case, you first turn the barebone SDP from the peer in a WebRTC offer from Janus, and then you turn your WebRTC answer into something the legacy peer can digest.
I've tested this briefly in a newly crafted demo, and it seems to be doing its job, at least for the very basic call setup. The
nosiptest.html
demo doesn't involve any signalling, but simply creates two handles attached to the plugin, one to be the caller and another the callee: then signalling is "simulated", so that caller and callee get to exchange media through the plain RTP/RTCP relay implemented by the plugin. The final effect is obviously talking to yourself, but the principle is that if you see something, then it's working.It's of course still not perfect and can be improved, but it's an important stepping stone. If this is something you care about, please test this and provide feedback.