Part 3 of this series of articles focusses on the Contact header. In particular, I examine the use-cases where it is necessary to “fix” (or alter) a received Contact header. Contact headers work in close combination with Record-Route and Route headers in a mechanism known as loose routing. To get the most from this article some prior knowledge is required about loose routing, so if you are not already well acquainted with this subject please head over to my separate post covering the topic before continuing:
Contact and Record-Route headers explained – The Smartvox Knowledgebase
The article on Contact and Record-Route headers also explains some of the terminology (e.g. URI) used here and throughout this series of articles.
How the Contact header is created and populated
A Contact header is inserted into a SIP request by the device that is creating the request. This might happen when a user makes a call from their VoIP handset and an INVITE request is generated. After it has constructed the SIP request, the handset sends it to the appropriate destination.
Here is an example of a Contact header created by a VoIP handset or softphone connected to a LAN (subnet 192.168.10.0/24) where the user account ID is 1001 and the transport protocol is TLS :
Contact: "John Quick" <sip:1001@192.168.10.12:5144;transport=tls>
In the above example, we can see that my name – inside double quotes – is included in front of the URI. This is called the display name. It is not always present. The username part of the URI has been set to match the user ID, 1001. This is quite common on IP phones and softphones, although it is not mandatory. On a PBX, this is also likely to be the extension number. The domain (or host) part of the URI has been set to the local IP address of the device. Furthermore, the device is listening for connections on port 5144. Finally, there is a URI parameter describing the transport protocol.
When might the Contact address need fixing?
In the example above, a LAN address has been used in the Contact header URI. When the SIP request is transmitted over the Internet to some remote device (perhaps a Proxy server operated by the telephony service provider) it will result in the remote device seeing a private IP address that it has no known way of reaching. At the boundary between the private network (LAN) and the public network (Internet or WAN) there will be a router or firewall using NAT (Network Address Translation), as shown below:
Unless 1-to-1 NAT is used, it is very likely that the NAT router will also perform PAT – Port Address Translation. This means that both the IP address and the port in the Contact header are useless for routing SIP requests back upstream. So what can be done about this?
Client-side solutions
The device responsible for creating the Contact header – the UAC for the request, the UAS for the response – would normally use its own network interface address as the default value for the <host> element of the Contact URI. However, because this is likely to cause problems when the device is behind NAT, most devices can be configured to use a different address or to automatically detect the address to use. STUN is one of the most effective mechanisms available for automatic detection. This is explained in detail in one of my earlier articles here.
IP phones are frequently used behind NAT, but so too it is possible for an IP-PBX or even the service provider’s end point or proxy to be behind NAT. The equipment used in these situations (for example Asterisk, FreeSwitch, etc) can be manually configured to use a fixed external IP address. The assumption here is that the external IP address on the NAT router is static. If you wanted to use this type of equipment behind NAT and the external IP address was dynamic, then it would be necessary to use one of the so-called Dynamic DNS (DDNS) solutions. This technology is explained in detail in Wikipedia – you can click here to open their article. It is a compromise solution and I would not expect a commercial service provider to use it.
Server-side solutions
It would be unwise to assume that every client device connecting to your proxy, gateway or VoIP server has been configured to use the correct IP address and port in the Contact header. If you are providing Internet telephony services to a wide range of users, you will almost certainly find that some of your customers are connecting via NAT and sending you a private LAN address in the Contact header. Even if the IP address or hostname is correct, the header might not show the correct port. This is why you need to look at the possibility of Contact header address fixing. Here, I will only be discussing how this is done within OpenSIPS.
Detecting and fixing a Contact address mismatch in OpenSIPS
The OpenSIPS nathelper module provides a number of functions that may be used within your script to detect and fix an incorrect Contact address. The function fix_nated_contact() can be used to rewrite the address in the Contact header. To detect if a request came from behind NAT you would use the function nat_uac_test(). It is documented here:
https://opensips.org/html/docs/modules/2.4.x/nathelper.html#func_nat_uac_test
The function is capable of performing a number of different tests individually or combined (when combined, the function returns true if any one of the tests was true). The most relevant tests here are probably 32 and 64 because they compare the source address and port with the address and port in the Contact URI. The test would be used to decide when to call the function fix_nated_contact(). Thus, a snippet of code for most SIP requests would look like this:
if (nat_uac_test("96")) # Assume a UAC behind NAT fix_nated_contact();
The code shown above would be required in sections of the script that deal with SIP requests – initial and loose routed – although REGISTER requests are handled differently. When dealing with SIP responses, it would be sensible to add an additional test to ensure that a Contact header is present. This code would go in the onreply_route:
if (is_header_present("Contact") && nat_uac_test("96")) # Assume response came from behind NAT fix_nated_contact();
REGISTER requests are handled in a slightly different way. For REGISTER requests coming from behind NAT, the Contact header is not changed. Instead, the source IP address and port are stored in a variable (an AVP) and this is used to populate a special field, “received”, in the location table when the registration is saved. The function fix_nated_contact() is replaced with fix_nated_register(). Furthermore, I recommend using a broader spectrum of tests in the nat_uac_test() function when handling REGISTER. Thus, a code snippet for REGISTER requests might look like this:
if (nat_uac_test("122")) { force_rport(); fix_nated_register(); setbflag(NATTED_CLIENT); }
When is it wrong to fix a Contact address?
At first glance, it may look like the above nathelper functions provide a fantastic “silver bullet” solution for fixing mismatched Contact URI addresses. However, there are situations where the test returns a false positive result – where fixing the Contact address is the wrong thing to do. In particular, you should not alter the Contact address when receiving a request from another proxy server.
Question: When might you find that your OpenSIPS proxy server is connected to another third party proxy server? Answer: It happens when OpenSIPS is used to construct a Teams SBC for ‘Direct Routing’ because you always connect to the core Microsoft services through a remote Teams Proxy. It could also happen when you, as a service provider, offer retail SIP trunks as a product to your customers. It may not be common, but it is quite possible for a customer operating an IP-PBX on their own premises to be using a proxy – it could even be an OpenSIPS proxy.
How can OpenSIPS discriminate between NAT and another Proxy?
There is no simple solution to this problem. Automatic detection is possible but quite complex. Therefore, I recommend using prior knowledge to create a list of source addresses that are known to be using a proxy. It is possible to check the source address for a request or response using the check_address() or check_source_address() functions from the Permissions module of OpenSIPS. If you must have a fully automated check then consider techniques such as detection of Record-Route headers, inspection of Via headers or closer inspection of the actual address in the Contact header. The nat_uac_test() function offers a wide range of tests and it is possible to combine multiple tests together, with logic requiring both to be true, something like this:
if (nat_uac_test("96") && nat_uac_test("2") )
While it is not too difficult to think of ways to detect an upstream proxy, the problem is more difficult when it comes to detecting downstream proxies and knowing when to call fix_nated_contact() from within the onreply_route section of the script. Smartvox can supply template scripts which include automatic detection of upstream and downstream proxies as a part of our professional services.
Other special cases
Asymmetric connections
Some client devices deliberately use asymmetric connections. That is, they send a request from a high “randomly” selected port but expect all responses to be sent to a fixed port such as 5060. It is even possible for the port to change during a dialogue – a device that supports multiple connections might initially listen on port 5060, but respond using a high randomly selected port that is different for each connected device. If you are using TCP or TLS, then it is very likely that a SIP client device will use an ephemeral port to send requests while specifying a standard fixed port where it listens for responses.
All of these cases where the transmit and listen ports are different are likely to trigger a positive result from the nat_uac_test() function. There is no universal solution to this and you will have to deal with it on a case-by-case basis. In one project I worked on a few years ago, we needed to accommodate a new customer who was using Cisco 7940 series phones that had been flashed with SIP firmware. These used asymmetric port connections and the solution I used in OpenSIPS involved inspecting the User-Agent parameter and detecting a text substring that was common to every one of these phones. Although inelegant, it worked.
Using the OpenSIPS Topology Hiding module
The primary purpose of the Topology Hiding module is, as the name suggests, to hide infrastructure information from each side of the proxy. For example, to ensure that devices connecting from the Internet cannot see any of the IP addresses of the provider’s core servers. Those addresses would normally be visible in the Record-Route and Via headers. Another benefit derived from using this module is the reduced packet size for your SIP requests and responses. It can help when UDP packets need to be kept below the MTU size (typically 1500 bytes) to avoid fragmentation.
Contact fixing for devices behind NAT is still necessary when using this module, but inspection of SIP packet captures will not look the same. This is because the TH module replaces the URI value in the Contact header. It inserts a unique hash key in the username and its own host address in the domain/host element. Even when fix_nated_contact() has been called, you will not see the “fixed” address in the Contact header being passed downstream. Instead, you should see something similar to this:
Contact: <sip:X.did.d8a.ece18a55@12.34.56.78>
…where 12.34.56.78 is the IP address of your OpenSIPS server and X.did.d8a.ece18a55 is a unique hash key that OpenSIPS can use to retrieve internally stored routing information later in the dialogue.
In your opensips.cfg script, the code responsible for calling fix_nated_contact() for SIP requests should execute before the topology_hiding() function is called. This ensures that the routing information stored internally by OpenSIPS will contain the “fixed” address instead of the original Contact address. For SIP responses, you code it just the same as you would without the TH module – i.e. within the onreply_route there should be code to check the Contact address and, if necessary, call fix_nated_contact().
Further reading
This is part 3 of a series of articles written at the start of 2021. Here are links to the others in that series and to other articles covering similar topics:
Topic Introduction: https://kb.smartvox.co.uk/opensips/nat-contact-and-via-fixing-in-sip-part-1/
About responses & Via’s: https://kb.smartvox.co.uk/opensips/nat-contact-and-via-fixing-in-sip-part-2/
About Record-Route: Contact and Record-Route headers explained – The Smartvox Knowledgebase
Conclusion
Knowing when to fix the Contact header is vitally important when you are constructing an OpenSIPS script, but it is a topic that does not get the coverage it deserves. Please forgive me for not providing a big sample chunk of script that you could copy and paste into your opensips.cfg and move on, but hopefully this article has allowed you to understand the issues and has given you some ideas to work with. If you want to offload the task, then get in touch with me at Smartvox and we may be able to come to an arrangement.
If you found this article helpful, please click the Facebook recommendation button at the top and the local Like flag below. If you didn’t, then leave me a comment so I know what to improve.
The topology hiding module is very important for business security purposes. Knowing how to use it alongside NAT handling is vital.
Thank you for sharing your knowledge.