Commit | Line | Data |
---|---|---|
de6d9b64 FB |
1 | /* |
2 | * UDP prototype streaming system | |
2d225591 | 3 | * Copyright (c) 2000, 2001, 2002 Fabrice Bellard. |
de6d9b64 | 4 | * |
b78e7197 DB |
5 | * This file is part of FFmpeg. |
6 | * | |
7 | * FFmpeg is free software; you can redistribute it and/or | |
19720f15 FB |
8 | * modify it under the terms of the GNU Lesser General Public |
9 | * License as published by the Free Software Foundation; either | |
b78e7197 | 10 | * version 2.1 of the License, or (at your option) any later version. |
de6d9b64 | 11 | * |
b78e7197 | 12 | * FFmpeg is distributed in the hope that it will be useful, |
de6d9b64 | 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
19720f15 FB |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | * Lesser General Public License for more details. | |
de6d9b64 | 16 | * |
19720f15 | 17 | * You should have received a copy of the GNU Lesser General Public |
b78e7197 | 18 | * License along with FFmpeg; if not, write to the Free Software |
5509bffa | 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
de6d9b64 | 20 | */ |
8be1c656 | 21 | #include "avformat.h" |
de6d9b64 | 22 | #include <unistd.h> |
42572ef5 | 23 | #include "network.h" |
de6d9b64 | 24 | |
9c633e9a AB |
25 | #ifndef IPV6_ADD_MEMBERSHIP |
26 | #define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP | |
27 | #define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP | |
28 | #endif | |
29 | ||
de6d9b64 | 30 | typedef struct { |
2d225591 FB |
31 | int udp_fd; |
32 | int ttl; | |
33 | int is_multicast; | |
34 | int local_port; | |
34ecc397 | 35 | int reuse_socket; |
7a91333f | 36 | #ifndef CONFIG_IPV6 |
2d225591 | 37 | struct sockaddr_in dest_addr; |
7a91333f HZ |
38 | #else |
39 | struct sockaddr_storage dest_addr; | |
7a91333f | 40 | #endif |
0ec12335 | 41 | size_t dest_addr_len; |
de6d9b64 FB |
42 | } UDPContext; |
43 | ||
44 | #define UDP_TX_BUF_SIZE 32768 | |
f18cae4d | 45 | #define UDP_MAX_PKT_SIZE 65536 |
de6d9b64 | 46 | |
9cdcb04f | 47 | static int udp_set_multicast_ttl(int sockfd, int mcastTTL, struct sockaddr *addr) { |
a8bde059 | 48 | #ifdef IP_MULTICAST_TTL |
7a91333f HZ |
49 | if (addr->sa_family == AF_INET) { |
50 | if (setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_TTL, &mcastTTL, sizeof(mcastTTL)) < 0) { | |
51 | perror("setsockopt(IP_MULTICAST_TTL)"); | |
52 | return -1; | |
53 | } | |
54 | } | |
a8bde059 LA |
55 | #endif |
56 | #ifdef CONFIG_IPV6 | |
7a91333f HZ |
57 | if (addr->sa_family == AF_INET6) { |
58 | if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &mcastTTL, sizeof(mcastTTL)) < 0) { | |
59 | perror("setsockopt(IPV6_MULTICAST_HOPS)"); | |
60 | return -1; | |
61 | } | |
62 | } | |
a8bde059 | 63 | #endif |
7a91333f HZ |
64 | return 0; |
65 | } | |
66 | ||
9cdcb04f | 67 | static int udp_join_multicast_group(int sockfd, struct sockaddr *addr) { |
a8bde059 | 68 | #ifdef IP_ADD_MEMBERSHIP |
7a91333f | 69 | if (addr->sa_family == AF_INET) { |
a8bde059 LA |
70 | struct ip_mreq mreq; |
71 | ||
7a91333f HZ |
72 | mreq.imr_multiaddr.s_addr = ((struct sockaddr_in *)addr)->sin_addr.s_addr; |
73 | mreq.imr_interface.s_addr= INADDR_ANY; | |
74 | if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const void *)&mreq, sizeof(mreq)) < 0) { | |
75 | perror("setsockopt(IP_ADD_MEMBERSHIP)"); | |
76 | return -1; | |
77 | } | |
78 | } | |
a8bde059 LA |
79 | #endif |
80 | #ifdef CONFIG_IPV6 | |
7a91333f | 81 | if (addr->sa_family == AF_INET6) { |
a8bde059 LA |
82 | struct ipv6_mreq mreq6; |
83 | ||
7a91333f HZ |
84 | memcpy(&mreq6.ipv6mr_multiaddr, &(((struct sockaddr_in6 *)addr)->sin6_addr), sizeof(struct in6_addr)); |
85 | mreq6.ipv6mr_interface= 0; | |
86 | if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq6, sizeof(mreq6)) < 0) { | |
87 | perror("setsockopt(IPV6_ADD_MEMBERSHIP)"); | |
88 | return -1; | |
89 | } | |
90 | } | |
a8bde059 | 91 | #endif |
7a91333f HZ |
92 | return 0; |
93 | } | |
94 | ||
9cdcb04f | 95 | static int udp_leave_multicast_group(int sockfd, struct sockaddr *addr) { |
a8bde059 | 96 | #ifdef IP_DROP_MEMBERSHIP |
7a91333f | 97 | if (addr->sa_family == AF_INET) { |
a8bde059 LA |
98 | struct ip_mreq mreq; |
99 | ||
7a91333f HZ |
100 | mreq.imr_multiaddr.s_addr = ((struct sockaddr_in *)addr)->sin_addr.s_addr; |
101 | mreq.imr_interface.s_addr= INADDR_ANY; | |
102 | if (setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const void *)&mreq, sizeof(mreq)) < 0) { | |
103 | perror("setsockopt(IP_DROP_MEMBERSHIP)"); | |
104 | return -1; | |
105 | } | |
106 | } | |
a8bde059 LA |
107 | #endif |
108 | #ifdef CONFIG_IPV6 | |
7a91333f | 109 | if (addr->sa_family == AF_INET6) { |
a8bde059 LA |
110 | struct ipv6_mreq mreq6; |
111 | ||
7a91333f HZ |
112 | memcpy(&mreq6.ipv6mr_multiaddr, &(((struct sockaddr_in6 *)addr)->sin6_addr), sizeof(struct in6_addr)); |
113 | mreq6.ipv6mr_interface= 0; | |
114 | if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq6, sizeof(mreq6)) < 0) { | |
115 | perror("setsockopt(IPV6_DROP_MEMBERSHIP)"); | |
116 | return -1; | |
117 | } | |
118 | } | |
a8bde059 | 119 | #endif |
7a91333f HZ |
120 | return 0; |
121 | } | |
122 | ||
a8bde059 | 123 | #ifdef CONFIG_IPV6 |
7b49ce2e | 124 | static struct addrinfo* udp_ipv6_resolve_host(const char *hostname, int port, int type, int family, int flags) { |
7a91333f HZ |
125 | struct addrinfo hints, *res = 0; |
126 | int error; | |
127 | char sport[16]; | |
d607861c | 128 | const char *node = 0, *service = "0"; |
7a91333f HZ |
129 | |
130 | if (port > 0) { | |
2fc8ea24 | 131 | snprintf(sport, sizeof(sport), "%d", port); |
7a91333f HZ |
132 | service = sport; |
133 | } | |
134 | if ((hostname) && (hostname[0] != '\0') && (hostname[0] != '?')) { | |
135 | node = hostname; | |
136 | } | |
7d8576c2 LA |
137 | memset(&hints, 0, sizeof(hints)); |
138 | hints.ai_socktype = type; | |
139 | hints.ai_family = family; | |
140 | hints.ai_flags = flags; | |
141 | if ((error = getaddrinfo(node, service, &hints, &res))) { | |
142 | av_log(NULL, AV_LOG_ERROR, "udp_ipv6_resolve_host: %s\n", gai_strerror(error)); | |
143 | } | |
144 | ||
7a91333f HZ |
145 | return res; |
146 | } | |
147 | ||
d05cb726 | 148 | static int udp_set_url(struct sockaddr_storage *addr, const char *hostname, int port) { |
7a91333f | 149 | struct addrinfo *res0; |
d05cb726 LA |
150 | int addr_len; |
151 | ||
7a91333f | 152 | res0 = udp_ipv6_resolve_host(hostname, port, SOCK_DGRAM, AF_UNSPEC, 0); |
6f3e0b21 | 153 | if (res0 == 0) return AVERROR(EIO); |
d05cb726 LA |
154 | memcpy(addr, res0->ai_addr, res0->ai_addrlen); |
155 | addr_len = res0->ai_addrlen; | |
7a91333f | 156 | freeaddrinfo(res0); |
d05cb726 LA |
157 | |
158 | return addr_len; | |
7a91333f HZ |
159 | } |
160 | ||
aa519c47 LA |
161 | static int udp_socket_create(UDPContext *s, struct sockaddr_storage *addr, int *addr_len) |
162 | { | |
7a91333f | 163 | int udp_fd = -1; |
276358c1 | 164 | struct addrinfo *res0 = NULL, *res = NULL; |
51844e6c | 165 | int family = AF_UNSPEC; |
115329f1 | 166 | |
51844e6c RB |
167 | if (((struct sockaddr *) &s->dest_addr)->sa_family) |
168 | family = ((struct sockaddr *) &s->dest_addr)->sa_family; | |
169 | res0 = udp_ipv6_resolve_host(0, s->local_port, SOCK_DGRAM, family, AI_PASSIVE); | |
7d8576c2 LA |
170 | if (res0 == 0) |
171 | goto fail; | |
172 | for (res = res0; res; res=res->ai_next) { | |
173 | udp_fd = socket(res->ai_family, SOCK_DGRAM, 0); | |
174 | if (udp_fd > 0) break; | |
175 | perror("socket"); | |
176 | } | |
276358c1 HZ |
177 | |
178 | if (udp_fd < 0) | |
7a91333f | 179 | goto fail; |
115329f1 | 180 | |
aa519c47 LA |
181 | memcpy(addr, res->ai_addr, res->ai_addrlen); |
182 | *addr_len = res->ai_addrlen; | |
7a91333f | 183 | |
aa519c47 | 184 | freeaddrinfo(res0); |
115329f1 | 185 | |
7a91333f | 186 | return udp_fd; |
115329f1 | 187 | |
7a91333f HZ |
188 | fail: |
189 | if (udp_fd >= 0) | |
7a91333f | 190 | closesocket(udp_fd); |
88730be6 MR |
191 | if(res0) |
192 | freeaddrinfo(res0); | |
7a91333f HZ |
193 | return -1; |
194 | } | |
195 | ||
aa519c47 LA |
196 | static int udp_port(struct sockaddr_storage *addr, int addr_len) |
197 | { | |
198 | char sbuf[NI_MAXSERV]; | |
199 | char hbuf[NI_MAXHOST]; | |
200 | ||
201 | if (getnameinfo((struct sockaddr *)addr, addr_len, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV) != 0) { | |
202 | perror("getnameinfo"); | |
203 | return -1; | |
204 | } | |
205 | ||
206 | return strtol(sbuf, NULL, 10); | |
207 | } | |
208 | ||
d05cb726 LA |
209 | #else |
210 | ||
211 | static int udp_set_url(struct sockaddr_in *addr, const char *hostname, int port) | |
212 | { | |
213 | /* set the destination address */ | |
214 | if (resolve_host(&addr->sin_addr, hostname) < 0) | |
215 | return AVERROR(EIO); | |
216 | addr->sin_family = AF_INET; | |
217 | addr->sin_port = htons(port); | |
218 | ||
219 | return sizeof(struct sockaddr_in); | |
220 | } | |
221 | ||
aa519c47 LA |
222 | static int udp_socket_create(UDPContext *s, struct sockaddr_in *addr, int *addr_len) |
223 | { | |
224 | int fd; | |
225 | ||
226 | fd = socket(AF_INET, SOCK_DGRAM, 0); | |
227 | if (fd < 0) | |
228 | return -1; | |
229 | ||
230 | addr->sin_family = AF_INET; | |
231 | addr->sin_addr.s_addr = htonl (INADDR_ANY); | |
232 | addr->sin_port = htons(s->local_port); | |
233 | *addr_len = sizeof(struct sockaddr_in); | |
234 | ||
235 | return fd; | |
236 | } | |
237 | ||
238 | static int udp_port(struct sockaddr_in *addr, int len) | |
239 | { | |
240 | return ntohs(addr->sin_port); | |
241 | } | |
0a4f20c6 | 242 | #endif /* CONFIG_IPV6 */ |
7a91333f HZ |
243 | |
244 | ||
2d225591 FB |
245 | /** |
246 | * If no filename is given to av_open_input_file because you want to | |
247 | * get the local port first, then you must call this function to set | |
248 | * the remote server address. | |
249 | * | |
250 | * url syntax: udp://host:port[?option=val...] | |
115329f1 | 251 | * option: 'multicast=1' : enable multicast |
2d225591 FB |
252 | * 'ttl=n' : set the ttl value (for multicast only) |
253 | * 'localport=n' : set the local port | |
9899efb4 | 254 | * 'pkt_size=n' : set max packet size |
34ecc397 | 255 | * 'reuse=1' : enable reusing the socket |
2d225591 FB |
256 | * |
257 | * @param s1 media file context | |
258 | * @param uri of the remote server | |
259 | * @return zero if no error. | |
260 | */ | |
261 | int udp_set_remote_url(URLContext *h, const char *uri) | |
262 | { | |
263 | UDPContext *s = h->priv_data; | |
264 | char hostname[256]; | |
265 | int port; | |
115329f1 | 266 | |
6ba5cbc6 | 267 | url_split(NULL, 0, NULL, 0, hostname, sizeof(hostname), &port, NULL, 0, uri); |
de6d9b64 | 268 | |
2d225591 | 269 | /* set the destination address */ |
d05cb726 LA |
270 | s->dest_addr_len = udp_set_url(&s->dest_addr, hostname, port); |
271 | if (s->dest_addr_len < 0) { | |
6f3e0b21 | 272 | return AVERROR(EIO); |
d05cb726 LA |
273 | } |
274 | ||
2d225591 FB |
275 | return 0; |
276 | } | |
277 | ||
278 | /** | |
279 | * Return the local port used by the UDP connexion | |
280 | * @param s1 media file context | |
281 | * @return the local port number | |
282 | */ | |
283 | int udp_get_local_port(URLContext *h) | |
284 | { | |
285 | UDPContext *s = h->priv_data; | |
286 | return s->local_port; | |
287 | } | |
288 | ||
289 | /** | |
290 | * Return the udp file handle for select() usage to wait for several RTP | |
291 | * streams at the same time. | |
292 | * @param h media file context | |
293 | */ | |
294 | int udp_get_file_handle(URLContext *h) | |
295 | { | |
296 | UDPContext *s = h->priv_data; | |
297 | return s->udp_fd; | |
298 | } | |
299 | ||
300 | /* put it in UDP context */ | |
de6d9b64 FB |
301 | /* return non zero if error */ |
302 | static int udp_open(URLContext *h, const char *uri, int flags) | |
303 | { | |
de6d9b64 | 304 | char hostname[1024]; |
2d225591 FB |
305 | int port, udp_fd = -1, tmp; |
306 | UDPContext *s = NULL; | |
88730be6 | 307 | int is_output; |
2d225591 FB |
308 | const char *p; |
309 | char buf[256]; | |
88730be6 | 310 | #ifndef CONFIG_IPV6 |
d4936869 | 311 | struct sockaddr_in my_addr; |
aa519c47 LA |
312 | #else |
313 | struct sockaddr_storage my_addr; | |
88730be6 | 314 | #endif |
aa519c47 | 315 | int len; |
de6d9b64 FB |
316 | |
317 | h->is_streamed = 1; | |
9899efb4 | 318 | h->max_packet_size = 1472; |
de6d9b64 | 319 | |
2d225591 | 320 | is_output = (flags & URL_WRONLY); |
115329f1 | 321 | |
51844e6c | 322 | s = av_mallocz(sizeof(UDPContext)); |
2d225591 | 323 | if (!s) |
8fa36ae0 | 324 | return AVERROR(ENOMEM); |
2d225591 FB |
325 | |
326 | h->priv_data = s; | |
327 | s->ttl = 16; | |
2d225591 FB |
328 | p = strchr(uri, '?'); |
329 | if (p) { | |
330 | s->is_multicast = find_info_tag(buf, sizeof(buf), "multicast", p); | |
34ecc397 | 331 | s->reuse_socket = find_info_tag(buf, sizeof(buf), "reuse", p); |
2d225591 FB |
332 | if (find_info_tag(buf, sizeof(buf), "ttl", p)) { |
333 | s->ttl = strtol(buf, NULL, 10); | |
334 | } | |
335 | if (find_info_tag(buf, sizeof(buf), "localport", p)) { | |
336 | s->local_port = strtol(buf, NULL, 10); | |
337 | } | |
9899efb4 MK |
338 | if (find_info_tag(buf, sizeof(buf), "pkt_size", p)) { |
339 | h->max_packet_size = strtol(buf, NULL, 10); | |
340 | } | |
de6d9b64 | 341 | } |
2d225591 FB |
342 | |
343 | /* fill the dest addr */ | |
6ba5cbc6 | 344 | url_split(NULL, 0, NULL, 0, hostname, sizeof(hostname), &port, NULL, 0, uri); |
115329f1 | 345 | |
2d225591 FB |
346 | /* XXX: fix url_split */ |
347 | if (hostname[0] == '\0' || hostname[0] == '?') { | |
348 | /* only accepts null hostname if input */ | |
349 | if (s->is_multicast || (flags & URL_WRONLY)) | |
350 | goto fail; | |
351 | } else { | |
352 | udp_set_remote_url(h, uri); | |
353 | } | |
de6d9b64 | 354 | |
1642cb6b RP |
355 | if(!ff_network_init()) |
356 | return AVERROR(EIO); | |
357 | ||
882d00f2 LA |
358 | if (s->is_multicast && !(h->flags & URL_WRONLY)) |
359 | s->local_port = port; | |
aa519c47 | 360 | udp_fd = udp_socket_create(s, &my_addr, &len); |
2d225591 FB |
361 | if (udp_fd < 0) |
362 | goto fail; | |
363 | ||
34ecc397 T |
364 | if (s->reuse_socket) |
365 | if (setsockopt (udp_fd, SOL_SOCKET, SO_REUSEADDR, &(s->reuse_socket), sizeof(s->reuse_socket)) != 0) | |
366 | goto fail; | |
367 | ||
de6d9b64 | 368 | /* the bind is needed to give a port to the socket now */ |
aa519c47 | 369 | if (bind(udp_fd,(struct sockaddr *)&my_addr, len) < 0) |
de6d9b64 FB |
370 | goto fail; |
371 | ||
d4936869 LA |
372 | len = sizeof(my_addr); |
373 | getsockname(udp_fd, (struct sockaddr *)&my_addr, &len); | |
aa519c47 LA |
374 | s->local_port = udp_port(&my_addr, len); |
375 | ||
7a91333f HZ |
376 | if (s->is_multicast) { |
377 | if (h->flags & URL_WRONLY) { | |
a8bde059 | 378 | /* output */ |
9cdcb04f | 379 | if (udp_set_multicast_ttl(udp_fd, s->ttl, (struct sockaddr *)&s->dest_addr) < 0) |
7a91333f HZ |
380 | goto fail; |
381 | } else { | |
a8bde059 | 382 | /* input */ |
9cdcb04f | 383 | if (udp_join_multicast_group(udp_fd, (struct sockaddr *)&s->dest_addr) < 0) |
7a91333f HZ |
384 | goto fail; |
385 | } | |
386 | } | |
de6d9b64 | 387 | |
2d225591 FB |
388 | if (is_output) { |
389 | /* limit the tx buf size to limit latency */ | |
390 | tmp = UDP_TX_BUF_SIZE; | |
391 | if (setsockopt(udp_fd, SOL_SOCKET, SO_SNDBUF, &tmp, sizeof(tmp)) < 0) { | |
392 | perror("setsockopt sndbuf"); | |
393 | goto fail; | |
394 | } | |
f18cae4d RP |
395 | } else { |
396 | /* set udp recv buffer size to the largest possible udp packet size to | |
397 | * avoid losing data on OSes that set this too low by default. */ | |
398 | tmp = UDP_MAX_PKT_SIZE; | |
399 | setsockopt(udp_fd, SOL_SOCKET, SO_RCVBUF, &tmp, sizeof(tmp)); | |
2d225591 FB |
400 | } |
401 | ||
402 | s->udp_fd = udp_fd; | |
de6d9b64 FB |
403 | return 0; |
404 | fail: | |
2d225591 | 405 | if (udp_fd >= 0) |
9ddd71fc | 406 | closesocket(udp_fd); |
2d225591 | 407 | av_free(s); |
6f3e0b21 | 408 | return AVERROR(EIO); |
de6d9b64 FB |
409 | } |
410 | ||
0c1a9eda | 411 | static int udp_read(URLContext *h, uint8_t *buf, int size) |
de6d9b64 FB |
412 | { |
413 | UDPContext *s = h->priv_data; | |
191e8ca7 | 414 | int len; |
2d225591 FB |
415 | |
416 | for(;;) { | |
4c7d5764 | 417 | len = recv(s->udp_fd, buf, size, 0); |
2d225591 | 418 | if (len < 0) { |
8da4034f AB |
419 | if (ff_neterrno() != FF_NETERROR(EAGAIN) && |
420 | ff_neterrno() != FF_NETERROR(EINTR)) | |
6f3e0b21 | 421 | return AVERROR(EIO); |
2d225591 FB |
422 | } else { |
423 | break; | |
424 | } | |
425 | } | |
426 | return len; | |
de6d9b64 FB |
427 | } |
428 | ||
0c1a9eda | 429 | static int udp_write(URLContext *h, uint8_t *buf, int size) |
de6d9b64 FB |
430 | { |
431 | UDPContext *s = h->priv_data; | |
2d225591 FB |
432 | int ret; |
433 | ||
434 | for(;;) { | |
115329f1 | 435 | ret = sendto (s->udp_fd, buf, size, 0, |
2d225591 | 436 | (struct sockaddr *) &s->dest_addr, |
7a91333f | 437 | s->dest_addr_len); |
2d225591 | 438 | if (ret < 0) { |
8da4034f AB |
439 | if (ff_neterrno() != FF_NETERROR(EINTR) && |
440 | ff_neterrno() != FF_NETERROR(EAGAIN)) | |
6f3e0b21 | 441 | return AVERROR(EIO); |
2d225591 FB |
442 | } else { |
443 | break; | |
444 | } | |
de6d9b64 | 445 | } |
2d225591 FB |
446 | return size; |
447 | } | |
448 | ||
449 | static int udp_close(URLContext *h) | |
450 | { | |
451 | UDPContext *s = h->priv_data; | |
452 | ||
7a91333f | 453 | if (s->is_multicast && !(h->flags & URL_WRONLY)) |
9cdcb04f | 454 | udp_leave_multicast_group(s->udp_fd, (struct sockaddr *)&s->dest_addr); |
9ddd71fc | 455 | closesocket(s->udp_fd); |
1642cb6b | 456 | ff_network_close(); |
2d225591 FB |
457 | av_free(s); |
458 | return 0; | |
de6d9b64 FB |
459 | } |
460 | ||
461 | URLProtocol udp_protocol = { | |
462 | "udp", | |
463 | udp_open, | |
2d225591 | 464 | udp_read, |
de6d9b64 FB |
465 | udp_write, |
466 | NULL, /* seek */ | |
467 | udp_close, | |
468 | }; |