11131Smax.romanov@nginx.com 21131Smax.romanov@nginx.com /* 31131Smax.romanov@nginx.com * Copyright (C) NGINX, Inc. 41131Smax.romanov@nginx.com */ 51131Smax.romanov@nginx.com 61131Smax.romanov@nginx.com #include <nxt_main.h> 71131Smax.romanov@nginx.com #include <nxt_router.h> 81131Smax.romanov@nginx.com #include <nxt_http.h> 91131Smax.romanov@nginx.com #include <nxt_h1proto.h> 101131Smax.romanov@nginx.com #include <nxt_websocket.h> 111131Smax.romanov@nginx.com #include <nxt_websocket_header.h> 121131Smax.romanov@nginx.com 131131Smax.romanov@nginx.com typedef struct { 141131Smax.romanov@nginx.com uint16_t code; 151131Smax.romanov@nginx.com uint8_t args; 161131Smax.romanov@nginx.com nxt_str_t desc; 171131Smax.romanov@nginx.com } nxt_ws_error_t; 181131Smax.romanov@nginx.com 191131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_keepalive(nxt_task_t *task, void *obj, void *data); 201131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_frame_header_read(nxt_task_t *task, void *obj, 211131Smax.romanov@nginx.com void *data); 221131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_keepalive_disable(nxt_task_t *task, 231131Smax.romanov@nginx.com nxt_h1proto_t *h1p); 241131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_keepalive_enable(nxt_task_t *task, 251131Smax.romanov@nginx.com nxt_h1proto_t *h1p); 261131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_frame_process(nxt_task_t *task, nxt_conn_t *c, 271131Smax.romanov@nginx.com nxt_h1proto_t *h1p, nxt_websocket_header_t *wsh); 281131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_error(nxt_task_t *task, void *obj, void *data); 291268Sigor@sysoev.ru static ssize_t nxt_h1p_ws_io_read_handler(nxt_task_t *task, nxt_conn_t *c); 301131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_timeout(nxt_task_t *task, void *obj, void *data); 311131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_frame_payload_read(nxt_task_t *task, void *obj, 321131Smax.romanov@nginx.com void *data); 331131Smax.romanov@nginx.com static void hxt_h1p_send_ws_error(nxt_task_t *task, nxt_http_request_t *r, 341131Smax.romanov@nginx.com const nxt_ws_error_t *err, ...); 351131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_error_sent(nxt_task_t *task, void *obj, void *data); 361131Smax.romanov@nginx.com static void nxt_h1p_conn_ws_pong(nxt_task_t *task, void *obj, void *data); 371131Smax.romanov@nginx.com 381131Smax.romanov@nginx.com static const nxt_conn_state_t nxt_h1p_read_ws_frame_header_state; 391131Smax.romanov@nginx.com static const nxt_conn_state_t nxt_h1p_read_ws_frame_payload_state; 401131Smax.romanov@nginx.com 411131Smax.romanov@nginx.com static const nxt_ws_error_t nxt_ws_err_out_of_memory = { 421131Smax.romanov@nginx.com NXT_WEBSOCKET_CR_INTERNAL_SERVER_ERROR, 431131Smax.romanov@nginx.com 0, nxt_string("Out of memory") }; 441131Smax.romanov@nginx.com static const nxt_ws_error_t nxt_ws_err_too_big = { 451131Smax.romanov@nginx.com NXT_WEBSOCKET_CR_MESSAGE_TOO_BIG, 461131Smax.romanov@nginx.com 1, nxt_string("Message too big: %uL bytes") }; 471131Smax.romanov@nginx.com static const nxt_ws_error_t nxt_ws_err_invalid_close_code = { 481131Smax.romanov@nginx.com NXT_WEBSOCKET_CR_PROTOCOL_ERROR, 491131Smax.romanov@nginx.com 1, nxt_string("Close code %ud is not valid") }; 501131Smax.romanov@nginx.com static const nxt_ws_error_t nxt_ws_err_going_away = { 511131Smax.romanov@nginx.com NXT_WEBSOCKET_CR_GOING_AWAY, 521131Smax.romanov@nginx.com 0, nxt_string("Remote peer is going away") }; 531131Smax.romanov@nginx.com static const nxt_ws_error_t nxt_ws_err_not_masked = { 541131Smax.romanov@nginx.com NXT_WEBSOCKET_CR_PROTOCOL_ERROR, 551131Smax.romanov@nginx.com 0, nxt_string("Not masked client frame") }; 561131Smax.romanov@nginx.com static const nxt_ws_error_t nxt_ws_err_ctrl_fragmented = { 571131Smax.romanov@nginx.com NXT_WEBSOCKET_CR_PROTOCOL_ERROR, 581131Smax.romanov@nginx.com 0, nxt_string("Fragmented control frame") }; 591131Smax.romanov@nginx.com static const nxt_ws_error_t nxt_ws_err_ctrl_too_big = { 601131Smax.romanov@nginx.com NXT_WEBSOCKET_CR_PROTOCOL_ERROR, 611131Smax.romanov@nginx.com 1, nxt_string("Control frame too big: %uL bytes") }; 621131Smax.romanov@nginx.com static const nxt_ws_error_t nxt_ws_err_invalid_close_len = { 631131Smax.romanov@nginx.com NXT_WEBSOCKET_CR_PROTOCOL_ERROR, 641131Smax.romanov@nginx.com 0, nxt_string("Close frame payload length cannot be 1") }; 651131Smax.romanov@nginx.com static const nxt_ws_error_t nxt_ws_err_invalid_opcode = { 661131Smax.romanov@nginx.com NXT_WEBSOCKET_CR_PROTOCOL_ERROR, 671131Smax.romanov@nginx.com 1, nxt_string("Unrecognized opcode %ud") }; 681131Smax.romanov@nginx.com static const nxt_ws_error_t nxt_ws_err_cont_expected = { 691131Smax.romanov@nginx.com NXT_WEBSOCKET_CR_PROTOCOL_ERROR, 701131Smax.romanov@nginx.com 1, nxt_string("Continuation expected, but %ud opcode received") }; 711131Smax.romanov@nginx.com 721131Smax.romanov@nginx.com void 731131Smax.romanov@nginx.com nxt_h1p_websocket_first_frame_start(nxt_task_t *task, nxt_http_request_t *r, 741131Smax.romanov@nginx.com nxt_buf_t *ws_frame) 751131Smax.romanov@nginx.com { 76*1956Smax.romanov@nginx.com nxt_conn_t *c; 77*1956Smax.romanov@nginx.com nxt_timer_t *timer; 78*1956Smax.romanov@nginx.com nxt_h1proto_t *h1p; 79*1956Smax.romanov@nginx.com nxt_websocket_conf_t *websocket_conf; 801131Smax.romanov@nginx.com 811131Smax.romanov@nginx.com nxt_debug(task, "h1p ws first frame start"); 821131Smax.romanov@nginx.com 831131Smax.romanov@nginx.com h1p = r->proto.h1; 841131Smax.romanov@nginx.com c = h1p->conn; 851131Smax.romanov@nginx.com 861131Smax.romanov@nginx.com if (!c->tcp_nodelay) { 871131Smax.romanov@nginx.com nxt_conn_tcp_nodelay_on(task, c); 881131Smax.romanov@nginx.com } 891131Smax.romanov@nginx.com 90*1956Smax.romanov@nginx.com websocket_conf = &r->conf->socket_conf->websocket_conf; 911131Smax.romanov@nginx.com 92*1956Smax.romanov@nginx.com if (nxt_slow_path(websocket_conf->keepalive_interval != 0)) { 931131Smax.romanov@nginx.com h1p->websocket_timer = nxt_mp_zget(c->mem_pool, 941131Smax.romanov@nginx.com sizeof(nxt_h1p_websocket_timer_t)); 951131Smax.romanov@nginx.com if (nxt_slow_path(h1p->websocket_timer == NULL)) { 961131Smax.romanov@nginx.com hxt_h1p_send_ws_error(task, r, &nxt_ws_err_out_of_memory); 971131Smax.romanov@nginx.com return; 981131Smax.romanov@nginx.com } 991131Smax.romanov@nginx.com 1001131Smax.romanov@nginx.com h1p->websocket_timer->keepalive_interval = 101*1956Smax.romanov@nginx.com websocket_conf->keepalive_interval; 1021131Smax.romanov@nginx.com h1p->websocket_timer->h1p = h1p; 1031131Smax.romanov@nginx.com 1041131Smax.romanov@nginx.com timer = &h1p->websocket_timer->timer; 1051131Smax.romanov@nginx.com timer->task = &c->task; 1061131Smax.romanov@nginx.com timer->work_queue = &task->thread->engine->fast_work_queue; 1071131Smax.romanov@nginx.com timer->log = &c->log; 1081131Smax.romanov@nginx.com timer->bias = NXT_TIMER_DEFAULT_BIAS; 1091131Smax.romanov@nginx.com timer->handler = nxt_h1p_conn_ws_keepalive; 1101131Smax.romanov@nginx.com } 1111131Smax.romanov@nginx.com 1121131Smax.romanov@nginx.com nxt_h1p_websocket_frame_start(task, r, ws_frame); 1131131Smax.romanov@nginx.com } 1141131Smax.romanov@nginx.com 1151131Smax.romanov@nginx.com 1161131Smax.romanov@nginx.com void 1171131Smax.romanov@nginx.com nxt_h1p_websocket_frame_start(nxt_task_t *task, nxt_http_request_t *r, 1181131Smax.romanov@nginx.com nxt_buf_t *ws_frame) 1191131Smax.romanov@nginx.com { 1201131Smax.romanov@nginx.com size_t size; 1211131Smax.romanov@nginx.com nxt_buf_t *in; 1221131Smax.romanov@nginx.com nxt_conn_t *c; 1231131Smax.romanov@nginx.com nxt_h1proto_t *h1p; 1241131Smax.romanov@nginx.com 1251131Smax.romanov@nginx.com nxt_debug(task, "h1p ws frame start"); 1261131Smax.romanov@nginx.com 1271131Smax.romanov@nginx.com h1p = r->proto.h1; 1281131Smax.romanov@nginx.com 1291131Smax.romanov@nginx.com if (nxt_slow_path(h1p->websocket_closed)) { 1301131Smax.romanov@nginx.com return; 1311131Smax.romanov@nginx.com } 1321131Smax.romanov@nginx.com 1331131Smax.romanov@nginx.com c = h1p->conn; 1341131Smax.romanov@nginx.com c->read = ws_frame; 1351131Smax.romanov@nginx.com 1361417Smax.romanov@nginx.com nxt_h1p_complete_buffers(task, h1p, 0); 1371131Smax.romanov@nginx.com 1381131Smax.romanov@nginx.com in = c->read; 1391131Smax.romanov@nginx.com c->read_state = &nxt_h1p_read_ws_frame_header_state; 1401131Smax.romanov@nginx.com 1411131Smax.romanov@nginx.com if (in == NULL) { 1421131Smax.romanov@nginx.com nxt_conn_read(task->thread->engine, c); 1431131Smax.romanov@nginx.com nxt_h1p_conn_ws_keepalive_enable(task, h1p); 1441131Smax.romanov@nginx.com 1451131Smax.romanov@nginx.com } else { 1461131Smax.romanov@nginx.com size = nxt_buf_mem_used_size(&in->mem); 1471131Smax.romanov@nginx.com 1481131Smax.romanov@nginx.com nxt_debug(task, "h1p read client ws frame"); 1491131Smax.romanov@nginx.com 1501131Smax.romanov@nginx.com nxt_memmove(in->mem.start, in->mem.pos, size); 1511131Smax.romanov@nginx.com 1521131Smax.romanov@nginx.com in->mem.pos = in->mem.start; 1531131Smax.romanov@nginx.com in->mem.free = in->mem.start + size; 1541131Smax.romanov@nginx.com 1551131Smax.romanov@nginx.com nxt_h1p_conn_ws_frame_header_read(task, c, h1p); 1561131Smax.romanov@nginx.com } 1571131Smax.romanov@nginx.com } 1581131Smax.romanov@nginx.com 1591131Smax.romanov@nginx.com 1601131Smax.romanov@nginx.com static void 1611131Smax.romanov@nginx.com nxt_h1p_conn_ws_keepalive(nxt_task_t *task, void *obj, void *data) 1621131Smax.romanov@nginx.com { 1631131Smax.romanov@nginx.com nxt_buf_t *out; 1641131Smax.romanov@nginx.com nxt_timer_t *timer; 1651131Smax.romanov@nginx.com nxt_h1proto_t *h1p; 1661131Smax.romanov@nginx.com nxt_http_request_t *r; 1671131Smax.romanov@nginx.com nxt_websocket_header_t *wsh; 1681131Smax.romanov@nginx.com nxt_h1p_websocket_timer_t *ws_timer; 1691131Smax.romanov@nginx.com 1701131Smax.romanov@nginx.com nxt_debug(task, "h1p conn ws keepalive"); 1711131Smax.romanov@nginx.com 1721131Smax.romanov@nginx.com timer = obj; 1731131Smax.romanov@nginx.com ws_timer = nxt_timer_data(timer, nxt_h1p_websocket_timer_t, timer); 1741131Smax.romanov@nginx.com h1p = ws_timer->h1p; 1751131Smax.romanov@nginx.com 1761131Smax.romanov@nginx.com r = h1p->request; 1771131Smax.romanov@nginx.com if (nxt_slow_path(r == NULL)) { 1781131Smax.romanov@nginx.com return; 1791131Smax.romanov@nginx.com } 1801131Smax.romanov@nginx.com 1811131Smax.romanov@nginx.com out = nxt_http_buf_mem(task, r, 2); 1821131Smax.romanov@nginx.com if (nxt_slow_path(out == NULL)) { 1831131Smax.romanov@nginx.com nxt_http_request_error_handler(task, r, r->proto.any); 1841131Smax.romanov@nginx.com return; 1851131Smax.romanov@nginx.com } 1861131Smax.romanov@nginx.com 1871131Smax.romanov@nginx.com out->mem.start[0] = 0; 1881131Smax.romanov@nginx.com out->mem.start[1] = 0; 1891131Smax.romanov@nginx.com 1901131Smax.romanov@nginx.com wsh = (nxt_websocket_header_t *) out->mem.start; 1911131Smax.romanov@nginx.com out->mem.free = nxt_websocket_frame_init(wsh, 0); 1921131Smax.romanov@nginx.com 1931131Smax.romanov@nginx.com wsh->fin = 1; 1941131Smax.romanov@nginx.com wsh->opcode = NXT_WEBSOCKET_OP_PING; 1951131Smax.romanov@nginx.com 1961131Smax.romanov@nginx.com nxt_http_request_send(task, r, out); 1971131Smax.romanov@nginx.com } 1981131Smax.romanov@nginx.com 1991131Smax.romanov@nginx.com 2001131Smax.romanov@nginx.com static const nxt_conn_state_t nxt_h1p_read_ws_frame_header_state 2011131Smax.romanov@nginx.com nxt_aligned(64) = 2021131Smax.romanov@nginx.com { 2031131Smax.romanov@nginx.com .ready_handler = nxt_h1p_conn_ws_frame_header_read, 2041131Smax.romanov@nginx.com .close_handler = nxt_h1p_conn_ws_error, 2051131Smax.romanov@nginx.com .error_handler = nxt_h1p_conn_ws_error, 2061131Smax.romanov@nginx.com 2071131Smax.romanov@nginx.com .io_read_handler = nxt_h1p_ws_io_read_handler, 2081131Smax.romanov@nginx.com 2091131Smax.romanov@nginx.com .timer_handler = nxt_h1p_conn_ws_timeout, 2101131Smax.romanov@nginx.com .timer_value = nxt_h1p_conn_request_timer_value, 2111131Smax.romanov@nginx.com .timer_data = offsetof(nxt_socket_conf_t, websocket_conf.read_timeout), 2121131Smax.romanov@nginx.com .timer_autoreset = 1, 2131131Smax.romanov@nginx.com }; 2141131Smax.romanov@nginx.com 2151131Smax.romanov@nginx.com 2161131Smax.romanov@nginx.com static void 2171131Smax.romanov@nginx.com nxt_h1p_conn_ws_frame_header_read(nxt_task_t *task, void *obj, void *data) 2181131Smax.romanov@nginx.com { 219*1956Smax.romanov@nginx.com size_t size, hsize, frame_size, max_frame_size; 220*1956Smax.romanov@nginx.com uint64_t payload_len; 221*1956Smax.romanov@nginx.com nxt_conn_t *c; 222*1956Smax.romanov@nginx.com nxt_h1proto_t *h1p; 223*1956Smax.romanov@nginx.com nxt_http_request_t *r; 224*1956Smax.romanov@nginx.com nxt_event_engine_t *engine; 225*1956Smax.romanov@nginx.com nxt_websocket_header_t *wsh; 2261131Smax.romanov@nginx.com 2271131Smax.romanov@nginx.com c = obj; 2281131Smax.romanov@nginx.com h1p = data; 2291131Smax.romanov@nginx.com 2301131Smax.romanov@nginx.com nxt_h1p_conn_ws_keepalive_disable(task, h1p); 2311131Smax.romanov@nginx.com 2321131Smax.romanov@nginx.com size = nxt_buf_mem_used_size(&c->read->mem); 2331131Smax.romanov@nginx.com 2341131Smax.romanov@nginx.com engine = task->thread->engine; 2351131Smax.romanov@nginx.com 2361131Smax.romanov@nginx.com if (size < 2) { 2371131Smax.romanov@nginx.com nxt_debug(task, "h1p conn ws frame header read %z", size); 2381131Smax.romanov@nginx.com 2391131Smax.romanov@nginx.com nxt_conn_read(engine, c); 2401131Smax.romanov@nginx.com nxt_h1p_conn_ws_keepalive_enable(task, h1p); 2411131Smax.romanov@nginx.com 2421131Smax.romanov@nginx.com return; 2431131Smax.romanov@nginx.com } 2441131Smax.romanov@nginx.com 2451131Smax.romanov@nginx.com wsh = (nxt_websocket_header_t *) c->read->mem.pos; 2461131Smax.romanov@nginx.com 2471131Smax.romanov@nginx.com hsize = nxt_websocket_frame_header_size(wsh); 2481131Smax.romanov@nginx.com 2491131Smax.romanov@nginx.com if (size < hsize) { 2501131Smax.romanov@nginx.com nxt_debug(task, "h1p conn ws frame header read %z < %z", size, hsize); 2511131Smax.romanov@nginx.com 2521131Smax.romanov@nginx.com nxt_conn_read(engine, c); 2531131Smax.romanov@nginx.com nxt_h1p_conn_ws_keepalive_enable(task, h1p); 2541131Smax.romanov@nginx.com 2551131Smax.romanov@nginx.com return; 2561131Smax.romanov@nginx.com } 2571131Smax.romanov@nginx.com 2581131Smax.romanov@nginx.com r = h1p->request; 2591131Smax.romanov@nginx.com if (nxt_slow_path(r == NULL)) { 2601131Smax.romanov@nginx.com return; 2611131Smax.romanov@nginx.com } 2621131Smax.romanov@nginx.com 2631131Smax.romanov@nginx.com r->ws_frame = c->read; 2641131Smax.romanov@nginx.com 2651131Smax.romanov@nginx.com if (nxt_slow_path(wsh->mask == 0)) { 2661131Smax.romanov@nginx.com hxt_h1p_send_ws_error(task, r, &nxt_ws_err_not_masked); 2671131Smax.romanov@nginx.com return; 2681131Smax.romanov@nginx.com } 2691131Smax.romanov@nginx.com 2701131Smax.romanov@nginx.com if ((wsh->opcode & NXT_WEBSOCKET_OP_CTRL) != 0) { 2711131Smax.romanov@nginx.com if (nxt_slow_path(wsh->fin == 0)) { 2721131Smax.romanov@nginx.com hxt_h1p_send_ws_error(task, r, &nxt_ws_err_ctrl_fragmented); 2731131Smax.romanov@nginx.com return; 2741131Smax.romanov@nginx.com } 2751131Smax.romanov@nginx.com 2761131Smax.romanov@nginx.com if (nxt_slow_path(wsh->opcode != NXT_WEBSOCKET_OP_PING 2771131Smax.romanov@nginx.com && wsh->opcode != NXT_WEBSOCKET_OP_PONG 2781131Smax.romanov@nginx.com && wsh->opcode != NXT_WEBSOCKET_OP_CLOSE)) 2791131Smax.romanov@nginx.com { 2801131Smax.romanov@nginx.com hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_opcode, 2811131Smax.romanov@nginx.com wsh->opcode); 2821131Smax.romanov@nginx.com return; 2831131Smax.romanov@nginx.com } 2841131Smax.romanov@nginx.com 2851131Smax.romanov@nginx.com if (nxt_slow_path(wsh->payload_len > 125)) { 2861131Smax.romanov@nginx.com hxt_h1p_send_ws_error(task, r, &nxt_ws_err_ctrl_too_big, 2871131Smax.romanov@nginx.com nxt_websocket_frame_payload_len(wsh)); 2881131Smax.romanov@nginx.com return; 2891131Smax.romanov@nginx.com } 2901131Smax.romanov@nginx.com 2911131Smax.romanov@nginx.com if (nxt_slow_path(wsh->opcode == NXT_WEBSOCKET_OP_CLOSE 2921131Smax.romanov@nginx.com && wsh->payload_len == 1)) 2931131Smax.romanov@nginx.com { 2941131Smax.romanov@nginx.com hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_close_len); 2951131Smax.romanov@nginx.com return; 2961131Smax.romanov@nginx.com } 2971131Smax.romanov@nginx.com 2981131Smax.romanov@nginx.com } else { 2991131Smax.romanov@nginx.com if (h1p->websocket_cont_expected) { 3001131Smax.romanov@nginx.com if (nxt_slow_path(wsh->opcode != NXT_WEBSOCKET_OP_CONT)) { 3011131Smax.romanov@nginx.com hxt_h1p_send_ws_error(task, r, &nxt_ws_err_cont_expected, 3021131Smax.romanov@nginx.com wsh->opcode); 3031131Smax.romanov@nginx.com return; 3041131Smax.romanov@nginx.com } 3051131Smax.romanov@nginx.com 3061131Smax.romanov@nginx.com } else { 3071131Smax.romanov@nginx.com if (nxt_slow_path(wsh->opcode != NXT_WEBSOCKET_OP_BINARY 3081131Smax.romanov@nginx.com && wsh->opcode != NXT_WEBSOCKET_OP_TEXT)) 3091131Smax.romanov@nginx.com { 3101131Smax.romanov@nginx.com hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_opcode, 3111131Smax.romanov@nginx.com wsh->opcode); 3121131Smax.romanov@nginx.com return; 3131131Smax.romanov@nginx.com } 3141131Smax.romanov@nginx.com } 3151131Smax.romanov@nginx.com 3161131Smax.romanov@nginx.com h1p->websocket_cont_expected = !wsh->fin; 3171131Smax.romanov@nginx.com } 3181131Smax.romanov@nginx.com 319*1956Smax.romanov@nginx.com max_frame_size = r->conf->socket_conf->websocket_conf.max_frame_size; 3201131Smax.romanov@nginx.com 3211131Smax.romanov@nginx.com payload_len = nxt_websocket_frame_payload_len(wsh); 3221131Smax.romanov@nginx.com 3231131Smax.romanov@nginx.com if (nxt_slow_path(hsize > max_frame_size 3241131Smax.romanov@nginx.com || payload_len > (max_frame_size - hsize))) 3251131Smax.romanov@nginx.com { 3261131Smax.romanov@nginx.com hxt_h1p_send_ws_error(task, r, &nxt_ws_err_too_big, payload_len); 3271131Smax.romanov@nginx.com return; 3281131Smax.romanov@nginx.com } 3291131Smax.romanov@nginx.com 3301131Smax.romanov@nginx.com c->read_state = &nxt_h1p_read_ws_frame_payload_state; 3311131Smax.romanov@nginx.com 3321131Smax.romanov@nginx.com frame_size = payload_len + hsize; 3331131Smax.romanov@nginx.com 3341131Smax.romanov@nginx.com nxt_debug(task, "h1p conn ws frame header read: %z, %z", size, frame_size); 3351131Smax.romanov@nginx.com 3361131Smax.romanov@nginx.com if (frame_size <= size) { 3371131Smax.romanov@nginx.com nxt_h1p_conn_ws_frame_process(task, c, h1p, wsh); 3381131Smax.romanov@nginx.com 3391131Smax.romanov@nginx.com return; 3401131Smax.romanov@nginx.com } 3411131Smax.romanov@nginx.com 3421131Smax.romanov@nginx.com if (frame_size < (size_t) nxt_buf_mem_size(&c->read->mem)) { 3431131Smax.romanov@nginx.com c->read->mem.end = c->read->mem.start + frame_size; 3441131Smax.romanov@nginx.com 3451131Smax.romanov@nginx.com } else { 3461131Smax.romanov@nginx.com nxt_buf_t *b = nxt_buf_mem_alloc(c->mem_pool, frame_size - size, 0); 3471131Smax.romanov@nginx.com 3481131Smax.romanov@nginx.com c->read->next = b; 3491131Smax.romanov@nginx.com c->read = b; 3501131Smax.romanov@nginx.com } 3511131Smax.romanov@nginx.com 3521131Smax.romanov@nginx.com nxt_conn_read(engine, c); 3531131Smax.romanov@nginx.com nxt_h1p_conn_ws_keepalive_enable(task, h1p); 3541131Smax.romanov@nginx.com } 3551131Smax.romanov@nginx.com 3561131Smax.romanov@nginx.com 3571131Smax.romanov@nginx.com static void 3581131Smax.romanov@nginx.com nxt_h1p_conn_ws_keepalive_disable(nxt_task_t *task, nxt_h1proto_t *h1p) 3591131Smax.romanov@nginx.com { 3601131Smax.romanov@nginx.com nxt_timer_t *timer; 3611131Smax.romanov@nginx.com 3621131Smax.romanov@nginx.com if (h1p->websocket_timer == NULL) { 3631131Smax.romanov@nginx.com return; 3641131Smax.romanov@nginx.com } 3651131Smax.romanov@nginx.com 3661131Smax.romanov@nginx.com timer = &h1p->websocket_timer->timer; 3671131Smax.romanov@nginx.com 3681131Smax.romanov@nginx.com if (nxt_slow_path(timer->handler != nxt_h1p_conn_ws_keepalive)) { 3691131Smax.romanov@nginx.com nxt_debug(task, "h1p ws keepalive disable: scheduled ws shutdown"); 3701131Smax.romanov@nginx.com return; 3711131Smax.romanov@nginx.com } 3721131Smax.romanov@nginx.com 3731131Smax.romanov@nginx.com nxt_timer_disable(task->thread->engine, timer); 3741131Smax.romanov@nginx.com } 3751131Smax.romanov@nginx.com 3761131Smax.romanov@nginx.com 3771131Smax.romanov@nginx.com static void 3781131Smax.romanov@nginx.com nxt_h1p_conn_ws_keepalive_enable(nxt_task_t *task, nxt_h1proto_t *h1p) 3791131Smax.romanov@nginx.com { 3801131Smax.romanov@nginx.com nxt_timer_t *timer; 3811131Smax.romanov@nginx.com 3821131Smax.romanov@nginx.com if (h1p->websocket_timer == NULL) { 3831131Smax.romanov@nginx.com return; 3841131Smax.romanov@nginx.com } 3851131Smax.romanov@nginx.com 3861131Smax.romanov@nginx.com timer = &h1p->websocket_timer->timer; 3871131Smax.romanov@nginx.com 3881131Smax.romanov@nginx.com if (nxt_slow_path(timer->handler != nxt_h1p_conn_ws_keepalive)) { 3891131Smax.romanov@nginx.com nxt_debug(task, "h1p ws keepalive enable: scheduled ws shutdown"); 3901131Smax.romanov@nginx.com return; 3911131Smax.romanov@nginx.com } 3921131Smax.romanov@nginx.com 3931131Smax.romanov@nginx.com nxt_timer_add(task->thread->engine, timer, 3941131Smax.romanov@nginx.com h1p->websocket_timer->keepalive_interval); 3951131Smax.romanov@nginx.com } 3961131Smax.romanov@nginx.com 3971131Smax.romanov@nginx.com 3981131Smax.romanov@nginx.com static void 3991131Smax.romanov@nginx.com nxt_h1p_conn_ws_frame_process(nxt_task_t *task, nxt_conn_t *c, 4001131Smax.romanov@nginx.com nxt_h1proto_t *h1p, nxt_websocket_header_t *wsh) 4011131Smax.romanov@nginx.com { 4021131Smax.romanov@nginx.com size_t hsize; 4031131Smax.romanov@nginx.com uint8_t *p, *mask; 4041131Smax.romanov@nginx.com uint16_t code; 4051131Smax.romanov@nginx.com nxt_http_request_t *r; 4061131Smax.romanov@nginx.com 4071131Smax.romanov@nginx.com r = h1p->request; 4081131Smax.romanov@nginx.com 4091131Smax.romanov@nginx.com c->read = NULL; 4101131Smax.romanov@nginx.com 4111131Smax.romanov@nginx.com if (nxt_slow_path(wsh->opcode == NXT_WEBSOCKET_OP_PING)) { 4121155Smax.romanov@nginx.com nxt_h1p_conn_ws_pong(task, r, NULL); 4131131Smax.romanov@nginx.com return; 4141131Smax.romanov@nginx.com } 4151131Smax.romanov@nginx.com 4161131Smax.romanov@nginx.com if (nxt_slow_path(wsh->opcode == NXT_WEBSOCKET_OP_CLOSE)) { 4171131Smax.romanov@nginx.com if (wsh->payload_len >= 2) { 4181131Smax.romanov@nginx.com hsize = nxt_websocket_frame_header_size(wsh); 4191131Smax.romanov@nginx.com mask = nxt_pointer_to(wsh, hsize - 4); 4201131Smax.romanov@nginx.com p = nxt_pointer_to(wsh, hsize); 4211131Smax.romanov@nginx.com 4221131Smax.romanov@nginx.com code = ((p[0] ^ mask[0]) << 8) + (p[1] ^ mask[1]); 4231131Smax.romanov@nginx.com 4241131Smax.romanov@nginx.com if (nxt_slow_path(code < 1000 || code >= 5000 4251131Smax.romanov@nginx.com || (code > 1003 && code < 1007) 4261131Smax.romanov@nginx.com || (code > 1014 && code < 3000))) 4271131Smax.romanov@nginx.com { 4281131Smax.romanov@nginx.com hxt_h1p_send_ws_error(task, r, &nxt_ws_err_invalid_close_code, 4291131Smax.romanov@nginx.com code); 4301131Smax.romanov@nginx.com return; 4311131Smax.romanov@nginx.com } 4321131Smax.romanov@nginx.com } 4331131Smax.romanov@nginx.com 4341131Smax.romanov@nginx.com h1p->websocket_closed = 1; 4351131Smax.romanov@nginx.com } 4361131Smax.romanov@nginx.com 4371155Smax.romanov@nginx.com r->state->ready_handler(task, r, NULL); 4381131Smax.romanov@nginx.com } 4391131Smax.romanov@nginx.com 4401131Smax.romanov@nginx.com 4411131Smax.romanov@nginx.com static void 4421131Smax.romanov@nginx.com nxt_h1p_conn_ws_error(nxt_task_t *task, void *obj, void *data) 4431131Smax.romanov@nginx.com { 4441131Smax.romanov@nginx.com nxt_h1proto_t *h1p; 4451131Smax.romanov@nginx.com nxt_http_request_t *r; 4461131Smax.romanov@nginx.com 4471131Smax.romanov@nginx.com h1p = data; 4481131Smax.romanov@nginx.com 4491131Smax.romanov@nginx.com nxt_debug(task, "h1p conn ws error"); 4501131Smax.romanov@nginx.com 4511131Smax.romanov@nginx.com r = h1p->request; 4521131Smax.romanov@nginx.com 4531131Smax.romanov@nginx.com h1p->keepalive = 0; 4541131Smax.romanov@nginx.com 4551131Smax.romanov@nginx.com if (nxt_fast_path(r != NULL)) { 4561131Smax.romanov@nginx.com r->state->error_handler(task, r, h1p); 4571131Smax.romanov@nginx.com } 4581131Smax.romanov@nginx.com } 4591131Smax.romanov@nginx.com 4601131Smax.romanov@nginx.com 4611131Smax.romanov@nginx.com static ssize_t 4621268Sigor@sysoev.ru nxt_h1p_ws_io_read_handler(nxt_task_t *task, nxt_conn_t *c) 4631131Smax.romanov@nginx.com { 4641131Smax.romanov@nginx.com size_t size; 4651131Smax.romanov@nginx.com ssize_t n; 4661131Smax.romanov@nginx.com nxt_buf_t *b; 4671131Smax.romanov@nginx.com 4681131Smax.romanov@nginx.com b = c->read; 4691131Smax.romanov@nginx.com 4701131Smax.romanov@nginx.com if (b == NULL) { 4711131Smax.romanov@nginx.com /* Enough for control frame. */ 4721131Smax.romanov@nginx.com size = 10 + 125; 4731131Smax.romanov@nginx.com 4741131Smax.romanov@nginx.com b = nxt_buf_mem_alloc(c->mem_pool, size, 0); 4751131Smax.romanov@nginx.com if (nxt_slow_path(b == NULL)) { 4761131Smax.romanov@nginx.com c->socket.error = NXT_ENOMEM; 4771131Smax.romanov@nginx.com return NXT_ERROR; 4781131Smax.romanov@nginx.com } 4791131Smax.romanov@nginx.com } 4801131Smax.romanov@nginx.com 4811131Smax.romanov@nginx.com n = c->io->recvbuf(c, b); 4821131Smax.romanov@nginx.com 4831131Smax.romanov@nginx.com if (n > 0) { 4841131Smax.romanov@nginx.com c->read = b; 4851131Smax.romanov@nginx.com 4861131Smax.romanov@nginx.com } else { 4871131Smax.romanov@nginx.com c->read = NULL; 4881131Smax.romanov@nginx.com nxt_mp_free(c->mem_pool, b); 4891131Smax.romanov@nginx.com } 4901131Smax.romanov@nginx.com 4911131Smax.romanov@nginx.com return n; 4921131Smax.romanov@nginx.com } 4931131Smax.romanov@nginx.com 4941131Smax.romanov@nginx.com 4951131Smax.romanov@nginx.com static void 4961131Smax.romanov@nginx.com nxt_h1p_conn_ws_timeout(nxt_task_t *task, void *obj, void *data) 4971131Smax.romanov@nginx.com { 4981131Smax.romanov@nginx.com nxt_conn_t *c; 4991131Smax.romanov@nginx.com nxt_timer_t *timer; 5001131Smax.romanov@nginx.com nxt_h1proto_t *h1p; 5011131Smax.romanov@nginx.com nxt_http_request_t *r; 5021131Smax.romanov@nginx.com 5031131Smax.romanov@nginx.com timer = obj; 5041131Smax.romanov@nginx.com 5051131Smax.romanov@nginx.com nxt_debug(task, "h1p conn ws timeout"); 5061131Smax.romanov@nginx.com 5071131Smax.romanov@nginx.com c = nxt_read_timer_conn(timer); 5081131Smax.romanov@nginx.com c->block_read = 1; 5091131Smax.romanov@nginx.com /* 5101131Smax.romanov@nginx.com * Disable SO_LINGER off during socket closing 5111131Smax.romanov@nginx.com * to send "408 Request Timeout" error response. 5121131Smax.romanov@nginx.com */ 5131131Smax.romanov@nginx.com c->socket.timedout = 0; 5141131Smax.romanov@nginx.com 5151131Smax.romanov@nginx.com h1p = c->socket.data; 5161131Smax.romanov@nginx.com h1p->keepalive = 0; 5171131Smax.romanov@nginx.com 5181131Smax.romanov@nginx.com r = h1p->request; 5191131Smax.romanov@nginx.com if (nxt_slow_path(r == NULL)) { 5201131Smax.romanov@nginx.com return; 5211131Smax.romanov@nginx.com } 5221131Smax.romanov@nginx.com 5231131Smax.romanov@nginx.com hxt_h1p_send_ws_error(task, r, &nxt_ws_err_going_away); 5241131Smax.romanov@nginx.com } 5251131Smax.romanov@nginx.com 5261131Smax.romanov@nginx.com 5271131Smax.romanov@nginx.com static const nxt_conn_state_t nxt_h1p_read_ws_frame_payload_state 5281131Smax.romanov@nginx.com nxt_aligned(64) = 5291131Smax.romanov@nginx.com { 5301131Smax.romanov@nginx.com .ready_handler = nxt_h1p_conn_ws_frame_payload_read, 5311131Smax.romanov@nginx.com .close_handler = nxt_h1p_conn_ws_error, 5321131Smax.romanov@nginx.com .error_handler = nxt_h1p_conn_ws_error, 5331131Smax.romanov@nginx.com 5341131Smax.romanov@nginx.com .timer_handler = nxt_h1p_conn_ws_timeout, 5351131Smax.romanov@nginx.com .timer_value = nxt_h1p_conn_request_timer_value, 5361131Smax.romanov@nginx.com .timer_data = offsetof(nxt_socket_conf_t, websocket_conf.read_timeout), 5371131Smax.romanov@nginx.com .timer_autoreset = 1, 5381131Smax.romanov@nginx.com }; 5391131Smax.romanov@nginx.com 5401131Smax.romanov@nginx.com 5411131Smax.romanov@nginx.com static void 5421131Smax.romanov@nginx.com nxt_h1p_conn_ws_frame_payload_read(nxt_task_t *task, void *obj, void *data) 5431131Smax.romanov@nginx.com { 5441131Smax.romanov@nginx.com nxt_conn_t *c; 5451131Smax.romanov@nginx.com nxt_h1proto_t *h1p; 5461131Smax.romanov@nginx.com nxt_http_request_t *r; 5471131Smax.romanov@nginx.com nxt_event_engine_t *engine; 5481131Smax.romanov@nginx.com nxt_websocket_header_t *wsh; 5491131Smax.romanov@nginx.com 5501131Smax.romanov@nginx.com c = obj; 5511131Smax.romanov@nginx.com h1p = data; 5521131Smax.romanov@nginx.com 5531131Smax.romanov@nginx.com nxt_h1p_conn_ws_keepalive_disable(task, h1p); 5541131Smax.romanov@nginx.com 5551131Smax.romanov@nginx.com nxt_debug(task, "h1p conn ws frame read"); 5561131Smax.romanov@nginx.com 5571131Smax.romanov@nginx.com if (nxt_buf_mem_free_size(&c->read->mem) == 0) { 5581131Smax.romanov@nginx.com r = h1p->request; 5591131Smax.romanov@nginx.com if (nxt_slow_path(r == NULL)) { 5601131Smax.romanov@nginx.com return; 5611131Smax.romanov@nginx.com } 5621131Smax.romanov@nginx.com 5631131Smax.romanov@nginx.com wsh = (nxt_websocket_header_t *) r->ws_frame->mem.pos; 5641131Smax.romanov@nginx.com 5651131Smax.romanov@nginx.com nxt_h1p_conn_ws_frame_process(task, c, h1p, wsh); 5661131Smax.romanov@nginx.com 5671131Smax.romanov@nginx.com return; 5681131Smax.romanov@nginx.com } 5691131Smax.romanov@nginx.com 5701131Smax.romanov@nginx.com engine = task->thread->engine; 5711131Smax.romanov@nginx.com 5721131Smax.romanov@nginx.com nxt_conn_read(engine, c); 5731131Smax.romanov@nginx.com nxt_h1p_conn_ws_keepalive_enable(task, h1p); 5741131Smax.romanov@nginx.com } 5751131Smax.romanov@nginx.com 5761131Smax.romanov@nginx.com 5771131Smax.romanov@nginx.com static void 5781131Smax.romanov@nginx.com hxt_h1p_send_ws_error(nxt_task_t *task, nxt_http_request_t *r, 5791131Smax.romanov@nginx.com const nxt_ws_error_t *err, ...) 5801131Smax.romanov@nginx.com { 5811131Smax.romanov@nginx.com u_char *p; 5821131Smax.romanov@nginx.com va_list args; 5831131Smax.romanov@nginx.com nxt_buf_t *out; 5841131Smax.romanov@nginx.com nxt_str_t desc; 5851131Smax.romanov@nginx.com nxt_websocket_header_t *wsh; 5861131Smax.romanov@nginx.com u_char buf[125]; 5871131Smax.romanov@nginx.com 5881131Smax.romanov@nginx.com if (nxt_slow_path(err->args)) { 5891131Smax.romanov@nginx.com va_start(args, err); 5901131Smax.romanov@nginx.com p = nxt_vsprintf(buf, buf + sizeof(buf), (char *) err->desc.start, 5911131Smax.romanov@nginx.com args); 5921131Smax.romanov@nginx.com va_end(args); 5931131Smax.romanov@nginx.com 5941131Smax.romanov@nginx.com desc.start = buf; 5951131Smax.romanov@nginx.com desc.length = p - buf; 5961131Smax.romanov@nginx.com 5971131Smax.romanov@nginx.com } else { 5981131Smax.romanov@nginx.com desc = err->desc; 5991131Smax.romanov@nginx.com } 6001131Smax.romanov@nginx.com 6011131Smax.romanov@nginx.com nxt_log(task, NXT_LOG_INFO, "websocket error %d: %V", err->code, &desc); 6021131Smax.romanov@nginx.com 6031131Smax.romanov@nginx.com out = nxt_http_buf_mem(task, r, 2 + sizeof(err->code) + desc.length); 6041131Smax.romanov@nginx.com if (nxt_slow_path(out == NULL)) { 6051131Smax.romanov@nginx.com nxt_http_request_error_handler(task, r, r->proto.any); 6061131Smax.romanov@nginx.com return; 6071131Smax.romanov@nginx.com } 6081131Smax.romanov@nginx.com 6091131Smax.romanov@nginx.com out->mem.start[0] = 0; 6101131Smax.romanov@nginx.com out->mem.start[1] = 0; 6111131Smax.romanov@nginx.com 6121131Smax.romanov@nginx.com wsh = (nxt_websocket_header_t *) out->mem.start; 6131131Smax.romanov@nginx.com p = nxt_websocket_frame_init(wsh, sizeof(err->code) + desc.length); 6141131Smax.romanov@nginx.com 6151131Smax.romanov@nginx.com wsh->fin = 1; 6161131Smax.romanov@nginx.com wsh->opcode = NXT_WEBSOCKET_OP_CLOSE; 6171131Smax.romanov@nginx.com 6181131Smax.romanov@nginx.com *p++ = (err->code >> 8) & 0xFF; 6191131Smax.romanov@nginx.com *p++ = err->code & 0xFF; 6201131Smax.romanov@nginx.com 6211131Smax.romanov@nginx.com out->mem.free = nxt_cpymem(p, desc.start, desc.length); 6221131Smax.romanov@nginx.com out->next = nxt_http_buf_last(r); 6231131Smax.romanov@nginx.com 6241131Smax.romanov@nginx.com if (out->next != NULL) { 6251131Smax.romanov@nginx.com out->next->completion_handler = nxt_h1p_conn_ws_error_sent; 6261131Smax.romanov@nginx.com } 6271131Smax.romanov@nginx.com 6281131Smax.romanov@nginx.com nxt_http_request_send(task, r, out); 6291131Smax.romanov@nginx.com } 6301131Smax.romanov@nginx.com 6311131Smax.romanov@nginx.com 6321131Smax.romanov@nginx.com static void 6331131Smax.romanov@nginx.com nxt_h1p_conn_ws_error_sent(nxt_task_t *task, void *obj, void *data) 6341131Smax.romanov@nginx.com { 6351131Smax.romanov@nginx.com nxt_http_request_t *r; 6361131Smax.romanov@nginx.com 6371131Smax.romanov@nginx.com r = data; 6381131Smax.romanov@nginx.com 6391131Smax.romanov@nginx.com nxt_debug(task, "h1p conn ws error sent"); 6401131Smax.romanov@nginx.com 6411131Smax.romanov@nginx.com r->state->error_handler(task, r, r->proto.any); 6421131Smax.romanov@nginx.com } 6431131Smax.romanov@nginx.com 6441131Smax.romanov@nginx.com 6451131Smax.romanov@nginx.com static void 6461131Smax.romanov@nginx.com nxt_h1p_conn_ws_pong(nxt_task_t *task, void *obj, void *data) 6471131Smax.romanov@nginx.com { 6481131Smax.romanov@nginx.com uint8_t payload_len, i; 6491131Smax.romanov@nginx.com nxt_buf_t *b, *out, *next; 6501131Smax.romanov@nginx.com nxt_http_request_t *r; 6511131Smax.romanov@nginx.com nxt_websocket_header_t *wsh; 6521131Smax.romanov@nginx.com uint8_t mask[4]; 6531131Smax.romanov@nginx.com 6541131Smax.romanov@nginx.com nxt_debug(task, "h1p conn ws pong"); 6551131Smax.romanov@nginx.com 6561131Smax.romanov@nginx.com r = obj; 6571131Smax.romanov@nginx.com b = r->ws_frame; 6581131Smax.romanov@nginx.com 6591131Smax.romanov@nginx.com wsh = (nxt_websocket_header_t *) b->mem.pos; 6601131Smax.romanov@nginx.com payload_len = wsh->payload_len; 6611131Smax.romanov@nginx.com 6621131Smax.romanov@nginx.com b->mem.pos += 2; 6631131Smax.romanov@nginx.com 6641131Smax.romanov@nginx.com nxt_memcpy(mask, b->mem.pos, 4); 6651131Smax.romanov@nginx.com 6661131Smax.romanov@nginx.com b->mem.pos += 4; 6671131Smax.romanov@nginx.com 6681131Smax.romanov@nginx.com out = nxt_http_buf_mem(task, r, 2 + payload_len); 6691131Smax.romanov@nginx.com if (nxt_slow_path(out == NULL)) { 6701131Smax.romanov@nginx.com nxt_http_request_error_handler(task, r, r->proto.any); 6711131Smax.romanov@nginx.com return; 6721131Smax.romanov@nginx.com } 6731131Smax.romanov@nginx.com 6741131Smax.romanov@nginx.com out->mem.start[0] = 0; 6751131Smax.romanov@nginx.com out->mem.start[1] = 0; 6761131Smax.romanov@nginx.com 6771131Smax.romanov@nginx.com wsh = (nxt_websocket_header_t *) out->mem.start; 6781131Smax.romanov@nginx.com out->mem.free = nxt_websocket_frame_init(wsh, payload_len); 6791131Smax.romanov@nginx.com 6801131Smax.romanov@nginx.com wsh->fin = 1; 6811131Smax.romanov@nginx.com wsh->opcode = NXT_WEBSOCKET_OP_PONG; 6821131Smax.romanov@nginx.com 6831131Smax.romanov@nginx.com for (i = 0; i < payload_len; i++) { 6841131Smax.romanov@nginx.com while (nxt_buf_mem_used_size(&b->mem) == 0) { 6851131Smax.romanov@nginx.com next = b->next; 6861269Sigor@sysoev.ru b->next = NULL; 6871131Smax.romanov@nginx.com 6881131Smax.romanov@nginx.com nxt_work_queue_add(&task->thread->engine->fast_work_queue, 6891131Smax.romanov@nginx.com b->completion_handler, task, b, b->parent); 6901131Smax.romanov@nginx.com 6911131Smax.romanov@nginx.com b = next; 6921131Smax.romanov@nginx.com } 6931131Smax.romanov@nginx.com 6941131Smax.romanov@nginx.com *out->mem.free++ = *b->mem.pos++ ^ mask[i % 4]; 6951131Smax.romanov@nginx.com } 6961131Smax.romanov@nginx.com 6971131Smax.romanov@nginx.com r->ws_frame = b; 6981131Smax.romanov@nginx.com 6991131Smax.romanov@nginx.com nxt_http_request_send(task, r, out); 7001131Smax.romanov@nginx.com 7011131Smax.romanov@nginx.com nxt_http_request_ws_frame_start(task, r, r->ws_frame); 7021131Smax.romanov@nginx.com } 703