Idea to have 2 instances of Freeswitch in Master-Slave mode, if one node dies, second will automatically takeover calls with 2-3 seconds lag. Yep, media would be restored.
Main scheme is looks like this
We need 3 IP's. 2 for nodes and 1 for floating.
Solution based on this, but with a bit of additional config.
Used Debian 8x64 as a host OS.
Assume, that we have FreeSWITCH'es already installed.
Install keepalived on both nodes.
apt-get install keepalived
Create files on both nodes
/etc/keepalived/keepalived.conf
global_defs {
router_id FREESW
}
vrrp_script chk_fs {
script "/etc/keepalived/scripts/ka-status.pl"
interval 1
}
vrrp_instance VI_FREESW {
# replace with SLAVE on a slave node
state MASTER
# change to SLAVE on slave node
interface <YOUR_INTERFACE_HERE>
#interface eth0
virtual_router_id <YOUR_ROUTER_ID>
# virtual_router_id 15
# higher is preferred for master
# disable to have failover be sticky
priority 1
advert_int 1
unicast_src_ip <CURRENT_NODE_IP>
#unicast_src_ip 192.168.10.10
unicast_peer {
<SLAVE_NODE_IP>
#192.168.10.11
}
authentication {
auth_type PASS
auth_pass YourPassHere
}
notify "/etc/keepalived/scripts/ka-notify.pl"
virtual_ipaddress {
<FLOATING_IP/CIDR> dev <YOUR_INTERFACE_HERE>
# 192.168.0.15/24 dev eth0 - Example
}
track_script {
chk_fs
}
}
/etc/keepalived/scripts/ka-notify.pl
#!/usr/bin/perl
# INSTANCE|VI_FREESW|BACKUP|50
my ($what,$id,$state,$prio) = @ARGV;
open(STDOUT, "|/usr/bin/logger -t ka-notify");
print "what($what) id($id) state($state) prio($prio)\n";
if ( $state eq "MASTER" )
{
print "Instance went to master, issuing sofia recover.\n";
system("/usr/bin/fs_cli", "-x", "sofia recover");
}
/etc/keepalived/scripts/ka-status.pl
#!/usr/bin/perl
use Sys::Syslog;
openlog "ka-status", "ndelay,pid", "local0";
my @required = ("internal", "external");
my %saw = ();
open(my $in, "-|") || exec("/usr/bin/fs_cli", "-x", "sofia xmlstatus");
while ( defined(my $line = <$in>) )
{
if ( $line =~ m|<name>(.*)</name>|o )
{
$saw{$1} = 1;
}
}
close($in);
foreach my $profile ( @required )
{
if ( ! $saw{$profile} )
{
syslog(LOG_INFO, "sip profile $profile not found, marking failure");
exit(1);
}
}
exit(0);
chmod +x /etc/keepalived/scripts/*.pl
echo "net.ipv4.ip_nonlocal_bind = 1" >> /etc/sysctl.conf
Make sure, that FreeSWITCH'es uses same runtime database and uses same hostname in switch.conf Also profiles you control, need to listen on FLOATING_IP address.
Better reboot here.
service keepalived start
To check state, best is to use ip addr show. On which node FLOATING_IP address is, this is master :)
You can play with priority parameter in keepalived.conf file to make one node Master at all cases.
Point, there is a issue, when you restore calls on one node, you can't get same calls back with sofia recover.
Wednesday, August 9, 2017
FusionPBX/FreeSWITCH save CLID on transfer
Idea is when call is received, transfer to next destination should come with callerID was received originally, not updated in moment of transfer. Sometimes it's needed for correct CRM integration or peoples just get used to it, cause it's default with blind transfer.
Actually, same ideas that described here, but for use in FusionPBX.
1. Create context save_transfer with order ~85
condition - <empty>
action - export - nolocal:execute_on_answer_1=lua number_save_on_transfer_store.lua
action - export - api_hangup_hook=lua number_save_on_transfer_db_cleanup.lua
2. Create context restore_transfer with order higher, than save_trasfer, ~80
condition - ${db(exists/number_transfer_store/${sip_from_user})} - ^true$
action - set - restored_number_on_transfer=${db(select/number_transfer_store/${sip_from_user})} - inline
action - set - effective_caller_id_number=${restored_number_on_transfer}
action - set - effective_caller_id_name=${restored_number_on_transfer}
action - db - delete/number_transfer_store/${sip_from_user}
condition - ${db(exists/number_transfer_store/${sip_from_user}_name)} - ^true$
action - set - restored_number_on_transfer_name=${db(select/number_transfer_store/${sip_from_user}_name)} - inline
action - set - effective_caller_id_name=${restored_number_on_transfer_name}
action - db - delete/number_transfer_store/${sip_from_user}_name
Lua files:
/usr/share/freeswitch/scripts/number_save_on_transfer_store.lua
-- Save number_answered / original caller_id to database
--api = freeswitch.API()
if (session:ready()) then
answered_extension = session:getVariable("dialed_user")
caller_id = session:getVariable("restored_number_on_transfer")
caller_name = session:getVariable("restored_number_on_transfer_name")
if (caller_id == nil) then
caller_id = session:getVariable("sip_from_user")
end
if (caller_name == nil) then
caller_name = session:getVariable("sip_from_display")
end
if (answered_extension ~= nil and caller_id ~= nil) then
freeswitch.consoleLog("INFO", "[NUMBER_ON_TRANSFER_SAVE] Got answered call from "..caller_id.." to "..answered_extension.."\n")
session:execute('db', 'insert/number_transfer_store/'..answered_extension..'/'..caller_id)
if (caller_name ~= nil) then
session:execute('db', 'insert/number_transfer_store/'..answered_extension..'_name/'..caller_name)
end
end
end
/usr/share/freeswitch/scripts/number_save_on_transfer_db_cleanup.lua
-- Cleanup database
api = freeswitch.API()
sip_to_user = env:getHeader("variable_last_sent_callee_id_number")
if (sip_to_user ~= nil) then
--serialized = env:serialize()
--freeswitch.consoleLog("INFO","[hangup]\n" .. serialized .. "\n")
freeswitch.consoleLog("INFO", "[DB_CLEANUP] Cleaning " .. sip_to_user .. "\n")
api:executeString('db delete/number_transfer_store/'..sip_to_user)
api:executeString('db delete/number_transfer_store/'..sip_to_user..'_name')
end
P.S.: At the end, db is can be easily replaced with hash. As we don't need persistence storage here.
Actually, same ideas that described here, but for use in FusionPBX.
1. Create context save_transfer with order ~85
condition - <empty>
action - export - nolocal:execute_on_answer_1=lua number_save_on_transfer_store.lua
action - export - api_hangup_hook=lua number_save_on_transfer_db_cleanup.lua
2. Create context restore_transfer with order higher, than save_trasfer, ~80
condition - ${db(exists/number_transfer_store/${sip_from_user})} - ^true$
action - set - restored_number_on_transfer=${db(select/number_transfer_store/${sip_from_user})} - inline
action - set - effective_caller_id_number=${restored_number_on_transfer}
action - set - effective_caller_id_name=${restored_number_on_transfer}
action - db - delete/number_transfer_store/${sip_from_user}
condition - ${db(exists/number_transfer_store/${sip_from_user}_name)} - ^true$
action - set - restored_number_on_transfer_name=${db(select/number_transfer_store/${sip_from_user}_name)} - inline
action - set - effective_caller_id_name=${restored_number_on_transfer_name}
action - db - delete/number_transfer_store/${sip_from_user}_name
Lua files:
/usr/share/freeswitch/scripts/number_save_on_transfer_store.lua
-- Save number_answered / original caller_id to database
--api = freeswitch.API()
if (session:ready()) then
answered_extension = session:getVariable("dialed_user")
caller_id = session:getVariable("restored_number_on_transfer")
caller_name = session:getVariable("restored_number_on_transfer_name")
if (caller_id == nil) then
caller_id = session:getVariable("sip_from_user")
end
if (caller_name == nil) then
caller_name = session:getVariable("sip_from_display")
end
if (answered_extension ~= nil and caller_id ~= nil) then
freeswitch.consoleLog("INFO", "[NUMBER_ON_TRANSFER_SAVE] Got answered call from "..caller_id.." to "..answered_extension.."\n")
session:execute('db', 'insert/number_transfer_store/'..answered_extension..'/'..caller_id)
if (caller_name ~= nil) then
session:execute('db', 'insert/number_transfer_store/'..answered_extension..'_name/'..caller_name)
end
end
end
/usr/share/freeswitch/scripts/number_save_on_transfer_db_cleanup.lua
-- Cleanup database
api = freeswitch.API()
sip_to_user = env:getHeader("variable_last_sent_callee_id_number")
if (sip_to_user ~= nil) then
--serialized = env:serialize()
--freeswitch.consoleLog("INFO","[hangup]\n" .. serialized .. "\n")
freeswitch.consoleLog("INFO", "[DB_CLEANUP] Cleaning " .. sip_to_user .. "\n")
api:executeString('db delete/number_transfer_store/'..sip_to_user)
api:executeString('db delete/number_transfer_store/'..sip_to_user..'_name')
end
P.S.: At the end, db is can be easily replaced with hash. As we don't need persistence storage here.
Subscribe to:
Posts (Atom)