• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

travelping / ergw_aaa / 820419574

pending completion
820419574

push

github

vkatsuba
add nodelay support for SCTP

0 of 2 new or added lines in 1 file covered. (0.0%)

2368 of 10511 relevant lines covered (22.53%)

153.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.84
/src/ergw_aaa_diameter.erl
1
%% Copyright 2018, Travelping GmbH <info@travelping.com>
2

3
%% This program is free software; you can redistribute it and/or
4
%% modify it under the terms of the GNU General Public License
5
%% as published by the Free Software Foundation; either version
6
%% 2 of the License, or (at your option) any later version.
7

8
-module(ergw_aaa_diameter).
9

10
-compile({parse_transform, cut}).
11

12
%% API
13
-export([validate_function/1,
14
         initialize_function/2]).
15
-export(['3gpp_from_session'/2, qos_from_session/1]).
16
-export([encode_ipv6prefix/1, decode_ipv6prefix/1]).
17
-export([validate_termination_cause_mapping/1]).
18

19
-include_lib("kernel/include/inet.hrl").
20
-include_lib("kernel/include/logger.hrl").
21
-include_lib("diameter/include/diameter.hrl").
22
-include_lib("diameter/include/diameter_gen_base_rfc6733.hrl").
23
-include("include/diameter_3gpp_ts29_061_sgi.hrl").
24

25
%% RFC: https://tools.ietf.org/html/rfc3588#section-8.15
26
-define(DEFAULT_TERMINATION_CAUSE_MAPPING, [
27
    {normal, 1},                 % ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'
28
    {administrative, 4},         % ?'DIAMETER_BASE_TERMINATION-CAUSE_ADMINISTRATIVE'
29
    {link_broken, 5},            % ?'DIAMETER_BASE_TERMINATION-CAUSE_LINK_BROKEN'
30
    {upf_failure, 5},            % ?'DIAMETER_BASE_TERMINATION-CAUSE_LINK_BROKEN'
31
    {remote_failure, 1},         % ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'
32
    {cp_inactivity_timeout, 1},  % ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'
33
    {up_inactivity_timeout, 1},  % ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'
34
    {peer_restart, 1},           % ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'
35
    {'ASR', 1},                  % ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'
36
    {error, 1},                  % ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'
37
    {req_timeout, 1},            % ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'
38
    {conn_error, 1},             % ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'
39
    {rate_limit, 1},             % ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'
40
    {ocs_hold_end, 1},           % ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'
41
    {peer_reject, 1}             % ?'DIAMETER_BASE_TERMINATION-CAUSE_LOGOUT'
42
]).
43

44
-define(VENDOR_ID_3GPP, 10415).
45
-define(VENDOR_ID_ETSI, 13019).
46
-define(VENDOR_ID_TP,   18681).
47

48
-define(DefaultFunctionOptions, [{transports, undefined},
49
                                 {'Origin-Host', undefined},
50
                                 {'Origin-Realm', undefined}
51
                                 ]).
52
-define(DefaultTransportOptions, [{connect_to, undefined}, 
53
                                  {unordered, true},
54
                                  {reuseaddr, true},
55
                                  {nodelay, true}
56
                                 ]).
57

58
-define(IS_IPv4(X), (is_tuple(X) andalso tuple_size(X) == 4)).
59
-define(IS_IPv6(X), (is_tuple(X) andalso tuple_size(X) == 8)).
60
-define(IS_IP(X), (is_tuple(X) andalso (tuple_size(X) == 4 orelse tuple_size(X) == 8))).
61
-define(non_empty_opts(X), ((is_list(X) andalso length(X) /= 0) orelse
62
                            (is_map(X) andalso map_size(X) /= 0))).
63

64
%%===================================================================
65
%% API
66
%%===================================================================
67

68
initialize_function(Id, #{'Origin-Host' := {OriginHost, Addr},
69
                          'Origin-Realm' := OriginRealm,
70
                          transports := Transports}) ->
71
    ProductName = application:get_env(ergw_aaa, product_name, "erGW-AAA"),
48✔
72

73
    %% adding the DIAMETER RFC base dictionary controls some options in
74
    %% the Erlang diameter app
75
    Base6733 = [{alias, base6733},
48✔
76
                {dictionary, diameter_gen_base_rfc6733},
77
                {module, [?MODULE, base6733]}],
78
    SvcOpts0 = #{'Origin-Host' => OriginHost,
48✔
79
                 'Origin-Realm' => OriginRealm,
80
                 'Origin-State-Id' => diameter:origin_state_id(),
81
                 'Host-IP-Address' => [Addr],
82
                 'Vendor-Id' => ?VENDOR_ID_TP,
83
                 'Product-Name' => ProductName,
84
                 'Supported-Vendor-Id' => [?VENDOR_ID_3GPP,
85
                                           ?VENDOR_ID_ETSI,
86
                                           ?VENDOR_ID_TP],
87
                 string_decode => false,
88
                 decode_format => map,
89
                 application => sets:from_list([Base6733])
90
                },
91
    SvcOpts = merge_svc(SvcOpts0, ergw_aaa_diameter_srv:get_service_opts(Id)),
48✔
92
    ok = diameter:start_service(Id, svc_to_opts(SvcOpts)),
48✔
93
    [ok = initialize_transport(Id, X) || X <- Transports],
48✔
94
    {ok, []}.
48✔
95

96
initialize_transport(Id, #{connect_to :=
97
                               #diameter_uri{type = _AAA, % aaa | aaas
98
                                             fqdn = Host,
99
                                             port = Port,
100
                                             transport = Transport,
101
                                             protocol = _Diameter}} = Opts) ->
102
    Caps = maps:fold(fun build_transport_caps/3, [], Opts),
72✔
103
    {ok, {Raddr, Type}} = resolve_hostname(Host),
72✔
104
    TransportOpts = [{capabilities, Caps},
72✔
105
                     {transport_module, transport_module(Transport)},
106
                     {transport_config, transport_config(Transport, Type, Raddr, Port, Opts)}],
107
    {ok, _} = diameter:add_transport(Id, {connect, TransportOpts}),
72✔
108
    ok.
72✔
109

110
%%%===================================================================
111
%%% Options Validation
112
%%%===================================================================
113

114
validate_capability('Origin-Host', {Host, Addr} = Value)
115
  when is_binary(Host), ?IS_IP(Addr) ->
116
    Value;
8✔
117
validate_capability('Origin-Host' = Opt, Value) when is_binary(Value) ->
118
    try
728✔
119
        {ok, {Addr, _Type}} = resolve_hostname(Value),
728✔
120
        {Value, Addr}
720✔
121
    catch _:_ -> validate_capability_error(Opt, Value)
8✔
122
    end;
123
validate_capability('Origin-Realm', Value) when is_binary(Value) ->
124
    Value;
720✔
125
validate_capability(Opt, Value) ->
126
    validate_capability_error(Opt, Value).
16✔
127

128
validate_capability_error(Opt, Value) ->
129
    throw({error, {options, {Opt, Value}}}).
24✔
130

131
validate_function(Opts) ->
132
    ergw_aaa_config:validate_options(fun validate_function/2, Opts,
744✔
133
                                     ?DefaultFunctionOptions, map).
134

135
validate_function(transports, Opts) when ?non_empty_opts(Opts) ->
136
    lists:map(
696✔
137
      ergw_aaa_config:validate_options(fun validate_transport/2, _,
720✔
138
                                       ?DefaultTransportOptions, map), Opts);
139
validate_function(K, V)
140
  when K =:= 'Origin-Host'; K =:= 'Origin-Realm' ->
141
    validate_capability(K, V);
1,472✔
142
validate_function(Opt, Value) ->
143
    validate_function_error(Opt, Value).
24✔
144

145
validate_function_error(Opt, Value) ->
146
    throw({error, {options, {Opt, Value}}}).
24✔
147

148
validate_transport(connect_to, Value) when is_record(Value, diameter_uri) ->
149
    Value;
8✔
150
validate_transport(connect_to = Opt, Value) when is_binary(Value) ->
151
    try
704✔
152
        #diameter_uri{} =
704✔
153
            diameter_types:'DiameterURI'(decode, Value, #{rfc => 6733})
154
    catch _:_ -> validate_transport_error(Opt, Value)
8✔
155
    end;
156
validate_transport(K, V)
157
  when K =:= 'Origin-Host'; K =:= 'Origin-Realm' ->
158
    validate_capability(K, V);
×
159
validate_transport(fragment_timer, Value) when Value =:= infinity ->
160
    Value;
×
161
validate_transport(fragment_timer, Value) 
162
  when is_integer(Value), Value >= 0, Value =< 16#FFFFFFFF ->
163
    Value;
8✔
164
validate_transport(recbuf, Value) when is_integer(Value), Value >= 16*1024 ->
165
    Value;
8✔
166
validate_transport(sndbuf, Value) when is_integer(Value), Value >= 16*1024 ->
167
    Value;
8✔
168
validate_transport(reuseaddr, Value) when is_boolean(Value) ->
169
    Value;
664✔
170
validate_transport(unordered, Value) when is_boolean(Value) ->
171
    Value;
640✔
172
validate_transport(nodelay, Value) when is_boolean(Value) ->
173
    Value;
688✔
174
validate_transport(Opt, Value) ->
175
    validate_transport_error(Opt, Value).
72✔
176

177
validate_transport_error(Opt, Value) ->
178
    throw({error, {options, {Opt, Value}}}).
80✔
179

180
validate_termination_cause_mapping(Opts) when is_list(Opts); is_map(Opts) ->
181
    ergw_aaa_config:validate_options(fun validate_termination_cause_mapping/2, Opts, ?DEFAULT_TERMINATION_CAUSE_MAPPING, map);
2,840✔
182
validate_termination_cause_mapping(Opts) ->
183
    throw({error, {termination_cause_mapping, Opts}}).
24✔
184

185
validate_termination_cause_mapping(Opt, Value) when is_atom(Opt), is_integer(Value) ->
186
    Value;
42,600✔
187
validate_termination_cause_mapping(Opt, Value) ->
188
    throw({error, {termination_cause_mapping, {Opt, Value}}}).
×
189

190
%%===================================================================
191
%% internal helpers
192
%%===================================================================
193

194
resolve_hostname(Name) when is_binary(Name) -> resolve_hostname(binary_to_list(Name));
800✔
195
resolve_hostname(Name) ->
196
    Name1 = case inet:gethostbyname(Name, inet6) of
800✔
197
        {error, nxdomain} -> inet:gethostbyname(Name, inet);
800✔
198
        Other -> Other
×
199
    end,
200
    case Name1 of
800✔
201
        {ok, #hostent{h_addr_list = [LocalIP | _], h_addrtype = Type}} ->
202
            {ok, {LocalIP, Type}};
792✔
203
        _ -> erlang:error(badarg, Name)
8✔
204
    end.
205

206
transport_module(tcp) -> diameter_tcp;
72✔
207
transport_module(sctp) -> diameter_sctp;
×
208
transport_module(_) -> unknown.
×
209

210
transport_config(tcp, Type, Raddr, Port, Opts) ->
211
    [Type, {raddr, Raddr}, {rport, Port}
72✔
212
     | maps:to_list(maps:with([fragment_timer, reuseaddr, recbuf, sndbuf, nodelay], Opts))];
213
transport_config(sctp, Type, Raddr, Port, Opts0) ->
NEW
214
    Opts =
×
215
        [Type, {raddr, Raddr}, {rport, Port}
216
        | maps:to_list(maps:with([reuseaddr, recbuf, sndbuf, nodelay, unordered], Opts0))],
NEW
217
    proplists:substitute_aliases([{nodelay, sctp_nodelay}], Opts).
×
218

219
svc_set(Key, Value, Opts)
220
  when is_atom(Key), is_list(Value) ->
221
    Set = sets:from_list(Value),
88✔
222
    maps:update_with(Key, fun(V) -> sets:union(Set, V) end, Set, Opts);
88✔
223
svc_set(Key, Value, Opts)
224
  when is_atom(Key) ->
225
    maps:update_with(Key, fun(V) -> sets:add_element(Value, V) end,
80✔
226
                     sets:from_list([Value]), Opts).
227

228
merge_svc(Opts, Services) ->
229
    lists:foldl(fun(Service, OptsIn) -> maps:fold(fun merge_svc/3, OptsIn, Service) end,
48✔
230
                Opts, Services).
231

232
merge_svc(K, V, Opts)
233
  when K =:= 'Auth-Application-Id';
234
       K =:= 'Acct-Application-Id';
235
       K =:= 'Vendor-Specific-Application-Id' ->
236
    svc_set(K, V, Opts);
112✔
237
merge_svc(K, [V1|_] = V, Opts)
238
  when K =:= application, is_list(V1) ->
239
    svc_set(K, V, Opts);
24✔
240
merge_svc(K, V, Opts)
241
  when K =:= application, is_list(V) ->
242
    svc_set(K, [V], Opts).
32✔
243

244
svc_to_opts(Opts) ->
245
    maps:fold(fun svc_to_opts/3, [], Opts).
48✔
246

247
svc_to_opts(K, V, Opts)
248
  when K =:= 'Auth-Application-Id';
249
       K =:= 'Acct-Application-Id';
250
       K =:= 'Vendor-Specific-Application-Id' ->
251
    [{K, sets:to_list(V)} | Opts];
104✔
252
svc_to_opts(K, V, Opts)
253
  when K =:= application ->
254
    Opts ++ [{K, X} || X <- sets:to_list(V)];
48✔
255
svc_to_opts(K, V, Opts) ->
256
    [{K, V}|Opts].
432✔
257

258
build_transport_caps('Origin-Host', {Host, Addr}, Caps) ->
259
    [{'Origin-Host', Host}, {'Host-IP-Address', [Addr]} | Caps];
×
260
build_transport_caps('Origin-Realm', Realm, Caps) ->
261
    [{'Origin-Realm', Realm} | Caps];
×
262
build_transport_caps(_, _, Caps) ->
263
    Caps.
288✔
264

265
%%===================================================================
266
%% 3GPP IE
267
%%===================================================================
268

269
'3gpp_from_session'(Key, Value)
270
  when (Key =:= '3GPP-Charging-Gateway-Address' orelse
271
        Key =:= '3GPP-SGSN-Address' orelse
272
        Key =:= '3GPP-GGSN-Address') andalso
273
       ?IS_IPv4(Value) ->
274
    ergw_aaa_3gpp_dict:ip2bin(Value);
176✔
275

276
'3gpp_from_session'(Key, Value)
277
  when (Key =:= '3GPP-Charging-Gateway-IPv6-Address' orelse
278
        Key =:= '3GPP-SGSN-IPv6-Address' orelse
279
        Key =:= '3GPP-GGSN-IPv6-Address') andalso
280
       ?IS_IPv6(Value) ->
281
    ergw_aaa_3gpp_dict:ip2bin(Value);
128✔
282

283
'3gpp_from_session'(Key, Value)
284
  when Key =:= '3GPP-PDP-Type';
285
       Key =:= '3GPP-GPRS-Negotiated-QoS-Profile';
286
       Key =:= '3GPP-NSAPI';
287
       Key =:= '3GPP-Session-Stop-Indicator';
288
       Key =:= '3GPP-Selection-Mode';
289
       Key =:= '3GPP-Charging-Characteristics';
290
       Key =:= '3GPP-IPv6-DNS-Servers';
291
       Key =:= '3GPP-Teardown-Indicator';
292
       Key =:= '3GPP-RAT-Type';
293
       Key =:= '3GPP-MS-TimeZone';
294
       Key =:= '3GPP-Allocate-IP-Type';
295
       Key =:= '3GPP-Secondary-RAT-Usage' ->
296
    ergw_aaa_3gpp_dict:encode(Key, Value);
7,546✔
297

298
'3gpp_from_session'(Key, Value)
299
  when Key =:= '3GPP-Charging-Id';
300
       Key =:= '3GPP-Camel-Charging';
301
       Key =:= '3GPP-IMSI';
302
       Key =:= '3GPP-IMSI-MCC-MNC';
303
       Key =:= '3GPP-GGSN-MCC-MNC';
304
       Key =:= '3GPP-SGSN-MCC-MNC';
305
       Key =:= '3GPP-IMEISV';
306
       Key =:= '3GPP-User-Location-Info';
307
       Key =:= '3GPP-Packet-Filter';
308
       Key =:= '3GPP-Negotiated-DSCP' ->
309
    Value.
6,435✔
310

311
arp_from_session('Priority-Level' = Key, PL, ARP) ->
312
    ARP#{Key => PL};
264✔
313
arp_from_session(Key, Value, ARP)
314
  when Key == 'Pre-emption-Capability';
315
       Key == 'Pre-emption-Vulnerability' ->
316
    ARP#{Key => [Value]};
528✔
317
arp_from_session(_K, _V, ARP) ->
318
    ARP.
×
319

320
-define(UINT32MAX, 16#ffffffff).
321

322
%% 3GPP TS 29.214 version 15.4.0, Section 4.4.10:
323
%%
324
%%   When the Rx session is being established, if the AF supports the corresponding
325
%%   feature [...] and needs to indicate bandwidth values higher than 2^32-1 bps,
326
%%   AVPs representing bitrate in bps shall be provided with value set to 2^32-1 bps
327
%%   and bandwidth AVPs representing bitrate in kbps shall be provided with the actual
328
%%   required bandwidth.
329

330
qos_from_session('Allocation-Retention-Priority' = Key, ARP, Info) ->
331
    Info#{Key => [maps:fold(fun arp_from_session/3, #{}, ARP)]};
264✔
332

333
qos_from_session('Max-Requested-Bandwidth-UL' = Key, MBR, Info)
334
  when MBR > ?UINT32MAX ->
335
    Info#{Key => ?UINT32MAX, 'Extended-Max-Requested-BW-UL' => [MBR div 1000]};
×
336
qos_from_session('Max-Requested-Bandwidth-DL' = Key, MBR, Info)
337
  when MBR > ?UINT32MAX ->
338
    Info#{Key => ?UINT32MAX, 'Extended-Max-Requested-BW-DL' => [MBR div 1000]};
×
339
qos_from_session('Guaranteed-Bitrate-UL' = Key, GBR, Info)
340
  when GBR > ?UINT32MAX ->
341
    Info#{Key => ?UINT32MAX, 'Extended-GBR-UL' => [GBR div 1000]};
×
342
qos_from_session('Guaranteed-Bitrate-DL' = Key, GBR, Info)
343
  when GBR > ?UINT32MAX ->
344
    Info#{Key => ?UINT32MAX, 'Extended-GBR-DL' => [GBR div 1000]};
×
345
qos_from_session('APN-Aggregate-Max-Bitrate-UL' = Key, AMBR, Info)
346
  when AMBR > ?UINT32MAX ->
347
    Info#{Key => ?UINT32MAX, 'Extended-APN-AMBR-UL' => [AMBR div 1000]};
×
348
qos_from_session('APN-Aggregate-Max-Bitrate-DL' = Key, AMBR, Info)
349
  when AMBR > ?UINT32MAX ->
350
    Info#{Key => ?UINT32MAX, 'Extended-APN-AMBR-DL' => [AMBR div 1000]};
×
351

352
qos_from_session(Key, Value, Info)
353
  when Key == 'QoS-Class-Identifier';
354
       Key == 'Max-Requested-Bandwidth-UL';
355
       Key == 'Max-Requested-Bandwidth-DL';
356
       Key == 'Guaranteed-Bitrate-UL';
357
       Key == 'Guaranteed-Bitrate-DL';
358
       Key == 'APN-Aggregate-Max-Bitrate-UL';
359
       Key == 'APN-Aggregate-Max-Bitrate-DL' ->
360
    Info#{Key => [Value]};
1,848✔
361

362
%% TBD:
363
%%   [ Bearer-Identifier ]
364
%%  *[ Conditional-APN-Aggregate-Max-Bitrate ]
365

366
qos_from_session(_K, _V, Info) ->
367
    Info.
×
368

369
qos_from_session(Info) ->
370
    maps:fold(fun qos_from_session/3, #{}, Info).
264✔
371

372
encode_ipv6prefix({{A,B,C,D,E,F,G,H}, PLen}) ->
373
    L = (PLen + 7) div 8,
160✔
374
    <<IP:L/bytes, _R/binary>> = <<A:16, B:16, C:16, D:16, E:16, F:16, G:16, H:16>>,
160✔
375
    <<0, PLen:8, IP/binary>>.
160✔
376

377
decode_ipv6prefix(<<_:8, PLen:8, Bin/binary>>) ->
378
    BinLen = (PLen + 7) div 8,
×
379
    <<P:BinLen/bytes, Rest/binary>> = Bin,
×
380
    <<A:16,B:16,C:16,D:16,E:16,F:16,G:16,H:16>> = eradius_lib:pad_to(16, P),
×
381
    {{{A,B,C,D,E,F,G,H}, PLen}, Rest}.
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2024 Coveralls, Inc