blob: 470ad2517903f91ff8c00339154eb7c5fe631a55 [file] [log] [blame]
Benny Prijono5ac0bd72008-03-13 15:11:29 +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 <pjnath.h>
20#include <pjlib-util.h>
21#include <pjlib.h>
22
23
24#define THIS_FILE "client_main.c"
25#define LOCAL_PORT 1998
26#define BANDWIDTH 64 /* -1 to disable */
27#define LIFETIME 600 /* -1 to disable */
28#define REQ_TRANSPORT -1 /* 0: udp, 1: tcp, -1: disable */
29#define REQ_PORT_PROPS -1 /* -1 to disable */
30#define REQ_IP 0 /* IP address string */
31
32//#define OPTIONS PJ_STUN_NO_AUTHENTICATE
33#define OPTIONS 0
34
35
36struct peer
37{
38 pj_sock_t sock;
39 pj_sockaddr addr;
40};
41
42
43static struct global
44{
45 pj_caching_pool cp;
46 pj_pool_t *pool;
47 pj_stun_config stun_config;
48 pj_thread_t *thread;
49 pj_bool_t quit;
50
51 pj_turn_udp *udp_rel;
52 pj_sockaddr relay_addr;
53
54 struct peer peer[2];
55} g;
56
57static struct options
58{
59 char *srv_addr;
60 char *srv_port;
61 char *realm;
62 char *user_name;
63 char *password;
Benny Prijono5ac0bd72008-03-13 15:11:29 +000064 pj_bool_t use_fingerprint;
65} o;
66
67
68static int worker_thread(void *unused);
69static pj_status_t parse_addr(const char *input, pj_sockaddr_in *addr);
70static void turn_on_rx_data(pj_turn_udp *udp_rel,
71 const pj_uint8_t *pkt,
72 unsigned pkt_len,
73 const pj_sockaddr_t *peer_addr,
74 unsigned addr_len);
75static void turn_on_state(pj_turn_udp *udp_rel, pj_turn_state_t old_state,
76 pj_turn_state_t new_state);
77
78
79static void my_perror(const char *title, pj_status_t status)
80{
81 char errmsg[PJ_ERR_MSG_SIZE];
82 pj_strerror(status, errmsg, sizeof(errmsg));
83
84 PJ_LOG(3,(THIS_FILE, "%s: %s", title, errmsg));
85}
86
87#define CHECK(expr) status=expr; \
88 if (status!=PJ_SUCCESS) { \
89 my_perror(#expr, status); \
90 return status; \
91 }
92
93static int init()
94{
95 int i;
96 pj_status_t status;
97
98 CHECK( pj_init() );
99 CHECK( pjlib_util_init() );
100 CHECK( pjnath_init() );
101
102 /* Check that server is specified */
103 if (!o.srv_addr) {
104 printf("Error: server must be specified\n");
105 return PJ_EINVAL;
106 }
107
108 pj_caching_pool_init(&g.cp, &pj_pool_factory_default_policy, 0);
109
110 g.pool = pj_pool_create(&g.cp.factory, "main", 1000, 1000, NULL);
111
112 /* Init global STUN config */
113 pj_stun_config_init(&g.stun_config, &g.cp.factory, 0, NULL, NULL);
114
115 /* Create global timer heap */
116 CHECK( pj_timer_heap_create(g.pool, 1000, &g.stun_config.timer_heap) );
117
118 /* Create global ioqueue */
119 CHECK( pj_ioqueue_create(g.pool, 16, &g.stun_config.ioqueue) );
120
121 /*
122 * Create peers
123 */
124 for (i=0; i<PJ_ARRAY_SIZE(g.peer); ++i) {
125 int len;
126 pj_sockaddr addr;
127 pj_uint16_t port;
128
129 CHECK( pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &g.peer[i].sock) );
130 CHECK( pj_sock_bind_in(g.peer[i].sock, 0, 0) );
131
132 len = sizeof(addr);
133 CHECK( pj_sock_getsockname(g.peer[i].sock, &addr, &len) );
Benny Prijono17d10b52008-03-14 17:56:11 +0000134 port = pj_sockaddr_get_port(&addr);
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000135
136 CHECK( pj_gethostip(pj_AF_INET(), &g.peer[i].addr) );
Benny Prijono9e6d60a2008-03-20 16:32:06 +0000137 pj_sockaddr_set_port(&g.peer[i].addr, port);
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000138
139 }
140
141 /* Start the worker thread */
142 CHECK( pj_thread_create(g.pool, "stun", &worker_thread, NULL, 0, 0, &g.thread) );
143
144
145 return PJ_SUCCESS;
146}
147
148
149static int shutdown()
150{
151 unsigned i;
152
153 if (g.thread) {
154 g.quit = 1;
155 pj_thread_join(g.thread);
156 pj_thread_destroy(g.thread);
157 g.thread = NULL;
158 }
159 if (g.udp_rel) {
160 pj_turn_udp_destroy(g.udp_rel);
161 g.udp_rel = NULL;
162 }
163 for (i=0; i<PJ_ARRAY_SIZE(g.peer); ++i) {
164 if (g.peer[i].sock) {
165 pj_sock_close(g.peer[i].sock);
166 g.peer[i].sock = 0;
167 }
168 }
169 if (g.stun_config.timer_heap) {
170 pj_timer_heap_destroy(g.stun_config.timer_heap);
171 g.stun_config.timer_heap = NULL;
172 }
173 if (g.stun_config.ioqueue) {
174 pj_ioqueue_destroy(g.stun_config.ioqueue);
175 g.stun_config.ioqueue = NULL;
176 }
177 if (g.pool) {
178 pj_pool_release(g.pool);
179 g.pool = NULL;
180 }
181 pj_pool_factory_dump(&g.cp.factory, PJ_TRUE);
182 pj_caching_pool_destroy(&g.cp);
183
184 return PJ_SUCCESS;
185}
186
187
188static int worker_thread(void *unused)
189{
190 PJ_UNUSED_ARG(unused);
191
192 while (!g.quit) {
193 const pj_time_val delay = {0, 10};
194 int i;
195 pj_fd_set_t readset;
196
197 /* Poll ioqueue for the TURN client */
198 pj_ioqueue_poll(g.stun_config.ioqueue, &delay);
199
200 /* Poll the timer heap */
201 pj_timer_heap_poll(g.stun_config.timer_heap, NULL);
202
203 /* Poll peer sockets */
204 PJ_FD_ZERO(&readset);
205 for (i=0; i<PJ_ARRAY_SIZE(g.peer); ++i) {
206 PJ_FD_SET(g.peer[i].sock, &readset);
207 }
208
209 if (pj_sock_select(64, &readset, NULL, NULL, &delay) <= 0)
210 continue;
211
212 /* Handle incoming data on peer socket */
213 for (i=0; i<PJ_ARRAY_SIZE(g.peer); ++i) {
214 char buf[128];
215 pj_ssize_t len;
216 pj_sockaddr src_addr;
217 int src_addr_len;
218 pj_status_t status;
219
220 if (!PJ_FD_ISSET(g.peer[i].sock, &readset))
221 continue;
222
223 len = sizeof(buf);
224 src_addr_len = sizeof(src_addr);
225
226 status = pj_sock_recvfrom(g.peer[i].sock, buf, &len, 0,
227 &src_addr, &src_addr_len);
228 if (status != PJ_SUCCESS) {
229 my_perror("recvfrom error", status);
230 } else {
231 char addrinfo[80];
232 pj_sockaddr_print(&src_addr, addrinfo, sizeof(addrinfo), 3);
233 PJ_LOG(3,(THIS_FILE, "Peer%d received %d bytes from %s: %.*s",
234 i, len, addrinfo, len, buf));
235 }
236 }
237 }
238
239 return 0;
240}
241
242static pj_status_t create_relay(void)
243{
244 pj_turn_udp_cb rel_cb;
245 pj_stun_auth_cred cred;
246 pj_str_t srv;
247 pj_status_t status;
248
249 if (g.udp_rel) {
250 PJ_LOG(1,(THIS_FILE, "Relay already created"));
251 return -1;
252 }
253
254 pj_bzero(&rel_cb, sizeof(rel_cb));
255 rel_cb.on_rx_data = &turn_on_rx_data;
256 rel_cb.on_state = &turn_on_state;
257 CHECK( pj_turn_udp_create(&g.stun_config, pj_AF_INET(), &rel_cb, 0,
258 NULL, &g.udp_rel) );
259
260 if (o.user_name) {
261 pj_bzero(&cred, sizeof(cred));
262 cred.type = PJ_STUN_AUTH_CRED_STATIC;
263 cred.data.static_cred.realm = pj_str(o.realm);
264 cred.data.static_cred.username = pj_str(o.user_name);
265 cred.data.static_cred.data_type = 0;
266 cred.data.static_cred.data = pj_str(o.password);
Benny Prijono9e6d60a2008-03-20 16:32:06 +0000267 //cred.data.static_cred.nonce = pj_str(o.nonce);
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000268 } else {
269 PJ_LOG(2,(THIS_FILE, "Warning: no credential is set"));
270 }
271
272 srv = pj_str(o.srv_addr);
273 CHECK( pj_turn_udp_init(g.udp_rel, /* the relay */
274 &srv, /* srv addr */
275 (o.srv_port?atoi(o.srv_port):PJ_STUN_PORT),/* def port */
276 NULL, /* resolver */
277 (o.user_name?&cred:NULL), /* credential */
278 NULL) /* alloc param */
279 );
280
281 return PJ_SUCCESS;
282}
283
284static void destroy_relay(void)
285{
286 if (g.udp_rel) {
287 pj_turn_udp_destroy(g.udp_rel);
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000288 }
289}
290
291
292static void turn_on_rx_data(pj_turn_udp *udp_rel,
293 const pj_uint8_t *pkt,
294 unsigned pkt_len,
295 const pj_sockaddr_t *peer_addr,
296 unsigned addr_len)
297{
298 char addrinfo[80];
299
300 pj_sockaddr_print(peer_addr, addrinfo, sizeof(addrinfo), 3);
301
302 PJ_LOG(3,(THIS_FILE, "Client received %d bytes data from %s: %.*s",
303 pkt_len, addrinfo, pkt_len, pkt));
304}
305
306
307static void turn_on_state(pj_turn_udp *udp_rel, pj_turn_state_t old_state,
308 pj_turn_state_t new_state)
309{
Benny Prijono9e6d60a2008-03-20 16:32:06 +0000310 PJ_LOG(3,(THIS_FILE, "State %s --> %s", pj_turn_state_name(old_state),
311 pj_turn_state_name(new_state)));
312
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000313 if (new_state == PJ_TURN_STATE_READY) {
314 pj_turn_session_info info;
315 pj_turn_udp_get_info(udp_rel, &info);
316 pj_memcpy(&g.relay_addr, &info.relay_addr, sizeof(pj_sockaddr));
Benny Prijono9e6d60a2008-03-20 16:32:06 +0000317 } else if (new_state > PJ_TURN_STATE_READY && g.udp_rel) {
318 PJ_LOG(3,(THIS_FILE, "Relay shutting down.."));
319 g.udp_rel = NULL;
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000320 }
321}
322
323static pj_status_t parse_addr(const char *input,
324 pj_sockaddr_in *addr)
325{
326 const char *pos;
327 pj_str_t ip;
328 pj_uint16_t port;
329 pj_sockaddr tmp_addr;
330
331 pos = pj_ansi_strchr(input, ':');
332 if (pos==NULL) {
333 puts("Invalid format");
334 return -1;
335 }
336
337 ip.ptr = (char*)input;
338 ip.slen = pos - input;
339 port = (pj_uint16_t)atoi(pos+1);
340
341 if (port==0) {
342 puts("Invalid port");
343 return -1;
344 }
345
346 if (pj_sockaddr_in_init(&tmp_addr.ipv4, &ip, port)!=PJ_SUCCESS) {
347 puts("Invalid address");
348 return -1;
349 }
350
351 pj_memcpy(addr, &tmp_addr, sizeof(pj_sockaddr_in));
352
353 return PJ_SUCCESS;
354}
355
356static void menu(void)
357{
358 pj_turn_session_info info;
359 char client_state[20], relay_addr[80], peer0_addr[80], peer1_addr[80];
360
361 if (g.udp_rel) {
362 pj_turn_udp_get_info(g.udp_rel, &info);
363 strcpy(client_state, pj_turn_state_name(info.state));
Benny Prijono17d10b52008-03-14 17:56:11 +0000364 if (info.state >= PJ_TURN_STATE_READY)
365 pj_sockaddr_print(&info.relay_addr, relay_addr, sizeof(relay_addr), 3);
366 else
367 strcpy(relay_addr, "0.0.0.0:0");
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000368 } else {
369 strcpy(client_state, "NULL");
370 strcpy(relay_addr, "0.0.0.0:0");
371 }
372
373 pj_sockaddr_print(&g.peer[0].addr, peer0_addr, sizeof(peer0_addr), 3);
374 pj_sockaddr_print(&g.peer[1].addr, peer1_addr, sizeof(peer1_addr), 3);
375
376
377 puts("\n");
378 puts("+====================================================================+");
379 puts("| CLIENT | PEER-0 |");
380 puts("| | |");
Benny Prijono17d10b52008-03-14 17:56:11 +0000381 printf("| State : %-12s | Address: %-21s |\n",
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000382 client_state, peer0_addr);
Benny Prijono17d10b52008-03-14 17:56:11 +0000383 printf("| Relay addr: %-21s | |\n",
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000384 relay_addr);
385 puts("| | 0 Send data to relay address |");
Benny Prijono9e6d60a2008-03-20 16:32:06 +0000386 puts("| a Allocate relay +--------------------------------+ ");
387 puts("| s,ss Send data to peer 0/1 | PEER-1 |");
388 puts("| b,bb BindChannel to peer 0/1 | |");
389 printf("| x Delete allocation | Address: %-21s |\n",
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000390 peer1_addr);
391 puts("+-----------------------------------+ |");
392 puts("| q Quit | 1 Send data to relay adderss |");
393 puts("+-----------------------------------+--------------------------------+");
394 printf(">>> ");
395 fflush(stdout);
396}
397
398
399static void console_main(void)
400{
401 while (!g.quit) {
402 char input[32];
403 struct peer *peer;
404 pj_ssize_t len;
405 pj_status_t status;
406
407 menu();
408
409 fgets(input, sizeof(input), stdin);
410
411 switch (input[0]) {
Benny Prijono9e6d60a2008-03-20 16:32:06 +0000412 case 'a':
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000413 create_relay();
414 break;
Benny Prijono9e6d60a2008-03-20 16:32:06 +0000415 case 's':
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000416 if (g.udp_rel == NULL) {
417 puts("Error: no relay");
418 continue;
419 }
Benny Prijono9e6d60a2008-03-20 16:32:06 +0000420 if (input[1]!='s')
421 peer = &g.peer[0];
422 else
423 peer = &g.peer[1];
424
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000425 strcpy(input, "Hello from client");
426 status = pj_turn_udp_sendto(g.udp_rel, input, strlen(input)+1,
427 &peer->addr,
428 pj_sockaddr_get_len(&peer->addr));
429 if (status != PJ_SUCCESS)
430 my_perror("turn_udp_sendto() failed", status);
431 break;
Benny Prijono9e6d60a2008-03-20 16:32:06 +0000432 case 'b':
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000433 if (g.udp_rel == NULL) {
434 puts("Error: no relay");
435 continue;
436 }
Benny Prijono9e6d60a2008-03-20 16:32:06 +0000437 if (input[1]!='b')
438 peer = &g.peer[0];
439 else
440 peer = &g.peer[1];
441
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000442 status = pj_turn_udp_bind_channel(g.udp_rel, &peer->addr,
443 pj_sockaddr_get_len(&peer->addr));
444 if (status != PJ_SUCCESS)
445 my_perror("turn_udp_bind_channel() failed", status);
446 break;
Benny Prijono9e6d60a2008-03-20 16:32:06 +0000447 case 'x':
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000448 if (g.udp_rel == NULL) {
449 puts("Error: no relay");
450 continue;
451 }
452 destroy_relay();
453 break;
454 case '0':
455 case '1':
Benny Prijono9e6d60a2008-03-20 16:32:06 +0000456 peer = &g.peer[input[0]-'0'];
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000457 sprintf(input, "Hello from peer%d", input[0]-'0');
458 len = strlen(input)+1;
459 pj_sock_sendto(peer->sock, input, &len, 0, &g.relay_addr,
460 pj_sockaddr_get_len(&g.relay_addr));
461 break;
462 case 'q':
463 g.quit = PJ_TRUE;
464 break;
465 }
466 }
467}
468
469
470static void usage(void)
471{
472 puts("Usage: pjturn_client TARGET [OPTIONS]");
473 puts("");
474 puts("where TARGET is \"host[:port]\"");
475 puts("");
476 puts("and OPTIONS:");
477 puts(" --realm, -r Set realm of the credential");
478 puts(" --username, -u Set username of the credential");
479 puts(" --password, -p Set password of the credential");
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000480 puts(" --fingerprint, -F Use fingerprint for outgoing requests");
481 puts(" --help, -h");
482}
483
484int main(int argc, char *argv[])
485{
486 struct pj_getopt_option long_options[] = {
487 { "realm", 1, 0, 'r'},
488 { "username", 1, 0, 'u'},
489 { "password", 1, 0, 'p'},
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000490 { "fingerprint",0, 0, 'F'},
491 { "data", 1, 0, 'D'},
492 { "help", 0, 0, 'h'}
493 };
494 int c, opt_id;
495 char *pos;
496 pj_status_t status;
497
498 while((c=pj_getopt_long(argc,argv, "r:u:p:N:hF", long_options, &opt_id))!=-1) {
499 switch (c) {
500 case 'r':
501 o.realm = pj_optarg;
502 break;
503 case 'u':
504 o.user_name = pj_optarg;
505 break;
506 case 'p':
507 o.password = pj_optarg;
508 break;
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000509 case 'h':
510 usage();
511 return 0;
512 case 'F':
513 o.use_fingerprint = PJ_TRUE;
514 break;
515
516 default:
517 printf("Argument \"%s\" is not valid. Use -h to see help",
518 argv[pj_optind]);
519 return 1;
520 }
521 }
522
523 if (pj_optind == argc) {
524 puts("Error: TARGET is needed");
525 return 1;
526 }
527
528 if ((pos=pj_ansi_strchr(argv[pj_optind], ':')) != NULL) {
529 o.srv_addr = argv[pj_optind];
530 *pos = '\0';
531 o.srv_port = pos+1;
532 } else {
533 o.srv_addr = argv[pj_optind];
534 }
535
536 if ((status=init()) != 0)
537 goto on_return;
538
Benny Prijono9e6d60a2008-03-20 16:32:06 +0000539 //if ((status=create_relay()) != 0)
540 // goto on_return;
Benny Prijono5ac0bd72008-03-13 15:11:29 +0000541
542 console_main();
543
544on_return:
545 shutdown();
546 return status ? 1 : 0;
547}
548