udpnetutils: add helper functions for udp uri handling
[platform/upstream/gst-plugins-good.git] / gst / udp / gstudpnetutils.c
1 /* GStreamer UDP network utility functions
2  * Copyright (C) 2006 Tim-Philipp Müller <tim centricular net>
3  * Copyright (C) 2006 Joni Valtanen <joni.valtanen@movial.fi>
4  * Copyright (C) 2009 Jarkko Palviainen <jarkko.palviainen@sesca.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include <errno.h>
27 #include <stdio.h>
28 #include <memory.h>
29
30 #include <gst/gst.h>
31
32 #include "gstudpnetutils.h"
33
34 /* EAI_ADDRFAMILY was obsoleted in BSD at some point */
35 #ifndef EAI_ADDRFAMILY
36 #define EAI_ADDRFAMILY 1
37 #endif
38
39 #ifdef G_OS_WIN32
40
41 gboolean
42 gst_udp_net_utils_win32_wsa_startup (GstObject * obj)
43 {
44   WSADATA w;
45   int error;
46
47   error = WSAStartup (0x0202, &w);
48
49   if (error) {
50     GST_WARNING_OBJECT (obj, "WSAStartup error: %d", error);
51     return FALSE;
52   }
53
54   if (w.wVersion != 0x0202) {
55     WSACleanup ();
56     GST_WARNING_OBJECT (obj, "Winsock version wrong : 0x%x", w.wVersion);
57     return FALSE;
58   }
59
60   return TRUE;
61 }
62
63 #endif
64
65 int
66 gst_udp_get_sockaddr_length (struct sockaddr_storage *addr)
67 {
68   /* MacOS is picky about passing precisely the correct length,
69    * so we calculate it here for the given socket type.
70    */
71   switch (addr->ss_family) {
72     case AF_INET:
73       return sizeof (struct sockaddr_in);
74     case AF_INET6:
75       return sizeof (struct sockaddr_in6);
76     default:
77       /* don't know, Screw MacOS and use the full length */
78       return sizeof (*addr);
79   }
80 }
81
82 int
83 gst_udp_get_addr (const char *hostname, int port, struct sockaddr_storage *addr)
84 {
85   struct addrinfo hints, *res = NULL, *nres;
86   char service[NI_MAXSERV];
87   int ret;
88
89   memset (&hints, 0, sizeof (hints));
90   hints.ai_family = AF_UNSPEC;
91   hints.ai_socktype = SOCK_DGRAM;
92   g_snprintf (service, sizeof (service) - 1, "%d", port);
93   service[sizeof (service) - 1] = '\0';
94
95   if ((ret = getaddrinfo (hostname, (port == -1) ? NULL : service, &hints,
96               &res)) < 0) {
97     goto beach;
98   }
99
100   nres = res;
101   while (nres) {
102     if (nres->ai_family == AF_INET || nres->ai_family == AF_INET6)
103       break;
104     nres = nres->ai_next;
105   }
106
107   if (nres) {
108     memcpy (addr, nres->ai_addr, nres->ai_addrlen);
109   } else {
110     ret = EAI_ADDRFAMILY;
111   }
112
113   freeaddrinfo (res);
114 beach:
115   return ret;
116 }
117
118 int
119 gst_udp_set_loop (int sockfd, gboolean loop)
120 {
121   socklen_t socklen;
122   struct sockaddr_storage addr;
123   int ret = -1;
124   int l = (loop == FALSE) ? 0 : 1;
125
126   socklen = sizeof (addr);
127   if ((ret = getsockname (sockfd, (struct sockaddr *) &addr, &socklen)) < 0) {
128     return ret;
129   }
130
131   switch (addr.ss_family) {
132     case AF_INET:
133     {
134       ret = setsockopt (sockfd, IPPROTO_IP, IP_MULTICAST_LOOP, &l, sizeof (l));
135       if (ret < 0)
136         return ret;
137
138       break;
139     }
140     case AF_INET6:
141     {
142       ret =
143           setsockopt (sockfd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &l,
144           sizeof (l));
145       if (ret < 0)
146         return ret;
147
148       break;
149     }
150     default:
151 #ifdef G_OS_WIN32
152       WSASetLastError (WSAEAFNOSUPPORT);
153 #else
154       errno = EAFNOSUPPORT;
155 #endif
156   }
157
158   return ret;
159 }
160
161 int
162 gst_udp_set_ttl (int sockfd, int ttl, gboolean is_multicast)
163 {
164   socklen_t socklen;
165   struct sockaddr_storage addr;
166   int optname = -1;
167   int ret = -1;
168
169   socklen = sizeof (addr);
170   if ((ret = getsockname (sockfd, (struct sockaddr *) &addr, &socklen)) < 0) {
171     return ret;
172   }
173
174   switch (addr.ss_family) {
175     case AF_INET:
176     {
177       optname = (is_multicast == TRUE) ? IP_MULTICAST_TTL : IP_TTL;
178       ret = setsockopt (sockfd, IPPROTO_IP, optname, &ttl, sizeof (ttl));
179       if (ret < 0)
180         return ret;
181       break;
182     }
183     case AF_INET6:
184     {
185       optname =
186           (is_multicast == TRUE) ? IPV6_MULTICAST_HOPS : IPV6_UNICAST_HOPS;
187       ret = setsockopt (sockfd, IPPROTO_IPV6, optname, &ttl, sizeof (ttl));
188       if (ret < 0)
189         return ret;
190
191       /* When using IPV4 address with IPV6 socket, both TTL values
192          must be set in order to actually use the given value.
193          Has no effect when IPV6 address is used. */
194       optname = (is_multicast == TRUE) ? IP_MULTICAST_TTL : IP_TTL;
195       ret = setsockopt (sockfd, IPPROTO_IP, optname, &ttl, sizeof (ttl));
196       if (ret < 0)
197         return ret;
198       break;
199     }
200     default:
201 #ifdef G_OS_WIN32
202       WSASetLastError (WSAEAFNOSUPPORT);
203 #else
204       errno = EAFNOSUPPORT;
205 #endif
206   }
207   return ret;
208 }
209
210 /* FIXME: Add interface selection for windows hosts.  */
211 int
212 gst_udp_join_group (int sockfd, struct sockaddr_storage *addr, gchar * iface)
213 {
214   int ret = -1;
215
216   switch (addr->ss_family) {
217     case AF_INET:
218     {
219 #ifdef HAVE_IP_MREQN
220       struct ip_mreqn mreq4;
221 #else
222       struct ip_mreq mreq4;
223 #endif
224
225       memset (&mreq4, 0, sizeof (mreq4));
226       mreq4.imr_multiaddr.s_addr =
227           ((struct sockaddr_in *) addr)->sin_addr.s_addr;
228 #ifdef HAVE_IP_MREQN
229       if (iface)
230         mreq4.imr_ifindex = if_nametoindex (iface);
231       else
232         mreq4.imr_ifindex = 0;  /* Pick any.  */
233 #else
234       mreq4.imr_interface.s_addr = INADDR_ANY;
235 #endif
236
237       if ((ret =
238               setsockopt (sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
239                   (const void *) &mreq4, sizeof (mreq4))) < 0)
240         return ret;
241
242       break;
243     }
244     case AF_INET6:
245     {
246       struct ipv6_mreq mreq6;
247
248       memset (&mreq6, 0, sizeof (mreq6));
249       memcpy (&mreq6.ipv6mr_multiaddr,
250           &(((struct sockaddr_in6 *) addr)->sin6_addr),
251           sizeof (struct in6_addr));
252       mreq6.ipv6mr_interface = 0;
253 #if !defined(G_OS_WIN32)
254       if (iface)
255         mreq6.ipv6mr_interface = if_nametoindex (iface);
256 #endif
257
258       if ((ret =
259               setsockopt (sockfd, IPPROTO_IPV6, IPV6_JOIN_GROUP,
260                   (const void *) &mreq6, sizeof (mreq6))) < 0)
261         return ret;
262
263       break;
264     }
265     default:
266 #ifdef G_OS_WIN32
267       WSASetLastError (WSAEAFNOSUPPORT);
268 #else
269       errno = EAFNOSUPPORT;
270 #endif
271   }
272   return ret;
273 }
274
275 int
276 gst_udp_leave_group (int sockfd, struct sockaddr_storage *addr)
277 {
278   int ret = -1;
279
280   switch (addr->ss_family) {
281     case AF_INET:
282     {
283       struct ip_mreq mreq4;
284
285       memset (&mreq4, 0, sizeof (mreq4));
286       mreq4.imr_multiaddr.s_addr =
287           ((struct sockaddr_in *) addr)->sin_addr.s_addr;
288       mreq4.imr_interface.s_addr = INADDR_ANY;
289
290       if ((ret =
291               setsockopt (sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP,
292                   (const void *) &mreq4, sizeof (mreq4))) < 0)
293         return ret;
294     }
295       break;
296
297     case AF_INET6:
298     {
299       struct ipv6_mreq mreq6;
300
301       memset (&mreq6, 0, sizeof (mreq6));
302       memcpy (&mreq6.ipv6mr_multiaddr,
303           &(((struct sockaddr_in6 *) addr)->sin6_addr),
304           sizeof (struct in6_addr));
305       mreq6.ipv6mr_interface = 0;
306
307       if ((ret =
308               setsockopt (sockfd, IPPROTO_IPV6, IPV6_LEAVE_GROUP,
309                   (const void *) &mreq6, sizeof (mreq6))) < 0)
310         return ret;
311     }
312       break;
313
314     default:
315 #ifdef G_OS_WIN32
316       WSASetLastError (WSAEAFNOSUPPORT);
317 #else
318       errno = EAFNOSUPPORT;
319 #endif
320   }
321
322   return ret;
323 }
324
325 int
326 gst_udp_is_multicast (struct sockaddr_storage *addr)
327 {
328   int ret = -1;
329
330   switch (addr->ss_family) {
331     case AF_INET:
332     {
333       struct sockaddr_in *addr4 = (struct sockaddr_in *) addr;
334
335       ret = IN_MULTICAST (g_ntohl (addr4->sin_addr.s_addr));
336     }
337       break;
338
339     case AF_INET6:
340     {
341       struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) addr;
342
343       ret = IN6_IS_ADDR_MULTICAST (&addr6->sin6_addr);
344     }
345       break;
346
347     default:
348 #ifdef G_OS_WIN32
349       WSASetLastError (WSAEAFNOSUPPORT);
350 #else
351       errno = EAFNOSUPPORT;
352 #endif
353   }
354
355   return ret;
356 }
357
358 void
359 gst_udp_uri_init (GstUDPUri * uri, const gchar * host, gint port)
360 {
361   uri->host = NULL;
362   uri->port = -1;
363   gst_udp_uri_update (uri, host, port);
364 }
365
366 int
367 gst_udp_uri_update (GstUDPUri * uri, const gchar * host, gint port)
368 {
369   if (host) {
370     g_free (uri->host);
371     uri->host = g_strdup (host);
372     if (strchr (host, ':'))
373       uri->is_ipv6 = TRUE;
374     else
375       uri->is_ipv6 = FALSE;
376   }
377   if (port != -1)
378     uri->port = port;
379
380   return 0;
381 }
382
383 int
384 gst_udp_parse_uri (const gchar * uristr, GstUDPUri * uri)
385 {
386   gchar *protocol;
387   gchar *location, *location_end;
388   gchar *colptr;
389
390   protocol = gst_uri_get_protocol (uristr);
391   if (strcmp (protocol, "udp") != 0)
392     goto wrong_protocol;
393   g_free (protocol);
394
395   location = gst_uri_get_location (uristr);
396   if (!location)
397     return FALSE;
398
399   GST_DEBUG ("got location '%s'", location);
400
401   if (location[0] == '[') {
402     GST_DEBUG ("parse IPV6 address '%s'", location);
403     location_end = strchr (location, ']');
404     if (location_end == NULL)
405       goto wrong_address;
406
407     uri->is_ipv6 = TRUE;
408     g_free (uri->host);
409     uri->host = g_strndup (location + 1, location_end - location - 1);
410     colptr = strrchr (location_end, ':');
411   } else {
412     GST_DEBUG ("parse IPV4 address '%s'", location);
413     uri->is_ipv6 = FALSE;
414     colptr = strrchr (location, ':');
415
416     g_free (uri->host);
417     if (colptr != NULL) {
418       uri->host = g_strndup (location, colptr - location);
419     } else {
420       uri->host = g_strdup (location);
421     }
422   }
423   GST_DEBUG ("host set to '%s'", uri->host);
424
425   if (colptr != NULL) {
426     uri->port = atoi (colptr + 1);
427   }
428   g_free (location);
429
430   return 0;
431
432   /* ERRORS */
433 wrong_protocol:
434   {
435     GST_ERROR ("error parsing uri %s: wrong protocol (%s != udp)", uristr,
436         protocol);
437     g_free (protocol);
438     return -1;
439   }
440 wrong_address:
441   {
442     GST_ERROR ("error parsing uri %s", uristr);
443     g_free (location);
444     return -1;
445   }
446 }
447
448 gchar *
449 gst_udp_uri_string (GstUDPUri * uri)
450 {
451   gchar *result;
452
453   if (uri->is_ipv6) {
454     result = g_strdup_printf ("udp://[%s]:%d", uri->host, uri->port);
455   } else {
456     result = g_strdup_printf ("udp://%s:%d", uri->host, uri->port);
457   }
458   return result;
459 }
460
461 void
462 gst_udp_uri_free (GstUDPUri * uri)
463 {
464   g_free (uri->host);
465   uri->host = NULL;
466   uri->port = -1;
467 }