From 9e0ebffd26e56586f6f2ab0469144c3685fc388c Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Sun, 9 Mar 2008 12:55:00 +0000 Subject: More work for ticket #485: updated pjnath with TURN-07 and added authentication in the server git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1852 74dad513-b988-da41-8d7b-12977e46ad98 --- pjnath/build/pjturn_srv.dsp | 12 +- pjnath/include/pjnath/stun_msg.h | 84 ++-- pjnath/include/pjnath/stun_session.h | 36 +- pjnath/src/pjnath/stun_msg.c | 13 +- pjnath/src/pjnath/stun_msg_dump.c | 4 +- pjnath/src/pjnath/stun_session.c | 26 ++ pjnath/src/pjturn-srv/allocation.c | 550 +++++++++++++++++++------- pjnath/src/pjturn-srv/auth.c | 132 +++++++ pjnath/src/pjturn-srv/auth.h | 115 ++++++ pjnath/src/pjturn-srv/listener_udp.c | 43 +- pjnath/src/pjturn-srv/main.c | 50 +++ pjnath/src/pjturn-srv/server.c | 743 ++++++++++++++++++++++------------- pjnath/src/pjturn-srv/turn.h | 215 +++++----- 13 files changed, 1436 insertions(+), 587 deletions(-) create mode 100644 pjnath/src/pjturn-srv/auth.c create mode 100644 pjnath/src/pjturn-srv/auth.h (limited to 'pjnath') diff --git a/pjnath/build/pjturn_srv.dsp b/pjnath/build/pjturn_srv.dsp index 34f90001..01316e48 100644 --- a/pjnath/build/pjturn_srv.dsp +++ b/pjnath/build/pjturn_srv.dsp @@ -42,7 +42,7 @@ RSC=rc.exe # 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" /I "../../pjlib-util/include" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MD /W4 /GX /O2 /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c # ADD BASE RSC /l 0x409 /d "NDEBUG" # ADD RSC /l 0x409 /d "NDEBUG" BSC32=bscmake.exe @@ -66,7 +66,7 @@ LINK32=link.exe # 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" /I "../../pjlib-util/include" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FR /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FR /YX /FD /GZ /c # ADD BASE RSC /l 0x409 /d "_DEBUG" # ADD RSC /l 0x409 /d "_DEBUG" BSC32=bscmake.exe @@ -91,6 +91,10 @@ SOURCE="..\src\pjturn-srv\allocation.c" # End Source File # Begin Source File +SOURCE="..\src\pjturn-srv\auth.c" +# End Source File +# Begin Source File + SOURCE="..\src\pjturn-srv\listener_udp.c" # End Source File # Begin Source File @@ -107,6 +111,10 @@ SOURCE="..\src\pjturn-srv\server.c" # PROP Default_Filter "h;hpp;hxx;hm;inl" # Begin Source File +SOURCE="..\src\pjturn-srv\auth.h" +# End Source File +# Begin Source File + SOURCE="..\src\pjturn-srv\turn.h" # End Source File # End Group diff --git a/pjnath/include/pjnath/stun_msg.h b/pjnath/include/pjnath/stun_msg.h index 8946a71b..cde21fa9 100644 --- a/pjnath/include/pjnath/stun_msg.h +++ b/pjnath/include/pjnath/stun_msg.h @@ -297,11 +297,11 @@ typedef enum pj_stun_attr_type 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_PROPS = 0x0018,/**< REQUESTED-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_RESERVATION_TOKEN = 0x0022,/**< TURN RESERVATION-TOKEN */ PJ_STUN_ATTR_XOR_REFLECTED_FROM = 0x0023,/**< XOR-REFLECTED-FROM */ PJ_STUN_ATTR_PRIORITY = 0x0024,/**< PRIORITY */ PJ_STUN_ATTR_USE_CANDIDATE = 0x0025,/**< USE-CANDIDATE */ @@ -358,6 +358,8 @@ typedef enum pj_stun_status PJ_STUN_SC_SERVER_ERROR = 500, /**< Server Error */ PJ_STUN_SC_INSUFFICIENT_CAPACITY = 507, /**< Insufficient Capacity (TURN) */ + PJ_STUN_SC_INSUFFICIENT_PORT_CAPACITY=508, /**< Insufficient Port Capacity + (TURN) */ PJ_STUN_SC_GLOBAL_FAILURE = 600 /**< Global Failure */ } pj_stun_status; @@ -945,61 +947,48 @@ typedef struct pj_stun_sockaddr_attr pj_stun_relay_addr_attr; typedef struct pj_stun_uint_attr pj_stun_req_addr_type; /** - * This describes the TURN REQUESTED-PORT-PROPS attribute, encoded as + * This describes the TURN REQUESTED-PROPS attribute, encoded as * STUN 32bit integer attribute. Few macros are provided to manipulate * the values in this attribute: #PJ_STUN_GET_RPP_BITS(), * #PJ_STUN_SET_RPP_BITS(), #PJ_STUN_GET_RPP_PORT(), and * #PJ_STUN_SET_RPP_PORT(). - * + * * 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: + * the relayed transport address that is allocated by the server. 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 | A | Specific Port Number | + | Prop-type | Reserved = 0 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ \endverbatim - * The two bits labeled A in the diagram above are for requested port - * alignment and have the following meaning: - * - * - 00 no specific port alignment - * - 01 odd port number - * - 10 even port number - * - 11 even port number; reserve next higher port - */ -typedef struct pj_stun_uint_attr pj_stun_req_port_props_attr; - -/** - * Get the 2 bits requested port alignment value from a 32bit integral - * value of TURN REQUESTED-PORT-PROPS attribute. - */ -#define PJ_STUN_GET_RPP_BITS(u32) ((u32 >> 16) & 0x03) - -/** - * Convert 2 bits requested port alignment value to a 32bit integral - * value of TURN REQUESTED-PORT-PROPS attribute. + * The field labeled "Prop-type" is an 8-bit field specifying the + * desired property. The rest of the attribute is RFFU (Reserved For + * Future Use) and MUST be set to 0 on transmission and ignored on + * reception. The values of the "Prop-type" field are: + * + * 0x00 (Reserved) + * 0x01 Even port number + * 0x02 Pair of ports */ -#define PJ_STUN_SET_RPP_BITS(A) (A << 16) +typedef struct pj_stun_uint_attr pj_stun_req_props_attr; /** - * Get the port number in TURN REQUESTED-PORT-PROPS attribute. The port - * number is returned in host byte order. + * Get the 8bit Prop-type value from a 32bit integral value of TURN + * TURN REQUESTED-PROPS attribute. */ -#define PJ_STUN_GET_RPP_PORT(u32) pj_ntohs((pj_uint16_t)(u32 & 0x0000FFFFL)) +#define PJ_STUN_GET_PROP_TYPE(u32) (u32 >> 24) /** - * Convert port number in host byte order to 32bit value to be encoded in - * TURN REQUESTED-PORT-PROPS attribute. + * Convert 8bit Prop-type value to a 32bit integral value of TURN + * REQUESTED-PROPS attribute. */ -#define PJ_STUN_SET_RPP_PORT(port) ((pj_uint32_t)pj_htons((pj_uint16_t)(port))) +#define PJ_STUN_SET_PROP_TYPE(PropType) (PropType << 24) /** @@ -1046,23 +1035,18 @@ typedef struct pj_stun_uint_attr pj_stun_req_transport_attr; /** - * This describes the TURN REQUESTED-IP attribute. - * The REQUESTED-IP attribute is used by the client to request that a - * specific IP address be allocated by the TURN server. This attribute - * is needed since it is anticipated that TURN servers will be multi- - * homed so as to be able to allocate more than 64k transport addresses. - * As a consequence, a client needing a second transport address on the - * same interface as a previous one can use this attribute to request a - * remote address from the same TURN server interface as the TURN - * client's previous remote address. + * This describes the TURN RESERVATION-TOKEN attribute. + * The RESERVATION-TOKEN attribute contains a token that uniquely + * identifies a relayed transport address being held in reserve by the + * server. The server includes this attribute in a success response to + * tell the client about the token, and the client includes this + * attribute in a subsequent Allocate request to request the server use + * that relayed transport address for the allocation. * - * The format of this attribute is identical to XOR-MAPPED-ADDRESS. - * However, the port component of the attribute MUST be ignored by the - * server. If a client wishes to request a specific IP address and - * port, it uses both the REQUESTED-IP and REQUESTED-PORT-PROPS - * attributes. + * The attribute value is a 64-bit-long field containing the token + * value. */ -typedef struct pj_stun_sockaddr_attr pj_stun_req_ip_attr; +typedef struct pj_stun_uint64_attr pj_stun_res_token_attr; /** * This describes the XOR-REFLECTED-FROM attribute, as described by diff --git a/pjnath/include/pjnath/stun_session.h b/pjnath/include/pjnath/stun_session.h index e0d93d31..2f096d57 100644 --- a/pjnath/include/pjnath/stun_session.h +++ b/pjnath/include/pjnath/stun_session.h @@ -293,7 +293,9 @@ PJ_DECL(pj_status_t) pj_stun_session_create_ind(pj_stun_session *sess, /** * Create a STUN response message. After the message has been * successfully created, application can send the message by calling - * pj_stun_session_send_msg(). + * pj_stun_session_send_msg(). Alternatively application may use + * pj_stun_session_respond() to create and send response in one function + * call. * * @param sess The STUN session instance. * @param req The STUN request where the response is to be created. @@ -315,7 +317,6 @@ PJ_DECL(pj_status_t) pj_stun_session_create_res(pj_stun_session *sess, 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 @@ -341,6 +342,37 @@ PJ_DECL(pj_status_t) pj_stun_session_send_msg(pj_stun_session *sess, unsigned addr_len, pj_stun_tx_data *tdata); +/** + * Create and send STUN response message. + * + * @param sess The STUN session instance. + * @param req The STUN request message to be responded. + * @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 cache Specify whether session should cache this response for + * future request retransmission. If TRUE, subsequent request + * retransmission will be handled by the session and it + * will not call request callback. + * @param dst_addr Destination address of the response (or equal to the + * source address of the original request). + * @param addr_len Address length. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pj_stun_session_respond(pj_stun_session *sess, + const pj_stun_msg *req, + unsigned code, + const char *err_msg, + pj_bool_t cache, + const pj_sockaddr_t *dst_addr, + unsigned addr_len); + /** * Cancel outgoing STUN transaction. This operation is only valid for outgoing * STUN request, to cease retransmission of the request and destroy the diff --git a/pjnath/src/pjnath/stun_msg.c b/pjnath/src/pjnath/stun_msg.c index f0b48c90..3e01d4aa 100644 --- a/pjnath/src/pjnath/stun_msg.c +++ b/pjnath/src/pjnath/stun_msg.c @@ -77,6 +77,7 @@ static struct { PJ_STUN_SC_ROLE_CONFLICT, "Role Conflict"}, { PJ_STUN_SC_SERVER_ERROR, "Server Error"}, { PJ_STUN_SC_INSUFFICIENT_CAPACITY, "Insufficient Capacity"}, + { PJ_STUN_SC_INSUFFICIENT_PORT_CAPACITY,"Insufficient Port Capacity"}, { PJ_STUN_SC_GLOBAL_FAILURE, "Global Failure"} }; @@ -289,8 +290,8 @@ static struct attr_desc mandatory_attr_desc[] = &encode_uint_attr }, { - /* PJ_STUN_ATTR_REQUESTED_PORT_PROPS, */ - "REQUESTED-PORT-PROPS", + /* PJ_STUN_ATTR_REQUESTED_PROPS, */ + "REQUESTED-PROPS", &decode_uint_attr, &encode_uint_attr }, @@ -349,10 +350,10 @@ static struct attr_desc mandatory_attr_desc[] = &encode_uint_attr }, { - /* PJ_STUN_ATTR_REQUESTED_IP, */ - "REQUESTED-IP", - &decode_xored_sockaddr_attr, - &encode_sockaddr_attr + /* PJ_STUN_ATTR_RESERVATION_TOKEN, */ + "RESERVATION-TOKEN", + &decode_uint64_attr, + &encode_uint64_attr }, { /* PJ_STUN_ATTR_XOR_REFLECTED_FROM, */ diff --git a/pjnath/src/pjnath/stun_msg_dump.c b/pjnath/src/pjnath/stun_msg_dump.c index ecf65504..a5556acc 100644 --- a/pjnath/src/pjnath/stun_msg_dump.c +++ b/pjnath/src/pjnath/stun_msg_dump.c @@ -74,7 +74,6 @@ static int print_attr(char *buffer, unsigned length, case PJ_STUN_ATTR_PEER_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: @@ -117,7 +116,7 @@ static int print_attr(char *buffer, unsigned length, 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_PROPS: case PJ_STUN_ATTR_REQ_TRANSPORT: case PJ_STUN_ATTR_TIMER_VAL: case PJ_STUN_ATTR_PRIORITY: @@ -207,6 +206,7 @@ static int print_attr(char *buffer, unsigned length, break; case PJ_STUN_ATTR_ICE_CONTROLLED: case PJ_STUN_ATTR_ICE_CONTROLLING: + case PJ_STUN_ATTR_RESERVATION_TOKEN: { const pj_stun_uint64_attr *attr; pj_uint8_t data[8]; diff --git a/pjnath/src/pjnath/stun_session.c b/pjnath/src/pjnath/stun_session.c index 0f9fc733..4225fe77 100644 --- a/pjnath/src/pjnath/stun_session.c +++ b/pjnath/src/pjnath/stun_session.c @@ -711,6 +711,32 @@ PJ_DEF(pj_status_t) pj_stun_session_send_msg( pj_stun_session *sess, return status; } + +/* + * Create and send STUN response message. + */ +PJ_DEF(pj_status_t) pj_stun_session_respond( pj_stun_session *sess, + const pj_stun_msg *req, + unsigned code, + const char *errmsg, + pj_bool_t cache, + const pj_sockaddr_t *dst_addr, + unsigned addr_len) +{ + pj_status_t status; + pj_str_t reason; + pj_stun_tx_data *tdata; + + status = pj_stun_session_create_res(sess, req, code, + (errmsg?pj_cstr(&reason,errmsg):NULL), + &tdata); + if (status != PJ_SUCCESS) + return status; + + return pj_stun_session_send_msg(sess, cache, dst_addr, addr_len, tdata); +} + + /* * Cancel outgoing STUN transaction. */ diff --git a/pjnath/src/pjturn-srv/allocation.c b/pjnath/src/pjturn-srv/allocation.c index 725863ce..5698ea41 100644 --- a/pjnath/src/pjturn-srv/allocation.c +++ b/pjnath/src/pjturn-srv/allocation.c @@ -17,6 +17,8 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "turn.h" +#include "auth.h" + #define THIS_FILE "allocation.c" @@ -30,6 +32,15 @@ enum { #define DESTROY_DELAY {0, 500} #define PEER_TABLE_SIZE 32 +#define MAX_CLIENT_BANDWIDTH 128 /* In Kbps */ +#define DEFA_CLIENT_BANDWIDTH 64 + +#define MIN_LIFETIME 30 +#define MAX_LIFETIME 600 +#define DEF_LIFETIME 300 + + + /* ChannelData header */ typedef struct channel_data_hdr { @@ -38,13 +49,30 @@ typedef struct channel_data_hdr } channel_data_hdr; +/* Parsed Allocation request. */ +typedef struct alloc_request +{ + unsigned tp_type; /* Requested transport */ + char addr[PJ_INET6_ADDRSTRLEN]; /* Requested IP */ + unsigned bandwidth; /* Requested bandwidth */ + unsigned lifetime; /* Lifetime. */ + unsigned rpp_bits; /* A bits */ + unsigned rpp_port; /* Requested port */ +} alloc_request; + + + /* Prototypes */ -static pj_status_t create_relay(pjturn_allocation *alloc, - const pjturn_allocation_req *req); +static void destroy_allocation(pj_turn_allocation *alloc); +static pj_status_t create_relay(pj_turn_srv *srv, + pj_turn_allocation *alloc, + const pj_stun_msg *msg, + const alloc_request *req, + pj_turn_relay_res *relay); +static void destroy_relay(pj_turn_relay_res *relay); static void on_rx_from_peer(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read); -static void destroy_relay(pjturn_relay_res *relay); static pj_status_t stun_on_send_msg(pj_stun_session *sess, const void *pkt, pj_size_t pkt_size, @@ -64,7 +92,7 @@ static pj_status_t stun_on_rx_indication(pj_stun_session *sess, unsigned src_addr_len); /* Log allocation error */ -static void alloc_err(pjturn_allocation *alloc, const char *title, +static void alloc_err(pj_turn_allocation *alloc, const char *title, pj_status_t status) { char errmsg[PJ_ERR_MSG_SIZE]; @@ -74,34 +102,225 @@ static void alloc_err(pjturn_allocation *alloc, const char *title, title, alloc->info, errmsg)); } + +/* Parse ALLOCATE request */ +static pj_status_t parse_allocate_req(alloc_request *cfg, + pj_stun_session *sess, + const pj_stun_msg *req, + const pj_sockaddr_t *src_addr, + unsigned src_addr_len) +{ + pj_stun_bandwidth_attr *attr_bw; + pj_stun_req_transport_attr *attr_req_tp; + pj_stun_res_token_attr *attr_res_token; + pj_stun_req_props_attr *attr_rpp; + pj_stun_lifetime_attr *attr_lifetime; + + pj_bzero(cfg, sizeof(*cfg)); + + /* Get BANDWIDTH attribute, if any. */ + attr_bw = (pj_stun_uint_attr*) + pj_stun_msg_find_attr(req, PJ_STUN_ATTR_BANDWIDTH, 0); + if (attr_bw) { + cfg->bandwidth = attr_bw->value; + } else { + cfg->bandwidth = DEFA_CLIENT_BANDWIDTH; + } + + /* Check if we can satisfy the bandwidth */ + if (cfg->bandwidth > MAX_CLIENT_BANDWIDTH) { + pj_stun_session_respond(sess, req, PJ_STUN_SC_ALLOCATION_QUOTA_REACHED, + "Invalid bandwidth", PJ_TRUE, + src_addr, src_addr_len); + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_ALLOCATION_QUOTA_REACHED); + } + + /* MUST have REQUESTED-TRANSPORT attribute */ + attr_req_tp = (pj_stun_uint_attr*) + pj_stun_msg_find_attr(req, PJ_STUN_ATTR_REQ_TRANSPORT, 0); + if (attr_req_tp == NULL) { + pj_stun_session_respond(sess, req, PJ_STUN_SC_BAD_REQUEST, + "Missing REQUESTED-TRANSPORT attribute", + PJ_TRUE, src_addr, src_addr_len); + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST); + } + + cfg->tp_type = PJ_STUN_GET_RT_PROTO(attr_req_tp->value); + + /* Can only support UDP for now */ + if (cfg->tp_type != PJ_TURN_TP_UDP) { + pj_stun_session_respond(sess, req, PJ_STUN_SC_UNSUPP_TRANSPORT_PROTO, + NULL, PJ_TRUE, src_addr, src_addr_len); + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNSUPP_TRANSPORT_PROTO); + } + + /* Get RESERVATION-TOKEN attribute, if any */ + attr_res_token = (pj_stun_res_token_attr*) + pj_stun_msg_find_attr(req, PJ_STUN_ATTR_RESERVATION_TOKEN, + 0); + if (attr_res_token) { + /* We don't support RESERVATION-TOKEN for now */ + pj_stun_session_respond(sess, req, + PJ_STUN_SC_BAD_REQUEST, + "RESERVATION-TOKEN is not supported", PJ_TRUE, + src_addr, src_addr_len); + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST); + } + + /* Get REQUESTED-PROPS attribute, if any */ + attr_rpp = (pj_stun_req_props_attr*) + pj_stun_msg_find_attr(req, PJ_STUN_ATTR_REQ_PROPS, 0); + if (attr_rpp) { + /* We don't support REQUESTED-PROPS for now */ + pj_stun_session_respond(sess, req, + PJ_STUN_SC_BAD_REQUEST, + "REQUESTED-PROPS is not supported", PJ_TRUE, + src_addr, src_addr_len); + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST); + } + + /* Get LIFETIME attribute */ + attr_lifetime = (pj_stun_uint_attr*) + pj_stun_msg_find_attr(req, PJ_STUN_ATTR_LIFETIME, 0); + if (attr_lifetime) { + cfg->lifetime = attr_lifetime->value; + if (cfg->lifetime < MIN_LIFETIME) { + pj_stun_session_respond(sess, req, PJ_STUN_SC_BAD_REQUEST, + "LIFETIME too short", PJ_TRUE, + src_addr, src_addr_len); + return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_BAD_REQUEST); + } + if (cfg->lifetime > MAX_LIFETIME) + cfg->lifetime = MAX_LIFETIME; + } else { + cfg->lifetime = DEF_LIFETIME; + } + + return PJ_SUCCESS; +} + + +/* Respond to ALLOCATE request */ +static pj_status_t send_allocate_response(pj_turn_allocation *alloc, + pj_stun_session *srv_sess, + const pj_stun_msg *msg) +{ + pj_stun_tx_data *tdata; + pj_status_t status; + + /* Respond the original ALLOCATE request */ + status = pj_stun_session_create_res(srv_sess, msg, 0, NULL, &tdata); + if (status != PJ_SUCCESS) + return status; + + /* Add RELAYED-ADDRESS attribute */ + pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg, + PJ_STUN_ATTR_RELAY_ADDR, PJ_TRUE, + &alloc->relay.hkey.addr, + pj_sockaddr_get_len(&alloc->relay.hkey.addr)); + + /* Add LIFETIME. */ + pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, + PJ_STUN_ATTR_LIFETIME, + (unsigned)alloc->relay.lifetime); + + /* Add BANDWIDTH */ + pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, + PJ_STUN_ATTR_BANDWIDTH, + alloc->bandwidth); + + /* Add RESERVATION-TOKEN */ + PJ_TODO(ADD_RESERVATION_TOKEN); + + /* Add XOR-MAPPED-ADDRESS */ + pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg, + PJ_STUN_ATTR_XOR_MAPPED_ADDR, PJ_TRUE, + &alloc->hkey.clt_addr, + pj_sockaddr_get_len(&alloc->hkey.clt_addr)); + + /* Send the response */ + return pj_stun_session_send_msg(srv_sess, PJ_TRUE, + &alloc->hkey.clt_addr, + pj_sockaddr_get_len(&alloc->hkey.clt_addr), + tdata); +} + + +/* + * Init credential for the allocation. We use static credential, meaning that + * the user's password must not change during allocation. + */ +static pj_status_t init_cred(pj_turn_allocation *alloc, const pj_stun_msg *req) +{ + const pj_stun_username_attr *user; + const pj_stun_realm_attr *realm; + const pj_stun_nonce_attr *nonce; + pj_status_t status; + + realm = (const pj_stun_realm_attr*) + pj_stun_msg_find_attr(req, PJ_STUN_ATTR_REALM, 0); + PJ_ASSERT_RETURN(realm != NULL, PJ_EBUG); + + user = (const pj_stun_username_attr*) + pj_stun_msg_find_attr(req, PJ_STUN_ATTR_USERNAME, 0); + PJ_ASSERT_RETURN(user != NULL, PJ_EBUG); + + nonce = (const pj_stun_nonce_attr*) + pj_stun_msg_find_attr(req, PJ_STUN_ATTR_NONCE, 0); + PJ_ASSERT_RETURN(nonce != NULL, PJ_EBUG); + + /* Lookup the password */ + status = pj_turn_get_password(NULL, NULL, &realm->value, + &user->value, alloc->pool, + &alloc->cred.data.static_cred.data_type, + &alloc->cred.data.static_cred.data); + if (status != PJ_SUCCESS) + return status; + + /* Save credential */ + alloc->cred.type = PJ_STUN_AUTH_CRED_STATIC; + pj_strdup(alloc->pool, &alloc->cred.data.static_cred.realm, &realm->value); + pj_strdup(alloc->pool, &alloc->cred.data.static_cred.username, &user->value); + pj_strdup(alloc->pool, &alloc->cred.data.static_cred.nonce, &nonce->value); + + return PJ_SUCCESS; +} + + /* * Create new allocation. */ -PJ_DEF(pj_status_t) pjturn_allocation_create(pjturn_listener *listener, - const pj_sockaddr_t *src_addr, - unsigned src_addr_len, - const pj_stun_msg *msg, - const pjturn_allocation_req *req, - pjturn_allocation **p_alloc) +PJ_DEF(pj_status_t) pj_turn_allocation_create(pj_turn_listener *listener, + const pj_sockaddr_t *src_addr, + unsigned src_addr_len, + const pj_stun_msg *msg, + pj_stun_session *srv_sess, + pj_turn_allocation **p_alloc) { - pjturn_srv *srv = listener->server; + pj_turn_srv *srv = listener->server; pj_pool_t *pool; - pjturn_allocation *alloc; + alloc_request req; + pj_turn_allocation *alloc; pj_stun_session_cb sess_cb; - char relay_info[80]; + char str_tmp[80]; pj_status_t status; + /* Parse ALLOCATE request */ + status = parse_allocate_req(&req, srv_sess, msg, src_addr, src_addr_len); + if (status != PJ_SUCCESS) + return status; + pool = pj_pool_create(srv->core.pf, "alloc%p", 1000, 1000, NULL); /* Init allocation structure */ - alloc = PJ_POOL_ZALLOC_T(pool, pjturn_allocation); + alloc = PJ_POOL_ZALLOC_T(pool, pj_turn_allocation); alloc->pool = pool; alloc->obj_name = pool->obj_name; alloc->listener = listener; alloc->clt_sock = PJ_INVALID_SOCKET; alloc->relay.tp.sock = PJ_INVALID_SOCKET; - alloc->bandwidth = req->bandwidth; + alloc->bandwidth = req.bandwidth; alloc->hkey.tp_type = listener->tp_type; pj_memcpy(&alloc->hkey.clt_addr, src_addr, src_addr_len); @@ -109,8 +328,7 @@ PJ_DEF(pj_status_t) pjturn_allocation_create(pjturn_listener *listener, status = pj_lock_create_recursive_mutex(pool, alloc->obj_name, &alloc->lock); if (status != PJ_SUCCESS) { - pjturn_allocation_destroy(alloc); - return status; + goto on_error; } /* Create peer hash table */ @@ -120,7 +338,7 @@ PJ_DEF(pj_status_t) pjturn_allocation_create(pjturn_listener *listener, alloc->ch_table = pj_hash_create(pool, PEER_TABLE_SIZE); /* Print info */ - pj_ansi_strcpy(alloc->info, pjturn_tp_type_name(listener->tp_type)); + pj_ansi_strcpy(alloc->info, pj_turn_tp_type_name(listener->tp_type)); alloc->info[3] = ':'; pj_sockaddr_print(src_addr, alloc->info+4, sizeof(alloc->info)-4, 3); @@ -132,44 +350,89 @@ PJ_DEF(pj_status_t) pjturn_allocation_create(pjturn_listener *listener, status = pj_stun_session_create(&srv->core.stun_cfg, alloc->obj_name, &sess_cb, PJ_FALSE, &alloc->sess); if (status != PJ_SUCCESS) { - pjturn_allocation_destroy(alloc); - return status; + goto on_error; } /* Attach to STUN session */ pj_stun_session_set_user_data(alloc->sess, alloc); + /* Init authentication credential */ + status = init_cred(alloc, msg); + if (status != PJ_SUCCESS) { + goto on_error; + } + + /* Attach authentication credential to STUN session */ + pj_stun_session_set_credential(alloc->sess, &alloc->cred); + /* Create the relay resource */ - status = pjturn_allocation_create_relay(srv, alloc, msg, req, - &alloc->relay); + status = create_relay(srv, alloc, msg, &req, &alloc->relay); if (status != PJ_SUCCESS) { - pjturn_allocation_destroy(alloc); - return status; + goto on_error; } /* Register this allocation */ - pjturn_srv_register_allocation(srv, alloc); + pj_turn_srv_register_allocation(srv, alloc); - pj_sockaddr_print(&alloc->relay.hkey.addr, relay_info, - sizeof(relay_info), 3); + /* Respond to ALLOCATE request */ + status = send_allocate_response(alloc, srv_sess, msg); + if (status != PJ_SUCCESS) + goto on_error; + + /* Done */ + pj_sockaddr_print(&alloc->relay.hkey.addr, str_tmp, + sizeof(str_tmp), 3); PJ_LOG(4,(alloc->obj_name, "Client %s created, relay addr=%s:%s", - alloc->info, pjturn_tp_type_name(req->tp_type), relay_info)); + alloc->info, pj_turn_tp_type_name(req.tp_type), str_tmp)); /* Success */ *p_alloc = alloc; return PJ_SUCCESS; + +on_error: + /* Send reply to the ALLOCATE request */ + pj_strerror(status, str_tmp, sizeof(str_tmp)); + pj_stun_session_respond(srv_sess, msg, PJ_STUN_SC_BAD_REQUEST, str_tmp, + PJ_TRUE, src_addr, src_addr_len); + + /* Cleanup */ + destroy_allocation(alloc); + return status; +} + + +/* Destroy relay resource */ +static void destroy_relay(pj_turn_relay_res *relay) +{ + if (relay->timer.id) { + pj_timer_heap_cancel(relay->allocation->listener->server->core.timer_heap, + &relay->timer); + relay->timer.id = PJ_FALSE; + } + + if (relay->tp.key) { + pj_ioqueue_unregister(relay->tp.key); + relay->tp.key = NULL; + relay->tp.sock = PJ_INVALID_SOCKET; + } else if (relay->tp.sock != PJ_INVALID_SOCKET) { + pj_sock_close(relay->tp.sock); + relay->tp.sock = PJ_INVALID_SOCKET; + } + + /* Mark as shutdown */ + relay->lifetime = 0; } /* - * Destroy allocation. + * Really destroy allocation. */ -PJ_DECL(void) pjturn_allocation_destroy(pjturn_allocation *alloc) +static void destroy_allocation(pj_turn_allocation *alloc) { pj_pool_t *pool; /* Unregister this allocation */ - pjturn_srv_unregister_allocation(alloc->listener->server, alloc); + pj_turn_srv_unregister_allocation(alloc->listener->server, alloc); /* Destroy relay */ destroy_relay(&alloc->relay); @@ -201,36 +464,23 @@ PJ_DECL(void) pjturn_allocation_destroy(pjturn_allocation *alloc) } -/* Destroy relay resource */ -static void destroy_relay(pjturn_relay_res *relay) +PJ_DECL(void) pj_turn_allocation_destroy(pj_turn_allocation *alloc) { - if (relay->timer.id) { - pj_timer_heap_cancel(relay->allocation->listener->server->core.timer_heap, - &relay->timer); - relay->timer.id = PJ_FALSE; - } - - if (relay->tp.key) { - pj_ioqueue_unregister(relay->tp.key); - relay->tp.key = NULL; - relay->tp.sock = PJ_INVALID_SOCKET; - } else if (relay->tp.sock != PJ_INVALID_SOCKET) { - pj_sock_close(relay->tp.sock); - relay->tp.sock = PJ_INVALID_SOCKET; - } - - /* Mark as shutdown */ - relay->lifetime = 0; + destroy_allocation(alloc); } -/* Initiate shutdown sequence for this allocation */ -static void alloc_shutdown(pjturn_allocation *alloc) + +/* Initiate shutdown sequence for this allocation and start destroy timer. + * Once allocation is marked as shutting down, any packets will be + * rejected/discarded + */ +static void alloc_shutdown(pj_turn_allocation *alloc) { pj_time_val destroy_delay = DESTROY_DELAY; /* Work with existing schedule */ if (alloc->relay.timer.id == TIMER_ID_TIMEOUT) { - /* Cancel existing timer */ + /* Cancel existing shutdown timer */ pj_timer_heap_cancel(alloc->listener->server->core.timer_heap, &alloc->relay.timer); alloc->relay.timer.id = TIMER_ID_NONE; @@ -257,8 +507,9 @@ static void alloc_shutdown(pjturn_allocation *alloc) &alloc->relay.timer, &destroy_delay); } + /* Reschedule timeout using current lifetime setting */ -static pj_status_t resched_timeout(pjturn_allocation *alloc) +static pj_status_t resched_timeout(pj_turn_allocation *alloc) { pj_time_val delay; pj_status_t status; @@ -291,10 +542,12 @@ static pj_status_t resched_timeout(pjturn_allocation *alloc) /* Timer timeout callback */ static void relay_timeout_cb(pj_timer_heap_t *heap, pj_timer_entry *e) { - pjturn_relay_res *rel; - pjturn_allocation *alloc; + pj_turn_relay_res *rel; + pj_turn_allocation *alloc; - rel = (pjturn_relay_res*) e->user_data; + PJ_UNUSED_ARG(heap); + + rel = (pj_turn_relay_res*) e->user_data; alloc = rel->allocation; if (e->id == TIMER_ID_TIMEOUT) { @@ -313,7 +566,7 @@ static void relay_timeout_cb(pj_timer_heap_t *heap, pj_timer_entry *e) PJ_LOG(4,(alloc->obj_name, "Client %s destroying..", alloc->info)); - pjturn_allocation_destroy(alloc); + destroy_allocation(alloc); } } @@ -321,11 +574,11 @@ static void relay_timeout_cb(pj_timer_heap_t *heap, pj_timer_entry *e) /* * Create relay. */ -PJ_DEF(pj_status_t) pjturn_allocation_create_relay(pjturn_srv *srv, - pjturn_allocation *alloc, - const pj_stun_msg *msg, - const pjturn_allocation_req *req, - pjturn_relay_res *relay) +static pj_status_t create_relay(pj_turn_srv *srv, + pj_turn_allocation *alloc, + const pj_stun_msg *msg, + const alloc_request *req, + pj_turn_relay_res *relay) { enum { RETRY = 40 }; pj_pool_t *pool = alloc->pool; @@ -365,9 +618,9 @@ PJ_DEF(pj_status_t) pjturn_allocation_create_relay(pjturn_srv *srv, relay->hkey.tp_type = req->tp_type; /* Create the socket */ - if (req->tp_type == PJTURN_TP_UDP) { + if (req->tp_type == PJ_TURN_TP_UDP) { sock_type = pj_SOCK_DGRAM(); - } else if (req->tp_type == PJTURN_TP_TCP) { + } else if (req->tp_type == PJ_TURN_TP_TCP) { sock_type = pj_SOCK_STREAM(); } else { pj_assert(!"Unknown transport"); @@ -395,16 +648,17 @@ PJ_DEF(pj_status_t) pjturn_allocation_create_relay(pjturn_srv *srv, if (req->rpp_port) { port = (pj_uint16_t) req->rpp_port; - } else if (req->tp_type == PJTURN_TP_UDP) { + } else if (req->tp_type == PJ_TURN_TP_UDP) { port = (pj_uint16_t) srv->ports.next_udp++; if (srv->ports.next_udp > srv->ports.max_udp) srv->ports.next_udp = srv->ports.min_udp; - } else if (req->tp_type == PJTURN_TP_TCP) { + } else if (req->tp_type == PJ_TURN_TP_TCP) { port = (pj_uint16_t) srv->ports.next_tcp++; if (srv->ports.next_tcp > srv->ports.max_tcp) srv->ports.next_tcp = srv->ports.min_tcp; } else { pj_assert(!"Invalid transport"); + port = 0; } pj_lock_release(srv->core.lock); @@ -463,27 +717,16 @@ PJ_DEF(pj_status_t) pjturn_allocation_create_relay(pjturn_srv *srv, } /* Create and send error response */ -static void send_reply_err(pjturn_allocation *alloc, +static void send_reply_err(pj_turn_allocation *alloc, const pj_stun_msg *req, pj_bool_t cache, int code, const char *errmsg) { pj_status_t status; - pj_str_t reason; - pj_stun_tx_data *tdata; - status = pj_stun_session_create_res(alloc->sess, req, - code, (errmsg?pj_cstr(&reason,errmsg):NULL), - &tdata); - if (status != PJ_SUCCESS) { - alloc_err(alloc, "Error creating STUN error response", status); - return; - } - - status = pj_stun_session_send_msg(alloc->sess, cache, - &alloc->hkey.clt_addr, - pj_sockaddr_get_len(&alloc->hkey.clt_addr), - tdata); + status = pj_stun_session_respond(alloc->sess, req, code, errmsg, cache, + &alloc->hkey.clt_addr, + pj_sockaddr_get_len(&alloc->hkey.clt_addr.addr)); if (status != PJ_SUCCESS) { alloc_err(alloc, "Error sending STUN error response", status); return; @@ -491,7 +734,7 @@ static void send_reply_err(pjturn_allocation *alloc, } /* Create and send successful response */ -static void send_reply_ok(pjturn_allocation *alloc, +static void send_reply_ok(pj_turn_allocation *alloc, const pj_stun_msg *req) { pj_status_t status; @@ -534,16 +777,16 @@ static void send_reply_ok(pjturn_allocation *alloc, /* Create new permission */ -static pjturn_permission *create_permission(pjturn_allocation *alloc, +static pj_turn_permission *create_permission(pj_turn_allocation *alloc, const pj_sockaddr_t *peer_addr, unsigned addr_len) { - pjturn_permission *perm; + pj_turn_permission *perm; - perm = PJ_POOL_ZALLOC_T(alloc->pool, pjturn_permission); + perm = PJ_POOL_ZALLOC_T(alloc->pool, pj_turn_permission); pj_memcpy(&perm->hkey.peer_addr, peer_addr, addr_len); - if (alloc->listener->tp_type == PJTURN_TP_UDP) { + if (alloc->listener->tp_type == PJ_TURN_TP_UDP) { perm->sock = alloc->listener->sock; } else { pj_assert(!"TCP is not supported yet"); @@ -551,18 +794,18 @@ static pjturn_permission *create_permission(pjturn_allocation *alloc, } perm->allocation = alloc; - perm->channel = PJTURN_INVALID_CHANNEL; + perm->channel = PJ_TURN_INVALID_CHANNEL; pj_gettimeofday(&perm->expiry); - perm->expiry.sec += PJTURN_PERM_TIMEOUT; + perm->expiry.sec += PJ_TURN_PERM_TIMEOUT; return perm; } /* Check if a permission isn't expired. Return NULL if expired. */ -static pjturn_permission *check_permission_expiry(pjturn_permission *perm) +static pj_turn_permission *check_permission_expiry(pj_turn_permission *perm) { - pjturn_allocation *alloc = perm->allocation; + pj_turn_allocation *alloc = perm->allocation; pj_time_val now; pj_gettimeofday(&now); @@ -576,7 +819,7 @@ static pjturn_permission *check_permission_expiry(pjturn_permission *perm) 0, NULL); /* Remove from channel hash table, if assigned a channel number */ - if (perm->channel != PJTURN_INVALID_CHANNEL) { + if (perm->channel != PJ_TURN_INVALID_CHANNEL) { pj_hash_set(NULL, alloc->ch_table, &perm->channel, sizeof(perm->channel), 0, NULL); } @@ -585,33 +828,33 @@ static pjturn_permission *check_permission_expiry(pjturn_permission *perm) } /* Lookup permission in hash table by the peer address */ -static pjturn_permission* -lookup_permission_by_addr(pjturn_allocation *alloc, +static pj_turn_permission* +lookup_permission_by_addr(pj_turn_allocation *alloc, const pj_sockaddr_t *peer_addr, unsigned addr_len) { - pjturn_permission_key key; - pjturn_permission *perm; + pj_turn_permission_key key; + pj_turn_permission *perm; pj_bzero(&key, sizeof(key)); pj_memcpy(&key, peer_addr, addr_len); /* Lookup in peer hash table */ - perm = (pjturn_permission*) pj_hash_get(alloc->peer_table, &key, + perm = (pj_turn_permission*) pj_hash_get(alloc->peer_table, &key, sizeof(key), NULL); return check_permission_expiry(perm); } /* Lookup permission in hash table by the channel number */ -static pjturn_permission* -lookup_permission_by_chnum(pjturn_allocation *alloc, +static pj_turn_permission* +lookup_permission_by_chnum(pj_turn_allocation *alloc, unsigned chnum) { pj_uint16_t chnum16 = (pj_uint16_t)chnum; - pjturn_permission *perm; + pj_turn_permission *perm; /* Lookup in peer hash table */ - perm = (pjturn_permission*) pj_hash_get(alloc->peer_table, &chnum16, + perm = (pj_turn_permission*) pj_hash_get(alloc->peer_table, &chnum16, sizeof(chnum16), NULL); return check_permission_expiry(perm); } @@ -619,25 +862,29 @@ lookup_permission_by_chnum(pjturn_allocation *alloc, /* Update permission because of data from client to peer. * Return PJ_TRUE is permission is found. */ -static pj_bool_t refresh_permission(pjturn_permission *perm) +static pj_bool_t refresh_permission(pj_turn_permission *perm) { pj_gettimeofday(&perm->expiry); - if (perm->channel == PJTURN_INVALID_CHANNEL) - perm->expiry.sec += PJTURN_PERM_TIMEOUT; + if (perm->channel == PJ_TURN_INVALID_CHANNEL) + perm->expiry.sec += PJ_TURN_PERM_TIMEOUT; else - perm->expiry.sec += PJTURN_CHANNEL_TIMEOUT; + perm->expiry.sec += PJ_TURN_CHANNEL_TIMEOUT; return PJ_TRUE; } /* - * Handle incoming packet from client. + * Handle incoming packet from client. This would have been called by + * server upon receiving packet from a listener. */ -PJ_DEF(void) pjturn_allocation_on_rx_client_pkt( pjturn_allocation *alloc, - pjturn_pkt *pkt) +PJ_DEF(void) pj_turn_allocation_on_rx_client_pkt(pj_turn_allocation *alloc, + pj_turn_pkt *pkt) { pj_bool_t is_stun; pj_status_t status; + /* Lock this allocation */ + pj_lock_acquire(alloc->lock); + /* Quickly check if this is STUN message */ is_stun = ((*((pj_uint8_t*)pkt->pkt) & 0xC0) == 0); @@ -649,7 +896,7 @@ PJ_DEF(void) pjturn_allocation_on_rx_client_pkt( pjturn_allocation *alloc, * callbacks. */ unsigned options = PJ_STUN_CHECK_PACKET; - if (pkt->listener->tp_type == PJTURN_TP_UDP) + if (pkt->listener->tp_type == PJ_TURN_TP_UDP) options |= PJ_STUN_IS_DATAGRAM; status = pj_stun_session_on_rx_pkt(alloc->sess, pkt->pkt, pkt->len, @@ -658,7 +905,7 @@ PJ_DEF(void) pjturn_allocation_on_rx_client_pkt( pjturn_allocation *alloc, pkt->src_addr_len); if (status != PJ_SUCCESS) { alloc_err(alloc, "Error handling STUN packet", status); - return; + goto on_return; } } else { @@ -666,20 +913,20 @@ PJ_DEF(void) pjturn_allocation_on_rx_client_pkt( pjturn_allocation *alloc, * This is not a STUN packet, must be ChannelData packet. */ channel_data_hdr *cd = (channel_data_hdr*)pkt->pkt; - pjturn_permission *perm; + pj_turn_permission *perm; pj_ssize_t len; /* For UDP check the packet length */ - if (alloc->listener->tp_type == PJTURN_TP_UDP) { + if (alloc->listener->tp_type == PJ_TURN_TP_UDP) { if (pkt->len < pj_ntohs(cd->length)+sizeof(*cd)) { PJ_LOG(4,(alloc->obj_name, "ChannelData from %s discarded: UDP size error", alloc->info)); - return; + goto on_return; } } else { pj_assert(!"Unsupported transport"); - return; + goto on_return; } perm = lookup_permission_by_chnum(alloc, pj_ntohs(cd->ch_number)); @@ -688,7 +935,7 @@ PJ_DEF(void) pjturn_allocation_on_rx_client_pkt( pjturn_allocation *alloc, PJ_LOG(4,(alloc->obj_name, "ChannelData from %s discarded: not found", alloc->info)); - return; + goto on_return; } /* Relay the data */ @@ -700,18 +947,23 @@ PJ_DEF(void) pjturn_allocation_on_rx_client_pkt( pjturn_allocation *alloc, /* Refresh permission */ refresh_permission(perm); } + +on_return: + /* Release lock */ + pj_lock_release(alloc->lock); } + /* * Handle incoming packet from peer. This function is called by * on_rx_from_peer(). */ -static void on_rx_peer_pkt(pjturn_allocation *alloc, - pjturn_relay_res *rel, - char *pkt, pj_size_t len, - const pj_sockaddr *src_addr) +static void handle_peer_pkt(pj_turn_allocation *alloc, + pj_turn_relay_res *rel, + char *pkt, pj_size_t len, + const pj_sockaddr *src_addr) { - pjturn_permission *perm; + pj_turn_permission *perm; /* Lookup permission */ perm = lookup_permission_by_addr(alloc, src_addr, @@ -724,14 +976,14 @@ static void on_rx_peer_pkt(pjturn_allocation *alloc, /* Send Data Indication or ChannelData, depends on whether * this permission is attached to a channel number. */ - if (perm->channel != PJTURN_INVALID_CHANNEL) { + if (perm->channel != PJ_TURN_INVALID_CHANNEL) { /* Send ChannelData */ channel_data_hdr *cd = (channel_data_hdr*)rel->tp.tx_pkt; - if (len > PJTURN_MAX_PKT_LEN) { + if (len > PJ_TURN_MAX_PKT_LEN) { char peer_addr[80]; pj_sockaddr_print(src_addr, peer_addr, sizeof(peer_addr), 3); - PJ_LOG(1,(alloc->obj_name, "Client %s: discarded data from %s " + PJ_LOG(4,(alloc->obj_name, "Client %s: discarded data from %s " "because it's too long (%d bytes)", alloc->info, peer_addr, len)); return; @@ -745,7 +997,7 @@ static void on_rx_peer_pkt(pjturn_allocation *alloc, pj_memcpy(rel->tp.rx_pkt+sizeof(channel_data_hdr), pkt, len); /* Send to client */ - pjturn_listener_sendto(alloc->listener, rel->tp.tx_pkt, + pj_turn_listener_sendto(alloc->listener, rel->tp.tx_pkt, len+sizeof(channel_data_hdr), 0, &alloc->hkey.clt_addr, pj_sockaddr_get_len(&alloc->hkey.clt_addr)); @@ -770,15 +1022,18 @@ static void on_rx_from_peer(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read) { - pjturn_relay_res *rel; + pj_turn_relay_res *rel; pj_status_t status; - rel = (pjturn_relay_res*) pj_ioqueue_get_user_data(key); + rel = (pj_turn_relay_res*) pj_ioqueue_get_user_data(key); + + /* Lock the allocation */ + pj_lock_acquire(rel->allocation->lock); do { if (bytes_read > 0) { - on_rx_peer_pkt(rel->allocation, rel, rel->tp.rx_pkt, - bytes_read, &rel->tp.src_addr); + handle_peer_pkt(rel->allocation, rel, rel->tp.rx_pkt, + bytes_read, &rel->tp.src_addr); } /* Read next packet */ @@ -794,6 +1049,8 @@ static void on_rx_from_peer(pj_ioqueue_key_t *key, } while (status != PJ_EPENDING && status != PJ_ECANCELLED); + /* Release allocation lock */ + pj_lock_release(rel->allocation->lock); } /* @@ -806,18 +1063,18 @@ static pj_status_t stun_on_send_msg(pj_stun_session *sess, const pj_sockaddr_t *dst_addr, unsigned addr_len) { - pjturn_allocation *alloc; + pj_turn_allocation *alloc; - alloc = (pjturn_allocation*) pj_stun_session_get_user_data(sess); + alloc = (pj_turn_allocation*) pj_stun_session_get_user_data(sess); - return pjturn_listener_sendto(alloc->listener, pkt, pkt_size, 0, + return pj_turn_listener_sendto(alloc->listener, pkt, pkt_size, 0, dst_addr, addr_len); } /* * Callback notification from STUN session when it receives STUN * requests. This callback was trigger by STUN incoming message - * processing in pjturn_allocation_on_rx_client_pkt(). + * processing in pj_turn_allocation_on_rx_client_pkt(). */ static pj_status_t stun_on_rx_request(pj_stun_session *sess, const pj_uint8_t *pkt, @@ -826,12 +1083,18 @@ static pj_status_t stun_on_rx_request(pj_stun_session *sess, const pj_sockaddr_t *src_addr, unsigned src_addr_len) { - pjturn_allocation *alloc; + pj_turn_allocation *alloc; - alloc = (pjturn_allocation*) pj_stun_session_get_user_data(sess); + PJ_UNUSED_ARG(pkt); + PJ_UNUSED_ARG(pkt_len); + PJ_UNUSED_ARG(src_addr); + PJ_UNUSED_ARG(src_addr_len); + + alloc = (pj_turn_allocation*) pj_stun_session_get_user_data(sess); /* Refuse to serve any request if we've been shutdown */ if (alloc->relay.lifetime == 0) { + /* Reject with 437 if we're shutting down */ send_reply_err(alloc, msg, PJ_TRUE, PJ_STUN_SC_ALLOCATION_MISMATCH, NULL); return PJ_SUCCESS; @@ -894,7 +1157,7 @@ static pj_status_t stun_on_rx_request(pj_stun_session *sess, */ pj_stun_channel_number_attr *ch_attr; pj_stun_peer_addr_attr *peer_attr; - pjturn_permission *p1, *p2; + pj_turn_permission *p1, *p2; ch_attr = (pj_stun_channel_number_attr*) pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_CHANNEL_NUMBER, 0); @@ -933,7 +1196,7 @@ static pj_status_t stun_on_rx_request(pj_stun_session *sess, */ p2 = lookup_permission_by_addr(alloc, &peer_attr->sockaddr, pj_sockaddr_get_len(&peer_attr->sockaddr)); - if (p2 && p2->channel != PJTURN_INVALID_CHANNEL) { + if (p2 && p2->channel != PJ_TURN_INVALID_CHANNEL) { send_reply_err(alloc, msg, PJ_TRUE, PJ_STUN_SC_BAD_REQUEST, "Peer address already assigned a channel number"); return PJ_SUCCESS; @@ -976,7 +1239,7 @@ static pj_status_t stun_on_rx_request(pj_stun_session *sess, /* * Callback notification from STUN session when it receives STUN * indications. This callback was trigger by STUN incoming message - * processing in pjturn_allocation_on_rx_client_pkt(). + * processing in pj_turn_allocation_on_rx_client_pkt(). */ static pj_status_t stun_on_rx_indication(pj_stun_session *sess, const pj_uint8_t *pkt, @@ -987,10 +1250,15 @@ static pj_status_t stun_on_rx_indication(pj_stun_session *sess, { pj_stun_peer_addr_attr *peer_attr; pj_stun_data_attr *data_attr; - pjturn_allocation *alloc; - pjturn_permission *perm; + pj_turn_allocation *alloc; + pj_turn_permission *perm; + + PJ_UNUSED_ARG(pkt); + PJ_UNUSED_ARG(pkt_len); + PJ_UNUSED_ARG(src_addr); + PJ_UNUSED_ARG(src_addr_len); - alloc = (pjturn_allocation*) pj_stun_session_get_user_data(sess); + alloc = (pj_turn_allocation*) pj_stun_session_get_user_data(sess); /* Only expect Send Indication */ if (msg->hdr.type != PJ_STUN_SEND_INDICATION) { @@ -1024,7 +1292,7 @@ static pj_status_t stun_on_rx_indication(pj_stun_session *sess, return PJ_SUCCESS; /* Relay the data to client */ - if (alloc->hkey.tp_type == PJTURN_TP_UDP) { + if (alloc->hkey.tp_type == PJ_TURN_TP_UDP) { pj_ssize_t len = data_attr->length; pj_sock_sendto(alloc->listener->sock, data_attr->data, &len, 0, &peer_attr->sockaddr, diff --git a/pjnath/src/pjturn-srv/auth.c b/pjnath/src/pjturn-srv/auth.c new file mode 100644 index 00000000..3071221c --- /dev/null +++ b/pjnath/src/pjturn-srv/auth.c @@ -0,0 +1,132 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2007 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "auth.h" +#include + + +#define MAX_REALM 80 +#define MAX_USERNAME 32 +#define MAX_PASSWORD 32 +#define MAX_NONCE 32 + +static char g_realm[MAX_REALM]; + +static struct cred_t +{ + char username[MAX_USERNAME]; + char passwd[MAX_PASSWORD]; +} g_cred[] = +{ + { "user", "passwd" }, +}; + +#define THE_NONCE "pjnath" + + +/* + * Initialize TURN authentication subsystem. + */ +PJ_DEF(pj_status_t) pj_turn_auth_init(const char *realm) +{ + PJ_ASSERT_RETURN(pj_ansi_strlen(realm) < MAX_REALM, PJ_ENAMETOOLONG); + pj_ansi_strcpy(g_realm, realm); + return PJ_SUCCESS; +} + +/* + * Shutdown TURN authentication subsystem. + */ +PJ_DEF(void) pj_turn_auth_dinit(void) +{ + /* Nothing to do */ +} + + +/* + * This function is called by pj_stun_verify_credential() when + * server needs to challenge the request with 401 response. + */ +PJ_DEF(pj_status_t) pj_turn_get_auth(void *user_data, + pj_pool_t *pool, + pj_str_t *realm, + pj_str_t *nonce) +{ + PJ_UNUSED_ARG(user_data); + PJ_UNUSED_ARG(pool); + + *realm = pj_str(g_realm); + *nonce = pj_str(THE_NONCE); + + return PJ_SUCCESS; +} + +/* + * This function is called to get the password for the specified username. + * This function is also used to check whether the username is valid. + */ +PJ_DEF(pj_status_t) pj_turn_get_password(const pj_stun_msg *msg, + void *user_data, + const pj_str_t *realm, + const pj_str_t *username, + pj_pool_t *pool, + int *data_type, + pj_str_t *data) +{ + unsigned i; + + PJ_UNUSED_ARG(msg); + PJ_UNUSED_ARG(user_data); + PJ_UNUSED_ARG(pool); + + if (pj_stricmp2(realm, g_realm)) + PJ_EINVAL; + + for (i=0; i + * + * 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_TURN_SRV_AUTH_H__ +#define __PJ_TURN_SRV_AUTH_H__ + +#include + +/** + * Initialize TURN authentication subsystem. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pj_turn_auth_init(const char *realm); + +/** + * Shutdown TURN authentication subsystem. + */ +PJ_DECL(void) pj_turn_auth_dinit(void); + +/** + * This function is called by pj_stun_verify_credential() when + * server needs to challenge the request with 401 response. + * + * @param user_data Should be ignored. + * @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_DECL(pj_status_t) pj_turn_get_auth(void *user_data, + pj_pool_t *pool, + pj_str_t *realm, + pj_str_t *nonce); + +/** + * This function is called to get the password for the specified username. + * This function is also used to check whether the username is valid. + * + * @param msg The STUN message where the password will be + * applied to. + * @param user_data Should be ignored. + * @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_DECL(pj_status_t) pj_turn_get_password(const pj_stun_msg *msg, + 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 function 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 msg The STUN message where the nonce was received. + * @param user_data Should be ignored. + * @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_DECL(pj_status_t) pj_turn_verify_nonce(const pj_stun_msg *msg, + void *user_data, + const pj_str_t *realm, + const pj_str_t *username, + const pj_str_t *nonce); + +#endif /* __PJ_TURN_SRV_AUTH_H__ */ + diff --git a/pjnath/src/pjturn-srv/listener_udp.c b/pjnath/src/pjturn-srv/listener_udp.c index d8f90ca4..b634d092 100644 --- a/pjnath/src/pjturn-srv/listener_udp.c +++ b/pjnath/src/pjturn-srv/listener_udp.c @@ -21,25 +21,25 @@ struct read_op { pj_ioqueue_op_key_t op_key; - pjturn_pkt pkt; + pj_turn_pkt pkt; }; struct udp_listener { - pjturn_listener base; + pj_turn_listener base; pj_ioqueue_key_t *key; unsigned read_cnt; struct read_op **read_op; /* Array of read_op's */ }; -static pj_status_t udp_sendto(pjturn_listener *listener, +static pj_status_t udp_sendto(pj_turn_listener *listener, const void *packet, pj_size_t size, unsigned flag, const pj_sockaddr_t *addr, int addr_len); -static pj_status_t udp_destroy(pjturn_listener *udp); +static pj_status_t udp_destroy(pj_turn_listener *udp); static void on_read_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read); @@ -48,13 +48,13 @@ static void on_read_complete(pj_ioqueue_key_t *key, /* * Create a new listener on the specified port. */ -PJ_DEF(pj_status_t) pjturn_listener_create_udp( pjturn_srv *srv, +PJ_DEF(pj_status_t) pj_turn_listener_create_udp( pj_turn_srv *srv, int af, const pj_str_t *bound_addr, unsigned port, unsigned concurrency_cnt, unsigned flags, - pjturn_listener **p_listener) + pj_turn_listener **p_listener) { pj_pool_t *pool; struct udp_listener *udp; @@ -63,11 +63,12 @@ PJ_DEF(pj_status_t) pjturn_listener_create_udp( pjturn_srv *srv, pj_status_t status; /* Create structure */ - pool = pj_pool_create(srv->core.pf, "udplis%p", 1000, 1000, NULL); + pool = pj_pool_create(srv->core.pf, "udp%p", 1000, 1000, NULL); udp = PJ_POOL_ZALLOC_T(pool, struct udp_listener); udp->base.pool = pool; + udp->base.obj_name = pool->obj_name; udp->base.server = srv; - udp->base.tp_type = PJTURN_TP_UDP; + udp->base.tp_type = PJ_TURN_TP_UDP; udp->base.sock = PJ_INVALID_SOCKET; udp->base.sendto = &udp_sendto; udp->base.destroy = &udp_destroy; @@ -85,6 +86,11 @@ PJ_DEF(pj_status_t) pjturn_listener_create_udp( pjturn_srv *srv, if (status != PJ_SUCCESS) goto on_error; + /* Create info */ + pj_ansi_strcpy(udp->base.info, "UDP:"); + pj_sockaddr_print(&udp->base.addr, udp->base.info+4, + sizeof(udp->base.info)-4, 3); + /* Bind socket */ status = pj_sock_bind(udp->base.sock, &udp->base.addr, pj_sockaddr_get_len(&udp->base.addr)); @@ -104,7 +110,8 @@ PJ_DEF(pj_status_t) pjturn_listener_create_udp( pjturn_srv *srv, /* Create each read_op and kick off read operation */ for (i=0; icore.pf, "rop%p", - 1000, 1000, NULL); + sizeof(struct read_op)+1000, + 1000, NULL); udp->read_op[i] = PJ_POOL_ZALLOC_T(rpool, struct read_op); udp->read_op[i]->pkt.pool = rpool; @@ -113,6 +120,8 @@ PJ_DEF(pj_status_t) pjturn_listener_create_udp( pjturn_srv *srv, } /* Done */ + PJ_LOG(4,(udp->base.obj_name, "Listener %s created", udp->base.info)); + *p_listener = &udp->base; return PJ_SUCCESS; @@ -126,7 +135,7 @@ on_error: /* * Destroy listener. */ -static pj_status_t udp_destroy(pjturn_listener *listener) +static pj_status_t udp_destroy(pj_turn_listener *listener) { struct udp_listener *udp = (struct udp_listener *)listener; unsigned i; @@ -149,8 +158,13 @@ static pj_status_t udp_destroy(pjturn_listener *listener) } if (udp->base.pool) { - pj_pool_release(udp->base.pool); + pj_pool_t *pool = udp->base.pool; + + PJ_LOG(4,(udp->base.obj_name, "Listener %s destroyed", + udp->base.info)); + udp->base.pool = NULL; + pj_pool_release(pool); } return PJ_SUCCESS; } @@ -158,7 +172,7 @@ static pj_status_t udp_destroy(pjturn_listener *listener) /* * Callback to send packet. */ -static pj_status_t udp_sendto(pjturn_listener *listener, +static pj_status_t udp_sendto(pj_turn_listener *listener, const void *packet, pj_size_t size, unsigned flag, @@ -166,8 +180,7 @@ static pj_status_t udp_sendto(pjturn_listener *listener, int addr_len) { pj_ssize_t len = size; - return pj_sock_sendto(listener->sock, packet, &len, flag, addr, - pj_sockaddr_get_len(addr)); + return pj_sock_sendto(listener->sock, packet, &len, flag, addr, addr_len); } /* @@ -191,7 +204,7 @@ static void on_read_complete(pj_ioqueue_key_t *key, read_op->pkt.len = bytes_read; pj_gettimeofday(&read_op->pkt.rx_time); - pjturn_srv_on_rx_pkt(udp->base.server, &read_op->pkt); + pj_turn_srv_on_rx_pkt(udp->base.server, &read_op->pkt); } /* Reset pool */ diff --git a/pjnath/src/pjturn-srv/main.c b/pjnath/src/pjturn-srv/main.c index 823eb28a..55b450c9 100644 --- a/pjnath/src/pjturn-srv/main.c +++ b/pjnath/src/pjturn-srv/main.c @@ -1 +1,51 @@ #include "turn.h" + +int err(const char *title, pj_status_t status) +{ + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(status, errmsg, sizeof(errmsg)); + + printf("%s: %s\n", title, errmsg); + return 1; +} + +int main() +{ + pj_caching_pool cp; + pj_turn_srv *srv; + pj_turn_listener *listener; + pj_status_t status; + + status = pj_init(); + if (status != PJ_SUCCESS) + return err("pj_init() error", status); + + pj_caching_pool_init(&cp, NULL, 0); + + status = pj_turn_srv_create(&cp.factory, &srv); + if (status != PJ_SUCCESS) + return err("Error creating server", status); + + status = pj_turn_listener_create_udp(srv, pj_AF_INET(), NULL, 3478, 1, 0, &listener); + if (status != PJ_SUCCESS) + return err("Error creating listener", status); + + status = pj_turn_srv_add_listener(srv, listener); + if (status != PJ_SUCCESS) + return err("Error adding listener", status); + + puts("Server is running"); + puts("Press to quit"); + + { + char line[10]; + fgets(line, sizeof(line), stdin); + } + + pj_turn_srv_destroy(srv); + pj_caching_pool_destroy(&cp); + pj_shutdown(); + + return 0; +} + diff --git a/pjnath/src/pjturn-srv/server.c b/pjnath/src/pjturn-srv/server.c index c9fc40cf..6765b3ca 100644 --- a/pjnath/src/pjturn-srv/server.c +++ b/pjnath/src/pjturn-srv/server.c @@ -17,25 +17,21 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "turn.h" +#include "auth.h" #define MAX_CLIENTS 32 #define MAX_PEERS_PER_CLIENT 8 -#define MAX_HANDLES (MAX_CLIENTS*MAX_PEERS_PER_CLIENT+MAX_LISTENERS) +//#define MAX_HANDLES (MAX_CLIENTS*MAX_PEERS_PER_CLIENT+MAX_LISTENERS) +#define MAX_HANDLES PJ_IOQUEUE_MAX_HANDLES #define MAX_TIMER (MAX_HANDLES * 2) #define MIN_PORT 49152 #define MAX_PORT 65535 #define MAX_LISTENERS 16 #define MAX_THREADS 2 - -#define MAX_CLIENT_BANDWIDTH 128 /* In Kbps */ -#define DEFA_CLIENT_BANDWIDTH 64 - -#define MIN_LIFETIME 32 -#define MAX_LIFETIME 600 -#define DEF_LIFETIME 300 - +#define MAX_NET_EVENTS 10 /* Prototypes */ +static int server_thread_proc(void *arg); static pj_status_t on_tx_stun_msg( pj_stun_session *sess, const void *pkt, pj_size_t pkt_size, @@ -48,60 +44,91 @@ static pj_status_t on_rx_stun_request(pj_stun_session *sess, const pj_sockaddr_t *src_addr, unsigned src_addr_len); +struct saved_cred +{ + pj_str_t realm; + pj_str_t username; + pj_str_t nonce; + int data_type; + pj_str_t data; +}; + + /* - * Get transport type name. + * Get transport type name, normally for logging purpose only. */ -PJ_DEF(const char*) pjturn_tp_type_name(int tp_type) +PJ_DEF(const char*) pj_turn_tp_type_name(int tp_type) { /* Must be 3 characters long! */ - if (tp_type == PJTURN_TP_UDP) + if (tp_type == PJ_TURN_TP_UDP) { return "UDP"; - else if (tp_type == PJTURN_TP_TCP) + } else if (tp_type == PJ_TURN_TP_TCP) { return "TCP"; - else + } else { + pj_assert(!"Unsupported transport"); return "???"; + } } /* * Create server. */ -PJ_DEF(pj_status_t) pjturn_srv_create( pj_pool_factory *pf, - pjturn_srv **p_srv) +PJ_DEF(pj_status_t) pj_turn_srv_create(pj_pool_factory *pf, + pj_turn_srv **p_srv) { pj_pool_t *pool; - pjturn_srv *srv; + pj_turn_srv *srv; + unsigned i; pj_status_t status; PJ_ASSERT_RETURN(pf && p_srv, PJ_EINVAL); /* Create server and init core settings */ pool = pj_pool_create(pf, "srv%p", 1000, 1000, NULL); - srv = PJ_POOL_ZALLOC_T(pool, pjturn_srv); - srv->core.obj_name = pool->obj_name; + srv = PJ_POOL_ZALLOC_T(pool, pj_turn_srv); + srv->obj_name = pool->obj_name; srv->core.pf = pf; srv->core.pool = pool; + srv->core.tls_key = srv->core.tls_data = -1; + /* Create ioqueue */ status = pj_ioqueue_create(pool, MAX_HANDLES, &srv->core.ioqueue); if (status != PJ_SUCCESS) goto on_error; - status = pj_timer_heap_create(pool, MAX_TIMER, &srv->core.timer_heap); + /* Server mutex */ + status = pj_lock_create_recursive_mutex(pool, srv->obj_name, + &srv->core.lock); if (status != PJ_SUCCESS) goto on_error; - srv->core.listener = pj_pool_calloc(pool, MAX_LISTENERS, - sizeof(srv->core.listener[0])); - srv->core.stun_sess = pj_pool_calloc(pool, MAX_LISTENERS, - (sizeof(srv->core.stun_sess[0]))); - - srv->core.thread_cnt = MAX_THREADS; - srv->core.thread = pj_pool_calloc(pool, srv->core.thread_cnt, - sizeof(pj_thread_t*)); + /* Allocate TLS */ + status = pj_thread_local_alloc(&srv->core.tls_key); + if (status != PJ_SUCCESS) + goto on_error; - status = pj_lock_create_recursive_mutex(pool, "srv%p", &srv->core.lock); + status = pj_thread_local_alloc(&srv->core.tls_data); + if (status != PJ_SUCCESS) + goto on_error; + + /* Create timer heap */ + status = pj_timer_heap_create(pool, MAX_TIMER, &srv->core.timer_heap); if (status != PJ_SUCCESS) goto on_error; + /* Configure lock for the timer heap */ + pj_timer_heap_set_lock(srv->core.timer_heap, srv->core.lock, PJ_FALSE); + + /* Array of listeners */ + srv->core.listener = (pj_turn_listener**) + pj_pool_calloc(pool, MAX_LISTENERS, + sizeof(srv->core.listener[0])); + + /* Array of STUN sessions, one for each listener */ + srv->core.stun_sess = (pj_stun_session**) + pj_pool_calloc(pool, MAX_LISTENERS, + (sizeof(srv->core.stun_sess[0]))); + /* Create hash tables */ srv->tables.alloc = pj_hash_create(pool, MAX_CLIENTS); srv->tables.res = pj_hash_create(pool, MAX_CLIENTS); @@ -116,27 +143,204 @@ PJ_DEF(pj_status_t) pjturn_srv_create( pj_pool_factory *pf, pj_stun_config_init(&srv->core.stun_cfg, pf, 0, srv->core.ioqueue, srv->core.timer_heap); + /* Init STUN credential */ + srv->core.cred.type = PJ_STUN_AUTH_CRED_DYNAMIC; + srv->core.cred.data.dyn_cred.user_data = srv; + srv->core.cred.data.dyn_cred.get_auth = &pj_turn_get_auth; + srv->core.cred.data.dyn_cred.get_cred = &pj_turn_srv_get_cred; + srv->core.cred.data.dyn_cred.get_password = &pj_turn_get_password; + srv->core.cred.data.dyn_cred.verify_nonce = &pj_turn_verify_nonce; + + /* Array of worker threads */ + srv->core.thread_cnt = MAX_THREADS; + srv->core.thread = (pj_thread_t**) + pj_pool_calloc(pool, srv->core.thread_cnt, + sizeof(pj_thread_t*)); + + /* Start the worker threads */ + for (i=0; icore.thread_cnt; ++i) { + status = pj_thread_create(pool, srv->obj_name, &server_thread_proc, + srv, 0, 0, &srv->core.thread[i]); + if (status != PJ_SUCCESS) + goto on_error; + } + + /* We're done. Application should add listeners now */ + PJ_LOG(4,(srv->obj_name, "TURN server v%s is running", + pj_get_version())); + *p_srv = srv; return PJ_SUCCESS; on_error: - pjturn_srv_destroy(srv); + pj_turn_srv_destroy(srv); return status; } -/** - * Create server. + +/* + * Handle timer and network events + */ +static void srv_handle_events(pj_turn_srv *srv, const pj_time_val *max_timeout) +{ + /* timeout is 'out' var. This just to make compiler happy. */ + pj_time_val timeout = { 0, 0}; + unsigned net_event_count = 0; + int c; + + /* Poll the timer. The timer heap has its own mutex for better + * granularity, so we don't need to lock the server. + */ + timeout.sec = timeout.msec = 0; + c = pj_timer_heap_poll( srv->core.timer_heap, &timeout ); + + /* timer_heap_poll should never ever returns negative value, or otherwise + * ioqueue_poll() will block forever! + */ + pj_assert(timeout.sec >= 0 && timeout.msec >= 0); + if (timeout.msec >= 1000) timeout.msec = 999; + + /* If caller specifies maximum time to wait, then compare the value with + * the timeout to wait from timer, and use the minimum value. + */ + if (max_timeout && PJ_TIME_VAL_GT(timeout, *max_timeout)) { + timeout = *max_timeout; + } + + /* Poll ioqueue. + * Repeat polling the ioqueue while we have immediate events, because + * timer heap may process more than one events, so if we only process + * one network events at a time (such as when IOCP backend is used), + * the ioqueue may have trouble keeping up with the request rate. + * + * For example, for each send() request, one network event will be + * reported by ioqueue for the send() completion. If we don't poll + * the ioqueue often enough, the send() completion will not be + * reported in timely manner. + */ + do { + c = pj_ioqueue_poll( srv->core.ioqueue, &timeout); + if (c < 0) { + pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout)); + return; + } else if (c == 0) { + break; + } else { + net_event_count += c; + timeout.sec = timeout.msec = 0; + } + } while (c > 0 && net_event_count < MAX_NET_EVENTS); + +} + +/* + * Server worker thread proc. + */ +static int server_thread_proc(void *arg) +{ + pj_turn_srv *srv = (pj_turn_srv*)arg; + + while (!srv->core.quit) { + pj_time_val timeout_max = {0, 500}; + srv_handle_events(srv, &timeout_max); + } + + return 0; +} + +/* + * Destroy the server. */ -PJ_DEF(pj_status_t) pjturn_srv_destroy(pjturn_srv *srv) +PJ_DEF(pj_status_t) pj_turn_srv_destroy(pj_turn_srv *srv) { + pj_hash_iterator_t itbuf, *it; + unsigned i; + + /* Stop all worker threads */ + srv->core.quit = PJ_TRUE; + for (i=0; icore.thread_cnt; ++i) { + if (srv->core.thread[i]) { + pj_thread_join(srv->core.thread[i]); + pj_thread_destroy(srv->core.thread[i]); + srv->core.thread[i] = NULL; + } + } + + /* Destroy all listeners and STUN sessions associated with them. */ + for (i=0; icore.lis_cnt; ++i) { + if (srv->core.listener[i]) { + pj_turn_listener_destroy(srv->core.listener[i]); + srv->core.listener[i] = NULL; + } + if (srv->core.stun_sess[i]) { + pj_stun_session_destroy(srv->core.stun_sess[i]); + srv->core.stun_sess[i] = NULL; + } + } + + /* Destroy all allocations */ + if (srv->tables.alloc) { + it = pj_hash_first(srv->tables.alloc, &itbuf); + while (it != NULL) { + pj_turn_allocation *alloc = (pj_turn_allocation*) + pj_hash_this(srv->tables.alloc, it); + pj_turn_allocation_destroy(alloc); + it = pj_hash_next(srv->tables.alloc, it); + } + } + + + /* Destroy hash tables (well, sort of) */ + if (srv->tables.alloc) { + srv->tables.alloc = NULL; + srv->tables.res = NULL; + } + + /* Destroy timer heap */ + if (srv->core.timer_heap) { + pj_timer_heap_destroy(srv->core.timer_heap); + srv->core.timer_heap = NULL; + } + + /* Destroy ioqueue */ + if (srv->core.ioqueue) { + pj_ioqueue_destroy(srv->core.ioqueue); + srv->core.ioqueue = NULL; + } + + /* Destroy thread local IDs */ + if (srv->core.tls_key != -1) { + pj_thread_local_free(srv->core.tls_key); + srv->core.tls_key = -1; + } + if (srv->core.tls_data != -1) { + pj_thread_local_free(srv->core.tls_data); + srv->core.tls_data = -1; + } + + /* Destroy server lock */ + if (srv->core.lock) { + pj_lock_destroy(srv->core.lock); + srv->core.lock = NULL; + } + + /* Release pool */ + if (srv->core.pool) { + pj_pool_t *pool = srv->core.pool; + srv->core.pool = NULL; + pj_pool_release(pool); + } + + /* Done */ return PJ_SUCCESS; } -/** + +/* * Add listener. */ -PJ_DEF(pj_status_t) pjturn_srv_add_listener(pjturn_srv *srv, - pjturn_listener *lis) +PJ_DEF(pj_status_t) pj_turn_srv_add_listener(pj_turn_srv *srv, + pj_turn_listener *lis) { pj_stun_session_cb sess_cb; unsigned index; @@ -156,27 +360,76 @@ PJ_DEF(pj_status_t) pjturn_srv_add_listener(pjturn_srv *srv, sess_cb.on_rx_request = &on_rx_stun_request; sess_cb.on_send_msg = &on_tx_stun_msg; - status = pj_stun_session_create(&srv->core.stun_cfg, "lis%p", &sess_cb, - PJ_FALSE, &sess); + status = pj_stun_session_create(&srv->core.stun_cfg, lis->obj_name, + &sess_cb, PJ_FALSE, &sess); if (status != PJ_SUCCESS) { srv->core.listener[index] = NULL; return status; } pj_stun_session_set_user_data(sess, lis); + pj_stun_session_set_credential(sess, &srv->core.cred); srv->core.stun_sess[index] = sess; lis->id = index; srv->core.lis_cnt++; + PJ_LOG(4,(srv->obj_name, "Listener %s/%s added at index %d", + lis->obj_name, lis->info, lis->id)); + return PJ_SUCCESS; } -/** - * Register an allocation. + +/* + * Send packet with this listener. */ -PJ_DEF(pj_status_t) pjturn_srv_register_allocation(pjturn_srv *srv, - pjturn_allocation *alloc) +PJ_DEF(pj_status_t) pj_turn_listener_sendto(pj_turn_listener *listener, + const void *packet, + pj_size_t size, + unsigned flag, + const pj_sockaddr_t *addr, + int addr_len) +{ + pj_assert(listener->id != PJ_TURN_INVALID_LIS_ID); + return listener->sendto(listener, packet, size, flag, addr, addr_len); +} + + +/* + * Destroy listener. + */ +PJ_DEF(pj_status_t) pj_turn_listener_destroy(pj_turn_listener *listener) +{ + pj_turn_srv *srv = listener->server; + unsigned i; + + /* Remove from our listener list */ + pj_lock_acquire(srv->core.lock); + for (i=0; icore.lis_cnt; ++i) { + if (srv->core.listener[i] == listener) { + srv->core.listener[i] = NULL; + srv->core.lis_cnt--; + listener->id = PJ_TURN_INVALID_LIS_ID; + if (srv->core.stun_sess[i]) { + pj_stun_session_destroy(srv->core.stun_sess[i]); + srv->core.stun_sess[i] = NULL; + } + break; + } + } + pj_lock_release(srv->core.lock); + + /* Destroy */ + return listener->destroy(listener); +} + + +/* + * Register an allocation to the hash tables. + */ +PJ_DEF(pj_status_t) pj_turn_srv_register_allocation(pj_turn_srv *srv, + pj_turn_allocation *alloc) { /* Add to hash tables */ pj_lock_acquire(srv->core.lock); @@ -190,11 +443,12 @@ PJ_DEF(pj_status_t) pjturn_srv_register_allocation(pjturn_srv *srv, return PJ_SUCCESS; } -/** - * Unregister an allocation. + +/* + * Unregister an allocation from the hash tables. */ -PJ_DEF(pj_status_t) pjturn_srv_unregister_allocation(pjturn_srv *srv, - pjturn_allocation *alloc) +PJ_DEF(pj_status_t) pj_turn_srv_unregister_allocation(pj_turn_srv *srv, + pj_turn_allocation *alloc) { /* Unregister from hash tables */ pj_lock_acquire(srv->core.lock); @@ -208,285 +462,204 @@ PJ_DEF(pj_status_t) pjturn_srv_unregister_allocation(pjturn_srv *srv, } -/* Callback from our own STUN session to send packet */ +/* Callback from our own STUN session whenever it needs to send + * outgoing STUN packet. + */ static pj_status_t on_tx_stun_msg( pj_stun_session *sess, const void *pkt, pj_size_t pkt_size, const pj_sockaddr_t *dst_addr, unsigned addr_len) { - pjturn_listener *listener; + pj_turn_listener *listener; - listener = (pjturn_listener*) pj_stun_session_get_user_data(sess); + listener = (pj_turn_listener*) pj_stun_session_get_user_data(sess); PJ_ASSERT_RETURN(listener!=NULL, PJ_EINVALIDOP); - return pjturn_listener_sendto(listener, pkt, pkt_size, 0, - dst_addr, addr_len); + return pj_turn_listener_sendto(listener, pkt, pkt_size, 0, + dst_addr, addr_len); } -/* Create and send error response */ -static pj_status_t respond_error(pj_stun_session *sess, const pj_stun_msg *req, - pj_bool_t cache, int code, const char *errmsg, - const pj_sockaddr_t *dst_addr, - unsigned addr_len) + +/* Respond to STUN request */ +static pj_status_t stun_respond(pj_turn_srv *srv, + pj_stun_session *sess, + const pj_stun_msg *req, + unsigned code, + const char *errmsg, + pj_bool_t cache, + const pj_sockaddr_t *dst_addr, + unsigned addr_len) { pj_status_t status; pj_str_t reason; pj_stun_tx_data *tdata; - status = pj_stun_session_create_res(sess, req, - code, (errmsg?pj_cstr(&reason,errmsg):NULL), + /* Create response */ + status = pj_stun_session_create_res(sess, req, code, + (errmsg?pj_cstr(&reason,errmsg):NULL), &tdata); if (status != PJ_SUCCESS) return status; - status = pj_stun_session_send_msg(sess, cache, dst_addr, addr_len, tdata); - return status; + /* Store the credential for future lookup. */ + if (pj_stun_auth_valid_for_msg(tdata->msg)) { + pj_turn_srv_put_cred(srv, req, tdata); + } + /* Send the response */ + return pj_stun_session_send_msg(sess, cache, dst_addr, addr_len, tdata); } -/* Parse ALLOCATE request */ -static pj_status_t parse_allocate_req(pjturn_allocation_req *cfg, - pjturn_listener *listener, - pj_stun_session *sess, - const pj_stun_msg *req, - const pj_sockaddr_t *src_addr, - unsigned src_addr_len) + +/* + * Store the credential to put placed for the specified message for + * future retrieval. + */ +PJ_DEF(pj_status_t) pj_turn_srv_put_cred(pj_turn_srv *srv, + const pj_stun_msg *req, + pj_stun_tx_data *response) { - pj_stun_bandwidth_attr *attr_bw; - pj_stun_req_transport_attr *attr_req_tp; - pj_stun_req_ip_attr *attr_req_ip; - pj_stun_req_port_props_attr *attr_rpp; - pj_stun_lifetime_attr *attr_lifetime; - - pj_bzero(cfg, sizeof(*cfg)); - - /* Get BANDWIDTH attribute, if any. */ - attr_bw = (pj_stun_uint_attr*) - pj_stun_msg_find_attr(req, PJ_STUN_ATTR_BANDWIDTH, 0); - if (attr_bw) { - cfg->bandwidth = attr_bw->value; - } else { - cfg->bandwidth = DEFA_CLIENT_BANDWIDTH; - } + pj_stun_username_attr *user; + pj_stun_realm_attr *realm; + pj_stun_nonce_attr *nonce; + struct saved_cred *saved_cred; + pj_status_t status; - /* Check if we can satisfy the bandwidth */ - if (cfg->bandwidth > MAX_CLIENT_BANDWIDTH) { - respond_error(sess, req, PJ_FALSE, - PJ_STUN_SC_ALLOCATION_QUOTA_REACHED, - "Invalid bandwidth", src_addr, src_addr_len); - return -1; - } + realm = (pj_stun_realm_attr*) + pj_stun_msg_find_attr(req, PJ_STUN_ATTR_REALM, 0); + PJ_ASSERT_RETURN(realm != NULL, PJ_EBUG); - /* Get REQUESTED-TRANSPORT attribute, is any */ - attr_req_tp = (pj_stun_uint_attr*) - pj_stun_msg_find_attr(req, PJ_STUN_ATTR_REQ_TRANSPORT, 0); - if (attr_req_tp) { - cfg->tp_type = PJ_STUN_GET_RT_PROTO(attr_req_tp->value); - } else { - cfg->tp_type = listener->tp_type; - } + user = (pj_stun_username_attr*) + pj_stun_msg_find_attr(req, PJ_STUN_ATTR_USERNAME, 0); + PJ_ASSERT_RETURN(user != NULL, PJ_EBUG); - /* Can only support UDP for now */ - if (cfg->tp_type != PJTURN_TP_UDP) { - respond_error(sess, req, PJ_FALSE, - PJ_STUN_SC_UNSUPP_TRANSPORT_PROTO, - NULL, src_addr, src_addr_len); - return -1; - } + nonce = (pj_stun_nonce_attr*) + pj_stun_msg_find_attr(req, PJ_STUN_ATTR_NONCE, 0); + PJ_ASSERT_RETURN(nonce != NULL, PJ_EBUG); - /* Get REQUESTED-IP attribute, if any */ - attr_req_ip = (pj_stun_sockaddr_attr*) - pj_stun_msg_find_attr(req, PJ_STUN_ATTR_REQ_IP, 0); - if (attr_req_ip) { - pj_sockaddr_print(&attr_req_ip->sockaddr, cfg->addr, - sizeof(cfg->addr), 0); - } + saved_cred = PJ_POOL_ALLOC_T(response->pool, struct saved_cred); - /* Get REQUESTED-PORT-PROPS attribute, if any */ - attr_rpp = (pj_stun_uint_attr*) - pj_stun_msg_find_attr(req, PJ_STUN_ATTR_REQ_PORT_PROPS, 0); - if (attr_rpp) { - cfg->rpp_bits = PJ_STUN_GET_RPP_BITS(attr_rpp->value); - cfg->rpp_port = PJ_STUN_GET_RPP_PORT(attr_rpp->value); - } else { - cfg->rpp_bits = 0; - cfg->rpp_port = 0; - } + /* Lookup the password */ + status = pj_turn_get_password(response->msg, NULL, &realm->value, + &user->value, response->pool, + &saved_cred->data_type, + &saved_cred->data); + if (status != PJ_SUCCESS) + return status; - /* Get LIFETIME attribute */ - attr_lifetime = (pj_stun_uint_attr*) - pj_stun_msg_find_attr(req, PJ_STUN_ATTR_LIFETIME, 0); - if (attr_lifetime) { - cfg->lifetime = attr_lifetime->value; - if (cfg->lifetime < MIN_LIFETIME || cfg->lifetime > MAX_LIFETIME) { - respond_error(sess, req, PJ_FALSE, - PJ_STUN_SC_BAD_REQUEST, - "Invalid LIFETIME value", src_addr, - src_addr_len); - return -1; - } - } else { - cfg->lifetime = DEF_LIFETIME; - } + /* Store credential */ + pj_strdup(response->pool, &saved_cred->username, &user->value); + pj_strdup(response->pool, &saved_cred->realm, &realm->value); + pj_strdup(response->pool, &saved_cred->nonce, &nonce->value); + + pj_thread_local_set(srv->core.tls_key, response->msg); + pj_thread_local_set(srv->core.tls_data, saved_cred); return PJ_SUCCESS; } -/* Callback from our own STUN session when incoming request arrives */ -static pj_status_t on_rx_stun_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) + +/** + * Retrieve previously stored credential for the specified message. + */ +PJ_DEF(pj_status_t) pj_turn_srv_get_cred(const pj_stun_msg *msg, + void *user_data, + pj_pool_t *pool, + pj_str_t *realm, + pj_str_t *username, + pj_str_t *nonce, + int *data_type, + pj_str_t *data) { - pjturn_listener *listener; - pjturn_srv *srv; - pjturn_allocation_req req; - pjturn_allocation *alloc; - pj_stun_tx_data *tdata; - pj_status_t status; + pj_turn_srv *srv; + const pj_stun_msg *saved_msg; + struct saved_cred *saved_cred; - listener = (pjturn_listener*) pj_stun_session_get_user_data(sess); - srv = listener->server; + PJ_UNUSED_ARG(pool); - /* Handle strayed REFRESH request */ - if (msg->hdr.type == PJ_STUN_REFRESH_REQUEST) { - return respond_error(sess, msg, PJ_FALSE, - PJ_STUN_SC_ALLOCATION_MISMATCH, - NULL, src_addr, src_addr_len); - } + srv = (pj_turn_srv*)user_data; - /* Respond any other requests with Bad Request response */ - if (msg->hdr.type != PJ_STUN_ALLOCATE_REQUEST) { - return respond_error(sess, msg, PJ_FALSE, PJ_STUN_SC_BAD_REQUEST, - NULL, src_addr, src_addr_len); - } + /* Lookup stored message and make sure it's for the same message */ + saved_msg = (const pj_stun_msg*) + pj_thread_local_get(srv->core.tls_key); + PJ_ASSERT_RETURN(saved_msg==msg, PJ_ENOTFOUND); - /* We have ALLOCATE request here, and it's authenticated. Parse the - * request. - */ - status = parse_allocate_req(&req, listener, sess, msg, src_addr, - src_addr_len); - if (status != PJ_SUCCESS) - return status; + /* Lookup saved credential */ + saved_cred = (struct saved_cred*) + pj_thread_local_get(srv->core.tls_data); + PJ_ASSERT_RETURN(saved_cred != NULL, PJ_ENOTFOUND); - /* Create new allocation. The relay resource will be allocated - * in this function. - */ - status = pjturn_allocation_create(listener, src_addr, src_addr_len, - msg, &req, &alloc); - if (status != PJ_SUCCESS) { - char errmsg[PJ_ERR_MSG_SIZE]; - pj_strerror(status, errmsg, sizeof(errmsg)); - return respond_error(sess, msg, PJ_FALSE, PJ_STUN_SC_SERVER_ERROR, - errmsg, src_addr, src_addr_len); - } + *realm = saved_cred->realm; + *username = saved_cred->username; + *nonce = saved_cred->nonce; + *data_type = saved_cred->data_type; + *data = saved_cred->data; - /* Respond the original ALLOCATE request */ - status = pj_stun_session_create_res(srv->core.stun_sess[listener->id], - msg, 0, NULL, &tdata); - if (status != PJ_SUCCESS) { - char errmsg[PJ_ERR_MSG_SIZE]; - - pjturn_allocation_destroy(alloc); - pj_strerror(status, errmsg, sizeof(errmsg)); - return respond_error(sess, msg, PJ_FALSE, PJ_STUN_SC_SERVER_ERROR, - errmsg, src_addr, src_addr_len); - } - - /* Add RELAYED-ADDRESS attribute */ - pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg, - PJ_STUN_ATTR_RELAY_ADDR, PJ_TRUE, - &alloc->relay.hkey.addr, - pj_sockaddr_get_len(&alloc->relay.hkey.addr)); - - /* Add LIFETIME. */ - pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, - PJ_STUN_ATTR_LIFETIME, - (unsigned)alloc->relay.lifetime); - - /* Add BANDWIDTH */ - pj_stun_msg_add_uint_attr(tdata->pool, tdata->msg, - PJ_STUN_ATTR_BANDWIDTH, - alloc->bandwidth); - - /* Add RESERVATION-TOKEN */ - PJ_TODO(ADD_RESERVATION_TOKEN); - - /* Add XOR-MAPPED-ADDRESS */ - pj_stun_msg_add_sockaddr_attr(tdata->pool, tdata->msg, - PJ_STUN_ATTR_XOR_MAPPED_ADDR, PJ_TRUE, - &alloc->hkey.clt_addr, - pj_sockaddr_get_len(&alloc->hkey.clt_addr)); - - /* Send the response */ - pj_stun_session_send_msg(srv->core.stun_sess[listener->id], PJ_TRUE, - src_addr, src_addr_len, tdata); + /* Don't clear saved_cred as this may be called more than once */ - /* Done. */ return PJ_SUCCESS; } -/* Handle packet from new client address. */ -static void handle_new_client( pjturn_srv *srv, - pjturn_pkt *pkt) +/* Callback from our own STUN session when incoming request arrives. + * This function is triggered by pj_stun_session_on_rx_pkt() call in + * pj_turn_srv_on_rx_pkt() function below. + */ +static pj_status_t on_rx_stun_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) { - unsigned options, lis_id; + pj_turn_listener *listener; + pj_turn_srv *srv; + pj_turn_allocation *alloc; pj_status_t status; - /* Check that this is a STUN message */ - options = PJ_STUN_CHECK_PACKET; - if (pkt->listener->tp_type == PJTURN_TP_UDP) - options |= PJ_STUN_IS_DATAGRAM; + PJ_UNUSED_ARG(pkt); + PJ_UNUSED_ARG(pkt_len); - status = pj_stun_msg_check(pkt->pkt, pkt->len, options); - if (status != PJ_SUCCESS) { - char errmsg[PJ_ERR_MSG_SIZE]; - char ip[PJ_INET6_ADDRSTRLEN+10]; - - pj_strerror(status, errmsg, sizeof(errmsg)); - PJ_LOG(5,(srv->core.obj_name, - "Non STUN packet from %s is dropped: %s", - pj_sockaddr_print(&pkt->src.clt_addr, ip, sizeof(ip), 3), - errmsg)); - return; - } + listener = (pj_turn_listener*) pj_stun_session_get_user_data(sess); + srv = listener->server; - lis_id = pkt->listener->id; + /* Respond any requests other than ALLOCATE with 437 response */ + if (msg->hdr.type != PJ_STUN_ALLOCATE_REQUEST) { + stun_respond(srv, sess, msg, PJ_STUN_SC_ALLOCATION_MISMATCH, + NULL, PJ_FALSE, src_addr, src_addr_len); + return PJ_SUCCESS; + } - /* Hand over processing to STUN session */ - options &= ~PJ_STUN_CHECK_PACKET; - status = pj_stun_session_on_rx_pkt(srv->core.stun_sess[lis_id], pkt->pkt, - pkt->len, options, NULL, - &pkt->src.clt_addr, - pkt->src_addr_len); + /* Create new allocation. The relay resource will be allocated + * in this function. + */ + status = pj_turn_allocation_create(listener, src_addr, src_addr_len, + msg, sess, &alloc); if (status != PJ_SUCCESS) { - char errmsg[PJ_ERR_MSG_SIZE]; - char ip[PJ_INET6_ADDRSTRLEN+10]; - - pj_strerror(status, errmsg, sizeof(errmsg)); - PJ_LOG(5,(srv->core.obj_name, - "Error processing STUN packet from %s: %s", - pj_sockaddr_print(&pkt->src.clt_addr, ip, sizeof(ip), 3), - errmsg)); - return; + /* STUN response has been sent, no need to reply here */ + return PJ_SUCCESS; } + + /* Done. */ + return PJ_SUCCESS; } /* - * This callback is called by UDP listener on incoming packet. + * This callback is called by UDP listener on incoming packet. This is + * the first entry for incoming packet (from client) to the server. From + * here, the packet may be handed over to an allocation if an allocation + * is found for the client address, or handed over to owned STUN session + * if an allocation is not found. */ -PJ_DEF(void) pjturn_srv_on_rx_pkt( pjturn_srv *srv, - pjturn_pkt *pkt) +PJ_DEF(void) pj_turn_srv_on_rx_pkt(pj_turn_srv *srv, + pj_turn_pkt *pkt) { - pjturn_allocation *alloc; + pj_turn_allocation *alloc; /* Get TURN allocation from the source address */ pj_lock_acquire(srv->core.lock); @@ -497,10 +670,52 @@ PJ_DEF(void) pjturn_srv_on_rx_pkt( pjturn_srv *srv, * allocation. */ if (alloc) { - pjturn_allocation_on_rx_client_pkt(alloc, pkt); + pj_turn_allocation_on_rx_client_pkt(alloc, pkt); } else { /* Otherwise this is a new client */ - handle_new_client(srv, pkt); + unsigned options, lis_id; + pj_status_t status; + + /* Check that this is a STUN message */ + options = PJ_STUN_CHECK_PACKET; + if (pkt->listener->tp_type == PJ_TURN_TP_UDP) + options |= PJ_STUN_IS_DATAGRAM; + + status = pj_stun_msg_check(pkt->pkt, pkt->len, options); + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + char ip[PJ_INET6_ADDRSTRLEN+10]; + + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(5,(srv->obj_name, + "Non STUN packet from %s is dropped: %s", + pj_sockaddr_print(&pkt->src.clt_addr, ip, sizeof(ip), 3), + errmsg)); + return; + } + + lis_id = pkt->listener->id; + + /* Hand over processing to STUN session. This will trigger + * on_rx_stun_request() callback to be called if the STUN + * message is a request. + */ + options &= ~PJ_STUN_CHECK_PACKET; + status = pj_stun_session_on_rx_pkt(srv->core.stun_sess[lis_id], + pkt->pkt, pkt->len, options, NULL, + &pkt->src.clt_addr, + pkt->src_addr_len); + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + char ip[PJ_INET6_ADDRSTRLEN+10]; + + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(5,(srv->obj_name, + "Error processing STUN packet from %s: %s", + pj_sockaddr_print(&pkt->src.clt_addr, ip, sizeof(ip), 3), + errmsg)); + return; + } } } diff --git a/pjnath/src/pjturn-srv/turn.h b/pjnath/src/pjturn-srv/turn.h index a53cabbf..a6dcd9ec 100644 --- a/pjnath/src/pjturn-srv/turn.h +++ b/pjnath/src/pjturn-srv/turn.h @@ -16,42 +16,43 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#ifndef __PJTURN_SRV_TURN_H__ -#define __PJTURN_SRV_TURN_H__ +#ifndef __PJ_TURN_SRV_TURN_H__ +#define __PJ_TURN_SRV_TURN_H__ #include #include -typedef struct pjturn_relay_res pjturn_relay_res; -typedef struct pjturn_listener pjturn_listener; -typedef struct pjturn_permission pjturn_permission; -typedef struct pjturn_allocation pjturn_allocation; -typedef struct pjturn_srv pjturn_srv; -typedef struct pjturn_pkt pjturn_pkt; +typedef struct pj_turn_relay_res pj_turn_relay_res; +typedef struct pj_turn_listener pj_turn_listener; +typedef struct pj_turn_permission pj_turn_permission; +typedef struct pj_turn_allocation pj_turn_allocation; +typedef struct pj_turn_srv pj_turn_srv; +typedef struct pj_turn_pkt pj_turn_pkt; -#define PJTURN_INVALID_CHANNEL 0xFFFF -#define PJTURN_NO_TIMEOUT ((long)0x7FFFFFFF) -#define PJTURN_MAX_PKT_LEN 3000 -#define PJTURN_PERM_TIMEOUT 300 -#define PJTURN_CHANNEL_TIMEOUT 600 +#define PJ_TURN_INVALID_CHANNEL 0xFFFF +#define PJ_TURN_INVALID_LIS_ID ((unsigned)-1) +#define PJ_TURN_NO_TIMEOUT ((long)0x7FFFFFFF) +#define PJ_TURN_MAX_PKT_LEN 3000 +#define PJ_TURN_PERM_TIMEOUT 300 +#define PJ_TURN_CHANNEL_TIMEOUT 600 /** Transport types */ enum { - PJTURN_TP_UDP = 16, /**< UDP. */ - PJTURN_TP_TCP = 6 /**< TCP. */ + PJ_TURN_TP_UDP = 16, /**< UDP. */ + PJ_TURN_TP_TCP = 6 /**< TCP. */ }; /** * Get transport type name string. */ -PJ_DECL(const char*) pjturn_tp_type_name(int tp_type); +PJ_DECL(const char*) pj_turn_tp_type_name(int tp_type); /** * This structure describes TURN relay resource. An allocation allocates * one relay resource, and optionally it may reserve another resource. */ -struct pjturn_relay_res +struct pj_turn_relay_res { /** Hash table key */ struct { @@ -63,7 +64,7 @@ struct pjturn_relay_res } hkey; /** Allocation who requested or reserved this resource. */ - pjturn_allocation *allocation; + pj_turn_allocation *allocation; /** Username used in credential */ pj_str_t user; @@ -92,7 +93,7 @@ struct pjturn_relay_res pj_ioqueue_op_key_t read_key; /** The incoming packet buffer */ - char rx_pkt[PJTURN_MAX_PKT_LEN]; + char rx_pkt[PJ_TURN_MAX_PKT_LEN]; /** Source address of the packet. */ pj_sockaddr src_addr; @@ -101,7 +102,7 @@ struct pjturn_relay_res int src_addr_len; /** The outgoing packet buffer. This must be 3wbit aligned. */ - char tx_pkt[PJTURN_MAX_PKT_LEN+4]; + char tx_pkt[PJ_TURN_MAX_PKT_LEN+4]; } tp; }; @@ -115,46 +116,20 @@ struct pjturn_relay_res * This structure describes key to lookup TURN allocations in the * allocation hash table. */ -typedef struct pjturn_allocation_key +typedef struct pj_turn_allocation_key { int tp_type; /**< Transport type. */ pj_sockaddr clt_addr; /**< Client's address. */ -} pjturn_allocation_key; +} pj_turn_allocation_key; /** - * Allocation request. + * This structure describes TURN pj_turn_allocation session. */ -typedef struct pjturn_allocation_req -{ - /** Requested transport */ - unsigned tp_type; - - /** Requested IP */ - char addr[PJ_INET6_ADDRSTRLEN]; - - /** Requested bandwidth */ - unsigned bandwidth; - - /** Lifetime. */ - unsigned lifetime; - - /** A bits */ - unsigned rpp_bits; - - /** Requested port */ - unsigned rpp_port; - -} pjturn_allocation_req; - - -/** - * This structure describes TURN pjturn_allocation session. - */ -struct pjturn_allocation +struct pj_turn_allocation { /** Hash table key to identify client. */ - pjturn_allocation_key hkey; + pj_turn_allocation_key hkey; /** Pool for this allocation. */ pj_pool_t *pool; @@ -169,16 +144,16 @@ struct pjturn_allocation pj_lock_t *lock; /** TURN listener. */ - pjturn_listener *listener; + pj_turn_listener *listener; /** Client socket, if connection to client is using TCP. */ pj_sock_t clt_sock; /** The relay resource for this allocation. */ - pjturn_relay_res relay; + pj_turn_relay_res relay; /** Relay resource reserved by this allocation, if any */ - pjturn_relay_res *resv; + pj_turn_relay_res *resv; /** Requested bandwidth */ unsigned bandwidth; @@ -186,6 +161,9 @@ struct pjturn_allocation /** STUN session for this client */ pj_stun_session *sess; + /** Credential for this STUN session. */ + pj_stun_auth_cred cred; + /** Peer hash table (keyed by peer address) */ pj_hash_table_t *peer_table; @@ -198,21 +176,21 @@ struct pjturn_allocation * This structure describes the hash table key to lookup TURN * permission. */ -typedef struct pjturn_permission_key +typedef struct pj_turn_permission_key { /** Peer address. */ pj_sockaddr peer_addr; -} pjturn_permission_key; +} pj_turn_permission_key; /** - * This structure describes TURN pjturn_permission or channel. + * This structure describes TURN pj_turn_permission or channel. */ -struct pjturn_permission +struct pj_turn_permission { /** Hash table key */ - pjturn_permission_key hkey; + pj_turn_permission_key hkey; /** Transport socket. If TCP is used, the value will be the actual * TCP socket. If UDP is used, the value will be the relay address @@ -220,9 +198,9 @@ struct pjturn_permission pj_sock_t sock; /** TURN allocation that owns this permission/channel */ - pjturn_allocation *allocation; + pj_turn_allocation *allocation; - /** Optional channel number, or PJTURN_INVALID_CHANNEL if channel number + /** Optional channel number, or PJ_TURN_INVALID_CHANNEL if channel number * is not requested for this permission. */ pj_uint16_t channel; @@ -234,31 +212,23 @@ struct pjturn_permission /** * Create new allocation. */ -PJ_DECL(pj_status_t) pjturn_allocation_create(pjturn_listener *listener, +PJ_DECL(pj_status_t) pj_turn_allocation_create(pj_turn_listener *listener, const pj_sockaddr_t *src_addr, unsigned src_addr_len, const pj_stun_msg *msg, - const pjturn_allocation_req *req, - pjturn_allocation **p_alloc); + pj_stun_session *srv_sess, + pj_turn_allocation **p_alloc); /** * Destroy allocation. */ -PJ_DECL(void) pjturn_allocation_destroy(pjturn_allocation *alloc); +PJ_DECL(void) pj_turn_allocation_destroy(pj_turn_allocation *alloc); -/** - * Create relay. - */ -PJ_DECL(pj_status_t) pjturn_allocation_create_relay(pjturn_srv *srv, - pjturn_allocation *alloc, - const pj_stun_msg *msg, - const pjturn_allocation_req *req, - pjturn_relay_res *relay); /** * Handle incoming packet from client. */ -PJ_DECL(void) pjturn_allocation_on_rx_client_pkt(pjturn_allocation *alloc, - pjturn_pkt *pkt); +PJ_DECL(void) pj_turn_allocation_on_rx_client_pkt(pj_turn_allocation *alloc, + pj_turn_pkt *pkt); /****************************************************************************/ /* @@ -269,10 +239,16 @@ PJ_DECL(void) pjturn_allocation_on_rx_client_pkt(pjturn_allocation *alloc, * This structure describes TURN listener socket. A TURN listener socket * listens for incoming connections from clients. */ -struct pjturn_listener +struct pj_turn_listener { + /** Object name/identification */ + char *obj_name; + + /** Slightly longer info about this listener */ + char info[80]; + /** TURN server instance. */ - pjturn_srv *server; + pj_turn_srv *server; /** Listener index in the server */ unsigned id; @@ -293,7 +269,7 @@ struct pjturn_listener unsigned flags; /** Sendto handler */ - pj_status_t (*sendto)(pjturn_listener *listener, + pj_status_t (*sendto)(pj_turn_listener *listener, const void *packet, pj_size_t size, unsigned flag, @@ -301,23 +277,23 @@ struct pjturn_listener int addr_len); /** Destroy handler */ - pj_status_t (*destroy)(pjturn_listener*); + pj_status_t (*destroy)(pj_turn_listener*); }; /** * An incoming packet. */ -struct pjturn_pkt +struct pj_turn_pkt { /** Pool for this packet */ pj_pool_t *pool; /** Listener that owns this. */ - pjturn_listener *listener; + pj_turn_listener *listener; /** Packet buffer (must be 32bit aligned). */ - pj_uint8_t pkt[PJTURN_MAX_PKT_LEN]; + pj_uint8_t pkt[PJ_TURN_MAX_PKT_LEN]; /** Size of the packet */ pj_size_t len; @@ -326,7 +302,7 @@ struct pjturn_pkt pj_time_val rx_time; /** Source transport type and source address. */ - pjturn_allocation_key src; + pj_turn_allocation_key src; /** Source address length. */ int src_addr_len; @@ -336,18 +312,18 @@ struct pjturn_pkt /** * Create a new listener on the specified port. */ -PJ_DECL(pj_status_t) pjturn_listener_create_udp(pjturn_srv *srv, +PJ_DECL(pj_status_t) pj_turn_listener_create_udp(pj_turn_srv *srv, int af, const pj_str_t *bound_addr, unsigned port, unsigned concurrency_cnt, unsigned flags, - pjturn_listener **p_listener); + pj_turn_listener **p_listener); /** * Send packet with this listener. */ -PJ_DECL(pj_status_t) pjturn_listener_sendto(pjturn_listener *listener, +PJ_DECL(pj_status_t) pj_turn_listener_sendto(pj_turn_listener *listener, const void *packet, pj_size_t size, unsigned flag, @@ -357,7 +333,7 @@ PJ_DECL(pj_status_t) pjturn_listener_sendto(pjturn_listener *listener, /** * Destroy listener. */ -PJ_DECL(pj_status_t) pjturn_listener_destroy(pjturn_listener *listener); +PJ_DECL(pj_status_t) pj_turn_listener_destroy(pj_turn_listener *listener); /****************************************************************************/ @@ -365,15 +341,15 @@ PJ_DECL(pj_status_t) pjturn_listener_destroy(pjturn_listener *listener); * TURN Server API */ /** - * This structure describes TURN pjturn_srv instance. + * This structure describes TURN pj_turn_srv instance. */ -struct pjturn_srv +struct pj_turn_srv { + /** Object name */ + char *obj_name; + /** Core settings */ struct { - /** Object name */ - char *obj_name; - /** Pool factory */ pj_pool_factory *pf; @@ -393,7 +369,7 @@ struct pjturn_srv unsigned lis_cnt; /** Array of listeners. */ - pjturn_listener **listener; + pj_turn_listener **listener; /** Array of STUN sessions, one for each listeners. */ pj_stun_session **stun_sess; @@ -404,9 +380,17 @@ struct pjturn_srv /** Array of worker threads. */ pj_thread_t **thread; + /** Thread quit signal */ + pj_bool_t quit; + /** STUN config. */ pj_stun_config stun_cfg; + /** STUN auth credential. */ + pj_stun_auth_cred cred; + + /** Thread local ID for storing credential */ + long tls_key, tls_data; } core; @@ -453,38 +437,59 @@ struct pjturn_srv /** * Create server. */ -PJ_DECL(pj_status_t) pjturn_srv_create(pj_pool_factory *pf, - pjturn_srv **p_srv); +PJ_DECL(pj_status_t) pj_turn_srv_create(pj_pool_factory *pf, + pj_turn_srv **p_srv); /** * Destroy server. */ -PJ_DECL(pj_status_t) pjturn_srv_destroy(pjturn_srv *srv); +PJ_DECL(pj_status_t) pj_turn_srv_destroy(pj_turn_srv *srv); /** * Add listener. */ -PJ_DECL(pj_status_t) pjturn_srv_add_listener(pjturn_srv *srv, - pjturn_listener *lis); +PJ_DECL(pj_status_t) pj_turn_srv_add_listener(pj_turn_srv *srv, + pj_turn_listener *lis); /** * Register an allocation. */ -PJ_DECL(pj_status_t) pjturn_srv_register_allocation(pjturn_srv *srv, - pjturn_allocation *alloc); +PJ_DECL(pj_status_t) pj_turn_srv_register_allocation(pj_turn_srv *srv, + pj_turn_allocation *alloc); /** * Unregister an allocation. */ -PJ_DECL(pj_status_t) pjturn_srv_unregister_allocation(pjturn_srv *srv, - pjturn_allocation *alloc); +PJ_DECL(pj_status_t) pj_turn_srv_unregister_allocation(pj_turn_srv *srv, + pj_turn_allocation *alloc); /** * This callback is called by UDP listener on incoming packet. */ -PJ_DECL(void) pjturn_srv_on_rx_pkt(pjturn_srv *srv, - pjturn_pkt *pkt); +PJ_DECL(void) pj_turn_srv_on_rx_pkt(pj_turn_srv *srv, + pj_turn_pkt *pkt); + + +/** + * Store the credential to put placed for the specified message for + * future retrieval. + */ +PJ_DECL(pj_status_t) pj_turn_srv_put_cred(pj_turn_srv *srv, + const pj_stun_msg *request, + pj_stun_tx_data *response); + +/** + * Retrieve previously stored credential for the specified message. + */ +PJ_DECL(pj_status_t) pj_turn_srv_get_cred(const pj_stun_msg *msg, + void *user_data, + pj_pool_t *pool, + pj_str_t *realm, + pj_str_t *username, + pj_str_t *nonce, + int *data_type, + pj_str_t *data); -#endif /* __PJTURN_SRV_TURN_H__ */ +#endif /* __PJ_TURN_SRV_TURN_H__ */ -- cgit v1.2.3