CLIENT_CONNECTION_ERROR add strings
This clears up a couple of issues with client connect.
- if CLIENT_CONNECTION_ERROR is coming, which of the many
ways the rejection may have happened is documented in the
in argument. It's still possible if it just got hung up on
in will be NULL, but now it has MANY more canned strings
describing the issue available at the callback
"getaddrinfo (ipv6) failed"
"unknown address family"
"getaddrinfo (ipv4) failed"
"set socket opts failed"
"insert wsi failed"
"lws_ssl_client_connect1 failed"
"lws_ssl_client_connect2 failed"
"Peer hung up"
"read failed"
"HS: URI missing"
"HS: Redirect code but no Location"
"HS: URI did not parse"
"HS: Redirect failed"
"HS: Server did not return 200"
"HS: OOM"
"HS: disallowed by client filter"
"HS: disallowed at ESTABLISHED"
"HS: ACCEPT missing"
"HS: ws upgrade response not 101"
"HS: UPGRADE missing"
"HS: Upgrade to something other than websocket"
"HS: CONNECTION missing"
"HS: UPGRADE malformed"
"HS: PROTOCOL malformed"
"HS: Cannot match protocol"
"HS: EXT: list too big"
"HS: EXT: failed setting defaults"
"HS: EXT: failed parsing defaults"
"HS: EXT: failed parsing options"
"HS: EXT: Rejects server options"
"HS: EXT: unknown ext"
"HS: Accept hash wrong"
"HS: Rejected by filter cb"
"HS: OOM"
"HS: SO_SNDBUF failed"
"HS: Rejected at CLIENT_ESTABLISHED"
- until now the user code did not get the new wsi that was created
in the client connection action until it returned. However the
client connection action may provoke callbacks like
CLIENT_CONNECTION_ERROR before then, if multiple client connections
are initiated it makes it unknown to user code which one the callback
applies to. The wsi is provided in the callback but it has not yet
returned from the client connect api to give that wsi to the user code.
To solve that there is a new member added to client connect info struct,
pwsi, which lets you pass a pointer to a struct wsi * in the user code
that will get filled in with the new wsi. That happens before any
callbacks could be provoked, and it is updated to NULL if the connect
action fails before returning from the client connect api.
diff --git a/lib/client-handshake.c b/lib/client-handshake.c
index 5c0d356..678979f 100644
--- a/lib/client-handshake.c
+++ b/lib/client-handshake.c
@@ -12,6 +12,7 @@
struct sockaddr_in server_addr4;
struct lws_pollfd pfd;
struct sockaddr *v;
+ const char *cce = NULL;
int n, plen = 0;
const char *ads;
@@ -72,6 +73,7 @@
#else
lwsl_err("getaddrinfo: %s\n", gai_strerror(n));
#endif
+ cce = "getaddrinfo (ipv6) failed";
goto oom4;
}
@@ -97,6 +99,7 @@
default:
lwsl_err("Unknown address family\n");
freeaddrinfo(result);
+ cce = "unknown address family";
goto oom4;
}
@@ -114,6 +117,7 @@
if (getaddrinfo(ads, NULL, &ai, &result)) {
lwsl_err("getaddrinfo failed\n");
+ cce = "getaddrinfo (ipv4) failed";
goto oom4;
}
@@ -157,6 +161,7 @@
if (lws_plat_set_socket_options(wsi->vhost, wsi->sock)) {
lwsl_err("Failed to set wsi socket options\n");
compatible_close(wsi->sock);
+ cce = "set socket opts failed";
goto oom4;
}
@@ -166,6 +171,7 @@
lws_libuv_accept(wsi, wsi->sock);
if (insert_wsi_socket_into_fds(context, wsi)) {
compatible_close(wsi->sock);
+ cce = "insert wsi failed";
goto oom4;
}
@@ -291,10 +297,12 @@
/* we're closing, losing some rx is OK */
wsi->u.hdr.ah->rxpos = wsi->u.hdr.ah->rxlen;
//lwsl_err("%d\n", wsi->mode);
- if (wsi->mode == LWSCM_HTTP_CLIENT)
+ if (wsi->mode == LWSCM_HTTP_CLIENT) {
wsi->vhost->protocols[0].callback(wsi,
LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
- wsi->user_space, NULL, 0);
+ wsi->user_space, (void *)cce, strlen(cce));
+ wsi->already_did_cce = 1;
+ }
/* take care that we might be inserted in fds already */
if (wsi->position_in_fds_table != -1)
goto failed;
@@ -566,12 +574,20 @@
wsi->u.hdr.stash->protocol[sizeof(wsi->u.hdr.stash->protocol) - 1] = '\0';
wsi->u.hdr.stash->method[sizeof(wsi->u.hdr.stash->method) - 1] = '\0';
+ if (i->pwsi)
+ *i->pwsi = wsi;
+
/* if we went on the waiting list, no probs just return the wsi
* when we get the ah, now or later, he will call
* lws_client_connect_via_info2() below.
*/
- if (lws_header_table_attach(wsi, 0) < 0)
- return NULL;
+ if (lws_header_table_attach(wsi, 0) < 0) {
+ /*
+ * if we failed here, the connection is already closed
+ * and freed.
+ */
+ goto bail1;
+ }
if (i->parent_wsi) {
lwsl_info("%s: created child %p of parent %p\n", __func__,
@@ -592,6 +608,10 @@
bail:
lws_free(wsi);
+bail1:
+ if (i->pwsi)
+ *i->pwsi = NULL;
+
return NULL;
}
diff --git a/lib/client.c b/lib/client.c
index 618f5bb..1103079 100755
--- a/lib/client.c
+++ b/lib/client.c
@@ -72,8 +72,9 @@
{
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
char *p = (char *)&pt->serv_buf[0];
- char *sb = p;
+ const char *cce = NULL;
unsigned char c;
+ char *sb = p;
int n, len;
switch (wsi->mode) {
@@ -155,8 +156,10 @@
n = lws_ssl_client_connect1(wsi);
if (!n)
return 0;
- if (n < 0)
+ if (n < 0) {
+ cce = "lws_ssl_client_connect1 failed";
goto bail3;
+ }
} else
wsi->ssl = NULL;
@@ -168,8 +171,10 @@
n = lws_ssl_client_connect2(wsi);
if (!n)
return 0;
- if (n < 0)
+ if (n < 0) {
+ cce = "lws_ssl_client_connect2 failed";
goto bail3;
+ }
} else
wsi->ssl = NULL;
#endif
@@ -220,7 +225,7 @@
lwsl_debug("Server connection %p (fd=%d) dead\n",
(void *)wsi, pollfd->fd);
-
+ cce = "Peer hung up";
goto bail3;
}
@@ -255,6 +260,7 @@
switch (n) {
case 0:
case LWS_SSL_CAPABLE_ERROR:
+ cce = "read failed";
goto bail3;
case LWS_SSL_CAPABLE_MORE_SERVICE:
return 0;
@@ -287,7 +293,8 @@
lwsl_info("closing conn at LWS_CONNMODE...SERVER_REPLY\n");
wsi->vhost->protocols[0].callback(wsi,
LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
- wsi->user_space, NULL, 0);
+ wsi->user_space, (void *)cce, cce ? strlen(cce) : 0);
+ wsi->already_did_cce = 1;
lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
return -1;
@@ -357,10 +364,10 @@
int
lws_client_interpret_server_handshake(struct lws *wsi)
{
- int n, len, okay = 0, isErrorCodeReceived = 0, port = 0, ssl = 0;
+ int n, len, okay = 0, port = 0, ssl = 0;
int close_reason = LWS_CLOSE_STATUS_PROTOCOL_ERR;
struct lws_context *context = wsi->context;
- const char *pc, *prot, *ads = NULL, *path;
+ const char *pc, *prot, *ads = NULL, *path, *cce = NULL;
struct allocated_headers *ah;
char *p;
#ifndef LWS_NO_EXTENSIONS
@@ -404,6 +411,7 @@
p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP);
if (wsi->do_ws && !p) {
lwsl_info("no URI\n");
+ cce = "HS: URI missing";
goto bail3;
}
if (!p) {
@@ -411,23 +419,29 @@
wsi->u.http.connection_type = HTTP_CONNECTION_CLOSE;
}
if (!p) {
+ cce = "HS: URI missing";
lwsl_info("no URI\n");
goto bail3;
}
n = atoi(p);
if (n == 301 || n == 302 || n == 303 || n == 307 || n == 308) {
p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_LOCATION);
- if (!p)
+ if (!p) {
+ cce = "HS: Redirect code but no Location";
goto bail3;
+ }
- if (lws_parse_uri(p, &prot, &ads, &port, &path))
+ if (lws_parse_uri(p, &prot, &ads, &port, &path)) {
+ cce = "HS: URI did not parse";
goto bail3;
+ }
if (!strcmp(prot, "wss://") || !strcmp(prot, "https://"))
ssl = 1;
if (lws_client_reset(wsi, ssl, ads, port, path, ads)) {
lwsl_err("Redirect failed\n");
+ cce = "HS: Redirect failed";
goto bail3;
}
return 0;
@@ -436,6 +450,7 @@
if (!wsi->do_ws) {
if (n != 200) {
lwsl_notice("Connection failed with code %d", n);
+ cce = "HS: Server did not return 200";
goto bail2;
}
@@ -451,6 +466,7 @@
/* allocate the per-connection user memory (if any) */
if (lws_ensure_user_space(wsi)) {
lwsl_err("Problem allocating wsi user mem\n");
+ cce = "HS: OOM";
goto bail2;
}
@@ -481,8 +497,11 @@
* headers and OK it
*/
if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH,
- wsi->user_space, NULL, 0))
+ wsi->user_space, NULL, 0)) {
+
+ cce = "HS: disallowed by client filter";
goto bail2;
+ }
/* clear his proxy connection timeout */
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
@@ -492,8 +511,10 @@
/* call him back to inform him he is up */
if (wsi->protocol->callback(wsi,
LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP,
- wsi->user_space, NULL, 0))
+ wsi->user_space, NULL, 0)) {
+ cce = "HS: disallowed at ESTABLISHED";
goto bail3;
+ }
/* free up his parsing allocations */
lws_header_table_detach(wsi, 0);
@@ -505,36 +526,41 @@
if (lws_hdr_total_length(wsi, WSI_TOKEN_ACCEPT) == 0) {
lwsl_info("no ACCEPT\n");
- isErrorCodeReceived = 1;
+ cce = "HS: ACCEPT missing";
goto bail3;
}
if (p && strncmp(p, "101", 3)) {
lwsl_warn(
"lws_client_handshake: got bad HTTP response '%s'\n", p);
+ cce = "HS: ws upgrade response not 101";
goto bail3;
}
p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE);
if (!p) {
lwsl_info("no UPGRADE\n");
+ cce = "HS: UPGRADE missing";
goto bail3;
}
strtolower(p);
if (strcmp(p, "websocket")) {
lwsl_warn(
"lws_client_handshake: got bad Upgrade header '%s'\n", p);
+ cce = "HS: Upgrade to something other than websocket";
goto bail3;
}
p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_CONNECTION);
if (!p) {
lwsl_info("no Connection hdr\n");
+ cce = "HS: CONNECTION missing";
goto bail3;
}
strtolower(p);
if (strcmp(p, "upgrade")) {
lwsl_warn("lws_client_int_s_hs: bad header %s\n", p);
+ cce = "HS: UPGRADE malformed";
goto bail3;
}
@@ -577,6 +603,7 @@
if (!okay) {
lwsl_err("lws_client_int_s_hs: got bad protocol %s\n", p);
+ cce = "HS: PROTOCOL malformed";
goto bail2;
}
@@ -595,6 +622,7 @@
if (wsi->protocol == NULL) {
lwsl_err("lws_client_int_s_hs: fail protocol %s\n", p);
+ cce = "HS: Cannot match protocol";
goto bail2;
}
@@ -615,6 +643,7 @@
if (lws_hdr_copy(wsi, sb, context->pt_serv_buf_size, WSI_TOKEN_EXTENSIONS) < 0) {
lwsl_warn("ext list from server failed to copy\n");
+ cce = "HS: EXT: list too big";
goto bail2;
}
@@ -688,8 +717,10 @@
if (user_callback_handle_rxflow(wsi->protocol->callback,
wsi, LWS_CALLBACK_WS_EXT_DEFAULTS,
(char *)ext->name, ext_name,
- sizeof(ext_name)))
+ sizeof(ext_name))) {
+ cce = "HS: EXT: failed setting defaults";
goto bail2;
+ }
if (ext_name[0] &&
lws_ext_parse_options(ext, wsi, wsi->act_ext_user[
@@ -697,6 +728,7 @@
strlen(ext_name))) {
lwsl_err("%s: unable to parse user defaults '%s'",
__func__, ext_name);
+ cce = "HS: EXT: failed parsing defaults";
goto bail2;
}
@@ -708,6 +740,7 @@
opts, a, c - a)) {
lwsl_err("%s: unable to parse remote def '%s'",
__func__, a);
+ cce = "HS: EXT: failed parsing options";
goto bail2;
}
@@ -717,6 +750,7 @@
NULL, 0)) {
lwsl_err("%s: ext %s rejects server options %s",
ext->name, a);
+ cce = "HS: EXT: Rejects server options";
goto bail2;
}
@@ -727,6 +761,7 @@
if (n == 0) {
lwsl_warn("Unknown ext '%s'!\n", ext_name);
+ cce = "HS: EXT: unknown ext";
goto bail2;
}
@@ -745,12 +780,14 @@
if (strcmp(p, wsi->u.hdr.ah->initial_handshake_hash_base64)) {
lwsl_warn("lws_client_int_s_hs: accept '%s' wrong vs '%s'\n", p,
wsi->u.hdr.ah->initial_handshake_hash_base64);
+ cce = "HS: Accept hash wrong";
goto bail2;
}
/* allocate the per-connection user memory (if any) */
if (lws_ensure_user_space(wsi)) {
lwsl_err("Problem allocating wsi user mem\n");
+ cce = "HS: OOM";
goto bail2;
}
@@ -759,8 +796,10 @@
* headers and OK it
*/
if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH,
- wsi->user_space, NULL, 0))
+ wsi->user_space, NULL, 0)) {
+ cce = "HS: Rejected by filter cb";
goto bail2;
+ }
/* clear his proxy connection timeout */
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
@@ -785,6 +824,7 @@
wsi->u.ws.rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */);
if (!wsi->u.ws.rx_ubuf) {
lwsl_err("Out of Mem allocating rx buffer %d\n", n);
+ cce = "HS: OOM";
goto bail2;
}
wsi->u.ws.rx_ubuf_alloc = n;
@@ -793,6 +833,7 @@
if (setsockopt(wsi->sock, SOL_SOCKET, SO_SNDBUF, (const char *)&n,
sizeof n)) {
lwsl_warn("Failed to set SNDBUF to %d", n);
+ cce = "HS: SO_SNDBUF failed";
goto bail3;
}
@@ -801,8 +842,10 @@
/* call him back to inform him he is up */
if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_ESTABLISHED,
- wsi->user_space, NULL, 0))
+ wsi->user_space, NULL, 0)) {
+ cce = "HS: Rejected at CLIENT_ESTABLISHED";
goto bail3;
+ }
#ifndef LWS_NO_EXTENSIONS
/*
* inform all extensions, not just active ones since they
@@ -828,18 +871,10 @@
close_reason = LWS_CLOSE_STATUS_NOSTATUS;
bail2:
- if (wsi->protocol && wsi->state == LWSS_ESTABLISHED) {
- if (isErrorCodeReceived && p) {
- wsi->protocol->callback(wsi,
- LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
- wsi->user_space, p,
- (unsigned int)strlen(p));
- } else {
- wsi->protocol->callback(wsi,
- LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
- wsi->user_space, NULL, 0);
- }
- }
+ wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
+ wsi->user_space, (void *)cce,
+ (unsigned int) (cce ? strlen(cce): 0));
+ wsi->already_did_cce = 1;
lwsl_info("closing connection due to bail2 connection error\n");
diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c
index df1e18a..586dab3 100755
--- a/lib/libwebsockets.c
+++ b/lib/libwebsockets.c
@@ -480,22 +480,12 @@
lwsl_debug("calling back CLOSED_HTTP\n");
wsi->vhost->protocols->callback(wsi, LWS_CALLBACK_CLOSED_HTTP,
wsi->user_space, NULL, 0 );
- } else if (wsi->mode == LWSCM_WSCL_WAITING_SERVER_REPLY ||
- wsi->mode == LWSCM_WSCL_WAITING_CONNECT) {
- char* errorString;
-
- lwsl_debug("Connection closed before server reply\n");
- errorString = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP);
- if (errorString) {
- wsi->vhost->protocols[0].callback(wsi,
- LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
- wsi->user_space, errorString,
- (unsigned int)strlen(errorString));
- } else {
+ } else if ((wsi->mode == LWSCM_WSCL_WAITING_SERVER_REPLY ||
+ wsi->mode == LWSCM_WSCL_WAITING_CONNECT) &&
+ !wsi->already_did_cce) {
wsi->vhost->protocols[0].callback(wsi,
LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
wsi->user_space, NULL, 0);
- }
} else
lwsl_debug("not calling back closed mode=%d state=%d\n",
wsi->mode, wsi->state_pre_close);
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index 5b69136..e927276 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -576,7 +576,47 @@
LWS_CALLBACK_CLIENT_CONNECTION_ERROR = 1,
/**< the request client connection has been unable to complete a
* handshake with the remote server. If in is non-NULL, you can
- * find an error string of length len where it points to. */
+ * find an error string of length len where it points to
+ *
+ * Diagnostic strings that may be returned include
+ *
+ * "getaddrinfo (ipv6) failed"
+ * "unknown address family"
+ * "getaddrinfo (ipv4) failed"
+ * "set socket opts failed"
+ * "insert wsi failed"
+ * "lws_ssl_client_connect1 failed"
+ * "lws_ssl_client_connect2 failed"
+ * "Peer hung up"
+ * "read failed"
+ * "HS: URI missing"
+ * "HS: Redirect code but no Location"
+ * "HS: URI did not parse"
+ * "HS: Redirect failed"
+ * "HS: Server did not return 200"
+ * "HS: OOM"
+ * "HS: disallowed by client filter"
+ * "HS: disallowed at ESTABLISHED"
+ * "HS: ACCEPT missing"
+ * "HS: ws upgrade response not 101"
+ * "HS: UPGRADE missing"
+ * "HS: Upgrade to something other than websocket"
+ * "HS: CONNECTION missing"
+ * "HS: UPGRADE malformed"
+ * "HS: PROTOCOL malformed"
+ * "HS: Cannot match protocol"
+ * "HS: EXT: list too big"
+ * "HS: EXT: failed setting defaults"
+ * "HS: EXT: failed parsing defaults"
+ * "HS: EXT: failed parsing options"
+ * "HS: EXT: Rejects server options"
+ * "HS: EXT: unknown ext"
+ * "HS: Accept hash wrong"
+ * "HS: Rejected by filter cb"
+ * "HS: OOM"
+ * "HS: SO_SNDBUF failed"
+ * "HS: Rejected at CLIENT_ESTABLISHED"
+ */
LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH = 2,
/**< this is the last chance for the client user code to examine the
* http headers and decide to reject the connection. If the
@@ -1841,6 +1881,17 @@
/**< see uri_replace_from */
struct lws_vhost *vhost;
/**< vhost to bind to (used to determine related SSL_CTX) */
+ struct lws **pwsi;
+ /**< if not NULL, store the new wsi here early in the connection
+ * process. Although we return the new wsi, the call to create the
+ * client connection does progress the connection somewhat and may
+ * meet an error that will result in the connection being scrubbed and
+ * NULL returned. While the wsi exists though, he may process a
+ * callback like CLIENT_CONNECTION_ERROR with his wsi: this gives the
+ * user callback a way to identify which wsi it is that faced the error
+ * even before the new wsi is returned and even if ultimately no wsi
+ * is returned.
+ */
/* Add new things just above here ---^
* This is part of the ABI, don't needlessly break compatibility
@@ -1857,9 +1908,10 @@
* lws_client_connect_via_info() - Connect to another websocket server
* \param ccinfo: pointer to lws_client_connect_info struct
*
- * This function creates a connection to a remote server
+ * This function creates a connection to a remote server using the
+ * information provided in ccinfo.
*/
-LWS_VISIBLE LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT
+LWS_VISIBLE LWS_EXTERN struct lws *
lws_client_connect_via_info(struct lws_client_connect_info * ccinfo);
/**
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index b71b78d..6cdfdd5 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -1259,6 +1259,7 @@
unsigned int cache_intermediaries:1;
unsigned int favoured_pollin:1;
unsigned int sending_chunked:1;
+ unsigned int already_did_cce:1;
#ifdef LWS_WITH_ACCESS_LOG
unsigned int access_log_pending:1;
#endif
diff --git a/lib/service.c b/lib/service.c
index e14709e..8d1a9b3 100644
--- a/lib/service.c
+++ b/lib/service.c
@@ -349,7 +349,7 @@
if (wsi->mode == LWSCM_WSCL_WAITING_SSL)
wsi->vhost->protocols[0].callback(wsi,
LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
- wsi->user_space, NULL, 0);
+ wsi->user_space, (void *)"Timed out waiting SSL", 21);
lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);