aboutsummaryrefslogblamecommitdiff
path: root/src/blog/building-a-personal-voip-system/index.md
blob: 73e73aaed1432892c4454413a7ce308f527e90d5 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987





                                                          
                                                                                    



















































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































                                                                                                                                                                           
---
title: Building a Personal VoIP System
date: May 25, 2023
description: Take control of your telephony with Asterisk!
---

I've always been a big [self-hoster](https://git.sacredheartsc.com/infrastructure/),
but had never attempted anything related to VoIP. I recently purchased some IP
phones and set up a personal home phone network using [Asterisk](https://www.asterisk.org/).
This guide will help you set up your own digital telephone system using
open-source tools.

This guide is written for those who are experienced with self-hosting, but are
totally unfamiliar with VoIP. Therefore, I'm going to gloss over certain
uninteresting technicalities for the sake of brevity.


## SIP: A Brief Introduction

Before getting your hands dirty with phone configuration and Asterisk dialplans,
it's worth taking some time to understand the underlying network protocols
involved. This will pay dividends later on when you're debugging the inevitable
connectivity issues with your voice calls (trust me).

The [Session Initialization Protocol](https://www.ietf.org/rfc/rfc3261.txt), or
SIP, is a signaling protocol used by basically every digital telephony device
produced in the last two decades. VoIP phones, softphones, office conferencing
devices...they all use SIP.  SIP can use either TCP or UDP, and usually listens
on port 5060.

There are two really important things you need to know about about SIP.

  1. SIP does **not** carry the voice data. It's just a signaling protocol,
     and it's only used to negotiate which IP address, port, and audio format
     should be used to carry the audio stream.

  2. SIP was initially released in 1999, and was designed with the assumption
     that each device has its own globally routable public IP address. After
     all, the IPv6 standard was released back in 1995, and NAT would soon be a
     thing of the past...right? Unforunately, this did not end up being the case.

For example, let's say we have a standard home network with two VoIP phones in
the local 192.168.1.0/24 subnet. Consider what happens when these two phones want
to call each other. In plain English, the SIP handshake will go something like
this:

> Hello 192.168.1.6.<br>
> I would like to place an audio call using the G.711 codec.<br>
> Please send your audio frames to me at address 192.168.1.5, port 20000.

> Hi there, 192.168.1.5!<br>
> Sure, I support the G.711 codec.<br>
> Please send your audio frames to me at address 192.168.1.6, port 30000.

Each phone randomly chooses an unused UDP port on which to receive the other
party's audio stream. After the SIP negotiation, they will both send voice data
to each other using the [Real-Time Transport Protocol](https://www.rfc-editor.org/rfc/rfc3550),
or RTP.  Since they are both on the same local network, this works fine.

### NAT Problems

Now consider what happens when we call someone _outside_ of our local network.
Let's say our router has a public IP of 203.0.113.8, and our friend Alice has 
address 198.51.100.7.

> Hello 198.51.100.7. Is Alice there?<br>
> I would like to place an audio call using the G.711 codec.<br>
> Please send your audio frames to me at address 192.168.1.5, port 20000.

> Hi there, "192.168.1.5". I see you have source IP 203.0.113.8...strange!<br>
> Sure, I support the G.711 codec.<br>
> Please send your audio frames to me at address 198.51.100.7, port 30000.

Thanks to [network address translation](https://en.wikipedia.org/wiki/Network_address_translation),
or NAT, we are able to make the SIP connection to Alice. Unfortunately, you
will probably find that audio only works in one direction!

We've got two problems. First, we've asked Alice to send her audio to our _local_
IP address, which won't route over the Internet. Luckily, most devices are smart
enough to use the source address of the incoming packet when these don't match up.
So Alice's audio stream will probably end up at our router.

But second, our router will still block all of her audio traffic! When it receives
a UDP packet from Alice's audio stream, there's no stateful NAT connection to
match it back to an internal client, so it's silently dropped. Sad!

### NAT Solutions

There are three ways to solve this problem.

1. Give each of your SIP devices its own public IP (probably not feasible).

2. Use a SIP [Application Layer Gateway](https://www.voip-info.org/routers-sip-alg/).
   This is a horrible feature offered by some routers. Basically, it deep-packet-inspects
   your SIP traffic, rewrites the headers, and creates port forwards on-the-fly
   to make sure the inbound audio stream makes its way to your device. 

   SIP ALGs are a total hack and notoriously buggy. In addition, when you decide
   to encrypt your SIP traffic using TLS, they quit working altogether.

3. Configure a fixed RTP port range on each of your SIP devices, then create
   static port forwarding rules on your router for each device. This is really
   the only decent option.

   Note that since we'll be using Asterisk, you also have the option of
   "proxying" all your audio streams through the Asterisk box--then you only
   have one port forward to set up (we'll get to that later).


## Asterisk: What _is_ it?

[Asterisk](https://www.asterisk.org/) is an open-source implementation of a
[private branch exchange](https://en.wikipedia.org/wiki/Business_telephone_system#Private_branch_exchange),
or PBX. If you're a VoIP novice like I was, this is probably meaningless to you.

A PBX is the central brain of any private telephone network. The general idea is
this: You plug a bunch of dumb telephones into one side of the PBX, and you plug
one or more uplinks to the [PSTN](https://en.wikipedia.org/wiki/Public_switched_telephone_network "Public Switched Telephone Network")
into the other side. The PBX lets the internal phones dial each other using
private internal extensions, while multiplexing calls to and from the PSTN. It
also handles advanced features like voicemail, call queues, hunt groups, and
interactive menus for callers.

In the old days, the PBX would be a huge box down in the server room with a
rat's nest of of RJ-11 telephone cables sprawling out, connecting it to every
single phone in the office. These monstrosities have since been replaced with
software-based PBXes that talk SIP over standard Ethernet networks. Asterisk is
the de-facto open source PBX.


## Jargon Glossary

Before we get started, you need to know some lingo. Asterisk is an _old_ project,
with an unintuitive configuration. Hopefully this section will help you decipher
some of the documentation and setup guides you find out there.

Extensions
: Technically, in Asterisk parlance, any dialed number is an extension. In
  practice though, an _extension_ typically refers to a  _local_ phone number
  (like 6001) with an associated SIP account. While extensions can be
  alphanumeric, virtually no one does this--most people use 3- or 4-digit
  numbers.

Queues
: Asterisk [queues](https://www.voip-info.org/asterisk-call-queues/) are a
  mechanism for managing incoming callers. You can configure which local
  extensions should ring for each queue, and even play custom music while
  callers are waiting.

SIP Trunk
: A "SIP Trunk" is just a fancy term for your upstream telephone provider. For
  example, if you subscribe to a provider like [VOIP.ms](https://voip.ms/en/invite/MzU5Nzc4),
  they'll give you a public phone number and a SIP server to connect to, along
  with a username and password. This is your "SIP Trunk."

DID
: A DID, or "Direct Inward Dial," is the technical term for a public phone number.

Contexts
: In Asterisk, every call is assigned a _context_. The context is simply a
  name of your choosing, and you specify it when you configure each extension
  or SIP trunk. For example, I give all my internal phones a context called
  `from-home`, and I give my SIP trunk a context called `from-pstn`.

Dialplan
: The Asterisk [dialplan](https://wiki.asterisk.org/wiki/display/AST/Dialplan)
  is an arcane scripting language where you define what happens when you dial a
  number or an incoming call is received. The dialplan is the glue between the
  _extensions_ and _contexts_ described above. The syntax is fairly unintuitive,
  but don't worry! We'll walk through it in a later section.

Codecs
: A codec is a type of digital encoding used for the audio stream over the
  wire. Every VoIP device supports one or more codecs, and you need at least
  one common codec with the other party to make calls. When no shared codecs
  are supported, Asterisk has the ability to do man-in-the-middle transcoding.
  <br>
  [G.711](https://en.wikipedia.org/wiki/G.711) is basically the only codec with
  universal support. It has two versions: `ulaw` (North America/Japan) and
  `alaw` (everywhere else). I use a higher quality codec ([G.722](https://en.wikipedia.org/wiki/G.722))
  for internal calls, and let Asterisk transcode down to G.711 when I call out
  to the PSTN.

BLF / Presence
: The BLF, or busy lamp field, is a little LED on your VoIP phone that lights
  up when one of your contacts is using their line. In Asterisk, this functionality
  is enabled by something called a [presence subscription](https://wiki.asterisk.org/wiki/display/AST/Configuring+res_pjsip+for+Presence+Subscriptions).

SIP / PJSIP
: On some guides online, you will see references to the `chan_sip` and `chan_pjsip`
  modules. `chan_sip` is the legacy Asterisk SIP implementation. You should be
  using PJSIP for everything these days.


## Step 1: Acquire an IP Phone

First, you will need one or more VoIP phones. Any device that supports SIP
calling should work fine with Asterisk, so this mostly comes down to personal
preference.

As a beginner, you'll probably want to select for ease of configuration. Most
modern VoIP phones expose a friendly web GUI where you configure all your SIP
accounts, ring settings, etc. I'd also recommend avoiding any devices that
rely on proprietary setup tools.

Personally, I've had zero problems with Yealink devices. I recommend the [T54W](https://www.yealink.com/en/product-detail/ip-phone-t54w)
model for a desk phone, or the [W73P](https://www.yealink.com/en/product-detail/dect-phone-w73p)
if you prefer a cordless model. You can buy these new at the usual online retailers,
but eBay often has used models for sale if you want to save some money.

If you don't want to buy a physical device, you can also use a softphone app
like [Linphone](https://f-droid.org/en/packages/org.linphone/). I use it on my
Android phone running [GrapheneOS](https://grapheneos.org/), and it works well
with the right settings. I'll cover Linphone in a later section.


## Step 2: Subscribe to a VoIP Provider

Next, you need to subscribe to a VoIP service so you can make and receive calls
over the PSTN. You'll often see people call this a "SIP Trunk."

Basically, you pay a VoIP company a small monthly fee, usually $3-$5 per month
for a single DID. In exchange, they give you a public telephone number, along
with credentials for their SIP server. After configuring Asterisk to connect to
that SIP server, you can make and receive calls via the PSTN with that number.

I can personally recommend two providers.

1. [VOIP.ms](https://voip.ms/en/invite/MzU5Nzc4) is an inexpensive provider with 
   servers in the USA, Canada, and western Europe. You can get "unlimited"
   inbound calls with a single DID for about $5 a month, or less if you pay by the
   minute.

   VOIP.ms also supports call encryption via TLS/SRTP, which is nice.

2. [JMP.chat](https://jmp.chat/) (USA/Canada only) is an XMPP-based service that
   lets you make and receive voice calls and SMS/MMS messages using your
   existing XMPP account. I especially like JMP because there's already tons of
   high-quality native XMPP apps for both desktop and mobile devices. In
   addition, their entire stack is open source.

   While JMP is primarily focused on XMPP calling, they also provide you with
   a SIP account that you can use with Asterisk.

There are _tons_ of other VoIP providers out there, so shop around.


## Step 3: Configure Asterisk

Now you're ready to set up Asterisk. These instructions are written for RHEL-based
distributions, but should be applicable to other Unixen.

### Installation

First, install Asterisk:

```bash
dnf install asterisk asterisk-pjsip asterisk-voicemail-plain
```

You'll also need the sound files. Some distributions include these with their
Asterisk package (EPEL does not). 

```bash
for codec in g722 g729 gsm sln16 ulaw wav; do
  curl -sL "https://downloads.asterisk.org/pub/telephony/sounds/asterisk-core-sounds-en-${codec}-current.tar.gz" \
  | tar xvz -C /usr/share/asterisk/sounds
done
```

### Network Configuration

Now we're ready to edit some config files. If you're behind a NAT (likely), you'll
need to start by telling Asterisk about your local network. Edit `/etc/asterisk/pjsip.conf`
and add some transport templates.

```lisp
; /etc/asterisk/pjsip.conf

; This template contains default settings for all transports. 
[transport-defaults](!)
type = transport
bind = 0.0.0.0

; For communication to any addresses within local_nets, Asterisk will not apply
; NAT-related workarounds.
local_net = 127.0.0.0/8
local_net = 10.0.0.0/8
local_net = 172.16.0.0/12
local_net = 192.168.0.0/16

; If you have a public static IP for your Asterisk server, set it here.
external_media_address     = 203.0.113.8
external_signaling_address = 203.0.113.8


; The following UDP and TCP transports will inherit from the defaults.
[transport-udp](transport-defaults)
protocol = udp

[transport-tcp](transport-defaults)
protocol = tcp
```

Remember our discussion about NAT and RTP audio streams above? We also need to
set up a static port range for incoming UDP audio traffic. Choose a suitable
range for your Asterisk server in `rtp.conf`. Then, set up port forwarding on
your router/firewall to forward all incoming UDP traffic within that range to the
internal IP of your Asterisk server.

**If your Asterisk server is behind NAT and you forget to do this, you'll experience
one-way audio in your phone calls.**

```lisp
; /etc/asterisk/rtp.conf

[general]
rtpstart=20000
rtpend=20999
```

### SIP Trunks

Next, we'll configure our SIP trunk(s).  I'll be using `pjsip_wizard.conf`, since
the PJSIP Wizard configuration style eliminates a ton of boilerplate.
For this step, you'll need a server hostname and port, along with the username
and password provided by your upstream SIP provider.

```lisp
; /etc/asterisk/pjsip_wizard.conf

; This template contains default settings for all SIP trunks.
[trunk-defaults](!)
type = wizard

; Require SIP authentication.
sends_auth = yes

; Require SIP registration.
sends_registrations = yes

; Send media to the address and port on the incoming packet, regardless of what
; the SIP headers say (NAT workaround).
endpoint/rtp_symmetric = yes

; Rewrite the SIP contact to the address and port of the request (NAT workaround).
endpoint/rewrite_contact = yes

; Send the Remote-Party-ID SIP header. Some providers need this.
endpoint/send_rpid = yes

; Force the ulaw codec, which should work for everything in North America.
endpoint/allow = !all,ulaw

; Call encryption is out of scope for this guide. 
endpoint/media_encryption = no

; If registration fails, keep trying ~forever.
registration/max_retries = 4294967295

; Don't assume an authentication failure is permanent.
registration/auth_rejection_permanent = no

; Perform a connectivity check every 30 seconds.
aor/qualify_frequency = 30


; Your SIP trunks go here. For this example, we'll assume you're using VOIP.ms.
; You can pick whatever section name you like, as long as its unique.
[voipms](trunk-defaults)
; You almost certainly want to use TCP.
transport = transport-tcp

; Hostname and port for your SIP provider.
remote_hosts = atlanta2.voip.ms:5060

; Choose a context name for incoming calls from this account. You'll use this
; name in your dialplan.
endpoint/context = from-pstn

; Your SIP provider will give you these credentials.
outbound_auth/username = 555555
outbound_auth/password = s3cret
```

### Local SIP Extensions

In the same file, we'll also configure our local extensions. You'll need an
extension for each VoIP handset or softphone within your network. I'll be
using a prefix of `6XXX` for local extensions, but feel free to use whatever
convention you prefer.

The following example has three extensions: two dedicated VoIP
handsets, and one Android softphone. Note that if you want
to use a softphone outside of your local network, you'll need to either open
your Asterisk instance to the world (not recommended, unless you know what you're doing)
or set up some kind of VPN.

```lisp
; /etc/asterisk/pjsip_wizard.conf

; This template contains default settings for all local extensions.
[extension-defaults](!)
type = wizard

; Require clients to register.
accepts_registrations = yes

; Require clients to authenticate.
accepts_auth = yes

; When simultaneous logins from the same account exceed max_contacts, disconnect
; the oldest session.
aor/remove_existing = yes

; For internal phones, allow the higher quality g722 codec in addition to ulaw.
endpoint/allow = !all,g722,ulaw

; Context name for BLF/presence subscriptions. This can be any string of your
; choosing, but I'm using "subscribe". We'll use this in the dialplan later.
endpoint/subscribe_context = subscribe


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Extension 6001 - VoIP phone
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[6001](extension-defaults)
; Dialplan context name for calls originating from this account.
endpoint/context = from-home

; Voicemail address (note: I'm using the same voicemail address for all extensions)
endpoint/mailboxes = 6000@default

; Internal Caller ID string for this device.
endpoint/callerid = Living Room <6001>

; Username for SIP account. By convention, this should be the extension number.
inbound_auth/username = 6001

; Password for SIP account (you can choose whatever password you like).
inbound_auth/password = s3cret

; Maximum number of simultaneous logins for this account.
aor/max_contacts = 1

; Check connectivity every 30 seconds.
aor/qualify_frequency = 30

; Set connectivity check timeout to 3 seconds.
aor/qualify_timeout = 3.0

; IMPORTANT! This setting determines whether the audio stream will be proxied
; through the Asterisk server.
;
; If this device is directly reachable by the internet (either by a publicly
; routable IP, or static port mappings on your router), choose YES.
;
; Otherwise, if this device is hidden behind NAT, choose NO.
endpoint/direct_media = yes


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Extension 6002 - VoIP phone
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; (Same settings as above, except for the extension number and password.)
[6002](extension-defaults)
endpoint/context      = from-home
endpoint/mailboxes    = 6000@default
endpoint/callerid     = Kitchen <6002>
inbound_auth/username = 6002
inbound_auth/password = s3cret2
aor/max_contacts      = 1
aor/qualify_frequency = 30
aor/qualify_timeout   = 3.0
endpoint/direct_media = yes


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Extension 6003 - Linphone app on Android device
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[6003](extension-defaults)
endpoint/context      = from-home
endpoint/mailboxes    = 6000@default
endpoint/callerid     = Smartphone <6003>
inbound_auth/username = 6003
inbound_auth/password = s3cret3
aor/max_contacts      = 1
endpoint/direct_media = no

; For mobile devices, connectivity checks should be disabled.
; Two reasons for this:
;   1. Poor cellular signal often causes the connectivity check to time out,
;      even when nothing is actually wrong.
;   2. Frequent traffic on the TCP session causes constant wakeups and kills
;      battery life.
aor/qualify_frequency = 0
```

### Voicemail

Voicemail recordings are stored on the local filesystem of the Asterisk server.
You can access them by dialing a special voicemail extension from a local phone
(configured in your dialplan). In addition, if you have a local [MTA](https://en.wikipedia.org/wiki/Message_transfer_agent "Mail Transfer Agent")
running, Asterisk can dispatch an email to an address of your choice whenever new
voicemails arrive.

In the previous section, we referenced a voicemail address called `6000@default`
for our internal extensions. We'll create the actual voicemail box in `voicemail.conf`.

```lisp
; /etc/asterisk/voicemail.conf

; This section contains global voicemail options.
[general]
; Audio formats to store voicemail files.
format=wav49|gsm|wav

; "From:" address used for sending voicemail emails.
serveremail=asterisk-noreply@example.com

; Whether to attach the voicemail audio file to notification emails.
attach=yes

; Maximum number of messages per mailbox.
maxmsg=100

; Maximum length of a voicemail message in seconds.
maxsecs=300

; Maximum length of a voicemail greeting in seconds.
maxgreet=60

; How many milliseconds to skip forward/back when rew/ff in message playback.
skipms=3000

; How many seconds of silence before we end the recording.
maxsilence=10

; Silence threshold (what we consider silence: the lower, the more sensitive).
silencethreshold=128

; Maximum number of failed login attempts.
maxlogins=3

; Email template for voicemail notifications.
emailsubject=New voicemail ${VM_MSGNUM} in mailbox ${VM_MAILBOX}
emailbody=Hi ${VM_NAME},\n\nYou have a new voicemail in mailbox ${VM_MAILBOX}.\n\nFrom: ${VM_CALLERID}\nDate: ${VM_DATE}\nDuration: ${VM_DUR}\nMessage Number: ${VM_MSGNUM}
emaildateformat=%A, %B %d, %Y at %r

; Default timezone for mailboxes (defined in zonemessages section below).
tz=myzone

; Locale for generating date/time strings.
locale=en_US.UTF-8

; Minimum voicemail password length.
minpassword=4


; Custom timezone definitions go in this section.
[zonemessages]
myzone=America/New_York|'vm-received' Q 'digits/at' IMp


;;;;;;;;;;;;;;;;;
; Voicemail Boxes
;;;;;;;;;;;;;;;;;

; Each of the following sections describe a voicemail "context," which is a
; a collection of voicemail boxes. If you don't need multiple contexts, you
; can specify "default".
;
; The format is as follows:
;   VOICEMAIL_NUMBER => INITIAL_PASSWORD,REAL_NAME,EMAIL_ADDRESS,,,
;
; The last three fields aren't relevant for simple configurations.

[default]
6000 => 1234,John Doe,johndoe@example.com,,,
```

### Queues

When someone calls our phone number, we'd like all our phones to ring simultaneously
(similar to a traditional landline phone). We can emulate this behavior in Asterisk
using a `ringall` queue.

Queues are configured in `queues.conf`.

```lisp
; /etc/asterisk/queues.conf

; Global queue settings go in this section.
[general]
; Persist dynamic member lists in the astdb.
persistentmembers = yes

; Some options for more intuitive queue behavior.
autofill                = yes
monitor-type            = MixMonitor
shared_lastcall         = yes
log_membername_as_agent = yes

;;;;;;;;;;;;;;;;;;;
; Queue Definitions
;;;;;;;;;;;;;;;;;;;

; The "home-phones" queue is for incoming calls to our home phone line.
[home-phones]
; For each incoming call, ring all members of the queue.
strategy = ringall

; Max number of seconds a caller waits in the queue.
timeout = 30

; Don't announce estimated hold time, etc.
announce-frequency          = 0
announce-holdtime           = no
announce-position           = no
periodic-announce-frequency = 0

; Allow ringing even when no queue members are present.
joinempty      = yes
leavewhenempty = no

; Ring member phones even when they are on another call.
ringinuse = yes

; Queue Members
;
; Each member is specified with the following format:
;  member => INTERFACE,PENALTY,FRIENDLY_NAME,PRESENCE_INTERFACE
;
; The "penalty" value is not interesting for our use case.
; With PJSIP, the BLF/Presence interface is identical to the standard interface name.
member => PJSIP/6001,0,Living Room,PJSIP/6001
member => PJSIP/6002,0,Kitchen,PJSIP/6002
member => PJSIP/6003,0,Smartphone,PJSIP/6003
```

### The Dialplan

At this point, we've configured our upstream SIP trunks, created SIP accounts for
some local phone extensions, and even defined a queue for incoming calls. 
All that's left is to glue all this together in a [dialplan](https://wiki.asterisk.org/wiki/display/AST/Dialplan)!

The dialplan is configured in `extensions.conf`, and it's definitely the least intuitive
aspect of Asterisk. You'll most likely find its syntax to be confusing at first glance.

The thing to remember is that in the dialplan, everything is an _application_.
Hanging up is performed by the `Hangup()` application, and voicemail prompts are handled
by the `Voicemail()` application. Dialing a phone number is handled by (you guessed it)
the `Dial()` application. Each application can take one or more comma-separated arguments.

Now, for the syntax. Each dialplan context is marked by square brackets. Each
line within the context is (confusingly) called an _extension_, and has the
following format:

```lisp
[context-name]
exten => NAME,PRIORITY,APPLICATION()
exten => NAME,PRIORITY,APPLICATION()
; etc...
```

The _name_ is the number (or pattern) of the extension.

The _priority_ defines the order in which the step should be executed.

Finally, the _application_ performs some action on the call.

A simple context definition might look something like this:

```lisp
[from-home]
; ${EXTEN} is a macro which expands to the currently dialed number.
exten => _6XXX,1,Dial(PJSIP/${EXTEN})
exten => _6XXX,2,Hangup()
```

This dialplan section allows internal phones to call each other.
The `_6XXX` is an extension pattern. When a device in the `from-home`
context dials a 4-digit extension beginning with `6`, Asterisk will
ring the corresponding PJSIP account.


Because it gets tedious repeating the same extension name over and over,
Asterisk provides some syntactic sugar. The exact same dialplan could also
be written as the following:

```lisp
[from-home]
exten => _6XXX,1,Dial(PJSIP/${EXTEN})
 same => n,Hangup()
```

Below, I've provided a complete Asterisk dialplan for our example VoIP network.
It supports the following features:

  1. Internal phones can dial each other via their 4-digit extension.

  2. Internal phones can dial out to the PSTN via standard 10-digit numbers.

  3. Internal phones can access the voicemail menu by dialing `*99`.

  4. Internal phones can show BLF/line status for all other phones.

  5. Incoming calls from the PSTN will ring all internal phones simultaneously.
     Callers will be sent to voicemail if no one answers within 25 seconds.

  6. If your phones support the "auto answer" header, you can initiate a whole-house
     intercom by dialing `6000`.

I'll provide plenty of comments to help you understand the arcane syntax.
Hopefully it will be enough to bootstrap your own dialplan!

```lisp
; /etc/asterisk/extensions.conf

; Remember, context names for each SIP account are specified in pjsip_wizard.conf.

; First, some safeguards against abuse of the built-in contexts.
[public]
exten => _X.,1,Hangup(3)
[default]
exten => _X.,1,Hangup(3)


; Next, some global variables. You'll need to change some of these.
[globals]
; Your local area code.
MY_AREA_CODE = 555

; Your real name and public phone number. This will be used for outgoing calls
; to the PSTN.
MY_CALLER_ID = John Doe <+5555555555>

; Dial this number from a local phone to access the voicemail menu.
VOICEMAIL_NUMBER = *99

; Voicemail address (configured in voicemail.conf)
VOICEMAIL_BOX = 6000@default

; Ring for this many seconds before forwarding the caller to voicemail.
VOICEMAIL_RING_TIMEOUT = 25

; Name of the 'ringall' queue for local phones.
HOME_QUEUE = home-phones

; Number to dial for local intercom.
INTERCOM   = 6000

; Dial pattern for local extensions.
LOCAL_EXTS = _6XXX


; Boilerplate to enable BLF/presence subscriptions. Note that the context name
; corresponds to the "endpoint/subscribe_context" value in pjsip_wizard.conf.
[subscribe]
exten => _XXXX,hint,PJSIP/${EXTEN}


; This context handles incoming calls from our SIP trunk provider. Each call is
; is placed into a ringall queue. If there is no answer, the caller is forwarded
; to voicemail.
[from-pstn]
exten => _X.,1,Queue(${HOME_QUEUE},nr,,,${VOICEMAIL_RING_TIMEOUT})
 same => n,Answer(500)
 same => n,Voicemail(${VOICEMAIL_BOX},su)
 same => n,Hangup()


; This is a function (or "gosub" in Asterisk lingo) that sets the "auto answer"
; SIP header on an outgoing call.
[gosub-intercom]
exten => s,1,Set(PJSIP_HEADER(add,Alert-Info)=auto answer)
 same => n,Return()


; This context handles incoming calls from local phones.
[from-home]
; When the INTERCOM number is dialed, page all members of the "home-phones" queue
; into a conference call.
exten => ${INTERCOM},1,Set(CALLERID(all)=Intercom <${EXTEN}>
 same => n,Page(${STRREPLACE(QUEUE_MEMBER_LIST(${HOME_QUEUE}),",","&")},db(gosub-intercom^s^1),10)
 same => n,Hangup()

; For local-to-local calls, ring indefinitely.
exten => ${LOCAL_EXTS},1,Dial(PJSIP/${EXTEN})
 same => n,Hangup()

; When the voicemail number is dialed, dump the caller into the voicemail menu.
exten => ${VOICEMAIL_NUMBER},1,Answer(500)
 same => n,VoiceMailMain(${VOICEMAIL_BOX},s)
 same => n,Hangup()

; The following extensions are used to dial out to the PSTN via our SIP trunk,
; using a personalized caller ID string.
;
; Recall that we named our SIP trunk "voipms" in pjsip_wizard.conf.
;
; N matches any digit 2-9
; X matches any digit 1-9

; For numbers formatted as +1xxxxxxxxxx, dial as-is.
exten => _+1NXXNXXXXXX,1,Set(CALLERID(all)=${MY_CALLER_ID})
 same => n,Dial(PJSIP/${EXTEN}@voipms)
 same => n,Hangup()

; For numbers like 1xxxxxxxxxx, add a leading "+".
exten => _1NXXNXXXXXX,1,Set(CALLERID(all)=${MY_CALLER_ID})
 same => n,Dial(PJSIP/+${EXTEN}@voipms)
 same => n,Hangup()

; For numbers without a country code, add a "+1". 
exten => _NXXNXXXXXX,1,Set(CALLERID(all)=${MY_CALLER_ID})
 same => n,Dial(PJSIP/+1${EXTEN}@voipms)
 same => n,Hangup()

; For 7-digit numbers, add the local area code.
exten => _NXXXXXX,1,Set(CALLERID(all)=${MY_CALLER_ID})
 same => n,Dial(PJSIP/+1${MY_AREA_CODE}${EXTEN}@voipms)
 same => n,Hangup()

; For 3-digit numbers, like 311, 411, 911 (UNTESTED!!!), dial as-is.
exten => _N11,1,Set(CALLERID(all)=${MY_CALLER_ID})
 same => n,Dial(PJSIP/${EXTEN}@voipms)
 same => n,Hangup()
```

### Troubleshooting

Once you've got all your configs in place, give Asterisk a kick:

```bash
systemctl restart asterisk
```

On RedHat-based distributions, you can check `/var/log/asterisk/messages` for
errors. You can also check the systemd journal:

```bash
journalctl -u asterisk
```

You can also get lots of real-time information from the Asterisk console.
To try it out, run the following command as root:

```bash
asterisk -rvvvvv
```

If you're experiencing connectivity issues with your voice calls, you can
enable SIP debugging in the console using the following command:

```default
pjsip set logger on
```

## Step 4: Configure your IP Phones

The final step is to configure your VoIP phones to connect to your Asterisk server,
and make a few test calls! The instructions here are for Yealink phones, but they
should easily translate to other manufacturers.

### SIP Account

Navigate to the IP address of the VoIP phone in your web browser, and log in with the
default username and password (usually `admin/admin`). From here, you should be able to
add a SIP account. In the Yealink interface, this is found under _Account→Register_.

For the "Living Room" extension we created above, you would set the following:

- **Register Name:** 6001
- **Username:** 6001
- **Password**: s3cret
- **Server Host:** _IP/hostname of your Asterisk server_
- **Server Port:** 5060
- **Transport:** UDP

### Codecs

You should also make sure the appropriate codecs are enabled on the device. In
the Yealink web interface, you'll find them under _Account→Codec_. I recommend
enabling these codecs in the following priority:

1. **G722**, for high-quality local calls
2. **PCMU** (_ulaw_), for dialing out to the PSTN

Asterisk will automatically transcode G722 down to _ulaw_ if the other side doesn't
support it. You can mess around with even higher quality codecs like Opus, but
in my experience they are not well supported nor easy to transcode.

### RTP Port Range

To reduce load on the Asterisk server, you may want your VoIP phone to send and
receive audio streams directly to and from the other party. To do this, you'll
need to configure your router/firewall to forward all incoming RTP traffic for
the device.

First, give the device a static IP on your local network. Then, configure your
router to port-forward all UDP traffic on your chosen RTP port range to that IP.

In the Yealink web interface, you can configure a static RTP port range under
_Network→Advanced→Local RTP Port_.

If you _don't_ do all this, then make sure `endpoint/direct_media` is set to
`no` for the SIP account in `pjsip_wizard.conf`! 

### Voicemail Number

You'll also want to configure which number the phone should dial when you press
the voicemail button. In the Yealink web interface, set _Account→Advanced→Voice Mail_
to `*99` (or whatever you number you chose for `VOICEMAIL_NUMBER` above).

### Busy Lamp Field (BLF)

You may want to have your phone's LED light up when a given line is on a call. In the
Yealink web interface, you can configure this under _Dsskey→Line Key_.

For each line you're interested in, set **Type** to _BLF_ and **Value** to the other
party's extension number.

### Intercom

For the intercom functionality described in the dialplan above, make sure the phone
is configured to allow auto-answer. In the Yealink web interface, you can configure
this by setting _Features→Intercom→Intercom Allow_ to _on_.

## VoIP on Android: Linphone

In the past, Android provided a built-in SIP client. Sadly, [as of Android 12](https://www.xda-developers.com/android-12-killing-native-sip-calling/),
this has been removed.

After trying all of the SIP apps available in F-Droid, I'm only able to recommend [Linphone](https://f-droid.org/en/packages/org.linphone/).
It works with bluetooth devices, has an intuitive interface, and reliably delivers calls.

To keep Android from killing the app, you'll need to make sure battery optimizations
are disabled. Go to _Settings→Apps→See All Apps→Linphone→App Battery Usage_ and select
_Unrestricted_.

When configuring the SIP account in Linphone, use the following settings:

- **Username:** 6003 _(or your chosen mobile extension)_
- **Password:** _your chosen SIP account password_
- **Domain:** _IP/hostname of your Asterisk server_
- **Transport:** TCP
- **Expire:** 3600
- **Apply prefix for outgoing calls:** _enabled_

You'll definitely want to use the TCP transport for a mobile device. Smartphone
radios are exceedingly efficient at keeping a long-running TCP connection open,
and TCP will reliably deliver your SIP packets even with a poor cellular signal.

With _Expire_ set to 3600, your phone will wake up every hour to re-establish the
SIP connection. I've never had any connectivity issues with the TCP transport, but
if you're paranoid, you might want to set this to a lower value.

I've also found the following Linphone settings useful:

- **Settings→Audio→Echo cancellation:** _disabled_ (most phones have hardware-level echo cancellation)
- **Settings→Audio→Adaptive rate control:** _enabled_
- **Settings→Audio→Codec bitrate limit:** 128 kbits/s
- **Settings→Audio→Codecs:** _enable PCMU and G722_
- **Settings→Call→Improve interactions with bluetooth devices:** _enabled_
- **Settings→Call→Voice mail URI:** \*99
- **Settings→Advanced→Start at boot time:** _enabled_

You may be tempted to hide the persistent notification icon while Linphone
is running. Don't do it! Whenever I've tried to hide this icon, I get tons of
missed calls, and Asterisk reports the device as offline for minutes at a time.
As long I keep the icon visible in the notification area, I don't have any issues.


## Closing Thoughts

For the past few months, I've used this exact setup for my primary phone number,
and I've been pleased with the results.

If you decide to self-host your own VoIP infrastructure, you'll definitely want
to configure some type of QoS on your router or firewall to prioritize voice
traffic. Otherwise, you'll experience lags and poor audio quality whenever
your network is congested.

In addition, you may want to investigate encrypting your calls via STRP and SIP
TLS. I don't personally do this, because voice calls are totally in the clear as
soon as you connect to the PSTN anyway.