Thursday, September 28, 2017

OpenSIPS proxy with auth on external trunk

A new task has arrived.
Make OpenSIPS (actually, here can be Kamailio as well) handle registration to external service, but also provide capability to dial out from other trunks, that are not aware of any credentials of other trunk and know about only OpenSIPS as a trunk. So, in some sort it's b2b.

1. A -> INVITE -> OpenSIPS                     B
2. A              OpenSIPS ->    INVITE     -> B
3. A              OpenSIPS <-    401(7)     <- B
4. A              OpenSIPS -> INVITE (auth) -> B
5. A              OpenSIPS <-      200      <- B

6. A  <- 200 <-   OpenSIPS

Very simplified scheme here. But the main issue here - on INVITE with auth headers you have to increase CSeq number by 1. Actually creating new dialog with side B. But you still have to hold old dialog with side A. And you have dynamically change on requests CSeq number. 
On question, "is there any OpenSIPS-wise way of making that", got an answer 

"Right now OpenSIPs does not support increasing the cseq during UAC authentication. At the end this is a limitation of the a proxy versus a B2B :)"

So, nothing is left, only to change it by hand.

So, parts of the script would looks like

...
#------------ uac related parts
loadmodule "uac_auth.so"
modparam("uac_auth","auth_realm_avp","$avp(uac_realm)")
modparam("uac_auth","auth_username_avp","$avp(uac_username)")
modparam("uac_auth","auth_password_avp","$avp(uac_password)")

loadmodule "uac.so"

loadmodule "uac_registrant.so"
modparam("uac_registrant", "db_url", "mysql://astercc:astercc@localhost/opensips")
modparam("uac_registrant", "timer_interval", 120)
modparam("uac_registrant", "hash_size", 2)
# In DB we're storing info on external services where to register
modparam("uac_registrant", "db_url", DBURL)
...

route {
...
# Handle sequential requests part
if (has_totag()) {
  if (loose_route()) {
    ...
  } else {
    ...
    if (is_method("ACK") && isflagset(AUTH_DONE)) {
      # Process ACK's
      if ($cs == $avp(original_cseq)) {
        route(INCREASE_CSEQ);
      }
    }
    ...
  }
}
...
# CANCEL processing
if (is_method("CANCEL")) {
  # Addition of process CSeq in a case of auth call
  if (isflagset(AUTH_DONE)) {
    # Process CANCEL's to both sides
    if ($cs == $avp(original_cseq)) {
        route(INCREASE_CSEQ);
    } else {
        route(RESTORE_CSEQ);
    }
  }
  ...
}
...
# INVITE processing
if (is_method("INVITE")) {
  ...
  # Here to find out call to external auth trunk
  if (...) {
    $avp(original_cseq) = $cs;
    setflag(IS_OUTBOUND_CALL);
    # Also point, here, if we already know that trunk with username and pass, provider may require specific Form and To fields
    #if (...) {
    #  uac_replace_to("sip:$tU@$rd");
    #  uac_replace_from("sip:$avp(uac_username)@$rd");
    #}
  }
  ...
}

onreply_route[1] {
...
  # On reply just restore original CSeq.
  route(RESTORE_CSEQ);
...
}

failure_route[1] {
  ...
  # Authentication reply on outbound call received?
  if (t_check_status("40[17]") && isflagset(IS_OUTBOUND_CALL)) {
    # Have we already tried to authenticate?
    if (isflagset(AUTH_DONE)) {
      t_reply("503","Authentication failed");
      exit;
    }
    # Set flag of auth preformed
    setflag(AUTH_DONE);

    # Getting realm from responce
    # Get Proxy-Auth header
    if ($(<reply>hdr(Proxy-Authenticate))) {
      $var(raw_auth) = $(<reply>hdr(Proxy-Authenticate));
    } 
    # Prefer WWW-Authenticate to Proxy-Authenticate
    if ($(<reply>hdr(WWW-Authenticate))) {
      $var(raw_auth) = $(<reply>hdr(WWW-Authenticate));
    }
    $var(reg_start) = "/(.*?)realm=\"//g";
    $var(reg_end) = "/\"(.*)//g";
    $var(raw_auth) = $(var(raw_auth){re.subst,$var(reg_start)});
    $avp(uac_realm) = $(var(raw_auth){re.subst,$var(reg_end)});
    # --- Here we assume, that avp(uac_username) and avp(uac_password) are set elsewhere
    if (uac_auth()) {
      route(INCREASE_CSEQ);
    } else {
      exit;
    }
    route(RELAY);
  }
  ...
}

route[RESTORE_CSEQ] {
  if (isflagset(AUTH_DONE) && is_avp_set("$avp(original_cseq)")) {
    remove_hf("CSeq:");
    append_hf("CSeq: $avp(original_cseq) $rm\r\n", "Call-ID"); 
  }
}

route[INCREASE_CSEQ] {
  if (isflagset(AUTH_DONE) && is_avp_set("$avp(original_cseq)")) {
    $var(inc_cseq) = $(avp(original_cseq){s.int}) + 1;
    remove_hf("CSeq:");
    append_hf("CSeq: $var(inc_cseq) $rm\r\n", "Call-ID");   
  }
}

No comments:

Post a Comment