commit 702b29bc22da6b6e441ed86ea059cbced5b123ac Author: mom040267 Date: Sun Apr 20 21:10:18 2014 +0000 initial code import diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..6b8ea91 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,46 @@ +Oleg Moskalenko : + General design and implementation + (2011-2013); + +Gabor Kovesdan, http://kovesdan.org : + FreeBSD packaging + (since v1.5.2.6); + +Daniel Pocock, http://danielpocock.com : + Debian packaging + (since v1.8.3.6); + +John Selbie (jselbie@gmail.com) : + Stuntman interoperability, RFC5780 fixes + MS Windows port work + (since v1.8.3.6); + +Lee Sylvester : + Status and statistics - ideas and pilot implementation + (since v1.8.4.0); + +Erik Johnston : + Access Control Lists, 2013 + (since v1.8.5.0); + +Roman Lisagor : + Testing, code optimization + (since v1.8.6.0); + +Vladimir Tsanev : + configure script and Makefile fixes, + Arch Linux port + (since v1.8.6.1); + +Po-sheng Lin : + Libevent dependencies cleaning + (since v2.0.1.1); + +Peter Dunkley : + CentOS/Fedora port + (since v2.6.6.1) + +Mutsutoshi Yoshimoto + TCP routing: testing and bug fixes + (since v3.2.2.7) + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..fc896c1 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,970 @@ +04/20/2014 Oleg Moskalenko +Version 3.3.0.0 'Threetrees': + - multi-tenant server. + +04/13/2014 Oleg Moskalenko +Version 3.2.3.6 'Marshal West': + - Addresses logging fixed. + - Redis admin options fixed. + - Redis compilation cleaned. + +04/07/2014 Oleg Moskalenko +Version 3.2.3.5 'Marshal West': + - Mobile allocation quota fixed (issue 121); + - --simple-log option added (Issue 122); + - documentation fixes (REST API, Redis). + +04/06/2014 Oleg Moskalenko +Version 3.2.3.4 'Marshal West': + - Mobile TCP sessions fixed (issue 120); + - log information improvements. + +04/04/2014 Oleg Moskalenko +Version 3.2.3.3 'Marshal West': + - Pkey and cert file descriptors to be closed + on initialization (issue 118); + - Address bind indefinite cycle on start-up fixed + (Issue 119); + - Allocation counters time lag improved. + +03/30/2014 Oleg Moskalenko +Version 3.2.3.2 'Marshal West': + - Allocation counters fixed (issue 117); + - a possible core dump in the server code fixed; + - a possible memory leak in server fixed. + +03/29/2014 Oleg Moskalenko +Version 3.2.3.1 'Marshal West': + - TCP congestion avoidance completed. + - Read and write streams are treated separately in + bandwidth control. + - Test client fixed. + - Experimental SHA256 key storage supported. + +03/17/2014 Oleg Moskalenko +Version 3.2.2.912 'Marshal West': + - TCP-in-TCP congestion avoidance implemented. + - UDP-in-TCP congestion avoidance improved. + - Alternate-server code cleaned. + +03/10/2014 Oleg Moskalenko +Version 3.2.2.911 'Marshal West': + - "Congestion control" for UDP-inside-TCP tunneling; + - memory management improvements; + - socket logging improvements; + - debug info added to CentOS and Fedora RPMs; + - TCP traffic buffering improved; + - Thread barriers cleaned; + - TCP memory leak fixed; + - minor TCP test client improvement. + +03/09/2014 Oleg Moskalenko +Version 3.2.2.910 'Marshal West': + - Log messages extended and cleaned. + - Some memory cleaning. + +03/02/2014 Oleg Moskalenko +Version 3.2.2.9 'Marshal West': + - Issue 113 fixed (TCP rate limit fixed); + - Issue 114 fixed (TCP stability). + +02/18/2014 Oleg Moskalenko +Version 3.2.2.8 'Marshal West': + - Issue 102: SO_BSDCOMPAT socket option removed; + - Issue 104: check for the REALM attribute value; + - Issue 105: no-cli segfault fixed; + - Issue 106: MESSAGE-INTEGRITY removed from DATA indication; + - Issue 108: Server should return 438 on unknown nonce; + - Issue 109: make the random functions stronger (mostly for + transaction ID and for nonce); + - Issue 111: fix valgrind warning on memory initialization. + - Issue 112: RTCP sockets logging. + +02/12/2014 Oleg Moskalenko +Version 3.2.2.7 'Marshal West': + - Possible indefinite cycle fixed in TCP/TCP routing (Issue 99); + - Address 0.0.0.0 can be used as a listener address (Issue 100); + - DHCP-configured servers supported (Issue 101); + +02/04/2014 Oleg Moskalenko +Version 3.2.2.6 'Marshal West': + - Channel traffic memory copy elimination. + - Send indication memory copy elimination. + - DTLS traffic processing memory copy eliminated. + - Mobility forbidden error code number fixed - according to the new draft document. + - getsockname() usage minimized. + - port allocation improved. + - default relay behavior fixed (when no relay addresses defined). + - atomic create permission request handling (Issue 97). + +01/25/2014 Oleg Moskalenko +Version 3.2.2.5 'Marshal West': + - code optimization. + +01/24/2014 Oleg Moskalenko +Version 3.2.2.4 'Marshal West': + - HMAC key handling fixed (Issue 96). + +01/23/2014 Oleg Moskalenko +Version 3.2.2.3 'Marshal West': + - Security fix (issue 95). + - Default "implicit" relay IP allocation policy is more usable + (issue 94 fixed). + - SSLv2 fixed (for those who are still using it) + (issue 93 fixed). + - Cosmetic changes. + +01/19/2014 Oleg Moskalenko +Version 3.2.2.1 'Marshal West': + - CPU/memory cache optimization (memory locality). + - torture tests enhanced. + - stability fixes. + - minor possible memory leak fix. + - new TLS options: --no-sslv2, --no-sslv3, --no-tlsv1, + --no-tlsv1_1, --no-tlsv1_2 + +01/06/2014 Oleg Moskalenko +Version 3.2.1.4 'Marshal West': + - Linux epoll performance improvements. + - DTLS minor fix. + +01/06/2014 Oleg Moskalenko +Version 3.2.1.3 'Marshal West': + - Telnet client added to installation when necessary. + +01/05/2014 Oleg Moskalenko +Version 3.2.1.2 'Marshal West': + - Config file adjusted for DragonFly. + +01/03/2014 Oleg Moskalenko +Version 3.2.1.1 'Marshal West': + - Minor TLS fix. + - Default cipher list is DEFAULT now. + +12/26/2013 Oleg Moskalenko +Version 3.2.1.0 'Marshal West': + - Optimized TCP network engine for Linux 3.9+. + - Security fix: DH and ECDH temporary keys are now + regenerated for each TLS or DTLS session. + - Fix for systems with multiple CPU cores (more than 128). + - DH TLS key now can be configured as 566, 1066 (default) or 2066 bits. + - DH TLS key can be taken from a PEM file. + - Issue 91 (test client crash) fixed. + - Configurable net engine type. + +12/25/2013 Oleg Moskalenko +Version 3.1.6.0 'Arch Lector': + - Timers optimization: linked list timers structure + for often-used intervals. + +12/23/2013 Oleg Moskalenko +Version 3.1.5.3 'Arch Lector': + - HTTP "keep-alive" support improved. + - TCP channel "fortification". + +12/19/2013 Oleg Moskalenko +Version 3.1.5.1 'Arch Lector': + - Private key password allowed for encrypted keys. + - HTTP "keep-alive" supported. + - "psd" CLI command added (ps dump to file). + +12/18/2013 Oleg Moskalenko +Version 3.1.4.2 'Arch Lector': + - Time functions optimization. + - Online changes to the alternate servers list thru telnet CLI. + - Certificate chain files allowed. + +12/13/2013 Oleg Moskalenko +Version 3.1.3.1 'Arch Lector': + - "Start time" ps command info added. + - Protocol option added to "pu" command. + - "Delete allocation" debug message fixed. + - "Allocation id" debug info message fixed. + - RFC6062 usage statistics fixed. + - Info/Debug messages cleaned. + +12/11/2013 Oleg Moskalenko +Version 3.1.2.3 'Arch Lector': + - CentOS 6 package fixed. + +12/10/2013 Oleg Moskalenko +Version 3.1.2.2 'Arch Lector': + - ps output typo fixed (TLS params). + - configurable EC curve name. + - CLI TLS-related information extended. + - "print users" (pu) CLI command added. + +12/09/2013 Oleg Moskalenko +Version 3.1.2.1 'Arch Lector': + - DH cipher suites basic implementation. + - Elliptic Curve cipher suites basic implementation. + - RFC 6062 crash fixed. + - More CLI parameters added. + - Redis allocation statistics fixed. + - Number of cli max session lines configurable. + - uclient cipher suite configurable. + +12/08/2013 Oleg Moskalenko +Version 3.1.1.0 'Arch Lector': + - Telnet CLI. + - RFC 6062 internal messaging fixed. + - Server relay endpoints (a non-standard feature). + - "atomic line" stdout log print. + - printed help minor fix. + - client program does not necessary + require certificate for TLS. + - docs fixes. + - allocation quota bug fixed. + +11/29/2013 Oleg Moskalenko +Version 3.0.2.1 'Practical Frost': + - TCP stability fixes. + - RFC 6062 "first packet(s)" bug fixed. + - RFC 6062 stability fixes. + - Multithreaded Mobile ICE. + +11/28/2013 Oleg Moskalenko +Version 3.0.1.4 'Practical Frost': + - CentOS/Fedora packaging fixed. + - PID file fixed. + +11/26/2013 Oleg Moskalenko +Version 3.0.1.3 'Practical Frost': + - Misc cosmetic changes. + - CentOS/Fedora packaging fixed. + +11/25/2013 Oleg Moskalenko +Version 3.0.1.2 'Practical Frost': + - Mobility draft implemented. + - DTLS communications fixes. + - UDP Linux optimization. + - Log output time starts with 0. + - A new "drop root privileges" options: + --proc-user and --proc-group added. + - SHA256 agility updated: 426 error code on too weak SHA function. + - "-m 0" and "-m 1" options improved. + - non-threading environment support dropped. + - stability fixes. + - OpenSUSE support added. + +11/10/2013 Oleg Moskalenko +Version 3.0.0.0 'Practical Frost': + - New network engine for Linux kernel 3.9+. + +11/08/2013 Oleg Moskalenko +Version 2.6.7.2 'Harding Grim': + - SHA256 agility updated: 441 error code on too weak SHA function. + +11/07/2013 Oleg Moskalenko +Version 2.6.7.1 'Harding Grim': + - CentOS / Fedora uninstall script. + - Debian compilation error fixed. + - OpenSSL 0.9.7 and earlier build fixed. + - NetBSD build fixed. + +11/03/2013 Oleg Moskalenko , + Peter Dunkley +Version 2.6.7.0 'Harding Grim': + - CentOS 6 pre-compiled distribution. + - Fedora pre-compiled distribution. + - TURN_NO_TLS case compilation cleaning. + - Text files cleaning. + - Issue 68 fixed (no-stun option added). + +10/27/2013 Oleg Moskalenko +Version 2.6.6.1 'Harding Grim': + - SHA256 added as a non-standard message integrity option. + - CentOS rpm specs added. + - Peter Dunkley added to the authors list. + +10/20/2013 Oleg Moskalenko +Version 2.6.6.0 'Harding Grim': + - Cygwin loopback relay interfaces fixed (Issue 62). + - rpath added to the Makefile (Issue 63). + - CONFLICTS added to FreeBSD port Makefile (Issue 64). + - Certificate check options, for server and for the test client (Issue 65). + - Some compilation cleaning. + +10/09/2013 Oleg Moskalenko +Version 2.6.5.2 'Harding Grim': + - Documentation changes. + - Redis-related memory leak fixed (Issue 61). + +09/25/2013 Oleg Moskalenko +Version 2.6.4.1 'Harding Grim': + - Crash on uninitialized redis db name is fixed (Issue 59). + - Optional authentication of STUN Binding request is implemented (Issue 60). + +09/16/2013 Oleg Moskalenko +Version 2.6.3.1 'Harding Grim': + - Issue 58: support changing white/black IP lists while server is running. + database tables (keys for redis) added for that new functionality. + +09/03/2013 Oleg Moskalenko +Version 2.6.2.2 'Harding Grim': + - Issue 52: RFC 6062 relay endpoints connection process + fixed for Linux pre-3.9 kernel. + +09/03/2013 Oleg Moskalenko +Version 2.6.2.1 'Harding Grim': + - UDP performance improvements. + - Issue 56: DTLS scaleability improvements. + - Issue 55: DTLS support in Cygwin. + - Issue 57: --pidfile option + - Issue 52: RFC 6062 relay endpoints connection process fixed. + - Issue 53: Fingerprints added to the indications. + - Issue 54: Long-term credentials mechanism integrity and software attributes + added to the indications. + +08/11/2013 Oleg Moskalenko +Version 2.6.1.4 'Harding Grim': + - UDP memory leak fixed. + +08/11/2013 Oleg Moskalenko +Version 2.6.1.3 'Harding Grim': + - DTLS crash fix. + +08/10/2013 Oleg Moskalenko +Version 2.6.1.2 'Harding Grim': + - TLS buffer decreased to avoid memory problems. + - TLS BIO object fix. + - UDP socket open/reopen process fixed. + +08/08/2013 Oleg Moskalenko +Version 2.6.1.1 'Harding Grim': + - Network optimization: + * "pure" UDP setup optimized (when no DTLS configured); + * Auxiliary listening endpoints (configured by + --aux-server=). + * --udp-self-balance option to balance the UDP traffic + among the aux endpoints (for clients supporting + 300 ALTERNATE-SERVER response). + - Security improvements: + * no authentication required on the load balancer server (Issue 50). + * REST API improvement: + = --secret-ts-exp-time option deprecated; + = In REST API timestamp, we are now using + the expiration time (Issue 31). + * Configurable cipher suite in the TURN server. + * SSL3 support. + * TLS 1.1 and 1.2 support. + * SSL2 "encapsulation" mode support. + * NULL OpenSSL cipher is allowed to be negotiated between + server and client. + * -U option (NULL cipher) added to the test client. + * DTLS crash fixed on overload. + - STUN enhancements and fixes: + * Classic STUN transaction ID fixed (Issue 48). + * Classic STUN attribute ERROR fixed (Issue 49). + * Unused RFC 5780 functionality removed from TCP, TLS and DTLS relays. + * resources optimization for stun-only: short connection expiration time. + +07/26/2013 Oleg Moskalenko , + Vladimir Tsanev +Version 2.5.2.1 'Shivers': + - log file placement changes. + - Base64 encode/decode memory initialization fix. + +07/23/2013 Oleg Moskalenko , + Po-sheng Lin +Version 2.5.1.2 'Shivers': + - getopt fix in client test programs. + - cosmetic changes. + - allow anonymous alternate-server functionality. + +07/21/2013 Oleg Moskalenko +Version 2.5.1.1 'Shivers': + - Improved "split" network engine: + two different threading models for TCP and UDP. + - DTLS crash fixed. + - Multithreading with Cygwin. + +07/20/2013 Oleg Moskalenko +Version 2.1.3.1 'Shivers': + - DTLS improvements for DOS attacks + - deeper optimization for DOS attack (mostly for Linux) + +07/19/2013 Oleg Moskalenko +Version 2.1.2.0 'Shivers': + - deeper optimization for DOS attack (mostly for Linux) + +07/18/2013 Oleg Moskalenko , + Po-sheng Lin +Version 2.1.1.1 'Shivers': + - udp fixes. + - Makefile cleaning. + - Dependencies cleaning. + - DOS attack client emulation. + - DOS attack defense logic added to the server. + +07/14/2013 Oleg Moskalenko +Version 2.0.0.0 'Shivers': + - new networking engine: + - scalable UDP socket model. + - multithreaded TCP relay implemented. + - race condition fixed in authentication of TCP sessions. + - Cygwin "port" fixed. + +06/23/2013 Oleg Moskalenko , + Vladimir Tsanev +Version 1.8.7.0 'Black Dow': + + - Added support for obsolete "classic" STUN RFC 3489; + - Full TURN support for Cygwin implemented: MS-Win UDP sockets fixed; + - Relay threads number changed; + - Fedora warnings fixed; + - turndb/testdbsetup.sh example file added; + - Multiple Makefile and ./configure script fixes implemented: + * Changes taken from Arch Linux port; + * Manpages installation and deinstallation; + * rfc5769check utility removed from installation, it is used for the + compilation result test only and makes no sense for the end user; + * "--parameter" format support in ./configure script; it allows + simpler native OS package definitions (like in Debian package); + * Mac OS X linking warnings removed. + * pthread test fixed. + +06/08/2013 Oleg Moskalenko +Version 1.8.6.3 'Black Dow': + + - DONT-FRAGMENT flag removed on UDP listening (clients-facing) sockets. + - UDP fix for Cygwin only: UDP channels work fine now. + - docs fixes. + +06/06/2013 Oleg Moskalenko +Version 1.8.6.2 'Black Dow': + + - Just cosmetic re-packaging for Debian, tarball warnings removed. + +06/05/2013 Oleg Moskalenko +Version 1.8.6.1 'Black Dow': + + - Peer permissions bug fixed. + +06/03/2013 Oleg Moskalenko +Version 1.8.6.0 'Black Dow': + + - Optimization. + - Mac OS X compilation fixes. + +06/01/2013 Oleg Moskalenko +Version 1.8.5.4 'Black Dow': + + - Issues 29 and 30 fixed (channels padding). + - minor fixes. + - Mac OS X compilation fixes. + - Cygwin-related compilation fixes and INSTALL additions. + +05/31/2013 Oleg Moskalenko +Version 1.8.5.3 'Black Dow': + + - REST API extra script example and docs extention. + +05/26/2013 Oleg Moskalenko +Version 1.8.5.1 'Black Dow': + + - Config file parsing fixed (Issue 28) + +05/20/2013 Oleg Moskalenko , + Erik Johnston +Version 1.8.5.0 'Black Dow': + + - IP access control lists. + - log file name fix. + - alt-* ports default behavior changed. + - "passive TCP" option in uclient. + +05/18/2013 Oleg Moskalenko +Version 1.8.4.5 'Black Dow': + + - socket conditions cleaned (SIGPIPE, etc) + +05/17/2013 Oleg Moskalenko +Version 1.8.4.4 'Black Dow': + + - configuration and installation adjusted for: + - NetBSD; + - Solaris; + - OpenBSD; + - Screen messages fixed; + - code security fixes. + +05/15/2013 Oleg Moskalenko +Version 1.8.4.3 'Black Dow': + + - Compilation warning removed. + - Log file fixed (Issue 26) + +05/15/2013 Oleg Moskalenko +Version 1.8.4.2 'Black Dow': + + - repackaging for Debian compliance. Docs separated. + +05/14/2013 Oleg Moskalenko +Version 1.8.4.1 'Black Dow': + + - Cosmetics (docs, warnings, etc). + - More complex case of TURN-server-behind-NAT is implemented, + when multiple public-ip/private-ip mappings are involved. + +05/13/2013 Oleg Moskalenko +Version 1.8.4.0 'Black Dow': + + - Redis DB support added. + - Crash on help text fixed. + - Max allocation time can be changed in the command-line or + in the config file. + +05/09/2013 Oleg Moskalenko +Version 1.8.3.9 'Black Dow': + + - No changes - just the tarball is repackaged for Debian compatibility. + +05/07/2013 Oleg Moskalenko +Version 1.8.3.8 'Black Dow': + + - multicast and loopback addresses disallow options added. + - option to direct all log messages to the system log (syslog). + +05/02/2013 Oleg Moskalenko +Version 1.8.3.7 'Black Dow': + + - Allocation status log. + +05/01/2013 Oleg Moskalenko +Version 1.8.3.6 'Black Dow': + + - Stuntman client interoperability fixed. + - Manpages installation fixed. + +04/30/2013 Oleg Moskalenko +Version 1.8.3.5 'Black Dow': + + - Lintian fixes. + +04/27/2013 Oleg Moskalenko +Version 1.8.3.4 'Black Dow': + + - Installation fixes. + +04/26/2013 Oleg Moskalenko +Version 1.8.3.3 'Black Dow': + + - Log file midnight rollover implemented (Issue 15). + +04/25/2013 Oleg Moskalenko +Version 1.8.3.1 'Black Dow': + + - Configurable REST API separator symbol (Issue 16). + - Stale Nonce bug fixed (Issue 17). + - Minor client fix. + +04/21/2013 Oleg Moskalenko +Version 1.8.3.0 'Black Dow': + + - STUN stand-alone functionality improved according to RFC 5389. + - ALTERNATE-SERVER implemented as "light" load balancing feature. + - stun-only option implemented. + - scripts directory reorganized. + +04/19/2013 Oleg Moskalenko +Version 1.8.2.1 'Black Dow': + + - Misc docs fixes. + +04/13/2013 Oleg Moskalenko +Version 1.8.2.0 'Black Dow': + + - Multiple database shared secrets supported for REST API. + - Added support for some OpenSSL FIPS versions (like openssl 0.9.8e-fips-rhel5). + +04/13/2013 Oleg Moskalenko +Version 1.8.1.3 'Black Dow': + + - Maintenance (docs, etc). + - Added partial support for Cygwin. Only TCP & TLS protocols + are support for client-to-server communications (as in RFC 5766 and + RFC 6062). UDP supported only for relay communications. DTLS is not + supported at all. The problem is in Winsock UDP sockets implementation. + +04/11/2013 Oleg Moskalenko +Version 1.8.1.2 'Black Dow': + + - Work on configuration and build. + +04/9/2013 Oleg Moskalenko +Version 1.8.1.1 'Black Dow': + + - Docs improvements. + - Load balancing use case added to TurnNetworks.pdf. + - Verbose mode split into 'normal' and 'extra' modes. + - Logging extended and fixed. + +04/7/2013 Oleg Moskalenko +Version 1.8.1.0 'Black Dow': + + - Compilation flags improved. + - utility programs renamed and moved to bin/ directory. + - README and turnserver man page separated into three sections - + turnserver, turnadmin, turnutils. + +04/6/2013 Oleg Moskalenko +Version 1.8.0.6 'Black Dow': + + - Added option "--psql-userdb" for better visual separation + between PostgreSQL and MySQL stuff. + - turnadmin flat files handling fixed. + - added set/show commands to turnadmin for secret key. + +04/6/2013 Oleg Moskalenko +Version 1.8.0.5 'Black Dow': + + - turnadmin MySQL connection fixed. + - minor cosmetic changes. + - Added "list" commands for long-term and short-term users, + to turnadmin. + +04/5/2013 Oleg Moskalenko +Version 1.8.0.4 'Black Dow': + + - Minor compilation fixes. + - Minor docs fixes. + - "connect_timeout" option support for MySQL. + +04/5/2013 Oleg Moskalenko +Version 1.8.0.3 'Black Dow': + + - Issue 11 (secret timestamp check) fixed. + +04/4/2013 Oleg Moskalenko +Version 1.8.0.2 'Black Dow': + + - TCP sockets flush removed. + - rfc5769check utility removed from the Makefile. + +04/4/2013 Oleg Moskalenko +Version 1.8.0.1 'Black Dow': + + - Some short-term auth problems fixed. + - rfc5769check utility added to the Makefile and upgraded. + +04/3/2013 Oleg Moskalenko +Version 1.8.0.0 'Black Dow': + + - Short-term credentials mechanism implemented. + +04/2/2013 Oleg Moskalenko +Version 1.7.3.1 'Superior Glokta': + + - Listeners code cleaned. + - The default number of extra relay threads changes from 0 to 1. + +04/1/2013 Oleg Moskalenko +Version 1.7.3.0 'Superior Glokta': + + - Issue 10 fixed: log file control options. + Two options added: --no-stdout-log and --log-file. + +03/29/2013 Oleg Moskalenko +Version 1.7.2.0 'Superior Glokta': + + - Issue 9 fixed (uclient). + - Secret-based authentication implemented (see TURNServerRESTAPI.pdf). + - Uclient docs fixed. + - database schema extended (table for the secret added). + +03/27/2013 Oleg Moskalenko +Version 1.7.1.2 'Superior Glokta': + + - CHANNEL BIND request handling fixed: now it produces an error + when client is trying to tie the same peer address to + different channels. + - uclient and peer test apps upgraded so that RTP channels + are talking to and RTCP channels are talking + to in client-to-peer communication patterns. + - compilation warning is fixed when MySQL is not used. + +03/27/2013 Oleg Moskalenko +Version 1.7.1.1 'Superior Glokta': + + - CONNECT response fixed in RFC 6062. + - uclient checks server responses integrity. + +03/26/2013 Oleg Moskalenko +Version 1.7.1.0 'Superior Glokta': + + - MySQL support added for the user keys repository. + - PostgreSQL support improved. + - Docs fixed. + - 64 bits platform fixes. + +03/23/2013 Oleg Moskalenko +Version 1.7.0.0 'Glokta': + + - Authentication fix. + - PostgreSQL database can be used as the user keys repository. + +03/21/2013 Oleg Moskalenko +Version 1.6.1.3 'Whirrun': + + - UDP segmentation fault fixed + +03/21/2013 Oleg Moskalenko +Version 1.6.1.2 'Whirrun': + + - RFC 6062 fix + +03/21/2013 Oleg Moskalenko +Version 1.6.1.1 'Whirrun': + + - Authentication error fixed + +03/19/2013 Oleg Moskalenko +Version 1.6.1.0 'Whirrun': + + - --stale-nonce option + - working on userdb + - "hang on" option fixed in uclient + +03/18/2013 Oleg Moskalenko +Version 1.6.0.2 'Whirrun': + + - working on userdb + - c++ compilation fix + +03/17/2013 Oleg Moskalenko +Version 1.6.0.1 'Whirrun': + + - uclient performance improved + - TurnNetworks.pdf document added + +03/15/2013 Oleg Moskalenko +Version 1.6.0.0 'Whirrun': + + - "Pure" TCP relaying (RFC 6062) implemented. + - Network interactions fixes. + - RFC 6062 test scripts added. + +03/03/2013 Oleg Moskalenko +Version 1.5.2.8 'Iosiv Lestek': + + - authorization processing improvements. + - peer application fixed. + - some ICE attributes added. + +02/27/2013 Oleg Moskalenko +Version 1.5.2.7 'Iosiv Lestek': + + - authorization processing improvements + - Issue 4 fixed. + - secure client-to-client script added + +02/22/2013 Oleg Moskalenko +Version 1.5.2.6 'Iosiv Lestek': + + - strcpy/strncpy fixed + - some screen messages fixed + - uclient statistics fixed + - software attribute fixed + - example scripts fixed + +02/16/2013 Oleg Moskalenko +Version 1.5.2.5 'Lestek': + + - uclient application fixed + - Docs fixes + +02/14/2013 Oleg Moskalenko +Version 1.5.2.4 'Lestek': + + - Crash fixed on unconfigured interfaces + - Docs fixes + +02/12/2013 Oleg Moskalenko +Version 1.5.2.3 'Lestek': + + - Added feature: TURN Server always uses fingerprints in a session if + the session client is using fingerprints. + - Default unsecure alternative port changed to 3479, + default secure alternative port changed to 5350. + - TURN Server always trying to search for default certificate file + turn_server_cert.pem and for default private key file + turn_server_pkey.pem, if not certificate or private key is + explicitly configured. + - configurable packet rate in the uclient test program. + - default peer port changed to 3480. + - -z, --no-auth option added to turnserver. + +02/11/2013 Oleg Moskalenko +Version 1.5.2.2 'Lestek': + + - Some cleanup added to the network input handlers. + +02/9/2013 Oleg Moskalenko +Version 1.5.2.1 'Lestek': + + - Binding requests do not require authentication. + - SOFTWARE in the end of the message. + +02/8/2013 Oleg Moskalenko +Version 1.5.2.0 'Lestek': + + - NAT discovery fixed (RFC5780). + +02/8/2013 Oleg Moskalenko +Version 1.5.1.6 'Calder': + + - Installation instructions fixed. + +02/8/2013 Oleg Moskalenko +Version 1.5.1.5 'Calder': + + - Mac compilation fixes. + - Fixes for old Linuxes. + +02/7/2013 Oleg Moskalenko +Version 1.5.1.4 'Calder': + + - Configuration alert (warning) messages. + - Relay addresses by default use listener addresses. + - Realm/user sequence fixed in the config file reading. + +01/27/2013 Oleg Moskalenko +Version 1.5.1.3 'Calder': + + - 'External' IP implemented for TURN-server-behind-NAT situation. + +01/26/2013 Oleg Moskalenko +Version 1.5.1.2 'Calder': + + - Alternative ports moved to 20000-ish territory. + - Docs fixes. + +01/22/2013 Oleg Moskalenko +Version 1.5.1.1 'Calder': + + - Docs fixes. + +01/22/2013 Oleg Moskalenko +Version 1.5.1.0 'Calder': + + - C++ compatible headers and build. + - C++ library header. + - HTML-formatted development reference. + +01/14/2013 Oleg Moskalenko +Version 1.5.0.0 'Calder': + + - RFC 5769 check utility implemented. + - RFC 5780 STUN extension implemented. + +01/13/2013 Oleg Moskalenko +Version 1.4.2.5 'Scale': + + - Issue 2 fixed. + +01/08/2013 Oleg Moskalenko +Version 1.4.2.4 'Scale': + + - Bogus "Bind to device" error message removed (Linux). + - Docs improvements. + +01/08/2013 Oleg Moskalenko +Version 1.4.2.3 'Scale': + + - Bandwidth limitation implemented (--max-bps option). + - DTLS communications improved. + +01/07/2013 Oleg Moskalenko +Version 1.4.2.2 'Scale': + + - Output messages fixed. + - Peer test application accepts multiple listening addresses. + - config search directories improved. + +01/06/2013 Oleg Moskalenko +Version 1.4.2.1 'Scale': + + - Examples directory structure fixed + - Installation fixes + - Output messages fixed + +01/05/2013 Oleg Moskalenko +Version 1.4.2 'Scale': + + - Daemon execution improved + - Installation fixes + - Added comments to the scripts + +01/04/2013 Oleg Moskalenko +Version 1.4.1.2 'Scale': + + - Configure script introduced + - Installation fixes + - Run as daemon + +01/01/2013 Oleg Moskalenko +Version 1.4.1 'Scale': + + - Options fixes + - Build fixes + - Script fixes + - Installation fixes + +12/31/2012 Oleg Moskalenko +Version 1.4 'Scale': + + - Separate file for the dynamic user database + - Build fixes + - Script fixes + - Logging fixes + +12/29/2012 Oleg Moskalenko +Version 1.3.0.2 'Ferro': + + - Debian 'squeeze' compilation fix + +12/26/2012 Oleg Moskalenko +Version 1.3.0.1 'Ferro': + + - install procedure minor improvements + +12/24/2012 Oleg Moskalenko +Version 1.3 'Ferro': + + - default conf file renamed to turnserver.conf + - build script improved + - client library linking fixed + - install procedure + +12/23/2012 Oleg Moskalenko +Version 1.2.3 'Luthar': + + - turnserver options fixed + - man page renamed to turnserver + +12/22/2012 Oleg Moskalenko +Version 1.2.2: + + - Man page fix + +12/21/2012 Oleg Moskalenko +Version 1.2.1 'Juvens': + + - Man page + +12/21/2012 Oleg Moskalenko +Version 1.2 'Euz': + + - Project cleaning + +12/20/2012 Oleg Moskalenko +Version 1.1 'no name': + + - DTLS extension + +12/17/2012 Oleg Moskalenko +Version 1.0 'no name': + + - RFC 5766 + - RFC 6156 diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..1858134 --- /dev/null +++ b/INSTALL @@ -0,0 +1,963 @@ +I. TURN Server as a standard OS package + +At the present time, several operation systems have this project pre-packaged: + +1) FreeBSD (and PC-BSD) have this project as a "port", named "turnserver", +in /usr/ports/net/turnserver directory. Installation is very simple: + +# optional commands, to update the ports tree: + $ sudo portsnap fetch + $ sudo portsnap update + +# Build and install the TURN Server: + $ cd /usr/ports/net/turnserver + $ sudo make install clean + +2) Debian "jessie" (and the recent version of Ubuntu and Mint) +have the predecessor of this project packaged as "rfc5766-turn-server", see the link: + +http://packages.qa.debian.org/r/rfc5766-turn-server.html + +In the new Debian "jessie", and in the related Ubuntu and Mint, you will +be able to just select rfc5766-turn-server from the packages list and +install it through Synaptic or through the package manager. + +3) ArchLinux has alse the predecessor of the TURN server package: + +https://aur.archlinux.org/packages/rfc5766-turn-server/ + +4) OpenSUSE has a package, too: + +https://build.opensuse.org/package/show/home:ehauenstein/rfc5766-turn-server + +If you are using a pre-packaged TURN server then you can skip +to the section IX. + +II. DOWNLOAD + +You have to download the archive file turnserver-*.tar.gz and unpack it: + +$ tar xfz turnserver-*.tgz + +it will create the directory 'turnserver-*' with all sources, build files, +examples and documentation. + +III. BUILD + +If you are sure that you system is ready for the build (see the section +"Extra libraries and Utilities" below) then you can build the system. +First, you have to run the configure script: + + $ cd turnserver-* + $ ./configure + +It will create a Makefile customized for your system. + +By default, the generated Makefile will be set to install everything +in: + - /usr on Solaris. + - /usr/pkg on NetBSD. + - /usr/local everywhere else. + +The binaries will be copied in bin subdirectory of the installation +destination, config files copied to etc subdirectory. There will be +also documents, examples and some other files, in separate directories. + + You can change the root configured destination directory by +setting PREFIX variable in the +configure command line. For example: + + $ PREFIX=/opt ./configure + +Or: + + $ ./configure --prefix=/opt + + You can change the auxiliary configured destination sub-directories by +setting BINDIR, CONFDIR, MANPREFIX, EXAMPLESDIR, DOCSDIR, LIBDIR, SCHEMADIR +and TURNINCLUDEDIR variables in the +configure command line. For example: + + $ PREFIX=/opt BINDIR=/opt/bin64 CONFDIR=/opt/conf ./configure + +Or: + + $ ./configure --prefix=/opt --bindir=/opt/bin64 --confdir=/opt/conf + + You also can change the compilation and link options by +setting common build variables in the +configure command line. For example: + + $ CC=clang CFLAGS=-D_CAURIB LDFLAGS=-lshanka ./configure --prefix=/opt/shy + +See below a separate INSTALL section for more details. + +The script configure is a proprietary script. It will create a Makefile +that you can use to build the project: + + $ make + +The make command without options will do the following: + - compile the code. + - create bin/ sub-directory and put the TURN server, TURN admin and + "utility" programs there. + - create lib/ sub-directory and put the client library there. + - create include/turn/ sub-directory and put include files there. + +The programs can be either called directly, or a shell scripts can be used. +The script examples are located in examples/scripts directory. These scripts +are just examples: you can run them successfully for the tests, but +you will have to change the script parameters for your real environment. + +The command: + + $ sudo make install + +will install everything into the system file structure (see below). + +(NOTE: On NetBSD, use "su root -c"). + +The command: + + $ sudo make deinstall + +will remove all installed TURN Server files from your system. + +The command: + + $ make clean + +will clean all results of the build and configuration actions. + +Do not run "make clean" before "make deinstall". The "clean" command will +remove the Makefile and you will not be able to "deinstall" then. If that +has happened, then run ./configure and make again, then deinstall and then +clean. + +NOTE: On most modern systems, the build will produce dynamically linked +executables. If you want statically linked executables, you have to modify, +accordingly, the Makefile.in template file. + +IV. INSTALL + +This step is optional. You can run the turnserver from the original build +directory, successfully, without installing the TURN server into the system. +You have to install the turnserver only if you want to integrate the +turnserver in your system. + +Run the command: + +$ make install + +It will install turnserver in /usr/local/ directory (or to whatever directory +was set in the PREFIX variable). You will have to copy +/usr/local/etc/turnserver.conf.default to /usr/local/etc/turnserver.conf file +and adjust your runtime configuration. + +This command will also: + + - copy the content of examples subdirectory into + PREFIX/share/examples/turnserver/ directory; + - copy the content of include/turn subdirectory into + PREFIX/include/turn/ directory; + - copy the database schema file turndb/schema.sql into + PREFIX/share/turnserver/ + directory; + - copy all docs into PREFIX/share/doc/turnserver/ directory. + +The installation destination of "make install" can be changed by +using DESTDIR variable, for example: + + $ ./configure --prefix=/usr + $ make + $ make DESTDIR=/opt install + +In this example, the root installation directory will be /opt/usr. + +The "configure" script by default generates a Makefile with "rpath" option +set for the binaries linking (if your compiler allows that option). If that +is not desirable (like in some OS packaging procedures), then run the +"configure" script with --disable-rpath option. + +If you do not want to use the rpath linking option, or you OS or compiler +do not allows that, then after the installation, you may have to adjust the +system-wide shared library search path by using "ldconfig -n " +(Linux), "ldconfig -m " (BSD) or "crle -u -l " +(Solaris). Your system must be able to find the libevent2, openssl and +(optionally) PostgreSQL and/or MySQL (MariaDB) and/or Redis shared libraries, +either with the help of the system-wide library search configuration or by +using LD_LIBRARY_PATH. "make install" will make a non-garantied effort to add +automatically PREFIX/lib and /usr/local/lib to the libraries search path, +but if you have some libraries in different non-default directories +you will have to add them manually to the search path, or you +will have to adjust LD_LIBRARY_PATH. + +V. PLATFORMS + +The TURN Server is using generic *NIX system APIs and is supposed to be +usable on wide range of *NIX systems. + +The following platforms have been used in the development: + + - Linux Ubuntu 11.x and 12.x, i386 and x86_64 + - FreeBSD 6.x, i386 + - FreeBSD 8.x, i386 + - PC-BSD 9.x, x86_64 + - Solaris 11, x86_64 + - Linux CentOS / Red Hat Enterprise Edition 6.3, x86_64 (amd64) + - Linux CentOS / Red Hat Enterprise Edition 6.4, x86_32 (i386) + - Linux Debian 'Squeeze', i386 + - Linux Mint 14.1 'Nadia', i386 + - Linux Debian 'Wheezy', x86_64 + - Cygwin 1.7.20 + - NetBSD 6.0.1 + - OpenBSD 5.3 + - Amazon Linux + - Mac OS X Mountain Lion + - ArchLinux + - Fedora 19 + - OpenSUSE 12.3 x86_64 + +It must work on many other *NIXes, as well. The configure script and/or +Makefile may need adjustments for other *NIXes not mentioned above. + +The code of the client messaging library can be compiled and used on +Windows, too, but it is not supported for now. + +VI. COMPILERS + +The TURN Server is written in C programming language, for portability +and for the performance reasons. + +The tested C compilers are: + + - gcc 3.4.4 thru 4.8.1 + - clang 3.0 or better + - Solaris Studio 12.3 C compiler, version 5.12 + +It may be compiled with others compilers, too. + +The code is compatible with C++ compiler, and a C++ compiler +(like g++) can be used for the compilation, too: + + $ CC=g++ ./configure + $ make + +VII. WHICH EXTRA LIBRARIES AND UTILITIES YOU NEED + +In addition to common *NIX OS services and libraries, to compile this code, +OpenSSL (version 1.0.0a or better recommended) and libevent2 (version 2.0.5 +or better) are required, the PostgreSQL C client development setup is +optional, the MySQL (MariaDB) C client development setup is optional, and the +Hiredis development files for Redis database access are optional. +For fully functional build, the extra set of libraries must be installed +in full version (the development headers and the libraries to link with). +For runtime, only runtime setup is required. If the build is modified for +static linking, then even runtime installation is not needed. + +OpenSSL, libevent2, PostgreSQL, MySQL (or MariaDB) and Hiredis +libraries can be downloaded from their web sites: + - http://www.openssl.org (required); + - http://www.libevent.org (required); + - http://www.postgresql.org (optional); + - http://www.mysql.org (or http://mariadb.org) (optional); + - http://redis.io (optional). + +The installations are pretty straightforward - the usual +"./configure" and "make install" commands. Install them into their default +locations - the configure script and the Makefile are assuming that they are +installed in their default locations. If not, then you will have to modify +those. + +Most modern popular systems (FreeBSD / PC-BSD, Linux Ubuntu 11.10+, Debian Wheezy, +Linux Mint 14+, Amazon Linux, Fedora) have a simpler way of the third party tools +installation: + + *) PC-BSD or FreeBSD (the FRESH ports database is assumed to be installed, with + the turnserver port included): + + $ cd /usr/ports/net/turnserver + $ sudo make install clear + + That's it - that command will install the TURN server with all necesary + thrid-party tools. + + If you system have no fresh ports repository: + + $ cd /usr/ports/security/openssl/ + $ sudo make install clean + $ cd /usr/ports/devel/libevent2/ + $ sudo make install clean + $ cd /usr/ports/databases/postgresql84-client/ (or any other version) + $ sudo make install clean + $ cd /usr/ports/databases/mysql51-client/ (or any other version) + $ sudo make install clean + $ cd /usr/ports/databases/hiredis/ + $ sudo make install clean + + **) Linux Ubuntu 11.10+, Debian Wheezy, Mint 14+: + + $ sudo apt-get install libssl-dev + $ sudo apt-get install libevent-dev + $ sudo apt-get install libpq-dev + $ sudo apt-get install mysql-client + $ sudo apt-get install libmysqlclient-dev + $ sudo apt-get install libhiredis-dev + + or you can use Synaptic or other software center. + + ***) Fedora: + + $ sudo yum install openssl-devel + $ sudo yum install libevent + $ sudo yum install libevent-devel + $ sudo yum install postgresql-devel + $ sudo yum install postgresql-server + $ sudo yum install mysql-devel + $ sudo yum install mysql-server + $ sudo yum install hiredis + $ sudo yum install hiredis-devel + + ****) Amazon Linux is similar to Fedora, but: + + - you have to install gcc first: + $ sudo yum install gcc + + - hiredis packages are not available, so do not issue the + hiredis installation commands. Redis support will not be + compiled, unless you install it "manually" before the TURN + server compilation. For Amazon EC2 AMIs, we install the + redis manually in the system. But the TURN server can be + perfectly installed without redis support - if you do not + need it. + + *****) Some OSes in Debian family (Debian Squeeze and + pre-11.10 Ubuntus) setups are similar to Debian Wheezy, + although some packages have different names. + + ******) On some CentOS / RedHat 6.x systems you have to install + libevent2 "manually", and optionally you have to download and + install Hiredis, but everything else can be found in the software + repository. Also, if you would like to make an RPM for CentOS, + check the directory rpm/ with the instructions. + +NOTE: If your tools are installed in non-standard locations, you will +have to adjust CFLAGS and LDFLAGS environment variables for TURN +server ./configure script. For example, to configure the TURN server +with Solaris 11 PostgreSQL 32-bits setup, you may use a command +like this: + + $ CFLAGS="${CFLAGS} -I/usr/postgres/9.2-pgdg/include/" LDFLAGS="${LDFLAGS} -L/usr/postgres/9.2-pgdg/lib/" ./configure + +Dynamic library paths: + +You may also have to adjust the turn server start script, add PostgreSQL +and/or MySQL and/or Redis runtime library path to LD_LIBRARY_PATH. +Or you may find that it would be more convenient to adjust the +system-wide shared library search path by using commands: + +on Linux: + + $ ldconfig -n + +or on BSD: + + $ ldconfig -m + +or on Solaris: + + $ crle -u -l + +On Mac OS X, you have three different choices for dynamic libraries handling: + +1) Use DYLD_LIBRARY_PATH environment variable in runtime; OR + +2) Before the compilation, check the dynamic libraries and adjust their identification names, +if necessary, to the absolute library path or to @rpath/. +For exmple, the MySQL dynamic library may need that adjustment. You will have to use +"adjust_name_tool" with -id option for that; OR + +3) After the compilation, you can use the same tool, "adjust_name_tool", with option -change, +to adjust the library paths values in the binary, where necessary. All library paths must be +absolute paths or @rpath/... . + +See also the next section. + +NOTE: See "PostgreSQL setup" and "MySQL setup" and "Redis setup" sections +below for more database setup information. + +NOTE: If you do not install PostgreSQL or MySQL or Redis then you will +be limited to flat files for user database. It will work great for +smaller user databases (like 100 users) but for larger systems you +will need PostgreSQL or MySQL or Redis. + +NOTE: To run PostgreSQL or MySQL or Redis server on the same system, +you will also have to install a corresponding PostgreSQL or MySQL or +Redis server package. The DB C development packages only provide +development libraries, and client libraries only provide client +access utilities and runtime libraries. The server packages may +include everything - client, C development and server runtime. + +NOTE: OpenSSL to be installed before libevent2. When libevent2 is building, +it is checking whether OpenSSL has been already installed, and which version +of OpenSSL. If the OpenSSL is missed, or too old, then libevent_openssl +library is not being created during the build, and you will not be able to +compile the TURN Server with TLS support. + +NOTE: An older libevent version, version 1.x.x, is often included in some *NIX +distributions. That version has its deficiencies and is inferior to the newer +libevent2, especially in the performance department. This is why we are +not providing backward compatibility with the older libevent 1.x version. +If you have a system with older libevent, then you have to install the new +libevent2 from their web site. It was tested with older *NIXes +(like FreeBSD 6.x) and it works just fine. + +NOTE: For extra security features (DTLS and SHA256) support, OpenSSL version +1.0.0a or newer is recommended. Older versions do not support DTLS, reliably, +in some cases. For example, the Debian 'Squeeze' Linux supplies 0.9.8 version +of OpenSSL, that does not work correctly with DTLS over IPv6. If your system +already has an older version of OpenSSL installed (usually in directory /usr) +then you may want to install your newer OpenSSL "over" the old one (because it +will most probably will not allow removal of the old one). When installing +the newer OpenSSL, run the OpenSSL's configure command like this: + + $ ./config --prefix=/usr + +that will set the installation prefix to /usr (without "--prefix=/usr" +by default it would be installed to /usr/local). This is necessary if you +want to overwrite your existing older OpenSSL installation. + +VIII. BUILDING WITH NON-DEFAULT PREFIX DIRECTORY + +Say, you have an older system with old openssl and old libevent +library and you do not want to change that, but you still want +to build the turnserver. + +Do the following steps: + +1) Download new openssl from openssl.org. +2) Configure and build new openssl and install it into /opt: + + $ ./config --prefix=/opt + $ make + $ make install + +3) Download the latest libevent2 from libevent.org, configure and install +it into /opt: + + $ ./configure --prefix=/opt + $ make + $ make install + +4) Change directory to coturn and build it: + + $ ./configure --prefix=/opt + $ make + +After that, you can either use it locally, or install it into /opt. +But remember that to run it, you have to adjust your LD_LIBRARY_PATH, +like that: + + $ LD_LIBRARY_PATH=/opt/lib ./bin/turnserver + +An alternative would be adjusting the system-wide shared library search path +by using + $ ldconfig -n (Linux) + $ ldconfig -m (BSD) + $ crle -u -l (Solaris) + +IX. TEST SCRIPT SETS + +First of all, we can use test vectors from RFC 5769 to double-check that our +STUN/TURN message encoding algorithms work properly. Run the utility: + + $ cd examples + $ ./scripts/rfc5769.sh + +It will perform several protocol checks and print the results on the output. +If anything has compiled wrongly (TURN Server, or OpenSSL libraries) +then you will see some errors. + +Now, you can perform the TURN functionality test (bare minimum TURN example). + +If everything compiled properly, then the following programs must run +together successfully, simulating TURN network routing in local loopback +networking environment: + +Open two shell screens or consoles: + +In shell number 1, run TURN server application: + $ cd examples + $ ./scripts/basic/relay.sh + +In shell number 2, run test client application: + + $ cd examples + $ ./scripts/basic/udp_c2c_client.sh + +If the client application produces output and in approximately 22 seconds +prints the jitter, loss and round-trip-delay statistics, then everything is +fine. + +There is another more complex test: + +In shell number 1, run TURN server application: + $ cd examples + $ ./scripts/basic/relay.sh + +In shell number 2, run "peer" application: + $ cd examples + $ ./scripts/peer.sh + +In shell number 3, run test client application: + + $ cd examples + $ ./scripts/basic/udp_client.sh (or ./scripts/basic/tcp_client.sh) + +There is a similar set of examples/scripts/longtermsecure/* scripts for +TURN environment with long-term authentication mechanism. This set of +scripts is more complex, and checking the scripts options is useful for +understanding how the TURN Server works: + +In shell number 1, run secure TURN server application: + $ cd examples + $ ./scripts/longtermsecure/secure_relay.sh + +In shell number 2, run "peer" application: + $ cd examples + $ ./scripts/peer.sh + +In shell number 3, run secure test client application: + + $ cd examples + $ ./scripts/longtermsecure/secure_udp_client.sh + + (or ./scripts/longtermsecure/secure_tcp_client.sh) + (or ./scripts/longtermsecure/secure_tls_client.sh) + (or ./scripts/longtermsecure/secure_dtls_client.sh) + (or ./scripts/longtermsecure/secure_udp_c2c.sh for "peerless" +client-to-client communications) + +The provided scripts are set for the local loopback communications, +as an example and as a test environment. Real networking IPs must be +used in real work environments. + +Try wireshark to check the communications between client, turnserver +and the peer. + +Check the README.* files and the comments in the scripts relay.sh and +secure_relay.sh as a guidance how to run the TURN server. + +X. OS X compilation notes + +OS X usually has an older version of openssl supplied, with some Apple +additions. The best option is to install a good fresh openssl development +library, free of Apple tweaks, from http://www.openssl.org. But the "native" +openssl will work, too. + +XI. MS Windows and Cygwin support + +Currently, this project cannot be compiled under MS Windows. + +As the project is using fairly straightforward *NIX API, it is supported +under Cygwin environment in MS Windows. + +One note for Cygwin users: we recommended libevent2 installation from the cygwin +"ports" site: http://sourceware.org/cygwinports/ . You will have to install +libevent2 runtime and libevent-devel packages. "Manual" libevent2 compilation +and installation in Cygwin is not recommended and does not garantee a good +outcome. + +XII. CLIENT API LIBRARY. + +The compilation process will create lib/ sub-directory with libturnclient.a +library. The header files for this library are located in include/turn/client/ +sub-directory. The C++ wrapper for the messaging functionality is located in +TurnMsgLib.h header. An example of C++ code can be found in stunclient.c file. +This file is compiled as a C++ program if C++ compiler is used, and as a C +program if C compiler is used. + +XIII. DOCS + +After installation, the man page turnserver(1) must be available. The man page +is located in man/man1 subdirectory. If you want to see the man page without +installation, run the command: + + $ man -M man turnserver + +HTML-formatted client library functions reference is located in docs/html +subdirectory of the original archive tree. After the installation, it will +be placed in PREFIX/share/doc/turnserver/html. + +XIV. PostgreSQL setup + +The site http://www.postgresql.org site has excellent extensive documentation. +For a quick-start guide, you can take a look into this page: +http://www.freebsddiary.org/postgresql.php. That page is written for +FreeBSD users, but it has lots of generic information applicable to other +*NIXes, too. + +For the psql-userdb TURN server parameter, you can either set a PostgreSQL +connection string, or a PostgreSQL URI, see the link: + +For 8.4 PostgreSQL version: +http://www.postgresql.org/docs/8.4/static/libpq-connect.html + +For newer 9.x versions: +http://www.postgresql.org/docs/9.2/static/libpq-connect.html#LIBPQ-CONNSTRING. + +In the PostgreSQL connection string or URI, you can set the host, the +access port, the database name, the user, and the user password +(if the access is secured). Numerous other parameters can be set, +see the links above. The TURN server will blindly use that connection +string without any modifications. You are responsible for the right +connection string format. + +Below are the steps to setup the PostgreSQL database server from scratch: + +1) Install PostgreSQL server. + +2) Find and edit Postgres' pg_hba.conf file to set the access options +(see docs). On different systems, it may be located in different places. +Set the lines for local access as "trust" for now (you can change it later), +for TCP/IP access set the value as "md5". +To set TCP/IP access from any host, use "0.0.0.0/0" for IPv4, and "::/0" +for IPv6. + +3) Edit postgresql.conf file to allow TCP/IP access - uncomment and edit +the "listen_addresses" option (see docs). On different systems, this file +may be located in different places. + +4) Restart your system or restart the postgresql server, for example: + + $ sudo /etc/init.d/postgresql stop + $ sudo /etc/init.d/postgresql start + +5) Check /etc/passwd file to find out which user account is used for the +PostgreSQL admin access on your system (it may be "pgsql", or "postgres", +or "postgresql"). Let's assume that this is "postgres" account. + +6) Create a database for the TURN purposes, with name, say, "turn": + + $ createdb -U postgres turn + +7) Create a user for the TURN with name, say, "turn": + $ psql -U postgres turn + turn=# create user turn with password 'turn'; + turn=# + Ctrl-D + +8) Create the TURN users database schema. + +The database schema for the TURN server is very minimalistic and is located +in project's turndb/schema.sql file, or in the system's +PREFIX/share/turnserver/schema.sql file after the turnserver installation: + +$ cat turndb/schema.sql | psql -U turn turn + NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "turnusers_lt_pkey" for table "turnusers_lt" + CREATE TABLE + NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "turnusers_st_pkey" for table "turnusers_st" + CREATE TABLE + CREATE TABLE +$ + +The schema description: + +# Table for long-term credentials mechanism authorization: +# +CREATE TABLE turnusers_lt ( + realm varchar(512), + name varchar(512), + hmackey char(128), + PRIMARY KEY (realm,name) +); + +# Table for short-term credentials mechanism authorisation: +# +CREATE TABLE turnusers_st ( + name varchar(512) PRIMARY KEY, + password varchar(512) +); + +# Table holding shared secrets for secret-based authorization +# (REST API). It can only be used together with the long-term +# mechanism: +# +CREATE TABLE turn_secret ( + realm varchar(512), + value varchar(512), + primary key (realm,value) +); + +# Table holding "white" allowed peer IP ranges. +# +CREATE TABLE allowed_peer_ip ( + ip_range varchar(256), + primary key (ip_range) +); + +# Table holding "black" denied peer IP ranges. +# +CREATE TABLE denied_peer_ip ( + ip_range varchar(256), + primary key (ip_range) +); + +# Table to match origin to realm. +# Multiple origins may have the same realm. +# If no realm is found or the origin is absent +# then the default realm is used. +# +CREATE TABLE turn_origin_to_realm ( + origin varchar(512), + realm varchar(512), + primary key (origin,realm) +); + +# Realm options. +# Valid options are 'max-bps', +# 'total-quota' and 'user-quota'. +# Values for them are integers (in text form). +# +CREATE TABLE turn_realm_option ( + realm varchar(512), + opt varchar(32), + value varchar(128), + primary key (realm,opt) +); + +The field hmackey contains HEX string representation of the key. +We do not store the user open passwords for long-term credentials, for security reasons. +Storing only the HMAC key has its own implications - if you change the realm, +you will have to update the HMAC keys of all users, because the realm is +used for the HMAC key generation. + +The key must be 32 characters (HEX representation of 16 bytes) for SHA1, +or 64 characters (HEX representation of 32 bytes) for SHA256. + +You can use turnadmin program to manage the database - you can either use +turnadmin to add/modify/delete users, or you can use turnadmin to produce +the hmac keys and modify the database with your favorite tools. + +More examples of database schema creation: + +psql -h -U -d < turndb/schema.sql +(old style for 8.4) + +psql postgresql://username:password@/databasename < turndb/schema.sql +(newer style for 9.x, UNIX domain local sockets) + +Or: + +psql postgresql://username:password@hostname:port/databasename < turndb/schema.sql +(newer style for 9.x, TCP/IP access) + +Below, the string "postgresql://turn:turn@/turn" is the connection URI. +Of course, the administrators can play with the connection string as they want. + +When starting the turnserver, the psql-userdb parameter will be, for example: + +turnserver ... --psql-userdb="host=localhost dbname=turn user=turn password=turn connect_timeout=30" + +Or, for 9.x PostgreSQL versions: +turnserver ... --psql-userdb=postgresql://username:password@/databasename ... + +9) You are ready to use the TURN database. The database name is "turn", +the user name is "turn", the user password is "turn". Of course, you can +choose your own names. Now, you will have to use the program turnadmin to fill the +database, or you can do that manually with psql. + +Fill in users, for example: + + Shared secret for the TURN REST API: + + $ bin/turnadmin -s logen -e "host=localhost dbname=turn user=turn password=turn" + + Long-term credentials mechanism: + + $ bin/turnadmin -a -e "host=localhost dbname=turn user=turn password=turn" -u gorst -r north.gov -p hero + $ bin/turnadmin -a -e "host=localhost dbname=turn user=turn password=turn" -u ninefingers -r north.gov -p youhavetoberealistic + + Long-term credentials mechanism with SHA256 extention: + $ bin/turnadmin -a -e "host=localhost dbname=turn user=turn password=turn" -u bethod -r north.gov -p king-of-north --sha256 + + Short-term credentials mechanism: + + $ bin/turnadmin -A -e "host=localhost dbname=turn user=turn password=turn" -u gorst -r north.gov -p hero + $ bin/turnadmin -A -e "host=localhost dbname=turn user=turn password=turn" -u ninefingers -r north.gov -p youhavetoberealistic + +XV. MySQL (MariaDB) setup + +The MySQL setup is similar to PostgreSQL (same idea), and is well documented +on their site http://www.mysql.org. The TURN Server database schema is the +same as for PostgreSQL and you can find it in turndb/schema.sql file, or +in the system's PREFIX/share/turnserver/schema.sql file after the turnserver +installation. + +The general setup idea is the same as for PostgreSQL case: + +1) Check that the mysql server access is OK. Immediately after the MySQL server +installation, it must be accessible, at the very minimum, at the localhost with +the root account. + +2) Login into mysql console from root account: + + $ sudo bash + # mysql -p mysql + +3) Add 'turn' user with 'turn' password (for example): + + > create user 'turn'@'localhost' identified by 'turn'; + +4) Create database 'turn' (for example) and grant privileges to user 'turn': + + > create database turn; + > grant all on turn.* to 'turn'@'localhost'; + > flush privileges; + Ctrl-D + +5) Create database schema: + + $ mysql -p -u turn turn < turndb/schema.sql + Enter password: turn + $ + +6) Fill in users, for example: + + Shared secret for the TURN REST API: + + $ bin/turnadmin -s logen -M "host=localhost dbname=turn user=turn password=turn" + + Long-term credentials mechanism: + + $ bin/turnadmin -a -M "host=localhost dbname=turn user=turn password=turn" -u gorst -r north.gov -p hero + $ bin/turnadmin -a -M "host=localhost dbname=turn user=turn password=turn" -u ninefingers -r north.gov -p youhavetoberealistic + + Long-term credentials mechanism with SHA256 extention: + $ bin/turnadmin -a -M "host=localhost dbname=turn user=turn password=turn" -u bethod -r north.gov -p king-of-north --sha256 + + Short-term credentials mechanism: + + $ bin/turnadmin -A -M "host=localhost dbname=turn user=turn password=turn" -u gorst -r north.gov -p hero + $ bin/turnadmin -A -M "host=localhost dbname=turn user=turn password=turn" -u ninefingers -r north.gov -p youhavetoberealistic + +7) Now we can use mysql in the turnserver. + +If the TURN server was compiled with MySQL support, then we can use the +TURN server database parameter --mysql-userdb. The value of this parameter +is a connection string for the MySQL database. As "native" MySQL does not +have such a feature as "connection string", the TURN server parses the +connection string and converts it into MySQL database connection parameter. +The format of the MySQL connection string is: + +"host= dbname= user= password= port= connect_timeout=" + +(all parameters are optional) + +So, an example of the MySQL database parameter in the TURN server command +line would be: + +--mysql-userdb="host=localhost dbname=turn user=turn password=turn connect_timeout=30" + +Or in the turnserver.conf file: + +mysql-userdb="host=localhost dbname=turn user=turn password=turn connect_timeout=30" + +XVI. Redis setup + +The Redis setup is well documented on their site http://redis.io. +The TURN Server Redis database schema description can be found +in schema.userdb.redis and schema.stats.redis files. Those files are located +either in the turndb subdirectory of the main source code directory, +or in /usr/local/share/turnserver/ after the installation, or somewhere in /usr/share/ +directory, depending on the OS and on the instalation package. + +If the TURN server was compiled with Hiredis support (Hiredis is the C client +library for Redis), then we can use the TURN server database parameter +--redis-userdb. The value of this parameter is a connection string +for the Redis database. As "native" Redis does not have such a feature as +"connection string", the TURN server parses the connection string and +converts it into Redis database access parameter. The format of the Redis +connection string is: + +"ip= dbname= password= port= connect_timeout=" + +(all parameters are optional) + +So, an example of the Redis database parameter in the TURN server command +line would be: + +--redis-userdb="ip=127.0.0.1 dbname=0 password=turn connect_timeout=30" + +Or in the turnserver.conf file: + +redis-userdb="ip=127.0.0.1 dbname=0 password=turn connect_timeout=30" + +Redis can be also used for the TURN allocation status check and for status and +traffic notifications. + +See the explanation in the turndb/schema.stats.redis file, and an example in +turndb/testredisdbsetup.sh file. One special thing about TURN Redis security setup +is that you can store open passwords for long-term credentials in Redis. +You cannot set open passwords for long-term credentials in MySQL and PostgreSQL - +with those DBs, you have to use the keys only. With Redis, you have a choice - +keys or open passwords. + +You also have to take care about Redis connection parameters, the timeout and the +keepalive. The following settings must be in your Redis config file +(/etc/redis.conf or /usr/local/etc/redis.conf): + +.......... +timeout 0 +.......... +tcp-keepalive 60 +.......... + +Redis TURN admin commands: + + Shared secret for the TURN REST API: + + $ bin/turnadmin -s logen -N "host=localhost dbname=0 user=turn password=turn" + + Long-term credentials mechanism: + + $ bin/turnadmin -a -N "host=localhost dbname=0 user=turn password=turn" -u gorst -r north.gov -p hero + $ bin/turnadmin -a -N "host=localhost dbname=0 user=turn password=turn" -u ninefingers -r north.gov -p youhavetoberealistic + + Long-term credentials mechanism with SHA256 extention: + $ bin/turnadmin -a -N "host=localhost dbname=0 user=turn password=turn" -u bethod -r north.gov -p king-of-north --sha256 + + Short-term credentials mechanism: + + $ bin/turnadmin -A -N "host=localhost dbname=0 user=turn password=turn" -u gorst -r north.gov -p hero + $ bin/turnadmin -A -N "host=localhost dbname=0 user=turn password=turn" -u ninefingers -r north.gov -p youhavetoberealistic + +XV. Performance tuning + +This topic is covered in the wiki page: + +http://code.google.com/p/coturn/wiki/turn_performance_and_load_balance + +XVI. TURN Server setup + +Read the project wiki pages: http://code.google.com/p/coturn/w/list + +Also, check the project from page links to the TURN/WebRTC configuration examples. +It may give you an idea how it can be done. + +XVI. Management interface + +You have a telnet interface (enabled by default) to access the turnserver process, +to view its state, to gather some statistical information, and to make some changes +on-the-fly. + +You can access that CLI interface with telnet or putty program (in telnet mode). +The process by default listens to port 5766 on IP address 127.0.0.1 for the telnet +connections. + +WARNING: all telnet communications are going unencrypted over the network. For +security reasons, we advise using the loopback IP addresses for CLI (127.0.0.1 +or ::1). The CLI may have a password configured, but that password is +transferred over the network unencrypted, too. So sticking to the local system +CLI access, and accessing the turnserver system terminal with ssh only, would +be a wise decision. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f352985 --- /dev/null +++ b/LICENSE @@ -0,0 +1,31 @@ +/* + * TURN Server - RFC5766 TURN Server implementation + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + diff --git a/LICENSE.OpenSSL b/LICENSE.OpenSSL new file mode 100644 index 0000000..a2c4adc --- /dev/null +++ b/LICENSE.OpenSSL @@ -0,0 +1,127 @@ + + LICENSE ISSUES + ============== + + The OpenSSL toolkit stays under a dual license, i.e. both the conditions of + the OpenSSL License and the original SSLeay license apply to the toolkit. + See below for the actual license texts. Actually both licenses are BSD-style + Open Source licenses. In case of any license issues related to OpenSSL + please contact openssl-core@openssl.org. + + OpenSSL License + --------------- + +/* ==================================================================== + * Copyright (c) 1998-2008 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + + Original SSLeay License + ----------------------- + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + diff --git a/Makefile.in b/Makefile.in new file mode 100755 index 0000000..785d738 --- /dev/null +++ b/Makefile.in @@ -0,0 +1,173 @@ + +LIBEVENT_INCLUDE = -I${PREFIX}/include/ -I/usr/local/include/ + +INCFLAGS = -Isrc -Isrc/apps/common -Isrc/server -Isrc/client -Isrc/client++ ${LIBEVENT_INCLUDE} + +CFLAGS += ${INCFLAGS} + +MAKE_DEPS = Makefile + +LIBCLIENTTURN_HEADERS = src/ns_turn_defs.h src/client++/TurnMsgLib.h src/client/ns_turn_ioaddr.h src/client/ns_turn_msg.h src/client/ns_turn_msg_defs.h src/client/ns_turn_msg_addr.h +LIBCLIENTTURN_MODS = src/client/ns_turn_ioaddr.c src/client/ns_turn_msg_addr.c src/client/ns_turn_msg.c +LIBCLIENTTURN_DEPS = ${LIBCLIENTTURN_HEADERS} ${MAKE_DEPS} +LIBCLIENTTURN_OBJS = build/obj/ns_turn_ioaddr.o build/obj/ns_turn_msg_addr.o build/obj/ns_turn_msg.o + +SERVERTURN_HEADERS = src/server/ns_turn_allocation.h src/server/ns_turn_ioalib.h src/server/ns_turn_khash.h src/server/ns_turn_maps_rtcp.h src/server/ns_turn_maps.h src/server/ns_turn_server.h src/server/ns_turn_session.h +SERVERTURN_DEPS = ${LIBCLIENTTURN_HEADERS} ${SERVERTURN_HEADERS} ${MAKE_DEPS} +SERVERTURN_MODS = ${LIBCLIENTTURN_MODS} src/server/ns_turn_allocation.c src/server/ns_turn_maps_rtcp.c src/server/ns_turn_maps.c src/server/ns_turn_server.c + +COMMON_HEADERS = src/apps/common/apputils.h src/apps/common/ns_turn_utils.h src/apps/common/stun_buffer.h +COMMON_MODS = src/apps/common/apputils.c src/apps/common/ns_turn_utils.c src/apps/common/stun_buffer.c +COMMON_DEPS = ${LIBCLIENTTURN_DEPS} ${COMMON_MODS} ${COMMON_HEADERS} + +IMPL_HEADERS = src/apps/relay/ns_ioalib_impl.h src/apps/relay/ns_sm.h src/apps/relay/turn_ports.h +IMPL_MODS = src/apps/relay/ns_ioalib_engine_impl.c src/apps/relay/turn_ports.c +IMPL_DEPS = ${COMMON_DEPS} ${IMPL_HEADERS} ${IMPL_MODS} + +HIREDIS_HEADERS = src/apps/common/hiredis_libevent2.h +HIREDIS_MODS = src/apps/common/hiredis_libevent2.c + +SERVERAPP_HEADERS = src/apps/relay/userdb.h src/apps/relay/tls_listener.h src/apps/relay/mainrelay.h src/apps/relay/turncli.h src/apps/relay/dtls_listener.h src/apps/relay/libtelnet.h ${HIREDIS_HEADERS} +SERVERAPP_MODS = src/apps/relay/mainrelay.c src/apps/relay/netengine.c src/apps/relay/libtelnet.c src/apps/relay/turncli.c src/apps/relay/userdb.c src/apps/relay/tls_listener.c src/apps/relay/dtls_listener.c ${HIREDIS_MODS} +SERVERAPP_DEPS = ${SERVERTURN_MODS} ${SERVERTURN_DEPS} ${SERVERAPP_MODS} ${SERVERAPP_HEADERS} ${COMMON_DEPS} ${IMPL_DEPS} lib/libturnclient.a + +TURN_BUILD_RESULTS = bin/turnutils_stunclient bin/turnutils_rfc5769check bin/turnutils_uclient bin/turnserver bin/turnutils_peer lib/libturnclient.a include/turn/ns_turn_defs.h + +all: ${TURN_BUILD_RESULTS} + +test: check + +check: bin/turnutils_rfc5769check + bin/turnutils_rfc5769check + +include/turn/ns_turn_defs.h: src/ns_turn_defs.h + ${RMCMD} include + ${MKBUILDDIR} include/turn/client + cp -pf src/client/*.h include/turn/client/ + cp -pf src/client++/*.h include/turn/client/ + cp -pf src/ns_turn_defs.h include/turn/ + +bin/turnutils_uclient: ${COMMON_DEPS} src/apps/uclient/session.h lib/libturnclient.a src/apps/uclient/mainuclient.c src/apps/uclient/uclient.c src/apps/uclient/uclient.h src/apps/uclient/startuclient.c src/apps/uclient/startuclient.h + ${MKBUILDDIR} bin + ${CC} ${CPPFLAGS} ${CFLAGS} src/apps/uclient/uclient.c src/apps/uclient/startuclient.c src/apps/uclient/mainuclient.c ${COMMON_MODS} -o $@ -Llib -lturnclient -Llib ${LDFLAGS} + +bin/turnutils_stunclient: ${COMMON_DEPS} lib/libturnclient.a src/apps/stunclient/stunclient.c + pwd + ${MKBUILDDIR} bin + ${CC} ${CPPFLAGS} ${CFLAGS} src/apps/stunclient/stunclient.c ${COMMON_MODS} -o $@ -Llib -lturnclient -Llib ${LDFLAGS} + +bin/turnutils_rfc5769check: ${COMMON_DEPS} lib/libturnclient.a src/apps/rfc5769/rfc5769check.c + pwd + ${MKBUILDDIR} bin + ${CC} ${CPPFLAGS} ${CFLAGS} src/apps/rfc5769/rfc5769check.c ${COMMON_MODS} -o $@ -Llib -lturnclient -Llib ${LDFLAGS} + +bin/turnserver: ${SERVERAPP_DEPS} + ${MKBUILDDIR} bin + ${RMCMD} bin/turnadmin + ${CC} ${CPPFLAGS} ${CFLAGS} ${DBCFLAGS} ${IMPL_MODS} -Ilib ${SERVERAPP_MODS} ${COMMON_MODS} ${SERVERTURN_MODS} -o $@ ${DBLIBS} ${LDFLAGS} + cd bin; ln -s turnserver turnadmin + +bin/turnutils_peer: ${COMMON_DEPS} ${LIBCLIENTTURN_MODS} ${LIBCLIENTTURN_DEPS} lib/libturnclient.a src/apps/peer/mainudpserver.c src/apps/peer/udpserver.h src/apps/peer/udpserver.c + ${MKBUILDDIR} bin + ${CC} ${CPPFLAGS} ${CFLAGS} src/apps/peer/mainudpserver.c src/apps/peer/udpserver.c ${COMMON_MODS} -o $@ -Llib -lturnclient -Llib ${LDFLAGS} + +### Client Library: + +lib/libturnclient.a: ${LIBCLIENTTURN_OBJS} ${LIBCLIENTTURN_DEPS} + ${MKBUILDDIR} lib + ${ARCHIVERCMD} $@ ${LIBCLIENTTURN_OBJS} + +build/obj/ns_turn_ioaddr.o: src/client/ns_turn_ioaddr.c ${LUBCLIENTTURN_DEPS} + ${MKBUILDDIR} build/obj + ${CC} ${CPPFLAGS} ${CFLAGS} -c src/client/ns_turn_ioaddr.c -o $@ + +build/obj/ns_turn_msg_addr.o: src/client/ns_turn_msg_addr.c ${LUBCLIENTTURN_DEPS} + ${MKBUILDDIR} build/obj + ${CC} ${CPPFLAGS} ${CFLAGS} -c src/client/ns_turn_msg_addr.c -o $@ + +build/obj/ns_turn_msg.o: src/client/ns_turn_msg.c ${LUBCLIENTTURN_DEPS} + ${MKBUILDDIR} build/obj + ${CC} ${CPPFLAGS} ${CFLAGS} -c src/client/ns_turn_msg.c -o $@ + +### Clean all: + +clean: + ${RMCMD} bin build lib obj *bak *~ */*~ */*/*~ */*/*/*~ *core */*core */*/*core include Makefile tmp + +distclean: clean + +### Install all: + +install: all ${MAKE_DEPS} + ${MKDIR} ${DESTDIR}${PREFIX} + ${MKDIR} ${DESTDIR}${BINDIR} + ${MKDIR} ${DESTDIR}${MANPREFIX}/man/man1 + ${MKDIR} ${DESTDIR}${CONFDIR} + ${MKDIR} ${DESTDIR}${LIBDIR} + ${MKDIR} ${DESTDIR}${EXAMPLESDIR} + ${MKDIR} ${DESTDIR}${DOCSDIR} + ${MKDIR} ${DESTDIR}${SCHEMADIR} + ${MKDIR} ${DESTDIR}${TURNINCLUDEDIR} + ${INSTALL_PROGRAM} bin/turnserver ${DESTDIR}${BINDIR} + ${INSTALL_PROGRAM} bin/turnadmin ${DESTDIR}${BINDIR} + ${INSTALL_PROGRAM} bin/turnutils_uclient ${DESTDIR}${BINDIR} + ${INSTALL_PROGRAM} bin/turnutils_peer ${DESTDIR}${BINDIR} + ${INSTALL_PROGRAM} bin/turnutils_stunclient ${DESTDIR}${BINDIR} + ${INSTALL_MAN} man/man1/turnserver.1 ${DESTDIR}${MANPREFIX}/man/man1/ + ${INSTALL_MAN} man/man1/turnadmin.1 ${DESTDIR}${MANPREFIX}/man/man1/ + ${INSTALL_MAN} man/man1/turnutils.1 ${DESTDIR}${MANPREFIX}/man/man1/ + ${INSTALL_MAN} man/man1/turnutils_uclient.1 ${DESTDIR}${MANPREFIX}/man/man1/ + ${INSTALL_MAN} man/man1/turnutils_stunclient.1 ${DESTDIR}${MANPREFIX}/man/man1/ + ${INSTALL_MAN} man/man1/turnutils_peer.1 ${DESTDIR}${MANPREFIX}/man/man1/ + ${INSTALL_MAN} man/man1/coturn.1 ${DESTDIR}${MANPREFIX}/man/man1/ + ${INSTALL_STATIC_LIB} lib/libturnclient.a ${DESTDIR}${LIBDIR} + ${INSTALL_DATA} LICENSE ${DESTDIR}${DOCSDIR} + ${INSTALL_DATA} README.turnserver ${DESTDIR}${DOCSDIR} + ${INSTALL_DATA} README.turnadmin ${DESTDIR}${DOCSDIR} + ${INSTALL_DATA} README.turnutils ${DESTDIR}${DOCSDIR} + ${INSTALL_DATA} INSTALL ${DESTDIR}${DOCSDIR} + ${INSTALL_DATA} postinstall.txt ${DESTDIR}${DOCSDIR} + ${INSTALL_DATA} turndb/schema.sql ${DESTDIR}${DOCSDIR} + ${INSTALL_DATA} turndb/schema.sql ${DESTDIR}${SCHEMADIR} + ${INSTALL_DATA} turndb/testredisdbsetup.sh ${DESTDIR}${SCHEMADIR} + ${INSTALL_DATA} turndb/testsqldbsetup.sql ${DESTDIR}${SCHEMADIR} + ${INSTALL_DATA} turndb/schema.userdb.redis ${DESTDIR}${DOCSDIR} + ${INSTALL_DATA} turndb/schema.userdb.redis ${DESTDIR}${SCHEMADIR} + ${INSTALL_DATA} turndb/schema.stats.redis ${DESTDIR}${DOCSDIR} + ${INSTALL_DATA} turndb/schema.stats.redis ${DESTDIR}${SCHEMADIR} + ${INSTALL_DATA} examples/etc/turnserver.conf ${DESTDIR}${CONFDIR}/turnserver.conf.default + ${INSTALL_DATA} examples/etc/turnuserdb.conf ${DESTDIR}${CONFDIR}/turnuserdb.conf.default + ${INSTALL_DIR} examples/etc ${DESTDIR}${EXAMPLESDIR} + ${INSTALL_DIR} examples/scripts ${DESTDIR}${EXAMPLESDIR} + ${RMCMD} ${DESTDIR}${EXAMPLESDIR}/scripts/rfc5769.sh + ${INSTALL_DIR} include/turn/client ${DESTDIR}${TURNINCLUDEDIR} + ${INSTALL_DATA} include/turn/ns_turn_defs.h ${DESTDIR}${TURNINCLUDEDIR} + ${MORECMD} ${DESTDIR}${DOCSDIR}/postinstall.txt + +deinstall: ${MAKE_DEPS} + ${PKILL_PROGRAM} turnserver || ${ECHO_CMD} OK + ${RMCMD} ${DESTDIR}${DOCSDIR} + ${RMCMD} ${DESTDIR}${SCHEMADIR} + ${RMCMD} ${DESTDIR}${BINDIR}/turnserver + ${RMCMD} ${DESTDIR}${BINDIR}/turnadmin + ${RMCMD} ${DESTDIR}${BINDIR}/turnutils_peer + ${RMCMD} ${DESTDIR}${BINDIR}/turnutils_uclient + ${RMCMD} ${DESTDIR}${BINDIR}/turnutils_stunclient + ${RMCMD} ${DESTDIR}${MANPREFIX}/man/man1/turnserver.1 + ${RMCMD} ${DESTDIR}${MANPREFIX}/man/man1/turnadmin.1 + ${RMCMD} ${DESTDIR}${MANPREFIX}/man/man1/turnutils.1 + ${RMCMD} ${DESTDIR}${MANPREFIX}/man/man1/turnutils_uclient.1 + ${RMCMD} ${DESTDIR}${MANPREFIX}/man/man1/turnutils_stunclient.1 + ${RMCMD} ${DESTDIR}${MANPREFIX}/man/man1/turnutils_peer.1 + ${RMCMD} ${DESTDIR}${MANPREFIX}/man/man1/coturn.1 + ${RMCMD} ${DESTDIR}${LIBDIR}/libturnclient.a + ${RMCMD} ${DESTDIR}${EXAMPLESDIR}/ + ${RMCMD} ${DESTDIR}${CONFDIR}/turnserver.conf.default + ${RMCMD} ${DESTDIR}${CONFDIR}/turnuserdb.conf.default + ${RMCMD} ${DESTDIR}${TURNINCLUDEDIR} + +uninstall: deinstall + +reinstall: deinstall install + + diff --git a/NOTE b/NOTE new file mode 100644 index 0000000..3cb9955 --- /dev/null +++ b/NOTE @@ -0,0 +1,2 @@ +This project is active in Google code: http://code.google.com/p/coturn/ + diff --git a/README.turnadmin b/README.turnadmin new file mode 100644 index 0000000..fb5f176 --- /dev/null +++ b/README.turnadmin @@ -0,0 +1,239 @@ +GENERAL INFORMATION + +turnadmin is a TURN administration tool. This tool can be used to manage +the user accounts (add/remove users, generate +TURN keys for the users). For security reasons, we do not recommend +storing passwords openly. The better option is to use pre-processed "keys" +which are then used for authentication. These keys are generated by turnadmin. +Turnadmin is a link to turnserver binary, but turnadmin performs different +functions. + +Options note: turnadmin has long and short option names, for most options. +Some options have only long form, some options have only short form. Their syntax +somewhat different, if an argument is required: + +The short form must be used as this (for example): + + $ turnadmin -u ... + +The long form equivalent must use the "=" character: + + $ turnadmin --user= ... + +If this is a flag option (no argument required) then their usage are the same, for example: + + $ turnadmin -k ... + +is equivalent to: + + $ turnadmin --key ... + +You have always the use the -r option with commands for long term credentials - +because data for multiple realms can be stored in the same database. + +===================================== + + NAME + +turnadmin - a TURN relay administration tool. + + SYNOPSIS + +$ turnadmin [command] [options] + +$ turnadmin [ -h | --help] + + DESCRIPTION + +Commands: + +-k, --key Generate key for a long-term credentials mechanism user. + +-a, --add Add or update a long-term user. + +-A, --add-st Add or update a short-term credentials mechanism user. + +-d, --delete Delete a long-term user. + +-D, --delete-st Delete a short-term user. + +-l, --list List long-term users in the database. + +-L, --list-st List short-term users in the database. + +-s, --set-secret= Add shared secret for TURN RESP API + +-S, --show-secret Show stored shared secrets for TURN REST API + +-X, --delete-secret= Delete a shared secret. + --delete-all_secrets Delete all shared secrets for REST API. + +-O, --add-origin Add origin-to-realm relation. + +-R, --del-origin Delete origin-to-realm relation. + +-I, --list-origins List origin-to-realm relations. + +-g, --set-realm-option Set realm params: max-bps, total-quota, user-quota. + +-G, --list-realm-options List realm params. + +NOTE: if you are using the flat file for the user database, then you will have +to use a text editor to set or show the shared secrets. + +NOTE: the origin functionality is not supported with flat user db file, +a "real" database must be used. + +Options with required values: + +-b, --userdb File-based user database file name (default - turnuserdb.conf). + See the --userdb option in the turnserver section. +-e, --psql-userdb PostgreSQL user database connection string. + See the --psql-userdb option in the turnserver section. +-M, --mysql-userdb MySQL user database connection string. + See the --mysql-userdb option in the turnserver section. +-N, --redis-userdb Redis user database connection string. + See the --redis-userdb option in the turnserver section. +-u, --user User name. +-r, --realm Realm, for long-term credentials mechanism only. +-p, --password Password. +-o, --origin Origin +-H, --sha256 Use SHA256 as the keys hash function (a non-standard feature). + By default, MD5 is used for the key storage encryption + (as required by the current STUN/TURNstandards). +--max-bps Set value of realm's max-bps parameter. +--total-quota Set value of realm's total-quota parameter. +--user-quota Set value of realm's user-quota parameter. +-h, --help Help. + +Generate a key: + +$ turnadmin -k -u -r -p + +Add/update a user in the userdb file or in the database: + +$ turnadmin -a [-b | -e | -M | -N ] -u -r -p + +Delete a user from the userdb file or from the database: + +$ turnadmin -d [-b | -e | -M | -N ] -u -r + +List all long-term users in MySQL database: + +$ turnadmin -l --mysql-userdb="" -r + +List all short-term users in Redis database: + +$ turnadmin -L --redis-userdb="" + +Set secret in MySQL database: + +$ turnadmin -s --mysql-userdb="" -r + +Show secret stored in PostgreSQL database: + +$ turnadmin -S --psql-userdb="" -r + +Set origin-to-realm relation in MySQL database: + +$ turnadmin --mysql-userdb="" -r -o + +Delete origin-to-realm relation from Redis DB: + +$ turnadmin --redis-userdb="" -o + +List all origin-to-realm relations in Redis DB: + +$ turnadmin --redis-userdb="" -I + +List the origin-to-realm relations in PostgreSQL DB for a single realm: + +$ turnadmin --psql-userdb="" -I -r + +Help: + +$ turnadmin -h + +======================================= + + DOCS + +After installation, run the command: + +$ man turnadmin + +or in the project root directory: + +$ man -M man turnadmin + +to see the man page. + +===================================== + + FILES + +/etc/turnserver.conf + +/etc/turnuserdb.conf + +/usr/local/etc/turnserver.conf + +/usr/local/etc/turnuserdb.conf + +===================================== + + DIRECTORIES + +/usr/local/share/turnserver + +/usr/local/share/doc/turnserver + +/usr/local/share/examples/turnserver + +====================================== + + SEE ALSO + + turnserver, turnutils + +====================================== + + WEB RESOURCES + + project page: + + http://code.google.com/p/coturn/ + + Wiki page: + + http://code.google.com/p/coturn/wiki/Readme + + forum: + + https://groups.google.com/forum/?fromgroups=#!forum/turn-server-project-rfc5766-turn-server/ + +====================================== + + AUTHORS + + Oleg Moskalenko + + Gabor Kovesdan http://kovesdan.org/ + + Daniel Pocock http://danielpocock.com/ + + John Selbie (jselbie@gmail.com) + + Lee Sylvester + + Erik Johnston + + Roman Lisagor + + Vladimir Tsanev + + Po-sheng Lin + + Peter Dunkley + + Mutsutoshi Yoshimoto diff --git a/README.turnserver b/README.turnserver new file mode 100644 index 0000000..d0ccff7 --- /dev/null +++ b/README.turnserver @@ -0,0 +1,880 @@ +GENERAL INFORMATION + +The TURN Server project contains the source code of a TURN server and TURN client +messaging library. Also, some extra programs provided, for testing-only +purposes. + +See the INSTALL file for the building instructions. + +After the build, you will have the following binary images: + +1. turnserver: TURN Server relay. +The compiled binary image of the TURN Server program is located in bin/ sub-directory. + +2. turnadmin: TURN administration tool. See README.turnadmin and turnadmin man page. + +3. turnutils_uclient. See README.turnutils and turnutils man page. + +4. turnutils_peer. See README.turnutils and turnutils man page. + +5. turnutils_stunclient. See README.turnutils and turnutils man page. + +6. turnutils_rfc5769check. See README.turnutils and turnutils man page. + +In the "examples/scripts" sub-directory, you will find the examples of command lines to be used +to run the programs. The scripts are meant to be run from examples/ sub-directory, for example: + +$ cd examples +$ ./scripts/secure_relay.sh + +RUNNING THE TURN SERVER + +Options note: turnserver has long and short option names, for most options. +Some options have only long form, some options have only short form. Their syntax +somewhat different, if an argument is required: + +The short form must be used as this (for example): + + $ turnserver -L 12.34.56.78 + +The long form equivalent must use the "=" character: + + $ turnserver --listening-ip=12.34.56.78 + +If this is a flag option (no argument required) then their usage are the same, for example: + + $ turnserver -a + +is equivalent to: + + $ turnserver --lt-cred-mech + +===================================== + + NAME + +turnserver - a TURN relay server implementation. + + SYNOPSIS + +$ turnserver [-n | -c ] [flags] [ --userdb= | --psql-userdb= | --mysql-userdb= | --redis-userdb= ] [-z | --no-auth | -a | --lt-cred-mech ] [options] +$ turnserver -h + + DESCRIPTION + +Config file settings: + +-n Do not use configuration file, use only command line parameters. + +-c Configuration file name (default - turnserver.conf). + The format of config file can be seen in + the supplied examples/etc/turnserver.conf example file. Long + names of the options are used as the configuration + items names in the file. If not an absolute path is supplied, + then the file is searched in the following directories: + * current directory + * current directory etc/ sub-directory + * upper directory level etc/ + * /etc/ + * /usr/local/etc/ + * installation directory /etc + +User database settings: + +-b, --userdb User database file name (default - turnuserdb.conf), + for long-term credentials mechanism only. + This user file database is being dynamically checked while the turnserver + is working, and the user accounts can be changed dynamically by + editing the database. + +-e, --psql-userdb User database connection string for PostgreSQL. + This database can be used for long-term and short-term credentials mechanisms, + and it can store the secret value for secret-based timed authentication in TURN RESP API. + The connection string format is like that: + + "host= dbname= user= password= connect_timeout=" + (for 8.x or newer Postgres). + + Or: + + "postgresql://username:password@hostname:port/databasename" (for 9.x or newer Postgres). + See the INSTALL file for more explanations and examples. + + Also, see http://www.PostgreSQL.org for full PostgreSQL documentation. + +-M, --mysql-userdb User database connection string for MySQL or MariaDB. + This database can be used for long-term and short-term credentials mechanisms, + and it can store the secret value for secret-based timed authentication in TURN RESP API. + The connection string format is like that: + + "host= dbname= user= password= connect_timeout=" + See the INSTALL file for more explanations and examples. + + Also, see http://www.mysql.org or http://mariadb.org + for full MySQL documentation. + +-N, --redis-userdb User database connection string for Redis. + This database can be used for long-term and short-term credentials mechanisms, + and it can store the secret value for secret-based timed authentication in TURN RESP API. + The connection string format is like that: + + "ip= dbname= password= connect_timeout=" + See the INSTALL file for more explanations and examples. + + Also, see http://redis.io for full Redis documentation. + +Flags: + +-v, --verbose Moderate verbose mode. + +-V, --Verbose Extra verbose mode, very annoying and not recommended. + +-o, --daemon Run server as daemon. + +-f, --fingerprint Use fingerprints in the TURN messages. If an incoming request + contains a fingerprint, then TURN server will always add + fingerprints to the messages in this session, regardless of the + per-server setting. + +-a, --lt-cred-mech Use long-term credentials mechanism (this one you need for WebRTC usage). + This option can be used with either flat file user database or + PostgreSQL DB or MySQL DB or Redis for user keys storage. +-A, --st-cred-mech Use the short-term credentials mechanism. This option requires + a PostgreSQL or MySQL or Redis DB for short term passwords storage. + +-z, --no-auth Do not use any credentials mechanism, allow anonymous access. + Opposite to -a and -A options. This is default option when no + authentication-related options are set. + By default, no credential mechanism is used - + any user is allowed. + +--use-auth-secret TURN REST API flag. + Flag that sets a special WebRTC authorization option + that is based upon authentication secret. The feature purpose + is to support "TURN Server REST API" as described in + the TURN REST API section below. + This option uses timestamp as part of combined username: + usercombo -> "timestamp:username", + turn user -> usercombo, + turn password -> base64(hmac(secret key, usercombo)). + This allows TURN credentials to be accounted for a specific user id. + If you don't have a suitable id, the timestamp alone can be used. + This option is just turns on secret-based authentication. + The actual value of the secret is defined either by option static-auth-secret, + or can be found in the turn_secret table in the database. + This option can be used with long-term credentials mechanisms only - + it does not make much sense with the short-term mechanism. + +--dh566 Use 566 bits predefined DH TLS key. Default size of the key is 1066. + +--dh2066 Use 2066 bits predefined DH TLS key. Default size of the key is 1066. + +--no-sslv2 Do not allow SSLv2 protocol. + +--no-sslv3 Do not allow SSLv3 protocol. + +--no-tlsv1 Do not allow TLSv1 protocol. + +--no-tlsv1_1 Do not allow TLSv1.1 protocol. + +--no-tlsv1_2 Do not allow TLSv1.2 protocol. + +--no-udp Do not start UDP client listeners. + +--no-tcp Do not start TCP client listeners. + +--no-tls Do not start TLS client listeners. + +--no-dtls Do not start DTLS client listeners. + +--no-udp-relay Do not allow UDP relay endpoints defined in RFC 5766, + use only TCP relay endpoints as defined in RFC 6062. + +--no-tcp-relay Do not allow TCP relay endpoints defined in RFC 6062, + use only UDP relay endpoints as defined in RFC 5766. + +--stale-nonce Use extra security with nonce value having limited lifetime (600 secs). + +--no-stdout-log Flag to prevent stdout log messages. + By default, all log messages are going to both stdout and to + the configured log file. With this option everything will be going to + the log file only (unless the log file itself is stdout). + +--syslog With this flag, all log will be redirected to the system log (syslog). + +--simple-log This flag means that no log file rollover will be used, and the log file + name will be constructed as-is, without PID and date appendage. + +--secure-stun Require authentication of the STUN Binding request. + By default, the clients are allowed anonymous access to the STUN Binding functionality. + +-S, --stun-only Run as STUN server only, all TURN requests will be ignored. + Option to suppress TURN functionality, only STUN requests will be processed. + +--no-stun Run as TURN server only, all STUN requests will be ignored. + Option to suppress STUN functionality, only TURN requests will be processed. + +--no-loopback-peers Disallow peers on the loopback addresses (127.x.x.x and ::1). + +--no-multicast-peers Disallow peers on well-known broadcast addresses + (224.0.0.0 and above, and FFXX:*). + +--sha256 Require SHA256 digest function to be used for the message integrity. + By default, the server uses SHA1 hashes. With this option, the server + requires the stronger SHA256 hashes. The client application must support + SHA256 hash function if this option is used. If the server obtains a message + from the client with a weaker (SHA1) hash function then the server returns + error code 426. + +--mobility Mobility with ICE (MICE) specs support. + +--no-cli Turn OFF the CLI support. By default it is always ON. + See also options --cli-ip and --cli-port. + +--server-relay Server relay. NON-STANDARD AND DANGEROUS OPTION. + Only for those applications when we want to run + server applications on the relay endpoints. + This option eliminates the IP permissions check + on the packets incoming to the relay endpoints. + See http://tools.ietf.org/search/rfc5766#section-17.2.3 . + +--udp-self-balance (recommended for older Linuxes only) + Automatically balance UDP traffic over auxiliary servers + (if configured). The load balancing is using the + ALTERNATE-SERVER mechanism. The TURN client must support + 300 ALTERNATE-SERVER response for this functionality. + +-h Help. + +Options with required values: + +-d, --listening-device Listener interface device. + (NOT RECOMMENDED. Optional functionality, Linux only). + The turnserver process must have root privileges to bind the + listening endpoint to a device. If turnserver must run as a + process without root privileges, then just do not use this setting. + +-L, --listening-ip Listener IP address of relay server. + Multiple listeners can be specified, for example: + -L ip1 -L ip2 -L ip3 + If no IP(s) specified, then all IPv4 and + IPv6 system IPs will be used for listening. + The same ip(s) can be used as both listening and relay ip(s). + +-p, --listening-port TURN listener port for UDP and TCP listeners (Default: 3478). + Note: actually, TLS & DTLS sessions can connect to the "plain" TCP & UDP + port(s), too - if allowed by configuration. + +--tls-listening-port TURN listener port for TLS and DTLS listeners (Default: 5349). + Note: actually, "plain" TCP & UDP sessions can connect to the TLS & DTLS + port(s), too - if allowed by configuration. The TURN server + "automatically" recognizes the type of traffic. Actually, two listening + endpoints (the "plain" one and the "tls" one) are equivalent in terms of + functionality; but we keep both endpoints to satisfy the RFC 5766 specs. + For secure TCP connections, we currently support SSL version 3 and + TLS versions 1.0, 1.1, 1.2. SSL2 "encapsulation mode" is also supported. + For secure UDP connections, we support DTLS version 1. + +--alt-listening-port Alternative listening port for UDP and TCP listeners; + default (or zero) value means "listening port plus one". + This is needed for STUN CHANGE_REQUEST - in RFC 5780 sense + or in old RFC 3489 sense - for NAT behavior discovery). The TURN Server + supports CHANGE_REQUEST only if it is started with more than one + listening IP address of the same family (IPv4 or IPv6). The CHANGE_REQUEST + is only supported by UDP protocol, other protocols are listening + on that endpoint only for "symmetry". + +--alt-tls-listening-port Alternative listening port for TLS and DTLS protocols. + Default (or zero) value means "TLS listening port plus one". + +--aux-server Auxiliary STUN/TURN server listening endpoint. + Aux servers have almost full TURN and STUN functionality. + The (minor) limitations are: + 1) Auxiliary servers do not have alternative ports and + they do not support STUN RFC 5780 functionality (CHANGE REQUEST). + 2) Auxiliary servers also are never returning ALTERNATIVE-SERVER reply. + + Valid formats are 1.2.3.4:5555 for IPv4 and [1:2::3:4]:5555 for IPv6. + There may be multiple aux-server options, each will be used for listening + to client requests. + +-i, --relay-device Relay interface device for relay sockets + (NOT RECOMMENDED. Optional, Linux only). + +-E, --relay-ip Relay address (the local IP address that + will be used to relay the packets to the + peer). Multiple relay addresses may be used: + -E ip1 -E ip2 -E ip3 + The same IP(s) can be used as both listening IP(s) and relay IP(s). + If no relay IP(s) specified, then the turnserver will apply the + default policy: it will decide itself which relay addresses to be + used, and it will always be using the client socket IP address as + the relay IP address of the TURN session (if the requested relay + address family is the same as the family of the client socket). + +-X, --external-ip TURN Server public/private address mapping, if the server is behind NAT. + In that situation, if a -X is used in form "-X " then that ip will be reported + as relay IP address of all allocations. This scenario works only in a simple case + when one single relay address is be used, and no CHANGE_REQUEST functionality is + required. That single relay address must be mapped by NAT to the 'external' IP. + The "external-ip" value, if not empty, is returned in XOR-RELAYED-ADDRESS field. + For that 'external' IP, NAT must forward ports directly (relayed port 12345 + must be always mapped to the same 'external' port 12345). + In more complex case when more than one IP address is involved, + that option must be used several times, each entry must + have form "-X ", to map all involved addresses. + CHANGE_REQUEST (RFC5780 or RFC3489) NAT discovery STUN functionality will work + correctly, if the addresses are mapped properly, even when the TURN server itself + is behind A NAT. + By default, this value is empty, and no address mapping is used. + +-m, --relay-threads Number of relay threads to handle the established connections + (in addition to authentication thread and the listener thread). + If set to 0 then application runs relay process in a single thread, + in the same thread with the listener process (the authentication thread will + still be a separate thread). In older systems (before Linux kernel 3.9), + the number of UDP threads is always one threads per network listening endpoint - + unless "-m 0" or "-m 1" is set. + +--min-port Lower bound of the UDP port range for relay + endpoints allocation. + Default value is 49152, according to RFC 5766. + +--max-port Upper bound of the UDP port range for relay + endpoints allocation. + Default value is 65535, according to RFC 5766. + +-u, --user Long-term security mechanism credentials user account, + in the column-separated form username:key. + Multiple user accounts may used in the command line. + The key is either the user password, or + the key is generated + by turnadmin command. In the second case, + the key must be prepended with 0x symbols. + The key is calculated over the user name, + the user realm, and the user password. + This setting may not be used with TURN REST API or + with short-term credentials mechanism. + +-r, --realm The default realm to be used for the users when no explicit + origin/realm relationship was found in the database, or if the TURN + server is not using any database (just the commands-line settings + and the userdb file). Must be used with long-term credentials + mechanism or with TURN REST API. + +-C, --rest-api-separator This is the timestamp/username separator symbol (character) in TURN REST API. + The default value is :. + +-q, --user-quota Per-user allocations quota: how many concurrent + allocations a user can create. This option can also be set + through the database, for a particular realm. + +-Q, --total-quota Total allocations quota: global limit on concurrent allocations. + This option can also be set through the database, for a particular realm. + +-s, --max-bps Max bytes-per-second bandwidth a TURN session is allowed to handle + (input and output network streams are treated separately). Anything above + that limit will be dropped or temporary suppressed (within the + available buffer limits). This option can also be set through the + database, for a particular realm. + +--static-auth-secret Static authentication secret value (a string) for TURN REST API only. + If not set, then the turn server will try to use the dynamic value + in turn_secret table in user database (if present). The database-stored + value can be changed on-the-fly by a separate program, so this is why + that other mode is dynamic. Multiple shared secrets can be used + (both in the database and in the "static" fashion). + +--cert Certificate file, PEM format. Same file + search rules applied as for the configuration + file. If both --no-tls and --no-dtls options + are specified, then this parameter is not needed. + Default value is turn_server_cert.pem. + +--pkey Private key file, PEM format. Same file + search rules applied as for the configuration + file. If both --no-tls and --no-dtls options + are specified, then this parameter is not needed. + Default value is turn_server_pkey.pem. + +--pkey-pwd If the private key file is encrypted, then this password to be used. + +--cipher-list Allowed OpenSSL cipher list for TLS/DTLS connections. + Default value is "DEFAULT". + +--CA-file CA file in OpenSSL format. + Forces TURN server to verify the client SSL certificates. + By default, no CA is set and no client certificate check is performed. + +--ec-curve-name Curve name for EC ciphers, if supported by OpenSSL library (TLS and DTLS). + The default value is prime256v1. + +--dh-file Use custom DH TLS key, stored in PEM format in the file. + Flags --dh566 and --dh2066 are ignored when the DH key is taken from a file. + +-l, --log-file Option to set the full path name of the log file. + By default, the turnserver tries to open a log file in + /var/log/turnserver, /var/log, /var/tmp, /tmp and . (current) + directories (which file open operation succeeds + first that file will be used). With this option you can set the + definite log file name. + The special names are "stdout" and "-" - they will force everything + to the stdout. Also, "syslog" name will redirect everything into + the system log (syslog), as if the option "--syslog" was set. + +--alternate-server Option to set the "redirection" mode. The value of this option + will be the address of the alternate server for UDP & TCP service in form of + [:]. The server will send this value in the attribute + ALTERNATE-SERVER, with error 300, on ALLOCATE request, to the client. + Client will receive only values with the same address family + as the client network endpoint address family. + See RFC 5389 and RFC 5766 for ALTERNATE-SERVER functionality description. + The client must use the obtained value for subsequent TURN communications. + If more than one --alternate-server options are provided, then the functionality + can be more accurately described as "load-balancing" than a mere "redirection". + If the port number is omitted, then the default port + number 3478 for the UDP/TCP protocols will be used. + Colon (:) characters in IPv6 addresses may conflict with the syntax of + the option. To alleviate this conflict, literal IPv6 addresses are enclosed + in square brackets in such resource identifiers, for example: + [2001:db8:85a3:8d3:1319:8a2e:370:7348]:3478 . + Multiple alternate servers can be set. They will be used in the + round-robin manner. All servers in the pool are considered of equal weight and + the load will be distributed equally. For example, if we have 4 alternate servers, + then each server will receive 25% of ALLOCATE requests. An alternate TURN server + address can be used more than one time with the alternate-server option, so this + can emulate "weighting" of the servers. + +--tls-alternate-server Option to set alternative server for TLS & DTLS services in form of + :. If the port number is omitted, then the default port + number 5349 for the TLS/DTLS protocols will be used. See the previous option for the + functionality description. + +-O, --redis-statsdb Redis status and statistics database connection string, if used (default - empty, + no Redis stats DB used). This database keeps allocations status information, and it can + be also used for publishing and delivering traffic and allocation event notifications. + This database option can be used independently of --redis-userdb option, + and actually Redis can be used for status/statistics and MySQL or PostgreSQL can + be used for the user database. + The connection string has the same parameters as redis-userdb connection string. + +--max-allocate-timeout Max time, in seconds, allowed for full allocation establishment. + Default is 60 seconds. + +--denied-peer-ip= + +--allowed-peer-ip= Options to ban or allow specific ip addresses or ranges + of ip addresses. If an ip address is specified as both allowed and denied, then + the ip address is considered to be allowed. This is useful when you wish to ban + a range of ip addresses, except for a few specific ips within that range. + This can be used when you do not want users of the turn server to be able to access + machines reachable by the turn server, but would otherwise be unreachable from the + internet (e.g. when the turn server is sitting behind a NAT). The 'white" and "black" peer + IP ranges can also be dynamically changed in the database. + The allowed/denied addresses (white/black lists) rules are very simple: + 1) If there is no rule for an address, then it is allowed; + 2) If there is an "allowed" rule that fits the address then it is allowed - no matter what; + 3) If there is no "allowed" rule that fits the address, and if there is a "denied" rule that + fits the address, then it is denied. + +--pidfile File name to store the pid of the process. + Default is /var/run/turnserver.pid (if superuser account is used) or + /var/tmp/turnserver.pid . + +--proc-user User name to run the process. After the initialization, the turnserver process + will make an attempt to change the current user ID to that user. + +--proc-group Group name to run the process. After the initialization, the turnserver process + will make an attempt to change the current group ID to that group. + +--cli-ip Local system IP address to be used for CLI management interface. + The turnserver process can be accessed for management with telnet, + at this IP address and on the CLI port (see the next parameter). + Default value is 127.0.0.1. You can use telnet or putty (in telnet mode) + to access the CLI management interface. + +--cli-port CLI management interface listening port. Default is 5766. + +--cli-password CLI access password. Default is empty (no password). + +--cli-max-output-sessions Maximum number of output sessions in ps CLI command. + This value can be changed on-the-fly in CLI. The default value is 256. + +--ne=[1|2|3] Set network engine type for the process (for internal purposes). + +================================== + +LOAD BALANCE AND PERFORMANCE TUNING + +This topic is covered in the wiki page: + +http://code.google.com/p/coturn/wiki/turn_performance_and_load_balance + +=================================== + +WEBRTC USAGE + +This is a set of notes for the WebRTC users: + +1) WebRTC uses long-term authentication mechanism, so you have to use -a +option (or --lt-cred-mech). WebRTC relaying will not work with anonymous access +or with short-term authentication. With -a option, do not forget to set the +default realm (-r option). You will also have to set up the user accounts, +for that you have a number of options: + + a) command-line options (-u). + + b) userdb config file. + + c) a database table (PostgreSQL or MySQL). You will have to set keys with + turnadmin utility (see docs and wiki for turnadmin). You cannot use open passwords + in the database. + + d) Redis key/value pair(s), if Redis is used. You key use either keys or + open passwords with Redis; see turndb/testredisdbsetup.sh file. + + e) You also can use the TURN REST API. You will need shared secret(s) set + either through the command line option, or through the config file, or through + the database table or Redis key/value pairs. + +2) Usually WebRTC uses fingerprinting (-f). + +3) -v option may be nice to see the connected clients. + +4) -X is needed if you are running your TURN server behind a NAT. + +5) --min-port and --max-port may be needed if you want to limit the relay endpoints ports +number range. + +=================================== + +TURN REST API + +In WebRTC, the browser obtains the TURN connection information from the web +server. This information is a secure information - because it contains the +necessary TURN credentials. As these credentials are transmitted over the +public networks, we have a potential security breach. + +If we have to transmit a valuable information over the public network, +then this information has to have a limited lifetime. Then the guy who +obtains this information without permission will be able to perform +only limited damage. + +This is how the idea of TURN REST API - time-limited TURN credentials - +appeared. This security mechanism is based upon the long-term credentials +mechanism. The main idea of the REST API is that the web server provides +the credentials to the client, but those credentials can be used only +limited time by an application that has to create a TURN server connection. + +The "classic" long-term credentials mechanism (LTCM) is described here: + +http://tools.ietf.org/html/rfc5389#section-10.2 +http://tools.ietf.org/html/rfc5389#section-15.4 + +For authentication, each user must know two things: the username and the +password. Optionally, the user must supply the ORIGIN value, so that the +server can figure out the realm to be used for the user. The nonce and +the realm values are supplied by the TURN server. But LTCM is not saying +anything about the nature and about the persistence +of the username and of the password; and this is used by the REST API. + +In the TURN REST API, there is no persistent passwords for users. A user has +just the username. The password is always temporary, and it is generated by +the web server on-demand, when the user accesses the WebRTC page. And, +actually, a temporary one-time session only, username is provided to the user, +too. + +The temporary user is generated as: + +temporary-username="timestamp" + ":" + "username" + +where username is the persistent user name, and the timestamp format is just +seconds sinse 1970 - the same value as time(NULL) function returns. + +The temporary password is obtained as HMAC-SHA1 function over the temporary +username, with shared secret as the HMAC key, and then the result is encoded: + +temporary-password = base64_encode(hmac-sha1(shared-secret, temporary-username)) + +Both the TURN server and the web server know the same shared secret. How the +shared secret is distributed among the involved entities is left to the WebRTC +deployment details - this is beyond the scope of the TURN REST API. + +So, a timestamp is used for the temporary password calculation, and this +timestamp can be retrieved from the temporary username. This information +is valuable, but only temporary, while the timestamp is not expired. Without +knowledge of the shared secret, a new temporary password cannot be generated. + +This is all formally described in Justin's Uberti TURN REST API document +that can be obtained following the link "TURN REST API" in the TURN Server +project's page http://code.google.com/p/coturn/. + +Once the temporary username and password are obtained by the client (browser) +application, then the rest is just 'classic" long-term credentials mechanism. +For developers, we are going to describe it step-by-step below: + + - a new TURN client sends a request command to the TURN server. + - TURN server sees that this is a new client and the message is not + authenticated. + - the TURN server generates a random nonce string, and return the + error 401 to the client, with nonce and realm included. + - the client sees the 401 error and it extracts two values from + the error response: the nonce and the realm. + - the client uses username, realm and password to produce a key: + + key = MD5(username ":" realm ":" SASLprep(password)) + (SASLprep is described here: http://tools.ietf.org/html/rfc4013) + + - the client forms a new request, adds username, realm and nonce to the + request. Then, the client calculates and adds the integrity field to + the request. This is the trickiest part of the process, and it is + described in the end of section 15.4: + http://tools.ietf.org/html/rfc5389#section-15.4 + - the client, optionally, adds the fingerprint field. This may be also + a tricky procedure, described in section 15.5 of the same document. + WebRTC usually uses fingerprinted TURN messages. + - the TURN server receives the request, reads the username. + - then the TURN server checks that the nonce and the realm in the request + are the valid ones. + - then the TURN server calculates the key. + - then the TURN server calculates the integrity field. + - then the TURN server compares the calculated integrity field with the + received one - they must be the same. If the integrity fields differ, + then the request is rejected. + +In subsequent communications, the client may go with exactly the same +sequence, but for optimization usually the client, having already +information about realm and nonce, pre-calculates the integrity string +for each request, so that the 401 error response becomes unnecessary. +The TURN server may use "--stale-nonce" option for extra security: in +some time, the nonce expires and the client will obtain 438 error response +with the new nonce, and the client will have to start using the new nonce. + +In subsequent communications, the sever and the client will always assume +the same password - the original password becomes the session parameter and +is never expiring. So the password is not changing while the session is valid +and unexpired. So, if the session is properly maintained, it may go forever, +even if the user password has been already changed (in the database). The +session simply is using the old password. Once the session got disconnected, +the client will have to use the new password to re-connect (if the password +has been changed). + +An example when a new shared secret is generated every hour by the TURN server +box and then supplied to the web server, remotely, is provided in the script +examples/scripts/restapi/shared_secret_maintainer.pl . + +A very important thing is that the nonce must be totally random and it must be +different for different clients and different sessions. + +=================================== + +DATABASES + +For the user database, the turnserver has the following options: + +1) Users can be set in the command line, with multiple -u or --user options. +Obviously, only a few users can be set that way, and their credentials are fixed +for the turnserver process lifetime. + +2) Users can be set in turnusers.conf flat file DB. The turnserver process periodically +re-reads this file, so the user accounts may be changed while the turnserver is running. +But still a relatively small (up to a hundred ?) number of users can be handled that way. + +3) Users can be stored in PostgreSQL database, if the turnserver was compiled with PostgreSQL +support. Each time turnserver checks user credentials, it reads the database (asynchronously, +of course, so that the current flow of packets is not delayed in any way), so any change in the +database content is immediately visible by the turnserver. This is the way if you need the +best scalability. The schema for the database can be found in schema.sql file. +For long-term credentials, you have to set the "keys" for the users; the "keys" are generated +by the turnadmin utility. For the key generation, you need username, password and the realm. +All users in the database must use the same realm value; if down the road you will decide +to change the realm name, then you will have to re-generate all user keys (that can be done +in a batch script). If you are using short-term credentials, then you use open passwords +in the database; you will have to make sure that nobody can access the database outside of +the TURN server box. See the file turndb/testsqldbsetup.sql as an example. + +4) The same is true for MySQL database. The same schema file is applicable. +The same considerations are applicable. + +5) The same is true for the Redis database, but the Redis database has aa different schema - +it can be found (in the form of explanation) in schema.userdb.redis. +Also, in Redis you can store both "keys" and open passwords (for long term credentials) - +the "open password" option is less secure but more convenient for low-security environments. +For short-term credentials, you will use open passwords only. See the file +turndb/testredisdbsetup.sh as an example. + +6) Of course, the turnserver can be used in non-secure mode, when users are allowed to establish +sessions anonymously. But in most cases (like WebRTC) that will not work. + +For the status and statistics database, there are two choices: + +1) The simplest choice is not to use it. Do not set --redis-statsdb option, and this functionality +will be simply ignored. + +2) If you choose to use it, then set the --redis-statsdb option. This may be the same database +as in --redis-userdb option, or it may be a different database. You may want to use different +database for security or convenience reasons. Also, you can use different database management +systems for the user database and for the ststus and statistics database. For example, you can use +MySQL as the user database, and you can use redis for the statistics. Or you can use Redis for both. + +So, we have 6 choices for the user management, and 2 choices for the statistics management. These +two are totally independent. So, you have overall 6*2=12 ways to handle persistent information, +choose any for your convenience. + +You do not have to handle the database information "manually" - the turnadmin program can handle +everything for you. For PostgreSQL and MySQL you will just have to create an empty database +with schema.sql SQL script. With Redis, you do not have to do even that - just run turnadmin and +it will set the users for you (see the turnadmin manuals). + +================================= + +LIBRARIES + +In the lib/ sub-directory the build process will create TURN client messaging library. +In the include/ sub-directory, the necessary include files will be placed. +The C++ wrapper for the messaging functionality is located in TurnMsgLib.h header. +An example of C++ code can be found in stunclient.c file. + +================================= + +DOCS + +After installation, run the command: + +$ man turnserver + +or in the project root directory: + +$ man -M man turnserver + +to see the man page. + +In the docs/html subdirectory of the original archive tree, you will find the client library +reference. After the installation, it will be placed in PREFIX/share/doc/turnserver/html. + +================================= + +LOGS + +When the TURN Server starts, it makes efforts to create a log file turn_.log +in the following directories: + + * /var/log + * /log/ + * /var/tmp + * /tmp + * current directory + +If all efforts failed (due to the system permission settings) then all +log messages are sent only to the standard output of the process. + +This behavior can be controlled by --log-file, --syslog and --no-stdout-log options. + +================================= + +TELNET CLI + +The turnserver process provides a telnet CLI access as statistics and basic management +interface. By default, the turnserver starts a telnet CLI listener on IP 127.0.0.1 and +port 5766. That can be changed by the command-cline options of the turnserver process +(see --cli-ip and --cli-port options). The full list of telnet CLI commands is provided +in "help" command output in the telnet CLI. + +================================= + +CLUSTERS + +TURN Server can be a part of the cluster installation. But, to support the "even port" functionality +(RTP/RTCP streams pairs) the client requests from a particular IP must be delivered to the same +TURN Server instance, so it requires some networking setup massaging for the cluster. The reason is that +the RTP and RTCP relaying endpoints must be allocated on the same relay IP. It would be possible +to design a scheme with the application-level requests forwarding (and we may do that later) but +it would affect the performance. + +================================= + +FILES + +/etc/turnserver.conf + +/etc/turnuserdb.conf + +/usr/local/etc/turnserver.conf + +/usr/local/etc/turnuserdb.conf + +================================= + +DIRECTORIES + +/usr/local/share/turnserver + +/usr/local/share/doc/turnserver + +/usr/local/share/examples/turnserver + +================================= + +STANDARDS + +obsolete STUN RFC 3489 + +new STUN RFC 5389 + +TURN RFC 5766 + +TURN-TCP extension RFC 6062 + +TURN IPv6 extension RFC 6156 + +STUN/TURN test vectors RFC 5769 + +STUN NAT behavior discovery RFC 5780 + +================================= + +SEE ALSO + + turnadmin, turnutils + +====================================== + + WEB RESOURCES + + project page: + + http://code.google.com/p/coturn/ + + Wiki page: + + http://code.google.com/p/coturn/wiki/Readme + + forum: + + https://groups.google.com/forum/?fromgroups=#!forum/turn-server-project-rfc5766-turn-server/ + +====================================== + + AUTHORS + + Oleg Moskalenko + + Gabor Kovesdan http://kovesdan.org/ + + Daniel Pocock http://danielpocock.com/ + + John Selbie (jselbie@gmail.com) + + Lee Sylvester + + Erik Johnston + + Roman Lisagor + + Vladimir Tsanev + + Po-sheng Lin + + Peter Dunkley + + Mutsutoshi Yoshimoto diff --git a/README.turnutils b/README.turnutils new file mode 100644 index 0000000..e0c7c8c --- /dev/null +++ b/README.turnutils @@ -0,0 +1,329 @@ +GENERAL INFORMATION + +A set of turnutils_* programs provides some utility functionality to be used +for testing and for setting up the TURN server. + +1. turnutils_uclient: emulates multiple UDP,TCP,TLS or DTLS clients. +(this program is provided for the testing purposes only !) +The compiled binary image of this program is located in bin/ +sub-directory. + +WARNING: the turnutils_uclient program is a primitive client application. +It does not implement the re-transmission pattern that is necessary for +a correct TURN client implementation. In TURN, the retransmission burden +is lying almost entirely on the client application. We provide the messaging +functionality in the client library, but the client must implement +the correct Networking IO processing in the client program code. + +2. turnutils_peer: a simple stateless UDP-only "echo" server, +to be used as the final server in relay pattern ("peer"). For every incoming +UDP packet, it simply echoes it back. +(this program is provided for the testing purposes only !) +When the test clients are communicating in the client-to-client manner +(when the "turnutils_uclient" program is used with "-y" option) then the +turnutils_peer is not needed. + +The compiled binary image of this program is located in bin/ subdirectory. + +3. turnutils_stunclient: a simple STUN client example. +The compiled binary image of this program is located in bin/ subdirectory. + +4. turnutils_rfc5769check: a utility that checks the correctness of the +STUN/TURN protocol implementation. This utility is used only for the compilation +check procedure, it is not copied to the installation destination. + + +In the "examples/scripts" subdirectory, you will find the examples of command lines to be used +to run the programs. The scripts are meant to be run from examples/ subdirectory, for example: + +$ cd examples + +$ ./scripts/secure_relay.sh + +===================================== + + NAME + +turnutils_uclient - this client emulation application is supplied for the test purposes only. + + SYNOPSIS + +$ turnutils_uclient [-tTSvsyhcxg] [options] + + DESCRIPTION + +It was designed to simulate multiple clients. It uses asynch IO API in +libevent to handle multiple clients. A client connects to the relay, +negotiates the session, and sends multiple (configured number) messages to the server (relay), +expecting the same number of replies. The length of the messages is configurable. +The message is an arbitrary octet stream, but it can be configured as a string. +The number of the messages to send is configurable. + +Flags: + +-t Use TCP for communications between client and TURN server (default is UDP). + +-T Use TCP for the relay transport (default - UDP). Implies options -t, -y, -c, + and ignores flags and options -s, -e, -r and -g. + +-P Passive TCP (RFC6062 with active peer). Implies -T. + +-S Secure SSL connection: SSL/TLS for TCP, DTLS for UDP. + +-U Secure unencrypted connection (suite eNULL): SSL/TLS for TCP, DTLS for UDP. + +-v Verbose. + +-s Use "Send" method in TURN; by default, it uses TURN Channels. + +-y Use client-to-client connections: + RTP/RTCP pair of channels to another RTP/RTCP pair of channels. + with this option the turnutils_peer application is not used, + as the allocated relay endpoints are talking to each other. + +-h Hang on indefinitely after the last sent packet. + +-c Do not create rtcp connections. + +-x Request IPv6 relay address (RFC6156). + +-X IPv4 relay address explicitly requested. + +-g Set DONT_FRAGMENT parameter in TURN requests. + +-A use short-term credentials mechanism for authentication. + By default, the program uses the long-term credentials mechanism + if authentication is required. + +-D Do mandatory channel padding even for UDP (like pjnath). + +-N do negative tests (some limited cases only). + +-R do negative protocol tests. + +-O DOS attack mode. + +-H SHA256 digest function for message integrity calculation. + Without this option, by default, SHA1 is used. + +-M Use TURN ICE Mobility. + +-I Do not set permissions on TURN relay endpoints + (for testing the non-standard server relay functionality). + +-G Generate extra requests (create permissions, channel bind). + +Options with required values: + +-l Message length (Default: 100 Bytes). + +-i Certificate file (for secure connections only, optional). + +-k Private key file (for secure connections only). + +-E CA file for server certificate verification, + if the server certificate to be verified. + +-p TURN Server port (Defaults: 3478 unsecure, 5349 secure). + +-n Number of messages to send (Default: 5). + +-d Local interface device (optional, Linux only). + +-L Local IP address (optional). + +-m Number of clients (Default: 1, 2 or 4, depending on options). + +-e Peer address. + +-r Peer port (Default: 3480). + +-z Per-session packet interval in milliseconds (Default: 20). + +-u STUN/TURN user name. + +-w STUN/TURN user password. + +-W TURN REST API authentication secret. Is not compatible with -A flag. + +-C This is the timestamp/username separator symbol (character) in + TURN REST API. The default value is :. + +-F Cipher suite for TLS/DTLS. Default value is DEFAULT. + +See the examples in the "examples/scripts" directory. + +====================================== + + NAME + +turnutils_peer - a simple UDP-only echo backend server. + + SYNOPSYS + +$ turnutils_peer [-v] [options] + + DESCRIPTION + +This application is used for the test purposes only, as a peer for the turnutils_uclient application. + +Options with required values: + +-p Listening UDP port (Default: 3480). + +-d Listening interface device (optional) + +-L Listening address of turnutils_peer server. Multiple listening addresses can be used, IPv4 and IPv6. +If no listener address(es) defined, then it listens on all IPv4 and IPv6 addresses. + +-v Verbose + +======================================== + + NAME + +turnutils_stunclient - a basic STUN client. + + SYNOPSIS + +$ turnutils_stunclient [options] + + DESCRIPTION + +It sends a "new" STUN RFC 5389 request (over UDP) and shows the reply information. + +Options with required values: + +-p STUN server port (Default: 3478). + +-L Local address to use (optional). + +-f Force RFC 5780 processing. + +The turnutils_stunclient program checks the results of the first request, +and if it finds that the STUN server supports RFC 5780 +(the binding response reveals that) then the turnutils_stunclient makes a couple more +requests with different parameters, to demonstrate the NAT discovery capabilities. + +This utility does not support the "old" "classic" STUN protocol (RFC 3489). + +===================================== + + NAME + +turnutils_rfc5769check - a utility that tests the correctness of STUN protocol implementation. + + SYNOPSIS + + $ turnutils_rfc5769check + + DESCRIPTION + +turnutils_rfc5769check tests the correctness of STUN protocol implementation +against the test vectors predefined in RFC 5769 and prints the results of the +tests on the screen. This utility is used only for the compilation +check procedure, it is not copied to the installation destination. + +Usage: + +$ turnutils_rfc5769check + +=================================== + +DOCS + +After installation, run the command: + +$ man turnutils + +or in the project root directory: + +$ man -M man turnutils + +to see the man page. + +===================================== + +FILES + +/etc/turnserver.conf + +/etc/turnuserdb.conf + +/usr/local/etc/turnserver.conf + +/usr/local/etc/turnuserdb.conf + +================================= + +DIRECTORIES + +/usr/local/share/turnserver + +/usr/local/share/doc/turnserver + +/usr/local/share/examples/turnserver + +=================================== + +STANDARDS + +new STUN RFC 5389 + +TURN RFC 5766 + +TURN-TCP extension RFC 6062 + +TURN IPv6 extension RFC 6156 + +STUN/TURN test vectors RFC 5769 + +STUN NAT behavior discovery RFC 5780 + +==================================== + +SEE ALSO + + turnserver, turnadmin + +====================================== + + WEB RESOURCES + + project page: + + http://code.google.com/p/coturn/ + + Wiki page: + + http://code.google.com/p/coturn/wiki/Readme + + forum: + + https://groups.google.com/forum/?fromgroups=#!forum/turn-server-project-rfc5766-turn-server/ + +====================================== + + AUTHORS + + Oleg Moskalenko + + Gabor Kovesdan http://kovesdan.org/ + + Daniel Pocock http://danielpocock.com/ + + John Selbie (jselbie@gmail.com) + + Lee Sylvester + + Erik Johnston + + Roman Lisagor + + Vladimir Tsanev + + Po-sheng Lin + + Peter Dunkley + + Mutsutoshi Yoshimoto diff --git a/STATUS b/STATUS new file mode 100644 index 0000000..8febb47 --- /dev/null +++ b/STATUS @@ -0,0 +1,101 @@ +Currently implemented functionality: + +1) RFC5389 (new STUN protocol) full server and client +implementations. We do not maintain strict compatibility +with the obsolete RFC 3489 "old STUN" protocol. + +2) RFC5766 TURN protocol full server and client +implementations. We support file-based long term +user credentials, for now. We added experimental DTLS +protocol, too. + +3) RFC6156 TURN IPv6 extension. + +4) We support the following client-to-server +network transports for TURN messages: + a) UDP + b) TCP + c) TLS + d) DTLS + +5) Performance tested. + +6) Torture and stability tests. + +7) Multiple *NIX platforms tested and supported. + +8) TTL field handling implemented for all platforms, preferred behavior in RFC5766. + +9) TOS (DiffServ and ECN) field handling (preferred behavior of RFC 5766) implemented, +for Linux. Other platforms support the alternative behavior of RFC 5766. + +10) DF field alternative behavior of RFC 5766 implemented. + +11) Bandwidth limitation per session implemented. + +12) RFC 5769 test vectors implemented (where applicable). + +13) RFC 5780 STUN extension: NAT behavior discovery. + +14) C++ mapping implemented. + +15) RFC 6062 TCP relaying implemented. + +16) Users can be stored in PostgreSQL database. + +17) Users can be stored in MySQL database. + +18) TURN Server REST API implemented. + +19) Short-term credentials mechanism implemented. + +20) Simple load-balancing with ALTERNATE-SERVER implemented. + +21) Redis database support added. + +22) RFC3489 backward compatibility. + +23) Multithreaded TCP relay processing (UDP relay has been +multithreaded from the beginning). + +24) Networking engine 2.0 implemented, with more scalable approach +to the UDP sockets handling. + +25) DOS attack prevention logic added to the server; DOS attack client +emulation implemented. + +26) Linux UDP sockets workaround added to counter RFC 1122 behavior. + +27) DTLS sockets re-implemented for better scalability and for Cygwin +compatibility. + +28) A number of TLS/DTLS improvements added: multiple protocols support, certificate check option. + +29) SHA256 support added (experimental). + +30) UDP network engine optimized for the new Linux kernels (3.9+). + +31) ICE Mobility draft implemented (experimental). + +32) CLI implemented. + +33) DH and EC TLS ciphers added. + +34) HTTP "keep alive" request supported. + +35) Optimized (for thousands and more sessions) timers implementation. + +36) TCP network engine optimized for the new Linux kernels (3.9+). + +37) telnet-based monitor implemented. + +38) Package memory copy eliminated in traffic routing. + +39) Congestion avoidance implemented, for all protocols. + +40) Multi-tenant server implemented. + +41) Coturn project forked from rfc5766-turn-server. + +Things to be implemented in future (the development roadmap) +are described in the TODO file. diff --git a/TODO b/TODO new file mode 100644 index 0000000..fa334de --- /dev/null +++ b/TODO @@ -0,0 +1,128 @@ +================================================================== + +### I. PLATFORMS SUPPORT ### + +================================================================== + +1) Fedora official package (turnserver or coturn ? TBD). + +2) MS Windows support. + + Cygwin is supported. A "real" MS-Windows port would + involve a usable GUI. + +================================================================== + +### II. DOCS ### + +================================================================== + +1) User's manual. + +2) Developer's manual. + +================================================================== + +### III. NETWORK ENGINE ### + +================================================================== + +1) Exclusive IP addresses for relay + +================================================================== + +### IV. PERFORMANCE OPTIMIZATION ### + +================================================================== + +1) A smarter load balancer has to be implemented. + + The load balancer has to have a heartbeat channels with + the slave servers, currently it is only just a dumb + round-robin load distributor. + +2) For a large enterprise, a user-space stack to be integrated. + + An another socket abstraction to be implemented, + the one that uses the user-space TCP/IP stack with + zero memory copy. This is an ambitious goal that would + increase the system scaleability, significantly. + The stock TCP/IP stack in UNIX and in MS Windows do not + scale gracefully. We are trying to suppress those issues + in the TURN Server, by using an advanced synchronous + I/O technique, but still the underlying stock TCP/IP stack + is a limitation. + +3) Multiple authentication threads. + +================================================================== + +### V. SECURITY ### + +================================================================== + +1) RADIUS integration ? + +2) Watch new TURN security draft. oAuth integration. + +================================================================== + +### VI. STANDARDS SUPPORT ### + +================================================================== + +1) Follow the draft ICE endpoint mobility standard and add changes +when necessary: + + https://ietf.org/doc/draft-wing-mmusic-ice-mobility/ + +2) For extra difficult NAT/FWs, consider implementing Websockets. + +3) MS TURN, MS STUN extensions. + +4) Bandwidth draft. + +5) ALPN with TLS and DTLS. + +6) Redirect draft. + +================================================================== + +### VII. MISC FEATURES ### + +================================================================== + +1) Locale support (?). + + Currently we assume that all text data is 8-bits ASCII + encoded, like C locale. It would be nice to support localized + strings (both 8-bits and 2-bytes). But I am not sure + whether this is really important, given the essentially + backend nature of the TURN Server. The TURN server is so + deeply "hidden" in the network infrastructure that the + significant code complication may be unjustified. + +2) HTTP or GUI status monitor and management. + + For enterprise users, a management (configuration, status + and statistics) GUI has to be implemented. Currently, all + these features are available through the shell command + line, telnet client and through Redis command line. + +3) Traffic recording (for selected allocations). + + That would be a helpful feature for a large enterprise + (for testing and security purposes). + +4) Ganglia monitoring. + +================================================================== + +### VIII. CODING STUFF ### + +================================================================== + +Nope + +================================================================== + diff --git a/configure b/configure new file mode 100755 index 0000000..c09a3b6 --- /dev/null +++ b/configure @@ -0,0 +1,1063 @@ +#!/bin/sh + +# Proprietary configure script of Coturn project + +cleanup() { + rm -rf ${TMPCPROGC} + rm -rf ${TMPCPROGB} + rm -rf ${TH_TMPCPROGC} + rm -rf ${TH_TMPCPROGB} + rm -rf ${DTLS_TMPCPROGC} + rm -rf ${DTLS_TMPCPROGB} + rm -rf ${PQ_TMPCPROGC} + rm -rf ${PQ_TMPCPROGB} + rm -rf ${MYSQL_TMPCPROGC} + rm -rf ${MYSQL_TMPCPROGB} + rm -rf ${D_TMPCPROGC} + rm -rf ${D_TMPCPROGB} + rm -rf ${E_TMPCPROGC} + rm -rf ${E_TMPCPROGO} + rm -rf ${HR_TMPCPROGC} + rm -rf ${HR_TMPCPROGB} + rm -rf ${TMPCADDRPROGO} +} + +testlibraw() { + ${CC} ${TMPCPROGC} -o ${TMPCPROGB} ${OSCFLAGS} ${OSLIBS} -${1} 2>>/dev/null + ER=$? + if ! [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "Do not use -${1}" + return 0 + else + OSLIBS="${OSLIBS} -${1}" + return 1 + fi +} + +testlibevent2_comp() { + ${CC} -c ${E_TMPCPROGC} -o ${E_TMPCPROGO} ${OSCFLAGS} 2>>/dev/null + ER=$? + if ! [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "Libevent2 development is not installed properly" + return 0 + else + return 1 + fi +} + +testhiredis() { + HIREDISCFLAGS= + HIREDISLIBS=-lhiredis + ${CC} ${HR_TMPCPROGC} -o ${HR_TMPCPROGB} ${OSCFLAGS} ${DBLIBS} ${HIREDISCFLAGS} ${HIREDISLIBS} 2>>/dev/null + ER=$? + if ! [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} + ${ECHO_CMD} "HIREDIS DEVELOPMENT LIBRARY (libhiredis.*) AND/OR HEADERS (hiredis/*.h)" + ${ECHO_CMD} " ARE NOT INSTALLED PROPERLY ON THIS SYSTEM." + ${ECHO_CMD} " THAT'S OK BUT THE TURN SERVER IS BUILDING WITHOUT REDIS SUPPORT." + ${ECHO_CMD} + return 0 + else + DBCFLAGS="${DBCFLAGS} ${HIREDISCFLAGS}" + DBLIBS="${DBLIBS} ${HIREDISLIBS}" + return 1 + fi +} + +testlibpq() { + POSTCFLAGS="-I${PREFIX}/pgsql/include -I${PREFIX}/include/pgsql/ -I${PREFIX}/include/postgres/ -I${PREFIX}/postgres/include/ -I${PREFIX}/include/postgresql/ -I${PREFIX}/postgresql/include/" + POSTCFLAGS="${POSTCFLAGS} -I/usr/local/pgsql/include -I/usr/local/include/pgsql/ -I/usr/local/include/postgres/ -I/usr/local/postgres/include/ -I/usr/local/include/postgresql/ -I/usr/local/postgresql/include/" + POSTCFLAGS="${POSTCFLAGS} -I/usr/pgsql/include -I/usr/include/pgsql/ -I/usr/include/postgres/ -I/usr/postgres/include/ -I/usr/include/postgresql/ -I/usr/postgresql/include/" + for ilib in ${PREFIX}/pgsql/lib ${PREFIX}/lib/pgsql ${PREFIX}/lib64/pgsql /usr/local/pgsql/lib /usr/local/lib/pgsql /usr/local/lib64/pgsql /usr/pgsql/lib /usr/lib/pgsql /usr/lib64/pgsql ${PREFIX}/postgres/lib ${PREFIX}/lib/postgres ${PREFIX}/lib64/postgres /usr/local/postgres/lib /usr/local/lib/postgres /usr/local/lib64/postgres /usr/postgres/lib /usr/lib/postgres /usr/lib64/postgres ${PREFIX}/postgresql/lib ${PREFIX}/lib/postgresql ${PREFIX}/lib64/postgresql /usr/local/postgresql/lib /usr/local/lib/postgresql /usr/local/lib64/postgresql /usr/postgresql/lib /usr/lib/postgresql /usr/lib64/postgresql + do + if [ -d ${ilib} ] ; then + POSTLIBS="${POSTLIBS} -L${ilib}" + if ! [ -z "${TURN_ACCEPT_RPATH}" ] ; then + TURN_RPATH="${TURN_RPATH} -Wl,-rpath,${ilib}" + fi + fi + done + POSTLIBS="${OSLIBS} ${POSTLIBS} -lpq" + ${CC} ${PQ_TMPCPROGC} -o ${PQ_TMPCPROGB} ${OSCFLAGS} ${DBCFLAGS} ${POSTCFLAGS} ${DBLIBS} ${POSTLIBS} ${OSLIBS} 2>>/dev/null + ER=$? + if ! [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} + ${ECHO_CMD} "POSTGRESQL DEVELOPMENT LIBRARY (libpq.a) AND/OR HEADER (libpq-fe.h)" + ${ECHO_CMD} " ARE NOT INSTALLED PROPERLY ON THIS SYSTEM." + ${ECHO_CMD} " THAT'S OK BUT THE TURN SERVER IS BUILDING WITHOUT POSTGRESQL DATABASE SUPPORT." + ${ECHO_CMD} + return 0 + else + DBCFLAGS="${DBCFLAGS} ${POSTCFLAGS}" + DBLIBS="${DBLIBS} ${POSTLIBS}" + return 1 + fi +} + +testlibmysql() { + MYSQL_CFLAGS="-I${PREFIX}/mysql/include -I${PREFIX}/include/mysql/" + MYSQL_CFLAGS="${MYSQL_CFLAGS} -I/usr/local/mysql/include -I/usr/local/include/mysql/" + MYSQL_CFLAGS="${MYSQL_CFLAGS} -I/usr/mysql/include -I/usr/include/mysql/" + for ilib in ${PREFIX}/mysql/lib ${PREFIX}/lib/mysql ${PREFIX}/lib64/mysql /usr/local/mysql/lib /usr/local/lib/mysql /usr/local/lib64/mysql /usr/mysql/lib /usr/lib/mysql /usr/lib64/mysql + do + if [ -d ${ilib} ] ; then + MYSQL_LIBS="${MYSQL_LIBS} -L${ilib}" + if ! [ -z "${TURN_ACCEPT_RPATH}" ] ; then + TURN_RPATH="${TURN_RPATH} -Wl,-rpath,${ilib}" + fi + fi + done + MYSQL_LIBS="${OSLIBS} ${MYSQL_LIBS} -lmysqlclient" + ${CC} ${MYSQL_TMPCPROGC} -o ${MYSQL_TMPCPROGB} ${OSCFLAGS} ${DBCFLAGS} ${DBLIBS} ${MYSQL_CFLAGS} ${MYSQL_LIBS} ${OSLIBS} 2>>/dev/null + ER=$? + if ! [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} + ${ECHO_CMD} "MYSQL DEVELOPMENT LIBRARY (libmysqlclient) AND/OR HEADER (mysql.h)" + ${ECHO_CMD} " ARE NOT INSTALLED PROPERLY ON THIS SYSTEM." + ${ECHO_CMD} " THAT'S OK BUT THE TURN SERVER IS BUILDING WITHOUT MYSQL DATABASE SUPPORT." + ${ECHO_CMD} + return 0 + else + DBCFLAGS="${DBCFLAGS} ${MYSQL_CFLAGS}" + DBLIBS="${DBLIBS} ${MYSQL_LIBS}" + return 1 + fi +} + +testlib() { + testlibraw l${1} +} + +pthread_testlib() { + + SYSTEM=`uname` + + if [ "${SYSTEM}" = "DragonFly" ] ; then + OSLIBS="${OSLIBS} -pthread" + fi + + ISBSD=`uname | grep -i bsd` + if ! [ -z "${ISBSD}" ] ; then + OSLIBS="${OSLIBS} -pthread" + fi + + if [ -z "${PTHREAD_LIBS}" ] ; then + ${CC} ${TH_TMPCPROGC} -o ${TH_TMPCPROGB} ${OSCFLAGS} ${OSLIBS} 2>>/dev/null + ER=$? + if [ ${ER} -eq 0 ] ; then + return 1 + else + ${CC} ${TH_TMPCPROGC} -o ${TH_TMPCPROGB} ${OSCFLAGS} ${OSLIBS} -pthread 2>>/dev/null + ER=$? + if [ ${ER} -eq 0 ] ; then + OSLIBS="${OSLIBS} -pthread" + return 1 + else + ${CC} ${TH_TMPCPROGC} -o ${TH_TMPCPROGB} ${OSCFLAGS} ${OSLIBS} -lpthread 2>>/dev/null + ER=$? + if [ ${ER} -eq 0 ] ; then + OSLIBS="${OSLIBS} -lpthread" + return 1 + fi + fi + fi + else + OSLIBS="${OSLIBS} ${PTHREAD_LIBS}" + fi + + + ${CC} ${TH_TMPCPROGC} -o ${TH_TMPCPROGB} ${OSCFLAGS} ${OSLIBS} 2>>/dev/null + ER=$? + if [ ${ER} -eq 0 ] ; then + return 1 + else + ${CC} ${TH_TMPCPROGC} -o ${TH_TMPCPROGB} ${OSCFLAGS} ${OSLIBS} -D_GNU_SOURCE 2>>/dev/null + ER=$? + if [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "Older GNU pthread library found" + OSCFLAGS="${OSCFLAGS} -D_GNU_SOURCE" + return 1 + else + ${ECHO_CMD} "Do not use pthreads" + fi + fi + + return 0 +} + +pthread_testbarriers() { + + ${ECHO_CMD} "pthread_barrier_t barrier;" >> ${TH_TMPCPROGC} + ${CC} ${TH_TMPCPROGC} -o ${TH_TMPCPROGB} ${OSCFLAGS} ${OSLIBS} 2>>/dev/null + ER=$? + if ! [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "pthread barriers not found" + OSCFLAGS="${OSCFLAGS} -DTURN_NO_THREAD_BARRIERS" + fi +} + +dtls_testlib() { + + if [ -z "${TURN_NO_DTLS}" ] ; then + ${CC} ${DTLS_TMPCPROGC} -o ${DTLS_TMPCPROGB} ${OSCFLAGS} ${OSLIBS} 2>>/dev/null + ER=$? + if [ ${ER} -eq 0 ] ; then + return 1 + else + return 0 + fi + else + return 0 + fi +} + +testdaemon() { + + ${CC} ${D_TMPCPROGC} -o ${D_TMPCPROGB} ${OSCFLAGS} ${OSLIBS} 2>>/dev/null + ER=$? + if [ ${ER} -eq 0 ] ; then + OSCFLAGS="${OSCFLAGS} -DTURN_HAS_DAEMON" + fi +} + +test_sin_len() { + TMPCADDRPROGC=src/client/ns_turn_ioaddr.c + ${CC} -c ${OSCFLAGS} -DTURN_HAS_SIN_LEN -Isrc ${TMPCADDRPROGC} -o ${TMPCADDRPROGO} 2>>/dev/null + ER=$? + if [ ${ER} -eq 0 ] ; then + OSCFLAGS="${OSCFLAGS} -DTURN_HAS_SIN_LEN" + ${ECHO_CMD} "Sockets code is fine: sin_len field present" + else + ${CC} -c ${OSCFLAGS} -Isrc ${TMPCADDRPROGC} -o ${TMPCADDRPROGO} 2>>/dev/null + ER=$? + if [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "Sockets code is fine: no sin_len field present" + else + ${ECHO_CMD} "WARNING: trial compilation failed: src/client/ns_turn_ioaddr.c" + fi + fi +} + +######################### +# Start +######################### + +cleanup + +######################### +# To be set: +######################### + +if [ -z "${ECHO_CMD}" ] ; then + ECHO_CMD=echo +fi + +if [ -z "${PORTNAME}" ] ; then + PORTNAME=turnserver +fi + +######################### + +# Installation directory options + +ac_prev= +ac_dashdash= +for ac_option +do + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval $ac_prev=\$ac_option + ac_prev= + continue + fi + + case $ac_option in + *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; + *=) ac_optarg= ;; + *) ac_optarg=yes ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case $ac_dashdash$ac_option in + --) + ac_dashdash=yes ;; + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=BINDIR ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + BINDIR=$ac_optarg ;; + + -datadir | --datadir | --datadi | --datad | -schemadir | --schemadir) + ac_prev=SCHEMADIR ;; + -datadir=* | --datadir=* | --datadi=* | --datad=* | -schemadir=* | --schemadir=*) + SCHEMADIR=$ac_optarg ;; + + -docdir | --docdir | --docdi | --doc | --do | -docsdir | --docsdir | --docsdi | --docs) + ac_prev=DOCDIR ;; + -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=* | -docsdir=* | --docsdir=* | --docsdi=* | --docs=*) + DOCSDIR=$ac_optarg ;; + + -examplesdir | --examplesdir | -examples | --examples) + ac_prev=EXAMPLESDIR ;; + -examplesdir=* | --examplesdir=* | -examples=* | --examples=*) + EXAMPLESDIR=$ac_optarg ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=INCLUDEDIR ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + INCLUDEDIR=$ac_optarg ;; + + -turnincludedir | --turnincludedir) + ac_prev=TURNINCLUDEDIR ;; + -turnincludedir=* | --turnincludedir=*) + TURNINCLUDEDIR=$ac_optarg ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=LIBDIR ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + LIBDIR=$ac_optarg ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m | -manprefix | --manprefix) + ac_prev=MAXPREFIX ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=* | -manprefix=* | --manprefix=*) + MANPREFIX=$ac_optarg ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=PREFIX ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + PREFIX=$ac_optarg ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy | -confdir | --confdir) + ac_prev=CONFDIR ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=* | -confdir=* | --confdir=*) + CONFDIR=$ac_optarg ;; + + -disable-rpath | --disable-rpath) + TURN_DISABLE_RPATH=1 ;; + + esac + +done + +if test -n "$ac_prev"; then + ac_option=--`echo $ac_prev | sed 's/_/-/g'` + as_fn_error $? "missing argument to $ac_option" +fi + +############################################# + +if [ -z "${PREFIX}" ] ; then + + if [ -z "${prefix}" ] ; then + + SYSTEM=`uname` + + if [ "${SYSTEM}" = "NetBSD" ] ; then + # A little tough guy + PREFIX=/usr/pkg + elif [ "${SYSTEM}" = "SunOS" ] ; then + # A fat guy + PREFIX=/usr + else + # An ordinary person + PREFIX=/usr/local + fi + else + PREFIX=${prefix} + fi +fi + +if [ -z "${BINDIR}" ] ; then + if [ -z "${bindir}" ] ; then + BINDIR=${PREFIX}/bin + else + BINDIR=${bindir} + fi +fi + +if [ -z "${CONFDIR}" ] ; then + if [ -z "${confdir}" ] ; then + CONFDIR=${PREFIX}/etc + else + CONFDIR=${confdir} + fi +fi + +if [ -z "${MANPREFIX}" ] ; then + if [ -z "${manprefix}" ] ; then + MANPREFIX=${PREFIX} + else + MANPREFIX=${manprefix} + fi +fi + +if [ -z "${EXAMPLESDIR}" ] ; then + if [ -z "${examplesdir}" ] ; then + EXAMPLESDIR=${PREFIX}/share/examples/${PORTNAME} + else + EXAMPLESDIR=${examplesdir} + fi +fi + +if [ -z "${DOCSDIR}" ] ; then + if [ -z "${docsdir}" ] ; then + DOCSDIR=${PREFIX}/share/doc/${PORTNAME} + else + DOCSDIR=${docsdir} + fi +fi + +if [ -z "${LIBDIR}" ] ; then + if [ -z "${libdir}" ] ; then + LIBDIR=${PREFIX}/lib + else + LIBDIR=${libdir} + fi +fi + +if [ -z "${SCHEMADIR}" ] ; then + if [ -z "${schemadir}" ] ; then + SCHEMADIR=${PREFIX}/share/${PORTNAME} + else + SCHEMADIR=${schemadir} + fi +fi + +if [ -z "${INCLUDEDIR}" ] ; then + if [ -z "${includedir}" ] ; then + INCLUDEDIR=${PREFIX}/include + else + INCLUDEDIR=${includedir} + fi +fi + +if [ -z "${TURNINCLUDEDIR}" ] ; then + if [ -z "${turnincludedir}" ] ; then + TURNINCLUDEDIR=${INCLUDEDIR}/turn + else + TURNINCLUDEDIR=${turnincludedir} + fi +fi + +############################################### + +if [ -z "${ARCHIVERCMD}" ] ; then + ARCHIVERCMD="ar -r" +fi + +if [ -z "${MORECMD}" ]; then + type more 2>>/dev/null + ER=$? + if [ ${ER} -eq 0 ] ; then + MORECMD="more" + else + type less 2>>/dev/null + ER=$? + if [ ${ER} -eq 0 ] ; then + MORECMD="less" + else + MORECMD="cat" + fi + fi +fi + +OSCFLAGS="-I${INCLUDEDIR} -I${PREFIX}/include/ -I/usr/local/include ${CFLAGS}" +OSLIBS="${LDFLAGS}" +for ilib in ${PREFIX}/lib/event2/ ${PREFIX}/lib/ /usr/local/lib/event2/ /usr/local/lib/ ${PREFIX}/lib64/event2/ ${PREFIX}/lib64/ /usr/local/lib64/event2/ /usr/local/lib64/ +do + if [ -d ${ilib} ] ; then + OSLIBS="${OSLIBS} -L${ilib}" + TURN_RPATH="${TURN_RPATH} -Wl,-rpath,${ilib}" + fi +done + +SYSTEM=`uname` +if [ "${SYSTEM}" = "NetBSD" ] ; then + OSCFLAGS="${OSCFLAGS} -I/usr/pkg/include" + OSLIBS="-L/usr/pkg/lib ${OSLIBS}" + if ! [ -z "${TURN_ACCEPT_RPATH}" ] ; then + TURN_RPATH="${TURN_RPATH} -Wl,-rpath,/usr/pkg/lib" + fi +fi + +########################### +# Install shell commands +########################### + +type ginstall 2>>/dev/null +ER=$? +if [ ${ER} -eq 0 ] ; then + INSTALL_PROGRAM="ginstall" + INSTALL_MAN="ginstall" + INSTALL_SCRIPT="ginstall" + INSTALL_SHARED_LIB="ginstall" + INSTALL_STATIC_LIB="ginstall" + INSTALL_DATA="ginstall" + MKDIR="ginstall -d" +else + type install 2>>/dev/null + ER=$? + if [ ${ER} -eq 0 ] ; then + INSTALL_PROGRAM="install" + INSTALL_MAN="install" + INSTALL_SCRIPT="install" + INSTALL_SHARED_LIB="install" + INSTALL_STATIC_LIB="install" + INSTALL_DATA="install" + MKDIR="install -d" + else + INSTALL_PROGRAM="cp -pf" + INSTALL_MAN="cp -pf" + INSTALL_SCRIPT="cp -pf" + INSTALL_SHARED_LIB="cp -pf" + INSTALL_STATIC_LIB="cp -pf" + INSTALL_DATA="cp -pf" + MKDIR="mkdir -p" + fi +fi + +type pkill 2>>/dev/null +ER=$? +if [ ${ER} -eq 0 ] ; then + PKILL_PROGRAM="pkill" +else + PKILL_PROGRAM="${ECHO_CMD}" +fi + +INSTALL_DIR="cp -rpf" +MKBUILDDIR="mkdir -p" +RMCMD="rm -rf" + +############################# +# Adjustments for Solaris +############################# + +SYSTEM=`uname` + +if [ "${SYSTEM}" = "SunOS" ] ; then +# Solaris ? is this you ?! + OSCFLAGS="${OSCFLAGS} -D__EXTENSIONS__ -D_XOPEN_SOURCE=500" +fi + +######################### +# Temporary DIR location: +######################### + +TMPDIR="." + +if [ -d /var/tmp ] ; then + TMPDIR="/var/tmp" +elif [ -d /tmp ] ; then + TMPDIR=/tmp +fi + +${ECHO_CMD} Use TMP dir ${TMPDIR} + +######################### +# Basic C test programs +######################### + +TMPCADDRPROGO=${TMPDIR}/__test__ccomp_addr_$$.o + +TMPCPROG=__test__ccomp__$$ +TMPCPROGC=${TMPDIR}/${TMPCPROG}.c +TMPCPROGB=${TMPDIR}/${TMPCPROG} + +cat > ${TMPCPROGC} < +int main(int argc, char** argv) { + return (int)(argv[argc][0]); +} +! + +TH_TMPCPROG=__test__ccomp__pthread__$$ +TH_TMPCPROGC=${TMPDIR}/${TH_TMPCPROG}.c +TH_TMPCPROGB=${TMPDIR}/${TH_TMPCPROG} + +cat > ${TH_TMPCPROGC} < +#include +int main(int argc, char** argv) { + pthread_mutexattr_settype(0,PTHREAD_MUTEX_RECURSIVE); + return (int)pthread_create(0,0,0,0)+(int)(argv[argc][0]); +} + +! + +DTLS_TMPCPROG=__test__ccomp__dtls__$$ +DTLS_TMPCPROGC=${TMPDIR}/${DTLS_TMPCPROG}.c +DTLS_TMPCPROGB=${TMPDIR}/${DTLS_TMPCPROG} + +cat > ${DTLS_TMPCPROGC} < +#include +#include +int main(int argc, char** argv) { + return (((int)(BIO_CTRL_DGRAM_QUERY_MTU)) + argc + (int)(argv[argc][0]) + DTLSv1_listen(NULL,NULL)); +} +! + +D_TMPCPROG=__test__ccomp__daemon__$$ +D_TMPCPROGC=${TMPDIR}/${D_TMPCPROG}.c +D_TMPCPROGB=${TMPDIR}/${D_TMPCPROG} + +cat > ${D_TMPCPROGC} < +#include +int main(int argc, char** argv) { + return (int)daemon(0,0)+(int)(argv[argc][0]); +} +! + +E_TMPCPROG=__test__ccomp__libevent2__$$ +E_TMPCPROGC=${TMPDIR}/${E_TMPCPROG}.c +E_TMPCPROGO=${TMPDIR}/${E_TMPCPROG}.o + +cat > ${E_TMPCPROGC} < +#include +int main(int argc, char** argv) { + return (int)(argv[argc][0]); +} +! + +HR_TMPCPROG=__test__ccomp__hiredis__$$ +HR_TMPCPROGC=${TMPDIR}/${HR_TMPCPROG}.c +HR_TMPCPROGB=${TMPDIR}/${HR_TMPCPROG} + +cat > ${HR_TMPCPROGC} < +#include +#include +int main(int argc, char** argv) { + redisAsyncHandleRead(NULL); + return (int)(argv[argc][0]); +} +! + +PQ_TMPCPROG=__test__ccomp__libpq__$$ +PQ_TMPCPROGC=${TMPDIR}/${PQ_TMPCPROG}.c +PQ_TMPCPROGB=${TMPDIR}/${PQ_TMPCPROG} + +cat > ${PQ_TMPCPROGC} < +#include +int main(int argc, char** argv) { + return (argc+(PQprotocolVersion(NULL))+(int)(argv[0][0])); +} +! + +MYSQL_TMPCPROG=__test__ccomp__libmysql__$$ +MYSQL_TMPCPROGC=${TMPDIR}/${MYSQL_TMPCPROG}.c +MYSQL_TMPCPROGB=${TMPDIR}/${MYSQL_TMPCPROG} + +cat > ${MYSQL_TMPCPROGC} < +#include +int main(int argc, char** argv) { + return (argc+ + (int)(mysql_real_connect(NULL, NULL, NULL, NULL, NULL, 0, NULL, 0)!=0)+ + (int)(argv[0][0])); +} +! + +########################## +# What is our compiler ? +########################## + +if [ -z "${CC}" ] ; then + CC=cc + ${CC} ${TMPCPROGC} ${OSCFLAGS} -o ${TMPCPROGB} 2>>/dev/null + ER=$? + if ! [ ${ER} -eq 0 ] ; then + CC=gcc + ${CC} ${TMPCPROGC} ${OSCFLAGS} -o ${TMPCPROGB} 2>>/dev/null + ER=$? + if ! [ ${ER} -eq 0 ] ; then + CC=clang + ${CC} ${TMPCPROGC} ${OSCFLAGS} -o ${TMPCPROGB} 2>>/dev/null + ER=$? + if ! [ ${ER} -eq 0 ] ; then + CC=unknown + fi + fi + fi +fi + +${ECHO_CMD} "Compiler: ${CC}" + +if [ -z "${TURN_ACCEPT_RPATH}" ] ; then + ${CC} ${TMPCPROGC} ${OSCFLAGS} -o ${TMPCPROGB} -Wl,-rpath,/usr/lib 2>>/dev/null + ER=$? + if [ ${ER} -eq 0 ] ; then + TURN_ACCEPT_RPATH=1 + fi +fi + +${CC} ${TMPCPROGC} ${OSCFLAGS} -o ${TMPCPROGB} 2>>/dev/null +ER=$? +if ! [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "ERROR: cannot use compiler ${CC} properly" + cleanup + exit +fi + +########################### +# Check if we can use GNU +# or Clang compiler flags +########################### + +GNUOSCFLAGS="-g ${GNUOSCFLAGS}" +GNUOSCFLAGS="${GNUOSCFLAGS} -Wall -Wno-deprecated-declarations -Wextra -Wformat-security -Wnested-externs -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wcast-qual" +GNUOSCFLAGS="${GNUOSCFLAGS} -Wno-write-strings" + +${CC} -Werror ${GNUOSCFLAGS} ${TMPCPROGC} ${OSCFLAGS} -o ${TMPCPROGB} 2>>/dev/null +ER=$? +if ! [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "Hm..." + ${CC} -Wall ${TMPCPROGC} ${OSCFLAGS} -o ${TMPCPROGB} 2>>/dev/null + ER=$? + if ! [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "Not an ordinary GNU or Clang compiler" + else + ${ECHO_CMD} "g++ or something..." + GNUOSCFLAGS="-g -Wall -Wno-deprecated-declarations -Wextra -Wformat-security -Wpointer-arith -Wcast-qual -Wno-write-strings" + ${CC} -Werror ${GNUOSCFLAGS} ${TMPCPROGC} ${OSCFLAGS} -o ${TMPCPROGB} 2>>/dev/null + ER=$? + if ! [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "Not an ordinary g++ compiler" + GNUOSCFLAGS="-x c++ -g -Wall -Wno-deprecated-declarations -Wextra -Wformat-security -Wpointer-arith -Wcast-qual -Wno-write-strings" + ${CC} -Werror ${GNUOSCFLAGS} ${TMPCPROGC} ${OSCFLAGS} -o ${TMPCPROGB} 2>>/dev/null + ER=$? + if ! [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "Not an ordinary c++ compiler" + else + ${ECHO_CMD} "Clang++ compiler ?" + OSCFLAGS="${GNUOSCFLAGS} ${OSCFLAGS}" + fi + else + OSCFLAGS="${GNUOSCFLAGS} ${OSCFLAGS}" + fi + fi +else + OSCFLAGS="${GNUOSCFLAGS} ${OSCFLAGS}" +fi + +########################### +# Test some general-purpose +# libraries +########################### + +testlib socket +testlib nsl +testlib dl +testlib rt +testlib wldap32 +ER=$? +if ! [ ${ER} -eq 0 ] ; then + echo "CYGWIN ?" +fi +testlib wldap64 +testlib intl + +########################### +# Test sockets compilation +########################### + +test_sin_len + +########################### +# Can we use multi-threading ? +########################### + +pthread_testlib +ER=$? +if [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "ERROR: Cannot find pthread library functions." + exit +fi + +if [ -z ${TURN_NO_THREAD_BARRIERS} ] ; then + pthread_testbarriers +else + TURN_NO_THREAD_BARRIERS="-DTURN_NO_THREAD_BARRIERS" +fi + +if [ -z ${TURN_IP_RECVERR} ] ; then + ${ECHO_CMD} "Ignore IP_RECVERR" +else + ${ECHO_CMD} "Use IP_RECVERR" + TURN_IP_RECVERR="-DTURN_IP_RECVERR" + OSCFLAGS="${OSCFLAGS} ${TURN_IP_RECVERR}" +fi + +########################### +# Can we use daemon ? +########################### + +testdaemon + +########################### +# Test OpenSSL installation +########################### + +testlib crypto +ER=$? +if ! [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "Crypto SSL lib found." +else + ${ECHO_CMD} "ERROR: OpenSSL Crypto development libraries are not installed properly in required location." + ${ECHO_CMD} "Abort." + cleanup + exit +fi + +testlib ssl +ER=$? +if ! [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "SSL lib found." +else + ${ECHO_CMD} "ERROR: OpenSSL development libraries are not installed properly in required location." + ${ECHO_CMD} "Abort." + cleanup + exit +fi + +########################### +# Can we use DTLS ? +########################### + +if [ -z ${TURN_NO_DTLS} ] ; then + +dtls_testlib +ER=$? +if [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "WARNING: Cannot find DTLS support." + ${ECHO_CMD} "Turning DTLS off." + TURN_NO_DTLS="-DTURN_NO_DTLS" +fi + +else + TURN_NO_DTLS="-DTURN_NO_DTLS" +fi + +########################### +# Test Libevent2 setup +########################### +testlibevent2_comp +ER=$? +if ! [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "Libevent2 development found." +else + ${ECHO_CMD} "ERROR: Libevent2 development libraries are not installed properly in required location." + ${ECHO_CMD} "ERROR: may be you have just too old libevent tool - then you have to upgrade it." + ${ECHO_CMD} "See the INSTALL file." + ${ECHO_CMD} "Abort." + cleanup + exit +fi + +testlib event_core +ER=$? +if ! [ ${ER} -eq 0 ] ; then + testlib event_extra + ${ECHO_CMD} "Libevent2 runtime found." +else + testlib event + ER=$? + if ! [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "Libevent2 runtime found." + else + ${ECHO_CMD} "ERROR: Libevent2 runtime libraries are not installed properly in required location." + ${ECHO_CMD} "See the INSTALL file." + ${ECHO_CMD} "Abort." + cleanup + exit + fi +fi + +if [ -z "${TURN_NO_TLS}" ] ; then + + testlib event_openssl + ER=$? + if ! [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "Libevent2 openssl found." + else + ${ECHO_CMD} "ERROR: Libevent2 development libraries are not compiled with OpenSSL support." + ${ECHO_CMD} "TLS will be disabled." + TURN_NO_TLS="-DTURN_NO_TLS" + fi + +else + TURN_NO_TLS="-DTURN_NO_TLS" +fi + +testlib event_pthreads +ER=$? +if ! [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "Libevent2 pthreads found." +else + ${ECHO_CMD} "ERROR: Libevent2 development libraries are not compiled with threads support." + exit +fi + +########################### +# Test PostgreSQL +########################### + +if [ -z "${TURN_NO_PQ}" ] ; then + + testlibpq + ER=$? + if ! [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "PostgreSQL found." + else + TURN_NO_PQ="-DTURN_NO_PQ" + fi +else + TURN_NO_PQ="-DTURN_NO_PQ" +fi + +########################### +# Test MySQL +########################### + +if [ -z "${TURN_NO_MYSQL}" ] ; then + + testlibmysql + ER=$? + if ! [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "MySQL found." + else + TURN_NO_MYSQL="-DTURN_NO_MYSQL" + fi +else + TURN_NO_MYSQL="-DTURN_NO_MYSQL" +fi + +########################### +# Test Redis +########################### + +if [ -z "${TURN_NO_HIREDIS}" ] ; then + + testhiredis + + ER=$? + if ! [ ${ER} -eq 0 ] ; then + ${ECHO_CMD} "Hiredis found." + else + TURN_NO_HIREDIS="-DTURN_NO_HIREDIS" + fi + +else + TURN_NO_HIREDIS="-DTURN_NO_HIREDIS" + +fi + +############################### +# LDCONFIG +############################### + +if [ -z "${LDCONFIG}" ] ; then + ISBSD=`uname | grep -i bsd` + if [ -z "${ISBSD}" ] ; then + ISLINUX=`uname | grep -i linux` + if [ -z "${ISLINUX}" ] ; then + SYSTEM=`uname` + if [ "${SYSTEM}" = "SunOS" ] ; then + LDCONFIG="crle -u -l" + else + LDCONFIG=${ECHO_CMD} + fi + else + LDCONFIG="ldconfig -n" + fi + else + LDCONFIG="ldconfig -m" + fi +fi + + +############################### +# So, what we have now: +############################### + +OSCFLAGS="${OSCFLAGS} ${TURN_NO_THREAD_BARRIERS} ${TURN_NO_DTLS} ${TURN_NO_TLS} -DINSTALL_PREFIX=${PREFIX}" + +if ! [ -z "${TURN_ACCEPT_RPATH}" ] ; then + if [ -z "${TURN_DISABLE_RPATH}" ] ; then + TURN_RPATH="${TURN_RPATH} -Wl,-rpath,/usr/local/lib" + OSLIBS="${OSLIBS} ${TURN_RPATH}" + fi +fi + +${ECHO_CMD} PREFIX="${PREFIX}" OSLIBS="${OSLIBS}" DBLIBS="${DBLIBS}" OSCFLAGS="${OSCFLAGS}" DBCFLAGS="${DBCFLAGS}" $@ + +############################### +# Make make: +############################### + +${ECHO_CMD} "#################################" > Makefile +${ECHO_CMD} "# Generated by configure script #" >> Makefile +${ECHO_CMD} "#################################" >> Makefile +${ECHO_CMD} "ECHO_CMD = ${ECHO_CMD}" >> Makefile +${ECHO_CMD} "CC = ${CC}" >> Makefile +${ECHO_CMD} "LDFLAGS += ${OSLIBS}" >> Makefile +${ECHO_CMD} "DBLIBS += ${DBLIBS}" >> Makefile +${ECHO_CMD} "CFLAGS += ${OSCFLAGS}" >> Makefile +${ECHO_CMD} "CPPFLAGS = ${CPPFLAGS}" >> Makefile +${ECHO_CMD} "DBCFLAGS += ${DBCFLAGS} ${TURN_NO_PQ} ${TURN_NO_MYSQL} ${TURN_NO_HIREDIS}" >> Makefile +${ECHO_CMD} "#" >> Makefile +${ECHO_CMD} "PORTNAME = ${PORTNAME}" >> Makefile +${ECHO_CMD} "PREFIX = ${PREFIX}" >> Makefile +${ECHO_CMD} "prefix = ${PREFIX}" >> Makefile +${ECHO_CMD} "BINDIR = ${BINDIR}" >> Makefile +${ECHO_CMD} "bindir = ${BINDIR}" >> Makefile +${ECHO_CMD} "CONFDIR = ${CONFDIR}" >> Makefile +${ECHO_CMD} "confdir = ${CONFDIR}" >> Makefile +${ECHO_CMD} "MANPREFIX = ${MANPREFIX}" >> Makefile +${ECHO_CMD} "manprefix = ${MANPREFIX}" >> Makefile +${ECHO_CMD} "EXAMPLESDIR = ${EXAMPLESDIR}" >> Makefile +${ECHO_CMD} "examplesdir = ${EXAMPLESDIR}" >> Makefile +${ECHO_CMD} "DOCSDIR = ${DOCSDIR}" >> Makefile +${ECHO_CMD} "docsdir = ${DOCSDIR}" >> Makefile +${ECHO_CMD} "LIBDIR = ${LIBDIR}" >> Makefile +${ECHO_CMD} "libdir = ${LIBDIR}" >> Makefile +${ECHO_CMD} "SCHEMADIR = ${SCHEMADIR}" >> Makefile +${ECHO_CMD} "schemadir = ${SCHEMADIR}" >> Makefile +${ECHO_CMD} "INCLUDEDIR = ${INCLUDEDIR}" >> Makefile +${ECHO_CMD} "includedir = ${INCLUDEDIR}" >> Makefile +${ECHO_CMD} "TURNINCLUDEDIR = ${TURNINCLUDEDIR}" >> Makefile +${ECHO_CMD} "turnincludedir = ${TURNINCLUDEDIR}" >> Makefile +${ECHO_CMD} "#" >> Makefile +${ECHO_CMD} "ARCHIVERCMD = ${ARCHIVERCMD}" >> Makefile +${ECHO_CMD} "MKDIR = ${MKDIR}" >> Makefile +${ECHO_CMD} "INSTALL_PROGRAM = ${INSTALL_PROGRAM}" >> Makefile +${ECHO_CMD} "PKILL_PROGRAM = ${PKILL_PROGRAM}" >> Makefile +${ECHO_CMD} "INSTALL_MAN = ${INSTALL_MAN}" >> Makefile +${ECHO_CMD} "INSTALL_SCRIPT = ${INSTALL_SCRIPT}" >> Makefile +${ECHO_CMD} "INSTALL_SHARED_LIB = ${INSTALL_SHARED_LIB}" >> Makefile +${ECHO_CMD} "INSTALL_STATIC_LIB = ${INSTALL_STATIC_LIB}" >> Makefile +${ECHO_CMD} "INSTALL_DATA = ${INSTALL_DATA}" >> Makefile +${ECHO_CMD} "INSTALL_DIR = ${INSTALL_DIR}" >> Makefile +${ECHO_CMD} "MKBUILDDIR = ${MKBUILDDIR}" >> Makefile +${ECHO_CMD} "RMCMD = ${RMCMD}" >> Makefile +${ECHO_CMD} "MORECMD = ${MORECMD}" >> Makefile +${ECHO_CMD} "LDCONFIG=${LDCONFIG}" >> Makefile +${ECHO_CMD} "################################" >> Makefile +${ECHO_CMD} "" >> Makefile +cat Makefile.in >> Makefile + +############################### +# End: +############################### + +cleanup + + diff --git a/examples/etc/turn_client_cert.pem b/examples/etc/turn_client_cert.pem new file mode 100644 index 0000000..2e00e7e --- /dev/null +++ b/examples/etc/turn_client_cert.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDzjCCArYCCQD3YHhln4EqhDANBgkqhkiG9w0BAQUFADCBpzELMAkGA1UEBhMC +VVMxCzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxXYWxudXQgQ3JlZWsxKzApBgNVBAoT +IlJGQzU3NjYgVFVSTiBTZXJ2ZXIgcHVibGljIHByb2plY3QxFDASBgNVBAsTC2Rl +dmVsb3BtZW50MQ0wCwYDVQQDEwRPbGVnMSIwIAYJKoZIhvcNAQkBFhNtb20wNDAy +NjdAZ21haWwuY29tMCAXDTEyMTEyNzAwNDEwNVoYDzIxMTIxMTAzMDA0MTA1WjCB +pzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxXYWxudXQgQ3Jl +ZWsxKzApBgNVBAoTIlJGQzU3NjYgVFVSTiBTZXJ2ZXIgcHVibGljIHByb2plY3Qx +FDASBgNVBAsTC2RldmVsb3BtZW50MQ0wCwYDVQQDEwRPbGVnMSIwIAYJKoZIhvcN +AQkBFhNtb20wNDAyNjdAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEA3huHvPYyvNZBK91bP3O1dBdOj93YQ3812BTcRMjEYnvSyyEosxFd +dEnILgDiFK//pFnDtwm7FxOCtVwRQ0+8qGTH4vH0EIpKTBsaafKH3L9CYe40pwcm +BJHvclOa4vl2Ghi09+M0UEHdokkM77K9rpXx7aZILoICkqnoAuBe0TY8D5PBXinM +gtk7HlrvANxSmPHAAaGQ5t/+jfTWVH1UYCpogTgCKYPbNi+joKu6oEz+qRKAqDYd +FY6/Qpiv7reYiNiVhM7HGNY27FkKDJDBhsmZRmtTIEdYFfcWPZvv69L7Rf1skOXF +Vm5/to3HArJJF+lz6YGj0C3pE6dZt6sUmQIDAQABMA0GCSqGSIb3DQEBBQUAA4IB +AQAhXgGdXXf0dMPdkfl4jv4dqFNSmax6wmeNc+oJC9qIFVDLsdAaAWXZ+pZHYIMR +UN8mQobsIZdfPQ0gs8CgUwrKziAjA92y2Q/I7vsg83qRLhysGC5etYMD/wlySDDS +AJKraevDPTEdmfNstCblubNG2PIeqV1isWtPMqB2dMsCeyzJXVyfD0QcABzFv4Fs +MMy7JI7MsctNh1tjV/0TsddDMeMLs22rix5fS8MZ6uunFzIuJ0MshFNehXFuvz0B +uNmn0k7djUm3h+2Avs3YGCo/8GtqHapc/lva/9gT+iEW0e7i0Ru5Jhar66VMzJqv ++wEhQafC77d3vWHtXQU8dYmM +-----END CERTIFICATE----- diff --git a/examples/etc/turn_client_pkey.pem b/examples/etc/turn_client_pkey.pem new file mode 100644 index 0000000..11f9d2a --- /dev/null +++ b/examples/etc/turn_client_pkey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA3huHvPYyvNZBK91bP3O1dBdOj93YQ3812BTcRMjEYnvSyyEo +sxFddEnILgDiFK//pFnDtwm7FxOCtVwRQ0+8qGTH4vH0EIpKTBsaafKH3L9CYe40 +pwcmBJHvclOa4vl2Ghi09+M0UEHdokkM77K9rpXx7aZILoICkqnoAuBe0TY8D5PB +XinMgtk7HlrvANxSmPHAAaGQ5t/+jfTWVH1UYCpogTgCKYPbNi+joKu6oEz+qRKA +qDYdFY6/Qpiv7reYiNiVhM7HGNY27FkKDJDBhsmZRmtTIEdYFfcWPZvv69L7Rf1s +kOXFVm5/to3HArJJF+lz6YGj0C3pE6dZt6sUmQIDAQABAoIBAH5ITN8FZEe10gws +qUrkcRD2h3aI/gMyetzGz45UUERmfq17xvY5M1eA884kNmbowoMhfoO9hqBSOYkA +Ndh9p5he5L+GLeyRlDi9WEFQ4iqCnC2uEEW/bMBAcVIhcvkGOT4ROiOPDRlsuaUh +v7cxe2OeYZVra7L1vJzC+eVYyNBN5CgK8w08MPEkupQS9+Jvr0QWCikRz187cG45 +EiDMrBKyJNE9lY6u4P8gJ+/NgaASWP/D3kbsjiQ2OwSGLrwDAvWC7Bx2GK3/0goA +btp7YGaWvp+mE5V91cOW+PfweC5Do4MjOr4ToNkczW0AxKE5o94yo56h+II5bX6N +z65VvtkCgYEA/Sq/3S2yup/Oodzj003KG4skWYFrj7KXeXgm7RZcpNwkd8JaFXJ/ +Cwl7/3bkRv6RHLmXX/2hcNWlxq3u6Efs1EjtycdArU68kO01vLdExJYIzHKmHikV +n+T4hukxGDzObxn3lH1KcOodh/x572Uufn79dewoZCPzH8t/jiMOWGcCgYEA4JfN +66Kq/oDookqenM9Ij5l6zeeNwzMjIlkU2eG0DAH0KdsBN/hTGGGRQVBk03YREQmK +crEhGAZxzfrX5fK11UVG3C2pqAtrVe6FuD32vFUpP1MO0ftSA889NoEwGdNZV4pV +Mk0+6xVCNOatj2inMXlQq5s68WfCzkiWD7uLCv8CgYBcwuYsF4tuYBGpMzNzAAS2 +1OPLu+T6cPiZdFHm+xOVAGiITPkO9LXiCGabsydvb+UhvkrdzCP0IQQt6RsplvkK +y3H9RfnHxprHC3NuI0SaN1Mf/j4pvOoEfTQm0pi/hcAp6zzQ9ptpBg8t/W98LPm9 +NbCPHamrD5UMqFajcOrXrwKBgD8D2M8IcRm/aYY/kYlFz4Ia+g3Trj7alj0I6YTI +gw/rbGph/FGL5ySsG2lL+T4rnlY9aw8LC9IF3OCCRRlLpCEWsu8MENIJgjA2IGa1 +XAkzi8MstrfL4BMZjn9AeBKG7kZVldnrOoATEuRs5L2cC20iMLQ1dbBOAKaITzJS +2IxZAoGBAKqwr/uennxJrnMtpjLBgcphoU3aXJZvzzDqlOaqzJp6Xmbese4sDEe0 +hvVHreigDzOnGnqL/vSjTDWaLqS/O1iE7p+UrGIkZj/Zl6Jk54OX6AHmWE2LhdlU +FYgIQKX7fuocpF1Dpe7xEeVwvdp+UqbDzHQg1CWGe1cBPYDYIkSH +-----END RSA PRIVATE KEY----- diff --git a/examples/etc/turn_server_cert.pem b/examples/etc/turn_server_cert.pem new file mode 100644 index 0000000..99b3f01 --- /dev/null +++ b/examples/etc/turn_server_cert.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDsDCCApgCCQCmgrJCiQlGOTANBgkqhkiG9w0BAQUFADCBmDELMAkGA1UEBhMC +VVMxCzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxXYWxudXQgQ3JlZWsxHDAaBgNVBAoT +E1RVUk4gU2VydmVyIHByb2plY3QxFDASBgNVBAsTC0RldmVsb3BtZW50MQ0wCwYD +VQQDEwRPbGVnMSIwIAYJKoZIhvcNAQkBFhNtb20wNDAyNjdAZ21haWwuY29tMCAX +DTEyMTEyNTA4MjAxNloYDzIxMTIxMTAxMDgyMDE2WjCBmDELMAkGA1UEBhMCVVMx +CzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxXYWxudXQgQ3JlZWsxHDAaBgNVBAoTE1RV +Uk4gU2VydmVyIHByb2plY3QxFDASBgNVBAsTC0RldmVsb3BtZW50MQ0wCwYDVQQD +EwRPbGVnMSIwIAYJKoZIhvcNAQkBFhNtb20wNDAyNjdAZ21haWwuY29tMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv6bYkERhZ43RjW4EuqCaTq5g+D+l +JI/GwlVzdzQ3+F4clMQDR1kp1nX+9AvwjCXz3AYwY1H9CqjmjGM4R9uNJJseK/aJ +d2DUFADkF+7I674XwX8U2Fy5on9jqWq3jdbb8eg/awcTBdrNLWNPquwfS2KVdooj +9yPkqnO0c3ko1/OzIQCcs09O3l/MPt+aOsHk3B9l79ZRs3zWkylI+we0Fnc+7tZE +psCztA+KCCoiJf7NenOvVhdKg7D1AXuzJ/P/Euvc3+CIiS9HI4pWLopY1k+HydLe +IcopqSbg9CRIKe1HOL8YTvCm2ZoTqgijwWUlGtwEDf2xxUQX/TLYiW8JFQIDAQAB +MA0GCSqGSIb3DQEBBQUAA4IBAQATbrBOLV4e8Qmsby9+srxXsdbNc60PmDZ4WiZ1 +IElfWmzM7wGXm9sJg1PX/7T24R1tbwZGLIhZnkhecG372GChULZJ9Pdjh0Ab2nK5 +LRKHXTpjp/xOJvx0JMCIIyRnGZT1nABPOk8uEjNW8PaU6yhQ4f5nKaSOgYGRCln6 +dcy5vylCsyD9Q7GXs0KOC38XD+Ycv6VLX4zKJ2Yum50Wt643nLjG9RlGT3FXWJ1K +HUbPC5TO6bcYLdiTjaYr+X8xC/x6h/Ngdo/16w7fRmQQ4uS+TVXrg8ITmI71KX/I +m7C9jbsubwzrhW84oZXYf+o/0ATtEAhiVLnHifKCCYikqfVj +-----END CERTIFICATE----- diff --git a/examples/etc/turn_server_pkey.pem b/examples/etc/turn_server_pkey.pem new file mode 100644 index 0000000..ada07d6 --- /dev/null +++ b/examples/etc/turn_server_pkey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAv6bYkERhZ43RjW4EuqCaTq5g+D+lJI/GwlVzdzQ3+F4clMQD +R1kp1nX+9AvwjCXz3AYwY1H9CqjmjGM4R9uNJJseK/aJd2DUFADkF+7I674XwX8U +2Fy5on9jqWq3jdbb8eg/awcTBdrNLWNPquwfS2KVdooj9yPkqnO0c3ko1/OzIQCc +s09O3l/MPt+aOsHk3B9l79ZRs3zWkylI+we0Fnc+7tZEpsCztA+KCCoiJf7NenOv +VhdKg7D1AXuzJ/P/Euvc3+CIiS9HI4pWLopY1k+HydLeIcopqSbg9CRIKe1HOL8Y +TvCm2ZoTqgijwWUlGtwEDf2xxUQX/TLYiW8JFQIDAQABAoIBADUPHCXUyKLCwKFH +NEf27sGZxX71H+NfaseioLT/3/8DDyagncfDB7I4OL2YEKC8YScpD3xv1n59BFcZ +oRtDzW+1AkVpm+VRCWYAWSXHFhkuJ6WKaVr9UOeMHStqQCcktP/kLKqU6s9UJDnM +pOHNPVzBjl+jHxHs/gGyxuKxSH2Anwkrzpiv5j0obKFnw3QtAqeZRs1NlvPtYt2S +eihZWr8r8LqylPk9ga9MYmO79Yr+EPVaqd6bmz4MpZJ4/7LEjx03Q6azdMCPhFNY +cYzPIDZFEj81Zj/tqA2MU/uTTUUrcXint4dHRJs34m5N68PV1Y1XhhH6FG0+X711 +ZymudoECgYEA/ChS5zmmOoLoaq2441+PzQbDP45qR6+G4slHwC8RDZhsYw0hQnp9 +n44Qagpt74J4FjxT20BdE714DZP32IqagUwatWRQ+z3UoGafkJSNc5JSEogwZ65C +nC8RI1pPHLEvE8IzBJiqUA1kbMOMfTYW694wdN9JVZang05/AXaJzm8CgYEAwpJ8 +nJRR9JFweHRrRgnrVk0Qi+ABbN9T/nhPXYab2vjBfeBOTA1Mob0M3zMJDCnL2i+D +K1GzE6WaYHElr45j2Wfphd/rRTk74WR4BaPpTCGaAhBQNn0ufqUkKsCPEAlTU+nG +iyXP4OvdMPjEBckjbKm/mlX7m0njSHAY6SWNorsCgYEAi8Yubk3efwChpMC3hBIs +vBHLmSdwclwyAPRh+X4djdO4AQ/+J8OObytond86IVHJD0pRkW+UKKUWLzCeakIq +cxGknHgHC72yZ1d7i8FMx4uMQwmLC23lLn5ImbgtslHlLqavcRTPE6DY0hFzhtS8 +z/JSGfbLx83C/V49uKnkqbECgYA6h1oYt70XdpCAi3ShcuZp5XCuwslq+JsJlyM4 +nP9RFTcPKGQlGHMOzBGNKor0L7Z0gYpRg5f8tvoDPMX7UzfR9CIY9UyOXDMZD+HS +wIWzMwBi0olueqV7zy1b9uSSDFwWh+IDhXJM1GaLDqnYm7KeQ0mxoV+4TLej2KSF +rZg3dQKBgQCVrVxFV8jHBsRsH5PzMx6pUSAollmuyte9mGU1MIE7EZf+LEQIAjGZ +9jvtAILYVJXwVZv1/zNxldUfBNuWc95ft+Gg7FEN0p0uLpdYNXQUcXuJaJ9tJ1td +ZfvRcrUXdFNKYt9/yaGeHVaIQfp4W1faZD7OnII7EOVkUKyv/qNGAA== +-----END RSA PRIVATE KEY----- diff --git a/examples/etc/turnserver.conf b/examples/etc/turnserver.conf new file mode 100644 index 0000000..07fdfb3 --- /dev/null +++ b/examples/etc/turnserver.conf @@ -0,0 +1,589 @@ +# Coturn TURN SERVER configuration file +# +# Boolean values note: where boolean value is supposed to be used, +# you can use '0', 'off', 'no', 'false', 'f' as 'false, +# and you can use '1', 'on', 'yes', 'true', 't' as 'true' +# If the value is missed, then it means 'true'. +# + +# Listener interface device (optional, Linux only). +# NOT RECOMMENDED. +# +#listening-device=eth0 + +# TURN listener port for UDP and TCP (Default: 3478). +# Note: actually, TLS & DTLS sessions can connect to the +# "plain" TCP & UDP port(s), too - if allowed by configuration. +# +#listening-port=3478 + +# TURN listener port for TLS (Default: 5349). +# Note: actually, "plain" TCP & UDP sessions can connect to the TLS & DTLS +# port(s), too - if allowed by configuration. The TURN server +# "automatically" recognizes the type of traffic. Actually, two listening +# endpoints (the "plain" one and the "tls" one) are equivalent in terms of +# functionality; but we keep both endpoints to satisfy the RFC 5766 specs. +# For secure TCP connections, we currently support SSL version 3 and +# TLS version 1.0, 1.1 and 1.2. SSL2 "encapculation mode" is also supported. +# For secure UDP connections, we support DTLS version 1. +# +#tls-listening-port=5349 + +# Alternative listening port for UDP and TCP listeners; +# default (or zero) value means "listening port plus one". +# This is needed for RFC 5780 support +# (STUN extension specs, NAT behavior discovery). The TURN Server +# supports RFC 5780 only if it is started with more than one +# listening IP address of the same family (IPv4 or IPv6). +# RFC 5780 is supported only by UDP protocol, other protocols +# are listening to that endpoint only for "symmetry". +# +#alt-listening-port=0 + +# Alternative listening port for TLS and DTLS protocols. +# Default (or zero) value means "TLS listening port plus one". +# +#alt-tls-listening-port=0 + +# Listener IP address of relay server. Multiple listeners can be specified. +# If no IP(s) specified in the config file or in the command line options, +# then all IPv4 and IPv6 system IPs will be used for listening. +# +#listening-ip=172.17.19.101 +#listening-ip=10.207.21.238 +#listening-ip=2607:f0d0:1002:51::4 + +# Auxiliary STUN/TURN server listening endpoint. +# Aux servers have almost full TURN and STUN functionality. +# The (minor) limitations are: +# +# 1) Auxiliary servers do not have alternative ports and +# they do not support STUN RFC 5780 functionality (CHANGE REQUEST). +# +# 2) Auxiliary servers also are never returning ALTERNATIVE-SERVER reply. +# +# Valid formats are 1.2.3.4:5555 for IPv4 and [1:2::3:4]:5555 for IPv6. +# +# There may be multiple aux-server options, each will be used for listening +# to client requests. +# +#aux-server=172.17.19.110:33478 +#aux-server=[2607:f0d0:1002:51::4]:33478 + +# (recommended for older Linuxes only) +# Automatically balance UDP traffic over auxiliary servers (if configured). +# The load balancing is using the ALTERNATE-SERVER mechanism. +# The TURN client must support 300 ALTERNATE-SERVER response for this +# functionality. +# +#udp-self-balance + +# Relay interface device for relay sockets (optional, Linux only). +# NOT RECOMMENDED. +# +#relay-device=eth1 + +# Relay address (the local IP address that will be used to relay the +# packets to the peer). +# Multiple relay addresses may be used. +# The same IP(s) can be used as both listening IP(s) and relay IP(s). +# +# If no relay IP(s) specified, then the turnserver will apply the default +# policy: it will decide itself which relay addresses to be used, and it +# will always be using the client socket IP address as the relay IP address +# of the TURN session (if the requested relay address family is the same +# as the family of the client socket). +# +#relay-ip=172.17.19.105 +#relay-ip=2607:f0d0:1002:51::5 + +# For Amazon EC2 users: +# +# TURN Server public/private address mapping, if the server is behind NAT. +# In that situation, if a -X is used in form "-X " then that ip will be reported +# as relay IP address of all allocations. This scenario works only in a simple case +# when one single relay address is be used, and no RFC5780 functionality is required. +# That single relay address must be mapped by NAT to the 'external' IP. +# The "external-ip" value, if not empty, is returned in XOR-RELAYED-ADDRESS field. +# For that 'external' IP, NAT must forward ports directly (relayed port 12345 +# must be always mapped to the same 'external' port 12345). +# +# In more complex case when more than one IP address is involved, +# that option must be used several times, each entry must +# have form "-X ", to map all involved addresses. +# RFC5780 NAT discovery STUN functionality will work correctly, +# if the addresses are mapped properly, even when the TURN server itself +# is behind A NAT. +# +# By default, this value is empty, and no address mapping is used. +# +#external-ip=60.70.80.91 +# +#OR: +# +#external-ip=60.70.80.91/172.17.19.101 +#external-ip=60.70.80.92/172.17.19.102 + + +# Number of relay threads to handle the established connections +# (in addition to authentication thread and the listener thread). +# If set to 0 then application runs relay process in a single thread, +# in the same thread with the listener process (the authentication thread will +# still be a separate thread). +# +# In the older systems (Linux kernel before 3.9), +# the number of UDP threads is always one thread per network listening endpoint - +# including the auxiliary endpoints - unless 0 (zero) or 1 (one) value is set. +# +#relay-threads=0 + +# Lower and upper bounds of the UDP relay endpoints: +# (default values are 49152 and 65535) +# +#min-port=49152 +#max-port=65535 + +# Uncomment to run TURN server in 'normal' 'moderate' verbose mode. +# By default the verbose mode is off. +#verbose + +# Uncomment to run TURN server in 'extra' verbose mode. +# This mode is very annoying and produces lots of output. +# Not recommended under any normal circumstances. +# +#Verbose + +# Uncomment to use fingerprints in the TURN messages. +# By default the fingerprints are off. +# +#fingerprint + +# Uncomment to use long-term credential mechanism. +# By default no credentials mechanism is used (any user allowed). +# This option can be used with either flat file user database or +# PostgreSQL DB or MySQL DB or Redis DB for user keys storage. +# +#lt-cred-mech + +# Uncomment to use short-term credential mechanism. +# By default no credentials mechanism is used (any user allowed). +# For short-term credential mechanism you have to use PostgreSQL or +# MySQL or Redis database for user password storage. +# +#st-cred-mech + +# This option is opposite to lt-cred-mech or st-cred-mech. +# (TURN Server with no-auth option allows anonymous access). +# If neither option is defined, and no users are defined, +# then no-auth is default. If at least one user is defined, +# in this file or in command line or in usersdb file, then +# lt-cred-mech is default. +# +#no-auth + +# TURN REST API flag. +# Flag that sets a special authorization option that is based upon authentication secret. +# This feature can be used with the long-term authentication mechanism, only. +# This feature purpose is to support "TURN Server REST API", see +# "TURN REST API" link in the project's page +# http://code.google.com/p/coturn/. +# +# This option is used with timestamp: +# +# usercombo -> "timestamp:userid" +# turn user -> usercombo +# turn password -> base64(hmac(secret key, usercombo)) +# +# This allows TURN credentials to be accounted for a specific user id. +# If you don't have a suitable id, the timestamp alone can be used. +# This option is just turning on secret-based authentication. +# The actual value of the secret is defined either by option static-auth-secret, +# or can be found in the turn_secret table in the database (see below). +# +#use-auth-secret + +# 'Static' authentication secret value (a string) for TURN REST API only. +# If not set, then the turn server +# will try to use the 'dynamic' value in turn_secret table +# in user database (if present). The database-stored value can be changed on-the-fly +# by a separate program, so this is why that other mode is 'dynamic'. +# +#static-auth-secret + +# 'Static' user accounts for long term credentials mechanism, only. +# This option cannot be used with TURN REST API or with short-term credentials +# mechanism. +# 'Static' user accounts are NOT dynamically checked by the turnserver process, +# so that they can NOT be changed while the turnserver is running. +# +#user=username1:key1 +#user=username2:key2 +# OR: +#user=username1:password1 +#user=username2:password2 +# +# Keys must be generated by turnadmin utility. The key value depends +# on user name, realm, and password: +# +# Example: +# $ turnadmin -k -u ninefingers -r north.gov -p youhavetoberealistic +# Output: 0xbc807ee29df3c9ffa736523fb2c4e8ee +# ('0x' in the beginning of the key is what differentiates the key from +# password. If it has 0x then it is a key, otherwise it is a password). +# +# The corresponding user account entry in the config file will be: +# +#user=ninefingers:0xbc807ee29df3c9ffa736523fb2c4e8ee +# Or, equivalently, with open clear password (less secure): +#user=ninefingers:youhavetoberealistic +# + +# 'Dynamic' user accounts database file name. +# Only users for long-term mechanism can be stored in a flat file, +# short-term mechanism will not work with option, the short-term +# mechanism required PostgreSQL or MySQL or Redis database. +# 'Dynamic' long-term user accounts are dynamically checked by the turnserver process, +# so that they can be changed while the turnserver is running. +# +# Default file name is turnuserdb.conf. +# +#userdb=/usr/local/etc/turnuserdb.conf + +# PostgreSQL database connection string in the case that we are using PostgreSQL +# as the user database. +# This database can be used for long-term and short-term credential mechanisms +# and it can store the secret value for secret-based timed authentication in TURN RESP API. +# See http://www.postgresql.org/docs/8.4/static/libpq-connect.html for 8.x PostgreSQL +# versions connection string format, see +# http://www.postgresql.org/docs/9.2/static/libpq-connect.html#LIBPQ-CONNSTRING +# for 9.x and newer connection string formats. +# +#psql-userdb="host= dbname= user= password= connect_timeout=30" + +# MySQL database connection string in the case that we are using MySQL +# as the user database. +# This database can be used for long-term and short-term credential mechanisms +# and it can store the secret value for secret-based timed authentication in TURN RESP API. +# Use string format as below (space separated parameters, all optional): +# +#mysql-userdb="host= dbname= user= password= port= connect_timeout=" + +# Redis database connection string in the case that we are using Redis +# as the user database. +# This database can be used for long-term and short-term credential mechanisms +# and it can store the secret value for secret-based timed authentication in TURN RESP API. +# Use string format as below (space separated parameters, all optional): +# +#redis-userdb="ip= dbname= password= port= connect_timeout=" + +# Redis status and statistics database connection string, if used (default - empty, no Redis stats DB used). +# This database keeps allocations status information, and it can be also used for publishing +# and delivering traffic and allocation event notifications. +# The connection string has the same parameters as redis-userdb connection string. +# Use string format as below (space separated parameters, all optional): +# +#redis-statsdb="ip= dbname= password= port= connect_timeout=" + +# The default realm to be used for the users when no explicit +# origin/realm relationship was found in the database, or if the TURN +# server is not using any database (just the commands-line settings +# and the userdb file). Must be used with long-term credentials +# mechanism or with TURN REST API. +# +#realm=mycompany.org + +# Per-user allocation quota. +# default value is 0 (no quota, unlimited number of sessions per user). +# This option can also be set through the database, for a particular realm. +# +#user-quota=0 + +# Total allocation quota. +# default value is 0 (no quota). +# This option can also be set through the database, for a particular realm. +# +#total-quota=0 + +# Max bytes-per-second bandwidth a TURN session is allowed to handle +# (input and output network streams are treated separately). Anything above +# that limit will be dropped or temporary suppressed (within +# the available buffer limits). +# This option can also be set through the database, for a particular realm. +# +#max-bps=0 + +# Uncomment if no UDP client listener is desired. +# By default UDP client listener is always started. +# +#no-udp + +# Uncomment if no TCP client listener is desired. +# By default TCP client listener is always started. +# +#no-tcp + +# Uncomment if no TLS client listener is desired. +# By default TLS client listener is always started. +# +#no-tls + +# Uncomment if no DTLS client listener is desired. +# By default DTLS client listener is always started. +# +#no-dtls + +# Uncomment if no UDP relay endpoints are allowed. +# By default UDP relay endpoints are enabled (like in RFC 5766). +# +#no-udp-relay + +# Uncomment if no TCP relay endpoints are allowed. +# By default TCP relay endpoints are enabled (like in RFC 6062). +# +#no-tcp-relay + +# Uncomment if extra security is desired, +# with nonce value having limited lifetime (600 secs). +# By default, the nonce value is unique for a session, +# but it has unlimited lifetime. With this option, +# the nonce lifetime is limited to 600 seconds, after that +# the client will get 438 error and will have to re-authenticate itself. +# +#stale-nonce + +# Certificate file. +# Use an absolute path or path relative to the +# configuration file. +# +#cert=/usr/local/etc/turn_server_cert.pem + +# Private key file. +# Use an absolute path or path relative to the +# configuration file. +# Use PEM file format. +# +#pkey=/usr/local/etc/turn_server_pkey.pem + +# Private key file password, if it is in encoded format. +# This option has no default value. +# +#pkey-pwd=... + +# Allowed OpenSSL cipher list for TLS/DTLS connections. +# Default value is "DEFAULT". +# +#cipher-list="DEFAULT" + +# CA file in OpenSSL format. +# Forces TURN server to verify the client SSL certificates. +# By default it is not set: there is no default value and the client +# certificate is not checked. +# +# Example: +#CA-file=/etc/ssh/id_rsa.cert + +# Curve name for EC ciphers, if supported by OpenSSL library (TLS and DTLS). +# The default value is prime256v1. +# +#ec-curve-name=prime256v1 + +# Use 566 bits predefined DH TLS key. Default size of the key is 1066. +# +#dh566 + +# Use 2066 bits predefined DH TLS key. Default size of the key is 1066. +# +#dh2066 + +# Use custom DH TLS key, stored in PEM format in the file. +# Flags --dh566 and --dh2066 are ignored when the DH key is taken from a file. +# +#dh-file= + +# Flag to prevent stdout log messages. +# By default, all log messages are going to both stdout and to +# the configured log file. With this option everything will be +# going to the configured log only (unless the log file itself is stdout). +# +#no-stdout-log + +# Option to set the log file name. +# By default, the turnserver tries to open a log file in +# /var/log, /var/tmp, /tmp and current directories directories +# (which open operation succeeds first that file will be used). +# With this option you can set the definite log file name. +# The special names are "stdout" and "-" - they will force everything +# to the stdout. Also, the "syslog" name will force everything to +# the system log (syslog). +# +#log-file=/var/tmp/turn.log + +# Option to redirect all log output into system log (syslog). +# +#syslog + +# This flag means that no log file rollover will be used, and the log file +# name will be constructed as-is, without PID and date appendage. +#simple-log + +# Option to set the "redirection" mode. The value of this option +# will be the address of the alternate server for UDP & TCP service in form of +# [:]. The server will send this value in the attribute +# ALTERNATE-SERVER, with error 300, on ALLOCATE request, to the client. +# Client will receive only values with the same address family +# as the client network endpoint address family. +# See RFC 5389 and RFC 5766 for ALTERNATE-SERVER functionality description. +# The client must use the obtained value for subsequent TURN communications. +# If more than one --alternate-server options are provided, then the functionality +# can be more accurately described as "load-balancing" than a mere "redirection". +# If the port number is omitted, then the default port +# number 3478 for the UDP/TCP protocols will be used. +# Colon (:) characters in IPv6 addresses may conflict with the syntax of +# the option. To alleviate this conflict, literal IPv6 addresses are enclosed +# in square brackets in such resource identifiers, for example: +# [2001:db8:85a3:8d3:1319:8a2e:370:7348]:3478 . +# Multiple alternate servers can be set. They will be used in the +# round-robin manner. All servers in the pool are considered of equal weight and +# the load will be distributed equally. For example, if we have 4 alternate servers, +# then each server will receive 25% of ALLOCATE requests. A alternate TURN server +# address can be used more than one time with the alternate-server option, so this +# can emulate "weighting" of the servers. +# +# Examples: +#alternate-server=1.2.3.4:5678 +#alternate-server=11.22.33.44:56789 +#alternate-server=5.6.7.8 +#alternate-server=[2001:db8:85a3:8d3:1319:8a2e:370:7348]:3478 + +# Option to set alternative server for TLS & DTLS services in form of +# :. If the port number is omitted, then the default port +# number 5349 for the TLS/DTLS protocols will be used. See the previous +# option for the functionality description. +# +# Examples: +#tls-alternate-server=1.2.3.4:5678 +#tls-alternate-server=11.22.33.44:56789 +#tls-alternate-server=[2001:db8:85a3:8d3:1319:8a2e:370:7348]:3478 + +# Option to suppress TURN functionality, only STUN requests will be processed. +# Run as STUN server only, all TURN requests will be ignored. +# By default, this option is NOT set. +# +#stun-only + +# Option to suppress STUN functionality, only TURN requests will be processed. +# Run as TURN server only, all STUN requests will be ignored. +# By default, this option is NOT set. +# +#no-stun + +# This is the timestamp/username separator symbol (character) in TURN REST API. +# The default value is ':'. +# rest-api-separator=: + +# Flag that can be used to disallow peers on the loopback addresses (127.x.x.x and ::1). +# This is an extra security measure. +# +#no-loopback-peers + +# Flag that can be used to disallow peers on well-known broadcast addresses (224.0.0.0 and above, and FFXX:*). +# This is an extra security measure. +# +#no-multicast-peers + +# Option to set the max time, in seconds, allowed for full allocation establishment. +# Default is 60 seconds. +# +#max-allocate-timeout=60 + +# Option to allow or ban specific ip addresses or ranges of ip addresses. +# If an ip address is specified as both allowed and denied, then the ip address is +# considered to be allowed. This is useful when you wish to ban a range of ip +# addresses, except for a few specific ips within that range. +# +# This can be used when you do not want users of the turn server to be able to access +# machines reachable by the turn server, but would otherwise be unreachable from the +# internet (e.g. when the turn server is sitting behind a NAT) +# +# Examples: +# denied-peer-ip=83.166.64.0-83.166.95.255 +# allowed-peer-ip=83.166.68.45 + +# File name to store the pid of the process. +# Default is /var/run/turnserver.pid (if superuser account is used) or +# /var/tmp/turnserver.pid . +# +#pidfile="/var/run/turnserver.pid" + +# Require authentication of the STUN Binding request. +# By default, the clients are allowed anonymous access to the STUN Binding functionality. +# +#secure-stun + +# Require SHA256 digest function to be used for the message integrity. +# By default, the server uses SHA1 (as per TURN standard specs). +# With this option, the server +# always requires the stronger SHA256 function. The client application +# must support SHA256 hash function if this option is used. If the server obtains +# a message from the client with a weaker (SHA1) hash function then the +# server returns error code 426. +# +#sha256 + +# Mobility with ICE (MICE) specs support. +# +#mobility + +# User name to run the process. After the initialization, the turnserver process +# will make an attempt to change the current user ID to that user. +# +#proc-user= + +# Group name to run the process. After the initialization, the turnserver process +# will make an attempt to change the current group ID to that group. +# +#proc-group= + +# Turn OFF the CLI support. +# By default it is always ON. +# See also options cli-ip and cli-port. +# +#no-cli + +#Local system IP address to be used for CLI server endpoint. Default value +# is 127.0.0.1. +# +#cli-ip=127.0.0.1 + +# CLI server port. Default is 5766. +# +#cli-port=5766 + +# CLI access password. Default is empty (no password). +# +#cli-password=logen + +# Server relay. NON-STANDARD AND DANGEROUS OPTION. +# Only for those applications when we want to run +# server applications on the relay endpoints. +# This option eliminates the IP permissions check on +# the packets incoming to the relay endpoints. +# +#server-relay + +# Maximum number of output sessions in ps CLI command. +# This value can be changed on-the-fly in CLI. The default value is 256. +# +#cli-max-output-sessions + +# Set network engine type for the process (for internal purposes). +# +#ne=[1|2|3] + +# Do not allow an SSL/TLS version of protocol +# +#no-sslv2 +#no-sslv3 +#no-tlsv1 +#no-tlsv1_1 +#no-tlsv1_2 diff --git a/examples/etc/turnuserdb.conf b/examples/etc/turnuserdb.conf new file mode 100644 index 0000000..340fd00 --- /dev/null +++ b/examples/etc/turnuserdb.conf @@ -0,0 +1,23 @@ +#This file can be used as user accounts storage for long-term credentials mechanism. +# +#username1:key1 +#username2:key2 +# OR: +#username1:password1 +#username2:password2 +# +# Keys must be generated by turnadmin utility. The key value depends +# on user name, realm, and password: +# +# Example: +# $ turnadmin -k -u ninefingers -r north.gov -p youhavetoberealistic +# Output: 0xbc807ee29df3c9ffa736523fb2c4e8ee +# ('0x' in the beginning of the key is what differentiates the key from +# password. If it has 0x then it is a key, otherwise it is a password). +# +# The corresponding user account entry in the userdb file will be: +# +#ninefingers:0xbc807ee29df3c9ffa736523fb2c4e8ee +# Or, equivalently (less secure): +#ninefingers:youhavetoberealistic +# diff --git a/examples/scripts/basic/dos_attack.sh b/examples/scripts/basic/dos_attack.sh new file mode 100755 index 0000000..e6b3604 --- /dev/null +++ b/examples/scripts/basic/dos_attack.sh @@ -0,0 +1,35 @@ +#!/bin/sh +# +# This is an example of a script for DOS attack emulation + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +while [ 0 ] ; do + +PATH=examples/bin/:../bin/:bin/:${PATH} turnutils_uclient -O -D -G -n 1 -m 12 -e 127.0.0.1 -X -g $@ ::1 & + +PATH=examples/bin/:../bin/:bin/:${PATH} turnutils_uclient -O -G -n 1 -m 12 -y -s $@ 127.0.0.1 & + +PATH=examples/bin/:../bin:bin/:${PATH} turnutils_uclient -O -G -t -n 1 -m 12 -e 127.0.0.1 -X -g $@ ::1 & + +PATH=examples/bin/:../bin:bin/:${PATH} turnutils_uclient -O -G -T -n 1 -m 12 -y -s $@ 127.0.0.1 & + +sleep 1 + +type killall >>/dev/null 2>>/dev/null +ER=$? +if [ ${ER} -eq 0 ] ; then + killall turnutils_uclient >>/dev/null 2>>/dev/null +else + type pkill >>/dev/null 2>>/dev/null + ER=$? + if [ ${ER} -eq 0 ] ; then + pkill turnutils_u >>/dev/null 2>>/dev/null + fi +fi + +done diff --git a/examples/scripts/basic/relay.sh b/examples/scripts/basic/relay.sh new file mode 100755 index 0000000..ee597c7 --- /dev/null +++ b/examples/scripts/basic/relay.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# +# This is an example how to start a TURN Server in +# non-secure mode (when authentication is not used). +# We start here a TURN Server listening on IPv4 address +# 127.0.0.1 and on IPv6 address ::1. We use 127.0.0.1 as +# IPv4 relay address, and we use ::1 as IPv6 relay address. +# Other options: +# set bandwidth limit on client session 3000000 bytes per second (--max-bps) +# use fingerprints (-f) +# use 3 relay threads (-m 3) +# use min UDP relay port 32355 and max UDP relay port 65535 +# --no-tls and --no-dtls mean that we are not trying to +# --no-auth means that no authentication to be used, +# allow anonymous users. +# start TLS and DTLS services. +# Other parameters (config file name, etc) are default. + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ +export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ + +PATH="bin:../bin:../../bin:${PATH}" turnserver -v --syslog -L 127.0.0.1 -L ::1 -E 127.0.0.1 -E ::1 --max-bps=3000000 -f -m 3 --min-port=32355 --max-port=65535 --no-tls --no-dtls --no-auth $@ + + + + diff --git a/examples/scripts/basic/tcp_client.sh b/examples/scripts/basic/tcp_client.sh new file mode 100755 index 0000000..a7ff1b2 --- /dev/null +++ b/examples/scripts/basic/tcp_client.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# +# This is an example of a script to run a "unsecure" TURN TCP client. +# Options: +# 1) -t is present, it means that TCP networking is used. +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 170 means that the payload size of the packets is 170 bytes +# (like average audio RTP packet). +# 8) -e 127.0.0.1 means that the clients will use peer address 127.0.0.1. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) -s option is absent - it means that the client will be using +# the "channel" mechanism for data. +# 11) -X means that IPv4 relay address is requested. +# 12) ::1 (the last parameter) is the TURN Server IP address. We use IPv6 here +# to illustrate how the TURN Server convert the traffic from IPv6 to IPv4 and back. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:../bin:bin/:${PATH} turnutils_uclient -t -n 1000 -m 10 -l 3037 -e 127.0.0.1 -g -X $@ ::1 + diff --git a/examples/scripts/basic/tcp_client_c2c_tcp_relay.sh b/examples/scripts/basic/tcp_client_c2c_tcp_relay.sh new file mode 100755 index 0000000..d67a1ad --- /dev/null +++ b/examples/scripts/basic/tcp_client_c2c_tcp_relay.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# +# This is an example of a script to run a "unsecure" TURN TCP client +# with TCP relay endpoints (RFC 6062). +# Options: +# 1) -T is present, it means that TCP networking is used with TCP relay endpoints. +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 170 means that the payload size of the packets is 170 bytes +# (like average audio RTP packet). +# 8) -y means that the clients will connect to the 'neighbor' clients, no peer app will be used. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) ::1 (the last parameter) is the TURN Server IP address. We use IPv6 here +# to illustrate how the TURN Server convert the traffic from IPv6 to IPv4 and back. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:../bin:bin/:${PATH} turnutils_uclient -T -n 1000 -m 10 -l 170 -y -g $@ ::1 + diff --git a/examples/scripts/basic/udp_c2c_client.sh b/examples/scripts/basic/udp_c2c_client.sh new file mode 100755 index 0000000..9c6f48f --- /dev/null +++ b/examples/scripts/basic/udp_c2c_client.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# +# This is an example of a script to run a "unsecure" TURN UDP client, +# in client-to-client fashion (when client talks to another client +# through their corresponding allocated relayed endpoints). +# Options: +# 1) -t is absent, it means that UDP networking is used. +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -y means "client to client" communication pattern. +# the client calculates the peer address +# (which is the allocated relayed endpoint of the next client in array of clients). +# 8) -l 170 means that the payload size of the packets is 170 bytes +# like average audio RTP packet). +# 9) -s option is absent - it means that the client will be using +# the "channel" mechanism for data. +# 10) 127.0.0.1 (the last parameter) is the TURN Server IP address. +# 11) -z 5 means that we want 5 ms interval between the packets (per each session). +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:../bin/:bin/:${PATH} turnutils_uclient -n 1000 -m 10 -y -l 170 -z 15 $@ 127.0.0.1 + diff --git a/examples/scripts/basic/udp_client.sh b/examples/scripts/basic/udp_client.sh new file mode 100755 index 0000000..3961597 --- /dev/null +++ b/examples/scripts/basic/udp_client.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# +# This is an example of a script to run a "unsecure" TURN UDP client. +# Options: +# 0) -D means "mandatory padding", like pjnath does; +# 1) -t is absent, it means that UDP networking is used. +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 171 means that the payload size of the packets is 171 bytes +# (like average audio RTP packet). +# 8) -e 127.0.0.1 means that the clients will use peer address 127.0.0.1. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) -s option is absent - it means that the client will be using +# the "channel" mechanism for data. +# 11) -X means that IPv4 relay address is requested. +# 12) 127.0.0.1 (the last parameter) is the TURN Server IP address. We use IPv4 here +# to illustrate how the TURN Server convert the traffic from IPv6 to IPv4 and back. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:../bin/:bin/:${PATH} turnutils_uclient -D -n 1000 -m 10 -l 171 -e 127.0.0.1 -g -X $@ 127.0.0.1 + diff --git a/examples/scripts/loadbalance/master_relay.sh b/examples/scripts/loadbalance/master_relay.sh new file mode 100755 index 0000000..f7d0e01 --- /dev/null +++ b/examples/scripts/loadbalance/master_relay.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# +# This is an example of a MASTER TURN server that distributes +# the load among several "slave" TURN servers. +# +# The TURN Server is started in +# secure mode (when authentication is used) - see option -a +# that means "use long-term credential mechanism". +# +# We start here a TURN Server listening on IPv4 address +# 127.0.0.1. We use 127.0.0.1 as the relay address, too. +# +# Other options: +# +# 1) set bandwidth limit on client session 3000000 bytes per second (--max-bps). +# 2) use fingerprints (-f) +# 3) use 3 relay threads (-m 3) +# 4) use min UDP relay port 32355 and max UDP relay port 65535 +# 5) "-r north.gov" means "use authentication realm north.gov" +# 6) "--user=ninefingers:0xbc807ee29df3c9ffa736523fb2c4e8ee" means +# "allow user 'ninefinger' with generated key '0xbc807ee29df3c9ffa736523fb2c4e8ee' ". +# 7) "--user=gorst:hero" means "allow user 'gorst' with password 'hero' ". +# 8) "--log-file=stdout" means that all log output will go to the stdout. +# 9) "-v" means normal verbose mode (with some moderate logging). +# 10) --no-dtls and --no-tls measn that we are not using DTLS & TLS protocols here +# (for the sake of simplicity). +# 11) --alternate-server options set the "slave" servers. +# Other parameters (config file name, etc) are default. + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ +export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ + +PATH="./bin/:../bin/:../../bin/:${PATH}" turnserver --syslog -a -L 127.0.0.1 -E 127.0.0.1 --max-bps=3000000 -f -m 3 --min-port=32355 --max-port=65535 --user=ninefingers:0xbc807ee29df3c9ffa736523fb2c4e8ee --user=gorst:hero -r north.gov --log-file=stdout -v --no-dtls --no-tls --alternate-server=127.0.0.1:3333 --alternate-server=127.0.0.1:4444 $@ + diff --git a/examples/scripts/loadbalance/slave_relay_1.sh b/examples/scripts/loadbalance/slave_relay_1.sh new file mode 100755 index 0000000..f924836 --- /dev/null +++ b/examples/scripts/loadbalance/slave_relay_1.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# +# This is an example of a SLAVE TURN server that accepts +# the redirected requests. +# +# The TURN Server is started in +# secure mode (when authentication is used) - see option -a +# that means "use long-term credential mechanism". +# +# We start here a TURN Server listening on IPv4 address +# 127.0.0.1. We use 127.0.0.1 as the relay address, too. +# +# Other options: +# +# 1) set bandwidth limit on client session 3000000 bytes per second (--max-bps). +# 2) use fingerprints (-f) +# 3) use 3 relay threads (-m 3) +# 4) use min UDP relay port 10000 and max UDP relay port 19999 +# 5) "-r north.gov" means "use authentication realm north.gov" +# 6) "--user=ninefingers:0xbc807ee29df3c9ffa736523fb2c4e8ee" means +# "allow user 'ninefinger' with generated key '0xbc807ee29df3c9ffa736523fb2c4e8ee' ". +# 7) "--user=gorst:hero" means "allow user 'gorst' with password 'hero' ". +# 8) "--log-file=stdout" means that all log output will go to the stdout. +# 9) "-v" means normal verbose mode (with some moderate logging). +# 10) --no-dtls and --no-tls measn that we are not using DTLS & TLS protocols here +# (for the sake of simplicity). +# 11) -p 3333 means that we are using UDP & TCP listening port 3333. +# Other parameters (config file name, etc) are default. + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ +export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ + +PATH="./bin/:../bin/:../../bin/:${PATH}" turnserver --syslog -a -L 127.0.0.1 -E 127.0.0.1 --max-bps=3000000 -f -m 3 --min-port=10000 --max-port=19999 --user=ninefingers:0xbc807ee29df3c9ffa736523fb2c4e8ee --user=gorst:hero -r north.gov --log-file=stdout -v --no-dtls --no-tls -p 3333 --cli-port=5767 $@ diff --git a/examples/scripts/loadbalance/slave_relay_2.sh b/examples/scripts/loadbalance/slave_relay_2.sh new file mode 100755 index 0000000..f923da9 --- /dev/null +++ b/examples/scripts/loadbalance/slave_relay_2.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# +# This is an example of a SLAVE TURN server that accepts +# the redirected requests. +# +# The TURN Server is started in +# secure mode (when authentication is used) - see option -a +# that means "use long-term credential mechanism". +# +# We start here a TURN Server listening on IPv4 address +# 127.0.0.1. We use 127.0.0.1 as the relay address, too. +# +# Other options: +# +# 1) set bandwidth limit on client session 3000000 bytes per second (--max-bps). +# 2) use fingerprints (-f) +# 3) use 3 relay threads (-m 3) +# 4) use min UDP relay port 20000 and max UDP relay port 29999 +# 5) "-r north.gov" means "use authentication realm north.gov" +# 6) "--user=ninefingers:0xbc807ee29df3c9ffa736523fb2c4e8ee" means +# "allow user 'ninefinger' with generated key '0xbc807ee29df3c9ffa736523fb2c4e8ee' ". +# 7) "--user=gorst:hero" means "allow user 'gorst' with password 'hero' ". +# 8) "--log-file=stdout" means that all log output will go to the stdout. +# 9) "-v" means normal verbose mode (with some moderate logging). +# 10) --no-dtls and --no-tls measn that we are not using DTLS & TLS protocols here +# (for the sake of simplicity). +# 11) -p 4444 means that we are using UDP & TCP listening port 4444. +# Other parameters (config file name, etc) are default. + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ +export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ + +PATH="./bin/:../bin/:../../bin/:${PATH}" turnserver --syslog -a -L 127.0.0.1 -E 127.0.0.1 --max-bps=3000000 -f -m 3 --min-port=20000 --max-port=29999 --user=ninefingers:0xbc807ee29df3c9ffa736523fb2c4e8ee --user=gorst:hero -r north.gov --log-file=stdout -v --no-dtls --no-tls -p 4444 --cli-port=5768 $@ diff --git a/examples/scripts/loadbalance/tcp_c2c_tcp_relay.sh b/examples/scripts/loadbalance/tcp_c2c_tcp_relay.sh new file mode 100755 index 0000000..b76a554 --- /dev/null +++ b/examples/scripts/loadbalance/tcp_c2c_tcp_relay.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# +# This is an example of a script to run a "secure" TURN TCP client +# with the long-term credentials mechanism and with +# TCP relay endpoints (RFC 6062). +# +# Options: +# +# 1) -T is present, it means that TCP networking is used, with TCP relay endpoints (RFC 6062). +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 170 means that the payload size of the packets is 170 bytes +# (like average audio RTP packet). +# 8) -y means that the clients will connect to the 'neighbor' clients, no peer app will be used. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) -u gorst means that if the server challenges the client with +# authentication challenge, then we use account "gorst". +# 11) -w hero sets the password for the account as "hero". +# 12) 127.0.0.1 (the last parameter) is the TURN Server IP address. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -T -n 1000 -m 10 -l 170 -y -g -u gorst -w hero $@ 127.0.0.1 diff --git a/examples/scripts/loadbalance/udp_c2c.sh b/examples/scripts/loadbalance/udp_c2c.sh new file mode 100755 index 0000000..52f2160 --- /dev/null +++ b/examples/scripts/loadbalance/udp_c2c.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# +# This is an example of a script to run a "secure" TURN UDP client +# with the long-term credentials mechanism, +# in client-to-client communication patter. +# +# Options: +# +# 1) -t is absent, it means that UDP networking is used. +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 170 means that the payload size of the packets is 170 bytes +# (like average audio RTP packet). +# 8) -y means that the clients will be connecting to each other and the peer will not be used. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) -u ninefingers means that if the server challenges the client with +# authentication challenge, then we use account "ninefingers". +# 11) -w youhavetoberealistic sets the password for the account as "youhavetoberealistic". +# 12) -s option is absent - it means that the client will be using +# the "channel" mechanism for data. +# 13) 127.0.0.1 (the last parameter) is the TURN Server IP address. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -n 1000 -m 10 -l 170 -g -u ninefingers -w youhavetoberealistic -y $@ 127.0.0.1 diff --git a/examples/scripts/longtermsecure/secure_dos_attack.sh b/examples/scripts/longtermsecure/secure_dos_attack.sh new file mode 100755 index 0000000..c5ff18c --- /dev/null +++ b/examples/scripts/longtermsecure/secure_dos_attack.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# +# This is an example of a script to run a DOS attack in a +# "secure" environment +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +while [ 0 ] ; do + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -G -O -n 10 -m 10 -l 170 -g -u ninefingers -w youhavetoberealistic -y $@ ::1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -G -O -n 10 -m 10 -l 170 -e 127.0.0.1 -X -g -u ninefingers -w youhavetoberealistic $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -G -O -S -k turn_client_pkey.pem -n 10 -m 10 -l 170 -e ::1 -x -g -u ninefingers -w youhavetoberealistic -s $@ 127.0.0.1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -G -O -t -n 30 -m 10 -l 170 -e 127.0.0.1 -X -g -u gorst -w hero $@ ::1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -G -O -T -n 10 -m 10 -l 170 -y -g -u gorst -w hero $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -G -O -T -S -k turn_client_pkey.pem -n 10 -m 10 -l 170 -y -g -u gorst -w hero $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -G -O -t -S -k turn_client_pkey.pem -n 10 -m 10 -l 170 -e 127.0.0.1 -X -g -u gorst -w hero $@ ::1 & + +sleep 2 + +type killall >>/dev/null 2>>/dev/null +ER=$? +if [ ${ER} -eq 0 ] ; then + killall turnutils_uclient >>/dev/null 2>>/dev/null +fi + +type pkill >>/dev/null 2>>/dev/null +ER=$? +if [ ${ER} -eq 0 ] ; then + pkill turnutils_u >>/dev/null 2>>/dev/null + pkill turnutils_uclie >>/dev/null 2>>/dev/null + pkill turnutils_uclient >>/dev/null 2>>/dev/null +else + sleep 10 +fi + +done + + diff --git a/examples/scripts/longtermsecure/secure_dtls_client.sh b/examples/scripts/longtermsecure/secure_dtls_client.sh new file mode 100755 index 0000000..c962b1d --- /dev/null +++ b/examples/scripts/longtermsecure/secure_dtls_client.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# +# This is an example of a script to run a "secure" TURN DTLS client +# with the long-term credentials mechanism. +# +# Options: +# +# 1) -t is absent, it means that UDP networking is used. +# 2) -S means "SSL protocol with default encryption" +# 3) -i absent. +# 4) -k sets private key file for TLS. +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 170 means that the payload size of the packets is 170 bytes +# (like average audio RTP packet). +# 8) -e 127.0.0.1 means that the clients will use peer IPv4 address 127.0.0.1. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) -u ninefingers means that if the server challenges the client with +# authentication challenge, then we use account "ninefingers". +# 11) -w youhavetoberealistic sets the password for the account. +# 12) -s option absent - that means that the client will be using +# the channel mechanism for data. +# 13) 127.0.0.1 (the last parameter) is the TURN Server IP address. +# We use IPv6 - to - IPv4 here to illustrate how the TURN Server +# converts the traffic from IPv6 to IPv4 and back. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -S -k turn_client_pkey.pem -n 1000 -m 10 -l 170 -e 127.0.0.1 -X -g -u ninefingers -w youhavetoberealistic $@ 127.0.0.1 + diff --git a/examples/scripts/longtermsecure/secure_dtls_client_cert.sh b/examples/scripts/longtermsecure/secure_dtls_client_cert.sh new file mode 100755 index 0000000..a779ac6 --- /dev/null +++ b/examples/scripts/longtermsecure/secure_dtls_client_cert.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# +# This is an example of a script to run a "secure" TURN DTLS client +# with the long-term credentials mechanism and with certificate check. +# +# Options: +# +# 1) -t is absent, it means that UDP networking is used. +# 2) -S means "SSL protocol with default encryption" +# 3) -i sets certificate file for TLS. -R sets certificate check mode. +# -E sets CA file for certificate check. +# 4) -k sets private key file for TLS. +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 170 means that the payload size of the packets is 170 bytes +# (like average audio RTP packet). +# 8) -e 127.0.0.1 means that the clients will use peer IPv4 address 127.0.0.1. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) -u bolt means that if the server challenges the client with +# authentication challenge, then we use account "bolt". +# 11) -w kwyjibo sets the password for the account. +# 12) -s option means that the client will be using "send" mechanism for data. +# 13) 127.0.0.1 (the last parameter) is the TURN Server IP address. +# We use IPv6 - to - IPv4 here to illustrate how the TURN Server +# converts the traffic from IPv6 to IPv4 and back. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -S -i turn_server_cert.pem -k turn_server_pkey.pem -E turn_server_cert.pem -n 1000 -m 10 -l 170 -e 127.0.0.1 -g -u bolt -w kwyjibo -s -X $@ 127.0.0.1 + diff --git a/examples/scripts/longtermsecure/secure_relay.sh b/examples/scripts/longtermsecure/secure_relay.sh new file mode 100755 index 0000000..d390265 --- /dev/null +++ b/examples/scripts/longtermsecure/secure_relay.sh @@ -0,0 +1,35 @@ +#!/bin/sh +# +# This is an example how to start a TURN Server in +# secure mode (when authentication is used) - see option -a +# that means "use long-term credential mechanism". +# +# We start here a TURN Server listening on IPv4 address +# 127.0.0.1 and on IPv6 address ::1. We use 127.0.0.1 as +# IPv4 relay address, and we use ::1 as IPv6 relay address. +# +# Other options: +# +# 1) set bandwidth limit on client session 3000000 bytes per second (--max-bps). +# 2) use fingerprints (-f) +# 3) use 10 relay threads (-m 10) +# 4) use min UDP relay port 32355 and max UDP relay port 65535 +# 5) "-r north.gov" means "use authentication realm north.gov" +# 6) "--user=ninefingers:youhavetoberealistic" means +# "allow user 'ninefinger' with password 'youhavetoberealistic' ". +# 7) "--user=gorst:hero" means "allow user 'gorst' with password 'hero' ". +# 8) "--cert=turn_server_cert.pem" sets the OpenSSL certificate file name. +# 9) "--pkey=turn_server_pkey.pem" sets the OpenSSL private key name. +# 10) "--log-file=stdout" means that all log output will go to the stdout. +# 11) "-v" means normal verbose mode (with some moderate logging). +# 12) --cipher-list=ALL:SSLv2 means that we support all OpenSSL ciphers, including SSLv2 +# Other parameters (config file name, etc) are default. + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ +export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ + +PATH="./bin/:../bin/:../../bin/:${PATH}" turnserver --syslog -a -L 127.0.0.1 -L ::1 -E 127.0.0.1 -E ::1 --max-bps=3000000 -f -m 10 --min-port=32355 --max-port=65535 --user=ninefingers:youhavetoberealistic --user=gorst:hero -r north.gov --cert=turn_server_cert.pem --pkey=turn_server_pkey.pem --log-file=stdout -v --cipher-list=ALL:SSLv2 $@ diff --git a/examples/scripts/longtermsecure/secure_relay_cert.sh b/examples/scripts/longtermsecure/secure_relay_cert.sh new file mode 100755 index 0000000..9df3b4d --- /dev/null +++ b/examples/scripts/longtermsecure/secure_relay_cert.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# +# This is an example how to start a TURN Server in +# secure mode (when authentication is used) - see option -a +# that means "use long-term credential mechanism". +# +# This script shows how to use certificate check option. +# +# We start here a TURN Server listening on IPv4 address +# 127.0.0.1 and on IPv6 address ::1. We use 127.0.0.1 as +# IPv4 relay address, and we use ::1 as IPv6 relay address. +# +# Other options: +# +# 1) set bandwidth limit on client session 3000000 bytes per second (--max-bps). +# 2) use fingerprints (-f) +# 3) use 10 relay threads (-m 10) +# 4) use min UDP relay port 32355 and max UDP relay port 65535 +# 5) "-r bolt.co" means "use authentication realm 'bolt.co'" +# 6) "--user=ninefingers:youhavetoberealistic" means "allow user +# 'ninefinger' with password 'youhavetoberealistic'.". +# 7) "--user=bolt:kwyjibo" means "allow user 'bolt' with password 'kwyjibo' ". +# 8) "--cert=..." sets the OpenSSL certificate file name. +# 9) "--pkey=..." sets the OpenSSL private key name. +# 10) --CA-file sets the CA file for client certificate check. +# 11) "--log-file=stdout" means that all log output will go to the stdout. +# 12) "-v" means normal verbose mode (with some moderate logging). +# 13) --cipher-list="ALL:SSLv2:!eNULL:!aNULL:!NULL" measn "all ciphers, except anonymous". +# Other parameters (config file name, etc) are default. + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ +export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ + +PATH="./bin/:../bin/:../../bin/:${PATH}" turnserver --syslog -a -L 127.0.0.1 -L ::1 -E 127.0.0.1 -E ::1 --max-bps=3000000 -f -m 10 --min-port=32355 --max-port=65535 --user=ninefingers:youhavetoberealistic --user=bolt:kwyjibo -r bolt.co --cert=turn_server_cert.pem --pkey=turn_server_pkey.pem --CA-file=turn_server_cert.pem --log-file=stdout -v --cipher-list="ALL:SSLv2:!eNULL:!aNULL:!NULL" $@ diff --git a/examples/scripts/longtermsecure/secure_tcp_client.sh b/examples/scripts/longtermsecure/secure_tcp_client.sh new file mode 100755 index 0000000..3e76efd --- /dev/null +++ b/examples/scripts/longtermsecure/secure_tcp_client.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# +# This is an example of a script to run a "secure" TURN TCP client +# with the long-term credentials mechanism. +# +# Options: +# +# 1) -t is present, it means that TCP networking is used. +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 170 means that the payload size of the packets is 170 bytes +# (like average audio RTP packet). +# 8) -e 127.0.0.1 means that the clients will use peer address 127.0.0.1. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) -u gorst means that if the server challenges the client with +# authentication challenge, then we use account "gorst". +# 11) -w hero sets the password for the account as "hero". +# 12) -s option is absent - it means that the client will be using +# the "channel" mechanism for data. +# 13) ::1 (the last parameter) is the TURN Server IP address. We use IPv6 here +# to illustrate how the TURN Server convert the traffic from IPv6 to IPv4 and back. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -t -n 3000 -m 10 -l 170 -e 127.0.0.1 -X -g -u gorst -w hero $@ ::1 diff --git a/examples/scripts/longtermsecure/secure_tcp_client_c2c_tcp_relay.sh b/examples/scripts/longtermsecure/secure_tcp_client_c2c_tcp_relay.sh new file mode 100755 index 0000000..68da930 --- /dev/null +++ b/examples/scripts/longtermsecure/secure_tcp_client_c2c_tcp_relay.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# +# This is an example of a script to run a "secure" TURN TCP client +# with the long-term credentials mechanism and with +# TCP relay endpoints (RFC 6062). +# +# Options: +# +# 1) -T is present, it means that TCP networking is used, with TCP relay endpoints (RFC 6062). +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 170 means that the payload size of the packets is 170 bytes +# (like average audio RTP packet). +# 8) -y means that the clients will connect to the 'neighbor' clients, no peer app will be used. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) -u gorst means that if the server challenges the client with +# authentication challenge, then we use account "gorst". +# 11) -w hero sets the password for the account as "hero". +# 12) 127.0.0.1 (the last parameter) is the TURN Server IP address. We use IPv4 here +# to illustrate how the TURN Server convert the traffic from IPv6 to IPv4 and back. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -T -n 1000 -m 10 -l 170 -y -g -u gorst -w hero $@ 127.0.0.1 + diff --git a/examples/scripts/longtermsecure/secure_tls_client.sh b/examples/scripts/longtermsecure/secure_tls_client.sh new file mode 100755 index 0000000..6eeef04 --- /dev/null +++ b/examples/scripts/longtermsecure/secure_tls_client.sh @@ -0,0 +1,34 @@ +#!/bin/sh +# +# This is an example of a script to run a "secure" TURN TLS client +# with the long-term credentials mechanism. +# +# Options: +# +# 1) -t is present, it means that TCP networking is used. +# 2) -S means "SSL/TLS protocol with default cipher". +# 3) -i absent. +# 4) -k sets private key file for TLS. +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 170 means that the payload size of the packets is 170 bytes +# (like average audio RTP packet). +# 8) -e 127.0.0.1 means that the clients will use peer address 127.0.0.1. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) -u gorst means that if the server challenges the client with +# authentication challenge, then we use account "gorst". +# 11) -w hero sets the password for the account as "hero". +# 12) -s option means that the client will be using "send" mechanism for data. +# 13) ::1 (the last parameter) is the TURN Server IP address. We use IPv6 here +# to illustrate how the TURN Server convert the traffic from IPv6 to IPv4 and back. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -t -S -k turn_client_pkey.pem -n 1000 -m 10 -l 170 -e 127.0.0.1 -X -g -u gorst -w hero $@ ::1 + diff --git a/examples/scripts/longtermsecure/secure_tls_client_c2c_tcp_relay.sh b/examples/scripts/longtermsecure/secure_tls_client_c2c_tcp_relay.sh new file mode 100755 index 0000000..ac6727f --- /dev/null +++ b/examples/scripts/longtermsecure/secure_tls_client_c2c_tcp_relay.sh @@ -0,0 +1,35 @@ +#!/bin/sh +# +# This is an example of a script to run a "secure" TURN TLS client +# with the long-term credentials mechanism and with +# TCP relay endpoints (RFC 6062). +# +# Options: +# +# 1) -T is present, it means that TCP networking is used, with TCP +# relay endpoints (RFC 6062. +# 2) -S means that "secure protocol", that is TLS in the case of TCP, +# will be used between the client and the TURN Server. +# 3) -i absent. +# 4) -k sets private key file for TLS. +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 170 means that the payload size of the packets is 170 bytes +# (like average audio RTP packet). +# 8) -y means that the clients will connect to the 'neighbor' clients, no peer app will be used. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) -u gorst means that if the server challenges the client with +# authentication challenge, then we use account "gorst". +# 11) -w hero sets the password for the account as "hero". +# 12) ::1 (the last parameter) is the TURN Server IP address. We use IPv6 here +# to illustrate how the TURN Server convert the traffic from IPv6 to IPv4 and back. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/:/usr/postgres/9.2-pgdg/lib + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -T -S -k turn_client_pkey.pem -n 1000 -m 10 -l 170 -y -g -u gorst -w hero $@ ::1 diff --git a/examples/scripts/longtermsecure/secure_tls_client_cert.sh b/examples/scripts/longtermsecure/secure_tls_client_cert.sh new file mode 100755 index 0000000..80d7db5 --- /dev/null +++ b/examples/scripts/longtermsecure/secure_tls_client_cert.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# +# This is an example of a script to run a "secure" TURN DTLS client +# with the long-term credentials mechanism and with certificate check. +# +# Options: +# +# 1) -t means that TCP networking is used. +# 2) -S means "SSL protocol with default encryption" +# 3) -i sets certificate file for TLS. -R sets certificate check mode. +# -E sets CA file for certificate check. +# 4) -k sets private key file for TLS. +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 170 means that the payload size of the packets is 170 bytes +# (like average audio RTP packet). +# 8) -e 127.0.0.1 means that the clients will use peer IPv4 address 127.0.0.1. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) -u bolt means that if the server challenges the client with +# authentication challenge, then we use account "bolt". +# 11) -w kwyjibo sets the password for the account. +# 12) -s option means that the client will be using "send" mechanism for data. +# 13) 127.0.0.1 (the last parameter) is the TURN Server IP address. +# We use IPv6 - to - IPv4 here to illustrate how the TURN Server +# converts the traffic from IPv6 to IPv4 and back. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -t -S -i turn_server_cert.pem -k turn_server_pkey.pem -E turn_server_cert.pem -n 1000 -m 10 -l 170 -e 127.0.0.1 -X -g -u bolt -w kwyjibo -s $@ 127.0.0.1 + diff --git a/examples/scripts/longtermsecure/secure_udp_c2c.sh b/examples/scripts/longtermsecure/secure_udp_c2c.sh new file mode 100755 index 0000000..c58a023 --- /dev/null +++ b/examples/scripts/longtermsecure/secure_udp_c2c.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# +# This is an example of a script to run a "secure" TURN UDP client +# with the long-term credentials mechanism, +# in client-to-client communication patter. +# +# Options: +# +# 1) -t is absent, it means that UDP networking is used. +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 170 means that the payload size of the packets is 170 bytes +# (like average audio RTP packet). +# 8) -y means that the clients will be connecting to each other and the peer will not be used. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) -u ninefingers means that if the server challenges the client with +# authentication challenge, then we use account "ninefingers". +# 11) -w youhavetoberealistic sets the password for the account as "youhavetoberealistic". +# 12) -s option is present - it means that the client will be using +# the DATA mechanism for data. +# 13) ::1 (the last parameter) is the TURN Server IP address. We use IPv6 here +# to illustrate how the TURN Server convert the traffic from IPv6 to IPv4 and back. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -s -n 1000 -m 10 -l 170 -g -u ninefingers -w youhavetoberealistic -y $@ ::1 diff --git a/examples/scripts/longtermsecure/secure_udp_client.sh b/examples/scripts/longtermsecure/secure_udp_client.sh new file mode 100755 index 0000000..3ac8f7c --- /dev/null +++ b/examples/scripts/longtermsecure/secure_udp_client.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# +# This is an example of a script to run a "secure" TURN UDP client +# with the long-term credentials mechanism. +# +# Options: +# +# 1) -t is absent, it means that UDP networking is used. +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 170 means that the payload size of the packets is 170 bytes +# (like average audio RTP packet). +# 8) -e 127.0.0.1 means that the clients will use peer address 127.0.0.1. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) -u ninefingers means that if the server challenges the client with +# authentication challenge, then we use account "ninefingers". +# 11) -w youhavetoberealistic sets the password for the account as "youhavetoberealistic". +# 12) -s option is absent - it means that the client will be using +# the "channel" mechanism for data. +# 13) ::1 (the last parameter) is the TURN Server IP address. We use IPv6 here +# to illustrate how the TURN Server convert the traffic from IPv6 to IPv4 and back. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -n 1000 -m 10 -l 170 -e 127.0.0.1 -X -g -u ninefingers -w youhavetoberealistic $@ ::1 + diff --git a/examples/scripts/longtermsecuredb/secure_relay_with_db_mysql.sh b/examples/scripts/longtermsecuredb/secure_relay_with_db_mysql.sh new file mode 100755 index 0000000..e53c8ef --- /dev/null +++ b/examples/scripts/longtermsecuredb/secure_relay_with_db_mysql.sh @@ -0,0 +1,34 @@ +#!/bin/sh +# +# This is an example how to start a TURN Server in +# secure mode with MySQL database for users +# with the long-term credentials mechanism. +# +# We start here a TURN Server listening on IPv4 address +# 127.0.0.1 and on IPv6 address ::1. We use 127.0.0.1 as +# IPv4 relay address, and we use ::1 as IPv6 relay address. +# +# Other options: +# +# 1) set bandwidth limit on client session 3000000 bytes per second (--max-bps). +# 2) use fingerprints (-f) +# 3) use 3 relay threads (-m 3) +# 4) use min UDP relay port 32355 and max UDP relay port 65535 +# 5) "-r north.gov" means "use authentication realm north.gov" +# 6) --mysql-userdb="host=localhost dbname=coturn user=turn password=turn connect_timeout=30" +# means that local MySQL database "coturn" will be used, with database user "turn" and +# database user password "turn", and connection timeout 30 seconds. +# 7) "--cert=example_turn_server_cert.pem" sets the OpenSSL certificate file name. +# 8) "--pkey=example_turn_server_pkey.pem" sets the OpenSSL private key name. +# 9) "--log-file=stdout" means that all log output will go to the stdout. +# 10) --cipher-list=ALL:SSLv2 means that we support all OpenSSL ciphers, including SSLv2 +# Other parameters (config file name, etc) are default. + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ +export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ + +PATH="./bin/:../bin/:../../bin/:${PATH}" turnserver -v --syslog -a -L 127.0.0.1 -L ::1 -E 127.0.0.1 -E ::1 --max-bps=3000000 -f -m 3 --min-port=32355 --max-port=65535 -r north.gov --mysql-userdb="host=localhost dbname=coturn user=turn password=turn connect_timeout=30" --cert=turn_server_cert.pem --pkey=turn_server_pkey.pem --log-file=stdout --cipher-list=ALL:SSLv2 $@ diff --git a/examples/scripts/longtermsecuredb/secure_relay_with_db_psql.sh b/examples/scripts/longtermsecuredb/secure_relay_with_db_psql.sh new file mode 100755 index 0000000..4f1245d --- /dev/null +++ b/examples/scripts/longtermsecuredb/secure_relay_with_db_psql.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# +# This is an example how to start a TURN Server in +# secure mode with Postgres database for users +# with the long-term credentials mechanism. +# +# We start here a TURN Server listening on IPv4 address +# 127.0.0.1 and on IPv6 address ::1. We use 127.0.0.1 as +# IPv4 relay address, and we use ::1 as IPv6 relay address. +# +# Other options: +# +# 1) set bandwidth limit on client session 3000000 bytes per second (--max-bps). +# 2) use fingerprints (-f) +# 3) use 3 relay threads (-m 3) +# 4) use min UDP relay port 32355 and max UDP relay port 65535 +# 5) "-r north.gov" means "use authentication realm north.gov" +# 6) --psql-userdb="host=localhost dbname=coturn user=turn password=turn connect_timeout=30" +# means that local database "coturn" will be used, with database user "turn" and database user +# password "turn". +# 7) "--cert=example_turn_server_cert.pem" sets the OpenSSL certificate file name. +# 8) "--pkey=example_turn_server_pkey.pem" sets the OpenSSL private key name. +# 9) "--log-file=stdout" means that all log output will go to the stdout. +# 10) --cipher-list=ALL:SSLv2 means that we support all OpenSSL ciphers, including SSLv2 +# Other parameters (config file name, etc) are default. + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ +export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ + +PATH="./bin/:../bin/:../../bin/:${PATH}" turnserver -v --syslog -a -L 127.0.0.1 -L ::1 -E 127.0.0.1 -E ::1 --max-bps=3000000 -f -m 3 --min-port=32355 --max-port=65535 -r north.gov --psql-userdb="host=localhost dbname=coturn user=turn password=turn connect_timeout=30" --cert=turn_server_cert.pem --pkey=turn_server_pkey.pem --log-file=stdout --cipher-list=ALL:SSLv2 $@ + +# Newer PostgreSQL style connection string example: +# PATH="./bin/:../bin/:../../bin/:${PATH}" turnserver -v --syslog -a -L 127.0.0.1 -L ::1 -E 127.0.0.1 -E ::1 --max-bps=3000000 -f -m 3 --min-port=32355 --max-port=65535 -r north.gov --psql-userdb=postgresql://turn:turn@/turn --cert=turn_server_cert.pem --pkey=turn_server_pkey.pem --log-file=stdout --cipher-list=ALL:SSLv2 $@ diff --git a/examples/scripts/longtermsecuredb/secure_relay_with_db_redis.sh b/examples/scripts/longtermsecuredb/secure_relay_with_db_redis.sh new file mode 100755 index 0000000..9800883 --- /dev/null +++ b/examples/scripts/longtermsecuredb/secure_relay_with_db_redis.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# +# This is an example how to start a TURN Server in +# secure mode with Redis database for users +# with the long-term credentials mechanism. +# +# We start here a TURN Server listening on IPv4 address +# 127.0.0.1 and on IPv6 address ::1. We use 127.0.0.1 as +# IPv4 relay address, and we use ::1 as IPv6 relay address. +# +# Other options: +# +# 1) set bandwidth limit on client session 3000000 bytes per second (--max-bps). +# 2) use fingerprints (-f) +# 3) use 3 relay threads (-m 3) +# 4) use min UDP relay port 32355 and max UDP relay port 65535 +# 5) "-r north.gov" means "use authentication realm north.gov" +# 6) --redis-userdb="ip=127.0.0.1 dbname=2 password=turn connect_timeout=30" +# means that local Redis database 0 will be used, +# database password is "turn", and connection timeout 30 seconds. +# 7) --redis-statsdb="ip=127.0.0.1 dbname=3 password=turn connect_timeout=30" +# means that we want to use Redis for status and statistics information, +# and this will be the database number 3. +# 8) "--cert=example_turn_server_cert.pem" sets the OpenSSL certificate file name. +# 9) "--pkey=example_turn_server_pkey.pem" sets the OpenSSL private key name. +# 10) "--log-file=stdout" means that all log output will go to the stdout. +# 11) --cipher-list=ALL:SSLv2 means that we support all OpenSSL ciphers, including SSLv2 +# Other parameters (config file name, etc) are default. + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ +export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ + +PATH="./bin/:../bin/:../../bin/:${PATH}" turnserver -v --syslog -a -L 127.0.0.1 -L ::1 -E 127.0.0.1 -E ::1 --max-bps=3000000 -f -m 3 --min-port=32355 --max-port=65535 -r north.gov --redis-userdb="ip=127.0.0.1 dbname=2 password=turn connect_timeout=30" --redis-statsdb="ip=127.0.0.1 dbname=3 password=turn connect_timeout=30" --cert=turn_server_cert.pem --pkey=turn_server_pkey.pem --log-file=stdout --cipher-list=ALL:SSLv2 $@ diff --git a/examples/scripts/mobile/mobile_dtls_client.sh b/examples/scripts/mobile/mobile_dtls_client.sh new file mode 100755 index 0000000..f8eed85 --- /dev/null +++ b/examples/scripts/mobile/mobile_dtls_client.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# +# This is an example of a script to run a "secure" TURN DTLS client +# with "mobile" option and the long-term credentials mechanism. +# +# Options: +# +# 1) -t is absent, it means that UDP networking is used. +# 2) -S means "SSL protocol with default encryption" +# 3) -i absent. +# 4) -k sets private key file for TLS. +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 170 means that the payload size of the packets is 170 bytes +# (like average audio RTP packet). +# 8) -e 127.0.0.1 means that the clients will use peer IPv4 address 127.0.0.1. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) -u ninefingers means that if the server challenges the client with +# authentication challenge, then we use account "ninefingers". +# 11) -w youhavetoberealistic sets the password for the account. +# 12) -s option means that the client will be using "send" mechanism for data. +# 13) -M turns on the Mobile ICE TURN functionality. +# 14) 127.0.0.1 (the last parameter) is the TURN Server IP address. +# We use IPv6 - to - IPv4 here to illustrate how the TURN Server +# converts the traffic from IPv6 to IPv4 and back. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -S -k turn_client_pkey.pem -n 1000 -m 10 -l 170 -e 127.0.0.1 -X -g -u ninefingers -w youhavetoberealistic -s -M $@ 127.0.0.1 + diff --git a/examples/scripts/mobile/mobile_relay.sh b/examples/scripts/mobile/mobile_relay.sh new file mode 100755 index 0000000..b7ff83c --- /dev/null +++ b/examples/scripts/mobile/mobile_relay.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# +# This is an example how to start a "mobile" TURN Server in +# secure mode (when authentication is used) - see option -a +# that means "use long-term credential mechanism". +# +# We start here a TURN Server listening on IPv4 address +# 127.0.0.1 and on IPv6 address ::1. We use 127.0.0.1 as +# IPv4 relay address, and we use ::1 as IPv6 relay address. +# +# Other options: +# +# 1) set bandwidth limit on client session 3000000 bytes per second (--max-bps). +# 2) use fingerprints (-f) +# 3) use 10 relay threads (-m 10) +# 4) use min UDP relay port 32355 and max UDP relay port 65535 +# 5) "-r north.gov" means "use authentication realm north.gov" +# 6) "--user=ninefingers:0xbc807ee29df3c9ffa736523fb2c4e8ee" means +# "allow user 'ninefinger' with generated key '0xbc807ee29df3c9ffa736523fb2c4e8ee' ". +# 7) "--user=gorst:hero" means "allow user 'gorst' with password 'hero' ". +# 8) "--cert=turn_server_cert.pem" sets the OpenSSL certificate file name. +# 9) "--pkey=turn_server_pkey.pem" sets the OpenSSL private key name. +# 10) "--log-file=stdout" means that all log output will go to the stdout. +# 11) "-v" means normal verbose mode (with some moderate logging). +# 12) "--mobility" turns on the Mobile ICE TURN functionality. +# 13) --cipher-list=ALL:SSLv2 means that we support all OpenSSL ciphers, including SSLv2 +# Other parameters (config file name, etc) are default. + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ +export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ + +PATH="./bin/:../bin/:../../bin/:${PATH}" turnserver --syslog -a -L 127.0.0.1 -L ::1 -E 127.0.0.1 -E ::1 --max-bps=3000000 -f -m 10 --min-port=32355 --max-port=65535 --user=ninefingers:0xbc807ee29df3c9ffa736523fb2c4e8ee --user=gorst:hero -r north.gov --cert=turn_server_cert.pem --pkey=turn_server_pkey.pem --log-file=stdout -v --mobility --cipher-list=ALL:SSLv2 $@ diff --git a/examples/scripts/mobile/mobile_tcp_client.sh b/examples/scripts/mobile/mobile_tcp_client.sh new file mode 100755 index 0000000..4e186b3 --- /dev/null +++ b/examples/scripts/mobile/mobile_tcp_client.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# +# This is an example of a script to run a "secure" "mobile" +# TURN TCP client with the long-term credentials mechanism. +# +# Options: +# +# 1) -t is present, it means that TCP networking is used. +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 170 means that the payload size of the packets is 170 bytes +# (like average audio RTP packet). +# 8) -e 127.0.0.1 means that the clients will use peer address 127.0.0.1. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) -u gorst means that if the server challenges the client with +# authentication challenge, then we use account "gorst". +# 11) -w hero sets the password for the account as "hero". +# 12) -s option is absent - it means that the client will be using +# the "channel" mechanism for data. +# 13) -M turns on the Mobile ICE TURN functionality. +# 14) ::1 (the last parameter) is the TURN Server IP address. We use IPv6 here +# to illustrate how the TURN Server convert the traffic from IPv6 to IPv4 and back. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -t -n 3000 -m 10 -l 170 -e 127.0.0.1 -X -g -u gorst -w hero -M $@ ::1 diff --git a/examples/scripts/mobile/mobile_tls_client_c2c_tcp_relay.sh b/examples/scripts/mobile/mobile_tls_client_c2c_tcp_relay.sh new file mode 100755 index 0000000..bbb017c --- /dev/null +++ b/examples/scripts/mobile/mobile_tls_client_c2c_tcp_relay.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# +# This is an example of a script to run a "secure" TURN TLS client +# with "mobile" option and with the long-term credentials mechanism and with +# TCP relay endpoints (RFC 6062). +# +# Options: +# +# 1) -T is present, it means that TCP networking is used, with TCP +# relay endpoints (RFC 6062. +# 2) -S means that "secure protocol", that is TLS in the case of TCP, +# will be used between the client and the TURN Server. +# 3) -i absent. +# 4) -k sets private key file for TLS. +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 170 means that the payload size of the packets is 170 bytes +# (like average audio RTP packet). +# 8) -y means that the clients will connect to the 'neighbor' clients, no peer app will be used. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) -u gorst means that if the server challenges the client with +# authentication challenge, then we use account "gorst". +# 11) -w hero sets the password for the account as "hero". +# 12) -M turns on the Mobile ICE TURN functionality. +# 13) ::1 (the last parameter) is the TURN Server IP address. We use IPv6 here +# to illustrate how the TURN Server convert the traffic from IPv6 to IPv4 and back. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/:/usr/postgres/9.2-pgdg/lib + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -T -S -k turn_client_pkey.pem -n 1000 -m 10 -l 170 -y -g -u gorst -w hero -M $@ ::1 diff --git a/examples/scripts/mobile/mobile_udp_client.sh b/examples/scripts/mobile/mobile_udp_client.sh new file mode 100755 index 0000000..6f453df --- /dev/null +++ b/examples/scripts/mobile/mobile_udp_client.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# +# This is an example of a script to run a "secure" TURN UDP client +# with "mobile" option and with the long-term credentials mechanism. +# +# Options: +# +# 1) -t is absent, it means that UDP networking is used. +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 170 means that the payload size of the packets is 170 bytes +# (like average audio RTP packet). +# 8) -e 127.0.0.1 means that the clients will use peer address 127.0.0.1. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) -u ninefingers means that if the server challenges the client with +# authentication challenge, then we use account "ninefingers". +# 11) -w youhavetoberealistic sets the password for the account as "youhavetoberealistic". +# 12) -s option is absent - it means that the client will be using +# the "channel" mechanism for data. +# 13) -M turns on the Mobile ICE TURN functionality. +# 14) ::1 (the last parameter) is the TURN Server IP address. We use IPv6 here +# to illustrate how the TURN Server convert the traffic from IPv6 to IPv4 and back. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -n 1000 -m 10 -l 170 -e 127.0.0.1 -X -g -u ninefingers -w youhavetoberealistic -M $@ ::1 + diff --git a/examples/scripts/peer.sh b/examples/scripts/peer.sh new file mode 100755 index 0000000..4e09f71 --- /dev/null +++ b/examples/scripts/peer.sh @@ -0,0 +1,16 @@ +#!/bin/sh +# +# This is a script for the peer application, +# for testing only purposes. It opens UDP echo-like sockets +# on IPv4 address 127.0.0.1 and IPv6 address ::1. +# The default port 3480 is used. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ +export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:bin/:../bin:${PATH} turnutils_peer -L 127.0.0.1 -L ::1 -L 0.0.0.0 $@ diff --git a/examples/scripts/readme.txt b/examples/scripts/readme.txt new file mode 100644 index 0000000..4514771 --- /dev/null +++ b/examples/scripts/readme.txt @@ -0,0 +1,26 @@ +This directory contains various example scripts for the TURN server +functionality illustration. + +1) peer.sh starts the "peer" application that serves as a peer for all examples. + +2) "basic" directory contains set of scripts which works together to demonstrate +very basic anynymous functionality of the TURN server. The "peer.sh" must be used, too. + +3) "longtermsecure" directory contains set of scripts demonstrating the long-term authentication +mechanism (peer.sh to be used, too). + +4) "longtermsecuredb" shows how to start TURN server with database. The clients from the +directory "longtermsecure" can be used with the relay scripts in the "longtermsecuredb" +directory. Of course, the database (PostgreSQL or MySQL) must be set for these scripts +to work correctly. + +5) "restapi" shows how to use TURN REST API. + +6) "shorttermsecure" shows how to use the short-term authentication mechanism. The short term +mechanism is always used with the database. + +7) "loadbalance" shows how to use the simple load-balancing mechanism based upon the +ALTERNATE-SERVER functionality. + + + diff --git a/examples/scripts/restapi/secure_relay_secret.sh b/examples/scripts/restapi/secure_relay_secret.sh new file mode 100755 index 0000000..539742c --- /dev/null +++ b/examples/scripts/restapi/secure_relay_secret.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# +# This is an example how to start a TURN Server in +# secure 'static' 'secret' mode (see TURNServerRESTAPI.pdf) +# with the long-term credentials mechanism. +# +# We start here a TURN Server listening on IPv4 address +# 127.0.0.1 and on IPv6 address ::1. We use 127.0.0.1 as +# IPv4 relay address, and we use ::1 as IPv6 relay address. +# +# Other options: +# +# 1) set bandwidth limit on client session 3000000 bytes per second (--max-bps). +# 2) use fingerprints (-f) +# 3) use 3 relay threads (-m 3) +# 4) use min UDP relay port 32355 and max UDP relay port 65535 +# 5) --use-auth-secret means that we are using 'secret' authentication mode. +# 6) --static-auth-secret=logen means that we will be using 'static' secret value. +# 7) --realm=north.gov sets realm value as "north.gov". +# 8) "--cert=example_turn_server_cert.pem" sets the OpenSSL certificate file name. +# 9) "--pkey=example_turn_server_pkey.pem" sets the OpenSSL private key name. +# 10) "--log-file=stdout" means that all log output will go to the stdout. +# 11) "-q 100" means that single user can create no more than 100 sessions +# 12) "-Q 300" means that there may be no more than 300 sessions totally +# 13) --cipher-list=ALL:SSLv2 means that we support all OpenSSL ciphers, including SSLv2. +# Other parameters (config file name, etc) are default. + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ +export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ + +PATH="./bin/:../bin/:../../bin/:${PATH}" turnserver -v --syslog -a -L 127.0.0.1 -L ::1 -E 127.0.0.1 -E ::1 --max-bps=3000000 -f -m 3 --min-port=32355 --max-port=65535 --use-auth-secret --static-auth-secret=logen --realm=north.gov --cert=turn_server_cert.pem --pkey=turn_server_pkey.pem --log-file=stdout -q 100 -Q 300 --cipher-list=ALL:SSLv2 $@ + diff --git a/examples/scripts/restapi/secure_relay_secret_with_db_mysql.sh b/examples/scripts/restapi/secure_relay_secret_with_db_mysql.sh new file mode 100755 index 0000000..eeab4d2 --- /dev/null +++ b/examples/scripts/restapi/secure_relay_secret_with_db_mysql.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# +# This is an example how to start a TURN Server in +# secure 'dynamic' 'secret' mode (see TURNServerRESTAPI.pdf) +# with MySQL database for users information +# with the long-term credentials mechanism. +# +# We start here a TURN Server listening on IPv4 address +# 127.0.0.1 and on IPv6 address ::1. We use 127.0.0.1 as +# IPv4 relay address, and we use ::1 as IPv6 relay address. +# +# Other options: +# +# 1) set bandwidth limit on client session 3000000 bytes per second (--max-bps). +# 2) use fingerprints (-f) +# 3) use 3 relay threads (-m 3) +# 4) use min UDP relay port 32355 and max UDP relay port 65535 +# 5) --use-auth-secret means that we are using 'secret' authentication mode. +# Absense of --static-auth-secret value means that we will be taking the secret value +# from the database ('dynamic' mode). +# 6) --realm=north.gov sets realm value as "north.gov". +# 7) --mysql-userdb="host=localhost dbname=coturn user=turn password=turn connect_timeout=30" +# means that local MySQL database "coturn" will be used, with database user "turn" and +# with database user password "turn", and connection timeout 30 seconds. +# 8) "--cert=example_turn_server_cert.pem" sets the OpenSSL certificate file name. +# 9) "--pkey=example_turn_server_pkey.pem" sets the OpenSSL private key name. +# 10) "--log-file=stdout" means that all log output will go to the stdout. +# 11) --cipher-list=ALL:SSLv2 means that we support all OpenSSL ciphers, including SSLv2 +# Other parameters (config file name, etc) are default. + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ +export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ + +PATH="./bin/:../bin/:../../bin/:${PATH}" turnserver -v --syslog -a -L 127.0.0.1 -L ::1 -E 127.0.0.1 -E ::1 --max-bps=3000000 -f -m 3 --min-port=32355 --max-port=65535 --use-auth-secret --realm=north.gov --mysql-userdb="host=localhost dbname=coturn user=turn password=turn connect_timeout=30" --cert=turn_server_cert.pem --pkey=turn_server_pkey.pem --log-file=stdout --cipher-list=ALL:SSLv2 $@ diff --git a/examples/scripts/restapi/secure_relay_secret_with_db_psql.sh b/examples/scripts/restapi/secure_relay_secret_with_db_psql.sh new file mode 100755 index 0000000..c0637c0 --- /dev/null +++ b/examples/scripts/restapi/secure_relay_secret_with_db_psql.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# +# This is an example how to start a TURN Server in +# secure 'dynamic' 'secret' mode (see TURNServerRESTAPI.pdf) +# with PostgreSQL database for users information +# with the long-term credentials mechanism. +# +# We start here a TURN Server listening on IPv4 address +# 127.0.0.1 and on IPv6 address ::1. We use 127.0.0.1 as +# IPv4 relay address, and we use ::1 as IPv6 relay address. +# +# Other options: +# +# 1) set bandwidth limit on client session 3000000 bytes per second (--max-bps). +# 2) use fingerprints (-f) +# 3) use 3 relay threads (-m 3) +# 4) use min UDP relay port 32355 and max UDP relay port 65535 +# 5) --use-auth-secret means that we are using 'secret' authentication mode. +# Absense of --static-auth-secret value means that we will be taking the secret value +# from the database ('dynamic' mode). +# 6)--realm=north.gov sets realm value as "north.gov". +# 7) --psql-userdb="host=localhost dbname=coturn user=turn password=turn connect_timeout=30" +# means that local PostgreSQL database "coturn" will be used, with database user "turn" and +# with database user password "turn", and connection timeout 30 seconds. +# 8) "--cert=example_turn_server_cert.pem" sets the OpenSSL certificate file name. +# 9) "--pkey=example_turn_server_pkey.pem" sets the OpenSSL private key name. +# 10) "--log-file=stdout" means that all log output will go to the stdout. +# 11) --cipher-list=ALL:SSLv2 means that we support all OpenSSL ciphers, including SSLv2. +# Other parameters (config file name, etc) are default. + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ +export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ + +PATH="./bin/:../bin/:../../bin/:${PATH}" turnserver -v --syslog -a -L 127.0.0.1 -L ::1 -E 127.0.0.1 -E ::1 --max-bps=3000000 -f -m 3 --min-port=32355 --max-port=65535 --use-auth-secret --realm=north.gov --psql-userdb="host=localhost dbname=coturn user=turn password=turn connect_timeout=30" --cert=turn_server_cert.pem --pkey=turn_server_pkey.pem --log-file=stdout --cipher-list=ALL:SSLv2 $@ diff --git a/examples/scripts/restapi/secure_relay_secret_with_db_redis.sh b/examples/scripts/restapi/secure_relay_secret_with_db_redis.sh new file mode 100755 index 0000000..0b64d56 --- /dev/null +++ b/examples/scripts/restapi/secure_relay_secret_with_db_redis.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# +# This is an example how to start a TURN Server in +# secure 'dynamic' 'secret' mode (see TURNServerRESTAPI.pdf) +# with Redis database for users information +# with the long-term credentials mechanism. +# +# We start here a TURN Server listening on IPv4 address +# 127.0.0.1 and on IPv6 address ::1. We use 127.0.0.1 as +# IPv4 relay address, and we use ::1 as IPv6 relay address. +# +# Other options: +# +# 1) set bandwidth limit on client session 3000000 bytes per second (--max-bps). +# 2) use fingerprints (-f) +# 3) use 3 relay threads (-m 3) +# 4) use min UDP relay port 32355 and max UDP relay port 65535 +# 5) --use-auth-secret means that we are using 'secret' authentication mode. +# Absense of --static-auth-secret value means that we will be taking the secret value +# from the database ('dynamic' mode). +# 6) --realm=north.gov sets realm value as "north.gov". +# 7) --redis-userdb="ip=127.0.0.1 dbname=2 password=turn connect_timeout=30" +# means that local Redis database 0 will be used, with database +# password "turn", and connection timeout 30 seconds. +# 8) "--cert=example_turn_server_cert.pem" sets the OpenSSL certificate file name. +# 9) "--pkey=example_turn_server_pkey.pem" sets the OpenSSL private key name. +# 10) "--log-file=stdout" means that all log output will go to the stdout. +# 11) --cipher-list=ALL:SSLv2 means that we support all OpenSSL ciphers, including SSLv2. +# Other parameters (config file name, etc) are default. + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ +export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ + +PATH="./bin/:../bin/:../../bin/:${PATH}" turnserver -v --syslog -a -L 127.0.0.1 -L ::1 -E 127.0.0.1 -E ::1 --max-bps=3000000 -f -m 3 --min-port=32355 --max-port=65535 --use-auth-secret --realm=north.gov --redis-userdb="ip=127.0.0.1 dbname=2 password=turn connect_timeout=30" --cert=turn_server_cert.pem --pkey=turn_server_pkey.pem --log-file=stdout --redis-statsdb="ip=127.0.0.1 dbname=3 password=turn connect_timeout=30" --cipher-list=ALL:SSLv2 $@ diff --git a/examples/scripts/restapi/secure_udp_client_with_secret.sh b/examples/scripts/restapi/secure_udp_client_with_secret.sh new file mode 100755 index 0000000..fc5e0fe --- /dev/null +++ b/examples/scripts/restapi/secure_udp_client_with_secret.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# +# This is an example of a script to run a "secure" TURN UDP client +# with the long-term credentials mechanism and with +# secret-based authorization (see TURNServerRESTAPI.pdf document). +# +# Options: +# +# 1) -t is absent, it means that UDP networking is used. +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 170 means that the payload size of the packets is 170 bytes +# (like average audio RTP packet). +# 8) -e 127.0.0.1 means that the clients will use peer address 127.0.0.1. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) -u ninefingers means that if the server challenges the client with +# authentication challenge, then we use account "ninefingers". +# 11) -W logen sets the secret for the secret-based authentication as "logen". +# 12) -s option is absent - it means that the client will be using +# the "channel" mechanism for data. +# 13) ::1 (the last parameter) is the TURN Server IPv6 address. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -z 5 -n 10000 -s -m 10 -l 170 -e 127.0.0.1 -X -g -u ninefingers -W logen $@ ::1 diff --git a/examples/scripts/restapi/shared_secret_maintainer.pl b/examples/scripts/restapi/shared_secret_maintainer.pl new file mode 100755 index 0000000..90c5d86 --- /dev/null +++ b/examples/scripts/restapi/shared_secret_maintainer.pl @@ -0,0 +1,105 @@ +#!/usr/bin/perl + +# +# This is an example of Perl script maintaining dynamic shared secret +# database for the REST API +# + +use strict; +use warnings; + +use DBI; +use HTTP::Request::Common; + +my $DBNAME="turn"; +my $DBUSERNAME="turn"; +my $DBPWD="turn"; +my $DBHOST="localhost"; + +my $webserver = 'http://example.com/'; + +my $old_secret = ""; +my $current_secret=""; + +my $INTERVAL=3600; + +my $dbh; + +$dbh = DBI->connect("DBI:mysql:$DBNAME;host=$DBHOST", $DBUSERNAME, $DBPWD) + || die "Could not connect to database: $DBI::errstr"; + +$dbh->do('CREATE TABLE IF NOT EXISTS turn_secret (value varchar(512))'); + +my $c = $dbh->do("delete from turn_secret"); +print "Deleted $c rows\n"; + +$dbh->disconnect(); + +do { + + $dbh = DBI->connect("DBI:mysql:$DBNAME;host=$DBHOST", $DBUSERNAME, $DBPWD) + || die "Could not connect to database: $DBI::errstr"; + + $dbh->do('CREATE TABLE IF NOT EXISTS turn_secret (value varchar(512))'); + + if(length($current_secret)) { + if(length($old_secret)) { + remove_secret($dbh, $old_secret); + } + $old_secret=$current_secret; + } + + print "CURRENT SECRET TO BE (RE)GENERATED\n"; + $current_secret = generate_secret(); + insert_secret($dbh, $current_secret); + + $dbh->disconnect(); + +# +# Web server interaction example: +# Here we can put code to submit this secret to the web server: +# + my $req = POST($webserver, Content => [param => $current_secret]); + + $req->method('PUT'); + + print $req->as_string,"\n"; + +# +# Alternatively, you can use this link for compute-on-demand: +# https://github.com/alfreddatakillen/computeengineondemand +# +# write your code here. +# + + sleep($INTERVAL); + +} while(1); + +sub remove_secret { + + my $dbh = shift; + my $secret=shift; + + my $c = $dbh->do("delete from turn_secret where value = '$secret'"); + print "Deleted $c rows\n"; + +} + +sub insert_secret { + + my $dbh = shift; + my $secret=shift; + + my $c = $dbh->do("insert into turn_secret values('$secret')"); + print "Inserted $c rows\n"; + +} + +sub generate_secret { + my @chars = ('0'..'9', 'A'..'F'); + my $len = 8; + my $string; + while($len--){ $string .= $chars[rand @chars] }; + return $string; +} diff --git a/examples/scripts/rfc5769.sh b/examples/scripts/rfc5769.sh new file mode 100755 index 0000000..afa3622 --- /dev/null +++ b/examples/scripts/rfc5769.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# +# This is a script for RFC 5769 STUN protocol check. +# It checks whether the main code was compiled correctly. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ +export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:bin/:../bin:${PATH} turnutils_rfc5769check $@ diff --git a/examples/scripts/selfloadbalance/secure_dos_attack.sh b/examples/scripts/selfloadbalance/secure_dos_attack.sh new file mode 100755 index 0000000..75189d6 --- /dev/null +++ b/examples/scripts/selfloadbalance/secure_dos_attack.sh @@ -0,0 +1,130 @@ +#!/bin/sh +# +# This is an example of a script to run a DOS attack +# in a "secure" environment on a server with +# self-load-balancing option +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +export SLEEP_TIME=9 + +while [ 0 ] ; do + +rm -rf /var/log/turnserver/* + +########################## + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -G -n 30 -m 10 -l 170 -g -u ninefingers -w youhavetoberealistic -y $@ ::1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -G -n 30 -m 10 -l 170 -e 127.0.0.1 -X -g -u ninefingers -w youhavetoberealistic $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -G -S -k turn_client_pkey.pem -n 10 -m 10 -l 170 -e ::1 -x -g -u ninefingers -w youhavetoberealistic -s $@ 127.0.0.1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -G -t -n 50 -m 10 -l 170 -e 127.0.0.1 -X -g -u gorst -w hero $@ ::1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -G -T -n 30 -m 10 -l 170 -y -g -u gorst -w hero $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -G -T -S -k turn_client_pkey.pem -n 30 -m 10 -l 170 -y -g -u gorst -w hero $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -G -t -S -k turn_client_pkey.pem -n 30 -m 10 -l 170 -e 127.0.0.1 -X -g -u gorst -w hero $@ ::1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -G -n 30 -m 10 -l 170 -g -u ninefingers -w youhavetoberealistic -y -p 12345 $@ ::1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -G -n 30 -m 10 -l 170 -e 127.0.0.1 -X -g -u ninefingers -w youhavetoberealistic -p 12345 $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -G -S -k turn_client_pkey.pem -n 30 -m 10 -l 170 -e ::1 -x -g -u ninefingers -w youhavetoberealistic -s -p 12345 $@ 127.0.0.1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -G -t -n 50 -m 10 -l 170 -e 127.0.0.1 -X -g -u gorst -w hero -p 12345 $@ ::1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -G -T -n 30 -m 10 -l 170 -y -g -u gorst -w hero -p 12345 $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -G -T -S -k turn_client_pkey.pem -n 30 -m 10 -l 170 -y -g -u gorst -w hero -p 12345 $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -G -t -S -k turn_client_pkey.pem -n 30 -m 10 -l 170 -e 127.0.0.1 -X -g -u gorst -w hero -p 12345 $@ ::1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -G -n 30 -m 10 -l 170 -g -u ninefingers -w youhavetoberealistic -y -p 12346 $@ ::1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -G -n 30 -m 10 -l 170 -e 127.0.0.1 -X -g -u ninefingers -w youhavetoberealistic -p 12346 $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -G -S -k turn_client_pkey.pem -n 30 -m 10 -l 170 -e ::1 -x -g -u ninefingers -w youhavetoberealistic -s -p 12346 $@ 127.0.0.1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -G -t -n 50 -m 10 -l 170 -e 127.0.0.1 -X -g -u gorst -w hero -p 12346 $@ ::1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -G -T -n 30 -m 10 -l 170 -y -g -u gorst -w hero -p 12346 $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -G -T -S -k turn_client_pkey.pem -n 30 -m 10 -l 170 -y -g -u gorst -w hero -p 12346 $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -G -t -S -k turn_client_pkey.pem -n 30 -m 10 -l 170 -e 127.0.0.1 -X -g -u gorst -w hero -p 12346 $@ ::1 & + +########################### + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -O -N -R -G -n 30 -m 10 -l 170 -g -u ninefingers -w youhavetoberealistic -y $@ ::1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -O -N -R -G -n 30 -m 10 -l 170 -e 127.0.0.1 -X -g -u ninefingers -w youhavetoberealistic $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -O -N -R -G -S -k turn_client_pkey.pem -n 10 -m 10 -l 170 -e ::1 -x -g -u ninefingers -w youhavetoberealistic -s $@ 127.0.0.1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -O -N -R -G -t -n 50 -m 10 -l 170 -e 127.0.0.1 -X -g -u gorst -w hero $@ ::1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -O -N -R -G -T -n 30 -m 10 -l 170 -y -g -u gorst -w hero $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -O -N -R -G -T -S -k turn_client_pkey.pem -n 30 -m 10 -l 170 -y -g -u gorst -w hero $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -O -N -R -G -t -S -k turn_client_pkey.pem -n 30 -m 10 -l 170 -e 127.0.0.1 -X -g -u gorst -w hero $@ ::1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -O -N -R -G -n 30 -m 10 -l 170 -g -u ninefingers -w youhavetoberealistic -y -p 12345 $@ ::1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -O -N -R -G -n 30 -m 10 -l 170 -e 127.0.0.1 -X -g -u ninefingers -w youhavetoberealistic -p 12345 $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -O -N -R -G -S -k turn_client_pkey.pem -n 30 -m 10 -l 170 -e ::1 -x -g -u ninefingers -w youhavetoberealistic -s -p 12345 $@ 127.0.0.1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -O -N -R -G -t -n 50 -m 10 -l 170 -e 127.0.0.1 -X -g -u gorst -w hero -p 12345 $@ ::1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -O -N -R -G -T -n 30 -m 10 -l 170 -y -g -u gorst -w hero -p 12345 $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -O -N -R -G -T -S -k turn_client_pkey.pem -n 30 -m 10 -l 170 -y -g -u gorst -w hero -p 12345 $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -O -N -R -G -t -S -k turn_client_pkey.pem -n 30 -m 10 -l 170 -e 127.0.0.1 -X -g -u gorst -w hero -p 12345 $@ ::1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -O -N -R -G -n 30 -m 10 -l 170 -g -u ninefingers -w youhavetoberealistic -y -p 12346 $@ ::1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -O -N -R -G -n 30 -m 10 -l 170 -e 127.0.0.1 -X -g -u ninefingers -w youhavetoberealistic -p 12346 $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -O -N -R -G -S -k turn_client_pkey.pem -n 30 -m 10 -l 170 -e ::1 -x -g -u ninefingers -w youhavetoberealistic -s -p 12346 $@ 127.0.0.1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -O -N -R -G -t -n 50 -m 10 -l 170 -e 127.0.0.1 -X -g -u gorst -w hero -p 12346 $@ ::1 & + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -O -N -R -G -T -n 30 -m 10 -l 170 -y -g -u gorst -w hero -p 12346 $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -O -N -R -G -T -S -k turn_client_pkey.pem -n 30 -m 10 -l 170 -y -g -u gorst -w hero -p 12346 $@ ::1 & + +PATH=examples/bin/:../bin:./bin/:${PATH} turnutils_uclient -O -N -R -G -t -S -k turn_client_pkey.pem -n 30 -m 10 -l 170 -e 127.0.0.1 -X -g -u gorst -w hero -p 12346 $@ ::1 & + +######################### + +sleep ${SLEEP_TIME} + +type killall >>/dev/null 2>>/dev/null +ER=$? +if [ ${ER} -eq 0 ] ; then + killall turnutils_uclient >>/dev/null 2>>/dev/null +fi + +type pkill >>/dev/null 2>>/dev/null +ER=$? +if [ ${ER} -eq 0 ] ; then + pkill turnutils_u >>/dev/null 2>>/dev/null + pkill turnutils_uclie >>/dev/null 2>>/dev/null + pkill turnutils_uclient >>/dev/null 2>>/dev/null +else + sleep 10 +fi + +done + + diff --git a/examples/scripts/selfloadbalance/secure_relay.sh b/examples/scripts/selfloadbalance/secure_relay.sh new file mode 100755 index 0000000..a537ada --- /dev/null +++ b/examples/scripts/selfloadbalance/secure_relay.sh @@ -0,0 +1,42 @@ +#!/bin/sh +# +# This is an example how to start a TURN Server +# with self-udp-balancing, in secure mode +# (when authentication is used) - see option -a +# that means "use long-term credential mechanism". +# +# We start here a TURN Server listening on IPv4 address +# 127.0.0.1 and on IPv6 address ::1. We use 127.0.0.1 as +# IPv4 relay address, and we use ::1 as IPv6 relay address. +# +# Other options: +# +# 1) --aux-server=... options start two auxiliary severs on IP address 127.0.0.1 +# and ports 12345 and 12346, and two auxiliary servers on IP adress ::1 +# with the same ports. +# 2) --self-udp-balance option forces the server to distribute the load from the +# main server points to the auxiliary servers through the ALTERNATE-SERVER +# mechanism. +# 3) set bandwidth limit on client session 3000000 bytes per second (--max-bps). +# 4) use fingerprints (-f) +# 5) use 10 relay threads (-m 10) +# 6) use min UDP relay port 32355 and max UDP relay port 65535 +# 7) "-r north.gov" means "use authentication realm north.gov" +# 8) "--user=ninefingers:youhavetoberealistic" means +# "allow user 'ninefinger' with password 'youhavetoberealistic' ". +# 9) "--user=gorst:hero" means "allow user 'gorst' with password 'hero' ". +# 10) "--cert=example_turn_server_cert.pem" sets the OpenSSL certificate file name. +# 11) "--pkey=example_turn_server_pkey.pem" sets the OpenSSL private key name. +# 12) "--log-file=stdout" means that all log output will go to the stdout. +# 13) "-v" means normal verbose mode (with some moderate logging). +# 14) --cipher-list=ALL:SSLv2 means that we support all OpenSSL ciphers, including SSLv2. +# Other parameters (config file name, etc) are default. + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ +export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ + +PATH="./bin/:../bin/:../../bin/:${PATH}" turnserver --aux-server=127.0.0.1:12345 --aux-server=[::1]:12345 --aux-server=127.0.0.1:12346 --aux-server=[::1]:12346 --udp-self-balance --syslog -a -L 127.0.0.1 -L ::1 -E 127.0.0.1 -E ::1 --max-bps=3000000 -f -m 10 --min-port=32355 --max-port=65535 --user=ninefingers:youhavetoberealistic --user=gorst:hero -r north.gov --cert=turn_server_cert.pem --pkey=turn_server_pkey.pem --log-file=stdout --cipher-list=ALL:SSLv2 $@ diff --git a/examples/scripts/shorttermsecure/secure_relay_short_term_mech.sh b/examples/scripts/shorttermsecure/secure_relay_short_term_mech.sh new file mode 100755 index 0000000..e7cfeb5 --- /dev/null +++ b/examples/scripts/shorttermsecure/secure_relay_short_term_mech.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# +# This is an example how to start a TURN Server in +# secure mode with short-term security mechanism - see option -A +# that means "use short-term credential mechanism". +# +# The short-term credentials mechanism must be used with PostgreSQL or +# MySQL database only, the flat file userdb cannot be used. +# +# We listen on available interfaces here, and we use the "external" IPs +# for relay endpoints allocation. +# +# Other options: +# +# 1) set bandwidth limit on client session 3000000 bytes per second (--max-bps). +# 2) use fingerprints (-f) +# 3) use 3 relay threads (-m 3) +# 4) use min UDP relay port 32355 and max UDP relay port 65535 +# 5) --mysql-userdb="host=localhost dbname=coturn user=turn password=turn connect_timeout=30" +# means that local MySQL database "coturn" will be used, with database user "turn" and +# database user password "turn", and connection timeout 30 seconds. +# 6) "--cert=example_turn_server_cert.pem" sets the OpenSSL certificate file name. +# 7) "--pkey=example_turn_server_pkey.pem" sets the OpenSSL private key name. +# 8) "--log-file=stdout" means that all log output will go to the stdout. +# 9) -E 127.0.0.1 and -E :;1 sets the relay addresses, in this case for loopback +# communications only. +# 10) --cipher-list=ALL:SSLv2 means that we support all OpenSSL ciphers, including SSLv2. +# Other parameters (config file name, etc) are default. + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ +export DYLD_LIBRARY_PATH=${DYLD_LIBRARY_PATH}:/usr/local/lib/:/usr/local/mysql/lib/ + +PATH="./bin/:../bin/:../../bin/:${PATH}" turnserver -v --syslog -A --max-bps=3000000 -f -m 3 --min-port=32355 --max-port=65535 --mysql-userdb="host=localhost dbname=coturn user=turn password=turn connect_timeout=30" --cert=turn_server_cert.pem --pkey=turn_server_pkey.pem --log-file=stdout -E 127.0.0.1 -E ::1 --cipher-list=ALL:SSLv2 $@ diff --git a/examples/scripts/shorttermsecure/secure_tcp_client_c2c_tcp_relay_short_term.sh b/examples/scripts/shorttermsecure/secure_tcp_client_c2c_tcp_relay_short_term.sh new file mode 100755 index 0000000..a68a079 --- /dev/null +++ b/examples/scripts/shorttermsecure/secure_tcp_client_c2c_tcp_relay_short_term.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# +# This is an example of a script to run a "secure" TURN TCP client +# with the short-term credentials mechanism and with +# TCP relay endpoints (RFC 6062). +# +# Options: +# +# 1) -T is present, it means that TCP networking is used, with TCP relay endpoints (RFC 6062). +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 170 means that the payload size of the packets is 170 bytes +# (like average audio RTP packet). +# 8) -y means that the clients will connect to the 'neighbor' clients, no peer app will be used. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) -A sets the short-term credentials mechanism. +# 11) -u gorst sets the client user name. +# 12) -w hero sets the password for the account as "hero". +# 13) ::1 (the last parameter) is the TURN Server IP address. We use IPv6 here +# to illustrate how the TURN Server convert the traffic from IPv6 to IPv4 and back. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -T -n 1000 -m 10 -l 170 -y -g -A -u gorst -w hero $@ ::1 + diff --git a/examples/scripts/shorttermsecure/secure_udp_client_short_term.sh b/examples/scripts/shorttermsecure/secure_udp_client_short_term.sh new file mode 100755 index 0000000..792b482 --- /dev/null +++ b/examples/scripts/shorttermsecure/secure_udp_client_short_term.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# +# This is an example of a script to run a "secure" TURN UDP client +# with short-term credential mechanism. +# +# Options: +# +# 1) -t is absent, it means that UDP networking is used. +# 5) -n 1000 means 1000 messages per single emulated client. Messages +# are sent with interval of 20 milliseconds, to emulate an RTP stream. +# 6) -m 10 means that 10 clients are emulated. +# 7) -l 170 means that the payload size of the packets is 170 bytes +# (like average audio RTP packet). +# 8) -e 127.0.0.1 means that the clients will use peer address 127.0.0.1. +# 9) -g means "set DONT_FRAGMENT parameter in TURN requests". +# 10) -A means that the short-term credentials mechanism is used. +# 11) -u ninefingers sets the client user name. +# 12) -w youhavetoberealistic sets the password for the user account as "youhavetoberealistic". +# 13) -s option means that the client will be using "send" indication for data trasfer. +# 14) ::1 (the last parameter) is the TURN Server IP address. We use IPv6 here +# to illustrate how the TURN Server convert the traffic from IPv6 to IPv4 and back. +# + +if [ -d examples ] ; then + cd examples +fi + +export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/local/lib/ + +PATH=examples/bin/:../bin/:./bin/:${PATH} turnutils_uclient -n 1000 -m 10 -l 170 -e 127.0.0.1 -X -g -A -u ninefingers -w youhavetoberealistic -s $@ ::1 diff --git a/make-man.sh b/make-man.sh new file mode 100755 index 0000000..a36132c --- /dev/null +++ b/make-man.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +rm -rf man/man1/* + +txt2man -s 1 -t TURN -I turnserver -I turnadmin -I turnutils -I turnutils_uclient -I turnutils_stunclient -I turnutils_rfc5769check -I turnutils_peer -B "TURN Server" README.turnserver | sed -e 's/-/\\-/g' > man/man1/turnserver.1 + +txt2man -s 1 -t TURN -I turnserver -I turnadmin -I turnutils -I turnutils_uclient -I turnutils_stunclient -I turnutils_rfc5769check -I turnutils_peer -B "TURN Server" README.turnadmin | sed -e 's/-/\\-/g'> man/man1/turnadmin.1 + +txt2man -s 1 -t TURN -I turnserver -I turnadmin -I turnutils -I turnutils_uclient -I turnutils_stunclient -I turnutils_rfc5769check -I turnutils_peer -B "TURN Server" README.turnutils | sed -e 's/-/\\-/g' > man/man1/turnutils.1 + +cd man/man1; ln -s turnutils.1 turnutils_uclient.1;cd ../.. +cd man/man1; ln -s turnutils.1 turnutils_peer.1;cd ../.. +cd man/man1; ln -s turnutils.1 turnutils_stunclient.1;cd ../.. +cd man/man1; ln -s turnserver.1 coturn.1;cd ../.. + diff --git a/man/man1/coturn.1 b/man/man1/coturn.1 new file mode 120000 index 0000000..9db33ed --- /dev/null +++ b/man/man1/coturn.1 @@ -0,0 +1 @@ +turnserver.1 \ No newline at end of file diff --git a/man/man1/turnadmin.1 b/man/man1/turnadmin.1 new file mode 100644 index 0000000..7537c85 --- /dev/null +++ b/man/man1/turnadmin.1 @@ -0,0 +1,325 @@ +.\" Text automatically generated by txt2man +.TH TURN 1 "20 April 2014" "" "" +.SH GENERAL INFORMATION + +\fIturnadmin\fP is a TURN administration tool. This tool can be used to manage +the user accounts (add/remove users, generate +TURN keys for the users). For security reasons, we do not recommend +storing passwords openly. The better option is to use pre\-processed "keys" +which are then used for authentication. These keys are generated by \fIturnadmin\fP. +Turnadmin is a link to \fIturnserver\fP binary, but \fIturnadmin\fP performs different +functions. +.PP +Options note: \fIturnadmin\fP has long and short option names, for most options. +Some options have only long form, some options have only short form. Their syntax +somewhat different, if an argument is required: +.PP +The short form must be used as this (for example): +.PP +.nf +.fam C + $ turnadmin \-u \.\.\. + +.fam T +.fi +The long form equivalent must use the "=" character: +.PP +.nf +.fam C + $ turnadmin \-\-user= \.\.\. + +.fam T +.fi +If this is a flag option (no argument required) then their usage are the same, for example: +.PP +.nf +.fam C + $ turnadmin \-k \.\.\. + +.fam T +.fi +is equivalent to: +.PP +.nf +.fam C + $ turnadmin \-\-key \.\.\. + +.fam T +.fi +You have always the use the \fB\-r\fP option with commands for long term credentials \- +because data for multiple realms can be stored in the same database. +.PP +===================================== +.SS NAME +\fB +\fBturnadmin \fP\- a TURN relay administration tool. +\fB +.SS SYNOPSIS + +$ \fIturnadmin\fP [command] [options] +.PP +$ \fIturnadmin\fP [ \fB\-h\fP | \fB\-\-help\fP] +.SS DESCRIPTION + +.TP +.B +Commands: +.TP +.B +\fB\-k\fP, \fB\-\-key\fP +Generate key for a long\-term credentials mechanism user. +.TP +.B +\fB\-a\fP, \fB\-\-add\fP +Add or update a long\-term user. +.TP +.B +\fB\-A\fP, \fB\-\-add\-st\fP +Add or update a short\-term credentials mechanism user. +.TP +.B +\fB\-d\fP, \fB\-\-delete\fP +Delete a long\-term user. +.TP +.B +\fB\-D\fP, \fB\-\-delete\-st\fP +Delete a short\-term user. +.TP +.B +\fB\-l\fP, \fB\-\-list\fP +List long\-term users in the database. +.TP +.B +\fB\-L\fP, \fB\-\-list\-st\fP +List short\-term users in the database. +.PP +\fB\-s\fP, \fB\-\-set\-secret\fP= Add shared secret for TURN RESP API +.TP +.B +\fB\-S\fP, \fB\-\-show\-secret\fP +Show stored shared secrets for TURN REST API +.PP +\fB\-X\fP, \fB\-\-delete\-secret\fP= Delete a shared secret. +.RS +.TP +.B +\fB\-\-delete\-all_secrets\fP +Delete all shared secrets for REST API. +.RE +.TP +.B +\fB\-O\fP, \fB\-\-add\-origin\fP +Add origin\-to\-realm relation. +.TP +.B +\fB\-R\fP, \fB\-\-del\-origin\fP +Delete origin\-to\-realm relation. +.TP +.B +\fB\-I\fP, \fB\-\-list\-origins\fP +List origin\-to\-realm relations. +.TP +.B +\fB\-g\fP, \fB\-\-set\-realm\-option\fP +Set realm params: max\-bps, total\-quota, user\-quota. +.TP +.B +\fB\-G\fP, \fB\-\-list\-realm\-options\fP +List realm params. +.PP +NOTE: if you are using the flat file for the user database, then you will have +to use a text editor to set or show the shared secrets. +.PP +NOTE: the origin functionality is not supported with flat user db file, +a "real" database must be used. +.TP +.B +Options with required values: +.TP +.B +\fB\-b\fP, \fB\-\-userdb\fP +File\-based user database file name (default \- turnuserdb.conf). +See the \fB\-\-userdb\fP option in the \fIturnserver\fP section. +.TP +.B +\fB\-e\fP, \fB\-\-psql\-userdb\fP +PostgreSQL user database connection string. +See the \fB\-\-psql\-userdb\fP option in the \fIturnserver\fP section. +.TP +.B +\fB\-M\fP, \fB\-\-mysql\-userdb\fP +MySQL user database connection string. +See the \fB\-\-mysql\-userdb\fP option in the \fIturnserver\fP section. +.TP +.B +\fB\-N\fP, \fB\-\-redis\-userdb\fP +Redis user database connection string. +See the \fB\-\-redis\-userdb\fP option in the \fIturnserver\fP section. +.TP +.B +\fB\-u\fP, \fB\-\-user\fP +User name. +.TP +.B +\fB\-r\fP, \fB\-\-realm\fP +Realm, for long\-term credentials mechanism only. +.TP +.B +\fB\-p\fP, \fB\-\-password\fP +Password. +.TP +.B +\fB\-o\fP, \fB\-\-origin\fP +Origin +.TP +.B +\fB\-H\fP, \fB\-\-sha256\fP +Use SHA256 as the keys hash function (a non\-standard feature). +By default, MD5 is used for the key storage encryption +(as required by the current STUN/TURNstandards). +.TP +.B +\fB\-\-max\-bps\fP +Set value of realm's max\-bps parameter. +.TP +.B +\fB\-\-total\-quota\fP +Set value of realm's total\-quota parameter. +.TP +.B +\fB\-\-user\-quota\fP +Set value of realm's user\-quota parameter. +.TP +.B +\fB\-h\fP, \fB\-\-help\fP +Help. +.TP +.B +Generate a key: +.PP +$ \fIturnadmin\fP \fB\-k\fP \fB\-u\fP \fB\-r\fP \fB\-p\fP +.PP +Add/update a user in the userdb file or in the database: +.PP +$ \fIturnadmin\fP \fB\-a\fP [\fB\-b\fP | \fB\-e\fP | \fB\-M\fP | \fB\-N\fP ] \fB\-u\fP \fB\-r\fP \fB\-p\fP +.PP +Delete a user from the userdb file or from the database: +.PP +$ \fIturnadmin\fP \fB\-d\fP [\fB\-b\fP | \fB\-e\fP | \fB\-M\fP | \fB\-N\fP ] \fB\-u\fP \fB\-r\fP +.PP +List all long\-term users in MySQL database: +.PP +$ \fIturnadmin\fP \fB\-l\fP \fB\-\-mysql\-userdb\fP="" \fB\-r\fP +.PP +List all short\-term users in Redis database: +.PP +$ \fIturnadmin\fP \fB\-L\fP \fB\-\-redis\-userdb\fP="" +.PP +Set secret in MySQL database: +.PP +$ \fIturnadmin\fP \fB\-s\fP \fB\-\-mysql\-userdb\fP="" \fB\-r\fP +.PP +Show secret stored in PostgreSQL database: +.PP +$ \fIturnadmin\fP \fB\-S\fP \fB\-\-psql\-userdb\fP="" \fB\-r\fP +.PP +Set origin\-to\-realm relation in MySQL database: +.PP +$ \fIturnadmin\fP \fB\-\-mysql\-userdb\fP="" \fB\-r\fP \fB\-o\fP +.PP +Delete origin\-to\-realm relation from Redis DB: +.PP +$ \fIturnadmin\fP \fB\-\-redis\-userdb\fP="" \fB\-o\fP +.PP +List all origin\-to\-realm relations in Redis DB: +.PP +$ \fIturnadmin\fP \fB\-\-redis\-userdb\fP="" \fB\-I\fP +.PP +List the origin\-to\-realm relations in PostgreSQL DB for a single realm: +.PP +$ \fIturnadmin\fP \fB\-\-psql\-userdb\fP="" \fB\-I\fP \fB\-r\fP +.TP +.B +Help: +.PP +$ \fIturnadmin\fP \fB\-h\fP +.PP +======================================= +.SS DOCS + +After installation, run the command: +.PP +$ man \fIturnadmin\fP +.PP +or in the project root directory: +.PP +$ man \fB\-M\fP man \fIturnadmin\fP +.PP +to see the man page. +.PP +===================================== +.SS FILES + +/etc/turnserver.conf +.PP +/etc/turnuserdb.conf +.PP +/usr/local/etc/turnserver.conf +.PP +/usr/local/etc/turnuserdb.conf +.PP +===================================== +.SS DIRECTORIES + +/usr/local/share/\fIturnserver\fP +.PP +/usr/local/share/doc/\fIturnserver\fP +.PP +/usr/local/share/examples/\fIturnserver\fP +.PP +====================================== +.SS SEE ALSO + +\fIturnserver\fP, \fIturnutils\fP +.RE +.PP +====================================== +.SS WEB RESOURCES + +project page: +.PP +http://code.google.com/p/coturn/ +.PP +Wiki page: +.PP +http://code.google.com/p/coturn/wiki/Readme +.PP +forum: +.PP +https://groups.google.com/forum/?fromgroups=#!forum/turn\-server\-project\-rfc5766\-turn\-server/ +.RE +.PP +====================================== +.SS AUTHORS + +Oleg Moskalenko +.PP +Gabor Kovesdan http://kovesdan.org/ +.PP +Daniel Pocock http://danielpocock.com/ +.PP +John Selbie (jselbie@gmail.com) +.PP +Lee Sylvester +.PP +Erik Johnston +.PP +Roman Lisagor +.PP +Vladimir Tsanev +.PP +Po\-sheng Lin +.PP +Peter Dunkley +.PP +Mutsutoshi Yoshimoto diff --git a/man/man1/turnserver.1 b/man/man1/turnserver.1 new file mode 100644 index 0000000..ac25cd5 --- /dev/null +++ b/man/man1/turnserver.1 @@ -0,0 +1,1121 @@ +.\" Text automatically generated by txt2man +.TH TURN 1 "20 April 2014" "" "" +.SH GENERAL INFORMATION + +The \fBTURN Server\fP project contains the source code of a TURN server and TURN client +messaging library. Also, some extra programs provided, for testing\-only +purposes. +.PP +See the INSTALL file for the building instructions. +.PP +After the build, you will have the following binary images: +.TP +.B +1. +\fIturnserver\fP: \fBTURN Server\fP relay. +The compiled binary image of the \fBTURN Server\fP program is located in bin/ sub\-directory. +.TP +.B +2. +\fIturnadmin\fP: TURN administration tool. See README.turnadmin and \fIturnadmin\fP man page. +.TP +.B +3. +turnutils_uclient. See README.turnutils and \fIturnutils\fP man page. +.TP +.B +4. +turnutils_peer. See README.turnutils and \fIturnutils\fP man page. +.TP +.B +5. +turnutils_stunclient. See README.turnutils and \fIturnutils\fP man page. +.TP +.B +6. +turnutils_rfc5769check. See README.turnutils and \fIturnutils\fP man page. +.PP +In the "examples/scripts" sub\-directory, you will find the examples of command lines to be used +to run the programs. The scripts are meant to be run from examples/ sub\-directory, for example: +.PP +$ cd examples +$ ./scripts/secure_relay.sh +.SH RUNNING THE TURN SERVER + +Options note: \fIturnserver\fP has long and short option names, for most options. +Some options have only long form, some options have only short form. Their syntax +somewhat different, if an argument is required: +.PP +The short form must be used as this (for example): +.PP +.nf +.fam C + $ turnserver \-L 12.34.56.78 + +.fam T +.fi +The long form equivalent must use the "=" character: +.PP +.nf +.fam C + $ turnserver \-\-listening\-ip=12.34.56.78 + +.fam T +.fi +If this is a flag option (no argument required) then their usage are the same, for example: +.PP +.nf +.fam C + $ turnserver \-a + +.fam T +.fi +is equivalent to: +.PP +.nf +.fam C + $ turnserver \-\-lt\-cred\-mech + +.fam T +.fi +===================================== +.SS NAME +\fB +\fBturnserver \fP\- a TURN relay server implementation. +\fB +.SS SYNOPSIS +.nf +.fam C + +$ \fIturnserver\fP [\fB\-n\fP | \fB\-c\fP ] [\fIflags\fP] [ \fB\-\-userdb\fP= | \fB\-\-psql\-userdb\fP= | \fB\-\-mysql\-userdb\fP= | \fB\-\-redis\-userdb\fP= ] [\fB\-z\fP | \fB\-\-no\-auth\fP | \fB\-a\fP | \fB\-\-lt\-cred\-mech\fP ] [\fIoptions\fP] +$ \fIturnserver\fP \fB\-h\fP + +.fam T +.fi +.fam T +.fi +.SS DESCRIPTION + +.TP +.B +Config file settings: +.TP +.B +\fB\-n\fP +Do not use configuration file, use only command line parameters. +.TP +.B +\fB\-c\fP +Configuration file name (default \- turnserver.conf). +The format of config file can be seen in +the supplied examples/etc/turnserver.conf example file. Long +names of the \fIoptions\fP are used as the configuration +items names in the file. If not an absolute path is supplied, +then the file is searched in the following directories: +.RS +.IP \(bu 3 +current directory +.IP \(bu 3 +current directory etc/ sub\-directory +.IP \(bu 3 +upper directory level etc/ +.IP \(bu 3 +/etc/ +.IP \(bu 3 +/usr/local/etc/ +.IP \(bu 3 +installation directory /etc +.RE +.TP +.B +User database settings: +.TP +.B +\fB\-b\fP, \fB\-\-userdb\fP +User database file name (default \- turnuserdb.conf), +for long\-term credentials mechanism only. +This user file database is being dynamically checked while the \fIturnserver\fP +is working, and the user accounts can be changed dynamically by +editing the database. +.TP +.B +\fB\-e\fP, \fB\-\-psql\-userdb\fP +User database connection string for PostgreSQL. +This database can be used for long\-term and short\-term credentials mechanisms, +and it can store the secret value for secret\-based timed authentication in TURN RESP API. +The connection string format is like that: +.RS +.PP +"host= dbname= user= password= connect_timeout=" +(for 8.x or newer Postgres). +.PP +Or: +.PP +"postgresql://username:password@hostname:port/databasename" (for 9.x or newer Postgres). +See the INSTALL file for more explanations and examples. +.PP +Also, see http://www.PostgreSQL.org for full PostgreSQL documentation. +.RE +.TP +.B +\fB\-M\fP, \fB\-\-mysql\-userdb\fP +User database connection string for MySQL or MariaDB. +This database can be used for long\-term and short\-term credentials mechanisms, +and it can store the secret value for secret\-based timed authentication in TURN RESP API. +The connection string format is like that: +.RS +.PP +"host= dbname= user= password= connect_timeout=" +See the INSTALL file for more explanations and examples. +.PP +Also, see http://www.mysql.org or http://mariadb.org +for full MySQL documentation. +.RE +.TP +.B +\fB\-N\fP, \fB\-\-redis\-userdb\fP +User database connection string for Redis. +This database can be used for long\-term and short\-term credentials mechanisms, +and it can store the secret value for secret\-based timed authentication in TURN RESP API. +The connection string format is like that: +.RS +.PP +"ip= dbname= password= connect_timeout=" +See the INSTALL file for more explanations and examples. +.PP +Also, see http://redis.io for full Redis documentation. +.RE +.TP +.B +Flags: +.TP +.B +\fB\-v\fP, \fB\-\-verbose\fP +Moderate verbose mode. +.TP +.B +\fB\-V\fP, \fB\-\-Verbose\fP +Extra verbose mode, very annoying and not recommended. +.TP +.B +\fB\-o\fP, \fB\-\-daemon\fP +Run server as daemon. +.TP +.B +\fB\-f\fP, \fB\-\-fingerprint\fP +Use fingerprints in the TURN messages. If an incoming request +contains a fingerprint, then TURN server will always add +fingerprints to the messages in this session, regardless of the +per\-server setting. +.TP +.B +\fB\-a\fP, \fB\-\-lt\-cred\-mech\fP +Use long\-term credentials mechanism (this one you need for WebRTC usage). +This option can be used with either flat file user database or +PostgreSQL DB or MySQL DB or Redis for user keys storage. +.TP +.B +\fB\-A\fP, \fB\-\-st\-cred\-mech\fP +Use the short\-term credentials mechanism. This option requires +a PostgreSQL or MySQL or Redis DB for short term passwords storage. +.TP +.B +\fB\-z\fP, \fB\-\-no\-auth\fP +Do not use any credentials mechanism, allow anonymous access. +Opposite to \fB\-a\fP and \fB\-A\fP \fIoptions\fP. This is default option when no +authentication\-related \fIoptions\fP are set. +By default, no credential mechanism is used \- +any user is allowed. +.TP +.B +\fB\-\-use\-auth\-secret\fP +TURN REST API flag. +Flag that sets a special WebRTC authorization option +that is based upon authentication secret. The feature purpose +is to support "\fBTURN Server\fP REST API" as described in +the TURN REST API section below. +This option uses timestamp as part of combined username: +usercombo \-> "timestamp:username", +turn user \-> usercombo, +turn password \-> \fBbase64\fP(hmac(secret key, usercombo)). +This allows TURN credentials to be accounted for a specific user id. +If you don't have a suitable id, the timestamp alone can be used. +This option is just turns on secret\-based authentication. +The actual value of the secret is defined either by option static\-auth\-secret, +or can be found in the turn_secret table in the database. +This option can be used with long\-term credentials mechanisms only \- +it does not make much sense with the short\-term mechanism. +.TP +.B +\fB\-\-dh566\fP +Use 566 bits predefined DH TLS key. Default size of the key is 1066. +.TP +.B +\fB\-\-dh2066\fP +Use 2066 bits predefined DH TLS key. Default size of the key is 1066. +.TP +.B +\fB\-\-no\-sslv2\fP +Do not allow SSLv2 protocol. +.TP +.B +\fB\-\-no\-sslv3\fP +Do not allow SSLv3 protocol. +.TP +.B +\fB\-\-no\-tlsv1\fP +Do not allow TLSv1 protocol. +.TP +.B +\fB\-\-no\-tlsv1_1\fP +Do not allow TLSv1.1 protocol. +.TP +.B +\fB\-\-no\-tlsv1_2\fP +Do not allow TLSv1.2 protocol. +.TP +.B +\fB\-\-no\-udp\fP +Do not start UDP client listeners. +.TP +.B +\fB\-\-no\-tcp\fP +Do not start TCP client listeners. +.TP +.B +\fB\-\-no\-tls\fP +Do not start TLS client listeners. +.TP +.B +\fB\-\-no\-dtls\fP +Do not start DTLS client listeners. +.TP +.B +\fB\-\-no\-udp\-relay\fP +Do not allow UDP relay endpoints defined in RFC 5766, +use only TCP relay endpoints as defined in RFC 6062. +.TP +.B +\fB\-\-no\-tcp\-relay\fP +Do not allow TCP relay endpoints defined in RFC 6062, +use only UDP relay endpoints as defined in RFC 5766. +.TP +.B +\fB\-\-stale\-nonce\fP +Use extra security with nonce value having limited lifetime (600 secs). +.TP +.B +\fB\-\-no\-stdout\-log\fP +Flag to prevent stdout log messages. +By default, all log messages are going to both stdout and to +the configured log file. With this option everything will be going to +the log file only (unless the log file itself is stdout). +.TP +.B +\fB\-\-syslog\fP +With this flag, all log will be redirected to the system log (syslog). +.TP +.B +\fB\-\-simple\-log\fP +This flag means that no log file rollover will be used, and the log file +name will be constructed as\-is, without PID and date appendage. +.TP +.B +\fB\-\-secure\-stun\fP +Require authentication of the STUN Binding request. +By default, the clients are allowed anonymous access to the STUN Binding functionality. +.TP +.B +\fB\-S\fP, \fB\-\-stun\-only\fP +Run as STUN server only, all TURN requests will be ignored. +Option to suppress TURN functionality, only STUN requests will be processed. +.TP +.B +\fB\-\-no\-stun\fP +Run as TURN server only, all STUN requests will be ignored. +Option to suppress STUN functionality, only TURN requests will be processed. +.TP +.B +\fB\-\-no\-loopback\-peers\fP +Disallow peers on the loopback addresses (127.x.x.x and ::1). +.TP +.B +\fB\-\-no\-multicast\-peers\fP +Disallow peers on well\-known broadcast addresses +(224.0.0.0 and above, and FFXX:*). +.TP +.B +\fB\-\-sha256\fP +Require SHA256 digest function to be used for the message integrity. +By default, the server uses SHA1 hashes. With this option, the server +requires the stronger SHA256 hashes. The client application must support +SHA256 hash function if this option is used. If the server obtains a message +from the client with a weaker (SHA1) hash function then the server returns +error code 426. +.TP +.B +\fB\-\-mobility\fP +Mobility with ICE (MICE) specs support. +.TP +.B +\fB\-\-no\-cli\fP +Turn OFF the CLI support. By default it is always ON. +See also \fIoptions\fP \fB\-\-cli\-ip\fP and \fB\-\-cli\-port\fP. +.TP +.B +\fB\-\-server\-relay\fP +Server relay. NON\-STANDARD AND DANGEROUS OPTION. +Only for those applications when we want to run +server applications on the relay endpoints. +This option eliminates the IP permissions check +on the packets incoming to the relay endpoints. +See http://tools.ietf.org/search/rfc5766#section\-17.2.3 . +.TP +.B +\fB\-\-udp\-self\-balance\fP +(recommended for older Linuxes only) +Automatically balance UDP traffic over auxiliary servers +(if configured). The load balancing is using the +ALTERNATE\-SERVER mechanism. The TURN client must support +300 ALTERNATE\-SERVER response for this functionality. +.TP +.B +\fB\-h\fP +Help. +.TP +.B +Options with required values: +.TP +.B +\fB\-d\fP, \fB\-\-listening\-device\fP +Listener interface device. +(NOT RECOMMENDED. Optional functionality, Linux only). +The \fIturnserver\fP process must have root privileges to bind the +listening endpoint to a device. If \fIturnserver\fP must run as a +process without root privileges, then just do not use this setting. +.TP +.B +\fB\-L\fP, \fB\-\-listening\-ip\fP +Listener IP address of relay server. +Multiple listeners can be specified, for example: +\fB\-L\fP ip1 \fB\-L\fP ip2 \fB\-L\fP ip3 +If no \fBIP\fP(s) specified, then all IPv4 and +IPv6 system IPs will be used for listening. +The same \fBip\fP(s) can be used as both listening and relay \fBip\fP(s). +.TP +.B +\fB\-p\fP, \fB\-\-listening\-port\fP +TURN listener port for UDP and TCP listeners (Default: 3478). +Note: actually, TLS & DTLS sessions can connect to the "plain" TCP & UDP +\fBport\fP(s), too \- if allowed by configuration. +.TP +.B +\fB\-\-tls\-listening\-port\fP +TURN listener port for TLS and DTLS listeners (Default: 5349). +Note: actually, "plain" TCP & UDP sessions can connect to the TLS & DTLS +\fBport\fP(s), too \- if allowed by configuration. The TURN server +"automatically" recognizes the type of traffic. Actually, two listening +endpoints (the "plain" one and the "tls" one) are equivalent in terms of +functionality; but we keep both endpoints to satisfy the RFC 5766 specs. +For secure TCP connections, we currently support SSL version 3 and +TLS versions 1.0, 1.1, 1.2. SSL2 "encapsulation mode" is also supported. +For secure UDP connections, we support DTLS version 1. +.TP +.B +\fB\-\-alt\-listening\-port\fP +Alternative listening port for UDP and TCP listeners; +default (or zero) value means "listening port plus one". +This is needed for STUN CHANGE_REQUEST \- in RFC 5780 sense +or in old RFC 3489 sense \- for NAT behavior discovery). The \fBTURN Server\fP +supports CHANGE_REQUEST only if it is started with more than one +listening IP address of the same family (IPv4 or IPv6). The CHANGE_REQUEST +is only supported by UDP protocol, other protocols are listening +on that endpoint only for "symmetry". +.TP +.B +\fB\-\-alt\-tls\-listening\-port\fP +Alternative listening port for TLS and DTLS protocols. +Default (or zero) value means "TLS listening port plus one". +.TP +.B +\fB\-\-aux\-server\fP +Auxiliary STUN/TURN server listening endpoint. +Aux servers have almost full TURN and STUN functionality. +The (minor) limitations are: +.RS +.IP 1) 4 +Auxiliary servers do not have alternative ports and +they do not support STUN RFC 5780 functionality (CHANGE REQUEST). +.IP 2) 4 +Auxiliary servers also are never returning ALTERNATIVE\-SERVER reply. +.RE +.PP +Valid formats are 1.2.3.4:5555 for IPv4 and [1:2::3:4]:5555 for IPv6. +There may be multiple aux\-server \fIoptions\fP, each will be used for listening +to client requests. +.TP +.B +\fB\-i\fP, \fB\-\-relay\-device\fP +Relay interface device for relay sockets +(NOT RECOMMENDED. Optional, Linux only). +.TP +.B +\fB\-E\fP, \fB\-\-relay\-ip\fP +Relay address (the local IP address that +will be used to relay the packets to the +peer). Multiple relay addresses may be used: +\fB\-E\fP ip1 \fB\-E\fP ip2 \fB\-E\fP ip3 +The same \fBIP\fP(s) can be used as both listening \fBIP\fP(s) and relay \fBIP\fP(s). +If no relay \fBIP\fP(s) specified, then the \fIturnserver\fP will apply the +default policy: it will decide itself which relay addresses to be +used, and it will always be using the client socket IP address as +the relay IP address of the TURN session (if the requested relay +address family is the same as the family of the client socket). +.TP +.B +\fB\-X\fP, \fB\-\-external\-ip\fP +\fBTURN Server\fP public/private address mapping, if the server is behind NAT. +In that situation, if a \fB\-X\fP is used in form "\fB\-X\fP " then that ip will be reported +as relay IP address of all allocations. This scenario works only in a simple case +when one single relay address is be used, and no CHANGE_REQUEST functionality is +required. That single relay address must be mapped by NAT to the 'external' IP. +The "external\-ip" value, if not empty, is returned in XOR\-RELAYED\-ADDRESS field. +For that 'external' IP, NAT must forward ports directly (relayed port 12345 +must be always mapped to the same 'external' port 12345). +In more complex case when more than one IP address is involved, +that option must be used several times, each entry must +have form "\fB\-X\fP ", to map all involved addresses. +CHANGE_REQUEST (RFC5780 or RFC3489) NAT discovery STUN functionality will work +correctly, if the addresses are mapped properly, even when the TURN server itself +is behind A NAT. +By default, this value is empty, and no address mapping is used. +.TP +.B +\fB\-m\fP, \fB\-\-relay\-threads\fP +Number of relay threads to handle the established connections +(in addition to authentication thread and the listener thread). +If set to 0 then application runs relay process in a single thread, +in the same thread with the listener process (the authentication thread will +still be a separate thread). In older systems (before Linux kernel 3.9), +the number of UDP threads is always one threads per network listening endpoint \- +unless "\fB\-m\fP 0" or "\fB\-m\fP 1" is set. +.TP +.B +\fB\-\-min\-port\fP +Lower bound of the UDP port range for relay +endpoints allocation. +Default value is 49152, according to RFC 5766. +.TP +.B +\fB\-\-max\-port\fP +Upper bound of the UDP port range for relay +endpoints allocation. +Default value is 65535, according to RFC 5766. +.TP +.B +\fB\-u\fP, \fB\-\-user\fP +Long\-term security mechanism credentials user account, +in the column\-separated form username:key. +Multiple user accounts may used in the command line. +The key is either the user password, or +the key is generated +by \fIturnadmin\fP command. In the second case, +the key must be prepended with 0x symbols. +The key is calculated over the user name, +the user realm, and the user password. +This setting may not be used with TURN REST API or +with short\-term credentials mechanism. +.TP +.B +\fB\-r\fP, \fB\-\-realm\fP +The default realm to be used for the users when no explicit +origin/realm relationship was found in the database, or if the TURN +server is not using any database (just the commands\-line settings +and the userdb file). Must be used with long\-term credentials +mechanism or with TURN REST API. +.TP +.B +\fB\-C\fP, \fB\-\-rest\-api\-separator\fP +This is the timestamp/username separator symbol (character) in TURN REST API. +The default value is :. +.TP +.B +\fB\-q\fP, \fB\-\-user\-quota\fP +Per\-user allocations quota: how many concurrent +allocations a user can create. This option can also be set +through the database, for a particular realm. +.TP +.B +\fB\-Q\fP, \fB\-\-total\-quota\fP +Total allocations quota: global limit on concurrent allocations. +This option can also be set through the database, for a particular realm. +.TP +.B +\fB\-s\fP, \fB\-\-max\-bps\fP +Max bytes\-per\-second bandwidth a TURN session is allowed to handle +(input and output network streams are treated separately). Anything above +that limit will be dropped or temporary suppressed (within the +available buffer limits). This option can also be set through the +database, for a particular realm. +.TP +.B +\fB\-\-static\-auth\-secret\fP +Static authentication secret value (a string) for TURN REST API only. +If not set, then the turn server will try to use the dynamic value +in turn_secret table in user database (if present). The database\-stored +value can be changed on\-the\-fly by a separate program, so this is why +that other mode is dynamic. Multiple shared secrets can be used +(both in the database and in the "static" fashion). +.TP +.B +\fB\-\-cert\fP +Certificate file, PEM format. Same file +search rules applied as for the configuration +file. If both \fB\-\-no\-tls\fP and \fB\-\-no\-dtls\fP \fIoptions\fP +are specified, then this parameter is not needed. +Default value is turn_server_cert.pem. +.TP +.B +\fB\-\-pkey\fP +Private key file, PEM format. Same file +search rules applied as for the configuration +file. If both \fB\-\-no\-tls\fP and \fB\-\-no\-dtls\fP \fIoptions\fP +are specified, then this parameter is not needed. +Default value is turn_server_pkey.pem. +.TP +.B +\fB\-\-pkey\-pwd\fP +If the private key file is encrypted, then this password to be used. +.TP +.B +\fB\-\-cipher\-list\fP +Allowed OpenSSL cipher list for TLS/DTLS connections. +Default value is "DEFAULT". +.TP +.B +\fB\-\-CA\-file\fP +CA file in OpenSSL format. +Forces TURN server to verify the client SSL certificates. +By default, no CA is set and no client certificate check is performed. +.TP +.B +\fB\-\-ec\-curve\-name\fP +Curve name for EC ciphers, if supported by OpenSSL library (TLS and DTLS). +The default value is prime256v1. +.TP +.B +\fB\-\-dh\-file\fP +Use custom DH TLS key, stored in PEM format in the file. +Flags \fB\-\-dh566\fP and \fB\-\-dh2066\fP are ignored when the DH key is taken from a file. +.TP +.B +\fB\-l\fP, \fB\-\-log\-file\fP +Option to set the full path name of the log file. +By default, the \fIturnserver\fP tries to open a log file in +/var/log/\fIturnserver\fP, /var/log, /var/tmp, /tmp and . (current) +directories (which file open operation succeeds +first that file will be used). With this option you can set the +definite log file name. +The special names are "stdout" and "\-" \- they will force everything +to the stdout. Also, "syslog" name will redirect everything into +the system log (syslog), as if the option "\fB\-\-syslog\fP" was set. +.TP +.B +\fB\-\-alternate\-server\fP +Option to set the "redirection" mode. The value of this option +will be the address of the alternate server for UDP & TCP service in form of +[:]. The server will send this value in the attribute +ALTERNATE\-SERVER, with error 300, on ALLOCATE request, to the client. +Client will receive only values with the same address family +as the client network endpoint address family. +See RFC 5389 and RFC 5766 for ALTERNATE\-SERVER functionality description. +The client must use the obtained value for subsequent TURN communications. +If more than one \fB\-\-alternate\-server\fP \fIoptions\fP are provided, then the functionality +can be more accurately described as "load\-balancing" than a mere "redirection". +If the port number is omitted, then the default port +number 3478 for the UDP/TCP protocols will be used. +Colon (:) characters in IPv6 addresses may conflict with the syntax of +the option. To alleviate this conflict, literal IPv6 addresses are enclosed +in square brackets in such resource identifiers, for example: +[2001:db8:85a3:8d3:1319:8a2e:370:7348]:3478 . +Multiple alternate servers can be set. They will be used in the +round\-robin manner. All servers in the pool are considered of equal weight and +the load will be distributed equally. For example, if we have 4 alternate servers, +then each server will receive 25% of ALLOCATE requests. An alternate TURN server +address can be used more than one time with the alternate\-server option, so this +can emulate "weighting" of the servers. +.TP +.B +\fB\-\-tls\-alternate\-server\fP +Option to set alternative server for TLS & DTLS services in form of +:. If the port number is omitted, then the default port +number 5349 for the TLS/DTLS protocols will be used. See the previous option for the +functionality description. +.TP +.B +\fB\-O\fP, \fB\-\-redis\-statsdb\fP +Redis status and statistics database connection string, if used (default \- empty, +no Redis stats DB used). This database keeps allocations status information, and it can +be also used for publishing and delivering traffic and allocation event notifications. +This database option can be used independently of \fB\-\-redis\-userdb\fP option, +and actually Redis can be used for status/statistics and MySQL or PostgreSQL can +be used for the user database. +The connection string has the same parameters as redis\-userdb connection string. +.TP +.B +\fB\-\-max\-allocate\-timeout\fP +Max time, in seconds, allowed for full allocation establishment. +Default is 60 seconds. +.PP +\fB\-\-denied\-peer\-ip\fP= +.PP +\fB\-\-allowed\-peer\-ip\fP= Options to ban or allow specific ip addresses or ranges +of ip addresses. If an ip address is specified as both allowed and denied, then +the ip address is considered to be allowed. This is useful when you wish to ban +a range of ip addresses, except for a few specific ips within that range. +This can be used when you do not want users of the turn server to be able to access +machines reachable by the turn server, but would otherwise be unreachable from the +internet (e.g. when the turn server is sitting behind a NAT). The 'white" and "black" peer +IP ranges can also be dynamically changed in the database. +The allowed/denied addresses (white/black lists) rules are very simple: +.RS +.IP 1) 4 +If there is no rule for an address, then it is allowed; +.IP 2) 4 +If there is an "allowed" rule that fits the address then it is allowed \- no matter what; +.IP 3) 4 +If there is no "allowed" rule that fits the address, and if there is a "denied" rule that +fits the address, then it is denied. +.RE +.TP +.B +\fB\-\-pidfile\fP +File name to store the pid of the process. +Default is /var/run/turnserver.pid (if superuser account is used) or +/var/tmp/turnserver.pid . +.TP +.B +\fB\-\-proc\-user\fP +User name to run the process. After the initialization, the \fIturnserver\fP process +will make an attempt to change the current user ID to that user. +.TP +.B +\fB\-\-proc\-group\fP +Group name to run the process. After the initialization, the \fIturnserver\fP process +will make an attempt to change the current group ID to that group. +.TP +.B +\fB\-\-cli\-ip\fP +Local system IP address to be used for CLI management interface. +The \fIturnserver\fP process can be accessed for management with telnet, +at this IP address and on the CLI port (see the next parameter). +Default value is 127.0.0.1. You can use telnet or putty (in telnet mode) +to access the CLI management interface. +.TP +.B +\fB\-\-cli\-port\fP +CLI management interface listening port. Default is 5766. +.TP +.B +\fB\-\-cli\-password\fP +CLI access password. Default is empty (no password). +.TP +.B +\fB\-\-cli\-max\-output\-sessions\fP +Maximum number of output sessions in ps CLI command. +This value can be changed on\-the\-fly in CLI. The default value is 256. +.TP +.B +\fB\-\-ne\fP=[1|2|3] +Set network engine type for the process (for internal purposes). +.PP +================================== +.SH LOAD BALANCE AND PERFORMANCE TUNING + +This topic is covered in the wiki page: +.PP +http://code.google.com/p/coturn/wiki/turn_performance_and_load_balance +.PP +=================================== +.SH WEBRTC USAGE + +This is a set of notes for the WebRTC users: +.IP 1) 4 +WebRTC uses long\-term authentication mechanism, so you have to use \fB\-a\fP +option (or \fB\-\-lt\-cred\-mech\fP). WebRTC relaying will not work with anonymous access +or with short\-term authentication. With \fB\-a\fP option, do not forget to set the +default realm (\fB\-r\fP option). You will also have to set up the user accounts, +for that you have a number of \fIoptions\fP: +.PP +.nf +.fam C + a) command\-line options (\-u). + + b) userdb config file. + + c) a database table (PostgreSQL or MySQL). You will have to set keys with + turnadmin utility (see docs and wiki for turnadmin). You cannot use open passwords + in the database. + + d) Redis key/value pair(s), if Redis is used. You key use either keys or + open passwords with Redis; see turndb/testredisdbsetup.sh file. + + e) You also can use the TURN REST API. You will need shared secret(s) set + either through the command line option, or through the config file, or through + the database table or Redis key/value pairs. + +.fam T +.fi +.IP 2) 4 +Usually WebRTC uses fingerprinting (\fB\-f\fP). +.IP 3) 4 +\fB\-v\fP option may be nice to see the connected clients. +.IP 4) 4 +\fB\-X\fP is needed if you are running your TURN server behind a NAT. +.IP 5) 4 +\fB\-\-min\-port\fP and \fB\-\-max\-port\fP may be needed if you want to limit the relay endpoints ports +number range. +.PP +=================================== +.SH TURN REST API + +In WebRTC, the browser obtains the TURN connection information from the web +server. This information is a secure information \- because it contains the +necessary TURN credentials. As these credentials are transmitted over the +public networks, we have a potential security breach. +.PP +If we have to transmit a valuable information over the public network, +then this information has to have a limited lifetime. Then the guy who +obtains this information without permission will be able to perform +only limited damage. +.PP +This is how the idea of TURN REST API \- time\-limited TURN credentials \- +appeared. This security mechanism is based upon the long\-term credentials +mechanism. The main idea of the REST API is that the web server provides +the credentials to the client, but those credentials can be used only +limited time by an application that has to create a TURN server connection. +.PP +The "classic" long\-term credentials mechanism (LTCM) is described here: +.PP +http://tools.ietf.org/html/rfc5389#section\-10.2 +http://tools.ietf.org/html/rfc5389#section\-15.4 +.PP +For authentication, each user must know two things: the username and the +password. Optionally, the user must supply the ORIGIN value, so that the +server can figure out the realm to be used for the user. The nonce and +the realm values are supplied by the TURN server. But LTCM is not saying +anything about the nature and about the persistence +of the username and of the password; and this is used by the REST API. +.PP +In the TURN REST API, there is no persistent passwords for users. A user has +just the username. The password is always temporary, and it is generated by +the web server on\-demand, when the user accesses the WebRTC page. And, +actually, a temporary one\-time session only, username is provided to the user, +too. +.PP +The temporary user is generated as: +.PP +temporary\-username="timestamp" + ":" + "username" +.PP +where username is the persistent user name, and the timestamp format is just +seconds sinse 1970 \- the same value as \fBtime\fP(NULL) function returns. +.PP +The temporary password is obtained as HMAC\-SHA1 function over the temporary +username, with shared secret as the HMAC key, and then the result is encoded: +.PP +temporary\-password = \fBbase64_encode\fP(hmac\-sha1(shared\-secret, temporary\-username)) +.PP +Both the TURN server and the web server know the same shared secret. How the +shared secret is distributed among the involved entities is left to the WebRTC +deployment details \- this is beyond the scope of the TURN REST API. +.PP +So, a timestamp is used for the temporary password calculation, and this +timestamp can be retrieved from the temporary username. This information +is valuable, but only temporary, while the timestamp is not expired. Without +knowledge of the shared secret, a new temporary password cannot be generated. +.PP +This is all formally described in Justin's Uberti TURN REST API document +that can be obtained following the link "TURN REST API" in the \fBTURN Server\fP +project's page http://code.google.com/p/coturn/. +.PP +Once the temporary username and password are obtained by the client (browser) +application, then the rest is just 'classic" long\-term credentials mechanism. +For developers, we are going to describe it step\-by\-step below: +.RS +.IP \(bu 3 +a new TURN client sends a request command to the TURN server. +.IP \(bu 3 +TURN server sees that this is a new client and the message is not +authenticated. +.IP \(bu 3 +the TURN server generates a random nonce string, and return the +error 401 to the client, with nonce and realm included. +.IP \(bu 3 +the client sees the 401 error and it extracts two values from +the error response: the nonce and the realm. +.IP \(bu 3 +the client uses username, realm and password to produce a key: +.PP +.nf +.fam C + key = MD5(username ":" realm ":" SASLprep(password)) +.fam T +.fi +(SASLprep is described here: http://tools.ietf.org/html/rfc4013) +.IP \(bu 3 +the client forms a new request, adds username, realm and nonce to the +request. Then, the client calculates and adds the integrity field to +the request. This is the trickiest part of the process, and it is +described in the end of section 15.4: +http://tools.ietf.org/html/rfc5389#section\-15.4 +.IP \(bu 3 +the client, optionally, adds the fingerprint field. This may be also +a tricky procedure, described in section 15.5 of the same document. +WebRTC usually uses fingerprinted TURN messages. +.IP \(bu 3 +the TURN server receives the request, reads the username. +.IP \(bu 3 +then the TURN server checks that the nonce and the realm in the request +are the valid ones. +.IP \(bu 3 +then the TURN server calculates the key. +.IP \(bu 3 +then the TURN server calculates the integrity field. +.IP \(bu 3 +then the TURN server compares the calculated integrity field with the +received one \- they must be the same. If the integrity fields differ, +then the request is rejected. +.RE +.PP +In subsequent communications, the client may go with exactly the same +sequence, but for optimization usually the client, having already +information about realm and nonce, pre\-calculates the integrity string +for each request, so that the 401 error response becomes unnecessary. +The TURN server may use "\fB\-\-stale\-nonce\fP" option for extra security: in +some time, the nonce expires and the client will obtain 438 error response +with the new nonce, and the client will have to start using the new nonce. +.PP +In subsequent communications, the sever and the client will always assume +the same password \- the original password becomes the session parameter and +is never expiring. So the password is not changing while the session is valid +and unexpired. So, if the session is properly maintained, it may go forever, +even if the user password has been already changed (in the database). The +session simply is using the old password. Once the session got disconnected, +the client will have to use the new password to re\-connect (if the password +has been changed). +.PP +An example when a new shared secret is generated every hour by the TURN server +box and then supplied to the web server, remotely, is provided in the script +examples/scripts/restapi/shared_secret_maintainer.pl . +.PP +A very important thing is that the nonce must be totally random and it must be +different for different clients and different sessions. +.PP +=================================== +.SH DATABASES + +For the user database, the \fIturnserver\fP has the following \fIoptions\fP: +.IP 1) 4 +Users can be set in the command line, with multiple \fB\-u\fP or \fB\-\-user\fP \fIoptions\fP. +Obviously, only a few users can be set that way, and their credentials are fixed +for the \fIturnserver\fP process lifetime. +.IP 2) 4 +Users can be set in turnusers.conf flat file DB. The \fIturnserver\fP process periodically +re\-reads this file, so the user accounts may be changed while the \fIturnserver\fP is running. +But still a relatively small (up to a hundred ?) number of users can be handled that way. +.IP 3) 4 +Users can be stored in PostgreSQL database, if the \fIturnserver\fP was compiled with PostgreSQL +support. Each time \fIturnserver\fP checks user credentials, it reads the database (asynchronously, +of course, so that the current flow of packets is not delayed in any way), so any change in the +database content is immediately visible by the \fIturnserver\fP. This is the way if you need the +best scalability. The schema for the database can be found in schema.sql file. +For long\-term credentials, you have to set the "keys" for the users; the "keys" are generated +by the \fIturnadmin\fP utility. For the key generation, you need username, password and the realm. +All users in the database must use the same realm value; if down the road you will decide +to change the realm name, then you will have to re\-generate all user keys (that can be done +in a batch script). If you are using short\-term credentials, then you use open passwords +in the database; you will have to make sure that nobody can access the database outside of +the TURN server box. See the file turndb/testsqldbsetup.sql as an example. +.IP 4) 4 +The same is true for MySQL database. The same schema file is applicable. +The same considerations are applicable. +.IP 5) 4 +The same is true for the Redis database, but the Redis database has aa different schema \- +it can be found (in the form of explanation) in schema.userdb.redis. +Also, in Redis you can store both "keys" and open passwords (for long term credentials) \- +the "open password" option is less secure but more convenient for low\-security environments. +For short\-term credentials, you will use open passwords only. See the file +turndb/testredisdbsetup.sh as an example. +.IP 6) 4 +Of course, the \fIturnserver\fP can be used in non\-secure mode, when users are allowed to establish +sessions anonymously. But in most cases (like WebRTC) that will not work. +.PP +For the status and statistics database, there are two choices: +.IP 1) 4 +The simplest choice is not to use it. Do not set \fB\-\-redis\-statsdb\fP option, and this functionality +will be simply ignored. +.IP 2) 4 +If you choose to use it, then set the \fB\-\-redis\-statsdb\fP option. This may be the same database +as in \fB\-\-redis\-userdb\fP option, or it may be a different database. You may want to use different +database for security or convenience reasons. Also, you can use different database management +systems for the user database and for the ststus and statistics database. For example, you can use +MySQL as the user database, and you can use redis for the statistics. Or you can use Redis for both. +.PP +So, we have 6 choices for the user management, and 2 choices for the statistics management. These +two are totally independent. So, you have overall 6*2=12 ways to handle persistent information, +choose any for your convenience. +.PP +You do not have to handle the database information "manually" \- the \fIturnadmin\fP program can handle +everything for you. For PostgreSQL and MySQL you will just have to create an empty database +with schema.sql SQL script. With Redis, you do not have to do even that \- just run \fIturnadmin\fP and +it will set the users for you (see the \fIturnadmin\fP manuals). +.PP +================================= +.SH LIBRARIES + +In the lib/ sub\-directory the build process will create TURN client messaging library. +In the include/ sub\-directory, the necessary include files will be placed. +The C++ wrapper for the messaging functionality is located in TurnMsgLib.h header. +An example of C++ code can be found in stunclient.c file. +.PP +================================= +.SH DOCS + +After installation, run the command: +.PP +$ man \fIturnserver\fP +.PP +or in the project root directory: +.PP +$ man \fB\-M\fP man \fIturnserver\fP +.PP +to see the man page. +.PP +In the docs/html subdirectory of the original archive tree, you will find the client library +reference. After the installation, it will be placed in PREFIX/share/doc/\fIturnserver\fP/html. +.PP +================================= +.SH LOGS + +When the \fBTURN Server\fP starts, it makes efforts to create a log file turn_.log +in the following directories: +.RS +.IP \(bu 3 +/var/log +.IP \(bu 3 +/log/ +.IP \(bu 3 +/var/tmp +.IP \(bu 3 +/tmp +.IP \(bu 3 +current directory +.RE +.PP +If all efforts failed (due to the system permission settings) then all +log messages are sent only to the standard output of the process. +.PP +This behavior can be controlled by \fB\-\-log\-file\fP, \fB\-\-syslog\fP and \fB\-\-no\-stdout\-log\fP \fIoptions\fP. +.PP +================================= +.SH TELNET CLI + +The \fIturnserver\fP process provides a telnet CLI access as statistics and basic management +interface. By default, the \fIturnserver\fP starts a telnet CLI listener on IP 127.0.0.1 and +port 5766. That can be changed by the command\-cline \fIoptions\fP of the \fIturnserver\fP process +(see \fB\-\-cli\-ip\fP and \fB\-\-cli\-port\fP \fIoptions\fP). The full list of telnet CLI commands is provided +in "help" command output in the telnet CLI. +.PP +================================= +.SH CLUSTERS + +\fBTURN Server\fP can be a part of the cluster installation. But, to support the "even port" functionality +(RTP/RTCP streams pairs) the client requests from a particular IP must be delivered to the same +\fBTURN Server\fP instance, so it requires some networking setup massaging for the cluster. The reason is that +the RTP and RTCP relaying endpoints must be allocated on the same relay IP. It would be possible +to design a scheme with the application\-level requests forwarding (and we may do that later) but +it would affect the performance. +.PP +================================= +.SH FILES + +/etc/turnserver.conf +.PP +/etc/turnuserdb.conf +.PP +/usr/local/etc/turnserver.conf +.PP +/usr/local/etc/turnuserdb.conf +.PP +================================= +.SH DIRECTORIES + +/usr/local/share/\fIturnserver\fP +.PP +/usr/local/share/doc/\fIturnserver\fP +.PP +/usr/local/share/examples/\fIturnserver\fP +.PP +================================= +.SH STANDARDS + +obsolete STUN RFC 3489 +.PP +new STUN RFC 5389 +.PP +TURN RFC 5766 +.PP +TURN\-TCP extension RFC 6062 +.PP +TURN IPv6 extension RFC 6156 +.PP +STUN/TURN test vectors RFC 5769 +.PP +STUN NAT behavior discovery RFC 5780 +.PP +================================= +.SH SEE ALSO + +\fIturnadmin\fP, \fIturnutils\fP +.RE +.PP +====================================== +.SS WEB RESOURCES + +project page: +.PP +http://code.google.com/p/coturn/ +.PP +Wiki page: +.PP +http://code.google.com/p/coturn/wiki/Readme +.PP +forum: +.PP +https://groups.google.com/forum/?fromgroups=#!forum/turn\-server\-project\-rfc5766\-turn\-server/ +.RE +.PP +====================================== +.SS AUTHORS + +Oleg Moskalenko +.PP +Gabor Kovesdan http://kovesdan.org/ +.PP +Daniel Pocock http://danielpocock.com/ +.PP +John Selbie (jselbie@gmail.com) +.PP +Lee Sylvester +.PP +Erik Johnston +.PP +Roman Lisagor +.PP +Vladimir Tsanev +.PP +Po\-sheng Lin +.PP +Peter Dunkley +.PP +Mutsutoshi Yoshimoto diff --git a/man/man1/turnutils.1 b/man/man1/turnutils.1 new file mode 100644 index 0000000..1e2c93d --- /dev/null +++ b/man/man1/turnutils.1 @@ -0,0 +1,439 @@ +.\" Text automatically generated by txt2man +.TH TURN 1 "20 April 2014" "" "" +.SH GENERAL INFORMATION + +A set of turnutils_* programs provides some utility functionality to be used +for testing and for setting up the TURN server. +.TP +.B +1. +\fIturnutils_uclient\fP: emulates multiple UDP,TCP,TLS or DTLS clients. +(this program is provided for the testing purposes only !) +The compiled binary image of this program is located in bin/ +sub\-directory. +.PP +WARNING: the \fIturnutils_uclient\fP program is a primitive client application. +It does not implement the re\-transmission pattern that is necessary for +a correct TURN client implementation. In TURN, the retransmission burden +is lying almost entirely on the client application. We provide the messaging +functionality in the client library, but the client must implement +the correct Networking IO processing in the client program code. +.TP +.B +2. +\fIturnutils_peer\fP: a simple stateless UDP\-only "echo" server, +to be used as the final server in relay pattern ("peer"). For every incoming +UDP packet, it simply echoes it back. +(this program is provided for the testing purposes only !) +When the test clients are communicating in the client\-to\-client manner +(when the "\fIturnutils_uclient\fP" program is used with "\fB\-y\fP" option) then the +\fIturnutils_peer\fP is not needed. +.PP +The compiled binary image of this program is located in bin/ subdirectory. +.TP +.B +3. +\fIturnutils_stunclient\fP: a simple STUN client example. +The compiled binary image of this program is located in bin/ subdirectory. +.TP +.B +4. +\fIturnutils_rfc5769check\fP: a utility that checks the correctness of the +STUN/TURN protocol implementation. This utility is used only for the compilation +check procedure, it is not copied to the installation destination. +.RE +.PP + +.RS +In the "examples/scripts" subdirectory, you will find the examples of command lines to be used +to run the programs. The scripts are meant to be run from examples/ subdirectory, for example: +.PP +$ cd examples +.PP +$ ./scripts/secure_relay.sh +.PP +===================================== +.SS NAME +\fB +\fBturnutils_uclient \fP\- this client emulation application is supplied for the test purposes only. +\fB +.SS SYNOPSIS + +$ \fIturnutils_uclient\fP [\fB\-tTSvsyhcxg\fP] [options] +.SS DESCRIPTION + +It was designed to simulate multiple clients. It uses asynch IO API in +libevent to handle multiple clients. A client connects to the relay, +negotiates the session, and sends multiple (configured number) messages to the server (relay), +expecting the same number of replies. The length of the messages is configurable. +The message is an arbitrary octet stream, but it can be configured as a string. +The number of the messages to send is configurable. +.TP +.B +Flags: +.TP +.B +\fB\-t\fP +Use TCP for communications between client and TURN server (default is UDP). +.TP +.B +\fB\-T\fP +Use TCP for the relay transport (default \- UDP). Implies options \fB\-t\fP, \fB\-y\fP, \fB\-c\fP, +and ignores flags and options \fB\-s\fP, \fB\-e\fP, \fB\-r\fP and \fB\-g\fP. +.TP +.B +\fB\-P\fP +Passive TCP (RFC6062 with active peer). Implies \fB\-T\fP. +.TP +.B +\fB\-S\fP +Secure SSL connection: SSL/TLS for TCP, DTLS for UDP. +.TP +.B +\fB\-U\fP +Secure unencrypted connection (suite eNULL): SSL/TLS for TCP, DTLS for UDP. +.TP +.B +\fB\-v\fP +Verbose. +.TP +.B +\fB\-s\fP +Use "Send" method in TURN; by default, it uses TURN Channels. +.TP +.B +\fB\-y\fP +Use client\-to\-client connections: +RTP/RTCP pair of channels to another RTP/RTCP pair of channels. +with this option the \fIturnutils_peer\fP application is not used, +as the allocated relay endpoints are talking to each other. +.TP +.B +\fB\-h\fP +Hang on indefinitely after the last sent packet. +.TP +.B +\fB\-c\fP +Do not create rtcp connections. +.TP +.B +\fB\-x\fP +Request IPv6 relay address (RFC6156). +.TP +.B +\fB\-X\fP +IPv4 relay address explicitly requested. +.TP +.B +\fB\-g\fP +Set DONT_FRAGMENT parameter in TURN requests. +.TP +.B +\fB\-A\fP +use short\-term credentials mechanism for authentication. +By default, the program uses the long\-term credentials mechanism +if authentication is required. +.TP +.B +\fB\-D\fP +Do mandatory channel padding even for UDP (like pjnath). +.TP +.B +\fB\-N\fP +do negative tests (some limited cases only). +.TP +.B +\fB\-R\fP +do negative protocol tests. +.TP +.B +\fB\-O\fP +DOS attack mode. +.TP +.B +\fB\-H\fP +SHA256 digest function for message integrity calculation. +Without this option, by default, SHA1 is used. +.TP +.B +\fB\-M\fP +Use TURN ICE Mobility. +.TP +.B +\fB\-I\fP +Do not set permissions on TURN relay endpoints +(for testing the non\-standard server relay functionality). +.TP +.B +\fB\-G\fP +Generate extra requests (create permissions, channel bind). +.TP +.B +Options with required values: +.TP +.B +\fB\-l\fP +Message length (Default: 100 Bytes). +.TP +.B +\fB\-i\fP +Certificate file (for secure connections only, optional). +.TP +.B +\fB\-k\fP +Private key file (for secure connections only). +.TP +.B +\fB\-E\fP +CA file for server certificate verification, +if the server certificate to be verified. +.TP +.B +\fB\-p\fP +\fBTURN Server\fP port (Defaults: 3478 unsecure, 5349 secure). +.TP +.B +\fB\-n\fP +Number of messages to send (Default: 5). +.TP +.B +\fB\-d\fP +Local interface device (optional, Linux only). +.TP +.B +\fB\-L\fP +Local IP address (optional). +.TP +.B +\fB\-m\fP +Number of clients (Default: 1, 2 or 4, depending on options). +.TP +.B +\fB\-e\fP +Peer address. +.TP +.B +\fB\-r\fP +Peer port (Default: 3480). +.TP +.B +\fB\-z\fP +Per\-session packet interval in milliseconds (Default: 20). +.TP +.B +\fB\-u\fP +STUN/TURN user name. +.TP +.B +\fB\-w\fP +STUN/TURN user password. +.TP +.B +\fB\-W\fP +TURN REST API authentication secret. Is not compatible with \fB\-A\fP flag. +.TP +.B +\fB\-C\fP +This is the timestamp/username separator symbol (character) in +TURN REST API. The default value is :. +.TP +.B +\fB\-F\fP +Cipher suite for TLS/DTLS. Default value is DEFAULT. +.PP +See the examples in the "examples/scripts" directory. +.PP +====================================== +.SS NAME +\fB +\fBturnutils_peer \fP\- a simple UDP\-only echo backend server. +\fB +.SS SYNOPSYS + +$ \fIturnutils_peer\fP [\fB\-v\fP] [options] +.SS DESCRIPTION + +This application is used for the test purposes only, as a peer for the \fIturnutils_uclient\fP application. +.TP +.B +Options with required values: +.TP +.B +\fB\-p\fP +Listening UDP port (Default: 3480). +.TP +.B +\fB\-d\fP +Listening interface device (optional) +.TP +.B +\fB\-L\fP +Listening address of \fIturnutils_peer\fP server. Multiple listening addresses can be used, IPv4 and IPv6. +If no listener \fBaddress\fP(es) defined, then it listens on all IPv4 and IPv6 addresses. +.TP +.B +\fB\-v\fP +Verbose +.PP +======================================== +.SS NAME +\fB +\fBturnutils_stunclient \fP\- a basic STUN client. +\fB +.SS SYNOPSIS +.nf +.fam C + +$ \fIturnutils_stunclient\fP [\fIoptions\fP] + +.fam T +.fi +.fam T +.fi +.SS DESCRIPTION + +It sends a "new" STUN RFC 5389 request (over UDP) and shows the reply information. +.TP +.B +Options with required values: +.TP +.B +\fB\-p\fP +STUN server port (Default: 3478). +.TP +.B +\fB\-L\fP +Local address to use (optional). +.TP +.B +\fB\-f\fP +Force RFC 5780 processing. +.PP +The \fIturnutils_stunclient\fP program checks the results of the first request, +and if it finds that the STUN server supports RFC 5780 +(the binding response reveals that) then the \fIturnutils_stunclient\fP makes a couple more +requests with different parameters, to demonstrate the NAT discovery capabilities. +.PP +This utility does not support the "old" "classic" STUN protocol (RFC 3489). +.PP +===================================== +.SS NAME +\fB +\fBturnutils_rfc5769check \fP\- a utility that tests the correctness of STUN protocol implementation. +\fB +.SS SYNOPSIS +.nf +.fam C + +$ \fIturnutils_rfc5769check\fP + +.fam T +.fi +.fam T +.fi +.SS DESCRIPTION + +\fIturnutils_rfc5769check\fP tests the correctness of STUN protocol implementation +against the test vectors predefined in RFC 5769 and prints the results of the +tests on the screen. This utility is used only for the compilation +check procedure, it is not copied to the installation destination. +.TP +.B +Usage: +.PP +$ \fIturnutils_rfc5769check\fP +.PP +=================================== +.SH DOCS + +After installation, run the command: +.PP +$ man \fIturnutils\fP +.PP +or in the project root directory: +.PP +$ man \fB\-M\fP man \fIturnutils\fP +.PP +to see the man page. +.PP +===================================== +.SH FILES + +/etc/turnserver.conf +.PP +/etc/turnuserdb.conf +.PP +/usr/local/etc/turnserver.conf +.PP +/usr/local/etc/turnuserdb.conf +.PP +================================= +.SH DIRECTORIES + +/usr/local/share/\fIturnserver\fP +.PP +/usr/local/share/doc/\fIturnserver\fP +.PP +/usr/local/share/examples/\fIturnserver\fP +.PP +=================================== +.SH STANDARDS + +new STUN RFC 5389 +.PP +TURN RFC 5766 +.PP +TURN\-TCP extension RFC 6062 +.PP +TURN IPv6 extension RFC 6156 +.PP +STUN/TURN test vectors RFC 5769 +.PP +STUN NAT behavior discovery RFC 5780 +.PP +==================================== +.SH SEE ALSO + +\fIturnserver\fP, \fIturnadmin\fP +.RE +.PP +====================================== +.SS WEB RESOURCES + +project page: +.PP +http://code.google.com/p/coturn/ +.PP +Wiki page: +.PP +http://code.google.com/p/coturn/wiki/Readme +.PP +forum: +.PP +https://groups.google.com/forum/?fromgroups=#!forum/turn\-server\-project\-rfc5766\-turn\-server/ +.RE +.PP +====================================== +.SS AUTHORS + +Oleg Moskalenko +.PP +Gabor Kovesdan http://kovesdan.org/ +.PP +Daniel Pocock http://danielpocock.com/ +.PP +John Selbie (jselbie@gmail.com) +.PP +Lee Sylvester +.PP +Erik Johnston +.PP +Roman Lisagor +.PP +Vladimir Tsanev +.PP +Po\-sheng Lin +.PP +Peter Dunkley +.PP +Mutsutoshi Yoshimoto diff --git a/man/man1/turnutils_peer.1 b/man/man1/turnutils_peer.1 new file mode 120000 index 0000000..6996d67 --- /dev/null +++ b/man/man1/turnutils_peer.1 @@ -0,0 +1 @@ +turnutils.1 \ No newline at end of file diff --git a/man/man1/turnutils_stunclient.1 b/man/man1/turnutils_stunclient.1 new file mode 120000 index 0000000..6996d67 --- /dev/null +++ b/man/man1/turnutils_stunclient.1 @@ -0,0 +1 @@ +turnutils.1 \ No newline at end of file diff --git a/man/man1/turnutils_uclient.1 b/man/man1/turnutils_uclient.1 new file mode 120000 index 0000000..6996d67 --- /dev/null +++ b/man/man1/turnutils_uclient.1 @@ -0,0 +1 @@ +turnutils.1 \ No newline at end of file diff --git a/postinstall.txt b/postinstall.txt new file mode 100644 index 0000000..c05efce --- /dev/null +++ b/postinstall.txt @@ -0,0 +1,35 @@ +================================================================== + +1) If you system supports automatic start-up system daemon services, +the, to enable the turnserver as an automatically started system +service, you have to: + + a) Create and edit /etc/turnserver.conf or + /usr/local/etc/turnserver.conf . + Use /usr/local/etc/turnserver.conf.default as an example. + + b) For user accounts settings, if using the turnserver + with authentication: create and edit /etc/turnuserdb.conf + file, or set up PostgreSQL or MySQL or Redis database for user accounts. + Use /usr/local/etc/turnuserdb.conf.default as example for flat file DB, + or use /usr/local/share/turnserver/schema.sql as SQL database schema, + or use /usr/local/share/turnserver/schema.userdb.redis as Redis + database schema description and/or /usr/local/share/turnserver/schema.stats.redis + as Redis status & statistics database schema description. + + c) add whatever is necessary to enable start-up daemon for the /usr/local/bin/turnserver. + +2) If you do not want the turnserver to be a system service, + then you can start/stop it "manually", using the "turnserver" + executable with appropriate options (see the documentation). + +3) To create database schema, use schema in file /usr/local/share/turnserver/schema.sql. + +4) For additional information, run: + + $ man turnserver + $ man turnadmin + $ man turnutils + +================================================================== + diff --git a/rpm/CentOS6.pre.build.sh b/rpm/CentOS6.pre.build.sh new file mode 100755 index 0000000..7a37ac2 --- /dev/null +++ b/rpm/CentOS6.pre.build.sh @@ -0,0 +1,98 @@ +#!/bin/bash + +# CentOS6 preparation script. + +CPWD=`pwd` + +. ./common.pre.build.sh + +cd ${CPWD} + +EPELRPM=epel-release-6-8.noarch.rpm +LIBEVENT_MAJOR_VERSION=2 +LIBEVENT_VERSION=${LIBEVENT_MAJOR_VERSION}.0.21 +LIBEVENT_DISTRO=libevent-${LIBEVENT_VERSION}-stable.tar.gz +LIBEVENT_SPEC_DIR=libevent.rpm +LIBEVENTSPEC_SVN_URL=${TURNSERVER_SVN_URL}/${LIBEVENT_SPEC_DIR} +LIBEVENT_SPEC_FILE=libevent.spec + +# Common packs + +PACKS="mysql-devel" +sudo yum -y install ${PACKS} +ER=$? +if ! [ ${ER} -eq 0 ] ; then + echo "Cannot install package(s) ${PACKS}" + cd ${CPWD} + exit -1 +fi + +# Libevent2: + +cd ${BUILDDIR}/SOURCES +if ! [ -f ${LIBEVENT_DISTRO} ] ; then + wget ${WGETOPTIONS} https://github.com/downloads/libevent/libevent/${LIBEVENT_DISTRO} + ER=$? + if ! [ ${ER} -eq 0 ] ; then + cd ${CPWD} + exit -1 + fi +fi + +if ! [ -f ${BUILDDIR}/SPECS/${LIBEVENT_SPEC_FILE} ] ; then + cd ${BUILDDIR}/tmp + rm -rf ${LIBEVENT_SPEC_DIR} + svn export ${LIBEVENTSPEC_SVN_URL} ${LIBEVENT_SPEC_DIR} + ER=$? + if ! [ ${ER} -eq 0 ] ; then + cd ${CPWD} + exit -1 + fi + + if ! [ -f ${LIBEVENT_SPEC_DIR}/${LIBEVENT_SPEC_FILE} ] ; then + echo "ERROR: cannot download ${LIBEVENT_SPEC_FILE} file" + cd ${CPWD} + exit -1 + fi + + cp ${LIBEVENT_SPEC_DIR}/${LIBEVENT_SPEC_FILE} ${BUILDDIR}/SPECS +fi + +cd ${BUILDDIR}/SPECS +rpmbuild -ba ${BUILDDIR}/SPECS/${LIBEVENT_SPEC_FILE} +ER=$? +if ! [ ${ER} -eq 0 ] ; then + cd ${CPWD} + exit -1 +fi + +PACK=${BUILDDIR}/RPMS/${ARCH}/libevent-${LIBEVENT_MAJOR_VERSION}*.rpm +sudo rpm ${RPMOPTIONS} ${PACK} +ER=$? +if ! [ ${ER} -eq 0 ] ; then + echo "Cannot install packages ${PACK}" + cd ${CPWD} + exit -1 +fi + +PACK=${BUILDDIR}/RPMS/${ARCH}/libevent-devel*.rpm +sudo rpm ${RPMOPTIONS} ${PACK} +ER=$? +if ! [ ${ER} -eq 0 ] ; then + echo "Cannot install packages ${PACK}" + cd ${CPWD} + exit -1 +fi + +# EPEL (for hiredis) + +cd ${CPWD} +./epel.install.sh + +# Platform file + +echo "CentOS6.5" > ${BUILDDIR}/platform + +cp ${CPWD}/epel.install.sh ${BUILDDIR}/install.sh + +cd ${CPWD} diff --git a/rpm/Fedora.pre.build.sh b/rpm/Fedora.pre.build.sh new file mode 100755 index 0000000..a5e8bc8 --- /dev/null +++ b/rpm/Fedora.pre.build.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +CPWD=`pwd` + +# Fedora preparation script. + +. ./common.pre.build.sh + +PACKS="libevent-devel mariadb-devel" +sudo yum -y install ${PACKS} +ER=$? +if ! [ ${ER} -eq 0 ] ; then + echo "Cannot install package(s) ${PACKS}" + cd ${CPWD} + exit -1 +fi + +echo "Fedora20" > ${BUILDDIR}/platform + +cd ${CPWD} diff --git a/rpm/build.instructions.txt b/rpm/build.instructions.txt new file mode 100644 index 0000000..4bfbfaa --- /dev/null +++ b/rpm/build.instructions.txt @@ -0,0 +1,43 @@ +MANUAL PROCESS FOR CENTOS 6: + + +The first thing you need to build/use the TURN server with CentOS is to build and install libevent 2.x.x. CentOS 6 ships with libevent 1.x.x. You can find a .spec file to build libevent 2.x.x here: https://github.com/crocodilertc/libevent + +To build libevent: + + 1) Install the dependencies for building libevent: gcc, make, redhat-rpm-config, doxygen, openssl-devel, rpm-build + 2) $ mkdir ~/rpmbuild + 3) $ mkdir ~/rpmbuild/SOURCES + 4) $ mkdir ~/rpmbuild/SPECS + 5) Put the tarball for libevent (https://github.com/downloads/libevent/libevent/libevent-2.0.21-stable.tar.gz) and put it into ~/rpmbuild/SOURCES + 6) Put the .spec for libevent (https://raw.github.com/crocodilertc/libevent/master/libevent.spec) into ~/rpmbuild/SPECS + 7) Build the RPMs, "rpmbuild -ba ~/rpmbuild/SPECS/libevent.spec" + + +To build the TURN server: + + 1) Install libevent and libevent-devel rpms + 2) Install EPEL (http://fedoraproject.org/wiki/EPEL) - needed for hiredis + 3) Install the dependencies for building the TURN server: gcc, make, redhat-rpm-config, openssl-devel, libevent-devel >= 2.0.0, + mysql-devel, postgresql-devel, hiredis-devel + 4) $ mkdir ~/rpmbuild + 5) $ mkdir ~/rpmbuild/SOURCES + 6) Export the TURN server from SVN, "svn export http://coturn.googlecode.com/svn/trunk/ turnserver-2.6.7.0" + 7) Create a tarball, "tar zcf ~/rpmbuild/SOURCES/turnserver-2.6.7.0.tar.gz turnserver-2.6.7.0" + 8) Build the RPMs, "rpmbuild -ta ~/rpmbuild/SOURCES/turnserver-2.6.7.0.tar.gz" + + +AUTOMATED PROCESS FOR CENTOS 6: + +$ cd <...>/coturn/rpm +$ ./CentOS6.pre.build.sh +$ ./build.sh +(then see the tarball in ~/rpmbuild/RPMS/) + +AUTOMATED PROCESS FOR Fedora: + +$ cd <...>/coturn/rpm +$ ./Fedora.pre.build.sh +$ ./build.sh +(then see the tarball in ~/rpmbuild/RPMS/) + diff --git a/rpm/build.settings.sh b/rpm/build.settings.sh new file mode 100755 index 0000000..1e61070 --- /dev/null +++ b/rpm/build.settings.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Common settings script. + +TURNVERSION=3.3.0.0 +BUILDDIR=~/rpmbuild +ARCH=`uname -p` +TURNSERVER_SVN_URL=http://coturn.googlecode.com/svn +TURNSERVER_SVN_URL_VER=trunk + +WGETOPTIONS="--no-check-certificate" +RPMOPTIONS="-ivh --force" + + diff --git a/rpm/build.sh b/rpm/build.sh new file mode 100755 index 0000000..745f692 --- /dev/null +++ b/rpm/build.sh @@ -0,0 +1,98 @@ +#!/bin/bash + +CPWD=`pwd` + +. ./build.settings.sh + +# Required packages + +PACKS="postgresql-devel hiredis-devel" + +sudo yum -y install ${PACKS} +ER=$? +if ! [ ${ER} -eq 0 ] ; then + echo "Cannot install packages ${PACKS}" + cd ${CPWD} + exit -1 +fi + +# TURN + +cd ${BUILDDIR}/tmp +rm -rf turnserver-${TURNVERSION} +svn export ${TURNSERVER_SVN_URL}/${TURNSERVER_SVN_URL_VER}/ turnserver-${TURNVERSION} +ER=$? +if ! [ ${ER} -eq 0 ] ; then + cd ${CPWD} + exit -1 +fi + +tar zcf ${BUILDDIR}/SOURCES/turnserver-${TURNVERSION}.tar.gz turnserver-${TURNVERSION} +ER=$? +if ! [ ${ER} -eq 0 ] ; then + cd ${CPWD} + exit -1 +fi + +rpmbuild -ta ${BUILDDIR}/SOURCES/turnserver-${TURNVERSION}.tar.gz +ER=$? +if ! [ ${ER} -eq 0 ] ; then + cd ${CPWD} + exit -1 +fi + +# Make binary tarball + +cd ${BUILDDIR}/RPMS/${ARCH} +mkdir -p di +mv *debuginfo* di +mv *devel* di +rm -rf turnserver-${TURNVERSION} +mkdir turnserver-${TURNVERSION} +mv *.rpm turnserver-${TURNVERSION}/ + +rm -rf turnserver-${TURNVERSION}/install.sh + +if [ -f ${BUILDDIR}/install.sh ] ; then + cat ${BUILDDIR}/install.sh > turnserver-${TURNVERSION}/install.sh +else + echo "#!/bin/sh" > turnserver-${TURNVERSION}/install.sh +fi + +cat <>turnserver-${TURNVERSION}/install.sh + +sudo yum -y install openssl +sudo yum -y install telnet + +for i in *.rpm ; do + + sudo yum -y install \${i} + ER=\$? + if ! [ \${ER} -eq 0 ] ; then + sudo rpm -Uvh \${i} + ER=\$? + if ! [ \${ER} -eq 0 ] ; then + sudo rpm -ivh --force \${i} + ER=\$? + if ! [ \${ER} -eq 0 ] ; then + echo "ERROR: cannot install package \${i}" + exit -1 + fi + fi + fi +done + +echo SUCCESS ! + +EOF + +chmod a+x turnserver-${TURNVERSION}/install.sh + +cp ${CPWD}/uninstall.turnserver.sh turnserver-${TURNVERSION}/ +chmod a+x turnserver-${TURNVERSION}/uninstall.turnserver.sh + +PLATFORM=`cat ${BUILDDIR}/platform` + +tar cvfz turnserver-${TURNVERSION}-${PLATFORM}-${ARCH}.tar.gz turnserver-${TURNVERSION} + +cd ${CPWD} diff --git a/rpm/common.pre.build.sh b/rpm/common.pre.build.sh new file mode 100755 index 0000000..0bf831e --- /dev/null +++ b/rpm/common.pre.build.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Common preparation script. + +. ./build.settings.sh + +# DIRS + +rm -rf ${BUILDDIR} + +mkdir -p ${BUILDDIR} +mkdir -p ${BUILDDIR}/SOURCES +mkdir -p ${BUILDDIR}/SPECS +mkdir -p ${BUILDDIR}/RPMS +mkdir -p ${BUILDDIR}/tmp + +# Common packs + +PACKS="make gcc redhat-rpm-config rpm-build doxygen openssl-devel svn" +sudo yum -y install ${PACKS} +ER=$? +if ! [ ${ER} -eq 0 ] ; then + echo "Cannot install packages ${PACKS}" + exit -1 +fi + diff --git a/rpm/epel.install.sh b/rpm/epel.install.sh new file mode 100755 index 0000000..7cc2b1f --- /dev/null +++ b/rpm/epel.install.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +CPWD=`pwd` + +# Epel installation script + +EPEL=epel-release-6-8.noarch +EPELRPM=${EPEL}.rpm +BUILDDIR=~/rpmbuild +WGETOPTIONS="--no-check-certificate" +RPMOPTIONS="-ivh --force" + +mkdir -p ${BUILDDIR} +mkdir -p ${BUILDDIR}/RPMS + +sudo yum -y install wget + +cd ${BUILDDIR}/RPMS +if ! [ -f ${EPELRPM} ] ; then + wget ${WGETOPTIONS} http://download.fedoraproject.org/pub/epel/6/i386/${EPELRPM} + ER=$? + if ! [ ${ER} -eq 0 ] ; then + cd ${CPWD} + exit -1 + fi +fi + +PACK=${EPELRPM} +sudo rpm ${RPMOPTIONS} ${PACK} +ER=$? +if ! [ ${ER} -eq 0 ] ; then + echo "Cannot install package ${PACK}" + cd ${CPWD} + exit -1 +fi + +cd ${CPWD} + + diff --git a/rpm/turnserver.init.el b/rpm/turnserver.init.el new file mode 100644 index 0000000..724f87c --- /dev/null +++ b/rpm/turnserver.init.el @@ -0,0 +1,82 @@ +#!/bin/bash +# +# Startup script for TURN Server +# +# chkconfig: 345 85 15 +# description: RFC 5766 TURN Server +# +# processname: turnserver +# pidfile: /var/run/turnserver.pid +# config: /etc/turnserver/turnserver.conf +# +### BEGIN INIT INFO +# Provides: turnserver +# Required-Start: $local_fs $network +# Short-Description: RFC 5766 TURN Server +# Description: RFC 5766 TURN Server +### END INIT INFO + +# Source function library. +. /etc/rc.d/init.d/functions + +TURN=/usr/bin/turnserver +PROG=turnserver +TURNCFG=/etc/turnserver/$PROG.conf +PID_FILE=/var/run/$PROG.pid +LOCK_FILE=/var/lock/subsys/$PROG +DEFAULTS=/etc/sysconfig/$PROG +RETVAL=0 +USER=turnserver + +start() { + echo -n $"Starting $PROG: " + daemon --user=$USER $TURN $OPTIONS + RETVAL=$? + if [ $RETVAL = 0 ]; then + pidofproc $TURN > $PID_FILE + RETVAL=$? + [ $RETVAL = 0 ] && touch $LOCK_FILE && success + fi + echo + return $RETVAL +} + +stop() { + echo -n $"Stopping $PROG: " + killproc $TURN + RETVAL=$? + echo + [ $RETVAL = 0 ] && rm -f $LOCK_FILE $PID_FILE +} + +[ -f $DEFAULTS ] && . $DEFAULTS +OPTIONS="-o -c $TURNCFG $EXTRA_OPTIONS" + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; + status) + status $TURN + RETVAL=$? + ;; + restart) + stop + start + ;; + condrestart) + if [ -f $PID_FILE ] ; then + stop + start + fi + ;; + *) + echo $"Usage: $PROG {start|stop|restart|condrestart|status|help}" + exit 1 +esac + +exit $RETVAL diff --git a/rpm/turnserver.service.fc b/rpm/turnserver.service.fc new file mode 100644 index 0000000..3ac4417 --- /dev/null +++ b/rpm/turnserver.service.fc @@ -0,0 +1,15 @@ +[Unit] +Description=coturn +Documentation=man:coturn(1) man:turnadmin(1) man:turnserver(1) +After=syslog.target network.target + +[Service] +Type=forking +EnvironmentFile=/etc/sysconfig/turnserver +PIDFile=/var/run/turnserver.pid +ExecStart=/usr/bin/turnserver -o -c /etc/turnserver/turnserver.conf $EXTRA_OPTIONS +ExecStopPost=/usr/bin/rm -f /var/run/turnserver.pid +Restart=on-abort + +[Install] +WantedBy=multi-user.target diff --git a/rpm/turnserver.spec b/rpm/turnserver.spec new file mode 100644 index 0000000..b566e09 --- /dev/null +++ b/rpm/turnserver.spec @@ -0,0 +1,358 @@ +Name: turnserver +Version: 3.3.0.0 +Release: 0%{dist} +Summary: Coturn TURN Server + +Group: System Environment/Libraries +License: BSD +URL: https://code.google.com/p/coturn/ +Source0: http://turnserver.open-sys.org/downloads/v%{version}/%{name}-%{version}.tar.gz + +BuildRequires: gcc, make, redhat-rpm-config +BuildRequires: openssl-devel, libevent-devel >= 2.0.0, postgresql-devel +BuildRequires: hiredis-devel +Requires: openssl, libevent >= 2.0.0, mysql-libs, postgresql-libs +Requires: hiredis, perl-DBI, perl-libwww-perl +Requires: telnet +%if 0%{?el6} +BuildRequires: epel-release, mysql-devel +Requires: epel-release, mysql-libs +%else +BuildRequires: mariadb-devel +Requires: mariadb-libs +%endif + + +%description +The TURN Server is a VoIP media traffic NAT traversal server and gateway. It +can be used as a general-purpose network traffic TURN server/gateway, too. + +This implementation also includes some extra features. Supported RFCs: + +TURN specs: +- RFC 5766 - base TURN specs +- RFC 6062 - TCP relaying TURN extension +- RFC 6156 - IPv6 extension for TURN +- Experimental DTLS support as client protocol. + +STUN specs: +- RFC 3489 - "classic" STUN +- RFC 5389 - base "new" STUN specs +- RFC 5769 - test vectors for STUN protocol testing +- RFC 5780 - NAT behavior discovery support + +The implementation fully supports the following client-to-TURN-server protocols: +- UDP (per RFC 5766) +- TCP (per RFC 5766 and RFC 6062) +- TLS (per RFC 5766 and RFC 6062); SSL3/TLS1.0/TLS1.1/TLS1.2; SSL2 wrapping + supported +- DTLS (experimental non-standard feature) + +Supported relay protocols: +- UDP (per RFC 5766) +- TCP (per RFC 6062) + +Supported user databases (for user repository, with passwords or keys, if +authentication is required): +- Flat files +- MySQL +- PostgreSQL +- Redis + +Redis can also be used for status and statistics storage and notification. + +Supported TURN authentication mechanisms: +- short-term +- long-term +- TURN REST API (a modification of the long-term mechanism, for time-limited + secret-based authentication, for WebRTC applications) + +The load balancing can be implemented with the following tools (either one or a +combination of them): +- network load-balancer server +- DNS-based load balancing +- built-in ALTERNATE-SERVER mechanism. + + +%package utils +Summary: TURN client utils +Group: System Environment/Libraries +Requires: turnserver-client-libs = %{version}-%{release} + +%description utils +This package contains the TURN client utils. + +%package client-libs +Summary: TURN client library +Group: System Environment/Libraries +Requires: openssl, libevent >= 2.0.0 + +%description client-libs +This package contains the TURN client library. + +%package client-devel +Summary: TURN client development headers. +Group: Development/Libraries +Requires: turnserver-client-libs = %{version}-%{release} + +%description client-devel +This package contains the TURN client development headers. + +%prep +%setup -q -n %{name}-%{version} + +%build +PREFIX=%{_prefix} CONFDIR=%{_sysconfdir}/%{name} EXAMPLESDIR=%{_datadir}/%{name} \ + MANPREFIX=%{_datadir} LIBDIR=%{_libdir} MORECMD=cat ./configure +make + +%install +rm -rf $RPM_BUILD_ROOT +DESTDIR=$RPM_BUILD_ROOT make install +mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig +install -m644 rpm/turnserver.sysconfig \ + $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/turnserver +mv $RPM_BUILD_ROOT/%{_sysconfdir}/%{name}/turnuserdb.conf.default \ + $RPM_BUILD_ROOT/%{_sysconfdir}/%{name}/turnuserdb.conf +%if 0%{?el6} +cat $RPM_BUILD_ROOT/%{_sysconfdir}/%{name}/turnserver.conf.default | \ + sed s/#syslog/syslog/g | \ + sed s/#no-stdout-log/no-stdout-log/g > \ + $RPM_BUILD_ROOT/%{_sysconfdir}/%{name}/turnserver.conf +mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/rc.d/init.d +install -m755 rpm/turnserver.init.el \ + $RPM_BUILD_ROOT/%{_sysconfdir}/rc.d/init.d/turnserver +%else +cat $RPM_BUILD_ROOT/%{_sysconfdir}/%{name}/turnserver.conf.default | \ + sed s/#syslog/syslog/g | \ + sed s/#no-stdout-log/no-stdout-log/g | \ + sed s/#pidfile/pidfile/g > \ + $RPM_BUILD_ROOT/%{_sysconfdir}/%{name}/turnserver.conf +mkdir -p $RPM_BUILD_ROOT/%{_unitdir} +install -m755 rpm/turnserver.service.fc \ + $RPM_BUILD_ROOT/%{_unitdir}/turnserver.service +%endif +rm -rf $RPM_BUILD_ROOT/%{_sysconfdir}/%{name}/turnserver.conf.default + +%clean +rm -rf "$RPM_BUILD_ROOT" + +%pre +%{_sbindir}/groupadd -r turnserver 2> /dev/null || : +%{_sbindir}/useradd -r -g turnserver -s /bin/false -c "TURN Server daemon" -d \ + %{_datadir}/%{name} turnserver 2> /dev/null || : + +%post +%if 0%{?el6} +/sbin/chkconfig --add turnserver +%else +/bin/systemctl --system daemon-reload +%endif + +%preun +if [ $1 = 0 ]; then +%if 0%{?el6} + /sbin/service turnserver stop > /dev/null 2>&1 + /sbin/chkconfig --del turnserver +%else + /bin/systemctl stop turnserver.service + /bin/systemctl disable turnserver.service 2> /dev/null +%endif +fi + +%postun +%if 0%{?fedora} +/bin/systemctl --system daemon-reload +%endif + +%files +%defattr(-,root,root) +%{_bindir}/turnserver +%{_bindir}/turnadmin +%{_mandir}/man1/coturn.1.gz +%{_mandir}/man1/turnserver.1.gz +%{_mandir}/man1/turnadmin.1.gz +%dir %attr(-,turnserver,turnserver) %{_sysconfdir}/%{name} +%config(noreplace) %attr(0644,turnserver,turnserver) %{_sysconfdir}/%{name}/turnserver.conf +%config(noreplace) %attr(0644,turnserver,turnserver) %{_sysconfdir}/%{name}/turnuserdb.conf +%config(noreplace) %{_sysconfdir}/sysconfig/turnserver +%if 0%{?el6} +%config %{_sysconfdir}/rc.d/init.d/turnserver +%else +%config %{_unitdir}/turnserver.service +%endif +%dir %{_docdir}/%{name} +%{_docdir}/%{name}/LICENSE +%{_docdir}/%{name}/INSTALL +%{_docdir}/%{name}/postinstall.txt +%{_docdir}/%{name}/README.turnadmin +%{_docdir}/%{name}/README.turnserver +%{_docdir}/%{name}/schema.sql +%{_docdir}/%{name}/schema.stats.redis +%{_docdir}/%{name}/schema.userdb.redis +%dir %{_datadir}/%{name} +%{_datadir}/%{name}/schema.sql +%{_datadir}/%{name}/schema.stats.redis +%{_datadir}/%{name}/schema.userdb.redis +%{_datadir}/%{name}/testredisdbsetup.sh +%{_datadir}/%{name}/testsqldbsetup.sql +%dir %{_datadir}/%{name}/etc +%{_datadir}/%{name}/etc/turn_server_cert.pem +%{_datadir}/%{name}/etc/turn_server_pkey.pem +%{_datadir}/%{name}/etc/turnserver.conf +%{_datadir}/%{name}/etc/turnuserdb.conf +%dir %{_datadir}/%{name}/scripts +%{_datadir}/%{name}/scripts/peer.sh +%{_datadir}/%{name}/scripts/readme.txt +%dir %{_datadir}/%{name}/scripts/basic +%{_datadir}/%{name}/scripts/basic/dos_attack.sh +%{_datadir}/%{name}/scripts/basic/relay.sh +%{_datadir}/%{name}/scripts/basic/tcp_client.sh +%{_datadir}/%{name}/scripts/basic/tcp_client_c2c_tcp_relay.sh +%{_datadir}/%{name}/scripts/basic/udp_c2c_client.sh +%{_datadir}/%{name}/scripts/basic/udp_client.sh +%dir %{_datadir}/%{name}/scripts/loadbalance +%{_datadir}/%{name}/scripts/loadbalance/master_relay.sh +%{_datadir}/%{name}/scripts/loadbalance/slave_relay_1.sh +%{_datadir}/%{name}/scripts/loadbalance/slave_relay_2.sh +%{_datadir}/%{name}/scripts/loadbalance/tcp_c2c_tcp_relay.sh +%{_datadir}/%{name}/scripts/loadbalance/udp_c2c.sh +%dir %{_datadir}/%{name}/scripts/longtermsecure +%{_datadir}/%{name}/scripts/longtermsecure/secure_dos_attack.sh +%{_datadir}/%{name}/scripts/longtermsecure/secure_dtls_client.sh +%{_datadir}/%{name}/scripts/longtermsecure/secure_dtls_client_cert.sh +%{_datadir}/%{name}/scripts/longtermsecure/secure_tls_client_cert.sh +%{_datadir}/%{name}/scripts/longtermsecure/secure_relay.sh +%{_datadir}/%{name}/scripts/longtermsecure/secure_relay_cert.sh +%{_datadir}/%{name}/scripts/longtermsecure/secure_tcp_client.sh +%{_datadir}/%{name}/scripts/longtermsecure/secure_tcp_client_c2c_tcp_relay.sh +%{_datadir}/%{name}/scripts/longtermsecure/secure_tls_client.sh +%{_datadir}/%{name}/scripts/longtermsecure/secure_tls_client_c2c_tcp_relay.sh +%{_datadir}/%{name}/scripts/longtermsecure/secure_udp_c2c.sh +%{_datadir}/%{name}/scripts/longtermsecure/secure_udp_client.sh +%dir %{_datadir}/%{name}/scripts/longtermsecuredb +%{_datadir}/%{name}/scripts/longtermsecuredb/secure_relay_with_db_mysql.sh +%{_datadir}/%{name}/scripts/longtermsecuredb/secure_relay_with_db_psql.sh +%{_datadir}/%{name}/scripts/longtermsecuredb/secure_relay_with_db_redis.sh +%dir %{_datadir}/%{name}/scripts/restapi +%{_datadir}/%{name}/scripts/restapi/secure_relay_secret.sh +%{_datadir}/%{name}/scripts/restapi/secure_relay_secret_with_db_mysql.sh +%{_datadir}/%{name}/scripts/restapi/secure_relay_secret_with_db_psql.sh +%{_datadir}/%{name}/scripts/restapi/secure_relay_secret_with_db_redis.sh +%{_datadir}/%{name}/scripts/restapi/secure_udp_client_with_secret.sh +%{_datadir}/%{name}/scripts/restapi/shared_secret_maintainer.pl +%dir %{_datadir}/%{name}/scripts/selfloadbalance +%{_datadir}/%{name}/scripts/selfloadbalance/secure_dos_attack.sh +%{_datadir}/%{name}/scripts/selfloadbalance/secure_relay.sh +%dir %{_datadir}/%{name}/scripts/shorttermsecure +%{_datadir}/%{name}/scripts/shorttermsecure/secure_relay_short_term_mech.sh +%{_datadir}/%{name}/scripts/shorttermsecure/secure_tcp_client_c2c_tcp_relay_short_term.sh +%{_datadir}/%{name}/scripts/shorttermsecure/secure_udp_client_short_term.sh +%dir %{_datadir}/%{name}/scripts/mobile +%{_datadir}/%{name}/scripts/mobile/mobile_relay.sh +%{_datadir}/%{name}/scripts/mobile/mobile_dtls_client.sh +%{_datadir}/%{name}/scripts/mobile/mobile_tcp_client.sh +%{_datadir}/%{name}/scripts/mobile/mobile_tls_client_c2c_tcp_relay.sh +%{_datadir}/%{name}/scripts/mobile/mobile_udp_client.sh + +%files utils +%defattr(-,root,root) +%{_bindir}/turnutils_peer +%{_bindir}/turnutils_stunclient +%{_bindir}/turnutils_uclient +%{_mandir}/man1/turnutils.1.gz +%{_mandir}/man1/turnutils_peer.1.gz +%{_mandir}/man1/turnutils_stunclient.1.gz +%{_mandir}/man1/turnutils_uclient.1.gz +%dir %{_docdir}/%{name} +%{_docdir}/%{name}/LICENSE +%{_docdir}/%{name}/README.turnutils +%dir %{_datadir}/%{name} +%dir %{_datadir}/%{name}/etc +%{_datadir}/%{name}/etc/turn_client_cert.pem +%{_datadir}/%{name}/etc/turn_client_pkey.pem + +%files client-libs +%{_docdir}/%{name}/LICENSE +%{_libdir}/libturnclient.a + +%files client-devel +%{_docdir}/%{name}/LICENSE +%dir %{_includedir}/turn +%{_includedir}/turn/ns_turn_defs.h +%dir %{_includedir}/turn/client +%{_includedir}/turn/client/ns_turn_ioaddr.h +%{_includedir}/turn/client/ns_turn_msg_addr.h +%{_includedir}/turn/client/ns_turn_msg_defs.h +%{_includedir}/turn/client/ns_turn_msg.h +%{_includedir}/turn/client/TurnMsgLib.h + +%changelog +* Sun Feb 09 2014 Oleg Moskalenko + - Sync to 3.3.0.0 +* Tue Feb 04 2014 Oleg Moskalenko + - Sync to 3.2.2.6 +* Sat Jan 25 2014 Oleg Moskalenko + - Sync to 3.2.2.5 +* Fri Jan 24 2014 Oleg Moskalenko + - Sync to 3.2.2.4 +* Thu Jan 23 2014 Oleg Moskalenko + - Sync to 3.2.2.3 +* Tue Jan 21 2014 Oleg Moskalenko + - Sync to 3.2.2.2 +* Sat Jan 11 2014 Oleg Moskalenko + - CPU optimization, added to 3.2.2.1 +* Mon Jan 06 2014 Oleg Moskalenko + - Linux epoll performance improvements, added to 3.2.1.4 +* Mon Jan 06 2014 Oleg Moskalenko + - Telnet client installation added to 3.2.1.3 +* Sun Jan 05 2014 Oleg Moskalenko + - Sync to 3.2.1.2 +* Fri Jan 03 2014 Oleg Moskalenko + - Sync to 3.2.1.1 +* Thu Dec 26 2013 Oleg Moskalenko + - Sync to 3.2.1.0 +* Wed Dec 25 2013 Oleg Moskalenko + - Sync to 3.1.6.0 +* Mon Dec 23 2013 Oleg Moskalenko + - Sync to 3.1.5.3 +* Fri Dec 20 2013 Oleg Moskalenko + - Sync to 3.1.5.1 +* Thu Dec 19 2013 Oleg Moskalenko + - Sync to 3.1.4.2 +* Sat Dec 14 2013 Oleg Moskalenko + - Sync to 3.1.3.1 +* Wed Dec 11 2013 Oleg Moskalenko + - OpenSSL installation fixed 3.1.2.3 +* Tue Dec 10 2013 Oleg Moskalenko + - Updated to version 3.1.2.2 +* Mon Dec 09 2013 Oleg Moskalenko + - Updated to version 3.1.2.1 +* Sun Dec 01 2013 Oleg Moskalenko + - Updated to version 3.1.1.0 +* Sat Nov 30 2013 Oleg Moskalenko + - Updated to version 3.0.2.1. +* Thu Nov 28 2013 Oleg Moskalenko + - Config file setting fixed: version 3.0.1.4. +* Wed Nov 27 2013 Oleg Moskalenko + - Config file setting fixed: version 3.0.1.3. +* Mon Nov 25 2013 Oleg Moskalenko + - Updated to version 3.0.1.2 +* Sun Nov 10 2013 Oleg Moskalenko + - Updated to version 3.0.0.0 +* Fri Nov 8 2013 Oleg Moskalenko + - Updated to version 2.6.7.2 +* Thu Nov 7 2013 Oleg Moskalenko + - Updated to version 2.6.7.1 +* Sun Nov 3 2013 Oleg Moskalenko + - Updated to version 2.6.7.0 +* Sat Nov 2 2013 Peter Dunkley + - Added Fedora support +* Thu Oct 31 2013 Oleg Moskalenko + - Updated to version 2.6.6.2 +* Sun Oct 27 2013 Oleg Moskalenko + - Updated to version 2.6.6.1 +* Sun Oct 27 2013 Peter Dunkley + - Updated to version 2.6.6.0 +* Fri May 3 2013 Peter Dunkley + - First version diff --git a/rpm/turnserver.sysconfig b/rpm/turnserver.sysconfig new file mode 100644 index 0000000..cc587bb --- /dev/null +++ b/rpm/turnserver.sysconfig @@ -0,0 +1,5 @@ +# +# TURN Server startup options +# + +EXTRA_OPTIONS="" diff --git a/rpm/uninstall.turnserver.sh b/rpm/uninstall.turnserver.sh new file mode 100755 index 0000000..01cd364 --- /dev/null +++ b/rpm/uninstall.turnserver.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +for i in `rpm -q -a | grep turnserver-utils-3` +do + echo $i + sudo rpm -e $i +done + +for i in `rpm -q -a | grep turnserver-client-libs-3` +do + echo $i + sudo rpm -e $i +done + +for i in `rpm -q -a | grep turnserver.*-3` +do + echo $i + sudo rpm -e $i +done diff --git a/src/apps/common/apputils.c b/src/apps/common/apputils.c new file mode 100644 index 0000000..0ef52c0 --- /dev/null +++ b/src/apps/common/apputils.c @@ -0,0 +1,902 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "ns_turn_utils.h" +#include "ns_turn_msg.h" + +#include "apputils.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +/************************/ + +int IS_TURN_SERVER = 0; + +/*********************** Sockets *********************************/ + +int socket_set_nonblocking(evutil_socket_t fd) +{ +#if defined(WIN32) + unsigned long nonblocking = 1; + ioctlsocket(fd, FIONBIO, (unsigned long*) &nonblocking); +#else + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { + perror("O_NONBLOCK"); + return -1; + } +#endif + return 0; +} + +void read_spare_buffer(evutil_socket_t fd) +{ + if(fd >= 0) { + static char buffer[65536]; + recv(fd, buffer, sizeof(buffer), MSG_DONTWAIT); + } +} + +int set_sock_buf_size(evutil_socket_t fd, int sz0) +{ + int sz; + + sz = sz0; + while (sz > 0) { + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (const void*) (&sz), (socklen_t) sizeof(sz)) < 0) { + sz = sz / 2; + } else { + break; + } + } + + if (sz < 1) { + perror("Cannot set socket rcv size"); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Cannot set rcv sock size %d on fd %d\n", sz0, fd); + } + + sz = sz0; + while (sz > 0) { + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (const void*) (&sz), (socklen_t) sizeof(sz)) < 0) { + sz = sz / 2; + } else { + break; + } + } + + if (sz < 1) { + perror("Cannot set socket snd size"); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Cannot set snd sock size %d on fd %d\n", sz0, fd); + } + + return 0; +} + +int socket_tcp_set_keepalive(evutil_socket_t fd) +{ +#ifdef SO_KEEPALIVE + /* Set the keepalive option active */ + { + int on = 1; + setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (const void*)&on, (socklen_t) sizeof(on)); + } +#else + UNUSED_ARG(fd); +#endif + +#ifdef SO_NOSIGPIPE + { + int on = 1; + setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, (const void*)&on, (socklen_t) sizeof(on)); + } +#endif + + return 0; +} + +int socket_set_reusable(evutil_socket_t fd, int flag) +{ + + if (fd < 0) + return -1; + else { + +#if defined(WIN32) + int use_reuseaddr = IS_TURN_SERVER; +#else + int use_reuseaddr = 1; +#endif + +#if defined(SO_REUSEPORT) + if (use_reuseaddr) { + int on = flag; + setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (const void*) &on, (socklen_t) sizeof(on)); + } +#endif + +#if defined(SO_REUSEADDR) + if (use_reuseaddr) { + int on = flag; + int ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void*) &on, (socklen_t) sizeof(on)); + if (ret < 0) + perror("SO_REUSEADDR"); + } +#endif + + return 0; + } +} + +int sock_bind_to_device(evutil_socket_t fd, const unsigned char* ifname) { + + if (fd >= 0 && ifname && ifname[0]) { + +#if defined(SO_BINDTODEVICE) + + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + + strncpy(ifr.ifr_name, (const char*) ifname, sizeof(ifr.ifr_name)); + + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void *) &ifr, sizeof(ifr)) < 0) { + if (errno == EPERM) + perror("You must obtain superuser privileges to bind a socket to device"); + else + perror("Cannot bind socket to device"); + + return -1; + } + + return 0; + +#endif + + } + + return 0; +} + +int addr_connect(evutil_socket_t fd, const ioa_addr* addr, int *out_errno) +{ + if (!addr || fd < 0) + return -1; + else { + int err = 0; + do { + if (addr->ss.sa_family == AF_INET) { + err = connect(fd, (const struct sockaddr *) addr, sizeof(struct sockaddr_in)); + } else if (addr->ss.sa_family == AF_INET6) { + err = connect(fd, (const struct sockaddr *) addr, sizeof(struct sockaddr_in6)); + } else { + return -1; + } + } while (err < 0 && errno == EINTR); + + if(out_errno) + *out_errno = errno; + + if (err < 0 && errno != EINPROGRESS) + perror("Connect"); + + return err; + } +} + +int addr_bind(evutil_socket_t fd, const ioa_addr* addr, int reusable) +{ + if (!addr || fd < 0) { + + return -1; + + } else { + + int ret = -1; + + socket_set_reusable(fd, reusable); + + if (addr->ss.sa_family == AF_INET) { + do { + ret = bind(fd, (const struct sockaddr *) addr, sizeof(struct sockaddr_in)); + } while (ret < 0 && errno == EINTR); + } else if (addr->ss.sa_family == AF_INET6) { + const int off = 0; + setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (const char *) &off, sizeof(off)); + do { + ret = bind(fd, (const struct sockaddr *) addr, sizeof(struct sockaddr_in6)); + } while (ret < 0 && errno == EINTR); + } else { + return -1; + } + if(ret<0) { + int err = errno; + perror("bind"); + char str[129]; + addr_to_string(addr,(u08bits*)str); + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "Trying to bind fd %d to <%s>: errno=%d\n", fd, str, err); + } + return ret; + } +} + +int addr_get_from_sock(evutil_socket_t fd, ioa_addr *addr) +{ + + if (fd < 0 || !addr) + return -1; + else { + + ioa_addr a; + a.ss.sa_family = AF_INET6; + socklen_t socklen = get_ioa_addr_len(&a); + if (getsockname(fd, (struct sockaddr*) &a, &socklen) < 0) { + a.ss.sa_family = AF_INET; + socklen = get_ioa_addr_len(&a); + if (getsockname(fd, (struct sockaddr*) &a, &socklen) < 0) { + return -1; + } + } + + addr_cpy(addr, &a); + + return 0; + } +} + +/////////////////// MTU ///////////////////////////////////////// + +int set_socket_df(evutil_socket_t fd, int family, int value) +{ + + int ret=0; + +#if defined(IP_DONTFRAG) && defined(IPPROTO_IP) //BSD + { + const int val=value; + /* kernel sets DF bit on outgoing IP packets */ + if(family==AF_INET) { + ret = setsockopt(fd, IPPROTO_IP, IP_DONTFRAG, &val, sizeof(val)); + } else { +#if defined(IPV6_DONTFRAG) && defined(IPPROTO_IPV6) + ret = setsockopt(fd, IPPROTO_IPV6, IPV6_DONTFRAG, &val, sizeof(val)); +#else +#error CANNOT SET IPV6 SOCKET DF FLAG (1) +#endif + } + if(ret<0) { + int err=errno; + perror("set socket df:"); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"%s: set sockopt failed: fd=%d, err=%d, family=%d\n",__FUNCTION__,fd,err,family); + } + } +#elif defined(IPPROTO_IP) && defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DO) && defined(IP_PMTUDISC_DONT) //LINUX + { + /* kernel sets DF bit on outgoing IP packets */ + if(family==AF_INET) { + int val=IP_PMTUDISC_DO; + if(!value) val=IP_PMTUDISC_DONT; + ret = setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &val, sizeof(val)); + } else { +#if defined(IPPROTO_IPV6) && defined(IPV6_MTU_DISCOVER) && defined(IPV6_PMTUDISC_DO) && defined(IPV6_PMTUDISC_DONT) + int val=IPV6_PMTUDISC_DO; + if(!value) val=IPV6_PMTUDISC_DONT; + ret = setsockopt(fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &val, sizeof(val)); +#else +#error CANNOT SET IPV6 SOCKET DF FLAG (2) +#endif + } + if(ret<0) { + perror("set DF"); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"%s: set sockopt failed\n",__FUNCTION__); + } + } +#else +//CANNOT SET SOCKET DF FLAG (3) : UNKNOWN PLATFORM + UNUSED_ARG(fd); + UNUSED_ARG(family); + UNUSED_ARG(value); +#endif + + return ret; +} + +static int get_mtu_from_ssl(SSL* ssl) +{ + int ret = SOSO_MTU; +#if !defined(TURN_NO_DTLS) + if(ssl) + ret = BIO_ctrl(SSL_get_wbio(ssl), BIO_CTRL_DGRAM_QUERY_MTU, 0, NULL); +#else + UNUSED_ARG(ssl); +#endif + return ret; +} + +static void set_query_mtu(SSL* ssl) { + if(ssl) { +#if defined(SSL_OP_NO_QUERY_MTU) + SSL_set_options(ssl, SSL_OP_NO_QUERY_MTU); +#else + ; +#endif + } +} + +int decrease_mtu(SSL* ssl, int mtu, int verbose) +{ + + if (!ssl) + return mtu; + + int new_mtu = get_mtu_from_ssl(ssl); + + if (new_mtu < 1) + new_mtu = mtu; + + if (new_mtu > MAX_MTU) + mtu = MAX_MTU; + if (new_mtu > 0 && new_mtu < MIN_MTU) + mtu = MIN_MTU; + else if (new_mtu < mtu) + mtu = new_mtu; + else + mtu -= MTU_STEP; + + if (mtu < MIN_MTU) + mtu = MIN_MTU; + + set_query_mtu(ssl); + if (verbose) + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "1. mtu to use: %d\n", mtu); + +#if !defined(TURN_NO_DTLS) + SSL_set_mtu(ssl,mtu); + BIO_ctrl(SSL_get_wbio(ssl), BIO_CTRL_DGRAM_SET_MTU, mtu, NULL); +#endif + + return mtu; +} + +int set_mtu_df(SSL* ssl, evutil_socket_t fd, int family, int mtu, int df_value, int verbose) { + + if(!ssl || fd<0) return 0; + + int ret=set_socket_df(fd, family, df_value); + + if(!mtu) mtu=SOSO_MTU; + else if(mtuMAX_MTU) mtu=MAX_MTU; + + set_query_mtu(ssl); + if(verbose) TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"3. mtu to use: %d\n",mtu); + +#if !defined(TURN_NO_DTLS) + + SSL_set_mtu(ssl,mtu); + + BIO_ctrl(SSL_get_wbio(ssl), BIO_CTRL_DGRAM_SET_MTU, mtu, NULL); + +#endif + + if(verbose) TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"4. new mtu: %d\n",get_mtu_from_ssl(ssl)); + + return ret; +} + +int get_socket_mtu(evutil_socket_t fd, int family, int verbose) +{ + + int ret = 0; + + UNUSED_ARG(fd); + UNUSED_ARG(family); + UNUSED_ARG(verbose); + +#if defined(IP_MTU) + int val = 0; + socklen_t slen=sizeof(val); + if(family==AF_INET) { + ret = getsockopt(fd, IPPROTO_IP, IP_MTU, &val, &slen); + } else { +#if defined(IPPROTO_IPV6) && defined(IPV6_MTU) + ret = getsockopt(fd, IPPROTO_IPV6, IPV6_MTU, &val, &slen); +#endif + ; + } + + ret = val; +#endif + + if (verbose) + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: final=%d\n", __FUNCTION__, ret); + + return ret; +} + +//////////////////// socket error handle //////////////////// + +int handle_socket_error() { + switch (errno) { + case EINTR: + /* Interrupted system call. + * Just ignore. + */ + return 1; + case ENOBUFS: + /* No buffers, temporary condition. + * Just ignore and try later. + */ + return 1; + case EAGAIN: +#if defined(EWOULDBLOCK) +#if (EWOULDBLOCK != EAGAIN) + case EWOULDBLOCK: +#endif +#endif + return 1; + case EMSGSIZE: + return 1; + case EBADF: + /* Invalid socket. + * Must close connection. + */ + return 0; + case EHOSTDOWN: + /* Host is down. + * Just ignore, might be an attacker + * sending fake ICMP messages. + */ + return 1; + case ECONNRESET: + case ECONNREFUSED: + /* Connection reset by peer. */ + return 0; + case ENOMEM: + /* Out of memory. + * Must close connection. + */ + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"Out of memory!\n"); + return 0; + case EACCES: + /* Permission denied. + * Just ignore, we might be blocked + * by some firewall policy. Try again + * and hope for the best. + */ + return 1; + default: + /* Something unexpected happened */ + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"Unexpected error! (errno = %d)\n", errno); + return 0; + } +} + +//////////////////// Misc utils ////////////////////////////// + +char *skip_blanks(char* s) +{ + while(*s==' ' || *s=='\t' || *s=='\n') + ++s; + + return s; +} + +//////////////////// Config file search ////////////////////// + +#define Q(x) #x +#define QUOTE(x) Q(x) + +#define ETCDIR INSTALL_PREFIX/etc/ +#define QETCDIR QUOTE(ETCDIR) + +#define ETCDIR1 INSTALL_PREFIX/etc/turnserver/ +#define QETCDIR1 QUOTE(ETCDIR1) + +#define ETCDIR2 INSTALL_PREFIX/etc/coturn/ +#define QETCDIR2 QUOTE(ETCDIR2) + +static const char *config_file_search_dirs[] = {"./", "./turnserver/", "./coturn/", "./etc/", "./etc/turnserver/", "./etc/coturn/", "../etc/", "../etc/turnserver/", "../etc/coturn/", "/etc/", "/etc/turnserver/", "/etc/coturn/", "/usr/local/etc/", "/usr/local/etc/turnserver/", "/usr/local/etc/coturn/", QETCDIR, QETCDIR1, QETCDIR2, NULL }; +static char *c_execdir=NULL; + +void set_execdir(void) +{ + /* On some systems, this may give us the execution path */ + char *_var = getenv("_"); + if(_var && *_var) { + _var = strdup(_var); + char *edir=_var; + if(edir[0]!='.') + edir = strstr(edir,"/"); + if(edir && *edir) + edir = dirname(edir); + else + edir = dirname(_var); + if(c_execdir) + turn_free(c_execdir,strlen(c_execdir)+1); + c_execdir = strdup(edir); + turn_free(_var,strlen(_var)+1); + } +} + +void print_abs_file_name(const char *msg1, const char *msg2, const char *fn) +{ + char absfn[1025]; + absfn[0]=0; + + if(fn) { + while(fn[0] && fn[0]==' ') ++fn; + if(fn[0]) { + if(fn[0]=='/') { + STRCPY(absfn,fn); + } else { + if(fn[0]=='.' && fn[1]=='/') + fn+=2; + if(!getcwd(absfn,sizeof(absfn)-1)) + absfn[0]=0; + size_t blen=strlen(absfn); + if(blen0)) { + rlim.rlim_cur = rlim.rlim_cur>>1; + } + return (unsigned long)rlim.rlim_cur; + } + } + + return 0; +} + +////////////////////// Base 64 //////////////////////////// + +static char encoding_table[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/'}; +static char *decoding_table = NULL; +static size_t mod_table[] = {0, 2, 1}; + +char *base64_encode(const unsigned char *data, + size_t input_length, + size_t *output_length) { + + *output_length = 4 * ((input_length + 2) / 3); + + char *encoded_data = (char*)turn_malloc(*output_length+1); + if (encoded_data == NULL) return NULL; + + size_t i,j; + for (i = 0, j = 0; i < input_length;) { + + u32bits octet_a = i < input_length ? data[i++] : 0; + u32bits octet_b = i < input_length ? data[i++] : 0; + u32bits octet_c = i < input_length ? data[i++] : 0; + + u32bits triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; + + encoded_data[j++] = encoding_table[(triple >> 3 * 6) & 0x3F]; + encoded_data[j++] = encoding_table[(triple >> 2 * 6) & 0x3F]; + encoded_data[j++] = encoding_table[(triple >> 1 * 6) & 0x3F]; + encoded_data[j++] = encoding_table[(triple >> 0 * 6) & 0x3F]; + } + + for (i = 0; i < mod_table[input_length % 3]; i++) + encoded_data[*output_length - 1 - i] = '='; + + encoded_data[*output_length]=0; + + return encoded_data; +} + +void build_base64_decoding_table() { + + decoding_table = (char*)turn_malloc(256); + ns_bzero(decoding_table,256); + + int i; + for (i = 0; i < 64; i++) + decoding_table[(unsigned char) encoding_table[i]] = (char)i; +} + +unsigned char *base64_decode(const char *data, + size_t input_length, + size_t *output_length) { + + if (decoding_table == NULL) build_base64_decoding_table(); + + if (input_length % 4 != 0) return NULL; + + *output_length = input_length / 4 * 3; + if (data[input_length - 1] == '=') (*output_length)--; + if (data[input_length - 2] == '=') (*output_length)--; + + unsigned char *decoded_data = (unsigned char*)turn_malloc(*output_length); + if (decoded_data == NULL) return NULL; + + int i; + size_t j; + for (i = 0, j = 0; i < (int)input_length;) { + + uint32_t sextet_a = + data[i] == '=' ? 0 & i++ : decoding_table[(int)data[i++]]; + uint32_t sextet_b = + data[i] == '=' ? 0 & i++ : decoding_table[(int)data[i++]]; + uint32_t sextet_c = + data[i] == '=' ? 0 & i++ : decoding_table[(int)data[i++]]; + uint32_t sextet_d = + data[i] == '=' ? 0 & i++ : decoding_table[(int)data[i++]]; + + uint32_t triple = (sextet_a << 3 * 6) + (sextet_b << 2 * 6) + + (sextet_c << 1 * 6) + (sextet_d << 0 * 6); + + if (j < *output_length) + decoded_data[j++] = (triple >> 2 * 8) & 0xFF; + if (j < *output_length) + decoded_data[j++] = (triple >> 1 * 8) & 0xFF; + if (j < *output_length) + decoded_data[j++] = (triple >> 0 * 8) & 0xFF; + + } + + return decoded_data; +} + +////////////////// SSL ///////////////////// + +static const char* turn_get_method(const SSL_METHOD *method, const char* mdefault) +{ + { + if(!method) + return mdefault; + else { + +#ifndef OPENSSL_NO_SSL2 + if(method == SSLv2_server_method()) { + return "SSLv2"; + } else if(method == SSLv2_client_method()) { + return "SSLv2"; + } else +#endif + + if(method == SSLv3_server_method()) { + return "SSLv3"; + } else if(method == SSLv3_client_method()) { + return "SSLv3"; + } else if(method == SSLv23_server_method()) { + return "SSLv23"; + } else if(method == SSLv23_client_method()) { + return "SSLv23"; + } else if(method == TLSv1_server_method()) { + return "TLSv1.0"; + } else if(method == TLSv1_client_method()) { + return "TLSv1.0"; +#if defined(SSL_TXT_TLSV1_1) + } else if(method == TLSv1_1_server_method()) { + return "TLSv1.1"; + } else if(method == TLSv1_1_client_method()) { + return "TLSv1.1"; +#if defined(SSL_TXT_TLSV1_2) + } else if(method == TLSv1_2_server_method()) { + return "TLSv1.2"; + } else if(method == TLSv1_2_client_method()) { + return "TLSv1.2"; +#endif +#endif +#if !defined(TURN_NO_DTLS) + } else if(method == DTLSv1_server_method()) { + return "DTLSv1.0"; + } else if(method == DTLSv1_client_method()) { + return "DTLSv1.0"; +#endif + } else { + if(mdefault) + return mdefault; + return "UNKNOWN"; + } + } + } + +} + +const char* turn_get_ssl_method(SSL *ssl, const char* mdefault) +{ + if(!ssl) + return mdefault; + else { + const SSL_METHOD *method = SSL_get_ssl_method(ssl); + if(!method) + return mdefault; + else + return turn_get_method(method, mdefault); + } +} + +//////////// EVENT BASE /////////////// + +struct event_base *turn_event_base_new(void) +{ + struct event_config *cfg = event_config_new(); + + event_config_set_flag(cfg,EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST); + + return event_base_new_with_config(cfg); +} + +////////////////////////////////////////////////////////////// diff --git a/src/apps/common/apputils.h b/src/apps/common/apputils.h new file mode 100644 index 0000000..21815c4 --- /dev/null +++ b/src/apps/common/apputils.h @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __APP_LIB__ +#define __APP_LIB__ + +#include + +#include + +#include "ns_turn_ioaddr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//////////// Common defines /////////////////////////// + +#define PEER_DEFAULT_PORT (3480) + +#define DTLS_MAX_RECV_TIMEOUT (5) + +#define UR_CLIENT_SOCK_BUF_SIZE (65536) +#define UR_SERVER_SOCK_BUF_SIZE (UR_CLIENT_SOCK_BUF_SIZE * 32) + +extern int IS_TURN_SERVER; + +/////////// SSL ////////////////////////// + +enum _TURN_TLS_TYPE { + TURN_TLS_NO=0, + TURN_TLS_SSL23, + TURN_TLS_v1_0, +#if defined(SSL_TXT_TLSV1_1) + TURN_TLS_v1_1, +#if defined(SSL_TXT_TLSV1_2) + TURN_TLS_v1_2, +#endif +#endif + TURN_TLS_TOTAL +}; + +typedef enum _TURN_TLS_TYPE TURN_TLS_TYPE; + +////////////////////////////////////////// + +#define EVENT_DEL(ev) if(ev) { event_del(ev); event_free(ev); ev=NULL; } + +////////////////////////////////////////// + +#define ioa_socket_raw int + +///////////////////////// Sockets /////////////////////////////// + +#if defined(WIN32) +/** Do the platform-specific call needed to close a socket returned from + socket() or accept(). */ +#define socket_closesocket(s) closesocket(s) +#else +/** Do the platform-specific call needed to close a socket returned from + socket() or accept(). */ +#define socket_closesocket(s) close(s) +#endif + +void read_spare_buffer(evutil_socket_t fd); + +int set_sock_buf_size(evutil_socket_t fd, int sz); + +int socket_set_reusable(evutil_socket_t fd, int reusable); +int sock_bind_to_device(evutil_socket_t fd, const unsigned char* ifname); +int socket_set_nonblocking(evutil_socket_t fd); +int socket_tcp_set_keepalive(evutil_socket_t fd); + +int addr_connect(evutil_socket_t fd, const ioa_addr* addr, int *out_errno); + +int addr_bind(evutil_socket_t fd, const ioa_addr* addr, int reusable); + +int addr_get_from_sock(evutil_socket_t fd, ioa_addr *addr); + +int handle_socket_error(void); + +/////////////////////// SYS ///////////////////// + +void ignore_sigpipe(void); +unsigned long set_system_parameters(int max_resources); + +///////////////////////// MTU ////////////////////////// + +#define MAX_MTU (1500 - 20 - 8) +#define MIN_MTU (576 - 20 - 8) +#define SOSO_MTU (1300) + +#define MTU_STEP (68) + +int set_socket_df(evutil_socket_t fd, int family, int value); +int set_mtu_df(SSL* ssl, evutil_socket_t fd, int family, int mtu, int df_value, int verbose); +int decrease_mtu(SSL* ssl, int mtu, int verbose); +int get_socket_mtu(evutil_socket_t fd, int family, int verbose); + +////////////////// Misc utils ///////////////////////// + +char *skip_blanks(char* s); + +////////////////// File search //////////////////////// + +char* find_config_file(const char *config_file, int print_file_name); +void set_execdir(void); +void print_abs_file_name(const char *msg1, const char *msg2, const char *fn); + +////////////////// Base64 ///////////////////////////// + +char *base64_encode(const unsigned char *data, + size_t input_length, + size_t *output_length); + +void build_base64_decoding_table(void); + +unsigned char *base64_decode(const char *data, + size_t input_length, + size_t *output_length); + +///////////// SSL //////////////// + +const char* turn_get_ssl_method(SSL *ssl, const char* mdefault); + +//////////// Event Base ///////////////////// + +struct event_base *turn_event_base_new(void); + +/////////////////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif //__APP_LIB__ diff --git a/src/apps/common/hiredis_libevent2.c b/src/apps/common/hiredis_libevent2.c new file mode 100644 index 0000000..2b6837e --- /dev/null +++ b/src/apps/common/hiredis_libevent2.c @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include + +#if !defined(TURN_NO_HIREDIS) + +#include "hiredis_libevent2.h" +#include "ns_turn_utils.h" + +#include +#include + +#include +#include + +//////////////// Libevent context /////////////////////// + +struct redisLibeventEvents +{ + redisAsyncContext *context; + int invalid; + int allocated; + struct event_base *base; + struct event *rev, *wev; + int rev_set, wev_set; + char *ip; + int port; + char *pwd; + int db; +}; + +///////////// Messages //////////////////////////// + +struct redis_message +{ + char format[513]; + char arg[513]; +}; + +/////////////////// forward declarations /////////////// + +static void redis_reconnect(struct redisLibeventEvents *e); + +////////////////////////////////////////////////////////// + +static int redis_le_valid(struct redisLibeventEvents *e) +{ + return (e && !(e->invalid) && (e->context)); +} + +/////////////////// Callbacks //////////////////////////// + +static void redisLibeventReadEvent(int fd, short event, void *arg) { + ((void)fd); ((void)event); + struct redisLibeventEvents *e = (struct redisLibeventEvents*)arg; + if(redis_le_valid(e)) { + { + char buf[8]; + int len = 0; + do { + len = recv(fd,buf,sizeof(buf),MSG_PEEK); + } while((len<0)&&(errno == EINTR)); + if(len<1) { + e->invalid = 1; + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Redis connection broken: e=0x%lx\n", __FUNCTION__, (unsigned long)e); + } + } + if(redis_le_valid(e)) { + redisAsyncHandleRead(e->context); + } + } +} + +static void redisLibeventWriteEvent(int fd, short event, void *arg) { + ((void)fd); ((void)event); + struct redisLibeventEvents *e = (struct redisLibeventEvents*)arg; + if(redis_le_valid(e)) { + redisAsyncHandleWrite(e->context); + } +} + +static void redisLibeventAddRead(void *privdata) { + struct redisLibeventEvents *e = (struct redisLibeventEvents*)privdata; + if(e && (e->rev) && !(e->rev_set)) { + event_add(e->rev,NULL); + e->rev_set = 1; + } +} + +static void redisLibeventDelRead(void *privdata) { + struct redisLibeventEvents *e = (struct redisLibeventEvents*)privdata; + if(e && e->rev && e->rev_set) { + event_del(e->rev); + e->rev_set = 0; + } +} + +static void redisLibeventAddWrite(void *privdata) { + struct redisLibeventEvents *e = (struct redisLibeventEvents*)privdata; + if(e && (e->wev) && !(e->wev_set)) { + event_add(e->wev,NULL); + e->wev_set = 1; + } +} + +static void redisLibeventDelWrite(void *privdata) { + struct redisLibeventEvents *e = (struct redisLibeventEvents*)privdata; + if(e && e->wev && e->wev_set) { + event_del(e->wev); + e->wev_set = 0; + } +} + +static void redisLibeventCleanup(void *privdata) +{ + + if (privdata) { + + struct redisLibeventEvents *e = (struct redisLibeventEvents *) privdata; + if (e->allocated) { + if (e->rev) { + if(e->rev_set) + event_del(e->rev); + event_free(e->rev); + e->rev = NULL; + } + e->rev_set = 0; + if (e->wev) { + if(e->wev_set) + event_del(e->wev); + event_free(e->wev); + e->wev = NULL; + } + e->wev_set = 0; + e->context = NULL; + } + } +} + +///////////////////////// Send-receive /////////////////////////// + +void redis_async_init(void) +{ + ; +} + +int is_redis_asyncconn_good(redis_context_handle rch) +{ + if(rch) { + struct redisLibeventEvents *e = (struct redisLibeventEvents*)rch; + if(redis_le_valid(e)) + return 1; + } + return 0; +} + +void send_message_to_redis(redis_context_handle rch, const char *command, const char *key, const char *format,...) +{ + if(!rch) { + return; + } else { + + struct redisLibeventEvents *e = (struct redisLibeventEvents*)rch; + + if(!redis_le_valid(e)) { + redis_reconnect(e); + } + + if(!redis_le_valid(e)) { + ; + } else { + + redisAsyncContext *ac=e->context; + + struct redis_message rm; + + snprintf(rm.format,sizeof(rm.format)-3,"%s %s ", command, key); + strcpy(rm.format+strlen(rm.format),"%s"); + + va_list args; + va_start (args, format); + vsnprintf(rm.arg, sizeof(rm.arg)-1, format, args); + va_end (args); + + if((redisAsyncCommand(ac, NULL, e, rm.format, rm.arg)!=REDIS_OK)) { + e->invalid = 1; + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Redis connection broken: ac=0x%lx, e=0x%x\n", __FUNCTION__,(unsigned long)ac,(unsigned long)e); + } + } + } +} + +///////////////////////// Attach ///////////////////////////////// + +redis_context_handle redisLibeventAttach(struct event_base *base, char *ip0, int port0, char *pwd, int db) +{ + + struct redisLibeventEvents *e = NULL; + redisAsyncContext *ac = NULL; + + char ip[256]; + if(ip0 && ip0[0]) + STRCPY(ip,ip0); + else + STRCPY(ip,"127.0.0.1"); + + int port = DEFAULT_REDIS_PORT; + if(port0>0) + port=port0; + + ac = redisAsyncConnect(ip, port); + if (!ac) { + fprintf(stderr,"Error: %s:%s\n", ac->errstr, ac->c.errstr); + return NULL; + } + + /* Create container for context and r/w events */ + e = (struct redisLibeventEvents*)turn_malloc(sizeof(struct redisLibeventEvents)); + ns_bzero(e,sizeof(struct redisLibeventEvents)); + + e->allocated = 1; + e->context = ac; + e->base = base; + e->ip = strdup(ip); + e->port = port; + if(pwd) + e->pwd = strdup(pwd); + e->db = db; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisLibeventAddRead; + ac->ev.delRead = redisLibeventDelRead; + ac->ev.addWrite = redisLibeventAddWrite; + ac->ev.delWrite = redisLibeventDelWrite; + ac->ev.cleanup = redisLibeventCleanup; + + ac->ev.data = e; + + /* Initialize and install read/write events */ + e->rev = event_new(e->base,e->context->c.fd, + EV_READ,redisLibeventReadEvent, + e); + + e->wev = event_new(e->base,e->context->c.fd, + EV_WRITE,redisLibeventWriteEvent, + e); + + if (e->rev == NULL || e->wev == NULL) { + turn_free(e, sizeof(struct redisLibeventEvents)); + return NULL; + } + + event_add(e->wev, NULL); + e->wev_set = 1; + + //Authentication + if(redis_le_valid(e) && pwd) { + if(redisAsyncCommand(ac, NULL, e, "AUTH %s", pwd)!=REDIS_OK) { + e->invalid = 1; + } + } + + if(redis_le_valid(e)) { + if(redisAsyncCommand(ac, NULL, e, "SELECT %d", db)!=REDIS_OK) { + e->invalid = 1; + } + } + + return (redis_context_handle)e; +} + +static void redis_reconnect(struct redisLibeventEvents *e) +{ + if(!e || !(e->allocated)) + return; + + if (e->rev) { + if(e->rev_set) + event_del(e->rev); + event_free(e->rev); + e->rev = NULL; + } + e->rev_set = 0; + + if (e->wev) { + if(e->wev_set) + event_del(e->wev); + event_free(e->wev); + e->wev = NULL; + } + e->wev_set = 0; + + redisAsyncContext *ac = NULL; + + if(e->context) { + e->context = NULL; + } + + ac = redisAsyncConnect(e->ip, e->port); + if(!ac) { + return; + } + + e->context = ac; + + /* Register functions to start/stop listening for events */ + ac->ev.addRead = redisLibeventAddRead; + ac->ev.delRead = redisLibeventDelRead; + ac->ev.addWrite = redisLibeventAddWrite; + ac->ev.delWrite = redisLibeventDelWrite; + ac->ev.cleanup = redisLibeventCleanup; + + ac->ev.data = e; + + /* Initialize and install read/write events */ + e->rev = event_new(e->base,e->context->c.fd, + EV_READ,redisLibeventReadEvent, + e); + + e->wev = event_new(e->base,e->context->c.fd, + EV_WRITE,redisLibeventWriteEvent, + e); + + if (e->rev == NULL || e->wev == NULL) { + return; + } + + event_add(e->wev, NULL); + e->wev_set = 1; + e->invalid = 0; + + //Authentication + if(redis_le_valid(e) && e->pwd) { + if(redisAsyncCommand(ac, NULL, e, "AUTH %s", e->pwd)!=REDIS_OK) { + e->invalid = 1; + } + } + + if(redis_le_valid(e)) { + if(redisAsyncCommand(ac, NULL, e, "SELECT %d", e->db)!=REDIS_OK) { + e->invalid = 1; + } + } + + if(redis_le_valid(e)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Re-connected to redis, async\n", __FUNCTION__); + } +} + +///////////////////////////////////////////////////////// + +#endif +/* TURN_NO_HIREDIS */ + diff --git a/src/apps/common/hiredis_libevent2.h b/src/apps/common/hiredis_libevent2.h new file mode 100644 index 0000000..54d8864 --- /dev/null +++ b/src/apps/common/hiredis_libevent2.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __HIREDIS_LIBEVENT_H__ + +#define __HIREDIS_LIBEVENT_H__ + +#if !defined(TURN_NO_HIREDIS) + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////// + +#define DEFAULT_REDIS_PORT (6379) + +typedef void* redis_context_handle; + +////////////////////////////////////// + +void redis_async_init(void); + +redis_context_handle redisLibeventAttach(struct event_base *base, char *ip, int port, char *pwd, int db); + +void send_message_to_redis(redis_context_handle rch, const char *command, const char *key, const char *format,...); + +int is_redis_asyncconn_good(redis_context_handle rch); + +#ifdef __cplusplus +} +#endif + +#endif +/* TURN_NO_HIREDIS */ + +#endif +/*__HIREDIS_LIBEVENT_H__*/ diff --git a/src/apps/common/ns_turn_utils.c b/src/apps/common/ns_turn_utils.c new file mode 100644 index 0000000..652d200 --- /dev/null +++ b/src/apps/common/ns_turn_utils.c @@ -0,0 +1,643 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "ns_turn_utils.h" +#include "ns_turn_ioalib.h" +#include "ns_turn_msg_defs.h" + +#include + +#include + +#include +#include + +#include +#include + +////////// LOG TIME OPTIMIZATION /////////// + +static volatile turn_time_t log_start_time = 0; +volatile int _log_time_value_set = 0; +volatile turn_time_t _log_time_value = 0; + +static inline turn_time_t log_time(void) +{ + if(!log_start_time) + log_start_time = turn_time(); + + if(_log_time_value_set) + return (_log_time_value - log_start_time); + + return (turn_time() - log_start_time); +} + +////////// MUTEXES ///////////// + +#define MAGIC_CODE (0xEFCD1983) + +int turn_mutex_lock(const turn_mutex *mutex) { + if(mutex && mutex->mutex && (mutex->data == MAGIC_CODE)) { + int ret = 0; + ret = pthread_mutex_lock((pthread_mutex_t*)mutex->mutex); + if(ret<0) { + perror("Mutex lock"); + } + return ret; + } else { + printf("Uninitialized mutex\n"); + return -1; + } +} + +int turn_mutex_unlock(const turn_mutex *mutex) { + if(mutex && mutex->mutex && (mutex->data == MAGIC_CODE)) { + int ret = 0; + ret = pthread_mutex_unlock((pthread_mutex_t*)mutex->mutex); + if(ret<0) { + perror("Mutex unlock"); + } + return ret; + } else { + printf("Uninitialized mutex\n"); + return -1; + } +} + +int turn_mutex_init(turn_mutex* mutex) { + if(mutex) { + mutex->data=MAGIC_CODE; + mutex->mutex=turn_malloc(sizeof(pthread_mutex_t)); + pthread_mutex_init((pthread_mutex_t*)mutex->mutex,NULL); + return 0; + } else { + return -1; + } +} + +int turn_mutex_init_recursive(turn_mutex* mutex) { + int ret = -1; + if (mutex) { + pthread_mutexattr_t attr; + if (pthread_mutexattr_init(&attr) < 0) { + perror("Cannot init mutex attr"); + } else { + if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) < 0) { + perror("Cannot set type on mutex attr"); + } else { + mutex->mutex = turn_malloc(sizeof(pthread_mutex_t)); + mutex->data = MAGIC_CODE; + if ((ret = pthread_mutex_init((pthread_mutex_t*) mutex->mutex, + &attr)) < 0) { + perror("Cannot init mutex"); + mutex->data = 0; + turn_free(mutex->mutex,sizeof(pthread_mutex_t)); + mutex->mutex = NULL; + } + } + pthread_mutexattr_destroy(&attr); + } + } + return ret; +} + +int turn_mutex_destroy(turn_mutex* mutex) { + if(mutex && mutex->mutex && mutex->data == MAGIC_CODE) { + int ret = 0; + ret = pthread_mutex_destroy((pthread_mutex_t*)(mutex->mutex)); + turn_free(mutex->mutex, sizeof(pthread_mutex_t)); + mutex->mutex=NULL; + mutex->data=0; + return ret; + } else { + return 0; + } +} + +///////////////////////// LOG /////////////////////////////////// + +#if defined(TURN_LOG_FUNC_IMPL) +extern void TURN_LOG_FUNC_IMPL(TURN_LOG_LEVEL level, const s08bits* format, va_list args); +#endif + +static int no_stdout_log = 0; + +void set_no_stdout_log(int val) +{ + no_stdout_log = val; +} + +void turn_log_func_default(TURN_LOG_LEVEL level, const s08bits* format, ...) +{ +#if !defined(TURN_LOG_FUNC_IMPL) + { + va_list args; + va_start(args,format); + vrtpprintf(level, format, args); + va_end(args); + } +#endif + + { + va_list args; + va_start(args,format); +#if defined(TURN_LOG_FUNC_IMPL) + TURN_LOG_FUNC_IMPL(level,format,args); +#else +#define MAX_RTPPRINTF_BUFFER_SIZE (1024) + char s[MAX_RTPPRINTF_BUFFER_SIZE+1]; +#undef MAX_RTPPRINTF_BUFFER_SIZE + if (level == TURN_LOG_LEVEL_ERROR) { + snprintf(s,sizeof(s)-100,"%lu: ERROR: ",(unsigned long)log_time()); + size_t slen = strlen(s); + vsnprintf(s+slen,sizeof(s)-slen-1,format, args); + fwrite(s,strlen(s),1,stdout); + } else if(!no_stdout_log) { + snprintf(s,sizeof(s)-100,"%lu: ",(unsigned long)log_time()); + size_t slen = strlen(s); + vsnprintf(s+slen,sizeof(s)-slen-1,format, args); + fwrite(s,strlen(s),1,stdout); + } +#endif + va_end(args); + } +} + +void addr_debug_print(int verbose, const ioa_addr *addr, const s08bits* s) +{ + if (verbose) { + if (!addr) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: EMPTY\n", s); + } else { + s08bits addrbuf[INET6_ADDRSTRLEN]; + if (!s) + s = ""; + if (addr->ss.sa_family == AF_INET) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "IPv4. %s: %s:%d\n", s, inet_ntop(AF_INET, + &addr->s4.sin_addr, addrbuf, INET6_ADDRSTRLEN), + nswap16(addr->s4.sin_port)); + } else if (addr->ss.sa_family == AF_INET6) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "IPv6. %s: %s:%d\n", s, inet_ntop(AF_INET6, + &addr->s6.sin6_addr, addrbuf, INET6_ADDRSTRLEN), + nswap16(addr->s6.sin6_port)); + } else { + if (addr_any_no_port(addr)) { + TURN_LOG_FUNC( + TURN_LOG_LEVEL_INFO, + "IP. %s: 0.0.0.0:%d\n", + s, + nswap16(addr->s4.sin_port)); + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: wrong IP address family: %d\n", s, + (int) (addr->ss.sa_family)); + } + } + } + } +} + +/*************************************/ + +#define FILE_STR_LEN (1025) + +static FILE* _rtpfile = NULL; +static int to_syslog = 0; +static int simple_log = 0; +static char log_fn[FILE_STR_LEN]="\0"; +static char log_fn_base[FILE_STR_LEN]="\0"; + +static turn_mutex log_mutex; +static int log_mutex_inited = 0; + +static void log_lock(void) { + if(!log_mutex_inited) { + log_mutex_inited=1; + turn_mutex_init_recursive(&log_mutex); + } + turn_mutex_lock(&log_mutex); +} + +static void log_unlock(void) { + turn_mutex_unlock(&log_mutex); +} + +static void get_date(char *s, size_t sz) { + time_t curtm; + struct tm* tm_info; + + curtm = time(NULL); + tm_info = localtime(&curtm); + + strftime(s, sz, "%F", tm_info); +} + +void set_logfile(const char *fn) +{ + if(fn) { + log_lock(); + if(strcmp(fn,log_fn_base)) { + reset_rtpprintf(); + STRCPY(log_fn_base,fn); + } + log_unlock(); + } +} + +void reset_rtpprintf(void) +{ + log_lock(); + if(_rtpfile) { + if(_rtpfile != stdout) + fclose(_rtpfile); + _rtpfile = NULL; + } + log_unlock(); +} + +static void set_log_file_name(char *base, char *f) +{ + if(simple_log) { + STRCPY(f,base); + return; + } + + char logdate[125]; + char *tail=strdup(".log"); + + get_date(logdate,sizeof(logdate)); + + char *base1=strdup(base); + + int len=(int)strlen(base1); + + --len; + + while(len>=0) { + if((base1[len]==' ')||(base1[len]=='\t')) { + base1[len]='_'; + } + --len; + } + + len=(int)strlen(base1); + + while(len>=0) { + if(base1[len]=='/') + break; + else if(base1[len]=='.') { + turn_free(tail,strlen(tail)+1); + tail=strdup(base1+len); + base1[len]=0; + if(strlen(tail)<2) { + turn_free(tail,strlen(tail)+1); + tail = strdup(".log"); + } + break; + } + --len; + } + + len=(int)strlen(base1); + if(len>0 && (base1[len-1]!='/') && (base1[len-1]!='-') && (base1[len-1]!='_')) { + snprintf(f, FILE_STR_LEN, "%s_%s%s", base1,logdate,tail); + } else { + snprintf(f, FILE_STR_LEN, "%s%s%s", base1,logdate,tail); + } + + turn_free(base1,strlen(base1)+1); + turn_free(tail,strlen(tail)+1); +} + +static void set_rtpfile(void) +{ + if(to_syslog) { + return; + } else if (!_rtpfile) { + if(log_fn_base[0]) { + if(!strcmp(log_fn_base,"syslog")) { + _rtpfile = stdout; + to_syslog = 1; + } else if(!strcmp(log_fn_base,"stdout")|| !strcmp(log_fn_base,"-")) { + _rtpfile = stdout; + no_stdout_log = 1; + } else { + set_log_file_name(log_fn_base,log_fn); + _rtpfile = fopen(log_fn, "w"); + if(_rtpfile) + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "log file opened: %s\n", log_fn); + } + if (!_rtpfile) { + fprintf(stderr,"ERROR: Cannot open log file for writing: %s\n",log_fn); + } else { + return; + } + } + } + + if(!_rtpfile) { + + char logbase[FILE_STR_LEN]; + char logtail[FILE_STR_LEN]; + char logf[FILE_STR_LEN]; + + if(simple_log) + snprintf(logtail, FILE_STR_LEN, "turn.log"); + else + snprintf(logtail, FILE_STR_LEN, "turn_%d_", (int)getpid()); + + snprintf(logbase, FILE_STR_LEN, "/var/log/turnserver/%s", logtail); + + set_log_file_name(logbase, logf); + + _rtpfile = fopen(logf, "w"); + if(_rtpfile) + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "log file opened: %s\n", logf); + else { + snprintf(logbase, FILE_STR_LEN, "/var/log/%s", logtail); + + set_log_file_name(logbase, logf); + _rtpfile = fopen(logf, "w"); + if(_rtpfile) + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "log file opened: %s\n", logf); + else { + snprintf(logbase, FILE_STR_LEN, "/var/tmp/%s", logtail); + set_log_file_name(logbase, logf); + _rtpfile = fopen(logf, "w"); + if(_rtpfile) + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "log file opened: %s\n", logf); + else { + snprintf(logbase, FILE_STR_LEN, "/tmp/%s", logtail); + set_log_file_name(logbase, logf); + _rtpfile = fopen(logf, "w"); + if(_rtpfile) + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "log file opened: %s\n", logf); + else { + snprintf(logbase, FILE_STR_LEN, "%s", logtail); + set_log_file_name(logbase, logf); + _rtpfile = fopen(logf, "w"); + if(_rtpfile) + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "log file opened: %s\n", logf); + else { + _rtpfile = stdout; + return; + } + } + } + } + } + + STRCPY(log_fn_base,logbase); + STRCPY(log_fn,logf); + } +} + +void set_log_to_syslog(int val) +{ + to_syslog = val; +} + +void set_simple_log(int val) +{ + simple_log = val; +} + +#define Q(x) #x +#define QUOTE(x) Q(x) + +void rollover_logfile(void) +{ + if(to_syslog || !(log_fn[0])) + return; + + { + FILE *f = fopen(log_fn,"r"); + if(!f) { + fprintf(stderr, "log file is damaged\n"); + reset_rtpprintf(); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "log file reopened: %s\n", log_fn); + return; + } else { + fclose(f); + } + } + + if(simple_log) + return; + + log_lock(); + if(_rtpfile && log_fn[0] && (_rtpfile != stdout)) { + char logf[FILE_STR_LEN]; + + set_log_file_name(log_fn_base,logf); + if(strcmp(log_fn,logf)) { + fclose(_rtpfile); + log_fn[0]=0; + _rtpfile = fopen(logf, "w"); + if(_rtpfile) { + STRCPY(log_fn,logf); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "log file opened: %s\n", log_fn); + } else { + _rtpfile = stdout; + } + } + } + log_unlock(); +} + +static int get_syslog_level(TURN_LOG_LEVEL level) +{ + switch(level) { + case TURN_LOG_LEVEL_CONTROL: + return LOG_NOTICE; + case TURN_LOG_LEVEL_WARNING: + return LOG_WARNING; + case TURN_LOG_LEVEL_ERROR: + return LOG_ERR; + default: + ; + }; + return LOG_INFO; +} + +int vrtpprintf(TURN_LOG_LEVEL level, const char *format, va_list args) +{ + /* Fix for Issue 24, raised by John Selbie: */ +#define MAX_RTPPRINTF_BUFFER_SIZE (1024) + char s[MAX_RTPPRINTF_BUFFER_SIZE+1]; +#undef MAX_RTPPRINTF_BUFFER_SIZE + + size_t sz; + + snprintf(s, sizeof(s), "%lu: ",(unsigned long)log_time()); + sz=strlen(s); + vsnprintf(s+sz, sizeof(s)-1-sz, format, args); + s[sizeof(s)-1]=0; + + if(to_syslog) { + syslog(get_syslog_level(level),"%s",s); + } else { + log_lock(); + set_rtpfile(); + if(fprintf(_rtpfile,"%s",s)<0) { + reset_rtpprintf(); + } else if(fflush(_rtpfile)<0) { + reset_rtpprintf(); + } + log_unlock(); + } + + return 0; +} + +void rtpprintf(const char *format, ...) +{ + va_list args; + va_start (args, format); + vrtpprintf(TURN_LOG_LEVEL_INFO, format, args); + va_end (args); +} + +///////////// ORIGIN /////////////////// + +int get_default_protocol_port(const char* scheme, size_t slen) +{ + if(scheme && (slen>0)) { + switch(slen) { + case 3: + if(!memcmp("ftp",scheme,3)) + return 21; + if(!memcmp("svn",scheme,3)) + return 3690; + if(!memcmp("ssh",scheme,4)) + return 22; + if(!memcmp("sip",scheme,3)) + return 5060; + break; + case 4: + if(!memcmp("http",scheme,4)) + return 80; + if(!memcmp("ldap",scheme,4)) + return 389; + if(!memcmp("sips",scheme,4)) + return 5061; + if(!memcmp("turn",scheme,4)) + return 3478; + if(!memcmp("stun",scheme,4)) + return 3478; + break; + case 5: + if(!memcmp("https",scheme,5)) + return 443; + if(!memcmp("ldaps",scheme,5)) + return 636; + if(!memcmp("turns",scheme,5)) + return 5349; + if(!memcmp("stuns",scheme,5)) + return 5349; + break; + case 6: + if(!memcmp("telnet",scheme,6)) + return 23; + if(!memcmp("radius",scheme,6)) + return 1645; + break; + case 7: + if(!memcmp("svn+ssh",scheme,7)) + return 22; + break; + default: + return 0; + }; + } + return 0; +} + +int get_canonic_origin(const char* o, char *co, int sz) +{ + int ret = -1; + + if(o && o[0] && co) { + co[0]=0; + struct evhttp_uri *uri = evhttp_uri_parse(o); + if(uri) { + const char *scheme = evhttp_uri_get_scheme(uri); + if(scheme && scheme[0]) { + size_t schlen = strlen(scheme); + if((schlen<(size_t)sz) && (schlen0) + snprintf(otmp+schlen,sizeof(otmp)-schlen-1,"://%s:%d",host,port); + else + snprintf(otmp+schlen,sizeof(otmp)-schlen-1,"://%s",host); + + { + char *s = otmp + schlen + 3; + while(*s) { + *s = (char)tolower(*s); + ++s; + } + } + + strncpy(co,otmp,sz); + co[sz]=0; + ret = 0; + } + } + } + evhttp_uri_free(uri); + } + } + + if(ret<0) { + strncpy(co,o,sz); + co[sz]=0; + } + + return ret; +} + +////////////////////////////////////////////////////////////////// diff --git a/src/apps/common/ns_turn_utils.h b/src/apps/common/ns_turn_utils.h new file mode 100644 index 0000000..3b0a92e --- /dev/null +++ b/src/apps/common/ns_turn_utils.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __TURN_ULIB__ +#define __TURN_ULIB__ + +#if !defined(TURN_LOG_FUNC) +#define TURN_LOG_FUNC turn_log_func_default +#endif + +#include "ns_turn_ioaddr.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////// LOG ////////////////////////// + +typedef enum { + TURN_LOG_LEVEL_INFO = 0, + TURN_LOG_LEVEL_CONTROL, + TURN_LOG_LEVEL_WARNING, + TURN_LOG_LEVEL_ERROR +} TURN_LOG_LEVEL; + +#define TURN_VERBOSE_NONE (0) +#define TURN_VERBOSE_NORMAL (1) +#define TURN_VERBOSE_EXTRA (2) + +#define eve(v) ((v)==TURN_VERBOSE_EXTRA) + +void set_no_stdout_log(int val); +void set_log_to_syslog(int val); +void set_simple_log(int val); + +void turn_log_func_default(TURN_LOG_LEVEL level, const s08bits* format, ...); + +void addr_debug_print(int verbose, const ioa_addr *addr, const s08bits* s); + +/* Log */ + +extern volatile int _log_time_value_set; +extern volatile turn_time_t _log_time_value; + +void rtpprintf(const char *format, ...); +int vrtpprintf(TURN_LOG_LEVEL level, const char *format, va_list args); +void reset_rtpprintf(void); +void set_logfile(const char *fn); +void rollover_logfile(void); + +/////////////////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif //__TURN_ULIB__ diff --git a/src/apps/common/stun_buffer.c b/src/apps/common/stun_buffer.c new file mode 100644 index 0000000..eac2fc0 --- /dev/null +++ b/src/apps/common/stun_buffer.c @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "stun_buffer.h" + +////////////////////// BUFFERS /////////////////////////// + +int stun_init_buffer(stun_buffer *buf) { + if(!buf) return -1; + ns_bzero(buf->buf,sizeof(buf->buf)); + buf->len=0; + buf->offset=0; + buf->coffset=0; + return 0; +} + +int stun_get_size(const stun_buffer *buf) { + if(!buf) return 0; + return sizeof(buf->buf); +} + +//////////////////////////////////////////////////////////// + +void stun_tid_from_message(const stun_buffer *buf, stun_tid* id) { + stun_tid_from_message_str(buf->buf,(size_t)(buf->len), id); +} + +void stun_tid_generate_in_message(stun_buffer* buf, stun_tid* id) { + if(buf) { + stun_tid_generate_in_message_str(buf->buf, id); + } +} + +//////////////////////////////////////////////////////// + +static inline int is_channel_msg(const stun_buffer* buf) { + if(buf && buf->len>0) { + return is_channel_msg_str(buf->buf, (size_t)(buf->len)); + } + return 0; +} + +int stun_is_command_message(const stun_buffer* buf) { + if(!buf || buf->len<=0) + return 0; + else + return stun_is_command_message_str(buf->buf,(size_t)(buf->len)); +} + +int stun_is_request(const stun_buffer* buf) { + return stun_is_request_str(buf->buf,(size_t)buf->len); +} + +int stun_is_success_response(const stun_buffer* buf) { + return stun_is_success_response_str(buf->buf, (size_t)(buf->len)); +} + +int stun_is_error_response(const stun_buffer* buf, int *err_code, u08bits *err_msg, size_t err_msg_size) { + return stun_is_error_response_str(buf->buf, (size_t)(buf->len), err_code, err_msg, err_msg_size); +} + +int stun_is_response(const stun_buffer* buf) { + return stun_is_response_str(buf->buf,(size_t)(buf->len)); +} + +int stun_is_indication(const stun_buffer* buf) { + if(is_channel_msg(buf)) return 0; + return IS_STUN_INDICATION(stun_get_msg_type(buf)); +} + +u16bits stun_get_method(const stun_buffer* buf) { + return stun_get_method_str(buf->buf, (size_t)(buf->len)); +} + +u16bits stun_get_msg_type(const stun_buffer* buf) { + if(!buf) return (u16bits)-1; + return stun_get_msg_type_str(buf->buf,(size_t)buf->len); +} + +//////////////////////////////////////////////////////////// + +static void stun_init_command(u16bits message_type, stun_buffer* buf) { + buf->len=stun_get_size(buf); + stun_init_command_str(message_type, buf->buf, (size_t*)(&(buf->len))); +} + +void stun_init_request(u16bits method, stun_buffer* buf) { + stun_init_command(stun_make_request(method), buf); +} + +void stun_init_indication(u16bits method, stun_buffer* buf) { + stun_init_command(stun_make_indication(method), buf); +} + +void stun_init_success_response(u16bits method, stun_buffer* buf, stun_tid* id) { + buf->len=stun_get_size(buf); + stun_init_success_response_str(method, buf->buf, (size_t*)(&(buf->len)), id); +} + +void stun_init_error_response(u16bits method, stun_buffer* buf, u16bits error_code, const u08bits *reason, stun_tid* id) { + buf->len=stun_get_size(buf); + stun_init_error_response_str(method, buf->buf, (size_t*)(&(buf->len)), error_code, reason, id); +} + +/////////////////////////////////////////////////////////////////////////////// + +int stun_get_command_message_len(const stun_buffer* buf) { + return stun_get_command_message_len_str(buf->buf, (size_t)(buf->len)); +} + +/////////////////////////////////////////////////////////////////////////////// + +int stun_init_channel_message(u16bits chnumber, stun_buffer* buf, int length, int do_padding) { + return stun_init_channel_message_str(chnumber, buf->buf, (size_t*)(&(buf->len)), length, do_padding); +} + +int stun_is_channel_message(stun_buffer* buf, u16bits* chnumber, int is_padding_mandatory) { + if(!buf) return 0; + size_t blen = (size_t)buf->len; + int ret = stun_is_channel_message_str(buf->buf, &blen, chnumber, is_padding_mandatory); + if(ret) { + buf->len=(ssize_t)blen; + } + return ret; +} + +/////////////////////////////////////////////////////////////////////////////// + +int stun_set_allocate_request(stun_buffer* buf, u32bits lifetime, int address_family, u08bits transport, int mobile) { + return stun_set_allocate_request_str(buf->buf, (size_t*)(&(buf->len)), lifetime, address_family, transport, mobile); +} + +int stun_set_allocate_response(stun_buffer* buf, stun_tid* tid, + const ioa_addr *relayed_addr, const ioa_addr *reflexive_addr, + u32bits lifetime, int error_code, const u08bits *reason, + u64bits reservation_token, char *mobile_id) { + + return stun_set_allocate_response_str(buf->buf, (size_t*)(&(buf->len)), tid, + relayed_addr, reflexive_addr, + lifetime, error_code, reason, + reservation_token, mobile_id); + +} + +/////////////////////////////////////////////////////////////////////////////// + +u16bits stun_set_channel_bind_request(stun_buffer* buf, + const ioa_addr* peer_addr, u16bits channel_number) { + + return stun_set_channel_bind_request_str(buf->buf,(size_t*)(&(buf->len)), peer_addr, channel_number); +} + +void stun_set_channel_bind_response(stun_buffer* buf, stun_tid* tid, int error_code, const u08bits *reason) { + stun_set_channel_bind_response_str(buf->buf, (size_t*)(&(buf->len)), tid, error_code, reason); +} + +//////////////////////////////////////////////////////////////// + +stun_attr_ref stun_attr_get_first(const stun_buffer* buf) { + return stun_attr_get_first_str(buf->buf, (size_t)(buf->len)); +} + +stun_attr_ref stun_attr_get_next(const stun_buffer* buf, stun_attr_ref prev) { + return stun_attr_get_next_str(buf->buf, (size_t)(buf->len), prev); +} + +int stun_attr_add(stun_buffer* buf, u16bits attr, const s08bits* avalue, int alen) { + return stun_attr_add_str(buf->buf, (size_t*)(&(buf->len)), attr, (const u08bits *)avalue, alen); +} + +int stun_attr_add_channel_number(stun_buffer* buf, u16bits chnumber) { + return stun_attr_add_channel_number_str(buf->buf, (size_t *)(&(buf->len)), chnumber); +} + +int stun_attr_add_addr(stun_buffer *buf,u16bits attr_type, const ioa_addr* ca) { + return stun_attr_add_addr_str(buf->buf,(size_t*)(&(buf->len)), attr_type, ca); +} + +int stun_attr_get_addr(const stun_buffer *buf, stun_attr_ref attr, ioa_addr* ca, + const ioa_addr *default_addr) { + + return stun_attr_get_addr_str(buf->buf, (size_t)(buf->len), attr, ca, default_addr); +} + +int stun_attr_get_first_addr(const stun_buffer *buf, u16bits attr_type, ioa_addr* ca, + const ioa_addr *default_addr) { + + return stun_attr_get_first_addr_str(buf->buf, (size_t)(buf->len), attr_type, ca, default_addr); +} + +int stun_attr_add_even_port(stun_buffer* buf, uint8_t value) { + if(value) value=0x80; + return stun_attr_add(buf,STUN_ATTRIBUTE_EVEN_PORT,(const s08bits*)&value,1); +} + +u16bits stun_attr_get_first_channel_number(const stun_buffer *buf) { + return stun_attr_get_first_channel_number_str(buf->buf, (size_t)(buf->len)); +} + +stun_attr_ref stun_attr_get_first_by_type(const stun_buffer* buf, u16bits attr_type) { + return stun_attr_get_first_by_type_str(buf->buf, (size_t)(buf->len), attr_type); +} + +/////////////////////////////////////////////////////////////////////////////// + +void stun_set_binding_request(stun_buffer* buf) { + stun_set_binding_request_str(buf->buf, (size_t*)(&(buf->len))); +} + +int stun_set_binding_response(stun_buffer* buf, stun_tid* tid, + const ioa_addr *reflexive_addr, int error_code, const u08bits *reason) { + return stun_set_binding_response_str(buf->buf, (size_t*)(&(buf->len)), tid, + reflexive_addr, error_code, reason, + 0,0); +} + +void stun_prepare_binding_request(stun_buffer* buf) { + stun_set_binding_request_str(buf->buf, (size_t*)(&(buf->len))); +} + +int stun_is_binding_response(const stun_buffer* buf) { + return stun_is_binding_response_str(buf->buf, (size_t)(buf->len)); +} + +/////////////////////////////////////////////////////// diff --git a/src/apps/common/stun_buffer.h b/src/apps/common/stun_buffer.h new file mode 100644 index 0000000..7a4e0fe --- /dev/null +++ b/src/apps/common/stun_buffer.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __TURN_STUN_BUF__ +#define __TURN_STUN_BUF__ + +#include "ns_turn_msg.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/////////////////////////////////////////////////////////////// + +typedef struct _stun_buffer { + u08bits channel[STUN_CHANNEL_HEADER_LENGTH]; + u08bits buf[STUN_BUFFER_SIZE]; + size_t len; + u16bits offset; + u08bits coffset; +} stun_buffer; + +////////////////////////////////////////////////////////////// + +int stun_init_buffer(stun_buffer *buf); +int stun_get_size(const stun_buffer *buf); + +////////////////////////////////////////////////////////////// + +void stun_tid_generate_in_message(stun_buffer* buf, stun_tid* id); +void stun_tid_from_message(const stun_buffer *buf, stun_tid* id); + +/////////////////////////////////////////////////////////////// + +int stun_is_command_message(const stun_buffer* buf); +int stun_is_request(const stun_buffer* buf); +int stun_is_response(const stun_buffer* buf); +int stun_is_success_response(const stun_buffer* buf); +int stun_is_error_response(const stun_buffer* buf, int *err_code, u08bits *err_msg, size_t err_msg_size); +int stun_is_indication(const stun_buffer* buf); +u16bits stun_get_method(const stun_buffer* buf); +u16bits stun_get_msg_type(const stun_buffer* buf); + +/////////////////////////////////////////////////////////////// + +void stun_init_request(u16bits method, stun_buffer* buf); +void stun_init_indication(u16bits method, stun_buffer* buf); +void stun_init_success_response(u16bits method, stun_buffer* buf, stun_tid* id); +void stun_init_error_response(u16bits method, stun_buffer* buf, u16bits error_code, const u08bits *reason, stun_tid* id); + +/////////////////////////////////////////////////////////////// + +int stun_attr_add(stun_buffer* buf, u16bits attr, const s08bits* avalue, int alen); +int stun_attr_add_channel_number(stun_buffer* buf, u16bits chnumber); +int stun_attr_add_addr(stun_buffer *buf,u16bits attr_type, const ioa_addr* ca); + +stun_attr_ref stun_attr_get_first(const stun_buffer* buf); +stun_attr_ref stun_attr_get_first_by_type(const stun_buffer* buf, u16bits attr_type); +stun_attr_ref stun_attr_get_next(const stun_buffer* buf, stun_attr_ref prev); +int stun_attr_get_addr(const stun_buffer *buf, stun_attr_ref attr, ioa_addr* ca, const ioa_addr *default_addr); +int stun_attr_add_even_port(stun_buffer* buf, uint8_t value); + +int stun_attr_get_first_addr(const stun_buffer *buf, u16bits attr_type, ioa_addr* ca, const ioa_addr *default_addr); +u16bits stun_attr_get_first_channel_number(const stun_buffer *buf); + +/////////////////////////////////////////////////////////////// + +int stun_get_command_message_len(const stun_buffer* buf); + +/////////////////////////////////////////////////////////////// + +int stun_init_channel_message(u16bits chnumber, stun_buffer* buf, int length, int do_padding); +int stun_is_channel_message(stun_buffer* buf, u16bits* chnumber, int is_padding_madatory); + +/////////////////////////////////////////////////////////////// + +int stun_set_allocate_request(stun_buffer* buf, u32bits lifetime, int address_family, u08bits transport, int mobile); +int stun_set_allocate_response(stun_buffer* buf, stun_tid* tid, + const ioa_addr *relayed_addr, const ioa_addr *reflexive_addr, + u32bits lifetime, + int error_code, const u08bits *reason, + u64bits reservation_token, char *mobile_id); + +/////////////////////////////////////////////////////////////// + +void stun_set_binding_request(stun_buffer* buf); +int stun_set_binding_response(stun_buffer* buf, stun_tid* tid, + const ioa_addr *reflexive_addr, int error_code, const u08bits *reason); + +void stun_prepare_binding_request(stun_buffer* buf); +int stun_is_binding_response(const stun_buffer* buf); + +/////////////////////////////////////////////////////////////// + +u16bits stun_set_channel_bind_request(stun_buffer* buf, const ioa_addr* peer_addr, u16bits channel_number); +void stun_set_channel_bind_response(stun_buffer* buf, stun_tid* tid, int error_code, const u08bits *reason); + +/////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif //__TURN_STUN_BUF__ diff --git a/src/apps/peer/mainudpserver.c b/src/apps/peer/mainudpserver.c new file mode 100644 index 0000000..2c500d7 --- /dev/null +++ b/src/apps/peer/mainudpserver.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "ns_turn_utils.h" +#include "udpserver.h" +#include "apputils.h" + +#include +#include +#include +#include +#include + +//////////////// local definitions ///////////////// + +static char Usage[] = + "Usage: server [options]\n" + "Options:\n" + " -p Listening UDP port (Default: 3480)\n" + " -d Listening interface device (optional)\n" + " -L Listening address\n" + " -v verbose\n"; + + +////////////////////////////////////////////////// + +int main(int argc, char **argv) +{ + int port = PEER_DEFAULT_PORT; + char **local_addr_list=NULL; + size_t las = 0; + int verbose = TURN_VERBOSE_NONE; + int c; + char ifname[1025] = "\0"; + + IS_TURN_SERVER = 1; + + set_logfile("stdout"); + set_system_parameters(0); + + while ((c = getopt(argc, argv, "d:p:L:v")) != -1) + switch (c){ + case 'd': + STRCPY(ifname, optarg); + break; + case 'p': + port = atoi(optarg); + break; + case 'L': + local_addr_list = (char**)realloc(local_addr_list,++las*sizeof(char*)); + local_addr_list[las-1]=strdup(optarg); + break; + case 'v': + verbose = TURN_VERBOSE_NORMAL; + break; + default: + fprintf(stderr, "%s\n", Usage); + exit(1); + } + + if(las<1) { + local_addr_list = (char**)realloc(local_addr_list,++las*sizeof(char*)); + local_addr_list[las-1]=strdup("0.0.0.0"); + local_addr_list = (char**)realloc(local_addr_list,++las*sizeof(char*)); + local_addr_list[las-1]=strdup("::"); + } + + + server_type* server = start_udp_server(verbose, ifname, local_addr_list, las, port); + run_udp_server(server); + clean_udp_server(server); + + return 0; +} + diff --git a/src/apps/peer/udpserver.c b/src/apps/peer/udpserver.c new file mode 100644 index 0000000..9e6e3fd --- /dev/null +++ b/src/apps/peer/udpserver.c @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "apputils.h" +#include "udpserver.h" +#include "stun_buffer.h" + +/////////////// io handlers /////////////////// + +static void udp_server_input_handler(evutil_socket_t fd, short what, void* arg) { + + if(!(what&EV_READ)) return; + + ioa_addr *addr = (ioa_addr*)arg; + + int len = 0; + int slen = get_ioa_addr_len(addr); + stun_buffer buffer; + ioa_addr remote_addr; + + do { + len = recvfrom(fd, buffer.buf, sizeof(buffer.buf)-1, 0, (struct sockaddr*) &remote_addr, (socklen_t*) &slen); + } while(len<0 && (errno==EINTR)); + + buffer.len=len; + + if(len>=0) { + do { + len = sendto(fd, buffer.buf, buffer.len, 0, (const struct sockaddr*) &remote_addr, (socklen_t) slen); + } while (len < 0 && ((errno == EINTR) || (errno == ENOBUFS) || (errno == EAGAIN))); + } +} + +///////////////////// operations ////////////////////////// + +static int udp_create_server_socket(server_type* server, + const char* ifname, const char *local_address, int port) { + + FUNCSTART; + + if(!server) return -1; + + evutil_socket_t udp_fd = -1; + ioa_addr *server_addr = (ioa_addr*)turn_malloc(sizeof(ioa_addr)); + + STRCPY(server->ifname,ifname); + + if(make_ioa_addr((const u08bits*)local_address, port, server_addr)<0) return -1; + + udp_fd = socket(server_addr->ss.sa_family, SOCK_DGRAM, 0); + if (udp_fd < 0) { + perror("socket"); + return -1; + } + + if(sock_bind_to_device(udp_fd, (unsigned char*)server->ifname)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"Cannot bind udp server socket to device %s\n",server->ifname); + } + + set_sock_buf_size(udp_fd,UR_SERVER_SOCK_BUF_SIZE); + + if(addr_bind(udp_fd,server_addr,1)<0) return -1; + + socket_set_nonblocking(udp_fd); + + struct event *udp_ev = event_new(server->event_base,udp_fd,EV_READ|EV_PERSIST, + udp_server_input_handler,server_addr); + + event_add(udp_ev,NULL); + + FUNCEND; + + return 0; +} + +static server_type* init_server(int verbose, const char* ifname, char **local_addresses, size_t las, int port) { + + server_type* server=(server_type*)turn_malloc(sizeof(server_type)); + + if(!server) return server; + + ns_bzero(server,sizeof(server_type)); + + server->verbose=verbose; + + server->event_base = turn_event_base_new(); + + while(las) { + udp_create_server_socket(server, ifname, local_addresses[--las], port); + udp_create_server_socket(server, ifname, local_addresses[las], port+1); + } + + return server; +} + +static int clean_server(server_type* server) { + if(server) { + if(server->event_base) event_base_free(server->event_base); + turn_free(server,sizeof(server_type)); + } + return 0; +} + +/////////////////////////////////////////////////////////// + +static void run_events(server_type* server) { + + if(!server) return; + + struct timeval timeout; + + timeout.tv_sec=0; + timeout.tv_usec=100000; + + event_base_loopexit(server->event_base, &timeout); + event_base_dispatch(server->event_base); +} + +///////////////////////////////////////////////////////////// + + +server_type* start_udp_server(int verbose, const char* ifname, char **local_addresses, size_t las, int port) { + return init_server(verbose, ifname, local_addresses, las, port); +} + +void run_udp_server(server_type* server) { + + if(server) { + + unsigned int cycle=0; + + while (1) { + + cycle++; + + run_events(server); + } + } +} + +void clean_udp_server(server_type* server) { + if(server) clean_server(server); +} + +////////////////////////////////////////////////////////////////// diff --git a/src/apps/peer/udpserver.h b/src/apps/peer/udpserver.h new file mode 100644 index 0000000..bd482c4 --- /dev/null +++ b/src/apps/peer/udpserver.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __UDP_SERVER__ +#define __UDP_SERVER__ + +////////////////////////////// + +#include "ns_turn_utils.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////// + +struct server_info; +typedef struct server_info server_type; + +/////////////////////////////////////////////////// + +#define FUNCSTART if(server && server->verbose) turn_log_func_default(TURN_LOG_LEVEL_INFO,"%s:%d:start\n",__FUNCTION__,__LINE__) +#define FUNCEND if(server && server->verbose) turn_log_func_default(TURN_LOG_LEVEL_INFO,"%s:%d:end\n",__FUNCTION__,__LINE__) + +/////////////////////////////////////////////////////// + +struct server_info { + char ifname[1025]; + struct event_base* event_base; + int verbose; +}; + +////////////////////////////// + +server_type* start_udp_server(int verbose, const char* ifname, char **local_addresses, size_t las, int port); + +void run_udp_server(server_type* server); + +void clean_udp_server(server_type* server); + +/////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif //__UDP_SERVER__ diff --git a/src/apps/relay/dtls_listener.c b/src/apps/relay/dtls_listener.c new file mode 100644 index 0000000..7d38778 --- /dev/null +++ b/src/apps/relay/dtls_listener.c @@ -0,0 +1,954 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "apputils.h" +#include "mainrelay.h" + +#include "dtls_listener.h" +#include "ns_ioalib_impl.h" + +#include +#include +#include +#include + +#include + +/* #define REQUEST_CLIENT_CERT */ + +/////////////////////////////////////////////////// + +#define FUNCSTART if(server && eve(server->verbose)) TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"%s:%d:start\n",__FUNCTION__,__LINE__) +#define FUNCEND if(server && eve(server->verbose)) TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"%s:%d:end\n",__FUNCTION__,__LINE__) + +#define COOKIE_SECRET_LENGTH (32) + +#define MAX_SINGLE_UDP_BATCH (16) + +struct dtls_listener_relay_server_info { + char ifname[1025]; + ioa_addr addr; + ioa_engine_handle e; + turn_turnserver *ts; + int verbose; + SSL_CTX *dtls_ctx; + struct event *udp_listen_ev; + ioa_socket_handle udp_listen_s; + ur_addr_map *children_ss; /* map of socket children on remote addr */ + struct message_to_relay sm; + int slen0; + ioa_engine_new_connection_event_handler connect_cb; +}; + +///////////// forward declarations //////// + +static int create_server_socket(dtls_listener_relay_server_type* server, int report_creation); +static int clean_server(dtls_listener_relay_server_type* server); +static int reopen_server_socket(dtls_listener_relay_server_type* server, evutil_socket_t fd); + +///////////// dtls message types ////////// + +int is_dtls_handshake_message(const unsigned char* buf, int len); +int is_dtls_data_message(const unsigned char* buf, int len); +int is_dtls_alert_message(const unsigned char* buf, int len); +int is_dtls_cipher_change_message(const unsigned char* buf, int len); + +int is_dtls_message(const unsigned char* buf, int len); + +int is_dtls_handshake_message(const unsigned char* buf, int len) { + return (buf && len>3 && buf[0]==0x16 && buf[1]==0xfe && buf[2]==0xff); +} + +int is_dtls_data_message(const unsigned char* buf, int len) { + return (buf && len>3 && buf[0]==0x17 && buf[1]==0xfe && buf[2]==0xff); +} + +int is_dtls_alert_message(const unsigned char* buf, int len) { + return (buf && len>3 && buf[0]==0x15 && buf[1]==0xfe && buf[2]==0xff); +} + +int is_dtls_cipher_change_message(const unsigned char* buf, int len) { + return (buf && len>3 && buf[0]==0x14 && buf[1]==0xfe && buf[2]==0xff); +} + +int is_dtls_message(const unsigned char* buf, int len) { + if(buf && (len>3) && (buf[1])==0xfe && (buf[2]==0xff)) { + switch (buf[0]) { + case 0x14: + case 0x15: + case 0x16: + case 0x17: + return 1; + default: + ; + } + } + return 0; +} + +///////////// utils ///////////////////// + +#if !defined(TURN_NO_DTLS) + +static void calculate_cookie(SSL* ssl, unsigned char *cookie_secret, unsigned int cookie_length) { + long rv=(long)ssl; + long inum=(cookie_length-(((long)cookie_secret)%sizeof(long)))/sizeof(long); + long i=0; + long *ip=(long*)cookie_secret; + for(i=0;ifd, ssl, nbh, server->verbose); + + if (rc < 0) + return NULL; + + addr_debug_print(server->verbose, remote_addr, "Accepted connection from"); + + ioa_socket_handle ioas = create_ioa_socket_from_ssl(server->e, sock, ssl, DTLS_SOCKET, CLIENT_SOCKET, remote_addr, local_addr); + if(ioas) { + + ioas->listener_server = server; + + addr_cpy(&(server->sm.m.sm.nd.src_addr),remote_addr); + server->sm.m.sm.nd.recv_ttl = TTL_IGNORE; + server->sm.m.sm.nd.recv_tos = TOS_IGNORE; + server->sm.m.sm.s = ioas; + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot create ioa_socket from SSL\n"); + } + + FUNCEND ; + + return ioas; +} + +static ioa_socket_handle dtls_server_input_handler(dtls_listener_relay_server_type* server, + ioa_socket_handle s, + ioa_network_buffer_handle nbh) +{ + FUNCSTART; + + if (!server || !nbh) { + return NULL; + } + + SSL* connecting_ssl = NULL; + + BIO *wbio = NULL; + struct timeval timeout; + + /* Create BIO */ + wbio = BIO_new_dgram(s->fd, BIO_NOCLOSE); + (void)BIO_dgram_set_peer(wbio, (struct sockaddr*) &(server->sm.m.sm.nd.src_addr)); + + /* Set and activate timeouts */ + timeout.tv_sec = DTLS_MAX_RECV_TIMEOUT; + timeout.tv_usec = 0; + BIO_ctrl(wbio, BIO_CTRL_DGRAM_SET_RECV_TIMEOUT, 0, &timeout); + + connecting_ssl = SSL_new(server->dtls_ctx); + + SSL_set_accept_state(connecting_ssl); + + SSL_set_bio(connecting_ssl, NULL, wbio); + SSL_set_options(connecting_ssl, SSL_OP_COOKIE_EXCHANGE); + + SSL_set_max_cert_list(connecting_ssl, 655350); + + ioa_socket_handle rc = dtls_accept_client_connection(server, s, connecting_ssl, + &(server->sm.m.sm.nd.src_addr), + &(server->addr), + nbh); + + if (!rc) { + if (!(SSL_get_shutdown(connecting_ssl) & SSL_SENT_SHUTDOWN)) { + SSL_set_shutdown(connecting_ssl, SSL_RECEIVED_SHUTDOWN); + SSL_shutdown(connecting_ssl); + } + SSL_free(connecting_ssl); + } + + return rc; +} + +#endif + +static int handle_udp_packet(dtls_listener_relay_server_type *server, + struct message_to_relay *sm, + ioa_engine_handle ioa_eng, turn_turnserver *ts) +{ + int verbose = ioa_eng->verbose; + ioa_socket_handle s = sm->m.sm.s; + + ur_addr_map_value_type mvt = 0; + if(!(server->children_ss)) { + server->children_ss = (ur_addr_map*)allocate_super_memory_engine(server->e, sizeof(ur_addr_map)); + ur_addr_map_init(server->children_ss); + } + ur_addr_map *amap = server->children_ss; + + ioa_socket_handle chs = NULL; + if ((ur_addr_map_get(amap, &(sm->m.sm.nd.src_addr), &mvt) > 0) && mvt) { + chs = (ioa_socket_handle) mvt; + } + + if (chs && !ioa_socket_tobeclosed(chs) + && (chs->sockets_container == amap) + && (chs->magic == SOCKET_MAGIC)) { + s = chs; + sm->m.sm.s = s; + if(s->ssl) { + int sslret = ssl_read(s->fd, s->ssl, sm->m.sm.nd.nbh, verbose); + if(sslret < 0) { + ioa_network_buffer_delete(ioa_eng, sm->m.sm.nd.nbh); + sm->m.sm.nd.nbh = NULL; + ts_ur_super_session *ss = (ts_ur_super_session *) s->session; + if (ss) { + turn_turnserver *server = (turn_turnserver *) ss->server; + if (server) { + shutdown_client_connection(server, ss, 0, "SSL read error"); + } + } else { + close_ioa_socket(s); + } + ur_addr_map_del(amap, &(sm->m.sm.nd.src_addr), NULL); + sm->m.sm.s = NULL; + s = NULL; + chs = NULL; + } else if(ioa_network_buffer_get_size(sm->m.sm.nd.nbh)>0) { + ; + } else { + ioa_network_buffer_delete(ioa_eng, sm->m.sm.nd.nbh); + sm->m.sm.nd.nbh = NULL; + } + } + + if (s && s->read_cb && sm->m.sm.nd.nbh) { + s->e = ioa_eng; + s->read_cb(s, IOA_EV_READ, &(sm->m.sm.nd), s->read_ctx); + ioa_network_buffer_delete(ioa_eng, sm->m.sm.nd.nbh); + sm->m.sm.nd.nbh = NULL; + + if (ioa_socket_tobeclosed(s)) { + ts_ur_super_session *ss = (ts_ur_super_session *) s->session; + if (ss) { + turn_turnserver *server = (turn_turnserver *) ss->server; + if (server) { + shutdown_client_connection(server, ss, 0, "UDP packet processing error"); + } + } + } + } + } else { + if (chs && ioa_socket_tobeclosed(chs)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: socket to be closed\n", __FUNCTION__); + { + u08bits saddr[129]; + u08bits rsaddr[129]; + long thrid = (long) pthread_self(); + addr_to_string(get_local_addr_from_ioa_socket(chs),saddr); + addr_to_string(get_remote_addr_from_ioa_socket(chs),rsaddr); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "%s: 111.111: thrid=0x%lx: Amap = 0x%lx, socket container=0x%lx, local addr %s, remote addr %s, s=0x%lx, done=%d, tbc=%d\n", + __FUNCTION__, thrid, (long) amap, + (long) (chs->sockets_container), (char*) saddr, + (char*) rsaddr, (long) s, (int) (chs->done), + (int) (chs->tobeclosed)); + } + } + + if (chs && (chs->magic != SOCKET_MAGIC)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: wrong socket magic\n", __FUNCTION__); + } + + if (chs && (chs->sockets_container != amap)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: wrong socket container\n", __FUNCTION__); + { + u08bits saddr[129]; + u08bits rsaddr[129]; + long thrid = (long) pthread_self(); + addr_to_string(get_local_addr_from_ioa_socket(chs),saddr); + addr_to_string(get_remote_addr_from_ioa_socket(chs),rsaddr); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "%s: 111.222: thrid=0x%lx: Amap = 0x%lx, socket container=0x%lx, local addr %s, remote addr %s, s=0x%lx, done=%d, tbc=%d, st=%d, sat=%d\n", + __FUNCTION__, thrid, (long) amap, + (long) (chs->sockets_container), (char*) saddr, + (char*) rsaddr, (long) chs, (int) (chs->done), + (int) (chs->tobeclosed), (int) (chs->st), + (int) (chs->sat)); + } + } + + chs = NULL; + +#if !defined(TURN_NO_DTLS) + if (!turn_params.no_dtls && + is_dtls_handshake_message(ioa_network_buffer_data(sm->m.sm.nd.nbh), + (int)ioa_network_buffer_get_size(sm->m.sm.nd.nbh))) { + chs = dtls_server_input_handler(server,s, sm->m.sm.nd.nbh); + ioa_network_buffer_delete(server->e, sm->m.sm.nd.nbh); + sm->m.sm.nd.nbh = NULL; + } +#endif + + if(!chs) { + chs = create_ioa_socket_from_fd(ioa_eng, s->fd, s, + UDP_SOCKET, CLIENT_SOCKET, &(sm->m.sm.nd.src_addr), + get_local_addr_from_ioa_socket(s)); + } + + s = chs; + sm->m.sm.s = s; + + if (s) { + if(verbose) { + u08bits saddr[129]; + u08bits rsaddr[129]; + addr_to_string(get_local_addr_from_ioa_socket(s),saddr); + addr_to_string(get_remote_addr_from_ioa_socket(s),rsaddr); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "%s: New UDP endpoint: local addr %s, remote addr %s\n", + __FUNCTION__, (char*) saddr,(char*) rsaddr); + } + s->e = ioa_eng; + s->listener_server = server; + add_socket_to_map(s, amap); + open_client_connection_session(ts, &(sm->m.sm)); + } + } + + return 0; +} + +static int create_new_connected_udp_socket( + dtls_listener_relay_server_type* server, ioa_socket_handle s) +{ + + evutil_socket_t udp_fd = socket(s->local_addr.ss.sa_family, SOCK_DGRAM, 0); + if (udp_fd < 0) { + perror("socket"); + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: Cannot allocate new socket\n", + __FUNCTION__); + return -1; + } + + if (sock_bind_to_device(udp_fd, (unsigned char*) (s->e->relay_ifname)) + < 0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "Cannot bind udp server socket to device %s\n", + (char*) (s->e->relay_ifname)); + } + + ioa_socket_handle ret = (ioa_socket*) turn_malloc(sizeof(ioa_socket)); + if (!ret) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: Cannot allocate new socket structure\n", __FUNCTION__); + close(udp_fd); + return -1; + } + + ns_bzero(ret, sizeof(ioa_socket)); + + ret->magic = SOCKET_MAGIC; + + ret->fd = udp_fd; + + ret->family = s->family; + ret->st = s->st; + ret->sat = CLIENT_SOCKET; + ret->local_addr_known = 1; + addr_cpy(&(ret->local_addr), &(s->local_addr)); + + if (addr_bind(udp_fd,&(s->local_addr),1) < 0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "Cannot bind new detached udp server socket to local addr\n"); + IOA_CLOSE_SOCKET(ret); + return -1; + } + ret->bound = 1; + + { + int connect_err = 0; + if (addr_connect(udp_fd, &(server->sm.m.sm.nd.src_addr), &connect_err) < 0) { + char sl[129]; + char sr[129]; + addr_to_string(&(ret->local_addr),(u08bits*)sl); + addr_to_string(&(server->sm.m.sm.nd.src_addr),(u08bits*)sr); + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "Cannot connect new detached udp client socket from local addr %s to remote addr %s\n",sl,sr); + IOA_CLOSE_SOCKET(ret); + return -1; + } + } + ret->connected = 1; + addr_cpy(&(ret->remote_addr), &(server->sm.m.sm.nd.src_addr)); + + set_socket_options(ret); + + ret->current_ttl = s->current_ttl; + ret->default_ttl = s->default_ttl; + + ret->current_tos = s->current_tos; + ret->default_tos = s->default_tos; + +#if !defined(TURN_NO_DTLS) + if (!turn_params.no_dtls + && is_dtls_handshake_message( + ioa_network_buffer_data(server->sm.m.sm.nd.nbh), + (int) ioa_network_buffer_get_size( + server->sm.m.sm.nd.nbh))) { + + SSL* connecting_ssl = NULL; + + BIO *wbio = NULL; + struct timeval timeout; + + /* Create BIO */ + wbio = BIO_new_dgram(ret->fd, BIO_NOCLOSE); + (void) BIO_dgram_set_peer(wbio, (struct sockaddr*) &(server->sm.m.sm.nd.src_addr)); + + BIO_ctrl(wbio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &(server->sm.m.sm.nd.src_addr)); + + /* Set and activate timeouts */ + timeout.tv_sec = DTLS_MAX_RECV_TIMEOUT; + timeout.tv_usec = 0; + BIO_ctrl(wbio, BIO_CTRL_DGRAM_SET_RECV_TIMEOUT, 0, &timeout); + + connecting_ssl = SSL_new(server->dtls_ctx); + + SSL_set_accept_state(connecting_ssl); + + SSL_set_bio(connecting_ssl, NULL, wbio); + + SSL_set_options(connecting_ssl, SSL_OP_COOKIE_EXCHANGE); + SSL_set_max_cert_list(connecting_ssl, 655350); + int rc = ssl_read(ret->fd, connecting_ssl, server->sm.m.sm.nd.nbh, + server->verbose); + + if (rc < 0) { + if (!(SSL_get_shutdown(connecting_ssl) & SSL_SENT_SHUTDOWN)) { + SSL_set_shutdown(connecting_ssl, SSL_RECEIVED_SHUTDOWN); + SSL_shutdown(connecting_ssl); + } + SSL_free(connecting_ssl); + IOA_CLOSE_SOCKET(ret); + return -1; + } + + addr_debug_print(server->verbose, &(server->sm.m.sm.nd.src_addr), + "Accepted DTLS connection from"); + + ret->ssl = connecting_ssl; + ret->listener_server = server; + + ioa_network_buffer_delete(server->e, server->sm.m.sm.nd.nbh); + server->sm.m.sm.nd.nbh = NULL; + } +#endif + + server->sm.m.sm.s = ret; + return server->connect_cb(server->e, &(server->sm)); +} + +static void udp_server_input_handler(evutil_socket_t fd, short what, void* arg) +{ + int cycle = 0; + + ioa_socket_handle s = (ioa_socket_handle)arg; + + dtls_listener_relay_server_type* server = (dtls_listener_relay_server_type*)s->listener_server; + + FUNCSTART; + + if (!(what & EV_READ)) { + return; + } + + //printf_server_socket(server, fd); + + ioa_network_buffer_handle *elem = NULL; + + start_udp_cycle: + + elem = (ioa_network_buffer_handle *)ioa_network_buffer_allocate(server->e); + + server->sm.m.sm.nd.nbh = elem; + server->sm.m.sm.nd.recv_ttl = TTL_IGNORE; + server->sm.m.sm.nd.recv_tos = TOS_IGNORE; + + addr_set_any(&(server->sm.m.sm.nd.src_addr)); + + int slen = server->slen0; + ssize_t bsize = 0; + + int flags = 0; + + do { + bsize = recvfrom(fd, ioa_network_buffer_data(elem), ioa_network_buffer_get_capacity_udp(), flags, (struct sockaddr*) &(server->sm.m.sm.nd.src_addr), (socklen_t*) &slen); + } while (bsize < 0 && (errno == EINTR)); + + int conn_reset = is_connreset(); + int to_block = would_block(); + + if (bsize < 0) { + + if(to_block) { + ioa_network_buffer_delete(server->e, server->sm.m.sm.nd.nbh); + server->sm.m.sm.nd.nbh = NULL; + FUNCEND; + return; + } + + #if defined(MSG_ERRQUEUE) + + //Linux + int eflags = MSG_ERRQUEUE | MSG_DONTWAIT; + static s08bits buffer[65535]; + u32bits errcode = 0; + ioa_addr orig_addr; + int ttl = 0; + int tos = 0; + udp_recvfrom(fd, &orig_addr, &(server->addr), buffer, + (int) sizeof(buffer), &ttl, &tos, server->e->cmsg, eflags, + &errcode); + //try again... + do { + bsize = recvfrom(fd, ioa_network_buffer_data(elem), ioa_network_buffer_get_capacity_udp(), flags, (struct sockaddr*) &(server->sm.m.sm.nd.src_addr), (socklen_t*) &slen); + } while (bsize < 0 && (errno == EINTR)); + + conn_reset = is_connreset(); + to_block = would_block(); + + #endif + + if(conn_reset) { + ioa_network_buffer_delete(server->e, server->sm.m.sm.nd.nbh); + server->sm.m.sm.nd.nbh = NULL; + reopen_server_socket(server,fd); + FUNCEND; + return; + } + } + + if(bsize<0) { + if(!to_block && !conn_reset) { + int ern=errno; + perror(__FUNCTION__); + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: recvfrom error %d\n",__FUNCTION__,ern); + } + ioa_network_buffer_delete(server->e, server->sm.m.sm.nd.nbh); + server->sm.m.sm.nd.nbh = NULL; + FUNCEND; + return; + } + + if (bsize > 0) { + + int rc = 0; + ioa_network_buffer_set_size(elem, (size_t)bsize); + + if(server->connect_cb) { + + rc = create_new_connected_udp_socket(server, s); + if(rc<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot handle UDP packet, size %d\n",(int)bsize); + } + + } else { + server->sm.m.sm.s = s; + rc = handle_udp_packet(server, &(server->sm), server->e, server->ts); + } + + if(rc < 0) { + if(eve(server->e->verbose)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot handle UDP event\n"); + } + } + } + + ioa_network_buffer_delete(server->e, server->sm.m.sm.nd.nbh); + server->sm.m.sm.nd.nbh = NULL; + + if((bsize>0) && (cycle++addr.ss.sa_family, SOCK_DGRAM, 0); + if (udp_listen_fd < 0) { + perror("socket"); + return -1; + } + + server->udp_listen_s = create_ioa_socket_from_fd(server->e, udp_listen_fd, NULL, UDP_SOCKET, LISTENER_SOCKET, NULL, &(server->addr)); + + server->udp_listen_s->listener_server = server; + + set_sock_buf_size(udp_listen_fd,UR_SERVER_SOCK_BUF_SIZE); + + if(sock_bind_to_device(udp_listen_fd, (unsigned char*)server->ifname)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"Cannot bind listener socket to device %s\n",server->ifname); + } + + { + const int max_binding_time = 60; + int addr_bind_cycle = 0; + retry_addr_bind: + + if(addr_bind(udp_listen_fd,&server->addr,1)<0) { + perror("Cannot bind local socket to addr"); + char saddr[129]; + addr_to_string(&server->addr,(u08bits*)saddr); + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING,"Cannot bind UDP/DTLS listener socket to addr %s\n",saddr); + if(addr_bind_cycle++udp_listen_ev = event_new(server->e->event_base,udp_listen_fd, + EV_READ|EV_PERSIST,udp_server_input_handler, + server->udp_listen_s); + + event_add(server->udp_listen_ev,NULL); + } + + if(report_creation) { + if(!turn_params.no_udp && !turn_params.no_dtls) + addr_debug_print(server->verbose, &server->addr,"UDP/DTLS listener opened on"); + else if(!turn_params.no_dtls) + addr_debug_print(server->verbose, &server->addr,"DTLS listener opened on"); + else if(!turn_params.no_udp) + addr_debug_print(server->verbose, &server->addr,"UDP listener opened on"); + } + + FUNCEND; + + return 0; +} + +static int reopen_server_socket(dtls_listener_relay_server_type* server, evutil_socket_t fd) +{ + UNUSED_ARG(fd); + + if(!server) + return 0; + + FUNCSTART; + + { + EVENT_DEL(server->udp_listen_ev); + + if(server->udp_listen_s->fd>=0) { + socket_closesocket(server->udp_listen_s->fd); + server->udp_listen_s->fd = -1; + } + + if (!(server->udp_listen_s)) { + return create_server_socket(server,1); + } + + ioa_socket_raw udp_listen_fd = socket(server->addr.ss.sa_family, SOCK_DGRAM, 0); + if (udp_listen_fd < 0) { + perror("socket"); + FUNCEND; + return -1; + } + + server->udp_listen_s->fd = udp_listen_fd; + + /* some UDP sessions may fail due to the race condition here */ + + set_socket_options(server->udp_listen_s); + + set_sock_buf_size(udp_listen_fd, UR_SERVER_SOCK_BUF_SIZE); + + if (sock_bind_to_device(udp_listen_fd, (unsigned char*) server->ifname) < 0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "Cannot bind listener socket to device %s\n", + server->ifname); + } + + if(addr_bind(udp_listen_fd,&server->addr,1)<0) { + perror("Cannot bind local socket to addr"); + char saddr[129]; + addr_to_string(&server->addr,(u08bits*)saddr); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"Cannot bind listener socket to addr %s\n",saddr); + return -1; + } + + server->udp_listen_ev = event_new(server->e->event_base, udp_listen_fd, + EV_READ | EV_PERSIST, udp_server_input_handler, + server->udp_listen_s); + + event_add(server->udp_listen_ev, NULL ); + } + + if (!turn_params.no_udp && !turn_params.no_dtls) + addr_debug_print(server->verbose, &server->addr, + "UDP/DTLS listener opened on "); + else if (!turn_params.no_dtls) + addr_debug_print(server->verbose, &server->addr, + "DTLS listener opened on "); + else if (!turn_params.no_udp) + addr_debug_print(server->verbose, &server->addr, + "UDP listener opened on "); + + FUNCEND; + + return 0; +} + +#if defined(REQUEST_CLIENT_CERT) + +static int dtls_verify_callback (int ok, X509_STORE_CTX *ctx) { + /* This function should ask the user + * if he trusts the received certificate. + * Here we always trust. + */ + if(ok && ctx) return 1; + return -1; +} + +#endif + +static int init_server(dtls_listener_relay_server_type* server, + const char* ifname, + const char *local_address, + int port, + int verbose, + ioa_engine_handle e, + turn_turnserver *ts, + int report_creation, + ioa_engine_new_connection_event_handler send_socket) { + + if(!server) return -1; + + server->dtls_ctx = e->dtls_ctx; + server->ts = ts; + server->connect_cb = send_socket; + + if(ifname) STRCPY(server->ifname,ifname); + + if(make_ioa_addr((const u08bits*)local_address, port, &server->addr)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"Cannot create a UDP/DTLS listener for address: %s\n",local_address); + return -1; + } + + server->slen0 = get_ioa_addr_len(&(server->addr)); + + server->verbose=verbose; + + server->e = e; + + if(server->dtls_ctx) { + +#if defined(REQUEST_CLIENT_CERT) + /* If client has to authenticate, then */ + SSL_CTX_set_verify(server->dtls_ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, dtls_verify_callback); +#endif + + SSL_CTX_set_read_ahead(server->dtls_ctx, 1); + +#if !defined(TURN_NO_DTLS) + SSL_CTX_set_cookie_generate_cb(server->dtls_ctx, generate_cookie); + SSL_CTX_set_cookie_verify_cb(server->dtls_ctx, verify_cookie); +#endif + } + + return create_server_socket(server, report_creation); +} + +static int clean_server(dtls_listener_relay_server_type* server) { + if(server) { + EVENT_DEL(server->udp_listen_ev); + close_ioa_socket(server->udp_listen_s); + server->udp_listen_s = NULL; + } + return 0; +} + +/////////////////////////////////////////////////////////// + +dtls_listener_relay_server_type* create_dtls_listener_server(const char* ifname, + const char *local_address, + int port, + int verbose, + ioa_engine_handle e, + turn_turnserver *ts, + int report_creation, + ioa_engine_new_connection_event_handler send_socket) { + + dtls_listener_relay_server_type* server=(dtls_listener_relay_server_type*) + allocate_super_memory_engine(e,sizeof(dtls_listener_relay_server_type)); + + if(init_server(server, + ifname, local_address, port, + verbose, + e, ts, report_creation, send_socket)<0) { + return NULL; + } else { + return server; + } +} + +ioa_engine_handle get_engine(dtls_listener_relay_server_type* server) +{ + if(server) + return server->e; + return NULL; +} + +//////////// UDP send //////////////// + +void udp_send_message(dtls_listener_relay_server_type *server, ioa_network_buffer_handle nbh, ioa_addr *dest) +{ + if(server && dest && nbh && (server->udp_listen_s)) + udp_send(server->udp_listen_s, dest, (s08bits*)ioa_network_buffer_data(nbh), (int)ioa_network_buffer_get_size(nbh)); +} + +////////////////////////////////////////////////////////////////// diff --git a/src/apps/relay/dtls_listener.h b/src/apps/relay/dtls_listener.h new file mode 100644 index 0000000..9d7cab6 --- /dev/null +++ b/src/apps/relay/dtls_listener.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __DTLS_LISTENER__ +#define __DTLS_LISTENER__ + +#include "ns_turn_utils.h" + +#include "ns_ioalib_impl.h" + +#include "ns_turn_server.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/////////////////////////////////////////// + +struct dtls_listener_relay_server_info; +typedef struct dtls_listener_relay_server_info dtls_listener_relay_server_type; + +/////////////////////////////////////////// + +dtls_listener_relay_server_type* create_dtls_listener_server(const char* ifname, + const char *local_address, + int port, + int verbose, + ioa_engine_handle e, + turn_turnserver *ts, + int report_creation, + ioa_engine_new_connection_event_handler send_socket); + +void udp_send_message(dtls_listener_relay_server_type *server, ioa_network_buffer_handle nbh, ioa_addr *dest); + +ioa_engine_handle get_engine(dtls_listener_relay_server_type* server); + +/////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif //__DTLS_LISTENER__ diff --git a/src/apps/relay/libtelnet.c b/src/apps/relay/libtelnet.c new file mode 100644 index 0000000..694a3d7 --- /dev/null +++ b/src/apps/relay/libtelnet.c @@ -0,0 +1,1545 @@ +/* + * libtelnet - TELNET protocol handling library + * + * Sean Middleditch + * sean@sourcemud.org + * + * The author or authors of this code dedicate any and all copyright interest + * in this code to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and successors. We + * intend this dedication to be an overt act of relinquishment in perpetuity of + * all present and future rights to this code under copyright law. + */ + +/** + * Minor fixes by Oleg Moskalenko + */ + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include +#include + +/* Win32 compatibility */ +#if defined(_WIN32) +# define vsnprintf _vsnprintf +# define __func__ __FUNCTION__ +# define ZLIB_WINAPI 1 +#endif + +#if defined(HAVE_ZLIB) +# include +#endif + +#include "libtelnet.h" + +/* inlinable functions */ +#if defined(__GNUC__) || __STDC_VERSION__ >= 199901L +# define INLINE __inline__ +#else +# define INLINE +#endif + +/* helper for Q-method option tracking */ +#define Q_US(q) ((q).state & 0x0F) +#define Q_HIM(q) (((q).state & 0xF0) >> 4) +#define Q_MAKE(us,him) ((us) | ((him) << 4)) + +/* helper for the negotiation routines */ +#define NEGOTIATE_EVENT(telnet,cmd,opt) \ + ev.type = (cmd); \ + ev.neg.telopt = (opt); \ + (telnet)->eh((telnet), &ev, (telnet)->ud); + +/* telnet state codes */ +enum telnet_state_t { + TELNET_STATE_DATA = 0, + TELNET_STATE_IAC, + TELNET_STATE_WILL, + TELNET_STATE_WONT, + TELNET_STATE_DO, + TELNET_STATE_DONT, + TELNET_STATE_SB, + TELNET_STATE_SB_DATA, + TELNET_STATE_SB_DATA_IAC +}; +typedef enum telnet_state_t telnet_state_t; + +/* telnet state tracker */ +struct telnet_t { + /* user data */ + void *ud; + /* telopt support table */ + const telnet_telopt_t *telopts; + /* event handler */ + telnet_event_handler_t eh; +#if defined(HAVE_ZLIB) + /* zlib (mccp2) compression */ + z_stream *z; +#endif + /* RFC1143 option negotiation states */ + struct telnet_rfc1143_t *q; + /* sub-request buffer */ + char *buffer; + /* current size of the buffer */ + size_t buffer_size; + /* current buffer write position (also length of buffer data) */ + size_t buffer_pos; + /* current state */ + enum telnet_state_t state; + /* option flags */ + unsigned char flags; + /* current subnegotiation telopt */ + unsigned char sb_telopt; + /* length of RFC1143 queue */ + unsigned char q_size; +}; + +/* RFC1143 option negotiation state */ +typedef struct telnet_rfc1143_t { + unsigned char telopt; + unsigned char state; +} telnet_rfc1143_t; + +/* RFC1143 state names */ +#define Q_NO 0 +#define Q_YES 1 +#define Q_WANTNO 2 +#define Q_WANTYES 3 +#define Q_WANTNO_OP 4 +#define Q_WANTYES_OP 5 + +/* buffer sizes */ +static const size_t _buffer_sizes[] = { 0, 512, 2048, 8192, 16384, }; +static const size_t _buffer_sizes_count = sizeof(_buffer_sizes) / + sizeof(_buffer_sizes[0]); + +/* error generation function */ +static telnet_error_t _error(telnet_t *telnet, unsigned line, + const char* func, telnet_error_t err, int fatal, const char *fmt, + ...) { + telnet_event_t ev; + char buffer[512]; + va_list va; + + /* format informational text */ + va_start(va, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, va); + va_end(va); + + /* send error event to the user */ + ev.type = fatal ? TELNET_EV_ERROR : TELNET_EV_WARNING; + ev.error.file = __FILE__; + ev.error.func = func; + ev.error.line = line; + ev.error.msg = buffer; + telnet->eh(telnet, &ev, telnet->ud); + + return err; +} + +#if defined(HAVE_ZLIB) +/* initialize the zlib box for a telnet box; if deflate is non-zero, it + * initializes zlib for delating (compression), otherwise for inflating + * (decompression). returns TELNET_EOK on success, something else on + * failure. + */ +telnet_error_t _init_zlib(telnet_t *telnet, int deflate, int err_fatal) { + z_stream *z; + int rs; + + /* if compression is already enabled, fail loudly */ + if (telnet->z != 0) + return _error(telnet, __LINE__, __func__, TELNET_EBADVAL, + err_fatal, "cannot initialize compression twice"); + + /* allocate zstream box */ + if ((z= (z_stream *)calloc(1, sizeof(z_stream))) == 0) + return _error(telnet, __LINE__, __func__, TELNET_ENOMEM, err_fatal, + "malloc() failed: %s", strerror(errno)); + + /* initialize */ + if (deflate) { + if ((rs = deflateInit(z, Z_DEFAULT_COMPRESSION)) != Z_OK) { + free(z); + return _error(telnet, __LINE__, __func__, TELNET_ECOMPRESS, + err_fatal, "deflateInit() failed: %s", zError(rs)); + } + telnet->flags |= TELNET_PFLAG_DEFLATE; + } else { + if ((rs = inflateInit(z)) != Z_OK) { + free(z); + return _error(telnet, __LINE__, __func__, TELNET_ECOMPRESS, + err_fatal, "inflateInit() failed: %s", zError(rs)); + } + telnet->flags &= ~TELNET_PFLAG_DEFLATE; + } + + telnet->z = z; + + return TELNET_EOK; +} +#endif /* defined(HAVE_ZLIB) */ + +/* push bytes out, compressing them first if need be */ +static void _send(telnet_t *telnet, const char *buffer, + size_t size) { + telnet_event_t ev; + +#if defined(HAVE_ZLIB) + /* if we have a deflate (compression) zlib box, use it */ + if (telnet->z != 0 && telnet->flags & TELNET_PFLAG_DEFLATE) { + char deflate_buffer[1024]; + int rs; + + /* initialize z state */ + telnet->z->next_in = (unsigned char *)buffer; + telnet->z->avail_in = size; + telnet->z->next_out = (unsigned char *)deflate_buffer; + telnet->z->avail_out = sizeof(deflate_buffer); + + /* deflate until buffer exhausted and all output is produced */ + while (telnet->z->avail_in > 0 || telnet->z->avail_out == 0) { + /* compress */ + if ((rs = deflate(telnet->z, Z_SYNC_FLUSH)) != Z_OK) { + _error(telnet, __LINE__, __func__, TELNET_ECOMPRESS, 1, + "deflate() failed: %s", zError(rs)); + deflateEnd(telnet->z); + free(telnet->z); + telnet->z = 0; + break; + } + + /* send event */ + ev.type = TELNET_EV_SEND; + ev.data.buffer = deflate_buffer; + ev.data.size = sizeof(deflate_buffer) - telnet->z->avail_out; + telnet->eh(telnet, &ev, telnet->ud); + + /* prepare output buffer for next run */ + telnet->z->next_out = (unsigned char *)deflate_buffer; + telnet->z->avail_out = sizeof(deflate_buffer); + } + + /* do not continue with remaining code */ + return; + } +#endif /* defined(HAVE_ZLIB) */ + + ev.type = TELNET_EV_SEND; + ev.data.buffer = buffer; + ev.data.size = size; + telnet->eh(telnet, &ev, telnet->ud); +} + +/* to send bags of unsigned chars */ +#define _sendu(t, d, s) _send((t), (const char*)(d), (s)) + +/* check if we support a particular telopt; if us is non-zero, we + * check if we (local) supports it, otherwise we check if he (remote) + * supports it. return non-zero if supported, zero if not supported. + */ +static INLINE int _check_telopt(telnet_t *telnet, unsigned char telopt, + int us) { + int i; + + /* if we have no telopts table, we obviously don't support it */ + if (telnet->telopts == 0) + return 0; + + /* loop unti found or end marker (us and him both 0) */ + for (i = 0; telnet->telopts[i].telopt != -1; ++i) { + if (telnet->telopts[i].telopt == telopt) { + if (us && telnet->telopts[i].us == TELNET_WILL) + return 1; + else if (!us && telnet->telopts[i].him == TELNET_DO) + return 1; + else + return 0; + } + } + + /* not found, so not supported */ + return 0; +} + +/* retrieve RFC1143 option state */ +static INLINE telnet_rfc1143_t _get_rfc1143(telnet_t *telnet, + unsigned char telopt) { + telnet_rfc1143_t empty; + int i; + + /* search for entry */ + for (i = 0; i != telnet->q_size; ++i) { + if (telnet->q[i].telopt == telopt) { + return telnet->q[i]; + } + } + + /* not found, return empty value */ + empty.telopt = telopt; + empty.state = 0; + return empty; +} + +/* save RFC1143 option state */ +static INLINE void _set_rfc1143(telnet_t *telnet, unsigned char telopt, + char us, char him) { + telnet_rfc1143_t *qtmp; + int i; + + /* search for entry */ + for (i = 0; i != telnet->q_size; ++i) { + if (telnet->q[i].telopt == telopt) { + telnet->q[i].state = Q_MAKE(us,him); + return; + } + } + + /* we're going to need to track state for it, so grow the queue + * by 4 (four) elements and put the telopt into it; bail on allocation + * error. we go by four because it seems like a reasonable guess as + * to the number of enabled options for most simple code, and it + * allows for an acceptable number of reallocations for complex code. + */ + if ((qtmp = (telnet_rfc1143_t *)realloc(telnet->q, + sizeof(telnet_rfc1143_t) * (telnet->q_size + 4))) == 0) { + _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, + "realloc() failed: %s", strerror(errno)); + return; + } + memset(&qtmp[telnet->q_size], 0, sizeof(telnet_rfc1143_t) * 4); + telnet->q = qtmp; + telnet->q[telnet->q_size].telopt = telopt; + telnet->q[telnet->q_size].state = Q_MAKE(us, him); + telnet->q_size += 4; +} + +/* send negotiation bytes */ +static INLINE void _send_negotiate(telnet_t *telnet, unsigned char cmd, + unsigned char telopt) { + unsigned char bytes[3]; + bytes[0] = TELNET_IAC; + bytes[1] = cmd; + bytes[2] = telopt; + _sendu(telnet, bytes, 3); +} + +/* negotiation handling magic for RFC1143 */ +static void _negotiate(telnet_t *telnet, unsigned char telopt) { + telnet_event_t ev; + telnet_rfc1143_t q; + + /* in PROXY mode, just pass it thru and do nothing */ + if (telnet->flags & TELNET_FLAG_PROXY) { + switch ((int)telnet->state) { + case TELNET_STATE_WILL: + NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); + break; + case TELNET_STATE_WONT: + NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt); + break; + case TELNET_STATE_DO: + NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt); + break; + case TELNET_STATE_DONT: + NEGOTIATE_EVENT(telnet, TELNET_EV_DONT, telopt); + break; + } + return; + } + + /* lookup the current state of the option */ + q = _get_rfc1143(telnet, telopt); + + /* start processing... */ + switch ((int)telnet->state) { + /* request to enable option on remote end or confirm DO */ + case TELNET_STATE_WILL: + switch (Q_HIM(q)) { + case Q_NO: + if (_check_telopt(telnet, telopt, 0)) { + _set_rfc1143(telnet, telopt, Q_US(q), Q_YES); + _send_negotiate(telnet, TELNET_DO, telopt); + NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); + } else + _send_negotiate(telnet, TELNET_DONT, telopt); + break; + case Q_WANTNO: + _set_rfc1143(telnet, telopt, Q_US(q), Q_NO); + NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt); + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "DONT answered by WILL"); + break; + case Q_WANTNO_OP: + _set_rfc1143(telnet, telopt, Q_US(q), Q_YES); + NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "DONT answered by WILL"); + break; + case Q_WANTYES: + _set_rfc1143(telnet, telopt, Q_US(q), Q_YES); + NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); + break; + case Q_WANTYES_OP: + _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTNO); + _send_negotiate(telnet, TELNET_DONT, telopt); + NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); + break; + } + break; + + /* request to disable option on remote end, confirm DONT, reject DO */ + case TELNET_STATE_WONT: + switch (Q_HIM(q)) { + case Q_YES: + _set_rfc1143(telnet, telopt, Q_US(q), Q_NO); + _send_negotiate(telnet, TELNET_DONT, telopt); + NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt); + break; + case Q_WANTNO: + _set_rfc1143(telnet, telopt, Q_US(q), Q_NO); + NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt); + break; + case Q_WANTNO_OP: + _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTYES); + NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt); + break; + case Q_WANTYES: + case Q_WANTYES_OP: + _set_rfc1143(telnet, telopt, Q_US(q), Q_NO); + break; + } + break; + + /* request to enable option on local end or confirm WILL */ + case TELNET_STATE_DO: + switch (Q_US(q)) { + case Q_NO: + if (_check_telopt(telnet, telopt, 1)) { + _set_rfc1143(telnet, telopt, Q_YES, Q_HIM(q)); + _send_negotiate(telnet, TELNET_WILL, telopt); + NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt); + } else + _send_negotiate(telnet, TELNET_WONT, telopt); + break; + case Q_WANTNO: + _set_rfc1143(telnet, telopt, Q_NO, Q_HIM(q)); + NEGOTIATE_EVENT(telnet, TELNET_EV_DONT, telopt); + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "WONT answered by DO"); + break; + case Q_WANTNO_OP: + _set_rfc1143(telnet, telopt, Q_YES, Q_HIM(q)); + NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt); + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "WONT answered by DO"); + break; + case Q_WANTYES: + _set_rfc1143(telnet, telopt, Q_YES, Q_HIM(q)); + NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt); + break; + case Q_WANTYES_OP: + _set_rfc1143(telnet, telopt, Q_WANTNO, Q_HIM(q)); + _send_negotiate(telnet, TELNET_WONT, telopt); + NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt); + break; + } + break; + + /* request to disable option on local end, confirm WONT, reject WILL */ + case TELNET_STATE_DONT: + switch (Q_US(q)) { + case Q_YES: + _set_rfc1143(telnet, telopt, Q_NO, Q_HIM(q)); + _send_negotiate(telnet, TELNET_WONT, telopt); + NEGOTIATE_EVENT(telnet, TELNET_EV_DONT, telopt); + break; + case Q_WANTNO: + _set_rfc1143(telnet, telopt, Q_NO, Q_HIM(q)); + NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt); + break; + case Q_WANTNO_OP: + _set_rfc1143(telnet, telopt, Q_WANTYES, Q_HIM(q)); + _send_negotiate(telnet, TELNET_WILL, telopt); + NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); + break; + case Q_WANTYES: + case Q_WANTYES_OP: + _set_rfc1143(telnet, telopt, Q_NO, Q_HIM(q)); + break; + } + break; + } +} + +/* process an ENVIRON/NEW-ENVIRON subnegotiation buffer + * + * the algorithm and approach used here is kind of a hack, + * but it reduces the number of memory allocations we have + * to make. + * + * we copy the bytes back into the buffer, starting at the very + * beginning, which makes it easy to handle the ENVIRON ESC + * escape mechanism as well as ensure the variable name and + * value strings are NUL-terminated, all while fitting inside + * of the original buffer. + */ +static int _environ_telnet(telnet_t *telnet, unsigned char type, + char* buffer, size_t size) { + telnet_event_t ev; + struct telnet_environ_t *values = 0; + char *c, *last, *out; + size_t index, count; + + /* if we have no data, just pass it through */ + if (size == 0) { + return 0; + } + + /* first byte must be a valid command */ + if ((unsigned)buffer[0] != TELNET_ENVIRON_SEND && + (unsigned)buffer[0] != TELNET_ENVIRON_IS && + (unsigned)buffer[0] != TELNET_ENVIRON_INFO) { + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "telopt %d subneg has invalid command", type); + return 0; + } + + /* store ENVIRON command */ + ev.environ.cmd = buffer[0]; + + /* if we have no arguments, send an event with no data end return */ + if (size == 1) { + /* no list of variables given */ + ev.environ.values = 0; + ev.environ.size = 0; + + /* invoke event with our arguments */ + ev.type = TELNET_EV_ENVIRON; + telnet->eh(telnet, &ev, telnet->ud); + + return 1; + } + + /* very second byte must be VAR or USERVAR, if present */ + if ((unsigned)buffer[1] != TELNET_ENVIRON_VAR && + (unsigned)buffer[1] != TELNET_ENVIRON_USERVAR) { + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "telopt %d subneg missing variable type", type); + return 0; + } + + /* ensure last byte is not an escape byte (makes parsing later easier) */ + if ((unsigned)buffer[size - 1] == TELNET_ENVIRON_ESC) { + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "telopt %d subneg ends with ESC", type); + return 0; + } + + /* count arguments; each valid entry starts with VAR or USERVAR */ + count = 0; + for (c = buffer + 1; c < buffer + size; ++c) { + if (*c == TELNET_ENVIRON_VAR || *c == TELNET_ENVIRON_USERVAR) { + ++count; + } else if (*c == TELNET_ENVIRON_ESC) { + /* skip the next byte */ + ++c; + } + } + + /* allocate argument array, bail on error */ + if ((values = (struct telnet_environ_t *)calloc(count, + sizeof(struct telnet_environ_t))) == 0) { + _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, + "calloc() failed: %s", strerror(errno)); + return 0; + } + + /* parse argument array strings */ + out = buffer; + c = buffer + 1; + for (index = 0; index != count; ++index) { + /* remember the variable type (will be VAR or USERVAR) */ + values[index].type = *c++; + + /* scan until we find an end-marker, and buffer up unescaped + * bytes into our buffer */ + last = out; + while (c < buffer + size) { + /* stop at the next variable or at the value */ + if ((unsigned)*c == TELNET_ENVIRON_VAR || + (unsigned)*c == TELNET_ENVIRON_VALUE || + (unsigned)*c == TELNET_ENVIRON_USERVAR) { + break; + } + + /* buffer next byte (taking into account ESC) */ + if (*c == TELNET_ENVIRON_ESC) { + ++c; + } + + *out++ = *c++; + } + *out++ = '\0'; + + /* store the variable name we have just received */ + values[index].var = last; + values[index].value = ""; + + /* if we got a value, find the next end marker and + * store the value; otherwise, store empty string */ + if (c < buffer + size && *c == TELNET_ENVIRON_VALUE) { + ++c; + last = out; + while (c < buffer + size) { + /* stop when we find the start of the next variable */ + if ((unsigned)*c == TELNET_ENVIRON_VAR || + (unsigned)*c == TELNET_ENVIRON_USERVAR) { + break; + } + + /* buffer next byte (taking into account ESC) */ + if (*c == TELNET_ENVIRON_ESC) { + ++c; + } + + *out++ = *c++; + } + *out++ = '\0'; + + /* store the variable value */ + values[index].value = last; + } + } + + /* pass values array and count to event */ + ev.environ.values = values; + ev.environ.size = count; + + /* invoke event with our arguments */ + ev.type = TELNET_EV_ENVIRON; + telnet->eh(telnet, &ev, telnet->ud); + + /* clean up */ + free(values); + return 1; +} + +/* process an MSSP subnegotiation buffer */ +static int _mssp_telnet(telnet_t *telnet, char* buffer, size_t size) { + telnet_event_t ev; + struct telnet_environ_t *values; + char *var = 0; + char *c, *last, *out; + size_t i, count; + unsigned char next_type; + + /* if we have no data, just pass it through */ + if (size == 0) { + return 0; + } + + /* first byte must be a VAR */ + if ((unsigned)buffer[0] != TELNET_MSSP_VAR) { + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "MSSP subnegotiation has invalid data"); + return 0; + } + + /* count the arguments, any part that starts with VALUE */ + for (count = 0, i = 0; i != size; ++i) { + if ((unsigned)buffer[i] == TELNET_MSSP_VAL) { + ++count; + } + } + + /* allocate argument array, bail on error */ + if ((values = (struct telnet_environ_t *)calloc(count, + sizeof(struct telnet_environ_t))) == 0) { + _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, + "calloc() failed: %s", strerror(errno)); + return 0; + } + + ev.mssp.values = values; + ev.mssp.size = count; + + /* allocate strings in argument array */ + out = last = buffer; + next_type = buffer[0]; + for (i = 0, c = buffer + 1; c < buffer + size;) { + /* search for end marker */ + while (c < buffer + size && (unsigned)*c != TELNET_MSSP_VAR && + (unsigned)*c != TELNET_MSSP_VAL) { + *out++ = *c++; + } + *out++ = '\0'; + + /* if it's a variable name, just store the name for now */ + if (next_type == TELNET_MSSP_VAR) { + var = last; + } else if (next_type == TELNET_MSSP_VAL && var != 0) { + values[i].var = var; + values[i].value = last; + ++i; + } else { + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "invalid MSSP subnegotiation data"); + free(values); + return 0; + } + + /* remember our next type and increment c for next loop run */ + last = out; + next_type = *c++; + } + + /* invoke event with our arguments */ + ev.type = TELNET_EV_MSSP; + telnet->eh(telnet, &ev, telnet->ud); + + /* clean up */ + free(values); + + return 0; +} + +/* parse ZMP command subnegotiation buffers */ +static int _zmp_telnet(telnet_t *telnet, const char* buffer, size_t size) { + telnet_event_t ev; + const char **argv; + const char *c; + size_t i, argc; + + /* make sure this is a valid ZMP buffer */ + if (size == 0 || buffer[size - 1] != 0) { + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "incomplete ZMP frame"); + return 0; + } + + /* count arguments */ + for (argc = 0, c = buffer; c != buffer + size; ++argc) + c += strlen(c) + 1; + + /* allocate argument array, bail on error */ + if ((argv = (const char **)calloc(argc, sizeof(char *))) == 0) { + _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, + "calloc() failed: %s", strerror(errno)); + return 0; + } + + /* populate argument array */ + for (i = 0, c = buffer; i != argc; ++i) { + argv[i] = c; + c += strlen(c) + 1; + } + + /* invoke event with our arguments */ + ev.type = TELNET_EV_ZMP; + ev.zmp.argv = argv; + ev.zmp.argc = argc; + telnet->eh(telnet, &ev, telnet->ud); + + /* clean up */ + free(argv); + return 0; +} + +/* parse TERMINAL-TYPE command subnegotiation buffers */ +static int _ttype_telnet(telnet_t *telnet, const char* buffer, size_t size) { + telnet_event_t ev; + + /* make sure request is not empty */ + if (size == 0) { + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "incomplete TERMINAL-TYPE request"); + return 0; + } + + /* make sure request has valid command type */ + if (buffer[0] != TELNET_TTYPE_IS && + buffer[0] != TELNET_TTYPE_SEND) { + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "TERMINAL-TYPE request has invalid type"); + return 0; + } + + /* send proper event */ + if (buffer[0] == TELNET_TTYPE_IS) { + char *name; + + /* allocate space for name */ + if ((name = (char *)malloc(size)) == 0) { + _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, + "malloc() failed: %s", strerror(errno)); + return 0; + } + memcpy(name, buffer + 1, size - 1); + name[size - 1] = '\0'; + + ev.type = TELNET_EV_TTYPE; + ev.ttype.cmd = TELNET_TTYPE_IS; + ev.ttype.name = name; + telnet->eh(telnet, &ev, telnet->ud); + + /* clean up */ + free(name); + } else { + ev.type = TELNET_EV_TTYPE; + ev.ttype.cmd = TELNET_TTYPE_SEND; + ev.ttype.name = 0; + telnet->eh(telnet, &ev, telnet->ud); + } + + return 0; +} + +/* process a subnegotiation buffer; return non-zero if the current buffer + * must be aborted and reprocessed due to COMPRESS2 being activated + */ +static int _subnegotiate(telnet_t *telnet) { + telnet_event_t ev; + + /* standard subnegotiation event */ + ev.type = TELNET_EV_SUBNEGOTIATION; + ev.sub.telopt = telnet->sb_telopt; + ev.sub.buffer = telnet->buffer; + ev.sub.size = telnet->buffer_pos; + telnet->eh(telnet, &ev, telnet->ud); + + switch (telnet->sb_telopt) { +#if defined(HAVE_ZLIB) + /* received COMPRESS2 begin marker, setup our zlib box and + * start handling the compressed stream if it's not already. + */ + case TELNET_TELOPT_COMPRESS2: + if (telnet->sb_telopt == TELNET_TELOPT_COMPRESS2) { + if (_init_zlib(telnet, 0, 1) != TELNET_EOK) + return 0; + + /* notify app that compression was enabled */ + ev.type = TELNET_EV_COMPRESS; + ev.compress.state = 1; + telnet->eh(telnet, &ev, telnet->ud); + return 1; + } + return 0; +#endif /* defined(HAVE_ZLIB) */ + + /* specially handled subnegotiation telopt types */ + case TELNET_TELOPT_ZMP: + return _zmp_telnet(telnet, telnet->buffer, telnet->buffer_pos); + case TELNET_TELOPT_TTYPE: + return _ttype_telnet(telnet, telnet->buffer, telnet->buffer_pos); + case TELNET_TELOPT_ENVIRON: + case TELNET_TELOPT_NEW_ENVIRON: + return _environ_telnet(telnet, telnet->sb_telopt, telnet->buffer, + telnet->buffer_pos); + case TELNET_TELOPT_MSSP: + return _mssp_telnet(telnet, telnet->buffer, telnet->buffer_pos); + default: + return 0; + } +} + +/* initialize a telnet state tracker */ +telnet_t *telnet_init(const telnet_telopt_t *telopts, + telnet_event_handler_t eh, unsigned char flags, void *user_data) { + /* allocate structure */ + struct telnet_t *telnet = (telnet_t*)calloc(1, sizeof(telnet_t)); + if (telnet == 0) + return 0; + + /* initialize data */ + telnet->ud = user_data; + telnet->telopts = telopts; + telnet->eh = eh; + telnet->flags = flags; + + return telnet; +} + +/* free up any memory allocated by a state tracker */ +void telnet_free(telnet_t *telnet) { + /* free sub-request buffer */ + if (telnet->buffer != 0) { + free(telnet->buffer); + telnet->buffer = 0; + telnet->buffer_size = 0; + telnet->buffer_pos = 0; + } + +#if defined(HAVE_ZLIB) + /* free zlib box */ + if (telnet->z != 0) { + if (telnet->flags & TELNET_PFLAG_DEFLATE) + deflateEnd(telnet->z); + else + inflateEnd(telnet->z); + free(telnet->z); + telnet->z = 0; + } +#endif /* defined(HAVE_ZLIB) */ + + /* free RFC1143 queue */ + if (telnet->q) { + free(telnet->q); + telnet->q = 0; + telnet->q_size = 0; + } + + /* free the telnet structure itself */ + free(telnet); +} + +/* push a byte into the telnet buffer */ +static telnet_error_t _buffer_byte(telnet_t *telnet, + unsigned char byte) { + char *new_buffer; + size_t i; + + /* check if we're out of room */ + if (telnet->buffer_pos == telnet->buffer_size) { + /* find the next buffer size */ + for (i = 0; i != _buffer_sizes_count; ++i) { + if (_buffer_sizes[i] == telnet->buffer_size) { + break; + } + } + + /* overflow -- can't grow any more */ + if (i >= _buffer_sizes_count - 1) { + _error(telnet, __LINE__, __func__, TELNET_EOVERFLOW, 0, + "subnegotiation buffer size limit reached"); + return TELNET_EOVERFLOW; + } + + /* (re)allocate buffer */ + new_buffer = (char *)realloc(telnet->buffer, _buffer_sizes[i + 1]); + if (new_buffer == 0) { + _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, + "realloc() failed"); + return TELNET_ENOMEM; + } + + telnet->buffer = new_buffer; + telnet->buffer_size = _buffer_sizes[i + 1]; + } + + /* push the byte, all set */ + telnet->buffer[telnet->buffer_pos++] = byte; + return TELNET_EOK; +} + +static void _process(telnet_t *telnet, const char *buffer, size_t size) { + telnet_event_t ev; + unsigned char byte; + size_t i, start; + for (i = start = 0; i != size; ++i) { + byte = buffer[i]; + switch (telnet->state) { + /* regular data */ + case TELNET_STATE_DATA: + /* on an IAC byte, pass through all pending bytes and + * switch states */ + if (byte == TELNET_IAC) { + if (i != start) { + ev.type = TELNET_EV_DATA; + ev.data.buffer = buffer + start; + ev.data.size = i - start; + telnet->eh(telnet, &ev, telnet->ud); + } + telnet->state = TELNET_STATE_IAC; + } + break; + + /* IAC command */ + case TELNET_STATE_IAC: + switch (byte) { + /* subnegotiation */ + case TELNET_SB: + telnet->state = TELNET_STATE_SB; + break; + /* negotiation commands */ + case TELNET_WILL: + telnet->state = TELNET_STATE_WILL; + break; + case TELNET_WONT: + telnet->state = TELNET_STATE_WONT; + break; + case TELNET_DO: + telnet->state = TELNET_STATE_DO; + break; + case TELNET_DONT: + telnet->state = TELNET_STATE_DONT; + break; + /* IAC escaping */ + case TELNET_IAC: + /* event */ + ev.type = TELNET_EV_DATA; + ev.data.buffer = (char*)&byte; + ev.data.size = 1; + telnet->eh(telnet, &ev, telnet->ud); + + /* state update */ + start = i + 1; + telnet->state = TELNET_STATE_DATA; + break; + /* some other command */ + default: + /* event */ + ev.type = TELNET_EV_IAC; + ev.iac.cmd = byte; + telnet->eh(telnet, &ev, telnet->ud); + + /* state update */ + start = i + 1; + telnet->state = TELNET_STATE_DATA; + } + break; + + /* negotiation commands */ + case TELNET_STATE_WILL: + case TELNET_STATE_WONT: + case TELNET_STATE_DO: + case TELNET_STATE_DONT: + _negotiate(telnet, byte); + start = i + 1; + telnet->state = TELNET_STATE_DATA; + break; + + /* subnegotiation -- determine subnegotiation telopt */ + case TELNET_STATE_SB: + telnet->sb_telopt = byte; + telnet->buffer_pos = 0; + telnet->state = TELNET_STATE_SB_DATA; + break; + + /* subnegotiation -- buffer bytes until end request */ + case TELNET_STATE_SB_DATA: + /* IAC command in subnegotiation -- either IAC SE or IAC IAC */ + if (byte == TELNET_IAC) { + telnet->state = TELNET_STATE_SB_DATA_IAC; + /* buffer the byte, or bail if we can't */ + } else if (_buffer_byte(telnet, byte) != TELNET_EOK) { + start = i + 1; + telnet->state = TELNET_STATE_DATA; + } + break; + + /* IAC escaping inside a subnegotiation */ + case TELNET_STATE_SB_DATA_IAC: + switch (byte) { + /* end subnegotiation */ + case TELNET_SE: + /* return to default state */ + start = i + 1; + telnet->state = TELNET_STATE_DATA; + + /* process subnegotiation */ + if (_subnegotiate(telnet) != 0) { + /* any remaining bytes in the buffer are compressed. + * we have to re-invoke telnet_recv to get those + * bytes inflated and abort trying to process the + * remaining compressed bytes in the current _process + * buffer argument + */ + telnet_recv(telnet, &buffer[start], size - start); + return; + } + break; + /* escaped IAC byte */ + case TELNET_IAC: + /* push IAC into buffer */ + if (_buffer_byte(telnet, TELNET_IAC) != + TELNET_EOK) { + start = i + 1; + telnet->state = TELNET_STATE_DATA; + } else { + telnet->state = TELNET_STATE_SB_DATA; + } + break; + /* something else -- protocol error. attempt to process + * content in subnegotiation buffer, then evaluate the + * given command as an IAC code. + */ + default: + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "unexpected byte after IAC inside SB: %d", + byte); + + /* enter IAC state */ + start = i + 1; + telnet->state = TELNET_STATE_IAC; + + /* process subnegotiation; see comment in + * TELNET_STATE_SB_DATA_IAC about invoking telnet_recv() + */ + if (_subnegotiate(telnet) != 0) { + telnet_recv(telnet, &buffer[start], size - start); + return; + } else { + /* recursive call to get the current input byte processed + * as a regular IAC command. we could use a goto, but + * that would be gross. + */ + _process(telnet, (char *)&byte, 1); + } + break; + } + break; + } + } + + /* pass through any remaining bytes */ + if (telnet->state == TELNET_STATE_DATA && i != start) { + ev.type = TELNET_EV_DATA; + ev.data.buffer = buffer + start; + ev.data.size = i - start; + telnet->eh(telnet, &ev, telnet->ud); + } +} + +/* push a bytes into the state tracker */ +void telnet_recv(telnet_t *telnet, const char *buffer, + size_t size) { +#if defined(HAVE_ZLIB) + /* if we have an inflate (decompression) zlib stream, use it */ + if (telnet->z != 0 && !(telnet->flags & TELNET_PFLAG_DEFLATE)) { + char inflate_buffer[1024]; + int rs; + + /* initialize zlib state */ + telnet->z->next_in = (unsigned char*)buffer; + telnet->z->avail_in = size; + telnet->z->next_out = (unsigned char *)inflate_buffer; + telnet->z->avail_out = sizeof(inflate_buffer); + + /* inflate until buffer exhausted and all output is produced */ + while (telnet->z->avail_in > 0 || telnet->z->avail_out == 0) { + /* reset output buffer */ + + /* decompress */ + rs = inflate(telnet->z, Z_SYNC_FLUSH); + + /* process the decompressed bytes on success */ + if (rs == Z_OK || rs == Z_STREAM_END) + _process(telnet, inflate_buffer, sizeof(inflate_buffer) - + telnet->z->avail_out); + else + _error(telnet, __LINE__, __func__, TELNET_ECOMPRESS, 1, + "inflate() failed: %s", zError(rs)); + + /* prepare output buffer for next run */ + telnet->z->next_out = (unsigned char *)inflate_buffer; + telnet->z->avail_out = sizeof(inflate_buffer); + + /* on error (or on end of stream) disable further inflation */ + if (rs != Z_OK) { + telnet_event_t ev; + + /* disable compression */ + inflateEnd(telnet->z); + free(telnet->z); + telnet->z = 0; + + /* send event */ + ev.type = TELNET_EV_COMPRESS; + ev.compress.state = 0; + telnet->eh(telnet, &ev, telnet->ud); + + break; + } + } + + /* COMPRESS2 is not negotiated, just process */ + } else +#endif /* defined(HAVE_ZLIB) */ + _process(telnet, buffer, size); +} + +/* send an iac command */ +void telnet_iac(telnet_t *telnet, unsigned char cmd) { + unsigned char bytes[2]; + bytes[0] = TELNET_IAC; + bytes[1] = cmd; + _sendu(telnet, bytes, 2); +} + +/* send negotiation */ +void telnet_negotiate(telnet_t *telnet, unsigned char cmd, + unsigned char telopt) { + telnet_rfc1143_t q; + + /* if we're in proxy mode, just send it now */ + if (telnet->flags & TELNET_FLAG_PROXY) { + unsigned char bytes[3]; + bytes[0] = TELNET_IAC; + bytes[1] = cmd; + bytes[2] = telopt; + _sendu(telnet, bytes, 3); + return; + } + + /* get current option states */ + q = _get_rfc1143(telnet, telopt); + + switch (cmd) { + /* advertise willingess to support an option */ + case TELNET_WILL: + switch (Q_US(q)) { + case Q_NO: + _set_rfc1143(telnet, telopt, Q_WANTYES, Q_HIM(q)); + _send_negotiate(telnet, TELNET_WILL, telopt); + break; + case Q_WANTNO: + _set_rfc1143(telnet, telopt, Q_WANTNO_OP, Q_HIM(q)); + break; + case Q_WANTYES_OP: + _set_rfc1143(telnet, telopt, Q_WANTYES, Q_HIM(q)); + break; + } + break; + + /* force turn-off of locally enabled option */ + case TELNET_WONT: + switch (Q_US(q)) { + case Q_YES: + _set_rfc1143(telnet, telopt, Q_WANTNO, Q_HIM(q)); + _send_negotiate(telnet, TELNET_WONT, telopt); + break; + case Q_WANTYES: + _set_rfc1143(telnet, telopt, Q_WANTYES_OP, Q_HIM(q)); + break; + case Q_WANTNO_OP: + _set_rfc1143(telnet, telopt, Q_WANTNO, Q_HIM(q)); + break; + } + break; + + /* ask remote end to enable an option */ + case TELNET_DO: + switch (Q_HIM(q)) { + case Q_NO: + _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTYES); + _send_negotiate(telnet, TELNET_DO, telopt); + break; + case Q_WANTNO: + _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTNO_OP); + break; + case Q_WANTYES_OP: + _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTYES); + break; + } + break; + + /* demand remote end disable an option */ + case TELNET_DONT: + switch (Q_HIM(q)) { + case Q_YES: + _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTNO); + _send_negotiate(telnet, TELNET_DONT, telopt); + break; + case Q_WANTYES: + _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTYES_OP); + break; + case Q_WANTNO_OP: + _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTNO); + break; + } + break; + } +} + +/* send non-command data (escapes IAC bytes) */ +void telnet_send(telnet_t *telnet, const char *buffer, + size_t size) { + size_t i, l; + + for (l = i = 0; i != size; ++i) { + /* dump prior portion of text, send escaped bytes */ + if (buffer[i] == (char)TELNET_IAC) { + /* dump prior text if any */ + if (i != l) { + _send(telnet, buffer + l, i - l); + } + l = i + 1; + + /* send escape */ + telnet_iac(telnet, TELNET_IAC); + } + } + + /* send whatever portion of buffer is left */ + if (i != l) { + _send(telnet, buffer + l, i - l); + } +} + +/* send subnegotiation header */ +void telnet_begin_sb(telnet_t *telnet, unsigned char telopt) { + unsigned char sb[3]; + sb[0] = TELNET_IAC; + sb[1] = TELNET_SB; + sb[2] = telopt; + _sendu(telnet, sb, 3); +} + + +/* send complete subnegotiation */ +void telnet_subnegotiation(telnet_t *telnet, unsigned char telopt, + const char *buffer, size_t size) { + unsigned char bytes[5]; + bytes[0] = TELNET_IAC; + bytes[1] = TELNET_SB; + bytes[2] = telopt; + bytes[3] = TELNET_IAC; + bytes[4] = TELNET_SE; + + _sendu(telnet, bytes, 3); + telnet_send(telnet, buffer, size); + _sendu(telnet, bytes + 3, 2); + +#if defined(HAVE_ZLIB) + /* if we're a proxy and we just sent the COMPRESS2 marker, we must + * make sure all further data is compressed if not already. + */ + if (telnet->flags & TELNET_FLAG_PROXY && + telopt == TELNET_TELOPT_COMPRESS2) { + telnet_event_t ev; + + if (_init_zlib(telnet, 1, 1) != TELNET_EOK) + return; + + /* notify app that compression was enabled */ + ev.type = TELNET_EV_COMPRESS; + ev.compress.state = 1; + telnet->eh(telnet, &ev, telnet->ud); + } +#endif /* defined(HAVE_ZLIB) */ +} + +void telnet_begin_compress2(telnet_t *telnet) { + UNUSED_ARG(telnet); +#if defined(HAVE_ZLIB) + static const unsigned char compress2[] = { TELNET_IAC, TELNET_SB, + TELNET_TELOPT_COMPRESS2, TELNET_IAC, TELNET_SE }; + + telnet_event_t ev; + + /* attempt to create output stream first, bail if we can't */ + if (_init_zlib(telnet, 1, 0) != TELNET_EOK) + return; + + /* send compression marker. we send directly to the event handler + * instead of passing through _send because _send would result in + * the compress marker itself being compressed. + */ + ev.type = TELNET_EV_SEND; + ev.data.buffer = (const char*)compress2; + ev.data.size = sizeof(compress2); + telnet->eh(telnet, &ev, telnet->ud); + + /* notify app that compression was successfully enabled */ + ev.type = TELNET_EV_COMPRESS; + ev.compress.state = 1; + telnet->eh(telnet, &ev, telnet->ud); +#endif /* defined(HAVE_ZLIB) */ +} + +/* send formatted data with \r and \n translation in addition to IAC IAC */ +int telnet_vprintf(telnet_t *telnet, const char *fmt, va_list va) { + static const char CRLF[] = { '\r', '\n' }; + static const char CRNUL[] = { '\r', '\0' }; + char buffer[1024]; + char *output = buffer; + int rs, i, l; + + /* format */ + rs = vsnprintf(buffer, sizeof(buffer), fmt, va); + if ((size_t)rs >= sizeof(buffer)) { + output = (char*)malloc(rs + 1); + if (output == 0) { + _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, + "malloc() failed: %s", strerror(errno)); + return -1; + } + rs = vsnprintf(output, rs + 1, fmt, va); + } + + /* send */ + for (l = i = 0; i != rs; ++i) { + /* special characters */ + if (output[i] == (char)TELNET_IAC || output[i] == '\r' || + output[i] == '\n') { + /* dump prior portion of text */ + if (i != l) + _send(telnet, output + l, i - l); + l = i + 1; + + /* IAC -> IAC IAC */ + if (output[i] == (char)TELNET_IAC) + telnet_iac(telnet, TELNET_IAC); + /* automatic translation of \r -> CRNUL */ + else if (output[i] == '\r') + _send(telnet, CRNUL, 2); + /* automatic translation of \n -> CRLF */ + else if (output[i] == '\n') + _send(telnet, CRLF, 2); + } + } + + /* send whatever portion of output is left */ + if (i != l) { + _send(telnet, output + l, i - l); + } + + /* free allocated memory, if any */ + if (output != buffer) { + free(output); + } + + return rs; +} + +/* see telnet_vprintf */ +int telnet_printf(telnet_t *telnet, const char *fmt, ...) { + va_list va; + int rs; + + va_start(va, fmt); + rs = telnet_vprintf(telnet, fmt, va); + va_end(va); + + return rs; +} + +/* send formatted data through telnet_send */ +int telnet_raw_vprintf(telnet_t *telnet, const char *fmt, va_list va) { + char buffer[1024]; + char *output = buffer; + int rs; + + /* format; allocate more space if necessary */ + rs = vsnprintf(buffer, sizeof(buffer), fmt, va); + if ((size_t)rs >= sizeof(buffer)) { + output = (char*)malloc(rs + 1); + if (output == 0) { + _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, + "malloc() failed: %s", strerror(errno)); + return -1; + } + rs = vsnprintf(output, rs + 1, fmt, va); + } + + /* send out the formatted data */ + telnet_send(telnet, output, rs); + + /* release allocated memory, if any */ + if (output != buffer) { + free(output); + } + + return rs; +} + +/* see telnet_raw_vprintf */ +int telnet_raw_printf(telnet_t *telnet, const char *fmt, ...) { + va_list va; + int rs; + + va_start(va, fmt); + rs = telnet_raw_vprintf(telnet, fmt, va); + va_end(va); + + return rs; +} + +/* begin NEW-ENVIRON subnegotation */ +void telnet_begin_newenviron(telnet_t *telnet, unsigned char cmd) { + telnet_begin_sb(telnet, TELNET_TELOPT_NEW_ENVIRON); + telnet_send(telnet, (char*)&cmd, 1); +} + +/* send a NEW-ENVIRON value */ +void telnet_newenviron_value(telnet_t *telnet, unsigned char type, + const char *string) { + telnet_send(telnet, (char*)&type, 1); + + if (string != 0) { + telnet_send(telnet, string, strlen(string)); + } +} + +/* send TERMINAL-TYPE SEND command */ +void telnet_ttype_send(telnet_t *telnet) { + static const unsigned char SEND[] = { TELNET_IAC, TELNET_SB, + TELNET_TELOPT_TTYPE, TELNET_TTYPE_SEND, TELNET_IAC, TELNET_SE }; + _sendu(telnet, SEND, sizeof(SEND)); +} + +/* send TERMINAL-TYPE IS command */ +void telnet_ttype_is(telnet_t *telnet, const char* ttype) { + static const unsigned char IS[] = { TELNET_IAC, TELNET_SB, + TELNET_TELOPT_TTYPE, TELNET_TTYPE_IS }; + _sendu(telnet, IS, sizeof(IS)); + _send(telnet, ttype, strlen(ttype)); + telnet_finish_sb(telnet); +} + +/* send ZMP data */ +void telnet_send_zmp(telnet_t *telnet, size_t argc, const char **argv) { + size_t i; + + /* ZMP header */ + telnet_begin_zmp(telnet, argv[0]); + + /* send out each argument, including trailing NUL byte */ + for (i = 1; i != argc; ++i) + telnet_zmp_arg(telnet, argv[i]); + + /* ZMP footer */ + telnet_finish_zmp(telnet); +} + +/* send ZMP data using varargs */ +void telnet_send_vzmpv(telnet_t *telnet, va_list va) { + const char* arg; + + /* ZMP header */ + telnet_begin_sb(telnet, TELNET_TELOPT_ZMP); + + /* send out each argument, including trailing NUL byte */ + while ((arg = va_arg(va, const char *)) != 0) + telnet_zmp_arg(telnet, arg); + + /* ZMP footer */ + telnet_finish_zmp(telnet); +} + +/* see telnet_send_vzmpv */ +void telnet_send_zmpv(telnet_t *telnet, ...) { + va_list va; + + va_start(va, telnet); + telnet_send_vzmpv(telnet, va); + va_end(va); +} + +/* begin a ZMP command */ +void telnet_begin_zmp(telnet_t *telnet, const char *cmd) { + telnet_begin_sb(telnet, TELNET_TELOPT_ZMP); + telnet_zmp_arg(telnet, cmd); +} + +/* send a ZMP argument */ +void telnet_zmp_arg(telnet_t *telnet, const char* arg) { + telnet_send(telnet, arg, strlen(arg) + 1); +} diff --git a/src/apps/relay/libtelnet.h b/src/apps/relay/libtelnet.h new file mode 100644 index 0000000..b36cc87 --- /dev/null +++ b/src/apps/relay/libtelnet.h @@ -0,0 +1,677 @@ +/*! + * \brief libtelnet - TELNET protocol handling library + * + * SUMMARY: + * + * libtelnet is a library for handling the TELNET protocol. It includes + * routines for parsing incoming data from a remote peer as well as formatting + * data to send to the remote peer. + * + * libtelnet uses a callback-oriented API, allowing application-specific + * handling of various events. The callback system is also used for buffering + * outgoing protocol data, allowing the application to maintain control over + * the actual socket connection. + * + * Features supported include the full TELNET protocol, Q-method option + * negotiation, ZMP, MCCP2, MSSP, and NEW-ENVIRON. + * + * CONFORMS TO: + * + * RFC854 - http://www.faqs.org/rfcs/rfc854.html + * RFC855 - http://www.faqs.org/rfcs/rfc855.html + * RFC1091 - http://www.faqs.org/rfcs/rfc1091.html + * RFC1143 - http://www.faqs.org/rfcs/rfc1143.html + * RFC1408 - http://www.faqs.org/rfcs/rfc1408.html + * RFC1572 - http://www.faqs.org/rfcs/rfc1572.html + * + * LICENSE: + * + * The author or authors of this code dedicate any and all copyright interest + * in this code to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and successors. We + * intend this dedication to be an overt act of relinquishment in perpetuity of + * all present and future rights to this code under copyright law. + * + * \file libtelnet.h + * + * \version 0.21 + * + * \author Sean Middleditch + */ + +/** + * Minor fixes by Oleg Moskalenko + */ + +#if !defined(LIBTELNET_INCLUDE) +#define LIBTELNET_INCLUDE 1 + +/* standard C headers necessary for the libtelnet API */ +#include + +/* C++ support */ +#if defined(__cplusplus) +extern "C" { +#endif + +/* printf type checking feature in GCC and some other compilers */ +#if __GNUC__ +# define TELNET_GNU_PRINTF(f,a) __attribute__((format(printf, f, a))) /*!< internal helper */ +#else +# define TELNET_GNU_PRINTF(f,a) /*!< internal helper */ +#endif + +/*! Telnet state tracker object type. */ +typedef struct telnet_t telnet_t; + +/*! Telnet event object type. */ +typedef union telnet_event_t telnet_event_t; + +/*! Telnet option table element type. */ +typedef struct telnet_telopt_t telnet_telopt_t; + +/*! \name Telnet commands */ +/*@{*/ +/*! Telnet commands and special values. */ +#define TELNET_IAC 255 +#define TELNET_DONT 254 +#define TELNET_DO 253 +#define TELNET_WONT 252 +#define TELNET_WILL 251 +#define TELNET_SB 250 +#define TELNET_GA 249 +#define TELNET_EL 248 +#define TELNET_EC 247 +#define TELNET_AYT 246 +#define TELNET_AO 245 +#define TELNET_IP 244 +#define TELNET_BREAK 243 +#define TELNET_DM 242 +#define TELNET_NOP 241 +#define TELNET_SE 240 +#define TELNET_EOR 239 +#define TELNET_ABORT 238 +#define TELNET_SUSP 237 +#define TELNET_EOF 236 +/*@}*/ + +/*! \name Telnet option values. */ +/*@{*/ +/*! Telnet options. */ +#define TELNET_TELOPT_BINARY 0 +#define TELNET_TELOPT_ECHO 1 +#define TELNET_TELOPT_RCP 2 +#define TELNET_TELOPT_SGA 3 +#define TELNET_TELOPT_NAMS 4 +#define TELNET_TELOPT_STATUS 5 +#define TELNET_TELOPT_TM 6 +#define TELNET_TELOPT_RCTE 7 +#define TELNET_TELOPT_NAOL 8 +#define TELNET_TELOPT_NAOP 9 +#define TELNET_TELOPT_NAOCRD 10 +#define TELNET_TELOPT_NAOHTS 11 +#define TELNET_TELOPT_NAOHTD 12 +#define TELNET_TELOPT_NAOFFD 13 +#define TELNET_TELOPT_NAOVTS 14 +#define TELNET_TELOPT_NAOVTD 15 +#define TELNET_TELOPT_NAOLFD 16 +#define TELNET_TELOPT_XASCII 17 +#define TELNET_TELOPT_LOGOUT 18 +#define TELNET_TELOPT_BM 19 +#define TELNET_TELOPT_DET 20 +#define TELNET_TELOPT_SUPDUP 21 +#define TELNET_TELOPT_SUPDUPOUTPUT 22 +#define TELNET_TELOPT_SNDLOC 23 +#define TELNET_TELOPT_TTYPE 24 +#define TELNET_TELOPT_EOR 25 +#define TELNET_TELOPT_TUID 26 +#define TELNET_TELOPT_OUTMRK 27 +#define TELNET_TELOPT_TTYLOC 28 +#define TELNET_TELOPT_3270REGIME 29 +#define TELNET_TELOPT_X3PAD 30 +#define TELNET_TELOPT_NAWS 31 +#define TELNET_TELOPT_TSPEED 32 +#define TELNET_TELOPT_LFLOW 33 +#define TELNET_TELOPT_LINEMODE 34 +#define TELNET_TELOPT_XDISPLOC 35 +#define TELNET_TELOPT_ENVIRON 36 +#define TELNET_TELOPT_AUTHENTICATION 37 +#define TELNET_TELOPT_ENCRYPT 38 +#define TELNET_TELOPT_NEW_ENVIRON 39 +#define TELNET_TELOPT_MSSP 70 +#define TELNET_TELOPT_COMPRESS2 86 +#define TELNET_TELOPT_ZMP 93 +#define TELNET_TELOPT_EXOPL 255 + +#define TELNET_TELOPT_MCCP2 86 +/*@}*/ + +/*! \name Protocol codes for TERMINAL-TYPE commands. */ +/*@{*/ +/*! TERMINAL-TYPE codes. */ +#define TELNET_TTYPE_IS 0 +#define TELNET_TTYPE_SEND 1 +/*@}*/ + +/*! \name Protocol codes for NEW-ENVIRON/ENVIRON commands. */ +/*@{*/ +/*! NEW-ENVIRON/ENVIRON codes. */ +#define TELNET_ENVIRON_IS 0 +#define TELNET_ENVIRON_SEND 1 +#define TELNET_ENVIRON_INFO 2 +#define TELNET_ENVIRON_VAR 0 +#define TELNET_ENVIRON_VALUE 1 +#define TELNET_ENVIRON_ESC 2 +#define TELNET_ENVIRON_USERVAR 3 +/*@}*/ + +/*! \name Protocol codes for MSSP commands. */ +/*@{*/ +/*! MSSP codes. */ +#define TELNET_MSSP_VAR 1 +#define TELNET_MSSP_VAL 2 +/*@}*/ + +/*! \name Telnet state tracker flags. */ +/*@{*/ +/*! Control behavior of telnet state tracker. */ +#define TELNET_FLAG_PROXY (1<<0) + +#define TELNET_PFLAG_DEFLATE (1<<7) +/*@}*/ + +#if !defined(UNUSED_ARG) +#define UNUSED_ARG(A) do { A=A; } while(0) +#endif + +/*! + * error codes + */ +enum telnet_error_t { + TELNET_EOK = 0, /*!< no error */ + TELNET_EBADVAL, /*!< invalid parameter, or API misuse */ + TELNET_ENOMEM, /*!< memory allocation failure */ + TELNET_EOVERFLOW, /*!< data exceeds buffer size */ + TELNET_EPROTOCOL, /*!< invalid sequence of special bytes */ + TELNET_ECOMPRESS /*!< error handling compressed streams */ +}; +typedef enum telnet_error_t telnet_error_t; /*!< Error code type. */ + +/*! + * event codes + */ +enum telnet_event_type_t { + TELNET_EV_DATA = 0, /*!< raw text data has been received */ + TELNET_EV_SEND, /*!< data needs to be sent to the peer */ + TELNET_EV_IAC, /*!< generic IAC code received */ + TELNET_EV_WILL, /*!< WILL option negotiation received */ + TELNET_EV_WONT, /*!< WONT option neogitation received */ + TELNET_EV_DO, /*!< DO option negotiation received */ + TELNET_EV_DONT, /*!< DONT option negotiation received */ + TELNET_EV_SUBNEGOTIATION, /*!< sub-negotiation data received */ + TELNET_EV_COMPRESS, /*!< compression has been enabled */ + TELNET_EV_ZMP, /*!< ZMP command has been received */ + TELNET_EV_TTYPE, /*!< TTYPE command has been received */ + TELNET_EV_ENVIRON, /*!< ENVIRON command has been received */ + TELNET_EV_MSSP, /*!< MSSP command has been received */ + TELNET_EV_WARNING, /*!< recoverable error has occured */ + TELNET_EV_ERROR /*!< non-recoverable error has occured */ +}; +typedef enum telnet_event_type_t telnet_event_type_t; /*!< Telnet event type. */ + +/*! + * environ/MSSP command information + */ +struct telnet_environ_t { + unsigned char type; /*!< either TELNET_ENVIRON_VAR or TELNET_ENVIRON_USERVAR */ + const char *var; /*!< name of the variable being set */ + const char *value; /*!< value of variable being set; empty string if no value */ +}; + +/*! + * event information + */ +union telnet_event_t { + /*! + * \brief Event type + * + * The type field will determine which of the other event structure fields + * have been filled in. For instance, if the event type is TELNET_EV_ZMP, + * then the zmp event field (and ONLY the zmp event field) will be filled + * in. + */ + enum telnet_event_type_t type; + + /*! + * data event: for DATA and SEND events + */ + struct data_t { + enum telnet_event_type_t _type; /*!< alias for type */ + const char *buffer; /*!< byte buffer */ + size_t size; /*!< number of bytes in buffer */ + } data; + + /*! + * WARNING and ERROR events + */ + struct error_t { + enum telnet_event_type_t _type; /*!< alias for type */ + const char *file; /*!< file the error occured in */ + const char *func; /*!< function the error occured in */ + const char *msg; /*!< error message string */ + int line; /*!< line of file error occured on */ + telnet_error_t errcode; /*!< error code */ + } error; + + /*! + * command event: for IAC + */ + struct iac_t { + enum telnet_event_type_t _type; /*!< alias for type */ + unsigned char cmd; /*!< telnet command received */ + } iac; + + /*! + * negotiation event: WILL, WONT, DO, DONT + */ + struct negotiate_t { + enum telnet_event_type_t _type; /*!< alias for type */ + unsigned char telopt; /*!< option being negotiated */ + } neg; + + /*! + * subnegotiation event + */ + struct subnegotiate_t { + enum telnet_event_type_t _type; /*!< alias for type */ + const char *buffer; /*!< data of sub-negotiation */ + size_t size; /*!< number of bytes in buffer */ + unsigned char telopt; /*!< option code for negotiation */ + } sub; + + /*! + * ZMP event + */ + struct zmp_t { + enum telnet_event_type_t _type; /*!< alias for type */ + const char **argv; /*!< array of argument string */ + size_t argc; /*!< number of elements in argv */ + } zmp; + + /*! + * TTYPE event + */ + struct ttype_t { + enum telnet_event_type_t _type; /*!< alias for type */ + unsigned char cmd; /*!< TELNET_TTYPE_IS or TELNET_TTYPE_SEND */ + const char* name; /*!< terminal type name (IS only) */ + } ttype; + + /*! + * COMPRESS event + */ + struct compress_t { + enum telnet_event_type_t _type; /*!< alias for type */ + unsigned char state; /*!< 1 if compression is enabled, + 0 if disabled */ + } compress; + + /*! + * ENVIRON/NEW-ENVIRON event + */ + struct environ_t { + enum telnet_event_type_t _type; /*!< alias for type */ + const struct telnet_environ_t *values; /*!< array of variable values */ + size_t size; /*!< number of elements in values */ + unsigned char cmd; /*!< SEND, IS, or INFO */ + } environ; + + /*! + * MSSP event + */ + struct mssp_t { + enum telnet_event_type_t _type; /*!< alias for type */ + const struct telnet_environ_t *values; /*!< array of variable values */ + size_t size; /*!< number of elements in values */ + } mssp; +}; + +/*! + * \brief event handler + * + * This is the type of function that must be passed to + * telnet_init() when creating a new telnet object. The + * function will be invoked once for every event generated + * by the libtelnet protocol parser. + * + * \param telnet The telnet object that generated the event + * \param event Event structure with details about the event + * \param user_data User-supplied pointer + */ +typedef void (*telnet_event_handler_t)(telnet_t *telnet, + telnet_event_t *event, void *user_data); + +/*! + * telopt support table element; use telopt of -1 for end marker + */ +struct telnet_telopt_t { + short telopt; /*!< one of the TELOPT codes or -1 */ + unsigned char us; /*!< TELNET_WILL or TELNET_WONT */ + unsigned char him; /*!< TELNET_DO or TELNET_DONT */ +}; + +/*! + * state tracker -- private data structure + */ +struct telnet_t; + +/*! + * \brief Initialize a telnet state tracker. + * + * This function initializes a new state tracker, which is used for all + * other libtelnet functions. Each connection must have its own + * telnet state tracker object. + * + * \param telopts Table of TELNET options the application supports. + * \param eh Event handler function called for every event. + * \param flags 0 or TELNET_FLAG_PROXY. + * \param user_data Optional data pointer that will be passsed to eh. + * \return Telent state tracker object. + */ +extern telnet_t* telnet_init(const telnet_telopt_t *telopts, + telnet_event_handler_t eh, unsigned char flags, void *user_data); + +/*! + * \brief Free up any memory allocated by a state tracker. + * + * This function must be called when a telnet state tracker is no + * longer needed (such as after the connection has been closed) to + * release any memory resources used by the state tracker. + * + * \param telnet Telnet state tracker object. + */ +extern void telnet_free(telnet_t *telnet); + +/*! + * \brief Push a byte buffer into the state tracker. + * + * Passes one or more bytes to the telnet state tracker for + * protocol parsing. The byte buffer is most often going to be + * the buffer that recv() was called for while handling the + * connection. + * + * \param telnet Telnet state tracker object. + * \param buffer Pointer to byte buffer. + * \param size Number of bytes pointed to by buffer. + */ +extern void telnet_recv(telnet_t *telnet, const char *buffer, + size_t size); + +/*! + * \brief Send a telnet command. + * + * \param telnet Telnet state tracker object. + * \param cmd Command to send. + */ +extern void telnet_iac(telnet_t *telnet, unsigned char cmd); + +/*! + * \brief Send negotiation command. + * + * Internally, libtelnet uses RFC1143 option negotiation rules. + * The negotiation commands sent with this function may be ignored + * if they are determined to be redundant. + * + * \param telnet Telnet state tracker object. + * \param cmd TELNET_WILL, TELNET_WONT, TELNET_DO, or TELNET_DONT. + * \param opt One of the TELNET_TELOPT_* values. + */ +extern void telnet_negotiate(telnet_t *telnet, unsigned char cmd, + unsigned char opt); + +/*! + * Send non-command data (escapes IAC bytes). + * + * \param telnet Telnet state tracker object. + * \param buffer Buffer of bytes to send. + * \param size Number of bytes to send. + */ +extern void telnet_send(telnet_t *telnet, + const char *buffer, size_t size); + +/*! + * \brief Begin a sub-negotiation command. + * + * Sends IAC SB followed by the telopt code. All following data sent + * will be part of the sub-negotiation, until telnet_finish_sb() is + * called. + * + * \param telnet Telnet state tracker object. + * \param telopt One of the TELNET_TELOPT_* values. + */ +extern void telnet_begin_sb(telnet_t *telnet, + unsigned char telopt); + +/*! + * \brief Finish a sub-negotiation command. + * + * This must be called after a call to telnet_begin_sb() to finish a + * sub-negotiation command. + * + * \param telnet Telnet state tracker object. + */ +#define telnet_finish_sb(telnet) telnet_iac((telnet), TELNET_SE) + +/*! + * \brief Shortcut for sending a complete subnegotiation buffer. + * + * Equivalent to: + * telnet_begin_sb(telnet, telopt); + * telnet_send(telnet, buffer, size); + * telnet_finish_sb(telnet); + * + * \param telnet Telnet state tracker format. + * \param telopt One of the TELNET_TELOPT_* values. + * \param buffer Byte buffer for sub-negotiation data. + * \param size Number of bytes to use for sub-negotiation data. + */ +extern void telnet_subnegotiation(telnet_t *telnet, unsigned char telopt, + const char *buffer, size_t size); + +/*! + * \brief Begin sending compressed data. + * + * This function will begein sending data using the COMPRESS2 option, + * which enables the use of zlib to compress data sent to the client. + * The client must offer support for COMPRESS2 with option negotiation, + * and zlib support must be compiled into libtelnet. + * + * Only the server may call this command. + * + * \param telnet Telnet state tracker object. + */ +extern void telnet_begin_compress2(telnet_t *telnet); + +/*! + * \brief Send formatted data. + * + * This function is a wrapper around telnet_send(). It allows using + * printf-style formatting. + * + * Additionally, this function will translate \\r to the CR NUL construct and + * \\n with CR LF, as well as automatically escaping IAC bytes like + * telnet_send(). + * + * \param telnet Telnet state tracker object. + * \param fmt Format string. + * \return Number of bytes sent. + */ +extern int telnet_printf(telnet_t *telnet, const char *fmt, ...) + TELNET_GNU_PRINTF(2, 3); + +/*! + * \brief Send formatted data. + * + * See telnet_printf(). + */ +extern int telnet_vprintf(telnet_t *telnet, const char *fmt, va_list va); + +/*! + * \brief Send formatted data (no newline escaping). + * + * This behaves identically to telnet_printf(), except that the \\r and \\n + * characters are not translated. The IAC byte is still escaped as normal + * with telnet_send(). + * + * \param telnet Telnet state tracker object. + * \param fmt Format string. + * \return Number of bytes sent. + */ +extern int telnet_raw_printf(telnet_t *telnet, const char *fmt, ...) + TELNET_GNU_PRINTF(2, 3); + +/*! + * \brief Send formatted data (no newline escaping). + * + * See telnet_raw_printf(). + */ +extern int telnet_raw_vprintf(telnet_t *telnet, const char *fmt, va_list va); + +/*! + * \brief Begin a new set of NEW-ENVIRON values to request or send. + * + * This function will begin the sub-negotiation block for sending or + * requesting NEW-ENVIRON values. + * + * The telnet_finish_newenviron() macro must be called after this + * function to terminate the NEW-ENVIRON command. + * + * \param telnet Telnet state tracker object. + * \param type One of TELNET_ENVIRON_SEND, TELNET_ENVIRON_IS, or + * TELNET_ENVIRON_INFO. + */ +extern void telnet_begin_newenviron(telnet_t *telnet, unsigned char type); + +/*! + * \brief Send a NEW-ENVIRON variable name or value. + * + * This can only be called between calls to telnet_begin_newenviron() and + * telnet_finish_newenviron(). + * + * \param telnet Telnet state tracker object. + * \param type One of TELNET_ENVIRON_VAR, TELNET_ENVIRON_USERVAR, or + * TELNET_ENVIRON_VALUE. + * \param string Variable name or value. + */ +extern void telnet_newenviron_value(telnet_t* telnet, unsigned char type, + const char *string); + +/*! + * \brief Finish a NEW-ENVIRON command. + * + * This must be called after a call to telnet_begin_newenviron() to finish a + * NEW-ENVIRON variable list. + * + * \param telnet Telnet state tracker object. + */ +#define telnet_finish_newenviron(telnet) telnet_finish_sb((telnet)) + +/*! + * \brief Send the TERMINAL-TYPE SEND command. + * + * Sends the sequence IAC TERMINAL-TYPE SEND. + * + * \param telnet Telnet state tracker object. + */ +extern void telnet_ttype_send(telnet_t *telnet); + +/*! + * \brief Send the TERMINAL-TYPE IS command. + * + * Sends the sequence IAC TERMINAL-TYPE IS "string". + * + * According to the RFC, the recipient of a TERMINAL-TYPE SEND shall + * send the next possible terminal-type the client supports. Upon sending + * the type, the client should switch modes to begin acting as the terminal + * type is just sent. + * + * The server may continue sending TERMINAL-TYPE IS until it receives a + * terminal type is understands. To indicate to the server that it has + * reached the end of the available optoins, the client must send the last + * terminal type a second time. When the server receives the same terminal + * type twice in a row, it knows it has seen all available terminal types. + * + * After the last terminal type is sent, if the client receives another + * TERMINAL-TYPE SEND command, it must begin enumerating the available + * terminal types from the very beginning. This allows the server to + * scan the available types for a preferred terminal type and, if none + * is found, to then ask the client to switch to an acceptable + * alternative. + * + * Note that if the client only supports a single terminal type, then + * simply sending that one type in response to every SEND will satisfy + * the behavior requirements. + * + * \param telnet Telnet state tracker object. + * \param ttype Name of the terminal-type being sent. + */ +extern void telnet_ttype_is(telnet_t *telnet, const char* ttype); + +/*! + * \brief Send a ZMP command. + * + * \param telnet Telnet state tracker object. + * \param argc Number of ZMP commands being sent. + * \param argv Array of argument strings. + */ +extern void telnet_send_zmp(telnet_t *telnet, size_t argc, const char **argv); + +/*! + * \brief Send a ZMP command. + * + * Arguments are listed out in var-args style. After the last argument, a + * NULL pointer must be passed in as a sentinel value. + * + * \param telnet Telnet state tracker object. + */ +extern void telnet_send_zmpv(telnet_t *telnet, ...); + +/*! + * \brief Send a ZMP command. + * + * See telnet_send_zmpv(). + */ +extern void telnet_send_vzmpv(telnet_t *telnet, va_list va); + +/*! + * \brief Begin sending a ZMP command + * + * \param telnet Telnet state tracker object. + * \param cmd The first argument (command name) for the ZMP command. + */ +extern void telnet_begin_zmp(telnet_t *telnet, const char *cmd); + +/*! + * \brief Send a ZMP command argument. + * + * \param telnet Telnet state tracker object. + * \param arg Telnet argument string. + */ +extern void telnet_zmp_arg(telnet_t *telnet, const char *arg); + +/*! + * \brief Finish a ZMP command. + * + * This must be called after a call to telnet_begin_zmp() to finish a + * ZMP argument list. + * + * \param telnet Telnet state tracker object. + */ +#define telnet_finish_zmp(telnet) telnet_finish_sb((telnet)) + +/* C++ support */ +#if defined(__cplusplus) +} /* extern "C" */ +#endif + +#endif /* !defined(LIBTELNET_INCLUDE) */ diff --git a/src/apps/relay/mainrelay.c b/src/apps/relay/mainrelay.c new file mode 100644 index 0000000..9c6bc2c --- /dev/null +++ b/src/apps/relay/mainrelay.c @@ -0,0 +1,2376 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "mainrelay.h" + +////// TEMPORARY data ////////// + +static int use_lt_credentials = 0; +static int use_st_credentials = 0; +static int anon_credentials = 0; + +//////TURN PARAMS STRUCTURE DEFINITION ////// + +#define DEFAULT_GENERAL_RELAY_SERVERS_NUMBER (1) + +turn_params_t turn_params = { + +NULL, NULL, + +#if defined(SSL_TXT_TLSV1_1) + NULL, +#if defined(SSL_TXT_TLSV1_2) + NULL, +#endif +#endif + +NULL, + +DH_1066, "", DEFAULT_EC_CURVE_NAME, "", +"turn_server_cert.pem","turn_server_pkey.pem", "", "", +0,0,0,0,0, +#if defined(TURN_NO_TLS) +1, +#else +0, +#endif + +#if defined(TURN_NO_DTLS) +1, +#else +0, +#endif + +TURN_VERBOSE_NONE,0,0, +"/var/run/turnserver.pid", +DEFAULT_STUN_PORT,DEFAULT_STUN_TLS_PORT,0,0,1, +0,0,0,0, +"", +#if !defined(TURN_NO_HIREDIS) +"",0, +#endif +{ + NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0,0,NULL,NULL,NULL +}, +{NULL, NULL, 0},{NULL, NULL, 0}, +NEV_UNKNOWN, +{ "Unknown", "UDP listening socket per session", "UDP thread per network endpoint", "UDP thread per CPU core" }, +//////////////// Relay servers ////////////////////////////////// +LOW_DEFAULT_PORTS_BOUNDARY,HIGH_DEFAULT_PORTS_BOUNDARY,0,0,"", +0,NULL,0,NULL,DEFAULT_GENERAL_RELAY_SERVERS_NUMBER,0, +////////////// Auth server ///////////////////////////////////// +{NULL,NULL,NULL,0 +#if !defined(TURN_NO_HIREDIS) +,NULL +#endif +}, +/////////////// AUX SERVERS //////////////// +{NULL,0,{0,NULL}},0, +/////////////// ALTERNATE SERVERS //////////////// +{NULL,0,{0,NULL}},{NULL,0,{0,NULL}}, +/////////////// stop server //////////////// +0, +/////////////// MISC PARAMS //////////////// +0,0,0,0,0,SHATYPE_SHA1,':',0,0,TURN_CREDENTIALS_NONE,0,0,0,0, +///////////// Users DB ////////////// +{ TURN_USERDB_TYPE_FILE, {"\0",NULL}, {0,NULL,NULL, {NULL,0}} } + +}; + +//////////////// OpenSSL Init ////////////////////// + +static void openssl_setup(void); + +/* + * openssl genrsa -out pkey 2048 + * openssl req -new -key pkey -out cert.req + * openssl x509 -req -days 365 -in cert.req -signkey pkey -out cert + * +*/ + +//////////// Common static process params //////// + +static gid_t procgroupid = 0; +static uid_t procuserid = 0; +static gid_t procgroupid_set = 0; +static uid_t procuserid_set = 0; +static char procusername[1025]="\0"; +static char procgroupname[1025]="\0"; + +////////////// Configuration functionality //////////////////////////////// + +static void read_config_file(int argc, char **argv, int pass); + +////////////////////////////////////////////////// + +static int make_local_listeners_list(void) +{ + int ret = 0; + + struct ifaddrs * ifs = NULL; + struct ifaddrs * ifa = NULL; + + char saddr[INET6_ADDRSTRLEN] = ""; + + if((getifaddrs(&ifs) == 0) && ifs) { + + for (ifa = ifs; ifa != NULL; ifa = ifa->ifa_next) { + + if(!(ifa->ifa_flags & IFF_UP)) + continue; + + if(!(ifa->ifa_addr)) + continue; + + if (ifa ->ifa_addr->sa_family == AF_INET) { + if(!inet_ntop(AF_INET, &((struct sockaddr_in *) ifa->ifa_addr)->sin_addr, saddr, + INET_ADDRSTRLEN)) + continue; + if(strstr(saddr,"169.254.") == saddr) + continue; + if(!strcmp(saddr,"0.0.0.0")) + continue; + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + if(!inet_ntop(AF_INET6, &((struct sockaddr_in6 *) ifa->ifa_addr)->sin6_addr, saddr, + INET6_ADDRSTRLEN)) + continue; + if(strstr(saddr,"fe80") == saddr) + continue; + if(!strcmp(saddr,"::")) + continue; + } else { + continue; + } + + add_listener_addr(saddr); + + if(!(ifa->ifa_flags & IFF_LOOPBACK)) + ret++; + } + freeifaddrs(ifs); + } + + return ret; +} + +static int make_local_relays_list(int allow_local, int family) +{ + struct ifaddrs * ifs = NULL; + struct ifaddrs * ifa = NULL; + + char saddr[INET6_ADDRSTRLEN] = ""; + + getifaddrs(&ifs); + + int counter = 0; + + if (ifs) { + for (ifa = ifs; ifa != NULL; ifa = ifa->ifa_next) { + + if(!(ifa->ifa_flags & IFF_UP)) + continue; + + if(!(ifa->ifa_name)) + continue; + if(!(ifa ->ifa_addr)) + continue; + + if(!allow_local && (ifa->ifa_flags & IFF_LOOPBACK)) + continue; + + if (ifa ->ifa_addr->sa_family == AF_INET) { + + if(family != AF_INET) + continue; + + if(!inet_ntop(AF_INET, &((struct sockaddr_in *) ifa->ifa_addr)->sin_addr, saddr, + INET_ADDRSTRLEN)) + continue; + if(strstr(saddr,"169.254.") == saddr) + continue; + if(!strcmp(saddr,"0.0.0.0")) + continue; + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + + if(family != AF_INET6) + continue; + + if(!inet_ntop(AF_INET6, &((struct sockaddr_in6 *) ifa->ifa_addr)->sin6_addr, saddr, + INET6_ADDRSTRLEN)) + continue; + if(strstr(saddr,"fe80") == saddr) + continue; + if(!strcmp(saddr,"::")) + continue; + } else + continue; + + if(add_relay_addr(saddr)>0) { + counter += 1; + } + } + freeifaddrs(ifs); + } + + return counter; +} + +int get_a_local_relay(int family, ioa_addr *relay_addr) +{ + struct ifaddrs * ifs = NULL; + + int allow_local = 0; + + int ret = -1; + + char saddr[INET6_ADDRSTRLEN] = ""; + + getifaddrs(&ifs); + + if (ifs) { + + galr_start: + + { + struct ifaddrs *ifa = NULL; + + for (ifa = ifs; ifa != NULL ; ifa = ifa->ifa_next) { + + if (!(ifa->ifa_flags & IFF_UP)) + continue; + + if (!(ifa->ifa_name)) + continue; + if (!(ifa->ifa_addr)) + continue; + + if (!allow_local && (ifa->ifa_flags & IFF_LOOPBACK)) + continue; + + if (ifa->ifa_addr->sa_family == AF_INET) { + + if (family != AF_INET) + continue; + + if (!inet_ntop(AF_INET, + &((struct sockaddr_in *) ifa->ifa_addr)->sin_addr, + saddr, INET_ADDRSTRLEN)) + continue; + if (strstr(saddr, "169.254.") == saddr) + continue; + if (!strcmp(saddr, "0.0.0.0")) + continue; + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + + if (family != AF_INET6) + continue; + + if (!inet_ntop(AF_INET6, + &((struct sockaddr_in6 *) ifa->ifa_addr)->sin6_addr, + saddr, INET6_ADDRSTRLEN)) + continue; + if (strstr(saddr, "fe80") == saddr) + continue; + if (!strcmp(saddr, "::")) + continue; + } else + continue; + + if (make_ioa_addr((const u08bits*) saddr, 0, relay_addr) < 0) { + continue; + } else { + ret = 0; + break; + } + } + } + + if(ret<0 && !allow_local) { + allow_local = 1; + goto galr_start; + } + + freeifaddrs(ifs); + } + + return -1; +} + +////////////////////////////////////////////////// + +static char Usage[] = "Usage: turnserver [options]\n" +"Options:\n" +" -d, --listening-device Listener interface device (NOT RECOMMENDED. Optional, Linux only).\n" +" -p, --listening-port TURN listener port (Default: 3478).\n" +" Note: actually, TLS & DTLS sessions can connect to the \"plain\" TCP & UDP port(s), too,\n" +" if allowed by configuration.\n" +" --tls-listening-port TURN listener port for TLS & DTLS listeners\n" +" (Default: 5349).\n" +" Note: actually, \"plain\" TCP & UDP sessions can connect to the TLS & DTLS port(s), too,\n" +" if allowed by configuration. The TURN server\n" +" \"automatically\" recognizes the type of traffic. Actually, two listening\n" +" endpoints (the \"plain\" one and the \"tls\" one) are equivalent in terms of\n" +" functionality; but we keep both endpoints to satisfy the RFC 5766 specs.\n" +" For secure TCP connections, we currently support SSL version 3 and\n" +" TLS versions 1.0, 1.1 and 1.2. For secure UDP connections, we support\n" +" DTLS version 1.\n" +" --alt-listening-port Alternative listening port for STUN CHANGE_REQUEST (in RFC 5780 sense, \n" +" or in old RFC 3489 sense, default is \"listening port plus one\").\n" +" --alt-tls-listening-port Alternative listening port for TLS and DTLS,\n" +" the default is \"TLS/DTLS port plus one\".\n" +" -L, --listening-ip Listener IP address of relay server. Multiple listeners can be specified.\n" +" --aux-server Auxiliary STUN/TURN server listening endpoint.\n" +" Auxiliary servers do not have alternative ports and\n" +" they do not support RFC 5780 functionality (CHANGE REQUEST).\n" +" Valid formats are 1.2.3.4:5555 for IPv4 and [1:2::3:4]:5555 for IPv6.\n" +" --udp-self-balance (recommended for older Linuxes only) Automatically balance UDP traffic\n" +" over auxiliary servers (if configured).\n" +" The load balancing is using the ALTERNATE-SERVER mechanism.\n" +" The TURN client must support 300 ALTERNATE-SERVER response for this functionality.\n" +" -i, --relay-device Relay interface device for relay sockets (NOT RECOMMENDED. Optional, Linux only).\n" +" -E, --relay-ip Relay address (the local IP address that will be used to relay the\n" +" packets to the peer).\n" +" Multiple relay addresses may be used.\n" +" The same IP(s) can be used as both listening IP(s) and relay IP(s).\n" +" If no relay IP(s) specified, then the turnserver will apply the default\n" +" policy: it will decide itself which relay addresses to be used, and it\n" +" will always be using the client socket IP address as the relay IP address\n" +" of the TURN session (if the requested relay address family is the same\n" +" as the family of the client socket).\n" +" -X, --external-ip TURN Server public/private address mapping, if the server is behind NAT.\n" +" In that situation, if a -X is used in form \"-X ip\" then that ip will be reported\n" +" as relay IP address of all allocations. This scenario works only in a simple case\n" +" when one single relay address is be used, and no STUN CHANGE_REQUEST functionality is required.\n" +" That single relay address must be mapped by NAT to the 'external' IP.\n" +" For that 'external' IP, NAT must forward ports directly (relayed port 12345\n" +" must be always mapped to the same 'external' port 12345).\n" +" In more complex case when more than one IP address is involved,\n" +" that option must be used several times in the command line, each entry must\n" +" have form \"-X public-ip/private-ip\", to map all involved addresses.\n" +" --no-loopback-peers Disallow peers on the loopback addresses (127.x.x.x and ::1).\n" +" --no-multicast-peers Disallow peers on well-known broadcast addresses (224.0.0.0 and above, and FFXX:*).\n" +" -m, --relay-threads Number of relay threads to handle the established connections\n" +" (in addition to authentication thread and the listener thread).\n" +" If set to 0 then application runs in single-threaded mode.\n" +" The default thread number is the number of CPUs.\n" +" In older systems (pre-Linux 3.9) the number of UDP relay threads always equals\n" +" the number of listening endpoints (unless -m 0 is set).\n" +" --min-port Lower bound of the UDP port range for relay endpoints allocation.\n" +" Default value is 49152, according to RFC 5766.\n" +" --max-port Upper bound of the UDP port range for relay endpoints allocation.\n" +" Default value is 65535, according to RFC 5766.\n" +" -v, --verbose 'Moderate' verbose mode.\n" +" -V, --Verbose Extra verbose mode, very annoying (for debug purposes only).\n" +" -o, --daemon Start process as daemon (detach from current shell).\n" +" -f, --fingerprint Use fingerprints in the TURN messages.\n" +" -a, --lt-cred-mech Use the long-term credential mechanism. This option can be used with either\n" +" flat file user database or PostgreSQL DB or MySQL DB for user keys storage.\n" +" -A, --st-cred-mech Use the short-term credential mechanism. This option requires\n" +" a PostgreSQL or MySQL DB for short term passwords storage.\n" +" -z, --no-auth Do not use any credential mechanism, allow anonymous access.\n" +" -u, --user User account, in form 'username:password', for long-term credentials.\n" +" Cannot be used with TURN REST API or with short-term credentials.\n" +" -r, --realm The default realm to be used for the users when no explicit\n" +" origin/realm relationship was found in the database, or if the TURN\n" +" server is not using any database (just the commands-line settings\n" +" and the userdb file). Must be used with long-term credentials \n" +" mechanism or with TURN REST API.\n" +" -q, --user-quota Per-user allocation quota: how many concurrent allocations a user can create.\n" +" This option can also be set through the database, for a particular realm.\n" +" -Q, --total-quota Total allocations quota: global limit on concurrent allocations.\n" +" This option can also be set through the database, for a particular realm.\n" +" -s, --max-bps Max bytes-per-second bandwidth a TURN session is allowed to handle\n" +" (input and output network streams are treated separately). Anything above\n" +" that limit will be dropped or temporary suppressed\n" +" (within the available buffer limits).\n" +" This option can also be set through the database, for a particular realm.\n" +" -c Configuration file name (default - turnserver.conf).\n" +" -b, --userdb User database file name (default - turnuserdb.conf) for long-term credentials only.\n" +#if !defined(TURN_NO_PQ) +" -e, --psql-userdb, --sql-userdb PostgreSQL database connection string, if used (default - empty, no PostreSQL DB used).\n" +" This database can be used for long-term and short-term credentials mechanisms,\n" +" and it can store the secret value(s) for secret-based timed authentication in TURN RESP API.\n" +" See http://www.postgresql.org/docs/8.4/static/libpq-connect.html for 8.x PostgreSQL\n" +" versions format, see \n" +" http://www.postgresql.org/docs/9.2/static/libpq-connect.html#LIBPQ-CONNSTRING\n" +" for 9.x and newer connection string formats.\n" +#endif +#if !defined(TURN_NO_MYSQL) +" -M, --mysql-userdb MySQL database connection string, if used (default - empty, no MySQL DB used).\n" +" This database can be used for long-term and short-term credentials mechanisms,\n" +" and it can store the secret value(s) for secret-based timed authentication in TURN RESP API.\n" +" The connection string my be space-separated list of parameters:\n" +" \"host= dbname= user= \\\n password= port= connect_timeout=\".\n" +" All parameters are optional.\n" +#endif +#if !defined(TURN_NO_HIREDIS) +" -N, --redis-userdb Redis user database connection string, if used (default - empty, no Redis DB used).\n" +" This database can be used for long-term and short-term credentials mechanisms,\n" +" and it can store the secret value(s) for secret-based timed authentication in TURN RESP API.\n" +" The connection string my be space-separated list of parameters:\n" +" \"host= dbname= \\\n password= port= connect_timeout=\".\n" +" All parameters are optional.\n" +" -O, --redis-statsdb Redis status and statistics database connection string, if used \n" +" (default - empty, no Redis stats DB used).\n" +" This database keeps allocations status information, and it can be also used for publishing\n" +" and delivering traffic and allocation event notifications.\n" +" The connection string has the same parameters as redis-userdb connection string.\n" +#endif +" --use-auth-secret TURN REST API flag.\n" +" Flag that sets a special authorization option that is based upon authentication secret\n" +" (TURN Server REST API, see TURNServerRESTAPI.pdf). This option is used with timestamp.\n" +" --static-auth-secret 'Static' authentication secret value (a string) for TURN REST API only.\n" +" If not set, then the turn server will try to use the 'dynamic' value\n" +" in turn_secret table in user database (if present).\n" +" That database value can be changed on-the-fly\n" +" by a separate program, so this is why it is 'dynamic'.\n" +" Multiple shared secrets can be used (both in the database and in the \"static\" fashion).\n" +" -n Do not use configuration file, take all parameters from the command line only.\n" +" --cert Certificate file, PEM format. Same file search rules\n" +" applied as for the configuration file.\n" +" If both --no-tls and --no_dtls options\n" +" are specified, then this parameter is not needed.\n" +" --pkey Private key file, PEM format. Same file search rules\n" +" applied as for the configuration file.\n" +" If both --no-tls and --no-dtls options\n" +" --pkey-pwd If the private key file is encrypted, then this password to be used.\n" +" --cipher-list <\"cipher-string\"> Allowed OpenSSL cipher list for TLS/DTLS connections.\n" +" Default value is \"DEFAULT\".\n" +" --CA-file CA file in OpenSSL format.\n" +" Forces TURN server to verify the client SSL certificates.\n" +" By default, no CA is set and no client certificate check is performed.\n" +" --ec-curve-name Curve name for EC ciphers, if supported by OpenSSL library\n" +" (TLS and DTLS). The default value is prime256v1.\n" +" --dh566 Use 566 bits predefined DH TLS key. Default size of the predefined key is 1066.\n" +" --dh2066 Use 2066 bits predefined DH TLS key. Default size of the predefined key is 1066.\n" +" --dh-file Use custom DH TLS key, stored in PEM format in the file.\n" +" Flags --dh566 and --dh2066 are ignored when the DH key is taken from a file.\n" +" --no-sslv2 Do not allow SSLv2 protocol.\n" +" --no-sslv3 Do not allow SSLv3 protocol.\n" +" --no-tlsv1 Do not allow TLSv1 protocol.\n" +" --no-tlsv1_1 Do not allow TLSv1.1 protocol.\n" +" --no-tlsv1_2 Do not allow TLSv1.2 protocol.\n" +" --no-udp Do not start UDP client listeners.\n" +" --no-tcp Do not start TCP client listeners.\n" +" --no-tls Do not start TLS client listeners.\n" +" --no-dtls Do not start DTLS client listeners.\n" +" --no-udp-relay Do not allow UDP relay endpoints, use only TCP relay option.\n" +" --no-tcp-relay Do not allow TCP relay endpoints, use only UDP relay options.\n" +" -l, --log-file Option to set the full path name of the log file.\n" +" By default, the turnserver tries to open a log file in\n" +" /var/log/turnserver/, /var/log, /var/tmp, /tmp and . (current) directories\n" +" (which open operation succeeds first that file will be used).\n" +" With this option you can set the definite log file name.\n" +" The special names are \"stdout\" and \"-\" - they will force everything\n" +" to the stdout; and \"syslog\" name will force all output to the syslog.\n" +" --no-stdout-log Flag to prevent stdout log messages.\n" +" By default, all log messages are going to both stdout and to\n" +" a log file. With this option everything will be going to the log file only\n" +" (unless the log file itself is stdout).\n" +" --syslog Output all log information into the system log (syslog), do not use the file output.\n" +" --simple-log This flag means that no log file rollover will be used, and the log file\n" +" name will be constructed as-is, without PID and date appendage.\n" +" --stale-nonce Use extra security with nonce value having limited lifetime (600 secs).\n" +" -S, --stun-only Option to set standalone STUN operation only, all TURN requests will be ignored.\n" +" --no-stun Option to suppress STUN functionality, only TURN requests will be processed.\n" +" --alternate-server Set the TURN server to redirect the allocate requests (UDP and TCP services).\n" +" Multiple alternate-server options can be set for load balancing purposes.\n" +" See the docs for more information.\n" +" --tls-alternate-server Set the TURN server to redirect the allocate requests (DTLS and TLS services).\n" +" Multiple alternate-server options can be set for load balancing purposes.\n" +" See the docs for more information.\n" +" -C, --rest-api-separator This is the timestamp/username separator symbol (character) in TURN REST API.\n" +" The default value is ':'.\n" +" --max-allocate-timeout= Max time, in seconds, allowed for full allocation establishment. Default is 60.\n" +" --allowed-peer-ip= Specifies an ip or range of ips that are explicitly allowed to connect to the \n" +" turn server. Multiple allowed-peer-ip can be set.\n" +" --denied-peer-ip= Specifies an ip or range of ips that are not allowed to connect to the turn server.\n" +" Multiple denied-peer-ip can be set.\n" +" --pidfile <\"pid-file-name\"> File name to store the pid of the process.\n" +" Default is /var/run/turnserver.pid (if superuser account is used) or\n" +" /var/tmp/turnserver.pid .\n" +" --secure-stun Require authentication of the STUN Binding request.\n" +" By default, the clients are allowed anonymous access to the STUN Binding functionality.\n" +" --sha256 Require SHA256 digest function to be used for the message integrity.\n" +" By default, the server SHA1 (as per TURN standard specs).\n" +" With this option, the server\n" +" requires the stronger SHA256 function. The client application must\n" +" support SHA256 hash function if this option is used. If the server obtains\n" +" a message from the client with a weaker (SHA1) hash function then the server\n" +" returns error code 426.\n" +" --proc-user User name to run the turnserver process.\n" +" After the initialization, the turnserver process\n" +" will make an attempt to change the current user ID to that user.\n" +" --proc-group Group name to run the turnserver process.\n" +" After the initialization, the turnserver process\n" +" will make an attempt to change the current group ID to that group.\n" +" --mobility Mobility with ICE (MICE) specs support.\n" +" --no-cli Turn OFF the CLI support. By default it is always ON.\n" +" --cli-ip= Local system IP address to be used for CLI server endpoint. Default value\n" +" is 127.0.0.1.\n" +" --cli-port= CLI server port. Default is 5766.\n" +" --cli-password= CLI access password. Default is empty (no password).\n" +" --server-relay Server relay. NON-STANDARD AND DANGEROUS OPTION. Only for those applications\n" +" when we want to run server applications on the relay endpoints.\n" +" This option eliminates the IP permissions check on the packets\n" +" incoming to the relay endpoints.\n" +" --cli-max-output-sessions Maximum number of output sessions in ps CLI command.\n" +" This value can be changed on-the-fly in CLI. The default value is 256.\n" +" --ne=[1|2|3] Set network engine type for the process (for internal purposes).\n" +" -h Help\n" +"\n" +" For more information, see the wiki pages:\n" +"\n" +" http://code.google.com/p/coturn/w/list\n" +"\n"; + +static char AdminUsage[] = "Usage: turnadmin [command] [options]\n" + "Commands:\n" + " -k, --key generate long-term credential mechanism key for a user\n" + " -a, --add add/update a long-term mechanism user\n" + " -A, --add-st add/update a short-term mechanism user\n" + " -d, --delete delete a long-term mechanism user\n" + " -D, --delete-st delete a short-term mechanism user\n" + " -l, --list list all long-term mechanism users\n" + " -L, --list-st list all short-term mechanism users\n" +#if !defined(TURN_NO_PQ) || !defined(TURN_NO_MYSQL) || !defined(TURN_NO_HIREDIS) + " -s, --set-secret= Add shared secret for TURN RESP API\n" + " -S, --show-secret Show stored shared secrets for TURN REST API\n" + " -X, --delete-secret= Delete a shared secret\n" + " --delete-all-secrets Delete all shared secrets for REST API\n" + " -O, --add-origin Add origin-to-realm relation.\n" + " -R, --del-origin Delete origin-to-realm relation.\n" + " -I, --list-origins List origin-to-realm relations.\n" + " -g, --set-realm-option Set realm params: max-bps, total-quota, user-quota.\n" + " -G, --list-realm-options List realm params.\n" +#endif + "Options with mandatory values:\n" + " -b, --userdb User database file, if flat DB file is used.\n" +#if !defined(TURN_NO_PQ) + " -e, --psql-userdb, --sql-userdb PostgreSQL user database connection string, if PostgreSQL DB is used.\n" +#endif +#if !defined(TURN_NO_MYSQL) + " -M, --mysql-userdb MySQL user database connection string, if MySQL DB is used.\n" +#endif +#if !defined(TURN_NO_HIREDIS) + " -N, --redis-userdb Redis user database connection string, if Redis DB is used.\n" +#endif + " -u, --user Username\n" + " -r, --realm Realm for long-term mechanism only\n" + " -p, --password Password\n" +#if !defined(TURN_NO_PQ) || !defined(TURN_NO_MYSQL) || !defined(TURN_NO_HIREDIS) + " -o, --origin Origin\n" +#endif + " -H, --sha256 Use SHA256 digest function to be used for the message integrity.\n" + " By default, the server SHA1 (as per TURN standard specs).\n" + " --max-bps Set value of realm's max-bps parameter.\n" + " Setting to zero value means removal of the option.\n" + " --total-quota Set value of realm's total-quota parameter.\n" + " Setting to zero value means removal of the option.\n" + " --user-quota Set value of realm's user-quota parameter.\n" + " Setting to zero value means removal of the option.\n" + " -h, --help Help\n"; + +#define OPTIONS "c:d:p:L:E:X:i:m:l:r:u:b:e:M:N:O:q:Q:s:C:vVofhznaAS" + +#define ADMIN_OPTIONS "gGORIHlLkaADSdb:e:M:N:u:r:p:s:X:o:h" + +enum EXTRA_OPTS { + NO_UDP_OPT=256, + NO_TCP_OPT, + NO_TLS_OPT, + NO_DTLS_OPT, + NO_UDP_RELAY_OPT, + NO_TCP_RELAY_OPT, + TLS_PORT_OPT, + ALT_PORT_OPT, + ALT_TLS_PORT_OPT, + CERT_FILE_OPT, + PKEY_FILE_OPT, + PKEY_PWD_OPT, + MIN_PORT_OPT, + MAX_PORT_OPT, + STALE_NONCE_OPT, + AUTH_SECRET_OPT, + DEL_ALL_AUTH_SECRETS_OPT, + STATIC_AUTH_SECRET_VAL_OPT, + AUTH_SECRET_TS_EXP, /* deprecated */ + NO_STDOUT_LOG_OPT, + SYSLOG_OPT, + SIMPLE_LOG_OPT, + AUX_SERVER_OPT, + UDP_SELF_BALANCE_OPT, + ALTERNATE_SERVER_OPT, + TLS_ALTERNATE_SERVER_OPT, + NO_MULTICAST_PEERS_OPT, + NO_LOOPBACK_PEERS_OPT, + MAX_ALLOCATE_TIMEOUT_OPT, + ALLOWED_PEER_IPS, + DENIED_PEER_IPS, + CIPHER_LIST_OPT, + PIDFILE_OPT, + SECURE_STUN_OPT, + CA_FILE_OPT, + DH_FILE_OPT, + SHA256_OPT, + NO_STUN_OPT, + PROC_USER_OPT, + PROC_GROUP_OPT, + MOBILITY_OPT, + NO_CLI_OPT, + CLI_IP_OPT, + CLI_PORT_OPT, + CLI_PASSWORD_OPT, + SERVER_RELAY_OPT, + CLI_MAX_SESSIONS_OPT, + EC_CURVE_NAME_OPT, + DH566_OPT, + DH2066_OPT, + NE_TYPE_OPT, + NO_SSLV2_OPT, + NO_SSLV3_OPT, + NO_TLSV1_OPT, + NO_TLSV1_1_OPT, + NO_TLSV1_2_OPT, + ADMIN_MAX_BPS_OPT, + ADMIN_TOTAL_QUOTA_OPT, + ADMIN_USER_QUOTA_OPT +}; + +static struct option long_options[] = { + { "listening-device", required_argument, NULL, 'd' }, + { "listening-port", required_argument, NULL, 'p' }, + { "tls-listening-port", required_argument, NULL, TLS_PORT_OPT }, + { "alt-listening-port", required_argument, NULL, ALT_PORT_OPT }, + { "alt-tls-listening-port", required_argument, NULL, ALT_TLS_PORT_OPT }, + { "listening-ip", required_argument, NULL, 'L' }, + { "relay-device", required_argument, NULL, 'i' }, + { "relay-ip", required_argument, NULL, 'E' }, + { "external-ip", required_argument, NULL, 'X' }, + { "relay-threads", required_argument, NULL, 'm' }, + { "min-port", required_argument, NULL, MIN_PORT_OPT }, + { "max-port", required_argument, NULL, MAX_PORT_OPT }, + { "lt-cred-mech", optional_argument, NULL, 'a' }, + { "st-cred-mech", optional_argument, NULL, 'A' }, + { "no-auth", optional_argument, NULL, 'z' }, + { "user", required_argument, NULL, 'u' }, + { "userdb", required_argument, NULL, 'b' }, +#if !defined(TURN_NO_PQ) + { "psql-userdb", required_argument, NULL, 'e' }, + { "sql-userdb", required_argument, NULL, 'e' }, +#endif +#if !defined(TURN_NO_MYSQL) + { "mysql-userdb", required_argument, NULL, 'M' }, +#endif +#if !defined(TURN_NO_HIREDIS) + { "redis-userdb", required_argument, NULL, 'N' }, + { "redis-statsdb", required_argument, NULL, 'O' }, +#endif + { "use-auth-secret", optional_argument, NULL, AUTH_SECRET_OPT }, + { "static-auth-secret", required_argument, NULL, STATIC_AUTH_SECRET_VAL_OPT }, +/* deprecated: */ { "secret-ts-exp-time", optional_argument, NULL, AUTH_SECRET_TS_EXP }, + { "realm", required_argument, NULL, 'r' }, + { "user-quota", required_argument, NULL, 'q' }, + { "total-quota", required_argument, NULL, 'Q' }, + { "max-bps", required_argument, NULL, 's' }, + { "verbose", optional_argument, NULL, 'v' }, + { "Verbose", optional_argument, NULL, 'V' }, + { "daemon", optional_argument, NULL, 'o' }, + { "fingerprint", optional_argument, NULL, 'f' }, + { "no-udp", optional_argument, NULL, NO_UDP_OPT }, + { "no-tcp", optional_argument, NULL, NO_TCP_OPT }, + { "no-tls", optional_argument, NULL, NO_TLS_OPT }, + { "no-dtls", optional_argument, NULL, NO_DTLS_OPT }, + { "no-udp-relay", optional_argument, NULL, NO_UDP_RELAY_OPT }, + { "no-tcp-relay", optional_argument, NULL, NO_TCP_RELAY_OPT }, + { "stale-nonce", optional_argument, NULL, STALE_NONCE_OPT }, + { "stun-only", optional_argument, NULL, 'S' }, + { "no-stun", optional_argument, NULL, NO_STUN_OPT }, + { "cert", required_argument, NULL, CERT_FILE_OPT }, + { "pkey", required_argument, NULL, PKEY_FILE_OPT }, + { "pkey-pwd", required_argument, NULL, PKEY_PWD_OPT }, + { "log-file", required_argument, NULL, 'l' }, + { "no-stdout-log", optional_argument, NULL, NO_STDOUT_LOG_OPT }, + { "syslog", optional_argument, NULL, SYSLOG_OPT }, + { "simple-log", optional_argument, NULL, SIMPLE_LOG_OPT }, + { "aux-server", required_argument, NULL, AUX_SERVER_OPT }, + { "udp-self-balance", optional_argument, NULL, UDP_SELF_BALANCE_OPT }, + { "alternate-server", required_argument, NULL, ALTERNATE_SERVER_OPT }, + { "tls-alternate-server", required_argument, NULL, TLS_ALTERNATE_SERVER_OPT }, + { "rest-api-separator", required_argument, NULL, 'C' }, + { "max-allocate-timeout", required_argument, NULL, MAX_ALLOCATE_TIMEOUT_OPT }, + { "no-multicast-peers", optional_argument, NULL, NO_MULTICAST_PEERS_OPT }, + { "no-loopback-peers", optional_argument, NULL, NO_LOOPBACK_PEERS_OPT }, + { "allowed-peer-ip", required_argument, NULL, ALLOWED_PEER_IPS }, + { "denied-peer-ip", required_argument, NULL, DENIED_PEER_IPS }, + { "cipher-list", required_argument, NULL, CIPHER_LIST_OPT }, + { "pidfile", required_argument, NULL, PIDFILE_OPT }, + { "secure-stun", optional_argument, NULL, SECURE_STUN_OPT }, + { "CA-file", required_argument, NULL, CA_FILE_OPT }, + { "dh-file", required_argument, NULL, DH_FILE_OPT }, + { "sha256", optional_argument, NULL, SHA256_OPT }, + { "proc-user", required_argument, NULL, PROC_USER_OPT }, + { "proc-group", required_argument, NULL, PROC_GROUP_OPT }, + { "mobility", optional_argument, NULL, MOBILITY_OPT }, + { "no-cli", optional_argument, NULL, NO_CLI_OPT }, + { "cli-ip", required_argument, NULL, CLI_IP_OPT }, + { "cli-port", required_argument, NULL, CLI_PORT_OPT }, + { "cli-password", required_argument, NULL, CLI_PASSWORD_OPT }, + { "server-relay", optional_argument, NULL, SERVER_RELAY_OPT }, + { "cli-max-output-sessions", required_argument, NULL, CLI_MAX_SESSIONS_OPT }, + { "ec-curve-name", required_argument, NULL, EC_CURVE_NAME_OPT }, + { "dh566", optional_argument, NULL, DH566_OPT }, + { "dh2066", optional_argument, NULL, DH2066_OPT }, + { "ne", required_argument, NULL, NE_TYPE_OPT }, + { "no-sslv2", optional_argument, NULL, NO_SSLV2_OPT }, + { "no-sslv3", optional_argument, NULL, NO_SSLV3_OPT }, + { "no-tlsv1", optional_argument, NULL, NO_TLSV1_OPT }, + { "no-tlsv1_1", optional_argument, NULL, NO_TLSV1_1_OPT }, + { "no-tlsv1_2", optional_argument, NULL, NO_TLSV1_2_OPT }, + { NULL, no_argument, NULL, 0 } +}; + +static struct option admin_long_options[] = { + { "key", no_argument, NULL, 'k' }, + { "add", no_argument, NULL, 'a' }, + { "delete", no_argument, NULL, 'd' }, + { "list", no_argument, NULL, 'l' }, + { "list-st", no_argument, NULL, 'L' }, +#if !defined(TURN_NO_PQ) || !defined(TURN_NO_MYSQL) || !defined(TURN_NO_HIREDIS) + { "set-secret", required_argument, NULL, 's' }, + { "show-secret", no_argument, NULL, 'S' }, + { "delete-secret", required_argument, NULL, 'X' }, + { "delete-all-secrets", no_argument, NULL, DEL_ALL_AUTH_SECRETS_OPT }, +#endif + { "add-st", no_argument, NULL, 'A' }, + { "delete-st", no_argument, NULL, 'D' }, + { "userdb", required_argument, NULL, 'b' }, +#if !defined(TURN_NO_PQ) + { "psql-userdb", required_argument, NULL, 'e' }, + { "sql-userdb", required_argument, NULL, 'e' }, +#endif +#if !defined(TURN_NO_MYSQL) + { "mysql-userdb", required_argument, NULL, 'M' }, +#endif +#if !defined(TURN_NO_HIREDIS) + { "redis-userdb", required_argument, NULL, 'N' }, +#endif + { "user", required_argument, NULL, 'u' }, + { "realm", required_argument, NULL, 'r' }, + { "password", required_argument, NULL, 'p' }, + { "sha256", no_argument, NULL, 'H' }, +#if !defined(TURN_NO_PQ) || !defined(TURN_NO_MYSQL) || !defined(TURN_NO_HIREDIS) + { "add-origin", no_argument, NULL, 'O' }, + { "del-origin", no_argument, NULL, 'R' }, + { "list-origins", required_argument, NULL, 'I' }, + { "origin", required_argument, NULL, 'o' }, + { "set-realm-option", no_argument, NULL, 'g' }, + { "list-realm-option", no_argument, NULL, 'G' }, + { "user-quota", required_argument, NULL, ADMIN_USER_QUOTA_OPT }, + { "total-quota", required_argument, NULL, ADMIN_TOTAL_QUOTA_OPT }, + { "max-bps", required_argument, NULL, ADMIN_MAX_BPS_OPT }, +#endif + { "help", no_argument, NULL, 'h' }, + { NULL, no_argument, NULL, 0 } +}; + +static int get_bool_value(const char* s) +{ + if(!s || !(s[0])) return 1; + if(s[0]=='0' || s[0]=='n' || s[0]=='N' || s[0]=='f' || s[0]=='F') return 0; + if(s[0]=='y' || s[0]=='Y' || s[0]=='t' || s[0]=='T') return 1; + if(s[0]>'0' && s[0]<='9') return 1; + if(!strcmp(s,"off") || !strcmp(s,"OFF") || !strcmp(s,"Off")) return 0; + if(!strcmp(s,"on") || !strcmp(s,"ON") || !strcmp(s,"On")) return 1; + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unknown boolean value: %s. You can use on/off, yes/no, 1/0, true/false.\n",s); + exit(-1); +} + +static void set_option(int c, char *value) +{ + if(value && value[0]=='=') { + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "WARNING: option -%c is possibly used incorrectly. The short form of the option must be used as this: -%c , no \'equals\' sign may be used, that sign is used only with long form options (like --user=).\n",(char)c,(char)c); + } + + switch (c) { + case NO_SSLV2_OPT: + turn_params.no_sslv2 = get_bool_value(value); + break; + case NO_SSLV3_OPT: + turn_params.no_sslv3 = get_bool_value(value); + break; + case NO_TLSV1_OPT: + turn_params.no_tlsv1 = get_bool_value(value); + break; + case NO_TLSV1_1_OPT: + turn_params.no_tlsv1_1 = get_bool_value(value); + break; + case NO_TLSV1_2_OPT: + turn_params.no_tlsv1_2 = get_bool_value(value); + break; + case NE_TYPE_OPT: + { + int ne = atoi(value); + if((ne<(int)NEV_MIN)||(ne>(int)NEV_MAX)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "ERROR: wrong version of the network engine: %d\n",ne); + } + turn_params.net_engine_version = (NET_ENG_VERSION)ne; + } + break; + case DH566_OPT: + if(get_bool_value(value)) + turn_params.dh_key_size = DH_566; + break; + case DH2066_OPT: + if(get_bool_value(value)) + turn_params.dh_key_size = DH_2066; + break; + case EC_CURVE_NAME_OPT: + STRCPY(turn_params.ec_curve_name,value); + break; + case CLI_MAX_SESSIONS_OPT: + cli_max_output_sessions = atoi(value); + break; + case SERVER_RELAY_OPT: + turn_params.server_relay = get_bool_value(value); + break; + case MOBILITY_OPT: + turn_params.mobility = get_bool_value(value); + break; + case NO_CLI_OPT: + use_cli = !get_bool_value(value); + break; + case CLI_IP_OPT: + if(make_ioa_addr((const u08bits*)value,0,&cli_addr)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"Cannot set cli address: %s\n",value); + } else{ + cli_addr_set = 1; + } + break; + case CLI_PORT_OPT: + cli_port = atoi(value); + break; + case CLI_PASSWORD_OPT: + STRCPY(cli_password,value); + break; + case PROC_USER_OPT: { + struct passwd* pwd = getpwnam(value); + if(!pwd) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unknown user name: %s\n",value); + exit(-1); + } else { + procuserid = pwd->pw_uid; + procuserid_set = 1; + STRCPY(procusername,value); + } + } + break; + case PROC_GROUP_OPT: { + struct group* gr = getgrnam(value); + if(!gr) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unknown group name: %s\n",value); + exit(-1); + } else { + procgroupid = gr->gr_gid; + procgroupid_set = 1; + STRCPY(procgroupname,value); + } + } + break; + case 'i': + STRCPY(turn_params.relay_ifname, value); + break; + case 'm': +#if defined(OPENSSL_THREADS) + if(atoi(value)>MAX_NUMBER_OF_GENERAL_RELAY_SERVERS) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "WARNING: max number of relay threads is 128.\n"); + turn_params.general_relay_servers_number = MAX_NUMBER_OF_GENERAL_RELAY_SERVERS; + } else if(atoi(value)<=0) { + turn_params.general_relay_servers_number = 0; + } else { + turn_params.general_relay_servers_number = atoi(value); + } +#else + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "WARNING: OpenSSL version is too old OR does not support threading,\n I am using single thread for relaying.\n"); +#endif + break; + case 'd': + STRCPY(turn_params.listener_ifname, value); + break; + case 'p': + turn_params.listener_port = atoi(value); + break; + case TLS_PORT_OPT: + turn_params.tls_listener_port = atoi(value); + break; + case ALT_PORT_OPT: + turn_params.alt_listener_port = atoi(value); + break; + case ALT_TLS_PORT_OPT: + turn_params.alt_tls_listener_port = atoi(value); + break; + case MIN_PORT_OPT: + turn_params.min_port = atoi(value); + break; + case MAX_PORT_OPT: + turn_params.max_port = atoi(value); + break; + case SECURE_STUN_OPT: + turn_params.secure_stun = get_bool_value(value); + break; + case SHA256_OPT: + if(get_bool_value(value)) + turn_params.shatype = SHATYPE_SHA256; + else + turn_params.shatype = SHATYPE_SHA1; + break; + case NO_MULTICAST_PEERS_OPT: + turn_params.no_multicast_peers = get_bool_value(value); + break; + case NO_LOOPBACK_PEERS_OPT: + turn_params.no_loopback_peers = get_bool_value(value); + break; + case STALE_NONCE_OPT: + turn_params.stale_nonce = get_bool_value(value); + break; + case MAX_ALLOCATE_TIMEOUT_OPT: + TURN_MAX_ALLOCATE_TIMEOUT = atoi(value); + TURN_MAX_ALLOCATE_TIMEOUT_STUN_ONLY = atoi(value); + break; + case 'S': + turn_params.stun_only = get_bool_value(value); + break; + case NO_STUN_OPT: + turn_params.no_stun = get_bool_value(value); + break; + case 'L': + add_listener_addr(value); + break; + case 'E': + add_relay_addr(value); + break; + case 'X': + if(value) { + char *div = strchr(value,'/'); + if(div) { + char *nval=strdup(value); + div = strchr(nval,'/'); + div[0]=0; + ++div; + ioa_addr apub,apriv; + if(make_ioa_addr((const u08bits*)nval,0,&apub)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"-X : Wrong address format: %s\n",nval); + } else { + if(make_ioa_addr((const u08bits*)div,0,&apriv)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"-X : Wrong address format: %s\n",div); + } else { + ioa_addr_add_mapping(&apub,&apriv); + } + } + turn_free(nval,strlen(nval)+1); + } else { + if(turn_params.external_ip) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "You cannot define external IP more than once in the configuration\n"); + } else { + turn_params.external_ip = (ioa_addr*)allocate_super_memory_engine(turn_params.listener.ioa_eng, sizeof(ioa_addr)); + if(make_ioa_addr((const u08bits*)value,0,turn_params.external_ip)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"-X : Wrong address format: %s\n",value); + turn_free(turn_params.external_ip,sizeof(ioa_addr)); + turn_params.external_ip = NULL; + } + } + } + } + break; + case 'v': + if(get_bool_value(value)) { + turn_params.verbose = TURN_VERBOSE_NORMAL; + } else { + turn_params.verbose = TURN_VERBOSE_NONE; + } + break; + case 'V': + if(get_bool_value(value)) { + turn_params.verbose = TURN_VERBOSE_EXTRA; + } + break; + case 'o': + turn_params.turn_daemon = get_bool_value(value); + break; + case 'a': + if (get_bool_value(value)) { + turn_params.ct = TURN_CREDENTIALS_LONG_TERM; + use_lt_credentials=1; + } else { + turn_params.ct = TURN_CREDENTIALS_UNDEFINED; + use_lt_credentials=0; + } + break; + case 'A': + if (get_bool_value(value)) { + turn_params.ct = TURN_CREDENTIALS_SHORT_TERM; + use_st_credentials=1; + } else { + turn_params.ct = TURN_CREDENTIALS_UNDEFINED; + use_st_credentials=0; + } + break; + case 'z': + if (!get_bool_value(value)) { + turn_params.ct = TURN_CREDENTIALS_UNDEFINED; + anon_credentials = 0; + } else { + turn_params.ct = TURN_CREDENTIALS_NONE; + anon_credentials = 1; + } + break; + case 'f': + turn_params.fingerprint = get_bool_value(value); + break; + case 'u': + add_user_account(value,0); + break; + case 'b': + STRCPY(turn_params.default_users_db.persistent_users_db.userdb, value); + turn_params.default_users_db.userdb_type = TURN_USERDB_TYPE_FILE; + break; +#if !defined(TURN_NO_PQ) + case 'e': + STRCPY(turn_params.default_users_db.persistent_users_db.userdb, value); + turn_params.default_users_db.userdb_type = TURN_USERDB_TYPE_PQ; + break; +#endif +#if !defined(TURN_NO_MYSQL) + case 'M': + STRCPY(turn_params.default_users_db.persistent_users_db.userdb, value); + turn_params.default_users_db.userdb_type = TURN_USERDB_TYPE_MYSQL; + break; +#endif +#if !defined(TURN_NO_HIREDIS) + case 'N': + STRCPY(turn_params.default_users_db.persistent_users_db.userdb, value); + turn_params.default_users_db.userdb_type = TURN_USERDB_TYPE_REDIS; + break; + case 'O': + STRCPY(turn_params.redis_statsdb, value); + turn_params.use_redis_statsdb = 1; + break; +#endif + case AUTH_SECRET_OPT: + turn_params.use_auth_secret_with_timestamp = 1; + break; + case STATIC_AUTH_SECRET_VAL_OPT: + add_to_secrets_list(&turn_params.default_users_db.ram_db.static_auth_secrets,value); + turn_params.use_auth_secret_with_timestamp = 1; + break; + case AUTH_SECRET_TS_EXP: + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "WARNING: Option --secret-ts-exp-time deprecated and has no effect.\n"); + break; + case 'r': + set_default_realm_name(value); + break; + case 'q': + turn_params.total_quota = (vint)atoi(value); + get_realm(NULL)->options.perf_options.user_quota = atoi(value); + break; + case 'Q': + turn_params.user_quota = (vint)atoi(value); + get_realm(NULL)->options.perf_options.total_quota = atoi(value); + break; + case 's': + turn_params.max_bps = (band_limit_t)atoi(value); + get_realm(NULL)->options.perf_options.max_bps = atoi(value); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%lu bytes per second allowed per session\n",(unsigned long)turn_params.max_bps); + break; + case NO_UDP_OPT: + turn_params.no_udp = get_bool_value(value); + break; + case NO_TCP_OPT: + turn_params.no_tcp = get_bool_value(value); + break; + case NO_UDP_RELAY_OPT: + turn_params.no_udp_relay = get_bool_value(value); + break; + case NO_TCP_RELAY_OPT: + turn_params.no_tcp_relay = get_bool_value(value); + break; + case NO_TLS_OPT: +#if defined(TURN_NO_TLS) + turn_params.no_tls = 1; +#else + turn_params.no_tls = get_bool_value(value); +#endif + break; + case NO_DTLS_OPT: +#if !defined(TURN_NO_DTLS) + turn_params.no_dtls = get_bool_value(value); +#else + turn_params.no_dtls = 1; +#endif + break; + case CERT_FILE_OPT: + STRCPY(turn_params.cert_file,value); + break; + case CA_FILE_OPT: + STRCPY(turn_params.ca_cert_file,value); + break; + case DH_FILE_OPT: + STRCPY(turn_params.dh_file,value); + break; + case PKEY_FILE_OPT: + STRCPY(turn_params.pkey_file,value); + break; + case PKEY_PWD_OPT: + STRCPY(turn_params.tls_password,value); + break; + case ALTERNATE_SERVER_OPT: + add_alternate_server(value); + break; + case AUX_SERVER_OPT: + add_aux_server(value); + break; + case UDP_SELF_BALANCE_OPT: + turn_params.udp_self_balance = get_bool_value(value); + break; + case TLS_ALTERNATE_SERVER_OPT: + add_tls_alternate_server(value); + break; + case ALLOWED_PEER_IPS: + if (add_ip_list_range(value, &turn_params.ip_whitelist) == 0) TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "White listing: %s\n", value); + break; + case DENIED_PEER_IPS: + if (add_ip_list_range(value, &turn_params.ip_blacklist) == 0) TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Black listing: %s\n", value); + break; + case CIPHER_LIST_OPT: + STRCPY(turn_params.cipher_list,value); + break; + case PIDFILE_OPT: + STRCPY(turn_params.pidfile,value); + break; + case 'C': + if(value && *value) { + turn_params.rest_api_separator=*value; + } + break; + /* these options have been already taken care of before: */ + case 'l': + case NO_STDOUT_LOG_OPT: + case SYSLOG_OPT: + case SIMPLE_LOG_OPT: + case 'c': + case 'n': + case 'h': + break; + default: + fprintf(stderr,"\n%s\n", Usage); + exit(-1); + } +} + +static int parse_arg_string(char *sarg, int *c, char **value) +{ + int i = 0; + char *name = sarg; + while(*sarg) { + if((*sarg==' ') || (*sarg=='=') || (*sarg=='\t')) { + *sarg=0; + do { + ++sarg; + } while((*sarg==' ') || (*sarg=='=') || (*sarg=='\t')); + *value = sarg; + break; + } + ++sarg; + *value=sarg; + } + + + if(value && *value && **value=='\"') { + *value += 1; + size_t len = strlen(*value); + while(len>0 && ( + ((*value)[len-1]=='\n') || + ((*value)[len-1]=='\r') || + ((*value)[len-1]==' ') || + ((*value)[len-1]=='\t') + ) ) { + (*value)[--len]=0; + } + if(len>0 && (*value)[len-1]=='\"') { + (*value)[--len]=0; + } + } + + while(long_options[i].name) { + if(strcmp(long_options[i].name,name)) { + ++i; + continue; + } + *c=long_options[i].val; + return 0; + } + + return -1; +} + +static void read_config_file(int argc, char **argv, int pass) +{ + static char config_file[1025] = DEFAULT_CONFIG_FILE; + + if(pass == 0) { + + if (argv) { + int i = 0; + for (i = 0; i < argc; i++) { + if (!strcmp(argv[i], "-c")) { + if (i < argc - 1) { + STRCPY(config_file, argv[i + 1]); + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "Wrong usage of -c option\n"); + } + } else if (!strcmp(argv[i], "-n")) { + turn_params.do_not_use_config_file = 1; + config_file[0]=0; + return; + } else if (!strcmp(argv[i], "-h")) { + printf("\n%s\n",Usage); + exit(0); + } + } + } + } + + if (!turn_params.do_not_use_config_file && config_file[0]) { + + FILE *f = NULL; + char *full_path_to_config_file = NULL; + + full_path_to_config_file = find_config_file(config_file, 1); + if (full_path_to_config_file) + f = fopen(full_path_to_config_file, "r"); + + if (f && full_path_to_config_file) { + + char sbuf[1025]; + char sarg[1035]; + + for (;;) { + char *s = fgets(sbuf, sizeof(sbuf) - 1, f); + if (!s) + break; + s = skip_blanks(s); + if (s[0] == '#') + continue; + if (!s[0]) + continue; + size_t slen = strlen(s); + while (slen && ((s[slen - 1] == 10) || (s[slen - 1] == 13))) + s[--slen] = 0; + if (slen) { + int c = 0; + char *value = NULL; + STRCPY(sarg, s); + if (parse_arg_string(sarg, &c, &value) < 0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "Bad configuration format: %s\n", + sarg); + } else if((pass == 0) && (c == 'l')) { + set_logfile(value); + } else if((pass==0) && (c==NO_STDOUT_LOG_OPT)) { + set_no_stdout_log(get_bool_value(value)); + } else if((pass==0) && (c==SYSLOG_OPT)) { + set_log_to_syslog(get_bool_value(value)); + } else if((pass==0) && (c==SIMPLE_LOG_OPT)) { + set_simple_log(get_bool_value(value)); + } else if((pass == 0) && (c != 'u')) { + set_option(c, value); + } else if((pass > 0) && (c == 'u')) { + set_option(c, value); + } + } + } + + fclose(f); + + } else + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "WARNING: Cannot find config file: %s. Default and command-line settings will be used.\n", + config_file); + } +} + +static int adminmain(int argc, char **argv) +{ + int c = 0; + + TURNADMIN_COMMAND_TYPE ct = TA_COMMAND_UNKNOWN; + int is_st = 0; + + u08bits user[STUN_MAX_USERNAME_SIZE+1]=""; + u08bits realm[STUN_MAX_REALM_SIZE+1]=""; + u08bits pwd[STUN_MAX_PWD_SIZE+1]=""; + u08bits secret[AUTH_SECRET_SIZE+1]=""; + u08bits origin[STUN_MAX_ORIGIN_SIZE+1]=""; + perf_options_t po = {-1,-1,-1}; + + while (((c = getopt_long(argc, argv, ADMIN_OPTIONS, admin_long_options, NULL)) != -1)) { + switch (c){ + case 'g': + ct = TA_SET_REALM_OPTION; + break; + case 'G': + ct = TA_LIST_REALM_OPTIONS; + break; + case ADMIN_USER_QUOTA_OPT: + po.user_quota = (vint)atoi(optarg); + break; + case ADMIN_TOTAL_QUOTA_OPT: + po.total_quota = (vint)atoi(optarg); + break; + case ADMIN_MAX_BPS_OPT: + po.max_bps = (vint)atoi(optarg); + break; + case 'O': + ct = TA_ADD_ORIGIN; + break; + case 'R': + ct = TA_DEL_ORIGIN; + break; + case 'I': + ct = TA_LIST_ORIGINS; + break; + case 'o': + STRCPY(origin,optarg); + break; + case 'k': + ct = TA_PRINT_KEY; + break; + case 'a': + ct = TA_UPDATE_USER; + break; + case 'd': + ct = TA_DELETE_USER; + break; + case 'A': + ct = TA_UPDATE_USER; + is_st = 1; + break; + case 'D': + ct = TA_DELETE_USER; + is_st = 1; + break; + case 'l': + ct = TA_LIST_USERS; + break; + case 'L': + ct = TA_LIST_USERS; + is_st = 1; + break; +#if !defined(TURN_NO_PQ) || !defined(TURN_NO_MYSQL) || !defined(TURN_NO_HIREDIS) + case 's': + ct = TA_SET_SECRET; + STRCPY(secret,optarg); + break; + case 'S': + ct = TA_SHOW_SECRET; + break; + case 'X': + ct = TA_DEL_SECRET; + if(optarg) + STRCPY(secret,optarg); + break; + case DEL_ALL_AUTH_SECRETS_OPT: + ct = TA_DEL_SECRET; + break; +#endif + case 'b': + STRCPY(turn_params.default_users_db.persistent_users_db.userdb,optarg); + turn_params.default_users_db.userdb_type = TURN_USERDB_TYPE_FILE; + break; +#if !defined(TURN_NO_PQ) + case 'e': + STRCPY(turn_params.default_users_db.persistent_users_db.userdb,optarg); + turn_params.default_users_db.userdb_type = TURN_USERDB_TYPE_PQ; + break; +#endif +#if !defined(TURN_NO_MYSQL) + case 'M': + STRCPY(turn_params.default_users_db.persistent_users_db.userdb,optarg); + turn_params.default_users_db.userdb_type = TURN_USERDB_TYPE_MYSQL; + break; +#endif +#if !defined(TURN_NO_HIREDIS) + case 'N': + STRCPY(turn_params.default_users_db.persistent_users_db.userdb,optarg); + turn_params.default_users_db.userdb_type = TURN_USERDB_TYPE_REDIS; + break; +#endif + case 'u': + STRCPY(user,optarg); + if(SASLprep((u08bits*)user)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong user name: %s\n",user); + exit(-1); + } + break; + case 'r': + set_default_realm_name(optarg); + STRCPY(realm,optarg); + if(SASLprep((u08bits*)realm)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong realm: %s\n",realm); + exit(-1); + } + break; + case 'p': + STRCPY(pwd,optarg); + if(SASLprep((u08bits*)pwd)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong password: %s\n",pwd); + exit(-1); + } + break; + case 'H': + if(get_bool_value(optarg)) + turn_params.shatype = SHATYPE_SHA256; + else + turn_params.shatype = SHATYPE_SHA1; + break; + case 'h': + printf("\n%s\n", AdminUsage); + exit(0); + break; + default: + fprintf(stderr,"\n%s\n", AdminUsage); + exit(-1); + } + } + + if(is_st && (turn_params.default_users_db.userdb_type == TURN_USERDB_TYPE_FILE)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "ERROR: you have to use a PostgreSQL or MySQL database with short-term credentials\n"); + exit(-1); + } + + if(!strlen(turn_params.default_users_db.persistent_users_db.userdb) && (turn_params.default_users_db.userdb_type == TURN_USERDB_TYPE_FILE)) + STRCPY(turn_params.default_users_db.persistent_users_db.userdb,DEFAULT_USERDB_FILE); + + if(ct == TA_COMMAND_UNKNOWN) { + fprintf(stderr,"\n%s\n", AdminUsage); + exit(-1); + } + + argc -= optind; + argv += optind; + + if(argc != 0) { + fprintf(stderr,"\n%s\n", AdminUsage); + exit(-1); + } + + return adminuser(user, realm, pwd, secret, origin, ct, is_st, &po); +} + +static void print_features(unsigned long mfn) +{ + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "\nRFC 3489/5389/5766/5780/6062/6156 STUN/TURN Server\nVersion %s\n",TURN_SOFTWARE); + + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "\nMax number of open files/sockets allowed for this process: %lu\n",mfn); + if(turn_params.net_engine_version == 1) + mfn = mfn/3; + else + mfn = mfn/2; + mfn = ((unsigned long)(mfn/500))*500; + if(mfn<500) + mfn = 500; + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "\nDue to the open files/sockets limitation,\nmax supported number of TURN Sessions possible is: %lu (approximately)\n",mfn); + + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "\n\n==== Show him the instruments, Practical Frost: ====\n\n"); + +#if defined(TURN_NO_TLS) + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "TLS is not supported\n"); +#else + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "TLS supported\n"); +#endif + +#if defined(TURN_NO_DTLS) + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "DTLS is not supported\n"); +#else + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "DTLS supported\n"); +#endif + +#if !defined(TURN_NO_HIREDIS) + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Redis supported\n"); +#else + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Redis is not supported\n"); +#endif + +#if !defined(TURN_NO_PQ) + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "PostgreSQL supported\n"); +#else + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "PostgreSQL is not supported\n"); +#endif + +#if !defined(TURN_NO_MYSQL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "MySQL supported\n"); +#else + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "MySQL is not supported\n"); +#endif + +#if defined(OPENSSL_THREADS) + //TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "OpenSSL multithreading supported\n"); +#else + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "OpenSSL multithreading is not supported (?!)\n"); +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x10000000L + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "OpenSSL version: fresh enough\n"); +#else + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "OpenSSL version: antique\n"); +#endif + + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Default Net Engine version: %d (%s)\n\n=====================================================\n\n", (int)turn_params.net_engine_version, turn_params.net_engine_version_txt[(int)turn_params.net_engine_version]); + +} + +#if defined(__linux__) || defined(__LINUX__) || defined(__linux) || defined(linux__) || defined(LINUX) || defined(__LINUX) || defined(LINUX__) +#include +#endif + +static void set_network_engine(void) +{ + if(turn_params.net_engine_version != NEV_UNKNOWN) + return; + turn_params.net_engine_version = NEV_UDP_SOCKET_PER_ENDPOINT; +#if defined(SO_REUSEPORT) +#if defined(__linux__) || defined(__LINUX__) || defined(__linux) || defined(linux__) || defined(LINUX) || defined(__LINUX) || defined(LINUX__) + turn_params.net_engine_version = NEV_UDP_SOCKET_PER_THREAD; +#else /* BSD ? */ + turn_params.net_engine_version = NEV_UDP_SOCKET_PER_SESSION; +#endif /* Linux */ +#else /* defined(SO_REUSEPORT) */ +#if defined(__linux__) || defined(__LINUX__) || defined(__linux) || defined(linux__) || defined(LINUX) || defined(__LINUX) || defined(LINUX__) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,33) + //net_engine_version = NEV_UDP_SOCKET_PER_SESSION; + turn_params.net_engine_version = NEV_UDP_SOCKET_PER_ENDPOINT; +#else + turn_params.net_engine_version = NEV_UDP_SOCKET_PER_ENDPOINT; +#endif /* Linux version */ +#endif /* Linux */ +#endif /* defined(SO_REUSEPORT) */ + +} + +static void drop_privileges(void) +{ + if(procgroupid_set) { + if(getgid() != procgroupid) { + if (setgid(procgroupid) != 0) { + perror("setgid: Unable to change group privileges"); + exit(-1); + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "New GID: %s(%lu)\n", procgroupname, (unsigned long)procgroupid); + } + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Keep GID: %s(%lu)\n", procgroupname, (unsigned long)procgroupid); + } + } + + if(procuserid_set) { + if(procuserid != getuid()) { + if (setuid(procuserid) != 0) { + perror("setuid: Unable to change user privileges"); + exit(-1); + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "New UID: %s(%lu)\n", procusername, (unsigned long)procuserid); + } + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Keep UID: %s(%lu)\n", procusername, (unsigned long)procuserid); + } + } +} + +int main(int argc, char **argv) +{ + int c = 0; + + IS_TURN_SERVER = 1; + + set_execdir(); + + init_super_memory(); + +#if !defined(TURN_NO_HIREDIS) + redis_async_init(); +#endif + + create_new_realm(NULL); + + init_turn_server_addrs_list(&turn_params.alternate_servers_list); + init_turn_server_addrs_list(&turn_params.tls_alternate_servers_list); + init_turn_server_addrs_list(&turn_params.aux_servers_list); + + set_network_engine(); + + init_listener(); + init_secrets_list(&turn_params.default_users_db.ram_db.static_auth_secrets); + init_dynamic_ip_lists(); + + if (!strstr(argv[0], "turnadmin")) { + while (((c = getopt_long(argc, argv, OPTIONS, long_options, NULL)) != -1)) { + switch (c){ + case 'l': + set_logfile(optarg); + break; + case NO_STDOUT_LOG_OPT: + set_no_stdout_log(get_bool_value(optarg)); + break; + case SYSLOG_OPT: + set_log_to_syslog(get_bool_value(optarg)); + break; + case SIMPLE_LOG_OPT: + set_simple_log(get_bool_value(optarg)); + break; + default: + ; + } + } + } + + optind = 0; + +#if defined(TURN_NO_TLS) + turn_params.no_tls = 1; +#endif + +#if defined(TURN_NO_DTLS) + turn_params.no_dtls = 1; +#endif + +#if defined(_SC_NPROCESSORS_ONLN) + + { + long cpus = (long)sysconf(_SC_NPROCESSORS_CONF); + + if(cpus<1) + cpus = 1; + else if(cpus>MAX_NUMBER_OF_GENERAL_RELAY_SERVERS) + cpus = MAX_NUMBER_OF_GENERAL_RELAY_SERVERS; + + turn_params.general_relay_servers_number = (turnserver_id)cpus; + } + +#endif + + ns_bzero(&turn_params.default_users_db,sizeof(default_users_db_t)); + turn_params.default_users_db.ram_db.static_accounts = ur_string_map_create(free); + turn_params.default_users_db.ram_db.dynamic_accounts = ur_string_map_create(free); + + if(strstr(argv[0],"turnadmin")) + return adminmain(argc,argv); + + { + unsigned long mfn = set_system_parameters(1); + + print_features(mfn); + } + + read_config_file(argc,argv,0); + + while (((c = getopt_long(argc, argv, OPTIONS, long_options, NULL)) != -1)) { + if(c != 'u') + set_option(c,optarg); + } + + read_config_file(argc,argv,1); + + optind = 0; + + while (((c = getopt_long(argc, argv, OPTIONS, long_options, NULL)) != -1)) { + if(c == 'u') { + set_option(c,optarg); + } + } + + if(turn_params.no_udp_relay && turn_params.no_tcp_relay) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "\nCONFIG ERROR: --no-udp-relay and --no-tcp-relay options cannot be used together.\n"); + exit(-1); + } + + if(turn_params.no_udp_relay) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "\nCONFIG: --no-udp-relay: UDP relay endpoints are not allowed.\n"); + } + + if(turn_params.no_tcp_relay) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "\nCONFIG: --no-tcp-relay: TCP relay endpoints are not allowed.\n"); + } + + if(turn_params.server_relay) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "\nCONFIG: WARNING: --server-relay: NON-STANDARD AND DANGEROUS OPTION.\n"); + } + + if(!strlen(turn_params.default_users_db.persistent_users_db.userdb) && (turn_params.default_users_db.userdb_type == TURN_USERDB_TYPE_FILE)) + STRCPY(turn_params.default_users_db.persistent_users_db.userdb,DEFAULT_USERDB_FILE); + + read_userdb_file(0); + update_white_and_black_lists(); + + argc -= optind; + argv += optind; + + if(argc>0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "\nCONFIGURATION ALERT: Unknown argument: %s\n",argv[argc-1]); + } + + if(use_lt_credentials && anon_credentials) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "\nCONFIG ERROR: -a and -z options cannot be used together.\n"); + exit(-1); + } + + if(use_st_credentials && anon_credentials) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "\nCONFIG ERROR: -A and -z options cannot be used together.\n"); + exit(-1); + } + + if(use_lt_credentials && use_st_credentials) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "\nCONFIG ERROR: -a and -A options cannot be used together.\n"); + exit(-1); + } + + if(!use_lt_credentials && !anon_credentials && !use_st_credentials) { + if(turn_params.default_users_db.ram_db.users_number) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "\nCONFIGURATION ALERT: you specified long-term user accounts, (-u option) \n but you did not specify the long-term credentials option\n (-a or --lt-cred-mech option).\n I am turning --lt-cred-mech ON for you, but double-check your configuration.\n"); + turn_params.ct = TURN_CREDENTIALS_LONG_TERM; + use_lt_credentials=1; + } else { + turn_params.ct = TURN_CREDENTIALS_NONE; + use_lt_credentials=0; + } + } + + if(use_lt_credentials) { + if(!turn_params.default_users_db.ram_db.users_number && (turn_params.default_users_db.userdb_type == TURN_USERDB_TYPE_FILE) && !turn_params.use_auth_secret_with_timestamp) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "\nCONFIGURATION ALERT: you did not specify any user account, (-u option) \n but you did specified a long-term credentials mechanism option (-a option).\n The TURN Server will be inaccessible.\n Check your configuration.\n"); + } else if(!get_realm(NULL)->options.name[0]) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "\nCONFIGURATION ALERT: you did specify the long-term credentials usage\n but you did not specify the default realm option (-r option).\n Check your configuration.\n"); + } + } + + if(anon_credentials) { + if(turn_params.default_users_db.ram_db.users_number) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "\nCONFIGURATION ALERT: you specified user accounts, (-u option) \n but you also specified the anonymous user access option (-z or --no-auth option).\n User accounts will be ignored.\n"); + turn_params.ct = TURN_CREDENTIALS_NONE; + use_lt_credentials=0; + use_st_credentials=0; + } + } + + if(turn_params.use_auth_secret_with_timestamp && use_st_credentials) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "\nCONFIGURATION ERROR: Authentication secret (REST API) cannot be used with short-term credentials mechanism.\n"); + exit(-1); + } + + openssl_setup(); + + int local_listeners = 0; + if (!turn_params.listener.addrs_number) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "NO EXPLICIT LISTENER ADDRESS(ES) ARE CONFIGURED\n"); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "===========Discovering listener addresses: =========\n"); + int maddrs = make_local_listeners_list(); + if((maddrs<1) || !turn_params.listener.addrs_number) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot configure any meaningful IP listener address\n", __FUNCTION__); + fprintf(stderr,"\n%s\n", Usage); + exit(-1); + } + local_listeners = 1; + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "=====================================================\n"); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Total: %d 'real' addresses discovered\n",maddrs); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "=====================================================\n"); + } + + if (!turn_params.relays_number) { + if(!local_listeners && turn_params.listener.addrs_number && turn_params.listener.addrs) { + size_t la = 0; + for(la=0;la0) + exit(0); + if(pid<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "ERROR: Cannot start daemon process\n"); + exit(-1); + } +#else + if(daemon(1,0)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "ERROR: Cannot start daemon process\n"); + exit(-1); + } + reset_rtpprintf(); +#endif + } + + if(turn_params.pidfile[0]) { + + char s[2049]; + FILE *f = fopen(turn_params.pidfile,"w"); + if(f) { + STRCPY(s,turn_params.pidfile); + } else { + snprintf(s,sizeof(s),"Cannot create pid file: %s",turn_params.pidfile); + perror(s); + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "%s\n", s); + + { + const char *pfs[] = {"/var/run/turnserver.pid", + "/var/spool/turnserver.pid", + "/var/turnserver.pid", + "/var/tmp/turnserver.pid", + "/tmp/turnserver.pid", + "turnserver.pid", + NULL}; + const char **ppfs = pfs; + while(*ppfs) { + f = fopen(*ppfs,"w"); + if(f) { + STRCPY(s,*ppfs); + break; + } else { + ++ppfs; + } + } + } + } + + if(f) { + fprintf(f,"%lu\n",(unsigned long)getpid()); + fclose(f); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "pid file created: %s\n", s); + } + } + + setup_server(); + + drop_privileges(); + + run_listener_server(&(turn_params.listener)); + + return 0; +} + +////////// OpenSSL locking //////////////////////////////////////// + +#if defined(OPENSSL_THREADS) + +static pthread_mutex_t* mutex_buf = NULL; + +static void locking_function(int mode, int n, const char *file, int line) { + UNUSED_ARG(file); + UNUSED_ARG(line); + if (mode & CRYPTO_LOCK) + pthread_mutex_lock(&mutex_buf[n]); + else + pthread_mutex_unlock(&mutex_buf[n]); +} + +#if OPENSSL_VERSION_NUMBER >= 0x10000000L +static void id_function(CRYPTO_THREADID *ctid) +{ + CRYPTO_THREADID_set_numeric(ctid, (unsigned long)pthread_self()); +} +#else +static unsigned long id_function(void) +{ + return (unsigned long)pthread_self(); +} +#endif + +#endif + +static int THREAD_setup(void) { + +#if defined(OPENSSL_THREADS) + + int i; + + mutex_buf = (pthread_mutex_t*) turn_malloc(CRYPTO_num_locks() + * sizeof(pthread_mutex_t)); + if (!mutex_buf) + return 0; + for (i = 0; i < CRYPTO_num_locks(); i++) + pthread_mutex_init(&mutex_buf[i], NULL); + +#if OPENSSL_VERSION_NUMBER >= 0x10000000L + CRYPTO_THREADID_set_callback(id_function); +#else + CRYPTO_set_id_callback(id_function); +#endif + + CRYPTO_set_locking_callback(locking_function); +#endif + + return 1; +} + +int THREAD_cleanup(void); +int THREAD_cleanup(void) { + +#if defined(OPENSSL_THREADS) + + int i; + + if (!mutex_buf) + return 0; + +#if OPENSSL_VERSION_NUMBER >= 0x10000000L + CRYPTO_THREADID_set_callback(NULL); +#else + CRYPTO_set_id_callback(NULL); +#endif + + CRYPTO_set_locking_callback(NULL); + for (i = 0; i < CRYPTO_num_locks(); i++) + pthread_mutex_destroy(&mutex_buf[i]); + turn_free(mutex_buf,sizeof(pthread_mutex_t)); + mutex_buf = NULL; + +#endif + + return 1; +} + +static void adjust_key_file_name(char *fn, const char* file_title, int critical) +{ + char *full_path_to_file = NULL; + + if(!fn[0]) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"\nERROR: you must set the %s file parameter\n",file_title); + goto keyerr; + } else { + + full_path_to_file = find_config_file(fn, 1); + { + FILE *f = full_path_to_file ? fopen(full_path_to_file,"r") : NULL; + if(!f) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING,"WARNING: cannot find %s file: %s (1)\n",file_title,fn); + goto keyerr; + } else { + fclose(f); + } + } + + if(!full_path_to_file) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING,"WARNING: cannot find %s file: %s (2)\n",file_title,fn); + goto keyerr; + } + + strncpy(fn,full_path_to_file,sizeof(turn_params.cert_file)-1); + fn[sizeof(turn_params.cert_file)-1]=0; + + if(full_path_to_file) + turn_free(full_path_to_file,strlen(full_path_to_file)+1); + return; + } + + keyerr: + { + if(critical) { + turn_params.no_tls = 1; + turn_params.no_dtls = 1; + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING,"WARNING: cannot start TLS and DTLS listeners because %s file is not set properly\n",file_title); + } + if(full_path_to_file) + turn_free(full_path_to_file,strlen(full_path_to_file)+1); + return; + } +} + +static void adjust_key_file_names(void) +{ + if(turn_params.ca_cert_file[0]) + adjust_key_file_name(turn_params.ca_cert_file,"CA",1); + adjust_key_file_name(turn_params.cert_file,"certificate",1); + adjust_key_file_name(turn_params.pkey_file,"private key",1); + if(turn_params.dh_file[0]) + adjust_key_file_name(turn_params.dh_file,"DH key",0); +} + +static DH *get_dh566(void) { + + unsigned char dh566_p[] = { + 0x36,0x53,0xA8,0x9C,0x3C,0xF1,0xD1,0x1B,0x2D,0xA2,0x64,0xDE, + 0x59,0x3B,0xE3,0x8C,0x27,0x74,0xC2,0xBE,0x9B,0x6D,0x56,0xE7, + 0xDF,0xFF,0x67,0x6A,0xD2,0x0C,0xE8,0x9E,0x52,0x00,0x05,0xB3, + 0x53,0xF7,0x1C,0x41,0xB2,0xAC,0x38,0x16,0x32,0x3A,0x8E,0x90, + 0x6C,0x7E,0xD1,0x44,0xCB,0xF9,0x2D,0x1E,0x4A,0x9A,0x32,0x81, + 0x58,0xE1,0xE1,0x17,0xC1,0x9C,0xF1,0x1E,0x96,0x2D,0x5F + }; + +// -----BEGIN DH PARAMETERS----- +//MEwCRzZTqJw88dEbLaJk3lk744wndMK+m21W59//Z2rSDOieUgAFs1P3HEGyrDgW +//MjqOkGx+0UTL+S0eSpoygVjh4RfBnPEeli1fAgEF +// -----END DH PARAMETERS----- + + unsigned char dh566_g[] = { 0x05 }; + DH *dh; + + if ((dh = DH_new()) == NULL ) + return (NULL ); + dh->p = BN_bin2bn(dh566_p, sizeof(dh566_p), NULL ); + dh->g = BN_bin2bn(dh566_g, sizeof(dh566_g), NULL ); + if ((dh->p == NULL )|| (dh->g == NULL)){ DH_free(dh); return(NULL);} + return (dh); +} + +static DH *get_dh1066(void) { + + unsigned char dh1066_p[] = { + 0x02,0x0E,0x26,0x6F,0xAA,0x9F,0xA8,0xE5,0x3F,0x70,0x88,0xF1, + 0xA9,0x29,0xAE,0x1A,0x2B,0xA8,0x2F,0xE8,0xE5,0x0E,0x81,0x78, + 0xD7,0x12,0x41,0xDC,0xE2,0xD5,0x10,0x6F,0x8A,0x35,0x23,0xCE, + 0x66,0x93,0x67,0x14,0xEA,0x0A,0x61,0xD4,0x43,0x63,0x5C,0xDF, + 0xDE,0xF5,0xB9,0xC6,0xB4,0x8C,0xBA,0x1A,0x25,0x9F,0x73,0x0F, + 0x1E,0x1A,0x97,0x42,0x2E,0x60,0x9E,0x4C,0x3C,0x70,0x6A,0xFB, + 0xDD,0xAA,0x7A,0x48,0xA5,0x1E,0x87,0xC8,0xA3,0x5E,0x26,0x40, + 0x1B,0xDE,0x08,0x5E,0xA2,0xB8,0xE8,0x76,0x43,0xE8,0xF1,0x4B, + 0x35,0x4C,0x38,0x92,0xB9,0xFF,0x61,0xE6,0x6C,0xBA,0xF9,0x16, + 0x36,0x3C,0x69,0x2D,0x57,0x90,0x62,0x8A,0xD0,0xD4,0xFB,0xB2, + 0x5A,0x61,0x99,0xA9,0xE8,0x93,0x80,0xA2,0xB7,0xDC,0xB1,0x6A, + 0xAF,0xE3 + }; + +// -----BEGIN DH PARAMETERS----- +// MIGMAoGGAg4mb6qfqOU/cIjxqSmuGiuoL+jlDoF41xJB3OLVEG+KNSPOZpNnFOoK +// YdRDY1zf3vW5xrSMuholn3MPHhqXQi5gnkw8cGr73ap6SKUeh8ijXiZAG94IXqK4 +// 6HZD6PFLNUw4krn/YeZsuvkWNjxpLVeQYorQ1PuyWmGZqeiTgKK33LFqr+MCAQI= +// -----END DH PARAMETERS----- + + unsigned char dh1066_g[] = { 0x02 }; + DH *dh; + + if ((dh = DH_new()) == NULL ) + return (NULL ); + dh->p = BN_bin2bn(dh1066_p, sizeof(dh1066_p), NULL ); + dh->g = BN_bin2bn(dh1066_g, sizeof(dh1066_g), NULL ); + if ((dh->p == NULL )|| (dh->g == NULL)){ DH_free(dh); return(NULL);} + return (dh); +} + +static DH *get_dh2066(void) { + + unsigned char dh2066_p[] = { + 0x03,0x31,0x77,0x20,0x58,0xA6,0x69,0xA3,0x9D,0x2D,0x5E,0xE0, + 0x5C,0x46,0x82,0x0F,0x9E,0x80,0xF0,0x00,0x2A,0xF9,0x0F,0x62, + 0x1F,0x89,0xCE,0x7D,0x2A,0xFD,0xC5,0x9A,0x7C,0x6A,0x60,0x2C, + 0xF1,0xDD,0xD4,0x4D,0x6B,0xCD,0xE9,0x95,0xDB,0x42,0x97,0xBA, + 0xE4,0xAF,0x41,0x38,0x8F,0x57,0x31,0xA4,0x39,0xDD,0x31,0xC3, + 0x6F,0x98,0x0E,0xE3,0xB1,0x43,0xD1,0x36,0xB0,0x01,0x28,0x42, + 0x71,0xD3,0xB0,0x36,0xA0,0x47,0x99,0x25,0x9B,0x32,0xF5,0x86, + 0xB1,0x13,0x5C,0x24,0x8D,0x8D,0x7F,0xE2,0x7F,0x9A,0xC1,0x52, + 0x58,0xC0,0x63,0xAA,0x00,0x7C,0x1F,0x11,0xBD,0xAC,0x4C,0x2D, + 0xE0,0xA2,0x9D,0x4E,0x21,0xE4,0x0B,0xCD,0x24,0x92,0xD2,0x37, + 0x27,0x84,0x59,0x90,0x46,0x2F,0xD5,0xB9,0x27,0x93,0x18,0x88, + 0xBD,0x91,0x5B,0x87,0x55,0x56,0xD8,0x1B,0xE4,0xCF,0x1C,0xAA, + 0xBC,0xCF,0x80,0x1E,0x35,0x2D,0xB1,0xBC,0x35,0x31,0x92,0x62, + 0x3C,0x91,0x8D,0x62,0xDA,0xCF,0x83,0x63,0x12,0x4B,0x30,0x80, + 0xEE,0x82,0x3C,0x2C,0xD2,0x17,0x13,0x1F,0xF9,0x62,0x33,0x5C, + 0x63,0xD8,0x75,0x5B,0xAA,0x16,0x5A,0x36,0x49,0x17,0x77,0xB7, + 0x74,0xBD,0x3E,0x3F,0x98,0x20,0x59,0x5E,0xC7,0x72,0xE8,0xA3, + 0x89,0x21,0xB4,0x3C,0x25,0xF4,0xF4,0x21,0x96,0x5A,0xA6,0x77, + 0xFF,0x2C,0x3A,0xFC,0x98,0x5F,0xC1,0xBF,0x2A,0xCF,0xB8,0x62, + 0x67,0x23,0xE8,0x2F,0xCC,0x7B,0x32,0x1B,0x6B,0x33,0x67,0x0A, + 0xCB,0xD0,0x1F,0x65,0xD7,0x84,0x54,0xF6,0xF1,0x88,0xB5,0xBB, + 0x0C,0x63,0x65,0x34,0xE4,0x66,0x4B + }; + +// -----BEGIN DH PARAMETERS----- +//MIIBCgKCAQMDMXcgWKZpo50tXuBcRoIPnoDwACr5D2Ific59Kv3FmnxqYCzx3dRN +//a83pldtCl7rkr0E4j1cxpDndMcNvmA7jsUPRNrABKEJx07A2oEeZJZsy9YaxE1wk +//jY1/4n+awVJYwGOqAHwfEb2sTC3gop1OIeQLzSSS0jcnhFmQRi/VuSeTGIi9kVuH +//VVbYG+TPHKq8z4AeNS2xvDUxkmI8kY1i2s+DYxJLMIDugjws0hcTH/liM1xj2HVb +//qhZaNkkXd7d0vT4/mCBZXsdy6KOJIbQ8JfT0IZZapnf/LDr8mF/BvyrPuGJnI+gv +//zHsyG2szZwrL0B9l14RU9vGItbsMY2U05GZLAgEF +// -----END DH PARAMETERS----- + + unsigned char dh2066_g[] = { 0x05 }; + DH *dh; + + if ((dh = DH_new()) == NULL ) + return (NULL ); + dh->p = BN_bin2bn(dh2066_p, sizeof(dh2066_p), NULL ); + dh->g = BN_bin2bn(dh2066_g, sizeof(dh2066_g), NULL ); + if ((dh->p == NULL )|| (dh->g == NULL)){ DH_free(dh); return(NULL);} + return (dh); +} + +static int pem_password_func(char *buf, int size, int rwflag, void *password) +{ + UNUSED_ARG(rwflag); + + strncpy(buf, (char * )(password), size); + buf[size - 1] = 0; + return (strlen(buf)); +} + +static void set_ctx(SSL_CTX* ctx, const char *protocol) +{ + SSL_CTX_set_default_passwd_cb_userdata(ctx, turn_params.tls_password); + + SSL_CTX_set_default_passwd_cb(ctx, pem_password_func); + + if(!(turn_params.cipher_list[0])) + STRCPY(turn_params.cipher_list,DEFAULT_CIPHER_LIST); + + SSL_CTX_set_cipher_list(ctx, turn_params.cipher_list); + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); + + if (!SSL_CTX_use_certificate_chain_file(ctx, turn_params.cert_file)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: ERROR: no certificate found\n", protocol); + } else { + print_abs_file_name(protocol, ": Certificate", turn_params.cert_file); + } + + if (!SSL_CTX_use_PrivateKey_file(ctx, turn_params.pkey_file, SSL_FILETYPE_PEM)) { + if (!SSL_CTX_use_RSAPrivateKey_file(ctx, turn_params.pkey_file, SSL_FILETYPE_PEM)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: ERROR: no valid private key found, or invalid private key password provided\n", protocol); + } else { + print_abs_file_name(protocol, ": Private RSA key", turn_params.pkey_file); + } + } else { + print_abs_file_name(protocol, ": Private key", turn_params.pkey_file); + } + + if (!SSL_CTX_check_private_key(ctx)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: ERROR: invalid private key\n", protocol); + } + + if(turn_params.ca_cert_file[0]) { + + if (!SSL_CTX_load_verify_locations(ctx, turn_params.ca_cert_file, NULL )) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot load CA from file: %s\n", turn_params.ca_cert_file); + } + + SSL_CTX_set_client_CA_list(ctx,SSL_load_client_CA_file(turn_params.ca_cert_file)); + + /* Set to require peer (client) certificate verification */ + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_CLIENT_ONCE, NULL); + + /* Set the verification depth to 9 */ + SSL_CTX_set_verify_depth(ctx, 9); + + } else { + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); + } + +#if !defined(OPENSSL_NO_EC) && defined(OPENSSL_EC_NAMED_CURVE) + { //Elliptic curve algorithms: + int nid = NID_X9_62_prime256v1; + + if (turn_params.ec_curve_name[0]) { + nid = OBJ_sn2nid(turn_params.ec_curve_name); + if (nid == 0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"unknown curve name (%s), using NID_X9_62_prime256v1\n",turn_params.ec_curve_name); + nid = NID_X9_62_prime256v1; + } + } + + EC_KEY *ecdh = EC_KEY_new_by_curve_name(nid); + if (!ecdh) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: ERROR: allocate EC suite\n"); + } else { + SSL_CTX_set_tmp_ecdh(ctx, ecdh); + EC_KEY_free(ecdh); + } + } +#endif + + {//DH algorithms: + + DH *dh = NULL; + if(turn_params.dh_file[0]) { + FILE *paramfile = fopen(turn_params.dh_file, "r"); + if (!paramfile) { + perror("Cannot open DH file"); + } else { + dh = PEM_read_DHparams(paramfile, NULL, NULL, NULL); + fclose(paramfile); + if(dh) { + turn_params.dh_key_size = DH_CUSTOM; + } + } + } + + if(!dh) { + if(turn_params.dh_key_size == DH_566) + dh = get_dh566(); + else if(turn_params.dh_key_size == DH_2066) + dh = get_dh2066(); + else + dh = get_dh1066(); + } + + if(!dh) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: ERROR: cannot allocate DH suite\n"); + } else { + if (1 != SSL_CTX_set_tmp_dh (ctx, dh)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: ERROR: cannot set DH\n"); + } + DH_free (dh); + } + } + + { + int op = 0; + +#if defined(SSL_OP_NO_SSLv2) + if(turn_params.no_sslv2) + op |= SSL_OP_NO_SSLv2; +#endif + if(turn_params.no_sslv3) + op |= SSL_OP_NO_SSLv3; + + if(turn_params.no_tlsv1) + op |= SSL_OP_NO_TLSv1; + +#if defined(SSL_OP_NO_TLSv1_1) + if(turn_params.no_tlsv1_1) + op |= SSL_OP_NO_TLSv1_1; +#endif +#if defined(SSL_OP_NO_TLSv1_2) + if(turn_params.no_tlsv1_2) + op |= SSL_OP_NO_TLSv1_2; +#endif + +#if defined(SSL_OP_CIPHER_SERVER_PREFERENCE) + op |= SSL_OP_CIPHER_SERVER_PREFERENCE; +#endif + +#if defined(SSL_OP_SINGLE_DH_USE) + op |= SSL_OP_SINGLE_DH_USE; +#endif + +#if defined(SSL_OP_SINGLE_ECDH_USE) + op |= SSL_OP_SINGLE_ECDH_USE; +#endif + + SSL_CTX_set_options(ctx, op); + } +} + +static void openssl_setup(void) +{ + THREAD_setup(); + SSL_load_error_strings(); + OpenSSL_add_ssl_algorithms(); + +#if defined(TURN_NO_TLS) + if(!turn_params.no_tls) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "WARNING: TLS is not supported\n"); + turn_params.no_tls = 1; + } +#endif + + if(!(turn_params.no_tls && turn_params.no_dtls) && !turn_params.cert_file[0]) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING,"\nWARNING: certificate file is not specified, I cannot start TLS/DTLS services.\nOnly 'plain' UDP/TCP listeners can be started.\n"); + turn_params.no_tls = 1; + turn_params.no_dtls = 1; + } + + if(!(turn_params.no_tls && turn_params.no_dtls) && !turn_params.pkey_file[0]) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING,"\nWARNING: private key file is not specified, I cannot start TLS/DTLS services.\nOnly 'plain' UDP/TCP listeners can be started.\n"); + turn_params.no_tls = 1; + turn_params.no_dtls = 1; + } + + if(!(turn_params.no_tls && turn_params.no_dtls)) { + adjust_key_file_names(); + } + + if(!turn_params.no_tls) { + turn_params.tls_ctx_ssl23 = SSL_CTX_new(SSLv23_server_method()); /*compatibility mode */ + set_ctx(turn_params.tls_ctx_ssl23,"SSL23"); + if(!turn_params.no_tlsv1) { + turn_params.tls_ctx_v1_0 = SSL_CTX_new(TLSv1_server_method()); + set_ctx(turn_params.tls_ctx_v1_0,"TLS1.0"); + } +#if defined(SSL_TXT_TLSV1_1) + if(!turn_params.no_tlsv1_1) { + turn_params.tls_ctx_v1_1 = SSL_CTX_new(TLSv1_1_server_method()); + set_ctx(turn_params.tls_ctx_v1_1,"TLS1.1"); + } +#if defined(SSL_TXT_TLSV1_2) + if(!turn_params.no_tlsv1_2) { + turn_params.tls_ctx_v1_2 = SSL_CTX_new(TLSv1_2_server_method()); + set_ctx(turn_params.tls_ctx_v1_2,"TLS1.2"); + } +#endif +#endif + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "TLS cipher suite: %s\n",turn_params.cipher_list); + } + + if(!turn_params.no_dtls) { +#if defined(TURN_NO_DTLS) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "ERROR: DTLS is not supported.\n"); +#else + if(OPENSSL_VERSION_NUMBER < 0x10000000L) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "WARNING: TURN Server was compiled with rather old OpenSSL version, DTLS may not be working correctly.\n"); + } + turn_params.dtls_ctx = SSL_CTX_new(DTLSv1_server_method()); + set_ctx(turn_params.dtls_ctx,"DTLS"); + SSL_CTX_set_read_ahead(turn_params.dtls_ctx, 1); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "DTLS cipher suite: %s\n",turn_params.cipher_list); +#endif + } +} + +/////////////////////////////// diff --git a/src/apps/relay/mainrelay.h b/src/apps/relay/mainrelay.h new file mode 100644 index 0000000..a76c1b3 --- /dev/null +++ b/src/apps/relay/mainrelay.h @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if !defined(__MAIN_RELAY__) +#define __MAIN_RELAY__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "ns_turn_utils.h" +#include "ns_turn_khash.h" + +#include "userdb.h" +#include "turncli.h" + +#include "tls_listener.h" +#include "dtls_listener.h" + +#include "ns_turn_server.h" +#include "ns_turn_maps.h" + +#include "apputils.h" + +#include "ns_ioalib_impl.h" + +#if !defined(TURN_NO_HIREDIS) +#include "hiredis_libevent2.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +////////////// DEFINES //////////////////////////// + +#define DEFAULT_CONFIG_FILE "turnserver.conf" + +#define DEFAULT_CIPHER_LIST "DEFAULT" +/* "ALL:eNULL:aNULL:NULL" */ + +#define DEFAULT_EC_CURVE_NAME "prime256v1" + +#define MAX_NUMBER_OF_GENERAL_RELAY_SERVERS ((u08bits)(0x80)) + +#define TURNSERVER_ID_BOUNDARY_BETWEEN_TCP_AND_UDP MAX_NUMBER_OF_GENERAL_RELAY_SERVERS +#define TURNSERVER_ID_BOUNDARY_BETWEEN_UDP_AND_TCP TURNSERVER_ID_BOUNDARY_BETWEEN_TCP_AND_UDP + +/////////// TYPES /////////////////////////////////// + +enum _DH_KEY_SIZE { + DH_566, + DH_1066, + DH_2066, + DH_CUSTOM +}; + +typedef enum _DH_KEY_SIZE DH_KEY_SIZE; + +///////// LISTENER SERVER TYPES ///////////////////// + +struct message_to_listener_to_client { + ioa_addr origin; + ioa_addr destination; + ioa_network_buffer_handle nbh; +}; + +enum _MESSAGE_TO_LISTENER_TYPE { + LMT_UNKNOWN, + LMT_TO_CLIENT +}; +typedef enum _MESSAGE_TO_LISTENER_TYPE MESSAGE_TO_LISTENER_TYPE; + +struct message_to_listener { + MESSAGE_TO_LISTENER_TYPE t; + union { + struct message_to_listener_to_client tc; + } m; +}; + +struct listener_server { + rtcp_map* rtcpmap; + turnipports* tp; + struct event_base* event_base; + ioa_engine_handle ioa_eng; + struct bufferevent *in_buf; + struct bufferevent *out_buf; + char **addrs; + ioa_addr **encaddrs; + size_t addrs_number; + size_t services_number; + dtls_listener_relay_server_type ***udp_services; + dtls_listener_relay_server_type ***dtls_services; + dtls_listener_relay_server_type ***aux_udp_services; +}; + +enum _NET_ENG_VERSION { + NEV_UNKNOWN=0, + NEV_MIN, + NEV_UDP_SOCKET_PER_SESSION=NEV_MIN, + NEV_UDP_SOCKET_PER_ENDPOINT, + NEV_UDP_SOCKET_PER_THREAD, + NEV_MAX=NEV_UDP_SOCKET_PER_THREAD, + NEV_TOTAL +}; + +typedef enum _NET_ENG_VERSION NET_ENG_VERSION; + +////////////// Auth Server Types //////////////// + +struct auth_server { + struct event_base* event_base; + struct bufferevent *in_buf; + struct bufferevent *out_buf; + pthread_t thr; +#if !defined(TURN_NO_HIREDIS) + redis_context_handle rch; +#endif +}; + +/////////// PARAMS ////////////////////////////////// + +typedef struct _turn_params_ { + +//////////////// OpenSSL group ////////////////////// + + SSL_CTX *tls_ctx_ssl23; + + SSL_CTX *tls_ctx_v1_0; + +#if defined(SSL_TXT_TLSV1_1) + SSL_CTX *tls_ctx_v1_1; +#if defined(SSL_TXT_TLSV1_2) + SSL_CTX *tls_ctx_v1_2; +#endif +#endif + + SSL_CTX *dtls_ctx; + + DH_KEY_SIZE dh_key_size; + + char cipher_list[1025]; + char ec_curve_name[33]; + + char ca_cert_file[1025]; + char cert_file[1025]; + char pkey_file[1025]; + char tls_password[513]; + char dh_file[1025]; + + int no_sslv2; + int no_sslv3; + int no_tlsv1; + int no_tlsv1_1; + int no_tlsv1_2; + int no_tls; + int no_dtls; + +//////////////// Common params //////////////////// + + int verbose; + int turn_daemon; + + int do_not_use_config_file; + + char pidfile[1025]; + + //////////////// Listener server ///////////////// + + int listener_port; + int tls_listener_port; + int alt_listener_port; + int alt_tls_listener_port; + int rfc5780; + + int no_udp; + int no_tcp; + + vint no_tcp_relay; + vint no_udp_relay; + + char listener_ifname[1025]; + +#if !defined(TURN_NO_HIREDIS) + char redis_statsdb[1025]; + int use_redis_statsdb; +#endif + + struct listener_server listener; + + ip_range_list_t ip_whitelist; + ip_range_list_t ip_blacklist; + + NET_ENG_VERSION net_engine_version; + const char* net_engine_version_txt[NEV_TOTAL]; + +//////////////// Relay servers ///////////// + + u16bits min_port; + u16bits max_port; + + vint no_multicast_peers; + vint no_loopback_peers; + + char relay_ifname[1025]; + + size_t relays_number; + char **relay_addrs; + int default_relays; + + // Single global public IP. + // If multiple public IPs are used + // then ioa_addr mapping must be used. + ioa_addr *external_ip; + + turnserver_id general_relay_servers_number; + turnserver_id udp_relay_servers_number; + +////////////// Auth server //////////////// + + struct auth_server authserver; + +/////////////// AUX SERVERS //////////////// + + turn_server_addrs_list_t aux_servers_list; + int udp_self_balance; + +/////////////// ALTERNATE SERVERS //////////////// + + turn_server_addrs_list_t alternate_servers_list; + turn_server_addrs_list_t tls_alternate_servers_list; + + int stop_turn_server; + +////////////// MISC PARAMS //////////////// + + vint stun_only; + vint no_stun; + vint secure_stun; + int server_relay; + int fingerprint; + SHATYPE shatype; + char rest_api_separator; + vint stale_nonce; + vint mobility; + turn_credential_type ct; + int use_auth_secret_with_timestamp; + band_limit_t max_bps; + vint total_quota; + vint user_quota; + +/////// Users DB /////////// + + default_users_db_t default_users_db; + +} turn_params_t; + +extern turn_params_t turn_params; + +//////////////// Listener server ///////////////// + +static inline int get_alt_listener_port(void) { + if(turn_params.alt_listener_port<1) + return turn_params.listener_port + 1; + return turn_params.alt_listener_port; +} + +static inline int get_alt_tls_listener_port(void) { + if(turn_params.alt_tls_listener_port<1) + return turn_params.tls_listener_port + 1; + return turn_params.alt_tls_listener_port; +} + +void add_aux_server(const char *saddr); + +void add_alternate_server(const char *saddr); +void del_alternate_server(const char *saddr); +void add_tls_alternate_server(const char *saddr); +void del_tls_alternate_server(const char *saddr); + +////////// Addrs //////////////////// + +void add_listener_addr(const char* addr); +int add_relay_addr(const char* addr); + +///////// Auth //////////////// + +void send_auth_message_to_auth_server(struct auth_message *am); + +/////////// Setup server //////// + +void init_listener(void); +void setup_server(void); +void run_listener_server(struct listener_server *ls); + +/////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif //__MAIN_RELAY__ diff --git a/src/apps/relay/netengine.c b/src/apps/relay/netengine.c new file mode 100644 index 0000000..326adac --- /dev/null +++ b/src/apps/relay/netengine.c @@ -0,0 +1,1573 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "mainrelay.h" + +//////////// Barrier for the threads ////////////// + +#if !defined(TURN_NO_THREAD_BARRIERS) +static unsigned int barrier_count = 0; +static pthread_barrier_t barrier; +#endif + +////////////////////////////////////////////// + +#define get_real_general_relay_servers_number() (turn_params.general_relay_servers_number > 1 ? turn_params.general_relay_servers_number : 1) +#define get_real_udp_relay_servers_number() (turn_params.udp_relay_servers_number > 1 ? turn_params.udp_relay_servers_number : 1) + +static struct relay_server **general_relay_servers = NULL; +static struct relay_server **udp_relay_servers = NULL; + +////////////////////////////////////////////// + +static void run_events(struct event_base *eb, ioa_engine_handle e); +static void setup_relay_server(struct relay_server *rs, ioa_engine_handle e, int to_set_rfc5780); + +/////////////// BARRIERS /////////////////// + +#if !defined(PTHREAD_BARRIER_SERIAL_THREAD) +#define PTHREAD_BARRIER_SERIAL_THREAD (-1) +#endif + +static void barrier_wait_func(const char* func, int line) +{ +#if !defined(TURN_NO_THREAD_BARRIERS) + int br = 0; + do { + br = pthread_barrier_wait(&barrier); + if ((br < 0)&&(br != PTHREAD_BARRIER_SERIAL_THREAD)) { + int err = errno; + perror("barrier wait"); + printf("%s:%s:%d: %d\n", __FUNCTION__, func,line,err); + } + } while (((br < 0)&&(br != PTHREAD_BARRIER_SERIAL_THREAD)) && (errno == EINTR)); +#else + UNUSED_ARG(func); + UNUSED_ARG(line); + sleep(5); +#endif +} + +#define barrier_wait() barrier_wait_func(__FUNCTION__,__LINE__) + +/////////////// AUX SERVERS //////////////// + +static void add_aux_server_list(const char *saddr, turn_server_addrs_list_t *list) +{ + if(saddr && list) { + ioa_addr addr; + if(make_ioa_addr_from_full_string((const u08bits*)saddr, 0, &addr)!=0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong full address format: %s\n",saddr); + } else { + list->addrs = (ioa_addr*)realloc(list->addrs,sizeof(ioa_addr)*(list->size+1)); + addr_cpy(&(list->addrs[(list->size)++]),&addr); + { + u08bits s[1025]; + addr_to_string(&addr, s); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Aux server: %s\n",s); + } + } + } +} + +void add_aux_server(const char *saddr) +{ + add_aux_server_list(saddr,&turn_params.aux_servers_list); +} + +/////////////// ALTERNATE SERVERS //////////////// + +static void add_alt_server(const char *saddr, int default_port, turn_server_addrs_list_t *list) +{ + if(saddr && list) { + ioa_addr addr; + + turn_mutex_lock(&(list->m)); + + if(make_ioa_addr_from_full_string((const u08bits*)saddr, default_port, &addr)!=0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong IP address format: %s\n",saddr); + } else { + list->addrs = (ioa_addr*)realloc(list->addrs,sizeof(ioa_addr)*(list->size+1)); + addr_cpy(&(list->addrs[(list->size)++]),&addr); + { + u08bits s[1025]; + addr_to_string(&addr, s); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Alternate server added: %s\n",s); + } + } + + turn_mutex_unlock(&(list->m)); + } +} + +static void del_alt_server(const char *saddr, int default_port, turn_server_addrs_list_t *list) +{ + if(saddr && list && list->size && list->addrs) { + + ioa_addr addr; + + turn_mutex_lock(&(list->m)); + + if(make_ioa_addr_from_full_string((const u08bits*)saddr, default_port, &addr)!=0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong IP address format: %s\n",saddr); + } else { + + size_t i; + int found = 0; + for(i=0;isize;++i) { + if(addr_eq(&(list->addrs[i]),&addr)) { + found = 1; + break; + } + } + + if(found) { + + size_t j; + ioa_addr *new_addrs = (ioa_addr*)malloc(sizeof(ioa_addr)*(list->size-1)); + for(j=0;jaddrs[j])); + } + for(j=i;jsize-1;++j) { + addr_cpy(&(new_addrs[j]),&(list->addrs[j+1])); + } + + free(list->addrs); + list->addrs = new_addrs; + list->size -= 1; + + { + u08bits s[1025]; + addr_to_string(&addr, s); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Alternate server removed: %s\n",s); + } + + del_alt_server(saddr, default_port, list); + } + } + + turn_mutex_unlock(&(list->m)); + } +} + +void add_alternate_server(const char *saddr) +{ + add_alt_server(saddr,DEFAULT_STUN_PORT,&turn_params.alternate_servers_list); +} + +void del_alternate_server(const char *saddr) +{ + del_alt_server(saddr,DEFAULT_STUN_PORT,&turn_params.alternate_servers_list); +} + +void add_tls_alternate_server(const char *saddr) +{ + add_alt_server(saddr,DEFAULT_STUN_TLS_PORT,&turn_params.tls_alternate_servers_list); +} + +void del_tls_alternate_server(const char *saddr) +{ + del_alt_server(saddr,DEFAULT_STUN_TLS_PORT,&turn_params.tls_alternate_servers_list); +} + +////////////////////////////////////////////////// + +void add_listener_addr(const char* addr) { + ioa_addr baddr; + if(make_ioa_addr((const u08bits*)addr,0,&baddr)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"Cannot add a listener address: %s\n",addr); + } else { + size_t i = 0; + for(i=0;i=0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, " relay %s initialization...\n",turn_params.relay_addrs[i]); + turnipports_add_ip(STUN_ATTRIBUTE_TRANSPORT_UDP_VALUE, &baddr); + turnipports_add_ip(STUN_ATTRIBUTE_TRANSPORT_TCP_VALUE, &baddr); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, " relay %s initialization done\n",turn_params.relay_addrs[i]); + } + } + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Relay ports initialization done\n"); +} + +////////////////////////////////////////////////// + +// communications between listener and relays ==>> + +static int handle_relay_message(relay_server_handle rs, struct message_to_relay *sm); + +void send_auth_message_to_auth_server(struct auth_message *am) +{ + struct evbuffer *output = bufferevent_get_output(turn_params.authserver.out_buf); + if(evbuffer_add(output,am,sizeof(struct auth_message))<0) { + fprintf(stderr,"%s: Weird buffer error\n",__FUNCTION__); + } +} + +static void auth_server_receive_message(struct bufferevent *bev, void *ptr) +{ + UNUSED_ARG(ptr); + + struct auth_message am; + int n = 0; + struct evbuffer *input = bufferevent_get_input(bev); + + while ((n = evbuffer_remove(input, &am, sizeof(struct auth_message))) > 0) { + if (n != sizeof(struct auth_message)) { + fprintf(stderr,"%s: Weird buffer error: size=%d\n",__FUNCTION__,n); + continue; + } + + if(am.ct == TURN_CREDENTIALS_SHORT_TERM) { + st_password_t pwd; + if(get_user_pwd(am.username,pwd)<0) { + am.success = 0; + } else { + ns_bcopy(pwd,am.pwd,sizeof(st_password_t)); + am.success = 1; + } + } else { + hmackey_t key; + if(get_user_key(am.username,am.realm,key,am.in_buffer.nbh)<0) { + am.success = 0; + } else { + ns_bcopy(key,am.key,sizeof(hmackey_t)); + am.success = 1; + } + } + + size_t dest = am.id; + + struct evbuffer *output = NULL; + + if(dest>=TURNSERVER_ID_BOUNDARY_BETWEEN_TCP_AND_UDP) { + dest -= TURNSERVER_ID_BOUNDARY_BETWEEN_TCP_AND_UDP; + if(dest >= get_real_udp_relay_servers_number()) { + TURN_LOG_FUNC( + TURN_LOG_LEVEL_ERROR, + "%s: Too large UDP relay number: %d\n", + __FUNCTION__,(int)dest); + } else { + output = bufferevent_get_output(udp_relay_servers[dest]->auth_out_buf); + } + } else { + if(dest >= get_real_general_relay_servers_number()) { + TURN_LOG_FUNC( + TURN_LOG_LEVEL_ERROR, + "%s: Too large general relay number: %d\n", + __FUNCTION__,(int)dest); + } else { + output = bufferevent_get_output(general_relay_servers[dest]->auth_out_buf); + } + } + + if(output) + evbuffer_add(output,&am,sizeof(struct auth_message)); + else { + ioa_network_buffer_delete(NULL, am.in_buffer.nbh); + am.in_buffer.nbh = NULL; + } + } +} + +static int send_socket_to_general_relay(ioa_engine_handle e, struct message_to_relay *sm) +{ + struct relay_server *rdest = sm->relay_server; + + if(!rdest) { + size_t dest = (hash_int32(addr_get_port(&(sm->m.sm.nd.src_addr)))) % get_real_general_relay_servers_number(); + rdest = general_relay_servers[dest]; + } + + struct message_to_relay *smptr = sm; + + smptr->t = RMT_SOCKET; + + { + struct evbuffer *output = NULL; + int success = 0; + + output = bufferevent_get_output(rdest->out_buf); + + if(output) { + + if(evbuffer_add(output,smptr,sizeof(struct message_to_relay))<0) { + TURN_LOG_FUNC( + TURN_LOG_LEVEL_ERROR, + "%s: Cannot add message to relay output buffer\n", + __FUNCTION__); + } else { + + success = 1; + smptr->m.sm.nd.nbh=NULL; + } + + } + + if(!success) { + ioa_network_buffer_delete(e, smptr->m.sm.nd.nbh); + smptr->m.sm.nd.nbh=NULL; + + IOA_CLOSE_SOCKET(smptr->m.sm.s); + + return -1; + } + } + + return 0; +} + +static int send_socket_to_relay(turnserver_id id, u64bits cid, stun_tid *tid, ioa_socket_handle s, + int message_integrity, MESSAGE_TO_RELAY_TYPE rmt, ioa_net_data *nd) +{ + int ret = 0; + + struct message_to_relay sm; + ns_bzero(&sm,sizeof(struct message_to_relay)); + sm.t = rmt; + + struct relay_server *rs = NULL; + if(id>=TURNSERVER_ID_BOUNDARY_BETWEEN_TCP_AND_UDP) { + size_t dest = id-TURNSERVER_ID_BOUNDARY_BETWEEN_TCP_AND_UDP; + if(dest >= get_real_udp_relay_servers_number()) { + TURN_LOG_FUNC( + TURN_LOG_LEVEL_ERROR, + "%s: Too large UDP relay number: %d, rmt=%d\n", + __FUNCTION__,(int)dest,(int)rmt); + ret = -1; + goto err; + } + rs = udp_relay_servers[dest]; + } else { + size_t dest = id; + if(dest >= get_real_general_relay_servers_number()) { + TURN_LOG_FUNC( + TURN_LOG_LEVEL_ERROR, + "%s: Too large general relay number: %d, rmt=%d\n", + __FUNCTION__,(int)dest,(int)rmt); + ret = -1; + goto err; + } + rs = general_relay_servers[dest]; + } + + switch (rmt) { + case(RMT_CB_SOCKET): { + + sm.m.cb_sm.id = id; + sm.m.cb_sm.connection_id = (tcp_connection_id)cid; + stun_tid_cpy(&(sm.m.cb_sm.tid),tid); + sm.m.cb_sm.s = s; + sm.m.cb_sm.message_integrity = message_integrity; + + break; + } + case (RMT_MOBILE_SOCKET): { + + if(nd && nd->nbh) { + sm.m.sm.s = s; + addr_cpy(&(sm.m.sm.nd.src_addr),&(nd->src_addr)); + sm.m.sm.nd.recv_tos = nd->recv_tos; + sm.m.sm.nd.recv_ttl = nd->recv_ttl; + sm.m.sm.nd.nbh = nd->nbh; + nd->nbh = NULL; + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: Empty buffer with mobile socket\n",__FUNCTION__); + ret = -1; + } + + break; + } + default: { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: UNKNOWN RMT message: %d\n",__FUNCTION__,(int)rmt); + ret = -1; + } + } + + if(ret == 0) { + + struct evbuffer *output = bufferevent_get_output(rs->out_buf); + if(output) { + evbuffer_add(output,&sm,sizeof(struct message_to_relay)); + } else { + TURN_LOG_FUNC( + TURN_LOG_LEVEL_ERROR, + "%s: Empty output buffer\n", + __FUNCTION__); + ret = -1; + } + } + + err: + if(ret < 0) { + IOA_CLOSE_SOCKET(s); + if(nd) { + ioa_network_buffer_delete(NULL, nd->nbh); + nd->nbh = NULL; + } + if(rmt == RMT_MOBILE_SOCKET) { + ioa_network_buffer_delete(NULL, sm.m.sm.nd.nbh); + sm.m.sm.nd.nbh = NULL; + } + } + + return ret; +} + +static int handle_relay_message(relay_server_handle rs, struct message_to_relay *sm) +{ + if(rs && sm) { + + switch (sm->t) { + + case RMT_SOCKET: { + + if (sm->m.sm.s->defer_nbh) { + if (!sm->m.sm.nd.nbh) { + sm->m.sm.nd.nbh = sm->m.sm.s->defer_nbh; + sm->m.sm.s->defer_nbh = NULL; + } else { + ioa_network_buffer_delete(rs->ioa_eng, sm->m.sm.s->defer_nbh); + sm->m.sm.s->defer_nbh = NULL; + } + } + + ioa_socket_handle s = sm->m.sm.s; + + if (!s) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: socket EMPTY\n",__FUNCTION__); + } else if (s->read_event || s->bev) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: socket wrongly preset: 0x%lx : 0x%lx\n", + __FUNCTION__, (long) s->read_event, (long) s->bev); + IOA_CLOSE_SOCKET(s); + } else { + s->e = rs->ioa_eng; + open_client_connection_session(&(rs->server), &(sm->m.sm)); + } + + ioa_network_buffer_delete(rs->ioa_eng, sm->m.sm.nd.nbh); + sm->m.sm.nd.nbh = NULL; + } + break; + case RMT_CB_SOCKET: + + turnserver_accept_tcp_client_data_connection(&(rs->server), sm->m.cb_sm.connection_id, + &(sm->m.cb_sm.tid), sm->m.cb_sm.s, sm->m.cb_sm.message_integrity); + + break; + case RMT_MOBILE_SOCKET: { + + ioa_socket_handle s = sm->m.sm.s; + + if (!s) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: mobile socket EMPTY\n",__FUNCTION__); + } else if (s->read_event || s->bev) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: mobile socket wrongly preset: 0x%lx : 0x%lx\n", + __FUNCTION__, (long) s->read_event, (long) s->bev); + IOA_CLOSE_SOCKET(s); + } else { + s->e = rs->ioa_eng; + open_client_connection_session(&(rs->server), &(sm->m.sm)); + } + + ioa_network_buffer_delete(rs->ioa_eng, sm->m.sm.nd.nbh); + sm->m.sm.nd.nbh = NULL; + break; + } + default: { + perror("Weird buffer type\n"); + } + } + } + + return 0; +} + +static void handle_relay_auth_message(struct relay_server *rs, struct auth_message *am) +{ + am->resume_func(am->success, am->key, am->pwd, + &(rs->server), am->ctxkey, &(am->in_buffer)); + if (am->in_buffer.nbh) { + ioa_network_buffer_delete(rs->ioa_eng, am->in_buffer.nbh); + am->in_buffer.nbh = NULL; + } +} + +static void relay_receive_message(struct bufferevent *bev, void *ptr) +{ + struct message_to_relay sm; + int n = 0; + struct evbuffer *input = bufferevent_get_input(bev); + struct relay_server *rs = (struct relay_server *)ptr; + + while ((n = evbuffer_remove(input, &sm, sizeof(struct message_to_relay))) > 0) { + + if (n != sizeof(struct message_to_relay)) { + perror("Weird buffer error\n"); + continue; + } + + handle_relay_message(rs, &sm); + } +} + +static void relay_receive_auth_message(struct bufferevent *bev, void *ptr) +{ + struct auth_message am; + int n = 0; + struct evbuffer *input = bufferevent_get_input(bev); + struct relay_server *rs = (struct relay_server *)ptr; + + while ((n = evbuffer_remove(input, &am, sizeof(struct auth_message))) > 0) { + + if (n != sizeof(struct auth_message)) { + perror("Weird auth_buffer error\n"); + continue; + } + + handle_relay_auth_message(rs, &am); + } +} + +static int send_message_from_listener_to_client(ioa_engine_handle e, ioa_network_buffer_handle nbh, ioa_addr *origin, ioa_addr *destination) +{ + + struct message_to_listener mm; + mm.t = LMT_TO_CLIENT; + addr_cpy(&(mm.m.tc.origin),origin); + addr_cpy(&(mm.m.tc.destination),destination); + mm.m.tc.nbh = ioa_network_buffer_allocate(e); + ioa_network_buffer_header_init(mm.m.tc.nbh); + ns_bcopy(ioa_network_buffer_data(nbh),ioa_network_buffer_data(mm.m.tc.nbh),ioa_network_buffer_get_size(nbh)); + ioa_network_buffer_set_size(mm.m.tc.nbh,ioa_network_buffer_get_size(nbh)); + + struct evbuffer *output = bufferevent_get_output(turn_params.listener.out_buf); + + evbuffer_add(output,&mm,sizeof(struct message_to_listener)); + + return 0; +} + +static void listener_receive_message(struct bufferevent *bev, void *ptr) +{ + UNUSED_ARG(ptr); + + struct message_to_listener mm; + int n = 0; + struct evbuffer *input = bufferevent_get_input(bev); + + while ((n = evbuffer_remove(input, &mm, sizeof(struct message_to_listener))) > 0) { + if (n != sizeof(struct message_to_listener)) { + perror("Weird buffer error\n"); + continue; + } + + if (mm.t != LMT_TO_CLIENT) { + perror("Weird buffer type\n"); + continue; + } + + size_t relay_thread_index = 0; + + if(turn_params.net_engine_version == NEV_UDP_SOCKET_PER_THREAD) { + size_t ri; + for(ri=0;rithr == pthread_self()) { + relay_thread_index=ri; + break; + } + } + } + + size_t i; + int found = 0; + for(i=0;i1) { + + /* UDP: */ + if(!turn_params.no_udp) { + + barrier_count += turn_params.listener.addrs_number; + + if(turn_params.rfc5780) { + barrier_count += turn_params.listener.addrs_number; + } + } + + if(!turn_params.no_dtls && (turn_params.no_udp || (turn_params.listener_port != turn_params.tls_listener_port))) { + + barrier_count += turn_params.listener.addrs_number; + + if(turn_params.rfc5780) { + barrier_count += turn_params.listener.addrs_number; + } + } + + if(!turn_params.no_udp || !turn_params.no_dtls) { + barrier_count += (unsigned int)turn_params.aux_servers_list.size; + } + } +#endif + +#if !defined(TURN_NO_THREAD_BARRIERS) + { + if(pthread_barrier_init(&barrier,NULL,barrier_count)<0) + perror("barrier init"); + } + +#endif +} + +static void setup_socket_per_endpoint_udp_listener_servers(void) +{ + size_t i = 0; + + /* Adjust udp relay number */ + + if(turn_params.general_relay_servers_number>1) { + + if (!turn_params.no_udp) { + + turn_params.udp_relay_servers_number += turn_params.listener.addrs_number; + + if (turn_params.rfc5780) { + turn_params.udp_relay_servers_number += turn_params.listener.addrs_number; + } + } + + if (!turn_params.no_dtls && (turn_params.no_udp || (turn_params.listener_port != turn_params.tls_listener_port))) { + + turn_params.udp_relay_servers_number += turn_params.listener.addrs_number; + + if (turn_params.rfc5780) { + turn_params.udp_relay_servers_number += turn_params.listener.addrs_number; + } + } + + if (!turn_params.no_udp || !turn_params.no_dtls) { + turn_params.udp_relay_servers_number += (unsigned int) turn_params.aux_servers_list.size; + } + } + + { + if (!turn_params.no_udp || !turn_params.no_dtls) { + udp_relay_servers = (struct relay_server**) allocate_super_memory_engine(turn_params.listener.ioa_eng, sizeof(struct relay_server *)*get_real_udp_relay_servers_number()); + + for (i = 0; i < get_real_udp_relay_servers_number(); i++) { + + ioa_engine_handle e = turn_params.listener.ioa_eng; + int is_5780 = turn_params.rfc5780; + + if(turn_params.general_relay_servers_number<=1) { + while(!(general_relay_servers[0]->ioa_eng)) + sched_yield(); + udp_relay_servers[i] = general_relay_servers[0]; + continue; + } else if(turn_params.general_relay_servers_number>1) { + e = create_new_listener_engine(); + is_5780 = is_5780 && (i >= (size_t) (turn_params.aux_servers_list.size)); + } + + super_memory_t *sm = new_super_memory_region(); + struct relay_server* udp_rs = (struct relay_server*) allocate_super_memory_region(sm, sizeof(struct relay_server)); + udp_rs->id = (turnserver_id) i + TURNSERVER_ID_BOUNDARY_BETWEEN_TCP_AND_UDP; + udp_rs->sm = sm; + setup_relay_server(udp_rs, e, is_5780); + udp_relay_servers[i] = udp_rs; + } + } + } + + int udp_relay_server_index = 0; + + /* Create listeners */ + + /* Aux UDP servers */ + for(i=0; iioa_eng, sizeof(dtls_listener_relay_server_type*)); + turn_params.listener.aux_udp_services[index][0] = create_dtls_listener_server(turn_params.listener_ifname, saddr, port, turn_params.verbose, udp_relay_servers[udp_relay_server_index]->ioa_eng, &(udp_relay_servers[udp_relay_server_index]->server), 1, NULL); + + if(turn_params.general_relay_servers_number>1) { + ++udp_relay_server_index; + pthread_t thr; + if(pthread_create(&thr, NULL, run_udp_listener_thread, turn_params.listener.aux_udp_services[index][0])<0) { + perror("Cannot create aux listener thread\n"); + exit(-1); + } + pthread_detach(thr); + } + } + } + + /* Main servers */ + for(i=0; iioa_eng, sizeof(dtls_listener_relay_server_type*)); + turn_params.listener.udp_services[index][0] = create_dtls_listener_server(turn_params.listener_ifname, turn_params.listener.addrs[i], turn_params.listener_port, turn_params.verbose, udp_relay_servers[udp_relay_server_index]->ioa_eng, &(udp_relay_servers[udp_relay_server_index]->server), 1, NULL); + + if(turn_params.general_relay_servers_number>1) { + ++udp_relay_server_index; + pthread_t thr; + if(pthread_create(&thr, NULL, run_udp_listener_thread, turn_params.listener.udp_services[index][0])<0) { + perror("Cannot create listener thread\n"); + exit(-1); + } + pthread_detach(thr); + } + + if(turn_params.rfc5780) { + + turn_params.listener.udp_services[index+1] = (dtls_listener_relay_server_type**)allocate_super_memory_engine(udp_relay_servers[udp_relay_server_index]->ioa_eng, sizeof(dtls_listener_relay_server_type*)); + turn_params.listener.udp_services[index+1][0] = create_dtls_listener_server(turn_params.listener_ifname, turn_params.listener.addrs[i], get_alt_listener_port(), turn_params.verbose, udp_relay_servers[udp_relay_server_index]->ioa_eng, &(udp_relay_servers[udp_relay_server_index]->server), 1, NULL); + + if(turn_params.general_relay_servers_number>1) { + ++udp_relay_server_index; + pthread_t thr; + if(pthread_create(&thr, NULL, run_udp_listener_thread, turn_params.listener.udp_services[index+1][0])<0) { + perror("Cannot create listener thread\n"); + exit(-1); + } + pthread_detach(thr); + } + } + } else { + turn_params.listener.udp_services[index] = NULL; + if(turn_params.rfc5780) + turn_params.listener.udp_services[index+1] = NULL; + } + if(!turn_params.no_dtls && (turn_params.no_udp || (turn_params.listener_port != turn_params.tls_listener_port))) { + + turn_params.listener.dtls_services[index] = (dtls_listener_relay_server_type**)allocate_super_memory_engine(udp_relay_servers[udp_relay_server_index]->ioa_eng, sizeof(dtls_listener_relay_server_type*)); + turn_params.listener.dtls_services[index][0] = create_dtls_listener_server(turn_params.listener_ifname, turn_params.listener.addrs[i], turn_params.tls_listener_port, turn_params.verbose, udp_relay_servers[udp_relay_server_index]->ioa_eng, &(udp_relay_servers[udp_relay_server_index]->server), 1, NULL); + + if(turn_params.general_relay_servers_number>1) { + ++udp_relay_server_index; + pthread_t thr; + if(pthread_create(&thr, NULL, run_udp_listener_thread, turn_params.listener.dtls_services[index][0])<0) { + perror("Cannot create listener thread\n"); + exit(-1); + } + pthread_detach(thr); + } + + if(turn_params.rfc5780) { + + turn_params.listener.dtls_services[index+1] = (dtls_listener_relay_server_type**)allocate_super_memory_engine(udp_relay_servers[udp_relay_server_index]->ioa_eng, sizeof(dtls_listener_relay_server_type*)); + turn_params.listener.dtls_services[index+1][0] = create_dtls_listener_server(turn_params.listener_ifname, turn_params.listener.addrs[i], get_alt_tls_listener_port(), turn_params.verbose, udp_relay_servers[udp_relay_server_index]->ioa_eng, &(udp_relay_servers[udp_relay_server_index]->server), 1, NULL); + + if(turn_params.general_relay_servers_number>1) { + ++udp_relay_server_index; + pthread_t thr; + if(pthread_create(&thr, NULL, run_udp_listener_thread, turn_params.listener.dtls_services[index+1][0])<0) { + perror("Cannot create listener thread\n"); + exit(-1); + } + pthread_detach(thr); + } + } + } else { + turn_params.listener.dtls_services[index] = NULL; + if(turn_params.rfc5780) + turn_params.listener.dtls_services[index+1] = NULL; + } + } +} + +static void setup_socket_per_thread_udp_listener_servers(void) +{ + size_t i = 0; + size_t relayindex = 0; + + /* Create listeners */ + + for(relayindex=0;relayindexioa_eng) || !(general_relay_servers[relayindex]->server.e)) + sched_yield(); + } + + /* Aux UDP servers */ + for(i=0; iioa_eng, &(general_relay_servers[relayindex]->server), !relayindex, NULL); + } + } + } + + /* Main servers */ + for(i=0; iioa_eng, &(general_relay_servers[relayindex]->server), !relayindex, NULL); + } + + if(turn_params.rfc5780) { + + turn_params.listener.udp_services[index+1] = (dtls_listener_relay_server_type**)allocate_super_memory_engine(turn_params.listener.ioa_eng, sizeof(dtls_listener_relay_server_type*) * get_real_general_relay_servers_number()); + + for(relayindex=0;relayindexioa_eng, &(general_relay_servers[relayindex]->server), !relayindex, NULL); + } + } + } else { + turn_params.listener.udp_services[index] = NULL; + if(turn_params.rfc5780) + turn_params.listener.udp_services[index+1] = NULL; + } + if(!turn_params.no_dtls && (turn_params.no_udp || (turn_params.listener_port != turn_params.tls_listener_port))) { + + turn_params.listener.dtls_services[index] = (dtls_listener_relay_server_type**)allocate_super_memory_engine(turn_params.listener.ioa_eng, sizeof(dtls_listener_relay_server_type*) * get_real_general_relay_servers_number()); + + for(relayindex=0;relayindexioa_eng, &(general_relay_servers[relayindex]->server), !relayindex, NULL); + } + + if(turn_params.rfc5780) { + + turn_params.listener.dtls_services[index+1] = (dtls_listener_relay_server_type**)allocate_super_memory_engine(turn_params.listener.ioa_eng, sizeof(dtls_listener_relay_server_type*) * get_real_general_relay_servers_number()); + + for(relayindex=0;relayindexioa_eng, &(general_relay_servers[relayindex]->server), !relayindex, NULL); + } + } + } else { + turn_params.listener.dtls_services[index] = NULL; + if(turn_params.rfc5780) + turn_params.listener.dtls_services[index+1] = NULL; + } + } +} + +static void setup_socket_per_session_udp_listener_servers(void) +{ + size_t i = 0; + + /* Aux UDP servers */ + for(i=0; iss.sa_family == turn_params.listener.encaddrs[i]->ss.sa_family) { + index=i; + break; + } + } + } + if(index!=0xffff) { + for(i=0;iss.sa_family == addr->ss.sa_family) { + addr_cpy(alt_addr,caddr); + addr_set_port(alt_addr, alt_port); + return 0; + } + } + } + } + } + + return -1; +} + +static void run_events(struct event_base *eb, ioa_engine_handle e) +{ + if(!eb && e) + eb = e->event_base; + + if (!eb) + return; + + struct timeval timeout; + + timeout.tv_sec = 5; + timeout.tv_usec = 0; + + event_base_loopexit(eb, &timeout); + + event_base_dispatch(eb); + + +#if !defined(TURN_NO_HIREDIS) + if(e) + send_message_to_redis(e->rch, "publish", "__XXX__", "__YYY__"); +#endif +} + +void run_listener_server(struct listener_server *ls) +{ + unsigned int cycle = 0; + while (!turn_params.stop_turn_server) { + + if (eve(turn_params.verbose)) { + if ((cycle++ & 15) == 0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: cycle=%u\n", __FUNCTION__, cycle); + } + } + + run_events(ls->event_base, ls->ioa_eng); + + rollover_logfile(); + } +} + +static void setup_relay_server(struct relay_server *rs, ioa_engine_handle e, int to_set_rfc5780) +{ + struct bufferevent *pair[2]; + int opts = BEV_OPT_DEFER_CALLBACKS | BEV_OPT_UNLOCK_CALLBACKS; + + if(e) { + rs->event_base = e->event_base; + rs->ioa_eng = e; + } else { + rs->event_base = turn_event_base_new(); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"IO method (general relay thread): %s\n",event_base_get_method(rs->event_base)); + rs->ioa_eng = create_ioa_engine(rs->sm, rs->event_base, turn_params.listener.tp, turn_params.relay_ifname, + turn_params.relays_number, turn_params.relay_addrs, turn_params.default_relays, turn_params.verbose, turn_params.max_bps +#if !defined(TURN_NO_HIREDIS) + ,turn_params.redis_statsdb +#endif + ); + set_ssl_ctx(rs->ioa_eng, turn_params.tls_ctx_ssl23, turn_params.tls_ctx_v1_0, +#if defined(SSL_TXT_TLSV1_1) + turn_params.tls_ctx_v1_1, +#if defined(SSL_TXT_TLSV1_2) + turn_params.tls_ctx_v1_2, +#endif +#endif + turn_params.dtls_ctx); + ioa_engine_set_rtcp_map(rs->ioa_eng, turn_params.listener.rtcpmap); + } + + opts |= BEV_OPT_THREADSAFE; + + bufferevent_pair_new(rs->event_base, opts, pair); + rs->in_buf = pair[0]; + rs->out_buf = pair[1]; + bufferevent_setcb(rs->in_buf, relay_receive_message, NULL, NULL, rs); + bufferevent_enable(rs->in_buf, EV_READ); + + bufferevent_pair_new(rs->event_base, opts, pair); + rs->auth_in_buf = pair[0]; + rs->auth_out_buf = pair[1]; + bufferevent_setcb(rs->auth_in_buf, relay_receive_auth_message, NULL, NULL, rs); + bufferevent_enable(rs->auth_in_buf, EV_READ); + + init_turn_server(&(rs->server), + rs->id, turn_params.verbose, + rs->ioa_eng, turn_params.ct, 0, + turn_params.fingerprint, DONT_FRAGMENT_SUPPORTED, + start_user_check, + check_new_allocation_quota, + release_allocation_quota, + turn_params.external_ip, + &turn_params.no_tcp_relay, + &turn_params.no_udp_relay, + &turn_params.stale_nonce, + &turn_params.stun_only, + &turn_params.no_stun, + &turn_params.alternate_servers_list, + &turn_params.tls_alternate_servers_list, + &turn_params.aux_servers_list, + turn_params.udp_self_balance, + &turn_params.no_multicast_peers, &turn_params.no_loopback_peers, + &turn_params.ip_whitelist, &turn_params.ip_blacklist, + send_socket_to_relay, + &turn_params.secure_stun, turn_params.shatype, &turn_params.mobility, + turn_params.server_relay, + send_turn_session_info); + + if(to_set_rfc5780) { + set_rfc5780(&(rs->server), get_alt_addr, send_message_from_listener_to_client); + } + + if(turn_params.net_engine_version == NEV_UDP_SOCKET_PER_THREAD) { + setup_tcp_listener_servers(rs->ioa_eng, rs); + } +} + +static void *run_general_relay_thread(void *arg) +{ + static int always_true = 1; + struct relay_server *rs = (struct relay_server *)arg; + + int udp_reuses_the_same_relay_server = (turn_params.general_relay_servers_number<=1) || (turn_params.net_engine_version == NEV_UDP_SOCKET_PER_THREAD) || (turn_params.net_engine_version == NEV_UDP_SOCKET_PER_SESSION); + + int we_need_rfc5780 = udp_reuses_the_same_relay_server && turn_params.rfc5780; + + ignore_sigpipe(); + + setup_relay_server(rs, NULL, we_need_rfc5780); + + barrier_wait(); + + while(always_true) { + run_events(rs->event_base, rs->ioa_eng); + } + + return arg; +} + +static void setup_general_relay_servers(void) +{ + size_t i = 0; + + general_relay_servers = (struct relay_server**)allocate_super_memory_engine(turn_params.listener.ioa_eng, sizeof(struct relay_server *)*get_real_general_relay_servers_number()); + + for(i=0;iid = (turnserver_id)i; + general_relay_servers[i]->sm = NULL; + setup_relay_server(general_relay_servers[i], turn_params.listener.ioa_eng, ((turn_params.net_engine_version == NEV_UDP_SOCKET_PER_THREAD) || (turn_params.net_engine_version == NEV_UDP_SOCKET_PER_SESSION)) && turn_params.rfc5780); + general_relay_servers[i]->thr = pthread_self(); + } else { + super_memory_t *sm = new_super_memory_region(); + general_relay_servers[i] = (struct relay_server*)allocate_super_memory_region(sm,sizeof(struct relay_server)); + general_relay_servers[i]->id = (turnserver_id)i; + general_relay_servers[i]->sm = sm; + if(pthread_create(&(general_relay_servers[i]->thr), NULL, run_general_relay_thread, general_relay_servers[i])<0) { + perror("Cannot create relay thread\n"); + exit(-1); + } + pthread_detach(general_relay_servers[i]->thr); + } + } +} + +static int run_auth_server_flag = 1; + +static void* run_auth_server_thread(void *arg) +{ + ignore_sigpipe(); + + ns_bzero(&turn_params.authserver,sizeof(struct auth_server)); + + turn_params.authserver.event_base = turn_event_base_new(); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"IO method (auth thread): %s\n",event_base_get_method(turn_params.authserver.event_base)); + + struct bufferevent *pair[2]; + int opts = BEV_OPT_DEFER_CALLBACKS | BEV_OPT_UNLOCK_CALLBACKS; + + opts |= BEV_OPT_THREADSAFE; + + bufferevent_pair_new(turn_params.authserver.event_base, opts, pair); + turn_params.authserver.in_buf = pair[0]; + turn_params.authserver.out_buf = pair[1]; + bufferevent_setcb(turn_params.authserver.in_buf, auth_server_receive_message, NULL, NULL, &turn_params.authserver); + bufferevent_enable(turn_params.authserver.in_buf, EV_READ); + +#if !defined(TURN_NO_HIREDIS) + turn_params.authserver.rch = get_redis_async_connection(turn_params.authserver.event_base, turn_params.redis_statsdb, 1); +#endif + + struct auth_server *authserver = &turn_params.authserver; + struct event_base *eb = authserver->event_base; + + barrier_wait(); + + while(run_auth_server_flag) { + reread_realms(); + run_events(eb,NULL); + read_userdb_file(0); + update_white_and_black_lists(); + auth_ping( +#if !defined(TURN_NO_HIREDIS) + authserver->rch +#endif + ); + } + + return arg; +} + +static void setup_auth_server(void) +{ + if(pthread_create(&(turn_params.authserver.thr), NULL, run_auth_server_thread, NULL)<0) { + perror("Cannot create auth thread\n"); + exit(-1); + } + pthread_detach(turn_params.authserver.thr); +} + +static void* run_cli_server_thread(void *arg) +{ + ignore_sigpipe(); + + setup_cli_thread(); + + barrier_wait(); + + while(cliserver.event_base) { + run_events(cliserver.event_base,NULL); + } + + return arg; +} + +static void setup_cli_server(void) +{ + ns_bzero(&cliserver,sizeof(struct cli_server)); + cliserver.listen_fd = -1; + cliserver.verbose = turn_params.verbose; + + if(pthread_create(&(cliserver.thr), NULL, run_cli_server_thread, &cliserver)<0) { + perror("Cannot create cli thread\n"); + exit(-1); + } + + pthread_detach(cliserver.thr); +} + +void setup_server(void) +{ + evthread_use_pthreads(); + +#if !defined(TURN_NO_THREAD_BARRIERS) + + /* relay threads plus auth thread plus main listener thread */ + /* udp address listener thread(s) will start later */ + barrier_count = turn_params.general_relay_servers_number+2; + + if(use_cli) { + barrier_count += 1; + } + +#endif + + setup_listener(); + allocate_relay_addrs_ports(); + setup_barriers(); + setup_general_relay_servers(); + + if(turn_params.net_engine_version == NEV_UDP_SOCKET_PER_THREAD) + setup_socket_per_thread_udp_listener_servers(); + else if(turn_params.net_engine_version == NEV_UDP_SOCKET_PER_ENDPOINT) + setup_socket_per_endpoint_udp_listener_servers(); + else if(turn_params.net_engine_version == NEV_UDP_SOCKET_PER_SESSION) + setup_socket_per_session_udp_listener_servers(); + + if(turn_params.net_engine_version != NEV_UDP_SOCKET_PER_THREAD) { + setup_tcp_listener_servers(turn_params.listener.ioa_eng, NULL); + } + + setup_auth_server(); + if(use_cli) + setup_cli_server(); + + barrier_wait(); +} + +void init_listener(void) +{ + ns_bzero(&turn_params.listener,sizeof(struct listener_server)); +} + +/////////////////////////////// diff --git a/src/apps/relay/ns_ioalib_engine_impl.c b/src/apps/relay/ns_ioalib_engine_impl.c new file mode 100644 index 0000000..5afe397 --- /dev/null +++ b/src/apps/relay/ns_ioalib_engine_impl.c @@ -0,0 +1,3654 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "ns_turn_utils.h" +#include "ns_turn_session.h" +#include "ns_turn_server.h" +#include "ns_turn_khash.h" + +#include "stun_buffer.h" +#include "apputils.h" + +#include "ns_ioalib_impl.h" + +#if !defined(TURN_NO_TLS) +#include +#endif + +#include + +#include + +#if !defined(TURN_NO_HIREDIS) +#include "hiredis_libevent2.h" +#endif + +/* Compilation test: +#if defined(IP_RECVTTL) +#undef IP_RECVTTL +#endif +#if defined(IPV6_RECVHOPLIMIT) +#undef IPV6_RECVHOPLIMIT +#endif +#if defined(IP_RECVTOS) +#undef IP_RECVTOS +#endif +#if defined(IPV6_RECVTCLASS) +#undef IPV6_RECVTCLASS +#endif +*/ + +#define MAX_ERRORS_IN_UDP_BATCH (1024) + +struct turn_sock_extended_err { + uint32_t ee_errno; /* error number */ + uint8_t ee_origin; /* where the error originated */ + uint8_t ee_type; /* type */ + uint8_t ee_code; /* code */ + uint8_t ee_pad; /* padding */ + uint32_t ee_info; /* additional information */ + uint32_t ee_data; /* other data */ +/* More data may follow */ +}; + +#define TRIAL_EFFORTS_TO_SEND (2) + +const int predef_timer_intervals[PREDEF_TIMERS_NUM] = {30,60,90,120,240,300,360,540,600,700,800,900,1800,3600}; + +/************** Forward function declarations ******/ + +static int socket_readerr(evutil_socket_t fd, ioa_addr *orig_addr); + +static void socket_input_handler(evutil_socket_t fd, short what, void* arg); +static void socket_output_handler_bev(struct bufferevent *bev, void* arg); +static void socket_input_handler_bev(struct bufferevent *bev, void* arg); +static void eventcb_bev(struct bufferevent *bev, short events, void *arg); + +static int send_ssl_backlog_buffers(ioa_socket_handle s); + +static int set_accept_cb(ioa_socket_handle s, accept_cb acb, void *arg); + +static void close_socket_net_data(ioa_socket_handle s); + +/************** Utils **************************/ + +static const int tcp_congestion_control = 1; +static const int udp_congestion_control = 1; + +static int bufferevent_enabled(struct bufferevent *bufev, short flags) +{ + return (bufferevent_get_enabled(bufev) & flags); +} + +static int is_socket_writeable(ioa_socket_handle s, size_t sz, const char *msg, int option) +{ + UNUSED_ARG(s); + UNUSED_ARG(sz); + UNUSED_ARG(msg); + UNUSED_ARG(option); + + if(!(s->done) && !(s->broken) && !(s->tobeclosed)) { + + switch(s->st) { + + case TCP_SOCKET: + case TLS_SOCKET: + if(s->bev) { + + struct evbuffer *evb = bufferevent_get_output(s->bev); + + if(evb) { + size_t bufsz = evbuffer_get_length(evb); + size_t newsz = bufsz + sz; + + switch(s->sat) { + case TCP_CLIENT_DATA_SOCKET: + case TCP_RELAY_DATA_SOCKET: + + switch(option) { + case 0: + case 1: + if(newsz >= BUFFEREVENT_MAX_TCP_TO_TCP_WRITE) { + return 0; + } + break; + case 3: + case 4: + if(newsz >= BUFFEREVENT_MAX_TCP_TO_TCP_WRITE) { + return 0; + } + break; + default: + return 1; + }; + break; + default: + if(option == 2) { + if(newsz >= BUFFEREVENT_MAX_UDP_TO_TCP_WRITE) { + return 0; + } + } + }; + } + } + break; + default: + ; + }; + } + + return 1; +} + +static void log_socket_event(ioa_socket_handle s, const char *msg, int error) { + if(s && (error || (s->e && s->e->verbose))) { + if(!msg) + msg = "General socket event"; + turnsession_id id = 0; + { + ts_ur_super_session *ss = s->session; + if (ss) { + id = ss->id; + } else{ + return; + } + } + + TURN_LOG_LEVEL ll = TURN_LOG_LEVEL_INFO; + if(error) + ll = TURN_LOG_LEVEL_ERROR; + + { + char sraddr[129]="\0"; + char sladdr[129]="\0"; + addr_to_string(&(s->remote_addr),(u08bits*)sraddr); + addr_to_string(&(s->local_addr),(u08bits*)sladdr); + + if(EVUTIL_SOCKET_ERROR()) { + TURN_LOG_FUNC(ll,"session %018llu: %s: %s (local %s, remote %s)\n",(unsigned long long)id, + msg, evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()), + sladdr,sraddr); + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"session %018llu: %s (local %s, remote %s)\n", + (unsigned long long)id,msg,sladdr,sraddr); + } + } + } +} + +int set_df_on_ioa_socket(ioa_socket_handle s, int value) +{ + if(!s) + return 0; + + if(s->parent_s) + return 0; + + if (s->do_not_use_df) + value = 0; + + if (s->current_df_relay_flag != value) { + s->current_df_relay_flag = value; + return set_socket_df(s->fd, s->family, value); + } + + return 0; +} + +void set_do_not_use_df(ioa_socket_handle s) +{ + if(s->parent_s) + return; + + s->do_not_use_df = 1; + s->current_df_relay_flag = 1; + set_socket_df(s->fd, s->family, 0); +} + +/************** Buffer List ********************/ + +static int buffer_list_empty(stun_buffer_list *bufs) +{ + if(bufs && bufs->head && bufs->tsz) + return 0; + return 1; +} + +static stun_buffer_list_elem *get_elem_from_buffer_list(stun_buffer_list *bufs) +{ + stun_buffer_list_elem *ret = NULL; + + if(bufs && bufs->head && bufs->tsz) { + + ret=bufs->head; + bufs->head=ret->next; + --bufs->tsz; + + ret->next=NULL; + ret->buf.len = 0; + ret->buf.offset = 0; + ret->buf.coffset = 0; + } + + return ret; +} + +static void pop_elem_from_buffer_list(stun_buffer_list *bufs) +{ + if(bufs && bufs->head && bufs->tsz) { + + stun_buffer_list_elem *ret = bufs->head; + bufs->head=ret->next; + --bufs->tsz; + turn_free(ret,sizeof(stun_buffer_list_elem)); + } +} + + + +static stun_buffer_list_elem *new_blist_elem(ioa_engine_handle e) +{ + stun_buffer_list_elem *ret = get_elem_from_buffer_list(&(e->bufs)); + + if(!ret) { + ret = (stun_buffer_list_elem *)turn_malloc(sizeof(stun_buffer_list_elem)); + ret->buf.len = 0; + ret->buf.offset = 0; + ret->buf.coffset = 0; + ret->next = NULL; + } + + return ret; +} + +static inline void add_elem_to_buffer_list(stun_buffer_list *bufs, stun_buffer_list_elem *buf_elem) +{ + buf_elem->next = bufs->head; + bufs->head = buf_elem; + bufs->tsz += 1; +} + +static void add_buffer_to_buffer_list(stun_buffer_list *bufs, s08bits *buf, size_t len) +{ + if(bufs && buf && (bufs->tszbuf.buf,len); + buf_elem->buf.len = len; + buf_elem->buf.offset = 0; + buf_elem->buf.coffset = 0; + add_elem_to_buffer_list(bufs,buf_elem); + } +} + +static void free_blist_elem(ioa_engine_handle e, stun_buffer_list_elem *buf_elem) +{ + if(buf_elem) { + if(e && (e->bufs.tszbufs), buf_elem); + } else { + turn_free(buf_elem,sizeof(stun_buffer_list_elem)); + } + } +} + +/************** ENGINE *************************/ + +#define TURN_JIFFIE_SIZE (3) +#define TURN_JIFFIE_LENGTH (1<<(TURN_JIFFIE_SIZE)) + +static void timer_handler(ioa_engine_handle e, void* arg) { + + UNUSED_ARG(arg); + + _log_time_value = turn_time(); + _log_time_value_set = 1; + + e->jiffie = _log_time_value >> TURN_JIFFIE_SIZE; +} + +ioa_engine_handle create_ioa_engine(super_memory_t *sm, + struct event_base *eb, turnipports *tp, const s08bits* relay_ifname, + size_t relays_number, s08bits **relay_addrs, int default_relays, + int verbose, band_limit_t max_bps +#if !defined(TURN_NO_HIREDIS) + ,const char* redis_report_connection_string +#endif + ) +{ + static int capabilities_checked = 0; + + if(!capabilities_checked) { + capabilities_checked = 1; +#if !defined(CMSG_SPACE) + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "On this platform, I am using alternative behavior of TTL/TOS according to RFC 5766.\n"); +#endif +#if !defined(IP_RECVTTL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "IPv4: On this platform, I am using alternative behavior of TTL according to RFC 5766.\n"); +#endif +#if !defined(IPV6_RECVHOPLIMIT) + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "IPv6: On this platform, I am using alternative behavior of TTL (HOPLIMIT) according to RFC 6156.\n"); +#endif +#if !defined(IP_RECVTOS) + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "IPv4: On this platform, I am using alternative behavior of TOS according to RFC 5766.\n"); +#endif +#if !defined(IPV6_RECVTCLASS) + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "IPv6: On this platform, I am using alternative behavior of TRAFFIC CLASS according to RFC 6156.\n"); +#endif + } + + if (!relays_number || !relay_addrs || !tp) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot create TURN engine\n", __FUNCTION__); + return NULL; + } else { + ioa_engine_handle e = (ioa_engine_handle)allocate_super_memory_region(sm, sizeof(ioa_engine)); + + e->sm = sm; + e->default_relays = default_relays; + e->max_bpj = max_bps; + e->verbose = verbose; + e->tp = tp; + if (eb) { + e->event_base = eb; + e->deallocate_eb = 0; + } else { + e->event_base = turn_event_base_new(); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"IO method (engine own thread): %s\n",event_base_get_method(e->event_base)); + e->deallocate_eb = 1; + } + +#if !defined(TURN_NO_HIREDIS) + if(redis_report_connection_string && *redis_report_connection_string) { + e->rch = get_redis_async_connection(e->event_base, redis_report_connection_string, 0); + } +#endif + + { + int t; + for(t=0;tevent_base, &duration); + if(!ptv) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"FATAL: cannot create preferable timeval for %d secs (%d number)\n",predef_timer_intervals[t],t); + exit(-1); + } else { + ns_bcopy(ptv,&(e->predef_timers[t]),sizeof(struct timeval)); + e->predef_timer_intervals[t] = predef_timer_intervals[t]; + } + } + } + + + if (relay_ifname) + STRCPY(e->relay_ifname, relay_ifname); + { + size_t i = 0; + e->relay_addrs = (ioa_addr*)allocate_super_memory_region(sm, relays_number * sizeof(ioa_addr)+8); + for (i = 0; i < relays_number; i++) { + if(make_ioa_addr((u08bits*) relay_addrs[i], 0, &(e->relay_addrs[i]))<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"Cannot add a relay address: %s\n",relay_addrs[i]); + } + } + e->relays_number = relays_number; + } + e->relay_addr_counter = (unsigned short) turn_random(); + timer_handler(e,e); + e->timer_ev = set_ioa_timer(e, 1, 0, timer_handler, e, 1, "timer_handler"); + return e; + } +} + +void set_ssl_ctx(ioa_engine_handle e, + SSL_CTX *tls_ctx_ssl23, + SSL_CTX *tls_ctx_v1_0, +#if defined(SSL_TXT_TLSV1_1) + SSL_CTX *tls_ctx_v1_1, +#if defined(SSL_TXT_TLSV1_2) + SSL_CTX *tls_ctx_v1_2, +#endif +#endif + SSL_CTX *dtls_ctx) +{ + e->tls_ctx_ssl23 = tls_ctx_ssl23; + e->tls_ctx_v1_0 = tls_ctx_v1_0; +#if defined(SSL_TXT_TLSV1_1) + e->tls_ctx_v1_1 = tls_ctx_v1_1; +#if defined(SSL_TXT_TLSV1_2) + e->tls_ctx_v1_2 = tls_ctx_v1_2; +#endif +#endif + e->dtls_ctx = dtls_ctx; +} + +void ioa_engine_set_rtcp_map(ioa_engine_handle e, rtcp_map *rtcpmap) +{ + if(e) + e->map_rtcp = rtcpmap; +} + +static const ioa_addr* ioa_engine_get_relay_addr(ioa_engine_handle e, ioa_socket_handle client_s, + int address_family, int *err_code) +{ + if(e) { + + int family = AF_INET; + if(address_family == STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV6) + family = AF_INET6; + + + if(e->default_relays) { + + //No relay addrs defined - just return the client address if appropriate: + + ioa_addr *client_addr = get_local_addr_from_ioa_socket(client_s); + if(client_addr) { + switch(address_family) { + case STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV4: + if (client_addr->ss.sa_family == AF_INET) + return client_addr; + break; + case STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV6: + if (client_addr->ss.sa_family == AF_INET6) + return client_addr; + break; + default: + return client_addr; + }; + } + } + + if (e->relays_number>0) { + + size_t i = 0; + + //Default recommended behavior: + + for(i=0; irelays_number; i++) { + + if(e->relay_addr_counter >= e->relays_number) + e->relay_addr_counter = 0; + ioa_addr *relay_addr = &(e->relay_addrs[e->relay_addr_counter++]); + + if(addr_any_no_port(relay_addr)) + get_a_local_relay(family, relay_addr); + + switch (address_family){ + case STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_DEFAULT: + case STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV4: + if (relay_addr->ss.sa_family == AF_INET) + return relay_addr; + break; + case STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV6: + if (relay_addr->ss.sa_family == AF_INET6) + return relay_addr; + break; + default: + ; + }; + } + + if(address_family == STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_DEFAULT) { + + //Fallback to "find whatever is available": + + if(e->relay_addr_counter >= e->relays_number) + e->relay_addr_counter = 0; + const ioa_addr *relay_addr = &(e->relay_addrs[e->relay_addr_counter++]); + return relay_addr; + } + + *err_code = 440; + } + } + return NULL; +} + +/******************** Timers ****************************/ + +static void timer_event_handler(evutil_socket_t fd, short what, void* arg) +{ + timer_event* te = (timer_event*)arg; + + if(!te) + return; + + UNUSED_ARG(fd); + + if (!(what & EV_TIMEOUT)) + return; + + if(te->e && eve(te->e->verbose)) + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: timeout 0x%lx: %s\n", __FUNCTION__,(long)te, te->txt); + + ioa_timer_event_handler cb = te->cb; + ioa_engine_handle e = te->e; + void *ctx = te->ctx; + + cb(e, ctx); +} + +ioa_timer_handle set_ioa_timer(ioa_engine_handle e, int secs, int ms, ioa_timer_event_handler cb, void* ctx, int persist, const s08bits *txt) +{ + ioa_timer_handle ret = NULL; + + if (e && cb && secs > 0) { + + timer_event * te = (timer_event*) turn_malloc(sizeof(timer_event)); + int flags = EV_TIMEOUT; + if (persist) + flags |= EV_PERSIST; + struct event *ev = event_new(e->event_base, -1, flags, timer_event_handler, te); + struct timeval tv; + + tv.tv_sec = secs; + + te->ctx = ctx; + te->e = e; + te->ev = ev; + te->cb = cb; + te->txt = strdup(txt); + + if(!ms) { + tv.tv_usec = 0; + int found = 0; + int t; + for(t=0;tpredef_timer_intervals[t] == secs) { + evtimer_add(ev,&(e->predef_timers[t])); + found = 1; + break; + } + } + if(!found) { + evtimer_add(ev,&tv); + } + } else { + tv.tv_usec = ms * 1000; + evtimer_add(ev,&tv); + } + + ret = te; + } + + return ret; +} + +void stop_ioa_timer(ioa_timer_handle th) +{ + if (th) { + timer_event *te = (timer_event *)th; + EVENT_DEL(te->ev); + } +} + +void delete_ioa_timer(ioa_timer_handle th) +{ + if (th) { + stop_ioa_timer(th); + timer_event *te = (timer_event *)th; + if(te->txt) { + turn_free(te->txt,strlen(te->txt)+1); + te->txt = NULL; + } + turn_free(th,sizeof(timer_event)); + } +} + +/************** SOCKETS HELPERS ***********************/ + +static int ioa_socket_check_bandwidth(ioa_socket_handle s, size_t sz, int read) +{ + if(s && (s->e) && sz && (s->sat == CLIENT_SOCKET) && (s->session)) { + + band_limit_t max_bps = s->e->max_bpj; + band_limit_t s_max_bps = s->session->realm_options.perf_options.max_bps; + if(s_max_bps>0) + max_bps = s_max_bps; + + if(max_bps<1) + return 1; + + max_bps = max_bps<jiffie != s->e->jiffie) { + + s->jiffie = s->e->jiffie; + s->jiffie_bytes_read = 0; + s->jiffie_bytes_write = 0; + + if(bsz > max_bps) { + + return 0; + + } else { + if(read) + s->jiffie_bytes_read = bsz; + else + s->jiffie_bytes_write = bsz; + return 1; + } + } else { + band_limit_t nsz; + if(read) + nsz = s->jiffie_bytes_read + bsz; + else + nsz = s->jiffie_bytes_write + bsz; + if(nsz > max_bps) { + return 0; + } else { + if(read) + s->jiffie_bytes_read = nsz; + else + s->jiffie_bytes_write = nsz; + return 1; + } + } + } + + return 1; +} + +int get_ioa_socket_from_reservation(ioa_engine_handle e, u64bits in_reservation_token, ioa_socket_handle *s) +{ + if (e && in_reservation_token && s) { + *s = rtcp_map_get(e->map_rtcp, in_reservation_token); + if (*s) { + rtcp_map_del_savefd(e->map_rtcp, in_reservation_token); + return 0; + } + } + return -1; +} + +/* Socket options helpers ==>> */ + +#define CORRECT_RAW_TTL(ttl) do { if(ttl<0 || ttl>255) ttl=TTL_DEFAULT; } while(0) +#define CORRECT_RAW_TOS(tos) do { if(tos<0 || tos>255) tos=TOS_DEFAULT; } while(0) + +static int get_raw_socket_ttl(evutil_socket_t fd, int family) +{ + int ttl = 0; + + if(family == AF_INET6) { +#if !defined(IPV6_RECVHOPLIMIT) + UNUSED_ARG(fd); + do { return TTL_IGNORE; } while(0); +#else + socklen_t slen = (socklen_t)sizeof(ttl); + if(getsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl,&slen)<0) { + perror("get HOPLIMIT on socket"); + return TTL_IGNORE; + } +#endif + } else { +#if !defined(IP_RECVTTL) + UNUSED_ARG(fd); + do { return TTL_IGNORE; } while(0); +#else + socklen_t slen = (socklen_t)sizeof(ttl); + if(getsockopt(fd, IPPROTO_IP, IP_TTL, &ttl,&slen)<0) { + perror("get TTL on socket"); + return TTL_IGNORE; + } +#endif + } + + CORRECT_RAW_TTL(ttl); + + return ttl; +} + +static int get_raw_socket_tos(evutil_socket_t fd, int family) +{ + int tos = 0; + + if(family == AF_INET6) { +#if !defined(IPV6_RECVTCLASS) + UNUSED_ARG(fd); + do { return TOS_IGNORE; } while(0); +#else + socklen_t slen = (socklen_t)sizeof(tos); + if(getsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &tos,&slen)<0) { + perror("get TCLASS on socket"); + return -1; + } +#endif + } else { +#if !defined(IP_RECVTOS) + UNUSED_ARG(fd); + do { return TOS_IGNORE; } while(0); +#else + socklen_t slen = (socklen_t)sizeof(tos); + if(getsockopt(fd, IPPROTO_IP, IP_TOS, &tos,&slen)<0) { + perror("get TOS on socket"); + return -1; + } +#endif + } + + CORRECT_RAW_TOS(tos); + + return tos; +} + +static int set_raw_socket_ttl(evutil_socket_t fd, int family, int ttl) +{ + + if(family == AF_INET6) { +#if !defined(IPV6_RECVHOPLIMIT) + UNUSED_ARG(fd); + UNUSED_ARG(ttl); +#else + CORRECT_RAW_TTL(ttl); + if(setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl,sizeof(ttl))<0) { + perror("set HOPLIMIT on socket"); + return -1; + } +#endif + } else { +#if !defined(IP_RECVTTL) + UNUSED_ARG(fd); + UNUSED_ARG(ttl); +#else + CORRECT_RAW_TTL(ttl); + if(setsockopt(fd, IPPROTO_IP, IP_TTL, &ttl,sizeof(ttl))<0) { + perror("set TTL on socket"); + return -1; + } +#endif + } + + return 0; +} + +static int set_raw_socket_tos(evutil_socket_t fd, int family, int tos) +{ + + if(family == AF_INET6) { +#if !defined(IPV6_RECVTCLASS) + UNUSED_ARG(fd); + UNUSED_ARG(tos); +#else + CORRECT_RAW_TOS(tos); + if(setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &tos,sizeof(tos))<0) { + perror("set TCLASS on socket"); + return -1; + } +#endif + } else { +#if !defined(IPV6_RECVTOS) + UNUSED_ARG(fd); + UNUSED_ARG(tos); +#else + if(setsockopt(fd, IPPROTO_IP, IP_TOS, &tos,sizeof(tos))<0) { + perror("set TOS on socket"); + return -1; + } +#endif + } + + return 0; +} + +static int set_socket_ttl(ioa_socket_handle s, int ttl) +{ + if(s->default_ttl < 0) //Unsupported + return -1; + + if(ttl < 0) + ttl = s->default_ttl; + + CORRECT_RAW_TTL(ttl); + + if(ttl > s->default_ttl) + ttl=s->default_ttl; + + if(s->current_ttl != ttl) { + int ret = set_raw_socket_ttl(s->fd, s->family, ttl); + s->current_ttl = ttl; + return ret; + } + + return 0; +} + +static int set_socket_tos(ioa_socket_handle s, int tos) +{ + if(s->default_tos < 0) //Unsupported + return -1; + + if(tos < 0) + tos = s->default_tos; + + CORRECT_RAW_TOS(tos); + + if(s->current_tos != tos) { + int ret = set_raw_socket_tos(s->fd, s->family, tos); + s->current_tos = tos; + return ret; + } + + return 0; +} + +int set_raw_socket_ttl_options(evutil_socket_t fd, int family) +{ + if (family == AF_INET6) { +#if !defined(IPV6_RECVHOPLIMIT) + UNUSED_ARG(fd); +#else + int recv_ttl_on = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &recv_ttl_on, + sizeof(recv_ttl_on)) < 0) { + perror("cannot set recvhoplimit\n"); + } +#endif + } else { +#if !defined(IP_RECVTTL) + UNUSED_ARG(fd); +#else + int recv_ttl_on = 1; + if (setsockopt(fd, IPPROTO_IP, IP_RECVTTL, &recv_ttl_on, + sizeof(recv_ttl_on)) < 0) { + perror("cannot set recvttl\n"); + } +#endif + } + + return 0; +} + +int set_raw_socket_tos_options(evutil_socket_t fd, int family) +{ + if (family == AF_INET6) { +#if !defined(IPV6_RECVTCLASS) + UNUSED_ARG(fd); +#else + int recv_tos_on = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVTCLASS, &recv_tos_on, + sizeof(recv_tos_on)) < 0) { + perror("cannot set recvtclass\n"); + } +#endif + } else { +#if !defined(IP_RECVTOS) + UNUSED_ARG(fd); +#else + int recv_tos_on = 1; + if (setsockopt(fd, IPPROTO_IP, IP_RECVTOS, &recv_tos_on, + sizeof(recv_tos_on)) < 0) { + perror("cannot set recvtos\n"); + } +#endif + } + + return 0; +} + +int set_socket_options_fd(evutil_socket_t fd, int tcp, int family) +{ + + if(fd<0) + return 0; + + set_sock_buf_size(fd,UR_CLIENT_SOCK_BUF_SIZE); + + if(tcp) { + struct linger so_linger; + so_linger.l_onoff = 1; + so_linger.l_linger = 0; + if(setsockopt(fd, + SOL_SOCKET, + SO_LINGER, + &so_linger, + sizeof(so_linger))<1) { + //perror("setsolinger") + ; + } + } + + socket_set_nonblocking(fd); + + if (!tcp) { + set_raw_socket_ttl_options(fd, family); + set_raw_socket_tos_options(fd, family); + +#ifdef IP_RECVERR + if (family != AF_INET6) { + int on = 0; +#ifdef TURN_IP_RECVERR + on = 1; +#endif + if(setsockopt(fd, IPPROTO_IP, IP_RECVERR, (void *)&on, sizeof(on))<0) + perror("IP_RECVERR"); + } +#endif + +#ifdef IPV6_RECVERR + if (family == AF_INET6) { + int on = 0; +#ifdef TURN_IP_RECVERR + on = 1; +#endif + if(setsockopt(fd, IPPROTO_IPV6, IPV6_RECVERR, (void *)&on, sizeof(on))<0) + perror("IPV6_RECVERR"); + } +#endif + + } else { + int flag = 1; + int result = setsockopt(fd, /* socket affected */ + IPPROTO_TCP, /* set option at TCP level */ + TCP_NODELAY, /* name of option */ + (char*)&flag, /* value */ + sizeof(int)); /* length of option value */ + if (result < 0) + perror("TCP_NODELAY"); + + socket_tcp_set_keepalive(fd); + } + + return 0; +} + +int set_socket_options(ioa_socket_handle s) +{ + if(!s || (s->parent_s)) + return 0; + + set_socket_options_fd(s->fd,((s->st == TCP_SOCKET) || (s->st == TLS_SOCKET) || (s->st == TENTATIVE_TCP_SOCKET)),s->family); + + s->default_ttl = get_raw_socket_ttl(s->fd, s->family); + s->current_ttl = s->default_ttl; + + s->default_tos = get_raw_socket_tos(s->fd, s->family); + s->current_tos = s->default_tos; + + return 0; +} + +/* <<== Socket options helpers */ + +ioa_socket_handle create_unbound_ioa_socket(ioa_engine_handle e, ioa_socket_handle parent_s, int family, SOCKET_TYPE st, SOCKET_APP_TYPE sat) +{ + evutil_socket_t fd = -1; + ioa_socket_handle ret = NULL; + + if(!parent_s) { + switch (st){ + case UDP_SOCKET: + fd = socket(family, SOCK_DGRAM, 0); + if (fd < 0) { + perror("UDP socket"); + return NULL; + } + set_sock_buf_size(fd, UR_CLIENT_SOCK_BUF_SIZE); + break; + case TCP_SOCKET: + fd = socket(family, SOCK_STREAM, 0); + if (fd < 0) { + perror("TCP socket"); + return NULL; + } + set_sock_buf_size(fd, UR_CLIENT_SOCK_BUF_SIZE); + break; + default: + /* we do not support other sockets in the relay position */ + return NULL; + } + } + + ret = (ioa_socket*)turn_malloc(sizeof(ioa_socket)); + ns_bzero(ret,sizeof(ioa_socket)); + + ret->magic = SOCKET_MAGIC; + + ret->fd = fd; + ret->family = family; + ret->st = st; + ret->sat = sat; + ret->e = e; + + if(parent_s) { + add_socket_to_parent(parent_s, ret); + } else { + set_socket_options(ret); + } + + return ret; +} + +static int bind_ioa_socket(ioa_socket_handle s, const ioa_addr* local_addr, int reusable) +{ + if(!s || (s->parent_s)) + return 0; + + if (s && s->fd >= 0 && s->e && local_addr) { + + int res = addr_bind(s->fd, local_addr, reusable); + if (res >= 0) { + s->bound = 1; + addr_cpy(&(s->local_addr), local_addr); + if(addr_get_port(local_addr)<1) { + ioa_addr tmpaddr; + addr_get_from_sock(s->fd, &tmpaddr); + if(addr_any(&(s->local_addr))) { + addr_cpy(&(s->local_addr),&tmpaddr); + } else { + addr_set_port(&(s->local_addr),addr_get_port(&tmpaddr)); + } + } + s->local_addr_known = 1; + return 0; + } + } + return -1; +} + +int create_relay_ioa_sockets(ioa_engine_handle e, + ioa_socket_handle client_s, + int address_family, u08bits transport, + int even_port, ioa_socket_handle *rtp_s, + ioa_socket_handle *rtcp_s, uint64_t *out_reservation_token, + int *err_code, const u08bits **reason, + accept_cb acb, void *acbarg) +{ + + *rtp_s = NULL; + if (rtcp_s) + *rtcp_s = NULL; + + turnipports* tp = e->tp; + + size_t iip = 0; + + for (iip = 0; iip < e->relays_number; ++iip) { + + ioa_addr relay_addr; + const ioa_addr *ra = ioa_engine_get_relay_addr(e, client_s, address_family, err_code); + if(ra) + addr_cpy(&relay_addr, ra); + + if(*err_code) { + if(*err_code == 440) + *reason = (const u08bits *) "Unsupported address family"; + return -1; + } + + int rtcp_port = -1; + + IOA_CLOSE_SOCKET(*rtp_s); + if(rtcp_s) + IOA_CLOSE_SOCKET(*rtcp_s); + + ioa_addr rtcp_local_addr; + addr_cpy(&rtcp_local_addr, &relay_addr); + + int i = 0; + int port = 0; + ioa_addr local_addr; + addr_cpy(&local_addr, &relay_addr); + for (i = 0; i < 0xFFFF; i++) { + port = 0; + rtcp_port = -1; + if (even_port < 0) { + port = turnipports_allocate(tp, transport, &relay_addr); + } else { + + port = turnipports_allocate_even(tp, &relay_addr, even_port, out_reservation_token); + if (port >= 0 && even_port > 0) { + + IOA_CLOSE_SOCKET(*rtcp_s); + *rtcp_s = create_unbound_ioa_socket(e, NULL, relay_addr.ss.sa_family, UDP_SOCKET, RELAY_RTCP_SOCKET); + if (*rtcp_s == NULL) { + perror("socket"); + IOA_CLOSE_SOCKET(*rtp_s); + addr_set_port(&local_addr, port); + turnipports_release(tp, transport, &local_addr); + rtcp_port = port + 1; + addr_set_port(&rtcp_local_addr, rtcp_port); + turnipports_release(tp, transport, &rtcp_local_addr); + return -1; + } + sock_bind_to_device((*rtcp_s)->fd, (unsigned char*)e->relay_ifname); + + rtcp_port = port + 1; + addr_set_port(&rtcp_local_addr, rtcp_port); + if (bind_ioa_socket(*rtcp_s, &rtcp_local_addr, + (transport == STUN_ATTRIBUTE_TRANSPORT_TCP_VALUE)) < 0) { + addr_set_port(&local_addr, port); + turnipports_release(tp, transport, &local_addr); + turnipports_release(tp, transport, &rtcp_local_addr); + rtcp_port = -1; + IOA_CLOSE_SOCKET(*rtcp_s); + continue; + } + } + } + if (port < 0) { + IOA_CLOSE_SOCKET(*rtp_s); + if (rtcp_s) + IOA_CLOSE_SOCKET(*rtcp_s); + rtcp_port = -1; + break; + } else { + + IOA_CLOSE_SOCKET(*rtp_s); + + *rtp_s = create_unbound_ioa_socket(e, NULL, relay_addr.ss.sa_family, + (transport == STUN_ATTRIBUTE_TRANSPORT_TCP_VALUE) ? TCP_SOCKET : UDP_SOCKET, + RELAY_SOCKET); + if (*rtp_s == NULL) { + if (rtcp_s) + IOA_CLOSE_SOCKET(*rtcp_s); + addr_set_port(&local_addr, port); + turnipports_release(tp, transport, &local_addr); + if (rtcp_port >= 0) + turnipports_release(tp, transport, &rtcp_local_addr); + perror("socket"); + return -1; + } + + sock_bind_to_device((*rtp_s)->fd, (unsigned char*)e->relay_ifname); + + addr_set_port(&local_addr, port); + if (bind_ioa_socket(*rtp_s, &local_addr, + (transport == STUN_ATTRIBUTE_TRANSPORT_TCP_VALUE)) >= 0) { + break; + } else { + IOA_CLOSE_SOCKET(*rtp_s); + if (rtcp_s) + IOA_CLOSE_SOCKET(*rtcp_s); + addr_set_port(&local_addr, port); + turnipports_release(tp, transport, &local_addr); + if (rtcp_port >= 0) + turnipports_release(tp, transport, &rtcp_local_addr); + rtcp_port = -1; + } + } + } + + if(i>=0xFFFF) { + IOA_CLOSE_SOCKET(*rtp_s); + if (rtcp_s) + IOA_CLOSE_SOCKET(*rtcp_s); + } + + if (*rtp_s) { + addr_set_port(&local_addr, port); + addr_debug_print(e->verbose, &local_addr, "Local relay addr"); + if (rtcp_s && *rtcp_s) { + addr_set_port(&local_addr, port+1); + addr_debug_print(e->verbose, &local_addr, "Local reserved relay addr"); + } + break; + } + } + + if (!(*rtp_s)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: no available ports 3\n", __FUNCTION__); + IOA_CLOSE_SOCKET(*rtp_s); + if (rtcp_s) + IOA_CLOSE_SOCKET(*rtcp_s); + return -1; + } + + set_accept_cb(*rtp_s, acb, acbarg); + + if (rtcp_s && *rtcp_s && out_reservation_token && *out_reservation_token) { + if (rtcp_map_put(e->map_rtcp, *out_reservation_token, *rtcp_s) < 0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: cannot update RTCP map\n", __FUNCTION__); + IOA_CLOSE_SOCKET(*rtp_s); + if (rtcp_s) + IOA_CLOSE_SOCKET(*rtcp_s); + return -1; + } + } + + return 0; +} + +/* RFC 6062 ==>> */ + +static void tcp_listener_input_handler(struct evconnlistener *l, evutil_socket_t fd, + struct sockaddr *sa, int socklen, void *arg) +{ + UNUSED_ARG(l); + + ioa_socket_handle list_s = (ioa_socket_handle) arg; + + ioa_addr client_addr; + ns_bcopy(sa,&client_addr,socklen); + + addr_debug_print(((list_s->e) && list_s->e->verbose), &client_addr,"tcp accepted from"); + + ioa_socket_handle s = + create_ioa_socket_from_fd( + list_s->e, + fd, + NULL, + TCP_SOCKET, + TCP_RELAY_DATA_SOCKET, + &client_addr, + &(list_s->local_addr)); + + if (s) { + if(list_s->acb) { + list_s->acb(s,list_s->acbarg); + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "Do not know what to do with accepted TCP socket\n"); + close_ioa_socket(s); + } + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "Cannot create ioa_socket from FD\n"); + socket_closesocket(fd); + } +} + +static int set_accept_cb(ioa_socket_handle s, accept_cb acb, void *arg) +{ + if(!s || s->parent_s) + return -1; + + if(s->st == TCP_SOCKET) { + s->list_ev = evconnlistener_new(s->e->event_base, + tcp_listener_input_handler, s, + LEV_OPT_REUSEABLE, + 1024, s->fd); + if(!(s->list_ev)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: cannot start TCP listener\n", __FUNCTION__); + return -1; + } + s->acb = acb; + s->acbarg = arg; + } + return 0; +} + +static void connect_eventcb(struct bufferevent *bev, short events, void *ptr) +{ + UNUSED_ARG(bev); + + ioa_socket_handle ret = (ioa_socket_handle) ptr; + if (ret) { + connect_cb cb = ret->conn_cb; + void *arg = ret->conn_arg; + if (events & BEV_EVENT_CONNECTED) { + ret->conn_cb = NULL; + ret->conn_arg = NULL; + if(ret->conn_bev) { + bufferevent_disable(ret->conn_bev,EV_READ|EV_WRITE); + bufferevent_free(ret->conn_bev); + ret->conn_bev=NULL; + } + ret->connected = 1; + if(cb) { + cb(1,arg); + } + } else if (events & BEV_EVENT_ERROR) { + /* An error occured while connecting. */ + ret->conn_cb = NULL; + ret->conn_arg = NULL; + if(ret->conn_bev) { + bufferevent_disable(ret->conn_bev,EV_READ|EV_WRITE); + bufferevent_free(ret->conn_bev); + ret->conn_bev=NULL; + } + if(cb) { + cb(0,arg); + } + } + } +} + +ioa_socket_handle ioa_create_connecting_tcp_relay_socket(ioa_socket_handle s, ioa_addr *peer_addr, connect_cb cb, void *arg) +{ + ioa_socket_handle ret = create_unbound_ioa_socket(s->e, NULL, s->family, s->st, TCP_RELAY_DATA_SOCKET); + + if(!ret) { + return NULL; + } + + ioa_addr new_local_addr; + addr_cpy(&new_local_addr, &(s->local_addr)); + +#if !defined(SO_REUSEPORT) + /* + * trick for OSes which do not support SO_REUSEPORT. + * Section 5.2 of RFC 6062 will not work correctly + * for those OSes (for example, Linux pre-3.9 kernel). + */ +#if !defined(__CYGWIN__) && !defined(__CYGWIN32__) && !defined(__CYGWIN64__) + close_socket_net_data(s); +#else + addr_set_port(&new_local_addr,0); +#endif +#endif + + if(bind_ioa_socket(ret, &new_local_addr,1)<0) { + IOA_CLOSE_SOCKET(ret); + ret = NULL; + goto ccs_end; + } + + addr_cpy(&(ret->remote_addr), peer_addr); + + set_ioa_socket_session(ret, s->session); + + if(ret->conn_bev) { + bufferevent_disable(ret->conn_bev,EV_READ|EV_WRITE); + bufferevent_free(ret->conn_bev); + ret->conn_bev=NULL; + } + + ret->conn_bev = bufferevent_socket_new(ret->e->event_base, + ret->fd, + BEV_OPT_THREADSAFE | BEV_OPT_DEFER_CALLBACKS | BEV_OPT_UNLOCK_CALLBACKS); + bufferevent_setcb(ret->conn_bev, NULL, NULL, connect_eventcb, ret); + + ret->conn_arg = arg; + ret->conn_cb = cb; + + if (bufferevent_socket_connect(ret->conn_bev, (struct sockaddr *) peer_addr, get_ioa_addr_len(peer_addr)) < 0) { + /* Error starting connection */ + set_ioa_socket_session(ret, NULL); + IOA_CLOSE_SOCKET(ret); + ret = NULL; + goto ccs_end; + } + + ccs_end: + +#if !defined(SO_REUSEPORT) +#if !defined(__CYGWIN__) && !defined(__CYGWIN32__) && !defined(__CYGWIN64__) + /* + * trick for OSes which do not support SO_REUSEPORT. + * Section 5.2 of RFC 6062 will not work correctly + * for those OSes (for example, Linux pre-3.9 kernel). + */ + s->fd = socket(s->family, SOCK_STREAM, 0); + if (s->fd < 0) { + perror("TCP socket"); + if(ret) { + set_ioa_socket_session(ret, NULL); + IOA_CLOSE_SOCKET(ret); + ret = NULL; + } + } else { + set_socket_options(s); + sock_bind_to_device(s->fd, (unsigned char*)s->e->relay_ifname); + if(bind_ioa_socket(s, &new_local_addr, 1)<0) { + if(ret) { + set_ioa_socket_session(ret, NULL); + IOA_CLOSE_SOCKET(ret); + ret = NULL; + } + } else { + set_accept_cb(s, s->acb, s->acbarg); + } + } +#endif +#endif + + return ret; +} + +/* <<== RFC 6062 */ + +void add_socket_to_parent(ioa_socket_handle parent_s, ioa_socket_handle s) +{ + if(parent_s && s) { + delete_socket_from_parent(s); + s->parent_s = parent_s; + s->fd = parent_s->fd; + } +} + +void delete_socket_from_parent(ioa_socket_handle s) +{ + if(s && s->parent_s) { + s->parent_s = NULL; + s->fd = -1; + } +} + +void add_socket_to_map(ioa_socket_handle s, ur_addr_map *amap) +{ + if(amap && s && (s->sockets_container != amap)) { + delete_socket_from_map(s); + ur_addr_map_del(amap, &(s->remote_addr),NULL); + ur_addr_map_put(amap, + &(s->remote_addr), + (ur_addr_map_value_type)s); + s->sockets_container = amap; + } +} + +void delete_socket_from_map(ioa_socket_handle s) +{ + if(s && s->sockets_container) { + ur_addr_map_del(s->sockets_container, + &(s->remote_addr), + NULL); + s->sockets_container = NULL; + } +} + +ioa_socket_handle create_ioa_socket_from_fd(ioa_engine_handle e, + ioa_socket_raw fd, ioa_socket_handle parent_s, + SOCKET_TYPE st, SOCKET_APP_TYPE sat, + const ioa_addr *remote_addr, const ioa_addr *local_addr) +{ + ioa_socket_handle ret = NULL; + + if ((fd < 0) && !parent_s) { + return NULL; + } + + ret = (ioa_socket*)turn_malloc(sizeof(ioa_socket)); + ns_bzero(ret,sizeof(ioa_socket)); + + ret->magic = SOCKET_MAGIC; + + ret->fd = fd; + ret->family = local_addr->ss.sa_family; + ret->st = st; + ret->sat = sat; + ret->e = e; + + if (local_addr) { + ret->bound = 1; + addr_cpy(&(ret->local_addr), local_addr); + } + + if (remote_addr) { + ret->connected = 1; + addr_cpy(&(ret->remote_addr), remote_addr); + } + + if(parent_s) { + add_socket_to_parent(parent_s, ret); + } else { + set_socket_options(ret); + } + + return ret; +} + +/* Only must be called for DTLS_SOCKET */ +ioa_socket_handle create_ioa_socket_from_ssl(ioa_engine_handle e, ioa_socket_handle parent_s, SSL* ssl, SOCKET_TYPE st, SOCKET_APP_TYPE sat, const ioa_addr *remote_addr, const ioa_addr *local_addr) +{ + ioa_socket_handle ret = create_ioa_socket_from_fd(e, parent_s->fd, parent_s, st, sat, remote_addr, local_addr); + + if(ret) { + ret->ssl = ssl; + if(st == DTLS_SOCKET) + STRCPY(ret->orig_ctx_type,"DTLSv1.0"); + } + + return ret; +} + +static void close_socket_net_data(ioa_socket_handle s) +{ + if(s) { + + EVENT_DEL(s->read_event); + if(s->list_ev) { + evconnlistener_free(s->list_ev); + s->list_ev = NULL; + } + if(s->conn_bev) { + bufferevent_disable(s->conn_bev,EV_READ|EV_WRITE); + bufferevent_free(s->conn_bev); + s->conn_bev=NULL; + } + if(s->bev) { + bufferevent_disable(s->bev,EV_READ|EV_WRITE); + bufferevent_free(s->bev); + s->bev=NULL; + } + + if (s->ssl) { + if (!s->broken) { + if(!(SSL_get_shutdown(s->ssl) & SSL_SENT_SHUTDOWN)) { + /* + * SSL_RECEIVED_SHUTDOWN tells SSL_shutdown to act as if we had already + * received a close notify from the other end. SSL_shutdown will then + * send the final close notify in reply. The other end will receive the + * close notify and send theirs. By this time, we will have already + * closed the socket and the other end's real close notify will never be + * received. In effect, both sides will think that they have completed a + * clean shutdown and keep their sessions valid. This strategy will fail + * if the socket is not ready for writing, in which case this hack will + * lead to an unclean shutdown and lost session on the other end. + */ + SSL_set_shutdown(s->ssl, SSL_RECEIVED_SHUTDOWN); + SSL_shutdown(s->ssl); + log_socket_event(s, "SSL shutdown received, socket to be closed",0); + } + } + SSL_free(s->ssl); + s->ssl = NULL; + } + + if (s->fd >= 0) { + socket_closesocket(s->fd); + s->fd = -1; + } + } +} + +void detach_socket_net_data(ioa_socket_handle s) +{ + if(s) { + EVENT_DEL(s->read_event); + s->read_cb = NULL; + s->read_ctx = NULL; + if(s->list_ev) { + evconnlistener_free(s->list_ev); + s->list_ev = NULL; + s->acb = NULL; + s->acbarg = NULL; + } + if(s->conn_bev) { + bufferevent_disable(s->conn_bev,EV_READ|EV_WRITE); + bufferevent_free(s->conn_bev); + s->conn_bev=NULL; + s->conn_arg=NULL; + s->conn_cb=NULL; + } + if(s->bev) { + bufferevent_disable(s->bev,EV_READ|EV_WRITE); + bufferevent_free(s->bev); + s->bev=NULL; + } + } +} + +void close_ioa_socket(ioa_socket_handle s) +{ + if (s) { + if(s->magic != SOCKET_MAGIC) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!! %s wrong magic on socket: 0x%lx, st=%d, sat=%d\n", __FUNCTION__,(long)s, s->st, s->sat); + return; + } + + if(s->done) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!! %s double free on socket: 0x%lx, st=%d, sat=%d\n", __FUNCTION__,(long)s, s->st, s->sat); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!! %s socket: 0x%lx was closed\n", __FUNCTION__,(long)s); + return; + } + + s->done = 1; + + while(!buffer_list_empty(&(s->bufs))) + pop_elem_from_buffer_list(&(s->bufs)); + + ioa_network_buffer_delete(s->e, s->defer_nbh); + + if(s->bound && s->e && s->e->tp) { + turnipports_release(s->e->tp, + ((s->st == TCP_SOCKET) ? STUN_ATTRIBUTE_TRANSPORT_TCP_VALUE : STUN_ATTRIBUTE_TRANSPORT_UDP_VALUE), + &(s->local_addr)); + } + + delete_socket_from_map(s); + delete_socket_from_parent(s); + + close_socket_net_data(s); + + s->session = NULL; + s->sub_session = NULL; + s->magic = 0; + + turn_free(s,sizeof(ioa_socket)); + } +} + +ioa_socket_handle detach_ioa_socket(ioa_socket_handle s, int full_detach) +{ + ioa_socket_handle ret = NULL; + + if (!s) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"Detaching NULL socket\n"); + } else { + if((s->magic != SOCKET_MAGIC)||(s->done)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s detach on bad socket: 0x%lx, st=%d, sat=%d\n", __FUNCTION__,(long)s, s->st, s->sat); + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s socket: 0x%lx was closed\n", __FUNCTION__,(long)s); + return ret; + } + if(s->tobeclosed) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s detach on tobeclosed socket: 0x%lx, st=%d, sat=%d\n", __FUNCTION__,(long)s, s->st, s->sat); + return ret; + } + if(!(s->e)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s detach on socket without engine: 0x%lx, st=%d, sat=%d\n", __FUNCTION__,(long)s, s->st, s->sat); + return ret; + } + + s->tobeclosed = 1; + + if(s->parent_s) { + if((s->st != UDP_SOCKET) && (s->st != DTLS_SOCKET)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s detach on non-UDP child socket: 0x%lx, st=%d, sat=%d\n", __FUNCTION__,(long)s, s->st, s->sat); + return ret; + } + } + + evutil_socket_t udp_fd = -1; + + if(full_detach && s->parent_s) { + + udp_fd = socket(s->local_addr.ss.sa_family, SOCK_DGRAM, 0); + if (udp_fd < 0) { + perror("socket"); + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"%s: Cannot allocate new socket\n",__FUNCTION__); + return ret; + } + } + + detach_socket_net_data(s); + + while(!buffer_list_empty(&(s->bufs))) + pop_elem_from_buffer_list(&(s->bufs)); + + ioa_network_buffer_delete(s->e, s->defer_nbh); + + ret = (ioa_socket*)turn_malloc(sizeof(ioa_socket)); + if(!ret) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"%s: Cannot allocate new socket structure\n",__FUNCTION__); + if(udp_fd>=0) + close(udp_fd); + return ret; + } + + ns_bzero(ret,sizeof(ioa_socket)); + + ret->magic = SOCKET_MAGIC; + + ret->username_hash = s->username_hash; + ret->realm_hash = s->realm_hash; + + ret->ssl = s->ssl; + ret->fd = s->fd; + + ret->family = s->family; + ret->st = s->st; + ret->sat = s->sat; + ret->bound = s->bound; + ret->local_addr_known = s->local_addr_known; + addr_cpy(&(ret->local_addr),&(s->local_addr)); + ret->connected = s->connected; + ioa_socket_handle parent_s = s->parent_s; + addr_cpy(&(ret->remote_addr),&(s->remote_addr)); + + ur_addr_map *sockets_container = s->sockets_container; + + delete_socket_from_map(s); + delete_socket_from_parent(s); + + if(full_detach && parent_s) { + + ret->fd = udp_fd; + + if(sock_bind_to_device(udp_fd, (unsigned char*)(s->e->relay_ifname))<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"Cannot bind udp server socket to device %s\n",(char*)(s->e->relay_ifname)); + } + + if(addr_bind(udp_fd,&(s->local_addr),1)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"Cannot bind new detached udp server socket to local addr\n"); + IOA_CLOSE_SOCKET(ret); + return ret; + } + + int connect_err=0; + if(addr_connect(udp_fd, &(s->remote_addr), &connect_err)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"Cannot connect new detached udp server socket to remote addr\n"); + IOA_CLOSE_SOCKET(ret); + return ret; + } + + set_socket_options(ret); + + } else { + add_socket_to_parent(parent_s, ret); + add_socket_to_map(ret,sockets_container); + } + + ret->current_ttl = s->current_ttl; + ret->default_ttl = s->default_ttl; + + ret->current_tos = s->current_tos; + ret->default_tos = s->default_tos; + + s->ssl = NULL; + s->fd = -1; + } + + return ret; +} + +ts_ur_super_session *get_ioa_socket_session(ioa_socket_handle s) +{ + if(s) + return s->session; + return NULL; +} + +void set_ioa_socket_session(ioa_socket_handle s, ts_ur_super_session *ss) +{ + if(s) + s->session = ss; +} + +void clear_ioa_socket_session_if(ioa_socket_handle s, void *ss) +{ + if(s && ((void*)(s->session)==ss)) { + s->session=NULL; + } +} + +tcp_connection *get_ioa_socket_sub_session(ioa_socket_handle s) +{ + if(s) + return s->sub_session; + return NULL; +} + +void set_ioa_socket_sub_session(ioa_socket_handle s, tcp_connection *tc) +{ + if(s) + s->sub_session = tc; +} + +int get_ioa_socket_address_family(ioa_socket_handle s) { + if(!s) { + return AF_INET; + } else if(s->parent_s) { + return s->parent_s->family; + } else { + return s->family; + } +} + +SOCKET_TYPE get_ioa_socket_type(ioa_socket_handle s) +{ + if(s) + return s->st; + + return UNKNOWN_SOCKET; +} + +SOCKET_APP_TYPE get_ioa_socket_app_type(ioa_socket_handle s) +{ + if(s) + return s->sat; + return UNKNOWN_APP_SOCKET; +} + +void set_ioa_socket_app_type(ioa_socket_handle s, SOCKET_APP_TYPE sat) { + if(s) + s->sat = sat; +} + +ioa_addr* get_local_addr_from_ioa_socket(ioa_socket_handle s) +{ + if (s) { + + if(s->parent_s) { + return get_local_addr_from_ioa_socket(s->parent_s); + } else if (s->local_addr_known) { + return &(s->local_addr); + } else if (s->bound && (addr_get_port(&(s->local_addr)) > 0)) { + s->local_addr_known = 1; + return &(s->local_addr); + } else { + ioa_addr tmpaddr; + if (addr_get_from_sock(s->fd, &tmpaddr) == 0) { + if(addr_get_port(&tmpaddr)>0) { + s->local_addr_known = 1; + s->bound = 1; + if(addr_any(&(s->local_addr))) { + addr_cpy(&(s->local_addr),&tmpaddr); + } else { + addr_set_port(&(s->local_addr),addr_get_port(&tmpaddr)); + } + return &(s->local_addr); + } + if(addr_any(&(s->local_addr))) { + addr_cpy(&(s->local_addr),&tmpaddr); + } + return &(s->local_addr); + } + } + } + return NULL; +} + +ioa_addr* get_remote_addr_from_ioa_socket(ioa_socket_handle s) +{ + if (s) { + + if (s->connected) { + return &(s->remote_addr); + } + } + return NULL; +} + +int get_local_mtu_ioa_socket(ioa_socket_handle s) +{ + if(s) { + if(s->parent_s) + return get_local_mtu_ioa_socket(s->parent_s); + + return get_socket_mtu(s->fd, s->family, (s->e && eve(s->e->verbose))); + } + return -1; +} + +/* + * Return: -1 - error, 0 or >0 - OK + * *read_len -1 - no data, >=0 - data available + */ +int ssl_read(evutil_socket_t fd, SSL* ssl, ioa_network_buffer_handle nbh, int verbose) +{ + int ret = 0; + + if (!ssl || !nbh) + return -1; + + s08bits* buffer = (s08bits*)ioa_network_buffer_data(nbh); + int buf_size = (int)ioa_network_buffer_get_capacity_udp(); + int read_len = (int)ioa_network_buffer_get_size(nbh); + + if(read_len < 1) + return -1; + + s08bits *new_buffer = buffer + buf_size; + int old_buffer_len = read_len; + + int len = 0; + + if (eve(verbose)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: before read...\n", __FUNCTION__); + } + + BIO *wbio = SSL_get_wbio(ssl); + if(wbio) { + BIO_set_fd(wbio,fd,BIO_NOCLOSE); + } + + BIO* rbio = BIO_new_mem_buf(buffer, old_buffer_len); + BIO_set_mem_eof_return(rbio, -1); + + ssl->rbio = rbio; + + int if1 = SSL_is_init_finished(ssl); + + do { + len = SSL_read(ssl, new_buffer, buf_size); + } while (len < 0 && (errno == EINTR)); + + int if2 = SSL_is_init_finished(ssl); + + if (eve(verbose)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: after read: %d\n", __FUNCTION__, len); + } + + if(SSL_get_shutdown(ssl)) { + + ret = -1; + + } else if (!if1 && if2) { + + if(verbose && SSL_get_peer_certificate(ssl)) { + printf("\n------------------------------------------------------------\n"); + X509_NAME_print_ex_fp(stdout, X509_get_subject_name(SSL_get_peer_certificate(ssl)), 1, + XN_FLAG_MULTILINE); + printf("\n\n Cipher: %s\n", SSL_CIPHER_get_name(SSL_get_current_cipher(ssl))); + printf("\n------------------------------------------------------------\n\n"); + } + + ret = 0; + + } else if (len < 0 && ((errno == ENOBUFS) || (errno == EAGAIN))) { + if (eve(verbose)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: ENOBUFS/EAGAIN\n", __FUNCTION__); + } + ret = 0; + } else { + + if (eve(verbose)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: read %d bytes\n", __FUNCTION__, (int) len); + } + + if (len >= 0) { + ret = len; + } else { + switch (SSL_get_error(ssl, len)){ + case SSL_ERROR_NONE: + //??? + ret = 0; + break; + case SSL_ERROR_WANT_READ: + ret = 0; + break; + case SSL_ERROR_WANT_WRITE: + ret = 0; + break; + case SSL_ERROR_ZERO_RETURN: + ret = 0; + break; + case SSL_ERROR_SYSCALL: + { + int err = errno; + if (handle_socket_error()) { + ret = 0; + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "TLS Socket read error: %d\n", err); + ret = -1; + } + break; + } + case SSL_ERROR_SSL: + if (verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "SSL read error: "); + s08bits buf[65536]; + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s (%d)\n", ERR_error_string(ERR_get_error(), buf), SSL_get_error(ssl, len)); + } + if (verbose) + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "SSL connection closed.\n"); + ret = -1; + break; + default: + if (verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Unexpected error while reading!\n"); + } + ret = -1; + } + } + } + + if(ret>0) { + ioa_network_buffer_add_offset_size(nbh, (u16bits)buf_size, 0, (size_t)ret); + } + + BIO_free(rbio); + ssl->rbio = NULL; + + return ret; +} + +static int socket_readerr(evutil_socket_t fd, ioa_addr *orig_addr) +{ + if ((fd < 0) || !orig_addr) + return -1; + +#if defined(CMSG_SPACE) && defined(MSG_ERRQUEUE) && defined(IP_RECVERR) + + u08bits ecmsg[TURN_CMSG_SZ+1]; + int flags = MSG_ERRQUEUE; + int len = 0; + + struct msghdr msg; + struct iovec iov; + char buffer[65536]; + + char *cmsg = (char*)ecmsg; + + msg.msg_control = cmsg; + msg.msg_controllen = TURN_CMSG_SZ; + /* CMSG_SPACE(sizeof(recv_ttl)+sizeof(recv_tos)) */ + + msg.msg_name = orig_addr; + msg.msg_namelen = (socklen_t)get_ioa_addr_len(orig_addr); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_iov->iov_base = buffer; + msg.msg_iov->iov_len = sizeof(buffer); + msg.msg_flags = 0; + + int try_cycle = 0; + + do { + + do { + len = recvmsg(fd,&msg,flags); + } while (len < 0 && (errno == EINTR)); + + } while((len>0)&&(try_cycle++iov_base = buffer; + msg.msg_iov->iov_len = (size_t)buf_size; + msg.msg_flags = 0; + +#if defined(MSG_ERRQUEUE) + int try_cycle = 0; + try_again: +#endif + + do { + len = recvmsg(fd,&msg,flags); + } while (len < 0 && (errno == EINTR)); + +#if defined(MSG_ERRQUEUE) + + if(flags & MSG_ERRQUEUE) { + if((len>0)&&(try_cycle++= 0) { + + struct cmsghdr *cmsgh; + + // Receive auxiliary data in msg + for (cmsgh = CMSG_FIRSTHDR(&msg); cmsgh != NULL; cmsgh + = CMSG_NXTHDR(&msg,cmsgh)) { + int l = cmsgh->cmsg_level; + int t = cmsgh->cmsg_type; + + switch(l) { + case IPPROTO_IP: + switch(t) { +#if defined(IP_RECVTTL) + case IP_RECVTTL: + case IP_TTL: + recv_ttl = *((recv_ttl_t *) CMSG_DATA(cmsgh)); + break; +#endif +#if defined(IP_RECVTOS) + case IP_RECVTOS: + case IP_TOS: + recv_tos = *((recv_tos_t *) CMSG_DATA(cmsgh)); + break; +#endif +#if defined(IP_RECVERR) + case IP_RECVERR: + { + struct turn_sock_extended_err *e=(struct turn_sock_extended_err*) CMSG_DATA(cmsgh); + if(errcode) + *errcode = e->ee_errno; + } + break; +#endif + default: + ; + /* no break */ + }; + break; + case IPPROTO_IPV6: + switch(t) { +#if defined(IPV6_RECVHOPLIMIT) + case IPV6_RECVHOPLIMIT: + case IPV6_HOPLIMIT: + recv_ttl = *((recv_ttl_t *) CMSG_DATA(cmsgh)); + break; +#endif +#if defined(IPV6_RECVTCLASS) + case IPV6_RECVTCLASS: + case IPV6_TCLASS: + recv_tos = *((recv_tos_t *) CMSG_DATA(cmsgh)); + break; +#endif +#if defined(IPV6_RECVERR) + case IPV6_RECVERR: + { + struct turn_sock_extended_err *e=(struct turn_sock_extended_err*) CMSG_DATA(cmsgh); + if(errcode) + *errcode = e->ee_errno; + } + break; +#endif + default: + ; + /* no break */ + }; + break; + default: + ; + /* no break */ + }; + } + } + +#endif + + *ttl = recv_ttl; + + CORRECT_RAW_TTL(*ttl); + + *tos = recv_tos; + + CORRECT_RAW_TOS(*tos); + + return len; +} + +#if !defined(TURN_NO_TLS) +static TURN_TLS_TYPE check_tentative_tls(ioa_socket_raw fd) +{ + TURN_TLS_TYPE ret = TURN_TLS_NO; + + char s[12]; + int len = 0; + + do { + len = (int)recv(fd, s, sizeof(s), MSG_PEEK); + } while (len < 0 && (errno == EINTR)); + + if(len>0 && ((size_t)len == sizeof(s))) { + if((s[0]==22)&&(s[1]==3)&&(s[5]==1)&&(s[9]==3)) { + char max_supported = (char)(TURN_TLS_TOTAL-2); + if(s[10] >= max_supported) + ret = (TURN_TLS_TYPE)((((int)TURN_TLS_TOTAL)-1)); + else + ret = (TURN_TLS_TYPE)(s[10]+1); + } else if((s[2]==1)&&(s[3]==3)) { + ret = TURN_TLS_SSL23; /* compatibility mode */ + } else if((s[2]==1)&&(s[3]==0)&&(s[4]==2)) { + ret = TURN_TLS_SSL23; /* old mode */ + } + } + + return ret; +} +#endif + +static int socket_input_worker(ioa_socket_handle s) +{ + int len = 0; + int ret = 0; + size_t app_msg_len = 0; + int ttl = TTL_IGNORE; + int tos = TOS_IGNORE; + ioa_addr remote_addr; + + int try_again = 0; + int try_ok = 0; + int try_cycle = 0; + const int MAX_TRIES = 16; + + if(!s) + return 0; + + if((s->magic != SOCKET_MAGIC)||(s->done)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!!%s on socket: 0x%lx, st=%d, sat=%d\n", __FUNCTION__,(long)s, s->st, s->sat); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!! %s socket: 0x%lx was closed\n", __FUNCTION__,(long)s); + return -1; + } + + if(!(s->e)) + return 0; + + if(s->tobeclosed) + return 0; + + if(s->connected) + addr_cpy(&remote_addr,&(s->remote_addr)); + + if(tcp_congestion_control && s->sub_session && s->bev) { + if(s == s->sub_session->client_s) { + if(!is_socket_writeable(s->sub_session->peer_s, STUN_BUFFER_SIZE,__FUNCTION__,0)) { + if(bufferevent_enabled(s->bev,EV_READ)) { + bufferevent_disable(s->bev,EV_READ); + } + } + } else if(s == s->sub_session->peer_s) { + if(!is_socket_writeable(s->sub_session->client_s, STUN_BUFFER_SIZE,__FUNCTION__,1)) { + if(bufferevent_enabled(s->bev,EV_READ)) { + bufferevent_disable(s->bev,EV_READ); + } + } + } + } + + if(s->st == TLS_SOCKET) { +#if !defined(TURN_NO_TLS) + SSL *ctx = bufferevent_openssl_get_ssl(s->bev); + if(!ctx || SSL_get_shutdown(ctx)) { + s->tobeclosed = 1; + return 0; + } +#endif + } else if(s->st == DTLS_SOCKET) { + if(!(s->ssl) || SSL_get_shutdown(s->ssl)) { + s->tobeclosed = 1; + return 0; + } + } + + if(!(s->e)) + return 0; + + if(s->st == TENTATIVE_TCP_SOCKET) { + EVENT_DEL(s->read_event); +#if !defined(TURN_NO_TLS) + TURN_TLS_TYPE tls_type = check_tentative_tls(s->fd); + if(tls_type) { + s->st = TLS_SOCKET; + if(s->ssl) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!!%s on socket: 0x%lx, st=%d, sat=%d: ssl already exist\n", __FUNCTION__,(long)s, s->st, s->sat); + } + if(s->bev) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!!%s on socket: 0x%lx, st=%d, sat=%d: bev already exist\n", __FUNCTION__,(long)s, s->st, s->sat); + } + switch(tls_type) { +#if defined(SSL_TXT_TLSV1_2) + case TURN_TLS_v1_2: + if(s->e->tls_ctx_v1_2) { + s->ssl = SSL_new(s->e->tls_ctx_v1_2); + STRCPY(s->orig_ctx_type,"TLSv1.2"); + break; + } +#endif +#if defined(SSL_TXT_TLSV1_1) + case TURN_TLS_v1_1: + if(s->e->tls_ctx_v1_1) { + s->ssl = SSL_new(s->e->tls_ctx_v1_1); + STRCPY(s->orig_ctx_type,"TLSv1.1"); + break; + } + +#endif + case TURN_TLS_v1_0: + if(s->e->tls_ctx_v1_0) { + s->ssl = SSL_new(s->e->tls_ctx_v1_0); + STRCPY(s->orig_ctx_type,"TLSv1.0"); + break; + } + default: + if(s->e->tls_ctx_ssl23) { + s->ssl = SSL_new(s->e->tls_ctx_ssl23); + STRCPY(s->orig_ctx_type,"SSLv23"); + } else { + s->tobeclosed = 1; + return 0; + } + }; + s->bev = bufferevent_openssl_socket_new(s->e->event_base, + s->fd, + s->ssl, + BUFFEREVENT_SSL_ACCEPTING, + BEV_OPT_THREADSAFE | BEV_OPT_DEFER_CALLBACKS | BEV_OPT_UNLOCK_CALLBACKS); + bufferevent_setcb(s->bev, socket_input_handler_bev, socket_output_handler_bev, + eventcb_bev, s); + bufferevent_setwatermark(s->bev, EV_READ|EV_WRITE, 0, BUFFEREVENT_HIGH_WATERMARK); + bufferevent_enable(s->bev, EV_READ|EV_WRITE); /* Start reading. */ + } else +#endif //TURN_NO_TLS + { + s->st = TCP_SOCKET; + if(s->bev) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!!%s on socket: 0x%lx, st=%d, sat=%d: bev already exist\n", __FUNCTION__,(long)s, s->st, s->sat); + } + s->bev = bufferevent_socket_new(s->e->event_base, + s->fd, + BEV_OPT_THREADSAFE | BEV_OPT_DEFER_CALLBACKS | BEV_OPT_UNLOCK_CALLBACKS); + bufferevent_setcb(s->bev, socket_input_handler_bev, socket_output_handler_bev, + eventcb_bev, s); + bufferevent_setwatermark(s->bev, EV_READ|EV_WRITE, 0, BUFFEREVENT_HIGH_WATERMARK); + bufferevent_enable(s->bev, EV_READ|EV_WRITE); /* Start reading. */ + } + } + + try_start: + + if(!(s->e)) + return 0; + + try_again=0; + try_ok=0; + + stun_buffer_list_elem *buf_elem = new_blist_elem(s->e); + len = -1; + + if(s->bev) { /* TCP & TLS */ + struct evbuffer *inbuf = bufferevent_get_input(s->bev); + if(inbuf) { + ev_ssize_t blen = evbuffer_copyout(inbuf, buf_elem->buf.buf, STUN_BUFFER_SIZE); + if(blen>0) { + int mlen = 0; + + if(blen>(ev_ssize_t)STUN_BUFFER_SIZE) + blen=(ev_ssize_t)STUN_BUFFER_SIZE; + + if(((s->st == TCP_SOCKET)||(s->st == TLS_SOCKET)) && ((s->sat == TCP_CLIENT_DATA_SOCKET)||(s->sat==TCP_RELAY_DATA_SOCKET))) { + mlen = blen; + } else { + mlen = stun_get_message_len_str(buf_elem->buf.buf, blen, 1, &app_msg_len); + } + + if(mlen>0 && mlen<=(int)blen) { + len = (int)bufferevent_read(s->bev, buf_elem->buf.buf, mlen); + if(len < 0) { + ret = -1; + s->tobeclosed = 1; + s->broken = 1; + log_socket_event(s, "socket read failed, to be closed",1); + } else if(s->st == TLS_SOCKET) { +#if !defined(TURN_NO_TLS) + SSL *ctx = bufferevent_openssl_get_ssl(s->bev); + if(!ctx || SSL_get_shutdown(ctx)) { + ret = -1; + s->tobeclosed = 1; + } +#endif + } + if(ret != -1) { + ret = len; + } + } + + } else if(blen<0) { + s->tobeclosed = 1; + s->broken = 1; + ret = -1; + log_socket_event(s, "socket buffer copy failed, to be closed",1); + } + } else { + s->tobeclosed = 1; + s->broken = 1; + ret = -1; + log_socket_event(s, "socket input failed, socket to be closed",1); + } + + if(len == 0) + len = -1; + } else if(s->fd>=0){ /* UDP and DTLS */ + ret = udp_recvfrom(s->fd, &remote_addr, &(s->local_addr), (s08bits*)(buf_elem->buf.buf), UDP_STUN_BUFFER_SIZE, &ttl, &tos, s->e->cmsg, 0, NULL); + len = ret; + if(s->ssl && (len>0)) { /* DTLS */ + send_ssl_backlog_buffers(s); + buf_elem->buf.len = (size_t)len; + ret = ssl_read(s->fd, s->ssl, (ioa_network_buffer_handle)buf_elem, ((s->e) && s->e->verbose)); + addr_cpy(&remote_addr,&(s->remote_addr)); + if(ret < 0) { + len = -1; + s->tobeclosed = 1; + s->broken = 1; + log_socket_event(s, "SSL read failed, to be closed",0); + } else { + len = (int)ioa_network_buffer_get_size((ioa_network_buffer_handle)buf_elem); + } + if((ret!=-1)&&(len>0)) + try_again = 1; + } else { /* UDP */ + if(ret>=0) + try_again = 1; + } + } else { + s->tobeclosed = 1; + s->broken = 1; + ret = -1; + log_socket_event(s, "socket unknown error, to be closed",1); + } + + if ((ret!=-1) && (len >= 0)) { + if(ioa_socket_check_bandwidth(s,(size_t)len,1)) { + if(app_msg_len) + buf_elem->buf.len = app_msg_len; + else + buf_elem->buf.len = len; + + if(s->read_cb) { + ioa_net_data nd; + + ns_bzero(&nd,sizeof(ioa_net_data)); + addr_cpy(&(nd.src_addr),&remote_addr); + nd.nbh = buf_elem; + nd.recv_ttl = ttl; + nd.recv_tos = tos; + + s->read_cb(s, IOA_EV_READ, &nd, s->read_ctx); + + if(nd.nbh) + free_blist_elem(s->e,buf_elem); + + buf_elem = NULL; + + try_ok = 1; + + } else { + ioa_network_buffer_delete(s->e, s->defer_nbh); + s->defer_nbh = buf_elem; + buf_elem = NULL; + } + } + } + + if(buf_elem) { + free_blist_elem(s->e,buf_elem); + buf_elem = NULL; + } + + if(try_again && try_ok && !(s->done) && + !(s->tobeclosed) && ((++try_cycle)parent_s)) { + goto try_start; + } + + return len; +} + +static void socket_input_handler(evutil_socket_t fd, short what, void* arg) +{ + + if (!(what & EV_READ)) + return; + + if(!arg) { + read_spare_buffer(fd); + return; + } + + ioa_socket_handle s = (ioa_socket_handle)arg; + + if(!s) + return; + + if((s->magic != SOCKET_MAGIC)||(s->done)) { + read_spare_buffer(fd); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!!%s on bad socket, ev=%d: 0x%lx, st=%d, sat=%d\n", __FUNCTION__,(int)what,(long)s, s->st, s->sat); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!! %s socket: 0x%lx was closed\n", __FUNCTION__,(long)s); + return; + } + + if(fd != s->fd) { + read_spare_buffer(fd); + return; + } + + if (!ioa_socket_tobeclosed(s)) + socket_input_worker(s); + + if((s->magic != SOCKET_MAGIC)||(s->done)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!!%s (1) on socket, ev=%d: 0x%lx, st=%d, sat=%d\n", __FUNCTION__,(int)what,(long)s, s->st, s->sat); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!! %s socket: 0x%lx was closed\n", __FUNCTION__,(long)s); + return; + } + + close_ioa_socket_after_processing_if_necessary(s); +} + +void close_ioa_socket_after_processing_if_necessary(ioa_socket_handle s) +{ + if (s && ioa_socket_tobeclosed(s)) { + switch (s->sat){ + case TCP_CLIENT_DATA_SOCKET: + case TCP_RELAY_DATA_SOCKET: + { + tcp_connection *tc = s->sub_session; + if (tc) { + delete_tcp_connection(tc); + } + } + break; + default: + { + ts_ur_super_session *ss = s->session; + if (ss) { + turn_turnserver *server = (turn_turnserver *) ss->server; + if (server) { + shutdown_client_connection(server, ss, 0, "general"); + } + } + } + } + } +} + +static void socket_output_handler_bev(struct bufferevent *bev, void* arg) +{ + + UNUSED_ARG(bev); + UNUSED_ARG(arg); + + if (tcp_congestion_control) { + + if (bev && arg) { + + ioa_socket_handle s = (ioa_socket_handle) arg; + + if ((s->magic != SOCKET_MAGIC)||(s->done)||ioa_socket_tobeclosed(s)||(bev != s->bev)) { + return; + } + + if (s->sub_session) { + + if (s == s->sub_session->client_s) { + if (s->sub_session->peer_s && s->sub_session->peer_s->bev) { + if (!bufferevent_enabled(s->sub_session->peer_s->bev, + EV_READ)) { + if (is_socket_writeable(s->sub_session->peer_s, + STUN_BUFFER_SIZE, __FUNCTION__, 3)) { + bufferevent_enable(s->sub_session->peer_s->bev,EV_READ); + socket_input_handler_bev( + s->sub_session->peer_s->bev, + s->sub_session->peer_s); + } + } + } + } else if (s == s->sub_session->peer_s) { + if (s->sub_session->client_s + && s->sub_session->client_s->bev) { + if (!bufferevent_enabled(s->sub_session->client_s->bev, + EV_READ)) { + if (is_socket_writeable(s->sub_session->client_s, + STUN_BUFFER_SIZE, __FUNCTION__, 4)) { + bufferevent_enable(s->sub_session->client_s->bev, EV_READ); + socket_input_handler_bev( + s->sub_session->client_s->bev, + s->sub_session->client_s); + } + } + } + } + } + } + } +} + +static void socket_input_handler_bev(struct bufferevent *bev, void* arg) +{ + + if (bev && arg) { + + ioa_socket_handle s = (ioa_socket_handle) arg; + + if(bev != s->bev) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!! %s socket: 0x%lx: wrong bev\n", __FUNCTION__,(long)s); + return; + } + + if((s->magic != SOCKET_MAGIC)||(s->done)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!!%s on socket: 0x%lx, st=%d, sat=%d\n", __FUNCTION__, (long) s, s->st, s->sat); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!! %s socket: 0x%lx was closed\n", __FUNCTION__,(long)s); + return; + } + + while (!ioa_socket_tobeclosed(s)) { + if (socket_input_worker(s) <= 0) + break; + } + + if((s->magic != SOCKET_MAGIC)||(s->done)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!!%s (1) on socket: 0x%lx, st=%d, sat=%d\n", __FUNCTION__, (long) s, s->st, s->sat); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!! %s socket: 0x%lx was closed\n", __FUNCTION__,(long)s); + return; + } + + if (ioa_socket_tobeclosed(s)) { + switch(s->sat) { + case TCP_CLIENT_DATA_SOCKET: + case TCP_RELAY_DATA_SOCKET: + { + tcp_connection *tc = s->sub_session; + if(tc) { + delete_tcp_connection(tc); + } + } + break; + default: + { + ts_ur_super_session *ss = s->session; + if (ss) { + turn_turnserver *server = (turn_turnserver *)ss->server; + if (server) { + shutdown_client_connection(server, ss, 0, "TCP socket buffer operation error (input handler)"); + } + } + } + } + } + } +} + +static void eventcb_bev(struct bufferevent *bev, short events, void *arg) +{ + UNUSED_ARG(bev); + + if (events & BEV_EVENT_CONNECTED) { + // Connect okay + } else if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) { + if (arg) { + ioa_socket_handle s = (ioa_socket_handle) arg; + + if((s->st != TCP_SOCKET)&&(s->st != TLS_SOCKET)&&(s->st != TENTATIVE_TCP_SOCKET)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!! %s: socket type is wrong on the socket: 0x%lx, st=%d, sat=%d\n",__FUNCTION__,(long)s,s->st,s->sat); + return; + } + + if(s->magic != SOCKET_MAGIC) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!! %s: magic is wrong on the socket: 0x%lx, st=%d, sat=%d\n",__FUNCTION__,(long)s,s->st,s->sat); + return; + } + + if (s->done) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!! %s: closed socket: 0x%lx (1): done=%d, fd=%d, br=%d, st=%d, sat=%d, tbc=%d\n", __FUNCTION__, (long) s, (int) s->done, + (int) s->fd, s->broken, s->st, s->sat, s->tobeclosed); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!! %s socket: 0x%lx was closed\n", __FUNCTION__,(long)s); + return; + } + + if (events & BEV_EVENT_ERROR) + s->broken = 1; + + s->tobeclosed = 1; + + switch (s->sat){ + case TCP_CLIENT_DATA_SOCKET: + case TCP_RELAY_DATA_SOCKET: + { + tcp_connection *tc = s->sub_session; + if (tc) { + delete_tcp_connection(tc); + } + } + break; + default: + { + ts_ur_super_session *ss = s->session; + if (ss) { + turn_turnserver *server = (turn_turnserver *) ss->server; + if (server) { + + + { + char sraddr[129]="\0"; + addr_to_string(&(s->remote_addr),(u08bits*)sraddr); + if (events & BEV_EVENT_EOF) { + if(server->verbose) + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"session %018llu: TCP socket closed remotely %s\n",(unsigned long long)(ss->id),sraddr); + if(s == ss->client_session.s) { + shutdown_client_connection(server, ss, 0, "TCP connection closed by client (callback)"); + } else if(s == ss->alloc.relay_session.s) { + shutdown_client_connection(server, ss, 0, "TCP connection closed by peer (callback)"); + } else { + shutdown_client_connection(server, ss, 0, "TCP connection closed by remote party (callback)"); + } + } else if (events & BEV_EVENT_ERROR) { + if(EVUTIL_SOCKET_ERROR()) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"session %018llu: TCP socket error: %s %s\n",(unsigned long long)(ss->id), + evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()), sraddr); + } else if(server->verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"session %018llu: TCP socket disconnected: %s\n",(unsigned long long)(ss->id),sraddr); + } + shutdown_client_connection(server, ss, 0, "TCP socket buffer operation error (callback)"); + } + } + } + } + } + }; + } + } +} + +static int ssl_send(ioa_socket_handle s, const s08bits* buffer, int len, int verbose) +{ + + if (!s || !(s->ssl) || !buffer || (s->fd<0)) + return -1; + + SSL *ssl = s->ssl; + + if (eve(verbose)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: before write: buffer=0x%lx, len=%d\n", __FUNCTION__,(long)buffer,len); + } + + if(s->parent_s) { + /* Trick only for "children" sockets: */ + BIO *wbio = SSL_get_wbio(ssl); + if(!wbio) + return -1; + int fd = BIO_get_fd(wbio,0); + int sfd = s->parent_s->fd; + if(sfd >= 0) { + if(fd != sfd) { + BIO_set_fd(wbio,sfd,BIO_NOCLOSE); + } + } + } else { + BIO *wbio = SSL_get_wbio(ssl); + if(!wbio) + return -1; + int fd = BIO_get_fd(wbio,0); + if(fd != s->fd) { + BIO_set_fd(wbio,s->fd,BIO_NOCLOSE); + } + } + + int rc = 0; + int try_again = 1; + +#if !defined(TURN_IP_RECVERR) + try_again = 0; +#endif + + try_start: + + do { + rc = SSL_write(ssl, buffer, len); + } while (rc < 0 && errno == EINTR); + + if (eve(verbose)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: after write: %d\n", __FUNCTION__,rc); + } + + if (rc < 0 && ((errno == ENOBUFS) || (errno == EAGAIN))) { + if (eve(verbose)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: ENOBUFS/EAGAIN\n", __FUNCTION__); + } + return 0; + } + + if (rc >= 0) { + + if (eve(verbose)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: wrote %d bytes\n", __FUNCTION__, (int) rc); + } + + return rc; + + } else { + + if (eve(verbose)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: failure: rc=%d, err=%d\n", __FUNCTION__, (int)rc,(int)SSL_get_error(ssl, rc)); + } + + switch (SSL_get_error(ssl, rc)){ + case SSL_ERROR_NONE: + //??? + if (eve(verbose)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "wrote %d bytes\n", (int) rc); + } + return 0; + case SSL_ERROR_WANT_WRITE: + return 0; + case SSL_ERROR_WANT_READ: + return 0; + case SSL_ERROR_SYSCALL: + { + int err = errno; + if (!handle_socket_error()) { + if(s->st == DTLS_SOCKET) { + if(is_connreset()) { + if(try_again) { + BIO *wbio = SSL_get_wbio(ssl); + if(wbio) { + int fd = BIO_get_fd(wbio,0); + if(fd>=0) { + try_again = 0; + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "DTLS Socket, tring to recover write operation...\n"); + socket_readerr(fd, &(s->local_addr)); + goto try_start; + } + } + } + } + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "DTLS Socket lost packet... fine\n"); + return 0; + } + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "DTLS Socket write error unrecoverable: %d; buffer=0x%lx, len=%d, ssl=0x%lx\n", err, (long)buffer, (int)len, (long)ssl); + return -1; + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "DTLS Socket write error recoverable: %d\n", err); + return 0; + } + } + case SSL_ERROR_SSL: + if (verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "SSL write error: "); + s08bits buf[65536]; + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s (%d)\n", ERR_error_string(ERR_get_error(), buf), + SSL_get_error(ssl, rc)); + } + return -1; + default: + if (verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Unexpected error while writing!\n"); + } + return -1; + } + } +} + +static int send_ssl_backlog_buffers(ioa_socket_handle s) +{ + int ret = 0; + if(s) { + stun_buffer_list_elem *buf_elem = s->bufs.head; + while(buf_elem) { + int rc = ssl_send(s, (s08bits*)buf_elem->buf.buf + buf_elem->buf.offset - buf_elem->buf.coffset, (size_t)buf_elem->buf.len, ((s->e) && s->e->verbose)); + if(rc<1) + break; + ++ret; + pop_elem_from_buffer_list(&(s->bufs)); + buf_elem = s->bufs.head; + } + } + + return ret; +} + +int is_connreset(void) { + switch (errno) { + case ECONNRESET: + case ECONNREFUSED: + return 1; + default: + ; + } + return 0; +} + +int would_block(void) { +#if defined(EWOULDBLOCK) + if(errno == EWOULDBLOCK) + return 1; +#endif + return (errno == EAGAIN); +} + +int udp_send(ioa_socket_handle s, const ioa_addr* dest_addr, const s08bits* buffer, int len) +{ + int rc = 0; + evutil_socket_t fd = -1; + + if(!s) + return -1; + + if(s->parent_s) + fd = s->parent_s->fd; + else + fd = s->fd; + + if(fd>=0) { + + int try_again = 1; + + int cycle; + +#if !defined(TURN_IP_RECVERR) + try_again = 0; +#endif + + try_start: + + cycle = 0; + + if (dest_addr) { + + int slen = get_ioa_addr_len(dest_addr); + + do { + rc = sendto(fd, buffer, len, 0, (const struct sockaddr*) dest_addr, (socklen_t) slen); + } while ( + ((rc < 0) && (errno == EINTR)) || + ((rc<0) && is_connreset() && (++cyclelocal_addr)); + goto try_start; + } + //Lost packet - sent to nowhere... fine. + rc = len; + } + } + } + + return rc; +} + +int send_data_from_ioa_socket_nbh(ioa_socket_handle s, ioa_addr* dest_addr, + ioa_network_buffer_handle nbh, + int ttl, int tos) +{ + int ret = -1; + + if(!s) { + ioa_network_buffer_delete(NULL, nbh); + return -1; + } + + if (s->done || (s->fd == -1)) { + TURN_LOG_FUNC( + TURN_LOG_LEVEL_INFO, + "!!! %s: (1) Trying to send data from closed socket: 0x%lx (1): done=%d, fd=%d, st=%d, sat=%d\n", + __FUNCTION__, (long) s, (int) s->done, + (int) s->fd, s->st, s->sat); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!! %s socket: 0x%lx was closed\n", __FUNCTION__,(long)s); + + } else if (nbh) { + if(!ioa_socket_check_bandwidth(s,ioa_network_buffer_get_size(nbh),0)) { + /* Bandwidth exhausted, we pretend everything is fine: */ + ret = (int)(ioa_network_buffer_get_size(nbh)); + } else { + if (!ioa_socket_tobeclosed(s) && s->e) { + + if (!(s->done || (s->fd == -1))) { + set_socket_ttl(s, ttl); + set_socket_tos(s, tos); + + if (s->connected && s->bev) { + if (s->st == TLS_SOCKET) { +#if !defined(TURN_NO_TLS) + SSL *ctx = bufferevent_openssl_get_ssl(s->bev); + if (!ctx || SSL_get_shutdown(ctx)) { + s->tobeclosed = 1; + ret = 0; + } +#endif + } + + if (!(s->tobeclosed)) { + + ret = (int) ioa_network_buffer_get_size(nbh); + + if(!udp_congestion_control || is_socket_writeable(s,(size_t)ret,__FUNCTION__,2)) { + if (bufferevent_write( + s->bev, + ioa_network_buffer_data(nbh), + ioa_network_buffer_get_size(nbh)) + < 0) { + ret = -1; + perror("bufev send"); + log_socket_event(s, "socket write failed, to be closed",1); + s->tobeclosed = 1; + s->broken = 1; + } + } else { + //drop the packet + ; + } + } + } else if (s->ssl) { + send_ssl_backlog_buffers(s); + ret = ssl_send( + s, + (s08bits*) ioa_network_buffer_data(nbh), + ioa_network_buffer_get_size(nbh), + ((s->e) && s->e->verbose)); + if (ret < 0) + s->tobeclosed = 1; + else if (ret == 0) + add_buffer_to_buffer_list( + &(s->bufs), + (s08bits*) ioa_network_buffer_data(nbh), + ioa_network_buffer_get_size(nbh)); + } else if (s->fd >= 0) { + + if (s->connected && !(s->parent_s)) { + dest_addr = NULL; /* ignore dest_addr */ + } else if (!dest_addr) { + dest_addr = &(s->remote_addr); + } + + ret = udp_send(s, + dest_addr, + (s08bits*) ioa_network_buffer_data(nbh),ioa_network_buffer_get_size(nbh)); + if (ret < 0) { + s->tobeclosed = 1; +#if defined(EADDRNOTAVAIL) + int perr=errno; +#endif + perror("udp send"); +#if defined(EADDRNOTAVAIL) + if(dest_addr && (perr==EADDRNOTAVAIL)) { + char sfrom[129]; + addr_to_string(&(s->local_addr), (u08bits*)sfrom); + char sto[129]; + addr_to_string(dest_addr, (u08bits*)sto); + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: network error: address unreachable from %s to %s\n", + __FUNCTION__,sfrom,sto); + } +#endif + } + } + } + } + } + } + + ioa_network_buffer_delete(s->e, nbh); + + return ret; +} + +int register_callback_on_ioa_socket(ioa_engine_handle e, ioa_socket_handle s, int event_type, ioa_net_event_handler cb, void* ctx, int clean_preexisting) +{ + if(s) { + + if (event_type & IOA_EV_READ) { + + if(e) + s->e = e; + + if(s->e && !(s->parent_s)) { + + switch(s->st) { + case DTLS_SOCKET: + case UDP_SOCKET: + if(s->read_event) { + if(!clean_preexisting) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: software error: buffer preset 1\n", __FUNCTION__); + return -1; + } + } else { + s->read_event = event_new(s->e->event_base,s->fd, EV_READ|EV_PERSIST, socket_input_handler, s); + event_add(s->read_event,NULL); + } + break; + case TENTATIVE_TCP_SOCKET: + if(s->bev) { + if(!clean_preexisting) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: software error: buffer preset 2\n", __FUNCTION__); + return -1; + } + } else if(s->read_event) { + if(!clean_preexisting) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: software error: buffer preset 3\n", __FUNCTION__); + return -1; + } + } else { + s->read_event = event_new(s->e->event_base,s->fd, EV_READ|EV_PERSIST, socket_input_handler, s); + event_add(s->read_event,NULL); + } + break; + case TCP_SOCKET: + if(s->bev) { + if(!clean_preexisting) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: software error: buffer preset 4\n", __FUNCTION__); + return -1; + } + } else { + s->bev = bufferevent_socket_new(s->e->event_base, + s->fd, + BEV_OPT_THREADSAFE | BEV_OPT_DEFER_CALLBACKS | BEV_OPT_UNLOCK_CALLBACKS); + bufferevent_setcb(s->bev, socket_input_handler_bev, socket_output_handler_bev, + eventcb_bev, s); + bufferevent_setwatermark(s->bev, EV_READ|EV_WRITE, 0, BUFFEREVENT_HIGH_WATERMARK); + bufferevent_enable(s->bev, EV_READ|EV_WRITE); /* Start reading. */ + } + break; + case TLS_SOCKET: + if(s->bev) { + if(!clean_preexisting) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: software error: buffer preset 5\n", __FUNCTION__); + return -1; + } + } else { +#if !defined(TURN_NO_TLS) + if(!(s->ssl)) { + //??? how we can get to this point ??? + s->ssl = SSL_new(e->tls_ctx_ssl23); + STRCPY(s->orig_ctx_type,"SSLv23"); + s->bev = bufferevent_openssl_socket_new(s->e->event_base, + s->fd, + s->ssl, + BUFFEREVENT_SSL_ACCEPTING, + BEV_OPT_THREADSAFE | BEV_OPT_DEFER_CALLBACKS | BEV_OPT_UNLOCK_CALLBACKS); + } else { + s->bev = bufferevent_openssl_socket_new(s->e->event_base, + s->fd, + s->ssl, + BUFFEREVENT_SSL_OPEN, + BEV_OPT_THREADSAFE | BEV_OPT_DEFER_CALLBACKS | BEV_OPT_UNLOCK_CALLBACKS); + } + bufferevent_setcb(s->bev, socket_input_handler_bev, socket_output_handler_bev, + eventcb_bev, s); + bufferevent_setwatermark(s->bev, EV_READ|EV_WRITE, 0, BUFFEREVENT_HIGH_WATERMARK); + bufferevent_enable(s->bev, EV_READ|EV_WRITE); /* Start reading. */ +#endif + } + break; + default: + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: software error: unknown socket type: %d\n", __FUNCTION__,(int)(s->st)); + return -1; + } + } + + s->read_cb = cb; + s->read_ctx = ctx; + return 0; + } + } + + /* unsupported event or else */ + return -1; +} + +int ioa_socket_tobeclosed(ioa_socket_handle s) +{ + if(s) { + if(s->magic != SOCKET_MAGIC) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!! %s: magic is wrong on the socket: 0x%lx, st=%d, sat=%d\n",__FUNCTION__,(long)s,s->st,s->sat); + return 1; + } + + if(s->done) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!! %s: ceck on already closed socket: 0x%lx, st=%d, sat=%d\n",__FUNCTION__,(long)s,s->st,s->sat); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!! %s socket: 0x%lx was closed\n", __FUNCTION__,(long)s); + return 1; + } + if(s->broken) { + log_socket_event(s, "socket broken", 0); + return 1; + } else if(s->tobeclosed) { + log_socket_event(s, "socket to be closed", 0); + return 1; + } else if(s->fd < 0) { + log_socket_event(s, "socket fd<0", 0); + return 1; + } else if(s->ssl) { + if(SSL_get_shutdown(s->ssl)) { + log_socket_event(s, "socket SSL shutdown", 0); + return 1; + } + } + } + return 0; +} + +void set_ioa_socket_tobeclosed(ioa_socket_handle s) +{ + if(s) + s->tobeclosed = 1; +} + +static u32bits string_hash(const u08bits *str) { + + u32bits hash = 0; + int c = 0; + + while ((c = *str++)) + hash = c + (hash << 6) + (hash << 16) - hash; + + return hash; +} + +int check_username_hash(ioa_socket_handle s, u08bits *username, u08bits *realm) +{ + if(s) { + if(username && username[0]) { + if(s->username_hash != string_hash(username)) { + return 0; + } + } + if(realm && realm[0]) { + if(s->realm_hash != string_hash(realm)) { + return 0; + } + } + } + return 1; +} + +void set_username_hash(ioa_socket_handle s, u08bits *username, u08bits *realm) +{ + if(s) { + if(username && username[0]) { + s->username_hash = string_hash(username); + } + if(realm && realm[0]) { + s->realm_hash = string_hash(realm); + } + } +} + +/* + * Network buffer functions + */ +ioa_network_buffer_handle ioa_network_buffer_allocate(ioa_engine_handle e) +{ + stun_buffer_list_elem *buf_elem = new_blist_elem(e); + buf_elem->buf.len = 0; + buf_elem->buf.offset = 0; + buf_elem->buf.coffset = 0; + return buf_elem; +} + +/* We do not use special header in this simple implementation */ +void ioa_network_buffer_header_init(ioa_network_buffer_handle nbh) +{ + UNUSED_ARG(nbh); +} + +u08bits *ioa_network_buffer_data(ioa_network_buffer_handle nbh) +{ + stun_buffer_list_elem *buf_elem = (stun_buffer_list_elem *)nbh; + return buf_elem->buf.buf + buf_elem->buf.offset - buf_elem->buf.coffset; +} + +size_t ioa_network_buffer_get_size(ioa_network_buffer_handle nbh) +{ + if(!nbh) + return 0; + else { + stun_buffer_list_elem *buf_elem = (stun_buffer_list_elem *)nbh; + return (size_t)(buf_elem->buf.len); + } +} + +size_t ioa_network_buffer_get_capacity(ioa_network_buffer_handle nbh) +{ + if(!nbh) + return 0; + else { + stun_buffer_list_elem *buf_elem = (stun_buffer_list_elem *)nbh; + if(buf_elem->buf.offset < STUN_BUFFER_SIZE) { + return (STUN_BUFFER_SIZE - buf_elem->buf.offset); + } + return 0; + } +} + +size_t ioa_network_buffer_get_capacity_udp(void) +{ + return UDP_STUN_BUFFER_SIZE; +} + +void ioa_network_buffer_set_size(ioa_network_buffer_handle nbh, size_t len) +{ + stun_buffer_list_elem *buf_elem = (stun_buffer_list_elem *)nbh; + buf_elem->buf.len=(size_t)len; +} + +void ioa_network_buffer_add_offset_size(ioa_network_buffer_handle nbh, u16bits offset, u08bits coffset, size_t len) +{ + stun_buffer_list_elem *buf_elem = (stun_buffer_list_elem *)nbh; + buf_elem->buf.len=(size_t)len; + buf_elem->buf.offset += offset; + buf_elem->buf.coffset += coffset; + + if((buf_elem->buf.offset + buf_elem->buf.len - buf_elem->buf.coffset)>=sizeof(buf_elem->buf.buf) || + (buf_elem->buf.offset + sizeof(buf_elem->buf.channel) < buf_elem->buf.coffset) + ) { + buf_elem->buf.coffset = 0; + buf_elem->buf.len = 0; + buf_elem->buf.offset = 0; + } +} + +u16bits ioa_network_buffer_get_offset(ioa_network_buffer_handle nbh) +{ + stun_buffer_list_elem *buf_elem = (stun_buffer_list_elem *)nbh; + return buf_elem->buf.offset; +} + +u08bits ioa_network_buffer_get_coffset(ioa_network_buffer_handle nbh) +{ + stun_buffer_list_elem *buf_elem = (stun_buffer_list_elem *)nbh; + return buf_elem->buf.coffset; +} + +void ioa_network_buffer_delete(ioa_engine_handle e, ioa_network_buffer_handle nbh) { + stun_buffer_list_elem *buf_elem = (stun_buffer_list_elem *)nbh; + free_blist_elem(e,buf_elem); +} + +/////////// REPORTING STATUS ///////////////////// + +void turn_report_allocation_set(void *a, turn_time_t lifetime, int refresh) +{ + if(a) { + ts_ur_super_session *ss = (ts_ur_super_session*)(((allocation*)a)->owner); + if(ss) { + const char* status="new"; + if(refresh) + status="refreshed"; + turn_turnserver *server = (turn_turnserver*)ss->server; + if(server) { + ioa_engine_handle e = turn_server_get_engine(server); + if(e && e->verbose) { + if(ss->client_session.s->ssl) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"session %018llu: %s, realm=<%s>, username=<%s>, lifetime=%lu, cipher=%s, method=%s (%s)\n", (unsigned long long)ss->id, status, (char*)ss->realm_options.name, (char*)ss->username, (unsigned long)lifetime, SSL_get_cipher(ss->client_session.s->ssl), + turn_get_ssl_method(ss->client_session.s->ssl, ss->client_session.s->orig_ctx_type),ss->client_session.s->orig_ctx_type); + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"session %018llu: %s, realm=<%s>, username=<%s>, lifetime=%lu\n", (unsigned long long)ss->id, status, (char*)ss->realm_options.name, (char*)ss->username, (unsigned long)lifetime); + } + } +#if !defined(TURN_NO_HIREDIS) + { + char key[1024]; + if(ss->realm_options.name[0]) { + snprintf(key,sizeof(key),"turn/realm/%s/user/%s/allocation/%018llu/status",ss->realm_options.name,(char*)ss->username, (unsigned long long)ss->id); + } else { + snprintf(key,sizeof(key),"turn/user/%s/allocation/%018llu/status",(char*)ss->username, (unsigned long long)ss->id); + } + send_message_to_redis(e->rch, "set", key, "%s lifetime=%lu", status, (unsigned long)lifetime); + send_message_to_redis(e->rch, "publish", key, "%s lifetime=%lu", status, (unsigned long)lifetime); + } +#endif + } + } + } +} + +void turn_report_allocation_delete(void *a) +{ + if(a) { + ts_ur_super_session *ss = (ts_ur_super_session*)(((allocation*)a)->owner); + if(ss) { + turn_turnserver *server = (turn_turnserver*)ss->server; + if(server) { + ioa_engine_handle e = turn_server_get_engine(server); + if(e && e->verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"session %018llu: delete: realm=<%s>, username=<%s>\n", (unsigned long long)ss->id, (char*)ss->realm_options.name, (char*)ss->username); + } +#if !defined(TURN_NO_HIREDIS) + { + char key[1024]; + if(ss->realm_options.name[0]) { + snprintf(key,sizeof(key),"turn/realm/%s/user/%s/allocation/%018llu/status",ss->realm_options.name,(char*)ss->username, (unsigned long long)ss->id); + } else { + snprintf(key,sizeof(key),"turn/user/%s/allocation/%018llu/status",(char*)ss->username, (unsigned long long)ss->id); + } + send_message_to_redis(e->rch, "del", key, ""); + send_message_to_redis(e->rch, "publish", key, "deleted"); + } +#endif + } + } + } +} + +void turn_report_session_usage(void *session) +{ + if(session) { + ts_ur_super_session *ss = (ts_ur_super_session *)session; + turn_turnserver *server = (turn_turnserver*)ss->server; + if(server && (ss->received_packets || ss->sent_packets)) { + ioa_engine_handle e = turn_server_get_engine(server); + if(((ss->received_packets+ss->sent_packets)&2047)==0) { + if(e && e->verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"session %018llu: usage: realm=<%s>, username=<%s>, rp=%lu, rb=%lu, sp=%lu, sb=%lu\n", (unsigned long long)(ss->id), (char*)ss->realm_options.name, (char*)ss->username, (unsigned long)(ss->received_packets), (unsigned long)(ss->received_bytes),(unsigned long)(ss->sent_packets),(unsigned long)(ss->sent_bytes)); + } +#if !defined(TURN_NO_HIREDIS) + { + char key[1024]; + if(ss->realm_options.name[0]) { + snprintf(key,sizeof(key),"turn/realm/%s/user/%s/allocation/%018llu/traffic",ss->realm_options.name,(char*)ss->username, (unsigned long long)(ss->id)); + } else { + snprintf(key,sizeof(key),"turn/user/%s/allocation/%018llu/traffic",(char*)ss->username, (unsigned long long)(ss->id)); + } + send_message_to_redis(e->rch, "publish", key, "rcvp=%lu, rcvb=%lu, sentp=%lu, sentb=%lu",(unsigned long)(ss->received_packets), (unsigned long)(ss->received_bytes),(unsigned long)(ss->sent_packets),(unsigned long)(ss->sent_bytes)); + } +#endif + ss->t_received_packets += ss->received_packets; + ss->t_received_bytes += ss->received_bytes; + ss->t_sent_packets += ss->sent_packets; + ss->t_sent_bytes += ss->sent_bytes; + + { + turn_time_t ct = get_turn_server_time(server); + if(ct != ss->start_time) { + ct = ct - ss->start_time; + ss->received_rate = (u32bits)(ss->t_received_bytes / ct); + ss->sent_rate = (u32bits)(ss->t_sent_bytes / ct); + ss->total_rate = ss->received_rate + ss->sent_rate; + } + } + + report_turn_session_info(server,ss,0); + + ss->received_packets=0; + ss->received_bytes=0; + ss->sent_packets=0; + ss->sent_bytes=0; + } + } + } +} + +/////////////// SSL /////////////////// + + +const char* get_ioa_socket_tls_cipher(ioa_socket_handle s) +{ + if(s && (s->ssl)) + return SSL_get_cipher(s->ssl); + return ""; +} + +const char* get_ioa_socket_tls_method(ioa_socket_handle s) +{ + if(s && (s->ssl)) + return turn_get_ssl_method(s->ssl,"UNKNOWN"); + return ""; +} + +///////////// Super Memory Region ////////////// + +#define TURN_SM_SIZE (1024<<11) + +struct _super_memory { + pthread_mutex_t mutex_sm; + char **super_memory; + size_t *sm_allocated; + size_t sm_total_sz; + size_t sm_chunk; + u32bits id; +}; + +static void init_super_memory_region(super_memory_t *r) +{ + if(r) { + ns_bzero(r,sizeof(super_memory_t)); + + r->super_memory = (char**)malloc(sizeof(char*)); + r->super_memory[0] = (char*)malloc(TURN_SM_SIZE); + ns_bzero(r->super_memory[0],TURN_SM_SIZE); + + r->sm_allocated = (size_t*)malloc(sizeof(size_t*)); + r->sm_allocated[0] = 0; + + r->sm_total_sz = TURN_SM_SIZE; + r->sm_chunk = 0; + + while(r->id == 0) + r->id = (u32bits)random(); + + pthread_mutex_init(&r->mutex_sm, NULL); + } +} + +void init_super_memory(void) +{ + ; +} + +super_memory_t* new_super_memory_region(void) +{ + super_memory_t* r = (super_memory_t*)malloc(sizeof(super_memory_t)); + init_super_memory_region(r); + return r; +} + +void* allocate_super_memory_region_func(super_memory_t *r, size_t size, const char* file, const char* func, int line) +{ + UNUSED_ARG(file); + UNUSED_ARG(func); + UNUSED_ARG(line); + + if(!r) + return malloc(size); + + void *ret = NULL; + + pthread_mutex_lock(&r->mutex_sm); + + size = ((size_t)((size+sizeof(void*))/(sizeof(void*)))) * sizeof(void*); + + if(size>=TURN_SM_SIZE) { + + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"(%s:%s:%d): Size too large for super memory: region id = %u, chunk=%lu, total=%lu, allocated=%lu, want=%lu\n",file,func,line,(unsigned int)r->id, (unsigned long)r->sm_chunk, (unsigned long)r->sm_total_sz, (unsigned long)r->sm_allocated[r->sm_chunk],(unsigned long)size); + + } else { + + size_t i = 0; + char *region = NULL; + size_t *rsz = NULL; + for(i=0;i<=r->sm_chunk;++i) { + + size_t left = (size_t)r->sm_total_sz - r->sm_allocated[i]; + + if(leftsuper_memory[i]; + rsz = r->sm_allocated + i; + break; + } + } + + if(!region) { + r->sm_chunk += 1; + r->super_memory = (char**)realloc(r->super_memory,(r->sm_chunk+1) * sizeof(char*)); + r->super_memory[r->sm_chunk] = (char*)malloc(TURN_SM_SIZE); + ns_bzero(r->super_memory[r->sm_chunk],TURN_SM_SIZE); + r->sm_allocated = (size_t*)realloc(r->sm_allocated,(r->sm_chunk+1) * sizeof(size_t*)); + r->sm_allocated[r->sm_chunk] = 0; + region = r->super_memory[r->sm_chunk]; + rsz = r->sm_allocated + r->sm_chunk; + } + + { + char* ptr = region + *rsz; + + ns_bzero(ptr, size); + + *rsz += size; + + ret = ptr; + } + } + + pthread_mutex_unlock(&r->mutex_sm); + + if(!ret) + ret = malloc(size); + + return ret; +} + +void* allocate_super_memory_engine_func(ioa_engine_handle e, size_t size, const char* file, const char* func, int line) +{ + if(e) + return allocate_super_memory_region_func(e->sm,size,file,func,line); + return allocate_super_memory_region_func(NULL,size,file,func,line); +} + +////////////////////////////////////////////////// diff --git a/src/apps/relay/ns_ioalib_impl.h b/src/apps/relay/ns_ioalib_impl.h new file mode 100644 index 0000000..3fe3529 --- /dev/null +++ b/src/apps/relay/ns_ioalib_impl.h @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * IO Abstraction library + */ + +#ifndef __IOA_LIBIMPL__ +#define __IOA_LIBIMPL__ + +#include +#include +#include + +#include + +#include + +#include "ns_turn_ioalib.h" +#include "turn_ports.h" +#include "ns_turn_maps_rtcp.h" +#include "ns_turn_maps.h" +#include "ns_turn_server.h" + +#include "apputils.h" +#include "stun_buffer.h" +#include "userdb.h" + +#include "ns_sm.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////// + +#define MAX_BUFFER_QUEUE_SIZE_PER_ENGINE (64) +#define MAX_SOCKET_BUFFER_BACKLOG (16) + +#define BUFFEREVENT_HIGH_WATERMARK (128<<10) +#define BUFFEREVENT_MAX_UDP_TO_TCP_WRITE (64<<9) +#define BUFFEREVENT_MAX_TCP_TO_TCP_WRITE (192<<10) + +typedef struct _stun_buffer_list_elem { + struct _stun_buffer_list_elem *next; + stun_buffer buf; +} stun_buffer_list_elem; + +typedef struct _stun_buffer_list { + stun_buffer_list_elem *head; + size_t tsz; +} stun_buffer_list; + +typedef vint band_limit_t; + +/* + * New connection callback + */ + +struct cb_socket_message { + turnserver_id id; + tcp_connection_id connection_id; + stun_tid tid; + ioa_socket_handle s; + int message_integrity; +}; + +struct relay_server { + turnserver_id id; + super_memory_t* sm; + struct event_base* event_base; + struct bufferevent *in_buf; + struct bufferevent *out_buf; + struct bufferevent *auth_in_buf; + struct bufferevent *auth_out_buf; + ioa_engine_handle ioa_eng; + turn_turnserver server; + pthread_t thr; +}; + +struct message_to_relay { + MESSAGE_TO_RELAY_TYPE t; + struct relay_server *relay_server; + union { + struct socket_message sm; + struct cb_socket_message cb_sm; + } m; +}; + +struct relay_server; +typedef struct relay_server *relay_server_handle; + +typedef int (*ioa_engine_new_connection_event_handler)(ioa_engine_handle e, struct message_to_relay *sm); +typedef int (*ioa_engine_udp_event_handler)(relay_server_handle rs, struct message_to_relay *sm); + +#define TURN_CMSG_SZ (65536) + +#define PREDEF_TIMERS_NUM (14) +extern const int predef_timer_intervals[PREDEF_TIMERS_NUM]; + +struct _ioa_engine +{ + super_memory_t *sm; + struct event_base *event_base; + int deallocate_eb; + int verbose; + turnipports* tp; + rtcp_map *map_rtcp; + stun_buffer_list bufs; + SSL_CTX *tls_ctx_ssl23; + SSL_CTX *tls_ctx_v1_0; +#if defined(SSL_TXT_TLSV1_1) + SSL_CTX *tls_ctx_v1_1; +#if defined(SSL_TXT_TLSV1_2) + SSL_CTX *tls_ctx_v1_2; +#endif +#endif + SSL_CTX *dtls_ctx; + turn_time_t jiffie; /* bandwidth check interval */ + band_limit_t max_bpj; + ioa_timer_handle timer_ev; + s08bits cmsg[TURN_CMSG_SZ+1]; + int predef_timer_intervals[PREDEF_TIMERS_NUM]; + struct timeval predef_timers[PREDEF_TIMERS_NUM]; + /* Relays */ + s08bits relay_ifname[1025]; + int default_relays; + size_t relays_number; + size_t relay_addr_counter; + ioa_addr *relay_addrs; +#if !defined(TURN_NO_HIREDIS) + redis_context_handle rch; +#endif +}; + +#define SOCKET_MAGIC (0xABACADEF) + +struct _ioa_socket +{ + evutil_socket_t fd; + struct _ioa_socket *parent_s; + void *listener_server; + u32bits magic; + ur_addr_map *sockets_container; /* relay container for UDP sockets */ + struct bufferevent *bev; + ioa_network_buffer_handle defer_nbh; + int family; + SOCKET_TYPE st; + SOCKET_APP_TYPE sat; + SSL* ssl; + char orig_ctx_type[16]; + int bound; + int local_addr_known; + ioa_addr local_addr; + int connected; + ioa_addr remote_addr; + ioa_engine_handle e; + struct event *read_event; + ioa_net_event_handler read_cb; + void *read_ctx; + int done; + ts_ur_super_session* session; + int current_df_relay_flag; + /* RFC6156: if IPv6 is involved, do not use DF: */ + int do_not_use_df; + int tobeclosed; + int broken; + int default_ttl; + int current_ttl; + int default_tos; + int current_tos; + stun_buffer_list bufs; + turn_time_t jiffie; /* bandwidth check interval */ + band_limit_t jiffie_bytes_read; + band_limit_t jiffie_bytes_write; + /* RFC 6062 ==>> */ + //Connection session: + tcp_connection *sub_session; + //Connect: + struct bufferevent *conn_bev; + connect_cb conn_cb; + void *conn_arg; + //Transferable sockets user data + u32bits username_hash; + u32bits realm_hash; + //Accept: + struct evconnlistener *list_ev; + accept_cb acb; + void *acbarg; + /* <<== RFC 6062 */ +}; + +typedef struct _timer_event +{ + struct event *ev; + ioa_engine_handle e; + ioa_timer_event_handler cb; + void *ctx; + s08bits* txt; +} timer_event; + +/////////////////////////////////// + +/* realm */ + +void create_new_realm(char* name); +int get_realm_data(char* name, realm_params_t* rp); + +/* engine handling */ + +ioa_engine_handle create_ioa_engine(super_memory_t *sm, + struct event_base *eb, turnipports* tp, + const s08bits* relay_if, + size_t relays_number, s08bits **relay_addrs, int default_relays, + int verbose, band_limit_t max_bps +#if !defined(TURN_NO_HIREDIS) + ,const char* redis_report_connection_string +#endif + ); + +void set_ssl_ctx(ioa_engine_handle e, + SSL_CTX *tls_ctx_ssl23, + SSL_CTX *tls_ctx_v1_0, +#if defined(SSL_TXT_TLSV1_1) + SSL_CTX *tls_ctx_v1_1, +#if defined(SSL_TXT_TLSV1_2) + SSL_CTX *tls_ctx_v1_2, +#endif +#endif + SSL_CTX *dtls_ctx); + +void ioa_engine_set_rtcp_map(ioa_engine_handle e, rtcp_map *rtcpmap); + +ioa_socket_handle create_ioa_socket_from_fd(ioa_engine_handle e, ioa_socket_raw fd, ioa_socket_handle parent_s, SOCKET_TYPE st, SOCKET_APP_TYPE sat, const ioa_addr *remote_addr, const ioa_addr *local_addr); +ioa_socket_handle create_ioa_socket_from_ssl(ioa_engine_handle e, ioa_socket_handle parent_s, SSL* ssl, SOCKET_TYPE st, SOCKET_APP_TYPE sat, const ioa_addr *remote_addr, const ioa_addr *local_addr); + +int get_a_local_relay(int family, ioa_addr *relay_addr); + +void add_socket_to_parent(ioa_socket_handle parent_s, ioa_socket_handle s); +void delete_socket_from_parent(ioa_socket_handle s); + +void add_socket_to_map(ioa_socket_handle s, ur_addr_map *amap); +void delete_socket_from_map(ioa_socket_handle s); + +int is_connreset(void); +int would_block(void); +int udp_send(ioa_socket_handle s, const ioa_addr* dest_addr, const s08bits* buffer, int len); +int udp_recvfrom(evutil_socket_t fd, ioa_addr* orig_addr, const ioa_addr *like_addr, s08bits* buffer, int buf_size, int *ttl, int *tos, s08bits *ecmsg, int flags, u32bits *errcode); +int ssl_read(evutil_socket_t fd, SSL* ssl, ioa_network_buffer_handle nbh, int verbose); + +int set_raw_socket_ttl_options(evutil_socket_t fd, int family); +int set_raw_socket_tos_options(evutil_socket_t fd, int family); + +int set_socket_options_fd(evutil_socket_t fd, int tcp, int family); +int set_socket_options(ioa_socket_handle s); + +///////////////////////// SUPER MEMORY //////// + +#define allocate_super_memory_engine(e,size) allocate_super_memory_engine_func(e, size, __FILE__, __FUNCTION__, __LINE__) +void* allocate_super_memory_engine_func(ioa_engine_handle e, size_t size, const char* file, const char* func, int line); + +///////////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif /* __IOA_LIBIMPL__ */ diff --git a/src/apps/relay/ns_sm.h b/src/apps/relay/ns_sm.h new file mode 100644 index 0000000..dfa366e --- /dev/null +++ b/src/apps/relay/ns_sm.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2011, 2012, 2013, 2014 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * IO Abstraction library + */ + +#ifndef __IOA_SM__ +#define __IOA_SM__ + +#include "ns_turn_ioalib.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////// + +struct _super_memory; +typedef struct _super_memory super_memory_t; + +////////////////////////////////////////////////////// + +void init_super_memory(void); + +super_memory_t* new_super_memory_region(void); + +#define allocate_super_memory_region(region,size) allocate_super_memory_region_func(region, size, __FILE__, __FUNCTION__, __LINE__) +void* allocate_super_memory_region_func(super_memory_t *region, size_t size, const char* file, const char* func, int line); + +///////////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif /* __IOA_SM__ */ diff --git a/src/apps/relay/tls_listener.c b/src/apps/relay/tls_listener.c new file mode 100644 index 0000000..f887f16 --- /dev/null +++ b/src/apps/relay/tls_listener.c @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "apputils.h" +#include "mainrelay.h" + +#include "ns_turn_utils.h" + +#include "tls_listener.h" +#include "ns_ioalib_impl.h" + +#include + +/////////////////////////////////////////////////// + +#define FUNCSTART if(server && eve(server->verbose)) TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"%s:%d:start\n",__FUNCTION__,__LINE__) +#define FUNCEND if(server && eve(server->verbose)) TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"%s:%d:end\n",__FUNCTION__,__LINE__) + +struct tls_listener_relay_server_info +{ + char ifname[1025]; + ioa_addr addr; + ioa_engine_handle e; + int verbose; + struct evconnlistener *l; + struct message_to_relay sm; + ioa_engine_new_connection_event_handler connect_cb; + struct relay_server *relay_server; +}; + +/////////////// io handlers /////////////////// + +static void server_input_handler(struct evconnlistener *l, evutil_socket_t fd, + struct sockaddr *sa, int socklen, void *arg) +{ + + UNUSED_ARG(l); + + tls_listener_relay_server_type * server = (tls_listener_relay_server_type*) arg; + + if(!(server->connect_cb)) { + socket_closesocket(fd); + return; + } + + FUNCSTART; + + if (!server) + return; + + ns_bcopy(sa,&(server->sm.m.sm.nd.src_addr),socklen); + + addr_debug_print(server->verbose, &(server->sm.m.sm.nd.src_addr),"tcp or tls connected to"); + + SOCKET_TYPE st = TENTATIVE_TCP_SOCKET; + + if(turn_params.no_tls) + st = TCP_SOCKET; + else if(turn_params.no_tcp) + st = TLS_SOCKET; + + ioa_socket_handle ioas = + create_ioa_socket_from_fd( + server->e, + fd, + NULL, + st, + CLIENT_SOCKET, + &(server->sm.m.sm.nd.src_addr), + &(server->addr)); + + if (ioas) { + + ioas->listener_server = server; + + server->sm.m.sm.nd.recv_ttl = TTL_IGNORE; + server->sm.m.sm.nd.recv_tos = TOS_IGNORE; + server->sm.m.sm.nd.nbh = NULL; + server->sm.m.sm.s = ioas; + server->sm.relay_server = server->relay_server; + + int rc = server->connect_cb(server->e, &(server->sm)); + + if (rc < 0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "Cannot create tcp or tls session\n"); + } + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "Cannot create ioa_socket from FD\n"); + socket_closesocket(fd); + } + + FUNCEND ; +} + +///////////////////// operations ////////////////////////// + +static int create_server_listener(tls_listener_relay_server_type* server) { + + FUNCSTART; + + if(!server) return -1; + + evutil_socket_t tls_listen_fd = -1; + + tls_listen_fd = socket(server->addr.ss.sa_family, SOCK_STREAM, 0); + if (tls_listen_fd < 0) { + perror("socket"); + return -1; + } + + if(sock_bind_to_device(tls_listen_fd, (unsigned char*)server->ifname)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"Cannot bind listener socket to device %s\n",server->ifname); + } + + { + const int max_binding_time = 60; + int addr_bind_cycle = 0; + retry_addr_bind: + + if(addr_bind(tls_listen_fd,&server->addr,1)<0) { + perror("Cannot bind local socket to addr"); + char saddr[129]; + addr_to_string(&server->addr,(u08bits*)saddr); + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING,"Cannot bind TCP/TLS listener socket to addr %s\n",saddr); + if(addr_bind_cycle++l = evconnlistener_new(server->e->event_base, + server_input_handler, server, + LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, + 1024, tls_listen_fd); + + if(!(server->l)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"Cannot create TLS listener\n"); + socket_closesocket(tls_listen_fd); + return -1; + } + + if(!turn_params.no_tcp && !turn_params.no_tls) + addr_debug_print(server->verbose, &server->addr,"TCP/TLS listener opened on "); + else if(!turn_params.no_tls) + addr_debug_print(server->verbose, &server->addr,"TLS listener opened on "); + else if(!turn_params.no_tcp) + addr_debug_print(server->verbose, &server->addr,"TCP listener opened on "); + + FUNCEND; + + return 0; +} + +static int init_server(tls_listener_relay_server_type* server, + const char* ifname, + const char *local_address, + int port, + int verbose, + ioa_engine_handle e, + ioa_engine_new_connection_event_handler send_socket, + struct relay_server *relay_server) { + + if(!server) return -1; + + server->connect_cb = send_socket; + server->relay_server = relay_server; + + if(ifname) STRCPY(server->ifname,ifname); + + if(make_ioa_addr((const u08bits*)local_address, port, &server->addr)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"Cannot create a TCP/TLS listener for address: %s\n",local_address); + return -1; + } + + server->verbose=verbose; + + server->e = e; + + return create_server_listener(server); +} + +/////////////////////////////////////////////////////////// + + +tls_listener_relay_server_type* create_tls_listener_server(const char* ifname, + const char *local_address, int port, int verbose, + ioa_engine_handle e, + ioa_engine_new_connection_event_handler send_socket, + struct relay_server *relay_server) +{ + + tls_listener_relay_server_type* server = + (tls_listener_relay_server_type*) allocate_super_memory_engine(e,sizeof(tls_listener_relay_server_type)); + + if (init_server(server, ifname, local_address, port, verbose, e, + send_socket, relay_server) < 0) { + return NULL ; + } else { + return server; + } +} + +////////////////////////////////////////////////////////////////// diff --git a/src/apps/relay/tls_listener.h b/src/apps/relay/tls_listener.h new file mode 100644 index 0000000..fda420a --- /dev/null +++ b/src/apps/relay/tls_listener.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __TLS_LISTENER__ +#define __TLS_LISTENER__ + +#include + +#include "ns_turn_utils.h" + +#include "ns_ioalib_impl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/////////////////////////////////////////// + +struct tls_listener_relay_server_info; +typedef struct tls_listener_relay_server_info tls_listener_relay_server_type; + +/////////////////////////////////////////// + +tls_listener_relay_server_type* create_tls_listener_server(const char* ifname, + const char *local_address, int port, int verbose, + ioa_engine_handle e, + ioa_engine_new_connection_event_handler send_socket, + struct relay_server *relay_server); + +/////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif //__TLS_LISTENER__ diff --git a/src/apps/relay/turn_ports.c b/src/apps/relay/turn_ports.c new file mode 100644 index 0000000..9db5a92 --- /dev/null +++ b/src/apps/relay/turn_ports.c @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "ns_turn_maps.h" +#include "ns_turn_msg_defs.h" + +#include "ns_turn_ioalib.h" +#include "ns_ioalib_impl.h" + +#include "turn_ports.h" + +////////// DATA //////////////////////////////////////////// + +#define PORTS_SIZE (0xFFFF+1) +#define TPS_OUT_OF_RANGE ((u32bits)(-1)) +#define TPS_TAKEN_SINGLE ((u32bits)(-2)) +#define TPS_TAKEN_EVEN ((u32bits)(-3)) +#define TPS_TAKEN_ODD ((u32bits)(-4)) + +struct _turnports { + u32bits status[PORTS_SIZE]; + u32bits low; + u32bits high; + u16bits range_start; + u16bits range_stop; + u16bits ports[PORTS_SIZE]; + TURN_MUTEX_DECLARE(mutex) +}; +typedef struct _turnports turnports; + +/////////////// TURNPORTS statics ////////////////////////// + +static turnports* turnports_create(super_memory_t *sm, u16bits start, u16bits end); +static u16bits turnports_size(turnports* tp); + +static int turnports_allocate(turnports* tp); +static int turnports_allocate_even(turnports* tp, int allocate_rtcp, u64bits *reservation_token); + +static void turnports_release(turnports* tp, u16bits port); + +static int turnports_is_allocated(turnports* tp, u16bits port); +static int turnports_is_available(turnports* tp, u16bits port); + +/////////////// UTILS ////////////////////////////////////// + +static int is_taken(u32bits status) { + int ret = -1; + switch (status) { + case TPS_TAKEN_SINGLE : + case TPS_TAKEN_EVEN : + case TPS_TAKEN_ODD : + ret = 1; + break; + default: + ret = 0; + }; + return ret; +} + +static void turnports_randomize(turnports* tp) { + if(tp) { + unsigned int size=(unsigned int)(tp->high-tp->low); + unsigned int i=0; + unsigned int cycles=size*10; + for(i=0;ilow + (u16bits)(((unsigned long)random())%((unsigned long)size))); + u16bits port2 = (u16bits)(tp->low + (u16bits)(((unsigned long)random())%((unsigned long)size))); + if(port1!=port2) { + int pos1=tp->status[port1]; + int pos2=tp->status[port2]; + int tmp=(int)tp->status[port1]; + tp->status[port1]=tp->status[port2]; + tp->status[port2]=(u32bits)tmp; + tmp=(int)tp->ports[pos1]; + tp->ports[pos1]=tp->ports[pos2]; + tp->ports[pos2]=(u16bits)tmp; + } + } + } +} + +static void turnports_init(turnports* tp, u16bits start, u16bits end) { + + tp->low=start; + tp->high=((u32bits)end)+1; + + tp->range_start=start; + tp->range_stop=end; + + int i=0; + for(i=0;istatus[i]=TPS_OUT_OF_RANGE; + tp->ports[i]=(u16bits)i; + } + for(i=start;i<=end;i++) { + tp->status[i]=(u32bits)i; + tp->ports[i]=(u16bits)i; + } + for(i=((int)end)+1;istatus[i]=TPS_OUT_OF_RANGE; + tp->ports[i]=(u16bits)i; + } + + turnports_randomize(tp); + + TURN_MUTEX_INIT_RECURSIVE(&(tp->mutex)); +} + +/////////////// FUNC /////////////////////////////////////// + +turnports* turnports_create(super_memory_t *sm, u16bits start, u16bits end) { + + if(start>end) return NULL; + + turnports* ret=(turnports*)allocate_super_memory_region(sm, sizeof(turnports)); + turnports_init(ret,start,end); + + return ret; +} + +u16bits turnports_size(turnports* tp) { + if(!tp) return 0; + else { + TURN_MUTEX_LOCK(&tp->mutex); + u16bits ret = (u16bits)((tp->high-tp->low)); + TURN_MUTEX_UNLOCK(&tp->mutex); + return ret; + } +} + +int turnports_allocate(turnports* tp) { + + int port=-1; + + TURN_MUTEX_LOCK(&tp->mutex); + + if(tp) { + + while(1) { + + if(tp->high <= tp->low) { + TURN_MUTEX_UNLOCK(&tp->mutex); + return -1; + } + + int position=(u16bits)(tp->low & 0x0000FFFF); + + port=(int)tp->ports[position]; + if(port<(int)(tp->range_start) || port>((int)(tp->range_stop))) { + TURN_MUTEX_UNLOCK(&tp->mutex); + return -1; + } + if(is_taken(tp->status[port])) { + ++(tp->low); + continue; + } + if(tp->status[port]!=tp->low) { + ++(tp->low); + continue; + } + tp->status[port]=TPS_TAKEN_SINGLE; + ++(tp->low); + break; + } + } + + TURN_MUTEX_UNLOCK(&tp->mutex); + + return port; +} + +void turnports_release(turnports* tp, u16bits port) { + TURN_MUTEX_LOCK(&tp->mutex); + if(tp && port>=tp->range_start && port<=tp->range_stop) { + u16bits position=(u16bits)(tp->high & 0x0000FFFF); + if(is_taken(tp->status[port])) { + tp->status[port]=tp->high; + tp->ports[position]=port; + ++(tp->high); + } + } + TURN_MUTEX_UNLOCK(&tp->mutex); +} + +int turnports_allocate_even(turnports* tp, int allocate_rtcp, u64bits *reservation_token) { + if(tp) { + TURN_MUTEX_LOCK(&tp->mutex); + u16bits size = turnports_size(tp); + if(size>1) { + u16bits i=0; + for(i=0;imutex); + return port; + } else { + int rtcp_port=port+1; + if(rtcp_port>tp->range_stop) { + turnports_release(tp,port); + } else if(!turnports_is_available(tp,rtcp_port)) { + turnports_release(tp,port); + } else { + tp->status[port]=TPS_TAKEN_EVEN; + tp->status[rtcp_port]=TPS_TAKEN_ODD; + if(reservation_token) { + u16bits *v16=(u16bits*)reservation_token; + u32bits *v32=(u32bits*)reservation_token; + v16[0]=(u16bits)(tp->ports[(u16bits)(tp->low & 0x0000FFFF)]); + v16[1]=(u16bits)(tp->ports[(u16bits)(tp->high & 0x0000FFFF)]); + v32[1]=(u32bits)turn_random(); + } + TURN_MUTEX_UNLOCK(&tp->mutex); + return port; + } + } + } + } + } + TURN_MUTEX_UNLOCK(&tp->mutex); + } + return -1; +} + +int turnports_is_allocated(turnports* tp, u16bits port) { + if(!tp) return 0; + else { + TURN_MUTEX_LOCK(&tp->mutex); + int ret = is_taken(tp->status[port]); + TURN_MUTEX_UNLOCK(&tp->mutex); + return ret; + } +} + +int turnports_is_available(turnports* tp, u16bits port) { + if(tp) { + TURN_MUTEX_LOCK(&tp->mutex); + u32bits status = tp->status[port]; + if((status!=TPS_OUT_OF_RANGE) && !is_taken(status)) { + u16bits position=(u16bits)(status & 0x0000FFFF); + if(tp->ports[position]==port) { + TURN_MUTEX_UNLOCK(&tp->mutex); + return 1; + } + } + TURN_MUTEX_UNLOCK(&tp->mutex); + } + return 0; +} + +/////////////////// IP-mapped PORTS ///////////////////////////////////// + +struct _turnipports +{ + super_memory_t *sm; + u16bits start; + u16bits end; + ur_addr_map ip_to_turnports_udp; + ur_addr_map ip_to_turnports_tcp; + TURN_MUTEX_DECLARE(mutex) +}; + +////////////////////////////////////////////////// + +static ur_addr_map *get_map(turnipports *tp, u08bits transport) +{ + if(transport == STUN_ATTRIBUTE_TRANSPORT_TCP_VALUE) + return &(tp->ip_to_turnports_tcp); + return &(tp->ip_to_turnports_udp); +} +////////////////////////////////////////////////// + +static turnipports* turnipports_singleton = NULL; + +turnipports* turnipports_create(super_memory_t *sm, u16bits start, u16bits end) +{ + turnipports *ret = (turnipports*) allocate_super_memory_region(sm, sizeof(turnipports)); + ret->sm = sm; + ur_addr_map_init(&(ret->ip_to_turnports_udp)); + ur_addr_map_init(&(ret->ip_to_turnports_tcp)); + ret->start = start; + ret->end = end; + TURN_MUTEX_INIT_RECURSIVE(&(ret->mutex)); + turnipports_singleton = ret; + return ret; +} + +static turnports* turnipports_add(turnipports* tp, u08bits transport, const ioa_addr *backend_addr) +{ + ur_addr_map_value_type t = 0; + if (tp && backend_addr) { + ioa_addr ba; + addr_cpy(&ba, backend_addr); + addr_set_port(&ba, 0); + TURN_MUTEX_LOCK((const turn_mutex*)&(tp->mutex)); + if (!ur_addr_map_get(get_map(tp, transport), &ba, &t)) { + t = (ur_addr_map_value_type) turnports_create(tp->sm, tp->start, tp->end); + ur_addr_map_put(get_map(tp, transport), &ba, t); + } + TURN_MUTEX_UNLOCK((const turn_mutex*)&(tp->mutex)); + } + return (turnports*) t; +} + +void turnipports_add_ip(u08bits transport, const ioa_addr *backend_addr) +{ + turnipports_add(turnipports_singleton, transport, backend_addr); +} + +int turnipports_allocate(turnipports* tp, u08bits transport, const ioa_addr *backend_addr) +{ + int ret = -1; + if (tp && backend_addr) { + TURN_MUTEX_LOCK((const turn_mutex*)&(tp->mutex)); + turnports *t = turnipports_add(tp, transport, backend_addr); + ret = turnports_allocate(t); + TURN_MUTEX_UNLOCK((const turn_mutex*)&(tp->mutex)); + } + return ret; +} + +int turnipports_allocate_even(turnipports* tp, const ioa_addr *backend_addr, int allocate_rtcp, + u64bits *reservation_token) +{ + int ret = -1; + if (tp && backend_addr) { + TURN_MUTEX_LOCK((const turn_mutex*)&(tp->mutex)); + turnports *t = turnipports_add(tp, STUN_ATTRIBUTE_TRANSPORT_UDP_VALUE, backend_addr); + ret = turnports_allocate_even(t, allocate_rtcp, reservation_token); + TURN_MUTEX_UNLOCK((const turn_mutex*)&(tp->mutex)); + } + return ret; +} + +void turnipports_release(turnipports* tp, u08bits transport, const ioa_addr *socket_addr) +{ + if (tp && socket_addr) { + ioa_addr ba; + ur_addr_map_value_type t; + addr_cpy(&ba, socket_addr); + addr_set_port(&ba, 0); + TURN_MUTEX_LOCK((const turn_mutex*)&(tp->mutex)); + if (ur_addr_map_get(get_map(tp, transport), &ba, &t)) { + turnports_release((turnports*) t, addr_get_port(socket_addr)); + } + TURN_MUTEX_UNLOCK((const turn_mutex*)&(tp->mutex)); + } +} + +int turnipports_is_allocated(turnipports* tp, u08bits transport, const ioa_addr *backend_addr, u16bits port) +{ + int ret = 0; + if (tp && backend_addr) { + ioa_addr ba; + ur_addr_map_value_type t; + addr_cpy(&ba, backend_addr); + addr_set_port(&ba, 0); + TURN_MUTEX_LOCK((const turn_mutex*)&(tp->mutex)); + if (ur_addr_map_get(get_map(tp,transport), &ba, &t)) { + ret = turnports_is_allocated((turnports*) t, port); + } + TURN_MUTEX_UNLOCK((const turn_mutex*)&(tp->mutex)); + } + return ret; +} + +int turnipports_is_available(turnipports* tp, u08bits transport, const ioa_addr *backend_addr, u16bits port) +{ + int ret = 0; + if (tp && backend_addr) { + ioa_addr ba; + ur_addr_map_value_type t; + addr_cpy(&ba, backend_addr); + addr_set_port(&ba, 0); + TURN_MUTEX_LOCK((const turn_mutex*)&(tp->mutex)); + if (!ur_addr_map_get(get_map(tp,transport), &ba, &t)) { + ret = 1; + } else { + ret = turnports_is_available((turnports*) t, port); + } + TURN_MUTEX_UNLOCK((const turn_mutex*)&(tp->mutex)); + } + return ret; +} + +////////////////////////////////////////////////////////////////// + + diff --git a/src/apps/relay/turn_ports.h b/src/apps/relay/turn_ports.h new file mode 100644 index 0000000..6b059af --- /dev/null +++ b/src/apps/relay/turn_ports.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __TURN_PORTS__ +#define __TURN_PORTS__ + +#include "ns_turn_ioaddr.h" + +#include "ns_sm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////// + +#define LOW_DEFAULT_PORTS_BOUNDARY (49152) +#define HIGH_DEFAULT_PORTS_BOUNDARY (65535) + +////////////////////////////////////////////////// + +struct _turnipports; +typedef struct _turnipports turnipports; + +////////////////////////////////////////////////// + +turnipports* turnipports_create(super_memory_t *sm, u16bits start, u16bits end); + +void turnipports_add_ip(u08bits transport, const ioa_addr *backend_addr); + +int turnipports_allocate(turnipports* tp, u08bits transport, const ioa_addr *backend_addr); +int turnipports_allocate_even(turnipports* tp, const ioa_addr *backend_addr, + int allocate_rtcp, u64bits *reservation_token); + +void turnipports_release(turnipports* tp, u08bits transport, const ioa_addr *socket_addr); + +int turnipports_is_allocated(turnipports* tp, u08bits transport, const ioa_addr *backend_addr, u16bits port); +int turnipports_is_available(turnipports* tp, u08bits transport, const ioa_addr *backend_addr, u16bits port); + +////////////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif //__TURN_PORTS__ diff --git a/src/apps/relay/turncli.c b/src/apps/relay/turncli.c new file mode 100644 index 0000000..ce1185b --- /dev/null +++ b/src/apps/relay/turncli.c @@ -0,0 +1,1261 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "libtelnet.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "userdb.h" +#include "mainrelay.h" + +#include "ns_turn_utils.h" + +#include "ns_turn_server.h" +#include "ns_turn_maps.h" + +#include "apputils.h" + +#include "turncli.h" + +/////////////////////////////// + +struct cli_server cliserver; + +int use_cli = 1; + +ioa_addr cli_addr; +int cli_addr_set = 0; + +int cli_port = CLI_DEFAULT_PORT; + +char cli_password[CLI_PASSWORD_LENGTH] = ""; + +int cli_max_output_sessions = DEFAULT_CLI_MAX_OUTPUT_SESSIONS; + +/////////////////////////////// + +struct cli_session { + evutil_socket_t fd; + int auth_completed; + size_t cmds; + struct bufferevent *bev; + ioa_addr addr; + telnet_t *ts; + FILE* f; + char realm[STUN_MAX_REALM_SIZE+1]; + char origin[STUN_MAX_ORIGIN_SIZE+1]; + realm_params_t *rp; +}; + +/////////////////////////////// + +#define CLI_PASSWORD_TRY_NUMBER (5) + +static const char *CLI_HELP_STR[] = + {"", + " ?, h, help - print this text", + "", + " quit, q, exit, bye - end CLI session", + "", + " stop, shutdown, halt - shutdown TURN Server", + "", + " pc - print configuration", + "", + " sr - set CLI session realm", + "", + " ur - unset CLI session realm", + "", + " so - set CLI session origin", + "", + " uo - unset CLI session origin", + "", + " tc - toggle a configuration parameter", + " (see pc command output for togglable param names)", + "", + " cc - change a configuration parameter", + " (see pc command output for changeable param names)", + "", + " ps [username] - print sessions, with optional exact user match", + "", + " psp - print sessions, with partial user string match", + "", + " psd - dump ps command output into file on the TURN server system", + "", + " pu [udp|tcp|dtls|tls]- print current users", + "", + " aas ip[:port} - add an alternate server reference", + " das ip[:port] - delete an alternate server reference", + " atas ip[:port] - add a TLS alternate server reference", + " dtas ip[:port] - delete a TLS alternate server reference", + "", + NULL}; + +static const char *CLI_GREETING_STR[] = { + "TURN Server", + TURN_SOFTWARE, + NULL}; + +static char CLI_CURSOR[] = "> "; + +static const telnet_telopt_t cli_telopts[] = { + { TELNET_TELOPT_ECHO, TELNET_WONT, TELNET_DONT }, + { TELNET_TELOPT_TTYPE, TELNET_WONT, TELNET_DONT }, + { TELNET_TELOPT_COMPRESS2, TELNET_WONT, TELNET_DONT }, + { TELNET_TELOPT_ZMP, TELNET_WONT, TELNET_DONT }, + { TELNET_TELOPT_MSSP, TELNET_WONT, TELNET_DONT }, + { TELNET_TELOPT_BINARY, TELNET_WONT, TELNET_DONT }, + { TELNET_TELOPT_NAWS, TELNET_WONT, TELNET_DONT }, + { -1, 0, 0 } + }; + +struct toggleable_command { + const char *cmd; + vintp data; +}; + +struct toggleable_command tcmds[] = { + {"stale-nonce",&turn_params.stale_nonce}, + {"stun-only",&turn_params.stun_only}, + {"no-stun",&turn_params.no_stun}, + {"secure-stun",&turn_params.secure_stun}, + {"no-udp-relay",&turn_params.no_udp_relay}, + {"no-tcp-relay",&turn_params.no_tcp_relay}, + {"no-multicast-peers",&turn_params.no_multicast_peers}, + {"no-loopback-peers",&turn_params.no_loopback_peers}, + {"mobility",&turn_params.mobility}, + {NULL,NULL} +}; + +/////////////////////////////// + +static void myprintf(struct cli_session *cs, const char *format, ...) +{ + if(cs && format) { + va_list args; + va_start (args, format); + if(cs->f) { + vfprintf(cs->f, format, args); + } else { + telnet_vprintf(cs->ts, format, args); + } + va_end (args); + } +} + +static void print_str_array(struct cli_session* cs, const char** sa) +{ + if(cs && sa) { + int i=0; + while(sa[i]) { + myprintf(cs,"%s\n",sa[i]); + i++; + } + } +} + +static const char* get_flag(int val) +{ + if(val) + return "ON"; + return "OFF"; +} + +static void cli_print_flag(struct cli_session* cs, int flag, const char* name, int changeable) +{ + if(cs && cs->ts && name) { + const char *sc=""; + if(changeable) + sc=" (*)"; + myprintf(cs," %s: %s%s\n",name,get_flag(flag),sc); + } +} + +static void cli_print_uint(struct cli_session* cs, unsigned long value, const char* name, int changeable) +{ + if(cs && cs->ts && name) { + const char *sc=""; + if(changeable==1) + sc=" (*)"; + else if(changeable==2) + sc=" (**)"; + myprintf(cs," %s: %lu%s\n",name,value,sc); + } +} + +static void cli_print_str(struct cli_session* cs, const char *value, const char* name, int changeable) +{ + if(cs && cs->ts && name && value) { + if(value[0] == 0) + value="empty"; + const char *sc=""; + if(changeable==1) + sc=" (*)"; + else if(changeable==2) + sc=" (**)"; + myprintf(cs," %s: %s%s\n",name,value,sc); + } +} + +static void cli_print_addr(struct cli_session* cs, ioa_addr *value, int use_port, const char* name, int changeable) +{ + if(cs && cs->ts && name && value) { + const char *sc=""; + if(changeable==1) + sc=" (*)"; + else if(changeable==2) + sc=" (**)"; + char s[256]; + if(!use_port) + addr_to_string_no_port(value,(u08bits*)s); + else + addr_to_string(value,(u08bits*)s); + myprintf(cs," %s: %s%s\n",name,s,sc); + } +} + +static void cli_print_addr_list(struct cli_session* cs, turn_server_addrs_list_t *value, int use_port, const char* name, int changeable) +{ + if(cs && cs->ts && name && value && value->size && value->addrs) { + const char *sc=""; + if(changeable==1) + sc=" (*)"; + else if(changeable==2) + sc=" (**)"; + char s[256]; + size_t i; + for(i=0;isize;i++) { + if(!use_port) + addr_to_string_no_port(&(value->addrs[i]),(u08bits*)s); + else + addr_to_string(&(value->addrs[i]),(u08bits*)s); + myprintf(cs," %s: %s%s\n",name,s,sc); + } + } +} + +static void cli_print_str_array(struct cli_session* cs, char **value, size_t sz, const char* name, int changeable) +{ + if(cs && cs->ts && name && value && sz) { + const char *sc=""; + if(changeable==1) + sc=" (*)"; + else if(changeable==2) + sc=" (**)"; + size_t i; + for(i=0;its && name && value && value->ranges_number && value->ranges) { + const char *sc=""; + if(changeable==1) + sc=" (*)"; + else if(changeable==2) + sc=" (**)"; + size_t i; + for(i=0;iranges_number;++i) { + if(value->ranges[i]) + myprintf(cs," %s: %s%s\n",name,value->ranges[i],sc); + } + } +} + +static void toggle_cli_param(struct cli_session* cs, const char* pn) +{ + if(cs && cs->ts && pn) { + + int i=0; + + while(tcmds[i].cmd && tcmds[i].data) { + if(strcmp(tcmds[i].cmd,pn) == 0) { + *(tcmds[i].data) = !(*(tcmds[i].data)); + cli_print_flag(cs,*(tcmds[i].data),tcmds[i].cmd,0); + return; + } + ++i; + } + + myprintf(cs, "\n"); + myprintf(cs, " Error: unknown or constant parameter: %s.\n",pn); + myprintf(cs, " You can toggle only the following parameters:\n"); + myprintf(cs, "\n"); + + i=0; + + while(tcmds[i].cmd && tcmds[i].data) { + cli_print_flag(cs,*(tcmds[i].data),tcmds[i].cmd,0); + ++i; + } + + myprintf(cs,"\n"); + } +} + +static void change_cli_param(struct cli_session* cs, const char* pn) +{ + if(cs && cs->ts && pn) { + + if(strstr(pn,"total-quota")==pn) { + int new_quota = change_total_quota(cs->realm, atoi(pn+strlen("total-quota"))); + cli_print_uint(cs,(unsigned long)new_quota,"total-quota",2); + return; + } else if(strstr(pn,"user-quota")==pn) { + int new_quota = change_user_quota(cs->realm, atoi(pn+strlen("user-quota"))); + cli_print_uint(cs,(unsigned long)new_quota,"user-quota",2); + return; + } else if(strstr(pn,"cli-max-output-sessions")==pn) { + cli_max_output_sessions = atoi(pn+strlen("cli-max-output-sessions")); + cli_print_uint(cs,(unsigned long)cli_max_output_sessions,"cli-max-output-sessions",2); + return; + } + + myprintf(cs, "\n"); + myprintf(cs, " Error: unknown or constant parameter: %s.\n",pn); + myprintf(cs, "\n"); + } +} + +struct ps_arg { + struct cli_session* cs; + size_t counter; + turn_time_t ct; + const char *username; + const char *pname; + int exact_match; + ur_string_map* users; + size_t *user_counters; + char **user_names; + size_t users_number; +}; + +static const char* pname(SOCKET_TYPE st) +{ + switch(st) { + case TCP_SOCKET: + return "TCP"; + case UDP_SOCKET: + return "UDP"; + case TLS_SOCKET: + return "TLS"; + case DTLS_SOCKET: + return "DTLS"; + case TENTATIVE_TCP_SOCKET: + return "TCP/TLS"; + default: + ; + }; + return "UNKNOWN"; +} + +static int print_session(ur_map_key_type key, ur_map_value_type value, void *arg) +{ + if(key && value && arg) { + struct ps_arg *csarg = (struct ps_arg*)arg; + struct cli_session* cs = csarg->cs; + struct turn_session_info *tsi = (struct turn_session_info *)value; + + if(cs->realm[0] && strcmp(cs->realm,tsi->realm)) + return 0; + + if(cs->origin[0] && strcmp(cs->origin,tsi->origin)) + return 0; + + if(csarg->users) { + ur_string_map_value_type value; + if(!ur_string_map_get(csarg->users, (ur_string_map_key_type)(char*)tsi->username, &value)) { + const char *pn=csarg->pname; + if(pn[0]) { + if(!strcmp(pn,"TLS") || !strcmp(pn,"tls") || !strcmp(pn,"Tls")) { + if(tsi->client_protocol != TLS_SOCKET) + return 0; + } else if(!strcmp(pn,"DTLS") || !strcmp(pn,"dtls") || !strcmp(pn,"Dtls")) { + if(tsi->client_protocol != DTLS_SOCKET) + return 0; + } else if(!strcmp(pn,"TCP") || !strcmp(pn,"tcp") || !strcmp(pn,"Tcp")) { + if(tsi->client_protocol != TCP_SOCKET) + return 0; + } else if(!strcmp(pn,"UDP") || !strcmp(pn,"udp") || !strcmp(pn,"Udp")) { + if(tsi->client_protocol != UDP_SOCKET) + return 0; + } else { + return 0; + } + } + value = (ur_string_map_value_type)csarg->users_number; + csarg->users_number += 1; + csarg->user_counters = (size_t*)turn_realloc(csarg->user_counters, + (size_t)value * sizeof(size_t), + csarg->users_number * sizeof(size_t)); + csarg->user_names = (char**)turn_realloc(csarg->user_names, + (size_t)value * sizeof(char*), + csarg->users_number * sizeof(char*)); + csarg->user_names[(size_t)value] = strdup((char*)tsi->username); + csarg->user_counters[(size_t)value] = 0; + ur_string_map_put(csarg->users, (ur_string_map_key_type)(char*)tsi->username, value); + } + csarg->user_counters[(size_t)value] += 1; + } else { + if(csarg->username[0]) { + if(csarg->exact_match) { + if(strcmp((char*)tsi->username, csarg->username)) + return 0; + } else { + if(!strstr((char*)tsi->username, csarg->username)) + return 0; + } + } + if(cs->f || (unsigned long)csarg->counter<(unsigned long)cli_max_output_sessions) { + myprintf(cs, "\n"); + myprintf(cs," %lu) id=%018llu, user <%s>:\n", + (unsigned long)(csarg->counter+1), + (unsigned long long)tsi->id, + tsi->username); + if(tsi->realm[0]) + myprintf(cs," realm: %s\n",tsi->realm); + if(tsi->origin[0]) + myprintf(cs," origin: %s\n",tsi->origin); + if(turn_time_before(csarg->ct, tsi->start_time)) { + myprintf(cs," started: undefined time\n"); + } else { + myprintf(cs," started %lu secs ago\n",(unsigned long)(csarg->ct - tsi->start_time)); + } + if(turn_time_before(tsi->expiration_time,csarg->ct)) { + myprintf(cs," expired\n"); + } else { + myprintf(cs," expiring in %lu secs\n",(unsigned long)(tsi->expiration_time - csarg->ct)); + } + myprintf(cs," client protocol %s, relay protocol %s\n",pname(tsi->client_protocol),pname(tsi->peer_protocol)); + { + if(!tsi->local_addr_data.saddr[0]) + addr_to_string(&(tsi->local_addr_data.addr),(u08bits*)tsi->local_addr_data.saddr); + if(!tsi->remote_addr_data.saddr[0]) + addr_to_string(&(tsi->remote_addr_data.addr),(u08bits*)tsi->remote_addr_data.saddr); + if(!tsi->relay_addr_data.saddr[0]) + addr_to_string(&(tsi->relay_addr_data.addr),(u08bits*)tsi->relay_addr_data.saddr); + myprintf(cs," client addr %s, server addr %s\n", + tsi->remote_addr_data.saddr, + tsi->local_addr_data.saddr); + myprintf(cs," relay addr %s\n", + tsi->relay_addr_data.saddr); + } + myprintf(cs," fingerprints enforced: %s\n",get_flag(tsi->enforce_fingerprints)); + myprintf(cs," mobile: %s\n",get_flag(tsi->is_mobile)); + if(tsi->tls_method[0]) { + myprintf(cs," TLS method: %s\n",tsi->tls_method); + myprintf(cs," TLS cipher: %s\n",tsi->tls_cipher); + } + myprintf(cs," usage: rp=%lu, rb=%lu, sp=%lu, sb=%lu\n",(unsigned long)(tsi->received_packets), (unsigned long)(tsi->received_bytes),(unsigned long)(tsi->sent_packets),(unsigned long)(tsi->sent_bytes)); + myprintf(cs," rate: r=%lu, s=%lu, total=%lu (bytes per sec)\n",(unsigned long)(tsi->received_rate), (unsigned long)(tsi->sent_rate),(unsigned long)(tsi->total_rate)); + if(tsi->main_peers_size) { + myprintf(cs," peers:\n"); + size_t i; + for(i=0;imain_peers_size;++i) { + if(!(tsi->main_peers_data[i].saddr[0])) + addr_to_string(&(tsi->main_peers_data[i].addr),(u08bits*)tsi->main_peers_data[i].saddr); + myprintf(cs," %s\n",tsi->main_peers_data[i].saddr); + } + if(tsi->extra_peers_size && tsi->extra_peers_data) { + for(i=0;iextra_peers_size;++i) { + if(!(tsi->extra_peers_data[i].saddr[0])) + addr_to_string(&(tsi->extra_peers_data[i].addr),(u08bits*)tsi->extra_peers_data[i].saddr); + myprintf(cs," %s\n",tsi->extra_peers_data[i].saddr); + } + } + } + } + } + + csarg->counter += 1; + } + return 0; +} + +static void print_sessions(struct cli_session* cs, const char* pn, int exact_match, int print_users) +{ + if(cs && cs->ts && pn) { + + while(pn[0] == ' ') ++pn; + if(pn[0] == '*') ++pn; + + const char *uname=""; + if(!print_users) { + uname = pn; + pn = ""; + } + + struct ps_arg arg = {cs,0,0,uname,pn,exact_match,NULL,NULL,NULL,0}; + + arg.ct = turn_time(); + + if(print_users) { + arg.users = ur_string_map_create(NULL); + } + + ur_map_foreach_arg(cliserver.sessions, (foreachcb_arg_type)print_session, &arg); + + myprintf(cs,"\n"); + + if(!print_users && !(cs->f)) { + if((unsigned long)arg.counter > (unsigned long)cli_max_output_sessions) { + myprintf(cs,"...\n"); + myprintf(cs,"\n"); + } + } else if(arg.user_counters && arg.user_names) { + size_t i; + for(i=0;i, %lu sessions\n", + arg.user_names[i], + (unsigned long)arg.user_counters[i]); + } + } + myprintf(cs,"\n"); + } + + { + char ts[1025]; + snprintf(ts,sizeof(ts)," Total sessions"); + if(cs->realm[0]) { + snprintf(ts+strlen(ts),sizeof(ts)-strlen(ts)," for realm %s",cs->realm); + if(cs->origin[0]) + snprintf(ts+strlen(ts),sizeof(ts)-strlen(ts)," and for origin %s",cs->origin); + } else { + if(cs->origin[0]) + snprintf(ts+strlen(ts),sizeof(ts)-strlen(ts)," for origin %s",cs->origin); + } + snprintf(ts+strlen(ts),sizeof(ts)-strlen(ts),": %lu", (unsigned long)arg.counter); + myprintf(cs,"%s\n", ts); + myprintf(cs,"\n"); + } + + if(!print_users && !(cs->f)) { + if((unsigned long)arg.counter > (unsigned long)cli_max_output_sessions) { + myprintf(cs," Warning: too many output sessions, more than the\n"); + myprintf(cs," current value of cli-max-output-sessions CLI parameter.\n"); + myprintf(cs," Refine your request or increase cli-max-output-sessions value.\n"); + myprintf(cs,"\n"); + } + } + + if(arg.user_counters) + turn_free(arg.user_counters,sizeof(size_t)*arg.users_number); + if(arg.user_names) { + size_t i; + for(i=0;ioptions.name; + if(rn[0]) + cli_print_str(cs,rn,"Default realm",0); + } + if(cs->realm[0]) + cli_print_str(cs,cs->realm,"CLI session realm",0); + else + cli_print_str(cs,get_realm(NULL)->options.name,"CLI session realm",0); + if(cs->origin[0]) + cli_print_str(cs,cs->origin,"CLI session origin",0); + if(turn_params.ct == TURN_CREDENTIALS_LONG_TERM) + cli_print_flag(cs,1,"Long-term authorization mechanism",0); + else if(turn_params.ct == TURN_CREDENTIALS_SHORT_TERM) + cli_print_flag(cs,1,"Short-term authorization mechanism",0); + else + cli_print_flag(cs,1,"Anonymous credentials",0); + cli_print_flag(cs,turn_params.use_auth_secret_with_timestamp,"REST API support",0); + if(turn_params.use_auth_secret_with_timestamp && turn_params.rest_api_separator) + cli_print_uint(cs,turn_params.rest_api_separator,"REST API separator ASCII number",0); + + myprintf(cs,"\n"); + + cli_print_uint(cs,(unsigned long)cs->rp->status.total_current_allocs,"total-current-allocs",0); + + cli_print_uint(cs,(unsigned long)cs->rp->options.perf_options.total_quota,"total-quota",2); + cli_print_uint(cs,(unsigned long)cs->rp->options.perf_options.user_quota,"user-quota",2); + cli_print_uint(cs,(unsigned long)cs->rp->options.perf_options.max_bps,"max-bps",0); + + myprintf(cs,"\n"); + + cli_print_uint(cs,(unsigned long)cli_max_output_sessions,"cli-max-output-sessions",2); + + { + myprintf(cs,"\n"); + const char *str=" (Note 1: parameters with (*) are toggleable)"; + myprintf(cs,"%s\n",str); + myprintf(cs,"\n"); + str=" (Note 2: parameters with (**) are changeable)"; + myprintf(cs,"%s\n",str); + myprintf(cs,"\n"); + } + } +} + +static void close_cli_session(struct cli_session* cs); + +static int run_cli_output(struct cli_session* cs, const char *buf, unsigned int len) +{ + if(cs && buf && len) { + if(bufferevent_write(cs->bev, buf, len)< 0) { + return -1; + } + return 0; + } + return -1; +} + +static void close_cli_session(struct cli_session* cs) +{ + if(cs) { + + addr_debug_print(cliserver.verbose, &(cs->addr),"CLI session disconnected from"); + + if(cs->ts) { + telnet_free(cs->ts); + cs->ts = NULL; + } + + if(cs->bev) { + bufferevent_flush(cs->bev,EV_READ|EV_WRITE,BEV_FLUSH); + bufferevent_disable(cs->bev,EV_READ|EV_WRITE); + bufferevent_free(cs->bev); + cs->bev=NULL; + } + + if(cs->fd>=0) { + close(cs->fd); + cs->fd = -1; + } + + turn_free(cs,sizeof(struct cli_session)); + } +} + +static void type_cli_cursor(struct cli_session* cs) +{ + if(cs && (cs->bev)) { + myprintf(cs, "%s", CLI_CURSOR); + } +} + +static void cli_add_alternate_server(struct cli_session* cs, const char* pn) +{ + if(cs && cs->ts && pn && *pn) { + add_alternate_server(pn); + } +} + +static void cli_add_tls_alternate_server(struct cli_session* cs, const char* pn) +{ + if(cs && cs->ts && pn && *pn) { + add_tls_alternate_server(pn); + } +} + +static void cli_del_alternate_server(struct cli_session* cs, const char* pn) +{ + if(cs && cs->ts && pn && *pn) { + del_alternate_server(pn); + } +} + +static void cli_del_tls_alternate_server(struct cli_session* cs, const char* pn) +{ + if(cs && cs->ts && pn && *pn) { + del_tls_alternate_server(pn); + } +} + +static int run_cli_input(struct cli_session* cs, const char *buf0, unsigned int len) +{ + int ret = 0; + + if(cs && buf0 && cs->ts && cs->bev) { + + char *buf = (char*)malloc(len+1); + ns_bcopy(buf0,buf,len); + buf[len]=0; + + char *cmd = buf; + + while((cmd[0]==' ') || (cmd[0]=='\t')) ++cmd; + + size_t sl = strlen(cmd); + + while(sl) { + char c = cmd[sl-1]; + if((c==10)||(c==13)) { + cmd[sl-1]=0; + --sl; + } else { + break; + } + } + + if(sl) { + cs->cmds += 1; + if(cli_password[0] && !(cs->auth_completed)) { + if(strcmp(cmd,cli_password)) { + if(cs->cmds>=CLI_PASSWORD_TRY_NUMBER) { + addr_debug_print(1, &(cs->addr),"CLI authentication error"); + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"CLI authentication error\n"); + close_cli_session(cs); + } else { + const char* ipwd="Enter password: "; + myprintf(cs,"%s\n",ipwd); + } + } else { + cs->auth_completed = 1; + addr_debug_print(1, &(cs->addr),"CLI authentication success"); + type_cli_cursor(cs); + } + } else if((strcmp(cmd,"bye") == 0)||(strcmp(cmd,"quit") == 0)||(strcmp(cmd,"exit") == 0)||(strcmp(cmd,"q") == 0)) { + const char* str="Bye !"; + myprintf(cs,"%s\n",str); + close_cli_session(cs); + ret = -1; + } else if((strcmp(cmd,"halt") == 0)||(strcmp(cmd,"shutdown") == 0)||(strcmp(cmd,"stop") == 0)) { + addr_debug_print(1, &(cs->addr),"Shutdown command received from CLI user"); + const char* str="TURN server is shutting down"; + myprintf(cs,"%s\n",str); + close_cli_session(cs); + turn_params.stop_turn_server = 1; + sleep(10); + exit(0); + } else if((strcmp(cmd,"?") == 0)||(strcmp(cmd,"h") == 0)||(strcmp(cmd,"help") == 0)) { + print_str_array(cs, CLI_GREETING_STR); + print_str_array(cs, CLI_HELP_STR); + type_cli_cursor(cs); + } else if(strcmp(cmd,"pc")==0) { + cli_print_configuration(cs); + type_cli_cursor(cs); + } else if(strstr(cmd,"tc ") == cmd) { + toggle_cli_param(cs,cmd+3); + } else if(strstr(cmd,"sr ") == cmd) { + STRCPY(cs->realm,cmd+3); + cs->rp = get_realm(cs->realm); + type_cli_cursor(cs); + } else if(strcmp(cmd,"ur") == 0) { + cs->realm[0]=0; + cs->rp = get_realm(NULL); + type_cli_cursor(cs); + } else if(strstr(cmd,"so ") == cmd) { + STRCPY(cs->origin,cmd+3); + type_cli_cursor(cs); + } else if(strcmp(cmd,"uo") == 0) { + cs->origin[0]=0; + type_cli_cursor(cs); + } else if(strstr(cmd,"tc") == cmd) { + toggle_cli_param(cs,cmd+2); + type_cli_cursor(cs); + } else if(strstr(cmd,"psp") == cmd) { + print_sessions(cs,cmd+3,0,0); + type_cli_cursor(cs); + } else if(strstr(cmd,"psd") == cmd) { + cmd += 3; + while(cmd[0]==' ') ++cmd; + if(!(cmd[0])) { + const char* str="You have to provide file name for ps dump\n"; + myprintf(cs,"%s\n",str); + } else { + cs->f = fopen(cmd,"w"); + if(!(cs->f)) { + const char* str="Cannot open file for writing\n"; + myprintf(cs,"%s\n",str); + } else { + print_sessions(cs,"",1,0); + fclose(cs->f); + cs->f = NULL; + } + } + type_cli_cursor(cs); + } else if(strstr(cmd,"pu ") == cmd) { + print_sessions(cs,cmd+3,0,1); + type_cli_cursor(cs); + } else if(!strcmp(cmd,"pu")) { + print_sessions(cs,cmd+2,0,1); + type_cli_cursor(cs); + } else if(strstr(cmd,"ps") == cmd) { + print_sessions(cs,cmd+2,1,0); + type_cli_cursor(cs); + } else if(strstr(cmd,"cc ") == cmd) { + change_cli_param(cs,cmd+3); + type_cli_cursor(cs); + } else if(strstr(cmd,"cc") == cmd) { + change_cli_param(cs,cmd+2); + type_cli_cursor(cs); + } else if(strstr(cmd,"aas ") == cmd) { + cli_add_alternate_server(cs,cmd+4); + type_cli_cursor(cs); + } else if(strstr(cmd,"atas ") == cmd) { + cli_add_tls_alternate_server(cs,cmd+5); + type_cli_cursor(cs); + } else if(strstr(cmd,"das ") == cmd) { + cli_del_alternate_server(cs,cmd+4); + type_cli_cursor(cs); + } else if(strstr(cmd,"dtas ") == cmd) { + cli_del_tls_alternate_server(cs,cmd+5); + type_cli_cursor(cs); + } else { + const char* str="Unknown command\n"; + myprintf(cs,"%s\n",str); + type_cli_cursor(cs); + } + } else { + type_cli_cursor(cs); + } + + free(buf); + } + + return ret; +} + +static void cli_socket_input_handler_bev(struct bufferevent *bev, void* arg) +{ + if (bev && arg) { + + struct cli_session* cs = (struct cli_session*) arg; + + if(!(cs->ts)) + return; + + stun_buffer buf; + + if(cs->bev) { + + int len = (int)bufferevent_read(cs->bev, buf.buf, STUN_BUFFER_SIZE-1); + if(len < 0) { + close_cli_session(cs); + return; + } else if(len == 0) { + return; + } + + buf.len = len; + buf.offset = 0; + buf.buf[len]=0; + + telnet_recv(cs->ts, (const char *)buf.buf, (unsigned int)(buf.len)); + } + } +} + +static void cli_eventcb_bev(struct bufferevent *bev, short events, void *arg) +{ + UNUSED_ARG(bev); + + if (events & BEV_EVENT_CONNECTED) { + // Connect okay + } else if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) { + if (arg) { + + struct cli_session* cs = (struct cli_session*) arg; + + close_cli_session(cs); + } + } +} + +static void cli_telnet_event_handler(telnet_t *telnet, telnet_event_t *event, void *user_data) +{ + if (user_data && telnet) { + + struct cli_session *cs = (struct cli_session *) user_data; + + switch (event->type){ + case TELNET_EV_DATA: + run_cli_input(cs, event->data.buffer, event->data.size); + break; + case TELNET_EV_SEND: + run_cli_output(cs, event->data.buffer, event->data.size); + break; + case TELNET_EV_ERROR: + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "TELNET error: %s", event->error.msg); + break; + default: + ; + }; + } +} + +static void cliserver_input_handler(struct evconnlistener *l, evutil_socket_t fd, + struct sockaddr *sa, int socklen, void *arg) +{ + UNUSED_ARG(l); + UNUSED_ARG(arg); + UNUSED_ARG(socklen); + + addr_debug_print(cliserver.verbose, (ioa_addr*)sa,"CLI connected to"); + + struct cli_session *clisession = (struct cli_session*)turn_malloc(sizeof(struct cli_session)); + ns_bzero(clisession,sizeof(struct cli_session)); + + clisession->rp = get_realm(NULL); + + set_socket_options_fd(fd, 1, sa->sa_family); + + clisession->fd = fd; + + addr_cpy(&(clisession->addr),(ioa_addr*)sa); + + clisession->bev = bufferevent_socket_new(cliserver.event_base, + fd, + BEV_OPT_DEFER_CALLBACKS | BEV_OPT_UNLOCK_CALLBACKS); + bufferevent_setcb(clisession->bev, cli_socket_input_handler_bev, NULL, + cli_eventcb_bev, clisession); + bufferevent_setwatermark(clisession->bev, EV_READ|EV_WRITE, 0, BUFFEREVENT_HIGH_WATERMARK); + bufferevent_enable(clisession->bev, EV_READ); /* Start reading. */ + + clisession->ts = telnet_init(cli_telopts, cli_telnet_event_handler, 0, clisession); + + if(!(clisession->ts)) { + const char *str = "Cannot open telnet session\n"; + addr_debug_print(cliserver.verbose, (ioa_addr*)sa,str); + close_cli_session(clisession); + } else { + print_str_array(clisession, CLI_GREETING_STR); + telnet_printf(clisession->ts,"\n"); + telnet_printf(clisession->ts,"Type '?' for help\n"); + if(cli_password[0]) { + const char* ipwd="Enter password: "; + telnet_printf(clisession->ts,"%s\n",ipwd); + } else { + type_cli_cursor(clisession); + } + } +} + +void setup_cli_thread(void) +{ + cliserver.event_base = turn_event_base_new(); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"IO method (cli thread): %s\n",event_base_get_method(cliserver.event_base)); + + struct bufferevent *pair[2]; + int opts = BEV_OPT_DEFER_CALLBACKS | BEV_OPT_UNLOCK_CALLBACKS; + + opts |= BEV_OPT_THREADSAFE; + + bufferevent_pair_new(cliserver.event_base, opts, pair); + cliserver.in_buf = pair[0]; + cliserver.out_buf = pair[1]; + bufferevent_setcb(cliserver.in_buf, cli_server_receive_message, NULL, NULL, &cliserver); + bufferevent_enable(cliserver.in_buf, EV_READ); + + if(!cli_addr_set) { + if(make_ioa_addr((const u08bits*)CLI_DEFAULT_IP,0,&cli_addr)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"Cannot set cli address %s\n",CLI_DEFAULT_IP); + return; + } + } + + addr_set_port(&cli_addr,cli_port); + + cliserver.listen_fd = socket(cli_addr.ss.sa_family, SOCK_STREAM, 0); + if (cliserver.listen_fd < 0) { + perror("socket"); + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"Cannot open CLI socket\n"); + return; + } + + if(addr_bind(cliserver.listen_fd,&cli_addr,1)<0) { + perror("Cannot bind CLI socket to addr"); + char saddr[129]; + addr_to_string(&cli_addr,(u08bits*)saddr); + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"Cannot bind CLI listener socket to addr %s\n",saddr); + socket_closesocket(cliserver.listen_fd); + return; + } + + socket_tcp_set_keepalive(cliserver.listen_fd); + + socket_set_nonblocking(cliserver.listen_fd); + + cliserver.l = evconnlistener_new(cliserver.event_base, + cliserver_input_handler, &cliserver, + LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, + 1024, cliserver.listen_fd); + + if(!(cliserver.l)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR,"Cannot create CLI listener\n"); + socket_closesocket(cliserver.listen_fd); + return; + } + + cliserver.sessions = ur_map_create(); + + addr_debug_print(cliserver.verbose, &cli_addr,"CLI listener opened on "); +} + +void cli_server_receive_message(struct bufferevent *bev, void *ptr) +{ + UNUSED_ARG(ptr); + + struct turn_session_info *tsi = (struct turn_session_info*)turn_malloc(sizeof(struct turn_session_info)); + turn_session_info_init(tsi); + int n = 0; + struct evbuffer *input = bufferevent_get_input(bev); + + while ((n = evbuffer_remove(input, tsi, sizeof(struct turn_session_info))) > 0) { + if (n != sizeof(struct turn_session_info)) { + fprintf(stderr,"%s: Weird CLI buffer error: size=%d\n",__FUNCTION__,n); + continue; + } + + ur_map_value_type t = 0; + if (ur_map_get(cliserver.sessions, (ur_map_key_type)tsi->id, &t) && t) { + struct turn_session_info *old = (struct turn_session_info*)t; + turn_session_info_clean(old); + turn_free(old,sizeof(struct turn_session_info)); + ur_map_del(cliserver.sessions, (ur_map_key_type)tsi->id, NULL); + } + + if(tsi->valid) { + ur_map_put(cliserver.sessions, (ur_map_key_type)tsi->id, (ur_map_value_type)tsi); + tsi = (struct turn_session_info*)turn_malloc(sizeof(struct turn_session_info)); + turn_session_info_init(tsi); + } else { + turn_session_info_clean(tsi); + } + } + + if(tsi) { + turn_session_info_clean(tsi); + turn_free(tsi,sizeof(struct turn_session_info)); + } +} + +int send_turn_session_info(struct turn_session_info* tsi) +{ + int ret = -1; + + if(!use_cli) + return ret; + + if(tsi) { + struct evbuffer *output = bufferevent_get_output(cliserver.out_buf); + if(output) { + if(evbuffer_add(output,tsi,sizeof(struct turn_session_info))>=0) { + ret = 0; + } + } + } + + return ret; +} + +/////////////////////////////// diff --git a/src/apps/relay/turncli.h b/src/apps/relay/turncli.h new file mode 100644 index 0000000..6a43244 --- /dev/null +++ b/src/apps/relay/turncli.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __TURNCLI__ +#define __TURNCLI__ + +#include +#include + +#include + +#include +#include + +#include "ns_turn_utils.h" +#include "ns_turn_maps.h" +#include "ns_turn_server.h" + +#include "apputils.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////////////// + +struct cli_server { + evutil_socket_t listen_fd; + struct event_base* event_base; + int verbose; + struct evconnlistener *l; + struct bufferevent *in_buf; + struct bufferevent *out_buf; + ur_map *sessions; + pthread_t thr; +}; + +/////////////////////////////////////////// + +extern struct cli_server cliserver; + +extern int use_cli; + +#define CLI_DEFAULT_IP ("127.0.0.1") +extern ioa_addr cli_addr; +extern int cli_addr_set; + +#define CLI_DEFAULT_PORT (5766) +extern int cli_port; + +#define CLI_PASSWORD_LENGTH (129) +extern char cli_password[CLI_PASSWORD_LENGTH]; + +#define DEFAULT_CLI_MAX_OUTPUT_SESSIONS (256) +extern int cli_max_output_sessions; + +//////////////////////////////////////////// + +void setup_cli_thread(void); + +void cli_server_receive_message(struct bufferevent *bev, void *ptr); + +int send_turn_session_info(struct turn_session_info* tsi); + +//////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif +/// __TURNCLI__/// + diff --git a/src/apps/relay/userdb.c b/src/apps/relay/userdb.c new file mode 100644 index 0000000..a45b5c0 --- /dev/null +++ b/src/apps/relay/userdb.c @@ -0,0 +1,3595 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(TURN_NO_PQ) +#include +#endif + +#if !defined(TURN_NO_MYSQL) +#include +#endif + +#if !defined(TURN_NO_HIREDIS) +#include "hiredis_libevent2.h" +#include +#endif + +#include + +#include + +#include +#include +#include +#include + +#include +#include + +#include "userdb.h" +#include "mainrelay.h" + +#include "ns_turn_utils.h" + +#include "ns_turn_server.h" +#include "ns_turn_maps.h" + +#include "apputils.h" + +//////////// USER DB ////////////////////////////// + +#define LONG_STRING_SIZE (TURN_LONG_STRING_SIZE) + +static int donot_print_connection_success=0; + +//////////// REALM ////////////// + +static realm_params_t *default_realm_params_ptr = NULL; +static const realm_params_t _default_realm_params = +{ + 1, + { + "\0", /* name */ + {0,0,0} + }, + {0,NULL} +}; + +static ur_string_map *realms = NULL; +static turn_mutex o_to_realm_mutex; +static ur_string_map *o_to_realm = NULL; +static secrets_list_t realms_list; + +void create_new_realm(char* name) +{ + realm_params_t *ret = NULL; + + if((name == NULL)||(name[0]==0)) { + if(default_realm_params_ptr) { + return; + } + /* init everything: */ + TURN_MUTEX_INIT_RECURSIVE(&o_to_realm_mutex); + init_secrets_list(&realms_list); + o_to_realm = ur_string_map_create(free); + default_realm_params_ptr = (realm_params_t*)malloc(sizeof(realm_params_t)); + ns_bcopy(&_default_realm_params,default_realm_params_ptr,sizeof(realm_params_t)); + realms = ur_string_map_create(NULL); + ur_string_map_lock(realms); + ret = default_realm_params_ptr; + } else { + ur_string_map_value_type value = 0; + ur_string_map_lock(realms); + if (!ur_string_map_get(realms, (const ur_string_map_key_type) name, &value)) { + ret = (realm_params_t*)turn_malloc(sizeof(realm_params_t)); + ns_bcopy(default_realm_params_ptr,ret,sizeof(realm_params_t)); + STRCPY(ret->options.name,name); + value = (ur_string_map_value_type)ret; + ur_string_map_put(realms, (const ur_string_map_key_type) name, value); + add_to_secrets_list(&realms_list, name); + } else { + ur_string_map_unlock(realms); + return; + } + } + + ret->status.alloc_counters = ur_string_map_create(NULL); + ur_string_map_unlock(realms); +} + +void get_default_realm_options(realm_options_t* ro) +{ + if(ro) { + ur_string_map_lock(realms); + ns_bcopy(&(default_realm_params_ptr->options),ro,sizeof(realm_options_t)); + ur_string_map_unlock(realms); + } +} + +void set_default_realm_name(char *realm) { + ur_string_map_lock(realms); + ur_string_map_value_type value = (ur_string_map_value_type)default_realm_params_ptr; + STRCPY(default_realm_params_ptr->options.name,realm); + ur_string_map_put(realms, (ur_string_map_key_type)default_realm_params_ptr->options.name, value); + add_to_secrets_list(&realms_list, realm); + ur_string_map_unlock(realms); +} + +realm_params_t* get_realm(char* name) +{ + if(name && name[0]) { + ur_string_map_lock(realms); + ur_string_map_value_type value = 0; + ur_string_map_key_type key = (ur_string_map_key_type)name; + if (ur_string_map_get(realms, key, &value)) { + ur_string_map_unlock(realms); + return (realm_params_t*)value; + } else { + realm_params_t *ret = (realm_params_t*)turn_malloc(sizeof(realm_params_t)); + ns_bcopy(default_realm_params_ptr,ret,sizeof(realm_params_t)); + STRCPY(ret->options.name,name); + value = (ur_string_map_value_type)ret; + ur_string_map_put(realms, key, value); + ret->status.alloc_counters = ur_string_map_create(NULL); + add_to_secrets_list(&realms_list, name); + ur_string_map_unlock(realms); + return ret; + } + } + + return default_realm_params_ptr; +} + +int get_realm_data(char* name, realm_params_t* rp) +{ + ur_string_map_lock(realms); + ns_bcopy(get_realm(name),rp,sizeof(realm_params_t)); + ur_string_map_unlock(realms); + return 0; +} + +void get_realm_options_by_origin(char *origin, realm_options_t* ro) +{ + ur_string_map_value_type value = 0; + TURN_MUTEX_LOCK(&o_to_realm_mutex); + if (ur_string_map_get(o_to_realm, (ur_string_map_key_type) origin, &value) && value) { + char *realm = strdup((char*)value); + TURN_MUTEX_UNLOCK(&o_to_realm_mutex); + realm_params_t rp; + get_realm_data(realm, &rp); + ns_bcopy(&(rp.options),ro,sizeof(realm_options_t)); + free(realm); + } else { + TURN_MUTEX_UNLOCK(&o_to_realm_mutex); + get_default_realm_options(ro); + } +} + +void get_realm_options_by_name(char *realm, realm_options_t* ro) +{ + realm_params_t rp; + get_realm_data(realm, &rp); + ns_bcopy(&(rp.options),ro,sizeof(realm_options_t)); +} + +int change_total_quota(char *realm, int value) +{ + int ret = value; + ur_string_map_lock(realms); + realm_params_t* rp = get_realm(realm); + rp->options.perf_options.total_quota = value; + ur_string_map_unlock(realms); + return ret; +} + +int change_user_quota(char *realm, int value) +{ + int ret = value; + ur_string_map_lock(realms); + realm_params_t* rp = get_realm(realm); + rp->options.perf_options.user_quota = value; + ur_string_map_unlock(realms); + return ret; +} + +static void must_set_admin_realm(void *realm0) +{ + char* realm = (char*)realm0; + if(!realm || !realm[0]) { + fprintf(stderr, "The operation cannot be completed: the realm must be set.\n"); + exit(-1); + } +} + +static void must_set_admin_user(void *user0) +{ + char* user = (char*)user0; + if(!user || !user[0]) { + fprintf(stderr, "The operation cannot be completed: the user must be set.\n"); + exit(-1); + } +} + +static void must_set_admin_pwd(void *pwd0) +{ + char* pwd = (char*)pwd0; + if(!pwd || !pwd[0]) { + fprintf(stderr, "The operation cannot be completed: the password must be set.\n"); + exit(-1); + } +} + +static void must_set_admin_origin(void *origin0) +{ + char* origin = (char*)origin0; + if(!origin || !origin[0]) { + fprintf(stderr, "The operation cannot be completed: the origin must be set.\n"); + exit(-1); + } +} + +/////////// SHARED SECRETS ///////////////// + +void init_secrets_list(secrets_list_t *sl) +{ + if(sl) { + ns_bzero(sl,sizeof(secrets_list_t)); + } +} + +void clean_secrets_list(secrets_list_t *sl) +{ + if(sl) { + if(sl->secrets) { + size_t i = 0; + for(i = 0;isz;++i) { + if(sl->secrets[i]) { + turn_free(sl->secrets[i], strlen(sl->secrets[i])+1); + } + } + turn_free(sl->secrets,(sl->sz)*sizeof(char*)); + sl->secrets = NULL; + sl->sz = 0; + } + } +} + +size_t get_secrets_list_size(secrets_list_t *sl) +{ + if(sl && sl->secrets) { + return sl->sz; + } + return 0; +} + +const char* get_secrets_list_elem(secrets_list_t *sl, size_t i) +{ + if(get_secrets_list_size(sl)>i) { + return sl->secrets[i]; + } + return NULL; +} + +void add_to_secrets_list(secrets_list_t *sl, const char* elem) +{ + if(sl && elem) { + sl->secrets = (char**)realloc(sl->secrets,(sizeof(char*)*(sl->sz+1))); + sl->secrets[sl->sz] = strdup(elem); + sl->sz += 1; + } +} + +/////////// USER DB CHECK ////////////////// + +static persistent_users_db_t* get_persistent_users_db(void) +{ + return &(turn_params.default_users_db.persistent_users_db); +} + +static int convert_string_key_to_binary(char* keysource, hmackey_t key, size_t sz) { + { + char is[3]; + size_t i; + unsigned int v; + is[2]=0; + for(i=0;iconnection); + if(pqdbconnection) { + ConnStatusType status = PQstatus(pqdbconnection); + if(status != CONNECTION_OK) { + PQfinish(pqdbconnection); + pqdbconnection = NULL; + } + } + if(!pqdbconnection) { + char *errmsg=NULL; + PQconninfoOption *co = PQconninfoParse(pud->userdb, &errmsg); + if(!co) { + if(errmsg) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot open PostgreSQL DB connection <%s>, connection string format error: %s\n",pud->userdb,errmsg); + turn_free(errmsg,strlen(errmsg)+1); + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot open PostgreSQL DB connection: <%s>, unknown connection string format error\n",pud->userdb); + } + } else { + PQconninfoFree(co); + if(errmsg) + turn_free(errmsg,strlen(errmsg)+1); + pqdbconnection = PQconnectdb(pud->userdb); + if(!pqdbconnection) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot open PostgreSQL DB connection: <%s>, runtime error\n",pud->userdb); + } else { + ConnStatusType status = PQstatus(pqdbconnection); + if(status != CONNECTION_OK) { + PQfinish(pqdbconnection); + pqdbconnection = NULL; + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot open PostgreSQL DB connection: <%s>, runtime error\n",pud->userdb); + } else if(!donot_print_connection_success){ + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "PostgreSQL DB connection success: %s\n",pud->userdb); + } + } + } + pud->connection = pqdbconnection; + } + return pqdbconnection; +} + +#endif + +#if !defined(TURN_NO_MYSQL) + +struct _Myconninfo { + char *host; + char *dbname; + char *user; + char *password; + unsigned int port; + unsigned int connect_timeout; +}; + +typedef struct _Myconninfo Myconninfo; + +static void MyconninfoFree(Myconninfo *co) { + if(co) { + if(co->host) turn_free(co->host,strlen(co->host)+1); + if(co->dbname) turn_free(co->dbname, strlen(co->dbname)+1); + if(co->user) turn_free(co->user, strlen(co->user)+1); + if(co->password) turn_free(co->password, strlen(co->password)+1); + ns_bzero(co,sizeof(Myconninfo)); + } +} + +static Myconninfo *MyconninfoParse(char *userdb, char **errmsg) +{ + Myconninfo *co = (Myconninfo*)turn_malloc(sizeof(Myconninfo)); + ns_bzero(co,sizeof(Myconninfo)); + if(userdb) { + char *s0=strdup(userdb); + char *s = s0; + + while(s && *s) { + + while(*s && (*s==' ')) ++s; + char *snext = strstr(s," "); + if(snext) { + *snext = 0; + ++snext; + } + + char* seq = strstr(s,"="); + if(!seq) { + MyconninfoFree(co); + co = NULL; + if(errmsg) { + *errmsg = strdup(s); + } + break; + } + + *seq = 0; + if(!strcmp(s,"host")) + co->host = strdup(seq+1); + else if(!strcmp(s,"ip")) + co->host = strdup(seq+1); + else if(!strcmp(s,"addr")) + co->host = strdup(seq+1); + else if(!strcmp(s,"ipaddr")) + co->host = strdup(seq+1); + else if(!strcmp(s,"hostaddr")) + co->host = strdup(seq+1); + else if(!strcmp(s,"dbname")) + co->dbname = strdup(seq+1); + else if(!strcmp(s,"db")) + co->dbname = strdup(seq+1); + else if(!strcmp(s,"database")) + co->dbname = strdup(seq+1); + else if(!strcmp(s,"user")) + co->user = strdup(seq+1); + else if(!strcmp(s,"uname")) + co->user = strdup(seq+1); + else if(!strcmp(s,"name")) + co->user = strdup(seq+1); + else if(!strcmp(s,"username")) + co->user = strdup(seq+1); + else if(!strcmp(s,"password")) + co->password = strdup(seq+1); + else if(!strcmp(s,"pwd")) + co->password = strdup(seq+1); + else if(!strcmp(s,"passwd")) + co->password = strdup(seq+1); + else if(!strcmp(s,"secret")) + co->password = strdup(seq+1); + else if(!strcmp(s,"port")) + co->port = (unsigned int)atoi(seq+1); + else if(!strcmp(s,"p")) + co->port = (unsigned int)atoi(seq+1); + else if(!strcmp(s,"connect_timeout")) + co->connect_timeout = (unsigned int)atoi(seq+1); + else if(!strcmp(s,"timeout")) + co->connect_timeout = (unsigned int)atoi(seq+1); + else { + MyconninfoFree(co); + co = NULL; + if(errmsg) { + *errmsg = strdup(s); + } + break; + } + + s = snext; + } + + turn_free(s0, strlen(s0)+1); + } + + if(!(co->dbname)) + co->dbname=strdup("0"); + if(!(co->host)) + co->host=strdup("127.0.0.1"); + if(!(co->user)) + co->user=strdup(""); + if(!(co->password)) + co->password=strdup(""); + + return co; +} + +static MYSQL *get_mydb_connection(void) +{ + + if(!is_mysql_userdb()) + return NULL; + + persistent_users_db_t *pud = get_persistent_users_db(); + + MYSQL *mydbconnection = (MYSQL*)(pud->connection); + + if(mydbconnection) { + if(mysql_ping(mydbconnection)) { + mysql_close(mydbconnection); + mydbconnection=NULL; + } + } + + if(!mydbconnection) { + char *errmsg=NULL; + Myconninfo *co=MyconninfoParse(pud->userdb, &errmsg); + if(!co) { + if(errmsg) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot open MySQL DB connection <%s>, connection string format error: %s\n",pud->userdb,errmsg); + turn_free(errmsg,strlen(errmsg)+1); + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot open MySQL DB connection <%s>, connection string format error\n",pud->userdb); + } + } else if(errmsg) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot open MySQL DB connection <%s>, connection string format error: %s\n",pud->userdb,errmsg); + turn_free(errmsg,strlen(errmsg)+1); + MyconninfoFree(co); + } else if(!(co->dbname)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "MySQL Database name is not provided: <%s>\n",pud->userdb); + MyconninfoFree(co); + } else { + mydbconnection = mysql_init(NULL); + if(!mydbconnection) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot initialize MySQL DB connection\n"); + } else { + if(co->connect_timeout) + mysql_options(mydbconnection,MYSQL_OPT_CONNECT_TIMEOUT,&(co->connect_timeout)); + MYSQL *conn = mysql_real_connect(mydbconnection, co->host, co->user, co->password, co->dbname, co->port, NULL, CLIENT_IGNORE_SIGPIPE); + if(!conn) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot open MySQL DB connection: <%s>, runtime error\n",pud->userdb); + mysql_close(mydbconnection); + mydbconnection=NULL; + } else if(mysql_select_db(mydbconnection, co->dbname)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot connect to MySQL DB: %s\n",co->dbname); + mysql_close(mydbconnection); + mydbconnection=NULL; + } else if(!donot_print_connection_success) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "MySQL DB connection success: %s\n",pud->userdb); + } + } + MyconninfoFree(co); + } + pud->connection = mydbconnection; + } + return mydbconnection; +} + +#endif + + +#if !defined(TURN_NO_HIREDIS) + +static void turnFreeRedisReply(void *reply) +{ + if(reply) { + freeReplyObject(reply); + } +} + +struct _Ryconninfo { + char *host; + char *dbname; + char *password; + unsigned int connect_timeout; + unsigned int port; +}; + +typedef struct _Ryconninfo Ryconninfo; + +static void RyconninfoFree(Ryconninfo *co) { + if(co) { + if(co->host) turn_free(co->host, strlen(co->host)+1); + if(co->dbname) turn_free(co->dbname, strlen(co->username)+1); + if(co->password) turn_free(co->password, strlen(co->password)+1); + ns_bzero(co,sizeof(Ryconninfo)); + } +} + +static Ryconninfo *RyconninfoParse(const char *userdb, char **errmsg) +{ + Ryconninfo *co = (Ryconninfo*) turn_malloc(sizeof(Ryconninfo)); + ns_bzero(co,sizeof(Ryconninfo)); + if (userdb) { + char *s0 = strdup(userdb); + char *s = s0; + + while (s && *s) { + + while (*s && (*s == ' ')) + ++s; + char *snext = strstr(s, " "); + if (snext) { + *snext = 0; + ++snext; + } + + char* seq = strstr(s, "="); + if (!seq) { + RyconninfoFree(co); + co = NULL; + if (errmsg) { + *errmsg = strdup(s); + } + break; + } + + *seq = 0; + if (!strcmp(s, "host")) + co->host = strdup(seq + 1); + else if (!strcmp(s, "ip")) + co->host = strdup(seq + 1); + else if (!strcmp(s, "addr")) + co->host = strdup(seq + 1); + else if (!strcmp(s, "ipaddr")) + co->host = strdup(seq + 1); + else if (!strcmp(s, "hostaddr")) + co->host = strdup(seq + 1); + else if (!strcmp(s, "dbname")) + co->dbname = strdup(seq + 1); + else if (!strcmp(s, "db")) + co->dbname = strdup(seq + 1); + else if (!strcmp(s, "database")) + co->dbname = strdup(seq + 1); + else if (!strcmp(s, "user")) + ; + else if (!strcmp(s, "uname")) + ; + else if (!strcmp(s, "name")) + ; + else if (!strcmp(s, "username")) + ; + else if (!strcmp(s, "password")) + co->password = strdup(seq + 1); + else if (!strcmp(s, "pwd")) + co->password = strdup(seq + 1); + else if (!strcmp(s, "passwd")) + co->password = strdup(seq + 1); + else if (!strcmp(s, "secret")) + co->password = strdup(seq + 1); + else if (!strcmp(s, "port")) + co->port = (unsigned int) atoi(seq + 1); + else if (!strcmp(s, "p")) + co->port = (unsigned int) atoi(seq + 1); + else if (!strcmp(s, "connect_timeout")) + co->connect_timeout = (unsigned int) atoi(seq + 1); + else if (!strcmp(s, "timeout")) + co->connect_timeout = (unsigned int) atoi(seq + 1); + else { + RyconninfoFree(co); + co = NULL; + if (errmsg) { + *errmsg = strdup(s); + } + break; + } + + s = snext; + } + + turn_free(s0, strlen(s0)+1); + } + + if(!(co->dbname)) + co->dbname=strdup("0"); + if(!(co->host)) + co->host=strdup("127.0.0.1"); + if(!(co->password)) + co->password=strdup(""); + + return co; +} + +redis_context_handle get_redis_async_connection(struct event_base *base, const char* connection_string, int delete_keys) +{ + redis_context_handle ret = NULL; + + char *errmsg = NULL; + if(base && connection_string && connection_string[0]) { + Ryconninfo *co = RyconninfoParse(connection_string, &errmsg); + if (!co) { + if (errmsg) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot open Redis DB connection <%s>, connection string format error: %s\n", connection_string, errmsg); + turn_free(errmsg,strlen(errmsg)+1); + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot open Redis DB connection <%s>, connection string format error\n", connection_string); + } + } else if (errmsg) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot open Redis DB connection <%s>, connection string format error: %s\n", connection_string, errmsg); + turn_free(errmsg,strlen(errmsg)+1); + RyconninfoFree(co); + } else { + + if(delete_keys) { + + redisContext *rc = NULL; + + char ip[256] = "\0"; + int port = DEFAULT_REDIS_PORT; + if (co->host) + STRCPY(ip,co->host); + if (!ip[0]) + STRCPY(ip,"127.0.0.1"); + + if (co->port) + port = (int) (co->port); + + if (co->connect_timeout) { + struct timeval tv; + tv.tv_usec = 0; + tv.tv_sec = (time_t) (co->connect_timeout); + rc = redisConnectWithTimeout(ip, port, tv); + } else { + rc = redisConnect(ip, port); + } + + if (!rc) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot initialize Redis DB async connection\n"); + } else { + if (co->password) { + turnFreeRedisReply(redisCommand(rc, "AUTH %s", co->password)); + } + if (co->dbname) { + turnFreeRedisReply(redisCommand(rc, "select %s", co->dbname)); + } + { + redisReply *reply = (redisReply*)redisCommand(rc, "keys turn/*/allocation/*/status"); + if(reply) { + secrets_list_t keys; + size_t isz = 0; + char s[513]; + + init_secrets_list(&keys); + + if (reply->type == REDIS_REPLY_ERROR) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", reply->str); + } else if (reply->type != REDIS_REPLY_ARRAY) { + if (reply->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", reply->type); + } else { + size_t i; + for (i = 0; i < reply->elements; ++i) { + add_to_secrets_list(&keys,reply->element[i]->str); + } + } + + for(isz=0;iszhost, co->port, co->password, atoi(co->dbname)); + + if (!ret) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot initialize Redis DB connection\n"); + } else if (is_redis_asyncconn_good(ret) && !donot_print_connection_success) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Redis DB async connection to be used: %s\n", connection_string); + } + RyconninfoFree(co); + } + } + + return ret; +} + +static redisContext *get_redis_connection(void) +{ + if(!is_redis_userdb()) + return NULL; + + persistent_users_db_t *pud = get_persistent_users_db(); + + redisContext *redisconnection = (redisContext*)(pud->connection); + + if(redisconnection) { + if(redisconnection->err) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot connect to redis, err=%d, flags=0x%x\n", __FUNCTION__,(int)redisconnection->err,(unsigned long)redisconnection->flags); + redisFree(redisconnection); + pud->connection = NULL; + redisconnection = NULL; + } + } + + if (!redisconnection) { + + char *errmsg = NULL; + Ryconninfo *co = RyconninfoParse(pud->userdb, &errmsg); + if (!co) { + if (errmsg) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot open Redis DB connection <%s>, connection string format error: %s\n", pud->userdb, errmsg); + turn_free(errmsg,strlen(errmsg)+1); + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot open Redis DB connection <%s>, connection string format error\n", pud->userdb); + } + } else if (errmsg) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot open Redis DB connection <%s>, connection string format error: %s\n", pud->userdb, errmsg); + turn_free(errmsg,strlen(errmsg)+1); + RyconninfoFree(co); + } else { + char ip[256] = "\0"; + int port = DEFAULT_REDIS_PORT; + if (co->host) + STRCPY(ip,co->host); + if (!ip[0]) + STRCPY(ip,"127.0.0.1"); + + if (co->port) + port = (int) (co->port); + + if (co->connect_timeout) { + struct timeval tv; + tv.tv_usec = 0; + tv.tv_sec = (time_t) (co->connect_timeout); + redisconnection = redisConnectWithTimeout(ip, port, tv); + } else { + redisconnection = redisConnect(ip, port); + } + + if (!redisconnection) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Cannot initialize Redis DB connection\n"); + } else { + if (co->password) { + turnFreeRedisReply(redisCommand(redisconnection, "AUTH %s", co->password)); + } + if (co->dbname) { + turnFreeRedisReply(redisCommand(redisconnection, "select %s", co->dbname)); + } + if (!donot_print_connection_success) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Redis DB sync connection success: %s\n", pud->userdb); + } + } + RyconninfoFree(co); + } + pud->connection = redisconnection; + + pud = get_persistent_users_db(); + + redisconnection = (redisContext*)(pud->connection); + } + + return redisconnection; +} + +#endif + +static int get_auth_secrets(secrets_list_t *sl, u08bits *realm) +{ + int ret = -1; + + clean_secrets_list(sl); + + if(get_secrets_list_size(&turn_params.default_users_db.ram_db.static_auth_secrets)) { + size_t i = 0; + for(i=0;itype == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", reply->str); + else if (reply->type != REDIS_REPLY_ARRAY) { + if (reply->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", reply->type); + } else { + size_t i; + for (i = 0; i < reply->elements; ++i) { + add_to_secrets_list(&keys,reply->element[i]->str); + } + } + + for(isz=0;isztype == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", rget->str); + else if (rget->type != REDIS_REPLY_STRING) { + if (rget->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", rget->type); + } else { + add_to_secrets_list(sl,rget->str); + } + turnFreeRedisReply(rget); + } + } + + clean_secrets_list(&keys); + + ret = 0; + + turnFreeRedisReply(reply); + } + } +#endif + + return ret; +} + +/* + * Timestamp retrieval + */ +static turn_time_t get_rest_api_timestamp(char *usname) +{ + turn_time_t ts = 0; + int ts_set = 0; + + char *col = strchr(usname,turn_params.rest_api_separator); + + if(col) { + if(col == usname) { + usname +=1; + } else { + char *ptr = usname; + int found_non_figure = 0; + while(ptr < col) { + if(!(ptr[0]>='0' && ptr[0]<='9')) { + found_non_figure=1; + break; + } + ++ptr; + } + if(found_non_figure) { + ts = (turn_time_t)atol(col+1); + ts_set = 1; + } else { + *col=0; + ts = (turn_time_t)atol(usname); + ts_set = 1; + *col=turn_params.rest_api_separator; + } + } + } + + if(!ts_set) { + ts = (turn_time_t)atol(usname); + } + + return ts; +} + +static char *get_real_username(char *usname) +{ + if(turn_params.use_auth_secret_with_timestamp) { + char *col=strchr(usname,turn_params.rest_api_separator); + if(col) { + if(col == usname) { + usname +=1; + } else { + char *ptr = usname; + int found_non_figure = 0; + while(ptr < col) { + if(!(ptr[0]>='0' && ptr[0]<='9')) { + found_non_figure=1; + break; + } + ++ptr; + } + if(!found_non_figure) { + usname = col+1; + } else { + *col=0; + usname = strdup(usname); + *col=turn_params.rest_api_separator; + return usname; + } + } + } + } + + return strdup(usname); +} + +/* + * Long-term mechanism password retrieval + */ +int get_user_key(u08bits *usname, u08bits *realm, hmackey_t key, ioa_network_buffer_handle nbh) +{ + int ret = -1; + + if(turn_params.use_auth_secret_with_timestamp) { + + turn_time_t ctime = (turn_time_t) time(NULL); + turn_time_t ts = 0; + secrets_list_t sl; + size_t sll = 0; + + init_secrets_list(&sl); + + if(get_auth_secrets(&sl, realm)<0) + return ret; + + ts = get_rest_api_timestamp((char*)usname); + + if(!turn_time_before(ts, ctime)) { + + u08bits hmac[MAXSHASIZE]; + unsigned int hmac_len; + st_password_t pwdtmp; + + hmac[0] = 0; + + stun_attr_ref sar = stun_attr_get_first_by_type_str(ioa_network_buffer_data(nbh), + ioa_network_buffer_get_size(nbh), + STUN_ATTRIBUTE_MESSAGE_INTEGRITY); + if (!sar) + return -1; + + int sarlen = stun_attr_get_len(sar); + switch(sarlen) { + case SHA1SIZEBYTES: + if(turn_params.shatype != SHATYPE_SHA1) + return -1; + hmac_len = SHA1SIZEBYTES; + break; + case SHA256SIZEBYTES: + if(turn_params.shatype != SHATYPE_SHA256) + return -1; + hmac_len = SHA256SIZEBYTES; + break; + default: + return -1; + }; + + for(sll=0;sll=0) { + size_t pwd_length = 0; + char *pwd = base64_encode(hmac,hmac_len,&pwd_length); + + if(pwd) { + if(pwd_length<1) { + turn_free(pwd,strlen(pwd)+1); + } else { + if(stun_produce_integrity_key_str((u08bits*)usname, realm, (u08bits*)pwd, key, turn_params.shatype)>=0) { + + if(stun_check_message_integrity_by_key_str(TURN_CREDENTIALS_LONG_TERM, + ioa_network_buffer_data(nbh), + ioa_network_buffer_get_size(nbh), + key, + pwdtmp, + turn_params.shatype,NULL)>0) { + + ret = 0; + } + } + turn_free(pwd,pwd_length); + + if(ret==0) + break; + } + } + } + } + } + } + + clean_secrets_list(&sl); + + return ret; + } + + ur_string_map_value_type ukey = NULL; + ur_string_map_lock(turn_params.default_users_db.ram_db.static_accounts); + if(ur_string_map_get(turn_params.default_users_db.ram_db.static_accounts, (ur_string_map_key_type)usname, &ukey)) { + ret = 0; + } else { + ur_string_map_lock(turn_params.default_users_db.ram_db.dynamic_accounts); + if(ur_string_map_get(turn_params.default_users_db.ram_db.dynamic_accounts, (ur_string_map_key_type)usname, &ukey)) { + ret = 0; + } + ur_string_map_unlock(turn_params.default_users_db.ram_db.dynamic_accounts); + } + ur_string_map_unlock(turn_params.default_users_db.ram_db.static_accounts); + + if(ret==0) { + size_t sz = get_hmackey_size(turn_params.shatype); + ns_bcopy(ukey,key,sz); + return 0; + } + +#if !defined(TURN_NO_PQ) + { + PGconn * pqc = get_pqdb_connection(); + if(pqc) { + char statement[LONG_STRING_SIZE]; + snprintf(statement,sizeof(statement),"select hmackey from turnusers_lt where name='%s' and realm='%s'",usname,realm); + PGresult *res = PQexec(pqc, statement); + + if(!res || (PQresultStatus(res) != PGRES_TUPLES_OK) || (PQntuples(res)!=1)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving PostgreSQL DB information: %s\n",PQerrorMessage(pqc)); + } else { + char *kval = PQgetvalue(res,0,0); + int len = PQgetlength(res,0,0); + if(kval) { + size_t sz = get_hmackey_size(turn_params.shatype); + if(((size_t)lentype == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", rget->str); + else if (rget->type != REDIS_REPLY_STRING) { + if (rget->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", rget->type); + } else { + size_t sz = get_hmackey_size(turn_params.shatype); + if(strlen(rget->str)str,usname); + } else if(convert_string_key_to_binary(rget->str, key, sz)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong key: %s, user %s\n",rget->str,usname); + } else { + ret = 0; + } + } + turnFreeRedisReply(rget); + } + if(ret != 0) { + snprintf(s,sizeof(s),"get turn/realm/%s/user/%s/password", (char*)realm, usname); + rget = (redisReply *)redisCommand(rc, s); + if(rget) { + if (rget->type == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", rget->str); + else if (rget->type != REDIS_REPLY_STRING) { + if (rget->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", rget->type); + } else { + if(stun_produce_integrity_key_str((u08bits*)usname, realm, (u08bits*)rget->str, key, turn_params.shatype)>=0) { + ret = 0; + } + } + turnFreeRedisReply(rget); + } + } + } + } +#endif + + return ret; +} + +/* + * Short-term mechanism password retrieval + */ +int get_user_pwd(u08bits *usname, st_password_t pwd) +{ + int ret = -1; + + UNUSED_ARG(pwd); + + char statement[LONG_STRING_SIZE]; + snprintf(statement,sizeof(statement),"select password from turnusers_st where name='%s'",usname); + + { +#if !defined(TURN_NO_PQ) + PGconn * pqc = get_pqdb_connection(); + if(pqc) { + PGresult *res = PQexec(pqc, statement); + + if(!res || (PQresultStatus(res) != PGRES_TUPLES_OK) || (PQntuples(res)!=1)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving PostgreSQL DB information: %s\n",PQerrorMessage(pqc)); + } else { + char *kval = PQgetvalue(res,0,0); + if(kval) { + strncpy((char*)pwd,kval,sizeof(st_password_t)); + ret = 0; + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong password data for user %s: NULL\n",usname); + } + } + + if(res) { + PQclear(res); + } + } +#endif +#if !defined(TURN_NO_MYSQL) + MYSQL * myc = get_mydb_connection(); + if(myc) { + int res = mysql_query(myc, statement); + if(res) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving MySQL DB information: %s\n",mysql_error(myc)); + } else { + MYSQL_RES *mres = mysql_store_result(myc); + if(!mres) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving MySQL DB information: %s\n",mysql_error(myc)); + } else if(mysql_field_count(myc)!=1) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unknown error retrieving MySQL DB information: %s\n",statement); + } else { + MYSQL_ROW row = mysql_fetch_row(mres); + if(row && row[0]) { + unsigned long *lengths = mysql_fetch_lengths(mres); + if(lengths) { + if(lengths[0]<1) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong password data for user %s, size in MySQL DB is zero(0)\n",usname); + } else { + ns_bcopy(row[0],pwd,lengths[0]); + pwd[lengths[0]]=0; + ret = 0; + } + } + } + } + + if(mres) + mysql_free_result(mres); + } + } +#endif +#if !defined(TURN_NO_HIREDIS) + { + redisContext * rc = get_redis_connection(); + if(rc) { + char s[LONG_STRING_SIZE]; + snprintf(s,sizeof(s),"get turn/user/%s/password", usname); + redisReply *rget = (redisReply *)redisCommand(rc, s); + if(rget) { + if (rget->type == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", rget->str); + else if (rget->type != REDIS_REPLY_STRING) { + if (rget->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", rget->type); + } else { + strncpy((char*)pwd,rget->str,SHORT_TERM_PASSWORD_SIZE); + pwd[SHORT_TERM_PASSWORD_SIZE]=0; + ret = 0; + } + turnFreeRedisReply(rget); + } + } + } +#endif + } + + return ret; +} + +u08bits *start_user_check(turnserver_id id, turn_credential_type ct, u08bits *usname, u08bits *realm, get_username_resume_cb resume, ioa_net_data *in_buffer, u64bits ctxkey, int *postpone_reply) +{ + *postpone_reply = 1; + + struct auth_message am; + ns_bzero(&am,sizeof(struct auth_message)); + am.id = id; + am.ct = ct; + STRCPY(am.username,usname); + STRCPY(am.realm,realm); + am.resume_func = resume; + memcpy(&(am.in_buffer),in_buffer,sizeof(ioa_net_data)); + in_buffer->nbh = NULL; + am.ctxkey = ctxkey; + + send_auth_message_to_auth_server(&am); + + return NULL; +} + +int check_new_allocation_quota(u08bits *user, u08bits *realm) +{ + int ret = 0; + if (user) { + u08bits *username = (u08bits*)get_real_username((char*)user); + realm_params_t *rp = get_realm((char*)realm); + ur_string_map_lock(rp->status.alloc_counters); + if (rp->options.perf_options.total_quota && (rp->status.total_current_allocs >= rp->options.perf_options.total_quota)) { + ret = -1; + } else if(username[0]){ + ur_string_map_value_type value = 0; + if (!ur_string_map_get(rp->status.alloc_counters, (ur_string_map_key_type) username, &value)) { + value = (ur_string_map_value_type) 1; + ur_string_map_put(rp->status.alloc_counters, (ur_string_map_key_type) username, value); + ++(rp->status.total_current_allocs); + } else { + if ((rp->options.perf_options.user_quota) && ((size_t) value >= (size_t)(rp->options.perf_options.user_quota))) { + ret = -1; + } else { + value = (ur_string_map_value_type)(((size_t)value) + 1); + ur_string_map_put(rp->status.alloc_counters, (ur_string_map_key_type) username, value); + ++(rp->status.total_current_allocs); + } + } + } else { + ++(rp->status.total_current_allocs); + } + turn_free(username,strlen(username)+1); + ur_string_map_unlock(rp->status.alloc_counters); + } + return ret; +} + +void release_allocation_quota(u08bits *user, u08bits *realm) +{ + if (user) { + u08bits *username = (u08bits*)get_real_username((char*)user); + realm_params_t *rp = get_realm((char*)realm); + ur_string_map_lock(rp->status.alloc_counters); + ur_string_map_value_type value = 0; + ur_string_map_get(rp->status.alloc_counters, (ur_string_map_key_type) username, &value); + if (value) { + value = (ur_string_map_value_type)(((size_t)value) - 1); + ur_string_map_put(rp->status.alloc_counters, (ur_string_map_key_type) username, value); + } + if (rp->status.total_current_allocs) + --(rp->status.total_current_allocs); + ur_string_map_unlock(rp->status.alloc_counters); + turn_free(username, strlen(username)+1); + } +} + +////////////////////////////////// + +void read_userdb_file(int to_print) +{ + static char *full_path_to_userdb_file = NULL; + static int first_read = 1; + static turn_time_t mtime = 0; + + if(turn_params.default_users_db.userdb_type != TURN_USERDB_TYPE_FILE) + return; + if(turn_params.use_auth_secret_with_timestamp) + return; + + FILE *f = NULL; + + persistent_users_db_t *pud = get_persistent_users_db(); + + if(full_path_to_userdb_file) { + struct stat sb; + if(stat(full_path_to_userdb_file,&sb)<0) { + perror("File statistics"); + } else { + turn_time_t newmtime = (turn_time_t)(sb.st_mtime); + if(mtime == newmtime) + return; + mtime = newmtime; + + } + } + + if (!full_path_to_userdb_file) + full_path_to_userdb_file = find_config_file(pud->userdb, first_read); + + if (full_path_to_userdb_file) + f = fopen(full_path_to_userdb_file, "r"); + + if (f) { + + char sbuf[LONG_STRING_SIZE]; + + ur_string_map_lock(turn_params.default_users_db.ram_db.dynamic_accounts); + + ur_string_map_clean(turn_params.default_users_db.ram_db.dynamic_accounts); + + for (;;) { + char *s = fgets(sbuf, sizeof(sbuf) - 1, f); + if (!s) + break; + s = skip_blanks(s); + if (s[0] == '#') + continue; + if (!s[0]) + continue; + size_t slen = strlen(s); + while (slen && (s[slen - 1] == 10 || s[slen - 1] == 13)) + s[--slen] = 0; + if (slen) { + if(to_print) { + char* sc=strstr(s,":"); + if(sc) + sc[0]=0; + printf("%s\n",s); + } else { + add_user_account(s,1); + } + } + } + + ur_string_map_unlock(turn_params.default_users_db.ram_db.dynamic_accounts); + + fclose(f); + + } else if (first_read) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "WARNING: Cannot find userdb file: %s: going without flat file user database.\n", pud->userdb); + } + + first_read = 0; +} + +int add_user_account(char *user, int dynamic) +{ + /* Realm is either default or empty for users taken from file or command-line */ + if(user && !turn_params.use_auth_secret_with_timestamp) { + char *s = strstr(user, ":"); + if(!s || (s==user) || (strlen(s)<2)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong user account: %s\n",user); + } else { + size_t ulen = s-user; + char *usname = (char*)turn_malloc(sizeof(char)*(ulen+1)); + strncpy(usname,user,ulen); + usname[ulen]=0; + if(SASLprep((u08bits*)usname)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong user name: %s\n",user); + turn_free(usname,sizeof(char)*(ulen+1)); + return -1; + } + s = skip_blanks(s+1); + hmackey_t *key = (hmackey_t*)turn_malloc(sizeof(hmackey_t)); + if(strstr(s,"0x")==s) { + char *keysource = s + 2; + size_t sz = get_hmackey_size(turn_params.shatype); + if(strlen(keysource)options.name, (u08bits*)s, *key, turn_params.shatype); + } + if(dynamic) { + ur_string_map_lock(turn_params.default_users_db.ram_db.dynamic_accounts); + ur_string_map_put(turn_params.default_users_db.ram_db.dynamic_accounts, (ur_string_map_key_type)usname, (ur_string_map_value_type)*key); + ur_string_map_unlock(turn_params.default_users_db.ram_db.dynamic_accounts); + } else { + ur_string_map_lock(turn_params.default_users_db.ram_db.static_accounts); + ur_string_map_put(turn_params.default_users_db.ram_db.static_accounts, (ur_string_map_key_type)usname, (ur_string_map_value_type)*key); + ur_string_map_unlock(turn_params.default_users_db.ram_db.static_accounts); + } + turn_params.default_users_db.ram_db.users_number++; + free(usname); + return 0; + } + } + + return -1; +} + +////////////////// Admin ///////////////////////// + +static int list_users(int is_st, u08bits *realm) +{ + donot_print_connection_success = 1; + + if(is_pqsql_userdb()){ +#if !defined(TURN_NO_PQ) + char statement[LONG_STRING_SIZE]; + PGconn *pqc = get_pqdb_connection(); + if(pqc) { + if(is_st) { + snprintf(statement,sizeof(statement),"select name from turnusers_st order by name"); + } else if(realm && realm[0]) { + snprintf(statement,sizeof(statement),"select name from turnusers_lt where realm='%s' order by name",realm); + } else { + snprintf(statement,sizeof(statement),"select name from turnusers_lt order by name"); + } + PGresult *res = PQexec(pqc, statement); + if(!res || (PQresultStatus(res) != PGRES_TUPLES_OK)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving PostgreSQL DB information: %s\n",PQerrorMessage(pqc)); + } else { + int i = 0; + for(i=0;itype == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", reply->str); + else if (reply->type != REDIS_REPLY_ARRAY) { + if (reply->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", reply->type); + } else { + size_t i; + for (i = 0; i < reply->elements; ++i) { + add_to_secrets_list(&keys,reply->element[i]->str); + } + } + turnFreeRedisReply(reply); + } + + if(realm && realm[0]) { + reply = (redisReply*)redisCommand(rc, "keys turn/realm/%s/user/*/password", (char*)realm); + } else { + reply = (redisReply*)redisCommand(rc, "keys turn/realm/*/user/*/password"); + } + if(reply) { + + if (reply->type == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", reply->str); + else if (reply->type != REDIS_REPLY_ARRAY) { + if (reply->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", reply->type); + } else { + size_t i; + for (i = 0; i < reply->elements; ++i) { + add_to_secrets_list(&keys,reply->element[i]->str); + } + } + turnFreeRedisReply(reply); + } + } else { + + reply = (redisReply*)redisCommand(rc, "keys turn/user/*/password"); + if(reply) { + if (reply->type == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", reply->str); + else if (reply->type != REDIS_REPLY_ARRAY) { + if (reply->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", reply->type); + } else { + size_t i; + for (i = 0; i < reply->elements; ++i) { + add_to_secrets_list(&keys,reply->element[i]->str); + } + } + turnFreeRedisReply(reply); + } + } + + for(isz=0;isztype == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", reply->str); + else if (reply->type != REDIS_REPLY_ARRAY) { + if (reply->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", reply->type); + } else { + size_t i; + for (i = 0; i < reply->elements; ++i) { + add_to_secrets_list(&keys,reply->element[i]->str); + } + } + + for(isz=0;isztype == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", rget->str); + else if (rget->type != REDIS_REPLY_STRING) { + if (rget->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", rget->type); + } else { + printf("%s\n",rget->str); + } + } + turnFreeRedisReply(rget); + } + + clean_secrets_list(&keys); + + turnFreeRedisReply(reply); + } + } +#endif + } + + return 0; +} + +static int del_secret(u08bits *secret, u08bits *realm) { + + UNUSED_ARG(secret); + + must_set_admin_realm(realm); + + donot_print_connection_success=1; + + if (is_pqsql_userdb()) { +#if !defined(TURN_NO_PQ) + char statement[LONG_STRING_SIZE]; + PGconn *pqc = get_pqdb_connection(); + if (pqc) { + if(!secret || (secret[0]==0)) + snprintf(statement,sizeof(statement),"delete from turn_secret where realm='%s'",realm); + else + snprintf(statement,sizeof(statement),"delete from turn_secret where value='%s' and realm='%s'",secret,realm); + + PGresult *res = PQexec(pqc, statement); + if (res) { + PQclear(res); + } + } +#endif + } else if (is_mysql_userdb()) { +#if !defined(TURN_NO_MYSQL) + char statement[LONG_STRING_SIZE]; + MYSQL * myc = get_mydb_connection(); + if (myc) { + if(!secret || (secret[0]==0)) + snprintf(statement,sizeof(statement),"delete from turn_secret where realm='%s'",realm); + else + snprintf(statement,sizeof(statement),"delete from turn_secret where value='%s' and realm='%s'",secret,realm); + mysql_query(myc, statement); + } +#endif + } else if(is_redis_userdb()) { +#if !defined(TURN_NO_HIREDIS) + redisContext *rc = get_redis_connection(); + if(rc) { + redisReply *reply = (redisReply*)redisCommand(rc, "keys turn/realm/%s/secret/*", (char*)realm); + if(reply) { + secrets_list_t keys; + size_t isz = 0; + char s[LONG_STRING_SIZE]; + + init_secrets_list(&keys); + + if (reply->type == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", reply->str); + else if (reply->type != REDIS_REPLY_ARRAY) { + if (reply->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", reply->type); + } else { + size_t i; + for (i = 0; i < reply->elements; ++i) { + add_to_secrets_list(&keys,reply->element[i]->str); + } + } + + for(isz=0;isztype == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", rget->str); + else if (rget->type != REDIS_REPLY_STRING) { + if (rget->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", rget->type); + } else { + if(!strcmp((char*)secret,rget->str)) { + snprintf(s,sizeof(s),"del %s", keys.secrets[isz]); + turnFreeRedisReply(redisCommand(rc, s)); + } + } + turnFreeRedisReply(rget); + } + } + } + + turnFreeRedisReply(redisCommand(rc, "save")); + + clean_secrets_list(&keys); + + turnFreeRedisReply(reply); + } + } +#endif + } + + return 0; +} + +static int set_secret(u08bits *secret, u08bits *realm) { + + if(!secret || (secret[0]==0)) + return 0; + + must_set_admin_realm(realm); + + donot_print_connection_success = 1; + + del_secret(secret, realm); + + if (is_pqsql_userdb()) { +#if !defined(TURN_NO_PQ) + char statement[LONG_STRING_SIZE]; + PGconn *pqc = get_pqdb_connection(); + if (pqc) { + snprintf(statement,sizeof(statement),"insert into turn_secret (realm,value) values('%s','%s')",realm,secret); + PGresult *res = PQexec(pqc, statement); + if (!res || (PQresultStatus(res) != PGRES_COMMAND_OK)) { + TURN_LOG_FUNC( + TURN_LOG_LEVEL_ERROR, + "Error inserting/updating secret key information: %s\n", + PQerrorMessage(pqc)); + } + if (res) { + PQclear(res); + } + } +#endif + } else if (is_mysql_userdb()) { +#if !defined(TURN_NO_MYSQL) + char statement[LONG_STRING_SIZE]; + MYSQL * myc = get_mydb_connection(); + if (myc) { + snprintf(statement,sizeof(statement),"insert into turn_secret (realm,value) values('%s','%s')",realm,secret); + int res = mysql_query(myc, statement); + if (res) { + TURN_LOG_FUNC( + TURN_LOG_LEVEL_ERROR, + "Error inserting/updating secret key information: %s\n", + mysql_error(myc)); + } + } +#endif + } else if(is_redis_userdb()) { +#if !defined(TURN_NO_HIREDIS) + redisContext *rc = get_redis_connection(); + if(rc) { + char s[LONG_STRING_SIZE]; + + del_secret(secret, realm); + + snprintf(s,sizeof(s),"set turn/realm/%s/secret/%lu %s", (char*)realm, (unsigned long)turn_time(), secret); + + turnFreeRedisReply(redisCommand(rc, s)); + turnFreeRedisReply(redisCommand(rc, "save")); + } +#endif + } + + return 0; +} + +static int add_origin(u08bits *origin0, u08bits *realm) +{ + UNUSED_ARG(realm); + UNUSED_ARG(origin0); + char origin[STUN_MAX_ORIGIN_SIZE+1]; + get_canonic_origin((char*)origin0, origin, sizeof(origin)-1); +#if !defined(TURN_NO_PQ) + if(is_pqsql_userdb()) { + char statement[LONG_STRING_SIZE]; + PGconn *pqc = get_pqdb_connection(); + if(pqc) { + snprintf(statement,sizeof(statement),"insert into turn_origin_to_realm (origin,realm) values('%s','%s')",origin,realm); + PGresult *res = PQexec(pqc, statement); + if(!res || (PQresultStatus(res) != PGRES_COMMAND_OK)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error inserting origin information: %s\n",PQerrorMessage(pqc)); + } + if(res) { + PQclear(res); + } + } + } +#endif + +#if !defined(TURN_NO_MYSQL) + if (is_mysql_userdb()) { + char statement[LONG_STRING_SIZE]; + MYSQL * myc = get_mydb_connection(); + if (myc) { + snprintf(statement,sizeof(statement),"insert into turn_origin_to_realm (origin,realm) values('%s','%s')",origin,realm); + int res = mysql_query(myc, statement); + if (res) { + TURN_LOG_FUNC( + TURN_LOG_LEVEL_ERROR, + "Error inserting origin information: %s\n", + mysql_error(myc)); + } + } + } +#endif + +#if !defined(TURN_NO_HIREDIS) + if(is_redis_userdb()) { + redisContext *rc = get_redis_connection(); + if(rc) { + char s[LONG_STRING_SIZE]; + + snprintf(s,sizeof(s),"set turn/origin/%s %s", (char*)origin, (char*)realm); + + turnFreeRedisReply(redisCommand(rc, s)); + turnFreeRedisReply(redisCommand(rc, "save")); + } + } +#endif + return 0; +} + +static int del_origin(u08bits *origin0) +{ + UNUSED_ARG(origin0); + char origin[STUN_MAX_ORIGIN_SIZE+1]; + get_canonic_origin((char*)origin0, origin, sizeof(origin)-1); +#if !defined(TURN_NO_PQ) + if(is_pqsql_userdb()) { + char statement[LONG_STRING_SIZE]; + PGconn *pqc = get_pqdb_connection(); + if(pqc) { + snprintf(statement,sizeof(statement),"delete from turn_origin_to_realm where origin='%s'",origin); + PGresult *res = PQexec(pqc, statement); + if(!res || (PQresultStatus(res) != PGRES_COMMAND_OK)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error deleting origin information: %s\n",PQerrorMessage(pqc)); + } + if(res) { + PQclear(res); + } + } + } +#endif + +#if !defined(TURN_NO_MYSQL) + if (is_mysql_userdb()) { + char statement[LONG_STRING_SIZE]; + MYSQL * myc = get_mydb_connection(); + if (myc) { + snprintf(statement,sizeof(statement),"delete from turn_origin_to_realm where origin='%s'",origin); + int res = mysql_query(myc, statement); + if (res) { + TURN_LOG_FUNC( + TURN_LOG_LEVEL_ERROR, + "Error deleting origin information: %s\n", + mysql_error(myc)); + } + } + } +#endif + +#if !defined(TURN_NO_HIREDIS) + if(is_redis_userdb()) { + redisContext *rc = get_redis_connection(); + if(rc) { + char s[LONG_STRING_SIZE]; + + snprintf(s,sizeof(s),"del turn/origin/%s", (char*)origin); + + turnFreeRedisReply(redisCommand(rc, s)); + turnFreeRedisReply(redisCommand(rc, "save")); + } + } +#endif + return 0; +} + +static int list_origins(u08bits *realm) +{ + donot_print_connection_success = 1; + + if(is_pqsql_userdb()){ +#if !defined(TURN_NO_PQ) + char statement[LONG_STRING_SIZE]; + PGconn *pqc = get_pqdb_connection(); + if(pqc) { + if(realm && realm[0]) { + snprintf(statement,sizeof(statement),"select origin,realm from turn_origin_to_realm where realm='%s' order by origin",realm); + } else { + snprintf(statement,sizeof(statement),"select origin,realm from turn_origin_to_realm order by origin,realm"); + } + PGresult *res = PQexec(pqc, statement); + if(!res || (PQresultStatus(res) != PGRES_TUPLES_OK)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving PostgreSQL DB information: %s\n",PQerrorMessage(pqc)); + } else { + int i = 0; + for(i=0;i> %s\n",oval,rval); + } + } + } + } + if(res) { + PQclear(res); + } + } +#endif + } else if(is_mysql_userdb()){ +#if !defined(TURN_NO_MYSQL) + char statement[LONG_STRING_SIZE]; + MYSQL * myc = get_mydb_connection(); + if(myc) { + if(realm && realm[0]) { + snprintf(statement,sizeof(statement),"select origin,realm from turn_origin_to_realm where realm='%s' order by origin",realm); + } else { + snprintf(statement,sizeof(statement),"select origin,realm from turn_origin_to_realm order by origin,realm"); + } + int res = mysql_query(myc, statement); + if(res) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving MySQL DB information: %s\n",mysql_error(myc)); + } else { + MYSQL_RES *mres = mysql_store_result(myc); + if(!mres) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving MySQL DB information: %s\n",mysql_error(myc)); + } else if(mysql_field_count(myc)!=2) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unknown error retrieving MySQL DB information: %s\n",statement); + } else { + for(;;) { + MYSQL_ROW row = mysql_fetch_row(mres); + if(!row) { + break; + } else { + if(row[0] && row[1]) { + printf("%s ==>> %s\n",row[0],row[1]); + } + } + } + } + + if(mres) + mysql_free_result(mres); + } + } +#endif + } else if(is_redis_userdb()) { +#if !defined(TURN_NO_HIREDIS) + redisContext *rc = get_redis_connection(); + if(rc) { + secrets_list_t keys; + size_t isz = 0; + + init_secrets_list(&keys); + + redisReply *reply = NULL; + + { + reply = (redisReply*)redisCommand(rc, "keys turn/origin/*"); + if(reply) { + + if (reply->type == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", reply->str); + else if (reply->type != REDIS_REPLY_ARRAY) { + if (reply->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", reply->type); + } else { + size_t i; + size_t offset = strlen("turn/origin/"); + for (i = 0; i < reply->elements; ++i) { + add_to_secrets_list(&keys,reply->element[i]->str+offset); + } + } + turnFreeRedisReply(reply); + } + } + + for(isz=0;isztype == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", reply->str); + else if (reply->type != REDIS_REPLY_STRING) { + if (reply->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", reply->type); + } else { + if(!(realm && realm[0] && strcmp((char*)realm,reply->str))) { + printf("%s ==>> %s\n",o,reply->str); + } + } + turnFreeRedisReply(reply); + } + } + + clean_secrets_list(&keys); + } +#endif + } + + return 0; +} + +static int set_realm_option_one(u08bits *realm, vint value, const char* opt) +{ + UNUSED_ARG(realm); + UNUSED_ARG(value); + UNUSED_ARG(opt); + if(value<0) + return 0; +#if !defined(TURN_NO_PQ) + if(is_pqsql_userdb()) { + char statement[LONG_STRING_SIZE]; + PGconn *pqc = get_pqdb_connection(); + if(pqc) { + { + snprintf(statement,sizeof(statement),"delete from turn_realm_option where realm='%s' and opt='%s'",realm,opt); + PGresult *res = PQexec(pqc, statement); + if(res) { + PQclear(res); + } + } + if(value>0) { + snprintf(statement,sizeof(statement),"insert into turn_realm_option (realm,opt,value) values('%s','%s','%d')",realm,opt,(int)value); + PGresult *res = PQexec(pqc, statement); + if(!res || (PQresultStatus(res) != PGRES_COMMAND_OK)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error inserting realm option information: %s\n",PQerrorMessage(pqc)); + } + if(res) { + PQclear(res); + } + } + } + } +#endif + +#if !defined(TURN_NO_MYSQL) + if (is_mysql_userdb()) { + char statement[LONG_STRING_SIZE]; + MYSQL * myc = get_mydb_connection(); + if (myc) { + { + snprintf(statement,sizeof(statement),"delete from turn_realm_option where realm='%s' and opt='%s'",realm,opt); + mysql_query(myc, statement); + } + if(value>0) { + snprintf(statement,sizeof(statement),"insert into turn_realm_option (realm,opt,value) values('%s','%s','%d')",realm,opt,(int)value); + int res = mysql_query(myc, statement); + if (res) { + TURN_LOG_FUNC( + TURN_LOG_LEVEL_ERROR, + "Error inserting realm option information: %s\n", + mysql_error(myc)); + } + } + } + } +#endif + +#if !defined(TURN_NO_HIREDIS) + if(is_redis_userdb()) { + redisContext *rc = get_redis_connection(); + if(rc) { + char s[LONG_STRING_SIZE]; + + if(value>0) + snprintf(s,sizeof(s),"set turn/realm/%s/%s %d", (char*)realm, opt, (int)value); + else + snprintf(s,sizeof(s),"del turn/realm/%s/%s", (char*)realm, opt); + + turnFreeRedisReply(redisCommand(rc, s)); + turnFreeRedisReply(redisCommand(rc, "save")); + } + } +#endif + return 0; +} + +static int set_realm_option(u08bits *realm, perf_options_t *po) +{ + set_realm_option_one(realm,po->max_bps,"max-bps"); + set_realm_option_one(realm,po->user_quota,"user-quota"); + set_realm_option_one(realm,po->total_quota,"total-quota"); + return 0; +} + +static int list_realm_options(u08bits *realm) +{ + donot_print_connection_success = 1; + + if(is_pqsql_userdb()){ +#if !defined(TURN_NO_PQ) + char statement[LONG_STRING_SIZE]; + PGconn *pqc = get_pqdb_connection(); + if(pqc) { + if(realm && realm[0]) { + snprintf(statement,sizeof(statement),"select realm,opt,value from turn_realm_option where realm='%s' order by realm,opt",realm); + } else { + snprintf(statement,sizeof(statement),"select realm,opt,value from turn_realm_option order by realm,opt"); + } + PGresult *res = PQexec(pqc, statement); + if(!res || (PQresultStatus(res) != PGRES_TUPLES_OK)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error retrieving PostgreSQL DB information: %s\n",PQerrorMessage(pqc)); + } else { + int i = 0; + for(i=0;itype == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", reply->str); + else if (reply->type != REDIS_REPLY_ARRAY) { + if (reply->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", reply->type); + } else { + size_t i; + for (i = 0; i < reply->elements; ++i) { + if(strstr(reply->element[i]->str,"/max-bps")|| + strstr(reply->element[i]->str,"/total-quota")|| + strstr(reply->element[i]->str,"/user-quota")) { + add_to_secrets_list(&keys,reply->element[i]->str); + } + } + } + turnFreeRedisReply(reply); + } + } + + size_t offset = strlen("turn/realm/"); + + for(isz=0;isztype == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", reply->str); + else if (reply->type != REDIS_REPLY_STRING) { + if (reply->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", reply->type); + } else { + printf("%s = %s\n",o+offset,reply->str); + } + turnFreeRedisReply(reply); + } + } + + clean_secrets_list(&keys); + } +#endif + } + + return 0; +} + +int adminuser(u08bits *user, u08bits *realm, u08bits *pwd, u08bits *secret, u08bits *origin, + TURNADMIN_COMMAND_TYPE ct, int is_st, + perf_options_t *po) +{ + hmackey_t key; + char skey[sizeof(hmackey_t)*2+1]; + + donot_print_connection_success = 1; + + st_password_t passwd; + + if(ct == TA_LIST_USERS) { + return list_users(is_st, realm); + } + + if(ct == TA_LIST_ORIGINS) { + return list_origins(realm); + } + + if(ct == TA_SHOW_SECRET) { + return show_secret(realm); + } + + if(ct == TA_SET_SECRET) { + return set_secret(secret, realm); + } + + if(ct == TA_DEL_SECRET) { + return del_secret(secret, realm); + } + + if(ct == TA_ADD_ORIGIN) { + must_set_admin_origin(origin); + must_set_admin_realm(realm); + return add_origin(origin,realm); + } + + if(ct == TA_DEL_ORIGIN) { + must_set_admin_origin(origin); + return del_origin(origin); + } + + if(ct == TA_SET_REALM_OPTION) { + must_set_admin_realm(realm); + if(!(po && (po->max_bps>=0 || po->total_quota>=0 || po->user_quota>=0))) { + fprintf(stderr, "The operation cannot be completed: a realm option must be set.\n"); + exit(-1); + } + return set_realm_option(realm,po); + } + + if(ct == TA_LIST_REALM_OPTIONS) { + return list_realm_options(realm); + } + + must_set_admin_user(user); + + if(ct != TA_DELETE_USER) { + + must_set_admin_pwd(pwd); + + if(is_st) { + strncpy((char*)passwd,(char*)pwd,sizeof(st_password_t)); + } else { + stun_produce_integrity_key_str(user, realm, pwd, key, turn_params.shatype); + size_t i = 0; + size_t sz = get_hmackey_size(turn_params.shatype); + int maxsz = (int)(sz*2)+1; + char *s=skey; + for(i=0;(i2);i++) { + snprintf(s,(size_t)(sz*2),"%02x",(unsigned int)key[i]); + maxsz-=2; + s+=2; + } + skey[sz*2]=0; + } + } + + if(ct == TA_PRINT_KEY) { + + if(!is_st) { + printf("0x%s\n",skey); + } + + } else if(is_pqsql_userdb()){ + +#if !defined(TURN_NO_PQ) + + if(!is_st) { + must_set_admin_realm(realm); + } + + char statement[LONG_STRING_SIZE]; + PGconn *pqc = get_pqdb_connection(); + if(pqc) { + if(ct == TA_DELETE_USER) { + if(is_st) { + snprintf(statement,sizeof(statement),"delete from turnusers_st where name='%s'",user); + } else { + snprintf(statement,sizeof(statement),"delete from turnusers_lt where name='%s' and realm='%s'",user,realm); + } + PGresult *res = PQexec(pqc, statement); + if(res) { + PQclear(res); + } + } + + if(ct == TA_UPDATE_USER) { + if(is_st) { + snprintf(statement,sizeof(statement),"insert into turnusers_st values('%s','%s')",user,passwd); + } else { + snprintf(statement,sizeof(statement),"insert into turnusers_lt (realm,name,hmackey) values('%s','%s','%s')",realm,user,skey); + } + PGresult *res = PQexec(pqc, statement); + if(!res || (PQresultStatus(res) != PGRES_COMMAND_OK)) { + if(res) { + PQclear(res); + } + if(is_st) { + snprintf(statement,sizeof(statement),"update turnusers_st set password='%s' where name='%s'",passwd,user); + } else { + snprintf(statement,sizeof(statement),"update turnusers_lt set hmackey='%s' where name='%s' and realm='%s'",skey,user,realm); + } + res = PQexec(pqc, statement); + if(!res || (PQresultStatus(res) != PGRES_COMMAND_OK)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error inserting/updating user information: %s\n",PQerrorMessage(pqc)); + } + } + if(res) { + PQclear(res); + } + } + } +#endif + } else if(is_mysql_userdb()){ + +#if !defined(TURN_NO_MYSQL) + + if(!is_st) { + must_set_admin_realm(realm); + } + + char statement[LONG_STRING_SIZE]; + MYSQL * myc = get_mydb_connection(); + if(myc) { + if(ct == TA_DELETE_USER) { + if(is_st) { + snprintf(statement,sizeof(statement),"delete from turnusers_st where name='%s'",user); + } else { + snprintf(statement,sizeof(statement),"delete from turnusers_lt where name='%s' and realm='%s'",user,realm); + } + int res = mysql_query(myc, statement); + if(res) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error deleting user key information: %s\n",mysql_error(myc)); + } + } + + if(ct == TA_UPDATE_USER) { + if(is_st) { + snprintf(statement,sizeof(statement),"insert into turnusers_st values('%s','%s')",user,passwd); + } else { + snprintf(statement,sizeof(statement),"insert into turnusers_lt (realm,name,hmackey) values('%s','%s','%s')",realm,user,skey); + } + int res = mysql_query(myc, statement); + if(res) { + if(is_st) { + snprintf(statement,sizeof(statement),"update turnusers_st set password='%s' where name='%s'",passwd,user); + } else { + snprintf(statement,sizeof(statement),"update turnusers_lt set hmackey='%s' where name='%s' and realm='%s'",skey,user,realm); + } + res = mysql_query(myc, statement); + if(res) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error inserting/updating user key information: %s\n",mysql_error(myc)); + } + } + } + } +#endif + } else if(is_redis_userdb()) { + +#if !defined(TURN_NO_HIREDIS) + + if(!is_st) { + must_set_admin_realm(realm); + } + + redisContext *rc = get_redis_connection(); + if(rc) { + char statement[LONG_STRING_SIZE]; + + if(ct == TA_DELETE_USER) { + if(is_st) { + snprintf(statement,sizeof(statement),"del turn/user/%s/password",user); + turnFreeRedisReply(redisCommand(rc, statement)); + } else { + snprintf(statement,sizeof(statement),"del turn/realm/%s/user/%s/key",(char*)realm,user); + turnFreeRedisReply(redisCommand(rc, statement)); + snprintf(statement,sizeof(statement),"del turn/realm/%s/user/%s/password",(char*)realm,user); + turnFreeRedisReply(redisCommand(rc, statement)); + } + } + + if(ct == TA_UPDATE_USER) { + if(is_st) { + snprintf(statement,sizeof(statement),"set turn/user/%s/password %s",user,passwd); + turnFreeRedisReply(redisCommand(rc, statement)); + } else { + snprintf(statement,sizeof(statement),"set turn/realm/%s/user/%s/key %s",(char*)realm,user,skey); + turnFreeRedisReply(redisCommand(rc, statement)); + snprintf(statement,sizeof(statement),"del turn/realm/%s/user/%s/password",(char*)realm,user); + turnFreeRedisReply(redisCommand(rc, statement)); + } + } + + turnFreeRedisReply(redisCommand(rc, "save")); + } +#endif + } else if(!is_st) { + + persistent_users_db_t *pud = get_persistent_users_db(); + char *full_path_to_userdb_file = find_config_file(pud->userdb, 1); + FILE *f = full_path_to_userdb_file ? fopen(full_path_to_userdb_file,"r") : NULL; + int found = 0; + char us[LONG_STRING_SIZE]; + size_t i = 0; + char **content = NULL; + size_t csz = 0; + + STRCPY(us, (char*) user); + strncpy(us + strlen(us), ":", sizeof(us)-1-strlen(us)); + us[sizeof(us)-1]=0; + + if (!f) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "File %s not found, will be created.\n",pud->userdb); + } else { + + char sarg[LONG_STRING_SIZE]; + char sbuf[LONG_STRING_SIZE]; + + for (;;) { + char *s0 = fgets(sbuf, sizeof(sbuf) - 1, f); + if (!s0) + break; + + size_t slen = strlen(s0); + while (slen && (s0[slen - 1] == 10 || s0[slen - 1] == 13)) + s0[--slen] = 0; + + char *s = skip_blanks(s0); + + if (s[0] == '#') + goto add_and_cont; + if (!s[0]) + goto add_and_cont; + + STRCPY(sarg, s); + if (strstr(sarg, us) == sarg) { + if (ct == TA_DELETE_USER) + continue; + + if (found) + continue; + found = 1; + STRCPY(us, (char*) user); + strncpy(us + strlen(us), ":0x", sizeof(us)-1-strlen(us)); + us[sizeof(us)-1]=0; + size_t sz = get_hmackey_size(turn_params.shatype); + for (i = 0; i < sz; i++) { + snprintf( + us + strlen(us), + sizeof(us)-strlen(us), + "%02x", + (unsigned int) key[i]); + } + + s0 = us; + } + + add_and_cont: + content = (char**)realloc(content, sizeof(char*) * (++csz)); + content[csz - 1] = strdup(s0); + } + + fclose(f); + } + + if(!found && (ct == TA_UPDATE_USER)) { + STRCPY(us,(char*)user); + strncpy(us+strlen(us),":0x",sizeof(us)-1-strlen(us)); + us[sizeof(us)-1]=0; + size_t sz = get_hmackey_size(turn_params.shatype); + for(i=0;iuserdb); + + size_t dirsz = strlen(full_path_to_userdb_file)+21; + char *dir = (char*)turn_malloc(dirsz+1); + strncpy(dir,full_path_to_userdb_file,dirsz); + dir[dirsz]=0; + size_t dlen = strlen(dir); + while(dlen) { + if(dir[dlen-1]=='/') + break; + dir[--dlen]=0; + } + strncpy(dir+strlen(dir),".tmp_userdb",dirsz-strlen(dir)); + + f = fopen(dir,"w"); + if(!f) { + perror("file open"); + exit(-1); + } + + for(i=0;itype == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", reply->str); + else if (reply->type != REDIS_REPLY_ARRAY) { + if (reply->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", reply->type); + } else { + size_t i; + for (i = 0; i < reply->elements; ++i) { + add_to_secrets_list(&keys,reply->element[i]->str); + } + } + + for(isz=0;isztype == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", rget->str); + else if (rget->type != REDIS_REPLY_STRING) { + if (rget->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", rget->type); + } else { + add_ip_list_range(rget->str,ret); + } + turnFreeRedisReply(rget); + } + } + + clean_secrets_list(&keys); + + turnFreeRedisReply(reply); + } + } +#endif + + return ret; +} + +static void ip_list_free(ip_range_list_t *l) +{ + if(l) { + size_t i; + for(i=0;iranges_number;++i) { + if(l->ranges && l->ranges[i]) + free(l->ranges[i]); + if(l->encaddrsranges && l->encaddrsranges[i]) + free(l->encaddrsranges[i]); + } + if(l->ranges) + free(l->ranges); + if(l->encaddrsranges) + free(l->encaddrsranges); + free(l); + } +} + +void update_white_and_black_lists(void) +{ + { + ip_range_list_t *wl = get_ip_list("allowed"); + ip_range_list_t *owl = NULL; + ioa_wrlock_whitelist(NULL); + owl = ipwhitelist; + ipwhitelist = wl; + ioa_unlock_whitelist(NULL); + ip_list_free(owl); + } + { + ip_range_list_t *bl = get_ip_list("denied"); + ip_range_list_t *obl = NULL; + ioa_wrlock_blacklist(NULL); + obl = ipblacklist; + ipblacklist = bl; + ioa_unlock_blacklist(NULL); + ip_list_free(obl); + } +} + +/////////////// add ACL record /////////////////// + +int add_ip_list_range(char* range, ip_range_list_t * list) +{ + char* separator = strchr(range, '-'); + + if (separator) { + *separator = '\0'; + } + + ioa_addr min, max; + + if (make_ioa_addr((const u08bits*) range, 0, &min) < 0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong address format: %s\n", range); + return -1; + } + + if (separator) { + if (make_ioa_addr((const u08bits*) separator + 1, 0, &max) < 0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Wrong address format: %s\n", separator + 1); + return -1; + } + } else { + // Doesn't have a '-' character in it, so assume that this is a single address + addr_cpy(&max, &min); + } + + if (separator) + *separator = '-'; + + ++(list->ranges_number); + list->ranges = (char**) realloc(list->ranges, sizeof(char*) * list->ranges_number); + list->ranges[list->ranges_number - 1] = strdup(range); + list->encaddrsranges = (ioa_addr_range**) realloc(list->encaddrsranges, sizeof(ioa_addr_range*) * list->ranges_number); + + list->encaddrsranges[list->ranges_number - 1] = (ioa_addr_range*) turn_malloc(sizeof(ioa_addr_range)); + + ioa_addr_range_set(list->encaddrsranges[list->ranges_number - 1], &min, &max); + + return 0; +} + +/////////// REALM ////////////// + +#if !defined(TURN_NO_HIREDIS) +static int set_redis_realm_opt(char *realm, const char* key, vint *value) +{ + int found = 0; + + redisContext *rc = get_redis_connection(); + + if(rc) { + redisReply *rget = NULL; + + char s[1025]; + + snprintf(s, sizeof(s), "get turn/realm/%s/%s", realm, key); + + rget = (redisReply *) redisCommand(rc, s); + if (rget) { + if (rget->type == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", rget->str); + else if (rget->type != REDIS_REPLY_STRING) { + if (rget->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", rget->type); + } else { + ur_string_map_lock(realms); + *value = atoi(rget->str); + ur_string_map_unlock(realms); + found = 1; + } + turnFreeRedisReply(rget); + } + } + + return found; +} +#endif + +void reread_realms(void) +{ +#if !defined(TURN_NO_PQ) + PGconn * pqc = get_pqdb_connection(); + if(pqc) { + char statement[LONG_STRING_SIZE]; + + { + snprintf(statement,sizeof(statement),"select origin,realm from turn_origin_to_realm"); + PGresult *res = PQexec(pqc, statement); + + if(res && (PQresultStatus(res) == PGRES_TUPLES_OK)) { + + ur_string_map *o_to_realm_new = ur_string_map_create(free); + + int i = 0; + for(i=0;ioptions.perf_options.max_bps = turn_params.max_bps; + ur_string_map_unlock(realms); + + ur_string_map_lock(realms); + rp->options.perf_options.total_quota = turn_params.total_quota; + ur_string_map_unlock(realms); + + ur_string_map_lock(realms); + rp->options.perf_options.user_quota = turn_params.user_quota; + ur_string_map_unlock(realms); + + } + } + + snprintf(statement,sizeof(statement),"select realm,opt,value from turn_realm_option"); + PGresult *res = PQexec(pqc, statement); + + if(res && (PQresultStatus(res) == PGRES_TUPLES_OK)) { + + int i = 0; + for(i=0;ioptions.perf_options.max_bps = (vint)atoi(vval); + else if(!strcmp(oval,"total-quota")) + rp->options.perf_options.total_quota = (vint)atoi(vval); + else if(!strcmp(oval,"user-quota")) + rp->options.perf_options.user_quota = (vint)atoi(vval); + else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unknown realm option: %s\n", oval); + } + } + } + } + + if(res) { + PQclear(res); + } + } + } +#endif + +#if !defined(TURN_NO_MYSQL) + MYSQL * myc = get_mydb_connection(); + if(myc) { + char statement[LONG_STRING_SIZE]; + { + snprintf(statement,sizeof(statement),"select origin,realm from turn_origin_to_realm"); + int res = mysql_query(myc, statement); + if(res == 0) { + MYSQL_RES *mres = mysql_store_result(myc); + if(mres && mysql_field_count(myc)==2) { + + ur_string_map *o_to_realm_new = ur_string_map_create(free); + + for(;;) { + MYSQL_ROW row = mysql_fetch_row(mres); + if(!row) { + break; + } else { + if(row[0] && row[1]) { + unsigned long *lengths = mysql_fetch_lengths(mres); + if(lengths) { + size_t sz = lengths[0]; + char oval[513]; + ns_bcopy(row[0],oval,sz); + oval[sz]=0; + char *rval=strdup(row[1]); + get_realm(rval); + ur_string_map_value_type value = strdup(rval); + ur_string_map_put(o_to_realm_new, (const ur_string_map_key_type) oval, value); + } + } + } + } + + TURN_MUTEX_LOCK(&o_to_realm_mutex); + ur_string_map_free(&o_to_realm); + o_to_realm = o_to_realm_new; + TURN_MUTEX_UNLOCK(&o_to_realm_mutex); + } + + if(mres) + mysql_free_result(mres); + } + } + { + size_t i = 0; + size_t rlsz = 0; + + ur_string_map_lock(realms); + rlsz = realms_list.sz; + ur_string_map_unlock(realms); + + for (i = 0; ioptions.perf_options.max_bps = turn_params.max_bps; + ur_string_map_unlock(realms); + + ur_string_map_lock(realms); + rp->options.perf_options.total_quota = turn_params.total_quota; + ur_string_map_unlock(realms); + + ur_string_map_lock(realms); + rp->options.perf_options.user_quota = turn_params.user_quota; + ur_string_map_unlock(realms); + + } + } + + snprintf(statement,sizeof(statement),"select realm,opt,value from turn_realm_option"); + int res = mysql_query(myc, statement); + if(res == 0) { + MYSQL_RES *mres = mysql_store_result(myc); + if(mres && mysql_field_count(myc)==3) { + + for(;;) { + MYSQL_ROW row = mysql_fetch_row(mres); + if(!row) { + break; + } else { + if(row[0] && row[1] && row[2]) { + unsigned long *lengths = mysql_fetch_lengths(mres); + if(lengths) { + char rval[513]; + size_t sz = lengths[0]; + ns_bcopy(row[0],rval,sz); + rval[sz]=0; + char oval[513]; + sz = lengths[1]; + ns_bcopy(row[1],oval,sz); + oval[sz]=0; + char vval[513]; + sz = lengths[2]; + ns_bcopy(row[2],vval,sz); + vval[sz]=0; + realm_params_t* rp = get_realm(rval); + if(!strcmp(oval,"max-bps")) + rp->options.perf_options.max_bps = (vint)atoi(vval); + else if(!strcmp(oval,"total-quota")) + rp->options.perf_options.total_quota = (vint)atoi(vval); + else if(!strcmp(oval,"user-quota")) + rp->options.perf_options.user_quota = (vint)atoi(vval); + else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unknown realm option: %s\n", oval); + } + } + } + } + } + } + + if(mres) + mysql_free_result(mres); + } + } +#endif + +#if !defined(TURN_NO_HIREDIS) + if (is_redis_userdb()) { + redisContext *rc = get_redis_connection(); + if (rc) { + + redisReply *reply = (redisReply*) redisCommand(rc, "keys turn/origin/*"); + if (reply) { + + ur_string_map *o_to_realm_new = ur_string_map_create(free); + + secrets_list_t keys; + + init_secrets_list(&keys); + + size_t isz = 0; + + char s[1025]; + + if (reply->type == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", reply->str); + else if (reply->type != REDIS_REPLY_ARRAY) { + if (reply->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", reply->type); + } else { + size_t i; + for (i = 0; i < reply->elements; ++i) { + add_to_secrets_list(&keys, reply->element[i]->str); + } + } + + size_t offset = strlen("turn/origin/"); + + for (isz = 0; isz < keys.sz; ++isz) { + char *origin = keys.secrets[isz] + offset; + snprintf(s, sizeof(s), "get %s", keys.secrets[isz]); + redisReply *rget = (redisReply *) redisCommand(rc, s); + if (rget) { + if (rget->type == REDIS_REPLY_ERROR) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Error: %s\n", rget->str); + else if (rget->type != REDIS_REPLY_STRING) { + if (rget->type != REDIS_REPLY_NIL) + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unexpected type: %d\n", rget->type); + } else { + get_realm(rget->str); + ur_string_map_value_type value = strdup(rget->str); + ur_string_map_put(o_to_realm_new, (const ur_string_map_key_type) origin, value); + } + turnFreeRedisReply(rget); + } + } + + clean_secrets_list(&keys); + + TURN_MUTEX_LOCK(&o_to_realm_mutex); + ur_string_map_free(&o_to_realm); + o_to_realm = o_to_realm_new; + TURN_MUTEX_UNLOCK(&o_to_realm_mutex); + + turnFreeRedisReply(reply); + } + + { + size_t i = 0; + size_t rlsz = 0; + + ur_string_map_lock(realms); + rlsz = realms_list.sz; + ur_string_map_unlock(realms); + + for (i = 0; ioptions.perf_options.max_bps))) { + ur_string_map_lock(realms); + rp->options.perf_options.max_bps = turn_params.max_bps; + ur_string_map_unlock(realms); + } + if(!set_redis_realm_opt(realm,"total-quota",&(rp->options.perf_options.total_quota))) { + ur_string_map_lock(realms); + rp->options.perf_options.total_quota = turn_params.total_quota; + ur_string_map_unlock(realms); + } + if(!set_redis_realm_opt(realm,"user-quota",&(rp->options.perf_options.user_quota))) { + ur_string_map_lock(realms); + rp->options.perf_options.user_quota = turn_params.user_quota; + ur_string_map_unlock(realms); + } + } + } + } + } +#endif +} + +/////////////////////////////// diff --git a/src/apps/relay/userdb.h b/src/apps/relay/userdb.h new file mode 100644 index 0000000..758166c --- /dev/null +++ b/src/apps/relay/userdb.h @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __USERDB__ +#define __USERDB__ + +#include +#include + +#if !defined(TURN_NO_HIREDIS) +#include "hiredis_libevent2.h" +#endif + +#include "ns_turn_utils.h" +#include "ns_turn_maps.h" +#include "ns_turn_server.h" + +#include "apputils.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//////////// Defines ////////////////////////////// + +#define DEFAULT_USERDB_FILE "turnuserdb.conf" + +#define AUTH_SECRET_SIZE (512) + +//////////// REALM ////////////// + +struct _realm_status_t; +typedef struct _realm_status_t realm_status_t; + +struct _realm_params_t; +typedef struct _realm_params_t realm_params_t; + +struct _realm_status_t { + + vint total_current_allocs; + ur_string_map *alloc_counters; + +}; + +struct _realm_params_t { + + int is_default_realm; + + realm_options_t options; + + realm_status_t status; + +}; + +//////////// USER DB ////////////////////////////// + +struct auth_message { + turnserver_id id; + turn_credential_type ct; + u08bits username[STUN_MAX_USERNAME_SIZE + 1]; + u08bits realm[STUN_MAX_REALM_SIZE + 1]; + hmackey_t key; + st_password_t pwd; + get_username_resume_cb resume_func; + ioa_net_data in_buffer; + u64bits ctxkey; + int success; +}; + +enum _TURN_USERDB_TYPE { + TURN_USERDB_TYPE_FILE=0 +#if !defined(TURN_NO_PQ) + ,TURN_USERDB_TYPE_PQ +#endif +#if !defined(TURN_NO_MYSQL) + ,TURN_USERDB_TYPE_MYSQL +#endif +#if !defined(TURN_NO_HIREDIS) + ,TURN_USERDB_TYPE_REDIS +#endif +}; + +typedef enum _TURN_USERDB_TYPE TURN_USERDB_TYPE; + +enum _TURNADMIN_COMMAND_TYPE { + TA_COMMAND_UNKNOWN, + TA_PRINT_KEY, + TA_UPDATE_USER, + TA_DELETE_USER, + TA_LIST_USERS, + TA_SET_SECRET, + TA_SHOW_SECRET, + TA_DEL_SECRET, + TA_ADD_ORIGIN, + TA_DEL_ORIGIN, + TA_LIST_ORIGINS, + TA_SET_REALM_OPTION, + TA_LIST_REALM_OPTIONS +}; + +typedef enum _TURNADMIN_COMMAND_TYPE TURNADMIN_COMMAND_TYPE; + +/////////// SHARED SECRETS ////////////////// + +struct _secrets_list { + char **secrets; + size_t sz; +}; +typedef struct _secrets_list secrets_list_t; + +/////////// USERS PARAM ///////////////////// + +#define TURN_LONG_STRING_SIZE (1025) + +typedef struct _ram_users_db_t { + size_t users_number; + ur_string_map *static_accounts; + ur_string_map *dynamic_accounts; + secrets_list_t static_auth_secrets; +} ram_users_db_t; + +typedef struct _persistent_users_db_t { + + char userdb[TURN_LONG_STRING_SIZE]; + void *connection; + +} persistent_users_db_t; + +typedef struct _default_users_db_t +{ + TURN_USERDB_TYPE userdb_type; + + persistent_users_db_t persistent_users_db; + + ram_users_db_t ram_db; + +} default_users_db_t; + +///////////////////////////////////////////// + +realm_params_t* get_realm(char* name); +void set_default_realm_name(char *realm); +int change_total_quota(char *realm, int value); +int change_user_quota(char *realm, int value); + +///////////////////////////////////////////// + +void init_secrets_list(secrets_list_t *sl); +void init_dynamic_ip_lists(void); +void update_white_and_black_lists(void); +void clean_secrets_list(secrets_list_t *sl); +size_t get_secrets_list_size(secrets_list_t *sl); +const char* get_secrets_list_elem(secrets_list_t *sl, size_t i); +void add_to_secrets_list(secrets_list_t *sl, const char* elem); + +/////////// USER DB CHECK ////////////////// + +int get_user_key(u08bits *uname, u08bits *realm, hmackey_t key, ioa_network_buffer_handle nbh); +int get_user_pwd(u08bits *uname, st_password_t pwd); +u08bits *start_user_check(turnserver_id id, turn_credential_type ct, u08bits *uname, u08bits *realm, get_username_resume_cb resume, ioa_net_data *in_buffer, u64bits ctxkey, int *postpone_reply); +int check_new_allocation_quota(u08bits *username, u08bits *realm); +void release_allocation_quota(u08bits *username, u08bits *realm); + +/////////// Handle user DB ///////////////// + +void read_userdb_file(int to_print); +void auth_ping( +#if !defined(TURN_NO_HIREDIS) + redis_context_handle rch +#else + void +#endif +); +void reread_realms(void); +int add_user_account(char *user, int dynamic); +int adminuser(u08bits *user, u08bits *realm, u08bits *pwd, u08bits *secret, u08bits *origin, TURNADMIN_COMMAND_TYPE ct, int is_st, perf_options_t* po); + +int add_ip_list_range(char* range, ip_range_list_t * list); + +///////////// Redis ////////////////////// + +#if !defined(TURN_NO_HIREDIS) +#include "hiredis_libevent2.h" +redis_context_handle get_redis_async_connection(struct event_base *base, const char* connection_string, int delete_keys); +#endif + +//////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif +/// __USERDB__/// + diff --git a/src/apps/rfc5769/rfc5769check.c b/src/apps/rfc5769/rfc5769check.c new file mode 100644 index 0000000..a8dc6cf --- /dev/null +++ b/src/apps/rfc5769/rfc5769check.c @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include "ns_turn_utils.h" +#include "apputils.h" +#include "stun_buffer.h" + +////////////////////////////////////////////////// + +static SHATYPE shatype = SHATYPE_SHA1; + +int main(int argc, const char **argv) +{ + int res = -1; + + UNUSED_ARG(argc); + UNUSED_ARG(argv); + + set_logfile("stdout"); + set_system_parameters(0); + + { + const unsigned char reqstc[] = + "\x00\x01\x00\x58" + "\x21\x12\xa4\x42" + "\xb7\xe7\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae" + "\x80\x22\x00\x10" + "STUN test client" + "\x00\x24\x00\x04" + "\x6e\x00\x01\xff" + "\x80\x29\x00\x08" + "\x93\x2f\xf9\xb1\x51\x26\x3b\x36" + "\x00\x06\x00\x09" + "\x65\x76\x74\x6a\x3a\x68\x36\x76\x59\x20\x20\x20" + "\x00\x08\x00\x14" + "\x9a\xea\xa7\x0c\xbf\xd8\xcb\x56\x78\x1e\xf2\xb5" + "\xb2\xd3\xf2\x49\xc1\xb5\x71\xa2" + "\x80\x28\x00\x04" + "\xe5\x7a\x3b\xcf"; + + u08bits buf[sizeof(reqstc)]; + memcpy(buf, reqstc, sizeof(reqstc)); + + {//fingerprintfs etc + + res = stun_is_command_message_full_check_str(buf, sizeof(reqstc) - 1, 1, NULL); + printf("RFC 5769 message fingerprint test(0) result: "); + + if (res) { + printf("success\n"); + } else if (res == 0) { + printf("failure on fingerprint(0) check\n"); + exit(-1); + } + } + + {//short-term credentials + u08bits uname[33]; + u08bits realm[33]; + u08bits upwd[33]; + strcpy((char*) upwd, "VOkJxbRl1RmTxUk/WvJxBt"); + + res = stun_check_message_integrity_str(TURN_CREDENTIALS_SHORT_TERM, buf, sizeof(reqstc) - 1, uname, realm, upwd, shatype); + printf("RFC 5769 simple request short-term credentials and integrity test result: "); + + if (res > 0) { + printf("success\n"); + } else if (res == 0) { + printf("failure on integrity check\n"); + exit(-1); + } else { + printf("failure on message structure check\n"); + exit(-1); + } + } + + {//negative fingerprint + buf[27] = 23; + + res = stun_is_command_message_full_check_str(buf, sizeof(reqstc) - 1, 1, NULL); + printf("RFC 5769 NEGATIVE fingerprint test(0) result: "); + + if (!res) { + printf("success\n"); + } else if (res == 0) { + printf("failure on NEGATIVE fingerprint check\n"); + exit(-1); + } + } + } + + { + const unsigned char reqltc[] = "\x00\x01\x00\x60" + "\x21\x12\xa4\x42" + "\x78\xad\x34\x33\xc6\xad\x72\xc0\x29\xda\x41\x2e" + "\x00\x06\x00\x12" + "\xe3\x83\x9e\xe3\x83\x88\xe3\x83\xaa\xe3\x83\x83" + "\xe3\x82\xaf\xe3\x82\xb9\x00\x00" + "\x00\x15\x00\x1c" + "\x66\x2f\x2f\x34\x39\x39\x6b\x39\x35\x34\x64\x36" + "\x4f\x4c\x33\x34\x6f\x4c\x39\x46\x53\x54\x76\x79" + "\x36\x34\x73\x41" + "\x00\x14\x00\x0b" + "\x65\x78\x61\x6d\x70\x6c\x65\x2e\x6f\x72\x67\x00" + "\x00\x08\x00\x14" + "\xf6\x70\x24\x65\x6d\xd6\x4a\x3e\x02\xb8\xe0\x71" + "\x2e\x85\xc9\xa2\x8c\xa8\x96\x66"; + + u08bits user[] = "\xe3\x83\x9e\xe3\x83\x88\xe3\x83\xaa\xe3\x83\x83" + "\xe3\x82\xaf\xe3\x82\xb9"; + + u08bits realm[33]; + u08bits nonce[29]; + u08bits upwd[33]; + + u08bits buf[sizeof(reqltc)]; + memcpy(buf, reqltc, sizeof(reqltc)); + + u08bits uname[sizeof(user)]; + memcpy(uname, user, sizeof(user)); + + strcpy((char*) realm, "example.org"); + strcpy((char*) upwd, "TheMatrIX"); + strcpy((char*)nonce,"f//499k954d6OL34oL9FSTvy64sA"); + + res = stun_check_message_integrity_str(TURN_CREDENTIALS_LONG_TERM, buf, sizeof(reqltc) - 1, uname, realm, + upwd, shatype); + + printf("RFC 5769 message structure, long-term credentials and integrity test result: "); + + if (res > 0) { + printf("success\n"); + } else if (res == 0) { + printf("failure on integrity check\n"); + exit(-1); + } else { + printf("failure on message structure check\n"); + exit(-1); + } + + { //encoding test + printf("RFC 5769 message encoding test result: "); + size_t len = 0; + u16bits message_type = STUN_METHOD_BINDING; + stun_tid tid; + u16bits *buf16 = (u16bits*)buf; + u32bits *buf32 = (u32bits*)buf; + memcpy(tid.tsx_id,"\x78\xad\x34\x33\xc6\xad\x72\xc0\x29\xda\x41\x2e",12); + stun_init_buffer_str(buf,&len); + message_type &= (u16bits)(0x3FFF); + buf16[0]=nswap16(message_type); + buf16[1]=0; + buf32[1]=nswap32(STUN_MAGIC_COOKIE); + stun_tid_message_cpy(buf, &tid); + stun_attr_add_integrity_by_user_str(buf, &len, uname, realm, upwd, nonce, shatype); + if(len != (sizeof(reqltc)-1)) { + printf("failure: length %d, must be %d\n",(int)len,(int)(sizeof(reqltc)-1)); + exit(-1); + } + if(memcmp(buf,reqltc,len)) { + printf("failure: wrong message content\n"); + { + int lines = 29; + int line = 0; + int col = 0; + int cols = 4; + for(line = 0;line 0) { + printf("success\n"); + } else if (res == 0) { + printf("failure on integrity check\n"); + exit(-1); + } else { + printf("failure on message structure check\n"); + exit(-1); + } + } + + {//negative fingerprint + buf[27] = 23; + + res = stun_is_command_message_full_check_str(buf, sizeof(respv4) - 1, 1, NULL); + printf("RFC 5769 NEGATIVE fingerprint test(1) result: "); + + if (!res) { + printf("success\n"); + } else if (res == 0) { + printf("failure on NEGATIVE fingerprint check\n"); + exit(-1); + } + } + + {//IPv4 addr + ioa_addr addr4; + ioa_addr addr4_test; + + printf("RFC 5769 IPv4 encoding result: "); + + res = stun_attr_get_first_addr_str(buf, sizeof(respv4)-1, STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS, &addr4, NULL); + if(res < 0) { + printf("failure on message structure check\n"); + exit(-1); + } + + make_ioa_addr((const u08bits*)"192.0.2.1", 32853, &addr4_test); + if(addr_eq(&addr4,&addr4_test)) { + printf("success\n"); + } else { + printf("failure on IPv4 deconding check\n"); + exit(-1); + } + } + } + + { + const unsigned char respv6[] = "\x01\x01\x00\x48" + "\x21\x12\xa4\x42" + "\xb7\xe7\xa7\x01\xbc\x34\xd6\x86\xfa\x87\xdf\xae" + "\x80\x22\x00\x0b" + "\x74\x65\x73\x74\x20\x76\x65\x63\x74\x6f\x72\x20" + "\x00\x20\x00\x14" + "\x00\x02\xa1\x47" + "\x01\x13\xa9\xfa\xa5\xd3\xf1\x79" + "\xbc\x25\xf4\xb5\xbe\xd2\xb9\xd9" + "\x00\x08\x00\x14" + "\xa3\x82\x95\x4e\x4b\xe6\x7b\xf1\x17\x84\xc9\x7c" + "\x82\x92\xc2\x75\xbf\xe3\xed\x41" + "\x80\x28\x00\x04" + "\xc8\xfb\x0b\x4c"; + + u08bits buf[sizeof(respv6)]; + + { //decoding test + memcpy(buf, respv6, sizeof(respv6)); + + res = stun_is_command_message_full_check_str(buf, sizeof(respv6) - 1, 1, NULL); + printf("RFC 5769 message fingerprint test(2) result: "); + + if (res) { + printf("success\n"); + } else if (res == 0) { + printf("failure on fingerprint(2) check\n"); + exit(-1); + } + } + + {//short-term credentials test + u08bits uname[33]; + u08bits realm[33]; + u08bits upwd[33]; + strcpy((char*) upwd, "VOkJxbRl1RmTxUk/WvJxBt"); + + res = stun_check_message_integrity_str(TURN_CREDENTIALS_SHORT_TERM, buf, sizeof(respv6) - 1, uname, realm, upwd, shatype); + printf("RFC 5769 IPv6 response short-term credentials and integrity test result: "); + + if (res > 0) { + printf("success\n"); + } else if (res == 0) { + printf("failure on integrity check\n"); + exit(-1); + } else { + printf("failure on message structure check\n"); + exit(-1); + } + } + + {//negative decoding test + buf[27] = 23; + + res = stun_is_command_message_full_check_str(buf, sizeof(respv6) - 1, 1, NULL); + printf("RFC 5769 NEGATIVE fingerprint test(2) result: "); + + if (!res) { + printf("success\n"); + } else if (res == 0) { + printf("failure on NEGATIVE fingerprint check\n"); + exit(-1); + } + } + + {//IPv6 deconding test + ioa_addr addr6; + ioa_addr addr6_test; + + printf("RFC 5769 IPv6 encoding result: "); + + res = stun_attr_get_first_addr_str(buf, sizeof(respv6) - 1, + STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS, &addr6, NULL); + if (res < 0) { + printf("failure on message structure check\n"); + exit(-1); + } + + make_ioa_addr((const u08bits*) "2001:db8:1234:5678:11:2233:4455:6677", 32853, &addr6_test); + if (addr_eq(&addr6, &addr6_test)) { + printf("success\n"); + } else { + printf("failure on IPv6 deconding check\n"); + exit(-1); + } + } + } + + return 0; +} diff --git a/src/apps/stunclient/stunclient.c b/src/apps/stunclient/stunclient.c new file mode 100644 index 0000000..d7ff4c8 --- /dev/null +++ b/src/apps/stunclient/stunclient.c @@ -0,0 +1,466 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include "ns_turn_utils.h" +#include "apputils.h" +#include "stun_buffer.h" + +#ifdef __cplusplus +#include "TurnMsgLib.h" +#endif + +//////////////////////////////////////////////////// + +static int udp_fd = -1; +static ioa_addr real_local_addr; +static int counter = 0; + +#ifdef __cplusplus + +static int run_stunclient(const char* rip, int rport, int *port, int *rfc5780, int response_port, int change_ip, int change_port, int padding) +{ + + ioa_addr remote_addr; + int new_udp_fd = -1; + + memset((void *) &remote_addr, 0, sizeof(ioa_addr)); + if (make_ioa_addr((const u08bits*) rip, rport, &remote_addr) < 0) + err(-1, NULL); + + if (udp_fd < 0) { + udp_fd = socket(remote_addr.ss.sa_family, SOCK_DGRAM, 0); + if (udp_fd < 0) + err(-1, NULL); + + if (!addr_any(&real_local_addr)) { + if (addr_bind(udp_fd, &real_local_addr,0) < 0) + err(-1, NULL); + } + } + + if (response_port >= 0) { + + new_udp_fd = socket(remote_addr.ss.sa_family, SOCK_DGRAM, 0); + if (new_udp_fd < 0) + err(-1, NULL); + + addr_set_port(&real_local_addr, response_port); + + if (addr_bind(new_udp_fd, &real_local_addr, 0) < 0) + err(-1, NULL); + } + + turn::StunMsgRequest req(STUN_METHOD_BINDING); + + req.constructBindingRequest(); + + if (response_port >= 0) { + turn::StunAttrResponsePort rpa; + rpa.setResponsePort((u16bits)response_port); + try { + req.addAttr(rpa); + } catch(turn::WrongStunAttrFormatException &ex1) { + printf("Wrong rp attr format\n"); + exit(-1); + } catch(turn::WrongStunBufferFormatException &ex2) { + printf("Wrong stun buffer format (1)\n"); + exit(-1); + } catch(...) { + printf("Wrong something (1)\n"); + exit(-1); + } + } + if (change_ip || change_port) { + turn::StunAttrChangeRequest cra; + cra.setChangeIp(change_ip); + cra.setChangePort(change_port); + try { + req.addAttr(cra); + } catch(turn::WrongStunAttrFormatException &ex1) { + printf("Wrong cr attr format\n"); + exit(-1); + } catch(turn::WrongStunBufferFormatException &ex2) { + printf("Wrong stun buffer format (2)\n"); + exit(-1); + } catch(...) { + printf("Wrong something (2)\n"); + exit(-1); + } + } + if (padding) { + turn::StunAttrPadding pa; + pa.setPadding(1500); + try { + req.addAttr(pa); + } catch(turn::WrongStunAttrFormatException &ex1) { + printf("Wrong p attr format\n"); + exit(-1); + } catch(turn::WrongStunBufferFormatException &ex2) { + printf("Wrong stun buffer format (3)\n"); + exit(-1); + } catch(...) { + printf("Wrong something (3)\n"); + exit(-1); + } + } + + { + int len = 0; + int slen = get_ioa_addr_len(&remote_addr); + + do { + len = sendto(udp_fd, req.getRawBuffer(), req.getSize(), 0, (struct sockaddr*) &remote_addr, (socklen_t) slen); + } while (len < 0 && ((errno == EINTR) || (errno == ENOBUFS) || (errno == EAGAIN))); + + if (len < 0) + err(-1, NULL); + + } + + if (addr_get_from_sock(udp_fd, &real_local_addr) < 0) { + printf("%s: Cannot get address from local socket\n", __FUNCTION__); + } else { + *port = addr_get_port(&real_local_addr); + } + + { + if(new_udp_fd >= 0) { + close(udp_fd); + udp_fd = new_udp_fd; + new_udp_fd = -1; + } + } + + { + int len = 0; + stun_buffer buf; + u08bits *ptr = buf.buf; + int recvd = 0; + const int to_recv = sizeof(buf.buf); + + do { + len = recv(udp_fd, ptr, to_recv - recvd, 0); + if (len > 0) { + recvd += len; + ptr += len; + break; + } + } while (len < 0 && (errno == EINTR)); + + if (recvd > 0) + len = recvd; + buf.len = len; + + try { + turn::StunMsgResponse res(buf.buf, sizeof(buf.buf), (size_t)buf.len, true); + + if (res.isCommand()) { + + if(res.isSuccess()) { + + if (res.isBindingResponse()) { + + ioa_addr reflexive_addr; + addr_set_any(&reflexive_addr); + turn::StunAttrIterator iter(res,STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS); + if (!iter.eof()) { + + turn::StunAttrAddr addr(iter); + addr.getAddr(reflexive_addr); + + turn::StunAttrIterator iter1(res,STUN_ATTRIBUTE_OTHER_ADDRESS); + if (!iter1.eof()) { + *rfc5780 = 1; + printf("\n========================================\n"); + printf("RFC 5780 response %d\n",++counter); + ioa_addr other_addr; + turn::StunAttrAddr addr1(iter1); + addr1.getAddr(other_addr); + turn::StunAttrIterator iter2(res,STUN_ATTRIBUTE_RESPONSE_ORIGIN); + if (!iter2.eof()) { + ioa_addr response_origin; + turn::StunAttrAddr addr2(iter2); + addr2.getAddr(response_origin); + addr_debug_print(1, &response_origin, "Response origin: "); + } + addr_debug_print(1, &other_addr, "Other addr: "); + } + addr_debug_print(1, &reflexive_addr, "UDP reflexive addr"); + + } else { + printf("Cannot read the response\n"); + } + } else { + printf("Wrong type of response\n"); + } + } else { + int err_code = res.getError(); + std::string reason = res.getReason(); + + printf("The response is an error %d (%s)\n", err_code, reason.c_str()); + } + } else { + printf("The response is not a reponse message\n"); + } + } catch(...) { + printf("The response is not a well formed STUN message\n"); + } + } + + return 0; +} + +#else + +static int run_stunclient(const char* rip, int rport, int *port, int *rfc5780, int response_port, int change_ip, int change_port, int padding) +{ + + ioa_addr remote_addr; + int new_udp_fd = -1; + stun_buffer buf; + + ns_bzero(&remote_addr, sizeof(remote_addr)); + if (make_ioa_addr((const u08bits*) rip, rport, &remote_addr) < 0) + err(-1, NULL); + + if (udp_fd < 0) { + udp_fd = socket(remote_addr.ss.sa_family, SOCK_DGRAM, 0); + if (udp_fd < 0) + err(-1, NULL); + + if (!addr_any(&real_local_addr)) { + if (addr_bind(udp_fd, &real_local_addr,0) < 0) + err(-1, NULL); + } + } + + if (response_port >= 0) { + + new_udp_fd = socket(remote_addr.ss.sa_family, SOCK_DGRAM, 0); + if (new_udp_fd < 0) + err(-1, NULL); + + addr_set_port(&real_local_addr, response_port); + + if (addr_bind(new_udp_fd, &real_local_addr,0) < 0) + err(-1, NULL); + } + + stun_prepare_binding_request(&buf); + + if (response_port >= 0) { + stun_attr_add_response_port_str((u08bits*) (buf.buf), (size_t*) &(buf.len), (u16bits) response_port); + } + if (change_ip || change_port) { + stun_attr_add_change_request_str((u08bits*) buf.buf, (size_t*) &(buf.len), change_ip, change_port); + } + if (padding) { + if(stun_attr_add_padding_str((u08bits*) buf.buf, (size_t*) &(buf.len), 1500)<0) { + printf("%s: ERROR: Cannot add padding\n",__FUNCTION__); + } + } + + { + int len = 0; + int slen = get_ioa_addr_len(&remote_addr); + + do { + len = sendto(udp_fd, buf.buf, buf.len, 0, (struct sockaddr*) &remote_addr, (socklen_t) slen); + } while (len < 0 && ((errno == EINTR) || (errno == ENOBUFS) || (errno == EAGAIN))); + + if (len < 0) + err(-1, NULL); + + } + + if (addr_get_from_sock(udp_fd, &real_local_addr) < 0) { + printf("%s: Cannot get address from local socket\n", __FUNCTION__); + } else { + *port = addr_get_port(&real_local_addr); + } + + { + if(new_udp_fd >= 0) { + socket_closesocket(udp_fd); + udp_fd = new_udp_fd; + new_udp_fd = -1; + } + } + + { + int len = 0; + u08bits *ptr = buf.buf; + int recvd = 0; + const int to_recv = sizeof(buf.buf); + + do { + len = recv(udp_fd, ptr, to_recv - recvd, 0); + if (len > 0) { + recvd += len; + ptr += len; + break; + } + } while (len < 0 && ((errno == EINTR) || (errno == EAGAIN))); + + if (recvd > 0) + len = recvd; + buf.len = len; + + if (stun_is_command_message(&buf)) { + + if (stun_is_response(&buf)) { + + if (stun_is_success_response(&buf)) { + + if (stun_is_binding_response(&buf)) { + + ioa_addr reflexive_addr; + addr_set_any(&reflexive_addr); + if (stun_attr_get_first_addr(&buf, STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS, &reflexive_addr, NULL) >= 0) { + + stun_attr_ref sar = stun_attr_get_first_by_type_str(buf.buf, buf.len, STUN_ATTRIBUTE_OTHER_ADDRESS); + if (sar) { + *rfc5780 = 1; + printf("\n========================================\n"); + printf("RFC 5780 response %d\n",++counter); + ioa_addr other_addr; + stun_attr_get_addr_str((u08bits *) buf.buf, (size_t) buf.len, sar, &other_addr, NULL); + sar = stun_attr_get_first_by_type_str(buf.buf, buf.len, STUN_ATTRIBUTE_RESPONSE_ORIGIN); + if (sar) { + ioa_addr response_origin; + stun_attr_get_addr_str((u08bits *) buf.buf, (size_t) buf.len, sar, &response_origin, NULL); + addr_debug_print(1, &response_origin, "Response origin: "); + } + addr_debug_print(1, &other_addr, "Other addr: "); + } + addr_debug_print(1, &reflexive_addr, "UDP reflexive addr"); + + } else { + printf("Cannot read the response\n"); + } + } else { + printf("Wrong type of response\n"); + } + } else { + int err_code = 0; + u08bits err_msg[1025] = "\0"; + size_t err_msg_size = sizeof(err_msg); + if (stun_is_error_response(&buf, &err_code, err_msg, err_msg_size)) { + printf("The response is an error %d (%s)\n", err_code, (char*) err_msg); + } else { + printf("The response is an unrecognized error\n"); + } + } + } else { + printf("The response is not a reponse message\n"); + } + } else { + printf("The response is not a STUN message\n"); + } + } + + return 0; +} +#endif + +//////////////// local definitions ///////////////// + +static char Usage[] = + "Usage: stunclient [options] address\n" + "Options:\n" + " -p STUN server port (Default: 3478)\n" + " -L Local address to use (optional)\n" + " -f Force RFC 5780 processing\n"; + +////////////////////////////////////////////////// + +int main(int argc, char **argv) +{ + int port = DEFAULT_STUN_PORT; + char local_addr[256]="\0"; + int c=0; + int forceRfc5780 = 0; + + set_logfile("stdout"); + set_system_parameters(0); + + ns_bzero(local_addr, sizeof(local_addr)); + + while ((c = getopt(argc, argv, "p:L:f")) != -1) { + switch(c) { + case 'f': + forceRfc5780 = 1; + break; + case 'p': + port = atoi(optarg); + break; + case 'L': + STRCPY(local_addr, optarg); + break; + default: + fprintf(stderr,"%s\n", Usage); + exit(1); + } + } + + if(optind>=argc) { + fprintf(stderr, "%s\n", Usage); + exit(-1); + } + + addr_set_any(&real_local_addr); + + if(local_addr[0]) { + if(make_ioa_addr((const u08bits*)local_addr, 0, &real_local_addr)<0) { + err(-1,NULL); + } + } + + int local_port = -1; + int rfc5780 = 0; + + run_stunclient(argv[optind], port, &local_port, &rfc5780,-1,0,0,0); + + if(rfc5780 || forceRfc5780) { + run_stunclient(argv[optind], port, &local_port, &rfc5780,local_port+1,1,1,0); + run_stunclient(argv[optind], port, &local_port, &rfc5780,-1,1,1,1); + } + + socket_closesocket(udp_fd); + + return 0; +} diff --git a/src/apps/uclient/mainuclient.c b/src/apps/uclient/mainuclient.c new file mode 100644 index 0000000..f7e8fb6 --- /dev/null +++ b/src/apps/uclient/mainuclient.c @@ -0,0 +1,532 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "apputils.h" +#include "uclient.h" +#include "ns_turn_utils.h" +#include "apputils.h" +#include "session.h" +#include "stun_buffer.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +/////////////// extern definitions ///////////////////// + +int clmessage_length=100; +int do_not_use_channel=0; +int c2c=0; +int clnet_verbose=TURN_VERBOSE_NONE; +int use_tcp=0; +int use_secure=0; +int use_short_term=0; +int hang_on=0; +ioa_addr peer_addr; +int no_rtcp = 0; +int default_address_family = STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_DEFAULT; +int dont_fragment = 0; +u08bits g_uname[STUN_MAX_USERNAME_SIZE+1]; +st_password_t g_upwd; +char g_auth_secret[1025]="\0"; +int g_use_auth_secret_with_timestamp = 0; +int use_fingerprints = 1; + +static char ca_cert_file[1025]=""; +static char cipher_suite[1025]=""; +char cert_file[1025]=""; +char pkey_file[1025]=""; +SSL_CTX *root_tls_ctx[32]; +int root_tls_ctx_num = 0; + +u08bits relay_transport = STUN_ATTRIBUTE_TRANSPORT_UDP_VALUE; +unsigned char client_ifname[1025] = ""; +int passive_tcp = 0; +int mandatory_channel_padding = 0; +int negative_test = 0; +int negative_protocol_test = 0; +int dos = 0; + +SHATYPE shatype = SHATYPE_SHA1; + +int mobility = 0; + +int no_permissions = 0; + +int extra_requests = 0; + +char origin[STUN_MAX_ORIGIN_SIZE+1] = "\0"; + +//////////////// local definitions ///////////////// + +static char Usage[] = + "Usage: uclient [flags] [options] turn-server-ip-address\n" + "Flags:\n" + " -t TCP (default - UDP).\n" + " -T TCP relay transport (default - UDP). Implies options -t, -y, -c, and ignores \n" + " options -s, -e, -r and -g.\n" + " -P Passive TCP (RFC6062 with active peer). Implies -T.\n" + " -S Secure connection: TLS for TCP, DTLS for UDP.\n" + " -U Secure connection with eNULL cipher.\n" + " -v Verbose.\n" + " -s Use send method.\n" + " -y Use client-to-client connections.\n" + " -h Hang on indefinitely after the last sent packet.\n" + " -c No rtcp connections.\n" + " -x IPv6 relay address requested.\n" + " -X IPv4 relay address explicitly requested.\n" + " -g Include DONT_FRAGMENT option.\n" + " -A Use short-term credentials mechanism. By default, the program uses\n" + " the long-term credentials mechanism if authentication is required.\n" + " -D Mandatory channel padding (like in pjnath).\n" + " -N Negative tests (some limited cases only).\n" + " -R Negative protocol tests.\n" + " -O DOS attack mode (quick connect and exit).\n" + " -H SHA256 digest function for message integrity calculation.\n" + " Without this option, by default, SHA1 is used.\n" + " -M ICE Mobility engaged.\n" + " -I Do not set permissions on TURN relay endpoints\n" + " (for testing the non-standard server relay functionality).\n" + " -G Generate extra requests (create permissions, channel bind).\n" + "Options:\n" + " -l Message length (Default: 100 Bytes).\n" + " -i Certificate file (for secure connections only, optional).\n" + " -k Private key file (for secure connections only).\n" + " -E CA file for server certificate verification, \n" + " if the server certificate to be verified.\n" + " -F Cipher suite for TLS/DTLS. Default value is DEFAULT.\n" + " -p TURN server port (Default: 3478 unsecure, 5349 secure).\n" + " -n Number of messages to send (Default: 5).\n" + " -d Local interface device (optional).\n" + " -L Local address.\n" + " -m Number of clients (default is 1).\n" + " -e Peer address.\n" + " -r Peer port (default 3480).\n" + " -z Per-session packet interval in milliseconds (default is 20 ms).\n" + " -u STUN/TURN user name.\n" + " -w STUN/TURN user password.\n" + " -W TURN REST API authentication secret. Is not compatible with -A option.\n" + " -C TURN REST API timestamp/username separator symbol (character). The default value is ':'.\n" + " -o - the ORIGIN STUN attribute value.\n"; + +////////////////////////////////////////////////// + +void recalculate_restapi_hmac(void) { + + if (g_use_auth_secret_with_timestamp) { + + u08bits hmac[MAXSHASIZE]; + unsigned int hmac_len; + + hmac_len = SHA256SIZEBYTES; + + hmac[0] = 0; + + if (stun_calculate_hmac(g_uname, strlen((char*) g_uname), + (u08bits*) g_auth_secret, strlen(g_auth_secret), hmac, + &hmac_len, SHATYPE_SHA256) >= 0) { + size_t pwd_length = 0; + char *pwd = base64_encode(hmac, hmac_len, &pwd_length); + + if (pwd) { + if (pwd_length > 0) { + ns_bcopy(pwd,g_upwd,pwd_length); + g_upwd[pwd_length] = 0; + } + } + free(pwd); + } + } +} + +int main(int argc, char **argv) +{ + int port = 0; + int messagenumber = 5; + char local_addr[256]; + int c; + int mclient = 1; + char peer_address[129] = "\0"; + int peer_port = PEER_DEFAULT_PORT; + + char rest_api_separator = ':'; + int use_null_cipher=0; + + set_logfile("stdout"); + + set_execdir(); + + set_system_parameters(0); + + ns_bzero(local_addr, sizeof(local_addr)); + + while ((c = getopt(argc, argv, "d:p:l:n:L:m:e:r:u:w:i:k:z:W:C:E:F:o:vsyhcxXgtTSAPDNOUHMRIG")) != -1) { + switch (c){ + case 'o': + STRCPY(origin,optarg); + break; + case 'G': + extra_requests = 1; + break; + case 'F': + STRCPY(cipher_suite,optarg); + break; + case 'I': + no_permissions = 1; + break; + case 'M': + mobility = 1; + break; + case 'H': + shatype = SHATYPE_SHA256; + break; + case 'E': + { + char* fn = find_config_file(optarg,1); + if(!fn) { + fprintf(stderr,"ERROR: file %s not found\n",optarg); + exit(-1); + } + STRCPY(ca_cert_file,fn); + } + break; + case 'O': + dos = 1; + break; + case 'C': + rest_api_separator=*optarg; + break; + case 'D': + mandatory_channel_padding = 1; + break; + case 'N': + negative_test = 1; + break; + case 'R': + negative_protocol_test = 1; + break; + case 'z': + RTP_PACKET_INTERVAL = atoi(optarg); + break; + case 'A': + use_short_term = 1; + break; + case 'u': + STRCPY(g_uname, optarg); + break; + case 'w': + STRCPY(g_upwd, optarg); + break; + case 'g': + dont_fragment = 1; + break; + case 'd': + STRCPY(client_ifname, optarg); + break; + case 'x': + default_address_family = STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV6; + break; + case 'X': + default_address_family = STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV4; + break; + case 'l': + clmessage_length = atoi(optarg); + break; + case 's': + do_not_use_channel = 1; + break; + case 'n': + messagenumber = atoi(optarg); + break; + case 'p': + port = atoi(optarg); + break; + case 'L': + STRCPY(local_addr, optarg); + break; + case 'e': + STRCPY(peer_address, optarg); + break; + case 'r': + peer_port = atoi(optarg); + break; + case 'v': + clnet_verbose = TURN_VERBOSE_NORMAL; + break; + case 'h': + hang_on = 1; + break; + case 'c': + no_rtcp = 1; + break; + case 'm': + mclient = atoi(optarg); + break; + case 'y': + c2c = 1; + break; + case 't': + use_tcp = 1; + break; + case 'P': + passive_tcp = 1; + /* implies 'T': */ + case 'T': + relay_transport = STUN_ATTRIBUTE_TRANSPORT_TCP_VALUE; + break; + case 'U': + use_null_cipher = 1; + /* implies 'S' */ + /* no break */ + case 'S': + use_secure = 1; + break; + case 'W': + g_use_auth_secret_with_timestamp = 1; + STRCPY(g_auth_secret,optarg); + break; + case 'i': + { + char* fn = find_config_file(optarg,1); + if(!fn) { + fprintf(stderr,"ERROR: file %s not found\n",optarg); + exit(-1); + } + STRCPY(cert_file,fn); + free(fn); + } + break; + case 'k': + { + char* fn = find_config_file(optarg,1); + if(!fn) { + fprintf(stderr,"ERROR: file %s not found\n",optarg); + exit(-1); + } + STRCPY(pkey_file,fn); + free(fn); + } + break; + default: + fprintf(stderr, "%s\n", Usage); + exit(1); + } + } + + if(g_use_auth_secret_with_timestamp) { + + if(use_short_term) { + fprintf(stderr,"ERROR: You cannot use authentication secret (REST API) with short-term credentials mechanism.\n"); + exit(-1); + } + { + char new_uname[1025]; + const unsigned long exp_time = 3600 * 24; /* one day */ + if(g_uname[0]) { + snprintf(new_uname,sizeof(new_uname),"%lu%c%s",(unsigned long)time(NULL) + exp_time,rest_api_separator, (char*)g_uname); + } else { + snprintf(new_uname,sizeof(new_uname),"%lu", (unsigned long)time(NULL) + exp_time); + } + STRCPY(g_uname,new_uname); + } + { + u08bits hmac[MAXSHASIZE]; + unsigned int hmac_len; + + switch(shatype) { + case SHATYPE_SHA256: + hmac_len = SHA256SIZEBYTES; + break; + default: + hmac_len = SHA1SIZEBYTES; + }; + + hmac[0]=0; + + if(stun_calculate_hmac(g_uname, strlen((char*)g_uname), (u08bits*)g_auth_secret, strlen(g_auth_secret), hmac, &hmac_len, shatype)>=0) { + size_t pwd_length = 0; + char *pwd = base64_encode(hmac,hmac_len,&pwd_length); + + if(pwd) { + if(pwd_length>0) { + ns_bcopy(pwd,g_upwd,pwd_length); + g_upwd[pwd_length]=0; + } + } + free(pwd); + } + } + } + + if(is_TCP_relay()) { + dont_fragment = 0; + no_rtcp = 1; + c2c = 1; + use_tcp = 1; + do_not_use_channel = 1; + } + + if(port == 0) { + if(use_secure) + port = DEFAULT_STUN_TLS_PORT; + else + port = DEFAULT_STUN_PORT; + } + + if (clmessage_length < (int) sizeof(message_info)) + clmessage_length = (int) sizeof(message_info); + + const int max_header = 100; + if(clmessage_length > (int)(STUN_BUFFER_SIZE-max_header)) { + fprintf(stderr,"Message length was corrected to %d\n",(STUN_BUFFER_SIZE-max_header)); + clmessage_length = (int)(STUN_BUFFER_SIZE-max_header); + } + + if (optind >= argc) { + fprintf(stderr, "%s\n", Usage); + exit(-1); + } + + if (!c2c) { + if (make_ioa_addr((const u08bits*) peer_address, peer_port, &peer_addr) < 0) + return -1; + if(peer_addr.ss.sa_family == AF_INET6) + default_address_family = STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV6; + + } + + /* SSL Init ==>> */ + + if(use_secure) { + + SSL_load_error_strings(); + OpenSSL_add_ssl_algorithms(); + + const char *csuite = "ALL:SSLv2"; //"AES256-SHA" "DH" + if(use_null_cipher) + csuite = "eNULL"; + else if(cipher_suite[0]) + csuite=cipher_suite; + + if(use_tcp) { +#ifndef OPENSSL_NO_SSL2 + root_tls_ctx[root_tls_ctx_num] = SSL_CTX_new(SSLv2_client_method()); + SSL_CTX_set_cipher_list(root_tls_ctx[root_tls_ctx_num], csuite); + root_tls_ctx_num++; +#endif + root_tls_ctx[root_tls_ctx_num] = SSL_CTX_new(SSLv23_client_method()); + SSL_CTX_set_cipher_list(root_tls_ctx[root_tls_ctx_num], csuite); + root_tls_ctx_num++; + root_tls_ctx[root_tls_ctx_num] = SSL_CTX_new(SSLv3_client_method()); + SSL_CTX_set_cipher_list(root_tls_ctx[root_tls_ctx_num], csuite); + root_tls_ctx_num++; + root_tls_ctx[root_tls_ctx_num] = SSL_CTX_new(TLSv1_client_method()); + SSL_CTX_set_cipher_list(root_tls_ctx[root_tls_ctx_num], csuite); + root_tls_ctx_num++; +#if defined(SSL_TXT_TLSV1_1) + root_tls_ctx[root_tls_ctx_num] = SSL_CTX_new(TLSv1_1_client_method()); + SSL_CTX_set_cipher_list(root_tls_ctx[root_tls_ctx_num], csuite); + root_tls_ctx_num++; +#if defined(SSL_TXT_TLSV1_2) + root_tls_ctx[root_tls_ctx_num] = SSL_CTX_new(TLSv1_2_client_method()); + SSL_CTX_set_cipher_list(root_tls_ctx[root_tls_ctx_num], csuite); + root_tls_ctx_num++; +#endif +#endif + } else { +#if defined(TURN_NO_DTLS) + fprintf(stderr,"ERROR: DTLS is not supported.\n"); + exit(-1); +#else + if(OPENSSL_VERSION_NUMBER < 0x10000000L) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "WARNING: OpenSSL version is rather old, DTLS may not be working correctly.\n"); + } + root_tls_ctx[root_tls_ctx_num] = SSL_CTX_new(DTLSv1_client_method()); + SSL_CTX_set_cipher_list(root_tls_ctx[root_tls_ctx_num], csuite); + root_tls_ctx_num++; +#endif + } + + int sslind = 0; + for(sslind = 0; sslind +#include + +#include "ns_turn_ioaddr.h" +#include "ns_turn_utils.h" + +#include "stun_buffer.h" +#include "apputils.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +///////// types //////////// + +enum _UR_STATE { + UR_STATE_UNKNOWN=0, + UR_STATE_READY, + UR_STATE_DONE +}; + +typedef enum _UR_STATE UR_STATE; + +//////////////// session info ////////////////////// + +typedef struct +{ + /* RFC 6062 */ + u32bits cid; + ioa_addr tcp_data_local_addr; + ioa_socket_raw tcp_data_fd; + SSL *tcp_data_ssl; + int tcp_data_bound; +} app_tcp_conn_info; + +typedef struct { + ioa_addr local_addr; + char lsaddr[129]; + ioa_addr remote_addr; + char rsaddr[129]; + char ifname[129]; + ioa_addr peer_addr; + ioa_socket_raw fd; + SSL *ssl; + int broken; + u08bits nonce[STUN_MAX_NONCE_SIZE+1]; + u08bits realm[STUN_MAX_REALM_SIZE+1]; + /* RFC 6062 */ + app_tcp_conn_info **tcp_conn; + size_t tcp_conn_number; + int is_peer; + SHATYPE shatype; + char s_mobile_id[33]; +} app_ur_conn_info; + +typedef struct { + app_ur_conn_info pinfo; + UR_STATE state; + unsigned int ctime; + uint16_t chnum; + int wait_cycles; + int timer_cycle; + int known_mtu; + int completed; + struct event *input_ev; + struct event *input_tcp_data_ev; + stun_buffer in_buffer; + stun_buffer out_buffer; + u32bits refresh_time; + u32bits finished_time; + //Msg counters: + int tot_msgnum; + int wmsgnum; + int rmsgnum; + int recvmsgnum; + u32bits recvtimems; + u32bits to_send_timems; + //Statistics: + size_t loss; + u64bits latency; + u64bits jitter; +} app_ur_session; + +/////////////////////////////////////////////////////// + +typedef struct _message_info { + int msgnum; + u64bits mstime; +} message_info; + +/////////////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif //__SESSION__ diff --git a/src/apps/uclient/startuclient.c b/src/apps/uclient/startuclient.c new file mode 100644 index 0000000..35016c8 --- /dev/null +++ b/src/apps/uclient/startuclient.c @@ -0,0 +1,1548 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include "apputils.h" +#include "ns_turn_utils.h" +#include "startuclient.h" +#include "ns_turn_msg.h" +#include "uclient.h" +#include "session.h" + +#include + +///////////////////////////////////////// + +#define MAX_CONNECT_EFFORTS (77) +#define DTLS_MAX_CONNECT_TIMEOUT (30) +#define EXTRA_CREATE_PERMS (25) + +static uint64_t current_reservation_token = 0; +static int allocate_rtcp = 0; +static const int never_allocate_rtcp = 0; + +///////////////////////////////////////// + +int rare_event(void) +{ + if(dos) + return (((unsigned long)random()) %1000 == 777); + return 0; +} + +int not_rare_event(void) +{ + if(dos) + return ((((unsigned long)random()) %1000) < 200); + return 0; +} + +static int get_allocate_address_family(ioa_addr *relay_addr) { + if(relay_addr->ss.sa_family == AF_INET) + return STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_DEFAULT; + else if(relay_addr->ss.sa_family == AF_INET6) + return STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV6; + else + return STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_INVALID; +} + +///////////////////////////////////////// + +static SSL* tls_connect(ioa_socket_raw fd, ioa_addr *remote_addr) +{ + int ctxtype = (int)(((unsigned long)random())%root_tls_ctx_num); + + SSL *ssl = SSL_new(root_tls_ctx[ctxtype]); + + if(use_tcp) { + SSL_set_fd(ssl, fd); + } else { +#if defined(TURN_NO_DTLS) + UNUSED_ARG(remote_addr); + fprintf(stderr,"ERROR: DTLS is not supported.\n"); + exit(-1); +#else + /* Create BIO, connect and set to already connected */ + BIO *bio = BIO_new_dgram(fd, BIO_CLOSE); + //bio = BIO_new_socket(fd, BIO_CLOSE); + + BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &remote_addr->ss); + + SSL_set_bio(ssl, bio, bio); + + { + struct timeval timeout; + /* Set and activate timeouts */ + timeout.tv_sec = DTLS_MAX_CONNECT_TIMEOUT; + timeout.tv_usec = 0; + BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_RECV_TIMEOUT, 0, &timeout); + } + + set_mtu_df(ssl, fd, remote_addr->ss.sa_family, SOSO_MTU, !use_tcp, clnet_verbose); +#endif + } + + SSL_set_max_cert_list(ssl, 655350); + + if (clnet_verbose) + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "call SSL_connect...\n"); + + int rc = 0; + + do { + do { + rc = SSL_connect(ssl); + } while (rc < 0 && errno == EINTR); + if (rc > 0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"%s: client session connected with cipher %s, method=%s\n",__FUNCTION__, + SSL_get_cipher(ssl),turn_get_ssl_method(ssl,NULL)); + if(clnet_verbose && SSL_get_peer_certificate(ssl)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "------------------------------------------------------------\n"); + X509_NAME_print_ex_fp(stdout, X509_get_subject_name(SSL_get_peer_certificate(ssl)), 1, + XN_FLAG_MULTILINE); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "\n\n Cipher: %s\n", SSL_CIPHER_get_name(SSL_get_current_cipher(ssl))); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "\n------------------------------------------------------------\n\n"); + } + break; + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: cannot connect\n", + __FUNCTION__); + switch (SSL_get_error(ssl, rc)) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + if(!dos) usleep(1000); + continue; + default: { + char buf[1025]; + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s (%d)\n", + ERR_error_string(ERR_get_error(), buf), SSL_get_error( + ssl, rc)); + exit(-1); + } + }; + } + } while (1); + + if (clnet_verbose && SSL_get_peer_certificate(ssl)) { + if(use_tcp) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "------TLS---------------------------------------------------\n"); + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "------DTLS---------------------------------------------------\n"); + } + X509_NAME_print_ex_fp(stdout, X509_get_subject_name( + SSL_get_peer_certificate(ssl)), 1, XN_FLAG_MULTILINE); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "\n\n Cipher: %s\n", + SSL_CIPHER_get_name(SSL_get_current_cipher(ssl))); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "\n------------------------------------------------------------\n\n"); + } + + return ssl; +} + +int socket_connect(evutil_socket_t clnet_fd, ioa_addr *remote_addr, int *connect_err) +{ + if (addr_connect(clnet_fd, remote_addr, connect_err) < 0) { + if(*connect_err == EINPROGRESS) + return 0; + if (*connect_err == EADDRINUSE) + return +1; + perror("connect"); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: cannot connect to remote addr: %d\n", __FUNCTION__,*connect_err); + exit(-1); + } + + return 0; +} + +static int clnet_connect(uint16_t clnet_remote_port, const char *remote_address, + const unsigned char* ifname, const char *local_address, int verbose, + app_ur_conn_info *clnet_info) { + + ioa_addr local_addr; + evutil_socket_t clnet_fd; + int connect_err; + + ioa_addr remote_addr; + + start_socket: + + clnet_fd = -1; + connect_err = 0; + + ns_bzero(&remote_addr, sizeof(ioa_addr)); + if (make_ioa_addr((const u08bits*) remote_address, clnet_remote_port, + &remote_addr) < 0) + return -1; + + ns_bzero(&local_addr, sizeof(ioa_addr)); + + clnet_fd = socket(remote_addr.ss.sa_family, use_tcp ? SOCK_STREAM : SOCK_DGRAM, 0); + if (clnet_fd < 0) { + perror("socket"); + exit(-1); + } + + if (sock_bind_to_device(clnet_fd, ifname) < 0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "Cannot bind client socket to device %s\n", ifname); + } + + set_sock_buf_size(clnet_fd, UR_CLIENT_SOCK_BUF_SIZE); + + if(clnet_info->is_peer && (*local_address==0)) { + + if(remote_addr.ss.sa_family == AF_INET6) { + if (make_ioa_addr((const u08bits*) "::1", 0, &local_addr) < 0) { + return -1; + } + } else { + if (make_ioa_addr((const u08bits*) "127.0.0.1", 0, &local_addr) < 0) { + return -1; + } + } + + addr_bind(clnet_fd, &local_addr, 0); + + } else if (strlen(local_address) > 0) { + + if (make_ioa_addr((const u08bits*) local_address, 0, + &local_addr) < 0) + return -1; + + addr_bind(clnet_fd, &local_addr,0); + } + + if(clnet_info->is_peer) { + ; + } else if(socket_connect(clnet_fd, &remote_addr, &connect_err)>0) + goto start_socket; + + if (clnet_info) { + addr_cpy(&(clnet_info->remote_addr), &remote_addr); + addr_cpy(&(clnet_info->local_addr), &local_addr); + clnet_info->fd = clnet_fd; + addr_get_from_sock(clnet_fd, &(clnet_info->local_addr)); + STRCPY(clnet_info->lsaddr,local_address); + STRCPY(clnet_info->rsaddr,remote_address); + STRCPY(clnet_info->ifname,(const char*)ifname); + } + + if (use_secure) { + clnet_info->ssl = tls_connect(clnet_info->fd, &remote_addr); + if (!clnet_info->ssl) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: cannot SSL connect to remote addr\n", __FUNCTION__); + exit(-1); + } + } + + if(verbose && clnet_info) { + addr_debug_print(verbose, &(clnet_info->local_addr), "Connected from"); + addr_debug_print(verbose, &remote_addr, "Connected to"); + } + + if(!dos) usleep(500); + + return 0; +} + +int read_mobility_ticket(app_ur_conn_info *clnet_info, stun_buffer *message) +{ + int ret = 0; + if(clnet_info && message) { + stun_attr_ref s_mobile_id_sar = stun_attr_get_first_by_type(message, STUN_ATTRIBUTE_MOBILITY_TICKET); + if(s_mobile_id_sar) { + int smid_len = stun_attr_get_len(s_mobile_id_sar); + if(smid_len>0 && (((size_t)smid_len)s_mobile_id))) { + const u08bits* smid_val = stun_attr_get_value(s_mobile_id_sar); + if(smid_val) { + ns_bcopy(smid_val, clnet_info->s_mobile_id, (size_t)smid_len); + clnet_info->s_mobile_id[smid_len] = 0; + if (clnet_verbose) + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "%s: smid=%s\n", __FUNCTION__, clnet_info->s_mobile_id); + } + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: ERROR: smid_len=%d\n", __FUNCTION__, smid_len); + ret = -1; + } + } + } + return ret; +} + +static int clnet_allocate(int verbose, + app_ur_conn_info *clnet_info, + ioa_addr *relay_addr, + int af, + char *turn_addr, u16bits *turn_port) { + + int af_cycle = 0; + int reopen_socket = 0; + + int allocate_finished; + + beg_allocate: + + allocate_finished=0; + + while (!allocate_finished && af_cycle++ < 32) { + + int allocate_sent = 0; + + if(reopen_socket && !use_tcp) { + socket_closesocket(clnet_info->fd); + clnet_info->fd = -1; + if (clnet_connect(addr_get_port(&(clnet_info->remote_addr)), clnet_info->rsaddr, (u08bits*)clnet_info->ifname, clnet_info->lsaddr, + verbose, clnet_info) < 0) { + exit(-1); + } + reopen_socket = 0; + } + + stun_buffer message; + if(current_reservation_token) + af = STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_DEFAULT; + + if(!dos) + stun_set_allocate_request(&message, 800, af, relay_transport, mobility); + else + stun_set_allocate_request(&message, 300, af, relay_transport, mobility); + if(dont_fragment) + stun_attr_add(&message, STUN_ATTRIBUTE_DONT_FRAGMENT, NULL, 0); + if(!no_rtcp) { + if (!never_allocate_rtcp && allocate_rtcp) { + uint64_t reservation_token = ioa_ntoh64(current_reservation_token); + stun_attr_add(&message, STUN_ATTRIBUTE_RESERVATION_TOKEN, + (char*) (&reservation_token), 8); + } else { + stun_attr_add_even_port(&message, 1); + } + } + + if(origin[0]) + stun_attr_add(&message, STUN_ATTRIBUTE_ORIGIN, origin, strlen(origin)); + + if(add_integrity(clnet_info, &message)<0) return -1; + + stun_attr_add_fingerprint_str(message.buf,(size_t*)&(message.len)); + + while (!allocate_sent) { + + int len = send_buffer(clnet_info, &message,0,0); + + if (len > 0) { + if (verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "allocate sent\n"); + } + allocate_sent = 1; + } else { + perror("send"); + exit(1); + } + } + + ////////////<<==allocate send + + if(not_rare_event()) return 0; + + ////////allocate response==>> + { + int allocate_received = 0; + stun_buffer message; + while (!allocate_received) { + + int len = recv_buffer(clnet_info, &message, 1, 0); + + if (len > 0) { + if (verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "allocate response received: \n"); + } + message.len = len; + int err_code = 0; + u08bits err_msg[129]; + if (stun_is_success_response(&message)) { + allocate_received = 1; + allocate_finished = 1; + + if(clnet_info->nonce[0] || use_short_term) { + SHATYPE sht = clnet_info->shatype; + if(stun_check_message_integrity_str(get_turn_credentials_type(), + message.buf, (size_t)(message.len), g_uname, + clnet_info->realm, g_upwd, sht)<1) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"Wrong integrity in allocate message received from server\n"); + return -1; + } + } + + if (verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "success\n"); + } + if (stun_attr_get_first_addr(&message, + STUN_ATTRIBUTE_XOR_RELAYED_ADDRESS, relay_addr, + NULL) < 0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "%s: !!!: relay addr cannot be received\n", + __FUNCTION__); + return -1; + } else { + if (verbose) { + ioa_addr remote_addr; + memcpy(&remote_addr, relay_addr, + sizeof(ioa_addr)); + addr_debug_print(verbose, &remote_addr, + "Received relay addr"); + } + } + stun_attr_ref rt_sar = stun_attr_get_first_by_type( + &message, STUN_ATTRIBUTE_RESERVATION_TOKEN); + uint64_t rtv = stun_attr_get_reservation_token_value(rt_sar); + current_reservation_token = rtv; + if (verbose) + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "%s: rtv=%llu\n", __FUNCTION__, rtv); + + read_mobility_ticket(clnet_info, &message); + + } else if (stun_is_challenge_response_str(message.buf, (size_t)message.len, + &err_code,err_msg,sizeof(err_msg), + clnet_info->realm,clnet_info->nonce)) { + if(err_code == SHA_TOO_WEAK && (clnet_info->shatype == SHATYPE_SHA1)) { + clnet_info->shatype = SHATYPE_SHA256; + recalculate_restapi_hmac(); + } + goto beg_allocate; + } else if (stun_is_error_response(&message, &err_code,err_msg,sizeof(err_msg))) { + + if(err_code == SHA_TOO_WEAK && (clnet_info->shatype == SHATYPE_SHA1) && use_short_term) { + clnet_info->shatype = SHATYPE_SHA256; + goto beg_allocate; + } + + allocate_received = 1; + + if(err_code == 300) { + + if(clnet_info->nonce[0] || use_short_term) { + SHATYPE sht = clnet_info->shatype; + if(stun_check_message_integrity_str(get_turn_credentials_type(), + message.buf, (size_t)(message.len), g_uname, + clnet_info->realm, g_upwd, sht)<1) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"Wrong integrity in allocate message received from server\n"); + return -1; + } + } + + ioa_addr alternate_server; + if(stun_attr_get_first_addr(&message, STUN_ATTRIBUTE_ALTERNATE_SERVER, &alternate_server, NULL)==-1) { + //error + } else if(turn_addr && turn_port){ + addr_to_string_no_port(&alternate_server, (u08bits*)turn_addr); + *turn_port = (u16bits)addr_get_port(&alternate_server); + } + + } + + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "error %d (%s)\n", + err_code,(char*)err_msg); + if (err_code != 437) { + allocate_finished = 1; + current_reservation_token = 0; + return -1; + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "trying allocate again...\n", err_code); + sleep(1); + reopen_socket = 1; + } + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "unknown allocate response\n"); + /* Try again ? */ + } + } else { + perror("recv"); + exit(-1); + break; + } + } + } + } + ////////////<<== allocate response received + + if(rare_event()) return 0; + + if(!allocate_finished) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "Cannot complete Allocation\n"); + exit(-1); + } + + allocate_rtcp = !allocate_rtcp; + + if (1) { + + af_cycle = 0; + + if(clnet_info->s_mobile_id[0]) { + + int fd = clnet_info->fd; + SSL* ssl = clnet_info->ssl; + + int close_now = (int)(random()%2); + + if(close_now) { + int close_socket = (int)(random()%2); + if(ssl && !close_socket) { + SSL_shutdown(ssl); + SSL_free(ssl); + ssl = NULL; + fd = -1; + } else if(fd>=0) { + close(fd); + fd = -1; + ssl = NULL; + } + } + + app_ur_conn_info ci; + ns_bcopy(clnet_info,&ci,sizeof(ci)); + ci.fd = -1; + ci.ssl = NULL; + clnet_info->fd = -1; + clnet_info->ssl = NULL; + //Reopen: + if(clnet_connect(addr_get_port(&(ci.remote_addr)), ci.rsaddr, + (unsigned char*)ci.ifname, ci.lsaddr, clnet_verbose, + clnet_info)<0) { + exit(-1); + } + + if(ssl) { + SSL_shutdown(ssl); + SSL_free(ssl); + } else if(fd>=0) { + close(fd); + } + } + + beg_refresh: + + if(af_cycle++>32) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "Cannot complete Refresh\n"); + exit(-1); + } + + //==>>refresh request, for an example only: + { + int refresh_sent = 0; + + stun_buffer message; + stun_init_request(STUN_METHOD_REFRESH, &message); + uint32_t lt = htonl(600); + stun_attr_add(&message, STUN_ATTRIBUTE_LIFETIME, (const char*) <, + 4); + + if(clnet_info->s_mobile_id[0]) { + stun_attr_add(&message, STUN_ATTRIBUTE_MOBILITY_TICKET, (const char*)clnet_info->s_mobile_id, strlen(clnet_info->s_mobile_id)); + } + + if(add_integrity(clnet_info, &message)<0) return -1; + + stun_attr_add_fingerprint_str(message.buf,(size_t*)&(message.len)); + + while (!refresh_sent) { + + int len = send_buffer(clnet_info, &message, 0,0); + + if (len > 0) { + if (verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "refresh sent\n"); + } + refresh_sent = 1; + } else { + perror("send"); + exit(1); + } + } + } + + if(not_rare_event()) return 0; + + ////////refresh response==>> + { + int refresh_received = 0; + stun_buffer message; + while (!refresh_received) { + + int len = recv_buffer(clnet_info, &message, 1, 0); + + if (len > 0) { + if (verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "refresh response received: \n"); + } + message.len = len; + int err_code = 0; + u08bits err_msg[129]; + if (stun_is_success_response(&message)) { + read_mobility_ticket(clnet_info, &message); + refresh_received = 1; + if (verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "success\n"); + } + } else if (stun_is_challenge_response_str(message.buf, (size_t)message.len, + &err_code,err_msg,sizeof(err_msg), + clnet_info->realm,clnet_info->nonce)) { + if(err_code == SHA_TOO_WEAK && (clnet_info->shatype == SHATYPE_SHA1)) { + clnet_info->shatype = SHATYPE_SHA256; + recalculate_restapi_hmac(); + } + goto beg_refresh; + } else if (stun_is_error_response(&message, &err_code,err_msg,sizeof(err_msg))) { + refresh_received = 1; + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "error %d (%s)\n", + err_code,(char*)err_msg); + return -1; + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "unknown refresh response\n"); + /* Try again ? */ + } + } else { + perror("recv"); + exit(-1); + break; + } + } + } + } + + return 0; +} + +static int turn_channel_bind(int verbose, uint16_t *chn, + app_ur_conn_info *clnet_info, ioa_addr *peer_addr) { + + beg_bind: + + { + int cb_sent = 0; + + stun_buffer message; + + if(negative_test) { + *chn = stun_set_channel_bind_request(&message, peer_addr, (u16bits)random()); + } else { + *chn = stun_set_channel_bind_request(&message, peer_addr, *chn); + } + + if(add_integrity(clnet_info, &message)<0) return -1; + + stun_attr_add_fingerprint_str(message.buf,(size_t*)&(message.len)); + + while (!cb_sent) { + + int len = send_buffer(clnet_info, &message, 0,0); + if (len > 0) { + if (verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "channel bind sent\n"); + } + cb_sent = 1; + } else { + perror("send"); + exit(1); + } + } + } + + ////////////<<==channel bind send + + if(not_rare_event()) return 0; + + ////////channel bind response==>> + + { + int cb_received = 0; + stun_buffer message; + while (!cb_received) { + + int len = recv_buffer(clnet_info, &message, 1, 0); + if (len > 0) { + if (verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "cb response received: \n"); + } + int err_code = 0; + u08bits err_msg[129]; + if (stun_is_success_response(&message)) { + + cb_received = 1; + + if(clnet_info->nonce[0] || use_short_term) { + SHATYPE sht = clnet_info->shatype; + if(stun_check_message_integrity_str(get_turn_credentials_type(), + message.buf, (size_t)(message.len), g_uname, + clnet_info->realm, g_upwd, sht)<1) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"Wrong integrity in channel bind message received from server\n"); + return -1; + } + } + + if (verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "success: 0x%x\n", + (int) (*chn)); + } + } else if (stun_is_challenge_response_str(message.buf, (size_t)message.len, + &err_code,err_msg,sizeof(err_msg), + clnet_info->realm,clnet_info->nonce)) { + if(err_code == SHA_TOO_WEAK && (clnet_info->shatype == SHATYPE_SHA1)) { + clnet_info->shatype = SHATYPE_SHA256; + recalculate_restapi_hmac(); + } + goto beg_bind; + } else if (stun_is_error_response(&message, &err_code,err_msg,sizeof(err_msg))) { + cb_received = 1; + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "channel bind: error %d (%s)\n", + err_code,(char*)err_msg); + return -1; + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "unknown channel bind response\n"); + /* Try again ? */ + } + } else { + perror("recv"); + exit(-1); + break; + } + } + } + + return 0; +} + +static int turn_create_permission(int verbose, app_ur_conn_info *clnet_info, + ioa_addr *peer_addr, int addrnum) +{ + + if(no_permissions || (addrnum<1)) + return 0; + + char saddr[129]="\0"; + if (verbose) { + addr_to_string(peer_addr,(u08bits*)saddr); + } + + beg_cp: + + { + int cp_sent = 0; + + stun_buffer message; + + stun_init_request(STUN_METHOD_CREATE_PERMISSION, &message); + { + int addrindex; + for(addrindex=0;addrindex 0) { + if (verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "create perm sent: %s\n",saddr); + } + cp_sent = 1; + } else { + perror("send"); + exit(1); + } + } + } + + ////////////<<==create permission send + + if(not_rare_event()) return 0; + + ////////create permission response==>> + + { + int cp_received = 0; + stun_buffer message; + while (!cp_received) { + + int len = recv_buffer(clnet_info, &message, 1, 0); + if (len > 0) { + if (verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "cp response received: \n"); + } + int err_code = 0; + u08bits err_msg[129]; + if (stun_is_success_response(&message)) { + + cp_received = 1; + + if(clnet_info->nonce[0] || use_short_term) { + SHATYPE sht = clnet_info->shatype; + if(stun_check_message_integrity_str(get_turn_credentials_type(), + message.buf, (size_t)(message.len), g_uname, + clnet_info->realm, g_upwd, sht)<1) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"Wrong integrity in create permission message received from server\n"); + return -1; + } + } + + if (verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "success\n"); + } + } else if (stun_is_challenge_response_str(message.buf, (size_t)message.len, + &err_code,err_msg,sizeof(err_msg), + clnet_info->realm,clnet_info->nonce)) { + if(err_code == SHA_TOO_WEAK && (clnet_info->shatype == SHATYPE_SHA1)) { + clnet_info->shatype = SHATYPE_SHA256; + recalculate_restapi_hmac(); + } + goto beg_cp; + } else if (stun_is_error_response(&message, &err_code,err_msg,sizeof(err_msg))) { + cp_received = 1; + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "create permission error %d (%s)\n", + err_code,(char*)err_msg); + return -1; + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "unknown create permission response\n"); + /* Try again ? */ + } + } else { + perror("recv"); + exit(-1); + } + } + } + + return 0; +} + +int start_connection(uint16_t clnet_remote_port0, + const char *remote_address0, + const unsigned char* ifname, const char *local_address, + int verbose, + app_ur_conn_info *clnet_info_probe, + app_ur_conn_info *clnet_info, + uint16_t *chn, + app_ur_conn_info *clnet_info_rtcp, + uint16_t *chn_rtcp) { + + ioa_addr relay_addr; + ioa_addr relay_addr_rtcp; + ioa_addr peer_addr_rtcp; + + addr_cpy(&peer_addr_rtcp,&peer_addr); + addr_set_port(&peer_addr_rtcp,addr_get_port(&peer_addr_rtcp)+1); + + /* Probe: */ + + if (clnet_connect(clnet_remote_port0, remote_address0, ifname, local_address, + verbose, clnet_info_probe) < 0) { + exit(-1); + } + + uint16_t clnet_remote_port = clnet_remote_port0; + char remote_address[1025]; + STRCPY(remote_address,remote_address0); + + clnet_allocate(verbose, clnet_info_probe, &relay_addr, default_address_family, remote_address, &clnet_remote_port); + + /* Real: */ + + *chn = 0; + if(chn_rtcp) *chn_rtcp=0; + + if (clnet_connect(clnet_remote_port, remote_address, ifname, local_address, + verbose, clnet_info) < 0) { + exit(-1); + } + + if(!no_rtcp) { + if (clnet_connect(clnet_remote_port, remote_address, ifname, local_address, + verbose, clnet_info_rtcp) < 0) { + exit(-1); + } + } + + int af = default_address_family ? default_address_family : get_allocate_address_family(&peer_addr); + if (clnet_allocate(verbose, clnet_info, &relay_addr, af, NULL,NULL) < 0) { + exit(-1); + } + + if(rare_event()) return 0; + + if(!no_rtcp) { + af = default_address_family ? default_address_family : get_allocate_address_family(&peer_addr_rtcp); + if (clnet_allocate(verbose, clnet_info_rtcp, &relay_addr_rtcp, af,NULL,NULL) < 0) { + exit(-1); + } + if(rare_event()) return 0; + } + + if (!dos) { + if (!do_not_use_channel) { + /* These multiple "channel bind" requests are here only because + * we are playing with the TURN server trying to screw it */ + if (turn_channel_bind(verbose, chn, clnet_info, &peer_addr_rtcp) + < 0) { + exit(-1); + } + if(rare_event()) return 0; + + if (turn_channel_bind(verbose, chn, clnet_info, &peer_addr_rtcp) + < 0) { + exit(-1); + } + if(rare_event()) return 0; + *chn = 0; + if (turn_channel_bind(verbose, chn, clnet_info, &peer_addr) < 0) { + exit(-1); + } + + if(rare_event()) return 0; + if (turn_channel_bind(verbose, chn, clnet_info, &peer_addr) < 0) { + exit(-1); + } + if(rare_event()) return 0; + + if(extra_requests) { + const char *sarbaddr = "164.156.178.190"; + if(random() % 2 == 0) + sarbaddr = "2001::172"; + ioa_addr arbaddr; + make_ioa_addr((const u08bits*)sarbaddr, 333, &arbaddr); + int i; + int maxi = (unsigned short)random() % EXTRA_CREATE_PERMS; + for(i=0;i0) + addr_cpy(&arbaddr[i],&arbaddr[0]); + addr_set_port(&arbaddr[i], (unsigned short)random()); + u08bits *u=(u08bits*)&(arbaddr[i].s4.sin_addr); + u[(unsigned short)random()%4] = u[(unsigned short)random()%4] + 1; + //char sss[128]; + //addr_to_string(&arbaddr[i],(u08bits*)sss); + //printf("%s: 111.111: %s\n",__FUNCTION__,sss); + } + turn_create_permission(verbose, clnet_info, arbaddr, maxi); + } + } else { + + int before=(random()%2 == 0); + + if(before) { + if (turn_create_permission(verbose, clnet_info, &peer_addr, 1) < 0) { + exit(-1); + } + if(rare_event()) return 0; + if (turn_create_permission(verbose, clnet_info, &peer_addr_rtcp, 1) + < 0) { + exit(-1); + } + if(rare_event()) return 0; + } + + if(extra_requests) { + const char *sarbaddr = "64.56.78.90"; + if(random() % 2 == 0) + sarbaddr = "2001::172"; + ioa_addr arbaddr[EXTRA_CREATE_PERMS]; + make_ioa_addr((const u08bits*)sarbaddr, 333, &arbaddr[0]); + int i; + int maxi = (unsigned short)random() % EXTRA_CREATE_PERMS; + for(i=0;i0) + addr_cpy(&arbaddr[i],&arbaddr[0]); + addr_set_port(&arbaddr[i], (unsigned short)random()); + u08bits *u=(u08bits*)&(arbaddr[i].s4.sin_addr); + u[(unsigned short)random()%4] = u[(unsigned short)random()%4] + 1; + //char sss[128]; + //addr_to_string(&arbaddr,(u08bits*)sss); + //printf("%s: 111.111: %s\n",__FUNCTION__,sss); + } + turn_create_permission(verbose, clnet_info, arbaddr, maxi); + } + + if(!before) { + if (turn_create_permission(verbose, clnet_info, &peer_addr, 1) < 0) { + exit(-1); + } + if(rare_event()) return 0; + if (turn_create_permission(verbose, clnet_info, &peer_addr_rtcp, 1) + < 0) { + exit(-1); + } + if(rare_event()) return 0; + } + + if (!no_rtcp) { + if (turn_create_permission(verbose, clnet_info_rtcp, + &peer_addr_rtcp, 1) < 0) { + exit(-1); + } + if(rare_event()) return 0; + if (turn_create_permission(verbose, clnet_info_rtcp, &peer_addr, 1) + < 0) { + exit(-1); + } + if(rare_event()) return 0; + } + } + } + + addr_cpy(&(clnet_info->peer_addr), &peer_addr); + if(!no_rtcp) + addr_cpy(&(clnet_info_rtcp->peer_addr), &peer_addr_rtcp); + + return 0; +} + + +int start_c2c_connection(uint16_t clnet_remote_port0, + const char *remote_address0, const unsigned char* ifname, + const char *local_address, int verbose, + app_ur_conn_info *clnet_info_probe, + app_ur_conn_info *clnet_info1, + uint16_t *chn1, app_ur_conn_info *clnet_info1_rtcp, + uint16_t *chn1_rtcp, + app_ur_conn_info *clnet_info2, uint16_t *chn2, + app_ur_conn_info *clnet_info2_rtcp, + uint16_t *chn2_rtcp) { + + ioa_addr relay_addr1; + ioa_addr relay_addr1_rtcp; + + ioa_addr relay_addr2; + ioa_addr relay_addr2_rtcp; + + *chn1 = 0; + *chn2 = 0; + if(chn1_rtcp) *chn1_rtcp=0; + if(chn2_rtcp) *chn2_rtcp=0; + + /* Probe: */ + + if (clnet_connect(clnet_remote_port0, remote_address0, ifname, local_address, + verbose, clnet_info_probe) < 0) { + exit(-1); + } + + uint16_t clnet_remote_port = clnet_remote_port0; + char remote_address[1025]; + STRCPY(remote_address,remote_address0); + + clnet_allocate(verbose, clnet_info_probe, &relay_addr1, default_address_family, remote_address, &clnet_remote_port); + + if(rare_event()) return 0; + + /* Real: */ + + if (clnet_connect(clnet_remote_port, remote_address, ifname, local_address, + verbose, clnet_info1) < 0) { + exit(-1); + } + + if(!no_rtcp) + if (clnet_connect(clnet_remote_port, remote_address, ifname, local_address, + verbose, clnet_info1_rtcp) < 0) { + exit(-1); + } + + if(passive_tcp) + clnet_info2->is_peer = 1; + + if (clnet_connect(clnet_remote_port, remote_address, ifname, local_address, + verbose, clnet_info2) < 0) { + exit(-1); + } + + if(!no_rtcp) + if (clnet_connect(clnet_remote_port, remote_address, ifname, local_address, + verbose, clnet_info2_rtcp) < 0) { + exit(-1); + } + + if(!no_rtcp) { + + if (clnet_allocate(verbose, clnet_info1, &relay_addr1, default_address_family,NULL,NULL) + < 0) { + exit(-1); + } + + if(rare_event()) return 0; + + if (clnet_allocate(verbose, clnet_info1_rtcp, + &relay_addr1_rtcp, default_address_family,NULL,NULL) < 0) { + exit(-1); + } + + if(rare_event()) return 0; + + if (clnet_allocate(verbose, clnet_info2, &relay_addr2, default_address_family,NULL,NULL) + < 0) { + exit(-1); + } + + if(rare_event()) return 0; + + if (clnet_allocate(verbose, clnet_info2_rtcp, + &relay_addr2_rtcp, default_address_family,NULL,NULL) < 0) { + exit(-1); + } + + if(rare_event()) return 0; + } else { + + if (clnet_allocate(verbose, clnet_info1, &relay_addr1, default_address_family,NULL,NULL) + < 0) { + exit(-1); + } + if(rare_event()) return 0; + if(!(clnet_info2->is_peer)) { + if (clnet_allocate(verbose, clnet_info2, &relay_addr2, default_address_family,NULL,NULL) < 0) { + exit(-1); + } + if(rare_event()) return 0; + } else { + addr_cpy(&(clnet_info2->remote_addr),&relay_addr1); + addr_cpy(&relay_addr2,&(clnet_info2->local_addr)); + } + } + + if (!do_not_use_channel) { + if (turn_channel_bind(verbose, chn1, clnet_info1, &relay_addr2) < 0) { + exit(-1); + } + + if(extra_requests) { + const char *sarbaddr = "164.156.178.190"; + if(random() % 2 == 0) + sarbaddr = "2001::172"; + ioa_addr arbaddr; + make_ioa_addr((const u08bits*)sarbaddr, 333, &arbaddr); + int i; + int maxi = (unsigned short)random() % EXTRA_CREATE_PERMS; + for(i=0;i0) + addr_cpy(&arbaddr[i],&arbaddr[0]); + addr_set_port(&arbaddr[i], (unsigned short)random()); + u08bits *u=(u08bits*)&(arbaddr[i].s4.sin_addr); + u[(unsigned short)random()%4] = u[(unsigned short)random()%4] + 1; + //char sss[128]; + //addr_to_string(&arbaddr[i],(u08bits*)sss); + //printf("%s: 111.111: %s\n",__FUNCTION__,sss); + } + turn_create_permission(verbose, clnet_info1, arbaddr, maxi); + } + + if(!no_rtcp) + if (turn_channel_bind(verbose, chn1_rtcp, clnet_info1_rtcp, + &relay_addr2_rtcp) < 0) { + exit(-1); + } + if(rare_event()) return 0; + if (turn_channel_bind(verbose, chn2, clnet_info2, &relay_addr1) < 0) { + exit(-1); + } + if(rare_event()) return 0; + if(!no_rtcp) + if (turn_channel_bind(verbose, chn2_rtcp, clnet_info2_rtcp, + &relay_addr1_rtcp) < 0) { + exit(-1); + } + if(rare_event()) return 0; + } else { + + if (turn_create_permission(verbose, clnet_info1, &relay_addr2, 1) < 0) { + exit(-1); + } + + if(extra_requests) { + const char *sarbaddr = "64.56.78.90"; + if(random() % 2 == 0) + sarbaddr = "2001::172"; + ioa_addr arbaddr; + make_ioa_addr((const u08bits*)sarbaddr, 333, &arbaddr); + int i; + int maxi = (unsigned short)random() % EXTRA_CREATE_PERMS; + for(i=0;iis_peer)) { + if (turn_create_permission(verbose, clnet_info2, &relay_addr1, 1) < 0) { + exit(-1); + } + if(rare_event()) return 0; + } + if (!no_rtcp) + if (turn_create_permission(verbose, clnet_info2_rtcp, &relay_addr1_rtcp, 1) < 0) { + exit(-1); + } + if(rare_event()) return 0; + } + + addr_cpy(&(clnet_info1->peer_addr), &relay_addr2); + if(!no_rtcp) + addr_cpy(&(clnet_info1_rtcp->peer_addr), &relay_addr2_rtcp); + addr_cpy(&(clnet_info2->peer_addr), &relay_addr1); + if(!no_rtcp) + addr_cpy(&(clnet_info2_rtcp->peer_addr), &relay_addr1_rtcp); + + return 0; +} + +//////////// RFC 6062 /////////////// + +int turn_tcp_connect(int verbose, app_ur_conn_info *clnet_info, ioa_addr *peer_addr) { + + { + int cp_sent = 0; + + stun_buffer message; + + stun_init_request(STUN_METHOD_CONNECT, &message); + stun_attr_add_addr(&message, STUN_ATTRIBUTE_XOR_PEER_ADDRESS, peer_addr); + + if(add_integrity(clnet_info, &message)<0) return -1; + + stun_attr_add_fingerprint_str(message.buf,(size_t*)&(message.len)); + + while (!cp_sent) { + + int len = send_buffer(clnet_info, &message, 0,0); + + if (len > 0) { + if (verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "tcp connect sent\n"); + } + cp_sent = 1; + } else { + perror("send"); + exit(1); + } + } + } + + ////////////<<==connect send + + return 0; +} + +static int turn_tcp_connection_bind(int verbose, app_ur_conn_info *clnet_info, app_tcp_conn_info *atc, int errorOK) { + + beg_cb: + + { + int cb_sent = 0; + + stun_buffer message; + u32bits cid = atc->cid; + + stun_init_request(STUN_METHOD_CONNECTION_BIND, &message); + + stun_attr_add(&message, STUN_ATTRIBUTE_CONNECTION_ID, (const s08bits*)&cid,4); + + if(add_integrity(clnet_info, &message)<0) return -1; + + stun_attr_add_fingerprint_str(message.buf,(size_t*)&(message.len)); + + while (!cb_sent) { + + int len = send_buffer(clnet_info, &message, 1, atc); + + if (len > 0) { + if (verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "connection bind sent\n"); + } + cb_sent = 1; + } else { + if(errorOK) + return 0; + perror("send"); + exit(1); + } + } + } + + ////////////<<==connection bind send + + if(not_rare_event()) return 0; + + ////////connection bind response==>> + + { + int cb_received = 0; + stun_buffer message; + while (!cb_received) { + + int len = recv_buffer(clnet_info, &message, 1, atc); + if (len > 0) { + if (verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "connect bind response received: \n"); + } + int err_code = 0; + u08bits err_msg[129]; + if (stun_is_success_response(&message)) { + + if(clnet_info->nonce[0] || use_short_term) { + SHATYPE sht = clnet_info->shatype; + if(stun_check_message_integrity_str(get_turn_credentials_type(), + message.buf, (size_t)(message.len), g_uname, + clnet_info->realm, g_upwd, sht)<1) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"Wrong integrity in connect bind message received from server\n"); + return -1; + } + } + + if(stun_get_method(&message)!=STUN_METHOD_CONNECTION_BIND) + continue; + cb_received = 1; + if (verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "success\n"); + } + atc->tcp_data_bound = 1; + } else if (stun_is_challenge_response_str(message.buf, (size_t)message.len, + &err_code,err_msg,sizeof(err_msg), + clnet_info->realm,clnet_info->nonce)) { + if(err_code == SHA_TOO_WEAK && (clnet_info->shatype == SHATYPE_SHA1)) { + clnet_info->shatype = SHATYPE_SHA256; + recalculate_restapi_hmac(); + } + goto beg_cb; + } else if (stun_is_error_response(&message, &err_code,err_msg,sizeof(err_msg))) { + cb_received = 1; + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "connection bind error %d (%s)\n", + err_code,(char*)err_msg); + return -1; + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "unknown connection bind response\n"); + /* Try again ? */ + } + } else { + if(errorOK) + return 0; + perror("recv"); + exit(-1); + } + } + } + + return 0; +} + +void tcp_data_connect(app_ur_session *elem, u32bits cid) +{ + int clnet_fd = socket(elem->pinfo.remote_addr.ss.sa_family, SOCK_STREAM, 0); + if (clnet_fd < 0) { + perror("socket"); + exit(-1); + } + + if (sock_bind_to_device(clnet_fd, client_ifname) < 0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "Cannot bind client socket to device %s\n", client_ifname); + } + set_sock_buf_size(clnet_fd, (UR_CLIENT_SOCK_BUF_SIZE<<2)); + + ++elem->pinfo.tcp_conn_number; + int i = (int)(elem->pinfo.tcp_conn_number-1); + elem->pinfo.tcp_conn=(app_tcp_conn_info**)realloc(elem->pinfo.tcp_conn,elem->pinfo.tcp_conn_number*sizeof(app_tcp_conn_info*)); + elem->pinfo.tcp_conn[i]=(app_tcp_conn_info*)turn_malloc(sizeof(app_tcp_conn_info)); + ns_bzero(elem->pinfo.tcp_conn[i],sizeof(app_tcp_conn_info)); + + elem->pinfo.tcp_conn[i]->tcp_data_fd = clnet_fd; + elem->pinfo.tcp_conn[i]->cid = cid; + + addr_cpy(&(elem->pinfo.tcp_conn[i]->tcp_data_local_addr),&(elem->pinfo.local_addr)); + + addr_set_port(&(elem->pinfo.tcp_conn[i]->tcp_data_local_addr),0); + + addr_bind(clnet_fd, &(elem->pinfo.tcp_conn[i]->tcp_data_local_addr), 1); + + addr_get_from_sock(clnet_fd,&(elem->pinfo.tcp_conn[i]->tcp_data_local_addr)); + + { + int cycle = 0; + while(cycle++<1024) { + int err = 0; + if (addr_connect(clnet_fd, &(elem->pinfo.remote_addr),&err) < 0) { + if(err == EADDRINUSE) { + socket_closesocket(clnet_fd); + clnet_fd = socket(elem->pinfo.remote_addr.ss.sa_family, SOCK_STREAM, 0); + if (clnet_fd < 0) { + perror("socket"); + exit(-1); + } + if (sock_bind_to_device(clnet_fd, client_ifname) < 0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "Cannot bind client socket to device %s\n", client_ifname); + } + set_sock_buf_size(clnet_fd, UR_CLIENT_SOCK_BUF_SIZE<<2); + + elem->pinfo.tcp_conn[i]->tcp_data_fd = clnet_fd; + + addr_cpy(&(elem->pinfo.tcp_conn[i]->tcp_data_local_addr),&(elem->pinfo.local_addr)); + + addr_set_port(&(elem->pinfo.tcp_conn[i]->tcp_data_local_addr),0); + + addr_bind(clnet_fd, &(elem->pinfo.tcp_conn[i]->tcp_data_local_addr),1); + + addr_get_from_sock(clnet_fd,&(elem->pinfo.tcp_conn[i]->tcp_data_local_addr)); + + continue; + + } else { + perror("connect"); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "%s: cannot connect to remote addr\n", __FUNCTION__); + exit(-1); + } + } else { + break; + } + } + } + + if(use_secure) { + elem->pinfo.tcp_conn[i]->tcp_data_ssl = tls_connect(elem->pinfo.tcp_conn[i]->tcp_data_fd, &(elem->pinfo.remote_addr)); + if(!(elem->pinfo.tcp_conn[i]->tcp_data_ssl)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "%s: cannot SSL connect to remote addr\n", __FUNCTION__); + exit(-1); + } + } + + if(turn_tcp_connection_bind(clnet_verbose, &(elem->pinfo), elem->pinfo.tcp_conn[i],0)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "%s: cannot BIND to tcp connection\n", __FUNCTION__); + } else { + + socket_set_nonblocking(clnet_fd); + + struct event* ev = event_new(client_event_base,clnet_fd, + EV_READ|EV_PERSIST,client_input_handler, + elem); + + event_add(ev,NULL); + + elem->input_tcp_data_ev = ev; + + addr_debug_print(clnet_verbose, &(elem->pinfo.remote_addr), "TCP data network connected to"); + } +} + +///////////////////////////////////////////////// diff --git a/src/apps/uclient/startuclient.h b/src/apps/uclient/startuclient.h new file mode 100644 index 0000000..fbef34e --- /dev/null +++ b/src/apps/uclient/startuclient.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __STARTCLIENT_TURN__ +#define __STARTCLIENT_TURN__ + +#include "ns_turn_utils.h" +#include "session.h" + +#ifdef __cplusplus +extern "C" { +#endif + +///////////////////////////////////////////////////////// + +int rare_event(void); +int not_rare_event(void); + +int start_c2c_connection(uint16_t clnet_remote_port, + const char *remote_address, + const unsigned char* ifname, const char *local_address, + int verbose, + app_ur_conn_info *clnet_info_probe, + app_ur_conn_info *clnet_info1, + uint16_t *chn1, + app_ur_conn_info *clnet_info1_rtcp, + uint16_t *chn1_rtcp, + app_ur_conn_info *clnet_info2, + uint16_t *chn2, + app_ur_conn_info *clnet_info2_rtcp, + uint16_t *chn2_rtcp); + +int start_connection(uint16_t clnet_remote_port, + const char *remote_address, + const unsigned char* ifname, const char *local_address, + int verbose, + app_ur_conn_info *clnet_info_probe, + app_ur_conn_info *clnet_info, + uint16_t *chn, + app_ur_conn_info *clnet_info_rtcp, + uint16_t *chn_rtcp); + +int turn_tcp_connect(int verbose, app_ur_conn_info *clnet_info, ioa_addr *peer_addr); + +void tcp_data_connect(app_ur_session *elem, u32bits cid); + +int socket_connect(evutil_socket_t clnet_fd, ioa_addr *remote_addr, int *connect_err); + +int read_mobility_ticket(app_ur_conn_info *clnet_info, stun_buffer *message); + +//////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif //__STARTCLIENT_TURN__ + diff --git a/src/apps/uclient/uclient.c b/src/apps/uclient/uclient.c new file mode 100644 index 0000000..6e23aa6 --- /dev/null +++ b/src/apps/uclient/uclient.c @@ -0,0 +1,1433 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "apputils.h" +#include "uclient.h" +#include "startuclient.h" +#include "ns_turn_utils.h" +#include "session.h" + +#include +#include + +#include + +static int verbose_packets=0; + +static size_t current_clients_number = 0; + +static int start_full_timer=0; +static u32bits tot_messages=0; +static u32bits tot_send_messages=0; +static u64bits tot_send_bytes = 0; +static u32bits tot_recv_messages=0; +static u64bits tot_recv_bytes = 0; +static u64bits tot_send_dropped = 0; + +struct event_base* client_event_base=NULL; + +static int client_write(app_ur_session *elem); +static int client_shutdown(app_ur_session *elem); + +static u64bits current_time = 0; +static u64bits current_mstime = 0; + +static char buffer_to_send[65536]="\0"; + +static int total_clients = 0; + +/* Patch for unlimited number of clients provided by ucudbm@gmail.com */ +static app_ur_session** elems = NULL; + +#define SLEEP_INTERVAL (234) + +int RTP_PACKET_INTERVAL = 20; + +static inline s64bits time_minus(u64bits t1, u64bits t2) { + return ( (s64bits)t1 - (s64bits)t2 ); +} + +static u64bits total_loss = 0; +static u64bits total_jitter = 0; +static u64bits total_latency = 0; + +static u64bits min_latency = 0xFFFFFFFF; +static u64bits max_latency = 0; +static u64bits min_jitter = 0xFFFFFFFF; +static u64bits max_jitter = 0; + + +static int show_statistics = 0; + +/////////////////////////////////////////////////////////////////////////////// + +static void __turn_getMSTime(void) { + static u64bits start_sec = 0; + struct timespec tp={0,0}; +#if defined(CLOCK_REALTIME) + clock_gettime(CLOCK_REALTIME, &tp); +#else + tp.tv_sec = time(NULL); +#endif + if(!start_sec) + start_sec = tp.tv_sec; + if(current_time != (u64bits)((u64bits)(tp.tv_sec)-start_sec)) + show_statistics = 1; + current_time = (u64bits)((u64bits)(tp.tv_sec)-start_sec); + current_mstime = (u64bits)((current_time * 1000) + (tp.tv_nsec/1000000)); +} + +//////////////////////////////////////////////////////////////////// + +static int refresh_channel(app_ur_session* elem, u16bits method, uint32_t lt); + +//////////////////////// SS //////////////////////////////////////// + +static app_ur_session* init_app_session(app_ur_session *ss) { + if(ss) { + ns_bzero(ss,sizeof(app_ur_session)); + ss->pinfo.fd=-1; + ss->pinfo.shatype = shatype; + } + return ss; +} + +static app_ur_session* create_new_ss(void) +{ + ++current_clients_number; + return init_app_session((app_ur_session*) turn_malloc(sizeof(app_ur_session))); +} + +static void uc_delete_session_elem_data(app_ur_session* cdi) { + if(cdi) { + EVENT_DEL(cdi->input_ev); + EVENT_DEL(cdi->input_tcp_data_ev); + if(cdi->pinfo.tcp_conn) { + int i = 0; + for(i=0;i<(int)(cdi->pinfo.tcp_conn_number);++i) { + if(cdi->pinfo.tcp_conn[i]) { + if(cdi->pinfo.tcp_conn[i]->tcp_data_ssl && !(cdi->pinfo.broken)) { + if(!(SSL_get_shutdown(cdi->pinfo.tcp_conn[i]->tcp_data_ssl) & SSL_SENT_SHUTDOWN)) { + SSL_set_shutdown(cdi->pinfo.tcp_conn[i]->tcp_data_ssl, SSL_RECEIVED_SHUTDOWN); + SSL_shutdown(cdi->pinfo.tcp_conn[i]->tcp_data_ssl); + } + if(cdi->pinfo.tcp_conn[i]->tcp_data_ssl) { + SSL_free(cdi->pinfo.tcp_conn[i]->tcp_data_ssl); + cdi->pinfo.tcp_conn[i]->tcp_data_ssl = NULL; + } + if(cdi->pinfo.tcp_conn[i]->tcp_data_fd>=0) { + socket_closesocket(cdi->pinfo.tcp_conn[i]->tcp_data_fd); + cdi->pinfo.tcp_conn[i]->tcp_data_fd=-1; + } + turn_free(cdi->pinfo.tcp_conn[i], 111); + cdi->pinfo.tcp_conn[i]=NULL; + } + } + } + cdi->pinfo.tcp_conn_number=0; + turn_free(cdi->pinfo.tcp_conn, 111); + cdi->pinfo.tcp_conn=NULL; + } + if(cdi->pinfo.ssl && !(cdi->pinfo.broken)) { + if(!(SSL_get_shutdown(cdi->pinfo.ssl) & SSL_SENT_SHUTDOWN)) { + SSL_set_shutdown(cdi->pinfo.ssl, SSL_RECEIVED_SHUTDOWN); + SSL_shutdown(cdi->pinfo.ssl); + } + } + if(cdi->pinfo.ssl) { + SSL_free(cdi->pinfo.ssl); + cdi->pinfo.ssl = NULL; + } + if(cdi->pinfo.fd>=0) { + socket_closesocket(cdi->pinfo.fd); + } + cdi->pinfo.fd=-1; + } +} + +static int remove_all_from_ss(app_ur_session* ss) +{ + if (ss) { + uc_delete_session_elem_data(ss); + + --current_clients_number; + } + + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +int send_buffer(app_ur_conn_info *clnet_info, stun_buffer* message, int data_connection, app_tcp_conn_info *atc) +{ + + int rc = 0; + int ret = -1; + + char *buffer = (char*) (message->buf); + + if(negative_protocol_test && (message->len>0)) { + if(random()%10 == 0) { + int np = (int)((unsigned long)random()%10); + while(np-->0) { + int pos = (int)((unsigned long)random()%(unsigned long)message->len); + int val = (int)((unsigned long)random()%256); + message->buf[pos]=(u08bits)val; + } + } + } + + SSL *ssl = clnet_info->ssl; + ioa_socket_raw fd = clnet_info->fd; + + if(data_connection) { + if(atc) { + ssl = atc->tcp_data_ssl; + fd = atc->tcp_data_fd; + } else if(is_TCP_relay()){ + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "trying to send tcp data buffer over unready connection: size=%d\n",(int)(message->len)); + return -1; + } + } + + if (ssl) { + + int message_sent = 0; + while (!message_sent) { + + if (SSL_get_shutdown(ssl)) { + return -1; + } + + int len = 0; + do { + len = SSL_write(ssl, buffer, (int)message->len); + } while (len < 0 && ((errno == EINTR) || (errno == ENOBUFS))); + + if(len == (int)message->len) { + if (clnet_verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "buffer sent: size=%d\n",len); + } + + message_sent = 1; + ret = len; + } else { + switch (SSL_get_error(ssl, len)){ + case SSL_ERROR_NONE: + /* Try again ? */ + break; + case SSL_ERROR_WANT_WRITE: + /* Just try again later */ + break; + case SSL_ERROR_WANT_READ: + /* continue with reading */ + break; + case SSL_ERROR_ZERO_RETURN: + /* Try again */ + break; + case SSL_ERROR_SYSCALL: + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"Socket write error 111.666: \n"); + if (handle_socket_error()) + break; + case SSL_ERROR_SSL: + { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "SSL write error: \n"); + char buf[1024]; + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "%s (%d)\n", + ERR_error_string(ERR_get_error(),buf), + SSL_get_error(ssl, len)); + } + default: + clnet_info->broken = 1; + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"Unexpected error while writing!\n"); + return -1; + } + } + } + + } else if (fd >= 0) { + + size_t left = (size_t) (message->len); + + while (left > 0) { + do { + rc = send(fd, buffer, left, 0); + } while (rc < 0 && ((errno == EINTR) || (errno == ENOBUFS))); + if (rc > 0) { + left -= (size_t) rc; + buffer += rc; + } else { + tot_send_dropped+=1; + break; + } + } + + if (left > 0) + return -1; + + ret = (int) message->len; + } + + return ret; +} + +int recv_buffer(app_ur_conn_info *clnet_info, stun_buffer* message, int sync, + app_tcp_conn_info *atc) { + + int rc = 0; + + ioa_socket_raw fd = clnet_info->fd; + if (atc) + fd = atc->tcp_data_fd; + + SSL* ssl = clnet_info->ssl; + if (atc) + ssl = atc->tcp_data_ssl; + + if (!use_secure && !use_tcp && fd >= 0) { + + /* Plain UDP */ + + do { + rc = recv(fd, message->buf, sizeof(message->buf) - 1, 0); + if (rc < 0 && errno == EAGAIN && sync) + errno = EINTR; + } while (rc < 0 && (errno == EINTR)); + + if (rc < 0) { + return -1; + } + + message->len = rc; + + } else if (use_secure && ssl && !(clnet_info->broken)) { + + /* TLS/DTLS */ + + int message_received = 0; + int cycle = 0; + while (!message_received && cycle++ < 100) { + + if (SSL_get_shutdown(ssl)) + return -1; + + rc = 0; + do { + rc = SSL_read(ssl, message->buf, sizeof(message->buf) - 1); + if (rc < 0 && errno == EAGAIN && sync) + continue; + } while (rc < 0 && (errno == EINTR)); + + if (rc > 0) { + + if (clnet_verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "response received: size=%d\n", rc); + } + message->len = rc; + message_received = 1; + + } else { + + int sslerr = SSL_get_error(ssl, rc); + + switch (sslerr) { + case SSL_ERROR_NONE: + /* Try again ? */ + break; + case SSL_ERROR_WANT_WRITE: + /* Just try again later */ + break; + case SSL_ERROR_WANT_READ: + /* continue with reading */ + break; + case SSL_ERROR_ZERO_RETURN: + /* Try again */ + break; + case SSL_ERROR_SYSCALL: + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "Socket read error 111.999: \n"); + if (handle_socket_error()) + break; + case SSL_ERROR_SSL: { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "SSL write error: \n"); + char buf[1024]; + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s (%d)\n", + ERR_error_string(ERR_get_error(), buf), + SSL_get_error(ssl, rc)); + } + default: + clnet_info->broken = 1; + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "Unexpected error while reading: rc=%d, sslerr=%d\n", + rc, sslerr); + return -1; + } + + if (!sync) + break; + } + } + + } else if (!use_secure && use_tcp && fd >= 0) { + + /* Plain TCP */ + + do { + rc = recv(fd, message->buf, sizeof(message->buf) - 1, MSG_PEEK); + if ((rc < 0) && (errno == EAGAIN) && sync) { + errno = EINTR; + } + } while (rc < 0 && (errno == EINTR)); + + if (rc > 0) { + int mlen = rc; + size_t app_msg_len = (size_t) rc; + if (!atc) { + mlen = stun_get_message_len_str(message->buf, rc, 1, + &app_msg_len); + } else { + if (!sync) + mlen = clmessage_length; + + if (mlen > clmessage_length) + mlen = clmessage_length; + + app_msg_len = (size_t) mlen; + } + + if (mlen > 0) { + + int rcr = 0; + int rsf = 0; + int cycle = 0; + while (rsf < mlen && cycle++ < 128) { + do { + rcr = recv(fd, message->buf + rsf, + (size_t) mlen - (size_t) rsf, 0); + if (rcr < 0 && errno == EAGAIN && sync) + errno = EINTR; + } while (rcr < 0 && (errno == EINTR)); + + if (rcr > 0) + rsf += rcr; + + } + + if (rsf < 1) + return -1; + + if (rsf < (int) app_msg_len) { + if ((size_t) (app_msg_len / (size_t) rsf) * ((size_t) (rsf)) + != app_msg_len) { + return -1; + } + } + + message->len = app_msg_len; + + rc = app_msg_len; + + } else { + rc = 0; + } + } + } + + return rc; +} + +static int client_read(app_ur_session *elem, int is_tcp_data, app_tcp_conn_info *atc) { + + if (!elem) + return -1; + + if (elem->state != UR_STATE_READY) + return -1; + + elem->ctime = current_time; + + app_ur_conn_info *clnet_info = &(elem->pinfo); + int err_code = 0; + u08bits err_msg[129]; + int rc = 0; + int applen = 0; + + if (clnet_verbose && verbose_packets) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "before read ...\n"); + } + + rc = recv_buffer(clnet_info, &(elem->in_buffer), 0, atc); + + if (clnet_verbose && verbose_packets) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "read %d bytes\n", (int) rc); + } + + if (rc > 0) { + + elem->in_buffer.len = rc; + + uint16_t chnumber = 0; + + const message_info *mi = NULL; + + size_t buffers = 1; + + if(is_tcp_data) { + if ((int)elem->in_buffer.len == clmessage_length) { + mi = (message_info*)(elem->in_buffer.buf); + } + } else if (stun_is_indication(&(elem->in_buffer))) { + + if(use_short_term) { + SHATYPE sht = elem->pinfo.shatype; + if(stun_check_message_integrity_str(get_turn_credentials_type(), + elem->in_buffer.buf, (size_t)(elem->in_buffer.len), g_uname, + elem->pinfo.realm, g_upwd, sht)<1) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"Wrong integrity in indication message 0x%x received from server\n",(unsigned int)stun_get_method(&(elem->in_buffer))); + return -1; + } + } + + uint16_t method = stun_get_method(&elem->in_buffer); + + if((method == STUN_METHOD_CONNECTION_ATTEMPT)&& is_TCP_relay()) { + stun_attr_ref sar = stun_attr_get_first(&(elem->in_buffer)); + u32bits cid = 0; + while(sar) { + int attr_type = stun_attr_get_type(sar); + if(attr_type == STUN_ATTRIBUTE_CONNECTION_ID) { + cid = *((const u32bits*)stun_attr_get_value(sar)); + break; + } + sar = stun_attr_get_next_str(elem->in_buffer.buf,elem->in_buffer.len,sar); + } + if(negative_test) { + tcp_data_connect(elem,(u64bits)random()); + } else { + /* positive test */ + tcp_data_connect(elem,cid); + } + return rc; + } else if (method != STUN_METHOD_DATA) { + TURN_LOG_FUNC( + TURN_LOG_LEVEL_INFO, + "ERROR: received indication message has wrong method: 0x%x\n", + (int) method); + return rc; + } else { + + stun_attr_ref sar = stun_attr_get_first_by_type(&(elem->in_buffer), STUN_ATTRIBUTE_DATA); + if (!sar) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "ERROR: received DATA message has no data, size=%d\n", rc); + return rc; + } + + int rlen = stun_attr_get_len(sar); + applen = rlen; + if (rlen != clmessage_length) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "ERROR: received DATA message has wrong len: %d, must be %d\n", rlen, clmessage_length); + tot_recv_bytes += applen; + return rc; + } + + const u08bits* data = stun_attr_get_value(sar); + + mi = (const message_info*) data; + } + + } else if (stun_is_success_response(&(elem->in_buffer))) { + + if(elem->pinfo.nonce[0] || use_short_term) { + SHATYPE sht = elem->pinfo.shatype; + if(stun_check_message_integrity_str(get_turn_credentials_type(), + elem->in_buffer.buf, (size_t)(elem->in_buffer.len), g_uname, + elem->pinfo.realm, g_upwd, sht)<1) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"Wrong integrity in success message 0x%x received from server\n",(unsigned int)stun_get_method(&(elem->in_buffer))); + return -1; + } + } + + if(is_TCP_relay() && (stun_get_method(&(elem->in_buffer)) == STUN_METHOD_CONNECT)) { + stun_attr_ref sar = stun_attr_get_first(&(elem->in_buffer)); + u32bits cid = 0; + while(sar) { + int attr_type = stun_attr_get_type(sar); + if(attr_type == STUN_ATTRIBUTE_CONNECTION_ID) { + cid = *((const u32bits*)stun_attr_get_value(sar)); + break; + } + sar = stun_attr_get_next_str(elem->in_buffer.buf,elem->in_buffer.len,sar); + } + tcp_data_connect(elem,cid); + } + + return rc; + } else if (stun_is_challenge_response_str(elem->in_buffer.buf, (size_t)elem->in_buffer.len, + &err_code,err_msg,sizeof(err_msg), + clnet_info->realm,clnet_info->nonce)) { + if(err_code == SHA_TOO_WEAK && (elem->pinfo.shatype == SHATYPE_SHA1)) { + elem->pinfo.shatype = SHATYPE_SHA256; + recalculate_restapi_hmac(); + } + + if(is_TCP_relay() && (stun_get_method(&(elem->in_buffer)) == STUN_METHOD_CONNECT)) { + turn_tcp_connect(clnet_verbose, &(elem->pinfo), &(elem->pinfo.peer_addr)); + } else if(stun_get_method(&(elem->in_buffer)) == STUN_METHOD_REFRESH) { + refresh_channel(elem, stun_get_method(&elem->in_buffer),600); + } + return rc; + } else if (stun_is_error_response(&(elem->in_buffer), NULL,NULL,0)) { + return rc; + } else if (stun_is_channel_message(&(elem->in_buffer), &chnumber, use_tcp)) { + if (elem->chnum != chnumber) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "ERROR: received message has wrong channel: %d\n", + (int) chnumber); + return rc; + } + + if (elem->in_buffer.len >= 4) { + if (((int)(elem->in_buffer.len-4) < clmessage_length) || + ((int)(elem->in_buffer.len-4) > clmessage_length + 3)) { + TURN_LOG_FUNC( + TURN_LOG_LEVEL_INFO, + "ERROR: received buffer have wrong length: %d, must be %d, len=%d\n", + rc, clmessage_length + 4,(int)elem->in_buffer.len); + return rc; + } + + mi = (message_info*)(elem->in_buffer.buf + 4); + applen = elem->in_buffer.len -4; + } + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "ERROR: Unknown message received of size: %d\n",(int)(elem->in_buffer.len)); + return rc; + } + + if(mi) { + /* + printf("%s: 111.111: msgnum=%d, rmsgnum=%d, sent=%lu, recv=%lu\n",__FUNCTION__, + mi->msgnum,elem->recvmsgnum,(unsigned long)mi->mstime,(unsigned long)current_mstime); + */ + if(mi->msgnum != elem->recvmsgnum+1) + ++(elem->loss); + else { + u64bits clatency = (u64bits)time_minus(current_mstime,mi->mstime); + if(clatency>max_latency) + max_latency = clatency; + if(clatencylatency += clatency; + if(elem->rmsgnum>0) { + u64bits cjitter = abs((int)(current_mstime-elem->recvtimems)-RTP_PACKET_INTERVAL); + + if(cjitter>max_jitter) + max_jitter = cjitter; + if(cjitterjitter += cjitter; + } + } + + elem->recvmsgnum = mi->msgnum; + } + + elem->rmsgnum+=buffers; + tot_recv_messages+=buffers; + if(applen > 0) + tot_recv_bytes += applen; + else + tot_recv_bytes += elem->in_buffer.len; + elem->recvtimems=current_mstime; + elem->wait_cycles = 0; + + } else if(rc == 0) { + return 0; + } else { + return -1; + } + + return rc; +} + +static int client_shutdown(app_ur_session *elem) { + + if(!elem) return -1; + + elem->state=UR_STATE_DONE; + + elem->ctime=current_time; + + remove_all_from_ss(elem); + + if (clnet_verbose) + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"done, connection 0x%lx closed.\n",(long)elem); + + return 0; +} + +static int client_write(app_ur_session *elem) { + + if(!elem) return -1; + + if(elem->state!=UR_STATE_READY) return -1; + + elem->ctime=current_time; + + message_info *mi = (message_info*)buffer_to_send; + mi->msgnum=elem->wmsgnum; + mi->mstime=current_mstime; + app_tcp_conn_info *atc=NULL; + + if (is_TCP_relay()) { + + memcpy(elem->out_buffer.buf, buffer_to_send, clmessage_length); + elem->out_buffer.len = clmessage_length; + + if(elem->pinfo.is_peer) { + if(send(elem->pinfo.fd, elem->out_buffer.buf, clmessage_length, 0)>=0) { + ++elem->wmsgnum; + elem->to_send_timems += RTP_PACKET_INTERVAL; + tot_send_messages++; + tot_send_bytes += clmessage_length; + } + return 0; + } + + if (!(elem->pinfo.tcp_conn) || !(elem->pinfo.tcp_conn_number)) { + return -1; + } + int i = (unsigned int)(random()) % elem->pinfo.tcp_conn_number; + atc = elem->pinfo.tcp_conn[i]; + if(!atc->tcp_data_bound) { + printf("%s: Uninitialized atc: i=%d, atc=0x%lx\n",__FUNCTION__,i,(long)atc); + return -1; + } + } else if(!do_not_use_channel) { + /* Let's always do padding: */ + stun_init_channel_message(elem->chnum, &(elem->out_buffer), clmessage_length, mandatory_channel_padding || use_tcp); + memcpy(elem->out_buffer.buf+4,buffer_to_send,clmessage_length); + } else { + stun_init_indication(STUN_METHOD_SEND, &(elem->out_buffer)); + stun_attr_add(&(elem->out_buffer), STUN_ATTRIBUTE_DATA, buffer_to_send, clmessage_length); + stun_attr_add_addr(&(elem->out_buffer),STUN_ATTRIBUTE_XOR_PEER_ADDRESS, &(elem->pinfo.peer_addr)); + if(dont_fragment) + stun_attr_add(&(elem->out_buffer), STUN_ATTRIBUTE_DONT_FRAGMENT, NULL, 0); + + if (use_short_term) { + if(add_integrity(&(elem->pinfo), &(elem->out_buffer))<0) return -1; + } + + if(use_fingerprints) + stun_attr_add_fingerprint_str(elem->out_buffer.buf,(size_t*)&(elem->out_buffer.len)); + } + + if (elem->out_buffer.len > 0) { + + if (clnet_verbose && verbose_packets) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "before write ...\n"); + } + + int rc=send_buffer(&(elem->pinfo),&(elem->out_buffer),1,atc); + + ++elem->wmsgnum; + elem->to_send_timems += RTP_PACKET_INTERVAL; + + if(rc >= 0) { + if (clnet_verbose && verbose_packets) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "wrote %d bytes\n", (int) rc); + } + tot_send_messages++; + tot_send_bytes += clmessage_length; + } else { + return -1; + } + } + + return 0; +} + +void client_input_handler(evutil_socket_t fd, short what, void* arg) { + + if(!(what&EV_READ)||!arg) return; + + UNUSED_ARG(fd); + + app_ur_session* elem = (app_ur_session*)arg; + if(!elem) { + return; + } + + switch(elem->state) { + case UR_STATE_READY: + do { + app_tcp_conn_info *atc = NULL; + int is_tcp_data = 0; + if(elem->pinfo.tcp_conn) { + int i = 0; + for(i=0;i<(int)(elem->pinfo.tcp_conn_number);++i) { + if(elem->pinfo.tcp_conn[i]) { + if((fd==elem->pinfo.tcp_conn[i]->tcp_data_fd) && (elem->pinfo.tcp_conn[i]->tcp_data_bound)) { + is_tcp_data = 1; + atc = elem->pinfo.tcp_conn[i]; + break; + } + } + } + } + int rc = client_read(elem, is_tcp_data, atc); + if(rc<=0) break; + } while(1); + + break; + default: + ; + } +} + +static void run_events(int short_burst) +{ + struct timeval timeout; + + if(!short_burst) { + timeout.tv_sec = 1; + timeout.tv_usec = 0; + } else { + timeout.tv_sec = 0; + timeout.tv_usec = 100000; + } + + event_base_loopexit(client_event_base, &timeout); + + event_base_dispatch(client_event_base); +} + +////////////////////// main method ///////////////// + +static int start_client(const char *remote_address, int port, + const unsigned char* ifname, const char *local_address, + int messagenumber, + int i) { + + app_ur_session* ss=create_new_ss(); + app_ur_session* ss_rtcp=NULL; + + if(!no_rtcp) + ss_rtcp = create_new_ss(); + + app_ur_conn_info clnet_info_probe; /* for load balancing probe */ + ns_bzero(&clnet_info_probe,sizeof(clnet_info_probe)); + clnet_info_probe.fd = -1; + clnet_info_probe.shatype = shatype; + + app_ur_conn_info *clnet_info=&(ss->pinfo); + app_ur_conn_info *clnet_info_rtcp=NULL; + + if(!no_rtcp) + clnet_info_rtcp = &(ss_rtcp->pinfo); + + uint16_t chnum=0; + uint16_t chnum_rtcp=0; + + start_connection(port, remote_address, + ifname, local_address, + clnet_verbose, + &clnet_info_probe, + clnet_info, &chnum, + clnet_info_rtcp, &chnum_rtcp); + + if(clnet_info_probe.ssl) { + SSL_free(clnet_info_probe.ssl); + clnet_info_probe.ssl = NULL; + clnet_info_probe.fd = -1; + } else if(clnet_info_probe.fd != -1) { + socket_closesocket(clnet_info_probe.fd); + clnet_info_probe.fd = -1; + } + + socket_set_nonblocking(clnet_info->fd); + + if(!no_rtcp) + socket_set_nonblocking(clnet_info_rtcp->fd); + + struct event* ev = event_new(client_event_base,clnet_info->fd, + EV_READ|EV_PERSIST,client_input_handler, + ss); + + event_add(ev,NULL); + + struct event* ev_rtcp = NULL; + + if(!no_rtcp) { + ev_rtcp = event_new(client_event_base,clnet_info_rtcp->fd, + EV_READ|EV_PERSIST,client_input_handler, + ss_rtcp); + + event_add(ev_rtcp,NULL); + } + + ss->state=UR_STATE_READY; + + ss->input_ev=ev; + ss->tot_msgnum=messagenumber; + ss->recvmsgnum=-1; + ss->chnum=chnum; + + if(!no_rtcp) { + + ss_rtcp->state=UR_STATE_READY; + + ss_rtcp->input_ev=ev_rtcp; + ss_rtcp->tot_msgnum=ss->tot_msgnum; + if(ss_rtcp->tot_msgnum<1) ss_rtcp->tot_msgnum=1; + ss_rtcp->recvmsgnum=-1; + ss_rtcp->chnum=chnum_rtcp; + } + + elems[i]=ss; + + refresh_channel(ss, 0, 600); + + if(!no_rtcp) + elems[i+1]=ss_rtcp; + + return 0; +} + +static int start_c2c(const char *remote_address, int port, + const unsigned char* ifname, const char *local_address, + int messagenumber, + int i) { + + app_ur_session* ss1=create_new_ss(); + app_ur_session* ss1_rtcp=NULL; + + if(!no_rtcp) + ss1_rtcp = create_new_ss(); + + app_ur_session* ss2=create_new_ss(); + app_ur_session* ss2_rtcp=NULL; + + if(!no_rtcp) + ss2_rtcp = create_new_ss(); + + app_ur_conn_info clnet_info_probe; /* for load balancing probe */ + ns_bzero(&clnet_info_probe,sizeof(clnet_info_probe)); + clnet_info_probe.fd = -1; + clnet_info_probe.shatype = shatype; + + app_ur_conn_info *clnet_info1=&(ss1->pinfo); + app_ur_conn_info *clnet_info1_rtcp=NULL; + + if(!no_rtcp) + clnet_info1_rtcp = &(ss1_rtcp->pinfo); + + app_ur_conn_info *clnet_info2=&(ss2->pinfo); + app_ur_conn_info *clnet_info2_rtcp=NULL; + + if(!no_rtcp) + clnet_info2_rtcp = &(ss2_rtcp->pinfo); + + uint16_t chnum1=0; + uint16_t chnum1_rtcp=0; + uint16_t chnum2=0; + uint16_t chnum2_rtcp=0; + + start_c2c_connection(port, remote_address, + ifname, local_address, + clnet_verbose, + &clnet_info_probe, + clnet_info1, &chnum1, + clnet_info1_rtcp, &chnum1_rtcp, + clnet_info2, &chnum2, + clnet_info2_rtcp, &chnum2_rtcp); + + if(clnet_info_probe.ssl) { + SSL_free(clnet_info_probe.ssl); + clnet_info_probe.ssl = NULL; + clnet_info_probe.fd = -1; + } else if(clnet_info_probe.fd != -1) { + socket_closesocket(clnet_info_probe.fd); + clnet_info_probe.fd = -1; + } + + socket_set_nonblocking(clnet_info1->fd); + + if(!no_rtcp) + socket_set_nonblocking(clnet_info1_rtcp->fd); + + socket_set_nonblocking(clnet_info2->fd); + + if(!no_rtcp) + socket_set_nonblocking(clnet_info2_rtcp->fd); + + struct event* ev1 = event_new(client_event_base,clnet_info1->fd, + EV_READ|EV_PERSIST,client_input_handler, + ss1); + + event_add(ev1,NULL); + + struct event* ev1_rtcp = NULL; + + if(!no_rtcp) { + ev1_rtcp = event_new(client_event_base,clnet_info1_rtcp->fd, + EV_READ|EV_PERSIST,client_input_handler, + ss1_rtcp); + + event_add(ev1_rtcp,NULL); + } + + struct event* ev2 = event_new(client_event_base,clnet_info2->fd, + EV_READ|EV_PERSIST,client_input_handler, + ss2); + + event_add(ev2,NULL); + + struct event* ev2_rtcp = NULL; + + if(!no_rtcp) { + ev2_rtcp = event_new(client_event_base,clnet_info2_rtcp->fd, + EV_READ|EV_PERSIST,client_input_handler, + ss2_rtcp); + + event_add(ev2_rtcp,NULL); + } + + ss1->state=UR_STATE_READY; + + ss1->input_ev=ev1; + ss1->tot_msgnum=messagenumber; + ss1->recvmsgnum=-1; + ss1->chnum=chnum1; + + if(!no_rtcp) { + + ss1_rtcp->state=UR_STATE_READY; + + ss1_rtcp->input_ev=ev1_rtcp; + ss1_rtcp->tot_msgnum=ss1->tot_msgnum; + if(ss1_rtcp->tot_msgnum<1) ss1_rtcp->tot_msgnum=1; + ss1_rtcp->recvmsgnum=-1; + ss1_rtcp->chnum=chnum1_rtcp; + } + + ss2->state=UR_STATE_READY; + + ss2->input_ev=ev2; + ss2->tot_msgnum=ss1->tot_msgnum; + ss2->recvmsgnum=-1; + ss2->chnum=chnum2; + + if(!no_rtcp) { + ss2_rtcp->state=UR_STATE_READY; + + ss2_rtcp->input_ev=ev2_rtcp; + ss2_rtcp->tot_msgnum=ss1_rtcp->tot_msgnum; + ss2_rtcp->recvmsgnum=-1; + ss2_rtcp->chnum=chnum2_rtcp; + } + + elems[i++]=ss1; + if(!no_rtcp) + elems[i++]=ss1_rtcp; + elems[i++]=ss2; + if(!no_rtcp) + elems[i++]=ss2_rtcp; + + return 0; +} + +static int refresh_channel(app_ur_session* elem, u16bits method, uint32_t lt) +{ + + stun_buffer message; + app_ur_conn_info *clnet_info = &(elem->pinfo); + + if(clnet_info->is_peer) + return 0; + + if (!method || (method == STUN_METHOD_REFRESH)) { + stun_init_request(STUN_METHOD_REFRESH, &message); + lt = htonl(lt); + stun_attr_add(&message, STUN_ATTRIBUTE_LIFETIME, (const char*) <, 4); + if(add_integrity(clnet_info, &message)<0) return -1; + if(use_fingerprints) + stun_attr_add_fingerprint_str(message.buf, (size_t*) &(message.len)); + send_buffer(clnet_info, &message, 0,0); + } + + if (lt && !addr_any(&(elem->pinfo.peer_addr))) { + + if(!no_permissions) { + if (!method || (method == STUN_METHOD_CREATE_PERMISSION)) { + stun_init_request(STUN_METHOD_CREATE_PERMISSION, &message); + stun_attr_add_addr(&message, STUN_ATTRIBUTE_XOR_PEER_ADDRESS, &(elem->pinfo.peer_addr)); + if(add_integrity(clnet_info, &message)<0) return -1; + if(use_fingerprints) + stun_attr_add_fingerprint_str(message.buf, (size_t*) &(message.len)); + send_buffer(&(elem->pinfo), &message, 0,0); + } + } + + if (!method || (method == STUN_METHOD_CHANNEL_BIND)) { + if (STUN_VALID_CHANNEL(elem->chnum)) { + stun_set_channel_bind_request(&message, &(elem->pinfo.peer_addr), elem->chnum); + if(add_integrity(clnet_info, &message)<0) return -1; + if(use_fingerprints) + stun_attr_add_fingerprint_str(message.buf, (size_t*) &(message.len)); + send_buffer(&(elem->pinfo), &message,1,0); + } + } + } + + elem->refresh_time = current_mstime + 30*1000; + + return 0; +} + +static inline int client_timer_handler(app_ur_session* elem) +{ + if (elem) { + if (!turn_time_before(current_mstime, elem->refresh_time)) { + refresh_channel(elem, 0, 600); + } + + if(hang_on && elem->completed) + return 0; + + if (!turn_time_before(current_mstime, elem->to_send_timems)) { + if (elem->wmsgnum >= elem->tot_msgnum) { + if (!turn_time_before(current_mstime, elem->finished_time) || + (tot_recv_messages>=tot_messages)) { + /* + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"%s: elem=0x%x: 111.111: c=%d, t=%d, r=%d, w=%d\n",__FUNCTION__,(int)elem,elem->wait_cycles,elem->tot_msgnum,elem->rmsgnum,elem->wmsgnum); + */ + /* + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"%s: 111.222: ly=%llu, ls=%llu, j=%llu\n",__FUNCTION__, + (unsigned long long)elem->latency, + (unsigned long long)elem->loss, + (unsigned long long)elem->jitter); + */ + total_loss += elem->loss; + elem->loss=0; + total_latency += elem->latency; + elem->latency=0; + total_jitter += elem->jitter; + elem->jitter=0; + elem->completed = 1; + if (!hang_on) { + refresh_channel(elem,0,0); + client_shutdown(elem); + return 1; + } else { + return 0; + } + } + } else { + client_write(elem); + elem->finished_time = current_mstime + STOPPING_TIME*1000; + } + } + } + + return 0; +} + +static void timer_handler(evutil_socket_t fd, short event, void *arg) +{ + UNUSED_ARG(fd); + UNUSED_ARG(event); + UNUSED_ARG(arg); + + __turn_getMSTime(); + + if(start_full_timer) { + int i = 0; + for (i = 0; i < total_clients; ++i) { + if (elems[i]) { + int finished = client_timer_handler(elems[i]); + if (finished) { + elems[i] = NULL; + } + } + } + } +} + +void start_mclient(const char *remote_address, int port, + const unsigned char* ifname, const char *local_address, + int messagenumber, int mclient) { + + if (mclient < 1) + mclient = 1; + + total_clients = mclient; + + if(c2c) { + //mclient must be a multiple of 4: + if(!no_rtcp) + mclient += ((4 - (mclient & 0x00000003)) & 0x00000003); + else if(mclient & 0x1) + ++mclient; + } else { + if(!no_rtcp) + if(mclient & 0x1) + ++mclient; + } + + elems = (app_ur_session**)malloc(sizeof(app_ur_session)*((mclient*2)+1)+sizeof(void*)); + + __turn_getMSTime(); + u32bits stime = current_time; + + memset(buffer_to_send, 7, clmessage_length); + + client_event_base = turn_event_base_new(); + + int i = 0; + int tot_clients = 0; + + if(c2c) { + if(!no_rtcp) + for (i = 0; i < (mclient >> 2); i++) { + if(!dos) usleep(SLEEP_INTERVAL); + if (start_c2c(remote_address, port, ifname, local_address, + messagenumber, i << 2) < 0) { + exit(-1); + } + tot_clients+=4; + } + else + for (i = 0; i < (mclient >> 1); i++) { + if(!dos) usleep(SLEEP_INTERVAL); + if (start_c2c(remote_address, port, ifname, local_address, + messagenumber, i << 1) < 0) { + exit(-1); + } + tot_clients+=2; + } + } else { + if(!no_rtcp) + for (i = 0; i < (mclient >> 1); i++) { + if(!dos) usleep(SLEEP_INTERVAL); + if (start_client(remote_address, port, ifname, local_address, + messagenumber, i << 1) < 0) { + exit(-1); + } + tot_clients+=2; + } + else + for (i = 0; i < mclient; i++) { + if(!dos) usleep(SLEEP_INTERVAL); + if (start_client(remote_address, port, ifname, local_address, + messagenumber, i) < 0) { + exit(-1); + } + tot_clients++; + } + } + + if(dos) + _exit(0); + + total_clients = tot_clients; + + __turn_getMSTime(); + + struct event *ev = event_new(client_event_base, -1, EV_TIMEOUT|EV_PERSIST, timer_handler, NULL); + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 1000; + + evtimer_add(ev,&tv); + + for(i=0;ipinfo.is_peer) { + int connect_err = 0; + socket_connect(elems[i]->pinfo.fd, &(elems[i]->pinfo.remote_addr), &connect_err); + } + } else if((i%2) == 0) { + if (turn_tcp_connect(clnet_verbose, &(elems[i]->pinfo), &(elems[i]->pinfo.peer_addr)) < 0) { + exit(-1); + } + } + } + run_events(1); + } + + __turn_getMSTime(); + + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Total connect time is %u\n", + ((unsigned int) (current_time - stime))); + + stime = current_time; + + if(is_TCP_relay()) { + u64bits connect_wait_start_time = current_time; + while(1) { + int i = 0; + int completed = 0; + if(passive_tcp) { + for(i=0;ipinfo.is_peer) { + completed+=1; + } else if(elems[i]->pinfo.tcp_conn_number>0 && + elems[i]->pinfo.tcp_conn[0]->tcp_data_bound) { + completed += elems[i]->pinfo.tcp_conn_number; + } + } + if(completed >= total_clients) + break; + } else { + for(i=0;ipinfo.tcp_conn_number>0 && + elems[i]->pinfo.tcp_conn[0]->tcp_data_bound) { + completed += elems[i]->pinfo.tcp_conn_number; + } + } + if(completed >= total_clients) + break; + } + run_events(0); + if(current_time > connect_wait_start_time + STARTING_TCP_RELAY_TIME) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_WARNING, "WARNING: %d connections are not completed\n", + (int)(total_clients - completed)); + break; + } + } + } + + __turn_getMSTime(); + stime = current_time; + + for(i=0;ito_send_timems = current_mstime + 1000 + ((u32bits)random())%5000; + } + + tot_messages = elems[0]->tot_msgnum * total_clients; + + start_full_timer = 1; + + while (1) { + + run_events(1); + + int msz = (int)current_clients_number; + if (msz < 1) { + break; + } + + if(show_statistics) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "%s: msz=%d, tot_send_msgs=%lu, tot_recv_msgs=%lu, tot_send_bytes ~ %llu, tot_recv_bytes ~ %llu\n", + __FUNCTION__, msz, (unsigned long) tot_send_messages, + (unsigned long) tot_recv_messages, + (unsigned long long) tot_send_bytes, + (unsigned long long) tot_recv_bytes); + show_statistics=0; + } + } + + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "%s: tot_send_msgs=%lu, tot_recv_msgs=%lu\n", + __FUNCTION__, + (unsigned long) tot_send_messages, + (unsigned long) tot_recv_messages); + + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "%s: tot_send_bytes ~ %lu, tot_recv_bytes ~ %lu\n", + __FUNCTION__, + (unsigned long) tot_send_bytes, + (unsigned long) tot_recv_bytes); + + if (client_event_base) + event_base_free(client_event_base); + + if(tot_send_messagesbuf, (size_t*)&(message->len), g_uname, g_upwd, clnet_info->shatype)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO," Cannot add integrity to the message\n"); + return -1; + } + } else if(clnet_info->nonce[0]) { + if(stun_attr_add_integrity_by_user_str(message->buf, (size_t*)&(message->len), g_uname, + clnet_info->realm, g_upwd, clnet_info->nonce, clnet_info->shatype)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO," Cannot add integrity to the message\n"); + return -1; + } + } + + return 0; +} + +/////////////////////////////////////////// + diff --git a/src/apps/uclient/uclient.h b/src/apps/uclient/uclient.h new file mode 100644 index 0000000..00ffdc0 --- /dev/null +++ b/src/apps/uclient/uclient.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __UCLIENT_ECHO__ +#define __UCLIENT_ECHO__ + +#include "ns_turn_utils.h" +#include "stun_buffer.h" +#include "session.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////// + +#define STOPPING_TIME (5) +#define STARTING_TCP_RELAY_TIME (10) + +extern int clmessage_length; +extern int do_not_use_channel; +extern int clnet_verbose; +extern int use_tcp; +extern int use_secure; +extern int use_short_term; +extern char cert_file[1025]; +extern char pkey_file[1025]; +extern int hang_on; +extern int c2c; +extern ioa_addr peer_addr; +extern int no_rtcp; +extern int default_address_family; +extern int dont_fragment; +extern u08bits g_uname[STUN_MAX_USERNAME_SIZE+1]; +extern st_password_t g_upwd; +extern char g_auth_secret[1025]; +extern int g_use_auth_secret_with_timestamp; +extern int use_fingerprints; +extern SSL_CTX *root_tls_ctx[32]; +extern int root_tls_ctx_num; +extern int RTP_PACKET_INTERVAL; +extern u08bits relay_transport; +extern unsigned char client_ifname[1025]; +extern struct event_base* client_event_base; +extern int passive_tcp; +extern int mandatory_channel_padding; +extern int negative_test; +extern int negative_protocol_test; +extern int dos; +extern SHATYPE shatype; +extern int mobility; +extern int no_permissions; +extern int extra_requests; + +extern char origin[STUN_MAX_ORIGIN_SIZE+1]; + +#define is_TCP_relay() (relay_transport == STUN_ATTRIBUTE_TRANSPORT_TCP_VALUE) + +void start_mclient(const char *remote_address, int port, + const unsigned char* ifname, const char *local_address, + int messagenumber, int mclient); + +int send_buffer(app_ur_conn_info *clnet_info, stun_buffer* message, int data_connection, app_tcp_conn_info *atc); +int recv_buffer(app_ur_conn_info *clnet_info, stun_buffer* message, int sync, app_tcp_conn_info *atc); + +void client_input_handler(evutil_socket_t fd, short what, void* arg); + +turn_credential_type get_turn_credentials_type(void); + +int add_integrity(app_ur_conn_info *clnet_info, stun_buffer *message); + +void recalculate_restapi_hmac(void); + +//////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif //__UCLIENT_ECHO__ + diff --git a/src/client++/TurnMsgLib.h b/src/client++/TurnMsgLib.h new file mode 100644 index 0000000..db97acc --- /dev/null +++ b/src/client++/TurnMsgLib.h @@ -0,0 +1,1195 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __LIB_TURN_MSG_CPP__ +#define __LIB_TURN_MSG_CPP__ + +#include "ns_turn_ioaddr.h" +#include "ns_turn_msg.h" + +#include + +namespace turn { + +class StunAttr; + +/** + * Exception "end of buffer" + */ +class EndOfStunMsgException { +public: + EndOfStunMsgException() {} + virtual ~EndOfStunMsgException() {} +}; + +/** + * Exception "wrong format of StunAttr" + */ +class WrongStunAttrFormatException { +public: + WrongStunAttrFormatException() {} + virtual ~WrongStunAttrFormatException() {} +}; + +/** + * Exception "wrong format of StunBuffer" + */ +class WrongStunBufferFormatException { +public: + WrongStunBufferFormatException() {} + virtual ~WrongStunBufferFormatException() {} +}; + +/** + * Iterator class for attributes + */ +class StunAttrIterator { +public: + /** + * Iterator constructor: creates iterator on raw messagebuffer. + */ + StunAttrIterator(u08bits *buf, size_t sz) throw (WrongStunBufferFormatException) : + _buf(buf), _sz(sz) { + if(!stun_is_command_message_str(_buf, _sz)) { + throw WrongStunBufferFormatException(); + } + _sar = stun_attr_get_first_str(_buf, _sz); + } + + /** + * Iterator constructor: create iterator over message. + */ + template + StunAttrIterator(T &msg) throw (WrongStunBufferFormatException) : + _buf(msg.getRawBuffer()), _sz(msg.getSize()) { + if(!stun_is_command_message_str(_buf, _sz)) { + throw WrongStunBufferFormatException(); + } + _sar = stun_attr_get_first_str(_buf, _sz); + } + + /** + * Iterator constructor: creates iterator over raw buffer, starting from first + * location of an attribute of particular type. + */ + StunAttrIterator(u08bits *buf, size_t sz, u16bits attr_type) throw (WrongStunBufferFormatException) : + _buf(buf), _sz(sz) { + if(!stun_is_command_message_str(_buf, _sz)) { + throw WrongStunBufferFormatException(); + } + _sar = stun_attr_get_first_by_type_str(_buf, _sz, attr_type); + } + + /** + * Iterator constructor: creates iterator over message, starting from first + * location of an attribute of particular type. + */ + template + StunAttrIterator(T &msg, u16bits attr_type) throw (WrongStunBufferFormatException) : + _buf(msg.getRawBuffer()), _sz(msg.getSize()) { + if(!stun_is_command_message_str(_buf, _sz)) { + throw WrongStunBufferFormatException(); + } + _sar = stun_attr_get_first_by_type_str(_buf, _sz, attr_type); + } + + /** + * Moves iterator to next attribute location + */ + void next() throw(EndOfStunMsgException) { + if(!_sar) { + throw EndOfStunMsgException(); + } + _sar = stun_attr_get_next_str(_buf,_sz,_sar); + } + + /** + * Is the iterator finished + */ + bool eof() const { + return (!_sar); + } + + /** + * Is the iterator at an address attribute + */ + bool isAddr() const { + return stun_attr_is_addr(_sar); + } + + /** + * Return address family attribute value (if the iterator at the "address family" attribute. + */ + int getAddressFamily() const { + return stun_get_requested_address_family(_sar); + } + + /** + * Get attribute type + */ + int getType() const { + return stun_attr_get_type(_sar); + } + + /** + * Destructor + */ + virtual ~StunAttrIterator() {} + + /** + * Return raw memroy field of the attribute value. + * If the attribute value length is zero (0), then return NULL. + */ + const u08bits *getRawBuffer(size_t &sz) const throw(WrongStunAttrFormatException) { + int len = stun_attr_get_len(_sar); + if(len<0) + throw WrongStunAttrFormatException(); + sz = (size_t)len; + const u08bits *value = stun_attr_get_value(_sar); + return value; + } + friend class StunAttr; +private: + u08bits *_buf; + size_t _sz; + stun_attr_ref _sar; +}; + +/** + * Root class of all STUN attributes. + * Can be also used for a generic attribute object. + */ +class StunAttr { +public: + /** + * Empty constructor + */ + StunAttr() : _attr_type(0), _value(0), _sz(0) {} + + /** + * Constructs attribute from iterator + */ + StunAttr(const StunAttrIterator &iter) throw(WrongStunAttrFormatException, EndOfStunMsgException) { + if(iter.eof()) { + throw EndOfStunMsgException(); + } + size_t sz = 0; + const u08bits *ptr = iter.getRawBuffer(sz); + if(sz>=0xFFFF) + throw WrongStunAttrFormatException(); + int at = iter.getType(); + if(at<0) + throw WrongStunAttrFormatException(); + _attr_type = (u16bits)at; + _sz = sz; + _value=(u08bits*)turn_malloc(_sz); + if(ptr) + ns_bcopy(ptr,_value,_sz); + } + + /** + * Destructor + */ + virtual ~StunAttr() { + if(_value) + turn_free(_value,_sz); + } + + /** + * Return raw data representation of the attribute + */ + const u08bits *getRawValue(size_t &sz) const { + sz=_sz; + return _value; + } + + /** + * Set raw data value + */ + void setRawValue(u08bits *value, size_t sz) throw(WrongStunAttrFormatException) { + if(sz>0xFFFF) + throw WrongStunAttrFormatException(); + if(_value) + turn_free(_value,_sz); + _sz = sz; + _value=(u08bits*)turn_malloc(_sz); + if(value) + ns_bcopy(value,_value,_sz); + } + + /** + * Get attribute type + */ + u16bits getType() const { + return _attr_type; + } + + /** + * Set attribute type + */ + void setType(u16bits at) { + _attr_type = at; + } + + /** + * Add attribute to a message + */ + template + int addToMsg(T &msg) throw(WrongStunAttrFormatException, WrongStunBufferFormatException) { + if(!_attr_type) + throw WrongStunAttrFormatException(); + u08bits *buffer = msg.getRawBuffer(); + if(buffer) { + size_t sz = msg.getSize(); + if(addToBuffer(buffer, sz)<0) { + throw WrongStunBufferFormatException(); + } + msg.setSize(sz); + return 0; + } + throw WrongStunBufferFormatException(); + } +protected: + + /** + * Virtual function member to add attribute to a raw buffer + */ + virtual int addToBuffer(u08bits *buffer, size_t &sz) throw(WrongStunAttrFormatException, WrongStunBufferFormatException) { + if(buffer) { + if(!_value) + throw WrongStunAttrFormatException(); + if(stun_attr_add_str(buffer, &sz, _attr_type, _value, _sz)<0) { + throw WrongStunBufferFormatException(); + } + return 0; + } + throw WrongStunBufferFormatException(); + } + + /** + * Get low-level iterator object + */ + static stun_attr_ref getSar(const StunAttrIterator &iter) { + return iter._sar; + } +private: + u16bits _attr_type; + u08bits *_value; + size_t _sz; +}; + +/** + * Channel number attribute class + */ +class StunAttrChannelNumber : public StunAttr { +public: + StunAttrChannelNumber() : _cn(0) { + setType(STUN_ATTRIBUTE_CHANNEL_NUMBER); + } + StunAttrChannelNumber(const StunAttrIterator &iter) + throw(WrongStunAttrFormatException, EndOfStunMsgException) : + StunAttr(iter) { + + if(iter.eof()) + throw EndOfStunMsgException(); + _cn = stun_attr_get_channel_number(getSar(iter)); + if(!_cn) + throw WrongStunAttrFormatException(); + } + virtual ~StunAttrChannelNumber() {} + u16bits getChannelNumber() const { + return _cn; + } + void setChannelNumber(u16bits cn) { + _cn = cn; + } +protected: + virtual int addToBuffer(u08bits *buffer, size_t &sz) throw(WrongStunAttrFormatException, WrongStunBufferFormatException) { + return stun_attr_add_channel_number_str(buffer,&sz,_cn); + } +private: + u16bits _cn; +}; + +/** + * Even port attribute class + */ +class StunAttrEvenPort : public StunAttr { +public: + StunAttrEvenPort() : _ep(0) { + setType(STUN_ATTRIBUTE_EVEN_PORT); + } + StunAttrEvenPort(const StunAttrIterator &iter) + throw(WrongStunAttrFormatException, EndOfStunMsgException) : + StunAttr(iter) { + + if(iter.eof()) + throw EndOfStunMsgException(); + _ep = stun_attr_get_even_port(getSar(iter)); + } + virtual ~StunAttrEvenPort() {} + u08bits getEvenPort() const { + return _ep; + } + void setEvenPort(u08bits ep) { + _ep = ep; + } +protected: + virtual int addToBuffer(u08bits *buffer, size_t &sz) throw(WrongStunAttrFormatException, WrongStunBufferFormatException) { + return stun_attr_add_str(buffer, &sz, STUN_ATTRIBUTE_EVEN_PORT, &_ep, 1); + } +private: + u08bits _ep; +}; + +/** + * Reservation token attribute class + */ +class StunAttrReservationToken : public StunAttr { +public: + StunAttrReservationToken() : _rt(0) { + setType(STUN_ATTRIBUTE_RESERVATION_TOKEN); + } + StunAttrReservationToken(const StunAttrIterator &iter) + throw(WrongStunAttrFormatException, EndOfStunMsgException) : + StunAttr(iter) { + + if(iter.eof()) + throw EndOfStunMsgException(); + _rt = stun_attr_get_reservation_token_value(getSar(iter)); + } + virtual ~StunAttrReservationToken() {} + u64bits getReservationToken() const { + return _rt; + } + void setReservationToken(u64bits rt) { + _rt = rt; + } +protected: + virtual int addToBuffer(u08bits *buffer, size_t &sz) throw(WrongStunAttrFormatException, WrongStunBufferFormatException) { + uint64_t reservation_token = ioa_ntoh64(_rt); + return stun_attr_add_str(buffer, &sz, STUN_ATTRIBUTE_RESERVATION_TOKEN, (u08bits*) (&reservation_token), 8); + } +private: + u64bits _rt; +}; + +/** + * This attribute class is used for all address attributes + */ +class StunAttrAddr : public StunAttr { +public: + StunAttrAddr(u16bits attr_type = 0) { + addr_set_any(&_addr); + setType(attr_type); + } + StunAttrAddr(const StunAttrIterator &iter) + throw(WrongStunAttrFormatException, EndOfStunMsgException) : + StunAttr(iter) { + + if(iter.eof()) + throw EndOfStunMsgException(); + size_t sz = 0; + const u08bits *buf = iter.getRawBuffer(sz); + if(stun_attr_get_addr_str(buf,sz,getSar(iter),&_addr,NULL)<0) { + throw WrongStunAttrFormatException(); + } + } + virtual ~StunAttrAddr() {} + void getAddr(ioa_addr &addr) const { + addr_cpy(&addr,&_addr); + } + void setAddr(ioa_addr &addr) { + addr_cpy(&_addr,&addr); + } +protected: + virtual int addToBuffer(u08bits *buffer, size_t &sz) throw(WrongStunAttrFormatException, WrongStunBufferFormatException) { + return stun_attr_add_addr_str(buffer, &sz, getType(), &_addr); + } +private: + ioa_addr _addr; +}; + +/** + * Change Request attribute class + */ +class StunAttrChangeRequest : public StunAttr { +public: + StunAttrChangeRequest() : _changeIp(0), _changePort(0) { + setType(STUN_ATTRIBUTE_CHANGE_REQUEST); + } + StunAttrChangeRequest(const StunAttrIterator &iter) + throw(WrongStunAttrFormatException, EndOfStunMsgException) : + StunAttr(iter) { + + if(iter.eof()) + throw EndOfStunMsgException(); + + if(stun_attr_get_change_request_str(getSar(iter), &_changeIp, &_changePort)<0) { + throw WrongStunAttrFormatException(); + } + } + virtual ~StunAttrChangeRequest() {} + bool getChangeIp() const { + return _changeIp; + } + void setChangeIp(bool ci) { + if(ci) + _changeIp = 1; + else + _changeIp = 0; + } + bool getChangePort() const { + return _changePort; + } + void setChangePort(bool cp) { + if(cp) + _changePort = 1; + else + _changePort = 0; + } +protected: + virtual int addToBuffer(u08bits *buffer, size_t &sz) throw(WrongStunAttrFormatException, WrongStunBufferFormatException) { + return stun_attr_add_change_request_str(buffer, &sz, _changeIp, _changePort); + } +private: + int _changeIp; + int _changePort; +}; + +/** + * Change Request attribute class + */ +class StunAttrResponsePort : public StunAttr { +public: + StunAttrResponsePort() : _rp(0) { + setType(STUN_ATTRIBUTE_RESPONSE_PORT); + } + StunAttrResponsePort(const StunAttrIterator &iter) + throw(WrongStunAttrFormatException, EndOfStunMsgException) : + StunAttr(iter) { + + if(iter.eof()) + throw EndOfStunMsgException(); + + int rp = stun_attr_get_response_port_str(getSar(iter)); + if(rp<0) { + throw WrongStunAttrFormatException(); + } + _rp = (u16bits)rp; + } + virtual ~StunAttrResponsePort() {} + u16bits getResponsePort() const { + return _rp; + } + void setResponsePort(u16bits p) { + _rp = p; + } +protected: + virtual int addToBuffer(u08bits *buffer, size_t &sz) throw(WrongStunAttrFormatException, WrongStunBufferFormatException) { + return stun_attr_add_response_port_str(buffer, &sz, _rp); + } +private: + u16bits _rp; +}; + +/** + * Padding attribute class + */ +class StunAttrPadding : public StunAttr { +public: + StunAttrPadding() : _p(0) { + setType(STUN_ATTRIBUTE_PADDING); + } + StunAttrPadding(const StunAttrIterator &iter) + throw(WrongStunAttrFormatException, EndOfStunMsgException) : + StunAttr(iter) { + + if(iter.eof()) + throw EndOfStunMsgException(); + + int p = stun_attr_get_padding_len_str(getSar(iter)); + if(p<0) { + throw WrongStunAttrFormatException(); + } + _p = (u16bits)p; + } + virtual ~StunAttrPadding() {} + u16bits getPadding() const { + return _p; + } + /** + * Set length of padding + */ + void setPadding(u16bits p) { + _p = p; + } +protected: + virtual int addToBuffer(u08bits *buffer, size_t &sz) throw(WrongStunAttrFormatException, WrongStunBufferFormatException) { + return stun_attr_add_padding_str(buffer, &sz, _p); + } +private: + u16bits _p; +}; + +/** + * Generic "STUN Message" class, base class for all messages + */ +class StunMsg { +public: + /** + * Empty constructor + */ + StunMsg() { + _allocated_sz = 0xFFFF; + _buffer = (u08bits*)turn_malloc(_allocated_sz); + _deallocate = true; + _sz = 0; + _constructed = 0; + } + + /** + * Construct message over raw buffer. + * Parameter "construct" is true if the buffer is initialized. + */ + StunMsg(u08bits *buffer, size_t total_sz, size_t sz, bool constructed) : + _buffer(buffer), _deallocate(false), _allocated_sz(total_sz), + _sz(sz), _constructed(constructed) {} + + /** + * Destructor + */ + virtual ~StunMsg() { + if(_deallocate && _buffer) { + turn_free(_buffer, _allocated_sz); + } + } + + /** + * Initialize buffer + */ + void construct() { + constructBuffer(); + } + + /** + * Checks if the message is properly constructed + */ + bool isValid() { + return check(); + } + + /** + * get raw buffer + */ + u08bits *getRawBuffer() { + return _buffer; + } + + /** + * Get message size in the buffer (message can be mnuch smaller than the whole buffer) + */ + size_t getSize() const { + return _sz; + } + + /** + * Set message size + */ + void setSize(size_t sz) throw(WrongStunBufferFormatException) { + if(sz>_allocated_sz) + throw WrongStunBufferFormatException(); + _sz = sz; + } + + /** + * Check if the raw buffer is a TURN "command" (request, response or indication). + */ + static bool isCommand(u08bits *buffer, size_t sz) { + return stun_is_command_message_str(buffer, sz); + } + + /** + * Check if the current message object is a "command" (request, response, or indication). + */ + bool isCommand() const { + return stun_is_command_message_str(_buffer, _sz); + } + + static bool isIndication(u08bits *buffer, size_t sz) { + return stun_is_indication_str(buffer, sz); + } + + static bool isRequest(u08bits *buffer, size_t sz) { + return stun_is_request_str(buffer, sz); + } + + static bool isSuccessResponse(u08bits *buffer, size_t sz) { + return stun_is_success_response_str(buffer, sz); + } + + static bool isErrorResponse(u08bits *buffer, size_t sz, + int &err_code, u08bits *err_msg, size_t err_msg_size) { + return stun_is_error_response_str(buffer, sz, &err_code, err_msg, err_msg_size); + } + + /** + * Check if the raw buffer is a challenge response (the one with 401 error and realm and nonce values). + */ + static bool isChallengeResponse(const u08bits* buf, size_t sz, + int &err_code, u08bits *err_msg, size_t err_msg_size, + u08bits *realm, u08bits *nonce) { + return stun_is_challenge_response_str(buf, sz, &err_code, err_msg, err_msg_size, realm, nonce); + } + + /** + * Check if the message is a channel message + */ + static bool isChannel(u08bits *buffer, size_t sz) { + return is_channel_msg_str(buffer, sz); + } + + /** + * Check if the fingerprint is present. + */ + static bool isFingerprintPresent(u08bits *buffer, size_t sz) { + if(!stun_is_command_message_str(buffer,sz)) + return false; + stun_attr_ref sar = stun_attr_get_first_by_type_str(buffer, sz, STUN_ATTRIBUTE_FINGERPRINT); + if(!sar) + return false; + + return true; + } + + /** + * Check the fingerprint + */ + static bool checkFingerprint(u08bits *buffer, size_t sz) { + return stun_is_command_message_full_check_str(buffer, sz, 1, NULL); + } + + /** + * Add attribute to the message + */ + int addAttr(StunAttr &attr) throw(WrongStunAttrFormatException, WrongStunBufferFormatException) { + return attr.addToMsg(*this); + } + + /** + * Get transaction ID + */ + virtual stun_tid getTid() const throw(WrongStunBufferFormatException) { + if(!_constructed || !isCommand()) + throw WrongStunBufferFormatException(); + stun_tid tid; + stun_tid_from_message_str(_buffer,_sz,&tid); + return tid; + } + + /** + * Set transaction ID + */ + virtual void setTid(stun_tid &tid) throw(WrongStunBufferFormatException) { + if(!_constructed || !isCommand()) + throw WrongStunBufferFormatException(); + stun_tid_message_cpy(_buffer, &tid); + } + + /** + * Add fingerprint to the message + */ + void addFingerprint() throw(WrongStunBufferFormatException) { + if(!_constructed || !isCommand()) + throw WrongStunBufferFormatException(); + stun_attr_add_fingerprint_str(_buffer,&_sz); + } + + /** + * Check message integrity, in secure communications. + */ + bool checkMessageIntegrity(turn_credential_type ct, std::string &uname, std::string &realm, std::string &upwd) const + throw(WrongStunBufferFormatException) { + if(!_constructed || !isCommand()) + throw WrongStunBufferFormatException(); + u08bits *suname=(u08bits*)strdup(uname.c_str()); + u08bits *srealm=(u08bits*)strdup(realm.c_str()); + u08bits *supwd=(u08bits*)strdup(upwd.c_str()); + SHATYPE sht = SHATYPE_SHA1; + bool ret = (0< stun_check_message_integrity_str(ct,_buffer, _sz, suname, srealm, supwd, sht)); + free(suname); + free(srealm); + free(supwd); + return ret; + } + + /** + * Adds long-term message integrity data to the message. + */ + void addLTMessageIntegrity(std::string &uname, std::string &realm, std::string &upwd, std::string &nonce) + throw(WrongStunBufferFormatException) { + + if(!_constructed || !isCommand()) + throw WrongStunBufferFormatException(); + + u08bits *suname=(u08bits*)strdup(uname.c_str()); + u08bits *srealm=(u08bits*)strdup(realm.c_str()); + u08bits *supwd=(u08bits*)strdup(upwd.c_str()); + u08bits *snonce=(u08bits*)strdup(nonce.c_str()); + + stun_attr_add_integrity_by_user_str(_buffer, &_sz, suname, srealm, supwd, snonce, SHATYPE_SHA1); + + free(suname); + free(srealm); + free(supwd); + free(snonce); + } + + /** + * Adds short-term message integrity data to the message. + */ + void addSTMessageIntegrity(std::string &uname, std::string &upwd) + throw(WrongStunBufferFormatException) { + + if(!_constructed || !isCommand()) + throw WrongStunBufferFormatException(); + + u08bits *suname=(u08bits*)strdup(uname.c_str()); + u08bits *supwd=(u08bits*)strdup(upwd.c_str()); + + stun_attr_add_integrity_by_user_short_term_str(_buffer, &_sz, suname, supwd, SHATYPE_SHA1); + + free(suname); + free(supwd); + } + +protected: + virtual void constructBuffer() = 0; + virtual bool check() = 0; +protected: + u08bits *_buffer; + bool _deallocate; + size_t _allocated_sz; + size_t _sz; + bool _constructed; +}; + +/** + * Class that represents the "request" flavor of STUN/TURN messages. + */ +class StunMsgRequest : public StunMsg { +public: + StunMsgRequest(u16bits method) : _method(method) {}; + StunMsgRequest(u08bits *buffer, size_t total_sz, size_t sz, bool constructed) + throw(WrongStunBufferFormatException) : + StunMsg(buffer,total_sz,sz,constructed),_method(0) { + + if(constructed) { + if(!stun_is_request_str(buffer,sz)) { + throw WrongStunBufferFormatException(); + } + _method = stun_get_method_str(buffer,sz); + } + } + virtual ~StunMsgRequest() {} + + /** + * Get request method + */ + u16bits getMethod() const { + return _method; + } + + /** + * Set method + */ + void setMethod(u16bits method) { + _method = method; + } + + /** + * Construct binding request + */ + void constructBindingRequest() { + stun_set_binding_request_str(_buffer, &_sz); + } + + bool isBindingRequest() const { + return stun_is_binding_request_str(_buffer,_sz,0); + } + + /** + * Construct allocate request + */ + void constructAllocateRequest(u32bits lifetime, int address_family, u08bits transport, int mobile) { + stun_set_allocate_request_str(_buffer, &_sz, lifetime, address_family, transport, mobile); + } + + /** + * Construct channel bind request + */ + void constructChannelBindRequest(const ioa_addr &peer_addr, u16bits channel_number) { + stun_set_channel_bind_request_str(_buffer, &_sz, + &peer_addr, channel_number); + } + +protected: + virtual void constructBuffer() { + stun_init_request_str(_method,_buffer,&_sz); + _constructed = true; + } + + virtual bool check() { + if(!_constructed) + return false; + if(!stun_is_request_str(_buffer,_sz)) { + return false; + } + if(_method != stun_get_method_str(_buffer,_sz)) { + return false; + } + return true; + } + +private: + u16bits _method; +}; + +/** + * Class for STUN/TURN responses + */ +class StunMsgResponse : public StunMsg { +public: + StunMsgResponse(u16bits method, stun_tid &tid) : _method(method), _err(0), _reason(""), _tid(tid) {}; + StunMsgResponse(u16bits method, int error_code, std::string reason, stun_tid &tid) : + _method(method), _err(error_code), _reason(reason), _tid(tid) { + + }; + StunMsgResponse(u08bits *buffer, size_t total_sz, size_t sz, bool constructed) + throw(WrongStunBufferFormatException) : + StunMsg(buffer,total_sz,sz,constructed),_method(0),_err(0),_reason("") { + + if(constructed) { + if(!stun_is_success_response_str(buffer,sz)) { + u08bits errtxt[0xFFFF]; + if(!stun_is_error_response_str(buffer,sz,&_err,errtxt,sizeof(errtxt))) { + throw WrongStunBufferFormatException(); + } + _reason = (char*)errtxt; + } + _method = stun_get_method_str(buffer,sz); + stun_tid_from_message_str(_buffer,_sz,&_tid); + } + } + + u16bits getMethod() const { + return _method; + } + + void setMethod(u16bits method) { + _method = method; + } + + /** + * Get error code + */ + int getError() const { + return _err; + } + + /** + * Set error code + */ + void setError(int err) { + _err = err; + } + + /** + * Get error message + */ + std::string getReason() const { + return _reason; + } + + /** + * Set error message + */ + void setReason(std::string reason) { + _reason = reason; + } + + /** + * Set transaction ID + */ + void setTid(stun_tid &tid) throw(WrongStunBufferFormatException) { + _tid = tid; + } + + /** + * Get transaction ID + */ + virtual stun_tid getTid() const throw(WrongStunBufferFormatException) { + return _tid; + } + + /** + * Check if this is a challenge response, and return realm and nonce + */ + bool isChallenge(std::string &realm, std::string &nonce) const { + bool ret = false; + if(_constructed) { + int err_code; + u08bits err_msg[1025]; + size_t err_msg_size=sizeof(err_msg); + u08bits srealm[0xFFFF]; + u08bits snonce[0xFFFF]; + ret = stun_is_challenge_response_str(_buffer, _sz, &err_code, err_msg, err_msg_size, srealm, snonce); + if(ret) { + realm = (char*)srealm; + nonce = (char*)snonce; + } + } + return ret; + } + + bool isChallenge() const { + std::string realm, nonce; + return isChallenge(realm, nonce); + } + + /** + * Check if this is a success response + */ + bool isSuccess() const { + return (_err == 0); + } + + /** + * Construct binding response + */ + void constructBindingResponse(stun_tid &tid, + const ioa_addr &reflexive_addr, int error_code, + const u08bits *reason) { + + stun_set_binding_response_str(_buffer, &_sz, &tid, + &reflexive_addr, error_code, + reason, 0 , 0); + } + + bool isBindingResponse() const { + return stun_is_binding_response_str(_buffer,_sz); + } + + /** + * Construct allocate response + */ + void constructAllocateResponse(stun_tid &tid, + const ioa_addr &relayed_addr, + const ioa_addr &reflexive_addr, + u32bits lifetime, int error_code, const u08bits *reason, + u64bits reservation_token, char *mobile_id) { + + stun_set_allocate_response_str(_buffer, &_sz, &tid, + &relayed_addr, + &reflexive_addr, + lifetime, error_code, reason, + reservation_token, mobile_id); + } + + /** + * Construct channel bind response + */ + void constructChannelBindResponse(stun_tid &tid, int error_code, const u08bits *reason) { + stun_set_channel_bind_response_str(_buffer, &_sz, &tid, error_code, reason); + } + +protected: + virtual void constructBuffer() { + if(_err) { + stun_init_error_response_str(_method, _buffer, &_sz, _err, (const u08bits*)_reason.c_str(), &_tid); + } else { + stun_init_success_response_str(_method, _buffer, &_sz, &_tid); + } + _constructed = true; + } + + virtual bool check() { + if(!_constructed) + return false; + if(!stun_is_success_response_str(_buffer,_sz)) { + u08bits errtxt[0xFFFF]; + int cerr=0; + if(!stun_is_error_response_str(_buffer,_sz,&cerr,errtxt,sizeof(errtxt))) { + throw WrongStunBufferFormatException(); + } + if(cerr != _err) { + throw WrongStunBufferFormatException(); + } + } + if(_method != stun_get_method_str(_buffer,_sz)) { + return false; + } + return true; + } + +private: + u16bits _method; + int _err; + std::string _reason; + stun_tid _tid; +}; + +/** + * Class for STUN/TURN indications + */ +class StunMsgIndication : public StunMsg { +public: + StunMsgIndication(u16bits method) : _method(method) {}; + StunMsgIndication(u08bits *buffer, size_t total_sz, size_t sz, bool constructed) + throw(WrongStunBufferFormatException) : + StunMsg(buffer,total_sz,sz,constructed),_method(0) { + + if(constructed) { + if(!stun_is_indication_str(buffer,sz)) { + throw WrongStunBufferFormatException(); + } + _method = stun_get_method_str(buffer,sz); + } + } + virtual ~StunMsgIndication() {} + + u16bits getMethod() const { + return _method; + } + + void setMethod(u16bits method) { + _method = method; + } + +protected: + virtual void constructBuffer() { + stun_init_indication_str(_method,_buffer,&_sz); + _constructed = true; + } + + virtual bool check() { + if(!_constructed) + return false; + if(!stun_is_indication_str(_buffer,_sz)) { + return false; + } + if(_method != stun_get_method_str(_buffer,_sz)) { + return false; + } + return true; + } + +private: + u16bits _method; +}; + +/** + * Channel message + */ +class StunMsgChannel : public StunMsg { +public: + StunMsgChannel(u16bits cn, int length) : _cn(cn), _len(length) {}; + StunMsgChannel(u08bits *buffer, size_t total_sz, size_t sz, bool constructed) + throw(WrongStunBufferFormatException) : + StunMsg(buffer,total_sz,sz,constructed),_cn(0) { + + if(constructed) { + if(!stun_is_channel_message_str(buffer,&_sz,&_cn,0)) { + throw WrongStunBufferFormatException(); + } + if(_sz>0xFFFF || _sz<4) + throw WrongStunBufferFormatException(); + + _len = _sz-4; + } else { + if(total_sz>0xFFFF || total_sz<4) + throw WrongStunBufferFormatException(); + + _len = 0; + } + } + virtual ~StunMsgChannel() {} + + u16bits getChannelNumber() const { + return _cn; + } + + void setChannelNumber(u16bits cn) { + _cn = cn; + } + + /** + * Get length of message itself (excluding the 4 channel number bytes) + */ + size_t getLength() const { + return _len; + } + + /** + * Set length of message itself (excluding the 4 channel number bytes) + */ + void setLength(size_t len) { + _len = len; + } + +protected: + virtual void constructBuffer() { + stun_init_channel_message_str(_cn,_buffer,&_sz,(int)_len,0); + _constructed = true; + } + + virtual bool check() { + if(!_constructed) + return false; + u16bits cn = 0; + if(!stun_is_channel_message_str(_buffer,&_sz,&cn,0)) { + return false; + } + if(_cn != cn) { + return false; + } + return true; + } + +private: + u16bits _cn; + size_t _len; +}; + +}; +/* namespace */ + +#endif +/* __LIB_TURN_MSG_CPP__ */ diff --git a/src/client/ns_turn_ioaddr.c b/src/client/ns_turn_ioaddr.c new file mode 100644 index 0000000..ef55ab1 --- /dev/null +++ b/src/client/ns_turn_ioaddr.c @@ -0,0 +1,477 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "ns_turn_ioaddr.h" + +////////////////////////////////////////////////////////////// + +u32bits get_ioa_addr_len(const ioa_addr* addr) { + if(addr->ss.sa_family == AF_INET) return sizeof(struct sockaddr_in); + else if(addr->ss.sa_family == AF_INET6) return sizeof(struct sockaddr_in6); + return 0; +} + +/////////////////////////////////////////////////////////////// + +void addr_set_any(ioa_addr *addr) { + if(addr) + ns_bzero(addr,sizeof(ioa_addr)); +} + +int addr_any(const ioa_addr* addr) { + + if(!addr) + return 1; + + if(addr->ss.sa_family == AF_INET) { + return ((addr->s4.sin_addr.s_addr==0)&&(addr->s4.sin_port==0)); + } else if(addr->ss.sa_family == AF_INET6) { + if(addr->s6.sin6_port!=0) return 0; + else { + size_t i; + for(i=0;is6.sin6_addr);i++) + if(((const s08bits*)&(addr->s6.sin6_addr))[i]) return 0; + } + } + + return 1; +} + +int addr_any_no_port(const ioa_addr* addr) { + if(!addr) + return 1; + + if(addr->ss.sa_family == AF_INET) { + return (addr->s4.sin_addr.s_addr==0); + } else if(addr->ss.sa_family == AF_INET6) { + size_t i; + for(i=0;is6.sin6_addr);i++) + if(((const s08bits*)(&(addr->s6.sin6_addr)))[i]) return 0; + } + + return 1; +} + +u32bits hash_int32(u32bits a) +{ + a = a ^ (a>>4); + a = (a^0xdeadbeef) + (a<<5); + a = a ^ (a>>11); + return a; +} + +u64bits hash_int64(u64bits a) +{ + a = a ^ (a>>4); + a = (a^0xdeadbeefdeadbeef) + (a<<5); + a = a ^ (a>>11); + return a; +} + +u32bits addr_hash(const ioa_addr *addr) +{ + if(!addr) + return 0; + + u32bits ret = 0; + if (addr->ss.sa_family == AF_INET) { + ret = hash_int32(addr->s4.sin_addr.s_addr + addr->s4.sin_port); + } else { + const u64bits *a = (const u64bits *) (&(addr->s6.sin6_addr)); + ret = (u32bits)((hash_int64(a[0])<<3) + (hash_int64(a[1] + addr->s6.sin6_port))); + } + return ret; +} + +u32bits addr_hash_no_port(const ioa_addr *addr) +{ + if(!addr) + return 0; + + u32bits ret = 0; + if (addr->ss.sa_family == AF_INET) { + ret = hash_int32(addr->s4.sin_addr.s_addr); + } else { + const u64bits *a = (const u64bits *) (&(addr->s6.sin6_addr)); + ret = (u32bits)((hash_int64(a[0])<<3) + (hash_int64(a[1]))); + } + return ret; +} + +void addr_cpy(ioa_addr* dst, const ioa_addr* src) { + if(dst && src) + ns_bcopy(src,dst,sizeof(ioa_addr)); +} + +void addr_cpy4(ioa_addr* dst, const struct sockaddr_in* src) { + if(src && dst) + ns_bcopy(src,dst,sizeof(struct sockaddr_in)); +} + +void addr_cpy6(ioa_addr* dst, const struct sockaddr_in6* src) { + if(src && dst) + ns_bcopy(src,dst,sizeof(struct sockaddr_in6)); +} + +int addr_eq(const ioa_addr* a1, const ioa_addr *a2) { + + if(!a1) return (!a2); + else if(!a2) return (!a1); + + if(a1->ss.sa_family == a2->ss.sa_family) { + if(a1->ss.sa_family == AF_INET && a1->s4.sin_port == a2->s4.sin_port) { + if((int)a1->s4.sin_addr.s_addr == (int)a2->s4.sin_addr.s_addr) { + return 1; + } + } else if(a1->ss.sa_family == AF_INET6 && a1->s6.sin6_port == a2->s6.sin6_port) { + const u64bits *p1=(const u64bits *)(&(a1->s6.sin6_addr)); + const u64bits *p2=(const u64bits *)(&(a2->s6.sin6_addr)); + if(p1[0]==p2[0] && p1[1]==p2[1]) { + return 1; + } + } + } + + return 0; +} + +int addr_eq_no_port(const ioa_addr* a1, const ioa_addr *a2) { + + if(!a1) return (!a2); + else if(!a2) return (!a1); + + if(a1->ss.sa_family == a2->ss.sa_family) { + if(a1->ss.sa_family == AF_INET) { + if((int)a1->s4.sin_addr.s_addr == (int)a2->s4.sin_addr.s_addr) { + return 1; + } + } else if(a1->ss.sa_family == AF_INET6) { + const u64bits *p1=(const u64bits *)(&(a1->s6.sin6_addr)); + const u64bits *p2=(const u64bits *)(&(a2->s6.sin6_addr)); + if(p1[0]==p2[0] && p1[1]==p2[1]) { + return 1; + } + } + } + return 0; +} + +int make_ioa_addr(const u08bits* saddr, int port, ioa_addr *addr) { + + if(!saddr || !addr) return -1; + + ns_bzero(addr, sizeof(ioa_addr)); + if((strlen((const s08bits*)saddr) == 0)|| + (inet_pton(AF_INET, (const s08bits*)saddr, &addr->s4.sin_addr) == 1)) { + addr->s4.sin_family = AF_INET; +#if defined(TURN_HAS_SIN_LEN) /* tested when configured */ + addr->s4.sin_len = sizeof(struct sockaddr_in); +#endif + addr->s4.sin_port = nswap16(port); + } else if (inet_pton(AF_INET6, (const s08bits*)saddr, &addr->s6.sin6_addr) == 1) { + addr->s6.sin6_family = AF_INET6; +#if defined(SIN6_LEN) /* this define is required by IPv6 if used */ + addr->s6.sin6_len = sizeof(struct sockaddr_in6); +#endif + addr->s6.sin6_port = nswap16(port); + } else { + return -1; + } + + return 0; +} + +static char* get_addr_string_and_port(char* s0, int *port) +{ + char *s = s0; + while(*s && (*s==' ')) ++s; + if(*s == '[') { + ++s; + char *tail = strstr(s,"]"); + if(tail) { + *tail=0; + ++tail; + while(*tail && (*tail==' ')) ++tail; + if(*tail==':') { + ++tail; + *port = atoi(tail); + return s; + } else if(*tail == 0) { + *port = 0; + return s; + } + } + } else { + char *tail = strstr(s,":"); + if(tail) { + *tail = 0; + ++tail; + *port = atoi(tail); + return s; + } else { + *port = 0; + return s; + } + } + return NULL; +} + +int make_ioa_addr_from_full_string(const u08bits* saddr, int default_port, ioa_addr *addr) +{ + if(!addr) + return -1; + + int ret = -1; + int port = 0; + char* s = strdup((const char*)saddr); + char *sa = get_addr_string_and_port(s,&port); + if(sa) { + if(port<1) + port = default_port; + ret = make_ioa_addr((u08bits*)sa,port,addr); + } + turn_free(s,strlen(s)+1); + return ret; +} + +int addr_to_string(const ioa_addr* addr, u08bits* saddr) +{ + + if (addr && saddr) { + + s08bits addrtmp[MAX_IOA_ADDR_STRING]; + + if (addr->ss.sa_family == AF_INET) { + inet_ntop(AF_INET, &addr->s4.sin_addr, addrtmp, INET_ADDRSTRLEN); + if(addr_get_port(addr)>0) + snprintf((s08bits*)saddr, MAX_IOA_ADDR_STRING, "%s:%d", addrtmp, addr_get_port(addr)); + else + strncpy((s08bits*)saddr, addrtmp, MAX_IOA_ADDR_STRING); + } else if (addr->ss.sa_family == AF_INET6) { + inet_ntop(AF_INET6, &addr->s6.sin6_addr, addrtmp, INET6_ADDRSTRLEN); + if(addr_get_port(addr)>0) + snprintf((s08bits*)saddr, MAX_IOA_ADDR_STRING, "[%s]:%d", addrtmp, addr_get_port(addr)); + else + strncpy((s08bits*)saddr, addrtmp, MAX_IOA_ADDR_STRING); + } else { + return -1; + } + + return 0; + } + + return -1; +} + +int addr_to_string_no_port(const ioa_addr* addr, u08bits* saddr) +{ + + if (addr && saddr) { + + s08bits addrtmp[MAX_IOA_ADDR_STRING]; + + if (addr->ss.sa_family == AF_INET) { + inet_ntop(AF_INET, &addr->s4.sin_addr, addrtmp, INET_ADDRSTRLEN); + strncpy((s08bits*)saddr, addrtmp, MAX_IOA_ADDR_STRING); + } else if (addr->ss.sa_family == AF_INET6) { + inet_ntop(AF_INET6, &addr->s6.sin6_addr, addrtmp, INET6_ADDRSTRLEN); + strncpy((s08bits*)saddr, addrtmp, MAX_IOA_ADDR_STRING); + } else { + return -1; + } + + return 0; + } + + return -1; +} + +void addr_set_port(ioa_addr* addr, int port) { + if(addr) { + if(addr->s4.sin_family == AF_INET) { + addr->s4.sin_port = nswap16(port); + } else if(addr->s6.sin6_family == AF_INET6) { + addr->s6.sin6_port = nswap16(port); + } + } +} + +int addr_get_port(const ioa_addr* addr) { + if(!addr) + return 0; + + if(addr->s4.sin_family == AF_INET) { + return nswap16(addr->s4.sin_port); + } else if(addr->s6.sin6_family == AF_INET6) { + return nswap16(addr->s6.sin6_port); + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////// + +void ioa_addr_range_set(ioa_addr_range* range, const ioa_addr* addr_min, const ioa_addr* addr_max) { + if(range) { + if(addr_min) addr_cpy(&(range->min),addr_min); + else addr_set_any(&(range->min)); + if(addr_max) addr_cpy(&(range->max),addr_max); + else addr_set_any(&(range->max)); + } +} + +int addr_less_eq(const ioa_addr* addr1, const ioa_addr* addr2) { + + if(!addr1) return 1; + else if(!addr2) return 0; + else { + if(addr1->ss.sa_family != addr2->ss.sa_family) return (addr1->ss.sa_family < addr2->ss.sa_family); + else if(addr1->ss.sa_family == AF_INET) { + return ((u32bits)nswap32(addr1->s4.sin_addr.s_addr) <= (u32bits)nswap32(addr2->s4.sin_addr.s_addr)); + } else if(addr1->ss.sa_family == AF_INET6) { + int i; + for(i=0;i<16;i++) { + if((u08bits)(((const s08bits*)&(addr1->s6.sin6_addr))[i]) > (u08bits)(((const s08bits*)&(addr2->s6.sin6_addr))[i])) + return 0; + } + return 1; + } else return 1; + } +} + +int ioa_addr_in_range(const ioa_addr_range* range, const ioa_addr* addr) { + + if(range && addr) { + if(addr_any(&(range->min)) || addr_less_eq(&(range->min),addr)) { + if(addr_any(&(range->max))) { + return 1; + } else { + return addr_less_eq(addr,&(range->max)); + } + } + } + + return 0; +} + +void ioa_addr_range_cpy(ioa_addr_range* dest, const ioa_addr_range* src) { + if(dest && src) { + addr_cpy(&(dest->min),&(src->min)); + addr_cpy(&(dest->max),&(src->max)); + } +} + +/////// Check whether this is a good address ////////////// + +int ioa_addr_is_multicast(ioa_addr *addr) +{ + if(addr) { + if(addr->ss.sa_family == AF_INET) { + const u08bits *u = ((const u08bits*)&(addr->s4.sin_addr)); + return (u[0] > 223); + } else if(addr->ss.sa_family == AF_INET6) { + u08bits u = ((const u08bits*)&(addr->s6.sin6_addr))[0]; + return (u == 255); + } + } + return 0; +} + +int ioa_addr_is_loopback(ioa_addr *addr) +{ + if(addr) { + if(addr->ss.sa_family == AF_INET) { + const u08bits *u = ((const u08bits*)&(addr->s4.sin_addr)); + return (u[0] == 127); + } else if(addr->ss.sa_family == AF_INET6) { + const u08bits *u = ((const u08bits*)&(addr->s6.sin6_addr)); + if(u[7] == 1) { + int i; + for(i=0;i<7;++i) { + if(u[i]) + return 0; + } + return 1; + } + } + } + return 0; +} + +/////// Map "public" address to "private" address ////////////// + +// Must be called only in a single-threaded context, +// before the program starts spawning threads: + +static ioa_addr **public_addrs = NULL; +static ioa_addr **private_addrs = NULL; +static size_t mcount = 0; +static size_t msz = 0; + +void ioa_addr_add_mapping(ioa_addr *apub, ioa_addr *apriv) +{ + size_t new_size = msz + sizeof(ioa_addr*); + public_addrs = (ioa_addr**)turn_realloc(public_addrs, msz, new_size); + private_addrs = (ioa_addr**)turn_realloc(private_addrs, msz, new_size); + public_addrs[mcount]=(ioa_addr*)turn_malloc(sizeof(ioa_addr)); + private_addrs[mcount]=(ioa_addr*)turn_malloc(sizeof(ioa_addr)); + addr_cpy(public_addrs[mcount],apub); + addr_cpy(private_addrs[mcount],apriv); + ++mcount; + msz += sizeof(ioa_addr*); +} + +void map_addr_from_public_to_private(const ioa_addr *public_addr, ioa_addr *private_addr) +{ + size_t i; + for(i=0;i +#include +#include +#include +#include + +#include + +/////////// + +long turn_random(void) +{ + long ret = 0; + if(!RAND_bytes((unsigned char *)&ret,sizeof(ret))) + ret = random(); + return ret; +} + +void turn_random32_size(u32bits *ar, size_t sz) +{ + if(!RAND_bytes((unsigned char *)ar, sz<<2)<0) { + size_t i; + for(i=0;i>1) | + ((tt & 0x0E00)>>2) | ((tt & 0x3000)>>2); +} + +u16bits stun_get_msg_type_str(const u08bits *buf, size_t len) { + if(!buf || len<2) return (u16bits)-1; + return ((nswap16(((const u16bits*)buf)[0])) & 0x3FFF); +} + +int is_channel_msg_str(const u08bits* buf, size_t blen) { + return (buf && blen>=4 && STUN_VALID_CHANNEL(nswap16(((const u16bits*)buf)[0]))); +} + +/////////////// message types ///////////////////////////////// + +int stun_is_command_message_str(const u08bits* buf, size_t blen) +{ + if (buf && blen >= STUN_HEADER_LENGTH) { + if (!STUN_VALID_CHANNEL(nswap16(((const u16bits*)buf)[0]))) { + if ((((u08bits) buf[0]) & ((u08bits) (0xC0))) == 0) { + if (nswap32(((const u32bits*)(buf))[1]) + == STUN_MAGIC_COOKIE) { + u16bits len = nswap16(((const u16bits*)(buf))[1]); + if ((len & 0x0003) == 0) { + if ((size_t) (len + STUN_HEADER_LENGTH) == blen) { + return 1; + } + } + } + } + } + } + return 0; +} + +int old_stun_is_command_message_str(const u08bits* buf, size_t blen, u32bits *cookie) +{ + if (buf && blen >= STUN_HEADER_LENGTH) { + if (!STUN_VALID_CHANNEL(nswap16(((const u16bits*)buf)[0]))) { + if ((((u08bits) buf[0]) & ((u08bits) (0xC0))) == 0) { + if (nswap32(((const u32bits*)(buf))[1]) + != STUN_MAGIC_COOKIE) { + u16bits len = nswap16(((const u16bits*)(buf))[1]); + if ((len & 0x0003) == 0) { + if ((size_t) (len + STUN_HEADER_LENGTH) == blen) { + *cookie = nswap32(((const u32bits*)(buf))[1]); + return 1; + } + } + } + } + } + } + return 0; +} + +int stun_is_command_message_full_check_str(const u08bits* buf, size_t blen, int must_check_fingerprint, int *fingerprint_present) { + if(!stun_is_command_message_str(buf,blen)) + return 0; + stun_attr_ref sar = stun_attr_get_first_by_type_str(buf, blen, STUN_ATTRIBUTE_FINGERPRINT); + if(!sar) { + if(fingerprint_present) + *fingerprint_present = 0; + if(stun_get_method_str(buf,blen) == STUN_METHOD_BINDING) { + return 1; + } + return !must_check_fingerprint; + } + if(stun_attr_get_len(sar) != 4) + return 0; + const u32bits* fingerprint = (const u32bits*)stun_attr_get_value(sar); + if(!fingerprint) + return !must_check_fingerprint; + u32bits crc32len = (u32bits)((((const u08bits*)fingerprint)-buf)-4); + int ret = (*fingerprint == nswap32(ns_crc32(buf,crc32len) ^ ((u32bits)0x5354554e))); + if(ret && fingerprint_present) + *fingerprint_present = ret; + return ret; +} + +int stun_is_command_message_offset_str(const u08bits* buf, size_t blen, int offset) { + return stun_is_command_message_str(buf + offset, blen); +} + +int stun_is_request_str(const u08bits* buf, size_t len) { + if(is_channel_msg_str(buf,len)) return 0; + return IS_STUN_REQUEST(stun_get_msg_type_str(buf,len)); +} + +int stun_is_success_response_str(const u08bits* buf, size_t len) { + if(is_channel_msg_str(buf,len)) return 0; + return IS_STUN_SUCCESS_RESP(stun_get_msg_type_str(buf,len)); +} + +int stun_is_error_response_str(const u08bits* buf, size_t len, int *err_code, u08bits *err_msg, size_t err_msg_size) { + if(is_channel_msg_str(buf,len)) return 0; + if(IS_STUN_ERR_RESP(stun_get_msg_type_str(buf,len))) { + if(err_code) { + stun_attr_ref sar = stun_attr_get_first_by_type_str(buf, len, STUN_ATTRIBUTE_ERROR_CODE); + if(sar) { + if(stun_attr_get_len(sar)>=4) { + const u08bits* val = (const u08bits*)stun_attr_get_value(sar); + *err_code=(int)(val[2]*100 + val[3]); + if(err_msg && err_msg_size>0) { + err_msg[0]=0; + if(stun_attr_get_len(sar)>4) { + size_t msg_len = stun_attr_get_len(sar) - 4; + if(msg_len>(err_msg_size-1)) + msg_len=err_msg_size - 1; + ns_bcopy(val+4, err_msg, msg_len); + err_msg[msg_len]=0; + } + } + } + } + } + return 1; + } + return 0; +} + +int stun_is_challenge_response_str(const u08bits* buf, size_t len, int *err_code, u08bits *err_msg, size_t err_msg_size, + u08bits *realm, u08bits *nonce) +{ + int ret = stun_is_error_response_str(buf, len, err_code, err_msg, err_msg_size); + + if(ret && (((*err_code) == 401) || ((*err_code) == 438) || ((*err_code) == SHA_TOO_WEAK))) { + + stun_attr_ref sar = stun_attr_get_first_by_type_str(buf,len,STUN_ATTRIBUTE_REALM); + if(sar) { + const u08bits *value = stun_attr_get_value(sar); + if(value) { + size_t vlen = (size_t)stun_attr_get_len(sar); + ns_bcopy(value,realm,vlen); + realm[vlen]=0; + sar = stun_attr_get_first_by_type_str(buf,len,STUN_ATTRIBUTE_NONCE); + if(sar) { + value = stun_attr_get_value(sar); + if(value) { + vlen = (size_t)stun_attr_get_len(sar); + ns_bcopy(value,nonce,vlen); + nonce[vlen]=0; + return 1; + } + } + } + } + } + + return 0; +} + +int stun_is_response_str(const u08bits* buf, size_t len) { + if(is_channel_msg_str(buf,len)) return 0; + if(IS_STUN_SUCCESS_RESP(stun_get_msg_type_str(buf,len))) return 1; + if(IS_STUN_ERR_RESP(stun_get_msg_type_str(buf,len))) return 1; + return 0; +} + +int stun_is_indication_str(const u08bits* buf, size_t len) { + if(is_channel_msg_str(buf,len)) return 0; + return IS_STUN_INDICATION(stun_get_msg_type_str(buf,len)); +} + +u16bits stun_make_request(u16bits method) { + return GET_STUN_REQUEST(stun_make_type(method)); +} + +u16bits stun_make_indication(u16bits method) { + return GET_STUN_INDICATION(stun_make_type(method)); +} + +u16bits stun_make_success_response(u16bits method) { + return GET_STUN_SUCCESS_RESP(stun_make_type(method)); +} + +u16bits stun_make_error_response(u16bits method) { + return GET_STUN_ERR_RESP(stun_make_type(method)); +} + +//////////////// INIT //////////////////////////////////////////// + +void stun_init_buffer_str(u08bits *buf, size_t *len) { + *len=STUN_HEADER_LENGTH; + ns_bzero(buf,*len); +} + +void stun_init_command_str(u16bits message_type, u08bits* buf, size_t *len) { + stun_init_buffer_str(buf,len); + message_type &= (u16bits)(0x3FFF); + ((u16bits*)buf)[0]=nswap16(message_type); + ((u16bits*)buf)[1]=0; + ((u32bits*)buf)[1]=nswap32(STUN_MAGIC_COOKIE); + stun_tid_generate_in_message_str(buf,NULL); +} + +void old_stun_init_command_str(u16bits message_type, u08bits* buf, size_t *len, u32bits cookie) { + stun_init_buffer_str(buf,len); + message_type &= (u16bits)(0x3FFF); + ((u16bits*)buf)[0]=nswap16(message_type); + ((u16bits*)buf)[1]=0; + ((u32bits*)buf)[1]=nswap32(cookie); + stun_tid_generate_in_message_str(buf,NULL); +} + +void stun_init_request_str(u16bits method, u08bits* buf, size_t *len) { + stun_init_command_str(stun_make_request(method), buf, len); +} + +void stun_init_indication_str(u16bits method, u08bits* buf, size_t *len) { + stun_init_command_str(stun_make_indication(method), buf, len); +} + +void stun_init_success_response_str(u16bits method, u08bits* buf, size_t *len, stun_tid* id) { + stun_init_command_str(stun_make_success_response(method), buf, len); + if(id) { + stun_tid_message_cpy(buf, id); + } +} + +void old_stun_init_success_response_str(u16bits method, u08bits* buf, size_t *len, stun_tid* id, u32bits cookie) { + old_stun_init_command_str(stun_make_success_response(method), buf, len, cookie); + if(id) { + stun_tid_message_cpy(buf, id); + } +} + +static void stun_init_error_response_common_str(u08bits* buf, size_t *len, + u16bits error_code, const u08bits *reason, + stun_tid* id) +{ + + if (!reason) { + + switch (error_code){ + case 300: + reason = (const u08bits *) "Try Alternate"; + break; + case 400: + reason = (const u08bits *) "Bad Request"; + break; + case 401: + reason = (const u08bits *) "Unauthorized"; + break; + case 404: + reason = (const u08bits *) "Not Found"; + break; + case 420: + reason = (const u08bits *) "Unknown Attribute"; + break; + case 438: + reason = (const u08bits *) "Stale Nonce"; + break; + case 500: + reason = (const u08bits *) "Server Error"; + break; + default: + reason = (const u08bits *) "Unknown Error"; + break; + }; + } + + u08bits avalue[513]; + avalue[0] = 0; + avalue[1] = 0; + avalue[2] = (u08bits) (error_code / 100); + avalue[3] = (u08bits) (error_code % 100); + strncpy((s08bits*) (avalue + 4), (const s08bits*) reason, sizeof(avalue)-4); + avalue[sizeof(avalue)-1]=0; + int alen = 4 + strlen((const s08bits*) (avalue+4)); + + //"Manual" padding for compatibility with classic old stun: + { + int rem = alen % 4; + if(rem) { + alen +=(4-rem); + } + } + + stun_attr_add_str(buf, len, STUN_ATTRIBUTE_ERROR_CODE, (u08bits*) avalue, alen); + if (id) { + stun_tid_message_cpy(buf, id); + } +} + +void old_stun_init_error_response_str(u16bits method, u08bits* buf, size_t *len, + u16bits error_code, const u08bits *reason, + stun_tid* id, u32bits cookie) +{ + + old_stun_init_command_str(stun_make_error_response(method), buf, len, cookie); + + stun_init_error_response_common_str(buf, len, + error_code, reason, + id); +} + +void stun_init_error_response_str(u16bits method, u08bits* buf, size_t *len, + u16bits error_code, const u08bits *reason, + stun_tid* id) +{ + + stun_init_command_str(stun_make_error_response(method), buf, len); + + stun_init_error_response_common_str(buf, len, + error_code, reason, + id); +} + +/////////// CHANNEL //////////////////////////////////////////////// + +int stun_init_channel_message_str(u16bits chnumber, u08bits* buf, size_t *len, int length, int do_padding) +{ + u16bits rlen = (u16bits)length; + + if(length<0 || (MAX_STUN_MESSAGE_SIZE<(4+length))) return -1; + ((u16bits*)(buf))[0]=nswap16(chnumber); + ((u16bits*)(buf))[1]=nswap16((u16bits)length); + + if(do_padding && (rlen & 0x0003)) + rlen = ((rlen>>2)+1)<<2; + + *len=4+rlen; + + return 0; +} + +int stun_is_channel_message_str(const u08bits *buf, size_t *blen, u16bits* chnumber, int mandatory_padding) +{ + u16bits datalen_header; + u16bits datalen_actual; + + if (!blen || (*blen < 4)) + return 0; + + u16bits chn = nswap16(((const u16bits*)(buf))[0]); + if (!STUN_VALID_CHANNEL(chn)) + return 0; + + if(*blen>(u16bits)-1) + *blen=(u16bits)-1; + + datalen_actual = (u16bits)(*blen) - 4; + datalen_header = ((const u16bits*)buf)[1]; + datalen_header = nswap16(datalen_header); + + if (datalen_header > datalen_actual) + return 0; + + if (datalen_header != datalen_actual) { + + /* maybe there are padding bytes for 32-bit alignment. Mandatory for TCP. Optional for UDP */ + + if(datalen_actual & 0x0003) { + + if(mandatory_padding) { + return 0; + } else if ((datalen_actual < datalen_header) || (datalen_header == 0)) { + return 0; + } else { + u16bits diff = datalen_actual - datalen_header; + if (diff > 3) + return 0; + } + } + } + + *blen = datalen_header + 4; + + if (chnumber) + *chnumber = chn; + + return 1; +} + +////////// STUN message /////////////////////////////// + +static inline int sheadof(const char *head, const char* full) +{ + while(*head) { + if(*head != *full) + return 0; + ++head;++full; + } + return 1; +} + +static inline const char* findstr(const char *hay, size_t slen, const char *needle) +{ + const char *ret = NULL; + + if(hay && slen && needle) { + size_t nlen=strlen(needle); + if(nlen<=slen) { + size_t smax = slen-nlen+1; + size_t i; + const char *sp = hay; + for(i=0;i=12) { + if((s[0]=='G')&&(s[1]=='E')&&(s[2]=='T')&&(s[3]==' ')) { + const char *sp=findstr(s+4,blen-4,"HTTP"); + if(sp) { + sp += 4; + size_t diff_blen = sp-s; + if(diff_blen+4 <= blen) { + sp=findstr(sp,blen-diff_blen,"\r\n\r\n"); + if(sp) { + return (int)(sp-s+4); + } + } + } + + } + } + return 0; +} + +int is_http_get(const char *s, size_t blen) { + return is_http_get_inline(s, blen); +} + +int stun_get_message_len_str(u08bits *buf, size_t blen, int padding, size_t *app_len) { + if (buf && blen) { + /* STUN request/response ? */ + if (buf && blen >= STUN_HEADER_LENGTH) { + if (!STUN_VALID_CHANNEL(nswap16(((const u16bits*)buf)[0]))) { + if ((((u08bits) buf[0]) & ((u08bits) (0xC0))) == 0) { + if (nswap32(((const u32bits*)(buf))[1]) + == STUN_MAGIC_COOKIE) { + u16bits len = nswap16(((const u16bits*)(buf))[1]); + if ((len & 0x0003) == 0) { + len += STUN_HEADER_LENGTH; + if ((size_t) len <= blen) { + *app_len = (size_t)len; + return (int)len; + } + } + } + } + } + } + + //HTTP request ? + { + int http_len = is_http_get_inline(((char*)buf), blen); + if((http_len>0) && ((size_t)http_len<=blen)) { + *app_len = (size_t)http_len; + return http_len; + } + } + + /* STUN channel ? */ + if(blen>=4) { + u16bits chn=nswap16(((const u16bits*)(buf))[0]); + if(STUN_VALID_CHANNEL(chn)) { + + u16bits bret = (4+(nswap16(((const u16bits*)(buf))[1]))); + + *app_len = bret; + + if(padding && (bret & 0x0003)) { + bret = ((bret>>2)+1)<<2; + } + + if(bret<=blen) { + return bret; + } + } + } + + } + + return -1; +} + +////////// ALLOCATE /////////////////////////////////// + +int stun_set_allocate_request_str(u08bits* buf, size_t *len, u32bits lifetime, int address_family, + u08bits transport, int mobile) { + + stun_init_request_str(STUN_METHOD_ALLOCATE, buf, len); + + //REQUESTED-TRANSPORT + { + u08bits field[4]; + field[0]=transport; + field[1]=0; + field[2]=0; + field[3]=0; + if(stun_attr_add_str(buf,len,STUN_ATTRIBUTE_REQUESTED_TRANSPORT,field,sizeof(field))<0) return -1; + } + + //LIFETIME + { + if(lifetime<1) lifetime=STUN_DEFAULT_ALLOCATE_LIFETIME; + u32bits field=nswap32(lifetime); + if(stun_attr_add_str(buf,len,STUN_ATTRIBUTE_LIFETIME,(u08bits*)(&field),sizeof(field))<0) return -1; + } + + if(mobile) { + if(stun_attr_add_str(buf,len,STUN_ATTRIBUTE_MOBILITY_TICKET,(const u08bits*)"",0)<0) return -1; + } + + //ADRESS-FAMILY + switch (address_family) { + case STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV4: + case STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV6: + { + u08bits field[4]; + field[0] = (u08bits)address_family; + field[1]=0; + field[2]=0; + field[3]=0; + if(stun_attr_add_str(buf,len,STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY,field,sizeof(field))<0) return -1; + break; + } + case STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_DEFAULT: + /* ignore */ + break; + default: + return -1; + }; + + return 0; +} + +int stun_set_allocate_response_str(u08bits* buf, size_t *len, stun_tid* tid, + const ioa_addr *relayed_addr, const ioa_addr *reflexive_addr, + u32bits lifetime, int error_code, const u08bits *reason, + u64bits reservation_token, char* mobile_id) { + + if(!error_code) { + + stun_init_success_response_str(STUN_METHOD_ALLOCATE, buf, len, tid); + + if(relayed_addr) { + if(stun_attr_add_addr_str(buf,len,STUN_ATTRIBUTE_XOR_RELAYED_ADDRESS,relayed_addr)<0) return -1; + } + + if(reflexive_addr) { + if(stun_attr_add_addr_str(buf,len,STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS,reflexive_addr)<0) return -1; + } + + if(reservation_token) { + reservation_token=nswap64(reservation_token); + stun_attr_add_str(buf,len,STUN_ATTRIBUTE_RESERVATION_TOKEN,(u08bits*)(&reservation_token),8); + } + + { + if(lifetime<1) lifetime=STUN_DEFAULT_ALLOCATE_LIFETIME; + else if(lifetime>STUN_MAX_ALLOCATE_LIFETIME) lifetime = STUN_MAX_ALLOCATE_LIFETIME; + + u32bits field=nswap32(lifetime); + if(stun_attr_add_str(buf,len,STUN_ATTRIBUTE_LIFETIME,(u08bits*)(&field),sizeof(field))<0) return -1; + } + + if(mobile_id && *mobile_id) { + if(stun_attr_add_str(buf,len,STUN_ATTRIBUTE_MOBILITY_TICKET,(u08bits*)mobile_id,strlen(mobile_id))<0) return -1; + } + + } else { + stun_init_error_response_str(STUN_METHOD_ALLOCATE, buf, len, error_code, reason, tid); + } + + return 0; +} + +/////////////// CHANNEL BIND /////////////////////////////////////// + +u16bits stun_set_channel_bind_request_str(u08bits* buf, size_t *len, + const ioa_addr* peer_addr, u16bits channel_number) { + + if(!STUN_VALID_CHANNEL(channel_number)) { + channel_number = 0x4000 + ((u16bits)(((u32bits)turn_random())%(0x7FFF-0x4000+1))); + } + + stun_init_request_str(STUN_METHOD_CHANNEL_BIND, buf, len); + + if(stun_attr_add_channel_number_str(buf, len, channel_number)<0) return 0; + + if(!peer_addr) { + ioa_addr ca; + ns_bzero(&ca,sizeof(ioa_addr)); + + if(stun_attr_add_addr_str(buf,len,STUN_ATTRIBUTE_XOR_PEER_ADDRESS, &ca)<0) return 0; + } else { + if(stun_attr_add_addr_str(buf,len,STUN_ATTRIBUTE_XOR_PEER_ADDRESS, peer_addr)<0) return 0; + } + + return channel_number; +} + +void stun_set_channel_bind_response_str(u08bits* buf, size_t *len, stun_tid* tid, int error_code, const u08bits *reason) { + if(!error_code) { + stun_init_success_response_str(STUN_METHOD_CHANNEL_BIND, buf, len, tid); + } else { + stun_init_error_response_str(STUN_METHOD_CHANNEL_BIND, buf, len, error_code, reason, tid); + } +} + +/////////////// BINDING /////////////////////////////////////// + +void stun_set_binding_request_str(u08bits* buf, size_t *len) { + stun_init_request_str(STUN_METHOD_BINDING, buf, len); +} + +int stun_set_binding_response_str(u08bits* buf, size_t *len, stun_tid* tid, + const ioa_addr *reflexive_addr, int error_code, const u08bits *reason, + u32bits cookie, int old_stun) + +{ + if (!error_code) { + if (!old_stun) { + stun_init_success_response_str(STUN_METHOD_BINDING, buf, len, tid); + } else { + old_stun_init_success_response_str(STUN_METHOD_BINDING, buf, len, tid, cookie); + } + if(!old_stun) { + if (stun_attr_add_addr_str(buf, len, STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS, reflexive_addr) < 0) + return -1; + } + if (stun_attr_add_addr_str(buf, len, STUN_ATTRIBUTE_MAPPED_ADDRESS, reflexive_addr) < 0) + return -1; + } else if (!old_stun) { + stun_init_error_response_str(STUN_METHOD_BINDING, buf, len, error_code, reason, tid); + } else { + old_stun_init_error_response_str(STUN_METHOD_BINDING, buf, len, error_code, reason, tid, cookie); + } + + return 0; +} + +int stun_is_binding_request_str(const u08bits* buf, size_t len, size_t offset) +{ + if(offset < len) { + buf += offset; + len -= offset; + if (stun_is_command_message_str(buf, len)) { + if (stun_is_request_str(buf, len) && (stun_get_method_str(buf, len) == STUN_METHOD_BINDING)) { + return 1; + } + } + } + return 0; +} + +int stun_is_binding_response_str(const u08bits* buf, size_t len) { + if(stun_is_command_message_str(buf,len) && + (stun_get_method_str(buf,len)==STUN_METHOD_BINDING)) { + if(stun_is_response_str(buf,len)) { + return 1; + } + } + return 0; +} + +/////////////////////////////// TID /////////////////////////////// + + +int stun_tid_equals(const stun_tid *id1, const stun_tid *id2) { + if(id1==id2) return 1; + if(!id1) return 0; + if(!id2) return 0; + { + unsigned int i=0; + for(i=0;itsx_id[i]!=id2->tsx_id[i]) return 0; + } + } + return 1; +} + +void stun_tid_cpy(stun_tid *id1, const stun_tid *id2) { + if(!id1) return; + if(!id2) return; + ns_bcopy((const void*)(id2->tsx_id),(void*)(id1->tsx_id),STUN_TID_SIZE); +} + +static void stun_tid_string_cpy(u08bits* s, const stun_tid* id) { + if(s && id) { + ns_bcopy((const void*)(id->tsx_id),s,STUN_TID_SIZE); + } +} + +static void stun_tid_from_string(const u08bits* s, stun_tid* id) { + if(s && id) { + ns_bcopy(s,(void*)(id->tsx_id),STUN_TID_SIZE); + } +} + +void stun_tid_from_message_str(const u08bits* buf, size_t len, stun_tid* id) { + UNUSED_ARG(len); + stun_tid_from_string(buf+8, id); +} + +void stun_tid_message_cpy(u08bits* buf, const stun_tid* id) { + if(buf && id) { + stun_tid_string_cpy(buf+8, id); + } +} + +void stun_tid_generate(stun_tid* id) { + if(id) { + u32bits *w=(u32bits*)(id->tsx_id); + turn_random32_size(w,3); + } +} + +void stun_tid_generate_in_message_str(u08bits* buf, stun_tid* id) { + stun_tid tmp; + if(!id) id=&tmp; + stun_tid_generate(id); + stun_tid_message_cpy(buf, id); +} + +/////////////////// TIME //////////////////////////////////////////////////////// + +u32bits stun_adjust_allocate_lifetime(u32bits lifetime) { + if(!lifetime) return STUN_DEFAULT_ALLOCATE_LIFETIME; + else if(lifetimeSTUN_MAX_ALLOCATE_LIFETIME) return STUN_MAX_ALLOCATE_LIFETIME; + return lifetime; +} + +////////////// ATTR ///////////////////////////////////////////////////////////// + +int stun_attr_get_type(stun_attr_ref attr) { + if(attr) + return (int)(nswap16(((const u16bits*)attr)[0])); + return -1; +} + +int stun_attr_get_len(stun_attr_ref attr) { + if(attr) + return (int)(nswap16(((const u16bits*)attr)[1])); + return -1; +} + +const u08bits* stun_attr_get_value(stun_attr_ref attr) { + if(attr) { + int len = (int)(nswap16(((const u16bits*)attr)[1])); + if(len<1) return NULL; + return ((const u08bits*)attr)+4; + } + return NULL; +} + +int stun_get_requested_address_family(stun_attr_ref attr) +{ + if (attr) { + int len = (int) (nswap16(((const u16bits*)attr)[1])); + if (len != 4) + return STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_INVALID; + int val = ((const u08bits*) attr)[4]; + switch (val){ + case STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV4: + return val; + case STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV6: + return val; + default: + return STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_INVALID; + }; + } + return STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_DEFAULT; +} + +u16bits stun_attr_get_channel_number(stun_attr_ref attr) { + if(attr) { + const u08bits* value = stun_attr_get_value(attr); + if(value && (stun_attr_get_len(attr) >= 2)) { + u16bits cn=nswap16(((const u16bits*)value)[0]); + if(STUN_VALID_CHANNEL(cn)) return cn; + } + } + return 0; +} + +u64bits stun_attr_get_reservation_token_value(stun_attr_ref attr) { + if(attr) { + const u08bits* value = stun_attr_get_value(attr); + if(value && (stun_attr_get_len(attr) == 8)) { + return nswap64(((const u64bits*)value)[0]); + } + } + return 0; +} + +int stun_attr_is_addr(stun_attr_ref attr) { + + if(attr) { + switch(stun_attr_get_type(attr)) { + case STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS: + case STUN_ATTRIBUTE_XOR_PEER_ADDRESS: + case STUN_ATTRIBUTE_XOR_RELAYED_ADDRESS: + case STUN_ATTRIBUTE_MAPPED_ADDRESS: + case STUN_ATTRIBUTE_ALTERNATE_SERVER: + case OLD_STUN_ATTRIBUTE_RESPONSE_ADDRESS: + case OLD_STUN_ATTRIBUTE_SOURCE_ADDRESS: + case OLD_STUN_ATTRIBUTE_CHANGED_ADDRESS: + case OLD_STUN_ATTRIBUTE_REFLECTED_FROM: + case STUN_ATTRIBUTE_RESPONSE_ORIGIN: + case STUN_ATTRIBUTE_OTHER_ADDRESS: + return 1; + break; + default: + ; + }; + } + return 0; +} + +u08bits stun_attr_get_even_port(stun_attr_ref attr) { + if(attr) { + const u08bits* value=stun_attr_get_value(attr); + if(value) { + if((u08bits)(value[0]) > 0x7F) return 1; + } + } + return 0; +} + +stun_attr_ref stun_attr_get_first_by_type_str(const u08bits* buf, size_t len, u16bits attr_type) { + + stun_attr_ref attr=stun_attr_get_first_str(buf,len); + while(attr) { + if(stun_attr_get_type(attr) == attr_type) { + return attr; + } + attr=stun_attr_get_next_str(buf,len,attr); + } + + return NULL; +} + +stun_attr_ref stun_attr_get_first_str(const u08bits* buf, size_t len) { + + if(stun_get_command_message_len_str(buf,len)>STUN_HEADER_LENGTH) { + return (stun_attr_ref)(buf+STUN_HEADER_LENGTH); + } + + return NULL; +} + +stun_attr_ref stun_attr_get_next_str(const u08bits* buf, size_t len, stun_attr_ref prev) { + + if(!prev) return stun_attr_get_first_str(buf,len); + else { + const u08bits* end = buf + stun_get_command_message_len_str(buf,len); + int attrlen=stun_attr_get_len(prev); + u16bits rem4 = ((u16bits)attrlen) & 0x0003; + if(rem4) { + attrlen = attrlen+4-(int)rem4; + } + const u08bits* attr_end=(const u08bits*)prev+4+attrlen; + if(attr_end=MAX_STUN_MESSAGE_SIZE) return -1; + else { + u08bits* attr_start=buf+clen; + + u16bits *attr_start_16t=(u16bits*)attr_start; + + stun_set_command_message_len_str(buf,newlen); + *len = newlen; + + attr_start_16t[0]=nswap16(attr); + attr_start_16t[1]=nswap16(alen); + if(alen>0) ns_bcopy(avalue,attr_start+4,alen); + return 0; + } +} + +int stun_attr_add_addr_str(u08bits *buf, size_t *len, u16bits attr_type, const ioa_addr* ca) { + + stun_tid tid; + stun_tid_from_message_str(buf, *len, &tid); + + int xor_ed=0; + switch(attr_type) { + case STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS: + case STUN_ATTRIBUTE_XOR_PEER_ADDRESS: + case STUN_ATTRIBUTE_XOR_RELAYED_ADDRESS: + xor_ed=1; + break; + default: + ; + }; + + ioa_addr public_addr; + map_addr_from_private_to_public(ca,&public_addr); + + u08bits cfield[64]; + int clen=0; + if(stun_addr_encode(&public_addr, cfield, &clen, xor_ed, STUN_MAGIC_COOKIE, tid.tsx_id)<0) { + return -1; + } + + if(stun_attr_add_str(buf,len,attr_type,(u08bits*)(&cfield),clen)<0) return -1; + + return 0; +} + +int stun_attr_get_addr_str(const u08bits *buf, size_t len, stun_attr_ref attr, ioa_addr* ca, const ioa_addr *default_addr) { + + stun_tid tid; + stun_tid_from_message_str(buf, len, &tid); + ioa_addr public_addr; + + ns_bzero(ca,sizeof(ioa_addr)); + + int attr_type = stun_attr_get_type(attr); + if(attr_type<0) return -1; + + int xor_ed=0; + switch(attr_type) { + case STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS: + case STUN_ATTRIBUTE_XOR_PEER_ADDRESS: + case STUN_ATTRIBUTE_XOR_RELAYED_ADDRESS: + xor_ed=1; + break; + default: + ; + }; + + const u08bits *cfield=stun_attr_get_value(attr); + if(!cfield) return -1; + + if(stun_addr_decode(&public_addr, cfield, stun_attr_get_len(attr), xor_ed, STUN_MAGIC_COOKIE, tid.tsx_id)<0) { + return -1; + } + + map_addr_from_public_to_private(&public_addr, ca); + + if(default_addr && addr_any_no_port(ca) && !addr_any_no_port(default_addr)) { + int port = addr_get_port(ca); + addr_cpy(ca,default_addr); + addr_set_port(ca,port); + } + + return 0; +} + +int stun_attr_get_first_addr_str(const u08bits *buf, size_t len, u16bits attr_type, ioa_addr* ca, const ioa_addr *default_addr) { + + stun_attr_ref attr=stun_attr_get_first_str(buf,len); + + while(attr) { + if(stun_attr_is_addr(attr) && (attr_type == stun_attr_get_type(attr))) { + if(stun_attr_get_addr_str(buf,len,attr,ca,default_addr)==0) { + return 0; + } + } + attr=stun_attr_get_next_str(buf,len,attr); + } + + return -1; +} + +int stun_attr_add_channel_number_str(u08bits* buf, size_t *len, u16bits chnumber) { + + u16bits field[2]; + field[0]=nswap16(chnumber); + field[1]=0; + + return stun_attr_add_str(buf,len,STUN_ATTRIBUTE_CHANNEL_NUMBER,(u08bits*)(field),sizeof(field)); +} + +u16bits stun_attr_get_first_channel_number_str(const u08bits *buf, size_t len) { + + stun_attr_ref attr=stun_attr_get_first_str(buf,len); + while(attr) { + if(stun_attr_get_type(attr) == STUN_ATTRIBUTE_CHANNEL_NUMBER) { + u16bits ret = stun_attr_get_channel_number(attr); + if(STUN_VALID_CHANNEL(ret)) { + return ret; + } + } + attr=stun_attr_get_next_str(buf,len,attr); + } + + return 0; +} + +////////////// FINGERPRINT //////////////////////////// + +int stun_attr_add_fingerprint_str(u08bits *buf, size_t *len) +{ + u32bits crc32 = 0; + stun_attr_add_str(buf, len, STUN_ATTRIBUTE_FINGERPRINT, (u08bits*)&crc32, 4); + crc32 = ns_crc32(buf,*len-8); + *((u32bits*)(buf+*len-4)) = nswap32(crc32 ^ ((u32bits)0x5354554e)); + return 0; +} +////////////// CRC /////////////////////////////////////////////// + +#define CRC_MASK 0xFFFFFFFFUL + +#define UPDATE_CRC(crc, c) crc = crctable[(u08bits)crc ^ (u08bits)(c)] ^ (crc >> 8) + +static const u32bits crctable[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, + 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, + 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, + 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, + 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, + 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, + 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, + 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, + 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, + 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, + 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, + 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, + 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, + 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, + 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, + 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, +}; + +/* + +#define CRCPOLY 0xEDB88320UL +reversed 0x04C11DB7 +1110 1101 1001 1000 1000 0011 0010 0000 + +static void make_crctable(void) +{ + uint i, j; + u32bits r; + + for (i = 0; i < 256; ++i) { + r = i; + for (j = 8; j > 0; --j) { + if (r & 1) + r = (r >> 1) ^ CRCPOLY; + else + r >>= 1; + } + crctable[i] = r; + } +} +*/ + +static u32bits ns_crc32(const u08bits *buffer, u32bits len) +{ + u32bits crc = CRC_MASK; + while ( len-- ) UPDATE_CRC( crc, *buffer++ ); + return (~crc); +} + +//////////// SASLprep RFC 4013 ///////////////////////////////////////// + +/* We support only basic ASCII table */ + +int SASLprep(u08bits *s) +{ + if(s) { + u08bits *strin = s; + u08bits *strout = s; + for(;;) { + u08bits c = *strin; + if(!c) { + *strout=0; + break; + } + + switch(c) { + case 0xAD: + ++strin; + break; + case 0xA0: + case 0x20: + *strout=0x20; + ++strout; + ++strin; + break; + case 0x7F: + return -1; + default: + if(c<0x1F) + return -1; + if(c>=0x80 && c<=0x9F) + return -1; + *strout=c; + ++strout; + ++strin; + }; + } + } + + return 0; +} + +//////////////// Message Integrity //////////////////////////// + +size_t get_hmackey_size(SHATYPE shatype) +{ + if(shatype == SHATYPE_SHA256) + return 32; + return 16; +} + +void print_bin_func(const char *name, size_t len, const void *s, const char *func) +{ + printf("<%s>:<%s>:len=%d:[",func,name,(int)len); + size_t i; + for(i=0;i orig_len) + return -1; + + if (stun_set_command_message_len_str(buf, new_len) < 0) + return -1; + + if(ct == TURN_CREDENTIALS_SHORT_TERM) { + res = stun_calculate_hmac(buf, (size_t) new_len - 4 - shasize, pwd, strlen((char*)pwd), new_hmac, &shasize, shatype); + } else { + res = stun_calculate_hmac(buf, (size_t) new_len - 4 - shasize, key, get_hmackey_size(shatype), new_hmac, &shasize, shatype); + } + + stun_set_command_message_len_str(buf, orig_len); + if(res<0) + return -1; + + old_hmac = stun_attr_get_value(sar); + if(!old_hmac) + return -1; + + if(bcmp(old_hmac,new_hmac,shasize)) + return 0; + + return 1; +} + +/* + * Return -1 if failure, 0 if the integrity is not correct, 1 if OK + */ +int stun_check_message_integrity_str(turn_credential_type ct, u08bits *buf, size_t len, u08bits *uname, u08bits *realm, u08bits *upwd, SHATYPE shatype) +{ + hmackey_t key; + st_password_t pwd; + + if(ct == TURN_CREDENTIALS_SHORT_TERM) + strncpy((char*)pwd,(char*)upwd,sizeof(st_password_t)); + else if (stun_produce_integrity_key_str(uname, realm, upwd, key, shatype) < 0) + return -1; + + return stun_check_message_integrity_by_key_str(ct, buf, len, key, pwd, shatype, NULL); +} + +/* RFC 5780 */ + +int stun_attr_get_change_request_str(stun_attr_ref attr, int *change_ip, int *change_port) +{ + if(stun_attr_get_len(attr) == 4) { + const u08bits* value = stun_attr_get_value(attr); + if(value) { + *change_ip = (value[3] & (u08bits)0x04); + *change_port = (value[3] & (u08bits)0x02); + return 0; + } + } + return -1; +} + +int stun_attr_add_change_request_str(u08bits *buf, size_t *len, int change_ip, int change_port) +{ + u08bits avalue[4]={0,0,0,0}; + + if(change_ip) { + if(change_port) { + avalue[3] = 0x06; + } else { + avalue[3] = 0x04; + } + } else if(change_port) { + avalue[3]=0x02; + } + + return stun_attr_add_str(buf, len, STUN_ATTRIBUTE_CHANGE_REQUEST, avalue, 4); +} + +int stun_attr_get_response_port_str(stun_attr_ref attr) +{ + if(stun_attr_get_len(attr) >= 2) { + const u08bits* value = stun_attr_get_value(attr); + if(value) { + return nswap16(((const u16bits*)value)[0]); + } + } + return -1; +} + +int stun_attr_add_response_port_str(u08bits *buf, size_t *len, u16bits port) +{ + u08bits avalue[4]={0,0,0,0}; + u16bits *port_ptr = (u16bits*)avalue; + + *port_ptr = nswap16(port); + + return stun_attr_add_str(buf, len, STUN_ATTRIBUTE_RESPONSE_PORT, avalue, 4); +} + +int stun_attr_get_padding_len_str(stun_attr_ref attr) { + int len = stun_attr_get_len(attr); + if(len<0) + return -1; + return (u16bits)len; +} + +int stun_attr_add_padding_str(u08bits *buf, size_t *len, u16bits padding_len) +{ + u08bits avalue[0xFFFF]; + ns_bzero(avalue,padding_len); + + return stun_attr_add_str(buf, len, STUN_ATTRIBUTE_PADDING, avalue, padding_len); +} + +/////////////////////////////////////////////////////////////// diff --git a/src/client/ns_turn_msg.h b/src/client/ns_turn_msg.h new file mode 100644 index 0000000..4a684f5 --- /dev/null +++ b/src/client/ns_turn_msg.h @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __LIB_TURN_MSG__ +#define __LIB_TURN_MSG__ + +#include "ns_turn_ioaddr.h" +#include "ns_turn_msg_defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/////////////////////////////////// + +/** + * Structure holding the STUN message Transaction ID + */ +#define STUN_TID_SIZE (12) +typedef struct { + /** + * Binary array + */ + uint8_t tsx_id[STUN_TID_SIZE]; +} stun_tid; + +typedef enum { + TURN_CREDENTIALS_NONE = 0, + TURN_CREDENTIALS_LONG_TERM, + TURN_CREDENTIALS_SHORT_TERM, + TURN_CREDENTIALS_UNDEFINED +} turn_credential_type; + +/** + * HMAC key + */ +typedef u08bits hmackey_t[64]; + +/** + * Short-term credentials password + */ +#define SHORT_TERM_PASSWORD_SIZE (512) +typedef u08bits st_password_t[SHORT_TERM_PASSWORD_SIZE+1]; + +/////////////////////////////////// + +typedef const void* stun_attr_ref; + +////////////////////////////////////////////////////////////// + +int stun_tid_equals(const stun_tid *id1, const stun_tid *id2); +void stun_tid_cpy(stun_tid *id_dst, const stun_tid *id_src); +void stun_tid_generate(stun_tid* id); + +/////////////////////////////////////////////////////////////// + +u16bits stun_make_type(u16bits method); +u16bits stun_make_request(u16bits method); +u16bits stun_make_indication(u16bits method); +u16bits stun_make_success_response(u16bits method); +u16bits stun_make_error_response(u16bits method); + +/////////////////////////////////////////////////////////////// + +u32bits stun_adjust_allocate_lifetime(u32bits lifetime); + +///////////// STR //////////////////////////////////////////////// + +int stun_get_message_len_str(u08bits *buf, size_t len, int padding, size_t *app_len); + +void stun_init_buffer_str(u08bits *buf, size_t *len); +void stun_init_command_str(u16bits message_type, u08bits* buf, size_t *len); +void old_stun_init_command_str(u16bits message_type, u08bits* buf, size_t *len, u32bits cookie); +void stun_init_request_str(u16bits method, u08bits* buf, size_t *len); +void stun_init_indication_str(u16bits method, u08bits* buf, size_t *len); +void stun_init_success_response_str(u16bits method, u08bits* buf, size_t *len, stun_tid* id); +void old_stun_init_success_response_str(u16bits method, u08bits* buf, size_t *len, stun_tid* id, u32bits cookie); +void stun_init_error_response_str(u16bits method, u08bits* buf, size_t *len, u16bits error_code, const u08bits *reason, stun_tid* id); +void old_stun_init_error_response_str(u16bits method, u08bits* buf, size_t *len, u16bits error_code, const u08bits *reason, stun_tid* id, u32bits cookie); +int stun_init_channel_message_str(u16bits chnumber, u08bits* buf, size_t *len, int length, int do_padding); + +int stun_is_command_message_str(const u08bits* buf, size_t blen); +int old_stun_is_command_message_str(const u08bits* buf, size_t blen, u32bits *cookie); +int stun_is_command_message_full_check_str(const u08bits* buf, size_t blen, int must_check_fingerprint, int *fingerprint_present); +int stun_is_command_message_offset_str(const u08bits* buf, size_t blen, int offset); +int stun_is_request_str(const u08bits* buf, size_t len); +int stun_is_success_response_str(const u08bits* buf, size_t len); +int stun_is_error_response_str(const u08bits* buf, size_t len, int *err_code, u08bits *err_msg, size_t err_msg_size); +int stun_is_challenge_response_str(const u08bits* buf, size_t len, int *err_code, u08bits *err_msg, size_t err_msg_size, u08bits *realm, u08bits *nonce); +int stun_is_response_str(const u08bits* buf, size_t len); +int stun_is_indication_str(const u08bits* buf, size_t len); +u16bits stun_get_method_str(const u08bits *buf, size_t len); +u16bits stun_get_msg_type_str(const u08bits *buf, size_t len); +int stun_is_channel_message_str(const u08bits *buf, size_t *blen, u16bits* chnumber, int mandatory_padding); +int is_channel_msg_str(const u08bits* buf, size_t blen); + +void stun_set_binding_request_str(u08bits* buf, size_t *len); +int stun_set_binding_response_str(u08bits* buf, size_t *len, stun_tid* tid, + const ioa_addr *reflexive_addr, int error_code, + const u08bits *reason, + u32bits cookie, int old_stun); +int stun_is_binding_request_str(const u08bits* buf, size_t len, size_t offset); +int stun_is_binding_response_str(const u08bits* buf, size_t len); + +void stun_tid_from_message_str(const u08bits* buf, size_t len, stun_tid* id); +void stun_tid_message_cpy(u08bits *buf, const stun_tid* id); +void stun_tid_generate_in_message_str(u08bits* buf, stun_tid* id); + +int stun_get_command_message_len_str(const u08bits* buf, size_t len); + +int stun_attr_is_addr(stun_attr_ref attr); +int stun_attr_get_type(stun_attr_ref attr); +int stun_attr_get_len(stun_attr_ref attr); +const u08bits* stun_attr_get_value(stun_attr_ref attr); +u16bits stun_attr_get_channel_number(stun_attr_ref attr); +u08bits stun_attr_get_even_port(stun_attr_ref attr); +u64bits stun_attr_get_reservation_token_value(stun_attr_ref attr); +stun_attr_ref stun_attr_get_first_by_type_str(const u08bits* buf, size_t len, u16bits attr_type); +stun_attr_ref stun_attr_get_first_str(const u08bits* buf, size_t len); +stun_attr_ref stun_attr_get_next_str(const u08bits* buf, size_t len, stun_attr_ref prev); +int stun_attr_add_str(u08bits* buf, size_t *len, u16bits attr, const u08bits* avalue, int alen); +int stun_attr_add_addr_str(u08bits *buf, size_t *len, u16bits attr_type, const ioa_addr* ca); +int stun_attr_get_addr_str(const u08bits *buf, size_t len, stun_attr_ref attr, ioa_addr* ca, const ioa_addr *default_addr); +int stun_attr_get_first_addr_str(const u08bits *buf, size_t len, u16bits attr_type, ioa_addr* ca, const ioa_addr *default_addr); +int stun_attr_add_channel_number_str(u08bits* buf, size_t *len, u16bits chnumber); +u16bits stun_attr_get_first_channel_number_str(const u08bits *buf, size_t len); + +int stun_set_allocate_request_str(u08bits* buf, size_t *len, u32bits lifetime, int address_family, u08bits transport, int mobile); +int stun_set_allocate_response_str(u08bits* buf, size_t *len, stun_tid* tid, + const ioa_addr *relayed_addr, + const ioa_addr *reflexive_addr, + u32bits lifetime, int error_code, const u08bits *reason, + u64bits reservation_token, char *mobile_id); + +u16bits stun_set_channel_bind_request_str(u08bits* buf, size_t *len, + const ioa_addr* peer_addr, u16bits channel_number); +void stun_set_channel_bind_response_str(u08bits* buf, size_t *len, stun_tid* tid, int error_code, const u08bits *reason); + +int stun_get_requested_address_family(stun_attr_ref attr); + +int stun_attr_add_fingerprint_str(u08bits *buf, size_t *len); + +int SASLprep(u08bits *s); + +#define print_bin(str, len, field) print_bin_func(str,len,field,__FUNCTION__) +void print_bin_func(const char *name, size_t len, const void *s, const char *func); + +/* + * Return -1 if failure, 0 if the integrity is not correct, 1 if OK + */ +int stun_check_message_integrity_by_key_str(turn_credential_type ct, u08bits *buf, size_t len, hmackey_t key, st_password_t pwd, SHATYPE shatype, int *too_weak); +int stun_check_message_integrity_str(turn_credential_type ct, u08bits *buf, size_t len, u08bits *uname, u08bits *realm, u08bits *upwd, SHATYPE shatype); +int stun_attr_add_integrity_str(turn_credential_type ct, u08bits *buf, size_t *len, hmackey_t key, st_password_t pwd, SHATYPE shatype); +int stun_attr_add_integrity_by_user_str(u08bits *buf, size_t *len, u08bits *uname, u08bits *realm, u08bits *upwd, u08bits *nonce, SHATYPE shatype); +int stun_attr_add_integrity_by_user_short_term_str(u08bits *buf, size_t *len, u08bits *uname, st_password_t pwd, SHATYPE shatype); +size_t get_hmackey_size(SHATYPE shatype); + +/* + * To be implemented with openssl + */ + +#define TURN_RANDOM_SIZE (sizeof(long)) +long turn_random(void); +void turn_random32_size(u32bits *ar, size_t sz); + +int stun_produce_integrity_key_str(u08bits *uname, u08bits *realm, u08bits *upwd, hmackey_t key, SHATYPE shatype); +int stun_calculate_hmac(const u08bits *buf, size_t len, const u08bits *key, size_t sz, u08bits *hmac, unsigned int *hmac_len, SHATYPE shatype); + +/* RFC 5780 */ +int stun_attr_get_change_request_str(stun_attr_ref attr, int *change_ip, int *change_port); +int stun_attr_add_change_request_str(u08bits *buf, size_t *len, int change_ip, int change_port); +int stun_attr_get_response_port_str(stun_attr_ref attr); +int stun_attr_add_response_port_str(u08bits *buf, size_t *len, u16bits port); +int stun_attr_get_padding_len_str(stun_attr_ref attr); +int stun_attr_add_padding_str(u08bits *buf, size_t *len, u16bits padding_len); + +/* HTTP */ +int is_http_get(const char *s, size_t blen); + +/////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif //__LIB_TURN_MSG__ diff --git a/src/client/ns_turn_msg_addr.c b/src/client/ns_turn_msg_addr.c new file mode 100644 index 0000000..d9193ca --- /dev/null +++ b/src/client/ns_turn_msg_addr.c @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "ns_turn_msg_addr.h" + +////////////////////////////////////////////////////////////////////////////// + +int stun_addr_encode(const ioa_addr* ca, u08bits *cfield, int *clen, int xor_ed, u32bits mc, const u08bits *tsx_id) { + + if(!cfield || !clen || !ca || !tsx_id) return -1; + + if (ca->ss.sa_family == AF_INET || ca->ss.sa_family==0) { + + /* IPv4 address */ + + *clen=8; + + cfield[0]=0; + cfield[1]=1; //IPv4 family + + if (xor_ed) { + + /* Port */ + ((u16bits*)cfield)[1] = (ca->s4.sin_port) ^ nswap16(mc >> 16); + + /* Address */ + ((u32bits*)cfield)[1] = (ca->s4.sin_addr.s_addr) ^ nswap32(mc); + + } else { + + /* Port */ + ((u16bits*)cfield)[1]=ca->s4.sin_port; + + /* Address */ + ((u32bits*)cfield)[1]=ca->s4.sin_addr.s_addr; + } + + } else if (ca->ss.sa_family == AF_INET6) { + + /* IPv6 address */ + + *clen=20; + + cfield[0]=0; + cfield[1]=2; //IPv6 family + + if (xor_ed) { + + unsigned int i; + u08bits *dst = ((u08bits*)cfield)+4; + const u08bits *src = (const u08bits*)&(ca->s6.sin6_addr); + u32bits magic = nswap32(mc); + + /* Port */ + ((u16bits*)cfield)[1] = ca->s6.sin6_port ^ nswap16(mc >> 16); + + /* Address */ + + for (i=0; i<4; ++i) { + dst[i] = (u08bits)(src[i] ^ ((const u08bits*)&magic)[i]); + } + for (i=0; i<12; ++i) { + dst[i+4] = (u08bits)(src[i+4] ^ tsx_id[i]); + } + + } else { + + /* Port */ + ((u16bits*)cfield)[1]=ca->s6.sin6_port; + + /* Address */ + ns_bcopy(&ca->s6.sin6_addr, ((u08bits*)cfield)+4, 16); + } + + } else { + return -1; + } + + return 0; +} + +int stun_addr_decode(ioa_addr* ca, const u08bits *cfield, int len, int xor_ed, u32bits mc, const u08bits *tsx_id) { + + if(!cfield || !len || !ca || !tsx_id || (len<8)) return -1; + + if(cfield[0]!=0) { + return -1; + } + + int sa_family; + + if(cfield[1]==1) sa_family=AF_INET; + else if(cfield[1]==2) sa_family=AF_INET6; + else return -1; + + ca->ss.sa_family=sa_family; + + if (sa_family == AF_INET) { + + if(len!=8) return -1; + + /* IPv4 address */ + + /* Port */ + ca->s4.sin_port=((const u16bits*)cfield)[1]; + + /* Address */ + ca->s4.sin_addr.s_addr=((const u32bits*)cfield)[1]; + + if (xor_ed) { + ca->s4.sin_port ^= nswap16(mc >> 16); + ca->s4.sin_addr.s_addr ^= nswap32(mc); + } + + } else if (sa_family == AF_INET6) { + + /* IPv6 address */ + + if(len!=20) return -1; + + /* Port */ + ca->s6.sin6_port = ((const u16bits*)cfield)[1]; + + /* Address */ + ns_bcopy(((const u08bits*)cfield)+4, &ca->s6.sin6_addr, 16); + + if (xor_ed) { + + unsigned int i; + u08bits *dst; + const u08bits *src; + u32bits magic = nswap32(mc); + + /* Port */ + ca->s6.sin6_port ^= nswap16(mc >> 16); + + /* Address */ + src = ((const u08bits*)cfield)+4; + dst = (u08bits*)&ca->s6.sin6_addr; + for (i=0; i<4; ++i) { + dst[i] = (u08bits)(src[i] ^ ((const u08bits*)&magic)[i]); + } + for (i=0; i<12; ++i) { + dst[i+4] = (u08bits)(src[i+4] ^ tsx_id[i]); + } + } + + } else { + return -1; + } + + return 0; +} + +////////////////////////////////////////////////////////////////////////////// + diff --git a/src/client/ns_turn_msg_addr.h b/src/client/ns_turn_msg_addr.h new file mode 100644 index 0000000..6d03c3c --- /dev/null +++ b/src/client/ns_turn_msg_addr.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __LIB_TURN_MSG_ADDR__ +#define __LIB_TURN_MSG_ADDR__ + +#include "ns_turn_ioaddr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/////////////////////////////////////////// + +int stun_addr_encode(const ioa_addr* ca, u08bits *cfield, int *clen, int xor_ed, u32bits mc, const u08bits *tsx_id); +int stun_addr_decode(ioa_addr* ca, const u08bits *cfield, int len, int xor_ed, u32bits mc, const u08bits *tsx_id); + +/////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif //__LIB_TURN_MSG_ADDR__ diff --git a/src/client/ns_turn_msg_defs.h b/src/client/ns_turn_msg_defs.h new file mode 100644 index 0000000..431e1e8 --- /dev/null +++ b/src/client/ns_turn_msg_defs.h @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __LIB_TURN_MSG_DEFS__ +#define __LIB_TURN_MSG_DEFS__ + +/////////////////////////////////////////// +// http://www.iana.org/assignments/stun-parameters/stun-parameters.xhtml +/////////////////////////////////////////// + +#define STUN_HEADER_LENGTH (20) +#define STUN_CHANNEL_HEADER_LENGTH (4) + +#define STUN_MAX_USERNAME_SIZE (513) +#define STUN_MAX_REALM_SIZE (127) +#define STUN_MAX_ORIGIN_SIZE (127) +#define STUN_MAX_NONCE_SIZE (127) +#define STUN_MAX_PWD_SIZE (127) + +#define STUN_MAGIC_COOKIE (0x2112A442) + +#define IS_STUN_REQUEST(msg_type) (((msg_type) & 0x0110) == 0x0000) +#define IS_STUN_INDICATION(msg_type) (((msg_type) & 0x0110) == 0x0010) +#define IS_STUN_SUCCESS_RESP(msg_type) (((msg_type) & 0x0110) == 0x0100) +#define IS_STUN_ERR_RESP(msg_type) (((msg_type) & 0x0110) == 0x0110) + +#define GET_STUN_REQUEST(msg_type) (msg_type & 0xFEEF) +#define GET_STUN_INDICATION(msg_type) ((msg_type & 0xFEEF)|0x0010) +#define GET_STUN_SUCCESS_RESP(msg_type) ((msg_type & 0xFEEF)|0x0100) +#define GET_STUN_ERR_RESP(msg_type) (msg_type | 0x0110) + +/* Lifetimes: */ +#define STUN_DEFAULT_ALLOCATE_LIFETIME (600) +#define STUN_MIN_ALLOCATE_LIFETIME STUN_DEFAULT_ALLOCATE_LIFETIME +#define STUN_MAX_ALLOCATE_LIFETIME (3600) +#define STUN_CHANNEL_LIFETIME (600) +#define STUN_PERMISSION_LIFETIME (300) +#define STUN_NONCE_EXPIRATION_TIME (600) +/**/ + +#define STUN_METHOD_BINDING (0x0001) +#define STUN_METHOD_ALLOCATE (0x0003) +#define STUN_METHOD_REFRESH (0x0004) +#define STUN_METHOD_SEND (0x0006) +#define STUN_METHOD_DATA (0x0007) +#define STUN_METHOD_CREATE_PERMISSION (0x0008) +#define STUN_METHOD_CHANNEL_BIND (0x0009) + +/* RFC 6062 ==>>*/ +#define STUN_METHOD_CONNECT (0x000a) +#define STUN_METHOD_CONNECTION_BIND (0x000b) +#define STUN_METHOD_CONNECTION_ATTEMPT (0x000c) +/* <<== RFC 6062 */ + +#define STUN_ATTRIBUTE_MAPPED_ADDRESS (0x0001) +#define OLD_STUN_ATTRIBUTE_RESPONSE_ADDRESS (0x0002) +#define STUN_ATTRIBUTE_CHANGE_REQUEST (0x0003) +#define OLD_STUN_ATTRIBUTE_SOURCE_ADDRESS (0x0004) +#define OLD_STUN_ATTRIBUTE_CHANGED_ADDRESS (0x0005) +#define STUN_ATTRIBUTE_USERNAME (0x0006) +#define OLD_STUN_ATTRIBUTE_PASSWORD (0x0007) +#define STUN_ATTRIBUTE_MESSAGE_INTEGRITY (0x0008) +#define STUN_ATTRIBUTE_ERROR_CODE (0x0009) +#define STUN_ATTRIBUTE_UNKNOWN_ATTRIBUTES (0x000A) +#define OLD_STUN_ATTRIBUTE_REFLECTED_FROM (0x000B) +#define STUN_ATTRIBUTE_REALM (0x0014) +#define STUN_ATTRIBUTE_NONCE (0x0015) +#define STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY (0x0017) +#define STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS (0x0020) +#define OLD_STUN_ATTRIBUTE_XOR_MAPPED_ADDRESS (0x8020) + +#define STUN_ATTRIBUTE_SOFTWARE (0x8022) +#define OLD_STUN_ATTRIBUTE_SERVER STUN_ATTRIBUTE_SOFTWARE +#define STUN_ATTRIBUTE_ALTERNATE_SERVER (0x8023) +#define STUN_ATTRIBUTE_FINGERPRINT (0x8028) + +#define STUN_ATTRIBUTE_CHANNEL_NUMBER (0x000C) +#define STUN_ATTRIBUTE_LIFETIME (0x000D) +#define STUN_ATTRIBUTE_BANDWIDTH (0x0010) +#define STUN_ATTRIBUTE_XOR_PEER_ADDRESS (0x0012) +#define STUN_ATTRIBUTE_DATA (0x0013) +#define STUN_ATTRIBUTE_XOR_RELAYED_ADDRESS (0x0016) +#define STUN_ATTRIBUTE_EVEN_PORT (0x0018) +#define STUN_ATTRIBUTE_REQUESTED_TRANSPORT (0x0019) +#define STUN_ATTRIBUTE_DONT_FRAGMENT (0x001A) +#define STUN_ATTRIBUTE_TIMER_VAL (0x0021) +#define STUN_ATTRIBUTE_RESERVATION_TOKEN (0x0022) + +/* ICE */ +#define STUN_ATTRIBUTE_PRIORITY (0x0024) +#define STUN_ATTRIBUTE_ICE_CONTROLLED (0x8029) + +/* RFC 5780 */ +#define STUN_ATTRIBUTE_PADDING (0x0026) +#define STUN_ATTRIBUTE_RESPONSE_PORT (0x0027) +#define STUN_ATTRIBUTE_RESPONSE_ORIGIN (0x802b) +#define STUN_ATTRIBUTE_OTHER_ADDRESS (0x802c) + +/* RFC 6062 ==>> */ +#define STUN_ATTRIBUTE_CONNECTION_ID (0x002a) +/* <<== RFC 6062 */ + +#define STUN_VALID_CHANNEL(chn) ((chn)>=0x4000 && (chn)<=0x7FFF) + +///////// values ////////////////// + +/* RFC 6156 ==>> */ +#define STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV4 (0x01) +#define STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV6 (0x02) +#define STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_DEFAULT (0x00) +#define STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_INVALID (-1) +/* <<== RFC 6156 */ + +/* RFC 6062 ==>> */ +#define STUN_ATTRIBUTE_TRANSPORT_TCP_VALUE (6) +#define STUN_ATTRIBUTE_TRANSPORT_UDP_VALUE (17) +#define STUN_ATTRIBUTE_TRANSPORT_TLS_VALUE (56) +#define STUN_ATTRIBUTE_TRANSPORT_DTLS_VALUE (250) +/* <<== RFC 6062 */ + +/* Mobility ==>> */ +#define STUN_ATTRIBUTE_MOBILITY_TICKET (0x802E) +#define STUN_ATTRIBUTE_MOBILITY_EVENT (0x802) +#define STUN_ATTRIBUTE_MOBILITY_SUPPORT (0x8000) +/* <<== Mobility */ + +/* Origin ==>> */ +#define STUN_ATTRIBUTE_ORIGIN (0x802F) +/* <<== Origin */ + +//////////////////////////////////////////////// + +#endif //__LIB_TURN_MSG_DEFS__ diff --git a/src/ns_turn_defs.h b/src/ns_turn_defs.h new file mode 100644 index 0000000..bbf364d --- /dev/null +++ b/src/ns_turn_defs.h @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __IOADEFS__ +#define __IOADEFS__ + +#define TURN_SERVER_VERSION "3.3.0.0" +#define TURN_SERVER_VERSION_NAME "Threetrees" +#define TURN_SOFTWARE "Coturn-"TURN_SERVER_VERSION" '"TURN_SERVER_VERSION_NAME"'" + +#if (defined(__unix__) || defined(unix)) && !defined(USG) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/////////////////////////////////////////// + +/* NS types: */ + +#define s08bits char +#define s16bits int16_t +#define s32bits int32_t +#define s64bits int64_t + +#define u08bits unsigned char +#define u16bits uint16_t +#define u32bits uint32_t +#define u64bits uint64_t + +#define ns_bcopy(src,dst,sz) bcopy((src),(dst),(sz)) +#define ns_bzero(ptr,sz) bzero((ptr),(sz)) + +#define nswap16(s) ntohs(s) +#define nswap32(ul) ntohl(ul) +#define nswap64(ull) ioa_ntoh64(ull) + +static inline u64bits _ioa_ntoh64(u64bits v) +{ +#if BYTE_ORDER == LITTLE_ENDIAN + u08bits *src = (u08bits*) &v; + u08bits* dst = src + 7; + while (src < dst) { + u08bits vdst = *dst; + *(dst--) = *src; + *(src++) = vdst; + } +#elif BYTE_ORDER == BIG_ENDIAN + /* OK */ +#else +#error WRONG BYTE_ORDER SETTING +#endif + return v; +} + +/* TTL */ +#define TTL_IGNORE ((int)(-1)) +#define TTL_DEFAULT (64) + +/* TOS */ +#define TOS_IGNORE ((int)(-1)) +#define TOS_DEFAULT (0) + +#define ioa_ntoh64 _ioa_ntoh64 +#define ioa_hton64 _ioa_ntoh64 + +#define turn_malloc(sz) malloc(sz) +#define turn_free(ptr,sz) free(ptr) +#define turn_realloc(ptr, old_sz, new_sz) realloc((ptr),(new_sz)) +#define turn_calloc(number, sz) calloc((number),(sz)) + +#define turn_time() ((turn_time_t)time(NULL)) + +typedef int vint; +typedef vint* vintp; + +typedef u32bits turn_time_t; + +#define turn_time_before(t1,t2) ((((s32bits)(t1))-((s32bits)(t2))) < 0) + +#if !defined(UNUSED_ARG) +#define UNUSED_ARG(A) do { A=A; } while(0) +#endif + +#define MAX_STUN_MESSAGE_SIZE (65507) +#define STUN_BUFFER_SIZE (MAX_STUN_MESSAGE_SIZE) +#define UDP_STUN_BUFFER_SIZE (1024<<4) + +#define NONCE_LENGTH_32BITS (4) + +#define DEFAULT_STUN_PORT (3478) +#define DEFAULT_STUN_TLS_PORT (5349) + +#if BYTE_ORDER == LITTLE_ENDIAN +#define DEFAULT_STUN_PORT_NBO (0x960D) +#elif BYTE_ORDER == BIG_ENDIAN +#define DEFAULT_STUN_PORT_NBO (0x0D96) +#else +#error WRONG BYTE_ORDER SETTING +#endif + +#define STRCPY(dst,src) \ + do { if((const char*)(dst) != (const char*)(src)) { \ + if(sizeof(dst)==sizeof(char*))\ + strcpy(((char*)(dst)),(const char*)(src));\ + else {\ + size_t szdst = sizeof((dst));\ + strncpy((char*)(dst),(const char*)(src),szdst);\ + ((char*)(dst))[szdst-1] = 0;\ + }\ + } } while(0) + +////////////////// Security //////////////////////////// + +#define SHA1SIZEBYTES (20) +#define SHA256SIZEBYTES (32) + +#define MAXSHASIZE (128) + +enum _SHATYPE { + SHATYPE_SHA1 = 0, + SHATYPE_SHA256 +}; + +typedef enum _SHATYPE SHATYPE; + +#define shatype_name(sht) ((sht == SHATYPE_SHA1) ? "SHA1" : ((sht == SHATYPE_SHA256) ? "SHA256" : "SHA UNKNOWN")) + +#define SHA_TOO_WEAK (426) + +//////////////////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif +/* __IODEFS__ */ diff --git a/src/server/ns_turn_allocation.c b/src/server/ns_turn_allocation.c new file mode 100644 index 0000000..e337aab --- /dev/null +++ b/src/server/ns_turn_allocation.c @@ -0,0 +1,694 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "ns_turn_allocation.h" + +/////////////// Permission forward declarations ///////////////// + +static void init_turn_permission_hashtable(turn_permission_hashtable *map); +static void free_turn_permission_hashtable(turn_permission_hashtable *map); +static turn_permission_info* get_from_turn_permission_hashtable(turn_permission_hashtable *map, const ioa_addr *addr); + +/////////////// ALLOCATION ////////////////////////////////////// + +void init_allocation(void *owner, allocation* a, ur_map *tcp_connections) { + if(a) { + ns_bzero(a,sizeof(allocation)); + a->owner = owner; + a->tcp_connections = tcp_connections; + init_turn_permission_hashtable(&(a->addr_to_perm)); + } +} + +void clear_allocation(allocation *a) +{ + if (a) { + + if(a->is_valid) + turn_report_allocation_delete(a); + + if(a->tcs.elems) { + size_t i; + size_t sz = a->tcs.sz; + for(i=0;itcs.elems[i]; + if(tc) { + delete_tcp_connection(tc); + a->tcs.elems[i] = NULL; + break; + } + } + turn_free(a->tcs.elems,sz*sizeof(tcp_connection*)); + a->tcs.elems = NULL; + } + a->tcs.sz = 0; + + clear_ioa_socket_session_if(a->relay_session.s, a->owner); + clear_ts_ur_session_data(&(a->relay_session)); + + IOA_EVENT_DEL(a->lifetime_ev); + + /* The order is important here: */ + free_turn_permission_hashtable(&(a->addr_to_perm)); + ch_map_clean(&(a->chns)); + + a->is_valid=0; + } +} + +ts_ur_session *get_relay_session(allocation *a) +{ + return &(a->relay_session); +} + +ioa_socket_handle get_relay_socket(allocation *a) +{ + return a->relay_session.s; +} + +void set_allocation_lifetime_ev(allocation *a, turn_time_t exp_time, ioa_timer_handle ev) +{ + if (a) { + IOA_EVENT_DEL(a->lifetime_ev); + a->expiration_time = exp_time; + a->lifetime_ev = ev; + } +} + +int is_allocation_valid(const allocation* a) { + if(a) return a->is_valid; + else return 0; +} + +void set_allocation_valid(allocation* a, int value) { + if(a) a->is_valid=value; +} + +turn_permission_info* allocation_get_permission(allocation* a, const ioa_addr *addr) { + if(a) { + return get_from_turn_permission_hashtable(&(a->addr_to_perm), addr); + } + return NULL; +} + +///////////////////////////// TURN_PERMISSION ///////////////////////////////// + +static int delete_channel_info_from_allocation_map(ur_map_key_type key, ur_map_value_type value); + +void turn_permission_clean(turn_permission_info* tinfo) +{ + if (tinfo && tinfo->allocated) { + + if(!(tinfo->lifetime_ev)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s: strange (1) permission to be cleaned\n",__FUNCTION__); + } + + IOA_EVENT_DEL(tinfo->lifetime_ev); + lm_map_foreach(&(tinfo->chns), (foreachcb_type) delete_channel_info_from_allocation_map); + lm_map_clean(&(tinfo->chns)); + ns_bzero(tinfo,sizeof(turn_permission_info)); + } +} + +static void init_turn_permission_hashtable(turn_permission_hashtable *map) +{ + if (map) + ns_bzero(map,sizeof(turn_permission_hashtable)); +} + +static void free_turn_permission_hashtable(turn_permission_hashtable *map) +{ + if(map) { + + size_t i; + for(i=0;itable[i]); + + { + size_t j; + for(j=0;jmain_slots[j]); + if(slot->info.allocated) { + turn_permission_clean(&(slot->info)); + } + } + } + + if(parray->extra_slots) { + size_t j; + for(j=0;jextra_sz;++j) { + turn_permission_slot *slot = parray->extra_slots[j]; + if(slot) { + if(slot->info.allocated) { + turn_permission_clean(&(slot->info)); + } + turn_free(slot,sizeof(turn_permission_slot)); + } + } + turn_free(parray->extra_slots, parray->extra_sz * sizeof(turn_permission_slot*)); + parray->extra_slots = NULL; + } + parray->extra_sz = 0; + } + } +} + +static turn_permission_info* get_from_turn_permission_hashtable(turn_permission_hashtable *map, const ioa_addr *addr) +{ + if (!addr || !map) + return NULL; + + u32bits index = addr_hash_no_port(addr) & (TURN_PERMISSION_HASHTABLE_SIZE-1); + turn_permission_array *parray = &(map->table[index]); + + { + size_t i; + for (i = 0; i < TURN_PERMISSION_ARRAY_SIZE; ++i) { + turn_permission_slot *slot = &(parray->main_slots[i]); + if (slot->info.allocated && addr_eq_no_port(&(slot->info.addr), addr)) { + return &(slot->info); + } + } + } + + if(parray->extra_slots) { + + size_t i; + size_t sz = parray->extra_sz; + for (i = 0; i < sz; ++i) { + turn_permission_slot *slot = parray->extra_slots[i]; + if (slot->info.allocated && addr_eq_no_port(&(slot->info.addr), addr)) { + return &(slot->info); + } + } + } + + return NULL; +} + +static void ch_info_clean(ch_info* c) { + if(c) { + IOA_EVENT_DEL(c->lifetime_ev); + ns_bzero(c,sizeof(ch_info)); + } +} + +static int delete_channel_info_from_allocation_map(ur_map_key_type key, ur_map_value_type value) +{ + UNUSED_ARG(key); + + if(value) { + ch_info* chn = (ch_info*)value; + + if(chn->chnum <1) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s: strange (0) channel to be cleaned: chnum<1\n",__FUNCTION__); + } + + ch_info_clean(chn); + } + + return 0; +} + +void turn_channel_delete(ch_info* chn) +{ + if(chn) { + int port = addr_get_port(&(chn->peer_addr)); + if(port<1) { + char s[129]; + addr_to_string(&(chn->peer_addr),(u08bits*)s); + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s: strange (1) channel to be cleaned: port is empty: %s\n",__FUNCTION__,s); + } + { + turn_permission_info* tinfo = (turn_permission_info*)chn->owner; + if(tinfo) { + lm_map_del(&(tinfo->chns), (ur_map_key_type)port,NULL); + } else { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s: strange (2) channel to be cleaned: permission is empty\n",__FUNCTION__); + } + } + delete_channel_info_from_allocation_map((ur_map_key_type)port,(ur_map_value_type)chn); + } +} + +ch_info* allocation_get_new_ch_info(allocation* a, u16bits chnum, ioa_addr* peer_addr) +{ + + turn_permission_info* tinfo = get_from_turn_permission_hashtable(&(a->addr_to_perm), peer_addr); + + if (!tinfo) + tinfo = allocation_add_permission(a, peer_addr); + + ch_info* chn = ch_map_get(&(a->chns), chnum, 1); + + chn->allocated = 1; + chn->chnum = chnum; + chn->port = addr_get_port(peer_addr); + addr_cpy(&(chn->peer_addr), peer_addr); + chn->owner = tinfo; + + lm_map_put(&(tinfo->chns), (ur_map_key_type) addr_get_port(peer_addr), (ur_map_value_type) chn); + + return chn; +} + +ch_info* allocation_get_ch_info(allocation* a, u16bits chnum) { + return ch_map_get(&(a->chns), chnum, 0); +} + +ch_info* allocation_get_ch_info_by_peer_addr(allocation* a, ioa_addr* peer_addr) { + turn_permission_info* tinfo = get_from_turn_permission_hashtable(&(a->addr_to_perm), peer_addr); + if(tinfo) { + return get_turn_channel(tinfo,peer_addr); + } + return NULL; +} + +u16bits get_turn_channel_number(turn_permission_info* tinfo, ioa_addr *addr) +{ + if (tinfo) { + ur_map_value_type t = 0; + if (lm_map_get(&(tinfo->chns), (ur_map_key_type)addr_get_port(addr), &t) && t) { + ch_info* chn = (ch_info*) t; + if (STUN_VALID_CHANNEL(chn->chnum)) { + return chn->chnum; + } + } + } + + return 0; +} + +ch_info *get_turn_channel(turn_permission_info* tinfo, ioa_addr *addr) +{ + if (tinfo) { + ur_map_value_type t = 0; + if (lm_map_get(&(tinfo->chns), (ur_map_key_type)addr_get_port(addr), &t) && t) { + ch_info* chn = (ch_info*) t; + if (STUN_VALID_CHANNEL(chn->chnum)) { + return chn; + } + } + } + + return NULL; +} + +turn_permission_hashtable *allocation_get_turn_permission_hashtable(allocation *a) +{ + return &(a->addr_to_perm); +} + +turn_permission_info* allocation_add_permission(allocation *a, const ioa_addr* addr) +{ + if (a && addr) { + + turn_permission_hashtable *map = &(a->addr_to_perm); + u32bits hash = addr_hash_no_port(addr); + size_t fds = (size_t) (hash & (TURN_PERMISSION_HASHTABLE_SIZE-1)); + + turn_permission_array *parray = &(map->table[fds]); + + turn_permission_slot *slot = NULL; + + { + size_t i; + for(i=0;imain_slots[i]); + if(!(slot->info.allocated)) { + break; + } else { + slot=NULL; + } + } + } + + if(!slot) { + + size_t old_sz = parray->extra_sz; + + turn_permission_slot **slots = parray->extra_slots; + + if(slots) { + size_t i; + for(i=0;iinfo.allocated)) { + break; + } else { + slot=NULL; + } + } + } + + if(!slot) { + size_t old_sz_mem = old_sz * sizeof(turn_permission_slot*); + parray->extra_slots = (turn_permission_slot **) turn_realloc(parray->extra_slots, + old_sz_mem, old_sz_mem + sizeof(turn_permission_slot*)); + slots = parray->extra_slots; + parray->extra_sz = old_sz + 1; + slots[old_sz] = (turn_permission_slot *)turn_malloc(sizeof(turn_permission_slot)); + slot = slots[old_sz]; + } + } + + ns_bzero(slot,sizeof(turn_permission_slot)); + slot->info.allocated = 1; + turn_permission_info *elem = &(slot->info); + addr_cpy(&(elem->addr), addr); + elem->owner = a; + + return elem; + } else { + return NULL; + } +} + +ch_info *ch_map_get(ch_map* map, u16bits chnum, int new_chn) +{ + ch_info *ret = NULL; + if(map) { + size_t index = (size_t)(chnum & (CH_MAP_HASH_SIZE-1)); + ch_map_array *a = &(map->table[index]); + + size_t i; + for(i=0;imain_chns[i]); + if(chi->allocated) { + if(!new_chn && (chi->chnum == chnum)) { + return chi; + } + } else if(new_chn) { + return chi; + } + } + + size_t old_sz = a->extra_sz; + if(old_sz && a->extra_chns) { + for(i=0;iextra_chns[i]; + if(chi) { + if(chi->allocated) { + if(!new_chn && (chi->chnum == chnum)) { + return chi; + } + } else if(new_chn) { + return chi; + } + } + } + } + + if(new_chn) { + size_t old_sz_mem = old_sz * sizeof(ch_info*); + a->extra_chns = (ch_info**)turn_realloc(a->extra_chns,old_sz_mem,old_sz_mem + sizeof(ch_info*)); + a->extra_chns[old_sz] = (ch_info*)turn_malloc(sizeof(ch_info)); + ns_bzero(a->extra_chns[old_sz],sizeof(ch_info)); + a->extra_sz += 1; + + return a->extra_chns[old_sz]; + } + } + + return ret; +} + +void ch_map_clean(ch_map* map) +{ + if(map) { + size_t index; + for(index = 0; index < CH_MAP_HASH_SIZE; ++index) { + + ch_map_array *a = &(map->table[index]); + + size_t i; + for(i=0;imain_chns[i]); + if(chi->allocated) { + ch_info_clean(chi); + } + } + + if(a->extra_chns) { + size_t sz = a->extra_sz; + for(i=0;iextra_chns[i]; + if(chi) { + if(chi->allocated) { + ch_info_clean(chi); + } + turn_free(chi,sizeof(ch_info)); + a->extra_chns[i] = NULL; + } + } + turn_free(a->extra_chns, sizeof(ch_info*)*sz); + a->extra_chns = NULL; + } + a->extra_sz = 0; + } + } +} + +////////////////// TCP connections /////////////////////////////// + +static void set_new_tc_id(u08bits server_id, tcp_connection *tc) { + allocation *a = (allocation*)(tc->owner); + ur_map *map = a->tcp_connections; + u32bits newid; + u32bits sid = server_id; + sid = sid<<24; + do { + newid = 0; + while (!newid) { + newid = (u32bits)turn_random(); + if(!newid) { + continue; + } + newid = newid & 0x00FFFFFF; + if(!newid) { + continue; + } + newid = newid | sid; + } + } while(ur_map_get(map, (ur_map_key_type)newid, NULL)); + tc->id = newid; + ur_map_put(map, (ur_map_key_type)newid, (ur_map_value_type)tc); +} + +tcp_connection *create_tcp_connection(u08bits server_id, allocation *a, stun_tid *tid, ioa_addr *peer_addr, int *err_code) +{ + tcp_connection_list *tcl = &(a->tcs); + if(tcl->elems) { + size_t i; + for(i=0;isz;++i) { + tcp_connection *otc = tcl->elems[i]; + if(otc) { + if(addr_eq(&(otc->peer_addr),peer_addr)) { + *err_code = 446; + return NULL; + } + } + } + } + tcp_connection *tc = (tcp_connection*)turn_malloc(sizeof(tcp_connection)); + ns_bzero(tc,sizeof(tcp_connection)); + addr_cpy(&(tc->peer_addr),peer_addr); + if(tid) + ns_bcopy(tid,&(tc->tid),sizeof(stun_tid)); + tc->owner = a; + + int found = 0; + if(a->tcs.elems) { + size_t i; + for(i=0;isz;++i) { + tcp_connection *otc = tcl->elems[i]; + if(!otc) { + tcl->elems[i] = tc; + found = 1; + break; + } + } + } + + if(!found) { + size_t old_sz_mem = a->tcs.sz * sizeof(tcp_connection*); + a->tcs.elems = (tcp_connection**)turn_realloc(a->tcs.elems,old_sz_mem,old_sz_mem+sizeof(tcp_connection*)); + a->tcs.elems[a->tcs.sz] = tc; + a->tcs.sz += 1; + tcl = &(a->tcs); + } + + set_new_tc_id(server_id, tc); + return tc; +} + +void delete_tcp_connection(tcp_connection *tc) +{ + if(tc) { + if(tc->done) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "!!! %s: check on already closed tcp data connection: 0x%lx\n",__FUNCTION__,(unsigned long)tc); + return; + } + tc->done = 1; + + clear_unsent_buffer(&(tc->ub_to_client)); + + IOA_EVENT_DEL(tc->peer_conn_timeout); + IOA_EVENT_DEL(tc->conn_bind_timeout); + allocation *a = (allocation*)(tc->owner); + if(a) { + ur_map *map = a->tcp_connections; + if(map) { + ur_map_del(map, (ur_map_key_type)(tc->id),NULL); + } + tcp_connection_list *tcl = &(a->tcs); + if(tcl->elems) { + size_t i; + for(i=0;isz;++i) { + if(tcl->elems[i] == tc) { + tcl->elems[i] = NULL; + break; + } + } + } + } + IOA_CLOSE_SOCKET(tc->client_s); + IOA_CLOSE_SOCKET(tc->peer_s); + turn_free(tc,sizeof(tcp_connection)); + } +} + +tcp_connection *get_and_clean_tcp_connection_by_id(ur_map *map, tcp_connection_id id) +{ + if(map) { + ur_map_value_type t = 0; + if (ur_map_get(map, (ur_map_key_type)id, &t) && t) { + ur_map_del(map, (ur_map_key_type)id,NULL); + return (tcp_connection*)t; + } + } + return NULL; +} + +tcp_connection *get_tcp_connection_by_peer(allocation *a, ioa_addr *peer_addr) +{ + if(a && peer_addr) { + tcp_connection_list *tcl = &(a->tcs); + if(tcl->elems) { + size_t i; + size_t sz = tcl->sz; + for(i=0;ielems[i]; + if(tc) { + if(addr_eq(&(tc->peer_addr),peer_addr)) { + return tc; + } + } + } + } + } + return NULL; +} + +int can_accept_tcp_connection_from_peer(allocation *a, ioa_addr *peer_addr, int server_relay) +{ + if(server_relay) + return 1; + + if(a && peer_addr) { + return (get_from_turn_permission_hashtable(&(a->addr_to_perm), peer_addr) != NULL); + } + + return 0; +} + +//////////////// Unsent buffers ////////////////////// + +void clear_unsent_buffer(unsent_buffer *ub) +{ + if(ub) { + if(ub->bufs) { + size_t sz; + for(sz = 0; szsz; sz++) { + ioa_network_buffer_handle nbh = ub->bufs[sz]; + if(nbh) { + ioa_network_buffer_delete(NULL, nbh); + ub->bufs[sz] = NULL; + } + } + turn_free(ub->bufs,sizeof(ioa_network_buffer_handle) * ub->sz); + ub->bufs = NULL; + } + ub->sz = 0; + } +} + +void add_unsent_buffer(unsent_buffer *ub, ioa_network_buffer_handle nbh) +{ + if(!ub || (ub->sz >= MAX_UNSENT_BUFFER_SIZE)) { + ioa_network_buffer_delete(NULL, nbh); + } else { + ub->bufs = (ioa_network_buffer_handle*)turn_realloc(ub->bufs, sizeof(ioa_network_buffer_handle) * ub->sz, sizeof(ioa_network_buffer_handle) * (ub->sz+1)); + ub->bufs[ub->sz] = nbh; + ub->sz +=1; + } +} + +ioa_network_buffer_handle top_unsent_buffer(unsent_buffer *ub) +{ + ioa_network_buffer_handle ret = NULL; + if(ub && ub->bufs && ub->sz) { + size_t sz; + for(sz=0; szsz; ++sz) { + if(ub->bufs[sz]) { + ret = ub->bufs[sz]; + break; + } + } + } + return ret; +} + +void pop_unsent_buffer(unsent_buffer *ub) +{ + if(ub && ub->bufs && ub->sz) { + size_t sz; + for(sz=0; szsz; ++sz) { + if(ub->bufs[sz]) { + ub->bufs[sz] = NULL; + break; + } + } + } +} + +////////////////////////////////////////////////////////////////// + diff --git a/src/server/ns_turn_allocation.h b/src/server/ns_turn_allocation.h new file mode 100644 index 0000000..200db1f --- /dev/null +++ b/src/server/ns_turn_allocation.h @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __TURN_TURN_A_LIB__ +#define __TURN_TURN_A_LIB__ + +#include "ns_turn_utils.h" +#include "ns_turn_msg.h" +#include "ns_turn_ioalib.h" +#include "ns_turn_maps.h" + +#ifdef __cplusplus +extern "C" { +#endif + +///////// Defines ////////// + +#define TCP_PEER_CONN_TIMEOUT (30) +#define TCP_CONN_BIND_TIMEOUT (30) + +///////// types //////////// + +enum _UR_STATE { + UR_STATE_UNKNOWN=0, + UR_STATE_READY, + UR_STATE_DONE +}; + +typedef enum _UR_STATE UR_STATE; + +////////////// Network session //////////////// + +typedef struct +{ + UR_STATE state; + ioa_socket_handle s; + int known_mtu; +} ts_ur_session; + +static inline void clear_ts_ur_session_data(ts_ur_session* cdi) +{ + if (cdi) + IOA_CLOSE_SOCKET(cdi->s); +} + +////////// RFC 6062 TCP connection //////// + +#define MAX_UNSENT_BUFFER_SIZE (0x10) + +enum _TC_STATE { + TC_STATE_UNKNOWN=0, + TC_STATE_CLIENT_TO_PEER_CONNECTING, + TC_STATE_PEER_CONNECTING, + TC_STATE_PEER_CONNECTED, + TC_STATE_READY, + TC_STATE_FAILED +}; + +typedef enum _TC_STATE TC_STATE; + +typedef u32bits tcp_connection_id; + +typedef struct { + size_t sz; + ioa_network_buffer_handle *bufs; +} unsent_buffer; + +struct _tcp_connection +{ + TC_STATE state; + tcp_connection_id id; + ioa_addr peer_addr; + ioa_socket_handle client_s; + ioa_socket_handle peer_s; + ioa_timer_handle peer_conn_timeout; + ioa_timer_handle conn_bind_timeout; + stun_tid tid; + void *owner; //a + int done; + unsent_buffer ub_to_client; +}; + +typedef struct _tcp_connection_list { + size_t sz; + tcp_connection **elems; +} tcp_connection_list; + +//////////////////////////////// + +#define TURN_PERMISSION_HASHTABLE_SIZE (0x8) +#define TURN_PERMISSION_ARRAY_SIZE (0x3) + +struct _allocation; + +typedef struct _ch_info { + u16bits chnum; + int allocated; + u16bits port; + ioa_addr peer_addr; + turn_time_t expiration_time; + ioa_timer_handle lifetime_ev; + void *owner; //perm +} ch_info; + +///////////// "channel" map ///////////////////// + +#define CH_MAP_HASH_SIZE (0x8) +#define CH_MAP_ARRAY_SIZE (0x3) + +typedef struct _chn_map_array { + ch_info main_chns[CH_MAP_ARRAY_SIZE]; + size_t extra_sz; + ch_info **extra_chns; +} ch_map_array; + +typedef struct _ch_map { + ch_map_array table[CH_MAP_HASH_SIZE]; +} ch_map; + +ch_info *ch_map_get(ch_map* map, u16bits chnum, int new_chn); +void ch_map_clean(ch_map* map); + +//////////////////////////// + +typedef struct _turn_permission_info { + int allocated; + lm_map chns; + ioa_addr addr; + turn_time_t expiration_time; + ioa_timer_handle lifetime_ev; + void* owner; //a +} turn_permission_info; + +typedef struct _turn_permission_slot { + turn_permission_info info; +} turn_permission_slot; + +typedef struct _turn_permission_array { + turn_permission_slot main_slots[TURN_PERMISSION_ARRAY_SIZE]; + size_t extra_sz; + turn_permission_slot **extra_slots; +} turn_permission_array; + +typedef struct _turn_permission_hashtable { + turn_permission_array table[TURN_PERMISSION_HASHTABLE_SIZE]; +} turn_permission_hashtable; + +//////////////// ALLOCATION ////////////////////// + +typedef struct _allocation { + int is_valid; + stun_tid tid; + turn_time_t expiration_time; + ioa_timer_handle lifetime_ev; + turn_permission_hashtable addr_to_perm; + ts_ur_session relay_session; + ch_map chns; /* chnum-to-ch_info* */ + void *owner; //ss + ur_map *tcp_connections; //global (per turn server) reference + tcp_connection_list tcs; //local reference +} allocation; + +//////////// CHANNELS //////////////////// + +u16bits get_turn_channel_number(turn_permission_info* tinfo, ioa_addr *addr); +ch_info *get_turn_channel(turn_permission_info* tinfo, ioa_addr *addr); + +void turn_channel_delete(ch_info* chn); + +/////////// ALLOCATION //////////// + +void init_allocation(void *owner, allocation* a, ur_map *tcp_connections); +void clear_allocation(allocation *a); + +void turn_permission_clean(turn_permission_info* tinfo); + +void set_allocation_lifetime_ev(allocation *a, turn_time_t exp_time, ioa_timer_handle ev); +int is_allocation_valid(const allocation* a); +void set_allocation_valid(allocation* a, int value); +turn_permission_info* allocation_get_permission(allocation* a, const ioa_addr *addr); +turn_permission_hashtable* allocation_get_turn_permission_hashtable(allocation *a); +turn_permission_info* allocation_add_permission(allocation *a, const ioa_addr* addr); + +ch_info* allocation_get_new_ch_info(allocation* a, u16bits chnum, ioa_addr* peer_addr); +ch_info* allocation_get_ch_info(allocation* a, u16bits chnum); +ch_info* allocation_get_ch_info_by_peer_addr(allocation* a, ioa_addr* peer_addr); + +ts_ur_session *get_relay_session(allocation *a); +ioa_socket_handle get_relay_socket(allocation *a); + +tcp_connection *get_and_clean_tcp_connection_by_id(ur_map *map, tcp_connection_id id); +tcp_connection *get_tcp_connection_by_peer(allocation *a, ioa_addr *peer_addr); +int can_accept_tcp_connection_from_peer(allocation *a, ioa_addr *peer_addr, int server_relay); +tcp_connection *create_tcp_connection(u08bits server_id, allocation *a, stun_tid *tid, ioa_addr *peer_addr, int *err_code); +void delete_tcp_connection(tcp_connection *tc); + +void clear_unsent_buffer(unsent_buffer *ub); +void add_unsent_buffer(unsent_buffer *ub, ioa_network_buffer_handle nbh); +ioa_network_buffer_handle top_unsent_buffer(unsent_buffer *ub); +void pop_unsent_buffer(unsent_buffer *ub); + +/////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif //__TURN_TURN_A_LIB__ diff --git a/src/server/ns_turn_ioalib.h b/src/server/ns_turn_ioalib.h new file mode 100644 index 0000000..7b9559a --- /dev/null +++ b/src/server/ns_turn_ioalib.h @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * IO Abstraction library + */ + +#ifndef __IOA_LIB__ +#define __IOA_LIB__ + +#include "ns_turn_ioaddr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +////////////// forward declarations //////// + +struct _ts_ur_super_session; +typedef struct _ts_ur_super_session ts_ur_super_session; + +struct _tcp_connection; +typedef struct _tcp_connection tcp_connection; + + +////////////// Mutexes ///////////////////// + +struct _turn_mutex { + u32bits data; + void* mutex; +}; + +typedef struct _turn_mutex turn_mutex; + +int turn_mutex_init(turn_mutex* mutex); +int turn_mutex_init_recursive(turn_mutex* mutex); + +int turn_mutex_lock(const turn_mutex *mutex); +int turn_mutex_unlock(const turn_mutex *mutex); + +int turn_mutex_destroy(turn_mutex* mutex); + +#define TURN_MUTEX_DECLARE(mutex) turn_mutex mutex; +#define TURN_MUTEX_INIT(mutex) turn_mutex_init(mutex) +#define TURN_MUTEX_INIT_RECURSIVE(mutex) turn_mutex_init_recursive(mutex) +#define TURN_MUTEX_LOCK(mutex) turn_mutex_lock(mutex) +#define TURN_MUTEX_UNLOCK(mutex) turn_mutex_unlock(mutex) +#define TURN_MUTEX_DESTROY(mutex) turn_mutex_destroy(mutex) + +/////// Sockets ////////////////////////////// + +#define IOA_EV_TIMEOUT 0x01 +#define IOA_EV_READ 0x02 +#define IOA_EV_WRITE 0x04 +#define IOA_EV_SIGNAL 0x08 +#define IOA_EV_CLOSE 0x10 + +enum _SOCKET_TYPE { + UNKNOWN_SOCKET=0, + TCP_SOCKET=6, + UDP_SOCKET=17, + TLS_SOCKET=56, + DTLS_SOCKET=250, + TENTATIVE_TCP_SOCKET=255 +}; + +typedef enum _SOCKET_TYPE SOCKET_TYPE; + +enum _SOCKET_APP_TYPE { + UNKNOWN_APP_SOCKET, + CLIENT_SOCKET, + RELAY_SOCKET, + RELAY_RTCP_SOCKET, + TCP_CLIENT_DATA_SOCKET, + TCP_RELAY_DATA_SOCKET, + LISTENER_SOCKET +}; + +typedef enum _SOCKET_APP_TYPE SOCKET_APP_TYPE; + +struct _ioa_socket; +typedef struct _ioa_socket ioa_socket; +typedef ioa_socket *ioa_socket_handle; + +struct _ioa_engine; +typedef struct _ioa_engine ioa_engine; +typedef ioa_engine *ioa_engine_handle; + +typedef void *ioa_timer_handle; + +typedef void *ioa_network_buffer_handle; + +/* event data for net event */ +typedef struct _ioa_net_data { + ioa_addr src_addr; + ioa_network_buffer_handle nbh; + int recv_ttl; + int recv_tos; +} ioa_net_data; + +/* Callback on TCP connection completion */ +typedef void (*connect_cb)(int success, void *arg); +/* Callback on accepted socket from TCP relay endpoint */ +typedef void (*accept_cb)(ioa_socket_handle s, void *arg); + +////////// REALM //////////// + +struct _realm_options_t; +typedef struct _realm_options_t realm_options_t; + +//////// IP White/black listing /////////// + +struct _ip_range_list { + char **ranges; + ioa_addr_range **encaddrsranges; + size_t ranges_number; +}; + +typedef struct _ip_range_list ip_range_list_t; + +void ioa_lock_whitelist(ioa_engine_handle e); +void ioa_unlock_whitelist(ioa_engine_handle e); +const ip_range_list_t* ioa_get_whitelist(ioa_engine_handle e); + +void ioa_lock_blacklist(ioa_engine_handle e); +void ioa_unlock_blacklist(ioa_engine_handle e); +const ip_range_list_t* ioa_get_blacklist(ioa_engine_handle e); + +//////////////////////////////////////////// + +/* + * Network buffer functions + */ +ioa_network_buffer_handle ioa_network_buffer_allocate(ioa_engine_handle e); +void ioa_network_buffer_header_init(ioa_network_buffer_handle nbh); +u08bits *ioa_network_buffer_data(ioa_network_buffer_handle nbh); +size_t ioa_network_buffer_get_size(ioa_network_buffer_handle nbh); +size_t ioa_network_buffer_get_capacity(ioa_network_buffer_handle nbh); +size_t ioa_network_buffer_get_capacity_udp(void); +void ioa_network_buffer_set_size(ioa_network_buffer_handle nbh, size_t len); +void ioa_network_buffer_add_offset_size(ioa_network_buffer_handle nbh, u16bits offset, u08bits coffset, size_t len); +u16bits ioa_network_buffer_get_offset(ioa_network_buffer_handle nbh); +u08bits ioa_network_buffer_get_coffset(ioa_network_buffer_handle nbh); +void ioa_network_buffer_delete(ioa_engine_handle e, ioa_network_buffer_handle nbh); + +/* + * Status reporting functions + */ +void turn_report_allocation_set(void *a, turn_time_t lifetime, int refresh); +void turn_report_allocation_delete(void *a); +void turn_report_session_usage(void *session); + +/* + * Network event handler callback + * chnum parameter is just an optimisation hint - + * the function must work correctly when chnum=0 + * (when no hint information is available). + */ +typedef void (*ioa_net_event_handler)(ioa_socket_handle s, int event_type, ioa_net_data *data, void *ctx); + +/* + * Timer callback + */ +typedef void (*ioa_timer_event_handler)(ioa_engine_handle e, void *ctx); + +/* timers */ + +ioa_timer_handle set_ioa_timer(ioa_engine_handle e, int secs, int ms, ioa_timer_event_handler cb, void *ctx, int persist, const s08bits *txt); +void stop_ioa_timer(ioa_timer_handle th); +void delete_ioa_timer(ioa_timer_handle th); +#define IOA_EVENT_DEL(E) do { if(E) { delete_ioa_timer(E); E = NULL; } } while(0) + +ioa_socket_handle create_unbound_ioa_socket(ioa_engine_handle e, ioa_socket_handle parent_s, int family, SOCKET_TYPE st, SOCKET_APP_TYPE sat); + +void inc_ioa_socket_ref_counter(ioa_socket_handle s); + +/* Relay socket handling */ +/* + * event_port == -1: no rtcp; + * event_port == 0: reserve rtcp; + * even_port == +1: reserve and bind rtcp. + */ +int create_relay_ioa_sockets(ioa_engine_handle e, ioa_socket_handle client_s, + int address_family, u08bits transport, + int even_port, ioa_socket_handle *rtp_s, ioa_socket_handle *rtcp_s, + u64bits *out_reservation_token, int *err_code, const u08bits **reason, + accept_cb acb, void *acbarg); + +ioa_socket_handle ioa_create_connecting_tcp_relay_socket(ioa_socket_handle s, ioa_addr *peer_addr, connect_cb cb, void *arg); + +int get_ioa_socket_from_reservation(ioa_engine_handle e, u64bits in_reservation_token, ioa_socket_handle *s); + +int get_ioa_socket_address_family(ioa_socket_handle s); +SOCKET_TYPE get_ioa_socket_type(ioa_socket_handle s); +SOCKET_APP_TYPE get_ioa_socket_app_type(ioa_socket_handle s); +const char* get_ioa_socket_tls_method(ioa_socket_handle s); +const char* get_ioa_socket_tls_cipher(ioa_socket_handle s); +void set_ioa_socket_app_type(ioa_socket_handle s, SOCKET_APP_TYPE sat); +ioa_addr* get_local_addr_from_ioa_socket(ioa_socket_handle s); +ioa_addr* get_remote_addr_from_ioa_socket(ioa_socket_handle s); +int get_local_mtu_ioa_socket(ioa_socket_handle s); +ts_ur_super_session *get_ioa_socket_session(ioa_socket_handle s); +void set_ioa_socket_session(ioa_socket_handle s, ts_ur_super_session *ss); +void clear_ioa_socket_session_if(ioa_socket_handle s, void *ss); +tcp_connection *get_ioa_socket_sub_session(ioa_socket_handle s); +void set_ioa_socket_sub_session(ioa_socket_handle s, tcp_connection *tc); +int register_callback_on_ioa_socket(ioa_engine_handle e, ioa_socket_handle s, int event_type, ioa_net_event_handler cb, void *ctx, int clean_preexisting); +int send_data_from_ioa_socket_nbh(ioa_socket_handle s, ioa_addr* dest_addr, ioa_network_buffer_handle nbh, int ttl, int tos); +void close_ioa_socket(ioa_socket_handle s); +#define IOA_CLOSE_SOCKET(S) do { if(S) { close_ioa_socket(S); S = NULL; } } while(0) +ioa_socket_handle detach_ioa_socket(ioa_socket_handle s, int full_detach); +void detach_socket_net_data(ioa_socket_handle s); +int set_df_on_ioa_socket(ioa_socket_handle s, int value); +void set_do_not_use_df(ioa_socket_handle s); +int ioa_socket_tobeclosed(ioa_socket_handle s); +void set_ioa_socket_tobeclosed(ioa_socket_handle s); +void close_ioa_socket_after_processing_if_necessary(ioa_socket_handle s); +int check_username_hash(ioa_socket_handle s, u08bits *username, u08bits *realm); +void set_username_hash(ioa_socket_handle s, u08bits *username, u08bits *realm); + +////////////////// Base64 ///////////////////////////// + +char *base64_encode(const unsigned char *data, + size_t input_length, + size_t *output_length); + +void build_base64_decoding_table(void); + +unsigned char *base64_decode(const char *data, + size_t input_length, + size_t *output_length); + +///////////// Realm /////////////////// + +void get_default_realm_options(realm_options_t* ro); +void get_realm_options_by_origin(char *origin, realm_options_t* ro); +void get_realm_options_by_name(char *realm, realm_options_t* ro); +int get_canonic_origin(const char* o, char *co, int sz); +int get_default_protocol_port(const char* scheme, size_t slen); + +/////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif /* __IOA_LIB__ */ diff --git a/src/server/ns_turn_khash.h b/src/server/ns_turn_khash.h new file mode 100644 index 0000000..813bc0c --- /dev/null +++ b/src/server/ns_turn_khash.h @@ -0,0 +1,391 @@ +/* The MIT License + + Copyright (c) 2008, by Attractive Chaos + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ + +/* Changes, 2011-2012: + proprietary header added, with proprietary memory management functions. +*/ + +/* + An example: + +#include "khash.h" +KHASH_MAP_INIT_INT(32, s08bits) +int main() { + int ret, is_missing; + khiter_t k; + khash_t(32) *h = kh_init(32); + k = kh_put(32, h, 5, &ret); + if (!ret) kh_del(32, h, k); + kh_value(h, k) = 10; + k = kh_get(32, h, 10); + is_missing = (k == kh_end(h)); + k = kh_get(32, h, 5); + kh_del(32, h, k); + for (k = kh_begin(h); k != kh_end(h); ++k) + if (kh_exist(h, k)) kh_value(h, k) = 1; + kh_destroy(32, h); + return 0; +} +*/ + +/* + 2008-09-19 (0.2.3): + + * Corrected the example + * Improved interfaces + + 2008-09-11 (0.2.2): + + * Improved speed a little in kh_put() + + 2008-09-10 (0.2.1): + + * Added kh_clear() + * Fixed a compiling error + + 2008-09-02 (0.2.0): + + * Changed to token concatenation which increases flexibility. + + 2008-08-31 (0.1.2): + + * Fixed a bug in kh_get(), which has not been tested previously. + + 2008-08-31 (0.1.1): + + * Added destructor +*/ + + +#ifndef __AC_KHASH_H +#define __AC_KHASH_H + +#define AC_VERSION_KHASH_H "0.2.2" + +#include "ns_turn_defs.h" + +typedef u32bits khint_t; +typedef khint_t khiter_t; + +typedef struct _str_chunk_t { + const s08bits *str; + size_t len; +} str_chunk_t; + +#define __ac_HASH_PRIME_SIZE 32 +static const u32bits __ac_prime_list[__ac_HASH_PRIME_SIZE] = +{ + 0ul, 3ul, 11ul, 23ul, 53ul, + 97ul, 193ul, 389ul, 769ul, 1543ul, + 3079ul, 6151ul, 12289ul, 24593ul, 49157ul, + 98317ul, 196613ul, 393241ul, 786433ul, 1572869ul, + 3145739ul, 6291469ul, 12582917ul, 25165843ul, 50331653ul, + 100663319ul, 201326611ul, 402653189ul, 805306457ul, 1610612741ul, + 3221225473ul, 4294967291ul +}; + +#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) +#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) +#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) +#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) +#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) +#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) +#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) + +static const double __ac_HASH_UPPER = 0.77; + +#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + typedef struct { \ + khint_t n_buckets, size, n_occupied, upper_bound; \ + u32bits *flags; u32bits flags_size; \ + khkey_t *keys; u32bits keys_size; \ + khval_t *vals; u32bits vals_size; \ + } kh_##name##_t; \ + static inline kh_##name##_t *kh_init_##name(void) { \ + return (kh_##name##_t*)turn_calloc(1, sizeof(kh_##name##_t)); \ + } \ + static inline void kh_destroy_##name(kh_##name##_t *h) \ + { \ + if (h) { \ + turn_free(h->keys,h->keys_size); turn_free(h->flags,h->flags_size); \ + turn_free(h->vals, h->vals_size); \ + turn_free(h, sizeof(kh_##name##_t)); \ + } \ + } \ + static inline void kh_clear_##name(kh_##name##_t *h) \ + { \ + if (h && h->flags) { \ + memset(h->flags, 0xaa, ((h->n_buckets>>4) + 1) * sizeof(u32bits)); \ + h->size = h->n_occupied = 0; \ + } \ + } \ + static inline khint_t kh_get_##name(kh_##name##_t *h, khkey_t key) \ + { \ + if (h->n_buckets) { \ + khint_t inc, k, i, last; \ + k = __hash_func(key); i = k % h->n_buckets; \ + inc = 1 + k % (h->n_buckets - 1); last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + if (i + inc >= h->n_buckets) i = i + inc - h->n_buckets; \ + else i += inc; \ + if (i == last) return h->n_buckets; \ + } \ + return __ac_iseither(h->flags, i)? h->n_buckets : i; \ + } else return 0; \ + } \ + static inline void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ + { \ + u32bits *new_flags = 0; \ + u32bits new_flags_size = 0; \ + khint_t j = 1; \ + { \ + khint_t t = __ac_HASH_PRIME_SIZE - 1; \ + while (__ac_prime_list[t] > new_n_buckets) --t; \ + new_n_buckets = __ac_prime_list[t+1]; \ + if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; \ + else { \ + new_flags_size = ((new_n_buckets>>4) + 1) * sizeof(u32bits); \ + new_flags = (u32bits*)turn_malloc(new_flags_size); \ + memset(new_flags, 0xaa, new_flags_size); \ + if (h->n_buckets < new_n_buckets) { \ + h->keys = (khkey_t*)turn_realloc(h->keys, h->keys_size, new_n_buckets * sizeof(khkey_t)); \ + h->keys_size = new_n_buckets * sizeof(khkey_t); \ + if (kh_is_map) { \ + h->vals = (khval_t*)turn_realloc(h->vals, h->vals_size, new_n_buckets * sizeof(khval_t)); \ + h->vals_size = new_n_buckets * sizeof(khval_t); \ + } \ + } \ + } \ + } \ + if (j) { \ + for (j = 0; j != h->n_buckets; ++j) { \ + if (__ac_iseither(h->flags, j) == 0) { \ + khkey_t key = h->keys[j]; \ + khval_t val; \ + if (kh_is_map) val = h->vals[j]; \ + __ac_set_isdel_true(h->flags, j); \ + while (1) { \ + khint_t inc, k, i; \ + k = __hash_func(key); \ + i = k % new_n_buckets; \ + inc = 1 + k % (new_n_buckets - 1); \ + while (!__ac_isempty(new_flags, i)) { \ + if (i + inc >= new_n_buckets) i = i + inc - new_n_buckets; \ + else i += inc; \ + } \ + __ac_set_isempty_false(new_flags, i); \ + if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { \ + { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ + if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ + __ac_set_isdel_true(h->flags, i); \ + } else { \ + h->keys[i] = key; \ + if (kh_is_map) h->vals[i] = val; \ + break; \ + } \ + } \ + } \ + } \ + if (h->n_buckets > new_n_buckets) { \ + h->keys = (khkey_t*)turn_realloc(h->keys, h->keys_size, new_n_buckets * sizeof(khkey_t)); \ + h->keys_size = new_n_buckets * sizeof(khkey_t); \ + if (kh_is_map) { \ + h->vals = (khval_t*)turn_realloc(h->vals, h->vals_size, new_n_buckets * sizeof(khval_t)); \ + h->vals_size = new_n_buckets * sizeof(khval_t); \ + } \ + } \ + turn_free(h->flags, h->flags_size); \ + h->flags = new_flags; \ + h->flags_size = new_flags_size; \ + h->n_buckets = new_n_buckets; \ + h->n_occupied = h->size; \ + h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ + } \ + } \ + static inline khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ + { \ + khint_t x; \ + if (h->n_occupied >= h->upper_bound) { \ + if (h->n_buckets > (h->size<<1)) kh_resize_##name(h, h->n_buckets - 1); \ + else kh_resize_##name(h, h->n_buckets + 1); \ + } \ + { \ + khint_t inc, k, i, site, last; \ + x = site = h->n_buckets; k = __hash_func(key); i = k % h->n_buckets; \ + if (__ac_isempty(h->flags, i)) x = i; \ + else { \ + inc = 1 + k % (h->n_buckets - 1); last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + if (__ac_isdel(h->flags, i)) site = i; \ + if (i + inc >= h->n_buckets) i = i + inc - h->n_buckets; \ + else i += inc; \ + if (i == last) { x = site; break; } \ + } \ + if (x == h->n_buckets) { \ + if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ + else x = i; \ + } \ + } \ + } \ + if (__ac_isempty(h->flags, x)) { \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; ++h->n_occupied; \ + *ret = 1; \ + } else if (__ac_isdel(h->flags, x)) { \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; \ + *ret = 2; \ + } else *ret = 0; \ + return x; \ + } \ + static inline void kh_del_##name(kh_##name##_t *h, khint_t x) \ + { \ + if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ + __ac_set_isdel_true(h->flags, x); \ + --h->size; \ + } \ + } + +/* --- BEGIN OF HASH FUNCTIONS --- */ + +#define kh_int_hash_func(key) (u32bits)((key<<3) + nswap32(key>>7)) +#define kh_int_hash_equal(a, b) (a == b) +#define kh_int64_hash_func(key) (u32bits)((key)>>33^(key)^(key)<<11) +#define kh_int64_hash_equal(a, b) (a == b) + +static inline khint_t __ac_X31_hash_string(const s08bits *s) +{ + khint_t h = *s; + if (h) + for (++s; *s; ++s) + h = (h << 5) - h + *s; + return h; +} +static inline khint_t __ac_X31_hash_cstring(const s08bits *s) +{ + khint_t h = tolower((int)*s); + if (h) + for (++s; *s; ++s) + h = (h << 5) - h + tolower((int)*s); + return h; +} +static inline khint_t __ac_X31_hash_nstring(const str_chunk_t *s) +{ + khint_t h = *(s->str); + if (h) { + size_t i; + for (i = 0; i < s->len; i++) + h = (h << 5) - h + s->str[i]; + } + return h; +} +static inline khint_t __ac_X31_hash_ncstring(const str_chunk_t *s) +{ + khint_t h = tolower((int)(*(s->str))); + if (h) { + size_t i; + for (i = 0; i < s->len; i++) + h = (h << 5) - h + tolower((int)(s->str[i])); + } + return h; +} +#define kh_str_hash_func(key) __ac_X31_hash_string(key) +#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) +#define kh_cstr_hash_func(key) __ac_X31_hash_cstring(key) +#define kh_cstr_hash_equal(a, b) (strcasecmp(a, b) == 0) +#define kh_nstr_hash_func(key) __ac_X31_hash_nstring(key) +#define kh_nstr_hash_equal(a, b) (a->len == b->len && (strncmp(a->str, b->str, a->len) == 0)) +#define kh_ncstr_hash_func(key) __ac_X31_hash_ncstring(key) +#define kh_ncstr_hash_equal(a, b) (a->len == b->len && (strncasecmp(a->str, b->str, a->len) == 0)) + +/* --- END OF HASH FUNCTIONS --- */ + +/* Other necessary macros... */ + +#define khash_t(name) kh_##name##_t + +#define kh_init(name) kh_init_##name() +#define kh_destroy(name, h) kh_destroy_##name(h) +#define kh_clear(name, h) kh_clear_##name(h) +#define kh_resize(name, h, s) kh_resize_##name(h, s) +#define kh_put(name, h, k, r) kh_put_##name(h, k, r) +#define kh_get(name, h, k) kh_get_##name(h, k) +#define kh_del(name, h, k) kh_del_##name(h, k) + +#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) +#define kh_key(h, x) ((h)->keys[x]) +#define kh_val(h, x) ((h)->vals[x]) +#define kh_value(h, x) ((h)->vals[x]) +#define kh_begin(h) (khint_t)(0) +#define kh_end(h) ((h)->n_buckets) +#define kh_size(h) ((h)->size) +#define kh_n_buckets(h) ((h)->n_buckets) + +/* More convenient interfaces */ + +#define KHASH_SET_INIT_INT(name) \ + KHASH_INIT(name, u32bits, s08bits, 0, kh_int_hash_func, kh_int_hash_equal) + +#define KHASH_MAP_INIT_INT(name, khval_t) \ + KHASH_INIT(name, u32bits, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) + +#define KHASH_SET_INIT_INT64(name) \ + KHASH_INIT(name, u64bits, s08bits, 0, kh_int64_hash_func, kh_int64_hash_equal) + +#define KHASH_MAP_INIT_INT64(name, khval_t) \ + KHASH_INIT(name, u64bits, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) + +typedef const s08bits *kh_cstr_t; +typedef const str_chunk_t *kh_ncstr_t; +#define KHASH_SET_INIT_STR(name) \ + KHASH_INIT(name, kh_cstr_t, s08bits, 0, kh_str_hash_func, kh_str_hash_equal) + +#define KHASH_MAP_INIT_STR(name, khval_t) \ + KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) + +#define KHASH_SET_INIT_CSTR(name) \ + KHASH_INIT(name, kh_cstr_t, s08bits, 0, kh_cstr_hash_func, kh_cstr_hash_equal) + +#define KHASH_MAP_INIT_CSTR(name, khval_t) \ + KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_cstr_hash_func, kh_cstr_hash_equal) + +#define KHASH_SET_INIT_NSTR(name) \ + KHASH_INIT(name, kh_ncstr_t, s08bits, 0, kh_nstr_hash_func, kh_nstr_hash_equal) + +#define KHASH_MAP_INIT_NSTR(name, khval_t) \ + KHASH_INIT(name, kh_ncstr_t, khval_t, 1, kh_nstr_hash_func, kh_nstr_hash_equal) + +#define KHASH_SET_INIT_NCSTR(name) \ + KHASH_INIT(name, kh_ncstr_t, s08bits, 0, kh_ncstr_hash_func, kh_ncstr_hash_equal) + +#define KHASH_MAP_INIT_NCSTR(name, khval_t) \ + KHASH_INIT(name, kh_ncstr_t, khval_t, 1, kh_ncstr_hash_func, kh_ncstr_hash_equal) + +////////////////////////////////////////////// + +#endif /* __AC_KHASH_H */ diff --git a/src/server/ns_turn_maps.c b/src/server/ns_turn_maps.c new file mode 100644 index 0000000..d5d3504 --- /dev/null +++ b/src/server/ns_turn_maps.c @@ -0,0 +1,1122 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "ns_turn_maps.h" + +#include "ns_turn_ioalib.h" + +#include "ns_turn_khash.h" + +KHASH_MAP_INIT_INT64(3, ur_map_value_type) + +#define MAGIC_HASH ((u64bits)(0x90ABCDEFL)) + +struct _ur_map { + khash_t(3) *h; + u64bits magic; + TURN_MUTEX_DECLARE(mutex) +}; + +static int ur_map_init(ur_map* map) { + if(map) { + map->h=kh_init(3); + if(map->h) { + map->magic=MAGIC_HASH; + TURN_MUTEX_INIT_RECURSIVE(&(map->mutex)); + return 0; + } + } + return -1; +} + +#define ur_map_valid(map) ((map) && ((map)->h) && ((map)->magic==MAGIC_HASH)) + +ur_map* ur_map_create() { + ur_map *map=(ur_map*)turn_malloc(sizeof(ur_map)); + if(ur_map_init(map)<0) { + turn_free(map,sizeof(ur_map)); + return NULL; + } + return map; +} + +/** + * @ret: + * 0 - success + * -1 - error + */ +int ur_map_put(ur_map* map, ur_map_key_type key, ur_map_value_type value) { + if(!ur_map_valid(map)) return -1; + else { + + int ret=0; + khiter_t k; + + k = kh_get(3, map->h, key); + if(k != kh_end(map->h)) { + kh_del(3, map->h, k); + } + + k = kh_put(3,map->h,key,&ret); + + if (!ret) { + kh_del(3, map->h, k); + return -1; + } + + kh_value(map->h, k) = value; + + return 0; + } +} + +/** + * @ret: + * 1 - success + * 0 - not found + */ +int ur_map_get(const ur_map* map, ur_map_key_type key, ur_map_value_type *value) { + if(!ur_map_valid(map)) return 0; + else { + + khiter_t k; + + k = kh_get(3, map->h, key); + if((k != kh_end(map->h)) && kh_exist(map->h,k)) { + if(value) *value=kh_value(map->h,k); + return 1; + } + + return 0; + } +} + +/** + * @ret: + * 1 - success + * 0 - not found + */ +int ur_map_del(ur_map* map, ur_map_key_type key,ur_map_del_func delfunc) { + if(!ur_map_valid(map)) return 0; + else { + + khiter_t k; + + k = kh_get(3, map->h, key); + if((k != kh_end(map->h)) && kh_exist(map->h,k)) { + if(delfunc) { + delfunc(kh_value(map->h,k)); + } + kh_del(3,map->h,k); + return 1; + } + + return 0; + } +} + +/** + * @ret: + * 1 - success + * 0 - not found + */ +int ur_map_exist(const ur_map* map, ur_map_key_type key) { + if(!ur_map_valid(map)) return 0; + else { + + khiter_t k; + + k = kh_get(3, map->h, key); + if((k != kh_end(map->h)) && kh_exist(map->h,k)) { + return 1; + } + + return 0; + } +} + +void ur_map_free(ur_map** map) { + if(map && ur_map_valid(*map)) { + kh_destroy(3,(*map)->h); + (*map)->h=NULL; + (*map)->magic=0; + TURN_MUTEX_DESTROY(&((*map)->mutex)); + turn_free(*map,sizeof(ur_map)); + *map=NULL; + } +} + +size_t ur_map_size(const ur_map* map) { + if(ur_map_valid(map)) { + return kh_size(map->h); + } else { + return 0; + } +} + +int ur_map_foreach(ur_map* map, foreachcb_type func) { + if(map && func && ur_map_valid(map)) { + khiter_t k; + for (k = kh_begin((*map)->h); k != kh_end(map->h); ++k) { + if (kh_exist(map->h, k)) { + if(func((ur_map_key_type)(kh_key(map->h, k)), + (ur_map_value_type)(kh_value(map->h, k)))) { + return 1; + } + } + } + } + return 0; +} + +int ur_map_foreach_arg(ur_map* map, foreachcb_arg_type func, void* arg) { + if(map && func && ur_map_valid(map)) { + khiter_t k; + for (k = kh_begin((*map)->h); k != kh_end(map->h); ++k) { + if (kh_exist(map->h, k)) { + if(func((ur_map_key_type)(kh_key(map->h, k)), + (ur_map_value_type)(kh_value(map->h, k)), + arg) + ) { + return 1; + } + } + } + } + return 0; +} + +int ur_map_lock(const ur_map* map) { + if(ur_map_valid(map)) { + TURN_MUTEX_LOCK((const turn_mutex*)&(map->mutex)); + return 0; + } + return -1; +} + +int ur_map_unlock(const ur_map* map) { + if(ur_map_valid(map)) { + TURN_MUTEX_UNLOCK((const turn_mutex*)&(map->mutex)); + return 0; + } + return -1; +} + +//////////////////// LOCAL MAPS //////////////////////////////////// + +void lm_map_init(lm_map *map) +{ + if(map) { + ns_bzero(map,sizeof(lm_map)); + } +} + +/** + * @ret: + * 0 - success + * -1 - error + */ + +int lm_map_put(lm_map* map, ur_map_key_type key, ur_map_value_type value) +{ + int ret = -1; + if(map && key && value) { + + size_t index = (size_t)(key & (LM_MAP_HASH_SIZE - 1)); + lm_map_array *a = &(map->table[index]); + + size_t i; + + for(i=0;imain_keys[i]; + ur_map_value_type value0 = a->main_values[i]; + + if(key0 == key) { + if(value0 == value) { + return 0; + } else { + return -1; + } + } + + if(!key0 || !value0) { + a->main_keys[i] = key; + a->main_values[i] = value; + return 0; + } + } + + size_t esz = a->extra_sz; + if(esz && a->extra_keys && a->extra_values) { + for(i=0;iextra_keys[i]; + ur_map_value_type *valuep = a->extra_values[i]; + if(keyp && valuep) { + if(!(*keyp) || !(*valuep)) { + *keyp = key; + *valuep = value; + return 0; + } + } else { + if(!(*keyp)) { + a->extra_keys[i] = (ur_map_key_type*)turn_malloc(sizeof(ur_map_key_type)); + keyp = a->extra_keys[i]; + } + if(!(*valuep)) { + a->extra_values[i] = (ur_map_value_type*)turn_malloc(sizeof(ur_map_value_type)); + valuep = a->extra_values[i]; + } + *keyp = key; + *valuep = value; + return 0; + } + } + } + + size_t old_sz = esz; + size_t old_sz_mem = esz * sizeof(ur_map_key_type*); + a->extra_keys = (ur_map_key_type**)turn_realloc(a->extra_keys,old_sz_mem,old_sz_mem + sizeof(ur_map_key_type*)); + a->extra_keys[old_sz] = (ur_map_key_type*)turn_malloc(sizeof(ur_map_key_type)); + *(a->extra_keys[old_sz]) = key; + + old_sz_mem = esz * sizeof(ur_map_value_type*); + a->extra_values = (ur_map_value_type**)turn_realloc(a->extra_values,old_sz_mem,old_sz_mem + sizeof(ur_map_value_type*)); + a->extra_values[old_sz] = (ur_map_value_type*)turn_malloc(sizeof(ur_map_value_type)); + *(a->extra_values[old_sz]) = value; + + a->extra_sz += 1; + + return 0; + } + return ret; +} + +/** + * @ret: + * 1 - success + * 0 - not found + */ + +int lm_map_get(const lm_map* map, ur_map_key_type key, ur_map_value_type *value) +{ + int ret = 0; + if(map && key) { + size_t index = (size_t)(key & (LM_MAP_HASH_SIZE - 1)); + const lm_map_array *a = &(map->table[index]); + + size_t i; + + for(i=0;imain_keys[i]; + if((key0 == key) && a->main_values[i]) { + if(value) { + *value = a->main_values[i]; + } + return 1; + } + } + + size_t esz = a->extra_sz; + if(esz && a->extra_keys && a->extra_values) { + for(i=0;iextra_keys[i]; + ur_map_value_type *valuep = a->extra_values[i]; + if(keyp && valuep) { + if(*keyp == key) { + if(value) + *value = *valuep; + return 1; + } + } + } + } + } + + return ret; +} +/** + * @ret: + * 1 - success + * 0 - not found + */ + +int lm_map_del(lm_map* map, ur_map_key_type key,ur_map_del_func delfunc) +{ + int ret = 0; + + if(map && key) { + size_t index = (size_t)(key & (LM_MAP_HASH_SIZE - 1)); + lm_map_array *a = &(map->table[index]); + + size_t i; + + for(i=0;imain_keys[i]; + + if((key0 == key) && a->main_values[i]) { + if(delfunc) { + delfunc(a->main_values[i]); + } + a->main_keys[i] = 0; + a->main_values[i] = 0; + return 1; + } + } + + size_t esz = a->extra_sz; + if(esz && a->extra_keys && a->extra_values) { + for(i=0;iextra_keys[i]; + ur_map_value_type *valuep = a->extra_values[i]; + if(keyp && valuep) { + if(*keyp == key) { + if(delfunc) + delfunc(*valuep); + *keyp = 0; + *valuep = 0; + return 1; + } + } + } + } + } + + return ret; +} +/** + * @ret: + * 1 - success + * 0 - not found + */ + +int lm_map_exist(const lm_map* map, ur_map_key_type key) +{ + return lm_map_get(map, key, NULL); +} + +void lm_map_clean(lm_map* map) +{ + size_t j; + for(j=0;jtable[j]); + + size_t esz = a->extra_sz; + if(esz) { + size_t i; + if(a->extra_keys) { + for(i=0;iextra_keys[i]; + if(keyp) { + *keyp = 0; + turn_free(keyp,sizeof(ur_map_key_type)); + } + } + turn_free(a->extra_keys,esz * sizeof(ur_map_key_type)); + a->extra_keys = NULL; + } + if(a->extra_values) { + for(i=0;iextra_values[i]; + if(valuep) { + *valuep = 0; + turn_free(valuep,sizeof(ur_map_value_type)); + } + } + turn_free(a->extra_values,esz * sizeof(ur_map_value_type)); + a->extra_values = NULL; + } + } + } + + lm_map_init(map); +} + +size_t lm_map_size(const lm_map* map) +{ + size_t ret = 0; + + if(map) { + + size_t i; + + for(i=0;itable[i]); + + size_t j; + + for(j=0;jmain_keys[j] && a->main_values[j]) { + ++ret; + } + } + + size_t esz = a->extra_sz; + if(esz && a->extra_values && a->extra_keys) { + for(j=0;jextra_keys[j]) && *(a->extra_values[j])) { + ++ret; + } + } + } + } + } + + return ret; +} + +int lm_map_foreach(lm_map* map, foreachcb_type func) +{ + size_t ret = 0; + + if(map) { + + size_t i; + + for(i=0;itable[i]); + + size_t j; + + for(j=0;jmain_keys[j] && a->main_values[j]) { + if(func((ur_map_key_type)a->main_keys[j], + (ur_map_value_type)a->main_values[j])) { + return 1; + } + } + } + + size_t esz = a->extra_sz; + if(esz && a->extra_values && a->extra_keys) { + for(j=0;jextra_keys[j]) && *(a->extra_values[j])) { + if(func((ur_map_key_type)*(a->extra_keys[j]), + (ur_map_value_type)*(a->extra_values[j]))) { + return 1; + } + } + } + } + } + } + + return ret; +} + +int lm_map_foreach_arg(lm_map* map, foreachcb_arg_type func, void* arg) +{ + size_t ret = 0; + + if(map) { + + size_t i; + + for(i=0;itable[i]); + + size_t j; + + for(j=0;jmain_keys[j] && a->main_values[j]) { + if(func((ur_map_key_type)a->main_keys[j], + (ur_map_value_type)a->main_values[j], + arg)) { + return 1; + } + } + } + + size_t esz = a->extra_sz; + if(esz && a->extra_values && a->extra_keys) { + for(j=0;jextra_keys[j]) && *(a->extra_values[j])) { + if(func((ur_map_key_type)*(a->extra_keys[j]), + (ur_map_value_type)*(a->extra_values[j]), + arg)) { + return 1; + } + } + } + } + } + } + + return ret; +} + +//////////////////// ADDR LISTS /////////////////////////////////// + +static void addr_list_free(addr_list_header* slh) { + if(slh) { + if(slh->extra_list) { + turn_free(slh->extra_list,sizeof(addr_elem)*(slh->extra_sz)); + } + ns_bzero(slh,sizeof(addr_list_header)); + } +} + +static void addr_list_add(addr_list_header* slh, const ioa_addr* key, ur_addr_map_value_type value) { + + if(!key || !value) return; + + addr_elem *elem = NULL; + size_t i; + + for(i=0;imain_list[i].value)) { + elem = &(slh->main_list[i]); + break; + } + } + + if(!elem && slh->extra_list) { + for(i=0;iextra_sz;++i) { + if(!(slh->extra_list[i].value)) { + elem = &(slh->extra_list[i]); + break; + } + } + } + + if(!elem) { + size_t old_sz = slh->extra_sz; + size_t old_sz_mem = old_sz * sizeof(addr_elem); + slh->extra_list = (addr_elem*)turn_realloc(slh->extra_list, old_sz_mem, old_sz_mem + sizeof(addr_elem)); + elem = &(slh->extra_list[old_sz]); + slh->extra_sz += 1; + } + + addr_cpy(&(elem->key),key); + elem->value=value; +} + +static void addr_list_remove(addr_list_header* slh, const ioa_addr* key, + ur_addr_map_func delfunc, int *counter) { + if(!slh || !key) return; + + if(counter) + *counter = 0; + + size_t i; + + for(i=0;imain_list[i]); + if(elem->value) { + if(addr_eq(&(elem->key),key)) { + if(delfunc && elem->value) + delfunc(elem->value); + elem->value = 0; + if(counter) { + *counter += 1; + } + } + } + } + + if(slh->extra_list) { + for(i=0;iextra_sz;++i) { + addr_elem *elem=&(slh->extra_list[i]); + if(elem->value) { + if(addr_eq(&(elem->key),key)) { + if(delfunc && elem->value) + delfunc(elem->value); + elem->value = 0; + if(counter) { + *counter += 1; + } + } + } + } + } +} + +static void addr_list_foreach(addr_list_header* slh, ur_addr_map_func func) { + if(slh && func) { + + size_t i; + + for(i=0;imain_list[i]); + if(elem->value) { + func(elem->value); + } + } + + if(slh->extra_list) { + for(i=0;iextra_sz;++i) { + addr_elem *elem=&(slh->extra_list[i]); + if(elem->value) { + func(elem->value); + } + } + } + } +} + +static addr_elem* addr_list_get(addr_list_header* slh, const ioa_addr* key) { + + if(!slh || !key) return NULL; + + size_t i; + + for(i=0;imain_list[i]); + if(elem->value) { + if(addr_eq(&(elem->key),key)) { + return elem; + } + } + } + + if(slh->extra_list) { + for(i=0;iextra_sz;++i) { + addr_elem *elem=&(slh->extra_list[i]); + if(elem->value) { + if(addr_eq(&(elem->key),key)) { + return elem; + } + } + } + } + + return NULL; +} + +static const addr_elem* addr_list_get_const(const addr_list_header* slh, const ioa_addr* key) { + + if(!slh || !key) return NULL; + + size_t i; + + for(i=0;imain_list[i]); + if(elem->value) { + if(addr_eq(&(elem->key),key)) { + return elem; + } + } + } + + if(slh->extra_list) { + for(i=0;iextra_sz;++i) { + const addr_elem *elem=&(slh->extra_list[i]); + if(elem->value) { + if(addr_eq(&(elem->key),key)) { + return elem; + } + } + } + } + + return NULL; +} + +////////// ADDR MAPS //////////////////////////////////////////// + +#define addr_map_index(key) (addr_hash((key)) & (ADDR_MAP_SIZE - 1)) + +#define get_addr_list_header(map, key) (&((map)->lists[addr_map_index((key))])) + +#define ur_addr_map_valid(map) ((map) && ((map)->magic==MAGIC_HASH)) + +void ur_addr_map_init(ur_addr_map* map) { + if(map) { + ns_bzero(map,sizeof(ur_addr_map)); + map->magic=MAGIC_HASH; + } +} + +void ur_addr_map_clean(ur_addr_map* map) { + if(map && ur_addr_map_valid(map)) { + u32bits i=0; + for(i=0;ilists[i])); + } + ns_bzero(map,sizeof(ur_addr_map)); + } +} + +/** + * @ret: + * 0 - success + * -1 - error + * if the addr key exists, the value is updated. + */ +int ur_addr_map_put(ur_addr_map* map, ioa_addr* key, ur_addr_map_value_type value) { + + if(!ur_addr_map_valid(map)) return -1; + + else { + + addr_list_header* slh = get_addr_list_header(map, key); + + addr_elem* elem = addr_list_get(slh, key); + if(elem) { + elem->value=value; + } else { + addr_list_add(slh,key,value); + } + + return 0; + } +} + +/** + * @ret: + * 1 - success + * 0 - not found + */ +int ur_addr_map_get(const ur_addr_map* map, ioa_addr* key, ur_addr_map_value_type *value) { + + if(!ur_addr_map_valid(map)) return 0; + + else { + + const addr_list_header* slh = get_addr_list_header(map, key); + + const addr_elem *elem = addr_list_get_const(slh, key); + if(elem) { + if(value) *value=elem->value; + return 1; + } + + return 0; + } +} + +/** + * @ret: + * 1 - success + * 0 - not found + */ +int ur_addr_map_del(ur_addr_map* map, ioa_addr* key,ur_addr_map_func delfunc) { + + if(!ur_addr_map_valid(map)) return 0; + + else { + + addr_list_header* slh = get_addr_list_header(map, key); + + int counter=0; + + addr_list_remove(slh, key, delfunc, &counter); + + return (counter>0); + } +} + +/** + * @ret: + * 1 - success + * 0 - not found + */ +void ur_addr_map_foreach(ur_addr_map* map, ur_addr_map_func func) { + + if(ur_addr_map_valid(map)) { + + u32bits i=0; + for(i=0;ilists[i]); + + addr_list_foreach(slh, func); + } + } +} + +//////////////////// STRING LISTS /////////////////////////////////// + +typedef struct _string_list { + struct _string_list* next; +} string_list; + +typedef struct _string_elem { + string_list list; + ur_string_map_key_type key; + u32bits key_size; + ur_string_map_value_type value; +} string_elem; + +typedef struct _string_list_header { + string_list *list; +} string_list_header; + +static size_t string_list_size(const string_list *sl) { + if(!sl) return 0; + return 1+string_list_size(sl->next); +} + +static void string_list_free(string_list_header* slh, ur_string_map_func del_value_func) { + if(slh) { + string_list* list=slh->list; + while(list) { + string_elem *elem=(string_elem*)list; + string_list* tail=elem->list.next; + if(elem->key) turn_free(elem->key,elem->key_size); + if(del_value_func && elem->value) + del_value_func(elem->value); + turn_free(elem,sizeof(string_elem)); + list=tail; + } + slh->list=NULL; + } +} + +static string_list* string_list_add(string_list* sl, const ur_string_map_key_type key, ur_string_map_value_type value) { + if(!key) return sl; + string_elem *elem=(string_elem*)turn_malloc(sizeof(string_elem)); + elem->list.next=sl; + elem->key_size = strlen(key)+1; + elem->key=(s08bits*)turn_malloc(elem->key_size); + ns_bcopy(key,elem->key,elem->key_size); + elem->value=value; + return &(elem->list); +} + +static string_list* string_list_remove(string_list* sl, const ur_string_map_key_type key, + ur_string_map_func del_value_func, int *counter) { + if(!sl || !key) return sl; + string_elem *elem=(string_elem*)sl; + string_list* tail=elem->list.next; + if(strcmp(elem->key,key)==0) { + turn_free(elem->key,elem->key_size); + if(del_value_func) + del_value_func(elem->value); + turn_free(elem,sizeof(string_elem)); + if(counter) *counter+=1; + sl=string_list_remove(tail, key, del_value_func, counter); + } else { + elem->list.next=string_list_remove(tail,key,del_value_func,counter); + } + return sl; +} + +static string_elem* string_list_get(string_list* sl, const ur_string_map_key_type key) { + + if(!sl || !key) return NULL; + + string_elem *elem=(string_elem*)sl; + if(strcmp(elem->key,key)==0) { + return elem; + } else { + return string_list_get(elem->list.next, key); + } +} + +////////// STRING MAPS //////////////////////////////////////////// + +#define STRING_MAP_SIZE (1024) + +struct _ur_string_map { + string_list_header lists[STRING_MAP_SIZE]; + u64bits magic; + ur_string_map_func del_value_func; + TURN_MUTEX_DECLARE(mutex) +}; + +static unsigned long string_hash(const ur_string_map_key_type key) { + + u08bits *str=(u08bits*)key; + + unsigned long hash = 0; + int c = 0; + + while ((c = *str++)) + hash = c + (hash << 6) + (hash << 16) - hash; + + return hash; +} + +static int string_map_index(const ur_string_map_key_type key) { + return (int)(string_hash(key) % STRING_MAP_SIZE); +} + +static string_list_header* get_string_list_header(ur_string_map *map, const ur_string_map_key_type key) { + return &(map->lists[string_map_index(key)]); +} + +static int ur_string_map_init(ur_string_map* map) { + if(map) { + ns_bzero(map,sizeof(ur_string_map)); + map->magic=MAGIC_HASH; + + TURN_MUTEX_INIT_RECURSIVE(&(map->mutex)); + + return 0; + } + return -1; +} + +static int ur_string_map_valid(const ur_string_map *map) { + return (map && map->magic==MAGIC_HASH); +} + +ur_string_map* ur_string_map_create(ur_string_map_func del_value_func) { + ur_string_map *map=(ur_string_map*)turn_malloc(sizeof(ur_string_map)); + if(ur_string_map_init(map)<0) { + turn_free(map,sizeof(ur_string_map)); + return NULL; + } + map->del_value_func = del_value_func; + return map; +} + +/** + * @ret: + * 0 - success + * -1 - error + * if the string key exists, and the value is different, return error. + */ +int ur_string_map_put(ur_string_map* map, const ur_string_map_key_type key, ur_string_map_value_type value) { + + if(!ur_string_map_valid(map)) return -1; + + else { + + string_list_header* slh = get_string_list_header(map, key); + + string_elem *elem = string_list_get(slh->list, key); + if(elem) { + if(elem->value != value) { + if(map->del_value_func) + map->del_value_func(elem->value); + elem->value = value; + } + return 0; + } + + slh->list=string_list_add(slh->list,key,value); + + return 0; + } +} + +/** + * @ret: + * 1 - success + * 0 - not found + */ +int ur_string_map_get(ur_string_map* map, const ur_string_map_key_type key, ur_string_map_value_type *value) { + + if(!ur_string_map_valid(map)) return 0; + + else { + + string_list_header* slh = get_string_list_header(map, key); + string_elem *elem = string_list_get(slh->list, key); + if(elem) { + if(value) *value=elem->value; + return 1; + } else { + return 0; + } + } +} + +/** + * @ret: + * 1 - success + * 0 - not found + */ +int ur_string_map_del(ur_string_map* map, const ur_string_map_key_type key) { + + if(!ur_string_map_valid(map)) return 0; + + else { + + string_list_header* slh = get_string_list_header(map, key); + + int counter=0; + + slh->list=string_list_remove(slh->list, key, map->del_value_func, &counter); + + return (counter>0); + } +} + +void ur_string_map_clean(ur_string_map* map) { + if (ur_string_map_valid(map)) { + int i = 0; + for (i = 0; i < STRING_MAP_SIZE; i++) { + string_list_free(&(map->lists[i]), map->del_value_func); + } + } +} + +void ur_string_map_free(ur_string_map** map) { + if(map && ur_string_map_valid(*map)) { + int i=0; + for(i=0;ilists[i]),(*map)->del_value_func); + } + (*map)->magic=0; + TURN_MUTEX_DESTROY(&((*map)->mutex)); + turn_free(*map,sizeof(ur_string_map)); + *map=NULL; + } +} + +size_t ur_string_map_size(const ur_string_map* map) { + if(ur_string_map_valid(map)) { + size_t ret=0; + int i=0; + for(i=0;ilists[i].list); + } + return ret; + } else { + return 0; + } +} + +int ur_string_map_lock(const ur_string_map* map) { + if(ur_string_map_valid(map)) { + TURN_MUTEX_LOCK((const turn_mutex*)&(map->mutex)); + return 0; + } + return -1; +} + +int ur_string_map_unlock(const ur_string_map* map) { + if(ur_string_map_valid(map)) { + TURN_MUTEX_UNLOCK((const turn_mutex*)&(map->mutex)); + return 0; + } + return -1; +} + +//////////////////////////////////////////////////////////////// diff --git a/src/server/ns_turn_maps.h b/src/server/ns_turn_maps.h new file mode 100644 index 0000000..3a873aa --- /dev/null +++ b/src/server/ns_turn_maps.h @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __TURN_MAPS__ +#define __TURN_MAPS__ + +#include "ns_turn_ioaddr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////// UR MAP ////////////////// + +struct _ur_map; +typedef struct _ur_map ur_map; + +//////////////// Common Definitions ////// + +typedef u64bits ur_map_key_type; +typedef unsigned long ur_map_value_type; + +typedef void (*ur_map_del_func)(ur_map_value_type); + +typedef int (*foreachcb_type)(ur_map_key_type key, ur_map_value_type value); +typedef int (*foreachcb_arg_type)(ur_map_key_type key, + ur_map_value_type value, + void *arg); + +///////////// non-local map ///////////////////// + +ur_map* ur_map_create(void); + +/** + * @ret: + * 0 - success + * -1 - error + */ + +int ur_map_put(ur_map* map, ur_map_key_type key, ur_map_value_type value); + +/** + * @ret: + * 1 - success + * 0 - not found + */ + +int ur_map_get(const ur_map* map, ur_map_key_type key, ur_map_value_type *value); +/** + * @ret: + * 1 - success + * 0 - not found + */ + +int ur_map_del(ur_map* map, ur_map_key_type key,ur_map_del_func delfunc); +/** + * @ret: + * 1 - success + * 0 - not found + */ + +int ur_map_exist(const ur_map* map, ur_map_key_type key); + +void ur_map_free(ur_map** map); + +size_t ur_map_size(const ur_map* map); + +int ur_map_foreach(ur_map* map, foreachcb_type func); + +int ur_map_foreach_arg(ur_map* map, foreachcb_arg_type func, void* arg); + +int ur_map_lock(const ur_map* map); +int ur_map_unlock(const ur_map* map); + +///////////// "local" map ///////////////////// + +#define LM_MAP_HASH_SIZE (8) +#define LM_MAP_ARRAY_SIZE (3) + +typedef struct _lm_map_array { + ur_map_key_type main_keys[LM_MAP_ARRAY_SIZE]; + ur_map_value_type main_values[LM_MAP_ARRAY_SIZE]; + size_t extra_sz; + ur_map_key_type **extra_keys; + ur_map_value_type **extra_values; +} lm_map_array; + +typedef struct _lm_map { + lm_map_array table[LM_MAP_HASH_SIZE]; +} lm_map; + +void lm_map_init(lm_map *map); + +/** + * @ret: + * 0 - success + * -1 - error + */ + +int lm_map_put(lm_map* map, ur_map_key_type key, ur_map_value_type value); + +/** + * @ret: + * 1 - success + * 0 - not found + */ + +int lm_map_get(const lm_map* map, ur_map_key_type key, ur_map_value_type *value); +/** + * @ret: + * 1 - success + * 0 - not found + */ + +int lm_map_del(lm_map* map, ur_map_key_type key,ur_map_del_func delfunc); +/** + * @ret: + * 1 - success + * 0 - not found + */ + +int lm_map_exist(const lm_map* map, ur_map_key_type key); + +void lm_map_clean(lm_map* map); + +size_t lm_map_size(const lm_map* map); + +int lm_map_foreach(lm_map* map, foreachcb_type func); + +int lm_map_foreach_arg(lm_map* map, foreachcb_arg_type func, void* arg); + +//////////////// UR ADDR MAP ////////////////// + +typedef unsigned long ur_addr_map_value_type; + +#define ADDR_MAP_SIZE (1024) +#define ADDR_ARRAY_SIZE (4) + +typedef struct _addr_elem { + ioa_addr key; + ur_addr_map_value_type value; +} addr_elem; + +typedef struct _addr_list_header { + addr_elem main_list[ADDR_ARRAY_SIZE]; + addr_elem *extra_list; + size_t extra_sz; +} addr_list_header; + +struct _ur_addr_map { + addr_list_header lists[ADDR_MAP_SIZE]; + u64bits magic; +}; + +struct _ur_addr_map; +typedef struct _ur_addr_map ur_addr_map; + +typedef void (*ur_addr_map_func)(ur_addr_map_value_type); + +void ur_addr_map_init(ur_addr_map* map); +void ur_addr_map_clean(ur_addr_map* map); + +/** + * @ret: + * 0 - success + * -1 - error + * if the addr key exists, the value is updated. + */ +int ur_addr_map_put(ur_addr_map* map, ioa_addr* key, ur_addr_map_value_type value); + +/** + * @ret: + * 1 - success + * 0 - not found + */ +int ur_addr_map_get(const ur_addr_map* map, ioa_addr* key, ur_addr_map_value_type *value); + +/** + * @ret: + * 1 - success + * 0 - not found + */ +int ur_addr_map_del(ur_addr_map* map, ioa_addr* key,ur_addr_map_func func); + +/** + * @ret: + * 1 - success + * 0 - not found + */ +void ur_addr_map_foreach(ur_addr_map* map, ur_addr_map_func func); + +size_t ur_addr_map_size(const ur_addr_map* map); + +//////////////// UR STRING MAP ////////////////// + +typedef s08bits* ur_string_map_key_type; +typedef void* ur_string_map_value_type; +struct _ur_string_map; +typedef struct _ur_string_map ur_string_map; + +typedef void (*ur_string_map_func)(ur_string_map_value_type); + +ur_string_map* ur_string_map_create(ur_string_map_func del_value_func); + +/** + * @ret: + * 0 - success + * -1 - error + * if the string key exists, and the value is different, return error. + */ +int ur_string_map_put(ur_string_map* map, const ur_string_map_key_type key, ur_string_map_value_type value); + +/** + * @ret: + * 1 - success + * 0 - not found + */ +int ur_string_map_get(ur_string_map* map, const ur_string_map_key_type key, ur_string_map_value_type *value); + +/** + * @ret: + * 1 - success + * 0 - not found + */ +int ur_string_map_del(ur_string_map* map, const ur_string_map_key_type key); + +void ur_string_map_clean(ur_string_map* map); +void ur_string_map_free(ur_string_map** map); + +size_t ur_string_map_size(const ur_string_map* map); + +int ur_string_map_lock(const ur_string_map* map); +int ur_string_map_unlock(const ur_string_map* map); + +//////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif //__TURN_MAPS__ diff --git a/src/server/ns_turn_maps_rtcp.c b/src/server/ns_turn_maps_rtcp.c new file mode 100644 index 0000000..704d3b1 --- /dev/null +++ b/src/server/ns_turn_maps_rtcp.c @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "ns_turn_maps_rtcp.h" + +#include "ns_turn_utils.h" +#include "ns_turn_ioaddr.h" + +//////////////////////////////////////////// + +#define MAGIC_RTCP_MAP (0x76859403) +#define RTCP_TIMEOUT (300) +#define MAX_TOKEN_DEL (1024) + +//////////////////////////////////////////// + +struct _rtcp_map { + u32bits magic; + ur_map *map; + ioa_timer_handle timer_ev; + TURN_MUTEX_DECLARE(mutex) +}; + + +typedef struct { + ioa_socket_handle s; + turn_time_t t; + rtcp_token_type token; +} rtcp_alloc_type; + +//////////////////////////////////////////// + +static int rtcp_map_valid(const rtcp_map *map) { + return (map && (map->magic==MAGIC_RTCP_MAP) && map->map); +} + +typedef struct { + rtcp_token_type tokens[MAX_TOKEN_DEL]; + int tn; + turn_time_t t; +} timeout_check_arg_type; + +static int timeout_check(ur_map_key_type key, + ur_map_value_type value, + void *arg) { + + if(value && arg) { + + timeout_check_arg_type *tcat=(timeout_check_arg_type*)arg; + + rtcp_alloc_type* rat=(rtcp_alloc_type*)value; + + if(turn_time_before(rat->t, tcat->t) && (tcat->tntokens[(tcat->tn)++]=key; + } + } + + return 0; +} + +static void rtcp_map_timeout_handler(ioa_engine_handle e, void* arg) { + + UNUSED_ARG(e); + + if(!arg) return; + + rtcp_map* map=(rtcp_map*)arg; + + if(rtcp_map_valid(map)) { + + TURN_MUTEX_LOCK(&map->mutex); + + timeout_check_arg_type tcat; + tcat.tn=0; + tcat.t=turn_time(); + + ur_map_foreach_arg(map->map, timeout_check, &tcat); + + TURN_MUTEX_UNLOCK(&map->mutex); + + int i=0; + for(i=0;imagic != MAGIC_RTCP_MAP) { + map->magic = MAGIC_RTCP_MAP; + map->map = ur_map_create(); + if (e) + map->timer_ev = set_ioa_timer(e, 3, 0, rtcp_map_timeout_handler, + map, 1, "rtcp_map_timeout_handler"); + TURN_MUTEX_INIT(&map->mutex); + if (rtcp_map_valid(map)) + return 0; + } + } + return -1; +} + +rtcp_map* rtcp_map_create(ioa_engine_handle e) { + rtcp_map *map=(rtcp_map*)turn_malloc(sizeof(rtcp_map)); + ns_bzero(map,sizeof(rtcp_map)); + if(rtcp_map_init(map,e)<0) { + turn_free(map,sizeof(rtcp_map)); + return NULL; + } + return map; +} + +/** + * @ret: + * 0 - success + * -1 - error + */ +int rtcp_map_put(rtcp_map* map, rtcp_token_type token, ioa_socket_handle s) { + if(!rtcp_map_valid(map)) return -1; + else { + rtcp_alloc_type *value=(rtcp_alloc_type*)turn_malloc(sizeof(rtcp_alloc_type)); + if(!value) return -1; + ns_bzero(value,sizeof(rtcp_alloc_type)); + value->s=s; + value->t=turn_time() + RTCP_TIMEOUT; + value->token=token; + TURN_MUTEX_LOCK(&map->mutex); + int ret = ur_map_put(map->map,token,(ur_map_value_type)value); + //TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"%s: 111.111: ret=%d, token=%llu\n",__FUNCTION__,ret,token); + TURN_MUTEX_UNLOCK(&map->mutex); + if(ret<0) turn_free(value,sizeof(rtcp_alloc_type)); + return ret; + } +} + +/** + * @ret: + * >=0 - success + * <0 - not found + */ +ioa_socket_handle rtcp_map_get(const rtcp_map* map, rtcp_token_type token) { + if(!rtcp_map_valid(map)) return NULL; + else { + ur_map_value_type value; + TURN_MUTEX_LOCK(&map->mutex); + int ret = ur_map_get(map->map,token,&value); + //TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"%s: 111.111: ret=%d, value=%llu, token=%llu\n",__FUNCTION__,ret,(unsigned long)value,token); + TURN_MUTEX_UNLOCK(&map->mutex); + if(!ret) return NULL; + rtcp_alloc_type* rval=(rtcp_alloc_type*)value; + if(!rval) return NULL; + //TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"%s: 111.222: ret=%d, token=%llu\n",__FUNCTION__,ret,token); + return rval->s; + } +} + +static void rtcp_alloc_free(ur_map_value_type value) +{ + rtcp_alloc_type *at = (rtcp_alloc_type *)value; + if (at) { + IOA_CLOSE_SOCKET(at->s); + turn_free(at,sizeof(rtcp_alloc_type)); + } +} + +static void rtcp_alloc_free_savefd(ur_map_value_type value) +{ + rtcp_alloc_type *at = (rtcp_alloc_type *) value; + if (at) { + turn_free(at,sizeof(rtcp_alloc_type)); + } +} + +static int foreachcb_free(ur_map_key_type key, ur_map_value_type value) { + UNUSED_ARG(key); + if(value) { + rtcp_alloc_free(value); + } + return 0; +} + +/** + * @ret: + * 1 - success + * 0 - not found + */ +int rtcp_map_del(rtcp_map* map, rtcp_token_type token) { + if(!rtcp_map_valid(map)) return 0; + else { + TURN_MUTEX_LOCK(&map->mutex); + int ret = ur_map_del(map->map,token,rtcp_alloc_free); + TURN_MUTEX_UNLOCK(&map->mutex); + return ret; + } +} +int rtcp_map_del_savefd(rtcp_map* map, rtcp_token_type token) { + if(!rtcp_map_valid(map)) return 0; + else { + TURN_MUTEX_LOCK(&map->mutex); + int ret = ur_map_del(map->map,token,rtcp_alloc_free_savefd); + TURN_MUTEX_UNLOCK(&map->mutex); + return ret; + } +} + +void rtcp_map_free(rtcp_map** map) { + if(map && rtcp_map_valid(*map)) { + TURN_MUTEX_LOCK(&((*map)->mutex)); + IOA_EVENT_DEL((*map)->timer_ev); + ur_map_foreach((*map)->map, foreachcb_free); + ur_map_free(&((*map)->map)); + (*map)->magic=0; + TURN_MUTEX_UNLOCK(&((*map)->mutex)); + TURN_MUTEX_DESTROY(&((*map)->mutex)); + turn_free(*map,sizeof(rtcp_map)); + *map=NULL; + } +} + +size_t rtcp_map_size(const rtcp_map* map) { + if(rtcp_map_valid(map)) { + TURN_MUTEX_LOCK(&map->mutex); + size_t ret = ur_map_size(map->map); + TURN_MUTEX_UNLOCK(&map->mutex); + return ret; + } else { + return 0; + } +} + +//////////////////////////////////////////////////////////////// diff --git a/src/server/ns_turn_maps_rtcp.h b/src/server/ns_turn_maps_rtcp.h new file mode 100644 index 0000000..19ce3cb --- /dev/null +++ b/src/server/ns_turn_maps_rtcp.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __TURN_RTCP_MAP__ +#define __TURN_RTCP_MAP__ + +#include "ns_turn_maps.h" +#include "ns_turn_ioalib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////// RTCP MAP ////////////////// + +typedef ur_map_key_type rtcp_token_type; + +struct _rtcp_map; +typedef struct _rtcp_map rtcp_map; + +//////////////////////////////////////////////// + +rtcp_map* rtcp_map_create(ioa_engine_handle e); + +/** + * @ret: + * 0 - success + * -1 - error + */ +int rtcp_map_put(rtcp_map* map, rtcp_token_type key, ioa_socket_handle s); + +/** + * @ret: + * >=0 - success + * <0 - not found + */ +ioa_socket_handle rtcp_map_get(const rtcp_map* map, rtcp_token_type token); + +/** + * @ret: + * 1 - success + * 0 - not found + */ +int rtcp_map_del(rtcp_map* map, rtcp_token_type token); +int rtcp_map_del_savefd(rtcp_map* map, rtcp_token_type token); + +/** + * @ret: + * 1 - success + * 0 - not found + */ +void rtcp_map_free(rtcp_map** map); + +size_t rtcp_map_size(const rtcp_map* map); + +//////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif //__TURN_RTCP_MAP__ diff --git a/src/server/ns_turn_server.c b/src/server/ns_turn_server.c new file mode 100644 index 0000000..5161d85 --- /dev/null +++ b/src/server/ns_turn_server.c @@ -0,0 +1,4407 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "ns_turn_server.h" + +#include "ns_turn_utils.h" +#include "ns_turn_allocation.h" +#include "ns_turn_msg_addr.h" +#include "ns_turn_ioalib.h" + +/////////////////////////////////////////// + +#define FUNCSTART if(server && eve(server->verbose)) TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"%s:%d:start\n",__FUNCTION__,__LINE__) +#define FUNCEND if(server && eve(server->verbose)) TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"%s:%d:end\n",__FUNCTION__,__LINE__) + +//////////////////////////////////////////////// + +#define MAX_NUMBER_OF_UNKNOWN_ATTRS (128) + +int TURN_MAX_ALLOCATE_TIMEOUT = 60; +int TURN_MAX_ALLOCATE_TIMEOUT_STUN_ONLY = 3; + +/////////////////////////////////////////// + +static int attach_socket_to_session(turn_turnserver* server, ioa_socket_handle s, ts_ur_super_session* ss); + +static int check_stun_auth(turn_turnserver *server, + ts_ur_super_session *ss, stun_tid *tid, int *resp_constructed, + int *err_code, const u08bits **reason, + ioa_net_data *in_buffer, ioa_network_buffer_handle nbh, + u16bits method, int *message_integrity, + int *postpone_reply, + int can_resume); + +static int create_relay_connection(turn_turnserver* server, + ts_ur_super_session *ss, u32bits lifetime, + int address_family, u08bits transport, + int even_port, u64bits in_reservation_token, u64bits *out_reservation_token, + int *err_code, const u08bits **reason, accept_cb acb); + +static int refresh_relay_connection(turn_turnserver* server, + ts_ur_super_session *ss, u32bits lifetime, int even_port, + u64bits in_reservation_token, u64bits *out_reservation_token, + int *err_code); + +static int write_client_connection(turn_turnserver *server, ts_ur_super_session* ss, ioa_network_buffer_handle nbh, int ttl, int tos); + +static void tcp_peer_accept_connection(ioa_socket_handle s, void *arg); + +static int read_client_connection(turn_turnserver *server, ts_ur_session *elem, + ts_ur_super_session *ss, ioa_net_data *in_buffer, + int can_resume, int count_usage); + +static int need_stun_authentication(turn_turnserver *server, ts_ur_super_session *ss); + +/////////////////// timer ////////////////////////// + +static void timer_timeout_handler(ioa_engine_handle e, void *arg) +{ + UNUSED_ARG(e); + if(arg) { + turn_turnserver *server=(turn_turnserver*)arg; + server->ctime = turn_time(); + } +} + +turn_time_t get_turn_server_time(turn_turnserver *server) +{ + if(server) { + return server->ctime; + } + return turn_time(); +} + +/////////////////// quota ////////////////////// + +static int inc_quota(ts_ur_super_session* ss, u08bits *username) +{ + if(ss && !(ss->quota_used) && ss->server && ((turn_turnserver*)ss->server)->chquotacb && username) { + + if(((turn_turnserver*)ss->server)->ct == TURN_CREDENTIALS_LONG_TERM) { + if(!(ss->realm_set)) { + return -1; + } + } + + if((((turn_turnserver*)ss->server)->chquotacb)(username, (u08bits*)ss->realm_options.name)<0) { + + return -1; + + } else { + + STRCPY(ss->username,username); + + ss->quota_used = 1; + } + } + + return 0; +} + +static void dec_quota(ts_ur_super_session* ss) +{ + if(ss && ss->quota_used && ss->server && ((turn_turnserver*)ss->server)->raqcb) { + + ss->quota_used = 0; + + (((turn_turnserver*)ss->server)->raqcb)(ss->username, (u08bits*)ss->realm_options.name); + } +} + +/////////////////// server lists /////////////////// + +void init_turn_server_addrs_list(turn_server_addrs_list_t *l) +{ + if(l) { + l->addrs = NULL; + l->size = 0; + turn_mutex_init(&(l->m)); + } +} + +/////////////////// RFC 5780 /////////////////////// + +void set_rfc5780(turn_turnserver *server, get_alt_addr_cb cb, send_message_cb smcb) +{ + if(server) { + if(!cb || !smcb) { + server->rfc5780 = 0; + server->alt_addr_cb = NULL; + server->sm_cb = NULL; + } else { + server->rfc5780 = 1; + server->alt_addr_cb = cb; + server->sm_cb = smcb; + } + } +} + +static int is_rfc5780(turn_turnserver *server) +{ + if(!server) + return 0; + + return ((server->rfc5780) && (server->alt_addr_cb)); +} + +static int get_other_address(turn_turnserver *server, ts_ur_super_session *ss, ioa_addr *alt_addr) +{ + if(is_rfc5780(server) && ss && ss->client_session.s) { + int ret = server->alt_addr_cb(get_local_addr_from_ioa_socket(ss->client_session.s), alt_addr); + return ret; + } + + return -1; +} + +static int send_turn_message_to(turn_turnserver *server, ioa_network_buffer_handle nbh, ioa_addr *response_origin, ioa_addr *response_destination) +{ + if(is_rfc5780(server) && nbh && response_origin && response_destination) { + return server->sm_cb(server->e, nbh, response_origin, response_destination); + } + + return -1; +} + +/////////////////// Peer addr check ///////////////////////////// + +static int good_peer_addr(turn_turnserver *server, ioa_addr *peer_addr) +{ + if(server && peer_addr) { + if(*(server->no_multicast_peers) && ioa_addr_is_multicast(peer_addr)) + return 0; + if(*(server->no_loopback_peers) && ioa_addr_is_loopback(peer_addr)) + return 0; + + { + int i; + + if(server->ip_whitelist) { + // White listing of addr ranges + for (i = server->ip_whitelist->ranges_number - 1; i >= 0; --i) { + if (ioa_addr_in_range(server->ip_whitelist->encaddrsranges[i], peer_addr)) + return 1; + } + } + + { + ioa_lock_whitelist(server->e); + + const ip_range_list_t* wl = ioa_get_whitelist(server->e); + if(wl) { + // White listing of addr ranges + for (i = wl->ranges_number - 1; i >= 0; --i) { + if (ioa_addr_in_range(wl->encaddrsranges[i], peer_addr)) { + ioa_unlock_whitelist(server->e); + return 1; + } + } + } + + ioa_unlock_whitelist(server->e); + } + + if(server->ip_blacklist) { + // Black listing of addr ranges + for (i = server->ip_blacklist->ranges_number - 1; i >= 0; --i) { + if (ioa_addr_in_range(server->ip_blacklist->encaddrsranges[i], peer_addr)) { + char saddr[129]; + addr_to_string_no_port(peer_addr,(u08bits*)saddr); + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "A peer IP %s denied in the range: %s\n",saddr,server->ip_blacklist->ranges[i]); + return 0; + } + } + } + + { + ioa_lock_blacklist(server->e); + + const ip_range_list_t* bl = ioa_get_blacklist(server->e); + if(bl) { + // Black listing of addr ranges + for (i = bl->ranges_number - 1; i >= 0; --i) { + if (ioa_addr_in_range(bl->encaddrsranges[i], peer_addr)) { + ioa_unlock_blacklist(server->e); + char saddr[129]; + addr_to_string_no_port(peer_addr,(u08bits*)saddr); + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "A peer IP %s denied in the range: %s\n",saddr,bl->ranges[i]); + return 0; + } + } + } + + ioa_unlock_blacklist(server->e); + } + } + } + + return 1; +} + +/////////////////// Allocation ////////////////////////////////// + +allocation* get_allocation_ss(ts_ur_super_session *ss) { + return &(ss->alloc); +} + +static inline ts_ur_session *get_relay_session_ss(ts_ur_super_session *ss) +{ + return &(ss->alloc.relay_session); +} + +static inline ioa_socket_handle get_relay_socket_ss(ts_ur_super_session *ss) +{ + return ss->alloc.relay_session.s; +} + +/////////// Session info /////// + +void turn_session_info_init(struct turn_session_info* tsi) { + if(tsi) { + ns_bzero(tsi,sizeof(struct turn_session_info)); + } +} + +void turn_session_info_clean(struct turn_session_info* tsi) { + if(tsi) { + if(tsi->extra_peers_data) { + turn_free(tsi->extra_peers_data, sizeof(addr_data)*(tsi->extra_peers_size)); + } + turn_session_info_init(tsi); + } +} + +void turn_session_info_add_peer(struct turn_session_info* tsi, ioa_addr *peer) +{ + if(tsi && peer) { + { + size_t i; + for(i=0;imain_peers_size;++i) { + if(addr_eq(peer, &(tsi->main_peers_data[i].addr))) { + return; + } + } + + if(tsi->main_peers_size < TURN_MAIN_PEERS_ARRAY_SIZE) { + addr_cpy(&(tsi->main_peers_data[tsi->main_peers_size].addr),peer); + addr_to_string(&(tsi->main_peers_data[tsi->main_peers_size].addr), + (u08bits*)tsi->main_peers_data[tsi->main_peers_size].saddr); + tsi->main_peers_size += 1; + return; + } + } + + if(tsi->extra_peers_data) { + size_t sz; + for(sz=0;szextra_peers_size;++sz) { + if(addr_eq(peer, &(tsi->extra_peers_data[sz].addr))) { + return; + } + } + } + tsi->extra_peers_data = (addr_data*)turn_realloc(tsi->extra_peers_data,tsi->extra_peers_size*sizeof(addr_data),(tsi->extra_peers_size+1)*sizeof(addr_data)); + addr_cpy(&(tsi->extra_peers_data[tsi->extra_peers_size].addr),peer); + addr_to_string(&(tsi->extra_peers_data[tsi->extra_peers_size].addr), + (u08bits*)tsi->extra_peers_data[tsi->extra_peers_size].saddr); + tsi->extra_peers_size += 1; + } +} + +struct tsi_arg { + struct turn_session_info* tsi; + ioa_addr *addr; +}; + +static int turn_session_info_foreachcb(ur_map_key_type key, ur_map_value_type value, void *arg) +{ + UNUSED_ARG(value); + + int port = (int)key; + struct tsi_arg *ta = (struct tsi_arg *)arg; + if(port && ta && ta->tsi && ta->addr) { + ioa_addr a; + addr_cpy(&a,ta->addr); + addr_set_port(&a,port); + turn_session_info_add_peer(ta->tsi,&a); + } + return 0; +} + +int turn_session_info_copy_from(struct turn_session_info* tsi, ts_ur_super_session *ss) +{ + int ret = -1; + + if(tsi && ss) { + tsi->id = ss->id; + tsi->start_time = ss->start_time; + tsi->valid = is_allocation_valid(&(ss->alloc)) && !(ss->to_be_closed) && (ss->quota_used); + if(tsi->valid) { + tsi->expiration_time = ss->alloc.expiration_time; + if(ss->client_session.s) { + tsi->client_protocol = get_ioa_socket_type(ss->client_session.s); + addr_cpy(&(tsi->local_addr_data.addr),get_local_addr_from_ioa_socket(ss->client_session.s)); + addr_to_string(&(tsi->local_addr_data.addr),(u08bits*)tsi->local_addr_data.saddr); + addr_cpy(&(tsi->remote_addr_data.addr),get_remote_addr_from_ioa_socket(ss->client_session.s)); + addr_to_string(&(tsi->remote_addr_data.addr),(u08bits*)tsi->remote_addr_data.saddr); + } + if(ss->alloc.relay_session.s) { + tsi->peer_protocol = get_ioa_socket_type(ss->alloc.relay_session.s); + addr_cpy(&(tsi->relay_addr_data.addr),get_local_addr_from_ioa_socket(ss->alloc.relay_session.s)); + addr_to_string(&(tsi->relay_addr_data.addr),(u08bits*)tsi->relay_addr_data.saddr); + } + STRCPY(tsi->username,ss->username); + tsi->enforce_fingerprints = ss->enforce_fingerprints; + STRCPY(tsi->tls_method, get_ioa_socket_tls_method(ss->client_session.s)); + STRCPY(tsi->tls_cipher, get_ioa_socket_tls_cipher(ss->client_session.s)); + STRCPY(tsi->realm, ss->realm_options.name); + STRCPY(tsi->origin, ss->origin); + + if(ss->t_received_packets > ss->received_packets) + tsi->received_packets = ss->t_received_packets; + else + tsi->received_packets = ss->received_packets; + + if(ss->t_sent_packets > ss->sent_packets) + tsi->sent_packets = ss->t_sent_packets; + else + tsi->sent_packets = ss->sent_packets; + + if(ss->t_received_bytes > ss->received_bytes) + tsi->received_bytes = ss->t_received_bytes; + else + tsi->received_bytes = ss->received_bytes; + + if(ss->t_sent_bytes > ss->sent_bytes) + tsi->sent_bytes = ss->t_sent_bytes; + else + tsi->sent_bytes = ss->sent_bytes; + + { + tsi->received_rate = ss->received_rate; + tsi->sent_rate = ss->sent_rate; + tsi->total_rate = tsi->received_rate + tsi->sent_rate; + } + + tsi->is_mobile = ss->is_mobile; + + { + size_t i; + for(i=0;ialloc.addr_to_perm.table[i]); + + { + size_t j; + for(j=0;jmain_slots[j]); + if(slot->info.allocated) { + turn_session_info_add_peer(tsi,&(slot->info.addr)); + struct tsi_arg arg = { + tsi, + &(slot->info.addr) + }; + lm_map_foreach_arg(&(slot->info.chns), turn_session_info_foreachcb, &arg); + } + } + } + + { + turn_permission_slot **slots = parray->extra_slots; + if(slots) { + size_t sz = parray->extra_sz; + size_t j; + for(j=0;jinfo.allocated) { + turn_session_info_add_peer(tsi,&(slot->info.addr)); + struct tsi_arg arg = { + tsi, + &(slot->info.addr) + }; + lm_map_foreach_arg(&(slot->info.chns), turn_session_info_foreachcb, &arg); + } + } + } + } + } + } + + { + tcp_connection_list *tcl = &(ss->alloc.tcs); + if(tcl->elems) { + size_t i; + size_t sz = tcl->sz; + for(i=0;ielems[i]) { + tcp_connection *tc = tcl->elems[i]; + if(tc) { + turn_session_info_add_peer(tsi,&(tc->peer_addr)); + } + } + } + } + } + } + + ret = 0; + } + + return ret; +} + +int report_turn_session_info(turn_turnserver *server, ts_ur_super_session *ss, int force_invalid) +{ + if(server && ss && server->send_turn_session_info) { + struct turn_session_info tsi; + turn_session_info_init(&tsi); + if(turn_session_info_copy_from(&tsi,ss)<0) { + turn_session_info_clean(&tsi); + } else { + if(force_invalid) + tsi.valid = 0; + if(server->send_turn_session_info(&tsi)<0) { + turn_session_info_clean(&tsi); + } else { + return 0; + } + } + } + + return -1; +} + +/////////// SS ///////////////// + +static int mobile_id_to_string(mobile_id_t mid, char *dst, size_t dst_sz) +{ + size_t output_length = 0; + + if(!dst) + return -1; + + char *s = base64_encode((const unsigned char *)&mid, + sizeof(mid), + &output_length); + + if(!s) + return -1; + + if(!output_length || (output_length+1 > dst_sz)) { + turn_free(s, output_length); + return -1; + } + + ns_bcopy(s, dst, output_length); + + turn_free(s, output_length); + + dst[output_length] = 0; + + return (int)output_length; +} + +static mobile_id_t string_to_mobile_id(char* src) +{ + mobile_id_t mid = 0; + + if(src) { + + size_t output_length = 0; + + unsigned char *out = base64_decode(src, strlen(src), &output_length); + + if(out) { + + if(output_length == sizeof(mid)) { + mid = *((mobile_id_t*)out); + } + + turn_free(out, output_length); + } + } + + return mid; +} + +static mobile_id_t get_new_mobile_id(turn_turnserver* server) +{ + mobile_id_t newid = 0; + + if(server && server->mobile_connections_map) { + ur_map *map = server->mobile_connections_map; + u64bits sid = server->id; + sid = sid<<56; + do { + while (!newid) { + if(TURN_RANDOM_SIZE == sizeof(mobile_id_t)) + newid = (mobile_id_t)turn_random(); + else { + newid = (mobile_id_t)turn_random(); + newid = (newid<<32) + (mobile_id_t)turn_random(); + } + if(!newid) { + continue; + } + newid = newid & 0x00FFFFFFFFFFFFFFLL; + if(!newid) { + continue; + } + newid = newid | sid; + } + } while(ur_map_get(map, (ur_map_key_type)newid, NULL)); + } + return newid; +} + +static void put_session_into_mobile_map(ts_ur_super_session *ss) +{ + if(ss && ss->server) { + turn_turnserver* server = (turn_turnserver*)(ss->server); + if(*(server->mobility) && server->mobile_connections_map) { + if(!(ss->mobile_id)) { + ss->mobile_id = get_new_mobile_id(server); + mobile_id_to_string(ss->mobile_id, ss->s_mobile_id, sizeof(ss->s_mobile_id)); + } + ur_map_put(server->mobile_connections_map, (ur_map_key_type)(ss->mobile_id), (ur_map_value_type)ss); + } + } + +} + +static void put_session_into_map(ts_ur_super_session *ss) +{ + if(ss && ss->server) { + turn_turnserver* server = (turn_turnserver*)(ss->server); + if(!(ss->id)) { + ss->id = (turnsession_id)((turnsession_id)server->id * 1000000000000000LL); + ss->id += ++(server->session_id_counter); + ss->start_time = server->ctime; + } + ur_map_put(server->sessions_map, (ur_map_key_type)(ss->id), (ur_map_value_type)ss); + put_session_into_mobile_map(ss); + } + +} + +static void delete_session_from_mobile_map(ts_ur_super_session *ss) +{ + if(ss && ss->server && ss->mobile_id) { + turn_turnserver* server = (turn_turnserver*)(ss->server); + if(server->mobile_connections_map) { + ur_map_del(server->mobile_connections_map, (ur_map_key_type)(ss->mobile_id), NULL); + } + ss->mobile_id = 0; + ss->s_mobile_id[0] = 0; + } +} + +static void delete_session_from_map(ts_ur_super_session *ss) +{ + if(ss && ss->server) { + turn_turnserver* server = (turn_turnserver*)(ss->server); + ur_map_del(server->sessions_map, (ur_map_key_type)(ss->id), NULL); + delete_session_from_mobile_map(ss); + } +} + +static ts_ur_super_session* get_session_from_map(turn_turnserver* server, turnsession_id sid) +{ + ts_ur_super_session *ss = NULL; + if(server) { + ur_map_value_type value = 0; + if(ur_map_get(server->sessions_map, (ur_map_key_type)sid, &value) && value) { + ss = (ts_ur_super_session*)value; + } + } + return ss; +} + +static ts_ur_super_session* get_session_from_mobile_map(turn_turnserver* server, mobile_id_t mid) +{ + ts_ur_super_session *ss = NULL; + if(server && *(server->mobility) && server->mobile_connections_map && mid) { + ur_map_value_type value = 0; + if(ur_map_get(server->mobile_connections_map, (ur_map_key_type)mid, &value) && value) { + ss = (ts_ur_super_session*)value; + } + } + return ss; +} + +static ts_ur_super_session* create_new_ss(turn_turnserver* server) { + // + //printf("%s: 111.111: session size=%lu\n",__FUNCTION__,(unsigned long)sizeof(ts_ur_super_session)); + // + ts_ur_super_session *ss = (ts_ur_super_session*)turn_malloc(sizeof(ts_ur_super_session)); + ns_bzero(ss,sizeof(ts_ur_super_session)); + ss->server = server; + get_default_realm_options(&(ss->realm_options)); + put_session_into_map(ss); + init_allocation(ss,&(ss->alloc), server->tcp_relay_connections); + return ss; +} + +static void delete_ur_map_ss(void *p) { + if (p) { + ts_ur_super_session* ss = (ts_ur_super_session*) p; + delete_session_from_map(ss); + clear_ts_ur_session_data(&(ss->client_session)); + clear_allocation(get_allocation_ss(ss)); + IOA_EVENT_DEL(ss->to_be_allocated_timeout_ev); + turn_free(p,sizeof(ts_ur_super_session)); + } +} + +/////////// clean all ///////////////////// + +static int turn_server_remove_all_from_ur_map_ss(ts_ur_super_session* ss) { + if (!ss) + return 0; + else { + int ret = 0; + if (ss->client_session.s) { + clear_ioa_socket_session_if(ss->client_session.s, ss); + } + if (get_relay_socket_ss(ss)) { + clear_ioa_socket_session_if(get_relay_socket_ss(ss), ss); + } + delete_ur_map_ss(ss); + return ret; + } +} + +///////////////////////////////////////////////////////////////// + +static void client_ss_channel_timeout_handler(ioa_engine_handle e, void* arg) { + + UNUSED_ARG(e); + + if (!arg) + return; + + ch_info* chn = (ch_info*) arg; + + turn_channel_delete(chn); +} + +static void client_ss_perm_timeout_handler(ioa_engine_handle e, void* arg) { + + UNUSED_ARG(e); + + if (!arg) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s: empty permission to be cleaned\n",__FUNCTION__); + return; + } + + turn_permission_info* tinfo = (turn_permission_info*) arg; + + if(!(tinfo->allocated)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s: unallocated permission to be cleaned\n",__FUNCTION__); + return; + } + + if(!(tinfo->lifetime_ev)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s: strange (1) permission to be cleaned\n",__FUNCTION__); + } + + if(!(tinfo->owner)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "!!! %s: strange (2) permission to be cleaned\n",__FUNCTION__); + } + + turn_permission_clean(tinfo); +} + +/////////////////////////////////////////////////////////////////// + +static int update_turn_permission_lifetime(ts_ur_super_session *ss, turn_permission_info *tinfo, turn_time_t time_delta) { + + if (ss && tinfo && tinfo->owner) { + + turn_turnserver *server = (turn_turnserver *) (ss->server); + + if (server) { + + if(!time_delta) time_delta = STUN_PERMISSION_LIFETIME; + tinfo->expiration_time = server->ctime + time_delta; + + IOA_EVENT_DEL(tinfo->lifetime_ev); + tinfo->lifetime_ev = set_ioa_timer(server->e, time_delta, 0, + client_ss_perm_timeout_handler, tinfo, 0, + "client_ss_channel_timeout_handler"); + + return 0; + } + } + return -1; +} + +static int update_channel_lifetime(ts_ur_super_session *ss, ch_info* chn) +{ + + if (chn) { + + turn_permission_info* tinfo = (turn_permission_info*) (chn->owner); + + if (tinfo && tinfo->owner) { + + turn_turnserver *server = (turn_turnserver *) (ss->server); + + if (server) { + + if (update_turn_permission_lifetime(ss, tinfo, STUN_CHANNEL_LIFETIME) < 0) + return -1; + + chn->expiration_time = server->ctime + STUN_CHANNEL_LIFETIME; + + IOA_EVENT_DEL(chn->lifetime_ev); + chn->lifetime_ev = set_ioa_timer(server->e, STUN_CHANNEL_LIFETIME, 0, + client_ss_channel_timeout_handler, + chn, 0, + "client_ss_channel_timeout_handler"); + + return 0; + } + } + } + return -1; +} + +/////////////// TURN /////////////////////////// + +#define SKIP_ATTRIBUTES case STUN_ATTRIBUTE_PRIORITY: case STUN_ATTRIBUTE_FINGERPRINT: case STUN_ATTRIBUTE_MESSAGE_INTEGRITY: break; \ + case STUN_ATTRIBUTE_USERNAME: case STUN_ATTRIBUTE_REALM: case STUN_ATTRIBUTE_NONCE: case STUN_ATTRIBUTE_ORIGIN: \ + sar = stun_attr_get_next_str(ioa_network_buffer_data(in_buffer->nbh),\ + ioa_network_buffer_get_size(in_buffer->nbh), sar); \ + continue + +static u08bits get_transport_value(const u08bits *value) { + if((value[0] == STUN_ATTRIBUTE_TRANSPORT_UDP_VALUE)|| + (value[0] == STUN_ATTRIBUTE_TRANSPORT_TCP_VALUE)) { + return value[0]; + } + return 0; +} + +static int handle_turn_allocate(turn_turnserver *server, + ts_ur_super_session *ss, stun_tid *tid, int *resp_constructed, + int *err_code, const u08bits **reason, u16bits *unknown_attrs, u16bits *ua_num, + ioa_net_data *in_buffer, ioa_network_buffer_handle nbh) { + + allocation* a = get_allocation_ss(ss); + + ts_ur_session* elem = &(ss->client_session); + + if (is_allocation_valid(a)) { + + if (!stun_tid_equals(tid, &(a->tid))) { + *err_code = 437; + *reason = (const u08bits *)"Wrong TID"; + } else { + size_t len = ioa_network_buffer_get_size(nbh); + ioa_addr xor_relayed_addr; + ioa_addr *relayed_addr = get_local_addr_from_ioa_socket(get_relay_socket_ss(ss)); + if(relayed_addr) { + if(server->external_ip_set) { + addr_cpy(&xor_relayed_addr, &(server->external_ip)); + addr_set_port(&xor_relayed_addr,addr_get_port(relayed_addr)); + } else { + addr_cpy(&xor_relayed_addr, relayed_addr); + } + stun_set_allocate_response_str(ioa_network_buffer_data(nbh), &len, + tid, + &xor_relayed_addr, + get_remote_addr_from_ioa_socket(elem->s), + (a->expiration_time - server->ctime), 0, NULL, 0, + ss->s_mobile_id); + ioa_network_buffer_set_size(nbh,len); + *resp_constructed = 1; + } + } + + } else { + + a = NULL; + + u08bits transport = 0; + u32bits lifetime = 0; + int even_port = -1; + int dont_fragment = 0; + u64bits in_reservation_token = 0; + int af = STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_DEFAULT; + u08bits username[STUN_MAX_USERNAME_SIZE+1]="\0"; + size_t ulen = 0; + + stun_attr_ref sar = stun_attr_get_first_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh)); + while (sar && (!(*err_code)) && (*ua_num < MAX_NUMBER_OF_UNKNOWN_ATTRS)) { + + int attr_type = stun_attr_get_type(sar); + + if(attr_type == STUN_ATTRIBUTE_USERNAME) { + const u08bits* value = stun_attr_get_value(sar); + if (value) { + ulen = stun_attr_get_len(sar); + if(ulen>=sizeof(username)) { + *err_code = 400; + *reason = (const u08bits *)"User name is too long"; + break; + } + ns_bcopy(value,username,ulen); + username[ulen]=0; + } + } + + switch (attr_type) { + SKIP_ATTRIBUTES; + case STUN_ATTRIBUTE_MOBILITY_TICKET: + if(!(*(server->mobility))) { + *err_code = 501; + *reason = (const u08bits *)"Mobility Forbidden"; + } else if (stun_attr_get_len(sar) != 0) { + *err_code = 400; + *reason = (const u08bits *)"Wrong Mobility Field"; + } else { + ss->is_mobile = 1; + } + break; + case STUN_ATTRIBUTE_REQUESTED_TRANSPORT: { + if (stun_attr_get_len(sar) != 4) { + *err_code = 400; + *reason = (const u08bits *)"Wrong Transport Field"; + } else if(transport) { + *err_code = 400; + *reason = (const u08bits *)"Duplicate Transport Fields"; + } else { + const u08bits* value = stun_attr_get_value(sar); + if (value) { + transport = get_transport_value(value); + if (!transport || value[1] || value[2] || value[3]) { + *err_code = 442; + *reason = (const u08bits *)"Unsupported Transport Protocol"; + } + if((transport == STUN_ATTRIBUTE_TRANSPORT_TCP_VALUE) && *(server->no_tcp_relay)) { + *err_code = 403; + *reason = (const u08bits *)"TCP Transport is not allowed by the TURN Server configuration"; + } else if((transport == STUN_ATTRIBUTE_TRANSPORT_UDP_VALUE) && *(server->no_udp_relay)) { + *err_code = 403; + *reason = (const u08bits *)"UDP Transport is not allowed by the TURN Server configuration"; + } else if(ss->client_session.s) { + SOCKET_TYPE cst = get_ioa_socket_type(ss->client_session.s); + if((transport == STUN_ATTRIBUTE_TRANSPORT_TCP_VALUE) && + (cst!=TCP_SOCKET) && (cst!=TLS_SOCKET)) { + *err_code = 400; + *reason = (const u08bits *)"Wrong Transport Data"; + } else { + ss->is_tcp_relay = (transport == STUN_ATTRIBUTE_TRANSPORT_TCP_VALUE); + } + } + } else { + *err_code = 400; + *reason = (const u08bits *)"Wrong Transport Data"; + } + } + } + break; + case STUN_ATTRIBUTE_DONT_FRAGMENT: + dont_fragment = 1; + if(!(server->dont_fragment)) + unknown_attrs[(*ua_num)++] = nswap16(attr_type); + break; + case STUN_ATTRIBUTE_LIFETIME: { + if (stun_attr_get_len(sar) != 4) { + *err_code = 400; + *reason = (const u08bits *)"Wrong Lifetime Field"; + } else { + const u08bits* value = stun_attr_get_value(sar); + if (!value) { + *err_code = 400; + *reason = (const u08bits *)"Wrong Lifetime Data"; + } else { + lifetime = nswap32(*((const u32bits*)value)); + } + } + } + break; + case STUN_ATTRIBUTE_EVEN_PORT: { + if (in_reservation_token) { + *err_code = 400; + *reason = (const u08bits *)"Even Port and Reservation Token cannot be used together"; + } else if (even_port >= 0) { + *err_code = 400; + *reason = (const u08bits *)"Even Port cannot be used in this request"; + } else { + even_port = stun_attr_get_even_port(sar); + } + } + break; + case STUN_ATTRIBUTE_RESERVATION_TOKEN: { + int len = stun_attr_get_len(sar); + if (len != 8) { + *err_code = 400; + *reason = (const u08bits *)"Wrong Format of Reservation Token"; + } else if(af != STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_DEFAULT) { + *err_code = 400; + *reason = (const u08bits *)"Address family attribute can not be used with reservation token request"; + } else { + if (even_port >= 0) { + *err_code = 400; + *reason = (const u08bits *)"Reservation Token cannot be used in this request with even port"; + } else if (in_reservation_token) { + *err_code = 400; + *reason = (const u08bits *)"Reservation Token cannot be used in this request"; + } else { + in_reservation_token = stun_attr_get_reservation_token_value(sar); + } + } + } + break; + case STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY: { + if(in_reservation_token) { + *err_code = 400; + *reason = (const u08bits *)"Address family attribute can not be used with reservation token request"; + } else { + int af_req = stun_get_requested_address_family(sar); + if(af == STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_DEFAULT) { + switch (af_req) { + case STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV4: + case STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV6: + af = af_req; + break; + default: + *err_code = 440; + *reason = (const u08bits *)"Unsupported address family requested"; + } + } else { + *err_code = 400; + *reason = (const u08bits *)"Only one address family attribute can be used in a request"; + } + } + } + break; + default: + if(attr_type>=0x0000 && attr_type<=0x7FFF) + unknown_attrs[(*ua_num)++] = nswap16(attr_type); + }; + sar = stun_attr_get_next_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + sar); + } + + if (!transport) { + + *err_code = 400; + if(!(*reason)) + *reason = (const u08bits *)"Transport field missed or wrong"; + + } else if (*ua_num > 0) { + + *err_code = 420; + if(!(*reason)) + *reason = (const u08bits *)"Unknown attribute"; + + } else if (*err_code) { + + ; + + } else if((transport == STUN_ATTRIBUTE_TRANSPORT_TCP_VALUE) && (dont_fragment || in_reservation_token || (even_port!=-1))) { + + *err_code = 400; + if(!(*reason)) + *reason = (const u08bits *)"Request parameters are incompatible with TCP transport"; + + } else { + + if(*(server->mobility)) { + if(!(ss->is_mobile)) { + delete_session_from_mobile_map(ss); + } + } + + lifetime = stun_adjust_allocate_lifetime(lifetime); + u64bits out_reservation_token = 0; + + if(inc_quota(ss, username)<0) { + + *err_code = 486; + *reason = (const u08bits *)"Allocation Quota Reached"; + + } else { + + if (create_relay_connection(server, ss, lifetime, + af, transport, + even_port, in_reservation_token, &out_reservation_token, + err_code, reason, + tcp_peer_accept_connection) < 0) { + + dec_quota(ss); + + if (!*err_code) { + *err_code = 437; + if(!(*reason)) + *reason = (const u08bits *)"Cannot create relay endpoint"; + } + + } else { + + a = get_allocation_ss(ss); + + set_allocation_valid(a,1); + + stun_tid_cpy(&(a->tid), tid); + + size_t len = ioa_network_buffer_get_size(nbh); + + ioa_addr xor_relayed_addr; + ioa_addr *relayed_addr = get_local_addr_from_ioa_socket(get_relay_socket_ss(ss)); + if(relayed_addr) { + if(server->external_ip_set) { + addr_cpy(&xor_relayed_addr, &(server->external_ip)); + addr_set_port(&xor_relayed_addr,addr_get_port(relayed_addr)); + } else { + addr_cpy(&xor_relayed_addr, relayed_addr); + } + + stun_set_allocate_response_str(ioa_network_buffer_data(nbh), &len, tid, + &xor_relayed_addr, + get_remote_addr_from_ioa_socket(elem->s), lifetime, + 0,NULL, + out_reservation_token, + ss->s_mobile_id); + ioa_network_buffer_set_size(nbh,len); + *resp_constructed = 1; + + turn_report_allocation_set(&(ss->alloc), lifetime, 0); + } + } + } + } + } + + if (!(*resp_constructed)) { + + if (!(*err_code)) { + *err_code = 437; + } + + size_t len = ioa_network_buffer_get_size(nbh); + stun_set_allocate_response_str(ioa_network_buffer_data(nbh), &len, tid, NULL, NULL, 0, *err_code, *reason, 0, ss->s_mobile_id); + ioa_network_buffer_set_size(nbh,len); + *resp_constructed = 1; + } + + return 0; +} + +static int handle_turn_refresh(turn_turnserver *server, + ts_ur_super_session *ss, stun_tid *tid, int *resp_constructed, + int *err_code, const u08bits **reason, u16bits *unknown_attrs, u16bits *ua_num, + ioa_net_data *in_buffer, ioa_network_buffer_handle nbh, + int message_integrity, int *no_response, int can_resume) { + + allocation* a = get_allocation_ss(ss); + + if (!is_allocation_valid(a) && !(*(server->mobility))) { + + *err_code = 437; + *reason = (const u08bits *)"Invalid allocation"; + + } else { + + u32bits lifetime = 0; + int to_delete = 0; + mobile_id_t mid = 0; + char smid[sizeof(ss->s_mobile_id)] = "\0"; + + stun_attr_ref sar = stun_attr_get_first_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh)); + while (sar && (!(*err_code)) && (*ua_num < MAX_NUMBER_OF_UNKNOWN_ATTRS)) { + int attr_type = stun_attr_get_type(sar); + switch (attr_type) { + SKIP_ATTRIBUTES; + case STUN_ATTRIBUTE_MOBILITY_TICKET: { + if(!(*(server->mobility))) { + *err_code = 501; + *reason = (const u08bits *)"Mobility forbidden"; + } if(is_allocation_valid(a)) { + *err_code = 400; + *reason = (const u08bits *)"Mobility ticket cannot be used for a stable, already established allocation"; + } else { + int smid_len = stun_attr_get_len(sar); + if(smid_len>0 && (((size_t)smid_len)relay_session.s); + if(addr) { + int is_err = 0; + switch (af_req) { + case STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV4: + if(addr->ss.sa_family != AF_INET) { + is_err = 1; + } + break; + case STUN_ATTRIBUTE_REQUESTED_ADDRESS_FAMILY_VALUE_IPV6: + if(addr->ss.sa_family != AF_INET6) { + is_err = 1; + } + break; + default: + is_err = 1; + } + + if(is_err) { + *err_code = 443; + *reason = (const u08bits *)"Peer Address Family Mismatch"; + } + } + } + break; + default: + if(attr_type>=0x0000 && attr_type<=0x7FFF) + unknown_attrs[(*ua_num)++] = nswap16(attr_type); + }; + sar = stun_attr_get_next_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), sar); + } + + if (*ua_num > 0) { + + *err_code = 420; + *reason = (const u08bits *)"Unknown attribute"; + + } else if (*err_code) { + + ; + + } else if(!is_allocation_valid(a)) { + + if(mid && smid[0]) { + + turnserver_id tsid = ((0xFF00000000000000LL) & mid)>>56; + + if(tsid != server->id) { + + if(server->send_socket_to_relay) { + ioa_socket_handle new_s = detach_ioa_socket(ss->client_session.s, 1); + if(new_s) { + if(server->send_socket_to_relay(tsid, mid, tid, new_s, message_integrity, + RMT_MOBILE_SOCKET, in_buffer)<0) { + *err_code = 400; + *reason = (const u08bits *)"Wrong mobile ticket"; + } else { + *no_response = 1; + } + } else { + *err_code = 500; + *reason = (const u08bits *)"Cannot create new socket"; + return -1; + } + } else { + *err_code = 500; + *reason = (const u08bits *)"Server send socket procedure is not set"; + } + + ss->to_be_closed = 1; + + } else { + + ts_ur_super_session *orig_ss = get_session_from_mobile_map(server, mid); + if(!orig_ss) { + *err_code = 404; + *reason = (const u08bits *)"Allocation not found"; + } else if(orig_ss == ss) { + *err_code = 437; + *reason = (const u08bits *)"Invalid allocation"; + } else if(!(orig_ss->is_mobile)) { + *err_code = 500; + *reason = (const u08bits *)"Software error: invalid mobile allocation"; + } else if(orig_ss->client_session.s == ss->client_session.s) { + *err_code = 500; + *reason = (const u08bits *)"Software error: invalid mobile client socket (orig)"; + } else if(!(ss->client_session.s)) { + *err_code = 500; + *reason = (const u08bits *)"Software error: invalid mobile client socket (new)"; + } else { + + get_realm_options_by_name(orig_ss->realm_options.name, &(ss->realm_options)); + + //Check security: + int postpone_reply = 0; + check_stun_auth(server, orig_ss, tid, resp_constructed, err_code, reason, in_buffer, nbh, + STUN_METHOD_REFRESH, &message_integrity, &postpone_reply, can_resume); + + if(postpone_reply) { + + *no_response = 1; + + } else if(!(*err_code)) { + + //Session transfer: + + if (to_delete) + lifetime = 0; + else + lifetime = stun_adjust_allocate_lifetime(lifetime); + + if (refresh_relay_connection(server, orig_ss, lifetime, 0, 0, 0, + err_code) < 0) { + + if (!(*err_code)) { + *err_code = 437; + *reason = (const u08bits *)"Cannot refresh relay connection (internal error)"; + } + + } else if(!to_delete && orig_ss && (inc_quota(orig_ss, orig_ss->username)<0)) { + + *err_code = 486; + *reason = (const u08bits *)"Allocation Quota Reached"; + + } else { + + //Transfer socket: + + ioa_socket_handle s = detach_ioa_socket(ss->client_session.s, 0); + + ss->to_be_closed = 1; + + if(!s) { + dec_quota(orig_ss); + *err_code = 500; + } else { + + if(attach_socket_to_session(server, s, orig_ss) < 0) { + IOA_CLOSE_SOCKET(s); + *err_code = 500; + dec_quota(orig_ss); + } else { + + delete_session_from_mobile_map(ss); + delete_session_from_mobile_map(orig_ss); + put_session_into_mobile_map(orig_ss); + + //Use new buffer and redefine ss: + nbh = ioa_network_buffer_allocate(server->e); + ss = orig_ss; + size_t len = ioa_network_buffer_get_size(nbh); + + turn_report_allocation_set(&(ss->alloc), lifetime, 1); + + stun_init_success_response_str(STUN_METHOD_REFRESH, ioa_network_buffer_data(nbh), &len, tid); + u32bits lt = nswap32(lifetime); + + stun_attr_add_str(ioa_network_buffer_data(nbh), &len, STUN_ATTRIBUTE_LIFETIME, + (const u08bits*) <, 4); + ioa_network_buffer_set_size(nbh,len); + + stun_attr_add_str(ioa_network_buffer_data(nbh), &len, + STUN_ATTRIBUTE_MOBILITY_TICKET, + (u08bits*)ss->s_mobile_id,strlen(ss->s_mobile_id)); + ioa_network_buffer_set_size(nbh,len); + + { + static const u08bits *field = (const u08bits *) TURN_SOFTWARE; + static const size_t fsz = sizeof(TURN_SOFTWARE)-1; + size_t len = ioa_network_buffer_get_size(nbh); + stun_attr_add_str(ioa_network_buffer_data(nbh), &len, STUN_ATTRIBUTE_SOFTWARE, field, fsz); + ioa_network_buffer_set_size(nbh, len); + } + + if(message_integrity) { + stun_attr_add_integrity_str(server->ct,ioa_network_buffer_data(nbh),&len,ss->hmackey,ss->pwd,server->shatype); + ioa_network_buffer_set_size(nbh,len); + } + + if ((server->fingerprint) || ss->enforce_fingerprints) { + if (stun_attr_add_fingerprint_str(ioa_network_buffer_data(nbh), &len) < 0) { + dec_quota(ss); + *err_code = 500; + ioa_network_buffer_delete(server->e, nbh); + return -1; + } + ioa_network_buffer_set_size(nbh, len); + } + + *no_response = 1; + + return write_client_connection(server, ss, nbh, TTL_IGNORE, TOS_IGNORE); + } + } + } + } + + report_turn_session_info(server,orig_ss,0); + } + } + } else { + *err_code = 437; + *reason = (const u08bits *)"Invalid allocation"; + } + + } else { + + if (to_delete) + lifetime = 0; + else + lifetime = stun_adjust_allocate_lifetime(lifetime); + + if (refresh_relay_connection(server, ss, lifetime, 0, 0, 0, + err_code) < 0) { + + if (!(*err_code)) { + *err_code = 437; + *reason = (const u08bits *)"Cannot refresh relay connection (internal error)"; + } + + } else { + + turn_report_allocation_set(&(ss->alloc), lifetime, 1); + + size_t len = ioa_network_buffer_get_size(nbh); + stun_init_success_response_str(STUN_METHOD_REFRESH, ioa_network_buffer_data(nbh), &len, tid); + u32bits lt = nswap32(lifetime); + + stun_attr_add_str(ioa_network_buffer_data(nbh), &len, STUN_ATTRIBUTE_LIFETIME, + (const u08bits*) <, 4); + ioa_network_buffer_set_size(nbh,len); + + *resp_constructed = 1; + } + } + } + + if(!no_response) { + if (!(*resp_constructed)) { + + if (!(*err_code)) { + *err_code = 437; + } + + size_t len = ioa_network_buffer_get_size(nbh); + stun_init_error_response_str(STUN_METHOD_REFRESH, ioa_network_buffer_data(nbh), &len, *err_code, *reason, tid); + ioa_network_buffer_set_size(nbh,len); + + *resp_constructed = 1; + } + } + + return 0; +} + +/* RFC 6062 ==>> */ + +static void tcp_deliver_delayed_buffer(unsent_buffer *ub, ioa_socket_handle s, ts_ur_super_session *ss) +{ + if(ub && s && ub->bufs && ub->sz && ss) { + size_t i = 0; + do { + ioa_network_buffer_handle nbh = top_unsent_buffer(ub); + if(!nbh) + break; + + u32bits bytes = (u32bits)ioa_network_buffer_get_size(nbh); + + int ret = send_data_from_ioa_socket_nbh(s, NULL, nbh, TTL_IGNORE, TOS_IGNORE); + if (ret < 0) { + set_ioa_socket_tobeclosed(s); + } else { + ++(ss->sent_packets); + ss->sent_bytes += bytes; + turn_report_session_usage(ss); + } + pop_unsent_buffer(ub); + } while(!ioa_socket_tobeclosed(s) && ((i++)owner; + if(a) { + ss=(ts_ur_super_session*)a->owner; + } + + if((tc->state != TC_STATE_READY) || !(tc->client_s)) { + add_unsent_buffer(&(tc->ub_to_client), in_buffer->nbh); + in_buffer->nbh = NULL; + return; + } + + ioa_network_buffer_handle nbh = in_buffer->nbh; + in_buffer->nbh = NULL; + + u32bits bytes = (u32bits)ioa_network_buffer_get_size(nbh); + + int ret = send_data_from_ioa_socket_nbh(tc->client_s, NULL, nbh, TTL_IGNORE, TOS_IGNORE); + if (ret < 0) { + set_ioa_socket_tobeclosed(s); + } else if(ss) { + ++(ss->sent_packets); + ss->sent_bytes += bytes; + turn_report_session_usage(ss); + } +} + +static void tcp_client_input_handler_rfc6062data(ioa_socket_handle s, int event_type, ioa_net_data *in_buffer, void *arg) +{ + if (!(event_type & IOA_EV_READ) || !arg) + return; + + UNUSED_ARG(s); + + tcp_connection *tc = (tcp_connection*)arg; + ts_ur_super_session *ss=NULL; + allocation *a=(allocation*)tc->owner; + if(a) { + ss=(ts_ur_super_session*)a->owner; + } + + if(tc->state != TC_STATE_READY) + return; + + if(!(tc->peer_s)) + return; + + ioa_network_buffer_handle nbh = in_buffer->nbh; + in_buffer->nbh = NULL; + + if(ss) { + u32bits bytes = (u32bits)ioa_network_buffer_get_size(nbh); + ++(ss->received_packets); + ss->received_bytes += bytes; + } + + int ret = send_data_from_ioa_socket_nbh(tc->peer_s, NULL, nbh, TTL_IGNORE, TOS_IGNORE); + if (ret < 0) { + set_ioa_socket_tobeclosed(s); + } + + turn_report_session_usage(ss); +} + +static void tcp_conn_bind_timeout_handler(ioa_engine_handle e, void *arg) +{ + UNUSED_ARG(e); + if(arg) { + tcp_connection *tc = (tcp_connection *)arg; + delete_tcp_connection(tc); + } +} + +static void tcp_peer_connection_completed_callback(int success, void *arg) +{ + if(arg) { + tcp_connection *tc = (tcp_connection *)arg; + allocation *a = (allocation*)(tc->owner); + ts_ur_super_session *ss = (ts_ur_super_session*)(a->owner); + turn_turnserver *server=(turn_turnserver*)(ss->server); + int err_code = 0; + + IOA_EVENT_DEL(tc->peer_conn_timeout); + + ioa_network_buffer_handle nbh = ioa_network_buffer_allocate(server->e); + size_t len = ioa_network_buffer_get_size(nbh); + + if(success) { + if(register_callback_on_ioa_socket(server->e, tc->peer_s, IOA_EV_READ, tcp_peer_input_handler, tc, 1)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: cannot set TCP peer data input callback\n", __FUNCTION__); + success=0; + err_code = 500; + } + } + + if(success) { + tc->state = TC_STATE_PEER_CONNECTED; + stun_init_success_response_str(STUN_METHOD_CONNECT, ioa_network_buffer_data(nbh), &len, &(tc->tid)); + stun_attr_add_str(ioa_network_buffer_data(nbh), &len, STUN_ATTRIBUTE_CONNECTION_ID, + (const u08bits*)&(tc->id), 4); + + IOA_EVENT_DEL(tc->conn_bind_timeout); + tc->conn_bind_timeout = set_ioa_timer(server->e, TCP_CONN_BIND_TIMEOUT, 0, + tcp_conn_bind_timeout_handler, tc, 0, + "tcp_conn_bind_timeout_handler"); + + } else { + tc->state = TC_STATE_FAILED; + if(!err_code) + err_code = 447; + const u08bits *reason = (const u08bits *)"Connection Timeout or Failure"; + stun_init_error_response_str(STUN_METHOD_CONNECT, ioa_network_buffer_data(nbh), &len, err_code, reason, &(tc->tid)); + } + + ioa_network_buffer_set_size(nbh,len); + + if(need_stun_authentication(server, ss)) { + stun_attr_add_integrity_str(server->ct,ioa_network_buffer_data(nbh),&len,ss->hmackey,ss->pwd,server->shatype); + ioa_network_buffer_set_size(nbh,len); + } + + write_client_connection(server, ss, nbh, TTL_IGNORE, TOS_IGNORE); + + if(!success) { + delete_tcp_connection(tc); + } + /* test */ + else if(0) + { + int i = 0; + for(i=0;i<22;i++) { + ioa_network_buffer_handle nbh_test = ioa_network_buffer_allocate(server->e); + size_t len_test = ioa_network_buffer_get_size(nbh_test); + u08bits *data = ioa_network_buffer_data(nbh_test); + const char* data_test="111.111.111.111.111"; + len_test = strlen(data_test); + ns_bcopy(data_test,data,len_test); + ioa_network_buffer_set_size(nbh_test,len_test); + send_data_from_ioa_socket_nbh(tc->peer_s, NULL, nbh_test, TTL_IGNORE, TOS_IGNORE); + } + } + } +} + +static void tcp_peer_conn_timeout_handler(ioa_engine_handle e, void *arg) +{ + UNUSED_ARG(e); + + tcp_peer_connection_completed_callback(0,arg); +} + +static int tcp_start_connection_to_peer(turn_turnserver *server, ts_ur_super_session *ss, stun_tid *tid, + allocation *a, ioa_addr *peer_addr, + int *err_code, const u08bits **reason) +{ + FUNCSTART; + + if(!ss) { + *err_code = 500; + *reason = (const u08bits *)"Server error: empty session"; + FUNCEND; + return -1; + } + + if(!(a->relay_session.s)) { + *err_code = 500; + *reason = (const u08bits *)"Server error: no relay connection created"; + FUNCEND; + return -1; + } + + tcp_connection *tc = get_tcp_connection_by_peer(a, peer_addr); + if(tc) { + *err_code = 446; + *reason = (const u08bits *)"Connection Already Exists"; + FUNCEND; + return -1; + } + + tc = create_tcp_connection(server->id, a, tid, peer_addr, err_code); + if(!tc) { + if(!(*err_code)) { + *err_code = 500; + *reason = (const u08bits *)"Server error: TCP connection object creation failed"; + } + FUNCEND; + return -1; + } else if(*err_code) { + delete_tcp_connection(tc); + FUNCEND; + return -1; + } + + IOA_EVENT_DEL(tc->peer_conn_timeout); + tc->peer_conn_timeout = set_ioa_timer(server->e, TCP_PEER_CONN_TIMEOUT, 0, + tcp_peer_conn_timeout_handler, tc, 0, + "tcp_peer_conn_timeout_handler"); + + ioa_socket_handle tcs = ioa_create_connecting_tcp_relay_socket(a->relay_session.s, peer_addr, tcp_peer_connection_completed_callback, tc); + if(!tcs) { + delete_tcp_connection(tc); + *err_code = 500; + *reason = (const u08bits *)"Server error: TCP relay socket for connection cannot be created"; + FUNCEND; + return -1; + } + + tc->state = TC_STATE_CLIENT_TO_PEER_CONNECTING; + IOA_CLOSE_SOCKET(tc->peer_s); + tc->peer_s = tcs; + set_ioa_socket_sub_session(tc->peer_s,tc); + + FUNCEND; + return 0; +} + +static void tcp_peer_accept_connection(ioa_socket_handle s, void *arg) +{ + if(s) { + + if(!arg) { + close_ioa_socket(s); + return; + } + + ts_ur_super_session *ss = (ts_ur_super_session*)arg; + turn_turnserver *server=(turn_turnserver*)(ss->server); + + FUNCSTART; + + allocation *a = &(ss->alloc); + ioa_addr *peer_addr = get_remote_addr_from_ioa_socket(s); + + tcp_connection *tc = get_tcp_connection_by_peer(a, peer_addr); + if(tc) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: peer data socket with this address already exist\n", __FUNCTION__); + if(tc->peer_s != s) + close_ioa_socket(s); + FUNCEND; + return; + } + + if(!good_peer_addr(server, peer_addr)) { + u08bits saddr[256]; + addr_to_string(peer_addr, saddr); + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: an attempt to connect from a peer with forbidden address: %s\n", __FUNCTION__,saddr); + close_ioa_socket(s); + FUNCEND; + return; + } + + if(!can_accept_tcp_connection_from_peer(a,peer_addr,server->server_relay)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: peer has no permission to connect\n", __FUNCTION__); + close_ioa_socket(s); + FUNCEND; + return; + } + + stun_tid tid; + ns_bzero(&tid,sizeof(stun_tid)); + int err_code=0; + tc = create_tcp_connection(server->id, a, &tid, peer_addr, &err_code); + if(!tc) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: cannot create TCP connection\n", __FUNCTION__); + close_ioa_socket(s); + FUNCEND; + return; + } + + tc->state = TC_STATE_PEER_CONNECTED; + tc->peer_s = s; + + set_ioa_socket_session(s,ss); + set_ioa_socket_sub_session(s,tc); + + if(register_callback_on_ioa_socket(server->e, s, IOA_EV_READ, tcp_peer_input_handler, tc, 1)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: cannot set TCP peer data input callback\n", __FUNCTION__); + close_ioa_socket(s); + tc->peer_s = NULL; + tc->state = TC_STATE_UNKNOWN; + FUNCEND; + return; + } + + IOA_EVENT_DEL(tc->conn_bind_timeout); + tc->conn_bind_timeout = set_ioa_timer(server->e, TCP_CONN_BIND_TIMEOUT, 0, + tcp_conn_bind_timeout_handler, tc, 0, + "tcp_conn_bind_timeout_handler"); + + ioa_network_buffer_handle nbh = ioa_network_buffer_allocate(server->e); + size_t len = ioa_network_buffer_get_size(nbh); + + stun_init_indication_str(STUN_METHOD_CONNECTION_ATTEMPT, ioa_network_buffer_data(nbh), &len); + stun_attr_add_str(ioa_network_buffer_data(nbh), &len, STUN_ATTRIBUTE_CONNECTION_ID, + (const u08bits*)&(tc->id), 4); + stun_attr_add_addr_str(ioa_network_buffer_data(nbh), &len, STUN_ATTRIBUTE_XOR_PEER_ADDRESS, peer_addr); + + ioa_network_buffer_set_size(nbh,len); + + { + static const u08bits *field = (const u08bits *) TURN_SOFTWARE; + static const size_t fsz = sizeof(TURN_SOFTWARE)-1; + size_t len = ioa_network_buffer_get_size(nbh); + stun_attr_add_str(ioa_network_buffer_data(nbh), &len, STUN_ATTRIBUTE_SOFTWARE, field, fsz); + ioa_network_buffer_set_size(nbh, len); + } + + /* We add integrity for short-term indication messages, only */ + if(server->ct == TURN_CREDENTIALS_SHORT_TERM) + { + stun_attr_add_integrity_str(server->ct,ioa_network_buffer_data(nbh),&len,ss->hmackey,ss->pwd,server->shatype); + ioa_network_buffer_set_size(nbh,len); + } + + if ((server->fingerprint) || ss->enforce_fingerprints) { + size_t len = ioa_network_buffer_get_size(nbh); + stun_attr_add_fingerprint_str(ioa_network_buffer_data(nbh), &len); + ioa_network_buffer_set_size(nbh, len); + } + + write_client_connection(server, ss, nbh, TTL_IGNORE, TOS_IGNORE); + + FUNCEND; + } +} + +static int handle_turn_connect(turn_turnserver *server, + ts_ur_super_session *ss, stun_tid *tid, + int *err_code, const u08bits **reason, u16bits *unknown_attrs, u16bits *ua_num, + ioa_net_data *in_buffer) { + + FUNCSTART; + ioa_addr peer_addr; + int peer_found = 0; + addr_set_any(&peer_addr); + allocation* a = get_allocation_ss(ss); + + if(!(ss->is_tcp_relay)) { + *err_code = 403; + *reason = (const u08bits *)"Connect cannot be used with UDP relay"; + } else if (!is_allocation_valid(a)) { + *err_code = 437; + *reason = (const u08bits *)"Allocation mismatch"; + } else { + + stun_attr_ref sar = stun_attr_get_first_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh)); + while (sar && (!(*err_code)) && (*ua_num < MAX_NUMBER_OF_UNKNOWN_ATTRS)) { + int attr_type = stun_attr_get_type(sar); + switch (attr_type) { + SKIP_ATTRIBUTES; + case STUN_ATTRIBUTE_XOR_PEER_ADDRESS: + { + if(stun_attr_get_addr_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + sar, &peer_addr, + &(ss->default_peer_addr)) == -1) { + *err_code = 400; + *reason = (const u08bits *)"Bad Peer Address"; + } else { + ioa_addr *relay_addr = get_local_addr_from_ioa_socket(a->relay_session.s); + + if(relay_addr && (relay_addr->ss.sa_family != peer_addr.ss.sa_family)) { + *err_code = 443; + *reason = (const u08bits *)"Peer Address Family Mismatch"; + } + + peer_found = 1; + } + break; + } + default: + if(attr_type>=0x0000 && attr_type<=0x7FFF) + unknown_attrs[(*ua_num)++] = nswap16(attr_type); + }; + sar = stun_attr_get_next_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + sar); + } + + if (*ua_num > 0) { + + *err_code = 420; + *reason = (const u08bits *)"Unknown attribute"; + + } else if (*err_code) { + + ; + + } else if (!peer_found) { + + *err_code = 400; + *reason = (const u08bits *)"Where is Peer Address ?"; + + } else { + if(!good_peer_addr(server,&peer_addr)) { + *err_code = 403; + *reason = (const u08bits *) "Forbidden IP"; + } else { + tcp_start_connection_to_peer(server, ss, tid, a, &peer_addr, err_code, reason); + } + } + } + + FUNCEND; + return 0; +} + +static int handle_turn_connection_bind(turn_turnserver *server, + ts_ur_super_session *ss, stun_tid *tid, int *resp_constructed, + int *err_code, const u08bits **reason, u16bits *unknown_attrs, u16bits *ua_num, + ioa_net_data *in_buffer, ioa_network_buffer_handle nbh, int message_integrity) { + + allocation* a = get_allocation_ss(ss); + + u16bits method = STUN_METHOD_CONNECTION_BIND; + + if (is_allocation_valid(a)) { + + *err_code = 400; + *reason = (const u08bits *)"Bad request: CONNECTION_BIND cannot be issued after allocation"; + + } else if((get_ioa_socket_type(ss->client_session.s)!=TCP_SOCKET) && (get_ioa_socket_type(ss->client_session.s)!=TLS_SOCKET)) { + + *err_code = 400; + *reason = (const u08bits *)"Bad request: CONNECTION_BIND only possible with TCP/TLS"; + + } else { + tcp_connection_id id = 0; + + stun_attr_ref sar = stun_attr_get_first_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh)); + while (sar && (!(*err_code)) && (*ua_num < MAX_NUMBER_OF_UNKNOWN_ATTRS)) { + int attr_type = stun_attr_get_type(sar); + switch (attr_type) { + SKIP_ATTRIBUTES; + case STUN_ATTRIBUTE_CONNECTION_ID: { + if (stun_attr_get_len(sar) != 4) { + *err_code = 400; + *reason = (const u08bits *)"Wrong Connection ID field format"; + } else { + const u08bits* value = stun_attr_get_value(sar); + if (!value) { + *err_code = 400; + *reason = (const u08bits *)"Wrong Connection ID field data"; + } else { + id = *((const u32bits*)value); //AS-IS encoding, no conversion to/from network byte order + } + } + } + break; + default: + if(attr_type>=0x0000 && attr_type<=0x7FFF) + unknown_attrs[(*ua_num)++] = nswap16(attr_type); + }; + sar = stun_attr_get_next_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), sar); + } + + if (*ua_num > 0) { + + *err_code = 420; + *reason = (const u08bits *)"Unknown attribute"; + + } else if (*err_code) { + + ; + + } else { + if(server->send_socket_to_relay) { + turnserver_id sid = (id & 0xFF000000)>>24; + ioa_socket_handle s = ss->client_session.s; + if(s) { + ioa_socket_handle new_s = detach_ioa_socket(s, 1); + if(new_s) { + if(server->send_socket_to_relay(sid, id, tid, new_s, message_integrity, RMT_CB_SOCKET, NULL)<0) { + *err_code = 400; + *reason = (const u08bits *)"Wrong connection id"; + } + } else { + *err_code = 500; + } + } else { + *err_code = 500; + } + } else { + *err_code = 500; + } + ss->to_be_closed = 1; + } + } + + if (!(*resp_constructed) && ss->client_session.s && !ioa_socket_tobeclosed(ss->client_session.s)) { + + if (!(*err_code)) { + *err_code = 437; + } + + size_t len = ioa_network_buffer_get_size(nbh); + stun_init_error_response_str(method, ioa_network_buffer_data(nbh), &len, *err_code, *reason, tid); + ioa_network_buffer_set_size(nbh,len); + + *resp_constructed = 1; + } + + return 0; +} + +int turnserver_accept_tcp_client_data_connection(turn_turnserver *server, tcp_connection_id tcid, stun_tid *tid, ioa_socket_handle s, int message_integrity) +{ + if(!server) + return -1; + + FUNCSTART; + + tcp_connection *tc = NULL; + ts_ur_super_session *ss = NULL; + + int err_code = 0; + + if(tcid && tid && s) { + + tc = get_and_clean_tcp_connection_by_id(server->tcp_relay_connections, tcid); + if(!tc || (tc->state == TC_STATE_READY) || (tc->client_s)) { + err_code = 400; + } else { + allocation *a = (allocation*)(tc->owner); + if(!a || !(a->owner)) { + err_code = 500; + } else { + ss = (ts_ur_super_session*)(a->owner); + if(!check_username_hash(s,ss->username,(u08bits*)ss->realm_options.name)) { + err_code = 401; + } else { + tc->state = TC_STATE_READY; + tc->client_s = s; + set_ioa_socket_session(s,ss); + set_ioa_socket_sub_session(s,tc); + set_ioa_socket_app_type(s,TCP_CLIENT_DATA_SOCKET); + if(register_callback_on_ioa_socket(server->e, s, IOA_EV_READ, tcp_client_input_handler_rfc6062data, tc, 1)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: cannot set TCP client data input callback\n", __FUNCTION__); + err_code = 500; + } else { + IOA_EVENT_DEL(tc->conn_bind_timeout); + } + } + } + } + + ioa_network_buffer_handle nbh = ioa_network_buffer_allocate(server->e); + + if(!err_code) { + size_t len = ioa_network_buffer_get_size(nbh); + stun_init_success_response_str(STUN_METHOD_CONNECTION_BIND, ioa_network_buffer_data(nbh), &len, tid); + ioa_network_buffer_set_size(nbh,len); + } else { + size_t len = ioa_network_buffer_get_size(nbh); + stun_init_error_response_str(STUN_METHOD_CONNECTION_BIND, ioa_network_buffer_data(nbh), &len, err_code, NULL, tid); + ioa_network_buffer_set_size(nbh,len); + } + + { + static const u08bits *field = (const u08bits *) TURN_SOFTWARE; + static const size_t fsz = sizeof(TURN_SOFTWARE)-1; + size_t len = ioa_network_buffer_get_size(nbh); + stun_attr_add_str(ioa_network_buffer_data(nbh), &len, STUN_ATTRIBUTE_SOFTWARE, field, fsz); + ioa_network_buffer_set_size(nbh, len); + } + + if(message_integrity && ss) { + size_t len = ioa_network_buffer_get_size(nbh); + stun_attr_add_integrity_str(server->ct,ioa_network_buffer_data(nbh),&len,ss->hmackey,ss->pwd,server->shatype); + ioa_network_buffer_set_size(nbh,len); + } + + if ((server->fingerprint) || (ss &&(ss->enforce_fingerprints))) { + size_t len = ioa_network_buffer_get_size(nbh); + stun_attr_add_fingerprint_str(ioa_network_buffer_data(nbh), &len); + ioa_network_buffer_set_size(nbh, len); + } + + if(ss && !err_code) { + send_data_from_ioa_socket_nbh(s, NULL, nbh, TTL_IGNORE, TOS_IGNORE); + tcp_deliver_delayed_buffer(&(tc->ub_to_client),s,ss); + FUNCEND; + return 0; + } else { + /* Just to set the necessary structures for the packet sending: */ + if(register_callback_on_ioa_socket(server->e, s, IOA_EV_READ, NULL, NULL, 1)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "%s: cannot set TCP tmp client data input callback\n", __FUNCTION__); + ioa_network_buffer_delete(server->e, nbh); + } else { + send_data_from_ioa_socket_nbh(s, NULL, nbh, TTL_IGNORE, TOS_IGNORE); + } + } + } + + if(s) { + if(tc && (s==tc->client_s)) { + ; + } else { + close_ioa_socket(s); + } + } + + FUNCEND; + return -1; +} + +/* <<== RFC 6062 */ + +static int handle_turn_channel_bind(turn_turnserver *server, + ts_ur_super_session *ss, stun_tid *tid, int *resp_constructed, + int *err_code, const u08bits **reason, u16bits *unknown_attrs, u16bits *ua_num, + ioa_net_data *in_buffer, ioa_network_buffer_handle nbh) { + + FUNCSTART; + u16bits chnum = 0; + ioa_addr peer_addr; + addr_set_any(&peer_addr); + allocation* a = get_allocation_ss(ss); + int addr_found = 0; + + if(ss->is_tcp_relay) { + *err_code = 403; + *reason = (const u08bits *)"Channel bind cannot be used with TCP relay"; + } else if (is_allocation_valid(a)) { + + stun_attr_ref sar = stun_attr_get_first_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh)); + while (sar && (!(*err_code)) && (*ua_num < MAX_NUMBER_OF_UNKNOWN_ATTRS)) { + int attr_type = stun_attr_get_type(sar); + switch (attr_type) { + SKIP_ATTRIBUTES; + case STUN_ATTRIBUTE_CHANNEL_NUMBER: { + if (chnum) { + chnum = 0; + *err_code = 400; + *reason = (const u08bits *)"Channel number cannot be duplicated in this request"; + break; + } + chnum = stun_attr_get_channel_number(sar); + if (!chnum) { + *err_code = 400; + *reason = (const u08bits *)"Channel number cannot be zero in this request"; + break; + } + } + break; + case STUN_ATTRIBUTE_XOR_PEER_ADDRESS: + { + stun_attr_get_addr_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + sar, &peer_addr, + &(ss->default_peer_addr)); + + ioa_addr *relay_addr = get_local_addr_from_ioa_socket(a->relay_session.s); + + if(relay_addr && relay_addr->ss.sa_family != peer_addr.ss.sa_family) { + *err_code = 443; + *reason = (const u08bits *)"Peer Address Family Mismatch"; + } + + if(addr_get_port(&peer_addr) < 1) { + *err_code = 400; + *reason = (const u08bits *)"Empty port number in channel bind request"; + } else { + addr_found = 1; + } + + break; + } + default: + if(attr_type>=0x0000 && attr_type<=0x7FFF) + unknown_attrs[(*ua_num)++] = nswap16(attr_type); + }; + sar = stun_attr_get_next_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + sar); + } + + if (*ua_num > 0) { + + *err_code = 420; + *reason = (const u08bits *)"Unknown attribute"; + + } else if (*err_code) { + + ; + + } else if (!chnum || addr_any(&peer_addr) || !addr_found) { + + *err_code = 400; + *reason = (const u08bits *)"Bad channel bind request"; + + } else if(!STUN_VALID_CHANNEL(chnum)) { + + *err_code = 400; + *reason = (const u08bits *)"Bad channel number"; + + } else { + + ch_info* chn = allocation_get_ch_info(a, chnum); + turn_permission_info* tinfo = NULL; + + if (chn) { + if (!addr_eq(&peer_addr, &(chn->peer_addr))) { + *err_code = 400; + *reason = (const u08bits *)"You cannot use the same channel number with different peer"; + } else { + tinfo = (turn_permission_info*) (chn->owner); + if (!tinfo) { + *err_code = 500; + *reason = (const u08bits *)"Wrong permission info"; + } else { + if (!addr_eq_no_port(&peer_addr, &(tinfo->addr))) { + *err_code = 500; + *reason = (const u08bits *)"Wrong permission info and peer addr conbination"; + } else if (chn->port != addr_get_port(&peer_addr)) { + *err_code = 500; + *reason = (const u08bits *)"Wrong port number"; + } + } + } + + } else { + + chn = allocation_get_ch_info_by_peer_addr(a, &peer_addr); + if(chn) { + *err_code = 400; + *reason = (const u08bits *)"You cannot use the same peer with different channel number"; + } else { + if(!good_peer_addr(server,&peer_addr)) { + *err_code = 403; + *reason = (const u08bits *) "Forbidden IP"; + } else { + chn = allocation_get_new_ch_info(a, chnum, &peer_addr); + if (!chn) { + *err_code = 500; + *reason = (const u08bits *) "Cannot find channel data"; + } else { + tinfo = (turn_permission_info*) (chn->owner); + if (!tinfo) { + *err_code = 500; + *reason + = (const u08bits *) "Wrong turn permission info"; + } + } + } + } + } + + if (!(*err_code) && chn && tinfo) { + + if (update_channel_lifetime(ss,chn) < 0) { + *err_code = 500; + *reason = (const u08bits *)"Cannot update channel lifetime (internal error)"; + } else { + size_t len = ioa_network_buffer_get_size(nbh); + stun_set_channel_bind_response_str(ioa_network_buffer_data(nbh), &len, tid, 0, NULL); + ioa_network_buffer_set_size(nbh,len); + *resp_constructed = 1; + } + } + } + } + + FUNCEND; + return 0; +} + +static int handle_turn_binding(turn_turnserver *server, + ts_ur_super_session *ss, stun_tid *tid, int *resp_constructed, + int *err_code, const u08bits **reason, u16bits *unknown_attrs, u16bits *ua_num, + ioa_net_data *in_buffer, ioa_network_buffer_handle nbh, + int *origin_changed, ioa_addr *response_origin, + int *dest_changed, ioa_addr *response_destination, + u32bits cookie, int old_stun) { + + FUNCSTART; + ts_ur_session* elem = &(ss->client_session); + int change_ip = 0; + int change_port = 0; + int padding = 0; + int response_port_present = 0; + u16bits response_port = 0; + SOCKET_TYPE st = get_ioa_socket_type(ss->client_session.s); + int use_reflected_from = 0; + + if(!(ss->client_session.s)) + return -1; + + *origin_changed = 0; + *dest_changed = 0; + + stun_attr_ref sar = stun_attr_get_first_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh)); + while (sar && (!(*err_code)) && (*ua_num < MAX_NUMBER_OF_UNKNOWN_ATTRS)) { + int attr_type = stun_attr_get_type(sar); + switch (attr_type) { + case OLD_STUN_ATTRIBUTE_PASSWORD: + SKIP_ATTRIBUTES; + case STUN_ATTRIBUTE_CHANGE_REQUEST: +/* + * This fix allows the client program from the Stuntman source to make STUN binding requests + * to this server. + * + * It was provided by John Selbie, from STUNTMAN project: + * + * "Here's the gist of the change. Stuntman comes with a STUN client library + * and client program. The client program displays the mapped IP address and + * port if it gets back a successful binding response. + * It also interops with JSTUN, a Java implementation of STUN. + * However, the JSTUN server refuses to respond to any binding request that + * doesn't have a CHANGE-REQUEST attribute in it. + * ... workaround is for the client to make a request with an empty CHANGE-REQUEST + * attribute (neither the ip or port bit are set)." + * + */ + stun_attr_get_change_request_str(sar, &change_ip, &change_port); + if( (!is_rfc5780(server)) && (change_ip || change_port)) { + *err_code = 420; + *reason = (const u08bits *)"Unknown attribute: TURN server was configured without RFC 5780 support"; + break; + } + if(change_ip || change_port) { + if(st != UDP_SOCKET) { + *err_code = 400; + *reason = (const u08bits *)"Wrong request: applicable only to UDP protocol"; + } + } + break; + case STUN_ATTRIBUTE_PADDING: + if(response_port_present) { + *err_code = 400; + *reason = (const u08bits *)"Wrong request format: you cannot use PADDING and RESPONSE_PORT together"; + } else if((st != UDP_SOCKET) && (st != DTLS_SOCKET)) { + *err_code = 400; + *reason = (const u08bits *)"Wrong request: padding applicable only to UDP and DTLS protocols"; + } else { + padding = 1; + } + break; + case STUN_ATTRIBUTE_RESPONSE_PORT: + if(padding) { + *err_code = 400; + *reason = (const u08bits *)"Wrong request format: you cannot use PADDING and RESPONSE_PORT together"; + } else if(st != UDP_SOCKET) { + *err_code = 400; + *reason = (const u08bits *)"Wrong request: applicable only to UDP protocol"; + } else { + int rp = stun_attr_get_response_port_str(sar); + if(rp>=0) { + response_port_present = 1; + response_port = (u16bits)rp; + } else { + *err_code = 400; + *reason = (const u08bits *)"Wrong response port format"; + } + } + break; + case OLD_STUN_ATTRIBUTE_RESPONSE_ADDRESS: + if(old_stun) { + use_reflected_from = 1; + stun_attr_get_addr_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + sar, response_destination, response_destination); + break; + } + default: + if(attr_type>=0x0000 && attr_type<=0x7FFF) + unknown_attrs[(*ua_num)++] = nswap16(attr_type); + }; + sar = stun_attr_get_next_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + sar); + } + + if (*ua_num > 0) { + + *err_code = 420; + *reason = (const u08bits *)"Unknown attribute"; + + } else if (*err_code) { + + ; + + } else if(ss->client_session.s) { + + size_t len = ioa_network_buffer_get_size(nbh); + if (stun_set_binding_response_str(ioa_network_buffer_data(nbh), &len, tid, + get_remote_addr_from_ioa_socket(elem->s), 0, NULL, cookie, old_stun) >= 0) { + + addr_cpy(response_origin, get_local_addr_from_ioa_socket(ss->client_session.s)); + + *resp_constructed = 1; + + if(old_stun && use_reflected_from) { + stun_attr_add_addr_str(ioa_network_buffer_data(nbh), &len, + OLD_STUN_ATTRIBUTE_REFLECTED_FROM, + get_remote_addr_from_ioa_socket(ss->client_session.s)); + } + + if(!is_rfc5780(server)) { + + if(old_stun) { + stun_attr_add_addr_str(ioa_network_buffer_data(nbh), &len, + OLD_STUN_ATTRIBUTE_SOURCE_ADDRESS, response_origin); + stun_attr_add_addr_str(ioa_network_buffer_data(nbh), &len, + OLD_STUN_ATTRIBUTE_CHANGED_ADDRESS, response_origin); + } else { + stun_attr_add_addr_str(ioa_network_buffer_data(nbh), &len, + STUN_ATTRIBUTE_RESPONSE_ORIGIN, response_origin); + } + + } else if(ss->client_session.s) { + + ioa_addr other_address; + + if(get_other_address(server,ss,&other_address) == 0) { + + addr_cpy(response_destination, get_remote_addr_from_ioa_socket(ss->client_session.s)); + + if(change_ip) { + *origin_changed = 1; + if(change_port) { + addr_cpy(response_origin,&other_address); + } else { + int old_port = addr_get_port(response_origin); + addr_cpy(response_origin,&other_address); + addr_set_port(response_origin,old_port); + } + } else if(change_port) { + *origin_changed = 1; + addr_set_port(response_origin,addr_get_port(&other_address)); + } + + if(old_stun) { + stun_attr_add_addr_str(ioa_network_buffer_data(nbh), &len, + OLD_STUN_ATTRIBUTE_SOURCE_ADDRESS, response_origin); + stun_attr_add_addr_str(ioa_network_buffer_data(nbh), &len, + OLD_STUN_ATTRIBUTE_CHANGED_ADDRESS, &other_address); + } else { + stun_attr_add_addr_str(ioa_network_buffer_data(nbh), &len, + STUN_ATTRIBUTE_RESPONSE_ORIGIN, response_origin); + stun_attr_add_addr_str(ioa_network_buffer_data(nbh), &len, + STUN_ATTRIBUTE_OTHER_ADDRESS, &other_address); + } + + if(response_port_present) { + *dest_changed = 1; + addr_set_port(response_destination, (int)response_port); + } + + if(padding) { + int mtu = get_local_mtu_ioa_socket(ss->client_session.s); + if(mtu<68) + mtu=1500; + + mtu = (mtu >> 2) << 2; + stun_attr_add_padding_str(ioa_network_buffer_data(nbh), &len, (u16bits)mtu); + } + } + } + } + ioa_network_buffer_set_size(nbh, len); + } + + FUNCEND; + return 0; +} + +static int handle_turn_send(turn_turnserver *server, ts_ur_super_session *ss, + int *err_code, const u08bits **reason, u16bits *unknown_attrs, u16bits *ua_num, + ioa_net_data *in_buffer) { + + FUNCSTART; + + ioa_addr peer_addr; + const u08bits* value = NULL; + int len = -1; + int addr_found = 0; + int set_df = 0; + + addr_set_any(&peer_addr); + allocation* a = get_allocation_ss(ss); + + if(ss->is_tcp_relay) { + *err_code = 403; + *reason = (const u08bits *)"Send cannot be used with TCP relay"; + } else if (is_allocation_valid(a) && (in_buffer->recv_ttl != 0)) { + + stun_attr_ref sar = stun_attr_get_first_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh)); + while (sar && (!(*err_code)) && (*ua_num < MAX_NUMBER_OF_UNKNOWN_ATTRS)) { + int attr_type = stun_attr_get_type(sar); + switch (attr_type) { + SKIP_ATTRIBUTES; + case STUN_ATTRIBUTE_DONT_FRAGMENT: + if(!(server->dont_fragment)) + unknown_attrs[(*ua_num)++] = nswap16(attr_type); + else + set_df = 1; + break; + case STUN_ATTRIBUTE_XOR_PEER_ADDRESS: { + if (addr_found) { + *err_code = 400; + *reason = (const u08bits *)"Address duplication"; + } else { + stun_attr_get_addr_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + sar, &peer_addr, + &(ss->default_peer_addr)); + } + } + break; + case STUN_ATTRIBUTE_DATA: { + if (len >= 0) { + *err_code = 400; + *reason = (const u08bits *)"Data duplication"; + } else { + len = stun_attr_get_len(sar); + value = stun_attr_get_value(sar); + } + } + break; + default: + if(attr_type>=0x0000 && attr_type<=0x7FFF) + unknown_attrs[(*ua_num)++] = nswap16(attr_type); + }; + sar = stun_attr_get_next_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + sar); + } + + if (*err_code) { + ; + } else if (*ua_num > 0) { + + *err_code = 420; + + } else if (!addr_any(&peer_addr) && len >= 0) { + + turn_permission_info* tinfo = NULL; + + if(!(server->server_relay)) + tinfo = allocation_get_permission(a, &peer_addr); + + if (tinfo || (server->server_relay)) { + + set_df_on_ioa_socket(get_relay_socket_ss(ss), set_df); + + ioa_network_buffer_handle nbh = in_buffer->nbh; + if(value && len>0) { + u16bits offset = (u16bits)(value - ioa_network_buffer_data(nbh)); + ioa_network_buffer_add_offset_size(nbh,offset,0,len); + } else { + len = 0; + ioa_network_buffer_set_size(nbh,len); + } + ioa_network_buffer_header_init(nbh); + send_data_from_ioa_socket_nbh(get_relay_socket_ss(ss), &peer_addr, nbh, in_buffer->recv_ttl-1, in_buffer->recv_tos); + in_buffer->nbh = NULL; + } + + } else { + *err_code = 400; + *reason = (const u08bits *)"No address found"; + } + } + + FUNCEND; + return 0; +} + +static int update_permission(ts_ur_super_session *ss, ioa_addr *peer_addr) { + + if (!ss || !peer_addr) + return -1; + + allocation* a = get_allocation_ss(ss); + + turn_permission_info* tinfo = allocation_get_permission(a, peer_addr); + + if (!tinfo) { + tinfo = allocation_add_permission(a, peer_addr); + } + + if (!tinfo) + return -1; + + if (update_turn_permission_lifetime(ss, tinfo, 0) < 0) + return -1; + + ch_info *chn = get_turn_channel(tinfo, peer_addr); + if(chn) { + if (update_channel_lifetime(ss, chn) < 0) + return -1; + } + + return 0; +} + +static int handle_turn_create_permission(turn_turnserver *server, + ts_ur_super_session *ss, stun_tid *tid, int *resp_constructed, + int *err_code, const u08bits **reason, u16bits *unknown_attrs, u16bits *ua_num, + ioa_net_data *in_buffer, ioa_network_buffer_handle nbh) { + + int ret = -1; + + int addr_found = 0; + + UNUSED_ARG(server); + + allocation* a = get_allocation_ss(ss); + + if (is_allocation_valid(a)) { + + { + stun_attr_ref sar = stun_attr_get_first_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh)); + + while (sar && (!(*err_code)) && (*ua_num < MAX_NUMBER_OF_UNKNOWN_ATTRS)) { + + int attr_type = stun_attr_get_type(sar); + + switch (attr_type) { + + SKIP_ATTRIBUTES; + + case STUN_ATTRIBUTE_XOR_PEER_ADDRESS: { + + ioa_addr peer_addr; + addr_set_any(&peer_addr); + + stun_attr_get_addr_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + sar, &peer_addr, + &(ss->default_peer_addr)); + + ioa_addr *relay_addr = get_local_addr_from_ioa_socket(a->relay_session.s); + + if(relay_addr && (relay_addr->ss.sa_family != peer_addr.ss.sa_family)) { + *err_code = 443; + *reason = (const u08bits *)"Peer Address Family Mismatch"; + } else if(!good_peer_addr(server, &peer_addr)) { + *err_code = 403; + *reason = (const u08bits *) "Forbidden IP"; + } else { + addr_found++; + } + } + break; + default: + if(attr_type>=0x0000 && attr_type<=0x7FFF) + unknown_attrs[(*ua_num)++] = nswap16(attr_type); + }; + sar = stun_attr_get_next_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + sar); + } + } + + if (*ua_num > 0) { + + *err_code = 420; + + } else if (*err_code) { + + ; + + } else if (!addr_found) { + + *err_code = 400; + *reason = (const u08bits *)"No address found"; + + } else { + + stun_attr_ref sar = stun_attr_get_first_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh)); + + while (sar) { + + int attr_type = stun_attr_get_type(sar); + + switch (attr_type) { + + SKIP_ATTRIBUTES; + + case STUN_ATTRIBUTE_XOR_PEER_ADDRESS: { + + ioa_addr peer_addr; + addr_set_any(&peer_addr); + + stun_attr_get_addr_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + sar, &peer_addr, + &(ss->default_peer_addr)); + + addr_set_port(&peer_addr, 0); + if (update_permission(ss, &peer_addr) < 0) { + *err_code = 500; + *reason = (const u08bits *)"Cannot update some permissions (critical server software error)"; + } + } + break; + default: + ; + } + + sar = stun_attr_get_next_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + sar); + } + + if(*err_code == 0) { + size_t len = ioa_network_buffer_get_size(nbh); + stun_init_success_response_str(STUN_METHOD_CREATE_PERMISSION, + ioa_network_buffer_data(nbh), &len, tid); + ioa_network_buffer_set_size(nbh,len); + + ret = 0; + *resp_constructed = 1; + } + } + } + + return ret; +} + +// AUTH ==>> + +static int need_stun_authentication(turn_turnserver *server, ts_ur_super_session *ss) +{ + UNUSED_ARG(ss); + + if(server) { + switch(server->ct) { + case TURN_CREDENTIALS_LONG_TERM: + return 1; + case TURN_CREDENTIALS_SHORT_TERM: + return 1; + default: + ; + }; + } + + return 0; +} + +static int create_challenge_response(ts_ur_super_session *ss, stun_tid *tid, int *resp_constructed, + int *err_code, const u08bits **reason, + ioa_network_buffer_handle nbh, + u16bits method) +{ + size_t len = ioa_network_buffer_get_size(nbh); + stun_init_error_response_str(method, ioa_network_buffer_data(nbh), &len, *err_code, *reason, tid); + *resp_constructed = 1; + stun_attr_add_str(ioa_network_buffer_data(nbh), &len, STUN_ATTRIBUTE_NONCE, + ss->nonce, (int)(NONCE_MAX_SIZE-1)); + char *realm = ss->realm_options.name; + stun_attr_add_str(ioa_network_buffer_data(nbh), &len, STUN_ATTRIBUTE_REALM, + (u08bits*)realm, (int)(strlen((s08bits*)(realm)))); + ioa_network_buffer_set_size(nbh,len); + return 0; +} + +#if !defined(min) +#define min(a,b) ((a)<=(b) ? (a) : (b)) +#endif + +static void resume_processing_after_username_check(int success, hmackey_t hmackey, st_password_t pwd, turn_turnserver *server, u64bits ctxkey, ioa_net_data *in_buffer) +{ + + if(server && in_buffer && in_buffer->nbh) { + + ts_ur_super_session *ss = get_session_from_map(server,(turnsession_id)ctxkey); + if(ss && ss->client_session.s) { + turn_turnserver *server = (turn_turnserver *)ss->server; + ts_ur_session *elem = &(ss->client_session); + + if(success) { + ns_bcopy(hmackey,ss->hmackey,sizeof(hmackey_t)); + ss->hmackey_set = 1; + ns_bcopy(pwd,ss->pwd,sizeof(st_password_t)); + } + + read_client_connection(server,elem,ss,in_buffer,0,0); + + close_ioa_socket_after_processing_if_necessary(ss->client_session.s); + + ioa_network_buffer_delete(server->e, in_buffer->nbh); + in_buffer->nbh=NULL; + } + } +} + +static int check_stun_auth(turn_turnserver *server, + ts_ur_super_session *ss, stun_tid *tid, int *resp_constructed, + int *err_code, const u08bits **reason, + ioa_net_data *in_buffer, ioa_network_buffer_handle nbh, + u16bits method, int *message_integrity, + int *postpone_reply, + int can_resume) +{ + u08bits usname[STUN_MAX_USERNAME_SIZE+1]; + u08bits nonce[STUN_MAX_NONCE_SIZE+1]; + u08bits realm[STUN_MAX_REALM_SIZE+1]; + size_t alen = 0; + + if(!need_stun_authentication(server, ss)) + return 0; + + int new_nonce = 0; + + { + int generate_new_nonce = 0; + if(ss->nonce[0]==0) { + generate_new_nonce = 1; + new_nonce = 1; + } + + if(*(server->stale_nonce)) { + if(turn_time_before(ss->nonce_expiration_time,server->ctime)) { + generate_new_nonce = 1; + } + } + + if(generate_new_nonce) { + + int i = 0; + + if(TURN_RANDOM_SIZE == 8) { + for(i=0;i<(NONCE_LENGTH_32BITS>>1);i++) { + u08bits *s = ss->nonce + 8*i; + u64bits rand=(u64bits)turn_random(); + snprintf((s08bits*)s, NONCE_MAX_SIZE-8*i, "%08lx",(unsigned long)rand); + } + } else { + for(i=0;inonce + 4*i; + u32bits rand=(u32bits)turn_random(); + snprintf((s08bits*)s, NONCE_MAX_SIZE-4*i, "%04x",(unsigned int)rand); + } + } + ss->nonce_expiration_time = server->ctime + STUN_NONCE_EXPIRATION_TIME; + } + } + + /* MESSAGE_INTEGRITY ATTR: */ + + stun_attr_ref sar = stun_attr_get_first_by_type_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + STUN_ATTRIBUTE_MESSAGE_INTEGRITY); + + if(!sar) { + *err_code = 401; + *reason = (const u08bits*)"Unauthorised"; + if(server->ct != TURN_CREDENTIALS_SHORT_TERM) { + return create_challenge_response(ss,tid,resp_constructed,err_code,reason,nbh,method); + } else { + return -1; + } + } + + { + int sarlen = stun_attr_get_len(sar); + switch(sarlen) { + case SHA1SIZEBYTES: + if(server->shatype != SHATYPE_SHA1) { + *err_code = SHA_TOO_WEAK; + return create_challenge_response(ss,tid,resp_constructed,err_code,reason,nbh,method); + return -1; + } + break; + case SHA256SIZEBYTES: + if(server->shatype != SHATYPE_SHA256) { + *err_code = 401; + return create_challenge_response(ss,tid,resp_constructed,err_code,reason,nbh,method); + return -1; + } + break; + default: + *err_code = 401; + return create_challenge_response(ss,tid,resp_constructed,err_code,reason,nbh,method); + return -1; + }; + } + + if(server->ct != TURN_CREDENTIALS_SHORT_TERM) { + + /* REALM ATTR: */ + + sar = stun_attr_get_first_by_type_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + STUN_ATTRIBUTE_REALM); + + if(!sar) { + *err_code = 400; + *reason = (const u08bits*)"Bad request"; + return -1; + } + + alen = min((size_t)stun_attr_get_len(sar),sizeof(realm)-1); + ns_bcopy(stun_attr_get_value(sar),realm,alen); + realm[alen]=0; + + if(method == STUN_METHOD_CONNECTION_BIND) { + + get_realm_options_by_name((char *)realm, &(ss->realm_options)); + + } else if(strcmp((char*)realm, (char*)(ss->realm_options.name))) { + *err_code = 400; + *reason = (const u08bits*)"Bad request: the realm value incorrect"; + return -1; + } + } + + /* USERNAME ATTR: */ + + sar = stun_attr_get_first_by_type_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + STUN_ATTRIBUTE_USERNAME); + + if(!sar) { + *err_code = 400; + *reason = (const u08bits*)"Bad request"; + return -1; + } + + alen = min((size_t)stun_attr_get_len(sar),sizeof(usname)-1); + ns_bcopy(stun_attr_get_value(sar),usname,alen); + usname[alen]=0; + + if(ss->username[0]) { + if(strcmp((char*)ss->username,(char*)usname)) { + *err_code = 401; + *reason = (const u08bits*)"Wrong username"; + return -1; + } + } else { + STRCPY(ss->username,usname); + set_username_hash(ss->client_session.s,ss->username,(u08bits*)ss->realm_options.name); + } + + if(server->ct != TURN_CREDENTIALS_SHORT_TERM) { + /* NONCE ATTR: */ + + sar = stun_attr_get_first_by_type_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + STUN_ATTRIBUTE_NONCE); + + if(!sar) { + *err_code = 400; + *reason = (const u08bits*)"Bad request"; + return -1; + } + + alen = min((size_t)stun_attr_get_len(sar),sizeof(nonce)-1); + ns_bcopy(stun_attr_get_value(sar),nonce,alen); + nonce[alen]=0; + + /* Stale Nonce check: */ + + if(new_nonce) { + *err_code = 438; + *reason = (const u08bits*)"Wrong nonce"; + return create_challenge_response(ss,tid,resp_constructed,err_code,reason,nbh,method); + } + + if(strcmp((s08bits*)ss->nonce,(s08bits*)nonce)) { + *err_code = 438; + *reason = (const u08bits*)"Stale nonce"; + return create_challenge_response(ss,tid,resp_constructed,err_code,reason,nbh,method); + } + } + + /* Password */ + if(!(ss->hmackey_set) && (ss->pwd[0] == 0)) { + ur_string_map_value_type ukey = NULL; + if(can_resume) { + ukey = (server->userkeycb)(server->id, server->ct, usname, realm, resume_processing_after_username_check, in_buffer, ss->id, postpone_reply); + if(*postpone_reply) { + return 0; + } + } + /* we always return NULL for short-term credentials here */ + if(!ukey) { + /* direct user pattern is supported only for long-term credentials */ + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: Cannot find credentials of user <%s>\n", + __FUNCTION__, (char*)usname); + *err_code = 401; + *reason = (const u08bits*)"Unauthorised"; + if(server->ct != TURN_CREDENTIALS_SHORT_TERM) { + return create_challenge_response(ss,tid,resp_constructed,err_code,reason,nbh,method); + } else { + return -1; + } + } + ns_bcopy(ukey,ss->hmackey,16); + ss->hmackey_set = 1; + } + + /* Check integrity */ + int too_weak = 0; + if(stun_check_message_integrity_by_key_str(server->ct,ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + ss->hmackey, + ss->pwd, + server->shatype, + &too_weak)<1) { + + if(too_weak) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: user %s credentials are incorrect: SHA function is too weak\n", + __FUNCTION__, (char*)usname); + *err_code = SHA_TOO_WEAK; + *reason = (const u08bits*)"Unauthorised: weak SHA function is used"; + if(server->ct != TURN_CREDENTIALS_SHORT_TERM) { + return create_challenge_response(ss,tid,resp_constructed,err_code,reason,nbh,method); + } else { + return -1; + } + } + + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: user %s credentials are incorrect\n", + __FUNCTION__, (char*)usname); + *err_code = 401; + *reason = (const u08bits*)"Unauthorised"; + if(server->ct != TURN_CREDENTIALS_SHORT_TERM) { + return create_challenge_response(ss,tid,resp_constructed,err_code,reason,nbh,method); + } else { + return -1; + } + } + + *message_integrity = 1; + + return 0; +} + +//<<== AUTH + +static void set_alternate_server(turn_server_addrs_list_t *asl, const ioa_addr *local_addr, size_t *counter, u16bits method, stun_tid *tid, int *resp_constructed, int *err_code, const u08bits **reason, ioa_network_buffer_handle nbh) +{ + if(asl && asl->size && local_addr) { + + size_t i; + + /* to prevent indefinite cycle: */ + + for(i=0;isize;++i) { + ioa_addr *addr = &(asl->addrs[i]); + if(addr_eq(addr,local_addr)) + return; + } + + for(i=0;isize;++i) { + if(*counter>=asl->size) + *counter = 0; + ioa_addr *addr = &(asl->addrs[*counter]); + *counter +=1; + if(addr->ss.sa_family == local_addr->ss.sa_family) { + + *err_code = 300; + *reason = (const u08bits *)"Redirect"; + + size_t len = ioa_network_buffer_get_size(nbh); + stun_init_error_response_str(method, ioa_network_buffer_data(nbh), &len, *err_code, *reason, tid); + *resp_constructed = 1; + stun_attr_add_addr_str(ioa_network_buffer_data(nbh), &len, STUN_ATTRIBUTE_ALTERNATE_SERVER, addr); + ioa_network_buffer_set_size(nbh,len); + + return; + } + } + } +} + +#define log_method(ss, method, err_code, reason) \ +{\ + if(!(err_code)) {\ + if(ss->origin[0]) {\ + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,\ + "session %018llu: origin <%s> realm <%s> user <%s>: incoming packet " method " processed, success\n",\ + (unsigned long long)(ss->id), (const char*)(ss->origin),(const char*)(ss->realm_options.name),(const char*)(ss->username));\ + } else {\ + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,\ + "session %018llu: realm <%s> user <%s>: incoming packet " method " processed, success\n",\ + (unsigned long long)(ss->id), (const char*)(ss->realm_options.name),(const char*)(ss->username));\ + }\ + } else {\ + if(ss->origin[0]) {\ + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,\ + "session %018llu: origin <%s> realm <%s> user <%s>: incoming packet " method " processed, error %d: %s\n",\ + (unsigned long long)(ss->id), (const char*)(ss->origin),(const char*)(ss->realm_options.name),(const char*)(ss->username), (err_code), (reason));\ + } else {\ + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,\ + "session %018llu: realm <%s> user <%s>: incoming packet " method " processed, error %d: %s\n",\ + (unsigned long long)(ss->id), (const char*)(ss->realm_options.name),(const char*)(ss->username), (err_code), (reason));\ + }\ + }\ +} + +static int handle_turn_command(turn_turnserver *server, ts_ur_super_session *ss, ioa_net_data *in_buffer, ioa_network_buffer_handle nbh, int *resp_constructed, int can_resume) +{ + + stun_tid tid; + int err_code = 0; + const u08bits *reason = NULL; + int no_response = 0; + int message_integrity = 0; + + if(!(ss->client_session.s)) + return -1; + + u16bits unknown_attrs[MAX_NUMBER_OF_UNKNOWN_ATTRS]; + u16bits ua_num = 0; + u16bits method = stun_get_method_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh)); + + *resp_constructed = 0; + + stun_tid_from_message_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + &tid); + + if (stun_is_request_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh))) { + + if((method == STUN_METHOD_BINDING) && (*(server->no_stun))) { + + no_response = 1; + if(server->verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "%s: STUN method 0x%x ignored\n", + __FUNCTION__, (unsigned int)method); + } + + } else if((method != STUN_METHOD_BINDING) && (*(server->stun_only))) { + + no_response = 1; + if(server->verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "%s: STUN method 0x%x ignored\n", + __FUNCTION__, (unsigned int)method); + } + + } else if((method != STUN_METHOD_BINDING) || (*(server->secure_stun))) { + + if(method == STUN_METHOD_ALLOCATE) { + + SOCKET_TYPE cst = get_ioa_socket_type(ss->client_session.s); + turn_server_addrs_list_t *asl = server->alternate_servers_list; + + if(((cst == UDP_SOCKET)||(cst == DTLS_SOCKET)) && server->self_udp_balance && + server->aux_servers_list && server->aux_servers_list->size) { + asl = server->aux_servers_list; + } else if(((cst == TLS_SOCKET) || (cst == DTLS_SOCKET)) && + server->tls_alternate_servers_list && server->tls_alternate_servers_list->size) { + asl = server->tls_alternate_servers_list; + } + + if(asl && asl->size) { + turn_mutex_lock(&(asl->m)); + set_alternate_server(asl,get_local_addr_from_ioa_socket(ss->client_session.s),&(server->as_counter),method,&tid,resp_constructed,&err_code,&reason,nbh); + turn_mutex_unlock(&(asl->m)); + } + } + + if(!(ss->realm_set) && (method == STUN_METHOD_ALLOCATE)) { + stun_attr_ref sar = stun_attr_get_first_by_type_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + STUN_ATTRIBUTE_ORIGIN); + + if(sar) { + int sarlen = stun_attr_get_len(sar); + if(sarlen>0) { + char *o = (char*)turn_malloc(sarlen+1); + ns_bcopy(stun_attr_get_value(sar),o,sarlen); + o[sarlen]=0; + char *corigin = (char*)turn_malloc(STUN_MAX_ORIGIN_SIZE+1); + corigin[0]=0; + if(get_canonic_origin(o,corigin,STUN_MAX_ORIGIN_SIZE)<0) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "%s: Wrong origin format: %s\n", + __FUNCTION__, o); + } + strncpy(ss->origin,corigin,STUN_MAX_ORIGIN_SIZE); + turn_free(corigin,sarlen+1); + turn_free(o,sarlen+1); + get_realm_options_by_origin(ss->origin,&(ss->realm_options)); + } + } + ss->realm_set = 1; + } + + if(!err_code && !(*resp_constructed) && !no_response) { + if(!(*(server->mobility)) || (method != STUN_METHOD_REFRESH) || is_allocation_valid(get_allocation_ss(ss))) { + int postpone_reply = 0; + check_stun_auth(server, ss, &tid, resp_constructed, &err_code, &reason, in_buffer, nbh, method, &message_integrity, &postpone_reply, can_resume); + if(postpone_reply) + no_response = 1; + } + } + } + + if (!err_code && !(*resp_constructed) && !no_response) { + + switch (method){ + + case STUN_METHOD_ALLOCATE: + + { + handle_turn_allocate(server, ss, &tid, resp_constructed, &err_code, &reason, + unknown_attrs, &ua_num, in_buffer, nbh); + + if(server->verbose) { + log_method(ss, "ALLOCATE", err_code, reason); + } + + break; + } + + case STUN_METHOD_CONNECT: + + handle_turn_connect(server, ss, &tid, &err_code, &reason, + unknown_attrs, &ua_num, in_buffer); + + if(server->verbose) { + log_method(ss, "CONNECT", err_code, reason); + } + + if(!err_code) + no_response = 1; + + break; + + case STUN_METHOD_CONNECTION_BIND: + + handle_turn_connection_bind(server, ss, &tid, resp_constructed, &err_code, &reason, + unknown_attrs, &ua_num, in_buffer, nbh, message_integrity); + + if(server->verbose) { + log_method(ss, "CONNECTION_BIND", err_code, reason); + } + + break; + + case STUN_METHOD_REFRESH: + + handle_turn_refresh(server, ss, &tid, resp_constructed, &err_code, &reason, + unknown_attrs, &ua_num, in_buffer, nbh, message_integrity, + &no_response, can_resume); + + if(server->verbose) { + log_method(ss, "REFRESH", err_code, reason); + } + break; + + case STUN_METHOD_CHANNEL_BIND: + + handle_turn_channel_bind(server, ss, &tid, resp_constructed, &err_code, &reason, + unknown_attrs, &ua_num, in_buffer, nbh); + + if(server->verbose) { + log_method(ss, "CHANNEL_BIND", err_code, reason); + } + break; + + case STUN_METHOD_CREATE_PERMISSION: + + handle_turn_create_permission(server, ss, &tid, resp_constructed, &err_code, &reason, + unknown_attrs, &ua_num, in_buffer, nbh); + + if(server->verbose) { + log_method(ss, "CREATE_PERMISSION", err_code, reason); + } + break; + + case STUN_METHOD_BINDING: + + { + int origin_changed=0; + ioa_addr response_origin; + int dest_changed=0; + ioa_addr response_destination; + + handle_turn_binding(server, ss, &tid, resp_constructed, &err_code, &reason, + unknown_attrs, &ua_num, in_buffer, nbh, + &origin_changed, &response_origin, + &dest_changed, &response_destination, + 0, 0); + + if(server->verbose) { + log_method(ss, "BINDING", err_code, reason); + } + + if(*resp_constructed && !err_code && (origin_changed || dest_changed)) { + + if (server->verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "RFC 5780 request successfully processed\n"); + } + + { + static const u08bits *field = (const u08bits *) TURN_SOFTWARE; + static const size_t fsz = sizeof(TURN_SOFTWARE)-1; + size_t len = ioa_network_buffer_get_size(nbh); + stun_attr_add_str(ioa_network_buffer_data(nbh), &len, STUN_ATTRIBUTE_SOFTWARE, field, fsz); + ioa_network_buffer_set_size(nbh, len); + } + + send_turn_message_to(server, nbh, &response_origin, &response_destination); + + no_response = 1; + } + + break; + } + default: + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, "Unsupported STUN request received, method 0x%x\n",(unsigned int)method); + }; + } + + } else if (stun_is_indication_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh))) { + + no_response = 1; + int postpone = 0; + + if(server->ct == TURN_CREDENTIALS_SHORT_TERM) { + check_stun_auth(server, ss, &tid, resp_constructed, &err_code, &reason, in_buffer, nbh, method, &message_integrity, &postpone, can_resume); + } + + if (!postpone && !err_code) { + + switch (method){ + + case STUN_METHOD_BINDING: + //ICE ? + break; + + case STUN_METHOD_SEND: + + handle_turn_send(server, ss, &err_code, &reason, unknown_attrs, &ua_num, in_buffer); + + if(eve(server->verbose)) { + log_method(ss, "SEND", err_code, reason); + } + + break; + + case STUN_METHOD_DATA: + + err_code = 403; + + if(eve(server->verbose)) { + log_method(ss, "DATA", err_code, reason); + } + + break; + + default: + if (server->verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Unsupported STUN indication received: method 0x%x\n",(unsigned int)method); + } + } + }; + + } else { + + no_response = 1; + + if (server->verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Wrong STUN message received\n"); + } + } + + if(ss->to_be_closed || !(ss->client_session.s) || ioa_socket_tobeclosed(ss->client_session.s)) + return 0; + + if (ua_num > 0) { + + err_code = 420; + + size_t len = ioa_network_buffer_get_size(nbh); + stun_init_error_response_str(method, ioa_network_buffer_data(nbh), &len, err_code, NULL, &tid); + + stun_attr_add_str(ioa_network_buffer_data(nbh), &len, STUN_ATTRIBUTE_UNKNOWN_ATTRIBUTES, (const u08bits*) unknown_attrs, (ua_num + * 2)); + + ioa_network_buffer_set_size(nbh,len); + + *resp_constructed = 1; + } + + if (!no_response) { + + if (!(*resp_constructed)) { + + if (!err_code) + err_code = 400; + + size_t len = ioa_network_buffer_get_size(nbh); + stun_init_error_response_str(method, ioa_network_buffer_data(nbh), &len, err_code, reason, &tid); + ioa_network_buffer_set_size(nbh,len); + *resp_constructed = 1; + } + + { + static const u08bits *field = (const u08bits *) TURN_SOFTWARE; + static const size_t fsz = sizeof(TURN_SOFTWARE)-1; + size_t len = ioa_network_buffer_get_size(nbh); + stun_attr_add_str(ioa_network_buffer_data(nbh), &len, STUN_ATTRIBUTE_SOFTWARE, field, fsz); + ioa_network_buffer_set_size(nbh, len); + } + + if(message_integrity) { + size_t len = ioa_network_buffer_get_size(nbh); + stun_attr_add_integrity_str(server->ct,ioa_network_buffer_data(nbh),&len,ss->hmackey,ss->pwd,server->shatype); + ioa_network_buffer_set_size(nbh,len); + } + + if(err_code) { + if(server->verbose) { + log_method(ss, "message", err_code, reason); + } + } + + } else { + *resp_constructed = 0; + } + + return 0; +} + +static int handle_old_stun_command(turn_turnserver *server, ts_ur_super_session *ss, ioa_net_data *in_buffer, ioa_network_buffer_handle nbh, int *resp_constructed, u32bits cookie) +{ + + stun_tid tid; + int err_code = 0; + const u08bits *reason = NULL; + int no_response = 0; + + u16bits unknown_attrs[MAX_NUMBER_OF_UNKNOWN_ATTRS]; + u16bits ua_num = 0; + u16bits method = stun_get_method_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh)); + + *resp_constructed = 0; + + stun_tid_from_message_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh), + &tid); + + if (stun_is_request_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh))) { + + if(method != STUN_METHOD_BINDING) { + no_response = 1; + if(server->verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "%s: OLD STUN method 0x%x ignored\n", + __FUNCTION__, (unsigned int)method); + } + } + + if (!err_code && !(*resp_constructed) && !no_response) { + + int origin_changed=0; + ioa_addr response_origin; + int dest_changed=0; + ioa_addr response_destination; + + handle_turn_binding(server, ss, &tid, resp_constructed, &err_code, &reason, + unknown_attrs, &ua_num, in_buffer, nbh, + &origin_changed, &response_origin, + &dest_changed, &response_destination, + cookie,1); + + if(server->verbose) { + log_method(ss, "OLD BINDING", err_code, reason); + } + + if(*resp_constructed && !err_code && (origin_changed || dest_changed)) { + + if (server->verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "RFC3489 CHANGE request successfully processed\n"); + } + + { + size_t newsz = (((sizeof(TURN_SOFTWARE))>>2) + 1)<<2; + u08bits software[120]; + if(newsz>sizeof(software)) + newsz = sizeof(software); + ns_bcopy(TURN_SOFTWARE,software,newsz); + size_t len = ioa_network_buffer_get_size(nbh); + stun_attr_add_str(ioa_network_buffer_data(nbh), &len, OLD_STUN_ATTRIBUTE_SERVER, software, newsz); + ioa_network_buffer_set_size(nbh, len); + } + + send_turn_message_to(server, nbh, &response_origin, &response_destination); + + no_response = 1; + } + } + } else { + + no_response = 1; + + if (server->verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Wrong OLD STUN message received\n"); + } + } + + if (ua_num > 0) { + + err_code = 420; + + size_t len = ioa_network_buffer_get_size(nbh); + old_stun_init_error_response_str(method, ioa_network_buffer_data(nbh), &len, err_code, NULL, &tid, cookie); + + stun_attr_add_str(ioa_network_buffer_data(nbh), &len, STUN_ATTRIBUTE_UNKNOWN_ATTRIBUTES, (const u08bits*) unknown_attrs, (ua_num * 2)); + + ioa_network_buffer_set_size(nbh,len); + + *resp_constructed = 1; + } + + if (!no_response) { + + if (!(*resp_constructed)) { + + if (!err_code) + err_code = 400; + + size_t len = ioa_network_buffer_get_size(nbh); + old_stun_init_error_response_str(method, ioa_network_buffer_data(nbh), &len, err_code, reason, &tid, cookie); + ioa_network_buffer_set_size(nbh,len); + *resp_constructed = 1; + } + + { + size_t newsz = (((sizeof(TURN_SOFTWARE))>>2) + 1)<<2; + u08bits software[120]; + if(newsz>sizeof(software)) + newsz = sizeof(software); + ns_bcopy(TURN_SOFTWARE,software,newsz); + size_t len = ioa_network_buffer_get_size(nbh); + stun_attr_add_str(ioa_network_buffer_data(nbh), &len, OLD_STUN_ATTRIBUTE_SERVER, software, newsz); + ioa_network_buffer_set_size(nbh, len); + } + + if(err_code) { + if(server->verbose) { + log_method(ss, "OLD STUN message", err_code, reason); + } + } + + } else { + *resp_constructed = 0; + } + + return 0; +} + +////////////////////////////////////////////////////////////////// + +static int write_to_peerchannel(ts_ur_super_session* ss, u16bits chnum, ioa_net_data *in_buffer) { + + int rc = 0; + + if (ss && get_relay_socket_ss(ss) && (in_buffer->recv_ttl!=0)) { + + allocation* a = get_allocation_ss(ss); + + if (is_allocation_valid(a)) { + + ch_info* chn = allocation_get_ch_info(a, chnum); + + if (!chn) + return -1; + + /* Channel packets are always sent with DF=0: */ + set_df_on_ioa_socket(get_relay_socket_ss(ss), 0); + + ioa_network_buffer_handle nbh = in_buffer->nbh; + + ioa_network_buffer_add_offset_size(in_buffer->nbh, STUN_CHANNEL_HEADER_LENGTH, 0, ioa_network_buffer_get_size(in_buffer->nbh)-STUN_CHANNEL_HEADER_LENGTH); + + ioa_network_buffer_header_init(nbh); + + rc = send_data_from_ioa_socket_nbh(get_relay_socket_ss(ss), &(chn->peer_addr), nbh, in_buffer->recv_ttl-1, in_buffer->recv_tos); + in_buffer->nbh = NULL; + } + } + + return rc; +} + +static void client_input_handler(ioa_socket_handle s, int event_type, + ioa_net_data *data, void *arg); +static void peer_input_handler(ioa_socket_handle s, int event_type, + ioa_net_data *data, void *arg); + +/////////////// Client actions ///////////////// + +int shutdown_client_connection(turn_turnserver *server, ts_ur_super_session *ss, int force, const char* reason) { + + FUNCSTART; + + if (!ss) + return -1; + + ts_ur_session* elem = &(ss->client_session); + + report_turn_session_info(server,ss,1); + dec_quota(ss); + + if(!force) { + + if (elem->s && server->verbose) { + + char sraddr[129]="\0"; + char sladdr[129]="\0"; + addr_to_string(get_remote_addr_from_ioa_socket(elem->s),(u08bits*)sraddr); + addr_to_string(get_local_addr_from_ioa_socket(elem->s),(u08bits*)sladdr); + + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "session %018llu: closed (1st stage), user <%s> realm <%s> origin <%s>, local %s, remote %s, reason: %s\n",(unsigned long long)(ss->id),(char*)ss->username,(char*)ss->realm_options.name,(char*)ss->origin, sladdr,sraddr,reason); + } + + IOA_CLOSE_SOCKET(elem->s); + + FUNCEND; + + return 0; + } + + if (eve(server->verbose)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "closing session 0x%lx, client socket 0x%lx in state %ld (socket session=0x%lx)\n", + (long) ss, + (long) elem->s, + (long) (elem->state), + (long)get_ioa_socket_session(elem->s)); + } + + if (elem->state == UR_STATE_DONE) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "!!! closing session 0x%lx, client session 0x%lx in DONE state\n", + (long)ss, (long) elem); + return -1; + } + + elem->state = UR_STATE_DONE; + + if (ss->alloc.relay_session.state == UR_STATE_DONE) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "!!! closing session 0x%lx, relay session 0x%lx in DONE state\n", + (long)ss, (long)&(ss->alloc.relay_session)); + return -1; + } + ss->alloc.relay_session.state = UR_STATE_DONE; + + if (server->disconnect) + server->disconnect(ss); + + IOA_CLOSE_SOCKET(elem->s); + IOA_CLOSE_SOCKET(ss->alloc.relay_session.s); + + if (server->verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "session %018llu: closed (2nd stage), user <%s> realm <%s> origin <%s>, reason: %s\n", + (unsigned long long)(ss->id), (char*)ss->username,(char*)ss->realm_options.name,(char*)ss->origin, reason); + } + + turn_server_remove_all_from_ur_map_ss(ss); + + FUNCEND; + + return 0; +} + +static void client_to_be_allocated_timeout_handler(ioa_engine_handle e, + void *arg) { + + if (!arg) + return; + + UNUSED_ARG(e); + + ts_ur_super_session* ss = (ts_ur_super_session*) arg; + + turn_turnserver* server = (turn_turnserver*) (ss->server); + + if (!server) + return; + + FUNCSTART; + + int to_close = 0; + + ioa_socket_handle s = ss->client_session.s; + if(!s || ioa_socket_tobeclosed(s)) { + to_close = 1; + } else { + ioa_socket_handle rs = ss->alloc.relay_session.s; + if(!rs || ioa_socket_tobeclosed(rs)) { + to_close = 1; + } else if(ss->alloc.relay_session.state == UR_STATE_DONE) { + to_close = 1; + } else if(ss->client_session.state == UR_STATE_DONE) { + to_close = 1; + } else if(!(ss->alloc.lifetime_ev)) { + to_close = 1; + } else if(!(ss->to_be_allocated_timeout_ev)) { + to_close = 1; + } + } + + if(to_close) { + IOA_EVENT_DEL(ss->to_be_allocated_timeout_ev); + shutdown_client_connection(server, ss, 1, "allocation watchdog determined stale session state"); + } + + FUNCEND; +} + +static int write_client_connection(turn_turnserver *server, ts_ur_super_session* ss, ioa_network_buffer_handle nbh, int ttl, int tos) { + + FUNCSTART; + + ts_ur_session* elem = &(ss->client_session); + + if (elem->state != UR_STATE_READY) { + ioa_network_buffer_delete(server->e, nbh); + FUNCEND; + return -1; + } else { + + ++(ss->sent_packets); + ss->sent_bytes += (u32bits)ioa_network_buffer_get_size(nbh); + turn_report_session_usage(ss); + + if (eve(server->verbose)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "%s: prepare to write to s 0x%lx\n", __FUNCTION__, + (long) (elem->s)); + } + + int ret = send_data_from_ioa_socket_nbh(elem->s, NULL, nbh, ttl, tos); + + FUNCEND; + return ret; + } +} + +static void client_ss_allocation_timeout_handler(ioa_engine_handle e, void *arg) { + + UNUSED_ARG(e); + + if (!arg) + return; + + ts_ur_super_session* ss = (ts_ur_super_session*)arg; + + if (!ss) + return; + + allocation* a = get_allocation_ss(ss); + + turn_turnserver* server = (turn_turnserver*) (ss->server); + + if (!server) { + clear_allocation(a); + return; + } + + FUNCSTART; + + shutdown_client_connection(server, ss, 0, "allocation timeout"); + + FUNCEND; +} + +static int create_relay_connection(turn_turnserver* server, + ts_ur_super_session *ss, u32bits lifetime, + int address_family, u08bits transport, + int even_port, u64bits in_reservation_token, u64bits *out_reservation_token, + int *err_code, const u08bits **reason, + accept_cb acb) { + + if (server && ss && ss->client_session.s) { + + allocation* a = get_allocation_ss(ss); + ts_ur_session* newelem = get_relay_session_ss(ss); + + IOA_CLOSE_SOCKET(newelem->s); + + ns_bzero(newelem, sizeof(ts_ur_session)); + newelem->s = NULL; + + ioa_socket_handle rtcp_s = NULL; + + if (in_reservation_token) { + + if (get_ioa_socket_from_reservation(server->e, in_reservation_token, + &newelem->s) < 0) { + IOA_CLOSE_SOCKET(newelem->s); + *err_code = 508; + *reason = (const u08bits *)"Cannot find reserved socket"; + return -1; + } + + if(!check_username_hash(newelem->s,ss->username,(u08bits*)ss->realm_options.name)) { + IOA_CLOSE_SOCKET(newelem->s); + *err_code = 508; + *reason = (const u08bits *)"Cannot find a valid reserved socket for this username"; + return -1; + } + + addr_debug_print(server->verbose, get_local_addr_from_ioa_socket(newelem->s), "Local relay addr (RTCP)"); + + } else { + + int res = create_relay_ioa_sockets(server->e, + ss->client_session.s, + address_family, transport, + even_port, &(newelem->s), &rtcp_s, out_reservation_token, + err_code, reason, acb, ss); + if (res < 0) { + if(!(*err_code)) + *err_code = 508; + if(!(*reason)) + *reason = (const u08bits *)"Cannot create socket"; + IOA_CLOSE_SOCKET(newelem->s); + IOA_CLOSE_SOCKET(rtcp_s); + return -1; + } + } + + if (newelem->s == NULL) { + IOA_CLOSE_SOCKET(rtcp_s); + *err_code = 508; + *reason = (const u08bits *)"Cannot create relay socket"; + return -1; + } + + set_username_hash(newelem->s,ss->username,(u08bits*)ss->realm_options.name); + + if (rtcp_s) { + if (out_reservation_token && *out_reservation_token) { + set_username_hash(rtcp_s,ss->username,(u08bits*)ss->realm_options.name); + /* OK */ + } else { + IOA_CLOSE_SOCKET(newelem->s); + IOA_CLOSE_SOCKET(rtcp_s); + *err_code = 508; + *reason = (const u08bits *)"Wrong reservation tokens (internal error)"; + return -1; + } + } + + newelem->state = UR_STATE_READY; + + /* RFC6156: do not use DF when IPv6 is involved: */ + if((get_local_addr_from_ioa_socket(newelem->s)->ss.sa_family == AF_INET6) || + (get_local_addr_from_ioa_socket(ss->client_session.s)->ss.sa_family == AF_INET6)) + set_do_not_use_df(newelem->s); + + if(get_ioa_socket_type(newelem->s) != TCP_SOCKET) { + register_callback_on_ioa_socket(server->e, newelem->s, IOA_EV_READ, + peer_input_handler, ss, 0); + } + + if (lifetime<1) + lifetime = STUN_DEFAULT_ALLOCATE_LIFETIME; + else if(lifetime>STUN_MAX_ALLOCATE_LIFETIME) + lifetime = STUN_MAX_ALLOCATE_LIFETIME; + + ioa_timer_handle ev = set_ioa_timer(server->e, lifetime, 0, + client_ss_allocation_timeout_handler, ss, 0, + "client_ss_allocation_timeout_handler"); + set_allocation_lifetime_ev(a, server->ctime + lifetime, ev); + + set_ioa_socket_session(newelem->s, ss); + } + + return 0; +} + +static int refresh_relay_connection(turn_turnserver* server, + ts_ur_super_session *ss, u32bits lifetime, int even_port, + u64bits in_reservation_token, u64bits *out_reservation_token, + int *err_code) { + + UNUSED_ARG(even_port); + UNUSED_ARG(in_reservation_token); + UNUSED_ARG(out_reservation_token); + UNUSED_ARG(err_code); + + allocation* a = get_allocation_ss(ss); + + if (server && ss && is_allocation_valid(a)) { + + if (lifetime < 1) { + set_allocation_valid(&(ss->alloc),0); + lifetime = 1; + } + + ioa_timer_handle ev = set_ioa_timer(server->e, lifetime, 0, + client_ss_allocation_timeout_handler, ss, 0, + "refresh_client_ss_allocation_timeout_handler"); + + set_allocation_lifetime_ev(a, server->ctime + lifetime, ev); + + return 0; + + } else { + return -1; + } +} + +static void write_http_echo(turn_turnserver *server, ts_ur_super_session *ss) +{ + if(server && ss && ss->client_session.s && !(ss->to_be_closed)) { + ioa_network_buffer_handle nbh_http = ioa_network_buffer_allocate(server->e); + size_t len_http = ioa_network_buffer_get_size(nbh_http); + u08bits *data = ioa_network_buffer_data(nbh_http); + char data_http[1025]; + char content_http[1025]; + const char* title = "TURN Server"; + snprintf(content_http,sizeof(content_http)-1,"\r\n\r\n \r\n %s\r\n \r\n \r\n %s\r\n \r\n\r\n",title,title); + snprintf(data_http,sizeof(data_http)-1,"HTTP/1.1 200 OK\r\nServer: %s\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: %d\r\n\r\n%s",TURN_SOFTWARE,(int)strlen(content_http),content_http); + len_http = strlen(data_http); + ns_bcopy(data_http,data,len_http); + ioa_network_buffer_set_size(nbh_http,len_http); + send_data_from_ioa_socket_nbh(ss->client_session.s, NULL, nbh_http, TTL_IGNORE, TOS_IGNORE); + } +} + +static int read_client_connection(turn_turnserver *server, ts_ur_session *elem, + ts_ur_super_session *ss, ioa_net_data *in_buffer, + int can_resume, int count_usage) { + + FUNCSTART; + + if (!server || !elem || !ss || !in_buffer) { + FUNCEND; + return -1; + } + + if (elem->state != UR_STATE_READY) { + FUNCEND; + return -1; + } + + int ret = (int)ioa_network_buffer_get_size(in_buffer->nbh); + if (ret < 0) { + FUNCEND; + return -1; + } + + if(count_usage) { + ++(ss->received_packets); + ss->received_bytes += (u32bits)ioa_network_buffer_get_size(in_buffer->nbh); + turn_report_session_usage(ss); + } + + if (eve(server->verbose)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "%s: data.buffer=0x%lx, data.len=%ld\n", __FUNCTION__, + (long)ioa_network_buffer_data(in_buffer->nbh), + (long)ioa_network_buffer_get_size(in_buffer->nbh)); + } + + u16bits chnum = 0; + u32bits old_stun_cookie = 0; + + size_t blen = ioa_network_buffer_get_size(in_buffer->nbh); + size_t orig_blen = blen; + SOCKET_TYPE st = get_ioa_socket_type(ss->client_session.s); + int is_padding_mandatory = ((st == TCP_SOCKET)||(st==TLS_SOCKET)||(st==TENTATIVE_TCP_SOCKET)); + + if (stun_is_channel_message_str(ioa_network_buffer_data(in_buffer->nbh), + &blen, + &chnum, + is_padding_mandatory)) { + + if(ss->is_tcp_relay) { + //Forbidden + FUNCEND; + return -1; + } + + int rc = 0; + + if(blen<=orig_blen) { + ioa_network_buffer_set_size(in_buffer->nbh,blen); + rc = write_to_peerchannel(ss, chnum, in_buffer); + } + + if (eve(server->verbose)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "%s: wrote to peer %d bytes\n", + __FUNCTION__, (int) rc); + } + + FUNCEND; + return 0; + + } else if (stun_is_command_message_full_check_str(ioa_network_buffer_data(in_buffer->nbh), ioa_network_buffer_get_size(in_buffer->nbh), 0, &(ss->enforce_fingerprints))) { + + ioa_network_buffer_handle nbh = ioa_network_buffer_allocate(server->e); + int resp_constructed = 0; + + u16bits method = stun_get_method_str(ioa_network_buffer_data(in_buffer->nbh), + ioa_network_buffer_get_size(in_buffer->nbh)); + + handle_turn_command(server, ss, in_buffer, nbh, &resp_constructed, can_resume); + + if((method != STUN_METHOD_BINDING) && (method != STUN_METHOD_SEND)) + report_turn_session_info(server,ss,0); + + if(ss->to_be_closed || ioa_socket_tobeclosed(ss->client_session.s)) { + FUNCEND; + ioa_network_buffer_delete(server->e, nbh); + return 0; + } + + if (resp_constructed) { + + if ((server->fingerprint) || ss->enforce_fingerprints) { + size_t len = ioa_network_buffer_get_size(nbh); + if (stun_attr_add_fingerprint_str(ioa_network_buffer_data(nbh), &len) < 0) { + FUNCEND ; + ioa_network_buffer_delete(server->e, nbh); + return -1; + } + ioa_network_buffer_set_size(nbh, len); + } + + int ret = write_client_connection(server, ss, nbh, TTL_IGNORE, TOS_IGNORE); + + FUNCEND ; + return ret; + } else { + ioa_network_buffer_delete(server->e, nbh); + return 0; + } + + } else if (old_stun_is_command_message_str(ioa_network_buffer_data(in_buffer->nbh), ioa_network_buffer_get_size(in_buffer->nbh), &old_stun_cookie) && !(*(server->no_stun))) { + + ioa_network_buffer_handle nbh = ioa_network_buffer_allocate(server->e); + int resp_constructed = 0; + + handle_old_stun_command(server, ss, in_buffer, nbh, &resp_constructed, old_stun_cookie); + + if (resp_constructed) { + + int ret = write_client_connection(server, ss, nbh, TTL_IGNORE, TOS_IGNORE); + + FUNCEND ; + return ret; + } else { + ioa_network_buffer_delete(server->e, nbh); + return 0; + } + + } else { + SOCKET_TYPE st = get_ioa_socket_type(ss->client_session.s); + if((st == TCP_SOCKET)||(st==TLS_SOCKET)||(st==TENTATIVE_TCP_SOCKET)) { + if(is_http_get((char*)ioa_network_buffer_data(in_buffer->nbh), ioa_network_buffer_get_size(in_buffer->nbh))) + write_http_echo(server,ss); + } + } + + //Unrecognized message received, ignore it + + FUNCEND; + return -1; +} + +static int attach_socket_to_session(turn_turnserver* server, ioa_socket_handle s, ts_ur_super_session* ss) { + + int ret = -1; + FUNCSTART; + + if(s && server && ss) { + + if(ss->client_session.s != s) { + + ts_ur_session *newelem = &(ss->client_session); + + if(newelem->s) { + IOA_CLOSE_SOCKET(newelem->s); + } + + newelem->s = s; + + register_callback_on_ioa_socket(server->e, newelem->s, IOA_EV_READ, + client_input_handler, ss, 0); + + set_ioa_socket_session(newelem->s, ss); + + newelem->state = UR_STATE_READY; + } + + ret = 0; + } + + FUNCEND; + return ret; +} + +int open_client_connection_session(turn_turnserver* server, + struct socket_message *sm) { + + FUNCSTART; + if (!server) + return -1; + + if (!(sm->s)) + return -1; + + ts_ur_super_session* ss = create_new_ss(server); + + ts_ur_session *newelem = &(ss->client_session); + + newelem->s = sm->s; + + register_callback_on_ioa_socket(server->e, newelem->s, IOA_EV_READ, + client_input_handler, ss, 0); + + set_ioa_socket_session(newelem->s, ss); + + newelem->state = UR_STATE_READY; + + int at = TURN_MAX_ALLOCATE_TIMEOUT; + if(*(server->stun_only)) + at = TURN_MAX_ALLOCATE_TIMEOUT_STUN_ONLY; + + IOA_EVENT_DEL(ss->to_be_allocated_timeout_ev); + ss->to_be_allocated_timeout_ev = set_ioa_timer(server->e, + at, 0, + client_to_be_allocated_timeout_handler, ss, 1, + "client_to_be_allocated_timeout_handler"); + + if(sm->nd.nbh) { + client_input_handler(newelem->s,IOA_EV_READ,&(sm->nd),ss); + ioa_network_buffer_delete(server->e, sm->nd.nbh); + sm->nd.nbh = NULL; + } + + FUNCEND; + + return 0; +} + +/////////////// io handlers /////////////////// + +static void peer_input_handler(ioa_socket_handle s, int event_type, + ioa_net_data *in_buffer, void *arg) { + + if (!(event_type & IOA_EV_READ) || !arg) + return; + + if(in_buffer->recv_ttl==0) + return; + + UNUSED_ARG(s); + + ts_ur_super_session* ss = (ts_ur_super_session*) arg; + + if(!ss) return; + + turn_turnserver *server = (turn_turnserver*) (ss->server); + + if (!server) { + return; + } + + ts_ur_session* elem = get_relay_session_ss(ss); + if (elem->s == NULL) { + return; + } + + int offset = STUN_CHANNEL_HEADER_LENGTH; + + int ilen = min((int)ioa_network_buffer_get_size(in_buffer->nbh), + (int)(ioa_network_buffer_get_capacity_udp() - offset)); + + if (ilen >= 0) { + + size_t len = (size_t)(ilen); + + allocation* a = get_allocation_ss(ss); + if (is_allocation_valid(a)) { + + u16bits chnum = 0; + + ioa_network_buffer_handle nbh = NULL; + + turn_permission_info* tinfo = allocation_get_permission(a, + &(in_buffer->src_addr)); + if (tinfo) { + chnum = get_turn_channel_number(tinfo, &(in_buffer->src_addr)); + } else if(!(server->server_relay)) { + return; + } + + if (chnum) { + nbh = in_buffer->nbh; + + ioa_network_buffer_add_offset_size(nbh, + 0, + STUN_CHANNEL_HEADER_LENGTH, + ioa_network_buffer_get_size(nbh)+STUN_CHANNEL_HEADER_LENGTH); + + ioa_network_buffer_header_init(nbh); + + SOCKET_TYPE st = get_ioa_socket_type(ss->client_session.s); + int do_padding = ((st == TCP_SOCKET)||(st==TLS_SOCKET)||(st==TENTATIVE_TCP_SOCKET)); + + stun_init_channel_message_str(chnum, ioa_network_buffer_data(nbh), &len, len, do_padding); + ioa_network_buffer_set_size(nbh,len); + in_buffer->nbh = NULL; + if (eve(server->verbose)) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "%s: send channel 0x%x\n", __FUNCTION__, + (int) (chnum)); + } + } else { + nbh = ioa_network_buffer_allocate(server->e); + stun_init_indication_str(STUN_METHOD_DATA, ioa_network_buffer_data(nbh), &len); + stun_attr_add_str(ioa_network_buffer_data(nbh), &len, STUN_ATTRIBUTE_DATA, + ioa_network_buffer_data(in_buffer->nbh), (size_t)ilen); + stun_attr_add_addr_str(ioa_network_buffer_data(nbh), &len, + STUN_ATTRIBUTE_XOR_PEER_ADDRESS, + &(in_buffer->src_addr)); + ioa_network_buffer_set_size(nbh,len); + + { + static const u08bits *field = (const u08bits *) TURN_SOFTWARE; + static const size_t fsz = sizeof(TURN_SOFTWARE)-1; + size_t len = ioa_network_buffer_get_size(nbh); + stun_attr_add_str(ioa_network_buffer_data(nbh), &len, STUN_ATTRIBUTE_SOFTWARE, field, fsz); + ioa_network_buffer_set_size(nbh, len); + } + + /* We add integrity for short-term indication messages, only */ + if(server->ct == TURN_CREDENTIALS_SHORT_TERM) + { + stun_attr_add_integrity_str(server->ct,ioa_network_buffer_data(nbh),&len,ss->hmackey,ss->pwd,server->shatype); + ioa_network_buffer_set_size(nbh,len); + } + + if ((server->fingerprint) || ss->enforce_fingerprints) { + size_t len = ioa_network_buffer_get_size(nbh); + stun_attr_add_fingerprint_str(ioa_network_buffer_data(nbh), &len); + ioa_network_buffer_set_size(nbh, len); + } + } + if (eve(server->verbose)) { + u16bits* t = (u16bits*) ioa_network_buffer_data(nbh); + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, "Send data: 0x%x\n", + (int) (nswap16(t[0]))); + } + + int ret = write_client_connection(server, ss, nbh, in_buffer->recv_ttl-1, in_buffer->recv_tos); + if (ret < 0) { + if(server->verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "session %018llu: client socket to be closed from peer handler: ss=0x%lx\n", (unsigned long long)(ss->id), (long)ss); + } + set_ioa_socket_tobeclosed(s); + } + } + } +} + +static void client_input_handler(ioa_socket_handle s, int event_type, + ioa_net_data *data, void *arg) { + + if (!arg) + return; + + UNUSED_ARG(s); + UNUSED_ARG(event_type); + + ts_ur_super_session* ss = (ts_ur_super_session*)arg; + + turn_turnserver *server = (turn_turnserver*)ss->server; + + if (!server) { + return; + } + + ts_ur_session* elem = &(ss->client_session); + + if (elem->s == NULL) { + return; + } + + int ret = 0; + + switch (elem->state) { + case UR_STATE_READY: + read_client_connection(server, elem, ss, data, 1, 1); + break; + case UR_STATE_DONE: + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "!!! %s: Trying to read from closed socket: s=0x%lx\n", + __FUNCTION__, (long) (elem->s)); + break; + default: + ret = -1; + } + + if (ret < 0) { + if(server->verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_ERROR, + "session %018llu: client socket error in client handler: s=0x%lx\n", (unsigned long long)(ss->id), (long) (elem->s)); + } + set_ioa_socket_tobeclosed(s); + } else if (ss->to_be_closed) { + if(server->verbose) { + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO, + "session %018llu: client socket to be closed in client handler: ss=0x%lx\n", (unsigned long long)(ss->id), (long)ss); + } + set_ioa_socket_tobeclosed(s); + } +} + +/////////////////////////////////////////////////////////// + +void init_turn_server(turn_turnserver* server, + turnserver_id id, int verbose, ioa_engine_handle e, turn_credential_type ct, + int stun_port, int fingerprint, dont_fragment_option_t dont_fragment, + get_user_key_cb userkeycb, + check_new_allocation_quota_cb chquotacb, + release_allocation_quota_cb raqcb, + ioa_addr *external_ip, + vintp no_tcp_relay, + vintp no_udp_relay, + vintp stale_nonce, + vintp stun_only, + vintp no_stun, + turn_server_addrs_list_t *alternate_servers_list, + turn_server_addrs_list_t *tls_alternate_servers_list, + turn_server_addrs_list_t *aux_servers_list, + int self_udp_balance, + vintp no_multicast_peers, vintp no_loopback_peers, + ip_range_list_t* ip_whitelist, ip_range_list_t* ip_blacklist, + send_socket_to_relay_cb send_socket_to_relay, + vintp secure_stun, SHATYPE shatype, vintp mobility, int server_relay, + send_turn_session_info_cb send_turn_session_info) { + + if (!server) + return; + + ns_bzero(server,sizeof(turn_turnserver)); + + server->e = e; + server->id = id; + server->ctime = turn_time(); + server->session_id_counter = 0; + server->sessions_map = ur_map_create(); + server->tcp_relay_connections = ur_map_create(); + server->ct = ct; + server->userkeycb = userkeycb; + server->chquotacb = chquotacb; + server->raqcb = raqcb; + server->no_multicast_peers = no_multicast_peers; + server->no_loopback_peers = no_loopback_peers; + server->secure_stun = secure_stun; + server->shatype = shatype; + server->mobility = mobility; + server->server_relay = server_relay; + server->send_turn_session_info = send_turn_session_info; + if(mobility) + server->mobile_connections_map = ur_map_create(); + + TURN_LOG_FUNC(TURN_LOG_LEVEL_INFO,"turn server id=%d created\n",(int)id); + + server->no_tcp_relay = no_tcp_relay; + server->no_udp_relay = no_udp_relay; + + server->alternate_servers_list = alternate_servers_list; + server->tls_alternate_servers_list = tls_alternate_servers_list; + server->aux_servers_list = aux_servers_list; + server->self_udp_balance = self_udp_balance; + + server->stale_nonce = stale_nonce; + server->stun_only = stun_only; + server->no_stun = no_stun; + + server->dont_fragment = dont_fragment; + server->fingerprint = fingerprint; + if(external_ip) { + addr_cpy(&(server->external_ip), external_ip); + server->external_ip_set = 1; + } + if (stun_port < 1) + stun_port = DEFAULT_STUN_PORT; + + server->verbose = verbose; + + server->ip_whitelist = ip_whitelist; + server->ip_blacklist = ip_blacklist; + + server->send_socket_to_relay = send_socket_to_relay; + + set_ioa_timer(server->e, 1, 0, timer_timeout_handler, server, 1, "timer_timeout_handler"); +} + +ioa_engine_handle turn_server_get_engine(turn_turnserver *s) { + if(s) + return s->e; + return NULL; +} + +void set_disconnect_cb(turn_turnserver* server, int(*disconnect)( + ts_ur_super_session*)) { + server->disconnect = disconnect; +} + +////////////////////////////////////////////////////////////////// diff --git a/src/server/ns_turn_server.h b/src/server/ns_turn_server.h new file mode 100644 index 0000000..ea740e6 --- /dev/null +++ b/src/server/ns_turn_server.h @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __TURN_SERVER__ +#define __TURN_SERVER__ + +#include "ns_turn_utils.h" +#include "ns_turn_session.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +//////////// ALTERNATE-SERVER ///////////// + +struct _turn_server_addrs_list { + ioa_addr *addrs; + volatile size_t size; + turn_mutex m; +}; + +typedef struct _turn_server_addrs_list turn_server_addrs_list_t; + +void init_turn_server_addrs_list(turn_server_addrs_list_t *l); + +////////// RFC 5780 /////////////////////// + +typedef int (*get_alt_addr_cb)(ioa_addr *addr, ioa_addr *alt_addr); +typedef int (*send_message_cb)(ioa_engine_handle e, ioa_network_buffer_handle nbh, ioa_addr *origin, ioa_addr *destination); + +////////////////////////////////////////// + +extern int TURN_MAX_ALLOCATE_TIMEOUT; +extern int TURN_MAX_ALLOCATE_TIMEOUT_STUN_ONLY; + +typedef u08bits turnserver_id; + +enum _MESSAGE_TO_RELAY_TYPE { + RMT_UNKNOWN = 0, + RMT_SOCKET, + RMT_CB_SOCKET, + RMT_MOBILE_SOCKET +}; +typedef enum _MESSAGE_TO_RELAY_TYPE MESSAGE_TO_RELAY_TYPE; + +struct socket_message { + ioa_socket_handle s; + ioa_net_data nd; +}; + +typedef enum { + DONT_FRAGMENT_UNSUPPORTED=0, + DONT_FRAGMENT_SUPPORTED, + DONT_FRAGMENT_SUPPORT_EMULATED +} dont_fragment_option_t; + +struct _turn_turnserver; +typedef struct _turn_turnserver turn_turnserver; + +typedef void (*get_username_resume_cb)(int success, hmackey_t hmackey, st_password_t pwd, turn_turnserver *server, u64bits ctxkey, ioa_net_data *in_buffer); +typedef u08bits *(*get_user_key_cb)(turnserver_id id, turn_credential_type ct, u08bits *uname, u08bits *realm, get_username_resume_cb resume, ioa_net_data *in_buffer, u64bits ctxkey, int *postpone_reply); +typedef int (*check_new_allocation_quota_cb)(u08bits *username, u08bits *realm); +typedef void (*release_allocation_quota_cb)(u08bits *username, u08bits *realm); +typedef int (*send_socket_to_relay_cb)(turnserver_id id, u64bits cid, stun_tid *tid, ioa_socket_handle s, int message_integrity, MESSAGE_TO_RELAY_TYPE rmt, ioa_net_data *nd); +typedef int (*send_turn_session_info_cb)(struct turn_session_info *tsi); + +struct _turn_turnserver { + + turnserver_id id; + + turnsession_id session_id_counter; + ur_map *sessions_map; + + turn_time_t ctime; + + ioa_engine_handle e; + int verbose; + int fingerprint; + int rfc5780; + vintp stale_nonce; + vintp stun_only; + vintp no_stun; + vintp secure_stun; + SHATYPE shatype; + turn_credential_type ct; + get_alt_addr_cb alt_addr_cb; + send_message_cb sm_cb; + dont_fragment_option_t dont_fragment; + int (*disconnect)(ts_ur_super_session*); + get_user_key_cb userkeycb; + check_new_allocation_quota_cb chquotacb; + release_allocation_quota_cb raqcb; + int external_ip_set; + ioa_addr external_ip; + vintp no_loopback_peers; + vintp no_multicast_peers; + send_turn_session_info_cb send_turn_session_info; + + /* RFC 6062 ==>> */ + vintp no_udp_relay; + vintp no_tcp_relay; + ur_map *tcp_relay_connections; + send_socket_to_relay_cb send_socket_to_relay; + /* <<== RFC 6062 */ + + /* Alternate servers ==>> */ + turn_server_addrs_list_t *alternate_servers_list; + size_t as_counter; + turn_server_addrs_list_t *tls_alternate_servers_list; + size_t tls_as_counter; + turn_server_addrs_list_t *aux_servers_list; + int self_udp_balance; + + /* White/black listing of address ranges */ + ip_range_list_t* ip_whitelist; + ip_range_list_t* ip_blacklist; + + /* Mobility */ + vintp mobility; + ur_map *mobile_connections_map; + + /* Server relay */ + int server_relay; +}; + +/////////////////////////////////////////// + +void init_turn_server(turn_turnserver* server, + turnserver_id id, int verbose, + ioa_engine_handle e, + turn_credential_type ct, + int stun_port, + int fingerprint, + dont_fragment_option_t dont_fragment, + get_user_key_cb userkeycb, + check_new_allocation_quota_cb chquotacb, + release_allocation_quota_cb raqcb, + ioa_addr *external_addr, + vintp no_tcp_relay, + vintp no_udp_relay, + vintp stale_nonce, + vintp stun_only, + vintp no_stun, + turn_server_addrs_list_t *alternate_servers_list, + turn_server_addrs_list_t *tls_alternate_servers_list, + turn_server_addrs_list_t *aux_servers_list, + int self_udp_balance, + vintp no_multicast_peers, + vintp no_loopback_peers, + ip_range_list_t* ip_whitelist, + ip_range_list_t* ip_blacklist, + send_socket_to_relay_cb send_socket_to_relay, + vintp secure_stun, + SHATYPE shatype, + vintp mobility, + int server_relay, + send_turn_session_info_cb send_turn_session_info); + +ioa_engine_handle turn_server_get_engine(turn_turnserver *s); + +////////// RFC 5780 /////////////////////// + +void set_rfc5780(turn_turnserver *server, get_alt_addr_cb cb, send_message_cb smcb); + +/////////////////////////////////////////// + +int open_client_connection_session(turn_turnserver* server, struct socket_message *sm); +int shutdown_client_connection(turn_turnserver *server, ts_ur_super_session *ss, int force, const char* reason); +void set_disconnect_cb(turn_turnserver* server, int (*disconnect)(ts_ur_super_session*)); + +int turnserver_accept_tcp_client_data_connection(turn_turnserver *server, tcp_connection_id tcid, stun_tid *tid, ioa_socket_handle s, int message_integrity); + +int report_turn_session_info(turn_turnserver *server, ts_ur_super_session *ss, int force_invalid); + +turn_time_t get_turn_server_time(turn_turnserver *server); + +/////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif //__TURN_SERVER__ diff --git a/src/server/ns_turn_session.h b/src/server/ns_turn_session.h new file mode 100644 index 0000000..86c6010 --- /dev/null +++ b/src/server/ns_turn_session.h @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2011, 2012, 2013 Citrix Systems + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __TURN_SESSION__ +#define __TURN_SESSION__ + +#include "ns_turn_utils.h" +#include "ns_turn_maps.h" +#include "ns_turn_ioalib.h" +#include "ns_turn_allocation.h" + +#ifdef __cplusplus +extern "C" { +#endif + +////////// REALM //////////// + +typedef struct _perf_options_t { + + vint max_bps; + vint total_quota; + vint user_quota; + +} perf_options_t; + +struct _realm_options_t { + + s08bits name[STUN_MAX_REALM_SIZE + 1]; + + perf_options_t perf_options; +}; + +//////////////// session info ////////////////////// + +typedef u64bits turnsession_id; + +#define NONCE_MAX_SIZE (NONCE_LENGTH_32BITS*4+1) + +typedef u64bits mobile_id_t; + +struct _ts_ur_super_session { + void* server; + turnsession_id id; + turn_time_t start_time; + ts_ur_session client_session; + ioa_addr default_peer_addr; + allocation alloc; + ioa_timer_handle to_be_allocated_timeout_ev; + u08bits nonce[NONCE_MAX_SIZE]; + turn_time_t nonce_expiration_time; + u08bits username[STUN_MAX_USERNAME_SIZE+1]; + hmackey_t hmackey; + int hmackey_set; + st_password_t pwd; + int enforce_fingerprints; + int is_tcp_relay; + int to_be_closed; + int quota_used; + /* Stats */ + u32bits received_packets; + u32bits sent_packets; + u32bits received_bytes; + u32bits sent_bytes; + u64bits t_received_packets; + u64bits t_sent_packets; + u64bits t_received_bytes; + u64bits t_sent_bytes; + u64bits received_rate; + size_t sent_rate; + size_t total_rate; + /* Mobile */ + int is_mobile; + mobile_id_t mobile_id; + char s_mobile_id[33]; + /* Realm */ + realm_options_t realm_options; + int realm_set; + s08bits origin[STUN_MAX_ORIGIN_SIZE + 1]; +}; + +////// Session info for statistics ////// + +#define TURN_ADDR_STR_SIZE (65) +#define TURN_MAIN_PEERS_ARRAY_SIZE (5) + +typedef struct _addr_data { + ioa_addr addr; + char saddr[TURN_ADDR_STR_SIZE]; +} addr_data; + +struct turn_session_info { + turnsession_id id; + int valid; + turn_time_t start_time; + turn_time_t expiration_time; + SOCKET_TYPE client_protocol; + SOCKET_TYPE peer_protocol; + char tls_method[17]; + char tls_cipher[65]; + addr_data local_addr_data; + addr_data remote_addr_data; + addr_data relay_addr_data; + u08bits username[STUN_MAX_USERNAME_SIZE+1]; + int enforce_fingerprints; +/* Stats */ + u64bits received_packets; + u64bits sent_packets; + u64bits received_bytes; + u64bits sent_bytes; + u32bits received_rate; + u32bits sent_rate; + u32bits total_rate; +/* Mobile */ + int is_mobile; +/* Peers */ + addr_data main_peers_data[TURN_MAIN_PEERS_ARRAY_SIZE]; + size_t main_peers_size; + addr_data *extra_peers_data; + size_t extra_peers_size; +/* Realm */ + char realm[STUN_MAX_REALM_SIZE+1]; + char origin[STUN_MAX_ORIGIN_SIZE + 1]; +}; + +void turn_session_info_init(struct turn_session_info* tsi); +void turn_session_info_clean(struct turn_session_info* tsi); +void turn_session_info_add_peer(struct turn_session_info* tsi, ioa_addr *peer); + +int turn_session_info_copy_from(struct turn_session_info* tsi, ts_ur_super_session *ss); + +////////////// ss ///////////////////// + +allocation* get_allocation_ss(ts_ur_super_session *ss); + +/////////////////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif //__TURN_SESSION__ diff --git a/turndb/schema.sql b/turndb/schema.sql new file mode 100644 index 0000000..e4d84ac --- /dev/null +++ b/turndb/schema.sql @@ -0,0 +1,41 @@ + +CREATE TABLE turnusers_lt ( + realm varchar(512), + name varchar(512), + hmackey char(128), + PRIMARY KEY (realm,name) +); + +CREATE TABLE turnusers_st ( + name varchar(512) PRIMARY KEY, + password varchar(512) +); + +CREATE TABLE turn_secret ( + realm varchar(512), + value varchar(512), + primary key (realm,value) +); + +CREATE TABLE allowed_peer_ip ( + ip_range varchar(256), + primary key (ip_range) +); + +CREATE TABLE denied_peer_ip ( + ip_range varchar(256), + primary key (ip_range) +); + +CREATE TABLE turn_origin_to_realm ( + origin varchar(512), + realm varchar(512), + primary key (origin,realm) +); + +CREATE TABLE turn_realm_option ( + realm varchar(512), + opt varchar(32), + value varchar(128), + primary key (realm,opt) +); diff --git a/turndb/schema.stats.redis b/turndb/schema.stats.redis new file mode 100644 index 0000000..c35fd0e --- /dev/null +++ b/turndb/schema.stats.redis @@ -0,0 +1,38 @@ +Redis database for allocation statuses and for event notifications +has the following schema: + +1) The allocation status is stored as keys "turn/user//allocation//status", +and the values may be "new lifetime=..." or "refreshed lifetime=...". There may be multiple +allocations under the same user name. + +2) Additionally, the same information is reported through the "publish" mechanism. +The same keys are used and the same status is reported, with the addition of status "deleted". +The user session that is interested in those statuses, must subscribe to the events as: + + psubscribe turn/realm/*/user/*/allocation/*/status + +3) Allocation traffic information is reported through the "publish" mechanism. +The keys are "turn/user//allocation//traffic". The application that is interested +in the traffic information must subscribe to the events as: + + psubscribe turn/realm/*/user/*/allocation/*/traffic + +Or, to receive all allocation events: + + psubscribe turn/realm/*/user/*/allocation/* + +4) Redis database configuration parameters + +TURN Server connects to the Redis and keeps the same connection during the +TURN server lifetime. That means that we have to take care about that +connection - it must NOT expire. + +You have to take care about Redis connection parameters, the timeout and the +keepalive. The following settings must be in your Redis config file +(/etc/redis.conf or /usr/local/etc/redis.conf): + +.......... +timeout 0 +.......... +tcp-keepalive 60 +.......... diff --git a/turndb/schema.userdb.redis b/turndb/schema.userdb.redis new file mode 100644 index 0000000..69b5f2e --- /dev/null +++ b/turndb/schema.userdb.redis @@ -0,0 +1,132 @@ + +I. The database + +Redis database for user authentication and peer permissions +has the following schema: + +1) For the long-term credentials there must be keys +"turn/realm//user//key" and the values must be +the the hmackeys. For example, for the user "gorst", realm "north.gov" +and password "hero", there must be key "turn/realm/north.gov/user/gorst/key" +with value "7da2270ccfa49786e0115366d3a3d14d". Alternatively, the password +may be stored in clear text format. Then the key will be +"turn/realm/north.gov/user/gorst/password" and the key will be simply "hero". + +2) For the short-term credentials, the passwords are stored always in +clear text format, with no realm name (because the short-term credentials +are not bound to a realm). So, there will be key "turn/user/gorst/password" +and the value will be "hero". + +3) For the shared secrets (REST API), several key/value pairs +may be used (same as in SQL schema). The key will be +"turn/realm//secret/" and the value will be +"". For example, if we have secrets "hero1", "hero2" and "hero3", +then we will have keys "turn/realm/north.gov/secret/123", +"turn/realm/north.gov/secret/234", "turn/realm/north.gov/secret/345" +and their values will be "hero1", "hero2", "hero3". The turnserver will +issue command "keys turn/realm/north.gov/secret/*" it it will try to use the +obtained keys in arbitrary order. + +4) The "white" and "black" peer IP ranges are stored as keys of the +following form: "turn/allowed-peer-ip/" or +"turn/denied-peer-ip/" + +The meaning of the keys is the same as the meaning of allowed-peer-ip and +denied-peer-ip turnserver command-line option. The only difference is that +the option values are "static" (they remain the same for the lifetime of +the turnserver process) but the database records can be dynamically changed +and they will be almost immediately "seen" by the turnserver process. + +II. Extra realms data in the database + +We can use more than one realm with the same instance of the TURN server. +This is done through the ORIGIN mechanism - users with different ORIGINS +are placed into different realms. The database includes information about the +relationships between the ORIGIN and realms, and about the extra realms +database numbers. + The relationship between ORIGIN and realm is set as keys of form: +"turn/origin/" with the realm-names as the value. Many different +ORIGIN keys may have the same realm. If the ORIGIN value is not found in +the database or the ORIGIN field is missed in the initial allocate +request, then the default realm is assumed. + +III) Example of a Redis default user database setup. + +This example sets user database for: + + * long-term credentials with hashed passwords and + with default realm "north.gov"; + * long-term credentials with open passwords and + with default realm "north.gov"; + * TURN REST API with shared secret "logen"; + * short-term credentials mechanism, with open passwords; + * Black and white IP peer lists used. + * Information how to match ORIGIN field with extra + realms (if used). If no origin match found + or the ORIGIN field is absent in the ALLOCATE request then the default + realm is used. + * The realm performance parameters: "max_bps", + "total_quota" and "user_quota" (same names as the turnserver + configuration options, with the same meanings). + +The shell command would be: + +$ redis-cli <