/src/vlc/modules/services_discovery/sap.c
Line | Count | Source (jump to first uncovered line) |
1 | | /***************************************************************************** |
2 | | * sap.c : SAP interface module |
3 | | ***************************************************************************** |
4 | | * Copyright (C) 2004-2005 the VideoLAN team |
5 | | * Copyright © 2007 Rémi Denis-Courmont |
6 | | * |
7 | | * Authors: Clément Stenac <zorglub@videolan.org> |
8 | | * Rémi Denis-Courmont |
9 | | * |
10 | | * This program is free software; you can redistribute it and/or modify |
11 | | * it under the terms of the GNU General Public License as published by |
12 | | * the Free Software Foundation; either version 2 of the License, or |
13 | | * (at your option) any later version. |
14 | | * |
15 | | * This program is distributed in the hope that it will be useful, |
16 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18 | | * GNU General Public License for more details. |
19 | | * |
20 | | * You should have received a copy of the GNU General Public License |
21 | | * along with this program; if not, write to the Free Software |
22 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. |
23 | | *****************************************************************************/ |
24 | | |
25 | | /***************************************************************************** |
26 | | * Includes |
27 | | *****************************************************************************/ |
28 | | #ifdef HAVE_CONFIG_H |
29 | | # include "config.h" |
30 | | #endif |
31 | | |
32 | | #define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS |
33 | | #include <vlc_common.h> |
34 | | #include <vlc_arrays.h> |
35 | | #include <vlc_threads.h> |
36 | | #include <vlc_poll.h> |
37 | | #include <vlc_plugin.h> |
38 | | #include <assert.h> |
39 | | |
40 | | #include <vlc_demux.h> |
41 | | #include <vlc_services_discovery.h> |
42 | | |
43 | | #include <vlc_network.h> |
44 | | #include <vlc_charset.h> |
45 | | |
46 | | #include <errno.h> |
47 | | #include <unistd.h> |
48 | | #ifdef HAVE_ARPA_INET_H |
49 | | # include <arpa/inet.h> |
50 | | #endif |
51 | | #ifdef HAVE_POLL_H |
52 | | # include <poll.h> |
53 | | #endif |
54 | | |
55 | | #ifdef HAVE_ZLIB_H |
56 | | # include <zlib.h> |
57 | | #endif |
58 | | |
59 | | #ifdef HAVE_NET_IF_H |
60 | | # include <net/if.h> |
61 | | #endif |
62 | | |
63 | | #ifdef HAVE_SEARCH_H |
64 | | # include <search.h> |
65 | | #endif |
66 | | |
67 | | #include "access/rtp/sdp.h" |
68 | | |
69 | | /************************************************************************ |
70 | | * Macros and definitions |
71 | | ************************************************************************/ |
72 | | |
73 | | #define MAX_LINE_LENGTH 256 |
74 | | |
75 | | /* SAP is always on that port */ |
76 | 0 | #define SAP_PORT 9875 |
77 | | /* Global-scope SAP address */ |
78 | 0 | #define SAP_V4_GLOBAL_ADDRESS "224.2.127.254" |
79 | | /* Organization-local SAP address */ |
80 | 0 | #define SAP_V4_ORG_ADDRESS "239.195.255.255" |
81 | | /* Local (smallest non-link-local scope) SAP address */ |
82 | 0 | #define SAP_V4_LOCAL_ADDRESS "239.255.255.255" |
83 | | /* Link-local SAP address */ |
84 | 0 | #define SAP_V4_LINK_ADDRESS "224.0.0.255" |
85 | | #define ADD_SESSION 1 |
86 | | |
87 | | typedef struct |
88 | | { |
89 | | vlc_thread_t thread; |
90 | | |
91 | | /* Socket descriptors */ |
92 | | int i_fd; |
93 | | int *pi_fd; |
94 | | |
95 | | /* Table of announces */ |
96 | | int i_announces; |
97 | | struct sap_announce_t **pp_announces; |
98 | | |
99 | | vlc_tick_t i_timeout; |
100 | | |
101 | | void *root_cat; |
102 | | } services_discovery_sys_t; |
103 | | |
104 | | static int Decompress( const unsigned char *psz_src, unsigned char **_dst, int i_len ) |
105 | 0 | { |
106 | | #ifdef HAVE_ZLIB |
107 | | int i_result, i_dstsize, n = 0; |
108 | | unsigned char *psz_dst = NULL; |
109 | | z_stream d_stream; |
110 | | |
111 | | memset (&d_stream, 0, sizeof (d_stream)); |
112 | | |
113 | | i_result = inflateInit(&d_stream); |
114 | | if( i_result != Z_OK ) |
115 | | return( -1 ); |
116 | | |
117 | | d_stream.next_in = (Bytef *)psz_src; |
118 | | d_stream.avail_in = i_len; |
119 | | |
120 | | do |
121 | | { |
122 | | n++; |
123 | | psz_dst = xrealloc( psz_dst, n * 1000 ); |
124 | | d_stream.next_out = (Bytef *)&psz_dst[(n - 1) * 1000]; |
125 | | d_stream.avail_out = 1000; |
126 | | |
127 | | i_result = inflate(&d_stream, Z_NO_FLUSH); |
128 | | if( ( i_result != Z_OK ) && ( i_result != Z_STREAM_END ) ) |
129 | | { |
130 | | inflateEnd( &d_stream ); |
131 | | free( psz_dst ); |
132 | | return( -1 ); |
133 | | } |
134 | | } |
135 | | while( ( d_stream.avail_out == 0 ) && ( d_stream.avail_in != 0 ) && |
136 | | ( i_result != Z_STREAM_END ) ); |
137 | | |
138 | | i_dstsize = d_stream.total_out; |
139 | | inflateEnd( &d_stream ); |
140 | | |
141 | | *_dst = xrealloc( psz_dst, i_dstsize ); |
142 | | |
143 | | return i_dstsize; |
144 | | #else |
145 | 0 | (void)psz_src; |
146 | 0 | (void)_dst; |
147 | 0 | (void)i_len; |
148 | 0 | return -1; |
149 | 0 | #endif |
150 | 0 | } |
151 | | |
152 | | struct category |
153 | | { |
154 | | input_item_t *item; |
155 | | services_discovery_t *sd; |
156 | | char name[]; |
157 | | }; |
158 | | |
159 | | static void DestroyCategory(void *data) |
160 | 0 | { |
161 | 0 | struct category *c = data; |
162 | |
|
163 | 0 | services_discovery_RemoveItem(c->sd, c->item); |
164 | 0 | input_item_Release(c->item); |
165 | 0 | free(c); |
166 | 0 | } |
167 | | |
168 | | static int cmpcat (const void *a, const void *b) |
169 | 0 | { |
170 | 0 | const struct category *ca = a, *cb = b; |
171 | 0 | return strcmp(ca->name, cb->name); |
172 | 0 | } |
173 | | |
174 | | static input_item_t *AddCategory (services_discovery_t *sd, const char *name) |
175 | 0 | { |
176 | 0 | services_discovery_sys_t *sys = sd->p_sys; |
177 | |
|
178 | 0 | struct category *c = malloc(sizeof(*c) + strlen(name) + 1); |
179 | 0 | if (unlikely(c == NULL)) |
180 | 0 | return NULL; |
181 | 0 | strcpy(c->name, name); |
182 | |
|
183 | 0 | void **cp = tsearch(c, &sys->root_cat, cmpcat); |
184 | 0 | if (cp == NULL) /* Out-of-memory */ |
185 | 0 | { |
186 | 0 | free(c); |
187 | 0 | return NULL; |
188 | 0 | } |
189 | 0 | if (*cp != c) |
190 | 0 | { |
191 | 0 | free(c); |
192 | 0 | c = *cp; |
193 | 0 | assert(c->item != NULL); |
194 | 0 | return c->item; |
195 | 0 | } |
196 | | |
197 | 0 | c->item = input_item_NewExt("vlc://nop", c->name, |
198 | 0 | INPUT_DURATION_INDEFINITE, |
199 | 0 | ITEM_TYPE_NODE, ITEM_LOCAL); |
200 | |
|
201 | 0 | if (unlikely(c->item == NULL)) |
202 | 0 | { |
203 | 0 | tdelete(c, &sys->root_cat, cmpcat); |
204 | 0 | return NULL; |
205 | 0 | } |
206 | 0 | services_discovery_AddItem(sd, c->item); |
207 | 0 | c->sd = sd; |
208 | |
|
209 | 0 | return c->item; |
210 | 0 | } |
211 | | |
212 | | typedef struct sap_announce_t |
213 | | { |
214 | | vlc_tick_t i_last; |
215 | | vlc_tick_t i_period; |
216 | | uint8_t i_period_trust; |
217 | | |
218 | | uint16_t i_hash; |
219 | | uint32_t i_source[4]; |
220 | | |
221 | | input_item_t * p_item; |
222 | | } sap_announce_t; |
223 | | |
224 | | static sap_announce_t *CreateAnnounce(services_discovery_t *p_sd, |
225 | | const uint32_t *i_source, |
226 | | uint16_t i_hash, const char *psz_sdp) |
227 | 0 | { |
228 | | /* Parse SDP info */ |
229 | 0 | struct vlc_sdp *p_sdp = vlc_sdp_parse(psz_sdp, strlen(psz_sdp)); |
230 | 0 | if (p_sdp == NULL) |
231 | 0 | return NULL; |
232 | | |
233 | 0 | sap_announce_t *p_sap = malloc(sizeof (*p_sap)); |
234 | 0 | if( p_sap == NULL ) |
235 | 0 | goto error; |
236 | | |
237 | 0 | char *uri = NULL; |
238 | |
|
239 | 0 | if (asprintf(&uri, "sdp://%s", psz_sdp) == -1) |
240 | 0 | goto error; |
241 | | |
242 | 0 | input_item_t *p_input; |
243 | 0 | const char *psz_value; |
244 | |
|
245 | 0 | p_sap->i_last = vlc_tick_now(); |
246 | 0 | p_sap->i_period = 0; |
247 | 0 | p_sap->i_period_trust = 0; |
248 | 0 | p_sap->i_hash = i_hash; |
249 | 0 | memcpy (p_sap->i_source, i_source, sizeof(p_sap->i_source)); |
250 | | |
251 | | /* Released in RemoveAnnounce */ |
252 | 0 | p_input = input_item_NewStream(uri, p_sdp->name, |
253 | 0 | INPUT_DURATION_INDEFINITE); |
254 | 0 | free(uri); |
255 | 0 | if( unlikely(p_input == NULL) ) |
256 | 0 | goto error; |
257 | | |
258 | 0 | p_sap->p_item = p_input; |
259 | |
|
260 | 0 | input_item_SetMeta(p_input, vlc_meta_Description, p_sdp->info); |
261 | |
|
262 | 0 | psz_value = vlc_sdp_attr_value(p_sdp, "tool"); |
263 | 0 | if( psz_value != NULL ) |
264 | 0 | { |
265 | 0 | input_item_AddInfo( p_input, _("Session"), _("Tool"), "%s", psz_value ); |
266 | 0 | } |
267 | | |
268 | | /* Handle category */ |
269 | 0 | psz_value = vlc_sdp_attr_value(p_sdp, "cat"); |
270 | 0 | char *str = NULL; |
271 | 0 | if (psz_value != NULL) |
272 | 0 | { |
273 | | /* a=cat provides a dot-separated hierarchy. |
274 | | * For the time being only replace dots with pipe. TODO: FIXME */ |
275 | 0 | str = strdup(psz_value); |
276 | 0 | if (likely(str != NULL)) |
277 | 0 | { |
278 | 0 | for (char *p = strchr(str, '.'); p != NULL; p = strchr(p, '.')) |
279 | 0 | *(p++) = '|'; |
280 | 0 | psz_value = str; |
281 | 0 | } |
282 | 0 | } |
283 | 0 | else |
284 | 0 | { |
285 | | /* backward compatibility with VLC 0.7.3-2.0.0 senders */ |
286 | 0 | psz_value = vlc_sdp_attr_value(p_sdp, "x-plgroup"); |
287 | 0 | } |
288 | 0 | input_item_t *cat = psz_value == NULL ? NULL : AddCategory(p_sd, psz_value); |
289 | 0 | free(str); |
290 | 0 | services_discovery_AddSubItem(p_sd, cat, p_input); |
291 | |
|
292 | 0 | vlc_sdp_free(p_sdp); |
293 | 0 | return p_sap; |
294 | 0 | error: |
295 | 0 | free(p_sap); |
296 | 0 | vlc_sdp_free(p_sdp); |
297 | 0 | return NULL; |
298 | 0 | } |
299 | | |
300 | | static int RemoveAnnounce( services_discovery_t *p_sd, |
301 | | sap_announce_t *p_announce ) |
302 | 0 | { |
303 | 0 | if( p_announce->p_item ) |
304 | 0 | { |
305 | 0 | services_discovery_RemoveItem( p_sd, p_announce->p_item ); |
306 | 0 | input_item_Release( p_announce->p_item ); |
307 | 0 | p_announce->p_item = NULL; |
308 | 0 | } |
309 | |
|
310 | 0 | services_discovery_sys_t *p_sys = p_sd->p_sys; |
311 | 0 | TAB_REMOVE(p_sys->i_announces, p_sys->pp_announces, p_announce); |
312 | 0 | free( p_announce ); |
313 | |
|
314 | 0 | return VLC_SUCCESS; |
315 | 0 | } |
316 | | |
317 | | static int InitSocket( services_discovery_t *p_sd, const char *psz_address, |
318 | | int i_port ) |
319 | 0 | { |
320 | 0 | int i_fd = net_ListenUDP1 (VLC_OBJECT(p_sd), psz_address, i_port); |
321 | 0 | if (i_fd == -1) |
322 | 0 | return VLC_EGENERIC; |
323 | | |
324 | 0 | shutdown( i_fd, SHUT_WR ); |
325 | 0 | services_discovery_sys_t *p_sys = p_sd->p_sys; |
326 | 0 | TAB_APPEND(p_sys->i_fd, p_sys->pi_fd, i_fd); |
327 | 0 | return VLC_SUCCESS; |
328 | 0 | } |
329 | | |
330 | | /* i_read is at least > 6 */ |
331 | | static int ParseSAP( services_discovery_t *p_sd, const uint8_t *buf, |
332 | | size_t len ) |
333 | 0 | { |
334 | 0 | services_discovery_sys_t *p_sys = p_sd->p_sys; |
335 | 0 | const char *psz_sdp; |
336 | 0 | const uint8_t *end = buf + len; |
337 | 0 | uint32_t i_source[4]; |
338 | |
|
339 | 0 | assert (buf[len] == '\0'); |
340 | | |
341 | 0 | if (len < 4) |
342 | 0 | return VLC_EGENERIC; |
343 | | |
344 | 0 | uint8_t flags = buf[0]; |
345 | 0 | uint8_t auth_len = buf[1]; |
346 | | |
347 | | /* First, check the sap announce is correct */ |
348 | 0 | if ((flags >> 5) != 1) |
349 | 0 | return VLC_EGENERIC; |
350 | | |
351 | 0 | bool b_ipv6 = (flags & 0x10) != 0; |
352 | 0 | bool b_need_delete = (flags & 0x04) != 0; |
353 | |
|
354 | 0 | if (flags & 0x02) |
355 | 0 | { |
356 | 0 | msg_Dbg( p_sd, "encrypted packet, unsupported" ); |
357 | 0 | return VLC_EGENERIC; |
358 | 0 | } |
359 | | |
360 | 0 | bool b_compressed = (flags & 0x01) != 0; |
361 | |
|
362 | 0 | uint16_t i_hash = U16_AT (buf + 2); |
363 | |
|
364 | 0 | if( i_hash == 0 ) |
365 | 0 | return VLC_EGENERIC; |
366 | | |
367 | 0 | buf += 4; |
368 | 0 | if( b_ipv6 ) |
369 | 0 | { |
370 | 0 | for( int i = 0; i < 4; i++,buf+=4) |
371 | 0 | i_source[i] = U32_AT(buf); |
372 | 0 | } |
373 | 0 | else |
374 | 0 | { |
375 | 0 | memset(i_source, 0, sizeof(i_source)); |
376 | 0 | i_source[3] = U32_AT(buf); |
377 | 0 | buf+=4; |
378 | 0 | } |
379 | | // Skips auth data |
380 | 0 | buf += auth_len; |
381 | 0 | if (buf > end) |
382 | 0 | return VLC_EGENERIC; |
383 | | |
384 | 0 | uint8_t *decomp = NULL; |
385 | 0 | if( b_compressed ) |
386 | 0 | { |
387 | 0 | int newsize = Decompress (buf, &decomp, end - buf); |
388 | 0 | if (newsize < 0) |
389 | 0 | { |
390 | 0 | msg_Dbg( p_sd, "decompression of SAP packet failed" ); |
391 | 0 | return VLC_EGENERIC; |
392 | 0 | } |
393 | | |
394 | 0 | decomp = xrealloc (decomp, newsize + 1); |
395 | 0 | decomp[newsize] = '\0'; |
396 | 0 | psz_sdp = (const char *)decomp; |
397 | 0 | len = newsize; |
398 | 0 | } |
399 | 0 | else |
400 | 0 | { |
401 | 0 | psz_sdp = (const char *)buf; |
402 | 0 | len = end - buf; |
403 | 0 | } |
404 | | |
405 | | /* len is a strlen here here. both buf and decomp are len+1 where the 1 should be a \0 */ |
406 | 0 | assert( psz_sdp[len] == '\0'); |
407 | | |
408 | | /* Skip payload type */ |
409 | | /* SAPv1 has implicit "application/sdp" payload type: first line is v=0 */ |
410 | 0 | if (strncmp (psz_sdp, "v=0", 3)) |
411 | 0 | { |
412 | 0 | size_t clen = strlen (psz_sdp) + 1; |
413 | |
|
414 | 0 | if (strcmp (psz_sdp, "application/sdp")) |
415 | 0 | { |
416 | 0 | msg_Dbg (p_sd, "unsupported content type: %s", psz_sdp); |
417 | 0 | goto error; |
418 | 0 | } |
419 | | |
420 | | // skips content type |
421 | 0 | if (len <= clen) |
422 | 0 | goto error; |
423 | | |
424 | 0 | len -= clen; |
425 | 0 | psz_sdp += clen; |
426 | 0 | } |
427 | | |
428 | 0 | for( int i = 0 ; i < p_sys->i_announces ; i++ ) |
429 | 0 | { |
430 | 0 | sap_announce_t * p_announce = p_sys->pp_announces[i]; |
431 | | /* FIXME: slow */ |
432 | 0 | if (p_announce->i_hash == i_hash |
433 | 0 | && memcmp(p_announce->i_source, i_source, sizeof (i_source)) == 0) |
434 | 0 | { |
435 | | /* We don't support delete announcement as they can easily |
436 | | * Be used to hijack an announcement by a third party. |
437 | | * Instead we cleverly implement Implicit Announcement removal. |
438 | | * |
439 | | * if( b_need_delete ) |
440 | | * RemoveAnnounce( p_sd, p_sys->pp_announces[i]); |
441 | | * else |
442 | | */ |
443 | |
|
444 | 0 | if( !b_need_delete ) |
445 | 0 | { |
446 | | /* No need to go after six, as we start to trust the |
447 | | * average period at six */ |
448 | 0 | if( p_announce->i_period_trust <= 5 ) |
449 | 0 | p_announce->i_period_trust++; |
450 | | |
451 | | /* Compute the average period */ |
452 | 0 | vlc_tick_t now = vlc_tick_now(); |
453 | 0 | p_announce->i_period = ( p_announce->i_period * (p_announce->i_period_trust-1) + (now - p_announce->i_last) ) / p_announce->i_period_trust; |
454 | 0 | p_announce->i_last = now; |
455 | 0 | } |
456 | 0 | free (decomp); |
457 | 0 | return VLC_SUCCESS; |
458 | 0 | } |
459 | 0 | } |
460 | | |
461 | 0 | sap_announce_t *sap = CreateAnnounce(p_sd, i_source, i_hash, psz_sdp); |
462 | 0 | if (sap != NULL) |
463 | 0 | TAB_APPEND(p_sys->i_announces, p_sys->pp_announces, sap); |
464 | | |
465 | 0 | free (decomp); |
466 | 0 | return VLC_SUCCESS; |
467 | 0 | error: |
468 | 0 | free (decomp); |
469 | 0 | return VLC_EGENERIC; |
470 | 0 | } |
471 | | |
472 | | /***************************************************************************** |
473 | | * Run: main SAP thread |
474 | | ***************************************************************************** |
475 | | * Listens to SAP packets, and sends them to packet_handle |
476 | | *****************************************************************************/ |
477 | 0 | #define MAX_SAP_BUFFER 5000 |
478 | | |
479 | | static void *Run( void *data ) |
480 | 0 | { |
481 | 0 | vlc_thread_set_name("vlc-sap"); |
482 | |
|
483 | 0 | services_discovery_t *p_sd = data; |
484 | 0 | services_discovery_sys_t *p_sys = p_sd->p_sys; |
485 | 0 | char *psz_addr; |
486 | 0 | int timeout = -1; |
487 | 0 | int canc = vlc_savecancel (); |
488 | | |
489 | | /* Braindead Winsock DNS resolver will get stuck over 2 seconds per failed |
490 | | * DNS queries, even if the DNS server returns an error with milliseconds. |
491 | | * You don't want to know why the bug (as of XP SP2) wasn't fixed since |
492 | | * Winsock 1.1 from Windows 95, if not Windows 3.1. |
493 | | * Anyway, to avoid a 30 seconds delay for failed IPv6 socket creation, |
494 | | * we have to open sockets in Run() rather than Open(). */ |
495 | 0 | InitSocket( p_sd, SAP_V4_GLOBAL_ADDRESS, SAP_PORT ); |
496 | 0 | InitSocket( p_sd, SAP_V4_ORG_ADDRESS, SAP_PORT ); |
497 | 0 | InitSocket( p_sd, SAP_V4_LOCAL_ADDRESS, SAP_PORT ); |
498 | 0 | InitSocket( p_sd, SAP_V4_LINK_ADDRESS, SAP_PORT ); |
499 | |
|
500 | 0 | char psz_address[NI_MAXNUMERICHOST] = "ff02::2:7ffe%"; |
501 | 0 | #ifndef _WIN32 |
502 | 0 | struct if_nameindex *l = if_nameindex (); |
503 | 0 | if (l != NULL) |
504 | 0 | { |
505 | 0 | char *ptr = strchr (psz_address, '%') + 1; |
506 | 0 | for (unsigned i = 0; l[i].if_index; i++) |
507 | 0 | { |
508 | 0 | strcpy (ptr, l[i].if_name); |
509 | 0 | InitSocket (p_sd, psz_address, SAP_PORT); |
510 | 0 | } |
511 | 0 | if_freenameindex (l); |
512 | 0 | } |
513 | | #else |
514 | | /* this is the Winsock2 equivalent of SIOCGIFCONF on BSD stacks, |
515 | | which if_nameindex uses internally anyway */ |
516 | | |
517 | | // first create a dummy socket to pin down the protocol family |
518 | | SOCKET s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP); |
519 | | if( s != INVALID_SOCKET ) |
520 | | { |
521 | | INTERFACE_INFO ifaces[10]; // Assume there will be no more than 10 IP interfaces |
522 | | DWORD len = sizeof(ifaces); |
523 | | |
524 | | if( SOCKET_ERROR != WSAIoctl(s, SIO_GET_INTERFACE_LIST, NULL, 0, &ifaces, len, &len, NULL, NULL) ) |
525 | | { |
526 | | unsigned ifcount = len/sizeof(INTERFACE_INFO); |
527 | | char *ptr = strchr (psz_address, '%') + 1; |
528 | | for(unsigned i = 1; i<=ifcount; ++i ) |
529 | | { |
530 | | // append link-local zone identifier |
531 | | sprintf(ptr, "%d", i); |
532 | | } |
533 | | } |
534 | | closesocket(s); |
535 | | } |
536 | | #endif |
537 | 0 | *strchr (psz_address, '%') = '\0'; |
538 | |
|
539 | 0 | static const char ipv6_scopes[] = "1456789ABCDE"; |
540 | 0 | for (const char *c_scope = ipv6_scopes; *c_scope; c_scope++) |
541 | 0 | { |
542 | 0 | psz_address[3] = *c_scope; |
543 | 0 | InitSocket( p_sd, psz_address, SAP_PORT ); |
544 | 0 | } |
545 | |
|
546 | 0 | psz_addr = var_CreateGetString( p_sd, "sap-addr" ); |
547 | 0 | if( psz_addr && *psz_addr ) |
548 | 0 | InitSocket( p_sd, psz_addr, SAP_PORT ); |
549 | 0 | free( psz_addr ); |
550 | |
|
551 | 0 | if( p_sys->i_fd == 0 ) |
552 | 0 | { |
553 | 0 | msg_Err( p_sd, "unable to listen on any address" ); |
554 | 0 | return NULL; |
555 | 0 | } |
556 | | |
557 | | /* read SAP packets */ |
558 | 0 | for (;;) |
559 | 0 | { |
560 | 0 | vlc_restorecancel (canc); |
561 | 0 | unsigned n = p_sys->i_fd; |
562 | 0 | struct pollfd ufd[n]; |
563 | |
|
564 | 0 | for (unsigned i = 0; i < n; i++) |
565 | 0 | { |
566 | 0 | ufd[i].fd = p_sys->pi_fd[i]; |
567 | 0 | ufd[i].events = POLLIN; |
568 | 0 | ufd[i].revents = 0; |
569 | 0 | } |
570 | |
|
571 | 0 | int val = poll (ufd, n, timeout); |
572 | 0 | canc = vlc_savecancel (); |
573 | 0 | if (val > 0) |
574 | 0 | { |
575 | 0 | for (unsigned i = 0; i < n; i++) |
576 | 0 | { |
577 | 0 | if (ufd[i].revents) |
578 | 0 | { |
579 | 0 | uint8_t p_buffer[MAX_SAP_BUFFER+1]; |
580 | 0 | ssize_t i_read; |
581 | |
|
582 | 0 | i_read = recv (ufd[i].fd, p_buffer, MAX_SAP_BUFFER, 0); |
583 | 0 | if (i_read < 0) |
584 | 0 | msg_Warn (p_sd, "receive error: %s", |
585 | 0 | vlc_strerror_c(errno)); |
586 | 0 | if (i_read > 6) |
587 | 0 | { |
588 | | /* Parse the packet */ |
589 | 0 | p_buffer[i_read] = '\0'; |
590 | 0 | ParseSAP (p_sd, p_buffer, i_read); |
591 | 0 | } |
592 | 0 | } |
593 | 0 | } |
594 | 0 | } |
595 | |
|
596 | 0 | vlc_tick_t now = vlc_tick_now(); |
597 | | |
598 | | /* A 1 hour timeout correspond to the RFC Implicit timeout. |
599 | | * This timeout is tuned in the following loop. */ |
600 | 0 | timeout = 1000 * 60 * 60; |
601 | | |
602 | | /* Check for items that need deletion */ |
603 | 0 | for( int i = 0; i < p_sys->i_announces; i++ ) |
604 | 0 | { |
605 | 0 | sap_announce_t * p_announce = p_sys->pp_announces[i]; |
606 | 0 | vlc_tick_t i_last_period = now - p_announce->i_last; |
607 | | |
608 | | /* Remove the announcement, if the last announcement was 1 hour ago |
609 | | * or if the last packet emitted was 10 times the average time |
610 | | * between two packets */ |
611 | 0 | if( ( p_announce->i_period_trust > 5 && i_last_period > 10 * p_announce->i_period ) || |
612 | 0 | i_last_period > p_sys->i_timeout ) |
613 | 0 | { |
614 | 0 | RemoveAnnounce( p_sd, p_announce ); |
615 | 0 | } |
616 | 0 | else |
617 | 0 | { |
618 | | /* Compute next timeout */ |
619 | 0 | if( p_announce->i_period_trust > 5 ) |
620 | 0 | timeout = __MIN(MS_FROM_VLC_TICK(10 * p_announce->i_period - i_last_period), timeout); |
621 | 0 | timeout = __MIN(MS_FROM_VLC_TICK(p_sys->i_timeout - i_last_period), timeout); |
622 | 0 | } |
623 | 0 | } |
624 | |
|
625 | 0 | if( !p_sys->i_announces ) |
626 | 0 | timeout = -1; /* We can safely poll indefinitely. */ |
627 | 0 | else if( timeout < 200 ) |
628 | 0 | timeout = 200; /* Don't wakeup too fast. */ |
629 | 0 | } |
630 | 0 | vlc_assert_unreachable (); |
631 | 0 | } |
632 | | |
633 | | /***************************************************************************** |
634 | | * Open: initialize and create stuff |
635 | | *****************************************************************************/ |
636 | | static int Open( vlc_object_t *p_this ) |
637 | 0 | { |
638 | 0 | services_discovery_t *p_sd = ( services_discovery_t* )p_this; |
639 | 0 | services_discovery_sys_t *p_sys = (services_discovery_sys_t *) |
640 | 0 | malloc( sizeof( services_discovery_sys_t ) ); |
641 | 0 | if( !p_sys ) |
642 | 0 | return VLC_ENOMEM; |
643 | | |
644 | 0 | p_sys->i_timeout = vlc_tick_from_sec(var_CreateGetInteger( p_sd, "sap-timeout" )); |
645 | |
|
646 | 0 | p_sd->p_sys = p_sys; |
647 | 0 | p_sd->description = _("Network streams (SAP)"); |
648 | |
|
649 | 0 | p_sys->pi_fd = NULL; |
650 | 0 | p_sys->i_fd = 0; |
651 | |
|
652 | 0 | p_sys->i_announces = 0; |
653 | 0 | p_sys->pp_announces = NULL; |
654 | 0 | p_sys->root_cat = NULL; |
655 | | /* TODO: create sockets here, and fix racy sockets table */ |
656 | 0 | if (vlc_clone (&p_sys->thread, Run, p_sd)) |
657 | 0 | { |
658 | 0 | free (p_sys); |
659 | 0 | return VLC_EGENERIC; |
660 | 0 | } |
661 | | |
662 | 0 | return VLC_SUCCESS; |
663 | 0 | } |
664 | | |
665 | | /***************************************************************************** |
666 | | * Close: |
667 | | *****************************************************************************/ |
668 | | static void Close( vlc_object_t *p_this ) |
669 | 0 | { |
670 | 0 | services_discovery_t *p_sd = ( services_discovery_t* )p_this; |
671 | 0 | services_discovery_sys_t *p_sys = p_sd->p_sys; |
672 | 0 | int i; |
673 | |
|
674 | 0 | vlc_cancel (p_sys->thread); |
675 | 0 | vlc_join (p_sys->thread, NULL); |
676 | |
|
677 | 0 | for( i = p_sys->i_fd-1 ; i >= 0 ; i-- ) |
678 | 0 | { |
679 | 0 | net_Close( p_sys->pi_fd[i] ); |
680 | 0 | } |
681 | 0 | FREENULL( p_sys->pi_fd ); |
682 | |
|
683 | 0 | for( i = p_sys->i_announces - 1; i>= 0; i-- ) |
684 | 0 | { |
685 | 0 | RemoveAnnounce( p_sd, p_sys->pp_announces[i] ); |
686 | 0 | } |
687 | 0 | FREENULL( p_sys->pp_announces ); |
688 | 0 | tdestroy(p_sys->root_cat, DestroyCategory); |
689 | |
|
690 | 0 | free( p_sys ); |
691 | 0 | } |
692 | | |
693 | | /***************************************************************************** |
694 | | * Module descriptor |
695 | | *****************************************************************************/ |
696 | | #define SAP_ADDR_TEXT N_( "SAP multicast address" ) |
697 | | #define SAP_ADDR_LONGTEXT N_( "The SAP module normally chooses itself the " \ |
698 | | "right addresses to listen to. However, you " \ |
699 | | "can specify a specific address." ) |
700 | | #define SAP_TIMEOUT_TEXT N_( "SAP timeout (seconds)" ) |
701 | | #define SAP_TIMEOUT_LONGTEXT N_( \ |
702 | | "Delay after which SAP items get deleted if no new announcement " \ |
703 | | "is received." ) |
704 | | #define SAP_PARSE_TEXT N_( "Try to parse the announce" ) |
705 | | #define SAP_PARSE_LONGTEXT N_( \ |
706 | | "This enables actual parsing of the announces by the SAP module. " \ |
707 | | "Otherwise, all announcements are parsed by the \"live555\" " \ |
708 | | "(RTP/RTSP) module." ) |
709 | | |
710 | | VLC_SD_PROBE_HELPER("sap", N_("Network streams (SAP)"), SD_CAT_LAN) |
711 | | |
712 | 4 | vlc_module_begin() |
713 | 2 | set_shortname(N_("SAP")) |
714 | 2 | set_description(N_("Network streams (SAP)") ) |
715 | 2 | set_subcategory(SUBCAT_PLAYLIST_SD) |
716 | | |
717 | 2 | add_string("sap-addr", NULL, SAP_ADDR_TEXT, SAP_ADDR_LONGTEXT) |
718 | 2 | add_integer("sap-timeout", 1800, |
719 | 2 | SAP_TIMEOUT_TEXT, SAP_TIMEOUT_LONGTEXT) |
720 | 2 | add_obsolete_bool("sap-parse") /* since 4.0.0 */ |
721 | 2 | add_obsolete_bool("sap-strict") /* since 4.0.0 */ |
722 | | |
723 | 2 | set_capability("services_discovery", 0) |
724 | 4 | set_callbacks(Open, Close) |
725 | | |
726 | 2 | VLC_SD_PROBE_SUBMODULE |
727 | 2 | vlc_module_end() |