]> git.sesse.net Git - vlc/blob - src/network/udp.c
Implement IPv6 multicast output interface selection (closes #491)
[vlc] / src / network / udp.c
1 /*****************************************************************************
2  * udp.c:
3  *****************************************************************************
4  * Copyright (C) 2004-2006 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Laurent Aimar <fenrir@videolan.org>
8  *          RĂ©mi Denis-Courmont <rem # videolan.org>
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  * Preamble
27  *****************************************************************************/
28 #include <stdlib.h>
29 #include <vlc/vlc.h>
30
31 #include <errno.h>
32
33 #ifdef HAVE_SYS_TIME_H
34 #    include <sys/time.h>
35 #endif
36
37 #include "network.h"
38
39 #ifdef WIN32
40 #   if defined(UNDER_CE)
41 #       undef IP_MULTICAST_TTL
42 #       define IP_MULTICAST_TTL 3
43 #       undef IP_ADD_MEMBERSHIP
44 #       define IP_ADD_MEMBERSHIP 5
45 #   endif
46 #   define EAFNOSUPPORT WSAEAFNOSUPPORT
47 #   define if_nametoindex( str ) atoi( str )
48 #else
49 #   include <unistd.h>
50 #   include <net/if.h>
51 #endif
52
53 #ifndef SOL_IP
54 # define SOL_IP IPPROTO_IP
55 #endif
56 #ifndef SOL_IPV6
57 # define SOL_IPV6 IPPROTO_IPV6
58 #endif
59 #ifndef IPPROTO_IPV6
60 # define IPPROTO_IPV6 41
61 #endif
62
63 extern int net_Socket( vlc_object_t *p_this, int i_family, int i_socktype,
64                        int i_protocol );
65
66
67 static int net_SetMcastHopLimit( vlc_object_t *p_this,
68                                  int fd, int family, int hlim )
69 {
70 #ifndef SYS_BEOS
71     int proto, cmd;
72
73     /* There is some confusion in the world whether IP_MULTICAST_TTL 
74      * takes a byte or an int as an argument.
75      * BSD seems to indicate byte so we are going with that and use
76      * int as a fallback to be safe */
77     switch( family )
78     {
79         case AF_INET:
80             proto = SOL_IP;
81             cmd = IP_MULTICAST_TTL;
82             break;
83
84 #ifdef IPV6_MULTICAST_HOPS
85         case AF_INET6:
86             proto = SOL_IPV6;
87             cmd = IPV6_MULTICAST_HOPS;
88             break;
89 #endif
90
91         default:
92             msg_Warn( p_this, "%s", strerror( EAFNOSUPPORT ) );
93             return VLC_EGENERIC;
94     }
95
96     if( setsockopt( fd, proto, cmd, &hlim, sizeof( hlim ) ) < 0 )
97     {
98         /* BSD compatibility */
99         unsigned char buf;
100
101         buf = (unsigned char)(( hlim > 255 ) ? 255 : hlim);
102         if( setsockopt( fd, proto, cmd, &buf, sizeof( buf ) ) )
103             return VLC_EGENERIC;
104     }
105 #endif
106     return VLC_SUCCESS;
107 }
108
109
110 static int net_SetMcastIface( vlc_object_t *p_this,
111                               int fd, int family, const char *str )
112 {
113     switch( family )
114     {
115 #ifndef SYS_BEOS
116         case AF_INET:
117         {
118             struct in_addr addr;
119
120             if( inet_pton( AF_INET, str, &addr) <= 0 )
121             {
122                 msg_Err( p_this, "Invalid multicast interface %s", str );
123                 return VLC_EGENERIC;
124             }
125
126             if( setsockopt( fd, SOL_IP, IP_MULTICAST_IF, &addr,
127                             sizeof( addr ) ) < 0 )
128             {
129                 msg_Err( p_this, "Cannot use %s as multicast interface: %s",
130                          strerror(errno) );
131                 return VLC_EGENERIC;
132             }
133             break;
134         }
135 #endif /* SYS_BEOS */
136
137 #ifdef IPV6_MULTICAST_IF
138         case AF_INET6:
139         {
140             int scope = if_nametoindex( str );
141
142             if( scope == 0 )
143             {
144                 msg_Err( p_this, "Invalid multicast interface %s", str );
145                 return VLC_EGENERIC;
146             }
147
148             if( setsockopt( fd, SOL_IPV6, IPV6_MULTICAST_IF,
149                             &scope, sizeof( scope ) ) < 0 )
150             {
151                 msg_Err( p_this, "Cannot use %s as multicast interface: %s",
152                          str, strerror( errno ) );
153                 return VLC_EGENERIC;
154             }
155             break;
156         }
157 #endif
158
159         default:
160             msg_Warn( p_this, "%s", strerror( EAFNOSUPPORT ) );
161             return VLC_EGENERIC;
162     }
163
164     return VLC_SUCCESS;
165 }
166
167 /*****************************************************************************
168  * __net_ConnectUDP:
169  *****************************************************************************
170  * Open a UDP socket to send data to a defined destination, with an optional
171  * hop limit.
172  *****************************************************************************/
173 int __net_ConnectUDP( vlc_object_t *p_this, const char *psz_host, int i_port,
174                       int i_hlim )
175 {
176     struct addrinfo hints, *res, *ptr;
177     int             i_val, i_handle = -1;
178     vlc_bool_t      b_unreach = VLC_FALSE;
179
180     if( i_port == 0 )
181         i_port = 1234; /* historical VLC thing */
182
183     if( i_hlim < 1 )
184         i_hlim = var_CreateGetInteger( p_this, "ttl" );
185
186     memset( &hints, 0, sizeof( hints ) );
187     hints.ai_socktype = SOCK_DGRAM;
188
189     msg_Dbg( p_this, "net: connecting to %s port %d", psz_host, i_port );
190
191     i_val = vlc_getaddrinfo( p_this, psz_host, i_port, &hints, &res );
192     if( i_val )
193     {
194         msg_Err( p_this, "cannot resolve %s port %d : %s", psz_host, i_port,
195                  vlc_gai_strerror( i_val ) );
196         return -1;
197     }
198
199     for( ptr = res; ptr != NULL; ptr = ptr->ai_next )
200     {
201         int fd;
202         char *psz_mif;
203
204         fd = net_Socket( p_this, ptr->ai_family, ptr->ai_socktype,
205                          ptr->ai_protocol );
206         if( fd == -1 )
207             continue;
208 #if !defined( SYS_BEOS )
209         else
210         {
211             int i_val;
212
213             /* Increase the receive buffer size to 1/2MB (8Mb/s during 1/2s)
214              * to avoid packet loss caused by scheduling problems */
215             i_val = 0x80000;
216             setsockopt( fd, SOL_SOCKET, SO_RCVBUF, (void *)&i_val,
217                         sizeof( i_val ) );
218             i_val = 0x80000;
219             setsockopt( fd, SOL_SOCKET, SO_SNDBUF, (void *)&i_val,
220                         sizeof( i_val ) );
221
222             /* Allow broadcast sending */
223             i_val = 1;
224             setsockopt( fd, SOL_SOCKET, SO_BROADCAST, (void*)&i_val,
225                         sizeof( i_val ) );
226         }
227 #endif
228
229         if( i_hlim > 0 )
230             net_SetMcastHopLimit( p_this, fd, ptr->ai_family, i_hlim );
231         psz_mif = config_GetPsz( p_this, (ptr->ai_family != AF_INET)
232                                             ? "miface" : "miface-addr" );
233         if( psz_mif != NULL )
234         {
235             net_SetMcastIface( p_this, fd, ptr->ai_family, psz_mif );
236             free( psz_mif );
237         }
238
239         if( connect( fd, ptr->ai_addr, ptr->ai_addrlen ) == 0 )
240         {
241             /* success */
242             i_handle = fd;
243             break;
244         }
245
246 #if defined( WIN32 ) || defined( UNDER_CE )
247         if( WSAGetLastError () == WSAENETUNREACH )
248 #else
249         if( errno == ENETUNREACH )
250 #endif
251             b_unreach = VLC_TRUE;
252         else
253         {
254             msg_Warn( p_this, "%s port %d : %s", psz_host, i_port,
255                       strerror( errno ) );
256             net_Close( fd );
257             continue;
258         }
259     }
260
261     vlc_freeaddrinfo( res );
262
263     if( i_handle == -1 )
264     {
265         if( b_unreach )
266             msg_Err( p_this, "Host %s port %d is unreachable", psz_host,
267                      i_port );
268         return -1;
269     }
270
271     return i_handle;
272 }
273
274 /*****************************************************************************
275  * __net_OpenUDP:
276  *****************************************************************************
277  * Open a UDP connection and return a handle
278  *****************************************************************************/
279 int __net_OpenUDP( vlc_object_t *p_this, const char *psz_bind, int i_bind,
280                    const char *psz_server, int i_server )
281 {
282     vlc_value_t      v4, v6;
283     void            *private;
284     network_socket_t sock;
285     module_t         *p_network = NULL;
286
287     if( ( psz_server != NULL ) && ( psz_server[0] == '\0' ) )
288         msg_Warn( p_this, "calling net_OpenUDP with an explicit destination "
289                   "is obsolete - use net_ConnectUDP instead" );
290     if( i_server != 0 )
291         msg_Warn( p_this, "calling net_OpenUDP with an explicit destination "
292                   "port is obsolete - use __net_ConnectUDP instead" );
293
294     if( psz_server == NULL ) psz_server = "";
295     if( psz_bind == NULL ) psz_bind = "";
296
297     /* Prepare the network_socket_t structure */
298     sock.psz_bind_addr   = psz_bind;
299     sock.i_bind_port     = i_bind;
300     sock.psz_server_addr = psz_server;
301     sock.i_server_port   = i_server;
302     sock.i_ttl           = 0;
303     sock.v6only          = 0;
304     sock.i_handle        = -1;
305
306     msg_Dbg( p_this, "net: connecting to '[%s]:%d@[%s]:%d'",
307              psz_server, i_server, psz_bind, i_bind );
308
309     /* Check if we have force ipv4 or ipv6 */
310     var_Create( p_this, "ipv4", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
311     var_Get( p_this, "ipv4", &v4 );
312     var_Create( p_this, "ipv6", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
313     var_Get( p_this, "ipv6", &v6 );
314
315     if( !v4.b_bool )
316     {
317         if( v6.b_bool )
318             sock.v6only = 1;
319
320         /* try IPv6 first (unless IPv4 forced) */
321         private = p_this->p_private;
322         p_this->p_private = (void*)&sock;
323         p_network = module_Need( p_this, "network", "ipv6", VLC_TRUE );
324
325         if( p_network != NULL )
326             module_Unneed( p_this, p_network );
327
328         p_this->p_private = private;
329
330         /*
331          * Check if the IP stack can receive IPv4 packets on IPv6 sockets.
332          * If yes, then it is better to use the IPv6 socket.
333          * Otherwise, if we also get an IPv4, we have to choose, so we use
334          * IPv4 only.
335          */
336         if( ( sock.i_handle != -1 ) && ( ( sock.v6only == 0 ) || v6.b_bool ) )
337             return sock.i_handle;
338     }
339
340     if( !v6.b_bool )
341     {
342         int fd6 = sock.i_handle;
343
344         /* also try IPv4 (unless IPv6 forced) */
345         private = p_this->p_private;
346         p_this->p_private = (void*)&sock;
347         p_network = module_Need( p_this, "network", "ipv4", VLC_TRUE );
348
349         if( p_network != NULL )
350             module_Unneed( p_this, p_network );
351
352         p_this->p_private = private;
353
354         if( fd6 != -1 )
355         {
356             if( sock.i_handle != -1 )
357             {
358                 msg_Warn( p_this, "net: lame IPv6/IPv4 dual-stack present. "
359                                   "Using only IPv4." );
360                 net_Close( fd6 );
361             }
362             else
363                 sock.i_handle = fd6;
364         }
365     }
366
367     if( sock.i_handle == -1 )
368         msg_Dbg( p_this, "net: connection to '[%s]:%d@[%s]:%d' failed",
369                 psz_server, i_server, psz_bind, i_bind );
370
371     return sock.i_handle;
372 }