389 lines
15 KiB
Erlang
389 lines
15 KiB
Erlang
% MGW NAT core routines as well as ISUP + SCCP Rewriting
|
|
|
|
% (C) 2011 by Harald Welte <laforge@gnumonks.org>
|
|
% (C) 2011 OnWaves
|
|
%
|
|
% 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/>.
|
|
%
|
|
% Additional Permission under GNU AGPL version 3 section 7:
|
|
%
|
|
% 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(mgw_nat).
|
|
-author("Harald Welte <laforge@gnumonks.org>").
|
|
|
|
% Main entry function for M2UA binary messages
|
|
-export([mangle_rx_data/4]).
|
|
|
|
% Action functions to apply specific translations
|
|
-export([mangle_rx_sccp/4, mangle_rx_isup/4]).
|
|
|
|
% exports belwo needed by map_masq.erl
|
|
-export([isup_party_internationalize/2, isup_party_nationalize/2, isup_party_replace_prefix/3,
|
|
isup_party_nat00_internationalize/1]).
|
|
|
|
%-include_lib("kernel/include/inet.hrl").
|
|
%-include_lib("kernel/include/inet_sctp.hrl").
|
|
|
|
-include_lib("osmo_ss7/include/xua.hrl").
|
|
-include_lib("osmo_ss7/include/m2ua.hrl").
|
|
-include_lib("osmo_ss7/include/mtp3.hrl").
|
|
-include_lib("osmo_ss7/include/isup.hrl").
|
|
-include_lib("osmo_ss7/include/sccp.hrl").
|
|
|
|
% mangle the received data
|
|
mangle_rx_data(From, Path, Data, Fn) when is_list(Path), is_binary(Data) ->
|
|
M2ua = m2ua_codec:parse_m2ua_msg(Data),
|
|
%io:format("M2UA Decode: ~p~n", [M2ua]),
|
|
case M2ua of
|
|
#xua_msg{msg_class = ?M2UA_MSGC_MAUP,
|
|
msg_type = ?M2UA_MAUP_MSGT_DATA} ->
|
|
M2ua_out = mangle_rx_m2ua_maup(Fn, From, Path, M2ua);
|
|
#xua_msg{} ->
|
|
% simply pass it along unmodified
|
|
M2ua_out = M2ua
|
|
end,
|
|
% re-encode the data
|
|
%io:format("M2UA Encode: ~p~n", [M2ua_out]),
|
|
m2ua_codec:encode_m2ua_msg(M2ua_out).
|
|
|
|
% mangle the received M2UA
|
|
mangle_rx_m2ua_maup(Fn, From, Path, M2ua = #xua_msg{payload = Params}) ->
|
|
{_Len, M2uaPayload} = proplists:get_value(16#300, Params),
|
|
Mtp3 = mtp3_codec:parse_mtp3_msg(M2uaPayload),
|
|
%io:format("MTP3 Decode: ~p~n", [Mtp3]),
|
|
Mtp3_out = mangle_rx_mtp3(Fn, From, Path ++ [M2ua], Mtp3),
|
|
%io:format("MTP3 Encode: ~p~n", [Mtp3_out]),
|
|
Mtp3OutBin = mtp3_codec:encode_mtp3_msg(Mtp3_out),
|
|
Params2 = proplists:delete(16#300, Params),
|
|
ParamsNew = Params2 ++ [{16#300, {byte_size(Mtp3OutBin), Mtp3OutBin}}],
|
|
% return mangled parsed m2ua msg
|
|
M2ua#xua_msg{payload = ParamsNew}.
|
|
|
|
% mangle the MTP3 payload
|
|
mangle_rx_mtp3(Fn, From, Path, Mtp3 = #mtp3_msg{service_ind = Service}) ->
|
|
mangle_rx_mtp3_serv(Fn, From, Path, Service, Mtp3).
|
|
|
|
% mangle the ISUP content
|
|
mangle_rx_mtp3_serv(Fn, From, Path, ?MTP3_SERV_ISUP, Mtp3 = #mtp3_msg{payload = Payload}) ->
|
|
%io:format("ISUP In: ~p~n", [Payload]),
|
|
Isup = isup_codec:parse_isup_msg(Payload),
|
|
%io:format("ISUP Decode: ~p~n", [Isup]),
|
|
%IsupMangled = mangle_rx_isup(From, Path, Isup#isup_msg.msg_type, Isup),
|
|
try Fn(isup, From, Path, Isup#isup_msg.msg_type, Isup) of
|
|
IsupMangled ->
|
|
if IsupMangled == Isup ->
|
|
% return the unmodified original payload
|
|
Mtp3;
|
|
true ->
|
|
io:format("ISUP Encode In: ~p~n", [IsupMangled]),
|
|
Payload_out = isup_codec:encode_isup_msg(IsupMangled),
|
|
io:format("ISUP Encode Out: ~p~n", [Payload_out]),
|
|
% return modified MTP3 payload
|
|
Mtp3#mtp3_msg{payload = Payload_out}
|
|
end
|
|
catch error:Error ->
|
|
io:format("ISUP In: ~p~n", [Payload]),
|
|
io:format("ISUP Decode: ~p~n", [Isup]),
|
|
io:format("ISUP mangling Error: ~p~n", [Error]),
|
|
% return the unmodified original payload
|
|
Mtp3
|
|
end;
|
|
% mangle the SCCP content
|
|
mangle_rx_mtp3_serv(Fn, From, Path, ?MTP3_SERV_SCCP, Mtp3 = #mtp3_msg{payload = Payload}) ->
|
|
%io:format("SCCP In: ~p~n", [Payload]),
|
|
{ok, Sccp} = sccp_codec:parse_sccp_msg(Payload),
|
|
%io:format("SCCP Decode: ~p~n", [Sccp]),
|
|
SccpMangled = Fn(sccp, From, Path ++ [Mtp3], Sccp#sccp_msg.msg_type, Sccp),
|
|
SccpMasqued = mangle_rx_sccp_map(Fn, From, Path ++ [Mtp3],
|
|
SccpMangled#sccp_msg.msg_type, SccpMangled),
|
|
%SccpMangled = mangle_rx_sccp(From, Path ++ [Mtp3], Sccp#sccp_msg.msg_type, Sccp),
|
|
%SccpMasqued = sccp_masq:sccp_masq_msg(From, SccpMangled#sccp_msg.msg_type,
|
|
% SccpMangled),
|
|
if SccpMasqued == Sccp ->
|
|
Mtp3;
|
|
true ->
|
|
%io:format("SCCP Encode In: ~p~n", [SccpMasqued]),
|
|
Payload_out = sccp_codec:encode_sccp_msg(SccpMasqued),
|
|
%io:format("SCCP Encode Out: ~p~n", [Payload_out]),
|
|
% return modified MTP3 payload
|
|
Mtp3#mtp3_msg{payload = Payload_out}
|
|
end;
|
|
% default: do nothing
|
|
mangle_rx_mtp3_serv(_Fn, _From, _Path, _, Mtp3) ->
|
|
Mtp3.
|
|
|
|
% Mangle the actual MAP payload inside the UDT data portion
|
|
mangle_rx_sccp_map(Fn, From, Path, ?SCCP_MSGT_UDT, Msg = #sccp_msg{parameters = Opts}) ->
|
|
UserData = proplists:get_value(user_data, Opts),
|
|
MapDec = map_codec:parse_tcap_msg(UserData),
|
|
%MapDecNew = map_masq:mangle_map(From, MapDec),
|
|
MapDecNew = Fn(map, From, Path ++ [Msg], 0, MapDec),
|
|
MapEncNew = maybe_re_encode(MapDec, MapDecNew, UserData),
|
|
Opts3 = lists:keyreplace(user_data, 1, Opts,
|
|
{user_data, MapEncNew}),
|
|
Msg#sccp_msg{parameters = Opts3};
|
|
mangle_rx_sccp_map(_Fn, _From, _Path, _MsgType, Msg) ->
|
|
Msg.
|
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
% Actual mangling of the decoded SCCP messages
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
|
% iterate over list of rewrite tuples and apply translation if there is a match
|
|
do_sccp_gt_rewrite(GT, _From, []) ->
|
|
GT;
|
|
do_sccp_gt_rewrite(GT = #global_title{phone_number = PhoneNum}, from_stp, [Head|List]) ->
|
|
{MscSide, StpSide, Comment} = Head,
|
|
StpSideList = osmo_util:int2digit_list(StpSide),
|
|
MscSideList = osmo_util:int2digit_list(MscSide),
|
|
if PhoneNum == StpSideList ->
|
|
NewPhoneNum = MscSideList,
|
|
io:format("SCCP STP->MSC rewrite (~p) ~p -> ~p~n",
|
|
[Comment, PhoneNum, NewPhoneNum]),
|
|
GT#global_title{phone_number = NewPhoneNum};
|
|
true ->
|
|
do_sccp_gt_rewrite(GT, from_stp, List)
|
|
end;
|
|
do_sccp_gt_rewrite(GT = #global_title{phone_number = PhoneNum}, from_msc, [Head|List]) ->
|
|
{MscSide, StpSide, Comment} = Head,
|
|
StpSideList = osmo_util:int2digit_list(StpSide),
|
|
MscSideList = osmo_util:int2digit_list(MscSide),
|
|
if PhoneNum == MscSideList ->
|
|
NewPhoneNum = StpSideList,
|
|
io:format("SCCP MSC->STP rewrite (~p) ~p -> ~p~n",
|
|
[Comment, PhoneNum, NewPhoneNum]),
|
|
GT#global_title{phone_number = NewPhoneNum};
|
|
true ->
|
|
do_sccp_gt_rewrite(GT, from_msc, List)
|
|
end.
|
|
|
|
% mangle called address
|
|
mangle_rx_called(from_stp, Addr = #sccp_addr{global_title = GT}) ->
|
|
{ok, RewriteTbl} = application:get_env(mgw_nat, sccp_rewrite_tbl),
|
|
GTout = do_sccp_gt_rewrite(GT, from_stp, RewriteTbl),
|
|
Addr#sccp_addr{global_title = GTout};
|
|
mangle_rx_called(_From, Addr) ->
|
|
Addr.
|
|
|
|
% mangle calling address
|
|
mangle_rx_calling(from_msc, Addr = #sccp_addr{global_title = GT}) ->
|
|
{ok, RewriteTbl} = application:get_env(mgw_nat, sccp_rewrite_tbl),
|
|
GTout = do_sccp_gt_rewrite(GT, from_msc, RewriteTbl),
|
|
Addr#sccp_addr{global_title = GTout};
|
|
mangle_rx_calling(_From, Addr) ->
|
|
Addr.
|
|
|
|
maybe_re_encode(DecOrig, DecNew, MapEncOld) when DecOrig == DecNew ->
|
|
MapEncOld;
|
|
maybe_re_encode(_DecOrig, DecNew, _MapEncOld) ->
|
|
map_codec:encode_tcap_msg(DecNew).
|
|
|
|
% Mangle the SCCP Calling / Called Addresses
|
|
mangle_rx_sccp(From, _Path, ?SCCP_MSGT_UDT, Msg = #sccp_msg{parameters = Opts}) ->
|
|
CalledParty = proplists:get_value(called_party_addr, Opts),
|
|
CalledPartyNew = mangle_rx_called(From, CalledParty),
|
|
CallingParty = proplists:get_value(calling_party_addr, Opts),
|
|
CallingPartyNew = mangle_rx_calling(From, CallingParty),
|
|
Opts1 = lists:keyreplace(called_party_addr, 1, Opts,
|
|
{called_party_addr, CalledPartyNew}),
|
|
Opts2 = lists:keyreplace(calling_party_addr, 1, Opts1,
|
|
{calling_party_addr, CallingPartyNew}),
|
|
Msg#sccp_msg{parameters = Opts2};
|
|
mangle_rx_sccp(_From, _Path, _MsgType, Msg) ->
|
|
Msg.
|
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
% Actual mangling of the decoded ISUP messages
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
|
% iterate over list of parameters and call mangle_rx_isup_par() for each one
|
|
mangle_rx_isup_params(_From, _MsgType, _Msg, ParListOut, []) ->
|
|
ParListOut;
|
|
mangle_rx_isup_params(From, MsgType, Msg, ParListOut, [Par|ParList]) ->
|
|
ParOut = mangle_rx_isup_par(From, MsgType, Msg, Par),
|
|
mangle_rx_isup_params(From, MsgType, Msg, ParListOut++[ParOut], ParList).
|
|
|
|
% manipulate phone numbers
|
|
mangle_rx_isup_par(From, MsgType, _Msg, {ParType, ParBody}) when
|
|
ParType == ?ISUP_PAR_CALLED_P_NUM;
|
|
ParType == ?ISUP_PAR_CONNECTED_NUM;
|
|
ParType == ?ISUP_PAR_CALLING_P_NUM ->
|
|
NewParBody = mangle_isup_number(From, MsgType, ParType, ParBody),
|
|
{ParType, NewParBody};
|
|
% defauly case: do not mangle this parameter
|
|
mangle_rx_isup_par(_From, _MsgType, _Msg, Par) ->
|
|
Par.
|
|
|
|
% mangle an incoming ISUP message
|
|
mangle_rx_isup(From, _Path, MsgType, Msg = #isup_msg{parameters = Params}) ->
|
|
ParamsOut = mangle_rx_isup_params(From, MsgType, Msg, [], Params),
|
|
% return message with modified parameter list
|
|
Msg#isup_msg{parameters = ParamsOut}.
|
|
|
|
% STP->MSC: Mangle a Party Number in IAM
|
|
mangle_isup_number(from_stp, ?ISUP_MSGT_IAM, NumType, PartyNum) ->
|
|
case NumType of
|
|
?ISUP_PAR_CALLED_P_NUM ->
|
|
% First convert to international number, if it is national
|
|
{ok, InternPfx} = application:get_env(mgw_nat, intern_pfx),
|
|
{ok, MsrnPfxStp} = application:get_env(mgw_nat, msrn_pfx_stp),
|
|
{ok, MsrnPfxMsc} = application:get_env(mgw_nat, msrn_pfx_msc),
|
|
Num1 = isup_party_internationalize(PartyNum, InternPfx),
|
|
io:format("IAM MSRN rewrite (STP->MSC): "),
|
|
isup_party_replace_prefix(Num1, MsrnPfxStp, MsrnPfxMsc);
|
|
?ISUP_PAR_CALLING_P_NUM ->
|
|
isup_party_nat00_internationalize(PartyNum);
|
|
_ ->
|
|
PartyNum
|
|
end;
|
|
% MSC->STP: Mangle connected number in response to IAM
|
|
mangle_isup_number(from_msc, MsgT, NumType, PartyNum) when MsgT == ?ISUP_MSGT_CON;
|
|
MsgT == ?ISUP_MSGT_ANM ->
|
|
case NumType of
|
|
?ISUP_PAR_CONNECTED_NUM ->
|
|
{ok, InternPfx} = application:get_env(mgw_nat, intern_pfx),
|
|
{ok, MsrnPfxStp} = application:get_env(mgw_nat, msrn_pfx_stp),
|
|
{ok, MsrnPfxMsc} = application:get_env(mgw_nat, msrn_pfx_msc),
|
|
io:format("CON MSRN rewrite (MSC->STP): "),
|
|
Num1 = isup_party_replace_prefix(PartyNum, MsrnPfxStp, MsrnPfxMsc),
|
|
% Second: convert to international number, if it is national
|
|
isup_party_internationalize(Num1, InternPfx);
|
|
_ ->
|
|
PartyNum
|
|
end;
|
|
% MAC->STP: Mangle IAM national -> international
|
|
mangle_isup_number(from_msc, ?ISUP_MSGT_IAM, NumType, PartyNum) ->
|
|
case NumType of
|
|
?ISUP_PAR_CALLED_P_NUM ->
|
|
{ok, InternPfx} = application:get_env(mgw_nat, intern_pfx),
|
|
isup_party_internationalize(PartyNum, InternPfx);
|
|
_ ->
|
|
PartyNum
|
|
end;
|
|
% STP->MSC: Mangle connected number in response to IAM (national->international)
|
|
mangle_isup_number(from_stp, MsgT, NumType, PartyNum) when MsgT == ?ISUP_MSGT_CON;
|
|
MsgT == ?ISUP_MSGT_ANM ->
|
|
case NumType of
|
|
?ISUP_PAR_CONNECTED_NUM ->
|
|
{ok, InternPfx} = application:get_env(mgw_nat, intern_pfx),
|
|
isup_party_internationalize(PartyNum, InternPfx);
|
|
_ ->
|
|
PartyNum
|
|
end;
|
|
% default case: no rewrite
|
|
mangle_isup_number(from_msc, _, _, PartyNum) ->
|
|
PartyNum.
|
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
% low-level number manipulation routines
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
|
% replace the prefix of PartyNum with NewPfx _if_ the current prefix matches MatchPfx
|
|
isup_party_replace_prefix(PartyNum, MatchPfxInt, NewPfxInt) ->
|
|
DigitsIn = PartyNum#party_number.phone_number,
|
|
NewPfx = osmo_util:int2digit_list(NewPfxInt),
|
|
MatchPfx = osmo_util:int2digit_list(MatchPfxInt),
|
|
MatchPfxLen = length(MatchPfx),
|
|
Pfx = lists:sublist(DigitsIn, 1, MatchPfxLen),
|
|
if Pfx == MatchPfx ->
|
|
Trailer = lists:sublist(DigitsIn, MatchPfxLen+1, length(DigitsIn)-MatchPfxLen),
|
|
DigitsOut = NewPfx ++ Trailer,
|
|
io:format("Prefix rewrite: ~p -> ~p~n", [DigitsIn, DigitsOut]);
|
|
true ->
|
|
io:format("Prefix rewrite: NO MATCH (~p != ~p)~n", [Pfx, MatchPfx]),
|
|
DigitsOut = DigitsIn
|
|
end,
|
|
PartyNum#party_number{phone_number = DigitsOut}.
|
|
|
|
% ensure the number is in international format. If it is national, add
|
|
% CountryCodeInt and switch type to international. If it's already
|
|
% national, just leave it at that.
|
|
isup_party_internationalize(PartyNum, CountryCodeInt) ->
|
|
#party_number{phone_number = DigitsIn, nature_of_addr_ind = Nature} = PartyNum,
|
|
CountryCode = osmo_util:int2digit_list(CountryCodeInt),
|
|
case Nature of
|
|
?ISUP_ADDR_NAT_NATIONAL ->
|
|
DigitsOut = CountryCode ++ DigitsIn,
|
|
NatureOut = ?ISUP_ADDR_NAT_INTERNATIONAL,
|
|
io:format("Internationalize: ~p -> ~p~n", [DigitsIn, DigitsOut]);
|
|
_ ->
|
|
DigitsOut = DigitsIn,
|
|
NatureOut = Nature
|
|
end,
|
|
PartyNum#party_number{phone_number = DigitsOut, nature_of_addr_ind = NatureOut}.
|
|
|
|
% take something like {national, 00493024033902} and make {intl, 493024033902}
|
|
isup_party_nat00_internationalize(PartyNum) ->
|
|
#party_number{phone_number = DigitsIn, nature_of_addr_ind = Nature} = PartyNum,
|
|
case Nature of
|
|
?ISUP_ADDR_NAT_NATIONAL ->
|
|
if length(DigitsIn) < 2 ->
|
|
{Pfx, Remain} = {DigitsIn, []};
|
|
true ->
|
|
{Pfx, Remain} = lists:split(2, DigitsIn)
|
|
end,
|
|
if Pfx == [0, 0] ->
|
|
DigitsOut = Remain,
|
|
NatureOut = ?ISUP_ADDR_NAT_INTERNATIONAL,
|
|
io:format("National+00 -> International: ~p -> ~p~n",
|
|
[DigitsIn, DigitsOut]);
|
|
true ->
|
|
DigitsOut = DigitsIn,
|
|
NatureOut = Nature
|
|
end;
|
|
_ ->
|
|
DigitsOut = DigitsIn,
|
|
NatureOut = Nature
|
|
end,
|
|
PartyNum#party_number{phone_number = DigitsOut, nature_of_addr_ind = NatureOut}.
|
|
|
|
% ensure that the resulting number is in national format. If the input
|
|
% is national, there is no change. If the input is international, _and_
|
|
% the country code equals CountryCodeInt, strip the CC and convert to
|
|
% national.
|
|
isup_party_nationalize(PartyNum, CountryCodeInt) ->
|
|
#party_number{phone_number = DigitsIn, nature_of_addr_ind = Nature} = PartyNum,
|
|
CountryCode = osmo_util:int2digit_list(CountryCodeInt),
|
|
CountryCodeLen = length(CountryCode),
|
|
case Nature of
|
|
?ISUP_ADDR_NAT_INTERNATIONAL ->
|
|
Pfx = lists:sublist(DigitsIn, CountryCodeLen),
|
|
if Pfx == CountryCode ->
|
|
DigitsOut = lists:sublist(DigitsIn, CountryCodeLen+1,
|
|
length(DigitsIn)-CountryCodeLen),
|
|
NatureOut = ?ISUP_ADDR_NAT_NATIONAL,
|
|
io:format("Nationalize: ~p -> ~p~n", [DigitsIn, DigitsOut]);
|
|
true ->
|
|
DigitsOut = DigitsIn,
|
|
NatureOut = Nature
|
|
end;
|
|
_ ->
|
|
DigitsOut = DigitsIn,
|
|
NatureOut = Nature
|
|
end,
|
|
PartyNum#party_number{phone_number = DigitsOut, nature_of_addr_ind = NatureOut}.
|