Freedom Internet PjSip

This Asterisk setup uses chan_pjsip / pjsip.conf instead of chan_sip / sip.conf.
Replace all the RED stuff by the appropriate values!
If you are not a Freedom internet or VoIPGRID customer, you have to replace the GREEN stuff as well.
And the BLUE stuff is about selecting transports.
Note: This setup is for PjSip without NAT! You can use PjSip with NAT though. See: Configuring res_pjsip to work through NAT

General

A major difference with sip.conf is that you can set far less defaults. A lot of general settings from sip.conf now have to be set on a per telephone basis.
The example below just sets the default realm;

[global]
type=global
;debug=yes
; From domain
default_realm=My_Domain

Furthermore, a lot of config statements have slightly different names. Below some of the differences.
Note: The meaning in sip.conf vs psjip.conf is not always perfectly identical!

sip.conf vs pjsip.conf
sip.confpjsip.conf
allowoverlapallow_overlap
bindaddrbind (See: Transports)
contactdenycontact_deny
contactpermitcontact_permit
defaultuserusername
directmediadirect_media
dtlsverifydtls_verify
dtmfmodedtmf_mode
fromdomainfrom_domain
fromuserfrom_user
sendrpidsend_rpid
sipdebugdebug
srvlookupsrv_lookups
tlscertfilecert_file
tlsdontverifyserver=yesverify_server=no
tlsprivatekeypriv_key_file
trustrpidtrust_id_inbound

More in the Python script 'sip_to_pjsip.py', which is part of the Asterisk source.
Note: There is no real equivalent of 'allowguest=yes'. There is however something pretty close at the bottom of this page.

The way registrations are stored in '/var/lib/asterisk/astdb.sqlite3' is also different. So, when you move from Sip to PjSip you need to re-register your telephones!

And you need to modify extensions.conf before you can dial!

Transports

There is a bit of a quirk here. When setting 'bind=[::]', older versions of PjSip will use IPv4-mapped IPv6 addresses. So instead of '10.20.30.40' it might use '::ffff:10.20.30.40'.
All that needs to be done to fix this, is to move the pointer seven bytes to the right;

  ::ffff:10.20.30.40
  ↓      ↑
  └───→──┘

This requires two lines of code. One to test if the address 'starts' with '::ffff:'. A second to move the pointer if it does.

PjSip on the other hand, doesn't do this. Instead, newer versions use IPv6-only sockets along side IPv4 sockets. This means that with newer versions you can use 'bind=[::]' in combination with 'bind=0.0.0.0'!
The example below can be used to test for IPv6-only sockets;

[transport-tcp6]
type=transport
allow_reload=yes
protocol=tcp
bind=[::]

After a reload check this with netstat;

~$ netstat -an | grep "5060"
tcp6       0      0 :::5060                 :::*                    LISTEN

Note that both IPv6-only and dual stack sockets show up as IPv6!

Next, try to telnet to '127.0.0.1 5060'. If this doesn't work, the IPv6 sockets are IPv6-only, and you can safely use 'bind=[::]' alongside 'bind=0.0.0.0'!

The example below uses the IPv6 address of the Ethernet interface ('2001:db8:1234:1::1') for IPv6 and any interface ('0.0.0.0') for IPv4. This will work with older versions of PjSip.
For older versions, replace '2001:db8:1234:1::1' by your IPv6 address.
For 'newer' versions (since 2017) use 'bind=[::]' instead of 'bind=[2001:db8:1234:1::1]'.

; --- UDP ---
; Default transport
[transport-udp6]
type=transport
allow_reload=yes
protocol=udp
;bind=[2001:db8:1234:1::1]	; Old
bind=[::]

; 1st fallback
[transport-udp]
type=transport
allow_reload=yes
protocol=udp
bind=0.0.0.0

; --- TCP ---
[transport-tcp6]
type=transport
allow_reload=yes
protocol=tcp
;bind=[2001:db8:1234:1::1]	; Old
bind=[::]

[transport-tcp]
type=transport
allow_reload=yes
protocol=tcp
bind=0.0.0.0

; --- TLS ---
[transport-tls6]
type=transport
allow_reload=yes
protocol=tls
;bind=[2001:db8:1234:1::1]	; Old
bind=[::]
cert_file=/etc/asterisk/fullchain.pem
priv_key_file=/etc/asterisk/privkey.pem
ca_list_path=/etc/ssl/certs
;verify_client=no
;verify_server=no
;require_client_cert=no
allow_wildcard_certs=yes
;dtls_verify=no
method=sslv23
;method=tlsv1_2
;method=tlsv1

[transport-tls]
type=transport
allow_reload=yes
protocol=tls
bind=0.0.0.0
cert_file=/etc/asterisk/fullchain.pem
priv_key_file=/etc/asterisk/privkey.pem
ca_list_path=/etc/ssl/certs
;verify_client=no
;verify_server=no
;require_client_cert=no
allow_wildcard_certs=yes
;dtls_verify=no
method=sslv23
;method=tlsv1_2
;method=tlsv1

The certs are generated with Dehydrated.
The TLS method ('method=') may depend on your software version and the TLS version used by your VoIP service provider / ITSP (Internet Telephony Service Provider).
Note: Without a 'verify_server=no' TLS will fail if your ITSP uses a self-signed cert.
Note: A wildcard cert for '*.example.org' allows 'foo.example.org', not 'foo.bar.example.org'.

After a reload, this should show up in netstat.

A 'bind=[2001:db8:1234:1::1]' shows up as;

~$ netstat -an | egrep "506[01]"
tcp        0      0 0.0.0.0:5060            :::*                    LISTEN
tcp        0      0 0.0.0.0:5061            :::*                    LISTEN
tcp6       0      0 2001:db8:1234:1::1:5060 :::*                    LISTEN
tcp6       0      0 2001:db8:1234:1::1:5061 :::*                    LISTEN     
udp        0      0 0.0.0.0:5060            :::*
udp6       0      0 2001:db8:1234:1::1:5060 :::*

A 'bind=[::]', on the other hand, shows up as;

tcp        0      0 0.0.0.0:5060            0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:5061            0.0.0.0:*               LISTEN     
tcp6       0      0 :::5060                 :::*                    LISTEN     
tcp6       0      0 :::5061                 :::*                    LISTEN     
udp        0      0 0.0.0.0:5060            0.0.0.0:*                          
udp6       0      0 :::5060                 :::*

This is similar to a 'sip.conf' with 'bindaddr=::', 'tcpenable=yes' and 'tlsenable=yes': Fully dual stack on all interfaces.
Below an 'asterisk -rx "pjsip show transports"';

Transport:  transport-tcp             tcp      0      0  0.0.0.0:5060
Transport:  transport-tcp6            tcp      0      0  [::]:5060
Transport:  transport-tls             tls      0      0  0.0.0.0:5061
Transport:  transport-tls6            tls      0      0  [::]:5061
Transport:  transport-udp             udp      0      0  0.0.0.0:5060
Transport:  transport-udp6            udp      0      0  [::]:5060

Transport selection

For remote registration PjSip will look for NAPTR, SRV, AAAA (IPv6 address) and A (IPv4 address) DNS records in that order. When SRV records are available it prefers TLS over TCP and TCP over UDP;

  1. NAPTR
    Any SIP addresses found here will be processed as below.
  2. SRV
    1. TLS
      1. AAAA
      2. A
    2. TCP
      1. AAAA
      2. A
    3. UDP
      1. AAAA
      2. A
  3. AAAA
  4. A

Without SRV records it uses the order set in pjsip.conf.
If you want something else you have to set the transport in the registration and endpoint sections.

Fail over

With res_sip and IAX, Asterisk just uses the 'first' host or IP address it finds; If an IP address fails, it won't try any other hosts or IP addresses.
With PjSip, if a host times out, it will try the next host or IP address. So PjSip makes full use of any redundancy your ITSP might provide.
Note: For an IPv6 to IPv4 fail over, you need to specify a transport in a non IP version specific way (see ITSP Transport below) or no transport at all. Personally, I don't think you need an IPv6 to IPv4 fail over. Unless of course, if your ITSP sends you an invite via IPv4 after registering using IPv6.

Telephones on LAN

A telephone called 'My_phone' with password 'Pass_Word' in 'My_Domain', with context 'lan-phones'.
Config sections are 'aor', 'auth' and 'endpoint'

Telephone AoR

AoR means Address-of-Record. This in fact the SIP URI of the telephone. In this case this established by the telephone registering to your Asterisk. Hence the 'auth' section.
'max_contacts' limits the number of telephones that can be simultaneously registered with the same username. In this case one;

; --- My_Phone ---
[my_phone]
type=aor
max_contacts=1

Telephone Auth

[my_phone]
type=auth
realm=My_Domain
username=My_Phone
password=Pass_Word

Telephone Endpoint

Endpoint describes the properties of the telephone;

[my_phone]
type=endpoint
context=lan-phones
dtmf_mode=auto
disallow=all
allow=alaw
;allow=ulaw
direct_media=no
from_domain=My_Domain
contact_deny=0.0.0.0/0
contact_deny=::/0
contact_permit=192.168.0.0/16
contact_permit=2001:db8:1234::/48
auth=my_phone
aors=my_phone

'direct_media=no' keeps Asterisk in the RTP audio path, turning it into a IPv6 <-> IPv4 proxy.

It's probably a good idea to test your telephones before configuring your ITSP.
Note: You need to re-register your telephones when moving from Sip to PjSip! And you need to modify extensions.conf before you can dial!

Below the result of a 'pjsip show endpoints';

 Endpoint:  my_phone                                             Not in use    0 of inf
     InAuth:  my_phone/my_phone
        Aor:  my_phone                                           1
      Contact:  my_phone/sip:my_phone@192.168.7.9:5060     Hash       NonQual         nan

If a telephone is not registered, it says 'Unavailable' instead of 'Not in use'.

Remote registrations

A remote registration with account-id / user-name 'Account_Id' and password 'Pass_Word'.
This has additional sections 'registration' and 'identify'.
Note: Your ITSP 'translates' your telephone number to your account-id / user-name. SIP on the other hand, deals with things like user-names and domains! So telephone numbers have a limited meaning.

ITSP Registration

; --- Registration ---
[freedom]
type=registration
retry_interval=20
max_retries=20000
contact_user=Telephone_Number
auth_rejection_permanent=no
expiration=120
;transport=transport-udp
;transport=transport-udp6
;transport=transport-tls6
outbound_auth=freedom
client_uri=sip:Account_Id@sipproxy.voipgrid.nl
;client_uri=sip:Account_Id@sip6.voipgrid.nl
server_uri=sip:sipproxy.voipgrid.nl
;server_uri=sip:sip6.voipgrid.nl
line=yes
endpoint=freedom

Note: If an endpoint is specified, Asterisk insists on 'line=yes' and vice versa!

Note: Using '_uri:sips:' instead of '_uri:sip:' may not work: If you can't make Asterisk use 'sips:' in outgoing 'INVITE' 'From:' headers as well, the remote server will reject INVITEs.
Below an registration example (uses sip6 instead of sipproxy);

REGISTER sip:sip6.voipgrid.nl SIP/2.0
Via: SIP/2.0/UDP [2001:db8:1234:1::1]:5060;rport;branch=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
From: <sip:Account_Id@sip6.voipgrid.nl>;tag=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
To: <sip:Account_Id@sip6.voipgrid.nl>
Call-ID: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
CSeq: XXXXX REGISTER
Contact: <sip:Telephone_Number@[2001:db8:1234:1::1]:5060>
Expires: 120
Allow: OPTIONS, REGISTER, SUBSCRIBE, NOTIFY, PUBLISH, INVITE, ACK, BYE, CANCEL, UPDATE, PRACK, MESSAGE, REFER
Max-Forwards: 70
User-Agent: Asterisk PBX Version
Authorization: Digest username="Account_Id", realm="voipgrid.nl", nonce="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", uri="sip:Account_Id@sip6.voipgrid.nl", response="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
Content-Length:  0

ITSP Transport

You might want to set the transport. If you do, make sure the selected protocol and IP version are supported by the remote end!
You can set a transport by a line 'transport=Some_Transport'. For instance, 'transport=transport-udp6'. This however, is IP version specific!
For a non IP version specific transport selection, append the transport to the SIP URI instead. The example below sets the transport to UDP;

server_uri=sip:sipproxy.voipgrid.nl\;transport=udp
client_uri=sip:Account_Id@sipproxy.voipgrid.nl\;transport=udp

This way you can have an IPv6 to IPv4 fail over, IF your ITSP supports it.

Line

'line=yes' adds a 'line=Some_String' to the 'Contact:' header line;

Contact: <sip:Telephone_Number@[2001:db8:1234:1::1]:5060;line=XXXXXXX>

This will put the same string in incoming 'INVITE' or 'To:' lines;

INVITE sip:Account_Id@[2001:db8:1234:1::1]:5060;line=XXXXXXX SIP/2.0

If for some reason the 'match' (see the identify section below) doesn't work, the match is made on the 'line' string.
Note: If an endpoint is specified, Asterisk insists on 'line=yes' and vice versa!

ITSP Auth

[freedom]
type=auth
;auth_type=userpass
realm=voipgrid.nl
username=Account_Id
password=Pass_Word

ITSP AoR

Here the AoR is a SIP URI;

; --- Endpoint ---
[freedom]
type=aor
contact=sip:sipproxy.voipgrid.nl
;contact=sip:sip6.voipgrid.nl

Some examples use 'contact=sip:Accout_Id@sipproxy.voipgrid.nl' instead.
If you you don't want to set a transport in the Endpoint section, you can set it here instead. The example below sets the transport to UDP;

contact=sip:sipproxy.voipgrid.nl\;transport=udp

ITSP Identify

This identifies incoming requests.

[freedom]
type=identify
endpoint=freedom
; Match voipgrid.nl
;match=185.103.76.0/22,195.35.114.0/23,2a06:2a80::/29
match=185.103.76.0/22
match=195.35.114.0/23
match=2a06:2a80::/29

Both a single-line and multi-line 'match' work.

ITSP Endpoint

[freedom]
type=endpoint
context=freedom-in
dtmf_mode=auto
disallow=all
allow=alaw
direct_media=no
trust_id_inbound=yes
send_rpid=yes
;transport=transport-udp
;transport=transport-udp6
;transport=transport-tls6
from_user=Account_Id
from_domain=voipgrid.nl
outbound_auth=freedom
aors=freedom
;rtp_ipv6=yes
; SRTP
;media_encryption_optimistic=yes
;media_encryption=sdes

Note: SRTP / media_encryption needs to be enabled at the remote end for incoming audio encryption to work!

'from_domain' sets the domain in the 'From:' header line in INVITE.
Some examples use 'from_domain=sipproxy.voipgrid.nl' instead of 'from_domain=voipgrid.nl'. Which results in 'Account_Id@sipproxy.voipgrid.nl' instead of 'Account_Id@voipgrid.nl'
Below an outgoing INVITE example (uses sip6 instead of sipproxy);

INVITE sip:Dialled_Number@sip6.voipgrid.nl SIP/2.0
Via: SIP/2.0/UDP [2001:db8:1234:1::1]:5060;branch=XXXXXXXXXXXXXXX
Max-Forwards: 70
From: "Display-Name" <sip:Account_Id@voipgrid.nl>;tag=XXXXXXXXXX
To: <sip:Dialled_Number@sip6.voipgrid.nl>
Contact: <sip:Account_Id@[2001:db8:1234:1::1]:5060>
Call-ID: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX@voipgrid.nl
CSeq: 102 INVITE
User-Agent: Asterisk PBX Version
Date: Thu, 07 Nov 2024 14:55:46 GMT
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH, MESSAGE
Supported: replaces, timer
Remote-Party-ID: "Display-Name" <sip:Telephone_Number@voipgrid.nl>;party=calling;privacy=off;screen=no
Content-Type: application/sdp
Content-Length: 279

When you don't set an endpoint transport, the default transport will be used for outgoing requests (such as outgoing INVITEs).
Note: Your ITSP may use a different transport for requests send to you (such as incoming INVITEs), than you configured for outgoing requests! They may use the transport you registered with instead. So if you configured a transport for registration, it's probably a good idea to configure the same transport for endpoint as well.
You can select a transport by a line 'transport=Some_Transport', or append a transport to the SIP URI in the AoR section.

extensions.conf

Use 'PJSIP' instead of 'SIP' in dial commands.
E.G.:

exten => 1234,1,Dial(PJSIP/Some_Extention,60,t)
	same => n,Hangup()

Rasterisk output

Check things with rasterisk.

Endpoints

Below the result of a 'pjsip show endpoints' (uses 'sip6.voipgrid.nl' and transport 'transport-tls6');

 Endpoint:  freedom                                              Not in use    0 of inf
    OutAuth:  freedom/Account_Id
        Aor:  freedom                                            0
      Contact:  freedom/sip:sip6.voipgrid.nl               Hash       NonQual         nan
  Transport:  transport-tls6            tls      0      0  [::]:5061
   Identify:  freedom/freedom
        Match: 185.103.76.0/22
        Match: 195.35.114.0/23
        Match: 2a06:2a80::/29

Registrations

Below the result of a 'pjsip show registrations';

 freedom/sip:sip6.voipgrid.nl                            freedom                     Registered        (exp. 102s)

Invite

An example incoming INVITE;

INVITE sip:Account_Id@[2001:db8:1234:1::1]:5060;line=XXXXXXX SIP/2.0
Record-Route: <sip:[2A06:2A80:0:114:0:0:0:41]:6060;r2=on;lr>
Record-Route: <sip:195.35.114.41:5070;r2=on;lr>
Record-Route: <sip:195.35.114.57;lr;ftag=XXXXXXXXXXXX;did=XXXXXXXXXXXX;mc=from-dutch;cc=XXXXXXXXXX>
Via: SIP/2.0/UDP [2A06:2A80:0:114:0:0:0:41]:6060;branch=XXXXXXXXXXXXXXXXXXXXXX
Via: SIP/2.0/UDP 195.35.114.57:5060;branch=XXXXXXXXXXXXXXXXXXXXXX
Via: SIP/2.0/UDP 195.35.114.107:5060;received=195.35.114.107;rport=5060;branch=XXXXXXXXXXXXXXXXXXXXX
From: "Display-Name" <sip:Telephone_Number@voipgrid.nl>;tag=XXXXXXXXXXXX
To: <sip:Account_Id@voipgrid.nl>
Contact: <sip:Telephone_Number@195.35.114.107:5060>
Call-ID: XXXXXXXXXXXX
CSeq: 28282 INVITE
Allow: OPTIONS, SUBSCRIBE, NOTIFY, PUBLISH, INVITE, ACK, BYE, CANCEL, UPDATE, PRACK, INFO, REFER
Supported: 100rel, timer, replaces, norefersub
Session-Expires: 1800
Min-SE: 90
Max-Forwards: 68
User-Agent: VGUA  
Content-Type: application/sdp
Content-Length: 357

Outgoing and incoming INVITEs compared;

INVITE
Header
line
OutgoingIncoming
INVITE Dialled
Telephone Number
Account Id /
User Name
From: Account Id /
User Name
Caller's
Telephone Number
To: Dialled
Telephone Number
Account Id /
User Name
Contact: Account Id /
User Name
Caller's
Telephone Number

Note: Your ITSP may alter the caller's telephone number in incoming INVITEs;
In the 'From:' line my ITSP uses 'trunk_prefix area_code subscribers_number' for national calls and 'international_call_prefix country_code area_code subscribers_number' for international calls. This is the number displayed on your telephone.
In the 'Contact:' line it's '+ country_code area_code subscribers_number'.

Telephone numbers in incoming INVITEs
National
From:
International
From:
Contact:
0207654321 0031207654321 +31207654321

Note: In this example the trunk prefix is '0' and the international call prefix is '00'. These vary by country. See: Country calling codes

Hairpin

Below the result of a 'pjsip show endpoints' during dialling myself (uses 'sip6.voipgrid.nl' and transport 'transport-udp6');

 Endpoint:  freedom                                              In use        2 of inf
    OutAuth:  freedom/Account_Id
        Aor:  freedom                                            0
      Contact:  freedom/sip:sip6.voipgrid.nl               Hash       NonQual         nan
  Transport:  transport-udp6            udp      0      0  [::]:5060
   Identify:  freedom/freedom
        Match: 185.103.76.0/22
        Match: 195.35.114.0/23
        Match: 2a06:2a80::/29
    Channel: PJSIP/freedom-XXXXXXXX/AppDial                      Up            00:00:08
        Exten:                           CLCID: "My_Display-Name" <My_Telephone_Number>
    Channel: PJSIP/freedom-XXXXXXXX/Dial                         Up            00:00:08
        Exten: Account_Id                CLCID: "" <Account_Id>

The first 'Channel' (AppDial) is outgoing, the second (Dial) incoming.
'Exten' is the dialled extension in incoming calls.
My ITSP tries to find the name that goes with the calling telephone number and, if found, makes this the Diplay-Name in the 'From:' header in the incoming INVITE. Asterisk then copies this to the INVITE send to my telephones. Which in turn, display the name;

 Endpoint:  my_phone                                             In use        1 of inf
     InAuth:  my_phone/my_phone
        Aor:  my_phone                                           1
      Contact:  my_phone/sip:my_phone@192.168.7.9:5060     Hash       NonQual         nan
    Channel: PJSIP/my_phone-XXXXXXXX/AppDial                     Up            00:00:10
        Exten:                           CLCID: "Display-Name" <Telephone_Number>

Use IPv6 instead of IPv4

If you ever find yourself behind a dual stack WAN link with CGN for IPv4, IPv6 is probably the only thing that works.
If you are a Freedom Internet or VoIPGRID customer and you prefer to use IPv6 over IPv4 for your remote registration, configure 'sip6.voipgrid.nl' instead of 'sipproxy.voipgrid.nl'. This will default to TCP.
If you prefer UDP to TCP, set the transport to 'transport-udp6'.
And if you prefer TLS, set the transport to 'transport-tls6'. This will connect to port 5061.

Match on header

This is about as close to 'allowguest=yes' as you can get. Probably not something you want, but if you do here it is.
This allows anyone who makes it past the firewall to access you with an INVITE containing: 'To: <sip:Me@My_Domain.Tld>';

In pjsip.conf:

[me]
type=aor
contact=sip:Me@My_Domain.Tld

[me]
type=identify
endpoint=me
match_header=To: /\<Me@My_Domain\.Tld\>/

Note that this is fact a regex. Somehow a regular match doesn't work.

[me]
type=endpoint
context=me-in
disallow=all
allow=alaw
allow=ulaw
allow=g722
aors=me

In extensions.conf:

[me-in]
exten => Me,1,Dial(PJSIP/Some_Extention,60,t)
	same => n,Hangup()