mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-16 00:00:07 +01:00

See: https://msdn.microsoft.com/en-us/library/windows/desktop/ms737625(v=vs.85).aspx Specifically the section reading: "Until the connection attempt completes on a nonblocking socket, all subsequent calls to connect on the same socket will fail with the error code WSAEALREADY, and WSAEISCONN when the connection completes successfully. Due to ambiguities in version 1.1 of the Windows Sockets specification, error codes returned from connect while a connection is already pending may vary among implementations. As a result, it is not recommended that applications use multiple calls to connect to detect connection completion. If they do, they must be prepared to handle WSAEINVAL and WSAEWOULDBLOCK error values the same way that they handle WSAEALREADY, to assure robust operation."
471 lines
12 KiB
C
471 lines
12 KiB
C
#include "private-libwebsockets.h"
|
|
|
|
struct libwebsocket *libwebsocket_client_connect_2(
|
|
struct libwebsocket_context *context,
|
|
struct libwebsocket *wsi
|
|
) {
|
|
struct libwebsocket_pollfd pfd;
|
|
#ifdef LWS_USE_IPV6
|
|
struct sockaddr_in6 server_addr6;
|
|
struct sockaddr_in6 client_addr6;
|
|
struct addrinfo hints, *result;
|
|
#endif
|
|
struct sockaddr_in server_addr4;
|
|
struct sockaddr_in client_addr4;
|
|
|
|
struct sockaddr *v;
|
|
int n;
|
|
int plen = 0;
|
|
const char *ads;
|
|
|
|
lwsl_client("libwebsocket_client_connect_2\n");
|
|
|
|
/*
|
|
* proxy?
|
|
*/
|
|
|
|
if (context->http_proxy_port) {
|
|
plen = sprintf((char *)context->service_buffer,
|
|
"CONNECT %s:%u HTTP/1.0\x0d\x0a"
|
|
"User-agent: libwebsockets\x0d\x0a"
|
|
/*Proxy-authorization: basic aGVsbG86d29ybGQ= */
|
|
"\x0d\x0a",
|
|
lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS),
|
|
wsi->u.hdr.ah->c_port);
|
|
ads = context->http_proxy_address;
|
|
|
|
#ifdef LWS_USE_IPV6
|
|
if (LWS_IPV6_ENABLED(context))
|
|
server_addr6.sin6_port = htons(context->http_proxy_port);
|
|
else
|
|
#endif
|
|
server_addr4.sin_port = htons(context->http_proxy_port);
|
|
|
|
} else {
|
|
ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS);
|
|
#ifdef LWS_USE_IPV6
|
|
if (LWS_IPV6_ENABLED(context))
|
|
server_addr6.sin6_port = htons(wsi->u.hdr.ah->c_port);
|
|
else
|
|
#endif
|
|
server_addr4.sin_port = htons(wsi->u.hdr.ah->c_port);
|
|
}
|
|
|
|
/*
|
|
* prepare the actual connection (to the proxy, if any)
|
|
*/
|
|
lwsl_client("libwebsocket_client_connect_2: address %s\n", ads);
|
|
|
|
#ifdef LWS_USE_IPV6
|
|
if (LWS_IPV6_ENABLED(context)) {
|
|
memset(&hints, 0, sizeof(struct addrinfo));
|
|
n = getaddrinfo(ads, NULL, &hints, &result);
|
|
if (n) {
|
|
#ifdef _WIN32
|
|
lwsl_err("getaddrinfo: %ls\n", gai_strerrorW(n));
|
|
#else
|
|
lwsl_err("getaddrinfo: %s\n", gai_strerror(n));
|
|
#endif
|
|
goto oom4;
|
|
}
|
|
|
|
server_addr6.sin6_family = AF_INET6;
|
|
switch (result->ai_family) {
|
|
case AF_INET:
|
|
/* map IPv4 to IPv6 */
|
|
bzero((char *)&server_addr6.sin6_addr,
|
|
sizeof(struct in6_addr));
|
|
server_addr6.sin6_addr.s6_addr[10] = 0xff;
|
|
server_addr6.sin6_addr.s6_addr[11] = 0xff;
|
|
memcpy(&server_addr6.sin6_addr.s6_addr[12],
|
|
&((struct sockaddr_in *)result->ai_addr)->sin_addr,
|
|
sizeof(struct in_addr));
|
|
break;
|
|
case AF_INET6:
|
|
memcpy(&server_addr6.sin6_addr,
|
|
&((struct sockaddr_in6 *)result->ai_addr)->sin6_addr,
|
|
sizeof(struct in6_addr));
|
|
break;
|
|
default:
|
|
lwsl_err("Unknown address family\n");
|
|
freeaddrinfo(result);
|
|
goto oom4;
|
|
}
|
|
|
|
freeaddrinfo(result);
|
|
} else
|
|
#endif
|
|
{
|
|
struct addrinfo ai, *res, *result;
|
|
void *p = NULL;
|
|
|
|
memset (&ai, 0, sizeof ai);
|
|
ai.ai_family = PF_UNSPEC;
|
|
ai.ai_socktype = SOCK_STREAM;
|
|
ai.ai_flags = AI_CANONNAME;
|
|
|
|
if (getaddrinfo(ads, NULL, &ai, &result))
|
|
goto oom4;
|
|
|
|
res = result;
|
|
while (!p && res) {
|
|
switch (res->ai_family) {
|
|
case AF_INET:
|
|
p = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
|
|
break;
|
|
}
|
|
|
|
res = res->ai_next;
|
|
}
|
|
|
|
if (!p) {
|
|
freeaddrinfo(result);
|
|
goto oom4;
|
|
}
|
|
|
|
server_addr4.sin_family = AF_INET;
|
|
server_addr4.sin_addr = *((struct in_addr *)p);
|
|
bzero(&server_addr4.sin_zero, 8);
|
|
freeaddrinfo(result);
|
|
}
|
|
|
|
if (wsi->sock < 0) {
|
|
|
|
#ifdef LWS_USE_IPV6
|
|
if (LWS_IPV6_ENABLED(context))
|
|
wsi->sock = socket(AF_INET6, SOCK_STREAM, 0);
|
|
else
|
|
#endif
|
|
wsi->sock = socket(AF_INET, SOCK_STREAM, 0);
|
|
|
|
if (wsi->sock < 0) {
|
|
lwsl_warn("Unable to open socket\n");
|
|
goto oom4;
|
|
}
|
|
|
|
if (lws_plat_set_socket_options(context, wsi->sock)) {
|
|
lwsl_err("Failed to set wsi socket options\n");
|
|
compatible_close(wsi->sock);
|
|
goto oom4;
|
|
}
|
|
|
|
wsi->mode = LWS_CONNMODE_WS_CLIENT_WAITING_CONNECT;
|
|
|
|
lws_libev_accept(context, wsi, wsi->sock);
|
|
insert_wsi_socket_into_fds(context, wsi);
|
|
|
|
libwebsocket_set_timeout(wsi,
|
|
PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE,
|
|
AWAITING_TIMEOUT);
|
|
#ifdef LWS_USE_IPV6
|
|
if (LWS_IPV6_ENABLED(context)) {
|
|
v = (struct sockaddr *)&client_addr6;
|
|
n = sizeof(client_addr6);
|
|
bzero((char *)v, n);
|
|
client_addr6.sin6_family = AF_INET6;
|
|
} else
|
|
#endif
|
|
{
|
|
v = (struct sockaddr *)&client_addr4;
|
|
n = sizeof(client_addr4);
|
|
bzero((char *)v, n);
|
|
client_addr4.sin_family = AF_INET;
|
|
}
|
|
|
|
if (context->iface) {
|
|
if (interface_to_sa(context, context->iface,
|
|
(struct sockaddr_in *)v, n) < 0) {
|
|
lwsl_err("Unable to find interface %s\n",
|
|
context->iface);
|
|
compatible_close(wsi->sock);
|
|
goto failed;
|
|
}
|
|
|
|
if (bind(wsi->sock, v, n) < 0) {
|
|
lwsl_err("Error binding to interface %s",
|
|
context->iface);
|
|
compatible_close(wsi->sock);
|
|
goto failed;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef LWS_USE_IPV6
|
|
if (LWS_IPV6_ENABLED(context)) {
|
|
v = (struct sockaddr *)&server_addr6;
|
|
n = sizeof(struct sockaddr_in6);
|
|
} else
|
|
#endif
|
|
{
|
|
v = (struct sockaddr *)&server_addr4;
|
|
n = sizeof(struct sockaddr);
|
|
}
|
|
|
|
if (connect(wsi->sock, v, n) == -1 || LWS_ERRNO == LWS_EISCONN) {
|
|
|
|
if (LWS_ERRNO == LWS_EALREADY
|
|
|| LWS_ERRNO == LWS_EINPROGRESS
|
|
|| LWS_ERRNO == LWS_EWOULDBLOCK
|
|
#ifdef _WIN32
|
|
|| LWS_ERRNO == WSAEINVAL
|
|
#endif
|
|
)
|
|
{
|
|
lwsl_client("nonblocking connect retry\n");
|
|
|
|
/*
|
|
* must do specifically a POLLOUT poll to hear
|
|
* about the connect completion
|
|
*/
|
|
if (lws_change_pollfd(wsi, 0, LWS_POLLOUT))
|
|
goto oom4;
|
|
lws_libev_io(context, wsi, LWS_EV_START | LWS_EV_WRITE);
|
|
|
|
return wsi;
|
|
}
|
|
|
|
if (LWS_ERRNO != LWS_EISCONN) {
|
|
lwsl_debug("Connect failed errno=%d\n", LWS_ERRNO);
|
|
goto failed;
|
|
}
|
|
}
|
|
|
|
lwsl_client("connected\n");
|
|
|
|
/* we are connected to server, or proxy */
|
|
|
|
if (context->http_proxy_port) {
|
|
|
|
/* OK from now on we talk via the proxy, so connect to that */
|
|
|
|
/*
|
|
* (will overwrite existing pointer,
|
|
* leaving old string/frag there but unreferenced)
|
|
*/
|
|
if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS,
|
|
context->http_proxy_address))
|
|
goto failed;
|
|
wsi->u.hdr.ah->c_port = context->http_proxy_port;
|
|
|
|
n = send(wsi->sock, context->service_buffer, plen, MSG_NOSIGNAL);
|
|
if (n < 0) {
|
|
lwsl_debug("ERROR writing to proxy socket\n");
|
|
goto failed;
|
|
}
|
|
|
|
libwebsocket_set_timeout(wsi,
|
|
PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE,
|
|
AWAITING_TIMEOUT);
|
|
|
|
wsi->mode = LWS_CONNMODE_WS_CLIENT_WAITING_PROXY_REPLY;
|
|
|
|
return wsi;
|
|
}
|
|
|
|
/*
|
|
* provoke service to issue the handshake directly
|
|
* we need to do it this way because in the proxy case, this is the
|
|
* next state and executed only if and when we get a good proxy
|
|
* response inside the state machine... but notice in SSL case this
|
|
* may not have sent anything yet with 0 return, and won't until some
|
|
* many retries from main loop. To stop that becoming endless,
|
|
* cover with a timeout.
|
|
*/
|
|
|
|
libwebsocket_set_timeout(wsi,
|
|
PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE, AWAITING_TIMEOUT);
|
|
|
|
wsi->mode = LWS_CONNMODE_WS_CLIENT_ISSUE_HANDSHAKE;
|
|
pfd.fd = wsi->sock;
|
|
pfd.revents = LWS_POLLIN;
|
|
|
|
n = libwebsocket_service_fd(context, &pfd);
|
|
|
|
if (n < 0)
|
|
goto failed;
|
|
|
|
if (n) /* returns 1 on failure after closing wsi */
|
|
return NULL;
|
|
|
|
return wsi;
|
|
|
|
oom4:
|
|
lws_free(wsi->u.hdr.ah);
|
|
lws_free(wsi);
|
|
return NULL;
|
|
|
|
failed:
|
|
libwebsocket_close_and_free_session(context, wsi,
|
|
LWS_CLOSE_STATUS_NOSTATUS);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* libwebsocket_client_connect() - Connect to another websocket server
|
|
* @context: Websocket context
|
|
* @address: Remote server address, eg, "myserver.com"
|
|
* @port: Port to connect to on the remote server, eg, 80
|
|
* @ssl_connection: 0 = ws://, 1 = wss:// encrypted, 2 = wss:// allow self
|
|
* signed certs
|
|
* @path: Websocket path on server
|
|
* @host: Hostname on server
|
|
* @origin: Socket origin name
|
|
* @protocol: Comma-separated list of protocols being asked for from
|
|
* the server, or just one. The server will pick the one it
|
|
* likes best. If you don't want to specify a protocol, which is
|
|
* legal, use NULL here.
|
|
* @ietf_version_or_minus_one: -1 to ask to connect using the default, latest
|
|
* protocol supported, or the specific protocol ordinal
|
|
*
|
|
* This function creates a connection to a remote server
|
|
*/
|
|
|
|
LWS_VISIBLE struct libwebsocket *
|
|
libwebsocket_client_connect(struct libwebsocket_context *context,
|
|
const char *address,
|
|
int port,
|
|
int ssl_connection,
|
|
const char *path,
|
|
const char *host,
|
|
const char *origin,
|
|
const char *protocol,
|
|
int ietf_version_or_minus_one)
|
|
{
|
|
struct libwebsocket *wsi;
|
|
|
|
wsi = lws_zalloc(sizeof(struct libwebsocket));
|
|
if (wsi == NULL)
|
|
goto bail;
|
|
|
|
wsi->sock = -1;
|
|
|
|
/* -1 means just use latest supported */
|
|
|
|
if (ietf_version_or_minus_one == -1)
|
|
ietf_version_or_minus_one = SPEC_LATEST_SUPPORTED;
|
|
|
|
wsi->ietf_spec_revision = ietf_version_or_minus_one;
|
|
wsi->user_space = NULL;
|
|
wsi->state = WSI_STATE_CLIENT_UNCONNECTED;
|
|
wsi->protocol = NULL;
|
|
wsi->pending_timeout = NO_PENDING_TIMEOUT;
|
|
|
|
#ifdef LWS_OPENSSL_SUPPORT
|
|
wsi->use_ssl = ssl_connection;
|
|
#else
|
|
if (ssl_connection) {
|
|
lwsl_err("libwebsockets not configured for ssl\n");
|
|
goto bail;
|
|
}
|
|
#endif
|
|
|
|
if (lws_allocate_header_table(wsi))
|
|
goto bail;
|
|
|
|
/*
|
|
* we're not necessarily in a position to action these right away,
|
|
* stash them... we only need during connect phase so u.hdr is fine
|
|
*/
|
|
wsi->u.hdr.ah->c_port = port;
|
|
if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, address))
|
|
goto bail1;
|
|
|
|
/* these only need u.hdr lifetime as well */
|
|
|
|
if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_URI, path))
|
|
goto bail1;
|
|
|
|
if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_HOST, host))
|
|
goto bail1;
|
|
|
|
if (origin)
|
|
if (lws_hdr_simple_create(wsi,
|
|
_WSI_TOKEN_CLIENT_ORIGIN, origin))
|
|
goto bail1;
|
|
/*
|
|
* this is a list of protocols we tell the server we're okay with
|
|
* stash it for later when we compare server response with it
|
|
*/
|
|
if (protocol)
|
|
if (lws_hdr_simple_create(wsi,
|
|
_WSI_TOKEN_CLIENT_SENT_PROTOCOLS, protocol))
|
|
goto bail1;
|
|
|
|
wsi->protocol = &context->protocols[0];
|
|
|
|
/*
|
|
* Check with each extension if it is able to route and proxy this
|
|
* connection for us. For example, an extension like x-google-mux
|
|
* can handle this and then we don't need an actual socket for this
|
|
* connection.
|
|
*/
|
|
|
|
if (lws_ext_callback_for_each_extension_type(context, wsi,
|
|
LWS_EXT_CALLBACK_CAN_PROXY_CLIENT_CONNECTION,
|
|
(void *)address, port) > 0) {
|
|
lwsl_client("libwebsocket_client_connect: ext handling conn\n");
|
|
|
|
libwebsocket_set_timeout(wsi,
|
|
PENDING_TIMEOUT_AWAITING_EXTENSION_CONNECT_RESPONSE,
|
|
AWAITING_TIMEOUT);
|
|
|
|
wsi->mode = LWS_CONNMODE_WS_CLIENT_WAITING_EXTENSION_CONNECT;
|
|
return wsi;
|
|
}
|
|
lwsl_client("libwebsocket_client_connect: direct conn\n");
|
|
|
|
return libwebsocket_client_connect_2(context, wsi);
|
|
|
|
bail1:
|
|
lws_free(wsi->u.hdr.ah);
|
|
bail:
|
|
lws_free(wsi);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* libwebsocket_client_connect_extended() - Connect to another websocket server
|
|
* @context: Websocket context
|
|
* @address: Remote server address, eg, "myserver.com"
|
|
* @port: Port to connect to on the remote server, eg, 80
|
|
* @ssl_connection: 0 = ws://, 1 = wss:// encrypted, 2 = wss:// allow self
|
|
* signed certs
|
|
* @path: Websocket path on server
|
|
* @host: Hostname on server
|
|
* @origin: Socket origin name
|
|
* @protocol: Comma-separated list of protocols being asked for from
|
|
* the server, or just one. The server will pick the one it
|
|
* likes best.
|
|
* @ietf_version_or_minus_one: -1 to ask to connect using the default, latest
|
|
* protocol supported, or the specific protocol ordinal
|
|
* @userdata: Pre-allocated user data
|
|
*
|
|
* This function creates a connection to a remote server
|
|
*/
|
|
|
|
LWS_VISIBLE struct libwebsocket *
|
|
libwebsocket_client_connect_extended(struct libwebsocket_context *context,
|
|
const char *address,
|
|
int port,
|
|
int ssl_connection,
|
|
const char *path,
|
|
const char *host,
|
|
const char *origin,
|
|
const char *protocol,
|
|
int ietf_version_or_minus_one,
|
|
void *userdata)
|
|
{
|
|
struct libwebsocket *ws =
|
|
libwebsocket_client_connect(context, address, port,
|
|
ssl_connection, path, host, origin, protocol,
|
|
ietf_version_or_minus_one);
|
|
|
|
if (ws && !ws->user_space && userdata) {
|
|
ws->user_space_externally_allocated = 1;
|
|
ws->user_space = userdata ;
|
|
}
|
|
|
|
return ws ;
|
|
}
|