ngcp-panel/lib/NGCP/Panel/Controller/CallRouting.pm

543 lines
21 KiB

package NGCP::Panel::Controller::CallRouting;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use parent 'Catalyst::Controller';
use NGCP::Panel::Form;
use NGCP::Panel::Utils::Navigation;
use NGCP::Panel::Utils::Subscriber;
use NGCP::Panel::Utils::Peering;
use Sys::Hostname;
sub auto :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) {
my ($self, $c) = @_;
$c->log->debug(__PACKAGE__ . '::auto');
NGCP::Panel::Utils::Navigation::check_redirect_chain(c => $c);
return 1;
}
sub root :PathPart('/') :CaptureArgs(0) {
my ( $self, $c ) = @_;
}
sub callroutingverify :Chained('/') :PathPart('callroutingverify') :Args(0) {
my ( $self, $c ) = @_;
my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::CallRouting::Verify", $c);
my $params = merge({}, $c->session->{created_objects});
my $posted = ($c->req->method eq 'POST');
my $data = $c->req->params;
my @log;
$form->process(
posted => $posted,
params => $c->request->params,
item => $params,
);
unless ($posted && $form->validated) {
$c->stash(
template => 'callrouting/verify.tt',
form => $form,
);
return;
}
# TODO: relocate the logic to a common module that can be centralised and
# reused by other components
# caller/callee general parsing
# remove leading/trailing spaces
# save the domain part
foreach my $type (qw(caller callee)) {
$data->{$type} =~ s/(^\s+|\s+$)//g;
$data->{$type} =~ s/^sip://;
if ($data->{$type} =~ s/\@(.+)$//) {
$data->{$type.'_domain'} = $1;
}
}
# caller lookup
if ($data->{caller_subscriber_id}) {
my $rs = $c->model('DB')->resultset('voip_subscribers')->search({
id => $data->{caller_subscriber_id},
});
unless ($rs->first) {
push @log, sprintf "no caller subscriber found with id %d",
$data->{caller_subscriber_id};
goto RESULT;
}
$data->{caller_subscriber} = $rs->first;
} elsif ($data->{caller_peer_id}) {
my $rs = $c->model('DB')->resultset('voip_peer_groups')->search({
id => $data->{caller_peer_id},
});
unless ($rs->first) {
push @log, sprintf "no caller peer found with id %d",
$data->{caller_peer_id};
goto RESULT;
}
$data->{caller_peer} = $rs->first;
unless ($data->{caller_peer}->voip_peer_hosts->first) {
push @log, sprintf "caller peer with id %d does not contain any peer hosts",
$rs->id;
}
$data->{caller_peer_host} = $data->{caller_peer}->voip_peer_hosts->first;
} else {
my $caller_uri = $data->{caller_domain}
? 'sip:'.$data->{caller}.'@'.$data->{caller_domain}
: $data->{caller};
push @log, sprintf "no caller subscriber/peer was specified, using subscriber lookup based on caller %s",
$caller_uri;
$data->{caller_subscriber} =
NGCP::Panel::Utils::Subscriber::lookup(
c => $c,
lookup => $caller_uri
);
}
if ($data->{caller_subscriber}) {
$data->{caller_subscriber_id} = $data->{caller_subscriber}->id;
my $caller_uri = sprintf 'sip:%s@%s',
$data->{caller_subscriber}->username,
$data->{caller_subscriber}->domain->domain;
$data->{caller_domain} = $data->{caller_subscriber}->domain->domain;
push @log, sprintf "found caller subscriber '%s' with id %d",
$caller_uri, $data->{caller_subscriber_id};
} else {
push @log, sprintf "no caller subscriber found.";
goto RESULT;
}
# caller sum up
push @log, sprintf "call from %s", $data->{caller};
$log[-1] .= $data->{caller_subscriber}
? sprintf " using subscriber '%s\@%s' id %s",
$data->{caller_subscriber}->username,
$data->{caller_subscriber}->domain->domain,
$data->{caller_subscriber}->id
: sprintf " using peer '%s' id %s",
$data->{caller_peer}->name,
$data->{caller_peer}->id;
if ($data->{caller_peer_host}) {
$log[-1] .= sprintf " and peer host %s (ip: %s) with id %d",
$data->{caller_peer_host}->name,
$data->{caller_peer_host}->ip,
$data->{caller_peer_host}->id;
}
# caller inbound rewrite rules lookup
if ($data->{caller_rewrite_id}) {
my $rs = $c->model('DB')->resultset('voip_rewrite_rule_sets')->search({
id => $data->{caller_rewrite_id},
});
if ($rs->first) {
push @log, sprintf "using caller rewrite rule set '%s' with id %d",
$rs->first->name, $rs->first->id;
$data->{caller_rewrite} = $rs->first;
}
} else {
my ($lookup_rws, $lookup_rws_type, $rws_rs);
if ($data->{'caller_subscriber_id'}) {
my $rs = NGCP::Panel::Utils::Preferences::get_usr_preference_rs(
c => $c, attribute => "rewrite_caller_in_dpid",
prov_subscriber =>
$data->{caller_subscriber}->provisioning_voip_subscriber,
);
if ($rs->first) {
$rws_rs = $c->model('DB')->resultset('voip_rewrite_rule_sets')->search({
caller_in_dpid => $rs->first->value
});
}
if ($rws_rs && $rws_rs->first) {
$lookup_rws = $rws_rs->first;
$lookup_rws_type = 'subscriber';
} else {
$rs = NGCP::Panel::Utils::Preferences::get_dom_preference_rs(
c => $c, attribute => "rewrite_caller_in_dpid",
prov_domain =>
$data->{caller_subscriber}->provisioning_voip_subscriber->domain,
);
if ($rs->first) {
$rws_rs = $c->model('DB')->resultset('voip_rewrite_rule_sets')->search({
caller_in_dpid => $rs->first->value
});
}
if ($rws_rs && $rws_rs->first) {
$lookup_rws = $rws_rs->first;
$lookup_rws_type = 'domain';
} else {
push @log, sprintf "no caller subscriber/domain rewrite rule sets were found";
}
}
} elsif ($data->{caller_peer_id}) {
my $rs = NGCP::Panel::Utils::Preferences::get_peer_preference_rs(
c => $c, attribute => "rewrite_caller_in_dpid",
peer_host => $data->{caller_peer_host},
);
if ($rs->first) {
$rws_rs = $c->model('DB')->resultset('voip_rewrite_rule_sets')->search({
caller_in_dpid => $rs->first->value
});
}
if ($rws_rs && $rws_rs->first) {
$lookup_rws = $rws_rs->first;
$lookup_rws_type = 'peer';
} else {
push @log, sprintf "no caller peer rewrite rule sets with were found";
}
}
if ($lookup_rws) {
push @log, sprintf "using caller %s inbound rewrite rule set '%s' with id %d",
$lookup_rws_type, $lookup_rws->name, $lookup_rws->id;
$data->{caller_rewrite} = $lookup_rws;
}
}
# apply inbound rewrite rules
foreach my $type (qw(caller callee)) {
$data->{$type.'_in'} = $data->{$type};
next unless $data->{caller_rewrite};
my $new;
if ($data->{caller_subscriber_id}) {
$new =
NGCP::Panel::Utils::Subscriber::apply_rewrite(
c => $c, subscriber => $data->{caller_subscriber},
direction => $type.'_in',
number => $data->{$type},
rws_id => $data->{caller_rewrite}->id,
);
} elsif ($data->{caller_peer_id}) {
$new =
NGCP::Panel::Utils::Peering::apply_rewrite(
c => $c, peer_host => $data->{caller_peer_host},
direction => $type.'_in',
number => $data->{$type},
rws_id => $data->{caller_rewrite}->id,
);
}
if ($new && $new ne $data->{$type}) {
push @log, sprintf "%s %s is rewritten based on the inbound rules into %s",
$type, $data->{$type}, $new;
}
$data->{$type.'_in'} = $new || $data->{$type};
}
# subscriber allowed_cli checks
if ($data->{caller_subscriber}) {
my %usr_prefs;
foreach my $pref (qw(allowed_clis allowed_clis_reject_policy cli user_cli)) {
my $rs = NGCP::Panel::Utils::Preferences::get_usr_preference_rs(
c => $c, attribute => $pref,
prov_subscriber =>
$data->{caller_subscriber}->provisioning_voip_subscriber,
);
if ($rs->first) {
@{$usr_prefs{$pref}} = map { $_->value } $rs->all;
} else {
$rs = NGCP::Panel::Utils::Preferences::get_dom_preference_rs(
c => $c, attribute => $pref,
prov_domain =>
$data->{caller_subscriber}->provisioning_voip_subscriber->domain,
);
if ($rs->first) {
@{$usr_prefs{$pref}} = map { $_->value } $rs->all;
}
}
}
my $match = map { my $val = s/\*/.*/gr;
$val =~ s/\?/.?/g;
$data->{caller_in} =~ /^$val$/;
} @{$usr_prefs{allowed_clis}};
if ($match) {
push @log, sprintf
"caller %s is accepted as it matches subscriber's 'allowed_clis'",
$data->{caller_in};
} else {
push @log, sprintf
"caller %s is rejected as it does not match subscriber's 'allowed_clis'",
$data->{caller_in};
if (defined $usr_prefs{allowed_clis_reject_policy}) {
SWITCH: for ($usr_prefs{allowed_clis_reject_policy}[0]) {
/^override_by_clir$/ && do {
push @log,
"'allowed_cli' reject policy is 'override_by_clir', anonymising caller";
$data->{caller_in} = 'anonymous';
last SWITCH;
};
/^override_by_usernpn$/ && do {
push @log,
"'allowed_cli' reject policy is 'override_by_usernpn'";
foreach my $cli (qw(user_cli cli)) {
if (defined $usr_prefs{$cli}) {
$data->{caller_in} = $usr_prefs{$cli}[0];
$log[-1] .= sprintf ", taken from '$cli' %s",
$usr_prefs{$cli}[0];
last;
}
}
last SWITCH;
};
/^reject$/ && do {
push @log,
"'allowed_cli' reject policy is 'reject', terminating the call";
goto RESULT;
last SWITCH;
};
}
}
}
}
# callee lookup
if ($data->{callee_peer_id}) {
my $rs = $c->model('DB')->resultset('voip_peer_groups')->search({
id => $data->{callee_peer_id},
});
unless ($rs->first) {
push @log, sprintf "no callee peer found with id %d",
$data->{callee_peer_id};
goto RESULT;
}
$data->{callee_peer} = $rs->first;
unless ($data->{callee_peer}->voip_peer_hosts->first) {
push @log, sprintf "callee peer with id %d does not contain any peer hosts",
$rs->id;
}
$data->{callee_peer_host} = $data->{callee_peer}->voip_peer_hosts->first;
} else {
push @log, sprintf "callee subscriber lookup based on %s",
$data->{callee_in};
$data->{callee_subscriber} =
NGCP::Panel::Utils::Subscriber::lookup(
c => $c,
lookup => $data->{callee_in},
);
if ($data->{callee_subscriber}) {
$data->{callee_subscriber_id} = $data->{callee_subscriber}->id;
my $sub = sprintf '%s@%s',
$data->{callee_subscriber}->username,
$data->{callee_subscriber}->domain->domain;
push @log, sprintf "found callee subscriber '%s' with id %d",
$sub, $data->{callee_subscriber_id};
} else {
foreach my $type (qw(caller callee)) {
$data->{$type.'_uri'} = 'sip:'.$data->{$type.'_in'};
if ($data->{$type.'_domain'}) {
$data->{$type.'_uri'} .= '@' . $data->{$type.'_domain'};
} elsif ($type eq 'callee' && $data->{caller_domain}) {
$data->{$type.'_uri'} .= '@' . $data->{caller_domain};
}
}
push @log,
sprintf "no callee subscriber found, performing peer lookup with caller uri %s and callee uri %s and callee %s",
@{$data}{qw(caller_uri callee_uri callee_in)};
$data->{callee_peers} =
NGCP::Panel::Utils::Peering::lookup(
c => $c,
prefix => $data->{callee_in},
caller => $data->{caller_uri},
callee => $data->{callee_uri},
);
unless ($data->{callee_peers} && scalar @{$data->{callee_peers}}) {
push @log, sprintf "no callee peers found";
goto RESULT;
}
# as we cannot check peer reply codes here, use first peer for now
$data->{callee_peer} = $data->{callee_peers}->[0];
$data->{callee_peer_id} = $data->{callee_peer}->id;
push @log, sprintf "matched peer '%s' with id %d",
$data->{callee_peer}->name, $data->{callee_peer}->id;
unless ($data->{callee_peer}->voip_peer_hosts->first) {
push @log, sprintf "callee peer with id %d does not contain any peer hosts",
$data->{callee_peer}->id;
}
$data->{callee_peer_host} = $data->{callee_peer}->voip_peer_hosts->first;
}
}
# callee sum up
push @log, sprintf "call to %s", $data->{callee};
if ($data->{callee_subscriber}) {
$log[-1] .= $data->{callee_subscriber}
? sprintf ", subscriber '%s\@%s' with id %d",
$data->{callee_subscriber}->username,
$data->{callee_subscriber}->domain->domain,
$data->{callee_subscriber}->id
: sprintf ", peer %s id %s",
$data->{callee_peer}->name,
$data->{callee_peer}->id;
} elsif ($data->{callee_peer_host}) {
$log[-1] .= sprintf " and peer host '%s' (ip: %s) with id %d",
$data->{callee_peer_host}->name,
$data->{callee_peer_host}->ip,
$data->{callee_peer_host}->id;
} else {
push @log, "this call does not have any termination point";
}
# callee outbound rewrite rules lookup
if ($data->{callee_rewrite_id}) {
my $rs = $c->model('DB')->resultset('voip_rewrite_rule_sets')->search({
id => $data->{callee_rewrite_id},
});
if ($rs->first) {
push @log, sprintf "using callee rewrite rule set '%s' with id %d",
$rs->first->name, $rs->first->id;
$data->{callee_rewrite} = $rs->first;
}
} else {
my ($lookup_rws, $lookup_rws_type, $rws_rs);
if ($data->{'callee_subscriber_id'}) {
my $rs = NGCP::Panel::Utils::Preferences::get_usr_preference_rs(
c => $c, attribute => "rewrite_callee_out_dpid",
prov_subscriber =>
$data->{callee_subscriber}->provisioning_voip_subscriber,
);
if ($rs->first) {
$rws_rs = $c->model('DB')->resultset('voip_rewrite_rule_sets')->search({
callee_out_dpid => $rs->first->value
});
}
if ($rws_rs && $rws_rs->first) {
$lookup_rws = $rws_rs->first;
$lookup_rws_type = 'subscriber';
} else {
$rs = NGCP::Panel::Utils::Preferences::get_dom_preference_rs(
c => $c, attribute => "rewrite_callee_out_dpid",
prov_domain =>
$data->{callee_subscriber}->provisioning_voip_subscriber->domain,
);
if ($rs->first) {
$rws_rs = $c->model('DB')->resultset('voip_rewrite_rule_sets')->search({
callee_out_dpid => $rs->first->value
});
}
if ($rws_rs && $rws_rs->first) {
$lookup_rws = $rws_rs->first;
$lookup_rws_type = 'domain';
} else {
push @log, sprintf "no callee subscriber/domain rewrite rule sets were found";
}
}
} elsif ($data->{callee_peer_id}) {
my $rs = NGCP::Panel::Utils::Preferences::get_peer_preference_rs(
c => $c, attribute => "rewrite_callee_out_dpid",
peer_host => $data->{callee_peer_host},
);
if ($rs->first) {
$rws_rs = $c->model('DB')->resultset('voip_rewrite_rule_sets')->search({
callee_out_dpid => $rs->first->value
});
}
if ($rws_rs && $rws_rs->first) {
$lookup_rws = $rws_rs->first;
$lookup_rws_type = 'peer';
} else {
push @log, sprintf "no callee peer rewrite rule sets with were found";
}
}
if ($lookup_rws) {
push @log, sprintf "using callee %s outbound rewrite rule set '%s' with id %d",
$lookup_rws_type, $lookup_rws->name, $lookup_rws->id;
$data->{callee_rewrite} = $lookup_rws;
}
}
# apply outbound rewrite rules
foreach my $type (qw(caller callee)) {
$data->{$type.'_out'} = $data->{$type.'_in'};
next unless $data->{callee_rewrite};
my $new;
if ($data->{callee_subscriber_id}) {
$new =
NGCP::Panel::Utils::Subscriber::apply_rewrite(
c => $c, subscriber => $data->{callee_subscriber},
direction => $type.'_out',
number => $data->{$type.'_in'},
rws_id => $data->{callee_rewrite}->id,
);
} elsif ($data->{callee_peer_id}) {
$new =
NGCP::Panel::Utils::Peering::apply_rewrite(
c => $c, peer_host => $data->{callee_peer_host},
direction => $type.'_out',
number => $data->{$type.'_in'},
rws_id => $data->{callee_rewrite}->id,
);
}
if ($new && $new ne $data->{$type.'_in'}) {
push @log, sprintf "%s %s is rewritten based on the outbound rules into %s",
$type, $data->{$type.'_in'}, $new;
}
$data->{$type.'_out'} = $new || $data->{$type.'_in'};
}
RESULT:
foreach my $type (qw(caller callee)) {
$data->{$type.'_type'} =
$data->{$type.'_subscriber'}
? 'subscriber'
: $data->{$type.'_peer'} ? 'peer' : 'unknown';
#foreach my $dir (qw(in out)) {
#$data->{$type.'_'.$dir} ||= $data->{$type.'_type'} ne 'unknown'
# ? $data->{$type}
# : '';
# fill in a value even if caller/callee is not identified
#}
}
$c->stash(
template => 'callrouting/result.tt',
close_target => '/callroutingverify',
caller => $data->{caller},
callee => $data->{callee},
caller_in => $data->{caller_in},
callee_in => $data->{callee_in},
caller_out => $data->{caller_out},
callee_out => $data->{callee_out},
caller_type => $data->{caller_type},
callee_type => $data->{callee_type},
log => \@log,
form => undef,
);
return;
}
1;
__END__
=head1 NAME
NGCP::Panel::Controller::CallRouting
=head1 DESCRIPTION
A helper to manipulate the call routing data
=head1 AUTHOR
Kirill Solomko <ksolomko@sipwise.com>
=head1 LICENSE
This library is free software. You can redistribute it and/or modify
it under the same terms as Perl itself.
=cut
# vim: set tabstop=4 expandtab: