RFC Compliant ENUM Macro
Originally written by Rob Thomas (xrobau@gmail.com). Questions to him!
Set 'DIAL_NUMBER' to be the number you wish to do an enumlookup on, and hand it to this.
NOTE: If you want to be more compliant with the URL's for E164 Servers, replace the '-' with spaces ' ' and then on lines with CUT put a space between the two comma's
So CUT(E164NETWORKS,,1) becomes CUT(E164NETWORKS, ,1)
And CUT(E164NETWORKS,,2-) becomes CUT(E164NETWORKS, ,2-)
5/24/2007 NOTE: in * V 1.4 ENUMLOOKUP has 5 parms not 4 so its been fixed below
30-Nov-07 NOTE: Fails to compare correctly in unless quotes removed in "${LEN(${E164NETWORKS})}" < "2" comparison below - fixed
16-Jun-08 NOTE: Fixed for other numeric comparisons also.
[macro-enumdial]
exten => s,1,Set(DIAL_NUMBER=${ARG1})
exten => s,n,Set(E164NETWORKS=e164.arpa-e164.info-e164.org)
exten => s,n,GotoIf($[${DIAL_NUMBER:0:1} = "+"]?begin) ; Skip next line if it already is prefixed by a plus
exten => s,n,Set(DIAL_NUMBER=+${DIAL_NUMBER}) ; Add a plus to the start, becasue ENUMLOOKUP needs it.
; Checking here to see if there are any e164 networks left to check.
exten => s,n(begin),GotoIf($[${LEN(${E164NETWORKS})} < 2]?failed)
; There are, so we take the first one
exten => s,n,Set(ENUMNET=${CUT(E164NETWORKS,,1)})
; And trim it from the front of E164NETWORKS
exten => s,n,Set(E164NETWORKS=${CUT(E164NETWORKS,,2-)})
; OK, this is now quite complex. To remain compliant, we have to iterate
; through, in order, the returned records. Since we want to make this
; call over the network, we can ignore tel: lines. Even if it's first
; priority.
exten => s,n,Set(ENUMCOUNT=${ENUMLOOKUP(${DIAL_NUMBER},ALL,c,,${ENUMNET})})
; Documentation is wrong. It can return nothing if the enum lookup fails. Grr.
; Now the count may be zero, so if it is, check the next network
exten => s,n,GotoIf($["${ENUMCOUNT}" = "0"]?begin)
exten => s,n,GotoIf($["x${ENUMCOUNT}" = "x"]?begin)
; Now, let's start through them.
exten => s,n,Set(ENUMPTR=1)
exten => s,n(startloop),Set(ENUM=${ENUMLOOKUP(${DIAL_NUMBER},ALL,,${ENUMPTR},${ENUMNET})})
; Sanity check the return, make sure there's something in there.
exten => s,n,GotoIf($[${LEN(${ENUM})} = 0]?continue)
exten => s,n,GotoIf($["${ENUM:0:3}" = "iax"]?iaxuri)
exten => s,n,GotoIf($["${ENUM:0:3}" = "sip"]?sipuri)
; It doesn't matter if you don't have h323 enabled, as when it tries to dial, it cares
; about dialstatus and retries if there are any enum results left.
exten => s,n,GotoIf($[${ENUM:0:3} = "h32"]?h323uri)
; If we're here, it's not a protocol we know about. Let's increment the pointer
; and if it's more than ENUMCOUNT, we know we've run out of options. Try the
; next e164 network.
exten => s,n(continue),Set(ENUMPTR=$[${ENUMPTR} + 1])
exten => s,n,GotoIf($[${ENUMPTR} > ${ENUMCOUNT}]?begin)
; OK. If we're here, we've still got some enum entries to go through. Back to
; the start with you!
exten => s,n,Goto(startloop)
; If the prefix is 'sip:'...
exten => s,n(sipuri),Set(DIALSTR=SIP/${ENUM:4})
exten => s,n,Goto(dodial)
; If it's IAX2...
exten => s,n(iaxuri),Set(DIALSTR=IAX2/${ENUM:5})
exten => s,n,Goto(dodial)
; Or even if it's H323.
exten => s,n(h323uri),Set(DIALSTR=H323/${ENUM:5})
exten => s,n(dodial),Dial(${DIALSTR})
exten => s,n,NoOp(Dial exited in macro-enum-dialout with ${DIALSTATUS})
; Now, if we're still here, that means the Dial failed for some reason.
; If it's CONGESTION or CHANUNAVAIL we probably want to try again on a
; different channel. However, if it's the last one, we don't have any
; left, and I didn't keep any previous dialstatuses, so hopefully
; someone looking throught the logs would have seen the NoOp's
exten => s,n,GotoIf($[${ENUMPTR} = ${ENUMCOUNT}]?noneleft)
exten => s,n,GotoIf($[$[${DIALSTATUS} = "CHANUNAVAIL"] | $[${DIALSTATUS} = "CONGESTION"] ]?continue)
; If we're here, then it's BUSY or NOANSWER or something and well, deal with it.
exten => s,n(noneleft),Goto(s-${DIALSTATUS},1)
; Here are the exit points for the macro.
exten => s,n(failed),NoOp(EnumLookups failed)
exten => s,n,Goto(end)
exten => s,n(nochans),NoOp(max channels used up)
exten => s,n(end),NoOp(Exiting macro-dialout-enum)
exten => s-BUSY,1,NoOp(Trunk is reporting BUSY)
exten => s-BUSY,2,Busy()
exten => s-BUSY,3,Wait(60)
exten => s-BUSY,4,NoOp()
exten => _s-.,1,NoOp(Dial failed due to ${DIALSTATUS})
An AEL version, by Luke-Jr <luke@dashjr.org>:
macro route-enum(exten, IsPOTS, Timeout, DialOpts) {
// based on http://www.voip-info.org/wiki/view/RFC+Compliant+ENUM+Macro
Set(DIAL_NUMBER=${exten});
Set(E164NETWORKS=e164.arpa-e164.info-e164.org);
if ("${ENUMDEPTH}" = "")
ENUMDEPTH=0;
ENUMDEPTH=1 + ${ENUMDEPTH};
if (${ENUMDEPTH} > 5) { // max enum depth we will go
// This is to prevent a looping enum entry; there is no other safe way to be sure we don't get stuck in such a trap.
Set(DIALSTATUS=EnumMaxDepth);
goto end;
};
if ("${DIAL_NUMBER:0:1}" != "+")
Set(DIAL_NUMBER=+${DIAL_NUMBER}); // Add a plus to the start, becasue ENUMLOOKUP needs it.
// Checking here to see if there are any e164 networks left to check.
for (&iterator(${E164NETWORKS},ENUMNET); "${ENUMNET}" != ""; &iterate(ENUMNET)) {
// OK, this is now quite complex. To remain compliant, we have to iterate through, in order, the returned records.
// Since we want to make this call over the network, we can ignore tel: lines. Even if it's first priority.
Set(ENUMCOUNT=${ENUMLOOKUP(${DIAL_NUMBER},ALL,c,,${ENUMNET})});
// Documentation is wrong. It can return nothing if the enum lookup fails. Grr.
// Now the count may be zero, so if it is, check the next network
if ("${ENUMCOUNT}" = "")
ENUMCOUNT=0;
// Now, let's start through them.
for (ENUMPTR=1; ${ENUMPTR} <= ${ENUMCOUNT}; ENUMPTR=${ENUMPTR} + 1) {
Set(ENUM=${ENUMLOOKUP(${DIAL_NUMBER},ALL,,${ENUMPTR},${ENUMNET})});
// Sanity check the return, make sure there's something in there.
if (${LEN(${ENUM})} = 0)
continue;
Set(DIALSTR=);
Set(DIALSTATUS=);
switch(${CUT(ENUM,:,1)}) {
case sip:
// If the prefix is 'sip:'...
Set(DIALSTR=SIP/${ENUM:4});
break;
case iax2:
// If it's IAX2...
Set(DIALSTR=IAX2/${ENUM:5});
break;
case h323:
// It doesn't matter if you don't have h323 enabled, as when it tries to dial,
// it cares about dialstatus and retries if there are any enum results left.
// Or even if it's H323.
Set(DIALSTR=H323/${ENUM:5});
break;
case tel:
// redirect to another number... potentially dangerous (looping)
&enumtest(${ENUM:4});
break;
default:
// If we're here, it's not a protocol we know about. Let's increment the pointer and
// if it's more than ENUMCOUNT, we know we've run out of options. Try the next e164 network.
Set(DIALSTATUS=CHANUNAVAIL);
break;
};
if ("${DIALSTR}" != "") {
Dial(${DIALSTR},${Timeout},o${DialOpts});
NoOp(Dial exited in enumtest with ${DIALSTATUS});
};
// Now, if we're still here, that means the Dial failed for some reason.
// If it's CONGESTION or CHANUNAVAIL we probably want to try again on a different channel.
// However, if it's the last one, we don't have any left, and I didn't keep any previous dialstatuses,
// so hopefully someone looking throught the logs would have seen the NoOp's
if ("${DIALSTATUS}" != "CONGESTION" & "${DIALSTATUS}" != "CHANUNAVAIL") {
NoOp(Enum Dial(${DIALSTR}) failed due to ${DIALSTATUS});
goto end;
};
};
};
end:
NoOp(EnumLookups failed);
};
macro iterate(varname) {
current=${${varname}_current} + 1;
SET(${varname}=${CUT(${varname}_choices,,${current})});
SET(${varname}_current=${current});
};
macro iterator(choices, varname) {
SET(${varname}_choices=${choices});
SET(${varname}_current=0);
&iterate(${varname});
};
Set 'DIAL_NUMBER' to be the number you wish to do an enumlookup on, and hand it to this.
NOTE: If you want to be more compliant with the URL's for E164 Servers, replace the '-' with spaces ' ' and then on lines with CUT put a space between the two comma's
So CUT(E164NETWORKS,,1) becomes CUT(E164NETWORKS, ,1)
And CUT(E164NETWORKS,,2-) becomes CUT(E164NETWORKS, ,2-)
5/24/2007 NOTE: in * V 1.4 ENUMLOOKUP has 5 parms not 4 so its been fixed below
30-Nov-07 NOTE: Fails to compare correctly in unless quotes removed in "${LEN(${E164NETWORKS})}" < "2" comparison below - fixed
16-Jun-08 NOTE: Fixed for other numeric comparisons also.
[macro-enumdial]
exten => s,1,Set(DIAL_NUMBER=${ARG1})
exten => s,n,Set(E164NETWORKS=e164.arpa-e164.info-e164.org)
exten => s,n,GotoIf($[${DIAL_NUMBER:0:1} = "+"]?begin) ; Skip next line if it already is prefixed by a plus
exten => s,n,Set(DIAL_NUMBER=+${DIAL_NUMBER}) ; Add a plus to the start, becasue ENUMLOOKUP needs it.
; Checking here to see if there are any e164 networks left to check.
exten => s,n(begin),GotoIf($[${LEN(${E164NETWORKS})} < 2]?failed)
; There are, so we take the first one
exten => s,n,Set(ENUMNET=${CUT(E164NETWORKS,,1)})
; And trim it from the front of E164NETWORKS
exten => s,n,Set(E164NETWORKS=${CUT(E164NETWORKS,,2-)})
; OK, this is now quite complex. To remain compliant, we have to iterate
; through, in order, the returned records. Since we want to make this
; call over the network, we can ignore tel: lines. Even if it's first
; priority.
exten => s,n,Set(ENUMCOUNT=${ENUMLOOKUP(${DIAL_NUMBER},ALL,c,,${ENUMNET})})
; Documentation is wrong. It can return nothing if the enum lookup fails. Grr.
; Now the count may be zero, so if it is, check the next network
exten => s,n,GotoIf($["${ENUMCOUNT}" = "0"]?begin)
exten => s,n,GotoIf($["x${ENUMCOUNT}" = "x"]?begin)
; Now, let's start through them.
exten => s,n,Set(ENUMPTR=1)
exten => s,n(startloop),Set(ENUM=${ENUMLOOKUP(${DIAL_NUMBER},ALL,,${ENUMPTR},${ENUMNET})})
; Sanity check the return, make sure there's something in there.
exten => s,n,GotoIf($[${LEN(${ENUM})} = 0]?continue)
exten => s,n,GotoIf($["${ENUM:0:3}" = "iax"]?iaxuri)
exten => s,n,GotoIf($["${ENUM:0:3}" = "sip"]?sipuri)
; It doesn't matter if you don't have h323 enabled, as when it tries to dial, it cares
; about dialstatus and retries if there are any enum results left.
exten => s,n,GotoIf($[${ENUM:0:3} = "h32"]?h323uri)
; If we're here, it's not a protocol we know about. Let's increment the pointer
; and if it's more than ENUMCOUNT, we know we've run out of options. Try the
; next e164 network.
exten => s,n(continue),Set(ENUMPTR=$[${ENUMPTR} + 1])
exten => s,n,GotoIf($[${ENUMPTR} > ${ENUMCOUNT}]?begin)
; OK. If we're here, we've still got some enum entries to go through. Back to
; the start with you!
exten => s,n,Goto(startloop)
; If the prefix is 'sip:'...
exten => s,n(sipuri),Set(DIALSTR=SIP/${ENUM:4})
exten => s,n,Goto(dodial)
; If it's IAX2...
exten => s,n(iaxuri),Set(DIALSTR=IAX2/${ENUM:5})
exten => s,n,Goto(dodial)
; Or even if it's H323.
exten => s,n(h323uri),Set(DIALSTR=H323/${ENUM:5})
exten => s,n(dodial),Dial(${DIALSTR})
exten => s,n,NoOp(Dial exited in macro-enum-dialout with ${DIALSTATUS})
; Now, if we're still here, that means the Dial failed for some reason.
; If it's CONGESTION or CHANUNAVAIL we probably want to try again on a
; different channel. However, if it's the last one, we don't have any
; left, and I didn't keep any previous dialstatuses, so hopefully
; someone looking throught the logs would have seen the NoOp's
exten => s,n,GotoIf($[${ENUMPTR} = ${ENUMCOUNT}]?noneleft)
exten => s,n,GotoIf($[$[${DIALSTATUS} = "CHANUNAVAIL"] | $[${DIALSTATUS} = "CONGESTION"] ]?continue)
; If we're here, then it's BUSY or NOANSWER or something and well, deal with it.
exten => s,n(noneleft),Goto(s-${DIALSTATUS},1)
; Here are the exit points for the macro.
exten => s,n(failed),NoOp(EnumLookups failed)
exten => s,n,Goto(end)
exten => s,n(nochans),NoOp(max channels used up)
exten => s,n(end),NoOp(Exiting macro-dialout-enum)
exten => s-BUSY,1,NoOp(Trunk is reporting BUSY)
exten => s-BUSY,2,Busy()
exten => s-BUSY,3,Wait(60)
exten => s-BUSY,4,NoOp()
exten => _s-.,1,NoOp(Dial failed due to ${DIALSTATUS})
An AEL version, by Luke-Jr <luke@dashjr.org>:
macro route-enum(exten, IsPOTS, Timeout, DialOpts) {
// based on http://www.voip-info.org/wiki/view/RFC+Compliant+ENUM+Macro
Set(DIAL_NUMBER=${exten});
Set(E164NETWORKS=e164.arpa-e164.info-e164.org);
if ("${ENUMDEPTH}" = "")
ENUMDEPTH=0;
ENUMDEPTH=1 + ${ENUMDEPTH};
if (${ENUMDEPTH} > 5) { // max enum depth we will go
// This is to prevent a looping enum entry; there is no other safe way to be sure we don't get stuck in such a trap.
Set(DIALSTATUS=EnumMaxDepth);
goto end;
};
if ("${DIAL_NUMBER:0:1}" != "+")
Set(DIAL_NUMBER=+${DIAL_NUMBER}); // Add a plus to the start, becasue ENUMLOOKUP needs it.
// Checking here to see if there are any e164 networks left to check.
for (&iterator(${E164NETWORKS},ENUMNET); "${ENUMNET}" != ""; &iterate(ENUMNET)) {
// OK, this is now quite complex. To remain compliant, we have to iterate through, in order, the returned records.
// Since we want to make this call over the network, we can ignore tel: lines. Even if it's first priority.
Set(ENUMCOUNT=${ENUMLOOKUP(${DIAL_NUMBER},ALL,c,,${ENUMNET})});
// Documentation is wrong. It can return nothing if the enum lookup fails. Grr.
// Now the count may be zero, so if it is, check the next network
if ("${ENUMCOUNT}" = "")
ENUMCOUNT=0;
// Now, let's start through them.
for (ENUMPTR=1; ${ENUMPTR} <= ${ENUMCOUNT}; ENUMPTR=${ENUMPTR} + 1) {
Set(ENUM=${ENUMLOOKUP(${DIAL_NUMBER},ALL,,${ENUMPTR},${ENUMNET})});
// Sanity check the return, make sure there's something in there.
if (${LEN(${ENUM})} = 0)
continue;
Set(DIALSTR=);
Set(DIALSTATUS=);
switch(${CUT(ENUM,:,1)}) {
case sip:
// If the prefix is 'sip:'...
Set(DIALSTR=SIP/${ENUM:4});
break;
case iax2:
// If it's IAX2...
Set(DIALSTR=IAX2/${ENUM:5});
break;
case h323:
// It doesn't matter if you don't have h323 enabled, as when it tries to dial,
// it cares about dialstatus and retries if there are any enum results left.
// Or even if it's H323.
Set(DIALSTR=H323/${ENUM:5});
break;
case tel:
// redirect to another number... potentially dangerous (looping)
&enumtest(${ENUM:4});
break;
default:
// If we're here, it's not a protocol we know about. Let's increment the pointer and
// if it's more than ENUMCOUNT, we know we've run out of options. Try the next e164 network.
Set(DIALSTATUS=CHANUNAVAIL);
break;
};
if ("${DIALSTR}" != "") {
Dial(${DIALSTR},${Timeout},o${DialOpts});
NoOp(Dial exited in enumtest with ${DIALSTATUS});
};
// Now, if we're still here, that means the Dial failed for some reason.
// If it's CONGESTION or CHANUNAVAIL we probably want to try again on a different channel.
// However, if it's the last one, we don't have any left, and I didn't keep any previous dialstatuses,
// so hopefully someone looking throught the logs would have seen the NoOp's
if ("${DIALSTATUS}" != "CONGESTION" & "${DIALSTATUS}" != "CHANUNAVAIL") {
NoOp(Enum Dial(${DIALSTR}) failed due to ${DIALSTATUS});
goto end;
};
};
};
end:
NoOp(EnumLookups failed);
};
macro iterate(varname) {
current=${${varname}_current} + 1;
SET(${varname}=${CUT(${varname}_choices,,${current})});
SET(${varname}_current=${current});
};
macro iterator(choices, varname) {
SET(${varname}_choices=${choices});
SET(${varname}_current=0);
&iterate(${varname});
};
Comments
333Re: double commas typo
Basicly a field was split from the options into its own field causing an extra comma to be required in later versions iirc.
Mike
333possible bug
exten => s,n(begin),GotoIf($"${LEN(${E164NETWORKS})}" < "2"?failed)
to:
exten => s,n(begin),GotoIf($${LEN(${E164NETWORKS})} < 2?failed)
333ENUMCOUNT
333double commas typo
There is a double commas typo.
The following lines:
exten => s,n,Set(ENUMCOUNT=${ENUMLOOKUP(${DIAL_NUMBER},ALL,c,,${ENUMNET})})
exten => s,n(startloop),Set(ENUM=${ENUMLOOKUP(${DIAL_NUMBER},ALL,,${ENUMPTR},${ENUMNET})})
Should be changed to:
exten => s,n,Set(ENUMCOUNT=${ENUMLOOKUP(${DIAL_NUMBER},ALL,c,${ENUMNET})})
exten => s,n(startloop),Set(ENUM=${ENUMLOOKUP(${DIAL_NUMBER},ALL,${ENUMPTR},${ENUMNET})})
333
; Since we want to make this
; call over the network, we can ignore tel: lines. Even if it's first
; priority.
Actually, according to RFC3966 a "tel:" URI does not imply "PSTN call": it just identifies in a globally unique and absolute way (E.164 format) a resource of type "telephone number". In fact, it might just represent an alias, to be fed back to the ENUM resolver. This means that the implementation provided here should recurse on any "tel:" line found, of course paying attention to avoid possible loops (good luck :-) )...
333New ENUMLOOKUP format