int LWS_WARN_UNUSED_RESULT lws_http_transaction_completed_client(struct lws *wsi) { lwsl_debug("%s: wsi %p\n", __func__, wsi); /* if we can't go back to accept new headers, drop the connection */ if (wsi->u.http.connection_type != HTTP_CONNECTION_KEEP_ALIVE) { lwsl_info("%s: %p: close connection\n", __func__, wsi); return 1; } /* we don't support chained client connections yet */ return 1; #if 0 /* otherwise set ourselves up ready to go again */ wsi->state = LWSS_CLIENT_HTTP_ESTABLISHED; wsi->mode = LWSCM_HTTP_CLIENT_ACCEPTED; wsi->u.http.content_length = 0; wsi->hdr_parsing_completed = 0; /* He asked for it to stay alive indefinitely */ lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); /* * As client, nothing new is going to come until we ask for it * we can drop the ah, if any */ if (wsi->u.hdr.ah) { lws_header_table_force_to_detachable_state(wsi); lws_header_table_detach(wsi, 0); } /* If we're (re)starting on headers, need other implied init */ wsi->u.hdr.ues = URIES_IDLE; lwsl_info("%s: %p: keep-alive await new transaction\n", __func__, wsi); return 0; #endif }
struct lws * lws_client_connect_2(struct lws *wsi) { sockaddr46 sa46; struct addrinfo *result; struct lws_context *context = wsi->context; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; struct lws_pollfd pfd; const char *cce = "", *iface; int n, port; ssize_t plen = 0; const char *ads; #ifdef LWS_USE_IPV6 char ipv6only = lws_check_opt(wsi->vhost->options, LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY | LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE); #if defined(__ANDROID__) ipv6only = 0; #endif #endif lwsl_client("%s\n", __func__); if (!wsi->u.hdr.ah) { cce = "ah was NULL at cc2"; lwsl_err("%s\n", cce); goto oom4; } /* * start off allowing ipv6 on connection if vhost allows it */ wsi->ipv6 = LWS_IPV6_ENABLED(wsi->vhost); /* Decide what it is we need to connect to: * * Priority 1: connect to http proxy */ if (wsi->vhost->http_proxy_port) { plen = sprintf((char *)pt->serv_buf, "CONNECT %s:%u HTTP/1.0\x0d\x0a" "User-agent: libwebsockets\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS), wsi->c_port); if (wsi->vhost->proxy_basic_auth_token[0]) plen += sprintf((char *)pt->serv_buf + plen, "Proxy-authorization: basic %s\x0d\x0a", wsi->vhost->proxy_basic_auth_token); plen += sprintf((char *)pt->serv_buf + plen, "\x0d\x0a"); ads = wsi->vhost->http_proxy_address; port = wsi->vhost->http_proxy_port; #if defined(LWS_WITH_SOCKS5) /* Priority 2: Connect to SOCK5 Proxy */ } else if (wsi->vhost->socks_proxy_port) { socks_generate_msg(wsi, SOCKS_MSG_GREETING, &plen); lwsl_client("Sending SOCKS Greeting\n"); ads = wsi->vhost->socks_proxy_address; port = wsi->vhost->socks_proxy_port; #endif } else { /* Priority 3: Connect directly */ ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); port = wsi->c_port; } /* * prepare the actual connection * to whatever we decided to connect to */ lwsl_notice("%s: %p: address %s\n", __func__, wsi, ads); n = lws_getaddrinfo46(wsi, ads, &result); #ifdef LWS_USE_IPV6 if (wsi->ipv6) { if (n) { /* lws_getaddrinfo46 failed, there is no usable result */ lwsl_notice("%s: lws_getaddrinfo46 failed %d\n", __func__, n); cce = "ipv6 lws_getaddrinfo46 failed"; goto oom4; } memset(&sa46, 0, sizeof(sa46)); sa46.sa6.sin6_family = AF_INET6; switch (result->ai_family) { case AF_INET: if (ipv6only) break; /* map IPv4 to IPv6 */ bzero((char *)&sa46.sa6.sin6_addr, sizeof(sa46.sa6.sin6_addr)); sa46.sa6.sin6_addr.s6_addr[10] = 0xff; sa46.sa6.sin6_addr.s6_addr[11] = 0xff; memcpy(&sa46.sa6.sin6_addr.s6_addr[12], &((struct sockaddr_in *)result->ai_addr)->sin_addr, sizeof(struct in_addr)); lwsl_notice("uplevelling AF_INET to AF_INET6\n"); break; case AF_INET6: memcpy(&sa46.sa6.sin6_addr, &((struct sockaddr_in6 *)result->ai_addr)->sin6_addr, sizeof(struct in6_addr)); sa46.sa6.sin6_scope_id = ((struct sockaddr_in6 *)result->ai_addr)->sin6_scope_id; sa46.sa6.sin6_flowinfo = ((struct sockaddr_in6 *)result->ai_addr)->sin6_flowinfo; break; default: lwsl_err("Unknown address family\n"); freeaddrinfo(result); cce = "unknown address family"; goto oom4; } } else #endif /* use ipv6 */ /* use ipv4 */ { void *p = NULL; if (!n) { struct addrinfo *res = result; /* pick the first AF_INET (IPv4) 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 defined(LWS_FALLBACK_GETHOSTBYNAME) } else if (n == EAI_SYSTEM) { struct hostent *host; lwsl_info("getaddrinfo (ipv4) failed, trying gethostbyname\n"); host = gethostbyname(ads); if (host) { p = host->h_addr; } else { lwsl_err("gethostbyname failed\n"); cce = "gethostbyname (ipv4) failed"; goto oom4; } #endif } else { lwsl_err("getaddrinfo failed\n"); cce = "getaddrinfo failed"; goto oom4; } if (!p) { if (result) freeaddrinfo(result); lwsl_err("Couldn't identify address\n"); cce = "unable to lookup address"; goto oom4; } sa46.sa4.sin_family = AF_INET; sa46.sa4.sin_addr = *((struct in_addr *)p); bzero(&sa46.sa4.sin_zero, 8); } if (result) freeaddrinfo(result); /* now we decided on ipv4 or ipv6, set the port */ if (!lws_socket_is_valid(wsi->desc.sockfd)) { #if defined(LWS_USE_LIBUV) if (LWS_LIBUV_ENABLED(context)) if (lws_libuv_check_watcher_active(wsi)) { lwsl_warn("Waiting for libuv watcher to close\n"); cce = "waiting for libuv watcher to close"; goto oom4; } #endif #ifdef LWS_USE_IPV6 if (wsi->ipv6) wsi->desc.sockfd = socket(AF_INET6, SOCK_STREAM, 0); else #endif wsi->desc.sockfd = socket(AF_INET, SOCK_STREAM, 0); if (!lws_socket_is_valid(wsi->desc.sockfd)) { lwsl_warn("Unable to open socket\n"); cce = "unable to open socket"; goto oom4; } if (lws_plat_set_socket_options(wsi->vhost, wsi->desc.sockfd)) { lwsl_err("Failed to set wsi socket options\n"); compatible_close(wsi->desc.sockfd); cce = "set socket opts failed"; goto oom4; } wsi->mode = LWSCM_WSCL_WAITING_CONNECT; lws_libev_accept(wsi, wsi->desc); lws_libuv_accept(wsi, wsi->desc); lws_libevent_accept(wsi, wsi->desc); if (insert_wsi_socket_into_fds(context, wsi)) { compatible_close(wsi->desc.sockfd); cce = "insert wsi failed"; goto oom4; } lws_change_pollfd(wsi, 0, LWS_POLLIN); /* * past here, we can't simply free the structs as error * handling as oom4 does. We have to run the whole close flow. */ if (!wsi->protocol) wsi->protocol = &wsi->vhost->protocols[0]; wsi->protocol->callback(wsi, LWS_CALLBACK_WSI_CREATE, wsi->user_space, NULL, 0); lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE, AWAITING_TIMEOUT); iface = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE); if (iface) { n = lws_socket_bind(wsi->vhost, wsi->desc.sockfd, 0, iface); if (n < 0) { cce = "unable to bind socket"; goto failed; } } } #ifdef LWS_USE_IPV6 if (wsi->ipv6) { sa46.sa6.sin6_port = htons(port); n = sizeof(struct sockaddr_in6); } else #endif { sa46.sa4.sin_port = htons(port); n = sizeof(struct sockaddr); } if (connect(wsi->desc.sockfd, (const struct sockaddr *)&sa46, 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 (errno = %d)\n", LWS_ERRNO); if (lws_plat_check_connection_error(wsi)) { cce = "socket connect failed"; goto failed; } /* * must do specifically a POLLOUT poll to hear * about the connect completion */ if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) { cce = "POLLOUT set failed"; goto failed; } return wsi; } if (LWS_ERRNO != LWS_EISCONN) { lwsl_notice("Connect failed errno=%d\n", LWS_ERRNO); cce = "connect failed"; goto failed; } } lwsl_client("connected\n"); /* we are connected to server, or proxy */ /* http proxy */ if (wsi->vhost->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, wsi->vhost->http_proxy_address)) goto failed; wsi->c_port = wsi->vhost->http_proxy_port; n = send(wsi->desc.sockfd, (char *)pt->serv_buf, plen, MSG_NOSIGNAL); if (n < 0) { lwsl_debug("ERROR writing to proxy socket\n"); cce = "proxy write failed"; goto failed; } lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE, AWAITING_TIMEOUT); wsi->mode = LWSCM_WSCL_WAITING_PROXY_REPLY; return wsi; } #if defined(LWS_WITH_SOCKS5) /* socks proxy */ else if (wsi->vhost->socks_proxy_port) { n = send(wsi->desc.sockfd, (char *)pt->serv_buf, plen, MSG_NOSIGNAL); if (n < 0) { lwsl_debug("ERROR writing socks greeting\n"); cce = "socks write failed"; goto failed; } lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SOCKS_GREETING_REPLY, AWAITING_TIMEOUT); wsi->mode = LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY; return wsi; } #endif /* * 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. */ lws_set_timeout(wsi, PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE, AWAITING_TIMEOUT); wsi->mode = LWSCM_WSCL_ISSUE_HANDSHAKE; pfd.fd = wsi->desc.sockfd; pfd.events = LWS_POLLIN; pfd.revents = LWS_POLLIN; n = lws_service_fd(context, &pfd); if (n < 0) { cce = "first service failed"; goto failed; } if (n) /* returns 1 on failure after closing wsi */ return NULL; return wsi; oom4: /* we're closing, losing some rx is OK */ lws_header_table_force_to_detachable_state(wsi); if (wsi->mode == LWSCM_HTTP_CLIENT || wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED || wsi->mode == LWSCM_WSCL_WAITING_CONNECT) { wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, 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 failed1; lws_remove_from_timeout_list(wsi); lws_header_table_detach(wsi, 0); lws_free(wsi); return NULL; failed: wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, (void *)cce, strlen(cce)); wsi->already_did_cce = 1; failed1: lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return NULL; }
char * lws_generate_client_handshake(struct lws *wsi, char *pkt) { char buf[128], hash[20], key_b64[40], *p = pkt; struct lws_context *context = wsi->context; const char *meth; int n; #ifndef LWS_NO_EXTENSIONS const struct lws_extension *ext; int ext_count = 0; #endif const char *pp = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD); if (!meth) { meth = "GET"; wsi->do_ws = 1; } else { wsi->do_ws = 0; } if (!strcmp(meth, "RAW")) { lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); lwsl_notice("client transition to raw\n"); if (pp) { const struct lws_protocols *pr; pr = lws_vhost_name_to_protocol(wsi->vhost, pp); if (!pr) { lwsl_err("protocol %s not enabled on vhost\n", pp); return NULL; } lws_bind_protocol(wsi, pr); } if ((wsi->protocol->callback)(wsi, LWS_CALLBACK_RAW_ADOPT, wsi->user_space, NULL, 0)) return NULL; lws_header_table_force_to_detachable_state(wsi); lws_union_transition(wsi, LWSCM_RAW); lws_header_table_detach(wsi, 1); return NULL; } if (wsi->do_ws) { /* * create the random key */ n = lws_get_random(context, hash, 16); if (n != 16) { lwsl_err("Unable to read from random dev %s\n", SYSTEM_RANDOM_FILEPATH); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return NULL; } lws_b64_encode_string(hash, 16, key_b64, sizeof(key_b64)); } /* * 04 example client handshake * * GET /chat HTTP/1.1 * Host: server.example.com * Upgrade: websocket * Connection: Upgrade * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== * Sec-WebSocket-Origin: http://example.com * Sec-WebSocket-Protocol: chat, superchat * Sec-WebSocket-Version: 4 */ p += sprintf(p, "%s %s HTTP/1.1\x0d\x0a", meth, lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI)); p += sprintf(p, "Pragma: no-cache\x0d\x0a" "Cache-Control: no-cache\x0d\x0a"); p += sprintf(p, "Host: %s\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST)); if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)) { if (lws_check_opt(context->options, LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN)) p += sprintf(p, "Origin: %s\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)); else p += sprintf(p, "Origin: http://%s\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)); } if (wsi->do_ws) { p += sprintf(p, "Upgrade: websocket\x0d\x0a" "Connection: Upgrade\x0d\x0a" "Sec-WebSocket-Key: "); strcpy(p, key_b64); p += strlen(key_b64); p += sprintf(p, "\x0d\x0a"); if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS)) p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS)); /* tell the server what extensions we could support */ #ifndef LWS_NO_EXTENSIONS ext = wsi->vhost->extensions; while (ext && ext->callback) { n = lws_ext_cb_all_exts(context, wsi, LWS_EXT_CB_CHECK_OK_TO_PROPOSE_EXTENSION, (char *)ext->name, 0); if (n) { /* an extension vetos us */ lwsl_ext("ext %s vetoed\n", (char *)ext->name); ext++; continue; } n = wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED, wsi->user_space, (char *)ext->name, 0); /* * zero return from callback means * go ahead and allow the extension, * it's what we get if the callback is * unhandled */ if (n) { ext++; continue; } /* apply it */ if (ext_count) *p++ = ','; else p += sprintf(p, "Sec-WebSocket-Extensions: "); p += sprintf(p, "%s", ext->client_offer); ext_count++; ext++; } if (ext_count) p += sprintf(p, "\x0d\x0a"); #endif if (wsi->ietf_spec_revision) p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a", wsi->ietf_spec_revision); /* prepare the expected server accept response */ key_b64[39] = '\0'; /* enforce composed length below buf sizeof */ n = sprintf(buf, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", key_b64); lws_SHA1((unsigned char *)buf, n, (unsigned char *)hash); lws_b64_encode_string(hash, 20, wsi->u.hdr.ah->initial_handshake_hash_base64, sizeof(wsi->u.hdr.ah->initial_handshake_hash_base64)); } /* give userland a chance to append, eg, cookies */ wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, wsi->user_space, &p, (pkt + context->pt_serv_buf_size) - p - 12); p += sprintf(p, "\x0d\x0a"); return p; }