summaryrefslogtreecommitdiff
path: root/pjnath
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2007-03-14 11:52:13 +0000
committerBenny Prijono <bennylp@teluu.com>2007-03-14 11:52:13 +0000
commitf20afe7a03041e4191d55a4f1901866103b1e57b (patch)
tree76845840c432de5f5f2cd421fe88c6773cf66c24 /pjnath
parent6c6138a3c3b17327edd596ac5e3e99b12bd09f52 (diff)
(big patch) Added top-level pjnath project and moved STUN related files to this new project
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1062 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjnath')
-rw-r--r--pjnath/build/pjnath.dsw89
-rw-r--r--pjnath/build/pjstun_client.dsp102
-rw-r--r--pjnath/build/pjstun_srv_test.dsp122
-rw-r--r--pjnath/include/pjnath/stun_auth.h275
-rw-r--r--pjnath/include/pjnath/stun_doc.h100
-rw-r--r--pjnath/include/pjnath/stun_endpoint.h117
-rw-r--r--pjnath/include/pjnath/stun_msg.h1539
-rw-r--r--pjnath/include/pjnath/stun_session.h372
-rw-r--r--pjnath/include/pjnath/stun_transaction.h217
-rw-r--r--pjnath/include/pjnath/turn_client.h132
-rw-r--r--pjnath/src/pjnath/stun_auth.c341
-rw-r--r--pjnath/src/pjnath/stun_endpoint.c69
-rw-r--r--pjnath/src/pjnath/stun_msg.c2162
-rw-r--r--pjnath/src/pjnath/stun_msg_dump.c250
-rw-r--r--pjnath/src/pjnath/stun_session.c903
-rw-r--r--pjnath/src/pjnath/stun_transaction.c315
-rw-r--r--pjnath/src/pjstun-client/client_main.c680
-rw-r--r--pjnath/src/pjstun-srv-test/bind_usage.c206
-rw-r--r--pjnath/src/pjstun-srv-test/main.c146
-rw-r--r--pjnath/src/pjstun-srv-test/server.c185
-rw-r--r--pjnath/src/pjstun-srv-test/server.h135
-rw-r--r--pjnath/src/pjstun-srv-test/turn_usage.c1408
-rw-r--r--pjnath/src/pjstun-srv-test/usage.c271
23 files changed, 10136 insertions, 0 deletions
diff --git a/pjnath/build/pjnath.dsw b/pjnath/build/pjnath.dsw
new file mode 100644
index 00000000..2105962e
--- /dev/null
+++ b/pjnath/build/pjnath.dsw
@@ -0,0 +1,89 @@
+Microsoft Developer Studio Workspace File, Format Version 6.00
+# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
+
+###############################################################################
+
+Project: "pjlib"=..\..\pjlib\build\pjlib.dsp - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "pjlib_test"=..\..\pjlib\build\pjlib_test.dsp - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "pjlib_util"="..\..\pjlib-util\build\pjlib_util.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Project: "pjstun_client"=.\pjstun_client.dsp - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+ Begin Project Dependency
+ Project_Dep_Name pjlib
+ End Project Dependency
+ Begin Project Dependency
+ Project_Dep_Name pjlib_util
+ End Project Dependency
+}}}
+
+###############################################################################
+
+Project: "pjstun_srv_test"=.\pjstun_srv_test.dsp - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+ Begin Project Dependency
+ Project_Dep_Name pjlib
+ End Project Dependency
+ Begin Project Dependency
+ Project_Dep_Name pjlib_util
+ End Project Dependency
+}}}
+
+###############################################################################
+
+Global:
+
+Package=<5>
+{{{
+}}}
+
+Package=<3>
+{{{
+}}}
+
+###############################################################################
+
diff --git a/pjnath/build/pjstun_client.dsp b/pjnath/build/pjstun_client.dsp
new file mode 100644
index 00000000..2d5705b7
--- /dev/null
+++ b/pjnath/build/pjstun_client.dsp
@@ -0,0 +1,102 @@
+# Microsoft Developer Studio Project File - Name="pjstun_client" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=pjstun_client - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "pjstun_client.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "pjstun_client.mak" CFG="pjstun_client - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "pjstun_client - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "pjstun_client - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "pjstun_client - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "./output/pjstun-client-i386-win32-vc6-release"
+# PROP BASE Intermediate_Dir "./output/pjstun-client-i386-win32-vc6-release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "./output/pjstun-client-i386-win32-vc6-release"
+# PROP Intermediate_Dir "./output/pjstun-client-i386-win32-vc6-release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GX /O2 /I "../include" /I "../../pjlib/include" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /YX /FD /c
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+# ADD LINK32 netapi32.lib mswsock.lib ws2_32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"../bin/pjstun-client-i386-win32-vc6-release.exe"
+
+!ELSEIF "$(CFG)" == "pjstun_client - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "./output/pjstun-client-i386-win32-vc6-debug"
+# PROP BASE Intermediate_Dir "./output/pjstun-client-i386-win32-vc6-debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "./output/pjstun-client-i386-win32-vc6-debug"
+# PROP Intermediate_Dir "./output/pjstun-client-i386-win32-vc6-debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /YX /FD /GZ /c
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 netapi32.lib ws2_32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"../bin/pjstun-client-i386-win32-vc6-debug.exe" /pdbtype:sept
+
+!ENDIF
+
+# Begin Target
+
+# Name "pjstun_client - Win32 Release"
+# Name "pjstun_client - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE="..\src\pjstun-client\client_main.c"
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/pjnath/build/pjstun_srv_test.dsp b/pjnath/build/pjstun_srv_test.dsp
new file mode 100644
index 00000000..fa1a5656
--- /dev/null
+++ b/pjnath/build/pjstun_srv_test.dsp
@@ -0,0 +1,122 @@
+# Microsoft Developer Studio Project File - Name="pjstun_srv_test" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=pjstun_srv_test - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "pjstun_srv_test.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "pjstun_srv_test.mak" CFG="pjstun_srv_test - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "pjstun_srv_test - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "pjstun_srv_test - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "pjstun_srv_test - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "./output/pjstun-srv-test-i386-win32-vc6-release"
+# PROP BASE Intermediate_Dir "./output/pjstun-srv-test-i386-win32-vc6-release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "./output/pjstun-srv-test-i386-win32-vc6-release"
+# PROP Intermediate_Dir "./output/pjstun-srv-test-i386-win32-vc6-release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD CPP /nologo /MD /W4 /GX /O2 /I "../include" /I "../../pjlib/include" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /YX /FD /c
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+# ADD LINK32 netapi32.lib mswsock.lib ws2_32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"../bin/pjstun-srv-test-i386-win32-vc6-release.exe"
+
+!ELSEIF "$(CFG)" == "pjstun_srv_test - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "./output/pjstun-srv-test-i386-win32-vc6-debug"
+# PROP BASE Intermediate_Dir "./output/pjstun-srv-test-i386-win32-vc6-debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "./output/pjstun-srv-test-i386-win32-vc6-debug"
+# PROP Intermediate_Dir "./output/pjstun-srv-test-i386-win32-vc6-debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /YX /FD /GZ /c
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 netapi32.lib ws2_32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"../bin/pjstun-srv-test-i386-win32-vc6-debug.exe" /pdbtype:sept
+
+!ENDIF
+
+# Begin Target
+
+# Name "pjstun_srv_test - Win32 Release"
+# Name "pjstun_srv_test - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE="..\src\pjstun-srv-test\bind_usage.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjstun-srv-test\main.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjstun-srv-test\server.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjstun-srv-test\turn_usage.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjstun-srv-test\usage.c"
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE="..\src\pjstun-srv-test\server.h"
+# End Source File
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/pjnath/include/pjnath/stun_auth.h b/pjnath/include/pjnath/stun_auth.h
new file mode 100644
index 00000000..49b2acbb
--- /dev/null
+++ b/pjnath/include/pjnath/stun_auth.h
@@ -0,0 +1,275 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __PJLIB_UTIL_STUN_AUTH_H__
+#define __PJLIB_UTIL_STUN_AUTH_H__
+
+/**
+ * @file stun_auth.h
+ * @brief STUN authentication.
+ */
+
+#include <pjlib-util/stun_msg.h>
+
+
+PJ_BEGIN_DECL
+
+
+/* **************************************************************************/
+/**
+ * @defgroup PJLIB_UTIL_STUN_AUTH STUN Authentication
+ * @ingroup PJLIB_UTIL_STUN
+ * @{
+ */
+
+/**
+ * Type of authentication data in the credential.
+ */
+typedef enum pj_stun_auth_cred_type
+{
+ /**
+ * The credential data contains a static credential to be matched
+ * against the credential in the message. A static credential can be
+ * used as both client side or server side authentication.
+ */
+ PJ_STUN_AUTH_CRED_STATIC,
+
+ /**
+ * The credential data contains callbacks to be called to verify the
+ * credential in the message. A dynamic credential is suitable when
+ * performing server side authentication where server does not know
+ * in advance the identity of the user requesting authentication.
+ */
+ PJ_STUN_AUTH_CRED_DYNAMIC
+
+} pj_stun_auth_cred_type;
+
+
+/**
+ * This structure contains the descriptions needed to perform server side
+ * authentication. Depending on the \a type set in the structure, application
+ * may specify a static username/password combination, or to have callbacks
+ * called by the function to authenticate the credential dynamically.
+ */
+typedef struct pj_stun_auth_cred
+{
+ /**
+ * The type of authentication information in this structure.
+ */
+ pj_stun_auth_cred_type type;
+
+ /**
+ * This union contains the authentication data.
+ */
+ union
+ {
+ /**
+ * This structure contains static data for performing authentication.
+ * A non-empty realm indicates whether short term or long term
+ * credential is used.
+ */
+ struct
+ {
+ /**
+ * If not-empty, it indicates that this is a long term credential.
+ */
+ pj_str_t realm;
+
+ /**
+ * The username of the credential.
+ */
+ pj_str_t username;
+
+ /**
+ * Data type to indicate the type of password in the \a data field.
+ * Value zero indicates that the data contains a plaintext
+ * password.
+ */
+ int data_type;
+
+ /**
+ * The data, which depends depends on the value of \a data_type
+ * field. When \a data_type is zero, this field will contain the
+ * plaintext password.
+ */
+ pj_str_t data;
+
+ /**
+ * Optional NONCE.
+ */
+ pj_str_t nonce;
+
+ } static_cred;
+
+ /**
+ * This structure contains callback to be called by the framework
+ * to authenticate the incoming message.
+ */
+ struct
+ {
+ /**
+ * User data which will be passed back to callback functions.
+ */
+ void *user_data;
+
+ /**
+ * This callback is called by pj_stun_verify_credential() when
+ * server needs to challenge the request with 401 response.
+ *
+ * @param user_data The user data as specified in the credential.
+ * @param pool Pool to allocate memory.
+ * @param realm On return, the function should fill in with
+ * realm if application wants to use long term
+ * credential. Otherwise application should set
+ * empty string for the realm.
+ * @param nonce On return, if application wants to use long
+ * term credential, it MUST fill in the nonce
+ * with some value. Otherwise if short term
+ * credential is wanted, it MAY set this value.
+ * If short term credential is wanted and the
+ * application doesn't want to include NONCE,
+ * then it must set this to empty string.
+ *
+ * @return The callback should return PJ_SUCCESS, or
+ * otherwise response message will not be
+ * created.
+ */
+ pj_status_t (*get_auth)(void *user_data,
+ pj_pool_t *pool,
+ pj_str_t *realm,
+ pj_str_t *nonce);
+
+ /**
+ * Get the password for the specified username. This function
+ * is also used to check whether the username is valid.
+ *
+ * @param user_data The user data as specified in the credential.
+ * @param realm The realm as specified in the message.
+ * @param username The username as specified in the message.
+ * @param pool Pool to allocate memory when necessary.
+ * @param data_type On return, application should fill up this
+ * argument with the type of data (which should
+ * be zero if data is a plaintext password).
+ * @param data On return, application should fill up this
+ * argument with the password according to
+ * data_type.
+ *
+ * @return The callback should return PJ_SUCCESS if
+ * username has been successfully verified
+ * and password was obtained. If non-PJ_SUCCESS
+ * is returned, it is assumed that the
+ * username is not valid.
+ */
+ pj_status_t (*get_password)(void *user_data,
+ const pj_str_t *realm,
+ const pj_str_t *username,
+ pj_pool_t *pool,
+ int *data_type,
+ pj_str_t *data);
+
+ /**
+ * This callback will be called to verify that the NONCE given
+ * in the message can be accepted. If this callback returns
+ * PJ_FALSE, 438 (Stale Nonce) response will be created.
+ *
+ * @param user_data The user data as specified in the credential.
+ * @param realm The realm as specified in the message.
+ * @param username The username as specified in the message.
+ * @param nonce The nonce to be verified.
+ *
+ * @return The callback MUST return non-zero if the
+ * NONCE can be accepted.
+ */
+ pj_bool_t (*verify_nonce)(void *user_data,
+ const pj_str_t *realm,
+ const pj_str_t *username,
+ const pj_str_t *nonce);
+
+ } dyn_cred;
+
+ } data;
+
+} pj_stun_auth_cred;
+
+
+/**
+ * Duplicate authentication credential.
+ *
+ * @param pool Pool to be used to allocate memory.
+ * @param dst Destination credential.
+ * @param src Source credential.
+ */
+PJ_DECL(void) pj_stun_auth_cred_dup(pj_pool_t *pool,
+ pj_stun_auth_cred *dst,
+ const pj_stun_auth_cred *src);
+
+
+/**
+ * Verify credential in the STUN message. Note that before calling this
+ * function, application must have checked that the message contains
+ * PJ_STUN_ATTR_MESSAGE_INTEGRITY attribute by calling pj_stun_msg_find_attr()
+ * function, because this function will reject the message with 401 error
+ * if it doesn't contain PJ_STUN_ATTR_MESSAGE_INTEGRITY attribute.
+ *
+ * @param pkt The original packet which has been parsed into
+ * the message. This packet MUST NOT have been modified
+ * after the parsing.
+ * @param pkt_len The length of the packet.
+ * @param msg The parsed message to be verified.
+ * @param cred Pointer to credential to be used to authenticate
+ * the message.
+ * @param pool If response is to be created, then memory will
+ * be allocated from this pool.
+ * @param p_response Optional pointer to receive the response message
+ * then the credential in the request fails to
+ * authenticate.
+ *
+ * @return PJ_SUCCESS if credential is verified successfully.
+ * If the verification fails and \a p_response is not
+ * NULL, an appropriate response will be returned in
+ * \a p_response.
+ */
+PJ_DECL(pj_status_t) pj_stun_verify_credential(const pj_uint8_t *pkt,
+ unsigned pkt_len,
+ const pj_stun_msg *msg,
+ pj_stun_auth_cred *cred,
+ pj_pool_t *pool,
+ pj_stun_msg **p_response);
+
+
+
+
+/**
+ * @}
+ */
+
+
+/* Calculate HMAC-SHA1 key for long term credential, by getting
+ * MD5 digest of username, realm, and password.
+ */
+void pj_stun_calc_md5_key(pj_uint8_t digest[16],
+ const pj_str_t *realm,
+ const pj_str_t *username,
+ const pj_str_t *passwd);
+
+
+PJ_END_DECL
+
+
+#endif /* __PJLIB_UTIL_STUN_AUTH_H__ */
+
diff --git a/pjnath/include/pjnath/stun_doc.h b/pjnath/include/pjnath/stun_doc.h
new file mode 100644
index 00000000..612bf1a7
--- /dev/null
+++ b/pjnath/include/pjnath/stun_doc.h
@@ -0,0 +1,100 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __PJ_STUN_SERVER_H__
+#define __PJ_STUN_SERVER_H__
+
+/*
+ * STUN documentation. There is no code here.
+ */
+
+/**
+ * @defgroup PJLIB_UTIL_STUN STUN and TURN
+ * @ingroup PJLIB_UTIL
+
+ This is the implementation of STUN/TURN in PJLIB-UTIL library.
+
+ The STUN/TURN implementation in PJLIB-UTIL has the following objectives:
+ - standard based (of course)
+ - supports both client and server side STUN/TURN services
+ - independent (that is, only dependent to pjlib), and general purpose
+ enough to be used not only by pjsip applications but also by other
+ types of applications
+ - must be able to support ICE.
+
+ The STUN/TURN implementation is based on the following standards:
+ - <A HREF="http://www.ietf.org/internet-drafts/draft-ietf-behave-rfc3489bis-05.txt">
+ draft-ietf-behave-rfc3489bis-05.txt</A>
+ - <A HREF="http://www.ietf.org/internet-drafts/draft-ietf-behave-turn-02.txt">
+ draft-ietf-behave-turn-02.txt</A>
+
+ But as STUN standards are currently defined as work in progress at IETF,
+ the implementation will be updated as these standards are updated.
+
+
+
+ @section stun_org_sec Organization
+
+ The implementation consists of the following components.
+
+ @subsection stun_msg_sec Messaging and Parsing
+
+ The lowest layer of the STUN implementation is the @ref PJLIB_UTIL_STUN_MSG
+ component. This part is responsible for encoding and decoding STUN messages.
+
+ This layer only implements message representation and parsing. In particular,
+ it does not provide any transport functionalities, therefore it can be used
+ by different types of applications.
+
+
+
+ @subsection stun_endpt_sec Endpoint
+
+ The @ref PJLIB_UTIL_STUN_ENDPOINT is used by the library to put together
+ common settings for all STUN objects. For example, the STUN endpoint has a
+ reference of timer heap to poll all STUN timers, reference to ioqueue to
+ poll network events for STUN servers, and some common settings used by
+ various STUN objects.
+
+
+ @subsection stun_clt_tsx_sec Client Transaction
+
+ The @ref PJLIB_UTIL_STUN_TRANSACTION is used to manage outgoing STUN request,
+ for example to retransmit the request and to notify application about the
+ completion of the request.
+
+ The @ref PJLIB_UTIL_STUN_TRANSACTION does not use any networking operations,
+ but instead application must supply the transaction with a callback to
+ be used by the transaction to send outgoing requests. This way the STUN
+ transaction is made more generic and can work with different types of
+ networking codes in application.
+
+
+
+ @subsection stun_srv_sec Server Components
+
+ The @ref PJLIB_UTIL_STUN_SERVER is used for:
+ - implementing STUN servers, and/or
+ - implementing server side STUN handling (for example for ICE).
+
+ */
+
+
+
+#endif /* __PJ_STUN_SERVER_H__ */
+
diff --git a/pjnath/include/pjnath/stun_endpoint.h b/pjnath/include/pjnath/stun_endpoint.h
new file mode 100644
index 00000000..b341309a
--- /dev/null
+++ b/pjnath/include/pjnath/stun_endpoint.h
@@ -0,0 +1,117 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __PJLIB_UTIL_STUN_ENDPOINT_H__
+#define __PJLIB_UTIL_STUN_ENDPOINT_H__
+
+/**
+ * @file stun_endpoint.h
+ * @brief STUN endpoint.
+ */
+
+#include <pjlib-util/stun_msg.h>
+
+
+PJ_BEGIN_DECL
+
+
+/* **************************************************************************/
+/**
+ * @defgroup PJLIB_UTIL_STUN_ENDPOINT STUN Endpoint
+ * @brief Management of incoming and outgoing STUN transactions.
+ * @ingroup PJLIB_UTIL_STUN
+ * @{
+ */
+
+/**
+ * Opaque declaration for STUN endpoint. STUN endpoint manages client and
+ * server STUN transactions, and it needs to be initialized before application
+ * can send or receive STUN messages.
+ */
+typedef struct pj_stun_endpoint
+{
+ /**
+ * Pool factory to be used by the STUN endpoint and all objects created
+ * that use this STUN endpoint.
+ */
+ pj_pool_factory *pf;
+
+ /**
+ * Ioqueue used by this endpoint.
+ */
+ pj_ioqueue_t *ioqueue;
+
+ /**
+ * Timer heap instance used by this endpoint.
+ */
+ pj_timer_heap_t *timer_heap;
+
+ /**
+ * Internal pool used by this endpoint. This shouldn't be used by
+ * application.
+ */
+ pj_pool_t *pool;
+
+ /**
+ * Options.
+ */
+ unsigned options;
+
+ /**
+ * The default initial STUN round-trip time estimation in msecs.
+ * The value normally is PJ_STUN_RTO_VALUE.
+ */
+ unsigned rto_msec;
+
+ /**
+ * The interval to cache outgoing STUN response in the STUN session,
+ * in miliseconds.
+ *
+ * Default 10000 (10 seconds).
+ */
+ unsigned res_cache_msec;
+
+} pj_stun_endpoint;
+
+
+
+/**
+ * Create a STUN endpoint instance.
+ */
+PJ_DECL(pj_status_t) pj_stun_endpoint_create(pj_pool_factory *factory,
+ unsigned options,
+ pj_ioqueue_t *ioqueue,
+ pj_timer_heap_t *timer_heap,
+ pj_stun_endpoint **p_endpt);
+
+/**
+ * Destroy STUN endpoint instance.
+ */
+PJ_DECL(pj_status_t) pj_stun_endpoint_destroy(pj_stun_endpoint *endpt);
+
+
+/**
+ * @}
+ */
+
+
+PJ_END_DECL
+
+
+#endif /* __PJLIB_UTIL_STUN_ENDPOINT_H__ */
+
diff --git a/pjnath/include/pjnath/stun_msg.h b/pjnath/include/pjnath/stun_msg.h
new file mode 100644
index 00000000..ae66c2d1
--- /dev/null
+++ b/pjnath/include/pjnath/stun_msg.h
@@ -0,0 +1,1539 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __PJLIB_UTIL_STUN_MSG_H__
+#define __PJLIB_UTIL_STUN_MSG_H__
+
+/**
+ * @file stun_msg.h
+ * @brief STUN message components.
+ */
+
+#include <pjlib-util/types.h>
+#include <pj/sock.h>
+
+
+PJ_BEGIN_DECL
+
+
+/* **************************************************************************/
+/**
+ * @defgroup PJLIB_UTIL_STUN_MSG STUN Message Representation and Parsing
+ * @brief Low-level representation and parsing of STUN messages.
+ * @ingroup PJLIB_UTIL_STUN
+ * @{
+ */
+
+
+/**
+ * The default initial STUN round-trip time estimation (the RTO value
+ * in RFC 3489-bis), in miliseconds.
+ * This value is used to control the STUN request
+ * retransmit time. The initial value of retransmission interval
+ * would be set to this value, and will be doubled after each
+ * retransmission.
+ */
+#ifndef PJ_STUN_RTO_VALUE
+# define PJ_STUN_RTO_VALUE 100
+#endif
+
+
+/**
+ * The STUN transaction timeout value, in miliseconds.
+ * After the last retransmission is sent and if no response is received
+ * after this time, the STUN transaction will be considered to have failed.
+ *
+ * The default value is 1600 miliseconds (as per RFC 3489-bis).
+ */
+#ifndef PJ_STUN_TIMEOUT_VALUE
+# define PJ_STUN_TIMEOUT_VALUE 1600
+#endif
+
+
+/**
+ * Maximum number of STUN retransmission count.
+ *
+ * Default: 7 (as per RFC 3489-bis)
+ */
+#ifndef PJ_STUN_MAX_RETRANSMIT_COUNT
+# define PJ_STUN_MAX_RETRANSMIT_COUNT 7
+#endif
+
+
+/**
+ * Maximum size of STUN message.
+ */
+#ifndef PJ_STUN_MAX_PKT_LEN
+# define PJ_STUN_MAX_PKT_LEN 512
+#endif
+
+
+/**
+ * Default STUN port as defined by RFC 3489.
+ */
+#define PJ_STUN_PORT 3478
+
+/**
+ * STUN magic cookie.
+ */
+#define PJ_STUN_MAGIC 0x2112A442
+
+
+/**
+ * STUN method constants.
+ */
+enum pj_stun_method_e
+{
+ /**
+ * STUN Binding method as defined by RFC 3489-bis.
+ */
+ PJ_STUN_BINDING_METHOD = 1,
+
+ /**
+ * STUN Shared Secret method as defined by RFC 3489-bis.
+ */
+ PJ_STUN_SHARED_SECRET_METHOD = 2,
+
+ /**
+ * STUN/TURN Allocate method as defined by draft-ietf-behave-turn
+ */
+ PJ_STUN_ALLOCATE_METHOD = 3,
+
+ /**
+ * STUN/TURN Send Indication as defined by draft-ietf-behave-turn
+ */
+ PJ_STUN_SEND_INDICATION_METHOD = 4,
+
+ /**
+ * STUN/TURN Data Indication as defined by draft-ietf-behave-turn
+ */
+ PJ_STUN_DATA_INDICATION_METHOD = 5,
+
+ /**
+ * STUN/TURN Set Active Destination as defined by draft-ietf-behave-turn
+ */
+ PJ_STUN_SET_ACTIVE_DESTINATION_METHOD = 6,
+
+ /**
+ * STUN/TURN Connect method as defined by draft-ietf-behave-turn
+ */
+ PJ_STUN_CONNECT_METHOD = 7,
+
+ /**
+ * STUN/TURN Connect Status indication method.
+ */
+ PJ_STUN_CONNECT_STATUS_METHOD = 8
+};
+
+
+/**
+ * Retrieve the STUN method from the message-type field of the STUN
+ * message.
+ */
+#define PJ_STUN_GET_METHOD(msg_type) ((msg_type) & 0xFEEF)
+
+
+/**
+ * STUN message classes constants.
+ */
+enum pj_stun_msg_class_e
+{
+ /**
+ * This specifies that the message type is a STUN request message.
+ */
+ PJ_STUN_REQUEST_CLASS = 0,
+
+ /**
+ * This specifies that the message type is a STUN indication message.
+ */
+ PJ_STUN_INDICATION_CLASS = 1,
+
+ /**
+ * This specifies that the message type is a STUN successful response.
+ */
+ PJ_STUN_SUCCESS_CLASS = 2,
+
+ /**
+ * This specifies that the message type is a STUN error response.
+ */
+ PJ_STUN_ERROR_CLASS = 3
+};
+
+
+/**
+ * Determine if the message type is a request.
+ */
+#define PJ_STUN_IS_REQUEST(msg_type) (((msg_type) & 0x0110) == 0x0000)
+
+
+/**
+ * Determine if the message type is a response.
+ */
+#define PJ_STUN_IS_RESPONSE(msg_type) (((msg_type) & 0x0110) == 0x0100)
+
+
+/**
+ * The response bit in the message type.
+ */
+#define PJ_STUN_RESPONSE_BIT (0x0100)
+
+/**
+ * Determine if the message type is an error response.
+ */
+#define PJ_STUN_IS_ERROR_RESPONSE(msg_type) (((msg_type) & 0x0110) == 0x0110)
+
+
+/**
+ * The error response bit in the message type.
+ */
+#define PJ_STUN_ERROR_RESPONSE_BIT (0x0110)
+
+
+/**
+ * Determine if the message type is an indication message.
+ */
+#define PJ_STUN_IS_INDICATION(msg_type) (((msg_type) & 0x0110) == 0x0010)
+
+
+/**
+ * The error response bit in the message type.
+ */
+#define PJ_STUN_INDICATION_BIT (0x0010)
+
+
+/**
+ * This enumeration describes STUN message types.
+ */
+typedef enum pj_stun_msg_type
+{
+ /**
+ * STUN BINDING request.
+ */
+ PJ_STUN_BINDING_REQUEST = 0x0001,
+
+ /**
+ * Successful response to STUN BINDING-REQUEST.
+ */
+ PJ_STUN_BINDING_RESPONSE = 0x0101,
+
+ /**
+ * Error response to STUN BINDING-REQUEST.
+ */
+ PJ_STUN_BINDING_ERROR_RESPONSE = 0x0111,
+
+ /**
+ * STUN SHARED-SECRET reqeust.
+ */
+ PJ_STUN_SHARED_SECRET_REQUEST = 0x0002,
+
+ /**
+ * Successful response to STUN SHARED-SECRET reqeust.
+ */
+ PJ_STUN_SHARED_SECRET_RESPONSE = 0x0102,
+
+ /**
+ * Error response to STUN SHARED-SECRET reqeust.
+ */
+ PJ_STUN_SHARED_SECRET_ERROR_RESPONSE = 0x0112,
+
+ /**
+ * STUN/TURN Allocate Request
+ */
+ PJ_STUN_ALLOCATE_REQUEST = 0x0003,
+
+ /**
+ * Successful response to STUN/TURN Allocate Request
+ */
+ PJ_STUN_ALLOCATE_RESPONSE = 0x0103,
+
+ /**
+ * Failure response to STUN/TURN Allocate Request
+ */
+ PJ_STUN_ALLOCATE_ERROR_RESPONSE = 0x0113,
+
+ /**
+ * STUN/TURN Send Indication
+ */
+ PJ_STUN_SEND_INDICATION = 0x0014,
+
+ /**
+ * STUN/TURN Data Indication
+ */
+ PJ_STUN_DATA_INDICATION = 0x0015,
+
+ /**
+ * STUN/TURN Set Active Destination Request
+ */
+ PJ_STUN_SET_ACTIVE_DESTINATION_REQUEST = 0x0006,
+
+ /**
+ * STUN/TURN Set Active Destination Response
+ */
+ PJ_STUN_SET_ACTIVE_DESTINATION_RESPONSE = 0x0106,
+
+ /**
+ * STUN/TURN Set Active Destination Error Response
+ */
+ PJ_STUN_SET_ACTIVE_DESTINATION_ERROR_RESPONSE = 0x0116,
+
+ /**
+ * STUN/TURN Connect Request
+ */
+ PJ_STUN_CONNECT_REQUEST = 0x0007,
+
+ /**
+ * STUN/TURN Connect Response
+ */
+ PJ_STUN_CONNECT_RESPONSE = 0x0107,
+
+ /**
+ * STUN/TURN Connect Error Response
+ */
+ PJ_STUN_CONNECT_ERROR_RESPONSE = 0x0117,
+
+ /**
+ * STUN/TURN Connect Status Indication
+ */
+ PJ_STUN_CONNECT_STATUS_INDICATION = 0x0018
+
+
+} pj_stun_msg_type;
+
+
+
+/**
+ * This enumeration describes STUN attribute types.
+ */
+typedef enum pj_stun_attr_type
+{
+ PJ_STUN_ATTR_MAPPED_ADDR = 0x0001,/**< MAPPED-ADDRESS. */
+ PJ_STUN_ATTR_RESPONSE_ADDR = 0x0002,/**< RESPONSE-ADDRESS (deprcatd)*/
+ PJ_STUN_ATTR_CHANGE_REQUEST = 0x0003,/**< CHANGE-REQUEST (deprecated)*/
+ PJ_STUN_ATTR_SOURCE_ADDR = 0x0004,/**< SOURCE-ADDRESS (deprecated)*/
+ PJ_STUN_ATTR_CHANGED_ADDR = 0x0005,/**< CHANGED-ADDRESS (deprecatd)*/
+ PJ_STUN_ATTR_USERNAME = 0x0006,/**< USERNAME attribute. */
+ PJ_STUN_ATTR_PASSWORD = 0x0007,/**< PASSWORD attribute. */
+ PJ_STUN_ATTR_MESSAGE_INTEGRITY = 0x0008,/**< MESSAGE-INTEGRITY. */
+ PJ_STUN_ATTR_ERROR_CODE = 0x0009,/**< ERROR-CODE. */
+ PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES = 0x000A,/**< UNKNOWN-ATTRIBUTES. */
+ PJ_STUN_ATTR_REFLECTED_FROM = 0x000B,/**< REFLECTED-FROM (deprecatd)*/
+ PJ_STUN_ATTR_LIFETIME = 0x000D,/**< LIFETIME attribute. */
+ PJ_STUN_ATTR_BANDWIDTH = 0x0010,/**< BANDWIDTH attribute */
+ PJ_STUN_ATTR_REMOTE_ADDR = 0x0012,/**< REMOTE-ADDRESS attribute */
+ PJ_STUN_ATTR_DATA = 0x0013,/**< DATA attribute. */
+ PJ_STUN_ATTR_REALM = 0x0014,/**< REALM attribute. */
+ PJ_STUN_ATTR_NONCE = 0x0015,/**< NONCE attribute. */
+ PJ_STUN_ATTR_RELAY_ADDR = 0x0016,/**< RELAY-ADDRESS attribute. */
+ PJ_STUN_ATTR_REQ_ADDR_TYPE = 0x0017,/**< REQUESTED-ADDRESS-TYPE */
+ PJ_STUN_ATTR_REQ_PORT_PROPS = 0x0018,/**< REQUESTED-PORT-PROPS */
+ PJ_STUN_ATTR_REQ_TRANSPORT = 0x0019,/**< REQUESTED-TRANSPORT */
+ PJ_STUN_ATTR_XOR_MAPPED_ADDR = 0x0020,/**< XOR-MAPPED-ADDRESS */
+ PJ_STUN_ATTR_TIMER_VAL = 0x0021,/**< TIMER-VAL attribute. */
+ PJ_STUN_ATTR_REQ_IP = 0x0022,/**< REQUESTED-IP attribute */
+ PJ_STUN_ATTR_XOR_REFLECTED_FROM = 0x0023,/**< XOR-REFLECTED-FROM */
+ PJ_STUN_ATTR_PRIORITY = 0x0024,/**< PRIORITY */
+ PJ_STUN_ATTR_USE_CANDIDATE = 0x0025,/**< USE-CANDIDATE */
+ PJ_STUN_ATTR_XOR_INTERNAL_ADDR = 0x0026,/**< XOR-INTERNAL-ADDRESS */
+
+ PJ_STUN_ATTR_END_MANDATORY_ATTR,
+
+ PJ_STUN_ATTR_START_EXTENDED_ATTR= 0x8021,
+
+ PJ_STUN_ATTR_SERVER = 0x8022,/**< SERVER attribute. */
+ PJ_STUN_ATTR_ALTERNATE_SERVER = 0x8023,/**< ALTERNATE-SERVER. */
+ PJ_STUN_ATTR_REFRESH_INTERVAL = 0x8024,/**< REFRESH-INTERVAL. */
+ PJ_STUN_ATTR_FINGERPRINT = 0x8028,/**< FINGERPRINT attribute. */
+
+ PJ_STUN_ATTR_END_EXTENDED_ATTR
+
+} pj_stun_attr_type;
+
+
+/**
+ * STUN error codes, which goes into STUN ERROR-CODE attribute.
+ */
+typedef enum pj_stun_status
+{
+ PJ_STUN_SC_TRY_ALTERNATE = 300, /**< Try Alternate */
+ PJ_STUN_SC_BAD_REQUEST = 400, /**< Bad Request */
+ PJ_STUN_SC_UNAUTHORIZED = 401, /**< Unauthorized */
+ PJ_STUN_SC_UNKNOWN_ATTRIBUTE = 420, /**< Unknown Attribute */
+ PJ_STUN_SC_STALE_CREDENTIALS = 430, /**< Stale Credentials */
+ PJ_STUN_SC_INTEGRITY_CHECK_FAILURE = 431, /**< Integrity Chk Fail */
+ PJ_STUN_SC_MISSING_USERNAME = 432, /**< Missing Username */
+ PJ_STUN_SC_USE_TLS = 433, /**< Use TLS */
+ PJ_STUN_SC_MISSING_REALM = 434, /**< Missing Realm */
+ PJ_STUN_SC_MISSING_NONCE = 435, /**< Missing Nonce */
+ PJ_STUN_SC_UNKNOWN_USERNAME = 436, /**< Unknown Username */
+ PJ_STUN_SC_NO_BINDING = 437, /**< No Binding. */
+ PJ_STUN_SC_STALE_NONCE = 438, /**< Stale Nonce */
+ PJ_STUN_SC_TRANSITIONING = 439, /**< Transitioning. */
+ PJ_STUN_SC_WRONG_USERNAME = 441, /**< Wrong Username. */
+ PJ_STUN_SC_UNSUPP_TRANSPORT_PROTO = 442, /**< Unsupported Transport or
+ Protocol */
+ PJ_STUN_SC_INVALID_IP_ADDR = 443, /**< Invalid IP Address */
+ PJ_STUN_SC_INVALID_PORT = 444, /**< Invalid Port */
+ PJ_STUN_SC_OPER_TCP_ONLY = 445, /**< Operation for TCP Only */
+ PJ_STUN_SC_CONNECTION_FAILURE = 446, /**< Connection Failure */
+ PJ_STUN_SC_CONNECTION_TIMEOUT = 447, /**< Connection Timeout */
+ PJ_STUN_SC_ALLOCATION_QUOTA_REACHED = 486, /**< Allocation Quota Reached
+ (TURN) */
+ PJ_STUN_SC_SERVER_ERROR = 500, /**< Server Error */
+ PJ_STUN_SC_INSUFFICIENT_CAPACITY = 507, /**< Insufficient Capacity
+ (TURN) */
+ PJ_STUN_SC_GLOBAL_FAILURE = 600 /**< Global Failure */
+} pj_stun_status;
+
+
+/**
+ * This structure describes STUN message header. A STUN message has the
+ * following format:
+ *
+ * \verbatim
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |0 0| STUN Message Type | Message Length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Magic Cookie |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ Transaction ID
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ \endverbatim
+ */
+typedef struct pj_stun_msg_hdr
+{
+ /**
+ * STUN message type, which the first two bits must be zeroes.
+ */
+ pj_uint16_t type;
+
+ /**
+ * The message length is the size, in bytes, of the message not
+ * including the 20 byte STUN header.
+ */
+ pj_uint16_t length;
+
+ /**
+ * The magic cookie is a fixed value, 0x2112A442 (PJ_STUN_MAGIC constant).
+ * In the previous version of this specification [15] this field was part
+ * of the transaction ID.
+ */
+ pj_uint32_t magic;
+
+ /**
+ * The transaction ID is a 96 bit identifier. STUN transactions are
+ * identified by their unique 96-bit transaction ID. For request/
+ * response transactions, the transaction ID is chosen by the STUN
+ * client and MUST be unique for each new STUN transaction generated by
+ * that STUN client. The transaction ID MUST be uniformly and randomly
+ * distributed between 0 and 2**96 - 1.
+ */
+ pj_uint8_t tsx_id[12];
+
+} pj_stun_msg_hdr;
+
+
+/**
+ * This structre describes STUN attribute header. Each attribute is
+ * TLV encoded, with a 16 bit type, 16 bit length, and variable value.
+ * Each STUN attribute ends on a 32 bit boundary:
+ *
+ * \verbatim
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Value .... |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ \endverbatim
+ */
+typedef struct pj_stun_attr_hdr
+{
+ /**
+ * STUN attribute type.
+ */
+ pj_uint16_t type;
+
+ /**
+ * The Length refers to the length of the actual useful content of the
+ * Value portion of the attribute, measured in bytes. The value
+ * in the Length field refers to the length of the Value part of the
+ * attribute prior to padding - i.e., the useful content.
+ */
+ pj_uint16_t length;
+
+} pj_stun_attr_hdr;
+
+
+/**
+ * This structure describes STUN generic IP address attribute, used for
+ * example to represent STUN MAPPED-ADDRESS attribute.
+ *
+ * The generic IP address attribute indicates the transport address.
+ * It consists of an eight bit address family, and a sixteen bit port,
+ * followed by a fixed length value representing the IP address. If the
+ * address family is IPv4, the address is 32 bits, in network byte
+ * order. If the address family is IPv6, the address is 128 bits in
+ * network byte order.
+ *
+ * The format of the generic IP address attribute is:
+ *
+ * \verbatim
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |x x x x x x x x| Family | Port |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Address (variable)
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ \endverbatim
+ */
+typedef struct pj_stun_sockaddr_attr
+{
+ /**
+ * Standard STUN attribute header.
+ */
+ pj_stun_attr_hdr hdr;
+
+ /**
+ * Flag to indicate whether this attribute should be sent in XOR-ed
+ * format, or has been received in XOR-ed format.
+ */
+ pj_bool_t xor_ed;
+
+ /**
+ * The socket address (as a union)
+ */
+ union {
+ pj_sockaddr addr; /**< Generic socket address. */
+ pj_sockaddr_in ipv4; /**< IPv4 socket address. */
+ pj_sockaddr_in6 ipv6; /**< IPv6 socket address. */
+ } addr;
+
+} pj_stun_sockaddr_attr;
+
+
+/**
+ * This structure represents a generic STUN attributes with no payload,
+ * and it is used for example by ICE USE-CANDIDATE attribute.
+ */
+typedef struct pj_stun_empty_attr
+{
+ /**
+ * Standard STUN attribute header.
+ */
+ pj_stun_attr_hdr hdr;
+
+} pj_stun_empty_attr;
+
+
+/**
+ * This structure represents generic STUN string attributes, such as STUN
+ * USERNAME, PASSWORD, SERVER, REALM, and NONCE attributes. Note that for REALM and
+ * NONCE attributes, the text MUST be quoted with.
+ */
+typedef struct pj_stun_string_attr
+{
+ /**
+ * Standard STUN attribute header.
+ */
+ pj_stun_attr_hdr hdr;
+
+ /**
+ * The string value.
+ */
+ pj_str_t value;
+
+} pj_stun_string_attr;
+
+
+/**
+ * This structure represents a generic STUN attributes with 32bit (unsigned)
+ * integer value, such as STUN FINGERPRINT and REFRESH-INTERVAL attributes.
+ */
+typedef struct pj_stun_uint_attr
+{
+ /**
+ * Standard STUN attribute header.
+ */
+ pj_stun_attr_hdr hdr;
+
+ /**
+ * The 32bit value.
+ */
+ pj_uint32_t value;
+
+} pj_stun_uint_attr;
+
+
+/**
+ * This structure represents generic STUN attributes to hold a raw binary
+ * data.
+ */
+typedef struct pj_stun_binary_attr
+{
+ /**
+ * Standard STUN attribute header.
+ */
+ pj_stun_attr_hdr hdr;
+
+ /**
+ * Length of the data.
+ */
+ unsigned length;
+
+ /**
+ * The raw data.
+ */
+ pj_uint8_t *data;
+
+} pj_stun_binary_attr;
+
+
+/**
+ * This structure describes STUN MESSAGE-INTEGRITY attribute.
+ * The MESSAGE-INTEGRITY attribute contains an HMAC-SHA1 [10] of the
+ * STUN message. The MESSAGE-INTEGRITY attribute can be present in any
+ * STUN message type. Since it uses the SHA1 hash, the HMAC will be 20
+ * bytes.
+ */
+typedef struct pj_stun_msgint_attr
+{
+ /**
+ * Standard STUN attribute header.
+ */
+ pj_stun_attr_hdr hdr;
+
+ /**
+ * The 20 bytes hmac value.
+ */
+ pj_uint8_t hmac[20];
+
+} pj_stun_msgint_attr;
+
+
+/**
+ * This structure describes STUN FINGERPRINT attribute. The FINGERPRINT
+ * attribute can be present in all STUN messages. It is computed as
+ * the CRC-32 of the STUN message up to (but excluding) the FINGERPRINT
+ * attribute itself, xor-d with the 32 bit value 0x5354554e
+ */
+typedef struct pj_stun_uint_attr pj_stun_fingerprint_attr;
+
+
+/**
+ * This structure represents STUN ERROR-CODE attribute. The ERROR-CODE
+ * attribute is present in the Binding Error Response and Shared Secret
+ * Error Response. It is a numeric value in the range of 100 to 699
+ * plus a textual reason phrase encoded in UTF-8
+ *
+ * \verbatim
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | 0 |Class| Number |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Reason Phrase (variable) ..
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ \endverbatim
+ */
+typedef struct pj_stun_errcode_attr
+{
+ /**
+ * Standard STUN attribute header.
+ */
+ pj_stun_attr_hdr hdr;
+
+ /**
+ * The value must be zero.
+ */
+ pj_uint16_t zero;
+
+ /**
+ * Error class (1-6).
+ */
+ pj_uint8_t err_class;
+
+ /**
+ * Error number is the error number modulo 100.
+ */
+ pj_uint8_t number;
+
+ /**
+ * The reason phrase.
+ */
+ pj_str_t reason;
+
+} pj_stun_errcode_attr;
+
+
+/**
+ * This describes STUN REALM attribute.
+ * The REALM attribute is present in requests and responses. It
+ * contains text which meets the grammar for "realm" as described in RFC
+ * 3261 [11], and will thus contain a quoted string (including the
+ * quotes).
+ */
+typedef struct pj_stun_string_attr pj_stun_realm_attr;
+
+
+/**
+ * This describes STUN NONCE attribute.
+ * The NONCE attribute is present in requests and in error responses.
+ * It contains a sequence of qdtext or quoted-pair, which are defined in
+ * RFC 3261 [11]. See RFC 2617 [7] for guidance on selection of nonce
+ * values in a server.
+ */
+typedef struct pj_stun_string_attr pj_stun_nonce_attr;
+
+
+/**
+ * This describes STUN UNKNOWN-ATTRIBUTES attribute.
+ * The UNKNOWN-ATTRIBUTES attribute is present only in an error response
+ * when the response code in the ERROR-CODE attribute is 420.
+ * The attribute contains a list of 16 bit values, each of which
+ * represents an attribute type that was not understood by the server.
+ * If the number of unknown attributes is an odd number, one of the
+ * attributes MUST be repeated in the list, so that the total length of
+ * the list is a multiple of 4 bytes.
+ */
+typedef struct pj_stun_unknown_attr
+{
+ /**
+ * Standard STUN attribute header.
+ */
+ pj_stun_attr_hdr hdr;
+
+ /**
+ * Number of unknown attributes in the array.
+ */
+ unsigned attr_count;
+
+ /**
+ * Array of unknown attribute IDs.
+ */
+ pj_uint16_t attrs[PJ_STUN_MAX_ATTR];
+
+} pj_stun_unknown_attr;
+
+
+/**
+ * This structure describes STUN MAPPED-ADDRESS attribute.
+ * The MAPPED-ADDRESS attribute indicates the mapped transport address.
+ */
+typedef struct pj_stun_sockaddr_attr pj_stun_mapped_addr_attr;
+
+
+/**
+ * This describes STUN XOR-MAPPED-ADDRESS attribute (which has the same
+ * format as STUN MAPPED-ADDRESS attribute).
+ * The XOR-MAPPED-ADDRESS attribute is present in responses. It
+ * provides the same information that would present in the MAPPED-
+ * ADDRESS attribute but because the NAT's public IP address is
+ * obfuscated through the XOR function, STUN messages are able to pass
+ * through NATs which would otherwise interfere with STUN.
+ */
+typedef struct pj_stun_sockaddr_attr pj_stun_xor_mapped_addr_attr;
+
+
+/**
+ * This describes STUN SERVER attribute.
+ * The server attribute contains a textual description of the software
+ * being used by the server, including manufacturer and version number.
+ * The attribute has no impact on operation of the protocol, and serves
+ * only as a tool for diagnostic and debugging purposes. The value of
+ * SERVER is variable length.
+ */
+typedef struct pj_stun_string_attr pj_stun_server_attr;
+
+
+/**
+ * This describes STUN ALTERNATE-SERVER attribute.
+ * The alternate server represents an alternate transport address for a
+ * different STUN server to try. It is encoded in the same way as
+ * MAPPED-ADDRESS.
+ */
+typedef struct pj_stun_sockaddr_attr pj_stun_alt_server_attr;
+
+
+/**
+ * This describes STUN REFRESH-INTERVAL attribute.
+ * The REFRESH-INTERVAL indicates the number of milliseconds that the
+ * server suggests the client should use between refreshes of the NAT
+ * bindings between the client and server.
+ */
+typedef struct pj_stun_uint_attr pj_stun_refresh_interval_attr;
+
+
+/**
+ * This structure describes STUN RESPONSE-ADDRESS attribute.
+ * The RESPONSE-ADDRESS attribute indicates where the response to a
+ * Binding Request should be sent. Its syntax is identical to MAPPED-
+ * ADDRESS.
+ *
+ * Note that the usage of this attribute has been deprecated by the
+ * RFC 3489-bis standard.
+ */
+typedef struct pj_stun_sockaddr_attr pj_stun_response_addr_attr;
+
+
+/**
+ * This structure describes STUN CHANGED-ADDRESS attribute.
+ * The CHANGED-ADDRESS attribute indicates the IP address and port where
+ * responses would have been sent from if the "change IP" and "change
+ * port" flags had been set in the CHANGE-REQUEST attribute of the
+ * Binding Request. The attribute is always present in a Binding
+ * Response, independent of the value of the flags. Its syntax is
+ * identical to MAPPED-ADDRESS.
+ *
+ * Note that the usage of this attribute has been deprecated by the
+ * RFC 3489-bis standard.
+ */
+typedef struct pj_stun_sockaddr_attr pj_stun_changed_addr_attr;
+
+
+/**
+ * This structure describes STUN CHANGE-REQUEST attribute.
+ * The CHANGE-REQUEST attribute is used by the client to request that
+ * the server use a different address and/or port when sending the
+ * response.
+ *
+ * Bit 29 of the value is the "change IP" flag. If true, it requests
+ * the server to send the Binding Response with a different IP address
+ * than the one the Binding Request was received on.
+ *
+ * Bit 30 of the value is the "change port" flag. If true, it requests
+ * the server to send the Binding Response with a different port than
+ * the one the Binding Request was received on.
+ *
+ * Note that the usage of this attribute has been deprecated by the
+ * RFC 3489-bis standard.
+ */
+typedef struct pj_stun_uint_attr pj_stun_change_request_attr;
+
+/**
+ * This structure describes STUN SOURCE-ADDRESS attribute.
+ * The SOURCE-ADDRESS attribute is present in Binding Responses. It
+ * indicates the source IP address and port that the server is sending
+ * the response from. Its syntax is identical to that of MAPPED-
+ * ADDRESS.
+ *
+ * Note that the usage of this attribute has been deprecated by the
+ * RFC 3489-bis standard.
+ */
+typedef struct pj_stun_sockaddr_attr pj_stun_src_addr_attr;
+
+
+/**
+ * This describes the STUN REFLECTED-FROM attribute.
+ * The REFLECTED-FROM attribute is present only in Binding Responses,
+ * when the Binding Request contained a RESPONSE-ADDRESS attribute. The
+ * attribute contains the identity (in terms of IP address) of the
+ * source where the request came from. Its purpose is to provide
+ * traceability, so that a STUN server cannot be used as a reflector for
+ * denial-of-service attacks.
+ */
+typedef struct pj_stun_sockaddr_attr pj_stun_reflected_from_attr;
+
+
+/**
+ * This describes STUN USERNAME attribute.
+ * The USERNAME attribute is used for message integrity. It identifies
+ * the shared secret used in the message integrity check. Consequently,
+ * the USERNAME MUST be included in any request that contains the
+ * MESSAGE-INTEGRITY attribute.
+ */
+typedef struct pj_stun_string_attr pj_stun_username_attr;
+
+
+/**
+ * This describes STUN PASSWORD attribute.
+ * If the message type is Shared Secret Response it MUST include the
+ * PASSWORD attribute.
+ */
+typedef struct pj_stun_string_attr pj_stun_password_attr;
+
+
+/**
+ * This describes STUN LIFETIME attribute.
+ * The lifetime attribute represents the duration for which the server
+ * will maintain an allocation in the absence of data traffic either
+ * from or to the client. It is a 32 bit value representing the number
+ * of seconds remaining until expiration.
+ */
+typedef struct pj_stun_uint_attr pj_stun_lifetime_attr;
+
+
+/**
+ * This describes STUN BANDWIDTH attribute.
+ * The bandwidth attribute represents the peak bandwidth, measured in
+ * kbits per second, that the client expects to use on the binding. The
+ * value represents the sum in the receive and send directions.
+ */
+typedef struct pj_stun_uint_attr pj_stun_bandwidth_attr;
+
+
+/**
+ * This describes the STUN REMOTE-ADDRESS attribute.
+ * The REMOTE-ADDRESS specifies the address and port of the peer as seen
+ * from the STUN relay server.
+ */
+typedef struct pj_stun_sockaddr_attr pj_stun_remote_addr_attr;
+
+
+/**
+ * This describes the STUN DATA attribute.
+ * The DATA attribute is present in Send Indications and Data
+ * Indications. It contains raw payload data that is to be sent (in the
+ * case of a Send Request) or was received (in the case of a Data
+ * Indication)..
+ */
+typedef struct pj_stun_binary_attr pj_stun_data_attr;
+
+
+/**
+ * This describes the STUN RELAY-ADDRESS attribute.
+ * The RELAY-ADDRESS is present in Allocate responses. It specifies the
+ * address and port that the server allocated to the client.
+ */
+typedef struct pj_stun_sockaddr_attr pj_stun_relay_addr_attr;
+
+
+/**
+ * This describes the REQUESTED-ADDRESS-TYPE attribute.
+ * The REQUESTED-ADDRESS-TYPE attribute is used by clients to request
+ * the allocation of a specific address type from a server. The
+ * following is the format of the REQUESTED-ADDRESS-TYPE attribute.
+
+ \verbatim
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Family | Reserved |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ \endverbatim
+ */
+typedef struct pj_stun_uint_attr pj_stun_req_addr_type;
+
+/**
+ * This describes the STUN REQUESTED-PORT-PROPS attribute.
+ * This attribute allows the client to request certain properties for
+ * the port that is allocated by the server. The attribute can be used
+ * with any transport protocol that has the notion of a 16 bit port
+ * space (including TCP and UDP). The attribute is 32 bits long. Its
+ * format is:
+
+ \verbatim
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Reserved = 0 |B| A |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ \endverbatim
+ */
+typedef struct pj_stun_uint_attr pj_stun_req_port_props_attr;
+
+
+/**
+ * This describes the STUN REQUESTED-TRANSPORT attribute.
+ * This attribute is used by the client to request a specific transport
+ * protocol for the allocated transport address. It is a 32 bit
+ * unsigned integer. Its values are: 0x0000 for UDP and 0x0000 for TCP.
+ */
+typedef struct pj_stun_uint_attr pj_stun_req_transport_attr;
+
+
+/**
+ * This describes the STUN REQUESTED-IP attribute.
+ * The REQUESTED-IP attribute is used by the client to request that a
+ * specific IP address be allocated to it.
+ */
+typedef struct pj_stun_sockaddr_attr pj_stun_req_ip_attr;
+
+/**
+ * This describes the XOR-REFLECTED-FROM attribute, as described by
+ * draft-macdonald-behave-nat-behavior-discovery-00.
+ * The XOR-REFLECTED-FROM attribute is used in place of the REFLECTED-
+ * FROM attribute. It provides the same information, but because the
+ * NAT's public address is obfuscated through the XOR function, It can
+ * pass through a NAT that would otherwise attempt to translate it to
+ * the private network address. XOR-REFLECTED-FROM has identical syntax
+ * to XOR-MAPPED-ADDRESS.
+ */
+typedef struct pj_stun_sockaddr_attr pj_stun_xor_reflected_from_attr;
+
+/**
+ * This describes the PRIORITY attribute from draft-ietf-mmusic-ice-13.
+ * The PRIORITY attribute indicates the priority that is to be
+ * associated with a peer reflexive candidate, should one be discovered
+ * by this check. It is a 32 bit unsigned integer, and has an attribute
+ * type of 0x0024.
+ */
+typedef struct pj_stun_uint_attr pj_stun_priority_attr;
+
+/**
+ * This describes the USE-CANDIDATE attribute from draft-ietf-mmusic-ice-13.
+ * The USE-CANDIDATE attribute indicates that the candidate pair
+ * resulting from this check should be used for transmission of media.
+ * The attribute has no content (the Length field of the attribute is
+ * zero); it serves as a flag.
+ */
+typedef struct pj_stun_empty_attr pj_stun_use_candidate_attr;
+
+/**
+ * This structure describes STUN XOR-INTERNAL-ADDRESS attribute from
+ * draft-wing-behave-nat-control-stun-usage-00.
+ * This attribute MUST be present in a Binding Response and may be used
+ * in other responses as well. This attribute is necessary to allow a
+ * STUN client to 'walk backwards' and communicate directly with all of
+ * the STUN-aware NATs along the path.
+ */
+typedef pj_stun_sockaddr_attr pj_stun_xor_internal_addr_attr;
+
+/**
+ * This describes the STUN TIMER-VAL attribute.
+ * The TIMER-VAL attribute is used only in conjunction with the Set
+ * Active Destination response. It conveys from the server, to the
+ * client, the value of the timer used in the server state machine.
+ */
+typedef struct pj_stun_uint_attr pj_stun_timer_val_attr;
+
+
+/**
+ * This structure describes a parsed STUN message. All integral fields
+ * in this structure (including IP addresses) will be in the host
+ * byte order.
+ */
+typedef struct pj_stun_msg
+{
+ /**
+ * STUN message header.
+ */
+ pj_stun_msg_hdr hdr;
+
+ /**
+ * Number of attributes in the STUN message.
+ */
+ unsigned attr_count;
+
+ /**
+ * Array of STUN attributes.
+ */
+ pj_stun_attr_hdr *attr[PJ_STUN_MAX_ATTR];
+
+} pj_stun_msg;
+
+
+/** STUN decoding options */
+enum pj_stun_decode_options
+{
+ /**
+ * Tell the decoder that the message was received from datagram
+ * oriented transport (such as UDP).
+ */
+ PJ_STUN_IS_DATAGRAM = 1,
+
+ /**
+ * Tell pj_stun_msg_decode() to check the validity of the STUN
+ * message by calling pj_stun_msg_check() before starting to
+ * decode the packet.
+ */
+ PJ_STUN_CHECK_PACKET = 2
+};
+
+
+/**
+ * Get STUN message method name.
+ *
+ * @param msg_type The STUN message type (in host byte order)
+ *
+ * @return The STUN message method name string.
+ */
+PJ_DECL(const char*) pj_stun_get_method_name(unsigned msg_type);
+
+
+/**
+ * Get STUN message class name.
+ *
+ * @param msg_type The STUN message type (in host byte order)
+ *
+ * @return The STUN message class name string.
+ */
+PJ_DECL(const char*) pj_stun_get_class_name(unsigned msg_type);
+
+
+/**
+ * Get STUN attribute name.
+ *
+ * @return attr_type The STUN attribute type (in host byte order).
+ *
+ * @return The STUN attribute type name string.
+ */
+PJ_DECL(const char*) pj_stun_get_attr_name(unsigned attr_type);
+
+
+/**
+ * Get STUN standard reason phrase for the specified error code.
+ *
+ * @param err_code The STUN error code.
+ *
+ * @return The STUN error reason phrase.
+ */
+PJ_DECL(pj_str_t) pj_stun_get_err_reason(int err_code);
+
+
+/**
+ * Create a generic STUN message.
+ *
+ * @param pool Pool to create the STUN message.
+ * @param msg_type The 14bit message type.
+ * @param magic Magic value to be put to the mesage; for requests,
+ * the value should be PJ_STUN_MAGIC.
+ * @param tsx_id Optional transaction ID, or NULL to let the
+ * function generates a random transaction ID.
+ * @param p_msg Pointer to receive the message.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pj_stun_msg_create(pj_pool_t *pool,
+ unsigned msg_type,
+ pj_uint32_t magic,
+ const pj_uint8_t tsx_id[12],
+ pj_stun_msg **p_msg);
+
+/**
+ * Create STUN response message.
+ *
+ * @param pool Pool to create the mesage.
+ * @param req_msg The request message.
+ * @param err_code STUN error code. If this value is not zero,
+ * then error response will be created, otherwise
+ * successful response will be created.
+ * @param err_msg Optional error message to explain err_code.
+ * If this value is NULL and err_code is not zero,
+ * the error string will be taken from the default
+ * STUN error message.
+ * @param p_response Pointer to receive the response.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error.
+ */
+PJ_DECL(pj_status_t) pj_stun_msg_create_response(pj_pool_t *pool,
+ const pj_stun_msg *req_msg,
+ unsigned err_code,
+ const pj_str_t *err_msg,
+ pj_stun_msg **p_response);
+
+
+/**
+ * Add STUN attribute to STUN message.
+ *
+ * @param msg The STUN message.
+ * @param attr The STUN attribute to be added to the message.
+ *
+ * @return PJ_SUCCESS on success, or PJ_ETOOMANY if there are
+ * already too many attributes in the message.
+ */
+PJ_DECL(pj_status_t) pj_stun_msg_add_attr(pj_stun_msg *msg,
+ pj_stun_attr_hdr *attr);
+
+
+/**
+ * Print the STUN message structure to a packet buffer, ready to be
+ * sent to remote destination. This function will take care about
+ * calculating the MESSAGE-INTEGRITY digest as well as FINGERPRINT
+ * value.
+ *
+ * If MESSAGE-INTEGRITY attribute is present, the function assumes
+ * that application wants to include credential (short or long term)
+ * in the message, and this function will calculate the HMAC digest
+ * from the message using the supplied password in the parameter.
+ * If REALM attribute is present, the HMAC digest is calculated as
+ * long term credential, otherwise as short term credential.
+ *
+ * If FINGERPRINT attribute is present, this function will calculate
+ * the FINGERPRINT CRC attribute for the message.
+ *
+ * @param msg The STUN message to be printed. Upon return,
+ * some fields in the header (such as message
+ * length) will be updated.
+ * @param pkt_buf The buffer to be filled with the packet.
+ * @param buf_size Size of the buffer.
+ * @param options Options, which currently must be zero.
+ * @param password Password to be used when credential is to be
+ * included. This parameter MUST be specified when
+ * the message contains MESSAGE-INTEGRITY attribute.
+ * @param p_msg_len Upon return, it will be filed with the size of
+ * the packet in bytes, or negative value on error.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_msg_encode(pj_stun_msg *msg,
+ pj_uint8_t *pkt_buf,
+ unsigned buf_size,
+ unsigned options,
+ const pj_str_t *password,
+ unsigned *p_msg_len);
+
+/**
+ * Check that the PDU is potentially a valid STUN message. This function
+ * is useful when application needs to multiplex STUN packets with other
+ * application traffic. When this function returns PJ_SUCCESS, there is a
+ * big chance that the packet is a STUN packet.
+ *
+ * Note that we cannot be sure that the PDU is a really valid STUN message
+ * until we actually parse the PDU.
+ *
+ * @param pdu The packet buffer.
+ * @param pdu_len The length of the packet buffer.
+ * @param options Additional options to be applied in the checking,
+ * which can be taken from pj_stun_decode_options. One
+ * of the useful option is PJ_STUN_IS_DATAGRAM which
+ * means that the pdu represents a whole STUN packet.
+ *
+ * @return PJ_SUCCESS if the PDU is a potentially valid STUN
+ * message.
+ */
+PJ_DECL(pj_status_t) pj_stun_msg_check(const pj_uint8_t *pdu,
+ unsigned pdu_len, unsigned options);
+
+
+/**
+ * Decode incoming packet into STUN message.
+ *
+ * @param pool Pool to allocate the message.
+ * @param pdu The incoming packet to be parsed.
+ * @param pdu_len The length of the incoming packet.
+ * @param options Parsing flags, according to pj_stun_decode_options.
+ * @param p_msg Pointer to receive the parsed message.
+ * @param p_parsed_len Optional pointer to receive how many bytes have
+ * been parsed for the STUN message. This is useful
+ * when the packet is received over stream oriented
+ * transport.
+ * @param p_response Optional pointer to receive an instance of response
+ * message, if one can be created. If the packet being
+ * decoded is a request message, and it contains error,
+ * and a response can be created, then the STUN
+ * response message will be returned on this argument.
+ *
+ * @return PJ_SUCCESS if a STUN message has been successfully
+ * decoded.
+ */
+PJ_DECL(pj_status_t) pj_stun_msg_decode(pj_pool_t *pool,
+ const pj_uint8_t *pdu,
+ unsigned pdu_len,
+ unsigned options,
+ pj_stun_msg **p_msg,
+ unsigned *p_parsed_len,
+ pj_stun_msg **p_response);
+
+/**
+ * Dump STUN message to a printable string output.
+ *
+ * @param msg The STUN message
+ * @param buffer Buffer where the printable string output will
+ * be printed on.
+ * @param length Specify the maximum length of the buffer.
+ * @param printed_len Optional pointer, which on output will be filled
+ * up with the actual length of the output string.
+ *
+ * @return The message string output.
+ */
+PJ_DECL(char*) pj_stun_msg_dump(const pj_stun_msg *msg,
+ char *buffer,
+ unsigned length,
+ unsigned *printed_len);
+
+
+/**
+ * Find STUN attribute in the STUN message, starting from the specified
+ * index.
+ *
+ * @param msg The STUN message.
+ * @param attr_type The attribute type to be found, from pj_stun_attr_type.
+ * @param start_index The start index of the attribute in the message.
+ * Specify zero to start searching from the first
+ * attribute.
+ *
+ * @return The attribute instance, or NULL if it cannot be
+ * found.
+ */
+PJ_DECL(pj_stun_attr_hdr*) pj_stun_msg_find_attr(const pj_stun_msg *msg,
+ int attr_type,
+ unsigned start_index);
+
+
+/**
+ * Create a generic STUN IP address attribute. The \a addr_len and
+ * \a addr parameters specify whether the address is IPv4 or IPv4
+ * address.
+ *
+ * @param pool The pool to allocate memory from.
+ * @param attr_type Attribute type, from #pj_stun_attr_type.
+ * @param xor_ed If non-zero, the port and address will be XOR-ed
+ * with magic, to make the XOR-MAPPED-ADDRESS attribute.
+ * @param addr A pj_sockaddr_in or pj_sockaddr_in6 structure.
+ * @param addr_len Length of \a addr parameter.
+ * @param p_attr Pointer to receive the attribute.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_sockaddr_attr_create(pj_pool_t *pool,
+ int attr_type,
+ pj_bool_t xor_ed,
+ const pj_sockaddr_t *addr,
+ unsigned addr_len,
+ pj_stun_sockaddr_attr **p_attr);
+
+
+/**
+ * Create and add generic STUN IP address attribute to a STUN message.
+ * The \a addr_len and \a addr parameters specify whether the address is
+ * IPv4 or IPv4 address.
+ *
+ * @param pool The pool to allocate memory from.
+ * @param msg The STUN message.
+ * @param attr_type Attribute type, from #pj_stun_attr_type.
+ * @param xor_ed If non-zero, the port and address will be XOR-ed
+ * with magic, to make the XOR-MAPPED-ADDRESS attribute.
+ * @param addr A pj_sockaddr_in or pj_sockaddr_in6 structure.
+ * @param addr_len Length of \a addr parameter.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_msg_add_sockaddr_attr(pj_pool_t *pool,
+ pj_stun_msg *msg,
+ int attr_type,
+ pj_bool_t xor_ed,
+ const pj_sockaddr_t *addr,
+ unsigned addr_len);
+
+/**
+ * Create a STUN generic string attribute.
+ *
+ * @param pool The pool to allocate memory from.
+ * @param attr_type Attribute type, from #pj_stun_attr_type.
+ * @param value The string value to be assigned to the attribute.
+ * @param p_attr Pointer to receive the attribute.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_string_attr_create(pj_pool_t *pool,
+ int attr_type,
+ const pj_str_t *value,
+ pj_stun_string_attr **p_attr);
+
+/**
+ * Create and add STUN generic string attribute to the message.
+ *
+ * @param pool The pool to allocate memory from.
+ * @param msg The STUN message.
+ * @param attr_type Attribute type, from #pj_stun_attr_type.
+ * @param value The string value to be assigned to the attribute.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_msg_add_string_attr(pj_pool_t *pool,
+ pj_stun_msg *msg,
+ int attr_type,
+ const pj_str_t *value);
+
+/**
+ * Create a STUN generic 32bit value attribute.
+ *
+ * @param pool The pool to allocate memory from.
+ * @param attr_type Attribute type, from #pj_stun_attr_type.
+ * @param value The 32bit value to be assigned to the attribute.
+ * @param p_attr Pointer to receive the attribute.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_uint_attr_create(pj_pool_t *pool,
+ int attr_type,
+ pj_uint32_t value,
+ pj_stun_uint_attr **p_attr);
+
+/**
+ * Create and add STUN generic 32bit value attribute to the message.
+ *
+ * @param pool The pool to allocate memory from.
+ * @param msg The STUN message
+ * @param attr_type Attribute type, from #pj_stun_attr_type.
+ * @param value The 32bit value to be assigned to the attribute.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_msg_add_uint_attr(pj_pool_t *pool,
+ pj_stun_msg *msg,
+ int attr_type,
+ pj_uint32_t value);
+
+
+/**
+ * Create a STUN MESSAGE-INTEGRITY attribute.
+ *
+ * @param pool The pool to allocate memory from.
+ * @param p_attr Pointer to receive the attribute.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_msgint_attr_create(pj_pool_t *pool,
+ pj_stun_msgint_attr **p_attr);
+
+/**
+ * Create and add STUN MESSAGE-INTEGRITY attribute.
+ *
+ * @param pool The pool to allocate memory from.
+ * @param msg The STUN message
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_msg_add_msgint_attr(pj_pool_t *pool,
+ pj_stun_msg *msg);
+
+/**
+ * Create a STUN ERROR-CODE attribute.
+ *
+ * @param pool The pool to allocate memory from.
+ * @param err_code STUN error code.
+ * @param err_reason Optional STUN error reason. If NULL is given, the
+ * standard error reason will be given.
+ * @param p_attr Pointer to receive the attribute.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_errcode_attr_create(pj_pool_t *pool,
+ int err_code,
+ const pj_str_t *err_reason,
+ pj_stun_errcode_attr **p_attr);
+
+
+/**
+ * Create and add STUN ERROR-CODE attribute to the message.
+ *
+ * @param pool The pool to allocate memory from.
+ * @param msg The STUN mesage.
+ * @param err_code STUN error code.
+ * @param err_reason Optional STUN error reason. If NULL is given, the
+ * standard error reason will be given.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_msg_add_errcode_attr(pj_pool_t *pool,
+ pj_stun_msg *msg,
+ int err_code,
+ const pj_str_t *err_reason);
+
+/**
+ * Create instance of STUN UNKNOWN-ATTRIBUTES attribute and copy the
+ * unknown attribute array to the attribute.
+ *
+ * @param pool The pool to allocate memory from.
+ * @param attr_cnt Number of attributes in the array (can be zero).
+ * @param attr Optional array of attributes.
+ * @param p_attr Pointer to receive the attribute.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_unknown_attr_create(pj_pool_t *pool,
+ unsigned attr_cnt,
+ const pj_uint16_t attr[],
+ pj_stun_unknown_attr **p_attr);
+
+/**
+ * Create and add STUN UNKNOWN-ATTRIBUTES attribute to the message.
+ *
+ * @param pool The pool to allocate memory from.
+ * @param msg The STUN message.
+ * @param attr_cnt Number of attributes in the array (can be zero).
+ * @param attr Optional array of attributes.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_msg_add_unknown_attr(pj_pool_t *pool,
+ pj_stun_msg *msg,
+ unsigned attr_cnt,
+ const pj_uint16_t attr[]);
+
+/**
+ * Create STUN binary attribute.
+ *
+ * @param pool The pool to allocate memory from.
+ * @param attr_type The attribute type, from #pj_stun_attr_type.
+ * @param data Data to be coped to the attribute, or NULL
+ * if no data to be copied now.
+ * @param length Length of data, or zero if no data is to be
+ * copied now.
+ * @param p_attr Pointer to receive the attribute.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_binary_attr_create(pj_pool_t *pool,
+ int attr_type,
+ const pj_uint8_t *data,
+ unsigned length,
+ pj_stun_binary_attr **p_attr);
+
+/**
+ * Create STUN binary attribute and add the attribute to the message.
+ *
+ * @param pool The pool to allocate memory from.
+ * @param msg The STUN message.
+ * @param attr_type The attribute type, from #pj_stun_attr_type.
+ * @param data Data to be coped to the attribute, or NULL
+ * if no data to be copied now.
+ * @param length Length of data, or zero if no data is to be
+ * copied now.
+ * @param p_attr Pointer to receive the attribute.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_msg_add_binary_attr(pj_pool_t *pool,
+ pj_stun_msg *msg,
+ int attr_type,
+ const pj_uint8_t *data,
+ unsigned length);
+
+
+/**
+ * @}
+ */
+
+
+PJ_END_DECL
+
+
+#endif /* __PJLIB_UTIL_STUN_MSG_H__ */
+
diff --git a/pjnath/include/pjnath/stun_session.h b/pjnath/include/pjnath/stun_session.h
new file mode 100644
index 00000000..fa18dc3a
--- /dev/null
+++ b/pjnath/include/pjnath/stun_session.h
@@ -0,0 +1,372 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __PJLIB_UTIL_STUN_SESSION_H__
+#define __PJLIB_UTIL_STUN_SESSION_H__
+
+#include <pjlib-util/stun_msg.h>
+#include <pjlib-util/stun_auth.h>
+#include <pjlib-util/stun_endpoint.h>
+#include <pjlib-util/stun_transaction.h>
+#include <pj/list.h>
+#include <pj/timer.h>
+
+PJ_BEGIN_DECL
+
+
+/* **************************************************************************/
+/**
+ * @defgroup PJLIB_UTIL_STUN_SESSION STUN Client/Server Session
+ * @brief STUN client and server session
+ * @ingroup PJLIB_UTIL_STUN
+ * @{
+ */
+
+
+/** Forward declaration for pj_stun_tx_data */
+typedef struct pj_stun_tx_data pj_stun_tx_data;
+
+/** Forward declaration for pj_stun_session */
+typedef struct pj_stun_session pj_stun_session;
+
+
+/**
+ * This is the callback to be registered to pj_stun_session, to send
+ * outgoing message and to receive various notifications from the STUN
+ * session.
+ */
+typedef struct pj_stun_session_cb
+{
+ /**
+ * Callback to be called by the STUN session to send outgoing message.
+ *
+ * @param sess The STUN session.
+ * @param pkt Packet to be sent.
+ * @param pkt_size Size of the packet to be sent.
+ * @param dst_addr The destination address.
+ * @param addr_len Length of destination address.
+ *
+ * @return The callback should return the status of the
+ * packet sending.
+ */
+ pj_status_t (*on_send_msg)(pj_stun_session *sess,
+ const void *pkt,
+ pj_size_t pkt_size,
+ const pj_sockaddr_t *dst_addr,
+ unsigned addr_len);
+
+ /**
+ * Callback to be called on incoming STUN request message. In the
+ * callback processing, application MUST create a response by calling
+ * pj_stun_session_create_response() function and send the response
+ * with pj_stun_session_send_msg() function, before returning from
+ * the callback.
+ *
+ * @param sess The STUN session.
+ * @param pkt Pointer to the original STUN packet.
+ * @param pkt_len Length of the STUN packet.
+ * @param msg The parsed STUN request.
+ * @param src_addr Source address of the packet.
+ * @param src_addr_len Length of the source address.
+ *
+ * @return The return value of this callback will be
+ * returned back to pj_stun_session_on_rx_pkt()
+ * function.
+ */
+ pj_status_t (*on_rx_request)(pj_stun_session *sess,
+ const pj_uint8_t *pkt,
+ unsigned pkt_len,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len);
+
+ /**
+ * Callback to be called when response is received or the transaction
+ * has timed out.
+ *
+ * @param sess The STUN session.
+ * @param status Status of the request. If the value if not
+ * PJ_SUCCESS, the transaction has timed-out
+ * or other error has occurred, and the response
+ * argument may be NULL.
+ * @param request The original STUN request.
+ * @param response The response message, on successful transaction.
+ */
+ void (*on_request_complete)(pj_stun_session *sess,
+ pj_status_t status,
+ pj_stun_tx_data *tdata,
+ const pj_stun_msg *response);
+
+
+ /**
+ * Type of callback to be called on incoming STUN indication.
+ */
+ pj_status_t (*on_rx_indication)(pj_stun_session *sess,
+ const pj_uint8_t *pkt,
+ unsigned pkt_len,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len);
+
+} pj_stun_session_cb;
+
+
+/**
+ * This structure describe the outgoing STUN transmit data to carry the
+ * message to be sent.
+ */
+struct pj_stun_tx_data
+{
+ PJ_DECL_LIST_MEMBER(struct pj_stun_tx_data);
+
+ pj_pool_t *pool; /**< Pool. */
+ pj_stun_session *sess; /**< The STUN session. */
+ pj_stun_msg *msg; /**< The STUN message. */
+
+ pj_stun_client_tsx *client_tsx; /**< Client STUN transaction. */
+ pj_uint32_t msg_magic; /**< Message magic. */
+ pj_uint8_t msg_key[12]; /**< Message/transaction key. */
+
+ void *pkt; /**< The STUN packet. */
+ unsigned max_len; /**< Length of packet buffer. */
+ unsigned pkt_size; /**< The actual length of STUN pkt. */
+
+ unsigned addr_len; /**< Length of destination address. */
+ const pj_sockaddr_t *dst_addr; /**< Destination address. */
+
+ pj_timer_entry res_timer; /**< Response cache timer. */
+};
+
+
+/**
+ * Create a STUN session.
+ *
+ * @param endpt The STUN endpoint, to be used to register timers etc.
+ * @param name Optional name to be associated with this instance. The
+ * name will be used for example for logging purpose.
+ * @param cb Session callback.
+ * @param fingerprint Enable message fingerprint for outgoing messages.
+ * @param p_sess Pointer to receive STUN session instance.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_session_create(pj_stun_endpoint *endpt,
+ const char *name,
+ const pj_stun_session_cb *cb,
+ pj_bool_t fingerprint,
+ pj_stun_session **p_sess);
+
+/**
+ * Destroy the STUN session.
+ *
+ * @param sess The STUN session instance.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_session_destroy(pj_stun_session *sess);
+
+/**
+ * Associated an arbitrary data with this STUN session. The user data may
+ * be retrieved later with pj_stun_session_get_user_data() function.
+ *
+ * @param sess The STUN session instance.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_session_set_user_data(pj_stun_session *sess,
+ void *user_data);
+
+/**
+ * Retrieve the user data previously associated to this STUN session with
+ * pj_stun_session_set_user_data().
+ *
+ * @param sess The STUN session instance.
+ *
+ * @return The user data associated with this STUN session.
+ */
+PJ_DECL(void*) pj_stun_session_get_user_data(pj_stun_session *sess);
+
+/**
+ * Set server name to be included in all response.
+ *
+ * @param sess The STUN session instance.
+ * @param srv_name Server name string.
+ *
+ * @return The user data associated with this STUN session.
+ */
+PJ_DECL(pj_status_t) pj_stun_session_set_server_name(pj_stun_session *sess,
+ const pj_str_t *srv_name);
+
+/**
+ * Set credential to be used by this session. Once credential is set, all
+ * outgoing messages will include MESSAGE-INTEGRITY, and all incoming
+ * message will be authenticated against this credential.
+ *
+ * To disable authentication after it has been set, call this function
+ * again with NULL as the argument.
+ *
+ * @param sess The STUN session instance.
+ * @param cred The credential to be used by this session. If NULL
+ * is specified, authentication will be disabled.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(void) pj_stun_session_set_credential(pj_stun_session *sess,
+ const pj_stun_auth_cred *cred);
+
+/**
+ * Create a STUN request message. After the message has been successfully
+ * created, application can send the message by calling
+ * pj_stun_session_send_msg().
+ *
+ * @param sess The STUN session instance.
+ * @param msg_type The STUN request message type, from pj_stun_method_e or
+ * from pj_stun_msg_type.
+ * @param p_tdata Pointer to receive STUN transmit data instance containing
+ * the request.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_session_create_req(pj_stun_session *sess,
+ int msg_type,
+ pj_stun_tx_data **p_tdata);
+
+/**
+ * Create a STUN Indication message. After the message has been successfully
+ * created, application can send the message by calling
+ * pj_stun_session_send_msg().
+ *
+ * @param sess The STUN session instance.
+ * @param msg_type The STUN request message type, from pj_stun_method_e or
+ * from pj_stun_msg_type. This function will add the
+ * indication bit as necessary.
+ * @param p_tdata Pointer to receive STUN transmit data instance containing
+ * the message.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_session_create_ind(pj_stun_session *sess,
+ int msg_type,
+ pj_stun_tx_data **p_tdata);
+
+/**
+ * Create a STUN response message. After the message has been
+ * successfully created, application can send the message by calling
+ * pj_stun_session_send_msg().
+ *
+ * @param sess The STUN session instance.
+ * @param req The STUN request where the response is to be created.
+ * @param err_code Error code to be set in the response, if error response
+ * is to be created, according to pj_stun_status enumeration.
+ * This argument MUST be zero if successful response is
+ * to be created.
+ * @param err_msg Optional pointer for the error message string, when
+ * creating error response. If the value is NULL and the
+ * \a err_code is non-zero, then default error message will
+ * be used.
+ * @param p_tdata Pointer to receive the response message created.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_session_create_response(pj_stun_session *sess,
+ const pj_stun_msg *req,
+ unsigned err_code,
+ const pj_str_t *err_msg,
+ pj_stun_tx_data **p_tdata);
+
+
+/**
+ * Send STUN message to the specified destination. This function will encode
+ * the pj_stun_msg instance to a packet buffer, and add credential or
+ * fingerprint if necessary. If the message is a request, the session will
+ * also create and manage a STUN client transaction to be used to manage the
+ * retransmission of the request. After the message has been encoded and
+ * transaction is setup, the \a on_send_msg() callback of pj_stun_session_cb
+ * (which is registered when the STUN session is created) will be called
+ * to actually send the message to the wire.
+ *
+ * @param sess The STUN session instance.
+ * @param cache_res If PJ_TRUE then response will be cached.
+ * @param dst_addr The destination socket address.
+ * @param addr_len Length of destination address.
+ * @param tdata The STUN transmit data containing the STUN message to
+ * be sent.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_session_send_msg(pj_stun_session *sess,
+ pj_bool_t cache_res,
+ const pj_sockaddr_t *dst_addr,
+ unsigned addr_len,
+ pj_stun_tx_data *tdata);
+
+/**
+ * Application must call this function to notify the STUN session about
+ * the arrival of STUN packet. The STUN packet MUST have been checked
+ * first with #pj_stun_msg_check() to verify that this is indeed a valid
+ * STUN packet.
+ *
+ * The STUN session will decode the packet into pj_stun_msg, and process
+ * the message accordingly. If the message is a response, it will search
+ * through the outstanding STUN client transactions for a matching
+ * transaction ID and hand over the response to the transaction.
+ *
+ * On successful message processing, application will be notified about
+ * the message via one of the pj_stun_session_cb callback.
+ *
+ * @param sess The STUN session instance.
+ * @param packet The packet containing STUN message.
+ * @param pkt_size Size of the packet.
+ * @param options Options, from #pj_stun_decode_options.
+ * @param parsed_len Optional pointer to receive the size of the parsed
+ * STUN message (useful if packet is received via a
+ * stream oriented protocol).
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_session_on_rx_pkt(pj_stun_session *sess,
+ const void *packet,
+ pj_size_t pkt_size,
+ unsigned options,
+ unsigned *parsed_len,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len);
+
+/**
+ * Destroy the transmit data. Call this function only when tdata has been
+ * created but application doesn't want to send the message (perhaps
+ * because of other error).
+ *
+ * @param sess The STUN session.
+ * @param tdata The transmit data.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(void) pj_stun_msg_destroy_tdata(pj_stun_session *sess,
+ pj_stun_tx_data *tdata);
+
+
+/**
+ * @}
+ */
+
+
+PJ_END_DECL
+
+#endif /* __PJLIB_UTIL_STUN_SESSION_H__ */
+
diff --git a/pjnath/include/pjnath/stun_transaction.h b/pjnath/include/pjnath/stun_transaction.h
new file mode 100644
index 00000000..c7a5bf93
--- /dev/null
+++ b/pjnath/include/pjnath/stun_transaction.h
@@ -0,0 +1,217 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __PJLIB_UTIL_STUN_TRANSACTION_H__
+#define __PJLIB_UTIL_STUN_TRANSACTION_H__
+
+/**
+ * @file stun_transaction.h
+ * @brief STUN transaction
+ */
+
+#include <pjlib-util/stun_msg.h>
+#include <pjlib-util/stun_endpoint.h>
+
+
+PJ_BEGIN_DECL
+
+
+/* **************************************************************************/
+/**
+ * @defgroup PJLIB_UTIL_STUN_TRANSACTION STUN Client Transaction
+ * @brief STUN client transaction
+ * @ingroup PJLIB_UTIL_STUN
+ * @{
+ *
+ The @ref PJLIB_UTIL_STUN_TRANSACTION is used to manage outgoing STUN request,
+ for example to retransmit the request and to notify application about the
+ completion of the request.
+
+ The @ref PJLIB_UTIL_STUN_TRANSACTION does not use any networking operations,
+ but instead application must supply the transaction with a callback to
+ be used by the transaction to send outgoing requests. This way the STUN
+ transaction is made more generic and can work with different types of
+ networking codes in application.
+
+
+ */
+
+/**
+ * Opaque declaration of STUN client transaction.
+ */
+typedef struct pj_stun_client_tsx pj_stun_client_tsx;
+
+/**
+ * STUN client transaction callback.
+ */
+typedef struct pj_stun_tsx_cb
+{
+ /**
+ * This callback is called when the STUN transaction completed.
+ *
+ * @param tsx The STUN transaction.
+ * @param status Status of the transaction. Status PJ_SUCCESS
+ * means that the request has received a successful
+ * response.
+ * @param response The STUN response, which value may be NULL if
+ * \a status is not PJ_SUCCESS.
+ */
+ void (*on_complete)(pj_stun_client_tsx *tsx,
+ pj_status_t status,
+ const pj_stun_msg *response);
+
+ /**
+ * This callback is called by the STUN transaction when it wants to send
+ * outgoing message.
+ *
+ * @param tsx The STUN transaction instance.
+ * @param stun_pkt The STUN packet to be sent.
+ * @param pkt_size Size of the STUN packet.
+ *
+ * @return If return value of the callback is not PJ_SUCCESS,
+ * the transaction will fail.
+ */
+ pj_status_t (*on_send_msg)(pj_stun_client_tsx *tsx,
+ const void *stun_pkt,
+ pj_size_t pkt_size);
+
+} pj_stun_tsx_cb;
+
+
+
+/**
+ * Create an instance of STUN client transaction. The STUN client
+ * transaction is used to transmit outgoing STUN request and to
+ * ensure the reliability of the request by periodically retransmitting
+ * the request, if necessary.
+ *
+ * @param endpt The STUN endpoint, which will be used to retrieve
+ * various settings for the transaction.
+ * @param pool Pool to be used to allocate memory from.
+ * @param cb Callback structure, to be used by the transaction
+ * to send message and to notify the application about
+ * the completion of the transaction.
+ * @param p_tsx Pointer to receive the transaction instance.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_client_tsx_create( pj_stun_endpoint *endpt,
+ pj_pool_t *pool,
+ const pj_stun_tsx_cb *cb,
+ pj_stun_client_tsx **p_tsx);
+
+/**
+ * Destroy a STUN client transaction.
+ *
+ * @param tsx The STUN transaction.
+ *
+ * @return PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_client_tsx_destroy(pj_stun_client_tsx *tsx);
+
+
+/**
+ * Check if transaction has completed.
+ *
+ * @param tsx The STUN transaction.
+ *
+ * @return Non-zero if transaction has completed.
+ */
+PJ_DECL(pj_bool_t) pj_stun_client_tsx_is_complete(pj_stun_client_tsx *tsx);
+
+
+/**
+ * Associate an arbitrary data with the STUN transaction. This data
+ * can be then retrieved later from the transaction, by using
+ * pj_stun_client_tsx_get_data() function.
+ *
+ * @param tsx The STUN client transaction.
+ * @param data Application data to be associated with the
+ * STUN transaction.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pj_stun_client_tsx_set_data(pj_stun_client_tsx *tsx,
+ void *data);
+
+
+/**
+ * Get the user data that was previously associated with the STUN
+ * transaction.
+ *
+ * @param tsx The STUN client transaction.
+ *
+ * @return The user data.
+ */
+PJ_DECL(void*) pj_stun_client_tsx_get_data(pj_stun_client_tsx *tsx);
+
+
+/**
+ * Start the STUN client transaction by sending STUN request using
+ * this transaction. If reliable transport such as TCP or TLS is used,
+ * the retransmit flag should be set to PJ_FALSE because reliablity
+ * will be assured by the transport layer.
+ *
+ * @param tsx The STUN client transaction.
+ * @param retransmit Should this message be retransmitted by the
+ * STUN transaction.
+ * @param pkt The STUN packet to send.
+ * @param pkt_len Length of STUN packet.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_client_tsx_send_msg(pj_stun_client_tsx *tsx,
+ pj_bool_t retransmit,
+ void *pkt,
+ unsigned pkt_len);
+
+
+
+/**
+ * Notify the STUN transaction about the arrival of STUN response.
+ * If the STUN response contains a final error (300 and greater), the
+ * transaction will be terminated and callback will be called. If the
+ * STUN response contains response code 100-299, retransmission
+ * will cease, but application must still call this function again
+ * with a final response later to allow the transaction to complete.
+ *
+ * @param tsx The STUN client transaction instance.
+ * @param packet The incoming packet.
+ * @param pkt_size Size of the incoming packet.
+ * @param parsed_len Optional pointer to receive the number of bytes
+ * that have been parsed from the incoming packet
+ * for the STUN message. This is useful if the
+ * STUN transaction is running over stream oriented
+ * socket such as TCP or TLS.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_client_tsx_on_rx_msg(pj_stun_client_tsx *tsx,
+ const pj_stun_msg *msg);
+
+
+/**
+ * @}
+ */
+
+
+PJ_END_DECL
+
+
+#endif /* __PJLIB_UTIL_STUN_TRANSACTION_H__ */
+
diff --git a/pjnath/include/pjnath/turn_client.h b/pjnath/include/pjnath/turn_client.h
new file mode 100644
index 00000000..83d8d2d6
--- /dev/null
+++ b/pjnath/include/pjnath/turn_client.h
@@ -0,0 +1,132 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __PJLIB_UTIL_TURN_CLIENT_H__
+#define __PJLIB_UTIL_TURN_CLIENT_H__
+
+/**
+ * @file turn_client.h
+ * @brief TURN client session.
+ */
+
+#include <pjlib-util/stun_msg.h>
+
+
+PJ_BEGIN_DECL
+
+
+/**
+ * @defgroup PJLIB_UTIL_TURN_CLIENT TURN Client Session
+ * @brief Management of STUN/TURN client session
+ * @ingroup PJLIB_UTIL_STUN
+ * @{
+ */
+
+typedef struct pj_turn_client pj_turn_client;
+
+/**
+ * This describes TURN client config.
+ */
+typedef struct pj_turn_client_cb
+{
+ /**
+ * Callback to be called by the TURN session to send outgoing message.
+ *
+ * @param client The TURN client session.
+ * @param pkt Packet to be sent.
+ * @param pkt_size Size of the packet to be sent.
+ * @param dst_addr The destination address.
+ * @param addr_len Length of destination address.
+ *
+ * @return The callback should return the status of the
+ * packet sending.
+ */
+ pj_status_t (*on_send_msg)(pj_turn_client *client,
+ const void *pkt,
+ pj_size_t pkt_size,
+ const pj_sockaddr_t *dst_addr,
+ unsigned addr_len);
+
+ /**
+ * Callback to be called by TURN session when its state has changed.
+ */
+ pj_status_t (*on_state_changed)(pj_turn_client *client);
+
+} pj_turn_client_cb;
+
+
+/**
+ * Options
+ */
+typedef struct pj_turn_client_config
+{
+ int bandwidth;
+ int lifetime;
+ int sock_type;
+ int port;
+} pj_turn_client_config;
+
+
+PJ_INLINE(void) pj_turn_client_config_default(pj_turn_client_config *cfg)
+{
+ pj_bzero(cfg, sizeof(*cfg));
+ cfg->bandwidth = -1;
+ cfg->lifetime = -1;
+ cfg->sock_type = -1;
+ cfg->port = -1;
+}
+
+
+/**
+ * This describes the TURN client session.
+ */
+struct pj_turn_client
+{
+ pj_pool_t *pool;
+ pj_stun_session *session;
+ pj_timer_entry alloc_timer;
+ pj_sockaddr_in mapped_addr;
+ pj_sockaddr_in relay_addr;
+};
+
+
+
+
+/**
+ * Create the TURN client session.
+ */
+PJ_DECL(pj_status_t) pj_turn_client_create(pj_stun_endpoint *endpt,
+ const pj_turn_client_config *cfg,
+ const pj_turn_client_cb *cb,
+ pj_turn_client **p_client);
+
+/**
+ * Start the TURN client session by sending Allocate request to the server.
+ */
+
+
+/**
+ * @}
+ */
+
+
+PJ_END_DECL
+
+
+#endif /* __PJLIB_UTIL_TURN_CLIENT_H__ */
+
diff --git a/pjnath/src/pjnath/stun_auth.c b/pjnath/src/pjnath/stun_auth.c
new file mode 100644
index 00000000..9a94fe0d
--- /dev/null
+++ b/pjnath/src/pjnath/stun_auth.c
@@ -0,0 +1,341 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjlib-util/stun_auth.h>
+#include <pjlib-util/errno.h>
+#include <pjlib-util/hmac_sha1.h>
+#include <pjlib-util/sha1.h>
+#include <pj/assert.h>
+#include <pj/string.h>
+
+
+/* Duplicate credential */
+PJ_DEF(void) pj_stun_auth_cred_dup( pj_pool_t *pool,
+ pj_stun_auth_cred *dst,
+ const pj_stun_auth_cred *src)
+{
+ dst->type = src->type;
+
+ switch (src->type) {
+ case PJ_STUN_AUTH_CRED_STATIC:
+ pj_strdup(pool, &dst->data.static_cred.realm,
+ &src->data.static_cred.realm);
+ pj_strdup(pool, &dst->data.static_cred.username,
+ &src->data.static_cred.username);
+ dst->data.static_cred.data_type = src->data.static_cred.data_type;
+ pj_strdup(pool, &dst->data.static_cred.data,
+ &src->data.static_cred.data);
+ pj_strdup(pool, &dst->data.static_cred.nonce,
+ &src->data.static_cred.nonce);
+ break;
+ case PJ_STUN_AUTH_CRED_DYNAMIC:
+ pj_memcpy(&dst->data.dyn_cred, &src->data.dyn_cred,
+ sizeof(src->data.dyn_cred));
+ break;
+ }
+}
+
+
+PJ_INLINE(pj_uint16_t) GET_VAL16(const pj_uint8_t *pdu, unsigned pos)
+{
+ return (pj_uint16_t) ((pdu[pos] << 8) + pdu[pos+1]);
+}
+
+
+/* Send 401 response */
+static pj_status_t create_challenge(pj_pool_t *pool,
+ const pj_stun_msg *msg,
+ int err_code,
+ const pj_str_t *err_msg,
+ const pj_str_t *realm,
+ const pj_str_t *nonce,
+ pj_stun_msg **p_response)
+{
+ pj_stun_msg *response;
+ pj_str_t tmp_nonce;
+ pj_status_t rc;
+
+ rc = pj_stun_msg_create_response(pool, msg,
+ err_code, err_msg, &response);
+ if (rc != PJ_SUCCESS)
+ return rc;
+
+
+ if (realm && realm->slen) {
+ rc = pj_stun_msg_add_string_attr(pool, response,
+ PJ_STUN_ATTR_REALM,
+ realm);
+ if (rc != PJ_SUCCESS)
+ return rc;
+
+ /* long term must include nonce */
+ if (!nonce || nonce->slen == 0) {
+ tmp_nonce = pj_str("pjstun");
+ nonce = &tmp_nonce;
+ }
+ }
+
+ if (nonce && nonce->slen) {
+ rc = pj_stun_msg_add_string_attr(pool, response,
+ PJ_STUN_ATTR_NONCE,
+ nonce);
+ if (rc != PJ_SUCCESS)
+ return rc;
+ }
+
+ *p_response = response;
+
+ return PJ_SUCCESS;
+}
+
+
+/* Verify credential */
+PJ_DEF(pj_status_t) pj_stun_verify_credential( const pj_uint8_t *pkt,
+ unsigned pkt_len,
+ const pj_stun_msg *msg,
+ pj_stun_auth_cred *cred,
+ pj_pool_t *pool,
+ pj_stun_msg **p_response)
+{
+ pj_str_t realm, nonce, password;
+ const pj_stun_msgint_attr *amsgi;
+ unsigned amsgi_pos;
+ const pj_stun_username_attr *auser;
+ pj_bool_t username_ok;
+ const pj_stun_realm_attr *arealm;
+ const pj_stun_realm_attr *anonce;
+ pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE];
+ pj_uint8_t md5_digest[16];
+ pj_str_t key;
+ pj_status_t status;
+
+ /* msg and credential MUST be specified */
+ PJ_ASSERT_RETURN(pkt && pkt_len && msg && cred, PJ_EINVAL);
+
+ /* If p_response is specified, pool MUST be specified. */
+ PJ_ASSERT_RETURN(!p_response || pool, PJ_EINVAL);
+
+ if (p_response)
+ *p_response = NULL;
+
+ if (!PJ_STUN_IS_REQUEST(msg->hdr.type))
+ p_response = NULL;
+
+ /* Get realm and nonce */
+ realm.slen = nonce.slen = 0;
+ if (cred->type == PJ_STUN_AUTH_CRED_STATIC) {
+ realm = cred->data.static_cred.realm;
+ nonce = cred->data.static_cred.nonce;
+ } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
+ status = cred->data.dyn_cred.get_auth(cred->data.dyn_cred.user_data,
+ pool, &realm, &nonce);
+ if (status != PJ_SUCCESS)
+ return status;
+ } else {
+ pj_assert(!"Unexpected");
+ return PJ_EBUG;
+ }
+
+ /* First check that MESSAGE-INTEGRITY is present */
+ amsgi = (const pj_stun_msgint_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_MESSAGE_INTEGRITY, 0);
+ if (amsgi == NULL) {
+ if (p_response) {
+ create_challenge(pool, msg, PJ_STUN_SC_UNAUTHORIZED, NULL,
+ &realm, &nonce, p_response);
+ }
+ return PJLIB_UTIL_ESTUNMSGINT;
+ }
+
+ /* Next check that USERNAME is present */
+ auser = (const pj_stun_username_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USERNAME, 0);
+ if (auser == NULL) {
+ if (p_response) {
+ create_challenge(pool, msg, PJ_STUN_SC_MISSING_USERNAME, NULL,
+ &realm, &nonce, p_response);
+ }
+ return PJLIB_UTIL_ESTUNNOUSERNAME;
+ }
+
+ /* Get REALM, if any */
+ arealm = (const pj_stun_realm_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REALM, 0);
+
+ /* Check if username match */
+ if (cred->type == PJ_STUN_AUTH_CRED_STATIC) {
+ username_ok = !pj_strcmp(&auser->value,
+ &cred->data.static_cred.username);
+ password = cred->data.static_cred.data;
+ } else if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
+ int data_type = 0;
+ pj_status_t rc;
+ rc = cred->data.dyn_cred.get_password(cred->data.dyn_cred.user_data,
+ (arealm?&arealm->value:NULL),
+ &auser->value, pool,
+ &data_type, &password);
+ username_ok = (rc == PJ_SUCCESS);
+ } else {
+ username_ok = PJ_TRUE;
+ password.slen = 0;
+ }
+
+ if (!username_ok) {
+ /* Username mismatch */
+ if (p_response) {
+ create_challenge(pool, msg, PJ_STUN_SC_UNKNOWN_USERNAME, NULL,
+ &realm, &nonce, p_response);
+ }
+ return PJLIB_UTIL_ESTUNUSERNAME;
+ }
+
+
+ /* Get NONCE attribute */
+ anonce = (pj_stun_nonce_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_NONCE, 0);
+
+ /* Check for long term/short term requirements. */
+ if (realm.slen != 0 && arealm == NULL) {
+ /* Long term credential is required and REALM is not present */
+ if (p_response) {
+ create_challenge(pool, msg, PJ_STUN_SC_MISSING_REALM, NULL,
+ &realm, &nonce, p_response);
+ }
+ return PJLIB_UTIL_ESTUNNOREALM;
+
+ } else if (realm.slen != 0 && arealm != NULL) {
+ /* We want long term, and REALM is present */
+
+ /* NONCE must be present. */
+ if (anonce == NULL) {
+ if (p_response) {
+ create_challenge(pool, msg, PJ_STUN_SC_MISSING_NONCE,
+ NULL, &realm, &nonce, p_response);
+ }
+ return PJLIB_UTIL_ESTUNNONCE;
+ }
+
+ /* Verify REALM matches */
+ if (pj_stricmp(&arealm->value, &realm)) {
+ /* REALM doesn't match */
+ if (p_response) {
+ create_challenge(pool, msg, PJ_STUN_SC_MISSING_REALM,
+ NULL, &realm, &nonce, p_response);
+ }
+ return PJLIB_UTIL_ESTUNNOREALM;
+ }
+
+ /* Valid case, will validate the message integrity later */
+
+ } else if (realm.slen == 0 && arealm != NULL) {
+ /* We want to use short term credential, but client uses long
+ * term credential. The draft doesn't mention anything about
+ * switching between long term and short term.
+ */
+
+ /* For now just accept the credential, anyway it will probably
+ * cause wrong message integrity value later.
+ */
+ } else if (realm.slen==0 && arealm == NULL) {
+ /* Short term authentication is wanted, and one is supplied */
+
+ /* Application MAY request NONCE to be supplied */
+ if (nonce.slen != 0) {
+ if (p_response) {
+ create_challenge(pool, msg, PJ_STUN_SC_MISSING_NONCE,
+ NULL, &realm, &nonce, p_response);
+ }
+ return PJLIB_UTIL_ESTUNNONCE;
+ }
+ }
+
+ /* If NONCE is present, validate it */
+ if (anonce) {
+ pj_bool_t ok;
+
+ if (cred->type == PJ_STUN_AUTH_CRED_DYNAMIC) {
+ ok=cred->data.dyn_cred.verify_nonce(cred->data.dyn_cred.user_data,
+ (arealm?&arealm->value:NULL),
+ &auser->value,
+ &anonce->value);
+ } else {
+ if (nonce.slen) {
+ ok = !pj_strcmp(&anonce->value, &nonce);
+ } else {
+ ok = PJ_TRUE;
+ }
+ }
+
+ if (!ok) {
+ if (p_response) {
+ create_challenge(pool, msg, PJ_STUN_SC_STALE_NONCE,
+ NULL, &realm, &nonce, p_response);
+ }
+ return PJLIB_UTIL_ESTUNNONCE;
+ }
+ }
+
+ /* Get the position of MESSAGE-INTEGRITY in the packet */
+ amsgi_pos = 20+msg->hdr.length-24;
+ if (GET_VAL16(pkt, amsgi_pos) == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
+ /* Found MESSAGE-INTEGRITY as the last attribute */
+ } else {
+ amsgi_pos = 0;
+ }
+
+ if (amsgi_pos==0) {
+ amsgi_pos = 20+msg->hdr.length-8-24;
+ if (GET_VAL16(pkt, amsgi_pos) == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
+ /* Found MESSAGE-INTEGRITY before FINGERPRINT */
+ } else {
+ amsgi_pos = 0;
+ }
+ }
+
+ if (amsgi_pos==0) {
+ pj_assert(!"Unable to find MESSAGE-INTEGRITY in the message!");
+ return PJ_EBUG;
+ }
+
+ /* Determine which key to use */
+ if (realm.slen) {
+ pj_stun_calc_md5_key(md5_digest, &realm, &auser->value, &password);
+ key.ptr = (char*)md5_digest;
+ key.slen = 16;
+ } else {
+ key = password;
+ }
+
+ /* Now calculate HMAC of the message */
+ pj_hmac_sha1(pkt, amsgi_pos, (pj_uint8_t*)key.ptr, key.slen, digest);
+
+ /* Compare HMACs */
+ if (pj_memcmp(amsgi->hmac, digest, 20)) {
+ /* HMAC value mismatch */
+ if (p_response) {
+ create_challenge(pool, msg, PJ_STUN_SC_INTEGRITY_CHECK_FAILURE,
+ NULL, &realm, &nonce, p_response);
+ }
+ return PJLIB_UTIL_ESTUNMSGINT;
+ }
+
+ /* Everything looks okay! */
+ return PJ_SUCCESS;
+}
+
+
diff --git a/pjnath/src/pjnath/stun_endpoint.c b/pjnath/src/pjnath/stun_endpoint.c
new file mode 100644
index 00000000..277e385e
--- /dev/null
+++ b/pjnath/src/pjnath/stun_endpoint.c
@@ -0,0 +1,69 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjlib-util/stun_endpoint.h>
+#include <pjlib-util/errno.h>
+#include <pj/assert.h>
+#include <pj/pool.h>
+
+
+/*
+ * Create a STUN endpoint instance.
+ */
+PJ_DEF(pj_status_t) pj_stun_endpoint_create( pj_pool_factory *factory,
+ unsigned options,
+ pj_ioqueue_t *ioqueue,
+ pj_timer_heap_t *timer_heap,
+ pj_stun_endpoint **p_endpt)
+{
+ pj_pool_t *pool;
+ pj_stun_endpoint *endpt;
+
+ PJ_ASSERT_RETURN(factory && p_endpt, PJ_EINVAL);
+
+ pool = pj_pool_create(factory, "stunendpt", 1000, 1000, NULL);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ endpt = PJ_POOL_ZALLOC_T(pool, pj_stun_endpoint);
+ endpt->pool = pool;
+ endpt->pf = factory;
+ endpt->options = options;
+ endpt->ioqueue = ioqueue;
+ endpt->timer_heap = timer_heap;
+ endpt->rto_msec = PJ_STUN_RTO_VALUE;
+ endpt->res_cache_msec = 10000;
+
+ *p_endpt = endpt;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy STUN endpoint instance.
+ */
+PJ_DEF(pj_status_t) pj_stun_endpoint_destroy(pj_stun_endpoint *endpt)
+{
+ PJ_ASSERT_RETURN(endpt, PJ_EINVAL);
+
+ pj_pool_release(endpt->pool);
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjnath/src/pjnath/stun_msg.c b/pjnath/src/pjnath/stun_msg.c
new file mode 100644
index 00000000..1c750d8e
--- /dev/null
+++ b/pjnath/src/pjnath/stun_msg.c
@@ -0,0 +1,2162 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjlib-util/stun_msg.h>
+#include <pjlib-util/crc32.h>
+#include <pjlib-util/errno.h>
+#include <pjlib-util/hmac_sha1.h>
+#include <pjlib-util/md5.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/rand.h>
+#include <pj/string.h>
+
+#define THIS_FILE "stun_msg.c"
+#define STUN_XOR_FINGERPRINT 0x5354554eL
+
+static const char *stun_method_names[] =
+{
+ "Unknown", /* 0 */
+ "Binding", /* 1 */
+ "Shared Secret", /* 2 */
+ "Allocate", /* 3 */
+ "Send", /* 4 */
+ "Data", /* 5 */
+ "Set Active Destination", /* 6 */
+ "Connect", /* 7 */
+ "Connect Status" /* 8 */
+};
+
+static struct
+{
+ int err_code;
+ const char *err_msg;
+} stun_err_msg_map[] =
+{
+ { PJ_STUN_SC_TRY_ALTERNATE, "Try Alternate"},
+ { PJ_STUN_SC_BAD_REQUEST, "Bad Request"},
+ { PJ_STUN_SC_UNAUTHORIZED, "Unauthorized"},
+ { PJ_STUN_SC_UNKNOWN_ATTRIBUTE, "Unknown Attribute"},
+ { PJ_STUN_SC_STALE_CREDENTIALS, "Stale Credentials"},
+ { PJ_STUN_SC_INTEGRITY_CHECK_FAILURE, "Integrity Check Failure"},
+ { PJ_STUN_SC_MISSING_USERNAME, "Missing Username"},
+ { PJ_STUN_SC_USE_TLS, "Use TLS"},
+ { PJ_STUN_SC_MISSING_REALM, "Missing Realm"},
+ { PJ_STUN_SC_MISSING_NONCE, "Missing Nonce"},
+ { PJ_STUN_SC_UNKNOWN_USERNAME, "Unknown Username"},
+ { PJ_STUN_SC_NO_BINDING, "No Binding"},
+ { PJ_STUN_SC_STALE_NONCE, "Stale Nonce"},
+ { PJ_STUN_SC_TRANSITIONING, "Active Destination Already Set"},
+ { PJ_STUN_SC_WRONG_USERNAME, "Wrong Username"},
+ { PJ_STUN_SC_UNSUPP_TRANSPORT_PROTO, "Unsupported Transport Protocol"},
+ { PJ_STUN_SC_INVALID_IP_ADDR, "Invalid IP Address"},
+ { PJ_STUN_SC_INVALID_PORT, "Invalid Port"},
+ { PJ_STUN_SC_OPER_TCP_ONLY, "Operation for TCP Only"},
+ { PJ_STUN_SC_CONNECTION_FAILURE, "Connection Failure"},
+ { PJ_STUN_SC_CONNECTION_TIMEOUT, "Connection Timeout"},
+ { PJ_STUN_SC_ALLOCATION_QUOTA_REACHED, "Allocation Quota Reached"},
+ { PJ_STUN_SC_SERVER_ERROR, "Server Error"},
+ { PJ_STUN_SC_INSUFFICIENT_CAPACITY, "Insufficient Capacity"},
+ { PJ_STUN_SC_GLOBAL_FAILURE, "Global Failure"}
+};
+
+
+
+struct attr_desc
+{
+ const char *name;
+ pj_status_t (*decode_attr)(pj_pool_t *pool, const pj_uint8_t *buf,
+ void **p_attr);
+ pj_status_t (*encode_attr)(const void *a, pj_uint8_t *buf,
+ unsigned len, unsigned *printed);
+
+};
+
+static pj_status_t decode_sockaddr_attr(pj_pool_t *pool,
+ const pj_uint8_t *buf,
+ void **p_attr);
+static pj_status_t decode_xored_sockaddr_attr(pj_pool_t *pool,
+ const pj_uint8_t *buf,
+ void **p_attr);
+static pj_status_t encode_sockaddr_attr(const void *a, pj_uint8_t *buf,
+ unsigned len,
+ unsigned *printed);
+static pj_status_t decode_string_attr(pj_pool_t *pool,
+ const pj_uint8_t *buf,
+ void **p_attr);
+static pj_status_t encode_string_attr(const void *a, pj_uint8_t *buf,
+ unsigned len, unsigned *printed);
+static pj_status_t decode_msgint_attr(pj_pool_t *pool,
+ const pj_uint8_t *buf,
+ void **p_attr);
+static pj_status_t encode_msgint_attr(const void *a, pj_uint8_t *buf,
+ unsigned len, unsigned *printed);
+static pj_status_t decode_errcode_attr(pj_pool_t *pool,
+ const pj_uint8_t *buf,
+ void **p_attr);
+static pj_status_t encode_errcode_attr(const void *a, pj_uint8_t *buf,
+ unsigned len, unsigned *printed);
+static pj_status_t decode_unknown_attr(pj_pool_t *pool,
+ const pj_uint8_t *buf,
+ void **p_attr);
+static pj_status_t encode_unknown_attr(const void *a, pj_uint8_t *buf,
+ unsigned len, unsigned *printed);
+static pj_status_t decode_uint_attr(pj_pool_t *pool,
+ const pj_uint8_t *buf,
+ void **p_attr);
+static pj_status_t encode_uint_attr(const void *a, pj_uint8_t *buf,
+ unsigned len, unsigned *printed);
+static pj_status_t decode_binary_attr(pj_pool_t *pool,
+ const pj_uint8_t *buf,
+ void **p_attr);
+static pj_status_t encode_binary_attr(const void *a, pj_uint8_t *buf,
+ unsigned len, unsigned *printed);
+static pj_status_t decode_empty_attr(pj_pool_t *pool,
+ const pj_uint8_t *buf,
+ void **p_attr);
+static pj_status_t encode_empty_attr(const void *a, pj_uint8_t *buf,
+ unsigned len, unsigned *printed);
+
+
+struct attr_desc mandatory_attr_desc[] =
+{
+ {
+ /* type zero */
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ /* PJ_STUN_ATTR_MAPPED_ADDR, */
+ "MAPPED-ADDRESS",
+ &decode_sockaddr_attr,
+ &encode_sockaddr_attr
+ },
+ {
+ /* PJ_STUN_ATTR_RESPONSE_ADDR, */
+ "RESPONSE-ADDRESS",
+ &decode_sockaddr_attr,
+ &encode_sockaddr_attr
+ },
+ {
+ /* PJ_STUN_ATTR_CHANGE_REQUEST, */
+ "CHANGE-REQUEST",
+ &decode_uint_attr,
+ &encode_uint_attr
+ },
+ {
+ /* PJ_STUN_ATTR_SOURCE_ADDR, */
+ "SOURCE-ADDRESS",
+ &decode_sockaddr_attr,
+ &encode_sockaddr_attr
+ },
+ {
+ /* PJ_STUN_ATTR_CHANGED_ADDR, */
+ "CHANGED-ADDRESS",
+ &decode_sockaddr_attr,
+ &encode_sockaddr_attr
+ },
+ {
+ /* PJ_STUN_ATTR_USERNAME, */
+ "USERNAME",
+ &decode_string_attr,
+ &encode_string_attr
+ },
+ {
+ /* PJ_STUN_ATTR_PASSWORD, */
+ "PASSWORD",
+ &decode_string_attr,
+ &encode_string_attr
+ },
+ {
+ /* PJ_STUN_ATTR_MESSAGE_INTEGRITY, */
+ "MESSAGE-INTEGRITY",
+ &decode_msgint_attr,
+ &encode_msgint_attr
+ },
+ {
+ /* PJ_STUN_ATTR_ERROR_CODE, */
+ "ERROR-CODE",
+ &decode_errcode_attr,
+ &encode_errcode_attr
+ },
+ {
+ /* PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES, */
+ "UNKNOWN-ATTRIBUTES",
+ &decode_unknown_attr,
+ &encode_unknown_attr
+ },
+ {
+ /* PJ_STUN_ATTR_REFLECTED_FROM, */
+ "REFLECTED-FROM",
+ &decode_sockaddr_attr,
+ &encode_sockaddr_attr
+ },
+ {
+ /* ID 0x000C is not assigned */
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ /* PJ_STUN_ATTR_LIFETIME, */
+ "LIFETIME",
+ &decode_uint_attr,
+ &encode_uint_attr
+ },
+ {
+ /* ID 0x000E is not assigned */
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ /* ID 0x000F is not assigned */
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ /* PJ_STUN_ATTR_BANDWIDTH, */
+ "BANDWIDTH",
+ &decode_uint_attr,
+ &encode_uint_attr
+ },
+ {
+ /* ID 0x0011 is not assigned */
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ /* PJ_STUN_ATTR_REMOTE_ADDRESS, */
+ "REMOTE-ADDRESS",
+ &decode_sockaddr_attr,
+ &encode_sockaddr_attr
+ },
+ {
+ /* PJ_STUN_ATTR_DATA, */
+ "DATA",
+ &decode_binary_attr,
+ &encode_binary_attr
+ },
+ {
+ /* PJ_STUN_ATTR_REALM, */
+ "REALM",
+ &decode_string_attr,
+ &encode_string_attr
+ },
+ {
+ /* PJ_STUN_ATTR_NONCE, */
+ "NONCE",
+ &decode_string_attr,
+ &encode_string_attr
+ },
+ {
+ /* PJ_STUN_ATTR_RELAY_ADDRESS, */
+ "RELAY-ADDRESS",
+ &decode_sockaddr_attr,
+ &encode_sockaddr_attr
+ },
+ {
+ /* PJ_STUN_ATTR_REQUESTED_ADDR_TYPE, */
+ "REQUESTED-ADDRESS-TYPE",
+ &decode_uint_attr,
+ &encode_uint_attr
+ },
+ {
+ /* PJ_STUN_ATTR_REQUESTED_PORT_PROPS, */
+ "REQUESTED-PORT-PROPS",
+ &decode_uint_attr,
+ &encode_uint_attr
+ },
+ {
+ /* PJ_STUN_ATTR_REQUESTED_TRANSPORT, */
+ "REQUESTED-TRANSPORT",
+ &decode_uint_attr,
+ &encode_uint_attr
+ },
+ {
+ /* ID 0x001A is not assigned */
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ /* ID 0x001B is not assigned */
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ /* ID 0x001C is not assigned */
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ /* ID 0x001D is not assigned */
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ /* ID 0x001E is not assigned */
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ /* ID 0x001F is not assigned */
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ /* PJ_STUN_ATTR_XOR_MAPPED_ADDRESS, */
+ "XOR-MAPPED-ADDRESS",
+ &decode_xored_sockaddr_attr,
+ &encode_sockaddr_attr
+ },
+ {
+ /* PJ_STUN_ATTR_TIMER_VAL, */
+ "TIMER-VAL",
+ &decode_uint_attr,
+ &encode_uint_attr
+ },
+ {
+ /* PJ_STUN_ATTR_REQUESTED_IP, */
+ "REQUESTED-IP",
+ &decode_sockaddr_attr,
+ &encode_sockaddr_attr
+ },
+ {
+ /* PJ_STUN_ATTR_XOR_REFLECTED_FROM, */
+ "XOR-REFLECTED-FROM",
+ &decode_xored_sockaddr_attr,
+ &encode_sockaddr_attr
+ },
+ {
+ /* PJ_STUN_ATTR_PRIORITY, */
+ "PRIORITY",
+ &decode_uint_attr,
+ &encode_uint_attr
+ },
+ {
+ /* PJ_STUN_ATTR_USE_CANDIDATE, */
+ "USE-CANDIDATE",
+ &decode_empty_attr,
+ &encode_empty_attr
+ },
+ {
+ /* PJ_STUN_ATTR_XOR_INTERNAL_ADDR, */
+ "XOR-INTERNAL-ADDRESS",
+ &decode_xored_sockaddr_attr,
+ &encode_sockaddr_attr
+ },
+
+ /* Sentinel */
+ {
+ /* PJ_STUN_ATTR_END_MANDATORY_ATTR */
+ NULL,
+ NULL,
+ NULL
+ }
+};
+
+static struct attr_desc extended_attr_desc[] =
+{
+ {
+ /* ID 0x8021 is not assigned */
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ /* PJ_STUN_ATTR_SERVER, */
+ "SERVER",
+ &decode_string_attr,
+ &encode_string_attr
+ },
+ {
+ /* PJ_STUN_ATTR_ALTERNATE_SERVER, */
+ "ALTERNATE-SERVER",
+ &decode_sockaddr_attr,
+ &encode_sockaddr_attr
+ },
+ {
+ /* PJ_STUN_ATTR_REFRESH_INTERVAL, */
+ "REFRESH-INTERVAL",
+ &decode_uint_attr,
+ &encode_uint_attr
+ },
+ {
+ /* ID 0x8025 is not assigned*/
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ /* PADDING, 0x8026 */
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ /* CACHE-TIMEOUT, 0x8027 */
+ NULL,
+ NULL,
+ NULL
+ },
+ {
+ /* PJ_STUN_ATTR_FINGERPRINT, */
+ "FINGERPRINT",
+ &decode_uint_attr,
+ &encode_uint_attr
+ }
+};
+
+
+
+/*
+ * Get STUN message type name.
+ */
+PJ_DEF(const char*) pj_stun_get_method_name(unsigned msg_type)
+{
+ unsigned method = PJ_STUN_GET_METHOD(msg_type);
+
+ if (method >= PJ_ARRAY_SIZE(stun_method_names))
+ return "???";
+
+ return stun_method_names[method];
+}
+
+
+/*
+ * Get STUN message class name.
+ */
+PJ_DEF(const char*) pj_stun_get_class_name(unsigned msg_type)
+{
+ if (PJ_STUN_IS_REQUEST(msg_type))
+ return "request";
+ else if (PJ_STUN_IS_RESPONSE(msg_type))
+ return "success response";
+ else if (PJ_STUN_IS_ERROR_RESPONSE(msg_type))
+ return "error response";
+ else if (PJ_STUN_IS_INDICATION(msg_type))
+ return "indication";
+ else
+ return "???";
+}
+
+
+static const struct attr_desc *find_attr_desc(unsigned attr_type)
+{
+ struct attr_desc *desc;
+
+ /* Check that attr_desc array is valid */
+ pj_assert(PJ_ARRAY_SIZE(mandatory_attr_desc)==
+ PJ_STUN_ATTR_END_MANDATORY_ATTR+1);
+ pj_assert(mandatory_attr_desc[PJ_STUN_ATTR_END_MANDATORY_ATTR].decode_attr
+ == NULL);
+ pj_assert(mandatory_attr_desc[PJ_STUN_ATTR_USE_CANDIDATE].decode_attr
+ == &decode_empty_attr);
+ pj_assert(PJ_ARRAY_SIZE(extended_attr_desc) ==
+ PJ_STUN_ATTR_END_EXTENDED_ATTR-PJ_STUN_ATTR_START_EXTENDED_ATTR);
+
+ if (attr_type < PJ_STUN_ATTR_START_EXTENDED_ATTR)
+ desc = &mandatory_attr_desc[attr_type];
+ else if (attr_type >= PJ_STUN_ATTR_START_EXTENDED_ATTR &&
+ attr_type < PJ_STUN_ATTR_END_EXTENDED_ATTR)
+ desc = &extended_attr_desc[attr_type-PJ_STUN_ATTR_START_EXTENDED_ATTR];
+ else
+ return NULL;
+
+ return desc->decode_attr == NULL ? NULL : desc;
+}
+
+
+/*
+ * Get STUN attribute name.
+ */
+PJ_DEF(const char*) pj_stun_get_attr_name(unsigned attr_type)
+{
+ const struct attr_desc *attr_desc;
+
+ attr_desc = find_attr_desc(attr_type);
+ if (!attr_desc || attr_desc->name==NULL)
+ return "???";
+
+ return attr_desc->name;
+}
+
+
+/**
+ * Get STUN standard reason phrase for the specified error code.
+ */
+PJ_DEF(pj_str_t) pj_stun_get_err_reason(int err_code)
+{
+ unsigned i;
+
+ for (i=0; i<PJ_ARRAY_SIZE(stun_err_msg_map); ++i) {
+ if (stun_err_msg_map[i].err_code == err_code)
+ return pj_str((char*)stun_err_msg_map[i].err_msg);
+ }
+ return pj_str(NULL);
+}
+
+
+
+#define INIT_ATTR(a,t,l) (a)->hdr.type=(pj_uint16_t)(t), \
+ (a)->hdr.length=(pj_uint16_t)(l)
+#define ATTR_HDR_LEN 4
+
+#define getval16(p, pos) (pj_uint16_t)(((p)[(pos)] << 8) | \
+ ((p)[(pos) + 1] << 0))
+
+
+//////////////////////////////////////////////////////////////////////////////
+/*
+ * STUN generic IP address container
+ */
+#define STUN_GENERIC_IP_ADDR_LEN 8
+
+/*
+ * Create a generic STUN IP address attribute for IPv4 address.
+ */
+PJ_DEF(pj_status_t)
+pj_stun_sockaddr_attr_create(pj_pool_t *pool,
+ int attr_type,
+ pj_bool_t xor_ed,
+ const pj_sockaddr_t *addr,
+ unsigned addr_len,
+ pj_stun_sockaddr_attr **p_attr)
+{
+ pj_stun_sockaddr_attr *attr;
+
+ PJ_ASSERT_RETURN(pool && addr_len && addr && p_attr, PJ_EINVAL);
+ PJ_ASSERT_RETURN(addr_len == sizeof(pj_sockaddr_in) ||
+ addr_len == sizeof(pj_sockaddr_in6), PJ_EINVAL);
+
+ attr = PJ_POOL_ZALLOC_T(pool, pj_stun_sockaddr_attr);
+ INIT_ATTR(attr, attr_type, STUN_GENERIC_IP_ADDR_LEN);
+
+ pj_memcpy(&attr->addr, addr, addr_len);
+ attr->xor_ed = xor_ed;
+
+ *p_attr = attr;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create and add generic STUN IP address attribute to a STUN message.
+ */
+PJ_DEF(pj_status_t)
+pj_stun_msg_add_sockaddr_attr(pj_pool_t *pool,
+ pj_stun_msg *msg,
+ int attr_type,
+ pj_bool_t xor_ed,
+ const pj_sockaddr_t *addr,
+ unsigned addr_len)
+{
+ pj_stun_sockaddr_attr *attr;
+ pj_status_t status;
+
+ status = pj_stun_sockaddr_attr_create(pool, attr_type, xor_ed,
+ addr, addr_len, &attr);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ return pj_stun_msg_add_attr(msg, &attr->hdr);
+}
+
+static pj_status_t decode_sockaddr_attr(pj_pool_t *pool,
+ const pj_uint8_t *buf,
+ void **p_attr)
+{
+ pj_stun_sockaddr_attr *attr;
+ pj_uint32_t val;
+
+ /* Create the attribute */
+ attr = PJ_POOL_ZALLOC_T(pool, pj_stun_sockaddr_attr);
+ pj_memcpy(attr, buf, ATTR_HDR_LEN);
+
+ /* Convert to host byte order */
+ attr->hdr.type = pj_ntohs(attr->hdr.type);
+ attr->hdr.length = pj_ntohs(attr->hdr.length);
+
+ /* Check that the attribute length is valid */
+ if (attr->hdr.length != STUN_GENERIC_IP_ADDR_LEN)
+ return PJLIB_UTIL_ESTUNINATTRLEN;
+
+ /* Check address family */
+ val = *(pj_uint8_t*)(buf + ATTR_HDR_LEN + 1);
+
+ /* Check address family is valid (only supports ipv4 for now) */
+ if (val != 1)
+ return PJLIB_UTIL_ESTUNIPV6NOTSUPP;
+
+ /* Get port and address */
+ pj_sockaddr_in_init(&attr->addr.ipv4, NULL, 0);
+ pj_memcpy(&attr->addr.ipv4.sin_port, buf+ATTR_HDR_LEN+2, 2);
+ pj_memcpy(&attr->addr.ipv4.sin_addr, buf+ATTR_HDR_LEN+4, 4);
+
+ /* Done */
+ *p_attr = attr;
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t decode_xored_sockaddr_attr(pj_pool_t *pool,
+ const pj_uint8_t *buf,
+ void **p_attr)
+{
+ pj_stun_sockaddr_attr *attr;
+ pj_uint32_t val;
+
+ /* Create the attribute */
+ attr = PJ_POOL_ZALLOC_T(pool, pj_stun_sockaddr_attr);
+ pj_memcpy(attr, buf, ATTR_HDR_LEN);
+
+ /* Convert to host byte order */
+ attr->hdr.type = pj_ntohs(attr->hdr.type);
+ attr->hdr.length = pj_ntohs(attr->hdr.length);
+
+ /* Check that the attribute length is valid */
+ if (attr->hdr.length != STUN_GENERIC_IP_ADDR_LEN)
+ return PJLIB_UTIL_ESTUNINATTRLEN;
+
+ /* Check address family */
+ val = *(pj_uint8_t*)(buf + ATTR_HDR_LEN + 1);
+
+ /* Check address family is valid (only supports ipv4 for now) */
+ if (val != 1)
+ return PJLIB_UTIL_ESTUNIPV6NOTSUPP;
+
+ /* Get port and address */
+ pj_sockaddr_in_init(&attr->addr.ipv4, NULL, 0);
+ pj_memcpy(&attr->addr.ipv4.sin_port, buf+ATTR_HDR_LEN+2, 2);
+ pj_memcpy(&attr->addr.ipv4.sin_addr, buf+ATTR_HDR_LEN+4, 4);
+
+ attr->addr.ipv4.sin_port ^= 0x2112;
+ attr->addr.ipv4.sin_addr.s_addr ^= pj_htonl(0x2112A442);
+
+ /* Done */
+ *p_attr = attr;
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t encode_sockaddr_attr(const void *a, pj_uint8_t *buf,
+ unsigned len, unsigned *printed)
+{
+ enum {
+ ATTR_LEN = ATTR_HDR_LEN + STUN_GENERIC_IP_ADDR_LEN
+ };
+ pj_uint8_t *start_buf = buf;
+ const pj_stun_sockaddr_attr *ca =
+ (const pj_stun_sockaddr_attr *)a;
+ pj_stun_sockaddr_attr *attr;
+
+ if (len < ATTR_LEN)
+ return PJ_ETOOSMALL;
+
+ /* Copy and convert headers to network byte order */
+ pj_memcpy(buf, a, ATTR_HDR_LEN);
+ attr = (pj_stun_sockaddr_attr*) buf;
+ attr->hdr.type = pj_htons(attr->hdr.type);
+ attr->hdr.length = pj_htons((pj_uint16_t)STUN_GENERIC_IP_ADDR_LEN);
+ buf += ATTR_HDR_LEN;
+
+ /* Ignored */
+ *buf++ = '\0';
+
+ /* Family (IPv4 only for now) */
+ PJ_ASSERT_RETURN(ca->addr.addr.sa_family == PJ_AF_INET, PJ_EINVAL);
+ *buf++ = 1;
+
+ if (ca->xor_ed) {
+ pj_uint32_t addr;
+ pj_uint16_t port;
+
+ addr = ca->addr.ipv4.sin_addr.s_addr;
+ port = ca->addr.ipv4.sin_port;
+
+ port ^= 0x2112;
+ addr ^= pj_htonl(0x2112A442);
+
+ /* Port */
+ pj_memcpy(buf, &port, 2);
+ buf += 2;
+
+ /* Address */
+ pj_memcpy(buf, &addr, 4);
+ buf += 4;
+
+ } else {
+ /* Port */
+ pj_memcpy(buf, &ca->addr.ipv4.sin_port, 2);
+ buf += 2;
+
+ /* Address */
+ pj_memcpy(buf, &ca->addr.ipv4.sin_addr, 4);
+ buf += 4;
+ }
+
+ pj_assert(buf - start_buf == ATTR_LEN);
+
+ /* Done */
+ *printed = buf - start_buf;
+
+ return PJ_SUCCESS;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+/*
+ * STUN generic string attribute
+ */
+
+/*
+ * Create a STUN generic string attribute.
+ */
+PJ_DEF(pj_status_t)
+pj_stun_string_attr_create(pj_pool_t *pool,
+ int attr_type,
+ const pj_str_t *value,
+ pj_stun_string_attr **p_attr)
+{
+ pj_stun_string_attr *attr;
+
+ PJ_ASSERT_RETURN(pool && value && p_attr, PJ_EINVAL);
+
+ attr = PJ_POOL_ZALLOC_T(pool, pj_stun_string_attr);
+ INIT_ATTR(attr, attr_type, value->slen);
+ pj_strdup(pool, &attr->value, value);
+
+ *p_attr = attr;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create and add STUN generic string attribute to the message.
+ */
+PJ_DEF(pj_status_t)
+pj_stun_msg_add_string_attr(pj_pool_t *pool,
+ pj_stun_msg *msg,
+ int attr_type,
+ const pj_str_t *value)
+{
+ pj_stun_string_attr *attr = NULL;
+ pj_status_t status;
+
+ status = pj_stun_string_attr_create(pool, attr_type, value,
+ &attr);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ return pj_stun_msg_add_attr(msg, &attr->hdr);
+}
+
+
+static pj_status_t decode_string_attr(pj_pool_t *pool,
+ const pj_uint8_t *buf,
+ void **p_attr)
+{
+ pj_stun_string_attr *attr;
+ pj_str_t value;
+
+ /* Create the attribute */
+ attr = PJ_POOL_ZALLOC_T(pool, pj_stun_string_attr);
+
+ /* Copy the header */
+ pj_memcpy(attr, buf, ATTR_HDR_LEN);
+
+ /* Convert to host byte order */
+ attr->hdr.type = pj_ntohs(attr->hdr.type);
+ attr->hdr.length = pj_ntohs(attr->hdr.length);
+
+ /* Get pointer to the string in the message */
+ value.ptr = ((char*)buf + ATTR_HDR_LEN);
+ value.slen = attr->hdr.length;
+
+ /* Copy the string to the attribute */
+ pj_strdup(pool, &attr->value, &value);
+
+ /* Done */
+ *p_attr = attr;
+
+ return PJ_SUCCESS;
+
+}
+
+
+static pj_status_t encode_string_attr(const void *a, pj_uint8_t *buf,
+ unsigned len, unsigned *printed)
+{
+ const pj_stun_string_attr *ca =
+ (const pj_stun_string_attr*)a;
+ pj_stun_attr_hdr *attr;
+
+ /* Calculated total attr_len (add padding if necessary) */
+ *printed = (ca->value.slen + ATTR_HDR_LEN + 3) & (~3);
+ if (len < *printed) {
+ *printed = 0;
+ return PJ_ETOOSMALL;
+ }
+
+ /* Copy header */
+ pj_memcpy(buf, a, ATTR_HDR_LEN);
+ attr = (pj_stun_attr_hdr*)buf;
+
+ /* Set the correct length */
+ attr->length = (pj_uint16_t) ca->value.slen;
+
+ /* Convert to network byte order */
+ attr->type = pj_htons(attr->type);
+ attr->length = pj_htons(attr->length);
+
+ /* Copy the string */
+ pj_memcpy(buf+ATTR_HDR_LEN, ca->value.ptr, ca->value.slen);
+
+ /* Done */
+ return PJ_SUCCESS;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+/*
+ * STUN empty attribute (used by USE-CANDIDATE).
+ */
+
+/*
+ * Create a STUN empty attribute.
+ */
+PJ_DEF(pj_status_t)
+pj_stun_empty_attr_create(pj_pool_t *pool,
+ int attr_type,
+ pj_stun_empty_attr **p_attr)
+{
+ pj_stun_empty_attr *attr;
+
+ PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL);
+
+ attr = PJ_POOL_ZALLOC_T(pool, pj_stun_empty_attr);
+ INIT_ATTR(attr, attr_type, sizeof(pj_stun_empty_attr));
+
+ *p_attr = attr;
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t decode_empty_attr(pj_pool_t *pool,
+ const pj_uint8_t *buf,
+ void **p_attr)
+{
+ pj_stun_empty_attr *attr;
+
+ /* Check that the struct address is valid */
+ pj_assert(sizeof(pj_stun_empty_attr) == ATTR_HDR_LEN);
+
+ /* Create the attribute */
+ attr = PJ_POOL_ZALLOC_T(pool, pj_stun_empty_attr);
+ pj_memcpy(attr, buf, ATTR_HDR_LEN);
+
+ /* Convert to host byte order */
+ attr->hdr.type = pj_ntohs(attr->hdr.type);
+ attr->hdr.length = pj_ntohs(attr->hdr.length);
+
+ /* Check that the attribute length is valid */
+ if (attr->hdr.length != ATTR_HDR_LEN)
+ return PJLIB_UTIL_ESTUNINATTRLEN;
+
+ /* Done */
+ *p_attr = attr;
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t encode_empty_attr(const void *a, pj_uint8_t *buf,
+ unsigned len, unsigned *printed)
+{
+ pj_stun_empty_attr *attr;
+
+ if (len < ATTR_HDR_LEN)
+ return PJ_ETOOSMALL;
+
+ /* Copy and convert attribute to network byte order */
+ pj_memcpy(buf, a, ATTR_HDR_LEN);
+ attr = (pj_stun_empty_attr*) buf;
+ attr->hdr.type = pj_htons(attr->hdr.type);
+ pj_assert(attr->hdr.length == ATTR_HDR_LEN);
+ attr->hdr.length = pj_htons(ATTR_HDR_LEN);
+
+ /* Done */
+ *printed = ATTR_HDR_LEN;
+
+ return PJ_SUCCESS;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+/*
+ * STUN generic 32bit integer attribute.
+ */
+#define STUN_UINT_LEN 4
+
+/*
+ * Create a STUN generic 32bit value attribute.
+ */
+PJ_DEF(pj_status_t)
+pj_stun_uint_attr_create(pj_pool_t *pool,
+ int attr_type,
+ pj_uint32_t value,
+ pj_stun_uint_attr **p_attr)
+{
+ pj_stun_uint_attr *attr;
+
+ PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL);
+
+ attr = PJ_POOL_ZALLOC_T(pool, pj_stun_uint_attr);
+ INIT_ATTR(attr, attr_type, STUN_UINT_LEN);
+ attr->value = value;
+
+ *p_attr = attr;
+
+ return PJ_SUCCESS;
+}
+
+/* Create and add STUN generic 32bit value attribute to the message. */
+PJ_DEF(pj_status_t)
+pj_stun_msg_add_uint_attr(pj_pool_t *pool,
+ pj_stun_msg *msg,
+ int attr_type,
+ pj_uint32_t value)
+{
+ pj_stun_uint_attr *attr = NULL;
+ pj_status_t status;
+
+ status = pj_stun_uint_attr_create(pool, attr_type, value, &attr);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ return pj_stun_msg_add_attr(msg, &attr->hdr);
+}
+
+static pj_status_t decode_uint_attr(pj_pool_t *pool,
+ const pj_uint8_t *buf,
+ void **p_attr)
+{
+ enum
+ {
+ ATTR_LEN = STUN_UINT_LEN + ATTR_HDR_LEN
+ };
+ pj_stun_uint_attr *attr;
+
+ /* Check that the struct address is valid */
+ pj_assert(sizeof(pj_stun_uint_attr) == ATTR_LEN);
+
+ /* Create the attribute */
+ attr = PJ_POOL_ZALLOC_T(pool, pj_stun_uint_attr);
+ pj_memcpy(attr, buf, ATTR_LEN);
+
+ /* Convert to host byte order */
+ attr->hdr.type = pj_ntohs(attr->hdr.type);
+ attr->hdr.length = pj_ntohs(attr->hdr.length);
+ attr->value = pj_ntohl(attr->value);
+
+ /* Check that the attribute length is valid */
+ if (attr->hdr.length != STUN_UINT_LEN)
+ return PJLIB_UTIL_ESTUNINATTRLEN;
+
+ /* Done */
+ *p_attr = attr;
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t encode_uint_attr(const void *a, pj_uint8_t *buf,
+ unsigned len, unsigned *printed)
+{
+ enum
+ {
+ ATTR_LEN = STUN_UINT_LEN + ATTR_HDR_LEN
+ };
+ pj_stun_uint_attr *attr;
+
+ if (len < ATTR_LEN)
+ return PJ_ETOOSMALL;
+
+ /* Copy and convert attribute to network byte order */
+ pj_memcpy(buf, a, ATTR_LEN);
+ attr = (pj_stun_uint_attr*) buf;
+ attr->hdr.type = pj_htons(attr->hdr.type);
+ pj_assert(attr->hdr.length == STUN_UINT_LEN);
+ attr->hdr.length = pj_htons(STUN_UINT_LEN);
+ attr->value = pj_htonl(attr->value);
+
+ /* Done */
+ *printed = ATTR_LEN;
+
+ return PJ_SUCCESS;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+/*
+ * STUN MESSAGE-INTEGRITY attribute.
+ */
+
+#define STUN_MSG_INTEGRITY_LEN 20
+
+/*
+ * Create a STUN MESSAGE-INTEGRITY attribute.
+ */
+PJ_DEF(pj_status_t)
+pj_stun_msgint_attr_create(pj_pool_t *pool,
+ pj_stun_msgint_attr **p_attr)
+{
+ pj_stun_msgint_attr *attr;
+
+ PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL);
+
+ attr = PJ_POOL_ZALLOC_T(pool, pj_stun_msgint_attr);
+ INIT_ATTR(attr, PJ_STUN_ATTR_MESSAGE_INTEGRITY, STUN_MSG_INTEGRITY_LEN);
+
+ *p_attr = attr;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pj_stun_msg_add_msgint_attr(pj_pool_t *pool,
+ pj_stun_msg *msg)
+{
+ pj_stun_msgint_attr *attr = NULL;
+ pj_status_t status;
+
+ status = pj_stun_msgint_attr_create(pool, &attr);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ return pj_stun_msg_add_attr(msg, &attr->hdr);
+}
+
+static pj_status_t decode_msgint_attr(pj_pool_t *pool,
+ const pj_uint8_t *buf,
+ void **p_attr)
+{
+ enum
+ {
+ ATTR_LEN = STUN_MSG_INTEGRITY_LEN + ATTR_HDR_LEN
+ };
+ pj_stun_msgint_attr *attr;
+
+ /* Check that struct size is valid */
+ pj_assert(sizeof(pj_stun_msgint_attr)==ATTR_LEN);
+
+ /* Create attribute */
+ attr = PJ_POOL_ZALLOC_T(pool, pj_stun_msgint_attr);
+ pj_memcpy(attr, buf, sizeof(pj_stun_msgint_attr));
+ attr->hdr.type = pj_ntohs(attr->hdr.type);
+ attr->hdr.length = pj_ntohs(attr->hdr.length);
+
+ /* Check that the attribute length is valid */
+ if (attr->hdr.length != STUN_MSG_INTEGRITY_LEN)
+ return PJLIB_UTIL_ESTUNINATTRLEN;
+
+ /* Done */
+ *p_attr = attr;
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t encode_msgint_attr(const void *a, pj_uint8_t *buf,
+ unsigned len, unsigned *printed)
+{
+ enum
+ {
+ ATTR_LEN = STUN_MSG_INTEGRITY_LEN + ATTR_HDR_LEN
+ };
+ pj_stun_msgint_attr *attr;
+
+ if (len < ATTR_LEN)
+ return PJ_ETOOSMALL;
+
+ /* Copy and convert attribute to network byte order */
+ pj_memcpy(buf, a, ATTR_LEN);
+ attr = (pj_stun_msgint_attr*) buf;
+ attr->hdr.type = pj_htons(attr->hdr.type);
+ pj_assert(attr->hdr.length == STUN_MSG_INTEGRITY_LEN);
+ attr->hdr.length = pj_htons(STUN_MSG_INTEGRITY_LEN);
+
+ /* Done */
+ *printed = ATTR_LEN;
+
+ return PJ_SUCCESS;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+/*
+ * STUN ERROR-CODE
+ */
+
+/*
+ * Create a STUN ERROR-CODE attribute.
+ */
+PJ_DEF(pj_status_t)
+pj_stun_errcode_attr_create(pj_pool_t *pool,
+ int err_code,
+ const pj_str_t *err_reason,
+ pj_stun_errcode_attr **p_attr)
+{
+ pj_stun_errcode_attr *attr;
+ char err_buf[80];
+ pj_str_t str;
+
+ PJ_ASSERT_RETURN(pool && err_code && p_attr, PJ_EINVAL);
+
+ if (err_reason == NULL) {
+ str = pj_stun_get_err_reason(err_code);
+ if (str.slen == 0) {
+ str.slen = pj_ansi_snprintf(err_buf, sizeof(err_buf),
+ "Unknown error %d", err_code);
+ str.ptr = err_buf;
+ }
+ err_reason = &str;
+ }
+
+ attr = PJ_POOL_ZALLOC_T(pool, pj_stun_errcode_attr);
+ INIT_ATTR(attr, PJ_STUN_ATTR_ERROR_CODE, 4+err_reason->slen);
+ attr->err_class = (pj_uint8_t)(err_code / 100);
+ attr->number = (pj_uint8_t) (err_code % 100);
+ pj_strdup(pool, &attr->reason, err_reason);
+
+ *p_attr = attr;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pj_stun_msg_add_errcode_attr(pj_pool_t *pool,
+ pj_stun_msg *msg,
+ int err_code,
+ const pj_str_t *err_reason)
+{
+ pj_stun_errcode_attr *err_attr = NULL;
+ pj_status_t status;
+
+ status = pj_stun_errcode_attr_create(pool, err_code, err_reason,
+ &err_attr);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ return pj_stun_msg_add_attr(msg, &err_attr->hdr);
+}
+
+static pj_status_t decode_errcode_attr(pj_pool_t *pool,
+ const pj_uint8_t *buf,
+ void **p_attr)
+{
+ pj_stun_errcode_attr *attr;
+ pj_str_t value;
+
+ /* Create the attribute */
+ attr = PJ_POOL_ZALLOC_T(pool, pj_stun_errcode_attr);
+
+ /* Copy the header */
+ pj_memcpy(attr, buf, ATTR_HDR_LEN + 4);
+
+ /* Convert to host byte order */
+ attr->hdr.type = pj_ntohs(attr->hdr.type);
+ attr->hdr.length = pj_ntohs(attr->hdr.length);
+
+ /* Get pointer to the string in the message */
+ value.ptr = ((char*)buf + ATTR_HDR_LEN + 4);
+ value.slen = attr->hdr.length - 4;
+
+ /* Copy the string to the attribute */
+ pj_strdup(pool, &attr->reason, &value);
+
+ /* Done */
+ *p_attr = attr;
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t encode_errcode_attr(const void *a, pj_uint8_t *buf,
+ unsigned len, unsigned *printed)
+{
+ const pj_stun_errcode_attr *ca =
+ (const pj_stun_errcode_attr*)a;
+ pj_stun_errcode_attr *attr;
+
+ if (len < ATTR_HDR_LEN + 4 + (unsigned)ca->reason.slen)
+ return PJ_ETOOSMALL;
+
+ /* Copy and convert attribute to network byte order */
+ pj_memcpy(buf, ca, ATTR_HDR_LEN + 4);
+
+ /* Update length */
+ attr = (pj_stun_errcode_attr*) buf;
+ attr->hdr.length = (pj_uint16_t)(4 + ca->reason.slen);
+
+ /* Convert fiends to network byte order */
+ attr->hdr.type = pj_htons(attr->hdr.type);
+ attr->hdr.length = pj_htons(attr->hdr.length);
+
+ /* Copy error string */
+ pj_memcpy(buf + ATTR_HDR_LEN + 4, ca->reason.ptr, ca->reason.slen);
+
+ /* Done */
+ *printed = (ATTR_HDR_LEN + 4 + ca->reason.slen + 3) & (~3);
+
+ return PJ_SUCCESS;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+/*
+ * STUN UNKNOWN-ATTRIBUTES attribute
+ */
+
+/*
+ * Create an empty instance of STUN UNKNOWN-ATTRIBUTES attribute.
+ *
+ * @param pool The pool to allocate memory from.
+ * @param p_attr Pointer to receive the attribute.
+ *
+ * @return PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DEF(pj_status_t)
+pj_stun_unknown_attr_create(pj_pool_t *pool,
+ unsigned attr_cnt,
+ const pj_uint16_t attr_array[],
+ pj_stun_unknown_attr **p_attr)
+{
+ pj_stun_unknown_attr *attr;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(pool && attr_cnt < PJ_STUN_MAX_ATTR && p_attr, PJ_EINVAL);
+
+ attr = PJ_POOL_ZALLOC_T(pool, pj_stun_unknown_attr);
+ INIT_ATTR(attr, PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES, attr_cnt * 2);
+
+ attr->attr_count = attr_cnt;
+ for (i=0; i<attr_cnt; ++i) {
+ attr->attrs[i] = attr_array[i];
+ }
+
+ /* If the number of unknown attributes is an odd number, one of the
+ * attributes MUST be repeated in the list.
+ */
+ if ((attr_cnt & 0x01)) {
+ attr->attrs[attr_cnt] = attr_array[attr_cnt-1];
+ }
+
+ *p_attr = NULL;
+
+ return PJ_SUCCESS;
+}
+
+
+/* Create and add STUN UNKNOWN-ATTRIBUTES attribute to the message. */
+PJ_DEF(pj_status_t)
+pj_stun_msg_add_unknown_attr(pj_pool_t *pool,
+ pj_stun_msg *msg,
+ unsigned attr_cnt,
+ const pj_uint16_t attr_types[])
+{
+ pj_stun_unknown_attr *attr = NULL;
+ pj_status_t status;
+
+ status = pj_stun_unknown_attr_create(pool, attr_cnt, attr_types, &attr);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ return pj_stun_msg_add_attr(msg, &attr->hdr);
+}
+
+static pj_status_t decode_unknown_attr(pj_pool_t *pool,
+ const pj_uint8_t *buf,
+ void **p_attr)
+{
+ pj_stun_unknown_attr *attr;
+ const pj_uint16_t *punk_attr;
+ unsigned i;
+
+ attr = PJ_POOL_ZALLOC_T(pool, pj_stun_unknown_attr);
+ pj_memcpy(attr, buf, ATTR_HDR_LEN);
+
+ attr->hdr.type = pj_ntohs(attr->hdr.type);
+ attr->hdr.length = pj_ntohs(attr->hdr.length);
+
+ attr->attr_count = (attr->hdr.length >> 1);
+ if (attr->attr_count > PJ_STUN_MAX_ATTR)
+ return PJ_ETOOMANY;
+
+ punk_attr = (const pj_uint16_t*)(buf + ATTR_HDR_LEN);
+ for (i=0; i<attr->attr_count; ++i) {
+ attr->attrs[i] = pj_ntohs(punk_attr[i]);
+ }
+
+ /* Done */
+ *p_attr = attr;
+
+ return PJ_SUCCESS;
+}
+
+
+static pj_status_t encode_unknown_attr(const void *a, pj_uint8_t *buf,
+ unsigned len, unsigned *printed)
+{
+ const pj_stun_unknown_attr *ca = (const pj_stun_unknown_attr*) a;
+ pj_stun_unknown_attr *attr;
+ pj_uint16_t *dst_unk_attr;
+ unsigned i;
+
+ /* Check that buffer is enough */
+ if (len < ATTR_HDR_LEN + (ca->attr_count << 1))
+ return PJ_ETOOSMALL;
+
+ /* Copy to message */
+ pj_memcpy(buf, ca, ATTR_HDR_LEN);
+
+ /* Set the correct length */
+ attr = (pj_stun_unknown_attr *) buf;
+ attr->hdr.length = (pj_uint16_t)(ca->attr_count << 1);
+
+ /* Convert to network byte order */
+ attr->hdr.type = pj_htons(attr->hdr.type);
+ attr->hdr.length = pj_htons(attr->hdr.length);
+
+ /* Copy individual attribute */
+ dst_unk_attr = (pj_uint16_t*)(buf + ATTR_HDR_LEN);
+ for (i=0; i < ca->attr_count; ++i, ++dst_unk_attr) {
+ *dst_unk_attr = pj_htons(attr->attrs[i]);
+ }
+
+ /* Done */
+ *printed = (ATTR_HDR_LEN + (ca->attr_count << 1) + 3) & (~3);
+
+ return PJ_SUCCESS;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+/*
+ * STUN generic binary attribute
+ */
+
+/*
+ * Create a blank binary attribute.
+ */
+PJ_DEF(pj_status_t)
+pj_stun_binary_attr_create(pj_pool_t *pool,
+ int attr_type,
+ const pj_uint8_t *data,
+ unsigned length,
+ pj_stun_binary_attr **p_attr)
+{
+ pj_stun_binary_attr *attr;
+
+ PJ_ASSERT_RETURN(pool && attr_type && p_attr, PJ_EINVAL);
+
+ attr = PJ_POOL_ZALLOC_T(pool, pj_stun_binary_attr);
+ INIT_ATTR(attr, attr_type, sizeof(pj_stun_binary_attr));
+
+ if (data && length) {
+ attr->length = length;
+ attr->data = pj_pool_alloc(pool, length);
+ pj_memcpy(attr->data, data, length);
+ }
+
+ *p_attr = attr;
+
+ return PJ_SUCCESS;
+}
+
+
+/* Create and add binary attr. */
+PJ_DEF(pj_status_t)
+pj_stun_msg_add_binary_attr(pj_pool_t *pool,
+ pj_stun_msg *msg,
+ int attr_type,
+ const pj_uint8_t *data,
+ unsigned length)
+{
+ pj_stun_binary_attr *attr = NULL;
+ pj_status_t status;
+
+ status = pj_stun_binary_attr_create(pool, attr_type,
+ data, length, &attr);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ return pj_stun_msg_add_attr(msg, &attr->hdr);
+}
+
+
+static pj_status_t decode_binary_attr(pj_pool_t *pool,
+ const pj_uint8_t *buf,
+ void **p_attr)
+{
+ pj_stun_binary_attr *attr;
+
+ /* Create the attribute */
+ attr = PJ_POOL_ZALLOC_T(pool, pj_stun_binary_attr);
+
+ /* Copy the header */
+ pj_memcpy(attr, buf, ATTR_HDR_LEN);
+
+ /* Convert to host byte order */
+ attr->hdr.type = pj_ntohs(attr->hdr.type);
+ attr->hdr.length = pj_ntohs(attr->hdr.length);
+
+ /* Copy the data to the attribute */
+ attr->length = attr->hdr.length;
+ attr->data = pj_pool_alloc(pool, attr->length);
+ pj_memcpy(attr->data, buf+ATTR_HDR_LEN, attr->length);
+
+ /* Done */
+ *p_attr = attr;
+
+ return PJ_SUCCESS;
+
+}
+
+
+static pj_status_t encode_binary_attr(const void *a, pj_uint8_t *buf,
+ unsigned len, unsigned *printed)
+{
+ const pj_stun_binary_attr *ca = (const pj_stun_binary_attr*)a;
+ pj_stun_attr_hdr *attr;
+
+ /* Calculated total attr_len (add padding if necessary) */
+ *printed = (ca->length + ATTR_HDR_LEN + 3) & (~3);
+ if (len < *printed)
+ return PJ_ETOOSMALL;
+
+ /* Copy header */
+ pj_memcpy(buf, a, ATTR_HDR_LEN);
+
+ /* Set the correct length */
+ attr = (pj_stun_attr_hdr*)buf;
+ attr->length = (pj_uint16_t) ca->length;
+
+ /* Convert to network byte order */
+ attr->type = pj_htons(attr->type);
+ attr->length = pj_htons(attr->length);
+
+ /* Copy the data */
+ pj_memcpy(buf+ATTR_HDR_LEN, ca->data, ca->length);
+
+ /* Done */
+ return PJ_SUCCESS;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+
+/*
+ * Create a blank STUN message.
+ */
+PJ_DEF(pj_status_t) pj_stun_msg_create( pj_pool_t *pool,
+ unsigned msg_type,
+ pj_uint32_t magic,
+ const pj_uint8_t tsx_id[12],
+ pj_stun_msg **p_msg)
+{
+ pj_stun_msg *msg;
+
+ PJ_ASSERT_RETURN(pool && msg_type && p_msg, PJ_EINVAL);
+
+ msg = PJ_POOL_ZALLOC_T(pool, pj_stun_msg);
+ msg->hdr.type = (pj_uint16_t) msg_type;
+ msg->hdr.magic = magic;
+
+ if (tsx_id) {
+ pj_memcpy(&msg->hdr.tsx_id, tsx_id, sizeof(msg->hdr.tsx_id));
+ } else {
+ struct transaction_id
+ {
+ pj_uint32_t proc_id;
+ pj_uint32_t random;
+ pj_uint32_t counter;
+ } id;
+ static pj_uint32_t pj_stun_tsx_id_counter;
+
+ id.proc_id = pj_getpid();
+ id.random = pj_rand();
+ id.counter = pj_stun_tsx_id_counter++;
+
+ pj_memcpy(&msg->hdr.tsx_id, &id, sizeof(msg->hdr.tsx_id));
+ }
+
+ *p_msg = msg;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Add STUN attribute to STUN message.
+ */
+PJ_DEF(pj_status_t) pj_stun_msg_add_attr(pj_stun_msg *msg,
+ pj_stun_attr_hdr *attr)
+{
+ PJ_ASSERT_RETURN(msg && attr, PJ_EINVAL);
+ PJ_ASSERT_RETURN(msg->attr_count < PJ_STUN_MAX_ATTR, PJ_ETOOMANY);
+
+ msg->attr[msg->attr_count++] = attr;
+ return PJ_SUCCESS;
+}
+
+
+PJ_INLINE(pj_uint16_t) GET_VAL16(const pj_uint8_t *pdu, unsigned pos)
+{
+ return (pj_uint16_t) ((pdu[pos] << 8) + pdu[pos+1]);
+}
+
+PJ_INLINE(pj_uint32_t) GET_VAL32(const pj_uint8_t *pdu, unsigned pos)
+{
+ return (pdu[pos+0] << 24) +
+ (pdu[pos+1] << 16) +
+ (pdu[pos+2] << 8) +
+ (pdu[pos+3]);
+}
+
+
+/*
+ * Check that the PDU is potentially a valid STUN message.
+ */
+PJ_DEF(pj_status_t) pj_stun_msg_check(const pj_uint8_t *pdu, unsigned pdu_len,
+ unsigned options)
+{
+ unsigned msg_len;
+
+ PJ_ASSERT_RETURN(pdu, PJ_EINVAL);
+
+ if (pdu_len < sizeof(pj_stun_msg_hdr))
+ return PJLIB_UTIL_ESTUNINMSGLEN;
+
+ /* First byte of STUN message is always 0x00 or 0x01. */
+ if (*pdu != 0x00 && *pdu != 0x01)
+ return PJLIB_UTIL_ESTUNINMSGTYPE;
+
+ /* Check the PDU length */
+ msg_len = GET_VAL16(pdu, 2);
+ if ((msg_len + 20 > pdu_len) ||
+ ((options & PJ_STUN_IS_DATAGRAM) && msg_len + 20 != pdu_len))
+ {
+ return PJLIB_UTIL_ESTUNINMSGLEN;
+ }
+
+ /* If magic is set, then there is great possibility that this is
+ * a STUN message.
+ */
+ if (GET_VAL32(pdu, 4) == PJ_STUN_MAGIC) {
+
+ /* Check if FINGERPRINT attribute is present */
+ if (GET_VAL16(pdu, msg_len + 20) == PJ_STUN_ATTR_FINGERPRINT) {
+ pj_uint16_t attr_len = GET_VAL16(pdu, msg_len + 22);
+ pj_uint32_t fingerprint = GET_VAL32(pdu, msg_len + 24);
+ pj_uint32_t crc;
+
+ if (attr_len != 4)
+ return PJLIB_UTIL_ESTUNINATTRLEN;
+
+ crc = pj_crc32_calc(pdu, msg_len + 20);
+ crc ^= STUN_XOR_FINGERPRINT;
+
+ if (crc != fingerprint)
+ return PJLIB_UTIL_ESTUNFINGERPRINT;
+ }
+ }
+
+ /* Could be a STUN message */
+ return PJ_SUCCESS;
+}
+
+
+/* Create error response */
+PJ_DEF(pj_status_t) pj_stun_msg_create_response(pj_pool_t *pool,
+ const pj_stun_msg *req_msg,
+ unsigned err_code,
+ const pj_str_t *err_msg,
+ pj_stun_msg **p_response)
+{
+ unsigned msg_type = req_msg->hdr.type;
+ pj_stun_msg *response = NULL;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pool && p_response, PJ_EINVAL);
+
+ PJ_ASSERT_RETURN(PJ_STUN_IS_REQUEST(msg_type),
+ PJLIB_UTIL_ESTUNINMSGTYPE);
+
+ /* Create response or error response */
+ if (err_code)
+ msg_type |= PJ_STUN_ERROR_RESPONSE_BIT;
+ else
+ msg_type |= PJ_STUN_RESPONSE_BIT;
+
+ status = pj_stun_msg_create(pool, msg_type, req_msg->hdr.magic,
+ req_msg->hdr.tsx_id, &response);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ /* Add error code attribute */
+ if (err_code) {
+ status = pj_stun_msg_add_errcode_attr(pool, response,
+ err_code, err_msg);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+ }
+
+ *p_response = response;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Parse incoming packet into STUN message.
+ */
+PJ_DEF(pj_status_t) pj_stun_msg_decode(pj_pool_t *pool,
+ const pj_uint8_t *pdu,
+ unsigned pdu_len,
+ unsigned options,
+ pj_stun_msg **p_msg,
+ unsigned *p_parsed_len,
+ pj_stun_msg **p_response)
+{
+
+ pj_stun_msg *msg;
+ unsigned uattr_cnt;
+ const pj_uint8_t *start_pdu = pdu;
+ pj_bool_t has_msg_int = PJ_FALSE;
+ pj_bool_t has_fingerprint = PJ_FALSE;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(options);
+
+ PJ_ASSERT_RETURN(pool && pdu && pdu_len && p_msg, PJ_EINVAL);
+ PJ_ASSERT_RETURN(sizeof(pj_stun_msg_hdr) == 20, PJ_EBUG);
+
+ if (p_parsed_len)
+ *p_parsed_len = 0;
+ if (p_response)
+ *p_response = NULL;
+
+ /* Check if this is a STUN message, if necessary */
+ if (options & PJ_STUN_CHECK_PACKET) {
+ status = pj_stun_msg_check(pdu, pdu_len, options);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /* Create the message, copy the header, and convert to host byte order */
+ msg = PJ_POOL_ZALLOC_T(pool, pj_stun_msg);
+ pj_memcpy(&msg->hdr, pdu, sizeof(pj_stun_msg_hdr));
+ msg->hdr.type = pj_ntohs(msg->hdr.type);
+ msg->hdr.length = pj_ntohs(msg->hdr.length);
+ msg->hdr.magic = pj_ntohl(msg->hdr.magic);
+
+ pdu += sizeof(pj_stun_msg_hdr);
+ /* pdu_len -= sizeof(pj_stun_msg_hdr); */
+ pdu_len = msg->hdr.length;
+
+ /* No need to create response if this is not a request */
+ if (!PJ_STUN_IS_REQUEST(msg->hdr.type))
+ p_response = NULL;
+
+ /* Parse attributes */
+ uattr_cnt = 0;
+ while (pdu_len > 0) {
+ unsigned attr_type, attr_val_len;
+ const struct attr_desc *adesc;
+
+ /* Get attribute type and length. If length is not aligned
+ * to 4 bytes boundary, add padding.
+ */
+ attr_type = pj_ntohs(*(pj_uint16_t*)pdu);
+ attr_val_len = pj_ntohs(*(pj_uint16_t*)(pdu+2));
+ attr_val_len = (attr_val_len + 3) & (~3);
+
+ /* Check length */
+ if (pdu_len < attr_val_len) {
+ pj_str_t err_msg;
+ char err_msg_buf[80];
+
+ err_msg.ptr = err_msg_buf;
+ err_msg.slen = pj_ansi_snprintf(err_msg_buf, sizeof(err_msg_buf),
+ "Attribute %s has invalid length",
+ pj_stun_get_attr_name(attr_type));
+
+ PJ_LOG(4,(THIS_FILE, "Error decoding message: %.*s",
+ (int)err_msg.slen, err_msg.ptr));
+
+ if (p_response) {
+ pj_stun_msg_create_response(pool, msg,
+ PJ_STUN_SC_BAD_REQUEST,
+ &err_msg, p_response);
+ }
+ return PJLIB_UTIL_ESTUNINATTRLEN;
+ }
+
+ /* Get the attribute descriptor */
+ adesc = find_attr_desc(attr_type);
+
+ if (adesc == NULL) {
+ /* Unrecognized attribute */
+
+ PJ_LOG(4,(THIS_FILE, "Unrecognized attribute type %d",
+ attr_type));
+
+ /* Is this a fatal condition? */
+ if (attr_type <= 0x7FFF) {
+ /* This is a mandatory attribute, we must return error
+ * if we don't understand the attribute.
+ */
+ if (p_response) {
+ unsigned err_code = PJ_STUN_SC_UNKNOWN_ATTRIBUTE;
+
+ status = pj_stun_msg_create_response(pool, msg,
+ err_code, NULL,
+ p_response);
+ if (status==PJ_SUCCESS) {
+ pj_uint16_t d = (pj_uint16_t)attr_type;
+ pj_stun_msg_add_unknown_attr(pool, *p_response, 1, &d);
+ }
+ }
+
+ return PJLIB_UTIL_ESTUNUNKNOWNATTR;
+ }
+
+ } else {
+ void *attr;
+ char err_msg1[PJ_ERR_MSG_SIZE],
+ err_msg2[PJ_ERR_MSG_SIZE];
+
+ /* Parse the attribute */
+ status = (adesc->decode_attr)(pool, pdu, &attr);
+
+ if (status != PJ_SUCCESS) {
+ pj_strerror(status, err_msg1, sizeof(err_msg1));
+
+ if (p_response) {
+ pj_str_t e;
+
+ e.ptr = err_msg2;
+ e.slen= pj_ansi_snprintf(err_msg2, sizeof(err_msg2),
+ "%s in %s",
+ err_msg1,
+ pj_stun_get_attr_name(attr_type));
+
+ pj_stun_msg_create_response(pool, msg,
+ PJ_STUN_SC_BAD_REQUEST,
+ &e, p_response);
+ }
+
+ PJ_LOG(4,(THIS_FILE,
+ "Error parsing STUN attribute %s: %s",
+ pj_stun_get_attr_name(attr_type),
+ err_msg1));
+
+ return status;
+ }
+
+ if (attr_type == PJ_STUN_ATTR_MESSAGE_INTEGRITY &&
+ !has_fingerprint)
+ {
+ if (has_msg_int) {
+ /* Already has MESSAGE-INTEGRITY */
+ if (p_response) {
+ pj_str_t e;
+ e = pj_str("MESSAGE-INTEGRITY already present");
+ pj_stun_msg_create_response(pool, msg,
+ PJ_STUN_SC_BAD_REQUEST,
+ NULL, p_response);
+ }
+ return PJLIB_UTIL_ESTUNDUPATTR;
+ }
+ has_msg_int = PJ_TRUE;
+
+ } else if (attr_type == PJ_STUN_ATTR_FINGERPRINT) {
+ if (has_fingerprint) {
+ /* Already has FINGERPRINT */
+ if (p_response) {
+ pj_str_t e;
+ e = pj_str("FINGERPRINT already present");
+ pj_stun_msg_create_response(pool, msg,
+ PJ_STUN_SC_BAD_REQUEST,
+ NULL, p_response);
+ }
+ return PJLIB_UTIL_ESTUNDUPATTR;
+ }
+ has_fingerprint = PJ_TRUE;
+ } else {
+ if (has_msg_int || has_fingerprint) {
+ /* Another attribute is found which is not FINGERPRINT
+ * after FINGERPRINT or MESSAGE-INTEGRITY */
+ if (p_response) {
+ pj_str_t e;
+ e = pj_str("Invalid attribute order");
+ pj_stun_msg_create_response(pool, msg,
+ PJ_STUN_SC_BAD_REQUEST,
+ NULL, p_response);
+ }
+ return has_fingerprint ? PJLIB_UTIL_ESTUNFINGERPOS :
+ PJLIB_UTIL_ESTUNMSGINTPOS;
+ }
+ }
+
+ /* Make sure we have rooms for the new attribute */
+ if (msg->attr_count >= PJ_STUN_MAX_ATTR) {
+ if (p_response) {
+ pj_str_t e;
+
+ e = pj_str("Too many attributes");
+ pj_stun_msg_create_response(pool, msg,
+ PJ_STUN_SC_BAD_REQUEST,
+ &e, p_response);
+ }
+ return PJLIB_UTIL_ESTUNTOOMANYATTR;
+ }
+
+ /* Add the attribute */
+ msg->attr[msg->attr_count++] = (pj_stun_attr_hdr*)attr;
+ }
+
+ pdu += (attr_val_len + 4);
+ pdu_len -= (attr_val_len + 4);
+ }
+
+ *p_msg = msg;
+
+ if (p_parsed_len)
+ *p_parsed_len = (pdu - start_pdu);
+
+ return PJ_SUCCESS;
+}
+
+/* Calculate HMAC-SHA1 key for long term credential, by getting
+ * MD5 digest of username, realm, and password.
+ */
+void pj_stun_calc_md5_key(pj_uint8_t digest[16],
+ const pj_str_t *realm,
+ const pj_str_t *username,
+ const pj_str_t *passwd)
+{
+ /* The 16-byte key for MESSAGE-INTEGRITY HMAC is formed by taking
+ * the MD5 hash of the result of concatenating the following five
+ * fields: (1) The username, with any quotes and trailing nulls
+ * removed, (2) A single colon, (3) The realm, with any quotes and
+ * trailing nulls removed, (4) A single colon, and (5) The
+ * password, with any trailing nulls removed.
+ */
+ pj_md5_context ctx;
+ pj_str_t s;
+
+ pj_md5_init(&ctx);
+
+#define REMOVE_QUOTE(s) if (s.slen && *s.ptr=='"') \
+ s.ptr++, s.slen--; \
+ if (s.slen && s.ptr[s.slen-1]=='"') \
+ s.slen--;
+
+ /* Add username */
+ s = *username;
+ REMOVE_QUOTE(s);
+ pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, s.slen);
+
+ /* Add single colon */
+ pj_md5_update(&ctx, (pj_uint8_t*)":", 1);
+
+ /* Add realm */
+ s = *realm;
+ REMOVE_QUOTE(s);
+ pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, s.slen);
+
+#undef REMOVE_QUOTE
+
+ /* Another colon */
+ pj_md5_update(&ctx, (pj_uint8_t*)":", 1);
+
+ /* Add password */
+ pj_md5_update(&ctx, (pj_uint8_t*)passwd->ptr, passwd->slen);
+
+ /* Done */
+ pj_md5_final(&ctx, digest);
+}
+
+
+/*
+static char *print_binary(const pj_uint8_t *data, unsigned data_len)
+{
+ static char static_buffer[1024];
+ char *buffer = static_buffer;
+ unsigned length=sizeof(static_buffer), i;
+
+ if (length < data_len * 2 + 8)
+ return "";
+
+ pj_ansi_sprintf(buffer, ", data=");
+ buffer += 7;
+
+ for (i=0; i<data_len; ++i) {
+ pj_ansi_sprintf(buffer, "%02x", (*data) & 0xFF);
+ buffer += 2;
+ data++;
+ }
+
+ pj_ansi_sprintf(buffer, "\n");
+ buffer++;
+
+ return static_buffer;
+}
+*/
+
+/*
+ * Print the message structure to a buffer.
+ */
+PJ_DEF(pj_status_t) pj_stun_msg_encode(pj_stun_msg *msg,
+ pj_uint8_t *buf, unsigned buf_size,
+ unsigned options,
+ const pj_str_t *password,
+ unsigned *p_msg_len)
+{
+ pj_stun_msg_hdr *hdr;
+ pj_uint8_t *start = buf;
+ pj_stun_realm_attr *arealm = NULL;
+ pj_stun_username_attr *auname = NULL;
+ pj_stun_msgint_attr *amsgint = NULL;
+ pj_stun_fingerprint_attr *afingerprint = NULL;
+ unsigned printed = 0;
+ pj_status_t status;
+ unsigned i;
+
+
+ PJ_ASSERT_RETURN(msg && buf && buf_size, PJ_EINVAL);
+
+ PJ_UNUSED_ARG(options);
+ PJ_ASSERT_RETURN(options == 0, PJ_EINVAL);
+
+ /* Copy the message header part and convert the header fields to
+ * network byte order
+ */
+ if (buf_size < sizeof(pj_stun_msg_hdr))
+ return PJ_ETOOSMALL;
+ pj_memcpy(buf, &msg->hdr, sizeof(pj_stun_msg_hdr));
+ hdr = (pj_stun_msg_hdr*) buf;
+ hdr->magic = pj_htonl(hdr->magic);
+ hdr->type = pj_htons(hdr->type);
+ /* We'll fill in the length later */
+
+ buf += sizeof(pj_stun_msg_hdr);
+ buf_size -= sizeof(pj_stun_msg_hdr);
+
+ /* Print each attribute */
+ for (i=0; i<msg->attr_count; ++i) {
+ const struct attr_desc *adesc;
+ const pj_stun_attr_hdr *attr_hdr = msg->attr[i];
+
+ if (attr_hdr->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
+ pj_assert(amsgint == NULL);
+ amsgint = (pj_stun_msgint_attr*) attr_hdr;
+
+ /* Stop when encountering MESSAGE-INTEGRITY */
+ break;
+
+ } else if (attr_hdr->type == PJ_STUN_ATTR_USERNAME) {
+ pj_assert(auname == NULL);
+ auname = (pj_stun_username_attr*) attr_hdr;
+
+ } else if (attr_hdr->type == PJ_STUN_ATTR_REALM) {
+ pj_assert(arealm == NULL);
+ arealm = (pj_stun_realm_attr*) attr_hdr;
+
+ } else if (attr_hdr->type == PJ_STUN_ATTR_FINGERPRINT) {
+ afingerprint = (pj_stun_fingerprint_attr*) attr_hdr;
+ break;
+ }
+
+ adesc = find_attr_desc(attr_hdr->type);
+ PJ_ASSERT_RETURN(adesc != NULL, PJ_EBUG);
+
+ status = adesc->encode_attr(attr_hdr, buf, buf_size, &printed);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ buf += printed;
+ buf_size -= printed;
+ }
+
+ /* We may have stopped printing attribute because we found
+ * MESSAGE-INTEGRITY or FINGERPRINT. Scan the rest of the
+ * attributes.
+ */
+ for ( ++i; i<msg->attr_count; ++i) {
+ const pj_stun_attr_hdr *attr_hdr = msg->attr[i];
+
+ /* There mustn't any attribute after FINGERPRINT */
+ PJ_ASSERT_RETURN(afingerprint == NULL, PJLIB_UTIL_ESTUNFINGERPOS);
+
+ if (attr_hdr->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
+ /* There mustn't be MESSAGE-INTEGRITY before */
+ PJ_ASSERT_RETURN(amsgint == NULL,
+ PJLIB_UTIL_ESTUNMSGINTPOS);
+ amsgint = (pj_stun_msgint_attr*) attr_hdr;
+
+ } else if (attr_hdr->type == PJ_STUN_ATTR_FINGERPRINT) {
+ afingerprint = (pj_stun_fingerprint_attr*) attr_hdr;
+ }
+ }
+
+ /* We MUST update the message length in the header NOW before
+ * calculating MESSAGE-INTEGRITY and FINGERPRINT.
+ * Note that length is not including the 20 bytes header.
+ */
+ if (amsgint && afingerprint) {
+ msg->hdr.length = (pj_uint16_t)((buf - start) - 20 + 24 + 8);
+ } else if (amsgint) {
+ msg->hdr.length = (pj_uint16_t)((buf - start) - 20 + 24);
+ } else if (afingerprint) {
+ msg->hdr.length = (pj_uint16_t)((buf - start) - 20 + 8);
+ } else {
+ msg->hdr.length = (pj_uint16_t)((buf - start) - 20);
+ }
+
+ /* hdr->length = pj_htons(length); */
+ start[2] = (pj_uint8_t)((msg->hdr.length >> 8) & 0x00FF);
+ start[3] = (pj_uint8_t)(msg->hdr.length & 0x00FF);
+
+ /* Calculate message integrity, if present */
+ if (amsgint != NULL) {
+
+ pj_uint8_t md5_key_buf[16];
+ pj_str_t key;
+
+ /* MESSAGE-INTEGRITY must be the last attribute in the message, or
+ * the last attribute before FINGERPRINT.
+ */
+ if (i < msg->attr_count-2) {
+ /* Should not happen for message generated by us */
+ pj_assert(PJ_FALSE);
+ return PJLIB_UTIL_ESTUNMSGINTPOS;
+
+ } else if (i == msg->attr_count-2) {
+ if (msg->attr[i+1]->type != PJ_STUN_ATTR_FINGERPRINT) {
+ /* Should not happen for message generated by us */
+ pj_assert(PJ_FALSE);
+ return PJLIB_UTIL_ESTUNMSGINTPOS;
+ } else {
+ afingerprint = (pj_stun_fingerprint_attr*) msg->attr[i+1];
+ }
+ }
+
+ /* Must have USERNAME attribute */
+ if (auname == NULL) {
+ /* Should not happen for message generated by us */
+ pj_assert(PJ_FALSE);
+ return PJLIB_UTIL_ESTUNNOUSERNAME;
+ }
+
+ /* Password must be specified */
+ PJ_ASSERT_RETURN(password, PJ_EINVAL);
+
+ /* Get the key to sign the message */
+ if (arealm == NULL ) {
+ /* For short term credential, the key is the password */
+ key = *password;
+
+ } else {
+ pj_stun_calc_md5_key(md5_key_buf, &arealm->value,
+ &auname->value, password);
+ key.ptr = (char*) md5_key_buf;
+ key.slen = 16;
+ }
+
+ /* Calculate HMAC-SHA1 digest */
+ pj_hmac_sha1((pj_uint8_t*)start, buf-start,
+ (pj_uint8_t*)key.ptr, key.slen,
+ amsgint->hmac);
+
+ /* Put this attribute in the message */
+ status = encode_msgint_attr(amsgint, buf, buf_size,
+ &printed);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ buf += printed;
+ buf_size -= printed;
+ }
+
+ /* Calculate FINGERPRINT if present */
+ if (afingerprint != NULL) {
+ afingerprint->value = pj_crc32_calc(start, buf-start);
+ afingerprint->value ^= STUN_XOR_FINGERPRINT;
+
+ /* Put this attribute in the message */
+ status = encode_uint_attr(afingerprint, buf, buf_size,
+ &printed);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ buf += printed;
+ buf_size -= printed;
+ }
+
+ /* Done */
+ if (p_msg_len)
+ *p_msg_len = (buf - start);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Find STUN attribute in the STUN message, starting from the specified
+ * index.
+ */
+PJ_DEF(pj_stun_attr_hdr*) pj_stun_msg_find_attr( const pj_stun_msg *msg,
+ int attr_type,
+ unsigned index)
+{
+ PJ_ASSERT_RETURN(msg, NULL);
+
+ for (; index < msg->attr_count; ++index) {
+ if (msg->attr[index]->type == attr_type)
+ return (pj_stun_attr_hdr*) msg->attr[index];
+ }
+
+ return NULL;
+}
+
diff --git a/pjnath/src/pjnath/stun_msg_dump.c b/pjnath/src/pjnath/stun_msg_dump.c
new file mode 100644
index 00000000..0ee0ebe4
--- /dev/null
+++ b/pjnath/src/pjnath/stun_msg_dump.c
@@ -0,0 +1,250 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjlib-util/stun_msg.h>
+#include <pjlib-util/errno.h>
+#include <pj/assert.h>
+#include <pj/string.h>
+
+
+#define APPLY() if (len < 1 || len >= (end-p)) \
+ goto on_return; \
+ p += len
+
+static int print_binary(char *buffer, unsigned length,
+ const pj_uint8_t *data, unsigned data_len)
+{
+ unsigned i;
+
+ if (length < data_len * 2 + 8)
+ return -1;
+
+ pj_ansi_sprintf(buffer, ", data=");
+ buffer += 7;
+
+ for (i=0; i<data_len; ++i) {
+ pj_ansi_sprintf(buffer, "%02x", (*data) & 0xFF);
+ buffer += 2;
+ data++;
+ }
+
+ pj_ansi_sprintf(buffer, "\n");
+ buffer++;
+
+ return data_len * 2 + 8;
+}
+
+static int print_attr(char *buffer, unsigned length,
+ const pj_stun_attr_hdr *ahdr)
+{
+ char *p = buffer, *end = buffer + length;
+ int len;
+
+ len = pj_ansi_snprintf(p, end-p,
+ " %s: length=%d",
+ pj_stun_get_attr_name(ahdr->type),
+ (int)ahdr->length);
+ APPLY();
+
+
+ switch (ahdr->type) {
+ case PJ_STUN_ATTR_MAPPED_ADDR:
+ case PJ_STUN_ATTR_RESPONSE_ADDR:
+ case PJ_STUN_ATTR_SOURCE_ADDR:
+ case PJ_STUN_ATTR_CHANGED_ADDR:
+ case PJ_STUN_ATTR_REFLECTED_FROM:
+ case PJ_STUN_ATTR_REMOTE_ADDR:
+ case PJ_STUN_ATTR_RELAY_ADDR:
+ case PJ_STUN_ATTR_XOR_MAPPED_ADDR:
+ case PJ_STUN_ATTR_REQ_IP:
+ case PJ_STUN_ATTR_XOR_REFLECTED_FROM:
+ case PJ_STUN_ATTR_XOR_INTERNAL_ADDR:
+ case PJ_STUN_ATTR_ALTERNATE_SERVER:
+ {
+ const pj_stun_sockaddr_attr *attr;
+
+ attr = (const pj_stun_sockaddr_attr*)ahdr;
+
+ if (attr->addr.addr.sa_family == PJ_AF_INET) {
+ len = pj_ansi_snprintf(p, end-p,
+ ", IPv4 addr=%s:%d\n",
+ pj_inet_ntoa(attr->addr.ipv4.sin_addr),
+ pj_ntohs(attr->addr.ipv4.sin_port));
+
+ } else if (attr->addr.addr.sa_family == PJ_AF_INET6) {
+ len = pj_ansi_snprintf(p, end-p,
+ ", IPv6 addr present\n");
+ } else {
+ len = pj_ansi_snprintf(p, end-p,
+ ", INVALID ADDRESS FAMILY!\n");
+ }
+ }
+ break;
+
+ case PJ_STUN_ATTR_CHANGE_REQUEST:
+ case PJ_STUN_ATTR_LIFETIME:
+ case PJ_STUN_ATTR_BANDWIDTH:
+ case PJ_STUN_ATTR_REQ_ADDR_TYPE:
+ case PJ_STUN_ATTR_REQ_PORT_PROPS:
+ case PJ_STUN_ATTR_REQ_TRANSPORT:
+ case PJ_STUN_ATTR_TIMER_VAL:
+ case PJ_STUN_ATTR_PRIORITY:
+ case PJ_STUN_ATTR_FINGERPRINT:
+ case PJ_STUN_ATTR_REFRESH_INTERVAL:
+ {
+ const pj_stun_uint_attr *attr;
+
+ attr = (const pj_stun_uint_attr*)ahdr;
+ len = pj_ansi_snprintf(p, end-p,
+ ", value=%d (0x%x)\n",
+ (pj_uint32_t)attr->value,
+ (pj_uint32_t)attr->value);
+ }
+ break;
+
+ case PJ_STUN_ATTR_USERNAME:
+ case PJ_STUN_ATTR_PASSWORD:
+ case PJ_STUN_ATTR_REALM:
+ case PJ_STUN_ATTR_NONCE:
+ case PJ_STUN_ATTR_SERVER:
+ {
+ const pj_stun_string_attr *attr;
+
+ attr = (pj_stun_string_attr*)ahdr;
+ len = pj_ansi_snprintf(p, end-p,
+ ", value=\"%.*s\"\n",
+ (int)attr->value.slen,
+ attr->value.ptr);
+ }
+ break;
+
+ case PJ_STUN_ATTR_ERROR_CODE:
+ {
+ const pj_stun_errcode_attr *attr;
+
+ attr = (const pj_stun_errcode_attr*) ahdr;
+ len = pj_ansi_snprintf(p, end-p,
+ ", err_code=%d, reason=\"%.*s\"\n",
+ attr->err_class*100 + attr->number,
+ (int)attr->reason.slen,
+ attr->reason.ptr);
+ }
+ break;
+
+ case PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES:
+ {
+ const pj_stun_unknown_attr *attr;
+ unsigned j;
+
+ attr = (const pj_stun_unknown_attr*) ahdr;
+
+ len = pj_ansi_snprintf(p, end-p,
+ ", unknown list:");
+ APPLY();
+
+ for (j=0; j<attr->attr_count; ++j) {
+ len = pj_ansi_snprintf(p, end-p,
+ " %d",
+ (int)attr->attrs[j]);
+ APPLY();
+ }
+ }
+ break;
+
+ case PJ_STUN_ATTR_MESSAGE_INTEGRITY:
+ {
+ const pj_stun_msgint_attr *attr;
+
+ attr = (const pj_stun_msgint_attr*) ahdr;
+ len = print_binary(p, end-p, attr->hmac, 20);
+ APPLY();
+ }
+ break;
+
+ case PJ_STUN_ATTR_DATA:
+ {
+ const pj_stun_binary_attr *attr;
+
+ attr = (const pj_stun_binary_attr*) ahdr;
+ len = print_binary(p, end-p, attr->data, attr->length);
+ APPLY();
+ }
+ break;
+ case PJ_STUN_ATTR_USE_CANDIDATE:
+ default:
+ len = pj_ansi_snprintf(p, end-p, "\n");
+
+ break;
+ }
+
+ APPLY();
+
+ return (p-buffer);
+
+on_return:
+ return len;
+}
+
+
+/*
+ * Dump STUN message to a printable string output.
+ */
+PJ_DEF(char*) pj_stun_msg_dump(const pj_stun_msg *msg,
+ char *buffer,
+ unsigned length,
+ unsigned *printed_len)
+{
+ char *p, *end;
+ int len;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(msg && buffer && length, NULL);
+
+ p = buffer;
+ end = buffer + length;
+
+ len = pj_ansi_snprintf(p, end-p, "STUN %s %s\n",
+ pj_stun_get_method_name(msg->hdr.type),
+ pj_stun_get_class_name(msg->hdr.type));
+ APPLY();
+
+ len = pj_ansi_snprintf(p, end-p,
+ " Hdr: length=%d, magic=%08x, tsx_id=%08x %08x %08x\n"
+ " Attributes:\n",
+ msg->hdr.length,
+ msg->hdr.magic,
+ *(pj_uint32_t*)&msg->hdr.tsx_id[0],
+ *(pj_uint32_t*)&msg->hdr.tsx_id[4],
+ *(pj_uint32_t*)&msg->hdr.tsx_id[8]);
+ APPLY();
+
+ for (i=0; i<msg->attr_count; ++i) {
+ len = print_attr(p, end-p, msg->attr[i]);
+ APPLY();
+ }
+
+on_return:
+ *p = '\0';
+ if (printed_len)
+ *printed_len = (p-buffer);
+ return buffer;
+
+}
+
+
+#undef APPLY
diff --git a/pjnath/src/pjnath/stun_session.c b/pjnath/src/pjnath/stun_session.c
new file mode 100644
index 00000000..b4e7356c
--- /dev/null
+++ b/pjnath/src/pjnath/stun_session.c
@@ -0,0 +1,903 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjlib-util/stun_session.h>
+#include <pjlib.h>
+
+struct pj_stun_session
+{
+ pj_stun_endpoint *endpt;
+ pj_pool_t *pool;
+ pj_mutex_t *mutex;
+ pj_stun_session_cb cb;
+ void *user_data;
+
+ pj_bool_t use_fingerprint;
+ pj_stun_auth_cred *cred;
+ pj_str_t srv_name;
+
+ pj_stun_tx_data pending_request_list;
+ pj_stun_tx_data cached_response_list;
+};
+
+#define SNAME(s_) ((s_)->pool->obj_name)
+
+#if PJ_LOG_MAX_LEVEL >= 5
+# define TRACE_(expr) PJ_LOG(5,expr)
+#else
+# define TRACE_(expr)
+#endif
+
+#if PJ_LOG_MAX_LEVEL >= 4
+# define LOG_ERR_(sess, title, rc) stun_perror(sess, title, rc)
+static void stun_perror(pj_stun_session *sess, const char *title,
+ pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+
+ PJ_LOG(4,(SNAME(sess), "%s: %s", title, errmsg));
+}
+
+#else
+# define LOG_ERR_(sess, title, rc)
+#endif
+
+#define TDATA_POOL_SIZE 1024
+#define TDATA_POOL_INC 1024
+
+
+static void tsx_on_complete(pj_stun_client_tsx *tsx,
+ pj_status_t status,
+ const pj_stun_msg *response);
+static pj_status_t tsx_on_send_msg(pj_stun_client_tsx *tsx,
+ const void *stun_pkt,
+ pj_size_t pkt_size);
+
+static pj_stun_tsx_cb tsx_cb =
+{
+ &tsx_on_complete,
+ &tsx_on_send_msg
+};
+
+
+static pj_status_t tsx_add(pj_stun_session *sess,
+ pj_stun_tx_data *tdata)
+{
+ pj_list_push_back(&sess->pending_request_list, tdata);
+ return PJ_SUCCESS;
+}
+
+static pj_status_t tsx_erase(pj_stun_session *sess,
+ pj_stun_tx_data *tdata)
+{
+ PJ_UNUSED_ARG(sess);
+ pj_list_erase(tdata);
+ return PJ_SUCCESS;
+}
+
+static pj_stun_tx_data* tsx_lookup(pj_stun_session *sess,
+ const pj_stun_msg *msg)
+{
+ pj_stun_tx_data *tdata;
+
+ tdata = sess->pending_request_list.next;
+ while (tdata != &sess->pending_request_list) {
+ pj_assert(sizeof(tdata->msg_key)==sizeof(msg->hdr.tsx_id));
+ if (tdata->msg_magic == msg->hdr.magic &&
+ pj_memcmp(tdata->msg_key, msg->hdr.tsx_id,
+ sizeof(msg->hdr.tsx_id))==0)
+ {
+ return tdata;
+ }
+ tdata = tdata->next;
+ }
+
+ return NULL;
+}
+
+static pj_status_t create_tdata(pj_stun_session *sess,
+ pj_stun_tx_data **p_tdata)
+{
+ pj_pool_t *pool;
+ pj_stun_tx_data *tdata;
+
+ /* Create pool and initialize basic tdata attributes */
+ pool = pj_pool_create(sess->endpt->pf, "tdata%p",
+ TDATA_POOL_SIZE, TDATA_POOL_INC, NULL);
+ PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
+
+ tdata = PJ_POOL_ZALLOC_T(pool, pj_stun_tx_data);
+ tdata->pool = pool;
+ tdata->sess = sess;
+
+ *p_tdata = tdata;
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t create_request_tdata(pj_stun_session *sess,
+ unsigned msg_type,
+ pj_stun_tx_data **p_tdata)
+{
+ pj_status_t status;
+ pj_stun_tx_data *tdata;
+
+ status = create_tdata(sess, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Create STUN message */
+ status = pj_stun_msg_create(tdata->pool, msg_type, PJ_STUN_MAGIC,
+ NULL, &tdata->msg);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(tdata->pool);
+ return status;
+ }
+
+ /* copy the request's transaction ID as the transaction key. */
+ pj_assert(sizeof(tdata->msg_key)==sizeof(tdata->msg->hdr.tsx_id));
+ tdata->msg_magic = tdata->msg->hdr.magic;
+ pj_memcpy(tdata->msg_key, tdata->msg->hdr.tsx_id,
+ sizeof(tdata->msg->hdr.tsx_id));
+
+ *p_tdata = tdata;
+
+ return PJ_SUCCESS;
+}
+
+static void destroy_tdata(pj_stun_tx_data *tdata)
+{
+ if (tdata->client_tsx) {
+ tsx_erase(tdata->sess, tdata);
+ pj_stun_client_tsx_destroy(tdata->client_tsx);
+ tdata->client_tsx = NULL;
+ }
+ if (tdata->res_timer.id != PJ_FALSE) {
+ pj_timer_heap_cancel(tdata->sess->endpt->timer_heap,
+ &tdata->res_timer);
+ tdata->res_timer.id = PJ_FALSE;
+ pj_list_erase(tdata);
+ }
+ pj_pool_release(tdata->pool);
+}
+
+/*
+ * Destroy the transmit data.
+ */
+PJ_DEF(void) pj_stun_msg_destroy_tdata( pj_stun_session *sess,
+ pj_stun_tx_data *tdata)
+{
+ PJ_UNUSED_ARG(sess);
+ destroy_tdata(tdata);
+}
+
+
+/* Timer callback to be called when it's time to destroy response cache */
+static void on_cache_timeout(pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry)
+{
+ pj_stun_tx_data *tdata;
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ entry->id = PJ_FALSE;
+ tdata = (pj_stun_tx_data*) entry->user_data;
+
+ PJ_LOG(5,(SNAME(tdata->sess), "Response cache deleted"));
+
+ pj_list_erase(tdata);
+ pj_stun_msg_destroy_tdata(tdata->sess, tdata);
+}
+
+static pj_str_t *get_passwd(pj_stun_session *sess)
+{
+ if (sess->cred == NULL)
+ return NULL;
+ else if (sess->cred->type == PJ_STUN_AUTH_CRED_STATIC)
+ return &sess->cred->data.static_cred.data;
+ else
+ return NULL;
+}
+
+static pj_status_t apply_msg_options(pj_stun_session *sess,
+ pj_pool_t *pool,
+ pj_stun_msg *msg)
+{
+ pj_status_t status = 0;
+
+ /* The server SHOULD include a SERVER attribute in all responses */
+ if (PJ_STUN_IS_RESPONSE(msg->hdr.type) ||
+ PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type))
+ {
+ pj_stun_msg_add_string_attr(pool, msg, PJ_STUN_ATTR_SERVER,
+ &sess->srv_name);
+ }
+
+ /* From draft-ietf-behave-rfc3489bis-05.txt
+ * Section 8.3.1. Formulating the Request Message
+ *
+ * Note: only put MESSAGE-INTEGRITY in non error response.
+ */
+ if (sess->cred && sess->cred->type == PJ_STUN_AUTH_CRED_STATIC &&
+ !PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type))
+ {
+ const pj_str_t *username;
+
+ /* Create and add USERNAME attribute */
+ username = &sess->cred->data.static_cred.username;
+ status = pj_stun_msg_add_string_attr(pool, msg,
+ PJ_STUN_ATTR_USERNAME,
+ username);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+
+ /* Add REALM only when long term credential is used */
+ if (sess->cred->data.static_cred.realm.slen) {
+ const pj_str_t *realm = &sess->cred->data.static_cred.realm;
+ status = pj_stun_msg_add_string_attr(pool, msg,
+ PJ_STUN_ATTR_REALM,
+ realm);
+ }
+
+ /* Add MESSAGE-INTEGRITY attribute */
+ status = pj_stun_msg_add_msgint_attr(pool, msg);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+
+ }
+
+ /* Add FINGERPRINT attribute if necessary */
+ if (sess->use_fingerprint) {
+ status = pj_stun_msg_add_uint_attr(pool, msg,
+ PJ_STUN_ATTR_FINGERPRINT, 0);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+static void tsx_on_complete(pj_stun_client_tsx *tsx,
+ pj_status_t status,
+ const pj_stun_msg *response)
+{
+ pj_stun_tx_data *tdata;
+
+ tdata = (pj_stun_tx_data*) pj_stun_client_tsx_get_data(tsx);
+
+ if (tdata->sess->cb.on_request_complete) {
+ (*tdata->sess->cb.on_request_complete)(tdata->sess, status, tdata,
+ response);
+ }
+}
+
+static pj_status_t tsx_on_send_msg(pj_stun_client_tsx *tsx,
+ const void *stun_pkt,
+ pj_size_t pkt_size)
+{
+ pj_stun_tx_data *tdata;
+
+ tdata = (pj_stun_tx_data*) pj_stun_client_tsx_get_data(tsx);
+
+ return tdata->sess->cb.on_send_msg(tdata->sess, stun_pkt, pkt_size,
+ tdata->dst_addr, tdata->addr_len);
+}
+
+/* **************************************************************************/
+
+PJ_DEF(pj_status_t) pj_stun_session_create( pj_stun_endpoint *endpt,
+ const char *name,
+ const pj_stun_session_cb *cb,
+ pj_bool_t fingerprint,
+ pj_stun_session **p_sess)
+{
+ pj_pool_t *pool;
+ pj_stun_session *sess;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(endpt && cb && p_sess, PJ_EINVAL);
+
+ if (name==NULL)
+ name = "sess%p";
+
+ pool = pj_pool_create(endpt->pf, name, 4000, 4000, NULL);
+ PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
+
+ sess = PJ_POOL_ZALLOC_T(pool, pj_stun_session);
+ sess->endpt = endpt;
+ sess->pool = pool;
+ pj_memcpy(&sess->cb, cb, sizeof(*cb));
+ sess->use_fingerprint = fingerprint;
+
+ sess->srv_name.ptr = pj_pool_alloc(pool, 32);
+ sess->srv_name.slen = pj_ansi_snprintf(sess->srv_name.ptr, 32,
+ "pj_stun-%s", PJ_VERSION);
+
+ pj_list_init(&sess->pending_request_list);
+ pj_list_init(&sess->cached_response_list);
+
+ status = pj_mutex_create_recursive(pool, name, &sess->mutex);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(pool);
+ return status;
+ }
+
+ *p_sess = sess;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pj_stun_session_destroy(pj_stun_session *sess)
+{
+ PJ_ASSERT_RETURN(sess, PJ_EINVAL);
+
+ pj_mutex_lock(sess->mutex);
+ while (!pj_list_empty(&sess->pending_request_list)) {
+ pj_stun_tx_data *tdata = sess->pending_request_list.next;
+ destroy_tdata(tdata);
+ }
+ while (!pj_list_empty(&sess->cached_response_list)) {
+ pj_stun_tx_data *tdata = sess->cached_response_list.next;
+ destroy_tdata(tdata);
+ }
+ pj_mutex_unlock(sess->mutex);
+
+ pj_mutex_destroy(sess->mutex);
+ pj_pool_release(sess->pool);
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pj_stun_session_set_user_data( pj_stun_session *sess,
+ void *user_data)
+{
+ PJ_ASSERT_RETURN(sess, PJ_EINVAL);
+ pj_mutex_lock(sess->mutex);
+ sess->user_data = user_data;
+ pj_mutex_unlock(sess->mutex);
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(void*) pj_stun_session_get_user_data(pj_stun_session *sess)
+{
+ PJ_ASSERT_RETURN(sess, NULL);
+ return sess->user_data;
+}
+
+PJ_DEF(pj_status_t) pj_stun_session_set_server_name(pj_stun_session *sess,
+ const pj_str_t *srv_name)
+{
+ PJ_ASSERT_RETURN(sess && srv_name, PJ_EINVAL);
+ pj_strdup(sess->pool, &sess->srv_name, srv_name);
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(void) pj_stun_session_set_credential(pj_stun_session *sess,
+ const pj_stun_auth_cred *cred)
+{
+ PJ_ASSERT_ON_FAIL(sess, return);
+ if (cred) {
+ if (!sess->cred)
+ sess->cred = pj_pool_alloc(sess->pool, sizeof(pj_stun_auth_cred));
+ pj_stun_auth_cred_dup(sess->pool, sess->cred, cred);
+ } else {
+ sess->cred = NULL;
+ }
+}
+
+
+PJ_DEF(pj_status_t) pj_stun_session_create_req(pj_stun_session *sess,
+ int method,
+ pj_stun_tx_data **p_tdata)
+{
+ pj_stun_tx_data *tdata = NULL;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(sess && p_tdata, PJ_EINVAL);
+
+ status = create_request_tdata(sess, method, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pj_stun_session_create_ind(pj_stun_session *sess,
+ int msg_type,
+ pj_stun_tx_data **p_tdata)
+{
+ pj_stun_tx_data *tdata = NULL;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(sess && p_tdata, PJ_EINVAL);
+
+ status = create_tdata(sess, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Create STUN message */
+ msg_type |= PJ_STUN_INDICATION_BIT;
+ status = pj_stun_msg_create(tdata->pool, msg_type, PJ_STUN_MAGIC,
+ NULL, &tdata->msg);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(tdata->pool);
+ return status;
+ }
+
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Create a STUN response message.
+ */
+PJ_DEF(pj_status_t) pj_stun_session_create_response( pj_stun_session *sess,
+ const pj_stun_msg *req,
+ unsigned err_code,
+ const pj_str_t *err_msg,
+ pj_stun_tx_data **p_tdata)
+{
+ pj_status_t status;
+ pj_stun_tx_data *tdata = NULL;
+
+ status = create_tdata(sess, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Create STUN response message */
+ status = pj_stun_msg_create_response(tdata->pool, req, err_code, err_msg,
+ &tdata->msg);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(tdata->pool);
+ return status;
+ }
+
+ /* copy the request's transaction ID as the transaction key. */
+ pj_assert(sizeof(tdata->msg_key)==sizeof(req->hdr.tsx_id));
+ tdata->msg_magic = req->hdr.magic;
+ pj_memcpy(tdata->msg_key, req->hdr.tsx_id, sizeof(req->hdr.tsx_id));
+
+ *p_tdata = tdata;
+
+ return PJ_SUCCESS;
+}
+
+
+/* Print outgoing message to log */
+static void dump_tx_msg(pj_stun_session *sess, const pj_stun_msg *msg,
+ unsigned pkt_size, const pj_sockaddr_t *addr)
+{
+ const char *dst_name;
+ int dst_port;
+ const pj_sockaddr *dst = (const pj_sockaddr*)addr;
+ char buf[512];
+
+ if (dst->sa_family == PJ_AF_INET) {
+ const pj_sockaddr_in *dst4 = (const pj_sockaddr_in*)dst;
+ dst_name = pj_inet_ntoa(dst4->sin_addr);
+ dst_port = pj_ntohs(dst4->sin_port);
+ } else if (dst->sa_family == PJ_AF_INET6) {
+ const pj_sockaddr_in6 *dst6 = (const pj_sockaddr_in6*)dst;
+ dst_name = "IPv6";
+ dst_port = pj_ntohs(dst6->sin6_port);
+ } else {
+ LOG_ERR_(sess, "Invalid address family", PJ_EINVAL);
+ return;
+ }
+
+ PJ_LOG(5,(SNAME(sess),
+ "TX %d bytes STUN message to %s:%d:\n"
+ "--- begin STUN message ---\n"
+ "%s"
+ "--- end of STUN message ---\n",
+ pkt_size, dst_name, dst_port,
+ pj_stun_msg_dump(msg, buf, sizeof(buf), NULL)));
+
+}
+
+
+PJ_DEF(pj_status_t) pj_stun_session_send_msg( pj_stun_session *sess,
+ pj_bool_t cache_res,
+ const pj_sockaddr_t *server,
+ unsigned addr_len,
+ pj_stun_tx_data *tdata)
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(sess && addr_len && server && tdata, PJ_EINVAL);
+
+ /* Allocate packet */
+ tdata->max_len = PJ_STUN_MAX_PKT_LEN;
+ tdata->pkt = pj_pool_alloc(tdata->pool, tdata->max_len);
+
+ /* Start locking the session now */
+ pj_mutex_lock(sess->mutex);
+
+ /* Apply options */
+ status = apply_msg_options(sess, tdata->pool, tdata->msg);
+ if (status != PJ_SUCCESS) {
+ pj_stun_msg_destroy_tdata(sess, tdata);
+ pj_mutex_unlock(sess->mutex);
+ LOG_ERR_(sess, "Error applying options", status);
+ return status;
+ }
+
+ /* Encode message */
+ status = pj_stun_msg_encode(tdata->msg, tdata->pkt, tdata->max_len,
+ 0, get_passwd(sess), &tdata->pkt_size);
+ if (status != PJ_SUCCESS) {
+ pj_stun_msg_destroy_tdata(sess, tdata);
+ pj_mutex_unlock(sess->mutex);
+ LOG_ERR_(sess, "STUN encode() error", status);
+ return status;
+ }
+
+ /* Dump packet */
+ dump_tx_msg(sess, tdata->msg, tdata->pkt_size, server);
+
+ /* If this is a STUN request message, then send the request with
+ * a new STUN client transaction.
+ */
+ if (PJ_STUN_IS_REQUEST(tdata->msg->hdr.type)) {
+
+ /* Create STUN client transaction */
+ status = pj_stun_client_tsx_create(sess->endpt, tdata->pool,
+ &tsx_cb, &tdata->client_tsx);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ pj_stun_client_tsx_set_data(tdata->client_tsx, (void*)tdata);
+
+ /* Save the remote address */
+ tdata->addr_len = addr_len;
+ tdata->dst_addr = server;
+
+ /* Send the request! */
+ status = pj_stun_client_tsx_send_msg(tdata->client_tsx, PJ_TRUE,
+ tdata->pkt, tdata->pkt_size);
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ pj_stun_msg_destroy_tdata(sess, tdata);
+ pj_mutex_unlock(sess->mutex);
+ LOG_ERR_(sess, "Error sending STUN request", status);
+ return status;
+ }
+
+ /* Add to pending request list */
+ tsx_add(sess, tdata);
+
+ } else {
+ if (cache_res &&
+ (PJ_STUN_IS_RESPONSE(tdata->msg->hdr.type) ||
+ PJ_STUN_IS_ERROR_RESPONSE(tdata->msg->hdr.type)))
+ {
+ /* Requested to keep the response in the cache */
+ pj_time_val timeout;
+
+ pj_memset(&tdata->res_timer, 0, sizeof(tdata->res_timer));
+ pj_timer_entry_init(&tdata->res_timer, PJ_TRUE, tdata,
+ &on_cache_timeout);
+
+ timeout.sec = sess->endpt->res_cache_msec / 1000;
+ timeout.msec = sess->endpt->res_cache_msec % 1000;
+
+ status = pj_timer_heap_schedule(sess->endpt->timer_heap,
+ &tdata->res_timer,
+ &timeout);
+ if (status != PJ_SUCCESS) {
+ pj_stun_msg_destroy_tdata(sess, tdata);
+ pj_mutex_unlock(sess->mutex);
+ LOG_ERR_(sess, "Error scheduling response timer", status);
+ return status;
+ }
+
+ pj_list_push_back(&sess->cached_response_list, tdata);
+ }
+
+ /* Otherwise for non-request message, send directly to transport. */
+ status = sess->cb.on_send_msg(sess, tdata->pkt, tdata->pkt_size,
+ server, addr_len);
+
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ LOG_ERR_(sess, "Error sending STUN request", status);
+ }
+
+ /* Destroy only when response is not cached*/
+ if (tdata->res_timer.id == 0) {
+ pj_stun_msg_destroy_tdata(sess, tdata);
+ }
+ }
+
+
+ pj_mutex_unlock(sess->mutex);
+ return status;
+}
+
+
+/* Send response */
+static pj_status_t send_response(pj_stun_session *sess,
+ pj_pool_t *pool, pj_stun_msg *response,
+ pj_bool_t retransmission,
+ const pj_sockaddr_t *addr, unsigned addr_len)
+{
+ pj_uint8_t *out_pkt;
+ unsigned out_max_len, out_len;
+ pj_status_t status;
+
+ /* Alloc packet buffer */
+ out_max_len = PJ_STUN_MAX_PKT_LEN;
+ out_pkt = pj_pool_alloc(pool, out_max_len);
+
+ /* Apply options */
+ if (!retransmission) {
+ apply_msg_options(sess, pool, response);
+ }
+
+ /* Encode */
+ status = pj_stun_msg_encode(response, out_pkt, out_max_len, 0,
+ get_passwd(sess), &out_len);
+ if (status != PJ_SUCCESS) {
+ LOG_ERR_(sess, "Error encoding message", status);
+ return status;
+ }
+
+ /* Print log */
+ dump_tx_msg(sess, response, out_len, addr);
+
+ /* Send packet */
+ status = sess->cb.on_send_msg(sess, out_pkt, out_len, addr, addr_len);
+
+ return status;
+}
+
+/* Authenticate incoming message */
+static pj_status_t authenticate_msg(pj_stun_session *sess,
+ const pj_uint8_t *pkt,
+ unsigned pkt_len,
+ const pj_stun_msg *msg,
+ pj_pool_t *tmp_pool,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ pj_stun_msg *response;
+ pj_status_t status;
+
+ if (PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type) || sess->cred == NULL)
+ return PJ_SUCCESS;
+
+ status = pj_stun_verify_credential(pkt, pkt_len, msg, sess->cred,
+ tmp_pool, &response);
+ if (status != PJ_SUCCESS && response != NULL) {
+ PJ_LOG(5,(SNAME(sess), "Message authentication failed"));
+ send_response(sess, tmp_pool, response, PJ_FALSE,
+ src_addr, src_addr_len);
+ }
+
+ return status;
+}
+
+
+/* Handle incoming response */
+static pj_status_t on_incoming_response(pj_stun_session *sess,
+ pj_stun_msg *msg)
+{
+ pj_stun_tx_data *tdata;
+ pj_status_t status;
+
+ /* Lookup pending client transaction */
+ tdata = tsx_lookup(sess, msg);
+ if (tdata == NULL) {
+ PJ_LOG(4,(SNAME(sess),
+ "Transaction not found, response silently discarded"));
+ return PJ_SUCCESS;
+ }
+
+ /* Pass the response to the transaction.
+ * If the message is accepted, transaction callback will be called,
+ * and this will call the session callback too.
+ */
+ status = pj_stun_client_tsx_on_rx_msg(tdata->client_tsx, msg);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ /* If transaction has completed, destroy the transmit data.
+ * This will remove the transaction from the pending list too.
+ */
+ if (pj_stun_client_tsx_is_complete(tdata->client_tsx)) {
+ pj_stun_msg_destroy_tdata(sess, tdata);
+ tdata = NULL;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* For requests, check if we cache the response */
+static pj_status_t check_cached_response(pj_stun_session *sess,
+ pj_pool_t *tmp_pool,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ pj_stun_tx_data *t;
+
+ /* First lookup response in response cache */
+ t = sess->cached_response_list.next;
+ while (t != &sess->cached_response_list) {
+ if (t->msg_magic == msg->hdr.magic &&
+ pj_memcmp(t->msg_key, msg->hdr.tsx_id,
+ sizeof(msg->hdr.tsx_id))==0)
+ {
+ break;
+ }
+ t = t->next;
+ }
+
+ if (t != &sess->cached_response_list) {
+ /* Found response in the cache */
+
+ PJ_LOG(5,(SNAME(sess),
+ "Request retransmission, sending cached response"));
+
+ send_response(sess, tmp_pool, t->msg, PJ_TRUE,
+ src_addr, src_addr_len);
+ return PJ_SUCCESS;
+ }
+
+ return PJ_ENOTFOUND;
+}
+
+/* Handle incoming request */
+static pj_status_t on_incoming_request(pj_stun_session *sess,
+ pj_pool_t *tmp_pool,
+ const pj_uint8_t *in_pkt,
+ unsigned in_pkt_len,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ pj_status_t status;
+
+ /* Distribute to handler, or respond with Bad Request */
+ if (sess->cb.on_rx_request) {
+ status = (*sess->cb.on_rx_request)(sess, in_pkt, in_pkt_len, msg,
+ src_addr, src_addr_len);
+ } else {
+ pj_stun_msg *response;
+
+ status = pj_stun_msg_create_response(tmp_pool, msg,
+ PJ_STUN_SC_BAD_REQUEST, NULL,
+ &response);
+ if (status == PJ_SUCCESS && response) {
+ status = send_response(sess, tmp_pool, response,
+ PJ_FALSE, src_addr, src_addr_len);
+ }
+ }
+
+ return status;
+}
+
+
+/* Handle incoming indication */
+static pj_status_t on_incoming_indication(pj_stun_session *sess,
+ pj_pool_t *tmp_pool,
+ const pj_uint8_t *in_pkt,
+ unsigned in_pkt_len,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ PJ_UNUSED_ARG(tmp_pool);
+
+ /* Distribute to handler */
+ if (sess->cb.on_rx_indication) {
+ return (*sess->cb.on_rx_indication)(sess, in_pkt, in_pkt_len, msg,
+ src_addr, src_addr_len);
+ } else {
+ return PJ_SUCCESS;
+ }
+}
+
+
+PJ_DEF(pj_status_t) pj_stun_session_on_rx_pkt(pj_stun_session *sess,
+ const void *packet,
+ pj_size_t pkt_size,
+ unsigned options,
+ unsigned *parsed_len,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ pj_stun_msg *msg, *response;
+ pj_pool_t *tmp_pool;
+ char *dump;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(sess && packet && pkt_size, PJ_EINVAL);
+
+ tmp_pool = pj_pool_create(sess->endpt->pf, "tmpstun", 1024, 1024, NULL);
+ if (!tmp_pool)
+ return PJ_ENOMEM;
+
+ /* Try to parse the message */
+ status = pj_stun_msg_decode(tmp_pool, (const pj_uint8_t*)packet,
+ pkt_size, options,
+ &msg, parsed_len, &response);
+ if (status != PJ_SUCCESS) {
+ LOG_ERR_(sess, "STUN msg_decode() error", status);
+ if (response) {
+ send_response(sess, tmp_pool, response,
+ PJ_FALSE, src_addr, src_addr_len);
+ }
+ pj_pool_release(tmp_pool);
+ return status;
+ }
+
+ dump = pj_pool_alloc(tmp_pool, PJ_STUN_MAX_PKT_LEN);
+
+ PJ_LOG(5,(SNAME(sess),
+ "RX STUN message:\n"
+ "--- begin STUN message ---\n"
+ "%s"
+ "--- end of STUN message ---\n",
+ pj_stun_msg_dump(msg, dump, PJ_STUN_MAX_PKT_LEN, NULL)));
+
+ pj_mutex_lock(sess->mutex);
+
+ /* For requests, check if we have cached response */
+ status = check_cached_response(sess, tmp_pool, msg,
+ src_addr, src_addr_len);
+ if (status == PJ_SUCCESS) {
+ goto on_return;
+ }
+
+ /* Authenticate the message */
+ status = authenticate_msg(sess, packet, pkt_size, msg, tmp_pool,
+ src_addr, src_addr_len);
+ if (status != PJ_SUCCESS) {
+ goto on_return;
+ }
+
+ /* Handle message */
+ if (PJ_STUN_IS_RESPONSE(msg->hdr.type) ||
+ PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type))
+ {
+ status = on_incoming_response(sess, msg);
+
+ } else if (PJ_STUN_IS_REQUEST(msg->hdr.type)) {
+
+ status = on_incoming_request(sess, tmp_pool, packet, pkt_size, msg,
+ src_addr, src_addr_len);
+
+ } else if (PJ_STUN_IS_INDICATION(msg->hdr.type)) {
+
+ status = on_incoming_indication(sess, tmp_pool, packet, pkt_size,
+ msg, src_addr, src_addr_len);
+
+ } else {
+ pj_assert(!"Unexpected!");
+ status = PJ_EBUG;
+ }
+
+on_return:
+ pj_mutex_unlock(sess->mutex);
+
+ pj_pool_release(tmp_pool);
+ return status;
+}
+
+
diff --git a/pjnath/src/pjnath/stun_transaction.c b/pjnath/src/pjnath/stun_transaction.c
new file mode 100644
index 00000000..0000e3a6
--- /dev/null
+++ b/pjnath/src/pjnath/stun_transaction.c
@@ -0,0 +1,315 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjlib-util/stun_transaction.h>
+#include <pjlib-util/errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/timer.h>
+
+
+#define TIMER_ACTIVE 1
+
+
+struct pj_stun_client_tsx
+{
+ char obj_name[PJ_MAX_OBJ_NAME];
+ pj_stun_endpoint *endpt;
+ pj_stun_tsx_cb cb;
+ void *user_data;
+
+ pj_bool_t complete;
+ \
+ pj_bool_t require_retransmit;
+ pj_timer_entry timer;
+ unsigned transmit_count;
+ pj_time_val retransmit_time;
+
+ void *last_pkt;
+ unsigned last_pkt_size;
+};
+
+
+static void retransmit_timer_callback(pj_timer_heap_t *timer_heap,
+ pj_timer_entry *timer);
+
+static void stun_perror(pj_stun_client_tsx *tsx, const char *title,
+ pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(tsx->obj_name, "%s: %s", title, errmsg));
+}
+
+
+/*
+ * Create a STUN client transaction.
+ */
+PJ_DEF(pj_status_t) pj_stun_client_tsx_create(pj_stun_endpoint *endpt,
+ pj_pool_t *pool,
+ const pj_stun_tsx_cb *cb,
+ pj_stun_client_tsx **p_tsx)
+{
+ pj_stun_client_tsx *tsx;
+
+ PJ_ASSERT_RETURN(endpt && cb && p_tsx, PJ_EINVAL);
+ PJ_ASSERT_RETURN(cb->on_send_msg, PJ_EINVAL);
+
+ tsx = PJ_POOL_ZALLOC_T(pool, pj_stun_client_tsx);
+ tsx->endpt = endpt;
+ pj_memcpy(&tsx->cb, cb, sizeof(*cb));
+
+ tsx->timer.cb = &retransmit_timer_callback;
+ tsx->timer.user_data = tsx;
+
+ pj_ansi_snprintf(tsx->obj_name, sizeof(tsx->obj_name), "stuntsx%p", tsx);
+
+ *p_tsx = tsx;
+
+ PJ_LOG(4,(tsx->obj_name, "STUN client transaction created"));
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * .
+ */
+PJ_DEF(pj_status_t) pj_stun_client_tsx_destroy(pj_stun_client_tsx *tsx)
+{
+ PJ_ASSERT_RETURN(tsx, PJ_EINVAL);
+
+ if (tsx->timer.id != 0) {
+ pj_timer_heap_cancel(tsx->endpt->timer_heap, &tsx->timer);
+ tsx->timer.id = 0;
+ }
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Check if transaction has completed.
+ */
+PJ_DEF(pj_bool_t) pj_stun_client_tsx_is_complete(pj_stun_client_tsx *tsx)
+{
+ PJ_ASSERT_RETURN(tsx, PJ_FALSE);
+ return tsx->complete;
+}
+
+
+/*
+ * Set user data.
+ */
+PJ_DEF(pj_status_t) pj_stun_client_tsx_set_data(pj_stun_client_tsx *tsx,
+ void *data)
+{
+ PJ_ASSERT_RETURN(tsx, PJ_EINVAL);
+ tsx->user_data = data;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the user data
+ */
+PJ_DEF(void*) pj_stun_client_tsx_get_data(pj_stun_client_tsx *tsx)
+{
+ PJ_ASSERT_RETURN(tsx, NULL);
+ return tsx->user_data;
+}
+
+
+/*
+ * Transmit message.
+ */
+static pj_status_t tsx_transmit_msg(pj_stun_client_tsx *tsx)
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(tsx->timer.id == 0, PJ_EBUSY);
+
+ if (tsx->require_retransmit) {
+ /* Calculate retransmit/timeout delay */
+ if (tsx->transmit_count == 0) {
+ tsx->retransmit_time.sec = 0;
+ tsx->retransmit_time.msec = tsx->endpt->rto_msec;
+
+ } else if (tsx->transmit_count < PJ_STUN_MAX_RETRANSMIT_COUNT-1) {
+ unsigned msec;
+
+ msec = PJ_TIME_VAL_MSEC(tsx->retransmit_time);
+ msec = (msec << 1) + 100;
+ tsx->retransmit_time.sec = msec / 1000;
+ tsx->retransmit_time.msec = msec % 1000;
+
+ } else {
+ tsx->retransmit_time.sec = PJ_STUN_TIMEOUT_VALUE / 1000;
+ tsx->retransmit_time.msec = PJ_STUN_TIMEOUT_VALUE % 1000;
+ }
+
+ /* Schedule timer first because when send_msg() failed we can
+ * cancel it (as opposed to when schedule_timer() failed we cannot
+ * cancel transmission).
+ */;
+ status = pj_timer_heap_schedule(tsx->endpt->timer_heap, &tsx->timer,
+ &tsx->retransmit_time);
+ if (status != PJ_SUCCESS) {
+ tsx->timer.id = 0;
+ return status;
+ }
+ tsx->timer.id = TIMER_ACTIVE;
+ }
+
+
+ /* Send message */
+ status = tsx->cb.on_send_msg(tsx, tsx->last_pkt, tsx->last_pkt_size);
+ if (status != PJ_SUCCESS) {
+ if (tsx->timer.id != 0) {
+ pj_timer_heap_cancel(tsx->endpt->timer_heap, &tsx->timer);
+ tsx->timer.id = 0;
+ }
+ stun_perror(tsx, "STUN error sending message", status);
+ return status;
+ }
+
+ tsx->transmit_count++;
+
+ PJ_LOG(4,(tsx->obj_name, "STUN sending message (transmit count=%d)",
+ tsx->transmit_count));
+ return status;
+}
+
+
+/*
+ * Send outgoing message and start STUN transaction.
+ */
+PJ_DEF(pj_status_t) pj_stun_client_tsx_send_msg(pj_stun_client_tsx *tsx,
+ pj_bool_t retransmit,
+ void *pkt,
+ unsigned pkt_len)
+{
+ PJ_ASSERT_RETURN(tsx && pkt && pkt_len, PJ_EINVAL);
+ PJ_ASSERT_RETURN(tsx->timer.id == 0, PJ_EBUSY);
+
+ /* Encode message */
+ tsx->last_pkt = pkt;
+ tsx->last_pkt_size = pkt_len;
+
+ /* Update STUN retransmit flag */
+ tsx->require_retransmit = retransmit;
+
+ /* Send the message */
+ return tsx_transmit_msg(tsx);
+}
+
+
+/* Retransmit timer callback */
+static void retransmit_timer_callback(pj_timer_heap_t *timer_heap,
+ pj_timer_entry *timer)
+{
+ pj_stun_client_tsx *tsx = (pj_stun_client_tsx *) timer->user_data;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ if (tsx->transmit_count >= PJ_STUN_MAX_RETRANSMIT_COUNT) {
+ /* Retransmission count exceeded. Transaction has failed */
+ tsx->timer.id = 0;
+ PJ_LOG(4,(tsx->obj_name, "STUN timeout waiting for response"));
+ tsx->complete = PJ_TRUE;
+ if (tsx->cb.on_complete) {
+ tsx->cb.on_complete(tsx, PJLIB_UTIL_ESTUNNOTRESPOND, NULL);
+ }
+ return;
+ }
+
+ tsx->timer.id = 0;
+ status = tsx_transmit_msg(tsx);
+ if (status != PJ_SUCCESS) {
+ tsx->timer.id = 0;
+ tsx->complete = PJ_TRUE;
+ if (tsx->cb.on_complete) {
+ tsx->cb.on_complete(tsx, status, NULL);
+ }
+ }
+}
+
+
+
+/*
+ * Notify the STUN transaction about the arrival of STUN response.
+ */
+PJ_DEF(pj_status_t) pj_stun_client_tsx_on_rx_msg(pj_stun_client_tsx *tsx,
+ const pj_stun_msg *msg)
+{
+ pj_stun_errcode_attr *err_attr;
+ pj_status_t status;
+
+ /* Must be STUN response message */
+ if (!PJ_STUN_IS_RESPONSE(msg->hdr.type) &&
+ !PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type))
+ {
+ PJ_LOG(4,(tsx->obj_name,
+ "STUN rx_msg() error: not response message"));
+ return PJLIB_UTIL_ESTUNNOTRESPONSE;
+ }
+
+
+ /* We have a response with matching transaction ID.
+ * We can cancel retransmit timer now.
+ */
+ if (tsx->timer.id) {
+ pj_timer_heap_cancel(tsx->endpt->timer_heap, &tsx->timer);
+ tsx->timer.id = 0;
+ }
+
+ /* Find STUN error code attribute */
+ err_attr = (pj_stun_errcode_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_ERROR_CODE, 0);
+
+ if (err_attr && err_attr->err_class <= 2) {
+ /* draft-ietf-behave-rfc3489bis-05.txt Section 8.3.2:
+ * Any response between 100 and 299 MUST result in the cessation
+ * of request retransmissions, but otherwise is discarded.
+ */
+ PJ_LOG(4,(tsx->obj_name,
+ "STUN rx_msg() error: received provisional %d code (%.*s)",
+ err_attr->err_class * 100 + err_attr->number,
+ (int)err_attr->reason.slen,
+ err_attr->reason.ptr));
+ return PJ_SUCCESS;
+ }
+
+ if (err_attr == NULL) {
+ status = PJ_SUCCESS;
+ } else {
+ status = PJLIB_UTIL_ESTUNTSXFAILED;
+ }
+
+ /* Call callback */
+ tsx->complete = PJ_TRUE;
+ if (tsx->cb.on_complete) {
+ tsx->cb.on_complete(tsx, status, msg);
+ }
+
+ return PJ_SUCCESS;
+
+}
+
diff --git a/pjnath/src/pjstun-client/client_main.c b/pjnath/src/pjstun-client/client_main.c
new file mode 100644
index 00000000..be65b516
--- /dev/null
+++ b/pjnath/src/pjstun-client/client_main.c
@@ -0,0 +1,680 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjlib-util.h>
+#include <pjlib.h>
+
+
+#define THIS_FILE "client_main.c"
+#define LOCAL_PORT 1998
+#define BANDWIDTH 64 /* -1 to disable */
+#define LIFETIME 600 /* -1 to disable */
+#define REQ_TRANSPORT -1 /* 0: udp, 1: tcp, -1: disable */
+#define REQ_PORT_PROPS -1 /* -1 to disable */
+#define REQ_IP NULL /* IP address string */
+
+
+static struct global
+{
+ pj_stun_endpoint *endpt;
+ pj_pool_t *pool;
+ pj_caching_pool cp;
+ pj_timer_heap_t *th;
+ pj_stun_session *sess;
+ pj_sock_t sock;
+ pj_thread_t *thread;
+ pj_bool_t quit;
+ pj_sockaddr_in peer_addr;
+ pj_sockaddr_in srv_addr;
+ pj_sockaddr_in relay_addr;
+ char data_buf[256];
+ char *data;
+} g;
+
+static struct options
+{
+ char *srv_addr;
+ char *srv_port;
+ char *realm;
+ char *user_name;
+ char *password;
+ char *nonce;
+ char *peer_addr;
+ pj_bool_t use_fingerprint;
+} o;
+
+
+static pj_status_t parse_addr(const char *input, pj_sockaddr_in *addr);
+
+
+static my_perror(const char *title, pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+
+ PJ_LOG(3,(THIS_FILE, "%s: %s", title, errmsg));
+}
+
+static pj_status_t on_send_msg(pj_stun_session *sess,
+ const void *pkt,
+ pj_size_t pkt_size,
+ const pj_sockaddr_t *srv_addr,
+ unsigned addr_len)
+{
+ pj_ssize_t len;
+ pj_status_t status;
+
+ len = pkt_size;
+ status = pj_sock_sendto(g.sock, pkt, &len, 0, srv_addr, addr_len);
+
+ if (status != PJ_SUCCESS)
+ my_perror("Error sending packet", status);
+
+ return status;
+}
+
+static void on_request_complete(pj_stun_session *sess,
+ pj_status_t status,
+ pj_stun_tx_data *tdata,
+ const pj_stun_msg *response)
+{
+ if (status == PJ_SUCCESS) {
+ switch (response->hdr.type) {
+ case PJ_STUN_ALLOCATE_RESPONSE:
+ {
+ pj_stun_relay_addr_attr *ar;
+
+ ar = (pj_stun_relay_addr_attr*)
+ pj_stun_msg_find_attr(response,
+ PJ_STUN_ATTR_RELAY_ADDR, 0);
+ if (ar) {
+ pj_memcpy(&g.relay_addr, &ar->addr.ipv4,
+ sizeof(pj_sockaddr_in));
+ PJ_LOG(3,(THIS_FILE, "Relay address is %s:%d",
+ pj_inet_ntoa(g.relay_addr.sin_addr),
+ (int)pj_ntohs(g.relay_addr.sin_port)));
+ } else {
+ pj_memset(&g.relay_addr, 0, sizeof(g.relay_addr));
+ }
+ }
+ break;
+ }
+ } else {
+ my_perror("Client transaction error", status);
+ }
+}
+
+static int worker_thread(void *unused)
+{
+ PJ_UNUSED_ARG(unused);
+
+ while (!g.quit) {
+ pj_time_val timeout = {0, 50};
+ pj_fd_set_t readset;
+ int n;
+
+ pj_timer_heap_poll(g.th, NULL);
+
+ PJ_FD_ZERO(&readset);
+ PJ_FD_SET(g.sock, &readset);
+
+ n = pj_sock_select(g.sock+1, &readset, NULL, NULL, &timeout);
+ if (n > 0) {
+ if (PJ_FD_ISSET(g.sock, &readset)) {
+ char buffer[512];
+ pj_ssize_t len;
+ pj_sockaddr_in addr;
+ int addrlen;
+ pj_status_t rc;
+
+ len = sizeof(buffer);
+ addrlen = sizeof(addr);
+ rc = pj_sock_recvfrom(g.sock, buffer, &len, 0, &addr, &addrlen);
+ if (rc != PJ_SUCCESS || len <= 0)
+ continue;
+
+ if (pj_stun_msg_check(buffer, len, PJ_STUN_IS_DATAGRAM)==PJ_SUCCESS) {
+ rc = pj_stun_session_on_rx_pkt(g.sess, buffer, len,
+ 0,
+ NULL, &addr, addrlen);
+ if (rc != PJ_SUCCESS)
+ my_perror("Error processing packet", rc);
+
+ } else {
+ buffer[len] = '\0';
+ PJ_LOG(3,(THIS_FILE, "Received data: %s", (char*)buffer));
+ }
+ }
+ } else if (n < 0)
+ pj_thread_sleep(50);
+ }
+
+ return 0;
+}
+
+static int init()
+{
+ pj_sockaddr_in addr;
+ pj_stun_session_cb stun_cb;
+ int len;
+ pj_status_t status;
+
+ g.sock = PJ_INVALID_SOCKET;
+
+ status = pj_init();
+ status = pjlib_util_init();
+
+ pj_caching_pool_init(&g.cp, &pj_pool_factory_default_policy, 0);
+
+ if (o.srv_addr) {
+ pj_str_t s;
+ pj_uint16_t port;
+
+ if (o.srv_port)
+ port = (pj_uint16_t) atoi(o.srv_port);
+ else
+ port = PJ_STUN_PORT;
+
+ status = pj_sockaddr_in_init(&g.srv_addr, pj_cstr(&s, o.srv_addr), port);
+ if (status != PJ_SUCCESS) {
+ my_perror("Invalid address", status);
+ return status;
+ }
+
+ printf("Destination address set to %s:%d\n", o.srv_addr, (int)port);
+ } else {
+ printf("Error: address must be specified\n");
+ return PJ_EINVAL;
+ }
+
+ g.pool = pj_pool_create(&g.cp.factory, NULL, 1000, 1000, NULL);
+
+ status = pj_timer_heap_create(g.pool, 1000, &g.th);
+ pj_assert(status == PJ_SUCCESS);
+
+ status = pj_stun_endpoint_create(&g.cp.factory, 0, NULL, g.th, &g.endpt);
+ pj_assert(status == PJ_SUCCESS);
+
+ status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &g.sock);
+ pj_assert(status == PJ_SUCCESS);
+
+ status = pj_sockaddr_in_init(&addr, NULL, 0);
+ pj_assert(status == PJ_SUCCESS);
+
+ addr.sin_port = pj_htons((pj_uint16_t)LOCAL_PORT);
+ status = pj_sock_bind(g.sock, &addr, sizeof(addr));
+ pj_assert(status == PJ_SUCCESS);
+
+ len = sizeof(addr);
+ status = pj_sock_getsockname(g.sock, &addr, &len);
+ pj_assert(status == PJ_SUCCESS);
+
+ PJ_LOG(3,(THIS_FILE, "Listening on port %d", (int)pj_ntohs(addr.sin_port)));
+
+ pj_memcpy(&g.peer_addr, &addr, sizeof(pj_sockaddr_in));
+ if (g.peer_addr.sin_addr.s_addr == 0)
+ pj_gethostip(&g.peer_addr.sin_addr);
+
+ pj_memset(&stun_cb, 0, sizeof(stun_cb));
+ stun_cb.on_send_msg = &on_send_msg;
+ stun_cb.on_request_complete = &on_request_complete;
+
+ status = pj_stun_session_create(g.endpt, NULL, &stun_cb,
+ o.use_fingerprint!=0, &g.sess);
+ pj_assert(status == PJ_SUCCESS);
+
+ if (o.user_name) {
+ pj_stun_auth_cred cred;
+
+ pj_bzero(&cred, sizeof(cred));
+
+ cred.type = PJ_STUN_AUTH_CRED_STATIC;
+ cred.data.static_cred.realm = pj_str(o.realm);
+ cred.data.static_cred.username = pj_str(o.user_name);
+ cred.data.static_cred.data_type = 0;
+ cred.data.static_cred.data = pj_str(o.password);
+ cred.data.static_cred.nonce = pj_str(o.nonce);
+
+ pj_stun_session_set_credential(g.sess, &cred);
+ puts("Session credential set");
+ } else {
+ puts("Credential not set");
+ }
+
+ if (o.peer_addr) {
+ if (parse_addr(o.peer_addr, &g.peer_addr)!=PJ_SUCCESS)
+ return -1;
+ }
+
+ status = pj_thread_create(g.pool, "stun", &worker_thread, NULL,
+ 0, 0, &g.thread);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ return PJ_SUCCESS;
+}
+
+
+static int shutdown()
+{
+ if (g.thread) {
+ g.quit = 1;
+ pj_thread_join(g.thread);
+ pj_thread_destroy(g.thread);
+ g.thread = NULL;
+ }
+ if (g.sess)
+ pj_stun_session_destroy(g.sess);
+ if (g.endpt)
+ pj_stun_endpoint_destroy(g.endpt);
+ if (g.sock != PJ_INVALID_SOCKET)
+ pj_sock_close(g.sock);
+ if (g.th)
+ pj_timer_heap_destroy(g.th);
+ if (g.pool)
+ pj_pool_release(g.pool);
+
+ pj_pool_factory_dump(&g.cp.factory, PJ_TRUE);
+ pj_caching_pool_destroy(&g.cp);
+
+ return PJ_SUCCESS;
+}
+
+static void send_bind_request(void)
+{
+ pj_stun_tx_data *tdata;
+ pj_status_t rc;
+
+ rc = pj_stun_session_create_req(g.sess, PJ_STUN_BINDING_REQUEST, &tdata);
+ pj_assert(rc == PJ_SUCCESS);
+
+ rc = pj_stun_session_send_msg(g.sess, PJ_FALSE,
+ &g.srv_addr, sizeof(g.srv_addr),
+ tdata);
+ if (rc != PJ_SUCCESS)
+ my_perror("Error sending STUN request", rc);
+}
+
+static void send_allocate_request(pj_bool_t allocate)
+{
+ pj_stun_tx_data *tdata;
+ pj_status_t rc;
+
+ rc = pj_stun_session_create_req(g.sess, PJ_STUN_ALLOCATE_REQUEST, &tdata);
+ pj_assert(rc == PJ_SUCCESS);
+
+
+ if (BANDWIDTH != -1) {
+ pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
+ PJ_STUN_ATTR_BANDWIDTH, BANDWIDTH);
+ }
+
+ if (!allocate) {
+ pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
+ PJ_STUN_ATTR_LIFETIME, 0);
+
+ } else {
+ if (LIFETIME != -1) {
+ pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
+ PJ_STUN_ATTR_LIFETIME, LIFETIME);
+ }
+
+ if (REQ_TRANSPORT != -1) {
+ pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
+ PJ_STUN_ATTR_REQ_TRANSPORT, REQ_TRANSPORT);
+ }
+
+ if (REQ_PORT_PROPS != -1) {
+ pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg,
+ PJ_STUN_ATTR_REQ_PORT_PROPS, REQ_PORT_PROPS);
+ }
+
+ if (REQ_IP != NULL) {
+ pj_sockaddr_in addr;
+ pj_str_t tmp;
+
+ pj_sockaddr_in_init(&addr, pj_cstr(&tmp, REQ_IP), 0);
+ pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg,
+ PJ_STUN_ATTR_REQ_IP, PJ_FALSE,
+ &addr, sizeof(addr));
+ }
+ }
+
+ rc = pj_stun_session_send_msg(g.sess, PJ_FALSE,
+ &g.srv_addr, sizeof(g.srv_addr),
+ tdata);
+ pj_assert(rc == PJ_SUCCESS);
+}
+
+static void send_sad_request(pj_bool_t set)
+{
+ pj_stun_tx_data *tdata;
+ pj_status_t rc;
+
+ if (g.peer_addr.sin_addr.s_addr == 0 ||
+ g.peer_addr.sin_port == 0)
+ {
+ puts("Error: peer address is not set");
+ return;
+ }
+
+ rc = pj_stun_session_create_req(g.sess, PJ_STUN_SET_ACTIVE_DESTINATION_REQUEST, &tdata);
+ pj_assert(rc == PJ_SUCCESS);
+
+ if (set) {
+ pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg,
+ PJ_STUN_ATTR_REMOTE_ADDR, PJ_FALSE,
+ &g.peer_addr, sizeof(g.peer_addr));
+ }
+
+ rc = pj_stun_session_send_msg(g.sess, PJ_FALSE,
+ &g.srv_addr, sizeof(g.srv_addr),
+ tdata);
+ pj_assert(rc == PJ_SUCCESS);
+}
+
+static void send_send_ind(void)
+{
+ pj_stun_tx_data *tdata;
+ int len;
+ pj_status_t rc;
+
+ if (g.peer_addr.sin_addr.s_addr == 0 ||
+ g.peer_addr.sin_port == 0)
+ {
+ puts("Error: peer address is not set");
+ return;
+ }
+
+ len = strlen(g.data);
+ if (len==0) {
+ puts("Error: data is not set");
+ return;
+ }
+
+ rc = pj_stun_session_create_ind(g.sess, PJ_STUN_SEND_INDICATION, &tdata);
+ pj_assert(rc == PJ_SUCCESS);
+
+ pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg,
+ PJ_STUN_ATTR_REMOTE_ADDR, PJ_FALSE,
+ &g.peer_addr, sizeof(g.peer_addr));
+ pj_stun_msg_add_binary_attr(tdata->pool, tdata->msg,
+ PJ_STUN_ATTR_DATA, g.data, len);
+
+ rc = pj_stun_session_send_msg(g.sess, PJ_FALSE,
+ &g.srv_addr, sizeof(g.srv_addr),
+ tdata);
+ pj_assert(rc == PJ_SUCCESS);
+
+}
+
+static void send_raw_data_to_srv(void)
+{
+ pj_ssize_t len;
+
+ if (g.srv_addr.sin_addr.s_addr == 0 ||
+ g.srv_addr.sin_port == 0)
+ {
+ puts("Error: server address is not set");
+ return;
+ }
+
+ len = strlen(g.data);
+ if (len==0) {
+ puts("Error: data is not set");
+ return;
+ }
+
+ len = strlen(g.data);
+ pj_sock_sendto(g.sock, g.data, &len, 0, &g.srv_addr, sizeof(g.srv_addr));
+}
+
+static void send_raw_data_to_relay(void)
+{
+ pj_ssize_t len;
+
+ if (g.relay_addr.sin_addr.s_addr == 0 ||
+ g.relay_addr.sin_port == 0)
+ {
+ puts("Error: relay address is not set");
+ return;
+ }
+
+ len = strlen(g.data);
+ if (len==0) {
+ puts("Error: data is not set");
+ return;
+ }
+
+ len = strlen(g.data);
+ pj_sock_sendto(g.sock, g.data, &len, 0, &g.relay_addr, sizeof(g.relay_addr));
+}
+
+static pj_status_t parse_addr(const char *input,
+ pj_sockaddr_in *addr)
+{
+ const char *pos;
+ pj_str_t ip;
+ pj_uint16_t port;
+ pj_sockaddr_in tmp_addr;
+
+ pos = pj_ansi_strchr(input, ':');
+ if (pos==NULL) {
+ puts("Invalid format");
+ return -1;
+ }
+
+ ip.ptr = (char*)input;
+ ip.slen = pos - input;
+ port = (pj_uint16_t)atoi(pos+1);
+
+ if (port==0) {
+ puts("Invalid port");
+ return -1;
+ }
+
+ if (pj_sockaddr_in_init(&tmp_addr, &ip, port)!=PJ_SUCCESS) {
+ puts("Invalid address");
+ return -1;
+ }
+
+ pj_memcpy(addr, &tmp_addr, sizeof(tmp_addr));
+
+ return PJ_SUCCESS;
+}
+
+static void set_peer_addr(void)
+{
+ char addr[64];
+
+ printf("Current peer address: %s:%d\n",
+ pj_inet_ntoa(g.peer_addr.sin_addr),
+ pj_ntohs(g.peer_addr.sin_port));
+
+ printf("Input peer address in IP:PORT format: ");
+ fflush(stdout);
+ gets(addr);
+
+ if (parse_addr(addr, &g.peer_addr) != PJ_SUCCESS) {
+ return;
+ }
+
+}
+
+static void menu(void)
+{
+ puts("Menu:");
+ printf(" pr Set peer address (currently %s:%d)\n",
+ pj_inet_ntoa(g.peer_addr.sin_addr), pj_ntohs(g.peer_addr.sin_port));
+ printf(" dt Set data (currently \"%s\")\n", g.data);
+ puts(" br Send Bind request");
+ puts(" ar Send Allocate request");
+ puts(" dr Send de-Allocate request");
+ puts(" sr Send Set Active Destination request");
+ puts(" cr Send clear Active Destination request");
+ puts(" si Send data with Send Indication");
+ puts(" rw Send raw data to TURN server");
+ puts(" rW Send raw data to relay address");
+ puts(" q Quit");
+ puts("");
+ printf("Choice: ");
+}
+
+
+static void console_main(void)
+{
+ while (!g.quit) {
+ char input[10];
+
+ menu();
+
+ fgets(input, sizeof(input), stdin);
+
+ if (0) {
+
+ } else if (input[0]=='d' && input[1]=='t') {
+ printf("Input data: ");
+ gets(g.data);
+
+ } else if (input[0]=='p' && input[1]=='r') {
+ set_peer_addr();
+
+ } else if (input[0]=='b' && input[1]=='r') {
+ send_bind_request();
+
+ } else if (input[0]=='a' && input[1]=='r') {
+ send_allocate_request(PJ_TRUE);
+
+ } else if (input[0]=='d' && input[1]=='r') {
+ send_allocate_request(PJ_FALSE);
+
+ } else if (input[0]=='s' && input[1]=='r') {
+ send_sad_request(PJ_TRUE);
+
+ } else if (input[0]=='c' && input[1]=='r') {
+ send_sad_request(PJ_FALSE);
+
+ } else if (input[0]=='s' && input[1]=='i') {
+ send_send_ind();
+
+ } else if (input[0]=='r' && input[1]=='w') {
+ send_raw_data_to_srv();
+
+ } else if (input[0]=='r' && input[1]=='W') {
+ send_raw_data_to_relay();
+
+ } else if (input[0]=='q') {
+ g.quit = 1;
+ }
+ }
+}
+
+
+static void usage(void)
+{
+ puts("Usage: pjstun_client TARGET [OPTIONS]");
+ puts("");
+ puts("where TARGET is \"host[:port]\"");
+ puts("");
+ puts("and OPTIONS:");
+ puts(" --realm, -r Set realm of the credential");
+ puts(" --username, -u Set username of the credential");
+ puts(" --password, -p Set password of the credential");
+ puts(" --nonce, -N Set NONCE");
+ puts(" --fingerprint, -F Use fingerprint for outgoing requests");
+ puts(" --peer, -P Set peer address (address is in HOST:PORT format)");
+ puts(" --data, -D Set data");
+ puts(" --help, -h");
+}
+
+int main(int argc, char *argv[])
+{
+ struct pj_getopt_option long_options[] = {
+ { "realm", 1, 0, 'r'},
+ { "username", 1, 0, 'u'},
+ { "password", 1, 0, 'p'},
+ { "nonce", 1, 0, 'N'},
+ { "fingerprint",0, 0, 'F'},
+ { "peer", 1, 0, 'P'},
+ { "data", 1, 0, 'D'},
+ { "help", 0, 0, 'h'}
+ };
+ int c, opt_id;
+ char *pos;
+ pj_status_t status;
+
+ g.data = g.data_buf;
+
+ while((c=pj_getopt_long(argc,argv, "r:u:p:hF", long_options, &opt_id))!=-1) {
+ switch (c) {
+ case 'r':
+ o.realm = pj_optarg;
+ break;
+ case 'u':
+ o.user_name = pj_optarg;
+ break;
+ case 'p':
+ o.password = pj_optarg;
+ break;
+ case 'N':
+ o.nonce = pj_optarg;
+ break;
+ case 'h':
+ usage();
+ return 0;
+ case 'F':
+ o.use_fingerprint = PJ_TRUE;
+ break;
+ case 'P':
+ o.peer_addr = pj_optarg;
+ break;
+ case 'D':
+ g.data = pj_optarg;
+ break;
+
+ default:
+ printf("Argument \"%s\" is not valid. Use -h to see help",
+ argv[pj_optind]);
+ return 1;
+ }
+ }
+
+ if (pj_optind == argc) {
+ puts("Error: TARGET is needed");
+ return 1;
+ }
+
+ if ((pos=pj_ansi_strchr(argv[pj_optind], ':')) != NULL) {
+ o.srv_addr = argv[pj_optind];
+ *pos = '\0';
+ o.srv_port = pos+1;
+ } else {
+ o.srv_addr = argv[pj_optind];
+ }
+
+ status = init();
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ console_main();
+
+on_return:
+ shutdown();
+ return status ? 1 : 0;
+}
+
diff --git a/pjnath/src/pjstun-srv-test/bind_usage.c b/pjnath/src/pjstun-srv-test/bind_usage.c
new file mode 100644
index 00000000..fc10fb91
--- /dev/null
+++ b/pjnath/src/pjstun-srv-test/bind_usage.c
@@ -0,0 +1,206 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "server.h"
+
+#define THIS_FILE "bind_usage.c"
+
+static void usage_on_rx_data(pj_stun_usage *usage,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len);
+static void usage_on_destroy(pj_stun_usage *usage);
+static pj_status_t sess_on_send_msg(pj_stun_session *sess,
+ const void *pkt,
+ pj_size_t pkt_size,
+ const pj_sockaddr_t *dst_addr,
+ unsigned addr_len);
+static pj_status_t sess_on_rx_request(pj_stun_session *sess,
+ const pj_uint8_t *pkt,
+ unsigned pkt_len,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len);
+
+struct bind_usage
+{
+ pj_pool_t *pool;
+ pj_stun_usage *usage;
+ pj_stun_session *session;
+};
+
+
+PJ_DEF(pj_status_t) pj_stun_bind_usage_create(pj_stun_server *srv,
+ const pj_str_t *ip_addr,
+ unsigned port,
+ pj_stun_usage **p_bu)
+{
+ pj_pool_t *pool;
+ struct bind_usage *bu;
+ pj_stun_server_info *si;
+ pj_stun_usage_cb usage_cb;
+ pj_stun_session_cb sess_cb;
+ pj_sockaddr_in local_addr;
+ pj_status_t status;
+
+ si = pj_stun_server_get_info(srv);
+
+ pool = pj_pool_create(si->pf, "bind%p", 128, 128, NULL);
+ bu = PJ_POOL_ZALLOC_T(pool, struct bind_usage);
+ bu->pool = pool;
+
+ status = pj_sockaddr_in_init(&local_addr, ip_addr, (pj_uint16_t)port);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_bzero(&usage_cb, sizeof(usage_cb));
+ usage_cb.on_rx_data = &usage_on_rx_data;
+ usage_cb.on_destroy = &usage_on_destroy;
+
+ status = pj_stun_usage_create(srv, "bind%p", &usage_cb,
+ PJ_AF_INET, PJ_SOCK_DGRAM, 0,
+ &local_addr, sizeof(local_addr),
+ &bu->usage);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(pool);
+ return status;
+ }
+
+ pj_bzero(&sess_cb, sizeof(sess_cb));
+ sess_cb.on_send_msg = &sess_on_send_msg;
+ sess_cb.on_rx_request = &sess_on_rx_request;
+ status = pj_stun_session_create(si->endpt, "bind%p", &sess_cb, PJ_FALSE,
+ &bu->session);
+ if (status != PJ_SUCCESS) {
+ pj_stun_usage_destroy(bu->usage);
+ return status;
+ }
+
+ pj_stun_usage_set_user_data(bu->usage, bu);
+ pj_stun_session_set_user_data(bu->session, bu);
+
+ if (p_bu)
+ *p_bu = bu->usage;
+
+ return PJ_SUCCESS;
+}
+
+
+static void usage_on_rx_data(pj_stun_usage *usage,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ struct bind_usage *bu;
+ pj_stun_session *session;
+ pj_status_t status;
+
+ bu = (struct bind_usage*) pj_stun_usage_get_user_data(usage);
+ session = bu->session;
+
+ /* Handle packet to session */
+ status = pj_stun_session_on_rx_pkt(session, (pj_uint8_t*)pkt, pkt_size,
+ PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET,
+ NULL, src_addr, src_addr_len);
+ if (status != PJ_SUCCESS) {
+ pj_stun_perror(THIS_FILE, "Error handling incoming packet", status);
+ return;
+ }
+}
+
+
+static pj_status_t sess_on_send_msg(pj_stun_session *sess,
+ const void *pkt,
+ pj_size_t pkt_size,
+ const pj_sockaddr_t *dst_addr,
+ unsigned addr_len)
+{
+ struct bind_usage *bu;
+ pj_stun_usage *usage;
+
+ bu = (struct bind_usage*) pj_stun_session_get_user_data(sess);
+ usage = bu->usage;
+
+ return pj_stun_usage_sendto(usage, pkt, pkt_size, 0,
+ dst_addr, addr_len);
+}
+
+
+static pj_status_t sess_on_rx_request(pj_stun_session *sess,
+ const pj_uint8_t *pkt,
+ unsigned pkt_len,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ pj_stun_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(pkt);
+ PJ_UNUSED_ARG(pkt_len);
+
+ /* Create response */
+ status = pj_stun_session_create_response(sess, msg, 0, NULL, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Create MAPPED-ADDRESS attribute */
+ status = pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg,
+ PJ_STUN_ATTR_MAPPED_ADDR,
+ PJ_FALSE,
+ src_addr, src_addr_len);
+ if (status != PJ_SUCCESS) {
+ pj_stun_perror(THIS_FILE, "Error creating response", status);
+ pj_stun_msg_destroy_tdata(sess, tdata);
+ return status;
+ }
+
+ /* On the presence of magic, create XOR-MAPPED-ADDRESS attribute */
+ if (msg->hdr.magic == PJ_STUN_MAGIC) {
+ status =
+ pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg,
+ PJ_STUN_ATTR_XOR_MAPPED_ADDR,
+ PJ_TRUE,
+ src_addr, src_addr_len);
+ if (status != PJ_SUCCESS) {
+ pj_stun_perror(THIS_FILE, "Error creating response", status);
+ pj_stun_msg_destroy_tdata(sess, tdata);
+ return status;
+ }
+ }
+
+ /* Send */
+ status = pj_stun_session_send_msg(sess, PJ_TRUE,
+ src_addr, src_addr_len, tdata);
+ return status;
+
+}
+
+static void usage_on_destroy(pj_stun_usage *usage)
+{
+ struct bind_usage *bu;
+
+ bu = (struct bind_usage*) pj_stun_usage_get_user_data(usage);
+ if (bu==NULL)
+ return;
+
+ pj_stun_session_destroy(bu->session);
+ pj_pool_release(bu->pool);
+}
diff --git a/pjnath/src/pjstun-srv-test/main.c b/pjnath/src/pjstun-srv-test/main.c
new file mode 100644
index 00000000..c462d47e
--- /dev/null
+++ b/pjnath/src/pjstun-srv-test/main.c
@@ -0,0 +1,146 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "server.h"
+
+#define THIS_FILE "main.c"
+
+struct options
+{
+ char *realm;
+ char *user_name;
+ char *password;
+ char *nonce;
+ pj_bool_t use_fingerprint;
+} o;
+
+static void usage(void)
+{
+ puts("Usage: pjstun_srv_test [OPTIONS]");
+ puts("");
+ puts("where OPTIONS:");
+ puts(" --realm, -r Set realm of the credential");
+ puts(" --username, -u Set username of the credential");
+ puts(" --password, -p Set password of the credential");
+ puts(" --nonce, -N Set NONCE");
+ puts(" --fingerprint, -F Use fingerprint for outgoing requests");
+ puts(" --help, -h");
+}
+
+
+static void server_main(pj_stun_server *srv)
+{
+ int quit = 0;
+
+ while (!quit) {
+ char line[10];
+
+ printf("Menu:\n"
+ " d Dump status\n"
+ " q Quit\n"
+ "Choice:");
+
+ fgets(line, sizeof(line), stdin);
+ if (line[0] == 'q') {
+ quit = 1;
+ } else if (line[0] == 'd') {
+ pj_stun_server_info *si = pj_stun_server_get_info(srv);
+ pj_pool_factory_dump(si->pf, PJ_TRUE);
+ }
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ struct pj_getopt_option long_options[] = {
+ { "realm", 1, 0, 'r'},
+ { "username", 1, 0, 'u'},
+ { "password", 1, 0, 'p'},
+ { "nonce", 1, 0, 'N'},
+ { "fingerprint",0, 0, 'F'},
+ { "help", 0, 0, 'h'}
+ };
+ int c, opt_id;
+ pj_caching_pool cp;
+ pj_stun_server *srv;
+ pj_status_t status;
+
+ while((c=pj_getopt_long(argc,argv, "r:u:p:hF", long_options, &opt_id))!=-1) {
+ switch (c) {
+ case 'r':
+ o.realm = pj_optarg;
+ break;
+ case 'u':
+ o.user_name = pj_optarg;
+ break;
+ case 'p':
+ o.password = pj_optarg;
+ break;
+ case 'N':
+ o.nonce = pj_optarg;
+ break;
+ case 'h':
+ usage();
+ return 0;
+ case 'F':
+ o.use_fingerprint = PJ_TRUE;
+ break;
+ default:
+ printf("Argument \"%s\" is not valid. Use -h to see help",
+ argv[pj_optind]);
+ return 1;
+ }
+ }
+
+ if (pj_optind != argc) {
+ puts("Error: invalid arguments");
+ return 1;
+ }
+
+ pj_init();
+ pjlib_util_init();
+ pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
+
+ status = pj_stun_server_create(&cp.factory, 1, &srv);
+ if (status != PJ_SUCCESS) {
+ pj_stun_perror(THIS_FILE, "Unable to create server", status);
+ return 1;
+ }
+
+ /*
+ status = pj_stun_bind_usage_create(srv, NULL, 3478, NULL);
+ if (status != PJ_SUCCESS) {
+ pj_stun_perror(THIS_FILE, "Unable to create bind usage", status);
+ return 1;
+ }
+ */
+
+ status = pj_stun_turn_usage_create(srv, PJ_SOCK_DGRAM, NULL,
+ 3478, NULL);
+ if (status != PJ_SUCCESS) {
+ pj_stun_perror(THIS_FILE, "Unable to create bind usage", status);
+ return 1;
+ }
+
+ server_main(srv);
+
+ pj_stun_server_destroy(srv);
+ pj_pool_factory_dump(&cp.factory, PJ_TRUE);
+ pj_shutdown();
+ return 0;
+}
diff --git a/pjnath/src/pjstun-srv-test/server.c b/pjnath/src/pjstun-srv-test/server.c
new file mode 100644
index 00000000..5fdb233e
--- /dev/null
+++ b/pjnath/src/pjstun-srv-test/server.c
@@ -0,0 +1,185 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "server.h"
+
+#define THIS_FILE "server.c"
+
+struct pj_stun_server
+{
+ pj_stun_server_info si;
+
+ pj_pool_t *pool;
+
+ pj_bool_t thread_quit_flag;
+ pj_thread_t **threads;
+
+ unsigned usage_cnt;
+ pj_stun_usage *usage[32];
+};
+
+PJ_DEF(pj_status_t) pj_stun_perror( const char *sender,
+ const char *title,
+ pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+
+ PJ_LOG(3,(sender, "%s: %s", title, errmsg));
+ return status;
+}
+
+static int worker_thread(void *p)
+{
+ pj_stun_server *srv = (pj_stun_server*)p;
+
+ while (!srv->thread_quit_flag) {
+ pj_time_val timeout = { 0, 50 };
+ pj_timer_heap_poll(srv->si.timer_heap, NULL);
+ pj_ioqueue_poll(srv->si.ioqueue, &timeout);
+ }
+
+ return 0;
+}
+
+
+PJ_DEF(pj_status_t) pj_stun_server_create(pj_pool_factory *pf,
+ unsigned thread_cnt,
+ pj_stun_server **p_srv)
+{
+ pj_pool_t *pool;
+ pj_stun_server *srv;
+ unsigned i;
+ pj_status_t status;
+
+ pool = pj_pool_create(pf, "server%p", 4000, 4000, NULL);
+
+ srv = PJ_POOL_ZALLOC_T(pool, pj_stun_server);
+ srv->pool = pool;
+ srv->si.pf = pf;
+
+ status = pj_ioqueue_create(srv->pool, PJ_IOQUEUE_MAX_HANDLES,
+ &srv->si.ioqueue);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_timer_heap_create(srv->pool, 1024, &srv->si.timer_heap);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_stun_endpoint_create(srv->si.pf, 0, srv->si.ioqueue,
+ srv->si.timer_heap, &srv->si.endpt);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ srv->si.thread_cnt = thread_cnt;
+ srv->threads = pj_pool_calloc(pool, thread_cnt, sizeof(pj_thread_t*));
+ for (i=0; i<thread_cnt; ++i) {
+ status = pj_thread_create(pool, "worker%p", &worker_thread,
+ srv, 0, 0, &srv->threads[i]);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ *p_srv = srv;
+ return PJ_SUCCESS;
+
+on_error:
+ pj_stun_server_destroy(srv);
+ return status;
+}
+
+
+PJ_DEF(pj_stun_server_info*) pj_stun_server_get_info(pj_stun_server *srv)
+{
+ return &srv->si;
+}
+
+
+pj_status_t pj_stun_server_register_usage(pj_stun_server *srv,
+ pj_stun_usage *usage)
+{
+ unsigned i;
+
+ for (i=0; i<PJ_ARRAY_SIZE(srv->usage); ++i) {
+ if (srv->usage[i] == usage)
+ return PJ_SUCCESS;
+ }
+
+ for (i=0; i<PJ_ARRAY_SIZE(srv->usage); ++i) {
+ if (srv->usage[i] == NULL)
+ break;
+ }
+
+ if (i == PJ_ARRAY_SIZE(srv->usage))
+ return PJ_ETOOMANY;
+
+ srv->usage[i] = usage;
+ ++srv->usage_cnt;
+
+ return PJ_SUCCESS;
+}
+
+pj_status_t pj_stun_server_unregister_usage(pj_stun_server *srv,
+ pj_stun_usage *usage)
+{
+ unsigned i;
+
+ for (i=0; i<PJ_ARRAY_SIZE(srv->usage); ++i) {
+ if (srv->usage[i] == usage)
+ break;
+ }
+
+ if (i != PJ_ARRAY_SIZE(srv->usage)) {
+ srv->usage[i] = NULL;
+ --srv->usage_cnt;
+ return PJ_SUCCESS;
+ }
+
+ return PJ_ENOTFOUND;
+}
+
+
+PJ_DEF(pj_status_t) pj_stun_server_destroy(pj_stun_server *srv)
+{
+ unsigned i;
+
+ for (i=0; i<PJ_ARRAY_SIZE(srv->usage); ++i) {
+ if (!srv->usage[i])
+ continue;
+
+ pj_stun_usage_destroy(srv->usage[i]);
+ pj_stun_server_unregister_usage(srv, srv->usage[i]);
+ }
+
+ srv->thread_quit_flag = PJ_TRUE;
+ for (i=0; i<srv->si.thread_cnt; ++i) {
+ pj_thread_join(srv->threads[i]);
+ srv->threads[i] = NULL;
+ }
+
+ pj_stun_endpoint_destroy(srv->si.endpt);
+ pj_timer_heap_destroy(srv->si.timer_heap);
+ pj_ioqueue_destroy(srv->si.ioqueue);
+ pj_pool_release(srv->pool);
+
+ return PJ_SUCCESS;
+}
+
+
diff --git a/pjnath/src/pjstun-srv-test/server.h b/pjnath/src/pjstun-srv-test/server.h
new file mode 100644
index 00000000..a88d87c2
--- /dev/null
+++ b/pjnath/src/pjstun-srv-test/server.h
@@ -0,0 +1,135 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __STUN_SERVER_H__
+#define __STUN_SERVER_H__
+
+#include <pjlib-util.h>
+#include <pjlib.h>
+
+
+/** Opaque declaration for STUN server instance */
+typedef struct pj_stun_server pj_stun_server;
+
+/** STUN server info */
+typedef struct pj_stun_server_info
+{
+ pj_pool_factory *pf;
+ pj_stun_endpoint *endpt;
+ pj_ioqueue_t *ioqueue;
+ pj_timer_heap_t *timer_heap;
+ unsigned thread_cnt;
+} pj_stun_server_info;
+
+/** STUN usage */
+typedef struct pj_stun_usage pj_stun_usage;
+
+/** STUN usage callback */
+typedef struct pj_stun_usage_cb
+{
+ void (*on_rx_data)(pj_stun_usage *usage,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len);
+ void (*on_destroy)(pj_stun_usage *usage);
+} pj_stun_usage_cb;
+
+
+PJ_DECL(pj_status_t) pj_stun_perror(const char *sender,
+ const char *title,
+ pj_status_t status);
+
+/**
+ * Create instance of STUN server.
+ */
+PJ_DECL(pj_status_t) pj_stun_server_create(pj_pool_factory *pf,
+ unsigned thread_cnt,
+ pj_stun_server **p_srv);
+
+/**
+ * Get STUN server info.
+ */
+PJ_DECL(pj_stun_server_info*) pj_stun_server_get_info(pj_stun_server *srv);
+
+
+/**
+ * Destroy STUN server.
+ */
+PJ_DECL(pj_status_t) pj_stun_server_destroy(pj_stun_server *srv);
+
+
+/**
+ * Create STUN usage.
+ */
+PJ_DECL(pj_status_t) pj_stun_usage_create(pj_stun_server *srv,
+ const char *name,
+ const pj_stun_usage_cb *cb,
+ int family,
+ int type,
+ int protocol,
+ const pj_sockaddr_t *local_addr,
+ int addr_len,
+ pj_stun_usage **p_usage);
+
+/**
+ * Destroy usage.
+ */
+PJ_DECL(pj_status_t) pj_stun_usage_destroy(pj_stun_usage *usage);
+
+/**
+ * Set user data.
+ */
+PJ_DECL(pj_status_t) pj_stun_usage_set_user_data(pj_stun_usage *usage,
+ void *user_data);
+/**
+ * Get user data.
+ */
+PJ_DECL(void*) pj_stun_usage_get_user_data(pj_stun_usage *usage);
+
+/**
+ * Send with the usage.
+ */
+PJ_DECL(pj_status_t) pj_stun_usage_sendto(pj_stun_usage *usage,
+ const void *pkt,
+ pj_size_t pkt_size,
+ unsigned flags,
+ const pj_sockaddr_t *dst_addr,
+ unsigned addr_len);
+
+PJ_DECL(pj_status_t) pj_stun_bind_usage_create(pj_stun_server *srv,
+ const pj_str_t *ip_addr,
+ unsigned port,
+ pj_stun_usage **p_bu);
+
+PJ_DECL(pj_status_t) pj_stun_turn_usage_create(pj_stun_server *srv,
+ int type,
+ const pj_str_t *ip_addr,
+ unsigned port,
+ pj_stun_usage **p_bu);
+
+
+pj_status_t pj_stun_server_register_usage(pj_stun_server *srv,
+ pj_stun_usage *usage);
+pj_status_t pj_stun_server_unregister_usage(pj_stun_server *srv,
+ pj_stun_usage *usage);
+
+
+#endif /* __STUN_SERVER_H__ */
+
+
diff --git a/pjnath/src/pjstun-srv-test/turn_usage.c b/pjnath/src/pjstun-srv-test/turn_usage.c
new file mode 100644
index 00000000..e3d2e595
--- /dev/null
+++ b/pjnath/src/pjstun-srv-test/turn_usage.c
@@ -0,0 +1,1408 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "server.h"
+
+#define THIS_FILE "turn_usage.c"
+
+#define MAX_CLIENTS 8000
+#define MAX_PEER_PER_CLIENT 16
+#define START_PORT 2000
+#define END_PORT 65530
+
+/*
+ * Forward declarations.
+ */
+struct turn_usage;
+struct turn_client;
+
+static void tu_on_rx_data(pj_stun_usage *usage,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len);
+static void tu_on_destroy(pj_stun_usage *usage);
+static pj_status_t tu_sess_on_send_msg(pj_stun_session *sess,
+ const void *pkt,
+ pj_size_t pkt_size,
+ const pj_sockaddr_t *dst_addr,
+ unsigned addr_len);
+static pj_status_t tu_sess_on_rx_request(pj_stun_session *sess,
+ const pj_uint8_t *pkt,
+ unsigned pkt_len,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len);
+
+static pj_status_t handle_binding_req(pj_stun_session *session,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len);
+
+static pj_status_t client_create(struct turn_usage *tu,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len,
+ struct turn_client **p_client);
+static pj_status_t client_destroy(struct turn_client *client,
+ pj_status_t reason);
+static pj_status_t client_handle_stun_msg(struct turn_client *client,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len);
+
+
+struct turn_usage
+{
+ pj_pool_factory *pf;
+ pj_stun_endpoint *endpt;
+ pj_ioqueue_t *ioqueue;
+ pj_timer_heap_t *timer_heap;
+ pj_pool_t *pool;
+ pj_mutex_t *mutex;
+ pj_stun_usage *usage;
+ int type;
+ pj_stun_session *default_session;
+ pj_hash_table_t *client_htable;
+
+ unsigned max_bw_kbps;
+ unsigned max_lifetime;
+
+ unsigned next_port;
+};
+
+struct peer;
+
+struct turn_client
+{
+ char obj_name[PJ_MAX_OBJ_NAME];
+ struct turn_usage *tu;
+ pj_pool_t *pool;
+ pj_stun_session *session;
+ pj_mutex_t *mutex;
+
+ pj_sockaddr_in client_src_addr;
+
+ /* Socket and socket address of the allocated port */
+ int sock_type;
+ pj_sock_t sock;
+ pj_ioqueue_key_t *key;
+ pj_sockaddr_in alloc_addr;
+
+ /* Allocation properties */
+ unsigned bw_kbps;
+ unsigned lifetime;
+ pj_timer_entry expiry_timer;
+
+
+ /* Hash table to keep all peers, key-ed by their address */
+ pj_hash_table_t *peer_htable;
+
+ /* Active destination, or sin_addr.s_addr will be zero if
+ * no active destination is set.
+ */
+ struct peer *active_peer;
+
+ /* Current packet received/sent from/to the allocated port */
+ pj_uint8_t pkt[4000];
+ pj_sockaddr_in pkt_src_addr;
+ int pkt_src_addr_len;
+ pj_ioqueue_op_key_t pkt_read_key;
+ pj_ioqueue_op_key_t pkt_write_key;
+};
+
+struct peer
+{
+ struct turn_client *client;
+ pj_sockaddr_in addr;
+};
+
+struct session_data
+{
+ struct turn_usage *tu;
+ struct turn_client *client;
+};
+
+
+/*
+ * This is the only public API, to create and start the TURN usage.
+ */
+PJ_DEF(pj_status_t) pj_stun_turn_usage_create(pj_stun_server *srv,
+ int type,
+ const pj_str_t *ip_addr,
+ unsigned port,
+ pj_stun_usage **p_bu)
+{
+ pj_pool_t *pool;
+ struct turn_usage *tu;
+ pj_stun_server_info *si;
+ pj_stun_usage_cb usage_cb;
+ pj_stun_session_cb sess_cb;
+ struct session_data *sd;
+ pj_sockaddr_in local_addr;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(srv && (type==PJ_SOCK_DGRAM||type==PJ_SOCK_STREAM),
+ PJ_EINVAL);
+ si = pj_stun_server_get_info(srv);
+
+ pool = pj_pool_create(si->pf, "turn%p", 4000, 4000, NULL);
+ tu = PJ_POOL_ZALLOC_T(pool, struct turn_usage);
+ tu->pool = pool;
+ tu->type = type;
+ tu->pf = si->pf;
+ tu->endpt = si->endpt;
+ tu->ioqueue = si->ioqueue;
+ tu->timer_heap = si->timer_heap;
+ tu->next_port = START_PORT;
+ tu->max_bw_kbps = 64;
+ tu->max_lifetime = 10 * 60;
+
+ status = pj_sockaddr_in_init(&local_addr, ip_addr, (pj_uint16_t)port);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Create usage */
+ pj_bzero(&usage_cb, sizeof(usage_cb));
+ usage_cb.on_rx_data = &tu_on_rx_data;
+ usage_cb.on_destroy = &tu_on_destroy;
+ status = pj_stun_usage_create(srv, "turn%p", &usage_cb,
+ PJ_AF_INET, tu->type, 0,
+ &local_addr, sizeof(local_addr),
+ &tu->usage);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(pool);
+ return status;
+ }
+ pj_stun_usage_set_user_data(tu->usage, tu);
+
+ /* Init hash tables */
+ tu->client_htable = pj_hash_create(tu->pool, MAX_CLIENTS);
+
+ /* Create default session */
+ pj_bzero(&sess_cb, sizeof(sess_cb));
+ sess_cb.on_send_msg = &tu_sess_on_send_msg;
+ sess_cb.on_rx_request = &tu_sess_on_rx_request;
+ status = pj_stun_session_create(si->endpt, "turns%p", &sess_cb, PJ_FALSE,
+ &tu->default_session);
+ if (status != PJ_SUCCESS) {
+ pj_stun_usage_destroy(tu->usage);
+ return status;
+ }
+
+ sd = PJ_POOL_ZALLOC_T(pool, struct session_data);
+ sd->tu = tu;
+ pj_stun_session_set_user_data(tu->default_session, sd);
+
+ /* Create mutex */
+ status = pj_mutex_create_recursive(pool, "turn%p", &tu->mutex);
+ if (status != PJ_SUCCESS) {
+ pj_stun_usage_destroy(tu->usage);
+ return status;
+ }
+
+ if (p_bu) {
+ *p_bu = tu->usage;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * This is a callback called by usage.c when the particular STUN usage
+ * is to be destroyed.
+ */
+static void tu_on_destroy(pj_stun_usage *usage)
+{
+ struct turn_usage *tu;
+ pj_hash_iterator_t hit, *it;
+
+ tu = (struct turn_usage*) pj_stun_usage_get_user_data(usage);
+
+ /* Destroy all clients */
+ if (tu->client_htable) {
+ it = pj_hash_first(tu->client_htable, &hit);
+ while (it) {
+ struct turn_client *client;
+
+ client = (struct turn_client *)pj_hash_this(tu->client_htable, it);
+ client_destroy(client, PJ_SUCCESS);
+
+ it = pj_hash_first(tu->client_htable, &hit);
+ }
+ }
+
+ pj_stun_session_destroy(tu->default_session);
+ pj_mutex_destroy(tu->mutex);
+ pj_pool_release(tu->pool);
+}
+
+
+/*
+ * This is a callback called by the usage.c to notify the TURN usage,
+ * that incoming packet (may or may not be a STUN packet) is received
+ * on the port where the TURN usage is listening.
+ */
+static void tu_on_rx_data(pj_stun_usage *usage,
+ void *pkt,
+ pj_size_t pkt_size,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ struct turn_usage *tu;
+ struct turn_client *client;
+ unsigned flags;
+ pj_status_t status;
+
+ /* Which usage instance is this */
+ tu = (struct turn_usage*) pj_stun_usage_get_user_data(usage);
+
+ /* Lookup client structure based on source address */
+ client = (struct turn_client*) pj_hash_get(tu->client_htable, src_addr,
+ src_addr_len, NULL);
+
+ /* STUN message decoding flag */
+ flags = 0;
+ if (tu->type == PJ_SOCK_DGRAM)
+ flags |= PJ_STUN_IS_DATAGRAM;
+
+
+ if (client) {
+ status = pj_stun_msg_check(pkt, pkt_size, flags);
+
+ if (status == PJ_SUCCESS) {
+ /* Received STUN message */
+ status = pj_stun_session_on_rx_pkt(client->session,
+ (pj_uint8_t*)pkt, pkt_size,
+ flags, NULL,
+ src_addr, src_addr_len);
+ } else if (client->active_peer) {
+ /* Received non-STUN message and client has active destination */
+ pj_ssize_t sz = pkt_size;
+ pj_ioqueue_sendto(client->key, &client->pkt_write_key,
+ pkt, &sz, 0,
+ &client->active_peer->addr,
+ sizeof(client->active_peer->addr));
+ } else {
+ /* Received non-STUN message and client doesn't have active
+ * destination.
+ */
+ /* Ignore */
+ }
+
+ } else {
+ /* Received packet (could be STUN or no) from new source */
+ flags |= PJ_STUN_CHECK_PACKET;
+ pj_stun_session_on_rx_pkt(tu->default_session, (pj_uint8_t*)pkt,
+ pkt_size, flags, NULL,
+ src_addr, src_addr_len);
+ }
+}
+
+
+/*
+ * This is a utility function provided by TU (Turn Usage) to reserve
+ * or allocate internal port/socket. The allocation needs to be
+ * coordinated to minimize bind() collissions.
+ */
+static pj_status_t tu_alloc_port(struct turn_usage *tu,
+ int type,
+ unsigned rpp_bits,
+ const pj_sockaddr_in *req_addr,
+ pj_sock_t *p_sock,
+ int *err_code)
+{
+ enum { RETRY = 100 };
+ pj_sockaddr_in addr;
+ pj_sock_t sock = PJ_INVALID_SOCKET;
+ unsigned retry;
+ pj_status_t status;
+
+ if (req_addr && req_addr->sin_port != 0) {
+
+ *err_code = PJ_STUN_SC_INVALID_PORT;
+
+ /* Allocate specific port */
+ status = pj_sock_socket(PJ_AF_INET, type, 0, &sock);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Bind */
+ status = pj_sock_bind(sock, req_addr, sizeof(pj_sockaddr_in));
+ if (status != PJ_SUCCESS) {
+ pj_sock_close(sock);
+ return status;
+ }
+
+ /* Success */
+ *p_sock = sock;
+ return PJ_SUCCESS;
+
+ } else {
+ status = -1;
+ *err_code = PJ_STUN_SC_INSUFFICIENT_CAPACITY;
+
+ if (req_addr && req_addr->sin_addr.s_addr) {
+ *err_code = PJ_STUN_SC_INVALID_IP_ADDR;
+ pj_memcpy(&addr, req_addr, sizeof(pj_sockaddr_in));
+ } else {
+ pj_sockaddr_in_init(&addr, NULL, 0);
+ }
+
+ for (retry=0; retry<RETRY && sock == PJ_INVALID_SOCKET; ++retry) {
+ switch (rpp_bits) {
+ case 1:
+ if ((tu->next_port & 0x01)==0)
+ tu->next_port++;
+ break;
+ case 2:
+ case 3:
+ if ((tu->next_port & 0x01)==1)
+ tu->next_port++;
+ break;
+ }
+
+ status = pj_sock_socket(PJ_AF_INET, type, 0, &sock);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ addr.sin_port = pj_htons((pj_uint16_t)tu->next_port);
+
+ if (++tu->next_port > END_PORT)
+ tu->next_port = START_PORT;
+
+ status = pj_sock_bind(sock, &addr, sizeof(addr));
+ if (status != PJ_SUCCESS) {
+ pj_sock_close(sock);
+ sock = PJ_INVALID_SOCKET;
+
+ /* If client requested specific IP address, assume that
+ * bind failed because the IP address is not valid. We
+ * don't want to retry that since it will exhaust our
+ * port space.
+ */
+ if (req_addr && req_addr->sin_addr.s_addr)
+ break;
+ }
+ }
+
+ if (sock == PJ_INVALID_SOCKET) {
+ return status;
+ }
+
+ *p_sock = sock;
+ return PJ_SUCCESS;
+ }
+}
+
+
+/*
+ * This callback is called by the TU's STUN session when it receives
+ * a valid STUN message. This is called from tu_on_rx_data above.
+ */
+static pj_status_t tu_sess_on_rx_request(pj_stun_session *sess,
+ const pj_uint8_t *pkt,
+ unsigned pkt_len,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ struct session_data *sd;
+ struct turn_client *client;
+ pj_stun_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(pkt);
+ PJ_UNUSED_ARG(pkt_len);
+
+ sd = (struct session_data*) pj_stun_session_get_user_data(sess);
+
+ pj_assert(sd->client == NULL);
+
+ if (msg->hdr.type == PJ_STUN_BINDING_REQUEST) {
+ return handle_binding_req(sess, msg, src_addr, src_addr_len);
+
+ } else if (msg->hdr.type != PJ_STUN_ALLOCATE_REQUEST) {
+ if (PJ_STUN_IS_REQUEST(msg->hdr.type)) {
+ status = pj_stun_session_create_response(sess, msg,
+ PJ_STUN_SC_NO_BINDING,
+ NULL, &tdata);
+ if (status==PJ_SUCCESS) {
+ status = pj_stun_session_send_msg(sess, PJ_FALSE,
+ src_addr, src_addr_len,
+ tdata);
+ }
+ } else {
+ PJ_LOG(4,(THIS_FILE,
+ "Received %s %s without matching Allocation, "
+ "ignored", pj_stun_get_method_name(msg->hdr.type),
+ pj_stun_get_class_name(msg->hdr.type)));
+ }
+ return PJ_SUCCESS;
+ }
+
+ status = client_create(sd->tu, src_addr, src_addr_len, &client);
+ if (status != PJ_SUCCESS) {
+ pj_stun_perror(THIS_FILE, "Error creating new TURN client",
+ status);
+ return status;
+ }
+
+
+ /* Hand over message to client */
+ pj_mutex_lock(client->mutex);
+ status = client_handle_stun_msg(client, msg, src_addr, src_addr_len);
+ pj_mutex_unlock(client->mutex);
+
+ return status;
+}
+
+
+/*
+ * This callback is called by STUN session when it needs to send packet
+ * to the network.
+ */
+static pj_status_t tu_sess_on_send_msg(pj_stun_session *sess,
+ const void *pkt,
+ pj_size_t pkt_size,
+ const pj_sockaddr_t *dst_addr,
+ unsigned addr_len)
+{
+ struct session_data *sd;
+
+ sd = (struct session_data*) pj_stun_session_get_user_data(sess);
+
+ if (sd->tu->type == PJ_SOCK_DGRAM) {
+ return pj_stun_usage_sendto(sd->tu->usage, pkt, pkt_size, 0,
+ dst_addr, addr_len);
+ } else {
+ return PJ_ENOTSUP;
+ }
+}
+
+
+/****************************************************************************/
+/*
+ * TURN client operations.
+ */
+
+/* Function prototypes */
+static pj_status_t client_create_relay(struct turn_client *client);
+static pj_status_t client_destroy_relay(struct turn_client *client);
+static void client_on_expired(pj_timer_heap_t *th, pj_timer_entry *e);
+static void client_on_read_complete(pj_ioqueue_key_t *key,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_read);
+static pj_status_t client_respond(struct turn_client *client,
+ const pj_stun_msg *msg,
+ int err_code,
+ const char *err_msg,
+ const pj_sockaddr_t *dst_addr,
+ int dst_addr_len);
+static struct peer* client_get_peer(struct turn_client *client,
+ const pj_sockaddr_in *peer_addr,
+ pj_uint32_t *hval);
+static struct peer* client_add_peer(struct turn_client *client,
+ const pj_sockaddr_in *peer_addr,
+ pj_uint32_t hval);
+
+static const char *get_tp_type(int type)
+{
+ if (type==PJ_SOCK_DGRAM)
+ return "udp";
+ else if (type==PJ_SOCK_STREAM)
+ return "tcp";
+ else
+ return "???";
+}
+
+
+/*
+ * This callback is called when incoming STUN message is received
+ * in the TURN usage. This is called from by tu_on_rx_data() when
+ * the packet is handed over to the client.
+ */
+static pj_status_t client_sess_on_rx_msg(pj_stun_session *sess,
+ const pj_uint8_t *pkt,
+ unsigned pkt_len,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ struct session_data *sd;
+
+ PJ_UNUSED_ARG(pkt);
+ PJ_UNUSED_ARG(pkt_len);
+
+ sd = (struct session_data*) pj_stun_session_get_user_data(sess);
+ pj_assert(sd->client != PJ_SUCCESS);
+
+ return client_handle_stun_msg(sd->client, msg, src_addr, src_addr_len);
+}
+
+
+/*
+ * This callback is called by client's STUN session to send outgoing
+ * STUN packet. It's called when client calls pj_stun_session_send_msg()
+ * function.
+ */
+static pj_status_t client_sess_on_send_msg(pj_stun_session *sess,
+ const void *pkt,
+ pj_size_t pkt_size,
+ const pj_sockaddr_t *dst_addr,
+ unsigned addr_len)
+{
+ struct session_data *sd;
+
+ sd = (struct session_data*) pj_stun_session_get_user_data(sess);
+
+ if (sd->tu->type == PJ_SOCK_DGRAM) {
+ return pj_stun_usage_sendto(sd->tu->usage, pkt, pkt_size, 0,
+ dst_addr, addr_len);
+ } else {
+ return PJ_ENOTSUP;
+ }
+}
+
+
+/*
+ * Create a new TURN client for the specified source address.
+ */
+static pj_status_t client_create(struct turn_usage *tu,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len,
+ struct turn_client **p_client)
+{
+ pj_pool_t *pool;
+ struct turn_client *client;
+ pj_stun_session_cb sess_cb;
+ struct session_data *sd;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(src_addr_len==sizeof(pj_sockaddr_in), PJ_EINVAL);
+
+ pool = pj_pool_create(tu->pf, "turnc%p", 4000, 4000, NULL);
+ client = PJ_POOL_ZALLOC_T(pool, struct turn_client);
+ client->pool = pool;
+ client->tu = tu;
+ client->sock = PJ_INVALID_SOCKET;
+
+ pj_memcpy(&client->client_src_addr, src_addr,
+ sizeof(client->client_src_addr));
+
+ if (src_addr) {
+ const pj_sockaddr_in *a4 = (const pj_sockaddr_in *)src_addr;
+ pj_ansi_snprintf(client->obj_name, sizeof(client->obj_name),
+ "%s:%s:%d",
+ get_tp_type(tu->type),
+ pj_inet_ntoa(a4->sin_addr),
+ (int)pj_ntohs(a4->sin_port));
+ client->obj_name[sizeof(client->obj_name)-1] = '\0';
+ }
+
+ /* Create session */
+ pj_bzero(&sess_cb, sizeof(sess_cb));
+ sess_cb.on_send_msg = &client_sess_on_send_msg;
+ sess_cb.on_rx_request = &client_sess_on_rx_msg;
+ sess_cb.on_rx_indication = &client_sess_on_rx_msg;
+ status = pj_stun_session_create(tu->endpt, client->obj_name,
+ &sess_cb, PJ_FALSE,
+ &client->session);
+ if (status != PJ_SUCCESS) {
+ pj_pool_release(pool);
+ return status;
+ }
+
+ sd = PJ_POOL_ZALLOC_T(pool, struct session_data);
+ sd->tu = tu;
+ sd->client = client;
+ pj_stun_session_set_user_data(client->session, sd);
+
+ /* Mutex */
+ status = pj_mutex_create_recursive(client->pool, pool->obj_name,
+ &client->mutex);
+ if (status != PJ_SUCCESS) {
+ client_destroy(client, status);
+ return status;
+ }
+
+ /* Create hash table */
+ client->peer_htable = pj_hash_create(client->pool, MAX_PEER_PER_CLIENT);
+ if (client->peer_htable == NULL) {
+ client_destroy(client, status);
+ return PJ_ENOMEM;
+ }
+
+ /* Init timer entry */
+ client->expiry_timer.user_data = client;
+ client->expiry_timer.cb = &client_on_expired;
+ client->expiry_timer.id = 0;
+
+ /* Register to hash table */
+ pj_mutex_lock(tu->mutex);
+ pj_hash_set(pool, tu->client_htable, src_addr, src_addr_len, 0, client);
+ pj_mutex_unlock(tu->mutex);
+
+ /* Done */
+ *p_client = client;
+
+ PJ_LOG(4,(THIS_FILE, "TURN client %s created", client->obj_name));
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Destroy TURN client.
+ */
+static pj_status_t client_destroy(struct turn_client *client,
+ pj_status_t reason)
+{
+ struct turn_usage *tu = client->tu;
+ char name[PJ_MAX_OBJ_NAME];
+
+ pj_assert(sizeof(name)==sizeof(client->obj_name));
+ pj_memcpy(name, client->obj_name, sizeof(name));
+
+ /* Kill timer if it's active */
+ if (client->expiry_timer.id != 0) {
+ pj_timer_heap_cancel(tu->timer_heap, &client->expiry_timer);
+ client->expiry_timer.id = PJ_FALSE;
+ }
+
+ /* Destroy relay */
+ client_destroy_relay(client);
+
+ /* Unregister client from hash table */
+ pj_mutex_lock(tu->mutex);
+ pj_hash_set(NULL, tu->client_htable,
+ &client->client_src_addr, sizeof(client->client_src_addr),
+ 0, NULL);
+ pj_mutex_unlock(tu->mutex);
+
+ /* Destroy STUN session */
+ if (client->session) {
+ pj_stun_session_destroy(client->session);
+ client->session = NULL;
+ }
+
+ /* Mutex */
+ if (client->mutex) {
+ pj_mutex_destroy(client->mutex);
+ client->mutex = NULL;
+ }
+
+ /* Finally destroy pool */
+ if (client->pool) {
+ pj_pool_t *pool = client->pool;
+ client->pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ if (reason == PJ_SUCCESS) {
+ PJ_LOG(4,(THIS_FILE, "TURN client %s destroyed", name));
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * This utility function is used to setup relay (with ioqueue) after
+ * socket has been allocated for the TURN client.
+ */
+static pj_status_t client_create_relay(struct turn_client *client)
+{
+ pj_ioqueue_callback client_ioq_cb;
+ int addrlen;
+ pj_status_t status;
+
+ /* Update address */
+ addrlen = sizeof(pj_sockaddr_in);
+ status = pj_sock_getsockname(client->sock, &client->alloc_addr,
+ &addrlen);
+ if (status != PJ_SUCCESS) {
+ pj_sock_close(client->sock);
+ client->sock = PJ_INVALID_SOCKET;
+ return status;
+ }
+
+ if (client->alloc_addr.sin_addr.s_addr == 0) {
+ status = pj_gethostip(&client->alloc_addr.sin_addr);
+ if (status != PJ_SUCCESS) {
+ pj_sock_close(client->sock);
+ client->sock = PJ_INVALID_SOCKET;
+ return status;
+ }
+ }
+
+ /* Register to ioqueue */
+ pj_bzero(&client_ioq_cb, sizeof(client_ioq_cb));
+ client_ioq_cb.on_read_complete = &client_on_read_complete;
+ status = pj_ioqueue_register_sock(client->pool, client->tu->ioqueue,
+ client->sock, client,
+ &client_ioq_cb, &client->key);
+ if (status != PJ_SUCCESS) {
+ pj_sock_close(client->sock);
+ client->sock = PJ_INVALID_SOCKET;
+ return status;
+ }
+
+ pj_ioqueue_op_key_init(&client->pkt_read_key,
+ sizeof(client->pkt_read_key));
+ pj_ioqueue_op_key_init(&client->pkt_write_key,
+ sizeof(client->pkt_write_key));
+
+ /* Trigger the first read */
+ client_on_read_complete(client->key, &client->pkt_read_key, 0);
+
+ PJ_LOG(4,(THIS_FILE, "TURN client %s: relay allocated on %s:%s:%d",
+ client->obj_name,
+ get_tp_type(client->sock_type),
+ pj_inet_ntoa(client->alloc_addr.sin_addr),
+ (int)pj_ntohs(client->alloc_addr.sin_port)));
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * This utility function is used to destroy the port allocated for
+ * the TURN client.
+ */
+static pj_status_t client_destroy_relay(struct turn_client *client)
+{
+ /* Close socket */
+ if (client->key) {
+ pj_ioqueue_unregister(client->key);
+ client->key = NULL;
+ client->sock = PJ_INVALID_SOCKET;
+ } else if (client->sock && client->sock != PJ_INVALID_SOCKET) {
+ pj_sock_close(client->sock);
+ client->sock = PJ_INVALID_SOCKET;
+ }
+
+ PJ_LOG(4,(THIS_FILE, "TURN client %s: relay allocation %s:%s:%d destroyed",
+ client->obj_name,
+ get_tp_type(client->sock_type),
+ pj_inet_ntoa(client->alloc_addr.sin_addr),
+ (int)pj_ntohs(client->alloc_addr.sin_port)));
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * From the source packet address, get the peer instance from hash table.
+ */
+static struct peer* client_get_peer(struct turn_client *client,
+ const pj_sockaddr_in *peer_addr,
+ pj_uint32_t *hval)
+{
+ return (struct peer*)
+ pj_hash_get(client->peer_htable, peer_addr, sizeof(*peer_addr), hval);
+}
+
+
+/*
+ * Add a peer instance to the peer hash table.
+ */
+static struct peer* client_add_peer(struct turn_client *client,
+ const pj_sockaddr_in *peer_addr,
+ unsigned hval)
+{
+ struct peer *peer;
+
+ peer = PJ_POOL_ZALLOC_T(client->pool, struct peer);
+ peer->client = client;
+ pj_memcpy(&peer->addr, peer_addr, sizeof(peer->addr));
+
+ pj_hash_set(client->pool, client->peer_htable,
+ &peer->addr, sizeof(peer->addr), hval, peer);
+
+ PJ_LOG(4,(THIS_FILE, "TURN client %s: peer %s:%s:%d added",
+ client->obj_name, get_tp_type(client->sock_type),
+ pj_inet_ntoa(peer->addr.sin_addr),
+ (int)pj_ntohs(peer->addr.sin_port)));
+
+ return peer;
+}
+
+
+/*
+ * Utility to send STUN response message (normally to send error response).
+ */
+static pj_status_t client_respond(struct turn_client *client,
+ const pj_stun_msg *msg,
+ int err_code,
+ const char *custom_msg,
+ const pj_sockaddr_t *dst_addr,
+ int dst_addr_len)
+{
+ pj_str_t err_msg;
+ pj_str_t *p_err_msg = NULL;
+ pj_stun_tx_data *response;
+ pj_status_t status;
+
+ if (custom_msg)
+ pj_cstr(&err_msg, custom_msg), p_err_msg = &err_msg;
+
+ status = pj_stun_session_create_response(client->session, msg,
+ err_code, p_err_msg,
+ &response);
+ if (status == PJ_SUCCESS)
+ status = pj_stun_session_send_msg(client->session, PJ_TRUE,
+ dst_addr, dst_addr_len, response);
+
+ return status;
+}
+
+
+/*
+ * Handle incoming initial or subsequent Allocate Request.
+ * This function is called by client_handle_stun_msg() below.
+ */
+static pj_status_t client_handle_allocate_req(struct turn_client *client,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ const pj_stun_bandwidth_attr *a_bw;
+ const pj_stun_lifetime_attr *a_lf;
+ const pj_stun_req_port_props_attr *a_rpp;
+ const pj_stun_req_transport_attr *a_rt;
+ const pj_stun_req_ip_attr *a_rip;
+ pj_stun_tx_data *response;
+ pj_sockaddr_in req_addr;
+ int addr_len;
+ unsigned req_bw, rpp_bits;
+ pj_time_val timeout;
+ pj_status_t status;
+
+ a_bw = (const pj_stun_bandwidth_attr *)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_BANDWIDTH, 0);
+ a_lf = (const pj_stun_lifetime_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_LIFETIME, 0);
+ a_rpp = (const pj_stun_req_port_props_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REQ_PORT_PROPS, 0);
+ a_rt = (const pj_stun_req_transport_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REQ_TRANSPORT, 0);
+ a_rip = (const pj_stun_req_ip_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REQ_IP, 0);
+
+ /* Init requested local address */
+ pj_sockaddr_in_init(&req_addr, NULL, 0);
+
+ /* Process BANDWIDTH attribute */
+ if (a_bw && a_bw->value > client->tu->max_bw_kbps) {
+ client_respond(client, msg, PJ_STUN_SC_INSUFFICIENT_CAPACITY, NULL,
+ src_addr, src_addr_len);
+ return PJ_SUCCESS;
+ } else if (a_bw) {
+ client->bw_kbps = req_bw = a_bw->value;
+ } else {
+ req_bw = 0;
+ client->bw_kbps = client->tu->max_bw_kbps;
+ }
+
+ /* Process REQUESTED-TRANSPORT attribute */
+ if (a_rt && a_rt->value != 0) {
+ client_respond(client, msg, PJ_STUN_SC_UNSUPP_TRANSPORT_PROTO, NULL,
+ src_addr, src_addr_len);
+ return PJ_SUCCESS;
+ } else if (a_rt) {
+ client->sock_type = a_rt->value ? PJ_SOCK_STREAM : PJ_SOCK_DGRAM;
+ } else {
+ client->sock_type = client->tu->type;;
+ }
+
+ /* Process REQUESTED-IP attribute */
+ if (a_rip && a_rip->addr.addr.sa_family != PJ_AF_INET) {
+ client_respond(client, msg, PJ_STUN_SC_INVALID_IP_ADDR, NULL,
+ src_addr, src_addr_len);
+ return PJ_SUCCESS;
+
+ } else if (a_rip) {
+ req_addr.sin_addr.s_addr = a_rip->addr.ipv4.sin_addr.s_addr;
+ }
+
+ /* Process REQUESTED-PORT-PROPS attribute */
+ if (a_rpp) {
+ unsigned port;
+
+ rpp_bits = (a_rpp->value & 0x00030000) >> 16;
+ port = (a_rpp->value & 0xFFFF);
+ req_addr.sin_port = pj_htons((pj_uint8_t)port);
+ } else {
+ rpp_bits = 0;
+ }
+
+ /* Process LIFETIME attribute */
+ if (a_lf && a_lf->value > client->tu->max_lifetime) {
+ client->lifetime = client->tu->max_lifetime;
+ } else if (a_lf) {
+ client->lifetime = a_lf->value;
+ } else {
+ client->lifetime = client->tu->max_lifetime;
+ }
+
+ /* Allocate socket if we don't have one */
+ if (client->key == NULL) {
+ int err_code;
+
+ PJ_LOG(4,(THIS_FILE, "TURN client %s: received initial Allocate "
+ "request, requested type:addr:port=%s:%s:%d, rpp "
+ "bits=%d, bw=%dkbps, lifetime=%d",
+ client->obj_name, get_tp_type(client->sock_type),
+ pj_inet_ntoa(req_addr.sin_addr), pj_ntohs(req_addr.sin_port),
+ rpp_bits, client->bw_kbps, client->lifetime));
+
+ status = tu_alloc_port(client->tu, client->sock_type, rpp_bits,
+ &req_addr, &client->sock, &err_code);
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(THIS_FILE, "TURN client %s: error allocating relay port"
+ ": %s",
+ client->obj_name, errmsg));
+
+ client_respond(client, msg, err_code, NULL,
+ src_addr, src_addr_len);
+
+ return status;
+ }
+
+ status = client_create_relay(client);
+ if (status != PJ_SUCCESS) {
+ client_respond(client, msg, PJ_STUN_SC_SERVER_ERROR, NULL,
+ src_addr, src_addr_len);
+ return status;
+ }
+ } else {
+ /* Otherwise check if the port parameter stays the same */
+ /* TODO */
+ PJ_LOG(4,(THIS_FILE, "TURN client %s: received Allocate refresh, "
+ "lifetime=%d",
+ client->obj_name, client->lifetime));
+ }
+
+ /* Refresh timer */
+ if (client->expiry_timer.id != PJ_FALSE) {
+ pj_timer_heap_cancel(client->tu->timer_heap, &client->expiry_timer);
+ client->expiry_timer.id = PJ_FALSE;
+ }
+ timeout.sec = client->lifetime;
+ timeout.msec = 0;
+ pj_timer_heap_schedule(client->tu->timer_heap, &client->expiry_timer, &timeout);
+ client->expiry_timer.id = PJ_TRUE;
+
+ /* Done successfully, create and send success response */
+ status = pj_stun_session_create_response(client->session, msg,
+ 0, NULL, &response);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ pj_stun_msg_add_uint_attr(response->pool, response->msg,
+ PJ_STUN_ATTR_BANDWIDTH, client->bw_kbps);
+ pj_stun_msg_add_uint_attr(response->pool, response->msg,
+ PJ_STUN_ATTR_LIFETIME, client->lifetime);
+ pj_stun_msg_add_sockaddr_attr(response->pool, response->msg,
+ PJ_STUN_ATTR_MAPPED_ADDR, PJ_FALSE,
+ src_addr, src_addr_len);
+ pj_stun_msg_add_sockaddr_attr(response->pool, response->msg,
+ PJ_STUN_ATTR_XOR_MAPPED_ADDR, PJ_TRUE,
+ src_addr, src_addr_len);
+
+ addr_len = sizeof(req_addr);
+ pj_sock_getsockname(client->sock, &req_addr, &addr_len);
+ pj_stun_msg_add_sockaddr_attr(response->pool, response->msg,
+ PJ_STUN_ATTR_RELAY_ADDR, PJ_FALSE,
+ &client->alloc_addr, addr_len);
+
+ PJ_LOG(4,(THIS_FILE, "TURN client %s: relay allocated or refreshed, "
+ "internal address is %s:%s:%d",
+ client->obj_name,
+ get_tp_type(client->sock_type),
+ pj_inet_ntoa(req_addr.sin_addr),
+ (int)pj_ntohs(req_addr.sin_port)));
+
+ return pj_stun_session_send_msg(client->session, PJ_TRUE,
+ src_addr, src_addr_len, response);
+}
+
+
+/*
+ * Handle incoming Binding request.
+ * This function is called by client_handle_stun_msg() below.
+ */
+static pj_status_t handle_binding_req(pj_stun_session *session,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ pj_stun_tx_data *tdata;
+ pj_status_t status;
+
+ /* Create response */
+ status = pj_stun_session_create_response(session, msg, 0, NULL,
+ &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Create MAPPED-ADDRESS attribute */
+ pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg,
+ PJ_STUN_ATTR_MAPPED_ADDR,
+ PJ_FALSE,
+ src_addr, src_addr_len);
+
+ /* On the presence of magic, create XOR-MAPPED-ADDRESS attribute */
+ if (msg->hdr.magic == PJ_STUN_MAGIC) {
+ status =
+ pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg,
+ PJ_STUN_ATTR_XOR_MAPPED_ADDR,
+ PJ_TRUE,
+ src_addr, src_addr_len);
+ }
+
+ /* Send */
+ status = pj_stun_session_send_msg(session, PJ_TRUE,
+ src_addr, src_addr_len, tdata);
+ return status;
+}
+
+
+/*
+ * client handling incoming STUN Set Active Destination request
+ * This function is called by client_handle_stun_msg() below.
+ */
+static pj_status_t client_handle_sad(struct turn_client *client,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ pj_stun_remote_addr_attr *a_raddr;
+
+ a_raddr = (pj_stun_remote_addr_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REMOTE_ADDR, 0);
+ if (!a_raddr) {
+ /* Remote active destination needs to be cleared */
+ client->active_peer = NULL;
+
+ } else if (a_raddr->addr.addr.sa_family != PJ_AF_INET) {
+ /* Bad request (not IPv4) */
+ client_respond(client, msg, PJ_STUN_SC_BAD_REQUEST, NULL,
+ src_addr, src_addr_len);
+ return PJ_SUCCESS;
+
+ } else if (client->active_peer) {
+ /* Client tries to set new active destination without clearing
+ * it first. Reject with 439.
+ */
+ client_respond(client, msg, PJ_STUN_SC_TRANSITIONING, NULL,
+ src_addr, src_addr_len);
+ return PJ_SUCCESS;
+
+ } else {
+ struct peer *peer;
+ pj_uint32_t hval = 0;
+
+ /* Add a new peer/permission if we don't have one for this address */
+ peer = client_get_peer(client, &a_raddr->addr.ipv4, &hval);
+ if (peer==NULL) {
+ peer = client_add_peer(client, &a_raddr->addr.ipv4, hval);
+ }
+
+ /* Set active destination */
+ client->active_peer = peer;
+ }
+
+ if (client->active_peer) {
+ PJ_LOG(4,(THIS_FILE,
+ "TURN client %s: active destination set to %s:%d",
+ client->obj_name,
+ pj_inet_ntoa(client->active_peer->addr.sin_addr),
+ (int)pj_ntohs(client->active_peer->addr.sin_port)));
+ } else {
+ PJ_LOG(4,(THIS_FILE, "TURN client %s: active destination cleared",
+ client->obj_name));
+ }
+
+ /* Respond with successful response */
+ client_respond(client, msg, 0, NULL, src_addr, src_addr_len);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * client handling incoming STUN Send Indication
+ * This function is called by client_handle_stun_msg() below.
+ */
+static pj_status_t client_handle_send_ind(struct turn_client *client,
+ const pj_stun_msg *msg)
+{
+ pj_stun_remote_addr_attr *a_raddr;
+ pj_stun_data_attr *a_data;
+ pj_uint32_t hval = 0;
+ const pj_uint8_t *data;
+ pj_ssize_t datalen;
+
+ /* Get REMOTE-ADDRESS attribute */
+ a_raddr = (pj_stun_remote_addr_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REMOTE_ADDR, 0);
+ if (!a_raddr) {
+ /* REMOTE-ADDRESS not present, discard packet */
+ return PJ_SUCCESS;
+
+ } else if (a_raddr->addr.addr.sa_family != PJ_AF_INET) {
+ /* REMOTE-ADDRESS present but not IPv4, discard packet */
+ return PJ_SUCCESS;
+
+ }
+
+ /* Get the DATA attribute */
+ a_data = (pj_stun_data_attr*)
+ pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_DATA, 0);
+ if (a_data) {
+ data = (const pj_uint8_t *)a_data->data;
+ datalen = a_data->length;
+
+ } else if (client->sock_type == PJ_SOCK_STREAM) {
+ /* Discard if no Data and Allocation type is TCP */
+ return PJ_SUCCESS;
+
+ } else {
+ data = (const pj_uint8_t *)"";
+ datalen = 0;
+ }
+
+ /* Add to peer table if necessary */
+ if (client_get_peer(client, &a_raddr->addr.ipv4, &hval)==NULL)
+ client_add_peer(client, &a_raddr->addr.ipv4, hval);
+
+ /* Send the packet */
+ pj_ioqueue_sendto(client->key, &client->pkt_write_key,
+ data, &datalen, 0,
+ &a_raddr->addr.ipv4, sizeof(a_raddr->addr.ipv4));
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * client handling unknown incoming STUN message.
+ * This function is called by client_handle_stun_msg() below.
+ */
+static pj_status_t client_handle_unknown_msg(struct turn_client *client,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ PJ_LOG(4,(THIS_FILE, "TURN client %s: unhandled %s %s",
+ client->obj_name, pj_stun_get_method_name(msg->hdr.type),
+ pj_stun_get_class_name(msg->hdr.type)));
+
+ if (PJ_STUN_IS_REQUEST(msg->hdr.type)) {
+ return client_respond(client, msg, PJ_STUN_SC_BAD_REQUEST, NULL,
+ src_addr, src_addr_len);
+ } else {
+ /* Ignore */
+ return PJ_SUCCESS;
+ }
+}
+
+
+/*
+ * Main entry for handling STUN messages arriving on the main TURN port,
+ * for this client
+ */
+static pj_status_t client_handle_stun_msg(struct turn_client *client,
+ const pj_stun_msg *msg,
+ const pj_sockaddr_t *src_addr,
+ unsigned src_addr_len)
+{
+ pj_status_t status;
+
+ switch (msg->hdr.type) {
+ case PJ_STUN_SEND_INDICATION:
+ status = client_handle_send_ind(client, msg);
+ break;
+
+ case PJ_STUN_SET_ACTIVE_DESTINATION_REQUEST:
+ status = client_handle_sad(client, msg,
+ src_addr, src_addr_len);
+ break;
+
+ case PJ_STUN_ALLOCATE_REQUEST:
+ status = client_handle_allocate_req(client, msg,
+ src_addr, src_addr_len);
+ break;
+
+ case PJ_STUN_BINDING_REQUEST:
+ status = handle_binding_req(client->session, msg,
+ src_addr, src_addr_len);
+ break;
+
+ default:
+ status = client_handle_unknown_msg(client, msg,
+ src_addr, src_addr_len);
+ break;
+ }
+
+ return status;
+}
+
+
+PJ_INLINE(pj_uint32_t) GET_VAL32(const pj_uint8_t *pdu, unsigned pos)
+{
+ return (pdu[pos+0] << 24) +
+ (pdu[pos+1] << 16) +
+ (pdu[pos+2] << 8) +
+ (pdu[pos+3]);
+}
+
+
+/*
+ * Handle incoming data from peer
+ * This function is called by client_on_read_complete() below.
+ */
+static void client_handle_peer_data(struct turn_client *client,
+ unsigned bytes_read)
+{
+ struct peer *peer;
+ pj_bool_t has_magic_cookie;
+ pj_status_t status;
+
+ /* Has the sender been registered as peer? */
+ peer = client_get_peer(client, &client->pkt_src_addr, NULL);
+ if (peer == NULL) {
+ /* Nope. Discard packet */
+ PJ_LOG(5,(THIS_FILE,
+ "TURN client %s: discarded data from %s:%d",
+ client->obj_name,
+ pj_inet_ntoa(client->pkt_src_addr.sin_addr),
+ (int)pj_ntohs(client->pkt_src_addr.sin_port)));
+ return;
+ }
+
+ /* Check if packet has STUN magic cookie */
+ has_magic_cookie = (GET_VAL32(client->pkt, 4) == PJ_STUN_MAGIC);
+
+ /* If this is the Active Destination and the packet doesn't have
+ * STUN magic cookie, send the packet to client as is.
+ */
+ if (peer == client->active_peer && !has_magic_cookie) {
+ pj_stun_usage_sendto(client->tu->usage, client->pkt, bytes_read, 0,
+ &client->pkt_src_addr, client->pkt_src_addr_len);
+ } else {
+ /* Otherwise wrap in Data Indication */
+ pj_stun_tx_data *data_ind;
+
+ status = pj_stun_session_create_ind(client->session,
+ PJ_STUN_DATA_INDICATION,
+ &data_ind);
+ if (status != PJ_SUCCESS)
+ return;
+
+ pj_stun_msg_add_sockaddr_attr(data_ind->pool, data_ind->msg,
+ PJ_STUN_ATTR_REMOTE_ADDR, PJ_FALSE,
+ &client->pkt_src_addr,
+ client->pkt_src_addr_len);
+ pj_stun_msg_add_binary_attr(data_ind->pool, data_ind->msg,
+ PJ_STUN_ATTR_DATA,
+ client->pkt, bytes_read);
+
+
+ pj_stun_session_send_msg(client->session, PJ_FALSE,
+ &client->pkt_src_addr,
+ client->pkt_src_addr_len,
+ data_ind);
+ }
+}
+
+
+/*
+ * This callback is called by the ioqueue when read operation has
+ * completed on the allocated relay port.
+ */
+static void client_on_read_complete(pj_ioqueue_key_t *key,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_read)
+{
+ enum { MAX_LOOP = 10 };
+ struct turn_client *client;
+ unsigned count;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(op_key);
+
+ client = pj_ioqueue_get_user_data(key);
+
+ /* Lock client */
+ pj_mutex_lock(client->mutex);
+
+ for (count=0; ; ++count) {
+ unsigned flags;
+
+ if (bytes_read > 0) {
+ /* Received data from peer! */
+ client_handle_peer_data(client, bytes_read);
+
+ } else if (bytes_read < 0) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(-bytes_read, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(THIS_FILE, "TURN client %s: error reading data "
+ "from allocated relay port: %s",
+ client->obj_name, errmsg));
+ }
+
+ bytes_read = sizeof(client->pkt);
+ flags = (count >= MAX_LOOP) ? PJ_IOQUEUE_ALWAYS_ASYNC : 0;
+ client->pkt_src_addr_len = sizeof(client->pkt_src_addr);
+ status = pj_ioqueue_recvfrom(client->key,
+ &client->pkt_read_key,
+ client->pkt, &bytes_read, flags,
+ &client->pkt_src_addr,
+ &client->pkt_src_addr_len);
+ if (status == PJ_EPENDING)
+ break;
+ }
+
+ /* Unlock client */
+ pj_mutex_unlock(client->mutex);
+}
+
+
+/* On Allocation timer timeout (i.e. we don't receive new Allocate request
+ * to refresh the allocation in time)
+ */
+static void client_on_expired(pj_timer_heap_t *th, pj_timer_entry *e)
+{
+ struct turn_client *client;
+
+ PJ_UNUSED_ARG(th);
+
+ client = (struct turn_client*) e->user_data;
+
+ PJ_LOG(4,(THIS_FILE, "TURN client %s: allocation timer timeout, "
+ "destroying client",
+ client->obj_name));
+ client_destroy(client, PJ_SUCCESS);
+}
+
diff --git a/pjnath/src/pjstun-srv-test/usage.c b/pjnath/src/pjstun-srv-test/usage.c
new file mode 100644
index 00000000..a8a5c274
--- /dev/null
+++ b/pjnath/src/pjstun-srv-test/usage.c
@@ -0,0 +1,271 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "server.h"
+
+struct worker
+{
+ pj_ioqueue_op_key_t read_key;
+ unsigned index;
+ pj_uint8_t readbuf[4000];
+ pj_sockaddr src_addr;
+ int src_addr_len;
+};
+
+struct pj_stun_usage
+{
+ pj_pool_t *pool;
+ pj_stun_server *srv;
+ pj_mutex_t *mutex;
+ pj_stun_usage_cb cb;
+ int type;
+ pj_sock_t sock;
+ pj_ioqueue_key_t *key;
+ unsigned worker_cnt;
+ struct worker *worker;
+
+ pj_ioqueue_op_key_t *send_key;
+ unsigned send_count, send_index;
+
+ pj_bool_t quitting;
+ void *user_data;
+};
+
+
+static void on_read_complete(pj_ioqueue_key_t *key,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_read);
+
+/*
+ * Create STUN usage.
+ */
+PJ_DEF(pj_status_t) pj_stun_usage_create( pj_stun_server *srv,
+ const char *name,
+ const pj_stun_usage_cb *cb,
+ int family,
+ int type,
+ int protocol,
+ const pj_sockaddr_t *local_addr,
+ int addr_len,
+ pj_stun_usage **p_usage)
+{
+ pj_stun_server_info *si;
+ pj_pool_t *pool;
+ pj_stun_usage *usage;
+ pj_ioqueue_callback ioqueue_cb;
+ unsigned i;
+ pj_status_t status;
+
+ si = pj_stun_server_get_info(srv);
+
+ pool = pj_pool_create(si->pf, name, 4000, 4000, NULL);
+ usage = PJ_POOL_ZALLOC_T(pool, pj_stun_usage);
+ usage->pool = pool;
+ usage->srv = srv;
+
+ status = pj_mutex_create_simple(pool, name, &usage->mutex);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ usage->type = type;
+ status = pj_sock_socket(family, type, protocol, &usage->sock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_sock_bind(usage->sock, local_addr, addr_len);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ pj_bzero(&ioqueue_cb, sizeof(ioqueue_cb));
+ ioqueue_cb.on_read_complete = &on_read_complete;
+ status = pj_ioqueue_register_sock(usage->pool, si->ioqueue, usage->sock,
+ usage, &ioqueue_cb, &usage->key);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ usage->worker_cnt = si->thread_cnt;
+ usage->worker = pj_pool_calloc(pool, si->thread_cnt,
+ sizeof(struct worker));
+ for (i=0; i<si->thread_cnt; ++i) {
+ pj_ioqueue_op_key_init(&usage->worker[i].read_key,
+ sizeof(usage->worker[i].read_key));
+ usage->worker[i].index = i;
+ }
+
+ usage->send_count = usage->worker_cnt * 2;
+ usage->send_key = pj_pool_calloc(pool, usage->send_count,
+ sizeof(pj_ioqueue_op_key_t));
+ for (i=0; i<usage->send_count; ++i) {
+ pj_ioqueue_op_key_init(&usage->send_key[i],
+ sizeof(usage->send_key[i]));
+ }
+
+ for (i=0; i<si->thread_cnt; ++i) {
+ pj_ssize_t size;
+
+ size = sizeof(usage->worker[i].readbuf);
+ usage->worker[i].src_addr_len = sizeof(usage->worker[i].src_addr);
+ status = pj_ioqueue_recvfrom(usage->key, &usage->worker[i].read_key,
+ usage->worker[i].readbuf, &size,
+ PJ_IOQUEUE_ALWAYS_ASYNC,
+ &usage->worker[i].src_addr,
+ &usage->worker[i].src_addr_len);
+ if (status != PJ_EPENDING)
+ goto on_error;
+ }
+
+ pj_stun_server_register_usage(srv, usage);
+
+ /* Only after everything has been initialized we copy the callback,
+ * to prevent callback from being called when we encounter error
+ * during initialiation (decendant would not expect this).
+ */
+ pj_memcpy(&usage->cb, cb, sizeof(*cb));
+
+ *p_usage = usage;
+ return PJ_SUCCESS;
+
+on_error:
+ pj_stun_usage_destroy(usage);
+ return status;
+}
+
+
+/**
+ * Destroy usage.
+ */
+PJ_DEF(pj_status_t) pj_stun_usage_destroy(pj_stun_usage *usage)
+{
+ pj_stun_server_unregister_usage(usage->srv, usage);
+ if (usage->cb.on_destroy)
+ (*usage->cb.on_destroy)(usage);
+
+ if (usage->key) {
+ pj_ioqueue_unregister(usage->key);
+ usage->key = NULL;
+ usage->sock = PJ_INVALID_SOCKET;
+ } else if (usage->sock != 0 && usage->sock != PJ_INVALID_SOCKET) {
+ pj_sock_close(usage->sock);
+ usage->sock = PJ_INVALID_SOCKET;
+ }
+
+ if (usage->mutex) {
+ pj_mutex_destroy(usage->mutex);
+ usage->mutex = NULL;
+ }
+
+ if (usage->pool) {
+ pj_pool_t *pool = usage->pool;
+ usage->pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/**
+ * Set user data.
+ */
+PJ_DEF(pj_status_t) pj_stun_usage_set_user_data( pj_stun_usage *usage,
+ void *user_data)
+{
+ usage->user_data = user_data;
+ return PJ_SUCCESS;
+}
+
+/**
+ * Get user data.
+ */
+PJ_DEF(void*) pj_stun_usage_get_user_data(pj_stun_usage *usage)
+{
+ return usage->user_data;
+}
+
+
+/**
+ * Send with the usage.
+ */
+PJ_DEF(pj_status_t) pj_stun_usage_sendto( pj_stun_usage *usage,
+ const void *pkt,
+ pj_size_t pkt_size,
+ unsigned flags,
+ const pj_sockaddr_t *dst_addr,
+ unsigned addr_len)
+{
+ pj_ssize_t size = pkt_size;
+ unsigned i, count = usage->send_count, index;
+
+ pj_mutex_lock(usage->mutex);
+ for (i=0, ++usage->send_index; i<count; ++i, ++usage->send_index) {
+ if (usage->send_index >= usage->send_count)
+ usage->send_index = 0;
+
+ if (pj_ioqueue_is_pending(usage->key, &usage->send_key[usage->send_index])==0) {
+ break;
+ }
+ }
+
+ if (i==count) {
+ pj_mutex_unlock(usage->mutex);
+ return PJ_EBUSY;
+ }
+
+ index = usage->send_index;
+ pj_mutex_unlock(usage->mutex);
+
+ return pj_ioqueue_sendto(usage->key, &usage->send_key[index],
+ pkt, &size, flags,
+ dst_addr, addr_len);
+}
+
+
+static void on_read_complete(pj_ioqueue_key_t *key,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_read)
+{
+ enum { MAX_LOOP = 10 };
+ pj_stun_usage *usage = pj_ioqueue_get_user_data(key);
+ struct worker *worker = (struct worker*) op_key;
+ unsigned count;
+ pj_status_t status;
+
+ for (count=0; !usage->quitting; ++count) {
+ unsigned flags;
+
+ if (bytes_read > 0) {
+ (*usage->cb.on_rx_data)(usage, worker->readbuf, bytes_read,
+ &worker->src_addr, worker->src_addr_len);
+ } else if (bytes_read < 0) {
+ pj_stun_perror(usage->pool->obj_name, "recv() error", -bytes_read);
+ }
+
+ if (usage->quitting)
+ break;
+
+ bytes_read = sizeof(worker->readbuf);
+ flags = (count >= MAX_LOOP) ? PJ_IOQUEUE_ALWAYS_ASYNC : 0;
+ worker->src_addr_len = sizeof(worker->src_addr);
+ status = pj_ioqueue_recvfrom(usage->key, &worker->read_key,
+ worker->readbuf, &bytes_read, flags,
+ &worker->src_addr, &worker->src_addr_len);
+ if (status == PJ_EPENDING)
+ break;
+ }
+}
+