diff options
author | Matt Jordan <mjordan@digium.com> | 2015-12-31 22:42:20 -0600 |
---|---|---|
committer | Gerrit Code Review <gerrit2@gerrit.digium.api> | 2015-12-31 22:42:20 -0600 |
commit | 5a75caa9e6a271eb64773f7473b0d927a0f8de70 (patch) | |
tree | a74991700711f9b4673d9dd82dc5893f5b30a3c0 | |
parent | a68467d293cecd6dceebeab47cf643544e5f8bc0 (diff) | |
parent | dde7f3c1c4c013dc7975c818ee36978e6944faa3 (diff) |
Merge "res_pjsip_history: Add a module that provides PJSIP history for debugging" into 13
-rw-r--r-- | CHANGES | 25 | ||||
-rw-r--r-- | res/res_pjsip_history.c | 1352 |
2 files changed, 1377 insertions, 0 deletions
@@ -9,6 +9,31 @@ ============================================================================== ------------------------------------------------------------------------------ +--- Functionality changes from Asterisk 13.7.0 to Asterisk 13.8.0 ------------ +------------------------------------------------------------------------------ + +res_pjsip_history +------------------ + * A new module, res_pjsip_history, has been added that provides SIP history + viewing/filtering from the CLI. The module is intended to be used on systems + with busy SIP traffic, where existing forms of viewing SIP messages - such + as the res_pjsip_logger - may be inadequate. The module provides two new + CLI commands: + - 'pjsip set history {on|off|clear}' - this enables/disables SIP history + capturing, as well as clears an existing history capture. Note that SIP + packets captured are stored in memory until cleared. As a result, the + history capture should only be used for debugging/viewing purposes, and + should *NOT* be left permanently enabled on a system. + - 'pjsip show history' - displays the captured SIP history. When invoked + with no options, the entire captured history is displayed. Two options + are available: + -- 'entry <num>' - display a detailed view of a single SIP message in + the history + -- 'where ...' - filter the history based on some expression. For more + information on filtering, view the current CLI help for the + 'pjsip show history' command. + +------------------------------------------------------------------------------ --- Functionality changes from Asterisk 13.6.0 to Asterisk 13.7.0 ------------ ------------------------------------------------------------------------------ diff --git a/res/res_pjsip_history.c b/res/res_pjsip_history.c new file mode 100644 index 000000000..ea5f5e801 --- /dev/null +++ b/res/res_pjsip_history.c @@ -0,0 +1,1352 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2015, Digium, Inc. + * + * Matt Jordan <mjordan@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \brief PJSIP History + * + * \author Matt Jordan <mjordan@digium.com> + * + */ + +/*** MODULEINFO + <depend>pjproject</depend> + <depend>res_pjsip</depend> + <support_level>extended</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include <pjsip.h> +#include <regex.h> + +#include "asterisk/res_pjsip.h" +#include "asterisk/module.h" +#include "asterisk/logger.h" +#include "asterisk/cli.h" +#include "asterisk/netsock2.h" +#include "asterisk/vector.h" +#include "asterisk/lock.h" + +#define HISTORY_INITIAL_SIZE 256 + +/*! \brief Pool factory used by pjlib to allocate memory. */ +static pj_caching_pool cachingpool; + +/*! \brief Whether or not we are storing history */ +static int enabled; + +/*! \brief Packet count */ +static int packet_number; + +/*! \brief An item in the history */ +struct pjsip_history_entry { + /*! \brief Packet number */ + int number; + /*! \brief Whether or not we transmitted the packet */ + int transmitted; + /*! \brief Time the packet was transmitted/received */ + struct timeval timestamp; + /*! \brief Source address */ + pj_sockaddr_in src; + /*! \brief Destination address */ + pj_sockaddr_in dst; + /*! \brief Memory pool used to allocate \c msg */ + pj_pool_t *pool; + /*! \brief The actual SIP message */ + pjsip_msg *msg; +}; + +/*! \brief Mutex that protects \ref vector_history */ +AST_MUTEX_DEFINE_STATIC(history_lock); + +/*! \brief The one and only history that we've captured */ +static AST_VECTOR(vector_history_t, struct pjsip_history_entry *) vector_history; + +struct expression_token; + +/*! \brief An operator that we understand in an expression */ +struct operator { + /*! \brief Our operator's symbol */ + const char *symbol; + /*! \brief Precedence of the symbol */ + int precedence; + /*! \brief Non-zero if the operator is evaluated right-to-left */ + int right_to_left; + /*! \brief Number of operands the operator takes */ + int operands; + /*! + * \brief Evaluation function for unary operators + * + * \param op The operator being evaluated + * \param type The type of value contained in \c operand + * \param operand A pointer to the value to evaluate + * + * \retval -1 error + * \retval 0 evaluation is False + * \retval 1 evaluation is True + */ + int (* const evaluate_unary)(struct operator *op, enum aco_option_type type, void *operand); + /*! + * \brief Evaluation function for binary operators + * + * \param op The operator being evaluated + * \param type The type of value contained in \c op_left + * \param op_left A pointer to the value to evaluate (a result or extracted from an entry) + * \param op_right The expression token containing the other value (a result or user-provided) + * + * \retval -1 error + * \retval 0 evaluation is False + * \retval 1 evaluation is True + */ + int (* const evaluate)(struct operator *op, enum aco_option_type type, void *op_left, struct expression_token *op_right); +}; + +/*! \brief A field that we understand and can perform operations on */ +struct allowed_field { + /*! \brief The representation of the field */ + const char *symbol; + /*! \brief The type /c get_field returns */ + enum aco_option_type return_type; + /*! + * \brief Function that returns the field from a pjsip_history_entry + * + * Note that the function must return a pointer to the location in + * \c pjsip_history_entry - no memory should be allocated as the caller + * will not dispose of any + */ + void *(* const get_field)(struct pjsip_history_entry *entry); +}; + +/*! \brief The type of token that has been parsed out of an expression */ +enum expression_token_type { + /*! The \c expression_token contains a field */ + TOKEN_TYPE_FIELD, + /*! The \c expression_token contains an operator */ + TOKEN_TYPE_OPERATOR, + /*! The \c expression_token contains a previous result */ + TOKEN_TYPE_RESULT +}; + +/*! \brief A token in the expression or an evaluated part of the expression */ +struct expression_token { + /*! \brief The next expression token in the queue */ + struct expression_token *next; + /*! \brief The type of value stored in the expression token */ + enum expression_token_type token_type; + /*! \brief An operator that evaluates expressions */ + struct operator *op; + /*! \brief The result of an evaluated expression */ + int result; + /*! \brief The field in the expression */ + char field[]; +}; + +/*! + * \brief Operator callback for determining equality + */ +static int evaluate_equal(struct operator *op, enum aco_option_type type, void *op_left, struct expression_token *op_right) +{ + switch (type) { + case OPT_BOOL_T: + case OPT_BOOLFLAG_T: + case OPT_INT_T: + case OPT_UINT_T: + { + int right; + + if (sscanf(op_right->field, "%30d", &right) != 1) { + ast_log(LOG_WARNING, "Unable to extract field '%s': not an integer\n", op_right->field); + return -1; + } + return (*(int *)op_left) == right; + } + case OPT_DOUBLE_T: + { + double right; + + if (sscanf(op_right->field, "%lf", &right) != 1) { + ast_log(LOG_WARNING, "Unable to extract field '%s': not a double\n", op_right->field); + return -1; + } + return (*(double *)op_left) == right; + } + case OPT_CHAR_ARRAY_T: + case OPT_STRINGFIELD_T: + /* In our case, we operate on pj_str_t */ + return pj_strcmp2(op_left, op_right->field) == 0; + case OPT_NOOP_T: + /* Used for timeval */ + { + struct timeval right = { 0, }; + + if (sscanf(op_right->field, "%ld", &right.tv_sec) != 1) { + ast_log(LOG_WARNING, "Unable to extract field '%s': not a timestamp\n", op_right->field); + return -1; + } + + return ast_tvcmp(*(struct timeval *)op_left, right) == 0; + } + case OPT_SOCKADDR_T: + /* In our case, we operate only on pj_sockaddr_t */ + { + pj_sockaddr right; + pj_str_t str_right; + + pj_cstr(&str_right, op_right->field); + if (pj_sockaddr_parse(pj_AF_UNSPEC(), 0, &str_right, &right) != PJ_SUCCESS) { + ast_log(LOG_WARNING, "Unable to convert field '%s': not an IPv4 or IPv6 address\n", op_right->field); + return -1; + } + + return pj_sockaddr_cmp(op_left, &right) == 0; + } + default: + ast_log(LOG_WARNING, "Cannot evaluate field '%s': invalid type for operator '%s'\n", + op_right->field, op->symbol); + } + + return -1; +} + +/*! + * \brief Operator callback for determining inequality + */ +static int evaluate_not_equal(struct operator *op, enum aco_option_type type, void *op_left, struct expression_token *op_right) +{ + return !evaluate_equal(op, type, op_left, op_right); +} + +/* + * \brief Operator callback for determining if one operand is less than another + */ +static int evaluate_less_than(struct operator *op, enum aco_option_type type, void *op_left, struct expression_token *op_right) +{ + switch (type) { + case OPT_BOOL_T: + case OPT_BOOLFLAG_T: + case OPT_INT_T: + case OPT_UINT_T: + { + int right; + + if (sscanf(op_right->field, "%30d", &right) != 1) { + ast_log(LOG_WARNING, "Unable to extract field '%s': not an integer\n", op_right->field); + return -1; + } + return (*(int *)op_left) < right; + } + case OPT_DOUBLE_T: + { + double right; + + if (sscanf(op_right->field, "%lf", &right) != 1) { + ast_log(LOG_WARNING, "Unable to extract field '%s': not a double\n", op_right->field); + return -1; + } + return (*(double *)op_left) < right; + } + case OPT_NOOP_T: + /* Used for timeval */ + { + struct timeval right = { 0, }; + + if (sscanf(op_right->field, "%ld", &right.tv_sec) != 1) { + ast_log(LOG_WARNING, "Unable to extract field '%s': not a timestamp\n", op_right->field); + return -1; + } + + return ast_tvcmp(*(struct timeval *)op_left, right) == -1; + } + default: + ast_log(LOG_WARNING, "Cannot evaluate field '%s': invalid type for operator '%s'\n", + op_right->field, op->symbol); + } + + return -1; +} + +/* + * \brief Operator callback for determining if one operand is greater than another + */ +static int evaluate_greater_than(struct operator *op, enum aco_option_type type, void *op_left, struct expression_token *op_right) +{ + switch (type) { + case OPT_BOOL_T: + case OPT_BOOLFLAG_T: + case OPT_INT_T: + case OPT_UINT_T: + { + int right; + + if (sscanf(op_right->field, "%30d", &right) != 1) { + ast_log(LOG_WARNING, "Unable to extract field '%s': not an integer\n", op_right->field); + return -1; + } + return (*(int *)op_left) > right; + } + case OPT_DOUBLE_T: + { + double right; + + if (sscanf(op_right->field, "%lf", &right) != 1) { + ast_log(LOG_WARNING, "Unable to extract field '%s': not a double\n", op_right->field); + return -1; + } + return (*(double *)op_left) > right; + } + case OPT_NOOP_T: + /* Used for timeval */ + { + struct timeval right = { 0, }; + + if (sscanf(op_right->field, "%ld", &right.tv_sec) != 1) { + ast_log(LOG_WARNING, "Unable to extract field '%s': not a timestamp\n", op_right->field); + return -1; + } + + return ast_tvcmp(*(struct timeval *)op_left, right) == 1; + } + default: + ast_log(LOG_WARNING, "Cannot evaluate field '%s': invalid type for operator '%s'\n", + op_right->field, op->symbol); + } + + return -1; +} + +/* + * \brief Operator callback for determining if one operand is less than or equal to another + */ +static int evaluate_less_than_or_equal(struct operator *op, enum aco_option_type type, void *op_left, struct expression_token *op_right) +{ + return !evaluate_greater_than(op, type, op_left, op_right); +} + +/* + * \brief Operator callback for determining if one operand is greater than or equal to another + */ +static int evaluate_greater_than_or_equal(struct operator *op, enum aco_option_type type, void *op_left, struct expression_token *op_right) +{ + return !evaluate_less_than(op, type, op_left, op_right); +} + +/* + * \brief Operator callback for determining logical NOT + */ +static int evaluate_not(struct operator *op, enum aco_option_type type, void *operand) +{ + switch (type) { + case OPT_BOOL_T: + case OPT_BOOLFLAG_T: + case OPT_INT_T: + case OPT_UINT_T: + return !(*(int *)operand); + default: + ast_log(LOG_WARNING, "Cannot evaluate: invalid operand type for operator '%s'\n", op->symbol); + } + + return -1; +} + +/* + * \brief Operator callback for determining logical AND + */ +static int evaluate_and(struct operator *op, enum aco_option_type type, void *op_left, struct expression_token *op_right) +{ + switch (type) { + case OPT_BOOL_T: + case OPT_BOOLFLAG_T: + case OPT_INT_T: + case OPT_UINT_T: + return (*(int *)op_left && op_right->result); + default: + ast_log(LOG_WARNING, "Cannot evaluate: invalid operand type for operator '%s'\n", op->symbol); + } + + return -1; +} + +/* + * \brief Operator callback for determining logical OR + */ +static int evaluate_or(struct operator *op, enum aco_option_type type, void *op_left, struct expression_token *op_right) +{ + switch (type) { + case OPT_BOOL_T: + case OPT_BOOLFLAG_T: + case OPT_INT_T: + case OPT_UINT_T: + return (*(int *)op_left || op_right->result); + default: + ast_log(LOG_WARNING, "Cannot evaluate: invalid operand type for operator '%s'\n", op->symbol); + } + + return -1; +} + +/* + * \brief Operator callback for regex 'like' + */ +static int evaluate_like(struct operator *op, enum aco_option_type type, void *op_left, struct expression_token *op_right) +{ + switch (type) { + case OPT_CHAR_ARRAY_T: + case OPT_STRINGFIELD_T: + /* In our case, we operate on pj_str_t */ + { + int result; + regex_t regexbuf; + char buf[pj_strlen(op_left) + 1]; + + ast_copy_pj_str(buf, op_left, pj_strlen(op_left)); + if (regcomp(®exbuf, op_right->field, REG_EXTENDED | REG_NOSUB)) { + ast_log(LOG_WARNING, "Failed to compile '%s' into a regular expression\n", op_right->field); + return -1; + } + + result = (regexec(®exbuf, buf, 0, NULL, 0) == 0); + regfree(®exbuf); + + return result; + } + default: + ast_log(LOG_WARNING, "Cannot evaluate: invalid operand type for operator '%s'\n", op->symbol); + } + + return -1; +} + +/*! + * \brief Operator token for a left parenthesis. + * + * While this is used by the shunting-yard algorithm implementation, + * it should never appear in the resulting RPN queue of expression tokens + */ +static struct operator left_paren = { + .symbol = "(", + .precedence = 15 +}; + +/*! + * \brief Our allowed operations + */ +static struct operator allowed_operators[] = { + { .symbol = "=", .precedence = 7, .operands = 2, .evaluate = evaluate_equal, }, + { .symbol = "==", .precedence = 7, .operands = 2, .evaluate = evaluate_equal, }, + { .symbol = "!=", .precedence = 7, .operands = 2, .evaluate = evaluate_not_equal, }, + { .symbol = "<", .precedence = 6, .operands = 2, .evaluate = evaluate_less_than, }, + { .symbol = ">", .precedence = 6, .operands = 2, .evaluate = evaluate_greater_than, }, + { .symbol = "<=", .precedence = 6, .operands = 2, .evaluate = evaluate_less_than_or_equal, }, + { .symbol = ">=", .precedence = 6, .operands = 2, .evaluate = evaluate_greater_than_or_equal, }, + { .symbol = "!", .precedence = 2, .operands = 1, .right_to_left = 1, .evaluate_unary = evaluate_not, }, + { .symbol = "&&", .precedence = 11, .operands = 2, .evaluate = evaluate_and, }, + { .symbol = "||", .precedence = 12, .operands = 2, .evaluate = evaluate_or, }, + { .symbol = "like", .precedence = 7, .operands = 2, .evaluate = evaluate_like, }, + { .symbol = "and", .precedence = 11, .operands = 2, .evaluate = evaluate_and, }, + { .symbol = "or", .precedence = 11, .operands = 2, .evaluate = evaluate_or, }, + { .symbol = "not", .precedence = 2, .operands = 1, .right_to_left = 1, .evaluate_unary = evaluate_not, }, +}; + +/*! \brief Callback to retrieve the entry index number */ +static void *entry_get_number(struct pjsip_history_entry *entry) +{ + return &entry->number; +} + +/*! \brief Callback to retrieve the entry's timestamp */ +static void *entry_get_timestamp(struct pjsip_history_entry *entry) +{ + return &entry->timestamp; +} + +/*! \brief Callback to retrieve the entry's destination address */ +static void *entry_get_addr(struct pjsip_history_entry *entry) +{ + if (entry->transmitted) { + return &entry->dst; + } else { + return &entry->src; + } +} + +/*! \brief Callback to retrieve the entry's SIP request method type */ +static void *entry_get_sip_msg_request_method(struct pjsip_history_entry *entry) +{ + if (entry->msg->type != PJSIP_REQUEST_MSG) { + return NULL; + } + + return &entry->msg->line.req.method.name; +} + +/*! \brief Callback to retrieve the entry's SIP Call-ID header */ +static void *entry_get_sip_msg_call_id(struct pjsip_history_entry *entry) +{ + pjsip_cid_hdr *cid_hdr; + + cid_hdr = PJSIP_MSG_CID_HDR(entry->msg); + + return &cid_hdr->id; +} + +/*! \brief The fields we allow */ +static struct allowed_field allowed_fields[] = { + { .symbol = "number", .return_type = OPT_INT_T, .get_field = entry_get_number, }, + /* We co-op the NOOP type here for timeval */ + { .symbol = "timestamp", .return_type = OPT_NOOP_T, .get_field = entry_get_timestamp, }, + { .symbol = "addr", .return_type = OPT_SOCKADDR_T, .get_field = entry_get_addr, }, + { .symbol = "sip.msg.request.method", .return_type = OPT_CHAR_ARRAY_T, .get_field = entry_get_sip_msg_request_method, }, + { .symbol = "sip.msg.call-id", .return_type = OPT_CHAR_ARRAY_T, .get_field = entry_get_sip_msg_call_id, }, +}; + +/*! \brief Free an expression token and all others it references */ +static struct expression_token *expression_token_free(struct expression_token *token) +{ + struct expression_token *it_token; + + it_token = token; + while (it_token) { + struct expression_token *prev = it_token; + + it_token = it_token->next; + ast_free(prev); + } + + return NULL; +} + +/*! + * \brief Allocate an expression token + * + * \param token_type The type of token in the expression + * \param value The value/operator/result to pack into the token + * + * \retval NULL on failure + * \retval \c expression_token on success + */ +static struct expression_token *expression_token_alloc(enum expression_token_type token_type, void *value) +{ + struct expression_token *token; + + switch (token_type) { + case TOKEN_TYPE_RESULT: + case TOKEN_TYPE_OPERATOR: + token = ast_calloc(1, sizeof(*token)); + break; + case TOKEN_TYPE_FIELD: + token = ast_calloc(1, sizeof(*token) + strlen((const char *)value) + 1); + break; + default: + ast_assert(0); + return NULL; + } + + if (!token) { + return NULL; + } + token->token_type = token_type; + + switch (token_type) { + case TOKEN_TYPE_RESULT: + token->result = *(int *)value; + break; + case TOKEN_TYPE_OPERATOR: + token->op = value; + break; + case TOKEN_TYPE_FIELD: + strcpy(token->field, value); /* safe */ + break; + default: + ast_assert(0); + } + + return token; +} + +/*! \brief Determine if the expression token matches a field in \c allowed_fields */ +static struct allowed_field *get_allowed_field(struct expression_token *token) +{ + int i; + + ast_assert(token->token_type == TOKEN_TYPE_FIELD); + + for (i = 0; i < ARRAY_LEN(allowed_fields); i++) { + if (strcasecmp(allowed_fields[i].symbol, token->field)) { + continue; + } + + return &allowed_fields[i]; + } + + return NULL; +} + +/*! \brief AO2 destructor for \c pjsip_history_entry */ +static void pjsip_history_entry_dtor(void *obj) +{ + struct pjsip_history_entry *entry = obj; + + if (entry->pool) { + pj_pool_release(entry->pool); + entry->pool = NULL; + } +} + +/*! + * \brief Create a \c pjsip_history_entry AO2 object + * + * \param msg The PJSIP message that this history entry wraps + * + * \retval An AO2 \c pjsip_history_entry object on success + * \retval NULL on failure + */ +static struct pjsip_history_entry *pjsip_history_entry_alloc(pjsip_msg *msg) +{ + struct pjsip_history_entry *entry; + + entry = ao2_alloc_options(sizeof(*entry), pjsip_history_entry_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!entry) { + return NULL; + } + entry->number = ast_atomic_fetchadd_int(&packet_number, 1); + entry->timestamp = ast_tvnow(); + entry->timestamp.tv_usec = 0; + + entry->pool = pj_pool_create(&cachingpool.factory, NULL, PJSIP_POOL_RDATA_LEN, + PJSIP_POOL_RDATA_INC, NULL); + if (!entry->pool) { + ao2_ref(entry, -1); + return NULL; + } + + entry->msg = pjsip_msg_clone(entry->pool, msg); + if (!entry->msg) { + ao2_ref(entry, -1); + return NULL; + } + + return entry; +} + +/*! \brief PJSIP callback when a SIP message is transmitted */ +static pj_status_t history_on_tx_msg(pjsip_tx_data *tdata) +{ + struct pjsip_history_entry *entry; + + if (!enabled) { + return PJ_SUCCESS; + } + + entry = pjsip_history_entry_alloc(tdata->msg); + if (!entry) { + return PJ_SUCCESS; + } + entry->transmitted = 1; + pj_sockaddr_cp(&entry->src, &tdata->tp_info.transport->local_addr); + pj_sockaddr_cp(&entry->dst, &tdata->tp_info.dst_addr); + + ast_mutex_lock(&history_lock); + AST_VECTOR_APPEND(&vector_history, entry); + ast_mutex_unlock(&history_lock); + + return PJ_SUCCESS; +} + +/*! \brief PJSIP callback when a SIP message is received */ +static pj_bool_t history_on_rx_msg(pjsip_rx_data *rdata) +{ + struct pjsip_history_entry *entry; + + if (!enabled) { + return PJ_FALSE; + } + + if (!rdata->msg_info.msg) { + return PJ_FALSE; + } + + entry = pjsip_history_entry_alloc(rdata->msg_info.msg); + if (!entry) { + return PJ_FALSE; + } + + if (rdata->tp_info.transport->addr_len) { + pj_sockaddr_cp(&entry->dst, &rdata->tp_info.transport->local_addr); + } + + if (rdata->pkt_info.src_addr_len) { + pj_sockaddr_cp(&entry->src, &rdata->pkt_info.src_addr); + } + + ast_mutex_lock(&history_lock); + AST_VECTOR_APPEND(&vector_history, entry); + ast_mutex_unlock(&history_lock); + + return PJ_FALSE; +} + +/*! \brief Vector callback that releases the reference for the entry in a history vector */ +static void clear_history_entry_cb(struct pjsip_history_entry *entry) +{ + ao2_ref(entry, -1); +} + +/*! + * \brief Remove all entries from \ref vector_history + * + * This must be called from a registered PJSIP thread + */ +static int clear_history_entries(void *obj) +{ + ast_mutex_lock(&history_lock); + AST_VECTOR_RESET(&vector_history, clear_history_entry_cb); + packet_number = 0; + ast_mutex_unlock(&history_lock); + + return 0; +} + +/*! + * \brief Build a reverse polish notation expression queue + * + * This function is an implementation of the Shunting-Yard Algorithm. It takes + * a user provided infix-notation expression and converts it into a reverse + * polish notation expression, which is a queue of tokens that can be easily + * parsed. + * + * \params a The CLI arguments provided by the User, containing the infix expression + * + * \retval NULL error + * \retval expression_token A 'queue' of expression tokens in RPN + */ +static struct expression_token *build_expression_queue(struct ast_cli_args *a) +{ + AST_VECTOR(, struct operator *) operators; /* A stack of saved operators */ + struct expression_token *output = NULL; /* The output queue */ + struct expression_token *head = NULL; /* Pointer to the head of /c output */ + int i; + +#define APPEND_TO_OUTPUT(output, token) do { \ + if ((output)) { \ + (output)->next = (token); \ + (output) = (token); \ + } else { \ + (output) = (token); \ + head = (output); \ + } \ +} while (0) + + if (AST_VECTOR_INIT(&operators, 8)) { + return NULL; + } + + for (i = 4; i < a->argc; i++) { + struct expression_token *out_token; + char *token = ast_strdupa(a->argv[i]); + int j; + + /* Strip off and append any left parentheses */ + if (token[0] == '(') { + AST_VECTOR_APPEND(&operators, &left_paren); + if (!token[1]) { + continue; + } + token = &token[1]; + } + + /* Handle the case where the token is an operator */ + for (j = 0; j < ARRAY_LEN(allowed_operators); j++) { + int k; + + if (strcasecmp(token, allowed_operators[j].symbol)) { + continue; + } + + for (k = AST_VECTOR_SIZE(&operators) - 1; k >= 0; k--) { + struct operator *top = AST_VECTOR_GET(&operators, k); + + /* Remove and push queued up operators, if they are of + * less precedence than this operator + */ + if ((allowed_operators[j].right_to_left && allowed_operators[j].precedence >= top->precedence) + || (!allowed_operators[j].right_to_left && allowed_operators[j].precedence > top->precedence)) { + + if (!(out_token = expression_token_alloc(TOKEN_TYPE_OPERATOR, top))) { + goto error; + } + APPEND_TO_OUTPUT(output, out_token); + AST_VECTOR_REMOVE(&operators, k, 1); + } + } + + AST_VECTOR_APPEND(&operators, &allowed_operators[j]); + token = NULL; + break; + } + + /* Token was an operator; continue to next token */ + if (!token) { + continue; + } + + /* Handle a right parentheses either by itself or as part of the token. + * If part of the token, push the token onto the output queue first + */ + if (token[0] == ')' || token[strlen(token) - 1] == ')') { + + if (token[strlen(token) - 1] == ')') { + token[strlen(token) - 1] = '\0'; + + if (!(out_token = expression_token_alloc(TOKEN_TYPE_FIELD, token))) { + goto error; + } + APPEND_TO_OUTPUT(output, out_token); + token = NULL; + } + + for (j = AST_VECTOR_SIZE(&operators) - 1; j >= 0; j--) { + struct operator *top = AST_VECTOR_GET(&operators, j); + + AST_VECTOR_REMOVE(&operators, j, 1); + if (top == &left_paren) { + break; + } + + if (!(out_token = expression_token_alloc(TOKEN_TYPE_OPERATOR, top))) { + goto error; + } + APPEND_TO_OUTPUT(output, out_token); + } + } + + /* Just a plain token, push to the output queue */ + if (token) { + if (!(out_token = expression_token_alloc(TOKEN_TYPE_FIELD, token))) { + goto error; + } + APPEND_TO_OUTPUT(output, out_token); + } + } + + /* Remove any non-applied operators that remain, applying them + * to the output queue + */ + for (i = AST_VECTOR_SIZE(&operators) - 1; i >= 0; i--) { + struct operator *top = AST_VECTOR_GET(&operators, i); + struct expression_token *out_token; + + AST_VECTOR_REMOVE(&operators, i, 1); + if (top == &left_paren) { + ast_log(LOG_WARNING, "Unbalanced '(' parentheses in expression!\n"); + continue; + } + + if (!(out_token = expression_token_alloc(TOKEN_TYPE_OPERATOR, top))) { + goto error; + } + APPEND_TO_OUTPUT(output, out_token); + } + + AST_VECTOR_FREE(&operators); + return head; + +error: + AST_VECTOR_FREE(&operators); + expression_token_free(output); + return NULL; +} + +/*! + * \brief Evaluate a single entry in this history using a RPN expression + * + * \param entry The entry in the history to evaluate + * \param queue The RPN expression + * + * \retval 0 The expression evaluated FALSE on \c entry + * \retval 1 The expression evaluated TRUE on \c entry + * \retval -1 The expression errored + */ +static int evaluate_history_entry(struct pjsip_history_entry *entry, struct expression_token *queue) +{ + AST_VECTOR(, struct expression_token *) stack; /* Our stack of results and operands */ + struct expression_token *it_queue; + struct expression_token *final; + int result; + int i; + + if (AST_VECTOR_INIT(&stack, 16)) { + return -1; + } + + for (it_queue = queue; it_queue; it_queue = it_queue->next) { + struct expression_token *op_one; + struct expression_token *op_two = NULL; + struct expression_token *result; + int res = 0; + + /* If this is not an operator, push it to the stack */ + if (!it_queue->op) { + AST_VECTOR_APPEND(&stack, it_queue); + continue; + } + + if (AST_VECTOR_SIZE(&stack) < it_queue->op->operands) { + ast_log(LOG_WARNING, "Unable to evaluate expression operator '%s': not enough operands\n", + it_queue->op->symbol); + goto error; + } + + if (it_queue->op->operands == 1) { + /* Unary operators currently consist only of 'not', which can only act + * upon an evaluated condition result. + */ + ast_assert(it_queue->op->evaluate_unary != NULL); + + op_one = AST_VECTOR_REMOVE(&stack, AST_VECTOR_SIZE(&stack) - 1, 1); + if (op_one->token_type != TOKEN_TYPE_RESULT) { + ast_log(LOG_WARNING, "Unable to evaluate '%s': operand is not the result of an operation\n", + it_queue->op->symbol); + goto error; + } + + res = it_queue->op->evaluate_unary(it_queue->op, OPT_INT_T, &op_one->result) == 0 ? 0 : 1; + } else if (it_queue->op->operands == 2) { + struct allowed_field *field; + enum aco_option_type type; + void *value; + + ast_assert(it_queue->op->evaluate != NULL); + + op_one = AST_VECTOR_REMOVE(&stack, AST_VECTOR_SIZE(&stack) - 1, 1); + op_two = AST_VECTOR_REMOVE(&stack, AST_VECTOR_SIZE(&stack) - 1, 1); + + /* If operand two is a field, then it must be a field we recognize. */ + if (op_two->token_type == TOKEN_TYPE_FIELD) { + field = get_allowed_field(op_two); + if (!field) { + ast_log(LOG_WARNING, "Unknown or unrecognized field: %s\n", op_two->field); + goto error; + } + + type = field->return_type; + value = field->get_field(entry); + } else if (op_two->token_type == TOKEN_TYPE_RESULT) { + type = OPT_INT_T; + value = &op_two->result; + } else { + ast_log(LOG_WARNING, "Attempting to evaluate an operator: %s\n", op_two->op->symbol); + goto error; + } + + if (value) { + res = it_queue->op->evaluate(it_queue->op, type, value, op_one) == 0 ? 0 : 1; + } else { + res = 0; + } + } else { + ast_log(LOG_WARNING, "Operator '%s' has an invalid number of operands\n", it_queue->op->symbol); + ast_assert(0); + goto error; + } + + /* Results are temporary; clean used ones up */ + if (op_one && op_one->token_type == TOKEN_TYPE_RESULT) { + ast_free(op_one); + } + if (op_two && op_two->token_type == TOKEN_TYPE_RESULT) { + ast_free(op_two); + } + + /* Push the result onto the stack */ + result = expression_token_alloc(TOKEN_TYPE_RESULT, &res); + if (!result) { + goto error; + } + AST_VECTOR_APPEND(&stack, result); + } + + /* + * When the evaluation is complete, we must have: + * - A single result remaining on the stack + * - An actual result + */ + if (AST_VECTOR_SIZE(&stack) != 1) { + ast_log(LOG_WARNING, "Expression was unbalanced: %zu results remained after evaluation\n", + AST_VECTOR_SIZE(&stack)); + goto error; + } + + final = AST_VECTOR_GET(&stack, 0); + if (final->token_type != TOKEN_TYPE_RESULT) { + ast_log(LOG_WARNING, "Expression did not create a usable result\n"); + goto error; + } + result = final->result; + ast_free(final); + + return result; + +error: + /* Clean out any remaining result expression tokens */ + for (i = 0; i < AST_VECTOR_SIZE(&stack); i++) { + struct expression_token *failed_token = AST_VECTOR_GET(&stack, i); + + if (failed_token->token_type == TOKEN_TYPE_RESULT) { + ast_free(failed_token); + } + } + AST_VECTOR_FREE(&stack); + return -1; +} + +/*! + * \brief Create a filtered history based on a user provided expression + * + * \param a The CLI arguments containing the expression + * + * \retval NULL on error + * \retval A vector containing the filtered history on success + */ +static struct vector_history_t *filter_history(struct ast_cli_args *a) +{ + struct vector_history_t *output; + struct expression_token *queue; + int i; + + output = ast_malloc(sizeof(*output)); + if (!output) { + return NULL; + } + + if (AST_VECTOR_INIT(output, HISTORY_INITIAL_SIZE / 2)) { + ast_free(output); + return NULL; + } + + queue = build_expression_queue(a); + if (!queue) { + return NULL; + } + + ast_mutex_lock(&history_lock); + for (i = 0; i < AST_VECTOR_SIZE(&vector_history); i++) { + struct pjsip_history_entry *entry = AST_VECTOR_GET(&vector_history, i); + int res; + + res = evaluate_history_entry(entry, queue); + if (res == -1) { + /* Error in expression evaluation; bail */ + ast_mutex_unlock(&history_lock); + AST_VECTOR_RESET(output, clear_history_entry_cb); + AST_VECTOR_FREE(output); + ast_free(output); + expression_token_free(queue); + return NULL; + } else if (!res) { + continue; + } else { + AST_VECTOR_APPEND(output, ao2_bump(entry)); + } + } + ast_mutex_unlock(&history_lock); + + expression_token_free(queue); + + return output; +} + +/*! \brief Print a detailed view of a single entry in the history to the CLI */ +static void display_single_entry(struct ast_cli_args *a, struct pjsip_history_entry *entry) +{ + char addr[64]; + char *buf; + + buf = ast_calloc(1, PJSIP_MAX_PKT_LEN * sizeof(char)); + if (!buf) { + return; + } + + if (pjsip_msg_print(entry->msg, buf, PJSIP_MAX_PKT_LEN) == -1) { + ast_log(LOG_WARNING, "Unable to print SIP message %d: packet too large!\n", entry->number); + ast_free(buf); + return; + } + + if (entry->transmitted) { + pj_sockaddr_print(&entry->dst, addr, sizeof(addr), 3); + } else { + pj_sockaddr_print(&entry->src, addr, sizeof(addr), 3); + } + + ast_cli(a->fd, "<--- History Entry %d %s %s at %-10.10ld --->\n", + entry->number, + entry->transmitted ? "Sent to" : "Received from", + addr, + entry->timestamp.tv_sec); + ast_cli(a->fd, "%s\n", buf); + + ast_free(buf); +} + +/*! \brief Print a list of the entries to the CLI */ +static void display_entry_list(struct ast_cli_args *a, struct vector_history_t *vec) +{ + int i; + + ast_cli(a->fd, "%-5.5s %-10.10s %-30.30s %-35.35s\n", + "No.", + "Timestamp", + "(Dir) Address", + "SIP Message"); + ast_cli(a->fd, "===== ========== ============================== ===================================\n"); + + for (i = 0; i < AST_VECTOR_SIZE(vec); i++) { + struct pjsip_history_entry *entry; + char addr[64]; + char line[256]; + + entry = AST_VECTOR_GET(vec, i); + + if (entry->transmitted) { + pj_sockaddr_print(&entry->dst, addr, sizeof(addr), 3); + } else { + pj_sockaddr_print(&entry->src, addr, sizeof(addr), 3); + } + + if (entry->msg->type == PJSIP_REQUEST_MSG) { + char uri[128]; + + pjsip_uri_print(PJSIP_URI_IN_REQ_URI, entry->msg->line.req.uri, uri, sizeof(uri)); + snprintf(line, sizeof(line), "%.*s %s SIP/2.0", + (int)pj_strlen(&entry->msg->line.req.method.name), + pj_strbuf(&entry->msg->line.req.method.name), + uri); + } else { + snprintf(line, sizeof(line), "SIP/2.0 %u %.*s", + entry->msg->line.status.code, + (int)pj_strlen(&entry->msg->line.status.reason), + pj_strbuf(&entry->msg->line.status.reason)); + } + + ast_cli(a->fd, "%-5.5d %-10.10ld %-5.5s %-24.24s %s\n", + entry->number, + entry->timestamp.tv_sec, + entry->transmitted ? "* ==>" : "* <==", + addr, + line); + } +} + +/*! \brief Cleanup routine for a history vector, serviced on a registered PJSIP thread */ +static int safe_vector_cleanup(void *obj) +{ + struct vector_history_t *vec = obj; + + AST_VECTOR_RESET(vec, clear_history_entry_cb); + AST_VECTOR_FREE(vec); + ast_free(vec); + + return 0; +} + +static char *pjsip_show_history(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct vector_history_t *vec = &vector_history; + struct pjsip_history_entry *entry = NULL; + + if (cmd == CLI_INIT) { + e->command = "pjsip show history"; + e->usage = + "Usage: pjsip show history [entry <num>|where [...]]\n" + " Displays the currently collected history or an\n" + " entry within the history.\n\n" + " * Running the command with no options will display\n" + " the entire history.\n" + " * Providing 'entry <num>' will display the full\n" + " detail of a particular entry in this history.\n" + " * Providing 'where ...' will allow for filtering\n" + " the history. The history can be filtered using\n" + " any of the following fields:\n" + " - number: The history entry number\n" + " - timestamp: The time associated with the history entry\n" + " - addr: The source/destination address of the SIP message\n" + " - sip.msg.request.method: The request method type\n" + " - sip.msg.call-id: The Call-ID header of the SIP message\n" + "\n" + " When filtering, standard Boolean operators can be used,\n" + " as well as 'like' for regexs.\n" + "\n" + " Example:\n" + " 'pjsip show history where number > 5 and (addr = \"192.168.0.3:5060\" or addr = \"192.168.0.5:5060\")'\n"; + return NULL; + } else if (cmd == CLI_GENERATE) { + return NULL; + } + + if (a->argc > 3) { + if (!strcasecmp(a->argv[3], "entry") && a->argc == 5) { + int num; + + if (sscanf(a->argv[4], "%30d", &num) != 1) { + ast_cli(a->fd, "'%s' is not a valid entry number\n", a->argv[4]); + return CLI_FAILURE; + } + + /* Get the entry at the provided position */ + ast_mutex_lock(&history_lock); + if (num >= AST_VECTOR_SIZE(&vector_history) || num < 0) { + ast_cli(a->fd, "Entry '%d' does not exist\n", num); + ast_mutex_unlock(&history_lock); + return CLI_FAILURE; + } + entry = ao2_bump(AST_VECTOR_GET(&vector_history, num)); + ast_mutex_unlock(&history_lock); + } else if (!strcasecmp(a->argv[3], "where")) { + vec = filter_history(a); + if (!vec) { + return CLI_FAILURE; + } + } else { + return CLI_SHOWUSAGE; + } + } + + if (AST_VECTOR_SIZE(vec) == 1) { + if (vec == &vector_history) { + ast_mutex_lock(&history_lock); + } + entry = ao2_bump(AST_VECTOR_GET(vec, 0)); + if (vec == &vector_history) { + ast_mutex_lock(&history_lock); + } + } + + if (entry) { + display_single_entry(a, entry); + } else { + if (vec == &vector_history) { + ast_mutex_lock(&history_lock); + } + + display_entry_list(a, vec); + + if (vec == &vector_history) { + ast_mutex_unlock(&history_lock); + } + } + + if (vec != &vector_history) { + ast_sip_push_task(NULL, safe_vector_cleanup, vec); + } + ao2_cleanup(entry); + + return CLI_SUCCESS; +} + +static char *pjsip_set_history(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + const char *what; + + if (cmd == CLI_INIT) { + e->command = "pjsip set history {on|off|clear}"; + e->usage = + "Usage: pjsip set history {on|off|clear}\n" + " Enables/disables/clears the PJSIP history.\n\n" + " Enabling the history will start recording transmitted/received\n" + " packets. Disabling the history will stop recording, but keep\n" + " the already received packets. Clearing the history will wipe\n" + " the received packets from memory.\n\n" + " As the PJSIP history is maintained in memory, and includes\n" + " all received/transmitted requests and responses, it should\n" + " only be enabled for debugging purposes, and cleared when done.\n"; + return NULL; + } else if (cmd == CLI_GENERATE) { + return NULL; + } + + what = a->argv[e->args - 1]; /* Guaranteed to exist */ + + if (a->argc == e->args) { + if (!strcasecmp(what, "on")) { + enabled = 1; + ast_cli(a->fd, "PJSIP History enabled\n"); + return CLI_SUCCESS; + } else if (!strcasecmp(what, "off")) { + enabled = 0; + ast_cli(a->fd, "PJSIP History disabled\n"); + return CLI_SUCCESS; + } else if (!strcasecmp(what, "clear")) { + ast_sip_push_task(NULL, clear_history_entries, NULL); + ast_cli(a->fd, "PJSIP History cleared\n"); + return CLI_SUCCESS; + } + } + + return CLI_SHOWUSAGE; +} + +static pjsip_module logging_module = { + .name = { "History Module", 14 }, + .priority = 0, + .on_rx_request = history_on_rx_msg, + .on_rx_response = history_on_rx_msg, + .on_tx_request = history_on_tx_msg, + .on_tx_response = history_on_tx_msg, +}; + +static struct ast_cli_entry cli_pjsip[] = { + AST_CLI_DEFINE(pjsip_set_history, "Enable/Disable PJSIP History"), + AST_CLI_DEFINE(pjsip_show_history, "Display PJSIP History"), +}; + +static int load_module(void) +{ + CHECK_PJSIP_MODULE_LOADED(); + + pj_caching_pool_init(&cachingpool, &pj_pool_factory_default_policy, 0); + + AST_VECTOR_INIT(&vector_history, HISTORY_INITIAL_SIZE); + + ast_sip_register_service(&logging_module); + ast_cli_register_multiple(cli_pjsip, ARRAY_LEN(cli_pjsip)); + + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + ast_cli_unregister_multiple(cli_pjsip, ARRAY_LEN(cli_pjsip)); + ast_sip_unregister_service(&logging_module); + + ast_sip_push_task_synchronous(NULL, clear_history_entries, NULL); + AST_VECTOR_FREE(&vector_history); + + pj_caching_pool_destroy(&cachingpool); + + return 0; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP History", + .support_level = AST_MODULE_SUPPORT_EXTENDED, + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_APP_DEPEND, + ); |