blob: 82ba3269ace708dfb9a3fd0c839f76a71e0b3fc2 [file] [log] [blame]
Harald Welte3bf7cb62011-04-03 00:25:34 +02001% M3UA in accordance with RFC4666 (http://tools.ietf.org/html/rfc4666)
2
3% (C) 2011 by Harald Welte <laforge@gnumonks.org>
4%
5% All Rights Reserved
6%
7% This program is free software; you can redistribute it and/or modify
8% it under the terms of the GNU Affero General Public License as
9% published by the Free Software Foundation; either version 3 of the
10% License, or (at your option) any later version.
11%
12% This program is distributed in the hope that it will be useful,
13% but WITHOUT ANY WARRANTY; without even the implied warranty of
14% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15% GNU General Public License for more details.
16%
17% You should have received a copy of the GNU Affero General Public License
18% along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20-module(m3ua_core).
21-author('Harald Welte <laforge@gnumonks.org>').
22
23-include_lib("kernel/include/inet_sctp.hrl").
24-include("sccp.hrl").
25-include("m3ua.hrl").
26
27-export([start_link/1]).
28
Harald Welteb2d3abf2011-04-04 11:26:11 +020029-export([init/1, terminate/3, code_change/4, handle_event/3, handle_info/3]).
Harald Welte3bf7cb62011-04-03 00:25:34 +020030
31% FSM states:
32-export([asp_down/2, asp_inactive/2, asp_active/2]).
33
34-define(T_ACK_TIMEOUT, 2*60*100).
35
36% Loop Data
37-record(m3ua_state, {
38 role, % asp | sgp
39 asp_state, % down, inactive, active
40 t_ack,
41 user_pid,
42 sctp_remote_ip,
43 sctp_remote_port,
Harald Welte8a0ab002011-04-03 22:16:12 +020044 sctp_local_port,
Harald Welte3bf7cb62011-04-03 00:25:34 +020045 sctp_sock,
46 sctp_assoc_id
47 }).
48
49start_link(InitOpts) ->
50 gen_fsm:start_link(?MODULE, InitOpts, [{debug, [trace]}]).
51
52reconnect_sctp(L = #m3ua_state{sctp_remote_ip = Ip, sctp_remote_port = Port, sctp_sock = Sock}) ->
53 io:format("SCTP Reconnect ~p:~p~n", [Ip, Port]),
Harald Welte8a0ab002011-04-03 22:16:12 +020054 InitMsg = #sctp_initmsg{num_ostreams = 2, max_instreams = 2},
Harald Welte3bf7cb62011-04-03 00:25:34 +020055 case gen_sctp:connect(Sock, Ip, Port, [{active, once}, {reuseaddr, true},
56 {sctp_initmsg, InitMsg}]) of
57 {ok, Assoc} ->
58 L#m3ua_state{sctp_assoc_id = Assoc#sctp_assoc_change.assoc_id};
59 {error, Error } ->
Harald Welte6fc5b292011-04-04 10:38:30 +020060 io:format("SCTP Error ~p, reconnecting~n", [Error]),
Harald Welte3bf7cb62011-04-03 00:25:34 +020061 reconnect_sctp(L)
62 end.
63
64init(InitOpts) ->
Harald Welte8a0ab002011-04-03 22:16:12 +020065 OpenOptsBase = [{active, once}, {reuseaddr, true}],
66 LocalPort = proplists:get_value(sctp_local_port, InitOpts),
67 case LocalPort of
68 undefined ->
69 OpenOpts = OpenOptsBase;
70 _ ->
71 OpenOpts = OpenOptsBase ++ [{port, LocalPort}]
72 end,
73 {ok, SctpSock} = gen_sctp:open(OpenOpts),
Harald Welte3bf7cb62011-04-03 00:25:34 +020074 LoopDat = #m3ua_state{role = asp, sctp_sock = SctpSock,
75 user_pid = proplists:get_value(user_pid, InitOpts),
76 sctp_remote_ip = proplists:get_value(sctp_remote_ip, InitOpts),
Harald Welte8a0ab002011-04-03 22:16:12 +020077 sctp_remote_port = proplists:get_value(sctp_remote_port, InitOpts),
78 sctp_local_port = LocalPort},
Harald Welte3bf7cb62011-04-03 00:25:34 +020079 LoopDat2 = reconnect_sctp(LoopDat),
80 {ok, asp_down, LoopDat2}.
81
Harald Welteb2d3abf2011-04-04 11:26:11 +020082terminate(Reason, _State, LoopDat) ->
83 io:format("Terminating ~p (Reason: ~p)~n", [?MODULE, Reason]),
84 gen_sctp:close(LoopDat#m3ua_state.sctp_sock).
85
86code_change(_OldVsn, StateName, StateData, _Extra) ->
87 {ok, StateName, StateData}.
88
Harald Welte3bf7cb62011-04-03 00:25:34 +020089% Helper function to send data to the SCTP peer
90send_sctp_to_peer(LoopDat, PktData) when is_binary(PktData) ->
91 #m3ua_state{sctp_sock = Sock, sctp_assoc_id = Assoc} = LoopDat,
92 SndRcvInfo = #sctp_sndrcvinfo{assoc_id = Assoc, ppid = 3, stream = 0},
93 gen_sctp:send(Sock, SndRcvInfo, PktData);
94
95% same as above, but for un-encoded #m3ua_msg{}
96send_sctp_to_peer(LoopDat, M3uaMsg) when is_record(M3uaMsg, m3ua_msg) ->
97 MsgBin = m3ua_codec:encode_m3ua_msg(M3uaMsg),
98 send_sctp_to_peer(LoopDat, MsgBin).
99
100% helper to send one of the up/down/act/inact management messages + start timer
101send_msg_start_tack(LoopDat, State, MsgClass, MsgType, Params) ->
102 % generate and send the respective message
103 Msg = #m3ua_msg{version = 1, msg_class = MsgClass, msg_type = MsgType, payload = Params},
104 send_sctp_to_peer(LoopDat, Msg),
105 % start T(ack) timer and wait for ASP_UP_ACK
Harald Welte6fc5b292011-04-04 10:38:30 +0200106 {ok, Tack} = timer:apply_after(?T_ACK_TIMEOUT, gen_fsm, send_event,
Harald Welte3bf7cb62011-04-03 00:25:34 +0200107 [self(), {timer_expired, t_ack, {MsgClass, MsgType, Params}}]),
108 {next_state, State, LoopDat#m3ua_state{t_ack = Tack}}.
109
110
Harald Welte3bf7cb62011-04-03 00:25:34 +0200111
Harald Welte6fc5b292011-04-04 10:38:30 +0200112handle_event(Event, State, LoopDat) ->
113 io:format("Unknown Event ~p in state ~p~n", [Event, State]),
114 {next_state, State, LoopDat}.
115
116
117
118handle_info({sctp, Socket, _RemoteIp, _RemotePort, {ANC, SAC}},
Harald Welte3bf7cb62011-04-03 00:25:34 +0200119 _State, LoopDat) when is_record(SAC, sctp_assoc_change) ->
120 io:format("SCTP Assoc Change ~p ~p~n", [ANC, SAC]),
Harald Welte6fc5b292011-04-04 10:38:30 +0200121 #sctp_assoc_change{state = SacState, outbound_streams = _OutStreams,
Harald Welteb2d3abf2011-04-04 11:26:11 +0200122 inbound_streams = _InStreams, assoc_id = _AssocId} = SAC,
Harald Welte3bf7cb62011-04-03 00:25:34 +0200123 case SacState of
124 comm_up ->
125 % FIXME: primmitive to the user
126 LoopDat2 = LoopDat;
127 comm_lost ->
128 LoopDat2 = reconnect_sctp(LoopDat);
129 addr_unreachable ->
130 LoopDat2 = reconnect_sctp(LoopDat)
131 end,
132 inet:setopts(Socket, [{active, once}]),
133 {next_state, asp_down, LoopDat2};
134
Harald Welte6fc5b292011-04-04 10:38:30 +0200135handle_info({sctp, Socket, RemoteIp, RemotePort, {[Anc], Data}}, State, LoopDat) ->
Harald Welte3bf7cb62011-04-03 00:25:34 +0200136 io:format("SCTP rx data: ~p ~p~n", [Anc, Data]),
137 % FIXME: process incoming SCTP data
Harald Welte6fc5b292011-04-04 10:38:30 +0200138 if Socket == LoopDat#m3ua_state.sctp_sock,
139 RemoteIp == LoopDat#m3ua_state.sctp_remote_ip,
140 RemotePort == LoopDat#m3ua_state.sctp_remote_port,
141 3 == Anc#sctp_sndrcvinfo.ppid ->
142 Ret = rx_sctp(Anc, Data, State, LoopDat);
143 true ->
144 io:format("unknown SCTP: ~p ~p~n", [Anc, Data]),
145 Ret = {next_state, State, LoopDat}
146 end,
Harald Welte3bf7cb62011-04-03 00:25:34 +0200147 inet:setopts(Socket, [{active, once}]),
Harald Welte6fc5b292011-04-04 10:38:30 +0200148 Ret;
Harald Welte3bf7cb62011-04-03 00:25:34 +0200149
Harald Welte6fc5b292011-04-04 10:38:30 +0200150handle_info({sctp, Socket, RemoteIp, RemotePort, {_Anc, Data}}, _State, LoopDat)
Harald Welte3bf7cb62011-04-03 00:25:34 +0200151 when is_record(Data, sctp_shutdown_event) ->
152 io:format("SCTP remote ~p:~p shutdown~n", [RemoteIp, RemotePort]),
153 inet:setopts(Socket, [{active, once}]),
154 {next_state, asp_down, LoopDat}.
155
156
157
158asp_down(#primitive{subsystem = 'M', gen_name = 'ASP_UP',
Harald Welte6fc5b292011-04-04 10:38:30 +0200159 spec_name = request, parameters = _Params}, LoopDat) ->
160 send_msg_start_tack(LoopDat, asp_down, ?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPUP, []);
Harald Welte3bf7cb62011-04-03 00:25:34 +0200161asp_down({timer_expired, t_ack, {?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPUP, Params}}, LoopDat) ->
162 send_msg_start_tack(LoopDat, asp_down, ?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPUP, Params);
163
164asp_down(#m3ua_msg{msg_class = ?M3UA_MSGC_ASPSM,
165 msg_type = ?M3UA_MSGT_ASPSM_ASPUP_ACK}, LoopDat) ->
Harald Welte6fc5b292011-04-04 10:38:30 +0200166 timer:cancel(LoopDat#m3ua_state.t_ack),
Harald Welte3bf7cb62011-04-03 00:25:34 +0200167 % transition into ASP_INACTIVE
Harald Welte6fc5b292011-04-04 10:38:30 +0200168 {next_state, asp_inactive, LoopDat};
169
170asp_down(M3uaMsg, LoopDat) when is_record(M3uaMsg, m3ua_msg) ->
171 rx_m3ua(M3uaMsg, asp_down, LoopDat).
Harald Welte3bf7cb62011-04-03 00:25:34 +0200172
173
Harald Welte6fc5b292011-04-04 10:38:30 +0200174asp_inactive(#primitive{subsystem = 'M', gen_name = 'ASP_ACTIVE',
175 spec_name = request, parameters = _Params}, LoopDat) ->
176 send_msg_start_tack(LoopDat, asp_inactive, ?M3UA_MSGC_ASPTM, ?M3UA_MSGT_ASPTM_ASPAC,
177 [{?M3UA_IEI_TRAF_MODE_TYPE, <<0,0,0,1>>}]);
Harald Welte3bf7cb62011-04-03 00:25:34 +0200178
179asp_inactive({timer_expired, t_ack, {?M3UA_MSGC_ASPTM, ?M3UA_MSGT_ASPTM_ASPAC, Params}}, LoopDat) ->
180 send_msg_start_tack(LoopDat, asp_inactive, ?M3UA_MSGC_ASPTM, ?M3UA_MSGT_ASPTM_ASPAC, Params);
181
182asp_inactive(#primitive{subsystem = 'M', gen_name = 'ASP_DOWN',
Harald Welte6fc5b292011-04-04 10:38:30 +0200183 spec_name = request, parameters = _Params}, LoopDat) ->
184 send_msg_start_tack(LoopDat, asp_inactive, ?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPDN, []);
Harald Welte3bf7cb62011-04-03 00:25:34 +0200185
186asp_inactive({timer_expired, t_ack, {?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPDN, Params}}, LoopDat) ->
187 send_msg_start_tack(LoopDat, asp_inactive, ?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPDN, Params);
188
189asp_inactive(#m3ua_msg{msg_class = ?M3UA_MSGC_ASPTM,
190 msg_type = ?M3UA_MSGT_ASPTM_ASPAC_ACK}, LoopDat) ->
Harald Welte6fc5b292011-04-04 10:38:30 +0200191 timer:cancel(LoopDat#m3ua_state.t_ack),
Harald Welte3bf7cb62011-04-03 00:25:34 +0200192 % transition into ASP_ACTIVE
193 % FIXME: signal this to the user
194 {next_state, asp_active, LoopDat};
195
196asp_inactive(#m3ua_msg{msg_class = ?M3UA_MSGC_ASPSM,
197 msg_type = ?M3UA_MSGT_ASPSM_ASPDN_ACK}, LoopDat) ->
Harald Welte6fc5b292011-04-04 10:38:30 +0200198 timer:cancel(LoopDat#m3ua_state.t_ack),
Harald Welte3bf7cb62011-04-03 00:25:34 +0200199 % transition into ASP_DOWN
200 % FIXME: signal this to the user
Harald Welte6fc5b292011-04-04 10:38:30 +0200201 {next_state, asp_down, LoopDat};
202
203asp_inactive(M3uaMsg, LoopDat) when is_record(M3uaMsg, m3ua_msg) ->
204 rx_m3ua(M3uaMsg, asp_inactive, LoopDat).
Harald Welte3bf7cb62011-04-03 00:25:34 +0200205
206
207
208asp_active(#m3ua_msg{msg_class = ?M3UA_MSGC_ASPSM,
209 msg_type = ?M3UA_MSGT_ASPSM_ASPDN_ACK}, LoopDat) ->
Harald Welte6fc5b292011-04-04 10:38:30 +0200210 timer:cancel(LoopDat#m3ua_state.t_ack),
Harald Welte3bf7cb62011-04-03 00:25:34 +0200211 % transition into ASP_DOWN
212 % FIXME: signal this to the user
213 {next_state, asp_down, LoopDat};
214
215asp_active(#m3ua_msg{msg_class = ?M3UA_MSGC_ASPTM,
216 msg_type = ?M3UA_MSGT_ASPTM_ASPIA_ACK}, LoopDat) ->
Harald Welte6fc5b292011-04-04 10:38:30 +0200217 timer:cancel(LoopDat#m3ua_state.t_ack),
Harald Welte3bf7cb62011-04-03 00:25:34 +0200218 % transition into ASP_INACTIVE
219 % FIXME: signal this to the user
220 {next_state, asp_inactive, LoopDat};
221
222asp_active(#primitive{subsystem = 'M', gen_name = 'ASP_DOWN',
Harald Welte6fc5b292011-04-04 10:38:30 +0200223 spec_name = request, parameters = _Params}, LoopDat) ->
224 send_msg_start_tack(LoopDat, asp_active, ?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPDN, []);
Harald Welte3bf7cb62011-04-03 00:25:34 +0200225
226asp_active({timer_expired, t_ack, {?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPDN, Params}}, LoopDat) ->
227 send_msg_start_tack(LoopDat, asp_active, ?M3UA_MSGC_ASPSM, ?M3UA_MSGT_ASPSM_ASPDN, Params);
228
229asp_active(#primitive{subsystem = 'M', gen_name = 'ASP_INACTIVE',
Harald Welte6fc5b292011-04-04 10:38:30 +0200230 spec_name = request, parameters = _Params}, LoopDat) ->
231 send_msg_start_tack(LoopDat, asp_active, ?M3UA_MSGC_ASPTM, ?M3UA_MSGT_ASPTM_ASPIA, []);
Harald Welte3bf7cb62011-04-03 00:25:34 +0200232
233asp_active({timer_expired, t_ack, {?M3UA_MSGC_ASPTM, ?M3UA_MSGT_ASPTM_ASPIA, Params}}, LoopDat) ->
234 send_msg_start_tack(LoopDat, asp_active, ?M3UA_MSGC_ASPTM, ?M3UA_MSGT_ASPTM_ASPIA, Params);
235
236asp_active(#primitive{subsystem = 'MTP', gen_name = 'TRANSFER',
237 spec_name = request, parameters = Params}, LoopDat) ->
238 % Send message to remote peer
239 OptList = [{?M3UA_IEI_PROTOCOL_DATA, Params}],
240 Msg = #m3ua_msg{version = 1, msg_class = ?M3UA_MSGC_TRANSFER,
241 msg_type = ?M3UA_MSGT_XFR_DATA,
242 payload = OptList},
243 send_sctp_to_peer(LoopDat, Msg),
244 {next_state, asp_active, LoopDat};
245asp_active(#m3ua_msg{version = 1, msg_class = ?M3UA_MSGC_TRANSFER,
246 msg_type = ?M3UA_MSGT_XFR_DATA, payload = Params}, LoopDat) ->
247 % FIXME: Send primitive to the user
Harald Welte6fc5b292011-04-04 10:38:30 +0200248 {next_state, asp_active, LoopDat};
249asp_active(#m3ua_msg{msg_class = ?M3UA_MSGC_ASPTM,
250 msg_type = ?M3UA_MSGT_ASPTM_ASPIA_ACK}, LoopDat) ->
251 timer:cancel(LoopDat#m3ua_state.t_ack),
252 % transition to ASP_INACTIVE
253 {next_state, asp_inactive, LoopDat};
254
255
256
257asp_active(M3uaMsg, LoopDat) when is_record(M3uaMsg, m3ua_msg) ->
258 rx_m3ua(M3uaMsg, asp_active, LoopDat).
259
260
Harald Welteb2d3abf2011-04-04 11:26:11 +0200261rx_sctp(_Anc, Data, State, LoopDat) ->
Harald Welte6fc5b292011-04-04 10:38:30 +0200262 M3uaMsg = m3ua_codec:parse_m3ua_msg(Data),
263 gen_fsm:send_event(self(), M3uaMsg),
264 {next_state, State, LoopDat}.
265
266
267rx_m3ua(Msg = #m3ua_msg{version = 1, msg_class = ?M3UA_MSGC_MGMT,
268 msg_type = ?M3UA_MSGT_MGMT_NTFY}, State, LoopDat) ->
269 io:format("M3UA NOTIFY~n"),
270 {next_state, State, LoopDat};
271
272rx_m3ua(Msg = #m3ua_msg{version = 1, msg_class = ?M3UA_MSGC_ASPSM,
273 msg_type = ?M3UA_MSGT_ASPSM_BEAT}, State, LoopDat) ->
274 % Send BEAT_ACK using the same payload as the BEAT msg
275 io:format("M3UA BEAT~n"),
276 send_sctp_to_peer(LoopDat, Msg#m3ua_msg{msg_type = ?M3UA_MSGT_ASPSM_BEAT_ACK}),
277 {next_state, State, LoopDat};
278
279rx_m3ua(Msg = #m3ua_msg{}, State, LoopDat) ->
280 io:format("M3UA Unknown messge ~p in state ~p~n", [Msg, State]),
281 {next_state, State, LoopDat}.