EXTENSIONS.CONF: Setting the Hints used by SLA
The SIP SUBSCRIBE/NOTIFY mechanism – what it is and how it works
The SIP protocol includes a standardised mechanism to allow any SIP client (an IP phone being an example of a SIP client) to monitor the state of another device. Details are provided in the SIP protocol document RFC3265. Basically, it works like this: If client device A wants to be informed of changes to the status of device B, it sends a SUBSCRIBE request – either directly to device B or to a server that is aware of the state of device B. If the SUBSCRIBE request is successful, then every time device B changes state, device A is sent a SIP NOTIFY message telling it about the event or change of status. This is the mechanism that IP phones use to control BLF lamps. It is the mechanism that Asterisk SLA uses to switch on and switch off the trunk status lamps on participating extension phones. (By the way, it is also the same mechanism that is used to switch on/switch off message waiting lamps).
Hints – How Asterisk supports the SUBSCRIBE/NOTIFY mechanism
Asterisk is like a PBX – it acts as a SIP server and it has awareness of the state of many things including attached phones, queues, voicemail boxes etc. It makes perfect sense that Asterisk should be able to accept SUBSCRIBE requests and then notify the subscribing device whenever there is a change of status in the monitored device. However, the SIP protocols and standards cannot pre-define a name for every possible device or status – instead, the protocol provides a general framework for event notification without defining the actual events or device names. To allow maximum flexibility, Asterisk allows device names to be configured within the dial plan – within the extensions.conf file. For this purpose it uses something called Hints. Hints are simply a user-configurable mapping between an arbitrary name tag and a specific telephony device or application that Asterisk knows about.
When Asterisk receives a SIP SUBSCRIBE request it checks for a hint in the dial plan that matches the name of the device to be monitored. The hint tells Asterisk which physical device this corresponds to. It is best understood by seeing some examples.
Some examples of Asterisk Hints
Hints usually map an extension number (or name) to a device. However, hints can also map to other special internal states (virtual devices) such as the state of a specific call park slot or the state of a specific meetme conference. SLA introduces a new virtual device that is used in a hint to allow subscriptions to the state of a trunk line as defined in the SLA.CONF file.
Here are some examples of Asterisk hints:
[mysubscribes]
exten => 4001,hint,SIP/4001 exten => 2001,hint,Zap/5 exten => parked01,hint,park:701@parkedcalls exten => 6555,hint,Meetme:555 exten => 8*4001_line1,hint,SLA:8*4001_line1 exten => 8*4001_line2,hint,SLA:8*4001_line2 |
Hints used for Asterisk SLA
Note particularly the last two lines in the above example. These hint lines are used to map the tag “8*4001_line1” to the state of the virtual device “SLA:8*4001_line1” and the tag “8*4001_line2” to the state of the virtual device “SLA:8*4001_line2”. What this means is that a programmable key on an IP phone can now subscribe to the state of “8*4001_line1” and it will then receive updates (in the form of SIP NOTIFY messages) every time the corresponding virtual device changes state. Virtual devices prefixed with “SLA:” identify elements within the array of internal states mentioned earlier. These internal states are updated when your dial plan calls the functions SLATrunk or SLAStation or when a call on one of those trunk lines ends. At this point, all the subscribing phones are sent a NOTIFY message to tell them the new state.
For Asterisk SLA to work properly, you must define appropriate hints for all the programmable keys assigned as shared line keys on your IP phones. If you had 4 trunk lines and 10 IP phones using SLA then this might mean you need to define a total of 40 hints in your dial plan! This is where the use of “autocontext” looks like an attractive option – it automatically creates the hints for you. However, there is a major downside to using autocontext for this purpose – any subsequent reload of the dial plan will erase all the auto-created hints. Therefore, you are advised to grit your teeth and just add all 40 hints manually. Unfortunately, you cannot use exten number patterns and the ${EXTEN} variable for hints – Asterisk checks if the hint device exists when the dial plan is first read so it throws errors up for hints that try to use variables.
Where does Asterisk look for the hints in extensions.conf?
To avoid any possible confusion about the location of these hints, it is recommended that you use the subscribecontext parameter in your sip.conf file to identify the location of all hints within extensions.conf.
There is a little documentation about subscribe context within the Asterisk wiki:
http://www.voip-info.org/wiki-Asterisk+config+sip.conf
You may find the examples in this link slightly more useful:
http://www.voip-info.org/wiki/view/480i+Busy+lamp+field+’BLF’+support
EXTENSIONS.CONF: Dial plan for Inbound calls using SLA
Dial plan for Inbound calls on Trunk Lines
Central to the correct handling of inbound calls is the function SLATrunk. Essentially, if a call comes in on trunk line 1 then you must call SLATrunk(line1). If a call arrives on trunk line 2, then you must call SLATrunk(line2). The parameter you pass to the SLATrunk function must be the name tag you assigned to that trunk in SLA.CONF.
Systems with SIP trunks are far less suited to SLA than those using conventional analogue trunks, so in this section we mostly concentrate on the use of analogue trunk lines. These might be Zap FXO ports or, if you are using the Pika Warp Appliance, they would be Pika FXO ports. If you are only using one FXO port then the dial plan is straightforward, but on systems with more than one trunk line some method must be used that ensures the correct parameter value is passed to function SLATrunk. There are two ways this can be done:
- Assign each trunk line to its own unique context within the zapata.conf or pika.conf file. In this case, autocontext may be used to automatically generate the required entries in the dial plan.
- Assign all trunks to a common inbound call handling context. Within that context, extract the FXO port number from the channel variable ${CHANNEL}. Autocontext is not suitable for use with this method.
The following example shows how to call the SLATrunk function within a dial plan context that is common to all the FXO ports, using a variable whose value is set from the ${CHANNEL} variable. (This sample code would only be suitable for a maximum of 9 FXO ports!):
[fxo]
exten => s,1,Noop(‘Channel ID is ‘${CHANNEL}) exten => s,n,Set(linetag=line${CHANNEL:-1}) exten => s,n,SLATrunk(${linetag}) exten => s,n,Noop(‘SLATrunk status is ‘${SLATRUNK_STATUS}) exten => s,n,Gotoif($[${SLATRUNK_STATUS} = SUCCESS] ?exit) exten => s,n,Answer() exten => s,n,Wait(1) exten => s,n,Background(greeting1) ….. here the dial plan depends on call handling choices and so is not shown exten => s,n(exit),Hangup |
Explanation for the above example dial plan: The variable ${CHANNEL} is pre-set by Asterisk to show the channel. On a Pika Appliance, it might look like this “Pika/fxo/1” for FXO port 1 etc. ${CHANNEL:-1} gets only the last character from that variable – the channel number. The variable “linetag” is created by the Set function and given a value of “line1” for FXO port 1, “line2” for FXO port 2 etc. The call to SLATrunk uses this variable as its parameter.
The function SLATrunk will eventually return and the dial plan will then execute the next step. We can check the outcome of the SLATrunk function using the variable ${SLATRUNK_STATUS} – it is a kind of return code. A value of “SUCCESS” indicates that the call was answered within SLATrunk so our dial plan does not need to do anything more. Any other return value shows that the call has not been answered, so we may want Asterisk to answer it and play a greeting then do further processing such as record a voicemail message or whatever.
Answering an incoming call
There are actually two ways that a user can answer an incoming call using SLA:
- If their phone is ringing, they can pick up the handset (or press the speaker phone button).
- If a shared line key is correctly programmed for the relevant trunk line, then the lamp will be flashing to show there is an incoming call – the user can press that shared line key to answer the call.
It is possible for a phone to ring and have no shared line key. It is also possible for a phone to be showing the incoming call on a shared line key, but to not be ringing (for example, when the station “ringdelay” parameter in sla.conf has a non-zero value). It is also possible for a phone to ring and show the incoming call on a shared line key at the same time. These things are all controlled by settings in SLA.CONF.
Asterisk function SLATrunk()
The SLATrunk function must be called in the dial plan whenever an inbound call arrives on the trunk line. Its behaviour depends on what it believes the current line status to be, but generally it will ring the associated stations according to the ring rules defined in the station definitions in SLA.CONF. This function is responsible for updating all the Stations that are subscribed for information about the line status of the selected line (using a SIP NOTIFY request).
You pass it the name label that identifies the trunk line. For example: SLATrunk(line2)
If the function is called for a trunk line that Asterisk believes is already active, it will return immediately to the next step in the dial plan and the channel variable “SLATRUNK_STATUS” will be set to “FAILURE”. If the function is called for a trunk line that it believes to be idle, then it will ring the various stations as configured in SLA.CONF until either the call is answered or the ring timeout (as defined in SLA.CONF) is reached. The channel variable SLATRUNK_STATUS will then have one of the following values: SUCCESS or RINGTIMEOUT.
EXTENSIONS.CONF: Dial plan for Outbound calls using SLA
What’s the problem?
The section of your dial plan that handles outbound calls has to be a bit clever. This is because there are several different cases that need to be handled correctly, yet they are all initially sent to the same section of the dial plan. It has to cope with internal calls and calls to PSTN numbers; recognise on-hook and off-hook dialling; recognise when the user has selected a specific outside line and when they simply want any available outside line; it also is this section that has to recognise and correctly handle other requests made using the shared line keys – for example, when the user wants to pick up or conference into an existing call on one of the shared lines.
Incidentally, Caller ID adds yet more complexity if, for some reason, you would like different trunk lines to use different caller ID’s. Fortunately, this tends not to be an issue on analogue trunks, but could be relevant when using SLA in combination with SIP trunk connections (or especially with a mixture of analogue and SIP trunks).
Which section of the dial plan is relevant for outbound call handling
When the user of an IP phone makes a call, the section of the dial plan that handles it is normally defined by the “context” parameter given in your SIP.CONF file. There may be a default context that applies to every SIP peer, but it is recommended that an explicit context is given, as shown in the following example:
Definition for IP Phones 4001 and 4003 within file SIP.CONF |
[4001]
type = friend username = 4001 callerid = “Grandstream GXP2000” <4001> secret = 123 host = dynamic context = sip-out subscribecontext = mysubscribes qualify = no call-limit=4 disallow = all allow = alaw allow = ulaw [4003] type = friend username = 4003 callerid = “Aastra 53i” <4003> secret = 123 host = dynamic context = sip-out subscribecontext = mysubscribes qualify = no call-limit=4 disallow = all allow = alaw allow = ulaw |
It is also possible that a domain specific context has been defined, in which case this setting may over-ride the explicit setting for the peer. It is important to get this right for correct sla operation so you should confirm which context is being used when the IP phone makes a call:
- Try increasing the “verbose” level to 3 and making a call from the IP phone, then check which context was used. The console output will show something like “Executing [number@context] …”
- From the Asterisk CLI (Command Line Interface) type the command “sip show users”. Look for the context name in the column “Def.Context”. However, be aware that this setting will be trumped by a different setting for the domain that the phone is registered on. You can see domain specific context settings by typing “sip show domains” at the CLI prompt and looking at the column called “Context”.
Once you are sure that calls from extension 4001 are being processed in a specific context (in the examples it is called “sip-out”), then it is ok to proceed to the next step.
Cases that must be handled in your dial plan
A new INVITE will be generated when the user presses one of the shared line keys, but one will also be generated when they dial a number. That doesn’t sound too complicated, but when you consider how different people may use a phone (or the same person at different times) then you can see that there is potential for complexity. Concentrating on the options that need to be considered for Aastra phones, the list looks like this:
- Press a shared line key, get dial tone, dial an external (PSTN) number
- Pick up handset (or press speaker phone button), get dial tone, dial number
- Press a line key, get dial tone, dial number
- Dial a number (off-hook dialling), press a line key
- Dial a number (off-hook dialling), press the Dial soft key
There are other cases (not shown above) that simply are not allowed – they will not work. In particular, you cannot dial the number then press a shared line key to make a call on a specific trunk line. Also, it would not make any sense to press a shared line key, get dial tone, then dial an internal number – you have selected a trunk line so you cannot now make a call to another extension!
Whether or not it makes a difference to dial the number first, then press the line key (as opposed to pressing the line key, then dialling the number) will depend on how you have programmed your IP phone. You may want to configure the line keys with an auto-dial number to make them behave a bit like the shared line keys. I have seen this suggested in some of the documentation for SLA and it could be useful if your users are confused by the difference between shared line keys (that use the programmable keys on the phone) and the phone’s own line keys. It would hardly be surprising if users did find this confusing.
If you program the phone’s own line keys to auto-dial an SLA name tag, then you can make them behave the same as the shared line keys, but only for a limited set of cases. Auto-dial is available on the Aastra and the adjacent screen shot shows how you might want to make use of this feature with SLA.
Other line keys would need to use SLA name tags that were appropriate for their line number. For example, line 2 would be set to auto-dial “8*4003_line2” etc.
Unfortunately, the Grandstream GXP2000 does not seem to provide an auto-dial facility, but if anyone knows better, please contact me.
Assuming you are using the Aastra phone with the auto-dial feature enabled, as discussed above, the dial plan will now need to be able to handle the following:
- Press a shared line key: a specific trunk line is seized, user hears dial tone and dials external number
- Press a phone line key: a specific trunk line is seized, user hears dial tone and dials external number
- Pick up handset (or press the speaker button): user hears dial tone and dials an internal number
- User dials an internal number (another extension) and presses a phone line key
- User dials an internal number (another extension) and presses the Dial soft key
The following cases are deliberately omitted from the list at this point because they cannot be handled correctly when autocontext is used. Techniques for handling them are discussed later:
- Pick up handset (or press the speaker button): user hears dial tone and dials an external number
- User dials a PSTN (external) number and presses a phone line key to make the call
- User dials a PSTN (external) number and presses the Dial soft key
Fortunately, several of the five cases described above will look exactly the same once they reach Asterisk, so the dial plan is a lot less complicated than you might expect. By the way, it is assumed in this example that you have assigned the same user account to all the auto-dial lines.
Cases 1 and 2 will produce identical interactions with Asterisk. Assuming the same internal number is called, cases 3, 4 and 5 will all result in an identical INVITE being sent to Asterisk. So really, there are just two different cases that must be handled. Not so bad after all.
Without autocontext: The dial plan to handle all this could be manually constructed as follows:
Dial plan snippet from EXTENSIONS.CONF |
[sip-out]
; User pressed a shared line key and will be presented with dial tone exten => 8*4001_line1,1,SLAStation(8*4001_line1) exten => 8*4001_line2,1,SLAStation(8*4001_line2) exten => 8*4003_line1,1,SLAStation(8*4003_line1) exten => 8*4003_line2,1,SLAStation(8*4003_line2) ; User dialled an internal extension (4 digit number starting with 4) exten => _4XXX,1,Dial(SIP/${EXTEN},26) exten => _4XXX,n,VoiceMail(${EXTEN}@other,b) exten => _4XXX,n,Congestion(5) |
With autocontext: If you were using autocontext for the stations, then the four lines of the dial plan that call the SLAStation function would be generated automatically, so you could simply have a dial plan like this:
Dial plan snippet from EXTENSIONS.CONF |
[sip-out]
; User dialled an internal extension (4 digit number starting with 4) exten => _4XXX,1,Dial(SIP/${EXTEN},26) exten => _4XXX,n,VoiceMail(${EXTEN}@other,b) exten => _4XXX,n,Congestion(5) |
The section of SLA.CONF that tells Asterisk to use autocontext would look like this:
[8*4003]
; Aastra 53i type=station device=SIP/4003 autocontext=sip-out ringdelay=0 ringtimeout=30 trunk=line1 trunk=line2 |
Who is generating the dial tone?
It is important to understand which piece of equipment generates the dial tone heard by the user. In cases 1 and 2, the audible dial tone is coming from Asterisk, but in case 3 the dial tone heard by the user is generated by the IP phone. When the IP phone is generating dial tone, it will send an invite to Asterisk only after the user has finished dialling. In cases 4 and 5, there is no audible dial tone because they are using “on-hook” dialling.
When Asterisk has to generate dial tone, it can do it in one of two ways:
- Using the built-in Asterisk DISA function
- By taking the physical trunk line device off-hook (only applicable to analogue trunks)
If you don’t know what DISA is, please click here
What happens inside Asterisk when a shared line key is pressed?
When the key for Shared Line 1 is pressed on the Aastra phone, it causes an INVITE to be sent to Asterisk. The target number of the INVITE will be “8*4003_line1” so it will match the corresponding line in the dial plan and call the command SLAStation(8*4003_line1). What this does depends on what you configured for the “Device” parameter of line1 in SLA.CONF. You may remember that I recommended you to set device like this:
device=Local/sla@line1-out |
By specifying a “Local” device, this actually means that Asterisk will jump to a new position in the dial plan and will start to execute the lines at that position. In the example shown above, it will jump to the context [line1-out] and try to find a step within that context matching the target extension ID – in this case, the target is set to “sla”, but it could be almost anything.
What you specify in your dial plan depends if you want to use DISA to present dial tone or if you want to take the trunk line off-hook. If it is a SIP trunk, then you have no choice – you must use the DISA function. If it is a Zap port or a Pika FXO port, then you should take it off hook as shown in the examples below:
[line1-out]
; The following three blocks show alternatives for shared line 1. ; You would only have one of them in a real dial plan. ; Select the one that matches your trunk’s hardware/technology ; For a SIP trunk, use the Asterisk DISA function to generate dial tone exten => sla,1,Disa(no-password|line1-out) ; Take Zap channel 1 off-hook. Assumes that channel 1 is an FXO port exten => sla,1,Dial(Zap/1/) ; Take Pika FXO channel 1 off-hook to allow direct dialling on the trunk exten => sla,1,Dial(PIKA/fxo/1//) ; The following is required only for the Disa mechanism used with a SIP trunk ; Calls to numbers matching PSTN numbers use the SIP trunk exten => _0XXXX.,1,Dial(SIP/${EXTEN}@provider1|50|w) ; Calls to any other number are considered invalid exten => i,1,Playback(invalid) exten => i,n,Hangup |
Why you should take the FXO trunk line off-hook
There is a major advantage in taking the trunk line off-hook as soon as the shared line key is pressed. When you present dial tone this way, there is no risk of an incoming call arriving on the trunk while the user is dialling. If, instead, you were to use the Asterisk DISA function, there would be a significant risk that an inbound call may start ringing the trunk line before the user has finished dialling. This is a situation that you really don’t want to allow – it would result in mis-handling of both the inbound and the outbound calls.
Another advantage of using the FXO “off-hook” technique is that it requires fewer entries in the dial plan and generally keeps the dial plan simpler.
Improving the dial plan to handle more cases
Using the dial plan described above, your Asterisk system will be able to handle the following dial requests from an Aastra or other suitable IP phone:
- Select a shared trunk line, hear dial tone, dial external number
- Pick up handset, hear dial tone, dial an internal number
- Dial an internal number, press the ‘Dial’ soft key (or press a phone line key)
However, it will fail if the user tries to dial an external number without first selecting one of the shared trunk lines. This would be a very annoying limitation for users – ok, you could regard it as a problem that can be addressed by suitable “user training”, but I have seen too many instances of poor equipment design being explained away with the limp excuse that the users just need to be better trained. If a user can dial an external number by pressing the line key then dialling, it is reasonable for them to expect to be able to dial and then press the line key. What you should be aiming for is a consistent and reasonably intelligent user interface – Asterisk is perfectly capable of pattern matching on dialled numbers, so how can we upgrade the dial plan to handle it correctly?
Unburden yourself from the chains of “autocontext”
Yes, this is the point where you have to abandon “autocontext”. We mentioned much earlier that autocontext was probably not worth using and if you want to take Asterisk shared line appearances to the next level, with an improved user experience, then autocontext must be disabled.
With autocontext disabled, you can now take advantage of the slightly curious naming convention we recommended for the stations in SLA.CONF. The recommendation was to prefix the extension number with “8*”. This was chosen because it allows extension patterns to be used in the dial plan. This means that a single line in the dial plan can now be used for tens or hundreds of different stations and lines. Look for the pattern “_8*.” in the samples below.
How can SLAStation offer DISA and also handle pre-dialled numbers?
This is the problem that has to solved – when a user dials a number on the IP phone, it sends an INVITE to Asterisk that must be handled in one specific dial plan context. However, depending on how the user dialled the number you may want the dial plan to do any one of the following three things:
- Use the Dial command to call an internal number – no shared trunk line is required
- Use the SLAStation command to select a specific shared trunk line, present dial tone to the user and then wait for the user to dial an external number
- Use the SLAStation command to select any available shared trunk line, then immediately place a call on that shared trunk line using a pre-dialled number
Our previous examples for the dial plan would not be able to handle the last of those three cases and it is not immediately obvious how you can use SLAStation to perform two different jobs. The trick that must be used is to set a variable before calling SLAStation and then make the dial plan inspect the variable just after SLAStation has been called.
We have also made an assumption that the Caller ID number of the IP phone that is making the call is preset to the correct extension number of that device. This data is then used as part of the parameter passed to the SLAStation function. If your IP phones are not configured with their extension number as the caller ID, then you would have to find some other way of identifying which extension is making the call and ensuring that the correct parameter is passed to SLAStation.
The code for the dial plan now looks like this:
[sip-out]
; User pressed a shared line key and will be presented with dial tone exten => _8*.,1,Set(_MYDATA=disa) exten => _8*.,n,SLAStation(${EXTEN}) ; User dialled an internal extension (4 digit number starting with 4) exten => _4XXX,1,Dial(SIP/${EXTEN},26) exten => _4XXX,n,VoiceMail(${EXTEN}@other,b) exten => _4XXX,n,Congestion(5) ; User dialled an external number – grab any free shared trunk line exten => _0XXXX.,1,Set(_MYDATA=${EXTEN}) exten => _0XXXX.,n,SLAStation(8*${CALLERID(num)}); Handle unrecognised number dialled exten => i,1,Answer() exten => i,2,Playback(invalid) exten => i,3,Hangup[line1-out] ; this context is used to process outbound calls on shared line 1 (Zap FXO) exten => sla,1,Gotoif($[${MYDATA} = disa] ?disa:data) ; disa – wait for caller to dial a number (presents trunk dial tone as prompt) exten => sla,n(disa),Dial(Zap/1/) exten => sla,n,Hangup ; data – user has already dialled a number and SLAStation has selected this line exten => sla,n(data),Dial(Zap/1/${MYDATA},40) exten => sla,n,Congestion(5)[line2-out] ; this context is used to process outbound calls on shared line 2 (Pika FXO) exten => sla,1,Gotoif($[${MYDATA} = disa] ?disa:data) ; disa – wait for caller to dial a number (presents trunk dial tone as prompt) exten => sla,n(disa),Dial(Pika/FXO/2//) exten => sla,n,Hangup ; data – user has already dialled a number and SLAStation has selected this line exten => sla,n(data),Dial(Pika/FXO/2/${MYDATA},40) exten => sla,n,Congestion(5)[line3-out] ; this context is used to process outbound calls on shared line 3 (SIP Provider1) exten => sla,1,Gotoif($[${MYDATA} = disa] ?disa:data) ; disa – wait for caller to dial a number (presents dial tone as prompt) exten => sla,n(disa),Disa(no-password,line3-out) exten => sla,n,Hangup ; data – user has already dialled a number and SLAStation has selected this line ; handle the results from the Asterisk Disa function |
Inside the [line1-out] and [line2-out] contexts the variable is tested. If it has been set to “disa” then the trunk is taken off-hook using the appropriate parameter in the Dial command. Otherwise the variable is assumed to contain the pre-dialled number and a call is placed on the line using the stored number.
Inside the [line3-out] context the variable is tested. If it has been set to “disa” then the Asterisk DISA function is called, but otherwise it is assumed to contain the pre-dialled number and a call is placed on the SIP line using the stored number. Extra dial plan code is required in this context to handle the results from the Asterisk Disa function – only calls to external numbers are permitted using Disa because the SLA mechanism has already allocated the trunk line.
Asterisk function SLAStation()
The SLAStation function must be called in the dial plan whenever the Programmed Key on the IP phone is pressed. It effectively seizes a Trunk Line on behalf of the Station that made the request, but its exact behaviour depends if the line is already in use. It is responsible for updating all the Stations that are subscribed for information about the line status of the selected line (using a SIP NOTIFY request).
You pass it the name label combination that identifies which key was pressed. For example:
exten => 8*4001_line2,1,SLAStation(8*4001_line2)
You can also pass it a name label that only identifies the IP Phone, in which case Asterisk will check each of the associated lines in sequence until it finds one that is free. This is recommended as a mechanism for general purpose dialling from your IP phone, such as when the phone is taken off-hook.
exten => 8*4001_call,1,SLAStation(8*4001)
If the function is called for a specific line and that line is idle, then the line is marked as “in-use” and is effectively reserved for that Station’s use. If the function is called for a specific line and the line is ringing, then it does a pickup of the ringing line. If the function is called for a specific line and the line is already “in-use” then the Station that initiated the request will join the call in a conference (it uses meetme).
For part 4 of this article, click here
3 thoughts on “Asterisk SLA (Shared Line Appearances) – Part 3”
Comments are closed.