blob: 9a80e9b015d0da06a995ec36332258c0416f84f5
1 | /* |
2 | * SCTP protocol |
3 | * Copyright (c) 2012 Luca Barbato |
4 | * |
5 | * This file is part of FFmpeg. |
6 | * |
7 | * FFmpeg is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU Lesser General Public |
9 | * License as published by the Free Software Foundation; either |
10 | * version 2.1 of the License, or (at your option) any later version. |
11 | * |
12 | * FFmpeg is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | * Lesser General Public License for more details. |
16 | * |
17 | * You should have received a copy of the GNU Lesser General Public |
18 | * License along with FFmpeg; if not, write to the Free Software |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
20 | */ |
21 | |
22 | /** |
23 | * @file |
24 | * |
25 | * sctp url_protocol |
26 | * |
27 | * url syntax: sctp://host:port[?option=val...] |
28 | * option: 'listen' : listen for an incoming connection |
29 | * 'max_streams=n' : set the maximum number of streams |
30 | * 'reuse=1' : enable reusing the socket [TBD] |
31 | * |
32 | * by setting the maximum number of streams the protocol will use the |
33 | * first two bytes of the incoming/outgoing buffer to store the |
34 | * stream number of the packet being read/written. |
35 | * @see sctp_read |
36 | * @see sctp_write |
37 | */ |
38 | |
39 | |
40 | #include <netinet/in.h> |
41 | #include <netinet/sctp.h> |
42 | |
43 | #include "config.h" |
44 | |
45 | #if HAVE_POLL_H |
46 | #include <poll.h> |
47 | #endif |
48 | |
49 | #include "libavutil/intreadwrite.h" |
50 | #include "libavutil/parseutils.h" |
51 | #include "libavutil/opt.h" |
52 | #include "avformat.h" |
53 | #include "internal.h" |
54 | #include "network.h" |
55 | #include "os_support.h" |
56 | #include "url.h" |
57 | |
58 | /* |
59 | * The sctp_recvmsg and sctp_sendmsg functions are part of the user |
60 | * library that offers support for the SCTP kernel Implementation. |
61 | * To avoid build-time clashes the functions sport an ff_-prefix here. |
62 | * The main purpose of this code is to provide the SCTP Socket API |
63 | * mappings for user applications to interface with SCTP in the kernel. |
64 | * |
65 | * This implementation is based on the Socket API Extensions for SCTP |
66 | * defined in <draft-ietf-tsvwg-sctpsocket-10.txt> |
67 | * |
68 | * Copyright (c) 2003 International Business Machines, Corp. |
69 | * |
70 | * Written or modified by: |
71 | * Ryan Layer <rmlayer@us.ibm.com> |
72 | */ |
73 | |
74 | static int ff_sctp_recvmsg(int s, void *msg, size_t len, struct sockaddr *from, |
75 | socklen_t *fromlen, struct sctp_sndrcvinfo *sinfo, |
76 | int *msg_flags) |
77 | { |
78 | int recvb; |
79 | struct iovec iov; |
80 | char incmsg[CMSG_SPACE(sizeof(struct sctp_sndrcvinfo))]; |
81 | struct msghdr inmsg = { 0 }; |
82 | struct cmsghdr *cmsg = NULL; |
83 | |
84 | iov.iov_base = msg; |
85 | iov.iov_len = len; |
86 | |
87 | inmsg.msg_name = from; |
88 | inmsg.msg_namelen = fromlen ? *fromlen : 0; |
89 | inmsg.msg_iov = &iov; |
90 | inmsg.msg_iovlen = 1; |
91 | inmsg.msg_control = incmsg; |
92 | inmsg.msg_controllen = sizeof(incmsg); |
93 | |
94 | if ((recvb = recvmsg(s, &inmsg, msg_flags ? *msg_flags : 0)) < 0) |
95 | return recvb; |
96 | |
97 | if (fromlen) |
98 | *fromlen = inmsg.msg_namelen; |
99 | if (msg_flags) |
100 | *msg_flags = inmsg.msg_flags; |
101 | |
102 | for (cmsg = CMSG_FIRSTHDR(&inmsg); cmsg; |
103 | cmsg = CMSG_NXTHDR(&inmsg, cmsg)) { |
104 | if ((IPPROTO_SCTP == cmsg->cmsg_level) && |
105 | (SCTP_SNDRCV == cmsg->cmsg_type)) |
106 | break; |
107 | } |
108 | |
109 | /* Copy sinfo. */ |
110 | if (cmsg) |
111 | memcpy(sinfo, CMSG_DATA(cmsg), sizeof(struct sctp_sndrcvinfo)); |
112 | |
113 | return recvb; |
114 | } |
115 | |
116 | static int ff_sctp_send(int s, const void *msg, size_t len, |
117 | const struct sctp_sndrcvinfo *sinfo, int flags) |
118 | { |
119 | struct msghdr outmsg = { 0 }; |
120 | struct iovec iov; |
121 | |
122 | outmsg.msg_name = NULL; |
123 | outmsg.msg_namelen = 0; |
124 | outmsg.msg_iov = &iov; |
125 | iov.iov_base = (void*)msg; |
126 | iov.iov_len = len; |
127 | outmsg.msg_iovlen = 1; |
128 | outmsg.msg_controllen = 0; |
129 | |
130 | if (sinfo) { |
131 | char outcmsg[CMSG_SPACE(sizeof(struct sctp_sndrcvinfo))]; |
132 | struct cmsghdr *cmsg; |
133 | |
134 | outmsg.msg_control = outcmsg; |
135 | outmsg.msg_controllen = sizeof(outcmsg); |
136 | outmsg.msg_flags = 0; |
137 | |
138 | cmsg = CMSG_FIRSTHDR(&outmsg); |
139 | cmsg->cmsg_level = IPPROTO_SCTP; |
140 | cmsg->cmsg_type = SCTP_SNDRCV; |
141 | cmsg->cmsg_len = CMSG_LEN(sizeof(struct sctp_sndrcvinfo)); |
142 | |
143 | outmsg.msg_controllen = cmsg->cmsg_len; |
144 | memcpy(CMSG_DATA(cmsg), sinfo, sizeof(struct sctp_sndrcvinfo)); |
145 | } |
146 | |
147 | return sendmsg(s, &outmsg, flags | MSG_NOSIGNAL); |
148 | } |
149 | |
150 | typedef struct SCTPContext { |
151 | const AVClass *class; |
152 | int fd; |
153 | int listen; |
154 | int timeout; |
155 | int listen_timeout; |
156 | int max_streams; |
157 | struct sockaddr_storage dest_addr; |
158 | } SCTPContext; |
159 | |
160 | #define OFFSET(x) offsetof(SCTPContext, x) |
161 | #define D AV_OPT_FLAG_DECODING_PARAM |
162 | #define E AV_OPT_FLAG_ENCODING_PARAM |
163 | static const AVOption options[] = { |
164 | { "listen", "Listen for incoming connections", OFFSET(listen), AV_OPT_TYPE_BOOL,{ .i64 = 0 }, 0, 1, .flags = D|E }, |
165 | { "timeout", "Connection timeout (in milliseconds)", OFFSET(timeout), AV_OPT_TYPE_INT, { .i64 = 10000 }, INT_MIN, INT_MAX, .flags = D|E }, |
166 | { "listen_timeout", "Bind timeout (in milliseconds)", OFFSET(listen_timeout), AV_OPT_TYPE_INT, { .i64 = -1 }, INT_MIN, INT_MAX, .flags = D|E }, |
167 | { "max_streams", "Max stream to allocate", OFFSET(max_streams), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT16_MAX, .flags = D|E }, |
168 | { NULL } |
169 | }; |
170 | |
171 | static const AVClass sctp_class = { |
172 | .class_name = "sctp", |
173 | .item_name = av_default_item_name, |
174 | .option = options, |
175 | .version = LIBAVUTIL_VERSION_INT, |
176 | }; |
177 | |
178 | static int sctp_open(URLContext *h, const char *uri, int flags) |
179 | { |
180 | struct addrinfo *ai, *cur_ai; |
181 | struct addrinfo hints = { 0 }; |
182 | struct sctp_event_subscribe event = { 0 }; |
183 | struct sctp_initmsg initparams = { 0 }; |
184 | int port; |
185 | int fd = -1; |
186 | SCTPContext *s = h->priv_data; |
187 | const char *p; |
188 | char buf[256]; |
189 | int ret; |
190 | char hostname[1024], proto[1024], path[1024]; |
191 | char portstr[10]; |
192 | |
193 | av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), |
194 | &port, path, sizeof(path), uri); |
195 | if (strcmp(proto, "sctp")) |
196 | return AVERROR(EINVAL); |
197 | if (port <= 0 || port >= 65536) { |
198 | av_log(s, AV_LOG_ERROR, "Port missing in uri\n"); |
199 | return AVERROR(EINVAL); |
200 | } |
201 | |
202 | p = strchr(uri, '?'); |
203 | if (p) { |
204 | if (av_find_info_tag(buf, sizeof(buf), "listen", p)) |
205 | s->listen = 1; |
206 | if (av_find_info_tag(buf, sizeof(buf), "max_streams", p)) |
207 | s->max_streams = strtol(buf, NULL, 10); |
208 | } |
209 | |
210 | hints.ai_family = AF_UNSPEC; |
211 | hints.ai_socktype = SOCK_STREAM; |
212 | snprintf(portstr, sizeof(portstr), "%d", port); |
213 | ret = getaddrinfo(hostname, portstr, &hints, &ai); |
214 | if (ret) { |
215 | av_log(h, AV_LOG_ERROR, "Failed to resolve hostname %s: %s\n", |
216 | hostname, gai_strerror(ret)); |
217 | return AVERROR(EIO); |
218 | } |
219 | |
220 | cur_ai = ai; |
221 | |
222 | restart: |
223 | fd = ff_socket(cur_ai->ai_family, SOCK_STREAM, IPPROTO_SCTP); |
224 | if (fd < 0) { |
225 | ret = ff_neterrno(); |
226 | goto fail; |
227 | } |
228 | |
229 | if (s->listen) { |
230 | if ((fd = ff_listen_bind(fd, cur_ai->ai_addr, cur_ai->ai_addrlen, |
231 | s->listen_timeout, h)) < 0) { |
232 | ret = fd; |
233 | goto fail1; |
234 | } |
235 | } else { |
236 | if ((ret = ff_listen_connect(fd, cur_ai->ai_addr, cur_ai->ai_addrlen, |
237 | s->timeout, h, !!cur_ai->ai_next)) < 0) { |
238 | |
239 | if (ret == AVERROR_EXIT) |
240 | goto fail1; |
241 | else |
242 | goto fail; |
243 | } |
244 | } |
245 | |
246 | event.sctp_data_io_event = 1; |
247 | /* TODO: Subscribe to more event types and handle them */ |
248 | |
249 | if (setsockopt(fd, IPPROTO_SCTP, SCTP_EVENTS, &event, |
250 | sizeof(event)) != 0) { |
251 | av_log(h, AV_LOG_ERROR, |
252 | "SCTP ERROR: Unable to subscribe to events\n"); |
253 | goto fail1; |
254 | } |
255 | |
256 | if (s->max_streams) { |
257 | initparams.sinit_max_instreams = s->max_streams; |
258 | initparams.sinit_num_ostreams = s->max_streams; |
259 | if (setsockopt(fd, IPPROTO_SCTP, SCTP_INITMSG, &initparams, |
260 | sizeof(initparams)) < 0) { |
261 | av_log(h, AV_LOG_ERROR, |
262 | "SCTP ERROR: Unable to initialize socket max streams %d\n", |
263 | s->max_streams); |
264 | ret = ff_neterrno(); |
265 | goto fail1; |
266 | } |
267 | } |
268 | |
269 | h->priv_data = s; |
270 | h->is_streamed = 1; |
271 | s->fd = fd; |
272 | freeaddrinfo(ai); |
273 | return 0; |
274 | |
275 | fail: |
276 | if (cur_ai->ai_next) { |
277 | /* Retry with the next sockaddr */ |
278 | cur_ai = cur_ai->ai_next; |
279 | if (fd >= 0) |
280 | closesocket(fd); |
281 | ret = 0; |
282 | goto restart; |
283 | } |
284 | fail1: |
285 | ret = AVERROR(EIO); |
286 | freeaddrinfo(ai); |
287 | return ret; |
288 | } |
289 | |
290 | static int sctp_wait_fd(int fd, int write) |
291 | { |
292 | int ev = write ? POLLOUT : POLLIN; |
293 | struct pollfd p = { .fd = fd, .events = ev, .revents = 0 }; |
294 | int ret; |
295 | |
296 | ret = poll(&p, 1, 100); |
297 | return ret < 0 ? ff_neterrno() : p.revents & ev ? 0 : AVERROR(EAGAIN); |
298 | } |
299 | |
300 | static int sctp_read(URLContext *h, uint8_t *buf, int size) |
301 | { |
302 | SCTPContext *s = h->priv_data; |
303 | int ret; |
304 | |
305 | if (!(h->flags & AVIO_FLAG_NONBLOCK)) { |
306 | ret = sctp_wait_fd(s->fd, 0); |
307 | if (ret < 0) |
308 | return ret; |
309 | } |
310 | |
311 | if (s->max_streams) { |
312 | /*StreamId is introduced as a 2byte code into the stream*/ |
313 | struct sctp_sndrcvinfo info = { 0 }; |
314 | ret = ff_sctp_recvmsg(s->fd, buf + 2, size - 2, NULL, 0, &info, 0); |
315 | AV_WB16(buf, info.sinfo_stream); |
316 | ret = ret < 0 ? ret : ret + 2; |
317 | } else |
318 | ret = recv(s->fd, buf, size, 0); |
319 | |
320 | return ret < 0 ? ff_neterrno() : ret; |
321 | } |
322 | |
323 | static int sctp_write(URLContext *h, const uint8_t *buf, int size) |
324 | { |
325 | SCTPContext *s = h->priv_data; |
326 | int ret; |
327 | |
328 | if (!(h->flags & AVIO_FLAG_NONBLOCK)) { |
329 | ret = sctp_wait_fd(s->fd, 1); |
330 | if (ret < 0) |
331 | return ret; |
332 | } |
333 | |
334 | if (s->max_streams) { |
335 | /*StreamId is introduced as a 2byte code into the stream*/ |
336 | struct sctp_sndrcvinfo info = { 0 }; |
337 | info.sinfo_stream = AV_RB16(buf); |
338 | if (info.sinfo_stream > s->max_streams) { |
339 | av_log(h, AV_LOG_ERROR, "bad input data\n"); |
340 | return AVERROR_BUG; |
341 | } |
342 | ret = ff_sctp_send(s->fd, buf + 2, size - 2, &info, MSG_EOR); |
343 | } else |
344 | ret = send(s->fd, buf, size, MSG_NOSIGNAL); |
345 | |
346 | return ret < 0 ? ff_neterrno() : ret; |
347 | } |
348 | |
349 | static int sctp_close(URLContext *h) |
350 | { |
351 | SCTPContext *s = h->priv_data; |
352 | closesocket(s->fd); |
353 | return 0; |
354 | } |
355 | |
356 | static int sctp_get_file_handle(URLContext *h) |
357 | { |
358 | SCTPContext *s = h->priv_data; |
359 | return s->fd; |
360 | } |
361 | |
362 | const URLProtocol ff_sctp_protocol = { |
363 | .name = "sctp", |
364 | .url_open = sctp_open, |
365 | .url_read = sctp_read, |
366 | .url_write = sctp_write, |
367 | .url_close = sctp_close, |
368 | .url_get_file_handle = sctp_get_file_handle, |
369 | .priv_data_size = sizeof(SCTPContext), |
370 | .flags = URL_PROTOCOL_FLAG_NETWORK, |
371 | .priv_data_class = &sctp_class, |
372 | }; |
373 |