diff --git a/UPGRADE.txt b/UPGRADE.txt index 74fc9310c7..71c21ae3a0 100644 --- a/UPGRADE.txt +++ b/UPGRADE.txt @@ -20,6 +20,13 @@ === =========================================================== +from 11.10.0 to 11.11.0 + - Added a compatibility option for chan_sip, 'websocket_write_timeout'. + When a websocket connection exists where Asterisk writes a substantial + amount of data to the connected client, and the connected client is slow + to process the received data, the socket may be disconnected. In such + cases, it may be necessary to adjust this value. Default is 100 ms. + from 11.10.0 to 11.10.1 - MixMonitor AMI actions now require users to have authorization classes. * MixMonitor - system diff --git a/channels/chan_sip.c b/channels/chan_sip.c index 594bc56a05..55d724a829 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -2578,6 +2578,10 @@ static void sip_websocket_callback(struct ast_websocket *session, struct ast_var goto end; } + if (ast_websocket_set_timeout(session, sip_cfg.websocket_write_timeout)) { + goto end; + } + while ((res = ast_wait_for_input(ast_websocket_fd(session), -1)) > 0) { char *payload; uint64_t payload_len; @@ -32241,6 +32245,12 @@ static int reload_config(enum channelreloadreason reason) ast_copy_string(default_parkinglot, v->value, sizeof(default_parkinglot)); } else if (!strcasecmp(v->name, "refer_addheaders")) { global_refer_addheaders = ast_true(v->value); + } else if (!strcasecmp(v->name, "websocket_write_timeout")) { + if (sscanf(v->value, "%30d", &sip_cfg.websocket_write_timeout) != 1 + || sip_cfg.websocket_write_timeout < 0) { + ast_log(LOG_WARNING, "'%s' is not a valid websocket_write_timeout value at line %d. Using default '%d'.\n", v->value, v->lineno, AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT); + sip_cfg.websocket_write_timeout = AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT; + } } } diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h index 99a0dae33d..0b4fc3192a 100644 --- a/channels/sip/include/sip.h +++ b/channels/sip/include/sip.h @@ -778,6 +778,7 @@ struct sip_settings { struct ast_format_cap *caps; /*!< Supported codecs */ int tcp_enabled; int default_max_forwards; /*!< Default max forwards (SIP Anti-loop) */ + int websocket_write_timeout; /*!< Socket write timeout for websocket transports, in ms */ }; /*! \brief The SIP socket definition */ diff --git a/configs/sip.conf.sample b/configs/sip.conf.sample index 962806e123..e240cdf903 100644 --- a/configs/sip.conf.sample +++ b/configs/sip.conf.sample @@ -229,6 +229,12 @@ tcpbindaddr=0.0.0.0 ; IP address for TCP server to bind to (0.0.0.0 ; unauthenticated sessions that will be allowed ; to connect at any given time. (default: 100) +;websocket_write_timeout = 100 ; Default write timeout to set on websocket transports. + ; This value may need to be adjusted for connections where + ; Asterisk must write a substantial amount of data and the + ; receiving clients are slow to process the received information. + ; Value is in milliseconds; default is 100 ms. + transport=udp ; Set the default transports. The order determines the primary default transport. ; If tcpenable=no and the transport set is tcp, we will fallback to UDP. diff --git a/include/asterisk/http_websocket.h b/include/asterisk/http_websocket.h index d59bc25cdc..5ddd1fbb59 100644 --- a/include/asterisk/http_websocket.h +++ b/include/asterisk/http_websocket.h @@ -21,6 +21,9 @@ #include "asterisk/optional_api.h" +/*! \brief Default websocket write timeout, in ms */ +#define AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT 100 + /*! * \file http_websocket.h * \brief Support for WebSocket connections within the Asterisk HTTP server. @@ -184,4 +187,14 @@ AST_OPTIONAL_API(int, ast_websocket_is_secure, (struct ast_websocket *session), */ AST_OPTIONAL_API(int, ast_websocket_set_nonblock, (struct ast_websocket *session), {return -1;}); +/*! + * \brief Set the timeout on a non-blocking WebSocket session. + * + * \since 11.11.0 + * + * \retval 0 on success + * \retval -1 on failure + */ +AST_OPTIONAL_API(int, ast_websocket_set_timeout, (struct ast_websocket *session, int timeout), {return -1;}); + #endif diff --git a/res/res_http_websocket.c b/res/res_http_websocket.c index 41939ccf21..c72c8da327 100644 --- a/res/res_http_websocket.c +++ b/res/res_http_websocket.c @@ -77,6 +77,7 @@ struct ast_websocket { size_t payload_len; /*!< Length of the payload */ char *payload; /*!< Pointer to the payload */ size_t reconstruct; /*!< Number of bytes before a reconstructed payload will be returned and a new one started */ + int timeout; /*!< The timeout for operations on the socket */ unsigned int secure:1; /*!< Bit to indicate that the transport is secure */ unsigned int closing:1; /*!< Bit to indicate that the session is in the process of being closed */ unsigned int close_sent:1; /*!< Bit to indicate that the session close opcode has been sent and no further data will be sent */ @@ -207,8 +208,9 @@ int AST_OPTIONAL_API_NAME(ast_websocket_close)(struct ast_websocket *session, ui session->close_sent = 1; ao2_lock(session); - res = (fwrite(frame, 1, 4, session->f) == 4) ? 0 : -1; + res = ast_careful_fwrite(session->f, session->fd, frame, 4, session->timeout); ao2_unlock(session); + return res; } @@ -251,12 +253,12 @@ int AST_OPTIONAL_API_NAME(ast_websocket_write)(struct ast_websocket *session, en return -1; } - if (fwrite(frame, 1, header_size, session->f) != header_size) { + if (ast_careful_fwrite(session->f, session->fd, frame, header_size, session->timeout)) { ao2_unlock(session); return -1; } - if (fwrite(payload, 1, actual_length, session->f) != actual_length) { + if (ast_careful_fwrite(session->f, session->fd, payload, actual_length, session->timeout)) { ao2_unlock(session); return -1; } @@ -318,6 +320,13 @@ int AST_OPTIONAL_API_NAME(ast_websocket_set_nonblock)(struct ast_websocket *sess return 0; } +int AST_OPTIONAL_API_NAME(ast_websocket_set_timeout)(struct ast_websocket *session, int timeout) +{ + session->timeout = timeout; + + return 0; +} + /* MAINTENANCE WARNING on ast_websocket_read()! * * We have to keep in mind during this function that the fact that session->fd seems ready @@ -462,8 +471,10 @@ int AST_OPTIONAL_API_NAME(ast_websocket_read)(struct ast_websocket *session, cha } /* Per the RFC for PING we need to send back an opcode with the application data as received */ - if (*opcode == AST_WEBSOCKET_OPCODE_PING) { - ast_websocket_write(session, AST_WEBSOCKET_OPCODE_PONG, *payload, *payload_len); + if ((*opcode == AST_WEBSOCKET_OPCODE_PING) && (ast_websocket_write(session, AST_WEBSOCKET_OPCODE_PONG, *payload, *payload_len))) { + *payload_len = 0; + ast_websocket_close(session, 1009); + return 0; } session->payload = new_payload; @@ -613,6 +624,7 @@ static int websocket_callback(struct ast_tcptls_session_instance *ser, const str ao2_ref(protocol_handler, -1); return 0; } + session->timeout = AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT; combined = ast_alloca(combined_length); snprintf(combined, combined_length, "%s%s", key, WEBSOCKET_GUID);