/* * Copyright (c) 2012 Tim Ruehsen * Copyright (c) 2015-2021 Free Software Foundation, Inc. * * This file is part of libwget. * * Libwget is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Libwget is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with libwget. If not, see . * * * network routines * * Changelog * 25.04.2012 Tim Ruehsen created * 16.11.2012 new functions tcp_set_family() and tcp_set_preferred_family() * * RFC 7413: TCP Fast Open */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_NETINET_TCP_H # include #endif #if ((defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__) # include #else # include #endif #if defined __APPLE__ && defined __MACH__ && defined CONNECT_DATA_IDEMPOTENT && defined CONNECT_RESUME_ON_READ_WRITE # define TCP_FASTOPEN_OSX #elif defined TCP_FASTOPEN_CONNECT // since Linux 4.11 # define TCP_FASTOPEN_LINUX_411 #elif defined TCP_FASTOPEN && defined MSG_FASTOPEN # define TCP_FASTOPEN_LINUX #endif #include #include "private.h" #include "net.h" /** * \file * \brief Functions to work with TCP sockets and SSL/TLS * \defgroup libwget-net TCP sockets * * @{ * * TCP sockets and DNS cache management functions. * * The following features are supported: * * - TCP Fast Open ([RFC 7413](https://tools.ietf.org/html/rfc7413)) * - SSL/TLS * * Most functions here take a `wget_tcp` structure as argument. * * The `wget_tcp` structure represents a TCP connection. You create it with wget_tcp_init() * and destroy it with wget_tcp_deinit(). You can connect to a remote host with wget_tcp_connect(), * or listen for incoming connections (and accept them) with wget_tcp_listen() and wget_tcp_accept(). * You end a connection with wget_tcp_close(). * * There are several knobs you can use to customize the behavior of most functions here. * The list that follows describes the most important parameters, although you can look at the getter and setter * functions here to see them all (`wget_tcp_get_xxx`, `wget_tcp_set_xxx`). * * - Timeout: maximum time to wait for an operation to complete. For example, for wget_tcp_read(), it sets the maximum time * to wait until some data is available to read. Most functions here can be non-blocking (with timeout = 0) returning immediately * or they can block indefinitely until something happens (with timeout = -1). For any value greater than zero, * the timeout is taken as milliseconds. * - Family and preferred family: these are used to determine which address family should be used when resolving a host name or * IP address. You probably use `AF_INET` or `AF_INET6` most of the time. The first one forces the library to use that family, * failing if it cannot find any IP address with it. The second one is just a hint, about which family you would prefer; it will try * to get an address of that family if possible, and will get another one if not. * - SSL/TLS: do you want to use TLS? * * When you create a new `wget_tcp` with wget_tcp_init(), it is initialized with the following parameters: * * - Timeout: -1 * - Connection timeout (max. time to wait for a connection to be accepted by the remote host): -1 * - DNS timeout (max. time to wait for a DNS query to return): -1 * - Family: `AF_UNSPEC` (basically means "I don't care, pick the first one available"). */ static struct wget_tcp_st global_tcp = { .sockfd = -1, .dns_timeout = -1, .connect_timeout = -1, .timeout = -1, .family = AF_UNSPEC, #if defined TCP_FASTOPEN_OSX .tcp_fastopen = 1, #elif defined TCP_FASTOPEN_LINUX_411 .tcp_fastopen = 1, #elif defined TCP_FASTOPEN_LINUX .tcp_fastopen = 1, .first_send = 1, #endif }; /* for Windows compatibility */ #include "sockets.h" /** * \return 0 for success, else failure * * Initialize the resources needed for network operations. */ int wget_net_init(void) { int rc = gl_sockets_startup(SOCKETS_2_2); return rc ? -1 : 0; } /** * \return 0 for success, else failure * * Free the resources allocated by wget_net_init(). */ int wget_net_deinit(void) { int rc = gl_sockets_cleanup(); return rc ? -1 : 0; } static int WGET_GCC_CONST value_to_family(int value) { switch (value) { case WGET_NET_FAMILY_IPV4: return AF_INET; case WGET_NET_FAMILY_IPV6: return AF_INET6; default: return AF_UNSPEC; } } static int WGET_GCC_CONST family_to_value(int family) { switch (family) { case AF_INET: return WGET_NET_FAMILY_IPV4; case AF_INET6: return WGET_NET_FAMILY_IPV6; default: return WGET_NET_FAMILY_ANY; } } /** * \param[in] tcp A `wget_tcp` structure representing a TCP connection, returned by wget_tcp_init(). * \param[in] protocol The protocol, either WGET_PROTOCOL_HTTP_2_0 or WGET_PROTOCOL_HTTP_1_1. * * Set the protocol for the connection provided, or globally. * * If \p tcp is NULL, theprotocol will be set globally (for all connections). Otherwise, * only for the provided connection (\p tcp). */ void wget_tcp_set_dns(wget_tcp *tcp, wget_dns *dns) { (tcp ? tcp : &global_tcp)->dns = dns; } /** * \param[in] tcp A `wget_tcp` structure representing a TCP connection, returned by wget_tcp_init(). Might be NULL. * \param[in] tcp_fastopen 1 or 0, whether to enable or disable TCP Fast Open. * * Enable or disable TCP Fast Open ([RFC 7413](https://tools.ietf.org/html/rfc7413)), if available. * * This function is a no-op on systems where TCP Fast Open is not supported. * * If \p tcp is NULL, TCP Fast Open is enabled or disabled globally. */ void wget_tcp_set_tcp_fastopen(wget_tcp *tcp, bool tcp_fastopen) { #if defined TCP_FASTOPEN_OSX || defined TCP_FASTOPEN_LINUX || defined TCP_FASTOPEN_LINUX_411 (tcp ? tcp : &global_tcp)->tcp_fastopen = tcp_fastopen; #else (void) tcp; (void) tcp_fastopen; #endif } /** * \param[in] tcp A `wget_tcp` structure representing a TCP connection, returned by wget_tcp_init(). Might be NULL. * \return 1 if TCP Fast Open is enabled, 0 otherwise. * * Tells whether TCP Fast Open is enabled or not. * * You can enable and disable it with wget_tcp_set_tcp_fastopen(). */ bool wget_tcp_get_tcp_fastopen(wget_tcp *tcp) { return (tcp ? tcp : &global_tcp)->tcp_fastopen; } /** * \param[in] tcp A `wget_tcp` structure representing a TCP connection, returned by wget_tcp_init(). Might be NULL. * \param[in] false_start 1 or 0, whether to enable or disable TLS False Start. * * Enable or disable TLS False Start ([RFC 7918](https://tools.ietf.org/html/rfc7413)). * * If \p tcp is NULL, TLS False Start is enabled or disabled globally. */ void wget_tcp_set_tls_false_start(wget_tcp *tcp, bool false_start) { (tcp ? tcp : &global_tcp)->tls_false_start = false_start; } /** * \param[in] tcp A `wget_tcp` structure representing a TCP connection, returned by wget_tcp_init(). Might be NULL. * \return 1 if TLS False Start is enabled, 0 otherwise. * * Tells whether TLS False Start is enabled or not. * * You can enable and disable it with wget_tcp_set_tls_false_start(). */ bool wget_tcp_get_tls_false_start(wget_tcp *tcp) { return (tcp ? tcp : &global_tcp)->tls_false_start; } /** * \param[in] tcp A `wget_tcp` structure representing a TCP connection, returned by wget_tcp_init(). * \param[in] protocol The protocol, either WGET_PROTOCOL_HTTP_2_0 or WGET_PROTOCOL_HTTP_1_1. * * Set the protocol for the connection provided, or globally. * * If \p tcp is NULL, theprotocol will be set globally (for all connections). Otherwise, * only for the provided connection (\p tcp). */ void wget_tcp_set_protocol(wget_tcp *tcp, int protocol) { (tcp ? tcp : &global_tcp)->protocol = protocol; } /** * \param[in] tcp A `wget_tcp` structure representing a TCP connection, returned by wget_tcp_init(). * \return The protocol with this connection, currently WGET_PROTOCOL_HTTP_2_0 or WGET_PROTOCOL_HTTP_1_1. * * Get protocol used with the provided connection, or globally (if \p tcp is NULL). */ int wget_tcp_get_protocol(wget_tcp *tcp) { return (tcp ? tcp : &global_tcp)->protocol; } /** * \param[in] tcp A `wget_tcp` structure representing a TCP connection, returned by wget_tcp_init(). Might be NULL. * \param[in] family One of the socket families defined in ``, such as `AF_INET` or `AF_INET6`. * * Tells the preferred address family that should be used when establishing a TCP connection. * * wget_tcp_resolve() will favor that and pick an address of that family if possible. * * If \p tcp is NULL, the preferred address family will be set globally. */ void wget_tcp_set_preferred_family(wget_tcp *tcp, int family) { (tcp ? tcp : &global_tcp)->preferred_family = value_to_family(family); } /** * \param[in] tcp A `wget_tcp` structure representing a TCP connection, returned by wget_tcp_init(). Might be NULL. * \return One of the socket families defined in ``, such as `AF_INET` or `AF_INET6`. * * Get the preferred address family that was previously set with wget_tcp_set_preferred_family(). */ int wget_tcp_get_preferred_family(wget_tcp *tcp) { return family_to_value((tcp ? tcp : &global_tcp)->preferred_family); } /** * \param[in] tcp A `wget_tcp` structure representing a TCP connection, returned by wget_tcp_init(). Might be NULL. * \param[in] family One of the socket families defined in ``, such as `AF_INET` or `AF_INET6`. * * Tell the address family that will be used when establishing a TCP connection. * * wget_tcp_resolve() will pick an address of that family, or fail if it cannot find one. * * If \p tcp is NULL, the address family will be set globally. */ void wget_tcp_set_family(wget_tcp *tcp, int family) { (tcp ? tcp : &global_tcp)->family = value_to_family(family); } /** * \param[in] tcp A `wget_tcp` structure representing a TCP connection, returned by wget_tcp_init(). Might be NULL. * \return One of the socket families defined in ``, such as `AF_INET` or `AF_INET6`. * * Get the address family that was previously set with wget_tcp_set_family(). */ int wget_tcp_get_family(wget_tcp *tcp) { return family_to_value((tcp ? tcp : &global_tcp)->family); } /** * \param[in] tcp A `wget_tcp` structure representing a TCP connection, returned by wget_tcp_init(). Might be NULL. * \return The local port. * * Get the port number the TCP connection \p tcp is bound to on the local machine. */ int wget_tcp_get_local_port(wget_tcp *tcp) { if (unlikely(!tcp)) return 0; struct sockaddr_storage addr_store; struct sockaddr *addr = (struct sockaddr *)&addr_store; socklen_t addr_len = sizeof(addr_store); /* Get automatically retrieved port number */ if (getsockname(tcp->sockfd, addr, &addr_len) == 0) { char s_port[NI_MAXSERV]; if (getnameinfo(addr, addr_len, NULL, 0, s_port, sizeof(s_port), NI_NUMERICSERV) == 0) return atoi(s_port); } return 0; } /** * \param[in] tcp A TCP connection. * \param[in] timeout The timeout value. * * Set the timeout for the TCP connection. * * This is the maximum time to wait until the remote host accepts our connection. * * The following two values are special: * * - `0`: No timeout, immediate. * - `-1`: Infinite timeout. Wait indefinitely. */ void wget_tcp_set_connect_timeout(wget_tcp *tcp, int timeout) { (tcp ? tcp : &global_tcp)->connect_timeout = timeout; } /** * \param[in] tcp A TCP connection. * \param[in] timeout The timeout value. * * Set the timeout (in milliseconds) for wget_tcp_read(), wget_tcp_write() and wget_tcp_accept(). * * The following two values are special: * * - `0`: No timeout, immediate. * - `-1`: Infinite timeout. Wait indefinitely. */ void wget_tcp_set_timeout(wget_tcp *tcp, int timeout) { (tcp ? tcp : &global_tcp)->timeout = timeout; } /** * \param[in] tcp A TCP connection. * \return The timeout value that was set with wget_tcp_set_timeout(). * * Get the timeout value that was set with wget_tcp_set_timeout(). */ int wget_tcp_get_timeout(wget_tcp *tcp) { return (tcp ? tcp : &global_tcp)->timeout; } /** * \param[in] tcp A TCP connection. Might be NULL. * \param[in] bind_address An IP address or host name. * * Set the IP address/hostname the socket \p tcp will bind to on the local machine * when connecting to a remote host. * * The hostname can explicitly set the port after a colon (':'). * * This is mainly relevant to wget_tcp_connect(). */ void wget_tcp_set_bind_address(wget_tcp *tcp, const char *bind_address) { if (!tcp) tcp = &global_tcp; wget_dns_freeaddrinfo(tcp->dns, &tcp->bind_addrinfo); if (bind_address) { const char *host, *s = bind_address; if (*s == '[') { /* IPv6 address within brackets */ char *p = strrchr(s, ']'); if (p) { host = s + 1; s = p + 1; } else { /* Something is broken */ host = s + 1; while (*s) s++; } } else { host = s; while (*s && *s != ':') s++; } if (*s == ':') { char port[6]; wget_strscpy(port, s + 1, sizeof(port)); if (c_isdigit(*port)) tcp->bind_addrinfo = wget_dns_resolve(tcp->dns, host, (uint16_t) atoi(port), tcp->family, tcp->preferred_family); } else { tcp->bind_addrinfo = wget_dns_resolve(tcp->dns, host, 0, tcp->family, tcp->preferred_family); } } } /** * \param[in] tcp A TCP connection. Might be NULL. * \param[in] bind_interface A network interface name. * * Set the Network Interface the socket \p tcp will bind to on the local machine * when connecting to a remote host. * * This is mainly relevant to wget_tcp_connect(). */ void wget_tcp_set_bind_interface(wget_tcp *tcp, const char *bind_interface) { if (!tcp) tcp = &global_tcp; tcp->bind_interface = bind_interface; } /** * \param[in] tcp A `wget_tcp` structure representing a TCP connection, returned by wget_tcp_init(). * \param[in] ssl Flag to enable or disable SSL/TLS on the given connection. * * Enable or disable SSL/TLS. * * If \p tcp is NULL, TLS will be enabled globally. Otherwise, TLS will be enabled only for the provided connection. */ void wget_tcp_set_ssl(wget_tcp *tcp, bool ssl) { (tcp ? tcp : &global_tcp)->ssl = ssl; } /** * \param[in] tcp A `wget_tcp` structure representing a TCP connection, returned by wget_tcp_init(). * \return 1 if TLs is enabled, 0 otherwise. * * Tells whether TLS is enabled or not. */ bool wget_tcp_get_ssl(wget_tcp *tcp) { return (tcp ? tcp : &global_tcp)->ssl; } /** * \param[in] tcp A `wget_tcp` structure representing a TCP connection, returned by wget_tcp_init(). * \return IP address as string, NULL if not available. * * Returns the IP address of a `wget_tcp` instance. */ const char *wget_tcp_get_ip(wget_tcp *tcp) { return tcp ? tcp->ip : NULL; } /** * \param[in] tcp A `wget_tcp` structure representing a TCP connection, returned by wget_tcp_init(). Might be NULL. * \param[in] hostname A hostname. The value of the SNI field. * * Sets the TLS Server Name Indication (SNI). For more info see [RFC 6066, sect. 3](https://tools.ietf.org/html/rfc6066#section-3). * * SNI basically does at the TLS layer what the `Host:` header field does at the application (HTTP) layer. * The server might use this information to locate an appropriate X.509 certificate from a pool of certificates, or to direct * the request to a specific virtual host, for instance. */ void wget_tcp_set_ssl_hostname(wget_tcp *tcp, const char *hostname) { if (!tcp) tcp = &global_tcp; xfree(tcp->ssl_hostname); tcp->ssl_hostname = wget_strdup(hostname); } /** * \param[in] tcp A `wget_tcp` structure representing a TCP connection, returned by wget_tcp_init(). Might be NULL. * \return A hostname. The value of the SNI field. * * Returns the value that was set to SNI with a previous call to wget_tcp_set_ssl_hostname(). */ const char *wget_tcp_get_ssl_hostname(wget_tcp *tcp) { return (tcp ? tcp : &global_tcp)->ssl_hostname; } /** * \return A new `wget_tcp` structure, with pre-defined parameters. * * Create a new `wget_tcp` structure, that represents a TCP connection. * It can be destroyed with wget_tcp_deinit(). * * This function does not establish or modify a TCP connection in any way. * That can be done with the other functions in this file, such as * wget_tcp_connect() or wget_tcp_listen() and wget_tcp_accept(). */ wget_tcp *wget_tcp_init(void) { wget_tcp *tcp = wget_malloc(sizeof(wget_tcp)); if (tcp) { *tcp = global_tcp; tcp->ssl_hostname = wget_strdup(global_tcp.ssl_hostname); } return tcp; } /** * \param[in] _tcp A **pointer** to a `wget_tcp` structure representing a TCP connection, returned by wget_tcp_init(). Might be NULL. * * Release a TCP connection (created with wget_tcp_init()). * * The `wget_tcp` structure will be freed and \p _tcp will be set to NULL. * * If \p _tcp is NULL, the SNI field will be cleared. * * Does not free the internal DNS cache, so that other connections can re-use it. * Call wget_dns_cache_free() if you want to free it. */ void wget_tcp_deinit(wget_tcp **_tcp) { wget_tcp *tcp; if (!_tcp) { xfree(global_tcp.ssl_hostname); return; } if ((tcp = *_tcp)) { wget_tcp_close(tcp); wget_dns_freeaddrinfo(tcp->dns, &tcp->bind_addrinfo); xfree(tcp->ssl_hostname); xfree(tcp->ip); xfree(tcp); *_tcp = NULL; } } static void _set_async(int fd) { #if ((defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__) unsigned long blocking = 0; if (ioctl(fd, FIONBIO, &blocking)) error_printf_exit(_("Failed to set socket to non-blocking\n")); #else int flags; if ((flags = fcntl(fd, F_GETFL)) < 0) error_printf_exit(_("Failed to get socket flags\n")); if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) error_printf_exit(_("Failed to set socket to non-blocking\n")); #endif } static void set_socket_options(const wget_tcp *tcp, int fd) { int on = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)) == -1) error_printf(_("Failed to set socket option REUSEADDR\n")); on = 1; if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on)) == -1) error_printf(_("Failed to set socket option NODELAY\n")); #ifdef SO_BINDTODEVICE if (tcp->bind_interface) { if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, tcp->bind_interface, (socklen_t)strlen(tcp->bind_interface)) == -1) error_printf(_("Failed to set socket option BINDTODEVICE\n")); } #else // Let's exit here instead of using a wrong interface (privacy concerns) if (tcp->bind_interface) error_printf_exit(_("Unsupported socket option BINDTODEVICE\n")); #endif #ifdef TCP_FASTOPEN_LINUX_411 on = 1; if (setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, (void *)&on, sizeof(on)) == -1) debug_printf("Failed to set socket option TCP_FASTOPEN_CONNECT\n"); #endif } /** * Test whether the given connection (\p tcp) is ready to read or write. * * The parameter \p flags can have one or both (with bitwise OR) of the following values: * * - `WGET_IO_READABLE`: Is data available for reading? * - `WGET_IO_WRITABLE`: Can we write immediately (without having to wait until the TCP buffer frees)? */ int wget_tcp_ready_2_transfer(wget_tcp *tcp, int flags) { if (likely(tcp)) return wget_ready_2_transfer(tcp->sockfd, tcp->timeout, flags); else return -1; } /** * \param[in] tcp A `wget_tcp` structure representing a TCP connection, returned by wget_tcp_init(). * \param[in] host Hostname or IP address to connect to. * \param[in] port port number * \return WGET_E_SUCCESS (0) on success, or a negative integer on error (some of WGET_E_XXX defined in ``). * * Open a TCP connection with a remote host. * * This function will use TLS if it has been enabled for this `wget_tcp`. You can enable it * with wget_tcp_set_ssl(). Additionally, you can also use wget_tcp_set_ssl_hostname() to set the * Server Name Indication (SNI). * * You can set which IP address and port on the local machine will the socket be bound to * with wget_tcp_set_bind_address(). Otherwise the socket will bind to any address and port * chosen by the operating system. * * You can also set which Network Interface on the local machine will the socket be bound to * with wget_tcp_bind_interface(). * * This function will try to use TCP Fast Open if enabled and available. If TCP Fast Open fails, * it will fall back to the normal TCP handshake, without raising an error. You can enable TCP Fast Open * with wget_tcp_set_tcp_fastopen(). * * If the connection fails, `WGET_E_CONNECT` is returned. */ int wget_tcp_connect(wget_tcp *tcp, const char *host, uint16_t port) { struct addrinfo *ai; int rc, ret = WGET_E_UNKNOWN; char adr[NI_MAXHOST], s_port[NI_MAXSERV]; int debug = wget_logger_is_active(wget_get_logger(WGET_LOGGER_DEBUG)); if (unlikely(!tcp)) return WGET_E_INVALID; wget_dns_freeaddrinfo(tcp->dns, &tcp->addrinfo); tcp->addrinfo = wget_dns_resolve(tcp->dns, host, port, tcp->family, tcp->preferred_family); for (ai = tcp->addrinfo; ai; ai = ai->ai_next) { if (debug) { rc = getnameinfo(ai->ai_addr, ai->ai_addrlen, adr, sizeof(adr), s_port, sizeof(s_port), NI_NUMERICHOST | NI_NUMERICSERV); if (rc == 0) debug_printf("trying %s:%s...\n", adr, s_port); else debug_printf("trying ???:%s (%s)...\n", s_port, gai_strerror(rc)); } int sockfd; if ((sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) != -1) { _set_async(sockfd); set_socket_options(tcp, sockfd); if (tcp->bind_addrinfo) { if (debug) { rc = getnameinfo(tcp->bind_addrinfo->ai_addr, tcp->bind_addrinfo->ai_addrlen, adr, sizeof(adr), s_port, sizeof(s_port), NI_NUMERICHOST | NI_NUMERICSERV); if (rc == 0) debug_printf("binding to %s:%s...\n", adr, s_port); else debug_printf("binding to ???:%s (%s)...\n", s_port, gai_strerror(rc)); } if (bind(sockfd, tcp->bind_addrinfo->ai_addr, tcp->bind_addrinfo->ai_addrlen) != 0) { error_printf(_("Failed to bind (%d)\n"), errno); close(sockfd); return WGET_E_UNKNOWN; } } /* Enable TCP Fast Open, if required by the user and available */ #ifdef TCP_FASTOPEN_OSX if (tcp->tcp_fastopen) { sa_endpoints_t endpoints = { .sae_dstaddr = ai->ai_addr, .sae_dstaddrlen = ai->ai_addrlen }; rc = connectx(sockfd, &endpoints, SAE_ASSOCID_ANY, CONNECT_RESUME_ON_READ_WRITE | CONNECT_DATA_IDEMPOTENT, NULL, 0, NULL, NULL); tcp->first_send = 0; #elif defined TCP_FASTOPEN_LINUX if (tcp->tcp_fastopen) { errno = 0; tcp->connect_addrinfo = ai; rc = 0; tcp->first_send = 1; #elif defined TCP_FASTOPEN_LINUX_411 if (tcp->tcp_fastopen) { tcp->connect_addrinfo = ai; rc = connect(sockfd, ai->ai_addr, ai->ai_addrlen); tcp->first_send = 0; #else if (0) { #endif } else { rc = connect(sockfd, ai->ai_addr, ai->ai_addrlen); tcp->first_send = 0; } if (rc < 0 && errno != EAGAIN && errno != EINPROGRESS ) { error_printf(_("Failed to connect (%d)\n"), errno); ret = WGET_E_CONNECT; close(sockfd); } else { tcp->sockfd = sockfd; if (tcp->ssl) { if ((ret = wget_ssl_open(tcp))) { if (ret == WGET_E_CERTIFICATE) { wget_tcp_close(tcp); break; /* stop here - the server cert couldn't be validated */ } /* do not free tcp->addrinfo when calling wget_tcp_close() */ struct addrinfo *ai_tmp = tcp->addrinfo; tcp->addrinfo = NULL; wget_tcp_close(tcp); tcp->addrinfo = ai_tmp; continue; } } if (getnameinfo(ai->ai_addr, ai->ai_addrlen, adr, sizeof(adr), s_port, sizeof(s_port), NI_NUMERICHOST | NI_NUMERICSERV) == 0) tcp->ip = wget_strdup(adr); else tcp->ip = NULL; return WGET_E_SUCCESS; } } else error_printf(_("Failed to create socket (%d)\n"), errno); } return ret; } /** * \param[in] tcp An active connection. * \return WGET_E_SUCCESS (0) on success, or a negative integer on error (one of WGET_E_XXX, defined in ``). * Start TLS for this connection. * * This will typically be called by wget_tcp_accept(). * * If the socket is listening (e.g. wget_tcp_listen(), wget_tcp_accept()), it will expect the client to perform a TLS handshake, * and fail if it doesn't. * * If this is a client connection (e.g. wget_tcp_connect()), it will try perform a TLS handshake with the server. */ int wget_tcp_tls_start(wget_tcp *tcp) { return wget_ssl_open(tcp); } /** * \param[in] tcp An active connection. * * Stops TLS, but does not close the connection. Data will be transmitted in the clear from now on. */ void wget_tcp_tls_stop(wget_tcp *tcp) { if (tcp) wget_ssl_close(&tcp->ssl_session); } /** * \param[in] tcp An active TCP connection. * \param[in] buf Destination buffer, at least \p count bytes long. * \param[in] count Length of the buffer \p buf. * \return Number of bytes read * * Read \p count bytes of data from the TCP connection represented by \p tcp * and store them in the buffer \p buf. * * This function knows whether the provided connection is over TLS or not * and it will do the right thing. * * The `tcp->timeout` parameter is taken into account by this function as well. * It specifies how long should this function wait until there's data available * to read (in milliseconds). The default timeout is -1, which means to wait indefinitely. * * The following two values are special: * * - `0`: No timeout, immediate. * - `-1`: Infinite timeout. Wait indefinitely until a new connection comes. * * You can set the timeout with wget_tcp_set_timeout(). * * In particular, the returned value will be zero if no data was available for reading * before the timeout elapsed. */ ssize_t wget_tcp_read(wget_tcp *tcp, char *buf, size_t count) { ssize_t rc; if (unlikely(!tcp || !buf)) return 0; if (tcp->ssl_session) { rc = wget_ssl_read_timeout(tcp->ssl_session, buf, count, tcp->timeout); } else { if (tcp->timeout) { if ((rc = wget_ready_2_read(tcp->sockfd, tcp->timeout)) <= 0) return rc; } rc = recvfrom(tcp->sockfd, buf, count, 0, NULL, NULL); } if (rc < 0) error_printf(_("Failed to read %zu bytes (%d)\n"), count, errno); return rc; } /** * \param[in] tcp An active TCP connection. * \param[in] buf A buffer, at least \p count bytes long. * \param[in] count Number of bytes from \p buf to send through \p tcp. * \return The number of bytes written, or -1 on error. * * Write \p count bytes of data from the buffer \p buf to the TCP connection * represented by \p tcp. * * This function knows whether the provided connection is over TLS or not * and it will do the right thing. * * TCP Fast Open will be used if it's available and enabled. You can enable TCP Fast Open * with wget_tcp_set_tcp_fastopen(). * * This function honors the `timeout` parameter. If the write operation fails because the socket buffer is full, * then it will wait at most that amount of milliseconds. If after the timeout the socket is still unavailable * for writing, this function returns zero. * * The following two values are special: * * - `0`: No timeout. The socket must be available immediately. * - `-1`: Infinite timeout. Wait indefinitely until the socket becomes available. * * You can set the timeout with wget_tcp_set_timeout(). */ ssize_t wget_tcp_write(wget_tcp *tcp, const char *buf, size_t count) { ssize_t nwritten = 0; if (unlikely(!tcp || !buf)) return -1; if (tcp->ssl_session) return wget_ssl_write_timeout(tcp->ssl_session, buf, count, tcp->timeout); while (count) { ssize_t n; #ifdef TCP_FASTOPEN_LINUX if (tcp->tcp_fastopen && tcp->first_send) { n = sendto(tcp->sockfd, buf, count, MSG_FASTOPEN, tcp->connect_addrinfo->ai_addr, tcp->connect_addrinfo->ai_addrlen); tcp->first_send = 0; if (n < 0 && errno == EOPNOTSUPP) { /* fallback from fastopen, e.g. when fastopen is disabled in system */ tcp->tcp_fastopen = 0; int rc = connect(tcp->sockfd, tcp->connect_addrinfo->ai_addr, tcp->connect_addrinfo->ai_addrlen); if (rc < 0 && errno != EAGAIN && errno != ENOTCONN && errno != EINPROGRESS) { error_printf(_("Failed to connect (%d)\n"), errno); return -1; } errno = EAGAIN; } } else #endif n = send(tcp->sockfd, buf, count, 0); if (n >= 0) { nwritten += n; if ((size_t)n >= count) return nwritten; count -= n; buf += n; } else { if (errno != EAGAIN && errno != ENOTCONN && errno != EINPROGRESS) { error_printf(_("Failed to write %zu bytes (%d)\n"), count, errno); return -1; } if (tcp->timeout) { int rc = wget_ready_2_write(tcp->sockfd, tcp->timeout); if (rc <= 0) return rc; } } } return 0; } /** * \param[in] tcp An active TCP connection. * \param[in] fmt Format string (like in `printf(3)`). * \param[in] args `va_args` argument list (like in `vprintf(3)`) * * Write data in vprintf-style format, to the connection \p tcp. * * It uses wget_tcp_write(). */ ssize_t wget_tcp_vprintf(wget_tcp *tcp, const char *fmt, va_list args) { char sbuf[4096]; wget_buffer buf; ssize_t len2; wget_buffer_init(&buf, sbuf, sizeof(sbuf)); wget_buffer_vprintf(&buf, fmt, args); len2 = wget_tcp_write(tcp, buf.data, buf.length); wget_buffer_deinit(&buf); if (len2 > 0) debug_write(buf.data, len2); if (len2 > 0 && (ssize_t) buf.length != len2) error_printf(_("%s: internal error: length mismatch %zu != %zd\n"), __func__, buf.length, len2); return len2; } /** * \param[in] tcp An active TCP connection. * \param[in] fmt Format string (like in `printf(3)`). * * Write data in printf-style format, to the connection \p tcp. * * It uses wget_tcp_vprintf(), which in turn uses wget_tcp_write(). */ ssize_t wget_tcp_printf(wget_tcp *tcp, const char *fmt, ...) { va_list args; va_start(args, fmt); ssize_t len = wget_tcp_vprintf(tcp, fmt, args); va_end(args); return len; } /** * \param[in] tcp An active TCP connection * * Close a TCP connection. */ void wget_tcp_close(wget_tcp *tcp) { if (likely(tcp)) { wget_tcp_tls_stop(tcp); if (tcp->sockfd != -1) { close(tcp->sockfd); tcp->sockfd = -1; } wget_dns_freeaddrinfo(tcp->dns, &tcp->addrinfo); } } /** @} */