From c9af503006232e2232e3173d1ec4f62b80cc213e Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Sun, 1 Apr 2007 22:58:47 +0000 Subject: Ticket #204: Implement SIP proxy functionalities (including some samples) git-svn-id: http://svn.pjsip.org/repos/pjproject/branches/pjproject-0.5-stable@1121 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/samples/proxy.h | 437 +++++++++++++++++++++++++++++++ pjsip-apps/src/samples/stateful_proxy.c | 349 ++++++++++++++++++++++++ pjsip-apps/src/samples/stateless_proxy.c | 251 ++++++++++++++++++ pjsip/include/pjsip/sip_msg.h | 13 + pjsip/include/pjsip/sip_util.h | 50 +++- pjsip/src/pjsip/sip_msg.c | 32 +++ pjsip/src/pjsip/sip_util.c | 4 + pjsip/src/pjsip/sip_util_proxy.c | 335 +++++++++++++++++++++-- 12 files changed, 1465 insertions(+), 35 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 301d8b5c..abd0c211 100644 --- a/pjsip-apps/build/Samples-vc.mak +++ b/pjsip-apps/build/Samples-vc.mak @@ -49,6 +49,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 c3fe6dd6..5ec7c714 100644 --- a/pjsip-apps/build/Samples.mak +++ b/pjsip-apps/build/Samples.mak @@ -52,6 +52,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/samples/proxy.h b/pjsip-apps/src/samples/proxy.h new file mode 100644 index 00000000..a7cba80c --- /dev/null +++ b/pjsip-apps/src/samples/proxy.h @@ -0,0 +1,437 @@ +/* $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" + " -p, --port N Set local listener port to N\n" + " -R, --rr Perform record routing\n" + " -h, --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", 1, 0, 'R'}, + { "help",1, 0, 'h'}, + }; + int c; + int opt_ind; + + pj_optind = 0; + while((c=pj_getopt_long(argc, argv, "pRh", 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 'h': + usage(); + return -1; + + default: + puts("Unknown option ignored"); + break; + } + } + + return PJ_SUCCESS; +} + + +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); + + 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. + */ + 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..2bce9188 --- /dev/null +++ b/pjsip-apps/src/samples/stateful_proxy.c @@ -0,0 +1,349 @@ +/* $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" + + +/* 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 ); + + +/* This is the data that is attached to the UAC transaction */ +struct tsx_data +{ + pjsip_transaction *uas_tsx; + pj_timer_entry timer; +}; + + +static pjsip_module mod_stateful_proxy = +{ + NULL, NULL, /* prev, next. */ + { "mod-stateful-proxy", 18 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* 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() */ +}; + + +static pj_status_t init_stateful_proxy(void) +{ + pj_status_t status; + + /* Register our module to receive incoming requests. */ + status = pjsip_endpt_register_module( global.endpt, &mod_stateful_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_transaction *uas_tsx, *uac_tsx; + struct tsx_data *tsx_data; + 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; + } + + /* Everything is set to forward the request. */ + + /* If this is an ACK request, forward statelessly */ + 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 */ + status = pjsip_tsx_create_uac(&mod_stateful_proxy, 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_stateful_proxy, 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. + */ + tsx_data = pj_pool_alloc(uac_tsx->pool, sizeof(struct tsx_data)); + tsx_data->uas_tsx = uas_tsx; + + uac_tsx->mod_data[mod_stateful_proxy.id] = (void*)tsx_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 UAC transaction */ + pjsip_tx_data_dec_ref(tdata); + pjsip_tsx_terminate(uac_tsx, PJSIP_SC_INTERNAL_SERVER_ERROR); + + /* Send 500/Internal Server Error to UAS transaction */ + status = pjsip_endpt_create_response(global.endpt, rdata, + PJSIP_SC_INTERNAL_SERVER_ERROR, + NULL, &err_res); + if (status == PJ_SUCCESS) + pjsip_tsx_send_msg(uas_tsx, err_res); + else + pjsip_tsx_terminate(uac_tsx, PJSIP_SC_INTERNAL_SERVER_ERROR); + + 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_transaction *uac_tsx; + 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; + } + + uac_tsx = pjsip_rdata_get_tsx(rdata); + + if (!uac_tsx) { + /* UAC transaction not found (it may have been destroyed). + * Forward response statelessly. + */ + 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; + } + } else { + struct tsx_data *tsx_data; + + tsx_data = (struct tsx_data*) uac_tsx->mod_data[mod_stateful_proxy.id]; + + /* Forward response with the UAS transaction */ + pjsip_tsx_send_msg(tsx_data->uas_tsx, tdata); + + /* Special case for pjsip: + * if response is 2xx for INVITE transaction, terminate the UAS + * transaction (otherwise it will retransmit the response). + */ + if (tsx_data->uas_tsx->method.id == PJSIP_INVITE_METHOD && + rdata->msg_info.msg->line.status.code/100 == 2) + { + pjsip_tsx_terminate(tsx_data->uas_tsx, + rdata->msg_info.msg->line.status.code); + tsx_data->uas_tsx = NULL; + } + } + + return PJ_TRUE; +} + + +/* + * main() + */ +int main(int argc, char *argv[]) +{ + pj_status_t status; + + global.port = 5060; + global.record_route = 0; + + status = init_options(argc, argv); + if (status != PJ_SUCCESS) + return 1; + + pj_log_set_level(4); + + 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..8e3e703f --- /dev/null +++ b/pjsip-apps/src/samples/stateless_proxy.c @@ -0,0 +1,251 @@ +/* $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_APPLICATION, /* 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; + + status = init_options(argc, argv); + if (status != PJ_SUCCESS) + return 1; + + pj_log_set_level(4); + + 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/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; } -- cgit v1.2.3