1 /*****************************************************************************
3 *****************************************************************************
4 * Copyright © 2004-2016 Rémi Denis-Courmont
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; either version 2.1 of the License, or
10 * (at your option) any later version.
12 * This program 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
15 * GNU Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
20 *****************************************************************************/
25 * Transport-layer stream abstraction
27 * This file implements the transport-layer stream (vlc_tls) abstraction.
43 #ifdef HAVE_NETINET_TCP_H
44 # include <netinet/tcp.h>
47 # define SOL_TCP IPPROTO_TCP
50 #include <vlc_common.h>
52 #include <vlc_interrupt.h>
54 ssize_t
vlc_tls_Read(vlc_tls_t
*session
, void *buf
, size_t len
, bool waitall
)
59 ufd
.fd
= vlc_tls_GetFD(session
);
64 for (size_t rcvd
= 0;;)
72 ssize_t val
= session
->readv(session
, &iov
, 1);
77 iov
.iov_base
= (char *)iov
.iov_base
+ val
;
81 if (iov
.iov_len
== 0 || val
== 0)
87 if (errno
!= EINTR
&& errno
!= EAGAIN
)
88 return rcvd
? (ssize_t
)rcvd
: -1;
91 vlc_poll_i11e(&ufd
, 1, -1);
95 ssize_t
vlc_tls_Write(vlc_tls_t
*session
, const void *buf
, size_t len
)
100 ufd
.fd
= vlc_tls_GetFD(session
);
101 ufd
.events
= POLLOUT
;
102 iov
.iov_base
= (void *)buf
;
105 for (size_t sent
= 0;;)
113 ssize_t val
= session
->writev(session
, &iov
, 1);
116 iov
.iov_base
= ((char *)iov
.iov_base
) + val
;
120 if (iov
.iov_len
== 0 || val
== 0)
126 if (errno
!= EINTR
&& errno
!= EAGAIN
)
127 return sent
? (ssize_t
)sent
: -1;
130 vlc_poll_i11e(&ufd
, 1, -1);
134 char *vlc_tls_GetLine(vlc_tls_t
*session
)
137 size_t linelen
= 0, linesize
= 0;
141 if (linelen
== linesize
)
145 char *newline
= realloc(line
, linesize
);
146 if (unlikely(newline
== NULL
))
151 if (vlc_tls_Read(session
, line
+ linelen
, 1, false) <= 0)
154 while (line
[linelen
++] != '\n');
156 if (linelen
>= 2 && line
[linelen
- 2] == '\r')
157 line
[linelen
- 2] = '\0';
165 typedef struct vlc_tls_socket
170 struct sockaddr peer
[];
173 static int vlc_tls_SocketGetFD(vlc_tls_t
*tls
)
175 vlc_tls_socket_t
*sock
= (struct vlc_tls_socket
*)tls
;
180 static ssize_t
vlc_tls_SocketRead(vlc_tls_t
*tls
, struct iovec
*iov
,
189 return recvmsg(vlc_tls_SocketGetFD(tls
), &msg
, 0);
192 static ssize_t
vlc_tls_SocketWrite(vlc_tls_t
*tls
, const struct iovec
*iov
,
195 const struct msghdr msg
=
197 .msg_iov
= (struct iovec
*)iov
,
201 return sendmsg(vlc_tls_SocketGetFD(tls
), &msg
, MSG_NOSIGNAL
);
204 static int vlc_tls_SocketShutdown(vlc_tls_t
*tls
, bool duplex
)
206 return shutdown(vlc_tls_SocketGetFD(tls
), duplex
? SHUT_RDWR
: SHUT_WR
);
209 static void vlc_tls_SocketClose(vlc_tls_t
*tls
)
211 net_Close(vlc_tls_SocketGetFD(tls
));
215 static vlc_tls_t
*vlc_tls_SocketAlloc(int fd
,
216 const struct sockaddr
*restrict peer
,
219 vlc_tls_socket_t
*sock
= malloc(sizeof (*sock
) + peerlen
);
220 if (unlikely(sock
== NULL
))
223 vlc_tls_t
*tls
= &sock
->tls
;
225 tls
->get_fd
= vlc_tls_SocketGetFD
;
226 tls
->readv
= vlc_tls_SocketRead
;
227 tls
->writev
= vlc_tls_SocketWrite
;
228 tls
->shutdown
= vlc_tls_SocketShutdown
;
229 tls
->close
= vlc_tls_SocketClose
;
233 sock
->peerlen
= peerlen
;
235 memcpy(sock
->peer
, peer
, peerlen
);
239 vlc_tls_t
*vlc_tls_SocketOpen(int fd
)
241 return vlc_tls_SocketAlloc(fd
, NULL
, 0);
244 int vlc_tls_SocketPair(int family
, int protocol
, vlc_tls_t
*pair
[2])
248 if (vlc_socketpair(family
, SOCK_STREAM
, protocol
, fds
, true))
251 for (size_t i
= 0; i
< 2; i
++)
253 setsockopt(fds
[i
], SOL_SOCKET
, SO_REUSEADDR
,
254 &(int){ 1 }, sizeof (int));
256 pair
[i
] = vlc_tls_SocketAlloc(fds
[i
], NULL
, 0);
257 if (unlikely(pair
[i
] == NULL
))
261 vlc_tls_SessionDelete(pair
[0]);
271 * Allocates an unconnected transport layer socket.
273 static vlc_tls_t
*vlc_tls_SocketAddrInfo(const struct addrinfo
*restrict info
)
275 int fd
= vlc_socket(info
->ai_family
, info
->ai_socktype
, info
->ai_protocol
,
276 true /* nonblocking */);
280 setsockopt(fd
, SOL_SOCKET
, SO_REUSEADDR
, &(int){ 1 }, sizeof (int));
282 if (info
->ai_socktype
== SOCK_STREAM
&& info
->ai_protocol
== IPPROTO_TCP
)
283 setsockopt(fd
, SOL_TCP
, TCP_NODELAY
, &(int){ 1 }, sizeof (int));
285 vlc_tls_t
*sk
= vlc_tls_SocketAlloc(fd
, info
->ai_addr
, info
->ai_addrlen
);
286 if (unlikely(sk
== NULL
))
292 * Waits for pending transport layer socket connection.
294 static int vlc_tls_WaitConnect(vlc_tls_t
*tls
)
296 const int fd
= vlc_tls_GetFD(tls
);
300 ufd
.events
= POLLOUT
;
310 while (vlc_poll_i11e(&ufd
, 1, -1) <= 0);
313 socklen_t len
= sizeof (val
);
315 if (getsockopt(fd
, SOL_SOCKET
, SO_ERROR
, &val
, &len
))
327 * Connects a transport layer socket.
329 static ssize_t
vlc_tls_Connect(vlc_tls_t
*tls
)
331 const vlc_tls_socket_t
*sock
= (vlc_tls_socket_t
*)tls
;
333 if (connect(sock
->fd
, sock
->peer
, sock
->peerlen
) == 0)
336 if (errno
!= EINPROGRESS
)
339 if (WSAGetLastError() != WSAEWOULDBLOCK
)
342 return vlc_tls_WaitConnect(tls
);
345 /* Callback for combined connection establishment and initial send */
346 static ssize_t
vlc_tls_ConnectWrite(vlc_tls_t
*tls
,
347 const struct iovec
*iov
,unsigned count
)
350 vlc_tls_socket_t
*sock
= (vlc_tls_socket_t
*)tls
;
351 const struct msghdr msg
=
353 .msg_name
= sock
->peer
,
354 .msg_namelen
= sock
->peerlen
,
355 .msg_iov
= (struct iovec
*)iov
,
360 /* Next time, write directly. Do not retry to connect. */
361 tls
->writev
= vlc_tls_SocketWrite
;
363 ret
= sendmsg(vlc_tls_SocketGetFD(tls
), &msg
, MSG_NOSIGNAL
|MSG_FASTOPEN
);
365 { /* Fast open in progress */
369 if (errno
== EINPROGRESS
)
371 if (vlc_tls_WaitConnect(tls
))
375 if (errno
!= EOPNOTSUPP
)
377 /* Fast open not supported or disabled... fallback to normal mode */
379 tls
->writev
= vlc_tls_SocketWrite
;
382 if (vlc_tls_Connect(tls
))
385 return vlc_tls_SocketWrite(tls
, iov
, count
);
388 vlc_tls_t
*vlc_tls_SocketOpenAddrInfo(const struct addrinfo
*restrict info
,
391 vlc_tls_t
*sock
= vlc_tls_SocketAddrInfo(info
);
396 { /* The socket is not connected yet.
397 * The connection will be triggered on the first send. */
398 sock
->writev
= vlc_tls_ConnectWrite
;
402 if (vlc_tls_Connect(sock
))
404 vlc_tls_SessionDelete(sock
);
411 vlc_tls_t
*vlc_tls_SocketOpenTCP(vlc_object_t
*obj
, const char *name
,
414 struct addrinfo hints
=
416 .ai_socktype
= SOCK_STREAM
,
417 .ai_protocol
= IPPROTO_TCP
,
420 assert(name
!= NULL
);
421 msg_Dbg(obj
, "resolving %s ...", name
);
423 int val
= vlc_getaddrinfo_i11e(name
, port
, &hints
, &res
);
425 { /* TODO: C locale for gai_strerror() */
426 msg_Err(obj
, "cannot resolve %s port %u: %s", name
, port
,
431 msg_Dbg(obj
, "connecting to %s port %u ...", name
, port
);
433 /* TODO: implement RFC8305 */
434 for (const struct addrinfo
*p
= res
; p
!= NULL
; p
= p
->ai_next
)
436 vlc_tls_t
*tls
= vlc_tls_SocketOpenAddrInfo(p
, false);
439 msg_Err(obj
, "connection error: %s", vlc_strerror_c(errno
));