]> git.sesse.net Git - vlc/blob - modules/misc/sap.c
* modules/misc/sap.c: compilation fix.
[vlc] / modules / misc / sap.c
1 /*****************************************************************************
2  * sap.c :  SAP interface module
3  *****************************************************************************
4  * Copyright (C) 2001 VideoLAN
5  * $Id: sap.c,v 1.31 2003/11/06 09:59:45 gbazin Exp $
6  *
7  * Authors: Arnaud Schauly <gitan@via.ecp.fr>
8  *          ClĂ©ment Stenac <zorglub@via.ecp.fr>
9  *          Damien Lucas <nitrox@videolan.org>
10  *          Laurent Aimar <fenrir@via.ecp.fr>
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
25  *****************************************************************************/
26
27 /*****************************************************************************
28  * Preamble
29  *****************************************************************************/
30 #include <stdlib.h>                                      /* malloc(), free() */
31
32 #include <vlc/vlc.h>
33 #include <vlc/intf.h>
34
35 #include <errno.h>                                                 /* ENOMEM */
36 #include <ctype.h>
37
38 #ifdef HAVE_UNISTD_H
39 #    include <unistd.h>
40 #endif
41 #ifdef HAVE_SYS_TIME_H
42 #    include <sys/time.h>
43 #endif
44
45 #ifdef WIN32
46 #   include <winsock2.h>
47 #   include <ws2tcpip.h>
48 #   ifndef IN_MULTICAST
49 #       define IN_MULTICAST(a) IN_CLASSD(a)
50 #   endif
51 #else
52 #   include <sys/socket.h>
53 #   include <netinet/in.h>
54 #   if HAVE_ARPA_INET_H
55 #      include <arpa/inet.h>
56 #   elif defined( SYS_BEOS )
57 #      include <net/netdb.h>
58 #   endif
59 #endif
60
61 #ifdef UNDER_CE
62 #   define close(a) CloseHandle(a)
63 #elif defined( WIN32 )
64 #   define close(a) closesocket(a)
65 #endif
66
67 #include "network.h"
68
69 #define MAX_LINE_LENGTH 256
70
71 /* SAP is always on that port */
72 #define HELLO_PORT 9875
73 #define HELLO_GROUP "224.2.127.254"
74 #define ADD_SESSION 1
75
76 #define IPV6_ADDR_1 "FF0"  /* Scope is inserted between them */
77 #define IPV6_ADDR_2 "::2:7FFE"
78
79
80 /*****************************************************************************
81  * Module descriptor
82  *****************************************************************************/
83 #define SAP_ADDR_TEXT N_("SAP multicast address")
84 #define SAP_ADDR_LONGTEXT N_("SAP multicast address")
85 #define SAP_IPV4_TEXT N_("IPv4-SAP listening")
86 #define SAP_IPV4_LONGTEXT N_("Set this if you want SAP to listen for IPv4 announces")
87 #define SAP_IPV6_TEXT N_("IPv6-SAP listening")
88 #define SAP_IPV6_LONGTEXT N_("Set this if you want SAP to listen for IPv6 announces")
89 #define SAP_SCOPE_TEXT N_("IPv6 SAP scope")
90 #define SAP_SCOPE_LONGTEXT N_("Sets the scope for IPv6 announces (default is 8)")
91
92 static int  Open ( vlc_object_t * );
93 static void Close( vlc_object_t * );
94
95 vlc_module_begin();
96     add_category_hint( N_("SAP"), NULL, VLC_TRUE );
97         add_string( "sap-addr", NULL, NULL,
98                      SAP_ADDR_TEXT, SAP_ADDR_LONGTEXT, VLC_TRUE );
99
100         add_bool( "sap-ipv4", 1 , NULL,
101                      SAP_IPV4_TEXT,SAP_IPV4_LONGTEXT, VLC_TRUE);
102
103         add_bool( "sap-ipv6", 0 , NULL,
104                    SAP_IPV6_TEXT, SAP_IPV6_LONGTEXT, VLC_TRUE);
105
106         add_string( "sap-ipv6-scope", "8" , NULL,
107                     SAP_SCOPE_TEXT, SAP_SCOPE_LONGTEXT, VLC_TRUE);
108
109     set_description( _("SAP interface") );
110     set_capability( "interface", 0 );
111     set_callbacks( Open, Close );
112 vlc_module_end();
113
114 /*****************************************************************************
115  * Local prototypes
116  *****************************************************************************/
117
118 static void Run    ( intf_thread_t *p_intf );
119 static ssize_t NetRead( intf_thread_t *, int fd[2], uint8_t *, int );
120
121 typedef struct media_descr_t media_descr_t;
122 typedef struct sess_descr_t sess_descr_t;
123 typedef struct attr_descr_t attr_descr_t;
124
125 static void sess_toitem( intf_thread_t *, sess_descr_t * );
126
127 static sess_descr_t *  parse_sdp( intf_thread_t *, char * ) ;
128 static void free_sd( sess_descr_t * );
129
130 /* Detect multicast addresses */
131 static int  ismult( char * );
132
133 /* The struct that contains sdp informations */
134 struct  sess_descr_t
135 {
136     int  i_version;
137     char *psz_sessionname;
138     char *psz_connection;
139
140     int           i_media;
141     media_descr_t **pp_media;
142     int           i_attributes;
143     attr_descr_t  **pp_attributes;
144 };
145
146 /* All this informations are not useful yet.  */
147 struct media_descr_t
148 {
149     char *psz_medianame;
150     char *psz_mediaconnection;
151 };
152
153 struct attr_descr_t
154 {
155     char *psz_field;
156     char *psz_value;
157 };
158
159 struct intf_sys_t
160 {
161     /* IPV4 and IPV6 */
162     int fd[2];
163
164     /* playlist group */
165     int i_group;
166 };
167
168 /*****************************************************************************
169  * Open: initialize and create stuff
170  *****************************************************************************/
171 static int Open( vlc_object_t *p_this )
172 {
173     intf_thread_t *p_intf = (intf_thread_t*)p_this;
174     intf_sys_t    *p_sys  = malloc( sizeof( intf_sys_t ) );
175
176     playlist_t          *p_playlist;
177
178     p_sys->fd[0] = -1;
179     p_sys->fd[1] = -1;
180     if( config_GetInt( p_intf, "sap-ipv4" ) )
181     {
182         char *psz_address = config_GetPsz( p_intf, "sap-addr" );
183         network_socket_t    sock;
184         module_t            *p_network;
185         if( psz_address == NULL || *psz_address == '\0' )
186         {
187             psz_address = strdup( HELLO_GROUP );
188         }
189
190         /* Prepare the network_socket_t structure */
191         sock.i_type            = NETWORK_UDP;
192         sock.psz_bind_addr     = psz_address;
193         sock.i_bind_port       = HELLO_PORT;
194         sock.psz_server_addr   = "";
195         sock.i_server_port     = 0;
196         sock.i_ttl             = 0;
197         p_intf->p_private = (void*) &sock;
198
199         p_network = module_Need( p_intf, "network", "ipv4" );
200         if( p_network )
201         {
202             p_sys->fd[0] = sock.i_handle;
203             module_Unneed( p_intf, p_network );
204         }
205         else
206         {
207             msg_Warn( p_intf, "failed to open %s:%d", psz_address, HELLO_PORT );
208         }
209         free( psz_address );
210     }
211
212     if( config_GetInt( p_intf, "sap-ipv6" ) )
213     {
214         char psz_address[100];
215         char *psz_scope = config_GetPsz( p_intf, "sap-ipv6-scope" );
216         network_socket_t    sock;
217         module_t            *p_network;
218
219         if( psz_scope == NULL || *psz_scope == '\0' )
220         {
221             psz_scope = strdup( "8" );
222         }
223         snprintf( psz_address, 100, "[%s%c%s]",IPV6_ADDR_1, psz_scope[0], IPV6_ADDR_2 );
224         free( psz_scope );
225
226         sock.i_type            = NETWORK_UDP;
227         sock.psz_bind_addr     = psz_address;
228         sock.i_bind_port       = HELLO_PORT;
229         sock.psz_server_addr   = "";
230         sock.i_server_port     = 0;
231         sock.i_ttl             = 0;
232         p_intf->p_private = (void*) &sock;
233
234         p_network = module_Need( p_intf, "network", "ipv6" );
235         if( p_network )
236         {
237             p_sys->fd[1] = sock.i_handle;
238             module_Unneed( p_intf, p_network );
239         }
240         else
241         {
242             msg_Warn( p_intf, "failed to open %s:%d", psz_address, HELLO_PORT );
243         }
244     }
245     if( p_sys->fd[0] <= 0 && p_sys->fd[1] <= 0 )
246     {
247         msg_Warn( p_intf, "IPV4 and IPV6 failed" );
248         free( p_sys );
249         return VLC_EGENERIC;
250     }
251
252     /* Create our playlist group */
253     p_playlist = (playlist_t *)vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST,
254                                                 FIND_ANYWHERE );
255     if( p_playlist )
256     {
257         playlist_group_t *p_group = playlist_CreateGroup( p_playlist , "SAP" );
258         p_sys->i_group = p_group->i_id;
259         vlc_object_release( p_playlist );
260     }
261
262     p_intf->pf_run = Run;
263     p_intf->p_sys  = p_sys;
264
265     return VLC_SUCCESS;
266 }
267
268 /*****************************************************************************
269  * Close:
270  *****************************************************************************/
271 static void Close( vlc_object_t *p_this )
272 {
273     intf_thread_t *p_intf = (intf_thread_t*)p_this;
274     intf_sys_t    *p_sys  = p_intf->p_sys;
275
276     if( p_sys->fd[0] > 0 )
277     {
278         close( p_sys->fd[0] );
279     }
280     if( p_sys->fd[1] > 0 )
281     {
282         close( p_sys->fd[1] );
283     }
284
285     free( p_sys );
286 }
287
288 /*****************************************************************************
289  * Run: sap thread
290  *****************************************************************************
291  * Listens to SAP packets, and sends them to packet_handle
292  *****************************************************************************/
293 #define MAX_SAP_BUFFER 2000
294
295 static void Run( intf_thread_t *p_intf )
296 {
297     intf_sys_t *p_sys  = p_intf->p_sys;
298     uint8_t     buffer[MAX_SAP_BUFFER + 1];
299
300     /* read SAP packets */
301     while( !p_intf->b_die )
302     {
303         int i_read = NetRead( p_intf, p_sys->fd, buffer, MAX_SAP_BUFFER );
304         uint8_t *p_sdp;
305
306         /* Minimum length is > 6 */
307         if( i_read <= 6 )
308         {
309             if( i_read < 0 )
310             {
311                 msg_Warn( p_intf, "Cannot read in the socket" );
312             }
313             continue;
314         }
315
316         buffer[i_read] = '\0';
317
318         /* Parse the SAP header */
319         p_sdp  = &buffer[4];
320         p_sdp += (buffer[0]&0x10) ? 16 : 4;
321         p_sdp += buffer[1];
322
323         while( p_sdp < &buffer[i_read-1] && *p_sdp != '\0' && p_sdp[0] != 'v' && p_sdp[1] != '=' )
324         {
325             p_sdp++;
326         }
327         if( *p_sdp == '\0' )
328         {
329             p_sdp++;
330         }
331         if( p_sdp < &buffer[i_read] )
332         {
333             sess_descr_t *p_sd = parse_sdp( p_intf, p_sdp );
334             if( p_sd )
335             {
336                 sess_toitem ( p_intf, p_sd );
337                 free_sd ( p_sd );
338             }
339         }
340     }
341 }
342
343 /**********************************************************************
344  * cfield_parse
345  *********************************************************************
346  * put into *ppsz_uri, the the uri in the cfield, psz_cfield.
347  *********************************************************************/
348
349 static void cfield_parse( char *psz_cfield, char **ppsz_uri )
350 {
351
352     char *psz_pos;
353     if( psz_cfield )
354     {
355         psz_pos = psz_cfield;
356
357         while( *psz_pos != ' ' && *psz_pos !='\0' )
358         {
359             psz_pos++;
360         }
361         psz_pos++;
362         while( *psz_pos != ' ' && *psz_pos !='\0' )
363         {
364             psz_pos++;
365         }
366         psz_pos++;
367         *ppsz_uri = psz_pos;
368         while( *psz_pos != ' ' && *psz_pos !='/'
369                         && *psz_pos != '\0' )
370         {
371             psz_pos++;
372         }
373         *psz_pos = '\0';
374
375     }
376     else
377     {
378         ppsz_uri = NULL;
379     }
380
381     return;
382
383 }
384
385 /**********************************************************************
386  * mfield_parse
387  *********************************************************************
388  * put into *ppsz_proto, and *ppsz_port, the protocol and the port.
389  *********************************************************************/
390
391
392 static void mfield_parse( char *psz_mfield, char **ppsz_proto,
393                char **ppsz_port )
394 {
395     char *psz_pos;
396     if( psz_mfield )
397     {
398         psz_pos = psz_mfield;
399         while( *psz_pos != '\0' && *psz_pos != ' ' )
400         {
401             psz_pos++;
402         }
403         psz_pos++;
404         *ppsz_port = psz_pos;
405         while( *psz_pos != '\0' && *psz_pos && *psz_pos !=' ' && *psz_pos!='/' )
406         {
407             psz_pos++;
408         }
409         if( *psz_pos == '/' )  // FIXME does not support multi-port
410         {
411             *psz_pos = '\0';
412             psz_pos++;
413             while( *psz_pos != '\0' && *psz_pos !=' ' )
414             {
415             psz_pos++;
416             }
417         }
418         *psz_pos = '\0';
419         psz_pos++;
420         *ppsz_proto = psz_pos;
421         while( *psz_pos!='\0' && *psz_pos !=' ' &&
422                         *psz_pos!='/' )
423         {
424             *psz_pos = tolower( *psz_pos );
425             psz_pos++;
426         }
427         *psz_pos = '\0';
428     }
429     else
430     {
431         *ppsz_proto = NULL;
432         *ppsz_port = NULL;
433     }
434     return;
435 }
436
437
438 /*******************************************************************
439  * sess_toitem : changes a sess_descr_t into a hurd of
440  * playlist_item_t, which are enqueued.
441  *******************************************************************
442  * Note : does not support sessions that take place on consecutive
443  * port or adresses yet.
444  *******************************************************************/
445
446 static void sess_toitem( intf_thread_t * p_intf, sess_descr_t * p_sd )
447 {
448     playlist_item_t * p_item;
449     char *psz_uri, *psz_proto;
450     char *psz_port;
451     char *psz_uri_default;
452     int i_count , i;
453     vlc_bool_t b_http = VLC_FALSE;
454     char *psz_http_path = NULL;
455     playlist_t *p_playlist;
456
457     psz_uri_default = NULL;
458     cfield_parse( p_sd->psz_connection, &psz_uri_default );
459
460     for( i_count = 0 ; i_count < p_sd->i_media ; i_count++ )
461     {
462         p_item = malloc( sizeof( playlist_item_t ) );
463         p_item->psz_name    = strdup( p_sd->psz_sessionname );
464         p_item->psz_uri     = NULL;
465         p_item->i_duration  = -1;
466         p_item->ppsz_options= NULL;
467         p_item->i_options   = 0;
468
469         p_item->i_type      = 0;
470         p_item->i_status    = 0;
471         p_item->b_autodeletion = VLC_FALSE;
472         p_item->b_enabled   = VLC_TRUE;
473         p_item->i_group     = p_intf->p_sys->i_group;
474         p_item->psz_author  = strdup( "" );
475
476         psz_uri = NULL;
477
478         /* Build what we have to put in p_item->psz_uri, with the m and
479          *  c fields  */
480
481         if( !p_sd->pp_media[i_count] )
482         {
483             return;
484         }
485
486         mfield_parse( p_sd->pp_media[i_count]->psz_medianame,
487                         & psz_proto, & psz_port );
488
489         if( !psz_proto || !psz_port )
490         {
491             return;
492         }
493
494         if( p_sd->pp_media[i_count]->psz_mediaconnection )
495         {
496             cfield_parse( p_sd->pp_media[i_count]->psz_mediaconnection,
497                             & psz_uri );
498         }
499         else
500         {
501             psz_uri = psz_uri_default;
502         }
503
504         if( psz_uri == NULL )
505         {
506             return;
507         }
508
509         for( i = 0 ; i< p_sd->i_attributes ; i++ )
510         {
511             if(!strcasecmp( p_sd->pp_attributes[i]->psz_field , "type") &&
512                 strstr( p_sd->pp_attributes[i]->psz_value, "http") )
513             {
514                 b_http = VLC_TRUE;
515             }
516             if(!strcasecmp( p_sd->pp_attributes[i]->psz_field , "http-path"))
517             {
518                 psz_http_path = strdup(  p_sd->pp_attributes[i]->psz_value );
519             }
520         }
521
522
523         /* Filling p_item->psz_uri */
524         if( b_http == VLC_FALSE )
525         {
526             p_item->psz_uri = malloc( strlen( psz_proto ) + strlen( psz_uri ) +
527                                       strlen( psz_port ) + 7 );
528             if( ismult( psz_uri ) )
529             {
530                 sprintf( p_item->psz_uri, "%s://@%s:%s",
531                          psz_proto, psz_uri, psz_port );
532             }
533             else
534             {
535                 sprintf( p_item->psz_uri, "%s://%s:%s",
536                          psz_proto, psz_uri, psz_port );
537             }
538         }
539         else
540         {
541             if( psz_http_path == NULL )
542             {
543                 psz_http_path = strdup( "/" );
544             }
545
546             p_item->psz_uri = malloc( strlen( psz_proto ) + strlen( psz_uri ) +
547                                       strlen( psz_port ) + strlen(psz_http_path) + 5 );
548             sprintf( p_item->psz_uri, "%s://%s:%s%s", psz_proto,
549                             psz_uri, psz_port,psz_http_path );
550
551             if( psz_http_path )
552             {
553                 free( psz_http_path );
554             }
555         }
556
557         /* Enqueueing p_item in the playlist */
558         p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
559         playlist_AddItem ( p_playlist, p_item, PLAYLIST_CHECK_INSERT, PLAYLIST_END );
560         vlc_object_release( p_playlist );
561     }
562 }
563
564 /***********************************************************************
565  * parse_sdp : SDP parsing
566  * *********************************************************************
567  * Make a sess_descr_t with a psz
568  ***********************************************************************/
569
570 static sess_descr_t *  parse_sdp( intf_thread_t * p_intf, char *p_packet )
571 {
572     sess_descr_t *  sd;
573
574     if( p_packet[0] != 'v' || p_packet[1] != '=' )
575     {
576         msg_Warn(p_intf, "bad SDP packet");
577         return NULL;
578     }
579
580     sd = malloc( sizeof( sess_descr_t ) );
581     sd->psz_sessionname = NULL;
582     sd->psz_connection  = NULL;
583     sd->i_media         = 0;
584     sd->pp_media        = NULL;
585     sd->i_attributes    = 0;
586     sd->pp_attributes   = NULL;
587
588     while( *p_packet != '\0'  )
589     {
590         char *psz_end;
591
592         /* Search begin of field */
593         while( *p_packet == '\n' || *p_packet == ' ' || *p_packet == '\t' )
594         {
595             p_packet++;
596         }
597         /* search end of line */
598         if( ( psz_end = strchr( p_packet, '\n' ) ) == NULL )
599         {
600             psz_end = p_packet + strlen( p_packet );
601         }
602
603         if( psz_end <= p_packet )
604         {
605             break;
606         }
607         *psz_end++ = '\0';
608
609         if( p_packet[1] != '=' )
610         {
611             msg_Warn( p_intf, "packet invalid" );
612             free_sd( sd );
613             return NULL;
614         }
615
616         switch( p_packet[0] )
617         {
618             case( 'v' ):
619                 sd->i_version = atoi( &p_packet[2] );
620                 break;
621             case( 's' ):
622                 sd->psz_sessionname = strdup( &p_packet[2] );
623                 break;
624             case ( 'o' ):
625             case( 'i' ):
626             case( 'u' ):
627             case( 'e' ):
628             case( 'p' ):
629             case( 't' ):
630             case( 'r' ):
631                 break;
632             case( 'a' ):
633             {
634                 char *psz_eof = strchr( &p_packet[2], ':' );
635
636                 if( psz_eof && psz_eof[1] != '\0' )
637                 {
638                     attr_descr_t *attr = malloc( sizeof( attr_descr_t ) );
639
640                     *psz_eof++ = '\0';
641
642                     attr->psz_field = strdup( &p_packet[2] );
643                     attr->psz_value = strdup( psz_eof );
644
645                     TAB_APPEND( sd->i_attributes, sd->pp_attributes, attr );
646                 }
647                 break;
648             }
649
650             case( 'm' ):
651             {
652                 media_descr_t *media = malloc( sizeof( media_descr_t ) );
653
654                 media->psz_medianame = strdup( &p_packet[2] );
655                 media->psz_mediaconnection = NULL;
656
657                 TAB_APPEND( sd->i_media, sd->pp_media, media );
658                 break;
659             }
660
661             case( 'c' ):
662                 if( sd->i_media <= 0 )
663                 {
664                     sd->psz_connection = strdup( &p_packet[2] );
665                 }
666                 else
667                 {
668                     sd->pp_media[sd->i_media-1]->psz_mediaconnection = strdup( &p_packet[2] );
669                 }
670                break;
671
672             default:
673                break;
674         }
675
676         p_packet = psz_end;
677     }
678
679     return sd;
680 }
681
682 #define FREE( p ) \
683     if( p ) { free( p ); (p) = NULL; }
684 static void free_sd( sess_descr_t * p_sd )
685 {
686     int i;
687
688     FREE( p_sd->psz_sessionname );
689     FREE( p_sd->psz_connection );
690
691     for( i = 0; i < p_sd->i_media ; i++ )
692     {
693         FREE( p_sd->pp_media[i]->psz_medianame );
694         FREE( p_sd->pp_media[i]->psz_mediaconnection );
695     }
696     for( i = 0; i < p_sd->i_attributes ; i++ )
697     {
698         FREE( p_sd->pp_attributes[i]->psz_field );
699         FREE( p_sd->pp_attributes[i]->psz_value );
700     }
701     FREE( p_sd->pp_attributes );
702     FREE( p_sd->pp_media );
703
704     free( p_sd );
705 }
706
707 /***********************************************************************
708  * ismult: returns true if we have a multicast address
709  ***********************************************************************/
710
711 static int ismult( char *psz_uri )
712 {
713     char *psz_end;
714     int  i_value;
715
716     i_value = strtol( psz_uri, &psz_end, 0 );
717
718     /* IPv6 */
719     if( psz_uri[0] == '[')
720     {
721       if( strncasecmp( &psz_uri[1], "FF0" , 3) ||
722           strncasecmp( &psz_uri[2], "FF0" , 3))
723             return( VLC_TRUE );
724         else
725             return( VLC_FALSE );
726     }
727
728     if( *psz_end != '.' ) { return( VLC_FALSE ); }
729
730     return( i_value < 224 ? VLC_FALSE : VLC_TRUE );
731 }
732
733
734
735 /*****************************************************************************
736  * Read: read on a file descriptor, checking b_die periodically
737  *****************************************************************************
738  * Taken from udp.c
739  *****************************************************************************/
740 static ssize_t NetRead( intf_thread_t *p_intf,
741                         int fd[2], uint8_t *p_buffer, int i_len )
742 {
743 #ifdef UNDER_CE
744     return -1;
745 #else
746     struct timeval  timeout;
747     fd_set          fds;
748     int             i_ret;
749     int             i_handle_max = __MAX( fd[0], fd[1] );
750
751     /* Initialize file descriptor set */
752     FD_ZERO( &fds );
753     if( fd[0] > 0 ) FD_SET( fd[0], &fds );
754     if( fd[1] > 0 ) FD_SET( fd[1], &fds );
755
756     /* We'll wait 0.5 second if nothing happens */
757     timeout.tv_sec = 0;
758     timeout.tv_usec = 500000;
759
760     /* Find if some data is available */
761     i_ret = select( i_handle_max + 1, &fds, NULL, NULL, &timeout );
762
763     if( i_ret == -1 && errno != EINTR )
764     {
765         msg_Err( p_intf, "network select error (%s)", strerror(errno) );
766     }
767     else if( i_ret > 0 )
768     {
769         if( fd[0] > 0 && FD_ISSET( fd[0], &fds ) )
770         {
771              return recv( fd[0], p_buffer, i_len, 0 );
772         }
773         else if( fd[1] > 0 && FD_ISSET( fd[1], &fds ) )
774         {
775              return recv( fd[1], p_buffer, i_len, 0 );
776         }
777     }
778     return 0;
779 #endif
780 }
781