Add callbackextension matching & realtime callbackextensions This patch is based on the one by David Vossel, developer extrodinaire, at https://reviewboard.asterisk.org/r/344/. If multiple peers are defined with the same host/port, but differing callbackextensions, it chooses the peer with the matching callbackextension. Since callbackextension creates an outbound registration with the callbackextension as the Contact address, matching an incoming request by that (in addition to the host/port) makes a lot of sense. This patch also adds support for callbackextension to realtime by querying all peers with callbackextensions on reload and adding registrations for them. (closes issue ASTERISK-13456) Review: https://reviewboard.asterisk.org/r/344/ Review: https://reviewboard.asterisk.org/r/1717/ diff --git a/CHANGES b/CHANGES index de18858..6a22ae2 100644 --- a/CHANGES +++ b/CHANGES @@ -24,6 +24,12 @@ SIP Changes The LastMsgsSent value has been re-added with the same functionality as in previous versions of Asterisk. + * Add support to realtime for the 'callbackextension' option + * When multiple peers exist with the same address, but differing + callbackextension options, incoming requests that are matched by address + will be matched to the peer with the matching callbackextension if it is + available. + ------------------------------------------------------------------------------ --- Functionality changes since Asterisk 10.3.0 ------------------------------ ------------------------------------------------------------------------------ diff --git a/channels/chan_sip.c b/channels/chan_sip.c index 41fb935..ad8da22 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -1456,13 +1456,14 @@ static void destroy_association(struct sip_peer *peer); static void set_insecure_flags(struct ast_flags *flags, const char *value, int lineno); static int handle_common_options(struct ast_flags *flags, struct ast_flags *mask, struct ast_variable *v); static void set_socket_transport(struct sip_socket *socket, int transport); +static int peer_ipcmp_cb_full(void *obj, void *arg, void *data, int flags); /* Realtime device support */ static void realtime_update_peer(const char *peername, struct ast_sockaddr *addr, const char *username, const char *fullcontact, const char *useragent, int expirey, unsigned short deprecated_username, int lastms); static void update_peer(struct sip_peer *p, int expire); static struct ast_variable *get_insecure_variable_from_config(struct ast_config *config); static const char *get_name_from_variable(const struct ast_variable *var); -static struct sip_peer *realtime_peer(const char *peername, struct ast_sockaddr *sin, int devstate_only, int which_objects); +static struct sip_peer *realtime_peer(const char *peername, struct ast_sockaddr *sin, char *callbackexten, int devstate_only, int which_objects); static char *sip_prune_realtime(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); /*--- Internal UA client handling (outbound registrations) */ @@ -4891,7 +4892,7 @@ static struct ast_variable *realtime_peer_get_sippeer_helper(const char **name, /* If varregs is NULL, we don't use sipregs. If we return true, then *name is * set. Using empty if-bodies instead of goto's while avoiding unnecessary * indents. */ -static int realtime_peer_by_addr(const char **name, struct ast_sockaddr *addr, const char *ipaddr, struct ast_variable **var, struct ast_variable **varregs) +static int realtime_peer_by_addr(const char **name, struct ast_sockaddr *addr, const char *ipaddr, const char *callbackexten, struct ast_variable **var, struct ast_variable **varregs) { char portstring[6]; /* up to 5 digits plus null terminator */ ast_copy_string(portstring, ast_sockaddr_stringify_port(addr), sizeof(portstring)); @@ -4899,8 +4900,11 @@ static int realtime_peer_by_addr(const char **name, struct ast_sockaddr *addr, c /* We're not finding this peer by this name anymore. Reset it. */ *name = NULL; - /* First check for fixed IP hosts */ - if ((*var = ast_load_realtime("sippeers", "host", ipaddr, "port", portstring, SENTINEL))) { + /* First check for fixed IP hosts with matching callbackextensions, if specified */ + if (!ast_strlen_zero(callbackexten) && (*var = ast_load_realtime("sippeers", "host", ipaddr, "port", portstring, "callbackextension", callbackexten, SENTINEL))) { + ; + /* Check for fixed IP hosts */ + } else if ((*var = ast_load_realtime("sippeers", "host", ipaddr, "port", portstring, SENTINEL))) { ; /* Check for registered hosts (in sipregs) */ } else if (varregs && (*varregs = ast_load_realtime("sipregs", "ipaddr", ipaddr, "port", portstring, SENTINEL)) && @@ -4951,6 +4955,38 @@ static int realtime_peer_by_addr(const char **name, struct ast_sockaddr *addr, c return 1; } +static int register_realtime_peers_with_callbackextens(void) +{ + struct ast_config *cfg; + char *cat = NULL; + + if (!(ast_check_realtime("sippeers"))) { + return 0; + } + + /* This is hacky. We want name to be the cat, so it is the first property */ + if (!(cfg = ast_load_realtime_multientry("sippeers", "name LIKE", "%", "callbackextension LIKE", "%", SENTINEL))) { + return -1; + } + + while ((cat = ast_category_browse(cfg, cat))) { + struct sip_peer *peer; + struct ast_variable *var = ast_category_root(cfg, cat); + + if (!(peer = build_peer(cat, var, NULL, TRUE, FALSE))) { + continue; + } + ast_log(LOG_NOTICE, "Created realtime peer '%s' for registration\n", peer->name); + + peer->is_realtime = 1; + sip_unref_peer(peer, "register_realtime_peers: Done registering releasing"); + } + + ast_config_destroy(cfg); + + return 0; +} + /*! \brief realtime_peer: Get peer from realtime storage * Checks the "sippeers" realtime family from extconfig.conf * Checks the "sipregs" realtime family from extconfig.conf if it's configured. @@ -4960,7 +4996,7 @@ static int realtime_peer_by_addr(const char **name, struct ast_sockaddr *addr, c * \note This is never called with both newpeername and addr at the same time. * If you do, be prepared to get a peer with a different name than newpeername. */ -static struct sip_peer *realtime_peer(const char *newpeername, struct ast_sockaddr *addr, int devstate_only, int which_objects) +static struct sip_peer *realtime_peer(const char *newpeername, struct ast_sockaddr *addr, char *callbackexten, int devstate_only, int which_objects) { struct sip_peer *peer = NULL; struct ast_variable *var = NULL; @@ -4976,7 +5012,7 @@ static struct sip_peer *realtime_peer(const char *newpeername, struct ast_sockad if (newpeername && realtime_peer_by_name(&newpeername, addr, ipaddr, &var, realtimeregs ? &varregs : NULL)) { ; - } else if (addr && realtime_peer_by_addr(&newpeername, addr, ipaddr, &var, realtimeregs ? &varregs : NULL)) { + } else if (addr && realtime_peer_by_addr(&newpeername, addr, ipaddr, callbackexten, &var, realtimeregs ? &varregs : NULL)) { ; } else { return NULL; @@ -5052,20 +5088,7 @@ static int find_by_name(void *obj, void *arg, void *data, int flags) return CMP_MATCH | CMP_STOP; } -/*! - * \brief Locate device by name or ip address - * \param peer, sin, realtime, devstate_only, transport - * \param which_objects Define which objects should be matched when doing a lookup - * by name. Valid options are FINDUSERS, FINDPEERS, or FINDALLDEVICES. - * Note that this option is not used at all when doing a lookup by IP. - * - * This is used on find matching device on name or ip/port. - * If the device was declared as type=peer, we don't match on peer name on incoming INVITEs. - * - * \note Avoid using this function in new functions if there is a way to avoid it, - * since it might cause a database lookup. - */ -struct sip_peer *sip_find_peer(const char *peer, struct ast_sockaddr *addr, int realtime, int which_objects, int devstate_only, int transport) +static struct sip_peer *sip_find_peer_full(const char *peer, struct ast_sockaddr *addr, char *callbackexten, int realtime, int which_objects, int devstate_only, int transport) { struct sip_peer *p = NULL; struct sip_peer tmp_peer; @@ -5077,10 +5100,10 @@ struct sip_peer *sip_find_peer(const char *peer, struct ast_sockaddr *addr, int ast_sockaddr_copy(&tmp_peer.addr, addr); tmp_peer.flags[0].flags = 0; tmp_peer.transports = transport; - p = ao2_t_find(peers_by_ip, &tmp_peer, OBJ_POINTER, "ao2_find in peers_by_ip table"); /* WAS: p = ASTOBJ_CONTAINER_FIND_FULL(&peerl, sin, name, sip_addr_hashfunc, 1, sip_addrcmp); */ + p = ao2_t_callback_data(peers_by_ip, OBJ_POINTER, peer_ipcmp_cb_full, &tmp_peer, callbackexten, "ao2_find in peers_by_ip table"); if (!p) { ast_set_flag(&tmp_peer.flags[0], SIP_INSECURE_PORT); - p = ao2_t_find(peers_by_ip, &tmp_peer, OBJ_POINTER, "ao2_find in peers_by_ip table 2"); /* WAS: p = ASTOBJ_CONTAINER_FIND_FULL(&peerl, sin, name, sip_addr_hashfunc, 1, sip_addrcmp); */ + p = ao2_t_callback_data(peers_by_ip, OBJ_POINTER, peer_ipcmp_cb_full, &tmp_peer, callbackexten, "ao2_find in peers_by_ip table 2"); if (p) { return p; } @@ -5088,7 +5111,9 @@ struct sip_peer *sip_find_peer(const char *peer, struct ast_sockaddr *addr, int } if (!p && (realtime || devstate_only)) { - p = realtime_peer(peer, addr, devstate_only, which_objects); + /* realtime_peer will return a peer with matching callbackexten if possible, otherwise one matching + * without the callbackexten */ + p = realtime_peer(peer, addr, callbackexten, devstate_only, which_objects); if (p) { switch (which_objects) { case FINDUSERS: @@ -5112,6 +5137,29 @@ struct sip_peer *sip_find_peer(const char *peer, struct ast_sockaddr *addr, int return p; } +/*! + * \brief Locate device by name or ip address + * \param peer, sin, realtime, devstate_only, transport + * \param which_objects Define which objects should be matched when doing a lookup + * by name. Valid options are FINDUSERS, FINDPEERS, or FINDALLDEVICES. + * Note that this option is not used at all when doing a lookup by IP. + * + * This is used on find matching device on name or ip/port. + * If the device was declared as type=peer, we don't match on peer name on incoming INVITEs. + * + * \note Avoid using this function in new functions if there is a way to avoid it, + * since it might cause a database lookup. + */ +struct sip_peer *sip_find_peer(const char *peer, struct ast_sockaddr *addr, int realtime, int which_objects, int devstate_only, int transport) +{ + return sip_find_peer_full(peer, addr, NULL, realtime, which_objects, devstate_only, transport); +} + +static struct sip_peer *sip_find_peer_by_ip_and_exten(struct ast_sockaddr *addr, char *callbackexten, int transport) +{ + return sip_find_peer_full(NULL, addr, callbackexten, TRUE, FINDPEERS, FALSE, transport); +} + /*! \brief Set nat mode on the various data sockets */ static void do_setnat(struct sip_pvt *p) { @@ -8418,16 +8466,16 @@ static struct sip_pvt *find_call(struct sip_request *req, struct ast_sockaddr *a /*! \brief create sip_registry object from register=> line in sip.conf and link into reg container */ static int sip_register(const char *value, int lineno) { - struct sip_registry *reg; + struct sip_registry *reg, *tmp; if (!(reg = ast_calloc_with_stringfields(1, struct sip_registry, 256))) { ast_log(LOG_ERROR, "Out of memory. Can't allocate SIP registry entry\n"); return -1; } - ast_atomic_fetchadd_int(®objs, 1); ASTOBJ_INIT(reg); + ast_copy_string(reg->name, value, sizeof(reg->name)); if (sip_parse_register_line(reg, default_expiry, value, lineno)) { registry_unref(reg, "failure to parse, unref the reg pointer"); return -1; @@ -8438,8 +8486,13 @@ static int sip_register(const char *value, int lineno) reg->refresh = reg->expiry = reg->configured_expiry = default_expiry; } - /* Add the new registry entry to the list */ - ASTOBJ_CONTAINER_LINK(®l, reg); + /* Add the new registry entry to the list, but only if it isn't already there */ + if ((tmp = ASTOBJ_CONTAINER_FIND(®l, reg->name))) { + registry_unref(tmp, "throw away found registry"); + } else { + ast_atomic_fetchadd_int(®objs, 1); + ASTOBJ_CONTAINER_LINK(®l, reg); + } /* release the reference given by ASTOBJ_INIT. The container has another reference */ registry_unref(reg, "unref the reg pointer"); @@ -16433,7 +16486,14 @@ static enum check_auth_result check_peer_ok(struct sip_pvt *p, char *of, /* Then find devices based on IP */ if (!peer) { - peer = sip_find_peer(NULL, &p->recv, TRUE, FINDPEERS, FALSE, p->socket.type); + char *uri_tmp, *callback = NULL, *dummy; + uri_tmp = ast_strdupa(uri2); + parse_uri(uri_tmp, "sip:,sips:", &callback, &dummy, &dummy, &dummy); + if (!ast_strlen_zero(callback) && (peer = sip_find_peer_by_ip_and_exten(&p->recv, callback, p->socket.type))) { + ; /* found, fall through */ + } else { + peer = sip_find_peer(NULL, &p->recv, TRUE, FINDPEERS, FALSE, p->socket.type); + } } } @@ -28283,7 +28343,6 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str time_t regseconds = 0; struct ast_flags peerflags[3] = {{(0)}}; struct ast_flags mask[3] = {{(0)}}; - char callback[256] = ""; struct sip_peer tmp_peer; const char *srvlookup = NULL; static int deprecation_warning = 1; @@ -28588,7 +28647,7 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str } else if (!strcasecmp(v->name, "regexten")) { ast_string_field_set(peer, regexten, v->value); } else if (!strcasecmp(v->name, "callbackextension")) { - ast_copy_string(callback, v->value, sizeof(callback)); + ast_string_field_set(peer, callback, v->value); } else if (!strcasecmp(v->name, "amaflags")) { format = ast_cdr_amaflags2int(v->value); if (format < 0) { @@ -28960,9 +29019,9 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, str ast_free_ha(oldha); ast_free_ha(olddirectmediaha); - if (!ast_strlen_zero(callback)) { /* build string from peer info */ + if (!ast_strlen_zero(peer->callback)) { /* build string from peer info */ char *reg_string; - if (ast_asprintf(®_string, "%s?%s:%s@%s/%s", peer->name, peer->username, !ast_strlen_zero(peer->remotesecret) ? peer->remotesecret : peer->secret, peer->tohost, callback) >= 0) { + if (ast_asprintf(®_string, "%s?%s:%s@%s/%s", peer->name, peer->username, !ast_strlen_zero(peer->remotesecret) ? peer->remotesecret : peer->secret, peer->tohost, peer->callback) >= 0) { sip_register(reg_string, 0); /* XXX TODO: count in registry_count */ ast_free(reg_string); } @@ -30116,6 +30175,8 @@ static int reload_config(enum channelreloadreason reason) /* Release configuration from memory */ ast_config_destroy(cfg); + register_realtime_peers_with_callbackextens(); + /* Load the list of manual NOTIFY types to support */ if (notify_types) { ast_config_destroy(notify_types); @@ -31013,9 +31074,17 @@ static int peer_iphash_cb(const void *obj, const int flags) * * \note the peer's addr struct provides to fields combined to make a key: the sin_addr.s_addr and sin_port fields. */ -static int peer_ipcmp_cb(void *obj, void *arg, int flags) +static int peer_ipcmp_cb_full(void *obj, void *arg, void *data, int flags) { struct sip_peer *peer = obj, *peer2 = arg; + char *callback = data; + + if (!ast_strlen_zero(callback) && strcasecmp(peer->callback, callback)) { + /* We require a callback extension match, but don't have one */ + return 0; + } + + /* At this point, we match the callback extension if we need to. Carry on. */ if (ast_sockaddr_cmp_addr(&peer->addr, &peer2->addr)) { /* IP doesn't match */ @@ -31038,6 +31107,10 @@ static int peer_ipcmp_cb(void *obj, void *arg, int flags) (CMP_MATCH | CMP_STOP) : 0; } +static int peer_ipcmp_cb(void *obj, void *arg, int flags) +{ + return peer_ipcmp_cb_full(obj, arg, NULL, flags); +} static int threadt_hash_cb(const void *obj, const int flags) { diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h index ab2ed1b..48626aa 100644 --- a/channels/sip/include/sip.h +++ b/channels/sip/include/sip.h @@ -1246,6 +1246,7 @@ struct sip_peer { AST_STRING_FIELD(mwi_from); /*!< Name to place in From header for outgoing NOTIFY requests */ AST_STRING_FIELD(engine); /*!< RTP Engine to use */ AST_STRING_FIELD(unsolicited_mailbox); /*!< Mailbox to store received unsolicited MWI NOTIFY messages information in */ + AST_STRING_FIELD(callback); /*!< Callback extension */ ); struct sip_socket socket; /*!< Socket used for this peer */ enum sip_transport default_outbound_transport; /*!< Peer Registration may change the default outbound transport. @@ -1331,7 +1332,7 @@ struct sip_peer { * \todo Convert this to astobj2 */ struct sip_registry { - ASTOBJ_COMPONENTS_FULL(struct sip_registry,1,1); + ASTOBJ_COMPONENTS_FULL(struct sip_registry, 80, 1); AST_DECLARE_STRING_FIELDS( AST_STRING_FIELD(callid); /*!< Global Call-ID */ AST_STRING_FIELD(realm); /*!< Authorization realm */ diff --git a/contrib/realtime/postgresql/realtime.sql b/contrib/realtime/postgresql/realtime.sql index f14cd79..7b8dcec 100644 --- a/contrib/realtime/postgresql/realtime.sql +++ b/contrib/realtime/postgresql/realtime.sql @@ -72,7 +72,8 @@ lastms integer DEFAULT 0 NOT NULL, defaultuser character varying(80), fullcontact character varying(80), regserver character varying(30), -useragent character varying(40) +useragent character varying(40), +callbackextension character varying(40) ); drop table voicemail_users;