373 lines
12 KiB
Erlang
373 lines
12 KiB
Erlang
% SCCP routing code
|
|
|
|
% (C) 2011 by Harald Welte <laforge@gnumonks.org>
|
|
%
|
|
% All Rights Reserved
|
|
%
|
|
% This program is free software; you can redistribute it and/or modify
|
|
% it under the terms of the GNU Affero General Public License as
|
|
% published by the Free Software Foundation; either version 3 of the
|
|
% License, or (at your option) any later version.
|
|
%
|
|
% This program is distributed in the hope that it will be useful,
|
|
% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
% GNU General Public License for more details.
|
|
%
|
|
% You should have received a copy of the GNU Affero General Public License
|
|
% along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
%
|
|
% If you modify this Program, or any covered work, by linking or
|
|
% combining it with runtime libraries of Erlang/OTP as released by
|
|
% Ericsson on http://www.erlang.org (or a modified version of these
|
|
% libraries), containing parts covered by the terms of the Erlang Public
|
|
% License (http://www.erlang.org/EPLICENSE), the licensors of this
|
|
% Program grant you additional permission to convey the resulting work
|
|
% without the need to license the runtime libraries of Erlang/OTP under
|
|
% the GNU Affero General Public License. Corresponding Source for a
|
|
% non-source form of such a combination shall include the source code
|
|
% for the parts of the runtime libraries of Erlang/OTP used as well as
|
|
% that of the covered work.
|
|
|
|
-module(sccp_routing).
|
|
-author('Harald Welte <laforge@gnumonks.org>').
|
|
|
|
-include_lib("osmo_ss7/include/osmo_util.hrl").
|
|
-include_lib("osmo_ss7/include/sccp.hrl").
|
|
-include_lib("osmo_ss7/include/mtp3.hrl").
|
|
|
|
-export([route_mtp3_sccp_in/1, route_local_out/1, select_opc/2]).
|
|
|
|
pointcode_is_local(Pc) ->
|
|
PcInt = osmo_util:pointcode2int(Pc),
|
|
ss7_links:is_pc_local(PcInt).
|
|
|
|
% local helper function
|
|
msg_return_or_cr_refusal(SccpMsg, RetCause, RefCause) ->
|
|
case sccp_codec:is_connectionless(SccpMsg) of
|
|
true ->
|
|
% if CL -> message return procedure
|
|
message_return(SccpMsg, RetCause);
|
|
false ->
|
|
% if CR -> connection refusal
|
|
connection_refusal(SccpMsg, RefCause)
|
|
end,
|
|
{error, routing}.
|
|
|
|
% local outgoing CL or CR message
|
|
route_local_out(SccpMsg) when is_record(SccpMsg, sccp_msg) ->
|
|
CalledParty = proplists:get_value(called_party_addr, SccpMsg#sccp_msg.parameters),
|
|
#sccp_addr{global_title = Gt, ssn = Ssn, point_code = Pc} = CalledParty,
|
|
if
|
|
(Gt == undefined) and ((Ssn == undefined) or (Ssn == 0)) ->
|
|
% left-most colunm of Table 1/Q714 -> Action four
|
|
Action = 4;
|
|
(Gt /= undefined) and ((Ssn == undefined) or (Ssn == 0)) ->
|
|
% second (from left) column of Table 1/Q.714
|
|
if (Pc == undefined) ->
|
|
Action = 2;
|
|
true ->
|
|
case pointcode_is_local(Pc) of
|
|
true ->
|
|
Action = 2;
|
|
false ->
|
|
Action = 3
|
|
end
|
|
end;
|
|
(Gt == undefined) and (Ssn /= undefined) ->
|
|
% third (from left) column of Table 1/Q.714
|
|
if (Pc == undefined) ->
|
|
Action = 4;
|
|
true ->
|
|
Action = 1
|
|
end;
|
|
(Gt /= undefined) and (Ssn /= undefined) ->
|
|
% last (from left) column of Table 1/Q.714
|
|
if (Pc == undefined) ->
|
|
Action = 2;
|
|
true ->
|
|
if CalledParty#sccp_addr.route_on_ssn ->
|
|
Action = 1;
|
|
true ->
|
|
case pointcode_is_local(Pc) of
|
|
true ->
|
|
Action = 2;
|
|
false ->
|
|
Action = 3
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
route_local_out_action(Action, SccpMsg, CalledParty).
|
|
|
|
% select Originating Point Code for given (local_out) SCCP Msg
|
|
select_opc(SccpMsg, LsName) when is_record(SccpMsg, sccp_msg) ->
|
|
% first try to find the Calling Party as specified by user
|
|
case proplists:get_value(calling_party_addr,
|
|
SccpMsg#sccp_msg.parameters) of
|
|
undefined ->
|
|
% no calling party: auto selection
|
|
select_opc_auto(SccpMsg, LsName);
|
|
CallingParty ->
|
|
case CallingParty#sccp_addr.point_code of
|
|
% calling party has no point code: auto selection
|
|
undefined ->
|
|
select_opc_auto(SccpMsg, LsName);
|
|
Opc ->
|
|
% calling party has point code: use it
|
|
Opc
|
|
end
|
|
end.
|
|
|
|
select_opc_auto(SccpMsg, LsName) when is_record(SccpMsg, sccp_msg) ->
|
|
% use SS7 link management to determine Opc
|
|
ss7_links:get_opc_for_linkset(LsName).
|
|
|
|
|
|
% Acccording to 2.3.2 Action (1)
|
|
route_local_out_action(1, SccpMsg, CalledParty) ->
|
|
#sccp_addr{global_title = Gt, ssn = Ssn, point_code = Pc} = CalledParty,
|
|
case pointcode_is_local(Pc) of
|
|
true ->
|
|
% c) procedures 2.3.1, item 2) are folloed
|
|
case sccp_user:pid_for_ssn(Ssn, Pc) of
|
|
{ok, UserPid} ->
|
|
% pass to either SCOC or SCLC
|
|
{local, SccpMsg, UserPid};
|
|
{error, _Error} ->
|
|
% message return / connection refusal
|
|
msg_return_or_cr_refusal(SccpMsg,
|
|
?SCCP_CAUSE_RET_UNEQUIP_USER,
|
|
?SCCP_CAUSE_REF_UNEQUIPPED_USER)
|
|
end;
|
|
false ->
|
|
% If the DPC is not the node itself and the remote DPC, SCCP
|
|
% and SSN are available, then the MTP-TRANSFER request
|
|
% primitive is invoked unless the compatibility test returns
|
|
% the message to SCLC or unless the message is discarded by the
|
|
% traffic limitation mechanism;
|
|
{ok, LsName} = ss7_routes:route_dpc(Pc),
|
|
{remote, SccpMsg, LsName, Pc}
|
|
end;
|
|
|
|
% Acccording to 2.3.2 Action (2)
|
|
route_local_out_action(2, SccpMsg, CalledParty) ->
|
|
% perform GTT
|
|
case gtt() of
|
|
undefined ->
|
|
% if CL -> message return procedure
|
|
% if CR -> connection refusal
|
|
msg_return_or_cr_refusal(SccpMsg,
|
|
?SCCP_CAUSE_RET_UNEQUIP_USER,
|
|
?SCCP_CAUSE_REF_UNEQUIPPED_USER);
|
|
Dpc ->
|
|
case pointcode_is_local(Dpc) of
|
|
true ->
|
|
% message is passed, based on the message type, to
|
|
% either SCOC or SCLC;
|
|
{local, SccpMsg, undefined};
|
|
false ->
|
|
% MTP-TRANSFER request primitive is invoked unless the
|
|
% compatibility test returns the message to SCLC or
|
|
% unless the message is discarded by the traffic
|
|
% limitation mechanism
|
|
{ok, LsName} = ss7_routes:route_dpc(Dpc),
|
|
{remote, SccpMsg, LsName, Dpc}
|
|
end
|
|
end;
|
|
|
|
% Acccording to 2.3.2 Action (3)
|
|
route_local_out_action(3, SccpMsg, CalledParty) ->
|
|
% The same actions as Action (1) apply, without checking the SSN.
|
|
#sccp_addr{global_title = Gt, point_code = Pc} = CalledParty,
|
|
case pointcode_is_local(Pc) of
|
|
true ->
|
|
% pass to either SCOC or SCLC
|
|
% theoretic case, as we only enter Action(3) for remote DPC
|
|
{local, SccpMsg, undefined};
|
|
false ->
|
|
% If the DPC is not the node itself and the remote DPC, SCCP
|
|
% and SSN are available, then the MTP-TRANSFER request
|
|
% primitive is invoked unless the compatibility test returns
|
|
% the message to SCLC or unless the message is discarded by the
|
|
% traffic limitation mechanism;
|
|
{ok, LsName} = ss7_routes:route_dpc(Pc),
|
|
{remote, SccpMsg, LsName, Pc}
|
|
end;
|
|
|
|
% Acccording to 2.3.2 Action (4)
|
|
route_local_out_action(4, SccpMsg, CalledParty) ->
|
|
% insufficient information.
|
|
msg_return_or_cr_refusal(SccpMsg, ?SCCP_CAUSE_RET_NOTRANS_ADDR,
|
|
?SCCP_CAUSE_REF_DEST_UNKNOWN).
|
|
|
|
|
|
|
|
route_cr_connless(Mtp3Msg, SccpMsg) when is_record(SccpMsg, sccp_msg) ->
|
|
CalledParty = proplists:get_value(called_party_addr, SccpMsg#sccp_msg.parameters),
|
|
case CalledParty#sccp_addr.route_on_ssn of
|
|
1 -> % sheet 3 (6)
|
|
#sccp_addr{ssn = Ssn, point_code = Pc}= CalledParty,
|
|
% check if the subsystem is available (FIXME: move this into SCLC ?!?)
|
|
case sccp_user:pid_for_ssn(Ssn, Pc) of
|
|
{ok, UserPid} ->
|
|
% forward to SCOC/SCLC
|
|
{local, SccpMsg, UserPid};
|
|
{error, Error} ->
|
|
% invoke connection refusal (if CR) or message return
|
|
msg_return_or_cr_refusal(SccpMsg,
|
|
?SCCP_CAUSE_RET_UNEQUIP_USER,
|
|
?SCCP_CAUSE_REF_UNEQUIPPED_USER)
|
|
end;
|
|
0 ->
|
|
% Check for hop counter and increment it
|
|
MsgPostHop = check_and_dec_hopctr(SccpMsg),
|
|
MsgClass = proplists:get_value(?SCCP_PNC_PROTOCOL_CLASS,
|
|
MsgPostHop#sccp_msg.parameters),
|
|
%% FIXME: gtt() and others need to be implemented according to
|
|
%% Q.714 C.1 sheet 2 and 3)
|
|
#sccp_addr{ssn = Ssn, point_code = Pc}= CalledParty,
|
|
% check if the subsystem is available (FIXME: move this into SCLC ?!?)
|
|
case sccp_user:pid_for_ssn(Ssn, Pc) of
|
|
{ok, UserPid} ->
|
|
% forward to SCOC/SCLC
|
|
{local, SccpMsg, UserPid};
|
|
{error, Error} ->
|
|
% invoke connection refusal (if CR) or message return
|
|
msg_return_or_cr_refusal(SccpMsg,
|
|
?SCCP_CAUSE_RET_UNEQUIP_USER,
|
|
?SCCP_CAUSE_REF_UNEQUIPPED_USER)
|
|
end
|
|
|
|
% case MsgClass of
|
|
% 0 ->
|
|
% % FIXME: Assign SLS
|
|
% ok;
|
|
% 1 ->
|
|
% % FIXME: Map incoming SLS to outgoing SLS
|
|
% ok;
|
|
% _Default ->
|
|
% ok
|
|
% end,
|
|
% % Optional screening function
|
|
% % GTT needs to be performed
|
|
% ok
|
|
end.
|
|
% FIXME: handle UDTS/XUDTS/LUDTS messages (RI=0 check) of C.1/Q.714 (1/12)
|
|
% FIXME: handle translation already performed == yes) case of C.1/Q.714 (1/12)
|
|
%route_main(SccpMsg),
|
|
%LsName = ss7_routes:route_dpc(),
|
|
%LsName = undefined,
|
|
%{remote, SccpMsg, LsName, undefined}.
|
|
|
|
|
|
% CR or connectionless message, coming in from MTP
|
|
% return values
|
|
% {local, SccpMsg, UserPid}
|
|
% {remote}
|
|
route_mtp3_sccp_in(Mtp3Msg) when is_record(Mtp3Msg, mtp3_msg) ->
|
|
{ok, Msg} = sccp_codec:parse_sccp_msg(Mtp3Msg#mtp3_msg.payload),
|
|
%io:format("Parsed Msg: ~p~n", [Msg]),
|
|
case Msg of
|
|
#sccp_msg{msg_type = ?SCCP_MSGT_CR} ->
|
|
route_cr_connless(Mtp3Msg, Msg);
|
|
_ ->
|
|
case sccp_codec:is_connectionless(Msg) of
|
|
true ->
|
|
route_cr_connless(Mtp3Msg, Msg);
|
|
false ->
|
|
{local, Msg, undefined}
|
|
end
|
|
end.
|
|
|
|
% Check if the message has a hop counter; decrement it if yes.
|
|
check_and_dec_hopctr(Msg = #sccp_msg{msg_type = MsgType}) when
|
|
MsgType == ?SCCP_MSGT_XUDT;
|
|
MsgType == ?SCCP_MSGT_XUDTS;
|
|
MsgType == ?SCCP_MSGT_LUDT;
|
|
MsgType == ?SCCP_MSGT_LUDTS;
|
|
MsgType == ?SCCP_MSGT_CR ->
|
|
HopCtr = proplists:get_value(?SCCP_PNC_HOP_COUNTER,
|
|
Msg#sccp_msg.parameters),
|
|
if
|
|
HopCtr =< 1 ->
|
|
% Error: Hop count expired
|
|
io:format("SCCP hop count expired~n"),
|
|
Msg;
|
|
true ->
|
|
ParNew = lists:keyreplace(?SCCP_PNC_HOP_COUNTER, 1,
|
|
Msg#sccp_msg.parameters,
|
|
{ ?SCCP_PNC_HOP_COUNTER, HopCtr -1}),
|
|
Msg#sccp_msg{parameters = ParNew}
|
|
end;
|
|
check_and_dec_hopctr(Msg = #sccp_msg{}) ->
|
|
Msg.
|
|
|
|
|
|
route_main(SccpMsg) when is_record(SccpMsg, sccp_msg) ->
|
|
CalledParty = proplists:get_value(called_party_addr, SccpMsg#sccp_msg.parameters),
|
|
case CalledParty#sccp_addr.point_code of
|
|
undefined ->
|
|
fixme
|
|
end.
|
|
|
|
|
|
% Message return procedure (Section 4.2 / Q.714)
|
|
message_return(SccpMsg = #sccp_msg{msg_type = MsgType}, Cause) when
|
|
MsgType == ?SCCP_MSGT_XUDT;
|
|
MsgType == ?SCCP_MSGT_UDT;
|
|
MsgType == ?SCCP_MSGT_LUDT ->
|
|
% only return the message if the respective option is set
|
|
{Class, Opt} = proplists:get_value(protocol_class, SccpMsg#sccp_msg.parameters),
|
|
if Opt /= 8 ->
|
|
ok;
|
|
true ->
|
|
RetMsg = gen_ret_msg(SccpMsg, Cause),
|
|
% FIXME: actually return it
|
|
ok
|
|
end;
|
|
message_return(_Msg, _Reason) ->
|
|
ok.
|
|
|
|
% transform UDT/LUDT/XUDT into UDTS/LUDTS/XUDTS
|
|
gen_ret_msg(SccpMsg = #sccp_msg{msg_type = MsgType, parameters = Params}, Cause) ->
|
|
% extract information fields required
|
|
{Class, _Opt} = proplists:get_value(protocol_class, Params),
|
|
RetMsgType = message_return_type(MsgType),
|
|
CalledParty = proplists:get_value(called_party_addr, Params),
|
|
CallingParty = proplists:get_value(calling_party_addr, Params),
|
|
% build new options proplist
|
|
Params1 = lists:keyreplace(called_party_addr, 1, Params,
|
|
{called_party_addr, CallingParty}),
|
|
Params2 = lists:keyreplace(calling_party_addr, 1, Params1,
|
|
{calling_party_addr, CalledParty}),
|
|
Params3 = [{return_cause, Cause}, {protocol_class, {Class, 0}}] ++ Params2,
|
|
% return the new message
|
|
SccpMsg#sccp_msg{msg_type = RetMsgType,
|
|
parameters = Params3}.
|
|
|
|
connection_refusal(SccpMsg = #sccp_msg{msg_type = ?SCCP_MSGT_CR}, Cause) ->
|
|
CrefMsg = gen_cref_msg(SccpMsg, Cause),
|
|
% FIXME: actually return it
|
|
ok.
|
|
|
|
gen_cref_msg(SccpMsg = #sccp_msg{msg_type = ?SCCP_MSGT_CR, parameters =
|
|
Params}, Cause) ->
|
|
CalledParty = proplists:get_value(called_party_addr, Params),
|
|
SrcLocalRef = proplists:get_value(src_local_ref, Params),
|
|
CrefParams = [{dst_local_ref, SrcLocalRef},
|
|
{refusal_cause, Cause}],
|
|
% FIXME: what about class / data/ ... ?
|
|
#sccp_msg{msg_type = ?SCCP_MSGT_CREF, parameters = CrefParams}.
|
|
|
|
message_return_type(?SCCP_MSGT_XUDT) ->
|
|
?SCCP_MSGT_XUDTS;
|
|
message_return_type(?SCCP_MSGT_UDT) ->
|
|
?SCCP_MSGT_UDTS;
|
|
message_return_type(?SCCP_MSGT_LUDT) ->
|
|
?SCCP_MSGT_LUDTS.
|
|
|
|
% dummy for now, we don't do GTT yet
|
|
gtt() ->
|
|
undefined.
|