blob: 65ccd198c42d57448439e2f1e590f8d0fc07366a [file] [log] [blame]
Benny Prijonofff245c2007-04-02 11:44:47 +00001/* $Id$ */
2/*
3 * Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19#include <pjsip.h>
20#include <pjlib-util.h>
21#include <pjlib.h>
22
23
24/* Options */
25static struct global_struct
26{
27 pj_caching_pool cp;
28 pjsip_endpoint *endpt;
29 int port;
30 pj_pool_t *pool;
31
32 pj_thread_t *thread;
33 pj_bool_t quit_flag;
34
35 pj_bool_t record_route;
36
37 unsigned name_cnt;
38 pjsip_host_port name[16];
39} global;
40
41
42
43static void app_perror(const char *msg, pj_status_t status)
44{
45 char errmsg[PJ_ERR_MSG_SIZE];
46
47 pj_strerror(status, errmsg, sizeof(errmsg));
48 PJ_LOG(1,(THIS_FILE, "%s: %s", msg, errmsg));
49}
50
51
52static void usage(void)
53{
54 puts("Options:\n"
55 "\n"
Benny Prijono230c2f92007-04-06 10:35:41 +000056 " -p, --port N Set local listener port to N\n"
57 " -R, --rr Perform record routing\n"
58 " -L, --log-level N Set log level to N (default: 4)\n"
59 " -h, --help Show this help screen\n"
Benny Prijonofff245c2007-04-02 11:44:47 +000060 );
61}
62
63
64static pj_status_t init_options(int argc, char *argv[])
65{
66 struct pj_getopt_option long_opt[] = {
67 { "port", 1, 0, 'p'},
68 { "rr", 0, 0, 'R'},
69 { "log-level", 1, 0, 'L'},
70 { "help", 0, 0, 'h'},
Benny Prijono230c2f92007-04-06 10:35:41 +000071 { NULL, 0, 0, 0}
Benny Prijonofff245c2007-04-02 11:44:47 +000072 };
73 int c;
74 int opt_ind;
75
76 pj_optind = 0;
Benny Prijono230c2f92007-04-06 10:35:41 +000077 while((c=pj_getopt_long(argc, argv, "p:L:Rh", long_opt, &opt_ind))!=-1) {
Benny Prijonofff245c2007-04-02 11:44:47 +000078 switch (c) {
79 case 'p':
80 global.port = atoi(pj_optarg);
Benny Prijono64ac31b2007-04-02 19:11:41 +000081 printf("Port is set to %d\n", global.port);
Benny Prijonofff245c2007-04-02 11:44:47 +000082 break;
83
84 case 'R':
85 global.record_route = PJ_TRUE;
86 printf("Using record route mode\n");
87 break;
88
89 case 'L':
90 pj_log_set_level(atoi(pj_optarg));
91 break;
92
93 case 'h':
94 usage();
95 return -1;
96
97 default:
98 puts("Unknown option. Run with --help for help.");
99 return -1;
100 }
101 }
102
103 return PJ_SUCCESS;
104}
105
106
107/*****************************************************************************
108 * This is a very simple PJSIP module, whose sole purpose is to display
109 * incoming and outgoing messages to log. This module will have priority
110 * higher than transport layer, which means:
111 *
112 * - incoming messages will come to this module first before reaching
113 * transaction layer.
114 *
115 * - outgoing messages will come to this module last, after the message
116 * has been 'printed' to contiguous buffer by transport layer and
117 * appropriate transport instance has been decided for this message.
118 *
119 */
120
121/* Notification on incoming messages */
122static pj_bool_t logging_on_rx_msg(pjsip_rx_data *rdata)
123{
124 PJ_LOG(5,(THIS_FILE, "RX %d bytes %s from %s %s:%d:\n"
125 "%.*s\n"
126 "--end msg--",
127 rdata->msg_info.len,
128 pjsip_rx_data_get_info(rdata),
129 rdata->tp_info.transport->type_name,
130 rdata->pkt_info.src_name,
131 rdata->pkt_info.src_port,
132 (int)rdata->msg_info.len,
133 rdata->msg_info.msg_buf));
134
135 /* Always return false, otherwise messages will not get processed! */
136 return PJ_FALSE;
137}
138
139/* Notification on outgoing messages */
140static pj_status_t logging_on_tx_msg(pjsip_tx_data *tdata)
141{
142
143 /* Important note:
144 * tp_info field is only valid after outgoing messages has passed
145 * transport layer. So don't try to access tp_info when the module
146 * has lower priority than transport layer.
147 */
148
149 PJ_LOG(5,(THIS_FILE, "TX %d bytes %s to %s %s:%d:\n"
150 "%.*s\n"
151 "--end msg--",
152 (tdata->buf.cur - tdata->buf.start),
153 pjsip_tx_data_get_info(tdata),
154 tdata->tp_info.transport->type_name,
155 tdata->tp_info.dst_name,
156 tdata->tp_info.dst_port,
157 (int)(tdata->buf.cur - tdata->buf.start),
158 tdata->buf.start));
159
160 /* Always return success, otherwise message will not get sent! */
161 return PJ_SUCCESS;
162}
163
164/* The module instance. */
165static pjsip_module mod_msg_logger =
166{
167 NULL, NULL, /* prev, next. */
168 { "mod-msg-logger", 14 }, /* Name. */
169 -1, /* Id */
170 PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */
171 NULL, /* load() */
172 NULL, /* start() */
173 NULL, /* stop() */
174 NULL, /* unload() */
175 &logging_on_rx_msg, /* on_rx_request() */
176 &logging_on_rx_msg, /* on_rx_response() */
177 &logging_on_tx_msg, /* on_tx_request. */
178 &logging_on_tx_msg, /* on_tx_response() */
179 NULL, /* on_tsx_state() */
180
181};
182
183
184static pj_status_t init_stack(void)
185{
186 pj_status_t status;
187
188 /* Must init PJLIB first: */
189 status = pj_init();
190 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
191
192
193 /* Then init PJLIB-UTIL: */
194 status = pjlib_util_init();
195 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
196
197
198 /* Must create a pool factory before we can allocate any memory. */
199 pj_caching_pool_init(&global.cp, &pj_pool_factory_default_policy, 0);
200
201 /* Create the endpoint: */
202 status = pjsip_endpt_create(&global.cp.factory, NULL, &global.endpt);
203 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
204
205 /* Init transaction layer for stateful proxy only */
206#if STATEFUL
207 status = pjsip_tsx_layer_init_module(global.endpt);
208 PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
209#endif
210
211 /* Create listening transport */
212 {
213 pj_sockaddr_in addr;
214
Benny Prijono8ab968f2007-07-20 08:08:30 +0000215 addr.sin_family = pj_AF_INET();
Benny Prijonofff245c2007-04-02 11:44:47 +0000216 addr.sin_addr.s_addr = 0;
217 addr.sin_port = pj_htons((pj_uint16_t)global.port);
218
219 status = pjsip_udp_transport_start( global.endpt, &addr,
220 NULL, 1, NULL);
221 if (status != PJ_SUCCESS)
222 return status;
223 }
224
225 /* Create pool for the application */
226 global.pool = pj_pool_create(&global.cp.factory, "proxyapp",
227 4000, 4000, NULL);
228
229 /* Register the logger module */
230 pjsip_endpt_register_module(global.endpt, &mod_msg_logger);
231
232 return PJ_SUCCESS;
233}
234
235
236static pj_status_t init_proxy(void)
237{
Benny Prijono3af35832007-08-01 11:38:14 +0000238 pj_in_addr pri_addr;
239 pj_in_addr addr_list[16];
240 unsigned addr_cnt = PJ_ARRAY_SIZE(addr_list);
Benny Prijonofff245c2007-04-02 11:44:47 +0000241 unsigned i;
242
243 /* List all names matching local endpoint.
244 * Note that PJLIB version 0.6 and newer has a function to
245 * enumerate local IP interface (pj_enum_ip_interface()), so
246 * by using it would be possible to list all IP interfaces in
247 * this host.
248 */
249
250 /* The first address is important since this would be the one
251 * to be added in Record-Route.
252 */
Benny Prijono3af35832007-08-01 11:38:14 +0000253 if (pj_gethostip(&pri_addr)==PJ_SUCCESS) {
Benny Prijonofff245c2007-04-02 11:44:47 +0000254 pj_strdup2(global.pool, &global.name[global.name_cnt].host,
Benny Prijono3af35832007-08-01 11:38:14 +0000255 pj_inet_ntoa(pri_addr));
Benny Prijonofff245c2007-04-02 11:44:47 +0000256 global.name[global.name_cnt].port = global.port;
257 global.name_cnt++;
258 }
259
Benny Prijono3af35832007-08-01 11:38:14 +0000260 /* Get the rest of IP interfaces */
261 if (pj_enum_ip_interface(&addr_cnt, addr_list) == PJ_SUCCESS) {
262 for (i=0; i<addr_cnt; ++i) {
263
264 if (addr_list[i].s_addr == pri_addr.s_addr)
265 continue;
266
267 pj_strdup2(global.pool, &global.name[global.name_cnt].host,
268 pj_inet_ntoa(addr_list[i]));
269 global.name[global.name_cnt].port = global.port;
270 global.name_cnt++;
271 }
272 }
273
274 /* No need to add this, this should have been added above.
Benny Prijonofff245c2007-04-02 11:44:47 +0000275 global.name[global.name_cnt].host = pj_str("127.0.0.1");
276 global.name[global.name_cnt].port = global.port;
277 global.name_cnt++;
Benny Prijono3af35832007-08-01 11:38:14 +0000278 */
Benny Prijonofff245c2007-04-02 11:44:47 +0000279
280 global.name[global.name_cnt].host = *pj_gethostname();
281 global.name[global.name_cnt].port = global.port;
282 global.name_cnt++;
283
284 global.name[global.name_cnt].host = pj_str("localhost");
285 global.name[global.name_cnt].port = global.port;
286 global.name_cnt++;
287
288 PJ_LOG(3,(THIS_FILE, "Proxy started, listening on port %d", global.port));
289 PJ_LOG(3,(THIS_FILE, "Local host aliases:"));
290 for (i=0; i<global.name_cnt; ++i) {
291 PJ_LOG(3,(THIS_FILE, " %.*s:%d",
292 (int)global.name[i].host.slen,
293 global.name[i].host.ptr,
294 global.name[i].port));
295 }
296
297 if (global.record_route) {
298 PJ_LOG(3,(THIS_FILE, "Using Record-Route mode"));
299 }
300
301 return PJ_SUCCESS;
302}
303
304
305#if PJ_HAS_THREADS
306static int worker_thread(void *p)
307{
308 pj_time_val delay = {0, 10};
309
310 PJ_UNUSED_ARG(p);
311
312 while (!global.quit_flag) {
313 pjsip_endpt_handle_events(global.endpt, &delay);
314 }
315
316 return 0;
317}
318#endif
319
320
321/* Utility to determine if URI is local to this host. */
322static pj_bool_t is_uri_local(const pjsip_sip_uri *uri)
323{
324 unsigned i;
325 for (i=0; i<global.name_cnt; ++i) {
326 if ((uri->port == global.name[i].port ||
327 (uri->port==0 && global.name[i].port==5060)) &&
328 pj_stricmp(&uri->host, &global.name[i].host)==0)
329 {
330 /* Match */
331 return PJ_TRUE;
332 }
333 }
334
335 /* Doesn't match */
336 return PJ_FALSE;
337}
338
339
340/* Proxy utility to verify incoming requests.
341 * Return non-zero if verification failed.
342 */
343static pj_status_t proxy_verify_request(pjsip_rx_data *rdata)
344{
345 const pj_str_t STR_PROXY_REQUIRE = {"Proxy-Require", 13};
346
347 /* RFC 3261 Section 16.3 Request Validation */
348
349 /* Before an element can proxy a request, it MUST verify the message's
350 * validity. A valid message must pass the following checks:
351 *
352 * 1. Reasonable Syntax
353 * 2. URI scheme
354 * 3. Max-Forwards
355 * 4. (Optional) Loop Detection
356 * 5. Proxy-Require
357 * 6. Proxy-Authorization
358 */
359
360 /* 1. Reasonable Syntax.
361 * This would have been checked by transport layer.
362 */
363
364 /* 2. URI scheme.
365 * We only want to support "sip:" URI scheme for this simple proxy.
366 */
367 if (!PJSIP_URI_SCHEME_IS_SIP(rdata->msg_info.msg->line.req.uri)) {
368 pjsip_endpt_respond_stateless(global.endpt, rdata,
369 PJSIP_SC_UNSUPPORTED_URI_SCHEME, NULL,
370 NULL, NULL);
371 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_UNSUPPORTED_URI_SCHEME);
372 }
373
374 /* 3. Max-Forwards.
375 * Send error if Max-Forwards is 1 or lower.
376 */
377 if (rdata->msg_info.max_fwd && rdata->msg_info.max_fwd->ivalue <= 1) {
378 pjsip_endpt_respond_stateless(global.endpt, rdata,
379 PJSIP_SC_TOO_MANY_HOPS, NULL,
380 NULL, NULL);
381 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_TOO_MANY_HOPS);
382 }
383
384 /* 4. (Optional) Loop Detection.
385 * Nah, we don't do that with this simple proxy.
386 */
387
388 /* 5. Proxy-Require */
389 if (pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_PROXY_REQUIRE,
390 NULL) != NULL)
391 {
392 pjsip_endpt_respond_stateless(global.endpt, rdata,
393 PJSIP_SC_BAD_EXTENSION, NULL,
394 NULL, NULL);
395 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_EXTENSION);
396 }
397
398 /* 6. Proxy-Authorization.
399 * Nah, we don't require any authorization with this sample.
400 */
401
402 return PJ_SUCCESS;
403}
404
405
406/* Process route information in the reqeust */
407static pj_status_t proxy_process_routing(pjsip_tx_data *tdata)
408{
409 pjsip_sip_uri *target;
410 pjsip_route_hdr *hroute;
411
412 /* RFC 3261 Section 16.4 Route Information Preprocessing */
413
414 target = (pjsip_sip_uri*) tdata->msg->line.req.uri;
415
416 /* The proxy MUST inspect the Request-URI of the request. If the
417 * Request-URI of the request contains a value this proxy previously
418 * placed into a Record-Route header field (see Section 16.6 item 4),
419 * the proxy MUST replace the Request-URI in the request with the last
420 * value from the Route header field, and remove that value from the
421 * Route header field. The proxy MUST then proceed as if it received
422 * this modified request.
423 */
424 /* Nah, we don't want to support this */
425
426 /* If the Request-URI contains a maddr parameter, the proxy MUST check
427 * to see if its value is in the set of addresses or domains the proxy
428 * is configured to be responsible for. If the Request-URI has a maddr
429 * parameter with a value the proxy is responsible for, and the request
430 * was received using the port and transport indicated (explicitly or by
431 * default) in the Request-URI, the proxy MUST strip the maddr and any
432 * non-default port or transport parameter and continue processing as if
433 * those values had not been present in the request.
434 */
435 if (target->maddr_param.slen != 0) {
436 pjsip_sip_uri maddr_uri;
437
438 maddr_uri.host = target->maddr_param;
439 maddr_uri.port = global.port;
440
441 if (is_uri_local(&maddr_uri)) {
442 target->maddr_param.slen = 0;
443 target->port = 0;
444 target->transport_param.slen = 0;
445 }
446 }
447
448 /* If the first value in the Route header field indicates this proxy,
449 * the proxy MUST remove that value from the request.
450 */
451 hroute = (pjsip_route_hdr*)
452 pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL);
453 if (hroute && is_uri_local((pjsip_sip_uri*)hroute->name_addr.uri)) {
454 pj_list_erase(hroute);
455 }
456
457 return PJ_SUCCESS;
458}
459
460
461/* Postprocess the request before forwarding it */
462static void proxy_postprocess(pjsip_tx_data *tdata)
463{
464 /* Optionally record-route */
465 if (global.record_route) {
466 char uribuf[128];
467 pj_str_t uri;
468 const pj_str_t H_RR = { "Record-Route", 12 };
469 pjsip_generic_string_hdr *rr;
470
471 pj_ansi_snprintf(uribuf, sizeof(uribuf), "<sip:%.*s:%d;lr>",
472 (int)global.name[0].host.slen,
473 global.name[0].host.ptr,
474 global.name[0].port);
475 uri = pj_str(uribuf);
476 rr = pjsip_generic_string_hdr_create(tdata->pool,
477 &H_RR, &uri);
478 pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)rr);
479 }
480}
481
482
483/* Calculate new target for the request */
484static pj_status_t proxy_calculate_target(pjsip_rx_data *rdata,
485 pjsip_tx_data *tdata)
486{
487 pjsip_sip_uri *target;
488
489 /* RFC 3261 Section 16.5 Determining Request Targets */
490
491 target = (pjsip_sip_uri*) tdata->msg->line.req.uri;
492
493 /* If the Request-URI of the request contains an maddr parameter, the
494 * Request-URI MUST be placed into the target set as the only target
495 * URI, and the proxy MUST proceed to Section 16.6.
496 */
497 if (target->maddr_param.slen) {
498 proxy_postprocess(tdata);
499 return PJ_SUCCESS;
500 }
501
502
503 /* If the domain of the Request-URI indicates a domain this element is
504 * not responsible for, the Request-URI MUST be placed into the target
505 * set as the only target, and the element MUST proceed to the task of
506 * Request Forwarding (Section 16.6).
507 */
508 if (!is_uri_local(target)) {
509 proxy_postprocess(tdata);
510 return PJ_SUCCESS;
511 }
512
513 /* If the target set for the request has not been predetermined as
514 * described above, this implies that the element is responsible for the
515 * domain in the Request-URI, and the element MAY use whatever mechanism
516 * it desires to determine where to send the request.
517 */
518
519 /* We're not interested to receive request destined to us, so
520 * respond with 404/Not Found (only if request is not ACK!).
521 */
522 if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) {
523 pjsip_endpt_respond_stateless(global.endpt, rdata,
524 PJSIP_SC_NOT_FOUND, NULL,
525 NULL, NULL);
526 }
527
528 /* Delete the request since we're not forwarding it */
529 pjsip_tx_data_dec_ref(tdata);
530
531 return PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_NOT_FOUND);
532}
533
534
535/* Destroy stack */
536static void destroy_stack(void)
537{
538 pjsip_endpt_destroy(global.endpt);
539 pj_pool_release(global.pool);
540 pj_caching_pool_destroy(&global.cp);
541
542 pj_shutdown();
543}
544