Notes on SIP
This page has my notes taken during the reverse-engineering of the SIP protocol, as implemented in Asterisk.
Example SIP Message
Here’s an extract of a real SIP message. Note that HTTP format is used. There is a request line, followed by a set of header lines, followed by a blank line, followed by the “body” of the request.
INVITE sip:61057@192.168.8.102:5060;user=phone SIP/2.0
Via: SIP/2.0/UDP 192.168.8.225;branch=z9hG4bKa236ad2d25EAA60
From: "Bruce AllStarLink" <sip:1001@192.168.8.102>;tag=C33436A7-230A9A
To: <sip:61057@192.168.8.102;user=phone>
CSeq: 1 INVITE
Call-ID: 5ad49fee49257106bfba2eceaea5130b
Contact: <sip:1001@192.168.8.225>
Allow: INVITE,ACK,BYE,CANCEL,OPTIONS,INFO,MESSAGE,SUBSCRIBE,NOTIFY,PRACK,UPDATE,REFER
User-Agent: PolycomVVX-VVX_410-UA/5.9.5.0614
Accept-Language: en
Supported: replaces,100rel
Allow-Events: conference,talk,hold
Max-Forwards: 70
Content-Type: application/sdp
Content-Length: 352
v=0
o=- 1761157361 1761157361 IN IP4 192.168.8.225
s=Polycom IP Phone
c=IN IP4 192.168.8.225
t=0 0
a=sendrecv
m=audio 2260 RTP/AVP 9 102 0 8 18 127
a=rtpmap:9 G722/8000
a=rtpmap:102 G7221/16000
a=fmtp:102 bitrate=32000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:18 G729/8000
a=fmtp:18 annexb=no
a=rtpmap:127 telephone-event/8000
SIP Handshake Notes
In this testing I used a properly configured SIP phone (.225) and an Asterisk server (.102). Here’s the sequence, filtered for the SIP traffic.

RTP Port Traversal Examination
In this testing I used a properly configured SIP phone (.225) and an Asterisk server (.102).
Commands used to check firewall:
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --zone=public --list-all
sudo firewall-cmd --zone=allstarlink --list-all
The last command shows that a custom–sip rule had been added, which likely opens port 5060 for inbound connections from the phone.
The AllStarLink documentation has some helpful firewall notes here.
The SIP negotiation happens on UDP port 5060 without any problems.
The final message in the SIP negotiation (OK (INVITE)) assigns UDP port 16314 to the RTP/media. That is a random server-assigned port inside of the expected range of 10,000 to 20,000.

However, that port is normally not open to inbound connections. Notice the ICMP messages being generated in response to the phones RTP messages that tell the phone that its messages are being rejected.

But after a few seconds the server finally sends an RTP packet of its own to the phone on port 16314. This triggers the firewall hole-punch and no more ICMP errors are seen from the firewall.

Authentication Notes
An initial INVITE message from the phone was rejected by the server with a status 401 (Unauthorized). However, this rejection contains this header:
WWW-Authenticate: Digest realm="asterisk",nonce="1761157360/8582866c17ef9e4cfd024254e37cf1e8",opaque="50d273e02ab5c6c3",algorithm=MD5,qop="auth"
The format of this header is dictated by RFC 2617 in section 3.2.1.
Digest authentication is requested using the MD5 algorithm.
The subsequent INVITE message from the phone contained this new header:
Authorization: Digest username="1001", realm="asterisk", nonce="1761157360/8582866c17ef9e4cfd024254e37cf1e8", qop=auth, cnonce="/68T/KlfvEd3p7T", nc=00000001, opaque="50d273e02ab5c6c3", uri="sip:61057@192.168.8.102:5060;user=phone", response="30ffb4415b99d73da5ef3badee2781d1", algorithm=MD5
This particular endpoint was configured (/etc/asterisk/pjsip.conf) with a type=auth, auth_type=userpass, username=1001, password=1001.
The critical part of this header is the response attribute which contains a hash value that allows the client to prove that it knows the password. The definition of how this hash is computed is provided in RFC 2617 in section 3.2.2.
Here’s a short Python script that demonstrates the steps to validate the secret. The request/response match those provided above.
import hashlib
import base64
def MD5(a):
md5_hash = hashlib.md5()
md5_hash.update(a.encode("utf-8"))
return md5_hash.hexdigest()
# Here is the secret that the client has:
password="1001"
# All of this is contained in the client's request (no
# secrets here):
realm="asterisk"
username="1001"
uri="sip:61057@192.168.8.102:5060;user=phone"
nonce="1761157360/8582866c17ef9e4cfd024254e37cf1e8"
nc="00000001"
cnonce="/68T/KlfvEd3p7T"
qop="auth"
response="30ffb4415b99d73da5ef3badee2781d1"
# Here we are following the steps outline in RFC 2616 section 3.2.2.1.
# Please see https://datatracker.ietf.org/doc/html/rfc2617#section-3.2.2.1
a1 = username + ":" + realm + ":" + password
a2 = "INVITE" + ":" + uri
secret = MD5(a1) + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + MD5(a2)
# Do we match?
assert(MD5(secret) == response)