-
Notifications
You must be signed in to change notification settings - Fork 2.5k
/
Copy pathjanus_sip.c
7715 lines (7525 loc) · 323 KB
/
janus_sip.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*! \file janus_sip.c
* \author Lorenzo Miniero <[email protected]>
* \copyright GNU General Public License v3
* \brief Janus SIP plugin
* \details Check the \ref sip for more details.
*
* \ingroup plugins
* \ref plugins
*
* \page sip SIP plugin documentation
* This is a simple SIP plugin for Janus, allowing WebRTC peers
* to register at a SIP server (e.g., Asterisk) and call SIP user agents
* through a Janus instance. Specifically, when attaching to the plugin peers
* are requested to provide their SIP server credentials, i.e., the address
* of the SIP server and their username/secret. This results in the plugin
* registering at the SIP server and acting as a SIP client on behalf of
* the web peer. Most of the SIP states and lifetime are masked by the plugin,
* and only the relevant events (e.g., INVITEs and BYEs) and functionality
* (call, hangup) are made available to the web peer: peers can call
* extensions at the SIP server or wait for incoming INVITEs, and during
* a call they can send DTMF tones. Calls can do plain RTP or SDES-SRTP.
*
* The concept behind this plugin is to allow different web pages associated
* to the same peer, and hence the same SIP user, to attach to the plugin
* at the same time and yet just do a SIP REGISTER once. The same should
* apply for calls: while an incoming call would be notified to all the
* web UIs associated to the peer, only one would be able to pick up and
* answer, in pretty much the same way as SIP forking works but without the
* need to fork in the same place. This specific functionality, though, has
* not been implemented as of yet.
*
* \section sipapi SIP Plugin API
*
* All requests you can send in the SIP Plugin API are asynchronous,
* which means all responses (successes and errors) will be delivered
* as events with the same transaction.
*
* The supported requests are \c register , \c unregister , \c call ,
* \progress , \c accept , \c decline , \c info , \c message , \c dtmf_info ,
* \c subscribe , \c unsubscribe , \c transfer , \c recording ,
* \c hold , \c unhold , \c update and \c hangup . \c register can be used,
* as the name suggests, to register a username at a SIP registrar to
* call and be called, while \c unregister unregisters it; \c call is used
* to send an INVITE to a different SIP URI through the plugin; in case one
* is invited instead of inviting, \c progress, \c accept and \c decline
* requests may be used. \c progress request is optional, and it is used to
* send 183 Session Progress response back to the caller, while
* \c accept and \c decline are used to accept or reject the call respectively;
* \c transfer takes care of attended and blind transfers (see \ref siptr for
* more details); \c hold and \c unhold can be used respectively to put a
* call on-hold and to resume it; \c info allows you to send a generic
* SIP INFO request, while \c dtmf_info is focused on using INFO for DTMF
* instead; \c message is the method you use to send a SIP message
* to the other peer; \c subscribe and \c unsubscribe are used to deal
* with SIP events, i.e., to send SUBSCRIBE requests that will result in
* NOTIFY asynchronous events; \c recording is used, instead, to record the
* conversation to one or more .mjr files (depending on the direction you
* want to record); \c update allows you to update an existing session
* (e.g., to do a renegotiation or force an ICE restart); finally, \c hangup
* can be used to terminate the communication at any time, either to
* hangup (BYE) an ongoing call or to cancel/decline (CANCEL/BYE) a call
* that hasn't started yet.
*
* No matter the request, an error response or event is always formatted
* like this:
*
\verbatim
{
"sip" : "event",
"error_code" : <numeric ID, check Macros below>,
"error" : "<error description as a string>"
}
\endverbatim
*
* Notice that the error syntax above refers to the plugin API messaging,
* and not SIP error codes obtained in response to SIP requests, which
* are notified using a different syntax:
*
\verbatim
{
"sip" : "event",
"result" : {
"event" : "<name of the error event>",
"code" : <SIP error code>,
"reason" : "<SIP error reason>",
"reason_header" : "<Reason header text; optional>",
"reason_header_protocol" : "<Reason header protocol; optional>",
"reason_header_cause" : "<Reason header cause code; optional>"
}
}
\endverbatim
*
* Coming to the available requests, you send a SIP REGISTER using the
* \c register request. To be more precise, a \c register request MAY result
* in a SIP REGISTER, as this method actually provides ways to start using
* a SIP account with no need for a registration. It is the case, for instance,
* of the so-called \c guest registrations: if you register as a \c guest ,
* it means you'll use the provided SIP URI in your \c From headers for calls,
* but you will actually not send a SIP REGISTER; this is especially useful
* for outgoing calls to services that don't require registration (e.g., IVR
* systems, or conference bridges), but also means you won't be able to
* receive calls unless peers know what your private SIP address is. A SIP
* REGISTER isn't sent also when registering as a \c helper : as we'll
* explain later, \c helper sessions are sessions only meant to facilitate
* the setup of \ref sipmc.
*
* That said, a \c register request has to be formatted as follows:
*
\verbatim
{
"request" : "register",
"type" : "<if guest or helper, no SIP REGISTER is actually sent; optional>",
"send_register" : <true|false; if false, no SIP REGISTER is actually sent; optional>,
"force_udp" : <true|false; if true, forces UDP for the SIP messaging; optional>,
"force_tcp" : <true|false; if true, forces TCP for the SIP messaging; optional>,
"sips" : <true|false; if true, configures a SIPS URI too when registering; optional>,
"rfc2543_cancel" : <true|false; if true, configures sip client to CANCEL pending INVITEs without having received a provisional response first; optional>,
"username" : "<SIP URI to register; mandatory>",
"secret" : "<password to use to register; optional>",
"ha1_secret" : "<prehashed password to use to register; optional>",
"authuser" : "<username to use to authenticate (overrides the one in the SIP URI); optional>",
"display_name" : "<display name to use when sending SIP REGISTER; optional>",
"user_agent" : "<user agent to use when sending SIP REGISTER; optional>",
"proxy" : "<server to register at; optional, as won't be needed in case the REGISTER is not goint to be sent (e.g., guests)>",
"outbound_proxy" : "<outbound proxy to use, if any; optional>",
"headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP REGISTER; optional>",
"contact_params" : "<array of key/value objects, to specify custom Contact URI params to add to the SIP REGISTER; optional>",
"incoming_header_prefixes" : "<array of strings, to specify custom (non-standard) headers to read on incoming SIP events; optional>",
"refresh" : "<true|false; if true, only uses the SIP REGISTER as an update and not a new registration; optional>",
"master_id" : "<ID of an already registered account, if this is an helper for multiple calls (more on that later); optional>",
"register_ttl" : "<integer; number of seconds after which the registration should expire; optional>"
}
\endverbatim
*
* A \c registering event will be sent back, as this is an asynchronous request.
*
* In case it is required to, this request will originate a SIP REGISTER to the
* specified server with the right credentials. 401 and 407 responses will be
* handled automatically, and so errors will not be notified back to the caller
* unless they're definitive (e.g., wrong credentials). A failure to register
* will return an error with name \c registration_failed. A successful registration,
* instead, is notified in a \c registered event formatted like this:
*
\verbatim
{
"sip" : "event",
"result" : {
"event" : "registered",
"username" : <SIP URI username>,
"register_sent" : <true|false, depending on whether a REGISTER was sent or not>,
"master_id" : <unique ID of this registered session in the plugin, if a potential master>
}
}
\endverbatim
*
* To unregister, just send an \c unregister request with no other arguments:
*
\verbatim
{
"request" : "unregister"
}
\endverbatim
*
* As before, an \c unregistering event will be sent back. Just as before,
* this will also send a SIP REGISTER in case it had been sent originally.
* A successful unregistration is notified in an \c unregistered event:
*
\verbatim
{
"sip" : "event",
"result" : {
"event" : "unregistered",
"username" : <SIP URI username>,
"register_sent" : <true|false, depending on whether a REGISTER was sent or not>
}
}
\endverbatim
*
* Once registered, you can call or wait to be called: notice that you won't
* be able to get incoming calls if you chose never to send a REGISTER at
* all, though.
*
* To send a SIP INVITE, you can use the \c call request, which has to
* be formatted like this:
*
\verbatim
{
"request" : "call",
"call_id" : "<user-defined value of Call-ID SIP header used in all SIP requests throughout the call; optional>",
"uri" : "<SIP URI to call; mandatory>",
"refer_id" : <in case this is the result of a REFER, the unique identifier that addresses it; optional>,
"headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP INVITE; optional>",
"srtp" : "<whether to mandate (sdes_mandatory) or offer (sdes_optional) SRTP support; optional>",
"srtp_profile" : "<SRTP profile to negotiate, in case SRTP is offered; optional>",
"secret" : "<password to use to call, only needed in case authentication is needed and no REGISTER was sent; optional>",
"ha1_secret" : "<prehashed password to use to call, only needed in case authentication is needed and no REGISTER was sent; optional>",
"authuser" : "<username to use to authenticate as to call, only needed in case authentication is needed and no REGISTER was sent; optional>",
"autoaccept_reinvites" : <true|false, whether we should blindly accept re-INVITEs with a 200 OK instead of relaying the SDP to the application; optional, TRUE by default>
}
\endverbatim
*
* A \c calling event will be sent back, as this is an asynchronous request.
*
* Notice that this request MUST be associated to a JSEP offer: there's no
* way to send an offerless INVITE via the SIP plugin. This will generate
* a SIP INVITE and send it according to the instructions. While a
* <code>100 Trying</code> will not be notified back to the user, a
* <code>180 Ringing</code> will, in a \c ringing event:
*
\verbatim
{
"sip" : "event",
"call_id" : "<value of SIP Call-ID header for related call>",
"result" : {
"event" : "ringing",
"headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
}
}
\endverbatim
*
* If the call is declined, or any other error occurs, a \c hangup error
* event will be sent back. If the call is accepted, instead, an \c accepted
* event will be sent back to the user, along with the JSEP answer originated
* by the callee:
*
\verbatim
{
"sip" : "event",
"call_id" : "<value of SIP Call-ID header for related call>",
"result" : {
"event" : "accepted",
"username" : "<SIP URI of the callee>",
"headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
}
}
\endverbatim
*
* At this point, PeerConnection-related considerations aside, the call
* can be considered established. A SIP ACK is sent automatically by the
* SIP plugin, so there's no action required of the application to do
* that manually.
*
* Notice that the SIP plugin supports early-media via \c 183 responses
* responses. In case a \c 183 response is received, it's sent back to
* the user, along with the JSEP answer originated by the callee, in
* a \c progress event:
*
\verbatim
{
"sip" : "event",
"call_id" : "<value of SIP Call-ID header for related call>",
"result" : {
"event" : "progress",
"username" : "<SIP URI of the callee>",
"headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
}
}
\endverbatim
*
* In case the caller received a \c progress event, the following
* \c accepted event will NOT contain a JSEP answer, as the one received
* in the "Session Progress" event will act as the SDP answer for the session.
*
* Notice that you only use \c call to start a conversation, that is for
* the first INVITE. To update a session via a re-INVITE, e.g., to renegotiate
* a session to add/remove streams or force an ICE restart, you do NOT
* use \c call, but another request called \c update instead. This request
* needs no arguments, as the whole context is derived from the current
* state of the session. It does need the new JSEP offer to provide, though,
* as part of the renegotiation.
*
\verbatim
{
"request" : "update"
}
\endverbatim
*
* An \c updating event will be sent back, as this is an asynchronous request.
*
* While the \c call request allows you to send a SIP INVITE (and the
* \c update request allows you to update an existing session), there is
* a way to react to SIP INVITEs as well, that is to handle incoming calls.
* Incoming calls are notified to the application via \c incomingcall
* events:
*
\verbatim
{
"sip" : "event",
"call_id" : "<value of SIP Call-ID header for related call>",
"result" : {
"event" : "incomingcall",
"username" : "<SIP URI of the caller>",
"displayname" : "<display name of the caller, if available; optional>",
"callee" : "<SIP URI that was called (in case the user is associated with multiple public URIs)>",
"referred_by" : "<SIP URI header conveying the identity of the transferor, if this is a transfer; optional>",
"replaces" : "<call-ID of the call that this is supposed to replace, if this is an attended transfer; optional>",
"srtp" : "<whether the caller mandates (sdes_mandatory) or offers (sdes_optional) SRTP support; optional>",
"headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
}
}
\endverbatim
*
* The \c incomingcall may or may not be accompanied by a JSEP offer, depending
* on whether the caller sent an offerless INVITE or a regular one. Optionally,
* you can progress the incoming call with the \c progress request:
*
\verbatim
{
"request" : "progress",
"srtp" : "<whether to mandate (sdes_mandatory) or offer (sdes_optional) SRTP support; optional>",
"headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP OK; optional>"
"autoaccept_reinvites" : <true|false, whether we should blindly accept re-INVITEs with a 200 OK instead of relaying the SDP to the browser; optional, TRUE by default>
}
\endverbatim
*
* A \c progressing event will be sent back, as this is an asynchronous request.
*
* This will result in a <code>183 Session Progress</code> to be sent back to the caller.
* A \c progress request must always be accompanied by a JSEP answer (if the
* \c incomingcall event contained an offer) or offer (in case it was an
* offerless INVITE). This request can be used to inform the caller that the early
* media is available, such as ringback audio, announcements or other audio streams,
* without the call being fully established.
*
* Furthermore, you can accept the incoming call with the \c accept request:
*
\verbatim
{
"request" : "accept",
"srtp" : "<whether to mandate (sdes_mandatory) or offer (sdes_optional) SRTP support; optional>",
"headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP OK; optional>"
"autoaccept_reinvites" : <true|false, whether we should blindly accept re-INVITEs with a 200 OK instead of relaying the SDP to the browser; optional, TRUE by default>
}
\endverbatim
*
* An \c accepting event will be sent back, as this is an asynchronous request.
*
* This will result in a <code>200 OK</code> to be sent back to the caller.
* As was the case for \c progress request, an \c accept request must always
* be accompanied by a JSEP answer (if the \c incomingcall event contained an
* offer) or offer (in case it was an offerless INVITE). In the former case,
* an \c accepted event will be sent back just to confirm the call can be
* considered established; in the latter case, instead, an \c accepting event
* will be sent back instead, and an \c accepted event will only follow later,
* as soon as a JSEP answer is available in the SIP ACK the caller sent back.
*
* Notice that in case you get an incoming call while you're in another
* call, you will NOT get an \c incomingcall event, but a \c missed_call
* event instead, and just as a notification as there's no way to have
* two calls at the same time on the same handle in the SIP plugin:
*
\verbatim
{
"sip" : "event",
"call_id" : "<value of SIP Call-ID header for related call>",
"result" : {
"event" : "missed_call",
"caller" : "<SIP URI of the caller>",
"displayname" : "<display name of the caller, if available; optional>",
"callee" : "<SIP URI that was called (in case the user is associated with multiple public URIs)>"
}
}
\endverbatim
*
* Besides, you only use \c accept to answer the first INVITE. To accept a
* re-INVITE instead, which would be notified via an \c updatingcall event,
* you do NOT use \c accept, but the previously introduced \c update instead.
* This request needs no arguments, as the whole context is derived from the current
* state of the session. It does need the new JSEP answer to provide, though,
* as part of the renegotiation. As before, an \c updated event will be
* sent back, as this is an asynchronous request.
*
* Closing a session depends on the call state. If you have an incoming
* call that you don't want to accept, use the \c decline request; in all
* other cases, use the \c hangup request instead. Both requests need no
* additional arguments, as the whole context can be extracted from the
* current state of the session in the plugin:
*
\verbatim
{
"request" : "decline",
"code" : <SIP code to be sent, if not set, 486 is used; optional>",
"headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP request; optional>"
}
\endverbatim
*
\verbatim
{
"request" : "hangup",
"headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP BYE; optional>"
}
\endverbatim
*
* Since these are asynchronous requests, you'll get an event in response:
* \c declining if you used \c decline and \c hangingup if you used \c hangup.
*
* As anticipated before, when a call is declined or being hung up, a
* \c hangup event is sent instead, which is basically a SIP error event
* notification as it includes the \c code and \c reason . A regular BYE,
* for instance, would be notified with \c 200 and <code>SIP BYE</code>,
* although a more verbose description may be provided as well.
*
* When a session has been established, there are different requests that
* you can use to interact with the session.
*
* First of all, you can put a call on-hold with the \c hold request.
* By default, this request will send a new INVITE to the peer with a
* \c sendonly direction for media, but in case you want to set a
* different direction (\c recvonly or \c inactive ) you can do that by
* passing a \c direction attribute as well:
*
\verbatim
{
"request" : "hold",
"direction" : "<sendonly, recvonly or inactive>"
}
\endverbatim
*
* No WebRTC renegotiation will be involved here on the holder side, as
* this will only trigger a re-INVITE on the SIP side. To remove the
* call from on-hold, just send a \c unhold request to the plugin,
* which requires no additional attributes:
*
\verbatim
{
"request" : "unhold"
}
\endverbatim
*
* and will restore the media direction that was set in the SDP before
* putting the call on-hold.
*
* The \c message request allows you to send a SIP MESSAGE to the peer.
* By default, it is sent in dialog, during active call.
* But, if the user is registered, it might be sent out of dialog also. In that case the uri parameter is required.
*
\verbatim
{
"request" : "message",
"call_id" : "<user-defined value of Call-ID SIP header used to send the message; optional>",
"content_type" : "<content type; optional>"
"content" : "<text to send>",
"uri" : "<SIP URI of the peer; optional; if set, the message will be sent out of dialog>",
"headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP MESSAGE; optional>"
}
\endverbatim
*
* A \c messagesent event will be sent back. Incoming SIP MESSAGEs, instead,
* are notified in \c message events:
*
\verbatim
{
"sip" : "event",
"result" : {
"event" : "message",
"sender" : "<SIP URI of the message sender>",
"displayname" : "<display name of the sender, if available; optional>",
"content_type" : "<content type of the message>",
"content" : "<content of the message>",
"headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
}
}
\endverbatim
*
* After delivery a \c messagedelivery event will be sent back with the SIP server response.
* Used to track the delivery status of the message.
*
\verbatim
{
"sip" : "event",
"call_id" : "<value of SIP Call-ID header for related message>",
"result" : {
"event" : "messagedelivery",
"code" : "<SIP error code>",
"reason" : "<SIP error reason>",
}
}
\endverbatim
*
* SIP INFO works pretty much the same way, except that you use an \c info
* request to one to the peer:
*
\verbatim
{
"request" : "info",
"type" : "<content type>"
"content" : "<message to send>",
"headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP INFO; optional>"
}
\endverbatim
*
* A \c infosent event will be sent back. Incoming SIP INFOs, instead,
* are notified in \c info events:
*
\verbatim
{
"sip" : "event",
"result" : {
"event" : "info",
"sender" : "<SIP URI of the message sender>",
"displayname" : "<display name of the sender, if available; optional>",
"type" : "<content type of the message>",
"content" : "<content of the message>",
"headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
}
}
\endverbatim
*
* As anticipated, SIP events are supported as well, using the SUBSCRIBE
* and NOTIFY mechanism. To do that, you need to use the \c subscribe
* request, which has to be formatted like this:
*
\verbatim
{
"request" : "subscribe",
"call_id" : "<user-defined value of Call-ID SIP header used in all SIP requests throughout the subscription; optional>",
"event" : "<the event to subscribe to, e.g., 'message-summary'; mandatory>",
"accept" : "<what should be put in the Accept header; optional>",
"to" : "<who should be the SUBSCRIBE addressed to; optional, will use the user's identity if missing>",
"subscribe_ttl" : "<integer; number of seconds after which the subscription should expire; optional>",
"headers" : "<array of key/value objects, to specify custom headers to add to the SIP SUBSCRIBE; optional>"
}
\endverbatim
*
* A \c subscribing event will be sent back, followed by a \c subscribe_succeeded if
* the SUBSCRIBE request was accepted, and a \c subscribe_failed if the transaction
* failed instead. Incoming SIP NOTIFY events, instead, are notified in \c notify events:
*
\verbatim
{
"sip" : "event",
"call_id" : "<value of SIP Call-ID header for related subscription>",
"result" : {
"event" : "notify",
"notify" : "<name of the event that the user is subscribed to, e.g., 'message-summary'>",
"substate" : "<substate of the subscription, e.g., 'active'>",
"content-type" : "<content-type of the message>"
"content" : "<content of the message>",
"headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
}
}
\endverbatim
*
* You can also record a SIP call, and it works pretty much the same the
* VideoCall plugin does. Specifically, you make use of the \c recording
* request to either start or stop a recording, using the following syntax:
*
\verbatim
{
"request" : "recording",
"action" : "<start|stop, depending on whether you want to start or stop recording something>"
"audio" : <true|false; whether or not our audio should be recorded>,
"video" : <true|false; whether or not our video should be recorded>,
"peer_audio" : <true|false; whether or not our peer's audio should be recorded>,
"peer_video" : <true|false; whether or not our peer's video should be recorded>,
"send_peer_pli" : <true|false; whether or not send PLI to request keyframe from peer>,
"filename" : "<base path/filename to use for all the recordings>"
}
\endverbatim
*
* As you can see, this means that the two sides of conversation are recorded
* separately, and so are the audio and video streams if available. You can
* choose which ones to record, in case you're interested in just a subset.
* The \c filename part is just a prefix, and dictates the actual filenames
* that will be used for the up-to-four recordings that may need to be enabled.
*
* A \c recordingupdated event is sent back in case the request is successful.
*
* \section sipmc Simultaneous SIP calls using the same account
*
* As anticipated in the previous sections, attaching to the SIP plugin
* with a Janus handle means creating a SIP stack on behalf of a user
* or application: this typically means registering an account, and being
* able to start or receive calls, handle subscriptions, and so on. This
* also means that, since in Janus each core handle can only be associated
* with a single PeerConnection, each SIP account is limited to a single
* call per time: if a user is in a SIP session already, and another call
* comes in, it's automatically rejected with a \c 486 \c Busy .
*
* While usually not a big deal, there are use cases where it might make
* sense to be able to support multiple concurrent calls, and maybe switch
* from one to the other seamlessly. This is possible in the SIP plugin
* using the so-called \c helper sessions. Specifically, \c helper sessions
* work under the assumption that there's a \c master session that is
* registered normally (the "regular" SIP plugin handle, that is), and
* that these \c helper sessions can simply be associated to that: any time
* another concurrent call is needed, if the \c master session is busy
* one of the \c helpers can be used; the more \c helper sessions are
* available, the more simultaneous calls can be established.
*
* The way this works is simple:
*
* 1. you create a SIP session the usual way, and send a regular \c register
* there; this will be the \c master session, and will return a \c master_id
* when successfully registered;
* 2. for each \c helper you want to add, you attach a new Janus handle
* to the SIP plugin, and send a \c register with \c type: \c "helper" and
* providing the same \c username as the master, plus a \c master_id attribute
* referencing the main session;
* 3. at this point, the new \c helper is associated to the \c master ,
* meaning it can be used to start new calls or receive calls exactly
* as the main session, and using the same account information, credentials,
* etc.
*
* Notice that, as soon as the \c master unregisters, or the Janus handle
* it's on is detached, all the \c helper sessions associated to it are
* automatically torn down as well. Specifically, the plugin will forcibly
* detach the related handles. Should you need to register again, and want
* some helpers there too, you'll have to add them again.
*
* If you want to see this in practice, the SIP plugin demo has a "hidden"
* function you can invoke from the JavaScript console to play with helpers:
* calling the \c addHelper() function will add a new helper, and show additional
* controls. You can add as many helpers as you want.
*
* \section siptr Attended and blind transfers
*
* The Janus SIP plugin supports both attended and blind transfers, and to
* do so mostly relies on the multiple calls functionality: as such, make
* sure you've read and are familiar with the section on \ref sipmc .
*
* Most of the transfer-related functionality are based on existing messages
* and events already documented in the previous section, but there are a
* few aspects you need to be aware of. First of all, if you're the transferor,
* you need to use a new request called \c transfer , that allows you to
* send a SIP REFER to the transferee so to reach a different target. The
* \c transfer request must be formatted like this:
*
\verbatim
{
"request" : "transfer",
"uri" : "<SIP URI to send the transferee too>",
"replace" : "<call-ID of the call this attended transfer is supposed to replace; default is none, which means blind/unattended transfer>"
}
\endverbatim
*
* Whether this is a blind (no call to replace) or attended transfer,
* a \c transferring event will be sent back, as this is an asynchronous
* request. Further updates will come in the form of NOTIFY-related events,
* as a REFER implicitly creates a subscription.
*
* The recipient of a REFER, instead, will receive an asynchronous event
* called \c transfer as well, with info it needs to be aware of. In fact,
* the SIP plugin doesn't do anything automatically: an incoming REFER is
* notified to the application, so that it can decide whether to follow
* up on the transfer or not. The syntax of the event is the following:
*
\verbatim
{
"sip" : "event",
"result" : {
"event" : "transfer",
"refer_id" : <unique ID, internal to Janus, of this referral>,
"refer_to" : "<SIP URI to call>",
"referred_by" : "<SIP URI SIP URI header conveying the identity of the transferor; optional>",
"replaces" : "<call-ID of the call this transfer is supposed to replace; optional, and only present for attended transfers>",
"headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
}
}
\endverbatim
*
* The most important property in that list is \c refer_id as that value
* must be included in the \c call request to call the target, if the
* transfer is accepted: in fact, that's the only way the SIP plugin has
* to correlate the new outgoing call to the previous transfer request,
* and thus be able to notify the transferor about how the call is
* proceeding by means of NOTIFY events. Notice that, if the transferee
* decides to follow up on the transfer request, and they're already in
* a call (e.g., with the transferor), then they must use a different
* handle for the purpose, e.g., via a helper as described in the
* \ref sipmc section.
*
* The transfer target will receive the call exactly as previously discussed,
* with the difference that it may or may not include a \c referred_by
* property for information purposes. Just as the transferee, if they're
* already in a call, it's up to the application to create a helper to
* setup a new Janus handle to accept the transfer.
*
* Notice that the plugin will NOT put the involved calls on-hold, or
* automatically close calls that are meant to be replaced by a transfer.
* All this is the application responsibility, and as such it's up to
* the developer to react to events accordingly.
*
*/
#include "plugin.h"
#include <arpa/inet.h>
#include <net/if.h>
#include <jansson.h>
#include <sofia-sip/msg_header.h>
#include <sofia-sip/nua.h>
#include <sofia-sip/nua_tag.h>
#include <sofia-sip/sdp.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-prototypes"
#include <sofia-sip/sip_header.h>
#pragma GCC diagnostic pop
#include <sofia-sip/sip_status.h>
#include <sofia-sip/url.h>
#include <sofia-sip/tport_tag.h>
#include <sofia-sip/su_log.h>
#include <sofia-sip/sofia_features.h>
#include "../debug.h"
#include "../apierror.h"
#include "../config.h"
#include "../mutex.h"
#include "../record.h"
#include "../rtp.h"
#include "../rtpsrtp.h"
#include "../rtcp.h"
#include "../sdp-utils.h"
#include "../utils.h"
#include "../ip-utils.h"
/* Plugin information */
#define JANUS_SIP_VERSION 9
#define JANUS_SIP_VERSION_STRING "0.0.9"
#define JANUS_SIP_DESCRIPTION "This is a simple SIP plugin for Janus, allowing WebRTC peers to register at a SIP server and call SIP user agents through a Janus instance."
#define JANUS_SIP_NAME "JANUS SIP plugin"
#define JANUS_SIP_AUTHOR "Meetecho s.r.l."
#define JANUS_SIP_PACKAGE "janus.plugin.sip"
/* Plugin methods */
janus_plugin *create(void);
int janus_sip_init(janus_callbacks *callback, const char *config_path);
void janus_sip_destroy(void);
int janus_sip_get_api_compatibility(void);
int janus_sip_get_version(void);
const char *janus_sip_get_version_string(void);
const char *janus_sip_get_description(void);
const char *janus_sip_get_name(void);
const char *janus_sip_get_author(void);
const char *janus_sip_get_package(void);
void janus_sip_create_session(janus_plugin_session *handle, int *error);
struct janus_plugin_result *janus_sip_handle_message(janus_plugin_session *handle, char *transaction, json_t *message, json_t *jsep);
void janus_sip_setup_media(janus_plugin_session *handle);
void janus_sip_incoming_rtp(janus_plugin_session *handle, janus_plugin_rtp *packet);
void janus_sip_incoming_rtcp(janus_plugin_session *handle, janus_plugin_rtcp *packet);
void janus_sip_hangup_media(janus_plugin_session *handle);
void janus_sip_destroy_session(janus_plugin_session *handle, int *error);
json_t *janus_sip_query_session(janus_plugin_session *handle);
/* Plugin setup */
static janus_plugin janus_sip_plugin =
JANUS_PLUGIN_INIT (
.init = janus_sip_init,
.destroy = janus_sip_destroy,
.get_api_compatibility = janus_sip_get_api_compatibility,
.get_version = janus_sip_get_version,
.get_version_string = janus_sip_get_version_string,
.get_description = janus_sip_get_description,
.get_name = janus_sip_get_name,
.get_author = janus_sip_get_author,
.get_package = janus_sip_get_package,
.create_session = janus_sip_create_session,
.handle_message = janus_sip_handle_message,
.setup_media = janus_sip_setup_media,
.incoming_rtp = janus_sip_incoming_rtp,
.incoming_rtcp = janus_sip_incoming_rtcp,
.hangup_media = janus_sip_hangup_media,
.destroy_session = janus_sip_destroy_session,
.query_session = janus_sip_query_session,
);
/* Plugin creator */
janus_plugin *create(void) {
JANUS_LOG(LOG_VERB, "%s created!\n", JANUS_SIP_NAME);
return &janus_sip_plugin;
}
/* Parameter validation */
static struct janus_json_parameter request_parameters[] = {
{"request", JSON_STRING, JANUS_JSON_PARAM_REQUIRED}
};
static struct janus_json_parameter register_parameters[] = {
{"type", JSON_STRING, 0},
{"send_register", JANUS_JSON_BOOL, 0},
{"force_udp", JANUS_JSON_BOOL, 0},
{"force_tcp", JANUS_JSON_BOOL, 0},
{"sips", JANUS_JSON_BOOL, 0},
{"rfc2543_cancel", JANUS_JSON_BOOL, 0},
{"username", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
{"secret", JSON_STRING, 0},
{"ha1_secret", JSON_STRING, 0},
{"authuser", JSON_STRING, 0},
{"display_name", JSON_STRING, 0},
{"user_agent", JSON_STRING, 0},
{"headers", JSON_OBJECT, 0},
{"contact_params", JSON_OBJECT, 0},
{"master_id", JANUS_JSON_INTEGER, 0},
{"refresh", JANUS_JSON_BOOL, 0},
{"incoming_header_prefixes", JSON_ARRAY, 0},
{"register_ttl", JANUS_JSON_INTEGER, 0}
};
static struct janus_json_parameter subscribe_parameters[] = {
{"to", JSON_STRING, 0},
{"event", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
{"accept", JSON_STRING, 0},
{"subscribe_ttl", JANUS_JSON_INTEGER, 0},
{"call_id", JANUS_JSON_STRING, 0},
{"headers", JSON_OBJECT, 0}
};
static struct janus_json_parameter proxy_parameters[] = {
{"proxy", JSON_STRING, 0},
{"outbound_proxy", JSON_STRING, 0}
};
static struct janus_json_parameter call_parameters[] = {
{"uri", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
{"headers", JSON_OBJECT, 0},
{"call_id", JANUS_JSON_STRING, 0},
{"srtp", JSON_STRING, 0},
{"srtp_profile", JSON_STRING, 0},
{"autoaccept_reinvites", JANUS_JSON_BOOL, 0},
{"refer_id", JANUS_JSON_INTEGER, 0},
/* The following are only needed in case "guest" registrations
* still need an authenticated INVITE for some reason */
{"secret", JSON_STRING, 0},
{"ha1_secret", JSON_STRING, 0},
{"authuser", JSON_STRING, 0}
};
static struct janus_json_parameter accept_parameters[] = {
{"srtp", JSON_STRING, 0},
{"headers", JSON_OBJECT, 0},
{"autoaccept_reinvites", JANUS_JSON_BOOL, 0}
};
static struct janus_json_parameter progress_parameters[] = {
{"srtp", JSON_STRING, 0},
{"headers", JSON_OBJECT, 0},
{"autoaccept_reinvites", JANUS_JSON_BOOL, 0}
};
static struct janus_json_parameter decline_parameters[] = {
{"code", JANUS_JSON_INTEGER, 0},
{"headers", JSON_OBJECT, 0},
{"refer_id", JANUS_JSON_INTEGER, 0}
};
static struct janus_json_parameter transfer_parameters[] = {
{"uri", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
{"replace", JANUS_JSON_STRING, 0}
};
static struct janus_json_parameter hold_parameters[] = {
{"direction", JSON_STRING, 0}
};
static struct janus_json_parameter recording_parameters[] = {
{"action", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
{"audio", JANUS_JSON_BOOL, 0},
{"video", JANUS_JSON_BOOL, 0},
{"peer_audio", JANUS_JSON_BOOL, 0},
{"peer_video", JANUS_JSON_BOOL, 0},
{"send_peer_pli", JANUS_JSON_BOOL, 0},
{"filename", JSON_STRING, 0}
};
static struct janus_json_parameter dtmf_info_parameters[] = {
{"digit", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
{"duration", JSON_INTEGER, JANUS_JSON_PARAM_POSITIVE},
{"headers", JSON_OBJECT, 0}
};
static struct janus_json_parameter info_parameters[] = {
{"type", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
{"content", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
{"headers", JSON_OBJECT, 0}
};
static struct janus_json_parameter sipmessage_parameters[] = {
{"content_type", JSON_STRING, 0},
{"content", JSON_STRING, JANUS_JSON_PARAM_REQUIRED},
{"uri", JSON_STRING, 0},
{"headers", JSON_OBJECT, 0},
{"call_id", JANUS_JSON_STRING, 0}
};
/* Useful stuff */
static volatile gint initialized = 0, stopping = 0;
static gboolean notify_events = TRUE;
static gboolean ipv6_disabled = FALSE;
static janus_callbacks *gateway = NULL;
static char *local_ip = NULL, *sdp_ip = NULL, *local_media_ip = NULL;
static janus_network_address janus_network_local_media_ip = { 0 };
static int keepalive_interval = 120;
static gboolean behind_nat = FALSE;
static char *user_agent;
#define JANUS_DEFAULT_REGISTER_TTL 3600
static int register_ttl = JANUS_DEFAULT_REGISTER_TTL;
#define JANUS_DEFAULT_SUBSCRIBE_TTL 3600
static int subscribe_ttl = JANUS_DEFAULT_SUBSCRIBE_TTL;
static uint16_t rtp_range_min = 10000;
static uint16_t rtp_range_max = 60000;
static int dscp_audio_rtp = 0;
static int dscp_video_rtp = 0;
static char *sips_certs_dir = NULL;
#define JANUS_DEFAULT_SIP_TIMER_T1X64 32000
static int sip_timer_t1x64 = JANUS_DEFAULT_SIP_TIMER_T1X64;
static uint16_t dtmf_keys[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '#', 'A', 'B', 'C', 'D'};
static gboolean query_contact_header = FALSE;
static GThread *handler_thread;
static void *janus_sip_handler(void *data);
static void janus_sip_hangup_media_internal(janus_plugin_session *handle);
typedef struct janus_sip_message {
janus_plugin_session *handle;
char *transaction;
json_t *message;
json_t *jsep;
} janus_sip_message;
static GAsyncQueue *messages = NULL;
static janus_sip_message exit_message;
typedef enum {
janus_sip_registration_status_disabled = -2,
janus_sip_registration_status_failed = -1,
janus_sip_registration_status_unregistered = 0,
janus_sip_registration_status_registering,
janus_sip_registration_status_registered,
janus_sip_registration_status_unregistering,
} janus_sip_registration_status;
static const char *janus_sip_registration_status_string(janus_sip_registration_status status) {
switch(status) {
case janus_sip_registration_status_disabled:
return "disabled";
case janus_sip_registration_status_failed:
return "failed";
case janus_sip_registration_status_unregistered:
return "unregistered";
case janus_sip_registration_status_registering:
return "registering";
case janus_sip_registration_status_registered:
return "registered";
case janus_sip_registration_status_unregistering:
return "unregistering";
default:
return "unknown";
}
}
typedef enum {
janus_sip_call_status_idle = 0,
janus_sip_call_status_inviting,
janus_sip_call_status_invited,
janus_sip_call_status_progress,
janus_sip_call_status_incall,
janus_sip_call_status_incall_reinviting,
janus_sip_call_status_incall_reinvited,
janus_sip_call_status_closing,
} janus_sip_call_status;
static const char *janus_sip_call_status_string(janus_sip_call_status status) {
switch(status) {
case janus_sip_call_status_idle:
return "idle";
case janus_sip_call_status_inviting:
return "inviting";
case janus_sip_call_status_invited:
return "invited";
case janus_sip_call_status_progress:
return "progress";
case janus_sip_call_status_incall:
return "incall";
case janus_sip_call_status_incall_reinviting:
return "incall_reinviting";
case janus_sip_call_status_incall_reinvited:
return "incall_reinvited";
case janus_sip_call_status_closing:
return "closing";
default:
return "unknown";
}
}
/* Sofia stuff */
typedef struct ssip_s ssip_t;
typedef struct ssip_oper_s ssip_oper_t;
#undef SU_ROOT_MAGIC_T
#define SU_ROOT_MAGIC_T ssip_t
#undef NUA_MAGIC_T
#define NUA_MAGIC_T ssip_t
#undef NUA_HMAGIC_T
#define NUA_HMAGIC_T ssip_oper_t
struct ssip_s {
su_home_t s_home[1];
su_root_t *s_root;
nua_t *s_nua;
nua_handle_t *s_nh_r, *s_nh_i, *s_nh_m;
char *contact_header; /* Only needed for Sofia SIP >= 1.13 */
GHashTable *subscriptions;
janus_mutex smutex;
struct janus_sip_session *session;