Update upstream source from tag 'upstream/16.15.0_dfsg'
Update to upstream version '16.15.0~dfsg'
with Debian dir b9b59cc37ea5ea5abc7fb2816076ca4f0af6f82f
Bernhard Schmidt
3 years ago
10 | 10 | === and the other UPGRADE files for older releases. |
11 | 11 | === |
12 | 12 | ============================================================================== |
13 | ||
14 | ------------------------------------------------------------------------------ | |
15 | --- Functionality changes from Asterisk 16.13.0 to Asterisk 16.14.0 ---------- | |
16 | ------------------------------------------------------------------------------ | |
17 | ||
18 | Core | |
19 | ------------------ | |
20 | * Added debug logging categories that allow a user to output debug information | |
21 | based on a specified category. This lets the user limit, and filter debug | |
22 | output to data relevant to a particular context, or topic. For instance the | |
23 | following categories are now available for debug logging purposes: | |
24 | ||
25 | dtls, dtls_packet, ice, rtcp, rtcp_packet, rtp, rtp_packet, stun, stun_packet | |
26 | ||
27 | These debug categories can be enable/disable via an Asterisk CLI command: | |
28 | ||
29 | core set debug category <category>[:<sublevel>] [category[:<sublevel] ...] | |
30 | core set debug category off [<category> [<category>] ...] | |
31 | ||
32 | If no sub-level is associated all debug statements for a given category are | |
33 | output. If a sub-level is given then only those statements assigned a value | |
34 | at or below the associated sub-level are output. | |
35 | ||
36 | STIR/SHAKEN | |
37 | ------------------ | |
38 | * STIR/SHAKEN support has been added to Asterisk. Configuration is done in | |
39 | stir_shaken.conf. There is a sample configuration file to help you get | |
40 | started (asterisk/configs/samples/stir_shaken.conf.sample). Once that's | |
41 | set up, you can enable STIR/SHAKEN on any endpoint by setting stir_shaken | |
42 | to yes on the endpoint configuration object. This will add an Identity | |
43 | header on outgoing INVITEs, and check for an Identity header on incoming | |
44 | INVITEs. This option has been added to Alembic as well. | |
45 | ||
46 | The information received on an incoming INVITE can be checked using the | |
47 | STIR_SHAKEN dialplan function. There are two variations: | |
48 | ||
49 | STIR_SHAKEN(count) | |
50 | STIR_SHAKEN(0, verify_result) | |
51 | ||
52 | The first variation will tell you how many STIR/SHAKEN results are on the | |
53 | channel. The second fetches information for a specific result. The first | |
54 | parameter is the index, followed by what information you want to retrieve. | |
55 | The available options are 'verify_result', 'identity', and 'attestation'. | |
56 | ||
57 | app_confbridge | |
58 | ------------------ | |
59 | * app_confbridge now has the ability to force the estimated bitrate on an SFU | |
60 | bridge. To use it, set a bridge profile's remb_behavior to "force" and | |
61 | set remb_estimated_bitrate to a rate in bits per second. The | |
62 | remb_estimated_bitrate parameter is ignored if remb_behavior is something | |
63 | other than "force". | |
13 | 64 | |
14 | 65 | ------------------------------------------------------------------------------ |
15 | 66 | --- Functionality changes from Asterisk 16.13.0 to Asterisk 16.14.0 ---------- |
0 | 2020-10-19 18:12 +0000 Asterisk Development Team <asteriskteam@digium.com> | |
1 | ||
2 | * asterisk 16.14.0 Released. | |
0 | 2020-11-19 12:34 +0000 Asterisk Development Team <asteriskteam@digium.com> | |
1 | ||
2 | * asterisk 16.15.0 Released. | |
3 | ||
4 | 2020-11-12 12:03 +0000 Asterisk Development Team <asteriskteam@digium.com> | |
5 | ||
6 | * asterisk 16.15.0-rc1 Released. | |
7 | ||
8 | 2020-11-12 05:48 +0000 [3366956139] Asterisk Development Team <asteriskteam@digium.com> | |
9 | ||
10 | * Update CHANGES and UPGRADE.txt for 16.15.0 | |
11 | 2020-11-02 13:53 +0000 [e1fd51cd2c] George Joseph <gjoseph@digium.com> | |
12 | ||
13 | * res_pjsip_outbound_registration.c: Use our own scheduler and other stuff | |
14 | ||
15 | * Instead of using the pjproject timer heap, we now use our own | |
16 | pjsip_scheduler. This allows us to more easily debug and allows us to | |
17 | see times in "pjsip show/list registrations" as well as being able to | |
18 | see the registrations in "pjsip show scheduled_tasks". | |
19 | ||
20 | * Added the last registration time, registration interval, and the next | |
21 | registration time to the CLI output. | |
22 | ||
23 | * Removed calls to pjsip_regc_info() except where absolutely necessary. | |
24 | Most of the calls were just to get the server and client URIs for log | |
25 | messages so we now just save them on the client_state object when we | |
26 | create it. | |
27 | ||
28 | * Added log messages where needed and updated most of the existong ones | |
29 | to include the registration object name at the start of the message. | |
30 | ||
31 | Change-Id: I4534a0fc78c7cb69f23b7b449dda9748c90daca2 | |
32 | ||
33 | 2020-11-02 13:53 +0000 [80f116c156] George Joseph <gjoseph@digium.com> | |
34 | ||
35 | * pjsip_scheduler.c: Add type ONESHOT and enhance cli show command | |
36 | ||
37 | * Added a ONESHOT type that never reschedules. | |
38 | ||
39 | * Added "like" capability to "pjsip show scheduled_tasks" so you can do | |
40 | the following: | |
41 | ||
42 | CLI> pjsip show scheduled_tasks like outreg | |
43 | PJSIP Scheduled Tasks: | |
44 | ||
45 | Task Name Interval Times Run ... | |
46 | ============================================= ========= ========= ... | |
47 | pjsip/outreg/testtrunk-reg-0-00000074 50.000 oneshot ... | |
48 | pjsip/outreg/voipms-reg-0-00000073 110.000 oneshot ... | |
49 | ||
50 | * Fixed incorrect display of "Next Start". | |
51 | ||
52 | * Compacted the displays of times in the CLI. | |
53 | ||
54 | * Added two new functions (ast_sip_sched_task_get_times2, | |
55 | ast_sip_sched_task_get_times_by_name2) that retrieve the interval, | |
56 | next start time, and next run time in addition to the times already | |
57 | returned by ast_sip_sched_task_get_times(). | |
58 | ||
59 | Change-Id: Ie718ca9fd30490b8a167bedf6b0b06d619dc52f3 | |
60 | ||
61 | 2020-10-02 14:32 +0000 [728cd55cde] Alexei Gradinari <alex2grad@gmail.com> | |
62 | ||
63 | * sched: AST_SCHED_REPLACE_UNREF can lead to use after free of data | |
64 | ||
65 | The data can be freed if the old object '_data' is the same object as | |
66 | new 'data'. Because at first the object is unreferenced which can lead | |
67 | to destroying it. | |
68 | ||
69 | This could happened in res_pjsip_pubsub when the publication is updated | |
70 | which could lead to segfault in function publish_expire. | |
71 | ||
72 | Change-Id: I0164f57c387243510bdbd2f8dcf33377b6c202da | |
73 | ||
74 | 2020-10-30 11:43 +0000 [ddfb76a864] Alexander Traud <pabstraud@compuserve.com> | |
75 | ||
76 | * res_pjsip/config_transport: Load and run without OpenSSL. | |
77 | ||
78 | ASTERISK-28933 | |
79 | Reported-by: Walter Doekes | |
80 | ||
81 | Change-Id: I65eac49e5b0a79261ea80e2b9b38a836886ed59f | |
82 | ||
83 | 2020-10-30 05:53 +0000 [277aa0ced6] Alexander Traud <pabstraud@compuserve.com> | |
84 | ||
85 | * res_stir_shaken: Include OpenSSL headers where used actually. | |
86 | ||
87 | This avoids the inclusion of the OpenSSL headers in the public header, | |
88 | which avoids one external library dependency in res_pjsip_stir_shaken. | |
89 | ||
90 | Change-Id: I6a07e2d81d2b5442e24e99b8cc733a99f881dcf4 | |
91 | ||
92 | 2020-10-18 13:40 +0000 [5046e1fb06] Dovid Bender <dovid@telecurve.com> | |
93 | ||
94 | * func_curl.c: Allow user to set what return codes constitute a failure. | |
95 | ||
96 | Currently any response from res_curl where we get an answer from the | |
97 | web server, regardless of what the response is (404, 403 etc.) Asterisk | |
98 | currently treats it as a success. This patch allows you to set which | |
99 | codes should be considered as a failure by Asterisk. If say we set | |
100 | failurecodes=404,403 then when using curl in realtime if a server gives | |
101 | a 404 error Asterisk will try to failover to the next option set in | |
102 | extconfig.conf | |
103 | ||
104 | ASTERISK-28825 | |
105 | ||
106 | Reported by: Dovid Bender | |
107 | Code by: Gobinda Paul | |
108 | ||
109 | Change-Id: I94443e508343e0a3e535e51ea6e0562767639987 | |
110 | ||
111 | 2020-10-19 17:21 +0000 [8973fe5cf3] Kevin Harwell <kharwell@digium.com> | |
112 | ||
113 | * AST-2020-001 - res_pjsip: Return dialog locked and referenced | |
114 | ||
115 | pjproject returns the dialog locked and with a reference. However, | |
116 | in Asterisk the method that handles this decrements the reference | |
117 | and removes the lock prior to returning. This makes it possible, | |
118 | under some circumstances, for another thread to free said dialog | |
119 | before the thread that created it attempts to use it again. Of | |
120 | course when the thread that created it tries to use a freed dialog | |
121 | a crash can occur. | |
122 | ||
123 | This patch makes it so Asterisk now returns the newly created | |
124 | dialog both locked, and with an added reference. This allows the | |
125 | caller to de-reference, and unlock the dialog when it is safe to | |
126 | do so. | |
127 | ||
128 | In the case of a new SIP Invite the lock, and reference are now | |
129 | held for the entirety of the new invite handling process. | |
130 | Otherwise it's possible for the dialog, or its dependent objects, | |
131 | like the transaction, to disappear. For example if there is a TCP | |
132 | transport error. | |
133 | ||
134 | ASTERISK-29057 #close | |
135 | ||
136 | Change-Id: I5ef645a47829596f402cf383dc02c629c618969e | |
137 | ||
138 | 2020-11-02 10:29 +0000 [58aa6a7057] Ben Ford <bford@digium.com> | |
139 | ||
140 | * AST-2020-002 - res_pjsip: Stop sending INVITEs after challenge limit. | |
141 | ||
142 | If Asterisk sends out an INVITE and receives a challenge with a | |
143 | different nonce value each time, it will continuously send out INVITEs, | |
144 | even if the call is hung up. The endpoint must be configured for | |
145 | outbound authentication for this to occur. A limit has been set on | |
146 | outbound INVITEs so that, once reached, Asterisk will stop sending | |
147 | INVITEs and the transaction will terminate. | |
148 | ||
149 | ASTERISK-29013 | |
150 | ||
151 | Change-Id: I2d001ca745b00ca8aa12030f2240cd72363b46f7 | |
152 | ||
153 | 2020-10-29 10:21 +0000 [e067d5c8fd] Sean Bright <sean.bright@gmail.com> | |
154 | ||
155 | * sip_to_pjsip.py: Handle #include globs and other fixes | |
156 | ||
157 | * Wildcards in #includes are now properly expanded | |
158 | ||
159 | * Implement operators for Section class to allow sorting | |
160 | ||
161 | ASTERISK-29142 #close | |
162 | ||
163 | Change-Id: I9b9cd95f4cbe5c24506b75d17173c5aa1a83e5df | |
164 | ||
165 | 2020-10-29 08:35 +0000 [13b56c4be6] Alexander Traud <pabstraud@compuserve.com> | |
166 | ||
167 | * Compiler fixes for GCC with -Og | |
168 | ||
169 | ASTERISK-29144 | |
170 | ||
171 | Change-Id: I2a72c072083b4492a223c6f9d73d21f4f424db62 | |
172 | ||
173 | 2020-10-30 03:46 +0000 [334661601a] Alexander Traud <pabstraud@compuserve.com> | |
174 | ||
175 | * Compiler fixes for GCC when printf %s is NULL | |
176 | ||
177 | ASTERISK-29146 | |
178 | ||
179 | Change-Id: Ib04bdad87d729f805f5fc620ef9952f58ea96d41 | |
180 | ||
181 | 2020-10-29 08:59 +0000 [92ca48d54c] Alexander Traud <pabstraud@compuserve.com> | |
182 | ||
183 | * Compiler fixes for GCC with -Os | |
184 | ||
185 | ASTERISK-29145 | |
186 | ||
187 | Change-Id: I9af705f2b9725c53141aef5d0ff512a1800f073c | |
188 | ||
189 | 2020-10-23 10:26 +0000 [951ce0524d] Alexander Traud <pabstraud@compuserve.com> | |
190 | ||
191 | * chan_sip: On authentication, pick MD5 for sure. | |
192 | ||
193 | RFC 8760 added new digest-access-authentication schemes. Testing | |
194 | revealed that chan_sip does not pick MD5 if several schemes are offered | |
195 | by the User Agent Server (UAS). This change does not implement any of | |
196 | the new schemes like SHA-256. This change makes sure, MD5 is picked so | |
197 | UAS with SHA-2 enabled, like the service www.linphone.org/freesip, can | |
198 | still be used. This should have worked since day one because SIP/2.0 | |
199 | already envisioned several schemes (see RFC 3261 and its augmented BNF | |
200 | for 'algorithm' which includes 'token' as third alternative; note: if | |
201 | 'algorithm' was not present, MD5 is still assumed even in RFC 7616). | |
202 | ||
203 | Change-Id: I61ca0b1f74b5ec2b5f3062c2d661cafeaf597fcd | |
204 | ||
205 | 2020-06-04 09:23 +0000 [f98eed17c1] Walter Doekes <walter+asterisk@wjd.nu> | |
206 | ||
207 | * main/say: Work around gcc 9 format-truncation false positive | |
208 | ||
209 | Version: gcc (Ubuntu 9.3.0-10ubuntu2) 9.3.0 | |
210 | Warning: | |
211 | say.c:2371:24: error: ‘%d’ directive output may be truncated writing | |
212 | between 1 and 11 bytes into a region of size 10 | |
213 | [-Werror=format-truncation=] | |
214 | 2371 | snprintf(buf, 10, "%d", num); | |
215 | say.c:2371:23: note: directive argument in the range [-2147483648, 9] | |
216 | ||
217 | That's not possible though, as the if() starts out checking for (num < 0), | |
218 | making this Warning a false positive. | |
219 | ||
220 | (Also replaced some else<TAB>if with else<SP>if while in the vicinity.) | |
221 | ||
222 | Change-Id: Ic7a70120188c9aa525a6d70289385bfce878438a | |
223 | ||
224 | 2020-10-19 15:31 +0000 [92e1de458a] Kevin Harwell <kharwell@digium.com> | |
225 | ||
226 | * res_pjsip, res_pjsip_session: initialize local variables | |
227 | ||
228 | This patch initializes a couple of local variables to some default values. | |
229 | Interestingly, in the 'pj_status_t dlg_status' case the value not being | |
230 | initialized caused memory to grow, and not be recovered, in the off nominal | |
231 | path (at least on my machine). | |
232 | ||
233 | Change-Id: I22ee65e1e1bff8efacea8a167c6c8428898523f7 | |
234 | ||
235 | 2020-10-23 09:55 +0000 [65426f4312] Alexander Traud <pabstraud@compuserve.com> | |
236 | ||
237 | * install_prereq: Add GMime 3.0. | |
238 | ||
239 | Ubuntu 20.10 does not come with GMime 2.6. Ubuntu 16.04 LTS does not | |
240 | come with GMime 3.0. aptitude ignores any missing package. Therefore, | |
241 | it installs the correct package(s). However, in Ubuntu 18.04 LTS and | |
242 | Ubuntu 20.04 LTS, both versions are installed alongside although only | |
243 | one is really needed. | |
244 | ||
245 | Change-Id: Ic58aa9f2e131d94671f286f17dbd61e1ccbabcb7 | |
246 | ||
247 | 2020-10-23 09:49 +0000 [fb721ce82c] Alexander Traud <pabstraud@compuserve.com> | |
248 | ||
249 | * BuildSystem: Enable Lua 5.4. | |
250 | ||
251 | Note to maintainers: Lua 5.4, Lua 5.3, and Lua 5.2 have not been tested | |
252 | at runtime with pbx_lua. Until then, use the lowest available version | |
253 | of Lua, if you enabled the module pbx_lua at all. | |
254 | ||
255 | Change-Id: Ie5270448b11fcb4e2a53d899e4fe7fea793ce7e0 | |
256 | ||
257 | 2020-10-22 11:21 +0000 [e326b133dc] Sean Bright <sean.bright@gmail.com> | |
258 | ||
259 | * features.conf.sample: Sample sound files incorrectly quoted | |
260 | ||
261 | ASTERISK-29136 #close | |
262 | ||
263 | Change-Id: I3186536d65a50014c8da4780c9224919caa81440 | |
264 | ||
265 | 2020-10-19 13:06 +0000 [69356a7895] Asterisk Development Team <asteriskteam@digium.com> | |
266 | ||
267 | * Update CHANGES and UPGRADE.txt for 16.14.0 | |
268 | 2020-10-12 00:45 +0000 [606bd35060] Andrew Siplas <andrew@asiplas.net> | |
269 | ||
270 | * logger.conf.sample: add missing comment mark | |
271 | ||
272 | Add missing comment mark from stock configuration. | |
273 | ||
274 | ASTERISK-29123 #close | |
275 | ||
276 | Change-Id: I4f94eb4544166bca8af4c17fd11edee3c6980620 | |
277 | ||
278 | 2020-08-28 16:32 +0000 [e051806e80] Kevin Harwell <kharwell@digium.com> | |
279 | ||
280 | * Logging: Add debug logging categories | |
281 | ||
282 | Added debug logging categories that allow a user to output debug | |
283 | information based on a specified category. This lets the user limit, | |
284 | and filter debug output to data relevant to a particular context, | |
285 | or topic. For instance the following categories are now available for | |
286 | debug logging purposes: | |
287 | ||
288 | dtls, dtls_packet, ice, rtcp, rtcp_packet, rtp, rtp_packet, | |
289 | stun, stun_packet | |
290 | ||
291 | These debug categories can be enable/disable via an Asterisk CLI command. | |
292 | ||
293 | While this overrides, and outputs debug data, core system debugging is | |
294 | not affected by this patch. Statements still output at their appropriate | |
295 | debug level. As well backwards compatibility has been maintained with | |
296 | past debug groups that could be enabled using the CLI (e.g. rtpdebug, | |
297 | stundebug, etc.). | |
298 | ||
299 | ASTERISK-29054 #close | |
300 | ||
301 | Change-Id: I6e6cb247bb1f01dbf34750b2cd98e5b5b41a1849 | |
302 | (cherry picked from commit 56028426de0692e8e36167251053c91b96e97c41) | |
303 | ||
304 | 2020-10-05 10:44 +0000 [0b835f2156] Jean Aunis <jean.aunis@prescom.fr> | |
305 | ||
306 | * resource_endpoints.c: memory leak when providing a 404 response | |
307 | ||
308 | When handling a send_message request to a non-existing endpoint, the response's | |
309 | body is overriden and not properly freed. | |
310 | ||
311 | ASTERISK-29108 | |
312 | ||
313 | Change-Id: Ie1d3d70065f80793445b60f5e4a7eb31b4b9c5c8 | |
314 | ||
315 | 2020-09-30 15:00 +0000 [d0313d8b12] Sean Bright <sean.bright@gmail.com> | |
316 | ||
317 | * tcptls.c: Don't close TCP client file descriptors more than once | |
318 | ||
319 | ASTERISK-28430 #close | |
320 | ||
321 | Change-Id: Ib556b0a0c95cca939e956886214ec8d828d89606 | |
322 | ||
323 | 2020-08-04 14:36 +0000 [681a1624b5] Ben Ford <bford@digium.com> | |
324 | ||
325 | * utils.c: NULL terminate ast_base64decode_string. | |
326 | ||
327 | With the addition of STIR/SHAKEN, the function ast_base64decode_string | |
328 | was added for convenience since there is a lot of converting done during | |
329 | the STIR/SHAKEN process. This function returned the decoded string for | |
330 | you, but did not NULL terminate it, causing some issues (specifically | |
331 | with MALLOC_DEBUG). Now, the returned string is NULL terminated, and the | |
332 | documentation has been updated to reflect this. | |
333 | ||
334 | Change-Id: Icdd7d05b323b0c47ff6ed43492937a03641bdcf5 | |
335 | ||
336 | 2020-07-20 13:05 +0000 [df7c4ed0ed] Ben Ford <bford@digium.com> | |
337 | ||
338 | * res_stir_shaken: Fix memory allocation error in curl.c | |
339 | ||
340 | Fixed a memory allocation that was not passing in the correct size for | |
341 | the struct in curl.c. | |
342 | ||
343 | Change-Id: I5fb92fbbe84b075fa6aefa2423786df80e114c3a | |
344 | ||
345 | 2020-06-24 11:49 +0000 [21ab0a450b] Ben Ford <bford@digium.com> | |
346 | ||
347 | * res_stir_shaken: Add stir_shaken option and general improvements. | |
348 | ||
349 | Added a new configuration option for PJSIP endpoints - stir_shaken. If | |
350 | set to yes, then STIR/SHAKEN support will be added to inbound and | |
351 | outbound INVITEs. The default is no. Alembic has been updated to include | |
352 | this option. | |
353 | ||
354 | Previously the dialplan function was not trimming the whitespace from | |
355 | the parameters it recieved. Now it does. | |
356 | ||
357 | Also added a conditional that, when TEST_FRAMEWORK is enabled, the | |
358 | timestamp in the identity header will be overlooked. This is just for | |
359 | testing, since the testsuite will rely on a SIPp scenario with a preset | |
360 | identity header to trigger the MISMATCH result. | |
361 | ||
362 | Change-Id: I43d67f1489b8c1c5729ed3ca8d71e35ddf438df1 | |
363 | ||
364 | 2020-06-02 09:04 +0000 [d979bdf87a] Ben Ford <bford@digium.com> | |
365 | ||
366 | * res_stir_shaken: Add outbound INVITE support. | |
367 | ||
368 | Integrated STIR/SHAKEN support with outgoing INVITEs. When an INVITE is | |
369 | sent, the caller ID will be checked to see if there is a certificate | |
370 | that corresponds to it. If so, that information will be retrieved and an | |
371 | Identity header will be added to the SIP message. The format is: | |
372 | ||
373 | header.payload.signature;info=<public_key_url>alg=ES256;ppt=shaken | |
374 | ||
375 | Header, payload, and signature are all BASE64 encoded. The public key | |
376 | URL is retrieved from the certificate. Currently the algorithm and ppt | |
377 | are ES256 and shaken, respectively. This message is signed and can be | |
378 | used for verification on the receiving end. | |
379 | ||
380 | Two new configuration options have been added to the certificate object: | |
381 | attestation and origid. The attestation is required and must be A, B, or | |
382 | C. origid is the origination identifier. | |
383 | ||
384 | A new utility function has been added as well that takes a string, | |
385 | allocates space, BASE64 encodes it, then returns it, eliminating the | |
386 | need to calculate the size yourself. | |
387 | ||
388 | Change-Id: I1f84d6a5839cb2ed152ef4255b380cfc2de662b4 | |
389 | ||
390 | 2020-05-19 14:46 +0000 [746ce16b16] Ben Ford <bford@digium.com> | |
391 | ||
392 | * res_stir_shaken: Add inbound INVITE support. | |
393 | ||
394 | Integrated STIR/SHAKEN support with incoming INVITES. Upon receiving an | |
395 | INVITE, the Identity header is retrieved, parsing the message to verify | |
396 | the signature. If any of the parsing fails, | |
397 | AST_STIR_SHAKEN_VERIFY_NOT_PRESENT will be added to the channel for this | |
398 | caller ID. If verification itself fails, | |
399 | AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED will be added. If anything in | |
400 | the payload does not line up with the SIP signaling, | |
401 | AST_STIR_SHAKEN_VERIFY_MISMATCH will be added. If all of the above steps | |
402 | pass, then AST_STIR_SHAKEN_VERIFY_PASSED will be added, completing the | |
403 | verification process. | |
404 | ||
405 | A new config option has been added to the general section for | |
406 | stir_shaken.conf. "signature_timeout" is the amount of time a signature | |
407 | will be considered valid. If an INVITE is received and the amount of | |
408 | time between when it was received and when it was signed is greater than | |
409 | signature_timeout, verification will fail. | |
410 | ||
411 | Some changes were also made to signing and verification. There was an | |
412 | error where the whole JSON string was being signed rather than the | |
413 | header combined with the payload. This has been changed to sign the | |
414 | correct thing. Verification has been changed to do this as well, and the | |
415 | unit tests have been updated to reflect these changes. | |
416 | ||
417 | A couple of utility functions have also been added. One decodes a BASE64 | |
418 | string and returns the decoded string, doing all the length calculations | |
419 | for you. The other retrieves a string value from a header in a rdata | |
420 | object. | |
421 | ||
422 | Change-Id: I855f857be3d1c63b64812ac35d9ce0534085b913 | |
423 | ||
424 | 2020-05-13 16:37 +0000 [9d7628829c] Ben Ford <bford@digium.com> | |
425 | ||
426 | * res_stir_shaken: Add unit tests for signing and verification. | |
427 | ||
428 | Added two unit tests, one for signing and another for verifying. | |
429 | stir_shaken_sign checks to make sure that all the required parameters | |
430 | are passed in and then signs the actual payload. If a signature is | |
431 | produced and a payload returned as a result, the test passes. | |
432 | stir_shaken_verify takes the signature from a signed payload to verify. | |
433 | This unit test also verifies that all the required information is passed | |
434 | in, and then attempts to verify the signature. If verification is | |
435 | successful and a payload is returned, the test passes. | |
436 | ||
437 | Change-Id: I9fa43380f861ccf710cd0f6b6c102a517c86ea13 | |
438 | ||
439 | 2020-05-04 16:11 +0000 [035b463c93] Ben Ford <bford@digium.com> | |
440 | ||
441 | * res_stir_shaken: Added dialplan function and API call. | |
442 | ||
443 | Adds the "STIR_SHAKEN" dialplan function and an API call to add a | |
444 | STIR_SHAKEN verification result to a channel. This information will be | |
445 | held in a datastore on the channel that can later be queried through the | |
446 | "STIR_SHAKEN" dialplan funtion to get information on STIR_SHAKEN results | |
447 | including identity, attestation, and verify_result. Here are some | |
448 | examples: | |
449 | ||
450 | STIR_SHAKEN(count) | |
451 | STIR_SHAKEN(0, identity) | |
452 | STIR_SHAKEN(1, attestation) | |
453 | STIR_SHAKEN(2, verify_result) | |
454 | ||
455 | Getting the count can be used to iterate through the results and pull | |
456 | information by specifying the index and the field you want to retrieve. | |
457 | ||
458 | Change-Id: Ice6d52a3a7d6e4607c9c35b28a1f7c25f5284a82 | |
459 | ||
460 | 2020-05-01 07:29 +0000 [0392a8e620] Joshua C. Colp <jcolp@sangoma.com> | |
461 | ||
462 | * res_stir_shaken: Use ast_asprintf for creating file path. | |
463 | ||
464 | Change-Id: Ice5d92ecea2f1101c80487484f48ef98be2f1824 | |
465 | ||
466 | 2020-04-15 13:15 +0000 [70af7e1311] Ben Ford <bford@digium.com> | |
467 | ||
468 | * res_stir_shaken: Implemented signature verification. | |
469 | ||
470 | There are a lot of moving parts in this patch, but the focus of it is on | |
471 | the verification of the signature using a public key located at the | |
472 | public key URL provided in the JSON payload. First, we check the | |
473 | database to see if we have already downloaded the key. If so, check to | |
474 | see if it has expired. If it has, redownload from the URL. If we don't | |
475 | have an entry in the database, just go ahead and download the public | |
476 | key. The expiration is tested each time we download the file. After | |
477 | that, read the public key from the file and use it to verify the | |
478 | signature. All sanity checking is done when the payload is first | |
479 | received, so the verification is complete once this point is reached. | |
480 | ||
481 | The XML has also been added since a new config option was added to | |
482 | general (curl_timeout). The maximum amount of time to wait for a | |
483 | download can be configured through this option, with a low value by | |
484 | default. | |
485 | ||
486 | Change-Id: I3ba4c63880493bf8c7d17a9cfca1af0e934d1a1c | |
487 | ||
488 | 2020-04-13 11:47 +0000 [971b125fc0] Alexander Traud <pabstraud@compuserve.com> | |
489 | ||
490 | * res_stir_shaken: Do not build without OpenSSL. | |
491 | ||
492 | Change-Id: Idba5151a3079f9dcc0076d635422c5df5845114f | |
493 | ||
494 | 2020-03-26 13:34 +0000 [e9ee9a381b] Ben Ford <bford@digium.com> | |
495 | ||
496 | * res_stir_shaken: Implemented signing of JSON payload. | |
497 | ||
498 | This change provides functions that take in a JSON payload, verify that | |
499 | the contents contain all the mandatory fields and required values (if | |
500 | any), and signs the payload with the private key. Four fields are added | |
501 | to the payload: x5u, attest, iat, and origid. As of now, these are just | |
502 | placeholder values that will be set to actual values once the logic is | |
503 | implemented for what to do when an actual payload is received, but the | |
504 | functions to add these values have all been implemented and are ready to | |
505 | use. Upon successful signing and the addition of those four values, a | |
506 | ast_stir_shaken_payload is returned, containing other useful information | |
507 | such as the algorithm and signature. | |
508 | ||
509 | Change-Id: I74fa41c0640ab2a64a1a80110155bd7062f13393 | |
510 | ||
511 | 2020-03-23 15:00 +0000 [716e51a3f3] Ben Ford <bford@digium.com> | |
512 | ||
513 | * res_stir_shaken: Initial commit and reading private key. | |
514 | ||
515 | This commit sets up some of the initial framework for the module and | |
516 | adds a way to read the private key from the specified file, which will | |
517 | then be appended to the certificate object. This works fine for now, but | |
518 | eventually some other structure will likely need to be used to store all | |
519 | this information. Similarly, the caller_id_number is specified on the | |
520 | certificate config object, but in the end we will want that information | |
521 | to be tied to the certificate itself and read it from there. | |
522 | ||
523 | A method has been added that will retrieve the private key associated | |
524 | with the caller_id_number passed in. Tab completion for certificates and | |
525 | stores has also been added. | |
526 | ||
527 | Change-Id: Ic4bc1416fab5d6afe15a8e2d32f7ddd4e023295f | |
528 | ||
529 | 2020-09-29 13:04 +0000 [7a64868118] Sean Bright <sean.bright@gmail.com> | |
530 | ||
531 | * pbx.c: On error, ast_add_extension2_lockopt should always free 'data' | |
532 | ||
533 | In the event that the desired extension already exists, | |
534 | ast_add_extension2_lockopt() will free the 'data' it is passed before | |
535 | returning an error, so we should not be freeing it ourselves. | |
536 | ||
537 | Additionally, there were two places where ast_add_extension2_lockopt() | |
538 | could return an error without also freeing the 'data' pointer, so we | |
539 | add that. | |
540 | ||
541 | ASTERISK-29097 #close | |
542 | ||
543 | Change-Id: I904707aae55169feda050a5ed7c6793b53fe6eae | |
544 | ||
545 | 2020-09-29 19:57 +0000 [9ac933fbba] Sean Bright <sean.bright@gmail.com> | |
546 | ||
547 | * app_voicemail.c: Document VMSayName interruption behavior | |
548 | ||
549 | ASTERISK-26424 #close | |
550 | ||
551 | Change-Id: I797ad0ed302d0b3d2c90543eff5b7207ed08ecf0 | |
552 | ||
553 | 2020-09-24 13:46 +0000 [3b0a53f257] George Joseph <gjoseph@digium.com> | |
554 | ||
555 | * app_confbridge/bridge_softmix: Add ability to force estimated bitrate | |
556 | ||
557 | app_confbridge now has the ability to set the estimated bitrate on an | |
558 | SFU bridge. To use it, set a bridge profile's remb_behavior to "force" | |
559 | and set remb_estimated_bitrate to a rate in bits per second. The | |
560 | remb_estimated_bitrate parameter is ignored if remb_behavior is something | |
561 | other than "force". | |
562 | ||
563 | Change-Id: Idce6464ff014a37ea3b82944452e56cc4d75ab0a | |
564 | ||
565 | 2020-09-28 07:42 +0000 [374d18cb97] lvl <digium@lvlconsultancy.nl> | |
566 | ||
567 | * res_musiconhold: Load all realtime entries, not just the first | |
568 | ||
569 | ASTERISK-29099 | |
570 | ||
571 | Change-Id: I45636679c0fb5a5f59114c8741626631a604e8a6 | |
572 | ||
573 | 2020-09-22 22:39 +0000 [cd793c7c81] Holger Hans Peter Freyther <holger@moiji-mobile.com> | |
574 | ||
575 | * res_pjsip_sdp_rtp: Fix accidentally native bridging calls | |
576 | ||
577 | Stop advertising RFC2833 support on the rtp_engine when DTMF mode is | |
578 | auto but no tel_event was found inside SDP file. | |
579 | ||
580 | On an incoming call create_rtp will be called and when session->dtmf is | |
581 | set to AST_SIP_DTMF_AUTO, the AST_RTP_PROPERTY_DTMF will be set without | |
582 | looking at the SDP file. | |
583 | ||
584 | Once get_codecs gets called we move the DTMF mode from RFC2833 to INBAND | |
585 | but continued to advertise RFC2833 support. | |
586 | ||
587 | This meant the native_rtp bridge would falsely consider the two channels | |
588 | as compatible. In addition to changing the DTMF mode we now set or | |
589 | remove the AST_RTP_PROPERTY_DTMF. | |
590 | ||
591 | The property is checked in ast_rtp_dtmf_compatible and called by | |
592 | native_rtp_bridge_compatible. | |
593 | ||
594 | ASTERISK-29051 #close | |
595 | ||
596 | Change-Id: I1e0c1e324598a437932c0b7836bcb626aba8e287 | |
597 | ||
598 | 2020-09-23 04:05 +0000 [efcc6d6f6b] Jasper van der Neut <jasper@isotopic.nl> | |
599 | ||
600 | * channels: Don't dereference NULL pointer | |
601 | ||
602 | Check result of ast_translator_build_path against NULL before dereferencing. | |
603 | ||
604 | ASTERISK-29091 | |
605 | ||
606 | Change-Id: Ia3538ea190bd371f70c9dd49984b021765691b29 | |
607 | ||
608 | 2020-09-24 09:54 +0000 [14b483dd5e] Torrey Searle <tsearle@voxbone.com> | |
609 | ||
610 | * res_pjsip_diversion: fix double 181 | |
611 | ||
612 | Arming response to both AST_SIP_SESSION_BEFORE_REDIRECTING and | |
613 | AST_SIP_SESSION_BEFORE_MEDIA causes 302 to to be handled twice, | |
614 | resulting in to 181 being generated. | |
615 | ||
616 | Change-Id: I866e5461564644ffb8a5e12b6f1330b50a7b63ab | |
617 | ||
618 | 2020-09-24 11:47 +0000 [fccf360fcb] Sean Bright <sean.bright@gmail.com> | |
619 | ||
620 | * res_musiconhold: Clarify that playlist mode only supports HTTP(S) URLs | |
621 | ||
622 | Change-Id: I41e77a04e4a523f4ed61a7a20b738ffd42be441e | |
623 | ||
624 | 2020-09-23 15:20 +0000 [cba132a797] Sean Bright <sean.bright@gmail.com> | |
625 | ||
626 | * dsp.c: Update calls to ast_format_cmp to check result properly | |
627 | ||
628 | ASTERISK-28311 #close | |
629 | ||
630 | Change-Id: Ib1ce8fc1a8752751f5bf3615c59245532dfd9aa2 | |
631 | ||
632 | 2020-09-22 05:05 +0000 [baa6e8f112] Joshua C. Colp <jcolp@sangoma.com> | |
633 | ||
634 | * res_pjsip_session: Fix stream name memory leak. | |
635 | ||
636 | When constructing a stream name based on the media type | |
637 | and position the allocated name was not being freed | |
638 | causing a leak. | |
639 | ||
640 | Change-Id: I52510863b24a2f531f0a55b440bb2c81844029de | |
641 | ||
642 | 2020-09-18 08:09 +0000 [799426cd58] Sean Bright <sean.bright@gmail.com> | |
643 | ||
644 | * func_curl.c: Prevent crash when using CURLOPT(httpheader) | |
645 | ||
646 | Because we use shared thread-local cURL instances, we need to ensure | |
647 | that the state of the cURL instance is correct before each invocation. | |
648 | ||
649 | In the case of custom headers, we were not resetting cURL's internal | |
650 | HTTP header pointer which could result in a crash if subsequent | |
651 | requests do not configure custom headers. | |
652 | ||
653 | ASTERISK-29085 #close | |
654 | ||
655 | Change-Id: I8b4ab34038156dfba613030a45f10e932d2e992d | |
656 | ||
657 | 2020-09-18 15:02 +0000 [4a7bbac0ed] Sean Bright <sean.bright@gmail.com> | |
658 | ||
659 | * res_musiconhold: Start playlist after initial announcement | |
660 | ||
661 | Only track our sample offset if we are playing a non-announcement file, | |
662 | otherwise we will skip that number of samples when we start playing the | |
663 | first MoH file. | |
664 | ||
665 | ASTERISK-24329 #close | |
666 | ||
667 | Change-Id: Ib6b3c84fcaa1063889ab38ba7e7fc50050a3ccfc | |
668 | ||
669 | 2020-09-22 05:13 +0000 [e6ed74347c] Joshua C. Colp <jcolp@sangoma.com> | |
670 | ||
671 | * res_pjsip_session: Fix session reference leak. | |
672 | ||
673 | The ast_sip_dialog_get_session function returns the session | |
674 | with reference count increased. This was not taken into | |
675 | account and was causing sessions to remain around when they | |
676 | should not be. | |
677 | ||
678 | ASTERISK-29089 | |
679 | ||
680 | Change-Id: I430fa721b0a824311a59effec6056e9ec528e3e8 | |
681 | ||
682 | 2020-09-16 08:01 +0000 [f7285140b4] Michal Hajek <michal.hajek@daktela.com> | |
683 | ||
684 | * res_stasis.c: Add compare function for bridges moh container | |
685 | ||
686 | Sometimes not play MOH on bridge. | |
687 | ||
688 | ASTERISK-29081 | |
689 | Reported-by: Michal Hajek <michal.hajek@daktela.com> | |
690 | ||
691 | Change-Id: I760c73e0c9be1d340303b5d1c18a00c4759e8232 | |
692 | ||
693 | 2020-09-17 11:40 +0000 [fb6f2157e7] George Joseph <gjoseph@digium.com> | |
694 | ||
695 | * logger.h: Fix ast_trace to respect scope_level | |
696 | ||
697 | ast_trace() was always emitting messages when it's level was set to -1 | |
698 | because it was ignoring scope_level. | |
699 | ||
700 | Change-Id: I849c8f4f4613899c37f82be0202024e7d117e506 | |
701 | ||
702 | 2020-09-17 13:01 +0000 [8d9633074e] George Joseph <gjoseph@digium.com> | |
703 | ||
704 | * bridge_softmix/sfu_topologies_on_join: Ignore topology change failures | |
705 | ||
706 | When a channel joins a bridge, we do topology change requests on all | |
707 | existing channels to add the new participant to them. However the | |
708 | announcer channel will return an error because it doesn't support | |
709 | topology in the first place. Unfortunately, there doesn't seem to be a | |
710 | reliable way to tell if the error is expected or not so the error is | |
711 | ignored for all channels. If the request fails on a "real" channel, | |
712 | that channel just won't get the new participant's video. | |
713 | ||
714 | Change-Id: Ic95db4683f27d224c1869fe887795d6b9fdea4f0 | |
715 | ||
716 | 2020-09-15 16:16 +0000 [9458577f68] Sean Bright <sean.bright@gmail.com> | |
717 | ||
718 | * res_pjsip_session.c: Fix build when TEST_FRAMEWORK is not defined | |
719 | ||
720 | Change-Id: Id4852c26e9c412af8e37b5dd3c15da9453ad3276 | |
721 | ||
722 | 2020-08-13 03:34 +0000 [5a12463c07] Torrey Searle <tsearle@voxbone.com> | |
723 | ||
724 | * res_pjsip_diversion: implement support for History-Info | |
725 | ||
726 | Implemention of History-Info capable of interworking with Diversion | |
727 | Header following RFC7544 | |
728 | ||
729 | ASTERISK-29027 #close | |
730 | ||
731 | Change-Id: I2296369582d4b295c5ea1e60bec391dd1d318fa6 | |
732 | ||
733 | 2020-09-14 13:23 +0000 [c9cc281484] Sean Bright <sean.bright@gmail.com> | |
734 | ||
735 | * format_cap: Perform codec lookups by pointer instead of name | |
736 | ||
737 | ASTERISK-28416 #close | |
738 | ||
739 | Change-Id: I069420875ebdbcaada52d92599a5f7de3cb2cdf4 | |
740 | ||
741 | 2020-09-11 11:09 +0000 [df429c97a1] George Joseph <gjoseph@digium.com> | |
742 | ||
743 | * res_pjsip_session: Fix issue with COLP and 491 | |
744 | ||
745 | The recent 491 changes introduced a check to determine if the active | |
746 | and pending topologies were equal and to suppress the re-invite if they | |
747 | were. When a re-invite is sent for a COLP-only change, the pending | |
748 | topology is NULL so that check doesn't happen and the re-invite is | |
749 | correctly sent. Of course, sending the re-invite sets the pending | |
750 | topology. If a 491 is received, when we resend the re-invite, the | |
751 | pending topology is set and since we didn't request a change to the | |
752 | topology in the first place, pending and active topologies are equal so | |
753 | the topologies-equal check causes the re-invite to be erroneously | |
754 | suppressed. | |
755 | ||
756 | This change checks if the topologies are equal before we run the media | |
757 | state resolver (which recreates the pending topology) so that when we | |
758 | do the final topologies-equal check we know if this was a topology | |
759 | change request. If it wasn't a change request, we don't suppress | |
760 | the re-invite even though the topologies are equal. | |
761 | ||
762 | ASTERISK-29014 | |
763 | ||
764 | Change-Id: Iffd7dd0500301156a566119ebde528d1a9573314 | |
765 | ||
766 | 2020-08-20 15:09 +0000 [6abf6f345d] George Joseph <gjoseph@digium.com> | |
767 | ||
768 | * debugging: Add enough to choke a mule | |
769 | ||
770 | Added to: | |
771 | * bridges/bridge_softmix.c | |
772 | * channels/chan_pjsip.c | |
773 | * include/asterisk/res_pjsip_session.h | |
774 | * main/channel.c | |
775 | * res/res_pjsip_session.c | |
776 | ||
777 | There NO functional changes in this commit. | |
778 | ||
779 | Change-Id: I06af034d1ff3ea1feb56596fd7bd6d7939dfdcc3 | |
780 | ||
781 | 2020-08-20 11:21 +0000 [65088494cb] George Joseph <gjoseph@digium.com> | |
782 | ||
783 | * res_pjsip_session: Handle multi-stream re-invites better | |
784 | ||
785 | When both Asterisk and a UA send re-invites at the same time, both | |
786 | send 491 "Transaction in progress" responses to each other and back | |
787 | off a specified amount of time before retrying. When Asterisk | |
788 | prepares to send its re-invite, it sets up the session's pending | |
789 | media state with the new topology it wants, then sends the | |
790 | re-invite. Unfortunately, when it received the re-invite from the | |
791 | UA, it partially processed the media in the re-invite and reset | |
792 | the pending media state before sending the 491 losing the state it | |
793 | set in its own re-invite. | |
794 | ||
795 | Asterisk also was not tracking re-invites received while an existing | |
796 | re-invite was queued resulting in sending stale SDP with missing | |
797 | or duplicated streams, or no re-invite at all because we erroneously | |
798 | determined that a re-invite wasn't needed. | |
799 | ||
800 | There was also an issue in bridge_softmix where we were using a stream | |
801 | from the wrong topology to determine if a stream was added. This also | |
802 | caused us to erroneously determine that a re-invite wasn't needed. | |
803 | ||
804 | Regardless of how the delayed re-invite was triggered, we need to | |
805 | reconcile the topology that was active at the time the delayed | |
806 | request was queued, the pending topology of the queued request, | |
807 | and the topology currently active on the session. To do this we | |
808 | need a topology resolver AND we need to make stream named unique | |
809 | so we can accurately tell what a stream has been added or removed | |
810 | and if we can re-use a slot in the topology. | |
811 | ||
812 | Summary of changes: | |
813 | ||
814 | * bridge_softmix: | |
815 | * We no longer reset the stream name to "removed" in | |
816 | remove_all_original_streams(). That was causing multiple streams | |
817 | to have the same name and wrecked the checks for duplicate streams. | |
818 | ||
819 | * softmix_bridge_stream_sources_update() was checking the old_stream | |
820 | to see if it had the softmix prefix and not considering the stream | |
821 | as "new" if it did. If the stream in that slot has something in it | |
822 | because another re-invite happened, then that slot in old might | |
823 | have a softmix stream but the same stream in new might actually | |
824 | be a new one. Now we check the new_stream's name instead of | |
825 | the old_stream's. | |
826 | ||
827 | * stream: | |
828 | * Instead of using plain media type name ("audio", "video", etc) as | |
829 | the default stream name, we now append the stream position to it | |
830 | to make it unique. We need to do this so we can distinguish multiple | |
831 | streams of the same type from each other. | |
832 | ||
833 | * When we set a stream's state to REMOVED, we no longer reset its | |
834 | name to "removed" or destroy its metadata. Again, we need to | |
835 | do this so we can distinguish multiple streams of the same | |
836 | type from each other. | |
837 | ||
838 | * res_pjsip_session: | |
839 | * Added resolve_refresh_media_states() that takes in 3 media states | |
840 | and creates an up-to-date pending media state that includes the changes | |
841 | that might have happened while a delayed session refresh was in the | |
842 | delayed queue. | |
843 | ||
844 | * Added is_media_state_valid() that checks the consistency of | |
845 | a media state and returns a true/false value. A valid state has: | |
846 | * The same number of stream entries as media session entries. | |
847 | Some media session entries can be NULL however. | |
848 | * No duplicate streams. | |
849 | * A valid stream for each non-NULL media session. | |
850 | * A stream that matches each media session's stream_num | |
851 | and media type. | |
852 | ||
853 | * Updated handle_incoming_sdp() to set the stream name to include the | |
854 | stream position number in the name to make it unique. | |
855 | ||
856 | * Updated the ast_sip_session_delayed_request structure to include both | |
857 | the pending and active media states and updated the associated delay | |
858 | functions to process them. | |
859 | ||
860 | * Updated sip_session_refresh() to accept both the pending and active | |
861 | media states that were in effect when the request was originally queued | |
862 | and to pass them on should the request need to be delayed again. | |
863 | ||
864 | * Updated sip_session_refresh() to call resolve_refresh_media_states() | |
865 | and substitute its results for the pending state passed in. | |
866 | ||
867 | * Updated sip_session_refresh() with additional debugging. | |
868 | ||
869 | * Updated session_reinvite_on_rx_request() to simply return PJ_FALSE | |
870 | to pjproject if a transaction is in progress. This stops us from | |
871 | creating a partial pending media state that would be invalid later on. | |
872 | ||
873 | * Updated reschedule_reinvite() to clone both the current pending and | |
874 | active media states and pass them to delay_request() so the resolver | |
875 | can tell what the original intention of the re-invite was. | |
876 | ||
877 | * Added a large unit test for the resolver. | |
878 | ||
879 | ASTERISK-29014 | |
880 | ||
881 | Change-Id: Id3440972943c611a15f652c6c569fa0e4536bfcb | |
882 | ||
883 | 2020-08-30 15:42 +0000 [af339d0adb] Sungtae Kim <pchero21@gmail.com> | |
884 | ||
885 | * res_stasis.c: Added video_single option for bridge creation | |
886 | ||
887 | Currently, it was not possible to create bridge with video_mode single. | |
888 | This made hard to put the bridge in a vidoe_single mode. | |
889 | So, added video_single option for Bridge creation using the ARI. | |
890 | This allows create a bridge with video_mode single. | |
891 | ||
892 | ASTERISK-29055 | |
893 | ||
894 | Change-Id: I43e720e5c83fc75fafe10fe22808ae7f055da2ae | |
895 | ||
896 | 2020-08-31 07:21 +0000 [a353b76c75] Sungtae Kim <pchero21@gmail.com> | |
897 | ||
898 | * realtime: Increased reg_server character size | |
899 | ||
900 | Currently, the ps_contacts table's reg_server column in realtime database type is varchar(20). | |
901 | This is fine for normal cases, but if the hostname is longer than 20, it returns error and then | |
902 | failed to register the contact address of the peer. | |
903 | ||
904 | Normally, 20 characters limitation for the hostname is fine, but with the cloud env. | |
905 | So, increased the size to 255. | |
906 | ||
907 | ASTERISK-29056 | |
908 | ||
909 | Change-Id: Iac52c8c35030303cfa551bb39f410b33bffc507d | |
910 | ||
911 | 2020-08-31 11:14 +0000 [e7620d034a] Ben Ford <bford@digium.com> | |
912 | ||
913 | * Bridging: Use a ref to bridge_channel's channel to prevent crash. | |
914 | ||
915 | There's a race condition with bridging where a bridge can be torn down | |
916 | causing the bridge_channel's ast_channel to become NULL when it's still | |
917 | needed. This particular case happened with attended transfers, but the | |
918 | crash occurred when trying to publish a stasis message. Now, the | |
919 | bridge_channel is locked, a ref to the ast_channel is obtained, and that | |
920 | ref is passed down the chain. | |
921 | ||
922 | Change-Id: Ic48715c0c041615d17d286790ae3e8c61bb28814 | |
3 | 923 | |
4 | 924 | 2020-09-09 15:01 +0000 Asterisk Development Team <asteriskteam@digium.com> |
5 | 925 |
564 | 564 | "$(ASTDATADIR)/firmware/iax" "$(ASTDATADIR)/images" "$(ASTDATADIR)/keys" \ |
565 | 565 | "$(ASTDATADIR)/phoneprov" "$(ASTDATADIR)/rest-api" "$(ASTDATADIR)/static-http" \ |
566 | 566 | "$(ASTDATADIR)/sounds" "$(ASTDATADIR)/moh" "$(ASTMANDIR)/man8" "$(AGI_DIR)" "$(ASTDBDIR)" \ |
567 | "$(ASTDATADIR)/third-party" | |
567 | "$(ASTDATADIR)/third-party" "${ASTDATADIR}/keys/stir_shaken" | |
568 | 568 | |
569 | 569 | installdirs: |
570 | 570 | @for i in $(INSTALLDIRS); do \ |
16 | 16 | === suggested replacement functionality. |
17 | 17 | === |
18 | 18 | =========================================================== |
19 | ||
20 | ------------------------------------------------------------------------------ | |
21 | --- Functionality changes from Asterisk 16.13.0 to Asterisk 16.14.0 ---------- | |
22 | ------------------------------------------------------------------------------ | |
23 | ||
24 | res_stir_shaken | |
25 | ------------------ | |
26 | * A new directory has been added under the default (e.g., /var/lib/asterisk) - | |
27 | inside the 'keys' directory - named 'stir_shaken'. This directory will | |
28 | hold public keys that have been downloaded for STIR/SHAKEN verification. | |
19 | 29 | |
20 | 30 | ------------------------------------------------------------------------------ |
21 | 31 | --- Functionality changes from Asterisk 16.9.0 to Asterisk 16.10.0 ----------- |
584 | 584 | unsigned int i; |
585 | 585 | ASN1BOOL aligned=TRUE; |
586 | 586 | int stat; |
587 | Q931InformationElement *ie; | |
587 | Q931InformationElement *ie=NULL; | |
588 | 588 | /* OOCTXT *pctxt = &gH323ep.msgctxt; */ |
589 | 589 | if(q931Msg ==NULL) |
590 | 590 | { |
1729 | 1729 | ast_brige_set_remb_behavior(conference->bridge, AST_BRIDGE_VIDEO_SFU_REMB_LOWEST_ALL); |
1730 | 1730 | } else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_HIGHEST_ALL)) { |
1731 | 1731 | ast_brige_set_remb_behavior(conference->bridge, AST_BRIDGE_VIDEO_SFU_REMB_HIGHEST_ALL); |
1732 | } else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_FORCE)) { | |
1733 | ast_brige_set_remb_behavior(conference->bridge, AST_BRIDGE_VIDEO_SFU_REMB_FORCE); | |
1734 | ast_bridge_set_remb_estimated_bitrate(conference->bridge, conference->b_profile.remb_estimated_bitrate); | |
1732 | 1735 | } |
1733 | 1736 | } |
1734 | 1737 |
395 | 395 | { |
396 | 396 | struct directory_item **block, *item; |
397 | 397 | int i, limit, res = 0; |
398 | char buf[9]; | |
398 | char buf[7+12]; /* INT_MIN has a length of 12 chars */ | |
399 | 399 | |
400 | 400 | /* option p(n): cellphone pause option */ |
401 | 401 | select_item_pause(chan, flags, opts); |
379 | 379 | <description> |
380 | 380 | <para>This application will say the recorded name of the voicemail user specified as the |
381 | 381 | argument to this application. If no context is provided, <literal>default</literal> is assumed.</para> |
382 | <para>Similar to the Background() application, playback of the recorded | |
383 | name can be interrupted by entering an extension, which will be searched | |
384 | for in the current context.</para> | |
382 | 385 | </description> |
383 | 386 | </application> |
384 | 387 | <function name="MAILBOX_EXISTS" language="en_US"> |
11299 | 11302 | int skipuser, int max_logins, int silent) |
11300 | 11303 | { |
11301 | 11304 | int useadsi = 0, valid = 0, logretries = 0; |
11302 | char password[AST_MAX_EXTENSION], *passptr; | |
11305 | char password[AST_MAX_EXTENSION], *passptr = NULL; | |
11303 | 11306 | struct ast_vm_user vmus, *vmu = NULL; |
11304 | 11307 | |
11305 | 11308 | /* If ADSI is supported, setup login screen */ |
532 | 532 | <para>The highest estimated maximum bitrate of all receivers in the bridge |
533 | 533 | is taken and sent to each sender.</para> |
534 | 534 | </enum> |
535 | <enum name="force"> | |
536 | <para>The bitrate configured in <literal>remb_estimated_bitrate</literal> | |
537 | is sent to each sender.</para> | |
538 | </enum> | |
535 | 539 | </enumlist> |
536 | 540 | </description> |
541 | <see-also><ref type="configOption">remb_estimated_bitrate</ref></see-also> | |
542 | </configOption> | |
543 | <configOption name="remb_estimated_bitrate"> | |
544 | <synopsis>Sets the estimated bitrate sent to each participant in REMB reports</synopsis> | |
545 | <description><para> | |
546 | When <literal>remb_behavior</literal> is set to <literal>force</literal>, | |
547 | this options sets the estimated bitrate (in bits per second) sent to each participant | |
548 | in REMB reports. | |
549 | </para></description> | |
550 | <see-also><ref type="configOption">remb_behavior</ref></see-also> | |
537 | 551 | </configOption> |
538 | 552 | <configOption name="enable_events" default="no"> |
539 | 553 | <synopsis>Enables events for this bridge</synopsis> |
2158 | 2172 | BRIDGE_OPT_REMB_BEHAVIOR_HIGHEST | |
2159 | 2173 | BRIDGE_OPT_REMB_BEHAVIOR_AVERAGE_ALL | |
2160 | 2174 | BRIDGE_OPT_REMB_BEHAVIOR_LOWEST_ALL | |
2161 | BRIDGE_OPT_REMB_BEHAVIOR_HIGHEST_ALL); | |
2175 | BRIDGE_OPT_REMB_BEHAVIOR_HIGHEST_ALL | | |
2176 | BRIDGE_OPT_REMB_BEHAVIOR_FORCE | |
2177 | ); | |
2162 | 2178 | |
2163 | 2179 | if (!strcasecmp(var->value, "average")) { |
2164 | 2180 | ast_set_flag(b_profile, BRIDGE_OPT_REMB_BEHAVIOR_AVERAGE); |
2172 | 2188 | ast_set_flag(b_profile, BRIDGE_OPT_REMB_BEHAVIOR_LOWEST_ALL); |
2173 | 2189 | } else if (!strcasecmp(var->value, "highest_all")) { |
2174 | 2190 | ast_set_flag(b_profile, BRIDGE_OPT_REMB_BEHAVIOR_HIGHEST_ALL); |
2191 | } else if (!strcasecmp(var->value, "force")) { | |
2192 | ast_set_flag(b_profile, BRIDGE_OPT_REMB_BEHAVIOR_FORCE); | |
2175 | 2193 | } else { |
2176 | 2194 | return -1; |
2177 | 2195 | } |
2418 | 2436 | aco_option_register(&cfg_info, "video_update_discard", ACO_EXACT, bridge_types, "2000", OPT_UINT_T, 0, FLDSET(struct bridge_profile, video_update_discard)); |
2419 | 2437 | aco_option_register(&cfg_info, "remb_send_interval", ACO_EXACT, bridge_types, "0", OPT_UINT_T, 0, FLDSET(struct bridge_profile, remb_send_interval)); |
2420 | 2438 | aco_option_register_custom(&cfg_info, "remb_behavior", ACO_EXACT, bridge_types, "average", remb_behavior_handler, 0); |
2439 | aco_option_register(&cfg_info, "remb_estimated_bitrate", ACO_EXACT, bridge_types, "0", OPT_UINT_T, 0, FLDSET(struct bridge_profile, remb_estimated_bitrate)); | |
2421 | 2440 | aco_option_register(&cfg_info, "enable_events", ACO_EXACT, bridge_types, "no", OPT_BOOLFLAG_T, 1, FLDSET(struct bridge_profile, flags), BRIDGE_OPT_ENABLE_EVENTS); |
2422 | 2441 | /* This option should only be used with the CONFBRIDGE dialplan function */ |
2423 | 2442 | aco_option_register_custom(&cfg_info, "template", ACO_EXACT, bridge_types, NULL, bridge_template_handler, 0); |
86 | 86 | BRIDGE_OPT_REMB_BEHAVIOR_AVERAGE_ALL = (1 << 12), /*!< The average of all REMB reports in the entire bridge is sent to each sender */ |
87 | 87 | BRIDGE_OPT_REMB_BEHAVIOR_LOWEST_ALL = (1 << 13), /*!< The lowest estimated maximum bitrate from all receivers is sent to each sender */ |
88 | 88 | BRIDGE_OPT_REMB_BEHAVIOR_HIGHEST_ALL = (1 << 14), /*!< The highest estimated maximum bitrate from all receivers is sent to each sender */ |
89 | BRIDGE_OPT_REMB_BEHAVIOR_FORCE = (1 << 15), /*!< Force the REMB estimated bitrate to that specifiec in remb_estimated_bitrate */ | |
89 | 90 | }; |
90 | 91 | |
91 | 92 | enum conf_menu_action_id { |
234 | 235 | char regcontext[AST_MAX_CONTEXT]; |
235 | 236 | unsigned int video_update_discard; /*!< Amount of time after sending a video update request that subsequent requests should be discarded */ |
236 | 237 | unsigned int remb_send_interval; /*!< Interval at which a combined REMB frame is sent to video sources */ |
238 | unsigned int remb_estimated_bitrate; /*!< Bitrate sent when BRIDGE_OPT_REMB_BEHAVIOR_FORCE is set */ | |
237 | 239 | }; |
238 | 240 | |
239 | 241 | /*! \brief The structure that represents a conference bridge */ |
0 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><title>Release Summary - asterisk-16.14.0</title><h1 align="center"><a name="top">Release Summary</a></h1><h3 align="center">asterisk-16.14.0</h3><h3 align="center">Date: 2020-10-19</h3><h3 align="center"><asteriskteam@digium.com></h3><hr><h2 align="center">Table of Contents</h2><ol> | |
1 | <li><a href="#summary">Summary</a></li> | |
2 | <li><a href="#contributors">Contributors</a></li> | |
3 | <li><a href="#closed_issues">Closed Issues</a></li> | |
4 | <li><a href="#commits">Other Changes</a></li> | |
5 | <li><a href="#diffstat">Diffstat</a></li> | |
6 | </ol><hr><a name="summary"><h2 align="center">Summary</h2></a><center><a href="#top">[Back to Top]</a></center><p>This release is a point release of an existing major version. The changes included were made to address problems that have been identified in this release series, or are minor, backwards compatible new features or improvements. Users should be able to safely upgrade to this version if this release series is already in use. Users considering upgrading from a previous version are strongly encouraged to review the UPGRADE.txt document as well as the CHANGES document for information about upgrading to this release series.</p><p>The data in this summary reflects changes that have been made since the previous release, asterisk-16.13.0.</p><hr><a name="contributors"><h2 align="center">Contributors</h2></a><center><a href="#top">[Back to Top]</a></center><p>This table lists the people who have submitted code, those that have tested patches, as well as those that reported issues on the issue tracker that were resolved in this release. For coders, the number is how many of their patches (of any size) were committed into this release. For testers, the number is the number of times their name was listed as assisting with testing a patch. Finally, for reporters, the number is the number of issues that they reported that were affected by commits that went into this release.</p><table width="100%" border="0"> | |
7 | <tr><th width="33%">Coders</th><th width="33%">Testers</th><th width="33%">Reporters</th></tr> | |
8 | <tr valign="top"><td width="33%">4 Sean Bright <sean.bright@gmail.com><br/>4 George Joseph <gjoseph@digium.com><br/>3 Joshua C. Colp <jcolp@sangoma.com><br/>2 Kevin Harwell <kharwell@digium.com><br/>2 Asterisk Development Team <asteriskteam@digium.com><br/>2 Alexander Traud <pabstraud@compuserve.com><br/>1 Kfir Itzhak <mastertheknife@gmail.com><br/>1 Evandro César Arruda <ecarruda@gmail.com><br/>1 Torrey Searle <tsearle@voxbone.com><br/>1 Patrick Verzele <patrick@verzele.be><br/>1 Nickolay Shmyrev <nshmyrev@alphacephei.com><br/>1 Andrew Siplas <andrew@asiplas.net><br/></td><td width="33%"><td width="33%">1 Nickolay V. Shmyrev <nshmyrev@alphacephei.com><br/>1 Ramarajan <pramarajan@sangoma.com><br/>1 Ove Aursand <oveaurs@gmail.com><br/>1 Evandro César Arruda <ecarruda@gmail.com><br/>1 Thomas Johnson <tjohnson@microautomation.com><br/>1 Misha Vodsedalek <vmisha@seznam.cz><br/>1 Andrew Siplas <andrew@asiplas.net><br/>1 Torrey Searle <tsearle@gmail.com><br/>1 Kfir Itzhak <mastertheknife@gmail.com><br/>1 Joshua C. Colp <jcolp@digium.com><br/>1 Karsten Wemheuer <kwe-digium@iptam.com><br/>1 Joseph Ades <josephades1@gmail.com><br/>1 Leandro Dardini <ldardini@gmail.com><br/></td></tr> | |
9 | </table><hr><a name="closed_issues"><h2 align="center">Closed Issues</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a list of all issues from the issue tracker that were closed by changes that went into this release.</p><h3>Bug</h3><h4>Category: Applications/app_queue</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-25665">ASTERISK-25665</a>: Duplicate logging in queue log for EXITEMPTY events<br/>Reported by: Ove Aursand<ul> | |
10 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=ae718c975af775511295093776f0c0a3d83b521d">[ae718c975a]</a> Kfir Itzhak -- app_queue: Fix leave-empty not recording a call as abandoned</li> | |
11 | </ul><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29043">ASTERISK-29043</a>: app_queue: Leave empty sometimes not recorded as abandoned<br/>Reported by: Kfir Itzhak<ul> | |
12 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=ae718c975af775511295093776f0c0a3d83b521d">[ae718c975a]</a> Kfir Itzhak -- app_queue: Fix leave-empty not recording a call as abandoned</li> | |
13 | </ul><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29034">ASTERISK-29034</a>: Lastpause of realtime members is reseting<br/>Reported by: Evandro César Arruda<ul> | |
14 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=aa93107444fc41c09c7a34620fdd7b86b90eb57a">[aa93107444]</a> Evandro César Arruda -- app_queue: Member lastpause time reseting</li> | |
15 | </ul><br><h4>Category: Applications/app_voicemail</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29029">ASTERISK-29029</a>: Voicemail "pollmailboxes"-option not working, bug in function handle_subscribe<br/>Reported by: Karsten Wemheuer<ul> | |
16 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=53a88df076023510d49d8f0818e24a8ec5d5dece">[53a88df076]</a> Sean Bright -- app_voicemail: Fix pollmailboxes</li> | |
17 | </ul><a href="https://issues.asterisk.org/jira/browse/ASTERISK-27273">ASTERISK-27273</a>: app_voicemail: When a voicemail is marked as "Urgent", it is not sent by email/processed by the mailcmd command<br/>Reported by: Leandro Dardini<ul> | |
18 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=788b60d935cb910f7417611416c54be3c8d49517">[788b60d935]</a> Sean Bright -- app_voicemail: Process urgent messages with mailcmd</li> | |
19 | </ul><br><h4>Category: Channels/chan_pjsip</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-28878">ASTERISK-28878</a>: chan_pjsip: PJSIP_MEDIA_OFFER Broken asterisk 16<br/>Reported by: Joseph Ades<ul> | |
20 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=17258e0cdf2a0b8c3747a63b2fe469a3e1b3544b">[17258e0cdf]</a> Kevin Harwell -- chan_pjsip: disallow PJSIP_SEND_SESSION_REFRESH pre-answer execution</li> | |
21 | </ul><br><h4>Category: Configs/Samples</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29123">ASTERISK-29123</a>: logger.conf.sample missing comment mark on line 115<br/>Reported by: Andrew Siplas<ul> | |
22 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=8ac6ae48ceb1c1c9f6cf8b36c58a2b8c1ab7a2de">[8ac6ae48ce]</a> Andrew Siplas -- logger.conf.sample: add missing comment mark</li> | |
23 | </ul><br><h4>Category: PBX/General</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29046">ASTERISK-29046</a>: pbx: Deadlock when doing a reload, while simultaneously doing an ExtensionState on a pattern match hint that ends up adding an extension<br/>Reported by: Ramarajan<ul> | |
24 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=04527176458b0183d440891ec31770abedece71c">[0452717645]</a> Joshua C. Colp -- pbx: Fix hints deadlock between reload and ExtensionState.</li> | |
25 | </ul><br><h4>Category: Resources/res_parking</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29042">ASTERISK-29042</a>: res_parking: Parker UUID is no longer copied<br/>Reported by: Misha Vodsedalek<ul> | |
26 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=075a7ee4a6b29afbfc9190b4abbfdd1983a75706">[075a7ee4a6]</a> Joshua C. Colp -- parking: Copy parker UUID as well.</li> | |
27 | </ul><br><h4>Category: Resources/res_pjsip_diversion</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29001">ASTERISK-29001</a>: chan_pjsip does not process or forward 181 responses<br/>Reported by: Torrey Searle<ul> | |
28 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=75756ab850891318aa76607fb612f49879ce2b55">[75756ab850]</a> Torrey Searle -- res_pjsip_diversion: handle 181</li> | |
29 | </ul><br><h4>Category: Resources/res_pjsip_session</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29033">ASTERISK-29033</a>: res_pjsip_session: Aggressively terminates session on failed re-INVITE<br/>Reported by: Joshua C. Colp<ul> | |
30 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=eaed23b340ab4dd15b25319878200ba2832c2660">[eaed23b340]</a> Joshua C. Colp -- res_pjsip_session: Don't aggressively terminate on failed re-INVITE.</li> | |
31 | </ul><br><h4>Category: Resources/res_rtp_asterisk</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-28974">ASTERISK-28974</a>: res_rtp_asterisk: T.140 messages have appended RTP string to each message block.<br/>Reported by: Thomas Johnson<ul> | |
32 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=32b593c3251b5c2ae5a62814ae082915138f2606">[32b593c325]</a> Sean Bright -- bridge_channel: Ensure text messages are zero terminated</li> | |
33 | </ul><br><h4>Category: Resources/res_speech</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29040">ASTERISK-29040</a>: res_speech: Assertion on format<br/>Reported by: Nickolay V. Shmyrev<ul> | |
34 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=444c6061617d32a4b744bebe3e9d67ccaedfeb9c">[444c606161]</a> Nickolay Shmyrev -- res_speech: Bump reference on format object</li> | |
35 | </ul><br><hr><a name="commits"><h2 align="center">Commits Not Associated with an Issue</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a list of all changes that went into this release that did not reference a JIRA issue.</p><table width="100%" border="1"> | |
36 | <tr><th>Revision</th><th>Author</th><th>Summary</th></tr> | |
37 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=c1a5bbe7bf084721b2e2e52cdfc68513fda609ab">c1a5bbe7bf</a></td><td>Asterisk Development Team</td><td>Update for 16.14.0-rc1</td></tr> | |
38 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=bd0724c7ed8a7c3bec9ec4320780c50a48e13590">bd0724c7ed</a></td><td>Asterisk Development Team</td><td>Update CHANGES and UPGRADE.txt for 16.14.0</td></tr> | |
39 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=ab34417f7ea9cd818a7b801b41da0c534b71c988">ab34417f7e</a></td><td>Patrick Verzele</td><td>res_pjsip_session: Deferred re-INVITE without SDP send a=sendrecv instead of a=sendonly</td></tr> | |
40 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=7b441505dcdb0b4d7a030adea3eae628e11ab80e">7b441505dc</a></td><td>Kevin Harwell</td><td>conversions: Add string to signed integer conversion functions</td></tr> | |
41 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=a14c5301e738c442013511c2e904c15891e1a023">a14c5301e7</a></td><td>George Joseph</td><td>ast_coredumper: Fix issues with naming</td></tr> | |
42 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=afc5cf8d677686fb567464e2ddb21f639a95953c">afc5cf8d67</a></td><td>Alexander Traud</td><td>samples: Fix keep_alive_interval default in pjsip.conf.</td></tr> | |
43 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=0fa20c8df6cccabfbb4724777ae2b4fd725836f8">0fa20c8df6</a></td><td>Alexander Traud</td><td>sip_nat_settings: Update script for latest Linux.</td></tr> | |
44 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=d28a44c33d1e72b91b78ba56282bf56a0730a277">d28a44c33d</a></td><td>George Joseph</td><td>logger.c: Added a new log formatter called "plain"</td></tr> | |
45 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=55358b127692aadbecfb400bc2665aeb871f043b">55358b1276</a></td><td>Sean Bright</td><td>res_musiconhold.c: Use ast_file_read_dir to scan MoH directory</td></tr> | |
46 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=b9ba0f8da8a37d065a07ad060ebbb94e324f281e">b9ba0f8da8</a></td><td>George Joseph</td><td>scope_trace: Added debug messages and added additional macros</td></tr> | |
47 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=377caaed3cf2fd2d2aea9f2c18eb6f87a612f5ff">377caaed3c</a></td><td>George Joseph</td><td>stream.c: Added 2 more debugging utils and added pos to stream string</td></tr> | |
48 | </table><hr><a name="diffstat"><h2 align="center">Diffstat Results</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a summary of the changes to the source code that went into this release that was generated using the diffstat utility.</p><pre>asterisk-16.13.0-summary.html | 77 ---------- | |
49 | asterisk-16.13.0-summary.txt | 248 ---------------------------------- | |
50 | b/.version | 2 | |
51 | b/CHANGES | 22 +++ | |
52 | b/ChangeLog | 229 ++++++++++++++++++++++++++++++- | |
53 | b/apps/app_queue.c | 3 | |
54 | b/apps/app_voicemail.c | 30 +--- | |
55 | b/asterisk-16.14.0-rc1-summary.html | 78 ++++++++++ | |
56 | b/asterisk-16.14.0-rc1-summary.txt | 242 +++++++++++++++++++++++++++++++++ | |
57 | b/channels/pjsip/dialplan_functions.c | 5 | |
58 | b/configs/samples/logger.conf.sample | 4 | |
59 | b/configs/samples/pjsip.conf.sample | 10 - | |
60 | b/contrib/scripts/ast_coredumper | 4 | |
61 | b/contrib/scripts/sip_nat_settings | 19 -- | |
62 | b/include/asterisk/conversions.h | 54 +++++++ | |
63 | b/include/asterisk/frame.h | 5 | |
64 | b/include/asterisk/logger.h | 86 ++++++++++- | |
65 | b/include/asterisk/stream.h | 26 +++ | |
66 | b/main/bridge_channel.c | 37 ++++- | |
67 | b/main/conversions.c | 51 ++++++ | |
68 | b/main/logger.c | 52 +++++++ | |
69 | b/main/stream.c | 27 +-- | |
70 | b/res/res_musiconhold.c | 131 +++++++++-------- | |
71 | b/res/res_pjsip_diversion.c | 6 | |
72 | b/res/res_pjsip_session.c | 13 + | |
73 | b/res/res_speech.c | 7 | |
74 | b/tests/test_conversions.c | 146 +++++++++++++++++++- | |
75 | 27 files changed, 1147 insertions(+), 467 deletions(-)</pre><br></html>⏎ |
0 | Release Summary | |
1 | ||
2 | asterisk-16.14.0 | |
3 | ||
4 | Date: 2020-10-19 | |
5 | ||
6 | <asteriskteam@digium.com> | |
7 | ||
8 | ---------------------------------------------------------------------- | |
9 | ||
10 | Table of Contents | |
11 | ||
12 | 1. Summary | |
13 | 2. Contributors | |
14 | 3. Closed Issues | |
15 | 4. Other Changes | |
16 | 5. Diffstat | |
17 | ||
18 | ---------------------------------------------------------------------- | |
19 | ||
20 | Summary | |
21 | ||
22 | [Back to Top] | |
23 | ||
24 | This release is a point release of an existing major version. The changes | |
25 | included were made to address problems that have been identified in this | |
26 | release series, or are minor, backwards compatible new features or | |
27 | improvements. Users should be able to safely upgrade to this version if | |
28 | this release series is already in use. Users considering upgrading from a | |
29 | previous version are strongly encouraged to review the UPGRADE.txt | |
30 | document as well as the CHANGES document for information about upgrading | |
31 | to this release series. | |
32 | ||
33 | The data in this summary reflects changes that have been made since the | |
34 | previous release, asterisk-16.13.0. | |
35 | ||
36 | ---------------------------------------------------------------------- | |
37 | ||
38 | Contributors | |
39 | ||
40 | [Back to Top] | |
41 | ||
42 | This table lists the people who have submitted code, those that have | |
43 | tested patches, as well as those that reported issues on the issue tracker | |
44 | that were resolved in this release. For coders, the number is how many of | |
45 | their patches (of any size) were committed into this release. For testers, | |
46 | the number is the number of times their name was listed as assisting with | |
47 | testing a patch. Finally, for reporters, the number is the number of | |
48 | issues that they reported that were affected by commits that went into | |
49 | this release. | |
50 | ||
51 | Coders Testers Reporters | |
52 | 4 Sean Bright 1 Nickolay V. Shmyrev | |
53 | 4 George Joseph 1 Ramarajan | |
54 | 3 Joshua C. Colp 1 Ove Aursand | |
55 | 2 Kevin Harwell 1 Evandro César Arruda | |
56 | 2 Asterisk Development Team 1 Thomas Johnson | |
57 | 2 Alexander Traud 1 Misha Vodsedalek | |
58 | 1 Kfir Itzhak 1 Andrew Siplas | |
59 | 1 Evandro César Arruda 1 Torrey Searle | |
60 | 1 Torrey Searle 1 Kfir Itzhak | |
61 | 1 Patrick Verzele 1 Joshua C. Colp | |
62 | 1 Nickolay Shmyrev 1 Karsten Wemheuer | |
63 | 1 Andrew Siplas 1 Joseph Ades | |
64 | 1 Leandro Dardini | |
65 | ||
66 | ---------------------------------------------------------------------- | |
67 | ||
68 | Closed Issues | |
69 | ||
70 | [Back to Top] | |
71 | ||
72 | This is a list of all issues from the issue tracker that were closed by | |
73 | changes that went into this release. | |
74 | ||
75 | Bug | |
76 | ||
77 | Category: Applications/app_queue | |
78 | ||
79 | ASTERISK-25665: Duplicate logging in queue log for EXITEMPTY events | |
80 | Reported by: Ove Aursand | |
81 | * [ae718c975a] Kfir Itzhak -- app_queue: Fix leave-empty not recording a | |
82 | call as abandoned | |
83 | ASTERISK-29043: app_queue: Leave empty sometimes not recorded as abandoned | |
84 | Reported by: Kfir Itzhak | |
85 | * [ae718c975a] Kfir Itzhak -- app_queue: Fix leave-empty not recording a | |
86 | call as abandoned | |
87 | ASTERISK-29034: Lastpause of realtime members is reseting | |
88 | Reported by: Evandro César Arruda | |
89 | * [aa93107444] Evandro César Arruda -- app_queue: Member lastpause time | |
90 | reseting | |
91 | ||
92 | Category: Applications/app_voicemail | |
93 | ||
94 | ASTERISK-29029: Voicemail "pollmailboxes"-option not working, bug in | |
95 | function handle_subscribe | |
96 | Reported by: Karsten Wemheuer | |
97 | * [53a88df076] Sean Bright -- app_voicemail: Fix pollmailboxes | |
98 | ASTERISK-27273: app_voicemail: When a voicemail is marked as "Urgent", it | |
99 | is not sent by email/processed by the mailcmd command | |
100 | Reported by: Leandro Dardini | |
101 | * [788b60d935] Sean Bright -- app_voicemail: Process urgent messages | |
102 | with mailcmd | |
103 | ||
104 | Category: Channels/chan_pjsip | |
105 | ||
106 | ASTERISK-28878: chan_pjsip: PJSIP_MEDIA_OFFER Broken asterisk 16 | |
107 | Reported by: Joseph Ades | |
108 | * [17258e0cdf] Kevin Harwell -- chan_pjsip: disallow | |
109 | PJSIP_SEND_SESSION_REFRESH pre-answer execution | |
110 | ||
111 | Category: Configs/Samples | |
112 | ||
113 | ASTERISK-29123: logger.conf.sample missing comment mark on line 115 | |
114 | Reported by: Andrew Siplas | |
115 | * [8ac6ae48ce] Andrew Siplas -- logger.conf.sample: add missing comment | |
116 | mark | |
117 | ||
118 | Category: PBX/General | |
119 | ||
120 | ASTERISK-29046: pbx: Deadlock when doing a reload, while simultaneously | |
121 | doing an ExtensionState on a pattern match hint that ends up adding an | |
122 | extension | |
123 | Reported by: Ramarajan | |
124 | * [0452717645] Joshua C. Colp -- pbx: Fix hints deadlock between reload | |
125 | and ExtensionState. | |
126 | ||
127 | Category: Resources/res_parking | |
128 | ||
129 | ASTERISK-29042: res_parking: Parker UUID is no longer copied | |
130 | Reported by: Misha Vodsedalek | |
131 | * [075a7ee4a6] Joshua C. Colp -- parking: Copy parker UUID as well. | |
132 | ||
133 | Category: Resources/res_pjsip_diversion | |
134 | ||
135 | ASTERISK-29001: chan_pjsip does not process or forward 181 responses | |
136 | Reported by: Torrey Searle | |
137 | * [75756ab850] Torrey Searle -- res_pjsip_diversion: handle 181 | |
138 | ||
139 | Category: Resources/res_pjsip_session | |
140 | ||
141 | ASTERISK-29033: res_pjsip_session: Aggressively terminates session on | |
142 | failed re-INVITE | |
143 | Reported by: Joshua C. Colp | |
144 | * [eaed23b340] Joshua C. Colp -- res_pjsip_session: Don't aggressively | |
145 | terminate on failed re-INVITE. | |
146 | ||
147 | Category: Resources/res_rtp_asterisk | |
148 | ||
149 | ASTERISK-28974: res_rtp_asterisk: T.140 messages have appended RTP string | |
150 | to each message block. | |
151 | Reported by: Thomas Johnson | |
152 | * [32b593c325] Sean Bright -- bridge_channel: Ensure text messages are | |
153 | zero terminated | |
154 | ||
155 | Category: Resources/res_speech | |
156 | ||
157 | ASTERISK-29040: res_speech: Assertion on format | |
158 | Reported by: Nickolay V. Shmyrev | |
159 | * [444c606161] Nickolay Shmyrev -- res_speech: Bump reference on format | |
160 | object | |
161 | ||
162 | ---------------------------------------------------------------------- | |
163 | ||
164 | Commits Not Associated with an Issue | |
165 | ||
166 | [Back to Top] | |
167 | ||
168 | This is a list of all changes that went into this release that did not | |
169 | reference a JIRA issue. | |
170 | ||
171 | +------------------------------------------------------------------------+ | |
172 | | Revision | Author | Summary | | |
173 | |------------+----------------------+------------------------------------| | |
174 | | c1a5bbe7bf | Asterisk Development | Update for 16.14.0-rc1 | | |
175 | | | Team | | | |
176 | |------------+----------------------+------------------------------------| | |
177 | | bd0724c7ed | Asterisk Development | Update CHANGES and UPGRADE.txt for | | |
178 | | | Team | 16.14.0 | | |
179 | |------------+----------------------+------------------------------------| | |
180 | | | | res_pjsip_session: Deferred | | |
181 | | ab34417f7e | Patrick Verzele | re-INVITE without SDP send | | |
182 | | | | a=sendrecv instead of a=sendonly | | |
183 | |------------+----------------------+------------------------------------| | |
184 | | 7b441505dc | Kevin Harwell | conversions: Add string to signed | | |
185 | | | | integer conversion functions | | |
186 | |------------+----------------------+------------------------------------| | |
187 | | a14c5301e7 | George Joseph | ast_coredumper: Fix issues with | | |
188 | | | | naming | | |
189 | |------------+----------------------+------------------------------------| | |
190 | | afc5cf8d67 | Alexander Traud | samples: Fix keep_alive_interval | | |
191 | | | | default in pjsip.conf. | | |
192 | |------------+----------------------+------------------------------------| | |
193 | | 0fa20c8df6 | Alexander Traud | sip_nat_settings: Update script | | |
194 | | | | for latest Linux. | | |
195 | |------------+----------------------+------------------------------------| | |
196 | | d28a44c33d | George Joseph | logger.c: Added a new log | | |
197 | | | | formatter called "plain" | | |
198 | |------------+----------------------+------------------------------------| | |
199 | | | | res_musiconhold.c: Use | | |
200 | | 55358b1276 | Sean Bright | ast_file_read_dir to scan MoH | | |
201 | | | | directory | | |
202 | |------------+----------------------+------------------------------------| | |
203 | | b9ba0f8da8 | George Joseph | scope_trace: Added debug messages | | |
204 | | | | and added additional macros | | |
205 | |------------+----------------------+------------------------------------| | |
206 | | | | stream.c: Added 2 more debugging | | |
207 | | 377caaed3c | George Joseph | utils and added pos to stream | | |
208 | | | | string | | |
209 | +------------------------------------------------------------------------+ | |
210 | ||
211 | ---------------------------------------------------------------------- | |
212 | ||
213 | Diffstat Results | |
214 | ||
215 | [Back to Top] | |
216 | ||
217 | This is a summary of the changes to the source code that went into this | |
218 | release that was generated using the diffstat utility. | |
219 | ||
220 | asterisk-16.13.0-summary.html | 77 ---------- | |
221 | asterisk-16.13.0-summary.txt | 248 ---------------------------------- | |
222 | b/.version | 2 | |
223 | b/CHANGES | 22 +++ | |
224 | b/ChangeLog | 229 ++++++++++++++++++++++++++++++- | |
225 | b/apps/app_queue.c | 3 | |
226 | b/apps/app_voicemail.c | 30 +--- | |
227 | b/asterisk-16.14.0-rc1-summary.html | 78 ++++++++++ | |
228 | b/asterisk-16.14.0-rc1-summary.txt | 242 +++++++++++++++++++++++++++++++++ | |
229 | b/channels/pjsip/dialplan_functions.c | 5 | |
230 | b/configs/samples/logger.conf.sample | 4 | |
231 | b/configs/samples/pjsip.conf.sample | 10 - | |
232 | b/contrib/scripts/ast_coredumper | 4 | |
233 | b/contrib/scripts/sip_nat_settings | 19 -- | |
234 | b/include/asterisk/conversions.h | 54 +++++++ | |
235 | b/include/asterisk/frame.h | 5 | |
236 | b/include/asterisk/logger.h | 86 ++++++++++- | |
237 | b/include/asterisk/stream.h | 26 +++ | |
238 | b/main/bridge_channel.c | 37 ++++- | |
239 | b/main/conversions.c | 51 ++++++ | |
240 | b/main/logger.c | 52 +++++++ | |
241 | b/main/stream.c | 27 +-- | |
242 | b/res/res_musiconhold.c | 131 +++++++++-------- | |
243 | b/res/res_pjsip_diversion.c | 6 | |
244 | b/res/res_pjsip_session.c | 13 + | |
245 | b/res/res_speech.c | 7 | |
246 | b/tests/test_conversions.c | 146 +++++++++++++++++++- | |
247 | 27 files changed, 1147 insertions(+), 467 deletions(-) |
0 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><title>Release Summary - asterisk-16.15.0</title><h1 align="center"><a name="top">Release Summary</a></h1><h3 align="center">asterisk-16.15.0</h3><h3 align="center">Date: 2020-11-19</h3><h3 align="center"><asteriskteam@digium.com></h3><hr><h2 align="center">Table of Contents</h2><ol> | |
1 | <li><a href="#summary">Summary</a></li> | |
2 | <li><a href="#contributors">Contributors</a></li> | |
3 | <li><a href="#closed_issues">Closed Issues</a></li> | |
4 | <li><a href="#commits">Other Changes</a></li> | |
5 | <li><a href="#diffstat">Diffstat</a></li> | |
6 | </ol><hr><a name="summary"><h2 align="center">Summary</h2></a><center><a href="#top">[Back to Top]</a></center><p>This release is a point release of an existing major version. The changes included were made to address problems that have been identified in this release series, or are minor, backwards compatible new features or improvements. Users should be able to safely upgrade to this version if this release series is already in use. Users considering upgrading from a previous version are strongly encouraged to review the UPGRADE.txt document as well as the CHANGES document for information about upgrading to this release series.</p><p>The data in this summary reflects changes that have been made since the previous release, asterisk-16.14.0.</p><hr><a name="contributors"><h2 align="center">Contributors</h2></a><center><a href="#top">[Back to Top]</a></center><p>This table lists the people who have submitted code, those that have tested patches, as well as those that reported issues on the issue tracker that were resolved in this release. For coders, the number is how many of their patches (of any size) were committed into this release. For testers, the number is the number of times their name was listed as assisting with testing a patch. Finally, for reporters, the number is the number of issues that they reported that were affected by commits that went into this release.</p><table width="100%" border="0"> | |
7 | <tr><th width="33%">Coders</th><th width="33%">Testers</th><th width="33%">Reporters</th></tr> | |
8 | <tr valign="top"><td width="33%">12 Ben Ford <bford@digium.com><br/>11 Sean Bright <sean.bright@gmail.com><br/>9 Alexander Traud <pabstraud@compuserve.com><br/>8 George Joseph <gjoseph@digium.com><br/>3 Kevin Harwell <kharwell@digium.com><br/>3 Asterisk Development Team <asteriskteam@digium.com><br/>3 Joshua C. Colp <jcolp@sangoma.com><br/>2 Torrey Searle <tsearle@voxbone.com><br/>2 Sungtae Kim <pchero21@gmail.com><br/>1 Holger Hans Peter Freyther <holger@moiji-mobile.com><br/>1 Dovid Bender <dovid@telecurve.com><br/>1 Walter Doekes <walter+asterisk@wjd.nu><br/>1 Alexei Gradinari <alex2grad@gmail.com><br/>1 Jean Aunis <jean.aunis@prescom.fr><br/>1 Jasper van der Neut <jasper@isotopic.nl><br/>1 Michal Hajek <michal.hajek@daktela.com><br/>1 laszlovl <digium@lvlconsultancy.nl><br/>1 Andrew Siplas <andrew@asiplas.net><br/></td><td width="33%"><td width="33%">3 Alexander Traud <pabstraud@compuserve.com><br/>2 George Joseph <gjoseph@digium.com><br/>2 sungtae kim <pchero21@gmail.com><br/>2 Sebastian Damm <damm@sipgate.de><br/>1 Walter Doekes <walter+asterisk@wjd.nu><br/>1 周家建 <zhou_0611@163.com><br/>1 Andrew Siplas <andrew@asiplas.net><br/>1 Jean Aunis - Prescom <jean.aunis@prescom.fr><br/>1 Kevin Harwell <kharwell@digium.com><br/>1 dovid <dovi5988@dovid.net><br/>1 Brian J. Murrell <brian@interlinx.bc.ca><br/>1 Vieri <vieridipaola@gmail.com><br/>1 Walter Doekes<br/>1 Ross Beer <ross.beer@voicehost.co.uk><br/>1 Benjamin M. <mailinglist@perspectives.qc.ca><br/>1 Péter Juhász <peter.juhasz@comnica.com><br/>1 under <under@list.ru><br/>1 Thomas Frederiksen <tommer@nicesurprise.com><br/>1 laszlovl <digium@lvlconsultancy.nl><br/>1 Michael Newton <miken32@gmail.com><br/>1 Sandro Gauci <sandro@enablesecurity.com><br/>1 Dovid Bender<br/>1 Jasper van der Neut <jasper@isotopic.nl><br/>1 Torrey Searle <tsearle@gmail.com><br/>1 Michal Hajek <michal.hajek@daktela.com><br/>1 Hajek Michal <michal.hajek@daktela.com><br/>1 Eric Smith <abkowald@gmail.com><br/></td></tr> | |
9 | </table><hr><a name="closed_issues"><h2 align="center">Closed Issues</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a list of all issues from the issue tracker that were closed by changes that went into this release.</p><h3>Security</h3><h4>Category: pjproject/pjsip</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29057">ASTERISK-29057</a>: pjsip: Crash on call rejection during high load<br/>Reported by: Sandro Gauci<ul> | |
10 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=8973fe5cf3d683fe8113c11c9cc7f2eb413c0b89">[8973fe5cf3]</a> Kevin Harwell -- AST-2020-001 - res_pjsip: Return dialog locked and referenced</li> | |
11 | </ul><br><h3>New Feature</h3><h4>Category: Resources/res_pjsip_diversion</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29027">ASTERISK-29027</a>: Implement support for History-Info<br/>Reported by: Torrey Searle<ul> | |
12 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=5a12463c0709513514abd42a48039d62ca9a6155">[5a12463c07]</a> Torrey Searle -- res_pjsip_diversion: implement support for History-Info</li> | |
13 | </ul><br><h3>Bug</h3><h4>Category: . I did not set the category correctly.</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29146">ASTERISK-29146</a>: GCC Warnings: ‘%s’ directive argument is null.<br/>Reported by: Alexander Traud<ul> | |
14 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=334661601a8a5b7d937c54f0a5e7df6326f05f3b">[334661601a]</a> Alexander Traud -- Compiler fixes for GCC when printf %s is NULL</li> | |
15 | </ul><br><h4>Category: Applications/app_directory</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29144">ASTERISK-29144</a>: GCC Warnings with OPTIMIZE=-Og make<br/>Reported by: Alexander Traud<ul> | |
16 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=13b56c4be6034ecf33f480ead096a22e72ac66c8">[13b56c4be6]</a> Alexander Traud -- Compiler fixes for GCC with -Og</li> | |
17 | </ul><br><h4>Category: Applications/app_voicemail</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29144">ASTERISK-29144</a>: GCC Warnings with OPTIMIZE=-Og make<br/>Reported by: Alexander Traud<ul> | |
18 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=13b56c4be6034ecf33f480ead096a22e72ac66c8">[13b56c4be6]</a> Alexander Traud -- Compiler fixes for GCC with -Og</li> | |
19 | </ul><a href="https://issues.asterisk.org/jira/browse/ASTERISK-26424">ASTERISK-26424</a>: app_voicemail: Undocumented behavior from VMSayName<br/>Reported by: Eric Smith<ul> | |
20 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=9ac933fbbad7f28d281b8c3ed3f3b6d2f8b9b580">[9ac933fbba]</a> Sean Bright -- app_voicemail.c: Document VMSayName interruption behavior</li> | |
21 | </ul><br><h4>Category: Channels/General</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29144">ASTERISK-29144</a>: GCC Warnings with OPTIMIZE=-Og make<br/>Reported by: Alexander Traud<ul> | |
22 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=13b56c4be6034ecf33f480ead096a22e72ac66c8">[13b56c4be6]</a> Alexander Traud -- Compiler fixes for GCC with -Og</li> | |
23 | </ul><br><h4>Category: Configs/Samples</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29123">ASTERISK-29123</a>: logger.conf.sample missing comment mark on line 115<br/>Reported by: Andrew Siplas<ul> | |
24 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=606bd35060c7802de68498a644a7fd799ffeca0a">[606bd35060]</a> Andrew Siplas -- logger.conf.sample: add missing comment mark</li> | |
25 | </ul><br><h4>Category: Contrib/General</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29142">ASTERISK-29142</a>: sip_to_pjsip.py: doesn't read globbed includes<br/>Reported by: Michael Newton<ul> | |
26 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=e067d5c8fd1538fa32d67084ae6c7dad728d0828">[e067d5c8fd]</a> Sean Bright -- sip_to_pjsip.py: Handle #include globs and other fixes</li> | |
27 | </ul><br><h4>Category: Core/Channels</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29091">ASTERISK-29091</a>: Crash when ast_translator_build_path fails<br/>Reported by: Jasper van der Neut<ul> | |
28 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=efcc6d6f6b277e9f4be5470680e0a947bb42275a">[efcc6d6f6b]</a> Jasper van der Neut -- channels: Don't dereference NULL pointer</li> | |
29 | </ul><br><h4>Category: Core/General</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-28430">ASTERISK-28430</a>: res_rtp_asterisk.c: FRACK!, Failed assertion errno != EBADF<br/>Reported by: under<ul> | |
30 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=d0313d8b12bc1eb5efa8c73962272aa2e707d0e3">[d0313d8b12]</a> Sean Bright -- tcptls.c: Don't close TCP client file descriptors more than once</li> | |
31 | </ul><a href="https://issues.asterisk.org/jira/browse/ASTERISK-28311">ASTERISK-28311</a>: dsp: ast_dsp_silence_noise_with_energy wrong judgment of frame format<br/>Reported by: 周家建<ul> | |
32 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=cba132a7979986fa82a189776473b77084ba951f">[cba132a797]</a> Sean Bright -- dsp.c: Update calls to ast_format_cmp to check result properly</li> | |
33 | </ul><br><h4>Category: Core/RTP</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-28416">ASTERISK-28416</a>: Unable to get rtp codec payload code for slin<br/>Reported by: Brian J. Murrell<ul> | |
34 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=c9cc281484aec8c6a73e94d7f586b1c1bd2a0dff">[c9cc281484]</a> Sean Bright -- format_cap: Perform codec lookups by pointer instead of name</li> | |
35 | </ul><br><h4>Category: Documentation</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29136">ASTERISK-29136</a>: config: Sample features.conf incorrectly includes " around sound files<br/>Reported by: Benjamin M.<ul> | |
36 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=e326b133dc3613778f2e0b6a15b69c5719a06d63">[e326b133dc]</a> Sean Bright -- features.conf.sample: Sample sound files incorrectly quoted</li> | |
37 | </ul><a href="https://issues.asterisk.org/jira/browse/ASTERISK-26424">ASTERISK-26424</a>: app_voicemail: Undocumented behavior from VMSayName<br/>Reported by: Eric Smith<ul> | |
38 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=9ac933fbbad7f28d281b8c3ed3f3b6d2f8b9b580">[9ac933fbba]</a> Sean Bright -- app_voicemail.c: Document VMSayName interruption behavior</li> | |
39 | </ul><br><h4>Category: Functions/func_curl</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-28825">ASTERISK-28825</a>: Any curl response checks out as valid even if 404 is returned.<br/>Reported by: dovid<ul> | |
40 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=5046e1fb062dd07c0593f10aee078b74a19e307b">[5046e1fb06]</a> Dovid Bender -- func_curl.c: Allow user to set what return codes constitute a failure.</li> | |
41 | </ul><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29085">ASTERISK-29085</a>: func_curl: Segmentation fault when using CURL after setting httpheader CURLOPT<br/>Reported by: Péter Juhász<ul> | |
42 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=799426cd58fed60a4eee33a3b08ce52709a74670">[799426cd58]</a> Sean Bright -- func_curl.c: Prevent crash when using CURLOPT(httpheader)</li> | |
43 | </ul><br><h4>Category: Functions/func_odbc</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29144">ASTERISK-29144</a>: GCC Warnings with OPTIMIZE=-Og make<br/>Reported by: Alexander Traud<ul> | |
44 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=13b56c4be6034ecf33f480ead096a22e72ac66c8">[13b56c4be6]</a> Alexander Traud -- Compiler fixes for GCC with -Og</li> | |
45 | </ul><br><h4>Category: Resources/res_ari_endpoints</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29108">ASTERISK-29108</a>: resource_endpoints.c : Memory leak if endpoint not found<br/>Reported by: Jean Aunis - Prescom<ul> | |
46 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=0b835f21563c33f1aa0b3793e8617b2d8fc81de8">[0b835f2156]</a> Jean Aunis -- resource_endpoints.c: memory leak when providing a 404 response</li> | |
47 | </ul><br><h4>Category: Resources/res_musiconhold</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29099">ASTERISK-29099</a>: res_musiconhold: Realtime MOH only loads a single entry<br/>Reported by: laszlovl<ul> | |
48 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=374d18cb974cb106026f5d8d722e13c106da1458">[374d18cb97]</a> laszlovl -- res_musiconhold: Load all realtime entries, not just the first</li> | |
49 | </ul><a href="https://issues.asterisk.org/jira/browse/ASTERISK-24329">ASTERISK-24329</a>: Music On Hold announcement cuts intro of music the first time it is played<br/>Reported by: Thomas Frederiksen<ul> | |
50 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=4a7bbac0ed8b27ead4901e7bccebb8c14c7c6797">[4a7bbac0ed]</a> Sean Bright -- res_musiconhold: Start playlist after initial announcement</li> | |
51 | </ul><br><h4>Category: Resources/res_pjsip</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-28933">ASTERISK-28933</a>: res_pjsip.so fails to load when bundled pjproject is compiled without libssl<br/>Reported by: Walter Doekes<ul> | |
52 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=ddfb76a864fd8e0d29fe7c14db5b8f760e3662f5">[ddfb76a864]</a> Alexander Traud -- res_pjsip/config_transport: Load and run without OpenSSL.</li> | |
53 | </ul><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29013">ASTERISK-29013</a>: res_pjsip: Asterisk doesn't stop sending invites (with auth) on 407 replies<br/>Reported by: Sebastian Damm<ul> | |
54 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=58aa6a7057ca0bf8d62e0fe8c7a9b1a6bcf7cd9f">[58aa6a7057]</a> Ben Ford -- AST-2020-002 - res_pjsip: Stop sending INVITEs after challenge limit.</li> | |
55 | </ul><br><h4>Category: Resources/res_pjsip_authenticator_digest</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29013">ASTERISK-29013</a>: res_pjsip: Asterisk doesn't stop sending invites (with auth) on 407 replies<br/>Reported by: Sebastian Damm<ul> | |
56 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=58aa6a7057ca0bf8d62e0fe8c7a9b1a6bcf7cd9f">[58aa6a7057]</a> Ben Ford -- AST-2020-002 - res_pjsip: Stop sending INVITEs after challenge limit.</li> | |
57 | </ul><br><h4>Category: Resources/res_pjsip_config_wizard</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29097">ASTERISK-29097</a>: res_pjsip_config_wizard: Crash when freeing string when failing to add extension<br/>Reported by: Vieri<ul> | |
58 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=7a648681184e7b17cd2a28d98b79f903c04a418b">[7a64868118]</a> Sean Bright -- pbx.c: On error, ast_add_extension2_lockopt should always free 'data'</li> | |
59 | </ul><br><h4>Category: Resources/res_pjsip_sdp_rtp</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29051">ASTERISK-29051</a>: res_pjsip_sdp_rtp: Does not set correct values on RTP instance when "auto" DTMF is used<br/>Reported by: Sebastian Damm<ul> | |
60 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=cd793c7c81fa261014623b57e9d8e96b0dad23c1">[cd793c7c81]</a> Holger Hans Peter Freyther -- res_pjsip_sdp_rtp: Fix accidentally native bridging calls</li> | |
61 | </ul><br><h4>Category: Resources/res_pjsip_session</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29014">ASTERISK-29014</a>: res_pjsip_session: Re-INVITE collisions aren't handled correctly<br/>Reported by: George Joseph<ul> | |
62 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=df429c97a14e393798868760f63c3bbb1705b220">[df429c97a1]</a> George Joseph -- res_pjsip_session: Fix issue with COLP and 491</li> | |
63 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=65088494cb8bbc07ffb402cf2e8ac9078d17bb22">[65088494cb]</a> George Joseph -- res_pjsip_session: Handle multi-stream re-invites better</li> | |
64 | </ul><br><h4>Category: Resources/res_rtp_asterisk</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29089">ASTERISK-29089</a>: RTP Ports not cleared after hangup<br/>Reported by: Ross Beer<ul> | |
65 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=e6ed74347ce748361e92bb0341c47c0ec2d0065f">[e6ed74347c]</a> Joshua C. Colp -- res_pjsip_session: Fix session reference leak.</li> | |
66 | </ul><br><h4>Category: Resources/res_stasis</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29081">ASTERISK-29081</a>: res_stasis: Add compare function for bridges moh container<br/>Reported by: Hajek Michal<ul> | |
67 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=f7285140b418899b55551e203489f80e5a75566e">[f7285140b4]</a> Michal Hajek -- res_stasis.c: Add compare function for bridges moh container</li> | |
68 | </ul><br><h4>Category: Utilities/muted</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29145">ASTERISK-29145</a>: GCC Warnings with OPTIMIZE=-Os make<br/>Reported by: Alexander Traud<ul> | |
69 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=92ca48d54c33c52a40cbeaa637caaecdd98a5587">[92ca48d54c]</a> Alexander Traud -- Compiler fixes for GCC with -Os</li> | |
70 | </ul><br><h3>Improvement</h3><h4>Category: Core/Logging</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29054">ASTERISK-29054</a>: Logger: Add debug logging categories<br/>Reported by: Kevin Harwell<ul> | |
71 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=e051806e80be7ac1da55def1fb1ddc5b85d6fbec">[e051806e80]</a> Kevin Harwell -- Logging: Add debug logging categories</li> | |
72 | </ul><br><h4>Category: Resources/General</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29056">ASTERISK-29056</a>: Increase reg_server column size for ps_contacts table realtime<br/>Reported by: sungtae kim<ul> | |
73 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=a353b76c75fbac19d44cc5c4884f1a0144ad225b">[a353b76c75]</a> Sungtae Kim -- realtime: Increased reg_server character size</li> | |
74 | </ul><br><h4>Category: Resources/res_stasis</h4><a href="https://issues.asterisk.org/jira/browse/ASTERISK-29055">ASTERISK-29055</a>: Create a Bridge with video_single mode<br/>Reported by: sungtae kim<ul> | |
75 | <li><a href="https://code.asterisk.org/code/changelog/asterisk?cs=af339d0adb39e883fcabffd799c08e9c04de0c57">[af339d0adb]</a> Sungtae Kim -- res_stasis.c: Added video_single option for bridge creation</li> | |
76 | </ul><br><hr><a name="commits"><h2 align="center">Commits Not Associated with an Issue</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a list of all changes that went into this release that did not reference a JIRA issue.</p><table width="100%" border="1"> | |
77 | <tr><th>Revision</th><th>Author</th><th>Summary</th></tr> | |
78 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=fbe2e9b6871fd9047af8091175c34638b388dc6c">fbe2e9b687</a></td><td>Asterisk Development Team</td><td>Update for 16.15.0-rc1</td></tr> | |
79 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=3366956139de7a96d776385ea61211d7be985ff2">3366956139</a></td><td>Asterisk Development Team</td><td>Update CHANGES and UPGRADE.txt for 16.15.0</td></tr> | |
80 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=e1fd51cd2c183a3eb61803f52277d1a7d7a18c9c">e1fd51cd2c</a></td><td>George Joseph</td><td>res_pjsip_outbound_registration.c: Use our own scheduler and other stuff</td></tr> | |
81 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=80f116c1562d41b107506ebf332062a20282ca04">80f116c156</a></td><td>George Joseph</td><td>pjsip_scheduler.c: Add type ONESHOT and enhance cli show command</td></tr> | |
82 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=728cd55cde57d90f331e8395be517beeef812346">728cd55cde</a></td><td>Alexei Gradinari</td><td>sched: AST_SCHED_REPLACE_UNREF can lead to use after free of data</td></tr> | |
83 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=277aa0ced61f9fa2a3693ed5d59165d4a7425e05">277aa0ced6</a></td><td>Alexander Traud</td><td>res_stir_shaken: Include OpenSSL headers where used actually.</td></tr> | |
84 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=951ce0524d9ccadccc96c8bf68aa78f91fd8000a">951ce0524d</a></td><td>Alexander Traud</td><td>chan_sip: On authentication, pick MD5 for sure.</td></tr> | |
85 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=f98eed17c1ece6c4f18a91e730905ec1af5f8e87">f98eed17c1</a></td><td>Walter Doekes</td><td>main/say: Work around gcc 9 format-truncation false positive</td></tr> | |
86 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=92e1de458af79457db064d507bca28fe980f88ff">92e1de458a</a></td><td>Kevin Harwell</td><td>res_pjsip, res_pjsip_session: initialize local variables</td></tr> | |
87 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=65426f4312d93b3ca8410ea2dbdfd3674a95049f">65426f4312</a></td><td>Alexander Traud</td><td>install_prereq: Add GMime 3.0.</td></tr> | |
88 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=fb721ce82c6d40d9aea621561719899f68cf5d13">fb721ce82c</a></td><td>Alexander Traud</td><td>BuildSystem: Enable Lua 5.4.</td></tr> | |
89 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=69356a7895e96694f0993b637cff65261b9f8dea">69356a7895</a></td><td>Asterisk Development Team</td><td>Update CHANGES and UPGRADE.txt for 16.14.0</td></tr> | |
90 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=681a1624b578c7f0f798f0eb0c5e54d308343ae8">681a1624b5</a></td><td>Ben Ford</td><td>utils.c: NULL terminate ast_base64decode_string.</td></tr> | |
91 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=df7c4ed0eda1fcb44fe106661a62d88bd977fdd7">df7c4ed0ed</a></td><td>Ben Ford</td><td>res_stir_shaken: Fix memory allocation error in curl.c</td></tr> | |
92 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=21ab0a450be4d16f7c82f6e23ad75d6a343f7de3">21ab0a450b</a></td><td>Ben Ford</td><td>res_stir_shaken: Add stir_shaken option and general improvements.</td></tr> | |
93 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=d979bdf87a70a822942130b791456e81cfbdb9bc">d979bdf87a</a></td><td>Ben Ford</td><td>res_stir_shaken: Add outbound INVITE support.</td></tr> | |
94 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=746ce16b169fd133593b2c3dc3568fd29b4f041b">746ce16b16</a></td><td>Ben Ford</td><td>res_stir_shaken: Add inbound INVITE support.</td></tr> | |
95 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=9d7628829cef6be12dc8029c720ce5e18e4701f0">9d7628829c</a></td><td>Ben Ford</td><td>res_stir_shaken: Add unit tests for signing and verification.</td></tr> | |
96 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=035b463c931a36c15a87118bc3fab832e8ad79c5">035b463c93</a></td><td>Ben Ford</td><td>res_stir_shaken: Added dialplan function and API call.</td></tr> | |
97 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=0392a8e620aaa0cc2df9c3b15174ec0b2eaab6c4">0392a8e620</a></td><td>Joshua C. Colp</td><td>res_stir_shaken: Use ast_asprintf for creating file path.</td></tr> | |
98 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=70af7e13114da93109332c125210a3b274dc89b3">70af7e1311</a></td><td>Ben Ford</td><td>res_stir_shaken: Implemented signature verification.</td></tr> | |
99 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=971b125fc08b4066ea3f3704c732421eeda7cac4">971b125fc0</a></td><td>Alexander Traud</td><td>res_stir_shaken: Do not build without OpenSSL.</td></tr> | |
100 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=e9ee9a381b96e3ce16fcee88a1fa6ca2dc54bd78">e9ee9a381b</a></td><td>Ben Ford</td><td>res_stir_shaken: Implemented signing of JSON payload.</td></tr> | |
101 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=716e51a3f36cc6ce19c04937a5d83e3b98c2c05c">716e51a3f3</a></td><td>Ben Ford</td><td>res_stir_shaken: Initial commit and reading private key.</td></tr> | |
102 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=3b0a53f257c20f899e6a594d810424583b812755">3b0a53f257</a></td><td>George Joseph</td><td>app_confbridge/bridge_softmix: Add ability to force estimated bitrate</td></tr> | |
103 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=14b483dd5e40cd574a6ae896a62c183034cfeea2">14b483dd5e</a></td><td>Torrey Searle</td><td>res_pjsip_diversion: fix double 181</td></tr> | |
104 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=fccf360fcb90622ee07c86755b4d3e0fc9fa757f">fccf360fcb</a></td><td>Sean Bright</td><td>res_musiconhold: Clarify that playlist mode only supports HTTP(S) URLs</td></tr> | |
105 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=baa6e8f1127c95bc871d00d595d4a468ca3fd3a4">baa6e8f112</a></td><td>Joshua C. Colp</td><td>res_pjsip_session: Fix stream name memory leak.</td></tr> | |
106 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=fb6f2157e7e02759a0e1bb701f133621a5f77389">fb6f2157e7</a></td><td>George Joseph</td><td>logger.h: Fix ast_trace to respect scope_level</td></tr> | |
107 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=8d9633074eb5068124714c3d3f415a40aadad557">8d9633074e</a></td><td>George Joseph</td><td>bridge_softmix/sfu_topologies_on_join: Ignore topology change failures</td></tr> | |
108 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=9458577f680a513734cf56730a4957d47cc32544">9458577f68</a></td><td>Sean Bright</td><td>res_pjsip_session.c: Fix build when TEST_FRAMEWORK is not defined</td></tr> | |
109 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=6abf6f345dbd0510d8a217d16cc1819e4d2bf815">6abf6f345d</a></td><td>George Joseph</td><td>debugging: Add enough to choke a mule</td></tr> | |
110 | <tr><td><a href="https://code.asterisk.org/code/changelog/asterisk?cs=e7620d034a0df7b438a91b77ebaa7d38c6a426ba">e7620d034a</a></td><td>Ben Ford</td><td>Bridging: Use a ref to bridge_channel's channel to prevent crash.</td></tr> | |
111 | </table><hr><a name="diffstat"><h2 align="center">Diffstat Results</h2></a><center><a href="#top">[Back to Top]</a></center><p>This is a summary of the changes to the source code that went into this release that was generated using the diffstat utility.</p><pre>asterisk-16.14.0-summary.html | 76 | |
112 | asterisk-16.14.0-summary.txt | 248 - | |
113 | b/.version | 2 | |
114 | b/CHANGES | 51 | |
115 | b/ChangeLog | 920 ++++ | |
116 | b/Makefile | 2 | |
117 | b/UPGRADE.txt | 10 | |
118 | b/addons/ooh323c/src/ooq931.c | 2 | |
119 | b/apps/app_confbridge.c | 3 | |
120 | b/apps/app_directory.c | 2 | |
121 | b/apps/app_voicemail.c | 5 | |
122 | b/apps/confbridge/conf_config_parser.c | 21 | |
123 | b/apps/confbridge/include/confbridge.h | 2 | |
124 | b/asterisk-16.15.0-rc1-summary.html | 215 + | |
125 | b/asterisk-16.15.0-rc1-summary.txt | 542 ++ | |
126 | b/bridges/bridge_softmix.c | 153 | |
127 | b/channels/chan_iax2.c | 2 | |
128 | b/channels/chan_pjsip.c | 74 | |
129 | b/channels/chan_sip.c | 13 | |
130 | b/configs/samples/confbridge.conf.sample | 7 | |
131 | b/configs/samples/features.conf.sample | 4 | |
132 | b/configs/samples/musiconhold.conf.sample | 4 | |
133 | b/configs/samples/pjsip.conf.sample | 18 | |
134 | b/configs/samples/res_curl.conf.sample | 1 | |
135 | b/configs/samples/stir_shaken.conf.sample | 61 | |
136 | b/configure | 2 | |
137 | b/configure.ac | 2 | |
138 | b/contrib/ast-db-manage/config/versions/1ae0609b6646_increse_reg_server_size.py | 22 | |
139 | b/contrib/ast-db-manage/config/versions/61797b9fced6_add_stir_shaken.py | 31 | |
140 | b/contrib/ast-db-manage/config/versions/e658c26033ca_create_history_info_flag.py | 38 | |
141 | b/contrib/realtime/mysql/mysql_config.sql | 18 | |
142 | b/contrib/realtime/postgresql/postgresql_config.sql | 18 | |
143 | b/contrib/scripts/install_prereq | 2 | |
144 | b/contrib/scripts/sip_to_pjsip/astconfigparser.py | 43 | |
145 | b/funcs/func_curl.c | 48 | |
146 | b/funcs/func_odbc.c | 2 | |
147 | b/include/asterisk/bridge.h | 14 | |
148 | b/include/asterisk/bridge_channel.h | 14 | |
149 | b/include/asterisk/format_cache.h | 13 | |
150 | b/include/asterisk/logger.h | 4 | |
151 | b/include/asterisk/logger_category.h | 178 | |
152 | b/include/asterisk/pbx.h | 8 | |
153 | b/include/asterisk/res_pjsip.h | 109 | |
154 | b/include/asterisk/res_pjsip_session.h | 8 | |
155 | b/include/asterisk/res_stir_shaken.h | 114 | |
156 | b/include/asterisk/rtp_engine.h | 79 | |
157 | b/include/asterisk/sched.h | 5 | |
158 | b/include/asterisk/stream.h | 4 | |
159 | b/include/asterisk/stun.h | 25 | |
160 | b/include/asterisk/utils.h | 26 | |
161 | b/main/bridge.c | 44 | |
162 | b/main/bridge_channel.c | 20 | |
163 | b/main/channel.c | 14 | |
164 | b/main/cli.c | 51 | |
165 | b/main/dsp.c | 4 | |
166 | b/main/format_cache.c | 21 | |
167 | b/main/format_cap.c | 2 | |
168 | b/main/indications.c | 6 | |
169 | b/main/logger.c | 5 | |
170 | b/main/logger_category.c | 324 + | |
171 | b/main/pbx.c | 12 | |
172 | b/main/rtp_engine.c | 68 | |
173 | b/main/say.c | 20 | |
174 | b/main/stream.c | 30 | |
175 | b/main/stun.c | 61 | |
176 | b/main/tcptls.c | 12 | |
177 | b/main/utils.c | 50 | |
178 | b/res/Makefile | 1 | |
179 | b/res/ari/resource_bridges.h | 4 | |
180 | b/res/ari/resource_endpoints.c | 1 | |
181 | b/res/parking/parking_bridge_features.c | 1 | |
182 | b/res/res_musiconhold.c | 24 | |
183 | b/res/res_parking.c | 1 | |
184 | b/res/res_pjsip.c | 78 | |
185 | b/res/res_pjsip/config_transport.c | 18 | |
186 | b/res/res_pjsip/pjsip_configuration.c | 2 | |
187 | b/res/res_pjsip/pjsip_scheduler.c | 180 | |
188 | b/res/res_pjsip/pjsip_transport_management.c | 2 | |
189 | b/res/res_pjsip_config_wizard.c | 1 | |
190 | b/res/res_pjsip_diversion.c | 326 + | |
191 | b/res/res_pjsip_outbound_registration.c | 296 - | |
192 | b/res/res_pjsip_pubsub.c | 10 | |
193 | b/res/res_pjsip_sdp_rtp.c | 26 | |
194 | b/res/res_pjsip_session.c | 1938 ++++++++-- | |
195 | b/res/res_pjsip_stir_shaken.c | 332 + | |
196 | b/res/res_rtp_asterisk.c | 403 +- | |
197 | b/res/res_stasis.c | 31 | |
198 | b/res/res_stir_shaken.c | 1665 ++++++++ | |
199 | b/res/res_stir_shaken.exports.in | 6 | |
200 | b/res/res_stir_shaken/certificate.c | 388 ++ | |
201 | b/res/res_stir_shaken/certificate.h | 119 | |
202 | b/res/res_stir_shaken/curl.c | 199 + | |
203 | b/res/res_stir_shaken/curl.h | 73 | |
204 | b/res/res_stir_shaken/general.c | 286 + | |
205 | b/res/res_stir_shaken/general.h | 111 | |
206 | b/res/res_stir_shaken/stir_shaken.c | 122 | |
207 | b/res/res_stir_shaken/stir_shaken.h | 55 | |
208 | b/res/res_stir_shaken/store.c | 202 + | |
209 | b/res/res_stir_shaken/store.h | 37 | |
210 | b/res/stasis/stasis_bridge.c | 2 | |
211 | b/rest-api/api-docs/bridges.json | 4 | |
212 | 101 files changed, 9762 insertions(+), 1191 deletions(-)</pre><br></html>⏎ |
0 | Release Summary | |
1 | ||
2 | asterisk-16.15.0 | |
3 | ||
4 | Date: 2020-11-19 | |
5 | ||
6 | <asteriskteam@digium.com> | |
7 | ||
8 | ---------------------------------------------------------------------- | |
9 | ||
10 | Table of Contents | |
11 | ||
12 | 1. Summary | |
13 | 2. Contributors | |
14 | 3. Closed Issues | |
15 | 4. Other Changes | |
16 | 5. Diffstat | |
17 | ||
18 | ---------------------------------------------------------------------- | |
19 | ||
20 | Summary | |
21 | ||
22 | [Back to Top] | |
23 | ||
24 | This release is a point release of an existing major version. The changes | |
25 | included were made to address problems that have been identified in this | |
26 | release series, or are minor, backwards compatible new features or | |
27 | improvements. Users should be able to safely upgrade to this version if | |
28 | this release series is already in use. Users considering upgrading from a | |
29 | previous version are strongly encouraged to review the UPGRADE.txt | |
30 | document as well as the CHANGES document for information about upgrading | |
31 | to this release series. | |
32 | ||
33 | The data in this summary reflects changes that have been made since the | |
34 | previous release, asterisk-16.14.0. | |
35 | ||
36 | ---------------------------------------------------------------------- | |
37 | ||
38 | Contributors | |
39 | ||
40 | [Back to Top] | |
41 | ||
42 | This table lists the people who have submitted code, those that have | |
43 | tested patches, as well as those that reported issues on the issue tracker | |
44 | that were resolved in this release. For coders, the number is how many of | |
45 | their patches (of any size) were committed into this release. For testers, | |
46 | the number is the number of times their name was listed as assisting with | |
47 | testing a patch. Finally, for reporters, the number is the number of | |
48 | issues that they reported that were affected by commits that went into | |
49 | this release. | |
50 | ||
51 | Coders Testers Reporters | |
52 | 12 Ben Ford 3 Alexander Traud | |
53 | 11 Sean Bright 2 George Joseph | |
54 | 9 Alexander Traud 2 sungtae kim | |
55 | 8 George Joseph 2 Sebastian Damm | |
56 | 3 Kevin Harwell 1 Walter Doekes | |
57 | 3 Asterisk Development Team 1 å*¨å®¶å»º | |
58 | 3 Joshua C. Colp 1 Andrew Siplas | |
59 | 2 Torrey Searle 1 Jean Aunis - Prescom | |
60 | 2 Sungtae Kim 1 Kevin Harwell | |
61 | 1 Holger Hans Peter Freyther 1 dovid | |
62 | 1 Dovid Bender 1 Brian J. Murrell | |
63 | 1 Walter Doekes 1 Vieri | |
64 | 1 Alexei Gradinari 1 Walter Doekes | |
65 | 1 Jean Aunis 1 Ross Beer | |
66 | 1 Jasper van der Neut 1 Benjamin M. | |
67 | 1 Michal Hajek 1 Péter Juhász | |
68 | 1 laszlovl 1 under | |
69 | 1 Andrew Siplas 1 Thomas Frederiksen | |
70 | 1 laszlovl | |
71 | 1 Michael Newton | |
72 | 1 Sandro Gauci | |
73 | 1 Dovid Bender | |
74 | 1 Jasper van der Neut | |
75 | 1 Torrey Searle | |
76 | 1 Michal Hajek | |
77 | 1 Hajek Michal | |
78 | 1 Eric Smith | |
79 | ||
80 | ---------------------------------------------------------------------- | |
81 | ||
82 | Closed Issues | |
83 | ||
84 | [Back to Top] | |
85 | ||
86 | This is a list of all issues from the issue tracker that were closed by | |
87 | changes that went into this release. | |
88 | ||
89 | Security | |
90 | ||
91 | Category: pjproject/pjsip | |
92 | ||
93 | ASTERISK-29057: pjsip: Crash on call rejection during high load | |
94 | Reported by: Sandro Gauci | |
95 | * [8973fe5cf3] Kevin Harwell -- AST-2020-001 - res_pjsip: Return dialog | |
96 | locked and referenced | |
97 | ||
98 | New Feature | |
99 | ||
100 | Category: Resources/res_pjsip_diversion | |
101 | ||
102 | ASTERISK-29027: Implement support for History-Info | |
103 | Reported by: Torrey Searle | |
104 | * [5a12463c07] Torrey Searle -- res_pjsip_diversion: implement support | |
105 | for History-Info | |
106 | ||
107 | Bug | |
108 | ||
109 | Category: . I did not set the category correctly. | |
110 | ||
111 | ASTERISK-29146: GCC Warnings: â**%sâ** directive argument is null. | |
112 | Reported by: Alexander Traud | |
113 | * [334661601a] Alexander Traud -- Compiler fixes for GCC when printf %s | |
114 | is NULL | |
115 | ||
116 | Category: Applications/app_directory | |
117 | ||
118 | ASTERISK-29144: GCC Warnings with OPTIMIZE=-Og make | |
119 | Reported by: Alexander Traud | |
120 | * [13b56c4be6] Alexander Traud -- Compiler fixes for GCC with -Og | |
121 | ||
122 | Category: Applications/app_voicemail | |
123 | ||
124 | ASTERISK-29144: GCC Warnings with OPTIMIZE=-Og make | |
125 | Reported by: Alexander Traud | |
126 | * [13b56c4be6] Alexander Traud -- Compiler fixes for GCC with -Og | |
127 | ASTERISK-26424: app_voicemail: Undocumented behavior from VMSayName | |
128 | Reported by: Eric Smith | |
129 | * [9ac933fbba] Sean Bright -- app_voicemail.c: Document VMSayName | |
130 | interruption behavior | |
131 | ||
132 | Category: Channels/General | |
133 | ||
134 | ASTERISK-29144: GCC Warnings with OPTIMIZE=-Og make | |
135 | Reported by: Alexander Traud | |
136 | * [13b56c4be6] Alexander Traud -- Compiler fixes for GCC with -Og | |
137 | ||
138 | Category: Configs/Samples | |
139 | ||
140 | ASTERISK-29123: logger.conf.sample missing comment mark on line 115 | |
141 | Reported by: Andrew Siplas | |
142 | * [606bd35060] Andrew Siplas -- logger.conf.sample: add missing comment | |
143 | mark | |
144 | ||
145 | Category: Contrib/General | |
146 | ||
147 | ASTERISK-29142: sip_to_pjsip.py: doesn't read globbed includes | |
148 | Reported by: Michael Newton | |
149 | * [e067d5c8fd] Sean Bright -- sip_to_pjsip.py: Handle #include globs and | |
150 | other fixes | |
151 | ||
152 | Category: Core/Channels | |
153 | ||
154 | ASTERISK-29091: Crash when ast_translator_build_path fails | |
155 | Reported by: Jasper van der Neut | |
156 | * [efcc6d6f6b] Jasper van der Neut -- channels: Don't dereference NULL | |
157 | pointer | |
158 | ||
159 | Category: Core/General | |
160 | ||
161 | ASTERISK-28430: res_rtp_asterisk.c: FRACK!, Failed assertion errno != | |
162 | EBADF | |
163 | Reported by: under | |
164 | * [d0313d8b12] Sean Bright -- tcptls.c: Don't close TCP client file | |
165 | descriptors more than once | |
166 | ASTERISK-28311: dsp: ast_dsp_silence_noise_with_energy wrong judgment of | |
167 | frame format | |
168 | Reported by: å*¨å®¶å»º | |
169 | * [cba132a797] Sean Bright -- dsp.c: Update calls to ast_format_cmp to | |
170 | check result properly | |
171 | ||
172 | Category: Core/RTP | |
173 | ||
174 | ASTERISK-28416: Unable to get rtp codec payload code for slin | |
175 | Reported by: Brian J. Murrell | |
176 | * [c9cc281484] Sean Bright -- format_cap: Perform codec lookups by | |
177 | pointer instead of name | |
178 | ||
179 | Category: Documentation | |
180 | ||
181 | ASTERISK-29136: config: Sample features.conf incorrectly includes " around | |
182 | sound files | |
183 | Reported by: Benjamin M. | |
184 | * [e326b133dc] Sean Bright -- features.conf.sample: Sample sound files | |
185 | incorrectly quoted | |
186 | ASTERISK-26424: app_voicemail: Undocumented behavior from VMSayName | |
187 | Reported by: Eric Smith | |
188 | * [9ac933fbba] Sean Bright -- app_voicemail.c: Document VMSayName | |
189 | interruption behavior | |
190 | ||
191 | Category: Functions/func_curl | |
192 | ||
193 | ASTERISK-28825: Any curl response checks out as valid even if 404 is | |
194 | returned. | |
195 | Reported by: dovid | |
196 | * [5046e1fb06] Dovid Bender -- func_curl.c: Allow user to set what | |
197 | return codes constitute a failure. | |
198 | ASTERISK-29085: func_curl: Segmentation fault when using CURL after | |
199 | setting httpheader CURLOPT | |
200 | Reported by: Péter Juhász | |
201 | * [799426cd58] Sean Bright -- func_curl.c: Prevent crash when using | |
202 | CURLOPT(httpheader) | |
203 | ||
204 | Category: Functions/func_odbc | |
205 | ||
206 | ASTERISK-29144: GCC Warnings with OPTIMIZE=-Og make | |
207 | Reported by: Alexander Traud | |
208 | * [13b56c4be6] Alexander Traud -- Compiler fixes for GCC with -Og | |
209 | ||
210 | Category: Resources/res_ari_endpoints | |
211 | ||
212 | ASTERISK-29108: resource_endpoints.c : Memory leak if endpoint not found | |
213 | Reported by: Jean Aunis - Prescom | |
214 | * [0b835f2156] Jean Aunis -- resource_endpoints.c: memory leak when | |
215 | providing a 404 response | |
216 | ||
217 | Category: Resources/res_musiconhold | |
218 | ||
219 | ASTERISK-29099: res_musiconhold: Realtime MOH only loads a single entry | |
220 | Reported by: laszlovl | |
221 | * [374d18cb97] laszlovl -- res_musiconhold: Load all realtime entries, | |
222 | not just the first | |
223 | ASTERISK-24329: Music On Hold announcement cuts intro of music the first | |
224 | time it is played | |
225 | Reported by: Thomas Frederiksen | |
226 | * [4a7bbac0ed] Sean Bright -- res_musiconhold: Start playlist after | |
227 | initial announcement | |
228 | ||
229 | Category: Resources/res_pjsip | |
230 | ||
231 | ASTERISK-28933: res_pjsip.so fails to load when bundled pjproject is | |
232 | compiled without libssl | |
233 | Reported by: Walter Doekes | |
234 | * [ddfb76a864] Alexander Traud -- res_pjsip/config_transport: Load and | |
235 | run without OpenSSL. | |
236 | ASTERISK-29013: res_pjsip: Asterisk doesn't stop sending invites (with | |
237 | auth) on 407 replies | |
238 | Reported by: Sebastian Damm | |
239 | * [58aa6a7057] Ben Ford -- AST-2020-002 - res_pjsip: Stop sending | |
240 | INVITEs after challenge limit. | |
241 | ||
242 | Category: Resources/res_pjsip_authenticator_digest | |
243 | ||
244 | ASTERISK-29013: res_pjsip: Asterisk doesn't stop sending invites (with | |
245 | auth) on 407 replies | |
246 | Reported by: Sebastian Damm | |
247 | * [58aa6a7057] Ben Ford -- AST-2020-002 - res_pjsip: Stop sending | |
248 | INVITEs after challenge limit. | |
249 | ||
250 | Category: Resources/res_pjsip_config_wizard | |
251 | ||
252 | ASTERISK-29097: res_pjsip_config_wizard: Crash when freeing string when | |
253 | failing to add extension | |
254 | Reported by: Vieri | |
255 | * [7a64868118] Sean Bright -- pbx.c: On error, | |
256 | ast_add_extension2_lockopt should always free 'data' | |
257 | ||
258 | Category: Resources/res_pjsip_sdp_rtp | |
259 | ||
260 | ASTERISK-29051: res_pjsip_sdp_rtp: Does not set correct values on RTP | |
261 | instance when "auto" DTMF is used | |
262 | Reported by: Sebastian Damm | |
263 | * [cd793c7c81] Holger Hans Peter Freyther -- res_pjsip_sdp_rtp: Fix | |
264 | accidentally native bridging calls | |
265 | ||
266 | Category: Resources/res_pjsip_session | |
267 | ||
268 | ASTERISK-29014: res_pjsip_session: Re-INVITE collisions aren't handled | |
269 | correctly | |
270 | Reported by: George Joseph | |
271 | * [df429c97a1] George Joseph -- res_pjsip_session: Fix issue with COLP | |
272 | and 491 | |
273 | * [65088494cb] George Joseph -- res_pjsip_session: Handle multi-stream | |
274 | re-invites better | |
275 | ||
276 | Category: Resources/res_rtp_asterisk | |
277 | ||
278 | ASTERISK-29089: RTP Ports not cleared after hangup | |
279 | Reported by: Ross Beer | |
280 | * [e6ed74347c] Joshua C. Colp -- res_pjsip_session: Fix session | |
281 | reference leak. | |
282 | ||
283 | Category: Resources/res_stasis | |
284 | ||
285 | ASTERISK-29081: res_stasis: Add compare function for bridges moh container | |
286 | Reported by: Hajek Michal | |
287 | * [f7285140b4] Michal Hajek -- res_stasis.c: Add compare function for | |
288 | bridges moh container | |
289 | ||
290 | Category: Utilities/muted | |
291 | ||
292 | ASTERISK-29145: GCC Warnings with OPTIMIZE=-Os make | |
293 | Reported by: Alexander Traud | |
294 | * [92ca48d54c] Alexander Traud -- Compiler fixes for GCC with -Os | |
295 | ||
296 | Improvement | |
297 | ||
298 | Category: Core/Logging | |
299 | ||
300 | ASTERISK-29054: Logger: Add debug logging categories | |
301 | Reported by: Kevin Harwell | |
302 | * [e051806e80] Kevin Harwell -- Logging: Add debug logging categories | |
303 | ||
304 | Category: Resources/General | |
305 | ||
306 | ASTERISK-29056: Increase reg_server column size for ps_contacts table | |
307 | realtime | |
308 | Reported by: sungtae kim | |
309 | * [a353b76c75] Sungtae Kim -- realtime: Increased reg_server character | |
310 | size | |
311 | ||
312 | Category: Resources/res_stasis | |
313 | ||
314 | ASTERISK-29055: Create a Bridge with video_single mode | |
315 | Reported by: sungtae kim | |
316 | * [af339d0adb] Sungtae Kim -- res_stasis.c: Added video_single option | |
317 | for bridge creation | |
318 | ||
319 | ---------------------------------------------------------------------- | |
320 | ||
321 | Commits Not Associated with an Issue | |
322 | ||
323 | [Back to Top] | |
324 | ||
325 | This is a list of all changes that went into this release that did not | |
326 | reference a JIRA issue. | |
327 | ||
328 | +------------------------------------------------------------------------+ | |
329 | | Revision | Author | Summary | | |
330 | |------------+---------------+-------------------------------------------| | |
331 | | | Asterisk | | | |
332 | | fbe2e9b687 | Development | Update for 16.15.0-rc1 | | |
333 | | | Team | | | |
334 | |------------+---------------+-------------------------------------------| | |
335 | | | Asterisk | Update CHANGES and UPGRADE.txt for | | |
336 | | 3366956139 | Development | 16.15.0 | | |
337 | | | Team | | | |
338 | |------------+---------------+-------------------------------------------| | |
339 | | e1fd51cd2c | George Joseph | res_pjsip_outbound_registration.c: Use | | |
340 | | | | our own scheduler and other stuff | | |
341 | |------------+---------------+-------------------------------------------| | |
342 | | 80f116c156 | George Joseph | pjsip_scheduler.c: Add type ONESHOT and | | |
343 | | | | enhance cli show command | | |
344 | |------------+---------------+-------------------------------------------| | |
345 | | 728cd55cde | Alexei | sched: AST_SCHED_REPLACE_UNREF can lead | | |
346 | | | Gradinari | to use after free of data | | |
347 | |------------+---------------+-------------------------------------------| | |
348 | | 277aa0ced6 | Alexander | res_stir_shaken: Include OpenSSL headers | | |
349 | | | Traud | where used actually. | | |
350 | |------------+---------------+-------------------------------------------| | |
351 | | 951ce0524d | Alexander | chan_sip: On authentication, pick MD5 for | | |
352 | | | Traud | sure. | | |
353 | |------------+---------------+-------------------------------------------| | |
354 | | f98eed17c1 | Walter Doekes | main/say: Work around gcc 9 | | |
355 | | | | format-truncation false positive | | |
356 | |------------+---------------+-------------------------------------------| | |
357 | | 92e1de458a | Kevin Harwell | res_pjsip, res_pjsip_session: initialize | | |
358 | | | | local variables | | |
359 | |------------+---------------+-------------------------------------------| | |
360 | | 65426f4312 | Alexander | install_prereq: Add GMime 3.0. | | |
361 | | | Traud | | | |
362 | |------------+---------------+-------------------------------------------| | |
363 | | fb721ce82c | Alexander | BuildSystem: Enable Lua 5.4. | | |
364 | | | Traud | | | |
365 | |------------+---------------+-------------------------------------------| | |
366 | | | Asterisk | Update CHANGES and UPGRADE.txt for | | |
367 | | 69356a7895 | Development | 16.14.0 | | |
368 | | | Team | | | |
369 | |------------+---------------+-------------------------------------------| | |
370 | | 681a1624b5 | Ben Ford | utils.c: NULL terminate | | |
371 | | | | ast_base64decode_string. | | |
372 | |------------+---------------+-------------------------------------------| | |
373 | | df7c4ed0ed | Ben Ford | res_stir_shaken: Fix memory allocation | | |
374 | | | | error in curl.c | | |
375 | |------------+---------------+-------------------------------------------| | |
376 | | 21ab0a450b | Ben Ford | res_stir_shaken: Add stir_shaken option | | |
377 | | | | and general improvements. | | |
378 | |------------+---------------+-------------------------------------------| | |
379 | | d979bdf87a | Ben Ford | res_stir_shaken: Add outbound INVITE | | |
380 | | | | support. | | |
381 | |------------+---------------+-------------------------------------------| | |
382 | | 746ce16b16 | Ben Ford | res_stir_shaken: Add inbound INVITE | | |
383 | | | | support. | | |
384 | |------------+---------------+-------------------------------------------| | |
385 | | 9d7628829c | Ben Ford | res_stir_shaken: Add unit tests for | | |
386 | | | | signing and verification. | | |
387 | |------------+---------------+-------------------------------------------| | |
388 | | 035b463c93 | Ben Ford | res_stir_shaken: Added dialplan function | | |
389 | | | | and API call. | | |
390 | |------------+---------------+-------------------------------------------| | |
391 | | 0392a8e620 | Joshua C. | res_stir_shaken: Use ast_asprintf for | | |
392 | | | Colp | creating file path. | | |
393 | |------------+---------------+-------------------------------------------| | |
394 | | 70af7e1311 | Ben Ford | res_stir_shaken: Implemented signature | | |
395 | | | | verification. | | |
396 | |------------+---------------+-------------------------------------------| | |
397 | | 971b125fc0 | Alexander | res_stir_shaken: Do not build without | | |
398 | | | Traud | OpenSSL. | | |
399 | |------------+---------------+-------------------------------------------| | |
400 | | e9ee9a381b | Ben Ford | res_stir_shaken: Implemented signing of | | |
401 | | | | JSON payload. | | |
402 | |------------+---------------+-------------------------------------------| | |
403 | | 716e51a3f3 | Ben Ford | res_stir_shaken: Initial commit and | | |
404 | | | | reading private key. | | |
405 | |------------+---------------+-------------------------------------------| | |
406 | | 3b0a53f257 | George Joseph | app_confbridge/bridge_softmix: Add | | |
407 | | | | ability to force estimated bitrate | | |
408 | |------------+---------------+-------------------------------------------| | |
409 | | 14b483dd5e | Torrey Searle | res_pjsip_diversion: fix double 181 | | |
410 | |------------+---------------+-------------------------------------------| | |
411 | | fccf360fcb | Sean Bright | res_musiconhold: Clarify that playlist | | |
412 | | | | mode only supports HTTP(S) URLs | | |
413 | |------------+---------------+-------------------------------------------| | |
414 | | baa6e8f112 | Joshua C. | res_pjsip_session: Fix stream name memory | | |
415 | | | Colp | leak. | | |
416 | |------------+---------------+-------------------------------------------| | |
417 | | fb6f2157e7 | George Joseph | logger.h: Fix ast_trace to respect | | |
418 | | | | scope_level | | |
419 | |------------+---------------+-------------------------------------------| | |
420 | | 8d9633074e | George Joseph | bridge_softmix/sfu_topologies_on_join: | | |
421 | | | | Ignore topology change failures | | |
422 | |------------+---------------+-------------------------------------------| | |
423 | | 9458577f68 | Sean Bright | res_pjsip_session.c: Fix build when | | |
424 | | | | TEST_FRAMEWORK is not defined | | |
425 | |------------+---------------+-------------------------------------------| | |
426 | | 6abf6f345d | George Joseph | debugging: Add enough to choke a mule | | |
427 | |------------+---------------+-------------------------------------------| | |
428 | | e7620d034a | Ben Ford | Bridging: Use a ref to bridge_channel's | | |
429 | | | | channel to prevent crash. | | |
430 | +------------------------------------------------------------------------+ | |
431 | ||
432 | ---------------------------------------------------------------------- | |
433 | ||
434 | Diffstat Results | |
435 | ||
436 | [Back to Top] | |
437 | ||
438 | This is a summary of the changes to the source code that went into this | |
439 | release that was generated using the diffstat utility. | |
440 | ||
441 | asterisk-16.14.0-summary.html | 76 | |
442 | asterisk-16.14.0-summary.txt | 248 - | |
443 | b/.version | 2 | |
444 | b/CHANGES | 51 | |
445 | b/ChangeLog | 920 ++++ | |
446 | b/Makefile | 2 | |
447 | b/UPGRADE.txt | 10 | |
448 | b/addons/ooh323c/src/ooq931.c | 2 | |
449 | b/apps/app_confbridge.c | 3 | |
450 | b/apps/app_directory.c | 2 | |
451 | b/apps/app_voicemail.c | 5 | |
452 | b/apps/confbridge/conf_config_parser.c | 21 | |
453 | b/apps/confbridge/include/confbridge.h | 2 | |
454 | b/asterisk-16.15.0-rc1-summary.html | 215 + | |
455 | b/asterisk-16.15.0-rc1-summary.txt | 542 ++ | |
456 | b/bridges/bridge_softmix.c | 153 | |
457 | b/channels/chan_iax2.c | 2 | |
458 | b/channels/chan_pjsip.c | 74 | |
459 | b/channels/chan_sip.c | 13 | |
460 | b/configs/samples/confbridge.conf.sample | 7 | |
461 | b/configs/samples/features.conf.sample | 4 | |
462 | b/configs/samples/musiconhold.conf.sample | 4 | |
463 | b/configs/samples/pjsip.conf.sample | 18 | |
464 | b/configs/samples/res_curl.conf.sample | 1 | |
465 | b/configs/samples/stir_shaken.conf.sample | 61 | |
466 | b/configure | 2 | |
467 | b/configure.ac | 2 | |
468 | b/contrib/ast-db-manage/config/versions/1ae0609b6646_increse_reg_server_size.py | 22 | |
469 | b/contrib/ast-db-manage/config/versions/61797b9fced6_add_stir_shaken.py | 31 | |
470 | b/contrib/ast-db-manage/config/versions/e658c26033ca_create_history_info_flag.py | 38 | |
471 | b/contrib/realtime/mysql/mysql_config.sql | 18 | |
472 | b/contrib/realtime/postgresql/postgresql_config.sql | 18 | |
473 | b/contrib/scripts/install_prereq | 2 | |
474 | b/contrib/scripts/sip_to_pjsip/astconfigparser.py | 43 | |
475 | b/funcs/func_curl.c | 48 | |
476 | b/funcs/func_odbc.c | 2 | |
477 | b/include/asterisk/bridge.h | 14 | |
478 | b/include/asterisk/bridge_channel.h | 14 | |
479 | b/include/asterisk/format_cache.h | 13 | |
480 | b/include/asterisk/logger.h | 4 | |
481 | b/include/asterisk/logger_category.h | 178 | |
482 | b/include/asterisk/pbx.h | 8 | |
483 | b/include/asterisk/res_pjsip.h | 109 | |
484 | b/include/asterisk/res_pjsip_session.h | 8 | |
485 | b/include/asterisk/res_stir_shaken.h | 114 | |
486 | b/include/asterisk/rtp_engine.h | 79 | |
487 | b/include/asterisk/sched.h | 5 | |
488 | b/include/asterisk/stream.h | 4 | |
489 | b/include/asterisk/stun.h | 25 | |
490 | b/include/asterisk/utils.h | 26 | |
491 | b/main/bridge.c | 44 | |
492 | b/main/bridge_channel.c | 20 | |
493 | b/main/channel.c | 14 | |
494 | b/main/cli.c | 51 | |
495 | b/main/dsp.c | 4 | |
496 | b/main/format_cache.c | 21 | |
497 | b/main/format_cap.c | 2 | |
498 | b/main/indications.c | 6 | |
499 | b/main/logger.c | 5 | |
500 | b/main/logger_category.c | 324 + | |
501 | b/main/pbx.c | 12 | |
502 | b/main/rtp_engine.c | 68 | |
503 | b/main/say.c | 20 | |
504 | b/main/stream.c | 30 | |
505 | b/main/stun.c | 61 | |
506 | b/main/tcptls.c | 12 | |
507 | b/main/utils.c | 50 | |
508 | b/res/Makefile | 1 | |
509 | b/res/ari/resource_bridges.h | 4 | |
510 | b/res/ari/resource_endpoints.c | 1 | |
511 | b/res/parking/parking_bridge_features.c | 1 | |
512 | b/res/res_musiconhold.c | 24 | |
513 | b/res/res_parking.c | 1 | |
514 | b/res/res_pjsip.c | 78 | |
515 | b/res/res_pjsip/config_transport.c | 18 | |
516 | b/res/res_pjsip/pjsip_configuration.c | 2 | |
517 | b/res/res_pjsip/pjsip_scheduler.c | 180 | |
518 | b/res/res_pjsip/pjsip_transport_management.c | 2 | |
519 | b/res/res_pjsip_config_wizard.c | 1 | |
520 | b/res/res_pjsip_diversion.c | 326 + | |
521 | b/res/res_pjsip_outbound_registration.c | 296 - | |
522 | b/res/res_pjsip_pubsub.c | 10 | |
523 | b/res/res_pjsip_sdp_rtp.c | 26 | |
524 | b/res/res_pjsip_session.c | 1938 ++++++++-- | |
525 | b/res/res_pjsip_stir_shaken.c | 332 + | |
526 | b/res/res_rtp_asterisk.c | 403 +- | |
527 | b/res/res_stasis.c | 31 | |
528 | b/res/res_stir_shaken.c | 1665 ++++++++ | |
529 | b/res/res_stir_shaken.exports.in | 6 | |
530 | b/res/res_stir_shaken/certificate.c | 388 ++ | |
531 | b/res/res_stir_shaken/certificate.h | 119 | |
532 | b/res/res_stir_shaken/curl.c | 199 + | |
533 | b/res/res_stir_shaken/curl.h | 73 | |
534 | b/res/res_stir_shaken/general.c | 286 + | |
535 | b/res/res_stir_shaken/general.h | 111 | |
536 | b/res/res_stir_shaken/stir_shaken.c | 122 | |
537 | b/res/res_stir_shaken/stir_shaken.h | 55 | |
538 | b/res/res_stir_shaken/store.c | 202 + | |
539 | b/res/res_stir_shaken/store.h | 37 | |
540 | b/res/stasis/stasis_bridge.c | 2 | |
541 | b/rest-api/api-docs/bridges.json | 4 | |
542 | 101 files changed, 9762 insertions(+), 1191 deletions(-) |
620 | 620 | static void sfu_topologies_on_join(struct ast_bridge *bridge, |
621 | 621 | struct ast_bridge_channel *joiner) |
622 | 622 | { |
623 | struct ast_stream_topology *joiner_video = NULL; | |
623 | RAII_VAR(struct ast_stream_topology *, joiner_video, NULL, ast_stream_topology_free); | |
624 | 624 | struct ast_bridge_channels_list *participants = &bridge->channels; |
625 | 625 | struct ast_bridge_channel *participant; |
626 | 626 | int res; |
627 | 627 | struct softmix_channel *sc; |
628 | SCOPE_ENTER(3, "%s: \n", ast_channel_name(joiner->chan)); | |
628 | 629 | |
629 | 630 | joiner_video = ast_stream_topology_alloc(); |
630 | 631 | if (!joiner_video) { |
631 | return; | |
632 | SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Couldn't alloc topology\n", ast_channel_name(joiner->chan)); | |
632 | 633 | } |
633 | 634 | |
634 | 635 | sc = joiner->tech_pvt; |
641 | 642 | ast_channel_unlock(joiner->chan); |
642 | 643 | |
643 | 644 | if (res || !sc->topology) { |
644 | goto cleanup; | |
645 | SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Couldn't append source streams\n", ast_channel_name(joiner->chan)); | |
645 | 646 | } |
646 | 647 | |
647 | 648 | AST_LIST_TRAVERSE(participants, participant, entry) { |
648 | 649 | if (participant == joiner) { |
649 | 650 | continue; |
650 | 651 | } |
652 | ast_trace(-1, "%s: Appending existing participant %s\n", ast_channel_name(joiner->chan), | |
653 | ast_channel_name(participant->chan)); | |
651 | 654 | ast_channel_lock(participant->chan); |
652 | 655 | res = append_source_streams(sc->topology, ast_channel_name(participant->chan), |
653 | 656 | bridge->softmix.send_sdp_label ? ast_channel_uniqueid(participant->chan) : NULL, |
654 | 657 | ast_channel_get_stream_topology(participant->chan)); |
655 | 658 | ast_channel_unlock(participant->chan); |
656 | 659 | if (res) { |
657 | goto cleanup; | |
658 | } | |
659 | } | |
660 | ||
661 | ast_channel_request_stream_topology_change(joiner->chan, sc->topology, NULL); | |
660 | SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s/%s: Couldn't append source streams\n", | |
661 | ast_channel_name(participant->chan), ast_channel_name(joiner->chan)); | |
662 | } | |
663 | } | |
664 | ||
665 | ast_trace(-1, "%s: Requesting topology change.\n", ast_channel_name(joiner->chan)); | |
666 | res = ast_channel_request_stream_topology_change(joiner->chan, sc->topology, NULL); | |
667 | if (res) { | |
668 | SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Couldn't request topology change\n", ast_channel_name(joiner->chan)); | |
669 | } | |
662 | 670 | |
663 | 671 | AST_LIST_TRAVERSE(participants, participant, entry) { |
664 | 672 | if (participant == joiner) { |
666 | 674 | } |
667 | 675 | |
668 | 676 | sc = participant->tech_pvt; |
677 | ast_trace(-1, "%s: Appending joiner %s\n", ast_channel_name(participant->chan), | |
678 | ast_channel_name(joiner->chan)); | |
679 | ||
669 | 680 | if (append_all_streams(sc->topology, joiner_video)) { |
670 | goto cleanup; | |
671 | } | |
672 | ast_channel_request_stream_topology_change(participant->chan, sc->topology, NULL); | |
673 | } | |
674 | ||
675 | cleanup: | |
676 | ast_stream_topology_free(joiner_video); | |
681 | SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s/%s: Couldn't append streams\n", | |
682 | ast_channel_name(participant->chan), ast_channel_name(joiner->chan)); | |
683 | } | |
684 | ast_trace(-1, "%s: Requesting topology change\n", ast_channel_name(participant->chan)); | |
685 | res = ast_channel_request_stream_topology_change(participant->chan, sc->topology, NULL); | |
686 | if (res) { | |
687 | ast_trace(-1, "%s/%s: Couldn't request topology change\n", | |
688 | ast_channel_name(participant->chan), ast_channel_name(joiner->chan)); | |
689 | } | |
690 | } | |
691 | ||
692 | SCOPE_EXIT(); | |
677 | 693 | } |
678 | 694 | |
679 | 695 | /*! \brief Function called when a channel is joined into the bridge */ |
690 | 706 | int pos_id; |
691 | 707 | int is_announcement = 0; |
692 | 708 | int samplerate_change; |
709 | SCOPE_ENTER(3, "%s:\n", ast_channel_name(bridge_channel->chan)); | |
693 | 710 | |
694 | 711 | softmix_data = bridge->tech_pvt; |
695 | 712 | if (!softmix_data) { |
696 | return -1; | |
713 | SCOPE_EXIT_RTN_VALUE(-1, "No tech_pvt\n"); | |
697 | 714 | } |
698 | 715 | |
699 | 716 | /* Create a new softmix_channel structure and allocate various things on it */ |
700 | 717 | if (!(sc = ast_calloc(1, sizeof(*sc)))) { |
701 | return -1; | |
718 | SCOPE_EXIT_RTN_VALUE(-1, "Couldn't alloc tech_pvt\n"); | |
702 | 719 | } |
703 | 720 | |
704 | 721 | samplerate_change = softmix_data->internal_rate; |
723 | 740 | "Could not allocate enough memory.\n", bridge->uniqueid, |
724 | 741 | ast_channel_name(bridge_channel->chan)); |
725 | 742 | ast_free(sc); |
726 | return -1; | |
743 | SCOPE_EXIT_RTN_VALUE(-1, "Couldn't do binaural join\n"); | |
727 | 744 | } |
728 | 745 | } |
729 | 746 | } |
752 | 769 | } |
753 | 770 | |
754 | 771 | softmix_poke_thread(softmix_data); |
755 | return 0; | |
772 | SCOPE_EXIT_RTN_VALUE(0); | |
756 | 773 | } |
757 | 774 | |
758 | 775 | static int remove_destination_streams(struct ast_stream_topology *topology, |
1080 | 1097 | if (!strcmp(ast_stream_get_name(stream), ast_stream_get_name(original_stream))) { |
1081 | 1098 | struct ast_stream *removed; |
1082 | 1099 | |
1083 | /* Since the participant is still going to be in the bridge we | |
1084 | * change the name so that routing does not attempt to route video | |
1085 | * to this stream. | |
1086 | */ | |
1087 | removed = ast_stream_clone(stream, "removed"); | |
1100 | removed = ast_stream_clone(stream, NULL); | |
1088 | 1101 | if (!removed) { |
1089 | 1102 | return -1; |
1090 | 1103 | } |
1382 | 1395 | case AST_BRIDGE_VIDEO_SFU_REMB_AVERAGE: |
1383 | 1396 | case AST_BRIDGE_VIDEO_SFU_REMB_LOWEST: |
1384 | 1397 | case AST_BRIDGE_VIDEO_SFU_REMB_HIGHEST: |
1398 | case AST_BRIDGE_VIDEO_SFU_REMB_FORCE: | |
1385 | 1399 | /* These will never actually get hit due to being handled by remb_collect_report below */ |
1386 | 1400 | break; |
1387 | 1401 | } |
1403 | 1417 | /* We evenly divide the available maximum bitrate across the video sources |
1404 | 1418 | * to this receiver so each source gets an equal slice. |
1405 | 1419 | */ |
1420 | ||
1421 | if (bridge->softmix.video_mode.mode_data.sfu_data.remb_behavior == AST_BRIDGE_VIDEO_SFU_REMB_FORCE) { | |
1422 | softmix_data->bitrate = bridge->softmix.video_mode.mode_data.sfu_data.estimated_bitrate; | |
1423 | return; | |
1424 | } | |
1425 | ||
1406 | 1426 | bitrate = (sc->remb.br_mantissa << sc->remb.br_exp) / AST_VECTOR_SIZE(&sc->video_sources); |
1407 | 1427 | |
1408 | 1428 | /* If this receiver has no bitrate yet ignore it */ |
1447 | 1467 | case AST_BRIDGE_VIDEO_SFU_REMB_LOWEST_ALL: |
1448 | 1468 | case AST_BRIDGE_VIDEO_SFU_REMB_HIGHEST_ALL: |
1449 | 1469 | /* These will never actually get hit due to being handled by remb_collect_report_all above */ |
1470 | break; | |
1471 | case AST_BRIDGE_VIDEO_SFU_REMB_FORCE: | |
1472 | /* Don't do anything, we've already forced it */ | |
1450 | 1473 | break; |
1451 | 1474 | } |
1452 | 1475 | } |
2253 | 2276 | struct ast_stream_topology *added_streams; |
2254 | 2277 | struct ast_bridge_channels_list *participants = &bridge->channels; |
2255 | 2278 | struct ast_bridge_channel *participant; |
2279 | SCOPE_ENTER(3, "%s: OT: %s NT: %s\n", ast_channel_name(bridge_channel->chan), | |
2280 | ast_str_tmp(256, ast_stream_topology_to_str(old_topology, &STR_TMP)), | |
2281 | ast_str_tmp(256, ast_stream_topology_to_str(new_topology, &STR_TMP))); | |
2256 | 2282 | |
2257 | 2283 | added_streams = ast_stream_topology_alloc(); |
2258 | 2284 | if (!added_streams) { |
2259 | return; | |
2285 | SCOPE_EXIT_LOG(LOG_ERROR, "%s: Couldn't alloc topology\n", ast_channel_name(bridge_channel->chan)); | |
2260 | 2286 | } |
2261 | 2287 | |
2262 | 2288 | /* We go through the old topology comparing it to the new topology to determine what streams |
2265 | 2291 | * Added streams are copied into a topology and added to each other participant while for |
2266 | 2292 | * removed streams we merely store their position and mark them as removed later. |
2267 | 2293 | */ |
2294 | ast_trace(-1, "%s: Checking for state changes\n", ast_channel_name(bridge_channel->chan)); | |
2268 | 2295 | for (index = 0; index < ast_stream_topology_get_count(sc->topology) && index < ast_stream_topology_get_count(new_topology); ++index) { |
2269 | 2296 | struct ast_stream *old_stream = ast_stream_topology_get_stream(sc->topology, index); |
2270 | 2297 | struct ast_stream *new_stream = ast_stream_topology_get_stream(new_topology, index); |
2298 | SCOPE_ENTER(4, "%s: Slot: %d Old stream: %s New stream: %s\n", ast_channel_name(bridge_channel->chan), | |
2299 | index, ast_str_tmp(256, ast_stream_to_str(old_stream, &STR_TMP)), | |
2300 | ast_str_tmp(256, ast_stream_to_str(new_stream, &STR_TMP))); | |
2271 | 2301 | |
2272 | 2302 | /* Ignore all streams that don't carry video and streams that are strictly outgoing destination streams */ |
2273 | 2303 | if ((ast_stream_get_type(old_stream) != AST_MEDIA_TYPE_VIDEO && ast_stream_get_type(new_stream) != AST_MEDIA_TYPE_VIDEO) || |
2274 | !strncmp(ast_stream_get_name(old_stream), SOFTBRIDGE_VIDEO_DEST_PREFIX, | |
2304 | !strncmp(ast_stream_get_name(new_stream), SOFTBRIDGE_VIDEO_DEST_PREFIX, | |
2275 | 2305 | SOFTBRIDGE_VIDEO_DEST_LEN)) { |
2276 | continue; | |
2306 | SCOPE_EXIT_EXPR(continue, "%s: Stream %d ignored\n", ast_channel_name(bridge_channel->chan), index); | |
2277 | 2307 | } |
2278 | 2308 | |
2279 | 2309 | if (ast_stream_get_type(old_stream) == AST_MEDIA_TYPE_VIDEO && ast_stream_get_type(new_stream) != AST_MEDIA_TYPE_VIDEO) { |
2280 | 2310 | /* If a stream renegotiates from video to non-video then we need to remove it as a source */ |
2311 | ast_trace(-1, "%s: Stream %d added to remove list\n", ast_channel_name(bridge_channel->chan), index); | |
2281 | 2312 | removed_streams[removed_streams_count++] = index; |
2282 | 2313 | } else if (ast_stream_get_type(old_stream) != AST_MEDIA_TYPE_VIDEO && ast_stream_get_type(new_stream) == AST_MEDIA_TYPE_VIDEO) { |
2283 | 2314 | if (ast_stream_get_state(new_stream) != AST_STREAM_STATE_REMOVED) { |
2285 | 2316 | if (append_source_stream(added_streams, ast_channel_name(bridge_channel->chan), |
2286 | 2317 | bridge->softmix.send_sdp_label ? ast_channel_uniqueid(bridge_channel->chan) : NULL, |
2287 | 2318 | new_stream, index)) { |
2288 | goto cleanup; | |
2319 | SCOPE_EXIT_EXPR(goto cleanup, "%s: Couldn't append source stream %d:%s\n", ast_channel_name(bridge_channel->chan), | |
2320 | index, ast_stream_get_name(new_stream)); | |
2289 | 2321 | } |
2322 | ast_trace(-1, "%s: Stream %d changed from non-video to video\n", ast_channel_name(bridge_channel->chan), index); | |
2290 | 2323 | } |
2291 | 2324 | } else if (ast_stream_get_state(old_stream) != AST_STREAM_STATE_REMOVED && |
2292 | 2325 | ast_stream_get_state(new_stream) != AST_STREAM_STATE_SENDRECV && ast_stream_get_state(new_stream) != AST_STREAM_STATE_RECVONLY) { |
2326 | ast_trace(-1, "%s: Stream %d added to remove list\n", ast_channel_name(bridge_channel->chan), index); | |
2293 | 2327 | /* If a stream renegotiates and is removed then we remove it */ |
2294 | 2328 | removed_streams[removed_streams_count++] = index; |
2295 | 2329 | } else if ((ast_stream_get_state(old_stream) == AST_STREAM_STATE_REMOVED || ast_stream_get_state(old_stream) == AST_STREAM_STATE_INACTIVE || |
2300 | 2334 | if (append_source_stream(added_streams, ast_channel_name(bridge_channel->chan), |
2301 | 2335 | bridge->softmix.send_sdp_label ? ast_channel_uniqueid(bridge_channel->chan) : NULL, |
2302 | 2336 | new_stream, index)) { |
2303 | goto cleanup; | |
2337 | SCOPE_EXIT_EXPR(goto cleanup, "%s: Couldn't append source stream %d:%s\n", ast_channel_name(bridge_channel->chan), | |
2338 | index, ast_stream_get_name(new_stream)); | |
2304 | 2339 | } |
2305 | } | |
2340 | ast_trace(-1, "%s: Stream %d:%s changed state from %s to %s\n", ast_channel_name(bridge_channel->chan), | |
2341 | index, ast_stream_get_name(old_stream), ast_stream_state2str(ast_stream_get_state(old_stream)), | |
2342 | ast_stream_state2str(ast_stream_get_state(new_stream))); | |
2343 | } else { | |
2344 | ast_trace(-1, "%s: Stream %d:%s didn't do anything\n", ast_channel_name(bridge_channel->chan), | |
2345 | index, ast_stream_get_name(old_stream)); | |
2346 | } | |
2347 | SCOPE_EXIT(); | |
2306 | 2348 | } |
2307 | 2349 | |
2308 | 2350 | /* Any newly added streams that did not take the position of a removed stream |
2310 | 2352 | * removed from the topology but merely marked as removed we can pick up where we |
2311 | 2353 | * left off when comparing the old and new topologies. |
2312 | 2354 | */ |
2355 | ast_trace(-1, "%s: Checking for newly added streams\n", ast_channel_name(bridge_channel->chan)); | |
2356 | ||
2313 | 2357 | for (; index < ast_stream_topology_get_count(new_topology); ++index) { |
2314 | 2358 | struct ast_stream *stream = ast_stream_topology_get_stream(new_topology, index); |
2359 | SCOPE_ENTER(4, "%s: Checking stream %d:%s\n", ast_channel_name(bridge_channel->chan), index, | |
2360 | ast_stream_get_name(stream)); | |
2315 | 2361 | |
2316 | 2362 | if (!is_video_source(stream)) { |
2317 | continue; | |
2363 | SCOPE_EXIT_EXPR(continue, "%s: Stream %d:%s is not video source\n", ast_channel_name(bridge_channel->chan), | |
2364 | index, ast_stream_get_name(stream)); | |
2318 | 2365 | } |
2319 | 2366 | |
2320 | 2367 | if (append_source_stream(added_streams, ast_channel_name(bridge_channel->chan), |
2321 | 2368 | bridge->softmix.send_sdp_label ? ast_channel_uniqueid(bridge_channel->chan) : NULL, |
2322 | 2369 | stream, index)) { |
2323 | goto cleanup; | |
2324 | } | |
2370 | SCOPE_EXIT_EXPR(goto cleanup, "%s: Couldn't append stream %d:%s\n", ast_channel_name(bridge_channel->chan), | |
2371 | index, ast_stream_get_name(stream)); | |
2372 | } | |
2373 | SCOPE_EXIT("%s: Added new stream %s\n", ast_channel_name(bridge_channel->chan), | |
2374 | ast_str_tmp(256, ast_stream_to_str(stream, &STR_TMP))); | |
2325 | 2375 | } |
2326 | 2376 | |
2327 | 2377 | /* We always update the stored topology if we can to reflect what is currently negotiated */ |
2336 | 2386 | * other participants. |
2337 | 2387 | */ |
2338 | 2388 | if (!removed_streams_count && !ast_stream_topology_get_count(added_streams)) { |
2389 | ast_trace(-1, "%s: Nothing added or removed\n", ast_channel_name(bridge_channel->chan)); | |
2339 | 2390 | goto cleanup; |
2340 | 2391 | } |
2341 | 2392 | |
2393 | ast_trace(-1, "%s: Processing adds and removes\n", ast_channel_name(bridge_channel->chan)); | |
2342 | 2394 | /* Go through each participant adding in the new streams and removing the old ones */ |
2343 | AST_LIST_TRAVERSE(participants, participant, entry) { | |
2395 | AST_LIST_TRAVERSE(participants, participant, entry) | |
2396 | { | |
2397 | struct softmix_channel *participant_sc = participant->tech_pvt; | |
2398 | SCOPE_ENTER(4, "%s/%s: Old participant topology %s\n", | |
2399 | ast_channel_name(bridge_channel->chan), | |
2400 | ast_channel_name(participant->chan), | |
2401 | ast_str_tmp(256, ast_stream_topology_to_str(participant_sc->topology, &STR_TMP))); | |
2402 | ||
2344 | 2403 | if (participant == bridge_channel) { |
2345 | continue; | |
2346 | } | |
2347 | ||
2348 | sc = participant->tech_pvt; | |
2404 | SCOPE_EXIT_EXPR(continue, "%s/%s: Same channel. Skipping\n", | |
2405 | ast_channel_name(bridge_channel->chan), | |
2406 | ast_channel_name(participant->chan)); | |
2407 | } | |
2349 | 2408 | |
2350 | 2409 | /* We add in all the new streams first so that they do not take the place |
2351 | 2410 | * of any of our removed streams, allowing the remote side to reset the state |
2352 | 2411 | * for each removed stream. */ |
2353 | if (append_all_streams(sc->topology, added_streams)) { | |
2354 | goto cleanup; | |
2355 | } | |
2412 | if (append_all_streams(participant_sc->topology, added_streams)) { | |
2413 | SCOPE_EXIT_EXPR(goto cleanup, "%s/%s: Couldn't append streams\n", ast_channel_name(bridge_channel->chan), | |
2414 | ast_channel_name(participant->chan)); | |
2415 | } | |
2416 | ast_trace(-1, "%s/%s: Adding streams %s\n", ast_channel_name(bridge_channel->chan), | |
2417 | ast_channel_name(participant->chan), | |
2418 | ast_str_tmp(256, ast_stream_topology_to_str(added_streams, &STR_TMP))); | |
2356 | 2419 | |
2357 | 2420 | /* Then we go through and remove any ones that were removed */ |
2358 | for (index = 0; removed_streams_count && index < ast_stream_topology_get_count(sc->topology); ++index) { | |
2421 | for (index = 0; | |
2422 | removed_streams_count && index < ast_stream_topology_get_count(sc->topology); ++index) { | |
2359 | 2423 | struct ast_stream *stream = ast_stream_topology_get_stream(sc->topology, index); |
2360 | 2424 | int removed_stream; |
2361 | 2425 | |
2362 | 2426 | for (removed_stream = 0; removed_stream < removed_streams_count; ++removed_stream) { |
2363 | if (is_video_dest(stream, ast_channel_name(bridge_channel->chan), removed_streams[removed_stream])) { | |
2427 | if (is_video_dest(stream, ast_channel_name(bridge_channel->chan), | |
2428 | removed_streams[removed_stream])) { | |
2429 | ast_trace(-1, "%s/%s: Removing stream %s\n", | |
2430 | ast_channel_name(bridge_channel->chan), | |
2431 | ast_channel_name(participant->chan), | |
2432 | ast_str_tmp(256, ast_stream_to_str(stream, &STR_TMP))); | |
2364 | 2433 | ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED); |
2365 | 2434 | } |
2366 | 2435 | } |
2367 | 2436 | } |
2368 | ||
2369 | ast_channel_request_stream_topology_change(participant->chan, sc->topology, NULL); | |
2370 | } | |
2371 | ||
2437 | ast_channel_request_stream_topology_change(participant->chan, participant_sc->topology, NULL); | |
2438 | SCOPE_EXIT("%s/%s: New participant topology %s\n", | |
2439 | ast_channel_name(bridge_channel->chan), | |
2440 | ast_channel_name(participant->chan), | |
2441 | ast_str_tmp(256, ast_stream_topology_to_str(participant_sc->topology, &STR_TMP))); | |
2442 | } | |
2443 | ||
2444 | ast_trace(-1, "%s: New topology %s\n", ast_channel_name(bridge_channel->chan), | |
2445 | ast_str_tmp(256, ast_stream_topology_to_str(sc->topology, &STR_TMP))); | |
2372 | 2446 | |
2373 | 2447 | cleanup: |
2374 | 2448 | ast_stream_topology_free(added_streams); |
2449 | SCOPE_EXIT(); | |
2375 | 2450 | } |
2376 | 2451 | |
2377 | 2452 | /*! |
2392 | 2467 | struct ast_vector_int media_types; |
2393 | 2468 | int nths[AST_MEDIA_TYPE_END] = {0}; |
2394 | 2469 | int idx; |
2470 | SCOPE_ENTER(3, "%s: \n", ast_channel_name(bridge_channel->chan)); | |
2395 | 2471 | |
2396 | 2472 | switch (bridge->softmix.video_mode.mode) { |
2397 | 2473 | case AST_BRIDGE_VIDEO_MODE_NONE: |
2399 | 2475 | case AST_BRIDGE_VIDEO_MODE_TALKER_SRC: |
2400 | 2476 | default: |
2401 | 2477 | ast_bridge_channel_stream_map(bridge_channel); |
2402 | return; | |
2478 | SCOPE_EXIT_RTN("%s: Not in SFU mode\n", ast_channel_name(bridge_channel->chan)); | |
2403 | 2479 | case AST_BRIDGE_VIDEO_MODE_SFU: |
2404 | 2480 | break; |
2405 | 2481 | } |
2509 | 2585 | } |
2510 | 2586 | |
2511 | 2587 | AST_VECTOR_FREE(&media_types); |
2588 | SCOPE_EXIT_RTN("%s\n", ast_channel_name(bridge_channel->chan)); | |
2512 | 2589 | } |
2513 | 2590 | |
2514 | 2591 | static struct ast_bridge_technology softmix_bridge = { |
2799 | 2799 | int error = 0; |
2800 | 2800 | |
2801 | 2801 | if (ast_strlen_zero(addr)) { |
2802 | ast_log(LOG_WARNING, "invalid calltokenoptional %s\n", addr); | |
2802 | ast_log(LOG_WARNING, "invalid calltokenoptional (null)\n"); | |
2803 | 2803 | return -1; |
2804 | 2804 | } |
2805 | 2805 |
1569 | 1569 | |
1570 | 1570 | static int on_topology_change_response(struct ast_sip_session *session, pjsip_rx_data *rdata) |
1571 | 1571 | { |
1572 | SCOPE_ENTER(3, "%s: Received response code %d. PT: %s AT: %s\n", ast_sip_session_get_name(session), | |
1573 | rdata->msg_info.msg->line.status.code, | |
1574 | ast_str_tmp(256, ast_stream_topology_to_str(session->pending_media_state->topology, &STR_TMP)), | |
1575 | ast_str_tmp(256, ast_stream_topology_to_str(session->active_media_state->topology, &STR_TMP))); | |
1576 | ||
1577 | ||
1572 | 1578 | if (PJSIP_IS_STATUS_IN_CLASS(rdata->msg_info.msg->line.status.code, 200)) { |
1573 | 1579 | /* The topology was changed to something new so give notice to what requested |
1574 | 1580 | * it so it queries the channel and updates accordingly. |
1575 | 1581 | */ |
1576 | 1582 | if (session->channel) { |
1577 | 1583 | ast_queue_control(session->channel, AST_CONTROL_STREAM_TOPOLOGY_CHANGED); |
1578 | } | |
1584 | SCOPE_EXIT_RTN_VALUE(0, "%s: Queued topology change frame\n", ast_sip_session_get_name(session)); | |
1585 | } | |
1586 | SCOPE_EXIT_RTN_VALUE(0, "%s: No channel? Can't queue topology change frame\n", ast_sip_session_get_name(session)); | |
1579 | 1587 | } else if (300 <= rdata->msg_info.msg->line.status.code) { |
1580 | 1588 | /* The topology change failed, so drop the current pending media state */ |
1581 | 1589 | ast_sip_session_media_state_reset(session->pending_media_state); |
1582 | } | |
1583 | ||
1584 | return 0; | |
1590 | SCOPE_EXIT_RTN_VALUE(0, "%s: response code > 300. Resetting pending media state\n", ast_sip_session_get_name(session)); | |
1591 | } | |
1592 | ||
1593 | SCOPE_EXIT_RTN_VALUE(0, "%s: Nothing to do\n", ast_sip_session_get_name(session)); | |
1585 | 1594 | } |
1586 | 1595 | |
1587 | 1596 | static int send_topology_change_refresh(void *data) |
1588 | 1597 | { |
1589 | 1598 | struct topology_change_refresh_data *refresh_data = data; |
1599 | struct ast_sip_session *session = refresh_data->session; | |
1590 | 1600 | int ret; |
1591 | ||
1592 | ret = ast_sip_session_refresh(refresh_data->session, NULL, NULL, on_topology_change_response, | |
1601 | SCOPE_ENTER(3, "%s: %s\n", ast_sip_session_get_name(session), | |
1602 | ast_str_tmp(256, ast_stream_topology_to_str(refresh_data->media_state->topology, &STR_TMP))); | |
1603 | ||
1604 | ||
1605 | ret = ast_sip_session_refresh(session, NULL, NULL, on_topology_change_response, | |
1593 | 1606 | AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, refresh_data->media_state); |
1594 | 1607 | refresh_data->media_state = NULL; |
1595 | 1608 | topology_change_refresh_data_free(refresh_data); |
1596 | 1609 | |
1597 | return ret; | |
1610 | SCOPE_EXIT_RTN_VALUE(ret, "%s\n", ast_sip_session_get_name(session)); | |
1598 | 1611 | } |
1599 | 1612 | |
1600 | 1613 | static int handle_topology_request_change(struct ast_sip_session *session, |
1626 | 1639 | size_t device_buf_size; |
1627 | 1640 | int i; |
1628 | 1641 | const struct ast_stream_topology *topology; |
1642 | struct ast_frame f = { | |
1643 | .frametype = AST_FRAME_CONTROL, | |
1644 | .subclass = { | |
1645 | .integer = condition | |
1646 | } | |
1647 | }; | |
1648 | char condition_name[256]; | |
1649 | SCOPE_ENTER(3, "%s: Indicated %s\n", ast_channel_name(ast), | |
1650 | ast_frame_subclass2str(&f, condition_name, sizeof(condition_name), NULL, 0)); | |
1629 | 1651 | |
1630 | 1652 | switch (condition) { |
1631 | 1653 | case AST_CONTROL_RINGING: |
1730 | 1752 | ao2_ref(channel->session, +1); |
1731 | 1753 | #ifdef HAVE_PJSIP_INV_SESSION_REF |
1732 | 1754 | if (pjsip_inv_add_ref(channel->session->inv_session) != PJ_SUCCESS) { |
1733 | ast_log(LOG_ERROR, "Can't increase the session reference counter\n"); | |
1734 | 1755 | ao2_cleanup(channel->session); |
1735 | return -1; | |
1756 | SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: Couldn't increase the session reference counter\n", | |
1757 | ast_channel_name(ast)); | |
1736 | 1758 | } |
1737 | 1759 | #endif |
1738 | 1760 | if (ast_sip_push_task(channel->session->serializer, update_connected_line_information, channel->session)) { |
1822 | 1844 | break; |
1823 | 1845 | case AST_CONTROL_STREAM_TOPOLOGY_REQUEST_CHANGE: |
1824 | 1846 | topology = data; |
1847 | ast_trace(-1, "%s: New topology: %s\n", ast_channel_name(ast), | |
1848 | ast_str_tmp(256, ast_stream_topology_to_str(topology, &STR_TMP))); | |
1825 | 1849 | res = handle_topology_request_change(channel->session, topology); |
1826 | 1850 | break; |
1827 | 1851 | case AST_CONTROL_STREAM_TOPOLOGY_CHANGED: |
1841 | 1865 | struct indicate_data *ind_data = indicate_data_alloc(channel->session, condition, response_code, data, datalen); |
1842 | 1866 | |
1843 | 1867 | if (!ind_data) { |
1844 | return -1; | |
1868 | SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: Couldn't alloc indicate data\n", ast_channel_name(ast)); | |
1845 | 1869 | } |
1846 | 1870 | #ifdef HAVE_PJSIP_INV_SESSION_REF |
1847 | 1871 | if (pjsip_inv_add_ref(ind_data->session->inv_session) != PJ_SUCCESS) { |
1848 | ast_log(LOG_ERROR, "Can't increase the session reference counter\n"); | |
1849 | 1872 | ao2_cleanup(ind_data); |
1850 | return -1; | |
1873 | SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: Couldn't increase the session reference counter\n", ast_channel_name(ast)); | |
1851 | 1874 | } |
1852 | 1875 | #endif |
1853 | 1876 | if (ast_sip_push_task(channel->session->serializer, indicate, ind_data)) { |
1854 | ast_log(LOG_NOTICE, "Cannot send response code %d to endpoint %s. Could not queue task properly\n", | |
1855 | response_code, ast_sorcery_object_get_id(channel->session->endpoint)); | |
1877 | ast_log(LOG_ERROR, "%s: Cannot send response code %d to endpoint %s. Could not queue task properly\n", | |
1878 | ast_channel_name(ast), response_code, ast_sorcery_object_get_id(channel->session->endpoint)); | |
1856 | 1879 | #ifdef HAVE_PJSIP_INV_SESSION_REF |
1857 | 1880 | pjsip_inv_dec_ref(ind_data->session->inv_session); |
1858 | 1881 | #endif |
1861 | 1884 | } |
1862 | 1885 | } |
1863 | 1886 | |
1864 | return res; | |
1887 | SCOPE_EXIT_RTN_VALUE(res, "%s\n", ast_channel_name(ast)); | |
1865 | 1888 | } |
1866 | 1889 | |
1867 | 1890 | struct transfer_data { |
3034 | 3057 | RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup); |
3035 | 3058 | struct transport_info_data *transport_data; |
3036 | 3059 | pjsip_tx_data *packet = NULL; |
3060 | SCOPE_ENTER(3, "%s\n", ast_sip_session_get_name(session)); | |
3037 | 3061 | |
3038 | 3062 | if (session->channel) { |
3039 | return 0; | |
3063 | SCOPE_EXIT_RTN_VALUE(0, "%s: No channel\n", ast_sip_session_get_name(session)); | |
3040 | 3064 | } |
3041 | 3065 | |
3042 | 3066 | /* Check for a to-tag to determine if this is a reinvite */ |
3052 | 3076 | */ |
3053 | 3077 | session->defer_terminate = 0; |
3054 | 3078 | ast_sip_session_terminate(session, 400); |
3055 | return -1; | |
3079 | SCOPE_EXIT_RTN_VALUE(-1, "%s: We have a To tag but no channel. Terminating session\n", ast_sip_session_get_name(session)); | |
3056 | 3080 | } |
3057 | 3081 | |
3058 | 3082 | datastore = ast_sip_session_alloc_datastore(&transport_info, "transport_info"); |
3059 | 3083 | if (!datastore) { |
3060 | return -1; | |
3084 | SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: Couldn't alloc transport_info datastore\n", ast_sip_session_get_name(session)); | |
3061 | 3085 | } |
3062 | 3086 | |
3063 | 3087 | transport_data = ast_calloc(1, sizeof(*transport_data)); |
3064 | 3088 | if (!transport_data) { |
3065 | return -1; | |
3089 | SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: Couldn't alloc transport_info\n", ast_sip_session_get_name(session)); | |
3066 | 3090 | } |
3067 | 3091 | pj_sockaddr_cp(&transport_data->local_addr, &rdata->tp_info.transport->local_addr); |
3068 | 3092 | pj_sockaddr_cp(&transport_data->remote_addr, &rdata->pkt_info.src_addr); |
3075 | 3099 | ast_sip_session_send_response(session, packet); |
3076 | 3100 | } |
3077 | 3101 | |
3078 | ast_log(LOG_ERROR, "Failed to allocate new PJSIP channel on incoming SIP INVITE\n"); | |
3079 | return -1; | |
3102 | SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: Failed to allocate new PJSIP channel on incoming SIP INVITE\n", | |
3103 | ast_sip_session_get_name(session)); | |
3080 | 3104 | } |
3081 | 3105 | /* channel gets created on incoming request, but we wait to call start |
3082 | 3106 | so other supplements have a chance to run */ |
3083 | return 0; | |
3107 | SCOPE_EXIT_RTN_VALUE(0, "%s\n", ast_sip_session_get_name(session)); | |
3084 | 3108 | } |
3085 | 3109 | |
3086 | 3110 | static int call_pickup_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata) |
3178 | 3202 | struct pjsip_status_line status = rdata->msg_info.msg->line.status; |
3179 | 3203 | struct ast_control_pvt_cause_code *cause_code; |
3180 | 3204 | int data_size = sizeof(*cause_code); |
3205 | SCOPE_ENTER(3, "%s: Status: %d\n", ast_sip_session_get_name(session), status.code); | |
3181 | 3206 | |
3182 | 3207 | if (!session->channel) { |
3183 | return; | |
3208 | SCOPE_EXIT_RTN("%s: No channel\n", ast_sip_session_get_name(session)); | |
3184 | 3209 | } |
3185 | 3210 | |
3186 | 3211 | /* Build and send the tech-specific cause information */ |
3200 | 3225 | |
3201 | 3226 | switch (status.code) { |
3202 | 3227 | case 180: |
3228 | ast_trace(-1, "%s: Queueing RINGING\n", ast_sip_session_get_name(session)); | |
3203 | 3229 | ast_queue_control(session->channel, AST_CONTROL_RINGING); |
3204 | 3230 | ast_channel_lock(session->channel); |
3205 | 3231 | if (ast_channel_state(session->channel) != AST_STATE_UP) { |
3208 | 3234 | ast_channel_unlock(session->channel); |
3209 | 3235 | break; |
3210 | 3236 | case 183: |
3237 | ast_trace(-1, "%s: Queueing PROGRESS\n", ast_sip_session_get_name(session)); | |
3211 | 3238 | if (session->endpoint->ignore_183_without_sdp) { |
3212 | 3239 | pjsip_rdata_sdp_info *sdp = pjsip_rdata_get_sdp_info(rdata); |
3213 | 3240 | if (sdp && sdp->body.ptr) { |
3218 | 3245 | } |
3219 | 3246 | break; |
3220 | 3247 | case 200: |
3248 | ast_trace(-1, "%s: Queueing ANSWER\n", ast_sip_session_get_name(session)); | |
3221 | 3249 | ast_queue_control(session->channel, AST_CONTROL_ANSWER); |
3222 | 3250 | break; |
3223 | 3251 | default: |
3224 | break; | |
3225 | } | |
3252 | ast_trace(-1, "%s: Not queueing anything\n", ast_sip_session_get_name(session)); | |
3253 | break; | |
3254 | } | |
3255 | ||
3256 | SCOPE_EXIT_RTN("%s\n", ast_sip_session_get_name(session)); | |
3226 | 3257 | } |
3227 | 3258 | |
3228 | 3259 | static int chan_pjsip_incoming_ack(struct ast_sip_session *session, struct pjsip_rx_data *rdata) |
3229 | 3260 | { |
3261 | SCOPE_ENTER(3, "%s\n", ast_sip_session_get_name(session)); | |
3262 | ||
3230 | 3263 | if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD) { |
3231 | 3264 | if (session->endpoint->media.direct_media.enabled && session->channel) { |
3265 | ast_trace(-1, "%s: Queueing SRCCHANGE\n", ast_sip_session_get_name(session)); | |
3232 | 3266 | ast_queue_control(session->channel, AST_CONTROL_SRCCHANGE); |
3233 | 3267 | } |
3234 | 3268 | } |
3235 | return 0; | |
3269 | SCOPE_EXIT_RTN_VALUE(0, "%s\n", ast_sip_session_get_name(session)); | |
3236 | 3270 | } |
3237 | 3271 | |
3238 | 3272 | static int update_devstate(void *obj, void *arg, int flags) |
23076 | 23076 | char tmp[512]; |
23077 | 23077 | char *c; |
23078 | 23078 | char oldnonce[256]; |
23079 | int start = 0; | |
23079 | 23080 | |
23080 | 23081 | /* table of recognised keywords, and places where they should be copied */ |
23081 | 23082 | const struct x { |
23090 | 23091 | { NULL, 0 }, |
23091 | 23092 | }; |
23092 | 23093 | |
23093 | ast_copy_string(tmp, sip_get_header(req, header), sizeof(tmp)); | |
23094 | if (ast_strlen_zero(tmp)) | |
23095 | return -1; | |
23094 | do { | |
23095 | ast_copy_string(tmp, __get_header(req, header, &start), sizeof(tmp)); | |
23096 | if (ast_strlen_zero(tmp)) | |
23097 | return -1; | |
23098 | } while (strcasestr(tmp, "algorithm=") && !strcasestr(tmp, "algorithm=MD5")); | |
23096 | 23099 | if (strncasecmp(tmp, "Digest ", strlen("Digest "))) { |
23097 | 23100 | ast_log(LOG_WARNING, "missing Digest.\n"); |
23098 | 23101 | return -1; |
28804 | 28807 | if (start && !found_supported) { |
28805 | 28808 | /* Format requested that we do not support */ |
28806 | 28809 | transmit_response(p, "406 Not Acceptable", req); |
28807 | ast_debug(2, "Received SIP mailbox subscription for unknown format: %s\n", accept); | |
28810 | ast_debug(2, "Received SIP mailbox subscription for unknown format\n"); | |
28808 | 28811 | pvt_set_needdestroy(p, "unknown format"); |
28809 | 28812 | if (authpeer) { |
28810 | 28813 | sip_unref_peer(authpeer, "sip_unref_peer, from handle_request_subscribe (authpeer 3)"); |
30384 | 30387 | |
30385 | 30388 | p_hdrval = sip_get_header(rsp, "Min-SE"); |
30386 | 30389 | if (ast_strlen_zero(p_hdrval)) { |
30387 | ast_log(LOG_WARNING, "422 response without a Min-SE header %s\n", p_hdrval); | |
30390 | ast_log(LOG_WARNING, "422 response without a Min-SE header\n"); | |
30388 | 30391 | return; |
30389 | 30392 | } |
30390 | 30393 | rtn = parse_minse(p_hdrval, &minse); |
263 | 263 | ; the highest maximum bitrate is forwarded to the sender. If set to "average_all" a single average |
264 | 264 | ; is generated from every receiver and the same value is sent to every sender. If set to |
265 | 265 | ; "lowest_all" the lowest maximum bitrate of all receivers is sent to every sender. If set to |
266 | ; "highest_all" the highest maximum bitrate of all receivers is sent to every sender. This | |
267 | ; defaults to "average". | |
266 | ; "highest_all" the highest maximum bitrate of all receivers is sent to every sender. | |
267 | ; When set to "force", the value set in remb_estimated_bitrate is sent to every sender. | |
268 | ; This defaults to "average". | |
269 | ;remb_estimated_bitrate=0 ; When remb_behavior is set to 'force', this options sets the estimated bitrate | |
270 | ; (in bits per second) sent to each participant in REMB reports. | |
268 | 271 | |
269 | 272 | ;enable_events=no ; If enabled, recipients who joined the bridge via a channel driver |
270 | 273 | ; that supports Enhanced Messaging (currently only chan_pjsip) will |
25 | 25 | ; By default, this is 2. |
26 | 26 | ;transferdialattempts = 3 ; Number of times that a transferer may attempt to dial an extension before |
27 | 27 | ; being kicked back to the original call. |
28 | ;transferretrysound = "beep" ; Sound to play when a transferer fails to dial a valid extension. | |
29 | ;transferinvalidsound = "beeperr" ; Sound to play when a transferer fails to dial a valid extension and is out of retries. | |
28 | ;transferretrysound = beep ; Sound to play when a transferer fails to dial a valid extension. | |
29 | ;transferinvalidsound = beeperr ; Sound to play when a transferer fails to dial a valid extension and is out of retries. | |
30 | 30 | ;atxferabort = *1 ; cancel the attended transfer |
31 | 31 | ;atxfercomplete = *2 ; complete the attended transfer, dropping out of the call |
32 | 32 | ;atxferthreeway = *3 ; complete the attended transfer, but stay in the call. This will turn the call into a multi-party bridge |
12 | 12 | ; valid mode options: |
13 | 13 | ; files -- read files from a directory in any Asterisk supported |
14 | 14 | ; media format |
15 | ; playlist -- provide a fixed list of filenames or URLs to play | |
15 | ; playlist -- provide a fixed list of filenames or HTTP(S) URLs to play | |
16 | 16 | ; quietmp3 -- default |
17 | 17 | ; mp3 -- loud |
18 | 18 | ; mp3nb -- unbuffered |
55 | 55 | ; Each entry must be one of: |
56 | 56 | ; |
57 | 57 | ; * An absolute path to the file to be played, without an extension. |
58 | ; * A URL | |
58 | ; * An HTTP(S) URL | |
59 | 59 | ; |
60 | 60 | ; The entries are played in the order in which they appear in the |
61 | 61 | ; configuration. The 'sort' option is not used for this mode. |
332 | 332 | ;device_state_busy_at=1 |
333 | 333 | ;allow_subscribe=yes |
334 | 334 | ;sub_min_expiry=30 |
335 | ; | |
336 | ; STIR/SHAKEN support. | |
337 | ; | |
338 | ;stir_shaken=no | |
335 | 339 | |
336 | 340 | ;[6001] |
337 | 341 | ;type=auth |
855 | 859 | ; chan_sip and prevents these 183 responses from |
856 | 860 | ; being forwarded. |
857 | 861 | ; (default: no) |
862 | ;stir_shaken = | |
863 | ; If this is enabled, STIR/SHAKEN operations will be | |
864 | ; performed on this endpoint. This includes inbound | |
865 | ; and outbound INVITEs. On an inbound INVITE, Asterisk | |
866 | ; will check for an Identity header and attempt to | |
867 | ; verify the call. On an outbound INVITE, Asterisk will | |
868 | ; add an Identity header that others can use to verify | |
869 | ; calls from this endpoint. Additional configuration is | |
870 | ; done in stir_shaken.conf. | |
871 | ; The STIR_SHAKEN dialplan function must be used to get | |
872 | ; the verification results on inbound INVITEs. Nothing | |
873 | ; happens to the call if verification fails; it's up to | |
874 | ; you to determine what to do with the results. | |
875 | ; (default: no) | |
858 | 876 | |
859 | 877 | ;==========================AUTH SECTION OPTIONS========================= |
860 | 878 | ;[auth] |
5 | 5 | proxytype=http |
6 | 6 | proxyport=8001 |
7 | 7 | ;proxyuserpwd=asterisk:asteriskrocks |
8 | ;failurecodes=404,408,503 |
0 | ; | |
1 | ; This file is used by the res_stir_shaken module to configure parameters | |
2 | ; used for STIR/SHAKEN. | |
3 | ; | |
4 | ; | |
5 | ; [general] | |
6 | ; | |
7 | ; File path to the certificate authority certificate | |
8 | ;ca_file=/etc/asterisk/stir/ca.crt | |
9 | ; | |
10 | ; File path to a chain of trust | |
11 | ;ca_path=/etc/asterisk/stir/ca | |
12 | ; | |
13 | ; Maximum size to use for caching public keys | |
14 | ;cache_max_size=1000 | |
15 | ; | |
16 | ; Maximum time (in seconds) to wait to CURL certificates | |
17 | ;curl_timeout=2 | |
18 | ; | |
19 | ; Amount of time (in seconds) a signature is valid for | |
20 | ;signature_timeout=15 | |
21 | ; | |
22 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
23 | ; | |
24 | ; A certificate store is used to examine, and load all certificates found in a | |
25 | ; given directory. When using this type the public key URL is generated based | |
26 | ; upon the filename, and variable substitution. | |
27 | ;[certificates] | |
28 | ; | |
29 | ; type must be "store" | |
30 | ;type=store | |
31 | ; | |
32 | ; Path to a directory containing certificates | |
33 | ;path=/etc/asterisk/stir | |
34 | ; | |
35 | ; URL to the public key(s). Must contain variable '${CERTIFICATE}' used for | |
36 | ; substitution | |
37 | ;public_key_url=http://mycompany.com/${CERTIFICATE}.pub | |
38 | ; | |
39 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
40 | ; | |
41 | ; Individual certificates are declared by using the certificate type. | |
42 | ;[alice] | |
43 | ; | |
44 | ; type must be "certificate" | |
45 | ;type=certificate | |
46 | ; | |
47 | ; File path to a certificate | |
48 | ;path=/etc/asterisk/stir/alice.crt | |
49 | ; | |
50 | ; URL to the public key | |
51 | ;public_key_url=http://mycompany.com/alice.pub | |
52 | ; | |
53 | ; The caller ID number to match on | |
54 | ;caller_id_number=1234567 | |
55 | ; | |
56 | ; Must have an attestation of A, B, or C | |
57 | ;attestation=C | |
58 | ; | |
59 | ; The origination identifier for the certificate | |
60 | ;origid=MyAsterisk |
29511 | 29511 | |
29512 | 29512 | |
29513 | 29513 | |
29514 | for ver in 5.3 5.2 5.1; do | |
29514 | for ver in 5.4 5.3 5.2 5.1; do | |
29515 | 29515 | |
29516 | 29516 | if test "x${PBX_LUA}" != "x1" -a "${USE_LUA}" != "no"; then |
29517 | 29517 | pbxlibdir="" |
2552 | 2552 | fi |
2553 | 2553 | AST_EXT_LIB_CHECK([OPUSFILE], [opusfile], [op_open_callbacks], [opus/opusfile.h], [], [$__opus_include]) |
2554 | 2554 | |
2555 | for ver in 5.3 5.2 5.1; do | |
2555 | for ver in 5.4 5.3 5.2 5.1; do | |
2556 | 2556 | AST_EXT_LIB_CHECK([LUA], lua${ver}, [luaL_newstate], lua${ver}/lua.h, [-lm]) |
2557 | 2557 | if test "x${PBX_LUA}" = "x1" ; then |
2558 | 2558 | if test x"${LUA_DIR}" = x; then |
0 | """increse reg server size | |
1 | ||
2 | Revision ID: 1ae0609b6646 | |
3 | Revises: 79290b511e4b | |
4 | Create Date: 2020-08-31 13:50:19.772439 | |
5 | ||
6 | """ | |
7 | ||
8 | # revision identifiers, used by Alembic. | |
9 | revision = '1ae0609b6646' | |
10 | down_revision = '79290b511e4b' | |
11 | ||
12 | from alembic import op | |
13 | import sqlalchemy as sa | |
14 | ||
15 | ||
16 | def upgrade(): | |
17 | op.alter_column('ps_contacts', 'reg_server', type_=sa.String(255)) | |
18 | ||
19 | ||
20 | def downgrade(): | |
21 | op.alter_column('ps_contacts', 'reg_server', type_=sa.String(20)) |
0 | """add stir shaken | |
1 | ||
2 | Revision ID: 61797b9fced6 | |
3 | Revises: fbb7766f17bc | |
4 | Create Date: 2020-06-29 11:52:59.946929 | |
5 | ||
6 | """ | |
7 | ||
8 | # revision identifiers, used by Alembic. | |
9 | revision = '61797b9fced6' | |
10 | down_revision = 'e658c26033ca' | |
11 | ||
12 | from alembic import op | |
13 | import sqlalchemy as sa | |
14 | from sqlalchemy.dialects.postgresql import ENUM | |
15 | ||
16 | YESNO_NAME = 'yesno_values' | |
17 | YESNO_VALUES = ['yes', 'no'] | |
18 | ||
19 | AST_BOOL_NAME = 'ast_bool_values' | |
20 | AST_BOOL_VALUES = [ '0', '1', | |
21 | 'off', 'on', | |
22 | 'false', 'true', | |
23 | 'no', 'yes' ] | |
24 | ||
25 | def upgrade(): | |
26 | ast_bool_values = ENUM(*AST_BOOL_VALUES, name=AST_BOOL_NAME, create_type=False) | |
27 | op.add_column('ps_endpoints', sa.Column('stir_shaken', ast_bool_values)) | |
28 | ||
29 | def downgrade(): | |
30 | op.drop_column('ps_endpoints', 'stir_shaken') |
0 | """create history info flag | |
1 | ||
2 | Revision ID: e658c26033ca | |
3 | Revises: 1ae0609b6646 | |
4 | Create Date: 2020-08-13 10:53:21.032591 | |
5 | ||
6 | """ | |
7 | ||
8 | # revision identifiers, used by Alembic. | |
9 | revision = 'e658c26033ca' | |
10 | down_revision = '1ae0609b6646' | |
11 | ||
12 | from alembic import op | |
13 | import sqlalchemy as sa | |
14 | from sqlalchemy.dialects.postgresql import ENUM | |
15 | ||
16 | AST_BOOL_NAME = 'ast_bool_values' | |
17 | # We'll just ignore the n/y and f/t abbreviations as Asterisk does not write | |
18 | # those aliases. | |
19 | AST_BOOL_VALUES = [ '0', '1', | |
20 | 'off', 'on', | |
21 | 'false', 'true', | |
22 | 'no', 'yes' ] | |
23 | ||
24 | def upgrade(): | |
25 | ############################# Enums ############################## | |
26 | ||
27 | # ast_bool_values has already been created, so use postgres enum object | |
28 | # type to get around "already created" issue - works okay with mysql | |
29 | ast_bool_values = ENUM(*AST_BOOL_VALUES, name=AST_BOOL_NAME, create_type=False) | |
30 | ||
31 | op.add_column('ps_endpoints', sa.Column('send_history_info', ast_bool_values)) | |
32 | ||
33 | ||
34 | def downgrade(): | |
35 | if op.get_context().bind.dialect.name == 'mssql': | |
36 | op.drop_constraint('ck_ps_endpoints_send_history_info_ast_bool_values','ps_endpoints') | |
37 | op.drop_column('ps_endpoints', 'send_history_info') |
1255 | 1255 | |
1256 | 1256 | UPDATE alembic_version SET version_num='79290b511e4b' WHERE alembic_version.version_num = 'fbb7766f17bc'; |
1257 | 1257 | |
1258 | -- Running upgrade 79290b511e4b -> 1ae0609b6646 | |
1259 | ||
1260 | ALTER TABLE ps_contacts MODIFY reg_server VARCHAR(255) NULL; | |
1261 | ||
1262 | UPDATE alembic_version SET version_num='1ae0609b6646' WHERE alembic_version.version_num = '79290b511e4b'; | |
1263 | ||
1264 | -- Running upgrade 1ae0609b6646 -> e658c26033ca | |
1265 | ||
1266 | ALTER TABLE ps_endpoints ADD COLUMN send_history_info ENUM('0','1','off','on','false','true','no','yes'); | |
1267 | ||
1268 | UPDATE alembic_version SET version_num='e658c26033ca' WHERE alembic_version.version_num = '1ae0609b6646'; | |
1269 | ||
1270 | -- Running upgrade e658c26033ca -> 61797b9fced6 | |
1271 | ||
1272 | ALTER TABLE ps_endpoints ADD COLUMN stir_shaken ENUM('0','1','off','on','false','true','no','yes'); | |
1273 | ||
1274 | UPDATE alembic_version SET version_num='61797b9fced6' WHERE alembic_version.version_num = 'e658c26033ca'; | |
1275 |
1357 | 1357 | |
1358 | 1358 | UPDATE alembic_version SET version_num='79290b511e4b' WHERE alembic_version.version_num = 'fbb7766f17bc'; |
1359 | 1359 | |
1360 | -- Running upgrade 79290b511e4b -> 1ae0609b6646 | |
1361 | ||
1362 | ALTER TABLE ps_contacts ALTER COLUMN reg_server TYPE VARCHAR(255); | |
1363 | ||
1364 | UPDATE alembic_version SET version_num='1ae0609b6646' WHERE alembic_version.version_num = '79290b511e4b'; | |
1365 | ||
1366 | -- Running upgrade 1ae0609b6646 -> e658c26033ca | |
1367 | ||
1368 | ALTER TABLE ps_endpoints ADD COLUMN send_history_info ast_bool_values; | |
1369 | ||
1370 | UPDATE alembic_version SET version_num='e658c26033ca' WHERE alembic_version.version_num = '1ae0609b6646'; | |
1371 | ||
1372 | -- Running upgrade e658c26033ca -> 61797b9fced6 | |
1373 | ||
1374 | ALTER TABLE ps_endpoints ADD COLUMN stir_shaken ast_bool_values; | |
1375 | ||
1376 | UPDATE alembic_version SET version_num='61797b9fced6' WHERE alembic_version.version_num = 'e658c26033ca'; | |
1377 | ||
1360 | 1378 | COMMIT; |
1361 | 1379 |
23 | 23 | PACKAGES_DEBIAN="$PACKAGES_DEBIAN libedit-dev libjansson-dev libsqlite3-dev uuid-dev libxml2-dev" |
24 | 24 | # Asterisk: for addons: |
25 | 25 | PACKAGES_DEBIAN="$PACKAGES_DEBIAN libspeex-dev libspeexdsp-dev libogg-dev libvorbis-dev libasound2-dev portaudio19-dev libcurl4-openssl-dev xmlstarlet bison flex" |
26 | PACKAGES_DEBIAN="$PACKAGES_DEBIAN libpq-dev unixodbc-dev libneon27-dev libgmime-2.6-dev liblua5.2-dev liburiparser-dev libxslt1-dev libssl-dev" | |
26 | PACKAGES_DEBIAN="$PACKAGES_DEBIAN libpq-dev unixodbc-dev libneon27-dev libgmime-2.6-dev libgmime-3.0-dev liblua5.2-dev liburiparser-dev libxslt1-dev libssl-dev" | |
27 | 27 | PACKAGES_DEBIAN="$PACKAGES_DEBIAN libvpb-dev libmysqlclient-dev libbluetooth-dev libradcli-dev freetds-dev libosptk-dev libjack-jackd2-dev bash libcap-dev" |
28 | 28 | PACKAGES_DEBIAN="$PACKAGES_DEBIAN libsnmp-dev libiksemel-dev libcorosync-common-dev libcpg-dev libcfg-dev libnewt-dev libpopt-dev libical-dev libspandsp-dev" |
29 | 29 | PACKAGES_DEBIAN="$PACKAGES_DEBIAN libresample1-dev libc-client2007e-dev binutils-dev libsrtp0-dev libsrtp2-dev libgsm1-dev doxygen graphviz zlib1g-dev libldap2-dev" |
5 | 5 | """ |
6 | 6 | |
7 | 7 | import re |
8 | import glob | |
8 | 9 | import itertools |
9 | 10 | |
10 | 11 | from astdicts import OrderedDict |
55 | 56 | Use self.id as means of determining equality |
56 | 57 | """ |
57 | 58 | return self.id == other.id |
59 | ||
60 | def __lt__(self, other): | |
61 | """ | |
62 | Use self.id as means of determining equality | |
63 | """ | |
64 | return self.id < other.id | |
65 | ||
66 | def __gt__(self, other): | |
67 | """ | |
68 | Use self.id as means of determining equality | |
69 | """ | |
70 | return self.id > other.id | |
71 | ||
72 | def __le__(self, other): | |
73 | """ | |
74 | Use self.id as means of determining equality | |
75 | """ | |
76 | return self.id <= other.id | |
77 | ||
78 | def __ge__(self, other): | |
79 | """ | |
80 | Use self.id as means of determining equality | |
81 | """ | |
82 | return self.id >= other.id | |
58 | 83 | |
59 | 84 | def get(self, key, from_self=True, from_templates=True, |
60 | 85 | from_defaults=True): |
214 | 239 | included filename, otherwise None. |
215 | 240 | """ |
216 | 241 | |
217 | match = re.match('^#include\s*[<"]?(.*)[>"]?$', line) | |
218 | return match.group(1) if match else None | |
242 | match = re.match('^#include\s*([^;]+).*$', line) | |
243 | if match: | |
244 | trimmed = match.group(1).rstrip() | |
245 | quoted = re.match('^"([^"]+)"$', trimmed) | |
246 | if quoted: | |
247 | return quoted.group(1) | |
248 | bracketed = re.match('^<([^>]+)>$', trimmed) | |
249 | if bracketed: | |
250 | return bracketed.group(1) | |
251 | return trimmed | |
252 | return None | |
219 | 253 | |
220 | 254 | |
221 | 255 | def try_section(line): |
457 | 491 | |
458 | 492 | include_name = try_include(line) |
459 | 493 | if include_name: |
460 | parser = self.add_include(include_name) | |
461 | parser.read(include_name, sect) | |
494 | for incl in sorted(glob.iglob(include_name)): | |
495 | parser = self.add_include(incl) | |
496 | parser.read(incl, sect) | |
462 | 497 | continue |
463 | 498 | |
464 | 499 | section, is_template, templates = try_section(line) |
186 | 186 | </enum> |
187 | 187 | </enumlist> |
188 | 188 | </enum> |
189 | <enum name="failurecodes"> | |
190 | <para>A comma separated list of HTTP response codes to be treated as errors</para> | |
191 | </enum> | |
189 | 192 | </enumlist> |
190 | 193 | </parameter> |
191 | 194 | </syntax> |
204 | 207 | ((LIBCURL_VERSION_MAJOR > (a)) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR > (b))) || ((LIBCURL_VERSION_MAJOR == (a)) && (LIBCURL_VERSION_MINOR == (b)) && (LIBCURL_VERSION_PATCH >= (c)))) |
205 | 208 | |
206 | 209 | #define CURLOPT_SPECIAL_HASHCOMPAT ((CURLoption) -500) |
210 | ||
211 | #define CURLOPT_SPECIAL_FAILURE_CODE 999 | |
207 | 212 | |
208 | 213 | static void curlds_free(void *data); |
209 | 214 | |
317 | 322 | } else if (!strcasecmp(name, "hashcompat")) { |
318 | 323 | *key = CURLOPT_SPECIAL_HASHCOMPAT; |
319 | 324 | *ot = OT_ENUM; |
325 | } else if (!strcasecmp(name, "failurecodes")) { | |
326 | *key = CURLOPT_SPECIAL_FAILURE_CODE; | |
327 | *ot = OT_STRING; | |
320 | 328 | } else { |
321 | 329 | return -1; |
322 | 330 | } |
654 | 662 | static int acf_curl_helper(struct ast_channel *chan, struct curl_args *args) |
655 | 663 | { |
656 | 664 | struct ast_str *escapebuf = ast_str_thread_get(&thread_escapebuf, 16); |
657 | int ret = -1; | |
665 | int ret = 0; | |
666 | long http_code = 0; /* read curl response */ | |
667 | size_t i; | |
668 | struct ast_vector_int hasfailurecode = { NULL }; | |
669 | char *failurecodestrings,*found; | |
658 | 670 | CURL **curl; |
659 | 671 | struct curl_settings *cur; |
660 | 672 | struct curl_slist *headers = NULL; |
681 | 693 | ast_autoservice_start(chan); |
682 | 694 | } |
683 | 695 | |
696 | AST_VECTOR_INIT(&hasfailurecode, 0); /*Initialize vector*/ | |
684 | 697 | AST_LIST_LOCK(&global_curl_info); |
685 | 698 | AST_LIST_TRAVERSE(&global_curl_info, cur, list) { |
686 | 699 | if (cur->key == CURLOPT_SPECIAL_HASHCOMPAT) { |
687 | 700 | hashcompat = (long) cur->value; |
688 | 701 | } else if (cur->key == CURLOPT_HTTPHEADER) { |
689 | 702 | headers = curl_slist_append(headers, (char*) cur->value); |
703 | } else if (cur->key == CURLOPT_SPECIAL_FAILURE_CODE) { | |
704 | failurecodestrings = (char*) cur->value; | |
705 | while( (found = strsep(&failurecodestrings, ",")) != NULL) { | |
706 | AST_VECTOR_APPEND(&hasfailurecode, atoi(found)); | |
707 | } | |
690 | 708 | } else { |
691 | 709 | curl_easy_setopt(*curl, cur->key, cur->value); |
692 | 710 | } |
705 | 723 | hashcompat = (long) cur->value; |
706 | 724 | } else if (cur->key == CURLOPT_HTTPHEADER) { |
707 | 725 | headers = curl_slist_append(headers, (char*) cur->value); |
726 | } else if (cur->key == CURLOPT_SPECIAL_FAILURE_CODE) { | |
727 | failurecodestrings = (char*) cur->value; | |
728 | while( (found = strsep(&failurecodestrings, ",")) != NULL) { | |
729 | AST_VECTOR_APPEND(&hasfailurecode, atoi(found)); | |
730 | } | |
708 | 731 | } else { |
709 | 732 | curl_easy_setopt(*curl, cur->key, cur->value); |
710 | 733 | } |
720 | 743 | curl_easy_setopt(*curl, CURLOPT_POSTFIELDS, args->postdata); |
721 | 744 | } |
722 | 745 | |
723 | if (headers) { | |
724 | curl_easy_setopt(*curl, CURLOPT_HTTPHEADER, headers); | |
725 | } | |
746 | /* Always assign the headers - even when NULL - in case we had | |
747 | * custom headers the last time we used this shared cURL | |
748 | * instance */ | |
749 | curl_easy_setopt(*curl, CURLOPT_HTTPHEADER, headers); | |
726 | 750 | |
727 | 751 | /* Temporarily assign a buffer for curl to write errors to. */ |
728 | 752 | curl_errbuf[0] = curl_errbuf[CURL_ERROR_SIZE] = '\0'; |
737 | 761 | * here, but the source allows it. See: "typecheck: allow NULL to unset |
738 | 762 | * CURLOPT_ERRORBUFFER" (62bcf005f4678a93158358265ba905bace33b834). */ |
739 | 763 | curl_easy_setopt(*curl, CURLOPT_ERRORBUFFER, (char*)NULL); |
764 | curl_easy_getinfo (*curl, CURLINFO_RESPONSE_CODE, &http_code); | |
765 | ||
766 | for (i = 0; i < AST_VECTOR_SIZE(&hasfailurecode); ++i) { | |
767 | if (http_code == AST_VECTOR_GET(&hasfailurecode,i)){ | |
768 | ast_log(LOG_NOTICE, "%s%sCURL '%s' returned response code (%ld).\n", | |
769 | chan ? ast_channel_name(chan) : "", | |
770 | chan ? ast_channel_name(chan) : ": ", | |
771 | args->url, | |
772 | http_code); | |
773 | ret=-1; | |
774 | break; | |
775 | } | |
776 | } | |
777 | AST_VECTOR_FREE(&hasfailurecode); /* Release the vector*/ | |
740 | 778 | |
741 | 779 | if (store) { |
742 | 780 | AST_LIST_UNLOCK(list); |
773 | 811 | ast_free(fields); |
774 | 812 | ast_free(values); |
775 | 813 | } |
776 | ret = 0; | |
777 | 814 | } |
778 | 815 | |
779 | 816 | if (chan) { |
883 | 920 | " ssl_verifypeer - Whether to verify the peer certificate (boolean)\n" |
884 | 921 | " hashcompat - Result data will be compatible for use with HASH()\n" |
885 | 922 | " - if value is \"legacy\", will translate '+' to ' '\n" |
923 | " failurecodes - A comma separated list of HTTP response codes to be treated as errors\n" | |
886 | 924 | "", |
887 | 925 | .read = acf_curlopt_read, |
888 | 926 | .read2 = acf_curlopt_read2, |
1180 | 1180 | static int init_acf_query(struct ast_config *cfg, char *catg, struct acf_odbc_query **query) |
1181 | 1181 | { |
1182 | 1182 | const char *tmp; |
1183 | const char *tmp2; | |
1183 | const char *tmp2 = NULL; | |
1184 | 1184 | int i; |
1185 | 1185 | |
1186 | 1186 | if (!cfg || !catg) { |
139 | 139 | AST_BRIDGE_VIDEO_SFU_REMB_LOWEST_ALL, |
140 | 140 | /*! The highest reported bitrate from all channels in the bridge is forwarded to each sender */ |
141 | 141 | AST_BRIDGE_VIDEO_SFU_REMB_HIGHEST_ALL, |
142 | /*! Force the REMB estimated bitrate to a specified value */ | |
143 | AST_BRIDGE_VIDEO_SFU_REMB_FORCE, | |
142 | 144 | }; |
143 | 145 | |
144 | 146 | /*! \brief This is used for selective forwarding unit configuration */ |
147 | 149 | unsigned int remb_send_interval; |
148 | 150 | /*! How the combined REMB report is generated */ |
149 | 151 | enum ast_bridge_video_sfu_remb_behavior remb_behavior; |
152 | /*! The estimated bitrate when behavior is "force" */ | |
153 | float estimated_bitrate; | |
150 | 154 | }; |
151 | 155 | |
152 | 156 | /*! \brief Data structure that defines a video source mode */ |
988 | 992 | void ast_brige_set_remb_behavior(struct ast_bridge *bridge, enum ast_bridge_video_sfu_remb_behavior behavior); |
989 | 993 | |
990 | 994 | /*! |
995 | * \brief Force the REMB report estimated bitrate to a specific max value | |
996 | * | |
997 | * \param bridge Bridge to set the REMB behavior on | |
998 | * \param estimated_bitrate The estimated bitrate in bits per second | |
999 | * | |
1000 | * \note This can only be called when the bridge has been set to the SFU video mode. | |
1001 | */ | |
1002 | void ast_bridge_set_remb_estimated_bitrate(struct ast_bridge *bridge, float estimated_bitrate); | |
1003 | ||
1004 | /*! | |
991 | 1005 | * \brief Update information about talker energy for talker src video mode. |
992 | 1006 | */ |
993 | 1007 | void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct ast_channel *chan, int talker_energy, int is_keyfame); |
193 | 193 | }; |
194 | 194 | |
195 | 195 | /*! |
196 | * \brief Get a ref to the bridge_channel's ast_channel | |
197 | * | |
198 | * \param bridge_channel The bridge channel | |
199 | * | |
200 | * \note The returned channel NEEDS to be unref'd once you are done with it. In general, this | |
201 | * function is best used when accessing the bridge_channel chan from outside of a bridging | |
202 | * thread. | |
203 | * | |
204 | * \retval ref'd ast_channel on success | |
205 | * \retval NULL otherwise | |
206 | */ | |
207 | struct ast_channel *ast_bridge_channel_get_chan(struct ast_bridge_channel *bridge_channel); | |
208 | ||
209 | /*! | |
196 | 210 | * \brief Try locking the bridge_channel. |
197 | 211 | * |
198 | 212 | * \param bridge_channel What to try locking |
313 | 313 | */ |
314 | 314 | int ast_format_cache_is_slinear(struct ast_format *format); |
315 | 315 | |
316 | /*! | |
317 | * \brief Retrieve a format from the cache by its codec | |
318 | * | |
319 | * \param codec The codec to search by | |
320 | * | |
321 | * \retval non-NULL if found | |
322 | * \retval NULL if not found | |
323 | * | |
324 | * \note The returned format has its reference count incremented. It must be | |
325 | * dropped using ao2_ref or ao2_cleanup. | |
326 | */ | |
327 | struct ast_format *ast_format_cache_get_by_codec(const struct ast_codec *codec); | |
328 | ||
316 | 329 | #endif /* _AST_FORMAT_CACHE_H */ |
662 | 662 | */ |
663 | 663 | #define ast_trace_raw(level, indent_type, ...) \ |
664 | 664 | ast_debug(level < 0 ? __scope_level : level, " " __VA_ARGS__); \ |
665 | if (TRACE_ATLEAST(level)) { \ | |
665 | if (TRACE_ATLEAST(level < 0 ? __scope_level : level)) { \ | |
666 | 666 | __ast_trace(__FILE__, __LINE__, __PRETTY_FUNCTION__, indent_type, 0, " " __VA_ARGS__); \ |
667 | 667 | } |
668 | 668 | |
677 | 677 | */ |
678 | 678 | #define ast_trace(level, ...) \ |
679 | 679 | ast_debug(level < 0 ? __scope_level : level, " " __VA_ARGS__); \ |
680 | if (TRACE_ATLEAST(level)) { \ | |
680 | if (TRACE_ATLEAST(level < 0 ? __scope_level : level)) { \ | |
681 | 681 | __ast_trace(__FILE__, __LINE__, __PRETTY_FUNCTION__, AST_TRACE_INDENT_SAME, 0, " " __VA_ARGS__); \ |
682 | 682 | } |
683 | 683 |
0 | /* | |
1 | * Asterisk -- An open source telephony toolkit. | |
2 | * | |
3 | * Copyright (C) 2020, Sangoma Technologies Corporation | |
4 | * | |
5 | * Kevin Harwell <kharwell@sangoma.com> | |
6 | * | |
7 | * See http://www.asterisk.org for more information about | |
8 | * the Asterisk project. Please do not directly contact | |
9 | * any of the maintainers of this project for assistance; | |
10 | * the project provides a web site, mailing lists and IRC | |
11 | * channels for your use. | |
12 | * | |
13 | * This program is free software, distributed under the terms of | |
14 | * the GNU General Public License Version 2. See the LICENSE file | |
15 | * at the top of the source tree. | |
16 | */ | |
17 | #ifndef ASTERISK_LOGGER_CATEGORY_H | |
18 | #define ASTERISK_LOGGER_CATEGORY_H | |
19 | ||
20 | #include "asterisk/logger.h" | |
21 | ||
22 | /*! | |
23 | * Logger category is enabled | |
24 | */ | |
25 | #define AST_LOG_CATEGORY_ENABLED -1 | |
26 | ||
27 | /*! | |
28 | * Logger category is disabled | |
29 | */ | |
30 | #define AST_LOG_CATEGORY_DISABLED 0 | |
31 | ||
32 | /*! | |
33 | * \brief Load/Initialize system wide logger category functionality | |
34 | * | |
35 | * \retval 0 Success, -1 Failure | |
36 | * | |
37 | * \since 16.14 | |
38 | * \since 17.8 | |
39 | * \since 18.0 | |
40 | */ | |
41 | int ast_logger_category_load(void); | |
42 | ||
43 | /*! | |
44 | * \brief Unload system wide logger category functionality | |
45 | * | |
46 | * \retval 0 Success, -1 Failure | |
47 | * | |
48 | * \since 16.14 | |
49 | * \since 17.8 | |
50 | * \since 18.0 | |
51 | */ | |
52 | int ast_logger_category_unload(void); | |
53 | ||
54 | /*! | |
55 | * \brief Register a debug level logger category | |
56 | * | |
57 | * \param name The name of the category | |
58 | * \param id The unique id of the category | |
59 | * | |
60 | * \retval 0 if failed to register/retrieve an id. Otherwise it returns the id | |
61 | * for the registered category. | |
62 | * | |
63 | * \since 16.14 | |
64 | * \since 17.8 | |
65 | * \since 18.0 | |
66 | */ | |
67 | uintmax_t ast_debug_category_register(const char *name); | |
68 | ||
69 | /*! | |
70 | * \brief Un-register a debug level logger category | |
71 | * | |
72 | * \retval 0 Success, -1 Failure | |
73 | * | |
74 | * \since 16.14 | |
75 | * \since 17.8 | |
76 | * \since 18.0 | |
77 | */ | |
78 | int ast_debug_category_unregister(const char *name); | |
79 | ||
80 | /*! | |
81 | * \brief Set the debug category's sublevel | |
82 | * | |
83 | * Statements are output at a specified sublevel. Typically any number greater | |
84 | * than or equal to 0. Other acceptable values include AST_LOG_CATEGORY_ENABLED | |
85 | * and AST_LOG_CATEGORY_DISABLED. | |
86 | * | |
87 | * \param name The name of the category | |
88 | * \param sublevel The debug sublevel output number | |
89 | * | |
90 | * \retval 0 Success, -1 Failure | |
91 | * | |
92 | * \since 16.14 | |
93 | * \since 17.8 | |
94 | * \since 18.0 | |
95 | */ | |
96 | int ast_debug_category_set_sublevel(const char *name, int sublevel); | |
97 | ||
98 | /*! | |
99 | * \brief Set one or more debug category's sublevel. | |
100 | * | |
101 | * Accepts an array of category names, and optional associated sublevels. Sublevels can | |
102 | * be associated with a name by using a ':' as a separator. For example: | |
103 | * | |
104 | * <category name>:<category sublevel> | |
105 | * | |
106 | * The given default sublevel is used if no sublevel is associated with a name. | |
107 | * | |
108 | * \param names An array of category names | |
109 | * \param size The size of the array (number of elements) | |
110 | * \param default_sublevel The sublevel value to use if one is not associated with a name | |
111 | * | |
112 | * \retval 0 Success, -1 Failure | |
113 | * | |
114 | * \since 16.14 | |
115 | * \since 17.8 | |
116 | * \since 18.0 | |
117 | */ | |
118 | int ast_debug_category_set_sublevels(const char * const *names, size_t size, int default_sublevel); | |
119 | ||
120 | /*! | |
121 | * \brief Add a unique (no duplicates) result to a request for completion for debug categories. | |
122 | * | |
123 | * \param argv A list of already completed options | |
124 | * \param argc The number of already completed options | |
125 | * \param word The word to complete | |
126 | * \param state The state | |
127 | * | |
128 | * \retval 0 Success, -1 Failure | |
129 | * | |
130 | * \since 16.14 | |
131 | * \since 17.8 | |
132 | * \since 18.0 | |
133 | */ | |
134 | char *ast_debug_category_complete(const char * const *argv, int argc, const char *word, int state); | |
135 | ||
136 | /*! | |
137 | * \brief Check if a debug category is enabled, and allowed to output | |
138 | * | |
139 | * \note If more than one id is specified then if even one is allowed "true" | |
140 | * is returned. | |
141 | * | |
142 | * \param sublevel Current set sublevel must be this sublevel or less | |
143 | * \param ids One or more unique category ids to check | |
144 | * | |
145 | * \retval 1 if allowed, 0 if not allowed | |
146 | * | |
147 | * \since 16.14 | |
148 | * \since 17.8 | |
149 | * \since 18.0 | |
150 | */ | |
151 | int ast_debug_category_is_allowed(int sublevel, uintmax_t ids); | |
152 | ||
153 | /*! | |
154 | * \brief Log for a debug category. | |
155 | * | |
156 | * This will output log data for debug under the following conditions: | |
157 | * | |
158 | * 1. The specified sublevel is at, or below the current system debug level | |
159 | * 2. At least one of the given category ids is enabled AND | |
160 | * a. The category sublevel is enabled OR the given sublevel is at, or | |
161 | * below a category's specified sublevel. | |
162 | * | |
163 | * \param sublevel The minimum level to output at | |
164 | * \param ids One or more unique category ids to output for | |
165 | * | |
166 | * \since 16.14 | |
167 | * \since 17.8 | |
168 | * \since 18.0 | |
169 | */ | |
170 | #define ast_debug_category(sublevel, ids, ...) \ | |
171 | do { \ | |
172 | if (DEBUG_ATLEAST(sublevel) || ast_debug_category_is_allowed(sublevel, ids)) { \ | |
173 | ast_log(AST_LOG_DEBUG, __VA_ARGS__); \ | |
174 | } \ | |
175 | } while (0) | |
176 | ||
177 | #endif /* ASTERISK_LOGGER_CATEGORY_H */ |
491 | 491 | * \param callerid pattern to match CallerID, or NULL to match any CallerID |
492 | 492 | * \param application application to run on the extension with that priority level |
493 | 493 | * \param data data to pass to the application |
494 | * \param datad | |
494 | * \param datad a pointer to a function that will deallocate \c data when needed | |
495 | * or NULL if \c data does not need to be freed. | |
495 | 496 | * \param registrar who registered the extension |
497 | * | |
498 | * \note On any failure, the function pointed to by \c datap will be called and passed the | |
499 | * \c data pointer. | |
496 | 500 | * |
497 | 501 | * \retval 0 success |
498 | 502 | * \retval -1 failure |
519 | 523 | * \since 12.0.0 |
520 | 524 | * |
521 | 525 | * \note con must be write locked prior to calling. For details about the arguments, |
522 | * check ast_add_extension2() | |
526 | * check ast_add_extension() | |
523 | 527 | */ |
524 | 528 | int ast_add_extension2_nolock(struct ast_context *con, int replace, const char *extension, |
525 | 529 | int priority, const char *label, const char *callerid, |
73 | 73 | |
74 | 74 | /*! \brief Maximum number of ciphers supported for a TLS transport */ |
75 | 75 | #define SIP_TLS_MAX_CIPHERS 64 |
76 | ||
77 | /*! Maximum number of challenges before assuming that we are in a loop */ | |
78 | #define MAX_RX_CHALLENGES 10 | |
76 | 79 | |
77 | 80 | /*! |
78 | 81 | * \brief Structure for SIP transport information |
579 | 582 | unsigned int send_diversion; |
580 | 583 | /*! When performing connected line update, which method should be used */ |
581 | 584 | enum ast_sip_session_refresh_method refresh_method; |
585 | /*! Do we add History-Info headers to applicable outgoing requests/responses? */ | |
586 | unsigned int send_history_info; | |
582 | 587 | }; |
583 | 588 | |
584 | 589 | /*! |
831 | 836 | unsigned int send_connected_line; |
832 | 837 | /*! Ignore 183 if no SDP is present */ |
833 | 838 | unsigned int ignore_183_without_sdp; |
839 | /*! Enable STIR/SHAKEN support on this endpoint */ | |
840 | unsigned int stir_shaken; | |
834 | 841 | }; |
835 | 842 | |
836 | 843 | /*! URI parameter for symmetric transport */ |
1701 | 1708 | AST_SIP_SCHED_TASK_VARIABLE = (1 << 0), |
1702 | 1709 | |
1703 | 1710 | /*! |
1711 | * Run just once. | |
1712 | * Return values are ignored. | |
1713 | */ | |
1714 | AST_SIP_SCHED_TASK_ONESHOT = (1 << 6), | |
1715 | ||
1716 | /*! | |
1704 | 1717 | * The task data is not an AO2 object. |
1705 | 1718 | */ |
1706 | 1719 | AST_SIP_SCHED_TASK_DATA_NOT_AO2 = (0 << 1), |
1822 | 1835 | struct timeval *when_queued, struct timeval *last_start, struct timeval *last_end); |
1823 | 1836 | |
1824 | 1837 | /*! |
1838 | * \brief Gets the queued, last start, last_end, time left, interval, next run | |
1839 | * \since 16.15.0 | |
1840 | * \since 18.1.0 | |
1841 | * | |
1842 | * \param schtd The task structure pointer | |
1843 | * \param[out] when_queued Pointer to a timeval structure to contain the time when queued | |
1844 | * \param[out] last_start Pointer to a timeval structure to contain the time when last started | |
1845 | * \param[out] last_end Pointer to a timeval structure to contain the time when last ended | |
1846 | * \param[out] interval Pointer to an int to contain the interval in ms | |
1847 | * \param[out] time_left Pointer to an int to contain the ms left to the next run | |
1848 | * \param[out] last_end Pointer to a timeval structure to contain the next run time | |
1849 | * \retval 0 Success | |
1850 | * \retval -1 Failure | |
1851 | * \note Any of the pointers can be NULL if you don't need them. | |
1852 | */ | |
1853 | int ast_sip_sched_task_get_times2(struct ast_sip_sched_task *schtd, | |
1854 | struct timeval *when_queued, struct timeval *last_start, struct timeval *last_end, | |
1855 | int *interval, int *time_left, struct timeval *next_start); | |
1856 | ||
1857 | /*! | |
1825 | 1858 | * \brief Gets the last start and end times of the task by name |
1826 | 1859 | * \since 13.9.0 |
1827 | 1860 | * |
1837 | 1870 | struct timeval *when_queued, struct timeval *last_start, struct timeval *last_end); |
1838 | 1871 | |
1839 | 1872 | /*! |
1873 | * \brief Gets the queued, last start, last_end, time left, interval, next run by task name | |
1874 | * \since 16.15.0 | |
1875 | * \since 18.1.0 | |
1876 | * | |
1877 | * \param name The task name | |
1878 | * \param[out] when_queued Pointer to a timeval structure to contain the time when queued | |
1879 | * \param[out] last_start Pointer to a timeval structure to contain the time when last started | |
1880 | * \param[out] last_end Pointer to a timeval structure to contain the time when last ended | |
1881 | * \param[out] interval Pointer to an int to contain the interval in ms | |
1882 | * \param[out] time_left Pointer to an int to contain the ms left to the next run | |
1883 | * \param[out] last_end Pointer to a timeval structure to contain the next run time | |
1884 | * \retval 0 Success | |
1885 | * \retval -1 Failure | |
1886 | * \note Any of the pointers can be NULL if you don't need them. | |
1887 | */ | |
1888 | int ast_sip_sched_task_get_times_by_name2(const char *name, | |
1889 | struct timeval *when_queued, struct timeval *last_start, struct timeval *last_end, | |
1890 | int *interval, int *time_left, struct timeval *next_start); | |
1891 | ||
1892 | /*! | |
1840 | 1893 | * \brief Gets the number of milliseconds until the next invocation |
1841 | 1894 | * \since 13.9.0 |
1842 | 1895 | * |
1919 | 1972 | /*! |
1920 | 1973 | * \brief General purpose method for creating a UAS dialog with an endpoint |
1921 | 1974 | * |
1975 | * \deprecated This function is unsafe (due to the returned object not being locked nor | |
1976 | * having its reference incremented) and should no longer be used. Instead | |
1977 | * use ast_sip_create_dialog_uas_locked so a properly locked and referenced | |
1978 | * object is returned. | |
1979 | * | |
1922 | 1980 | * \param endpoint A pointer to the endpoint |
1923 | 1981 | * \param rdata The request that is starting the dialog |
1924 | 1982 | * \param[out] status On failure, the reason for failure in creating the dialog |
1925 | 1983 | */ |
1926 | 1984 | pjsip_dialog *ast_sip_create_dialog_uas(const struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, pj_status_t *status); |
1985 | ||
1986 | /*! | |
1987 | * \brief General purpose method for creating a UAS dialog with an endpoint | |
1988 | * | |
1989 | * This function creates and returns a locked, and referenced counted pjsip | |
1990 | * dialog object. The caller is thus responsible for freeing the allocated | |
1991 | * memory, decrementing the reference, and releasing the lock when done with | |
1992 | * the returned object. | |
1993 | * | |
1994 | * \note The safest way to unlock the object, and decrement its reference is by | |
1995 | * calling pjsip_dlg_dec_lock. Alternatively, pjsip_dlg_dec_session can be | |
1996 | * used to decrement the reference only. | |
1997 | * | |
1998 | * The dialog is returned locked and with a reference in order to ensure that the | |
1999 | * dialog object, and any of its associated objects (e.g. transaction) are not | |
2000 | * untimely destroyed. For instance, that could happen when a transport error | |
2001 | * occurs. | |
2002 | * | |
2003 | * As long as the caller maintains a reference to the dialog there should be no | |
2004 | * worry that it might unknowningly be destroyed. However, once the caller unlocks | |
2005 | * the dialog there is a danger that some of the dialog's internal objects could | |
2006 | * be lost and/or compromised. For example, when the aforementioned transport error | |
2007 | * occurs the dialog's associated transaction gets destroyed (see pjsip_dlg_on_tsx_state | |
2008 | * in sip_dialog.c, and mod_inv_on_tsx_state in sip_inv.c). | |
2009 | * | |
2010 | * In this case and before using the dialog again the caller should re-lock the | |
2011 | * dialog, check to make sure the dialog is still established, and the transaction | |
2012 | * still exists and has not been destroyed. | |
2013 | * | |
2014 | * \param endpoint A pointer to the endpoint | |
2015 | * \param rdata The request that is starting the dialog | |
2016 | * \param[out] status On failure, the reason for failure in creating the dialog | |
2017 | * | |
2018 | * \retval A locked, and reference counted pjsip_dialog object. | |
2019 | * \retval NULL on failure | |
2020 | */ | |
2021 | pjsip_dialog *ast_sip_create_dialog_uas_locked(const struct ast_sip_endpoint *endpoint, | |
2022 | pjsip_rx_data *rdata, pj_status_t *status); | |
1927 | 2023 | |
1928 | 2024 | /*! |
1929 | 2025 | * \brief General purpose method for creating an rdata structure using specific information |
2161 | 2257 | * \retval non-NULL The matching endpoint |
2162 | 2258 | */ |
2163 | 2259 | struct ast_sip_endpoint *ast_sip_identify_endpoint(pjsip_rx_data *rdata); |
2260 | ||
2261 | /*! | |
2262 | * \brief Get a specific header value from rdata | |
2263 | * | |
2264 | * \note The returned value does not need to be freed since it's from the rdata pool | |
2265 | * | |
2266 | * \param rdata The rdata | |
2267 | * \param str The header to find | |
2268 | * | |
2269 | * \retval NULL on failure | |
2270 | * \retval The header value on success | |
2271 | */ | |
2272 | char *ast_sip_rdata_get_header_value(pjsip_rx_data *rdata, const pj_str_t str); | |
2164 | 2273 | |
2165 | 2274 | /*! |
2166 | 2275 | * \brief Set the outbound proxy for an outbound SIP message |
116 | 116 | unsigned int changed; |
117 | 117 | /*! \brief Remote media stream label */ |
118 | 118 | char *remote_mslabel; |
119 | /*! \brief Remote stream label */ | |
120 | char *remote_label; | |
121 | /*! \brief Stream name */ | |
122 | char *stream_name; | |
119 | 123 | }; |
120 | 124 | |
121 | 125 | /*! |
218 | 222 | enum ast_sip_dtmf_mode dtmf; |
219 | 223 | /*! Initial incoming INVITE Request-URI. NULL otherwise. */ |
220 | 224 | pjsip_uri *request_uri; |
221 | /* Media statistics for negotiated RTP streams */ | |
225 | /*! Media statistics for negotiated RTP streams */ | |
222 | 226 | AST_VECTOR(, struct ast_rtp_instance_stats *) media_stats; |
227 | /*! Number of challenges received during outgoing requests to determine if we are in a loop */ | |
228 | unsigned int authentication_challenge_count:4; | |
223 | 229 | }; |
224 | 230 | |
225 | 231 | typedef int (*ast_sip_session_request_creation_cb)(struct ast_sip_session *session, pjsip_tx_data *tdata); |
0 | /* | |
1 | * Asterisk -- An open source telephony toolkit. | |
2 | * | |
3 | * Copyright (C) 2020, Sangoma Technologies Corporation | |
4 | * | |
5 | * Kevin Harwell <kharwell@sangoma.com> | |
6 | * | |
7 | * See http://www.asterisk.org for more information about | |
8 | * the Asterisk project. Please do not directly contact | |
9 | * any of the maintainers of this project for assistance; | |
10 | * the project provides a web site, mailing lists and IRC | |
11 | * channels for your use. | |
12 | * | |
13 | * This program is free software, distributed under the terms of | |
14 | * the GNU General Public License Version 2. See the LICENSE file | |
15 | * at the top of the source tree. | |
16 | */ | |
17 | #ifndef _RES_STIR_SHAKEN_H | |
18 | #define _RES_STIR_SHAKEN_H | |
19 | ||
20 | #define STIR_SHAKEN_ENCRYPTION_ALGORITHM "ES256" | |
21 | #define STIR_SHAKEN_PPT "shaken" | |
22 | #define STIR_SHAKEN_TYPE "passport" | |
23 | ||
24 | enum ast_stir_shaken_verification_result { | |
25 | AST_STIR_SHAKEN_VERIFY_NOT_PRESENT, /*! No STIR/SHAKEN information was available */ | |
26 | AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED, /*! Signature verification failed */ | |
27 | AST_STIR_SHAKEN_VERIFY_MISMATCH, /*! Contents of the signaling and the STIR/SHAKEN payload did not match */ | |
28 | AST_STIR_SHAKEN_VERIFY_PASSED, /*! Signature verified and contents match signaling */ | |
29 | }; | |
30 | ||
31 | struct ast_stir_shaken_payload; | |
32 | ||
33 | struct ast_json; | |
34 | ||
35 | /*! | |
36 | * \brief Retrieve the value for 'signature' from an ast_stir_shaken_payload | |
37 | * | |
38 | * \param payload The payload | |
39 | * | |
40 | * \retval The signature | |
41 | */ | |
42 | unsigned char *ast_stir_shaken_payload_get_signature(const struct ast_stir_shaken_payload *payload); | |
43 | ||
44 | /*! | |
45 | * \brief Retrieve the value for 'public_key_url' from an ast_stir_shaken_payload | |
46 | * | |
47 | * \param payload The payload | |
48 | * | |
49 | * \retval The public key URL | |
50 | */ | |
51 | char *ast_stir_shaken_payload_get_public_key_url(const struct ast_stir_shaken_payload *payload); | |
52 | ||
53 | /*! | |
54 | * \brief Retrieve the value for 'signature_timeout' from 'general' config object | |
55 | * | |
56 | * \retval The signature timeout | |
57 | */ | |
58 | unsigned int ast_stir_shaken_get_signature_timeout(void); | |
59 | ||
60 | /*! | |
61 | * \brief Add a STIR/SHAKEN verification result to a channel | |
62 | * | |
63 | * \param chan The channel | |
64 | * \param identity The identity | |
65 | * \param attestation The attestation | |
66 | * \param result The verification result | |
67 | * | |
68 | * \retval -1 on failure | |
69 | * \retval 0 on success | |
70 | */ | |
71 | int ast_stir_shaken_add_verification(struct ast_channel *chan, const char *identity, const char *attestation, | |
72 | enum ast_stir_shaken_verification_result result); | |
73 | ||
74 | /*! | |
75 | * \brief Verify a JSON STIR/SHAKEN payload | |
76 | * | |
77 | * \param header The payload header | |
78 | * \param payload The payload section | |
79 | * \param signature The payload signature | |
80 | * \param algorithm The signature algorithm | |
81 | * \param public_key_url The public key URL | |
82 | * | |
83 | * \retval ast_stir_shaken_payload on success | |
84 | * \retval NULL on failure | |
85 | */ | |
86 | struct ast_stir_shaken_payload *ast_stir_shaken_verify(const char *header, const char *payload, const char *signature, | |
87 | const char *algorithm, const char *public_key_url); | |
88 | ||
89 | /*! | |
90 | * \brief Retrieve the stir/shaken sorcery context | |
91 | * | |
92 | * \retval The stir/shaken sorcery context | |
93 | */ | |
94 | struct ast_sorcery *ast_stir_shaken_sorcery(void); | |
95 | ||
96 | /*! | |
97 | * \brief Free a STIR/SHAKEN payload | |
98 | */ | |
99 | void ast_stir_shaken_payload_free(struct ast_stir_shaken_payload *payload); | |
100 | ||
101 | /*! | |
102 | * \brief Sign a JSON STIR/SHAKEN payload | |
103 | * | |
104 | * \note This function will automatically add the "attest", "iat", and "origid" fields. | |
105 | * | |
106 | * \param json The JWT to sign | |
107 | * | |
108 | * \retval ast_stir_shaken_payload on success | |
109 | * \retval NULL on failure | |
110 | */ | |
111 | struct ast_stir_shaken_payload *ast_stir_shaken_sign(struct ast_json *json); | |
112 | ||
113 | #endif /* _RES_STIR_SHAKEN_H */ |
76 | 76 | #include "asterisk/res_srtp.h" |
77 | 77 | #include "asterisk/stasis.h" |
78 | 78 | #include "asterisk/vector.h" |
79 | #include "asterisk/logger_category.h" | |
79 | 80 | |
80 | 81 | /*! Maximum number of payload types RTP can support. */ |
81 | 82 | #define AST_RTP_MAX_PT 128 |
2818 | 2819 | */ |
2819 | 2820 | struct stasis_topic *ast_rtp_topic(void); |
2820 | 2821 | |
2822 | /* RTP debug logging category name */ | |
2823 | #define AST_LOG_CATEGORY_RTP "rtp" | |
2824 | /* RTP packet debug logging category name */ | |
2825 | #define AST_LOG_CATEGORY_RTP_PACKET "rtp_packet" | |
2826 | /* RTCP debug logging category name */ | |
2827 | #define AST_LOG_CATEGORY_RTCP "rtcp" | |
2828 | /* RTCP packet debug logging category name */ | |
2829 | #define AST_LOG_CATEGORY_RTCP_PACKET "rtcp_packet" | |
2830 | /* DTLS debug logging category name */ | |
2831 | #define AST_LOG_CATEGORY_DTLS "dtls" | |
2832 | /* DTLS packet debug logging category name */ | |
2833 | #define AST_LOG_CATEGORY_DTLS_PACKET "dtls_packet" | |
2834 | /* ICE debug logging category name */ | |
2835 | #define AST_LOG_CATEGORY_ICE "ice" | |
2836 | ||
2837 | uintmax_t ast_debug_category_rtp_id(void); | |
2838 | uintmax_t ast_debug_category_rtp_packet_id(void); | |
2839 | uintmax_t ast_debug_category_rtcp_id(void); | |
2840 | uintmax_t ast_debug_category_rtcp_packet_id(void); | |
2841 | uintmax_t ast_debug_category_dtls_id(void); | |
2842 | uintmax_t ast_debug_category_dtls_packet_id(void); | |
2843 | uintmax_t ast_debug_category_ice_id(void); | |
2844 | ||
2845 | #define AST_DEBUG_CATEGORY_RTP ast_debug_category_rtp_id() /* RTP debug logging category id */ | |
2846 | #define AST_DEBUG_CATEGORY_RTP_PACKET ast_debug_category_rtp_packet_id() /* RTP packet debug logging category id */ | |
2847 | #define AST_DEBUG_CATEGORY_RTCP ast_debug_category_rtcp_id() /* RTCP debug logging category id */ | |
2848 | #define AST_DEBUG_CATEGORY_RTCP_PACKET ast_debug_category_rtcp_packet_id() /* RTCP packet debug logging category id */ | |
2849 | #define AST_DEBUG_CATEGORY_DTLS ast_debug_category_dtls_id() /* DTLS debug logging category id */ | |
2850 | #define AST_DEBUG_CATEGORY_DTLS_PACKET ast_debug_category_dtls_packet_id() /* DTLS packet debug logging category id */ | |
2851 | #define AST_DEBUG_CATEGORY_ICE ast_debug_category_ice_id() /* ICE debug logging category id */ | |
2852 | ||
2853 | /*! | |
2854 | * \brief Log debug level RTP information | |
2855 | * | |
2856 | * \param sublevel Debug output sublevel (>= 0) | |
2857 | * \param ... String format and any associated arguments | |
2858 | */ | |
2859 | #define ast_debug_rtp(sublevel, ...) \ | |
2860 | ast_debug_category(sublevel, AST_DEBUG_CATEGORY_RTP, __VA_ARGS__) | |
2861 | ||
2862 | /* Allow logging of RTP packets? */ | |
2863 | #define ast_debug_rtp_packet_is_allowed \ | |
2864 | ast_debug_category_is_allowed(AST_LOG_CATEGORY_ENABLED, AST_DEBUG_CATEGORY_RTP_PACKET) | |
2865 | ||
2866 | /*! | |
2867 | * \brief Log debug level RTCP information | |
2868 | * | |
2869 | * \param sublevel Debug output sublevel (>= 0) | |
2870 | * \param ... String format and any associated arguments | |
2871 | */ | |
2872 | #define ast_debug_rtcp(sublevel, ...) \ | |
2873 | ast_debug_category(sublevel, AST_DEBUG_CATEGORY_RTCP, __VA_ARGS__) | |
2874 | ||
2875 | /* Allow logging of RTCP packets? */ | |
2876 | #define ast_debug_rtcp_packet_is_allowed \ | |
2877 | ast_debug_category_is_allowed(AST_LOG_CATEGORY_ENABLED, AST_DEBUG_CATEGORY_RTCP_PACKET) | |
2878 | ||
2879 | /*! | |
2880 | * \brief Log debug level DTLS information | |
2881 | * | |
2882 | * \param sublevel Debug output sublevel (>= 0) | |
2883 | * \param ... String format and any associated arguments | |
2884 | */ | |
2885 | #define ast_debug_dtls(sublevel, ...) \ | |
2886 | ast_debug_category(sublevel, AST_DEBUG_CATEGORY_DTLS, __VA_ARGS__) | |
2887 | ||
2888 | /* Allow logging of DTLS packets? */ | |
2889 | #define ast_debug_dtls_packet_is_allowed \ | |
2890 | ast_debug_category_is_allowed(AST_LOG_CATEGORY_ENABLED, AST_DEBUG_CATEGORY_DTLS_PACKET) | |
2891 | /*! | |
2892 | * \brief Log debug level ICE information | |
2893 | * | |
2894 | * \param sublevel Debug output sublevel (>= 0) | |
2895 | * \param ... String format and any associated arguments | |
2896 | */ | |
2897 | #define ast_debug_ice(sublevel, ...) \ | |
2898 | ast_debug_category(sublevel, AST_DEBUG_CATEGORY_ICE, __VA_ARGS__) | |
2899 | ||
2821 | 2900 | /* @} */ |
2822 | 2901 | |
2823 | 2902 | #if defined(__cplusplus) || defined(c_plusplus) |
135 | 135 | while (id > -1 && (_res = ast_sched_del(sched, id) && _count++ < 10)) { \ |
136 | 136 | usleep(1); \ |
137 | 137 | } \ |
138 | if (!_res && _data) \ | |
138 | if (!_res && _data && _data != data) \ | |
139 | 139 | unrefcall; /* should ref _data! */ \ |
140 | 140 | if (_count == 10) \ |
141 | 141 | ast_log(LOG_WARNING, "Unable to cancel schedule ID %d. This is probably a bug (%s: %s, line %d).\n", id, __FILE__, __PRETTY_FUNCTION__, __LINE__); \ |
142 | refcall; \ | |
142 | if (_data != data) \ | |
143 | refcall; \ | |
143 | 144 | id = ast_sched_add_variable(sched, when, callback, data, variable); \ |
144 | 145 | if (id == -1) \ |
145 | 146 | addfailcall; \ |
428 | 428 | * \returns the position of the stream in the topology (-1 on error) |
429 | 429 | * |
430 | 430 | * \since 15 |
431 | * | |
432 | * \note If the stream's name is empty, it'll be set to <stream_type>-<position> | |
431 | 433 | */ |
432 | 434 | int ast_stream_topology_append_stream(struct ast_stream_topology *topology, |
433 | 435 | struct ast_stream *stream); |
473 | 475 | * the first unused position. You can't set positions beyond that. |
474 | 476 | * |
475 | 477 | * \since 15 |
478 | * | |
479 | * \note If the stream's name is empty, it'll be set to <stream_type>-<position> | |
476 | 480 | */ |
477 | 481 | int ast_stream_topology_set_stream(struct ast_stream_topology *topology, |
478 | 482 | unsigned int position, struct ast_stream *stream); |
26 | 26 | #define _ASTERISK_STUN_H |
27 | 27 | |
28 | 28 | #include "asterisk/network.h" |
29 | #include "asterisk/logger_category.h" | |
29 | 30 | |
30 | 31 | #if defined(__cplusplus) || defined(c_plusplus) |
31 | 32 | extern "C" { |
32 | 33 | #endif |
34 | ||
35 | /* STUN debug logging category name */ | |
36 | #define AST_LOG_CATEGORY_STUN "stun" | |
37 | /* STUN packet debug logging category name */ | |
38 | #define AST_LOG_CATEGORY_STUN_PACKET "stun_packet" | |
39 | ||
40 | uintmax_t ast_debug_category_stun_id(void); | |
41 | uintmax_t ast_debug_category_stun_packet_id(void); | |
42 | ||
43 | #define AST_DEBUG_CATEGORY_STUN ast_debug_category_stun_id() /* STUN debug logging category id */ | |
44 | #define AST_DEBUG_CATEGORY_STUN_PACKET ast_debug_category_stun_packet_id() /* STUN packet debug logging category id */ | |
45 | ||
46 | /*! | |
47 | * \brief Log debug level STUN information | |
48 | * | |
49 | * \param sublevel Debug output sublevel (>= 0) | |
50 | * \param ... String format and any associated arguments | |
51 | */ | |
52 | #define ast_debug_stun(sublevel, ...) \ | |
53 | ast_debug_category(sublevel, AST_DEBUG_CATEGORY_STUN, __VA_ARGS__) | |
54 | ||
55 | /* Is logging of stun packets allowed? */ | |
56 | #define ast_debug_stun_packet_is_allowed \ | |
57 | ast_debug_category_is_allowed(AST_LOG_CATEGORY_ENABLED, AST_DEBUG_CATEGORY_STUN_PACKET) | |
33 | 58 | |
34 | 59 | static const int STANDARD_STUN_PORT = 3478; |
35 | 60 |
239 | 239 | int ast_base64encode(char *dst, const unsigned char *src, int srclen, int max); |
240 | 240 | |
241 | 241 | /*! |
242 | * \brief Same as ast_base64encode, but does hte math for you and returns | |
243 | * an encoded string | |
244 | * | |
245 | * \note The returned string will need to be freed later | |
246 | * | |
247 | * \param src The source buffer | |
248 | * | |
249 | * \retval NULL on failure | |
250 | * \retval Encoded string on success | |
251 | */ | |
252 | char *ast_base64encode_string(const char *src); | |
253 | ||
254 | /*! | |
242 | 255 | * \brief Decode data from base64 |
243 | 256 | * \param dst the destination buffer |
244 | 257 | * \param src the source buffer |
248 | 261 | * this parameter should be sizeof(dst) - 1. |
249 | 262 | */ |
250 | 263 | int ast_base64decode(unsigned char *dst, const char *src, int max); |
264 | ||
265 | /*! | |
266 | * \brief Same as ast_base64decode, but does the math for you and returns | |
267 | * a decoded string | |
268 | * | |
269 | * \note The returned string will need to be freed later and IS NULL terminated | |
270 | * | |
271 | * \param src The source buffer | |
272 | * | |
273 | * \retval NULL on failure | |
274 | * \retval Decoded string on success | |
275 | */ | |
276 | char *ast_base64decode_string(const char *src); | |
251 | 277 | |
252 | 278 | #define AST_URI_ALPHANUM (1 << 0) |
253 | 279 | #define AST_URI_MARK (1 << 1) |
1752 | 1752 | ast_channel_lock(chan); |
1753 | 1753 | ast_channel_internal_bridge_channel_set(chan, NULL); |
1754 | 1754 | ast_channel_unlock(chan); |
1755 | /* Due to a race condition, we lock the bridge channel here for ast_bridge_channel_get_chan */ | |
1756 | ao2_lock(bridge_channel); | |
1755 | 1757 | bridge_channel->chan = NULL; |
1758 | ao2_unlock(bridge_channel); | |
1756 | 1759 | /* If bridge_channel->swap is not NULL then the join failed. */ |
1757 | 1760 | ao2_t_cleanup(bridge_channel->swap, "Bridge complete: join failed"); |
1758 | 1761 | bridge_channel->swap = NULL; |
1821 | 1824 | ast_channel_lock(chan); |
1822 | 1825 | ast_channel_internal_bridge_channel_set(chan, NULL); |
1823 | 1826 | ast_channel_unlock(chan); |
1827 | /* Lock here for ast_bridge_channel_get_chan */ | |
1828 | ao2_lock(bridge_channel); | |
1824 | 1829 | bridge_channel->chan = NULL; |
1830 | ao2_unlock(bridge_channel); | |
1825 | 1831 | /* If bridge_channel->swap is not NULL then the join failed. */ |
1826 | 1832 | ao2_t_cleanup(bridge_channel->swap, "Bridge complete: Independent impart join failed"); |
1827 | 1833 | bridge_channel->swap = NULL; |
1922 | 1928 | ast_channel_lock(chan); |
1923 | 1929 | ast_channel_internal_bridge_channel_set(chan, NULL); |
1924 | 1930 | ast_channel_unlock(chan); |
1931 | /* Lock here for ast_bridge_channel_get_chan */ | |
1932 | ao2_lock(bridge_channel); | |
1925 | 1933 | bridge_channel->chan = NULL; |
1934 | ao2_unlock(bridge_channel); | |
1926 | 1935 | ao2_t_cleanup(bridge_channel->swap, "Bridge complete: Impart failed"); |
1927 | 1936 | bridge_channel->swap = NULL; |
1928 | 1937 | ast_bridge_features_destroy(bridge_channel->features); |
3827 | 3836 | ast_bridge_lock(bridge); |
3828 | 3837 | cleanup_video_mode(bridge); |
3829 | 3838 | bridge->softmix.video_mode.mode = AST_BRIDGE_VIDEO_MODE_SINGLE_SRC; |
3830 | bridge->softmix.video_mode.mode_data.single_src_data.chan_vsrc = ast_channel_ref(video_src_chan); | |
3831 | ast_verb(5, "Video source in bridge '%s' (%s) is now '%s' (%s)\n", | |
3832 | bridge->name, bridge->uniqueid, | |
3833 | ast_channel_name(video_src_chan), | |
3834 | ast_channel_uniqueid(video_src_chan)); | |
3839 | if (video_src_chan) { | |
3840 | bridge->softmix.video_mode.mode_data.single_src_data.chan_vsrc = ast_channel_ref(video_src_chan); | |
3841 | ast_verb(5, "Video source in bridge '%s' (%s) is now '%s' (%s)\n", | |
3842 | bridge->name, bridge->uniqueid, | |
3843 | ast_channel_name(video_src_chan), | |
3844 | ast_channel_uniqueid(video_src_chan)); | |
3845 | ast_indicate(video_src_chan, AST_CONTROL_VIDUPDATE); | |
3846 | } | |
3835 | 3847 | ast_bridge_publish_state(bridge); |
3836 | ast_indicate(video_src_chan, AST_CONTROL_VIDUPDATE); | |
3837 | 3848 | ast_bridge_unlock(bridge); |
3838 | 3849 | } |
3839 | 3850 | |
3875 | 3886 | |
3876 | 3887 | ast_bridge_lock(bridge); |
3877 | 3888 | bridge->softmix.video_mode.mode_data.sfu_data.remb_behavior = behavior; |
3889 | ast_bridge_unlock(bridge); | |
3890 | } | |
3891 | ||
3892 | void ast_bridge_set_remb_estimated_bitrate(struct ast_bridge *bridge, float estimated_bitrate) | |
3893 | { | |
3894 | ast_assert(bridge->softmix.video_mode.mode == AST_BRIDGE_VIDEO_MODE_SFU); | |
3895 | ||
3896 | ast_bridge_lock(bridge); | |
3897 | bridge->softmix.video_mode.mode_data.sfu_data.estimated_bitrate = estimated_bitrate; | |
3878 | 3898 | ast_bridge_unlock(bridge); |
3879 | 3899 | } |
3880 | 3900 | |
4770 | 4790 | |
4771 | 4791 | if (to_transferee_bridge_channel) { |
4772 | 4792 | /* Take off hold if they are on hold. */ |
4773 | ast_bridge_channel_write_unhold(to_transferee_bridge_channel); | |
4793 | if (ast_bridge_channel_write_unhold(to_transferee_bridge_channel)) { | |
4794 | ast_log(LOG_ERROR, "Transferee channel disappeared during transfer!\n"); | |
4795 | res = AST_BRIDGE_TRANSFER_FAIL; | |
4796 | goto end; | |
4797 | } | |
4774 | 4798 | } |
4775 | 4799 | |
4776 | 4800 | if (to_target_bridge_channel) { |
4777 | 4801 | const char *target_complete_sound; |
4778 | 4802 | |
4779 | 4803 | /* Take off hold if they are on hold. */ |
4780 | ast_bridge_channel_write_unhold(to_target_bridge_channel); | |
4804 | if (ast_bridge_channel_write_unhold(to_target_bridge_channel)) { | |
4805 | ast_log(LOG_ERROR, "Target channel disappeared during transfer!\n"); | |
4806 | res = AST_BRIDGE_TRANSFER_FAIL; | |
4807 | goto end; | |
4808 | } | |
4781 | 4809 | |
4782 | 4810 | /* Is there a courtesy sound to play to the target? */ |
4783 | 4811 | ast_channel_lock(to_transfer_target); |
205 | 205 | static void bridge_sync_signal(struct bridge_sync *sync_struct) |
206 | 206 | { |
207 | 207 | ast_sem_post(&sync_struct->sem); |
208 | } | |
209 | ||
210 | struct ast_channel *ast_bridge_channel_get_chan(struct ast_bridge_channel *bridge_channel) | |
211 | { | |
212 | struct ast_channel *chan; | |
213 | ||
214 | ao2_lock(bridge_channel); | |
215 | chan = ao2_bump(bridge_channel->chan); | |
216 | ao2_unlock(bridge_channel); | |
217 | ||
218 | return chan; | |
208 | 219 | } |
209 | 220 | |
210 | 221 | void ast_bridge_channel_lock_bridge(struct ast_bridge_channel *bridge_channel) |
1176 | 1187 | |
1177 | 1188 | int ast_bridge_channel_write_unhold(struct ast_bridge_channel *bridge_channel) |
1178 | 1189 | { |
1179 | ast_channel_publish_cached_blob(bridge_channel->chan, ast_channel_unhold_type(), NULL); | |
1190 | struct ast_channel *chan = ast_bridge_channel_get_chan(bridge_channel); | |
1191 | ||
1192 | if (!chan) { | |
1193 | return -1; | |
1194 | } | |
1195 | ||
1196 | ast_channel_publish_cached_blob(chan, ast_channel_unhold_type(), NULL); | |
1197 | ao2_ref(chan, -1); | |
1180 | 1198 | |
1181 | 1199 | return ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_UNHOLD, NULL, 0); |
1182 | 1200 | } |
5720 | 5720 | if (!direction) { |
5721 | 5721 | /* reading */ |
5722 | 5722 | trans_pvt = ast_translator_build_path(best_set_fmt, best_native_fmt); |
5723 | trans_pvt->interleaved_stereo = 0; | |
5723 | if (trans_pvt) { | |
5724 | trans_pvt->interleaved_stereo = 0; | |
5725 | } | |
5724 | 5726 | } else { |
5725 | 5727 | /* writing */ |
5726 | 5728 | trans_pvt = ast_translator_build_path(best_native_fmt, best_set_fmt); |
5727 | trans_pvt->interleaved_stereo = interleaved_stereo; | |
5729 | if (trans_pvt) { | |
5730 | trans_pvt->interleaved_stereo = interleaved_stereo; | |
5731 | } | |
5728 | 5732 | } |
5729 | 5733 | access->set_trans(chan, trans_pvt); |
5730 | 5734 | res = trans_pvt ? 0 : -1; |
11028 | 11032 | } |
11029 | 11033 | |
11030 | 11034 | if (ast_stream_topology_equal(ast_channel_get_stream_topology(chan), topology)) { |
11031 | ast_debug(3, "Topology of %s already matches what is requested so ignoring topology change request\n", | |
11032 | ast_channel_name(chan)); | |
11035 | ast_debug(2, "%s: Topologies already match. Current: %s Requested: %s\n", | |
11036 | ast_channel_name(chan), | |
11037 | ast_str_tmp(256, ast_stream_topology_to_str(ast_channel_get_stream_topology(chan), &STR_TMP)), | |
11038 | ast_str_tmp(256, ast_stream_topology_to_str(topology, &STR_TMP))); | |
11033 | 11039 | ast_channel_unlock(chan); |
11034 | 11040 | return 0; |
11035 | 11041 | } |
54 | 54 | #include "asterisk/app.h" |
55 | 55 | #include "asterisk/lock.h" |
56 | 56 | #include "asterisk/threadstorage.h" |
57 | #include "asterisk/logger_category.h" | |
57 | 58 | #include "asterisk/translate.h" |
58 | 59 | #include "asterisk/bridge.h" |
59 | 60 | #include "asterisk/stasis_channels.h" |
477 | 478 | if (!strcasecmp(a->argv[e->args], "atleast")) { |
478 | 479 | atleast = 1; |
479 | 480 | } |
481 | ||
480 | 482 | if (a->argc != e->args + atleast + 1 && a->argc != e->args + atleast + 2) { |
481 | 483 | return CLI_SHOWUSAGE; |
482 | 484 | } |
485 | ||
483 | 486 | if (sscanf(a->argv[e->args + atleast], "%30d", &newlevel) != 1) { |
484 | 487 | return CLI_SHOWUSAGE; |
485 | 488 | } |
584 | 587 | return NULL; |
585 | 588 | |
586 | 589 | case CLI_GENERATE: |
590 | if (!strcasecmp(argv3, "category")) { | |
591 | return NULL; | |
592 | } | |
593 | ||
587 | 594 | if (!strcasecmp(argv3, "atleast")) { |
588 | 595 | atleast = 1; |
589 | 596 | } |
617 | 624 | */ |
618 | 625 | |
619 | 626 | return handle_debug_or_trace(DEBUG_HANDLER, e, cmd, a); |
620 | ||
621 | 627 | } |
622 | 628 | |
623 | 629 | static char *handle_trace(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) |
1533 | 1539 | } |
1534 | 1540 | |
1535 | 1541 | ast_cli(a->fd, "Debugging on new channels is %s\n", args.is_off ? "disabled" : "enabled"); |
1542 | ||
1543 | return CLI_SUCCESS; | |
1544 | } | |
1545 | ||
1546 | static char *handle_debug_category(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) | |
1547 | { | |
1548 | const char *argv4 = a->argv ? S_OR(a->argv[4], "") : ""; | |
1549 | int offset = strncasecmp(argv4, "off", strlen(argv4)) ? 0 : 1; | |
1550 | ||
1551 | switch (cmd) { | |
1552 | case CLI_INIT: | |
1553 | e->command = "core set debug category"; | |
1554 | e->usage = | |
1555 | "Usage: core set debug category <category>[:<sublevel>] [category[:<sublevel] ...]\n" | |
1556 | " core set debug category off [<category> [<category>] ...]\n\n" | |
1557 | " Allows enabling and disabling debug logging categories.\n" | |
1558 | " When a category is enabled all relevant debug messages are logged\n" | |
1559 | " for a given category. However, if a sublevel is specified only\n" | |
1560 | " those categorized messages at or below the coded debug sublevel\n" | |
1561 | " are logged.\n"; | |
1562 | return NULL; | |
1563 | ||
1564 | case CLI_GENERATE: | |
1565 | if (a->pos < e->args) { | |
1566 | return NULL; | |
1567 | } | |
1568 | ||
1569 | if (a->pos == 4 && offset) { | |
1570 | ast_cli_completion_add(ast_strdup("off")); | |
1571 | } | |
1572 | ||
1573 | return ast_debug_category_complete(a->argv + 4, | |
1574 | a->pos - e->args, a->word, a->n - 1); | |
1575 | } | |
1576 | ||
1577 | if (a->argc <= e->args) { | |
1578 | return CLI_SHOWUSAGE; | |
1579 | } | |
1580 | ||
1581 | ast_debug_category_set_sublevels(a->argv + e->args + offset, a->argc - e->args - offset, | |
1582 | offset ? AST_LOG_CATEGORY_DISABLED : AST_LOG_CATEGORY_ENABLED); | |
1536 | 1583 | |
1537 | 1584 | return CLI_SUCCESS; |
1538 | 1585 | } |
1955 | 2002 | |
1956 | 2003 | AST_CLI_DEFINE(handle_core_set_debug_channel, "Enable/disable debugging on a channel"), |
1957 | 2004 | |
2005 | AST_CLI_DEFINE(handle_debug_category, "Enable/disable debugging categories"), | |
2006 | ||
1958 | 2007 | AST_CLI_DEFINE(handle_debug, "Set level of debug chattiness"), |
1959 | 2008 | AST_CLI_DEFINE(handle_trace, "Set level of trace chattiness"), |
1960 | 2009 | AST_CLI_DEFINE(handle_verbose, "Set level of verbose chattiness"), |
1441 | 1441 | } else { |
1442 | 1442 | odata = f->data.ptr; |
1443 | 1443 | len = f->datalen; |
1444 | if (ast_format_cmp(f->subclass.format, ast_format_ulaw)) { | |
1444 | if (ast_format_cmp(f->subclass.format, ast_format_ulaw) == AST_FORMAT_CMP_EQUAL) { | |
1445 | 1445 | s = ast_alloca(len * 2); |
1446 | 1446 | for (x = 0; x < len; x++) { |
1447 | 1447 | s[x] = AST_MULAW(odata[x]); |
1448 | 1448 | } |
1449 | } else if (ast_format_cmp(f->subclass.format, ast_format_alaw)) { | |
1449 | } else if (ast_format_cmp(f->subclass.format, ast_format_alaw) == AST_FORMAT_CMP_EQUAL) { | |
1450 | 1450 | s = ast_alloca(len * 2); |
1451 | 1451 | for (x = 0; x < len; x++) { |
1452 | 1452 | s[x] = AST_ALAW(odata[x]); |
554 | 554 | |
555 | 555 | return 0; |
556 | 556 | } |
557 | ||
558 | struct ast_format *ast_format_cache_get_by_codec(const struct ast_codec *codec) | |
559 | { | |
560 | struct ast_format *format; | |
561 | struct ao2_iterator it; | |
562 | ||
563 | for (it = ao2_iterator_init(formats, 0); | |
564 | (format = ao2_iterator_next(&it)); | |
565 | ao2_ref(format, -1)) { | |
566 | struct ast_codec *candidate = ast_format_get_codec(format); | |
567 | if (codec == candidate) { | |
568 | ao2_cleanup(candidate); | |
569 | ao2_iterator_destroy(&it); | |
570 | return format; | |
571 | } | |
572 | ao2_cleanup(candidate); | |
573 | } | |
574 | ||
575 | ao2_iterator_destroy(&it); | |
576 | return NULL; | |
577 | } |
231 | 231 | continue; |
232 | 232 | } |
233 | 233 | |
234 | format = ast_format_cache_get(codec->name); | |
234 | format = ast_format_cache_get_by_codec(codec); | |
235 | 235 | |
236 | 236 | if (format == ast_format_none) { |
237 | 237 | ao2_ref(format, -1); |
701 | 701 | ast_tone_zone_lock(tz); |
702 | 702 | |
703 | 703 | if (ast_register_indication(tz, a->argv[3], a->argv[4])) { |
704 | ast_log(LOG_WARNING, "Unable to register indication %s/%s\n", a->argv[2], a->argv[3]); | |
704 | if (ast_strlen_zero(a->argv[3])) { | |
705 | ast_log(LOG_WARNING, "Unable to register indication %s\n", a->argv[2]); | |
706 | } else { | |
707 | ast_log(LOG_WARNING, "Unable to register indication %s/%s\n", a->argv[2], a->argv[3]); | |
708 | } | |
705 | 709 | if (created_country) { |
706 | 710 | ast_unregister_indication_country(a->argv[2]); |
707 | 711 | } |
51 | 51 | #include "asterisk/module.h" |
52 | 52 | #include "asterisk/paths.h" /* use ast_config_AST_LOG_DIR */ |
53 | 53 | #include "asterisk/logger.h" |
54 | #include "asterisk/logger_category.h" | |
54 | 55 | #include "asterisk/lock.h" |
55 | 56 | #include "asterisk/channel.h" |
56 | 57 | #include "asterisk/config.h" |
1867 | 1868 | ast_log(LOG_ERROR, "Errors detected in logger.conf. Default console logging is being used.\n"); |
1868 | 1869 | } |
1869 | 1870 | |
1871 | ast_logger_category_load(); | |
1872 | ||
1870 | 1873 | return 0; |
1871 | 1874 | } |
1872 | 1875 | |
1873 | 1876 | void close_logger(void) |
1874 | 1877 | { |
1875 | 1878 | struct logchannel *f = NULL; |
1879 | ||
1880 | ast_logger_category_unload(); | |
1876 | 1881 | |
1877 | 1882 | ast_cli_unregister_multiple(cli_logger, ARRAY_LEN(cli_logger)); |
1878 | 1883 |
0 | /* | |
1 | * Asterisk -- An open source telephony toolkit. | |
2 | * | |
3 | * Copyright (C) 2020, Sangoma Technologies Corporation | |
4 | * | |
5 | * Kevin Harwell <kharwell@sangoma.com> | |
6 | * | |
7 | * See http://www.asterisk.org for more information about | |
8 | * the Asterisk project. Please do not directly contact | |
9 | * any of the maintainers of this project for assistance; | |
10 | * the project provides a web site, mailing lists and IRC | |
11 | * channels for your use. | |
12 | * | |
13 | * This program is free software, distributed under the terms of | |
14 | * the GNU General Public License Version 2. See the LICENSE file | |
15 | * at the top of the source tree. | |
16 | */ | |
17 | ||
18 | #include "asterisk.h" | |
19 | ||
20 | #include "asterisk/cli.h" | |
21 | #include "asterisk/conversions.h" | |
22 | #include "asterisk/logger_category.h" | |
23 | #include "asterisk/vector.h" | |
24 | ||
25 | struct category_t { | |
26 | int sublevel; | |
27 | uintmax_t id; | |
28 | char name[0]; | |
29 | }; | |
30 | ||
31 | AST_VECTOR_RW(categories_t, struct category_t *); | |
32 | ||
33 | struct categories_level_t { | |
34 | int type; | |
35 | int sublevel; | |
36 | uintmax_t id_pool; | |
37 | uintmax_t state; | |
38 | struct categories_t categories; | |
39 | }; | |
40 | ||
41 | /*! \brief Retrieve the next available id. | |
42 | * | |
43 | * Ids must be a power of 2. This allows for fast lookup, and "or'ing" of ids | |
44 | * in order to permit multiple categories in a log statement. | |
45 | */ | |
46 | static uintmax_t get_next_id(struct categories_level_t *level) | |
47 | { | |
48 | if (level->id_pool == 0) { | |
49 | level->id_pool = 1; | |
50 | } else if (level->id_pool >= (UINTMAX_MAX / 2)) { | |
51 | /* No more ids left*/ | |
52 | return 0; | |
53 | } else { | |
54 | level->id_pool <<= 1; | |
55 | } | |
56 | ||
57 | return level->id_pool; | |
58 | } | |
59 | ||
60 | static int cmp_by_name(const struct category_t *category, const char *name) | |
61 | { | |
62 | return !strcmp(category->name, name); | |
63 | } | |
64 | ||
65 | static uintmax_t category_register(struct categories_level_t *level, const char *name) | |
66 | { | |
67 | int i; | |
68 | struct category_t *category; | |
69 | ||
70 | AST_VECTOR_RW_WRLOCK(&level->categories); | |
71 | ||
72 | i = AST_VECTOR_GET_INDEX(&level->categories, name, cmp_by_name); | |
73 | if (i >= 0) { | |
74 | AST_VECTOR_RW_UNLOCK(&level->categories); | |
75 | ast_log(LOG_ERROR, "Cannot register logger category '%s'. " | |
76 | "Name already used for type.\n", name); | |
77 | return 0; | |
78 | } | |
79 | ||
80 | category = ast_calloc(1, sizeof(*category) + strlen(name) + 1); | |
81 | if (!category) { | |
82 | AST_VECTOR_RW_UNLOCK(&level->categories); | |
83 | return 0; | |
84 | } | |
85 | ||
86 | category->id = get_next_id(level); | |
87 | category->sublevel = AST_LOG_CATEGORY_DISABLED; | |
88 | strcpy(category->name, name); /* Safe */ | |
89 | ||
90 | if (AST_VECTOR_APPEND(&level->categories, category)) { | |
91 | AST_VECTOR_RW_UNLOCK(&level->categories); | |
92 | ast_log(LOG_ERROR, "Cannot register logger category '%s'. " | |
93 | "Unable to append.\n", name); | |
94 | return 0; | |
95 | } | |
96 | ||
97 | AST_VECTOR_RW_UNLOCK(&level->categories); | |
98 | return category->id; | |
99 | } | |
100 | ||
101 | static int category_unregister(struct categories_level_t *level, const char *name) | |
102 | { | |
103 | int res; | |
104 | ||
105 | AST_VECTOR_RW_WRLOCK(&level->categories); | |
106 | res = AST_VECTOR_REMOVE_CMP_UNORDERED(&level->categories, name, cmp_by_name, ast_free); | |
107 | AST_VECTOR_RW_UNLOCK(&level->categories); | |
108 | ||
109 | return res; | |
110 | } | |
111 | ||
112 | static int casecmp_by_name(const struct category_t *category, const char *name) | |
113 | { | |
114 | return !strcasecmp(category->name, name); | |
115 | } | |
116 | ||
117 | static int category_set_sublevel(struct category_t *category, struct categories_level_t *level, | |
118 | const char *name, int sublevel) | |
119 | { | |
120 | int locked = 0; | |
121 | ||
122 | if (!category) { | |
123 | struct category_t **obj; | |
124 | ||
125 | if (!name) { | |
126 | return -1; | |
127 | } | |
128 | ||
129 | locked = !AST_VECTOR_RW_WRLOCK(&level->categories); | |
130 | if (!locked) { | |
131 | return -1; | |
132 | } | |
133 | ||
134 | obj = AST_VECTOR_GET_CMP(&level->categories, name, casecmp_by_name); | |
135 | if (!obj) { | |
136 | AST_VECTOR_RW_UNLOCK(&level->categories); | |
137 | return -1; | |
138 | } | |
139 | ||
140 | category = *obj; | |
141 | } | |
142 | ||
143 | category->sublevel = sublevel; | |
144 | ||
145 | if (category->sublevel == AST_LOG_CATEGORY_DISABLED) { | |
146 | level->state &= ~category->id; | |
147 | } else { | |
148 | level->state |= category->id; | |
149 | } | |
150 | ||
151 | if (locked) { | |
152 | AST_VECTOR_RW_UNLOCK(&level->categories); | |
153 | } | |
154 | ||
155 | return 0; | |
156 | } | |
157 | ||
158 | static int category_set_sublevels(struct categories_level_t *level, | |
159 | const char * const *names, size_t size, int default_sublevel) | |
160 | { | |
161 | int i; | |
162 | ||
163 | if (!names || !size) { | |
164 | level->state = default_sublevel; | |
165 | AST_VECTOR_RW_WRLOCK(&level->categories); | |
166 | AST_VECTOR_CALLBACK_VOID(&level->categories, category_set_sublevel, | |
167 | level, NULL, default_sublevel); | |
168 | AST_VECTOR_RW_UNLOCK(&level->categories); | |
169 | return 0; | |
170 | } | |
171 | ||
172 | for (i = 0; i < size; ++i) { | |
173 | const char *sublevel; | |
174 | int num = default_sublevel; | |
175 | ||
176 | sublevel = strchr(names[i], ':'); | |
177 | if (sublevel) { | |
178 | size_t len = ++sublevel - names[i]; | |
179 | char name[len]; | |
180 | ||
181 | if (*sublevel && ast_str_to_int(sublevel, &num)) { | |
182 | continue; | |
183 | } | |
184 | ||
185 | ast_copy_string(name, names[i], len); | |
186 | category_set_sublevel(NULL, level, name, num); | |
187 | } else { | |
188 | category_set_sublevel(NULL, level, names[i], num); | |
189 | } | |
190 | } | |
191 | ||
192 | return 0; | |
193 | } | |
194 | ||
195 | static char *category_complete(struct categories_level_t *level, const char * const *argv, | |
196 | int argc, const char *word, int state) | |
197 | { | |
198 | int wordlen = strlen(word); | |
199 | int which = 0; | |
200 | int i, j; | |
201 | ||
202 | AST_VECTOR_RW_RDLOCK(&level->categories); | |
203 | ||
204 | if (argc == AST_VECTOR_SIZE(&level->categories)) { | |
205 | AST_VECTOR_RW_UNLOCK(&level->categories); | |
206 | return NULL; | |
207 | } | |
208 | ||
209 | for (i = 0; i < AST_VECTOR_SIZE(&level->categories); ++i) { | |
210 | struct category_t *category = AST_VECTOR_GET(&level->categories, i); | |
211 | ||
212 | if (!strncasecmp(word, category->name, wordlen) && (++which > state)) { | |
213 | /* Check to see if one is already been included */ | |
214 | for (j = 0; j < argc; ++j) { | |
215 | if (!strncasecmp(category->name, argv[j], strlen(category->name))) { | |
216 | break; | |
217 | } | |
218 | } | |
219 | ||
220 | if (j != argc) { | |
221 | continue; | |
222 | } | |
223 | ||
224 | if (state != -1) { | |
225 | AST_VECTOR_RW_UNLOCK(&level->categories); | |
226 | return ast_strdup(category->name); | |
227 | } | |
228 | ||
229 | if (ast_cli_completion_add(ast_strdup(category->name))) { | |
230 | AST_VECTOR_RW_UNLOCK(&level->categories); | |
231 | return NULL; | |
232 | } | |
233 | } | |
234 | } | |
235 | ||
236 | AST_VECTOR_RW_UNLOCK(&level->categories); | |
237 | return NULL; | |
238 | } | |
239 | ||
240 | static int category_is_allowed(int sublevel, struct categories_level_t *level, uintmax_t ids) | |
241 | { | |
242 | /* Check level, and potentially allow but only if there is a matching state enabled */ | |
243 | if (ids & level->state) { | |
244 | int i; | |
245 | ||
246 | if (sublevel == AST_LOG_CATEGORY_ENABLED || sublevel == 0) { | |
247 | /* If at least one id is enabled then always allow these sublevels */ | |
248 | return 1; | |
249 | } | |
250 | ||
251 | AST_VECTOR_RW_RDLOCK(&level->categories); | |
252 | for (i = 0; i < AST_VECTOR_SIZE(&level->categories); ++i) { | |
253 | struct category_t *category = AST_VECTOR_GET(&level->categories, i); | |
254 | ||
255 | /* | |
256 | * If there is at least one matching category, and that category is enabled | |
257 | * or its sub-level is at or above the given sub-level then allow. | |
258 | */ | |
259 | if ((category->id & ids) && category->sublevel != AST_LOG_CATEGORY_DISABLED && | |
260 | (category->sublevel == AST_LOG_CATEGORY_ENABLED || category->sublevel >= sublevel)) { | |
261 | AST_VECTOR_RW_UNLOCK(&level->categories); | |
262 | return 1; | |
263 | } | |
264 | } | |
265 | AST_VECTOR_RW_UNLOCK(&level->categories); | |
266 | } | |
267 | ||
268 | return 0; | |
269 | } | |
270 | ||
271 | static struct categories_level_t debug_categories = { | |
272 | .type = __LOG_DEBUG, | |
273 | .sublevel = 0, | |
274 | .id_pool = 0, | |
275 | .state = 0, | |
276 | }; | |
277 | ||
278 | uintmax_t ast_debug_category_register(const char *name) | |
279 | { | |
280 | return category_register(&debug_categories, name); | |
281 | } | |
282 | ||
283 | int ast_debug_category_unregister(const char *name) | |
284 | { | |
285 | return category_unregister(&debug_categories, name); | |
286 | } | |
287 | ||
288 | int ast_debug_category_set_sublevel(const char *name, int sublevel) | |
289 | { | |
290 | return category_set_sublevel(NULL, &debug_categories, name, sublevel); | |
291 | } | |
292 | ||
293 | int ast_debug_category_set_sublevels(const char * const *names, | |
294 | size_t size, int default_sublevel) | |
295 | { | |
296 | return category_set_sublevels(&debug_categories, names, size, default_sublevel); | |
297 | } | |
298 | ||
299 | char *ast_debug_category_complete(const char * const *argv, int argc, | |
300 | const char *word, int state) | |
301 | { | |
302 | return category_complete(&debug_categories, argv, argc, word, state); | |
303 | } | |
304 | ||
305 | int ast_debug_category_is_allowed(int sublevel, uintmax_t ids) | |
306 | { | |
307 | return category_is_allowed(sublevel, &debug_categories, ids); | |
308 | } | |
309 | ||
310 | int ast_logger_category_unload(void) | |
311 | { | |
312 | AST_VECTOR_RW_FREE(&debug_categories.categories); | |
313 | return 0; | |
314 | } | |
315 | ||
316 | int ast_logger_category_load(void) | |
317 | { | |
318 | if (AST_VECTOR_RW_INIT(&debug_categories.categories, 10)) { | |
319 | return -1; | |
320 | } | |
321 | ||
322 | return 0; | |
323 | } |
7345 | 7345 | if (ast_strlen_zero(extension)) { |
7346 | 7346 | ast_log(LOG_ERROR,"You have to be kidding-- add exten '' to context %s? Figure out a name and call me back. Action ignored.\n", |
7347 | 7347 | con->name); |
7348 | /* We always need to deallocate 'data' on failure */ | |
7349 | if (datad) { | |
7350 | datad(data); | |
7351 | } | |
7348 | 7352 | return -1; |
7349 | 7353 | } |
7350 | 7354 | |
7400 | 7404 | } |
7401 | 7405 | |
7402 | 7406 | /* Be optimistic: Build the extension structure first */ |
7403 | if (!(tmp = ast_calloc(1, length))) | |
7407 | tmp = ast_calloc(1, length); | |
7408 | if (!tmp) { | |
7409 | /* We always need to deallocate 'data' on failure */ | |
7410 | if (datad) { | |
7411 | datad(data); | |
7412 | } | |
7404 | 7413 | return -1; |
7414 | } | |
7405 | 7415 | |
7406 | 7416 | if (ast_strlen_zero(label)) /* let's turn empty labels to a null ptr */ |
7407 | 7417 | label = 0; |
3532 | 3532 | return rtp_topic; |
3533 | 3533 | } |
3534 | 3534 | |
3535 | static uintmax_t debug_category_rtp_id; | |
3536 | ||
3537 | uintmax_t ast_debug_category_rtp_id(void) | |
3538 | { | |
3539 | return debug_category_rtp_id; | |
3540 | } | |
3541 | ||
3542 | static uintmax_t debug_category_rtp_packet_id; | |
3543 | ||
3544 | uintmax_t ast_debug_category_rtp_packet_id(void) | |
3545 | { | |
3546 | return debug_category_rtp_packet_id; | |
3547 | } | |
3548 | ||
3549 | static uintmax_t debug_category_rtcp_id; | |
3550 | ||
3551 | uintmax_t ast_debug_category_rtcp_id(void) | |
3552 | { | |
3553 | return debug_category_rtcp_id; | |
3554 | } | |
3555 | ||
3556 | static uintmax_t debug_category_rtcp_packet_id; | |
3557 | ||
3558 | uintmax_t ast_debug_category_rtcp_packet_id(void) | |
3559 | { | |
3560 | return debug_category_rtcp_packet_id; | |
3561 | } | |
3562 | ||
3563 | static uintmax_t debug_category_dtls_id; | |
3564 | ||
3565 | uintmax_t ast_debug_category_dtls_id(void) | |
3566 | { | |
3567 | return debug_category_dtls_id; | |
3568 | } | |
3569 | ||
3570 | static uintmax_t debug_category_dtls_packet_id; | |
3571 | ||
3572 | uintmax_t ast_debug_category_dtls_packet_id(void) | |
3573 | { | |
3574 | return debug_category_dtls_packet_id; | |
3575 | } | |
3576 | ||
3577 | static uintmax_t debug_category_ice_id; | |
3578 | ||
3579 | uintmax_t ast_debug_category_ice_id(void) | |
3580 | { | |
3581 | return debug_category_ice_id; | |
3582 | } | |
3583 | ||
3535 | 3584 | static void rtp_engine_shutdown(void) |
3536 | 3585 | { |
3537 | 3586 | int x; |
3556 | 3605 | } |
3557 | 3606 | mime_types_len = 0; |
3558 | 3607 | ast_rwlock_unlock(&mime_types_lock); |
3608 | ||
3609 | ast_debug_category_unregister(AST_LOG_CATEGORY_ICE); | |
3610 | ||
3611 | ast_debug_category_unregister(AST_LOG_CATEGORY_DTLS_PACKET); | |
3612 | ast_debug_category_unregister(AST_LOG_CATEGORY_DTLS); | |
3613 | ||
3614 | ast_debug_category_unregister(AST_LOG_CATEGORY_RTCP_PACKET); | |
3615 | ast_debug_category_unregister(AST_LOG_CATEGORY_RTCP); | |
3616 | ||
3617 | ast_debug_category_unregister(AST_LOG_CATEGORY_RTP_PACKET); | |
3618 | ast_debug_category_unregister(AST_LOG_CATEGORY_RTP); | |
3559 | 3619 | } |
3560 | 3620 | |
3561 | 3621 | int ast_rtp_engine_init(void) |
3683 | 3743 | add_static_payload(127, ast_format_slin96, 0); |
3684 | 3744 | /* payload types above 127 are not valid */ |
3685 | 3745 | |
3746 | debug_category_rtp_id = ast_debug_category_register(AST_LOG_CATEGORY_RTP); | |
3747 | debug_category_rtp_packet_id = ast_debug_category_register(AST_LOG_CATEGORY_RTP_PACKET); | |
3748 | debug_category_rtcp_id = ast_debug_category_register(AST_LOG_CATEGORY_RTCP); | |
3749 | debug_category_rtcp_packet_id = ast_debug_category_register(AST_LOG_CATEGORY_RTCP_PACKET); | |
3750 | debug_category_dtls_id = ast_debug_category_register(AST_LOG_CATEGORY_DTLS); | |
3751 | debug_category_dtls_packet_id = ast_debug_category_register(AST_LOG_CATEGORY_DTLS_PACKET); | |
3752 | debug_category_ice_id = ast_debug_category_register(AST_LOG_CATEGORY_ICE); | |
3753 | ||
3686 | 3754 | return 0; |
3687 | 3755 | } |
3688 | 3756 |
580 | 580 | } else if (playh) { |
581 | 581 | ast_copy_string(fn, "digits/hundred", sizeof(fn)); |
582 | 582 | playh = 0; |
583 | } else if (num < 20) { | |
583 | } else if (num < 20) { | |
584 | 584 | snprintf(fn, sizeof(fn), "digits/%d", num); |
585 | 585 | num = 0; |
586 | } else if (num < 100) { | |
586 | } else if (num < 100) { | |
587 | 587 | snprintf(fn, sizeof(fn), "digits/%d", (num /10) * 10); |
588 | 588 | num %= 10; |
589 | 589 | } else { |
1247 | 1247 | return res; |
1248 | 1248 | ast_copy_string(fn, "digits/thousand", sizeof(fn)); |
1249 | 1249 | num = num % 1000; |
1250 | } else if (num < 1000000000) { | |
1250 | } else if (num < 1000000000) { | |
1251 | 1251 | res = ast_say_number_full_fr(chan, num / 1000000, ints, language, options, audiofd, ctrlfd); |
1252 | 1252 | if (res) |
1253 | 1253 | return res; |
1482 | 1482 | } else if (num < 30) { |
1483 | 1483 | ast_copy_string(fn, "digits/20on", sizeof(fn)); |
1484 | 1484 | num -= 20; |
1485 | } else if (num < 100) { | |
1485 | } else if (num < 100) { | |
1486 | 1486 | snprintf(fn, sizeof(fn), "digits/%d", (num /10) * 10); |
1487 | 1487 | num %= 10; |
1488 | 1488 | } else { |
2494 | 2494 | } else if (playt) { |
2495 | 2495 | snprintf(fn, sizeof(fn), "digits/thousand"); |
2496 | 2496 | playt = 0; |
2497 | } else if (num < 10) { | |
2498 | snprintf(buf, 10, "%d", num); | |
2497 | } else if (num < 10) { | |
2498 | snprintf(buf, 12, "%d", num); | |
2499 | 2499 | if (last_length - strlen(buf) > 1 && last_length != 0) { |
2500 | 2500 | last_length = strlen(buf); |
2501 | 2501 | playz++; |
2503 | 2503 | } |
2504 | 2504 | snprintf(fn, sizeof(fn), "digits/%d", num); |
2505 | 2505 | num = 0; |
2506 | } else if (num < 100) { | |
2506 | } else if (num < 100) { | |
2507 | 2507 | snprintf(buf, 10, "%d", num); |
2508 | 2508 | if (last_length - strlen(buf) > 1 && last_length != 0) { |
2509 | 2509 | last_length = strlen(buf); |
2679 | 2679 | } else { |
2680 | 2680 | num = 0; |
2681 | 2681 | } |
2682 | } else if (num < 20) { | |
2682 | } else if (num < 20) { | |
2683 | 2683 | if (options && strlen(options) == 1 && num < 3) { |
2684 | 2684 | snprintf(fn, sizeof(fn), "digits/%d%s", num, options); |
2685 | 2685 | } else { |
2847 | 2847 | } else if (playohz) { |
2848 | 2848 | ast_copy_string(fn, "digits/0-hundred-odd", sizeof(fn)); |
2849 | 2849 | playohz = 0; |
2850 | } else if (num < 20) { | |
2850 | } else if (num < 20) { | |
2851 | 2851 | snprintf(fn, sizeof(fn), "digits/%d", num); |
2852 | 2852 | num = 0; |
2853 | } else if (num < 100) { | |
2853 | } else if (num < 100) { | |
2854 | 2854 | snprintf(fn, sizeof(fn), "digits/%d", (num /10) * 10); |
2855 | 2855 | num %= 10; |
2856 | 2856 | if ((num == 5) || (num == 4) || (num == 1)) playl++; |
100 | 100 | [AST_STREAM_STATE_INACTIVE] = "inactive", |
101 | 101 | }; |
102 | 102 | |
103 | #define MIN_STREAM_NAME_LEN 16 | |
104 | ||
103 | 105 | struct ast_stream *ast_stream_alloc(const char *name, enum ast_media_type type) |
104 | 106 | { |
105 | 107 | struct ast_stream *stream; |
106 | size_t name_len = MAX(strlen(S_OR(name, "")), 7); /* Ensure there is enough room for 'removed' */ | |
108 | size_t name_len = MAX(strlen(S_OR(name, "")), MIN_STREAM_NAME_LEN); /* Ensure there is enough room for 'removed' or a type-position */ | |
107 | 109 | |
108 | 110 | stream = ast_calloc(1, sizeof(*stream) + name_len + 1); |
109 | 111 | if (!stream) { |
135 | 137 | } |
136 | 138 | |
137 | 139 | stream_name = name ?: stream->name; |
138 | name_len = MAX(strlen(stream_name), 7); /* Ensure there is enough room for 'removed' */ | |
140 | name_len = MAX(strlen(S_OR(stream_name, "")), MIN_STREAM_NAME_LEN); /* Ensure there is enough room for 'removed' or a type-position */ | |
139 | 141 | new_stream = ast_calloc(1, sizeof(*stream) + name_len + 1); |
140 | 142 | if (!new_stream) { |
141 | 143 | return NULL; |
252 | 254 | |
253 | 255 | stream->state = state; |
254 | 256 | |
255 | /* When a stream is set to removed that means that any previous data for it | |
256 | * is no longer valid. We therefore change its name to removed and remove | |
257 | * any old metadata associated with it. | |
258 | */ | |
259 | if (state == AST_STREAM_STATE_REMOVED) { | |
260 | strcpy(stream->name, "removed"); | |
261 | ast_variables_destroy(stream->metadata); | |
262 | stream->metadata = NULL; | |
263 | if (stream->formats) { | |
264 | ast_format_cap_remove_by_type(stream->formats, AST_MEDIA_TYPE_UNKNOWN); | |
265 | } | |
266 | } | |
267 | 257 | } |
268 | 258 | |
269 | 259 | const char *ast_stream_state2str(enum ast_stream_state state) |
520 | 510 | |
521 | 511 | stream->position = AST_VECTOR_SIZE(&topology->streams) - 1; |
522 | 512 | |
513 | if (ast_strlen_zero(stream->name)) { | |
514 | snprintf(stream->name, MIN_STREAM_NAME_LEN, "%s-%d", ast_codec_media_type2str(stream->type), stream->position); | |
515 | } | |
516 | ||
523 | 517 | return AST_VECTOR_SIZE(&topology->streams) - 1; |
524 | 518 | } |
525 | 519 | |
576 | 570 | return AST_VECTOR_APPEND(&topology->streams, stream); |
577 | 571 | } |
578 | 572 | |
573 | if (ast_strlen_zero(stream->name)) { | |
574 | snprintf(stream->name, MIN_STREAM_NAME_LEN, "%s-%d", ast_codec_media_type2str(stream->type), stream->position); | |
575 | } | |
576 | ||
579 | 577 | return AST_VECTOR_REPLACE(&topology->streams, position, stream); |
580 | 578 | } |
581 | 579 | |
634 | 632 | return NULL; |
635 | 633 | } |
636 | 634 | |
637 | stream = ast_stream_alloc(ast_codec_media_type2str(type), type); | |
635 | stream = ast_stream_alloc(NULL, type); | |
638 | 636 | if (!stream) { |
639 | 637 | ao2_cleanup(new_cap); |
640 | 638 | ast_stream_topology_free(topology); |
649 | 647 | ast_stream_topology_free(topology); |
650 | 648 | return NULL; |
651 | 649 | } |
650 | ||
651 | snprintf(stream->name, MIN_STREAM_NAME_LEN, "%s-%d", ast_codec_media_type2str(stream->type), stream->position); | |
652 | 652 | } |
653 | 653 | |
654 | 654 | return topology; |
31 | 31 | |
32 | 32 | #include "asterisk.h" |
33 | 33 | |
34 | #include "asterisk/logger.h" | |
35 | #include "asterisk/logger_category.h" | |
34 | 36 | #include "asterisk/_private.h" |
35 | 37 | #include "asterisk/stun.h" |
36 | 38 | #include "asterisk/cli.h" |
37 | 39 | #include "asterisk/utils.h" |
38 | 40 | #include "asterisk/channel.h" |
39 | ||
40 | static int stundebug; /*!< Are we debugging stun? */ | |
41 | 41 | |
42 | 42 | /*! |
43 | 43 | * \brief STUN support code |
177 | 177 | |
178 | 178 | static int stun_process_attr(struct stun_state *state, struct stun_attr *attr) |
179 | 179 | { |
180 | if (stundebug) | |
180 | if (ast_debug_stun_packet_is_allowed) { | |
181 | 181 | ast_verbose("Found STUN Attribute %s (%04x), length %d\n", |
182 | stun_attr2str(ntohs(attr->attr)), (unsigned)ntohs(attr->attr), ntohs(attr->len)); | |
182 | stun_attr2str(ntohs(attr->attr)), (unsigned)ntohs(attr->attr), ntohs(attr->len)); | |
183 | } | |
183 | 184 | switch (ntohs(attr->attr)) { |
184 | 185 | case STUN_USERNAME: |
185 | 186 | state->username = (const char *) (attr->value); |
188 | 189 | state->password = (const char *) (attr->value); |
189 | 190 | break; |
190 | 191 | default: |
191 | if (stundebug) | |
192 | if (ast_debug_stun_packet_is_allowed) { | |
192 | 193 | ast_verbose("Ignoring STUN attribute %s (%04x), length %d\n", |
193 | stun_attr2str(ntohs(attr->attr)), (unsigned)ntohs(attr->attr), ntohs(attr->len)); | |
194 | } | |
194 | stun_attr2str(ntohs(attr->attr)), (unsigned)ntohs(attr->attr), ntohs(attr->len)); | |
195 | } | |
196 | } | |
197 | ||
195 | 198 | return 0; |
196 | 199 | } |
197 | 200 | |
274 | 277 | * while 'data' is advanced accordingly. |
275 | 278 | */ |
276 | 279 | if (len < sizeof(struct stun_header)) { |
277 | ast_debug(1, "Runt STUN packet (only %d, wanting at least %d)\n", (int) len, (int) sizeof(struct stun_header)); | |
280 | ast_debug_stun(1, "Runt STUN packet (only %d, wanting at least %d)\n", (int) len, (int) sizeof(struct stun_header)); | |
278 | 281 | return -1; |
279 | 282 | } |
280 | 283 | len -= sizeof(struct stun_header); |
281 | 284 | data += sizeof(struct stun_header); |
282 | 285 | x = ntohs(hdr->msglen); /* len as advertised in the message */ |
283 | if (stundebug) | |
286 | if (ast_debug_stun_packet_is_allowed) | |
284 | 287 | ast_verbose("STUN Packet, msg %s (%04x), length: %d\n", stun_msg2str(ntohs(hdr->msgtype)), (unsigned)ntohs(hdr->msgtype), x); |
285 | 288 | if (x > len) { |
286 | ast_debug(1, "Scrambled STUN packet length (got %d, expecting %d)\n", x, (int)len); | |
289 | ast_debug_stun(1, "Scrambled STUN packet length (got %d, expecting %d)\n", x, (int)len); | |
287 | 290 | } else |
288 | 291 | len = x; |
289 | 292 | memset(&st, 0, sizeof(st)); |
290 | 293 | while (len) { |
291 | 294 | if (len < sizeof(struct stun_attr)) { |
292 | ast_debug(1, "Runt Attribute (got %d, expecting %d)\n", (int)len, (int) sizeof(struct stun_attr)); | |
295 | ast_debug_stun(1, "Runt Attribute (got %d, expecting %d)\n", (int)len, (int) sizeof(struct stun_attr)); | |
293 | 296 | break; |
294 | 297 | } |
295 | 298 | attr = (struct stun_attr *)data; |
296 | 299 | /* compute total attribute length */ |
297 | 300 | x = ntohs(attr->len) + sizeof(struct stun_attr); |
298 | 301 | if (x > len) { |
299 | ast_debug(1, "Inconsistent Attribute (length %d exceeds remaining msg len %d)\n", x, (int)len); | |
302 | ast_debug_stun(1, "Inconsistent Attribute (length %d exceeds remaining msg len %d)\n", x, (int)len); | |
300 | 303 | break; |
301 | 304 | } |
302 | 305 | if (stun_cb) |
303 | 306 | stun_cb(attr, arg); |
304 | 307 | if (stun_process_attr(&st, attr)) { |
305 | ast_debug(1, "Failed to handle attribute %s (%04x)\n", stun_attr2str(ntohs(attr->attr)), (unsigned)ntohs(attr->attr)); | |
308 | ast_debug_stun(1, "Failed to handle attribute %s (%04x)\n", stun_attr2str(ntohs(attr->attr)), (unsigned)ntohs(attr->attr)); | |
306 | 309 | break; |
307 | 310 | } |
308 | 311 | /* Clear attribute id: in case previous entry was a string, |
336 | 339 | attr = (struct stun_attr *)resp->ies; |
337 | 340 | switch (ntohs(hdr->msgtype)) { |
338 | 341 | case STUN_BINDREQ: |
339 | if (stundebug) | |
342 | if (ast_debug_stun_packet_is_allowed) | |
340 | 343 | ast_verbose("STUN Bind Request, username: %s\n", |
341 | 344 | st.username ? st.username : "<none>"); |
342 | 345 | if (st.username) { |
354 | 357 | ret = AST_STUN_ACCEPT; |
355 | 358 | break; |
356 | 359 | default: |
357 | if (stundebug) | |
360 | if (ast_debug_stun_packet_is_allowed) | |
358 | 361 | ast_verbose("Dunno what to do with STUN message %04x (%s)\n", (unsigned)ntohs(hdr->msgtype), stun_msg2str(ntohs(hdr->msgtype))); |
359 | 362 | } |
360 | 363 | } |
415 | 418 | /* Send STUN message. */ |
416 | 419 | res = stun_send(s, dst, req); |
417 | 420 | if (res < 0) { |
418 | ast_debug(1, "stun_send try %d failed: %s\n", retry, strerror(errno)); | |
421 | ast_debug_stun(1, "stun_send try %d failed: %s\n", retry, strerror(errno)); | |
419 | 422 | break; |
420 | 423 | } |
421 | 424 | if (!answer) { |
458 | 461 | res = recvfrom(s, rsp_buf, sizeof(rsp_buf) - 1, |
459 | 462 | 0, (struct sockaddr *) &src, &srclen); |
460 | 463 | if (res < 0) { |
461 | ast_debug(1, "recvfrom try %d failed: %s\n", retry, strerror(errno)); | |
464 | ast_debug_stun(1, "recvfrom try %d failed: %s\n", retry, strerror(errno)); | |
462 | 465 | break; |
463 | 466 | } |
464 | 467 | |
499 | 502 | return CLI_SHOWUSAGE; |
500 | 503 | |
501 | 504 | if (!strncasecmp(a->argv[e->args-1], "on", 2)) |
502 | stundebug = 1; | |
505 | ast_debug_category_set_sublevel(AST_LOG_CATEGORY_STUN_PACKET, AST_LOG_CATEGORY_ENABLED); | |
503 | 506 | else if (!strncasecmp(a->argv[e->args-1], "off", 3)) |
504 | stundebug = 0; | |
507 | ast_debug_category_set_sublevel(AST_LOG_CATEGORY_STUN_PACKET, AST_LOG_CATEGORY_DISABLED); | |
505 | 508 | else |
506 | 509 | return CLI_SHOWUSAGE; |
507 | 510 | |
508 | ast_cli(a->fd, "STUN Debugging %s\n", stundebug ? "Enabled" : "Disabled"); | |
511 | ast_cli(a->fd, "STUN Debugging %s\n", ast_debug_stun_packet_is_allowed ? "Enabled" : "Disabled"); | |
509 | 512 | return CLI_SUCCESS; |
510 | 513 | } |
511 | 514 | |
513 | 516 | AST_CLI_DEFINE(handle_cli_stun_set_debug, "Enable/Disable STUN debugging"), |
514 | 517 | }; |
515 | 518 | |
519 | static uintmax_t debug_category_stun_id; | |
520 | ||
521 | uintmax_t ast_debug_category_stun_id(void) | |
522 | { | |
523 | return debug_category_stun_id; | |
524 | } | |
525 | ||
526 | static uintmax_t debug_category_stun_packet_id; | |
527 | ||
528 | uintmax_t ast_debug_category_stun_packet_id(void) | |
529 | { | |
530 | return debug_category_stun_packet_id; | |
531 | } | |
532 | ||
516 | 533 | static void stun_shutdown(void) |
517 | 534 | { |
518 | 535 | ast_cli_unregister_multiple(cli_stun, sizeof(cli_stun) / sizeof(struct ast_cli_entry)); |
536 | ||
537 | ast_debug_category_unregister(AST_LOG_CATEGORY_STUN_PACKET); | |
538 | ast_debug_category_unregister(AST_LOG_CATEGORY_STUN); | |
519 | 539 | } |
520 | 540 | |
521 | 541 | /*! \brief Initialize the STUN system in Asterisk */ |
523 | 543 | { |
524 | 544 | ast_cli_register_multiple(cli_stun, sizeof(cli_stun) / sizeof(struct ast_cli_entry)); |
525 | 545 | ast_register_cleanup(stun_shutdown); |
526 | } | |
546 | ||
547 | debug_category_stun_id = ast_debug_category_register(AST_LOG_CATEGORY_STUN); | |
548 | debug_category_stun_packet_id = ast_debug_category_register(AST_LOG_CATEGORY_STUN_PACKET); | |
549 | } |
607 | 607 | return handle_tcptls_connection(tcptls_session); |
608 | 608 | |
609 | 609 | client_start_error: |
610 | if (desc) { | |
611 | close(desc->accept_fd); | |
612 | desc->accept_fd = -1; | |
613 | } | |
614 | 610 | ao2_ref(tcptls_session, -1); |
615 | 611 | return NULL; |
616 | ||
617 | 612 | } |
618 | 613 | |
619 | 614 | struct ast_tcptls_session_instance *ast_tcptls_client_create(struct ast_tcptls_session_args *desc) |
629 | 624 | |
630 | 625 | /* If we return early, there is no connection */ |
631 | 626 | ast_sockaddr_setnull(&desc->old_address); |
632 | ||
633 | if (desc->accept_fd != -1) { | |
634 | close(desc->accept_fd); | |
635 | } | |
636 | 627 | |
637 | 628 | fd = desc->accept_fd = socket(ast_sockaddr_is_ipv6(&desc->remote_address) ? |
638 | 629 | AF_INET6 : AF_INET, SOCK_STREAM, IPPROTO_TCP); |
670 | 661 | if (!tcptls_session->stream) { |
671 | 662 | goto error; |
672 | 663 | } |
664 | ||
665 | /* From here on out, the iostream owns the accept_fd and it will take | |
666 | * care of closing it when the iostream is closed */ | |
673 | 667 | |
674 | 668 | tcptls_session->parent = desc; |
675 | 669 | tcptls_session->parent->worker_fn = NULL; |
307 | 307 | } |
308 | 308 | /* Don't worry about left over bits, they're extra anyway */ |
309 | 309 | return cnt; |
310 | } | |
311 | ||
312 | /*! \brief Decode BASE64 encoded text and return the string */ | |
313 | char *ast_base64decode_string(const char *src) | |
314 | { | |
315 | size_t encoded_len; | |
316 | size_t decoded_len; | |
317 | int padding = 0; | |
318 | unsigned char *decoded_string; | |
319 | ||
320 | if (ast_strlen_zero(src)) { | |
321 | return NULL; | |
322 | } | |
323 | ||
324 | encoded_len = strlen(src); | |
325 | if (encoded_len > 2 && src[encoded_len - 1] == '=') { | |
326 | padding++; | |
327 | if (src[encoded_len - 2] == '=') { | |
328 | padding++; | |
329 | } | |
330 | } | |
331 | ||
332 | decoded_len = (encoded_len / 4 * 3) - padding; | |
333 | decoded_string = ast_malloc(decoded_len + 1); | |
334 | if (!decoded_string) { | |
335 | return NULL; | |
336 | } | |
337 | ||
338 | ast_base64decode(decoded_string, src, decoded_len); | |
339 | decoded_string[decoded_len] = '\0'; | |
340 | ||
341 | return (char *)decoded_string; | |
310 | 342 | } |
311 | 343 | |
312 | 344 | /*! \brief encode text to BASE64 coding */ |
364 | 396 | int ast_base64encode(char *dst, const unsigned char *src, int srclen, int max) |
365 | 397 | { |
366 | 398 | return ast_base64encode_full(dst, src, srclen, max, 0); |
399 | } | |
400 | ||
401 | /*! \brief Encode to BASE64 and return encoded string */ | |
402 | char *ast_base64encode_string(const char *src) | |
403 | { | |
404 | size_t encoded_len; | |
405 | char *encoded_string; | |
406 | ||
407 | if (ast_strlen_zero(src)) { | |
408 | return NULL; | |
409 | } | |
410 | ||
411 | encoded_len = ((strlen(src) * 4 / 3 + 3) & ~3) + 1; | |
412 | encoded_string = ast_calloc(1, encoded_len); | |
413 | ||
414 | ast_base64encode(encoded_string, (const unsigned char *)src, strlen(src), encoded_len); | |
415 | ||
416 | return encoded_string; | |
367 | 417 | } |
368 | 418 | |
369 | 419 | static void base64_init(void) |
68 | 68 | $(call MOD_ADD_C,res_ari,ari/cli.c ari/config.c ari/ari_websockets.c) |
69 | 69 | $(call MOD_ADD_C,res_ari_model,ari/ari_model_validators.c) |
70 | 70 | $(call MOD_ADD_C,res_stasis_recording,stasis_recording/stored.c) |
71 | $(call MOD_ADD_C,res_stir_shaken,$(wildcard res_stir_shaken/*.c)) | |
71 | 72 | |
72 | 73 | res_parking.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION) |
73 | 74 |
51 | 51 | void ast_ari_bridges_list(struct ast_variable *headers, struct ast_ari_bridges_list_args *args, struct ast_ari_response *response); |
52 | 52 | /*! Argument struct for ast_ari_bridges_create() */ |
53 | 53 | struct ast_ari_bridges_create_args { |
54 | /*! Comma separated list of bridge type attributes (mixing, holding, dtmf_events, proxy_media, video_sfu). */ | |
54 | /*! Comma separated list of bridge type attributes (mixing, holding, dtmf_events, proxy_media, video_sfu, video_single). */ | |
55 | 55 | const char *type; |
56 | 56 | /*! Unique ID to give to the bridge being created. */ |
57 | 57 | const char *bridge_id; |
81 | 81 | void ast_ari_bridges_create(struct ast_variable *headers, struct ast_ari_bridges_create_args *args, struct ast_ari_response *response); |
82 | 82 | /*! Argument struct for ast_ari_bridges_create_with_id() */ |
83 | 83 | struct ast_ari_bridges_create_with_id_args { |
84 | /*! Comma separated list of bridge type attributes (mixing, holding, dtmf_events, proxy_media, video_sfu) to set. */ | |
84 | /*! Comma separated list of bridge type attributes (mixing, holding, dtmf_events, proxy_media, video_sfu, video_single) to set. */ | |
85 | 85 | const char *type; |
86 | 86 | /*! Unique ID to give to the bridge being created. */ |
87 | 87 | const char *bridge_id; |
210 | 210 | if (ast_msg_send(msg, to, from)) { |
211 | 211 | ast_ari_response_error(response, 404, "Not Found", |
212 | 212 | "Endpoint not found"); |
213 | return; | |
213 | 214 | } |
214 | 215 | |
215 | 216 | response->message = ast_json_null(); |
660 | 660 | dial_string_flat, PARK_DIAL_CONTEXT, ast_get_extension_registrar(existing_exten)); |
661 | 661 | } else if (ast_add_extension2_nolock(park_dial_context, 1, dial_string_flat, 1, NULL, NULL, |
662 | 662 | "Dial", duplicate_returnexten, ast_free_ptr, BASE_REGISTRAR, NULL, 0)) { |
663 | ast_free(duplicate_returnexten); | |
664 | 663 | ast_log(LOG_ERROR, "Failed to create parking redial parker extension %s@%s - Dial(%s)\n", |
665 | 664 | dial_string_flat, PARK_DIAL_CONTEXT, returnexten); |
666 | 665 | } |
476 | 476 | return -1; |
477 | 477 | } |
478 | 478 | |
479 | state->samples += f->samples; | |
479 | /* Only track our offset within the current file if we are not in the | |
480 | * the middle of an announcement */ | |
481 | if (!state->announcement) { | |
482 | state->samples += f->samples; | |
483 | } | |
484 | ||
480 | 485 | state->sample_queue -= f->samples; |
481 | 486 | if (ast_format_cmp(f->subclass.format, state->mohwfmt) == AST_FORMAT_CMP_NOT_EQUAL) { |
482 | 487 | ao2_replace(state->mohwfmt, f->subclass.format); |
1138 | 1143 | |
1139 | 1144 | AST_VECTOR_APPEND(playlist_entries, dup); |
1140 | 1145 | } else { |
1141 | ast_log(LOG_ERROR, "Playlist entries must be a URL or absolute path, '%s' provided.\n", var->value); | |
1146 | ast_log(LOG_ERROR, "Playlist entries must be an HTTP(S) URL or absolute path, '%s' provided.\n", var->value); | |
1142 | 1147 | } |
1143 | 1148 | } else if (!strcasecmp(var->name, "directory")) { |
1144 | 1149 | ast_copy_string(mohclass->dir, var->value, sizeof(mohclass->dir)); |
1603 | 1608 | if (var) { |
1604 | 1609 | const char *mode = ast_variable_find_in_list(var, "mode"); |
1605 | 1610 | if (ast_strings_equal(mode, "playlist")) { |
1606 | struct ast_variable *entries = ast_load_realtime("musiconhold_entry", "name", name, SENTINEL); | |
1607 | struct ast_variable *cur = entries; | |
1611 | struct ast_config *entries = ast_load_realtime_multientry("musiconhold_entry", "position >=", "0", "name", name, SENTINEL); | |
1612 | char *category = NULL; | |
1608 | 1613 | size_t entry_count = 0; |
1609 | for (; cur; cur = cur->next) { | |
1610 | if (!strcmp(cur->name, "entry")) { | |
1611 | struct ast_variable *dup = ast_variable_new(cur->name, cur->value, ""); | |
1614 | ||
1615 | while ((category = ast_category_browse(entries, category))) { | |
1616 | const char *entry = ast_variable_retrieve(entries, category, "entry"); | |
1617 | ||
1618 | if (entry) { | |
1619 | struct ast_variable *dup = ast_variable_new("entry", entry, ""); | |
1612 | 1620 | if (dup) { |
1613 | 1621 | entry_count++; |
1614 | 1622 | ast_variable_list_append(&var, dup); |
1615 | 1623 | } |
1616 | 1624 | } |
1617 | 1625 | } |
1618 | ast_variables_destroy(entries); | |
1626 | ast_config_destroy(entries); | |
1619 | 1627 | |
1620 | 1628 | if (entry_count == 0) { |
1621 | 1629 | /* Behave as though this class doesn't exist */ |
723 | 723 | |
724 | 724 | if (ast_add_extension2_nolock(context, replace, extension, priority, NULL, NULL, |
725 | 725 | application, data_duplicate, ast_free_ptr, registrar, NULL, 0)) { |
726 | ast_free(data_duplicate); | |
727 | 726 | return -1; |
728 | 727 | } |
729 | 728 |
617 | 617 | res = pjsip_tcp_transport_start3(ast_sip_get_pjsip_endpoint(), &cfg, |
618 | 618 | &temp_state->state->factory); |
619 | 619 | } |
620 | #if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 | |
620 | 621 | } else if (transport->type == AST_TRANSPORT_TLS) { |
621 | 622 | static int option = 1; |
622 | 623 | |
647 | 648 | &temp_state->state->host, NULL, transport->async_operations, |
648 | 649 | &temp_state->state->factory); |
649 | 650 | } |
651 | #endif | |
650 | 652 | } else if ((transport->type == AST_TRANSPORT_WS) || (transport->type == AST_TRANSPORT_WSS)) { |
651 | 653 | if (transport->cos || transport->tos) { |
652 | 654 | ast_log(LOG_WARNING, "TOS and COS values ignored for websocket transport\n"); |
977 | 979 | } |
978 | 980 | |
979 | 981 | /*! \brief Helper function which turns a cipher name into an identifier */ |
982 | #if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 | |
980 | 983 | static pj_ssl_cipher cipher_name_to_id(const char *name) |
981 | 984 | { |
982 | 985 | pj_ssl_cipher ciphers[PJ_SSL_SOCK_MAX_CIPHERS]; |
996 | 999 | |
997 | 1000 | return 0; |
998 | 1001 | } |
1002 | #endif | |
999 | 1003 | |
1000 | 1004 | /*! |
1001 | 1005 | * \internal |
1007 | 1011 | * \retval 0 on success. |
1008 | 1012 | * \retval -1 on error. |
1009 | 1013 | */ |
1014 | #if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 | |
1010 | 1015 | static int transport_cipher_add(struct ast_sip_transport_state *state, const char *name) |
1011 | 1016 | { |
1012 | 1017 | pj_ssl_cipher cipher; |
1037 | 1042 | return -1; |
1038 | 1043 | } |
1039 | 1044 | } |
1045 | #endif | |
1040 | 1046 | |
1041 | 1047 | /*! \brief Custom handler for TLS cipher setting */ |
1048 | #if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 | |
1042 | 1049 | static int transport_tls_cipher_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) |
1043 | 1050 | { |
1044 | 1051 | struct ast_sip_transport *transport = obj; |
1065 | 1072 | } |
1066 | 1073 | return res ? -1 : 0; |
1067 | 1074 | } |
1068 | ||
1075 | #endif | |
1076 | ||
1077 | #if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 | |
1069 | 1078 | static void cipher_to_str(char **buf, const pj_ssl_cipher *ciphers, unsigned int cipher_num) |
1070 | 1079 | { |
1071 | 1080 | struct ast_str *str; |
1087 | 1096 | *buf = ast_strdup(ast_str_buffer(str)); |
1088 | 1097 | ast_free(str); |
1089 | 1098 | } |
1090 | ||
1099 | #endif | |
1100 | ||
1101 | #if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 | |
1091 | 1102 | static int transport_tls_cipher_to_str(const void *obj, const intptr_t *args, char **buf) |
1092 | 1103 | { |
1093 | 1104 | const struct ast_sip_transport *transport = obj; |
1100 | 1111 | cipher_to_str(buf, state->ciphers, state->tls.ciphers_num); |
1101 | 1112 | return *buf ? 0 : -1; |
1102 | 1113 | } |
1103 | ||
1114 | #endif | |
1115 | ||
1116 | #if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 | |
1104 | 1117 | static char *handle_pjsip_list_ciphers(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) |
1105 | 1118 | { |
1106 | 1119 | pj_ssl_cipher ciphers[PJ_SSL_SOCK_MAX_CIPHERS]; |
1131 | 1144 | ast_free(buf); |
1132 | 1145 | return CLI_SUCCESS; |
1133 | 1146 | } |
1147 | #endif | |
1134 | 1148 | |
1135 | 1149 | /*! \brief Custom handler for localnet setting */ |
1136 | 1150 | static int transport_localnet_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) |
1330 | 1344 | } |
1331 | 1345 | |
1332 | 1346 | static struct ast_cli_entry cli_commands[] = { |
1347 | #if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 | |
1333 | 1348 | AST_CLI_DEFINE(handle_pjsip_list_ciphers, "List available OpenSSL cipher names"), |
1349 | #endif | |
1334 | 1350 | AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "List PJSIP Transports", |
1335 | 1351 | .command = "pjsip list transports", |
1336 | 1352 | .usage = "Usage: pjsip list transports [ like <pattern> ]\n" |
1432 | 1448 | ast_sorcery_object_field_register_custom(sorcery, "transport", "verify_client", "", transport_tls_bool_handler, verify_client_to_str, NULL, 0, 0); |
1433 | 1449 | ast_sorcery_object_field_register_custom(sorcery, "transport", "require_client_cert", "", transport_tls_bool_handler, require_client_cert_to_str, NULL, 0, 0); |
1434 | 1450 | ast_sorcery_object_field_register_custom(sorcery, "transport", "method", "", transport_tls_method_handler, tls_method_to_str, NULL, 0, 0); |
1451 | #if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 | |
1435 | 1452 | ast_sorcery_object_field_register_custom(sorcery, "transport", "cipher", "", transport_tls_cipher_handler, transport_tls_cipher_to_str, NULL, 0, 0); |
1453 | #endif | |
1436 | 1454 | ast_sorcery_object_field_register_custom(sorcery, "transport", "local_net", "", transport_localnet_handler, localnet_to_str, localnet_to_vl, 0, 0); |
1437 | 1455 | ast_sorcery_object_field_register_custom(sorcery, "transport", "tos", "0", transport_tos_handler, tos_to_str, NULL, 0, 0); |
1438 | 1456 | ast_sorcery_object_field_register(sorcery, "transport", "cos", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_transport, cos)); |
1881 | 1881 | ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_rpid", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.send_rpid)); |
1882 | 1882 | ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rpid_immediate", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.rpid_immediate)); |
1883 | 1883 | ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_diversion", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.send_diversion)); |
1884 | ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_history_info", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.send_history_info)); | |
1884 | 1885 | ast_sorcery_object_field_register(sip_sorcery, "endpoint", "mailboxes", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, subscription.mwi.mailboxes)); |
1885 | 1886 | ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "voicemail_extension", "", voicemail_extension_handler, voicemail_extension_to_str, NULL, 0, 0); |
1886 | 1887 | ast_sorcery_object_field_register(sip_sorcery, "endpoint", "aggregate_mwi", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, subscription.mwi.aggregate)); |
1965 | 1966 | ast_sorcery_object_field_register(sip_sorcery, "endpoint", "accept_multiple_sdp_answers", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtp.accept_multiple_sdp_answers)); |
1966 | 1967 | ast_sorcery_object_field_register(sip_sorcery, "endpoint", "suppress_q850_reason_headers", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, suppress_q850_reason_headers)); |
1967 | 1968 | ast_sorcery_object_field_register(sip_sorcery, "endpoint", "ignore_183_without_sdp", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, ignore_183_without_sdp)); |
1969 | ast_sorcery_object_field_register(sip_sorcery, "endpoint", "stir_shaken", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, stir_shaken)); | |
1968 | 1970 | |
1969 | 1971 | if (ast_sip_initialize_sorcery_transport()) { |
1970 | 1972 | ast_log(LOG_ERROR, "Failed to register SIP transport support with sorcery\n"); |
28 | 28 | #include "include/res_pjsip_private.h" |
29 | 29 | #include "asterisk/res_pjsip_cli.h" |
30 | 30 | #include "asterisk/taskprocessor.h" |
31 | ||
32 | #include <regex.h> | |
31 | 33 | |
32 | 34 | #define TASK_BUCKETS 53 |
33 | 35 | |
104 | 106 | * Don't restart if the task returned <= 0 or if the interval |
105 | 107 | * was set to 0 while the task was running |
106 | 108 | */ |
107 | if (res <= 0 || !schtd->interval) { | |
109 | if ((schtd->flags & AST_SIP_SCHED_TASK_ONESHOT) || res <= 0 || !schtd->interval) { | |
108 | 110 | schtd->interval = 0; |
109 | 111 | ao2_unlock(schtd); |
110 | 112 | ao2_unlink(tasks, schtd); |
231 | 233 | return res; |
232 | 234 | } |
233 | 235 | |
234 | ||
235 | int ast_sip_sched_task_get_times(struct ast_sip_sched_task *schtd, | |
236 | struct timeval *queued, struct timeval *last_start, struct timeval *last_end) | |
236 | int ast_sip_sched_task_get_times2(struct ast_sip_sched_task *schtd, | |
237 | struct timeval *queued, struct timeval *last_start, struct timeval *last_end, | |
238 | int *interval, int *time_left, struct timeval *next_start) | |
237 | 239 | { |
238 | 240 | ao2_lock(schtd); |
239 | 241 | if (queued) { |
245 | 247 | if (last_end) { |
246 | 248 | memcpy(last_end, &schtd->last_end, sizeof(struct timeval)); |
247 | 249 | } |
250 | ||
251 | if (interval) { | |
252 | *interval = schtd->interval; | |
253 | } | |
254 | ||
255 | if (time_left || next_start) { | |
256 | int delay; | |
257 | struct timeval since_when; | |
258 | struct timeval now; | |
259 | struct timeval next; | |
260 | ||
261 | if (schtd->interval) { | |
262 | delay = schtd->interval; | |
263 | now = ast_tvnow(); | |
264 | ||
265 | if (schtd->flags & AST_SIP_SCHED_TASK_DELAY) { | |
266 | since_when = schtd->is_running ? now : schtd->last_end; | |
267 | } else { | |
268 | since_when = schtd->last_start.tv_sec ? schtd->last_start : schtd->when_queued; | |
269 | } | |
270 | ||
271 | delay -= ast_tvdiff_ms(now, since_when); | |
272 | ||
273 | delay = delay < 0 ? 0 : delay; | |
274 | ||
275 | ||
276 | if (time_left) { | |
277 | *time_left = delay; | |
278 | } | |
279 | ||
280 | if (next_start) { | |
281 | next = ast_tvadd(now, ast_tv(delay / 1000, (delay % 1000) * 1000)); | |
282 | memcpy(next_start, &next, sizeof(struct timeval)); | |
283 | } | |
284 | } else { | |
285 | if (time_left) { | |
286 | *time_left = -1; | |
287 | } | |
288 | } | |
289 | ||
290 | } | |
291 | ||
248 | 292 | ao2_unlock(schtd); |
249 | 293 | |
250 | 294 | return 0; |
295 | } | |
296 | ||
297 | ||
298 | int ast_sip_sched_task_get_times(struct ast_sip_sched_task *schtd, | |
299 | struct timeval *queued, struct timeval *last_start, struct timeval *last_end) | |
300 | { | |
301 | return ast_sip_sched_task_get_times2(schtd, queued, last_start, last_end, NULL, NULL, NULL); | |
302 | } | |
303 | ||
304 | int ast_sip_sched_task_get_times_by_name2(const char *name, | |
305 | struct timeval *queued, struct timeval *last_start, struct timeval *last_end, | |
306 | int *interval, int *time_left, struct timeval *next_start) | |
307 | { | |
308 | int res; | |
309 | struct ast_sip_sched_task *schtd; | |
310 | ||
311 | if (ast_strlen_zero(name)) { | |
312 | return -1; | |
313 | } | |
314 | ||
315 | schtd = ao2_find(tasks, name, OBJ_SEARCH_KEY); | |
316 | if (!schtd) { | |
317 | return -1; | |
318 | } | |
319 | ||
320 | res = ast_sip_sched_task_get_times2(schtd, queued, last_start, last_end, interval, time_left, | |
321 | next_start); | |
322 | ao2_ref(schtd, -1); | |
323 | return res; | |
251 | 324 | } |
252 | 325 | |
253 | 326 | int ast_sip_sched_task_get_times_by_name(const char *name, |
254 | 327 | struct timeval *queued, struct timeval *last_start, struct timeval *last_end) |
255 | 328 | { |
256 | int res; | |
257 | struct ast_sip_sched_task *schtd; | |
258 | ||
259 | if (ast_strlen_zero(name)) { | |
260 | return -1; | |
261 | } | |
262 | ||
263 | schtd = ao2_find(tasks, name, OBJ_SEARCH_KEY); | |
264 | if (!schtd) { | |
265 | return -1; | |
266 | } | |
267 | ||
268 | res = ast_sip_sched_task_get_times(schtd, queued, last_start, last_end); | |
269 | ao2_ref(schtd, -1); | |
270 | return res; | |
329 | return ast_sip_sched_task_get_times_by_name2(name, queued, last_start, last_end, | |
330 | NULL, NULL, NULL); | |
271 | 331 | } |
272 | 332 | |
273 | 333 | int ast_sip_sched_task_get_name(struct ast_sip_sched_task *schtd, char *name, size_t maxlen) |
286 | 346 | int ast_sip_sched_task_get_next_run(struct ast_sip_sched_task *schtd) |
287 | 347 | { |
288 | 348 | int delay; |
289 | struct timeval since_when; | |
290 | struct timeval now; | |
291 | ||
292 | ao2_lock(schtd); | |
293 | ||
294 | if (schtd->interval) { | |
295 | delay = schtd->interval; | |
296 | now = ast_tvnow(); | |
297 | ||
298 | if (schtd->flags & AST_SIP_SCHED_TASK_DELAY) { | |
299 | since_when = schtd->is_running ? now : schtd->last_end; | |
300 | } else { | |
301 | since_when = schtd->last_start.tv_sec ? schtd->last_start : schtd->when_queued; | |
302 | } | |
303 | ||
304 | delay -= ast_tvdiff_ms(now, since_when); | |
305 | ||
306 | delay = delay < 0 ? 0 : delay; | |
307 | } else { | |
308 | delay = -1; | |
309 | } | |
310 | ||
311 | ao2_unlock(schtd); | |
349 | ||
350 | ast_sip_sched_task_get_times2(schtd, NULL, NULL, NULL, NULL, &delay, NULL); | |
312 | 351 | |
313 | 352 | return delay; |
314 | 353 | } |
395 | 434 | schtd->task = sip_task; |
396 | 435 | schtd->interval = interval; |
397 | 436 | schtd->flags = flags; |
437 | schtd->last_start = ast_tv(0, 0); | |
398 | 438 | if (!ast_strlen_zero(name)) { |
399 | 439 | strcpy(schtd->name, name); /* Safe */ |
400 | 440 | } else { |
444 | 484 | #undef ID_LEN |
445 | 485 | } |
446 | 486 | |
487 | #define TIME_FORMAT "%a %T" | |
488 | ||
447 | 489 | static char *cli_show_tasks(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) |
448 | 490 | { |
449 | 491 | struct ao2_iterator iter; |
450 | 492 | struct ao2_container *sorted_tasks; |
451 | 493 | struct ast_sip_sched_task *schtd; |
452 | const char *log_format; | |
453 | 494 | struct ast_tm tm; |
495 | char times_run[16]; | |
454 | 496 | char queued[32]; |
455 | 497 | char last_start[32]; |
456 | 498 | char next_start[32]; |
457 | int datelen; | |
458 | 499 | struct timeval now; |
459 | static const char separator[] = "============================================="; | |
500 | int using_regex = 0; | |
501 | regex_t regex; | |
460 | 502 | |
461 | 503 | switch (cmd) { |
462 | 504 | case CLI_INIT: |
463 | 505 | e->command = "pjsip show scheduled_tasks"; |
464 | e->usage = "Usage: pjsip show scheduled_tasks\n" | |
465 | " Show all scheduled tasks\n"; | |
506 | e->usage = "Usage: pjsip show scheduled_tasks [ like <pattern> ]\n" | |
507 | " Show scheduled pjsip tasks\n"; | |
466 | 508 | return NULL; |
467 | 509 | case CLI_GENERATE: |
468 | 510 | return NULL; |
469 | 511 | } |
470 | 512 | |
471 | if (a->argc != 3) { | |
513 | if (a->argc != 3 && a->argc != 5) { | |
472 | 514 | return CLI_SHOWUSAGE; |
515 | } | |
516 | ||
517 | if (a->argc == 5) { | |
518 | int regrc; | |
519 | if (!strcasecmp(a->argv[3], "like") == 0) { | |
520 | return CLI_SHOWUSAGE; | |
521 | } | |
522 | regrc = regcomp(®ex, a->argv[4], REG_EXTENDED | REG_ICASE | REG_NOSUB); | |
523 | if (regrc) { | |
524 | char err[256]; | |
525 | regerror(regrc, ®ex, err, 256); | |
526 | ast_cli(a->fd, "PJSIP Scheduled Tasks: Error: %s\n", err); | |
527 | return CLI_FAILURE; | |
528 | } | |
529 | using_regex = 1; | |
473 | 530 | } |
474 | 531 | |
475 | 532 | /* Get a sorted snapshot of the scheduled tasks */ |
476 | 533 | sorted_tasks = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, |
477 | 534 | ast_sip_sched_task_sort_fn, NULL); |
478 | 535 | if (!sorted_tasks) { |
479 | return CLI_SUCCESS; | |
536 | ast_cli(a->fd, "PJSIP Scheduled Tasks: Unable to allocate temporary container\n"); | |
537 | return CLI_FAILURE; | |
480 | 538 | } |
481 | 539 | if (ao2_container_dup(sorted_tasks, tasks, 0)) { |
482 | 540 | ao2_ref(sorted_tasks, -1); |
483 | return CLI_SUCCESS; | |
541 | ast_cli(a->fd, "PJSIP Scheduled Tasks: Unable to sort temporary container\n"); | |
542 | return CLI_FAILURE; | |
484 | 543 | } |
485 | 544 | |
486 | 545 | now = ast_tvnow(); |
487 | log_format = ast_logger_get_dateformat(); | |
488 | 546 | |
489 | 547 | ast_localtime(&now, &tm, NULL); |
490 | datelen = ast_strftime(queued, sizeof(queued), log_format, &tm); | |
491 | 548 | |
492 | 549 | ast_cli(a->fd, "PJSIP Scheduled Tasks:\n\n"); |
493 | 550 | |
494 | ast_cli(a->fd, "%1$-45s %2$-9s %3$-9s %4$-5s %6$-*5$s %7$-*5$s %8$-*5$s %9$7s\n", | |
495 | "Task Name", "Interval", "Times Run", "State", | |
496 | datelen, "Queued", "Last Started", "Next Start", "( secs)"); | |
497 | ||
498 | ast_cli(a->fd, "%1$-45.45s %2$-9.9s %3$-9.9s %4$-5.5s %6$-*5$.*5$s %7$-*5$.*5$s %9$-*8$.*8$s\n", | |
499 | separator, separator, separator, separator, | |
500 | datelen, separator, separator, datelen + 8, separator); | |
551 | ast_cli(a->fd, | |
552 | "<Task Name....................................> <Interval> <Times Run> <State>" | |
553 | " <Queued....> <Last Start> <Next Start.....secs>\n" | |
554 | "==============================================================================" | |
555 | "===================================================\n"); | |
501 | 556 | |
502 | 557 | iter = ao2_iterator_init(sorted_tasks, AO2_ITERATOR_UNLINK); |
503 | 558 | for (; (schtd = ao2_iterator_next(&iter)); ao2_ref(schtd, -1)) { |
505 | 560 | struct timeval next; |
506 | 561 | |
507 | 562 | ao2_lock(schtd); |
563 | ||
564 | if (using_regex && regexec(®ex, schtd->name, 0, NULL, 0) == REG_NOMATCH) { | |
565 | ao2_unlock(schtd); | |
566 | continue; | |
567 | } | |
508 | 568 | |
509 | 569 | next_run_sec = ast_sip_sched_task_get_next_run(schtd) / 1000; |
510 | 570 | if (next_run_sec < 0) { |
515 | 575 | next = ast_tvadd(now, ast_tv(next_run_sec, 0)); |
516 | 576 | |
517 | 577 | ast_localtime(&schtd->when_queued, &tm, NULL); |
518 | ast_strftime(queued, sizeof(queued), log_format, &tm); | |
519 | ||
520 | if (ast_tvzero(schtd->last_start)) { | |
521 | strcpy(last_start, "not yet started"); | |
522 | } else { | |
523 | ast_localtime(&schtd->last_start, &tm, NULL); | |
524 | ast_strftime(last_start, sizeof(last_start), log_format, &tm); | |
525 | } | |
578 | ast_strftime(queued, sizeof(queued), TIME_FORMAT, &tm); | |
579 | ||
580 | ast_localtime(&schtd->last_start, &tm, NULL); | |
581 | ast_strftime(last_start, sizeof(last_start), TIME_FORMAT, &tm); | |
526 | 582 | |
527 | 583 | ast_localtime(&next, &tm, NULL); |
528 | ast_strftime(next_start, sizeof(next_start), log_format, &tm); | |
529 | ||
530 | ast_cli(a->fd, "%1$-46.46s%2$9.3f %3$9d %4$-5s %6$-*5$s %7$-*5$s %8$-*5$s (%9$5d)\n", | |
584 | ast_strftime(next_start, sizeof(next_start), TIME_FORMAT, &tm); | |
585 | ||
586 | sprintf(times_run, "%d", schtd->run_count); | |
587 | ||
588 | ast_cli(a->fd, "%-46.46s %9d %9s %-5s %-12s %-12s %-12s %8d\n", | |
531 | 589 | schtd->name, |
532 | schtd->interval / 1000.0, | |
533 | schtd->run_count, | |
590 | schtd->interval / 1000, | |
591 | schtd->flags & AST_SIP_SCHED_TASK_ONESHOT ? "oneshot" : times_run, | |
534 | 592 | schtd->is_running ? "run" : "wait", |
535 | datelen, queued, last_start, | |
593 | queued, | |
594 | (ast_tvzero(schtd->last_start) || (schtd->flags & AST_SIP_SCHED_TASK_ONESHOT) ? "" : last_start), | |
536 | 595 | next_start, |
537 | 596 | next_run_sec); |
538 | 597 | ao2_unlock(schtd); |
539 | 598 | } |
599 | if (using_regex) { | |
600 | regfree(®ex); | |
601 | } | |
540 | 602 | ao2_iterator_destroy(&iter); |
603 | ast_cli(a->fd, "\nTotal Scheduled Tasks: %d\n\n", ao2_container_count(sorted_tasks)); | |
541 | 604 | ao2_ref(sorted_tasks, -1); |
542 | ast_cli(a->fd, "\n"); | |
543 | 605 | |
544 | 606 | return CLI_SUCCESS; |
545 | 607 | } |
546 | 608 | |
547 | 609 | static struct ast_cli_entry cli_commands[] = { |
548 | AST_CLI_DEFINE(cli_show_tasks, "Show all scheduled tasks"), | |
610 | AST_CLI_DEFINE(cli_show_tasks, "Show pjsip scheduled tasks"), | |
549 | 611 | }; |
550 | 612 | |
551 | 613 | int ast_sip_initialize_scheduler(void) |
77 | 77 | static void *keepalive_transport_thread(void *data) |
78 | 78 | { |
79 | 79 | struct ao2_container *transports; |
80 | pj_thread_desc desc; | |
80 | pj_thread_desc desc = { 0 }; | |
81 | 81 | pj_thread_t *thread; |
82 | 82 | |
83 | 83 | if (pj_thread_register("Asterisk Keepalive Thread", desc, &thread) != PJ_SUCCESS) { |
425 | 425 | <synopsis>Send the Diversion header, conveying the diversion |
426 | 426 | information to the called user agent</synopsis> |
427 | 427 | </configOption> |
428 | <configOption name="send_history_info" default="no"> | |
429 | <synopsis>Send the History-Info header, conveying the diversion | |
430 | information to the called and calling user agents</synopsis> | |
431 | </configOption> | |
428 | 432 | <configOption name="send_pai" default="no"> |
429 | 433 | <synopsis>Send the P-Asserted-Identity header</synopsis> |
430 | 434 | </configOption> |
1151 | 1155 | this 183 can cause loss of ringback tone. This flag emulates |
1152 | 1156 | the behavior of chan_sip and prevents these 183 responses from |
1153 | 1157 | being forwarded.</para> |
1158 | </description> | |
1159 | </configOption> | |
1160 | <configOption name="stir_shaken" default="no"> | |
1161 | <synopsis>Enable STIR/SHAKEN support on this endpoint</synopsis> | |
1162 | <description><para> | |
1163 | Enable STIR/SHAKEN support on this endpoint. On incoming INVITEs, | |
1164 | the Identity header will be checked for validity. On outgoing | |
1165 | INVITEs, an Identity header will be added.</para> | |
1154 | 1166 | </description> |
1155 | 1167 | </configOption> |
1156 | 1168 | </configObject> |
3106 | 3118 | return endpoint; |
3107 | 3119 | } |
3108 | 3120 | |
3121 | char *ast_sip_rdata_get_header_value(pjsip_rx_data *rdata, const pj_str_t str) | |
3122 | { | |
3123 | pjsip_generic_string_hdr *hdr; | |
3124 | pj_str_t hdr_val; | |
3125 | ||
3126 | hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str, NULL); | |
3127 | if (!hdr) { | |
3128 | return NULL; | |
3129 | } | |
3130 | ||
3131 | pj_strdup_with_null(rdata->tp_info.pool, &hdr_val, &hdr->hvalue); | |
3132 | ||
3133 | return hdr_val.ptr; | |
3134 | } | |
3135 | ||
3109 | 3136 | static int do_cli_dump_endpt(void *v_a) |
3110 | 3137 | { |
3111 | 3138 | struct ast_cli_args *a = v_a; |
3676 | 3703 | return 0; |
3677 | 3704 | } |
3678 | 3705 | |
3679 | pjsip_dialog *ast_sip_create_dialog_uas(const struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, pj_status_t *status) | |
3706 | typedef pj_status_t (*create_dlg_uac)(pjsip_user_agent *ua, pjsip_rx_data *rdata, | |
3707 | const pj_str_t *contact, pjsip_dialog **p_dlg); | |
3708 | ||
3709 | static pjsip_dialog *create_dialog_uas(const struct ast_sip_endpoint *endpoint, | |
3710 | pjsip_rx_data *rdata, pj_status_t *status, create_dlg_uac create_fun) | |
3680 | 3711 | { |
3681 | 3712 | pjsip_dialog *dlg; |
3682 | 3713 | pj_str_t contact; |
3711 | 3742 | (type != PJSIP_TRANSPORT_UDP && type != PJSIP_TRANSPORT_UDP6) ? ";transport=" : "", |
3712 | 3743 | (type != PJSIP_TRANSPORT_UDP && type != PJSIP_TRANSPORT_UDP6) ? pjsip_transport_get_type_name(type) : ""); |
3713 | 3744 | |
3714 | #ifdef HAVE_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK | |
3715 | *status = pjsip_dlg_create_uas_and_inc_lock(pjsip_ua_instance(), rdata, &contact, &dlg); | |
3716 | #else | |
3717 | *status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, &contact, &dlg); | |
3718 | #endif | |
3745 | *status = create_fun(pjsip_ua_instance(), rdata, &contact, &dlg); | |
3719 | 3746 | if (*status != PJ_SUCCESS) { |
3720 | 3747 | char err[PJ_ERR_MSG_SIZE]; |
3721 | 3748 | |
3728 | 3755 | dlg->sess_count++; |
3729 | 3756 | pjsip_dlg_set_transport(dlg, &selector); |
3730 | 3757 | dlg->sess_count--; |
3758 | ||
3759 | return dlg; | |
3760 | } | |
3761 | ||
3762 | pjsip_dialog *ast_sip_create_dialog_uas(const struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata, pj_status_t *status) | |
3763 | { | |
3731 | 3764 | #ifdef HAVE_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK |
3732 | pjsip_dlg_dec_lock(dlg); | |
3765 | pjsip_dialog *dlg; | |
3766 | ||
3767 | dlg = create_dialog_uas(endpoint, rdata, status, pjsip_dlg_create_uas_and_inc_lock); | |
3768 | if (dlg) { | |
3769 | pjsip_dlg_dec_lock(dlg); | |
3770 | } | |
3771 | ||
3772 | return dlg; | |
3773 | #else | |
3774 | return create_dialog_uas(endpoint, rdata, status, pjsip_dlg_create_uas); | |
3733 | 3775 | #endif |
3776 | } | |
3777 | ||
3778 | pjsip_dialog *ast_sip_create_dialog_uas_locked(const struct ast_sip_endpoint *endpoint, | |
3779 | pjsip_rx_data *rdata, pj_status_t *status) | |
3780 | { | |
3781 | #ifdef HAVE_PJSIP_DLG_CREATE_UAS_AND_INC_LOCK | |
3782 | return create_dialog_uas(endpoint, rdata, status, pjsip_dlg_create_uas_and_inc_lock); | |
3783 | #else | |
3784 | /* | |
3785 | * This is put here in order to be compatible with older versions of pjproject. | |
3786 | * Best we can do in this case is immediately lock after getting the dialog. | |
3787 | * However, that does leave a "gap" between creating and locking. | |
3788 | */ | |
3789 | pjsip_dialog *dlg; | |
3790 | ||
3791 | dlg = create_dialog_uas(endpoint, rdata, status, pjsip_dlg_create_uas); | |
3792 | if (dlg) { | |
3793 | pjsip_dlg_inc_lock(dlg); | |
3794 | } | |
3734 | 3795 | |
3735 | 3796 | return dlg; |
3797 | #endif | |
3736 | 3798 | } |
3737 | 3799 | |
3738 | 3800 | int ast_sip_create_rdata_with_contact(pjsip_rx_data *rdata, char *packet, const char *src_name, int src_port, |
4027 | 4089 | return pj_stristr(&method, message_method) ? PJ_TRUE : PJ_FALSE; |
4028 | 4090 | } |
4029 | 4091 | |
4030 | /*! Maximum number of challenges before assuming that we are in a loop */ | |
4031 | #define MAX_RX_CHALLENGES 10 | |
4032 | 4092 | #define TIMER_INACTIVE 0 |
4033 | 4093 | #define TIMEOUT_TIMER2 5 |
4034 | 4094 |
474 | 474 | |
475 | 475 | if (ast_add_extension2_nolock(context, 0, exten, priority, NULL, NULL, |
476 | 476 | app, data, free_ptr, BASE_REGISTRAR, NULL, 0)) { |
477 | ast_free(data); | |
478 | 477 | return -1; |
479 | 478 | } |
480 | 479 |
35 | 35 | #include "asterisk/strings.h" |
36 | 36 | |
37 | 37 | static const pj_str_t diversion_name = { "Diversion", 9 }; |
38 | static const pj_str_t history_info_name = { "History-Info", 12 }; | |
39 | static pj_str_t HISTINFO_SUPPORTED_NAME = { "histinfo", 8 }; | |
38 | 40 | |
39 | 41 | /*! |
40 | 42 | * \internal |
79 | 81 | static const struct reasons { |
80 | 82 | enum AST_REDIRECTING_REASON code; |
81 | 83 | const char *text; |
84 | const unsigned int cause; | |
82 | 85 | } reason_table[] = { |
83 | { AST_REDIRECTING_REASON_UNKNOWN, "unknown" }, | |
84 | { AST_REDIRECTING_REASON_USER_BUSY, "user-busy" }, | |
85 | { AST_REDIRECTING_REASON_NO_ANSWER, "no-answer" }, | |
86 | { AST_REDIRECTING_REASON_UNAVAILABLE, "unavailable" }, | |
87 | { AST_REDIRECTING_REASON_UNCONDITIONAL, "unconditional" }, | |
88 | { AST_REDIRECTING_REASON_TIME_OF_DAY, "time-of-day" }, | |
89 | { AST_REDIRECTING_REASON_DO_NOT_DISTURB, "do-not-disturb" }, | |
90 | { AST_REDIRECTING_REASON_DEFLECTION, "deflection" }, | |
91 | { AST_REDIRECTING_REASON_FOLLOW_ME, "follow-me" }, | |
92 | { AST_REDIRECTING_REASON_OUT_OF_ORDER, "out-of-service" }, | |
93 | { AST_REDIRECTING_REASON_AWAY, "away" }, | |
94 | { AST_REDIRECTING_REASON_CALL_FWD_DTE, "cf_dte" }, /* Non-standard */ | |
95 | { AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm" }, /* Non-standard */ | |
86 | { AST_REDIRECTING_REASON_UNKNOWN, "unknown", 404 }, | |
87 | { AST_REDIRECTING_REASON_USER_BUSY, "user-busy", 486 }, | |
88 | { AST_REDIRECTING_REASON_NO_ANSWER, "no-answer", 408 }, | |
89 | { AST_REDIRECTING_REASON_UNAVAILABLE, "unavailable", 503 }, | |
90 | { AST_REDIRECTING_REASON_UNCONDITIONAL, "unconditional", 302 }, | |
91 | { AST_REDIRECTING_REASON_TIME_OF_DAY, "time-of-day", 404 }, | |
92 | { AST_REDIRECTING_REASON_DO_NOT_DISTURB, "do-not-disturb", 404 }, | |
93 | { AST_REDIRECTING_REASON_DEFLECTION, "deflection", 480 }, | |
94 | { AST_REDIRECTING_REASON_FOLLOW_ME, "follow-me", 404 }, | |
95 | { AST_REDIRECTING_REASON_OUT_OF_ORDER, "out-of-service", 404 }, | |
96 | { AST_REDIRECTING_REASON_AWAY, "away", 404 }, | |
97 | { AST_REDIRECTING_REASON_CALL_FWD_DTE, "cf_dte", 404 }, /* Non-standard */ | |
98 | { AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm", 404 }, /* Non-standard */ | |
96 | 99 | }; |
100 | ||
101 | static enum AST_REDIRECTING_REASON cause_to_reason(const unsigned long cause) { | |
102 | switch(cause) { | |
103 | case 302: | |
104 | return AST_REDIRECTING_REASON_UNCONDITIONAL; | |
105 | case 486: | |
106 | return AST_REDIRECTING_REASON_USER_BUSY; | |
107 | case 408: | |
108 | return AST_REDIRECTING_REASON_NO_ANSWER; | |
109 | case 480: | |
110 | case 487: | |
111 | return AST_REDIRECTING_REASON_DEFLECTION; | |
112 | case 503: | |
113 | return AST_REDIRECTING_REASON_UNAVAILABLE; | |
114 | default: | |
115 | return AST_REDIRECTING_REASON_UNKNOWN; | |
116 | } | |
117 | } | |
118 | ||
119 | static int add_supported(pjsip_tx_data *tdata) | |
120 | { | |
121 | pjsip_supported_hdr *hdr; | |
122 | ||
123 | hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_SUPPORTED, NULL); | |
124 | if (!hdr) { | |
125 | /* insert a new Supported header */ | |
126 | hdr = pjsip_supported_hdr_create(tdata->pool); | |
127 | if (!hdr) { | |
128 | return -1; | |
129 | } | |
130 | ||
131 | pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr); | |
132 | } | |
133 | ||
134 | /* add on to the existing Supported header */ | |
135 | pj_strassign(&hdr->values[hdr->count++], &HISTINFO_SUPPORTED_NAME); | |
136 | ||
137 | return 0; | |
138 | } | |
97 | 139 | |
98 | 140 | static const char *reason_code_to_str(const struct ast_party_redirecting_reason *reason) |
99 | 141 | { |
115 | 157 | return "unknown"; |
116 | 158 | } |
117 | 159 | |
160 | static const unsigned int reason_code_to_cause(const struct ast_party_redirecting_reason *reason) | |
161 | { | |
162 | int idx; | |
163 | int code; | |
164 | ||
165 | code = reason->code; | |
166 | for (idx = 0; idx < ARRAY_LEN(reason_table); ++idx) { | |
167 | if (code == reason_table[idx].code) { | |
168 | return reason_table[idx].cause; | |
169 | } | |
170 | } | |
171 | ||
172 | return 404; | |
173 | } | |
174 | ||
118 | 175 | static pjsip_fromto_hdr *get_diversion_header(pjsip_rx_data *rdata) |
119 | 176 | { |
120 | 177 | static const pj_str_t from_name = { "From", 4 }; |
132 | 189 | /* parse as a fromto header */ |
133 | 190 | return pjsip_parse_hdr(rdata->tp_info.pool, &from_name, value.ptr, |
134 | 191 | pj_strlen(&value), &size); |
192 | } | |
193 | ||
194 | /* Asterisk keeps track of 2 things. The redirected from address and | |
195 | * the redirected to address. If first=0 method will get the most recent | |
196 | * redirection target for use as the redirected to address. If first=1 | |
197 | * then this method will get the original redirection target (index=1) | |
198 | * for use as the redirected from address. | |
199 | */ | |
200 | static pjsip_fromto_hdr *get_history_info_header(pjsip_rx_data *rdata, const unsigned int first) | |
201 | { | |
202 | static const pj_str_t from_name = { "From", 4 }; | |
203 | pjsip_fromto_hdr * result_hdr = NULL; | |
204 | ||
205 | pjsip_generic_string_hdr *hdr = NULL; | |
206 | ||
207 | hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &history_info_name, NULL); | |
208 | ||
209 | if (!hdr) { | |
210 | return NULL; | |
211 | } | |
212 | ||
213 | do { | |
214 | static const pj_str_t index_name = { "index", 5 }; | |
215 | pj_str_t value; | |
216 | int size; | |
217 | pjsip_fromto_hdr * fromto_hdr = NULL; | |
218 | pjsip_param * index = NULL; | |
219 | ||
220 | pj_strdup_with_null(rdata->tp_info.pool, &value, &hdr->hvalue); | |
221 | ||
222 | /* parse as a fromto header */ | |
223 | fromto_hdr = pjsip_parse_hdr(rdata->tp_info.pool, &from_name, value.ptr, | |
224 | pj_strlen(&value), &size); | |
225 | ||
226 | if (fromto_hdr == NULL) { | |
227 | continue; | |
228 | } | |
229 | ||
230 | index = pjsip_param_find(&fromto_hdr->other_param, &index_name); | |
231 | ||
232 | if (index) { | |
233 | if (!pj_strcmp2(&index->value, "1")) { | |
234 | if (!first) { | |
235 | continue; | |
236 | } else { | |
237 | return fromto_hdr; | |
238 | } | |
239 | } | |
240 | } | |
241 | ||
242 | result_hdr = fromto_hdr; | |
243 | ||
244 | } while ((hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &history_info_name, hdr->next))); | |
245 | ||
246 | return result_hdr; | |
135 | 247 | } |
136 | 248 | |
137 | 249 | static void set_redirecting_value(char **dst, const pj_str_t *src) |
196 | 308 | } |
197 | 309 | } |
198 | 310 | |
199 | static void set_redirecting_reason(pjsip_fromto_hdr *hdr, | |
311 | static void set_redirecting_reason_by_cause(pjsip_name_addr *name_addr, | |
200 | 312 | struct ast_party_redirecting_reason *data) |
201 | 313 | { |
314 | static const pj_str_t cause_name = { "cause", 5 }; | |
315 | pjsip_sip_uri *uri = pjsip_uri_get_uri(name_addr); | |
316 | pjsip_param *cause = pjsip_param_find(&uri->other_param, &cause_name); | |
317 | unsigned long cause_value; | |
318 | ||
319 | if (!cause) { | |
320 | return; | |
321 | } | |
322 | ||
323 | cause_value = pj_strtoul(&cause->value); | |
324 | ||
325 | data->code = cause_to_reason(cause_value); | |
326 | ast_free(data->str); | |
327 | data->str = ast_strdup(""); | |
328 | } | |
329 | ||
330 | static void set_redirecting_reason(pjsip_fromto_hdr *from_info, pjsip_name_addr *to_info, | |
331 | struct ast_party_redirecting_reason *data) | |
332 | { | |
202 | 333 | static const pj_str_t reason_name = { "reason", 6 }; |
203 | pjsip_param *reason = pjsip_param_find(&hdr->other_param, &reason_name); | |
334 | pjsip_param *reason = pjsip_param_find(&from_info->other_param, &reason_name); | |
204 | 335 | char *reason_str; |
205 | 336 | |
206 | 337 | if (!reason) { |
338 | if (to_info) { | |
339 | set_redirecting_reason_by_cause(to_info, data); | |
340 | } | |
207 | 341 | return; |
208 | 342 | } |
209 | 343 | |
245 | 379 | if (from_info) { |
246 | 380 | set_redirecting_id((pjsip_name_addr*)from_info->uri, |
247 | 381 | &data.from, &update.from); |
248 | set_redirecting_reason(from_info, &data.reason); | |
382 | set_redirecting_reason(from_info, to_info, &data.reason); | |
383 | ast_set_party_id_all(&update.priv_to); | |
249 | 384 | } else { |
250 | 385 | copy_redirecting_id(&data.from, &session->id, &update.from); |
251 | 386 | } |
252 | 387 | |
253 | set_redirecting_id(to_info, &data.to, &update.to); | |
388 | if (to_info) { | |
389 | set_redirecting_id(to_info, &data.to, &update.to); | |
390 | } | |
254 | 391 | |
255 | 392 | ast_set_party_id_all(&update.priv_orig); |
256 | 393 | ast_set_party_id_all(&update.priv_from); |
272 | 409 | if (hdr) { |
273 | 410 | set_redirecting(session, hdr, (pjsip_name_addr*) |
274 | 411 | PJSIP_MSG_TO_HDR(rdata->msg_info.msg)->uri); |
412 | } else { | |
413 | pjsip_fromto_hdr *history_info_to; | |
414 | pjsip_fromto_hdr *history_info_from; | |
415 | history_info_to = get_history_info_header(rdata, 0); | |
416 | ||
417 | if (history_info_to) { | |
418 | /* If History-Info is present, then it will also include the original | |
419 | redirected-from in addition to the redirected-to */ | |
420 | history_info_from = get_history_info_header(rdata, 1); | |
421 | set_redirecting(session, history_info_from, (pjsip_name_addr*)history_info_to->uri); | |
422 | } | |
275 | 423 | } |
276 | 424 | |
277 | 425 | return 0; |
284 | 432 | |
285 | 433 | pjsip_status_line status = rdata->msg_info.msg->line.status; |
286 | 434 | pjsip_fromto_hdr *div_hdr; |
435 | pjsip_fromto_hdr *history_info_to; | |
436 | pjsip_fromto_hdr *history_info_from; | |
287 | 437 | pjsip_contact_hdr *contact_hdr; |
288 | 438 | |
289 | 439 | if ((status.code != 302) && (status.code != 181)) { |
291 | 441 | } |
292 | 442 | |
293 | 443 | /* use the diversion header info if there is one. if not one then use the |
294 | session caller id info. if that doesn't exist use info from the To hdr*/ | |
295 | if (!(div_hdr = get_diversion_header(rdata)) && !session->id.number.valid) { | |
296 | div_hdr = PJSIP_MSG_TO_HDR(rdata->msg_info.msg); | |
297 | } | |
298 | ||
299 | contact_hdr = pjsip_msg_find_hdr_by_names(rdata->msg_info.msg, &contact_name, &contact_name_s, NULL); | |
300 | ||
301 | set_redirecting(session, div_hdr, contact_hdr ? (pjsip_name_addr*)contact_hdr->uri : | |
302 | (pjsip_name_addr*)PJSIP_MSG_FROM_HDR(rdata->msg_info.msg)->uri); | |
444 | the history-info, if that doesn't exist, use session caller id info. if | |
445 | that doesn't exist use info from the To hdr*/ | |
446 | if (!(div_hdr = get_diversion_header(rdata))) { | |
447 | history_info_to = get_history_info_header(rdata, 0); | |
448 | ||
449 | if (history_info_to) { | |
450 | /* If History-Info is present, then it will also include the original | |
451 | redirected-from in addition to the redirected-to */ | |
452 | history_info_from = get_history_info_header(rdata, 1); | |
453 | set_redirecting(session, history_info_from, (pjsip_name_addr*)history_info_to->uri); | |
454 | return; | |
455 | } | |
456 | if (!div_hdr && !session->id.number.valid) { | |
457 | div_hdr = PJSIP_MSG_TO_HDR(rdata->msg_info.msg); | |
458 | } | |
459 | } | |
460 | ||
461 | ||
462 | if (status.code == 302) { | |
463 | /* With 302, Contact indicates the final destination and possibly Diversion indicates the hop before */ | |
464 | contact_hdr = pjsip_msg_find_hdr_by_names(rdata->msg_info.msg, &contact_name, &contact_name_s, NULL); | |
465 | ||
466 | set_redirecting(session, div_hdr, contact_hdr ? (pjsip_name_addr*)contact_hdr->uri : | |
467 | (pjsip_name_addr*)PJSIP_MSG_FROM_HDR(rdata->msg_info.msg)->uri); | |
468 | } else { | |
469 | /* With 181, Diversion is non-standard, but if present indicates the new final destination, and To indicating the original */ | |
470 | set_redirecting(session, PJSIP_MSG_TO_HDR(rdata->msg_info.msg), | |
471 | div_hdr ? (pjsip_name_addr*)div_hdr->uri : NULL); | |
472 | } | |
303 | 473 | } |
304 | 474 | |
305 | 475 | /*! |
311 | 481 | */ |
312 | 482 | static void add_diversion_header(pjsip_tx_data *tdata, struct ast_party_redirecting *data) |
313 | 483 | { |
484 | static const pj_str_t reason_name = { "reason", 6 }; | |
485 | ||
314 | 486 | pjsip_fromto_hdr *hdr; |
315 | 487 | pjsip_name_addr *name_addr; |
316 | 488 | pjsip_sip_uri *uri; |
319 | 491 | const char *reason_str; |
320 | 492 | const char *quote_str; |
321 | 493 | char *reason_buf; |
322 | ||
323 | struct ast_party_id *id = &data->from; | |
324 | pjsip_uri *base = PJSIP_MSG_FROM_HDR(tdata->msg)->uri; | |
494 | pjsip_uri *base; | |
495 | ||
496 | struct ast_party_id *id = NULL; | |
497 | if (tdata->msg->type == PJSIP_REQUEST_MSG) { | |
498 | id = &data->from; | |
499 | } else { | |
500 | /* In responses indicate the new destination */ | |
501 | id = &data->to; | |
502 | } | |
503 | ||
504 | base = PJSIP_MSG_FROM_HDR(tdata->msg)->uri; | |
325 | 505 | |
326 | 506 | if (!id->number.valid || ast_strlen_zero(id->number.str)) { |
327 | 507 | return; |
338 | 518 | pj_strdup2(tdata->pool, &uri->user, id->number.str); |
339 | 519 | |
340 | 520 | param = PJ_POOL_ALLOC_T(tdata->pool, pjsip_param); |
341 | param->name = pj_str("reason"); | |
521 | param->name = reason_name; | |
342 | 522 | |
343 | 523 | reason_str = reason_code_to_str(&data->reason); |
344 | 524 | |
360 | 540 | pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr); |
361 | 541 | } |
362 | 542 | |
543 | /*! | |
544 | * \internal | |
545 | * \brief Adds history-info header information to an outbound SIP message | |
546 | * | |
547 | * \param tdata The outbound message | |
548 | * \param data The redirecting data used to fill parts of the history-info header | |
549 | */ | |
550 | static void add_history_info_header(pjsip_tx_data *tdata, struct ast_party_redirecting *data) | |
551 | { | |
552 | static const pj_str_t index_name = { "index", 5 }; | |
553 | static const pj_str_t cause_name = { "cause", 5 }; | |
554 | static const pj_str_t first_index = { "1", 1 }; | |
555 | static const pj_str_t last_index = { "1.1", 3 }; | |
556 | ||
557 | pjsip_fromto_hdr *hdr; | |
558 | pjsip_name_addr *name_addr; | |
559 | pjsip_sip_uri *uri; | |
560 | pjsip_param *param; | |
561 | pjsip_fromto_hdr *old_hdr; | |
562 | unsigned int cause; | |
563 | char *cause_buf; | |
564 | ||
565 | struct ast_party_id *to = &data->to; | |
566 | struct ast_party_id *from = &data->from; | |
567 | ||
568 | pjsip_uri *base = PJSIP_MSG_TO_HDR(tdata->msg)->uri; | |
569 | ||
570 | ||
571 | hdr = pjsip_from_hdr_create(tdata->pool); | |
572 | hdr->type = PJSIP_H_OTHER; | |
573 | hdr->sname = hdr->name = history_info_name; | |
574 | ||
575 | name_addr = pjsip_uri_clone(tdata->pool, base); | |
576 | uri = pjsip_uri_get_uri(name_addr->uri); | |
577 | ||
578 | /* if no redirecting information, then TO is the original destination */ | |
579 | if (from->number.valid && !ast_strlen_zero(from->number.str)) { | |
580 | pj_strdup2(tdata->pool, &name_addr->display, from->name.str); | |
581 | pj_strdup2(tdata->pool, &uri->user, from->number.str); | |
582 | } | |
583 | ||
584 | param = PJ_POOL_ALLOC_T(tdata->pool, pjsip_param); | |
585 | param->name = index_name; | |
586 | param->value = first_index; | |
587 | ||
588 | ||
589 | pj_list_insert_before(&hdr->other_param, param); | |
590 | hdr->uri = (pjsip_uri *) name_addr; | |
591 | ||
592 | while ((old_hdr = pjsip_msg_find_hdr_by_name(tdata->msg, &history_info_name, NULL)) != NULL) { | |
593 | pj_list_erase(old_hdr); | |
594 | } | |
595 | ||
596 | pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr); | |
597 | ||
598 | if (!to->number.valid || ast_strlen_zero(to->number.str)) { | |
599 | return; | |
600 | } | |
601 | ||
602 | hdr = pjsip_from_hdr_create(tdata->pool); | |
603 | hdr->type = PJSIP_H_OTHER; | |
604 | hdr->sname = hdr->name = history_info_name; | |
605 | ||
606 | name_addr = pjsip_uri_clone(tdata->pool, base); | |
607 | uri = pjsip_uri_get_uri(name_addr->uri); | |
608 | ||
609 | pj_strdup2(tdata->pool, &name_addr->display, to->name.str); | |
610 | pj_strdup2(tdata->pool, &uri->user, to->number.str); | |
611 | ||
612 | param = PJ_POOL_ALLOC_T(tdata->pool, pjsip_param); | |
613 | param->name = index_name; | |
614 | param->value = last_index; | |
615 | pj_list_insert_before(&hdr->other_param, param); | |
616 | ||
617 | param = PJ_POOL_ALLOC_T(tdata->pool, pjsip_param); | |
618 | param->name = cause_name; | |
619 | cause = reason_code_to_cause(&data->reason); | |
620 | cause_buf = pj_pool_alloc(tdata->pool, 4); | |
621 | snprintf(cause_buf, 4, "%ud", cause); | |
622 | param->value = pj_str(cause_buf); | |
623 | pj_list_insert_before(&uri->other_param, param); | |
624 | hdr->uri = (pjsip_uri *) name_addr; | |
625 | ||
626 | pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr); | |
627 | } | |
628 | ||
363 | 629 | static void get_redirecting_add_diversion(struct ast_sip_session *session, pjsip_tx_data *tdata) |
364 | 630 | { |
365 | 631 | struct ast_party_redirecting *data; |
632 | ||
633 | add_supported(tdata); | |
366 | 634 | |
367 | 635 | if (session->channel && session->endpoint->id.send_diversion && |
368 | 636 | (data = ast_channel_redirecting(session->channel))->count) { |
369 | 637 | add_diversion_header(tdata, data); |
638 | } | |
639 | if (session->channel && session->endpoint->id.send_history_info) { | |
640 | data = ast_channel_redirecting(session->channel); | |
641 | add_history_info_header(tdata, data); | |
370 | 642 | } |
371 | 643 | } |
372 | 644 | |
409 | 681 | .incoming_response = diversion_incoming_response, |
410 | 682 | .outgoing_request = diversion_outgoing_request, |
411 | 683 | .outgoing_response = diversion_outgoing_response, |
412 | .response_priority = AST_SIP_SESSION_BEFORE_REDIRECTING|AST_SIP_SESSION_BEFORE_MEDIA, | |
684 | .response_priority = AST_SIP_SESSION_BEFORE_MEDIA, | |
413 | 685 | }; |
414 | 686 | |
415 | 687 | static int load_module(void) |
342 | 342 | */ |
343 | 343 | pjsip_tx_data *last_tdata; |
344 | 344 | /*! \brief Timer entry for retrying on temporal responses */ |
345 | pj_timer_entry timer; | |
345 | struct ast_sip_sched_task *sched_task; | |
346 | 346 | /*! \brief Optional line parameter placed into Contact */ |
347 | 347 | char line[LINE_PARAMETER_SIZE]; |
348 | 348 | /*! \brief Current number of retries */ |
373 | 373 | char *transport_name; |
374 | 374 | /*! \brief The name of the registration sorcery object */ |
375 | 375 | char *registration_name; |
376 | /*! \brief Server URI */ | |
377 | char *server_uri; | |
378 | /*! \brief Client URI */ | |
379 | char *client_uri; | |
380 | /*! \brief Contact URI */ | |
381 | char *contact_uri; | |
376 | 382 | }; |
377 | 383 | |
378 | 384 | /*! \brief Outbound registration state information (persists for lifetime that registration should exist) */ |
511 | 517 | return NULL; |
512 | 518 | } |
513 | 519 | |
514 | ast_debug(3, "Determined relationship to outbound registration '%s' based on line '%s', using configured endpoint '%s'\n", | |
515 | ast_sorcery_object_get_id(state->registration), state->client_state->line, state->registration->endpoint); | |
520 | ast_debug(3, "%s: Line '%s', using configured endpoint '%s'\n", | |
521 | state->client_state->registration_name, state->client_state->line, state->registration->endpoint); | |
516 | 522 | |
517 | 523 | return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", state->registration->endpoint); |
518 | 524 | } |
524 | 530 | /*! \brief Helper function which cancels the timer on a client */ |
525 | 531 | static void cancel_registration(struct sip_outbound_registration_client_state *client_state) |
526 | 532 | { |
527 | if (pj_timer_heap_cancel_if_active(pjsip_endpt_get_timer_heap(ast_sip_get_pjsip_endpoint()), | |
528 | &client_state->timer, client_state->timer.id)) { | |
529 | /* The timer was successfully cancelled, drop the refcount of client_state */ | |
530 | ao2_ref(client_state, -1); | |
533 | if (client_state->sched_task) { | |
534 | ast_sip_sched_task_cancel(client_state->sched_task); | |
535 | ao2_cleanup(client_state->sched_task); | |
536 | client_state->sched_task = NULL; | |
531 | 537 | } |
532 | 538 | } |
533 | 539 | |
544 | 550 | callback_invoked = ast_threadstorage_get(®ister_callback_invoked, sizeof(int)); |
545 | 551 | if (!callback_invoked) { |
546 | 552 | pjsip_tx_data_dec_ref(tdata); |
553 | ast_log(LOG_ERROR, "%s: Failed to get threadstorage for registration to server '%s' from client '%s'\n", | |
554 | client_state->registration_name, client_state->server_uri, client_state->client_uri); | |
547 | 555 | return PJ_ENOMEM; |
548 | 556 | } |
549 | 557 | *callback_invoked = 0; |
571 | 579 | * drop the references we just added |
572 | 580 | */ |
573 | 581 | if ((status != PJ_SUCCESS) && !(*callback_invoked)) { |
582 | ast_log(LOG_ERROR, "%s: Failed send registration to server '%s' from client '%s'\n", | |
583 | client_state->registration_name, client_state->server_uri, client_state->client_uri); | |
574 | 584 | pjsip_tx_data_dec_ref(tdata); |
575 | 585 | ao2_ref(client_state, -1); |
576 | 586 | return status; |
592 | 602 | /*! \brief Callback function for registering */ |
593 | 603 | static int handle_client_registration(void *data) |
594 | 604 | { |
595 | RAII_VAR(struct sip_outbound_registration_client_state *, client_state, data, ao2_cleanup); | |
605 | struct sip_outbound_registration_client_state *client_state = data; | |
596 | 606 | pjsip_tx_data *tdata; |
597 | 607 | |
598 | if (client_state->status == SIP_REGISTRATION_STOPPED | |
599 | || pjsip_regc_register(client_state->client, PJ_FALSE, &tdata) != PJ_SUCCESS) { | |
608 | if (client_state->status == SIP_REGISTRATION_STOPPED) { | |
600 | 609 | return 0; |
601 | 610 | } |
602 | 611 | |
603 | if (DEBUG_ATLEAST(1)) { | |
604 | pjsip_regc_info info; | |
605 | ||
606 | pjsip_regc_get_info(client_state->client, &info); | |
607 | ast_log(LOG_DEBUG, "Outbound REGISTER attempt %u to '%.*s' with client '%.*s'\n", | |
608 | client_state->retries + 1, | |
609 | (int) info.server_uri.slen, info.server_uri.ptr, | |
610 | (int) info.client_uri.slen, info.client_uri.ptr); | |
611 | } | |
612 | if (pjsip_regc_register(client_state->client, PJ_FALSE, &tdata) != PJ_SUCCESS) { | |
613 | ast_log(LOG_ERROR, "%s: Failed to create registration to server '%s' from client '%s'\n", | |
614 | client_state->registration_name, client_state->server_uri, client_state->client_uri); | |
615 | return 0; | |
616 | } | |
617 | ||
618 | ast_debug(1, "%s: Outbound REGISTER attempt %u to '%s' with client '%s'\n", | |
619 | client_state->registration_name, | |
620 | client_state->retries + 1, | |
621 | client_state->server_uri, client_state->client_uri); | |
612 | 622 | |
613 | 623 | if (client_state->support_path) { |
614 | 624 | pjsip_supported_hdr *hdr; |
619 | 629 | hdr = pjsip_supported_hdr_create(tdata->pool); |
620 | 630 | if (!hdr) { |
621 | 631 | pjsip_tx_data_dec_ref(tdata); |
622 | return -1; | |
632 | ast_log(LOG_ERROR, "%s: Failed to add SUPPORTED header to registration to server '%s' from client '%s'\n", | |
633 | client_state->registration_name, client_state->server_uri, client_state->client_uri); | |
634 | return 0; | |
623 | 635 | } |
624 | 636 | |
625 | 637 | pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr); |
634 | 646 | return 0; |
635 | 647 | } |
636 | 648 | |
637 | /*! \brief Timer callback function, used just for registrations */ | |
638 | static void sip_outbound_registration_timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry) | |
639 | { | |
640 | struct sip_outbound_registration_client_state *client_state = entry->user_data; | |
641 | ||
642 | entry->id = 0; | |
643 | ||
644 | /* | |
645 | * Transfer client_state reference to serializer task so the | |
646 | * nominal path will not dec the client_state ref in this | |
647 | * pjproject callback thread. | |
648 | */ | |
649 | if (ast_sip_push_task(client_state->serializer, handle_client_registration, client_state)) { | |
650 | ast_log(LOG_WARNING, "Scheduled outbound registration could not be executed.\n"); | |
651 | ao2_ref(client_state, -1); | |
652 | } | |
653 | } | |
654 | ||
655 | 649 | /*! \brief Helper function which sets up the timer to re-register in a specific amount of time */ |
656 | static void schedule_registration(struct sip_outbound_registration_client_state *client_state, unsigned int seconds) | |
657 | { | |
658 | pj_time_val delay = { .sec = seconds, }; | |
659 | pjsip_regc_info info; | |
650 | static void schedule_registration(struct sip_outbound_registration_client_state *client_state, unsigned int ms) | |
651 | { | |
652 | ||
653 | ast_debug(1, "%s: Scheduling outbound registration to server '%s' from client '%s' in %d ms\n", | |
654 | client_state->registration_name, client_state->server_uri, client_state->client_uri, ms); | |
660 | 655 | |
661 | 656 | cancel_registration(client_state); |
662 | 657 | |
663 | pjsip_regc_get_info(client_state->client, &info); | |
664 | ast_debug(1, "Scheduling outbound registration to server '%.*s' from client '%.*s' in %d seconds\n", | |
665 | (int) info.server_uri.slen, info.server_uri.ptr, | |
666 | (int) info.client_uri.slen, info.client_uri.ptr, | |
667 | seconds); | |
668 | ||
669 | ao2_ref(client_state, +1); | |
670 | if (pjsip_endpt_schedule_timer(ast_sip_get_pjsip_endpoint(), &client_state->timer, &delay) != PJ_SUCCESS) { | |
671 | ast_log(LOG_WARNING, "Failed to schedule registration to server '%.*s' from client '%.*s'\n", | |
672 | (int) info.server_uri.slen, info.server_uri.ptr, | |
673 | (int) info.client_uri.slen, info.client_uri.ptr); | |
674 | ao2_ref(client_state, -1); | |
658 | client_state->sched_task = ast_sip_schedule_task(client_state->serializer, | |
659 | ms, handle_client_registration, ast_taskprocessor_name(client_state->serializer), | |
660 | client_state, | |
661 | AST_SIP_SCHED_TASK_ONESHOT | AST_SIP_SCHED_TASK_DATA_AO2); | |
662 | ||
663 | if (!client_state->sched_task) { | |
664 | ast_log(LOG_WARNING, "%s: Failed to schedule registration to server '%s' from client '%s'\n", | |
665 | client_state->registration_name, client_state->server_uri, client_state->client_uri); | |
675 | 666 | } |
676 | 667 | } |
677 | 668 | |
708 | 699 | { |
709 | 700 | struct sip_outbound_registration_client_state *client_state = data; |
710 | 701 | |
711 | cancel_registration(client_state); | |
712 | ||
713 | 702 | if (client_state->client) { |
714 | 703 | pjsip_regc_info info; |
715 | 704 | pjsip_tx_data *tdata; |
716 | 705 | |
706 | /* We need to get the live state to test "is_busy" */ | |
717 | 707 | pjsip_regc_get_info(client_state->client, &info); |
718 | 708 | |
719 | 709 | if (info.is_busy == PJ_TRUE) { |
720 | 710 | /* If a client transaction is in progress we defer until it is complete */ |
721 | 711 | ast_debug(1, |
722 | "Registration transaction is busy with server '%.*s' from client '%.*s'.\n", | |
712 | "%s: Registration transaction is busy with server '%.*s' from client '%.*s'.\n", | |
713 | client_state->registration_name, | |
723 | 714 | (int) info.server_uri.slen, info.server_uri.ptr, |
724 | 715 | (int) info.client_uri.slen, info.client_uri.ptr); |
725 | 716 | client_state->destroy = 1; |
732 | 723 | break; |
733 | 724 | case SIP_REGISTRATION_REGISTERED: |
734 | 725 | ast_debug(1, |
735 | "Trying to unregister with server '%.*s' from client '%.*s' before destruction.\n", | |
726 | "%s: Trying to unregister with server '%.*s' from client '%.*s' before destruction.\n", | |
727 | client_state->registration_name, | |
736 | 728 | (int) info.server_uri.slen, info.server_uri.ptr, |
737 | 729 | (int) info.client_uri.slen, info.client_uri.ptr); |
738 | ||
730 | cancel_registration(client_state); | |
739 | 731 | update_client_state_status(client_state, SIP_REGISTRATION_STOPPING); |
740 | 732 | client_state->destroy = 1; |
741 | 733 | if (pjsip_regc_unregister(client_state->client, &tdata) == PJ_SUCCESS |
745 | 737 | } |
746 | 738 | break; |
747 | 739 | case SIP_REGISTRATION_REJECTED_TEMPORARY: |
740 | case SIP_REGISTRATION_STOPPING: | |
741 | cancel_registration(client_state); | |
742 | break; | |
748 | 743 | case SIP_REGISTRATION_REJECTED_PERMANENT: |
749 | case SIP_REGISTRATION_STOPPING: | |
750 | 744 | case SIP_REGISTRATION_STOPPED: |
751 | 745 | break; |
752 | 746 | } |
757 | 751 | |
758 | 752 | update_client_state_status(client_state, SIP_REGISTRATION_STOPPED); |
759 | 753 | ast_sip_auth_vector_destroy(&client_state->outbound_auths); |
754 | ||
760 | 755 | ao2_ref(client_state, -1); |
761 | 756 | |
762 | 757 | return 0; |
814 | 809 | } |
815 | 810 | } |
816 | 811 | |
817 | static void schedule_retry(struct registration_response *response, unsigned int interval, | |
818 | const char *server_uri, const char *client_uri) | |
812 | static void schedule_retry(struct registration_response *response, unsigned int interval) | |
819 | 813 | { |
820 | 814 | update_client_state_status(response->client_state, SIP_REGISTRATION_REJECTED_TEMPORARY); |
821 | schedule_registration(response->client_state, interval); | |
815 | schedule_registration(response->client_state, interval * 1000); | |
822 | 816 | |
823 | 817 | if (response->rdata) { |
824 | ast_log(LOG_WARNING, "Temporal response '%d' received from '%s' on " | |
818 | ast_log(LOG_WARNING, "%s: Temporal response '%d' received from '%s' on " | |
825 | 819 | "registration attempt to '%s', retrying in '%u'\n", |
826 | response->code, server_uri, client_uri, interval); | |
820 | response->client_state->registration_name, | |
821 | response->code, response->client_state->server_uri, response->client_state->client_uri, interval); | |
827 | 822 | } else { |
828 | ast_log(LOG_WARNING, "No response received from '%s' on " | |
823 | ast_log(LOG_WARNING, "%s: No response received from '%s' on " | |
829 | 824 | "registration attempt to '%s', retrying in '%u'\n", |
830 | server_uri, client_uri, interval); | |
825 | response->client_state->registration_name, | |
826 | response->client_state->server_uri, response->client_state->client_uri, interval); | |
831 | 827 | } |
832 | 828 | } |
833 | 829 | |
840 | 836 | return 0; |
841 | 837 | } |
842 | 838 | |
843 | if (DEBUG_ATLEAST(1)) { | |
844 | pjsip_regc_info info; | |
845 | ||
846 | pjsip_regc_get_info(state->client_state->client, &info); | |
847 | ast_log(LOG_DEBUG, | |
848 | "Outbound registration transport to server '%.*s' from client '%.*s' shutdown\n", | |
849 | (int) info.server_uri.slen, info.server_uri.ptr, | |
850 | (int) info.client_uri.slen, info.client_uri.ptr); | |
851 | } | |
839 | ast_debug(1, | |
840 | "%s: Outbound registration transport to server '%s' from client '%s' shutdown\n", | |
841 | state->client_state->registration_name, state->client_state->server_uri, state->client_state->client_uri); | |
852 | 842 | |
853 | 843 | cancel_registration(state->client_state); |
854 | 844 | |
921 | 911 | static int handle_registration_response(void *data) |
922 | 912 | { |
923 | 913 | struct registration_response *response = data; |
924 | pjsip_regc_info info; | |
925 | char server_uri[PJSIP_MAX_URL_SIZE]; | |
926 | char client_uri[PJSIP_MAX_URL_SIZE]; | |
927 | 914 | |
928 | 915 | if (response->client_state->status == SIP_REGISTRATION_STOPPED) { |
929 | 916 | ao2_ref(response, -1); |
930 | 917 | return 0; |
931 | 918 | } |
932 | 919 | |
933 | pjsip_regc_get_info(response->client_state->client, &info); | |
934 | ast_copy_pj_str(server_uri, &info.server_uri, sizeof(server_uri)); | |
935 | ast_copy_pj_str(client_uri, &info.client_uri, sizeof(client_uri)); | |
936 | ||
937 | ast_debug(1, "Processing REGISTER response %d from server '%s' for client '%s'\n", | |
938 | response->code, server_uri, client_uri); | |
920 | ast_debug(1, "%s: Processing REGISTER response %d from server '%s' for client '%s' with expiration '%d'\n", | |
921 | response->client_state->registration_name, response->code, response->client_state->server_uri, | |
922 | response->client_state->client_uri, response->expiration); | |
939 | 923 | |
940 | 924 | if (response->code == 408 || response->code == 503) { |
941 | 925 | if ((ast_sip_failover_request(response->old_request))) { |
957 | 941 | if (!ast_sip_create_request_with_auth(&response->client_state->outbound_auths, |
958 | 942 | response->rdata, response->old_request, &tdata)) { |
959 | 943 | response->client_state->auth_attempted = 1; |
960 | ast_debug(1, "Sending authenticated REGISTER to server '%s' from client '%s'\n", | |
961 | server_uri, client_uri); | |
944 | ast_debug(1, "%s: Sending authenticated REGISTER to server '%s' from client '%s'\n", | |
945 | response->client_state->registration_name, response->client_state->server_uri, | |
946 | response->client_state->client_uri); | |
962 | 947 | pjsip_tx_data_add_ref(tdata); |
963 | 948 | res = registration_client_send(response->client_state, tdata); |
964 | 949 | |
972 | 957 | return 0; |
973 | 958 | } |
974 | 959 | } else { |
975 | ast_log(LOG_WARNING, "Failed to create authenticated REGISTER request to server '%s' from client '%s'\n", | |
976 | server_uri, client_uri); | |
960 | ast_log(LOG_WARNING, "%s: Failed to create authenticated REGISTER request to server '%s' from client '%s'\n", | |
961 | response->client_state->registration_name, response->client_state->server_uri, | |
962 | response->client_state->client_uri); | |
977 | 963 | } |
978 | 964 | /* Otherwise, fall through so the failure is processed appropriately */ |
979 | 965 | } |
986 | 972 | int next_registration_round; |
987 | 973 | |
988 | 974 | /* If the registration went fine simply reschedule registration for the future */ |
989 | ast_debug(1, "Outbound registration to '%s' with client '%s' successful\n", server_uri, client_uri); | |
975 | ast_debug(1, "%s: Outbound registration to '%s' with client '%s' successful\n", | |
976 | response->client_state->registration_name, response->client_state->server_uri, | |
977 | response->client_state->client_uri); | |
990 | 978 | update_client_state_status(response->client_state, SIP_REGISTRATION_REGISTERED); |
991 | 979 | response->client_state->retries = 0; |
992 | next_registration_round = response->expiration - REREGISTER_BUFFER_TIME; | |
980 | next_registration_round = (response->expiration - REREGISTER_BUFFER_TIME) * 1000; | |
993 | 981 | if (next_registration_round < 0) { |
994 | /* Re-register immediately. */ | |
995 | next_registration_round = 0; | |
982 | /* Re-register no sooner than the buffer time. */ | |
983 | next_registration_round = REREGISTER_BUFFER_TIME * 1000; | |
996 | 984 | } |
997 | 985 | schedule_registration(response->client_state, next_registration_round); |
998 | 986 | |
1000 | 988 | registration_transport_monitor_setup(response->rdata->tp_info.transport, |
1001 | 989 | response->client_state->registration_name); |
1002 | 990 | } else { |
1003 | ast_debug(1, "Outbound unregistration to '%s' with client '%s' successful\n", server_uri, client_uri); | |
991 | ast_debug(1, "%s: Outbound unregistration to '%s' with client '%s' successful\n", | |
992 | response->client_state->registration_name, response->client_state->server_uri, | |
993 | response->client_state->client_uri); | |
1004 | 994 | update_client_state_status(response->client_state, SIP_REGISTRATION_UNREGISTERED); |
1005 | 995 | ast_sip_transport_monitor_unregister(response->rdata->tp_info.transport, |
1006 | 996 | registration_transport_shutdown_cb, response->client_state->registration_name, |
1010 | 1000 | /* We need to deal with the pending destruction instead. */ |
1011 | 1001 | } else if (response->retry_after) { |
1012 | 1002 | /* If we have been instructed to retry after a period of time, schedule it as such */ |
1013 | schedule_retry(response, response->retry_after, server_uri, client_uri); | |
1003 | schedule_retry(response, response->retry_after); | |
1014 | 1004 | } else if (response->client_state->retry_interval |
1015 | 1005 | && sip_outbound_registration_is_temporal(response->code, response->client_state)) { |
1016 | 1006 | if (response->client_state->retries == response->client_state->max_retries) { |
1017 | 1007 | /* If we received enough temporal responses to exceed our maximum give up permanently */ |
1018 | 1008 | update_client_state_status(response->client_state, SIP_REGISTRATION_REJECTED_PERMANENT); |
1019 | ast_log(LOG_WARNING, "Maximum retries reached when attempting outbound registration to '%s' with client '%s', stopping registration attempt\n", | |
1020 | server_uri, client_uri); | |
1009 | ast_log(LOG_WARNING, "%s: Maximum retries reached when attempting outbound registration to '%s' with client '%s', stopping registration attempt\n", | |
1010 | response->client_state->registration_name, response->client_state->server_uri, | |
1011 | response->client_state->client_uri); | |
1021 | 1012 | } else { |
1022 | 1013 | /* On the other hand if we can still try some more do so */ |
1023 | 1014 | response->client_state->retries++; |
1024 | schedule_retry(response, response->client_state->retry_interval, server_uri, client_uri); | |
1015 | schedule_retry(response, response->client_state->retry_interval); | |
1025 | 1016 | } |
1026 | 1017 | } else { |
1027 | 1018 | if (response->code == 403 |
1030 | 1021 | /* A forbidden response retry interval is configured and there are retries remaining */ |
1031 | 1022 | update_client_state_status(response->client_state, SIP_REGISTRATION_REJECTED_TEMPORARY); |
1032 | 1023 | response->client_state->retries++; |
1033 | schedule_registration(response->client_state, response->client_state->forbidden_retry_interval); | |
1034 | ast_log(LOG_WARNING, "403 Forbidden fatal response received from '%s' on registration attempt to '%s', retrying in '%u' seconds\n", | |
1035 | server_uri, client_uri, response->client_state->forbidden_retry_interval); | |
1024 | schedule_registration(response->client_state, response->client_state->forbidden_retry_interval * 1000); | |
1025 | ast_log(LOG_WARNING, "%s: 403 Forbidden fatal response received from '%s' on registration attempt to '%s', retrying in '%u' seconds\n", | |
1026 | response->client_state->registration_name, response->client_state->server_uri, | |
1027 | response->client_state->client_uri, response->client_state->forbidden_retry_interval); | |
1036 | 1028 | } else if (response->client_state->fatal_retry_interval |
1037 | 1029 | && response->client_state->retries < response->client_state->max_retries) { |
1038 | 1030 | /* Some kind of fatal failure response received, so retry according to configured interval */ |
1039 | 1031 | update_client_state_status(response->client_state, SIP_REGISTRATION_REJECTED_TEMPORARY); |
1040 | 1032 | response->client_state->retries++; |
1041 | schedule_registration(response->client_state, response->client_state->fatal_retry_interval); | |
1042 | ast_log(LOG_WARNING, "'%d' fatal response received from '%s' on registration attempt to '%s', retrying in '%u' seconds\n", | |
1043 | response->code, server_uri, client_uri, response->client_state->fatal_retry_interval); | |
1033 | schedule_registration(response->client_state, response->client_state->fatal_retry_interval * 1000); | |
1034 | ast_log(LOG_WARNING, "%s: '%d' fatal response received from '%s' on registration attempt to '%s', retrying in '%u' seconds\n", | |
1035 | response->client_state->registration_name, response->code, response->client_state->server_uri, | |
1036 | response->client_state->client_uri, response->client_state->fatal_retry_interval); | |
1044 | 1037 | } else { |
1045 | 1038 | /* Finally if there's no hope of registering give up */ |
1046 | 1039 | update_client_state_status(response->client_state, SIP_REGISTRATION_REJECTED_PERMANENT); |
1047 | 1040 | if (response->rdata) { |
1048 | ast_log(LOG_WARNING, "Fatal response '%d' received from '%s' on registration attempt to '%s', stopping outbound registration\n", | |
1049 | response->code, server_uri, client_uri); | |
1041 | ast_log(LOG_WARNING, "%s: Fatal response '%d' received from '%s' on registration attempt to '%s', stopping outbound registration\n", | |
1042 | response->client_state->registration_name, response->code, response->client_state->server_uri, | |
1043 | response->client_state->client_uri); | |
1050 | 1044 | } else { |
1051 | ast_log(LOG_WARNING, "Fatal registration attempt to '%s', stopping outbound registration\n", client_uri); | |
1045 | ast_log(LOG_WARNING, "%s: Fatal registration attempt to '%s', stopping outbound registration\n", | |
1046 | response->client_state->registration_name, response->client_state->client_uri); | |
1052 | 1047 | } |
1053 | 1048 | } |
1054 | 1049 | } |
1055 | 1050 | |
1056 | ast_system_publish_registry("PJSIP", client_uri, server_uri, | |
1051 | ast_system_publish_registry("PJSIP", response->client_state->client_uri, response->client_state->server_uri, | |
1057 | 1052 | sip_outbound_registration_status_str(response->client_state->status), NULL); |
1058 | 1053 | |
1059 | 1054 | if (response->client_state->destroy) { |
1094 | 1089 | */ |
1095 | 1090 | response->client_state = client_state; |
1096 | 1091 | |
1097 | ast_debug(1, "Received REGISTER response %d(%.*s)\n", | |
1098 | param->code, (int) param->reason.slen, param->reason.ptr); | |
1092 | ast_debug(1, "%s: Received REGISTER response %d(%.*s) from '%s': Expires: %d\n", | |
1093 | client_state->registration_name, | |
1094 | param->code, (int) param->reason.slen, param->reason.ptr, | |
1095 | client_state->server_uri, param->expiration); | |
1099 | 1096 | |
1100 | 1097 | if (param->rdata) { |
1101 | 1098 | struct pjsip_retry_after_hdr *retry_after; |
1130 | 1127 | * pjproject callback thread. |
1131 | 1128 | */ |
1132 | 1129 | if (ast_sip_push_task(client_state->serializer, handle_registration_response, response)) { |
1133 | ast_log(LOG_WARNING, "Failed to pass incoming registration response to threadpool\n"); | |
1130 | ast_log(LOG_WARNING, "%s: Failed to pass incoming registration response to threadpool\n", | |
1131 | client_state->registration_name); | |
1134 | 1132 | ao2_cleanup(response); |
1135 | 1133 | } |
1136 | 1134 | } |
1140 | 1138 | { |
1141 | 1139 | struct sip_outbound_registration_state *state = obj; |
1142 | 1140 | |
1143 | ast_debug(3, "Destroying registration state for registration to server '%s' from client '%s'\n", | |
1141 | ast_debug(3, "%s: Destroying registration state for registration to server '%s' from client '%s'\n", | |
1142 | state->client_state->registration_name, | |
1144 | 1143 | state->registration ? state->registration->server_uri : "", |
1145 | 1144 | state->registration ? state->registration->client_uri : ""); |
1146 | 1145 | ao2_cleanup(state->registration); |
1151 | 1150 | ao2_ref(state->client_state, -1); |
1152 | 1151 | } else if (ast_sip_push_task(state->client_state->serializer, |
1153 | 1152 | handle_client_state_destruction, state->client_state)) { |
1154 | ast_log(LOG_WARNING, "Failed to pass outbound registration client destruction to threadpool\n"); | |
1153 | ast_log(LOG_WARNING, "%s: Failed to pass outbound registration client destruction to threadpool\n", | |
1154 | state->client_state->registration_name); | |
1155 | 1155 | ao2_ref(state->client_state, -1); |
1156 | 1156 | } |
1157 | 1157 | } |
1160 | 1160 | static void sip_outbound_registration_client_state_destroy(void *obj) |
1161 | 1161 | { |
1162 | 1162 | struct sip_outbound_registration_client_state *client_state = obj; |
1163 | ||
1164 | ast_debug(3, "%s: Destroying registration client state\n", | |
1165 | client_state->registration_name); | |
1163 | 1166 | |
1164 | 1167 | ast_statsd_log_string("PJSIP.registrations.count", AST_STATSD_GAUGE, "-1", 1.0); |
1165 | 1168 | ast_statsd_log_string_va("PJSIP.registrations.state.%s", AST_STATSD_GAUGE, "-1", 1.0, |
1168 | 1171 | ast_taskprocessor_unreference(client_state->serializer); |
1169 | 1172 | ast_free(client_state->transport_name); |
1170 | 1173 | ast_free(client_state->registration_name); |
1174 | ast_free(client_state->server_uri); | |
1175 | ast_free(client_state->client_uri); | |
1176 | ast_free(client_state->contact_uri); | |
1171 | 1177 | if (client_state->last_tdata) { |
1172 | 1178 | pjsip_tx_data_dec_ref(client_state->last_tdata); |
1173 | 1179 | } |
1191 | 1197 | } |
1192 | 1198 | |
1193 | 1199 | state->client_state->status = SIP_REGISTRATION_UNREGISTERED; |
1194 | pj_timer_entry_init(&state->client_state->timer, 0, state->client_state, | |
1195 | sip_outbound_registration_timer_cb); | |
1196 | 1200 | state->client_state->transport_name = ast_strdup(registration->transport); |
1197 | 1201 | state->client_state->registration_name = |
1198 | 1202 | ast_strdup(ast_sorcery_object_get_id(registration)); |
1427 | 1431 | } |
1428 | 1432 | |
1429 | 1433 | pj_cstr(&server_uri, registration->server_uri); |
1430 | ||
1434 | state->client_state->server_uri = ast_strdup(registration->server_uri); | |
1431 | 1435 | |
1432 | 1436 | if (sip_dialog_create_contact(pjsip_regc_get_pool(state->client_state->client), |
1433 | 1437 | &contact_uri, S_OR(registration->contact_user, "s"), &server_uri, &selector, |
1434 | 1438 | state->client_state->line)) { |
1435 | 1439 | return -1; |
1436 | 1440 | } |
1441 | ast_copy_pj_str2(&state->client_state->contact_uri, &contact_uri); | |
1437 | 1442 | |
1438 | 1443 | pj_cstr(&client_uri, registration->client_uri); |
1444 | state->client_state->client_uri = ast_strdup(registration->client_uri); | |
1445 | ||
1439 | 1446 | if (pjsip_regc_init(state->client_state->client, &server_uri, &client_uri, |
1440 | 1447 | &client_uri, 1, &contact_uri, registration->expiration) != PJ_SUCCESS) { |
1441 | 1448 | return -1; |
1442 | 1449 | } |
1443 | 1450 | |
1451 | ||
1444 | 1452 | return 0; |
1445 | 1453 | } |
1446 | 1454 | |
1450 | 1458 | struct sip_outbound_registration_state *state = data; |
1451 | 1459 | struct sip_outbound_registration *registration = ao2_bump(state->registration); |
1452 | 1460 | size_t i; |
1461 | ||
1462 | ast_debug(1, "%s: Performing register to server '%s' from client '%s' Expiration: %d\n", | |
1463 | state->client_state->registration_name, | |
1464 | state->registration->server_uri, state->registration->client_uri, registration->expiration); | |
1453 | 1465 | |
1454 | 1466 | /* Just in case the client state is being reused for this registration, free the auth information */ |
1455 | 1467 | ast_sip_auth_vector_destroy(&state->client_state->outbound_auths); |
1472 | 1484 | |
1473 | 1485 | pjsip_regc_update_expires(state->client_state->client, registration->expiration); |
1474 | 1486 | |
1475 | schedule_registration(state->client_state, (ast_random() % 10) + 1); | |
1487 | schedule_registration(state->client_state, ((ast_random() % 10) + 1) * 1000); | |
1476 | 1488 | |
1477 | 1489 | ao2_ref(registration, -1); |
1478 | 1490 | ao2_ref(state, -1); |
1601 | 1613 | struct sip_outbound_registration_state *state = obj; |
1602 | 1614 | struct pjsip_regc *client = state->client_state->client; |
1603 | 1615 | pjsip_tx_data *tdata; |
1604 | pjsip_regc_info info; | |
1605 | ||
1606 | pjsip_regc_get_info(client, &info); | |
1607 | ast_debug(1, "Unregistering contacts with server '%s' from client '%s'\n", | |
1616 | ||
1617 | ast_debug(1, "%s: Unregistering contacts with server '%s' from client '%s'\n", | |
1618 | state->client_state->registration_name, | |
1608 | 1619 | state->registration->server_uri, state->registration->client_uri); |
1609 | 1620 | |
1610 | 1621 | cancel_registration(state->client_state); |
1631 | 1642 | static int queue_register(struct sip_outbound_registration_state *state) |
1632 | 1643 | { |
1633 | 1644 | ao2_ref(state, +1); |
1645 | ||
1646 | ast_debug(1, "%s: Queueing register to server '%s' from client '%s'\n", | |
1647 | state->client_state->registration_name, | |
1648 | state->registration->server_uri, state->registration->client_uri); | |
1649 | ||
1634 | 1650 | if (ast_sip_push_task(state->client_state->serializer, sip_outbound_registration_perform, state)) { |
1635 | 1651 | ao2_ref(state, -1); |
1636 | 1652 | return -1; |
1884 | 1900 | ast_sip_sorcery_object_to_ami(ami->registration, &buf); |
1885 | 1901 | |
1886 | 1902 | if ((state = get_state(ast_sorcery_object_get_id(ami->registration)))) { |
1887 | pjsip_regc_info info; | |
1903 | int next_reg = 0; | |
1888 | 1904 | |
1889 | 1905 | if (state->client_state->status == SIP_REGISTRATION_REGISTERED) { |
1890 | 1906 | ++ami->registered; |
1895 | 1911 | ast_str_append(&buf, 0, "Status: %s\r\n", |
1896 | 1912 | sip_outbound_registration_status_str(state->client_state->status)); |
1897 | 1913 | |
1898 | pjsip_regc_get_info(state->client_state->client, &info); | |
1899 | ast_str_append(&buf, 0, "NextReg: %d\r\n", info.next_reg); | |
1914 | if (state->client_state->status == SIP_REGISTRATION_REGISTERED && state->client_state->sched_task) { | |
1915 | ast_sip_sched_task_get_times2(state->client_state->sched_task, NULL, NULL, NULL, NULL, | |
1916 | &next_reg, NULL); | |
1917 | } | |
1918 | ||
1919 | ast_str_append(&buf, 0, "NextReg: %d\r\n", next_reg / 1000); | |
1900 | 1920 | ao2_ref(state, -1); |
1901 | 1921 | } |
1902 | 1922 | |
2001 | 2021 | ast_assert(context->output_buffer != NULL); |
2002 | 2022 | |
2003 | 2023 | ast_str_append(&context->output_buffer, 0, |
2004 | " <Registration/ServerURI..............................> <Auth..........> <Status.......>\n"); | |
2024 | " <Registration/ServerURI..............................> <Auth..........> <Status.......>"); | |
2025 | ast_str_append(&context->output_buffer, 0, | |
2026 | " <Last Reg..> <Intvl> <Next Start.....secs>\n" | |
2027 | "=============================================="); | |
2005 | 2028 | |
2006 | 2029 | return 0; |
2007 | 2030 | } |
2031 | ||
2032 | #define TIME_FORMAT "%a %T" | |
2008 | 2033 | |
2009 | 2034 | static int cli_print_body(void *obj, void *arg, int flags) |
2010 | 2035 | { |
2012 | 2037 | struct ast_sip_cli_context *context = arg; |
2013 | 2038 | const char *id = ast_sorcery_object_get_id(registration); |
2014 | 2039 | struct sip_outbound_registration_state *state = get_state(id); |
2040 | int next_run_ms = 0; | |
2041 | struct timeval next = { 0, 0 }; | |
2042 | struct timeval last = { 0, 0 }; | |
2043 | struct ast_tm tm; | |
2044 | char next_start[32] = ""; | |
2045 | char last_start[32] = ""; | |
2046 | int interval; | |
2047 | ||
2015 | 2048 | #define REGISTRATION_URI_FIELD_LEN 53 |
2016 | 2049 | |
2017 | 2050 | ast_assert(context->output_buffer != NULL); |
2018 | 2051 | |
2019 | ast_str_append(&context->output_buffer, 0, " %-s/%-*.*s %-16s %-16s\n", | |
2052 | if (state->client_state->sched_task) { | |
2053 | ast_sip_sched_task_get_times2(state->client_state->sched_task, &last, NULL, NULL, &interval, | |
2054 | &next_run_ms, &next); | |
2055 | ||
2056 | ast_localtime(&last, &tm, NULL); | |
2057 | ast_strftime(last_start, sizeof(last_start), TIME_FORMAT, &tm); | |
2058 | ||
2059 | ast_localtime(&next, &tm, NULL); | |
2060 | ast_strftime(next_start, sizeof(next_start), TIME_FORMAT, &tm); | |
2061 | } | |
2062 | ||
2063 | ast_str_append(&context->output_buffer, 0, " %-s/%-*.*s %-16s %-16s", | |
2020 | 2064 | id, |
2021 | 2065 | (int) (REGISTRATION_URI_FIELD_LEN - strlen(id)), |
2022 | 2066 | (int) (REGISTRATION_URI_FIELD_LEN - strlen(id)), |
2024 | 2068 | AST_VECTOR_SIZE(®istration->outbound_auths) |
2025 | 2069 | ? AST_VECTOR_GET(®istration->outbound_auths, 0) |
2026 | 2070 | : "n/a", |
2027 | (state ? sip_outbound_registration_status_str(state->client_state->status) : "Unregistered")); | |
2071 | (state ? sip_outbound_registration_status_str(state->client_state->status) : "Unregistered") | |
2072 | ); | |
2073 | ||
2074 | ast_str_append(&context->output_buffer, 0, " %-12s %7d %-12s %8d\n", | |
2075 | last_start, interval / 1000, next_start, next_run_ms / 1000); | |
2076 | ||
2028 | 2077 | ao2_cleanup(state); |
2078 | ||
2029 | 2079 | |
2030 | 2080 | if (context->show_details |
2031 | 2081 | || (context->show_details_only_level_0 && context->indent_level == 0)) { |
1476 | 1476 | } |
1477 | 1477 | sub_tree->role = AST_SIP_NOTIFIER; |
1478 | 1478 | |
1479 | dlg = ast_sip_create_dialog_uas(endpoint, rdata, dlg_status); | |
1479 | dlg = ast_sip_create_dialog_uas_locked(endpoint, rdata, dlg_status); | |
1480 | 1480 | if (!dlg) { |
1481 | 1481 | if (*dlg_status != PJ_EEXISTS) { |
1482 | 1482 | ast_log(LOG_WARNING, "Unable to create dialog for SIP subscription\n"); |
1497 | 1497 | } |
1498 | 1498 | |
1499 | 1499 | pjsip_evsub_create_uas(dlg, &pubsub_cb, rdata, 0, &sub_tree->evsub); |
1500 | ||
1500 | 1501 | subscription_setup_dialog(sub_tree, dlg); |
1502 | ||
1503 | /* | |
1504 | * The evsub and subscription setup both add dialog refs, so the dialog ref that | |
1505 | * was added when the dialog was created (see ast_sip_create_dialog_uas_lock) can | |
1506 | * now be removed. The lock should no longer be needed so can be removed too. | |
1507 | */ | |
1508 | pjsip_dlg_dec_lock(dlg); | |
1501 | 1509 | |
1502 | 1510 | #ifdef HAVE_PJSIP_EVSUB_GRP_LOCK |
1503 | 1511 | pjsip_evsub_add_ref(sub_tree->evsub); |
51 | 51 | #include "asterisk/dsp.h" |
52 | 52 | #include "asterisk/linkedlists.h" /* for AST_LIST_NEXT */ |
53 | 53 | #include "asterisk/stream.h" |
54 | #include "asterisk/logger_category.h" | |
54 | 55 | #include "asterisk/format_cache.h" |
55 | 56 | |
56 | 57 | #include "asterisk/res_pjsip.h" |
80 | 81 | keepalive = ast_rtp_instance_get_keepalive(rtp); |
81 | 82 | |
82 | 83 | if (!ast_sockaddr_isnull(&session_media->direct_media_addr)) { |
83 | ast_debug(3, "Not sending RTP keepalive on RTP instance %p since direct media is in use\n", rtp); | |
84 | ast_debug_rtp(3, "(%p) RTP not sending keepalive since direct media is in use\n", rtp); | |
84 | 85 | return keepalive * 1000; |
85 | 86 | } |
86 | 87 | |
87 | 88 | interval = time(NULL) - ast_rtp_instance_get_last_tx(rtp); |
88 | 89 | send_keepalive = interval >= keepalive; |
89 | 90 | |
90 | ast_debug(3, "It has been %d seconds since RTP was last sent on instance %p. %sending keepalive\n", | |
91 | (int) interval, rtp, send_keepalive ? "S" : "Not s"); | |
91 | ast_debug_rtp(3, "(%p) RTP it has been %d seconds since RTP was last sent. %sending keepalive\n", | |
92 | rtp, (int) interval, send_keepalive ? "S" : "Not s"); | |
92 | 93 | |
93 | 94 | if (send_keepalive) { |
94 | 95 | ast_rtp_instance_sendcng(rtp, 0); |
136 | 137 | * - disconnect channel unless direct media is in use. |
137 | 138 | */ |
138 | 139 | if (!ast_sockaddr_isnull(&session_media->direct_media_addr)) { |
139 | ast_debug(3, "Not disconnecting channel '%s' for lack of %s RTP activity in %d seconds " | |
140 | "since direct media is in use\n", ast_channel_name(chan), | |
140 | ast_debug_rtp(3, "(%p) RTP not disconnecting channel '%s' for lack of %s RTP activity in %d seconds " | |
141 | "since direct media is in use\n", rtp, ast_channel_name(chan), | |
141 | 142 | ast_codec_media_type2str(session_media->type), elapsed); |
142 | 143 | ast_channel_unlock(chan); |
143 | 144 | ast_channel_unref(chan); |
231 | 232 | |
232 | 233 | if (session->endpoint->media.bind_rtp_to_media_address && !ast_strlen_zero(session->endpoint->media.address)) { |
233 | 234 | if (ast_sockaddr_parse(&temp_media_address, session->endpoint->media.address, 0)) { |
234 | ast_debug(1, "Endpoint %s: Binding RTP media to %s\n", | |
235 | ast_debug_rtp(1, "Endpoint %s: Binding RTP media to %s\n", | |
235 | 236 | ast_sorcery_object_get_id(session->endpoint), |
236 | 237 | session->endpoint->media.address); |
237 | 238 | media_address = &temp_media_address; |
238 | 239 | } else { |
239 | ast_debug(1, "Endpoint %s: RTP media address invalid: %s\n", | |
240 | ast_debug_rtp(1, "Endpoint %s: RTP media address invalid: %s\n", | |
240 | 241 | ast_sorcery_object_get_id(session->endpoint), |
241 | 242 | session->endpoint->media.address); |
242 | 243 | } |
254 | 255 | |
255 | 256 | pj_sockaddr_print(&trans_state->host, hoststr, sizeof(hoststr), 0); |
256 | 257 | if (ast_sockaddr_parse(&temp_media_address, hoststr, 0)) { |
257 | ast_debug(1, "Transport %s bound to %s: Using it for RTP media.\n", | |
258 | ast_debug_rtp(1, "Transport %s bound to %s: Using it for RTP media.\n", | |
258 | 259 | session->endpoint->transport, hoststr); |
259 | 260 | media_address = &temp_media_address; |
260 | 261 | } else { |
261 | ast_debug(1, "Transport %s bound to %s: Invalid for RTP media.\n", | |
262 | ast_debug_rtp(1, "Transport %s bound to %s: Invalid for RTP media.\n", | |
262 | 263 | session->endpoint->transport, hoststr); |
263 | 264 | } |
264 | 265 | ao2_ref(trans_state, -1); |
375 | 376 | } |
376 | 377 | if (!tel_event && (session->dtmf == AST_SIP_DTMF_AUTO)) { |
377 | 378 | ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_INBAND); |
379 | ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_DTMF, 0); | |
378 | 380 | } |
379 | 381 | |
380 | 382 | if (session->dtmf == AST_SIP_DTMF_AUTO_INFO) { |
381 | 383 | if (tel_event) { |
382 | 384 | ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_RFC2833); |
385 | ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_DTMF, 1); | |
383 | 386 | } else { |
384 | 387 | ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_NONE); |
388 | ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_DTMF, 0); | |
385 | 389 | } |
386 | 390 | } |
387 | 391 | |
713 | 717 | return; |
714 | 718 | } |
715 | 719 | |
720 | ast_debug_ice(2, "(%p) ICE process attributes\n", session_media->rtp); | |
721 | ||
716 | 722 | attr = pjmedia_sdp_media_find_attr2(remote_stream, "ice-ufrag", NULL); |
717 | 723 | if (!attr) { |
718 | 724 | attr = pjmedia_sdp_attr_find2(remote->attr_count, remote->attr, "ice-ufrag", NULL); |
721 | 727 | ast_copy_pj_str(attr_value, (pj_str_t*)&attr->value, sizeof(attr_value)); |
722 | 728 | ice->set_authentication(session_media->rtp, attr_value, NULL); |
723 | 729 | } else { |
730 | ast_debug_ice(2, "(%p) ICE no, or invalid ice-ufrag\n", session_media->rtp); | |
724 | 731 | return; |
725 | 732 | } |
726 | 733 | |
732 | 739 | ast_copy_pj_str(attr_value, (pj_str_t*)&attr->value, sizeof(attr_value)); |
733 | 740 | ice->set_authentication(session_media->rtp, NULL, attr_value); |
734 | 741 | } else { |
742 | ast_debug_ice(2, "(%p) ICE no, or invalid ice-pwd\n", session_media->rtp); | |
735 | 743 | return; |
736 | 744 | } |
737 | 745 |
74 | 74 | ast_sip_session_sdp_creation_cb on_sdp_creation, |
75 | 75 | ast_sip_session_response_cb on_response, |
76 | 76 | enum ast_sip_session_refresh_method method, int generate_new_sdp, |
77 | struct ast_sip_session_media_state *media_state, | |
77 | struct ast_sip_session_media_state *pending_media_state, | |
78 | struct ast_sip_session_media_state *active_media_state, | |
78 | 79 | int queued); |
79 | 80 | |
80 | 81 | /*! \brief NAT hook for modifying outgoing messages with SDP */ |
480 | 481 | |
481 | 482 | ast_free(session_media->mid); |
482 | 483 | ast_free(session_media->remote_mslabel); |
484 | ast_free(session_media->remote_label); | |
485 | ast_free(session_media->stream_name); | |
483 | 486 | } |
484 | 487 | |
485 | 488 | struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_session *session, |
486 | 489 | struct ast_sip_session_media_state *media_state, enum ast_media_type type, int position) |
487 | 490 | { |
488 | 491 | struct ast_sip_session_media *session_media = NULL; |
492 | SCOPE_ENTER(1, "%s Adding position %d\n", ast_sip_session_get_name(session), position); | |
489 | 493 | |
490 | 494 | /* It is possible for this media state to already contain a session for the stream. If this |
491 | 495 | * is the case we simply return it. |
492 | 496 | */ |
493 | 497 | if (position < AST_VECTOR_SIZE(&media_state->sessions)) { |
494 | return AST_VECTOR_GET(&media_state->sessions, position); | |
498 | session_media = AST_VECTOR_GET(&media_state->sessions, position); | |
499 | if (session_media) { | |
500 | SCOPE_EXIT_RTN_VALUE(session_media, "Using existing media_session\n"); | |
501 | } | |
495 | 502 | } |
496 | 503 | |
497 | 504 | /* Determine if we can reuse the session media from the active media state if present */ |
500 | 507 | /* A stream can never exist without an accompanying media session */ |
501 | 508 | if (session_media->type == type) { |
502 | 509 | ao2_ref(session_media, +1); |
510 | ast_trace(1, "Reusing existing media session\n"); | |
503 | 511 | /* |
504 | 512 | * If this session_media was previously removed, its bundle group was probably reset |
505 | 513 | * to -1 so if bundling is enabled on the endpoint, we need to reset it to 0, set |
511 | 519 | ast_free(session_media->mid); |
512 | 520 | if (ast_asprintf(&session_media->mid, "%s-%d", ast_codec_media_type2str(type), position) < 0) { |
513 | 521 | ao2_ref(session_media, -1); |
514 | return NULL; | |
522 | SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't alloc mid\n"); | |
515 | 523 | } |
516 | 524 | } |
517 | 525 | } else { |
526 | ast_trace(1, "Can't reuse existing media session because the types are different. %s <> %s\n", | |
527 | ast_codec_media_type2str(type), ast_codec_media_type2str(session_media->type)); | |
518 | 528 | session_media = NULL; |
519 | 529 | } |
520 | 530 | } |
525 | 535 | if (!session_media) { |
526 | 536 | return NULL; |
527 | 537 | } |
538 | ast_trace(1, "Creating new media session\n"); | |
528 | 539 | |
529 | 540 | session_media->encryption = session->endpoint->media.rtp.encryption; |
530 | 541 | session_media->remote_ice = session->endpoint->media.rtp.ice_support; |
540 | 551 | */ |
541 | 552 | if (ast_asprintf(&session_media->mid, "%s-%d", ast_codec_media_type2str(type), position) < 0) { |
542 | 553 | ao2_ref(session_media, -1); |
543 | return NULL; | |
554 | SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't alloc mid\n"); | |
544 | 555 | } |
545 | 556 | session_media->bundle_group = 0; |
546 | 557 | |
554 | 565 | } |
555 | 566 | } |
556 | 567 | |
568 | ast_free(session_media->stream_name); | |
569 | session_media->stream_name = ast_strdup(ast_stream_get_name(ast_stream_topology_get_stream(media_state->topology, position))); | |
570 | ||
557 | 571 | if (AST_VECTOR_REPLACE(&media_state->sessions, position, session_media)) { |
558 | 572 | ao2_ref(session_media, -1); |
559 | 573 | |
560 | return NULL; | |
574 | SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't replace media_session\n"); | |
561 | 575 | } |
562 | 576 | |
563 | 577 | /* If this stream will be active in some way and it is the first of this type then consider this the default media session to match */ |
564 | 578 | if (!media_state->default_session[type] && ast_stream_get_state(ast_stream_topology_get_stream(media_state->topology, position)) != AST_STREAM_STATE_REMOVED) { |
579 | ast_trace(1, "Setting media session as default for %s\n", ast_codec_media_type2str(session_media->type)); | |
580 | ||
565 | 581 | media_state->default_session[type] = session_media; |
566 | 582 | } |
567 | 583 | |
568 | return session_media; | |
584 | SCOPE_EXIT_RTN_VALUE(session_media, "Done\n"); | |
569 | 585 | } |
570 | 586 | |
571 | 587 | static int is_stream_limitation_reached(enum ast_media_type type, const struct ast_sip_endpoint *endpoint, int *type_streams) |
670 | 686 | |
671 | 687 | ast_free(session_media->remote_mslabel); |
672 | 688 | session_media->remote_mslabel = NULL; |
689 | ast_free(session_media->remote_label); | |
690 | session_media->remote_label = NULL; | |
673 | 691 | |
674 | 692 | for (index = 0; index < stream->attr_count; ++index) { |
675 | 693 | pjmedia_sdp_attr *attr = stream->attr[index]; |
678 | 696 | char *msid, *tmp = attr_value; |
679 | 697 | static const pj_str_t STR_msid = { "msid", 4 }; |
680 | 698 | static const pj_str_t STR_ssrc = { "ssrc", 4 }; |
681 | ||
682 | if (!pj_strcmp(&attr->name, &STR_msid)) { | |
699 | static const pj_str_t STR_label = { "label", 5 }; | |
700 | ||
701 | if (!pj_strcmp(&attr->name, &STR_label)) { | |
702 | ast_copy_pj_str(attr_value, &attr->value, sizeof(attr_value)); | |
703 | session_media->remote_label = ast_strdup(attr_value); | |
704 | } else if (!pj_strcmp(&attr->name, &STR_msid)) { | |
683 | 705 | ast_copy_pj_str(attr_value, &attr->value, sizeof(attr_value)); |
684 | 706 | msid = strsep(&tmp, " "); |
685 | 707 | session_media->remote_mslabel = ast_strdup(msid); |
739 | 761 | int i; |
740 | 762 | int handled = 0; |
741 | 763 | int type_streams[AST_MEDIA_TYPE_END] = {0}; |
764 | SCOPE_ENTER(3, "%s: Media count: %d\n", ast_sip_session_get_name(session), sdp->media_count); | |
742 | 765 | |
743 | 766 | if (session->inv_session && session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) { |
744 | ast_log(LOG_ERROR, "Failed to handle incoming SDP. Session has been already disconnected\n"); | |
745 | return -1; | |
767 | SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: Failed to handle incoming SDP. Session has been already disconnected\n", | |
768 | ast_sip_session_get_name(session)); | |
746 | 769 | } |
747 | 770 | |
748 | 771 | /* It is possible for SDP deferral to have already created a pending topology */ |
749 | 772 | if (!session->pending_media_state->topology) { |
750 | 773 | session->pending_media_state->topology = ast_stream_topology_alloc(); |
751 | 774 | if (!session->pending_media_state->topology) { |
752 | return -1; | |
775 | SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: Couldn't alloc pending topology\n", | |
776 | ast_sip_session_get_name(session)); | |
753 | 777 | } |
754 | 778 | } |
755 | 779 | |
763 | 787 | enum ast_media_type type; |
764 | 788 | struct ast_stream *stream = NULL; |
765 | 789 | pjmedia_sdp_media *remote_stream = sdp->media[i]; |
790 | SCOPE_ENTER(4, "%s: Processing stream %d\n", ast_sip_session_get_name(session), i); | |
766 | 791 | |
767 | 792 | /* We need a null-terminated version of the media string */ |
768 | 793 | ast_copy_pj_str(media, &sdp->media[i]->desc.media, sizeof(media)); |
771 | 796 | /* See if we have an already existing stream, which can occur from SDP deferral checking */ |
772 | 797 | if (i < ast_stream_topology_get_count(session->pending_media_state->topology)) { |
773 | 798 | stream = ast_stream_topology_get_stream(session->pending_media_state->topology, i); |
799 | ast_trace(-1, "%s: Using existing pending stream %s\n", ast_sip_session_get_name(session), | |
800 | ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP))); | |
774 | 801 | } |
775 | 802 | if (!stream) { |
776 | 803 | struct ast_stream *existing_stream = NULL; |
804 | char *stream_name = NULL, *stream_name_allocated = NULL; | |
805 | const char *stream_label = NULL; | |
777 | 806 | |
778 | 807 | if (session->active_media_state->topology && |
779 | 808 | (i < ast_stream_topology_get_count(session->active_media_state->topology))) { |
780 | 809 | existing_stream = ast_stream_topology_get_stream(session->active_media_state->topology, i); |
781 | } | |
782 | ||
783 | stream = ast_stream_alloc(existing_stream ? ast_stream_get_name(existing_stream) : ast_codec_media_type2str(type), type); | |
810 | ast_trace(-1, "%s: Found existing active stream %s\n", ast_sip_session_get_name(session), | |
811 | ast_str_tmp(128, ast_stream_to_str(existing_stream, &STR_TMP))); | |
812 | ||
813 | if (ast_stream_get_state(existing_stream) != AST_STREAM_STATE_REMOVED) { | |
814 | stream_name = (char *)ast_stream_get_name(existing_stream); | |
815 | stream_label = ast_stream_get_metadata(existing_stream, "SDP:LABEL"); | |
816 | } | |
817 | } | |
818 | ||
819 | if (ast_strlen_zero(stream_name)) { | |
820 | if (ast_asprintf(&stream_name_allocated, "%s-%d", ast_codec_media_type2str(type), i) < 0) { | |
821 | handled = 0; | |
822 | SCOPE_EXIT_LOG_EXPR(goto end, LOG_ERROR, "%s: Couldn't alloc stream name\n", | |
823 | ast_sip_session_get_name(session)); | |
824 | ||
825 | } | |
826 | stream_name = stream_name_allocated; | |
827 | ast_trace(-1, "%s: Using %s for new stream name\n", ast_sip_session_get_name(session), | |
828 | stream_name); | |
829 | } | |
830 | ||
831 | stream = ast_stream_alloc(stream_name, type); | |
832 | ast_free(stream_name_allocated); | |
784 | 833 | if (!stream) { |
785 | return -1; | |
786 | } | |
834 | handled = 0; | |
835 | SCOPE_EXIT_LOG_EXPR(goto end, LOG_ERROR, "%s: Couldn't alloc stream\n", | |
836 | ast_sip_session_get_name(session)); | |
837 | } | |
838 | ||
839 | if (!ast_strlen_zero(stream_label)) { | |
840 | ast_stream_set_metadata(stream, "SDP:LABEL", stream_label); | |
841 | ast_trace(-1, "%s: Using %s for new stream label\n", ast_sip_session_get_name(session), | |
842 | stream_label); | |
843 | ||
844 | } | |
845 | ||
787 | 846 | if (ast_stream_topology_set_stream(session->pending_media_state->topology, i, stream)) { |
788 | 847 | ast_stream_free(stream); |
789 | return -1; | |
790 | } | |
791 | if (existing_stream) { | |
792 | const char *stream_label = ast_stream_get_metadata(existing_stream, "SDP:LABEL"); | |
793 | ||
794 | if (!ast_strlen_zero(stream_label)) { | |
795 | ast_stream_set_metadata(stream, "SDP:LABEL", stream_label); | |
796 | } | |
848 | handled = 0; | |
849 | SCOPE_EXIT_LOG_EXPR(goto end, LOG_ERROR, "%s: Couldn't set stream in topology\n", | |
850 | ast_sip_session_get_name(session)); | |
797 | 851 | } |
798 | 852 | |
799 | 853 | /* For backwards compatibility with the core the default audio stream is always sendrecv */ |
814 | 868 | } else { |
815 | 869 | ast_stream_set_state(stream, AST_STREAM_STATE_SENDRECV); |
816 | 870 | } |
871 | ast_trace(-1, "%s: Using new stream %s\n", ast_sip_session_get_name(session), | |
872 | ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP))); | |
817 | 873 | } |
818 | 874 | |
819 | 875 | session_media = ast_sip_session_media_state_add(session, session->pending_media_state, ast_media_type_from_str(media), i); |
820 | 876 | if (!session_media) { |
821 | return -1; | |
877 | SCOPE_EXIT_LOG_EXPR(goto end, LOG_ERROR, "%s: Couldn't alloc session media\n", | |
878 | ast_sip_session_get_name(session)); | |
822 | 879 | } |
823 | 880 | |
824 | 881 | /* If this stream is already declined mark it as such, or mark it as such if we've reached the limit */ |
825 | 882 | if (!remote_stream->desc.port || is_stream_limitation_reached(type, session->endpoint, type_streams)) { |
826 | ast_debug(1, "Declining incoming SDP media stream '%s' at position '%d'\n", | |
827 | ast_codec_media_type2str(type), i); | |
828 | 883 | remove_stream_from_bundle(session_media, stream); |
829 | continue; | |
884 | SCOPE_EXIT_EXPR(continue, "%s: Declining incoming SDP media stream %s'\n", | |
885 | ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP))); | |
830 | 886 | } |
831 | 887 | |
832 | 888 | set_mid_and_bundle_group(session, session_media, sdp, remote_stream); |
834 | 890 | |
835 | 891 | if (session_media->handler) { |
836 | 892 | handler = session_media->handler; |
837 | ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n", | |
838 | ast_codec_media_type2str(session_media->type), | |
893 | ast_trace(-1, "%s: Negotiating incoming SDP media stream %s using %s SDP handler\n", | |
894 | ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)), | |
839 | 895 | session_media->handler->id); |
840 | 896 | res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, i, stream); |
841 | 897 | if (res < 0) { |
842 | 898 | /* Catastrophic failure. Abort! */ |
843 | return -1; | |
899 | SCOPE_EXIT_LOG_EXPR(goto end, LOG_ERROR, "%s: Couldn't negotiate stream %s\n", | |
900 | ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP))); | |
844 | 901 | } else if (res == 0) { |
845 | ast_debug(1, "Declining incoming SDP media stream '%s' at position '%d'\n", | |
846 | ast_codec_media_type2str(type), i); | |
847 | 902 | remove_stream_from_bundle(session_media, stream); |
848 | continue; | |
903 | SCOPE_EXIT_EXPR(continue, "%s: Declining incoming SDP media stream %s\n", | |
904 | ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP))); | |
849 | 905 | } else if (res > 0) { |
850 | ast_debug(1, "Media stream '%s' handled by %s\n", | |
851 | ast_codec_media_type2str(session_media->type), | |
852 | session_media->handler->id); | |
853 | /* Handled by this handler. Move to the next stream */ | |
854 | 906 | handled = 1; |
855 | 907 | ++type_streams[type]; |
856 | continue; | |
908 | /* Handled by this handler. Move to the next stream */ | |
909 | SCOPE_EXIT_EXPR(continue, "%s: Media stream %s handled by %s\n", | |
910 | ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)), | |
911 | session_media->handler->id); | |
857 | 912 | } |
858 | 913 | } |
859 | 914 | |
860 | 915 | handler_list = ao2_find(sdp_handlers, media, OBJ_KEY); |
861 | 916 | if (!handler_list) { |
862 | ast_debug(1, "No registered SDP handlers for media type '%s'\n", media); | |
863 | continue; | |
917 | SCOPE_EXIT_EXPR(continue, "%s: Media stream %s has no registered handlers\n", | |
918 | ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP))); | |
864 | 919 | } |
865 | 920 | AST_LIST_TRAVERSE(&handler_list->list, handler, next) { |
866 | 921 | if (handler == session_media->handler) { |
867 | 922 | continue; |
868 | 923 | } |
869 | ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n", | |
870 | ast_codec_media_type2str(session_media->type), | |
924 | ast_trace(-1, "%s: Negotiating incoming SDP media stream %s using %s SDP handler\n", | |
925 | ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)), | |
871 | 926 | handler->id); |
927 | ||
872 | 928 | res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, i, stream); |
873 | 929 | if (res < 0) { |
874 | 930 | /* Catastrophic failure. Abort! */ |
875 | return -1; | |
931 | handled = 0; | |
932 | SCOPE_EXIT_LOG_EXPR(goto end, LOG_ERROR, "%s: Couldn't negotiate stream %s\n", | |
933 | ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP))); | |
876 | 934 | } else if (res == 0) { |
877 | ast_debug(1, "Declining incoming SDP media stream '%s' at position '%d'\n", | |
878 | ast_codec_media_type2str(type), i); | |
879 | 935 | remove_stream_from_bundle(session_media, stream); |
936 | ast_trace(-1, "%s: Declining incoming SDP media stream %s\n", | |
937 | ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP))); | |
880 | 938 | continue; |
881 | 939 | } else if (res > 0) { |
882 | ast_debug(1, "Media stream '%s' handled by %s\n", | |
883 | ast_codec_media_type2str(session_media->type), | |
884 | handler->id); | |
885 | /* Handled by this handler. Move to the next stream */ | |
886 | 940 | session_media_set_handler(session_media, handler); |
887 | 941 | handled = 1; |
888 | 942 | ++type_streams[type]; |
943 | ast_trace(-1, "%s: Media stream %s handled by %s\n", | |
944 | ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)), | |
945 | session_media->handler->id); | |
889 | 946 | break; |
890 | 947 | } |
891 | 948 | } |
892 | } | |
893 | if (!handled) { | |
894 | return -1; | |
895 | } | |
896 | return 0; | |
949 | ||
950 | SCOPE_EXIT("%s: Done with stream %s\n", ast_sip_session_get_name(session), | |
951 | ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP))); | |
952 | } | |
953 | ||
954 | end: | |
955 | SCOPE_EXIT_RTN_VALUE(handled ? 0 : -1, "%s: Handled? %s\n", ast_sip_session_get_name(session), | |
956 | handled ? "yes" : "no"); | |
897 | 957 | } |
898 | 958 | |
899 | 959 | static int handle_negotiated_sdp_session_media(struct ast_sip_session_media *session_media, |
936 | 996 | |
937 | 997 | handler = session_media->handler; |
938 | 998 | if (handler) { |
939 | ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n", | |
940 | ast_codec_media_type2str(session_media->type), | |
999 | ast_debug(4, "%s: Applying negotiated SDP media stream '%s' using %s SDP handler\n", | |
1000 | ast_sip_session_get_name(session), ast_codec_media_type2str(session_media->type), | |
941 | 1001 | handler->id); |
942 | 1002 | res = handler->apply_negotiated_sdp_stream(session, session_media, local, remote, index, asterisk_stream); |
943 | 1003 | if (res >= 0) { |
944 | ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n", | |
945 | ast_codec_media_type2str(session_media->type), | |
1004 | ast_debug(4, "%s: Applied negotiated SDP media stream '%s' using %s SDP handler\n", | |
1005 | ast_sip_session_get_name(session), ast_codec_media_type2str(session_media->type), | |
946 | 1006 | handler->id); |
947 | 1007 | return 0; |
948 | 1008 | } |
951 | 1011 | |
952 | 1012 | handler_list = ao2_find(sdp_handlers, media, OBJ_KEY); |
953 | 1013 | if (!handler_list) { |
954 | ast_debug(1, "No registered SDP handlers for media type '%s'\n", media); | |
1014 | ast_debug(4, "%s: No registered SDP handlers for media type '%s'\n", ast_sip_session_get_name(session), media); | |
955 | 1015 | return -1; |
956 | 1016 | } |
957 | 1017 | AST_LIST_TRAVERSE(&handler_list->list, handler, next) { |
958 | 1018 | if (handler == session_media->handler) { |
959 | 1019 | continue; |
960 | 1020 | } |
961 | ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n", | |
962 | ast_codec_media_type2str(session_media->type), | |
1021 | ast_debug(4, "%s: Applying negotiated SDP media stream '%s' using %s SDP handler\n", | |
1022 | ast_sip_session_get_name(session), ast_codec_media_type2str(session_media->type), | |
963 | 1023 | handler->id); |
964 | 1024 | res = handler->apply_negotiated_sdp_stream(session, session_media, local, remote, index, asterisk_stream); |
965 | 1025 | if (res < 0) { |
967 | 1027 | return -1; |
968 | 1028 | } |
969 | 1029 | if (res > 0) { |
970 | ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n", | |
971 | ast_codec_media_type2str(session_media->type), | |
1030 | ast_debug(4, "%s: Applied negotiated SDP media stream '%s' using %s SDP handler\n", | |
1031 | ast_sip_session_get_name(session), ast_codec_media_type2str(session_media->type), | |
972 | 1032 | handler->id); |
973 | 1033 | /* Handled by this handler. Move to the next stream */ |
974 | 1034 | session_media_set_handler(session_media, handler); |
977 | 1037 | } |
978 | 1038 | |
979 | 1039 | if (session_media->handler && session_media->handler->stream_stop) { |
980 | ast_debug(1, "Stopping SDP media stream '%s' as it is not currently negotiated\n", | |
981 | ast_codec_media_type2str(session_media->type)); | |
1040 | ast_debug(4, "%s: Stopping SDP media stream '%s' as it is not currently negotiated\n", | |
1041 | ast_sip_session_get_name(session), ast_codec_media_type2str(session_media->type)); | |
982 | 1042 | session_media->handler->stream_stop(session_media); |
983 | 1043 | } |
984 | 1044 | |
1003 | 1063 | active_media_state_clone = |
1004 | 1064 | ast_sip_session_media_state_clone(session->active_media_state); |
1005 | 1065 | if (!active_media_state_clone) { |
1006 | ast_log(LOG_WARNING, "Unable to clone active media state for channel '%s'\n", | |
1007 | session->channel ? ast_channel_name(session->channel) : "unknown"); | |
1066 | ast_log(LOG_WARNING, "%s: Unable to clone active media state\n", | |
1067 | ast_sip_session_get_name(session)); | |
1008 | 1068 | return -1; |
1009 | 1069 | } |
1010 | 1070 | |
1011 | 1071 | ast_sip_session_media_state_free(session->pending_media_state); |
1012 | 1072 | session->pending_media_state = active_media_state_clone; |
1013 | 1073 | } else { |
1014 | ast_log(LOG_WARNING, "No pending or active media state for channel '%s'\n", | |
1015 | session->channel ? ast_channel_name(session->channel) : "unknown"); | |
1074 | ast_log(LOG_WARNING, "%s: No pending or active media state\n", | |
1075 | ast_sip_session_get_name(session)); | |
1016 | 1076 | return -1; |
1017 | 1077 | } |
1018 | 1078 | } |
1024 | 1084 | */ |
1025 | 1085 | if (ast_stream_topology_get_count(session->pending_media_state->topology) != local->media_count |
1026 | 1086 | || AST_VECTOR_SIZE(&session->pending_media_state->sessions) != local->media_count) { |
1027 | ast_log(LOG_WARNING, "Local SDP for channel '%s' contains %d media streams while we expected it to contain %u\n", | |
1028 | session->channel ? ast_channel_name(session->channel) : "unknown", | |
1087 | ast_log(LOG_WARNING, "%s: Local SDP contains %d media streams while we expected it to contain %u\n", | |
1088 | ast_sip_session_get_name(session), | |
1029 | 1089 | ast_stream_topology_get_count(session->pending_media_state->topology), local->media_count); |
1030 | 1090 | return -1; |
1031 | 1091 | } |
1269 | 1329 | /*! Whether to generate new SDP */ |
1270 | 1330 | int generate_new_sdp; |
1271 | 1331 | /*! Requested media state for the SDP */ |
1272 | struct ast_sip_session_media_state *media_state; | |
1332 | struct ast_sip_session_media_state *pending_media_state; | |
1333 | /*! Active media state at the time of the original request */ | |
1334 | struct ast_sip_session_media_state *active_media_state; | |
1335 | ||
1273 | 1336 | AST_LIST_ENTRY(ast_sip_session_delayed_request) next; |
1274 | 1337 | }; |
1275 | 1338 | |
1279 | 1342 | ast_sip_session_sdp_creation_cb on_sdp_creation, |
1280 | 1343 | ast_sip_session_response_cb on_response, |
1281 | 1344 | int generate_new_sdp, |
1282 | struct ast_sip_session_media_state *media_state) | |
1345 | struct ast_sip_session_media_state *pending_media_state, | |
1346 | struct ast_sip_session_media_state *active_media_state) | |
1283 | 1347 | { |
1284 | 1348 | struct ast_sip_session_delayed_request *delay = ast_calloc(1, sizeof(*delay)); |
1285 | 1349 | |
1291 | 1355 | delay->on_sdp_creation = on_sdp_creation; |
1292 | 1356 | delay->on_response = on_response; |
1293 | 1357 | delay->generate_new_sdp = generate_new_sdp; |
1294 | delay->media_state = media_state; | |
1358 | delay->pending_media_state = pending_media_state; | |
1359 | delay->active_media_state = active_media_state; | |
1295 | 1360 | return delay; |
1296 | 1361 | } |
1297 | 1362 | |
1298 | 1363 | static void delayed_request_free(struct ast_sip_session_delayed_request *delay) |
1299 | 1364 | { |
1300 | ast_sip_session_media_state_free(delay->media_state); | |
1365 | ast_sip_session_media_state_free(delay->pending_media_state); | |
1366 | ast_sip_session_media_state_free(delay->active_media_state); | |
1301 | 1367 | ast_free(delay); |
1302 | 1368 | } |
1303 | 1369 | |
1312 | 1378 | static int send_delayed_request(struct ast_sip_session *session, struct ast_sip_session_delayed_request *delay) |
1313 | 1379 | { |
1314 | 1380 | int res; |
1315 | ||
1316 | ast_debug(3, "Endpoint '%s(%s)' sending delayed %s request.\n", | |
1317 | ast_sorcery_object_get_id(session->endpoint), | |
1318 | session->channel ? ast_channel_name(session->channel) : "", | |
1381 | SCOPE_ENTER(3, "%s: sending delayed %s request\n", | |
1382 | ast_sip_session_get_name(session), | |
1319 | 1383 | delayed_method2str(delay->method)); |
1320 | 1384 | |
1321 | 1385 | switch (delay->method) { |
1322 | 1386 | case DELAYED_METHOD_INVITE: |
1323 | 1387 | res = sip_session_refresh(session, delay->on_request_creation, |
1324 | 1388 | delay->on_sdp_creation, delay->on_response, |
1325 | AST_SIP_SESSION_REFRESH_METHOD_INVITE, delay->generate_new_sdp, delay->media_state, 1); | |
1389 | AST_SIP_SESSION_REFRESH_METHOD_INVITE, delay->generate_new_sdp, delay->pending_media_state, | |
1390 | delay->active_media_state, 1); | |
1326 | 1391 | /* Ownership of media state transitions to ast_sip_session_refresh */ |
1327 | delay->media_state = NULL; | |
1328 | return res; | |
1392 | delay->pending_media_state = NULL; | |
1393 | delay->active_media_state = NULL; | |
1394 | SCOPE_EXIT_RTN_VALUE(res, "%s\n", ast_sip_session_get_name(session)); | |
1329 | 1395 | case DELAYED_METHOD_UPDATE: |
1330 | 1396 | res = sip_session_refresh(session, delay->on_request_creation, |
1331 | 1397 | delay->on_sdp_creation, delay->on_response, |
1332 | AST_SIP_SESSION_REFRESH_METHOD_UPDATE, delay->generate_new_sdp, delay->media_state, 1); | |
1398 | AST_SIP_SESSION_REFRESH_METHOD_UPDATE, delay->generate_new_sdp, delay->pending_media_state, | |
1399 | delay->active_media_state, 1); | |
1333 | 1400 | /* Ownership of media state transitions to ast_sip_session_refresh */ |
1334 | delay->media_state = NULL; | |
1335 | return res; | |
1401 | delay->pending_media_state = NULL; | |
1402 | delay->active_media_state = NULL; | |
1403 | SCOPE_EXIT_RTN_VALUE(res, "%s\n", ast_sip_session_get_name(session)); | |
1336 | 1404 | case DELAYED_METHOD_BYE: |
1337 | 1405 | ast_sip_session_terminate(session, 0); |
1338 | return 0; | |
1339 | } | |
1340 | ast_log(LOG_WARNING, "Don't know how to send delayed %s(%d) request.\n", | |
1406 | SCOPE_EXIT_RTN_VALUE(0, "%s: Terminating session on delayed BYE\n", ast_sip_session_get_name(session)); | |
1407 | } | |
1408 | ||
1409 | SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING, "%s: Don't know how to send delayed %s(%d) request.\n", | |
1410 | ast_sip_session_get_name(session), | |
1341 | 1411 | delayed_method2str(delay->method), delay->method); |
1342 | return -1; | |
1343 | 1412 | } |
1344 | 1413 | |
1345 | 1414 | /*! |
1358 | 1427 | struct ast_sip_session_delayed_request *delay; |
1359 | 1428 | int found = 0; |
1360 | 1429 | int res = 0; |
1430 | SCOPE_ENTER(3, "%s\n", ast_sip_session_get_name(session)); | |
1361 | 1431 | |
1362 | 1432 | AST_LIST_TRAVERSE_SAFE_BEGIN(&session->delayed_requests, delay, next) { |
1363 | 1433 | switch (delay->method) { |
1365 | 1435 | break; |
1366 | 1436 | case DELAYED_METHOD_UPDATE: |
1367 | 1437 | AST_LIST_REMOVE_CURRENT(next); |
1438 | ast_trace(-1, "%s: Sending delayed %s request\n", ast_sip_session_get_name(session), | |
1439 | delayed_method2str(delay->method)); | |
1368 | 1440 | res = send_delayed_request(session, delay); |
1369 | 1441 | delayed_request_free(delay); |
1370 | 1442 | if (!res) { |
1383 | 1455 | AST_LIST_TRAVERSE_SAFE_END; |
1384 | 1456 | |
1385 | 1457 | ao2_ref(session, -1); |
1386 | return res; | |
1458 | SCOPE_EXIT_RTN_VALUE(res, "%s\n", ast_sip_session_get_name(session)); | |
1387 | 1459 | } |
1388 | 1460 | |
1389 | 1461 | /*! |
1403 | 1475 | int found = 0; |
1404 | 1476 | int res = 0; |
1405 | 1477 | int timer_running; |
1478 | SCOPE_ENTER(3, "%s\n", ast_sip_session_get_name(session)); | |
1406 | 1479 | |
1407 | 1480 | /* re-INVITE collision timer running? */ |
1408 | 1481 | timer_running = pj_timer_entry_running(&session->rescheduled_reinvite); |
1421 | 1494 | } |
1422 | 1495 | if (found) { |
1423 | 1496 | AST_LIST_REMOVE_CURRENT(next); |
1497 | ast_trace(-1, "%s: Sending delayed %s request\n", ast_sip_session_get_name(session), | |
1498 | delayed_method2str(delay->method)); | |
1424 | 1499 | res = send_delayed_request(session, delay); |
1425 | 1500 | delayed_request_free(delay); |
1426 | 1501 | if (!res) { |
1431 | 1506 | AST_LIST_TRAVERSE_SAFE_END; |
1432 | 1507 | |
1433 | 1508 | ao2_ref(session, -1); |
1434 | return res; | |
1509 | SCOPE_EXIT_RTN_VALUE(res, "%s\n", ast_sip_session_get_name(session)); | |
1435 | 1510 | } |
1436 | 1511 | |
1437 | 1512 | /*! |
1448 | 1523 | { |
1449 | 1524 | struct ast_sip_session *session = vsession; |
1450 | 1525 | int res; |
1526 | SCOPE_ENTER(3, "%s\n", ast_sip_session_get_name(session)); | |
1451 | 1527 | |
1452 | 1528 | if (session->inv_session->invite_tsx) { |
1453 | 1529 | /* |
1460 | 1536 | res = invite_terminated(session); |
1461 | 1537 | } |
1462 | 1538 | |
1463 | return res; | |
1539 | SCOPE_EXIT_RTN_VALUE(res, "%s\n", ast_sip_session_get_name(session)); | |
1464 | 1540 | } |
1465 | 1541 | |
1466 | 1542 | /*! |
1502 | 1578 | ast_sip_session_response_cb on_response, |
1503 | 1579 | int generate_new_sdp, |
1504 | 1580 | enum delayed_method method, |
1505 | struct ast_sip_session_media_state *media_state) | |
1581 | struct ast_sip_session_media_state *pending_media_state, | |
1582 | struct ast_sip_session_media_state *active_media_state, | |
1583 | int queue_head) | |
1506 | 1584 | { |
1507 | 1585 | struct ast_sip_session_delayed_request *delay = delayed_request_alloc(method, |
1508 | on_request, on_sdp_creation, on_response, generate_new_sdp, media_state); | |
1586 | on_request, on_sdp_creation, on_response, generate_new_sdp, pending_media_state, | |
1587 | active_media_state); | |
1588 | SCOPE_ENTER(3, "%s\n", ast_sip_session_get_name(session)); | |
1509 | 1589 | |
1510 | 1590 | if (!delay) { |
1511 | ast_sip_session_media_state_free(media_state); | |
1512 | return -1; | |
1513 | } | |
1514 | ||
1515 | if (method == DELAYED_METHOD_BYE) { | |
1591 | ast_sip_session_media_state_free(pending_media_state); | |
1592 | ast_sip_session_media_state_free(active_media_state); | |
1593 | SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "Unable to allocate delay request\n"); | |
1594 | } | |
1595 | ||
1596 | if (method == DELAYED_METHOD_BYE || queue_head) { | |
1516 | 1597 | /* Send BYE as early as possible */ |
1517 | 1598 | AST_LIST_INSERT_HEAD(&session->delayed_requests, delay, next); |
1518 | 1599 | } else { |
1519 | 1600 | AST_LIST_INSERT_TAIL(&session->delayed_requests, delay, next); |
1520 | 1601 | } |
1521 | return 0; | |
1602 | SCOPE_EXIT_RTN_VALUE(0); | |
1522 | 1603 | } |
1523 | 1604 | |
1524 | 1605 | static pjmedia_sdp_session *generate_session_refresh_sdp(struct ast_sip_session *session) |
1525 | 1606 | { |
1526 | 1607 | pjsip_inv_session *inv_session = session->inv_session; |
1527 | 1608 | const pjmedia_sdp_session *previous_sdp = NULL; |
1609 | SCOPE_ENTER(3, "%s\n", ast_sip_session_get_name(session)); | |
1528 | 1610 | |
1529 | 1611 | if (inv_session->neg) { |
1530 | 1612 | if (pjmedia_sdp_neg_was_answer_remote(inv_session->neg)) { |
1533 | 1615 | pjmedia_sdp_neg_get_active_local(inv_session->neg, &previous_sdp); |
1534 | 1616 | } |
1535 | 1617 | } |
1536 | return create_local_sdp(inv_session, session, previous_sdp); | |
1618 | SCOPE_EXIT_RTN_VALUE(create_local_sdp(inv_session, session, previous_sdp)); | |
1537 | 1619 | } |
1538 | 1620 | |
1539 | 1621 | static void set_from_header(struct ast_sip_session *session) |
1597 | 1679 | ast_channel_lock(session->channel); |
1598 | 1680 | pjsip_from_domain = pbx_builtin_getvar_helper(session->channel, "SIPFROMDOMAIN"); |
1599 | 1681 | if (!ast_strlen_zero(pjsip_from_domain)) { |
1600 | ast_debug(3, "From header domain reset by channel variable SIPFROMDOMAIN (%s)\n", pjsip_from_domain); | |
1682 | ast_debug(3, "%s: From header domain reset by channel variable SIPFROMDOMAIN (%s)\n", | |
1683 | ast_sip_session_get_name(session), pjsip_from_domain); | |
1601 | 1684 | pj_strdup2(dlg_pool, &dlg_info_uri->host, pjsip_from_domain); |
1602 | 1685 | } |
1603 | 1686 | ast_channel_unlock(session->channel); |
1629 | 1712 | } |
1630 | 1713 | } |
1631 | 1714 | |
1715 | /* | |
1716 | * Helper macros for merging and validating media states | |
1717 | */ | |
1718 | #define STREAM_REMOVED(_stream) (ast_stream_get_state(_stream) == AST_STREAM_STATE_REMOVED) | |
1719 | #define STATE_REMOVED(_stream_state) (_stream_state == AST_STREAM_STATE_REMOVED) | |
1720 | #define STATE_NONE(_stream_state) (_stream_state == AST_STREAM_STATE_END) | |
1721 | #define GET_STREAM_SAFE(_topology, _i) (_i < ast_stream_topology_get_count(_topology) ? ast_stream_topology_get_stream(_topology, _i) : NULL) | |
1722 | #define GET_STREAM_STATE_SAFE(_stream) (_stream ? ast_stream_get_state(_stream) : AST_STREAM_STATE_END) | |
1723 | #define GET_STREAM_NAME_SAFE(_stream) (_stream ? ast_stream_get_name(_stream) : "") | |
1724 | ||
1725 | /*! | |
1726 | * \internal | |
1727 | * \brief Validate a media state | |
1728 | * | |
1729 | * \param state Media state | |
1730 | * | |
1731 | * \retval 1 The media state is valid | |
1732 | * \retval 0 The media state is NOT valid | |
1733 | * | |
1734 | */ | |
1735 | static int is_media_state_valid(const char *session_name, struct ast_sip_session_media_state *state) | |
1736 | { | |
1737 | int stream_count = ast_stream_topology_get_count(state->topology); | |
1738 | int session_count = AST_VECTOR_SIZE(&state->sessions); | |
1739 | int i; | |
1740 | int res = 0; | |
1741 | SCOPE_ENTER(3, "%s: Topology: %s\n", session_name, | |
1742 | ast_str_tmp(256, ast_stream_topology_to_str(state->topology, &STR_TMP))); | |
1743 | ||
1744 | if (session_count != stream_count) { | |
1745 | SCOPE_EXIT_RTN_VALUE(0, "%s: %d media sessions but %d streams\n", session_name, | |
1746 | session_count, stream_count); | |
1747 | } | |
1748 | ||
1749 | for (i = 0; i < stream_count; i++) { | |
1750 | struct ast_sip_session_media *media = NULL; | |
1751 | struct ast_stream *stream = ast_stream_topology_get_stream(state->topology, i); | |
1752 | const char *stream_name = NULL; | |
1753 | int j; | |
1754 | SCOPE_ENTER(4, "%s: Checking stream %s\n", session_name, ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP))); | |
1755 | ||
1756 | if (!stream) { | |
1757 | SCOPE_EXIT_EXPR(goto end, "%s: stream %d is null\n", session_name, i); | |
1758 | } | |
1759 | stream_name = ast_stream_get_name(stream); | |
1760 | ||
1761 | for (j = 0; j < stream_count; j++) { | |
1762 | struct ast_stream *possible_dup = ast_stream_topology_get_stream(state->topology, j); | |
1763 | if (j == i || !possible_dup) { | |
1764 | continue; | |
1765 | } | |
1766 | if (!STREAM_REMOVED(stream) && ast_strings_equal(stream_name, GET_STREAM_NAME_SAFE(possible_dup))) { | |
1767 | SCOPE_EXIT_EXPR(goto end, "%s: stream %i %s is duplicated to %d\n", session_name, | |
1768 | i, stream_name, j); | |
1769 | } | |
1770 | } | |
1771 | ||
1772 | media = AST_VECTOR_GET(&state->sessions, i); | |
1773 | if (!media) { | |
1774 | SCOPE_EXIT_EXPR(continue, "%s: media %d is null\n", session_name, i); | |
1775 | } | |
1776 | ||
1777 | for (j = 0; j < session_count; j++) { | |
1778 | struct ast_sip_session_media *possible_dup = AST_VECTOR_GET(&state->sessions, j); | |
1779 | if (j == i || !possible_dup) { | |
1780 | continue; | |
1781 | } | |
1782 | if (!ast_strlen_zero(media->label) && !ast_strlen_zero(possible_dup->label) | |
1783 | && ast_strings_equal(media->label, possible_dup->label)) { | |
1784 | SCOPE_EXIT_EXPR(goto end, "%s: media %d %s is duplicated to %d\n", session_name, | |
1785 | i, media->label, j); | |
1786 | } | |
1787 | } | |
1788 | ||
1789 | if (media->stream_num != i) { | |
1790 | SCOPE_EXIT_EXPR(goto end, "%s: media %d has stream_num %d\n", session_name, | |
1791 | i, media->stream_num); | |
1792 | } | |
1793 | ||
1794 | if (media->type != ast_stream_get_type(stream)) { | |
1795 | SCOPE_EXIT_EXPR(goto end, "%s: media %d has type %s but stream has type %s\n", stream_name, | |
1796 | i, ast_codec_media_type2str(media->type), ast_codec_media_type2str(ast_stream_get_type(stream))); | |
1797 | } | |
1798 | SCOPE_EXIT("%s: Done with stream %s\n", session_name, ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP))); | |
1799 | } | |
1800 | ||
1801 | res = 1; | |
1802 | end: | |
1803 | SCOPE_EXIT_RTN_VALUE(res, "%s: %s\n", session_name, res ? "Valid" : "NOT Valid"); | |
1804 | } | |
1805 | ||
1806 | /*! | |
1807 | * \internal | |
1808 | * \brief Merge media states for a delayed session refresh | |
1809 | * | |
1810 | * \param session_name For log messages | |
1811 | * \param delayed_pending_state The pending media state at the time the resuest was queued | |
1812 | * \param delayed_active_state The active media state at the time the resuest was queued | |
1813 | * \param current_active_state The current active media state | |
1814 | * \param run_validation Whether to run validation on the resulting media state or not | |
1815 | * | |
1816 | * \returns New merged topology or NULL if there's an error | |
1817 | * | |
1818 | */ | |
1819 | static struct ast_sip_session_media_state *resolve_refresh_media_states( | |
1820 | const char *session_name, | |
1821 | struct ast_sip_session_media_state *delayed_pending_state, | |
1822 | struct ast_sip_session_media_state *delayed_active_state, | |
1823 | struct ast_sip_session_media_state *current_active_state, | |
1824 | int run_post_validation) | |
1825 | { | |
1826 | RAII_VAR(struct ast_sip_session_media_state *, new_pending_state, NULL, ast_sip_session_media_state_free); | |
1827 | struct ast_sip_session_media_state *returned_media_state = NULL; | |
1828 | struct ast_stream_topology *delayed_pending = delayed_pending_state->topology; | |
1829 | struct ast_stream_topology *delayed_active = delayed_active_state->topology; | |
1830 | struct ast_stream_topology *current_active = current_active_state->topology; | |
1831 | struct ast_stream_topology *new_pending = NULL; | |
1832 | int i; | |
1833 | int max_stream_count; | |
1834 | int res; | |
1835 | SCOPE_ENTER(2, "%s: DP: %s DA: %s CA: %s\n", session_name, | |
1836 | ast_str_tmp(256, ast_stream_topology_to_str(delayed_pending, &STR_TMP)), | |
1837 | ast_str_tmp(256, ast_stream_topology_to_str(delayed_active, &STR_TMP)), | |
1838 | ast_str_tmp(256, ast_stream_topology_to_str(current_active, &STR_TMP)) | |
1839 | ); | |
1840 | ||
1841 | max_stream_count = MAX(ast_stream_topology_get_count(delayed_pending), | |
1842 | ast_stream_topology_get_count(delayed_active)); | |
1843 | max_stream_count = MAX(max_stream_count, ast_stream_topology_get_count(current_active)); | |
1844 | ||
1845 | /* | |
1846 | * The new_pending_state is always based on the currently negotiated state because | |
1847 | * the stream ordering in its topology must be preserved. | |
1848 | */ | |
1849 | new_pending_state = ast_sip_session_media_state_clone(current_active_state); | |
1850 | if (!new_pending_state) { | |
1851 | SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Couldn't clone current_active_state to new_pending_state\n", session_name); | |
1852 | } | |
1853 | new_pending = new_pending_state->topology; | |
1854 | ||
1855 | for (i = 0; i < max_stream_count; i++) { | |
1856 | struct ast_stream *dp_stream = GET_STREAM_SAFE(delayed_pending, i); | |
1857 | struct ast_stream *da_stream = GET_STREAM_SAFE(delayed_active, i); | |
1858 | struct ast_stream *ca_stream = GET_STREAM_SAFE(current_active, i); | |
1859 | struct ast_stream *np_stream = GET_STREAM_SAFE(new_pending, i); | |
1860 | struct ast_stream *found_da_stream = NULL; | |
1861 | struct ast_stream *found_np_stream = NULL; | |
1862 | enum ast_stream_state dp_state = GET_STREAM_STATE_SAFE(dp_stream); | |
1863 | enum ast_stream_state da_state = GET_STREAM_STATE_SAFE(da_stream); | |
1864 | enum ast_stream_state ca_state = GET_STREAM_STATE_SAFE(ca_stream); | |
1865 | enum ast_stream_state np_state = GET_STREAM_STATE_SAFE(np_stream); | |
1866 | enum ast_stream_state found_da_state = AST_STREAM_STATE_END; | |
1867 | enum ast_stream_state found_np_state = AST_STREAM_STATE_END; | |
1868 | const char *da_name = GET_STREAM_NAME_SAFE(da_stream); | |
1869 | const char *dp_name = GET_STREAM_NAME_SAFE(dp_stream); | |
1870 | const char *ca_name = GET_STREAM_NAME_SAFE(ca_stream); | |
1871 | const char *np_name = GET_STREAM_NAME_SAFE(np_stream); | |
1872 | const char *found_da_name __attribute__((unused)) = ""; | |
1873 | const char *found_np_name __attribute__((unused)) = ""; | |
1874 | int found_da_slot __attribute__((unused)) = -1; | |
1875 | int found_np_slot = -1; | |
1876 | int removed_np_slot = -1; | |
1877 | int j; | |
1878 | SCOPE_ENTER(3, "%s: slot: %d DP: %s DA: %s CA: %s\n", session_name, i, | |
1879 | ast_str_tmp(128, ast_stream_to_str(dp_stream, &STR_TMP)), | |
1880 | ast_str_tmp(128, ast_stream_to_str(da_stream, &STR_TMP)), | |
1881 | ast_str_tmp(128, ast_stream_to_str(ca_stream, &STR_TMP))); | |
1882 | ||
1883 | if (STATE_NONE(da_state) && STATE_NONE(dp_state) && STATE_NONE(ca_state)) { | |
1884 | SCOPE_EXIT_EXPR(break, "%s: All gone\n", session_name); | |
1885 | } | |
1886 | ||
1887 | /* | |
1888 | * Simple cases are handled first to avoid having to search the NP and DA | |
1889 | * topologies for streams with the same name but not in the same position. | |
1890 | */ | |
1891 | ||
1892 | if (STATE_NONE(dp_state) && !STATE_NONE(da_state)) { | |
1893 | /* | |
1894 | * The slot in the delayed pending topology can't be empty if the delayed | |
1895 | * active topology has a stream there. Streams can't just go away. They | |
1896 | * can be reused or marked "removed" but they can't go away. | |
1897 | */ | |
1898 | SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_WARNING, "%s: DP slot is empty but DA is not\n", session_name); | |
1899 | } | |
1900 | ||
1901 | if (STATE_NONE(dp_state)) { | |
1902 | /* | |
1903 | * The current active topology can certainly have streams that weren't | |
1904 | * in existence when the delayed request was queued. In this case, | |
1905 | * no action is needed since we already copied the current active topology | |
1906 | * to the new pending one. | |
1907 | */ | |
1908 | SCOPE_EXIT_EXPR(continue, "%s: No DP stream so use CA stream as is\n", session_name); | |
1909 | } | |
1910 | ||
1911 | if (ast_strings_equal(dp_name, da_name) && ast_strings_equal(da_name, ca_name)) { | |
1912 | /* | |
1913 | * The delayed pending stream in this slot matches by name, the streams | |
1914 | * in the same slot in the other two topologies. Easy case. | |
1915 | */ | |
1916 | ast_trace(-1, "%s: Same stream in all 3 states\n", session_name); | |
1917 | if (dp_state == da_state && da_state == ca_state) { | |
1918 | /* All the same state, no need to update. */ | |
1919 | SCOPE_EXIT_EXPR(continue, "%s: All in the same state so nothing to do\n", session_name); | |
1920 | } | |
1921 | if (da_state != ca_state) { | |
1922 | /* | |
1923 | * Something set the CA state between the time this request was queued | |
1924 | * and now. The CA state wins so we don't do anything. | |
1925 | */ | |
1926 | SCOPE_EXIT_EXPR(continue, "%s: Ignoring request to change state from %s to %s\n", | |
1927 | session_name, ast_stream_state2str(ca_state), ast_stream_state2str(dp_state)); | |
1928 | } | |
1929 | if (dp_state != da_state) { | |
1930 | /* DP needs to update the state */ | |
1931 | ast_stream_set_state(np_stream, dp_state); | |
1932 | SCOPE_EXIT_EXPR(continue, "%s: Changed NP stream state from %s to %s\n", | |
1933 | session_name, ast_stream_state2str(ca_state), ast_stream_state2str(dp_state)); | |
1934 | } | |
1935 | } | |
1936 | ||
1937 | /* | |
1938 | * We're done with the simple cases. For the rest, we need to identify if the | |
1939 | * DP stream we're trying to take action on is already in the other topologies | |
1940 | * possibly in a different slot. To do that, if the stream in the DA or CA slots | |
1941 | * doesn't match the current DP stream, we need to iterate over the topology | |
1942 | * looking for a stream with the same name. | |
1943 | */ | |
1944 | ||
1945 | /* | |
1946 | * Since we already copied all of the CA streams to the NP topology, we'll use it | |
1947 | * instead of CA because we'll be updating the NP as we go. | |
1948 | */ | |
1949 | if (!ast_strings_equal(dp_name, np_name)) { | |
1950 | /* | |
1951 | * The NP stream in this slot doesn't have the same name as the DP stream | |
1952 | * so we need to see if it's in another NP slot. We're not going to stop | |
1953 | * when we find a matching stream because we also want to find the first | |
1954 | * removed removed slot, if any, so we can re-use this slot. We'll break | |
1955 | * early if we find both before we reach the end. | |
1956 | */ | |
1957 | ast_trace(-1, "%s: Checking if DP is already in NP somewhere\n", session_name); | |
1958 | for (j = 0; j < ast_stream_topology_get_count(new_pending); j++) { | |
1959 | struct ast_stream *possible_existing = ast_stream_topology_get_stream(new_pending, j); | |
1960 | const char *possible_existing_name = GET_STREAM_NAME_SAFE(possible_existing); | |
1961 | ||
1962 | ast_trace(-1, "%s: Checking %s against %s\n", session_name, dp_name, possible_existing_name); | |
1963 | if (found_np_slot == -1 && ast_strings_equal(dp_name, possible_existing_name)) { | |
1964 | ast_trace(-1, "%s: Pending stream %s slot %d is in NP slot %d\n", session_name, | |
1965 | dp_name, i, j); | |
1966 | found_np_slot = j; | |
1967 | found_np_stream = possible_existing; | |
1968 | found_np_state = ast_stream_get_state(possible_existing); | |
1969 | found_np_name = ast_stream_get_name(possible_existing); | |
1970 | } | |
1971 | if (STREAM_REMOVED(possible_existing) && removed_np_slot == -1) { | |
1972 | removed_np_slot = j; | |
1973 | } | |
1974 | if (removed_np_slot >= 0 && found_np_slot >= 0) { | |
1975 | break; | |
1976 | } | |
1977 | } | |
1978 | } else { | |
1979 | /* Makes the subsequent code easier */ | |
1980 | found_np_slot = i; | |
1981 | found_np_stream = np_stream; | |
1982 | found_np_state = np_state; | |
1983 | found_np_name = np_name; | |
1984 | } | |
1985 | ||
1986 | if (!ast_strings_equal(dp_name, da_name)) { | |
1987 | /* | |
1988 | * The DA stream in this slot doesn't have the same name as the DP stream | |
1989 | * so we need to see if it's in another DA slot. In real life, the DA stream | |
1990 | * in this slot could have a different name but there shouldn't be a case | |
1991 | * where the DP stream is another slot in the DA topology. Just in case though. | |
1992 | * We don't care about removed slots in the DA topology. | |
1993 | */ | |
1994 | ast_trace(-1, "%s: Checking if DP is already in DA somewhere\n", session_name); | |
1995 | for (j = 0; j < ast_stream_topology_get_count(delayed_active); j++) { | |
1996 | struct ast_stream *possible_existing = ast_stream_topology_get_stream(delayed_active, j); | |
1997 | const char *possible_existing_name = GET_STREAM_NAME_SAFE(possible_existing); | |
1998 | ||
1999 | ast_trace(-1, "%s: Checking %s against %s\n", session_name, dp_name, possible_existing_name); | |
2000 | if (ast_strings_equal(dp_name, possible_existing_name)) { | |
2001 | ast_trace(-1, "%s: Pending stream %s slot %d is already in delayed active slot %d\n", | |
2002 | session_name, dp_name, i, j); | |
2003 | found_da_slot = j; | |
2004 | found_da_stream = possible_existing; | |
2005 | found_da_state = ast_stream_get_state(possible_existing); | |
2006 | found_da_name = ast_stream_get_name(possible_existing); | |
2007 | break; | |
2008 | } | |
2009 | } | |
2010 | } else { | |
2011 | /* Makes the subsequent code easier */ | |
2012 | found_da_slot = i; | |
2013 | found_da_stream = da_stream; | |
2014 | found_da_state = da_state; | |
2015 | found_da_name = da_name; | |
2016 | } | |
2017 | ||
2018 | ast_trace(-1, "%s: Found NP slot: %d Found removed NP slot: %d Found DA slot: %d\n", | |
2019 | session_name, found_np_slot, removed_np_slot, found_da_slot); | |
2020 | ||
2021 | /* | |
2022 | * Now we know whether the DP stream is new or changing state and we know if the DP | |
2023 | * stream exists in the other topologies and if so, where in those topologies it exists. | |
2024 | */ | |
2025 | ||
2026 | if (!found_da_stream) { | |
2027 | /* | |
2028 | * The DP stream isn't in the DA topology which would imply that the intention of the | |
2029 | * request was to add the stream, not change its state. It's possible though that | |
2030 | * the stream was added by another request between the time this request was queued | |
2031 | * and now so we need to check the CA topology as well. | |
2032 | */ | |
2033 | ast_trace(-1, "%s: There was no corresponding DA stream so the request was to add a stream\n", session_name); | |
2034 | ||
2035 | if (found_np_stream) { | |
2036 | /* | |
2037 | * We found it in the CA topology. Since the intention was to add it | |
2038 | * and it's already there, there's nothing to do. | |
2039 | */ | |
2040 | SCOPE_EXIT_EXPR(continue, "%s: New stream requested but it's already in CA\n", session_name); | |
2041 | } else { | |
2042 | /* OK, it's not in either which would again imply that the intention of the | |
2043 | * request was to add the stream. | |
2044 | */ | |
2045 | ast_trace(-1, "%s: There was no corresponding NP stream\n", session_name); | |
2046 | if (STATE_REMOVED(dp_state)) { | |
2047 | /* | |
2048 | * How can DP request to remove a stream that doesn't seem to exist anythere? | |
2049 | * It's not. It's possible that the stream was already removed and the slot | |
2050 | * reused in the CA topology, but it would still have to exist in the DA | |
2051 | * topology. Bail. | |
2052 | */ | |
2053 | SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, | |
2054 | "%s: Attempting to remove stream %d:%s but it doesn't exist anywhere.\n", session_name, i, dp_name); | |
2055 | } else { | |
2056 | /* | |
2057 | * We're now sure we want to add the the stream. Since we can re-use | |
2058 | * slots in the CA topology that have streams marked as "removed", we | |
2059 | * use the slot we saved in removed_np_slot if it exists. | |
2060 | */ | |
2061 | ast_trace(-1, "%s: Checking for open slot\n", session_name); | |
2062 | if (removed_np_slot >= 0) { | |
2063 | struct ast_sip_session_media *old_media = AST_VECTOR_GET(&new_pending_state->sessions, removed_np_slot); | |
2064 | res = ast_stream_topology_set_stream(new_pending, removed_np_slot, ast_stream_clone(dp_stream, NULL)); | |
2065 | if (res != 0) { | |
2066 | SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_WARNING, "%s: Couldn't set stream in new topology\n", session_name); | |
2067 | } | |
2068 | /* | |
2069 | * Since we're reusing the removed_np_slot slot for something else, we need | |
2070 | * to free and remove any session media already in it. | |
2071 | * ast_stream_topology_set_stream() took care of freeing the old stream. | |
2072 | */ | |
2073 | res = AST_VECTOR_REPLACE(&new_pending_state->sessions, removed_np_slot, NULL); | |
2074 | if (res != 0) { | |
2075 | SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_WARNING, "%s: Couldn't replace media session\n", session_name); | |
2076 | } | |
2077 | ||
2078 | ao2_cleanup(old_media); | |
2079 | SCOPE_EXIT_EXPR(continue, "%s: Replaced removed stream in slot %d\n", | |
2080 | session_name, removed_np_slot); | |
2081 | } else { | |
2082 | int new_slot = ast_stream_topology_append_stream(new_pending, ast_stream_clone(dp_stream, NULL)); | |
2083 | if (new_slot < 0) { | |
2084 | SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_WARNING, "%s: Couldn't append stream in new topology\n", session_name); | |
2085 | } | |
2086 | ||
2087 | res = AST_VECTOR_REPLACE(&new_pending_state->sessions, new_slot, NULL); | |
2088 | if (res != 0) { | |
2089 | SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_WARNING, "%s: Couldn't replace media session\n", session_name); | |
2090 | } | |
2091 | SCOPE_EXIT_EXPR(continue, "%s: Appended new stream to slot %d\n", | |
2092 | session_name, new_slot); | |
2093 | } | |
2094 | } | |
2095 | } | |
2096 | } else { | |
2097 | /* | |
2098 | * The DP stream exists in the DA topology so it's a change of some sort. | |
2099 | */ | |
2100 | ast_trace(-1, "%s: There was a corresponding DA stream so the request was to change/remove a stream\n", session_name); | |
2101 | if (dp_state == found_da_state) { | |
2102 | /* No change? Let's see if it's in CA */ | |
2103 | if (!found_np_stream) { | |
2104 | /* | |
2105 | * The DP and DA state are the same which would imply that the stream | |
2106 | * already exists but it's not in the CA topology. It's possible that | |
2107 | * between the time this request was queued and now the stream was removed | |
2108 | * from the CA topology and the slot used for something else. Nothing | |
2109 | * we can do here. | |
2110 | */ | |
2111 | SCOPE_EXIT_EXPR(continue, "%s: Stream doesn't exist in CA so nothing to do\n", session_name); | |
2112 | } else if (dp_state == found_np_state) { | |
2113 | SCOPE_EXIT_EXPR(continue, "%s: States are the same all around so nothing to do\n", session_name); | |
2114 | } else { | |
2115 | SCOPE_EXIT_EXPR(continue, "%s: Something changed the CA state so we're going to leave it as is\n", session_name); | |
2116 | } | |
2117 | } else { | |
2118 | /* We have a state change. */ | |
2119 | ast_trace(-1, "%s: Requesting state change to %s\n", session_name, ast_stream_state2str(dp_state)); | |
2120 | if (!found_np_stream) { | |
2121 | SCOPE_EXIT_EXPR(continue, "%s: Stream doesn't exist in CA so nothing to do\n", session_name); | |
2122 | } else if (da_state == found_np_state) { | |
2123 | ast_stream_set_state(found_np_stream, dp_state); | |
2124 | SCOPE_EXIT_EXPR(continue, "%s: Changed NP stream state from %s to %s\n", | |
2125 | session_name, ast_stream_state2str(found_np_state), ast_stream_state2str(dp_state)); | |
2126 | } else { | |
2127 | SCOPE_EXIT_EXPR(continue, "%s: Something changed the CA state so we're going to leave it as is\n", | |
2128 | session_name); | |
2129 | } | |
2130 | } | |
2131 | } | |
2132 | ||
2133 | SCOPE_EXIT("%s: Done with slot %d\n", session_name, i); | |
2134 | } | |
2135 | ||
2136 | ast_trace(-1, "%s: Resetting default media states\n", session_name); | |
2137 | for (i = 0; i < AST_MEDIA_TYPE_END; i++) { | |
2138 | int j; | |
2139 | new_pending_state->default_session[i] = NULL; | |
2140 | for (j = 0; j < AST_VECTOR_SIZE(&new_pending_state->sessions); j++) { | |
2141 | struct ast_sip_session_media *media = AST_VECTOR_GET(&new_pending_state->sessions, j); | |
2142 | struct ast_stream *stream = ast_stream_topology_get_stream(new_pending_state->topology, j); | |
2143 | ||
2144 | if (media && media->type == i && !STREAM_REMOVED(stream)) { | |
2145 | new_pending_state->default_session[i] = media; | |
2146 | break; | |
2147 | } | |
2148 | } | |
2149 | } | |
2150 | ||
2151 | if (run_post_validation) { | |
2152 | ast_trace(-1, "%s: Running post-validation\n", session_name); | |
2153 | if (!is_media_state_valid(session_name, new_pending_state)) { | |
2154 | SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "State not consistent\n"); | |
2155 | } | |
2156 | } | |
2157 | ||
2158 | /* | |
2159 | * We need to move the new pending state to another variable and set new_pending_state to NULL | |
2160 | * so RAII_VAR doesn't free it. | |
2161 | */ | |
2162 | returned_media_state = new_pending_state; | |
2163 | new_pending_state = NULL; | |
2164 | SCOPE_EXIT_RTN_VALUE(returned_media_state, "%s: NP: %s\n", session_name, | |
2165 | ast_str_tmp(256, ast_stream_topology_to_str(new_pending, &STR_TMP))); | |
2166 | } | |
2167 | ||
1632 | 2168 | static int sip_session_refresh(struct ast_sip_session *session, |
1633 | 2169 | ast_sip_session_request_creation_cb on_request_creation, |
1634 | 2170 | ast_sip_session_sdp_creation_cb on_sdp_creation, |
1635 | 2171 | ast_sip_session_response_cb on_response, |
1636 | 2172 | enum ast_sip_session_refresh_method method, int generate_new_sdp, |
1637 | struct ast_sip_session_media_state *media_state, | |
2173 | struct ast_sip_session_media_state *pending_media_state, | |
2174 | struct ast_sip_session_media_state *active_media_state, | |
1638 | 2175 | int queued) |
1639 | 2176 | { |
1640 | 2177 | pjsip_inv_session *inv_session = session->inv_session; |
1641 | 2178 | pjmedia_sdp_session *new_sdp = NULL; |
1642 | 2179 | pjsip_tx_data *tdata; |
1643 | ||
1644 | if (media_state && (!media_state->topology || !generate_new_sdp)) { | |
1645 | ast_sip_session_media_state_free(media_state); | |
1646 | return -1; | |
2180 | int res; | |
2181 | SCOPE_ENTER(3, "%s: New SDP? %s Queued? %s DP: %s DA: %s\n", ast_sip_session_get_name(session), | |
2182 | generate_new_sdp ? "yes" : "no", queued ? "yes" : "no", | |
2183 | pending_media_state ? ast_str_tmp(256, ast_stream_topology_to_str(pending_media_state->topology, &STR_TMP)) : "none", | |
2184 | active_media_state ? ast_str_tmp(256, ast_stream_topology_to_str(active_media_state->topology, &STR_TMP)) : "none"); | |
2185 | ||
2186 | if (pending_media_state && (!pending_media_state->topology || !generate_new_sdp)) { | |
2187 | ||
2188 | ast_sip_session_media_state_free(pending_media_state); | |
2189 | ast_sip_session_media_state_free(active_media_state); | |
2190 | SCOPE_EXIT_RTN_VALUE(-1, "%s: Not sending reinvite because %s%s\n", ast_sip_session_get_name(session), | |
2191 | pending_media_state->topology == NULL ? "pending topology is null " : "", | |
2192 | !generate_new_sdp ? "generate_new_sdp is false" : ""); | |
1647 | 2193 | } |
1648 | 2194 | |
1649 | 2195 | if (inv_session->state == PJSIP_INV_STATE_DISCONNECTED) { |
1650 | 2196 | /* Don't try to do anything with a hung-up call */ |
1651 | ast_debug(3, "Not sending reinvite to %s because of disconnected state...\n", | |
1652 | ast_sorcery_object_get_id(session->endpoint)); | |
1653 | ast_sip_session_media_state_free(media_state); | |
1654 | return 0; | |
2197 | ast_sip_session_media_state_free(pending_media_state); | |
2198 | ast_sip_session_media_state_free(active_media_state); | |
2199 | SCOPE_EXIT_RTN_VALUE(0, "%s: Not sending reinvite because of disconnected state\n", | |
2200 | ast_sip_session_get_name(session)); | |
1655 | 2201 | } |
1656 | 2202 | |
1657 | 2203 | /* If the dialog has not yet been established we have to defer until it has */ |
1658 | 2204 | if (inv_session->dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED) { |
1659 | ast_debug(3, "Delay sending request to %s because dialog has not been established...\n", | |
1660 | ast_sorcery_object_get_id(session->endpoint)); | |
1661 | return delay_request(session, on_request_creation, on_sdp_creation, on_response, | |
2205 | res = delay_request(session, on_request_creation, on_sdp_creation, on_response, | |
1662 | 2206 | generate_new_sdp, |
1663 | 2207 | method == AST_SIP_SESSION_REFRESH_METHOD_INVITE |
1664 | 2208 | ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE, |
1665 | media_state); | |
2209 | pending_media_state, active_media_state ? active_media_state : ast_sip_session_media_state_clone(session->active_media_state), queued); | |
2210 | SCOPE_EXIT_RTN_VALUE(res, "%s: Delay sending reinvite because dialog has not been established\n", | |
2211 | ast_sip_session_get_name(session)); | |
1666 | 2212 | } |
1667 | 2213 | |
1668 | 2214 | if (method == AST_SIP_SESSION_REFRESH_METHOD_INVITE) { |
1669 | 2215 | if (inv_session->invite_tsx) { |
1670 | 2216 | /* We can't send a reinvite yet, so delay it */ |
1671 | ast_debug(3, "Delay sending reinvite to %s because of outstanding transaction...\n", | |
1672 | ast_sorcery_object_get_id(session->endpoint)); | |
1673 | return delay_request(session, on_request_creation, on_sdp_creation, | |
1674 | on_response, generate_new_sdp, DELAYED_METHOD_INVITE, media_state); | |
2217 | res = delay_request(session, on_request_creation, on_sdp_creation, | |
2218 | on_response, generate_new_sdp, DELAYED_METHOD_INVITE, pending_media_state, | |
2219 | active_media_state ? active_media_state : ast_sip_session_media_state_clone(session->active_media_state), queued); | |
2220 | SCOPE_EXIT_RTN_VALUE(res, "%s: Delay sending reinvite because of outstanding transaction\n", | |
2221 | ast_sip_session_get_name(session)); | |
1675 | 2222 | } else if (inv_session->state != PJSIP_INV_STATE_CONFIRMED) { |
1676 | 2223 | /* Initial INVITE transaction failed to progress us to a confirmed state |
1677 | 2224 | * which means re-invites are not possible |
1678 | 2225 | */ |
1679 | ast_debug(3, "Not sending reinvite to %s because not in confirmed state...\n", | |
1680 | ast_sorcery_object_get_id(session->endpoint)); | |
1681 | ast_sip_session_media_state_free(media_state); | |
1682 | return 0; | |
2226 | ast_sip_session_media_state_free(pending_media_state); | |
2227 | ast_sip_session_media_state_free(active_media_state); | |
2228 | SCOPE_EXIT_RTN_VALUE(0, "%s: Not sending reinvite because not in confirmed state\n", | |
2229 | ast_sip_session_get_name(session)); | |
1683 | 2230 | } |
1684 | 2231 | } |
1685 | 2232 | |
1688 | 2235 | if (inv_session->neg |
1689 | 2236 | && pjmedia_sdp_neg_get_state(inv_session->neg) |
1690 | 2237 | != PJMEDIA_SDP_NEG_STATE_DONE) { |
1691 | ast_debug(3, "Delay session refresh with new SDP to %s because SDP negotiation is not yet done...\n", | |
1692 | ast_sorcery_object_get_id(session->endpoint)); | |
1693 | return delay_request(session, on_request_creation, on_sdp_creation, | |
2238 | res = delay_request(session, on_request_creation, on_sdp_creation, | |
1694 | 2239 | on_response, generate_new_sdp, |
1695 | 2240 | method == AST_SIP_SESSION_REFRESH_METHOD_INVITE |
1696 | ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE, media_state); | |
2241 | ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE, pending_media_state, | |
2242 | active_media_state ? active_media_state : ast_sip_session_media_state_clone(session->active_media_state), queued); | |
2243 | SCOPE_EXIT_RTN_VALUE(res, "%s: Delay session refresh with new SDP because SDP negotiation is not yet done\n", | |
2244 | ast_sip_session_get_name(session)); | |
1697 | 2245 | } |
1698 | 2246 | |
1699 | 2247 | /* If an explicitly requested media state has been provided use it instead of any pending one */ |
1700 | if (media_state) { | |
2248 | if (pending_media_state) { | |
1701 | 2249 | int index; |
1702 | 2250 | int type_streams[AST_MEDIA_TYPE_END] = {0}; |
1703 | struct ast_stream *stream; | |
2251 | int topology_change_request = 0; | |
2252 | ||
2253 | ast_trace(-1, "%s: Pending media state exists\n", ast_sip_session_get_name(session)); | |
1704 | 2254 | |
1705 | 2255 | /* Media state conveys a desired media state, so if there are outstanding |
1706 | 2256 | * delayed requests we need to ensure we go into the queue and not jump |
1708 | 2258 | * order. |
1709 | 2259 | */ |
1710 | 2260 | if (!queued && !AST_LIST_EMPTY(&session->delayed_requests)) { |
1711 | ast_debug(3, "Delay sending reinvite to %s because of outstanding requests...\n", | |
1712 | ast_sorcery_object_get_id(session->endpoint)); | |
1713 | return delay_request(session, on_request_creation, on_sdp_creation, | |
2261 | res = delay_request(session, on_request_creation, on_sdp_creation, | |
1714 | 2262 | on_response, generate_new_sdp, |
1715 | 2263 | method == AST_SIP_SESSION_REFRESH_METHOD_INVITE |
1716 | ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE, media_state); | |
2264 | ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE, pending_media_state, | |
2265 | active_media_state ? active_media_state : ast_sip_session_media_state_clone(session->active_media_state), queued); | |
2266 | SCOPE_EXIT_RTN_VALUE(res, "%s: Delay sending reinvite because of outstanding requests\n", | |
2267 | ast_sip_session_get_name(session)); | |
2268 | } | |
2269 | ||
2270 | if (active_media_state) { | |
2271 | struct ast_sip_session_media_state *new_pending_state; | |
2272 | /* | |
2273 | * We need to check if the passed in active and pending states are equal | |
2274 | * before we run the media states resolver. We'll use the flag later | |
2275 | * to signal whether this was topology change or some other change such | |
2276 | * as a connected line change. | |
2277 | */ | |
2278 | topology_change_request = !ast_stream_topology_equal(active_media_state->topology, pending_media_state->topology); | |
2279 | ||
2280 | ast_trace(-1, "%s: Active media state exists and is%s equal to pending\n", ast_sip_session_get_name(session), | |
2281 | topology_change_request ? " not" : ""); | |
2282 | ast_trace(-1, "%s: DP: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(pending_media_state->topology, &STR_TMP))); | |
2283 | ast_trace(-1, "%s: DA: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(active_media_state->topology, &STR_TMP))); | |
2284 | ast_trace(-1, "%s: CP: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(session->pending_media_state->topology, &STR_TMP))); | |
2285 | ast_trace(-1, "%s: CA: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(session->active_media_state->topology, &STR_TMP))); | |
2286 | ||
2287 | new_pending_state = resolve_refresh_media_states(ast_sip_session_get_name(session), | |
2288 | pending_media_state, active_media_state, session->active_media_state, 1); | |
2289 | if (new_pending_state) { | |
2290 | ast_trace(-1, "%s: NP: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(new_pending_state->topology, &STR_TMP))); | |
2291 | ast_sip_session_media_state_free(pending_media_state); | |
2292 | pending_media_state = new_pending_state; | |
2293 | } else { | |
2294 | ast_sip_session_media_state_reset(pending_media_state); | |
2295 | ast_sip_session_media_state_free(active_media_state); | |
2296 | SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING, "%s: Unable to merge media states\n", ast_sip_session_get_name(session)); | |
2297 | } | |
1717 | 2298 | } |
1718 | 2299 | |
1719 | 2300 | /* Prune the media state so the number of streams fit within the configured limits - we do it here |
1721 | 2302 | * of the SDP when producing it we'd be in trouble. We also enforce formats here for media types that |
1722 | 2303 | * are configurable on the endpoint. |
1723 | 2304 | */ |
1724 | for (index = 0; index < ast_stream_topology_get_count(media_state->topology); ++index) { | |
2305 | ast_trace(-1, "%s: Pruning and checking formats of streams\n", ast_sip_session_get_name(session)); | |
2306 | ||
2307 | for (index = 0; index < ast_stream_topology_get_count(pending_media_state->topology); ++index) { | |
1725 | 2308 | struct ast_stream *existing_stream = NULL; |
1726 | ||
1727 | stream = ast_stream_topology_get_stream(media_state->topology, index); | |
2309 | struct ast_stream *stream = ast_stream_topology_get_stream(pending_media_state->topology, index); | |
2310 | SCOPE_ENTER(4, "%s: Checking stream %s\n", ast_sip_session_get_name(session), | |
2311 | ast_stream_get_name(stream)); | |
1728 | 2312 | |
1729 | 2313 | if (session->active_media_state->topology && |
1730 | 2314 | index < ast_stream_topology_get_count(session->active_media_state->topology)) { |
1731 | 2315 | existing_stream = ast_stream_topology_get_stream(session->active_media_state->topology, index); |
2316 | ast_trace(-1, "%s: Found existing stream %s\n", ast_sip_session_get_name(session), | |
2317 | ast_stream_get_name(existing_stream)); | |
1732 | 2318 | } |
1733 | 2319 | |
1734 | 2320 | if (is_stream_limitation_reached(ast_stream_get_type(stream), session->endpoint, type_streams)) { |
1735 | if (index < AST_VECTOR_SIZE(&media_state->sessions)) { | |
1736 | struct ast_sip_session_media *session_media = AST_VECTOR_GET(&media_state->sessions, index); | |
2321 | if (index < AST_VECTOR_SIZE(&pending_media_state->sessions)) { | |
2322 | struct ast_sip_session_media *session_media = AST_VECTOR_GET(&pending_media_state->sessions, index); | |
1737 | 2323 | |
1738 | 2324 | ao2_cleanup(session_media); |
1739 | AST_VECTOR_REMOVE(&media_state->sessions, index, 1); | |
2325 | AST_VECTOR_REMOVE(&pending_media_state->sessions, index, 1); | |
1740 | 2326 | } |
1741 | 2327 | |
1742 | ast_stream_topology_del_stream(media_state->topology, index); | |
2328 | ast_stream_topology_del_stream(pending_media_state->topology, index); | |
2329 | ast_trace(-1, "%s: Dropped overlimit stream %s\n", ast_sip_session_get_name(session), | |
2330 | ast_stream_get_name(stream)); | |
1743 | 2331 | |
1744 | 2332 | /* A stream has potentially moved into our spot so we need to jump back so we process it */ |
1745 | 2333 | index -= 1; |
1746 | continue; | |
2334 | SCOPE_EXIT_EXPR(continue); | |
1747 | 2335 | } |
1748 | 2336 | |
1749 | 2337 | /* No need to do anything with stream if it's media state is removed */ |
1750 | 2338 | if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) { |
1751 | 2339 | /* If there is no existing stream we can just not have this stream in the topology at all. */ |
1752 | 2340 | if (!existing_stream) { |
1753 | ast_stream_topology_del_stream(media_state->topology, index); | |
2341 | ast_trace(-1, "%s: Dropped removed stream %s\n", ast_sip_session_get_name(session), | |
2342 | ast_stream_get_name(stream)); | |
2343 | ast_stream_topology_del_stream(pending_media_state->topology, index); | |
2344 | /* TODO: Do we need to remove the corresponding media state? */ | |
1754 | 2345 | index -= 1; |
1755 | 2346 | } |
1756 | continue; | |
2347 | SCOPE_EXIT_EXPR(continue); | |
1757 | 2348 | } |
1758 | 2349 | |
1759 | 2350 | /* Enforce the configured allowed codecs on audio and video streams */ |
1763 | 2354 | |
1764 | 2355 | joint_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); |
1765 | 2356 | if (!joint_cap) { |
1766 | ast_sip_session_media_state_free(media_state); | |
1767 | return 0; | |
2357 | ast_sip_session_media_state_free(pending_media_state); | |
2358 | ast_sip_session_media_state_free(active_media_state); | |
2359 | res = -1; | |
2360 | SCOPE_EXIT_LOG_EXPR(goto end, LOG_ERROR, "%s: Unable to alloc format caps\n", ast_sip_session_get_name(session)); | |
1768 | 2361 | } |
1769 | 2362 | ast_format_cap_get_compatible(ast_stream_get_formats(stream), session->endpoint->media.codecs, joint_cap); |
1770 | 2363 | if (!ast_format_cap_count(joint_cap)) { |
1774 | 2367 | /* If there is no existing stream we can just not have this stream in the topology |
1775 | 2368 | * at all. |
1776 | 2369 | */ |
1777 | ast_stream_topology_del_stream(media_state->topology, index); | |
2370 | ast_stream_topology_del_stream(pending_media_state->topology, index); | |
1778 | 2371 | index -= 1; |
1779 | continue; | |
2372 | SCOPE_EXIT_EXPR(continue, "%s: Dropped incompatible stream %s\n", | |
2373 | ast_sip_session_get_name(session), ast_stream_get_name(stream)); | |
1780 | 2374 | } else if (ast_stream_get_state(stream) != ast_stream_get_state(existing_stream) || |
1781 | 2375 | strcmp(ast_stream_get_name(stream), ast_stream_get_name(existing_stream))) { |
1782 | 2376 | /* If the underlying stream is a different type or different name then we have to |
1784 | 2378 | * is preserved. |
1785 | 2379 | */ |
1786 | 2380 | ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED); |
1787 | continue; | |
2381 | SCOPE_EXIT_EXPR(continue, "%s: Dropped incompatible stream %s\n", | |
2382 | ast_sip_session_get_name(session), ast_stream_get_name(stream)); | |
1788 | 2383 | } else { |
1789 | 2384 | /* However if the stream is otherwise remaining the same we can keep the formats |
1790 | 2385 | * that exist on it already which allows media to continue to flow. We don't modify |
1799 | 2394 | } |
1800 | 2395 | |
1801 | 2396 | ++type_streams[ast_stream_get_type(stream)]; |
2397 | ||
2398 | SCOPE_EXIT(); | |
1802 | 2399 | } |
1803 | 2400 | |
1804 | 2401 | if (session->active_media_state->topology) { |
1807 | 2404 | * streams than are currently present we fill in the topology to match the current number of streams |
1808 | 2405 | * that are active. |
1809 | 2406 | */ |
1810 | for (index = ast_stream_topology_get_count(media_state->topology); | |
2407 | ||
2408 | for (index = ast_stream_topology_get_count(pending_media_state->topology); | |
1811 | 2409 | index < ast_stream_topology_get_count(session->active_media_state->topology); ++index) { |
2410 | struct ast_stream *stream = ast_stream_topology_get_stream(session->active_media_state->topology, index); | |
1812 | 2411 | struct ast_stream *cloned; |
1813 | ||
1814 | stream = ast_stream_topology_get_stream(session->active_media_state->topology, index); | |
1815 | ast_assert(stream != NULL); | |
2412 | int position; | |
2413 | SCOPE_ENTER(4, "%s: Stream %s not in pending\n", ast_sip_session_get_name(session), | |
2414 | ast_stream_get_name(stream)); | |
1816 | 2415 | |
1817 | 2416 | cloned = ast_stream_clone(stream, NULL); |
1818 | 2417 | if (!cloned) { |
1819 | ast_sip_session_media_state_free(media_state); | |
1820 | return -1; | |
2418 | ast_sip_session_media_state_free(pending_media_state); | |
2419 | ast_sip_session_media_state_free(active_media_state); | |
2420 | res = -1; | |
2421 | SCOPE_EXIT_LOG_EXPR(goto end, LOG_ERROR, "%s: Unable to clone stream %s\n", | |
2422 | ast_sip_session_get_name(session), ast_stream_get_name(stream)); | |
1821 | 2423 | } |
1822 | 2424 | |
1823 | 2425 | ast_stream_set_state(cloned, AST_STREAM_STATE_REMOVED); |
1824 | if (ast_stream_topology_append_stream(media_state->topology, cloned) < 0) { | |
2426 | position = ast_stream_topology_append_stream(pending_media_state->topology, cloned); | |
2427 | if (position < 0) { | |
1825 | 2428 | ast_stream_free(cloned); |
1826 | ast_sip_session_media_state_free(media_state); | |
1827 | return -1; | |
2429 | ast_sip_session_media_state_free(pending_media_state); | |
2430 | ast_sip_session_media_state_free(active_media_state); | |
2431 | res = -1; | |
2432 | SCOPE_EXIT_LOG_EXPR(goto end, LOG_ERROR, "%s: Unable to append cloned stream\n", | |
2433 | ast_sip_session_get_name(session)); | |
1828 | 2434 | } |
2435 | SCOPE_EXIT("%s: Appended empty stream in position %d to make counts match\n", | |
2436 | ast_sip_session_get_name(session), position); | |
1829 | 2437 | } |
1830 | 2438 | |
1831 | /* If the resulting media state matches the existing active state don't bother doing a session refresh */ | |
1832 | if (ast_stream_topology_equal(session->active_media_state->topology, media_state->topology)) { | |
1833 | ast_sip_session_media_state_free(media_state); | |
2439 | /* | |
2440 | * We can suppress this re-invite if the pending topology is equal to the currently | |
2441 | * active topology but only if this re-invite was the result of a requested topology | |
2442 | * change. If it was the result of some other change, like connected line, then | |
2443 | * we don't want to suppress it even though the topologies are equal. | |
2444 | */ | |
2445 | if (topology_change_request && ast_stream_topology_equal(session->active_media_state->topology, pending_media_state->topology)) { | |
2446 | ast_trace(-1, "%s: CA: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(session->active_media_state->topology, &STR_TMP))); | |
2447 | ast_trace(-1, "%s: NP: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(pending_media_state->topology, &STR_TMP))); | |
2448 | ast_sip_session_media_state_free(pending_media_state); | |
2449 | ast_sip_session_media_state_free(active_media_state); | |
1834 | 2450 | /* For external consumers we return 0 to say success, but internally for |
1835 | 2451 | * send_delayed_request we return a separate value to indicate that this |
1836 | 2452 | * session refresh would be redundant so we didn't send it |
1837 | 2453 | */ |
1838 | return queued ? 1 : 0; | |
2454 | SCOPE_EXIT_RTN_VALUE(queued ? 1 : 0, "%s: Topologies are equal. Not sending re-invite\n", | |
2455 | ast_sip_session_get_name(session)); | |
1839 | 2456 | } |
1840 | 2457 | } |
1841 | 2458 | |
1842 | 2459 | ast_sip_session_media_state_free(session->pending_media_state); |
1843 | session->pending_media_state = media_state; | |
2460 | session->pending_media_state = pending_media_state; | |
1844 | 2461 | } |
1845 | 2462 | |
1846 | 2463 | new_sdp = generate_session_refresh_sdp(session); |
1847 | 2464 | if (!new_sdp) { |
1848 | ast_log(LOG_ERROR, "Failed to generate session refresh SDP. Not sending session refresh\n"); | |
1849 | 2465 | ast_sip_session_media_state_reset(session->pending_media_state); |
1850 | return -1; | |
2466 | ast_sip_session_media_state_free(active_media_state); | |
2467 | SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING, "%s: Failed to generate session refresh SDP. Not sending session refresh\n", | |
2468 | ast_sip_session_get_name(session)); | |
1851 | 2469 | } |
1852 | 2470 | if (on_sdp_creation) { |
1853 | 2471 | if (on_sdp_creation(session, new_sdp)) { |
1854 | 2472 | ast_sip_session_media_state_reset(session->pending_media_state); |
1855 | return -1; | |
2473 | ast_sip_session_media_state_free(active_media_state); | |
2474 | SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING, "%s: on_sdp_creation failed\n", ast_sip_session_get_name(session)); | |
1856 | 2475 | } |
1857 | 2476 | } |
1858 | 2477 | } |
1859 | 2478 | |
1860 | 2479 | if (method == AST_SIP_SESSION_REFRESH_METHOD_INVITE) { |
1861 | 2480 | if (pjsip_inv_reinvite(inv_session, NULL, new_sdp, &tdata)) { |
1862 | ast_log(LOG_WARNING, "Failed to create reinvite properly.\n"); | |
1863 | 2481 | if (generate_new_sdp) { |
1864 | 2482 | ast_sip_session_media_state_reset(session->pending_media_state); |
1865 | 2483 | } |
1866 | return -1; | |
2484 | ast_sip_session_media_state_free(active_media_state); | |
2485 | SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING, "%s: Failed to create reinvite properly\n", ast_sip_session_get_name(session)); | |
1867 | 2486 | } |
1868 | 2487 | } else if (pjsip_inv_update(inv_session, NULL, new_sdp, &tdata)) { |
1869 | ast_log(LOG_WARNING, "Failed to create UPDATE properly.\n"); | |
1870 | 2488 | if (generate_new_sdp) { |
1871 | 2489 | ast_sip_session_media_state_reset(session->pending_media_state); |
1872 | 2490 | } |
1873 | return -1; | |
2491 | ast_sip_session_media_state_free(active_media_state); | |
2492 | SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING, "%s: Failed to create UPDATE properly\n", ast_sip_session_get_name(session)); | |
1874 | 2493 | } |
1875 | 2494 | if (on_request_creation) { |
1876 | 2495 | if (on_request_creation(session, tdata)) { |
1877 | 2496 | if (generate_new_sdp) { |
1878 | 2497 | ast_sip_session_media_state_reset(session->pending_media_state); |
1879 | 2498 | } |
1880 | return -1; | |
1881 | } | |
1882 | } | |
1883 | ast_debug(3, "Sending session refresh SDP via %s to %s\n", | |
1884 | method == AST_SIP_SESSION_REFRESH_METHOD_INVITE ? "re-INVITE" : "UPDATE", | |
1885 | ast_sorcery_object_get_id(session->endpoint)); | |
2499 | ast_sip_session_media_state_free(active_media_state); | |
2500 | SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING, "%s: on_request_creation failed.\n", ast_sip_session_get_name(session)); | |
2501 | } | |
2502 | } | |
1886 | 2503 | ast_sip_session_send_request_with_cb(session, tdata, on_response); |
1887 | return 0; | |
2504 | ast_sip_session_media_state_free(active_media_state); | |
2505 | ||
2506 | end: | |
2507 | SCOPE_EXIT_RTN_VALUE(res, "%s: Sending session refresh SDP via %s\n", ast_sip_session_get_name(session), | |
2508 | method == AST_SIP_SESSION_REFRESH_METHOD_INVITE ? "re-INVITE" : "UPDATE"); | |
1888 | 2509 | } |
1889 | 2510 | |
1890 | 2511 | int ast_sip_session_refresh(struct ast_sip_session *session, |
1895 | 2516 | struct ast_sip_session_media_state *media_state) |
1896 | 2517 | { |
1897 | 2518 | return sip_session_refresh(session, on_request_creation, on_sdp_creation, |
1898 | on_response, method, generate_new_sdp, media_state, 0); | |
2519 | on_response, method, generate_new_sdp, media_state, NULL, 0); | |
1899 | 2520 | } |
1900 | 2521 | |
1901 | 2522 | int ast_sip_session_regenerate_answer(struct ast_sip_session *session, |
2057 | 2678 | |
2058 | 2679 | handler_list = ao2_find(sdp_handlers, media, OBJ_KEY); |
2059 | 2680 | if (!handler_list) { |
2060 | ast_debug(1, "No registered SDP handlers for media type '%s'\n", media); | |
2681 | ast_debug(3, "%s: No registered SDP handlers for media type '%s'\n", ast_sip_session_get_name(session), media); | |
2061 | 2682 | continue; |
2062 | 2683 | } |
2063 | 2684 | AST_LIST_TRAVERSE(&handler_list->list, handler, next) { |
2102 | 2723 | !(dlg = pjsip_ua_find_dialog(&rdata->msg_info.cid->id, &rdata->msg_info.to->tag, &rdata->msg_info.from->tag, PJ_FALSE)) || |
2103 | 2724 | !(session = ast_sip_dialog_get_session(dlg)) || |
2104 | 2725 | !session->channel) { |
2726 | return PJ_FALSE; | |
2727 | } | |
2728 | ||
2729 | if (session->inv_session->invite_tsx) { | |
2730 | /* There's a transaction in progress so bail now and let pjproject send 491 */ | |
2105 | 2731 | return PJ_FALSE; |
2106 | 2732 | } |
2107 | 2733 | |
2221 | 2847 | .on_rx_request = session_reinvite_on_rx_request, |
2222 | 2848 | }; |
2223 | 2849 | |
2224 | ||
2225 | 2850 | void ast_sip_session_send_request_with_cb(struct ast_sip_session *session, pjsip_tx_data *tdata, |
2226 | 2851 | ast_sip_session_response_cb on_response) |
2227 | 2852 | { |
2308 | 2933 | struct ast_sip_session *session = obj; |
2309 | 2934 | struct ast_sip_session_delayed_request *delay; |
2310 | 2935 | |
2936 | #ifdef TEST_FRAMEWORK | |
2311 | 2937 | /* We dup the endpoint ID in case the endpoint gets freed out from under us */ |
2312 | 2938 | const char *endpoint_name = session->endpoint ? |
2313 | 2939 | ast_strdupa(ast_sorcery_object_get_id(session->endpoint)) : "<none>"; |
2314 | ||
2315 | ast_debug(3, "Destroying SIP session with endpoint %s\n", endpoint_name); | |
2940 | #endif | |
2941 | ||
2942 | ast_debug(3, "%s: Destroying SIP session\n", ast_sip_session_get_name(session)); | |
2316 | 2943 | |
2317 | 2944 | ast_test_suite_event_notify("SESSION_DESTROYING", |
2318 | 2945 | "Endpoint: %s\r\n" |
2473 | 3100 | return NULL; |
2474 | 3101 | } |
2475 | 3102 | |
3103 | /* Track the number of challenges received on outbound requests */ | |
3104 | session->authentication_challenge_count = 0; | |
3105 | ||
2476 | 3106 | /* Fire seesion begin handlers */ |
2477 | 3107 | handle_session_begin(session); |
2478 | 3108 | |
2629 | 3259 | } |
2630 | 3260 | |
2631 | 3261 | inv = pjsip_dlg_get_inv_session(dlg); |
3262 | session = inv->mod_data[session_module.id]; | |
3263 | ||
2632 | 3264 | if (PJSIP_INV_STATE_CONFIRMED <= inv->state) { |
2633 | 3265 | /* |
2634 | 3266 | * We cannot handle reINVITE authentication at this |
2635 | 3267 | * time because the reINVITE transaction is still in |
2636 | 3268 | * progress. |
2637 | 3269 | */ |
2638 | ast_debug(1, "A reINVITE is being challenged.\n"); | |
3270 | ast_debug(3, "%s: A reINVITE is being challenged\n", ast_sip_session_get_name(session)); | |
2639 | 3271 | return PJ_FALSE; |
2640 | 3272 | } |
2641 | ast_debug(1, "Initial INVITE is being challenged.\n"); | |
2642 | ||
2643 | session = inv->mod_data[session_module.id]; | |
3273 | ast_debug(3, "%s: Initial INVITE is being challenged.\n", ast_sip_session_get_name(session)); | |
3274 | ||
3275 | if (++session->authentication_challenge_count > MAX_RX_CHALLENGES) { | |
3276 | ast_debug(3, "%s: Initial INVITE reached maximum number of auth attempts.\n", ast_sip_session_get_name(session)); | |
3277 | return PJ_FALSE; | |
3278 | } | |
2644 | 3279 | |
2645 | 3280 | if (ast_sip_create_request_with_auth(&session->endpoint->outbound_auths, rdata, |
2646 | 3281 | tsx->last_tx, &tdata)) { |
2891 | 3526 | break; |
2892 | 3527 | case PJSIP_INV_STATE_CONFIRMED: |
2893 | 3528 | if (session->inv_session->invite_tsx) { |
2894 | ast_debug(3, "Delay sending BYE to %s because of outstanding transaction...\n", | |
2895 | ast_sorcery_object_get_id(session->endpoint)); | |
3529 | ast_debug(3, "%s: Delay sending BYE because of outstanding transaction...\n", | |
3530 | ast_sip_session_get_name(session)); | |
2896 | 3531 | /* If this is delayed the only thing that will happen is a BYE request so we don't |
2897 | 3532 | * actually need to store the response code for when it happens. |
2898 | 3533 | */ |
2899 | delay_request(session, NULL, NULL, NULL, 0, DELAYED_METHOD_BYE, NULL); | |
3534 | delay_request(session, NULL, NULL, NULL, 0, DELAYED_METHOD_BYE, NULL, NULL, 1); | |
2900 | 3535 | break; |
2901 | 3536 | } |
2902 | 3537 | /* Fall through */ |
3014 | 3649 | |
3015 | 3650 | if (session->ended_while_deferred) { |
3016 | 3651 | /* Complete the session end started by the remote hangup. */ |
3017 | ast_debug(3, "Ending session (%p) after being deferred\n", session); | |
3652 | ast_debug(3, "%s: Ending session after being deferred\n", ast_sip_session_get_name(session)); | |
3018 | 3653 | session->ended_while_deferred = 0; |
3019 | 3654 | session_end(session); |
3020 | 3655 | } |
3078 | 3713 | |
3079 | 3714 | pickup_cfg = ast_get_chan_features_pickup_config(session->channel); |
3080 | 3715 | if (!pickup_cfg) { |
3081 | ast_log(LOG_ERROR, "Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n"); | |
3716 | ast_log(LOG_ERROR, "%s: Unable to retrieve pickup configuration options. Unable to detect call pickup extension\n", | |
3717 | ast_sip_session_get_name(session)); | |
3082 | 3718 | pickupexten = ""; |
3083 | 3719 | } else { |
3084 | 3720 | pickupexten = ast_strdupa(pickup_cfg->pickupexten); |
3115 | 3751 | return SIP_GET_DEST_EXTEN_NOT_FOUND; |
3116 | 3752 | } |
3117 | 3753 | |
3754 | /* | |
3755 | * /internal | |
3756 | * /brief Process initial answer for an incoming invite | |
3757 | * | |
3758 | * This function should only be called during the setup, and handling of a | |
3759 | * new incoming invite. Most, if not all of the time, this will be called | |
3760 | * when an error occurs and we need to respond as such. | |
3761 | * | |
3762 | * When a SIP session termination code is given for the answer it's assumed | |
3763 | * this call then will be the final bit of processing before ending session | |
3764 | * setup. As such, we've been holding a lock, and a reference on the invite | |
3765 | * session's dialog. So before returning this function removes that reference, | |
3766 | * and unlocks the dialog. | |
3767 | * | |
3768 | * \param inv_session The session on which to answer | |
3769 | * \param rdata The original request | |
3770 | * \param answer_code The answer's numeric code | |
3771 | * \param terminate_code The termination code if the answer fails | |
3772 | * \param notify Whether or not to call on_state_changed | |
3773 | * | |
3774 | * \retval 0 if invite successfully answered, -1 if an error occurred | |
3775 | */ | |
3776 | static int new_invite_initial_answer(pjsip_inv_session *inv_session, pjsip_rx_data *rdata, | |
3777 | int answer_code, int terminate_code, pj_bool_t notify) | |
3778 | { | |
3779 | pjsip_tx_data *tdata = NULL; | |
3780 | int res = 0; | |
3781 | ||
3782 | if (inv_session->state != PJSIP_INV_STATE_DISCONNECTED) { | |
3783 | if (pjsip_inv_initial_answer( | |
3784 | inv_session, rdata, answer_code, NULL, NULL, &tdata) != PJ_SUCCESS) { | |
3785 | ||
3786 | pjsip_inv_terminate(inv_session, terminate_code ? terminate_code : answer_code, notify); | |
3787 | res = -1; | |
3788 | } else { | |
3789 | pjsip_inv_send_msg(inv_session, tdata); | |
3790 | } | |
3791 | } | |
3792 | ||
3793 | if (answer_code >= 300) { | |
3794 | /* | |
3795 | * A session is ending. The dialog has a reference that needs to be | |
3796 | * removed and holds a lock that needs to be unlocked before returning. | |
3797 | */ | |
3798 | pjsip_dlg_dec_lock(inv_session->dlg); | |
3799 | } | |
3800 | ||
3801 | return res; | |
3802 | } | |
3803 | ||
3804 | /* | |
3805 | * /internal | |
3806 | * /brief Create and initialize a pjsip invite session | |
3807 | ||
3808 | * pjsip_inv_session adds, and maintains a reference to the dialog upon a successful | |
3809 | * invite session creation until the session is destroyed. However, we'll wait to | |
3810 | * remove the reference that was added for the dialog when it gets created since we're | |
3811 | * not ready to unlock the dialog in this function. | |
3812 | * | |
3813 | * So, if this function successfully returns that means it returns with its newly | |
3814 | * created, and associated dialog locked and with two references (i.e. dialog's | |
3815 | * reference count should be 2). | |
3816 | * | |
3817 | * \param endpoint A pointer to the endpoint | |
3818 | * \param rdata The request that is starting the dialog | |
3819 | * | |
3820 | * \retval A pjsip invite session object | |
3821 | * \retval NULL on error | |
3822 | */ | |
3118 | 3823 | static pjsip_inv_session *pre_session_setup(pjsip_rx_data *rdata, const struct ast_sip_endpoint *endpoint) |
3119 | 3824 | { |
3120 | 3825 | pjsip_tx_data *tdata; |
3121 | 3826 | pjsip_dialog *dlg; |
3122 | 3827 | pjsip_inv_session *inv_session; |
3123 | 3828 | unsigned int options = endpoint->extensions.flags; |
3124 | pj_status_t dlg_status; | |
3829 | pj_status_t dlg_status = PJ_EUNKNOWN; | |
3125 | 3830 | |
3126 | 3831 | if (pjsip_inv_verify_request(rdata, &options, NULL, NULL, ast_sip_get_pjsip_endpoint(), &tdata) != PJ_SUCCESS) { |
3127 | 3832 | if (tdata) { |
3133 | 3838 | } |
3134 | 3839 | return NULL; |
3135 | 3840 | } |
3136 | dlg = ast_sip_create_dialog_uas(endpoint, rdata, &dlg_status); | |
3841 | ||
3842 | dlg = ast_sip_create_dialog_uas_locked(endpoint, rdata, &dlg_status); | |
3137 | 3843 | if (!dlg) { |
3138 | 3844 | if (dlg_status != PJ_EEXISTS) { |
3139 | 3845 | pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL); |
3140 | 3846 | } |
3141 | 3847 | return NULL; |
3142 | 3848 | } |
3849 | ||
3850 | /* | |
3851 | * The returned dialog holds a lock and has a reference added. Any paths where the | |
3852 | * dialog invite session is not returned must unlock the dialog and remove its reference. | |
3853 | */ | |
3854 | ||
3143 | 3855 | if (pjsip_inv_create_uas(dlg, rdata, NULL, options, &inv_session) != PJ_SUCCESS) { |
3144 | 3856 | pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL); |
3857 | /* | |
3858 | * The acquired dialog holds a lock, and a reference. Since the dialog is not | |
3859 | * going to be returned here it must first be unlocked and de-referenced. This | |
3860 | * must be done prior to calling dialog termination. | |
3861 | */ | |
3862 | pjsip_dlg_dec_lock(dlg); | |
3145 | 3863 | pjsip_dlg_terminate(dlg); |
3146 | 3864 | return NULL; |
3147 | 3865 | } |
3150 | 3868 | inv_session->sdp_neg_flags = PJMEDIA_SDP_NEG_ALLOW_MEDIA_CHANGE; |
3151 | 3869 | #endif |
3152 | 3870 | if (pjsip_dlg_add_usage(dlg, &session_module, NULL) != PJ_SUCCESS) { |
3153 | if (pjsip_inv_initial_answer(inv_session, rdata, 500, NULL, NULL, &tdata) != PJ_SUCCESS) { | |
3154 | pjsip_inv_terminate(inv_session, 500, PJ_FALSE); | |
3155 | } | |
3156 | pjsip_inv_send_msg(inv_session, tdata); | |
3871 | /* Dialog's lock and a reference are removed in new_invite_initial_answer */ | |
3872 | new_invite_initial_answer(inv_session, rdata, 500, 500, PJ_FALSE); | |
3873 | /* Remove 2nd reference added at inv_session creation */ | |
3874 | pjsip_dlg_dec_session(inv_session->dlg, &session_module); | |
3157 | 3875 | return NULL; |
3158 | 3876 | } |
3877 | ||
3159 | 3878 | return inv_session; |
3160 | 3879 | } |
3161 | 3880 | |
3230 | 3949 | pjsip_rdata_sdp_info *sdp_info; |
3231 | 3950 | pjmedia_sdp_session *local = NULL; |
3232 | 3951 | char buffer[AST_SOCKADDR_BUFLEN]; |
3952 | SCOPE_ENTER(3, "%s\n", ast_sip_session_get_name(invite->session)); | |
3953 | ||
3233 | 3954 | |
3234 | 3955 | /* From this point on, any calls to pjsip_inv_terminate have the last argument as PJ_TRUE |
3235 | 3956 | * so that we will be notified so we can destroy the session properly |
3236 | 3957 | */ |
3237 | 3958 | |
3238 | 3959 | if (invite->session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) { |
3239 | ast_log(LOG_ERROR, "Session already DISCONNECTED [reason=%d (%s)]\n", | |
3960 | ast_trace_log(-1, LOG_ERROR, "%s: Session already DISCONNECTED [reason=%d (%s)]\n", | |
3961 | ast_sip_session_get_name(invite->session), | |
3240 | 3962 | invite->session->inv_session->cause, |
3241 | 3963 | pjsip_get_status_text(invite->session->inv_session->cause)->ptr); |
3242 | 3964 | #ifdef HAVE_PJSIP_INV_SESSION_REF |
3243 | 3965 | pjsip_inv_dec_ref(invite->session->inv_session); |
3244 | 3966 | #endif |
3245 | return -1; | |
3967 | SCOPE_EXIT_RTN_VALUE(-1); | |
3246 | 3968 | } |
3247 | 3969 | |
3248 | 3970 | switch (get_destination(invite->session, invite->rdata)) { |
3250 | 3972 | /* Things worked. Keep going */ |
3251 | 3973 | break; |
3252 | 3974 | case SIP_GET_DEST_UNSUPPORTED_URI: |
3975 | ast_trace(-1, "%s: Call (%s:%s) to extension '%s' - unsupported uri\n", | |
3976 | ast_sip_session_get_name(invite->session), | |
3977 | invite->rdata->tp_info.transport->type_name, | |
3978 | pj_sockaddr_print(&invite->rdata->pkt_info.src_addr, buffer, sizeof(buffer), 3), | |
3979 | invite->session->exten); | |
3253 | 3980 | if (pjsip_inv_initial_answer(invite->session->inv_session, invite->rdata, 416, NULL, NULL, &tdata) == PJ_SUCCESS) { |
3254 | 3981 | ast_sip_session_send_response(invite->session, tdata); |
3255 | 3982 | } else { |
3257 | 3984 | } |
3258 | 3985 | goto end; |
3259 | 3986 | case SIP_GET_DEST_EXTEN_PARTIAL: |
3260 | ast_debug(1, "Call from '%s' (%s:%s) to extension '%s' - partial match\n", | |
3261 | ast_sorcery_object_get_id(invite->session->endpoint), | |
3987 | ast_trace(-1, "%s: Call (%s:%s) to extension '%s' - partial match\n", | |
3988 | ast_sip_session_get_name(invite->session), | |
3262 | 3989 | invite->rdata->tp_info.transport->type_name, |
3263 | 3990 | pj_sockaddr_print(&invite->rdata->pkt_info.src_addr, buffer, sizeof(buffer), 3), |
3264 | 3991 | invite->session->exten); |
3271 | 3998 | goto end; |
3272 | 3999 | case SIP_GET_DEST_EXTEN_NOT_FOUND: |
3273 | 4000 | default: |
3274 | ast_log(LOG_NOTICE, "Call from '%s' (%s:%s) to extension '%s' rejected because extension not found in context '%s'.\n", | |
3275 | ast_sorcery_object_get_id(invite->session->endpoint), | |
4001 | ast_trace_log(-1, LOG_NOTICE, "%s: Call (%s:%s) to extension '%s' rejected because extension not found in context '%s'.\n", | |
4002 | ast_sip_session_get_name(invite->session), | |
3276 | 4003 | invite->rdata->tp_info.transport->type_name, |
3277 | 4004 | pj_sockaddr_print(&invite->rdata->pkt_info.src_addr, buffer, sizeof(buffer), 3), |
3278 | 4005 | invite->session->exten, |
3305 | 4032 | * so let's go ahead and send a 100 Trying out to stop any |
3306 | 4033 | * retransmissions. |
3307 | 4034 | */ |
4035 | ast_trace(-1, "%s: Call (%s:%s) to extension '%s' sending 100 Trying\n", | |
4036 | ast_sip_session_get_name(invite->session), | |
4037 | invite->rdata->tp_info.transport->type_name, | |
4038 | pj_sockaddr_print(&invite->rdata->pkt_info.src_addr, buffer, sizeof(buffer), 3), | |
4039 | invite->session->exten); | |
3308 | 4040 | if (pjsip_inv_initial_answer(invite->session->inv_session, invite->rdata, 100, NULL, NULL, &tdata) != PJ_SUCCESS) { |
3309 | 4041 | pjsip_inv_terminate(invite->session->inv_session, 500, PJ_TRUE); |
3310 | 4042 | goto end; |
3352 | 4084 | #ifdef HAVE_PJSIP_INV_SESSION_REF |
3353 | 4085 | pjsip_inv_dec_ref(invite->session->inv_session); |
3354 | 4086 | #endif |
3355 | return 0; | |
4087 | SCOPE_EXIT_RTN_VALUE(0, "%s\n", ast_sip_session_get_name(invite->session)); | |
3356 | 4088 | } |
3357 | 4089 | |
3358 | 4090 | static void handle_new_invite_request(pjsip_rx_data *rdata) |
3359 | 4091 | { |
3360 | 4092 | RAII_VAR(struct ast_sip_endpoint *, endpoint, |
3361 | 4093 | ast_pjsip_rdata_get_endpoint(rdata), ao2_cleanup); |
3362 | pjsip_tx_data *tdata = NULL; | |
3363 | 4094 | pjsip_inv_session *inv_session = NULL; |
3364 | 4095 | struct ast_sip_session *session; |
3365 | 4096 | struct new_invite invite; |
3372 | 4103 | return; |
3373 | 4104 | } |
3374 | 4105 | |
4106 | /* | |
4107 | * Upon a successful pre_session_setup the associated dialog is returned locked | |
4108 | * and with an added reference. Well actually two references. One added when the | |
4109 | * dialog itself was created, and another added when the pjsip invite session was | |
4110 | * created and the dialog was added to it. | |
4111 | * | |
4112 | * In order to ensure the dialog's, and any of its internal attributes, lifetimes | |
4113 | * we'll hold the lock and maintain the reference throughout the entire new invite | |
4114 | * handling process. See ast_sip_create_dialog_uas_locked for more details but, | |
4115 | * basically we do this to make sure a transport failure does not destroy the dialog | |
4116 | * and/or transaction out from underneath us between pjsip calls. Alternatively, we | |
4117 | * could probably release the lock if we needed to, but then we'd have to re-lock and | |
4118 | * check the dialog and transaction prior to every pjsip call. | |
4119 | * | |
4120 | * That means any off nominal/failure paths in this function must remove the associated | |
4121 | * dialog reference added at dialog creation, and remove the lock. As well the | |
4122 | * referenced pjsip invite session must be "cleaned up", which should also then | |
4123 | * remove its reference to the dialog at that time. | |
4124 | * | |
4125 | * Nominally we'll unlock the dialog, and release the reference when all new invite | |
4126 | * process handling has successfully completed. | |
4127 | */ | |
4128 | ||
3375 | 4129 | #ifdef HAVE_PJSIP_INV_SESSION_REF |
3376 | 4130 | if (pjsip_inv_add_ref(inv_session) != PJ_SUCCESS) { |
3377 | 4131 | ast_log(LOG_ERROR, "Can't increase the session reference counter\n"); |
3378 | if (inv_session->state != PJSIP_INV_STATE_DISCONNECTED) { | |
3379 | if (pjsip_inv_initial_answer(inv_session, rdata, 500, NULL, NULL, &tdata) == PJ_SUCCESS) { | |
3380 | pjsip_inv_terminate(inv_session, 500, PJ_FALSE); | |
3381 | } else { | |
3382 | pjsip_inv_send_msg(inv_session, tdata); | |
3383 | } | |
4132 | /* Dialog's lock and a reference are removed in new_invite_initial_answer */ | |
4133 | if (!new_invite_initial_answer(inv_session, rdata, 500, 500, PJ_FALSE)) { | |
4134 | /* Terminate the session if it wasn't done in the answer */ | |
4135 | pjsip_inv_terminate(inv_session, 500, PJ_FALSE); | |
3384 | 4136 | } |
3385 | 4137 | return; |
3386 | 4138 | } |
3387 | 4139 | #endif |
3388 | ||
3389 | 4140 | session = ast_sip_session_alloc(endpoint, NULL, inv_session, rdata); |
3390 | 4141 | if (!session) { |
3391 | if (pjsip_inv_initial_answer(inv_session, rdata, 500, NULL, NULL, &tdata) == PJ_SUCCESS) { | |
4142 | /* Dialog's lock and reference are removed in new_invite_initial_answer */ | |
4143 | if (!new_invite_initial_answer(inv_session, rdata, 500, 500, PJ_FALSE)) { | |
4144 | /* Terminate the session if it wasn't done in the answer */ | |
3392 | 4145 | pjsip_inv_terminate(inv_session, 500, PJ_FALSE); |
3393 | } else { | |
3394 | pjsip_inv_send_msg(inv_session, tdata); | |
3395 | } | |
4146 | } | |
4147 | ||
3396 | 4148 | #ifdef HAVE_PJSIP_INV_SESSION_REF |
3397 | 4149 | pjsip_inv_dec_ref(inv_session); |
3398 | 4150 | #endif |
3409 | 4161 | invite.session = session; |
3410 | 4162 | invite.rdata = rdata; |
3411 | 4163 | new_invite(&invite); |
4164 | ||
4165 | /* | |
4166 | * The dialog lock and reference added at dialog creation time must be | |
4167 | * maintained throughout the new invite process. Since we're pretty much | |
4168 | * done at this point with things it's safe to go ahead and remove the lock | |
4169 | * and the reference here. See ast_sip_create_dialog_uas_locked for more info. | |
4170 | * | |
4171 | * Note, any future functionality added that does work using the dialog must | |
4172 | * be done before this. | |
4173 | */ | |
4174 | pjsip_dlg_dec_lock(inv_session->dlg); | |
3412 | 4175 | |
3413 | 4176 | ao2_ref(session, -1); |
3414 | 4177 | } |
3488 | 4251 | { |
3489 | 4252 | struct ast_sip_session *session = entry->user_data; |
3490 | 4253 | |
3491 | ast_debug(3, "Endpoint '%s(%s)' re-INVITE collision timer expired.\n", | |
3492 | ast_sorcery_object_get_id(session->endpoint), | |
3493 | session->channel ? ast_channel_name(session->channel) : ""); | |
4254 | ast_debug(3, "%s: re-INVITE collision timer expired.\n", | |
4255 | ast_sip_session_get_name(session)); | |
3494 | 4256 | |
3495 | 4257 | if (AST_LIST_EMPTY(&session->delayed_requests)) { |
3496 | 4258 | /* No delayed request pending, so just return */ |
3510 | 4272 | { |
3511 | 4273 | pjsip_inv_session *inv = session->inv_session; |
3512 | 4274 | pj_time_val tv; |
3513 | ||
3514 | ast_debug(3, "Endpoint '%s(%s)' re-INVITE collision.\n", | |
3515 | ast_sorcery_object_get_id(session->endpoint), | |
3516 | session->channel ? ast_channel_name(session->channel) : ""); | |
3517 | if (delay_request(session, NULL, NULL, on_response, 1, DELAYED_METHOD_INVITE, NULL)) { | |
3518 | return; | |
3519 | } | |
4275 | struct ast_sip_session_media_state *pending_media_state; | |
4276 | struct ast_sip_session_media_state *active_media_state; | |
4277 | const char *session_name = ast_sip_session_get_name(session); | |
4278 | SCOPE_ENTER(3, "%s\n", session_name); | |
4279 | ||
4280 | pending_media_state = ast_sip_session_media_state_clone(session->pending_media_state); | |
4281 | if (!pending_media_state) { | |
4282 | SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Failed to clone pending media state\n", session_name); | |
4283 | } | |
4284 | ||
4285 | active_media_state = ast_sip_session_media_state_clone(session->active_media_state); | |
4286 | if (!active_media_state) { | |
4287 | ast_sip_session_media_state_free(pending_media_state); | |
4288 | SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Failed to clone active media state\n", session_name); | |
4289 | } | |
4290 | ||
4291 | if (delay_request(session, NULL, NULL, on_response, 1, DELAYED_METHOD_INVITE, pending_media_state, | |
4292 | active_media_state, 1)) { | |
4293 | ast_sip_session_media_state_free(pending_media_state); | |
4294 | ast_sip_session_media_state_free(active_media_state); | |
4295 | SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Failed to add delayed request\n", session_name); | |
4296 | } | |
4297 | ||
3520 | 4298 | if (pj_timer_entry_running(&session->rescheduled_reinvite)) { |
3521 | 4299 | /* Timer already running. Something weird is going on. */ |
3522 | ast_debug(1, "Endpoint '%s(%s)' re-INVITE collision while timer running!!!\n", | |
3523 | ast_sorcery_object_get_id(session->endpoint), | |
3524 | session->channel ? ast_channel_name(session->channel) : ""); | |
3525 | return; | |
4300 | SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: re-INVITE collision while timer running!!!\n", session_name); | |
3526 | 4301 | } |
3527 | 4302 | |
3528 | 4303 | tv.sec = 0; |
3537 | 4312 | if (pjsip_endpt_schedule_timer(ast_sip_get_pjsip_endpoint(), |
3538 | 4313 | &session->rescheduled_reinvite, &tv) != PJ_SUCCESS) { |
3539 | 4314 | ao2_ref(session, -1); |
3540 | } | |
4315 | SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Couldn't schedule timer\n", session_name); | |
4316 | } | |
4317 | ||
4318 | SCOPE_EXIT_RTN(); | |
3541 | 4319 | } |
3542 | 4320 | |
3543 | 4321 | static void __print_debug_details(const char *function, pjsip_inv_session *inv, pjsip_transaction *tsx, pjsip_event *e) |
3595 | 4373 | { |
3596 | 4374 | struct ast_sip_session_supplement *supplement; |
3597 | 4375 | struct pjsip_request_line req = rdata->msg_info.msg->line.req; |
3598 | ||
3599 | ast_debug(3, "Method is %.*s\n", (int) pj_strlen(&req.method.name), pj_strbuf(&req.method.name)); | |
4376 | SCOPE_ENTER(3, "%s: Method is %.*s\n", ast_sip_session_get_name(session), (int) pj_strlen(&req.method.name), pj_strbuf(&req.method.name)); | |
4377 | ||
3600 | 4378 | AST_LIST_TRAVERSE(&session->supplements, supplement, next) { |
3601 | 4379 | if (supplement->incoming_request && does_method_match(&req.method.name, supplement->method)) { |
3602 | 4380 | if (supplement->incoming_request(session, rdata)) { |
3604 | 4382 | } |
3605 | 4383 | } |
3606 | 4384 | } |
4385 | ||
4386 | SCOPE_EXIT("%s\n", ast_sip_session_get_name(session)); | |
3607 | 4387 | } |
3608 | 4388 | |
3609 | 4389 | static void handle_session_begin(struct ast_sip_session *session) |
3645 | 4425 | { |
3646 | 4426 | struct ast_sip_session_supplement *supplement; |
3647 | 4427 | struct pjsip_status_line status = rdata->msg_info.msg->line.status; |
3648 | ||
3649 | ast_debug(3, "Response is %d %.*s\n", status.code, (int) pj_strlen(&status.reason), | |
3650 | pj_strbuf(&status.reason)); | |
4428 | SCOPE_ENTER(3, "%s: Response is %d %.*s\n", ast_sip_session_get_name(session), | |
4429 | status.code, (int) pj_strlen(&status.reason), pj_strbuf(&status.reason)); | |
3651 | 4430 | |
3652 | 4431 | AST_LIST_TRAVERSE(&session->supplements, supplement, next) { |
3653 | 4432 | if (!(supplement->response_priority & response_priority)) { |
3657 | 4436 | supplement->incoming_response(session, rdata); |
3658 | 4437 | } |
3659 | 4438 | } |
4439 | ||
4440 | SCOPE_EXIT("%s\n", ast_sip_session_get_name(session)); | |
3660 | 4441 | } |
3661 | 4442 | |
3662 | 4443 | static int handle_incoming(struct ast_sip_session *session, pjsip_rx_data *rdata, |
3663 | 4444 | enum ast_sip_session_response_priority response_priority) |
3664 | 4445 | { |
3665 | ast_debug(3, "Received %s\n", rdata->msg_info.msg->type == PJSIP_REQUEST_MSG ? | |
3666 | "request" : "response"); | |
3667 | ||
3668 | 4446 | if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) { |
3669 | 4447 | handle_incoming_request(session, rdata); |
3670 | 4448 | } else { |
3678 | 4456 | { |
3679 | 4457 | struct ast_sip_session_supplement *supplement; |
3680 | 4458 | struct pjsip_request_line req = tdata->msg->line.req; |
3681 | ||
3682 | ast_debug(3, "Method is %.*s\n", (int) pj_strlen(&req.method.name), pj_strbuf(&req.method.name)); | |
4459 | SCOPE_ENTER(3, "%s: Method is %.*s\n", ast_sip_session_get_name(session), | |
4460 | (int) pj_strlen(&req.method.name), pj_strbuf(&req.method.name)); | |
4461 | ||
3683 | 4462 | AST_LIST_TRAVERSE(&session->supplements, supplement, next) { |
3684 | 4463 | if (supplement->outgoing_request && does_method_match(&req.method.name, supplement->method)) { |
3685 | 4464 | supplement->outgoing_request(session, tdata); |
3686 | 4465 | } |
3687 | 4466 | } |
4467 | SCOPE_EXIT("%s\n", ast_sip_session_get_name(session)); | |
3688 | 4468 | } |
3689 | 4469 | |
3690 | 4470 | static void handle_outgoing_response(struct ast_sip_session *session, pjsip_tx_data *tdata) |
3692 | 4472 | struct ast_sip_session_supplement *supplement; |
3693 | 4473 | struct pjsip_status_line status = tdata->msg->line.status; |
3694 | 4474 | pjsip_cseq_hdr *cseq = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL); |
3695 | ||
3696 | if (!cseq) { | |
3697 | ast_log(LOG_ERROR, "Cannot send response due to missing sequence header"); | |
3698 | return; | |
3699 | } | |
3700 | ||
3701 | ast_debug(3, "Method is %.*s, Response is %d %.*s\n", (int) pj_strlen(&cseq->method.name), | |
4475 | SCOPE_ENTER(3, "%s: Method is %.*s, Response is %d %.*s\n", ast_sip_session_get_name(session), | |
4476 | (int) pj_strlen(&cseq->method.name), | |
3702 | 4477 | pj_strbuf(&cseq->method.name), status.code, (int) pj_strlen(&status.reason), |
3703 | 4478 | pj_strbuf(&status.reason)); |
4479 | ||
4480 | ||
4481 | if (!cseq) { | |
4482 | SCOPE_EXIT_LOG_RTN(LOG_ERROR, "%s: Cannot send response due to missing sequence header", | |
4483 | ast_sip_session_get_name(session)); | |
4484 | } | |
3704 | 4485 | |
3705 | 4486 | AST_LIST_TRAVERSE(&session->supplements, supplement, next) { |
3706 | 4487 | if (supplement->outgoing_response && does_method_match(&cseq->method.name, supplement->method)) { |
3707 | 4488 | supplement->outgoing_response(session, tdata); |
3708 | 4489 | } |
3709 | 4490 | } |
4491 | ||
4492 | SCOPE_EXIT("%s\n", ast_sip_session_get_name(session)); | |
3710 | 4493 | } |
3711 | 4494 | |
3712 | 4495 | static int session_end(void *vsession) |
3771 | 4554 | struct ast_sip_session *session, pjsip_rx_data *rdata) |
3772 | 4555 | { |
3773 | 4556 | pjsip_msg *msg; |
4557 | ast_debug(3, "%s: Received %s\n", ast_sip_session_get_name(session), rdata->msg_info.msg->type == PJSIP_REQUEST_MSG ? | |
4558 | "request" : "response"); | |
4559 | ||
3774 | 4560 | |
3775 | 4561 | handle_incoming(session, rdata, AST_SIP_SESSION_BEFORE_MEDIA); |
3776 | 4562 | msg = rdata->msg_info.msg; |
3786 | 4572 | * SDP answer. |
3787 | 4573 | */ |
3788 | 4574 | ast_debug(1, |
3789 | "Endpoint '%s(%s)': Ending session due to incomplete SDP negotiation. %s\n", | |
3790 | ast_sorcery_object_get_id(session->endpoint), | |
3791 | session->channel ? ast_channel_name(session->channel) : "", | |
4575 | "%s: Ending session due to incomplete SDP negotiation. %s\n", | |
4576 | ast_sip_session_get_name(session), | |
3792 | 4577 | pjsip_rx_data_get_info(rdata)); |
3793 | 4578 | if (pjsip_inv_end_session(inv, 400, NULL, &tdata) == PJ_SUCCESS |
3794 | 4579 | && tdata) { |
3825 | 4610 | handle_incoming_before_media(inv, session, e->body.rx_msg.rdata); |
3826 | 4611 | break; |
3827 | 4612 | case PJSIP_EVENT_TSX_STATE: |
3828 | ast_debug(3, "Source of transaction state change is %s\n", pjsip_event_str(e->body.tsx_state.type)); | |
4613 | ast_debug(3, "%s: Source of transaction state change is %s\n", ast_sip_session_get_name(session), | |
4614 | pjsip_event_str(e->body.tsx_state.type)); | |
3829 | 4615 | /* Transaction state changes are prompted by some other underlying event. */ |
3830 | 4616 | switch(e->body.tsx_state.type) { |
3831 | 4617 | case PJSIP_EVENT_TX_MSG: |
3861 | 4647 | |
3862 | 4648 | if (inv->state == PJSIP_INV_STATE_DISCONNECTED) { |
3863 | 4649 | if (session->defer_end) { |
3864 | ast_debug(3, "Deferring session (%p) end\n", session); | |
4650 | ast_debug(3, "%s: Deferring session end\n", ast_sip_session_get_name(session)); | |
3865 | 4651 | session->ended_while_deferred = 1; |
3866 | 4652 | return; |
3867 | 4653 | } |
3967 | 4753 | return; |
3968 | 4754 | } |
3969 | 4755 | if (inv->state == PJSIP_INV_STATE_CONFIRMED) { |
3970 | ast_debug(1, "reINVITE received final response code %d\n", | |
4756 | ast_debug(1, "%s: reINVITE received final response code %d\n", | |
4757 | ast_sip_session_get_name(session), | |
3971 | 4758 | tsx->status_code); |
3972 | 4759 | if ((tsx->status_code == 401 || tsx->status_code == 407) |
4760 | && ++session->authentication_challenge_count < MAX_RX_CHALLENGES | |
3973 | 4761 | && !ast_sip_create_request_with_auth( |
3974 | 4762 | &session->endpoint->outbound_auths, |
3975 | 4763 | e->body.tsx_state.src.rdata, tsx->last_tx, &tdata)) { |
4040 | 4828 | pjsip_rx_data_get_info(e->body.tsx_state.src.rdata), |
4041 | 4829 | sdp_negotiation_done ? "complete" : "incomplete"); |
4042 | 4830 | if (!sdp_negotiation_done) { |
4043 | ast_debug(1, "Endpoint '%s(%s)': Incomplete SDP negotiation cancelled session. %s\n", | |
4044 | ast_sorcery_object_get_id(session->endpoint), | |
4045 | session->channel ? ast_channel_name(session->channel) : "", | |
4831 | ast_debug(1, "%s: Incomplete SDP negotiation cancelled session. %s\n", | |
4832 | ast_sip_session_get_name(session), | |
4046 | 4833 | pjsip_rx_data_get_info(e->body.tsx_state.src.rdata)); |
4047 | 4834 | } else if (pjsip_inv_end_session(inv, 500, NULL, &tdata) == PJ_SUCCESS |
4048 | 4835 | && tdata) { |
4049 | ast_debug(1, "Endpoint '%s(%s)': Ending session due to RFC5407 race condition. %s\n", | |
4050 | ast_sorcery_object_get_id(session->endpoint), | |
4051 | session->channel ? ast_channel_name(session->channel) : "", | |
4836 | ast_debug(1, "%s: Ending session due to RFC5407 race condition. %s\n", | |
4837 | ast_sip_session_get_name(session), | |
4052 | 4838 | pjsip_rx_data_get_info(e->body.tsx_state.src.rdata)); |
4053 | 4839 | ast_sip_session_send_request(session, tdata); |
4054 | 4840 | } |
4060 | 4846 | if (tsx->role == PJSIP_ROLE_UAC) { |
4061 | 4847 | if (tsx->state == PJSIP_TSX_STATE_COMPLETED) { |
4062 | 4848 | /* This means we got a final response to our outgoing method */ |
4063 | ast_debug(1, "%.*s received final response code %d\n", | |
4849 | ast_debug(1, "%s: %.*s received final response code %d\n", | |
4850 | ast_sip_session_get_name(session), | |
4064 | 4851 | (int) pj_strlen(&tsx->method.name), pj_strbuf(&tsx->method.name), |
4065 | 4852 | tsx->status_code); |
4066 | 4853 | if ((tsx->status_code == 401 || tsx->status_code == 407) |
4854 | && ++session->authentication_challenge_count < MAX_RX_CHALLENGES | |
4067 | 4855 | && !ast_sip_create_request_with_auth( |
4068 | 4856 | &session->endpoint->outbound_auths, |
4069 | 4857 | e->body.tsx_state.src.rdata, tsx->last_tx, &tdata)) { |
4102 | 4890 | |
4103 | 4891 | if (tsx->method.id == PJSIP_INVITE_METHOD) { |
4104 | 4892 | if (tsx->state == PJSIP_TSX_STATE_PROCEEDING) { |
4105 | ast_debug(3, "Endpoint '%s(%s)' INVITE delay check. tsx-state:%s\n", | |
4106 | ast_sorcery_object_get_id(session->endpoint), | |
4107 | session->channel ? ast_channel_name(session->channel) : "", | |
4893 | ast_debug(3, "%s: INVITE delay check. tsx-state:%s\n", | |
4894 | ast_sip_session_get_name(session), | |
4108 | 4895 | pjsip_tsx_state_str(tsx->state)); |
4109 | 4896 | check_delayed_requests(session, invite_proceeding); |
4110 | 4897 | } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { |
4113 | 4900 | * queuing delayed requests, no matter what event caused |
4114 | 4901 | * the transaction to terminate. |
4115 | 4902 | */ |
4116 | ast_debug(3, "Endpoint '%s(%s)' INVITE delay check. tsx-state:%s\n", | |
4117 | ast_sorcery_object_get_id(session->endpoint), | |
4118 | session->channel ? ast_channel_name(session->channel) : "", | |
4903 | ast_debug(3, "%s: INVITE delay check. tsx-state:%s\n", | |
4904 | ast_sip_session_get_name(session), | |
4119 | 4905 | pjsip_tsx_state_str(tsx->state)); |
4120 | 4906 | check_delayed_requests(session, invite_terminated); |
4121 | 4907 | } |
4122 | 4908 | } else if (tsx->role == PJSIP_ROLE_UAC |
4123 | 4909 | && tsx->state == PJSIP_TSX_STATE_COMPLETED |
4124 | 4910 | && !pj_strcmp2(&tsx->method.name, "UPDATE")) { |
4125 | ast_debug(3, "Endpoint '%s(%s)' UPDATE delay check. tsx-state:%s\n", | |
4126 | ast_sorcery_object_get_id(session->endpoint), | |
4127 | session->channel ? ast_channel_name(session->channel) : "", | |
4911 | ast_debug(3, "%s: UPDATE delay check. tsx-state:%s\n", | |
4912 | ast_sip_session_get_name(session), | |
4128 | 4913 | pjsip_tsx_state_str(tsx->state)); |
4129 | 4914 | check_delayed_requests(session, update_completed); |
4130 | 4915 | } |
4260 | 5045 | pjmedia_sdp_session *local; |
4261 | 5046 | int i; |
4262 | 5047 | int stream; |
5048 | SCOPE_ENTER(3, "%s\n", ast_sip_session_get_name(session)); | |
4263 | 5049 | |
4264 | 5050 | if (inv->state == PJSIP_INV_STATE_DISCONNECTED) { |
4265 | ast_log(LOG_ERROR, "Failed to create session SDP. Session has been already disconnected\n"); | |
4266 | return NULL; | |
5051 | SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Failed to create session SDP. Session has been already disconnected\n", | |
5052 | ast_sip_session_get_name(session)); | |
4267 | 5053 | } |
4268 | 5054 | |
4269 | 5055 | if (!inv->pool_prov || !(local = PJ_POOL_ZALLOC_T(inv->pool_prov, pjmedia_sdp_session))) { |
4270 | return NULL; | |
5056 | SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Pool allocation failure\n", ast_sip_session_get_name(session)); | |
4271 | 5057 | } |
4272 | 5058 | |
4273 | 5059 | if (!offer) { |
4292 | 5078 | session->pending_media_state->topology = ast_stream_topology_clone(session->endpoint->media.topology); |
4293 | 5079 | } |
4294 | 5080 | if (!session->pending_media_state->topology) { |
4295 | return NULL; | |
4296 | } | |
4297 | } | |
5081 | SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: No pending media state topology\n", ast_sip_session_get_name(session)); | |
5082 | } | |
5083 | } | |
5084 | ||
5085 | ast_trace(-1, "%s: Processing streams\n", ast_sip_session_get_name(session)); | |
4298 | 5086 | |
4299 | 5087 | for (i = 0; i < ast_stream_topology_get_count(session->pending_media_state->topology); ++i) { |
4300 | 5088 | struct ast_sip_session_media *session_media; |
4301 | struct ast_stream *stream; | |
5089 | struct ast_stream *stream = ast_stream_topology_get_stream(session->pending_media_state->topology, i); | |
4302 | 5090 | unsigned int streams = local->media_count; |
5091 | SCOPE_ENTER(4, "%s: Processing stream %s\n", ast_sip_session_get_name(session), | |
5092 | ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP))); | |
4303 | 5093 | |
4304 | 5094 | /* This code does not enforce any maximum stream count limitations as that is done on either |
4305 | 5095 | * the handling of an incoming SDP offer or on the handling of a session refresh. |
4306 | 5096 | */ |
4307 | 5097 | |
4308 | stream = ast_stream_topology_get_stream(session->pending_media_state->topology, i); | |
4309 | ||
4310 | 5098 | session_media = ast_sip_session_media_state_add(session, session->pending_media_state, ast_stream_get_type(stream), i); |
4311 | 5099 | if (!session_media) { |
4312 | return NULL; | |
5100 | local = NULL; | |
5101 | SCOPE_EXIT_LOG_EXPR(goto end, LOG_ERROR, "%s: Couldn't alloc/add session media for stream %s\n", | |
5102 | ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP))); | |
4313 | 5103 | } |
4314 | 5104 | |
4315 | 5105 | if (add_sdp_streams(session_media, session, local, offer, stream)) { |
4316 | return NULL; | |
5106 | local = NULL; | |
5107 | SCOPE_EXIT_LOG_EXPR(goto end, LOG_ERROR, "%s: Couldn't add sdp streams for stream %s\n", | |
5108 | ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP))); | |
4317 | 5109 | } |
4318 | 5110 | |
4319 | 5111 | /* If a stream was actually added then add any additional details */ |
4327 | 5119 | attr = pjmedia_sdp_attr_create(inv->pool_prov, "mid", pj_cstr(&stmp, session_media->mid)); |
4328 | 5120 | pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr); |
4329 | 5121 | } |
5122 | ||
5123 | ast_trace(-1, "%s: Stream %s added%s%s\n", ast_sip_session_get_name(session), | |
5124 | ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)), | |
5125 | S_COR(!ast_strlen_zero(session_media->mid), " with mid ", ""), S_OR(session_media->mid, "")); | |
5126 | ||
4330 | 5127 | } |
4331 | 5128 | |
4332 | 5129 | /* Ensure that we never exceed the maximum number of streams PJMEDIA will allow. */ |
4333 | 5130 | if (local->media_count == PJMEDIA_MAX_SDP_MEDIA) { |
4334 | break; | |
4335 | } | |
5131 | SCOPE_EXIT_EXPR(break, "%s: Stream %s exceeded max pjmedia count of %d\n", | |
5132 | ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)), | |
5133 | PJMEDIA_MAX_SDP_MEDIA); | |
5134 | } | |
5135 | ||
5136 | SCOPE_EXIT("%s: Done with %s\n", ast_sip_session_get_name(session), | |
5137 | ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP))); | |
5138 | ||
4336 | 5139 | } |
4337 | 5140 | |
4338 | 5141 | /* Add any bundle groups that are present on the media state */ |
5142 | ast_trace(-1, "%s: Adding bundle groups (if available)\n", ast_sip_session_get_name(session)); | |
4339 | 5143 | if (add_bundle_groups(session, inv->pool_prov, local)) { |
4340 | return NULL; | |
5144 | SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Couldn't add bundle groups\n", ast_sip_session_get_name(session)); | |
4341 | 5145 | } |
4342 | 5146 | |
4343 | 5147 | /* Use the connection details of an available media if possible for SDP level */ |
5148 | ast_trace(-1, "%s: Copying connection details\n", ast_sip_session_get_name(session)); | |
5149 | ||
4344 | 5150 | for (stream = 0; stream < local->media_count; stream++) { |
5151 | SCOPE_ENTER(4, "%s: Processing media %d\n", ast_sip_session_get_name(session), stream); | |
4345 | 5152 | if (!local->media[stream]->conn) { |
4346 | continue; | |
5153 | SCOPE_EXIT_EXPR(continue, "%s: Media %d has no connection info\n", ast_sip_session_get_name(session), stream); | |
4347 | 5154 | } |
4348 | 5155 | |
4349 | 5156 | if (local->conn) { |
4352 | 5159 | !pj_strcmp(&local->conn->addr, &local->media[stream]->conn->addr)) { |
4353 | 5160 | local->media[stream]->conn = NULL; |
4354 | 5161 | } |
4355 | continue; | |
5162 | SCOPE_EXIT_EXPR(continue, "%s: Media %d has good existing connection info\n", ast_sip_session_get_name(session), stream); | |
4356 | 5163 | } |
4357 | 5164 | |
4358 | 5165 | /* This stream's connection info will serve as the connection details for SDP level */ |
4359 | 5166 | local->conn = local->media[stream]->conn; |
4360 | 5167 | local->media[stream]->conn = NULL; |
4361 | 5168 | |
4362 | continue; | |
5169 | SCOPE_EXIT_EXPR(continue, "%s: Media %d reset\n", ast_sip_session_get_name(session), stream); | |
4363 | 5170 | } |
4364 | 5171 | |
4365 | 5172 | /* If no SDP level connection details are present then create some */ |
4366 | 5173 | if (!local->conn) { |
5174 | ast_trace(-1, "%s: Creating connection details\n", ast_sip_session_get_name(session)); | |
5175 | ||
4367 | 5176 | local->conn = pj_pool_zalloc(inv->pool_prov, sizeof(struct pjmedia_sdp_conn)); |
4368 | 5177 | local->conn->net_type = STR_IN; |
4369 | 5178 | local->conn->addr_type = session->endpoint->media.rtp.ipv6 ? STR_IP6 : STR_IP4; |
4379 | 5188 | pj_strassign(&local->origin.addr_type, &local->conn->addr_type); |
4380 | 5189 | pj_strassign(&local->origin.addr, &local->conn->addr); |
4381 | 5190 | |
4382 | return local; | |
5191 | end: | |
5192 | SCOPE_EXIT_RTN_VALUE(local, "%s\n", ast_sip_session_get_name(session)); | |
4383 | 5193 | } |
4384 | 5194 | |
4385 | 5195 | static void session_inv_on_rx_offer(pjsip_inv_session *inv, const pjmedia_sdp_session *offer) |
4386 | 5196 | { |
4387 | struct ast_sip_session *session; | |
5197 | struct ast_sip_session *session = inv->mod_data[session_module.id]; | |
4388 | 5198 | pjmedia_sdp_session *answer; |
5199 | SCOPE_ENTER(3, "%s\n", ast_sip_session_get_name(session)); | |
4389 | 5200 | |
4390 | 5201 | if (ast_shutdown_final()) { |
4391 | return; | |
5202 | SCOPE_EXIT_RTN("%s: Shutdown in progress\n", ast_sip_session_get_name(session)); | |
4392 | 5203 | } |
4393 | 5204 | |
4394 | 5205 | session = inv->mod_data[session_module.id]; |
4395 | 5206 | if (handle_incoming_sdp(session, offer)) { |
4396 | 5207 | ast_sip_session_media_state_reset(session->pending_media_state); |
4397 | return; | |
5208 | SCOPE_EXIT_RTN("%s: handle_incoming_sdp failed\n", ast_sip_session_get_name(session)); | |
4398 | 5209 | } |
4399 | 5210 | |
4400 | 5211 | if ((answer = create_local_sdp(inv, session, offer))) { |
4401 | 5212 | pjsip_inv_set_sdp_answer(inv, answer); |
4402 | } | |
5213 | SCOPE_EXIT_RTN("%s: Set SDP answer\n", ast_sip_session_get_name(session)); | |
5214 | } | |
5215 | SCOPE_EXIT_RTN("%s: create_local_sdp failed\n", ast_sip_session_get_name(session)); | |
4403 | 5216 | } |
4404 | 5217 | |
4405 | 5218 | #if 0 |
4411 | 5224 | |
4412 | 5225 | static void session_inv_on_media_update(pjsip_inv_session *inv, pj_status_t status) |
4413 | 5226 | { |
4414 | struct ast_sip_session *session; | |
5227 | struct ast_sip_session *session = inv->mod_data[session_module.id]; | |
4415 | 5228 | const pjmedia_sdp_session *local, *remote; |
5229 | SCOPE_ENTER(3, "%s\n", ast_sip_session_get_name(session)); | |
4416 | 5230 | |
4417 | 5231 | if (ast_shutdown_final()) { |
4418 | return; | |
5232 | SCOPE_EXIT_RTN("%s: Shutdown in progress\n", ast_sip_session_get_name(session)); | |
4419 | 5233 | } |
4420 | 5234 | |
4421 | 5235 | session = inv->mod_data[session_module.id]; |
4425 | 5239 | * don't care about media updates. |
4426 | 5240 | * Just ignore |
4427 | 5241 | */ |
4428 | return; | |
5242 | SCOPE_EXIT_RTN("%s: No channel or session\n", ast_sip_session_get_name(session)); | |
4429 | 5243 | } |
4430 | 5244 | |
4431 | 5245 | if (session->endpoint) { |
4443 | 5257 | |
4444 | 5258 | if (inv->following_fork) { |
4445 | 5259 | if (session->endpoint->media.rtp.follow_early_media_fork) { |
4446 | ast_debug(3, "Following early media fork with different To tags\n"); | |
5260 | ast_trace(-1, "%s: Following early media fork with different To tags\n", ast_sip_session_get_name(session)); | |
4447 | 5261 | } else { |
4448 | ast_debug(3, "Not following early media fork with different To tags\n"); | |
5262 | ast_trace(-1, "%s: Not following early media fork with different To tags\n", ast_sip_session_get_name(session)); | |
4449 | 5263 | bail = 1; |
4450 | 5264 | } |
4451 | 5265 | } |
4452 | 5266 | #ifdef HAVE_PJSIP_INV_ACCEPT_MULTIPLE_SDP_ANSWERS |
4453 | 5267 | else if (inv->updated_sdp_answer) { |
4454 | 5268 | if (session->endpoint->media.rtp.accept_multiple_sdp_answers) { |
4455 | ast_debug(3, "Accepting updated SDP with same To tag\n"); | |
5269 | ast_trace(-1, "%s: Accepting updated SDP with same To tag\n", ast_sip_session_get_name(session)); | |
4456 | 5270 | } else { |
4457 | ast_debug(3, "Ignoring updated SDP answer with same To tag\n"); | |
5271 | ast_trace(-1, "%s: Ignoring updated SDP answer with same To tag\n", ast_sip_session_get_name(session)); | |
4458 | 5272 | bail = 1; |
4459 | 5273 | } |
4460 | 5274 | } |
4461 | 5275 | #endif |
4462 | 5276 | if (bail) { |
4463 | return; | |
5277 | SCOPE_EXIT_RTN("%s: Bailing\n", ast_sip_session_get_name(session)); | |
4464 | 5278 | } |
4465 | 5279 | } |
4466 | 5280 | |
4469 | 5283 | ast_channel_hangupcause_set(session->channel, AST_CAUSE_BEARERCAPABILITY_NOTAVAIL); |
4470 | 5284 | ast_set_hangupsource(session->channel, ast_channel_name(session->channel), 0); |
4471 | 5285 | ast_queue_hangup(session->channel); |
4472 | return; | |
5286 | SCOPE_EXIT_RTN("%s: Couldn't get active or local or remote negotiator. Hanging up\n", ast_sip_session_get_name(session)); | |
4473 | 5287 | } |
4474 | 5288 | |
4475 | 5289 | if (handle_negotiated_sdp(session, local, remote)) { |
4476 | 5290 | ast_sip_session_media_state_reset(session->pending_media_state); |
4477 | } | |
5291 | SCOPE_EXIT_RTN("%s: handle_negotiated_sdp failed. Resetting pending media state\n", ast_sip_session_get_name(session)); | |
5292 | } | |
5293 | SCOPE_EXIT_RTN("%s\n", ast_sip_session_get_name(session)); | |
4478 | 5294 | } |
4479 | 5295 | |
4480 | 5296 | static pjsip_redirect_op session_inv_on_redirected(pjsip_inv_session *inv, const pjsip_uri *target, const pjsip_event *e) |
4544 | 5360 | struct ast_sip_nat_hook *hook = ast_sip_mod_data_get( |
4545 | 5361 | tdata->mod_data, session_module.id, MOD_DATA_NAT_HOOK); |
4546 | 5362 | struct pjmedia_sdp_session *sdp; |
5363 | pjsip_dialog *dlg = pjsip_tdata_get_dlg(tdata); | |
5364 | RAII_VAR(struct ast_sip_session *, session, dlg ? ast_sip_dialog_get_session(dlg) : NULL, ao2_cleanup); | |
4547 | 5365 | int stream; |
4548 | 5366 | |
4549 | 5367 | /* SDP produced by us directly will never be multipart */ |
4567 | 5385 | * outgoing session IP is local. If it is, we'll do |
4568 | 5386 | * rewriting. No localnet configured? Always rewrite. */ |
4569 | 5387 | if (ast_sip_transport_is_local(transport_state, &our_sdp_addr) || !transport_state->localnet) { |
4570 | ast_debug(5, "Setting external media address to %s\n", ast_sockaddr_stringify_addr_remote(&transport_state->external_media_address)); | |
4571 | pj_strdup2(tdata->pool, &sdp->conn->addr, ast_sockaddr_stringify_addr_remote(&transport_state->external_media_address)); | |
5388 | ast_debug(5, "%s: Setting external media address to %s\n", ast_sip_session_get_name(session), | |
5389 | ast_sockaddr_stringify_host(&transport_state->external_media_address)); | |
5390 | pj_strdup2(tdata->pool, &sdp->conn->addr, ast_sockaddr_stringify_host(&transport_state->external_media_address)); | |
4572 | 5391 | pj_strassign(&sdp->origin.addr, &sdp->conn->addr); |
4573 | 5392 | } |
4574 | 5393 | } |
4584 | 5403 | |
4585 | 5404 | handler_list = ao2_find(sdp_handlers, media, OBJ_KEY); |
4586 | 5405 | if (!handler_list) { |
4587 | ast_debug(1, "No registered SDP handlers for media type '%s'\n", media); | |
5406 | ast_debug(4, "%s: No registered SDP handlers for media type '%s'\n", ast_sip_session_get_name(session), | |
5407 | media); | |
4588 | 5408 | continue; |
4589 | 5409 | } |
4590 | 5410 | AST_LIST_TRAVERSE(&handler_list->list, handler, next) { |
4597 | 5417 | /* We purposely do this so that the hook will not be invoked multiple times, ie: if a retransmit occurs */ |
4598 | 5418 | ast_sip_mod_data_set(tdata->pool, tdata->mod_data, session_module.id, MOD_DATA_NAT_HOOK, nat_hook); |
4599 | 5419 | } |
5420 | ||
5421 | #ifdef TEST_FRAMEWORK | |
5422 | ||
5423 | static struct ast_stream *test_stream_alloc(const char *name, enum ast_media_type type, enum ast_stream_state state) | |
5424 | { | |
5425 | struct ast_stream *stream; | |
5426 | ||
5427 | stream = ast_stream_alloc(name, type); | |
5428 | if (!stream) { | |
5429 | return NULL; | |
5430 | } | |
5431 | ast_stream_set_state(stream, state); | |
5432 | ||
5433 | return stream; | |
5434 | } | |
5435 | ||
5436 | static struct ast_sip_session_media *test_media_add( | |
5437 | struct ast_sip_session_media_state *media_state, const char *name, enum ast_media_type type, | |
5438 | enum ast_stream_state state, int position) | |
5439 | { | |
5440 | struct ast_sip_session_media *session_media = NULL; | |
5441 | struct ast_stream *stream = NULL; | |
5442 | ||
5443 | stream = test_stream_alloc(name, type, state); | |
5444 | if (!stream) { | |
5445 | return NULL; | |
5446 | } | |
5447 | ||
5448 | if (position >= 0 && position < ast_stream_topology_get_count(media_state->topology)) { | |
5449 | ast_stream_topology_set_stream(media_state->topology, position, stream); | |
5450 | } else { | |
5451 | position = ast_stream_topology_append_stream(media_state->topology, stream); | |
5452 | } | |
5453 | ||
5454 | session_media = ao2_alloc_options(sizeof(*session_media), session_media_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK); | |
5455 | if (!session_media) { | |
5456 | return NULL; | |
5457 | } | |
5458 | ||
5459 | session_media->keepalive_sched_id = -1; | |
5460 | session_media->timeout_sched_id = -1; | |
5461 | session_media->type = type; | |
5462 | session_media->stream_num = position; | |
5463 | session_media->bundle_group = -1; | |
5464 | strcpy(session_media->label, name); | |
5465 | ||
5466 | if (AST_VECTOR_REPLACE(&media_state->sessions, position, session_media)) { | |
5467 | ao2_ref(session_media, -1); | |
5468 | ||
5469 | return NULL; | |
5470 | } | |
5471 | ||
5472 | /* If this stream will be active in some way and it is the first of this type then consider this the default media session to match */ | |
5473 | if (!media_state->default_session[type] && ast_stream_get_state(ast_stream_topology_get_stream(media_state->topology, position)) != AST_STREAM_STATE_REMOVED) { | |
5474 | media_state->default_session[type] = session_media; | |
5475 | } | |
5476 | ||
5477 | return session_media; | |
5478 | } | |
5479 | ||
5480 | static int test_is_media_session_equal(struct ast_sip_session_media *left, struct ast_sip_session_media *right) | |
5481 | { | |
5482 | if (left == right) { | |
5483 | return 1; | |
5484 | } | |
5485 | ||
5486 | if (!left) { | |
5487 | return 1; | |
5488 | } | |
5489 | ||
5490 | if (!right) { | |
5491 | return 0; | |
5492 | } | |
5493 | return memcmp(left, right, sizeof(*left)) == 0; | |
5494 | } | |
5495 | ||
5496 | static int test_is_media_state_equal(struct ast_sip_session_media_state *left, struct ast_sip_session_media_state *right, | |
5497 | int assert_on_failure) | |
5498 | { | |
5499 | int i; | |
5500 | SCOPE_ENTER(2); | |
5501 | ||
5502 | if (left == right) { | |
5503 | SCOPE_EXIT_RTN_VALUE(1, "equal\n"); | |
5504 | } | |
5505 | ||
5506 | if (!(left && right)) { | |
5507 | ast_assert(!assert_on_failure); | |
5508 | SCOPE_EXIT_RTN_VALUE(0, "one is null: left: %p right: %p\n", left, right); | |
5509 | } | |
5510 | ||
5511 | if (!ast_stream_topology_equal(left->topology, right->topology)) { | |
5512 | ast_assert(!assert_on_failure); | |
5513 | SCOPE_EXIT_RTN_VALUE(0, "topologies differ\n"); | |
5514 | } | |
5515 | if (AST_VECTOR_SIZE(&left->sessions) != AST_VECTOR_SIZE(&right->sessions)) { | |
5516 | ast_assert(!assert_on_failure); | |
5517 | SCOPE_EXIT_RTN_VALUE(0, "session vector sizes different: left %lu != right %lu\n", AST_VECTOR_SIZE(&left->sessions), | |
5518 | AST_VECTOR_SIZE(&right->sessions)); | |
5519 | } | |
5520 | if (AST_VECTOR_SIZE(&left->read_callbacks) != AST_VECTOR_SIZE(&right->read_callbacks)) { | |
5521 | ast_assert(!assert_on_failure); | |
5522 | SCOPE_EXIT_RTN_VALUE(0, "read_callback vector sizes different: left %lu != right %lu\n", AST_VECTOR_SIZE(&left->read_callbacks), | |
5523 | AST_VECTOR_SIZE(&right->read_callbacks)); | |
5524 | } | |
5525 | ||
5526 | for (i = 0; i < AST_VECTOR_SIZE(&left->sessions) ; i++) { | |
5527 | if (!test_is_media_session_equal(AST_VECTOR_GET(&left->sessions, i), AST_VECTOR_GET(&right->sessions, i))) { | |
5528 | ast_assert(!assert_on_failure); | |
5529 | SCOPE_EXIT_RTN_VALUE(0, "Media session %d different\n", i); | |
5530 | } | |
5531 | } | |
5532 | ||
5533 | for (i = 0; i < AST_VECTOR_SIZE(&left->read_callbacks) ; i++) { | |
5534 | if (memcmp(AST_VECTOR_GET_ADDR(&left->read_callbacks, i), | |
5535 | AST_VECTOR_GET_ADDR(&right->read_callbacks, i), | |
5536 | sizeof(struct ast_sip_session_media_read_callback_state)) != 0) { | |
5537 | ast_assert(!assert_on_failure); | |
5538 | SCOPE_EXIT_RTN_VALUE(0, "read_callback %d different\n", i); | |
5539 | } | |
5540 | } | |
5541 | ||
5542 | for (i = 0; i < AST_MEDIA_TYPE_END; i++) { | |
5543 | if (!(left->default_session[i] && right->default_session[i])) { | |
5544 | continue; | |
5545 | } | |
5546 | if (!left->default_session[i] || !right->default_session[i] | |
5547 | || left->default_session[i]->stream_num != right->default_session[i]->stream_num) { | |
5548 | ast_assert(!assert_on_failure); | |
5549 | SCOPE_EXIT_RTN_VALUE(0, "Default media session %d different. Left: %s Right: %s\n", i, | |
5550 | left->default_session[i] ? left->default_session[i]->label : "null", | |
5551 | right->default_session[i] ? right->default_session[i]->label : "null"); | |
5552 | } | |
5553 | } | |
5554 | ||
5555 | SCOPE_EXIT_RTN_VALUE(1, "equal\n"); | |
5556 | } | |
5557 | ||
5558 | AST_TEST_DEFINE(test_resolve_refresh_media_states) | |
5559 | { | |
5560 | #define FREE_STATE() \ | |
5561 | ({ \ | |
5562 | ast_sip_session_media_state_free(new_pending_state); \ | |
5563 | new_pending_state = NULL; \ | |
5564 | ast_sip_session_media_state_free(delayed_pending_state); \ | |
5565 | delayed_pending_state = NULL; \ | |
5566 | ast_sip_session_media_state_free(delayed_active_state); \ | |
5567 | delayed_active_state = NULL; \ | |
5568 | ast_sip_session_media_state_free(current_active_state); \ | |
5569 | current_active_state = NULL; \ | |
5570 | ast_sip_session_media_state_free(expected_pending_state); \ | |
5571 | expected_pending_state = NULL; \ | |
5572 | }) | |
5573 | ||
5574 | #define RESET_STATE(__num) \ | |
5575 | ({ \ | |
5576 | testnum=__num; \ | |
5577 | ast_trace(-1, "Test %d\n", testnum); \ | |
5578 | test_failed = 0; \ | |
5579 | delayed_pending_state = ast_sip_session_media_state_alloc(); \ | |
5580 | delayed_pending_state->topology = ast_stream_topology_alloc(); \ | |
5581 | delayed_active_state = ast_sip_session_media_state_alloc(); \ | |
5582 | delayed_active_state->topology = ast_stream_topology_alloc(); \ | |
5583 | current_active_state = ast_sip_session_media_state_alloc(); \ | |
5584 | current_active_state->topology = ast_stream_topology_alloc(); \ | |
5585 | expected_pending_state = ast_sip_session_media_state_alloc(); \ | |
5586 | expected_pending_state->topology = ast_stream_topology_alloc(); \ | |
5587 | }) | |
5588 | ||
5589 | #define CHECKER() \ | |
5590 | ({ \ | |
5591 | new_pending_state = resolve_refresh_media_states("unittest", delayed_pending_state, delayed_active_state, current_active_state, 1); \ | |
5592 | if (!test_is_media_state_equal(new_pending_state, expected_pending_state, 0)) { \ | |
5593 | res = AST_TEST_FAIL; \ | |
5594 | test_failed = 1; \ | |
5595 | ast_test_status_update(test, "da: %s\n", ast_str_tmp(256, ast_stream_topology_to_str(delayed_active_state->topology, &STR_TMP))); \ | |
5596 | ast_test_status_update(test, "dp: %s\n", ast_str_tmp(256, ast_stream_topology_to_str(delayed_pending_state->topology, &STR_TMP))); \ | |
5597 | ast_test_status_update(test, "ca: %s\n", ast_str_tmp(256, ast_stream_topology_to_str(current_active_state->topology, &STR_TMP))); \ | |
5598 | ast_test_status_update(test, "ep: %s\n", ast_str_tmp(256, ast_stream_topology_to_str(expected_pending_state->topology, &STR_TMP))); \ | |
5599 | ast_test_status_update(test, "np: %s\n", ast_str_tmp(256, ast_stream_topology_to_str(new_pending_state->topology, &STR_TMP))); \ | |
5600 | } \ | |
5601 | ast_test_status_update(test, "Test %d %s\n", testnum, test_failed ? "FAILED" : "passed"); \ | |
5602 | ast_trace(-1, "Test %d %s\n", testnum, test_failed ? "FAILED" : "passed"); \ | |
5603 | test_failed = 0; \ | |
5604 | FREE_STATE(); \ | |
5605 | }) | |
5606 | ||
5607 | ||
5608 | struct ast_sip_session_media_state * delayed_pending_state = NULL; | |
5609 | struct ast_sip_session_media_state * delayed_active_state = NULL; | |
5610 | struct ast_sip_session_media_state * current_active_state = NULL; | |
5611 | struct ast_sip_session_media_state * new_pending_state = NULL; | |
5612 | struct ast_sip_session_media_state * expected_pending_state = NULL; | |
5613 | enum ast_test_result_state res = AST_TEST_PASS; | |
5614 | int test_failed = 0; | |
5615 | int testnum = 0; | |
5616 | SCOPE_ENTER(1); | |
5617 | ||
5618 | switch (cmd) { | |
5619 | case TEST_INIT: | |
5620 | info->name = "merge_refresh_topologies"; | |
5621 | info->category = "/res/res_pjsip_session/"; | |
5622 | info->summary = "Test merging of delayed request topologies"; | |
5623 | info->description = "Test merging of delayed request topologies"; | |
5624 | SCOPE_EXIT_RTN_VALUE(AST_TEST_NOT_RUN); | |
5625 | case TEST_EXECUTE: | |
5626 | break; | |
5627 | } | |
5628 | ||
5629 | RESET_STATE(1); | |
5630 | test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5631 | test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5632 | test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5633 | ||
5634 | test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5635 | test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5636 | test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5637 | test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5638 | ||
5639 | test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5640 | test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5641 | test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5642 | ||
5643 | test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5644 | test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5645 | test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5646 | test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5647 | CHECKER(); | |
5648 | ||
5649 | RESET_STATE(2); | |
5650 | test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5651 | test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5652 | test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5653 | ||
5654 | test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5655 | test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5656 | test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5657 | test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5658 | ||
5659 | test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5660 | test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5661 | test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5662 | ||
5663 | test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5664 | test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5665 | test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5666 | test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5667 | CHECKER(); | |
5668 | ||
5669 | RESET_STATE(3); | |
5670 | test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5671 | test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5672 | test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5673 | ||
5674 | test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5675 | test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5676 | test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5677 | test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5678 | ||
5679 | test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5680 | test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5681 | test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5682 | test_media_add(current_active_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5683 | test_media_add(current_active_state, "myvideo5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5684 | ||
5685 | test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5686 | test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5687 | test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5688 | test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5689 | test_media_add(expected_pending_state, "myvideo5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5690 | test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5691 | CHECKER(); | |
5692 | ||
5693 | RESET_STATE(4); | |
5694 | test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5695 | test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5696 | test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5697 | ||
5698 | test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5699 | test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5700 | test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5701 | test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5702 | ||
5703 | test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5704 | test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5705 | test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); | |
5706 | ||
5707 | test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5708 | test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5709 | test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5710 | CHECKER(); | |
5711 | ||
5712 | RESET_STATE(5); | |
5713 | test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5714 | test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5715 | test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5716 | ||
5717 | test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5718 | test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5719 | test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); | |
5720 | ||
5721 | test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5722 | test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5723 | test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); | |
5724 | ||
5725 | test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5726 | test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5727 | test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); | |
5728 | CHECKER(); | |
5729 | ||
5730 | RESET_STATE(6); | |
5731 | test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5732 | test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5733 | test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5734 | ||
5735 | test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5736 | test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5737 | test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5738 | test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5739 | ||
5740 | test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5741 | test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5742 | test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); | |
5743 | test_media_add(current_active_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5744 | ||
5745 | test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5746 | test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5747 | test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5748 | test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5749 | CHECKER(); | |
5750 | ||
5751 | RESET_STATE(7); | |
5752 | test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5753 | test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5754 | test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5755 | ||
5756 | test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5757 | test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5758 | test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5759 | test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5760 | test_media_add(delayed_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5761 | ||
5762 | test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5763 | test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5764 | test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5765 | test_media_add(current_active_state, "myvideo5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5766 | test_media_add(current_active_state, "myvideo6", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5767 | ||
5768 | test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5769 | test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5770 | test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5771 | test_media_add(expected_pending_state, "myvideo5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5772 | test_media_add(expected_pending_state, "myvideo6", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5773 | test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5774 | test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5775 | CHECKER(); | |
5776 | ||
5777 | RESET_STATE(8); | |
5778 | test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5779 | test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5780 | test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5781 | ||
5782 | test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5783 | test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5784 | test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5785 | test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5786 | test_media_add(delayed_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5787 | ||
5788 | test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5789 | test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5790 | test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); | |
5791 | ||
5792 | test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5793 | test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5794 | test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5795 | test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5796 | CHECKER(); | |
5797 | ||
5798 | RESET_STATE(9); | |
5799 | test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5800 | test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5801 | test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5802 | ||
5803 | test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5804 | test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5805 | test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5806 | test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5807 | test_media_add(delayed_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5808 | ||
5809 | test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5810 | test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); | |
5811 | test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); | |
5812 | ||
5813 | test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5814 | test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5815 | test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5816 | CHECKER(); | |
5817 | ||
5818 | RESET_STATE(10); | |
5819 | test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5820 | test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5821 | test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5822 | ||
5823 | test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5824 | test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); | |
5825 | test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); | |
5826 | ||
5827 | test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5828 | test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5829 | test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5830 | test_media_add(current_active_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5831 | ||
5832 | test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5833 | test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); | |
5834 | test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); | |
5835 | test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5836 | CHECKER(); | |
5837 | ||
5838 | RESET_STATE(11); | |
5839 | test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5840 | test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5841 | test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5842 | test_media_add(delayed_active_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5843 | ||
5844 | test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5845 | test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5846 | test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5847 | test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5848 | ||
5849 | test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5850 | test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5851 | test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5852 | test_media_add(current_active_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5853 | ||
5854 | test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5855 | test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5856 | test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5857 | test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5858 | test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5859 | CHECKER(); | |
5860 | ||
5861 | RESET_STATE(12); | |
5862 | test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5863 | test_media_add(delayed_active_state, "292-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5864 | test_media_add(delayed_active_state, "296-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5865 | ||
5866 | test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5867 | test_media_add(delayed_pending_state, "292-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5868 | test_media_add(delayed_pending_state, "296-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5869 | test_media_add(delayed_pending_state, "297-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5870 | test_media_add(delayed_pending_state, "294-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5871 | ||
5872 | test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5873 | test_media_add(current_active_state, "292-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5874 | test_media_add(current_active_state, "296-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5875 | test_media_add(current_active_state, "290-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5876 | test_media_add(current_active_state, "297-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5877 | ||
5878 | test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5879 | test_media_add(expected_pending_state, "292-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5880 | test_media_add(expected_pending_state, "296-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5881 | test_media_add(expected_pending_state, "290-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5882 | test_media_add(expected_pending_state, "297-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5883 | test_media_add(expected_pending_state, "294-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5884 | CHECKER(); | |
5885 | ||
5886 | RESET_STATE(13); | |
5887 | test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5888 | test_media_add(delayed_active_state, "293-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5889 | test_media_add(delayed_active_state, "292-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5890 | test_media_add(delayed_active_state, "294-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5891 | test_media_add(delayed_active_state, "295-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5892 | test_media_add(delayed_active_state, "296-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5893 | ||
5894 | test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5895 | test_media_add(delayed_pending_state, "293-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5896 | test_media_add(delayed_pending_state, "292-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5897 | test_media_add(delayed_pending_state, "294-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5898 | test_media_add(delayed_pending_state, "295-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5899 | test_media_add(delayed_pending_state, "296-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5900 | test_media_add(delayed_pending_state, "298-7", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5901 | ||
5902 | test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5903 | test_media_add(current_active_state, "293-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5904 | test_media_add(current_active_state, "292-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5905 | test_media_add(current_active_state, "294-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5906 | test_media_add(current_active_state, "295-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5907 | test_media_add(current_active_state, "296-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5908 | test_media_add(current_active_state, "290-6", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5909 | ||
5910 | test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5911 | test_media_add(expected_pending_state, "293-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5912 | test_media_add(expected_pending_state, "292-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5913 | test_media_add(expected_pending_state, "294-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5914 | test_media_add(expected_pending_state, "295-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5915 | test_media_add(expected_pending_state, "296-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5916 | test_media_add(expected_pending_state, "290-6", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5917 | test_media_add(expected_pending_state, "298-7", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5918 | CHECKER(); | |
5919 | ||
5920 | RESET_STATE(14); | |
5921 | test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5922 | test_media_add(delayed_active_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5923 | test_media_add(delayed_active_state, "297-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5924 | ||
5925 | test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5926 | test_media_add(delayed_pending_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5927 | test_media_add(delayed_pending_state, "294-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5928 | test_media_add(delayed_pending_state, "295-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5929 | ||
5930 | test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5931 | test_media_add(current_active_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5932 | test_media_add(current_active_state, "297-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5933 | test_media_add(current_active_state, "291-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5934 | test_media_add(current_active_state, "294-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5935 | ||
5936 | test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5937 | test_media_add(expected_pending_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5938 | test_media_add(expected_pending_state, "297-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5939 | test_media_add(expected_pending_state, "291-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5940 | test_media_add(expected_pending_state, "294-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5941 | test_media_add(expected_pending_state, "295-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5942 | CHECKER(); | |
5943 | ||
5944 | RESET_STATE(15); | |
5945 | test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5946 | test_media_add(delayed_active_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5947 | test_media_add(delayed_active_state, "297-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5948 | ||
5949 | test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5950 | test_media_add(delayed_pending_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDONLY, -1); | |
5951 | test_media_add(delayed_pending_state, "294-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5952 | test_media_add(delayed_pending_state, "295-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5953 | ||
5954 | test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5955 | test_media_add(current_active_state, "297-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5956 | test_media_add(current_active_state, "291-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5957 | test_media_add(current_active_state, "294-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5958 | test_media_add(current_active_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5959 | ||
5960 | test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); | |
5961 | test_media_add(expected_pending_state, "297-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5962 | test_media_add(expected_pending_state, "291-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5963 | test_media_add(expected_pending_state, "294-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5964 | test_media_add(expected_pending_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDONLY, -1); | |
5965 | test_media_add(expected_pending_state, "295-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); | |
5966 | CHECKER(); | |
5967 | ||
5968 | SCOPE_EXIT_RTN_VALUE(res); | |
5969 | } | |
5970 | #endif /* TEST_FRAMEWORK */ | |
4600 | 5971 | |
4601 | 5972 | static int load_module(void) |
4602 | 5973 | { |
4626 | 5997 | ast_sip_register_service(&outbound_invite_auth_module); |
4627 | 5998 | |
4628 | 5999 | ast_module_shutdown_ref(ast_module_info->self); |
4629 | ||
6000 | #ifdef TEST_FRAMEWORK | |
6001 | AST_TEST_REGISTER(test_resolve_refresh_media_states); | |
6002 | #endif | |
4630 | 6003 | return AST_MODULE_LOAD_SUCCESS; |
4631 | 6004 | } |
4632 | 6005 | |
4633 | 6006 | static int unload_module(void) |
4634 | 6007 | { |
6008 | #ifdef TEST_FRAMEWORK | |
6009 | AST_TEST_UNREGISTER(test_resolve_refresh_media_states); | |
6010 | #endif | |
4635 | 6011 | ast_sip_unregister_service(&outbound_invite_auth_module); |
4636 | 6012 | ast_sip_unregister_service(&session_reinvite_module); |
4637 | 6013 | ast_sip_unregister_service(&session_module); |
0 | /* | |
1 | * Asterisk -- An open source telephony toolkit. | |
2 | * | |
3 | * Copyright (C) 2020, Sangoma Technologies Corporation | |
4 | * | |
5 | * Ben Ford <bford@sangoma.com> | |
6 | * | |
7 | * See http://www.asterisk.org for more information about | |
8 | * the Asterisk project. Please do not directly contact | |
9 | * any of the maintainers of this project for assistance; | |
10 | * the project provides a web site, mailing lists and IRC | |
11 | * channels for your use. | |
12 | * | |
13 | * This program is free software, distributed under the terms of | |
14 | * the GNU General Public License Version 2. See the LICENSE file | |
15 | * at the top of the source tree. | |
16 | */ | |
17 | ||
18 | /*** MODULEINFO | |
19 | <depend>pjproject</depend> | |
20 | <depend>res_pjsip</depend> | |
21 | <depend>res_pjsip_session</depend> | |
22 | <depend>res_stir_shaken</depend> | |
23 | <support_level>core</support_level> | |
24 | ***/ | |
25 | ||
26 | #include "asterisk.h" | |
27 | ||
28 | #include <pjsip.h> | |
29 | #include <pjsip_ua.h> | |
30 | ||
31 | #include "asterisk/res_pjsip.h" | |
32 | #include "asterisk/res_pjsip_session.h" | |
33 | #include "asterisk/module.h" | |
34 | ||
35 | #include "asterisk/res_stir_shaken.h" | |
36 | ||
37 | /*! | |
38 | * \brief Get the attestation from the payload | |
39 | * | |
40 | * \param json_str The JSON string representation of the payload | |
41 | * | |
42 | * \retval Empty string on failure | |
43 | * \retval The attestation on success | |
44 | */ | |
45 | static char *get_attestation_from_payload(const char *json_str) | |
46 | { | |
47 | RAII_VAR(struct ast_json *, json, NULL, ast_json_free); | |
48 | char *attestation; | |
49 | ||
50 | json = ast_json_load_string(json_str, NULL); | |
51 | attestation = (char *)ast_json_string_get(ast_json_object_get(json, "attest")); | |
52 | ||
53 | if (!ast_strlen_zero(attestation)) { | |
54 | return attestation; | |
55 | } | |
56 | ||
57 | return ""; | |
58 | } | |
59 | ||
60 | /*! | |
61 | * \brief Compare the caller ID from the INVITE with the one in the payload | |
62 | * | |
63 | * \param json_str The JSON string represntation of the payload | |
64 | * | |
65 | * \retval -1 on failure | |
66 | * \retval 0 on success | |
67 | */ | |
68 | static int compare_caller_id(char *caller_id, const char *json_str) | |
69 | { | |
70 | RAII_VAR(struct ast_json *, json, NULL, ast_json_free); | |
71 | char *caller_id_other; | |
72 | ||
73 | json = ast_json_load_string(json_str, NULL); | |
74 | caller_id_other = (char *)ast_json_string_get(ast_json_object_get( | |
75 | ast_json_object_get(json, "orig"), "tn")); | |
76 | ||
77 | if (strcmp(caller_id, caller_id_other)) { | |
78 | return -1; | |
79 | } | |
80 | ||
81 | return 0; | |
82 | } | |
83 | ||
84 | /*! | |
85 | * \brief Compare the current timestamp with the one in the payload. If the difference | |
86 | * is greater than the signature timeout, it's not valid anymore | |
87 | * | |
88 | * \param json_str The JSON string representation of the payload | |
89 | * | |
90 | * \retval -1 on failure | |
91 | * \retval 0 on success | |
92 | */ | |
93 | static int compare_timestamp(const char *json_str) | |
94 | { | |
95 | RAII_VAR(struct ast_json *, json, NULL, ast_json_free); | |
96 | long int timestamp; | |
97 | struct timeval now = ast_tvnow(); | |
98 | ||
99 | #ifdef TEST_FRAMEWORK | |
100 | ast_debug(3, "Ignoring STIR/SHAKEN timestamp\n"); | |
101 | return 0; | |
102 | #endif | |
103 | ||
104 | json = ast_json_load_string(json_str, NULL); | |
105 | timestamp = ast_json_integer_get(ast_json_object_get(json, "iat")); | |
106 | ||
107 | if (now.tv_sec - timestamp > ast_stir_shaken_get_signature_timeout()) { | |
108 | return -1; | |
109 | } | |
110 | ||
111 | return 0; | |
112 | } | |
113 | ||
114 | /*! | |
115 | * \internal | |
116 | * \brief Session supplement callback on an incoming INVITE request | |
117 | * | |
118 | * When we receive an INVITE, check it for STIR/SHAKEN information and | |
119 | * decide what to do from there | |
120 | * | |
121 | * \param session The session that has received an INVITE | |
122 | * \param rdata The incoming INVITE | |
123 | */ | |
124 | static int stir_shaken_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata) | |
125 | { | |
126 | static const pj_str_t identity_str = { "Identity", 8 }; | |
127 | char *identity_hdr_val; | |
128 | char *encoded_val; | |
129 | struct ast_channel *chan = session->channel; | |
130 | char *caller_id = session->id.number.str; | |
131 | RAII_VAR(char *, header, NULL, ast_free); | |
132 | RAII_VAR(char *, payload, NULL, ast_free); | |
133 | char *signature; | |
134 | char *algorithm; | |
135 | char *public_key_url; | |
136 | char *attestation; | |
137 | int mismatch = 0; | |
138 | struct ast_stir_shaken_payload *ss_payload; | |
139 | ||
140 | if (!session->endpoint->stir_shaken) { | |
141 | return 0; | |
142 | } | |
143 | ||
144 | identity_hdr_val = ast_sip_rdata_get_header_value(rdata, identity_str); | |
145 | if (ast_strlen_zero(identity_hdr_val)) { | |
146 | ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_NOT_PRESENT); | |
147 | return 0; | |
148 | } | |
149 | ||
150 | encoded_val = strtok_r(identity_hdr_val, ".", &identity_hdr_val); | |
151 | header = ast_base64decode_string(encoded_val); | |
152 | if (ast_strlen_zero(header)) { | |
153 | ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED); | |
154 | return 0; | |
155 | } | |
156 | ||
157 | encoded_val = strtok_r(identity_hdr_val, ".", &identity_hdr_val); | |
158 | payload = ast_base64decode_string(encoded_val); | |
159 | if (ast_strlen_zero(payload)) { | |
160 | ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED); | |
161 | return 0; | |
162 | } | |
163 | ||
164 | /* It's fine to leave the signature encoded */ | |
165 | signature = strtok_r(identity_hdr_val, ";", &identity_hdr_val); | |
166 | if (ast_strlen_zero(signature)) { | |
167 | ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED); | |
168 | return 0; | |
169 | } | |
170 | ||
171 | /* Trim "info=<" to get public key URL */ | |
172 | strtok_r(identity_hdr_val, "<", &identity_hdr_val); | |
173 | public_key_url = strtok_r(identity_hdr_val, ">", &identity_hdr_val); | |
174 | if (ast_strlen_zero(public_key_url)) { | |
175 | ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED); | |
176 | return 0; | |
177 | } | |
178 | ||
179 | algorithm = strtok_r(identity_hdr_val, ";", &identity_hdr_val); | |
180 | if (ast_strlen_zero(algorithm)) { | |
181 | ast_stir_shaken_add_verification(chan, caller_id, "", AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED); | |
182 | return 0; | |
183 | } | |
184 | ||
185 | attestation = get_attestation_from_payload(payload); | |
186 | ||
187 | ss_payload = ast_stir_shaken_verify(header, payload, signature, algorithm, public_key_url); | |
188 | if (!ss_payload) { | |
189 | ast_stir_shaken_add_verification(chan, caller_id, attestation, AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED); | |
190 | return 0; | |
191 | } | |
192 | ast_stir_shaken_payload_free(ss_payload); | |
193 | ||
194 | mismatch |= compare_caller_id(caller_id, payload); | |
195 | mismatch |= compare_timestamp(payload); | |
196 | ||
197 | if (mismatch) { | |
198 | ast_stir_shaken_add_verification(chan, caller_id, attestation, AST_STIR_SHAKEN_VERIFY_MISMATCH); | |
199 | return 0; | |
200 | } | |
201 | ||
202 | ast_stir_shaken_add_verification(chan, caller_id, attestation, AST_STIR_SHAKEN_VERIFY_PASSED); | |
203 | ||
204 | return 0; | |
205 | } | |
206 | ||
207 | static void add_identity_header(const struct ast_sip_session *session, pjsip_tx_data *tdata) | |
208 | { | |
209 | static const pj_str_t identity_str = { "Identity", 8 }; | |
210 | pjsip_generic_string_hdr *identity_hdr; | |
211 | pj_str_t identity_val; | |
212 | pjsip_fromto_hdr *old_identity; | |
213 | char *signature; | |
214 | char *public_key_url; | |
215 | struct ast_json *header; | |
216 | struct ast_json *payload; | |
217 | char *dumped_string; | |
218 | RAII_VAR(struct ast_json *, json, NULL, ast_json_free); | |
219 | RAII_VAR(struct ast_stir_shaken_payload *, ss_payload, NULL, ast_stir_shaken_payload_free); | |
220 | RAII_VAR(char *, encoded_header, NULL, ast_free); | |
221 | RAII_VAR(char *, encoded_payload, NULL, ast_free); | |
222 | RAII_VAR(char *, combined_str, NULL, ast_free); | |
223 | size_t combined_size; | |
224 | ||
225 | old_identity = pjsip_msg_find_hdr_by_name(tdata->msg, &identity_str, NULL); | |
226 | if (old_identity) { | |
227 | return; | |
228 | } | |
229 | ||
230 | /* x5u (public key URL), attestation, and origid will be added by ast_stir_shaken_sign */ | |
231 | json = ast_json_pack("{s: {s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg", "ES256", "ppt", "shaken", "typ", "passport", | |
232 | "payload", "orig", "tn", session->id.number.str); | |
233 | if (!json) { | |
234 | ast_log(LOG_ERROR, "Failed to allocate memory for STIR/SHAKEN JSON\n"); | |
235 | return; | |
236 | } | |
237 | ||
238 | ss_payload = ast_stir_shaken_sign(json); | |
239 | if (!ss_payload) { | |
240 | ast_log(LOG_ERROR, "Failed to allocate memory for STIR/SHAKEN payload\n"); | |
241 | return; | |
242 | } | |
243 | ||
244 | header = ast_json_object_get(json, "header"); | |
245 | dumped_string = ast_json_dump_string(header); | |
246 | encoded_header = ast_base64encode_string(dumped_string); | |
247 | ast_json_free(dumped_string); | |
248 | if (!encoded_header) { | |
249 | ast_log(LOG_ERROR, "Failed to encode STIR/SHAKEN header\n"); | |
250 | return; | |
251 | } | |
252 | ||
253 | payload = ast_json_object_get(json, "payload"); | |
254 | dumped_string = ast_json_dump_string(payload); | |
255 | encoded_payload = ast_base64encode_string(dumped_string); | |
256 | ast_json_free(dumped_string); | |
257 | if (!encoded_payload) { | |
258 | ast_log(LOG_ERROR, "Failed to encode STIR/SHAKEN payload\n"); | |
259 | return; | |
260 | } | |
261 | ||
262 | signature = (char *)ast_stir_shaken_payload_get_signature(ss_payload); | |
263 | public_key_url = ast_stir_shaken_payload_get_public_key_url(ss_payload); | |
264 | ||
265 | /* The format for the identity header: | |
266 | * header.payload.signature;info=<public_key_url>alg=STIR_SHAKEN_ENCRYPTION_ALGORITHM;ppt=STIR_SHAKEN_PPT | |
267 | */ | |
268 | combined_size = strlen(encoded_header) + 1 + strlen(encoded_payload) + 1 | |
269 | + strlen(signature) + strlen(";info=<>alg=;ppt=") + strlen(public_key_url) | |
270 | + strlen(STIR_SHAKEN_ENCRYPTION_ALGORITHM) + strlen(STIR_SHAKEN_PPT) + 1; | |
271 | combined_str = ast_calloc(1, combined_size); | |
272 | if (!combined_str) { | |
273 | ast_log(LOG_ERROR, "Failed to allocate memory for STIR/SHAKEN identity string\n"); | |
274 | return; | |
275 | } | |
276 | snprintf(combined_str, combined_size, "%s.%s.%s;info=<%s>alg=%s;ppt=%s", encoded_header, | |
277 | encoded_payload, signature, public_key_url, STIR_SHAKEN_ENCRYPTION_ALGORITHM, STIR_SHAKEN_PPT); | |
278 | ||
279 | identity_val = pj_str(combined_str); | |
280 | identity_hdr = pjsip_generic_string_hdr_create(tdata->pool, &identity_str, &identity_val); | |
281 | if (!identity_hdr) { | |
282 | ast_log(LOG_ERROR, "Failed to create STIR/SHAKEN Identity header\n"); | |
283 | return; | |
284 | } | |
285 | ||
286 | pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)identity_hdr); | |
287 | } | |
288 | ||
289 | static void stir_shaken_outgoing_request(struct ast_sip_session *session, pjsip_tx_data *tdata) | |
290 | { | |
291 | if (!session->endpoint->stir_shaken) { | |
292 | return; | |
293 | } | |
294 | ||
295 | if (ast_strlen_zero(session->id.number.str) && session->id.number.valid) { | |
296 | return; | |
297 | } | |
298 | ||
299 | add_identity_header(session, tdata); | |
300 | } | |
301 | ||
302 | static struct ast_sip_session_supplement stir_shaken_supplement = { | |
303 | .method = "INVITE", | |
304 | .priority = AST_SIP_SUPPLEMENT_PRIORITY_CHANNEL + 1, /* Run AFTER channel creation */ | |
305 | .incoming_request = stir_shaken_incoming_request, | |
306 | .outgoing_request = stir_shaken_outgoing_request, | |
307 | }; | |
308 | ||
309 | static int unload_module(void) | |
310 | { | |
311 | ast_sip_session_unregister_supplement(&stir_shaken_supplement); | |
312 | return 0; | |
313 | } | |
314 | ||
315 | static int load_module(void) | |
316 | { | |
317 | ast_sip_session_register_supplement(&stir_shaken_supplement); | |
318 | return AST_MODULE_LOAD_SUCCESS; | |
319 | } | |
320 | ||
321 | #undef AST_BUILDOPT_SUM | |
322 | #define AST_BUILDOPT_SUM "" | |
323 | ||
324 | AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, | |
325 | "PSIP STIR/SHAKEN Module for Asterisk", | |
326 | .support_level = AST_MODULE_SUPPORT_CORE, | |
327 | .load = load_module, | |
328 | .unload = unload_module, | |
329 | .load_pri = AST_MODPRI_DEFAULT, | |
330 | .requires = "res_pjsip,res_stir_shaken", | |
331 | ); |
63 | 63 | #endif |
64 | 64 | |
65 | 65 | #include "asterisk/options.h" |
66 | #include "asterisk/logger_category.h" | |
66 | 67 | #include "asterisk/stun.h" |
67 | 68 | #include "asterisk/pbx.h" |
68 | 69 | #include "asterisk/frame.h" |
109 | 110 | #define MAXIMUM_RTP_RECV_BUFFER_SIZE (DEFAULT_RTP_RECV_BUFFER_SIZE + 20) /*!< Maximum RTP receive buffer size */ |
110 | 111 | #define OLD_PACKET_COUNT 1000 /*!< The number of previous packets that are considered old */ |
111 | 112 | #define MISSING_SEQNOS_ADDED_TRIGGER 2 /*!< The number of immediate missing packets that will trigger an immediate NACK */ |
113 | ||
112 | 114 | #define SEQNO_CYCLE_OVER 65536 /*!< The number after the maximum allowed sequence number */ |
113 | 115 | |
114 | 116 | /*! Full INTRA-frame Request / Fast Update Request (From RFC2032) */ |
188 | 190 | |
189 | 191 | static int rtpstart = DEFAULT_RTP_START; /*!< First port for RTP sessions (set in rtp.conf) */ |
190 | 192 | static int rtpend = DEFAULT_RTP_END; /*!< Last port for RTP sessions (set in rtp.conf) */ |
191 | static int rtpdebug; /*!< Are we debugging? */ | |
192 | static int rtcpdebug; /*!< Are we debugging RTCP? */ | |
193 | 193 | static int rtcpstats; /*!< Are we debugging RTCP? */ |
194 | 194 | static int rtcpinterval = RTCP_DEFAULT_INTERVALMS; /*!< Time between rtcp reports in millisecs */ |
195 | 195 | static struct ast_sockaddr rtpdebugaddr; /*!< Debug packets to/from this host */ |
668 | 668 | int rtcp = 0; |
669 | 669 | struct ast_sockaddr remote_address = { {0, } }; |
670 | 670 | int ice; |
671 | int bytes_sent; | |
671 | 672 | |
672 | 673 | /* OpenSSL can't tolerate a packet not being sent, so we always state that |
673 | 674 | * we sent the packet. If it isn't then retransmission will occur. |
684 | 685 | return len; |
685 | 686 | } |
686 | 687 | |
687 | __rtp_sendto(instance, (char *)buf, len, 0, &remote_address, rtcp, &ice, 0); | |
688 | bytes_sent = __rtp_sendto(instance, (char *)buf, len, 0, &remote_address, rtcp, &ice, 0); | |
689 | ||
690 | if (bytes_sent > 0 && ast_debug_dtls_packet_is_allowed) { | |
691 | ast_debug(0, "(%p) DTLS - sent %s packet to %s%s (len %-6.6d)\n", | |
692 | instance, rtcp ? "RTCP" : "RTP", ast_sockaddr_stringify(&remote_address), | |
693 | ice ? " (via ICE)" : "", bytes_sent); | |
694 | } | |
688 | 695 | |
689 | 696 | return len; |
690 | 697 | } |
816 | 823 | ast_sockaddr_copy(&remote_candidate->relay_address, &candidate->relay_address); |
817 | 824 | remote_candidate->type = candidate->type; |
818 | 825 | |
826 | ast_debug_ice(2, "(%p) ICE add remote candidate\n", instance); | |
827 | ||
819 | 828 | ao2_link(rtp->ice_proposed_remote_candidates, remote_candidate); |
820 | 829 | ao2_ref(remote_candidate, -1); |
821 | 830 | } |
861 | 870 | ao2_unlock(instance); |
862 | 871 | ao2_ref(ice, -1); |
863 | 872 | ao2_lock(instance); |
873 | ast_debug_ice(2, "(%p) ICE stopped\n", instance); | |
864 | 874 | } |
865 | 875 | } |
866 | 876 | |
921 | 931 | struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); |
922 | 932 | int res; |
923 | 933 | |
924 | ast_debug(3, "Resetting ICE for RTP instance '%p'\n", instance); | |
934 | ast_debug_ice(3, "(%p) ICE resetting\n", instance); | |
925 | 935 | if (!rtp->ice->real_ice->is_nominating && !rtp->ice->real_ice->is_complete) { |
926 | ast_debug(3, "Nevermind. ICE isn't ready for a reset\n"); | |
936 | ast_debug_ice(3, " (%p) ICE nevermind, not ready for a reset\n", instance); | |
927 | 937 | return 0; |
928 | 938 | } |
929 | 939 | |
930 | ast_debug(3, "Recreating ICE session %s (%d) for RTP instance '%p'\n", ast_sockaddr_stringify(&rtp->ice_original_rtp_addr), rtp->ice_port, instance); | |
940 | ast_debug_ice(3, "(%p) ICE recreating ICE session %s (%d)\n", | |
941 | instance, ast_sockaddr_stringify(&rtp->ice_original_rtp_addr), rtp->ice_port); | |
931 | 942 | res = ice_create(instance, &rtp->ice_original_rtp_addr, rtp->ice_port, 1); |
932 | 943 | if (!res) { |
933 | 944 | /* Use the current expected role for the ICE session */ |
1003 | 1014 | /* Check for equivalence in the lists */ |
1004 | 1015 | if (rtp->ice_active_remote_candidates && |
1005 | 1016 | !ice_candidates_compare(rtp->ice_proposed_remote_candidates, rtp->ice_active_remote_candidates)) { |
1006 | ast_debug(3, "Proposed == active candidates for RTP instance '%p'\n", instance); | |
1017 | ast_debug_ice(2, "(%p) ICE proposed equals active candidates\n", instance); | |
1007 | 1018 | ao2_cleanup(rtp->ice_proposed_remote_candidates); |
1008 | 1019 | rtp->ice_proposed_remote_candidates = NULL; |
1009 | 1020 | /* If this ICE session is being preserved then go back to the role it currently is */ |
1016 | 1027 | rtp->ice_active_remote_candidates = rtp->ice_proposed_remote_candidates; |
1017 | 1028 | rtp->ice_proposed_remote_candidates = NULL; |
1018 | 1029 | |
1030 | ast_debug_ice(2, "(%p) ICE start\n", instance); | |
1031 | ||
1019 | 1032 | /* Reset the ICE session. Is this going to work? */ |
1020 | 1033 | if (ice_reset_session(instance)) { |
1021 | ast_log(LOG_NOTICE, "Failed to create replacement ICE session\n"); | |
1034 | ast_log(LOG_NOTICE, "(%p) ICE failed to create replacement session\n", instance); | |
1022 | 1035 | return; |
1023 | 1036 | } |
1024 | 1037 | |
1053 | 1066 | } |
1054 | 1067 | |
1055 | 1068 | if (candidate->id == AST_RTP_ICE_COMPONENT_RTP && rtp->turn_rtp) { |
1056 | ast_debug(3, "RTP candidate %s (%p)\n", ast_sockaddr_stringify(&candidate->address), instance); | |
1069 | ast_debug_ice(2, "(%p) ICE RTP candidate %s\n", instance, ast_sockaddr_stringify(&candidate->address)); | |
1057 | 1070 | /* Release the instance lock to avoid deadlock with PJPROJECT group lock */ |
1058 | 1071 | ao2_unlock(instance); |
1059 | 1072 | pj_turn_sock_set_perm(rtp->turn_rtp, 1, &candidates[cand_cnt].addr, 1); |
1060 | 1073 | ao2_lock(instance); |
1061 | 1074 | } else if (candidate->id == AST_RTP_ICE_COMPONENT_RTCP && rtp->turn_rtcp) { |
1062 | ast_debug(3, "RTCP candidate %s (%p)\n", ast_sockaddr_stringify(&candidate->address), instance); | |
1075 | ast_debug_ice(2, "(%p) ICE RTCP candidate %s\n", instance, ast_sockaddr_stringify(&candidate->address)); | |
1063 | 1076 | /* Release the instance lock to avoid deadlock with PJPROJECT group lock */ |
1064 | 1077 | ao2_unlock(instance); |
1065 | 1078 | pj_turn_sock_set_perm(rtp->turn_rtcp, 1, &candidates[cand_cnt].addr, 1); |
1073 | 1086 | ao2_iterator_destroy(&i); |
1074 | 1087 | |
1075 | 1088 | if (cand_cnt < ao2_container_count(rtp->ice_active_remote_candidates)) { |
1076 | ast_log(LOG_WARNING, "Lost %d ICE candidates. Consider increasing PJ_ICE_MAX_CAND in PJSIP (%p)\n", | |
1077 | ao2_container_count(rtp->ice_active_remote_candidates) - cand_cnt, instance); | |
1089 | ast_log(LOG_WARNING, "(%p) ICE lost %d candidates. Consider increasing PJ_ICE_MAX_CAND in PJSIP\n", | |
1090 | instance, ao2_container_count(rtp->ice_active_remote_candidates) - cand_cnt); | |
1078 | 1091 | } |
1079 | 1092 | |
1080 | 1093 | if (!has_rtp) { |
1081 | ast_log(LOG_WARNING, "No RTP candidates; skipping ICE checklist (%p)\n", instance); | |
1094 | ast_log(LOG_WARNING, "(%p) ICE no RTP candidates; skipping checklist\n", instance); | |
1082 | 1095 | } |
1083 | 1096 | |
1084 | 1097 | /* If we're only dealing with one ICE component, then we don't care about the lack of RTCP candidates */ |
1085 | 1098 | if (!has_rtcp && rtp->ice_num_components > 1) { |
1086 | ast_log(LOG_WARNING, "No RTCP candidates; skipping ICE checklist (%p)\n", instance); | |
1099 | ast_log(LOG_WARNING, "(%p) ICE no RTCP candidates; skipping checklist\n", instance); | |
1087 | 1100 | } |
1088 | 1101 | |
1089 | 1102 | if (rtp->ice && has_rtp && (has_rtcp || rtp->ice_num_components == 1)) { |
1097 | 1110 | ao2_unlock(instance); |
1098 | 1111 | res = pj_ice_sess_create_check_list(ice->real_ice, &ufrag, &passwd, cand_cnt, &candidates[0]); |
1099 | 1112 | if (res == PJ_SUCCESS) { |
1100 | ast_debug(3, "Successfully created ICE checklist (%p)\n", instance); | |
1113 | ast_debug_ice(2, "(%p) ICE successfully created checklist\n", instance); | |
1101 | 1114 | ast_test_suite_event_notify("ICECHECKLISTCREATE", "Result: SUCCESS"); |
1102 | 1115 | pj_ice_sess_start_check(ice->real_ice); |
1103 | 1116 | pj_timer_heap_poll(timer_heap, NULL); |
1110 | 1123 | ao2_lock(instance); |
1111 | 1124 | |
1112 | 1125 | pj_strerror(res, reason, sizeof(reason)); |
1113 | ast_log(LOG_WARNING, "Failed to create ICE session check list: %s (%p)\n", reason, instance); | |
1126 | ast_log(LOG_WARNING, "(%p) ICE failed to create session check list: %s\n", instance, reason); | |
1114 | 1127 | } |
1115 | 1128 | |
1116 | 1129 | ast_test_suite_event_notify("ICECHECKLISTCREATE", "Result: FAILURE"); |
1173 | 1186 | { |
1174 | 1187 | struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); |
1175 | 1188 | |
1176 | ast_debug(3, "Set role to %s (%p)\n", | |
1177 | role == AST_RTP_ICE_ROLE_CONTROLLED ? "CONTROLLED" : "CONTROLLING", instance); | |
1178 | ||
1179 | 1189 | if (!rtp->ice) { |
1180 | ast_debug(3, "Set role failed; no ice instance (%p)\n", instance); | |
1190 | ast_debug_ice(3, "(%p) ICE set role failed; no ice instance\n", instance); | |
1181 | 1191 | return; |
1182 | 1192 | } |
1183 | 1193 | |
1185 | 1195 | |
1186 | 1196 | if (!rtp->ice->real_ice->is_nominating && !rtp->ice->real_ice->is_complete) { |
1187 | 1197 | pj_thread_register_check(); |
1188 | ||
1198 | ast_debug_ice(2, "(%p) ICE set role to %s\n", | |
1199 | instance, role == AST_RTP_ICE_ROLE_CONTROLLED ? "CONTROLLED" : "CONTROLLING"); | |
1189 | 1200 | pj_ice_sess_change_role(rtp->ice->real_ice, role == AST_RTP_ICE_ROLE_CONTROLLED ? |
1190 | 1201 | PJ_ICE_SESS_ROLE_CONTROLLED : PJ_ICE_SESS_ROLE_CONTROLLING); |
1191 | 1202 | } else { |
1192 | ast_debug(3, "Not setting ICE role because state is %s\n", rtp->ice->real_ice->is_nominating ? "nominating" : "complete" ); | |
1203 | ast_debug_ice(2, "(%p) ICE not setting role because state is %s\n", | |
1204 | instance, rtp->ice->real_ice->is_nominating ? "nominating" : "complete"); | |
1193 | 1205 | } |
1194 | 1206 | } |
1195 | 1207 | |
1260 | 1272 | ao2_ref(ice, -1); |
1261 | 1273 | ao2_lock(instance); |
1262 | 1274 | if (!rtp->ice || status != PJ_SUCCESS) { |
1275 | ast_debug_ice(2, "(%p) ICE unable to add candidate: %s, %d\n", instance, ast_sockaddr_stringify( | |
1276 | &candidate->address), candidate->priority); | |
1263 | 1277 | ao2_ref(candidate, -1); |
1264 | 1278 | return; |
1265 | 1279 | } |
1266 | 1280 | |
1267 | 1281 | /* By placing the candidate into the ICE session it will have produced the priority, so update the local candidate with it */ |
1268 | 1282 | candidate->priority = rtp->ice->real_ice->lcand[rtp->ice->real_ice->lcand_cnt - 1].prio; |
1283 | ||
1284 | ast_debug_ice(2, "(%p) ICE add candidate: %s, %d\n", instance, ast_sockaddr_stringify( | |
1285 | &candidate->address), candidate->priority); | |
1269 | 1286 | |
1270 | 1287 | ao2_link(rtp->ice_local_candidates, candidate); |
1271 | 1288 | ao2_ref(candidate, -1); |
1291 | 1308 | char buf[100]; |
1292 | 1309 | |
1293 | 1310 | pj_strerror(status, buf, sizeof(buf)); |
1294 | ast_log(LOG_WARNING, "PJ ICE Rx error status code: %d '%s'.\n", | |
1295 | (int)status, buf); | |
1311 | ast_log(LOG_WARNING, "(%p) ICE PJ Rx error status code: %d '%s'.\n", | |
1312 | instance, (int)status, buf); | |
1296 | 1313 | return; |
1297 | 1314 | } |
1298 | 1315 | if (!rtp->rtp_passthrough) { |
1623 | 1640 | turn_cb, &turn_sock_cfg, instance, turn_sock); |
1624 | 1641 | ao2_cleanup(ice); |
1625 | 1642 | if (status != PJ_SUCCESS) { |
1626 | ast_log(LOG_WARNING, "Could not create a TURN client socket\n"); | |
1643 | ast_log(LOG_WARNING, "(%p) Could not create a TURN client socket\n", instance); | |
1627 | 1644 | ao2_lock(instance); |
1628 | 1645 | return; |
1629 | 1646 | } |
1634 | 1651 | pj_strset2(&cred.data.static_cred.data, (char*)password); |
1635 | 1652 | |
1636 | 1653 | pj_turn_sock_alloc(*turn_sock, pj_cstr(&turn_addr, server), port, NULL, &cred, NULL); |
1654 | ||
1655 | ast_debug_ice(2, "(%p) ICE request TURN %s %s candidate\n", instance, | |
1656 | transport == AST_TRANSPORT_UDP ? "UDP" : "TCP", | |
1657 | component == AST_RTP_ICE_COMPONENT_RTP ? "RTP" : "RTCP"); | |
1658 | ||
1637 | 1659 | ao2_lock(instance); |
1638 | 1660 | |
1639 | 1661 | /* |
1686 | 1708 | if (!icesupport || !rtp->ice || rtp->ice_num_components == num_components) { |
1687 | 1709 | return; |
1688 | 1710 | } |
1711 | ||
1712 | ast_debug_ice(2, "(%p) ICE change number of components %u -> %u\n", instance, | |
1713 | rtp->ice_num_components, num_components); | |
1689 | 1714 | |
1690 | 1715 | rtp->ice_num_components = num_components; |
1691 | 1716 | ice_reset_session(instance); |
1781 | 1806 | return 0; |
1782 | 1807 | } |
1783 | 1808 | |
1809 | ast_debug_dtls(3, "(%p) DTLS RTCP setup\n", instance); | |
1784 | 1810 | return dtls_details_initialize(&rtp->rtcp->dtls, rtp->ssl_ctx, rtp->dtls.dtls_setup, instance); |
1785 | 1811 | } |
1786 | 1812 | |
2074 | 2100 | return 0; |
2075 | 2101 | } |
2076 | 2102 | |
2103 | ast_debug_dtls(3, "(%p) DTLS RTP setup\n", instance); | |
2104 | ||
2077 | 2105 | if (!ast_rtp_engine_srtp_is_registered()) { |
2078 | 2106 | ast_log(LOG_ERROR, "SRTP support module is not loaded or available. Try loading res_srtp.so.\n"); |
2079 | 2107 | return -1; |
2196 | 2224 | struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); |
2197 | 2225 | SSL *ssl = rtp->dtls.ssl; |
2198 | 2226 | |
2227 | ast_debug_dtls(3, "(%p) DTLS stop\n", instance); | |
2199 | 2228 | ao2_unlock(instance); |
2200 | 2229 | dtls_srtp_stop_timeout_timer(instance, rtp, 0); |
2201 | 2230 | ao2_lock(instance); |
2468 | 2497 | { |
2469 | 2498 | struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); |
2470 | 2499 | |
2471 | ast_debug(3, "dtls_perform_handshake (%p) - ssl = %p, setup = %d\n", | |
2500 | ast_debug_dtls(3, "(%p) DTLS perform handshake - ssl = %p, setup = %d\n", | |
2472 | 2501 | rtp, dtls->ssl, dtls->dtls_setup); |
2473 | 2502 | |
2474 | 2503 | /* If we are not acting as a client connecting to the remote side then |
2508 | 2537 | } |
2509 | 2538 | dtls->connection = AST_RTP_DTLS_CONNECTION_NEW; |
2510 | 2539 | |
2511 | ast_debug(3, "dtls_perform_setup - connection reset'\n"); | |
2540 | ast_debug_dtls(3, "DTLS perform setup - connection reset\n"); | |
2512 | 2541 | } |
2513 | 2542 | #endif |
2514 | 2543 | |
2547 | 2576 | return; |
2548 | 2577 | } |
2549 | 2578 | |
2550 | ast_debug(3, "ast_rtp_on_ice_complete (%p) - perform DTLS\n", rtp); | |
2579 | ast_debug_category(2, AST_DEBUG_CATEGORY_ICE | AST_DEBUG_CATEGORY_DTLS, | |
2580 | "(%p) ICE starting media - perform DTLS - (%p)\n", instance, rtp); | |
2551 | 2581 | |
2552 | 2582 | /* |
2553 | 2583 | * Seemingly no reason to call dtls_perform_setup here. Currently we'll do a full |
2584 | 2614 | /* PJPROJECT ICE optional callback */ |
2585 | 2615 | static void ast_rtp_on_valid_pair(pj_ice_sess *ice) |
2586 | 2616 | { |
2617 | ast_debug_ice(2, "(%p) ICE valid pair, start media\n", ice->user_data); | |
2587 | 2618 | ast_rtp_ice_start_media(ice, PJ_SUCCESS); |
2588 | 2619 | } |
2589 | 2620 | #endif |
2591 | 2622 | /* PJPROJECT ICE callback */ |
2592 | 2623 | static void ast_rtp_on_ice_complete(pj_ice_sess *ice, pj_status_t status) |
2593 | 2624 | { |
2625 | ast_debug_ice(2, "(%p) ICE complete, start media\n", ice->user_data); | |
2594 | 2626 | ast_rtp_ice_start_media(ice, status); |
2595 | 2627 | } |
2596 | 2628 | |
2680 | 2712 | |
2681 | 2713 | static inline int rtp_debug_test_addr(struct ast_sockaddr *addr) |
2682 | 2714 | { |
2683 | if (!rtpdebug) { | |
2715 | if (!ast_debug_rtp_packet_is_allowed) { | |
2684 | 2716 | return 0; |
2685 | 2717 | } |
2686 | 2718 | if (!ast_sockaddr_isnull(&rtpdebugaddr)) { |
2696 | 2728 | |
2697 | 2729 | static inline int rtcp_debug_test_addr(struct ast_sockaddr *addr) |
2698 | 2730 | { |
2699 | if (!rtcpdebug) { | |
2731 | if (!ast_debug_rtcp_packet_is_allowed) { | |
2700 | 2732 | return 0; |
2701 | 2733 | } |
2702 | 2734 | if (!ast_sockaddr_isnull(&rtcpdebugaddr)) { |
2718 | 2750 | struct dtls_details *dtls = !rtcp ? &rtp->dtls : &rtp->rtcp->dtls; |
2719 | 2751 | struct timeval dtls_timeout; |
2720 | 2752 | |
2753 | ast_debug_dtls(3, "(%p) DTLS srtp - handle timeout - rtcp=%d\n", instance, rtcp); | |
2721 | 2754 | DTLSv1_handle_timeout(dtls->ssl); |
2722 | 2755 | |
2723 | 2756 | /* If a timeout can't be retrieved then this recurring scheduled item must stop */ |
2777 | 2810 | ao2_ref(instance, -1); |
2778 | 2811 | ast_log(LOG_WARNING, "Scheduling '%s' DTLS retransmission for RTP instance [%p] failed.\n", |
2779 | 2812 | !rtcp ? "RTP" : "RTCP", instance); |
2813 | } else { | |
2814 | ast_debug_dtls(3, "(%p) DTLS srtp - scheduled timeout timer for '%d'\n", instance, timeout); | |
2780 | 2815 | } |
2781 | 2816 | } |
2782 | 2817 | } |
2787 | 2822 | struct dtls_details *dtls = !rtcp ? &rtp->dtls : &rtp->rtcp->dtls; |
2788 | 2823 | |
2789 | 2824 | AST_SCHED_DEL_UNREF(rtp->sched, dtls->timeout_timer, ao2_ref(instance, -1)); |
2825 | ast_debug_dtls(3, "(%p) DTLS srtp - stopped timeout timer'\n", instance); | |
2790 | 2826 | } |
2791 | 2827 | |
2792 | 2828 | /* Scheduler callback */ |
2797 | 2833 | |
2798 | 2834 | ao2_lock(instance); |
2799 | 2835 | |
2836 | ast_debug_dtls(3, "(%p) DTLS srtp - renegotiate'\n", instance); | |
2800 | 2837 | SSL_renegotiate(rtp->dtls.ssl); |
2801 | 2838 | SSL_do_handshake(rtp->dtls.ssl); |
2802 | 2839 | |
2820 | 2857 | struct ast_srtp_policy *local_policy, *remote_policy = NULL; |
2821 | 2858 | int res = -1; |
2822 | 2859 | struct dtls_details *dtls = !rtcp ? &rtp->dtls : &rtp->rtcp->dtls; |
2860 | ||
2861 | ast_debug_dtls(3, "(%p) DTLS srtp - add local ssrc - rtcp=%d, set_remote_policy=%d'\n", | |
2862 | instance, rtcp, set_remote_policy); | |
2823 | 2863 | |
2824 | 2864 | /* Produce key information and set up SRTP */ |
2825 | 2865 | if (!SSL_export_keying_material(dtls->ssl, material, SRTP_MASTER_LEN * 2, "EXTRACTOR-dtls_srtp", 19, NULL, 0, 0)) { |
2898 | 2938 | struct dtls_details *dtls = !rtcp ? &rtp->dtls : &rtp->rtcp->dtls; |
2899 | 2939 | int index; |
2900 | 2940 | |
2901 | ast_debug(3, "Setup DTLS SRTP (%p)'\n", rtp); | |
2941 | ast_debug_dtls(3, "(%p) DTLS setup SRTP rtp=%p'\n", instance, rtp); | |
2902 | 2942 | |
2903 | 2943 | /* If a fingerprint is present in the SDP make sure that the peer certificate matches it */ |
2904 | 2944 | if (rtp->dtls_verify & AST_RTP_DTLS_VERIFY_FINGERPRINT) { |
3047 | 3087 | return -1; |
3048 | 3088 | } |
3049 | 3089 | |
3050 | ast_debug(3, "__rtp_recvfrom (%p) - Got SSL packet '%d'\n", rtp, *in); | |
3090 | ast_debug_dtls(3, "(%p) DTLS - __rtp_recvfrom rtp=%p - Got SSL packet '%d'\n", instance, rtp, *in); | |
3051 | 3091 | |
3052 | 3092 | /* |
3053 | 3093 | * A race condition is prevented between dtls_perform_handshake() |
3089 | 3129 | /* Notify that dtls has been established */ |
3090 | 3130 | res = RTP_DTLS_ESTABLISHED; |
3091 | 3131 | |
3092 | ast_debug(3, "__rtp_recvfrom (%p) - DTLS established'\n", rtp); | |
3132 | ast_debug_dtls(3, "(%p) DTLS - __rtp_recvfrom rtp=%p - established'\n", instance, rtp); | |
3093 | 3133 | } else { |
3094 | 3134 | /* Since we've sent additional traffic start the timeout timer for retransmission */ |
3095 | 3135 | dtls_srtp_start_timeout_timer(instance, rtp, rtcp); |
3483 | 3523 | |
3484 | 3524 | if (getifaddrs(&ifa) < 0) { |
3485 | 3525 | /* If we can't get addresses, we can't load ICE candidates */ |
3486 | ast_log(LOG_ERROR, "Error obtaining list of local addresses: %s\n", | |
3487 | strerror(errno)); | |
3526 | ast_log(LOG_ERROR, "(%p) ICE Error obtaining list of local addresses: %s\n", | |
3527 | instance, strerror(errno)); | |
3488 | 3528 | } else { |
3529 | ast_debug_ice(2, "(%p) ICE add system candidates\n", instance); | |
3489 | 3530 | /* Iterate through the list of addresses obtained from the system, |
3490 | 3531 | * until we've iterated through all of them, or accepted |
3491 | 3532 | * PJ_ICE_MAX_CAND candidates */ |
3587 | 3628 | struct sockaddr_in answer; |
3588 | 3629 | int rsp; |
3589 | 3630 | |
3631 | ast_debug_category(3, AST_DEBUG_CATEGORY_ICE | AST_DEBUG_CATEGORY_STUN, | |
3632 | "(%p) ICE request STUN %s %s candidate\n", instance, | |
3633 | transport == AST_TRANSPORT_UDP ? "UDP" : "TCP", | |
3634 | component == AST_RTP_ICE_COMPONENT_RTP ? "RTP" : "RTCP"); | |
3635 | ||
3590 | 3636 | /* |
3591 | 3637 | * The instance should not be locked because we can block |
3592 | 3638 | * waiting for a STUN respone. |
3698 | 3744 | ao2_cleanup(rtp->ice_local_candidates); |
3699 | 3745 | rtp->ice_local_candidates = NULL; |
3700 | 3746 | |
3747 | ast_debug_ice(2, "(%p) ICE create%s\n", instance, replace ? " and replace" : ""); | |
3748 | ||
3701 | 3749 | ice = ao2_alloc_options(sizeof(*ice), ice_wrap_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK); |
3702 | 3750 | if (!ice) { |
3703 | 3751 | ast_rtp_ice_stop(instance); |
3781 | 3829 | ast_sockaddr_set_port(&rtp->bind_address, x); |
3782 | 3830 | /* Try to bind, this will tell us whether the port is available or not */ |
3783 | 3831 | if (!ast_bind(rtp->s, &rtp->bind_address)) { |
3784 | ast_debug(1, "Allocated port %d for RTP instance '%p'\n", x, instance); | |
3832 | ast_debug_rtp(1, "(%p) RTP allocated port %d\n", instance, x); | |
3785 | 3833 | ast_rtp_instance_set_local_address(instance, &rtp->bind_address); |
3786 | 3834 | ast_test_suite_event_notify("RTP_PORT_ALLOCATED", "Port: %d", x); |
3787 | 3835 | break; |
3811 | 3859 | /* Create an ICE session for ICE negotiation */ |
3812 | 3860 | if (icesupport) { |
3813 | 3861 | rtp->ice_num_components = 2; |
3814 | ast_debug(3, "Creating ICE session %s (%d) for RTP instance '%p'\n", ast_sockaddr_stringify(&rtp->bind_address), x, instance); | |
3862 | ast_debug_ice(2, "(%p) ICE creating session %s (%d)\n", instance, | |
3863 | ast_sockaddr_stringify(&rtp->bind_address), x); | |
3815 | 3864 | if (ice_create(instance, &rtp->bind_address, x, 0)) { |
3816 | ast_log(LOG_NOTICE, "Failed to create ICE session\n"); | |
3865 | ast_log(LOG_NOTICE, "(%p) ICE failed to create session\n", instance); | |
3817 | 3866 | } else { |
3818 | 3867 | rtp->ice_port = x; |
3819 | 3868 | ast_sockaddr_copy(&rtp->ice_original_rtp_addr, &rtp->bind_address); |
3890 | 3939 | rtp->turn_rtcp = NULL; |
3891 | 3940 | } |
3892 | 3941 | |
3942 | ast_debug_ice(2, "(%p) ICE RTP transport deallocating\n", instance); | |
3893 | 3943 | /* Destroy any ICE session */ |
3894 | 3944 | ast_rtp_ice_stop(instance); |
3895 | 3945 | |
4217 | 4267 | rtp->dtmfmute = ast_tvadd(ast_tvnow(), ast_tv(0, 500000)); |
4218 | 4268 | |
4219 | 4269 | if (duration > 0 && (measured_samples = duration * ast_rtp_get_rate(rtp->f.subclass.format) / 1000) > rtp->send_duration) { |
4220 | ast_debug(2, "Adjusting final end duration from %d to %u\n", rtp->send_duration, measured_samples); | |
4270 | ast_debug_rtp(2, "(%p) RTP adjusting final end duration from %d to %u\n", | |
4271 | instance, rtp->send_duration, measured_samples); | |
4221 | 4272 | rtp->send_duration = measured_samples; |
4222 | 4273 | } |
4223 | 4274 | |
4280 | 4331 | |
4281 | 4332 | /* We simply set this bit so that the next packet sent will have the marker bit turned on */ |
4282 | 4333 | ast_set_flag(rtp, FLAG_NEED_MARKER_BIT); |
4283 | ast_debug(3, "Setting the marker bit due to a source update\n"); | |
4334 | ast_debug_rtp(3, "(%p) RTP setting the marker bit due to a source update\n", instance); | |
4284 | 4335 | |
4285 | 4336 | return; |
4286 | 4337 | } |
4298 | 4349 | ast_set_flag(rtp, FLAG_NEED_MARKER_BIT); |
4299 | 4350 | } |
4300 | 4351 | |
4301 | ast_debug(3, "Changing ssrc from %u to %u due to a source change\n", rtp->ssrc, ssrc); | |
4352 | ast_debug_rtp(3, "(%p) RTP changing ssrc from %u to %u due to a source change\n", | |
4353 | instance, rtp->ssrc, ssrc); | |
4302 | 4354 | |
4303 | 4355 | if (srtp) { |
4304 | ast_debug(3, "Changing ssrc for SRTP from %u to %u\n", rtp->ssrc, ssrc); | |
4356 | ast_debug_rtp(3, "(%p) RTP changing ssrc for SRTP from %u to %u\n", | |
4357 | instance, rtp->ssrc, ssrc); | |
4305 | 4358 | res_srtp->change_source(srtp, rtp->ssrc, ssrc); |
4306 | 4359 | if (rtcp_srtp != srtp) { |
4307 | 4360 | res_srtp->change_source(rtcp_srtp, rtp->ssrc, ssrc); |
4638 | 4691 | res = ast_rtcp_generate_report(instance, rtcpheader, report, sr); |
4639 | 4692 | |
4640 | 4693 | if (res == 0 || res == 1) { |
4641 | ast_debug(1, "Failed to generate %s report!\n", sr ? "SR" : "RR"); | |
4694 | ast_debug_rtcp(1, "(%p) RTCP failed to generate %s report!\n", instance, sr ? "SR" : "RR"); | |
4642 | 4695 | return 0; |
4643 | 4696 | } |
4644 | 4697 | |
4647 | 4700 | res = ast_rtcp_generate_sdes(instance, rtcpheader + packet_len, report); |
4648 | 4701 | |
4649 | 4702 | if (res == 0 || res == 1) { |
4650 | ast_debug(1, "Failed to generate SDES!\n"); | |
4703 | ast_debug_rtcp(1, "(%p) RTCP failed to generate SDES!\n", instance); | |
4651 | 4704 | return 0; |
4652 | 4705 | } |
4653 | 4706 | |
4855 | 4908 | if (abs((int)rtp->lastts - pred) < MAX_TIMESTAMP_SKEW) { |
4856 | 4909 | rtp->lastts = pred; |
4857 | 4910 | } else { |
4858 | ast_debug(3, "Difference is %d, ms is %u\n", abs((int)rtp->lastts - pred), ms); | |
4911 | ast_debug_rtp(3, "(%p) RTP audio difference is %d, ms is %u\n", | |
4912 | instance, abs((int)rtp->lastts - pred), ms); | |
4859 | 4913 | mark = 1; |
4860 | 4914 | } |
4861 | 4915 | } |
4870 | 4924 | rtp->lastts = pred; |
4871 | 4925 | rtp->lastovidtimestamp += frame->samples; |
4872 | 4926 | } else { |
4873 | ast_debug(3, "Difference is %d, ms is %u (%u), pred/ts/samples %u/%d/%d\n", abs((int)rtp->lastts - pred), ms, ms * 90, rtp->lastts, pred, frame->samples); | |
4927 | ast_debug_rtp(3, "(%p) RTP video difference is %d, ms is %u (%u), pred/ts/samples %u/%d/%d\n", | |
4928 | instance, abs((int)rtp->lastts - pred), ms, ms * 90, rtp->lastts, pred, frame->samples); | |
4874 | 4929 | rtp->lastovidtimestamp = rtp->lastts; |
4875 | 4930 | } |
4876 | 4931 | } |
4884 | 4939 | rtp->lastts = pred; |
4885 | 4940 | rtp->lastotexttimestamp += frame->samples; |
4886 | 4941 | } else { |
4887 | ast_debug(3, "Difference is %d, ms is %u, pred/ts/samples %u/%d/%d\n", abs((int)rtp->lastts - pred), ms, rtp->lastts, pred, frame->samples); | |
4942 | ast_debug_rtp(3, "(%p) RTP other difference is %d, ms is %u, pred/ts/samples %u/%d/%d\n", | |
4943 | instance, abs((int)rtp->lastts - pred), ms, rtp->lastts, pred, frame->samples); | |
4888 | 4944 | rtp->lastotexttimestamp = rtp->lastts; |
4889 | 4945 | } |
4890 | 4946 | } |
5000 | 5056 | res = rtp_sendto(instance, (void *)rtpheader, packet_len, 0, &remote_address, &ice); |
5001 | 5057 | if (res < 0) { |
5002 | 5058 | if (!ast_rtp_instance_get_prop(instance, AST_RTP_PROPERTY_NAT) || (ast_rtp_instance_get_prop(instance, AST_RTP_PROPERTY_NAT) && (ast_test_flag(rtp, FLAG_NAT_ACTIVE) == FLAG_NAT_ACTIVE))) { |
5003 | ast_debug(1, "RTP Transmission error of packet %d to %s: %s\n", | |
5004 | rtp->seqno, | |
5059 | ast_debug_rtp(1, "(%p) RTP transmission error of packet %d to %s: %s\n", | |
5060 | instance, rtp->seqno, | |
5005 | 5061 | ast_sockaddr_stringify(&remote_address), |
5006 | 5062 | strerror(errno)); |
5007 | } else if (((ast_test_flag(rtp, FLAG_NAT_ACTIVE) == FLAG_NAT_INACTIVE) || rtpdebug) && !ast_test_flag(rtp, FLAG_NAT_INACTIVE_NOWARN)) { | |
5063 | } else if (((ast_test_flag(rtp, FLAG_NAT_ACTIVE) == FLAG_NAT_INACTIVE) || ast_debug_rtp_packet_is_allowed) && !ast_test_flag(rtp, FLAG_NAT_INACTIVE_NOWARN)) { | |
5008 | 5064 | /* Only give this error message once if we are not RTP debugging */ |
5009 | if (rtpdebug) | |
5010 | ast_debug(0, "RTP NAT: Can't write RTP to private address %s, waiting for other end to send audio...\n", | |
5011 | ast_sockaddr_stringify(&remote_address)); | |
5065 | if (ast_debug_rtp_packet_is_allowed) | |
5066 | ast_debug(0, "(%p) RTP NAT: Can't write RTP to private address %s, waiting for other end to send audio...\n", | |
5067 | instance, ast_sockaddr_stringify(&remote_address)); | |
5012 | 5068 | ast_set_flag(rtp, FLAG_NAT_INACTIVE_NOWARN); |
5013 | 5069 | } |
5014 | 5070 | } else { |
5015 | 5071 | if (rtp->rtcp && rtp->rtcp->schedid < 0) { |
5016 | ast_debug(1, "Starting RTCP transmission on RTP instance '%p'\n", instance); | |
5072 | ast_debug_rtcp(1, "(%p) RTCP starting transmission\n", instance); | |
5017 | 5073 | ao2_ref(instance, +1); |
5018 | 5074 | rtp->rtcp->schedid = ast_sched_add(rtp->sched, ast_rtcp_calc_interval(rtp), ast_rtcp_write, instance); |
5019 | 5075 | if (rtp->rtcp->schedid < 0) { |
5159 | 5215 | ao2_cleanup); |
5160 | 5216 | |
5161 | 5217 | if (feedback->fmt != AST_RTP_RTCP_FMT_REMB) { |
5162 | ast_debug(1, "Provided an RTCP feedback frame of format %d to write on RTP instance '%p' but only REMB is supported\n", | |
5163 | feedback->fmt, instance); | |
5218 | ast_debug_rtcp(1, "(%p) RTCP provided feedback frame of format %d to write, but only REMB is supported\n", | |
5219 | instance, feedback->fmt); | |
5164 | 5220 | return; |
5165 | 5221 | } |
5166 | 5222 | |
5170 | 5226 | |
5171 | 5227 | /* If REMB support is not enabled don't send this RTCP packet */ |
5172 | 5228 | if (!ast_rtp_instance_get_prop(instance, AST_RTP_PROPERTY_REMB)) { |
5173 | ast_debug(1, "Provided an RTCP feedback REMB report to write on RTP instance '%p' but REMB support not enabled\n", | |
5229 | ast_debug_rtcp(1, "(%p) RTCP provided feedback REMB report to write, but REMB support not enabled\n", | |
5174 | 5230 | instance); |
5175 | 5231 | return; |
5176 | 5232 | } |
5222 | 5278 | |
5223 | 5279 | /* If we don't actually know the remote address don't even bother doing anything */ |
5224 | 5280 | if (ast_sockaddr_isnull(&remote_address)) { |
5225 | ast_debug(1, "No remote address on RTP instance '%p' so dropping frame\n", instance); | |
5281 | ast_debug_rtp(1, "(%p) RTP no remote address on instance, so dropping frame\n", instance); | |
5226 | 5282 | return 0; |
5227 | 5283 | } |
5228 | 5284 | |
5239 | 5295 | |
5240 | 5296 | /* If there is no data length we can't very well send the packet */ |
5241 | 5297 | if (!frame->datalen) { |
5242 | ast_debug(1, "Received frame with no data for RTP instance '%p' so dropping frame\n", instance); | |
5298 | ast_debug_rtp(1, "(%p) RTP received frame with no data for instance, so dropping frame\n", instance); | |
5243 | 5299 | return 0; |
5244 | 5300 | } |
5245 | 5301 | |
5271 | 5327 | format = frame->subclass.format; |
5272 | 5328 | if (ast_format_cmp(rtp->lasttxformat, format) == AST_FORMAT_CMP_NOT_EQUAL) { |
5273 | 5329 | /* Oh dear, if the format changed we will have to set up a new smoother */ |
5274 | ast_debug(1, "Ooh, format changed from %s to %s\n", | |
5275 | ast_format_get_name(rtp->lasttxformat), | |
5330 | ast_debug_rtp(1, "(%p) RTP ooh, format changed from %s to %s\n", | |
5331 | instance, ast_format_get_name(rtp->lasttxformat), | |
5276 | 5332 | ast_format_get_name(frame->subclass.format)); |
5277 | 5333 | ao2_replace(rtp->lasttxformat, format); |
5278 | 5334 | if (rtp->smoother) { |
5397 | 5453 | ast_rtp_instance_get_remote_address(instance, &remote_address); |
5398 | 5454 | |
5399 | 5455 | if (((compensate && type == AST_FRAME_DTMF_END) || (type == AST_FRAME_DTMF_BEGIN)) && ast_tvcmp(ast_tvnow(), rtp->dtmfmute) < 0) { |
5400 | ast_debug(1, "Ignore potential DTMF echo from '%s'\n", | |
5401 | ast_sockaddr_stringify(&remote_address)); | |
5456 | ast_debug_rtp(1, "(%p) RTP ignore potential DTMF echo from '%s'\n", | |
5457 | instance, ast_sockaddr_stringify(&remote_address)); | |
5402 | 5458 | rtp->resp = 0; |
5403 | 5459 | rtp->dtmfsamples = 0; |
5404 | 5460 | return &ast_null_frame; |
5405 | 5461 | } |
5406 | ast_debug(1, "Creating %s DTMF Frame: %d (%c), at %s\n", | |
5407 | type == AST_FRAME_DTMF_END ? "END" : "BEGIN", | |
5462 | ast_debug_rtp(1, "(%p) RTP creating %s DTMF Frame: %d (%c), at %s\n", | |
5463 | instance, type == AST_FRAME_DTMF_END ? "END" : "BEGIN", | |
5408 | 5464 | rtp->resp, rtp->resp, |
5409 | 5465 | ast_sockaddr_stringify(&remote_address)); |
5410 | 5466 | if (rtp->resp == 'X') { |
5449 | 5505 | } |
5450 | 5506 | |
5451 | 5507 | /* Print out debug if turned on */ |
5452 | if (rtpdebug) | |
5508 | if (ast_debug_rtp_packet_is_allowed) | |
5453 | 5509 | ast_debug(0, "- RTP 2833 Event: %08x (len = %d)\n", event, len); |
5454 | 5510 | |
5455 | 5511 | /* Figure out what digit was pressed */ |
5465 | 5521 | resp = 'X'; |
5466 | 5522 | } else { |
5467 | 5523 | /* Not a supported event */ |
5468 | ast_debug(1, "Ignoring RTP 2833 Event: %08x. Not a DTMF Digit.\n", event); | |
5524 | ast_debug_rtp(1, "(%p) RTP ignoring RTP 2833 Event: %08x. Not a DTMF Digit.\n", instance, event); | |
5469 | 5525 | return; |
5470 | 5526 | } |
5471 | 5527 | |
5503 | 5559 | rtp->resp = 0; |
5504 | 5560 | rtp->dtmf_duration = rtp->dtmf_timeout = 0; |
5505 | 5561 | AST_LIST_INSERT_TAIL(frames, f, frame_list); |
5506 | } else if (rtpdebug) { | |
5507 | ast_debug(1, "Dropping duplicate or out of order DTMF END frame (seqno: %u, ts %u, digit %c)\n", | |
5508 | seqno, timestamp, resp); | |
5562 | } else if (ast_debug_rtp_packet_is_allowed) { | |
5563 | ast_debug_rtp(1, "(%p) RTP dropping duplicate or out of order DTMF END frame (seqno: %u, ts %u, digit %c)\n", | |
5564 | instance, seqno, timestamp, resp); | |
5509 | 5565 | } |
5510 | 5566 | } else { |
5511 | 5567 | /* Begin/continuation */ |
5520 | 5576 | * improperly duplicate incoming DTMF, so just drop |
5521 | 5577 | * this. |
5522 | 5578 | */ |
5523 | if (rtpdebug) { | |
5524 | ast_debug(1, "Dropping out of order DTMF frame (seqno %u, ts %u, digit %c)\n", | |
5579 | if (ast_debug_rtp_packet_is_allowed) { | |
5580 | ast_debug(0, "Dropping out of order DTMF frame (seqno %u, ts %u, digit %c)\n", | |
5525 | 5581 | seqno, timestamp, resp); |
5526 | 5582 | } |
5527 | 5583 | return; |
5605 | 5661 | power = data[2]; |
5606 | 5662 | event = data[3] & 0x1f; |
5607 | 5663 | |
5608 | if (rtpdebug) | |
5664 | if (ast_debug_rtp_packet_is_allowed) | |
5609 | 5665 | ast_debug(0, "Cisco DTMF Digit: %02x (len=%d, seq=%d, flags=%02x, power=%u, history count=%d)\n", event, len, seq, flags, power, (len - 4) / 2); |
5610 | 5666 | if (event < 10) { |
5611 | 5667 | resp = '0' + event; |
5645 | 5701 | /* Convert comfort noise into audio with various codecs. Unfortunately this doesn't |
5646 | 5702 | totally help us out becuase we don't have an engine to keep it going and we are not |
5647 | 5703 | guaranteed to have it every 20ms or anything */ |
5648 | if (rtpdebug) { | |
5704 | if (ast_debug_rtp_packet_is_allowed) { | |
5649 | 5705 | ast_debug(0, "- RTP 3389 Comfort noise event: Format %s (len = %d)\n", |
5650 | 5706 | ast_format_get_name(rtp->lastrxformat), len); |
5651 | 5707 | } |
5887 | 5943 | unsigned int packets_not_found = 0; |
5888 | 5944 | |
5889 | 5945 | if (!rtp->send_buffer) { |
5890 | ast_debug(1, "Tried to handle NACK request, but we don't have a RTP packet storage!\n"); | |
5946 | ast_debug_rtcp(1, "(%p) RTCP tried to handle NACK request, " | |
5947 | "but we don't have a RTP packet storage!\n", instance); | |
5891 | 5948 | return res; |
5892 | 5949 | } |
5893 | 5950 | |
5916 | 5973 | } |
5917 | 5974 | res += rtp_sendto(instance, payload->buf, payload->size, 0, &remote_address, &ice); |
5918 | 5975 | } else { |
5919 | ast_debug(1, "Received NACK request for RTP packet with seqno %d, but we don't have it\n", pid); | |
5976 | ast_debug_rtcp(1, "(%p) RTCP received NACK request for RTP packet with seqno %d, " | |
5977 | "but we don't have it\n", instance, pid); | |
5920 | 5978 | packets_not_found++; |
5921 | 5979 | } |
5922 | 5980 | /* |
5938 | 5996 | } |
5939 | 5997 | res += rtp_sendto(instance, payload->buf, payload->size, 0, &remote_address, &ice); |
5940 | 5998 | } else { |
5941 | ast_debug(1, "Remote end also requested RTP packet with seqno %d, but we don't have it\n", seqno); | |
5999 | ast_debug_rtcp(1, "(%p) RTCP remote end also requested RTP packet with seqno %d, " | |
6000 | "but we don't have it\n", instance, seqno); | |
5942 | 6001 | packets_not_found++; |
5943 | 6002 | } |
5944 | 6003 | } |
5953 | 6012 | */ |
5954 | 6013 | ast_data_buffer_resize(rtp->send_buffer, MIN(MAXIMUM_RTP_SEND_BUFFER_SIZE, |
5955 | 6014 | ast_data_buffer_max(rtp->send_buffer) + packets_not_found)); |
5956 | ast_debug(2, "Send buffer on RTP instance '%p' is now at maximum of %zu\n", instance, ast_data_buffer_max(rtp->send_buffer)); | |
6015 | ast_debug_rtcp(2, "(%p) RTCP send buffer on RTP instance is now at maximum of %zu\n", | |
6016 | instance, ast_data_buffer_max(rtp->send_buffer)); | |
5957 | 6017 | } |
5958 | 6018 | |
5959 | 6019 | return res; |
6027 | 6087 | |
6028 | 6088 | packetwords = len / 4; |
6029 | 6089 | |
6030 | ast_debug(1, "Got RTCP report of %d bytes from %s\n", | |
6031 | len, ast_sockaddr_stringify(addr)); | |
6090 | ast_debug_rtcp(1, "(%p) RTCP got report of %d bytes from %s\n", | |
6091 | instance, len, ast_sockaddr_stringify(addr)); | |
6032 | 6092 | |
6033 | 6093 | /* |
6034 | 6094 | * Validate the RTCP packet according to an adapted and slightly |
6035 | 6095 | * modified RFC3550 validation algorithm. |
6036 | 6096 | */ |
6037 | 6097 | if (packetwords < RTCP_HEADER_SSRC_LENGTH) { |
6038 | ast_debug(1, "%p -- RTCP from %s: Frame size (%u words) is too short\n", | |
6039 | transport_rtp, ast_sockaddr_stringify(addr), packetwords); | |
6098 | ast_debug_rtcp(1, "(%p) RTCP %p -- from %s: Frame size (%u words) is too short\n", | |
6099 | instance, transport_rtp, ast_sockaddr_stringify(addr), packetwords); | |
6040 | 6100 | return &ast_null_frame; |
6041 | 6101 | } |
6042 | 6102 | position = 0; |
6043 | 6103 | first_word = ntohl(rtcpheader[position]); |
6044 | 6104 | if ((first_word & RTCP_VALID_MASK) != RTCP_VALID_VALUE) { |
6045 | ast_debug(1, "%p -- RTCP from %s: Failed first packet validity check\n", | |
6046 | transport_rtp, ast_sockaddr_stringify(addr)); | |
6105 | ast_debug_rtcp(1, "(%p) RTCP %p -- from %s: Failed first packet validity check\n", | |
6106 | instance, transport_rtp, ast_sockaddr_stringify(addr)); | |
6047 | 6107 | return &ast_null_frame; |
6048 | 6108 | } |
6049 | 6109 | do { |
6054 | 6114 | first_word = ntohl(rtcpheader[position]); |
6055 | 6115 | } while ((first_word & RTCP_VERSION_MASK_SHIFTED) == RTCP_VERSION_SHIFTED); |
6056 | 6116 | if (position != packetwords) { |
6057 | ast_debug(1, "%p -- RTCP from %s: Failed packet version or length check\n", | |
6058 | transport_rtp, ast_sockaddr_stringify(addr)); | |
6117 | ast_debug_rtcp(1, "(%p) RTCP %p -- from %s: Failed packet version or length check\n", | |
6118 | instance, transport_rtp, ast_sockaddr_stringify(addr)); | |
6059 | 6119 | return &ast_null_frame; |
6060 | 6120 | } |
6061 | 6121 | |
6145 | 6205 | min_length = length; |
6146 | 6206 | break; |
6147 | 6207 | default: |
6148 | ast_debug(1, "%p -- RTCP from %s: %u(%s) skipping record\n", | |
6149 | transport_rtp, ast_sockaddr_stringify(addr), pt, rtcp_payload_type2str(pt)); | |
6208 | ast_debug_rtcp(1, "(%p) RTCP %p -- from %s: %u(%s) skipping record\n", | |
6209 | instance, transport_rtp, ast_sockaddr_stringify(addr), pt, rtcp_payload_type2str(pt)); | |
6150 | 6210 | if (rtcp_debug_test_addr(addr)) { |
6151 | 6211 | ast_verbose("\n"); |
6152 | 6212 | ast_verbose("RTCP from %s: %u(%s) skipping record\n", |
6156 | 6216 | continue; |
6157 | 6217 | } |
6158 | 6218 | if (length < min_length) { |
6159 | ast_debug(1, "%p -- RTCP from %s: %u(%s) length field less than expected minimum. Min:%u Got:%u\n", | |
6160 | transport_rtp, ast_sockaddr_stringify(addr), pt, rtcp_payload_type2str(pt), | |
6219 | ast_debug_rtcp(1, "(%p) RTCP %p -- from %s: %u(%s) length field less than expected minimum. Min:%u Got:%u\n", | |
6220 | instance, transport_rtp, ast_sockaddr_stringify(addr), pt, rtcp_payload_type2str(pt), | |
6161 | 6221 | min_length - 1, length - 1); |
6162 | 6222 | return &ast_null_frame; |
6163 | 6223 | } |
6247 | 6307 | * for a different stream. |
6248 | 6308 | */ |
6249 | 6309 | position += length; |
6250 | ast_debug(1, "%p -- RTCP from %s: Skipping record, received SSRC '%u' != expected '%u'\n", | |
6251 | rtp, ast_sockaddr_stringify(addr), ssrc, rtp->themssrc); | |
6310 | ast_debug_rtcp(1, "(%p) RTCP %p -- from %s: Skipping record, received SSRC '%u' != expected '%u'\n", | |
6311 | instance, rtp, ast_sockaddr_stringify(addr), ssrc, rtp->themssrc); | |
6252 | 6312 | if (child) { |
6253 | 6313 | ao2_unlock(child); |
6254 | 6314 | } |
6261 | 6321 | /* Send to whoever sent to us */ |
6262 | 6322 | if (ast_sockaddr_cmp(&rtp->rtcp->them, addr)) { |
6263 | 6323 | ast_sockaddr_copy(&rtp->rtcp->them, addr); |
6264 | if (rtpdebug) { | |
6265 | ast_debug(0, "RTCP NAT: Got RTCP from other end. Now sending to address %s\n", | |
6266 | ast_sockaddr_stringify(addr)); | |
6324 | if (ast_debug_rtp_packet_is_allowed) { | |
6325 | ast_debug(0, "(%p) RTCP NAT: Got RTCP from other end. Now sending to address %s\n", | |
6326 | instance, ast_sockaddr_stringify(addr)); | |
6267 | 6327 | } |
6268 | 6328 | } |
6269 | 6329 | } |
6526 | 6586 | if (ast_sockaddr_is_ipv4(&addr)) { |
6527 | 6587 | ast_sockaddr_to_sin(&addr, &addr_tmp); |
6528 | 6588 | } else if (ast_sockaddr_ipv4_mapped(&addr, &addr_v4)) { |
6529 | ast_debug(1, "Using IPv6 mapped address %s for STUN\n", | |
6530 | ast_sockaddr_stringify(&addr)); | |
6589 | ast_debug_stun(2, "(%p) STUN using IPv6 mapped address %s\n", | |
6590 | instance, ast_sockaddr_stringify(&addr)); | |
6531 | 6591 | ast_sockaddr_to_sin(&addr_v4, &addr_tmp); |
6532 | 6592 | } else { |
6533 | ast_debug(1, "Cannot do STUN for non IPv4 address %s\n", | |
6534 | ast_sockaddr_stringify(&addr)); | |
6593 | ast_debug_stun(2, "(%p) STUN cannot do for non IPv4 address %s\n", | |
6594 | instance, ast_sockaddr_stringify(&addr)); | |
6535 | 6595 | return &ast_null_frame; |
6536 | 6596 | } |
6537 | 6597 | if ((ast_stun_handle_packet(rtp->rtcp->s, &addr_tmp, read_area, res, NULL, NULL) == AST_STUN_ACCEPT)) { |
6578 | 6638 | |
6579 | 6639 | /* If the payload coming in is not one of the negotiated ones then send it to the core, this will cause formats to change and the bridge to break */ |
6580 | 6640 | if (ast_rtp_codecs_find_payload_code(ast_rtp_instance_get_codecs(instance1), bridged_payload) == -1) { |
6581 | ast_debug(1, "Unsupported payload type received \n"); | |
6641 | ast_debug_rtp(1, "(%p, %p) RTP unsupported payload type received\n", instance, instance1); | |
6582 | 6642 | return -1; |
6583 | 6643 | } |
6584 | 6644 | |
6588 | 6648 | * core so they can be filtered accordingly. |
6589 | 6649 | */ |
6590 | 6650 | if (rtp->last_end_timestamp == timestamp) { |
6591 | ast_debug(1, "Feeding packet with duplicate timestamp to core\n"); | |
6651 | ast_debug_rtp(1, "(%p, %p) RTP feeding packet with duplicate timestamp to core\n", instance, instance1); | |
6592 | 6652 | return -1; |
6593 | 6653 | } |
6594 | 6654 | |
6620 | 6680 | |
6621 | 6681 | /* If bridged peer is in dtmf, feed all packets to core until it finishes to avoid infinite dtmf */ |
6622 | 6682 | if (bridged->sending_digit) { |
6623 | ast_debug(1, "Feeding packet to core until DTMF finishes\n"); | |
6683 | ast_debug_rtp(1, "(%p, %p) RTP Feeding packet to core until DTMF finishes\n", instance, instance1); | |
6624 | 6684 | ao2_unlock(instance1); |
6625 | 6685 | ao2_lock(instance); |
6626 | 6686 | return -1; |
6634 | 6694 | if (!bridged->asymmetric_codec |
6635 | 6695 | && bridged->lastrxformat != ast_format_none |
6636 | 6696 | && ast_format_cmp(payload_type->format, bridged->lastrxformat) == AST_FORMAT_CMP_NOT_EQUAL) { |
6637 | ast_debug(1, "Asymmetric RTP codecs detected (TX: %s, RX: %s) sending frame to core\n", | |
6638 | ast_format_get_name(payload_type->format), | |
6697 | ast_debug_rtp(1, "(%p, %p) RTP asymmetric RTP codecs detected (TX: %s, RX: %s) sending frame to core\n", | |
6698 | instance, instance1, ast_format_get_name(payload_type->format), | |
6639 | 6699 | ast_format_get_name(bridged->lastrxformat)); |
6640 | 6700 | ao2_unlock(instance1); |
6641 | 6701 | ao2_lock(instance); |
6648 | 6708 | ast_rtp_instance_get_remote_address(instance1, &remote_address); |
6649 | 6709 | |
6650 | 6710 | if (ast_sockaddr_isnull(&remote_address)) { |
6651 | ast_debug(5, "Remote address is null, most likely RTP has been stopped\n"); | |
6711 | ast_debug_rtp(5, "(%p, %p) RTP remote address is null, most likely RTP has been stopped\n", | |
6712 | instance, instance1); | |
6652 | 6713 | ao2_unlock(instance1); |
6653 | 6714 | ao2_lock(instance); |
6654 | 6715 | return 0; |
6685 | 6746 | "RTP Transmission error of packet to %s: %s\n", |
6686 | 6747 | ast_sockaddr_stringify(&remote_address), |
6687 | 6748 | strerror(errno)); |
6688 | } else if (((ast_test_flag(bridged, FLAG_NAT_ACTIVE) == FLAG_NAT_INACTIVE) || rtpdebug) && !ast_test_flag(bridged, FLAG_NAT_INACTIVE_NOWARN)) { | |
6689 | if (rtpdebug || DEBUG_ATLEAST(1)) { | |
6749 | } else if (((ast_test_flag(bridged, FLAG_NAT_ACTIVE) == FLAG_NAT_INACTIVE) || ast_debug_rtp_packet_is_allowed) && !ast_test_flag(bridged, FLAG_NAT_INACTIVE_NOWARN)) { | |
6750 | if (ast_debug_rtp_packet_is_allowed || DEBUG_ATLEAST(1)) { | |
6690 | 6751 | ast_log(LOG_WARNING, |
6691 | 6752 | "RTP NAT: Can't write RTP to private " |
6692 | 6753 | "address %s, waiting for other end to " |
6964 | 7025 | /* The packet is now fully constructed so send it out */ |
6965 | 7026 | ast_sockaddr_copy(&remote_address, &rtp->rtcp->them); |
6966 | 7027 | |
6967 | ast_debug(2, "Sending transport-cc feedback packet of size '%d' on '%s' with packet count of %d (small = %d, large = %d, lost = %d)\n", | |
6968 | packet_len, ast_rtp_instance_get_channel_id(instance), packet_count, small_delta_count, large_delta_count, lost_count); | |
7028 | ast_debug_rtcp(2, "(%p) RTCP sending transport-cc feedback packet of size '%d' on '%s' with packet count of %d (small = %d, large = %d, lost = %d)\n", | |
7029 | instance, packet_len, ast_rtp_instance_get_channel_id(instance), packet_count, small_delta_count, large_delta_count, lost_count); | |
6969 | 7030 | |
6970 | 7031 | res = rtcp_sendto(instance, (unsigned int *)rtcpheader, packet_len, 0, &remote_address, &ice); |
6971 | 7032 | if (res < 0) { |
7025 | 7086 | |
7026 | 7087 | /* If we have not yet scheduled the periodic sending of feedback for this transport then do so */ |
7027 | 7088 | if (transport_rtp->transport_wide_cc.schedid < 0 && transport_rtp->rtcp) { |
7028 | ast_debug(1, "Starting RTCP transport-cc feedback transmission on RTP instance '%p'\n", transport); | |
7089 | ast_debug_rtcp(1, "(%p) RTCP starting transport-cc feedback transmission on RTP instance '%p'\n", instance, transport); | |
7029 | 7090 | ao2_ref(transport, +1); |
7030 | 7091 | transport_rtp->transport_wide_cc.schedid = ast_sched_add(rtp->sched, 1000, |
7031 | 7092 | rtp_transport_wide_cc_feedback_produce, transport); |
7459 | 7520 | if (ast_sockaddr_is_ipv4(&addr)) { |
7460 | 7521 | ast_sockaddr_to_sin(&addr, &addr_tmp); |
7461 | 7522 | } else if (ast_sockaddr_ipv4_mapped(&addr, &addr_v4)) { |
7462 | ast_debug(1, "Using IPv6 mapped address %s for STUN\n", | |
7463 | ast_sockaddr_stringify(&addr)); | |
7523 | ast_debug_stun(1, "(%p) STUN using IPv6 mapped address %s\n", | |
7524 | instance, ast_sockaddr_stringify(&addr)); | |
7464 | 7525 | ast_sockaddr_to_sin(&addr_v4, &addr_tmp); |
7465 | 7526 | } else { |
7466 | ast_debug(1, "Cannot do STUN for non IPv4 address %s\n", | |
7467 | ast_sockaddr_stringify(&addr)); | |
7527 | ast_debug_stun(1, "(%p) STUN cannot do for non IPv4 address %s\n", | |
7528 | instance, ast_sockaddr_stringify(&addr)); | |
7468 | 7529 | return &ast_null_frame; |
7469 | 7530 | } |
7470 | 7531 | if ((ast_stun_handle_packet(rtp->s, &addr_tmp, read_area, res, NULL, NULL) == AST_STUN_ACCEPT) && |
7610 | 7671 | break; |
7611 | 7672 | } |
7612 | 7673 | /* Not ready to accept the RTP stream candidate */ |
7613 | ast_debug(1, "%p -- Received RTP packet from %s, dropping due to strict RTP protection. Will switch to it in %d packets.\n", | |
7614 | rtp, ast_sockaddr_stringify(&addr), rtp->rtp_source_learn.packets); | |
7674 | ast_debug_rtp(1, "(%p) RTP %p -- Received packet from %s, dropping due to strict RTP protection. Will switch to it in %d packets.\n", | |
7675 | instance, rtp, ast_sockaddr_stringify(&addr), rtp->rtp_source_learn.packets); | |
7615 | 7676 | } else { |
7616 | 7677 | /* |
7617 | 7678 | * This is either an attacking stream or |
7619 | 7680 | */ |
7620 | 7681 | ast_sockaddr_copy(&rtp->rtp_source_learn.proposed_address, &addr); |
7621 | 7682 | rtp_learning_seq_init(&rtp->rtp_source_learn, seqno); |
7622 | ast_debug(1, "%p -- Received RTP packet from %s, dropping due to strict RTP protection. Qualifying new stream.\n", | |
7623 | rtp, ast_sockaddr_stringify(&addr)); | |
7683 | ast_debug_rtp(1, "(%p) RTP %p -- Received packet from %s, dropping due to strict RTP protection. Qualifying new stream.\n", | |
7684 | instance, rtp, ast_sockaddr_stringify(&addr)); | |
7624 | 7685 | } |
7625 | 7686 | return &ast_null_frame; |
7626 | 7687 | } |
7638 | 7699 | if (!ast_sockaddr_cmp(&rtp->strict_rtp_address, &addr)) { |
7639 | 7700 | break; |
7640 | 7701 | } |
7641 | ast_debug(1, "%p -- Received RTP packet from %s, dropping due to strict RTP protection.\n", | |
7642 | rtp, ast_sockaddr_stringify(&addr)); | |
7702 | ast_debug_rtp(1, "(%p) RTP %p -- Received packet from %s, dropping due to strict RTP protection.\n", | |
7703 | instance, rtp, ast_sockaddr_stringify(&addr)); | |
7643 | 7704 | #ifdef TEST_FRAMEWORK |
7644 | 7705 | { |
7645 | 7706 | static int strict_rtp_test_event = 1; |
7666 | 7727 | ast_sockaddr_set_port(&rtp->rtcp->them, ast_sockaddr_port(&addr) + 1); |
7667 | 7728 | } |
7668 | 7729 | ast_set_flag(rtp, FLAG_NAT_ACTIVE); |
7669 | if (rtpdebug) | |
7670 | ast_debug(0, "RTP NAT: Got audio from other end. Now sending to address %s\n", | |
7671 | ast_sockaddr_stringify(&remote_address)); | |
7730 | if (ast_debug_rtp_packet_is_allowed) | |
7731 | ast_debug(0, "(%p) RTP NAT: Got audio from other end. Now sending to address %s\n", | |
7732 | instance, ast_sockaddr_stringify(&remote_address)); | |
7672 | 7733 | } |
7673 | 7734 | } |
7674 | 7735 | |
7696 | 7757 | }; |
7697 | 7758 | |
7698 | 7759 | if (!mark) { |
7699 | if (rtpdebug) { | |
7700 | ast_debug(1, "Forcing Marker bit, because SSRC has changed\n"); | |
7760 | if (ast_debug_rtp_packet_is_allowed) { | |
7761 | ast_debug(0, "(%p) RTP forcing Marker bit, because SSRC has changed\n", instance); | |
7701 | 7762 | } |
7702 | 7763 | mark = 1; |
7703 | 7764 | } |
7749 | 7810 | |
7750 | 7811 | if (!AST_VECTOR_REMOVE_CMP_ORDERED(&rtp->missing_seqno, seqno, find_by_value, |
7751 | 7812 | AST_VECTOR_ELEM_CLEANUP_NOOP)) { |
7752 | ast_debug(2, "Packet with sequence number '%d' on RTP instance '%p' is no longer missing\n", | |
7753 | seqno, instance); | |
7813 | ast_debug_rtp(2, "(%p) RTP Packet with sequence number '%d' on instance is no longer missing\n", | |
7814 | instance, seqno); | |
7754 | 7815 | } |
7755 | 7816 | |
7756 | 7817 | /* If we don't have the next packet after this we can directly return the frame, as there is no |
7792 | 7853 | return AST_LIST_FIRST(&frames); |
7793 | 7854 | } |
7794 | 7855 | |
7795 | ast_debug(2, "Pulled buffered packet with sequence number '%d' to additionally return on RTP instance '%p'\n", | |
7796 | frame->seqno, instance); | |
7856 | ast_debug_rtp(2, "(%p) RTP pulled buffered packet with sequence number '%d' to additionally return\n", | |
7857 | instance, frame->seqno); | |
7797 | 7858 | AST_LIST_INSERT_TAIL(&frames, frame, frame_list); |
7798 | 7859 | prev_seqno = rtp->expectedrxseqno; |
7799 | 7860 | rtp->expectedrxseqno++; |
7814 | 7875 | */ |
7815 | 7876 | |
7816 | 7877 | if (rtp->rtp_source_learn.stream_type == AST_MEDIA_TYPE_VIDEO) { |
7817 | ast_debug(2, "Source on RTP instance '%p' has wild gap or packet loss, sending FIR\n", | |
7878 | ast_debug_rtp(2, "(%p) RTP source has wild gap or packet loss, sending FIR\n", | |
7818 | 7879 | instance); |
7819 | 7880 | rtp_write_rtcp_fir(instance, rtp, &remote_address); |
7820 | 7881 | } |
7832 | 7893 | if (frame) { |
7833 | 7894 | AST_LIST_INSERT_TAIL(&frames, frame, frame_list); |
7834 | 7895 | prev_seqno = seqno; |
7835 | ast_debug(2, "Inserted just received packet with sequence number '%d' in correct order on RTP instance '%p'\n", | |
7836 | seqno, instance); | |
7896 | ast_debug_rtp(2, "(%p) RTP inserted just received packet with sequence number '%d' in correct order\n", | |
7897 | instance, seqno); | |
7837 | 7898 | } |
7838 | 7899 | /* It is possible due to packet retransmission for this packet to also exist in the receive |
7839 | 7900 | * buffer so we explicitly remove it in case this occurs, otherwise the receive buffer will |
7857 | 7918 | if (frame) { |
7858 | 7919 | AST_LIST_INSERT_TAIL(&frames, frame, frame_list); |
7859 | 7920 | prev_seqno = rtp->expectedrxseqno; |
7860 | ast_debug(2, "Emptying queue and returning packet with sequence number '%d' from RTP instance '%p'\n", | |
7861 | frame->seqno, instance); | |
7921 | ast_debug_rtp(2, "(%p) RTP emptying queue and returning packet with sequence number '%d'\n", | |
7922 | instance, frame->seqno); | |
7862 | 7923 | } |
7863 | 7924 | ast_free(payload); |
7864 | 7925 | } |
7882 | 7943 | rtp->expectedrxseqno = 0; |
7883 | 7944 | } |
7884 | 7945 | |
7885 | ast_debug(2, "Adding just received packet with sequence number '%d' to end of dumped queue on RTP instance '%p'\n", | |
7886 | seqno, instance); | |
7946 | ast_debug_rtp(2, "(%p) RTP adding just received packet with sequence number '%d' to end of dumped queue\n", | |
7947 | instance, seqno); | |
7887 | 7948 | } |
7888 | 7949 | |
7889 | 7950 | /* When we flush increase our chance for next time by growing the receive buffer when possible |
7891 | 7952 | */ |
7892 | 7953 | ast_data_buffer_resize(rtp->recv_buffer, MIN(MAXIMUM_RTP_RECV_BUFFER_SIZE, |
7893 | 7954 | ast_data_buffer_max(rtp->recv_buffer) + AST_VECTOR_SIZE(&rtp->missing_seqno))); |
7894 | ast_debug(2, "Receive buffer on RTP instance '%p' is now at maximum of %zu\n", instance, ast_data_buffer_max(rtp->recv_buffer)); | |
7955 | ast_debug_rtp(2, "(%p) RTP receive buffer is now at maximum of %zu\n", instance, ast_data_buffer_max(rtp->recv_buffer)); | |
7895 | 7956 | |
7896 | 7957 | /* As there is such a large gap we don't want to flood the order side with missing packets, so we |
7897 | 7958 | * give up and start anew. |
7917 | 7978 | if ((seqno < rtp->expectedrxseqno && ((rtp->expectedrxseqno - seqno) <= OLD_PACKET_COUNT)) || |
7918 | 7979 | (seqno > rtp->expectedrxseqno && (seqno >= (65535 - OLD_PACKET_COUNT + rtp->expectedrxseqno)))) { |
7919 | 7980 | /* If this is a packet from the past then we have received a duplicate packet, so just drop it */ |
7920 | ast_debug(2, "Received an old packet with sequence number '%d' on RTP instance '%p', dropping it\n", | |
7921 | seqno, instance); | |
7981 | ast_debug_rtp(2, "(%p) RTP received an old packet with sequence number '%d', dropping it\n", | |
7982 | instance, seqno); | |
7922 | 7983 | return &ast_null_frame; |
7923 | 7984 | } else if (ast_data_buffer_get(rtp->recv_buffer, seqno)) { |
7924 | 7985 | /* If this is a packet we already have buffered then it is a duplicate, so just drop it */ |
7925 | ast_debug(2, "Received a duplicate transmission of packet with sequence number '%d' on RTP instance '%p', dropping it\n", | |
7926 | seqno, instance); | |
7986 | ast_debug_rtp(2, "(%p) RTP received a duplicate transmission of packet with sequence number '%d', dropping it\n", | |
7987 | instance, seqno); | |
7927 | 7988 | return &ast_null_frame; |
7928 | 7989 | } else { |
7929 | 7990 | /* This is an out of order packet from the future */ |
7932 | 7993 | int remove_failed; |
7933 | 7994 | unsigned int missing_seqnos_added = 0; |
7934 | 7995 | |
7935 | ast_debug(2, "Received an out of order packet with sequence number '%d' while expecting '%d' from the future on RTP instance '%p'\n", | |
7936 | seqno, rtp->expectedrxseqno, instance); | |
7996 | ast_debug_rtp(2, "(%p) RTP received an out of order packet with sequence number '%d' while expecting '%d' from the future\n", | |
7997 | instance, seqno, rtp->expectedrxseqno); | |
7937 | 7998 | |
7938 | 7999 | payload = ast_malloc(sizeof(*payload) + res); |
7939 | 8000 | if (!payload) { |
7960 | 8021 | remove_failed = AST_VECTOR_REMOVE_CMP_ORDERED(&rtp->missing_seqno, seqno, find_by_value, |
7961 | 8022 | AST_VECTOR_ELEM_CLEANUP_NOOP); |
7962 | 8023 | if (!remove_failed) { |
7963 | ast_debug(2, "Packet with sequence number '%d' on RTP instance '%p' is no longer missing\n", | |
7964 | seqno, instance); | |
8024 | ast_debug_rtp(2, "(%p) RTP packet with sequence number '%d' is no longer missing\n", | |
8025 | instance, seqno); | |
7965 | 8026 | } |
7966 | 8027 | |
7967 | 8028 | /* The missing sequence number code works by taking the sequence number of the |
8007 | 8068 | continue; |
8008 | 8069 | } |
8009 | 8070 | |
8010 | ast_debug(2, "Added missing sequence number '%d' to RTP instance '%p'\n", | |
8011 | missing_seqno, instance); | |
8071 | ast_debug_rtp(2, "(%p) RTP added missing sequence number '%d'\n", | |
8072 | instance, missing_seqno); | |
8012 | 8073 | AST_VECTOR_ADD_SORTED(&rtp->missing_seqno, missing_seqno, |
8013 | 8074 | compare_by_value); |
8014 | 8075 | missing_seqnos_added++; |
8038 | 8099 | */ |
8039 | 8100 | rtcpheader = ast_malloc(sizeof(*rtcpheader) + data_size); |
8040 | 8101 | if (!rtcpheader) { |
8041 | ast_debug(1, "Failed to allocate memory for NACK\n"); | |
8102 | ast_debug_rtcp(1, "(%p) RTCP failed to allocate memory for NACK\n", instance); | |
8042 | 8103 | return &ast_null_frame; |
8043 | 8104 | } |
8044 | 8105 | |
8055 | 8116 | res = ast_rtcp_generate_nack(instance, rtcpheader + packet_len); |
8056 | 8117 | |
8057 | 8118 | if (res == 0) { |
8058 | ast_debug(1, "Failed to construct NACK, stopping here\n"); | |
8119 | ast_debug_rtcp(1, "(%p) RTCP failed to construct NACK, stopping here\n", instance); | |
8059 | 8120 | return &ast_null_frame; |
8060 | 8121 | } |
8061 | 8122 | |
8063 | 8124 | |
8064 | 8125 | res = rtcp_sendto(instance, rtcpheader, packet_len, 0, &remote_address, &ice); |
8065 | 8126 | if (res < 0) { |
8066 | ast_debug(1, "Failed to send NACK request out\n"); | |
8127 | ast_debug_rtcp(1, "(%p) RTCP failed to send NACK request out\n", instance); | |
8067 | 8128 | } else { |
8068 | ast_debug(2, "Sending a NACK request on RTP instance '%p' to get missing packets\n", instance); | |
8129 | ast_debug_rtcp(2, "(%p) RTCP sending a NACK request to get missing packets\n", instance); | |
8069 | 8130 | /* Update RTCP SR/RR statistics */ |
8070 | 8131 | ast_rtcp_calculate_sr_rr_statistics(instance, rtcp_report, remote_address, ice, sr); |
8071 | 8132 | } |
8085 | 8146 | struct ast_sockaddr local_addr; |
8086 | 8147 | |
8087 | 8148 | if (rtp->rtcp && rtp->rtcp->type == value) { |
8088 | ast_debug(1, "Ignoring duplicate RTCP property on RTP instance '%p'\n", instance); | |
8149 | ast_debug_rtcp(1, "(%p) RTCP ignoring duplicate property\n", instance); | |
8089 | 8150 | return; |
8090 | 8151 | } |
8091 | 8152 | |
8137 | 8198 | AF_INET : |
8138 | 8199 | ast_sockaddr_is_ipv6(&rtp->rtcp->us) ? |
8139 | 8200 | AF_INET6 : -1)) < 0) { |
8140 | ast_debug(1, "Failed to create a new socket for RTCP on instance '%p'\n", instance); | |
8201 | ast_debug_rtcp(1, "(%p) RTCP failed to create a new socket\n", instance); | |
8141 | 8202 | ast_free(rtp->rtcp->local_addr_str); |
8142 | 8203 | ast_free(rtp->rtcp); |
8143 | 8204 | rtp->rtcp = NULL; |
8146 | 8207 | |
8147 | 8208 | /* Try to actually bind to the IP address and port we are going to use for RTCP, if this fails we have to bail out */ |
8148 | 8209 | if (ast_bind(rtp->rtcp->s, &rtp->rtcp->us)) { |
8149 | ast_debug(1, "Failed to setup RTCP on RTP instance '%p'\n", instance); | |
8210 | ast_debug_rtcp(1, "(%p) RTCP failed to setup RTP instance\n", instance); | |
8150 | 8211 | close(rtp->rtcp->s); |
8151 | 8212 | ast_free(rtp->rtcp->local_addr_str); |
8152 | 8213 | ast_free(rtp->rtcp); |
8186 | 8247 | #endif |
8187 | 8248 | } |
8188 | 8249 | |
8189 | ast_debug(1, "Setup RTCP on RTP instance '%p'\n", instance); | |
8250 | ast_debug_rtcp(1, "(%p) RTCP setup on RTP instance\n", instance); | |
8190 | 8251 | } else { |
8191 | 8252 | if (rtp->rtcp) { |
8192 | 8253 | if (rtp->rtcp->schedid > -1) { |
8196 | 8257 | ao2_ref(instance, -1); |
8197 | 8258 | } else { |
8198 | 8259 | /* Unable to cancel scheduler entry */ |
8199 | ast_debug(1, "Failed to tear down RTCP on RTP instance '%p'\n", instance); | |
8260 | ast_debug_rtcp(1, "(%p) RTCP failed to tear down RTCP\n", instance); | |
8200 | 8261 | ao2_lock(instance); |
8201 | 8262 | return; |
8202 | 8263 | } |
8208 | 8269 | if (!ast_sched_del(rtp->sched, rtp->transport_wide_cc.schedid)) { |
8209 | 8270 | ao2_ref(instance, -1); |
8210 | 8271 | } else { |
8211 | ast_debug(1, "Failed to tear down RTCP transport-cc feedback on RTP instance '%p'\n", instance); | |
8272 | ast_debug_rtcp(1, "(%p) RTCP failed to tear down transport-cc feedback\n", instance); | |
8212 | 8273 | ao2_lock(instance); |
8213 | 8274 | return; |
8214 | 8275 | } |
8288 | 8349 | } |
8289 | 8350 | |
8290 | 8351 | if (rtp->rtcp && !ast_sockaddr_isnull(addr)) { |
8291 | ast_debug(1, "Setting RTCP address on RTP instance '%p'\n", instance); | |
8352 | ast_debug_rtcp(1, "(%p) RTCP setting address on RTP instance\n", instance); | |
8292 | 8353 | ast_sockaddr_copy(&rtp->rtcp->them, addr); |
8293 | 8354 | |
8294 | 8355 | if (rtp->rtcp->type == AST_RTP_INSTANCE_RTCP_STANDARD) { |
8784 | 8845 | } |
8785 | 8846 | #endif |
8786 | 8847 | |
8787 | ast_debug(3, "ast_rtp_activate (%p) - setup and perform DTLS'\n", rtp); | |
8848 | ast_debug_dtls(3, "(%p) DTLS - ast_rtp_activate rtp=%p - setup and perform DTLS'\n", instance, rtp); | |
8788 | 8849 | |
8789 | 8850 | dtls_perform_setup(&rtp->dtls); |
8790 | 8851 | dtls_perform_handshake(instance, &rtp->dtls, 0); |
8809 | 8870 | return CLI_FAILURE; |
8810 | 8871 | } |
8811 | 8872 | rtpdebugport = (!ast_strlen_zero(debugport) && debugport[0] != '0'); |
8812 | ast_cli(a->fd, "RTP Debugging Enabled for address: %s\n", | |
8873 | ast_cli(a->fd, "RTP Packet Debugging Enabled for address: %s\n", | |
8813 | 8874 | ast_sockaddr_stringify(&rtpdebugaddr)); |
8814 | rtpdebug = 1; | |
8875 | ast_debug_category_set_sublevel(AST_LOG_CATEGORY_RTP_PACKET, AST_LOG_CATEGORY_ENABLED); | |
8815 | 8876 | return CLI_SUCCESS; |
8816 | 8877 | } |
8817 | 8878 | |
8826 | 8887 | return CLI_FAILURE; |
8827 | 8888 | } |
8828 | 8889 | rtcpdebugport = (!ast_strlen_zero(debugport) && debugport[0] != '0'); |
8829 | ast_cli(a->fd, "RTCP Debugging Enabled for address: %s\n", | |
8890 | ast_cli(a->fd, "RTCP Packet Debugging Enabled for address: %s\n", | |
8830 | 8891 | ast_sockaddr_stringify(&rtcpdebugaddr)); |
8831 | rtcpdebug = 1; | |
8892 | ast_debug_category_set_sublevel(AST_LOG_CATEGORY_RTCP_PACKET, AST_LOG_CATEGORY_ENABLED); | |
8832 | 8893 | return CLI_SUCCESS; |
8833 | 8894 | } |
8834 | 8895 | |
8849 | 8910 | |
8850 | 8911 | if (a->argc == e->args) { /* set on or off */ |
8851 | 8912 | if (!strncasecmp(a->argv[e->args-1], "on", 2)) { |
8852 | rtpdebug = 1; | |
8913 | ast_debug_category_set_sublevel(AST_LOG_CATEGORY_RTP_PACKET, AST_LOG_CATEGORY_ENABLED); | |
8853 | 8914 | memset(&rtpdebugaddr, 0, sizeof(rtpdebugaddr)); |
8854 | ast_cli(a->fd, "RTP Debugging Enabled\n"); | |
8915 | ast_cli(a->fd, "RTP Packet Debugging Enabled\n"); | |
8855 | 8916 | return CLI_SUCCESS; |
8856 | 8917 | } else if (!strncasecmp(a->argv[e->args-1], "off", 3)) { |
8857 | rtpdebug = 0; | |
8858 | ast_cli(a->fd, "RTP Debugging Disabled\n"); | |
8918 | ast_debug_category_set_sublevel(AST_LOG_CATEGORY_RTP_PACKET, AST_LOG_CATEGORY_DISABLED); | |
8919 | ast_cli(a->fd, "RTP Packet Debugging Disabled\n"); | |
8859 | 8920 | return CLI_SUCCESS; |
8860 | 8921 | } |
8861 | 8922 | } else if (a->argc == e->args +1) { /* ip */ |
8920 | 8981 | |
8921 | 8982 | if (a->argc == e->args) { /* set on or off */ |
8922 | 8983 | if (!strncasecmp(a->argv[e->args-1], "on", 2)) { |
8923 | rtcpdebug = 1; | |
8984 | ast_debug_category_set_sublevel(AST_LOG_CATEGORY_RTCP_PACKET, AST_LOG_CATEGORY_ENABLED); | |
8924 | 8985 | memset(&rtcpdebugaddr, 0, sizeof(rtcpdebugaddr)); |
8925 | ast_cli(a->fd, "RTCP Debugging Enabled\n"); | |
8986 | ast_cli(a->fd, "RTCP Packet Debugging Enabled\n"); | |
8926 | 8987 | return CLI_SUCCESS; |
8927 | 8988 | } else if (!strncasecmp(a->argv[e->args-1], "off", 3)) { |
8928 | rtcpdebug = 0; | |
8929 | ast_cli(a->fd, "RTCP Debugging Disabled\n"); | |
8989 | ast_debug_category_set_sublevel(AST_LOG_CATEGORY_RTCP_PACKET, AST_LOG_CATEGORY_DISABLED); | |
8990 | ast_cli(a->fd, "RTCP Packet Debugging Disabled\n"); | |
8930 | 8991 | return CLI_SUCCESS; |
8931 | 8992 | } |
8932 | 8993 | } else if (a->argc == e->args +1) { /* ip */ |
424 | 424 | ); |
425 | 425 | }; |
426 | 426 | |
427 | /*! AO2 comparison function for bridges moh container */ | |
428 | static int bridges_channel_compare(void *obj, void *arg, int flags) | |
429 | { | |
430 | const struct stasis_app_bridge_channel_wrapper *object_left = obj; | |
431 | const struct stasis_app_bridge_channel_wrapper *object_right = arg; | |
432 | const char *right_key = arg; | |
433 | int cmp; | |
434 | ||
435 | switch (flags & OBJ_SEARCH_MASK) { | |
436 | case OBJ_SEARCH_OBJECT: | |
437 | right_key = object_right->bridge_id; | |
438 | case OBJ_SEARCH_KEY: | |
439 | cmp = strcmp(object_left->bridge_id, right_key); | |
440 | break; | |
441 | case OBJ_SEARCH_PARTIAL_KEY: | |
442 | cmp = strncmp(object_left->bridge_id, right_key, strlen(right_key)); | |
443 | break; | |
444 | default: | |
445 | cmp = 0; | |
446 | break; | |
447 | } | |
448 | if (cmp) { | |
449 | return 0; | |
450 | } | |
451 | return CMP_MATCH; | |
452 | } | |
453 | ||
427 | 454 | static void stasis_app_bridge_channel_wrapper_destructor(void *obj) |
428 | 455 | { |
429 | 456 | struct stasis_app_bridge_channel_wrapper *wrapper = obj; |
791 | 818 | capabilities &= ~AST_BRIDGE_CAPABILITY_NATIVE; |
792 | 819 | } else if (!strcmp(requested_type, "video_sfu")) { |
793 | 820 | video_mode = AST_BRIDGE_VIDEO_MODE_SFU; |
821 | } else if (!strcmp(requested_type, "video_single")) { | |
822 | video_mode = AST_BRIDGE_VIDEO_MODE_SINGLE_SRC; | |
794 | 823 | } |
795 | 824 | } |
796 | 825 | |
2320 | 2349 | BRIDGES_NUM_BUCKETS, bridges_hash, NULL, bridges_compare); |
2321 | 2350 | app_bridges_moh = ao2_container_alloc_hash( |
2322 | 2351 | AO2_ALLOC_OPT_LOCK_MUTEX, 0, |
2323 | 37, bridges_channel_hash_fn, NULL, NULL); | |
2352 | 37, bridges_channel_hash_fn, NULL, bridges_channel_compare); | |
2324 | 2353 | app_bridges_playback = ao2_container_alloc_hash( |
2325 | 2354 | AO2_ALLOC_OPT_LOCK_MUTEX, AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, |
2326 | 2355 | 37, bridges_channel_hash_fn, bridges_channel_sort_fn, NULL); |
0 | /* | |
1 | * Asterisk -- An open source telephony toolkit. | |
2 | * | |
3 | * Copyright (C) 2020, Sangoma Technologies Corporation | |
4 | * | |
5 | * Kevin Harwell <kharwell@digium.com> | |
6 | * | |
7 | * See http://www.asterisk.org for more information about | |
8 | * the Asterisk project. Please do not directly contact | |
9 | * any of the maintainers of this project for assistance; | |
10 | * the project provides a web site, mailing lists and IRC | |
11 | * channels for your use. | |
12 | * | |
13 | * This program is free software, distributed under the terms of | |
14 | * the GNU General Public License Version 2. See the LICENSE file | |
15 | * at the top of the source tree. | |
16 | */ | |
17 | ||
18 | #include "asterisk.h" | |
19 | ||
20 | #include <sys/stat.h> | |
21 | ||
22 | #include "asterisk/cli.h" | |
23 | #include "asterisk/sorcery.h" | |
24 | ||
25 | #include "stir_shaken.h" | |
26 | #include "certificate.h" | |
27 | #include "asterisk/res_stir_shaken.h" | |
28 | ||
29 | #define CONFIG_TYPE "certificate" | |
30 | ||
31 | struct stir_shaken_certificate { | |
32 | SORCERY_OBJECT(details); | |
33 | AST_DECLARE_STRING_FIELDS( | |
34 | /*! Path to a directory containing certificates */ | |
35 | AST_STRING_FIELD(path); | |
36 | /*! URL to the public key */ | |
37 | AST_STRING_FIELD(public_key_url); | |
38 | /*! The caller ID number associated with the certificate */ | |
39 | AST_STRING_FIELD(caller_id_number); | |
40 | /*! The attestation level for this certificate */ | |
41 | AST_STRING_FIELD(attestation); | |
42 | /*! The origination ID for this certificate */ | |
43 | AST_STRING_FIELD(origid); | |
44 | ); | |
45 | /*! The private key for the certificate */ | |
46 | EVP_PKEY *private_key; | |
47 | }; | |
48 | ||
49 | static struct stir_shaken_certificate *stir_shaken_certificate_get(const char *id) | |
50 | { | |
51 | return ast_sorcery_retrieve_by_id(ast_stir_shaken_sorcery(), CONFIG_TYPE, id); | |
52 | } | |
53 | ||
54 | static struct ao2_container *stir_shaken_certificate_get_all(void) | |
55 | { | |
56 | return ast_sorcery_retrieve_by_fields(ast_stir_shaken_sorcery(), CONFIG_TYPE, | |
57 | AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL); | |
58 | } | |
59 | ||
60 | static void stir_shaken_certificate_destructor(void *obj) | |
61 | { | |
62 | struct stir_shaken_certificate *cfg = obj; | |
63 | ||
64 | EVP_PKEY_free(cfg->private_key); | |
65 | ast_string_field_free_memory(cfg); | |
66 | } | |
67 | ||
68 | static void *stir_shaken_certificate_alloc(const char *name) | |
69 | { | |
70 | struct stir_shaken_certificate *cfg; | |
71 | ||
72 | cfg = ast_sorcery_generic_alloc(sizeof(*cfg), stir_shaken_certificate_destructor); | |
73 | if (!cfg) { | |
74 | return NULL; | |
75 | } | |
76 | ||
77 | if (ast_string_field_init(cfg, 512)) { | |
78 | ao2_ref(cfg, -1); | |
79 | return NULL; | |
80 | } | |
81 | ||
82 | return cfg; | |
83 | } | |
84 | ||
85 | struct stir_shaken_certificate *stir_shaken_certificate_get_by_caller_id_number(const char *caller_id_number) | |
86 | { | |
87 | struct ast_variable fields = { | |
88 | .name = "caller_id_number", | |
89 | .value = caller_id_number, | |
90 | .next = NULL, | |
91 | }; | |
92 | ||
93 | return ast_sorcery_retrieve_by_fields(ast_stir_shaken_sorcery(), | |
94 | "certificate", AST_RETRIEVE_FLAG_DEFAULT, &fields); | |
95 | } | |
96 | ||
97 | const char *stir_shaken_certificate_get_public_key_url(struct stir_shaken_certificate *cert) | |
98 | { | |
99 | return cert ? cert->public_key_url : NULL; | |
100 | } | |
101 | ||
102 | const char *stir_shaken_certificate_get_attestation(struct stir_shaken_certificate *cert) | |
103 | { | |
104 | return cert ? cert->attestation : NULL; | |
105 | } | |
106 | ||
107 | const char *stir_shaken_certificate_get_origid(struct stir_shaken_certificate *cert) | |
108 | { | |
109 | return cert ? cert->origid : NULL; | |
110 | } | |
111 | ||
112 | EVP_PKEY *stir_shaken_certificate_get_private_key(struct stir_shaken_certificate *cert) | |
113 | { | |
114 | return cert ? cert->private_key : NULL; | |
115 | } | |
116 | ||
117 | static int stir_shaken_certificate_apply(const struct ast_sorcery *sorcery, void *obj) | |
118 | { | |
119 | EVP_PKEY *private_key; | |
120 | struct stir_shaken_certificate *cert = obj; | |
121 | ||
122 | if (ast_strlen_zero(cert->caller_id_number)) { | |
123 | ast_log(LOG_ERROR, "Caller ID must be present\n"); | |
124 | return -1; | |
125 | } | |
126 | ||
127 | if (ast_strlen_zero(cert->attestation)) { | |
128 | ast_log(LOG_ERROR, "Attestation must be present\n"); | |
129 | return -1; | |
130 | } | |
131 | ||
132 | private_key = stir_shaken_read_key(cert->path, 1); | |
133 | if (!private_key) { | |
134 | return -1; | |
135 | } | |
136 | ||
137 | cert->private_key = private_key; | |
138 | ||
139 | return 0; | |
140 | } | |
141 | ||
142 | static char *stir_shaken_certificate_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) | |
143 | { | |
144 | struct stir_shaken_certificate *cfg; | |
145 | ||
146 | switch(cmd) { | |
147 | case CLI_INIT: | |
148 | e->command = "stir_shaken show certificate"; | |
149 | e->usage = | |
150 | "Usage: stir_shaken show certificate <id>\n" | |
151 | " Show the certificate stir/shaken settings for a given id\n"; | |
152 | return NULL; | |
153 | case CLI_GENERATE: | |
154 | if (a->pos == 3) { | |
155 | return stir_shaken_tab_complete_name(a->word, stir_shaken_certificate_get_all()); | |
156 | } else { | |
157 | return NULL; | |
158 | } | |
159 | } | |
160 | ||
161 | if (a->argc != 4) { | |
162 | return CLI_SHOWUSAGE; | |
163 | } | |
164 | ||
165 | cfg = stir_shaken_certificate_get(a->argv[3]); | |
166 | stir_shaken_cli_show(cfg, a, 0); | |
167 | ao2_cleanup(cfg); | |
168 | ||
169 | return CLI_SUCCESS; | |
170 | } | |
171 | ||
172 | static char *stir_shaken_certificate_show_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) | |
173 | { | |
174 | struct ao2_container *container; | |
175 | ||
176 | switch(cmd) { | |
177 | case CLI_INIT: | |
178 | e->command = "stir_shaken show certificates"; | |
179 | e->usage = | |
180 | "Usage: stir_shaken show certificates\n" | |
181 | " Show all configured certificates for stir/shaken\n"; | |
182 | return NULL; | |
183 | case CLI_GENERATE: | |
184 | return NULL; | |
185 | } | |
186 | ||
187 | if (a->argc != 3) { | |
188 | return CLI_SHOWUSAGE; | |
189 | } | |
190 | ||
191 | container = stir_shaken_certificate_get_all(); | |
192 | if (!container || ao2_container_count(container) == 0) { | |
193 | ast_cli(a->fd, "No stir/shaken certificates found\n"); | |
194 | ao2_cleanup(container); | |
195 | return CLI_SUCCESS; | |
196 | } | |
197 | ||
198 | ao2_callback(container, OBJ_NODATA, stir_shaken_cli_show, a); | |
199 | ao2_ref(container, -1); | |
200 | ||
201 | return CLI_SUCCESS; | |
202 | } | |
203 | ||
204 | static struct ast_cli_entry stir_shaken_certificate_cli[] = { | |
205 | AST_CLI_DEFINE(stir_shaken_certificate_show, "Show stir/shaken certificate configuration by id"), | |
206 | AST_CLI_DEFINE(stir_shaken_certificate_show_all, "Show all stir/shaken certificate configurations"), | |
207 | }; | |
208 | ||
209 | static int on_load_path(const struct aco_option *opt, struct ast_variable *var, void *obj) | |
210 | { | |
211 | struct stir_shaken_certificate *cfg = obj; | |
212 | struct stat statbuf; | |
213 | ||
214 | if (stat(var->value, &statbuf)) { | |
215 | ast_log(LOG_ERROR, "stir/shaken - path '%s' not found\n", var->value); | |
216 | return -1; | |
217 | } | |
218 | ||
219 | if (!S_ISREG(statbuf.st_mode)) { | |
220 | ast_log(LOG_ERROR, "stir/shaken - path '%s' is not a file\n", var->value); | |
221 | return -1; | |
222 | } | |
223 | ||
224 | return ast_string_field_set(cfg, path, var->value); | |
225 | } | |
226 | ||
227 | static int path_to_str(const void *obj, const intptr_t *args, char **buf) | |
228 | { | |
229 | const struct stir_shaken_certificate *cfg = obj; | |
230 | ||
231 | *buf = ast_strdup(cfg->path); | |
232 | ||
233 | return 0; | |
234 | } | |
235 | ||
236 | static int on_load_public_key_url(const struct aco_option *opt, struct ast_variable *var, void *obj) | |
237 | { | |
238 | struct stir_shaken_certificate *cfg = obj; | |
239 | ||
240 | if (!ast_begins_with(var->value, "http")) { | |
241 | ast_log(LOG_ERROR, "stir/shaken - public_key_url scheme must be 'http[s]'\n"); | |
242 | return -1; | |
243 | } | |
244 | ||
245 | return ast_string_field_set(cfg, public_key_url, var->value); | |
246 | } | |
247 | ||
248 | static int public_key_url_to_str(const void *obj, const intptr_t *args, char **buf) | |
249 | { | |
250 | const struct stir_shaken_certificate *cfg = obj; | |
251 | ||
252 | *buf = ast_strdup(cfg->public_key_url); | |
253 | ||
254 | return 0; | |
255 | } | |
256 | ||
257 | static int on_load_attestation(const struct aco_option *opt, struct ast_variable *var, void *obj) | |
258 | { | |
259 | struct stir_shaken_certificate *cfg = obj; | |
260 | ||
261 | if (strcmp(var->value, "A") && strcmp(var->value, "B") && strcmp(var->value, "C")) { | |
262 | ast_log(LOG_ERROR, "stir/shaken - attestation level must be A, B, or C (object=%s)\n", | |
263 | ast_sorcery_object_get_id(cfg)); | |
264 | return -1; | |
265 | } | |
266 | ||
267 | return ast_string_field_set(cfg, attestation, var->value); | |
268 | } | |
269 | ||
270 | static int attestation_to_str(const void *obj, const intptr_t *args, char **buf) | |
271 | { | |
272 | const struct stir_shaken_certificate *cfg = obj; | |
273 | ||
274 | *buf = ast_strdup(cfg->attestation); | |
275 | ||
276 | return 0; | |
277 | } | |
278 | ||
279 | #ifdef TEST_FRAMEWORK | |
280 | ||
281 | /* Name for test certificaate */ | |
282 | #define TEST_CONFIG_NAME "test_stir_shaken_certificate" | |
283 | /* The public key URL to use for the test certificate */ | |
284 | #define TEST_CONFIG_URL "http://testing123" | |
285 | ||
286 | int test_stir_shaken_cleanup_cert(const char *caller_id_number) | |
287 | { | |
288 | struct stir_shaken_certificate *cert; | |
289 | struct ast_sorcery *sorcery; | |
290 | int res = 0; | |
291 | ||
292 | sorcery = ast_stir_shaken_sorcery(); | |
293 | ||
294 | cert = stir_shaken_certificate_get_by_caller_id_number(caller_id_number); | |
295 | if (!cert) { | |
296 | return 0; | |
297 | } | |
298 | ||
299 | res = ast_sorcery_delete(sorcery, cert); | |
300 | ao2_cleanup(cert); | |
301 | if (res) { | |
302 | ast_log(LOG_ERROR, "Failed to delete sorcery object with caller ID " | |
303 | "'%s'\n", caller_id_number); | |
304 | return -1; | |
305 | } | |
306 | ||
307 | res = ast_sorcery_remove_wizard_mapping(sorcery, CONFIG_TYPE, "memory"); | |
308 | ||
309 | return res; | |
310 | } | |
311 | ||
312 | int test_stir_shaken_create_cert(const char *caller_id_number, const char *file_path) | |
313 | { | |
314 | struct stir_shaken_certificate *cert; | |
315 | struct ast_sorcery *sorcery; | |
316 | EVP_PKEY *private_key; | |
317 | int res = 0; | |
318 | ||
319 | sorcery = ast_stir_shaken_sorcery(); | |
320 | ||
321 | res = ast_sorcery_insert_wizard_mapping(sorcery, CONFIG_TYPE, "memory", "testing", 0, 0); | |
322 | if (res) { | |
323 | ast_log(LOG_ERROR, "Failed to insert STIR/SHAKEN test certificate mapping\n"); | |
324 | return -1; | |
325 | } | |
326 | ||
327 | cert = ast_sorcery_alloc(sorcery, CONFIG_TYPE, TEST_CONFIG_NAME); | |
328 | if (!cert) { | |
329 | ast_log(LOG_ERROR, "Failed to allocate test certificate\n"); | |
330 | return -1; | |
331 | } | |
332 | ||
333 | ast_string_field_set(cert, path, file_path); | |
334 | ast_string_field_set(cert, public_key_url, TEST_CONFIG_URL); | |
335 | ast_string_field_set(cert, caller_id_number, caller_id_number); | |
336 | ||
337 | private_key = stir_shaken_read_key(cert->path, 1); | |
338 | if (!private_key) { | |
339 | ast_log(LOG_ERROR, "Failed to read test key from %s\n", cert->path); | |
340 | test_stir_shaken_cleanup_cert(caller_id_number); | |
341 | return -1; | |
342 | } | |
343 | ||
344 | cert->private_key = private_key; | |
345 | ||
346 | ast_sorcery_create(sorcery, cert); | |
347 | ||
348 | return res; | |
349 | } | |
350 | ||
351 | #endif /* TEST_FRAMEWORK */ | |
352 | ||
353 | int stir_shaken_certificate_unload(void) | |
354 | { | |
355 | ast_cli_unregister_multiple(stir_shaken_certificate_cli, | |
356 | ARRAY_LEN(stir_shaken_certificate_cli)); | |
357 | ||
358 | return 0; | |
359 | } | |
360 | ||
361 | int stir_shaken_certificate_load(void) | |
362 | { | |
363 | struct ast_sorcery *sorcery = ast_stir_shaken_sorcery(); | |
364 | ||
365 | ast_sorcery_apply_default(sorcery, CONFIG_TYPE, "config", "stir_shaken.conf,criteria=type=certificate"); | |
366 | ||
367 | if (ast_sorcery_object_register(sorcery, CONFIG_TYPE, stir_shaken_certificate_alloc, | |
368 | NULL, stir_shaken_certificate_apply)) { | |
369 | ast_log(LOG_ERROR, "stir/shaken - failed to register '%s' sorcery object\n", CONFIG_TYPE); | |
370 | return -1; | |
371 | } | |
372 | ||
373 | ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "type", "", OPT_NOOP_T, 0, 0); | |
374 | ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "path", "", | |
375 | on_load_path, path_to_str, NULL, 0, 0); | |
376 | ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "public_key_url", "", | |
377 | on_load_public_key_url, public_key_url_to_str, NULL, 0, 0); | |
378 | ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "attestation", "", | |
379 | on_load_attestation, attestation_to_str, NULL, 0, 0); | |
380 | ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "origid", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct stir_shaken_certificate, origid)); | |
381 | ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "caller_id_number", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct stir_shaken_certificate, caller_id_number)); | |
382 | ||
383 | ast_cli_register_multiple(stir_shaken_certificate_cli, | |
384 | ARRAY_LEN(stir_shaken_certificate_cli)); | |
385 | ||
386 | return 0; | |
387 | } |
0 | /* | |
1 | * Asterisk -- An open source telephony toolkit. | |
2 | * | |
3 | * Copyright (C) 2020, Sangoma Technologies Corporation | |
4 | * | |
5 | * Kevin Harwell <kharwell@sangoma.com> | |
6 | * | |
7 | * See http://www.asterisk.org for more information about | |
8 | * the Asterisk project. Please do not directly contact | |
9 | * any of the maintainers of this project for assistance; | |
10 | * the project provides a web site, mailing lists and IRC | |
11 | * channels for your use. | |
12 | * | |
13 | * This program is free software, distributed under the terms of | |
14 | * the GNU General Public License Version 2. See the LICENSE file | |
15 | * at the top of the source tree. | |
16 | */ | |
17 | #ifndef _STIR_SHAKEN_CERTIFICATE_H | |
18 | #define _STIR_SHAKEN_CERTIFICATE_H | |
19 | ||
20 | #include <openssl/evp.h> | |
21 | ||
22 | struct ast_sorcery; | |
23 | ||
24 | struct stir_shaken_certificate; | |
25 | ||
26 | /*! | |
27 | * \brief Get a STIR/SHAKEN certificate by caller ID number | |
28 | * | |
29 | * \param callier_id_number The caller ID number | |
30 | * | |
31 | * \retval NULL if not found | |
32 | * \retval The certificate on success | |
33 | */ | |
34 | struct stir_shaken_certificate *stir_shaken_certificate_get_by_caller_id_number(const char *caller_id_number); | |
35 | ||
36 | /*! | |
37 | * \brief Get the public key URL associated with a certificate | |
38 | * | |
39 | * \param cert The certificate to get the public key URL from | |
40 | * | |
41 | * \retval NULL on failure | |
42 | * \retval The public key URL on success | |
43 | */ | |
44 | const char *stir_shaken_certificate_get_public_key_url(struct stir_shaken_certificate *cert); | |
45 | ||
46 | /*! | |
47 | * \brief Get the attestation level associated with a certificate | |
48 | * | |
49 | * \param cert The certificate | |
50 | * | |
51 | * \retval NULL on failure | |
52 | * \retval The attestation on success | |
53 | */ | |
54 | const char *stir_shaken_certificate_get_attestation(struct stir_shaken_certificate *cert); | |
55 | ||
56 | /*! | |
57 | * \brief Get the origination ID associated with a certificate | |
58 | * | |
59 | * \param cert The certificate | |
60 | * | |
61 | * \retval NULL on failure | |
62 | * \retval The origid on success | |
63 | */ | |
64 | const char *stir_shaken_certificate_get_origid(struct stir_shaken_certificate *cert); | |
65 | ||
66 | /*! | |
67 | * \brief Get the private key associated with a certificate | |
68 | * | |
69 | * \param cert The certificate to get the private key from | |
70 | * | |
71 | * \retval NULL on failure | |
72 | * \retval The private key on success | |
73 | */ | |
74 | EVP_PKEY *stir_shaken_certificate_get_private_key(struct stir_shaken_certificate *cert); | |
75 | ||
76 | #ifdef TEST_FRAMEWORK | |
77 | ||
78 | /*! | |
79 | * \brief Clean up the certificate and mappings set up in test_stir_shaken_init | |
80 | * | |
81 | * \param caller_id_number The caller ID of the certificate to clean up | |
82 | * | |
83 | * \retval non-zero on failure | |
84 | * \retval 0 on success | |
85 | */ | |
86 | int test_stir_shaken_cleanup_cert(const char *caller_id_number); | |
87 | ||
88 | /*! | |
89 | * \brief Initialize a test certificate through wizard mappings | |
90 | * | |
91 | * \note test_stir_shaken_cleanup should be called when done with this certificate | |
92 | * | |
93 | * \param caller_id_number The caller ID of the certificate to create | |
94 | * \param file_path The path to the private key for this certificate | |
95 | * | |
96 | * \retval non-zero on failure | |
97 | * \retval 0 on success | |
98 | */ | |
99 | int test_stir_shaken_create_cert(const char *caller_id_number, const char *file_path); | |
100 | ||
101 | #endif /* TEST_FRAMEWORK */ | |
102 | ||
103 | /*! | |
104 | * \brief Load time initialization for the stir/shaken 'certificate' configuration | |
105 | * | |
106 | * \retval 0 on success, -1 on error | |
107 | */ | |
108 | int stir_shaken_certificate_load(void); | |
109 | ||
110 | /*! | |
111 | * \brief Unload time cleanup for the stir/shaken 'certificate' configuration | |
112 | * | |
113 | * \retval 0 on success, -1 on error | |
114 | */ | |
115 | int stir_shaken_certificate_unload(void); | |
116 | ||
117 | #endif /* _STIR_SHAKEN_CERTIFICATE_H */ | |
118 |
0 | /* | |
1 | * Asterisk -- An open source telephony toolkit. | |
2 | * | |
3 | * Copyright (C) 2020, Sangoma Technologies Corporation | |
4 | * | |
5 | * Ben Ford <bford@sangoma.com> | |
6 | * | |
7 | * See http://www.asterisk.org for more information about | |
8 | * the Asterisk project. Please do not directly contact | |
9 | * any of the maintainers of this project for assistance; | |
10 | * the project provides a web site, mailing lists and IRC | |
11 | * channels for your use. | |
12 | * | |
13 | * This program is free software, distributed under the terms of | |
14 | * the GNU General Public License Version 2. See the LICENSE file | |
15 | * at the top of the source tree. | |
16 | */ | |
17 | ||
18 | #include "asterisk.h" | |
19 | ||
20 | #include "asterisk/utils.h" | |
21 | #include "asterisk/logger.h" | |
22 | #include "curl.h" | |
23 | #include "general.h" | |
24 | ||
25 | #include <curl/curl.h> | |
26 | ||
27 | /* Used to check CURL headers */ | |
28 | #define MAX_HEADER_LENGTH 1023 | |
29 | ||
30 | /* Used for CURL requests */ | |
31 | #define GLOBAL_USERAGENT "asterisk-libcurl-agent/1.0" | |
32 | ||
33 | /* CURL callback data to avoid storing useless info in AstDB */ | |
34 | struct curl_cb_data { | |
35 | char *cache_control; | |
36 | char *expires; | |
37 | }; | |
38 | ||
39 | struct curl_cb_data *curl_cb_data_create(void) | |
40 | { | |
41 | struct curl_cb_data *data; | |
42 | ||
43 | data = ast_calloc(1, sizeof(*data)); | |
44 | ||
45 | return data; | |
46 | } | |
47 | ||
48 | void curl_cb_data_free(struct curl_cb_data *data) | |
49 | { | |
50 | if (!data) { | |
51 | return; | |
52 | } | |
53 | ||
54 | ast_free(data->cache_control); | |
55 | ast_free(data->expires); | |
56 | ||
57 | ast_free(data); | |
58 | } | |
59 | ||
60 | char *curl_cb_data_get_cache_control(const struct curl_cb_data *data) | |
61 | { | |
62 | if (!data) { | |
63 | return NULL; | |
64 | } | |
65 | ||
66 | return data->cache_control; | |
67 | } | |
68 | ||
69 | char *curl_cb_data_get_expires(const struct curl_cb_data *data) | |
70 | { | |
71 | if (!data) { | |
72 | return NULL; | |
73 | } | |
74 | ||
75 | return data->expires; | |
76 | } | |
77 | ||
78 | /*! | |
79 | * \brief Called when a CURL request completes | |
80 | * | |
81 | * \param data The curl_cb_data structure to store expiration info | |
82 | */ | |
83 | static size_t curl_header_callback(char *buffer, size_t size, size_t nitems, void *data) | |
84 | { | |
85 | struct curl_cb_data *cb_data = data; | |
86 | size_t realsize; | |
87 | char *header; | |
88 | char *value; | |
89 | ||
90 | realsize = size * nitems; | |
91 | ||
92 | if (realsize > MAX_HEADER_LENGTH) { | |
93 | ast_log(LOG_WARNING, "CURL header length is too large (size: '%zu' | max: '%d')\n", | |
94 | realsize, MAX_HEADER_LENGTH); | |
95 | return 0; | |
96 | } | |
97 | ||
98 | header = ast_alloca(realsize + 1); | |
99 | memcpy(header, buffer, realsize); | |
100 | header[realsize] = '\0'; | |
101 | value = strchr(header, ':'); | |
102 | if (!value) { | |
103 | return realsize; | |
104 | } | |
105 | *value++ = '\0'; | |
106 | value = ast_trim_blanks(ast_skip_blanks(value)); | |
107 | ||
108 | if (!strcasecmp(header, "Cache-Control")) { | |
109 | cb_data->cache_control = ast_strdup(value); | |
110 | } else if (!strcasecmp(header, "Expires")) { | |
111 | cb_data->expires = ast_strdup(value); | |
112 | } | |
113 | ||
114 | return realsize; | |
115 | } | |
116 | ||
117 | /*! | |
118 | * \brief Prepare a CURL instance to use | |
119 | * | |
120 | * \param data The CURL callback data | |
121 | * | |
122 | * \retval NULL on failure | |
123 | * \retval CURL instance on success | |
124 | */ | |
125 | static CURL *get_curl_instance(struct curl_cb_data *data) | |
126 | { | |
127 | CURL *curl; | |
128 | struct stir_shaken_general *cfg; | |
129 | unsigned int curl_timeout; | |
130 | ||
131 | cfg = stir_shaken_general_get(); | |
132 | curl_timeout = ast_stir_shaken_curl_timeout(cfg); | |
133 | ao2_cleanup(cfg); | |
134 | ||
135 | curl = curl_easy_init(); | |
136 | if (!curl) { | |
137 | return NULL; | |
138 | } | |
139 | ||
140 | curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); | |
141 | curl_easy_setopt(curl, CURLOPT_TIMEOUT, curl_timeout); | |
142 | curl_easy_setopt(curl, CURLOPT_USERAGENT, GLOBAL_USERAGENT); | |
143 | curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); | |
144 | curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_header_callback); | |
145 | curl_easy_setopt(curl, CURLOPT_HEADERDATA, data); | |
146 | ||
147 | return curl; | |
148 | } | |
149 | ||
150 | int curl_public_key(const char *public_key_url, const char *path, struct curl_cb_data *data) | |
151 | { | |
152 | FILE *public_key_file; | |
153 | long http_code; | |
154 | CURL *curl; | |
155 | char curl_errbuf[CURL_ERROR_SIZE + 1]; | |
156 | char hash[41]; | |
157 | ||
158 | ast_sha1_hash(hash, public_key_url); | |
159 | ||
160 | curl_errbuf[CURL_ERROR_SIZE] = '\0'; | |
161 | ||
162 | public_key_file = fopen(path, "wb"); | |
163 | if (!public_key_file) { | |
164 | ast_log(LOG_ERROR, "Failed to open file '%s' to write public key from '%s': %s (%d)\n", | |
165 | path, public_key_url, strerror(errno), errno); | |
166 | return -1; | |
167 | } | |
168 | ||
169 | curl = get_curl_instance(data); | |
170 | if (!curl) { | |
171 | ast_log(LOG_ERROR, "Failed to set up CURL isntance for '%s'\n", public_key_url); | |
172 | fclose(public_key_file); | |
173 | return -1; | |
174 | } | |
175 | ||
176 | curl_easy_setopt(curl, CURLOPT_URL, public_key_url); | |
177 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, public_key_file); | |
178 | curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf); | |
179 | ||
180 | if (curl_easy_perform(curl)) { | |
181 | ast_log(LOG_ERROR, "%s\n", curl_errbuf); | |
182 | curl_easy_cleanup(curl); | |
183 | fclose(public_key_file); | |
184 | return -1; | |
185 | } | |
186 | ||
187 | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); | |
188 | ||
189 | curl_easy_cleanup(curl); | |
190 | fclose(public_key_file); | |
191 | ||
192 | if (http_code / 100 != 2) { | |
193 | ast_log(LOG_ERROR, "Failed to retrieve URL '%s': code %ld\n", public_key_url, http_code); | |
194 | return -1; | |
195 | } | |
196 | ||
197 | return 0; | |
198 | } |
0 | /* | |
1 | * Asterisk -- An open source telephony toolkit. | |
2 | * | |
3 | * Copyright (C) 2020, Sangoma Technologies Corporation | |
4 | * | |
5 | * Ben Ford <bford@sangoma.com> | |
6 | * | |
7 | * See http://www.asterisk.org for more information about | |
8 | * the Asterisk project. Please do not directly contact | |
9 | * any of the maintainers of this project for assistance; | |
10 | * the project provides a web site, mailing lists and IRC | |
11 | * channels for your use. | |
12 | * | |
13 | * This program is free software, distributed under the terms of | |
14 | * the GNU General Public License Version 2. See the LICENSE file | |
15 | * at the top of the source tree. | |
16 | */ | |
17 | #ifndef _STIR_SHAKEN_CURL_H | |
18 | #define _STIR_SHAKEN_CURL_H | |
19 | ||
20 | /* Forward declarion for CURL callback data */ | |
21 | struct curl_cb_data; | |
22 | ||
23 | /*! | |
24 | * \brief Allocate memory for a curl_cb_data struct | |
25 | * | |
26 | * \note This will need to be freed by the consumer using curl_cb_data_free | |
27 | * | |
28 | * \retval NULL on failure | |
29 | * \retval curl_cb_struct on success | |
30 | */ | |
31 | struct curl_cb_data *curl_cb_data_create(void); | |
32 | ||
33 | /*! | |
34 | * \brief Free a curl_cb_data struct | |
35 | * | |
36 | * \param data The curl_cb_data struct to free | |
37 | */ | |
38 | void curl_cb_data_free(struct curl_cb_data *data); | |
39 | ||
40 | /*! | |
41 | * \brief Get the cache_control field from a curl_cb_data struct | |
42 | * | |
43 | * \param data The curl_cb_data | |
44 | * | |
45 | * \retval cache_control on success | |
46 | * \retval NULL otherwise | |
47 | */ | |
48 | char *curl_cb_data_get_cache_control(const struct curl_cb_data *data); | |
49 | ||
50 | /*! | |
51 | * \brief Get the expires field from a curl_cb_data struct | |
52 | * | |
53 | * \param data The curl_cb_data | |
54 | * | |
55 | * \retval expires on success | |
56 | * \retval NULL otherwise | |
57 | */ | |
58 | char *curl_cb_data_get_expires(const struct curl_cb_data *data); | |
59 | ||
60 | /*! | |
61 | * \brief CURL the public key from the provided URL to the specified path | |
62 | * | |
63 | * \param public_key_url The public key URL | |
64 | * \param path The path to download the file to | |
65 | * \param data The curl_cb_data | |
66 | * | |
67 | * \retval 1 on failure | |
68 | * \retval 0 on success | |
69 | */ | |
70 | int curl_public_key(const char *public_key_url, const char *path, struct curl_cb_data *data); | |
71 | ||
72 | #endif /* _STIR_SHAKEN_CURL_H */ |
0 | /* | |
1 | * Asterisk -- An open source telephony toolkit. | |
2 | * | |
3 | * Copyright (C) 2020, Sangoma Technologies Corporation | |
4 | * | |
5 | * Kevin Harwell <kharwell@digium.com> | |
6 | * | |
7 | * See http://www.asterisk.org for more information about | |
8 | * the Asterisk project. Please do not directly contact | |
9 | * any of the maintainers of this project for assistance; | |
10 | * the project provides a web site, mailing lists and IRC | |
11 | * channels for your use. | |
12 | * | |
13 | * This program is free software, distributed under the terms of | |
14 | * the GNU General Public License Version 2. See the LICENSE file | |
15 | * at the top of the source tree. | |
16 | */ | |
17 | ||
18 | #include "asterisk.h" | |
19 | ||
20 | #include "asterisk/cli.h" | |
21 | #include "asterisk/sorcery.h" | |
22 | ||
23 | #include "stir_shaken.h" | |
24 | #include "general.h" | |
25 | #include "asterisk/res_stir_shaken.h" | |
26 | ||
27 | #define CONFIG_TYPE "general" | |
28 | ||
29 | #define DEFAULT_CA_FILE "" | |
30 | #define DEFAULT_CA_PATH "" | |
31 | #define DEFAULT_CACHE_MAX_SIZE 1000 | |
32 | #define DEFAULT_CURL_TIMEOUT 2 | |
33 | #define DEFAULT_SIGNATURE_TIMEOUT 15 | |
34 | ||
35 | struct stir_shaken_general { | |
36 | SORCERY_OBJECT(details); | |
37 | AST_DECLARE_STRING_FIELDS( | |
38 | /*! File path to a certificate authority */ | |
39 | AST_STRING_FIELD(ca_file); | |
40 | /*! File path to a chain of trust */ | |
41 | AST_STRING_FIELD(ca_path); | |
42 | ); | |
43 | /*! Maximum size of public keys cache */ | |
44 | unsigned int cache_max_size; | |
45 | /*! Maximum time to wait to CURL certificates */ | |
46 | unsigned int curl_timeout; | |
47 | /*! Amount of time a signature is valid for */ | |
48 | unsigned int signature_timeout; | |
49 | }; | |
50 | ||
51 | static struct stir_shaken_general *default_config = NULL; | |
52 | ||
53 | struct stir_shaken_general *stir_shaken_general_get() | |
54 | { | |
55 | struct stir_shaken_general *cfg; | |
56 | struct ao2_container *container; | |
57 | ||
58 | container = ast_sorcery_retrieve_by_fields(ast_stir_shaken_sorcery(), CONFIG_TYPE, | |
59 | AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL); | |
60 | if (!container || ao2_container_count(container) == 0) { | |
61 | ao2_cleanup(container); | |
62 | return ao2_bump(default_config); | |
63 | } | |
64 | ||
65 | cfg = ao2_find(container, NULL, 0); | |
66 | ao2_ref(container, -1); | |
67 | ||
68 | return cfg; | |
69 | } | |
70 | ||
71 | const char *ast_stir_shaken_ca_file(const struct stir_shaken_general *cfg) | |
72 | { | |
73 | return cfg ? cfg->ca_file : DEFAULT_CA_FILE; | |
74 | } | |
75 | ||
76 | const char *ast_stir_shaken_ca_path(const struct stir_shaken_general *cfg) | |
77 | { | |
78 | return cfg ? cfg->ca_path : DEFAULT_CA_PATH; | |
79 | } | |
80 | ||
81 | unsigned int ast_stir_shaken_cache_max_size(const struct stir_shaken_general *cfg) | |
82 | { | |
83 | return cfg ? cfg->cache_max_size : DEFAULT_CACHE_MAX_SIZE; | |
84 | } | |
85 | ||
86 | unsigned int ast_stir_shaken_curl_timeout(const struct stir_shaken_general *cfg) | |
87 | { | |
88 | return cfg ? cfg->curl_timeout : DEFAULT_CURL_TIMEOUT; | |
89 | } | |
90 | ||
91 | unsigned int ast_stir_shaken_signature_timeout(const struct stir_shaken_general *cfg) | |
92 | { | |
93 | return cfg ? cfg->signature_timeout : DEFAULT_SIGNATURE_TIMEOUT; | |
94 | } | |
95 | ||
96 | static void stir_shaken_general_destructor(void *obj) | |
97 | { | |
98 | struct stir_shaken_general *cfg = obj; | |
99 | ||
100 | ast_string_field_free_memory(cfg); | |
101 | } | |
102 | ||
103 | static void *stir_shaken_general_alloc(const char *name) | |
104 | { | |
105 | struct stir_shaken_general *cfg; | |
106 | ||
107 | cfg = ast_sorcery_generic_alloc(sizeof(*cfg), stir_shaken_general_destructor); | |
108 | if (!cfg) { | |
109 | return NULL; | |
110 | } | |
111 | ||
112 | if (ast_string_field_init(cfg, 512)) { | |
113 | ao2_ref(cfg, -1); | |
114 | return NULL; | |
115 | } | |
116 | ||
117 | return cfg; | |
118 | } | |
119 | ||
120 | static int stir_shaken_general_apply(const struct ast_sorcery *sorcery, void *obj) | |
121 | { | |
122 | return 0; | |
123 | } | |
124 | ||
125 | static void stir_shaken_general_loaded(const char *name, const struct ast_sorcery *sorcery, | |
126 | const char *object_type, int reloaded) | |
127 | { | |
128 | struct stir_shaken_general *cfg; | |
129 | ||
130 | if (strcmp(object_type, CONFIG_TYPE)) { | |
131 | /* Not interested */ | |
132 | return; | |
133 | } | |
134 | ||
135 | if (default_config) { | |
136 | ao2_ref(default_config, -1); | |
137 | default_config = NULL; | |
138 | } | |
139 | ||
140 | cfg = stir_shaken_general_get(); | |
141 | if (cfg) { | |
142 | ao2_ref(cfg, -1); | |
143 | return; | |
144 | } | |
145 | ||
146 | /* Use the default configuration if on is not specified */ | |
147 | default_config = ast_sorcery_alloc(sorcery, CONFIG_TYPE, NULL); | |
148 | if (default_config) { | |
149 | stir_shaken_general_apply(sorcery, default_config); | |
150 | } | |
151 | } | |
152 | ||
153 | static const struct ast_sorcery_instance_observer stir_shaken_general_observer = { | |
154 | .object_type_loaded = stir_shaken_general_loaded, | |
155 | }; | |
156 | ||
157 | static char *stir_shaken_general_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) | |
158 | { | |
159 | struct stir_shaken_general *cfg; | |
160 | ||
161 | switch(cmd) { | |
162 | case CLI_INIT: | |
163 | e->command = "stir_shaken show general"; | |
164 | e->usage = | |
165 | "Usage: stir_shaken show general\n" | |
166 | " Show the general stir/shaken settings\n"; | |
167 | return NULL; | |
168 | case CLI_GENERATE: | |
169 | return NULL; | |
170 | } | |
171 | ||
172 | if (a->argc != 3) { | |
173 | return CLI_SHOWUSAGE; | |
174 | } | |
175 | ||
176 | cfg = stir_shaken_general_get(); | |
177 | stir_shaken_cli_show(cfg, a, 0); | |
178 | ao2_cleanup(cfg); | |
179 | ||
180 | return CLI_SUCCESS; | |
181 | } | |
182 | ||
183 | static struct ast_cli_entry stir_shaken_general_cli[] = { | |
184 | AST_CLI_DEFINE(stir_shaken_general_show, "Show stir/shaken general configuration"), | |
185 | }; | |
186 | ||
187 | static int on_load_ca_file(const struct aco_option *opt, struct ast_variable *var, void *obj) | |
188 | { | |
189 | struct stir_shaken_general *cfg = obj; | |
190 | ||
191 | if (!ast_file_is_readable(var->value)) { | |
192 | ast_log(LOG_ERROR, "stir/shaken - %s '%s' not found, or is unreadable\n", | |
193 | var->name, var->value); | |
194 | return -1; | |
195 | } | |
196 | ||
197 | return ast_string_field_set(cfg, ca_file, var->value); | |
198 | } | |
199 | ||
200 | static int ca_file_to_str(const void *obj, const intptr_t *args, char **buf) | |
201 | { | |
202 | const struct stir_shaken_general *cfg = obj; | |
203 | ||
204 | *buf = ast_strdup(cfg->ca_file); | |
205 | ||
206 | return 0; | |
207 | } | |
208 | ||
209 | static int on_load_ca_path(const struct aco_option *opt, struct ast_variable *var, void *obj) | |
210 | { | |
211 | struct stir_shaken_general *cfg = obj; | |
212 | ||
213 | if (!ast_file_is_readable(var->value)) { | |
214 | ast_log(LOG_ERROR, "stir/shaken - %s '%s' not found, or is unreadable\n", | |
215 | var->name, var->value); | |
216 | return -1; | |
217 | } | |
218 | ||
219 | return ast_string_field_set(cfg, ca_path, var->value); | |
220 | } | |
221 | ||
222 | static int ca_path_to_str(const void *obj, const intptr_t *args, char **buf) | |
223 | { | |
224 | const struct stir_shaken_general *cfg = obj; | |
225 | ||
226 | *buf = ast_strdup(cfg->ca_path); | |
227 | ||
228 | return 0; | |
229 | } | |
230 | ||
231 | int stir_shaken_general_unload(void) | |
232 | { | |
233 | ast_cli_unregister_multiple(stir_shaken_general_cli, | |
234 | ARRAY_LEN(stir_shaken_general_cli)); | |
235 | ||
236 | ast_sorcery_instance_observer_remove(ast_stir_shaken_sorcery(), | |
237 | &stir_shaken_general_observer); | |
238 | ||
239 | if (default_config) { | |
240 | ao2_ref(default_config, -1); | |
241 | default_config = NULL; | |
242 | } | |
243 | ||
244 | return 0; | |
245 | } | |
246 | ||
247 | int stir_shaken_general_load(void) | |
248 | { | |
249 | struct ast_sorcery *sorcery = ast_stir_shaken_sorcery(); | |
250 | ||
251 | ast_sorcery_apply_default(sorcery, CONFIG_TYPE, "config", | |
252 | "stir_shaken.conf,criteria=type=general,single_object=yes,explicit_name=general"); | |
253 | ||
254 | if (ast_sorcery_object_register(sorcery, CONFIG_TYPE, stir_shaken_general_alloc, | |
255 | NULL, stir_shaken_general_apply)) { | |
256 | ast_log(LOG_ERROR, "stir/shaken - failed to register '%s' sorcery object\n", CONFIG_TYPE); | |
257 | return -1; | |
258 | } | |
259 | ||
260 | ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "type", "", OPT_NOOP_T, 0, 0); | |
261 | ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "ca_file", | |
262 | DEFAULT_CA_FILE, on_load_ca_file, ca_file_to_str, NULL, 0, 0); | |
263 | ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "ca_path", | |
264 | DEFAULT_CA_PATH, on_load_ca_path, ca_path_to_str, NULL, 0, 0); | |
265 | ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "cache_max_size", | |
266 | __stringify(DEFAULT_CACHE_MAX_SIZE), OPT_UINT_T, 0, | |
267 | FLDSET(struct stir_shaken_general, cache_max_size)); | |
268 | ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "curl_timeout", | |
269 | __stringify(DEFAULT_CURL_TIMEOUT), OPT_UINT_T, 0, | |
270 | FLDSET(struct stir_shaken_general, curl_timeout)); | |
271 | ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "signature_timeout", | |
272 | __stringify(DEFAULT_SIGNATURE_TIMEOUT), OPT_UINT_T, 0, | |
273 | FLDSET(struct stir_shaken_general, signature_timeout)); | |
274 | ||
275 | if (ast_sorcery_instance_observer_add(sorcery, &stir_shaken_general_observer)) { | |
276 | ast_log(LOG_ERROR, "stir/shaken - failed to register loaded observer for '%s' " | |
277 | "sorcery object type\n", CONFIG_TYPE); | |
278 | return -1; | |
279 | } | |
280 | ||
281 | ast_cli_register_multiple(stir_shaken_general_cli, | |
282 | ARRAY_LEN(stir_shaken_general_cli)); | |
283 | ||
284 | return 0; | |
285 | } |
0 | /* | |
1 | * Asterisk -- An open source telephony toolkit. | |
2 | * | |
3 | * Copyright (C) 2020, Sangoma Technologies Corporation | |
4 | * | |
5 | * Kevin Harwell <kharwell@sangoma.com> | |
6 | * | |
7 | * See http://www.asterisk.org for more information about | |
8 | * the Asterisk project. Please do not directly contact | |
9 | * any of the maintainers of this project for assistance; | |
10 | * the project provides a web site, mailing lists and IRC | |
11 | * channels for your use. | |
12 | * | |
13 | * This program is free software, distributed under the terms of | |
14 | * the GNU General Public License Version 2. See the LICENSE file | |
15 | * at the top of the source tree. | |
16 | */ | |
17 | #ifndef _STIR_SHAKEN_GENERAL_H | |
18 | #define _STIR_SHAKEN_GENERAL_H | |
19 | ||
20 | struct ast_sorcery; | |
21 | ||
22 | /*! | |
23 | * \brief General configuration for stir/shaken | |
24 | */ | |
25 | struct stir_shaken_general; | |
26 | ||
27 | /*! | |
28 | * \brief Retrieve the stir/shaken 'general' configuration object | |
29 | * | |
30 | * A default configuration object is returned if no configuration was specified. | |
31 | * As well, NULL can be returned if there is no configuration, and a problem | |
32 | * occurred while loading the defaults. | |
33 | * | |
34 | * \note Object is returned with a reference that the caller is responsible | |
35 | * for de-referencing. | |
36 | * | |
37 | * \retval A 'general' configuration object, or NULL | |
38 | */ | |
39 | struct stir_shaken_general *stir_shaken_general_get(void); | |
40 | ||
41 | /*! | |
42 | * \brief Retrieve the 'ca_file' general configuration option value | |
43 | * | |
44 | * \note If a NULL configuration is given, then the default value is returned | |
45 | * | |
46 | * \param cfg A 'general' configuration object | |
47 | * | |
48 | * \retval The 'ca_file' value | |
49 | */ | |
50 | const char *ast_stir_shaken_ca_file(const struct stir_shaken_general *cfg); | |
51 | ||
52 | /*! | |
53 | * \brief Retrieve the 'ca_path' general configuration option value | |
54 | * | |
55 | * \note If a NULL configuration is given, then the default value is returned | |
56 | * | |
57 | * \param cfg A 'general' configuration object | |
58 | * | |
59 | * \retval The 'ca_path' value | |
60 | */ | |
61 | const char *ast_stir_shaken_ca_path(const struct stir_shaken_general *cfg); | |
62 | ||
63 | /*! | |
64 | * \brief Retrieve the 'cache_max_size' general configuration option value | |
65 | * | |
66 | * \note If a NULL configuration is given, then the default value is returned | |
67 | * | |
68 | * \param cfg A 'general' configuration object | |
69 | * | |
70 | * \retval The 'cache_max_size' value | |
71 | */ | |
72 | unsigned int ast_stir_shaken_cache_max_size(const struct stir_shaken_general *cfg); | |
73 | ||
74 | /*! | |
75 | * \brief Retrieve the 'curl_timeout' general configuration option value | |
76 | * | |
77 | * \note If a NULL configuration is given, then the default value is returned | |
78 | * | |
79 | * \param cfg A 'general' configuration object | |
80 | * | |
81 | * \retval The 'curl_timeout' value | |
82 | */ | |
83 | unsigned int ast_stir_shaken_curl_timeout(const struct stir_shaken_general *cfg); | |
84 | ||
85 | /*! | |
86 | * \brief Retrieve the 'signature_timeout' general configuration option value | |
87 | * | |
88 | * \note if a NULL configuration is given, then the default value is returned | |
89 | * | |
90 | * \param cfg A 'general' configuration object | |
91 | * | |
92 | * \retval The 'signature_timeout' value | |
93 | */ | |
94 | unsigned int ast_stir_shaken_signature_timeout(const struct stir_shaken_general *cfg); | |
95 | ||
96 | /*! | |
97 | * \brief Load time initialization for the stir/shaken 'general' configuration | |
98 | * | |
99 | * \retval 0 on success, -1 on error | |
100 | */ | |
101 | int stir_shaken_general_load(void); | |
102 | ||
103 | /*! | |
104 | * \brief Unload time cleanup for the stir/shaken 'general' configuration | |
105 | * | |
106 | * \retval 0 on success, -1 on error | |
107 | */ | |
108 | int stir_shaken_general_unload(void); | |
109 | ||
110 | #endif /* _STIR_SHAKEN_GENERAL_H */ |
0 | /* | |
1 | * Asterisk -- An open source telephony toolkit. | |
2 | * | |
3 | * Copyright (C) 2020, Sangoma Technologies Corporation | |
4 | * | |
5 | * Kevin Harwell <kharwell@digium.com> | |
6 | * | |
7 | * See http://www.asterisk.org for more information about | |
8 | * the Asterisk project. Please do not directly contact | |
9 | * any of the maintainers of this project for assistance; | |
10 | * the project provides a web site, mailing lists and IRC | |
11 | * channels for your use. | |
12 | * | |
13 | * This program is free software, distributed under the terms of | |
14 | * the GNU General Public License Version 2. See the LICENSE file | |
15 | * at the top of the source tree. | |
16 | */ | |
17 | ||
18 | /*! \file | |
19 | * | |
20 | * \brief Internal stir/shaken utilities | |
21 | */ | |
22 | ||
23 | #include "asterisk.h" | |
24 | ||
25 | #include <openssl/evp.h> | |
26 | #include <openssl/pem.h> | |
27 | ||
28 | #include "asterisk/cli.h" | |
29 | #include "asterisk/sorcery.h" | |
30 | ||
31 | #include "stir_shaken.h" | |
32 | #include "asterisk/res_stir_shaken.h" | |
33 | ||
34 | int stir_shaken_cli_show(void *obj, void *arg, int flags) | |
35 | { | |
36 | struct ast_cli_args *a = arg; | |
37 | struct ast_variable *options; | |
38 | struct ast_variable *i; | |
39 | ||
40 | if (!obj) { | |
41 | ast_cli(a->fd, "No stir/shaken configuration found\n"); | |
42 | return 0; | |
43 | } | |
44 | ||
45 | options = ast_variable_list_sort(ast_sorcery_objectset_create2( | |
46 | ast_stir_shaken_sorcery(), obj, AST_HANDLER_ONLY_STRING)); | |
47 | if (!options) { | |
48 | return 0; | |
49 | } | |
50 | ||
51 | ast_cli(a->fd, "%s: %s\n", ast_sorcery_object_get_type(obj), | |
52 | ast_sorcery_object_get_id(obj)); | |
53 | ||
54 | for (i = options; i; i = i->next) { | |
55 | ast_cli(a->fd, "\t%s: %s\n", i->name, i->value); | |
56 | } | |
57 | ||
58 | ast_cli(a->fd, "\n"); | |
59 | ||
60 | ast_variables_destroy(options); | |
61 | ||
62 | return 0; | |
63 | } | |
64 | ||
65 | char *stir_shaken_tab_complete_name(const char *word, struct ao2_container *container) | |
66 | { | |
67 | void *obj; | |
68 | struct ao2_iterator it; | |
69 | int wordlen = strlen(word); | |
70 | int ret; | |
71 | ||
72 | it = ao2_iterator_init(container, 0); | |
73 | while ((obj = ao2_iterator_next(&it))) { | |
74 | if (!strncasecmp(word, ast_sorcery_object_get_id(obj), wordlen)) { | |
75 | ret = ast_cli_completion_add(ast_strdup(ast_sorcery_object_get_id(obj))); | |
76 | if (ret) { | |
77 | ao2_ref(obj, -1); | |
78 | break; | |
79 | } | |
80 | } | |
81 | ao2_ref(obj, -1); | |
82 | } | |
83 | ao2_iterator_destroy(&it); | |
84 | ||
85 | return NULL; | |
86 | } | |
87 | ||
88 | EVP_PKEY *stir_shaken_read_key(const char *path, int priv) | |
89 | { | |
90 | EVP_PKEY *key = NULL; | |
91 | FILE *fp; | |
92 | ||
93 | fp = fopen(path, "r"); | |
94 | if (!fp) { | |
95 | ast_log(LOG_ERROR, "Failed to read %s key file '%s'\n", priv ? "private" : "public", path); | |
96 | return NULL; | |
97 | } | |
98 | ||
99 | if (priv) { | |
100 | key = PEM_read_PrivateKey(fp, NULL, NULL, NULL); | |
101 | } else { | |
102 | key = PEM_read_PUBKEY(fp, NULL, NULL, NULL); | |
103 | } | |
104 | ||
105 | if (!key) { | |
106 | ast_log(LOG_ERROR, "Failed to read %s key from file '%s'\n", priv ? "private" : "public", path); | |
107 | fclose(fp); | |
108 | return NULL; | |
109 | } | |
110 | ||
111 | if (EVP_PKEY_id(key) != EVP_PKEY_EC) { | |
112 | ast_log(LOG_ERROR, "%s key from '%s' must be of type EVP_PKEY_EC\n", priv ? "private" : "public", path); | |
113 | fclose(fp); | |
114 | EVP_PKEY_free(key); | |
115 | return NULL; | |
116 | } | |
117 | ||
118 | fclose(fp); | |
119 | ||
120 | return key; | |
121 | } |
0 | /* | |
1 | * Asterisk -- An open source telephony toolkit. | |
2 | * | |
3 | * Copyright (C) 2020, Sangoma Technologies Corporation | |
4 | * | |
5 | * Kevin Harwell <kharwell@sangoma.com> | |
6 | * | |
7 | * See http://www.asterisk.org for more information about | |
8 | * the Asterisk project. Please do not directly contact | |
9 | * any of the maintainers of this project for assistance; | |
10 | * the project provides a web site, mailing lists and IRC | |
11 | * channels for your use. | |
12 | * | |
13 | * This program is free software, distributed under the terms of | |
14 | * the GNU General Public License Version 2. See the LICENSE file | |
15 | * at the top of the source tree. | |
16 | */ | |
17 | #ifndef _STIR_SHAKEN_H | |
18 | #define _STIR_SHAKEN_H | |
19 | ||
20 | #include <openssl/evp.h> | |
21 | ||
22 | /*! | |
23 | * \brief Output configuration settings to the Asterisk CLI | |
24 | * | |
25 | * \param obj A sorcery object containing configuration data | |
26 | * \param arg Asterisk CLI argument object | |
27 | * \param flags ao2 container flags | |
28 | * | |
29 | * \retval 0 | |
30 | */ | |
31 | int stir_shaken_cli_show(void *obj, void *arg, int flags); | |
32 | ||
33 | /*! | |
34 | * \brief Tab completion for name matching with STIR/SHAKEN CLI commands | |
35 | * | |
36 | * \param word The word to tab complete on | |
37 | * \param container The sorcery container to iterate through | |
38 | * | |
39 | * \retval The tab completion options | |
40 | */ | |
41 | char *stir_shaken_tab_complete_name(const char *word, struct ao2_container *container); | |
42 | ||
43 | /*! | |
44 | * \brief Reads the public (or private) key from the specified path | |
45 | * | |
46 | * \param path The path to the file containing the private key | |
47 | * \param priv Specify 0 for public, 1 for private | |
48 | * | |
49 | * \retval NULL on failure | |
50 | * \retval The public/private key on success | |
51 | */ | |
52 | EVP_PKEY *stir_shaken_read_key(const char *path, int priv); | |
53 | ||
54 | #endif /* _STIR_SHAKEN_H */ |
0 | /* | |
1 | * Asterisk -- An open source telephony toolkit. | |
2 | * | |
3 | * Copyright (C) 2020, Sangoma Technologies Corporation | |
4 | * | |
5 | * Kevin Harwell <kharwell@digium.com> | |
6 | * | |
7 | * See http://www.asterisk.org for more information about | |
8 | * the Asterisk project. Please do not directly contact | |
9 | * any of the maintainers of this project for assistance; | |
10 | * the project provides a web site, mailing lists and IRC | |
11 | * channels for your use. | |
12 | * | |
13 | * This program is free software, distributed under the terms of | |
14 | * the GNU General Public License Version 2. See the LICENSE file | |
15 | * at the top of the source tree. | |
16 | */ | |
17 | ||
18 | #include "asterisk.h" | |
19 | ||
20 | #include <sys/stat.h> | |
21 | ||
22 | #include "asterisk/cli.h" | |
23 | #include "asterisk/sorcery.h" | |
24 | ||
25 | #include "stir_shaken.h" | |
26 | #include "store.h" | |
27 | #include "asterisk/res_stir_shaken.h" | |
28 | ||
29 | #define CONFIG_TYPE "store" | |
30 | ||
31 | #define VARIABLE_SUBSTITUTE "${CERTIFICATE}" | |
32 | ||
33 | struct stir_shaken_store { | |
34 | SORCERY_OBJECT(details); | |
35 | AST_DECLARE_STRING_FIELDS( | |
36 | /*! Path to a directory containing certificates */ | |
37 | AST_STRING_FIELD(path); | |
38 | /*! URL to the public key */ | |
39 | AST_STRING_FIELD(public_key_url); | |
40 | ); | |
41 | }; | |
42 | ||
43 | static struct stir_shaken_store *stir_shaken_store_get(const char *id) | |
44 | { | |
45 | return ast_sorcery_retrieve_by_id(ast_stir_shaken_sorcery(), CONFIG_TYPE, id); | |
46 | } | |
47 | ||
48 | static struct ao2_container *stir_shaken_store_get_all(void) | |
49 | { | |
50 | return ast_sorcery_retrieve_by_fields(ast_stir_shaken_sorcery(), CONFIG_TYPE, | |
51 | AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL); | |
52 | } | |
53 | ||
54 | static void stir_shaken_store_destructor(void *obj) | |
55 | { | |
56 | struct stir_shaken_store *cfg = obj; | |
57 | ||
58 | ast_string_field_free_memory(cfg); | |
59 | } | |
60 | ||
61 | static void *stir_shaken_store_alloc(const char *name) | |
62 | { | |
63 | struct stir_shaken_store *cfg; | |
64 | ||
65 | cfg = ast_sorcery_generic_alloc(sizeof(*cfg), stir_shaken_store_destructor); | |
66 | if (!cfg) { | |
67 | return NULL; | |
68 | } | |
69 | ||
70 | if (ast_string_field_init(cfg, 512)) { | |
71 | ao2_ref(cfg, -1); | |
72 | return NULL; | |
73 | } | |
74 | ||
75 | return cfg; | |
76 | } | |
77 | ||
78 | static int stir_shaken_store_apply(const struct ast_sorcery *sorcery, void *obj) | |
79 | { | |
80 | return 0; | |
81 | } | |
82 | ||
83 | static char *stir_shaken_store_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) | |
84 | { | |
85 | struct stir_shaken_store *cfg; | |
86 | ||
87 | switch(cmd) { | |
88 | case CLI_INIT: | |
89 | e->command = "stir_shaken show store"; | |
90 | e->usage = | |
91 | "Usage: stir_shaken show store <id>\n" | |
92 | " Show the store stir/shaken settings for a given id\n"; | |
93 | return NULL; | |
94 | case CLI_GENERATE: | |
95 | if (a->pos == 3) { | |
96 | return stir_shaken_tab_complete_name(a->word, stir_shaken_store_get_all()); | |
97 | } else { | |
98 | return NULL; | |
99 | }; | |
100 | } | |
101 | ||
102 | if (a->argc != 4) { | |
103 | return CLI_SHOWUSAGE; | |
104 | } | |
105 | ||
106 | cfg = stir_shaken_store_get(a->argv[3]); | |
107 | stir_shaken_cli_show(cfg, a, 0); | |
108 | ao2_cleanup(cfg); | |
109 | ||
110 | return CLI_SUCCESS; | |
111 | } | |
112 | ||
113 | static struct ast_cli_entry stir_shaken_store_cli[] = { | |
114 | AST_CLI_DEFINE(stir_shaken_store_show, "Show stir/shaken store configuration by id"), | |
115 | }; | |
116 | ||
117 | static int on_load_path(const struct aco_option *opt, struct ast_variable *var, void *obj) | |
118 | { | |
119 | struct stir_shaken_store *cfg = obj; | |
120 | struct stat statbuf; | |
121 | ||
122 | if (stat(var->value, &statbuf)) { | |
123 | ast_log(LOG_ERROR, "stir/shaken - path '%s' not found\n", var->value); | |
124 | return -1; | |
125 | } | |
126 | ||
127 | if (!S_ISDIR(statbuf.st_mode)) { | |
128 | ast_log(LOG_ERROR, "stir/shaken - path '%s' is not a directory\n", var->value); | |
129 | return -1; | |
130 | } | |
131 | ||
132 | return ast_string_field_set(cfg, path, var->value); | |
133 | } | |
134 | ||
135 | static int path_to_str(const void *obj, const intptr_t *args, char **buf) | |
136 | { | |
137 | const struct stir_shaken_store *cfg = obj; | |
138 | ||
139 | *buf = ast_strdup(cfg->path); | |
140 | ||
141 | return 0; | |
142 | } | |
143 | ||
144 | static int on_load_public_key_url(const struct aco_option *opt, struct ast_variable *var, void *obj) | |
145 | { | |
146 | struct stir_shaken_store *cfg = obj; | |
147 | ||
148 | if (!ast_begins_with(var->value, "http")) { | |
149 | ast_log(LOG_ERROR, "stir/shaken - public_key_url scheme must be 'http[s]'\n"); | |
150 | return -1; | |
151 | } | |
152 | ||
153 | if (!strstr(var->value, VARIABLE_SUBSTITUTE)) { | |
154 | ast_log(LOG_ERROR, "stir/shaken - public_key_url must contain variable '%s' " | |
155 | "used for substitution\n", VARIABLE_SUBSTITUTE); | |
156 | return -1; | |
157 | } | |
158 | ||
159 | return ast_string_field_set(cfg, public_key_url, var->value); | |
160 | } | |
161 | ||
162 | static int public_key_url_to_str(const void *obj, const intptr_t *args, char **buf) | |
163 | { | |
164 | const struct stir_shaken_store *cfg = obj; | |
165 | ||
166 | *buf = ast_strdup(cfg->public_key_url); | |
167 | ||
168 | return 0; | |
169 | } | |
170 | ||
171 | int stir_shaken_store_unload(void) | |
172 | { | |
173 | ast_cli_unregister_multiple(stir_shaken_store_cli, | |
174 | ARRAY_LEN(stir_shaken_store_cli)); | |
175 | ||
176 | return 0; | |
177 | } | |
178 | ||
179 | int stir_shaken_store_load(void) | |
180 | { | |
181 | struct ast_sorcery *sorcery = ast_stir_shaken_sorcery(); | |
182 | ||
183 | ast_sorcery_apply_default(sorcery, CONFIG_TYPE, "config", "stir_shaken.conf,criteria=type=store"); | |
184 | ||
185 | if (ast_sorcery_object_register(sorcery, CONFIG_TYPE, stir_shaken_store_alloc, | |
186 | NULL, stir_shaken_store_apply)) { | |
187 | ast_log(LOG_ERROR, "stir/shaken - failed to register '%s' sorcery object\n", CONFIG_TYPE); | |
188 | return -1; | |
189 | } | |
190 | ||
191 | ast_sorcery_object_field_register(sorcery, CONFIG_TYPE, "type", "", OPT_NOOP_T, 0, 0); | |
192 | ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "path", "", | |
193 | on_load_path, path_to_str, NULL, 0, 0); | |
194 | ast_sorcery_object_field_register_custom(sorcery, CONFIG_TYPE, "public_key_url", "", | |
195 | on_load_public_key_url, public_key_url_to_str, NULL, 0, 0); | |
196 | ||
197 | ast_cli_register_multiple(stir_shaken_store_cli, | |
198 | ARRAY_LEN(stir_shaken_store_cli)); | |
199 | ||
200 | return 0; | |
201 | } |
0 | /* | |
1 | * Asterisk -- An open source telephony toolkit. | |
2 | * | |
3 | * Copyright (C) 2020, Sangoma Technologies Corporation | |
4 | * | |
5 | * Kevin Harwell <kharwell@sangoma.com> | |
6 | * | |
7 | * See http://www.asterisk.org for more information about | |
8 | * the Asterisk project. Please do not directly contact | |
9 | * any of the maintainers of this project for assistance; | |
10 | * the project provides a web site, mailing lists and IRC | |
11 | * channels for your use. | |
12 | * | |
13 | * This program is free software, distributed under the terms of | |
14 | * the GNU General Public License Version 2. See the LICENSE file | |
15 | * at the top of the source tree. | |
16 | */ | |
17 | #ifndef _STIR_SHAKEN_STORE_H | |
18 | #define _STIR_SHAKEN_STORE_H | |
19 | ||
20 | struct ast_sorcery; | |
21 | ||
22 | /*! | |
23 | * \brief Load time initialization for the stir/shaken 'store' configuration | |
24 | * | |
25 | * \retval 0 on success, -1 on error | |
26 | */ | |
27 | int stir_shaken_store_load(void); | |
28 | ||
29 | /*! | |
30 | * \brief Unload time cleanup for the stir/shaken 'store' configuration | |
31 | * | |
32 | * \retval 0 on success, -1 on error | |
33 | */ | |
34 | int stir_shaken_store_unload(void); | |
35 | ||
36 | #endif /* _STIR_SHAKEN_STORE_H */ |
0 | /* | |
1 | * Asterisk -- An open source telephony toolkit. | |
2 | * | |
3 | * Copyright (C) 2020, Sangoma Technologies Corporation | |
4 | * | |
5 | * Kevin Harwell <kharwell@digium.com> | |
6 | * | |
7 | * See http://www.asterisk.org for more information about | |
8 | * the Asterisk project. Please do not directly contact | |
9 | * any of the maintainers of this project for assistance; | |
10 | * the project provides a web site, mailing lists and IRC | |
11 | * channels for your use. | |
12 | * | |
13 | * This program is free software, distributed under the terms of | |
14 | * the GNU General Public License Version 2. See the LICENSE file | |
15 | * at the top of the source tree. | |
16 | */ | |
17 | ||
18 | /*** MODULEINFO | |
19 | <depend>crypto</depend> | |
20 | <depend>curl</depend> | |
21 | <depend>res_curl</depend> | |
22 | <support_level>core</support_level> | |
23 | ***/ | |
24 | ||
25 | #include "asterisk.h" | |
26 | ||
27 | #include <openssl/evp.h> | |
28 | ||
29 | #include "asterisk/module.h" | |
30 | #include "asterisk/sorcery.h" | |
31 | #include "asterisk/time.h" | |
32 | #include "asterisk/json.h" | |
33 | #include "asterisk/astdb.h" | |
34 | #include "asterisk/paths.h" | |
35 | #include "asterisk/conversions.h" | |
36 | #include "asterisk/pbx.h" | |
37 | #include "asterisk/global_datastores.h" | |
38 | #include "asterisk/app.h" | |
39 | #include "asterisk/test.h" | |
40 | ||
41 | #include "asterisk/res_stir_shaken.h" | |
42 | #include "res_stir_shaken/stir_shaken.h" | |
43 | #include "res_stir_shaken/general.h" | |
44 | #include "res_stir_shaken/store.h" | |
45 | #include "res_stir_shaken/certificate.h" | |
46 | #include "res_stir_shaken/curl.h" | |
47 | ||
48 | /*** DOCUMENTATION | |
49 | <configInfo name="res_stir_shaken" language="en_US"> | |
50 | <synopsis>STIR/SHAKEN module for Asterisk</synopsis> | |
51 | <configFile name="stir_shaken.conf"> | |
52 | <configObject name="general"> | |
53 | <synopsis>STIR/SHAKEN general options</synopsis> | |
54 | <configOption name="type"> | |
55 | <synopsis>Must be of type 'general'.</synopsis> | |
56 | </configOption> | |
57 | <configOption name="ca_file" default=""> | |
58 | <synopsis>File path to the certificate authority certificate</synopsis> | |
59 | </configOption> | |
60 | <configOption name="ca_path" default=""> | |
61 | <synopsis>File path to a chain of trust</synopsis> | |
62 | </configOption> | |
63 | <configOption name="cache_max_size" default="1000"> | |
64 | <synopsis>Maximum size to use for caching public keys</synopsis> | |
65 | </configOption> | |
66 | <configOption name="curl_timeout" default="2"> | |
67 | <synopsis>Maximum time to wait to CURL certificates</synopsis> | |
68 | </configOption> | |
69 | <configOption name="signature_timeout" default="15"> | |
70 | <synopsis>Amount of time a signature is valid for</synopsis> | |
71 | </configOption> | |
72 | </configObject> | |
73 | <configObject name="store"> | |
74 | <synopsis>STIR/SHAKEN certificate store options</synopsis> | |
75 | <configOption name="type"> | |
76 | <synopsis>Must be of type 'store'.</synopsis> | |
77 | </configOption> | |
78 | <configOption name="path" default=""> | |
79 | <synopsis>Path to a directory containing certificates</synopsis> | |
80 | </configOption> | |
81 | <configOption name="public_key_url" default=""> | |
82 | <synopsis>URL to the public key(s)</synopsis> | |
83 | <description><para> | |
84 | Must be a valid http, or https, URL. The URL must also contain the ${CERTIFICATE} variable, which is used for public key name substitution. | |
85 | For example: http://mycompany.com/${CERTIFICATE}.pub | |
86 | </para></description> | |
87 | </configOption> | |
88 | </configObject> | |
89 | <configObject name="certificate"> | |
90 | <synopsis>STIR/SHAKEN certificate options</synopsis> | |
91 | <configOption name="type"> | |
92 | <synopsis>Must be of type 'certificate'.</synopsis> | |
93 | </configOption> | |
94 | <configOption name="path" default=""> | |
95 | <synopsis>File path to a certificate</synopsis> | |
96 | </configOption> | |
97 | <configOption name="public_key_url" default=""> | |
98 | <synopsis>URL to the public key</synopsis> | |
99 | <description><para> | |
100 | Must be a valid http, or https, URL. | |
101 | </para></description> | |
102 | </configOption> | |
103 | <configOption name="attestation"> | |
104 | <synopsis>Attestation level</synopsis> | |
105 | </configOption> | |
106 | <configOption name="origid" default=""> | |
107 | <synopsis>The origination ID</synopsis> | |
108 | </configOption> | |
109 | <configOption name="caller_id_number" default=""> | |
110 | <synopsis>The caller ID number to match on.</synopsis> | |
111 | </configOption> | |
112 | </configObject> | |
113 | </configFile> | |
114 | </configInfo> | |
115 | <function name="STIR_SHAKEN" language="en_US"> | |
116 | <synopsis> | |
117 | Gets the number of STIR/SHAKEN results or a specific STIR/SHAKEN value from a result on the channel. | |
118 | </synopsis> | |
119 | <syntax> | |
120 | <parameter name="index" required="true"> | |
121 | <para>The index of the STIR/SHAKEN result to get. If only 'count' is passed in, gets the number of STIR/SHAKEN results instead.</para> | |
122 | </parameter> | |
123 | <parameter name="value" required="false"> | |
124 | <para>The value to get from the STIR/SHAKEN result. Only used when an index is passed in (instead of 'count'). Allowable values:</para> | |
125 | <enumlist> | |
126 | <enum name = "identity" /> | |
127 | <enum name = "attestation" /> | |
128 | <enum name = "verify_result" /> | |
129 | </enumlist> | |
130 | </parameter> | |
131 | </syntax> | |
132 | <description> | |
133 | <para>This function will either return the number of STIR/SHAKEN identities, or return information on the specified identity. | |
134 | To get the number of identities, just pass 'count' as the only parameter to the function. If you want to get information on a | |
135 | specific STIR/SHAKEN identity, you can get the number of identities and then pass an index as the first parameter and one of | |
136 | the values you would like to retrieve as the second parameter. | |
137 | </para> | |
138 | <example title="Get count and retrieve value"> | |
139 | same => n,NoOp(Number of STIR/SHAKEN identities: ${STIR_SHAKEN(count)}) | |
140 | same => n,NoOp(Identity ${STIR_SHAKEN(0, identity)} has attestation level ${STIR_SHAKEN(0, attestation)}) | |
141 | </example> | |
142 | </description> | |
143 | </function> | |
144 | ***/ | |
145 | ||
146 | static struct ast_sorcery *stir_shaken_sorcery; | |
147 | ||
148 | /* Used for AstDB entries */ | |
149 | #define AST_DB_FAMILY "STIR_SHAKEN" | |
150 | ||
151 | /* The directory name to store keys in. Appended to ast_config_DATA_DIR */ | |
152 | #define STIR_SHAKEN_DIR_NAME "stir_shaken" | |
153 | ||
154 | /* The maximum length for path storage */ | |
155 | #define MAX_PATH_LEN 256 | |
156 | ||
157 | /* The default amount of time (in seconds) to use for certificate expiration | |
158 | * if no cache data is available | |
159 | */ | |
160 | #define EXPIRATION_BUFFER 15 | |
161 | ||
162 | struct ast_stir_shaken_payload { | |
163 | /*! The JWT header */ | |
164 | struct ast_json *header; | |
165 | /*! The JWT payload */ | |
166 | struct ast_json *payload; | |
167 | /*! Signature for the payload */ | |
168 | unsigned char *signature; | |
169 | /*! The algorithm used */ | |
170 | char *algorithm; | |
171 | /*! THe URL to the public key for the certificate */ | |
172 | char *public_key_url; | |
173 | }; | |
174 | ||
175 | struct ast_sorcery *ast_stir_shaken_sorcery(void) | |
176 | { | |
177 | return stir_shaken_sorcery; | |
178 | } | |
179 | ||
180 | void ast_stir_shaken_payload_free(struct ast_stir_shaken_payload *payload) | |
181 | { | |
182 | if (!payload) { | |
183 | return; | |
184 | } | |
185 | ||
186 | ast_json_unref(payload->header); | |
187 | ast_json_unref(payload->payload); | |
188 | ast_free(payload->algorithm); | |
189 | ast_free(payload->public_key_url); | |
190 | ast_free(payload->signature); | |
191 | ||
192 | ast_free(payload); | |
193 | } | |
194 | ||
195 | unsigned char *ast_stir_shaken_payload_get_signature(const struct ast_stir_shaken_payload *payload) | |
196 | { | |
197 | return payload ? payload->signature : NULL; | |
198 | } | |
199 | ||
200 | char *ast_stir_shaken_payload_get_public_key_url(const struct ast_stir_shaken_payload *payload) | |
201 | { | |
202 | return payload ? payload->public_key_url : NULL; | |
203 | } | |
204 | ||
205 | unsigned int ast_stir_shaken_get_signature_timeout(void) | |
206 | { | |
207 | return ast_stir_shaken_signature_timeout(stir_shaken_general_get()); | |
208 | } | |
209 | ||
210 | /*! | |
211 | * \brief Convert an ast_stir_shaken_verification_result to string representation | |
212 | * | |
213 | * \param result The result to convert | |
214 | * | |
215 | * \retval empty string if not a valid enum value | |
216 | * \retval string representation of result otherwise | |
217 | */ | |
218 | static const char *stir_shaken_verification_result_to_string(enum ast_stir_shaken_verification_result result) | |
219 | { | |
220 | switch (result) { | |
221 | case AST_STIR_SHAKEN_VERIFY_NOT_PRESENT: | |
222 | return "Verification not present"; | |
223 | case AST_STIR_SHAKEN_VERIFY_SIGNATURE_FAILED: | |
224 | return "Signature failed"; | |
225 | case AST_STIR_SHAKEN_VERIFY_MISMATCH: | |
226 | return "Verification mismatch"; | |
227 | case AST_STIR_SHAKEN_VERIFY_PASSED: | |
228 | return "Verification passed"; | |
229 | default: | |
230 | break; | |
231 | } | |
232 | ||
233 | return ""; | |
234 | } | |
235 | ||
236 | /* The datastore struct holding verification information for the channel */ | |
237 | struct stir_shaken_datastore { | |
238 | /* The identitifier for the STIR/SHAKEN verification */ | |
239 | char *identity; | |
240 | /* The attestation value */ | |
241 | char *attestation; | |
242 | /* The actual verification result */ | |
243 | enum ast_stir_shaken_verification_result verify_result; | |
244 | }; | |
245 | ||
246 | /*! | |
247 | * \brief Frees a stir_shaken_datastore structure | |
248 | * | |
249 | * \param datastore The datastore to free | |
250 | */ | |
251 | static void stir_shaken_datastore_free(struct stir_shaken_datastore *datastore) | |
252 | { | |
253 | if (!datastore) { | |
254 | return; | |
255 | } | |
256 | ||
257 | ast_free(datastore->identity); | |
258 | ast_free(datastore->attestation); | |
259 | ast_free(datastore); | |
260 | } | |
261 | ||
262 | /*! | |
263 | * \brief The callback to destroy a stir_shaken_datastore | |
264 | * | |
265 | * \param data The stir_shaken_datastore | |
266 | */ | |
267 | static void stir_shaken_datastore_destroy_cb(void *data) | |
268 | { | |
269 | struct stir_shaken_datastore *datastore = data; | |
270 | stir_shaken_datastore_free(datastore); | |
271 | } | |
272 | ||
273 | /* The stir_shaken_datastore info used to add and compare stir_shaken_datastores on the channel */ | |
274 | static const struct ast_datastore_info stir_shaken_datastore_info = { | |
275 | .type = "STIR/SHAKEN VERIFICATION", | |
276 | .destroy = stir_shaken_datastore_destroy_cb, | |
277 | }; | |
278 | ||
279 | int ast_stir_shaken_add_verification(struct ast_channel *chan, const char *identity, const char *attestation, | |
280 | enum ast_stir_shaken_verification_result result) | |
281 | { | |
282 | struct stir_shaken_datastore *ss_datastore; | |
283 | struct ast_datastore *datastore; | |
284 | const char *chan_name; | |
285 | ||
286 | if (!chan) { | |
287 | ast_log(LOG_ERROR, "Channel is required to add STIR/SHAKEN verification\n"); | |
288 | return -1; | |
289 | } | |
290 | ||
291 | chan_name = ast_channel_name(chan); | |
292 | ||
293 | if (!identity) { | |
294 | ast_log(LOG_ERROR, "No identity to add STIR/SHAKEN verification to channel " | |
295 | "%s\n", chan_name); | |
296 | return -1; | |
297 | } | |
298 | ||
299 | if (!attestation) { | |
300 | ast_log(LOG_ERROR, "Attestation cannot be NULL to add STIR/SHAKEN verification to " | |
301 | "channel %s\n", chan_name); | |
302 | return -1; | |
303 | } | |
304 | ||
305 | ss_datastore = ast_calloc(1, sizeof(*ss_datastore)); | |
306 | if (!ss_datastore) { | |
307 | ast_log(LOG_ERROR, "Failed to allocate space for STIR/SHAKEN datastore for " | |
308 | "channel %s\n", chan_name); | |
309 | return -1; | |
310 | } | |
311 | ||
312 | ss_datastore->identity = ast_strdup(identity); | |
313 | if (!ss_datastore->identity) { | |
314 | ast_log(LOG_ERROR, "Failed to allocate space for STIR/SHAKEN datastore " | |
315 | "identity for channel %s\n", chan_name); | |
316 | stir_shaken_datastore_free(ss_datastore); | |
317 | return -1; | |
318 | } | |
319 | ||
320 | ss_datastore->attestation = ast_strdup(attestation); | |
321 | if (!ss_datastore->attestation) { | |
322 | ast_log(LOG_ERROR, "Failed to allocate space for STIR/SHAKEN datastore " | |
323 | "attestation for channel %s\n", chan_name); | |
324 | stir_shaken_datastore_free(ss_datastore); | |
325 | return -1; | |
326 | } | |
327 | ||
328 | ss_datastore->verify_result = result; | |
329 | ||
330 | datastore = ast_datastore_alloc(&stir_shaken_datastore_info, NULL); | |
331 | if (!datastore) { | |
332 | ast_log(LOG_ERROR, "Failed to allocate space for datastore for channel " | |
333 | "%s\n", chan_name); | |
334 | stir_shaken_datastore_free(ss_datastore); | |
335 | return -1; | |
336 | } | |
337 | ||
338 | datastore->data = ss_datastore; | |
339 | ||
340 | ast_channel_lock(chan); | |
341 | ast_channel_datastore_add(chan, datastore); | |
342 | ast_channel_unlock(chan); | |
343 | ||
344 | return 0; | |
345 | } | |
346 | ||
347 | /*! | |
348 | * \brief Sets the expiration for the public key based on the provided fields. | |
349 | * If Cache-Control is present, use it. Otherwise, use Expires. | |
350 | * | |
351 | * \param hash The hash for the public key URL | |
352 | * \param data The CURL callback data containing expiration data | |
353 | */ | |
354 | static void set_public_key_expiration(const char *public_key_url, const struct curl_cb_data *data) | |
355 | { | |
356 | char time_buf[32]; | |
357 | char *value; | |
358 | struct timeval actual_expires = ast_tvnow(); | |
359 | char hash[41]; | |
360 | ||
361 | ast_sha1_hash(hash, public_key_url); | |
362 | ||
363 | value = curl_cb_data_get_cache_control(data); | |
364 | if (!ast_strlen_zero(value)) { | |
365 | char *str_max_age; | |
366 | ||
367 | str_max_age = strstr(value, "s-maxage"); | |
368 | if (!str_max_age) { | |
369 | str_max_age = strstr(value, "max-age"); | |
370 | } | |
371 | ||
372 | if (str_max_age) { | |
373 | unsigned int max_age; | |
374 | char *equal = strchr(str_max_age, '='); | |
375 | if (equal && !ast_str_to_uint(equal + 1, &max_age)) { | |
376 | actual_expires.tv_sec += max_age; | |
377 | } | |
378 | } | |
379 | } else { | |
380 | value = curl_cb_data_get_expires(data); | |
381 | if (!ast_strlen_zero(value)) { | |
382 | struct tm expires_time; | |
383 | ||
384 | strptime(value, "%a, %d %b %Y %T %z", &expires_time); | |
385 | expires_time.tm_isdst = -1; | |
386 | actual_expires.tv_sec = mktime(&expires_time); | |
387 | } | |
388 | } | |
389 | ||
390 | if (ast_strlen_zero(value)) { | |
391 | actual_expires.tv_sec += EXPIRATION_BUFFER; | |
392 | } | |
393 | ||
394 | snprintf(time_buf, sizeof(time_buf), "%30lu", actual_expires.tv_sec); | |
395 | ||
396 | ast_db_put(hash, "expiration", time_buf); | |
397 | } | |
398 | ||
399 | /*! | |
400 | * \brief Check to see if the public key is expired | |
401 | * | |
402 | * \param public_key_url The public key URL | |
403 | * | |
404 | * \retval 1 if expired | |
405 | * \retval 0 if not expired | |
406 | */ | |
407 | static int public_key_is_expired(const char *public_key_url) | |
408 | { | |
409 | struct timeval current_time = ast_tvnow(); | |
410 | struct timeval expires = { .tv_sec = 0, .tv_usec = 0 }; | |
411 | char expiration[32]; | |
412 | char hash[41]; | |
413 | ||
414 | ast_sha1_hash(hash, public_key_url); | |
415 | ast_db_get(hash, "expiration", expiration, sizeof(expiration)); | |
416 | ||
417 | if (ast_strlen_zero(expiration)) { | |
418 | return 1; | |
419 | } | |
420 | ||
421 | if (ast_str_to_ulong(expiration, (unsigned long *)&expires.tv_sec)) { | |
422 | return 1; | |
423 | } | |
424 | ||
425 | return ast_tvcmp(current_time, expires) == -1 ? 0 : 1; | |
426 | } | |
427 | ||
428 | /*! | |
429 | * \brief Returns the path to the downloaded file for the provided URL | |
430 | * | |
431 | * \param public_key_url The public key URL | |
432 | * | |
433 | * \retval Empty string if not present in AstDB | |
434 | * \retval The file path if present in AstDB | |
435 | */ | |
436 | static char *get_path_to_public_key(const char *public_key_url) | |
437 | { | |
438 | char hash[41]; | |
439 | char file_path[MAX_PATH_LEN]; | |
440 | ||
441 | ast_sha1_hash(hash, public_key_url); | |
442 | ||
443 | ast_db_get(hash, "path", file_path, sizeof(file_path)); | |
444 | ||
445 | if (ast_strlen_zero(file_path)) { | |
446 | file_path[0] = '\0'; | |
447 | } | |
448 | ||
449 | return ast_strdup(file_path); | |
450 | } | |
451 | ||
452 | /*! | |
453 | * \brief Add the public key details and file path to AstDB | |
454 | * | |
455 | * \param public_key_url The public key URL | |
456 | * \param filepath The path to the file | |
457 | */ | |
458 | static void add_public_key_to_astdb(const char *public_key_url, const char *filepath) | |
459 | { | |
460 | char hash[41]; | |
461 | ||
462 | ast_sha1_hash(hash, public_key_url); | |
463 | ||
464 | ast_db_put(AST_DB_FAMILY, public_key_url, hash); | |
465 | ast_db_put(hash, "path", filepath); | |
466 | } | |
467 | ||
468 | /*! | |
469 | * \brief Remove the public key details and associated information from AstDB | |
470 | * | |
471 | * \param public_key_url The public key URL | |
472 | */ | |
473 | static void remove_public_key_from_astdb(const char *public_key_url) | |
474 | { | |
475 | char hash[41]; | |
476 | char filepath[MAX_PATH_LEN]; | |
477 | ||
478 | ast_sha1_hash(hash, public_key_url); | |
479 | ||
480 | /* Remove this public key from storage */ | |
481 | ast_db_get(hash, "path", filepath, sizeof(filepath)); | |
482 | ||
483 | /* Remove the actual file from the system */ | |
484 | remove(filepath); | |
485 | ||
486 | ast_db_del(AST_DB_FAMILY, public_key_url); | |
487 | ast_db_deltree(hash, NULL); | |
488 | } | |
489 | ||
490 | /*! | |
491 | * \brief Verifies the signature using a public key | |
492 | * | |
493 | * \param msg The payload | |
494 | * \param signature The signature to verify | |
495 | * \param public_key The public key used for verification | |
496 | * | |
497 | * \retval -1 on failure | |
498 | * \retval 0 on success | |
499 | */ | |
500 | static int stir_shaken_verify_signature(const char *msg, const char *signature, EVP_PKEY *public_key) | |
501 | { | |
502 | EVP_MD_CTX *mdctx = NULL; | |
503 | int ret = 0; | |
504 | unsigned char *decoded_signature; | |
505 | size_t signature_length, decoded_signature_length, padding = 0; | |
506 | ||
507 | mdctx = EVP_MD_CTX_create(); | |
508 | if (!mdctx) { | |
509 | ast_log(LOG_ERROR, "Failed to create Message Digest Context\n"); | |
510 | return -1; | |
511 | } | |
512 | ||
513 | ret = EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, public_key); | |
514 | if (ret != 1) { | |
515 | ast_log(LOG_ERROR, "Failed to initialize Message Digest Context\n"); | |
516 | EVP_MD_CTX_destroy(mdctx); | |
517 | return -1; | |
518 | } | |
519 | ||
520 | ret = EVP_DigestVerifyUpdate(mdctx, (unsigned char *)msg, strlen(msg)); | |
521 | if (ret != 1) { | |
522 | ast_log(LOG_ERROR, "Failed to update Message Digest Context\n"); | |
523 | EVP_MD_CTX_destroy(mdctx); | |
524 | return -1; | |
525 | } | |
526 | ||
527 | /* We need to decode the signature from base64 to bytes. Make sure we have | |
528 | * at least enough characters for this check */ | |
529 | signature_length = strlen(signature); | |
530 | if (signature_length > 2 && signature[signature_length - 1] == '=') { | |
531 | padding++; | |
532 | if (signature[signature_length - 2] == '=') { | |
533 | padding++; | |
534 | } | |
535 | } | |
536 | ||
537 | decoded_signature_length = (signature_length / 4 * 3) - padding; | |
538 | decoded_signature = ast_calloc(1, decoded_signature_length); | |
539 | ast_base64decode(decoded_signature, signature, decoded_signature_length); | |
540 | ||
541 | ret = EVP_DigestVerifyFinal(mdctx, decoded_signature, decoded_signature_length); | |
542 | if (ret != 1) { | |
543 | ast_log(LOG_ERROR, "Failed final phase of signature verification\n"); | |
544 | EVP_MD_CTX_destroy(mdctx); | |
545 | ast_free(decoded_signature); | |
546 | return -1; | |
547 | } | |
548 | ||
549 | EVP_MD_CTX_destroy(mdctx); | |
550 | ast_free(decoded_signature); | |
551 | ||
552 | return 0; | |
553 | } | |
554 | ||
555 | /*! | |
556 | * \brief CURL the file located at public_key_url to the specified path | |
557 | * | |
558 | * \param public_key_url The public key URL | |
559 | * \param path The path to download the file to | |
560 | * | |
561 | * \retval -1 on failure | |
562 | * \retval 0 on success | |
563 | */ | |
564 | static int run_curl(const char *public_key_url, const char *path) | |
565 | { | |
566 | struct curl_cb_data *data; | |
567 | ||
568 | data = curl_cb_data_create(); | |
569 | if (!data) { | |
570 | ast_log(LOG_ERROR, "Failed to create CURL callback data\n"); | |
571 | return -1; | |
572 | } | |
573 | ||
574 | if (curl_public_key(public_key_url, path, data)) { | |
575 | ast_log(LOG_ERROR, "Could not retrieve public key for '%s'\n", public_key_url); | |
576 | curl_cb_data_free(data); | |
577 | return -1; | |
578 | } | |
579 | ||
580 | set_public_key_expiration(public_key_url, data); | |
581 | curl_cb_data_free(data); | |
582 | ||
583 | return 0; | |
584 | } | |
585 | ||
586 | /*! | |
587 | * \brief Downloads the public key from public_key_url. If curl is non-zero, that signals | |
588 | * CURL has already been run, and we should bail here. The entry is added to AstDB as well. | |
589 | * | |
590 | * \param public_key_url The public key URL | |
591 | * \param path The path to download the file to | |
592 | * \param curl Flag signaling if we have run CURL or not | |
593 | * | |
594 | * \retval -1 on failure | |
595 | * \retval 0 on success | |
596 | */ | |
597 | static int curl_and_check_expiration(const char *public_key_url, const char *path, int *curl) | |
598 | { | |
599 | if (curl) { | |
600 | ast_log(LOG_ERROR, "Already downloaded public key '%s'\n", path); | |
601 | return -1; | |
602 | } | |
603 | ||
604 | if (run_curl(public_key_url, path)) { | |
605 | return -1; | |
606 | } | |
607 | ||
608 | if (public_key_is_expired(public_key_url)) { | |
609 | ast_log(LOG_ERROR, "Newly downloaded public key '%s' is expired\n", path); | |
610 | return -1; | |
611 | } | |
612 | ||
613 | *curl = 1; | |
614 | add_public_key_to_astdb(public_key_url, path); | |
615 | ||
616 | return 0; | |
617 | } | |
618 | ||
619 | struct ast_stir_shaken_payload *ast_stir_shaken_verify(const char *header, const char *payload, const char *signature, | |
620 | const char *algorithm, const char *public_key_url) | |
621 | { | |
622 | struct ast_stir_shaken_payload *ret_payload; | |
623 | EVP_PKEY *public_key; | |
624 | char *filename; | |
625 | int curl = 0; | |
626 | RAII_VAR(char *, file_path, NULL, ast_free); | |
627 | RAII_VAR(char *, combined_str, NULL, ast_free); | |
628 | size_t combined_size; | |
629 | ||
630 | if (ast_strlen_zero(header)) { | |
631 | ast_log(LOG_ERROR, "'header' is required for STIR/SHAKEN verification\n"); | |
632 | return NULL; | |
633 | } | |
634 | ||
635 | if (ast_strlen_zero(payload)) { | |
636 | ast_log(LOG_ERROR, "'payload' is required for STIR/SHAKEN verification\n"); | |
637 | return NULL; | |
638 | } | |
639 | ||
640 | if (ast_strlen_zero(signature)) { | |
641 | ast_log(LOG_ERROR, "'signature' is required for STIR/SHAKEN verification\n"); | |
642 | return NULL; | |
643 | } | |
644 | ||
645 | if (ast_strlen_zero(algorithm)) { | |
646 | ast_log(LOG_ERROR, "'algorithm' is required for STIR/SHAKEN verification\n"); | |
647 | return NULL; | |
648 | } | |
649 | ||
650 | if (ast_strlen_zero(public_key_url)) { | |
651 | ast_log(LOG_ERROR, "'public_key_url' is required for STIR/SHAKEN verification\n"); | |
652 | return NULL; | |
653 | } | |
654 | ||
655 | /* Check to see if we have already downloaded this public key. The reason we | |
656 | * store the file path is because: | |
657 | * | |
658 | * 1. If, for some reason, the default directory changes, we still know where | |
659 | * to look for the files we already have. | |
660 | * | |
661 | * 2. In the future, if we want to add a way to store the keys in multiple | |
662 | * {configurable) directories, we already have the storage mechanism in place. | |
663 | * The only thing that would be left to do is pull from the configuration. | |
664 | */ | |
665 | file_path = get_path_to_public_key(public_key_url); | |
666 | ||
667 | /* If we don't have an entry in AstDB, CURL from the provided URL */ | |
668 | if (ast_strlen_zero(file_path)) { | |
669 | /* Remove this entry from the database, since we will be | |
670 | * downloading a new file anyways. | |
671 | */ | |
672 | remove_public_key_from_astdb(public_key_url); | |
673 | ||
674 | /* Go ahead and free file_path, in case anything was allocated above */ | |
675 | ast_free(file_path); | |
676 | ||
677 | /* Set up the default path */ | |
678 | filename = basename(public_key_url); | |
679 | if (ast_asprintf(&file_path, "%s/keys/%s/%s", ast_config_AST_DATA_DIR, STIR_SHAKEN_DIR_NAME, filename) < 0) { | |
680 | return NULL; | |
681 | } | |
682 | ||
683 | /* Download to the default path */ | |
684 | if (run_curl(public_key_url, file_path)) { | |
685 | return NULL; | |
686 | } | |
687 | ||
688 | /* Signal that we have already downloaded a new file, no reason to do it again */ | |
689 | curl = 1; | |
690 | ||
691 | /* We should have a successful download at this point, so | |
692 | * add an entry to the database. | |
693 | */ | |
694 | add_public_key_to_astdb(public_key_url, file_path); | |
695 | } | |
696 | ||
697 | /* Check to see if the key we downloaded (or already had) is expired */ | |
698 | if (public_key_is_expired(public_key_url)) { | |
699 | ||
700 | ast_debug(3, "Public key '%s' is expired\n", public_key_url); | |
701 | ||
702 | remove_public_key_from_astdb(public_key_url); | |
703 | ||
704 | /* If this fails, then there's nothing we can do */ | |
705 | if (curl_and_check_expiration(public_key_url, file_path, &curl)) { | |
706 | return NULL; | |
707 | } | |
708 | } | |
709 | ||
710 | /* First attempt to read the key. If it fails, try downloading the file, | |
711 | * unless we already did. Check for expiration again */ | |
712 | public_key = stir_shaken_read_key(file_path, 0); | |
713 | if (!public_key) { | |
714 | ||
715 | ast_debug(3, "Failed first read of public key file '%s'\n", file_path); | |
716 | ||
717 | remove_public_key_from_astdb(public_key_url); | |
718 | ||
719 | if (curl_and_check_expiration(public_key_url, file_path, &curl)) { | |
720 | return NULL; | |
721 | } | |
722 | ||
723 | public_key = stir_shaken_read_key(file_path, 0); | |
724 | if (!public_key) { | |
725 | ast_log(LOG_ERROR, "Failed to read public key from '%s'\n", file_path); | |
726 | remove_public_key_from_astdb(public_key_url); | |
727 | return NULL; | |
728 | } | |
729 | } | |
730 | ||
731 | /* Combine the header and payload to get the original signed message: header.payload */ | |
732 | combined_size = strlen(header) + strlen(payload) + 2; | |
733 | combined_str = ast_calloc(1, combined_size); | |
734 | if (!combined_str) { | |
735 | ast_log(LOG_ERROR, "Failed to allocate space for message to verify\n"); | |
736 | EVP_PKEY_free(public_key); | |
737 | return NULL; | |
738 | } | |
739 | snprintf(combined_str, combined_size, "%s.%s", header, payload); | |
740 | if (stir_shaken_verify_signature(combined_str, signature, public_key)) { | |
741 | ast_log(LOG_ERROR, "Failed to verify signature\n"); | |
742 | EVP_PKEY_free(public_key); | |
743 | return NULL; | |
744 | } | |
745 | ||
746 | /* We don't need the public key anymore */ | |
747 | EVP_PKEY_free(public_key); | |
748 | ||
749 | ret_payload = ast_calloc(1, sizeof(*ret_payload)); | |
750 | if (!ret_payload) { | |
751 | ast_log(LOG_ERROR, "Failed to allocate STIR/SHAKEN payload\n"); | |
752 | return NULL; | |
753 | } | |
754 | ||
755 | ret_payload->header = ast_json_load_string(header, NULL); | |
756 | if (!ret_payload->header) { | |
757 | ast_log(LOG_ERROR, "Failed to create JSON from header\n"); | |
758 | ast_stir_shaken_payload_free(ret_payload); | |
759 | return NULL; | |
760 | } | |
761 | ||
762 | ret_payload->payload = ast_json_load_string(payload, NULL); | |
763 | if (!ret_payload->payload) { | |
764 | ast_log(LOG_ERROR, "Failed to create JSON from payload\n"); | |
765 | ast_stir_shaken_payload_free(ret_payload); | |
766 | return NULL; | |
767 | } | |
768 | ||
769 | ret_payload->signature = (unsigned char *)ast_strdup(signature); | |
770 | ret_payload->algorithm = ast_strdup(algorithm); | |
771 | ret_payload->public_key_url = ast_strdup(public_key_url); | |
772 | ||
773 | return ret_payload; | |
774 | } | |
775 | ||
776 | /*! | |
777 | * \brief Verifies the necessary contents are in the JSON and returns a | |
778 | * ast_stir_shaken_payload with the extracted values. | |
779 | * | |
780 | * \param json The JSON to verify | |
781 | * | |
782 | * \return ast_stir_shaken_payload on success | |
783 | * \return NULL on failure | |
784 | */ | |
785 | static struct ast_stir_shaken_payload *stir_shaken_verify_json(struct ast_json *json) | |
786 | { | |
787 | struct ast_stir_shaken_payload *payload; | |
788 | struct ast_json *obj; | |
789 | const char *val; | |
790 | ||
791 | payload = ast_calloc(1, sizeof(*payload)); | |
792 | if (!payload) { | |
793 | ast_log(LOG_ERROR, "Failed to allocate STIR/SHAKEN payload\n"); | |
794 | goto cleanup; | |
795 | } | |
796 | ||
797 | /* Look through the header first */ | |
798 | obj = ast_json_object_get(json, "header"); | |
799 | if (!obj) { | |
800 | ast_log(LOG_ERROR, "STIR/SHAKEN JWT did not have the required field 'header'\n"); | |
801 | goto cleanup; | |
802 | } | |
803 | ||
804 | payload->header = ast_json_deep_copy(obj); | |
805 | if (!payload->header) { | |
806 | ast_log(LOG_ERROR, "STIR_SHAKEN payload failed to copy 'header'\n"); | |
807 | goto cleanup; | |
808 | } | |
809 | ||
810 | /* Check the ppt value for "shaken" */ | |
811 | val = ast_json_string_get(ast_json_object_get(obj, "ppt")); | |
812 | if (ast_strlen_zero(val)) { | |
813 | ast_log(LOG_ERROR, "STIR/SHAKEN JWT did not have the required field 'ppt'\n"); | |
814 | goto cleanup; | |
815 | } | |
816 | if (strcmp(val, STIR_SHAKEN_PPT)) { | |
817 | ast_log(LOG_ERROR, "STIR/SHAKEN JWT field 'ppt' did not have " | |
818 | "required value '%s' (was '%s')\n", STIR_SHAKEN_PPT, val); | |
819 | goto cleanup; | |
820 | } | |
821 | ||
822 | /* Check the typ value for "passport" */ | |
823 | val = ast_json_string_get(ast_json_object_get(obj, "typ")); | |
824 | if (ast_strlen_zero(val)) { | |
825 | ast_log(LOG_ERROR, "STIR/SHAKEN JWT did not have the required field 'typ'\n"); | |
826 | goto cleanup; | |
827 | } | |
828 | if (strcmp(val, STIR_SHAKEN_TYPE)) { | |
829 | ast_log(LOG_ERROR, "STIR/SHAKEN JWT field 'typ' did not have " | |
830 | "required value '%s' (was '%s')\n", STIR_SHAKEN_TYPE, val); | |
831 | goto cleanup; | |
832 | } | |
833 | ||
834 | /* Check the alg value for "ES256" */ | |
835 | val = ast_json_string_get(ast_json_object_get(obj, "alg")); | |
836 | if (ast_strlen_zero(val)) { | |
837 | ast_log(LOG_ERROR, "STIR/SHAKEN JWT did not have required field 'alg'\n"); | |
838 | goto cleanup; | |
839 | } | |
840 | if (strcmp(val, STIR_SHAKEN_ENCRYPTION_ALGORITHM)) { | |
841 | ast_log(LOG_ERROR, "STIR/SHAKEN JWT field 'alg' did not have " | |
842 | "required value '%s' (was '%s')\n", STIR_SHAKEN_ENCRYPTION_ALGORITHM, val); | |
843 | goto cleanup; | |
844 | } | |
845 | ||
846 | payload->algorithm = ast_strdup(val); | |
847 | if (!payload->algorithm) { | |
848 | ast_log(LOG_ERROR, "STIR/SHAKEN payload failed to copy 'algorithm'\n"); | |
849 | goto cleanup; | |
850 | } | |
851 | ||
852 | /* Now let's check the payload section */ | |
853 | obj = ast_json_object_get(json, "payload"); | |
854 | if (!obj) { | |
855 | ast_log(LOG_ERROR, "STIR/SHAKEN payload JWT did not have required field 'payload'\n"); | |
856 | goto cleanup; | |
857 | } | |
858 | ||
859 | /* Check the orig tn value for not NULL */ | |
860 | val = ast_json_string_get(ast_json_object_get(ast_json_object_get(obj, "orig"), "tn")); | |
861 | if (ast_strlen_zero(val)) { | |
862 | ast_log(LOG_ERROR, "STIR/SHAKEN JWT did not have required field 'orig->tn'\n"); | |
863 | goto cleanup; | |
864 | } | |
865 | ||
866 | /* Payload seems sane. Copy it and return on success */ | |
867 | payload->payload = ast_json_deep_copy(obj); | |
868 | if (!payload->payload) { | |
869 | ast_log(LOG_ERROR, "STIR/SHAKEN payload failed to copy 'payload'\n"); | |
870 | goto cleanup; | |
871 | } | |
872 | ||
873 | return payload; | |
874 | ||
875 | cleanup: | |
876 | ast_stir_shaken_payload_free(payload); | |
877 | return NULL; | |
878 | } | |
879 | ||
880 | /*! | |
881 | * \brief Signs the payload and returns the signature. | |
882 | * | |
883 | * \param json_str The string representation of the JSON | |
884 | * \param private_key The private key used to sign the payload | |
885 | * | |
886 | * \retval signature on success | |
887 | * \retval NULL on failure | |
888 | */ | |
889 | static unsigned char *stir_shaken_sign(char *json_str, EVP_PKEY *private_key) | |
890 | { | |
891 | EVP_MD_CTX *mdctx = NULL; | |
892 | int ret = 0; | |
893 | unsigned char *encoded_signature = NULL; | |
894 | unsigned char *signature = NULL; | |
895 | size_t encoded_length = 0; | |
896 | size_t signature_length = 0; | |
897 | ||
898 | mdctx = EVP_MD_CTX_create(); | |
899 | if (!mdctx) { | |
900 | ast_log(LOG_ERROR, "Failed to create Message Digest Context\n"); | |
901 | goto cleanup; | |
902 | } | |
903 | ||
904 | ret = EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, private_key); | |
905 | if (ret != 1) { | |
906 | ast_log(LOG_ERROR, "Failed to initialize Message Digest Context\n"); | |
907 | goto cleanup; | |
908 | } | |
909 | ||
910 | ret = EVP_DigestSignUpdate(mdctx, json_str, strlen(json_str)); | |
911 | if (ret != 1) { | |
912 | ast_log(LOG_ERROR, "Failed to update Message Digest Context\n"); | |
913 | goto cleanup; | |
914 | } | |
915 | ||
916 | ret = EVP_DigestSignFinal(mdctx, NULL, &signature_length); | |
917 | if (ret != 1) { | |
918 | ast_log(LOG_ERROR, "Failed initial phase of Message Digest Context signing\n"); | |
919 | goto cleanup; | |
920 | } | |
921 | ||
922 | signature = ast_calloc(1, sizeof(unsigned char) * signature_length); | |
923 | if (!signature) { | |
924 | ast_log(LOG_ERROR, "Failed to allocate space for signature\n"); | |
925 | goto cleanup; | |
926 | } | |
927 | ||
928 | ret = EVP_DigestSignFinal(mdctx, signature, &signature_length); | |
929 | if (ret != 1) { | |
930 | ast_log(LOG_ERROR, "Failed final phase of Message Digest Context signing\n"); | |
931 | goto cleanup; | |
932 | } | |
933 | ||
934 | /* There are 6 bits to 1 base64 digit, so in order to get the size of the base64 encoded | |
935 | * signature, we need to multiply by the number of bits in a byte and divide by 6. Since | |
936 | * there's rounding when doing base64 conversions, add 3 bytes, just in case, and account | |
937 | * for padding. Add another byte for the NULL-terminator. | |
938 | */ | |
939 | encoded_length = ((signature_length * 4 / 3 + 3) & ~3) + 1; | |
940 | encoded_signature = ast_calloc(1, encoded_length); | |
941 | if (!encoded_signature) { | |
942 | ast_log(LOG_ERROR, "Failed to allocate space for encoded signature\n"); | |
943 | goto cleanup; | |
944 | } | |
945 | ||
946 | ast_base64encode((char *)encoded_signature, signature, signature_length, encoded_length); | |
947 | ||
948 | cleanup: | |
949 | if (mdctx) { | |
950 | EVP_MD_CTX_destroy(mdctx); | |
951 | } | |
952 | ast_free(signature); | |
953 | ||
954 | return encoded_signature; | |
955 | } | |
956 | ||
957 | /*! | |
958 | * \brief Adds the 'x5u' (public key URL) field to the JWT. | |
959 | * | |
960 | * \param json The JWT | |
961 | * \param x5u The public key URL | |
962 | * | |
963 | * \retval 0 on success | |
964 | * \retval -1 on failure | |
965 | */ | |
966 | static int stir_shaken_add_x5u(struct ast_json *json, const char *x5u) | |
967 | { | |
968 | struct ast_json *value; | |
969 | ||
970 | value = ast_json_string_create(x5u); | |
971 | if (!value) { | |
972 | return -1; | |
973 | } | |
974 | ||
975 | return ast_json_object_set(ast_json_object_get(json, "header"), "x5u", value); | |
976 | } | |
977 | ||
978 | /*! | |
979 | * \brief Adds the 'attest' field to the JWT. | |
980 | * | |
981 | * \param json The JWT | |
982 | * \param attest The value to set attest to | |
983 | * | |
984 | * \retval 0 on success | |
985 | * \retval -1 on failure | |
986 | */ | |
987 | static int stir_shaken_add_attest(struct ast_json *json, const char *attest) | |
988 | { | |
989 | struct ast_json *value; | |
990 | ||
991 | value = ast_json_string_create(attest); | |
992 | if (!value) { | |
993 | return -1; | |
994 | } | |
995 | ||
996 | return ast_json_object_set(ast_json_object_get(json, "payload"), "attest", value); | |
997 | } | |
998 | ||
999 | /*! | |
1000 | * \brief Adds the 'origid' field to the JWT. | |
1001 | * | |
1002 | * \param json The JWT | |
1003 | * \param origid The value to set origid to | |
1004 | * | |
1005 | * \retval 0 on success | |
1006 | * \retval -1 on failure | |
1007 | */ | |
1008 | static int stir_shaken_add_origid(struct ast_json *json, const char *origid) | |
1009 | { | |
1010 | struct ast_json *value; | |
1011 | ||
1012 | value = ast_json_string_create(origid); | |
1013 | if (!origid) { | |
1014 | return -1; | |
1015 | } | |
1016 | ||
1017 | return ast_json_object_set(ast_json_object_get(json, "payload"), "origid", value); | |
1018 | } | |
1019 | ||
1020 | /*! | |
1021 | * \brief Adds the 'iat' field to the JWT. | |
1022 | * | |
1023 | * \param json The JWT | |
1024 | * | |
1025 | * \retval 0 on success | |
1026 | * \retval -1 on failure | |
1027 | */ | |
1028 | static int stir_shaken_add_iat(struct ast_json *json) | |
1029 | { | |
1030 | struct ast_json *value; | |
1031 | struct timeval tv; | |
1032 | int timestamp; | |
1033 | ||
1034 | tv = ast_tvnow(); | |
1035 | timestamp = tv.tv_sec + tv.tv_usec / 1000; | |
1036 | value = ast_json_integer_create(timestamp); | |
1037 | ||
1038 | return ast_json_object_set(ast_json_object_get(json, "payload"), "iat", value); | |
1039 | } | |
1040 | ||
1041 | struct ast_stir_shaken_payload *ast_stir_shaken_sign(struct ast_json *json) | |
1042 | { | |
1043 | struct ast_stir_shaken_payload *ss_payload; | |
1044 | unsigned char *signature; | |
1045 | const char *public_key_url; | |
1046 | const char *caller_id_num; | |
1047 | const char *header; | |
1048 | const char *payload; | |
1049 | struct ast_json *tmp_json; | |
1050 | char *msg = NULL; | |
1051 | size_t msg_len; | |
1052 | struct stir_shaken_certificate *cert = NULL; | |
1053 | ||
1054 | ss_payload = stir_shaken_verify_json(json); | |
1055 | if (!ss_payload) { | |
1056 | return NULL; | |
1057 | } | |
1058 | ||
1059 | /* From the payload section of the JSON, get the orig section, and then get | |
1060 | * the value of tn. This will be the caller ID number */ | |
1061 | caller_id_num = ast_json_string_get(ast_json_object_get(ast_json_object_get( | |
1062 | ast_json_object_get(json, "payload"), "orig"), "tn")); | |
1063 | if (!caller_id_num) { | |
1064 | ast_log(LOG_ERROR, "Failed to get caller ID number from JWT\n"); | |
1065 | goto cleanup; | |
1066 | } | |
1067 | ||
1068 | cert = stir_shaken_certificate_get_by_caller_id_number(caller_id_num); | |
1069 | if (!cert) { | |
1070 | ast_log(LOG_ERROR, "Failed to retrieve certificate for caller ID " | |
1071 | "'%s'\n", caller_id_num); | |
1072 | goto cleanup; | |
1073 | } | |
1074 | ||
1075 | public_key_url = stir_shaken_certificate_get_public_key_url(cert); | |
1076 | if (stir_shaken_add_x5u(json, public_key_url)) { | |
1077 | ast_log(LOG_ERROR, "Failed to add 'x5u' (public key URL) to payload\n"); | |
1078 | goto cleanup; | |
1079 | } | |
1080 | ss_payload->public_key_url = ast_strdup(public_key_url); | |
1081 | ||
1082 | if (stir_shaken_add_attest(json, stir_shaken_certificate_get_attestation(cert))) { | |
1083 | ast_log(LOG_ERROR, "Failed to add 'attest' to payload\n"); | |
1084 | goto cleanup; | |
1085 | } | |
1086 | ||
1087 | if (stir_shaken_add_origid(json, stir_shaken_certificate_get_origid(cert))) { | |
1088 | ast_log(LOG_ERROR, "Failed to add 'origid' to payload\n"); | |
1089 | goto cleanup; | |
1090 | } | |
1091 | ||
1092 | if (stir_shaken_add_iat(json)) { | |
1093 | ast_log(LOG_ERROR, "Failed to add 'iat' to payload\n"); | |
1094 | goto cleanup; | |
1095 | } | |
1096 | ||
1097 | /* Get the header and the payload. Combine them to get the message to sign */ | |
1098 | tmp_json = ast_json_object_get(json, "header"); | |
1099 | header = ast_json_dump_string(tmp_json); | |
1100 | tmp_json = ast_json_object_get(json, "payload"); | |
1101 | payload = ast_json_dump_string(tmp_json); | |
1102 | msg_len = strlen(header) + strlen(payload) + 2; | |
1103 | msg = ast_calloc(1, msg_len); | |
1104 | if (!msg) { | |
1105 | ast_log(LOG_ERROR, "Failed to allocate space for message to sign\n"); | |
1106 | goto cleanup; | |
1107 | } | |
1108 | snprintf(msg, msg_len, "%s.%s", header, payload); | |
1109 | ||
1110 | signature = stir_shaken_sign(msg, stir_shaken_certificate_get_private_key(cert)); | |
1111 | if (!signature) { | |
1112 | goto cleanup; | |
1113 | } | |
1114 | ||
1115 | ss_payload->signature = signature; | |
1116 | ao2_cleanup(cert); | |
1117 | ast_free(msg); | |
1118 | ||
1119 | return ss_payload; | |
1120 | ||
1121 | cleanup: | |
1122 | ao2_cleanup(cert); | |
1123 | ast_stir_shaken_payload_free(ss_payload); | |
1124 | ast_free(msg); | |
1125 | return NULL; | |
1126 | } | |
1127 | ||
1128 | /*! | |
1129 | * \brief Retrieves STIR/SHAKEN verification information for the channel via dialplan. | |
1130 | * Examples: | |
1131 | * | |
1132 | * STIR_SHAKEN(count) | |
1133 | * STIR_SHAKEN(0, identity) | |
1134 | * STIR_SHAKEN(1, attestation) | |
1135 | * STIR_SHAKEN(27, verify_result) | |
1136 | * | |
1137 | * \retval -1 on failure | |
1138 | * \retval 0 on success | |
1139 | */ | |
1140 | static int stir_shaken_read(struct ast_channel *chan, const char *function, | |
1141 | char *data, char *buf, size_t len) | |
1142 | { | |
1143 | struct stir_shaken_datastore *ss_datastore; | |
1144 | struct ast_datastore *datastore; | |
1145 | char *parse; | |
1146 | char *first; | |
1147 | char *second; | |
1148 | unsigned int target_index, current_index = 0; | |
1149 | AST_DECLARE_APP_ARGS(args, | |
1150 | AST_APP_ARG(first_param); | |
1151 | AST_APP_ARG(second_param); | |
1152 | ); | |
1153 | ||
1154 | if (ast_strlen_zero(data)) { | |
1155 | ast_log(LOG_WARNING, "%s requires at least one argument\n", function); | |
1156 | return -1; | |
1157 | } | |
1158 | ||
1159 | if (!chan) { | |
1160 | ast_log(LOG_ERROR, "No channel for %s function\n", function); | |
1161 | return -1; | |
1162 | } | |
1163 | ||
1164 | parse = ast_strdupa(data); | |
1165 | ||
1166 | AST_STANDARD_APP_ARGS(args, parse); | |
1167 | ||
1168 | first = ast_strip(args.first_param); | |
1169 | if (ast_strlen_zero(first)) { | |
1170 | ast_log(LOG_ERROR, "An argument must be passed to %s\n", function); | |
1171 | return -1; | |
1172 | } | |
1173 | ||
1174 | second = ast_strip(args.second_param); | |
1175 | ||
1176 | /* Check if we are only looking for the number of STIR/SHAKEN verification results */ | |
1177 | if (!strcasecmp(first, "count")) { | |
1178 | ||
1179 | size_t count = 0; | |
1180 | ||
1181 | if (!ast_strlen_zero(second)) { | |
1182 | ast_log(LOG_ERROR, "%s only takes 1 paramater for 'count'\n", function); | |
1183 | return -1; | |
1184 | } | |
1185 | ||
1186 | ast_channel_lock(chan); | |
1187 | AST_LIST_TRAVERSE(ast_channel_datastores(chan), datastore, entry) { | |
1188 | if (datastore->info != &stir_shaken_datastore_info) { | |
1189 | continue; | |
1190 | } | |
1191 | count++; | |
1192 | } | |
1193 | ast_channel_unlock(chan); | |
1194 | ||
1195 | snprintf(buf, len, "%zu", count); | |
1196 | return 0; | |
1197 | } | |
1198 | ||
1199 | /* If we aren't doing a count, then there should be two parameters. The field | |
1200 | * we are searching for will be the second parameter. The index is the first. | |
1201 | */ | |
1202 | if (ast_strlen_zero(second)) { | |
1203 | ast_log(LOG_ERROR, "Retrieving a value using %s requires two paramaters (index, value) " | |
1204 | "- only index was given\n", function); | |
1205 | return -1; | |
1206 | } | |
1207 | ||
1208 | if (ast_str_to_uint(first, &target_index)) { | |
1209 | ast_log(LOG_ERROR, "Failed to convert index %s to integer for function %s\n", | |
1210 | first, function); | |
1211 | return -1; | |
1212 | } | |
1213 | ||
1214 | /* We don't store by uid for the datastore, so just search for the specified index */ | |
1215 | ast_channel_lock(chan); | |
1216 | AST_LIST_TRAVERSE(ast_channel_datastores(chan), datastore, entry) { | |
1217 | if (datastore->info != &stir_shaken_datastore_info) { | |
1218 | continue; | |
1219 | } | |
1220 | ||
1221 | if (current_index == target_index) { | |
1222 | break; | |
1223 | } | |
1224 | ||
1225 | current_index++; | |
1226 | } | |
1227 | ast_channel_unlock(chan); | |
1228 | if (current_index != target_index || !datastore) { | |
1229 | ast_log(LOG_WARNING, "No STIR/SHAKEN results for index '%s'\n", first); | |
1230 | return -1; | |
1231 | } | |
1232 | ss_datastore = datastore->data; | |
1233 | ||
1234 | if (!strcasecmp(second, "identity")) { | |
1235 | ast_copy_string(buf, ss_datastore->identity, len); | |
1236 | } else if (!strcasecmp(second, "attestation")) { | |
1237 | ast_copy_string(buf, ss_datastore->attestation, len); | |
1238 | } else if (!strcasecmp(second, "verify_result")) { | |
1239 | ast_copy_string(buf, stir_shaken_verification_result_to_string(ss_datastore->verify_result), len); | |
1240 | } else { | |
1241 | ast_log(LOG_ERROR, "No such value '%s' for %s\n", second, function); | |
1242 | return -1; | |
1243 | } | |
1244 | ||
1245 | return 0; | |
1246 | } | |
1247 | ||
1248 | static struct ast_custom_function stir_shaken_function = { | |
1249 | .name = "STIR_SHAKEN", | |
1250 | .read = stir_shaken_read, | |
1251 | }; | |
1252 | ||
1253 | #ifdef TEST_FRAMEWORK | |
1254 | ||
1255 | static void test_stir_shaken_add_fake_astdb_entry(const char *public_key_url, const char *file_path) | |
1256 | { | |
1257 | struct timeval expires = ast_tvnow(); | |
1258 | char time_buf[32]; | |
1259 | char hash[41]; | |
1260 | ||
1261 | ast_sha1_hash(hash, public_key_url); | |
1262 | add_public_key_to_astdb(public_key_url, file_path); | |
1263 | snprintf(time_buf, sizeof(time_buf), "%30lu", expires.tv_sec + 300); | |
1264 | ||
1265 | ast_db_put(hash, "expiration", time_buf); | |
1266 | } | |
1267 | ||
1268 | /*! | |
1269 | * \brief Create a private or public key certificate | |
1270 | * | |
1271 | * \param file_path The path of the file to create | |
1272 | * \param private Set to 0 if public, 1 if private | |
1273 | * | |
1274 | * \retval -1 on failure | |
1275 | * \retval 0 on success | |
1276 | */ | |
1277 | static int test_stir_shaken_write_temp_key(char *file_path, int private) | |
1278 | { | |
1279 | FILE *file; | |
1280 | int fd; | |
1281 | char *data; | |
1282 | char *type = private ? "private" : "public"; | |
1283 | char *private_data = | |
1284 | "-----BEGIN EC PRIVATE KEY-----\n" | |
1285 | "MHcCAQEEIFkNGlrmRky2j7wmjGBGoPFBsyEQELmEYN02BiiG508noAoGCCqGSM49\n" | |
1286 | "AwEHoUQDQgAECwCaeAYwVG/FAnEnkwaucz6o047iSWq3cJBBUc0n2ZlUDr5VywAz\n" | |
1287 | "MZ86EthIqF3CGZjhLHn0xRITXYwfqTtWBw==\n" | |
1288 | "-----END EC PRIVATE KEY-----"; | |
1289 | char *public_data = | |
1290 | "-----BEGIN PUBLIC KEY-----\n" | |
1291 | "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECwCaeAYwVG/FAnEnkwaucz6o047i\n" | |
1292 | "SWq3cJBBUc0n2ZlUDr5VywAzMZ86EthIqF3CGZjhLHn0xRITXYwfqTtWBw==\n" | |
1293 | "-----END PUBLIC KEY-----"; | |
1294 | ||
1295 | fd = mkstemp(file_path); | |
1296 | if (fd < 0) { | |
1297 | ast_log(LOG_ERROR, "Failed to create temp %s file: %s\n", type, strerror(errno)); | |
1298 | return -1; | |
1299 | } | |
1300 | ||
1301 | file = fdopen(fd, "w"); | |
1302 | if (!file) { | |
1303 | ast_log(LOG_ERROR, "Failed to create temp %s key file: %s\n", type, strerror(errno)); | |
1304 | return -1; | |
1305 | } | |
1306 | ||
1307 | data = private ? private_data : public_data; | |
1308 | if (fputs(data, file) == EOF) { | |
1309 | ast_log(LOG_ERROR, "Failed to write temp %s key file\n", type); | |
1310 | fclose(file); | |
1311 | return -1; | |
1312 | } | |
1313 | ||
1314 | fclose(file); | |
1315 | ||
1316 | return 0; | |
1317 | } | |
1318 | ||
1319 | AST_TEST_DEFINE(test_stir_shaken_sign) | |
1320 | { | |
1321 | char *caller_id_number = "1234567"; | |
1322 | char file_path[] = "/tmp/stir_shaken_private.XXXXXX"; | |
1323 | RAII_VAR(char *, rm_on_exit, file_path, unlink); | |
1324 | RAII_VAR(struct ast_json *, json, NULL, ast_json_free); | |
1325 | RAII_VAR(struct ast_stir_shaken_payload *, payload, NULL, ast_stir_shaken_payload_free); | |
1326 | ||
1327 | switch (cmd) { | |
1328 | case TEST_INIT: | |
1329 | info->name = "stir_shaken_sign"; | |
1330 | info->category = "/res/res_stir_shaken/"; | |
1331 | info->summary = "STIR/SHAKEN sign unit test"; | |
1332 | info->description = | |
1333 | "Tests signing a JWT with a private key."; | |
1334 | return AST_TEST_NOT_RUN; | |
1335 | case TEST_EXECUTE: | |
1336 | break; | |
1337 | } | |
1338 | ||
1339 | /* We only need a private key to sign */ | |
1340 | test_stir_shaken_write_temp_key(file_path, 1); | |
1341 | test_stir_shaken_create_cert(caller_id_number, file_path); | |
1342 | ||
1343 | /* Test missing header section */ | |
1344 | json = ast_json_pack("{s: {s: {s: s}}}", "payload", "orig", "tn", caller_id_number); | |
1345 | payload = ast_stir_shaken_sign(json); | |
1346 | if (payload) { | |
1347 | ast_test_status_update(test, "Signed an invalid JWT (missing 'header')\n"); | |
1348 | test_stir_shaken_cleanup_cert(caller_id_number); | |
1349 | return AST_TEST_FAIL; | |
1350 | } | |
1351 | ||
1352 | /* Test missing payload section */ | |
1353 | ast_json_free(json); | |
1354 | json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}}", "header", "alg", | |
1355 | STIR_SHAKEN_ENCRYPTION_ALGORITHM, "ppt", STIR_SHAKEN_PPT, "typ", STIR_SHAKEN_TYPE, | |
1356 | "x5u", "http://testing123"); | |
1357 | payload = ast_stir_shaken_sign(json); | |
1358 | if (payload) { | |
1359 | ast_test_status_update(test, "Signed an invalid JWT (missing 'payload')\n"); | |
1360 | test_stir_shaken_cleanup_cert(caller_id_number); | |
1361 | return AST_TEST_FAIL; | |
1362 | } | |
1363 | ||
1364 | /* Test missing alg section */ | |
1365 | ast_json_free(json); | |
1366 | json = ast_json_pack("{s: {s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "ppt", | |
1367 | STIR_SHAKEN_PPT, "typ", STIR_SHAKEN_TYPE, "x5u", "http://testing123", "payload", | |
1368 | "orig", "tn", caller_id_number); | |
1369 | payload = ast_stir_shaken_sign(json); | |
1370 | if (payload) { | |
1371 | ast_test_status_update(test, "Signed an invalid JWT (missing 'alg')\n"); | |
1372 | test_stir_shaken_cleanup_cert(caller_id_number); | |
1373 | return AST_TEST_FAIL; | |
1374 | } | |
1375 | ||
1376 | /* Test invalid alg value */ | |
1377 | ast_json_free(json); | |
1378 | json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg", | |
1379 | "invalid algorithm", "ppt", STIR_SHAKEN_PPT, "typ", STIR_SHAKEN_TYPE, | |
1380 | "x5u", "http://testing123", "payload", "orig", "tn", caller_id_number); | |
1381 | payload = ast_stir_shaken_sign(json); | |
1382 | if (payload) { | |
1383 | ast_test_status_update(test, "Signed an invalid JWT (wrong 'alg')\n"); | |
1384 | test_stir_shaken_cleanup_cert(caller_id_number); | |
1385 | return AST_TEST_FAIL; | |
1386 | } | |
1387 | ||
1388 | /* Test missing ppt section */ | |
1389 | ast_json_free(json); | |
1390 | json = ast_json_pack("{s: {s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg", | |
1391 | STIR_SHAKEN_ENCRYPTION_ALGORITHM, "typ", STIR_SHAKEN_TYPE, "x5u", "http://testing123", | |
1392 | "payload", "orig", "tn", caller_id_number); | |
1393 | payload = ast_stir_shaken_sign(json); | |
1394 | if (payload) { | |
1395 | ast_test_status_update(test, "Signed an invalid JWT (missing 'ppt')\n"); | |
1396 | test_stir_shaken_cleanup_cert(caller_id_number); | |
1397 | return AST_TEST_FAIL; | |
1398 | } | |
1399 | ||
1400 | /* Test invalid ppt value */ | |
1401 | ast_json_free(json); | |
1402 | json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg", | |
1403 | STIR_SHAKEN_ENCRYPTION_ALGORITHM, "ppt", "invalid ppt", "typ", STIR_SHAKEN_TYPE, | |
1404 | "x5u", "http://testing123", "payload", "orig", "tn", caller_id_number); | |
1405 | payload = ast_stir_shaken_sign(json); | |
1406 | if (payload) { | |
1407 | ast_test_status_update(test, "Signed an invalid JWT (wrong 'ppt')\n"); | |
1408 | test_stir_shaken_cleanup_cert(caller_id_number); | |
1409 | return AST_TEST_FAIL; | |
1410 | } | |
1411 | ||
1412 | /* Test missing typ section */ | |
1413 | ast_json_free(json); | |
1414 | json = ast_json_pack("{s: {s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg", | |
1415 | STIR_SHAKEN_ENCRYPTION_ALGORITHM, "ppt", STIR_SHAKEN_PPT, "x5u", "http://testing123", | |
1416 | "payload", "orig", "tn", caller_id_number); | |
1417 | payload = ast_stir_shaken_sign(json); | |
1418 | if (payload) { | |
1419 | ast_test_status_update(test, "Signed an invalid JWT (missing 'typ')\n"); | |
1420 | test_stir_shaken_cleanup_cert(caller_id_number); | |
1421 | return AST_TEST_FAIL; | |
1422 | } | |
1423 | ||
1424 | /* Test invalid typ value */ | |
1425 | ast_json_free(json); | |
1426 | json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg", | |
1427 | STIR_SHAKEN_ENCRYPTION_ALGORITHM, "ppt", STIR_SHAKEN_PPT, "typ", "invalid typ", | |
1428 | "x5u", "http://testing123", "payload", "orig", "tn", caller_id_number); | |
1429 | payload = ast_stir_shaken_sign(json); | |
1430 | if (payload) { | |
1431 | ast_test_status_update(test, "Signed an invalid JWT (wrong 'typ')\n"); | |
1432 | test_stir_shaken_cleanup_cert(caller_id_number); | |
1433 | return AST_TEST_FAIL; | |
1434 | } | |
1435 | ||
1436 | /* Test missing orig section */ | |
1437 | ast_json_free(json); | |
1438 | json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: s}}", "header", "alg", | |
1439 | STIR_SHAKEN_ENCRYPTION_ALGORITHM, "ppt", STIR_SHAKEN_PPT, "typ", STIR_SHAKEN_TYPE, | |
1440 | "x5u", "http://testing123", "payload", "filler", "filler"); | |
1441 | payload = ast_stir_shaken_sign(json); | |
1442 | if (payload) { | |
1443 | ast_test_status_update(test, "Signed an invalid JWT (missing 'orig')\n"); | |
1444 | test_stir_shaken_cleanup_cert(caller_id_number); | |
1445 | return AST_TEST_FAIL; | |
1446 | } | |
1447 | ||
1448 | /* Test missing tn section */ | |
1449 | ast_json_free(json); | |
1450 | json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: s}}", "header", "alg", | |
1451 | STIR_SHAKEN_ENCRYPTION_ALGORITHM, "ppt", STIR_SHAKEN_PPT, "typ", STIR_SHAKEN_TYPE, | |
1452 | "x5u", "http://testing123", "payload", "orig", "filler"); | |
1453 | payload = ast_stir_shaken_sign(json); | |
1454 | if (payload) { | |
1455 | ast_test_status_update(test, "Signed an invalid JWT (missing 'tn')\n"); | |
1456 | test_stir_shaken_cleanup_cert(caller_id_number); | |
1457 | return AST_TEST_FAIL; | |
1458 | } | |
1459 | ||
1460 | /* Test valid JWT */ | |
1461 | ast_json_free(json); | |
1462 | json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg", | |
1463 | STIR_SHAKEN_ENCRYPTION_ALGORITHM, "ppt", STIR_SHAKEN_PPT, "typ", STIR_SHAKEN_TYPE, | |
1464 | "x5u", "http://testing123", "payload", "orig", "tn", caller_id_number); | |
1465 | payload = ast_stir_shaken_sign(json); | |
1466 | if (!payload) { | |
1467 | ast_test_status_update(test, "Failed to sign a valid JWT\n"); | |
1468 | test_stir_shaken_cleanup_cert(caller_id_number); | |
1469 | return AST_TEST_FAIL; | |
1470 | } | |
1471 | ||
1472 | test_stir_shaken_cleanup_cert(caller_id_number); | |
1473 | ||
1474 | return AST_TEST_PASS; | |
1475 | } | |
1476 | ||
1477 | AST_TEST_DEFINE(test_stir_shaken_verify) | |
1478 | { | |
1479 | char *caller_id_number = "1234567"; | |
1480 | char *public_key_url = "http://testing123"; | |
1481 | char *header; | |
1482 | char *payload; | |
1483 | struct ast_json *tmp_json; | |
1484 | char public_path[] = "/tmp/stir_shaken_public.XXXXXX"; | |
1485 | char private_path[] = "/tmp/stir_shaken_public.XXXXXX"; | |
1486 | RAII_VAR(char *, rm_on_exit_public, public_path, unlink); | |
1487 | RAII_VAR(char *, rm_on_exit_private, private_path, unlink); | |
1488 | RAII_VAR(struct ast_json *, json, NULL, ast_json_free); | |
1489 | RAII_VAR(struct ast_stir_shaken_payload *, signed_payload, NULL, ast_stir_shaken_payload_free); | |
1490 | RAII_VAR(struct ast_stir_shaken_payload *, returned_payload, NULL, ast_stir_shaken_payload_free); | |
1491 | ||
1492 | switch (cmd) { | |
1493 | case TEST_INIT: | |
1494 | info->name = "stir_shaken_verify"; | |
1495 | info->category = "/res/res_stir_shaken/"; | |
1496 | info->summary = "STIR/SHAKEN verify unit test"; | |
1497 | info->description = | |
1498 | "Tests verifying a signature with a public key"; | |
1499 | return AST_TEST_NOT_RUN; | |
1500 | case TEST_EXECUTE: | |
1501 | break; | |
1502 | } | |
1503 | ||
1504 | /* We need the private key to sign, but we also need the corresponding | |
1505 | * public key to verify */ | |
1506 | test_stir_shaken_write_temp_key(public_path, 0); | |
1507 | test_stir_shaken_write_temp_key(private_path, 1); | |
1508 | test_stir_shaken_create_cert(caller_id_number, private_path); | |
1509 | ||
1510 | /* Get the signature */ | |
1511 | json = ast_json_pack("{s: {s: s, s: s, s: s, s: s}, s: {s: {s: s}}}", "header", "alg", | |
1512 | STIR_SHAKEN_ENCRYPTION_ALGORITHM, "ppt", STIR_SHAKEN_PPT, "typ", STIR_SHAKEN_TYPE, | |
1513 | "x5u", public_key_url, "payload", "orig", "tn", caller_id_number); | |
1514 | signed_payload = ast_stir_shaken_sign(json); | |
1515 | if (!signed_payload) { | |
1516 | ast_test_status_update(test, "Failed to sign a valid JWT\n"); | |
1517 | test_stir_shaken_cleanup_cert(caller_id_number); | |
1518 | return AST_TEST_FAIL; | |
1519 | } | |
1520 | ||
1521 | /* Get the header and payload for ast_stir_shaken_verify */ | |
1522 | tmp_json = ast_json_object_get(json, "header"); | |
1523 | header = ast_json_dump_string(tmp_json); | |
1524 | tmp_json = ast_json_object_get(json, "payload"); | |
1525 | payload = ast_json_dump_string(tmp_json); | |
1526 | ||
1527 | /* Test empty header parameter */ | |
1528 | returned_payload = ast_stir_shaken_verify("", payload, (const char *)signed_payload->signature, | |
1529 | STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_key_url); | |
1530 | if (returned_payload) { | |
1531 | ast_test_status_update(test, "Verified a signature with missing 'header'\n"); | |
1532 | test_stir_shaken_cleanup_cert(caller_id_number); | |
1533 | return AST_TEST_FAIL; | |
1534 | } | |
1535 | ||
1536 | /* Test empty payload parameter */ | |
1537 | returned_payload = ast_stir_shaken_verify(header, "", (const char *)signed_payload->signature, | |
1538 | STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_key_url); | |
1539 | if (returned_payload) { | |
1540 | ast_test_status_update(test, "Verified a signature with missing 'payload'\n"); | |
1541 | test_stir_shaken_cleanup_cert(caller_id_number); | |
1542 | return AST_TEST_FAIL; | |
1543 | } | |
1544 | ||
1545 | /* Test empty signature parameter */ | |
1546 | returned_payload = ast_stir_shaken_verify(header, payload, "", | |
1547 | STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_key_url); | |
1548 | if (returned_payload) { | |
1549 | ast_test_status_update(test, "Verified a signature with missing 'signature'\n"); | |
1550 | test_stir_shaken_cleanup_cert(caller_id_number); | |
1551 | return AST_TEST_FAIL; | |
1552 | } | |
1553 | ||
1554 | /* Test empty algorithm parameter */ | |
1555 | returned_payload = ast_stir_shaken_verify(header, payload, (const char *)signed_payload->signature, | |
1556 | "", public_key_url); | |
1557 | if (returned_payload) { | |
1558 | ast_test_status_update(test, "Verified a signature with missing 'algorithm'\n"); | |
1559 | test_stir_shaken_cleanup_cert(caller_id_number); | |
1560 | return AST_TEST_FAIL; | |
1561 | } | |
1562 | ||
1563 | /* Test empty public key URL */ | |
1564 | returned_payload = ast_stir_shaken_verify(header, payload, (const char *)signed_payload->signature, | |
1565 | STIR_SHAKEN_ENCRYPTION_ALGORITHM, ""); | |
1566 | if (returned_payload) { | |
1567 | ast_test_status_update(test, "Verified a signature with missing 'public key URL'\n"); | |
1568 | test_stir_shaken_cleanup_cert(caller_id_number); | |
1569 | return AST_TEST_FAIL; | |
1570 | } | |
1571 | ||
1572 | /* Trick the function into thinking we've already downloaded the key */ | |
1573 | test_stir_shaken_add_fake_astdb_entry(public_key_url, public_path); | |
1574 | ||
1575 | /* Verify a valid signature */ | |
1576 | returned_payload = ast_stir_shaken_verify(header, payload, (const char *)signed_payload->signature, | |
1577 | STIR_SHAKEN_ENCRYPTION_ALGORITHM, public_key_url); | |
1578 | if (!returned_payload) { | |
1579 | ast_test_status_update(test, "Failed to verify a valid signature\n"); | |
1580 | remove_public_key_from_astdb(public_key_url); | |
1581 | test_stir_shaken_cleanup_cert(caller_id_number); | |
1582 | return AST_TEST_FAIL; | |
1583 | } | |
1584 | ||
1585 | remove_public_key_from_astdb(public_key_url); | |
1586 | ||
1587 | test_stir_shaken_cleanup_cert(caller_id_number); | |
1588 | ||
1589 | return AST_TEST_PASS; | |
1590 | } | |
1591 | ||
1592 | #endif /* TEST_FRAMEWORK */ | |
1593 | ||
1594 | static int reload_module(void) | |
1595 | { | |
1596 | if (stir_shaken_sorcery) { | |
1597 | ast_sorcery_reload(stir_shaken_sorcery); | |
1598 | } | |
1599 | ||
1600 | return 0; | |
1601 | } | |
1602 | ||
1603 | static int unload_module(void) | |
1604 | { | |
1605 | int res = 0; | |
1606 | ||
1607 | stir_shaken_certificate_unload(); | |
1608 | stir_shaken_store_unload(); | |
1609 | stir_shaken_general_unload(); | |
1610 | ||
1611 | ast_sorcery_unref(stir_shaken_sorcery); | |
1612 | stir_shaken_sorcery = NULL; | |
1613 | ||
1614 | res |= ast_custom_function_unregister(&stir_shaken_function); | |
1615 | ||
1616 | AST_TEST_UNREGISTER(test_stir_shaken_sign); | |
1617 | AST_TEST_UNREGISTER(test_stir_shaken_verify); | |
1618 | ||
1619 | return res; | |
1620 | } | |
1621 | ||
1622 | static int load_module(void) | |
1623 | { | |
1624 | int res = 0; | |
1625 | ||
1626 | if (!(stir_shaken_sorcery = ast_sorcery_open())) { | |
1627 | ast_log(LOG_ERROR, "stir/shaken - failed to open sorcery\n"); | |
1628 | return AST_MODULE_LOAD_DECLINE; | |
1629 | } | |
1630 | ||
1631 | if (stir_shaken_general_load()) { | |
1632 | unload_module(); | |
1633 | return AST_MODULE_LOAD_DECLINE; | |
1634 | } | |
1635 | ||
1636 | if (stir_shaken_store_load()) { | |
1637 | unload_module(); | |
1638 | return AST_MODULE_LOAD_DECLINE; | |
1639 | } | |
1640 | ||
1641 | if (stir_shaken_certificate_load()) { | |
1642 | unload_module(); | |
1643 | return AST_MODULE_LOAD_DECLINE; | |
1644 | } | |
1645 | ||
1646 | ast_sorcery_load(ast_stir_shaken_sorcery()); | |
1647 | ||
1648 | res |= ast_custom_function_register(&stir_shaken_function); | |
1649 | ||
1650 | AST_TEST_REGISTER(test_stir_shaken_sign); | |
1651 | AST_TEST_REGISTER(test_stir_shaken_verify); | |
1652 | ||
1653 | return res; | |
1654 | } | |
1655 | ||
1656 | AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, | |
1657 | "STIR/SHAKEN Module for Asterisk", | |
1658 | .support_level = AST_MODULE_SUPPORT_CORE, | |
1659 | .load = load_module, | |
1660 | .unload = unload_module, | |
1661 | .reload = reload_module, | |
1662 | .load_pri = AST_MODPRI_CHANNEL_DEPEND - 1, | |
1663 | .requires = "res_curl", | |
1664 | ); |
312 | 312 | * this should rarely be changed but should become configurable in the future. |
313 | 313 | */ |
314 | 314 | ast_bridge_set_video_update_discard(bridge, 5); |
315 | } else if (video_mode == AST_BRIDGE_VIDEO_MODE_SINGLE_SRC) { | |
316 | ast_bridge_set_single_src_video_mode(bridge, NULL); | |
315 | 317 | } else { |
316 | 318 | ast_bridge_set_talker_src_video_mode(bridge); |
317 | 319 | } |
29 | 29 | "parameters": [ |
30 | 30 | { |
31 | 31 | "name": "type", |
32 | "description": "Comma separated list of bridge type attributes (mixing, holding, dtmf_events, proxy_media, video_sfu).", | |
32 | "description": "Comma separated list of bridge type attributes (mixing, holding, dtmf_events, proxy_media, video_sfu, video_single).", | |
33 | 33 | "paramType": "query", |
34 | 34 | "required": false, |
35 | 35 | "allowMultiple": false, |
68 | 68 | "parameters": [ |
69 | 69 | { |
70 | 70 | "name": "type", |
71 | "description": "Comma separated list of bridge type attributes (mixing, holding, dtmf_events, proxy_media, video_sfu) to set.", | |
71 | "description": "Comma separated list of bridge type attributes (mixing, holding, dtmf_events, proxy_media, video_sfu, video_single) to set.", | |
72 | 72 | "paramType": "query", |
73 | 73 | "required": false, |
74 | 74 | "allowMultiple": false, |
745 | 745 | }, |
746 | 746 | "video_mode": { |
747 | 747 | "type": "string", |
748 | "description": "The video mode the bridge is using. One of 'none', 'talker', or 'single'.", | |
748 | "description": "The video mode the bridge is using. One of 'none', 'talker', 'sfu', or 'single'.", | |
749 | 749 | "required": false |
750 | 750 | }, |
751 | 751 | "video_source_id": { |