/* Callback from active socket when incoming packet is received */ static pj_bool_t on_data_recvfrom(pj_activesock_t *asock, void *data, pj_size_t size, const pj_sockaddr_t *src_addr, int addr_len, pj_status_t status) { pj_stun_sock *stun_sock; pj_stun_msg_hdr *hdr; pj_uint16_t type; stun_sock = (pj_stun_sock*) pj_activesock_get_user_data(asock); if (!stun_sock) return PJ_FALSE; /* Log socket error */ if (status != PJ_SUCCESS) { PJ_PERROR(2,(stun_sock->obj_name, status, "recvfrom() error")); return PJ_TRUE; } pj_grp_lock_acquire(stun_sock->grp_lock); /* Check that this is STUN message */ status = pj_stun_msg_check((const pj_uint8_t*)data, size, PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET); if (status != PJ_SUCCESS) { /* Not STUN -- give it to application */ goto process_app_data; } /* Treat packet as STUN header and copy the STUN message type. * We don't want to access the type directly from the header * since it may not be properly aligned. */ hdr = (pj_stun_msg_hdr*) data; pj_memcpy(&type, &hdr->type, 2); type = pj_ntohs(type); /* If the packet is a STUN Binding response and part of the * transaction ID matches our internal ID, then this is * our internal STUN message (Binding request or keep alive). * Give it to our STUN session. */ if (!PJ_STUN_IS_RESPONSE(type) || PJ_STUN_GET_METHOD(type) != PJ_STUN_BINDING_METHOD || pj_memcmp(hdr->tsx_id, stun_sock->tsx_id, 10) != 0) { /* Not STUN Binding response, or STUN transaction ID mismatch. * This is not our message too -- give it to application. */ goto process_app_data; } /* This is our STUN Binding response. Give it to the STUN session */ status = pj_stun_session_on_rx_pkt(stun_sock->stun_sess, data, size, PJ_STUN_IS_DATAGRAM, NULL, NULL, src_addr, addr_len); status = pj_grp_lock_release(stun_sock->grp_lock); return status!=PJ_EGONE ? PJ_TRUE : PJ_FALSE; process_app_data: if (stun_sock->cb.on_rx_data) { pj_bool_t ret; ret = (*stun_sock->cb.on_rx_data)(stun_sock, data, size, src_addr, addr_len); status = pj_grp_lock_release(stun_sock->grp_lock); return status!=PJ_EGONE ? PJ_TRUE : PJ_FALSE; } status = pj_grp_lock_release(stun_sock->grp_lock); return status!=PJ_EGONE ? PJ_TRUE : PJ_FALSE; }
/* Start socket. */ PJ_DEF(pj_status_t) pj_stun_sock_start( pj_stun_sock *stun_sock, const pj_str_t *domain, pj_uint16_t default_port, pj_dns_resolver *resolver) { pj_status_t status; PJ_ASSERT_RETURN(stun_sock && domain && default_port, PJ_EINVAL); pj_grp_lock_acquire(stun_sock->grp_lock); /* Check whether the domain contains IP address */ stun_sock->srv_addr.addr.sa_family = (pj_uint16_t)stun_sock->af; status = pj_inet_pton(stun_sock->af, domain, pj_sockaddr_get_addr(&stun_sock->srv_addr)); if (status != PJ_SUCCESS) { stun_sock->srv_addr.addr.sa_family = (pj_uint16_t)0; } /* If resolver is set, try to resolve with DNS SRV first. It * will fallback to DNS A/AAAA when no SRV record is found. */ if (status != PJ_SUCCESS && resolver) { const pj_str_t res_name = pj_str("_stun._udp."); unsigned opt; pj_assert(stun_sock->q == NULL); opt = PJ_DNS_SRV_FALLBACK_A; if (stun_sock->af == pj_AF_INET6()) { opt |= (PJ_DNS_SRV_RESOLVE_AAAA | PJ_DNS_SRV_FALLBACK_AAAA); } status = pj_dns_srv_resolve(domain, &res_name, default_port, stun_sock->pool, resolver, opt, stun_sock, &dns_srv_resolver_cb, &stun_sock->q); /* Processing will resume when the DNS SRV callback is called */ } else { if (status != PJ_SUCCESS) { pj_addrinfo ai; unsigned cnt = 1; status = pj_getaddrinfo(stun_sock->af, domain, &cnt, &ai); if (status != PJ_SUCCESS) return status; pj_sockaddr_cp(&stun_sock->srv_addr, &ai.ai_addr); } pj_sockaddr_set_port(&stun_sock->srv_addr, (pj_uint16_t)default_port); /* Start sending Binding request */ status = get_mapped_addr(stun_sock); } pj_grp_lock_release(stun_sock->grp_lock); return status; }
/* Get info */ PJ_DEF(pj_status_t) pj_stun_sock_get_info( pj_stun_sock *stun_sock, pj_stun_sock_info *info) { int addr_len; pj_status_t status; PJ_ASSERT_RETURN(stun_sock && info, PJ_EINVAL); pj_grp_lock_acquire(stun_sock->grp_lock); /* Copy STUN server address and mapped address */ pj_memcpy(&info->srv_addr, &stun_sock->srv_addr, sizeof(pj_sockaddr)); pj_memcpy(&info->mapped_addr, &stun_sock->mapped_addr, sizeof(pj_sockaddr)); /* Retrieve bound address */ addr_len = sizeof(info->bound_addr); status = pj_sock_getsockname(stun_sock->sock_fd, &info->bound_addr, &addr_len); if (status != PJ_SUCCESS) { pj_grp_lock_release(stun_sock->grp_lock); return status; } /* If socket is bound to a specific interface, then only put that * interface in the alias list. Otherwise query all the interfaces * in the host. */ if (pj_sockaddr_has_addr(&info->bound_addr)) { info->alias_cnt = 1; pj_sockaddr_cp(&info->aliases[0], &info->bound_addr); } else { pj_sockaddr def_addr; pj_uint16_t port = pj_sockaddr_get_port(&info->bound_addr); unsigned i; /* Get the default address */ status = pj_gethostip(stun_sock->af, &def_addr); if (status != PJ_SUCCESS) { pj_grp_lock_release(stun_sock->grp_lock); return status; } pj_sockaddr_set_port(&def_addr, port); /* Enum all IP interfaces in the host */ info->alias_cnt = PJ_ARRAY_SIZE(info->aliases); status = pj_enum_ip_interface(stun_sock->af, &info->alias_cnt, info->aliases); if (status != PJ_SUCCESS) { pj_grp_lock_release(stun_sock->grp_lock); return status; } /* Set the port number for each address. */ for (i=0; i<info->alias_cnt; ++i) { pj_sockaddr_set_port(&info->aliases[i], port); } /* Put the default IP in the first slot */ for (i=0; i<info->alias_cnt; ++i) { if (pj_sockaddr_cmp(&info->aliases[i], &def_addr)==0) { if (i!=0) { pj_sockaddr_cp(&info->aliases[i], &info->aliases[0]); pj_sockaddr_cp(&info->aliases[0], &def_addr); } break; } } } pj_grp_lock_release(stun_sock->grp_lock); return PJ_SUCCESS; }
/* * Callback upon request completion. */ static void on_request_complete(pj_stun_session *stun_sess, pj_status_t status, void *token, pj_stun_tx_data *tdata, const pj_stun_msg *response, const pj_sockaddr_t *src_addr, unsigned src_addr_len) { nat_detect_session *sess; pj_stun_sockaddr_attr *mattr = NULL; pj_stun_changed_addr_attr *ca = NULL; pj_uint32_t *tsx_id; int cmp; unsigned test_id; PJ_UNUSED_ARG(token); PJ_UNUSED_ARG(tdata); PJ_UNUSED_ARG(src_addr); PJ_UNUSED_ARG(src_addr_len); sess = (nat_detect_session*) pj_stun_session_get_user_data(stun_sess); pj_grp_lock_acquire(sess->grp_lock); /* Find errors in the response */ if (status == PJ_SUCCESS) { /* Check error message */ if (PJ_STUN_IS_ERROR_RESPONSE(response->hdr.type)) { pj_stun_errcode_attr *eattr; int err_code; eattr = (pj_stun_errcode_attr*) pj_stun_msg_find_attr(response, PJ_STUN_ATTR_ERROR_CODE, 0); if (eattr != NULL) err_code = eattr->err_code; else err_code = PJ_STUN_SC_SERVER_ERROR; status = PJ_STATUS_FROM_STUN_CODE(err_code); } else { /* Get MAPPED-ADDRESS or XOR-MAPPED-ADDRESS */ mattr = (pj_stun_sockaddr_attr*) pj_stun_msg_find_attr(response, PJ_STUN_ATTR_XOR_MAPPED_ADDR, 0); if (mattr == NULL) { mattr = (pj_stun_sockaddr_attr*) pj_stun_msg_find_attr(response, PJ_STUN_ATTR_MAPPED_ADDR, 0); } if (mattr == NULL) { status = PJNATH_ESTUNNOMAPPEDADDR; } /* Get CHANGED-ADDRESS attribute */ ca = (pj_stun_changed_addr_attr*) pj_stun_msg_find_attr(response, PJ_STUN_ATTR_CHANGED_ADDR, 0); if (ca == NULL) { status = PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_SERVER_ERROR); } } } /* Save the result */ tsx_id = (pj_uint32_t*) tdata->msg->hdr.tsx_id; test_id = tsx_id[2]; if (test_id >= ST_MAX) { PJ_LOG(4,(sess->pool->obj_name, "Invalid transaction ID %u in response", test_id)); end_session(sess, PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_SERVER_ERROR), PJ_STUN_NAT_TYPE_ERR_UNKNOWN); goto on_return; } PJ_LOG(5,(sess->pool->obj_name, "Completed %s, status=%d", test_names[test_id], status)); sess->result[test_id].complete = PJ_TRUE; sess->result[test_id].status = status; if (status == PJ_SUCCESS) { pj_memcpy(&sess->result[test_id].ma, &mattr->sockaddr.ipv4, sizeof(pj_sockaddr_in)); pj_memcpy(&sess->result[test_id].ca, &ca->sockaddr.ipv4, sizeof(pj_sockaddr_in)); } /* Send Test 1B only when Test 2 completes. Must not send Test 1B * before Test 2 completes to avoid creating mapping on the NAT. */ if (!sess->result[ST_TEST_1B].executed && sess->result[ST_TEST_2].complete && sess->result[ST_TEST_2].status != PJ_SUCCESS && sess->result[ST_TEST_1].complete && sess->result[ST_TEST_1].status == PJ_SUCCESS) { cmp = pj_memcmp(&sess->local_addr, &sess->result[ST_TEST_1].ma, sizeof(pj_sockaddr_in)); if (cmp != 0) send_test(sess, ST_TEST_1B, &sess->result[ST_TEST_1].ca, 0); } if (test_completed(sess)<3 || test_completed(sess)!=test_executed(sess)) goto on_return; /* Handle the test result according to RFC 3489 page 22: +--------+ | Test | | 1 | +--------+ | | V /\ /\ N / \ Y / \ Y +--------+ UDP <-------/Resp\--------->/ IP \------------->| Test | Blocked \ ? / \Same/ | 2 | \ / \? / +--------+ \/ \/ | | N | | V V /\ +--------+ Sym. N / \ | Test | UDP <---/Resp\ | 2 | Firewall \ ? / +--------+ \ / | \/ V |Y /\ /\ | Symmetric N / \ +--------+ N / \ V NAT <--- / IP \<-----| Test |<--- /Resp\ Open \Same/ | 1B | \ ? / Internet \? / +--------+ \ / \/ \/ | |Y | | | V | Full | Cone V /\ +--------+ / \ Y | Test |------>/Resp\---->Restricted | 3 | \ ? / +--------+ \ / \/ |N | Port +------>Restricted Figure 2: Flow for type discovery process */ switch (sess->result[ST_TEST_1].status) { case PJNATH_ESTUNTIMEDOUT: /* * Test 1 has timed-out. Conclude with NAT_TYPE_BLOCKED. */ end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_BLOCKED); break; case PJ_SUCCESS: /* * Test 1 is successful. Further tests are needed to detect * NAT type. Compare the MAPPED-ADDRESS with the local address. */ cmp = pj_memcmp(&sess->local_addr, &sess->result[ST_TEST_1].ma, sizeof(pj_sockaddr_in)); if (cmp==0) { /* * MAPPED-ADDRESS and local address is equal. Need one more * test to determine NAT type. */ switch (sess->result[ST_TEST_2].status) { case PJ_SUCCESS: /* * Test 2 is also successful. We're in the open. */ end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_OPEN); break; case PJNATH_ESTUNTIMEDOUT: /* * Test 2 has timed out. We're behind somekind of UDP * firewall. */ end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_SYMMETRIC_UDP); break; default: /* * We've got other error with Test 2. */ end_session(sess, sess->result[ST_TEST_2].status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); break; } } else { /* * MAPPED-ADDRESS is different than local address. * We're behind NAT. */ switch (sess->result[ST_TEST_2].status) { case PJ_SUCCESS: /* * Test 2 is successful. We're behind a full-cone NAT. */ end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_FULL_CONE); break; case PJNATH_ESTUNTIMEDOUT: /* * Test 2 has timed-out Check result of test 1B.. */ switch (sess->result[ST_TEST_1B].status) { case PJ_SUCCESS: /* * Compare the MAPPED-ADDRESS of test 1B with the * MAPPED-ADDRESS returned in test 1.. */ cmp = pj_memcmp(&sess->result[ST_TEST_1].ma, &sess->result[ST_TEST_1B].ma, sizeof(pj_sockaddr_in)); if (cmp != 0) { /* * MAPPED-ADDRESS is different, we're behind a * symmetric NAT. */ end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_SYMMETRIC); } else { /* * MAPPED-ADDRESS is equal. We're behind a restricted * or port-restricted NAT, depending on the result of * test 3. */ switch (sess->result[ST_TEST_3].status) { case PJ_SUCCESS: /* * Test 3 is successful, we're behind a restricted * NAT. */ end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_RESTRICTED); break; case PJNATH_ESTUNTIMEDOUT: /* * Test 3 failed, we're behind a port restricted * NAT. */ end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_PORT_RESTRICTED); break; default: /* * Got other error with test 3. */ end_session(sess, sess->result[ST_TEST_3].status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); break; } } break; case PJNATH_ESTUNTIMEDOUT: /* * Strangely test 1B has failed. Maybe connectivity was * lost? Or perhaps port 3489 (the usual port number in * CHANGED-ADDRESS) is blocked? */ switch (sess->result[ST_TEST_3].status) { case PJ_SUCCESS: /* Although test 1B failed, test 3 was successful. * It could be that port 3489 is blocked, while the * NAT itself looks to be a Restricted one. */ end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_RESTRICTED); break; default: /* Can't distinguish between Symmetric and Port * Restricted, so set the type to Unknown */ end_session(sess, PJ_SUCCESS, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); break; } break; default: /* * Got other error with test 1B. */ end_session(sess, sess->result[ST_TEST_1B].status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); break; } break; default: /* * We've got other error with Test 2. */ end_session(sess, sess->result[ST_TEST_2].status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); break; } } break; default: /* * We've got other error with Test 1. */ end_session(sess, sess->result[ST_TEST_1].status, PJ_STUN_NAT_TYPE_ERR_UNKNOWN); break; } on_return: pj_grp_lock_release(sess->grp_lock); }
/* * Send outgoing message and start STUN transaction. */ PJ_DEF(pj_status_t) pj_stun_client_tsx_send_msg(pj_stun_client_tsx *tsx, pj_bool_t retransmit, void *pkt, unsigned pkt_len) { pj_status_t status; PJ_ASSERT_RETURN(tsx && pkt && pkt_len, PJ_EINVAL); PJ_ASSERT_RETURN(tsx->retransmit_timer.id == 0, PJ_EBUSY); pj_grp_lock_acquire(tsx->grp_lock); /* Encode message */ tsx->last_pkt = pkt; tsx->last_pkt_size = pkt_len; /* Update STUN retransmit flag */ tsx->require_retransmit = retransmit; /* For TCP, schedule timeout timer after PJ_STUN_TIMEOUT_VALUE. * Since we don't have timeout timer, simulate this by using * retransmit timer. */ if (!retransmit) { unsigned timeout; pj_assert(tsx->retransmit_timer.id == 0); tsx->transmit_count = PJ_STUN_MAX_TRANSMIT_COUNT; timeout = tsx->rto_msec * 16; tsx->retransmit_time.sec = timeout / 1000; tsx->retransmit_time.msec = timeout % 1000; /* Schedule timer first because when send_msg() failed we can * cancel it (as opposed to when schedule_timer() failed we cannot * cancel transmission). */; status = pj_timer_heap_schedule_w_grp_lock(tsx->timer_heap, &tsx->retransmit_timer, &tsx->retransmit_time, TIMER_ACTIVE, tsx->grp_lock); if (status != PJ_SUCCESS) { tsx->retransmit_timer.id = TIMER_INACTIVE; pj_grp_lock_release(tsx->grp_lock); return status; } } /* Send the message */ status = tsx_transmit_msg(tsx, PJ_TRUE); if (status != PJ_SUCCESS) { pj_timer_heap_cancel_if_active(tsx->timer_heap, &tsx->retransmit_timer, TIMER_INACTIVE); pj_grp_lock_release(tsx->grp_lock); return status; } pj_grp_lock_release(tsx->grp_lock); return PJ_SUCCESS; }
/** * Unlock the TURN socket. */ PJ_DEF(pj_status_t) pj_turn_sock_unlock(pj_turn_sock *turn_sock) { return pj_grp_lock_release(turn_sock->grp_lock); }
/* * Notification from ioqueue when incoming UDP packet is received. */ static pj_bool_t on_data_read(pj_activesock_t *asock, void *data, pj_size_t size, pj_status_t status, pj_size_t *remainder) { pj_turn_sock *turn_sock; pj_bool_t ret = PJ_TRUE; turn_sock = (pj_turn_sock*) pj_activesock_get_user_data(asock); pj_grp_lock_acquire(turn_sock->grp_lock); if (status == PJ_SUCCESS && turn_sock->sess && !turn_sock->is_destroying) { /* Report incoming packet to TURN session, repeat while we have * "packet" in the buffer (required for stream-oriented transports) */ unsigned pkt_len; //PJ_LOG(5,(turn_sock->pool->obj_name, // "Incoming data, %lu bytes total buffer", size)); while ((pkt_len=has_packet(turn_sock, data, size)) != 0) { pj_size_t parsed_len; //const pj_uint8_t *pkt = (const pj_uint8_t*)data; //PJ_LOG(5,(turn_sock->pool->obj_name, // "Packet start: %02X %02X %02X %02X", // pkt[0], pkt[1], pkt[2], pkt[3])); //PJ_LOG(5,(turn_sock->pool->obj_name, // "Processing %lu bytes packet of %lu bytes total buffer", // pkt_len, size)); parsed_len = (unsigned)size; pj_turn_session_on_rx_pkt(turn_sock->sess, data, size, &parsed_len); /* parsed_len may be zero if we have parsing error, so use our * previous calculation to exhaust the bad packet. */ if (parsed_len == 0) parsed_len = pkt_len; if (parsed_len < (unsigned)size) { *remainder = size - parsed_len; pj_memmove(data, ((char*)data)+parsed_len, *remainder); } else { *remainder = 0; } size = *remainder; //PJ_LOG(5,(turn_sock->pool->obj_name, // "Buffer size now %lu bytes", size)); } } else if (status != PJ_SUCCESS && turn_sock->conn_type != PJ_TURN_TP_UDP) { sess_fail(turn_sock, "TCP connection closed", status); ret = PJ_FALSE; goto on_return; } on_return: pj_grp_lock_release(turn_sock->grp_lock); return ret; }
static pjsip_dialog *find_dialog(pjsip_rx_data *rdata) { pj_str_t tsx_key; pjsip_transaction *tsx; pjsip_dialog *dlg; pj_str_t *local_tag; pj_str_t *remote_tag; if (!rdata->msg_info.msg) { return NULL; } if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) { local_tag = &rdata->msg_info.to->tag; remote_tag = &rdata->msg_info.from->tag; } else { local_tag = &rdata->msg_info.from->tag; remote_tag = &rdata->msg_info.to->tag; } /* We can only call the convenient method for * 1) responses * 2) non-CANCEL requests * 3) CANCEL requests with a to-tag */ if (rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG || pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_cancel_method) || rdata->msg_info.to->tag.slen != 0) { dlg = pjsip_ua_find_dialog(&rdata->msg_info.cid->id, local_tag, remote_tag, PJ_FALSE); if (dlg) { return dlg; } } /* * There may still be a matching dialog if this is * 1) an incoming CANCEL request without a to-tag * 2) an incoming response to a dialog-creating request. */ if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) { /* CANCEL requests will need to match the INVITE we initially received. Any * other request type will either have been matched already or is not in * dialog */ pjsip_tsx_create_key(rdata->tp_info.pool, &tsx_key, PJSIP_ROLE_UAS, pjsip_get_invite_method(), rdata); } else { pjsip_tsx_create_key(rdata->tp_info.pool, &tsx_key, PJSIP_ROLE_UAC, &rdata->msg_info.cseq->method, rdata); } tsx = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_TRUE); if (!tsx) { ast_debug(3, "Could not find matching transaction for %s\n", pjsip_rx_data_get_info(rdata)); return NULL; } dlg = pjsip_tsx_get_dlg(tsx); #ifdef HAVE_PJ_TRANSACTION_GRP_LOCK pj_grp_lock_release(tsx->grp_lock); #else pj_mutex_unlock(tsx->mutex); #endif return dlg; }
/* * This is the handler to receive message for this test. It is used to * control and verify the behavior of the message transmitted by the * transaction. */ static pj_bool_t msg_receiver_on_rx_request(pjsip_rx_data *rdata) { if (pj_stricmp2(&rdata->msg_info.via->branch_param, TEST1_BRANCH_ID) == 0) { /* * The TEST1_BRANCH_ID test performs the verifications for transaction * retransmission mechanism. It will not answer the incoming request * with any response. */ pjsip_msg *msg = rdata->msg_info.msg; PJ_LOG(4,(THIS_FILE, " received request")); /* Only wants to take INVITE or OPTIONS method. */ if (msg->line.req.method.id != PJSIP_INVITE_METHOD && msg->line.req.method.id != PJSIP_OPTIONS_METHOD) { PJ_LOG(3,(THIS_FILE, " error: received unexpected method %.*s", msg->line.req.method.name.slen, msg->line.req.method.name.ptr)); test_complete = -600; return PJ_TRUE; } if (recv_count == 0) { recv_count++; //pj_gettimeofday(&recv_last); recv_last = rdata->pkt_info.timestamp; } else { pj_time_val now; unsigned msec_expected, msec_elapsed; int max_received; //pj_gettimeofday(&now); now = rdata->pkt_info.timestamp; PJ_TIME_VAL_SUB(now, recv_last); msec_elapsed = now.sec*1000 + now.msec; ++recv_count; msec_expected = (1<<(recv_count-2))*pjsip_cfg()->tsx.t1; if (msg->line.req.method.id != PJSIP_INVITE_METHOD) { if (msec_expected > pjsip_cfg()->tsx.t2) msec_expected = pjsip_cfg()->tsx.t2; max_received = 11; } else { max_received = 7; } if (DIFF(msec_expected, msec_elapsed) > TEST1_ALLOWED_DIFF) { PJ_LOG(3,(THIS_FILE, " error: expecting retransmission no. %d in %d " "ms, received in %d ms", recv_count-1, msec_expected, msec_elapsed)); test_complete = -610; } if (recv_count > max_received) { PJ_LOG(3,(THIS_FILE, " error: too many messages (%d) received", recv_count)); test_complete = -620; } //pj_gettimeofday(&recv_last); recv_last = rdata->pkt_info.timestamp; } return PJ_TRUE; } else if (pj_stricmp2(&rdata->msg_info.via->branch_param, TEST4_BRANCH_ID) == 0) { /* * The TEST4_BRANCH_ID test simulates transport failure after several * retransmissions. */ recv_count++; if (recv_count == TEST4_RETRANSMIT_CNT) { /* Simulate transport failure. */ pjsip_loop_set_failure(loop, 2, NULL); } else if (recv_count > TEST4_RETRANSMIT_CNT) { PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", recv_count)); test_complete = -631; } return PJ_TRUE; } else if (pj_stricmp2(&rdata->msg_info.via->branch_param, TEST5_BRANCH_ID) == 0) { /* * The TEST5_BRANCH_ID test simulates user terminating the transaction * after several retransmissions. */ recv_count++; if (recv_count == TEST5_RETRANSMIT_CNT+1) { pj_str_t key; pjsip_transaction *tsx; pjsip_tsx_create_key( rdata->tp_info.pool, &key, PJSIP_ROLE_UAC, &rdata->msg_info.msg->line.req.method, rdata); tsx = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE); if (tsx) { pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED); pj_grp_lock_release(tsx->grp_lock); } else { PJ_LOG(3,(THIS_FILE, " error: uac transaction not found!")); test_complete = -633; } } else if (recv_count > TEST5_RETRANSMIT_CNT+1) { PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", recv_count)); test_complete = -634; } return PJ_TRUE; } else if (pj_stricmp2(&rdata->msg_info.via->branch_param, TEST6_BRANCH_ID) == 0) { /* * The TEST6_BRANCH_ID test successfull non-INVITE transaction. */ pj_status_t status; recv_count++; if (recv_count > 1) { PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", recv_count)); test_complete = -635; } status = pjsip_endpt_respond_stateless(endpt, rdata, 202, NULL, NULL, NULL); if (status != PJ_SUCCESS) { app_perror(" error: unable to send response", status); test_complete = -636; } return PJ_TRUE; } else if (pj_stricmp2(&rdata->msg_info.via->branch_param, TEST7_BRANCH_ID) == 0) { /* * The TEST7_BRANCH_ID test successfull non-INVITE transaction * with provisional response. */ pj_status_t status; pjsip_response_addr res_addr; struct response *r; pjsip_tx_data *tdata; pj_time_val delay = { 2, 0 }; recv_count++; if (recv_count > 1) { PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", recv_count)); test_complete = -640; return PJ_TRUE; } /* Respond with provisional response */ status = pjsip_endpt_create_response(endpt, rdata, 100, NULL, &tdata); pj_assert(status == PJ_SUCCESS); status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr); pj_assert(status == PJ_SUCCESS); status = pjsip_endpt_send_response(endpt, &res_addr, tdata, NULL, NULL); pj_assert(status == PJ_SUCCESS); /* Create the final response. */ status = pjsip_endpt_create_response(endpt, rdata, 202, NULL, &tdata); pj_assert(status == PJ_SUCCESS); /* Schedule sending final response in couple of of secs. */ r = PJ_POOL_ALLOC_T(tdata->pool, struct response); r->res_addr = res_addr; r->tdata = tdata; if (r->res_addr.transport) pjsip_transport_add_ref(r->res_addr.transport); timer.entry.cb = &send_response_callback; timer.entry.user_data = r; pjsip_endpt_schedule_timer(endpt, &timer.entry, &delay); return PJ_TRUE; } else
/* * pj_ioqueue_unregister() * * Unregister handle from ioqueue. */ PJ_DEF(pj_status_t) pj_ioqueue_unregister( pj_ioqueue_key_t *key) { pj_ioqueue_t *ioqueue; struct epoll_event ev; int status; PJ_ASSERT_RETURN(key != NULL, PJ_EINVAL); ioqueue = key->ioqueue; /* Lock the key to make sure no callback is simultaneously modifying * the key. We need to lock the key before ioqueue here to prevent * deadlock. */ pj_ioqueue_lock_key(key); /* Also lock ioqueue */ pj_lock_acquire(ioqueue->lock); pj_assert(ioqueue->count > 0); --ioqueue->count; #if !PJ_IOQUEUE_HAS_SAFE_UNREG pj_list_erase(key); #endif ev.events = 0; ev.epoll_data = (epoll_data_type)key; status = os_epoll_ctl( ioqueue->epfd, EPOLL_CTL_DEL, key->fd, &ev); if (status != 0) { pj_status_t rc = pj_get_os_error(); pj_lock_release(ioqueue->lock); return rc; } /* Destroy the key. */ pj_sock_close(key->fd); pj_lock_release(ioqueue->lock); #if PJ_IOQUEUE_HAS_SAFE_UNREG /* Mark key is closing. */ key->closing = 1; /* Decrement counter. */ decrement_counter(key); /* Done. */ if (key->grp_lock) { /* just dec_ref and unlock. we will set grp_lock to NULL * elsewhere */ pj_grp_lock_t *grp_lock = key->grp_lock; // Don't set grp_lock to NULL otherwise the other thread // will crash. Just leave it as dangling pointer, but this // should be safe //key->grp_lock = NULL; pj_grp_lock_dec_ref_dbg(grp_lock, "ioqueue", 0); pj_grp_lock_release(grp_lock); } else { pj_ioqueue_unlock_key(key); } #else if (key->grp_lock) { /* set grp_lock to NULL and unlock */ pj_grp_lock_t *grp_lock = key->grp_lock; // Don't set grp_lock to NULL otherwise the other thread // will crash. Just leave it as dangling pointer, but this // should be safe //key->grp_lock = NULL; pj_grp_lock_dec_ref_dbg(grp_lock, "ioqueue", 0); pj_grp_lock_release(grp_lock); } else { pj_ioqueue_unlock_key(key); } pj_lock_destroy(key->lock); #endif return PJ_SUCCESS; }
/* Callback to be called to handle new incoming requests. */ static pj_bool_t proxy_on_rx_request( pjsip_rx_data *rdata ) { pjsip_transaction *uas_tsx, *uac_tsx; struct uac_data *uac_data; struct uas_data *uas_data; pjsip_tx_data *tdata; pj_status_t status; if (rdata->msg_info.msg->line.req.method.id != PJSIP_CANCEL_METHOD) { /* Verify incoming request */ status = proxy_verify_request(rdata); if (status != PJ_SUCCESS) { app_perror("RX invalid request", status); return PJ_TRUE; } /* * Request looks sane, next clone the request to create transmit data. */ status = pjsip_endpt_create_request_fwd(global.endpt, rdata, NULL, NULL, 0, &tdata); if (status != PJ_SUCCESS) { pjsip_endpt_respond_stateless(global.endpt, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL, NULL); return PJ_TRUE; } /* Process routing */ status = proxy_process_routing(tdata); if (status != PJ_SUCCESS) { app_perror("Error processing route", status); return PJ_TRUE; } /* Calculate target */ status = proxy_calculate_target(rdata, tdata); if (status != PJ_SUCCESS) { app_perror("Error calculating target", status); return PJ_TRUE; } /* Everything is set to forward the request. */ /* If this is an ACK request, forward statelessly. * This happens if the proxy records route and this ACK * is sent for 2xx response. An ACK that is sent for non-2xx * final response will be absorbed by transaction layer, and * it will not be received by on_rx_request() callback. */ if (tdata->msg->line.req.method.id == PJSIP_ACK_METHOD) { status = pjsip_endpt_send_request_stateless(global.endpt, tdata, NULL, NULL); if (status != PJ_SUCCESS) { app_perror("Error forwarding request", status); return PJ_TRUE; } return PJ_TRUE; } /* Create UAC transaction for forwarding the request. * Set our module as the transaction user to receive further * events from this transaction. */ status = pjsip_tsx_create_uac(&mod_tu, tdata, &uac_tsx); if (status != PJ_SUCCESS) { pjsip_tx_data_dec_ref(tdata); pjsip_endpt_respond_stateless(global.endpt, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL, NULL); return PJ_TRUE; } /* Create UAS transaction to handle incoming request */ status = pjsip_tsx_create_uas(&mod_tu, rdata, &uas_tsx); if (status != PJ_SUCCESS) { pjsip_tx_data_dec_ref(tdata); pjsip_endpt_respond_stateless(global.endpt, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL, NULL); pjsip_tsx_terminate(uac_tsx, PJSIP_SC_INTERNAL_SERVER_ERROR); return PJ_TRUE; } /* Feed the request to the UAS transaction to drive it's state * out of NULL state. */ pjsip_tsx_recv_msg(uas_tsx, rdata); /* Attach a data to the UAC transaction, to be used to find the * UAS transaction when we receive response in the UAC side. */ uac_data = (struct uac_data*) pj_pool_alloc(uac_tsx->pool, sizeof(struct uac_data)); uac_data->uas_tsx = uas_tsx; uac_tsx->mod_data[mod_tu.id] = (void*)uac_data; /* Attach data to the UAS transaction, to find the UAC transaction * when cancelling INVITE request. */ uas_data = (struct uas_data*) pj_pool_alloc(uas_tsx->pool, sizeof(struct uas_data)); uas_data->uac_tsx = uac_tsx; uas_tsx->mod_data[mod_tu.id] = (void*)uas_data; /* Everything is setup, forward the request */ status = pjsip_tsx_send_msg(uac_tsx, tdata); if (status != PJ_SUCCESS) { pjsip_tx_data *err_res; /* Fail to send request, for some reason */ /* Destroy transmit data */ pjsip_tx_data_dec_ref(tdata); /* I think UAC transaction should have been destroyed when * it fails to send request, so no need to destroy it. pjsip_tsx_terminate(uac_tsx, PJSIP_SC_INTERNAL_SERVER_ERROR); */ /* Send 500/Internal Server Error to UAS transaction */ pjsip_endpt_create_response(global.endpt, rdata, 500, NULL, &err_res); pjsip_tsx_send_msg(uas_tsx, err_res); return PJ_TRUE; } /* Send 100/Trying if this is an INVITE */ if (rdata->msg_info.msg->line.req.method.id == PJSIP_INVITE_METHOD) { pjsip_tx_data *res100; pjsip_endpt_create_response(global.endpt, rdata, 100, NULL, &res100); pjsip_tsx_send_msg(uas_tsx, res100); } } else { /* This is CANCEL request */ pjsip_transaction *invite_uas; struct uas_data *uas_data2; pj_str_t key; /* Find the UAS INVITE transaction */ pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_UAS_ROLE, pjsip_get_invite_method(), rdata); invite_uas = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE); if (!invite_uas) { /* Invite transaction not found, respond CANCEL with 481 */ pjsip_endpt_respond_stateless(global.endpt, rdata, 481, NULL, NULL, NULL); return PJ_TRUE; } /* Respond 200 OK to CANCEL */ pjsip_endpt_respond(global.endpt, NULL, rdata, 200, NULL, NULL, NULL, NULL); /* Send CANCEL to cancel the UAC transaction. * The UAS INVITE transaction will get final response when * we receive final response from the UAC INVITE transaction. */ uas_data2 = (struct uas_data*) invite_uas->mod_data[mod_tu.id]; if (uas_data2->uac_tsx && uas_data2->uac_tsx->status_code < 200) { pjsip_tx_data *cancel; pj_grp_lock_acquire(uas_data2->uac_tsx->grp_lock); pjsip_endpt_create_cancel(global.endpt, uas_data2->uac_tsx->last_tx, &cancel); pjsip_endpt_send_request(global.endpt, cancel, -1, NULL, NULL); pj_grp_lock_release(uas_data2->uac_tsx->grp_lock); } /* Unlock UAS tsx because it is locked in find_tsx() */ pj_grp_lock_release(invite_uas->grp_lock); } return PJ_TRUE; }
static void sas_log_rx_msg(pjsip_rx_data* rdata) { SAS::TrailId trail = 0; if (rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG) { // Message is a response, so try to correlate to an existing UAC // transaction using the top-most Via header. pj_str_t key; pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_ROLE_UAC, &rdata->msg_info.cseq->method, rdata); pjsip_transaction* tsx = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE); if (tsx) { // Found the UAC transaction, so get the trail if there is one. trail = get_trail(tsx); // Unlock tsx because it is locked in find_tsx() pj_grp_lock_release(tsx->grp_lock); } } else if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD) { // Message is an ACK, so try to correlate it to the existing UAS // transaction using the top-most Via header. pj_str_t key; pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_UAS_ROLE, &rdata->msg_info.cseq->method, rdata); pjsip_transaction* tsx = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE); if (tsx) { // Found the UAS transaction, so get the trail if there is one. trail = get_trail(tsx); // Unlock tsx because it is locked in find_tsx() pj_grp_lock_release(tsx->grp_lock); } } else if (rdata->msg_info.msg->line.req.method.id == PJSIP_CANCEL_METHOD) { // Message is a CANCEL request chasing an INVITE, so we want to try to // correlate it to the INVITE trail for the purposes of SAS tracing. pj_str_t key; pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_UAS_ROLE, pjsip_get_invite_method(), rdata); pjsip_transaction* tsx = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE); if (tsx) { // Found the INVITE UAS transaction, so get the trail if there is one. trail = get_trail(tsx); // Unlock tsx because it is locked in find_tsx() pj_grp_lock_release(tsx->grp_lock); } } if (trail == 0) { // The message doesn't correlate to an existing trail, so create a new // one. trail = SAS::new_trail(1u); } // Store the trail in the message as it gets passed up the stack. set_trail(rdata, trail); // Log the message event. SAS::Event event(trail, SASEvent::RX_SIP_MSG, 0); event.add_static_param(pjsip_transport_get_type_from_flag(rdata->tp_info.transport->flag)); event.add_static_param(rdata->pkt_info.src_port); event.add_var_param(rdata->pkt_info.src_name); event.add_var_param(rdata->msg_info.len, rdata->msg_info.msg_buf); SAS::report_event(event); }