add new file implementing SCCP routing (no GTT yet)

This commit is contained in:
Harald Welte 2011-10-09 14:32:30 +02:00
parent 49fb161451
commit a2ac68342b
1 changed files with 300 additions and 0 deletions

300
src/sccp_routing.erl Normal file
View File

@ -0,0 +1,300 @@
% 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/>.
-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]).
pointcode_is_local(Pc) ->
% FIXME: use SCRC routing information
true.
% local helper function
msg_return_or_cr_refusal(SccpMsg, RetCause, RefCause) ->
case sccp_codec:is_connectionless(SccpMsg) of
false ->
% if CL -> message return procedure
message_return(SccpMsg, RetCause);
true ->
% if CR -> connection refusal
connection_refusal(SccpMsg, RefCause)
end.
% 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).
% 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:local_ssn_avail(Ssn, Pc) of
true ->
% pass to either SCOC or SCLC
{local, SccpMsg};
false ->
% 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;
{remote, SccpMsg}
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};
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
{remote, SccpMsg}
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.
% FIXME
ok;
% 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, Mtp3Msg};
{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),
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),
{remote}.
% CR or connectionless message, coming in from MTP
% return values
% {local, SccpMsg, Mtp3Msg}
% {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, Mtp3Msg}
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.
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.