From f29bc375ee021410406820f65047a2ea1026148f Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Mon, 2 Apr 2007 11:44:47 +0000 Subject: Ticket #205: merged proxy functionalities from stable to trunk git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1127 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip-apps/build/Samples-vc.mak | 2 + pjsip-apps/build/Samples.mak | 2 + pjsip-apps/build/samples.dsp | 12 + pjsip-apps/build/samples.vcproj | 13 + pjsip-apps/src/pjsua/pjsua_app.c | 11 +- pjsip-apps/src/samples/proxy.h | 525 ++++++++++++++++++++++++++++ pjsip-apps/src/samples/stateful_proxy.c | 580 +++++++++++++++++++++++++++++++ pjsip-apps/src/samples/stateless_proxy.c | 250 +++++++++++++ pjsip/include/pjsip/sip_msg.h | 13 + pjsip/include/pjsip/sip_util.h | 50 ++- pjsip/include/pjsua-lib/pjsua.h | 19 + pjsip/src/pjsip/sip_msg.c | 32 ++ pjsip/src/pjsip/sip_util.c | 4 + pjsip/src/pjsip/sip_util_proxy.c | 335 ++++++++++++++++-- pjsip/src/pjsua-lib/pjsua_acc.c | 39 +++ 15 files changed, 1842 insertions(+), 45 deletions(-) create mode 100644 pjsip-apps/src/samples/proxy.h create mode 100644 pjsip-apps/src/samples/stateful_proxy.c create mode 100644 pjsip-apps/src/samples/stateless_proxy.c diff --git a/pjsip-apps/build/Samples-vc.mak b/pjsip-apps/build/Samples-vc.mak index c4f0d6c9..18d5d65c 100644 --- a/pjsip-apps/build/Samples-vc.mak +++ b/pjsip-apps/build/Samples-vc.mak @@ -52,6 +52,8 @@ SAMPLES = $(BINDIR)\confsample.exe \ $(BINDIR)\sipstateless.exe \ $(BINDIR)\sndinfo.exe \ $(BINDIR)\sndtest.exe \ + $(BINDIR)\stateful_proxy.exe \ + $(BINDIR)\stateless_proxy.exe \ $(BINDIR)\streamutil.exe \ $(BINDIR)\tonegen.exe diff --git a/pjsip-apps/build/Samples.mak b/pjsip-apps/build/Samples.mak index a7eb5ad4..66113fda 100644 --- a/pjsip-apps/build/Samples.mak +++ b/pjsip-apps/build/Samples.mak @@ -57,6 +57,8 @@ SAMPLES := confsample \ sipstateless \ sndinfo \ sndtest \ + stateful_proxy \ + stateless_proxy \ streamutil \ tonegen diff --git a/pjsip-apps/build/samples.dsp b/pjsip-apps/build/samples.dsp index f6373600..91e5fb77 100644 --- a/pjsip-apps/build/samples.dsp +++ b/pjsip-apps/build/samples.dsp @@ -154,6 +154,14 @@ SOURCE=..\src\samples\sndtest.c # End Source File # Begin Source File +SOURCE=..\src\samples\stateful_proxy.c +# End Source File +# Begin Source File + +SOURCE=..\src\samples\stateless_proxy.c +# End Source File +# Begin Source File + SOURCE=..\src\samples\streamutil.c # End Source File # Begin Source File @@ -166,6 +174,10 @@ SOURCE=..\src\samples\tonegen.c # PROP Default_Filter "h;hpp;hxx;hm;inl" # Begin Source File +SOURCE=..\src\samples\proxy.h +# End Source File +# Begin Source File + SOURCE=..\src\samples\util.h # End Source File # End Group diff --git a/pjsip-apps/build/samples.vcproj b/pjsip-apps/build/samples.vcproj index 422f06c8..986dacbd 100644 --- a/pjsip-apps/build/samples.vcproj +++ b/pjsip-apps/build/samples.vcproj @@ -4,6 +4,7 @@ Version="8.00" Name="samples" ProjectGUID="{E378A1FC-0C9C-4462-860F-7E60BC1BF84E}" + RootNamespace="samples" Keyword="MakeFileProj" > @@ -136,6 +137,14 @@ RelativePath="..\src\samples\sndtest.c" > + + + + @@ -149,6 +158,10 @@ Name="Header Files" Filter="h;hpp;hxx;hm;inl" > + + diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c index 53b96415..ab72f847 100644 --- a/pjsip-apps/src/pjsua/pjsua_app.c +++ b/pjsip-apps/src/pjsua/pjsua_app.c @@ -2056,7 +2056,6 @@ static void send_request(char *cstr_method, const pj_str_t *dst_uri) pj_str_t str_method; pjsip_method method; pjsip_tx_data *tdata; - pjsua_acc_info acc_info; pjsip_endpoint *endpt; pj_status_t status; @@ -2065,15 +2064,7 @@ static void send_request(char *cstr_method, const pj_str_t *dst_uri) str_method = pj_str(cstr_method); pjsip_method_init_np(&method, &str_method); - pjsua_acc_get_info(current_acc, &acc_info); - - status = pjsip_endpt_create_request(endpt, &method, dst_uri, - &acc_info.acc_uri, dst_uri, - NULL, NULL, -1, NULL, &tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create request", status); - return; - } + status = pjsua_acc_create_request(current_acc, &method, dst_uri, &tdata); status = pjsip_endpt_send_request(endpt, tdata, -1, NULL, NULL); if (status != PJ_SUCCESS) { diff --git a/pjsip-apps/src/samples/proxy.h b/pjsip-apps/src/samples/proxy.h new file mode 100644 index 00000000..5f62a503 --- /dev/null +++ b/pjsip-apps/src/samples/proxy.h @@ -0,0 +1,525 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2007 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include + + +/* Options */ +static struct global_struct +{ + pj_caching_pool cp; + pjsip_endpoint *endpt; + int port; + pj_pool_t *pool; + + pj_thread_t *thread; + pj_bool_t quit_flag; + + pj_bool_t record_route; + + unsigned name_cnt; + pjsip_host_port name[16]; +} global; + + + +static void app_perror(const char *msg, pj_status_t status) +{ + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(1,(THIS_FILE, "%s: %s", msg, errmsg)); +} + + +static void usage(void) +{ + puts("Options:\n" + "\n" + " --port N Set local listener port to N\n" + " --rr Perform record routing\n" + " --log-level N Set log level to N (default: 4)\n" + " --help Show this help screen\n" + ); +} + + +static pj_status_t init_options(int argc, char *argv[]) +{ + struct pj_getopt_option long_opt[] = { + { "port", 1, 0, 'p'}, + { "rr", 0, 0, 'R'}, + { "log-level", 1, 0, 'L'}, + { "help", 0, 0, 'h'}, + }; + int c; + int opt_ind; + + pj_optind = 0; + while((c=pj_getopt_long(argc, argv, "", long_opt, &opt_ind))!=-1) { + switch (c) { + case 'p': + global.port = atoi(pj_optarg); + printf("Port is set to %d\n", global.pool); + break; + + case 'R': + global.record_route = PJ_TRUE; + printf("Using record route mode\n"); + break; + + case 'L': + pj_log_set_level(atoi(pj_optarg)); + break; + + case 'h': + usage(); + return -1; + + default: + puts("Unknown option. Run with --help for help."); + return -1; + } + } + + return PJ_SUCCESS; +} + + +/***************************************************************************** + * This is a very simple PJSIP module, whose sole purpose is to display + * incoming and outgoing messages to log. This module will have priority + * higher than transport layer, which means: + * + * - incoming messages will come to this module first before reaching + * transaction layer. + * + * - outgoing messages will come to this module last, after the message + * has been 'printed' to contiguous buffer by transport layer and + * appropriate transport instance has been decided for this message. + * + */ + +/* Notification on incoming messages */ +static pj_bool_t logging_on_rx_msg(pjsip_rx_data *rdata) +{ + PJ_LOG(5,(THIS_FILE, "RX %d bytes %s from %s %s:%d:\n" + "%.*s\n" + "--end msg--", + rdata->msg_info.len, + pjsip_rx_data_get_info(rdata), + rdata->tp_info.transport->type_name, + rdata->pkt_info.src_name, + rdata->pkt_info.src_port, + (int)rdata->msg_info.len, + rdata->msg_info.msg_buf)); + + /* Always return false, otherwise messages will not get processed! */ + return PJ_FALSE; +} + +/* Notification on outgoing messages */ +static pj_status_t logging_on_tx_msg(pjsip_tx_data *tdata) +{ + + /* Important note: + * tp_info field is only valid after outgoing messages has passed + * transport layer. So don't try to access tp_info when the module + * has lower priority than transport layer. + */ + + PJ_LOG(5,(THIS_FILE, "TX %d bytes %s to %s %s:%d:\n" + "%.*s\n" + "--end msg--", + (tdata->buf.cur - tdata->buf.start), + pjsip_tx_data_get_info(tdata), + tdata->tp_info.transport->type_name, + tdata->tp_info.dst_name, + tdata->tp_info.dst_port, + (int)(tdata->buf.cur - tdata->buf.start), + tdata->buf.start)); + + /* Always return success, otherwise message will not get sent! */ + return PJ_SUCCESS; +} + +/* The module instance. */ +static pjsip_module mod_msg_logger = +{ + NULL, NULL, /* prev, next. */ + { "mod-msg-logger", 14 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &logging_on_rx_msg, /* on_rx_request() */ + &logging_on_rx_msg, /* on_rx_response() */ + &logging_on_tx_msg, /* on_tx_request. */ + &logging_on_tx_msg, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + +}; + + +static pj_status_t init_stack(void) +{ + pj_status_t status; + + /* Must init PJLIB first: */ + status = pj_init(); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + + /* Then init PJLIB-UTIL: */ + status = pjlib_util_init(); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + + /* Must create a pool factory before we can allocate any memory. */ + pj_caching_pool_init(&global.cp, &pj_pool_factory_default_policy, 0); + + /* Create the endpoint: */ + status = pjsip_endpt_create(&global.cp.factory, NULL, &global.endpt); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); + + /* Init transaction layer for stateful proxy only */ +#if STATEFUL + status = pjsip_tsx_layer_init_module(global.endpt); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); +#endif + + /* Create listening transport */ + { + pj_sockaddr_in addr; + + addr.sin_family = PJ_AF_INET; + addr.sin_addr.s_addr = 0; + addr.sin_port = pj_htons((pj_uint16_t)global.port); + + status = pjsip_udp_transport_start( global.endpt, &addr, + NULL, 1, NULL); + if (status != PJ_SUCCESS) + return status; + } + + /* Create pool for the application */ + global.pool = pj_pool_create(&global.cp.factory, "proxyapp", + 4000, 4000, NULL); + + /* Register the logger module */ + pjsip_endpt_register_module(global.endpt, &mod_msg_logger); + + return PJ_SUCCESS; +} + + +static pj_status_t init_proxy(void) +{ + pj_in_addr addr; + unsigned i; + + /* List all names matching local endpoint. + * Note that PJLIB version 0.6 and newer has a function to + * enumerate local IP interface (pj_enum_ip_interface()), so + * by using it would be possible to list all IP interfaces in + * this host. + */ + + /* The first address is important since this would be the one + * to be added in Record-Route. + */ + if (pj_gethostip(&addr) == PJ_SUCCESS) { + pj_strdup2(global.pool, &global.name[global.name_cnt].host, + pj_inet_ntoa(addr)); + global.name[global.name_cnt].port = global.port; + global.name_cnt++; + } + + global.name[global.name_cnt].host = pj_str("127.0.0.1"); + global.name[global.name_cnt].port = global.port; + global.name_cnt++; + + global.name[global.name_cnt].host = *pj_gethostname(); + global.name[global.name_cnt].port = global.port; + global.name_cnt++; + + global.name[global.name_cnt].host = pj_str("localhost"); + global.name[global.name_cnt].port = global.port; + global.name_cnt++; + + PJ_LOG(3,(THIS_FILE, "Proxy started, listening on port %d", global.port)); + PJ_LOG(3,(THIS_FILE, "Local host aliases:")); + for (i=0; iport == global.name[i].port || + (uri->port==0 && global.name[i].port==5060)) && + pj_stricmp(&uri->host, &global.name[i].host)==0) + { + /* Match */ + return PJ_TRUE; + } + } + + /* Doesn't match */ + return PJ_FALSE; +} + + +/* Proxy utility to verify incoming requests. + * Return non-zero if verification failed. + */ +static pj_status_t proxy_verify_request(pjsip_rx_data *rdata) +{ + const pj_str_t STR_PROXY_REQUIRE = {"Proxy-Require", 13}; + + /* RFC 3261 Section 16.3 Request Validation */ + + /* Before an element can proxy a request, it MUST verify the message's + * validity. A valid message must pass the following checks: + * + * 1. Reasonable Syntax + * 2. URI scheme + * 3. Max-Forwards + * 4. (Optional) Loop Detection + * 5. Proxy-Require + * 6. Proxy-Authorization + */ + + /* 1. Reasonable Syntax. + * This would have been checked by transport layer. + */ + + /* 2. URI scheme. + * We only want to support "sip:" URI scheme for this simple proxy. + */ + if (!PJSIP_URI_SCHEME_IS_SIP(rdata->msg_info.msg->line.req.uri)) { + pjsip_endpt_respond_stateless(global.endpt, rdata, + PJSIP_SC_UNSUPPORTED_URI_SCHEME, NULL, + NULL, NULL); + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_UNSUPPORTED_URI_SCHEME); + } + + /* 3. Max-Forwards. + * Send error if Max-Forwards is 1 or lower. + */ + if (rdata->msg_info.max_fwd && rdata->msg_info.max_fwd->ivalue <= 1) { + pjsip_endpt_respond_stateless(global.endpt, rdata, + PJSIP_SC_TOO_MANY_HOPS, NULL, + NULL, NULL); + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_TOO_MANY_HOPS); + } + + /* 4. (Optional) Loop Detection. + * Nah, we don't do that with this simple proxy. + */ + + /* 5. Proxy-Require */ + if (pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_PROXY_REQUIRE, + NULL) != NULL) + { + pjsip_endpt_respond_stateless(global.endpt, rdata, + PJSIP_SC_BAD_EXTENSION, NULL, + NULL, NULL); + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_EXTENSION); + } + + /* 6. Proxy-Authorization. + * Nah, we don't require any authorization with this sample. + */ + + return PJ_SUCCESS; +} + + +/* Process route information in the reqeust */ +static pj_status_t proxy_process_routing(pjsip_tx_data *tdata) +{ + pjsip_sip_uri *target; + pjsip_route_hdr *hroute; + + /* RFC 3261 Section 16.4 Route Information Preprocessing */ + + target = (pjsip_sip_uri*) tdata->msg->line.req.uri; + + /* The proxy MUST inspect the Request-URI of the request. If the + * Request-URI of the request contains a value this proxy previously + * placed into a Record-Route header field (see Section 16.6 item 4), + * the proxy MUST replace the Request-URI in the request with the last + * value from the Route header field, and remove that value from the + * Route header field. The proxy MUST then proceed as if it received + * this modified request. + */ + /* Nah, we don't want to support this */ + + /* If the Request-URI contains a maddr parameter, the proxy MUST check + * to see if its value is in the set of addresses or domains the proxy + * is configured to be responsible for. If the Request-URI has a maddr + * parameter with a value the proxy is responsible for, and the request + * was received using the port and transport indicated (explicitly or by + * default) in the Request-URI, the proxy MUST strip the maddr and any + * non-default port or transport parameter and continue processing as if + * those values had not been present in the request. + */ + if (target->maddr_param.slen != 0) { + pjsip_sip_uri maddr_uri; + + maddr_uri.host = target->maddr_param; + maddr_uri.port = global.port; + + if (is_uri_local(&maddr_uri)) { + target->maddr_param.slen = 0; + target->port = 0; + target->transport_param.slen = 0; + } + } + + /* If the first value in the Route header field indicates this proxy, + * the proxy MUST remove that value from the request. + */ + hroute = (pjsip_route_hdr*) + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL); + if (hroute && is_uri_local((pjsip_sip_uri*)hroute->name_addr.uri)) { + pj_list_erase(hroute); + } + + return PJ_SUCCESS; +} + + +/* Postprocess the request before forwarding it */ +static void proxy_postprocess(pjsip_tx_data *tdata) +{ + /* Optionally record-route */ + if (global.record_route) { + char uribuf[128]; + pj_str_t uri; + const pj_str_t H_RR = { "Record-Route", 12 }; + pjsip_generic_string_hdr *rr; + + pj_ansi_snprintf(uribuf, sizeof(uribuf), "", + (int)global.name[0].host.slen, + global.name[0].host.ptr, + global.name[0].port); + uri = pj_str(uribuf); + rr = pjsip_generic_string_hdr_create(tdata->pool, + &H_RR, &uri); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)rr); + } +} + + +/* Calculate new target for the request */ +static pj_status_t proxy_calculate_target(pjsip_rx_data *rdata, + pjsip_tx_data *tdata) +{ + pjsip_sip_uri *target; + + /* RFC 3261 Section 16.5 Determining Request Targets */ + + target = (pjsip_sip_uri*) tdata->msg->line.req.uri; + + /* If the Request-URI of the request contains an maddr parameter, the + * Request-URI MUST be placed into the target set as the only target + * URI, and the proxy MUST proceed to Section 16.6. + */ + if (target->maddr_param.slen) { + proxy_postprocess(tdata); + return PJ_SUCCESS; + } + + + /* If the domain of the Request-URI indicates a domain this element is + * not responsible for, the Request-URI MUST be placed into the target + * set as the only target, and the element MUST proceed to the task of + * Request Forwarding (Section 16.6). + */ + if (!is_uri_local(target)) { + proxy_postprocess(tdata); + return PJ_SUCCESS; + } + + /* If the target set for the request has not been predetermined as + * described above, this implies that the element is responsible for the + * domain in the Request-URI, and the element MAY use whatever mechanism + * it desires to determine where to send the request. + */ + + /* We're not interested to receive request destined to us, so + * respond with 404/Not Found (only if request is not ACK!). + */ + if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) { + pjsip_endpt_respond_stateless(global.endpt, rdata, + PJSIP_SC_NOT_FOUND, NULL, + NULL, NULL); + } + + /* Delete the request since we're not forwarding it */ + pjsip_tx_data_dec_ref(tdata); + + return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_FOUND); +} + + +/* Destroy stack */ +static void destroy_stack(void) +{ + pjsip_endpt_destroy(global.endpt); + pj_pool_release(global.pool); + pj_caching_pool_destroy(&global.cp); + + pj_shutdown(); +} + diff --git a/pjsip-apps/src/samples/stateful_proxy.c b/pjsip-apps/src/samples/stateful_proxy.c new file mode 100644 index 00000000..d83ec91c --- /dev/null +++ b/pjsip-apps/src/samples/stateful_proxy.c @@ -0,0 +1,580 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2007 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#define THIS_FILE "stateful_proxy.c" + +/* Common proxy functions */ +#define STATEFUL 1 +#include "proxy.h" + + +/* + * mod_stateful_proxy is the module to receive SIP request and + * response message that is outside any transaction context. + */ +static pj_bool_t proxy_on_rx_request(pjsip_rx_data *rdata ); +static pj_bool_t proxy_on_rx_response(pjsip_rx_data *rdata ); + +static pjsip_module mod_stateful_proxy = +{ + NULL, NULL, /* prev, next. */ + { "mod-stateful-proxy", 18 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_UA_PROXY_LAYER, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &proxy_on_rx_request, /* on_rx_request() */ + &proxy_on_rx_response, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + + +/* + * mod_tu (tu=Transaction User) is the module to receive notification + * from transaction when the transaction state has changed. + */ +static void tu_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event); + +static pjsip_module mod_tu = +{ + NULL, NULL, /* prev, next. */ + { "mod-transaction-user", 20 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + NULL, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + &tu_on_tsx_state, /* on_tsx_state() */ +}; + + +/* This is the data that is attached to the UAC transaction */ +struct uac_data +{ + pjsip_transaction *uas_tsx; + pj_timer_entry timer; +}; + + +/* This is the data that is attached to the UAS transaction */ +struct uas_data +{ + pjsip_transaction *uac_tsx; +}; + + + +static pj_status_t init_stateful_proxy(void) +{ + pj_status_t status; + + status = pjsip_endpt_register_module( global.endpt, &mod_stateful_proxy); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); + + status = pjsip_endpt_register_module( global.endpt, &mod_tu); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); + + return PJ_SUCCESS; +} + + +/* Callback to be called to handle new incoming requests. */ +static pj_bool_t proxy_on_rx_request( pjsip_rx_data *rdata ) +{ + pjsip_transaction *uas_tsx, *uac_tsx; + struct uac_data *uac_data; + struct uas_data *uas_data; + pjsip_tx_data *tdata; + pj_status_t status; + + if (rdata->msg_info.msg->line.req.method.id != PJSIP_CANCEL_METHOD) { + + /* Verify incoming request */ + status = proxy_verify_request(rdata); + if (status != PJ_SUCCESS) { + app_perror("RX invalid request", status); + return PJ_TRUE; + } + + /* + * Request looks sane, next clone the request to create transmit data. + */ + status = pjsip_endpt_create_request_fwd(global.endpt, rdata, NULL, + NULL, 0, &tdata); + if (status != PJ_SUCCESS) { + pjsip_endpt_respond_stateless(global.endpt, rdata, + PJSIP_SC_INTERNAL_SERVER_ERROR, + NULL, NULL, NULL); + return PJ_TRUE; + } + + + /* Process routing */ + status = proxy_process_routing(tdata); + if (status != PJ_SUCCESS) { + app_perror("Error processing route", status); + return PJ_TRUE; + } + + /* Calculate target */ + status = proxy_calculate_target(rdata, tdata); + if (status != PJ_SUCCESS) { + app_perror("Error calculating target", status); + return PJ_TRUE; + } + + /* Everything is set to forward the request. */ + + /* If this is an ACK request, forward statelessly. + * This happens if the proxy records route and this ACK + * is sent for 2xx response. An ACK that is sent for non-2xx + * final response will be absorbed by transaction layer, and + * it will not be received by on_rx_request() callback. + */ + if (tdata->msg->line.req.method.id == PJSIP_ACK_METHOD) { + status = pjsip_endpt_send_request_stateless(global.endpt, tdata, + NULL, NULL); + if (status != PJ_SUCCESS) { + app_perror("Error forwarding request", status); + return PJ_TRUE; + } + + return PJ_TRUE; + } + + /* Create UAC transaction for forwarding the request. + * Set our module as the transaction user to receive further + * events from this transaction. + */ + status = pjsip_tsx_create_uac(&mod_tu, tdata, &uac_tsx); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + pjsip_endpt_respond_stateless(global.endpt, rdata, + PJSIP_SC_INTERNAL_SERVER_ERROR, + NULL, NULL, NULL); + return PJ_TRUE; + } + + /* Create UAS transaction to handle incoming request */ + status = pjsip_tsx_create_uas(&mod_tu, rdata, &uas_tsx); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + pjsip_endpt_respond_stateless(global.endpt, rdata, + PJSIP_SC_INTERNAL_SERVER_ERROR, + NULL, NULL, NULL); + pjsip_tsx_terminate(uac_tsx, PJSIP_SC_INTERNAL_SERVER_ERROR); + return PJ_TRUE; + } + + /* Feed the request to the UAS transaction to drive it's state + * out of NULL state. + */ + pjsip_tsx_recv_msg(uas_tsx, rdata); + + /* Attach a data to the UAC transaction, to be used to find the + * UAS transaction when we receive response in the UAC side. + */ + uac_data = (struct uac_data*) + pj_pool_alloc(uac_tsx->pool, sizeof(struct uac_data)); + uac_data->uas_tsx = uas_tsx; + uac_tsx->mod_data[mod_tu.id] = (void*)uac_data; + + /* Attach data to the UAS transaction, to find the UAC transaction + * when cancelling INVITE request. + */ + uas_data = (struct uas_data*) + pj_pool_alloc(uas_tsx->pool, sizeof(struct uas_data)); + uas_data->uac_tsx = uac_tsx; + uas_tsx->mod_data[mod_tu.id] = (void*)uas_data; + + /* Everything is setup, forward the request */ + status = pjsip_tsx_send_msg(uac_tsx, tdata); + if (status != PJ_SUCCESS) { + pjsip_tx_data *err_res; + + /* Fail to send request, for some reason */ + + /* Destroy transmit data */ + pjsip_tx_data_dec_ref(tdata); + + /* I think UAC transaction should have been destroyed when + * it fails to send request, so no need to destroy it. + pjsip_tsx_terminate(uac_tsx, PJSIP_SC_INTERNAL_SERVER_ERROR); + */ + + /* Send 500/Internal Server Error to UAS transaction */ + pjsip_endpt_create_response(global.endpt, rdata, + 500, NULL, &err_res); + pjsip_tsx_send_msg(uas_tsx, err_res); + + return PJ_TRUE; + } + + /* Send 100/Trying if this is an INVITE */ + if (rdata->msg_info.msg->line.req.method.id == PJSIP_INVITE_METHOD) { + pjsip_tx_data *res100; + + pjsip_endpt_create_response(global.endpt, rdata, 100, NULL, + &res100); + pjsip_tsx_send_msg(uas_tsx, res100); + } + + } else { + /* This is CANCEL request */ + pjsip_transaction *invite_uas; + struct uas_data *uas_data; + pj_str_t key; + + /* Find the UAS INVITE transaction */ + pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_UAS_ROLE, + &pjsip_invite_method, rdata); + invite_uas = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE); + if (!invite_uas) { + /* Invite transaction not found, respond CANCEL with 481 */ + pjsip_endpt_respond_stateless(global.endpt, rdata, 481, NULL, + NULL, NULL); + return PJ_TRUE; + } + + /* Respond 200 OK to CANCEL */ + pjsip_endpt_respond(global.endpt, NULL, rdata, 200, NULL, NULL, + NULL, NULL); + + /* Send CANCEL to cancel the UAC transaction. + * The UAS INVITE transaction will get final response when + * we receive final response from the UAC INVITE transaction. + */ + uas_data = (struct uas_data*) invite_uas->mod_data[mod_tu.id]; + if (uas_data->uac_tsx) { + pjsip_tx_data *cancel; + + pj_mutex_lock(uas_data->uac_tsx->mutex); + + pjsip_endpt_create_cancel(global.endpt, uas_data->uac_tsx->last_tx, + &cancel); + pjsip_endpt_send_request(global.endpt, cancel, -1, NULL, NULL); + + pj_mutex_unlock(uas_data->uac_tsx->mutex); + } + + /* Unlock UAS tsx because it is locked in find_tsx() */ + pj_mutex_unlock(invite_uas->mutex); + } + + return PJ_TRUE; +} + + +/* Callback to be called to handle incoming response outside + * any transactions. This happens for example when 2xx/OK + * for INVITE is received and transaction will be destroyed + * immediately, so we need to forward the subsequent 2xx/OK + * retransmission statelessly. + */ +static pj_bool_t proxy_on_rx_response( pjsip_rx_data *rdata ) +{ + pjsip_tx_data *tdata; + pjsip_response_addr res_addr; + pjsip_via_hdr *hvia; + pj_status_t status; + + /* Create response to be forwarded upstream (Via will be stripped here) */ + status = pjsip_endpt_create_response_fwd(global.endpt, rdata, 0, &tdata); + if (status != PJ_SUCCESS) { + app_perror("Error creating response", status); + return PJ_TRUE; + } + + /* Get topmost Via header */ + hvia = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL); + if (hvia == NULL) { + /* Invalid response! Just drop it */ + pjsip_tx_data_dec_ref(tdata); + return PJ_TRUE; + } + + /* Calculate the address to forward the response */ + pj_bzero(&res_addr, sizeof(res_addr)); + res_addr.dst_host.type = PJSIP_TRANSPORT_UDP; + res_addr.dst_host.flag = + pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_UDP); + + /* Destination address is Via's received param */ + res_addr.dst_host.addr.host = hvia->recvd_param; + if (res_addr.dst_host.addr.host.slen == 0) { + /* Someone has messed up our Via header! */ + res_addr.dst_host.addr.host = hvia->sent_by.host; + } + + /* Destination port is the rport */ + if (hvia->rport_param != 0 && hvia->rport_param != -1) + res_addr.dst_host.addr.port = hvia->rport_param; + + if (res_addr.dst_host.addr.port == 0) { + /* Ugh, original sender didn't put rport! + * At best, can only send the response to the port in Via. + */ + res_addr.dst_host.addr.port = hvia->sent_by.port; + } + + /* Forward response */ + status = pjsip_endpt_send_response(global.endpt, &res_addr, tdata, + NULL, NULL); + if (status != PJ_SUCCESS) { + app_perror("Error forwarding response", status); + return PJ_TRUE; + } + + return PJ_TRUE; +} + + +/* Callback to be called to handle transaction state changed. */ +static void tu_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event) +{ + struct uac_data *uac_data; + pj_status_t status; + + if (tsx->role == PJSIP_ROLE_UAS) { + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + struct uas_data *uas_data; + + uas_data = (struct uas_data*) tsx->mod_data[mod_tu.id]; + if (uas_data->uac_tsx) { + uac_data = (struct uac_data*) + uas_data->uac_tsx->mod_data[mod_tu.id]; + uac_data->uas_tsx = NULL; + } + + } + return; + } + + /* Get the data that we attached to the UAC transaction previously */ + uac_data = (struct uac_data*) tsx->mod_data[mod_tu.id]; + + + /* Handle incoming response */ + if (event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) { + + pjsip_rx_data *rdata; + pjsip_response_addr res_addr; + pjsip_via_hdr *hvia; + pjsip_tx_data *tdata; + + rdata = event->body.tsx_state.src.rdata; + + /* Do not forward 100 response for INVITE (we already responded + * INVITE with 100) + */ + if (tsx->method.id == PJSIP_INVITE_METHOD && + rdata->msg_info.msg->line.status.code == 100) + { + return; + } + + /* Create response to be forwarded upstream + * (Via will be stripped here) + */ + status = pjsip_endpt_create_response_fwd(global.endpt, rdata, 0, + &tdata); + if (status != PJ_SUCCESS) { + app_perror("Error creating response", status); + return; + } + + /* Get topmost Via header of the new response */ + hvia = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, + NULL); + if (hvia == NULL) { + /* Invalid response! Just drop it */ + pjsip_tx_data_dec_ref(tdata); + return; + } + + /* Calculate the address to forward the response */ + pj_bzero(&res_addr, sizeof(res_addr)); + res_addr.dst_host.type = PJSIP_TRANSPORT_UDP; + res_addr.dst_host.flag = + pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_UDP); + + /* Destination address is Via's received param */ + res_addr.dst_host.addr.host = hvia->recvd_param; + if (res_addr.dst_host.addr.host.slen == 0) { + /* Someone has messed up our Via header! */ + res_addr.dst_host.addr.host = hvia->sent_by.host; + } + + /* Destination port is the rport */ + if (hvia->rport_param != 0 && hvia->rport_param != -1) + res_addr.dst_host.addr.port = hvia->rport_param; + + if (res_addr.dst_host.addr.port == 0) { + /* Ugh, original sender didn't put rport! + * At best, can only send the response to the port in Via. + */ + res_addr.dst_host.addr.port = hvia->sent_by.port; + } + + /* Forward response with the UAS transaction */ + pjsip_tsx_send_msg(uac_data->uas_tsx, tdata); + + } + + /* If UAC transaction is terminated, terminate the UAS as well. + * This could happen because of: + * - timeout on the UAC side + * - receipt of 2xx response to INVITE + */ + if (tsx->state == PJSIP_TSX_STATE_TERMINATED && uac_data->uas_tsx) { + + pjsip_transaction *uas_tsx; + struct uas_data *uas_data; + + uas_tsx = uac_data->uas_tsx; + uas_data = (struct uas_data*) uas_tsx->mod_data[mod_tu.id]; + uas_data->uac_tsx = NULL; + + if (event->body.tsx_state.type == PJSIP_EVENT_TIMER) { + + /* Send 408/Timeout if this is an INVITE transaction, since + * we must have sent provisional response before. For non + * INVITE transaction, just destroy it. + */ + if (tsx->method.id == PJSIP_INVITE_METHOD) { + + pjsip_tx_data *tdata = uas_tsx->last_tx; + + tdata->msg->line.status.code = PJSIP_SC_REQUEST_TIMEOUT; + tdata->msg->line.status.reason = pj_str("Request timed out"); + tdata->msg->body = NULL; + + pjsip_tx_data_add_ref(tdata); + pjsip_tx_data_invalidate_msg(tdata); + + pjsip_tsx_send_msg(uas_tsx, tdata); + + } else { + /* For non-INVITE, just destroy the UAS transaction */ + pjsip_tsx_terminate(uas_tsx, PJSIP_SC_REQUEST_TIMEOUT); + } + + } else if (event->body.tsx_state.type == PJSIP_EVENT_RX_MSG) { + + if (uas_tsx->state < PJSIP_TSX_STATE_TERMINATED) { + pjsip_msg *msg; + int code; + + msg = event->body.tsx_state.src.rdata->msg_info.msg; + code = msg->line.status.code; + + uac_data->uas_tsx = NULL; + pjsip_tsx_terminate(uas_tsx, code); + } + } + } +} + + +/* + * main() + */ +int main(int argc, char *argv[]) +{ + pj_status_t status; + + global.port = 5060; + global.record_route = 0; + + pj_log_set_level(4); + + status = init_options(argc, argv); + if (status != PJ_SUCCESS) + return 1; + + status = init_stack(); + if (status != PJ_SUCCESS) { + app_perror("Error initializing stack", status); + return 1; + } + + status = init_proxy(); + if (status != PJ_SUCCESS) { + app_perror("Error initializing proxy", status); + return 1; + } + + status = init_stateful_proxy(); + if (status != PJ_SUCCESS) { + app_perror("Error initializing stateful proxy", status); + return 1; + } + +#if PJ_HAS_THREADS + status = pj_thread_create(global.pool, "sproxy", &worker_thread, + NULL, 0, 0, &global.thread); + if (status != PJ_SUCCESS) { + app_perror("Error creating thread", status); + return 1; + } + + while (!global.quit_flag) { + char line[10]; + + puts("\n" + "Menu:\n" + " q quit\n" + " d dump status\n" + " dd dump detailed status\n" + ""); + + fgets(line, sizeof(line), stdin); + + if (line[0] == 'q') { + global.quit_flag = PJ_TRUE; + } else if (line[0] == 'd') { + pj_bool_t detail = (line[1] == 'd'); + pjsip_endpt_dump(global.endpt, detail); + pjsip_tsx_layer_dump(detail); + } + } + + pj_thread_join(global.thread); + +#else + puts("\nPress Ctrl-C to quit\n"); + for (;;) { + pj_time_val delay = {0, 0}; + pjsip_endpt_handle_events(global.endpt, &delay); + } +#endif + + destroy_stack(); + + return 0; +} + diff --git a/pjsip-apps/src/samples/stateless_proxy.c b/pjsip-apps/src/samples/stateless_proxy.c new file mode 100644 index 00000000..f40b263c --- /dev/null +++ b/pjsip-apps/src/samples/stateless_proxy.c @@ -0,0 +1,250 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2007 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#define THIS_FILE "stateless_proxy.c" + +/* Common proxy functions */ +#define STATEFUL 0 +#include "proxy.h" + + +/* Callback to be called to handle incoming requests. */ +static pj_bool_t on_rx_request( pjsip_rx_data *rdata ); + +/* Callback to be called to handle incoming response. */ +static pj_bool_t on_rx_response( pjsip_rx_data *rdata ); + + +static pj_status_t init_stateless_proxy(void) +{ + static pjsip_module mod_stateless_proxy = + { + NULL, NULL, /* prev, next. */ + { "mod-stateless-proxy", 19 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_UA_PROXY_LAYER, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &on_rx_request, /* on_rx_request() */ + &on_rx_response, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + }; + + pj_status_t status; + + /* Register our module to receive incoming requests. */ + status = pjsip_endpt_register_module( global.endpt, &mod_stateless_proxy); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); + + return PJ_SUCCESS; +} + + +/* Callback to be called to handle incoming requests. */ +static pj_bool_t on_rx_request( pjsip_rx_data *rdata ) +{ + pjsip_tx_data *tdata; + pj_status_t status; + + + /* Verify incoming request */ + status = proxy_verify_request(rdata); + if (status != PJ_SUCCESS) { + app_perror("RX invalid request", status); + return PJ_TRUE; + } + + /* + * Request looks sane, next clone the request to create transmit data. + */ + status = pjsip_endpt_create_request_fwd(global.endpt, rdata, NULL, + NULL, 0, &tdata); + if (status != PJ_SUCCESS) { + pjsip_endpt_respond_stateless(global.endpt, rdata, + PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, + NULL, NULL); + return PJ_TRUE; + } + + + /* Process routing */ + status = proxy_process_routing(tdata); + if (status != PJ_SUCCESS) { + app_perror("Error processing route", status); + return PJ_TRUE; + } + + /* Calculate target */ + status = proxy_calculate_target(rdata, tdata); + if (status != PJ_SUCCESS) { + app_perror("Error calculating target", status); + return PJ_TRUE; + } + + /* Target is set, forward the request */ + status = pjsip_endpt_send_request_stateless(global.endpt, tdata, + NULL, NULL); + if (status != PJ_SUCCESS) { + app_perror("Error forwarding request", status); + return PJ_TRUE; + } + + return PJ_TRUE; +} + + +/* Callback to be called to handle incoming response. */ +static pj_bool_t on_rx_response( pjsip_rx_data *rdata ) +{ + pjsip_tx_data *tdata; + pjsip_response_addr res_addr; + pjsip_via_hdr *hvia; + pj_status_t status; + + /* Create response to be forwarded upstream (Via will be stripped here) */ + status = pjsip_endpt_create_response_fwd(global.endpt, rdata, 0, &tdata); + if (status != PJ_SUCCESS) { + app_perror("Error creating response", status); + return PJ_TRUE; + } + + /* Get topmost Via header */ + hvia = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL); + if (hvia == NULL) { + /* Invalid response! Just drop it */ + pjsip_tx_data_dec_ref(tdata); + return PJ_TRUE; + } + + /* Calculate the address to forward the response */ + pj_bzero(&res_addr, sizeof(res_addr)); + res_addr.dst_host.type = PJSIP_TRANSPORT_UDP; + res_addr.dst_host.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_UDP); + + /* Destination address is Via's received param */ + res_addr.dst_host.addr.host = hvia->recvd_param; + if (res_addr.dst_host.addr.host.slen == 0) { + /* Someone has messed up our Via header! */ + res_addr.dst_host.addr.host = hvia->sent_by.host; + } + + /* Destination port is the rpot */ + if (hvia->rport_param != 0 && hvia->rport_param != -1) + res_addr.dst_host.addr.port = hvia->rport_param; + + if (res_addr.dst_host.addr.port == 0) { + /* Ugh, original sender didn't put rport! + * At best, can only send the response to the port in Via. + */ + res_addr.dst_host.addr.port = hvia->sent_by.port; + } + + /* Forward response */ + status = pjsip_endpt_send_response(global.endpt, &res_addr, tdata, + NULL, NULL); + if (status != PJ_SUCCESS) { + app_perror("Error forwarding response", status); + return PJ_TRUE; + } + + return PJ_TRUE; +} + + +/* + * main() + */ +int main(int argc, char *argv[]) +{ + pj_status_t status; + + global.port = 5060; + pj_log_set_level(4); + + status = init_options(argc, argv); + if (status != PJ_SUCCESS) + return 1; + + status = init_stack(); + if (status != PJ_SUCCESS) { + app_perror("Error initializing stack", status); + return 1; + } + + status = init_proxy(); + if (status != PJ_SUCCESS) { + app_perror("Error initializing proxy", status); + return 1; + } + + status = init_stateless_proxy(); + if (status != PJ_SUCCESS) { + app_perror("Error initializing stateless proxy", status); + return 1; + } + +#if PJ_HAS_THREADS + status = pj_thread_create(global.pool, "sproxy", &worker_thread, + NULL, 0, 0, &global.thread); + if (status != PJ_SUCCESS) { + app_perror("Error creating thread", status); + return 1; + } + + while (!global.quit_flag) { + char line[10]; + + puts("\n" + "Menu:\n" + " q quit\n" + " d dump status\n" + " dd dump detailed status\n" + ""); + + fgets(line, sizeof(line), stdin); + + if (line[0] == 'q') { + global.quit_flag = PJ_TRUE; + } else if (line[0] == 'd') { + pj_bool_t detail = (line[1] == 'd'); + pjsip_endpt_dump(global.endpt, detail); +#if STATEFUL + pjsip_tsx_layer_dump(detail); +#endif + } + } + + pj_thread_join(global.thread); + +#else + puts("\nPress Ctrl-C to quit\n"); + for (;;) { + pj_time_val delay = {0, 0}; + pjsip_endpt_handle_events(global.endpt, &delay); + } +#endif + + destroy_stack(); + + return 0; +} + diff --git a/pjsip/include/pjsip/sip_msg.h b/pjsip/include/pjsip/sip_msg.h index 6c7b0201..7a303521 100644 --- a/pjsip/include/pjsip/sip_msg.h +++ b/pjsip/include/pjsip/sip_msg.h @@ -710,6 +710,19 @@ struct pjsip_msg */ PJ_DECL(pjsip_msg*) pjsip_msg_create( pj_pool_t *pool, pjsip_msg_type_e type); + +/** + * Perform a deep clone of a SIP message. + * + * @param pool The pool for creating the new message. + * @param msg The message to be duplicated. + * + * @return New message, which is duplicated from the original + * message. + */ +PJ_DECL(pjsip_msg*) pjsip_msg_clone( pj_pool_t *pool, const pjsip_msg *msg); + + /** * Find a header in the message by the header type. * diff --git a/pjsip/include/pjsip/sip_util.h b/pjsip/include/pjsip/sip_util.h index 4d19d306..62fb847c 100644 --- a/pjsip/include/pjsip/sip_util.h +++ b/pjsip/include/pjsip/sip_util.h @@ -471,6 +471,17 @@ PJ_DECL(pj_status_t) pjsip_endpt_send_request( pjsip_endpoint *endpt, void *token, void (*cb)(void*,pjsip_event*)); +/** + * @} + */ + +/** + * @defgroup PJSIP_PROXY_CORE Core Proxy Layer + * @ingroup PJSIP + * @brief Core proxy operations + * @{ + */ + /** * Create new request message to be forwarded upstream to new destination URI * in uri. The new request is a full/deep clone of the request received in @@ -478,21 +489,32 @@ PJ_DECL(pj_status_t) pjsip_endpt_send_request( pjsip_endpoint *endpt, * The branch parameter, if not NULL, will be used as the branch-param in * the Via header. If it is NULL, then a unique branch parameter will be used. * + * Note: this function DOES NOT perform Route information preprocessing as + * described in RFC 3261 Section 16.4. Application must take care of + * removing/updating the Route headers according of the rules as + * described in that section. + * * @param endpt The endpoint instance. * @param rdata The incoming request message. * @param uri The URI where the request will be forwarded to. - * @param branch Optional branch parameter. + * @param branch Optional branch parameter. Application may specify its + * own branch, for example if it wishes to perform loop + * detection. If the branch parameter is not specified, + * this function will generate its own by calling + * #pjsip_calculate_branch_id() function. * @param options Optional option flags when duplicating the message. * @param tdata The result. * * @return PJ_SUCCESS on success. */ -PJ_DECL(pj_status_t) pjsip_endpt_create_request_fwd( pjsip_endpoint *endpt, - pjsip_rx_data *rdata, - const pjsip_uri *uri, - const pj_str_t *branch, - unsigned options, - pjsip_tx_data **tdata); +PJ_DECL(pj_status_t) pjsip_endpt_create_request_fwd(pjsip_endpoint *endpt, + pjsip_rx_data *rdata, + const pjsip_uri *uri, + const pj_str_t *branch, + unsigned options, + pjsip_tx_data **tdata); + + /** * Create new response message to be forwarded downstream by the proxy from @@ -515,13 +537,17 @@ PJ_DECL(pj_status_t) pjsip_endpt_create_response_fwd( pjsip_endpoint *endpt, pjsip_tx_data **tdata); + /** * Create a globally unique branch parameter based on the information in - * the incoming request message. This function guarantees that subsequent - * retransmissions of the same request will generate the same branch id. - * This function can also be used in the loop detection process. - * If the same request arrives back in the proxy with the same URL, it will - * calculate into the same branch id. + * the incoming request message, for the purpose of creating a new request + * for forwarding. This is the default implementation used by + * #pjsip_endpt_create_request_fwd() function if the branch parameter is + * not specified. + * + * The default implementation here will just create an MD5 hash of the + * top-most Via. + * * Note that the returned string was allocated from rdata's pool. * * @param rdata The incoming request message. diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index fee08d33..b6ba7d53 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -2200,6 +2200,25 @@ PJ_DECL(pjsua_acc_id) pjsua_acc_find_for_outgoing(const pj_str_t *url); PJ_DECL(pjsua_acc_id) pjsua_acc_find_for_incoming(pjsip_rx_data *rdata); +/** + * Create arbitrary requests using the account. Application should only use + * this function to create auxiliary requests outside dialog, such as + * OPTIONS, and use the call or presence API to create dialog related + * requests. + * + * @param acc_id The account ID. + * @param method The SIP method of the request. + * @param target Target URI. + * @param p_tdata Pointer to receive the request. + * + * @return PJ_SUCCESS or the error code. + */ +PJ_DECL(pj_status_t) pjsua_acc_create_request(pjsua_acc_id acc_id, + const pjsip_method *method, + const pj_str_t *target, + pjsip_tx_data **p_tdata); + + /** * Create a suitable URI to be put as Contact based on the specified * target URI for the specified account. diff --git a/pjsip/src/pjsip/sip_msg.c b/pjsip/src/pjsip/sip_msg.c index 66304bd0..47c493db 100644 --- a/pjsip/src/pjsip/sip_msg.c +++ b/pjsip/src/pjsip/sip_msg.c @@ -247,6 +247,38 @@ PJ_DEF(pjsip_msg*) pjsip_msg_create( pj_pool_t *pool, pjsip_msg_type_e type) return msg; } +PJ_DEF(pjsip_msg*) pjsip_msg_clone( pj_pool_t *pool, const pjsip_msg *src) +{ + pjsip_msg *dst; + const pjsip_hdr *sh; + + dst = pjsip_msg_create(pool, src->type); + + /* Clone request/status line */ + if (src->type == PJSIP_REQUEST_MSG) { + pjsip_method_copy(pool, &dst->line.req.method, &src->line.req.method); + dst->line.req.uri = pjsip_uri_clone(pool, src->line.req.uri); + } else { + dst->line.status.code = src->line.status.code; + pj_strdup(pool, &dst->line.status.reason, &src->line.status.reason); + } + + /* Clone headers */ + sh = src->hdr.next; + while (sh != &src->hdr) { + pjsip_hdr *dh = pjsip_hdr_clone(pool, sh); + pjsip_msg_add_hdr(dst, dh); + sh = sh->next; + } + + /* Clone message body */ + if (src->body) { + dst->body = pjsip_msg_body_clone(pool, src->body); + } + + return dst; +} + PJ_DEF(void*) pjsip_msg_find_hdr( const pjsip_msg *msg, pjsip_hdr_e hdr_type, const void *start) { diff --git a/pjsip/src/pjsip/sip_util.c b/pjsip/src/pjsip/sip_util.c index e95faaeb..3f1add48 100644 --- a/pjsip/src/pjsip/sip_util.c +++ b/pjsip/src/pjsip/sip_util.c @@ -1037,6 +1037,10 @@ PJ_DEF(pj_status_t) pjsip_get_response_addr( pj_pool_t *pool, /* Check arguments. */ PJ_ASSERT_RETURN(pool && rdata && res_addr, PJ_EINVAL); + /* rdata must be a request message! */ + PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG, + PJ_EINVAL); + /* All requests must have "received" parameter. * This must always be done in transport layer. */ diff --git a/pjsip/src/pjsip/sip_util_proxy.c b/pjsip/src/pjsip/sip_util_proxy.c index 73d1c4b6..b9631397 100644 --- a/pjsip/src/pjsip/sip_util_proxy.c +++ b/pjsip/src/pjsip/sip_util_proxy.c @@ -17,50 +17,339 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include +#include #include +#include #include +#include +#include +#include +#include +#include -PJ_DEF(pj_status_t) pjsip_endpt_create_request_fwd( pjsip_endpoint *endpt, - pjsip_rx_data *rdata, - const pjsip_uri *uri, - const pj_str_t *branch, - unsigned options, - pjsip_tx_data **tdata) + +/** + * Clone the incoming SIP request or response message. A forwarding proxy + * typically would need to clone the incoming SIP message before processing + * the message. + * + * Once a transmit data is created, the reference counter is initialized to 1. + * + * @param endpt The endpoint instance. + * @param rdata The incoming SIP message. + * @param p_tdata Pointer to receive the transmit data containing + * the duplicated message. + * + * @return PJ_SUCCESS on success. + */ +PJ_DEF(pj_status_t) pjsip_endpt_clone_msg( pjsip_endpoint *endpt, + const pjsip_rx_data *rdata, + pjsip_tx_data **p_tdata) +{ + pjsip_tx_data *tdata; + pj_status_t status; + + status = pjsip_endpt_create_tdata(endpt, &tdata); + if (status != PJ_SUCCESS) + return status; + + tdata->msg = pjsip_msg_clone(tdata->pool, rdata->msg_info.msg); + + pjsip_tx_data_add_ref(tdata); + + *p_tdata = tdata; + + return PJ_SUCCESS; +} + + +/* + * Create new request message to be forwarded upstream to new destination URI + * in uri. + */ +PJ_DEF(pj_status_t) pjsip_endpt_create_request_fwd(pjsip_endpoint *endpt, + pjsip_rx_data *rdata, + const pjsip_uri *uri, + const pj_str_t *branch, + unsigned options, + pjsip_tx_data **p_tdata) { - PJ_UNUSED_ARG(endpt); - PJ_UNUSED_ARG(rdata); - PJ_UNUSED_ARG(uri); - PJ_UNUSED_ARG(branch); + pjsip_tx_data *tdata; + pj_status_t status; + PJ_USE_EXCEPTION; + + + PJ_ASSERT_RETURN(endpt && rdata && p_tdata, PJ_EINVAL); + PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + PJ_UNUSED_ARG(options); - PJ_UNUSED_ARG(tdata); - pj_assert(!"Not implemented yet"); - return PJ_EBUG; + + /* Request forwarding rule in RFC 3261 section 16.6: + * + * For each target, the proxy forwards the request following these + * steps: + * + * 1. Make a copy of the received request + * 2. Update the Request-URI + * 3. Update the Max-Forwards header field + * 4. Optionally add a Record-route header field value + * 5. Optionally add additional header fields + * 6. Postprocess routing information + * 7. Determine the next-hop address, port, and transport + * 8. Add a Via header field value + * 9. Add a Content-Length header field if necessary + * 10. Forward the new request + * + * Of these steps, we only do step 1-3, since the later will be + * done by application. + */ + + status = pjsip_endpt_create_tdata(endpt, &tdata); + if (status != PJ_SUCCESS) + return status; + + /* Always increment ref counter to 1 */ + pjsip_tx_data_add_ref(tdata); + + /* Duplicate the request */ + PJ_TRY { + pjsip_msg *dst; + const pjsip_msg *src = rdata->msg_info.msg; + const pjsip_hdr *hsrc; + + /* Create the request */ + tdata->msg = dst = pjsip_msg_create(tdata->pool, PJSIP_REQUEST_MSG); + + /* Duplicate request method */ + pjsip_method_copy(tdata->pool, &tdata->msg->line.req.method, + &src->line.req.method); + + /* Set request URI */ + if (uri) { + dst->line.req.uri = pjsip_uri_clone(tdata->pool, uri); + } else { + dst->line.req.uri = pjsip_uri_clone(tdata->pool, src->line.req.uri); + } + + /* Clone ALL headers */ + hsrc = src->hdr.next; + while (hsrc != &src->hdr) { + + pjsip_hdr *hdst; + + /* If this is the top-most Via header, insert our own before + * cloning the header. + */ + if (hsrc == (pjsip_hdr*)rdata->msg_info.via) { + pjsip_via_hdr *hvia; + hvia = pjsip_via_hdr_create(tdata->pool); + if (branch) + pj_strdup(tdata->pool, &hvia->branch_param, branch); + else { + pj_str_t new_branch = pjsip_calculate_branch_id(rdata); + pj_strdup(tdata->pool, &hvia->branch_param, &new_branch); + } + pjsip_msg_add_hdr(dst, (pjsip_hdr*)hvia); + + } + /* Skip Content-Type and Content-Length as these would be + * generated when the the message is printed. + */ + else if (hsrc->type == PJSIP_H_CONTENT_LENGTH || + hsrc->type == PJSIP_H_CONTENT_TYPE) { + + hsrc = hsrc->next; + continue; + + } +#if 0 + /* If this is the top-most Route header and it indicates loose + * route, remove the header. + */ + else if (hsrc == (pjsip_hdr*)rdata->msg_info.route) { + + const pjsip_route_hdr *hroute = (const pjsip_route_hdr*) hsrc; + const pjsip_sip_uri *sip_uri; + + if (!PJSIP_URI_SCHEME_IS_SIP(hroute->name_addr.uri) && + !PJSIP_URI_SCHEME_IS_SIPS(hroute->name_addr.uri)) + { + /* This is a bad request! */ + status = PJSIP_EINVALIDHDR; + goto on_error; + } + + sip_uri = (pjsip_sip_uri*) hroute->name_addr.uri; + + if (sip_uri->lr_param) { + /* Yes lr param is present, skip this Route header */ + hsrc = hsrc->next; + continue; + } + } +#endif + + /* Clone the header */ + hdst = pjsip_hdr_clone(tdata->pool, hsrc); + + /* If this is Max-Forward header, decrement the value */ + if (hdst->type == PJSIP_H_MAX_FORWARDS) { + pjsip_max_fwd_hdr *hmaxfwd = (pjsip_max_fwd_hdr*)hdst; + --hmaxfwd->ivalue; + } + + /* Append header to new request */ + pjsip_msg_add_hdr(dst, hdst); + + + hsrc = hsrc->next; + } + + /* 16.6.3: + * If the copy does not contain a Max-Forwards header field, the + * proxy MUST add one with a field value, which SHOULD be 70. + */ + if (rdata->msg_info.max_fwd == NULL) { + pjsip_max_fwd_hdr *hmaxfwd = + pjsip_max_fwd_hdr_create(tdata->pool, 70); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hmaxfwd); + } + + /* Clone request body */ + if (src->body) { + dst->body = pjsip_msg_body_clone(tdata->pool, src->body); + } + + } + PJ_CATCH_ANY { + status = PJ_ENOMEM; + goto on_error; + } + PJ_END + + + /* Done */ + *p_tdata = tdata; + return PJ_SUCCESS; + +on_error: + pjsip_tx_data_dec_ref(tdata); + return status; } PJ_DEF(pj_status_t) pjsip_endpt_create_response_fwd( pjsip_endpoint *endpt, pjsip_rx_data *rdata, unsigned options, - pjsip_tx_data **tdata) + pjsip_tx_data **p_tdata) { - PJ_UNUSED_ARG(endpt); - PJ_UNUSED_ARG(rdata); + pjsip_tx_data *tdata; + pj_status_t status; + PJ_USE_EXCEPTION; + PJ_UNUSED_ARG(options); - PJ_UNUSED_ARG(tdata); - pj_assert(!"Not implemented yet"); - return PJ_EBUG; + status = pjsip_endpt_create_tdata(endpt, &tdata); + if (status != PJ_SUCCESS) + return status; + + pjsip_tx_data_add_ref(tdata); + + PJ_TRY { + pjsip_msg *dst; + const pjsip_msg *src = rdata->msg_info.msg; + const pjsip_hdr *hsrc; + + /* Create the request */ + tdata->msg = dst = pjsip_msg_create(tdata->pool, PJSIP_RESPONSE_MSG); + + /* Clone the status line */ + dst->line.status.code = src->line.status.code; + pj_strdup(tdata->pool, &dst->line.status.reason, + &src->line.status.reason); + + /* Duplicate all headers */ + hsrc = src->hdr.next; + while (hsrc != &src->hdr) { + + /* Skip Content-Type and Content-Length as these would be + * generated when the the message is printed. + */ + if (hsrc->type == PJSIP_H_CONTENT_LENGTH || + hsrc->type == PJSIP_H_CONTENT_TYPE) { + + hsrc = hsrc->next; + continue; + + } + /* Remove the first Via header */ + else if (hsrc == (pjsip_hdr*) rdata->msg_info.via) { + + hsrc = hsrc->next; + continue; + } + + pjsip_msg_add_hdr(dst, pjsip_hdr_clone(tdata->pool, hsrc)); + + hsrc = hsrc->next; + } + + /* Clone message body */ + if (src->body) + dst->body = pjsip_msg_body_clone(tdata->pool, src->body); + + + } + PJ_CATCH_ANY { + status = PJ_ENOMEM; + goto on_error; + } + PJ_END; + + *p_tdata = tdata; + return PJ_SUCCESS; + +on_error: + pjsip_tx_data_dec_ref(tdata); + return status; +} + + +static void digest2str(const unsigned char digest[], char *output) +{ + int i; + for (i = 0; i<16; ++i) { + pj_val_to_hex_digit(digest[i], output); + output += 2; + } } PJ_DEF(pj_str_t) pjsip_calculate_branch_id( pjsip_rx_data *rdata ) { - pj_str_t empty_str = { NULL, 0 }; + pj_md5_context ctx; + pj_uint8_t digest[16]; + pj_str_t branch; + + /* Create branch ID for new request by calculating MD5 hash + * of the branch parameter in top-most Via header. + */ + pj_md5_init(&ctx); + pj_md5_update(&ctx, (pj_uint8_t*)rdata->msg_info.via->branch_param.ptr, + rdata->msg_info.via->branch_param.slen); + pj_md5_final(&ctx, digest); + + branch.ptr = pj_pool_alloc(rdata->tp_info.pool, + 32 + PJSIP_RFC3261_BRANCH_LEN); + pj_memcpy(branch.ptr, PJSIP_RFC3261_BRANCH_ID, PJSIP_RFC3261_BRANCH_LEN); + + digest2str(digest, branch.ptr+PJSIP_RFC3261_BRANCH_LEN); + + branch.slen = 32 + PJSIP_RFC3261_BRANCH_LEN; - PJ_UNUSED_ARG(rdata); - pj_assert(!"Not implemented yet"); - return empty_str; + return branch; } diff --git a/pjsip/src/pjsua-lib/pjsua_acc.c b/pjsip/src/pjsua-lib/pjsua_acc.c index 3e39b15f..b2b63740 100644 --- a/pjsip/src/pjsua-lib/pjsua_acc.c +++ b/pjsip/src/pjsua-lib/pjsua_acc.c @@ -925,6 +925,45 @@ PJ_DEF(pjsua_acc_id) pjsua_acc_find_for_incoming(pjsip_rx_data *rdata) } +/* + * Create arbitrary requests for this account. + */ +PJ_DEF(pj_status_t) pjsua_acc_create_request(pjsua_acc_id acc_id, + const pjsip_method *method, + const pj_str_t *target, + pjsip_tx_data **p_tdata) +{ + pjsip_tx_data *tdata; + pjsua_acc *acc; + pjsip_route_hdr *r; + pj_status_t status; + + PJ_ASSERT_RETURN(method && target && p_tdata, PJ_EINVAL); + PJ_ASSERT_RETURN(pjsua_acc_is_valid(acc_id), PJ_EINVAL); + + acc = &pjsua_var.acc[acc_id]; + + status = pjsip_endpt_create_request(pjsua_var.endpt, method, target, + &acc->cfg.id, target, + NULL, NULL, -1, NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create request", status); + return status; + } + + /* Copy routeset */ + r = acc->route_set.next; + while (r != &acc->route_set) { + pjsip_msg_add_hdr(tdata->msg, pjsip_hdr_clone(tdata->pool, r)); + r = r->next; + } + + /* Done */ + *p_tdata = tdata; + return PJ_SUCCESS; +} + + PJ_DEF(pj_status_t) pjsua_acc_create_uac_contact( pj_pool_t *pool, pj_str_t *contact, pjsua_acc_id acc_id, -- cgit v1.2.3