Uncategorized

Building a 302 Redirect Server With Kamailio

/

I worked with the Broadworks platform for over 10 years. One of my favorite elements of their solution is the Network Server, or NS. The NS processes all INVITES from Back-to-Back User Agents to perform translations, routing lookups and more. Once the INVITE has finished processing, the NS will return a 302 Redirect with the Contacts that should be used, or another final response (such as 404 Not found).

One rainy Saturday in Amsterdam (during COVID-19) I decided to build a basic 302 Redirect Server with Kamailio the mimic some of the functionality of the Broadworks NS.

I started off with the minimal-proxy-config file as most Kamailio based projects do. You can find the complete code HERE, including a docker-compose file that you can use to launch the entire environment. The docker-compose file will build the following topology.


Loading Modules and Setting Modparams

First, we need to load the modules used in this tutorial. Before you add these into your config file, be aware that there is an order that modules should be loaded. Check the full file in my gitlab repo for a better understanding.

loadmodule "dmq.so"
loadmodule "db_mysql.so"
loadmodule "htable.so"

I’m also pulling in environmental variables to use in the config. This is performed via #!substdef in combination with the $env psudovariable.

####### Other Args and Env Vars #########
#!substdef "/DEFAULT_DEST/$env(DEFAULT_DEST):5060/"
#!substdef "/MYIP/$env(MYIP)/"
#!substdef "/LOCAL_SOCKET/$env(LOCAL_SOCKET)/"
#!substdef "/PEERNAME/$env(PEERNAME)/"

Next, we need to configure a few modparams. For htable, we are enabling dmq, defining the database url, and defining which tables needs to be replicated via DMQ. Because we are using a database with htable, Kamailio will obtain the contents of the respective database table upon reload of the service.

# ---- htable params ----
modparam("htable", "enable_dmq", 1)
modparam("htable", "db_url", "mysql://user:password@172.16.10.254/db")
modparam("htable", "htable", "dnis=>size=14;dmqreplicate=1;dbtable=dnis;")
modparam("htable", "htable", "deny_list=>size=5;dmqreplicate=1;")
modparam("htable", "htable", "active_calls=>size=5;dmqreplicate=1;")
modparam("htable", "htable", "admission_limits=>size=5;dmqreplicate=1;")

We also need to enable DMQ. The server_address value is where this server is listening for DMQ messages. The notification_address is where all DMQ messages will be sent for another peer to process.

modparam("dmq", "server_address", "sip:MYIP:5060")
modparam("dmq", "notification_address", "sip:PEERNAME:5060")

We are setting flags based upon values being true while the routing logic is taking place. You can learn more about flag operations here.

#!define FLT_A_ON_NET 20
#!define FLT_B_ON_NET 22
#!define FLT_DENY_ADMISSION 23
#!define FLT_DENY_CALL 24

Building the Routing Configuration

Now that we have a few basic settings in place, we can begin looking at the routing logic. The request_route block is simple, and the logic is really handled within other route blocks.

request_route {
  route(HANDLE_DMQ);  // This is the entry point for handling all DMQ messages
  route(HANDLE_OPTIONS); // Responds with a 200 OK for all OPTIONS

  if (is_method("INVITE")) {
    route(IS_A_ON_NET); // Looks up if the originator is listed in htables
    route(IS_B_ON_NET); // Looks up if the term # is in htables
    route(IS_DENIED); // Performs a deny function for blocked B numbers.
    route(REPLY_302); // Sends the 302 redirect 
  } else {
    xlog("L_INFO", "MAIN | $ci | Non INVITE received.");
  }
}

Handling the specific DMQ traffic is performed by the dmq_handle_message() function. You can see that this is only for requests where the method is KDMQ.

route[HANDLE_DMQ]{
  if(is_method("KDMQ"))
   {
           dmq_handle_message();
   }
}

Handling of OPTIONS is much like the DMQ block. This route block checks if the method is OPTIONS, and replies with a 200 OK. This is configured as the “SBC” is sending OPTION pings to determine if the 302 Redirect Servers are online.

route[HANDLE_OPTIONS]{
  if(is_method("OPTIONS"))
   {
           sl_send_reply(200, "OK");
           exit;
   }
}

The routing block IS_A_ON_NET determines if the A numbers is on-net. It looks into the hash table named dnis, and queries with the from user value ($fu). If you aren’t familiar with the pseudovariables, you should spend some time learning the basics. If $fu exists in htable, set the flag bit, otherwise, reset it.

route[IS_A_ON_NET] {
  # This route block tests the originating number to determine if it is linked the platform
  xlog("L_INFO", "IS_A_ON_NET | $sht(dnis=>$fU) \n");
  if ($sht(dnis=>$fu)) {
    setflag(FLT_A_ON_NET);
    return 1;
  } else {
    resetflag(FLT_A_ON_NET);
    return 0;
  }
}

The logic is similar on the B number, with the exception that I am searching for the request user ($rU) value, and that a different flag is set.

route[IS_B_ON_NET] {
  # Tests the B number to determine if this call is to an on-net number
  xlog("L_INFO", "IS_B_ON_NET | $sht(dnis=>$rU) \n");
  if ($sht(dnis=>$rU) != $null) {
    xlog("L_INFO", "IS_B_ON_NET | Trying to set flag \n");
    setflag(FLT_B_ON_NET);
    return 1;
  } else {
    resetflag(FLT_B_ON_NET);
    return 0;
  }
}

In order to determine if a B number is blocked, IS_DENIED will lookup the $rU value in the deny_list htable. If the $rU matches an entry in that field, the DENY_CALL routing block is called which adds an X-header and replies with a 403.

route[IS_DENIED] {
  # Looks up the B number to determine if it is known "bad" destination
  xlog("L_INFO", "IS_DENIED| $sht(deny_list=>$rU) \n");
  if ($sht(deny_list=>$rU)) != $null {
    route(DENY_CALL);
  } else {
    return 0;
  }
}

route[DENY_CALL] {
  # Denies a call because the B number is in the naughty list.
  xlog("L_INFO", "DENY_CALL | $ci $rU \n");
  append_to_reply("X-Deny-Reason: Calls to this destination number are not allowed\n");
  sl_send_reply("403", "Calls Cannot be made to this number");
  exit;
}


Finally, we make it to the point where a 302 is returned. Assuming that the flag FLT_B_ON_NET is set, we add a Contact header and set the host portion of the URI to what was defined as the value in htable. Here’s a peak.

root@522c7d38f9d2:/# kamcmd htable.dump dnis
{
	entry: 12426
	size: 1
	slot: {
		{
			name: 8675309
			value: 172.16.10.50
			type: str
		}
	}
}

I have the number “8675309” (yes.. like the song), listed as a KEY and the VALUE is 172.16.10.50, which is the IP of an internal endpoint. So the Contact header will look like "Contact: sip:8675309@172.16.10.50". You can add additional entries that point to other internal devices. This will allow you to route certain numbers to the platform where they are provisioned, or easily re-route when you need to migrate a number to a different system.

If the B number is not on-net, we will set the Contact as the default destination value. Once the Contact header is added, we send a 302 Redirect back to the “SBC”. Once the “SBC” receives the 302, it is configured to look at the 302, and relay the INVITE to the internal endpoint.

route[REPLY_302] {
  # Sends a 302 redirect back to the proxy that requested the routing lookup
  xlog("L_INFO", "REPLY_302 \n");
  if isflagset(FLT_B_ON_NET) {
    xlog("L_INFO", "REPLY_302 | B IS ON NET $sht(dnis=>$rU) \n");
    append_to_reply("Contact: sip:$rU@$sht(dnis=>$rU)\r\n");
  } else {
    append_to_reply("Contact: sip:$rU@" + 'DEFAULT_DEST' + "\r\n");
  }
  sl_send_reply("302", "Redirect");
  exit;
}

If you are interested in how the “SBC” is handling the 302 redirect, it’s just a failure route.

// This is on the SBC / Edge Proxy. 
failure_route[REDIRECT_NOACC] {
  xlog("L_INFO", "REDIRECT_NOAC | INTERNAL | $ci | $si \n");
	if(!t_check_status("3[0-9][0-9]")) {
		exit;
	}
  xlog("L_INFO", "REDIRECT_NOACC|  \n");
	get_redirects("3:1");
	route(RELAY);
}

After built this topology, I added 100,000 DNIS entries into my database, and then rebooted the 302 redirect servers. This loaded surprisingly fast considering that everything was running on my under powered MacBook Air. Anyways, I hope that this article provided you with some ideas about how Kamailio can be used as a 302 Redirect Server in your projects.