| % Internal SS7 route database keeping |
| |
| % (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/>. |
| |
| |
| % this module is keeping the point code routing table for the MTP3 layer |
| % of the Omsocom SS7 protocol stack. Routes are created and deleted |
| % with create_route() and delete_route(), the arguments are |
| % * destination point code |
| % * point code mask |
| % * name of the linkset |
| % |
| % there is one function to actually make a routing decision: route_dpc/1 |
| % with a single argument: the destination point code. |
| |
| -module(ss7_routes). |
| -behaviour(gen_server). |
| |
| -include_lib("osmo_ss7/include/mtp3.hrl"). |
| |
| % gen_fsm callbacks |
| -export([init/1, handle_call/3, handle_info/2, terminate/2, code_change/3]). |
| |
| % our published API |
| -export([start_link/0]). |
| |
| % client functions, may internally talk to our sccp_user server |
| -export([create_route/3, delete_route/3]). |
| -export([dump/0]). |
| -export([route_dpc/1]). |
| |
| -record(ss7route, { |
| remote_pc_mask, % {remote_pc, remote_pc_mask} |
| linkset_name |
| }). |
| |
| -record(sr_state, { |
| route_tbl |
| }). |
| |
| % initialization code |
| |
| start_link() -> |
| gen_server:start_link({local, ?MODULE}, ?MODULE, [], [{debug, [trace]}]). |
| |
| init(_Arg) -> |
| RouteTbl = ets:new(ss7_routes, [ordered_set, named_table, |
| {keypos, #ss7route.remote_pc_mask}]), |
| process_flag(trap_exit, true), |
| {ok, #sr_state{route_tbl = RouteTbl}}. |
| |
| % client side API |
| |
| % all write operations go through gen_server:call(), as only the ?MODULE |
| % process has permission to modify the table content |
| |
| create_route(RemotePcIn, RemoteMask, LinksetName) -> |
| RemotePc = osmo_util:pointcode2int(RemotePcIn), |
| gen_server:call(?MODULE, {create_route, {RemotePc, RemoteMask, LinksetName}}). |
| |
| delete_route(RemotePcIn, RemoteMask, LinksetName) -> |
| RemotePc = osmo_util:pointcode2int(RemotePcIn), |
| gen_server:call(?MODULE, {delete_route, {RemotePc, RemoteMask, LinksetName}}). |
| |
| % the lookup functions can directly use the ets named_table from within |
| % the client process, no need to go through a synchronous IPC |
| |
| route_dpc(DpcIn) -> |
| Dpc = osmo_util:pointcode2int(DpcIn), |
| % this was generated by ets:fun2ms() on the shell |
| Match = [{#ss7route{remote_pc_mask={'$1','$2'},linkset_name='$3'}, |
| [{'==',{'band',Dpc,'$2'},'$1'}], |
| ['$3']}], |
| case ets:select(ss7_routes, Match) of |
| [Name|_] -> |
| {ok, Name}; |
| _ -> |
| {error, no_route} |
| end. |
| |
| dump() -> |
| List = ets:tab2list(ss7_routes), |
| dump_routes(List). |
| |
| dump_routes([]) -> |
| ok; |
| dump_routes([Head|Tail]) when is_record(Head, ss7route) -> |
| dump_single_route(Head), |
| dump_routes(Tail). |
| |
| dump_single_route(#ss7route{remote_pc_mask = {Pc, Mask}, |
| linkset_name = Name}) -> |
| PcTuple = osmo_util:pointcode_fmt(itu, Pc), |
| MaskTuple = osmo_util:pointcode_fmt(itu, Mask), |
| io:format("Dest PC ~p/~p -> Linkset ~p~n", |
| [PcTuple, MaskTuple, Name]). |
| |
| % server side code |
| |
| handle_call({create_route, {RemotePc, RemoteMask, Name}}, |
| {_FromPid, _FromRef}, S) -> |
| #sr_state{route_tbl = Tbl} = S, |
| R = #ss7route{remote_pc_mask = {RemotePc, RemoteMask}, |
| linkset_name = Name}, |
| case ets:insert_new(Tbl, R) of |
| false -> |
| {reply, {error, ets_insert}, S}; |
| _ -> |
| {reply, ok, S} |
| end; |
| |
| handle_call({delete_route, {RemotePc, RemoteMask, _Name}}, |
| {_FromPid, _FromRef}, S) -> |
| #sr_state{route_tbl = Tbl} = S, |
| ets:delete(Tbl, {RemotePc, RemoteMask}), |
| {reply, ok, S}. |
| |
| handle_info(Info, S) -> |
| error_logger:error_report(["unknown handle_info", |
| {module, ?MODULE}, |
| {info, Info}, {state, S}]), |
| {noreply, S}. |
| |
| terminate(Reason, _S) -> |
| io:format("terminating ~p with reason ~p", [?MODULE, Reason]), |
| ok. |
| |
| code_change(_OldVsn, State, _Extra) -> |
| {ok, State}. |