Example #1
0
/// Delete the data for the specified namespace and key.  Writes the data
/// unconditionally, so CAS is not needed.
Store::Status BaseMemcachedStore::delete_data(const std::string& table,
                                              const std::string& key,
                                              SAS::TrailId trail)
{
  TRC_DEBUG("Deleting key %s from table %s", key.c_str(), table.c_str());

  // Construct the fully qualified key.
  std::string fqkey = table + "\\\\" + key;

  // Delete from the read replicas - read replicas are a superset of the write replicas
  const std::vector<memcached_st*>& replicas = get_replicas(fqkey, Op::READ);
  TRC_DEBUG("Deleting from the %d read replicas for key %s", replicas.size(), fqkey.c_str());

  if (_tombstone_lifetime == 0)
  {
    delete_without_tombstone(fqkey, replicas, trail);
  }
  else
  {
    delete_with_tombstone(fqkey, replicas, trail);
  }

  return Status::OK;
}
// Generate a WWW-Authenticate header. This has the format:
// WWW-Authenticate: Digest realm="<home domain>",
//                          qop="auth",
//                          nonce="<nonce>",
//                          opaque="<opaque>",
//                          [stale=TRUE]
void HTTPDigestAuthenticate::generate_www_auth_header(std::string& www_auth_header, bool include_stale, AuthStore::Digest* digest)
{
  www_auth_header = "Digest";
  www_auth_header.append(" realm=\"").append(_home_domain).append("\"");
  www_auth_header.append(",qop=\"").append("auth").append("\"");
  www_auth_header.append(",nonce=\"").append(digest->_nonce).append("\"");
  www_auth_header.append(",opaque=\"").append(digest->_opaque).append("\"");

  if (include_stale)
  {
    www_auth_header.append(",stale=TRUE");
  }

  TRC_DEBUG("WWW-Authenticate header generated: %s", www_auth_header.c_str());

  TRC_DEBUG("Raising correlating marker with opaque value = %s",
            digest->_opaque.c_str());
  SAS::Marker corr(_trail, MARKED_ID_GENERIC_CORRELATOR, 0);
  corr.add_var_param(digest->_opaque);

  // The marker should be trace-scoped, and should not reactivate any trail
  // groups
  SAS::report_marker(corr, SAS::Marker::Scope::Trace, false);
}
Example #3
0
ConnectionTracker::~ConnectionTracker()
{
  for (std::map<pjsip_transport *, pjsip_tp_state_listener_key *>::iterator 
                                             it = _connection_listeners.begin();
       it != _connection_listeners.end();
       ++it)
  {
    TRC_DEBUG("Stop listening on connection %p", it->first);
    pjsip_transport_remove_state_listener(it->first,
                                          it->second,
                                          (void *)this);
  }

  pthread_mutex_destroy(&_lock);
}
Example #4
0
SessionStore::Session* SessionStore::get_session_data(const std::string& call_id,
        const role_of_node_t role,
        const node_functionality_t function,
        SAS::TrailId trail)
{
    std::string key = create_key(call_id, role, function);
    TRC_DEBUG("Retrieving session data for %s", key.c_str());
    Session* session = NULL;

    std::string data;
    uint64_t cas;
    Store::Status status = _store->get_data("session", key, data, cas, trail);

    if (status == Store::Status::OK && !data.empty())
    {
        // Retrieved the data, so deserialize it.
        TRC_DEBUG("Retrieved record, CAS = %ld", cas);
        session = deserialize_session(data);

        if (session != NULL)
        {
            session->_cas = cas;
        }
        else
        {
            // Could not deserialize the record. Treat it as not found.
            TRC_INFO("Failed to deserialize record");
            SAS::Event event(trail, SASEvent::SESSION_DESERIALIZATION_FAILED, 0);
            event.add_var_param(call_id);
            event.add_var_param(data);
            SAS::report_event(event);
        }
    }

    return session;
}
void BaseResolver::untested(const AddrInfo& ai)
{
  std::string ai_str = ai.to_string();
  TRC_DEBUG("%s returned untested", ai_str.c_str());

  pthread_mutex_lock(&_hosts_lock);
  Hosts::iterator i = _hosts.find(ai);

  if (i != _hosts.end())
  {
    i->second.untested(pthread_self());
  }

  pthread_mutex_unlock(&_hosts_lock);
}
Example #6
0
std::vector<std::string> SubscriberManager::subscriptions_to_remove(const Bindings& orig_bindings,
                                                                    const Subscriptions& orig_subscriptions,
                                                                    const Bindings& bindings_to_update,
                                                                    const std::vector<std::string> binding_ids_to_remove)
{
  std::vector<std::string> subscription_ids_to_remove;
  std::set<std::string> missing_uris;

  // Store off the contact URIs of bindings to be removed. Any subscriptions
  // sharing any of these contact URIs will be removed.
  for (std::string binding_id : binding_ids_to_remove)
  {
    Bindings::const_iterator b = orig_bindings.find(binding_id);
    if (b != orig_bindings.end())
    {
      missing_uris.insert(b->second->_uri);
    }
  }

  // Store off the original contact URI of bindings where the contact is about
  // to be changed. Any subscriptions that share any of the original contact
  // URIs will be removed.
  for (BindingPair bp : bindings_to_update)
  {
    Bindings::const_iterator b = orig_bindings.find(bp.first);
    if ((b != orig_bindings.end()) &&
        (b->second->_uri != bp.second->_uri))
    {
      missing_uris.insert(b->second->_uri);
    }
  }

  // Loop over the subscriptions. If any have the same contact as one of the
  // missing URIs, the subscription should be removed.
  for (SubscriptionPair sp : orig_subscriptions)
  {
    if (missing_uris.find(sp.second->_req_uri) != missing_uris.end())
    {
      TRC_DEBUG("Subscription %s is being removed because the binding that shares"
                " its contact URI %s is being removed or changing contact URI",
                sp.first.c_str(),
                sp.second->_req_uri.c_str());
      subscription_ids_to_remove.push_back(sp.first);
    }
  }

  return subscription_ids_to_remove;
}
Example #7
0
HTTPCode SubscriberManager::deregister_with_hss(const std::string& aor_id,
                                                const std::string& dereg_reason,
                                                const std::string& server_name,
                                                HSSConnection::irs_info& irs_info,
                                                SAS::TrailId trail)
{
  TRC_DEBUG("All bindings removed for AoR %s - deregister with HSS",
            aor_id.c_str());

  HSSConnection::irs_query irs_query;
  irs_query._public_id = aor_id;
  irs_query._req_type = dereg_reason;
  irs_query._server_name = server_name;

  return get_subscriber_state(irs_query, irs_info, trail);
}
Example #8
0
void ConnectionTracker::unquiesce()
{
  TRC_DEBUG("Unquiesce connections");

  pthread_mutex_lock(&_lock);

  // It is not possible to "un-shutdown" a pjsip transport.  All connections
  // that were previously active will eventually be closed. Instead we just
  // clear the quiescing flag so we won't shut down any new connections that are
  // established.
  //
  // Note it is illegal to call this method if we're not quiescing.
  assert(_quiescing);
  _quiescing = PJ_FALSE;

  pthread_mutex_unlock(&_lock);
}
Example #9
0
void BaseMemcachedStore::delete_with_tombstone(const std::string& fqkey,
                                               const std::vector<memcached_st*>& replicas,
                                               SAS::TrailId trail)
{
  if (trail != 0)
  {
    SAS::Event event(trail, SASEvent::MEMCACHED_DELETE, 0);
    event.add_var_param(fqkey);
    event.add_static_param(_tombstone_lifetime);
    SAS::report_event(event);
  }

  const char* key_ptr = fqkey.data();
  const size_t key_len = fqkey.length();

  // Calculate a timestamp (least-significant 32 bits of milliseconds since the
  // epoch) for the current time.  We store this in the flags field to allow us
  // to resolve conflicts when resynchronizing between memcached servers.
  struct timespec ts;
  (void)clock_gettime(CLOCK_REALTIME, &ts);
  uint32_t flags = (uint32_t)((ts.tv_sec * 1000) + (ts.tv_nsec / 1000000));

  // Calculate the vbucket for this key.
  int vbucket = vbucket_for_key(fqkey);

  for (size_t ii = 0; ii < replicas.size(); ++ii)
  {
    TRC_DEBUG("Attempt write tombstone to replica %d (connection %p)",
              ii,
              replicas[ii]);

    memcached_return_t rc = memcached_set_vb(replicas[ii],
                                             key_ptr,
                                             key_len,
                                             _binary ? vbucket : 0,
                                             TOMBSTONE.data(),
                                             TOMBSTONE.length(),
                                             _tombstone_lifetime,
                                             flags);

    if (!memcached_success(rc))
    {
      log_delete_failure(fqkey, ii, replicas.size(), trail, 1);
    }
  }
}
Example #10
0
void DNSEnumService::parse_naptr_reply(const struct ares_naptr_reply* naptr_reply,
                                       std::vector<DNSEnumService::Rule>& rules)
{
  for (const struct ares_naptr_reply* record = naptr_reply; record != NULL; record = record->next)
  {
    TRC_DEBUG("Got NAPTR record: %u %u \"%s\" \"%s\" \"%s\" %s", record->order, record->preference, record->service, record->flags, record->regexp, record->replacement);
    if ((strcasecmp((char*)record->service, "e2u+sip") == 0) ||
        (strcasecmp((char*)record->service, "e2u+pstn:sip") == 0) || 
        (strcasecmp((char*)record->service, "e2u+pstn:tel") == 0))
    {
      boost::regex regex;
      std::string replace;
      bool terminal = false;

      if (!EnumService::parse_regex_replace(std::string((char*)record->regexp), regex, replace))
      {
        TRC_WARNING("DNS ENUM record contains unparseable regular expression: %s", record->regexp);
        // As above, we don't give up totally here.
        continue;
      }

      // The only valid flag is u.  If we see any other flags, we must ignore
      // the whole record (according to RFC 3761, 2.4.1).
      if (strcasecmp((char*)record->flags, "u") == 0)
      {
        terminal = true;
      }
      else if (strcmp((char*)record->flags, "") != 0)
      {
        TRC_WARNING("DNS ENUM record contains unknown flags: %s", record->flags);
        // Note that we don't give up totally here.  If we end up with an empty
        // list, we'll break out then.  Otherwise, we'll just try and push on.
        continue;
      }

      rules.push_back(Rule(regex,
                           replace,
                           terminal,
                           record->order,
                           record->preference));
    }
  }
  std::sort(rules.begin(), rules.end(), DNSEnumService::Rule::compare_order_preference);
}
Example #11
0
/// Performs an HSS LIR query.
int ICSCFLIRouter::hss_query()
{
  int status_code = PJSIP_SC_OK;

  // If we've already done one query we must force the HSS to return
  // capabilities this time.
  std::string auth_type = (_hss_rsp.scscf.empty()) ? "" : "CAPAB";

  TRC_DEBUG("Perform LIR - impu %s, originating %s, auth_type %s",
            _impu.c_str(),
            (_originating) ? "true" : "false",
            (auth_type != "") ? auth_type.c_str() : "None");
  rapidjson::Document* rsp = NULL;
  HTTPCode rc =_hss->get_location_data(_impu,
                                       _originating,
                                       auth_type,
                                       rsp,
                                       _trail);

  if (rc == HTTP_NOT_FOUND)
  {
    // HSS returned not found, so reject the request with a 404.
    status_code = PJSIP_SC_NOT_FOUND;
  }
  else if ((rc != HTTP_OK) ||
           (rsp == NULL))
  {
    // HSS failed to respond or responded with invalid data, so reject the
    // request with a 480.
    // LCOV_EXCL_START
    status_code = PJSIP_SC_TEMPORARILY_UNAVAILABLE;
    // LCOV_EXCL_STOP
  }
  else
  {
    // HSS returned a well-formed response, so parse it.
    status_code = parse_hss_response(rsp, auth_type == "CAPAB");
  }

  delete rsp;

  return status_code;
}
  void accumulate_internal(CurrentAndPrevious<ContinuousStatistics>& data, uint32_t sample)
  {
    struct timespec now;
    clock_gettime(CLOCK_REALTIME_COARSE, &now);

    ContinuousStatistics* current_data = data.get_current(now);

    TRC_DEBUG("Accumulating sample %uui into continuous accumulator statistic", sample);

    current_data->count++;

    // Compute the updated sum and sqsum based on the previous values, dependent on
    // how long since an update happened. Additionally update the sum of squares as a
    // rolling total, and update the time of the last update. Also maintain a
    // current value held, that can be used if the period ends.

    uint64_t time_since_last_update = ((now.tv_sec * 1000) + (now.tv_nsec / 1000000))
                                     - (current_data->time_last_update_ms.load());
    uint32_t current_value = current_data->current_value.load();

    current_data->time_last_update_ms = (now.tv_sec * 1000) + (now.tv_nsec / 1000000);
    current_data->sum += current_value * time_since_last_update;
    current_data->sqsum += current_value * current_value * time_since_last_update;
    current_data->current_value = sample;

    // Update the low- and high-water marks.  In each case, we get the current
    // value, decide whether a change is required and then atomically swap it
    // if so, repeating if it was changed in the meantime.  Note that
    // compare_exchange_weak loads the current value into the expected value
    // parameter (lwm or hwm below) if the compare fails.
    uint_fast64_t lwm = current_data->lwm.load();
    while ((sample < lwm) &&
           (!current_data->lwm.compare_exchange_weak(lwm, sample)))
    {
      // Do nothing.
    }
    uint_fast64_t hwm = current_data->hwm.load();
    while ((sample > hwm) &&
           (!current_data->hwm.compare_exchange_weak(hwm, sample)))
    {
      // Do nothing.
    }
  };
  /// Add rows to the table for a given string index (one row per time period
  /// that we are tracking.
  void create_and_add_rows(std::string string_index)
  {
    // This is going to mutate the maps so get the write lock first.
    pthread_rwlock_wrlock(&_table_lock);

    // Check that the rows don't already exist.  We only call this function if
    // the rows don't exist, but it's possible that they've been added
    // inbetween us checking that they don't exist and getting the write lock.
    if (_five_second.count(string_index) != 0)
    {
      // The rows already exist.  We don't release the RW lock until we've
      // completely finished adding all the rows so existence in the 5s map
      // means that all the rows are fully created.  Just return.
      TRC_DEBUG("Tried to add new rows but another thread beat us to it");
      pthread_rwlock_unlock(&_table_lock);
      return;
    }

    // Create CurrentAndPrevious views of EventStatisticAccumulator to
    // accumulate statistics for these rows at each of 5s and 5m scopes.
    _five_second[string_index] = new CurrentAndPrevious<EventStatisticAccumulator>(5000);
    _five_minute[string_index] = new CurrentAndPrevious<EventStatisticAccumulator>(300000);

    // Now add the actual rows to the table, referencing the
    // EventStatisticAccumulator views we've just created.  Note that we create
    // the rows with an abitrary internal key (using a _table_rows counter to
    // generate unique keys) as we don't need to be able to look them up again
    // in future.  We don't need this because we don't currently support
    // removing rows from the table.  If we need to add this in future then we
    // would need to start keying the rows off a tuple of string index and time
    // scope.
    this->add(_table_rows++, new TimeAndStringBasedEventRow(TimePeriodIndexes::scopePrevious5SecondPeriod,
                                    string_index,
                                    new TimeAndStringBasedEventRow::PreviousView((_five_second)[string_index])));
    this->add(_table_rows++, new TimeAndStringBasedEventRow(TimePeriodIndexes::scopeCurrent5MinutePeriod,
                                    string_index,
                                    new TimeAndStringBasedEventRow::CurrentView((_five_minute)[string_index])));
    this->add(_table_rows++, new TimeAndStringBasedEventRow(TimePeriodIndexes::scopePrevious5MinutePeriod,
                                    string_index,
                                    new TimeAndStringBasedEventRow::PreviousView((_five_minute)[string_index])));

    pthread_rwlock_unlock(&_table_lock);
  }
void BaseResolver::success(const AddrInfo& ai)
{
  if (Log::enabled(Log::DEBUG_LEVEL))
  {
    std::string ai_str = ai.to_string();
    TRC_DEBUG("Successful response from  %s", ai_str.c_str());
  }

  pthread_mutex_lock(&_hosts_lock);

  Hosts::iterator i = _hosts.find(ai);

  if (i != _hosts.end())
  {
    i->second.success();
  }

  pthread_mutex_unlock(&_hosts_lock);
}
Example #15
0
SIPResolver::SIPResolver(DnsCachedResolver* dns_client,
                         int blacklist_duration) :
  BaseResolver(dns_client)
{
  TRC_DEBUG("Creating SIP resolver");

  // Create the NAPTR cache.
  std::map<std::string, int> naptr_services;
  naptr_services["SIP+D2U"] = IPPROTO_UDP;
  naptr_services["SIP+D2T"] = IPPROTO_TCP;
  create_naptr_cache(naptr_services);

  // Create the SRV cache.
  create_srv_cache();

  // Create the blacklist.
  create_blacklist(blacklist_duration);

  TRC_STATUS("Created SIP resolver");
}
Example #16
0
// Pop a specific timer, if required pass the timer on to the replication layer to
// reset the timer for another pop, otherwise destroy the timer record.
void TimerHandler::pop(Timer* timer)
{
  // Tombstones are reaped when they pop.
  if (timer->is_tombstone())
  {
    TRC_DEBUG("Discarding expired tombstone");
    delete timer;
    timer = NULL;
    return;
  }

  // Increment the timer's sequence before sending the callback.
  timer->sequence_number++;

  // Update the timer in case it has out of date configuration
  timer->update_cluster_information();

  // The callback borrows of the timer at this point.
  _callback->perform(timer); timer = NULL;
}
static void sas_log_tx_msg(pjsip_tx_data *tdata)
{
  // For outgoing messages always use the trail identified in the module data
  SAS::TrailId trail = get_trail(tdata);

  if (trail == DONT_LOG_TO_SAS)
  {
    TRC_DEBUG("Skipping SAS logging for OPTIONS response");
    return;
  }
  else if (trail != 0)
  {
    // Raise SAS markers on initial requests only - responses in the same
    // transaction will have the same trail ID so don't need additional markers
    if (tdata->msg->type == PJSIP_REQUEST_MSG)
    {
      PJUtils::report_sas_to_from_markers(trail, tdata->msg);

      PJUtils::mark_sas_call_branch_ids(trail, NULL, tdata->msg);
    }

    // Log the message event.
    SAS::Event event(trail, SASEvent::TX_SIP_MSG, 0);
    event.add_static_param(pjsip_transport_get_type_from_flag(tdata->tp_info.transport->flag));
    event.add_static_param(tdata->tp_info.dst_port);
    event.add_var_param(tdata->tp_info.dst_name);
    event.add_compressed_param((int)(tdata->buf.cur - tdata->buf.start),
                               tdata->buf.start,
                               &SASEvent::PROFILE_SIP);
    SAS::report_event(event);
  }
  else
  {
    TRC_ERROR("Transmitting message with no SAS trail identifier\n%.*s",
              (int)(tdata->buf.cur - tdata->buf.start),
              tdata->buf.start);
  }
}
Example #18
0
Store::Status AuthStore::set_digest(const std::string& impi,
                                    const std::string& nonce,
                                    const AuthStore::Digest* digest,
                                    SAS::TrailId trail)
{
  std::string key = impi + '\\' + nonce;
  std::string data = serialize_digest(digest);

  TRC_DEBUG("Set digest for %s\n%s", key.c_str(), data.c_str());

  Store::Status status = _data_store->set_data("AuthStore",
                                               key,
                                               data,
                                               digest->_cas,
                                               _expiry,
                                               trail);

  if (status != Store::Status::OK)
  {
    // LCOV_EXCL_START - Store used in UTs doesn't fail
    TRC_ERROR("Failed to write digest for key %s", key.c_str());

    SAS::Event event(trail, SASEvent::AUTHSTORE_SET_FAILURE, 0);
    event.add_var_param(key);
    event.add_var_param(data);
    SAS::report_event(event);
    // LCOV_EXCL_STOP
  }
  else
  {
    SAS::Event event(trail, SASEvent::AUTHSTORE_SET_SUCCESS, 0);
    event.add_var_param(key);
    event.add_var_param(data);
    SAS::report_event(event);
  }

  return status;
}
Example #19
0
std::string JSONEnumService::lookup_uri_from_user(const std::string &user, SAS::TrailId trail) const
{
  std::string uri;

  TRC_DEBUG("Translating URI via JSON ENUM lookup");

  if (user.empty())
  {
    TRC_INFO("No dial string supplied, so don't do ENUM lookup");
    return std::string();
  }

  std::string aus = user_to_aus(user);
  struct NumberPrefix* pfix = prefix_match(aus);

  if (pfix == NULL)
  {
    TRC_INFO("No matching number range %s from ENUM lookup", user.c_str());
    return uri;
  }

  // Apply the regular expression to the user string to generate a new
  // URI.
  try
  {
    uri = boost::regex_replace(aus, pfix->match, pfix->replace);
  }
  catch(...) // LCOV_EXCL_START Only throws if expression too complex or similar hard-to-hit conditions
  {
    TRC_ERROR("Failed to translate number with regex");
    return uri;
    // LCOV_EXCL_STOP
  }

  TRC_INFO("Number %s found, translated URI = %s", user.c_str(), uri.c_str());

  return uri;
}
Example #20
0
void ICSCFSproutletTsx::add_p_profile_header(const std::string& wildcard,
                                             pjsip_msg* req)
{
  // The wildcard can contain characters that aren't valid in
  // URIs (e.g. square brackets) - escape these before attempting to
  // create a URI.
  pjsip_routing_hdr* ppk = identity_hdr_create(get_pool(req),
                                               STR_P_PROFILE_KEY);
  pjsip_uri* wildcard_uri = PJUtils::uri_from_string(
                                   PJUtils::escape_string_for_uri(wildcard),
                                   get_pool(req));

  if (wildcard_uri)
  {
    TRC_DEBUG("Adding a P-Profile-Key header for %s", wildcard.c_str());
    ppk->name_addr.uri = wildcard_uri;
    pjsip_msg_add_hdr(req, (pjsip_hdr*)ppk);
  }
  else
  {
    TRC_ERROR("Invalid wildcard returned on LIA: %s", wildcard.c_str());
  }
}
Example #21
0
void ConnectionPool::init()
{
  // Create an initial set of connections.
  for (int ii = 0; ii < _num_connections; ++ii)
  {
    create_connection(ii);
  }

  if (_recycle_period != 0)
  {
    // Spawn a thread to recycle connections
    pj_status_t status = pj_thread_create(_pool, "recycler",
                                          &recycle_thread,
                                          (void*)this, 0, 0, &_recycler);
    if (status != PJ_SUCCESS)
    {
      TRC_ERROR("Error creating recycler thread, %s",
                PJUtils::pj_status_to_string(status).c_str());
    }
  }

  TRC_DEBUG("Started %d connections to %.*s:%d", _num_connections, _target.host.slen, _target.host.ptr, _target.port);
}
Example #22
0
/// Add P-Charging headers on incoming out-of-dialog/dialog initiating requests
static void proxy_add_p_charging_header(pjsip_tx_data *tdata)
{
  TRC_DEBUG("Add P-Charging headers");

  std::string cdf_domain = PJUtils::pj_str_to_string(&stack_data.cdf_domain);

  if (cdf_domain != "")
  {
    // Add the P-Charging-Function-Addresses. The value of the CDF is passed in
    // as a parameter in bono - if this isn't present then don't set these
    // headers.
    pjsip_p_c_f_a_hdr* p_c_f_a = pjsip_p_c_f_a_hdr_create(tdata->pool);
    pjsip_param* new_param = (pjsip_param*) pj_pool_alloc(tdata->pool, sizeof(pjsip_param));
    new_param->name = STR_CCF;
    new_param->value = stack_data.cdf_domain;

    pj_list_insert_before(&p_c_f_a->ccf, new_param);
    pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)p_c_f_a);

    // Add the P-Charging-Vector Id. The icid-value is the Call-ID, and the
    // icid-generated-at is the bono hostname - it must be unique to the node that
    // generates it.
    pjsip_cid_hdr* call_id = (pjsip_cid_hdr*)pjsip_msg_find_hdr_by_name(tdata->msg,
                                                                        &STR_CALL_ID,
                                                                        NULL);
    std::string c_id = PJUtils::pj_str_to_string(&call_id->id);
    c_id.erase(std::remove(c_id.begin(), c_id.end(), '@'), c_id.end());
    c_id.erase(std::remove(c_id.begin(), c_id.end(), '"'), c_id.end());

    pjsip_p_c_v_hdr* p_c_v = pjsip_p_c_v_hdr_create(tdata->pool);

    pj_strdup2(tdata->pool, &p_c_v->icid, c_id.c_str());
    p_c_v->icid_gen_addr = stack_data.public_host;

    pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)p_c_v);
  }
}
bool BaseResolver::NAPTRCacheFactory::parse_regex_replace(const std::string& regex_replace,
                                                          boost::regex& regex,
                                                          std::string& replace)
{
  bool success = false;

  // Split the regular expression into the match and replace sections.  RFC3402
  // says any character other than 1-9 or i can be the delimiter, but
  // recommends / or !.  We just use the first character and reject if it
  // doesn't neatly split the regex into two.
  std::vector<std::string> match_replace;
  Utils::split_string(regex_replace, regex_replace[0], match_replace);

  if (match_replace.size() == 2)
  {
    TRC_DEBUG("Split regex into match=%s, replace=%s", match_replace[0].c_str(), match_replace[1].c_str());
    try
    {
      regex.assign(match_replace[0]);
      replace = match_replace[1];
      success = true;
    }
    // LCOV_EXCL_START
    catch (...)
    {
      success = false;
    }
    // LCOV_EXCL_STOP
  }
  else
  {
    success = false;
  }

  return success;
}
Example #24
0
//---------------------------------------------------------------------------------------
std::string Templater::generate()
//---------------------------------------------------------------------------------------
{
    TRC_DEBUG_FUNC_ENTER(0U, "");

    std::size_t tagOpenBracket  = mContent.find(Templater::MACRO_TAG);
    std::size_t tagCloseBracket = mContent.find(Templater::MACRO_TAG, tagOpenBracket+1);

    while(   std::string::npos != tagOpenBracket
             && std::string::npos != tagCloseBracket)
    {
        std::string token = mContent.substr(   tagOpenBracket + Templater::MACRO_TAG.length(),
                                               tagCloseBracket - tagOpenBracket - Templater::MACRO_TAG.length() );

        std::size_t lastModifiedSymbol = tagCloseBracket + 1;

        auto iter = mMacroses.find(token);
        if (iter != mMacroses.end())
        {
            TRC_DEBUG(0U, "macro recognized: '%s' -> '%s'", token.c_str(), iter->second.c_str());

            mContent.replace( mContent.begin() + tagOpenBracket,
                              mContent.begin() + tagCloseBracket + Templater::MACRO_TAG.length(),
                              iter->second);

            lastModifiedSymbol = tagOpenBracket + iter->second.length();
        }

        tagOpenBracket  = mContent.find(Templater::MACRO_TAG, lastModifiedSymbol + 1);
        tagCloseBracket = mContent.find(Templater::MACRO_TAG, tagOpenBracket + 1);
    }

    TRC_DEBUG_FUNC_EXIT(0U);

    return mContent;
}
Example #25
0
JSONEnumService::JSONEnumService(std::string configuration)
{
  // Check whether the file exists.
  struct stat s;
  if ((stat(configuration.c_str(), &s) != 0) &&
      (errno == ENOENT))
  {
    TRC_STATUS("No ENUM configuration (file %s does not exist)",
               configuration.c_str());
    CL_SPROUT_ENUM_FILE_MISSING.log(configuration.c_str());
    return;
  }

  TRC_STATUS("Loading ENUM configuration from %s", configuration.c_str());

  // Read from the file
  std::ifstream fs(configuration.c_str());
  std::string enum_str((std::istreambuf_iterator<char>(fs)),
                        std::istreambuf_iterator<char>());

  if (enum_str == "")
  {
    // LCOV_EXCL_START
    TRC_ERROR("Failed to read ENUM configuration data from %s",
              configuration.c_str());
    CL_SPROUT_ENUM_FILE_EMPTY.log(configuration.c_str());
    return;
    // LCOV_EXCL_STOP
  }

  // Now parse the document
  rapidjson::Document doc;
  doc.Parse<0>(enum_str.c_str());

  if (doc.HasParseError())
  {
    TRC_ERROR("Failed to read ENUM configuration data: %s\nError: %s",
              enum_str.c_str(),
              rapidjson::GetParseError_En(doc.GetParseError()));
    CL_SPROUT_ENUM_FILE_INVALID.log(configuration.c_str());
    return;
  }

  try
  {
    JSON_ASSERT_CONTAINS(doc, "number_blocks");
    JSON_ASSERT_ARRAY(doc["number_blocks"]);
    const rapidjson::Value& nb_arr = doc["number_blocks"];

    for (rapidjson::Value::ConstValueIterator nb_it = nb_arr.Begin();
         nb_it != nb_arr.End();
         ++nb_it)
    {
      try
      {
        std::string prefix; 
        JSON_GET_STRING_MEMBER(*nb_it, "prefix", prefix);
        std::string regex;
        JSON_GET_STRING_MEMBER(*nb_it, "regex", regex);

        // Entry is well-formed, so add it.
        TRC_DEBUG("Found valid number prefix block %s", prefix.c_str());
        NumberPrefix *pfix = new NumberPrefix;
        pfix->prefix = prefix;

        if (parse_regex_replace(regex, pfix->match, pfix->replace))
        {
          _number_prefixes.push_back(pfix);
          TRC_STATUS("  Adding number prefix %s, regex=%s",
                     pfix->prefix.c_str(), regex.c_str());
        }
        else
        {
          TRC_WARNING("Badly formed regular expression in ENUM number block %s",
                      regex.c_str());
          delete pfix;
        }
      }
      catch (JsonFormatError err)
      {
        // Badly formed number block.
        TRC_WARNING("Badly formed ENUM number block (hit error at %s:%d)",
                    err._file, err._line);
        CL_SPROUT_ENUM_FILE_INVALID.log(configuration.c_str());
      }
    }
  }
  catch (JsonFormatError err)
  {
    TRC_ERROR("Badly formed ENUM configuration data - missing number_blocks object");
    CL_SPROUT_ENUM_FILE_INVALID.log(configuration.c_str());
  }
}
Example #26
0
std::string DNSEnumService::lookup_uri_from_user(const std::string& user, SAS::TrailId trail) const
{
  if (user.empty())
  {
    TRC_INFO("No dial string supplied, so don't do ENUM lookup");
    return std::string();
  }

  // Log starting ENUM processing.
  SAS::Event event(trail, SASEvent::ENUM_START, 0);
  event.add_var_param(user);
  SAS::report_event(event);

  // Determine the Application Unique String (AUS) from the user.  This is
  // used to form the first key, and also as the input into the regular
  // expressions.
  std::string aus = user_to_aus(user);
  std::string string = aus;
  // Get the resolver to use.  This comes from thread-local data.
  DNSResolver* resolver = get_resolver();
  // Spin round until we've finished (successfully or otherwise) or we've done
  // the maximum number of queries.
  bool complete = false;
  bool failed = false;
  bool server_failed = false;
  int dns_queries = 0;
  while ((!complete) &&
         (!failed) &&
         (dns_queries < MAX_DNS_QUERIES))
  {
    // Translate the key into a domain and issue a query for it.
    std::string domain = key_to_domain(string);
    struct ares_naptr_reply* naptr_reply = NULL;
    int status = resolver->perform_naptr_query(domain, naptr_reply, trail);
    if (status == ARES_SUCCESS)
    {
      // Parse the reply into a sorted list of rules.
      std::vector<Rule> rules;
      parse_naptr_reply(naptr_reply, rules);
      // Now spin through the rules, looking for the first match.
      std::vector<DNSEnumService::Rule>::const_iterator rule;
      for (rule = rules.begin();
           rule != rules.end();
           ++rule)
      {
        if (rule->matches(string))
        {
          // We found a match, so apply the regular expression to the AUS (not
          // the previous string - this is what ENUM mandates).  If this was a
          // terminal rule, we now have a SIP URI and we're finished.
          // Otherwise, the output of the regular expression is used as the
          // next key.
          try
          {
            string = rule->replace(aus, trail);
            complete = rule->is_terminal();
          }
          catch(...) // LCOV_EXCL_START Only throws if expression too complex or similar hard-to-hit conditions
          {
            TRC_ERROR("Failed to translate number with regex");
            failed = true;
            // LCOV_EXCL_STOP
          }
          break;
        }
      }
      // If we didn't find a match (and so hit the end of the list), consider
      // this a failure.
      failed = failed || (rule == rules.end());
    }
    else if (status == ARES_ENOTFOUND)
    {
      // Our DNS query failed, so give up, but this is not an ENUM server issue -
      // we just tried to look up an unknown name.
      failed = true;
    }
    else
    {
      // Our DNS query failed. Give up, and track an ENUM server failure.
      failed = true;
      server_failed = true;
    }


    // Free off the NAPTR reply if we have one.
    if (naptr_reply != NULL)
    {
      resolver->free_naptr_reply(naptr_reply);
      naptr_reply = NULL;
    }

    dns_queries++;
  }

  // Log that we've finished processing (and whether it was successful or not).
  if (complete)
  {
    TRC_DEBUG("Enum lookup completes: %s", string.c_str());
    SAS::Event event(trail, SASEvent::ENUM_COMPLETE, 0);
    event.add_var_param(user);
    event.add_var_param(string);
    SAS::report_event(event);
  }
  else
  {
    TRC_WARNING("Enum lookup did not complete for user %s", user.c_str());
    SAS::Event event(trail, SASEvent::ENUM_INCOMPLETE, 0);
    event.add_var_param(user);
    SAS::report_event(event);
    // On failure, we must return an empty (rather than incomplete) string.
    string = std::string("");
  }

  // Report state of last communication attempt (which may potentially set/clear
  // an associated alarm). 
  if (_comm_monitor)
  {
    if (server_failed)
    {
      _comm_monitor->inform_failure();
    }
    else
    {
      _comm_monitor->inform_success();
    }
  }

  return string;
}
Example #27
0
// Determine the type of a URI.
//
// Parameters:
//
// - uri - the URI to classify
// - prefer_sip - for ambiguous URIs like sip:[email protected] (which could be a global phone
// number or just a SIP URI), prefer to interpret it as SIP
// - check_np - check for the presence of Number Portability parameters and
// classify accordingly
//
URIClass URIClassifier::classify_uri(const pjsip_uri* uri, bool prefer_sip, bool check_np)
{
  URIClass ret = URIClass::UNKNOWN;

  // First, check to see if this URI has number portability data - this takes priority
  bool has_rn = false;
  bool has_npdi = false;

  if (check_np)
  {
    if (PJSIP_URI_SCHEME_IS_TEL(uri))
    {
      // If the URI is a tel URI, pull out the information from the other_params
      has_rn = (pjsip_param_find(&((pjsip_tel_uri*)uri)->other_param, &STR_RN) != NULL);
      has_npdi = (pjsip_param_find(&((pjsip_tel_uri*)uri)->other_param, &STR_NPDI) != NULL);
    }
    else if (PJSIP_URI_SCHEME_IS_SIP(uri))
    {
      // If the URI is a tel URI, pull out the information from the userinfo_params
      has_rn = (pjsip_param_find(&((pjsip_sip_uri*)uri)->userinfo_param, &STR_RN) != NULL);
      has_npdi = (pjsip_param_find(&((pjsip_sip_uri*)uri)->userinfo_param, &STR_NPDI) != NULL);
    }
  }

  if (has_rn)
  {
    if (has_npdi)
    {
      ret = FINAL_NP_DATA;
    }
    else
    {
      ret = NP_DATA;
    }
  }
  // No number portability data
  else if (PJSIP_URI_SCHEME_IS_TEL(uri))
  {
    // TEL URIs can only represent phone numbers - decide if it's a global (E.164) number or not
    pjsip_tel_uri* tel_uri = (pjsip_tel_uri*)uri;
    std::string user = PJUtils::pj_str_to_string(&tel_uri->number);
    boost::match_results<std::string::const_iterator> results;
    if (boost::regex_match(user, results, CHARS_ALLOWED_IN_GLOBAL_NUM))
    {
      ret = GLOBAL_PHONE_NUMBER;
    }
    else
    {
      ret = enforce_global ? LOCAL_PHONE_NUMBER : GLOBAL_PHONE_NUMBER;
    }
  }
  else if (PJSIP_URI_SCHEME_IS_SIP(uri))
  {
    pjsip_sip_uri* sip_uri = (pjsip_sip_uri*)uri;
    pj_str_t host = sip_uri->host;
    bool home_domain = is_home_domain(host);
    bool local_to_node = is_local_name(host);
    bool is_gruu = (pjsip_param_find(&((pjsip_sip_uri*)uri)->other_param, &STR_GR) != NULL);
    bool treat_number_as_phone = !enforce_user_phone && !prefer_sip;

    TRC_DEBUG("home domain: %s, local_to_node: %s, is_gruu: %s, enforce_user_phone: %s, prefer_sip: %s, treat_number_as_phone: %s",
              home_domain ? "true" : "false",
              local_to_node ? "true" : "false",
              is_gruu ? "true" : "false",
              enforce_user_phone ? "true" : "false",
              prefer_sip ? "true" : "false",
              treat_number_as_phone ? "true" : "false");

    bool classified = false;
    // SIP URI that's 'really' a phone number - apply the same logic as for TEL URIs
    if ((!pj_strcmp(&((pjsip_sip_uri*)uri)->user_param, &STR_USER_PHONE) ||
         (home_domain && treat_number_as_phone && !is_gruu)))
    {
      // Get the user part minus any parameters.
      std::string user = PJUtils::pj_str_to_string(&sip_uri->user);
      if (!user.empty())
      {
        std::vector<std::string> user_tokens;
        Utils::split_string(user, ';', user_tokens, 0, true);
        boost::match_results<std::string::const_iterator> results;
        if (boost::regex_match(user_tokens[0], results, CHARS_ALLOWED_IN_GLOBAL_NUM))
        {
          ret = GLOBAL_PHONE_NUMBER;
          classified = true;
        }
        else if (boost::regex_match(user_tokens[0], results, CHARS_ALLOWED_IN_LOCAL_NUM))
        {
          ret = enforce_global ? LOCAL_PHONE_NUMBER : GLOBAL_PHONE_NUMBER;
          classified = true;
        }
      }
    }
    if (!classified)
    {
      // Not a phone number - classify it based on domain
      ret = (home_domain) ? HOME_DOMAIN_SIP_URI : ((local_to_node) ? NODE_LOCAL_SIP_URI : OFFNET_SIP_URI);
    }
  }

  std::string uri_str = PJUtils::uri_to_string(PJSIP_URI_IN_OTHER, uri);
  TRC_DEBUG("Classified URI %s as %d", uri_str.c_str(), (int)ret);
  return ret;
}
// Check if the response from the client matches the stored digest
// The logic is:
//   HA1 is the digest returned from Homestead.
//   HA2 = MD5(method : uri), e.g. MD5(GET:/org.projectclearwater.call-list/users/<IMPU>/call-list.xml)
//   response = MD5(HA1 : nonce : nonce_count (provided by client) : cnonce : qop : HA2)
HTTPCode HTTPDigestAuthenticate::check_if_matches(AuthStore::Digest* digest,
                                                  std::string& www_auth_header,
                                                  Response* response)
{
  HTTPCode rc = HTTP_OK;

  if (digest->_opaque != response->_opaque)
  {
    TRC_DEBUG("The opaque value in the request (%s) doesn't match the stored value (%s)",
              response->_opaque.c_str(), digest->_opaque.c_str());
    return HTTP_BAD_REQUEST;
  }
  else if (response->_realm != digest->_realm)
  {
    TRC_DEBUG("Request not targeted at the stored domain. Target: %s, Realm: %s",
              response->_realm.c_str(), digest->_realm.c_str());
    return HTTP_BAD_REQUEST;
  }

  unsigned char ha2[Utils::MD5_HASH_SIZE];
  unsigned char ha2_hex[Utils::HEX_HASH_SIZE + 1];

  MD5_CTX Md5Ctx;
  MD5_Init(&Md5Ctx);
  MD5_Update(&Md5Ctx, _method.c_str(), strlen(_method.c_str()));
  MD5_Update(&Md5Ctx, ":", 1);
  MD5_Update(&Md5Ctx, response->_uri.c_str(), strlen(response->_uri.c_str()));
  MD5_Final(ha2, &Md5Ctx);
  Utils::hashToHex(ha2, ha2_hex);

  unsigned char resp[Utils::MD5_HASH_SIZE];
  unsigned char resp_hex[Utils::HEX_HASH_SIZE + 1];

  MD5_Init(&Md5Ctx);
  MD5_Update(&Md5Ctx, digest->_ha1.c_str(), strlen(digest->_ha1.c_str()));
  MD5_Update(&Md5Ctx, ":", 1);
  MD5_Update(&Md5Ctx, response->_nonce.c_str(), strlen(response->_nonce.c_str()));
  MD5_Update(&Md5Ctx, ":", 1);
  MD5_Update(&Md5Ctx, response->_nc.c_str(), strlen(response->_nc.c_str()));
  MD5_Update(&Md5Ctx, ":", 1);
  MD5_Update(&Md5Ctx, response->_cnonce.c_str(), strlen(response->_cnonce.c_str()));
  MD5_Update(&Md5Ctx, ":", 1);
  MD5_Update(&Md5Ctx, response->_qop.c_str(), strlen(response->_qop.c_str()));
  MD5_Update(&Md5Ctx, ":", 1);
  MD5_Update(&Md5Ctx, ha2_hex, Utils::HEX_HASH_SIZE);
  MD5_Final(resp, &Md5Ctx);
  Utils::hashToHex(resp, resp_hex);

  std::string stored_response((const char*) resp_hex);

  if (response->_response == stored_response)
  {
    TRC_DEBUG("Client response matches stored digest");

    // If the nonce count supplied isn't an int then this will return 0,
    // and the nonce will be treated as stale.
    uint32_t client_count = atoi(response->_nc.c_str());

    // Nonce count is stale. Request the digest from Homestead again.
    if (client_count < digest->_nonce_count)
    {
      TRC_DEBUG("Client response's nonce count is too low");
      SAS::Event event(_trail, SASEvent::AUTHENTICATION_OUT_OF_DATE, 0);
      SAS::report_event(event);

      rc = request_digest_and_store(www_auth_header, true, response);
    }
    else if (_impu != digest->_impu)
    {
      TRC_DEBUG("Request's IMPU doesn't match stored IMPU. Target: %s, Stored: %s",
               _impu.c_str(), digest->_impu.c_str());
      SAS::Event event(_trail, SASEvent::AUTHENTICATION_WRONG_IMPU, 0);
      event.add_var_param(_impu);
      event.add_var_param(digest->_impu);
      SAS::report_event(event);

      rc = request_digest_and_store(www_auth_header, true, response);
    }
    else
    {
      // Authentication successful. Increment the stored nonce count
      digest->_nonce_count++;
      Store::Status store_rc = _auth_store->set_digest(_impi,
                                                       digest->_nonce,
                                                       digest,
                                                       _trail);
      TRC_DEBUG("Updating nonce count - store returned %d", store_rc);

      if (store_rc == Store::DATA_CONTENTION)
      {
        // The write to the store failed due to a CAS mismatch.  This means that
        // the digest has already been used to authenticate another request, so
        // the authentication on this request is stale. Rechallenge.
        TRC_DEBUG("Failed to update nonce count - rechallenge");
        SAS::Event event(_trail, SASEvent::AUTHENTICATION_OUT_OF_DATE, 1);
        SAS::report_event(event);
        rc = request_digest_and_store(www_auth_header, true, response);
      }
      else
      {
        // The nonce count was either updated successfully (in which case we
        // accept the request) or the store failed (in which case we're not sure
        // what to do for the best, and accepting the request is sensible
        // default behaviour).
        TRC_DEBUG("Authentication accepted");
        SAS::Event event(_trail, SASEvent::AUTHENTICATION_ACCEPTED, 0);
        SAS::report_event(event);
        _stat_auth_success_count->increment();
      }
    }
  }
  else
  {
    // Digest doesn't match - reject the request
    TRC_DEBUG("Client response doesn't match stored digest");
    SAS::Event event(_trail, SASEvent::AUTHENTICATION_REJECTED, 0);
    SAS::report_event(event);

    _stat_auth_failure_count->increment();

    rc = HTTP_FORBIDDEN;
  }

  return rc;
}
HTTPCode HTTPDigestAuthenticate::parse_auth_header(std::string auth_header,
                                                   bool& auth_info,
                                                   Response* response)
{
  HTTPCode rc = HTTP_OK;

  std::string digest = "Digest";
  size_t digest_pos = auth_header.find(digest);

  if (digest_pos != 0)
  {
    TRC_DEBUG("Authorization header doesn't contain Digest credentials");
    return HTTP_BAD_REQUEST;
  }

  std::string rest_of_header = auth_header.substr(digest.length());

  // Split the request on the delimiter ','
  std::vector<std::string> response_params;
  Utils::split_string(rest_of_header, ',', response_params, 0, true, true);

  // Then split by = and sort through array.
  std::map<std::string, std::string> response_key_values;
  std::vector<std::string> temp_value;

  for (std::vector<std::string>::iterator ii = response_params.begin(); ii != response_params.end(); ++ii)
  {
    std::string parameter(ii->c_str());
    Utils::split_string(parameter, '=', temp_value, 2, true);

    // Strip quotes off befores storing
    int val_len = temp_value[1].length();
    if (val_len > 1 && temp_value[1][0] == '"' && temp_value[1][val_len - 1] == '"')
    {
      temp_value[1] = temp_value[1].substr(1, val_len - 2);
    }

    response_key_values.insert(std::pair<std::string, std::string>(temp_value[0], temp_value[1]));
    temp_value.clear();
  }

  std::map<std::string, std::string>::iterator username_entry =
                                  response_key_values.find("username");
  std::map<std::string, std::string>::iterator realm_entry =
                                  response_key_values.find("realm");
  std::map<std::string, std::string>::iterator nonce_entry =
                                  response_key_values.find("nonce");
  std::map<std::string, std::string>::iterator uri_entry =
                                  response_key_values.find("uri");
  std::map<std::string, std::string>::iterator qop_entry =
                                  response_key_values.find("qop");
  std::map<std::string, std::string>::iterator nc_entry =
                                  response_key_values.find("nc");
  std::map<std::string, std::string>::iterator cnonce_entry =
                                  response_key_values.find("cnonce");
  std::map<std::string, std::string>::iterator response_entry =
                                  response_key_values.find("response");
  std::map<std::string, std::string>::iterator opaque_entry =
                                  response_key_values.find("opaque");

  // A valid Authorization header must contain a username.
  // It must then either contain all of a realm, nonce, uri, qop, nc,
  // cnonce, response and opaque values, or none of the above.
  // It can contain other parameters; these aren't validated.
  if ((username_entry != response_key_values.end()) &&
      (realm_entry != response_key_values.end()) &&
      (nonce_entry != response_key_values.end()) &&
      (uri_entry != response_key_values.end()) &&
      (qop_entry != response_key_values.end()) &&
      (nc_entry != response_key_values.end()) &&
      (cnonce_entry != response_key_values.end()) &&
      (response_entry != response_key_values.end()) &&
      (opaque_entry != response_key_values.end()))
  {
      TRC_DEBUG("Authorization header valid and complete");
      _impi = username_entry->second;
      auth_info = true;
      response->set_members(username_entry->second,
                            realm_entry->second,
                            nonce_entry->second,
                            uri_entry->second,
                            qop_entry->second,
                            nc_entry->second,
                            cnonce_entry->second,
                            response_entry->second,
                            opaque_entry->second);

    TRC_DEBUG("Raising correlating marker with opaque value = %s",
              opaque_entry->second.c_str());
    SAS::Marker corr(_trail, MARKED_ID_GENERIC_CORRELATOR, 0);
    corr.add_var_param(opaque_entry->second);

    // The marker should be trace-scoped, and should not reactivate any trail
    // groups 
    SAS::report_marker(corr, SAS::Marker::Scope::Trace, false);      
  }
  else if ((username_entry != response_key_values.end()) &&
           (realm_entry == response_key_values.end()) &&
           (nonce_entry == response_key_values.end()) &&
           (uri_entry == response_key_values.end()) &&
           (qop_entry == response_key_values.end()) &&
           (nc_entry == response_key_values.end()) &&
           (cnonce_entry == response_key_values.end()) &&
           (response_entry == response_key_values.end()) &&
           (opaque_entry == response_key_values.end()))
  {
    TRC_DEBUG("Authorization header valid and minimal");
    _impi = username_entry->second;
    auth_info = false;
  }
  else
  {
    TRC_DEBUG("Authorization header not valid");
    rc = HTTP_BAD_REQUEST;
  }

  return rc;
}
Example #30
0
void ConnectionPool::transport_state_update(pjsip_transport* tp, pjsip_transport_state state)
{
  // Transport state has changed.
  pthread_mutex_lock(&_tp_hash_lock);

  std::map<pjsip_transport*, int>::const_iterator i = _tp_map.find(tp);

  if (i != _tp_map.end())
  {
    int hash_slot = i->second;

    if ((state == PJSIP_TP_STATE_CONNECTED) && (!_tp_hash[hash_slot].connected))
    {
      // New connection has connected successfully, so update the statistics.
      TRC_DEBUG("Transport %s in slot %d has connected", tp->obj_name, hash_slot);
      _tp_hash[hash_slot].connected = PJ_TRUE;
      ++_active_connections;
      increment_connection_count(tp);

      if (_recycle_period > 0)
      {
        // Compute a TTL for the connection.  To avoid all the recycling being
        // synchronized we set the TTL to the specified average recycle time
        // perturbed by a random factor.
        int ttl = _recycle_period + (rand() % (2 * _recycle_margin)) - _recycle_margin;
        _tp_hash[hash_slot].recycle_time = time(NULL) + ttl;
      }
      else
      {
        // Connection recycling is disabled.
        _tp_hash[hash_slot].recycle_time = 0;
      }
    }
    else if ((state == PJSIP_TP_STATE_DISCONNECTED) ||
             (state == PJSIP_TP_STATE_DESTROYED))
    {
      // Either a connection has failed or been shutdown, or a new connection
      // failed to connect.
      TRC_DEBUG("Transport %s in slot %d has failed", tp->obj_name, hash_slot);

      if (_tp_hash[hash_slot].connected)
      {
        // A connection has failed, so update the statistics.
        --_active_connections;
        decrement_connection_count(tp);
      }
      else
      {
        // Failed to establish a connection to this server, so blacklist
        // it so we steer clear of it for a while.  We don't blacklist
        // if an existing connection fails as this may be a transient error
        // or even a disconnect triggered by an inactivity timeout .
        AddrInfo server;
        server.transport = IPPROTO_TCP;
        server.port = pj_sockaddr_get_port(&tp->key.rem_addr);
        server.address.af = tp->key.rem_addr.addr.sa_family;
        if (server.address.af == AF_INET)
        {
          server.address.addr.ipv4.s_addr = tp->key.rem_addr.ipv4.sin_addr.s_addr;
        }
        else
        {
          memcpy((char*)&server.address.addr.ipv6,
                 (char*)&tp->key.rem_addr.ipv6.sin6_addr,
                 sizeof(struct in6_addr));
        }
        PJUtils::blacklist_server(server);
      }

      // Don't listen for any more state changes on this connection (but note
      // it's illegal to call any methods on the transport once it's entered the
      // `destroyed` state).
      if (state != PJSIP_TP_STATE_DESTROYED)
      {
        pjsip_transport_remove_state_listener(tp,
                                              _tp_hash[hash_slot].listener_key,
                                              (void *)this);
      }

      // Remove the transport from the hash and the map.
      _tp_hash[hash_slot].tp = NULL;
      _tp_hash[hash_slot].listener_key = NULL;
      _tp_hash[hash_slot].connected = PJ_FALSE;
      _tp_map.erase(tp);

      // Remove our reference to the transport.
      pjsip_transport_dec_ref(tp);
    }
  }

  pthread_mutex_unlock(&_tp_hash_lock);
}