From 57146b3a1e7cdeded5dea35a9d617f30e4494cf0 Mon Sep 17 00:00:00 2001 From: Andreas Schultz Date: Mon, 15 Apr 2024 18:53:06 +0200 Subject: [PATCH 1/8] add rebar3_fmt plugin and apply consistent formating --- .../eradius_prometheus_collector/rebar.config | 4 +- .../src/eradius_prometheus_collector.app.src | 2 +- .../src/eradius_prometheus_collector.erl | 104 ++++---- dicts_compiler.erl | 54 ++-- include/eradius_dict.hrl | 22 +- include/eradius_lib.hrl | 172 ++++++------- priv/dev.config | 36 +-- rebar.config | 20 +- sample/example.config | 28 +-- sample/rebar.config | 4 +- sample/src/eradius_server_sample.app.src | 16 +- sample/src/eradius_server_sample.erl | 10 +- src/eradius.app.src | 58 ++--- src/eradius_auth.erl | 10 +- src/eradius_client.erl | 238 +++++++++--------- src/eradius_client_socket.erl | 6 +- src/eradius_config.erl | 22 +- src/eradius_counter.erl | 66 ++--- src/eradius_counter_aggregator.erl | 112 ++++----- src/eradius_dict.erl | 28 +-- src/eradius_eap_packet.erl | 28 +-- src/eradius_lib.erl | 114 ++++----- src/eradius_log.erl | 74 +++--- src/eradius_node_mon.erl | 20 +- src/eradius_proxy.erl | 82 +++--- src/eradius_server.erl | 64 +++-- src/eradius_server_mon.erl | 56 ++--- test/eradius_auth_SUITE.erl | 70 +++--- test/eradius_client_SUITE.erl | 120 ++++----- test/eradius_client_socket_test.erl | 42 ++-- test/eradius_config_SUITE.erl | 212 ++++++++-------- test/eradius_lib_SUITE.erl | 44 ++-- test/eradius_logtest.erl | 52 ++-- test/eradius_metrics_SUITE.erl | 50 ++-- test/eradius_proxy_SUITE.erl | 66 ++--- test/eradius_test.hrl | 12 +- test/eradius_test_handler.erl | 6 +- 37 files changed, 1073 insertions(+), 1051 deletions(-) diff --git a/applications/eradius_prometheus_collector/rebar.config b/applications/eradius_prometheus_collector/rebar.config index b767a711..d07757cd 100644 --- a/applications/eradius_prometheus_collector/rebar.config +++ b/applications/eradius_prometheus_collector/rebar.config @@ -1,3 +1,3 @@ {deps, [ - {prometheus, "4.10.0"} -]}. + {prometheus, "4.10.0"} + ]}. diff --git a/applications/eradius_prometheus_collector/src/eradius_prometheus_collector.app.src b/applications/eradius_prometheus_collector/src/eradius_prometheus_collector.app.src index 76e7b648..9c1762e5 100644 --- a/applications/eradius_prometheus_collector/src/eradius_prometheus_collector.app.src +++ b/applications/eradius_prometheus_collector/src/eradius_prometheus_collector.app.src @@ -9,4 +9,4 @@ {env,[]}, {modules, [eradius_prometheus_collector]}, {licenses, ["MIT"]} -]}. + ]}. diff --git a/applications/eradius_prometheus_collector/src/eradius_prometheus_collector.erl b/applications/eradius_prometheus_collector/src/eradius_prometheus_collector.erl index 31cc6cc1..fd2b1546 100644 --- a/applications/eradius_prometheus_collector/src/eradius_prometheus_collector.erl +++ b/applications/eradius_prometheus_collector/src/eradius_prometheus_collector.erl @@ -80,15 +80,15 @@ collect_metrics(_, {PromMetricType, Fun, Stats}) -> fetch_histogram(Name, Labels) -> try lists:flatten(lists:map(fun ({LabelsFromStat, Buckets, DurationUnit}) -> - case compare_labels(Labels, LabelsFromStat) of - true -> - {Buckets1, Values} = lists:unzip(Buckets), - Values1 = augment_counters(Values), - Buckets2 = lists:zip(Buckets1, Values1), - {Buckets2, LabelsFromStat, DurationUnit}; - _ -> [] - end - end, prometheus_histogram:values(default, Name))) + case compare_labels(Labels, LabelsFromStat) of + true -> + {Buckets1, Values} = lists:unzip(Buckets), + Values1 = augment_counters(Values), + Buckets2 = lists:zip(Buckets1, Values1), + {Buckets2, LabelsFromStat, DurationUnit}; + _ -> [] + end + end, prometheus_histogram:values(default, Name))) catch _:_ -> [] end. fetch_counter(Name, Labels) -> @@ -98,45 +98,45 @@ fetch_counter(Name, Labels) -> fetch_counter(uptime_milliseconds, Stat, Labels) -> {{ServerMetrics, _}, _, _} = Stat, lists:flatten(lists:map(fun (#server_counter{} = Cnt) -> - fetch_server_value(Labels, Cnt, fun () -> erlang:system_time(milli_seconds) - Cnt#server_counter.startTime end) - end, ServerMetrics)); + fetch_server_value(Labels, Cnt, fun () -> erlang:system_time(milli_seconds) - Cnt#server_counter.startTime end) + end, ServerMetrics)); fetch_counter(since_last_reset_milliseconds, Stat, Labels) -> {{ServerMetrics, _}, _, _} = Stat, lists:flatten(lists:map(fun (#server_counter{} = Cnt) -> - fetch_server_value(Labels, Cnt, fun () -> erlang:system_time(milli_seconds) - Cnt#server_counter.resetTime end) - end, ServerMetrics)); + fetch_server_value(Labels, Cnt, fun () -> erlang:system_time(milli_seconds) - Cnt#server_counter.resetTime end) + end, ServerMetrics)); fetch_counter(Name, Stat, Labels) -> case get_metric_info(Name, Stat) of {_, undefined, _} -> []; {Metrics, MetricIdx, RadiusMetricType} -> lists:flatten(lists:map(fun (Cnt) -> - case get_labels_and_val(MetricIdx, {Cnt, RadiusMetricType}, {Name, Labels}) of - {Value, LabelsFromStat} -> - case compare_labels(Labels, LabelsFromStat) of - true -> {Value, LabelsFromStat}; - _ -> [] - end; - List -> - lists:map(fun ({Value, LabelsFromStat}) -> - case compare_labels(Labels, LabelsFromStat) of - true -> {Value, LabelsFromStat}; - _ -> [] - end - end, List) - end - end, Metrics)) + case get_labels_and_val(MetricIdx, {Cnt, RadiusMetricType}, {Name, Labels}) of + {Value, LabelsFromStat} -> + case compare_labels(Labels, LabelsFromStat) of + true -> {Value, LabelsFromStat}; + _ -> [] + end; + List -> + lists:map(fun ({Value, LabelsFromStat}) -> + case compare_labels(Labels, LabelsFromStat) of + true -> {Value, LabelsFromStat}; + _ -> [] + end + end, List) + end + end, Metrics)) end. %% from prometheus.erl as prometheus_histogram:values/1 returns %% non-cumulative values augment_counters([Start | Counters]) -> - augment_counters(Counters, [Start], Start). + augment_counters(Counters, [Start], Start). augment_counters([], LAcc, _CAcc) -> - LAcc; + LAcc; augment_counters([Counter | Counters], LAcc, CAcc) -> - augment_counters(Counters, LAcc ++ [CAcc + Counter], CAcc + Counter). + augment_counters(Counters, LAcc ++ [CAcc + Counter], CAcc + Counter). build_metric(uptime_milliseconds, Type, Stat) -> {{ServerMetrics, _}, _, _} = Stat, @@ -154,25 +154,25 @@ build_metric(MetricName, Type, Stat) MetricName =:= accounting_requests_total; MetricName =:= accounting_responses_total -> lists:flatten(lists:map(fun (AcctType) -> - case get_metric_info(MetricName, Stat) of - {_, undefined, _} -> - []; - {Metrics, MetricIdx, RadiusMetricType} -> - lists:map(fun (Cnt) -> - {Value, Labels} = get_labels_and_val(MetricIdx, {Cnt, RadiusMetricType}, {MetricName, [{acct_type, AcctType}]}), - metric(Type, Value, Labels) - end, Metrics) - end - end, [start, stop, update])); + case get_metric_info(MetricName, Stat) of + {_, undefined, _} -> + []; + {Metrics, MetricIdx, RadiusMetricType} -> + lists:map(fun (Cnt) -> + {Value, Labels} = get_labels_and_val(MetricIdx, {Cnt, RadiusMetricType}, {MetricName, [{acct_type, AcctType}]}), + metric(Type, Value, Labels) + end, Metrics) + end + end, [start, stop, update])); build_metric(MetricName, Type, Stat) -> case get_metric_info(MetricName, Stat) of {_, undefined, _} -> []; {Metrics, MetricIdx, RadiusMetricType} -> lists:flatten(lists:map(fun (Cnt) -> - {Value, Labels} = get_labels_and_val(MetricIdx, {Cnt, RadiusMetricType}, {}), - metric(Type, Value, Labels) - end, Metrics)) + {Value, Labels} = get_labels_and_val(MetricIdx, {Cnt, RadiusMetricType}, {}), + metric(Type, Value, Labels) + end, Metrics)) end. metric(_, [], []) -> undefined; @@ -281,9 +281,9 @@ get_labels_and_val(_, {#nas_counter{} = Cnt, server}, {Name, Labels}) case get_value(Name, Type, Cnt) of undefined -> lists:map(fun (AcctType) -> - ResLabels = get_labels(Cnt, ServerIP, ServerPort, NasId, NasIP), - {get_value(Name, AcctType, Cnt), [{acct_type, AcctType} | ResLabels]} - end, ?ACCT_TYPES); + ResLabels = get_labels(Cnt, ServerIP, ServerPort, NasId, NasIP), + {get_value(Name, AcctType, Cnt), [{acct_type, AcctType} | ResLabels]} + end, ?ACCT_TYPES); Value -> ResLabels = get_labels(Cnt, ServerIP, ServerPort, NasId, NasIP), {Value, [{acct_type, Type} | ResLabels]} @@ -299,16 +299,16 @@ get_labels_and_val(_, {#client_counter{} = Cnt, client}, {Name, Labels}) case get_value(Name, Type, Cnt) of undefined -> lists:map(fun (AcctType) -> - ResLabels = get_labels(Cnt, ServerIP, ServerPort, ClientName, ClientIP), - {get_value(Name, AcctType, Cnt), [{acct_type, AcctType} | ResLabels]} - end, ?ACCT_TYPES); + ResLabels = get_labels(Cnt, ServerIP, ServerPort, ClientName, ClientIP), + {get_value(Name, AcctType, Cnt), [{acct_type, AcctType} | ResLabels]} + end, ?ACCT_TYPES); Value -> ResLabels = get_labels(Cnt, ServerIP, ServerPort, ClientName, ClientIP), {Value, [{acct_type, Type} | ResLabels]} end; get_labels_and_val(MetricIdx, {#client_counter{} = Cnt, client}, _) -> - {{ClientName, ClientIP, _ClientPort}, {_, ServerIP, ServerPort}} = Cnt#client_counter.key, - {element(MetricIdx + 1, Cnt), get_labels(Cnt, ServerIP, ServerPort, ClientName, ClientIP)}; + {{ClientName, ClientIP, _ClientPort}, {_, ServerIP, ServerPort}} = Cnt#client_counter.key, + {element(MetricIdx + 1, Cnt), get_labels(Cnt, ServerIP, ServerPort, ClientName, ClientIP)}; get_labels_and_val(_, _, _) -> {[], []}. diff --git a/dicts_compiler.erl b/dicts_compiler.erl index 1e1c360b..2aad5743 100755 --- a/dicts_compiler.erl +++ b/dicts_compiler.erl @@ -15,16 +15,16 @@ main(_) -> ok. compile() -> case find_files("priv/dictionaries", "^dictionary.*") of - [] -> + [] -> ok; Dictionaries0 -> {ok, Basedir} = file:get_cwd(), IncludeDir = filename:join([Basedir, "include"]), ok = filelib:ensure_dir(IncludeDir), - % sort dictionaries in alphabetical order to be sure that - % basic `priv/dictionaries/dictionary` builds first - % because it contains some attributes which may be needed - % for vendor's dictionaries + %% sort dictionaries in alphabetical order to be sure that + %% basic `priv/dictionaries/dictionary` builds first + %% because it contains some attributes which may be needed + % for vendor's dictionaries Dictionaries = lists:sort(Dictionaries0), Targets = [{Dictionary, out_files(Dictionary)} || Dictionary <- Dictionaries], compile_each(Targets) @@ -32,7 +32,7 @@ compile() -> clean() -> case find_files("priv/dictionaries", "^dictionary.*") of - [] -> + [] -> ok; Dictionaries -> Targets = [{Dictionary, out_files(Dictionary)} || Dictionary <- Dictionaries], @@ -87,12 +87,12 @@ clean_each([{_Dictionary, {Headerfile, Mapfile}}|Rest]) -> %%% -------------------------------------------------------------------- emit([A|T], {HrlPid, _} = Hrl, Map) when is_record(A, attribute) -> io:format(HrlPid, "-define( '~s' , ~w ).~n", - [d2u(A#attribute.name), A#attribute.id]), + [d2u(A#attribute.name), A#attribute.id]), io:format(Map, "~110p.~n", [A]), emit(T, Hrl, Map); emit([V|T], {HrlPid, _} = Hrl, Map) when is_record(V, vendor) -> io:format(HrlPid, "-define( '~s' , ~w ).~n", - [d2u(V#vendor.name), V#vendor.type]), + [d2u(V#vendor.name), V#vendor.type]), io:format(Map, "~p.~n", [V]), emit(T, Hrl, Map); emit([V|T], Hrl, Map) when is_record(V, value) -> @@ -101,8 +101,8 @@ emit([V|T], Hrl, Map) when is_record(V, value) -> emit([{include, Include}|T], {HrlPid, _} = Hrl, Map) -> EscapedInclude = re:replace(Include, "\\.", "_", [global, {return, list}]), io:format(HrlPid, "-include( \"~s.hrl\" ).~n", [EscapedInclude]), - % No need to add ".map" extension here. It will be added by eradius_dict - % server automatically. + %% No need to add ".map" extension here. It will be added by eradius_dict + %% server automatically. io:format(Map, "{include, ~p}.~n", [EscapedInclude]), emit(T, Hrl, Map); emit([header|T], {HrlPid, HrlName} = Hrl, Map) -> @@ -122,20 +122,20 @@ emit([], _, _) -> parse_dict(File) when is_list(File) -> {ok,B} = file:read_file(File), F = fun(Line,{undefined = Vendor, AccList}) -> - case pd(string:tokens(Line,"\s\t\r")) of - {ok,E} -> {Vendor, [E|AccList]}; - {include,Hrl} -> {Vendor, [{include, Hrl}|AccList]}; - {begin_vendor, VendId} -> {{vendor, VendId}, AccList}; - _ -> {Vendor, AccList} - end; - (Line, {{vendor, VendId} = Vendor, AccList}) -> - case pd(string:tokens(Line, "\s\t\r"), VendId) of - {end_vendor} -> {undefined, AccList}; - {include,Hrl} -> {Vendor, [{include, Hrl}|AccList]}; - {ok,E} -> {Vendor, [E|AccList]}; - _ -> {Vendor, AccList} - end - end, + case pd(string:tokens(Line,"\s\t\r")) of + {ok,E} -> {Vendor, [E|AccList]}; + {include,Hrl} -> {Vendor, [{include, Hrl}|AccList]}; + {begin_vendor, VendId} -> {{vendor, VendId}, AccList}; + _ -> {Vendor, AccList} + end; + (Line, {{vendor, VendId} = Vendor, AccList}) -> + case pd(string:tokens(Line, "\s\t\r"), VendId) of + {end_vendor} -> {undefined, AccList}; + {include,Hrl} -> {Vendor, [{include, Hrl}|AccList]}; + {ok,E} -> {Vendor, [E|AccList]}; + _ -> {Vendor, AccList} + end + end, {_, L} = lists:foldl(F,{undefined, []},string:tokens(b2l(B),"\n")), [header|L] ++ [footer]. @@ -170,7 +170,7 @@ parse_attribute_attrs(Attr, [Attribute|Tail]) -> parse_attribute_attrs(NewAttr, Rest ++ Tail). pd(["$INCLUDE", Name]) -> - {include, Name}; + {include, Name}; pd(["BEGIN-VENDOR", Name]) -> case get({vendor, Name}) of @@ -240,6 +240,6 @@ d2u(L) when is_list(L) -> repl(L,X,Y) when is_list(L) -> F = fun(Z) when Z == X -> Y; - (C) -> C - end, + (C) -> C + end, lists:map(F,L). diff --git a/include/eradius_dict.hrl b/include/eradius_dict.hrl index 4b1dc872..c2d12678 100644 --- a/include/eradius_dict.hrl +++ b/include/eradius_dict.hrl @@ -1,16 +1,16 @@ -record(attribute, { - id :: eradius_dict:attribute_id(), - type = 'octets' :: eradius_dict:attribute_type(), - name :: string(), - enc = 'no' :: eradius_dict:attribute_encryption() -}). + id :: eradius_dict:attribute_id(), + type = 'octets' :: eradius_dict:attribute_type(), + name :: string(), + enc = 'no' :: eradius_dict:attribute_encryption() + }). -record(vendor, { - type :: eradius_dict:vendor_id(), - name :: string() -}). + type :: eradius_dict:vendor_id(), + name :: string() + }). -record(value, { - id :: eradius_dict:value_id(), - name :: string() -}). + id :: eradius_dict:value_id(), + name :: string() + }). diff --git a/include/eradius_lib.hrl b/include/eradius_lib.hrl index eef81b4f..e5236be4 100644 --- a/include/eradius_lib.hrl +++ b/include/eradius_lib.hrl @@ -57,100 +57,100 @@ -define(RTCNAS_Reboot, 11). -record(client_counter, { - key :: term(), - requests = 0 :: non_neg_integer(), - replies = 0 :: non_neg_integer(), - accessRequests = 0 :: non_neg_integer(), - accessAccepts = 0 :: non_neg_integer(), - accessRejects = 0 :: non_neg_integer(), - accessChallenges = 0 :: non_neg_integer(), - accountRequestsStart = 0 :: non_neg_integer(), - accountRequestsStop = 0 :: non_neg_integer(), - accountRequestsUpdate = 0 :: non_neg_integer(), - accountResponsesStart = 0 :: non_neg_integer(), - accountResponsesStop = 0 :: non_neg_integer(), - accountResponsesUpdate = 0 :: non_neg_integer(), - coaRequests = 0 :: non_neg_integer(), - coaAcks = 0 :: non_neg_integer(), - coaNaks = 0 :: non_neg_integer(), - discRequests = 0 :: non_neg_integer(), - discAcks = 0 :: non_neg_integer(), - discNaks = 0 :: non_neg_integer(), - retransmissions = 0 :: non_neg_integer(), - badAuthenticators = 0 :: non_neg_integer(), - packetsDropped = 0 :: non_neg_integer(), - unknownTypes = 0 :: non_neg_integer(), - server_name :: string(), - pending = 0 :: non_neg_integer(), - timeouts = 0 :: non_neg_integer() - }). + key :: term(), + requests = 0 :: non_neg_integer(), + replies = 0 :: non_neg_integer(), + accessRequests = 0 :: non_neg_integer(), + accessAccepts = 0 :: non_neg_integer(), + accessRejects = 0 :: non_neg_integer(), + accessChallenges = 0 :: non_neg_integer(), + accountRequestsStart = 0 :: non_neg_integer(), + accountRequestsStop = 0 :: non_neg_integer(), + accountRequestsUpdate = 0 :: non_neg_integer(), + accountResponsesStart = 0 :: non_neg_integer(), + accountResponsesStop = 0 :: non_neg_integer(), + accountResponsesUpdate = 0 :: non_neg_integer(), + coaRequests = 0 :: non_neg_integer(), + coaAcks = 0 :: non_neg_integer(), + coaNaks = 0 :: non_neg_integer(), + discRequests = 0 :: non_neg_integer(), + discAcks = 0 :: non_neg_integer(), + discNaks = 0 :: non_neg_integer(), + retransmissions = 0 :: non_neg_integer(), + badAuthenticators = 0 :: non_neg_integer(), + packetsDropped = 0 :: non_neg_integer(), + unknownTypes = 0 :: non_neg_integer(), + server_name :: string(), + pending = 0 :: non_neg_integer(), + timeouts = 0 :: non_neg_integer() + }). -record(nas_counter, { - key :: term(), - requests = 0 :: non_neg_integer(), - replies = 0 :: non_neg_integer(), - dupRequests = 0 :: non_neg_integer(), - malformedRequests = 0 :: non_neg_integer(), - accessRequests = 0 :: non_neg_integer(), - accessAccepts = 0 :: non_neg_integer(), - accessRejects = 0 :: non_neg_integer(), - accessChallenges = 0 :: non_neg_integer(), - accountRequestsStart = 0 :: non_neg_integer(), - accountRequestsStop = 0 :: non_neg_integer(), - accountRequestsUpdate = 0 :: non_neg_integer(), - accountResponsesStart = 0 :: non_neg_integer(), - accountResponsesStop = 0 :: non_neg_integer(), - accountResponsesUpdate = 0 :: non_neg_integer(), - noRecords = 0 :: non_neg_integer(), - badAuthenticators = 0 :: non_neg_integer(), - packetsDropped = 0 :: non_neg_integer(), - unknownTypes = 0 :: non_neg_integer(), - handlerFailure = 0 :: non_neg_integer(), - coaRequests = 0 :: non_neg_integer(), - coaAcks = 0 :: non_neg_integer(), - coaNaks = 0 :: non_neg_integer(), - discRequests = 0 :: non_neg_integer(), - discAcks = 0 :: non_neg_integer(), - discNaks = 0 :: non_neg_integer(), - retransmissions = 0 :: non_neg_integer(), - server_name :: string(), - pending = 0 :: non_neg_integer() - }). + key :: term(), + requests = 0 :: non_neg_integer(), + replies = 0 :: non_neg_integer(), + dupRequests = 0 :: non_neg_integer(), + malformedRequests = 0 :: non_neg_integer(), + accessRequests = 0 :: non_neg_integer(), + accessAccepts = 0 :: non_neg_integer(), + accessRejects = 0 :: non_neg_integer(), + accessChallenges = 0 :: non_neg_integer(), + accountRequestsStart = 0 :: non_neg_integer(), + accountRequestsStop = 0 :: non_neg_integer(), + accountRequestsUpdate = 0 :: non_neg_integer(), + accountResponsesStart = 0 :: non_neg_integer(), + accountResponsesStop = 0 :: non_neg_integer(), + accountResponsesUpdate = 0 :: non_neg_integer(), + noRecords = 0 :: non_neg_integer(), + badAuthenticators = 0 :: non_neg_integer(), + packetsDropped = 0 :: non_neg_integer(), + unknownTypes = 0 :: non_neg_integer(), + handlerFailure = 0 :: non_neg_integer(), + coaRequests = 0 :: non_neg_integer(), + coaAcks = 0 :: non_neg_integer(), + coaNaks = 0 :: non_neg_integer(), + discRequests = 0 :: non_neg_integer(), + discAcks = 0 :: non_neg_integer(), + discNaks = 0 :: non_neg_integer(), + retransmissions = 0 :: non_neg_integer(), + server_name :: string(), + pending = 0 :: non_neg_integer() + }). -record(server_counter, { - key :: term(), - server_name :: string(), - startTime :: erlang:timestamp(), - resetTime :: erlang:timestamp(), - invalidRequests = 0 :: non_neg_integer(), - discardNoHandler = 0 :: non_neg_integer() -}). + key :: term(), + server_name :: string(), + startTime :: erlang:timestamp(), + resetTime :: erlang:timestamp(), + invalidRequests = 0 :: non_neg_integer(), + discardNoHandler = 0 :: non_neg_integer() + }). -record(nas_prop, { - server_ip :: inet:ip_address(), - server_port :: eradius_server:port_number(), - nas_id :: term(), - nas_ip :: inet:ip_address(), - nas_port :: eradius_server:port_number(), - metrics_info :: {atom_address(), atom_address()}, - secret :: eradius_lib:secret(), - handler_nodes = local :: list(atom()) | local -}). + server_ip :: inet:ip_address(), + server_port :: eradius_server:port_number(), + nas_id :: term(), + nas_ip :: inet:ip_address(), + nas_port :: eradius_server:port_number(), + metrics_info :: {atom_address(), atom_address()}, + secret :: eradius_lib:secret(), + handler_nodes = local :: list(atom()) | local + }). -record(radius_request, { - reqid = 0 :: byte(), - cmd = request :: eradius_lib:command(), - attrs = [] :: eradius_lib:attribute_list(), - secret :: eradius_lib:secret(), - authenticator :: eradius_lib:authenticator(), - msg_hmac = false :: boolean(), - eap_msg = <<>> :: binary() -}). + reqid = 0 :: byte(), + cmd = request :: eradius_lib:command(), + attrs = [] :: eradius_lib:attribute_list(), + secret :: eradius_lib:secret(), + authenticator :: eradius_lib:authenticator(), + msg_hmac = false :: boolean(), + eap_msg = <<>> :: binary() + }). -record(decoder_state, { - request_authenticator :: 'undefined' | binary(), - attrs = [] :: eradius_lib:attribute_list(), - hmac_pos :: 'undefined' | non_neg_integer(), - eap_msg = [] :: [binary()] -}). + request_authenticator :: 'undefined' | binary(), + attrs = [] :: eradius_lib:attribute_list(), + hmac_pos :: 'undefined' | non_neg_integer(), + eap_msg = [] :: [binary()] + }). diff --git a/priv/dev.config b/priv/dev.config index b93ff762..14ebf2f0 100644 --- a/priv/dev.config +++ b/priv/dev.config @@ -1,29 +1,29 @@ -%-*-Erlang-*- + %-*-Erlang-*- {kernel, [{logger, [{handler, default, logger_std_h, #{level => info, config => - #{sync_mode_qlen => 10000, - drop_mode_qlen => 10000, - flush_qlen => 10000} + #{sync_mode_qlen => 10000, + drop_mode_qlen => 10000, + flush_qlen => 10000} } } ]} ]}. {eradius, [ - {description, "Erlang RADIUS server"}, - {registered, [eradius_dict, eradius_sup, eradius_server_top_sup, eradius_server_sup, eradius_server_mon]}, - {applications, [kernel, stdlib, crypto]}, - {mod, {eradius, []}}, - {env, [ - {servers, []}, - {logging, true}, - {logfile, "./radius.log"}, - {tables, [dictionary]}, - {client_ip, undefined}, - {client_ports, 20}, - {resend_timeout, 30000} - ]} -]}. + {description, "Erlang RADIUS server"}, + {registered, [eradius_dict, eradius_sup, eradius_server_top_sup, eradius_server_sup, eradius_server_mon]}, + {applications, [kernel, stdlib, crypto]}, + {mod, {eradius, []}}, + {env, [ + {servers, []}, + {logging, true}, + {logfile, "./radius.log"}, + {tables, [dictionary]}, + {client_ip, undefined}, + {client_ports, 20}, + {resend_timeout, 30000} + ]} + ]}. diff --git a/rebar.config b/rebar.config index 237c1725..be584e49 100644 --- a/rebar.config +++ b/rebar.config @@ -1,22 +1,24 @@ -%-*-Erlang-*- +%%-*-Erlang-*- {erl_opts, [debug_info]}. {minimum_otp_vsn, "22"}. {pre_hooks, [{compile, "escript dicts_compiler.erl compile"}, - {clean, "escript dicts_compiler.erl clean"}]}. + {clean, "escript dicts_compiler.erl clean"}]}. {profiles, [ - {test, [ - {erl_opts, [nowarn_export_all]}, + {test, [ + {erl_opts, [nowarn_export_all]}, {project_app_dirs, ["applications/*", "src/*", "."]}, - {deps, [{meck, "0.9.0"}]} - ]} - ]}. + {deps, [{meck, "0.9.0"}]} + ]} + ]}. + +{project_plugins, [rebar3_fmt]}. %% xref checks to run {xref_checks, [undefined_function_calls, undefined_functions, - locals_not_used, deprecated_function_calls, - deprecated_functions]}. + locals_not_used, deprecated_function_calls, + deprecated_functions]}. {xref_ignores, [{prometheus_histogram, declare, 1}, {prometheus_histogram, observe, 3}, diff --git a/sample/example.config b/sample/example.config index 0ce5d579..5371dff7 100644 --- a/sample/example.config +++ b/sample/example.config @@ -1,16 +1,16 @@ %% vim: ft=erlang [{eradius, [ - {client_ip, {127, 0, 0, 1}}, - {client_ports, 256}, - {resend_timeout, 500}, - {recbuf, 4000000}, - {tables, [dictionary]}, - {session_nodes, local}, - {radius_callback, eradius_server_sample}, - {root, [ - {{"root", []}, [{"127.0.0.1", "secret"}]} - ]}, - {servers, [ - {root, {"127.0.0.1", [1812, 1813]}} - ]} -]}]. + {client_ip, {127, 0, 0, 1}}, + {client_ports, 256}, + {resend_timeout, 500}, + {recbuf, 4000000}, + {tables, [dictionary]}, + {session_nodes, local}, + {radius_callback, eradius_server_sample}, + {root, [ + {{"root", []}, [{"127.0.0.1", "secret"}]} + ]}, + {servers, [ + {root, {"127.0.0.1", [1812, 1813]}} + ]} + ]}]. diff --git a/sample/rebar.config b/sample/rebar.config index e921f643..630facb2 100644 --- a/sample/rebar.config +++ b/sample/rebar.config @@ -1,3 +1,3 @@ {deps, [ - eradius -]}. + eradius + ]}. diff --git a/sample/src/eradius_server_sample.app.src b/sample/src/eradius_server_sample.app.src index 175d17f1..23c55315 100644 --- a/sample/src/eradius_server_sample.app.src +++ b/sample/src/eradius_server_sample.app.src @@ -1,10 +1,10 @@ %% vim: ft=erlang {application, eradius_server_sample, [ - {description, "Erlang RADIUS sample server"}, - {vsn, "0.0.1"}, - {registered, []}, - {applications, [kernel, stdlib, eradius]}, - {mod, {eradius_server_sample, []}}, - {env, []}, - {modules, []} -]}. + {description, "Erlang RADIUS sample server"}, + {vsn, "0.0.1"}, + {registered, []}, + {applications, [kernel, stdlib, eradius]}, + {mod, {eradius_server_sample, []}}, + {env, []}, + {modules, []} + ]}. diff --git a/sample/src/eradius_server_sample.erl b/sample/src/eradius_server_sample.erl index b0207736..6af51c73 100644 --- a/sample/src/eradius_server_sample.erl +++ b/sample/src/eradius_server_sample.erl @@ -32,12 +32,12 @@ test() -> do_test(Count) -> Tasks = [run_task(fun() -> - Port = erlang:phash2(self(), 1) + 1812, % 1812, 1813 - {ok, R, A} = eradius_client:send_request({{127,0,0,1}, Port, "secret"}, - #radius_request{cmd = request}, []), - #radius_request{} = eradius_lib:decode_request(R, <<"secret">>, A) + Port = erlang:phash2(self(), 1) + 1812, % 1812, 1813 + {ok, R, A} = eradius_client:send_request({{127,0,0,1}, Port, "secret"}, + #radius_request{cmd = request}, []), + #radius_request{} = eradius_lib:decode_request(R, <<"secret">>, A) end) - || _ <- lists:seq(1, Count)], + || _ <- lists:seq(1, Count)], [await(Task) || Task <- Tasks], Count. diff --git a/src/eradius.app.src b/src/eradius.app.src index ced16030..3af15b11 100644 --- a/src/eradius.app.src +++ b/src/eradius.app.src @@ -1,31 +1,31 @@ %% vim: ft=erlang {application, eradius, [ - {description, "Erlang RADIUS server"}, - {vsn, semver}, - {registered, [eradius_dict, eradius_sup, eradius_server_top_sup, eradius_server_sup, eradius_server_mon]}, - {applications, [kernel, stdlib, crypto]}, - {mod, {eradius, []}}, - {env, [ - {servers, []}, - {tables, [dictionary]}, - {client_ip, undefined}, - {client_ports, 100}, - {resend_timeout, 30000}, - {logging, false}, - {counter_aggregator, false}, - {server_status_metrics_enabled, false}, - {logfile, "./radius.log"}, - {recbuf, 8192}, - {sndbuf, 131072} - ]}, - {maintainers, ["Andreas Schultz", "Vladimir Tarasenko", "Yury Gargay"]}, - {licenses, ["MIT"]}, - {links, [{"Github", "https://github.com/travelping/eradius"}]}, - %% List copied from rebar3_hex.hrl ?DEFAULT_FILES, adding "Makefile" - {files, ["applications", "src", "c_src", "include/eradius_*.hrl", "rebar.config.script" - ,"priv/dictionaries", "rebar.config", "rebar.lock" - ,"README*", "readme*" - ,"LICENSE*", "license*" - ,"NOTICE" - ,"dicts_compiler.erl"]} -]}. + {description, "Erlang RADIUS server"}, + {vsn, semver}, + {registered, [eradius_dict, eradius_sup, eradius_server_top_sup, eradius_server_sup, eradius_server_mon]}, + {applications, [kernel, stdlib, crypto]}, + {mod, {eradius, []}}, + {env, [ + {servers, []}, + {tables, [dictionary]}, + {client_ip, undefined}, + {client_ports, 100}, + {resend_timeout, 30000}, + {logging, false}, + {counter_aggregator, false}, + {server_status_metrics_enabled, false}, + {logfile, "./radius.log"}, + {recbuf, 8192}, + {sndbuf, 131072} + ]}, + {maintainers, ["Andreas Schultz", "Vladimir Tarasenko", "Yury Gargay"]}, + {licenses, ["MIT"]}, + {links, [{"Github", "https://github.com/travelping/eradius"}]}, + %% List copied from rebar3_hex.hrl ?DEFAULT_FILES, adding "Makefile" + {files, ["applications", "src", "c_src", "include/eradius_*.hrl", "rebar.config.script" + ,"priv/dictionaries", "rebar.config", "rebar.lock" + ,"README*", "readme*" + ,"LICENSE*", "license*" + ,"NOTICE" + ,"dicts_compiler.erl"]} + ]}. diff --git a/src/eradius_auth.erl b/src/eradius_auth.erl index 6124f2a9..bc3f7e63 100644 --- a/src/eradius_auth.erl +++ b/src/eradius_auth.erl @@ -22,7 +22,7 @@ %% @doc check the request password using all available authentication mechanisms. %% Tries CHAP, then MS-CHAP, then MS-CHAPv2, finally PAP. -spec check_password(binary(), #radius_request{}) -> - false | {boolean(), eradius_lib:attribute_list()}. + false | {boolean(), eradius_lib:attribute_list()}. check_password(Password, Req = #radius_request{authenticator = Authenticator, attrs = AVPs}) -> case lookup_auth_attrs(AVPs) of {false, Chap_pass, false, false, false, false} -> @@ -191,7 +191,7 @@ ms_chap(Passwd, Challenge, <<_Ident:1/binary, Flags:1/integer-unit:8, LMResponse NTResponse =:= challenge_response(Challenge, NtPasswdHash); true -> false - end, + end, Resp2 = if Resp1 == false -> LMResponse =:= challenge_response(Challenge, LmPasswdHash); @@ -282,9 +282,9 @@ v2_magic2() -> %% ------------------------------------------------------------------------------------------ %% -- MPPE magic constants when used with MS-CHAP-V2 mppe_magic1() -> - <<16#54, 16#68, 16#69, 16#73, 16#20, 16#69, 16#73, 16#20, 16#74, - 16#68, 16#65, 16#20, 16#4d, 16#50, 16#50, 16#45, 16#20, 16#4d, - 16#61, 16#73, 16#74, 16#65, 16#72, 16#20, 16#4b, 16#65, 16#79>>. + <<16#54, 16#68, 16#69, 16#73, 16#20, 16#69, 16#73, 16#20, 16#74, + 16#68, 16#65, 16#20, 16#4d, 16#50, 16#50, 16#45, 16#20, 16#4d, + 16#61, 16#73, 16#74, 16#65, 16#72, 16#20, 16#4b, 16#65, 16#79>>. mppe_magic2() -> <<16#4F, 16#6E, 16#20, 16#74, 16#68, 16#65, 16#20, 16#63, 16#6C, 16#69, diff --git a/src/eradius_client.erl b/src/eradius_client.erl index dfccd133..906b8367 100644 --- a/src/eradius_client.erl +++ b/src/eradius_client.erl @@ -50,19 +50,19 @@ %% ------------------------------------------------------------------------------------------ %% -- API -% @private +%% @private start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). -% @equiv send_request(NAS, Request, []) +%% @equiv send_request(NAS, Request, []) -spec send_request(nas_address(), #radius_request{}) -> {ok, binary()} | {error, 'timeout' | 'socket_down'}. send_request(NAS, Request) -> send_request(NAS, Request, []). -% @doc Send a radius request to the given NAS. -% If no answer is received within the specified timeout, the request will be sent again. +%% @doc Send a radius request to the given NAS. +%% If no answer is received within the specified timeout, the request will be sent again. -spec send_request(nas_address(), #radius_request{}, options()) -> - {ok, binary(), eradius_lib:authenticator()} | {error, 'timeout' | 'socket_down'}. + {ok, binary(), eradius_lib:authenticator()} | {error, 'timeout' | 'socket_down'}. send_request({Host, Port, Secret}, Request, Options) when ?GOOD_CMD(Request) andalso is_binary(Host) -> send_request({erlang:binary_to_list(Host), Port, Secret}, Request, Options); @@ -77,17 +77,17 @@ send_request({IP, Port, Secret}, Request, Options) when ?GOOD_CMD(Request) andal Retries = proplists:get_value(retries, Options, ?DEFAULT_RETRIES), Timeout = proplists:get_value(timeout, Options, ?DEFAULT_TIMEOUT), SendReqFn = fun () -> - Peer = {ServerName, {IP, Port}}, - update_client_requests(MetricsInfo), - {Socket, ReqId} = gen_server:call(?SERVER, {wanna_send, Peer, MetricsInfo}), - Response = send_request_loop(Socket, ReqId, Peer, - Request#radius_request{reqid = ReqId, secret = Secret}, - Retries, Timeout, MetricsInfo), - proceed_response(Request, Response, Peer, TS1, MetricsInfo, Options) - end, - % If we have other RADIUS upstream servers check current one, - % maybe it is already marked as inactive and try to find another - % one + Peer = {ServerName, {IP, Port}}, + update_client_requests(MetricsInfo), + {Socket, ReqId} = gen_server:call(?SERVER, {wanna_send, Peer, MetricsInfo}), + Response = send_request_loop(Socket, ReqId, Peer, + Request#radius_request{reqid = ReqId, secret = Secret}, + Retries, Timeout, MetricsInfo), + proceed_response(Request, Response, Peer, TS1, MetricsInfo, Options) + end, + %% If we have other RADIUS upstream servers check current one, + %% maybe it is already marked as inactive and try to find another + %% one case proplists:get_value(failover, Options, []) of [] -> SendReqFn(); @@ -98,13 +98,13 @@ send_request({IP, Port, Secret}, Request, Options) when ?GOOD_CMD(Request) andal {{IP, Port, Secret}, _NewPool} -> SendReqFn(); {NewPeer, []} -> - % Special case, we don't have servers in the pool anymore, but we need - % to preserve `failover` option to mark current server as inactive if - % it will fail + %% Special case, we don't have servers in the pool anymore, but we need + %% to preserve `failover` option to mark current server as inactive if + %% it will fail NewOptions = lists:keyreplace(failover, 1, Options, {failover, undefined}), send_request(NewPeer, Request, NewOptions); {NewPeer, NewPool} -> - % current server is not in list of active servers, so use another one + %% current server is not in list of active servers, so use another one NewOptions = lists:keyreplace(failover, 1, Options, {failover, NewPool}), send_request(NewPeer, Request, NewOptions) end @@ -112,14 +112,14 @@ send_request({IP, Port, Secret}, Request, Options) when ?GOOD_CMD(Request) andal send_request({_IP, _Port, _Secret}, _Request, _Options) -> error(badarg). -% @equiv send_remote_request(Node, NAS, Request, []) +%% @equiv send_remote_request(Node, NAS, Request, []) -spec send_remote_request(node(), nas_address(), #radius_request{}) -> {ok, binary()} | {error, 'timeout' | 'node_down' | 'socket_down'}. send_remote_request(Node, NAS, Request) -> send_remote_request(Node, NAS, Request, []). -% @doc Send a radius request to the given NAS through a socket on the specified node. -% If no answer is received within the specified timeout, the request will be sent again. -% The request will not be sent again if the remote node is unreachable. +%% @doc Send a radius request to the given NAS through a socket on the specified node. +%% If no answer is received within the specified timeout, the request will be sent again. +%% The request will not be sent again if the remote node is unreachable. -spec send_remote_request(node(), nas_address(), #radius_request{}, options()) -> {ok, binary()} | {error, 'timeout' | 'node_down' | 'socket_down'}. send_remote_request(Node, {IP, Port, Secret}, Request, Options) when ?GOOD_CMD(Request) -> TS1 = erlang:monotonic_time(), @@ -139,15 +139,15 @@ send_remote_request(Node, {IP, Port, Secret}, Request, Options) when ?GOOD_CMD(R Retries = proplists:get_value(retries, Options, ?DEFAULT_RETRIES), Timeout = proplists:get_value(timeout, Options, ?DEFAULT_TIMEOUT), SenderPid = spawn(Node, ?MODULE, send_remote_request_loop, - [self(), Socket, ReqId, Peer, Request1, Retries, Timeout, MetricsInfo]), + [self(), Socket, ReqId, Peer, Request1, Retries, Timeout, MetricsInfo]), SenderMonitor = monitor(process, SenderPid), Response = receive - {SenderPid, Result} -> - erlang:demonitor(SenderMonitor, [flush]), - Result; - {'DOWN', SenderMonitor, process, SenderPid, _Reason} -> - {error, socket_down} - end, + {SenderPid, Result} -> + erlang:demonitor(SenderMonitor, [flush]), + Result; + {'DOWN', SenderMonitor, process, SenderPid, _Reason} -> + {error, socket_down} + end, proceed_response(Request, Response, Peer, TS1, MetricsInfo, Options) catch exit:{{nodedown, Node}, _} -> @@ -203,18 +203,18 @@ handle_failed_request(Request, {ServerIP, Port} = _FailedServer, UpstreamServers case ets:lookup(?MODULE, {ServerIP, Port}) of [{{ServerIP, Port}, Retries, InitialRetries}] -> FailedTries = proplists:get_value(retries, Options, ?DEFAULT_RETRIES), - % Mark the given RADIUS server as 'non-active' if there were more tries - % than possible + %% Mark the given RADIUS server as 'non-active' if there were more tries + %% than possible if FailedTries >= Retries -> ets:delete(?MODULE, {ServerIP, Port}), Timeout = application:get_env(eradius, unreachable_timeout, 60), timer:apply_after(Timeout * 1000, ?MODULE, restore_upstream_server, [{ServerIP, Port, InitialRetries, InitialRetries}]); true -> - % RADIUS client tried to send a request to the {ServierIP, Port} RADIUS - % server. There were done FailedTries tries and all of them failed. - % So decrease amount of tries for the given RADIUS server that - % that will be used for next RADIUS requests towards this RADIUS server. + %% RADIUS client tried to send a request to the {ServierIP, Port} RADIUS + %% server. There were done FailedTries tries and all of them failed. + %% So decrease amount of tries for the given RADIUS server that + %% that will be used for next RADIUS requests towards this RADIUS server. ets:update_counter(?MODULE, {ServerIP, Port}, -FailedTries) end; [] -> @@ -224,12 +224,12 @@ handle_failed_request(Request, {ServerIP, Port} = _FailedServer, UpstreamServers [] -> Response; {NewPeer, NewPool} -> - % leave only active upstream servers + %% leave only active upstream servers NewOptions = lists:keyreplace(failover, 1, Options, {failover, NewPool}), send_request(NewPeer, Request, NewOptions) end. -% @private +%% @private send_remote_request_loop(ReplyPid, Socket, ReqId, Peer, EncRequest, Retries, Timeout, MetricsInfo) -> ReplyPid ! {self(), send_request_loop(Socket, ReqId, Peer, EncRequest, Retries, Timeout, MetricsInfo)}. @@ -263,11 +263,11 @@ send_request_loop(Socket, SMon, Peer = {_ServerName, {IP, Port}}, ReqId, Authent send_request_loop(Socket, SMon, Peer, ReqId, Authenticator, EncRequest, Timeout, RetryN - 1, MetricsInfo, Secret, Request) end. -% @private +%% @private update_client_requests(MetricsInfo) -> eradius_counter:inc_counter(requests, MetricsInfo). -% @private +%% @private update_client_request(pending, MetricsInfo, Pending, _) -> if Pending =< 0 -> eradius_counter:dec_counter(pending, MetricsInfo); true -> eradius_counter:inc_counter(pending, MetricsInfo) @@ -276,7 +276,7 @@ update_client_request(Cmd, MetricsInfo, Ms, Request) -> eradius_counter:observe(eradius_client_request_duration_milliseconds, MetricsInfo, Ms, "Execution time of a RADIUS request"), update_client_request_by_type(Cmd, MetricsInfo, Ms, Request). -% @private +%% @private update_client_request_by_type(request, MetricsInfo, Ms, _) -> eradius_counter:observe(eradius_client_access_request_duration_milliseconds, MetricsInfo, Ms, "Access-Request execution time"), eradius_counter:inc_counter(accessRequests, MetricsInfo); @@ -319,13 +319,13 @@ reconfigure() -> %% ------------------------------------------------------------------------------------------ %% -- socket process manager -record(state, { - socket_ip :: null | inet:ip_address(), - no_ports = 1 :: pos_integer(), - idcounters = maps:new() :: map(), - sockets = array:new() :: array:array(), - sup :: pid(), - clients = [] :: [{{integer(),integer(),integer(),integer()}, integer()}] -}). + socket_ip :: null | inet:ip_address(), + no_ports = 1 :: pos_integer(), + idcounters = maps:new() :: map(), + sockets = array:new() :: array:array(), + sup :: pid(), + clients = [] :: [{{integer(),integer(),integer(),integer()}, integer()}] + }). %% @private init([]) -> @@ -386,9 +386,9 @@ configure(State) -> undefined -> prepare_pools(); _ -> - % if ets table is already exists - which could be in a case of - % reconfigure, just re-create the table and fill it with newly - % configured pools of RADIUS upstream servers + %% if ets table is already exists - which could be in a case of + %% reconfigure, just re-create the table and fill it with newly + %% configured pools of RADIUS upstream servers ets:delete(?MODULE), prepare_pools() end, @@ -425,20 +425,20 @@ store_upstream_servers({Server, _, _}) -> store_upstream_servers(Server) -> HandlerDefinitions = application:get_env(eradius, Server, []), UpdatePoolFn = fun (HandlerOpts) -> - {DefaultRoute, Routes, Retries} = eradius_proxy:get_routes_info(HandlerOpts), - eradius_proxy:put_default_route_to_pool(DefaultRoute, Retries), - eradius_proxy:put_routes_to_pool(Routes, Retries) - end, + {DefaultRoute, Routes, Retries} = eradius_proxy:get_routes_info(HandlerOpts), + eradius_proxy:put_default_route_to_pool(DefaultRoute, Retries), + eradius_proxy:put_routes_to_pool(Routes, Retries) + end, lists:foreach(fun (HandlerDefinition) -> - case HandlerDefinition of - {{_, []}, _} -> ok; - {{_, _, []}, _} -> ok; - {{_, HandlerOpts}, _} -> UpdatePoolFn(HandlerOpts); - {{_, _, HandlerOpts}, _} -> UpdatePoolFn(HandlerOpts); - _HandlerDefinition -> ok - end - end, - HandlerDefinitions). + case HandlerDefinition of + {{_, []}, _} -> ok; + {{_, _, []}, _} -> ok; + {{_, HandlerOpts}, _} -> UpdatePoolFn(HandlerOpts); + {{_, _, HandlerOpts}, _} -> UpdatePoolFn(HandlerOpts); + _HandlerDefinition -> ok + end + end, + HandlerDefinitions). %% private store_radius_server_from_pool(Addr, Port, Retries) when is_tuple(Addr) and is_integer(Port) and is_integer(Retries) -> @@ -463,7 +463,7 @@ configure_address(State = #state{socket_ip = OAdd, sockets = Sockts}, NPorts, NA undefined -> done; _ -> Pid ! close end - end, Sockts), + end, Sockts), {ok, State#state{sockets = array:new(), socket_ip = NAdd, no_ports = NPorts}} end. @@ -489,12 +489,13 @@ close_sockets(NPorts, Sockets) -> false -> List = array:to_list(Sockets), {_, Rest} = lists:split(NPorts, List), - lists:map( fun(Pid) -> - case Pid of - undefined -> done; - _ -> Pid ! close - end - end, Rest), + lists:map( + fun(Pid) -> + case Pid of + undefined -> done; + _ -> Pid ! close + end + end, Rest), array:resize(NPorts, Sockets) end. @@ -516,14 +517,14 @@ find_socket_process(PortIdx, Sockets, SocketIP, Sup) -> case array:get(PortIdx, Sockets) of undefined -> Res = supervisor:start_child(Sup, {PortIdx, - {eradius_client_socket, start, [SocketIP, self(), PortIdx]}, - transient, brutal_kill, worker, [eradius_client_socket]}), + {eradius_client_socket, start, [SocketIP, self(), PortIdx]}, + transient, brutal_kill, worker, [eradius_client_socket]}), Pid = case Res of - {ok, P} -> P; - {error, already_present} -> - {ok, P} = supervisor:restart_child(Sup, PortIdx), - P - end, + {ok, P} -> P; + {error, already_present} -> + {ok, P} = supervisor:restart_child(Sup, PortIdx), + P + end, {Pid, array:set(PortIdx, Pid, Sockets)}; Pid when is_pid(Pid) -> {Pid, Sockets} @@ -546,12 +547,12 @@ init_server_status_metrics() -> false -> ok; true -> - % That will be called at eradius startup and we must be sure that prometheus - % application already started if server status metrics supposed to be used + %% That will be called at eradius startup and we must be sure that prometheus + %% application already started if server status metrics supposed to be used application:ensure_all_started(prometheus), ets:foldl(fun ({{Addr, Port}, _, _}, _Acc) -> - eradius_counter:set_boolean_metric(server_status, [Addr, Port], false) - end, [], ?MODULE) + eradius_counter:set_boolean_metric(server_status, [Addr, Port], false) + end, [], ?MODULE) end. make_metrics_info(Options, {ServerIP, ServerPort}) -> @@ -581,35 +582,38 @@ update_server_status_metric(IP, Port, false, _Options) -> eradius_counter:set_boolean_metric(server_status, [IP, Port], false); update_server_status_metric(IP, Port, true, Options) -> UpstreamServers = proplists:get_value(failover, Options, []), - % set all servesr from pool as inactive + %% set all servesr from pool as inactive if is_list(UpstreamServers) -> - lists:foreach(fun (Server) -> - case Server of - {ServerIP, ServerPort, _} -> - eradius_counter:set_boolean_metric(server_status, [ServerIP, ServerPort], false); - {ServerIP, ServerPort, _, _} -> - eradius_counter:set_boolean_metric(server_status, [ServerIP, ServerPort], false); - _ -> - ok - end - - end, UpstreamServers); - true -> - ok + lists:foreach( + fun (Server) -> + case Server of + {ServerIP, ServerPort, _} -> + eradius_counter:set_boolean_metric(server_status, [ServerIP, ServerPort], false); + {ServerIP, ServerPort, _, _} -> + eradius_counter:set_boolean_metric(server_status, [ServerIP, ServerPort], false); + _ -> + ok + end + + end, UpstreamServers); + true -> + ok end, - % set current service as active + %% set current service as active eradius_counter:set_boolean_metric(server_status, [IP, Port], true). client_request_counter_account_match_spec_compile() -> case persistent_term:get({?MODULE, ?FUNCTION_NAME}, undefined) of undefined -> - MatchSpecCompile = ets:match_spec_compile(ets:fun2ms(fun - ({?RStatus_Type, ?RStatus_Type_Start}) -> accountRequestsStart; - ({?RStatus_Type, ?RStatus_Type_Stop}) -> accountRequestsStop; - ({?RStatus_Type, ?RStatus_Type_Update}) -> accountRequestsUpdate; - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Start}) -> accountRequestsStart; - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Stop}) -> accountRequestsStop; - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Update}) -> accountRequestsUpdate end)), + MatchSpecCompile = + ets:match_spec_compile( + ets:fun2ms( + fun ({?RStatus_Type, ?RStatus_Type_Start}) -> accountRequestsStart; + ({?RStatus_Type, ?RStatus_Type_Stop}) -> accountRequestsStop; + ({?RStatus_Type, ?RStatus_Type_Update}) -> accountRequestsUpdate; + ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Start}) -> accountRequestsStart; + ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Stop}) -> accountRequestsStop; + ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Update}) -> accountRequestsUpdate end)), persistent_term:put({?MODULE, ?FUNCTION_NAME}, MatchSpecCompile), MatchSpecCompile; MatchSpecCompile -> @@ -619,13 +623,15 @@ client_request_counter_account_match_spec_compile() -> client_response_counter_account_match_spec_compile() -> case persistent_term:get({?MODULE, ?FUNCTION_NAME}, undefined) of undefined -> - MatchSpecCompile = ets:match_spec_compile(ets:fun2ms(fun - ({?RStatus_Type, ?RStatus_Type_Start}) -> accountResponsesStart; - ({?RStatus_Type, ?RStatus_Type_Stop}) -> accountResponsesStop; - ({?RStatus_Type, ?RStatus_Type_Update}) -> accountResponsesUpdate; - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Start}) -> accountResponsesStart; - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Stop}) -> accountResponsesStop; - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Update}) -> accountResponsesUpdate end)), + MatchSpecCompile = + ets:match_spec_compile( + ets:fun2ms( + fun ({?RStatus_Type, ?RStatus_Type_Start}) -> accountResponsesStart; + ({?RStatus_Type, ?RStatus_Type_Stop}) -> accountResponsesStop; + ({?RStatus_Type, ?RStatus_Type_Update}) -> accountResponsesUpdate; + ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Start}) -> accountResponsesStart; + ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Stop}) -> accountResponsesStop; + ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Update}) -> accountResponsesUpdate end)), persistent_term:put({?MODULE, ?FUNCTION_NAME}, MatchSpecCompile), MatchSpecCompile; MatchSpecCompile -> @@ -638,11 +644,11 @@ find_suitable_peer([]) -> []; find_suitable_peer([{Host, Port, Secret} | Pool]) when is_list(Host) -> try - IP = get_ip(Host), - find_suitable_peer([{IP, Port, Secret} | Pool]) + IP = get_ip(Host), + find_suitable_peer([{IP, Port, Secret} | Pool]) catch _:_ -> - % can't resolve ip by some reasons, just ignore it - find_suitable_peer(Pool) + %% can't resolve ip by some reasons, just ignore it + find_suitable_peer(Pool) end; find_suitable_peer([{IP, Port, Secret} | Pool]) -> case ets:lookup(?MODULE, {IP, Port}) of diff --git a/src/eradius_client_socket.erl b/src/eradius_client_socket.erl index d03ff2b3..f53a8f3f 100644 --- a/src/eradius_client_socket.erl +++ b/src/eradius_client_socket.erl @@ -20,7 +20,7 @@ init([SocketIP, Client, PortIdx]) -> end, RecBuf = application:get_env(eradius, recbuf, 8192), SndBuf = application:get_env(eradius, sndbuf, 131072), - {ok, Socket} = gen_udp:open(0, [{active, once}, binary , {recbuf, RecBuf}, {sndbuf, SndBuf} | ExtraOptions]), + {ok, Socket} = gen_udp:open(0, [{active, once}, binary, {recbuf, RecBuf}, {sndbuf, SndBuf} | ExtraOptions]), {ok, #state{client = Client, socket = Socket, pending = maps:new(), mode = active, counter = 0}}. handle_call(_Request, _From, State) -> @@ -30,7 +30,7 @@ handle_cast(_Msg, State) -> {noreply, State}. handle_info({SenderPid, send_request, {IP, Port}, ReqId, EncRequest}, - State = #state{socket = Socket, pending = Pending, counter = Counter}) -> + State = #state{socket = Socket, pending = Pending, counter = Counter}) -> case gen_udp:send(Socket, IP, Port, EncRequest) of ok -> ReqKey = {IP, Port, ReqId}, @@ -42,7 +42,7 @@ handle_info({SenderPid, send_request, {IP, Port}, ReqId, EncRequest}, end; handle_info({udp, Socket, FromIP, FromPort, EncRequest}, - State = #state{socket = Socket, pending = Pending, mode = Mode, counter = Counter}) -> + State = #state{socket = Socket, pending = Pending, mode = Mode, counter = Counter}) -> case eradius_lib:decode_request_id(EncRequest) of {ReqId, EncRequest} -> case maps:find({FromIP, FromPort, ReqId}, Pending) of diff --git a/src/eradius_config.erl b/src/eradius_config.erl index cd6b6930..b0ea1e2b 100644 --- a/src/eradius_config.erl +++ b/src/eradius_config.erl @@ -1,7 +1,7 @@ -module(eradius_config). -% Eradius API's: + % Eradius API's: -export([validate_new_config/0, validate_new_config/2, validate_config/1]). -% Config validating API functions: + % Config validating API functions: -export([get_app_env/2, validate_ip/1, validate_port/1, validate_ports/1, map_helper/3, map_helper/2, ok_error_helper/2, validate_secret/1, validate_options/1, validate_socket_options/1, validate_server/1]). @@ -12,7 +12,7 @@ -define(pos_int(X), is_integer(X), X >= 0). -define(ip4_address_num(X), ?pos_int(X), X < 256). -define(ip4_address(T), ?ip4_address_num(element(1, T)), ?ip4_address_num(element(2, T)), - ?ip4_address_num(element(3, T)), ?ip4_address_num(element(4, T))). + ?ip4_address_num(element(3, T)), ?ip4_address_num(element(4, T))). -define(valid_atom(Value), Value =/= invalid). -define(valid(X), is_tuple(X), ?valid_atom(element(1, X))). -define(is_io(IO), is_list(IO) orelse is_binary(IO)). @@ -114,8 +114,8 @@ validate_nas(NasId, _IP, Secret, _Name, _) when ?is_io(Secret) -> validate_nas(_NasId, _IP, Secret, _Name, _) -> ?invalid("bad RADIUS secret: ~p", [Secret]). -% -------------------------------------------------------------------------------------------------- -% -- direct validation function +%% -------------------------------------------------------------------------------------------------- +%% -- direct validation function validate_ip(IP) when is_list(IP) -> ok_error_helper(inet_parse:address(IP), {"bad IP address: ~p", [IP]}); @@ -145,8 +145,8 @@ validate_options(Opts) -> validate_socket_options(SocketOpts) when is_list(SocketOpts) -> BannedOpts = [ip, binary, list, active], IsBannedFn = fun(Opt) -> - proplists:is_defined(Opt, SocketOpts) - end, + proplists:is_defined(Opt, SocketOpts) + end, case lists:any(IsBannedFn, BannedOpts) of true -> ?invalid("bad socket options specified: ~p", [SocketOpts]); @@ -173,8 +173,8 @@ check_root(Nodes) -> {root, Values} end. -% -------------------------------------------------------------------------------------------------- -% -- build right format function +%% -------------------------------------------------------------------------------------------------- +%% -- build right format function build_nas_behavior_list({Module, Nas, Args}, ListOfNases) -> lists:map(fun({undefined, IP, Secret, Nodes}) -> @@ -351,8 +351,8 @@ plmerge(List1, List2) -> M1 = [{K, V} || {K, V} <- List1, not proplists:is_defined(K, List2)], lists:keysort(1, M1 ++ List2). -% -------------------------------------------------------------------------------------------------- -% -- helpers +%% -------------------------------------------------------------------------------------------------- +%% -- helpers get_app_env(Env) -> get_app_env(eradius, Env). diff --git a/src/eradius_counter.erl b/src/eradius_counter.erl index 9e922550..9d245e59 100644 --- a/src/eradius_counter.erl +++ b/src/eradius_counter.erl @@ -14,8 +14,8 @@ -include("eradius_lib.hrl"). -record(state, { - reset :: erlang:timestamp() - }). + reset :: erlang:timestamp() + }). -type srv_counters() :: [#server_counter{}]. -type nas_counters() :: {erlang:timestamp(), [#nas_counter{}]}. @@ -91,9 +91,9 @@ set_boolean_metric(Name, Labels, Value) -> try prometheus_boolean:set(Name, Labels, Value) catch _:_ -> - prometheus_boolean:declare([{name, server_status}, {labels, [server_ip, server_port]}, - {help, "Status of an upstream RADIUS Server"}]), - prometheus_boolean:set(Name, Labels, Value) + prometheus_boolean:declare([{name, server_status}, {labels, [server_ip, server_port]}, + {help, "Status of an upstream RADIUS Server"}]), + prometheus_boolean:set(Name, Labels, Value) end; _ -> ok @@ -270,32 +270,32 @@ counter_idx(accountResponsesUpdate, client) -> #client_counter.accountResponsesU add_counter(Cnt1 = #nas_counter{}, Cnt2 = #nas_counter{}) -> #nas_counter{ - key = Cnt1#nas_counter.key, - requests = Cnt1#nas_counter.requests + Cnt2#nas_counter.requests, - replies = Cnt1#nas_counter.replies + Cnt2#nas_counter.replies, - dupRequests = Cnt1#nas_counter.dupRequests + Cnt2#nas_counter.dupRequests, - malformedRequests = Cnt1#nas_counter.malformedRequests + Cnt2#nas_counter.malformedRequests, - accessRequests = Cnt1#nas_counter.accessRequests + Cnt2#nas_counter.accessRequests, - accessAccepts = Cnt1#nas_counter.accessAccepts + Cnt2#nas_counter.accessAccepts, - accessRejects = Cnt1#nas_counter.accessRejects + Cnt2#nas_counter.accessRejects, - accessChallenges = Cnt1#nas_counter.accessChallenges + Cnt2#nas_counter.accessChallenges, - accountRequestsStart = Cnt1#nas_counter.accountRequestsStart + Cnt2#nas_counter.accountRequestsStart, - accountRequestsStop = Cnt1#nas_counter.accountRequestsStop + Cnt2#nas_counter.accountRequestsStop, - accountRequestsUpdate = Cnt1#nas_counter.accountRequestsUpdate + Cnt2#nas_counter.accountRequestsUpdate, - accountResponsesStart = Cnt1#nas_counter.accountResponsesStart + Cnt2#nas_counter.accountResponsesStart, - accountResponsesStop = Cnt1#nas_counter.accountResponsesStop + Cnt2#nas_counter.accountResponsesStop, - accountResponsesUpdate = Cnt1#nas_counter.accountResponsesUpdate + Cnt2#nas_counter.accountResponsesUpdate, - noRecords = Cnt1#nas_counter.noRecords + Cnt2#nas_counter.noRecords, - badAuthenticators = Cnt1#nas_counter.badAuthenticators + Cnt2#nas_counter.badAuthenticators, - packetsDropped = Cnt1#nas_counter.packetsDropped + Cnt2#nas_counter.packetsDropped, - unknownTypes = Cnt1#nas_counter.unknownTypes + Cnt2#nas_counter.unknownTypes, - handlerFailure = Cnt1#nas_counter.handlerFailure + Cnt2#nas_counter.handlerFailure, - coaRequests = Cnt1#nas_counter.coaRequests + Cnt2#nas_counter.coaRequests, - coaAcks = Cnt1#nas_counter.coaAcks + Cnt2#nas_counter.coaAcks, - coaNaks = Cnt1#nas_counter.coaNaks + Cnt2#nas_counter.coaNaks, - discRequests = Cnt1#nas_counter.discRequests + Cnt2#nas_counter.discRequests, - discAcks = Cnt1#nas_counter.discAcks + Cnt2#nas_counter.discAcks, - discNaks = Cnt1#nas_counter.discNaks + Cnt2#nas_counter.discNaks, - retransmissions = Cnt1#nas_counter.retransmissions + Cnt2#nas_counter.retransmissions, - pending = Cnt1#nas_counter.pending + Cnt2#nas_counter.pending + key = Cnt1#nas_counter.key, + requests = Cnt1#nas_counter.requests + Cnt2#nas_counter.requests, + replies = Cnt1#nas_counter.replies + Cnt2#nas_counter.replies, + dupRequests = Cnt1#nas_counter.dupRequests + Cnt2#nas_counter.dupRequests, + malformedRequests = Cnt1#nas_counter.malformedRequests + Cnt2#nas_counter.malformedRequests, + accessRequests = Cnt1#nas_counter.accessRequests + Cnt2#nas_counter.accessRequests, + accessAccepts = Cnt1#nas_counter.accessAccepts + Cnt2#nas_counter.accessAccepts, + accessRejects = Cnt1#nas_counter.accessRejects + Cnt2#nas_counter.accessRejects, + accessChallenges = Cnt1#nas_counter.accessChallenges + Cnt2#nas_counter.accessChallenges, + accountRequestsStart = Cnt1#nas_counter.accountRequestsStart + Cnt2#nas_counter.accountRequestsStart, + accountRequestsStop = Cnt1#nas_counter.accountRequestsStop + Cnt2#nas_counter.accountRequestsStop, + accountRequestsUpdate = Cnt1#nas_counter.accountRequestsUpdate + Cnt2#nas_counter.accountRequestsUpdate, + accountResponsesStart = Cnt1#nas_counter.accountResponsesStart + Cnt2#nas_counter.accountResponsesStart, + accountResponsesStop = Cnt1#nas_counter.accountResponsesStop + Cnt2#nas_counter.accountResponsesStop, + accountResponsesUpdate = Cnt1#nas_counter.accountResponsesUpdate + Cnt2#nas_counter.accountResponsesUpdate, + noRecords = Cnt1#nas_counter.noRecords + Cnt2#nas_counter.noRecords, + badAuthenticators = Cnt1#nas_counter.badAuthenticators + Cnt2#nas_counter.badAuthenticators, + packetsDropped = Cnt1#nas_counter.packetsDropped + Cnt2#nas_counter.packetsDropped, + unknownTypes = Cnt1#nas_counter.unknownTypes + Cnt2#nas_counter.unknownTypes, + handlerFailure = Cnt1#nas_counter.handlerFailure + Cnt2#nas_counter.handlerFailure, + coaRequests = Cnt1#nas_counter.coaRequests + Cnt2#nas_counter.coaRequests, + coaAcks = Cnt1#nas_counter.coaAcks + Cnt2#nas_counter.coaAcks, + coaNaks = Cnt1#nas_counter.coaNaks + Cnt2#nas_counter.coaNaks, + discRequests = Cnt1#nas_counter.discRequests + Cnt2#nas_counter.discRequests, + discAcks = Cnt1#nas_counter.discAcks + Cnt2#nas_counter.discAcks, + discNaks = Cnt1#nas_counter.discNaks + Cnt2#nas_counter.discNaks, + retransmissions = Cnt1#nas_counter.retransmissions + Cnt2#nas_counter.retransmissions, + pending = Cnt1#nas_counter.pending + Cnt2#nas_counter.pending }. diff --git a/src/eradius_counter_aggregator.erl b/src/eradius_counter_aggregator.erl index b972002f..b137b370 100644 --- a/src/eradius_counter_aggregator.erl +++ b/src/eradius_counter_aggregator.erl @@ -10,9 +10,9 @@ -define(INTERVAL_HB, 5000). -record(state, { - me :: reference(), - reset :: erlang:timestamp() - }). + me :: reference(), + reset :: erlang:timestamp() + }). %% @doc reset all counters to zero reset() -> @@ -94,69 +94,69 @@ update_stats(Rec = #nas_counter{key = Key}) -> Cnt0 = case ets:lookup(?MODULE, Key) of [] -> #nas_counter{key = Key}; [Cnt] -> Cnt - end, + end, ets:insert(?MODULE, add_counter(Cnt0, Rec)); update_stats(Rec = #client_counter{key = Key}) -> Cnt0 = case ets:lookup(?MODULE, Key) of [] -> #client_counter{key = Key}; [Cnt] -> Cnt - end, + end, ets:insert(?MODULE, add_counter(Cnt0, Rec)). add_counter(Cnt1 = #nas_counter{}, Cnt2 = #nas_counter{}) -> #nas_counter{ - key = Cnt1#nas_counter.key, - requests = Cnt1#nas_counter.requests + Cnt2#nas_counter.requests, - replies = Cnt1#nas_counter.replies + Cnt2#nas_counter.replies, - dupRequests = Cnt1#nas_counter.dupRequests + Cnt2#nas_counter.dupRequests, - malformedRequests = Cnt1#nas_counter.malformedRequests + Cnt2#nas_counter.malformedRequests, - accessRequests = Cnt1#nas_counter.accessRequests + Cnt2#nas_counter.accessRequests, - accessAccepts = Cnt1#nas_counter.accessAccepts + Cnt2#nas_counter.accessAccepts, - accessRejects = Cnt1#nas_counter.accessRejects + Cnt2#nas_counter.accessRejects, - accessChallenges = Cnt1#nas_counter.accessChallenges + Cnt2#nas_counter.accessChallenges, - accountRequestsStart = Cnt1#nas_counter.accountRequestsStart + Cnt2#nas_counter.accountRequestsStart, - accountRequestsStop = Cnt1#nas_counter.accountRequestsStop + Cnt2#nas_counter.accountRequestsStop, - accountRequestsUpdate = Cnt1#nas_counter.accountRequestsUpdate + Cnt2#nas_counter.accountRequestsUpdate, - accountResponsesStart = Cnt1#nas_counter.accountResponsesStart + Cnt2#nas_counter.accountResponsesStart, - accountResponsesStop = Cnt1#nas_counter.accountResponsesStop + Cnt2#nas_counter.accountResponsesStop, - accountResponsesUpdate = Cnt1#nas_counter.accountResponsesUpdate + Cnt2#nas_counter.accountResponsesUpdate, - noRecords = Cnt1#nas_counter.noRecords + Cnt2#nas_counter.noRecords, - badAuthenticators = Cnt1#nas_counter.badAuthenticators + Cnt2#nas_counter.badAuthenticators, - packetsDropped = Cnt1#nas_counter.packetsDropped + Cnt2#nas_counter.packetsDropped, - unknownTypes = Cnt1#nas_counter.unknownTypes + Cnt2#nas_counter.unknownTypes, - handlerFailure = Cnt1#nas_counter.handlerFailure + Cnt2#nas_counter.handlerFailure, - coaRequests = Cnt1#nas_counter.coaRequests + Cnt2#nas_counter.coaRequests, - coaAcks = Cnt1#nas_counter.coaAcks + Cnt2#nas_counter.coaAcks, - coaNaks = Cnt1#nas_counter.coaNaks + Cnt2#nas_counter.coaNaks, - discRequests = Cnt1#nas_counter.discRequests + Cnt2#nas_counter.discRequests, - discAcks = Cnt1#nas_counter.discAcks + Cnt2#nas_counter.discAcks, - discNaks = Cnt1#nas_counter.discNaks + Cnt2#nas_counter.discNaks + key = Cnt1#nas_counter.key, + requests = Cnt1#nas_counter.requests + Cnt2#nas_counter.requests, + replies = Cnt1#nas_counter.replies + Cnt2#nas_counter.replies, + dupRequests = Cnt1#nas_counter.dupRequests + Cnt2#nas_counter.dupRequests, + malformedRequests = Cnt1#nas_counter.malformedRequests + Cnt2#nas_counter.malformedRequests, + accessRequests = Cnt1#nas_counter.accessRequests + Cnt2#nas_counter.accessRequests, + accessAccepts = Cnt1#nas_counter.accessAccepts + Cnt2#nas_counter.accessAccepts, + accessRejects = Cnt1#nas_counter.accessRejects + Cnt2#nas_counter.accessRejects, + accessChallenges = Cnt1#nas_counter.accessChallenges + Cnt2#nas_counter.accessChallenges, + accountRequestsStart = Cnt1#nas_counter.accountRequestsStart + Cnt2#nas_counter.accountRequestsStart, + accountRequestsStop = Cnt1#nas_counter.accountRequestsStop + Cnt2#nas_counter.accountRequestsStop, + accountRequestsUpdate = Cnt1#nas_counter.accountRequestsUpdate + Cnt2#nas_counter.accountRequestsUpdate, + accountResponsesStart = Cnt1#nas_counter.accountResponsesStart + Cnt2#nas_counter.accountResponsesStart, + accountResponsesStop = Cnt1#nas_counter.accountResponsesStop + Cnt2#nas_counter.accountResponsesStop, + accountResponsesUpdate = Cnt1#nas_counter.accountResponsesUpdate + Cnt2#nas_counter.accountResponsesUpdate, + noRecords = Cnt1#nas_counter.noRecords + Cnt2#nas_counter.noRecords, + badAuthenticators = Cnt1#nas_counter.badAuthenticators + Cnt2#nas_counter.badAuthenticators, + packetsDropped = Cnt1#nas_counter.packetsDropped + Cnt2#nas_counter.packetsDropped, + unknownTypes = Cnt1#nas_counter.unknownTypes + Cnt2#nas_counter.unknownTypes, + handlerFailure = Cnt1#nas_counter.handlerFailure + Cnt2#nas_counter.handlerFailure, + coaRequests = Cnt1#nas_counter.coaRequests + Cnt2#nas_counter.coaRequests, + coaAcks = Cnt1#nas_counter.coaAcks + Cnt2#nas_counter.coaAcks, + coaNaks = Cnt1#nas_counter.coaNaks + Cnt2#nas_counter.coaNaks, + discRequests = Cnt1#nas_counter.discRequests + Cnt2#nas_counter.discRequests, + discAcks = Cnt1#nas_counter.discAcks + Cnt2#nas_counter.discAcks, + discNaks = Cnt1#nas_counter.discNaks + Cnt2#nas_counter.discNaks }; add_counter(Cnt1 = #client_counter{}, Cnt2 = #client_counter{}) -> #client_counter{ - key = Cnt1#client_counter.key, - requests = Cnt1#client_counter.requests + Cnt2#client_counter.requests, - replies = Cnt1#client_counter.replies + Cnt2#client_counter.replies, - accessRequests = Cnt1#client_counter.accessRequests + Cnt2#client_counter.accessRequests, - accessAccepts = Cnt1#client_counter.accessAccepts + Cnt2#client_counter.accessAccepts, - accessRejects = Cnt1#client_counter.accessRejects + Cnt2#client_counter.accessRejects, - accessChallenges = Cnt1#client_counter.accessChallenges + Cnt2#client_counter.accessChallenges, - accountRequestsStart = Cnt1#client_counter.accountRequestsStart + Cnt2#client_counter.accountRequestsStart, - accountRequestsStop = Cnt1#client_counter.accountRequestsStop + Cnt2#client_counter.accountRequestsStop, - accountRequestsUpdate = Cnt1#client_counter.accountRequestsUpdate + Cnt2#client_counter.accountRequestsUpdate, - accountResponsesStart = Cnt1#client_counter.accountResponsesStart + Cnt2#client_counter.accountResponsesStart, - accountResponsesStop = Cnt1#client_counter.accountResponsesStop + Cnt2#client_counter.accountResponsesStop, - accountResponsesUpdate = Cnt1#client_counter.accountResponsesUpdate + Cnt2#client_counter.accountResponsesUpdate, - badAuthenticators = Cnt1#client_counter.badAuthenticators + Cnt2#client_counter.badAuthenticators, - packetsDropped = Cnt1#client_counter.packetsDropped + Cnt2#client_counter.packetsDropped, - unknownTypes = Cnt1#client_counter.unknownTypes + Cnt2#client_counter.unknownTypes, - coaRequests = Cnt1#client_counter.coaRequests + Cnt2#client_counter.coaRequests, - coaAcks = Cnt1#client_counter.coaAcks + Cnt2#client_counter.coaAcks, - coaNaks = Cnt1#client_counter.coaNaks + Cnt2#client_counter.coaNaks, - discRequests = Cnt1#client_counter.discRequests + Cnt2#client_counter.discRequests, - discAcks = Cnt1#client_counter.discAcks + Cnt2#client_counter.discAcks, - discNaks = Cnt1#client_counter.discNaks + Cnt2#client_counter.discNaks, - retransmissions = Cnt1#client_counter.retransmissions + Cnt2#client_counter.retransmissions, - timeouts = Cnt1#client_counter.timeouts + Cnt2#client_counter.timeouts, - pending = Cnt1#client_counter.pending + Cnt2#client_counter.pending + key = Cnt1#client_counter.key, + requests = Cnt1#client_counter.requests + Cnt2#client_counter.requests, + replies = Cnt1#client_counter.replies + Cnt2#client_counter.replies, + accessRequests = Cnt1#client_counter.accessRequests + Cnt2#client_counter.accessRequests, + accessAccepts = Cnt1#client_counter.accessAccepts + Cnt2#client_counter.accessAccepts, + accessRejects = Cnt1#client_counter.accessRejects + Cnt2#client_counter.accessRejects, + accessChallenges = Cnt1#client_counter.accessChallenges + Cnt2#client_counter.accessChallenges, + accountRequestsStart = Cnt1#client_counter.accountRequestsStart + Cnt2#client_counter.accountRequestsStart, + accountRequestsStop = Cnt1#client_counter.accountRequestsStop + Cnt2#client_counter.accountRequestsStop, + accountRequestsUpdate = Cnt1#client_counter.accountRequestsUpdate + Cnt2#client_counter.accountRequestsUpdate, + accountResponsesStart = Cnt1#client_counter.accountResponsesStart + Cnt2#client_counter.accountResponsesStart, + accountResponsesStop = Cnt1#client_counter.accountResponsesStop + Cnt2#client_counter.accountResponsesStop, + accountResponsesUpdate = Cnt1#client_counter.accountResponsesUpdate + Cnt2#client_counter.accountResponsesUpdate, + badAuthenticators = Cnt1#client_counter.badAuthenticators + Cnt2#client_counter.badAuthenticators, + packetsDropped = Cnt1#client_counter.packetsDropped + Cnt2#client_counter.packetsDropped, + unknownTypes = Cnt1#client_counter.unknownTypes + Cnt2#client_counter.unknownTypes, + coaRequests = Cnt1#client_counter.coaRequests + Cnt2#client_counter.coaRequests, + coaAcks = Cnt1#client_counter.coaAcks + Cnt2#client_counter.coaAcks, + coaNaks = Cnt1#client_counter.coaNaks + Cnt2#client_counter.coaNaks, + discRequests = Cnt1#client_counter.discRequests + Cnt2#client_counter.discRequests, + discAcks = Cnt1#client_counter.discAcks + Cnt2#client_counter.discAcks, + discNaks = Cnt1#client_counter.discNaks + Cnt2#client_counter.discNaks, + retransmissions = Cnt1#client_counter.retransmissions + Cnt2#client_counter.retransmissions, + timeouts = Cnt1#client_counter.timeouts + Cnt2#client_counter.timeouts, + pending = Cnt1#client_counter.pending + Cnt2#client_counter.pending }. diff --git a/src/eradius_dict.erl b/src/eradius_dict.erl index 095bcfd7..3fca6f30 100644 --- a/src/eradius_dict.erl +++ b/src/eradius_dict.erl @@ -3,7 +3,7 @@ -module(eradius_dict). -export([start_link/0, lookup/2, load_tables/1, load_tables/2, unload_tables/1, unload_tables/2]). -export_type([attribute/0, attr_value/0, table_name/0, attribute_id/0, attribute_type/0, - attribute_prim_type/0, attribute_encryption/0, vendor_id/0, value_id/0]). + attribute_prim_type/0, attribute_encryption/0, vendor_id/0, value_id/0]). -behaviour(gen_server). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). @@ -18,7 +18,7 @@ -type attribute_encryption() :: 'no' | 'scramble' | 'salt_crypt' | 'ascend'. -type attribute_type() :: attribute_prim_type() | {tagged, attribute_prim_type()}. -type attribute_prim_type() :: 'string' | 'integer' | 'integer64' | 'ipaddr' | 'ipv6addr' - | 'ipv6prefix' | 'date' | 'abinary' | 'binary' | 'octets'. + | 'ipv6prefix' | 'date' | 'abinary' | 'binary' | 'octets'. -type value_id() :: {attribute_id(), pos_integer()}. -type vendor_id() :: pos_integer(). @@ -98,9 +98,9 @@ do_unload_tables(_Dir, []) -> ok; do_unload_tables(Dir, Tables) -> try - % Unlike of what we do in do_load_tables, we don't treat includes here. - % Usually when you want to purge some tables you want to do exactly - % this, and you don't want to purge some extra tables. + %% Unlike of what we do in do_load_tables, we don't treat includes here. + %% Usually when you want to purge some tables you want to do exactly + %% this, and you don't want to purge some extra tables. {_, Defs} = prepare_tables(Dir, Tables), dict_delete(Defs), ?LOG(info, "Unloaded RADIUS tables: ~p", [Tables]) @@ -113,12 +113,12 @@ do_unload_tables(Dir, Tables) -> -spec prepare_tables(file:filename(), [table_name()]) -> {list(), list()}. prepare_tables(Dir, Tables) -> All = lists:flatmap(fun (Tab) -> - TabFile = filename:join(Dir, mapfile(Tab)), - case file:consult(TabFile) of - {ok, Res} -> Res; - {error, _Error} -> throw({consult, TabFile}) - end - end, Tables), + TabFile = filename:join(Dir, mapfile(Tab)), + case file:consult(TabFile) of + {ok, Res} -> Res; + {error, _Error} -> throw({consult, TabFile}) + end + end, Tables), lists:partition(fun({include, _}) -> true; (_) -> false end, All). dict_insert(Value) when is_list(Value) -> @@ -135,8 +135,8 @@ dict_delete(Value) when is_tuple(Value) -> dict_lookup(Type, Id) -> try - persistent_term:get({?MODULE, Type, Id}) + persistent_term:get({?MODULE, Type, Id}) catch - error:badarg -> - false + error:badarg -> + false end. diff --git a/src/eradius_eap_packet.erl b/src/eradius_eap_packet.erl index c2e79c07..0f55cbd4 100644 --- a/src/eradius_eap_packet.erl +++ b/src/eradius_eap_packet.erl @@ -61,10 +61,10 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. decode(<>) -> DataLen = Len - 4, case Rest of - <> -> - do_decode_payload(code(Code), Id, Data); - _ -> - {error, invalid_length} + <> -> + do_decode_payload(code(Code), Id, Data); + _ -> + {error, invalid_length} end. %% @doc endecode a EPA message @@ -75,9 +75,9 @@ encode(Code, Id, Msg) -> do_decode_payload(Code, Id, Data) -> try - decode_payload(Code, Id, Data) + decode_payload(Code, Id, Data) catch - _ -> {error, invalid_payload} + _ -> {error, invalid_payload} end. decode_payload(Code, Id, <>) @@ -87,9 +87,9 @@ decode_payload(Code, Id, <>) decode_payload(Code, Id, Data) when Code == success; Code == failure -> case Data of - <<>> -> {ok, {Code, Id}}; - _ -> {error, invalid_length} - end. + <<>> -> {ok, {Code, Id}}; + _ -> {error, invalid_length} + end. %% @doc EAP decoder functions for RFC-3784 types @@ -130,11 +130,11 @@ decode_eap_type({0, 3}, Data) -> {nak_ext, [{Vendor,Type} || <<_T:8, Vendor:24, Type:32>> <= Data]}; decode_eap_type(Type, Data) -> - case lookup_type(Type) of - [Module] -> - Module:decode_eap_type(Type, Data); - _ -> {Type, Data} - end. + case lookup_type(Type) of + [Module] -> + Module:decode_eap_type(Type, Data); + _ -> {Type, Data} + end. encode_payload(Code, Msg) when Code == request; Code == response -> diff --git a/src/eradius_lib.erl b/src/eradius_lib.erl index 71ac2de9..650cbde3 100644 --- a/src/eradius_lib.erl +++ b/src/eradius_lib.erl @@ -4,7 +4,7 @@ -export([timestamp/0, timestamp/1, printable_peer/2, make_addr_info/1]). -export_type([command/0, secret/0, authenticator/0, attribute_list/0]). -% -compile(bin_opt_info). +%% -compile(bin_opt_info). -ifdef(TEST). -export([encode_value/2, decode_value/2, scramble/3, ascend/3]). @@ -21,8 +21,8 @@ -define(IS_ATTR(Key, Attr), ?IS_KEY(Key, element(1, Attr))). -define(IS_KEY(Key, Attr), ((is_record(Attr, attribute) andalso (element(2, Attr) == Key)) - orelse - (Attr == Key)) ). + orelse + (Attr == Key)) ). %% ------------------------------------------------------------------------------------------ %% -- Request Accessors -spec random_authenticator() -> authenticator(). @@ -114,8 +114,8 @@ encode_message_authenticator(Req = #radius_request{reqid = ReqID, cmd = Command, chunk(Bin, Length) -> case Bin of - <> -> {First, Rest}; - _ -> {Bin, <<>>} + <> -> {First, Rest}; + _ -> {Bin, <<>>} end. encode_eap_attribute({<<>>, _}, EncReq) -> @@ -158,7 +158,7 @@ encode_attribute(_Req, _Attr = #attribute{id = ?REAP_Message}, _) -> encode_attribute(Req, Attr = #attribute{id = {Vendor, ID}}, Value) -> EncValue = encode_attribute(Req, Attr#attribute{id = ID}, Value), if byte_size(EncValue) + 6 > 255 -> - error(badarg, [{Vendor, ID}, Value]); + error(badarg, [{Vendor, ID}, Value]); true -> ok end, <>; @@ -169,14 +169,14 @@ encode_attribute(Req, #attribute{type = {tagged, Type}, id = ID, enc = Enc}, Val end, EncValue = encrypt_value(Req, encode_value(Type, UntaggedValue), Enc), if byte_size(EncValue) + 3 > 255 -> - error(badarg, [ID, Value]); + error(badarg, [ID, Value]); true -> ok end, <>; encode_attribute(Req, #attribute{type = Type, id = ID, enc = Enc}, Value)-> EncValue = encrypt_value(Req, encode_value(Type, Value), Enc), if byte_size(EncValue) + 2 > 255 -> - error(badarg, [ID, Value]); + error(badarg, [ID, Value]); true -> ok end, <>. @@ -241,24 +241,24 @@ decode_request0(< GivenBodySize -> - throw({bad_pdu, "false packet size"}); - ActualBodySize == GivenBodySize -> - Body0; - true -> - binary:part(Body0, 0, GivenBodySize) + ActualBodySize > GivenBodySize -> + throw({bad_pdu, "false packet size"}); + ActualBodySize == GivenBodySize -> + Body0; + true -> + binary:part(Body0, 0, GivenBodySize) end, Command = decode_command(Cmd), PartialRequest = #radius_request{cmd = Command, reqid = ReqId, authenticator = PacketAuthenticator, secret = Secret, msg_hmac = false}, DecodedState = decode_attributes(PartialRequest, RequestAuthenticator, Body), Request = PartialRequest#radius_request{attrs = lists:reverse(DecodedState#decoder_state.attrs), - eap_msg = list_to_binary(lists:reverse(DecodedState#decoder_state.eap_msg))}, + eap_msg = list_to_binary(lists:reverse(DecodedState#decoder_state.eap_msg))}, validate_authenticator(Command, <>, RequestAuthenticator, PacketAuthenticator, Body, Secret), if - is_integer(DecodedState#decoder_state.hmac_pos) -> - validate_packet_authenticator(Cmd, ReqId, Len, Body, DecodedState#decoder_state.hmac_pos, Secret, PacketAuthenticator, RequestAuthenticator), - Request#radius_request{msg_hmac = true}; - true -> Request + is_integer(DecodedState#decoder_state.hmac_pos) -> + validate_packet_authenticator(Cmd, ReqId, Len, Body, DecodedState#decoder_state.hmac_pos, Secret, PacketAuthenticator, RequestAuthenticator), + Request#radius_request{msg_hmac = true}; + true -> Request end. -spec validate_packet_authenticator(non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), binary(), binary(), authenticator(), authenticator() | 'undefined') -> ok. @@ -272,10 +272,10 @@ validate_packet_authenticator(Cmd, ReqId, Len, Auth, Body, Pos, Secret) -> case Body of <> -> case message_authenticator(Secret, [<>, Auth, Before, zero_authenticator(), After]) of - Value -> - ok; - _ -> - throw({bad_pdu, "Message-Authenticator Attribute is invalid"}) + Value -> + ok; + _ -> + throw({bad_pdu, "Message-Authenticator Attribute is invalid"}) end; _ -> throw({bad_pdu, "Message-Authenticator Attribute is malformed"}) @@ -284,15 +284,15 @@ validate_packet_authenticator(Cmd, ReqId, Len, Auth, Body, Pos, Secret) -> validate_authenticator(accreq, Head, _RequestAuthenticator, PacketAuthenticator, Body, Secret) -> compare_authenticator(crypto:hash(md5, [Head, zero_authenticator(), Body, Secret]), PacketAuthenticator); validate_authenticator(Cmd, Head, RequestAuthenticator, PacketAuthenticator, Body, Secret) - when - (Cmd =:= accept) orelse - (Cmd =:= reject) orelse - (Cmd =:= accresp) orelse - (Cmd =:= coaack) orelse - (Cmd =:= coanak) orelse - (Cmd =:= discack) orelse - (Cmd =:= discnak) orelse - (Cmd =:= challenge) -> + when + (Cmd =:= accept) orelse + (Cmd =:= reject) orelse + (Cmd =:= accresp) orelse + (Cmd =:= coaack) orelse + (Cmd =:= coanak) orelse + (Cmd =:= discack) orelse + (Cmd =:= discnak) orelse + (Cmd =:= challenge) -> compare_authenticator(crypto:hash(md5, [Head, RequestAuthenticator, Body, Secret]), PacketAuthenticator); validate_authenticator(_Cmd, _Head, _RequestAuthenticator, _PacketAuthenticator, _Body, _Secret) -> @@ -332,11 +332,11 @@ decode_attributes(Req, <>, Pos, State) ValueLength = ChunkLength - 2, <> = ChunkRest, NewState = case eradius_dict:lookup(attribute, Type) of - AttrRec = #attribute{} -> - decode_attribute(Value, Req, AttrRec, Pos + 2, State); - _ -> - append_attr({Type, Value}, State) - end, + AttrRec = #attribute{} -> + decode_attribute(Value, Req, AttrRec, Pos + 2, State); + _ -> + append_attr({Type, Value}, State) + end, decode_attributes(Req, PacketRest, Pos + ChunkLength, NewState). %% gotcha: the function returns a LIST of attribute-value pairs because @@ -354,12 +354,12 @@ decode_attribute(<>, Req, Attr = #attribute{type = Type, enc = decode_attribute(WholeBin = <>, Req, Attr = #attribute{type = {tagged, Type}}, _Pos, State) -> case {decode_tag_value(Tag), Attr#attribute.enc} of {0, no} -> - % decode including tag byte if tag is out of range + %% decode including tag byte if tag is out of range append_attr({Attr, {0, decode_value(WholeBin, Type)}}, State); {TagV, no} -> append_attr({Attr, {TagV, decode_value(Bin, Type)}}, State); {TagV, Encryption} -> - % for encrypted attributes, tag byte is never part of the value + %% for encrypted attributes, tag byte is never part of the value append_attr({Attr, {TagV, decode_value(decrypt_value(Req, State, Bin, Encryption), Type)}}, State) end. @@ -412,17 +412,17 @@ decode_integer(Bin) -> end. -spec decrypt_value(#radius_request{}, #decoder_state{}, binary(), - eradius_dict:attribute_encryption()) -> eradius_dict:attr_value(). + eradius_dict:attribute_encryption()) -> eradius_dict:attr_value(). decrypt_value(#radius_request{secret = Secret, authenticator = Authenticator}, - _, <>, scramble) -> + _, <>, scramble) -> scramble(Secret, Authenticator, Val); decrypt_value(#radius_request{secret = Secret}, - #decoder_state{request_authenticator = RequestAuthenticator}, - <>, salt_crypt) -when is_binary(RequestAuthenticator) -> + #decoder_state{request_authenticator = RequestAuthenticator}, + <>, salt_crypt) + when is_binary(RequestAuthenticator) -> salt_decrypt(Secret, RequestAuthenticator, Val); decrypt_value(#radius_request{secret = Secret, authenticator = Authenticator}, - _, <>, ascend) -> + _, <>, ascend) -> ascend(Secret, Authenticator, Val); decrypt_value(_Req, _State, <>, _Type) -> Val. @@ -435,11 +435,11 @@ decode_vendor_specific_attribute(Req, VendorID, <> = ChunkRest, VendorAttrKey = {VendorID, Type}, NewState = case eradius_dict:lookup(attribute, VendorAttrKey) of - Attr = #attribute{} -> - decode_attribute(Value, Req, Attr, Pos + 2, State); - _ -> - append_attr({VendorAttrKey, Value}, State) - end, + Attr = #attribute{} -> + decode_attribute(Value, Req, Attr, Pos + 2, State); + _ -> + append_attr({VendorAttrKey, Value}, State) + end, decode_vendor_specific_attribute(Req, VendorID, PacketRest, Pos + ChunkLength, NewState). %% ------------------------------------------------------------------------------------------ @@ -484,9 +484,9 @@ do_salt_crypt(Op, Salt, SharedSecret, RequestAuthenticator, < salt_crypt(Op, SharedSecret, B, <>, CipherText) -> NewCipherText = crypto:exor(PlainText, B), Bnext = case Op of - decrypt -> crypto:hash(md5, [SharedSecret, PlainText]); - encrypt -> crypto:hash(md5, [SharedSecret, NewCipherText]) - end, + decrypt -> crypto:hash(md5, [SharedSecret, PlainText]); + encrypt -> crypto:hash(md5, [SharedSecret, NewCipherText]) + end, salt_crypt(Op, SharedSecret, Bnext, Remaining, <>); salt_crypt(_Op, _SharedSecret, _B, << >>, CipherText) -> @@ -503,10 +503,10 @@ ascend(SharedSecret, RequestAuthenticator, <>) -> %% </a> -compile({inline, pad_to/2}). pad_to(Width, Binary) -> - case (Width - byte_size(Binary) rem Width) rem Width of - 0 -> Binary; - N -> <<Binary/binary, 0:(N*8)>> - end. + case (Width - byte_size(Binary) rem Width) rem Width of + 0 -> Binary; + N -> <<Binary/binary, 0:(N*8)>> + end. -spec timestamp() -> erlang:timestamp(). timestamp() -> diff --git a/src/eradius_log.erl b/src/eradius_log.erl index 20232fe3..f2974fc4 100644 --- a/src/eradius_log.erl +++ b/src/eradius_log.erl @@ -1,24 +1,24 @@ -% Copyright (c) 2010-2011 by Travelping GmbH <info@travelping.com> - -% Permission is hereby granted, free of charge, to any person obtaining a -% copy of this software and associated documentation files (the "Software"), -% to deal in the Software without restriction, including without limitation -% the rights to use, copy, modify, merge, publish, distribute, sublicense, -% and/or sell copies of the Software, and to permit persons to whom the -% Software is furnished to do so, subject to the following conditions: - -% The above copyright notice and this permission notice shall be included in -% all copies or substantial portions of the Software. - -% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -% DEALINGS IN THE SOFTWARE. - -% @private +%% Copyright (c) 2010-2011 by Travelping GmbH <info@travelping.com> + +%% Permission is hereby granted, free of charge, to any person obtaining a +%% copy of this software and associated documentation files (the "Software"), +%% to deal in the Software without restriction, including without limitation +%% the rights to use, copy, modify, merge, publish, distribute, sublicense, +%% and/or sell copies of the Software, and to permit persons to whom the +%% Software is furnished to do so, subject to the following conditions: + +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. + +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +%% DEALINGS IN THE SOFTWARE. + +%% @private -module(eradius_log). -behaviour(gen_server). @@ -81,7 +81,7 @@ handle_call(reconfigure, _From, State) -> file:close(State), {reply, ok, init_logger()}; -% for tests +%% for tests handle_call(get_state, _From, State) -> {reply, State, State}; @@ -99,7 +99,7 @@ handle_cast({write_request, Time, Sender, Request}, State) -> catch _:Error -> ?LOG(error, "Failed to log RADIUS request: error: ~p, request: ~p, sender: ~p, " - "logging will be disabled", [Error, Request, Sender]), + "logging will be disabled", [Error, Request, Sender]), {noreply, logger_disabled} end. @@ -245,8 +245,8 @@ collectable_attr_value(_Attr, Val) -> radius_date({{YYYY,MM,DD},{Hour,Min,Sec}}) -> DayNumber = calendar:day_of_the_week(YYYY, MM, DD), list_to_binary( - io_lib:format("~s ~3.s ~2.2.0w ~2.2.0w:~2.2.0w:~2.2.0w ~4.4.0w", - [day(DayNumber), month(MM), DD, Hour, Min, Sec, YYYY])). + io_lib:format("~s ~3.s ~2.2.0w ~2.2.0w:~2.2.0w:~2.2.0w ~4.4.0w", + [day(DayNumber), month(MM), DD, Hour, Min, Sec, YYYY])). format_unknown({VendId, Id}) -> case eradius_dict:lookup(vendor, VendId) of @@ -299,16 +299,16 @@ bin_to_hexstr(Bin) -> format_acct_status_type(Request) -> StatusType = eradius_lib:get_attr(Request, ?Acct_Status_Type), case StatusType of - undefined -> - ""; - 1 -> - "Start"; - 2 -> - "Stop"; - 3 -> - "Interim Update"; - 7 -> - "Accounting-On"; - 8 -> - "Accounting-Off" + undefined -> + ""; + 1 -> + "Start"; + 2 -> + "Stop"; + 3 -> + "Interim Update"; + 7 -> + "Accounting-On"; + 8 -> + "Accounting-Off" end. diff --git a/src/eradius_node_mon.erl b/src/eradius_node_mon.erl index 7d323724..f352ab40 100644 --- a/src/eradius_node_mon.erl +++ b/src/eradius_node_mon.erl @@ -52,11 +52,11 @@ get_remote_version(Node) -> %% ------------------------------------------------------------------------------------------ %% -- gen_server callbacks -record(state, { - live_registrar_nodes = sets:new() :: sets:set(), - dead_registrar_nodes = sets:new() :: sets:set(), - app_masters = maps:new() :: map(), - ping_timer :: reference() -}). + live_registrar_nodes = sets:new() :: sets:set(), + dead_registrar_nodes = sets:new() :: sets:set(), + app_masters = maps:new() :: map(), + ping_timer :: reference() + }). init([]) -> ets:new(?NODE_TAB, [bag, named_table, protected, {read_concurrency, true}]), @@ -80,8 +80,8 @@ handle_cast({remote_modules_ready_v1, ApplicationMaster, Modules}, State) -> handle_cast({modules_ready, ApplicationMaster, Modules}, State) -> NewState = State#state{app_masters = register_locally({ApplicationMaster, Modules}, State#state.app_masters)}, lists:foreach(fun (Node) -> - check_eradius_version(Node), - gen_server:cast({?SERVER, Node}, {remote_modules_ready_v1, ApplicationMaster, Modules}) + check_eradius_version(Node), + gen_server:cast({?SERVER, Node}, {remote_modules_ready_v1, ApplicationMaster, Modules}) end, nodes()), {noreply, NewState}. @@ -136,9 +136,9 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. dict_prepend(Key, List, Map) -> update_with(Key, fun (Old) -> List ++ Old end, List, Map). -% NOTE: -% copy-pasted from maps.erl to have backward compatability with OTP 18 -% it can be rewmoved if minimal version of OTP will be set to 19. +%% NOTE: +%% copy-pasted from maps.erl to have backward compatability with OTP 18 +%% it can be rewmoved if minimal version of OTP will be set to 19. update_with(Key,Fun,Init,Map) when is_function(Fun,1), is_map(Map) -> case maps:find(Key,Map) of {ok,Val} -> maps:update(Key,Fun(Val),Map); diff --git a/src/eradius_proxy.erl b/src/eradius_proxy.erl index 46d41b9a..ff95eb89 100644 --- a/src/eradius_proxy.erl +++ b/src/eradius_proxy.erl @@ -35,7 +35,7 @@ -behaviour(eradius_server). -export([radius_request/3, validate_arguments/1, get_routes_info/1, - put_default_route_to_pool/2, put_routes_to_pool/2]). + put_default_route_to_pool/2, put_routes_to_pool/2]). -ifdef(TEST). -export([resolve_routes/4, validate_options/1, new_request/3, @@ -90,18 +90,18 @@ validate_arguments(Args) -> compile_routes(undefined) -> []; compile_routes(Routes) -> RoutesOpts = lists:map(fun (Route) -> - {Name, Relay, RouteOptions} = route(Route), - case re:compile(Name) of - {ok, R} -> - case validate_route({Relay, RouteOptions}) of - false -> - false; - _ -> {R, Relay, RouteOptions} - end; - {error, {Error, Position}} -> - throw("Error during regexp compilation - " ++ Error ++ " at position " ++ integer_to_list(Position)) - end - end, Routes), + {Name, Relay, RouteOptions} = route(Route), + case re:compile(Name) of + {ok, R} -> + case validate_route({Relay, RouteOptions}) of + false -> + false; + _ -> {R, Relay, RouteOptions} + end; + {error, {Error, Position}} -> + throw("Error during regexp compilation - " ++ Error ++ " at position " ++ integer_to_list(Position)) + end + end, Routes), RelaysRegexps = lists:any(fun(Route) -> Route == false end, RoutesOpts), if RelaysRegexps == false -> RoutesOpts; @@ -109,11 +109,11 @@ compile_routes(Routes) -> false end. -% @private + % @private -spec send_to_server(Request :: #radius_request{}, Route :: undefined_route() | route(), Options :: eradius_client:options()) -> - {reply, Reply :: #radius_request{}} | term(). + {reply, Reply :: #radius_request{}} | term(). send_to_server(_Request, {undefined, 0, []}, _) -> {error, no_route}; @@ -123,8 +123,8 @@ send_to_server(#radius_request{reqid = ReqID} = Request, {{Server, Port, Secret} {ok, Result, Auth} -> decode_request(Result, ReqID, Secret, Auth); no_active_servers -> - % If all RADIUS servers are marked as inactive for now just use - % just skip fail-over mechanism and use default given Peer + % If all RADIUS servers are marked as inactive for now just use + % just skip fail-over mechanism and use default given Peer send_to_server(Request, {Server, Port, Secret}, Options); Error -> ?LOG(error, "~p: error during send_request (~p)", [?MODULE, Error]), @@ -138,7 +138,7 @@ send_to_server(#radius_request{reqid = ReqID} = Request, {Server, Port, Secret}, Error end. -% @private + % @private decode_request(Result, ReqID, Secret, Auth) -> case eradius_lib:decode_request(Result, Secret, Auth) of Reply = #radius_request{} -> @@ -148,7 +148,7 @@ decode_request(Result, ReqID, Secret, Auth) -> Error end. -% @private + % @private -spec validate_route(Route :: route()) -> boolean(). validate_route({{Host, Port, Secret}, RouteOpts}) -> validate_route_options(RouteOpts) and validate_route({Host, Port, Secret}); @@ -163,7 +163,7 @@ validate_route({Host, Port, Secret}) when is_tuple(Host) -> validate_route({Host, _Port, _Secret}) when is_binary(Host) -> true; validate_route(_) -> false. -% @private + % @private -spec validate_route_options(Options :: [proplists:property()] | pool_name()) -> boolean(). validate_route_options(PoolName) when is_atom(PoolName) -> true; @@ -173,7 +173,7 @@ validate_route_options(Options) -> Keys = proplists:get_keys(Options), lists:all(fun(Key) -> validate_route_option(Key, proplists:get_value(Key, Options)) end, Keys). -% @private + % @private -spec validate_route_option(Key :: atom(), Value :: term()) -> boolean(). validate_route_option(timeout, Value) when is_integer(Value) -> true; @@ -184,13 +184,13 @@ validate_route_option(pool, Value) when is_atom(Value) -> validate_route_option(_, _) -> false. -% @private + % @private -spec validate_options(Options :: [proplists:property()]) -> boolean(). validate_options(Options) -> Keys = proplists:get_keys(Options), lists:all(fun(Key) -> validate_option(Key, proplists:get_value(Key, Options)) end, Keys). -% @private + % @private -spec validate_option(Key :: atom(), Value :: term()) -> boolean(). validate_option(type, Value) when Value =:= realm; Value =:= prefix -> true; validate_option(type, _Value) -> false; @@ -202,21 +202,21 @@ validate_option(retries, Value) when is_integer(Value) -> true; validate_option(_, _) -> false. -% @private + % @private -spec new_request(Request :: #radius_request{}, Username :: undefined | binary(), NewUsername :: string()) -> - NewRequest :: #radius_request{}. + NewRequest :: #radius_request{}. new_request(Request, Username, Username) -> Request; new_request(Request, _Username, NewUsername) -> eradius_lib:set_attr(eradius_lib:del_attr(Request, ?User_Name), ?User_Name, NewUsername). -% @private + % @private -spec resolve_routes(Username :: undefined | binary(), DefaultRoute :: undefined_route() | route(), Routes :: routes(), Options :: [proplists:property()]) -> - {NewUsername :: string(), Route :: route()}. + {NewUsername :: string(), Route :: route()}. resolve_routes( undefined, DefaultRoute, _Routes, _Options) -> {undefined, DefaultRoute}; resolve_routes(Username, DefaultRoute, Routes, Options) -> @@ -242,9 +242,9 @@ find_suitable_relay(Key, [{Regexp, Relay, RelayOpts} | Routes], DefaultRoute) -> _ -> {Relay, RelayOpts} end. -% @private + % @private -spec get_key(Username :: binary() | string() | [], Type :: atom(), Strip :: boolean(), Separator :: list()) -> - {Key :: not_found | string(), NewUsername :: string()}. + {Key :: not_found | string(), NewUsername :: string()}. get_key([], _, _, _) -> {not_found, []}; get_key(Username, Type, Strip, Separator) when is_binary(Username) -> get_key(binary_to_list(Username), Type, Strip, Separator); @@ -256,9 +256,9 @@ get_key(Username, prefix, Strip, Separator) -> {Prefix, strip(Username, prefix, Strip, Separator)}; get_key(Username, _, _, _) -> {not_found, Username}. -% @private + % @private -spec strip(Username :: string(), Type :: atom(), Strip :: boolean(), Separator :: list()) -> - NewUsername :: string(). + NewUsername :: string(). strip(Username, _, false, _) -> Username; strip(Username, realm, true, Separator) -> case string:tokens(Username, Separator) of @@ -298,16 +298,16 @@ put_default_route_to_pool(_, _) -> ok. put_routes_to_pool(false, _Retries) -> ok; put_routes_to_pool({routes, Routes}, Retries) -> lists:foreach(fun (Route) -> - case Route of - {_RouteName, {Host, Port, _Secret}} -> - eradius_client:store_radius_server_from_pool(Host, Port, Retries); - {_RouteName, {Host, Port, _Secret}, _Pool} -> - eradius_client:store_radius_server_from_pool(Host, Port, Retries); - {Host, Port, _Secret, _Opts} -> - eradius_client:store_radius_server_from_pool(Host, Port, Retries); - _ -> ok - end - end, Routes). + case Route of + {_RouteName, {Host, Port, _Secret}} -> + eradius_client:store_radius_server_from_pool(Host, Port, Retries); + {_RouteName, {Host, Port, _Secret}, _Pool} -> + eradius_client:store_radius_server_from_pool(Host, Port, Retries); + {Host, Port, _Secret, _Opts} -> + eradius_client:store_radius_server_from_pool(Host, Port, Retries); + _ -> ok + end + end, Routes). get_proxy_opt(_, [], Default) -> Default; get_proxy_opt(OptName, [{OptName, AddrOrRoutes} | _], _) -> AddrOrRoutes; diff --git a/src/eradius_server.erl b/src/eradius_server.erl index 6bb680ab..a4ad3677 100644 --- a/src/eradius_server.erl +++ b/src/eradius_server.erl @@ -28,7 +28,9 @@ %% ``` %% -record(radius_request, { %% reqid :: byte(), -%% cmd :: 'request' | 'accept' | 'challenge' | 'reject' | 'accreq' | 'accresp' | 'coareq' | 'coaack' | 'coanak' | 'discreq' | 'discack' | 'discnak'm +%% cmd :: 'request' | 'accept' | 'challenge' | 'reject' | +%% 'accreq' | 'accresp' | 'coareq' | 'coaack' | 'coanak' | +%% 'discreq' | 'discack' | 'discnak' %% attrs :: eradius_lib:attribute_list(), %% secret :: eradius_lib:secret(), %% authenticator :: eradius_lib:authenticator(), @@ -77,13 +79,13 @@ -type udp_packet() :: {udp, udp_socket(), inet:ip_address(), port_number(), binary()}. -record(state, { - socket :: udp_socket(), % Socket Reference of opened UDP port - ip = {0,0,0,0} :: inet:ip_address(), % IP to which this socket is bound - port = 0 :: port_number(), % Port number we are listening on - transacts :: ets:tid(), % ETS table containing current transactions - counter :: #server_counter{}, % statistics counter, - name :: atom() % server name -}). + socket :: udp_socket(), % Socket Reference of opened UDP port + ip = {0,0,0,0} :: inet:ip_address(), % IP to which this socket is bound + port = 0 :: port_number(), % Port number we are listening on + transacts :: ets:tid(), % ETS table containing current transactions + counter :: #server_counter{}, % statistics counter, + name :: atom() % server name + }). -optional_callbacks([validate_arguments/1]). @@ -143,7 +145,7 @@ handle_info(ReqUDP = {udp, Socket, FromIP, FromPortNo, Packet}, [{_ReqKey, {handling, HandlerPid}}] -> %% handler process is still working on the request ?LOG(debug, "~s From: ~s INF: Handler process ~p is still working on the request. duplicate request (being handled) ~p", - [printable_peer(ServerIP, Port), printable_peer(FromIP, FromPortNo), HandlerPid, ReqKey]), + [printable_peer(ServerIP, Port), printable_peer(FromIP, FromPortNo), HandlerPid, ReqKey]), eradius_counter:inc_counter(dupRequests, NasProp); [{_ReqKey, {replied, HandlerPid}}] -> %% handler process waiting for resend message @@ -221,23 +223,23 @@ do_radius(ServerPid, ServerName, ReqKey, Handler = {HandlerMod, _}, NasProp, {ud case run_handler(Nodes, NasProp, Handler, EncRequest) of {reply, EncReply, {ReqCmd, RespCmd}, Request} -> ?LOG(debug, "~s From: ~s INF: Sending response for request ~p", - [printable_peer(ServerIP, Port), printable_peer(FromIP, FromPort), ReqKey]), + [printable_peer(ServerIP, Port), printable_peer(FromIP, FromPort), ReqKey]), TS2 = erlang:monotonic_time(), inc_counter({ReqCmd, RespCmd}, ServerName, NasProp, TS2 - TS1, Request), gen_udp:send(Socket, FromIP, FromPort, EncReply), case application:get_env(eradius, resend_timeout, 2000) of ResendTimeout when ResendTimeout > 0, is_integer(ResendTimeout) -> - ServerPid ! {replied, ReqKey, self()}, - wait_resend_init(ServerPid, ReqKey, FromIP, FromPort, EncReply, ResendTimeout, ?RESEND_RETRIES); + ServerPid ! {replied, ReqKey, self()}, + wait_resend_init(ServerPid, ReqKey, FromIP, FromPort, EncReply, ResendTimeout, ?RESEND_RETRIES); _ -> ok end; {discard, Reason} -> ?LOG(debug, "~s From: ~s INF: Handler discarded the request ~p for reason ~1000.p", - [printable_peer(ServerIP, Port), printable_peer(FromIP, FromPort), Reason, ReqKey]), + [printable_peer(ServerIP, Port), printable_peer(FromIP, FromPort), Reason, ReqKey]), inc_discard_counter(Reason, NasProp); {exit, Reason} -> ?LOG(debug, "~s From: ~s INF: Handler exited for reason ~p, discarding request ~p", - [printable_peer(ServerIP, Port), printable_peer(FromIP, FromPort), Reason, ReqKey]), + [printable_peer(ServerIP, Port), printable_peer(FromIP, FromPort), Reason, ReqKey]), inc_discard_counter(packetsDropped, NasProp) end, eradius_counter:dec_counter(pending, NasProp). @@ -342,7 +344,7 @@ apply_handler_mod(HandlerMod, HandlerArg, Request, NasProp) -> {reply, EncReply, {Request#radius_request.cmd, ReplyCmd}, Request}; noreply -> ?LOG(error, "~s INF: Noreply for request ~p from handler ~p: returned value: ~p", - [printable_peer(ServerIP, Port), Request, HandlerArg, noreply]), + [printable_peer(ServerIP, Port), Request, HandlerArg, noreply]), {discard, handler_returned_noreply}; {error, timeout} -> ReqType = eradius_log:format_cmd(Request#radius_request.cmd), @@ -356,12 +358,12 @@ apply_handler_mod(HandlerMod, HandlerArg, Request, NasProp) -> {discard, {bad_return, {error, timeout}}}; OtherReturn -> ?LOG(error, "~s INF: Unexpected return for request ~p from handler ~p: returned value: ~p", - [printable_peer(ServerIP, Port), Request, HandlerArg, OtherReturn]), + [printable_peer(ServerIP, Port), Request, HandlerArg, OtherReturn]), {discard, {bad_return, OtherReturn}} catch Class:Reason:S -> ?LOG(error, "~s INF: Handler crashed after request ~p, radius handler class: ~p, reason of crash: ~p, stacktrace: ~p", - [printable_peer(ServerIP, Port), Request, Class, Reason, S]), + [printable_peer(ServerIP, Port), Request, Class, Reason, S]), {exit, {Class, Reason}} end. @@ -449,10 +451,16 @@ inc_discard_counter(_Reason, NasProp) -> server_request_counter_account_match_spec_compile() -> case persistent_term:get({?MODULE, ?FUNCTION_NAME}, undefined) of undefined -> - MatchSpecCompile = ets:match_spec_compile(ets:fun2ms(fun - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Start}) -> accountRequestsStart; - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Stop}) -> accountRequestsStop; - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Update}) -> accountRequestsUpdate end)), + MatchSpecCompile = + ets:match_spec_compile( + ets:fun2ms( + fun ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Start}) -> + accountRequestsStart; + ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Stop}) -> + accountRequestsStop; + ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Update}) -> + accountRequestsUpdate + end)), persistent_term:put({?MODULE, ?FUNCTION_NAME}, MatchSpecCompile), MatchSpecCompile; MatchSpecCompile -> @@ -462,10 +470,16 @@ server_request_counter_account_match_spec_compile() -> server_response_counter_account_match_spec_compile() -> case persistent_term:get({?MODULE, ?FUNCTION_NAME}, undefined) of undefined -> - MatchSpecCompile = ets:match_spec_compile(ets:fun2ms(fun - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Start}) -> accountResponsesStart; - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Stop}) -> accountResponsesStop; - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Update}) -> accountResponsesUpdate end)), + MatchSpecCompile = + ets:match_spec_compile( + ets:fun2ms( + fun ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Start}) -> + accountResponsesStart; + ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Stop}) -> + accountResponsesStop; + ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Update}) -> + accountResponsesUpdate + end)), persistent_term:put({?MODULE, ?FUNCTION_NAME}, MatchSpecCompile), MatchSpecCompile; MatchSpecCompile -> diff --git a/src/eradius_server_mon.erl b/src/eradius_server_mon.erl index 6f4b6f84..dabaa386 100644 --- a/src/eradius_server_mon.erl +++ b/src/eradius_server_mon.erl @@ -22,11 +22,11 @@ -import(eradius_lib, [printable_peer/2]). -record(nas, { - key :: {server(), inet:ip_address()}, - server_name :: server_name(), - handler :: handler(), - prop :: #nas_prop{} -}). + key :: {server(), inet:ip_address()}, + server_name :: server_name(), + handler :: handler(), + prop :: #nas_prop{} + }). %% ------------------------------------------------------------------------------------------ %% -- API @@ -65,7 +65,7 @@ lookup_pid(ServerIP, ServerPort) -> %% @doc returns the list of all currently configured NASs -spec all_nas_keys() -> [term()]. all_nas_keys() -> - ets:select(?NAS_TAB, [{#nas{key = '$1', _ = '_'}, [], ['$1']}]). + ets:select(?NAS_TAB, [{#nas{key = '$1', _ = '_'}, [], ['$1']}]). %% ------------------------------------------------------------------------------------------ %% -- gen_server callbacks @@ -130,11 +130,11 @@ server_naslist({ServerName, {IP, Port, _Opts}, HandlerList}) -> server_naslist({ServerName, {IP, Port}, HandlerList}); server_naslist({ServerName, {IP, Port}, HandlerList}) -> lists:map(fun({NasId, NasIP, Secret, HandlerNodes, HandlerMod, HandlerArgs}) -> - ServerInfo = eradius_lib:make_addr_info({ServerName, {IP, Port}}), - NasInfo = eradius_lib:make_addr_info({NasId, {NasIP, undefined}}), - #nas{key = {{IP, Port}, NasIP}, server_name = ServerName, handler = {HandlerMod, HandlerArgs}, - prop = #nas_prop{handler_nodes = HandlerNodes, nas_id = NasId, nas_ip = NasIP, secret = Secret, - metrics_info = {ServerInfo, NasInfo}}} + ServerInfo = eradius_lib:make_addr_info({ServerName, {IP, Port}}), + NasInfo = eradius_lib:make_addr_info({NasId, {NasIP, undefined}}), + #nas{key = {{IP, Port}, NasIP}, server_name = ServerName, handler = {HandlerMod, HandlerArgs}, + prop = #nas_prop{handler_nodes = HandlerNodes, nas_id = NasId, nas_ip = NasIP, secret = Secret, + metrics_info = {ServerInfo, NasInfo}}} end, HandlerList). config_nodes(NasHandler) -> @@ -142,26 +142,26 @@ config_nodes(NasHandler) -> update_server(Running, ToStop, ToStart) -> Stopped = lists:map(fun(ServerAddr = {_ServerName, Addr}) -> - StoppedServer = {_, _, Pid} = lists:keyfind(Addr, 2, Running), - eradius_server_sup:stop_instance(ServerAddr, Pid), - StoppedServer + StoppedServer = {_, _, Pid} = lists:keyfind(Addr, 2, Running), + eradius_server_sup:stop_instance(ServerAddr, Pid), + StoppedServer end, ToStop), StartFn = fun({ServerName, Addr = {IP, Port, _Opts}}=ServerAddr) -> - case eradius_server_sup:start_instance(ServerAddr) of - {ok, Pid} -> - {ServerName, Addr, Pid}; - {error, Error} -> - ?LOG(error, "Could not start listener on host: ~s, occurring error: ~p", - [printable_peer(IP, Port), Error]) - end - end, + case eradius_server_sup:start_instance(ServerAddr) of + {ok, Pid} -> + {ServerName, Addr, Pid}; + {error, Error} -> + ?LOG(error, "Could not start listener on host: ~s, occurring error: ~p", + [printable_peer(IP, Port), Error]) + end + end, NewStarted = lists:map(fun - ({ServerName, {IP, Port}}) -> - StartFn({ServerName, {IP, Port, []}}); - (ServerAddr) -> - StartFn(ServerAddr) - end, - ToStart), + ({ServerName, {IP, Port}}) -> + StartFn({ServerName, {IP, Port, []}}); + (ServerAddr) -> + StartFn(ServerAddr) + end, + ToStart), (Running -- Stopped) ++ NewStarted. update_nases(ToDelete, ToInsert) -> diff --git a/test/eradius_auth_SUITE.erl b/test/eradius_auth_SUITE.erl index f49f839a..aec33608 100644 --- a/test/eradius_auth_SUITE.erl +++ b/test/eradius_auth_SUITE.erl @@ -1,44 +1,44 @@ -% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> - -% Permission is hereby granted, free of charge, to any person obtaining a -% copy of this software and associated documentation files (the "Software"), -% to deal in the Software without restriction, including without limitation -% the rights to use, copy, modify, merge, publish, distribute, sublicense, -% and/or sell copies of the Software, and to permit persons to whom the -% Software is furnished to do so, subject to the following conditions: - -% The above copyright notice and this permission notice shall be included in -% all copies or substantial portions of the Software. - -% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -% DEALINGS IN THE SOFTWARE. +%% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> + +%% Permission is hereby granted, free of charge, to any person obtaining a +%% copy of this software and associated documentation files (the "Software"), +%% to deal in the Software without restriction, including without limitation +%% the rights to use, copy, modify, merge, publish, distribute, sublicense, +%% and/or sell copies of the Software, and to permit persons to whom the +%% Software is furnished to do so, subject to the following conditions: + +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. + +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +%% DEALINGS IN THE SOFTWARE. -module(eradius_auth_SUITE). -compile(export_all). -include("eradius_test.hrl"). all() -> [ - unicode_test, - password_hash_test, - unicode_password_hash_test, - password_hashhash_test, - password_hash2_test, - des_key_test, - nt_response_test, - v2_authenticator_test, - mppe_master_key_test, - mppe_sendstart_key40_test, - mppe_sendstart_key56_test, - mppe_sendstart_key128_test, - mppe_start_key40_test, - mppe_start_key56_test, - mppe_start_key128_test - ]. + unicode_test, + password_hash_test, + unicode_password_hash_test, + password_hashhash_test, + password_hash2_test, + des_key_test, + nt_response_test, + v2_authenticator_test, + mppe_master_key_test, + mppe_sendstart_key40_test, + mppe_sendstart_key56_test, + mppe_sendstart_key128_test, + mppe_start_key40_test, + mppe_start_key56_test, + mppe_start_key128_test + ]. %% 0-to-256-char UserName diff --git a/test/eradius_client_SUITE.erl b/test/eradius_client_SUITE.erl index ded4ea01..9dd2265b 100644 --- a/test/eradius_client_SUITE.erl +++ b/test/eradius_client_SUITE.erl @@ -1,22 +1,22 @@ -% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> - -% Permission is hereby granted, free of charge, to any person obtaining a -% copy of this software and associated documentation files (the "Software"), -% to deal in the Software without restriction, including without limitation -% the rights to use, copy, modify, merge, publish, distribute, sublicense, -% and/or sell copies of the Software, and to permit persons to whom the -% Software is furnished to do so, subject to the following conditions: - -% The above copyright notice and this permission notice shall be included in -% all copies or substantial portions of the Software. - -% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -% DEALINGS IN THE SOFTWARE. +%% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> + +%% Permission is hereby granted, free of charge, to any person obtaining a +%% copy of this software and associated documentation files (the "Software"), +%% to deal in the Software without restriction, including without limitation +%% the rights to use, copy, modify, merge, publish, distribute, sublicense, +%% and/or sell copies of the Software, and to permit persons to whom the +%% Software is furnished to do so, subject to the following conditions: + +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. + +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +%% DEALINGS IN THE SOFTWARE. -module(eradius_client_SUITE). -compile(export_all). @@ -46,17 +46,17 @@ ?GOOD_SERVER_2_TUPLE]). all() -> [ - send_request, - wanna_send, - reconf_address, - wanna_send, - reconf_ports_30, - wanna_send, - reconf_ports_10, - wanna_send, - send_request_failover, - check_upstream_servers - ]. + send_request, + wanna_send, + reconf_address, + wanna_send, + reconf_ports_30, + wanna_send, + reconf_ports_10, + wanna_send, + send_request_failover, + check_upstream_servers + ]. init_per_suite(Config) -> {ok, _} = application:ensure_all_started(eradius), @@ -132,13 +132,13 @@ testSocket(Pid) -> end. -record(state, { - socket_ip :: inet:ip_address(), - no_ports = 1 :: pos_integer(), - idcounters = maps:new() :: map(), - sockets = array:new() :: array:array(), - sup :: pid(), - subscribed_clients = [] :: [{{integer(),integer(),integer(),integer()}, integer()}] -}). + socket_ip :: inet:ip_address(), + no_ports = 1 :: pos_integer(), + idcounters = maps:new() :: map(), + sockets = array:new() :: array:array(), + sup :: pid(), + subscribed_clients = [] :: [{{integer(),integer(),integer(),integer()}, integer()}] + }). split(N, List) -> split2(N, [], List). @@ -178,25 +178,25 @@ test(false, Msg) -> check(OldState, NewState = #state{no_ports = P}, null, A) -> check(OldState, NewState, P, A); check(OldState, NewState = #state{socket_ip = A}, P, null) -> check(OldState, NewState, P, A); check(#state{sockets = OS, no_ports = _OP, idcounters = _OC, socket_ip = OA}, - #state{sockets = NS, no_ports = NP, idcounters = NC, socket_ip = NA}, - P, A) -> + #state{sockets = NS, no_ports = NP, idcounters = NC, socket_ip = NA}, + P, A) -> {ok, PA} = parse_ip(A), test(PA == NA, "Adress not configured") and - case NA of - OA -> - {_, Rest} = split(NP, array:to_list(OS)), - test(P == NP,"Ports not configured") and - test(maps:fold( fun(_Peer, {NextPortIdx, _NextReqId}, Akk) -> - Akk and (NextPortIdx =< NP) - end, true, NC), "Invalid port counter") and - test(getSocketCount() =< NP, "Sockets not closed") and - test(array:size(NS) =< NP, "Socket array not resized") and - test(lists:all(fun(Pid) -> testSocket(Pid) end, Rest), "Sockets still available"); - _ -> - test(array:size(NS) == 0, "Socket array not cleaned") and - test(getSocketCount() == 0, "Sockets not closed") and - test(lists:all(fun(Pid) -> testSocket(Pid) end, array:to_list(OS)), "Sockets still available") - end. + case NA of + OA -> + {_, Rest} = split(NP, array:to_list(OS)), + test(P == NP,"Ports not configured") and + test(maps:fold( fun(_Peer, {NextPortIdx, _NextReqId}, Akk) -> + Akk and (NextPortIdx =< NP) + end, true, NC), "Invalid port counter") and + test(getSocketCount() =< NP, "Sockets not closed") and + test(array:size(NS) =< NP, "Socket array not resized") and + test(lists:all(fun(Pid) -> testSocket(Pid) end, Rest), "Sockets still available"); + _ -> + test(array:size(NS) == 0, "Socket array not cleaned") and + test(getSocketCount() == 0, "Sockets not closed") and + test(lists:all(fun(Pid) -> testSocket(Pid) end, array:to_list(OS)), "Sockets still available") + end. %% TESTS @@ -217,12 +217,12 @@ send(FUN, Ports, Address) -> wanna_send(_Config) -> lists:map(fun(_) -> - IP = {rand:uniform(100), rand:uniform(100), rand:uniform(100), rand:uniform(100)}, - Port = rand:uniform(100), - MetricsInfo = {{undefined, undefined, undefined}, {undefined, undefined, undefined}}, - FUN = fun() -> gen_server:call(eradius_client, {wanna_send, {undefined, {IP, Port}}, MetricsInfo}) end, - send(FUN, null, null) - end, lists:seq(1, 10)). + IP = {rand:uniform(100), rand:uniform(100), rand:uniform(100), rand:uniform(100)}, + Port = rand:uniform(100), + MetricsInfo = {{undefined, undefined, undefined}, {undefined, undefined, undefined}}, + FUN = fun() -> gen_server:call(eradius_client, {wanna_send, {undefined, {IP, Port}}, MetricsInfo}) end, + send(FUN, null, null) + end, lists:seq(1, 10)). %% I've catched some data races with `delSocket()' and `getSocketCount()' when %% `delSocket()' happens after `getSocketCount()' (because `delSocket()' is sent from another process). diff --git a/test/eradius_client_socket_test.erl b/test/eradius_client_socket_test.erl index ee465c59..c2a3bf12 100644 --- a/test/eradius_client_socket_test.erl +++ b/test/eradius_client_socket_test.erl @@ -1,22 +1,22 @@ -% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> - -% Permission is hereby granted, free of charge, to any person obtaining a -% copy of this software and associated documentation files (the "Software"), -% to deal in the Software without restriction, including without limitation -% the rights to use, copy, modify, merge, publish, distribute, sublicense, -% and/or sell copies of the Software, and to permit persons to whom the -% Software is furnished to do so, subject to the following conditions: - -% The above copyright notice and this permission notice shall be included in -% all copies or substantial portions of the Software. - -% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -% DEALINGS IN THE SOFTWARE. +%% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> + +%% Permission is hereby granted, free of charge, to any person obtaining a +%% copy of this software and associated documentation files (the "Software"), +%% to deal in the Software without restriction, including without limitation +%% the rights to use, copy, modify, merge, publish, distribute, sublicense, +%% and/or sell copies of the Software, and to permit persons to whom the +%% Software is furnished to do so, subject to the following conditions: + +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. + +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +%% DEALINGS IN THE SOFTWARE. -module(eradius_client_socket_test). @@ -42,13 +42,13 @@ handle_cast(_Msg, State) -> {noreply, State}. handle_info({SenderPid, send_request, {IP, Port}, ReqId, _EncRequest}, - State = #state{pending = Pending, counter = Counter}) -> + State = #state{pending = Pending, counter = Counter}) -> ReqKey = {IP, Port, ReqId}, NPending = Pending#{ReqKey => SenderPid}, {noreply, State#state{pending = NPending, counter = Counter+1}}; handle_info(close, State) -> - %~ {noreply, State#state{mode = inactive}}; + %~ {noreply, State#state{mode = inactive}}; {stop, normal, State}; handle_info({status, Pid}, State = #state{mode = Mode}) -> diff --git a/test/eradius_config_SUITE.erl b/test/eradius_config_SUITE.erl index 39edc763..2f35e1e6 100644 --- a/test/eradius_config_SUITE.erl +++ b/test/eradius_config_SUITE.erl @@ -1,22 +1,22 @@ -% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> +%% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> -% Permission is hereby granted, free of charge, to any person obtaining a -% copy of this software and associated documentation files (the "Software"), -% to deal in the Software without restriction, including without limitation -% the rights to use, copy, modify, merge, publish, distribute, sublicense, -% and/or sell copies of the Software, and to permit persons to whom the -% Software is furnished to do so, subject to the following conditions: +%% Permission is hereby granted, free of charge, to any person obtaining a +%% copy of this software and associated documentation files (the "Software"), +%% to deal in the Software without restriction, including without limitation +%% the rights to use, copy, modify, merge, publish, distribute, sublicense, +%% and/or sell copies of the Software, and to permit persons to whom the +%% Software is furnished to do so, subject to the following conditions: -% The above copyright notice and this permission notice shall be included in -% all copies or substantial portions of the Software. +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. -% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -% DEALINGS IN THE SOFTWARE. +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +%% DEALINGS IN THE SOFTWARE. -module(eradius_config_SUITE). -compile(export_all). @@ -40,40 +40,40 @@ config_1(_Config) -> Conf = [{session_nodes, ['node1@host1', 'node2@host2']}, {radius_callback, ?MODULE}, {servers, [ - {root, {eradius_test_handler:localhost(ip), [1812, 1813]}} + {root, {eradius_test_handler:localhost(ip), [1812, 1813]}} ]}, {root, [ - { {"NAS1", [arg1, arg2]}, - [{"10.18.14.2/30", <<"secret1">>}]}, - { {"NAS2", [arg1, arg2]}, - [{{10, 18, 14, 3}, <<"secret2">>, [{nas_id, <<"name">>}]}]} + { {"NAS1", [arg1, arg2]}, + [{"10.18.14.2/30", <<"secret1">>}]}, + { {"NAS2", [arg1, arg2]}, + [{{10, 18, 14, 3}, <<"secret2">>, [{nas_id, <<"name">>}]}]} ]}], ok = apply_conf(Conf), LocalHost = eradius_test_handler:localhost(tuple), ?match({ok, {?MODULE,[arg1,arg2]}, - #nas_prop{ - server_ip = LocalHost, - server_port = 1813, - nas_id = <<"name">>, - nas_ip = {10,18,14,3}, - handler_nodes = ['node1@host1', 'node2@host2'] - }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,3})), + #nas_prop{ + server_ip = LocalHost, + server_port = 1813, + nas_id = <<"name">>, + nas_ip = {10,18,14,3}, + handler_nodes = ['node1@host1', 'node2@host2'] + }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,3})), ?match({ok, {?MODULE,[arg1,arg2]}, - #nas_prop{ - server_ip = LocalHost, - server_port = 1812, - nas_id = <<"name">>, - nas_ip = {10,18,14,3}, - handler_nodes = ['node1@host1', 'node2@host2'] - }}, eradius_server_mon:lookup_handler(LocalHost, 1812, {10,18,14,3})), + #nas_prop{ + server_ip = LocalHost, + server_port = 1812, + nas_id = <<"name">>, + nas_ip = {10,18,14,3}, + handler_nodes = ['node1@host1', 'node2@host2'] + }}, eradius_server_mon:lookup_handler(LocalHost, 1812, {10,18,14,3})), ?match({ok, {?MODULE,[arg1,arg2]}, - #nas_prop{ - server_ip = LocalHost, - server_port = 1813, - nas_id = <<"NAS1_10.18.14.2">>, - nas_ip = {10,18,14,2}, - handler_nodes = ['node1@host1', 'node2@host2'] - }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,2})), + #nas_prop{ + server_ip = LocalHost, + server_port = 1813, + nas_id = <<"NAS1_10.18.14.2">>, + nas_ip = {10,18,14,2}, + handler_nodes = ['node1@host1', 'node2@host2'] + }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,2})), ok. config_2(_Config) -> @@ -83,41 +83,41 @@ config_2(_Config) -> ] }, {servers, [ - {root, {eradius_test_handler:localhost(ip), [1812, 1813]}} + {root, {eradius_test_handler:localhost(ip), [1812, 1813]}} ]}, {root, [ - { {handler1, "NAS1", [arg1, arg2]}, - [ {"10.18.14.3", <<"secret1">>, [{group, "NodeGroup1"}]}, - {"10.18.14.4", <<"secret1">>, [{group, "NodeGroup1"}]} ] }, - { {handler2, "NAS2", [arg3, arg4]}, - [ {"10.18.14.2", <<"secret2">>, [{group, "NodeGroup2"}]} ] } - ]}], + { {handler1, "NAS1", [arg1, arg2]}, + [ {"10.18.14.3", <<"secret1">>, [{group, "NodeGroup1"}]}, + {"10.18.14.4", <<"secret1">>, [{group, "NodeGroup1"}]} ] }, + { {handler2, "NAS2", [arg3, arg4]}, + [ {"10.18.14.2", <<"secret2">>, [{group, "NodeGroup2"}]} ] } + ]}], ok = apply_conf(Conf), LocalHost = eradius_test_handler:localhost(tuple), ?match({ok, {handler1,[arg1,arg2]}, - #nas_prop{ - server_ip = LocalHost, - server_port = 1813, - nas_id = <<"NAS1_10.18.14.3">>, - nas_ip = {10,18,14,3}, - handler_nodes = ['node1@host1', 'node2@host2'] - }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,3})), + #nas_prop{ + server_ip = LocalHost, + server_port = 1813, + nas_id = <<"NAS1_10.18.14.3">>, + nas_ip = {10,18,14,3}, + handler_nodes = ['node1@host1', 'node2@host2'] + }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,3})), ?match({ok, {handler1,[arg1,arg2]}, - #nas_prop{ - server_ip = LocalHost, - server_port = 1813, - nas_id = <<"NAS1_10.18.14.4">>, - nas_ip = {10,18,14,4}, - handler_nodes = ['node1@host1', 'node2@host2'] - }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,4})), + #nas_prop{ + server_ip = LocalHost, + server_port = 1813, + nas_id = <<"NAS1_10.18.14.4">>, + nas_ip = {10,18,14,4}, + handler_nodes = ['node1@host1', 'node2@host2'] + }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,4})), ?match({ok, {handler2,[arg3,arg4]}, - #nas_prop{ - server_ip = LocalHost, - server_port = 1813, - nas_id = <<"NAS2_10.18.14.2">>, - nas_ip = {10,18,14,2}, - handler_nodes = ['node3@host3', 'node4@host4'] - }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,2})), + #nas_prop{ + server_ip = LocalHost, + server_port = 1813, + nas_id = <<"NAS2_10.18.14.2">>, + nas_ip = {10,18,14,2}, + handler_nodes = ['node3@host3', 'node4@host4'] + }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,2})), ok. config_socket_options(_Config) -> @@ -159,46 +159,46 @@ config_with_ranges(_Config) -> ] }, {servers, [ - {root, {eradius_test_handler:localhost(ip), [1812, 1813]}} + {root, {eradius_test_handler:localhost(ip), [1812, 1813]}} ]}, {root, [ - { {handler, "NAS", []}, - [ {"10.18.14.2/30", <<"secret2">>, [{group, "NodeGroup"}]} ] } - ]}], + { {handler, "NAS", []}, + [ {"10.18.14.2/30", <<"secret2">>, [{group, "NodeGroup"}]} ] } + ]}], ok = apply_conf(Conf), LocalHost = eradius_test_handler:localhost(tuple), ?match({ok, {handler,[]}, - #nas_prop{ - server_ip = LocalHost, - server_port = 1812, - nas_id = <<"NAS_10.18.14.2">>, - nas_ip = {10,18,14,2}, - handler_nodes = Nodes - }}, eradius_server_mon:lookup_handler(LocalHost, 1812, {10,18,14,2})), + #nas_prop{ + server_ip = LocalHost, + server_port = 1812, + nas_id = <<"NAS_10.18.14.2">>, + nas_ip = {10,18,14,2}, + handler_nodes = Nodes + }}, eradius_server_mon:lookup_handler(LocalHost, 1812, {10,18,14,2})), ?match({ok, {handler,[]}, - #nas_prop{ - server_ip = LocalHost, - server_port = 1812, - nas_id = <<"NAS_10.18.14.3">>, - nas_ip = {10,18,14,3}, - handler_nodes = Nodes - }}, eradius_server_mon:lookup_handler(LocalHost, 1812, {10,18,14,3})), + #nas_prop{ + server_ip = LocalHost, + server_port = 1812, + nas_id = <<"NAS_10.18.14.3">>, + nas_ip = {10,18,14,3}, + handler_nodes = Nodes + }}, eradius_server_mon:lookup_handler(LocalHost, 1812, {10,18,14,3})), ?match({ok, {handler,[]}, - #nas_prop{ - server_ip = LocalHost, - server_port = 1813, - nas_id = <<"NAS_10.18.14.1">>, - nas_ip = {10,18,14,1}, - handler_nodes = Nodes - }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,1})), + #nas_prop{ + server_ip = LocalHost, + server_port = 1813, + nas_id = <<"NAS_10.18.14.1">>, + nas_ip = {10,18,14,1}, + handler_nodes = Nodes + }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,1})), ?match({ok, {handler,[]}, - #nas_prop{ - server_ip = LocalHost, - server_port = 1813, - nas_id = <<"NAS_10.18.14.2">>, - nas_ip = {10,18,14,2}, - handler_nodes = Nodes - }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,2})), + #nas_prop{ + server_ip = LocalHost, + server_port = 1813, + nas_id = <<"NAS_10.18.14.2">>, + nas_ip = {10,18,14,2}, + handler_nodes = Nodes + }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,2})), ok. log_test(_Config) -> @@ -208,7 +208,7 @@ log_test(_Config) -> LogOn1 = [{logging, true}, {logfile, LogFile1}], LogOff = [{logging, false}], - % via eradius_log:reconfigure/0 + %% via eradius_log:reconfigure/0 set_env(LogOn0), ok = eradius_log:reconfigure(), ?match(true, logger_disabled /= gen_server:call(eradius_log, get_state)), @@ -224,7 +224,7 @@ log_test(_Config) -> ?match(true, logger_disabled /= gen_server:call(eradius_log, get_state)), ?match(true, filelib:is_file(LogFile1)), - % via eradius:config_change/3 + %% via eradius:config_change/3 set_env(LogOff), eradius:config_change([], LogOff, []), logger_disabled = gen_server:call(eradius_log, get_state), @@ -233,7 +233,7 @@ log_test(_Config) -> eradius:config_change([], LogOn1, []), ?match(true, logger_disabled /= gen_server:call(eradius_log, get_state)), - % check default value for logging + %% check default value for logging application:unset_env(eradius, logging), eradius:config_change([], [], [logging]), logger_disabled = gen_server:call(eradius_log, get_state), @@ -249,7 +249,7 @@ apply_conf(Config) -> generate_ip_list_test(_) -> ?equal([{192, 168, 11, 148}, {192, 168, 11, 149}, {192, 168, 11, 150}, {192, 168, 11, 151}], - eradius_config:generate_ip_list({192, 168, 11, 150}, "30")), + eradius_config:generate_ip_list({192, 168, 11, 150}, "30")), eradius_config:generate_ip_list({192, 168, 11, 150}, 24), ?equal(256, length(eradius_config:generate_ip_list({192, 168, 11, 150}, 24))), ?equal(2048, length(eradius_config:generate_ip_list({192, 168, 11, 10}, 21))), diff --git a/test/eradius_lib_SUITE.erl b/test/eradius_lib_SUITE.erl index 1496213d..8dc884c9 100644 --- a/test/eradius_lib_SUITE.erl +++ b/test/eradius_lib_SUITE.erl @@ -1,22 +1,22 @@ -% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> - -% Permission is hereby granted, free of charge, to any person obtaining a -% copy of this software and associated documentation files (the "Software"), -% to deal in the Software without restriction, including without limitation -% the rights to use, copy, modify, merge, publish, distribute, sublicense, -% and/or sell copies of the Software, and to permit persons to whom the -% Software is furnished to do so, subject to the following conditions: - -% The above copyright notice and this permission notice shall be included in -% all copies or substantial portions of the Software. - -% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -% DEALINGS IN THE SOFTWARE. +%% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> + +%% Permission is hereby granted, free of charge, to any person obtaining a +%% copy of this software and associated documentation files (the "Software"), +%% to deal in the Software without restriction, including without limitation +%% the rights to use, copy, modify, merge, publish, distribute, sublicense, +%% and/or sell copies of the Software, and to permit persons to whom the +%% Software is furnished to do so, subject to the following conditions: + +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. + +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +%% DEALINGS IN THE SOFTWARE. -module(eradius_lib_SUITE). -include("eradius_lib.hrl"). @@ -63,9 +63,9 @@ init_per_suite(Config) -> Config. end_per_suite(_Config) -> ok. init_per_testcase(Test, Config) when Test == dec_vendor_integer_t - orelse Test == dec_vendor_string_t - orelse Test == dec_vendor_ipv4_t - orelse Test == vendor_attribute_id_conflict_test -> + orelse Test == dec_vendor_string_t + orelse Test == dec_vendor_ipv4_t + orelse Test == vendor_attribute_id_conflict_test -> application:set_env(eradius, tables, [dictionary]), eradius_dict:start_link(), eradius_dict:unload_tables([dictionary_3gpp]), diff --git a/test/eradius_logtest.erl b/test/eradius_logtest.erl index a093e27b..aa410aad 100644 --- a/test/eradius_logtest.erl +++ b/test/eradius_logtest.erl @@ -1,22 +1,22 @@ -% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> - -% Permission is hereby granted, free of charge, to any person obtaining a -% copy of this software and associated documentation files (the "Software"), -% to deal in the Software without restriction, including without limitation -% the rights to use, copy, modify, merge, publish, distribute, sublicense, -% and/or sell copies of the Software, and to permit persons to whom the -% Software is furnished to do so, subject to the following conditions: - -% The above copyright notice and this permission notice shall be included in -% all copies or substantial portions of the Software. - -% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -% DEALINGS IN THE SOFTWARE. +%% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> + +%% Permission is hereby granted, free of charge, to any person obtaining a +%% copy of this software and associated documentation files (the "Software"), +%% to deal in the Software without restriction, including without limitation +%% the rights to use, copy, modify, merge, publish, distribute, sublicense, +%% and/or sell copies of the Software, and to permit persons to whom the +%% Software is furnished to do so, subject to the following conditions: + +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. + +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +%% DEALINGS IN THE SOFTWARE. -module(eradius_logtest). @@ -53,14 +53,14 @@ start() -> ]}, {session_nodes, [node()]}, {root, [ - { {eradius_logtest, "root", [] }, [{"127.0.0.1/24", ?SECRET, [{nas_id, <<"Test_Nas_Id">>}]}] } - ]}, + { {eradius_logtest, "root", [] }, [{"127.0.0.1/24", ?SECRET, [{nas_id, <<"Test_Nas_Id">>}]}] } + ]}, {test, [ - { {eradius_logtest, "test", [] }, [{eradius_test_handler:localhost(ip), ?SECRET3, [{nas_id, <<"Test_Nas_Id_test">>}]}] } - ]}, + { {eradius_logtest, "test", [] }, [{eradius_test_handler:localhost(ip), ?SECRET3, [{nas_id, <<"Test_Nas_Id_test">>}]}] } + ]}, {proxy, [ { {eradius_proxy, "proxy", ProxyConfig }, [{eradius_test_handler:localhost(ip), ?SECRET2, [{nas_id, <<"Test_Nas_proxy">>}]}] } - ]} + ]} ], [application:set_env(eradius, Key, Value) || {Key, Value} <- Config], {ok, _} = application:ensure_all_started(eradius), @@ -92,7 +92,7 @@ radius_request(#radius_request{cmd = accreq}, _NasProp, _) -> validate_arguments(_Args) -> true. test_client() -> - test_client(request). + test_client(request). test_client(Command) -> eradius_dict:load_tables([dictionary, dictionary_3gpp]), @@ -100,7 +100,7 @@ test_client(Command) -> send_request(eradius_test_handler:localhost(tuple), 1813, ?SECRET, Request). test_proxy() -> - test_proxy(request). + test_proxy(request). test_proxy(Command) -> eradius_dict:load_tables([dictionary, dictionary_3gpp]), diff --git a/test/eradius_metrics_SUITE.erl b/test/eradius_metrics_SUITE.erl index 11e2f2c3..60cc0fbb 100644 --- a/test/eradius_metrics_SUITE.erl +++ b/test/eradius_metrics_SUITE.erl @@ -1,22 +1,22 @@ -% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> - -% Permission is hereby granted, free of charge, to any person obtaining a -% copy of this software and associated documentation files (the "Software"), -% to deal in the Software without restriction, including without limitation -% the rights to use, copy, modify, merge, publish, distribute, sublicense, -% and/or sell copies of the Software, and to permit persons to whom the -% Software is furnished to do so, subject to the following conditions: - -% The above copyright notice and this permission notice shall be included in -% all copies or substantial portions of the Software. - -% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -% DEALINGS IN THE SOFTWARE. +%% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> + +%% Permission is hereby granted, free of charge, to any person obtaining a +%% copy of this software and associated documentation files (the "Software"), +%% to deal in the Software without restriction, including without limitation +%% the rights to use, copy, modify, merge, publish, distribute, sublicense, +%% and/or sell copies of the Software, and to permit persons to whom the +%% Software is furnished to do so, subject to the following conditions: + +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. + +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +%% DEALINGS IN THE SOFTWARE. -module(eradius_metrics_SUITE). -compile(export_all). @@ -53,8 +53,8 @@ init_per_suite(Config) -> { {"good", [] }, [{"127.0.0.2", ?SECRET, [{nas_id, <<"good_nas">>}]}] } ]}, {bad, [ - { {"bad", [] }, [{"127.0.0.2", ?SECRET, [{nas_id, <<"bad_nas">>}]}] } - ]}, + { {"bad", [] }, [{"127.0.0.2", ?SECRET, [{nas_id, <<"bad_nas">>}]}] } + ]}, {error, [ { {"error", [] }, [{"127.0.0.2", ?SECRET, [{nas_id, <<"error_nas">>}]}] } ]}, @@ -66,8 +66,8 @@ init_per_suite(Config) -> ], [application:set_env(eradius, Key, Value) || {Key, Value} <- EradiusConfig], application:set_env(prometheus, collectors, [eradius_prometheus_collector]), - % prometheus is not included directly to eradius but prometheus_eradius_collector - % should include it + %% prometheus is not included directly to eradius but prometheus_eradius_collector + %% should include it application:ensure_all_started(prometheus), {ok, _} = application:ensure_all_started(eradius), spawn(fun() -> @@ -92,7 +92,7 @@ good_requests(_Config) -> {coareq, coa, coa_ack}, {discreq, disconnect, disconnect_ack}], [check_single_request(good, EradiusRequestType, RequestType, ResponseType) || - {EradiusRequestType, RequestType, ResponseType} <- Requests ], + {EradiusRequestType, RequestType, ResponseType} <- Requests ], check_total_requests(good, length(Requests)). bad_requests(_Config) -> @@ -100,7 +100,7 @@ bad_requests(_Config) -> {coareq, coa, coa_nak}, {discreq, disconnect, disconnect_nak}], [check_single_request(bad, EradiusRequestType, RequestType, ResponseType) || - {EradiusRequestType, RequestType, ResponseType} <- Requests ], + {EradiusRequestType, RequestType, ResponseType} <- Requests ], check_total_requests(bad, length(Requests)). error_requests(_Config) -> diff --git a/test/eradius_proxy_SUITE.erl b/test/eradius_proxy_SUITE.erl index f2f348b4..b76ee2f1 100644 --- a/test/eradius_proxy_SUITE.erl +++ b/test/eradius_proxy_SUITE.erl @@ -1,22 +1,22 @@ -% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> +%% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> -% Permission is hereby granted, free of charge, to any person obtaining a -% copy of this software and associated documentation files (the "Software"), -% to deal in the Software without restriction, including without limitation -% the rights to use, copy, modify, merge, publish, distribute, sublicense, -% and/or sell copies of the Software, and to permit persons to whom the -% Software is furnished to do so, subject to the following conditions: +%% Permission is hereby granted, free of charge, to any person obtaining a +%% copy of this software and associated documentation files (the "Software"), +%% to deal in the Software without restriction, including without limitation +%% the rights to use, copy, modify, merge, publish, distribute, sublicense, +%% and/or sell copies of the Software, and to permit persons to whom the +%% Software is furnished to do so, subject to the following conditions: -% The above copyright notice and this permission notice shall be included in -% all copies or substantial portions of the Software. +%% The above copyright notice and this permission notice shall be included in +%% all copies or substantial portions of the Software. -% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -% DEALINGS IN THE SOFTWARE. +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +%% DEALINGS IN THE SOFTWARE. -module(eradius_proxy_SUITE). -compile(export_all). @@ -25,13 +25,13 @@ -include("eradius_test.hrl"). all() -> [ - resolve_routes_test, - validate_arguments_test, - validate_options_test, - new_request_test, - get_key_test, - strip_test - ]. + resolve_routes_test, + validate_arguments_test, + validate_options_test, + new_request_test, + get_key_test, + strip_test + ]. resolve_routes_test(_) -> DefaultRoute = {eradius_test_handler:localhost(tuple), 1813, <<"secret">>}, @@ -42,12 +42,12 @@ resolve_routes_test(_) -> {ok, R2} = re:compile("test"), {ok, R3} = re:compile("^dev_.*"), Routes = [{R1, Prod}, {R2, Test, [{pool, test_pool}]}, {R3, Dev}], - % default + %% default ?equal({undefined, DefaultRoute}, eradius_proxy:resolve_routes(undefined, DefaultRoute, Routes,[])), ?equal({"user", DefaultRoute}, eradius_proxy:resolve_routes(<<"user">>, DefaultRoute, Routes, [])), ?equal({"user@prod", Prod}, eradius_proxy:resolve_routes(<<"user@prod">>, DefaultRoute, Routes,[])), ?equal({"user@test", {Test, [{pool, test_pool}]}}, eradius_proxy:resolve_routes(<<"user@test">>, DefaultRoute, Routes,[])), - % strip + %% strip Opts = [{strip, true}], ?equal({"user", DefaultRoute}, eradius_proxy:resolve_routes(<<"user">>, DefaultRoute, Routes, Opts)), ?equal({"user", Prod}, eradius_proxy:resolve_routes(<<"user@prod">>, DefaultRoute, Routes, Opts)), @@ -55,11 +55,11 @@ resolve_routes_test(_) -> ?equal({"user", Dev}, eradius_proxy:resolve_routes(<<"user@dev_server">>, DefaultRoute, Routes, Opts)), ?equal({"user", DefaultRoute}, eradius_proxy:resolve_routes(<<"user@dev-server">>, DefaultRoute, Routes, Opts)), - % prefix + %% prefix Opts1 = [{type, prefix}, {separator, "/"}], ?equal({"user/example", DefaultRoute}, eradius_proxy:resolve_routes(<<"user/example">>, DefaultRoute, Routes, Opts1)), ?equal({"test/user", {Test, [{pool, test_pool}]}}, eradius_proxy:resolve_routes(<<"test/user">>, DefaultRoute, Routes, Opts1)), - % prefix and strip + %% prefix and strip Opts2 = Opts ++ Opts1, ?equal({"example", DefaultRoute}, eradius_proxy:resolve_routes(<<"user/example">>, DefaultRoute, Routes, Opts2)), ?equal({"user", {Test, [{pool, test_pool}]}}, eradius_proxy:resolve_routes(<<"test/user">>, DefaultRoute, Routes, Opts2)), @@ -73,15 +73,15 @@ validate_arguments_test(_) -> ]} ], GoodOldConfig = [{default_route, {eradius_test_handler:localhost(tuple), 1813, <<"secret">>}, test_pool}, - {options, [{type, realm}, {strip, true}, {separator, "@"}]}, - {routes, [{"test_1", {eradius_test_handler:localhost(tuple), 1815, <<"secret1">>}, [{pool, test_pool}]}, - {"test_2", {<<"localhost">>, 1816, <<"secret2">>}} - ]} - ], + {options, [{type, realm}, {strip, true}, {separator, "@"}]}, + {routes, [{"test_1", {eradius_test_handler:localhost(tuple), 1815, <<"secret1">>}, [{pool, test_pool}]}, + {"test_2", {<<"localhost">>, 1816, <<"secret2">>}} + ]} + ], BadConfig = [{default_route, {eradius_test_handler:localhost(tuple), 1813, <<"secret">>}}, {options, [{type, abc}]} - ], + ], BadConfig1 = [{default_route, {eradius_test_handler:localhost(tuple), 0, <<"secret">>}}], BadConfig2 = [{default_route, {abc, 123, <<"secret">>}}], BadConfig3 = [{default_route, {eradius_test_handler:localhost(tuple), 1813, <<"secret">>}}, diff --git a/test/eradius_test.hrl b/test/eradius_test.hrl index 5e03be47..4e9727d8 100644 --- a/test/eradius_test.hrl +++ b/test/eradius_test.hrl @@ -10,9 +10,9 @@ end)())). -define(equal(Expected, Actual), - (fun (Expected@@@, Expected@@@) -> true; - (Expected@@@, Actual@@@) -> - ct:pal("MISMATCH(~s:~b, ~s)~nExpected: ~p~nActual: ~p~n", - [?FILE, ?LINE, ??Actual, Expected@@@, Actual@@@]), - false - end)(Expected, Actual) orelse error(badmatch)). + (fun (Expected@@@, Expected@@@) -> true; + (Expected@@@, Actual@@@) -> + ct:pal("MISMATCH(~s:~b, ~s)~nExpected: ~p~nActual: ~p~n", + [?FILE, ?LINE, ??Actual, Expected@@@, Actual@@@]), + false + end)(Expected, Actual) orelse error(badmatch)). diff --git a/test/eradius_test_handler.erl b/test/eradius_test_handler.erl index e370af8c..20a8c3db 100644 --- a/test/eradius_test_handler.erl +++ b/test/eradius_test_handler.erl @@ -19,7 +19,7 @@ start() -> {two, {localhost(ip), [1813]}}]), application:set_env(eradius, unreachable_timeout, 2), application:set_env(eradius, servers_pool, [{test_pool, [{localhost(tuple), 1812, "secret"}, - % fake upstream server for fail-over + %% fake upstream server for fail-over {localhost(string), 1820, "secret"}]}]), application:ensure_all_started(eradius), eradius:modules_ready([?MODULE]). @@ -52,8 +52,8 @@ radius_request(#radius_request{cmd = request}, _Nasprop, _Args) -> %% triggers the load balancing in eradius_client. localhost(string) -> case os:getenv("TRAVIS") of - false -> "localhost"; - _ -> "ip4-loopback" + false -> "localhost"; + _ -> "ip4-loopback" end; localhost(binary) -> list_to_binary(localhost(string)); From 9329ab9d325654369601f2327e30aeee4f825745 Mon Sep 17 00:00:00 2001 From: Andreas Schultz <andreas.schultz@travelping.com> Date: Mon, 15 Apr 2024 18:54:52 +0200 Subject: [PATCH 2/8] [client] remove unused message from init --- src/eradius_client_socket.erl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/eradius_client_socket.erl b/src/eradius_client_socket.erl index f53a8f3f..4e16ef38 100644 --- a/src/eradius_client_socket.erl +++ b/src/eradius_client_socket.erl @@ -11,7 +11,6 @@ start(SocketIP, Client, PortIdx) -> gen_server:start_link(?MODULE, [SocketIP, Client, PortIdx], []). init([SocketIP, Client, PortIdx]) -> - Client ! {PortIdx, self()}, case SocketIP of undefined -> ExtraOptions = []; From d5323854bda9cbda720407f8674cd9fde609d396 Mon Sep 17 00:00:00 2001 From: Andreas Schultz <andreas.schultz@travelping.com> Date: Tue, 16 Apr 2024 09:58:59 +0200 Subject: [PATCH 3/8] [client] rewrite socket handler logic to be OTP like --- src/eradius_client.erl | 106 ++++++++++++--------- src/eradius_client_socket.erl | 142 ++++++++++++++++++++-------- src/eradius_client_sup.erl | 50 +++++++++- test/eradius_client_SUITE.erl | 90 ++++++------------ test/eradius_client_socket_test.erl | 66 ------------- 5 files changed, 234 insertions(+), 220 deletions(-) delete mode 100644 test/eradius_client_socket_test.erl diff --git a/src/eradius_client.erl b/src/eradius_client.erl index 906b8367..b4212ba0 100644 --- a/src/eradius_client.erl +++ b/src/eradius_client.erl @@ -11,7 +11,9 @@ %% parameter. Changing it currently requires a restart. It can be given as a string or ip address tuple, %% or the atom ``undefined'' (the default), which uses whatever address the OS selects. -module(eradius_client). + -export([start_link/0, send_request/2, send_request/3, send_remote_request/3, send_remote_request/4]). + %% internal -export([reconfigure/0, send_remote_request_loop/8, find_suitable_peer/1, restore_upstream_server/1, store_radius_server_from_pool/3, @@ -22,6 +24,10 @@ -import(eradius_lib, [printable_peer/2]). +-ifdef(TEST). +-export([get_state/0]). +-endif. + -include_lib("stdlib/include/ms_transform.hrl"). -include_lib("kernel/include/logger.hrl"). -include("eradius_dict.hrl"). @@ -230,37 +236,48 @@ handle_failed_request(Request, {ServerIP, Port} = _FailedServer, UpstreamServers end. %% @private +%% send_remote_request_loop/8 send_remote_request_loop(ReplyPid, Socket, ReqId, Peer, EncRequest, Retries, Timeout, MetricsInfo) -> ReplyPid ! {self(), send_request_loop(Socket, ReqId, Peer, EncRequest, Retries, Timeout, MetricsInfo)}. -send_request_loop(Socket, ReqId, Peer, Request = #radius_request{}, Retries, Timeout, undefined) -> +%% send_remote_request_loop/7 +send_request_loop(Socket, ReqId, Peer, Request = #radius_request{}, + Retries, Timeout, undefined) -> send_request_loop(Socket, ReqId, Peer, Request, Retries, Timeout, eradius_lib:make_addr_info(Peer)); -send_request_loop(Socket, ReqId, Peer, Request, Retries, Timeout, MetricsInfo) -> +send_request_loop(Socket, ReqId, Peer, Request, + Retries, Timeout, MetricsInfo) -> {Authenticator, EncRequest} = eradius_lib:encode_request(Request), - SMon = erlang:monitor(process, Socket), - send_request_loop(Socket, SMon, Peer, ReqId, Authenticator, EncRequest, Timeout, Retries, MetricsInfo, Request#radius_request.secret, Request). + send_request_loop(Socket, Peer, ReqId, Authenticator, EncRequest, + Timeout, Retries, MetricsInfo, Request#radius_request.secret, Request). -send_request_loop(_Socket, SMon, _Peer, _ReqId, _Authenticator, _EncRequest, Timeout, 0, MetricsInfo, _Secret, Request) -> +%% send_remote_request_loop/10 +send_request_loop(_Socket, _Peer, _ReqId, _Authenticator, _EncRequest, + Timeout, 0, MetricsInfo, _Secret, Request) -> TS = erlang:convert_time_unit(Timeout, millisecond, native), update_client_request(timeout, MetricsInfo, TS, Request), - erlang:demonitor(SMon, [flush]), {error, timeout}; -send_request_loop(Socket, SMon, Peer = {_ServerName, {IP, Port}}, ReqId, Authenticator, EncRequest, Timeout, RetryN, MetricsInfo, Secret, Request) -> - Socket ! {self(), send_request, {IP, Port}, ReqId, EncRequest}, - update_client_request(pending, MetricsInfo, 1, Request), - receive - {Socket, response, ReqId, Response} -> - update_client_request(pending, MetricsInfo, -1, Request), +send_request_loop(Socket, Peer = {_ServerName, {IP, Port}}, ReqId, Authenticator, EncRequest, + Timeout, RetryN, MetricsInfo, Secret, Request) -> + Result = + try + update_client_request(pending, MetricsInfo, 1, Request), + eradius_client_socket:send_request(Socket, {IP, Port}, ReqId, EncRequest, Timeout) + after + update_client_request(pending, MetricsInfo, -1, Request) + end, + + case Result of + {response, ReqId, Response} -> {ok, Response, Secret, Authenticator}; - {'DOWN', SMon, process, Socket, _} -> + {error, close} -> {error, socket_down}; - {Socket, error, Error} -> - {error, Error} - after - Timeout -> + {error, timeout} -> TS = erlang:convert_time_unit(Timeout, millisecond, native), update_client_request(retransmission, MetricsInfo, TS, Request), - send_request_loop(Socket, SMon, Peer, ReqId, Authenticator, EncRequest, Timeout, RetryN - 1, MetricsInfo, Secret, Request) + send_request_loop(Socket, Peer, ReqId, Authenticator, EncRequest, + Timeout, RetryN - 1, MetricsInfo, Secret, Request); + {error, _} = Error -> + Error end. %% @private @@ -329,7 +346,7 @@ reconfigure() -> %% @private init([]) -> - {ok, Sup} = eradius_client_sup:start(), + {ok, Sup} = eradius_client_sup:start_link(), case configure(#state{socket_ip = null, sup = Sup}) of {error, Error} -> {stop, Error}; Else -> Else @@ -355,10 +372,6 @@ handle_call(reconfigure, _From, State) -> {ok, NState} -> {reply, ok, NState} end; -%% @private -handle_call(debug, _From, State) -> - {reply, {ok, State}, State}; - %% @private handle_call(_OtherCall, _From, State) -> {noreply, State}. @@ -402,6 +415,16 @@ configure(State) -> {error, {bad_client_ip, ClientIP}} end. +-ifdef(TEST). + +get_state() -> + State = sys:get_state(?SERVER), + Keys = record_info(fields, state), + Values = tl(tuple_to_list(State)), + maps:from_list(lists:zip(Keys, Values)). + +-endif. + %% private prepare_pools() -> ets:new(?MODULE, [ordered_set, public, named_table, {keypos, 1}, {write_concurrency,true}]), @@ -456,14 +479,14 @@ configure_address(State = #state{socket_ip = OAdd, sockets = Sockts}, NPorts, NA {ok, State#state{socket_ip = NAdd, no_ports = NPorts}}; NAdd -> configure_ports(State, NPorts); - _ -> + _ -> ?LOG(info, "Reopening RADIUS client sockets (client_ip changed to ~s)", [inet:ntoa(NAdd)]), - array:map( fun(_PortIdx, Pid) -> - case Pid of - undefined -> done; - _ -> Pid ! close - end - end, Sockts), + array:map( + fun(_PortIdx, undefined) -> + ok; + (_PortIdx, Socket) -> + eradius_client_socket:close(Socket) + end, Sockts), {ok, State#state{sockets = array:new(), socket_ip = NAdd, no_ports = NPorts}} end. @@ -490,11 +513,8 @@ close_sockets(NPorts, Sockets) -> List = array:to_list(Sockets), {_, Rest} = lists:split(NPorts, List), lists:map( - fun(Pid) -> - case Pid of - undefined -> done; - _ -> Pid ! close - end + fun(undefined) -> ok; + (Socket) -> eradius_client_socket:close(Socket) end, Rest), array:resize(NPorts, Sockets) end. @@ -516,18 +536,10 @@ next_port_and_req_id(Peer, NumberOfPorts, Counters) -> find_socket_process(PortIdx, Sockets, SocketIP, Sup) -> case array:get(PortIdx, Sockets) of undefined -> - Res = supervisor:start_child(Sup, {PortIdx, - {eradius_client_socket, start, [SocketIP, self(), PortIdx]}, - transient, brutal_kill, worker, [eradius_client_socket]}), - Pid = case Res of - {ok, P} -> P; - {error, already_present} -> - {ok, P} = supervisor:restart_child(Sup, PortIdx), - P - end, - {Pid, array:set(PortIdx, Pid, Sockets)}; - Pid when is_pid(Pid) -> - {Pid, Sockets} + {ok, Socket} = eradius_client_socket:new(Sup, SocketIP), + {Socket, array:set(PortIdx, Socket, Sockets)}; + Socket -> + {Socket, Sockets} end. update_socket_process(PortIdx, Sockets, Pid) -> diff --git a/src/eradius_client_socket.erl b/src/eradius_client_socket.erl index 4e16ef38..7472853e 100644 --- a/src/eradius_client_socket.erl +++ b/src/eradius_client_socket.erl @@ -1,76 +1,125 @@ +%% Copyright (c) 2002-2007, Martin Björklund and Torbjörn Törnkvist +%% Copyright (c) 2011, Travelping GmbH <info@travelping.com> +%% +%% SPDX-License-Identifier: MIT +%% -module(eradius_client_socket). -behaviour(gen_server). --export([start/3]). +%% API +-export([new/2, start_link/1, send_request/5, close/1]). + +%% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --record(state, {client, socket, pending, mode, counter}). +-record(state, {socket, active_n, pending, mode, counter}). + +%%%========================================================================= +%%% API +%%%========================================================================= + +new(Sup, SocketIP) -> + eradius_client_sup:new(Sup, SocketIP). + +start_link(SocketIP) -> + gen_server:start_link(?MODULE, [SocketIP], []). -start(SocketIP, Client, PortIdx) -> - gen_server:start_link(?MODULE, [SocketIP, Client, PortIdx], []). +send_request(Socket, Peer, ReqId, Request, Timeout) -> + try + gen_server:call(Socket, {send_request, Peer, ReqId, Request}, Timeout) + catch + exit:{timeout, _} -> + {error, timeout}; + exit:{noproc, _} -> + {error, closed}; + {nodedown, _} -> + {error, closed} + end. -init([SocketIP, Client, PortIdx]) -> +close(Socket) -> + gen_server:cast(Socket, close). + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +init([SocketIP]) -> case SocketIP of undefined -> ExtraOptions = []; SocketIP when is_tuple(SocketIP) -> ExtraOptions = [{ip, SocketIP}] end, + ActiveN = application:get_env(eradius, active_n, 100), RecBuf = application:get_env(eradius, recbuf, 8192), SndBuf = application:get_env(eradius, sndbuf, 131072), - {ok, Socket} = gen_udp:open(0, [{active, once}, binary, {recbuf, RecBuf}, {sndbuf, SndBuf} | ExtraOptions]), - {ok, #state{client = Client, socket = Socket, pending = maps:new(), mode = active, counter = 0}}. + Opts = [{active, ActiveN}, binary, {recbuf, RecBuf}, {sndbuf, SndBuf} | ExtraOptions], + {ok, Socket} = gen_udp:open(0, Opts), + + State = #state{ + socket = Socket, + active_n = ActiveN, + pending = #{}, + mode = active + }, + {ok, State}. + +handle_call({send_request, {IP, Port}, ReqId, Request}, From, + #state{socket = Socket, pending = Pending} = State) -> + case gen_udp:send(Socket, IP, Port, Request) of + ok -> + ReqKey = {IP, Port, ReqId}, + NPending = Pending#{ReqKey => From}, + {noreply, State#state{pending = NPending}}; + {error, Reason} -> + {reply, {error, Reason}, State} + end; handle_call(_Request, _From, State) -> {noreply, State}. +handle_cast(close, #state{pending = Pending} = State) + when map_size(Pending) =:= 0 -> + {stop, normal, State}; +handle_cast(close, State) -> + {noreply, State#state{mode = inactive}}; + handle_cast(_Msg, State) -> {noreply, State}. -handle_info({SenderPid, send_request, {IP, Port}, ReqId, EncRequest}, - State = #state{socket = Socket, pending = Pending, counter = Counter}) -> - case gen_udp:send(Socket, IP, Port, EncRequest) of - ok -> - ReqKey = {IP, Port, ReqId}, - NPending = maps:put(ReqKey, SenderPid, Pending), - {noreply, State#state{pending = NPending, counter = Counter+1}}; - {error, Reason} -> - SenderPid ! {error, Reason}, - {noreply, State} - end; +handle_info({udp_passive, _Socket}, #state{socket = Socket, active_n = ActiveN} = State) -> + inet:setopts(Socket, [{active, ActiveN}]), + {noreply, State}; -handle_info({udp, Socket, FromIP, FromPort, EncRequest}, - State = #state{socket = Socket, pending = Pending, mode = Mode, counter = Counter}) -> - case eradius_lib:decode_request_id(EncRequest) of - {ReqId, EncRequest} -> - case maps:find({FromIP, FromPort, ReqId}, Pending) of - error -> - %% discard reply because we didn't expect it - inet:setopts(Socket, [{active, once}]), - {noreply, State}; - {ok, WaitingSender} -> - WaitingSender ! {self(), response, ReqId, EncRequest}, - inet:setopts(Socket, [{active, once}]), +handle_info({udp, Socket, FromIP, FromPort, Request}, + State = #state{socket = Socket, pending = Pending, mode = Mode}) -> + case eradius_lib:decode_request_id(Request) of + {ReqId, Request} -> + case Pending of + #{{FromIP, FromPort, ReqId} := From} -> + gen_server:reply(From, {response, ReqId, Request}), + + flow_control(State), NPending = maps:remove({FromIP, FromPort, ReqId}, Pending), - NState = State#state{pending = NPending, counter = Counter-1}, - case {Mode, Counter-1} of - {inactive, 0} -> {stop, normal, NState}; - _ -> {noreply, NState} - end + NState = State#state{pending = NPending}, + case Mode of + inactive when map_size(NPending) =:= 0 -> + {stop, normal, NState}; + _ -> + {noreply, NState} + end; + _ -> + %% discard reply because we didn't expect it + flow_control(State), + {noreply, State} end; {bad_pdu, _} -> %% discard reply because it was malformed - inet:setopts(Socket, [{active, once}]), + flow_control(State), {noreply, State} end; -handle_info(close, State = #state{counter = Counter}) -> - case Counter of - 0 -> {stop, normal, State}; - _ -> {noreply, State#state{mode = inactive}} - end; - handle_info(_Info, State) -> {noreply, State}. @@ -79,3 +128,12 @@ terminate(_Reason, _State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. + +%%%========================================================================= +%%% internal functions +%%%========================================================================= + +flow_control(#state{socket = Socket, active_n = once}) -> + inet:setopts(Socket, [{active, once}]); +flow_control(_) -> + ok. diff --git a/src/eradius_client_sup.erl b/src/eradius_client_sup.erl index cd6b8339..7c40b2cd 100644 --- a/src/eradius_client_sup.erl +++ b/src/eradius_client_sup.erl @@ -1,12 +1,52 @@ - -module(eradius_client_sup). -behaviour(supervisor). --export([start/0, init/1]). +%% API +-export([start_link/0, new/2]). + +%% Supervisor callbacks +-export([init/1]). + +-define(SERVER, ?MODULE). + +%%%=================================================================== +%%% API functions +%%%=================================================================== + +-spec start_link() -> {ok, Pid :: pid()} | + {error, {already_started, Pid :: pid()}} | + {error, {shutdown, term()}} | + {error, term()} | + ignore. +start_link() -> + supervisor:start_link(?MODULE, []). -start() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). +new(Sup, SocketId) -> + supervisor:start_child(Sup, [SocketId]). +%%%=================================================================== +%%% Supervisor callbacks +%%%=================================================================== + +-spec init(Args :: term()) -> + {ok, {SupFlags :: supervisor:sup_flags(), + [ChildSpec :: supervisor:child_spec()]}} | + ignore. init([]) -> - {ok, {{one_for_one, 5, 10}, []}}. + SupFlags = #{strategy => simple_one_for_one, + intensity => 5, + period => 10}, + + Child = #{id => eradius_client_socket, + start => {eradius_client_socket, start_link, []}, + restart => transient, + shutdown => 5000, + type => worker, + modules => [eradius_client_socket]}, + + {ok, {SupFlags, [Child]}}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/test/eradius_client_SUITE.erl b/test/eradius_client_SUITE.erl index 9dd2265b..bdf972e6 100644 --- a/test/eradius_client_SUITE.erl +++ b/test/eradius_client_SUITE.erl @@ -23,6 +23,8 @@ -include("test/eradius_test.hrl"). +-define(HUT_SOCKET, eradius_client_socket). + -define(BAD_SERVER_IP, {eradius_test_handler:localhost(ip), 1820, "secret"}). -define(BAD_SERVER_INITIAL_RETRIES, 3). -define(BAD_SERVER_TUPLE_INITIAL, {{eradius_test_handler:localhost(tuple), 1820}, @@ -45,6 +47,7 @@ ?BAD_SERVER_TUPLE_INITIAL, ?GOOD_SERVER_2_TUPLE]). +-spec all() -> [ct_suite:ct_test_def(), ...]. all() -> [ send_request, wanna_send, @@ -60,11 +63,10 @@ all() -> [ init_per_suite(Config) -> {ok, _} = application:ensure_all_started(eradius), - startSocketCounter(), + logger:set_primary_config(level, debug), Config. end_per_suite(_Config) -> - stopSocketCounter(), application:stop(eradius), ok. @@ -97,48 +99,15 @@ end_per_testcase(_Test, Config) -> %% STUFF -socketCounter(Count) -> - receive - add -> socketCounter(Count+1); - del -> socketCounter(Count-1); - {get, PID} -> PID ! {ok, Count}, socketCounter(Count); - stop -> done - end. - -startSocketCounter() -> - register(socketCounter, spawn(?MODULE, socketCounter, [0])). - -stopSocketCounter() -> - socketCounter ! stop, - unregister(socketCounter). - -addSocket() -> socketCounter ! add. -delSocket() -> socketCounter ! del. - getSocketCount() -> - socketCounter ! {get, self()}, - receive - {ok, Count} -> Count - end. + #{sup := Sup} = eradius_client:get_state(), + Counts = supervisor:count_children(Sup), + proplists:get_value(active, Counts). -testSocket(undefined) -> true; +testSocket(undefined) -> + true; testSocket(Pid) -> - Pid ! {status, self()}, - receive - {ok, active} -> false; - {ok, inactive} -> true - after - 50 -> true - end. - --record(state, { - socket_ip :: inet:ip_address(), - no_ports = 1 :: pos_integer(), - idcounters = maps:new() :: map(), - sockets = array:new() :: array:array(), - sup :: pid(), - subscribed_clients = [] :: [{{integer(),integer(),integer(),integer()}, integer()}] - }). + not is_process_alive(Pid). split(N, List) -> split2(N, [], List). @@ -147,14 +116,17 @@ split2(_, List1, []) -> {lists:reverse(List1), []}; split2(N, List1, [L|List2]) -> split2(N-1, [L|List1], List2). meckStart() -> - ok = meck:new(eradius_client_socket), - ok = meck:expect(eradius_client_socket, start, fun(X, Y, Z) -> eradius_client_socket_test:start(X, Y, Z) end), - ok = meck:expect(eradius_client_socket, init, fun(X) -> eradius_client_socket_test:init(X) end), - ok = meck:expect(eradius_client_socket, handle_call, fun(X, Y, Z) -> eradius_client_socket_test:handle_call(X, Y, Z) end), - ok = meck:expect(eradius_client_socket, handle_cast, fun(X, Y) -> eradius_client_socket_test:handle_cast(X, Y) end), - ok = meck:expect(eradius_client_socket, handle_info, fun(X, Y) -> eradius_client_socket_test:handle_info(X, Y) end), - ok = meck:expect(eradius_client_socket, terminate, fun(X, Y) -> eradius_client_socket_test:terminate(X, Y) end), - ok = meck:expect(eradius_client_socket, code_change, fun(X, Y, Z) -> eradius_client_socket_test:code_change(X, Y, Z) end). + ok = meck:new(eradius_client_socket, [passthrough]), + ok = meck:expect(eradius_client_socket, init, + fun(_) -> {ok, undefined} end), + ok = meck:expect(eradius_client_socket, handle_call, + fun(_Request, _From, State) -> {noreply, State} end), + ok = meck:expect(eradius_client_socket, handle_cast, + fun(close, State) -> {stop, normal, State}; + (_Request, State) -> {noreply, State} end), + ok = meck:expect(eradius_client_socket, handle_info, + fun(_Info, State) -> {noreply, State} end), + ok. meckStop() -> ok = meck:unload(eradius_client_socket). @@ -172,13 +144,13 @@ parse_ip(T = {_, _, _, _, _, _}) -> test(true, _Msg) -> true; test(false, Msg) -> - io:format(standard_error, "~s~n", [Msg]), + ct:pal("~s", [Msg]), false. -check(OldState, NewState = #state{no_ports = P}, null, A) -> check(OldState, NewState, P, A); -check(OldState, NewState = #state{socket_ip = A}, P, null) -> check(OldState, NewState, P, A); -check(#state{sockets = OS, no_ports = _OP, idcounters = _OC, socket_ip = OA}, - #state{sockets = NS, no_ports = NP, idcounters = NC, socket_ip = NA}, +check(OldState, NewState = #{no_ports := P}, null, A) -> check(OldState, NewState, P, A); +check(OldState, NewState = #{socket_ip := A}, P, null) -> check(OldState, NewState, P, A); +check(#{sockets := OS, no_ports := _OP, idcounters := _OC, socket_ip := OA}, + #{sockets := NS, no_ports := NP, idcounters := NC, socket_ip := NA}, P, A) -> {ok, PA} = parse_ip(A), test(PA == NA, "Adress not configured") and @@ -209,9 +181,9 @@ send_request(_Config) -> send(FUN, Ports, Address) -> meckStart(), - {ok, OldState} = gen_server:call(eradius_client, debug), + OldState = eradius_client:get_state(), FUN(), - {ok, NewState} = gen_server:call(eradius_client, debug), + NewState = eradius_client:get_state(), true = check(OldState, NewState, Ports, Address), meckStop(). @@ -224,11 +196,9 @@ wanna_send(_Config) -> send(FUN, null, null) end, lists:seq(1, 10)). -%% I've catched some data races with `delSocket()' and `getSocketCount()' when -%% `delSocket()' happens after `getSocketCount()' (because `delSocket()' is sent from another process). -%% I don't know a better decision than add some delay before `getSocketCount()' +%% socket shutdown is done asynchronous, the tests need to wait a bit for it to finish. reconf_address(_Config) -> - FUN = fun() -> gen_server:call(eradius_client, reconfigure), timer:sleep(100) end, + FUN = fun() -> eradius_client:reconfigure(), timer:sleep(100) end, application:set_env(eradius, client_ip, "7.13.23.42"), send(FUN, null, "7.13.23.42"). diff --git a/test/eradius_client_socket_test.erl b/test/eradius_client_socket_test.erl deleted file mode 100644 index c2a3bf12..00000000 --- a/test/eradius_client_socket_test.erl +++ /dev/null @@ -1,66 +0,0 @@ -%% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> - -%% Permission is hereby granted, free of charge, to any person obtaining a -%% copy of this software and associated documentation files (the "Software"), -%% to deal in the Software without restriction, including without limitation -%% the rights to use, copy, modify, merge, publish, distribute, sublicense, -%% and/or sell copies of the Software, and to permit persons to whom the -%% Software is furnished to do so, subject to the following conditions: - -%% The above copyright notice and this permission notice shall be included in -%% all copies or substantial portions of the Software. - -%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -%% DEALINGS IN THE SOFTWARE. - --module(eradius_client_socket_test). - --behaviour(gen_server). - --export([start/3]). --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). - --record(state, {client, socket, pending, mode, counter}). - -start(SocketIP, Client, PortIdx) -> - gen_server:start_link(?MODULE, [SocketIP, Client, PortIdx], []). - -init([_SocketIP, Client, PortIdx]) -> - Client ! {PortIdx, self()}, - eradius_client_SUITE:addSocket(), - {ok, #state{pending = maps:new(), mode = active, counter = 0}}. - -handle_call(_Request, _From, State) -> - {noreply, State}. - -handle_cast(_Msg, State) -> - {noreply, State}. - -handle_info({SenderPid, send_request, {IP, Port}, ReqId, _EncRequest}, - State = #state{pending = Pending, counter = Counter}) -> - ReqKey = {IP, Port, ReqId}, - NPending = Pending#{ReqKey => SenderPid}, - {noreply, State#state{pending = NPending, counter = Counter+1}}; - -handle_info(close, State) -> - %~ {noreply, State#state{mode = inactive}}; - {stop, normal, State}; - -handle_info({status, Pid}, State = #state{mode = Mode}) -> - Pid ! {ok, Mode}, - {noreply, State}; - -handle_info(_Info, State) -> - {noreply, State}. - -terminate(_Reason, _State) -> - eradius_client_SUITE:delSocket(). - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - From 1dcae285d898028cd0ee81fd35599bdcb0cafe46 Mon Sep 17 00:00:00 2001 From: Andreas Schultz <andreas.schultz@travelping.com> Date: Tue, 16 Apr 2024 15:07:20 +0200 Subject: [PATCH 4/8] [client] split socket manager from sending functionality Seperate concerns better. Socket managing is not in the mngr module, while the sending logic is in the client module. --- src/eradius.erl | 2 +- src/eradius_client.erl | 382 +++++----------------------- src/eradius_client_mngr.erl | 399 ++++++++++++++++++++++++++++++ src/eradius_client_socket.erl | 28 +-- src/eradius_client_socket_sup.erl | 52 ++++ src/eradius_client_sup.erl | 46 ++-- src/eradius_internal.hrl | 2 + src/eradius_proxy.erl | 10 +- src/eradius_server.erl | 18 +- src/eradius_sup.erl | 18 +- test/eradius_client_SUITE.erl | 28 +-- test/eradius_metrics_SUITE.erl | 2 +- 12 files changed, 603 insertions(+), 384 deletions(-) create mode 100644 src/eradius_client_mngr.erl create mode 100644 src/eradius_client_socket_sup.erl create mode 100644 src/eradius_internal.hrl diff --git a/src/eradius.erl b/src/eradius.erl index dbd1e473..7bc497e0 100644 --- a/src/eradius.erl +++ b/src/eradius.erl @@ -62,7 +62,7 @@ config_change(Added, Changed, Removed) -> Keys = [K || {K, _} <- Added ++ Changed] ++ Removed, (lists:member(logging, Keys) or lists:member(logfile, Keys)) andalso eradius_log:reconfigure(), - eradius_client:reconfigure(). + eradius_client_mngr:reconfigure(). do_config_change({tables, NewTables}) -> eradius_dict:load_tables(NewTables); diff --git a/src/eradius_client.erl b/src/eradius_client.erl index b4212ba0..4ab7a473 100644 --- a/src/eradius_client.erl +++ b/src/eradius_client.erl @@ -1,3 +1,8 @@ +%% Copyright (c) 2002-2007, Martin Björklund and Torbjörn Törnkvist +%% Copyright (c) 2011, Travelping GmbH <info@travelping.com> +%% +%% SPDX-License-Identifier: MIT +%% %% @doc This module contains a RADIUS client that can be used to send authentication and accounting requests. %% A counter is kept for every NAS in order to determine the next request id and sender port %% for each outgoing request. The implementation naively assumes that you won't send requests to a @@ -12,31 +17,22 @@ %% or the atom ``undefined'' (the default), which uses whatever address the OS selects. -module(eradius_client). --export([start_link/0, send_request/2, send_request/3, send_remote_request/3, send_remote_request/4]). - -%% internal --export([reconfigure/0, send_remote_request_loop/8, find_suitable_peer/1, - restore_upstream_server/1, store_radius_server_from_pool/3, - init_server_status_metrics/0]). +%% API +-export([send_request/2, send_request/3, + send_remote_request/3, send_remote_request/4]). --behaviour(gen_server). --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +%% internal API +-export([send_remote_request_loop/8]). -import(eradius_lib, [printable_peer/2]). --ifdef(TEST). --export([get_state/0]). --endif. - -include_lib("stdlib/include/ms_transform.hrl"). -include_lib("kernel/include/logger.hrl"). +-include_lib("kernel/include/inet.hrl"). -include("eradius_dict.hrl"). -include("eradius_lib.hrl"). +-include("eradius_internal.hrl"). --define(SERVER, ?MODULE). --define(DEFAULT_RETRIES, 3). --define(DEFAULT_TIMEOUT, 5000). --define(RECONFIGURE_TIMEOUT, 15000). -define(GOOD_CMD(Req), (Req#radius_request.cmd == 'request' orelse Req#radius_request.cmd == 'accreq' orelse Req#radius_request.cmd == 'coareq' orelse @@ -52,13 +48,11 @@ -export_type([nas_address/0, options/0]). --include_lib("kernel/include/inet.hrl"). +-define(SERVER, ?MODULE). -%% ------------------------------------------------------------------------------------------ -%% -- API -%% @private -start_link() -> - gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). +%%%========================================================================= +%%% API +%%%========================================================================= %% @equiv send_request(NAS, Request, []) -spec send_request(nas_address(), #radius_request{}) -> {ok, binary()} | {error, 'timeout' | 'socket_down'}. @@ -85,7 +79,7 @@ send_request({IP, Port, Secret}, Request, Options) when ?GOOD_CMD(Request) andal SendReqFn = fun () -> Peer = {ServerName, {IP, Port}}, update_client_requests(MetricsInfo), - {Socket, ReqId} = gen_server:call(?SERVER, {wanna_send, Peer, MetricsInfo}), + {Socket, ReqId} = eradius_client_mngr:wanna_send(Peer), Response = send_request_loop(Socket, ReqId, Peer, Request#radius_request{reqid = ReqId, secret = Secret}, Retries, Timeout, MetricsInfo), @@ -98,7 +92,7 @@ send_request({IP, Port, Secret}, Request, Options) when ?GOOD_CMD(Request) andal [] -> SendReqFn(); UpstreamServers -> - case find_suitable_peer([{IP, Port, Secret} | UpstreamServers]) of + case eradius_client_mngr:find_suitable_peer([{IP, Port, Secret} | UpstreamServers]) of [] -> no_active_servers; {{IP, Port, Secret}, _NewPool} -> @@ -133,7 +127,7 @@ send_remote_request(Node, {IP, Port, Secret}, Request, Options) when ?GOOD_CMD(R MetricsInfo = make_metrics_info(Options, {IP, Port}), update_client_requests(MetricsInfo), Peer = {ServerName, {IP, Port}}, - try gen_server:call({?SERVER, Node}, {wanna_send, Peer, MetricsInfo}) of + try eradius_client_mngr:wanna_send(Node, Peer) of {Socket, ReqId} -> Request1 = case eradius_node_mon:get_remote_version(Node) of {0, Minor} when Minor < 6 -> @@ -162,29 +156,40 @@ send_remote_request(Node, {IP, Port, Secret}, Request, Options) when ?GOOD_CMD(R send_remote_request(_Node, {_IP, _Port, _Secret}, _Request, _Options) -> error(badarg). -restore_upstream_server({ServerIP, Port, Retries, InitialRetries}) -> - ets:insert(?MODULE, {{ServerIP, Port}, Retries, InitialRetries}). - proceed_response(Request, {ok, Response, Secret, Authenticator}, _Peer = {_ServerName, {ServerIP, Port}}, TS1, MetricsInfo, Options) -> update_client_request(Request#radius_request.cmd, MetricsInfo, erlang:monotonic_time() - TS1, Request), update_client_responses(MetricsInfo), case eradius_lib:decode_request(Response, Secret, Authenticator) of - {bad_pdu, "Message-Authenticator Attribute is invalid" = Reason} -> - update_client_response(bad_authenticator, MetricsInfo, Request), - ?LOG(error, "~s INF: Noreply for request ~p. Could not decode the request, reason: ~s", [printable_peer(ServerIP, Port), Request, Reason]), - noreply; - {bad_pdu, "Authenticator Attribute is invalid" = Reason} -> - update_client_response(bad_authenticator, MetricsInfo, Request), - ?LOG(error, "~s INF: Noreply for request ~p. Could not decode the request, reason: ~s", [printable_peer(ServerIP, Port), Request, Reason]), - noreply; - {bad_pdu, "unknown request type" = Reason} -> - update_client_response(unknown_req_type, MetricsInfo, Request), - ?LOG(error, "~s INF: Noreply for request ~p. Could not decode the request, reason: ~s", [printable_peer(ServerIP, Port), Request, Reason]), - noreply; {bad_pdu, Reason} -> - update_client_response(dropped, MetricsInfo, Request), - ?LOG(error, "~s INF: Noreply for request ~p. Could not decode the request, reason: ~s", [printable_peer(ServerIP, Port), Request, Reason]), - maybe_failover(Request, noreply, {ServerIP, Port}, Options); + eradius_client_mngr:request_failed(ServerIP, Port, Options), + update_server_status_metric(ServerIP, Port, false, Options), + + case Reason of + "Message-Authenticator Attribute is invalid" -> + update_client_response(bad_authenticator, MetricsInfo, Request), + ?LOG(error, "~s INF: Noreply for request ~p. " + "Message-Authenticator Attribute is invalid", + [printable_peer(ServerIP, Port), Request]), + noreply; + "Authenticator Attribute is invalid" -> + update_client_response(bad_authenticator, MetricsInfo, Request), + ?LOG(error, "~s INF: Noreply for request ~p. " + "Authenticator Attribute is invalid", + [printable_peer(ServerIP, Port), Request]), + noreply; + "unknown request type" -> + update_client_response(unknown_req_type, MetricsInfo, Request), + ?LOG(error, "~s INF: Noreply for request ~p. " + "unknown request type", + [printable_peer(ServerIP, Port), Request]), + noreply; + _ -> + update_client_response(dropped, MetricsInfo, Request), + ?LOG(error, "~s INF: Noreply for request ~p. " + "Could not decode the request, reason: ~s", + [printable_peer(ServerIP, Port), Request, Reason]), + maybe_failover(Request, noreply, Options) + end; Decoded -> update_server_status_metric(ServerIP, Port, true, Options), update_client_response(Decoded#radius_request.cmd, MetricsInfo, Request), @@ -194,39 +199,15 @@ proceed_response(Request, {ok, Response, Secret, Authenticator}, _Peer = {_Serve proceed_response(Request, Response, {_ServerName, {ServerIP, Port}}, TS1, MetricsInfo, Options) -> update_client_responses(MetricsInfo), update_client_request(Request#radius_request.cmd, MetricsInfo, erlang:monotonic_time() - TS1, Request), - maybe_failover(Request, Response, {ServerIP, Port}, Options). -maybe_failover(Request, Response, {ServerIP, Port}, Options) -> + eradius_client_mngr:request_failed(ServerIP, Port, Options), update_server_status_metric(ServerIP, Port, false, Options), - case proplists:get_value(failover, Options, []) of - [] -> - Response; - UpstreamServers -> - handle_failed_request(Request, {ServerIP, Port}, UpstreamServers, Response, Options) - end. -handle_failed_request(Request, {ServerIP, Port} = _FailedServer, UpstreamServers, Response, Options) -> - case ets:lookup(?MODULE, {ServerIP, Port}) of - [{{ServerIP, Port}, Retries, InitialRetries}] -> - FailedTries = proplists:get_value(retries, Options, ?DEFAULT_RETRIES), - %% Mark the given RADIUS server as 'non-active' if there were more tries - %% than possible - if FailedTries >= Retries -> - ets:delete(?MODULE, {ServerIP, Port}), - Timeout = application:get_env(eradius, unreachable_timeout, 60), - timer:apply_after(Timeout * 1000, ?MODULE, restore_upstream_server, - [{ServerIP, Port, InitialRetries, InitialRetries}]); - true -> - %% RADIUS client tried to send a request to the {ServierIP, Port} RADIUS - %% server. There were done FailedTries tries and all of them failed. - %% So decrease amount of tries for the given RADIUS server that - %% that will be used for next RADIUS requests towards this RADIUS server. - ets:update_counter(?MODULE, {ServerIP, Port}, -FailedTries) - end; - [] -> - ok - end, - case find_suitable_peer(UpstreamServers) of + maybe_failover(Request, Response, Options). + +maybe_failover(Request, Response, Options) -> + UpstreamServers = proplists:get_value(failover, Options, []), + case eradius_client_mngr:find_suitable_peer(UpstreamServers) of [] -> Response; {NewPeer, NewPool} -> @@ -329,221 +310,9 @@ update_client_response(bad_authenticator, MetricsInfo, _) -> eradius_counter:inc update_client_response(unknown_req_type, MetricsInfo, _) -> eradius_counter:inc_counter(unknownTypes, MetricsInfo); update_client_response(_, _, _) -> ok. -%% @private -reconfigure() -> - catch gen_server:call(?SERVER, reconfigure, ?RECONFIGURE_TIMEOUT). - -%% ------------------------------------------------------------------------------------------ -%% -- socket process manager --record(state, { - socket_ip :: null | inet:ip_address(), - no_ports = 1 :: pos_integer(), - idcounters = maps:new() :: map(), - sockets = array:new() :: array:array(), - sup :: pid(), - clients = [] :: [{{integer(),integer(),integer(),integer()}, integer()}] - }). - -%% @private -init([]) -> - {ok, Sup} = eradius_client_sup:start_link(), - case configure(#state{socket_ip = null, sup = Sup}) of - {error, Error} -> {stop, Error}; - Else -> Else - end. - -%% @private -handle_call({wanna_send, Peer = {_PeerName, PeerSocket}, _MetricsInfo}, _From, State) -> - {PortIdx, ReqId, NewIdCounters} = next_port_and_req_id(PeerSocket, State#state.no_ports, State#state.idcounters), - {SocketProcess, NewSockets} = find_socket_process(PortIdx, State#state.sockets, State#state.socket_ip, State#state.sup), - IsCreated = lists:member(Peer, State#state.clients), - NewState = case IsCreated of - false -> - State#state{idcounters = NewIdCounters, sockets = NewSockets, clients = [Peer | State#state.clients]}; - true -> - State#state{idcounters = NewIdCounters, sockets = NewSockets} - end, - {reply, {SocketProcess, ReqId}, NewState}; - -%% @private -handle_call(reconfigure, _From, State) -> - case configure(State) of - {error, Error} -> {reply, Error, State}; - {ok, NState} -> {reply, ok, NState} - end; - -%% @private -handle_call(_OtherCall, _From, State) -> - {noreply, State}. - -%% @private -handle_cast(_Msg, State) -> {noreply, State}. - -%% @private -handle_info({PortIdx, Pid}, State = #state{sockets = Sockets}) -> - NSockets = update_socket_process(PortIdx, Sockets, Pid), - {noreply, State#state{sockets = NSockets}}; - -handle_info(_Info, State) -> - {noreply, State}. - -%% @private -terminate(_Reason, _State) -> ok. - -%% @private -code_change(_OldVsn, State, _Extra) -> {ok, State}. - -%% @private -configure(State) -> - case ets:info(?MODULE) of - undefined -> - prepare_pools(); - _ -> - %% if ets table is already exists - which could be in a case of - %% reconfigure, just re-create the table and fill it with newly - %% configured pools of RADIUS upstream servers - ets:delete(?MODULE), - prepare_pools() - end, - {ok, ClientPortCount} = application:get_env(eradius, client_ports), - {ok, ClientIP} = application:get_env(eradius, client_ip), - case parse_ip(ClientIP) of - {ok, Address} -> - configure_address(State, ClientPortCount, Address); - {error, _} -> - ?LOG(error, "Invalid RADIUS client IP (parsing failed): ~p", [ClientIP]), - {error, {bad_client_ip, ClientIP}} - end. - --ifdef(TEST). - -get_state() -> - State = sys:get_state(?SERVER), - Keys = record_info(fields, state), - Values = tl(tuple_to_list(State)), - maps:from_list(lists:zip(Keys, Values)). - --endif. - -%% private -prepare_pools() -> - ets:new(?MODULE, [ordered_set, public, named_table, {keypos, 1}, {write_concurrency,true}]), - lists:foreach(fun({_PoolName, Servers}) -> prepare_pool(Servers) end, application:get_env(eradius, servers_pool, [])), - lists:foreach(fun(Server) -> store_upstream_servers(Server) end, application:get_env(eradius, servers, [])), - init_server_status_metrics(). - -prepare_pool([]) -> ok; -prepare_pool([{Addr, Port, _, Opts} | Servers]) -> - Retries = proplists:get_value(retries, Opts, ?DEFAULT_RETRIES), - store_radius_server_from_pool(Addr, Port, Retries), - prepare_pool(Servers); -prepare_pool([{Addr, Port, _} | Servers]) -> - store_radius_server_from_pool(Addr, Port, ?DEFAULT_RETRIES), - prepare_pool(Servers). - -store_upstream_servers({Server, _}) -> - store_upstream_servers(Server); -store_upstream_servers({Server, _, _}) -> - store_upstream_servers(Server); -store_upstream_servers(Server) -> - HandlerDefinitions = application:get_env(eradius, Server, []), - UpdatePoolFn = fun (HandlerOpts) -> - {DefaultRoute, Routes, Retries} = eradius_proxy:get_routes_info(HandlerOpts), - eradius_proxy:put_default_route_to_pool(DefaultRoute, Retries), - eradius_proxy:put_routes_to_pool(Routes, Retries) - end, - lists:foreach(fun (HandlerDefinition) -> - case HandlerDefinition of - {{_, []}, _} -> ok; - {{_, _, []}, _} -> ok; - {{_, HandlerOpts}, _} -> UpdatePoolFn(HandlerOpts); - {{_, _, HandlerOpts}, _} -> UpdatePoolFn(HandlerOpts); - _HandlerDefinition -> ok - end - end, - HandlerDefinitions). - -%% private -store_radius_server_from_pool(Addr, Port, Retries) when is_tuple(Addr) and is_integer(Port) and is_integer(Retries) -> - ets:insert(?MODULE, {{Addr, Port}, Retries, Retries}); -store_radius_server_from_pool(Addr, Port, Retries) when is_list(Addr) and is_integer(Port) and is_integer(Retries) -> - IP = get_ip(Addr), - ets:insert(?MODULE, {{IP, Port}, Retries, Retries}); -store_radius_server_from_pool(Addr, Port, Retries) -> - ?LOG(error, "bad RADIUS upstream server specified in RADIUS servers pool configuration ~p", [{Addr, Port, Retries}]), - error(badarg). - -configure_address(State = #state{socket_ip = OAdd, sockets = Sockts}, NPorts, NAdd) -> - case OAdd of - null -> - {ok, State#state{socket_ip = NAdd, no_ports = NPorts}}; - NAdd -> - configure_ports(State, NPorts); - _ -> - ?LOG(info, "Reopening RADIUS client sockets (client_ip changed to ~s)", [inet:ntoa(NAdd)]), - array:map( - fun(_PortIdx, undefined) -> - ok; - (_PortIdx, Socket) -> - eradius_client_socket:close(Socket) - end, Sockts), - {ok, State#state{sockets = array:new(), socket_ip = NAdd, no_ports = NPorts}} - end. - -configure_ports(State = #state{no_ports = OPorts, sockets = Sockets}, NPorts) -> - if - OPorts =< NPorts -> - {ok, State#state{no_ports = NPorts}}; - true -> - Counters = fix_counters(NPorts, State#state.idcounters), - NSockets = close_sockets(NPorts, Sockets), - {ok, State#state{sockets = NSockets, no_ports = NPorts, idcounters = Counters}} - end. - -fix_counters(NPorts, Counters) -> - maps:map(fun(_Peer, Value = {NextPortIdx, _NextReqId}) when NextPortIdx < NPorts -> Value; - (_Peer, {_NextPortIdx, NextReqId}) -> {0, NextReqId} - end, Counters). - -close_sockets(NPorts, Sockets) -> - case array:size(Sockets) =< NPorts of - true -> - Sockets; - false -> - List = array:to_list(Sockets), - {_, Rest} = lists:split(NPorts, List), - lists:map( - fun(undefined) -> ok; - (Socket) -> eradius_client_socket:close(Socket) - end, Rest), - array:resize(NPorts, Sockets) - end. - -next_port_and_req_id(Peer, NumberOfPorts, Counters) -> - case Counters of - #{Peer := {NextPortIdx, ReqId}} when ReqId < 255 -> - NextReqId = (ReqId + 1); - #{Peer := {PortIdx, 255}} -> - NextPortIdx = (PortIdx + 1) rem (NumberOfPorts - 1), - NextReqId = 0; - _ -> - NextPortIdx = erlang:phash2(Peer, NumberOfPorts), - NextReqId = 0 - end, - NewCounters = Counters#{Peer => {NextPortIdx, NextReqId}}, - {NextPortIdx, NextReqId, NewCounters}. - -find_socket_process(PortIdx, Sockets, SocketIP, Sup) -> - case array:get(PortIdx, Sockets) of - undefined -> - {ok, Socket} = eradius_client_socket:new(Sup, SocketIP), - {Socket, array:set(PortIdx, Socket, Sockets)}; - Socket -> - {Socket, Sockets} - end. - -update_socket_process(PortIdx, Sockets, Pid) -> - array:set(PortIdx, Pid, Sockets). +%%%========================================================================= +%%% internal functions +%%%========================================================================= parse_ip(undefined) -> {ok, undefined}; @@ -551,22 +320,9 @@ parse_ip(Address) when is_list(Address) -> inet_parse:address(Address); parse_ip(T = {_, _, _, _}) -> {ok, T}; -parse_ip(T = {_, _, _, _, _, _}) -> +parse_ip(T = {_, _, _, _, _, _, _, _}) -> {ok, T}. -init_server_status_metrics() -> - case application:get_env(eradius, server_status_metrics_enabled, false) of - false -> - ok; - true -> - %% That will be called at eradius startup and we must be sure that prometheus - %% application already started if server status metrics supposed to be used - application:ensure_all_started(prometheus), - ets:foldl(fun ({{Addr, Port}, _, _}, _Acc) -> - eradius_counter:set_boolean_metric(server_status, [Addr, Port], false) - end, [], ?MODULE) - end. - make_metrics_info(Options, {ServerIP, ServerPort}) -> ServerName = proplists:get_value(server_name, Options, undefined), ClientName = proplists:get_value(client_name, Options, undefined), @@ -650,28 +406,6 @@ client_response_counter_account_match_spec_compile() -> MatchSpecCompile end. -find_suitable_peer(undefined) -> - []; -find_suitable_peer([]) -> - []; -find_suitable_peer([{Host, Port, Secret} | Pool]) when is_list(Host) -> - try - IP = get_ip(Host), - find_suitable_peer([{IP, Port, Secret} | Pool]) - catch _:_ -> - %% can't resolve ip by some reasons, just ignore it - find_suitable_peer(Pool) - end; -find_suitable_peer([{IP, Port, Secret} | Pool]) -> - case ets:lookup(?MODULE, {IP, Port}) of - [] -> - find_suitable_peer(Pool); - [{{IP, Port}, _Retries, _InitialRetries}] -> - {{IP, Port, Secret}, Pool} - end; -find_suitable_peer([{IP, Port, Secret, _Opts} | Pool]) -> - find_suitable_peer([{IP, Port, Secret} | Pool]). - get_ip(Host) -> case inet:gethostbyname(Host) of {ok, #hostent{h_addrtype = inet, h_addr_list = [IP]}} -> diff --git a/src/eradius_client_mngr.erl b/src/eradius_client_mngr.erl new file mode 100644 index 00000000..3a2e1a20 --- /dev/null +++ b/src/eradius_client_mngr.erl @@ -0,0 +1,399 @@ +%% Copyright (c) 2002-2007, Martin Björklund and Torbjörn Törnkvist +%% Copyright (c) 2011, Travelping GmbH <info@travelping.com> +%% +%% SPDX-License-Identifier: MIT +%% +-module(eradius_client_mngr). + +-behaviour(gen_server). + +%% external API +-export([start_link/0, wanna_send/1, wanna_send/2, reconfigure/0, reconfigure/1]). + +%% internal API +-export([store_radius_server_from_pool/3, + request_failed/3, + restore_upstream_server/1, + find_suitable_peer/1]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). + +-ifdef(TEST). +-export([get_state/0, servers/0, servers/1, init_server_status_metrics/0]). +-endif. + +-include_lib("kernel/include/logger.hrl"). +-include_lib("kernel/include/inet.hrl"). +-include("eradius_internal.hrl"). + +-type client_opts() :: + #{family => inet | inet6, + ip => any | inet:ip_address(), + active_n => once | non_neg_integer(), + no_ports => non_neg_integer(), + recbuf => non_neg_integer(), + sndbuf => non_neg_integer(), + server_pool => [term()], + servers => [term()]}. +-type client_config() :: + #{family := inet | inet6, + ip := any | inet:ip_address(), + active_n := once | non_neg_integer(), + no_ports := non_neg_integer(), + recbuf := non_neg_integer(), + sndbuf := non_neg_integer(), + servers_pool => [term()], + servers => [term()]}. + +-export_type([client_config/0]). + +-record(state, { + config :: client_config(), + socket_id :: {Family :: inet | inet6, IP :: any | inet:ip_address()}, + no_ports = 1 :: pos_integer(), + idcounters = maps:new() :: map(), + sockets = array:new() :: array:array(), + clients = [] :: [{{integer(),integer(),integer(),integer()}, integer()}] + }). + +-define(SERVER, ?MODULE). + +-define(RECONFIGURE_TIMEOUT, 15000). + +%%%========================================================================= +%%% API +%%%========================================================================= + +start_link() -> + case client_config(default_client_opts()) of + {ok, Config} -> gen_server:start_link({local, ?SERVER}, ?MODULE, [Config], []); + {error, _} = Error -> Error + end. + +wanna_send(Peer) -> + gen_server:call(?SERVER, {wanna_send, Peer}). + +wanna_send(Node, Peer) -> + gen_server:call({?SERVER, Node}, {wanna_send, Peer}). + +%% @private +reconfigure() -> + %% backward compatibility wrapper + (catch reconfigure(#{})). + +%% @doc reconfigure the Radius client +reconfigure(Opts) -> + gen_server:call(?SERVER, {reconfigure, Opts}, ?RECONFIGURE_TIMEOUT). + +request_failed(ServerIP, Port, Options) -> + case ets:lookup(?MODULE, {ServerIP, Port}) of + [{{ServerIP, Port}, Retries, InitialRetries}] -> + FailedTries = proplists:get_value(retries, Options, ?DEFAULT_RETRIES), + %% Mark the given RADIUS server as 'non-active' if there were more tries + %% than possible + if FailedTries >= Retries -> + ets:delete(?MODULE, {ServerIP, Port}), + Timeout = application:get_env(eradius, unreachable_timeout, 60), + timer:apply_after(Timeout * 1000, ?MODULE, restore_upstream_server, + [{ServerIP, Port, InitialRetries, InitialRetries}]); + true -> + %% RADIUS client tried to send a request to the {ServierIP, Port} RADIUS + %% server. There were done FailedTries tries and all of them failed. + %% So decrease amount of tries for the given RADIUS server that + %% that will be used for next RADIUS requests towards this RADIUS server. + ets:update_counter(?MODULE, {ServerIP, Port}, -FailedTries) + end; + [] -> + ok + end. + +restore_upstream_server({ServerIP, Port, Retries, InitialRetries}) -> + ets:insert(?MODULE, {{ServerIP, Port}, Retries, InitialRetries}). + +find_suitable_peer(undefined) -> + []; +find_suitable_peer([]) -> + []; +find_suitable_peer([{Host, Port, Secret} | Pool]) when is_list(Host) -> + try + IP = get_ip(Host), + find_suitable_peer([{IP, Port, Secret} | Pool]) + catch _:_ -> + %% can't resolve ip by some reasons, just ignore it + find_suitable_peer(Pool) + end; +find_suitable_peer([{IP, Port, Secret} | Pool]) -> + case ets:lookup(?MODULE, {IP, Port}) of + [] -> + find_suitable_peer(Pool); + [{{IP, Port}, _Retries, _InitialRetries}] -> + {{IP, Port, Secret}, Pool} + end; +find_suitable_peer([{IP, Port, Secret, _Opts} | Pool]) -> + find_suitable_peer([{IP, Port, Secret} | Pool]). + +-ifdef(TEST). + +get_state() -> + State = sys:get_state(?SERVER), + Keys = record_info(fields, state), + Values = tl(tuple_to_list(State)), + maps:from_list(lists:zip(Keys, Values)). + +servers() -> + ets:tab2list(?MODULE). + +servers(Key) -> + ets:lookup(?MODULE, Key). + +-endif. + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +init([#{no_ports := NPorts} = Config]) -> + ets:new(?MODULE, [public, named_table, ordered_set, {keypos, 1}, {write_concurrency, true}]), + prepare_pools(Config), + + State = #state{ + config = Config, + socket_id = socket_id(Config), + no_ports = NPorts}, + {ok, State}. + +%% @private +handle_call({wanna_send, Peer = {_PeerName, PeerSocket}}, _From, + #state{config = Config, + no_ports = NoPorts, idcounters = IdCounters, + sockets = Sockets, clients = Clients} = State0) -> + {PortIdx, ReqId, NewIdCounters} = next_port_and_req_id(PeerSocket, NoPorts, IdCounters), + {SocketProcess, NewSockets} = find_socket_process(PortIdx, Sockets, Config), + State1 = State0#state{idcounters = NewIdCounters, sockets = NewSockets}, + State = + case lists:member(Peer, Clients) of + false -> State1#state{clients = [Peer | Clients]}; + true -> State1 + end, + {reply, {SocketProcess, ReqId}, State}; + +%% @private +handle_call({reconfigure, Opts}, _From, #state{config = OConfig} = State0) -> + case client_config(maps:merge(OConfig, Opts)) of + {ok, Config} -> + ets:delete_all_objects(?MODULE), + prepare_pools(Config), + + State = reconfigure_address(Config, State0#state{config = Config}), + {reply, ok, State}; + + {error, _} = Error -> + {reply, Error, State0} + end; + +%% @private +handle_call(_OtherCall, _From, State) -> + {noreply, State}. + +%% @private +handle_cast(_Msg, State) -> {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +%% @private +terminate(_Reason, _State) -> ok. + +%% @private +code_change(_OldVsn, State, _Extra) -> {ok, State}. + +%%%========================================================================= +%%% internal functions +%%%========================================================================= + +socket_id(#{family := Family, ip := IP}) -> + {Family, IP}. + +socket_id_str({_, IP}) when is_tuple(IP) -> + inet:ntoa(IP); +socket_id_str({_, IP}) when is_atom(IP) -> + atom_to_list(IP). + +get_ip(Host) -> + case inet:gethostbyname(Host) of + {ok, #hostent{h_addrtype = inet, h_addr_list = [IP]}} -> + IP; + {ok, #hostent{h_addrtype = inet, h_addr_list = [_ | _] = IPs}} -> + Index = rand:uniform(length(IPs)), + lists:nth(Index, IPs); + _ -> error(badarg) + end. + +%% @private +-spec default_client_opts() -> client_opts(). +default_client_opts() -> + #{ip => application:get_env(eradius, client_ip, any), + no_ports => application:get_env(eradius, client_ports, 10), + active_n => application:get_env(eradius, active_n, 100), + recbuf => application:get_env(eradius, recbuf, 8192), + sndbuf => application:get_env(eradius, sndbuf, 131072), + servers_pool => application:get_env(eradius, servers_pool, []), + servers => application:get_env(eradius, servers, []) + }. + + +-spec client_config(client_opts()) -> {ok, client_config()} | {error, _}. +client_config(#{ip := IP} = Opts) when is_atom(IP) -> + {ok, Opts#{family => inet6, ip := any}}; +client_config(#{ip := {_, _, _, _}} = Opts) -> + {ok, Opts#{family => inet}}; +client_config(#{ip := {_, _, _, _, _, _, _, _}} = Opts) -> + {ok, Opts#{family => inet6}}; +client_config(#{ip := Address} = Opts) when is_list(Address) -> + case inet_parse:address(Address) of + {ok, {_, _, _, _} = IP} -> + {ok, Opts#{family => inet, ip => IP}}; + {ok, {_, _, _, _, _, _, _, _} = IP} -> + {ok, Opts#{family => inet6, ip => IP}}; + _ -> + ?LOG(error, "Invalid RADIUS client IP (parsing failed): ~p", [Address]), + {error, {bad_client_ip, Address}} + end. + +%% private +prepare_pools(#{servers_pool := PoolList, servers := ServerList}) -> + lists:foreach(fun({_PoolName, Servers}) -> prepare_pool(Servers) end, PoolList), + lists:foreach(fun(Server) -> store_upstream_servers(Server) end, ServerList), + init_server_status_metrics(). + +prepare_pool([]) -> ok; +prepare_pool([{Addr, Port, _, Opts} | Servers]) -> + Retries = proplists:get_value(retries, Opts, ?DEFAULT_RETRIES), + store_radius_server_from_pool(Addr, Port, Retries), + prepare_pool(Servers); +prepare_pool([{Addr, Port, _} | Servers]) -> + store_radius_server_from_pool(Addr, Port, ?DEFAULT_RETRIES), + prepare_pool(Servers). + +store_upstream_servers({Server, _}) -> + store_upstream_servers(Server); +store_upstream_servers({Server, _, _}) -> + store_upstream_servers(Server); +store_upstream_servers(Server) -> + %% TBD: move proxy config into the proxy logic... + + HandlerDefinitions = application:get_env(eradius, Server, []), + UpdatePoolFn = fun (HandlerOpts) -> + {DefaultRoute, Routes, Retries} = eradius_proxy:get_routes_info(HandlerOpts), + eradius_proxy:put_default_route_to_pool(DefaultRoute, Retries), + eradius_proxy:put_routes_to_pool(Routes, Retries) + end, + lists:foreach(fun (HandlerDefinition) -> + case HandlerDefinition of + {{_, []}, _} -> ok; + {{_, _, []}, _} -> ok; + {{_, HandlerOpts}, _} -> UpdatePoolFn(HandlerOpts); + {{_, _, HandlerOpts}, _} -> UpdatePoolFn(HandlerOpts); + _HandlerDefinition -> ok + end + end, + HandlerDefinitions). + +%% private +store_radius_server_from_pool(Addr, Port, Retries) + when is_tuple(Addr), is_integer(Port), is_integer(Retries) -> + ets:insert(?MODULE, {{Addr, Port}, Retries, Retries}); +store_radius_server_from_pool(Addr, Port, Retries) + when is_list(Addr), is_integer(Port), is_integer(Retries) -> + IP = get_ip(Addr), + ets:insert(?MODULE, {{IP, Port}, Retries, Retries}); +store_radius_server_from_pool(Addr, Port, Retries) -> + ?LOG(error, "bad RADIUS upstream server specified in RADIUS servers pool configuration ~p", [{Addr, Port, Retries}]), + error(badarg). + +reconfigure_address(#{no_ports := NPorts} = Config, + #state{socket_id = OAdd, sockets = Sockts} = State) -> + NAdd = socket_id(Config), + case OAdd of + NAdd -> + reconfigure_ports(NPorts, State); + _ -> + ?LOG(info, "Reopening RADIUS client sockets (client_ip changed to ~s)", + [socket_id_str(NAdd)]), + array:map( + fun(_PortIdx, undefined) -> + ok; + (_PortIdx, Socket) -> + eradius_client_socket:close(Socket) + end, Sockts), + Counters = fix_counters(NPorts, State#state.idcounters), + State#state{sockets = array:new(), socket_id = NAdd, + no_ports = NPorts, idcounters = Counters} + end. + +reconfigure_ports(NPorts, #state{no_ports = OPorts, sockets = Sockets} = State) -> + if + OPorts =< NPorts -> + State#state{no_ports = NPorts}; + true -> + Counters = fix_counters(NPorts, State#state.idcounters), + NSockets = close_sockets(NPorts, Sockets), + State#state{sockets = NSockets, no_ports = NPorts, idcounters = Counters} + end. + +fix_counters(NPorts, Counters) -> + maps:map(fun(_Peer, Value = {NextPortIdx, _NextReqId}) when NextPortIdx < NPorts -> Value; + (_Peer, {_NextPortIdx, NextReqId}) -> {0, NextReqId} + end, Counters). + +close_sockets(NPorts, Sockets) -> + case array:size(Sockets) =< NPorts of + true -> + Sockets; + false -> + List = array:to_list(Sockets), + {_, Rest} = lists:split(NPorts, List), + lists:map( + fun(undefined) -> ok; + (Socket) -> eradius_client_socket:close(Socket) + end, Rest), + array:resize(NPorts, Sockets) + end. + +next_port_and_req_id(Peer, NumberOfPorts, Counters) -> + case Counters of + #{Peer := {NextPortIdx, ReqId}} when ReqId < 255 -> + NextReqId = (ReqId + 1); + #{Peer := {PortIdx, 255}} -> + NextPortIdx = (PortIdx + 1) rem (NumberOfPorts - 1), + NextReqId = 0; + _ -> + NextPortIdx = erlang:phash2(Peer, NumberOfPorts), + NextReqId = 0 + end, + NewCounters = Counters#{Peer => {NextPortIdx, NextReqId}}, + {NextPortIdx, NextReqId, NewCounters}. + +find_socket_process(PortIdx, Sockets, Config) -> + case array:get(PortIdx, Sockets) of + undefined -> + {ok, Socket} = eradius_client_socket:new(Config), + {Socket, array:set(PortIdx, Socket, Sockets)}; + Socket -> + {Socket, Sockets} + end. + +%% @private +init_server_status_metrics() -> + case application:get_env(eradius, server_status_metrics_enabled, false) of + false -> + ok; + true -> + %% That will be called at eradius startup and we must be sure that prometheus + %% application already started if server status metrics supposed to be used + application:ensure_all_started(prometheus), + ets:foldl(fun ({{Addr, Port}, _, _}, _Acc) -> + eradius_counter:set_boolean_metric(server_status, [Addr, Port], false) + end, [], ?MODULE) + end. diff --git a/src/eradius_client_socket.erl b/src/eradius_client_socket.erl index 7472853e..22fddf3d 100644 --- a/src/eradius_client_socket.erl +++ b/src/eradius_client_socket.erl @@ -8,7 +8,7 @@ -behaviour(gen_server). %% API --export([new/2, start_link/1, send_request/5, close/1]). +-export([new/1, start_link/1, send_request/5, close/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). @@ -19,11 +19,11 @@ %%% API %%%========================================================================= -new(Sup, SocketIP) -> - eradius_client_sup:new(Sup, SocketIP). +new(Config) -> + eradius_client_socket_sup:new(Config). -start_link(SocketIP) -> - gen_server:start_link(?MODULE, [SocketIP], []). +start_link(Config) -> + gen_server:start_link(?MODULE, [Config], []). send_request(Socket, Peer, ReqId, Request, Timeout) -> try @@ -44,17 +44,17 @@ close(Socket) -> %%% gen_server callbacks %%%=================================================================== -init([SocketIP]) -> - case SocketIP of - undefined -> +init([#{family := Family, ip := IP, active_n := ActiveN, + recbuf := RecBuf, sndbuf := SndBuf} = _Config]) -> + case IP of + any -> ExtraOptions = []; - SocketIP when is_tuple(SocketIP) -> - ExtraOptions = [{ip, SocketIP}] + _ when is_tuple(IP) -> + ExtraOptions = [{ip, IP}] end, - ActiveN = application:get_env(eradius, active_n, 100), - RecBuf = application:get_env(eradius, recbuf, 8192), - SndBuf = application:get_env(eradius, sndbuf, 131072), - Opts = [{active, ActiveN}, binary, {recbuf, RecBuf}, {sndbuf, SndBuf} | ExtraOptions], + + Opts = [{active, ActiveN}, binary, {recbuf, RecBuf}, {sndbuf, SndBuf}, + Family | ExtraOptions], {ok, Socket} = gen_udp:open(0, Opts), State = #state{ diff --git a/src/eradius_client_socket_sup.erl b/src/eradius_client_socket_sup.erl new file mode 100644 index 00000000..954156be --- /dev/null +++ b/src/eradius_client_socket_sup.erl @@ -0,0 +1,52 @@ +-module(eradius_client_socket_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0, new/1]). + +%% Supervisor callbacks +-export([init/1]). + +-define(SERVER, ?MODULE). + +%%%=================================================================== +%%% API functions +%%%=================================================================== + +-spec start_link() -> {ok, Pid :: pid()} | + {error, {already_started, Pid :: pid()}} | + {error, {shutdown, term()}} | + {error, term()} | + ignore. +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +new(Config) -> + supervisor:start_child(?SERVER, [Config]). + +%%%=================================================================== +%%% Supervisor callbacks +%%%=================================================================== + +-spec init(Args :: term()) -> + {ok, {SupFlags :: supervisor:sup_flags(), + [ChildSpec :: supervisor:child_spec()]}} | + ignore. +init([]) -> + SupFlags = #{strategy => simple_one_for_one, + intensity => 5, + period => 10}, + + Child = #{id => eradius_client_socket, + start => {eradius_client_socket, start_link, []}, + restart => transient, + shutdown => 5000, + type => worker, + modules => [eradius_client_socket]}, + + {ok, {SupFlags, [Child]}}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/src/eradius_client_sup.erl b/src/eradius_client_sup.erl index 7c40b2cd..6db9d435 100644 --- a/src/eradius_client_sup.erl +++ b/src/eradius_client_sup.erl @@ -3,7 +3,7 @@ -behaviour(supervisor). %% API --export([start_link/0, new/2]). +-export([start_link/1, new/2]). %% Supervisor callbacks -export([init/1]). @@ -14,16 +14,23 @@ %%% API functions %%%=================================================================== --spec start_link() -> {ok, Pid :: pid()} | +-spec start_link(Config :: eradius_client:client_config()) -> + {ok, Pid :: pid()} | {error, {already_started, Pid :: pid()}} | {error, {shutdown, term()}} | {error, term()} | ignore. -start_link() -> - supervisor:start_link(?MODULE, []). +start_link(Config) -> + supervisor:start_link(?MODULE, [Config]). -new(Sup, SocketId) -> - supervisor:start_child(Sup, [SocketId]). +new(Owner, SocketId) -> + Children = supervisor:which_children(Owner), + case lists:keyfind(eradius_client_socket_sup, 1, Children) of + {eradius_client_socket_sup, SupPid, _, _} when is_pid(SupPid) -> + supervisor:start_child(SupPid, [SocketId]); + _ -> + {error, dead} + end. %%%=================================================================== %%% Supervisor callbacks @@ -33,19 +40,26 @@ new(Sup, SocketId) -> {ok, {SupFlags :: supervisor:sup_flags(), [ChildSpec :: supervisor:child_spec()]}} | ignore. -init([]) -> - SupFlags = #{strategy => simple_one_for_one, +init([Opts]) -> + SupFlags = #{strategy => one_for_one, intensity => 5, period => 10}, + Client = + #{id => eradius_client, + start => {eradius_client, start_link, [self(), Opts]}, + restart => permanent, + shutdown => 5000, + type => worker, + modules => [eradius_client]}, + SocketSup = + #{id => eradius_client_socket_sup, + start => {eradius_client_socket_sup, start_link, []}, + restart => permanent, + shutdown => 5000, + type => supervisor, + modules => [eradius_client_socket_sup]}, - Child = #{id => eradius_client_socket, - start => {eradius_client_socket, start_link, []}, - restart => transient, - shutdown => 5000, - type => worker, - modules => [eradius_client_socket]}, - - {ok, {SupFlags, [Child]}}. + {ok, {SupFlags, [Client, SocketSup]}}. %%%=================================================================== %%% Internal functions diff --git a/src/eradius_internal.hrl b/src/eradius_internal.hrl new file mode 100644 index 00000000..cc814287 --- /dev/null +++ b/src/eradius_internal.hrl @@ -0,0 +1,2 @@ +-define(DEFAULT_RETRIES, 3). +-define(DEFAULT_TIMEOUT, 5000). diff --git a/src/eradius_proxy.erl b/src/eradius_proxy.erl index ff95eb89..5961b13b 100644 --- a/src/eradius_proxy.erl +++ b/src/eradius_proxy.erl @@ -290,9 +290,9 @@ get_routes_info(HandlerOpts) -> put_default_route_to_pool(false, _) -> ok; put_default_route_to_pool({default_route, {Host, Port, _Secret}}, Retries) -> - eradius_client:store_radius_server_from_pool(Host, Port, Retries); + eradius_client_mngr:store_radius_server_from_pool(Host, Port, Retries); put_default_route_to_pool({default_route, {Host, Port, _Secret}, _PoolName}, Retries) -> - eradius_client:store_radius_server_from_pool(Host, Port, Retries); + eradius_client_mngr:store_radius_server_from_pool(Host, Port, Retries); put_default_route_to_pool(_, _) -> ok. put_routes_to_pool(false, _Retries) -> ok; @@ -300,11 +300,11 @@ put_routes_to_pool({routes, Routes}, Retries) -> lists:foreach(fun (Route) -> case Route of {_RouteName, {Host, Port, _Secret}} -> - eradius_client:store_radius_server_from_pool(Host, Port, Retries); + eradius_client_mngr:store_radius_server_from_pool(Host, Port, Retries); {_RouteName, {Host, Port, _Secret}, _Pool} -> - eradius_client:store_radius_server_from_pool(Host, Port, Retries); + eradius_client_mngr:store_radius_server_from_pool(Host, Port, Retries); {Host, Port, _Secret, _Opts} -> - eradius_client:store_radius_server_from_pool(Host, Port, Retries); + eradius_client_mngr:store_radius_server_from_pool(Host, Port, Retries); _ -> ok end end, Routes). diff --git a/src/eradius_server.erl b/src/eradius_server.erl index a4ad3677..87e358e7 100644 --- a/src/eradius_server.erl +++ b/src/eradius_server.erl @@ -307,17 +307,21 @@ handle_request({HandlerMod, HandlerArg}, NasProp = #nas_prop{secret = Secret, na maps:from_list(eradius_log:collect_meta(Sender, Request))), eradius_log:write_request(Sender, Request), apply_handler_mod(HandlerMod, HandlerArg, Request, NasProp); - {bad_pdu, "Message-Authenticator Attribute is invalid" = Reason} -> - ?LOG(error, "~s INF: Could not decode the request, reason: ~s", [printable_peer(ServerIP, Port), Reason]), + {bad_pdu, "Message-Authenticator Attribute is invalid"} -> + ?LOG(error, "~s INF: Message-Authenticator Attribute is invalid", + [printable_peer(ServerIP, Port)]), {discard, bad_authenticator}; - {bad_pdu, "Authenticator Attribute is invalid" = Reason} -> - ?LOG(error, "~s INF: Could not decode the request, reason: ~s", [printable_peer(ServerIP, Port), Reason]), + {bad_pdu, "Authenticator Attribute is invalid"} -> + ?LOG(error, "~s INF: Authenticator Attribute is invalid", + [printable_peer(ServerIP, Port)]), {discard, bad_authenticator}; - {bad_pdu, "unknown request type" = Reason} -> - ?LOG(error, "~s INF: Could not decode the request, reason: ~s", [printable_peer(ServerIP, Port), Reason]), + {bad_pdu, "unknown request type"} -> + ?LOG(error, "~s INF: unknown request type", + [printable_peer(ServerIP, Port)]), {discard, unknown_req_type}; {bad_pdu, Reason} -> - ?LOG(error, "~s INF: Could not decode the request, reason: ~s", [printable_peer(ServerIP, Port), Reason]), + ?LOG(error, "~s INF: Could not decode the request, reason: ~s", + [printable_peer(ServerIP, Port), Reason]), {discard, malformed} end. diff --git a/src/eradius_sup.erl b/src/eradius_sup.erl index 342af84f..86093a41 100644 --- a/src/eradius_sup.erl +++ b/src/eradius_sup.erl @@ -25,6 +25,20 @@ init([]) -> NodeMon = {node_mon, {eradius_node_mon, start_link, []}, permanent, brutal_kill, worker, [eradius_node_mon]}, RadiusLog = {radius_log, {eradius_log, start_link, []}, permanent, brutal_kill, worker, [eradius_log]}, ServerTopSup = {server_top_sup, {eradius_server_top_sup, start_link, []}, permanent, infinity, supervisor, [eradius_server_top_sup]}, - Client = {client, {eradius_client, start_link, []}, permanent, 500, worker, [eradius_client]}, + ClientMngr = + #{id => client_mngr, + start => {eradius_client_mngr, start_link, []}, + restart => permanent, + shutdown => 500, + type => worker, + modules => [eradius_client_mngr]}, + ClientSocketSup = + #{id => eradius_client_socket_sup, + start => {eradius_client_socket_sup, start_link, []}, + restart => permanent, + shutdown => 5000, + type => supervisor, + modules => [eradius_client_socket_sup]}, - {ok, {SupFlags, [DictServer, NodeMon, StatsServer, StatsCollect, RadiusLog, ServerTopSup, Client]}}. + {ok, {SupFlags, [DictServer, NodeMon, StatsServer, StatsCollect, RadiusLog, + ServerTopSup, ClientSocketSup, ClientMngr]}}. diff --git a/test/eradius_client_SUITE.erl b/test/eradius_client_SUITE.erl index bdf972e6..b30d4e5f 100644 --- a/test/eradius_client_SUITE.erl +++ b/test/eradius_client_SUITE.erl @@ -100,8 +100,7 @@ end_per_testcase(_Test, Config) -> %% STUFF getSocketCount() -> - #{sup := Sup} = eradius_client:get_state(), - Counts = supervisor:count_children(Sup), + Counts = supervisor:count_children(eradius_client_socket_sup), proplists:get_value(active, Counts). testSocket(undefined) -> @@ -133,6 +132,8 @@ meckStop() -> parse_ip(undefined) -> {ok, undefined}; +parse_ip(any) -> + {ok, any}; parse_ip(Address) when is_list(Address) -> inet_parse:address(Address); parse_ip(T = {_, _, _, _}) -> @@ -148,9 +149,9 @@ test(false, Msg) -> false. check(OldState, NewState = #{no_ports := P}, null, A) -> check(OldState, NewState, P, A); -check(OldState, NewState = #{socket_ip := A}, P, null) -> check(OldState, NewState, P, A); -check(#{sockets := OS, no_ports := _OP, idcounters := _OC, socket_ip := OA}, - #{sockets := NS, no_ports := NP, idcounters := NC, socket_ip := NA}, +check(OldState, NewState = #{socket_id := {_, A}}, P, null) -> check(OldState, NewState, P, A); +check(#{sockets := OS, no_ports := _OP, idcounters := _OC, socket_id := {_, OA}}, + #{sockets := NS, no_ports := NP, idcounters := NC, socket_id := {_, NA}}, P, A) -> {ok, PA} = parse_ip(A), test(PA == NA, "Adress not configured") and @@ -181,9 +182,9 @@ send_request(_Config) -> send(FUN, Ports, Address) -> meckStart(), - OldState = eradius_client:get_state(), + OldState = eradius_client_mngr:get_state(), FUN(), - NewState = eradius_client:get_state(), + NewState = eradius_client_mngr:get_state(), true = check(OldState, NewState, Ports, Address), meckStop(). @@ -191,24 +192,23 @@ wanna_send(_Config) -> lists:map(fun(_) -> IP = {rand:uniform(100), rand:uniform(100), rand:uniform(100), rand:uniform(100)}, Port = rand:uniform(100), - MetricsInfo = {{undefined, undefined, undefined}, {undefined, undefined, undefined}}, - FUN = fun() -> gen_server:call(eradius_client, {wanna_send, {undefined, {IP, Port}}, MetricsInfo}) end, + FUN = fun() -> eradius_client_mngr:wanna_send({undefined, {IP, Port}}) end, send(FUN, null, null) end, lists:seq(1, 10)). %% socket shutdown is done asynchronous, the tests need to wait a bit for it to finish. reconf_address(_Config) -> - FUN = fun() -> eradius_client:reconfigure(), timer:sleep(100) end, + FUN = fun() -> eradius_client_mngr:reconfigure(), timer:sleep(100) end, application:set_env(eradius, client_ip, "7.13.23.42"), send(FUN, null, "7.13.23.42"). reconf_ports_30(_Config) -> - FUN = fun() -> gen_server:call(eradius_client, reconfigure), timer:sleep(100) end, + FUN = fun() -> eradius_client_mngr:reconfigure(), timer:sleep(100) end, application:set_env(eradius, client_ports, 30), send(FUN, 30, null). reconf_ports_10(_Config) -> - FUN = fun() -> gen_server:call(eradius_client, reconfigure), timer:sleep(100) end, + FUN = fun() -> eradius_client_mngr:reconfigure(), timer:sleep(100) end, application:set_env(eradius, client_ports, 10), send(FUN, 10, null). @@ -216,9 +216,9 @@ send_request_failover(_Config) -> ?equal(accept, eradius_test_handler:send_request_failover(?BAD_SERVER_IP)), {ok, Timeout} = application:get_env(eradius, unreachable_timeout), timer:sleep(Timeout * 1000), - ?equal([?BAD_SERVER_TUPLE], ets:lookup(eradius_client, ?BAD_SERVER_IP_ETS_KEY)), + ?equal([?BAD_SERVER_TUPLE], eradius_client_mngr:servers(?BAD_SERVER_IP_ETS_KEY)), ok. check_upstream_servers(_Config) -> - ?equal(?RADIUS_SERVERS, ets:tab2list(eradius_client)), + ?equal(?RADIUS_SERVERS, eradius_client_mngr:servers()), ok. diff --git a/test/eradius_metrics_SUITE.erl b/test/eradius_metrics_SUITE.erl index 60cc0fbb..d788d843 100644 --- a/test/eradius_metrics_SUITE.erl +++ b/test/eradius_metrics_SUITE.erl @@ -82,7 +82,7 @@ end_per_suite(_Config) -> ok. init_per_testcase(_, Config) -> - eradius_client:init_server_status_metrics(), + eradius_client_mngr:init_server_status_metrics(), Config. %% tests From 9d3efa527ae44630a986fbf0ecb823ef08cf1604 Mon Sep 17 00:00:00 2001 From: Andreas Schultz <andreas.schultz@travelping.com> Date: Tue, 16 Apr 2024 16:54:42 +0200 Subject: [PATCH 5/8] [client] move request timeout handling to socket process There was no logic in place to remove entries from the pending registry once the timeout hits. The client side timeout would only handle re-transmission, but not the cleanup. Move the timeout logic to the sender and let it also cleanup the pending registry. --- src/eradius_client.erl | 2 +- src/eradius_client_socket.erl | 94 +++++++++++++++++++++++------------ 2 files changed, 62 insertions(+), 34 deletions(-) diff --git a/src/eradius_client.erl b/src/eradius_client.erl index 4ab7a473..ce58debb 100644 --- a/src/eradius_client.erl +++ b/src/eradius_client.erl @@ -248,7 +248,7 @@ send_request_loop(Socket, Peer = {_ServerName, {IP, Port}}, ReqId, Authenticator end, case Result of - {response, ReqId, Response} -> + {ok, Response} -> {ok, Response, Secret, Authenticator}; {error, close} -> {error, socket_down}; diff --git a/src/eradius_client_socket.erl b/src/eradius_client_socket.erl index 22fddf3d..9278119e 100644 --- a/src/eradius_client_socket.erl +++ b/src/eradius_client_socket.erl @@ -13,7 +13,7 @@ %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --record(state, {socket, active_n, pending, mode, counter}). +-record(state, {family, socket, active_n, pending, mode, counter}). %%%========================================================================= %%% API @@ -27,10 +27,8 @@ start_link(Config) -> send_request(Socket, Peer, ReqId, Request, Timeout) -> try - gen_server:call(Socket, {send_request, Peer, ReqId, Request}, Timeout) + gen_server:call(Socket, {send_request, Peer, ReqId, Request, Timeout}, infinity) catch - exit:{timeout, _} -> - {error, timeout}; exit:{noproc, _} -> {error, closed}; {nodedown, _} -> @@ -58,6 +56,7 @@ init([#{family := Family, ip := IP, active_n := ActiveN, {ok, Socket} = gen_udp:open(0, Opts), State = #state{ + family = Family, socket = Socket, active_n = ActiveN, pending = #{}, @@ -65,15 +64,19 @@ init([#{family := Family, ip := IP, active_n := ActiveN, }, {ok, State}. -handle_call({send_request, {IP, Port}, ReqId, Request}, From, - #state{socket = Socket, pending = Pending} = State) -> - case gen_udp:send(Socket, IP, Port, Request) of - ok -> - ReqKey = {IP, Port, ReqId}, - NPending = Pending#{ReqKey => From}, - {noreply, State#state{pending = NPending}}; - {error, Reason} -> - {reply, {error, Reason}, State} +handle_call({send_request, {IP, Port}, ReqId, Request, Timeout}, From, + #state{family = Family, socket = Socket} = State) -> + case send_ip(Family, IP) of + {ok, SendIP} -> + case gen_udp:send(Socket, SendIP, Port, Request) of + ok -> + ReqKey = {SendIP, Port, ReqId}, + {noreply, pending_request(ReqKey, From, Timeout, State)}; + {error, _} = Error -> + {reply, Error, State} + end; + {error, _} = Error -> + {reply, Error, State} end; handle_call(_Request, _From, State) -> @@ -92,27 +95,17 @@ handle_info({udp_passive, _Socket}, #state{socket = Socket, active_n = ActiveN} inet:setopts(Socket, [{active, ActiveN}]), {noreply, State}; -handle_info({udp, Socket, FromIP, FromPort, Request}, - State = #state{socket = Socket, pending = Pending, mode = Mode}) -> - case eradius_lib:decode_request_id(Request) of - {ReqId, Request} -> - case Pending of - #{{FromIP, FromPort, ReqId} := From} -> - gen_server:reply(From, {response, ReqId, Request}), - - flow_control(State), - NPending = maps:remove({FromIP, FromPort, ReqId}, Pending), - NState = State#state{pending = NPending}, - case Mode of - inactive when map_size(NPending) =:= 0 -> - {stop, normal, NState}; - _ -> - {noreply, NState} - end; +handle_info({udp, Socket, FromIP, FromPort, Response}, + State = #state{socket = Socket, mode = Mode}) -> + case eradius_lib:decode_request_id(Response) of + {ReqId, Response} -> + NState = request_done({FromIP, FromPort, ReqId}, {ok, Response}, State), + case Mode of + inactive when map_size(State#state.pending) =:= 0 -> + {stop, normal, NState}; _ -> - %% discard reply because we didn't expect it - flow_control(State), - {noreply, State} + flow_control(NState), + {noreply, NState} end; {bad_pdu, _} -> %% discard reply because it was malformed @@ -120,6 +113,17 @@ handle_info({udp, Socket, FromIP, FromPort, Request}, {noreply, State} end; +handle_info({timeout, TRef, ReqKey}, #state{pending = Pending} = State) -> + NState = + case Pending of + #{ReqKey := {From, TRef}} -> + gen_server:reply(From, {error, timeout}), + State#state{pending = maps:remove(ReqKey, Pending)}; + _ -> + State + end, + {noreply, NState}; + handle_info(_Info, State) -> {noreply, State}. @@ -137,3 +141,27 @@ flow_control(#state{socket = Socket, active_n = once}) -> inet:setopts(Socket, [{active, once}]); flow_control(_) -> ok. + +pending_request(ReqKey, From, Timeout, + #state{pending = Pending} = State) -> + TRef = erlang:start_timer(Timeout, self(), ReqKey), + State#state{pending = Pending#{ReqKey => {From, TRef}}}. + +request_done(ReqKey, Reply, #state{pending = Pending} = State) -> + case Pending of + #{ReqKey := {From, TRef}} -> + gen_server:reply(From, Reply), + erlang:cancel_timer(TRef), + State#state{pending = maps:remove(ReqKey, Pending)}; + _ -> + State + end. + +send_ip(inet, {_, _, _, _} = IP) -> + {ok, IP}; +send_ip(inet6, {_, _, _, _} = IP) -> + {ok, inet:ipv4_mapped_ipv6_address(IP)}; +send_ip(inet6, {_, _, _, _,_, _, _, _} = IP) -> + {ok, IP}; +send_ip(_, _) -> + {error, eafnosupport}. From 09a861e805a37ce420abece9b756fe3f345f6500 Mon Sep 17 00:00:00 2001 From: Andreas Schultz <andreas.schultz@travelping.com> Date: Wed, 17 Apr 2024 12:54:25 +0200 Subject: [PATCH 6/8] [client] add more socket options to the client --- src/eradius_client_socket.erl | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/eradius_client_socket.erl b/src/eradius_client_socket.erl index 9278119e..764f0f98 100644 --- a/src/eradius_client_socket.erl +++ b/src/eradius_client_socket.erl @@ -42,17 +42,8 @@ close(Socket) -> %%% gen_server callbacks %%%=================================================================== -init([#{family := Family, ip := IP, active_n := ActiveN, - recbuf := RecBuf, sndbuf := SndBuf} = _Config]) -> - case IP of - any -> - ExtraOptions = []; - _ when is_tuple(IP) -> - ExtraOptions = [{ip, IP}] - end, - - Opts = [{active, ActiveN}, binary, {recbuf, RecBuf}, {sndbuf, SndBuf}, - Family | ExtraOptions], +init([#{family := Family, active_n := ActiveN} = Config]) -> + Opts = inet_opts(Config, [{active, ActiveN}, binary, Family]), {ok, Socket} = gen_udp:open(0, Opts), State = #state{ @@ -165,3 +156,15 @@ send_ip(inet6, {_, _, _, _,_, _, _, _} = IP) -> {ok, IP}; send_ip(_, _) -> {error, eafnosupport}. + +inet_opts(Config, Opts0) -> + Opts = + maps:to_list( + maps:with([recbuf, sndbuf, ip, + ipv6_v6only, netns, bind_to_device, read_packets], Config)) ++ Opts0, + case Config of + #{inet_backend := Backend} when Backend =:= inet; Backend =:= socket -> + [{inet_backend, Backend} | Opts]; + _ -> + Opts + end. From 349282c34732d0bc4ce2f3d2a399ec43a32d1db8 Mon Sep 17 00:00:00 2001 From: Andreas Schultz <andreas.schultz@travelping.com> Date: Wed, 17 Apr 2024 12:55:21 +0200 Subject: [PATCH 7/8] [ct] prepare for IPv6 support Fully prepare client suite for full IPv6 support and add some minor tweaks to the others to move them closer to IPv6 support. --- test/eradius_client_SUITE.erl | 202 +++++++++++++++++++++------------ test/eradius_config_SUITE.erl | 14 +-- test/eradius_logtest.erl | 24 ++-- test/eradius_metrics_SUITE.erl | 36 +++--- test/eradius_proxy_SUITE.erl | 30 ++--- test/eradius_test_handler.erl | 61 +++++----- test/eradius_test_lib.erl | 53 +++++++++ 7 files changed, 268 insertions(+), 152 deletions(-) create mode 100644 test/eradius_test_lib.erl diff --git a/test/eradius_client_SUITE.erl b/test/eradius_client_SUITE.erl index b30d4e5f..a7a34386 100644 --- a/test/eradius_client_SUITE.erl +++ b/test/eradius_client_SUITE.erl @@ -1,65 +1,81 @@ -%% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> - -%% Permission is hereby granted, free of charge, to any person obtaining a -%% copy of this software and associated documentation files (the "Software"), -%% to deal in the Software without restriction, including without limitation -%% the rights to use, copy, modify, merge, publish, distribute, sublicense, -%% and/or sell copies of the Software, and to permit persons to whom the -%% Software is furnished to do so, subject to the following conditions: - -%% The above copyright notice and this permission notice shall be included in -%% all copies or substantial portions of the Software. - -%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -%% DEALINGS IN THE SOFTWARE. - +%% Copyright (c) 2010-2017, Travelping GmbH <info@travelping.com> +%% +%% SPDX-License-Identifier: MIT +%% -module(eradius_client_SUITE). --compile(export_all). + +-compile([export_all, nowarn_export_all]). + +-behaviour(ct_suite). -include("test/eradius_test.hrl"). +%%%=================================================================== +%%% Defines +%%%=================================================================== + -define(HUT_SOCKET, eradius_client_socket). --define(BAD_SERVER_IP, {eradius_test_handler:localhost(ip), 1820, "secret"}). +-define(BAD_SERVER_IP(Family), + {eradius_test_lib:localhost(Family, ip), 1820, "secret"}). -define(BAD_SERVER_INITIAL_RETRIES, 3). --define(BAD_SERVER_TUPLE_INITIAL, {{eradius_test_handler:localhost(tuple), 1820}, - ?BAD_SERVER_INITIAL_RETRIES, - ?BAD_SERVER_INITIAL_RETRIES}). --define(BAD_SERVER_TUPLE, {{eradius_test_handler:localhost(tuple), 1820}, - ?BAD_SERVER_INITIAL_RETRIES - 1, - ?BAD_SERVER_INITIAL_RETRIES}). --define(BAD_SERVER_IP_ETS_KEY, {eradius_test_handler:localhost(tuple), 1820}). +-define(BAD_SERVER_TUPLE_INITIAL(Family), + {{eradius_test_lib:localhost(Family, tuple), 1820}, + ?BAD_SERVER_INITIAL_RETRIES, + ?BAD_SERVER_INITIAL_RETRIES}). +-define(BAD_SERVER_TUPLE(Family), + {{eradius_test_lib:localhost(Family, tuple), 1820}, + ?BAD_SERVER_INITIAL_RETRIES - 1, + ?BAD_SERVER_INITIAL_RETRIES}). +-define(BAD_SERVER_IP_ETS_KEY(Family), + {eradius_test_lib:localhost(Family, tuple), 1820}). -define(GOOD_SERVER_INITIAL_RETRIES, 3). --define(GOOD_SERVER_TUPLE, {{eradius_test_handler:localhost(tuple), 1812}, - ?GOOD_SERVER_INITIAL_RETRIES, - ?GOOD_SERVER_INITIAL_RETRIES}). --define(GOOD_SERVER_2_TUPLE, {{{127, 0, 0, 2}, 1813}, - ?GOOD_SERVER_INITIAL_RETRIES, - ?GOOD_SERVER_INITIAL_RETRIES}). - --define(RADIUS_SERVERS, [?GOOD_SERVER_TUPLE, - ?BAD_SERVER_TUPLE_INITIAL, - ?GOOD_SERVER_2_TUPLE]). +-define(GOOD_SERVER_TUPLE(Family), + {{eradius_test_lib:localhost(Family, tuple), 1812}, + ?GOOD_SERVER_INITIAL_RETRIES, + ?GOOD_SERVER_INITIAL_RETRIES}). +-define(GOOD_SERVER_2_TUPLE(Family), + {{eradius_test_lib:badhost(Family), 1813}, + ?GOOD_SERVER_INITIAL_RETRIES, + ?GOOD_SERVER_INITIAL_RETRIES}). + +-define(RADIUS_SERVERS(Family), + [?GOOD_SERVER_TUPLE(Family), + ?BAD_SERVER_TUPLE_INITIAL(Family), + ?GOOD_SERVER_2_TUPLE(Family)]). + +%%%=================================================================== +%%% Setup +%%%=================================================================== -spec all() -> [ct_suite:ct_test_def(), ...]. -all() -> [ - send_request, - wanna_send, - reconf_address, - wanna_send, - reconf_ports_30, - wanna_send, - reconf_ports_10, - wanna_send, - send_request_failover, - check_upstream_servers - ]. +all() -> + [{group, ipv4}, + {group, ipv4_mapped_ipv6}, + {group, ipv6}]. + +common() -> + [send_request, + wanna_send, + reconf_address, + wanna_send, + reconf_ports_30, + wanna_send, + reconf_ports_10, + wanna_send, + send_request_failover, + check_upstream_servers + ]. + +-spec groups() -> [ct_suite:ct_group_def(), ...]. +groups() -> + SocketGroups = [{group, inet}, {group, socket}], + [{inet, [], common()}, + {socket, [], common()}, + {ipv4, [], SocketGroups}, + {ipv4_mapped_ipv6, [], SocketGroups}, + {ipv6, [], SocketGroups}]. init_per_suite(Config) -> {ok, _} = application:ensure_all_started(eradius), @@ -70,17 +86,48 @@ end_per_suite(_Config) -> application:stop(eradius), ok. +init_per_group(inet, Config) -> + [{inet_backend, inet} | Config]; +init_per_group(socket, Config) -> + [{inet_backend, socket} | Config]; +init_per_group(ipv6 = Group, Config) -> + {skip, "no IPv6 server support (yet)"}; + %% case eradius_test_lib:has_ipv6_test_config() of + %% true -> + %% [{family, Group} | Config]; + %% _ -> + %% {skip, "IPv6 test IPs not configured"} + %% end; +init_per_group(ipv4_mapped_ipv6 = Group, Config) -> + case eradius_test_lib:has_ipv6_test_config() of + true -> + [{family, Group} | Config]; + _ -> + {skip, "IPv6 test IPs not configured"} + end; +init_per_group(ipv4 = Group, Config) -> + [{family, Group} | Config]. + +end_per_group(_Group, _Config) -> + application:stop(eradius), + ok. + +start_handler(Config) -> + Backend = proplists:get_value(inet_backend, Config, inet), + Family = proplists:get_value(family, Config), + eradius_test_handler:start(Backend, Family). + init_per_testcase(send_request, Config) -> application:stop(eradius), - eradius_test_handler:start(), + start_handler(Config), Config; init_per_testcase(send_request_failover, Config) -> application:stop(eradius), - eradius_test_handler:start(), + start_handler(Config), Config; init_per_testcase(check_upstream_servers, Config) -> application:stop(eradius), - eradius_test_handler:start(), + start_handler(Config), Config; init_per_testcase(_Test, Config) -> Config. @@ -157,6 +204,7 @@ check(#{sockets := OS, no_ports := _OP, idcounters := _OC, socket_id := {_, OA}} test(PA == NA, "Adress not configured") and case NA of OA -> + ct:pal("NP: ~p, NC: ~p", [NP, NC]), {_, Rest} = split(NP, array:to_list(OS)), test(P == NP,"Ports not configured") and test(maps:fold( fun(_Peer, {NextPortIdx, _NextReqId}, Akk) -> @@ -173,11 +221,16 @@ check(#{sockets := OS, no_ports := _OP, idcounters := _OC, socket_id := {_, OA}} %% TESTS -send_request(_Config) -> - ?equal(accept, eradius_test_handler:send_request(eradius_test_handler:localhost(tuple))), - ?equal(accept, eradius_test_handler:send_request(eradius_test_handler:localhost(ip))), - ?equal(accept, eradius_test_handler:send_request(eradius_test_handler:localhost(string))), - ?equal(accept, eradius_test_handler:send_request(eradius_test_handler:localhost(binary))), +send_request(Config) -> + Family = proplists:get_value(family, Config), + ?equal(accept, + eradius_test_handler:send_request(eradius_test_lib:localhost(Family, tuple))), + ?equal(accept, + eradius_test_handler:send_request(eradius_test_lib:localhost(Family, ip))), + ?equal(accept, + eradius_test_handler:send_request(eradius_test_lib:localhost(Family, string))), + ?equal(accept, + eradius_test_handler:send_request(eradius_test_lib:localhost(Family, binary))), ok. send(FUN, Ports, Address) -> @@ -198,27 +251,36 @@ wanna_send(_Config) -> %% socket shutdown is done asynchronous, the tests need to wait a bit for it to finish. reconf_address(_Config) -> - FUN = fun() -> eradius_client_mngr:reconfigure(), timer:sleep(100) end, - application:set_env(eradius, client_ip, "7.13.23.42"), + FUN = fun() -> + eradius_client_mngr:reconfigure(#{ip => "7.13.23.42"}), + timer:sleep(100) + end, send(FUN, null, "7.13.23.42"). reconf_ports_30(_Config) -> - FUN = fun() -> eradius_client_mngr:reconfigure(), timer:sleep(100) end, - application:set_env(eradius, client_ports, 30), + FUN = fun() -> + eradius_client_mngr:reconfigure(#{no_ports => 30}), + timer:sleep(100) + end, send(FUN, 30, null). reconf_ports_10(_Config) -> - FUN = fun() -> eradius_client_mngr:reconfigure(), timer:sleep(100) end, - application:set_env(eradius, client_ports, 10), + FUN = fun() -> + eradius_client_mngr:reconfigure(#{no_ports => 10}), + timer:sleep(100) + end, send(FUN, 10, null). -send_request_failover(_Config) -> - ?equal(accept, eradius_test_handler:send_request_failover(?BAD_SERVER_IP)), +send_request_failover(Config) -> + Family = proplists:get_value(family, Config), + ?equal(accept, eradius_test_handler:send_request_failover(?BAD_SERVER_IP(Family))), {ok, Timeout} = application:get_env(eradius, unreachable_timeout), timer:sleep(Timeout * 1000), - ?equal([?BAD_SERVER_TUPLE], eradius_client_mngr:servers(?BAD_SERVER_IP_ETS_KEY)), + ?equal([?BAD_SERVER_TUPLE(Family)], + eradius_client_mngr:servers(?BAD_SERVER_IP_ETS_KEY(Family))), ok. -check_upstream_servers(_Config) -> - ?equal(?RADIUS_SERVERS, eradius_client_mngr:servers()), +check_upstream_servers(Config) -> + Family = proplists:get_value(family, Config), + ?equal(lists:keysort(1, ?RADIUS_SERVERS(Family)), eradius_client_mngr:servers()), ok. diff --git a/test/eradius_config_SUITE.erl b/test/eradius_config_SUITE.erl index 2f35e1e6..07dd2cba 100644 --- a/test/eradius_config_SUITE.erl +++ b/test/eradius_config_SUITE.erl @@ -40,7 +40,7 @@ config_1(_Config) -> Conf = [{session_nodes, ['node1@host1', 'node2@host2']}, {radius_callback, ?MODULE}, {servers, [ - {root, {eradius_test_handler:localhost(ip), [1812, 1813]}} + {root, {eradius_test_lib:localhost(ip), [1812, 1813]}} ]}, {root, [ { {"NAS1", [arg1, arg2]}, @@ -49,7 +49,7 @@ config_1(_Config) -> [{{10, 18, 14, 3}, <<"secret2">>, [{nas_id, <<"name">>}]}]} ]}], ok = apply_conf(Conf), - LocalHost = eradius_test_handler:localhost(tuple), + LocalHost = eradius_test_lib:localhost(tuple), ?match({ok, {?MODULE,[arg1,arg2]}, #nas_prop{ server_ip = LocalHost, @@ -83,7 +83,7 @@ config_2(_Config) -> ] }, {servers, [ - {root, {eradius_test_handler:localhost(ip), [1812, 1813]}} + {root, {eradius_test_lib:localhost(ip), [1812, 1813]}} ]}, {root, [ { {handler1, "NAS1", [arg1, arg2]}, @@ -93,7 +93,7 @@ config_2(_Config) -> [ {"10.18.14.2", <<"secret2">>, [{group, "NodeGroup2"}]} ] } ]}], ok = apply_conf(Conf), - LocalHost = eradius_test_handler:localhost(tuple), + LocalHost = eradius_test_lib:localhost(tuple), ?match({ok, {handler1,[arg1,arg2]}, #nas_prop{ server_ip = LocalHost, @@ -146,7 +146,7 @@ test_validate_server(_Config) -> ok. config_nas_removing(_Config) -> - Conf = [{servers, [ {root, {eradius_test_handler:localhost(ip), [1812, 1813]}} ]}, + Conf = [{servers, [ {root, {eradius_test_lib:localhost(ip), [1812, 1813]}} ]}, {root, [ ]}], ok = apply_conf(Conf), ?match([], ets:tab2list(eradius_nas_tab)), @@ -159,14 +159,14 @@ config_with_ranges(_Config) -> ] }, {servers, [ - {root, {eradius_test_handler:localhost(ip), [1812, 1813]}} + {root, {eradius_test_lib:localhost(ip), [1812, 1813]}} ]}, {root, [ { {handler, "NAS", []}, [ {"10.18.14.2/30", <<"secret2">>, [{group, "NodeGroup"}]} ] } ]}], ok = apply_conf(Conf), - LocalHost = eradius_test_handler:localhost(tuple), + LocalHost = eradius_test_lib:localhost(tuple), ?match({ok, {handler,[]}, #nas_prop{ server_ip = LocalHost, diff --git a/test/eradius_logtest.erl b/test/eradius_logtest.erl index aa410aad..125422ab 100644 --- a/test/eradius_logtest.erl +++ b/test/eradius_logtest.erl @@ -41,25 +41,25 @@ start() -> application:load(eradius), - ProxyConfig = [{default_route, {eradius_test_handler:localhost(tuple), 1813, ?SECRET}}, + ProxyConfig = [{default_route, {eradius_test_lib:localhost(tuple), 1813, ?SECRET}}, {options, [{type, realm}, {strip, true}, {separator, "@"}]}, - {routes, [{"test", {eradius_test_handler:localhost(tuple), 1815, ?SECRET3}} + {routes, [{"test", {eradius_test_lib:localhost(tuple), 1815, ?SECRET3}} ]} ], Config = [{radius_callback, eradius_logtest}, - {servers, [{root, {eradius_test_handler:localhost(ip), [1812, 1813]}}, - {test, {eradius_test_handler:localhost(ip), [1815]}}, - {proxy, {eradius_test_handler:localhost(ip), [11812, 11813]}} + {servers, [{root, {eradius_test_lib:localhost(ip), [1812, 1813]}}, + {test, {eradius_test_lib:localhost(ip), [1815]}}, + {proxy, {eradius_test_lib:localhost(ip), [11812, 11813]}} ]}, {session_nodes, [node()]}, {root, [ { {eradius_logtest, "root", [] }, [{"127.0.0.1/24", ?SECRET, [{nas_id, <<"Test_Nas_Id">>}]}] } ]}, {test, [ - { {eradius_logtest, "test", [] }, [{eradius_test_handler:localhost(ip), ?SECRET3, [{nas_id, <<"Test_Nas_Id_test">>}]}] } + { {eradius_logtest, "test", [] }, [{eradius_test_lib:localhost(ip), ?SECRET3, [{nas_id, <<"Test_Nas_Id_test">>}]}] } ]}, {proxy, [ - { {eradius_proxy, "proxy", ProxyConfig }, [{eradius_test_handler:localhost(ip), ?SECRET2, [{nas_id, <<"Test_Nas_proxy">>}]}] } + { {eradius_proxy, "proxy", ProxyConfig }, [{eradius_test_lib:localhost(ip), ?SECRET2, [{nas_id, <<"Test_Nas_proxy">>}]}] } ]} ], [application:set_env(eradius, Key, Value) || {Key, Value} <- Config], @@ -97,20 +97,20 @@ test_client() -> test_client(Command) -> eradius_dict:load_tables([dictionary, dictionary_3gpp]), Request = eradius_lib:set_attributes(#radius_request{cmd = Command, msg_hmac = true}, attrs("user")), - send_request(eradius_test_handler:localhost(tuple), 1813, ?SECRET, Request). + send_request(eradius_test_lib:localhost(tuple), 1813, ?SECRET, Request). test_proxy() -> test_proxy(request). test_proxy(Command) -> eradius_dict:load_tables([dictionary, dictionary_3gpp]), - send_request(eradius_test_handler:localhost(tuple), 11813, ?SECRET2, #radius_request{cmd = Command}), + send_request(eradius_test_lib:localhost(tuple), 11813, ?SECRET2, #radius_request{cmd = Command}), Request = eradius_lib:set_attributes(#radius_request{cmd = Command, msg_hmac = true}, attrs("proxy_test")), - send_request(eradius_test_handler:localhost(tuple), 11813, ?SECRET2, Request), + send_request(eradius_test_lib:localhost(tuple), 11813, ?SECRET2, Request), Request2 = eradius_lib:set_attributes(#radius_request{cmd = Command, msg_hmac = true}, attrs("user@test")), - send_request(eradius_test_handler:localhost(tuple), 11813, ?SECRET2, Request2), + send_request(eradius_test_lib:localhost(tuple), 11813, ?SECRET2, Request2), Request3 = eradius_lib:set_attributes(#radius_request{cmd = Command, msg_hmac = true}, attrs("user@domain@test")), - send_request(eradius_test_handler:localhost(tuple), 11813, ?SECRET2, Request3). + send_request(eradius_test_lib:localhost(tuple), 11813, ?SECRET2, Request3). send_request(Ip, Port, Secret, Request) -> case eradius_client:send_request({Ip, Port, Secret}, Request) of diff --git a/test/eradius_metrics_SUITE.erl b/test/eradius_metrics_SUITE.erl index d788d843..a94dff65 100644 --- a/test/eradius_metrics_SUITE.erl +++ b/test/eradius_metrics_SUITE.erl @@ -31,7 +31,7 @@ -define(ATTRS_BAD, [{?NAS_Identifier, "bad"}]). -define(ATTRS_ERROR, [{?NAS_Identifier, "error"}]). -define(ATTRS_AS_RECORD, [{#attribute{id = ?RStatus_Type}, ?RStatus_Type_Start}]). --define(LOCALHOST, eradius_test_handler:localhost(atom)). +-define(LOCALHOST, eradius_test_lib:localhost(atom)). %% test callbacks all() -> [good_requests, bad_requests, error_requests, request_with_attrs_as_record]. @@ -39,15 +39,15 @@ all() -> [good_requests, bad_requests, error_requests, request_with_attrs_as_rec init_per_suite(Config) -> application:load(eradius), EradiusConfig = [{radius_callback, ?MODULE}, - {servers, [{good, {eradius_test_handler:localhost(ip), [1812]}}, %% for 'positive' responses, e.g. access accepts - {bad, {eradius_test_handler:localhost(ip), [1813]}}, %% for 'negative' responses, e.g. coa naks - {error, {eradius_test_handler:localhost(ip), [1814]}} %% here things go wrong, e.g. duplicate requests + {servers, [{good, {eradius_test_lib:localhost(ip), [1812]}}, %% for 'positive' responses, e.g. access accepts + {bad, {eradius_test_lib:localhost(ip), [1813]}}, %% for 'negative' responses, e.g. coa naks + {error, {eradius_test_lib:localhost(ip), [1814]}} %% here things go wrong, e.g. duplicate requests ]}, {session_nodes, [node()]}, {servers_pool, - [{test_pool, [{eradius_test_handler:localhost(tuple), 1814, ?SECRET}, - {eradius_test_handler:localhost(tuple), 1813, ?SECRET}, - {eradius_test_handler:localhost(tuple), 1812, ?SECRET}]}] + [{test_pool, [{eradius_test_lib:localhost(tuple), 1814, ?SECRET}, + {eradius_test_lib:localhost(tuple), 1813, ?SECRET}, + {eradius_test_lib:localhost(tuple), 1812, ?SECRET}]}] }, {good, [ { {"good", [] }, [{"127.0.0.2", ?SECRET, [{nas_id, <<"good_nas">>}]}] } @@ -107,12 +107,12 @@ error_requests(_Config) -> check_single_request(error, request, access, access_accept). request_with_attrs_as_record(_Config) -> - ok = send_request(accreq, eradius_test_handler:localhost(tuple), 1812, ?ATTRS_AS_RECORD, [{server_name, good}, {client_name, test_records}]), + ok = send_request(accreq, eradius_test_lib:localhost(tuple), 1812, ?ATTRS_AS_RECORD, [{server_name, good}, {client_name, test_records}]), ok = check_metric(accreq, client_accounting_requests_total, [{server_name, good}, {client_name, test_records}, {acct_type, start}], 1). %% helpers check_single_request(good, EradiusRequestType, _RequestType, _ResponseType) -> - ok = send_request(EradiusRequestType, eradius_test_handler:localhost(tuple), 1812, ?ATTRS_GOOD, [{server_name, good}, {client_name, test}]), + ok = send_request(EradiusRequestType, eradius_test_lib:localhost(tuple), 1812, ?ATTRS_GOOD, [{server_name, good}, {client_name, test}]), ok = check_metric(client_access_requests_total, [{server_name, good}], 1), ok = check_metric_multi(EradiusRequestType, client_accounting_requests_total, [{server_name, good}], 1), ok = check_metric_multi({bad_type, EradiusRequestType}, client_accounting_requests_total, [{server_name, good}, {acct_type, bad_type}], 0), @@ -122,18 +122,18 @@ check_single_request(good, EradiusRequestType, _RequestType, _ResponseType) -> ok = check_metric(client_accept_responses_total, [{server_name, good}], 1), ok = check_metric(accept_responses_total, [{server_name, good}], 1), ok = check_metric(access_requests_total, [{server_name, good}], 1), - ok = check_metric(server_status, true, [eradius_test_handler:localhost(tuple), 1812]); + ok = check_metric(server_status, true, [eradius_test_lib:localhost(tuple), 1812]); check_single_request(bad, EradiusRequestType, _RequestType, _ResponseType) -> - ok = send_request(EradiusRequestType, eradius_test_handler:localhost(tuple), 1813, ?ATTRS_BAD, [{server_name, bad}, {client_name, test}]), + ok = send_request(EradiusRequestType, eradius_test_lib:localhost(tuple), 1813, ?ATTRS_BAD, [{server_name, bad}, {client_name, test}]), ok = check_metric(client_access_requests_total, [{server_name, bad}], 1), ok = check_metric(client_reject_responses_total, [{server_name, bad}], 1), ok = check_metric(access_requests_total, [{server_name, bad}], 1), ok = check_metric(reject_responses_total, [{server_name, bad}], 1), - ok = check_metric(server_status, true, [eradius_test_handler:localhost(tuple), 1813]); + ok = check_metric(server_status, true, [eradius_test_lib:localhost(tuple), 1813]); check_single_request(error, EradiusRequestType, _RequestType, _ResponseType) -> - ok = send_request(EradiusRequestType, eradius_test_handler:localhost(tuple), 1814, ?ATTRS_ERROR, + ok = send_request(EradiusRequestType, eradius_test_lib:localhost(tuple), 1814, ?ATTRS_ERROR, [{server_name, error}, {client_name, test}, {timeout, 1000}, - {failover, [{eradius_test_handler:localhost(tuple), 1812, ?SECRET}]}]), + {failover, [{eradius_test_lib:localhost(tuple), 1812, ?SECRET}]}]), ok = check_metric(client_access_requests_total, [{server_name, error}], 1), ok = check_metric(client_retransmissions_total, [{server_name, error}], 1), ok = check_metric(access_requests_total, [{server_name, error}], 1), @@ -141,10 +141,10 @@ check_single_request(error, EradiusRequestType, _RequestType, _ResponseType) -> ok = check_metric(duplicated_requests_total, [{server_name, error}], 1), ok = check_metric(client_requests_total, [{server_name, error}], 1), ok = check_metric(requests_total, [{server_name, error}], 2), - ok = check_metric(server_status, false, [eradius_test_handler:localhost(tuple), 1812]), - ok = check_metric(server_status, false, [eradius_test_handler:localhost(tuple), 1813]), - ok = check_metric(server_status, true, [eradius_test_handler:localhost(tuple), 1814]), - ok = check_metric(server_status, undefined, [eradius_test_handler:localhost(tuple), 1815]). + ok = check_metric(server_status, false, [eradius_test_lib:localhost(tuple), 1812]), + ok = check_metric(server_status, false, [eradius_test_lib:localhost(tuple), 1813]), + ok = check_metric(server_status, true, [eradius_test_lib:localhost(tuple), 1814]), + ok = check_metric(server_status, undefined, [eradius_test_lib:localhost(tuple), 1815]). check_total_requests(good, N) -> diff --git a/test/eradius_proxy_SUITE.erl b/test/eradius_proxy_SUITE.erl index b76ee2f1..85c6a2e2 100644 --- a/test/eradius_proxy_SUITE.erl +++ b/test/eradius_proxy_SUITE.erl @@ -34,10 +34,10 @@ all() -> [ ]. resolve_routes_test(_) -> - DefaultRoute = {eradius_test_handler:localhost(tuple), 1813, <<"secret">>}, - Prod = {eradius_test_handler:localhost(tuple), 1812, <<"prod">>}, - Test = {eradius_test_handler:localhost(tuple), 11813, <<"test">>}, - Dev = {eradius_test_handler:localhost(tuple), 11814, <<"dev">>}, + DefaultRoute = {eradius_test_lib:localhost(tuple), 1813, <<"secret">>}, + Prod = {eradius_test_lib:localhost(tuple), 1812, <<"prod">>}, + Test = {eradius_test_lib:localhost(tuple), 11813, <<"test">>}, + Dev = {eradius_test_lib:localhost(tuple), 11814, <<"dev">>}, {ok, R1} = re:compile("prod"), {ok, R2} = re:compile("test"), {ok, R3} = re:compile("^dev_.*"), @@ -66,41 +66,41 @@ resolve_routes_test(_) -> ok. validate_arguments_test(_) -> - GoodConfig = [{default_route, {eradius_test_handler:localhost(tuple), 1813, <<"secret">>}}, + GoodConfig = [{default_route, {eradius_test_lib:localhost(tuple), 1813, <<"secret">>}}, {options, [{type, realm}, {strip, true}, {separator, "@"}]}, - {routes, [{"test_1", {eradius_test_handler:localhost(tuple), 1815, <<"secret1">>}, [{pool, test_pool}]}, + {routes, [{"test_1", {eradius_test_lib:localhost(tuple), 1815, <<"secret1">>}, [{pool, test_pool}]}, {"test_2", {<<"localhost">>, 1816, <<"secret2">>}} ]} ], - GoodOldConfig = [{default_route, {eradius_test_handler:localhost(tuple), 1813, <<"secret">>}, test_pool}, + GoodOldConfig = [{default_route, {eradius_test_lib:localhost(tuple), 1813, <<"secret">>}, test_pool}, {options, [{type, realm}, {strip, true}, {separator, "@"}]}, - {routes, [{"test_1", {eradius_test_handler:localhost(tuple), 1815, <<"secret1">>}, [{pool, test_pool}]}, + {routes, [{"test_1", {eradius_test_lib:localhost(tuple), 1815, <<"secret1">>}, [{pool, test_pool}]}, {"test_2", {<<"localhost">>, 1816, <<"secret2">>}} ]} ], - BadConfig = [{default_route, {eradius_test_handler:localhost(tuple), 1813, <<"secret">>}}, + BadConfig = [{default_route, {eradius_test_lib:localhost(tuple), 1813, <<"secret">>}}, {options, [{type, abc}]} ], - BadConfig1 = [{default_route, {eradius_test_handler:localhost(tuple), 0, <<"secret">>}}], + BadConfig1 = [{default_route, {eradius_test_lib:localhost(tuple), 0, <<"secret">>}}], BadConfig2 = [{default_route, {abc, 123, <<"secret">>}}], - BadConfig3 = [{default_route, {eradius_test_handler:localhost(tuple), 1813, <<"secret">>}}, + BadConfig3 = [{default_route, {eradius_test_lib:localhost(tuple), 1813, <<"secret">>}}, {options, [{type, realm}, {strip, true}, {separator, "@"}]}, {routes, [{"test_1", {wrong_ip, 1815, <<"secret1">>}}, {"test_2", {<<"localhost">>, 1816, <<"secret2">>}} ]}], - BadConfig4 = [{default_route, {eradius_test_handler:localhost(tuple), 1813, <<"secret">>}}, + BadConfig4 = [{default_route, {eradius_test_lib:localhost(tuple), 1813, <<"secret">>}}, {options, [{type, realm}, {strip, true}, {separator, "@"}, {timeout, "wrong"}]}, {routes, [{"test", {wrong_ip, 1815, <<"secret1">>}}, {"test_2", {<<"localhost">>, 1816, <<"secret2">>}} ]}], - BadConfig5 = [{default_route, {eradius_test_handler:localhost(tuple), 1813, <<"secret">>}}, + BadConfig5 = [{default_route, {eradius_test_lib:localhost(tuple), 1813, <<"secret">>}}, {options, [{type, realm}, {strip, true}, {separator, "@"}, {retries, "wrong"}]}, {routes, [{"test", {wrong_ip, 1815, <<"secret1">>}}, {"test_2", {"localhost", 1816, <<"secret2">>}} ]}], - BadConfig6 = [{default_route, {eradius_test_handler:localhost(tuple), 1813, <<"secret">>, [{pool, "wrong_pool"}]}}], - BadConfig7 = [{default_route, {eradius_test_handler:localhost(tuple), 1813, <<"secret">>}}, + BadConfig6 = [{default_route, {eradius_test_lib:localhost(tuple), 1813, <<"secret">>, [{pool, "wrong_pool"}]}}], + BadConfig7 = [{default_route, {eradius_test_lib:localhost(tuple), 1813, <<"secret">>}}, {routes, [{"test", {wrong_ip, 1815, <<"secret1">>}, [{pool, "wrong_pool"}]}]}], {Result, ConfigData} = eradius_proxy:validate_arguments(GoodConfig), ?equal(true, Result), diff --git a/test/eradius_test_handler.erl b/test/eradius_test_handler.erl index 20a8c3db..7427ac86 100644 --- a/test/eradius_test_handler.erl +++ b/test/eradius_test_handler.erl @@ -2,26 +2,46 @@ -behaviour(eradius_server). --export([start/0, stop/0, send_request/1, send_request_failover/1, radius_request/3]). --export([localhost/1]). +-export([start/0, start/2, stop/0, send_request/1, send_request_failover/1, radius_request/3]). -include("include/eradius_lib.hrl"). start() -> + start(inet, ipv4). + +start(Backend, Family) -> application:load(eradius), application:set_env(eradius, radius_callback, ?MODULE), - application:set_env(eradius, client_ip, localhost(tuple)), + %% application:set_env(eradius, client_ip, eradius_test_lib:localhost(tuple)), application:set_env(eradius, session_nodes, local), - application:set_env(eradius, one, [{{"ONE", []}, [{localhost(ip), "secret"}]}]), - application:set_env(eradius, two, [{{"TWO", [{default_route, {{127, 0, 0, 2}, 1813, <<"secret">>}}]}, - [{localhost(ip), "secret"}]}]), - application:set_env(eradius, servers, [{one, {localhost(ip), [1812]}}, - {two, {localhost(ip), [1813]}}]), + application:set_env(eradius, one, + [{{"ONE", []}, [{eradius_test_lib:localhost(ip), "secret"}]}]), + application:set_env(eradius, two, + [{{"TWO", [{default_route, {{127, 0, 0, 2}, 1813, <<"secret">>}}]}, + [{eradius_test_lib:localhost(ip), "secret"}]}]), + application:set_env(eradius, servers, + [{one, {eradius_test_lib:localhost(ip), [1812]}}, + {two, {eradius_test_lib:localhost(ip), [1813]}}]), application:set_env(eradius, unreachable_timeout, 2), - application:set_env(eradius, servers_pool, [{test_pool, [{localhost(tuple), 1812, "secret"}, - %% fake upstream server for fail-over - {localhost(string), 1820, "secret"}]}]), + application:set_env(eradius, servers_pool, + [{test_pool, + [{eradius_test_lib:localhost(tuple), 1812, "secret"}, + %% fake upstream server for fail-over + {eradius_test_lib:localhost(string), 1820, "secret"}]}]), application:ensure_all_started(eradius), + + ClientConfig = + #{inet_backend => Backend, + family => eradius_test_lib:inet_family(Family), + ip => eradius_test_lib:localhost(Family, tuple), + servers => [{one, {eradius_test_lib:localhost(Family, ip), [1812]}}, + {two, {eradius_test_lib:localhost(Family, ip), [1813]}}], + servers_pool => + [{test_pool, [{eradius_test_lib:localhost(Family, tuple), 1812, "secret"}, + %% fake upstream server for fail-over + {eradius_test_lib:localhost(Family, string), 1820, "secret"}]}] + }, + eradius_client_mngr:reconfigure(ClientConfig), eradius:modules_ready([?MODULE]). stop() -> @@ -45,22 +65,3 @@ send_request_failover(Server) -> radius_request(#radius_request{cmd = request}, _Nasprop, _Args) -> {reply, #radius_request{cmd = accept}}. - -%% travis is stupid, it includes localhost twice with -%% different IPs in /etc/hosts. This will cause a list -%% of IP to be returned from inet:gethostbyname and that -%% triggers the load balancing in eradius_client. -localhost(string) -> - case os:getenv("TRAVIS") of - false -> "localhost"; - _ -> "ip4-loopback" - end; -localhost(binary) -> - list_to_binary(localhost(string)); -localhost(tuple) -> - {ok, IP} = inet:getaddr(localhost(string), inet), - IP; -localhost(ip) -> - inet:ntoa(localhost(tuple)); -localhost(atom) -> - list_to_atom(localhost(ip)). diff --git a/test/eradius_test_lib.erl b/test/eradius_test_lib.erl new file mode 100644 index 00000000..bde63471 --- /dev/null +++ b/test/eradius_test_lib.erl @@ -0,0 +1,53 @@ +%% Copyright (c) 2010-2017, Travelping GmbH <info@travelping.com> +%% +%% SPDX-License-Identifier: MIT +%% +-module(eradius_test_lib). + +-compile([export_all, nowarn_export_all]). + +%%%=================================================================== +%%% Helper functions +%%%=================================================================== + +has_ipv6_test_config() -> + try + {ok, IfList} = inet:getifaddrs(), + Lo = proplists:get_value("lo", IfList), + V6 = [X || {addr, X = {0,0,0,0,0,0,0,1}} <- Lo], + ct:pal("V6: ~p", [V6]), + length(V6) > 0 + catch + _:_ -> + false + end. + +inet_family(ipv4) -> inet; +inet_family(ipv6) -> inet6; +inet_family(ipv4_mapped_ipv6) -> inet6. + +badhost(Family) + when Family =:= ipv4; Family =:= ipv4_mapped_ipv6 -> + {127, 0, 0, 2}; +badhost(ipv6) -> + {0, 0, 0, 0, 0, 0, 0, 2}. + +localhost(Type) -> + localhost(ipv4, Type). + +localhost(Family, string) when Family =:= ipv4; Family =:= ipv4_mapped_ipv6 -> + "ip4-loopback"; +localhost(ipv6, string) -> + "ip6-loopback"; +localhost(Family, binary) -> + list_to_binary(localhost(Family, string)); +localhost(Family, tuple) when Family =:= ipv4; Family =:= ipv4_mapped_ipv6 -> + {ok, IP} = inet:getaddr(localhost(ipv4, string), inet), + IP; +localhost(ipv6, tuple) -> + {ok, IP} = inet:getaddr(localhost(ipv6, string), inet6), + IP; +localhost(Family, ip) -> + inet:ntoa(localhost(Family, tuple)); +localhost(Family, atom) -> + list_to_atom(localhost(Family, ip)). From 1ff6c008f1840cfdd5af353e336f525ddc15225b Mon Sep 17 00:00:00 2001 From: Andreas Schultz <andreas.schultz@travelping.com> Date: Thu, 18 Apr 2024 14:07:56 +0200 Subject: [PATCH 8/8] rewrite eradius v3 rewrite of eradius, major changes: * clients and servers are now started and configured through APIs, not app env settings any more * IPv6 support * supports multiple server and client instances * metrics are optional and callback based (allows the easy use of other metrics frameworks) * distributed handlers are no longer support, use erpc to replicate in use case specific code if needed. * removed proxy support (use freeradius or similar instead) --- .github/workflows/hex.yaml | 8 +- .github/workflows/main.yml | 22 +- README.md | 334 +------- .../eradius_prometheus_collector/rebar.config | 3 - .../src/eradius_prometheus_collector.app.src | 12 - .../src/eradius_prometheus_collector.erl | 336 -------- dicts_compiler.erl | 2 +- include/eradius_lib.hrl | 31 +- rebar.config | 36 +- src/eradius.app.src | 60 +- src/eradius.erl | 70 +- src/eradius_auth.erl | 62 +- src/eradius_client.erl | 459 +++-------- src/eradius_client_mngr.erl | 516 +++++++----- src/eradius_client_socket.erl | 43 +- src/eradius_client_socket_sup.erl | 15 +- src/eradius_client_sup.erl | 41 +- src/eradius_client_top_sup.erl | 60 ++ src/eradius_config.erl | 401 ---------- src/eradius_counter.erl | 301 ------- src/eradius_counter_aggregator.erl | 162 ---- src/eradius_dict.erl | 56 +- src/eradius_eap_packet.erl | 27 +- src/eradius_lib.erl | 557 +------------ src/eradius_log.erl | 206 ++--- src/eradius_metrics_prometheus.erl | 575 ++++++++++++++ src/eradius_node_mon.erl | 186 ----- src/eradius_proxy.erl | 334 -------- src/eradius_req.erl | 732 +++++++++++++++++ src/eradius_server.erl | 746 ++++++++---------- src/eradius_server_mon.erl | 169 ---- src/eradius_server_sup.erl | 23 +- src/eradius_server_top_sup.erl | 27 - src/eradius_sup.erl | 34 +- test/eradius_client_SUITE.erl | 118 +-- test/eradius_config_SUITE.erl | 257 ------ test/eradius_lib_SUITE.erl | 79 +- test/eradius_logtest.erl | 134 ---- test/eradius_metrics_SUITE.erl | 292 ++++--- test/eradius_proxy_SUITE.erl | 166 ---- test/eradius_test_handler.erl | 100 ++- test/eradius_test_lib.erl | 36 +- 42 files changed, 2891 insertions(+), 4937 deletions(-) delete mode 100644 applications/eradius_prometheus_collector/rebar.config delete mode 100644 applications/eradius_prometheus_collector/src/eradius_prometheus_collector.app.src delete mode 100644 applications/eradius_prometheus_collector/src/eradius_prometheus_collector.erl create mode 100644 src/eradius_client_top_sup.erl delete mode 100644 src/eradius_config.erl delete mode 100644 src/eradius_counter.erl delete mode 100644 src/eradius_counter_aggregator.erl create mode 100644 src/eradius_metrics_prometheus.erl delete mode 100644 src/eradius_node_mon.erl delete mode 100644 src/eradius_proxy.erl create mode 100644 src/eradius_req.erl delete mode 100644 src/eradius_server_mon.erl delete mode 100644 src/eradius_server_top_sup.erl delete mode 100644 test/eradius_config_SUITE.erl delete mode 100644 test/eradius_logtest.erl delete mode 100644 test/eradius_proxy_SUITE.erl diff --git a/.github/workflows/hex.yaml b/.github/workflows/hex.yaml index 85107a68..66dcda79 100644 --- a/.github/workflows/hex.yaml +++ b/.github/workflows/hex.yaml @@ -7,9 +7,9 @@ on: jobs: publish: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 container: - image: erlang:23.2.5.0-alpine + image: erlang:26.2-alpine steps: - name: Prepare run: | @@ -17,7 +17,7 @@ jobs: apk --no-cache upgrade apk --no-cache add gcc git libc-dev libc-utils libgcc linux-headers make bash \ musl-dev musl-utils ncurses-dev pcre2 pkgconf scanelf wget zlib - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: work around for permission issue run: | git config --global --add safe.directory /__w/eradius/eradius @@ -25,5 +25,5 @@ jobs: env: HEX_API_KEY: ${{ secrets.HEX_API_KEY }} run: | - rebar3 edoc + rebar3 ex_doc rebar3 hex publish -r hexpm --yes diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7fd1e840..ec4dcc42 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,21 +8,37 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - otp: [22, 23, 24, 25] + otp: [25.3, 26.0, 26.1, 26.2, 27.0-rc3] container: image: erlang:${{ matrix.otp }}-alpine steps: + - name: Prepare + run: | + apk update + apk --no-cache upgrade + apk --no-cache add zstd - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Build run: rebar3 compile - name: Run tests run: | + export ERL_AFLAGS="+pc unicode -enable-feature all" rebar3 do xref, ct + rebar3 as dialyzer do dialyzer + - name: Tar Test Output + if: ${{ always() }} + run: tar -cf - _build/test/logs/ | zstd -15 -o ct-logs-${{ matrix.otp }}.tar.zst + - name: Archive Test Output + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: test-output-${{ matrix.otp }} + path: ct-logs-${{ matrix.otp }}.tar.xz slack: needs: test - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 if: always() steps: - name: Slack notification diff --git a/README.md b/README.md index 751bfaf1..50266f35 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,19 @@ [![Build Status][gh badge]][gh] [![Erlang Versions][erlang version badge]][gh] +eradius is a library to add [RADIUS] client and server funtionality to Erlang applications. + +v3 of eradius is in many places a full rewrite of the v2 versions and is not API compatible +with older versions. + +Previous versions provided generic, standalone [RADIUS] servers and proxies. This generic +functionality is better handled by existing, feature rich, stand alone RADIUS servers. + +This versions aims a providing support for implementing a versatile, simple to use +RADIUS client and a flexible library for implementing sue case specific RADIUS server +functionality. + +v2 was based on the original Jungerl code, some piece of it might have survived. This fork of `eradius` is a radical deviation from the original Jungerl code. It contains a generic [RADIUS](https://en.wikipedia.org/wiki/RADIUS) client, support for several authentication mechanisms and dynamic configuration @@ -15,328 +28,46 @@ several authentication mechanisms and dynamic configuration * [Erlang Version Support](#erlang-version-support) * [Building eradius](#building-eradius) * [Using eradius](#using-eradius) -* [Run sample server](#run-sample-server) * [Metrics](#metrics) -* [RADIUS server configuration](#radius-server-configuration) - * [eradius configuration example 1](#eradius-configuration-example-1) - * [eradius configuration example 2](#eradius-configuration-example-2) - * [eradius configuration example 3](#eradius-configuration-example-3) - * [eradius configuration example 4](#eradius-configuration-example-4) -* [Support of failover for client](#support-of-failover-for-client) - * [Failover configuration](#failover-configuration) - * [Failover Erlang code usage](#failover-erlang-code-usage) -* [Eradius counter aggregator](#eradius-counter-aggregator) * [Tables](#tables) # Erlang Version Support All minor version of the current major release and the highest minor version of the previous major release will be supported. -At the moment this means OTP `21.3`, OTP `22.x`, OTP `23.x` and OTP `24.x` are supported. OTP versions before `21.0` -do not work due the use of logger. When in doubt check the `otp_release` section in -[main.yml](.github/workflows/main.yml) for tested versions. +At the time of writing, OTP `25.3`, `26.0` `26.1` and `26.2` are supported. +When in doubt check the `otp_release` section in [main.yml](.github/workflows/main.yml) for +tested versions. -# Building eradius +> #### NOTE {: .tip} +> +> OTP-25 does not support the `-feature(maybe_expr, enable).` compiler directive, it is therefore necessary to pass the `-enable-feature all` option to the compiler. -```sh -$ rebar3 compile -``` +## Planned incompatiblities -# Using eradius +### v3.1 will require OTP-27 -Eradius requires a certain degree of configuration which is described in the -app.src file. Minimal examples of server callbacks can be found in the tests. +The current planning is to switch to documentation support introduced with OTP-27 for the v3.1 +release. That release will therefore drop support for pre OTP-27 releases. -# Run sample server +# Building eradius ```sh -$ cd sample -$ rebar3 shell --config example.config --apps eradius_server_sample +$ rebar3 compile ``` -Then run a simple benchmark: +# Using eradius -```erlang -1> eradius_server_sample:test(). -... -13:40:43.979 [info] 127.0.0.1:59254 [8]: Access-Accept -13:40:43.979 [info] 127.0.0.1:59254 [6]: Access-Accept -13:40:43.980 [info] 127.0.0.1:59254 [3]: Access-Accept -13:40:43.980 [info] 127.0.0.1:59254 [0]: Access-Accept -4333.788381 req/sec. -ok -``` +Eradius client are started and configured through their APIs. See `m:eradius_server` and +`m:eradius_client` for the APIs and settings. # Metrics -Eradius exposes following metrics via exometer: - * counter and handle time for requests - * counter for responses (this includes acks, naks, accepts etc.) - -The measurements are available for client, server and also for the specific -NAS callbacks. Further they are exposed in a 'total' fashion but also itemized -by request/response type (e.g. access request, accounting response etc.). - -It is possible to expose measurements compliant with [RFC 2619](https://tools.ietf.org/html/rfc2619) and [RFC 2621](https://tools.ietf.org/html/rfc2621) using -the build in metrics. - -The handle time metrics are generated internally using histograms. These histograms -all have a time span of 60s. The precise metrics are defined in [include/eradius_metrics.hrl](include/eradius_metrics.hrl). +A sample metrics callback module is provided that exposes metrics through prometheus.erl that +are compatible with the metrics that where included in previous versions. See more in [METRICS.md](METRICS.md). -# RADIUS server configuration - -> :warning: **Notes** :warning: -> * Square brackets ([]) denote an array that consists of n comma-separated objects. -> * Curly brackets ({}) denote a tuple that consists of a defined number of objects. - -Servers in this configuration are endpoints consisting of an IPv4 address and one or more ports. -`servers` is a list `[]` of said endpoints: -``` -servers == { servers, [<Server>] } -``` -Each server is tuple ({}): -``` -Server == { <SymbolicName>, { <IP>, [<Ports>] } } | { <SymbolicName>, { <IP>, [<Ports>], <ExtraSocketOptions> } } -ExtraServerOptions == [<ServerOption>] -ExtraSocketOptions == [{socket_opts, [socket_setopt()]}] (see: https://erlang.org/doc/man/inet.html#setopts-2) -ServerOption == {rate_config, <SymbolicNameLimit> | <RateConfigList>} -``` - -Rate configuration can be configured per server, in extra configuration, with a symbolic name or directly in server -``` -{SymbolicNameLimit, RateConfigList} -RateConfigList == [<RateOption>] -RateOption == { limit | max_size | max_time, integer() | undefined } -``` - -Each server is assigned a list of handlers. This list defines the NASes that are allowed to send RADIUS requests to a server and -which handler is to process the request. - -Handler assignment: `{<SymbolicName>, [<Handlers>]}` - -``` -SymbolicName == Reference to a previously defined server. -Handler == { <HandlerDefinition>, [<Sources>] } -``` - -If only one handler module is used, it can be defined globally as `{radius_callback, <HandlerMod>}`. -If more than one handler modules are used, they have to be given in the HandlerDefinition: - -``` -HandlerDefinition == {<HandlerMod>, <NasId>, <HandlerArgs>} | {<NasId>, <HandlerArgs>} -HandlerMod == Handler module to process the received requests. -NasId == String describing the Source. -HandlerArgs == List of arguments givent the handler module. -Source == {<IP>, <Secret>} | {<IP>, <Secret>, [<SourceOption>]} -SourceOption == {group, <GroupName>} | {nas_id, <NasId> } - -IP == IPv4 source address. -Secret == Binary. Passphrase, the NAS authenticates with. -GroupName: -RADIUS requests received by a server are forwarded to lists of nodes. -The lists are assigned to handlers, so the RADIUS requests of every handler can be forwarded to different nodes, if necessary. -The lists are referenced by a GroupName. If only one group is defined, the GroupName can be omitted. -In this case, all handlers forward their requests to the same list of nodes. -Session nodes == {session_nodes, ['node@host', ...]} | {session_nodes, [{<GroupName>, ['node@host', ...]}]} -``` - -## eradius configuration example 1 - -All requests are forwarded to the same globally defined list of nodes. -Only one handler module is used. - -```erlang -[{eradius, [ - {session_nodes, ['node1@host1', 'node2@host2']}, - {radius_callback, tposs_pcrf_radius}, - {servers, [ - {root, {"127.0.0.1", [1812, 1813]}} - ]}, - {root, [ - { - {"NAS1", [handler_arg1, handler_arg2]}, - [ {"10.18.14.2", <<"secret1">>} ] - }, - { - {"NAS2", [handler_arg1, handler_arg2]}, - [ {"10.18.14.3", <<"secret2">>, [{nas_id, <<"name">>}]} ] - } - ]} -]}] -``` - -## eradius configuration example 2 - -Requests of different sources are forwarded to different nodes. -Different handlers are used for the sources. - -```erlang -[{eradius, [ - {session_nodes, [ - {"NodeGroup1", ['node1@host1', 'node2@host2']}, - {"NodeGroup2", ['node3@host3', 'node4@host4']} - ]}, - {servers, [ - {root, {"127.0.0.1", [1812, 1813]}} - ]}, - {root, [ - { - {tposs_pcrf_handler1, "NAS1", [handler_arg1, handler_arg2]}, - [ {"10.18.14.2", <<"secret1">>, [{group, "NodeGroup1"}]} ] - }, - { - {tposs_pcrf_handler2, "NAS2", [handler_arg3, handler_arg4]}, - [ {"10.18.14.3", <<"secret2">>, [{group, "NodeGroup2"}]} ] - } - ]} -]}] -``` - -## eradius configuration example 3 - -Requests of different sources are forwarded to different nodes. -Different handlers are used for the sources. - -```erlang -[{eradius, [ - {session_nodes, [ - {"NodeGroup1", ['node1@host1', 'node2@host2']}, - {"NodeGroup2", ['node3@host3', 'node4@host4']} - ]}, - {servers, [ - {root, {"127.0.0.1", [1812, 1813], [{socket_opts, [{recbuf, 8192}, - {sndbuf, 131072}, - {netns, "/var/run/netns/myns"}]}]}} - ]}, - {root, [ - { - {tposs_pcrf_handler1, "NAS1", [handler_arg1, handler_arg2]}, - [ {"10.18.14.2", <<"secret1">>, [{group, "NodeGroup1"}]} ] - }, - { - {tposs_pcrf_handler2, "NAS2", [handler_arg3, handler_arg4]}, - [ {"10.18.14.3", <<"secret2">>, [{group, "NodeGroup2"}]} ] - } - ]} -]}] -``` - -## eradius configuration example 4 - -Example of full configuration with keys which can use in `eradius`: - -```erlang -[{eradius, [ - %% The IP address used to send RADIUS requests - {client_ip, {127, 0, 0, 1}}, - %% The maximum number of open ports that will be used by RADIUS clients - {client_ports, 256}, - %% how long the binary response is kept before re-sending it - {resend_timeout, 500}, - %% List of RADIUS dictionaries - {tables, [dictionary]}, - %% List of nodes where RADIUS requests possibly will be forwarded by a RADIUS server - {session_nodes, local}, - %% A RADIUS requests handler callback module - {radius_callback, eradius_server_sample}, - %% NAS specified for `root` RADIUS server - {root, [ - {{"root", []}, [{"127.0.0.1", "secret"}]} - ]}, - %% NAS specified for `acct` RADIUS server - {acct, [ - {{eradius_proxy, "radius_acct", [{default_route, {{127, 0, 0, 2}, 1813, <<"secret">>}, [{pool, pool_name}, {timeout, 5000}, {retries, 3}]]}, - [{"127.0.0.1", "secret"}]} - ]}, - %% List of RADIUS servers - {servers, [ - {root, {"127.0.0.1", [1812]}}, - {acct, {"127.0.0.1", [1813]}} - ]}, - {counter_aggregator, false}, - %% List of histogram buckets for RADIUS servers metrics - {histogram_buckets, [10, 30, 50, 75, 100, 1000, 2000]}, - %% Simple file-based logging of RADIUS requests and metadata - {logging, true}, - %% Path to log file - {logfile, "./radius.log"}, - %% List of upstream RADIUS servers pools - {servers_pool, [ - {pool_name, [ - {{127, 0, 0, 2}, 1812, <<"secret">>, [{retries, 3}]}, - {{127, 0, 0, 3}, 1812, <<"secret">>} - ]} - ]}, - {server_status_metrics_enabled, false}, - {counter_aggregator, false}, - %% Size of RADIUS receive buffer - {recbuf, 8192}, - %% The minimum size of the send buffer to use for the RADIUS - {sndbuf, 131072} -]}]. -``` - -# Support of failover for client - -Added support for fail-over. -Set of secondary RADIUS servers could be passed to the RADIUS client API `eradius_client:send_request/3` via options or to RADIUS proxy via configuration. - -If the response wasn't received after a number of requests specified by `retries` RADIUS client options - such RADIUS servers will be marked as non-active and RADIUS requests will not be sent for such non-active RADIUS servers, while configurable timeout (`eradius.unreachable_timeout`) is not expired. - -Secondary RADIUS servers could be specified via RADIUS proxy configuration, with the new configuration option - pool name. - -## Failover configuration - -Configuration example of failover where the `pool_name` is `atom` specifies name of a pool of secondary RADIUS servers. - -```erlang -[{eradius, [ - %%% ... - {default_route, {{127, 0, 0, 1}, 1812, <<"secret">>}, [{pool, pool_name}]} - %%% ... -]}] -``` -All pools are configured via: - -```erlang -[{eradius, [ - %%% ... - {servers_pool, [ - {pool_name, [ - {{127, 0, 0, 2}, 1812, <<"secret">>, [{retries, 3}]}, - {{127, 0, 0, 3}, 1812, <<"secret">>} - ]} - ]} - %%% ... -]}] -``` - -## Failover Erlang code usage -In a case when RADIUS proxy (eradius_proxy handler) is not used, a list of RADIUS upstream servers could be passed to the `eradius_client:send_radius_request/3` via options, for example: - -```erlang -eradius_client:send_request(Server, Request, [{failover, [{"localhost", 1814, <<"secret">>}]}]). -``` - -If `failover` option was not passed to the client through the options or RADIUS proxy configuration there should not be any performance impact as RADIUS client will try to a RADIUS request to only one RADIUS server that is defined in `eradius_client:send_request/3` options. - -For each secondary RADIUS server server status metrics could be enabled via boolean `server_status_metrics_enabled` configuration option. - -# Eradius counter aggregator -The `eradius_counter_aggregator` would go over all nodes in an Erlang cluster and aggregate the counter values from all nodes. -Configuration value of `counter_aggregator` can be `true` or `false` where `true` - is enable, `false` - is disable counter aggregator. -By default the `counter_aggregator` is disabled and have default value `false`. -Configuration example: -```erlang -[{eradius, [ - %%% ... - {counter_aggregator, true} - %%% ... -]}] -``` - # Tables A list of RADIUS dictionaries to be loaded at startup. The atoms in this list are resolved to files in @@ -348,10 +79,11 @@ Example: [dictionary, dictionary_cisco, dictionary_travelping] ``` -<!-- Badges --> +<!-- Badges and Links--> [hexpm]: https://hex.pm/packages/eradius [hexpm version]: https://img.shields.io/hexpm/v/eradius.svg?style=flat-square [hexpm downloads]: https://img.shields.io/hexpm/dt/eradius.svg?style=flat-square [gh]: https://github.com/travelping/eradius/actions/workflows/main.yml [gh badge]: https://img.shields.io/github/workflow/status/travelping/eradius/CI?style=flat-square -[erlang version badge]: https://img.shields.io/badge/erlang-22.0%20to%2024.0.1-blue.svg?style=flat-square +[erlang version badge]: https://img.shields.io/badge/erlang-25.3%20to%2026.2-blue.svg?style=flat-square +[RADIUS]: https://en.wikipedia.org/wiki/RADIUS diff --git a/applications/eradius_prometheus_collector/rebar.config b/applications/eradius_prometheus_collector/rebar.config deleted file mode 100644 index d07757cd..00000000 --- a/applications/eradius_prometheus_collector/rebar.config +++ /dev/null @@ -1,3 +0,0 @@ -{deps, [ - {prometheus, "4.10.0"} - ]}. diff --git a/applications/eradius_prometheus_collector/src/eradius_prometheus_collector.app.src b/applications/eradius_prometheus_collector/src/eradius_prometheus_collector.app.src deleted file mode 100644 index 9c1762e5..00000000 --- a/applications/eradius_prometheus_collector/src/eradius_prometheus_collector.app.src +++ /dev/null @@ -1,12 +0,0 @@ -{application, eradius_prometheus_collector, - [{description, "An OTP library"}, - {vsn, "0.1.0"}, - {registered, []}, - {applications, - [kernel, - stdlib - ]}, - {env,[]}, - {modules, [eradius_prometheus_collector]}, - {licenses, ["MIT"]} - ]}. diff --git a/applications/eradius_prometheus_collector/src/eradius_prometheus_collector.erl b/applications/eradius_prometheus_collector/src/eradius_prometheus_collector.erl deleted file mode 100644 index fd2b1546..00000000 --- a/applications/eradius_prometheus_collector/src/eradius_prometheus_collector.erl +++ /dev/null @@ -1,336 +0,0 @@ --module(eradius_prometheus_collector). - --behaviour(prometheus_collector). - --include_lib("eradius/include/eradius_lib.hrl"). --include_lib("prometheus/include/prometheus.hrl"). - --export([deregister_cleanup/1, collect_mf/2, collect_metrics/2, fetch_counter/2, fetch_counter/3, fetch_histogram/2]). --export([augment_counters/1]). - --import(prometheus_model_helpers, [create_mf/5, gauge_metric/2, counter_metric/1]). - --define(METRIC_NAME_PREFIX, "eradius_"). - --define(METRICS, [ - {uptime_milliseconds, gauge, "RADIUS server uptime"}, - {since_last_reset_milliseconds, gauge, "RADIUS last server reset time"}, - - {requests_total, counter, "Amount of requests received by the RADIUS server"}, - {replies_total, counter, "Amount of responses"}, - - {access_requests_total, counter, "Amount of Access requests received by the RADIUS server"}, - {accounting_requests_total, counter, "Amount of Accounting requests received by RADIUS server"}, - {coa_requests_total, counter, "Amount of CoA requests received by the RADIUS server"}, - {disconnect_requests_total, counter, "Amount of Disconnect requests received by the RADIUS server"}, - {accept_responses_total, counter, "Amount of Access-Accept responses"}, - {reject_responses_total, counter, "Amount of Access-Reject responses"}, - {access_challenge_total, counter, "Amount of Access-Challenge responses"}, - {accounting_responses_total, counter, "Amount of Accounting responses"}, - {coa_acks_total, counter, "Amount of CoA ACK responses"}, - {coa_nacks_total, counter, "Amount of CoA Nack responses"}, - {disconnect_acks_total, counter, "Amount of Disconnect-Ack responses"}, - {disconnect_nacks_total, counter, "Amount of Disconnect-Nack responses"}, - {malformed_requests_total, counter, "Amount of malformed requests on RADIUS server"}, - {invalid_requests_total, counter, "Amount of invalid requests on RADIUS server"}, - {retransmissions_total, counter, "Amount of retrasmissions done by NAS"}, - {duplicated_requests_total, counter, "Amount of duplicated requests"}, - {pending_requests_total, gauge, "Amount of pending requests"}, - {packets_dropped_total, counter, "Amount of dropped packets"}, - {unknown_type_request_total, counter, "Amount of RADIUS requests with unknown type"}, - {bad_authenticator_request_total, counter, "Amount of RADIUS requests with bad authenticator"}, - - {client_requests_total, counter, "Amount of requests sent by a client"}, - {client_replies_total, counter, "Amount of replies received by a client"}, - {client_access_requests_total, counter, "Amount of Access requests sent by a client"}, - {client_accounting_requests_total, counter, "Amount of Accounting requests sent by a client"}, - {client_coa_requests_total, counter, "Amount of CoA requests sent by a client"}, - {client_disconnect_requests_total, counter, "Amount of Disconnect requests sent by client"}, - {client_retransmissions_total, counter, "Amount of retransmissions done by a cliet"}, - {client_timeouts_total, counter, "Amount of timeout errors triggered on a client"}, - {client_accept_responses_total, counter, "Amount of Accept responses received by a client"}, - {client_reject_responses_total, counter, "Amount of Reject responses received by a client"}, - {client_access_challenge_total, counter, "Amount of Access-Challenge responses"}, - {client_accounting_responses_total, counter, "Amount of Accounting responses received by a client"}, - {client_coa_nacks_total, counter, "Amount of CoA Nack received by a client"}, - {client_coa_acks_total, counter, "Amount of CoA Ack received by a client"}, - {client_disconnect_acks_total, counter, "Amount of Disconnect Acks received by a client"}, - {client_disconnect_nacks_total, counter, "Amount of Disconnect Nacks received by a client"}, - {client_packets_dropped_total, counter, "Amount of dropped packets"}, - {client_unknown_type_request_total, counter, "Amount of RADIUS requests with unknown type"}, - {client_bad_authenticator_request_total, counter, "Amount of RADIUS requests with bad authenticator"}, - {client_pending_requests_total, gauge, "Amount of pending requests on client site"} - ]). - --define(ACCT_TYPES, [start, stop, update]). - -collect_mf(_Registry, Callback) -> - {Stats, NasCntFields, ClientCntFields} = get_stats(), - [mf(Callback, Metric, {Stats, NasCntFields, ClientCntFields}) || Metric <- ?METRICS], - ok. - -mf(Callback, {Name, PromMetricType, Help}, Data) -> - Callback(create_mf(?METRIC_NAME(Name), Help, PromMetricType, ?MODULE, - {PromMetricType, fun (_) -> Name end, Data})), - ok. - -collect_metrics(_, {PromMetricType, Fun, Stats}) -> - build_metric(Fun(Stats), PromMetricType, Stats). - -fetch_histogram(Name, Labels) -> - try - lists:flatten(lists:map(fun ({LabelsFromStat, Buckets, DurationUnit}) -> - case compare_labels(Labels, LabelsFromStat) of - true -> - {Buckets1, Values} = lists:unzip(Buckets), - Values1 = augment_counters(Values), - Buckets2 = lists:zip(Buckets1, Values1), - {Buckets2, LabelsFromStat, DurationUnit}; - _ -> [] - end - end, prometheus_histogram:values(default, Name))) - catch _:_ -> [] end. - -fetch_counter(Name, Labels) -> - {Stats, NasCntFields, ClientCntFields} = get_stats(), - fetch_counter(Name, {Stats, NasCntFields, ClientCntFields}, Labels). - -fetch_counter(uptime_milliseconds, Stat, Labels) -> - {{ServerMetrics, _}, _, _} = Stat, - lists:flatten(lists:map(fun (#server_counter{} = Cnt) -> - fetch_server_value(Labels, Cnt, fun () -> erlang:system_time(milli_seconds) - Cnt#server_counter.startTime end) - end, ServerMetrics)); -fetch_counter(since_last_reset_milliseconds, Stat, Labels) -> - {{ServerMetrics, _}, _, _} = Stat, - lists:flatten(lists:map(fun (#server_counter{} = Cnt) -> - fetch_server_value(Labels, Cnt, fun () -> erlang:system_time(milli_seconds) - Cnt#server_counter.resetTime end) - end, ServerMetrics)); -fetch_counter(Name, Stat, Labels) -> - case get_metric_info(Name, Stat) of - {_, undefined, _} -> - []; - {Metrics, MetricIdx, RadiusMetricType} -> - lists:flatten(lists:map(fun (Cnt) -> - case get_labels_and_val(MetricIdx, {Cnt, RadiusMetricType}, {Name, Labels}) of - {Value, LabelsFromStat} -> - case compare_labels(Labels, LabelsFromStat) of - true -> {Value, LabelsFromStat}; - _ -> [] - end; - List -> - lists:map(fun ({Value, LabelsFromStat}) -> - case compare_labels(Labels, LabelsFromStat) of - true -> {Value, LabelsFromStat}; - _ -> [] - end - end, List) - end - end, Metrics)) - end. - -%% from prometheus.erl as prometheus_histogram:values/1 returns -%% non-cumulative values -augment_counters([Start | Counters]) -> - augment_counters(Counters, [Start], Start). - -augment_counters([], LAcc, _CAcc) -> - LAcc; -augment_counters([Counter | Counters], LAcc, CAcc) -> - augment_counters(Counters, LAcc ++ [CAcc + Counter], CAcc + Counter). - -build_metric(uptime_milliseconds, Type, Stat) -> - {{ServerMetrics, _}, _, _} = Stat, - lists:map(fun (#server_counter{} = Cnt) -> - build_server_metric_value(Type, Cnt, fun () -> erlang:system_time(milli_seconds) - Cnt#server_counter.startTime end) - end, ServerMetrics); -build_metric(since_last_reset_milliseconds, Type, Stat) -> - {{ServerMetrics, _}, _, _} = Stat, - lists:map(fun (#server_counter{} = Cnt) -> - build_server_metric_value(Type, Cnt, fun () -> erlang:system_time(milli_seconds) - Cnt#server_counter.resetTime end) - end, ServerMetrics); -build_metric(MetricName, Type, Stat) - when MetricName =:= client_accounting_requests_total; - MetricName =:= client_accounting_responses_total; - MetricName =:= accounting_requests_total; - MetricName =:= accounting_responses_total -> - lists:flatten(lists:map(fun (AcctType) -> - case get_metric_info(MetricName, Stat) of - {_, undefined, _} -> - []; - {Metrics, MetricIdx, RadiusMetricType} -> - lists:map(fun (Cnt) -> - {Value, Labels} = get_labels_and_val(MetricIdx, {Cnt, RadiusMetricType}, {MetricName, [{acct_type, AcctType}]}), - metric(Type, Value, Labels) - end, Metrics) - end - end, [start, stop, update])); -build_metric(MetricName, Type, Stat) -> - case get_metric_info(MetricName, Stat) of - {_, undefined, _} -> - []; - {Metrics, MetricIdx, RadiusMetricType} -> - lists:flatten(lists:map(fun (Cnt) -> - {Value, Labels} = get_labels_and_val(MetricIdx, {Cnt, RadiusMetricType}, {}), - metric(Type, Value, Labels) - end, Metrics)) - end. - -metric(_, [], []) -> undefined; -metric(counter, Value, Labels) -> counter_metric({Labels, Value}); -metric(gauge, Value, Labels) -> gauge_metric(Labels, Value). - -deregister_cleanup(_) -> ok. - -%% helper to make mapping between prometheus metrics names and eradius_counter fields -%% @private -map_record_field(requests_total) -> {requests, server}; -map_record_field(replies_total) -> {replies, server}; -map_record_field(access_requests_total) -> {accessRequests, server}; -map_record_field(accounting_requests_total) -> {accountRequestsStart, server}; -map_record_field(coa_requests_total) -> {coaRequests, server}; -map_record_field(disconnect_requests_total) -> {discRequests, server}; -map_record_field(accept_responses_total) -> {accessAccepts, server}; -map_record_field(access_challenge_total) -> {accessChallenges, server}; -map_record_field(reject_responses_total) -> {accessRejects, server}; -map_record_field(accounting_responses_total) -> {accountResponsesStart, server}; -map_record_field(coa_acks_total) -> {coaAcks, server}; -map_record_field(coa_nacks_total) -> {coa_nacks_total, server}; -map_record_field(disconnect_acks_total) -> {discAcks, server}; -map_record_field(disconnect_nacks_total) -> {discNaks, server}; -map_record_field(malformed_requests_total) -> {malformedRequests, server}; -map_record_field(invalid_requests_total) -> {invalidRequests, server}; -map_record_field(retransmissions_total) -> {retransmissions, server}; -map_record_field(duplicated_requests_total) -> {dupRequests, server}; -map_record_field(pending_requests_total) -> {pending, server}; -map_record_field(packets_dropped_total) -> {packetsDropped, server}; -map_record_field(unknown_type_request_total) -> {unknownTypes, server}; -map_record_field(bad_authenticator_request_total) -> {badAuthenticators, server}; -map_record_field(client_requests_total) -> {requests, client}; -map_record_field(client_replies_total) -> {replies, client}; -map_record_field(client_access_requests_total) -> {accessRequests, client}; -map_record_field(client_accept_responses_total) -> {accessAccepts, client}; -map_record_field(client_access_challenge_total) -> {accessChallenges, client}; -map_record_field(client_reject_responses_total) -> {accessRejects, client}; -map_record_field(client_accounting_requests_total) -> {accountRequestsStart, client}; -map_record_field(client_accounting_responses_total) -> {accountResponsesStart, client}; -map_record_field(client_coa_requests_total) -> {coaRequests, client}; -map_record_field(client_coa_nacks_total) -> {coaNaks, client}; -map_record_field(client_coa_acks_total) -> {coaAcks, client}; -map_record_field(client_disconnect_requests_total) -> {discRequests, client}; -map_record_field(client_disconnect_nacks_total) -> {discNaks, client}; -map_record_field(client_disconnect_acks_total) -> {discAcks, client}; -map_record_field(client_retransmissions_total) -> {retransmissions, client}; -map_record_field(client_pending_requests_total) -> {pending, client}; -map_record_field(client_timeouts_total) -> {timeouts, client}; -map_record_field(client_packets_dropped_total) -> {packetsDropped, client}; -map_record_field(client_unknown_type_request_total) -> {unknownTypes, client}; -map_record_field(client_bad_authenticator_request_total) -> {badAuthenticators, client}; -map_record_field(_) -> undefined. - -%% Helper to fetch stats from eradius_counter -%% @private -get_stats() -> - Stats = eradius_counter:read(), - NasCntFields = lists:zip(record_info(fields, nas_counter), lists:seq(1, length(record_info(fields, nas_counter)))), - ClientCntFields = lists:zip(record_info(fields, client_counter), lists:seq(1, length(record_info(fields, client_counter)))), - {Stats, NasCntFields, ClientCntFields}. - -%% Helper to get value for the given metric from the #server_counter{} -%% @private -fetch_server_value(Labels, #server_counter{} = Cnt, FnVal) -> - {ServerIP, ServerPort} = Cnt#server_counter.key, - LabelsFromStat = [{server_name, Cnt#server_counter.server_name}, - {server_ip, inet:ntoa(ServerIP)}, - {server_port, ServerPort}], - case compare_labels(Labels, LabelsFromStat) of - true -> {FnVal(), LabelsFromStat}; - _ -> [] - end. - -%% Helper to build prometheus metric for the given metric from the #server_counter{} -%% @private -build_server_metric_value(Type, #server_counter{} = Cnt, FnVal) -> - {ServerIP, ServerPort} = Cnt#server_counter.key, - metric(Type, FnVal(), [{server_name, Cnt#server_counter.server_name}, - {server_ip, inet:ntoa(ServerIP)}, - {server_port, ServerPort}]). - -%% Helper to compare Labels from a query and labels from eradius_counter stat -%% @private -compare_labels(_, []) -> false; -compare_labels(LabelsFromQuery, LabelsFromStat) -> - lists:all(fun ({K, V}) -> V == proplists:get_value(K, LabelsFromStat, undefined) end, LabelsFromQuery). - -%% Helper to fetch a metric information from eradius_counter stats by the given metric name -%% @private -get_metric_info(Name, Stat) -> - {{_, {_, Metrics}}, NasFields, ClientFields} = Stat, - {Metric, RadiusMetricType} = map_record_field(Name), - case RadiusMetricType of - client -> {Metrics, proplists:get_value(Metric, ClientFields), RadiusMetricType}; - server -> {Metrics, proplists:get_value(Metric, NasFields), RadiusMetricType} - end. - -%% Helper to get a value of a server/nas metric by the given #nas_counter{} or #client_counter{} index -%% @private -get_labels_and_val(_, {#nas_counter{} = Cnt, server}, {Name, Labels}) - when Name =:= accounting_requests_total; - Name =:= accounting_responses_total -> - Type = proplists:get_value(acct_type, Labels), - {{ServerIP, ServerPort}, NasIP, NasId} = Cnt#nas_counter.key, - case get_value(Name, Type, Cnt) of - undefined -> - lists:map(fun (AcctType) -> - ResLabels = get_labels(Cnt, ServerIP, ServerPort, NasId, NasIP), - {get_value(Name, AcctType, Cnt), [{acct_type, AcctType} | ResLabels]} - end, ?ACCT_TYPES); - Value -> - ResLabels = get_labels(Cnt, ServerIP, ServerPort, NasId, NasIP), - {Value, [{acct_type, Type} | ResLabels]} - end; -get_labels_and_val(MetricIdx, {#nas_counter{} = Cnt, server}, _) -> - {{ServerIP, ServerPort}, NasIP, NasId} = Cnt#nas_counter.key, - {element(MetricIdx + 1, Cnt), get_labels(Cnt, ServerIP, ServerPort, NasId, NasIP)}; -get_labels_and_val(_, {#client_counter{} = Cnt, client}, {Name, Labels}) - when Name =:= client_accounting_requests_total; - Name =:= client_accounting_responses_total -> - Type = proplists:get_value(acct_type, Labels), - {{ClientName, ClientIP, _ClientPort}, {_, ServerIP, ServerPort}} = Cnt#client_counter.key, - case get_value(Name, Type, Cnt) of - undefined -> - lists:map(fun (AcctType) -> - ResLabels = get_labels(Cnt, ServerIP, ServerPort, ClientName, ClientIP), - {get_value(Name, AcctType, Cnt), [{acct_type, AcctType} | ResLabels]} - end, ?ACCT_TYPES); - Value -> - ResLabels = get_labels(Cnt, ServerIP, ServerPort, ClientName, ClientIP), - {Value, [{acct_type, Type} | ResLabels]} - end; -get_labels_and_val(MetricIdx, {#client_counter{} = Cnt, client}, _) -> - {{ClientName, ClientIP, _ClientPort}, {_, ServerIP, ServerPort}} = Cnt#client_counter.key, - {element(MetricIdx + 1, Cnt), get_labels(Cnt, ServerIP, ServerPort, ClientName, ClientIP)}; -get_labels_and_val(_, _, _) -> - {[], []}. - -%% @private -get_labels(#client_counter{server_name = ServerName}, ServerIP, ServerPort, ClientName, ClientIP) -> - [{server_name, ServerName}, {server_ip, ServerIP}, - {server_port, ServerPort}, {client_name, ClientName}, {client_ip, ClientIP}]; -get_labels(#nas_counter{server_name = ServerName}, ServerIP, ServerPort, NasId, NasIP) -> - [{server_name, ServerName}, {server_ip, inet:ntoa(ServerIP)}, - {server_port, ServerPort}, {nas_id, NasId}, {nas_ip, inet:ntoa(NasIP)}]. - -%% @private -get_value(accounting_requests_total, start, #nas_counter{accountRequestsStart = Value}) -> Value; -get_value(accounting_requests_total, stop, #nas_counter{accountRequestsStop = Value}) -> Value; -get_value(accounting_requests_total, update, #nas_counter{accountRequestsUpdate = Value}) -> Value; -get_value(accounting_responses_total, start, #nas_counter{accountResponsesStart = Value}) -> Value; -get_value(accounting_responses_total, stop, #nas_counter{accountResponsesStop = Value}) -> Value; -get_value(accounting_responses_total, update, #nas_counter{accountResponsesUpdate = Value}) -> Value; -get_value(client_accounting_requests_total, start, #client_counter{accountRequestsStart = Value}) -> Value; -get_value(client_accounting_requests_total, stop, #client_counter{accountRequestsStop = Value}) -> Value; -get_value(client_accounting_requests_total, update, #client_counter{accountRequestsUpdate = Value}) -> Value; -get_value(client_accounting_responses_total, start, #client_counter{accountResponsesStart = Value}) -> Value; -get_value(client_accounting_responses_total, stop, #client_counter{accountResponsesStop = Value}) -> Value; -get_value(client_accounting_responses_total, update, #client_counter{accountResponsesUpdate = Value}) -> Value; -get_value(_, _, _) -> undefined. diff --git a/dicts_compiler.erl b/dicts_compiler.erl index 2aad5743..6e4eef27 100755 --- a/dicts_compiler.erl +++ b/dicts_compiler.erl @@ -24,7 +24,7 @@ compile() -> %% sort dictionaries in alphabetical order to be sure that %% basic `priv/dictionaries/dictionary` builds first %% because it contains some attributes which may be needed - % for vendor's dictionaries + %% for vendor's dictionaries Dictionaries = lists:sort(Dictionaries0), Targets = [{Dictionary, out_files(Dictionary)} || Dictionary <- Dictionaries], compile_each(Targets) diff --git a/include/eradius_lib.hrl b/include/eradius_lib.hrl index e5236be4..e44fc53c 100644 --- a/include/eradius_lib.hrl +++ b/include/eradius_lib.hrl @@ -1,6 +1,6 @@ -define(BYTE, integer-unit:8). % Nice syntactic sugar... --type server() :: {inet:ip_address(), eradius_server:port_number()}. +-type server() :: {inet:ip_address(), inet:port_number()}. -type handler() :: {module(), term()}. -type server_name() :: atom(). -type atom_name() :: atom(). @@ -125,32 +125,3 @@ invalidRequests = 0 :: non_neg_integer(), discardNoHandler = 0 :: non_neg_integer() }). - --record(nas_prop, { - server_ip :: inet:ip_address(), - server_port :: eradius_server:port_number(), - nas_id :: term(), - nas_ip :: inet:ip_address(), - nas_port :: eradius_server:port_number(), - metrics_info :: {atom_address(), atom_address()}, - secret :: eradius_lib:secret(), - handler_nodes = local :: list(atom()) | local - }). - --record(radius_request, { - reqid = 0 :: byte(), - cmd = request :: eradius_lib:command(), - attrs = [] :: eradius_lib:attribute_list(), - secret :: eradius_lib:secret(), - authenticator :: eradius_lib:authenticator(), - msg_hmac = false :: boolean(), - eap_msg = <<>> :: binary() - }). - - --record(decoder_state, { - request_authenticator :: 'undefined' | binary(), - attrs = [] :: eradius_lib:attribute_list(), - hmac_pos :: 'undefined' | non_neg_integer(), - eap_msg = [] :: [binary()] - }). diff --git a/rebar.config b/rebar.config index be584e49..9a819778 100644 --- a/rebar.config +++ b/rebar.config @@ -1,15 +1,16 @@ %%-*-Erlang-*- {erl_opts, [debug_info]}. -{minimum_otp_vsn, "22"}. +{minimum_otp_vsn, "25.3"}. {pre_hooks, [{compile, "escript dicts_compiler.erl compile"}, {clean, "escript dicts_compiler.erl clean"}]}. {profiles, [ + {dialyzer, [{deps, [{prometheus, "4.11.0"}]}]}, {test, [ {erl_opts, [nowarn_export_all]}, - {project_app_dirs, ["applications/*", "src/*", "."]}, - {deps, [{meck, "0.9.0"}]} + {deps, [{meck, "0.9.2"}, + {prometheus, "4.11.0"}]} ]} ]}. @@ -18,12 +19,37 @@ %% xref checks to run {xref_checks, [undefined_function_calls, undefined_functions, locals_not_used, deprecated_function_calls, - deprecated_functions]}. + deprecated_functions, exports_not_used]}. {xref_ignores, [{prometheus_histogram, declare, 1}, {prometheus_histogram, observe, 3}, + {prometheus_counter, declare, 1}, + {prometheus_counter, inc, 3}, + {prometheus_gauge, declare, 1}, + {prometheus_gauge, inc, 3}, {prometheus_boolean, declare, 1}, {prometheus_boolean, set, 3}]}. %% == Plugins == -{plugins, [rebar3_hex]}. +{plugins, [rebar3_hex, rebar3_ex_doc]}. + +%% == Dialyzer == + +{dialyzer, + [%%{warnings, [unmatched_returns, underspecs]}, + {plt_extra_apps, [prometheus]} + ]}. + +%% == ExDoc == + +{ex_doc, [ + {extras, ["README.md", "METRICS.md", "LICENSE"]}, + {main, "README.md"}, + {source_url, "https://github.com/travelping/eradius"} +]}. + +%% == Hex == + +{hex, [ + {doc, #{provider => ex_doc}} +]}. diff --git a/src/eradius.app.src b/src/eradius.app.src index 3af15b11..deffd7dd 100644 --- a/src/eradius.app.src +++ b/src/eradius.app.src @@ -1,31 +1,31 @@ %% vim: ft=erlang -{application, eradius, [ - {description, "Erlang RADIUS server"}, - {vsn, semver}, - {registered, [eradius_dict, eradius_sup, eradius_server_top_sup, eradius_server_sup, eradius_server_mon]}, - {applications, [kernel, stdlib, crypto]}, - {mod, {eradius, []}}, - {env, [ - {servers, []}, - {tables, [dictionary]}, - {client_ip, undefined}, - {client_ports, 100}, - {resend_timeout, 30000}, - {logging, false}, - {counter_aggregator, false}, - {server_status_metrics_enabled, false}, - {logfile, "./radius.log"}, - {recbuf, 8192}, - {sndbuf, 131072} - ]}, - {maintainers, ["Andreas Schultz", "Vladimir Tarasenko", "Yury Gargay"]}, - {licenses, ["MIT"]}, - {links, [{"Github", "https://github.com/travelping/eradius"}]}, - %% List copied from rebar3_hex.hrl ?DEFAULT_FILES, adding "Makefile" - {files, ["applications", "src", "c_src", "include/eradius_*.hrl", "rebar.config.script" - ,"priv/dictionaries", "rebar.config", "rebar.lock" - ,"README*", "readme*" - ,"LICENSE*", "license*" - ,"NOTICE" - ,"dicts_compiler.erl"]} - ]}. +{application, eradius, + [{description, "Erlang RADIUS server"}, + {vsn, semver}, + {registered, [eradius_dict, eradius_sup, eradius_server_sup]}, + {applications, [kernel, stdlib, crypto]}, + {mod, {eradius, []}}, + {env, [ + {servers, []}, + {tables, [dictionary]}, + {client_ip, undefined}, + {client_ports, 100}, + {resend_timeout, 30000}, + {logging, false}, + {counter_aggregator, false}, + {server_status_metrics_enabled, false}, + {logfile, "./radius.log"}, + {recbuf, 8192}, + {sndbuf, 131072} + ]}, + {maintainers, ["Andreas Schultz", "Vladimir Tarasenko", "Yury Gargay"]}, + {licenses, ["MIT"]}, + {links, [{"Github", "https://github.com/travelping/eradius"}]}, + %% List copied from rebar3_hex.hrl ?DEFAULT_FILES, adding "Makefile" + {files, ["applications", "src", "c_src", "include/eradius_*.hrl", "rebar.config.script" + ,"priv/dictionaries", "rebar.config", "rebar.lock" + ,"README*", "readme*" + ,"LICENSE*", "license*" + ,"NOTICE" + ,"dicts_compiler.erl"]} + ]}. diff --git a/src/eradius.erl b/src/eradius.erl index 7bc497e0..13c6a4e0 100644 --- a/src/eradius.erl +++ b/src/eradius.erl @@ -1,51 +1,49 @@ %% @doc Main module of the eradius application. -module(eradius). --export([load_tables/1, load_tables/2, - modules_ready/1, modules_ready/2, - statistics/1]). -behaviour(application). --export([start/2, stop/1, config_change/3]). + +%% API +-export([load_tables/1, load_tables/2, + start_server/3, start_server/4]). +-ignore_xref([load_tables/1, load_tables/2, + start_server/3, start_server/4]). + +%% application callbacks +-export([start/2, stop/1]). %% internal use -include("eradius_lib.hrl"). +%%%========================================================================= +%%% API +%%%========================================================================= + %% @doc Load RADIUS dictionaries from the default directory. -spec load_tables(list(eradius_dict:table_name())) -> ok | {error, {consult, eradius_dict:table_name()}}. load_tables(Tables) -> eradius_dict:load_tables(Tables). %% @doc Load RADIUS dictionaries from a certain directory. --spec load_tables(file:filename(), list(eradius_dict:table_name())) -> ok | {error, {consult, eradius_dict:table_name()}}. +-spec load_tables(Dir :: file:filename(), Tables :: [Table :: eradius_dict:table_name()]) -> + ok | {error, {consult, Table :: eradius_dict:table_name()}}. load_tables(Dir, Tables) -> eradius_dict:load_tables(Dir, Tables). -%% @equiv modules_ready(self(), Modules) -modules_ready(Modules) -> - eradius_node_mon:modules_ready(self(), Modules). - -%% @doc Announce request handler module availability. -%% Applications need to call this function (usually from their application master) -%% in order to make their modules (which should implement the {@link eradius_server} behaviour) -%% available for processing. The modules will be revoked when the given Pid goes down. -modules_ready(Pid, Modules) -> - eradius_node_mon:modules_ready(Pid, Modules). +start_server(IP, Port, #{handler := {_, _}, clients := #{}} = Opts) + when (IP =:= any orelse is_tuple(IP)) andalso + is_integer(Port) andalso Port >= 0 andalso Port < 65536 -> + eradius_server:start_instance(IP, Port, Opts). -%% @doc manipulate server statistics -%% * reset: reset all counters to zero -%% * pull: read counters and reset to zero -%% * read: read counters -statistics(reset) -> - eradius_counter_aggregator:reset(); -statistics(pull) -> - eradius_counter_aggregator:pull(); -statistics(read) -> - eradius_counter_aggregator:read(). +start_server(ServerName, IP, Port, #{handler := {_, _}, clients := #{}} = Opts) + when (IP =:= any orelse is_tuple(IP)) andalso + is_integer(Port) andalso Port >= 0 andalso Port < 65536 -> + eradius_server:start_instance(ServerName, IP, Port, Opts). - -%% ---------------------------------------------------------------------------------------------------- -%% -- application callbacks +%%%=================================================================== +%%% application callbacks +%%%=================================================================== %% @private start(_StartType, _StartArgs) -> @@ -54,19 +52,3 @@ start(_StartType, _StartArgs) -> %% @private stop(_State) -> ok. - -%% @private -config_change(Added, Changed, Removed) -> - lists:foreach(fun do_config_change/1, Added), - lists:foreach(fun do_config_change/1, Changed), - Keys = [K || {K, _} <- Added ++ Changed] ++ Removed, - (lists:member(logging, Keys) or lists:member(logfile, Keys)) - andalso eradius_log:reconfigure(), - eradius_client_mngr:reconfigure(). - -do_config_change({tables, NewTables}) -> - eradius_dict:load_tables(NewTables); -do_config_change({servers, _}) -> - eradius_server_mon:reconfigure(); -do_config_change({_, _}) -> - ok. diff --git a/src/eradius_auth.erl b/src/eradius_auth.erl index bc3f7e63..2ec7eb7b 100644 --- a/src/eradius_auth.erl +++ b/src/eradius_auth.erl @@ -1,16 +1,33 @@ +%% Copyright (c) 2002-2007, Martin Björklund and Torbjörn Törnkvist +%% Copyright (c) 2011, Travelping GmbH <info@travelping.com> +%% +%% SPDX-License-Identifier: MIT +%% + %% @doc user authentication helper functions -module(eradius_auth). + -export([check_password/2]). -export([pap/2, chap/3, ms_chap/3, ms_chap_v2/4]). -export([des_key_from_hash/1, nt_password_hash/1, challenge_response/2, ascii_to_unicode/1]). +-ignore_xref([check_password/2]). +-ignore_xref([pap/2, chap/3, ms_chap/3, ms_chap_v2/4]). +-ignore_xref([des_key_from_hash/1, nt_password_hash/1, challenge_response/2, + ascii_to_unicode/1]). + -ifdef(TEST). -export([nt_hash/1, v2_generate_nt_response/4, mppe_get_master_key/2, mppe_generate_session_keys/3, mppe_get_asymetric_send_start_key/2, v2_generate_authenticator_response/5]). +-ignore_xref([nt_hash/1, v2_generate_nt_response/4, mppe_get_master_key/2, + mppe_generate_session_keys/3, mppe_get_asymetric_send_start_key/2, + v2_generate_authenticator_response/5]). -endif. +-dialyzer({nowarn_function, [mppe_generate_session_keys/3]}). + -include("eradius_lib.hrl"). -include("eradius_dict.hrl"). -include("dictionary.hrl"). @@ -21,9 +38,10 @@ %% @doc check the request password using all available authentication mechanisms. %% Tries CHAP, then MS-CHAP, then MS-CHAPv2, finally PAP. --spec check_password(binary(), #radius_request{}) -> - false | {boolean(), eradius_lib:attribute_list()}. -check_password(Password, Req = #radius_request{authenticator = Authenticator, attrs = AVPs}) -> +-spec check_password(binary(), eradius_req:req()) -> + false | {boolean(), eradius_req:attribute_list()}. +check_password(Password, #{authenticator := Authenticator, is_valid := true} = Req) -> + {AVPs, _} = eradius_req:attrs(Req), case lookup_auth_attrs(AVPs) of {false, Chap_pass, false, false, false, false} -> {chap(Password, Chap_pass, Authenticator), []}; @@ -32,12 +50,12 @@ check_password(Password, Req = #radius_request{authenticator = Authenticator, at {false, _, _, Challenge, Response, false} when Challenge =/= false, Response =/= false -> ms_chap(Password, Challenge, Response); {false, _, _, Challenge, false, Response} when Challenge =/= false, Response =/= false -> - Username = eradius_lib:get_attr(Req, ?User_Name), + Username = eradius_req:attr(?User_Name, Req), ms_chap_v2(Username, Password, Challenge, Response); {ReqPassword, _, _, _, _, _} when ReqPassword =/= false -> {pap(Password, ReqPassword), []}; {_, _, _, _, _, _} -> - {false, []} + false end. %% composite lookup function, retrieve User_Password, CHAP_Password and CHAP_Challenge at once @@ -59,7 +77,8 @@ lookup_auth_attrs(Attrs, [{#attribute{id = ?MS_CHAP2_Response}, Val}|T]) -> lookup_auth_attrs(Attrs, [{{_,_} = Id, Val}|T]) -> %% fallback for undecoded AVPs - lookup_auth_attrs(Attrs, [{#attribute{id = Id}, Val}|T]); + %% init name field to make dialyzer happy + lookup_auth_attrs(Attrs, [{#attribute{id = Id, name = ""}, Val}|T]); lookup_auth_attrs(Attrs, [_|T]) -> lookup_auth_attrs(Attrs, T); lookup_auth_attrs(Attrs, []) -> @@ -181,29 +200,34 @@ lm_password_hash(Password) -> << (des_hash(Key1))/binary, (des_hash(Key2))/binary >>. %% @doc MS-CHAP authentication --spec ms_chap(binary(), binary(), binary()) -> false | {true, eradius_lib:attribute_list()}. -ms_chap(Passwd, Challenge, <<_Ident:1/binary, Flags:1/integer-unit:8, LMResponse:24/binary, NTResponse:24/binary>>) -> +-spec ms_chap(binary(), binary(), binary()) -> {boolean(), eradius_req:attribute_list()}. +ms_chap(Passwd, Challenge, + <<_Ident:1/binary, Flags:1/integer-unit:8, LMResponse:24/binary, NTResponse:24/binary>>) -> LmPasswdHash = lm_password_hash(Passwd), NtPasswdHash = nt_password_hash(Passwd), - Resp1 = if - Flags == 1; NTResponse =/= << 0:24/unit:8 >> -> + Resp1 = case Flags of + 1 when NTResponse =/= << 0:24/unit:8 >> -> NTResponse =:= challenge_response(Challenge, NtPasswdHash); - true -> + _ -> false end, - Resp2 = if - Resp1 == false -> + Resp2 = case Resp1 of + false -> LMResponse =:= challenge_response(Challenge, LmPasswdHash); - Resp1 -> + _ -> Resp1 end, - - Resp2 andalso {true, ms_chap_attrs(LmPasswdHash, NtPasswdHash)}. + case Resp2 of + false -> {false, []}; + true -> {true, ms_chap_attrs(LmPasswdHash, NtPasswdHash)} + end. %% @doc MS-CHAP-V2 authentication --spec ms_chap_v2(binary(), binary(), binary(), binary()) -> false | {true, eradius_lib:attribute_list()}. -ms_chap_v2(UserName, Passwd, AuthenticatorChallenge, <<Ident:1/binary, _Flags:1/binary, PeerChallenge:16/binary, _Reserved:8/binary, Response/binary>>) -> +-spec ms_chap_v2(binary(), binary(), binary(), binary()) -> + {boolean(), eradius_req:attribute_list()}. +ms_chap_v2(UserName, Passwd, AuthenticatorChallenge, + <<Ident:1/binary, _Flags:1/binary, PeerChallenge:16/binary, _Reserved:8/binary, Response/binary>>) -> PasswdHash = nt_password_hash(Passwd), ExpectedResponse = v2_generate_nt_response(AuthenticatorChallenge, PeerChallenge, UserName, PasswdHash), @@ -211,7 +235,7 @@ ms_chap_v2(UserName, Passwd, AuthenticatorChallenge, <<Ident:1/binary, _Flags:1/ ExpectedResponse == Response -> {true, ms_chap_v2_attrs(UserName, PasswdHash, AuthenticatorChallenge, Ident, PeerChallenge, Response)}; true -> - false + {false, []} end. %% @doc calculate MS-CHAP response attributes diff --git a/src/eradius_client.erl b/src/eradius_client.erl index ce58debb..5eb9cc30 100644 --- a/src/eradius_client.erl +++ b/src/eradius_client.erl @@ -4,9 +4,8 @@ %% SPDX-License-Identifier: MIT %% %% @doc This module contains a RADIUS client that can be used to send authentication and accounting requests. -%% A counter is kept for every NAS in order to determine the next request id and sender port -%% for each outgoing request. The implementation naively assumes that you won't send requests to a -%% distinct number of NASs over the lifetime of the VM, which is why the counters are not garbage-collected. +%% A counter is kept for every client instance in order to determine the next request id and sender port +%% for each outgoing request. %% %% The client uses OS-assigned ports. The maximum number of open ports can be specified through the %% ``client_ports'' application environment variable, it defaults to ``20''. The number of ports should not @@ -14,18 +13,16 @@ %% %% The IP address used to send requests is read <emph>once</emph> (at startup) from the ``client_ip'' %% parameter. Changing it currently requires a restart. It can be given as a string or ip address tuple, -%% or the atom ``undefined'' (the default), which uses whatever address the OS selects. +%% or the atom ``any'' (the default), which uses whatever address the OS selects. -module(eradius_client). %% API --export([send_request/2, send_request/3, - send_remote_request/3, send_remote_request/4]). - -%% internal API --export([send_remote_request_loop/8]). +-export([send_request/3, send_request/4]). +-ignore_xref([send_request/3, send_request/4]). -import(eradius_lib, [printable_peer/2]). + -include_lib("stdlib/include/ms_transform.hrl"). -include_lib("kernel/include/logger.hrl"). -include_lib("kernel/include/inet.hrl"). @@ -33,385 +30,113 @@ -include("eradius_lib.hrl"). -include("eradius_internal.hrl"). --define(GOOD_CMD(Req), (Req#radius_request.cmd == 'request' orelse - Req#radius_request.cmd == 'accreq' orelse - Req#radius_request.cmd == 'coareq' orelse - Req#radius_request.cmd == 'discreq')). +-define(GOOD_CMD(Cmd), (Cmd == 'request' orelse + Cmd == 'accreq' orelse + Cmd == 'coareq' orelse + Cmd == 'discreq')). --type nas_address() :: {string() | binary() | inet:ip_address(), - eradius_server:port_number(), - eradius_lib:secret()}. --type options() :: [{retries, pos_integer()} | - {timeout, timeout()} | - {server_name, atom()} | - {metrics_info, {atom(), atom(), atom()}}]. +-type options() :: #{retries => pos_integer(), + timeout => timeout(), + failover => [eradius_client_mngr:server_name()] + }. +%% Options for a RADIUS request to override configure +%% server defaults and to add failover alternatives --export_type([nas_address/0, options/0]). +-export_type([options/0]). +-define(DEFAULT_REQUEST_OPTS, #{retries => 3, timeout => 5_000, failover => []}). -define(SERVER, ?MODULE). %%%========================================================================= %%% API %%%========================================================================= -%% @equiv send_request(NAS, Request, []) --spec send_request(nas_address(), #radius_request{}) -> {ok, binary()} | {error, 'timeout' | 'socket_down'}. -send_request(NAS, Request) -> - send_request(NAS, Request, []). - -%% @doc Send a radius request to the given NAS. -%% If no answer is received within the specified timeout, the request will be sent again. --spec send_request(nas_address(), #radius_request{}, options()) -> - {ok, binary(), eradius_lib:authenticator()} | {error, 'timeout' | 'socket_down'}. -send_request({Host, Port, Secret}, Request, Options) - when ?GOOD_CMD(Request) andalso is_binary(Host) -> - send_request({erlang:binary_to_list(Host), Port, Secret}, Request, Options); -send_request({Host, Port, Secret}, Request, Options) - when ?GOOD_CMD(Request) andalso is_list(Host) -> - IP = get_ip(Host), - send_request({IP, Port, Secret}, Request, Options); -send_request({IP, Port, Secret}, Request, Options) when ?GOOD_CMD(Request) andalso is_tuple(IP) -> - TS1 = erlang:monotonic_time(), - ServerName = proplists:get_value(server_name, Options, undefined), - MetricsInfo = make_metrics_info(Options, {IP, Port}), - Retries = proplists:get_value(retries, Options, ?DEFAULT_RETRIES), - Timeout = proplists:get_value(timeout, Options, ?DEFAULT_TIMEOUT), - SendReqFn = fun () -> - Peer = {ServerName, {IP, Port}}, - update_client_requests(MetricsInfo), - {Socket, ReqId} = eradius_client_mngr:wanna_send(Peer), - Response = send_request_loop(Socket, ReqId, Peer, - Request#radius_request{reqid = ReqId, secret = Secret}, - Retries, Timeout, MetricsInfo), - proceed_response(Request, Response, Peer, TS1, MetricsInfo, Options) - end, - %% If we have other RADIUS upstream servers check current one, - %% maybe it is already marked as inactive and try to find another - %% one - case proplists:get_value(failover, Options, []) of - [] -> - SendReqFn(); - UpstreamServers -> - case eradius_client_mngr:find_suitable_peer([{IP, Port, Secret} | UpstreamServers]) of - [] -> - no_active_servers; - {{IP, Port, Secret}, _NewPool} -> - SendReqFn(); - {NewPeer, []} -> - %% Special case, we don't have servers in the pool anymore, but we need - %% to preserve `failover` option to mark current server as inactive if - %% it will fail - NewOptions = lists:keyreplace(failover, 1, Options, {failover, undefined}), - send_request(NewPeer, Request, NewOptions); - {NewPeer, NewPool} -> - %% current server is not in list of active servers, so use another one - NewOptions = lists:keyreplace(failover, 1, Options, {failover, NewPool}), - send_request(NewPeer, Request, NewOptions) - end - end; -send_request({_IP, _Port, _Secret}, _Request, _Options) -> - error(badarg). - -%% @equiv send_remote_request(Node, NAS, Request, []) --spec send_remote_request(node(), nas_address(), #radius_request{}) -> {ok, binary()} | {error, 'timeout' | 'node_down' | 'socket_down'}. -send_remote_request(Node, NAS, Request) -> - send_remote_request(Node, NAS, Request, []). +%% @equiv send_request(ServerRef, ServerName, Req, []) +-spec send_request(gen_server:server_ref(), + eradius_client_mngr:server_name() | [eradius_client_mngr:server_name()], + eradius_req:req()) -> + {{ok, eradius_req:req()} | {error, 'timeout' | 'socket_down'}, eradius_req:req()}. +send_request(ServerRef, ServerName, #{cmd := _, payload := _} = Req) -> + send_request(ServerRef, ServerName, Req, #{}). -%% @doc Send a radius request to the given NAS through a socket on the specified node. +%% @doc Send a radius request to the given server or server pool. %% If no answer is received within the specified timeout, the request will be sent again. -%% The request will not be sent again if the remote node is unreachable. --spec send_remote_request(node(), nas_address(), #radius_request{}, options()) -> {ok, binary()} | {error, 'timeout' | 'node_down' | 'socket_down'}. -send_remote_request(Node, {IP, Port, Secret}, Request, Options) when ?GOOD_CMD(Request) -> - TS1 = erlang:monotonic_time(), - ServerName = proplists:get_value(server_name, Options, undefined), - MetricsInfo = make_metrics_info(Options, {IP, Port}), - update_client_requests(MetricsInfo), - Peer = {ServerName, {IP, Port}}, - try eradius_client_mngr:wanna_send(Node, Peer) of - {Socket, ReqId} -> - Request1 = case eradius_node_mon:get_remote_version(Node) of - {0, Minor} when Minor < 6 -> - {_, EncRequest} = eradius_lib:encode_request(Request#radius_request{reqid = ReqId, secret = Secret}), - EncRequest; - _ -> - Request#radius_request{reqid = ReqId, secret = Secret} - end, - Retries = proplists:get_value(retries, Options, ?DEFAULT_RETRIES), - Timeout = proplists:get_value(timeout, Options, ?DEFAULT_TIMEOUT), - SenderPid = spawn(Node, ?MODULE, send_remote_request_loop, - [self(), Socket, ReqId, Peer, Request1, Retries, Timeout, MetricsInfo]), - SenderMonitor = monitor(process, SenderPid), - Response = receive - {SenderPid, Result} -> - erlang:demonitor(SenderMonitor, [flush]), - Result; - {'DOWN', SenderMonitor, process, SenderPid, _Reason} -> - {error, socket_down} - end, - proceed_response(Request, Response, Peer, TS1, MetricsInfo, Options) - catch - exit:{{nodedown, Node}, _} -> - {error, node_down} - end; -send_remote_request(_Node, {_IP, _Port, _Secret}, _Request, _Options) -> - error(badarg). - -proceed_response(Request, {ok, Response, Secret, Authenticator}, _Peer = {_ServerName, {ServerIP, Port}}, TS1, MetricsInfo, Options) -> - update_client_request(Request#radius_request.cmd, MetricsInfo, erlang:monotonic_time() - TS1, Request), - update_client_responses(MetricsInfo), - case eradius_lib:decode_request(Response, Secret, Authenticator) of - {bad_pdu, Reason} -> - eradius_client_mngr:request_failed(ServerIP, Port, Options), - update_server_status_metric(ServerIP, Port, false, Options), - - case Reason of - "Message-Authenticator Attribute is invalid" -> - update_client_response(bad_authenticator, MetricsInfo, Request), - ?LOG(error, "~s INF: Noreply for request ~p. " - "Message-Authenticator Attribute is invalid", - [printable_peer(ServerIP, Port), Request]), - noreply; - "Authenticator Attribute is invalid" -> - update_client_response(bad_authenticator, MetricsInfo, Request), - ?LOG(error, "~s INF: Noreply for request ~p. " - "Authenticator Attribute is invalid", - [printable_peer(ServerIP, Port), Request]), - noreply; - "unknown request type" -> - update_client_response(unknown_req_type, MetricsInfo, Request), - ?LOG(error, "~s INF: Noreply for request ~p. " - "unknown request type", - [printable_peer(ServerIP, Port), Request]), - noreply; - _ -> - update_client_response(dropped, MetricsInfo, Request), - ?LOG(error, "~s INF: Noreply for request ~p. " - "Could not decode the request, reason: ~s", - [printable_peer(ServerIP, Port), Request, Reason]), - maybe_failover(Request, noreply, Options) - end; - Decoded -> - update_server_status_metric(ServerIP, Port, true, Options), - update_client_response(Decoded#radius_request.cmd, MetricsInfo, Request), - {ok, Response, Authenticator} - end; - -proceed_response(Request, Response, {_ServerName, {ServerIP, Port}}, TS1, MetricsInfo, Options) -> - update_client_responses(MetricsInfo), - update_client_request(Request#radius_request.cmd, MetricsInfo, erlang:monotonic_time() - TS1, Request), - - eradius_client_mngr:request_failed(ServerIP, Port, Options), - update_server_status_metric(ServerIP, Port, false, Options), +-spec send_request(gen_server:server_ref(), + eradius_client_mngr:server_name() | [eradius_client_mngr:server_pool()], + eradius_req:req(), options()) -> + {{ok, eradius_req:req()} | {error, 'timeout' | 'socket_down'}, eradius_req:req()}. +send_request(ServerRef, ServerName, #{cmd := Cmd} = Req, Opts) + when ?GOOD_CMD(Cmd), is_map(Opts), is_list(ServerName) -> + do_send_request(ServerRef, ServerName, [], Req, Opts); +send_request(ServerRef, ServerName, Req, Opts) when not is_list(ServerName) -> + send_request(ServerRef, [ServerName], Req, Opts). + +do_send_request(_ServerRef, [], _Tried, _Req, _Opts) -> + {error, no_active_servers}; +do_send_request(ServerRef, Peers, Tried, Req0, Opts0) -> + case eradius_client_mngr:wanna_send(ServerRef, Peers, Tried) of + {ok, {Socket, ReqId, ServerName, Server, ReqInfo}} -> + #{secret := Secret} = Server, + + ServerOpts0 = maps:with([retries, timeout], Server), + ServerOpts = maps:merge(?DEFAULT_REQUEST_OPTS, ServerOpts0), + Opts = maps:merge(ServerOpts, Opts0), + + Req1 = maps:merge(Req0, ReqInfo), + Req2 = eradius_req:record_metric(request, #{}, Req1), + + {Response, Req} = + send_request_loop( + Socket, ReqId, Req2#{req_id => ReqId, secret => Secret}, Opts), + proceed_response(ServerRef, [ServerName | Tried], Req, Response, ServerName, Opts); - maybe_failover(Request, Response, Options). - -maybe_failover(Request, Response, Options) -> - UpstreamServers = proplists:get_value(failover, Options, []), - case eradius_client_mngr:find_suitable_peer(UpstreamServers) of - [] -> - Response; - {NewPeer, NewPool} -> - %% leave only active upstream servers - NewOptions = lists:keyreplace(failover, 1, Options, {failover, NewPool}), - send_request(NewPeer, Request, NewOptions) + {error, _} = Error -> + maybe_failover(ServerRef, Tried, Req0, Error, Opts0) end. -%% @private -%% send_remote_request_loop/8 -send_remote_request_loop(ReplyPid, Socket, ReqId, Peer, EncRequest, Retries, Timeout, MetricsInfo) -> - ReplyPid ! {self(), send_request_loop(Socket, ReqId, Peer, EncRequest, Retries, Timeout, MetricsInfo)}. - -%% send_remote_request_loop/7 -send_request_loop(Socket, ReqId, Peer, Request = #radius_request{}, - Retries, Timeout, undefined) -> - send_request_loop(Socket, ReqId, Peer, Request, Retries, Timeout, eradius_lib:make_addr_info(Peer)); -send_request_loop(Socket, ReqId, Peer, Request, - Retries, Timeout, MetricsInfo) -> - {Authenticator, EncRequest} = eradius_lib:encode_request(Request), - send_request_loop(Socket, Peer, ReqId, Authenticator, EncRequest, - Timeout, Retries, MetricsInfo, Request#radius_request.secret, Request). - -%% send_remote_request_loop/10 -send_request_loop(_Socket, _Peer, _ReqId, _Authenticator, _EncRequest, - Timeout, 0, MetricsInfo, _Secret, Request) -> - TS = erlang:convert_time_unit(Timeout, millisecond, native), - update_client_request(timeout, MetricsInfo, TS, Request), - {error, timeout}; -send_request_loop(Socket, Peer = {_ServerName, {IP, Port}}, ReqId, Authenticator, EncRequest, - Timeout, RetryN, MetricsInfo, Secret, Request) -> +proceed_response(_ServerRef, _Tried, Req, {ok, Resp0}, _ServerName, _Opts) -> + Resp = eradius_req:record_metric(reply, #{request => Req}, Resp0), + {{ok, Resp}, Req}; + +proceed_response(ServerRef, Tried, Req0, {error, Error} = Response, ServerName, Opts) -> + Req = eradius_req:record_metric(discard, #{reason => Error, request => Req0}, Req0), + eradius_client_mngr:request_failed(ServerRef, ServerName), + maybe_failover(ServerRef, Tried, Req, Response, Opts). + +maybe_failover(ServerRef, Tried, Req, _Response, #{failover := [_|_] = FailOver} = Opts) -> + do_send_request(ServerRef, FailOver, Tried, Req, Opts#{failover := []}); +maybe_failover(_, _, Req, Response, _) -> + {Response, Req}. + +%% send_request_loop/4 +send_request_loop(Socket, ReqId, Req0, Opts) -> + {Packet, Req} = eradius_req:packet(Req0), + send_request_loop(Socket, ReqId, Packet, Opts, Req). + +%% send_request_loop/8 +send_request_loop(_Socket, _ReqId, _Packet, #{retries := 0}, Req) -> + {{error, timeout}, Req}; +send_request_loop(Socket, ReqId, Packet, + #{timeout := Timeout, retries := RetryN} = Opts, + #{server_addr := PeerAddress} = Req) -> Result = try - update_client_request(pending, MetricsInfo, 1, Request), - eradius_client_socket:send_request(Socket, {IP, Port}, ReqId, EncRequest, Timeout) + %% update_client_request(pending, 1), + eradius_client_socket:send_request(Socket, PeerAddress, ReqId, Packet, Timeout) after - update_client_request(pending, MetricsInfo, -1, Request) + %% update_client_request(pending, -1) + ok end, case Result of - {ok, Response} -> - {ok, Response, Secret, Authenticator}; + {ok, Header, Body} -> + {{ok, eradius_req:response(Header, Body, Req)}, Req}; {error, close} -> - {error, socket_down}; + {{error, socket_down}, Req}; {error, timeout} -> - TS = erlang:convert_time_unit(Timeout, millisecond, native), - update_client_request(retransmission, MetricsInfo, TS, Request), - send_request_loop(Socket, Peer, ReqId, Authenticator, EncRequest, - Timeout, RetryN - 1, MetricsInfo, Secret, Request); + ReqN = eradius_req:record_metric(retransmission, #{}, Req), + send_request_loop(Socket, ReqId, Packet, + Opts#{retries := RetryN - 1}, ReqN); {error, _} = Error -> - Error - end. - -%% @private -update_client_requests(MetricsInfo) -> - eradius_counter:inc_counter(requests, MetricsInfo). - -%% @private -update_client_request(pending, MetricsInfo, Pending, _) -> - if Pending =< 0 -> eradius_counter:dec_counter(pending, MetricsInfo); - true -> eradius_counter:inc_counter(pending, MetricsInfo) - end; -update_client_request(Cmd, MetricsInfo, Ms, Request) -> - eradius_counter:observe(eradius_client_request_duration_milliseconds, MetricsInfo, Ms, "Execution time of a RADIUS request"), - update_client_request_by_type(Cmd, MetricsInfo, Ms, Request). - -%% @private -update_client_request_by_type(request, MetricsInfo, Ms, _) -> - eradius_counter:observe(eradius_client_access_request_duration_milliseconds, MetricsInfo, Ms, "Access-Request execution time"), - eradius_counter:inc_counter(accessRequests, MetricsInfo); -update_client_request_by_type(accreq, MetricsInfo, Ms, Request) -> - eradius_counter:observe(eradius_client_accounting_request_duration_milliseconds, MetricsInfo, Ms, "Accounting-Request execution time"), - inc_request_counter_accounting(MetricsInfo, Request); -update_client_request_by_type(coareq, MetricsInfo, Ms, _) -> - eradius_counter:observe(eradius_client_coa_request_duration_milliseconds, MetricsInfo, Ms, "Coa request execution time"), - eradius_counter:inc_counter(coaRequests, MetricsInfo); -update_client_request_by_type(discreq, MetricsInfo, Ms, _) -> - eradius_counter:observe(eradius_client_disconnect_request_duration_milliseconds, MetricsInfo, Ms, "Disconnect execution time"), - eradius_counter:inc_counter(discRequests, MetricsInfo); -update_client_request_by_type(retransmission, MetricsInfo, _Ms, _) -> - eradius_counter:inc_counter(retransmissions, MetricsInfo); -update_client_request_by_type(timeout, MetricsInfo, _Ms, _) -> - eradius_counter:inc_counter(timeouts, MetricsInfo); -update_client_request_by_type(_, _, _, _) -> ok. - -%% @private -update_client_responses(MetricsInfo) -> eradius_counter:inc_counter(replies, MetricsInfo). - -%% @private -update_client_response(accept, MetricsInfo, _) -> eradius_counter:inc_counter(accessAccepts, MetricsInfo); -update_client_response(reject, MetricsInfo, _) -> eradius_counter:inc_counter(accessRejects, MetricsInfo); -update_client_response(challenge, MetricsInfo, _) -> eradius_counter:inc_counter(accessChallenges, MetricsInfo); -update_client_response(accresp, MetricsInfo, Request) -> inc_responses_counter_accounting(MetricsInfo, Request); -update_client_response(coanak, MetricsInfo, _) -> eradius_counter:inc_counter(coaNaks, MetricsInfo); -update_client_response(coaack, MetricsInfo, _) -> eradius_counter:inc_counter(coaAcks, MetricsInfo); -update_client_response(discnak, MetricsInfo, _) -> eradius_counter:inc_counter(discNaks, MetricsInfo); -update_client_response(discack, MetricsInfo, _) -> eradius_counter:inc_counter(discAcks, MetricsInfo); -update_client_response(dropped, MetricsInfo, _) -> eradius_counter:inc_counter(packetsDropped, MetricsInfo); -update_client_response(bad_authenticator, MetricsInfo, _) -> eradius_counter:inc_counter(badAuthenticators, MetricsInfo); -update_client_response(unknown_req_type, MetricsInfo, _) -> eradius_counter:inc_counter(unknownTypes, MetricsInfo); -update_client_response(_, _, _) -> ok. - -%%%========================================================================= -%%% internal functions -%%%========================================================================= - -parse_ip(undefined) -> - {ok, undefined}; -parse_ip(Address) when is_list(Address) -> - inet_parse:address(Address); -parse_ip(T = {_, _, _, _}) -> - {ok, T}; -parse_ip(T = {_, _, _, _, _, _, _, _}) -> - {ok, T}. - -make_metrics_info(Options, {ServerIP, ServerPort}) -> - ServerName = proplists:get_value(server_name, Options, undefined), - ClientName = proplists:get_value(client_name, Options, undefined), - ClientIP = application:get_env(eradius, client_ip, undefined), - {ok, ParsedClientIP} = parse_ip(ClientIP), - ClientAddrInfo = eradius_lib:make_addr_info({ClientName, {ParsedClientIP, undefined}}), - ServerAddrInfo = eradius_lib:make_addr_info({ServerName, {ServerIP, ServerPort}}), - {ClientAddrInfo, ServerAddrInfo}. - -inc_request_counter_accounting(MetricsInfo, #radius_request{attrs = Attrs}) -> - Requests = ets:match_spec_run(Attrs, client_request_counter_account_match_spec_compile()), - [eradius_counter:inc_counter(Type, MetricsInfo) || Type <- Requests], - ok; -inc_request_counter_accounting(_, _) -> - ok. - -inc_responses_counter_accounting(MetricsInfo, #radius_request{attrs = Attrs}) -> - Responses = ets:match_spec_run(Attrs, client_response_counter_account_match_spec_compile()), - [eradius_counter:inc_counter(Type, MetricsInfo) || Type <- Responses], - ok; -inc_responses_counter_accounting(_, _) -> - ok. - -update_server_status_metric(IP, Port, false, _Options) -> - eradius_counter:set_boolean_metric(server_status, [IP, Port], false); -update_server_status_metric(IP, Port, true, Options) -> - UpstreamServers = proplists:get_value(failover, Options, []), - %% set all servesr from pool as inactive - if is_list(UpstreamServers) -> - lists:foreach( - fun (Server) -> - case Server of - {ServerIP, ServerPort, _} -> - eradius_counter:set_boolean_metric(server_status, [ServerIP, ServerPort], false); - {ServerIP, ServerPort, _, _} -> - eradius_counter:set_boolean_metric(server_status, [ServerIP, ServerPort], false); - _ -> - ok - end - - end, UpstreamServers); - true -> - ok - end, - %% set current service as active - eradius_counter:set_boolean_metric(server_status, [IP, Port], true). - -client_request_counter_account_match_spec_compile() -> - case persistent_term:get({?MODULE, ?FUNCTION_NAME}, undefined) of - undefined -> - MatchSpecCompile = - ets:match_spec_compile( - ets:fun2ms( - fun ({?RStatus_Type, ?RStatus_Type_Start}) -> accountRequestsStart; - ({?RStatus_Type, ?RStatus_Type_Stop}) -> accountRequestsStop; - ({?RStatus_Type, ?RStatus_Type_Update}) -> accountRequestsUpdate; - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Start}) -> accountRequestsStart; - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Stop}) -> accountRequestsStop; - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Update}) -> accountRequestsUpdate end)), - persistent_term:put({?MODULE, ?FUNCTION_NAME}, MatchSpecCompile), - MatchSpecCompile; - MatchSpecCompile -> - MatchSpecCompile - end. - -client_response_counter_account_match_spec_compile() -> - case persistent_term:get({?MODULE, ?FUNCTION_NAME}, undefined) of - undefined -> - MatchSpecCompile = - ets:match_spec_compile( - ets:fun2ms( - fun ({?RStatus_Type, ?RStatus_Type_Start}) -> accountResponsesStart; - ({?RStatus_Type, ?RStatus_Type_Stop}) -> accountResponsesStop; - ({?RStatus_Type, ?RStatus_Type_Update}) -> accountResponsesUpdate; - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Start}) -> accountResponsesStart; - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Stop}) -> accountResponsesStop; - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Update}) -> accountResponsesUpdate end)), - persistent_term:put({?MODULE, ?FUNCTION_NAME}, MatchSpecCompile), - MatchSpecCompile; - MatchSpecCompile -> - MatchSpecCompile - end. - -get_ip(Host) -> - case inet:gethostbyname(Host) of - {ok, #hostent{h_addrtype = inet, h_addr_list = [IP]}} -> - IP; - {ok, #hostent{h_addrtype = inet, h_addr_list = [_ | _] = IPs}} -> - Index = rand:uniform(length(IPs)), - lists:nth(Index, IPs); - _ -> error(badarg) + {Error, Req} end. diff --git a/src/eradius_client_mngr.erl b/src/eradius_client_mngr.erl index 3a2e1a20..b1490baa 100644 --- a/src/eradius_client_mngr.erl +++ b/src/eradius_client_mngr.erl @@ -3,149 +3,198 @@ %% %% SPDX-License-Identifier: MIT %% +%% @doc This module contains the management logic for the RADIUS client instances. +%% A counter is kept for every client instance in order to determine the next request id and sender port +%% for each outgoing request. +%% +%% The client uses OS-assigned ports. The maximum number of open ports can be specified through the +%% ``client_ports'' option, it defaults to ``20''. The number of ports should not +%% be set too low. If ``N'' ports are opened, the maximum number of concurrent requests is ``N * 256''. +%% +%% The IP address used to send requests is configured through the ``ip'' option. +%% Changing it currently requires a restart. It can be given as a string or ip address tuple, +%% or the atom ``any'' (the default), which uses whatever address the OS selects. -module(eradius_client_mngr). +-feature(maybe_expr, enable). -behaviour(gen_server). %% external API --export([start_link/0, wanna_send/1, wanna_send/2, reconfigure/0, reconfigure/1]). +-export([start_client/1, start_client/2]). %% internal API --export([store_radius_server_from_pool/3, - request_failed/3, - restore_upstream_server/1, - find_suitable_peer/1]). +-export([start_link/2, start_link/3]). +-export([wanna_send/3, reconfigure/2]). +-export([request_failed/2]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -ifdef(TEST). --export([get_state/0, servers/0, servers/1, init_server_status_metrics/0]). +-export([get_state/1, servers/1, server/2, get_socket_count/1]). +-ignore_xref([get_state/1, servers/1, server/2, get_socket_count/1]). -endif. +-ignore_xref([start_client/1, start_client/2]). +-ignore_xref([start_link/2, start_link/3]). +-ignore_xref([reconfigure/2]). + -include_lib("kernel/include/logger.hrl"). -include_lib("kernel/include/inet.hrl"). -include("eradius_internal.hrl"). +-type server_name() :: atom() | binary(). +%% Name of RADIUS server (or client). + +-type server_opts() :: #{ip := inet:ip_address(), + port := inet:port_number(), + secret := binary, + retries => non_neg_integer(), + timeout => non_neg_integer()}. +%% Options to describe a RADIUS server. + +-type server() :: #{ip := inet:ip_address(), + port := inet:port_number(), + secret := binary, + retries := non_neg_integer(), + timeout := non_neg_integer(), + failed := non_neg_integer()}. +%% Options to describe a RADIUS server. +%% Conceptually the same as `t:server_opts/0', except that may fields are mandatory. + +-type server_pool() :: [server_name()]. +%% List of server names that form a pool. + +-type servers() :: #{server_name() := server() | server_pool()}. +%% Map of server and pool definition. Key is the name of the entry. + -type client_opts() :: - #{family => inet | inet6, + #{name => server_name(), + servers := #{server_name() := server_opts() | server_pool()}, + family => inet | inet6, ip => any | inet:ip_address(), active_n => once | non_neg_integer(), no_ports => non_neg_integer(), recbuf => non_neg_integer(), sndbuf => non_neg_integer(), - server_pool => [term()], - servers => [term()]}. + metrics_callback => eradius_req:metrics_callback() + }. +%% Options to configure the RADIUS client. + -type client_config() :: - #{family := inet | inet6, + #{name := server_name(), + servers := servers(), + family := inet | inet6, ip := any | inet:ip_address(), active_n := once | non_neg_integer(), no_ports := non_neg_integer(), recbuf := non_neg_integer(), sndbuf := non_neg_integer(), - servers_pool => [term()], - servers => [term()]}. + metrics_callback := 'undefined' | eradius_req:metrics_callback() + }. +%% Options to configure the RADIUS client. +%% Conceptually the same as `t:client_opts/0', except that may fields are mandatory. --export_type([client_config/0]). +-export_type([server_name/0, server_pool/0, servers/0, client_opts/0]). -record(state, { + owner :: pid(), config :: client_config(), + client_name :: server_name(), + client_addr :: any | inet:ip_address(), + servers :: servers(), socket_id :: {Family :: inet | inet6, IP :: any | inet:ip_address()}, no_ports = 1 :: pos_integer(), idcounters = maps:new() :: map(), sockets = array:new() :: array:array(), - clients = [] :: [{{integer(),integer(),integer(),integer()}, integer()}] + metrics_callback :: undefined | eradius_req:metrics_callback() }). --define(SERVER, ?MODULE). - -define(RECONFIGURE_TIMEOUT, 15000). +-define(DEFAULT_MAX_RETRIES, 20). +-define(DEFAULT_DOWN_TIME, 1000). %%%========================================================================= %%% API %%%========================================================================= -start_link() -> - case client_config(default_client_opts()) of - {ok, Config} -> gen_server:start_link({local, ?SERVER}, ?MODULE, [Config], []); - {error, _} = Error -> Error +%% @doc Start a new RADIUS client that is managed by the eradius applications supervisor tree. +-spec start_client(client_opts()) -> + {ok, pid()} | {error, supervisor:startchild_err()}. +start_client(Opts) -> + eradius_client_top_sup:start_client([Opts]). + +%% @doc Start a new, named RADIUS client that is managed by the eradius applications supervisor tree. +-spec start_client(server_name(), client_opts()) -> + {ok, pid()} | {error, supervisor:startchild_err()}. +start_client(ServerName, Opts) -> + maybe + ok ?= check_already_started(ServerName), + eradius_client_top_sup:start_client([ServerName, Opts]) end. -wanna_send(Peer) -> - gen_server:call(?SERVER, {wanna_send, Peer}). +%% @private +-spec start_link(pid(), client_opts()) -> + {ok, pid()} | {error, supervisor:startchild_err()}. +start_link(Owner, Opts) -> + maybe + {ok, Config} ?= client_config(maps:merge(default_client_opts(), Opts)), + gen_server:start_link(?MODULE, [Owner, Config], []) + end. -wanna_send(Node, Peer) -> - gen_server:call({?SERVER, Node}, {wanna_send, Peer}). +%% @private +-spec start_link(pid(), server_name(), client_opts()) -> + {ok, pid()} | {error, supervisor:startchild_err()}. +start_link(Owner, ServerName, Opts) -> + maybe + ok ?= check_already_started(ServerName), + {ok, Config} ?= client_config(maps:merge(default_client_opts(), Opts)), + gen_server:start_link(ServerName, ?MODULE, [Owner, Config], []) + end. %% @private -reconfigure() -> - %% backward compatibility wrapper - (catch reconfigure(#{})). +wanna_send(Server, Peer, Tried) -> + gen_server:call(Server, {wanna_send, Peer, Tried}). -%% @doc reconfigure the Radius client -reconfigure(Opts) -> - gen_server:call(?SERVER, {reconfigure, Opts}, ?RECONFIGURE_TIMEOUT). - -request_failed(ServerIP, Port, Options) -> - case ets:lookup(?MODULE, {ServerIP, Port}) of - [{{ServerIP, Port}, Retries, InitialRetries}] -> - FailedTries = proplists:get_value(retries, Options, ?DEFAULT_RETRIES), - %% Mark the given RADIUS server as 'non-active' if there were more tries - %% than possible - if FailedTries >= Retries -> - ets:delete(?MODULE, {ServerIP, Port}), - Timeout = application:get_env(eradius, unreachable_timeout, 60), - timer:apply_after(Timeout * 1000, ?MODULE, restore_upstream_server, - [{ServerIP, Port, InitialRetries, InitialRetries}]); - true -> - %% RADIUS client tried to send a request to the {ServierIP, Port} RADIUS - %% server. There were done FailedTries tries and all of them failed. - %% So decrease amount of tries for the given RADIUS server that - %% that will be used for next RADIUS requests towards this RADIUS server. - ets:update_counter(?MODULE, {ServerIP, Port}, -FailedTries) - end; - [] -> - ok - end. +%% @private +request_failed(Server, Peer) -> + gen_server:call(Server, {failed, Peer}). -restore_upstream_server({ServerIP, Port, Retries, InitialRetries}) -> - ets:insert(?MODULE, {{ServerIP, Port}, Retries, InitialRetries}). - -find_suitable_peer(undefined) -> - []; -find_suitable_peer([]) -> - []; -find_suitable_peer([{Host, Port, Secret} | Pool]) when is_list(Host) -> - try - IP = get_ip(Host), - find_suitable_peer([{IP, Port, Secret} | Pool]) - catch _:_ -> - %% can't resolve ip by some reasons, just ignore it - find_suitable_peer(Pool) - end; -find_suitable_peer([{IP, Port, Secret} | Pool]) -> - case ets:lookup(?MODULE, {IP, Port}) of - [] -> - find_suitable_peer(Pool); - [{{IP, Port}, _Retries, _InitialRetries}] -> - {{IP, Port, Secret}, Pool} - end; -find_suitable_peer([{IP, Port, Secret, _Opts} | Pool]) -> - find_suitable_peer([{IP, Port, Secret} | Pool]). +%% @doc reconfigure the Radius client +reconfigure(ServerRef, Opts) -> + gen_server:call(ServerRef, {reconfigure, Opts}, ?RECONFIGURE_TIMEOUT). -ifdef(TEST). -get_state() -> - State = sys:get_state(?SERVER), +get_state(ServerRef) -> + State = sys:get_state(ServerRef), Keys = record_info(fields, state), Values = tl(tuple_to_list(State)), maps:from_list(lists:zip(Keys, Values)). -servers() -> - ets:tab2list(?MODULE). - -servers(Key) -> - ets:lookup(?MODULE, Key). +get_socket_count(ServerRef) -> + #state{owner = Owner} = sys:get_state(ServerRef), + {ok, SockSup} = eradius_client_sup:socket_supervisor(Owner), + Counts = supervisor:count_children(SockSup), + proplists:get_value(active, Counts). + +servers(ServerRef) -> + #state{servers = Servers} = sys:get_state(ServerRef), + maps:fold( + fun(_, #{ip := IP, port := Port, retries := Retries, failed := Failed} = _, M) + when Failed < Retries -> + [{{IP, Port}, Retries, Failed} | M]; + (_, _, M) -> M + end, [], Servers). + +server(ServerRef, Key) -> + #state{servers = Servers} = sys:get_state(ServerRef), + case Servers of + #{Key := #{ip := IP, port := Port, retries := Retries, failed := Failed}} -> + {{IP, Port}, Retries, Failed}; + _ -> + undefined + end. -endif. @@ -153,38 +202,70 @@ servers(Key) -> %%% gen_server callbacks %%%=================================================================== -init([#{no_ports := NPorts} = Config]) -> - ets:new(?MODULE, [public, named_table, ordered_set, {keypos, 1}, {write_concurrency, true}]), - prepare_pools(Config), - +%% @private +init([Owner, #{name := ClientName, servers := Servers, + ip := IP, no_ports := NPorts, + metrics_callback := MetricsCallback} = Config]) -> + process_flag(trap_exit, true), + ?LOG(info, "Starting RADIUS client"), State = #state{ + client_name = ClientName, + client_addr = IP, + owner = Owner, config = Config, + servers = Servers, socket_id = socket_id(Config), - no_ports = NPorts}, + no_ports = NPorts, + metrics_callback = MetricsCallback + }, {ok, State}. %% @private -handle_call({wanna_send, Peer = {_PeerName, PeerSocket}}, _From, - #state{config = Config, - no_ports = NoPorts, idcounters = IdCounters, - sockets = Sockets, clients = Clients} = State0) -> - {PortIdx, ReqId, NewIdCounters} = next_port_and_req_id(PeerSocket, NoPorts, IdCounters), - {SocketProcess, NewSockets} = find_socket_process(PortIdx, Sockets, Config), - State1 = State0#state{idcounters = NewIdCounters, sockets = NewSockets}, - State = - case lists:member(Peer, Clients) of - false -> State1#state{clients = [Peer | Clients]}; - true -> State1 +handle_call({wanna_send, Candidates, Tried}, _From, + #state{ + client_name = ClientName, + client_addr = ClientAddr, + servers = Servers, + no_ports = NoPorts, idcounters = IdCounters, + sockets = Sockets, + metrics_callback = MetricsCallback} = State0) -> + case select_server(Candidates, Tried, Servers) of + {ok, {ServerName, #{ip := IP, port := Port} = Server}} -> + ServerAddr = {IP, Port}, + {PortIdx, ReqId, NewIdCounters} = + next_port_and_req_id(ServerAddr, NoPorts, IdCounters), + {SocketProcess, NewSockets} = find_socket_process(PortIdx, Sockets, State0), + State = State0#state{idcounters = NewIdCounters, sockets = NewSockets}, + ReqInfo = + #{server => ServerName, server_addr => ServerAddr, + client => ClientName, client_addr => ClientAddr, + metrics_callback => MetricsCallback}, + Reply = {ok, {SocketProcess, ReqId, ServerName, Server, ReqInfo}}, + {reply, Reply, State}; + {error, _} = Error -> + {reply, Error, State0} + end; + +handle_call({failed, Peer}, _From, #state{servers = Servers0} = State0) -> + Servers = + case Servers0 of + #{Peer := #{retries := Retries, failed := Failed} = Server} + when Failed < Retries -> + Servers0#{Peer := Server#{failed := Failed + 1}}; + #{Peer := #{retries := Retries, failed := Failed} = Server} + when Failed =:= Retries -> + erlang:start_timer(?DEFAULT_DOWN_TIME, self(), {reset, Peer}), + Servers0#{Peer := Server#{failed := Failed + 1}}; + _ -> + Servers0 end, - {reply, {SocketProcess, ReqId}, State}; + State = State0#state{servers = Servers}, + {reply, ok, State}; %% @private handle_call({reconfigure, Opts}, _From, #state{config = OConfig} = State0) -> case client_config(maps:merge(OConfig, Opts)) of {ok, Config} -> - ets:delete_all_objects(?MODULE), - prepare_pools(Config), - State = reconfigure_address(Config, State0#state{config = Config}), {reply, ok, State}; @@ -199,11 +280,25 @@ handle_call(_OtherCall, _From, State) -> %% @private handle_cast(_Msg, State) -> {noreply, State}. +%% @private +handle_info({timeout, _, {reset, Peer}}, #state{servers = Servers0} = State0) -> + Servers = + case Servers0 of + #{Peer := Server} -> + Servers0#{Peer := Server#{failed := 0}}; + _ -> + Servers0 + end, + State = State0#state{servers = Servers}, + {noreply, State}; + handle_info(_Info, State) -> - {noreply, State}. + {noreply, State}. %% @private -terminate(_Reason, _State) -> ok. +terminate(Reason, _State) -> + ?LOG(info, "RADIUS client stopped with ~p", [Reason]), + ok. %% @private code_change(_OldVsn, State, _Extra) -> {ok, State}. @@ -212,6 +307,20 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. %%% internal functions %%%========================================================================= +check_already_started(Name) -> + case where(Name) of + Pid when is_pid(Pid) -> + {error, {already_started, Pid}}; + undefined -> + ok + end. + +where({global, Name}) -> global:whereis_name(Name); +where({via, Module, Name}) -> Module:whereis_name(Name); +where({local, Name}) -> whereis(Name); +where(ServerName) -> + error(badarg, [ServerName]). + socket_id(#{family := Family, ip := IP}) -> {Family, IP}. @@ -220,97 +329,117 @@ socket_id_str({_, IP}) when is_tuple(IP) -> socket_id_str({_, IP}) when is_atom(IP) -> atom_to_list(IP). -get_ip(Host) -> - case inet:gethostbyname(Host) of - {ok, #hostent{h_addrtype = inet, h_addr_list = [IP]}} -> - IP; - {ok, #hostent{h_addrtype = inet, h_addr_list = [_ | _] = IPs}} -> - Index = rand:uniform(length(IPs)), - lists:nth(Index, IPs); - _ -> error(badarg) - end. - %% @private --spec default_client_opts() -> client_opts(). default_client_opts() -> - #{ip => application:get_env(eradius, client_ip, any), - no_ports => application:get_env(eradius, client_ports, 10), - active_n => application:get_env(eradius, active_n, 100), - recbuf => application:get_env(eradius, recbuf, 8192), - sndbuf => application:get_env(eradius, sndbuf, 131072), - servers_pool => application:get_env(eradius, servers_pool, []), - servers => application:get_env(eradius, servers, []) + #{family => inet6, + ip => any, + no_ports => 10, + active_n => 100, + recbuf => 8192, + sndbuf => 131072, + metrics_callback => undefined }. +socket_ip(inet, {_, _, _, _} = IP) -> + IP; +socket_ip(inet6, {_, _, _, _} = IP) -> + inet:ipv4_mapped_ipv6_address(IP); +socket_ip(inet6, {_, _, _, _,_, _, _, _} = IP) -> + IP. + +select_server(Candidates, Tried, Servers) -> + case select_servers(Candidates, Servers, []) -- Tried of + [] -> + {error, no_active_servers}; + PL -> + N = rand:uniform(length(PL)), + ServerName = lists:nth(N, PL), + {ok, {ServerName, maps:get(ServerName, Servers)}} + end. + +select_servers([], _Servers, Selected) -> + Selected; +select_servers([Candidate|More], Servers, Selected) -> + case Servers of + #{Candidate := [_|_] = Pool} -> + select_servers(More, Servers, select_servers(Pool, Servers, Selected)); + #{Candidate := #{retries := Retries, failed := Failed}} + when Failed < Retries -> + select_servers(More, Servers, [Candidate | Selected]); + _ -> + select_servers(More, Servers, Selected) + end. -spec client_config(client_opts()) -> {ok, client_config()} | {error, _}. -client_config(#{ip := IP} = Opts) when is_atom(IP) -> - {ok, Opts#{family => inet6, ip := any}}; -client_config(#{ip := {_, _, _, _}} = Opts) -> - {ok, Opts#{family => inet}}; -client_config(#{ip := {_, _, _, _, _, _, _, _}} = Opts) -> - {ok, Opts#{family => inet6}}; -client_config(#{ip := Address} = Opts) when is_list(Address) -> +client_config_ip(#{ip := IP} = Opts) when is_atom(IP) -> + {ok, Opts#{ip := any}}; +client_config_ip(#{family := Family, ip := IP} = Opts) when is_tuple(IP) -> + {ok, Opts#{ip := socket_ip(Family, IP)}}; +client_config_ip(#{ip := Address} = Opts) when is_list(Address) -> case inet_parse:address(Address) of - {ok, {_, _, _, _} = IP} -> - {ok, Opts#{family => inet, ip => IP}}; - {ok, {_, _, _, _, _, _, _, _} = IP} -> - {ok, Opts#{family => inet6, ip => IP}}; + {ok, IP} -> + client_config_ip(Opts#{ip => IP}); _ -> ?LOG(error, "Invalid RADIUS client IP (parsing failed): ~p", [Address]), {error, {bad_client_ip, Address}} end. -%% private -prepare_pools(#{servers_pool := PoolList, servers := ServerList}) -> - lists:foreach(fun({_PoolName, Servers}) -> prepare_pool(Servers) end, PoolList), - lists:foreach(fun(Server) -> store_upstream_servers(Server) end, ServerList), - init_server_status_metrics(). - -prepare_pool([]) -> ok; -prepare_pool([{Addr, Port, _, Opts} | Servers]) -> - Retries = proplists:get_value(retries, Opts, ?DEFAULT_RETRIES), - store_radius_server_from_pool(Addr, Port, Retries), - prepare_pool(Servers); -prepare_pool([{Addr, Port, _} | Servers]) -> - store_radius_server_from_pool(Addr, Port, ?DEFAULT_RETRIES), - prepare_pool(Servers). - -store_upstream_servers({Server, _}) -> - store_upstream_servers(Server); -store_upstream_servers({Server, _, _}) -> - store_upstream_servers(Server); -store_upstream_servers(Server) -> - %% TBD: move proxy config into the proxy logic... - - HandlerDefinitions = application:get_env(eradius, Server, []), - UpdatePoolFn = fun (HandlerOpts) -> - {DefaultRoute, Routes, Retries} = eradius_proxy:get_routes_info(HandlerOpts), - eradius_proxy:put_default_route_to_pool(DefaultRoute, Retries), - eradius_proxy:put_routes_to_pool(Routes, Retries) - end, - lists:foreach(fun (HandlerDefinition) -> - case HandlerDefinition of - {{_, []}, _} -> ok; - {{_, _, []}, _} -> ok; - {{_, HandlerOpts}, _} -> UpdatePoolFn(HandlerOpts); - {{_, _, HandlerOpts}, _} -> UpdatePoolFn(HandlerOpts); - _HandlerDefinition -> ok - end - end, - HandlerDefinitions). - -%% private -store_radius_server_from_pool(Addr, Port, Retries) - when is_tuple(Addr), is_integer(Port), is_integer(Retries) -> - ets:insert(?MODULE, {{Addr, Port}, Retries, Retries}); -store_radius_server_from_pool(Addr, Port, Retries) - when is_list(Addr), is_integer(Port), is_integer(Retries) -> - IP = get_ip(Addr), - ets:insert(?MODULE, {{IP, Port}, Retries, Retries}); -store_radius_server_from_pool(Addr, Port, Retries) -> - ?LOG(error, "bad RADIUS upstream server specified in RADIUS servers pool configuration ~p", [{Addr, Port, Retries}]), - error(badarg). +client_config_servers(none, _, Servers) -> + {ok, Servers}; +client_config_servers({ServerName, #{ip := IP, port := _, secret := _} = SIn, Next}, + #{family := Family} = Opts, Servers) -> + Server = SIn#{ip := socket_ip(Family, IP), + retries => maps:get(retries, SIn, ?DEFAULT_MAX_RETRIES), + failed => 0}, + client_config_servers(maps:next(Next), Opts, Servers#{ServerName => Server}); +client_config_servers({ServerPoolName, [_|_] = Pool, Next}, + #{servers := CfgServers} = Opts, Servers) -> + HasAll = lists:all(fun(SrvId) -> is_map_key(SrvId, CfgServers) end, Pool), + case HasAll of + true -> client_config_servers(maps:next(Next), Opts, Servers#{ServerPoolName => Pool}); + false -> {error, {server_definition_missing, Pool}} + end; +client_config_servers({ServerName, _, _}, _, _) -> + {error, {mandatory_opts_missing, ServerName}}. + +client_config_servers(#{servers := Servers} = Opts) -> + maybe + {ok, NewServers} ?= + client_config_servers(maps:next(maps:iterator(Servers)), Opts, #{}), + {ok, Opts#{servers := NewServers}} + end. + +client_config_name(#{name := _} = Opts) -> + {ok, Opts}; +client_config_name(#{netdev := NetDev} = Opts) -> + client_config_name([$%, NetDev], Opts); +client_config_name(#{netns := NetNS} = Opts) -> + client_config_name([$@, NetNS], Opts); +client_config_name(Opts) -> + client_config_name([], Opts). + +client_config_name(Tag, #{family := inet6, ip := IP, ipv6_v6only := true} = Opts) + when IP =:= any; IP =:= {0, 0, 0, 0, 0, 0, 0, 0} -> + client_config_name("*", Tag, Opts); +client_config_name(Tag, #{family := inet6, ip := any} = Opts) -> + client_config_name("[::]", Tag, Opts); +client_config_name(Tag, #{family := inet, ip := any} = Opts) -> + client_config_name("[0.0.0.0]", Tag, Opts); +client_config_name(Tag, #{family := inet6, ip := IP} = Opts) -> + client_config_name([$[, inet:ntoa(IP), $]], Tag, Opts); +client_config_name(Tag, #{family := inet, ip := IP} = Opts) -> + client_config_name(inet:ntoa(IP), Tag, Opts). + +client_config_name(IP, Tag, Opts) -> + {ok, Opts#{name => iolist_to_binary([IP, Tag])}}. + +client_config(Opts0) -> + maybe + {ok, Opts1} ?= client_config_ip(Opts0), + {ok, Opts2} ?= client_config_servers(Opts1), + {ok, Opts} ?= client_config_name(Opts2), + {ok, Opts#{metrics_callback => maps:get(metrics_callback, Opts0, undefined)}} + end. reconfigure_address(#{no_ports := NPorts} = Config, #state{socket_id = OAdd, sockets = Sockts} = State) -> @@ -375,25 +504,12 @@ next_port_and_req_id(Peer, NumberOfPorts, Counters) -> NewCounters = Counters#{Peer => {NextPortIdx, NextReqId}}, {NextPortIdx, NextReqId, NewCounters}. -find_socket_process(PortIdx, Sockets, Config) -> +find_socket_process(PortIdx, Sockets, #state{owner = Owner, config = Config}) -> case array:get(PortIdx, Sockets) of undefined -> - {ok, Socket} = eradius_client_socket:new(Config), + {ok, Supervisor} = eradius_client_sup:socket_supervisor(Owner), + {ok, Socket} = eradius_client_socket:new(Supervisor, Config), {Socket, array:set(PortIdx, Socket, Sockets)}; Socket -> {Socket, Sockets} end. - -%% @private -init_server_status_metrics() -> - case application:get_env(eradius, server_status_metrics_enabled, false) of - false -> - ok; - true -> - %% That will be called at eradius startup and we must be sure that prometheus - %% application already started if server status metrics supposed to be used - application:ensure_all_started(prometheus), - ets:foldl(fun ({{Addr, Port}, _, _}, _Acc) -> - eradius_counter:set_boolean_metric(server_status, [Addr, Port], false) - end, [], ?MODULE) - end. diff --git a/src/eradius_client_socket.erl b/src/eradius_client_socket.erl index 764f0f98..1515744f 100644 --- a/src/eradius_client_socket.erl +++ b/src/eradius_client_socket.erl @@ -3,24 +3,27 @@ %% %% SPDX-License-Identifier: MIT %% +%% @private -module(eradius_client_socket). -behaviour(gen_server). %% API --export([new/1, start_link/1, send_request/5, close/1]). +-export([new/2, start_link/1, send_request/5, close/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +-ignore_xref([start_link/1]). + -record(state, {family, socket, active_n, pending, mode, counter}). %%%========================================================================= %%% API %%%========================================================================= -new(Config) -> - eradius_client_socket_sup:new(Config). +new(Supervisor, Config) -> + eradius_client_socket_sup:new(Supervisor, Config). start_link(Config) -> gen_server:start_link(?MODULE, [Config], []). @@ -88,21 +91,17 @@ handle_info({udp_passive, _Socket}, #state{socket = Socket, active_n = ActiveN} handle_info({udp, Socket, FromIP, FromPort, Response}, State = #state{socket = Socket, mode = Mode}) -> - case eradius_lib:decode_request_id(Response) of - {ReqId, Response} -> - NState = request_done({FromIP, FromPort, ReqId}, {ok, Response}, State), - case Mode of - inactive when map_size(State#state.pending) =:= 0 -> - {stop, normal, NState}; - _ -> - flow_control(NState), - {noreply, NState} - end; - {bad_pdu, _} -> - %% discard reply because it was malformed - flow_control(State), - {noreply, State} - end; + flow_control(State), + NState = + case Response of + <<Header:20/binary, Body/binary>> -> + <<_, ReqId:8, _/binary>> = Header, + request_done({FromIP, FromPort, ReqId}, {ok, Header, Body}, State); + _ -> + %% discard reply because it was malformed + State + end, + noreply_or_stop(NState); handle_info({timeout, TRef, ReqKey}, #state{pending = Pending} = State) -> NState = @@ -113,7 +112,7 @@ handle_info({timeout, TRef, ReqKey}, #state{pending = Pending} = State) -> _ -> State end, - {noreply, NState}; + noreply_or_stop(NState); handle_info(_Info, State) -> {noreply, State}. @@ -133,6 +132,12 @@ flow_control(#state{socket = Socket, active_n = once}) -> flow_control(_) -> ok. +noreply_or_stop(#state{pending = Pending, mode = inactive} = State) + when map_size(Pending) =:= 0 -> + {stop, normal, State}; +noreply_or_stop(State) -> + {noreply, State}. + pending_request(ReqKey, From, Timeout, #state{pending = Pending} = State) -> TRef = erlang:start_timer(Timeout, self(), ReqKey), diff --git a/src/eradius_client_socket_sup.erl b/src/eradius_client_socket_sup.erl index 954156be..db2d5957 100644 --- a/src/eradius_client_socket_sup.erl +++ b/src/eradius_client_socket_sup.erl @@ -1,13 +1,20 @@ +%% Copyright (c) 2024 Travelping GmbH <info@travelping.com> +%% +%% SPDX-License-Identifier: MIT +%% +%% @private -module(eradius_client_socket_sup). -behaviour(supervisor). %% API --export([start_link/0, new/1]). +-export([start_link/0, new/2]). %% Supervisor callbacks -export([init/1]). +-ignore_xref([start_link/0]). + -define(SERVER, ?MODULE). %%%=================================================================== @@ -20,10 +27,10 @@ {error, term()} | ignore. start_link() -> - supervisor:start_link({local, ?SERVER}, ?MODULE, []). + supervisor:start_link(?MODULE, []). -new(Config) -> - supervisor:start_child(?SERVER, [Config]). +new(Supervisor, Config) -> + supervisor:start_child(Supervisor, [Config]). %%%=================================================================== %%% Supervisor callbacks diff --git a/src/eradius_client_sup.erl b/src/eradius_client_sup.erl index 6db9d435..e8d63ea0 100644 --- a/src/eradius_client_sup.erl +++ b/src/eradius_client_sup.erl @@ -1,33 +1,40 @@ +%% Copyright (c) 2024 Travelping GmbH <info@travelping.com> +%% +%% SPDX-License-Identifier: MIT +%% +%% @private -module(eradius_client_sup). -behaviour(supervisor). %% API --export([start_link/1, new/2]). +-export([start_link/1, socket_supervisor/1]). %% Supervisor callbacks -export([init/1]). +-ignore_xref([start_link/1]). + -define(SERVER, ?MODULE). %%%=================================================================== %%% API functions %%%=================================================================== --spec start_link(Config :: eradius_client:client_config()) -> +-spec start_link(Config :: eradius_client_mngr:client_opts()) -> {ok, Pid :: pid()} | {error, {already_started, Pid :: pid()}} | {error, {shutdown, term()}} | {error, term()} | ignore. -start_link(Config) -> - supervisor:start_link(?MODULE, [Config]). +start_link(Opts) -> + supervisor:start_link(?MODULE, Opts). -new(Owner, SocketId) -> +socket_supervisor(Owner) -> Children = supervisor:which_children(Owner), case lists:keyfind(eradius_client_socket_sup, 1, Children) of - {eradius_client_socket_sup, SupPid, _, _} when is_pid(SupPid) -> - supervisor:start_child(SupPid, [SocketId]); + {eradius_client_socket_sup, Pid, _, _} when is_pid(Pid) -> + {ok, Pid}; _ -> {error, dead} end. @@ -40,26 +47,26 @@ new(Owner, SocketId) -> {ok, {SupFlags :: supervisor:sup_flags(), [ChildSpec :: supervisor:child_spec()]}} | ignore. -init([Opts]) -> +init(Opts) -> SupFlags = #{strategy => one_for_one, intensity => 5, period => 10}, - Client = - #{id => eradius_client, - start => {eradius_client, start_link, [self(), Opts]}, - restart => permanent, - shutdown => 5000, - type => worker, - modules => [eradius_client]}, - SocketSup = + ClientSocketSup = #{id => eradius_client_socket_sup, start => {eradius_client_socket_sup, start_link, []}, restart => permanent, shutdown => 5000, type => supervisor, modules => [eradius_client_socket_sup]}, + ClientMngr = + #{id => eradius_client_mngr, + start => {eradius_client_mngr, start_link, [self() | Opts]}, + restart => permanent, + shutdown => 5000, + type => worker, + modules => [eradius_client_mngr]}, - {ok, {SupFlags, [Client, SocketSup]}}. + {ok, {SupFlags, [ClientSocketSup, ClientMngr]}}. %%%=================================================================== %%% Internal functions diff --git a/src/eradius_client_top_sup.erl b/src/eradius_client_top_sup.erl new file mode 100644 index 00000000..fff1e951 --- /dev/null +++ b/src/eradius_client_top_sup.erl @@ -0,0 +1,60 @@ +%% Copyright (c) 2024 Travelping GmbH <info@travelping.com> +%% +%% SPDX-License-Identifier: MIT +%% +%% @private +-module(eradius_client_top_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0, start_client/1]). + +%% Supervisor callbacks +-export([init/1]). + +-ignore_xref([start_link/0]). + +-define(SERVER, ?MODULE). + +%%%=================================================================== +%%% API functions +%%%=================================================================== + +-spec start_link() -> {ok, Pid :: pid()} | + {error, {already_started, Pid :: pid()}} | + {error, {shutdown, term()}} | + {error, term()} | + ignore. +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +start_client(Opts) -> + supervisor:start_child(?SERVER, [Opts]). + +%%%=================================================================== +%%% Supervisor callbacks +%%%=================================================================== + +-spec init(Args :: term()) -> + {ok, {SupFlags :: supervisor:sup_flags(), + [ChildSpec :: supervisor:child_spec()]}} | + ignore. +init([]) -> + SupFlags = #{strategy => simple_one_for_one, + intensity => 1, + period => 5}, + + ClientSup = + #{id => eradius_client_sup, + start => {eradius_client_sup, start_link, []}, + restart => permanent, + shutdown => 5000, + type => supervisor, + modules => [eradius_client_sup]}, + + {ok, {SupFlags, [ClientSup]}}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/src/eradius_config.erl b/src/eradius_config.erl deleted file mode 100644 index b0ea1e2b..00000000 --- a/src/eradius_config.erl +++ /dev/null @@ -1,401 +0,0 @@ --module(eradius_config). - % Eradius API's: --export([validate_new_config/0, validate_new_config/2, validate_config/1]). - % Config validating API functions: --export([get_app_env/2, validate_ip/1, validate_port/1, validate_ports/1, - map_helper/3, map_helper/2, ok_error_helper/2, validate_secret/1, - validate_options/1, validate_socket_options/1, validate_server/1]). --export([generate_ip_list/2]). - -%% ------------------------------------------------------------------------------------------ -%% -- config validation --define(pos_int(X), is_integer(X), X >= 0). --define(ip4_address_num(X), ?pos_int(X), X < 256). --define(ip4_address(T), ?ip4_address_num(element(1, T)), ?ip4_address_num(element(2, T)), - ?ip4_address_num(element(3, T)), ?ip4_address_num(element(4, T))). --define(valid_atom(Value), Value =/= invalid). --define(valid(X), is_tuple(X), ?valid_atom(element(1, X))). --define(is_io(IO), is_list(IO) orelse is_binary(IO)). --define(invalid(ErrorMsg, ErrorValue), {invalid, io_lib:format(ErrorMsg, ErrorValue)}). - -validate_new_config() -> - validate_new_config(get_app_env(servers), get_app_env(session_nodes)). - -validate_new_config({invalid, _} = Invalid, _Nodes) -> Invalid; -validate_new_config(_Servers, {invalid, _} = Invalid) -> Invalid; -validate_new_config(Servers, Nodes) -> - validate_new_config_start(Servers, check_root(Nodes)). - -validate_new_config_start(_Servers, {invalid, _} = Invalid) -> Invalid; -validate_new_config_start(Servers, Nodes) -> - map_helper(fun(Server) -> validate_new_server_config(Server, Nodes) end, Servers, flatten). - -validate_new_server_config({Name, {IP, ListOfPorts}}, Nodes) -> - validate_new_server_config(Name, get_app_env(Name), validate_ip(IP), validate_ports(ListOfPorts), [], Nodes); - -validate_new_server_config({Name, {IP, ListOfPorts, Opts}}, Nodes) -> - validate_new_server_config(Name, get_app_env(Name), validate_ip(IP), validate_ports(ListOfPorts), validate_options(Opts), Nodes). - -validate_new_server_config(_Server, {invalid, _} = Invalid, _IP, _ListOfPorts, _Opts, _Nodes) -> Invalid; -validate_new_server_config(_Server, _NasList, {invalid, _} = Invalid, _ListOfPorts, _Opts, _Nodes) -> Invalid; -validate_new_server_config(_Server, _NasList, _IP, {invalid, _} = Invalid, _Opts, _Nodes) -> Invalid; -validate_new_server_config(_Server, _NasList, _IP, _ListOfPorts, {invalid, _} = Invalid, _Nodes) -> Invalid; -validate_new_server_config(Server, NasList, IP, ListOfPorts, Opts, Nodes) -> - case validate_new_nas_list(NasList, {IP, ListOfPorts, Nodes}) of - {invalid, _} = Invalid -> - Invalid; - Values -> - lists:map(fun(Port) -> {Server, {IP, Port, Opts}, Values} end, ListOfPorts) - end. - -validate_new_nas_list(NasLists, ServerConfig) -> - map_helper(fun(NasList) -> validate_behavior_naslist(NasList, ServerConfig) end, NasLists, flatten). - -validate_behavior_naslist({Behavior, ListOfNases}, {_IP, _ListOfPorts, Nodes}) -> - validate_behavior_nases(validate_behavior(Behavior), validate_naslist(ListOfNases, Nodes)). - -validate_behavior_nases({invalid, _} = Invalid, _) -> Invalid; -validate_behavior_nases(_, {invalid, _} = Invalid) -> Invalid; -validate_behavior_nases(Behavior, Nases) -> - build_nas_behavior_list(Behavior, Nases). - -validate_behavior({Nas, Args}) -> - validate_behavior({get_app_env(radius_callback), Nas, Args}); -validate_behavior({{invalid, _} = Invalid, _Nas, _Args}) -> - Invalid; -validate_behavior({Module, Nas, _Args} = Value) when is_atom(Module) andalso ?is_io(Nas) -> - code:is_loaded(Module) =:= false andalso code:load_file(Module), - case erlang:function_exported(Module, validate_arguments, 1) of - true -> validate_arguments(Value); - false -> Value - end; -validate_behavior({Module, _, _}) when is_atom(Module) -> - ?invalid("bad NAS Id in Behavior specification: ~p", [Module]); -validate_behavior({Module, _, _}) -> - ?invalid("bad module in Behavior specification: ~p", [Module]); -validate_behavior(Term) -> - ?invalid("bad Term in Behavior specification: ~p", [Term]). - -validate_arguments({Module, Nas, Args} = Value) -> - case Module:validate_arguments(Args) of - true -> Value; - {true, NewArgs} -> {Module, Nas, NewArgs}; - false -> ?invalid("~p: bad configuration", [Module]); - Error -> ?invalid("~p: bad configuration: ~p", [Module, Error]) - end. - -validate_naslist(ListOfNases, Nodes) -> map_helper(fun(Nas) -> validate_nas(Nas, Nodes) end, ListOfNases, yes). - -validate_nas({IP, Secret}, Nodes) -> - validate_nas({IP, Secret, []}, Nodes); -validate_nas({IP, Secret, Options}, Nodes) -> - validate_nas({proplists:get_value(nas_id, Options), IP, Secret, proplists:get_value(group, Options)}, Nodes); -validate_nas({NasId, IP, Secret, undefined}, {root, Nodes}) -> - validate_nas(NasId, IP, Secret, root, Nodes); -validate_nas({NasId, IP, Secret, GroupName}, Nodes) when is_list(Nodes) -> - validate_nas(NasId, IP, Secret, GroupName, proplists:get_value(GroupName, Nodes)); -validate_nas(Term, _) -> - ?invalid("bad term in NAS specification: ~p", [Term]). - -validate_nas(_NasId, {invalid, _} = Invalid, _Secret, _Name, _Nodes) -> Invalid; - -validate_nas(NasId, IP, Secret, Name, undefined) -> - validate_nas(NasId, IP, Secret, Name, validate_handler_nodes(Name)); -validate_nas(_NasId, IP, _Secret, Name, {invalid, _}) -> - ?invalid("group ~p for nas ~p is undefined", [Name, IP]); -validate_nas(NasId, IP, Secret, _Name, Nodes) when ?is_io(Secret) andalso (?is_io(NasId) orelse NasId == undefined) -> - case is_list(IP) andalso string:tokens(IP, "/") of - [IP0, Mask] -> - [{NasId, validate_ip(IP1), validate_secret(Secret), Nodes} || IP1 <- generate_ip_list(validate_ip(IP0), Mask)]; - _ -> {NasId, validate_ip(IP), validate_secret(Secret), Nodes} - end; -validate_nas(NasId, _IP, Secret, _Name, _) when ?is_io(Secret) -> - ?invalid("bad nas id name: ~p", [NasId]); -validate_nas(_NasId, _IP, Secret, _Name, _) -> - ?invalid("bad RADIUS secret: ~p", [Secret]). - -%% -------------------------------------------------------------------------------------------------- -%% -- direct validation function - -validate_ip(IP) when is_list(IP) -> - ok_error_helper(inet_parse:address(IP), {"bad IP address: ~p", [IP]}); -validate_ip(IP) when ?ip4_address(IP) -> - IP; -validate_ip(X) -> - ?invalid("bad IP address: ~p", [X]). - -validate_ports(Ports) -> map_helper(fun validate_port/1, Ports). -validate_port(Port) when is_list(Port) -> validate_port(catch list_to_integer(Port)); -validate_port(Port) when ?pos_int(Port) -> Port; -validate_port(Port) when is_integer(Port) -> ?invalid("port number out of range: ~p", [Port]); -validate_port(Port) -> ?invalid("bad port number: ~p", [Port]). - -validate_options(Opts) when is_list(Opts) -> - SocketOpts = proplists:get_value(socket_opts, Opts, []), - case validate_socket_options(SocketOpts) of - {invalid, Reason} = E -> - io:format("validate_socket_options: ~p", [Reason]), - E; - _ -> - Opts - end; -validate_options(Opts) -> - ?invalid("expect a list of options: ~p", Opts). - -validate_socket_options(SocketOpts) when is_list(SocketOpts) -> - BannedOpts = [ip, binary, list, active], - IsBannedFn = fun(Opt) -> - proplists:is_defined(Opt, SocketOpts) - end, - case lists:any(IsBannedFn, BannedOpts) of - true -> - ?invalid("bad socket options specified: ~p", [SocketOpts]); - false -> - SocketOpts - end; -validate_socket_options(Opts) -> - ?invalid("expect a list of options: ~p", Opts). - -check_root([First | _] = AllNodes) when is_tuple(First) -> - map_helper(fun({Name, List}) -> - case validate_handler_nodes(List) of - {invalid, _} = Invalid -> - Invalid; - Value -> - {Name, Value} - end - end, AllNodes); -check_root(Nodes) -> - case validate_handler_nodes(Nodes) of - {invalid, _} = Invalid -> - Invalid; - Values -> - {root, Values} - end. - -%% -------------------------------------------------------------------------------------------------- -%% -- build right format function - -build_nas_behavior_list({Module, Nas, Args}, ListOfNases) -> - lists:map(fun({undefined, IP, Secret, Nodes}) -> - {build_nasname(Nas, IP), IP, Secret, Nodes, Module, Args}; - ({NasName, IP, Secret, Nodes}) -> - {NasName, IP, Secret, Nodes, Module, Args} - end, ListOfNases). - -build_nasname(Nas, IP) -> - NasBinary = tob(Nas), - IPString = inet_parse:ntoa(IP), - <<NasBinary/binary, "_", (list_to_binary(IPString))/binary>>. - -tob(Integer) when is_integer(Integer) -> tob(integer_to_list(Integer)); -tob(List) when is_list(List) -> list_to_binary(List); -tob(Binary) -> Binary. - --type valid_nas() :: {inet:ip_address(), binary(), list(atom()), module(), term()}. --type valid_server() :: {eradius_server_mon:server(), list(valid_nas())}. --type valid_config() :: list(valid_server()). - --spec validate_config(list(term())) -> valid_config() | {invalid, io_lib:chars()}. -validate_config(Config) -> - case Config of - [Server | _] -> - case Server of - {List, SecondList} when is_list(List) and is_list(SecondList) -> - validate_server_config(dedup_keys(Config)); - %% Check format of new command - {_Name, ServerConf} when is_tuple(ServerConf) -> - validate_new_config() - end; - [] -> - validate_server_config(dedup_keys(Config)) - end. - --spec validate_server_config(list(term())) -> valid_config() | {invalid, io_lib:chars()}. -validate_server_config([]) -> - []; -validate_server_config([{Server, NasList} | ConfigRest]) -> - case validate_server(Server) of - {invalid, _} = E -> - E; - ValidServer -> - case validate_nas_list(NasList) of - {invalid, _} = E -> - E; - ValidNasList -> - case validate_server_config(ConfigRest) of - E = {invalid, _} -> - E; - ValidConfigRest -> - [{ValidServer, ValidNasList} | ValidConfigRest] - end - end - end; -validate_server_config([InvalidTerm | _ConfigRest]) -> ?invalid("bad term in server list: ~p", [InvalidTerm]). - -validate_server({IP, Port}) when is_list(Port) -> - case (catch list_to_integer(Port)) of - {'EXIT', _} -> - {invalid, io_lib:format("bad port number: ~p", [Port])}; - Num when ?pos_int(Num) -> - validate_server({IP, Num}); - Num -> - {invalid, io_lib:format("port number out of range: ~p", [Num])} - end; -validate_server({IP, Port}) when is_list(IP), ?pos_int(Port) -> - case inet_parse:ipv4_address(IP) of - {ok, Address} -> - {Address, Port}; - {error, einval} -> - {invalid, io_lib:format("bad IP address: ~p", [IP])} - end; -validate_server({IP, Port}) when ?ip4_address(IP), ?pos_int(Port) -> - {IP, Port}; -validate_server(String) when is_list(String) -> - %% TODO: IPv6 address support - case string:tokens(String, ":") of - [IP, Port] -> - validate_server({IP, Port}); - _ -> - {invalid, io_lib:format("bad address/port combination: ~p", [String])} - end; -validate_server({IP, Port, Opts}) when is_list(Opts) -> - case {validate_server({IP, Port}), validate_options(Opts)} of - {{invalid, _Reason} = E, _} -> - E; - {_, {invalid, _Reason} = E} -> - E; - {{ValidIP, ValidPort}, ValidOpts} -> - {ValidIP, ValidPort, ValidOpts} - end; -validate_server(X) -> - {invalid, io_lib:format("bad address/port combination: ~p", [X])}. - -validate_nas_list([]) -> - []; -validate_nas_list([{NasAddress, Secret, HandlerNodes, Module, Args} | NasListRest]) when is_list(NasAddress) -> - case inet_parse:ipv4_address(NasAddress) of - {ok, ValidAddress} -> - validate_nas_list([{ValidAddress, Secret, HandlerNodes, Module, Args} | NasListRest]); - {error, einval} -> - {invalid, io_lib:format("bad IP address in NAS specification: ~p", [NasAddress])} - end; -validate_nas_list([{NasAddress, Secret, HandlerNodes, Module, Args} | NasListRest]) when ?ip4_address(NasAddress) -> - case validate_secret(Secret) of - E = {invalid, _} -> - E; - ValidSecret -> - case validate_handler_nodes(HandlerNodes) of - E = {invalid, _} -> - E; - ValidHandlerNodes -> - case Module of - _ when is_atom(Module) -> - case validate_nas_list(NasListRest) of - E = {invalid, _} -> - E; - ValidNasListRest -> - [{build_nasname("", NasAddress), NasAddress, ValidSecret, ValidHandlerNodes, Module, Args} | ValidNasListRest] - end; - _Else -> - {invalid, io_lib:format("bad module in NAS specifification: ~p", [Module])} - end - end - end; -validate_nas_list([{InvalidAddress, _, _, _, _} | _NasListRest]) -> - {invalid, io_lib:format("bad IP address in NAS specification: ~p", [InvalidAddress])}; -validate_nas_list([OtherTerm | _NasListRest]) -> - {invalid, io_lib:format("bad term in NAS specification: ~p", [OtherTerm])}. - -validate_secret(Secret) when is_list(Secret) -> - unicode:characters_to_binary(Secret); -validate_secret(Secret) when is_binary(Secret) -> - Secret; -validate_secret(OtherTerm) -> - {invalid, io_lib:format("bad RADIUS secret: ~p", [OtherTerm])}. - -validate_handler_nodes(local) -> - local; -validate_handler_nodes("local") -> - local; -validate_handler_nodes([]) -> - {invalid, "empty node list"}; -validate_handler_nodes(NodeL) when is_list(NodeL) -> - validate_node_list(NodeL); -validate_handler_nodes(OtherTerm) -> - {invalid, io_lib:format("bad node list: ~p", [OtherTerm])}. - -validate_node_list([]) -> - []; -validate_node_list([Node | Rest]) when is_atom(Node) -> - case validate_node_list(Rest) of - E = {invalid, _} -> - E; - ValidRest -> - [Node | ValidRest] - end; -validate_node_list([OtherTerm | _]) -> - {invalid, io_lib:format("bad term in node list: ~p", [OtherTerm])}. - -dedup_keys(Proplist) -> - dedup_keys1(lists:keysort(1, Proplist)). - -dedup_keys1(Proplist) -> - lists:foldr(fun ({K, V1}, [{K, V2} | R]) -> - [{K, plmerge(V1, V2)} | R]; - ({K, V}, R) -> - [{K, V} | R] - end, [], Proplist). - -plmerge(List1, List2) -> - M1 = [{K, V} || {K, V} <- List1, not proplists:is_defined(K, List2)], - lists:keysort(1, M1 ++ List2). - -%% -------------------------------------------------------------------------------------------------- -%% -- helpers - -get_app_env(Env) -> - get_app_env(eradius, Env). -get_app_env(App, Env) -> - case application:get_env(App, Env) of - {ok, Value} -> - Value; - _ -> - ?invalid("config parameter: ~p is undefined for application ~p", [Env, App]) - end. - -map_helper(Fun, Values) -> - map_helper(Fun, Values, no). -map_helper(Fun, Values, Type) -> - map_helper(Fun, Values, Type, []). -map_helper(_Fun, [], _Type, Values) -> lists:reverse(Values); -map_helper(Fun, [Head | Tail], Type, Values) -> - case Fun(Head) of - {invalid, _} = Error -> - Error; - Result when Type =/= no andalso is_list(Result) -> - map_helper(Fun, Tail, Type, Result ++ Values); - Result -> - map_helper(Fun, Tail, Type, [Result | Values]) - end. - -ok_error_helper({error, _Error}, {Msg, Value}) when is_list(Msg) -> ?invalid(Msg, Value); -ok_error_helper({error, _Error}, ErrorMsg) when is_list(ErrorMsg) -> ErrorMsg; -ok_error_helper({ok, Value}, _ErrorMessage) -> Value; -ok_error_helper(Value, _ErrorMessage) -> Value. - -generate_ip_list(IP, Mask) when is_list(Mask) -> - generate_ip_list(IP, catch list_to_integer(Mask)); -generate_ip_list({A, B, C, D}, Mask) when Mask >=0, Mask =< 32 -> - <<Address:32/integer>> = <<A, B, C, D>>, - Wildcard = 16#ffffffff bsr Mask, - <<Netmask:32/unsigned-integer>> = << (bnot Wildcard):32 >>, - generate_ip(Address band Netmask, Address bor Wildcard); -generate_ip_list(_, Mask) -> ?invalid("invalid mask ~p", [Mask]). - -generate_ip(E, E) -> - <<A:8, B:8, C:8, D:8>> = <<E:32/integer>>, - [{A, B, C, D}]; -generate_ip(S, E) -> - <<A:8, B:8, C:8, D:8>> = <<S:32/integer>>, - [{A, B, C, D} | generate_ip(S+1, E)]. diff --git a/src/eradius_counter.erl b/src/eradius_counter.erl deleted file mode 100644 index 9d245e59..00000000 --- a/src/eradius_counter.erl +++ /dev/null @@ -1,301 +0,0 @@ -%% @doc -%% This module implements the statitics counter for RADIUS servers and clients - --module(eradius_counter). --export([init_counter/1, init_counter/2, inc_counter/2, dec_counter/2, reset_counter/1, reset_counter/2, - inc_request_counter/2, inc_reply_counter/2, observe/4, observe/5, - set_boolean_metric/3]). - --behaviour(gen_server). --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --export([start_link/0, reset/0, pull/0, read/0, aggregate/1]). --export([collect/2]). - --include("eradius_lib.hrl"). - --record(state, { - reset :: erlang:timestamp() - }). - --type srv_counters() :: [#server_counter{}]. --type nas_counters() :: {erlang:timestamp(), [#nas_counter{}]}. --type stats() :: {srv_counters(), nas_counters()}. - -%% ------------------------------------------------------------------------------------------ -%% API -%% @doc initialize a counter structure -init_counter({ServerIP, ServerPort, ServerName}) when is_integer(ServerPort) -> - #server_counter{key = {ServerIP, ServerPort}, startTime = eradius_lib:timestamp(), resetTime = eradius_lib:timestamp(), server_name = ServerName}; -init_counter({{ClientName, ClientIP, ClientPort}, {ServerName, ServerIp, ServerPort}}) -> - #client_counter{key = {{ClientName, ClientIP, ClientPort}, {ServerName, ServerIp, ServerPort}}, server_name = ServerName}. -init_counter(#nas_prop{server_ip = ServerIP, server_port = ServerPort, nas_ip = NasIP, nas_id = NasId}, ServerName) -> - #nas_counter{key = {{ServerIP, ServerPort}, NasIP, NasId}, server_name = ServerName}. - -%% @doc reset counters -reset_counter(#server_counter{startTime = Up}) -> #server_counter{startTime = Up, resetTime = eradius_lib:timestamp()}. -reset_counter(Nas = #nas_prop{}, ServerName) -> - init_counter(Nas, ServerName). - -%% @doc increment requests counters -inc_request_counter(Counter, Nas) -> - inc_counter(Counter, Nas). - -%% @doc increment reply counters -inc_reply_counter(Counter, Nas) -> - inc_counter(Counter, Nas). - -%% @doc increment a specific counter value -inc_counter(invalidRequests, Counters = #server_counter{invalidRequests = Value}) -> - Counters#server_counter{invalidRequests = Value + 1}; -inc_counter(discardNoHandler, Counters = #server_counter{discardNoHandler = Value}) -> - Counters#server_counter{discardNoHandler = Value + 1}; -inc_counter(Counter, Nas = #nas_prop{}) -> - gen_server:cast(?MODULE, {inc_counter, Counter, Nas}); -inc_counter(Counter, {{ClientName, ClientIP, ClientPort}, {ServerName, ServerIp, ServerPort}}) -> - gen_server:cast(?MODULE, {inc_counter, Counter, {{ClientName, ClientIP, ClientPort}, {ServerName, ServerIp, ServerPort}}}). - -dec_counter(Counter, Nas = #nas_prop{}) -> - gen_server:cast(?MODULE, {dec_counter, Counter, Nas}); -dec_counter(Counter, {{ClientName, ClientIP, ClientPort}, {ServerName, ServerIp, ServerPort}}) -> - gen_server:cast(?MODULE, {dec_counter, Counter, {{ClientName, ClientIP, ClientPort}, {ServerName, ServerIp, ServerPort}}}). - -%% @doc reset all counters to zero -reset() -> - gen_server:call(?MODULE, reset). - -%% @doc read counters and reset to zero --spec pull() -> stats(). -pull() -> - gen_server:call(?MODULE, pull). - -%% @doc read counters --spec read() -> stats(). -read() -> - gen_server:call(?MODULE, read). - -%% @doc calculate the per server sum of all counters of a per NAS list of counters --spec aggregate(stats()) -> stats(). -aggregate({Servers, {ResetTS, Nass}}) -> - NSums = lists:foldl(fun(Nas = #nas_counter{key = {ServerId, _}}, Acc) -> - orddict:update(ServerId, fun(Value) -> add_counter(Value, Nas) end, Nas#nas_counter{key = ServerId}, Acc) - end, - orddict:new(), Nass), - NSum1 = [Value || {_Key, Value} <- orddict:to_list(NSums)], - {Servers, {ResetTS, NSum1}}. - -%% @doc Set Value for the given prometheus boolean metric by the given Name with -%% the given values -set_boolean_metric(Name, Labels, Value) -> - case code:is_loaded(prometheus) of - {file, _} -> - try - prometheus_boolean:set(Name, Labels, Value) - catch _:_ -> - prometheus_boolean:declare([{name, server_status}, {labels, [server_ip, server_port]}, - {help, "Status of an upstream RADIUS Server"}]), - prometheus_boolean:set(Name, Labels, Value) - end; - _ -> - ok - end. - -%% @doc Update the given histogram metric value -%% NOTE: We use prometheus_histogram collector here instead of eradius_counter ets table because -%% it is much easy to use histograms in this way. As we don't need to manage buckets and do -%% the other histogram things in eradius, but prometheus.erl will do it for us -observe(Name, {{ClientName, ClientIP, _}, {ServerName, ServerIP, ServerPort}} = MetricsInfo, Value, Help) -> - case code:is_loaded(prometheus) of - {file, _} -> - try - prometheus_histogram:observe(Name, [ServerIP, ServerPort, ServerName, ClientName, ClientIP], Value) - catch _:_ -> - Buckets = application:get_env(eradius, histogram_buckets, [10, 30, 50, 75, 100, 1000, 2000]), - prometheus_histogram:declare([{name, Name}, {labels, [server_ip, server_port, server_name, client_name, client_ip]}, - {duration_unit, milliseconds}, - {buckets, Buckets}, {help, Help}]), - observe(Name, MetricsInfo, Value, Help) - end; - _ -> - ok - end. -observe(Name, #nas_prop{server_ip = ServerIP, server_port = ServerPort, nas_ip = NasIP, nas_id = NasId} = Nas, Value, ServerName, Help) -> - case code:is_loaded(prometheus) of - {file, _} -> - try - prometheus_histogram:observe(Name, [inet:ntoa(ServerIP), ServerPort, ServerName, inet:ntoa(NasIP), NasId], Value) - catch _:_ -> - Buckets = application:get_env(eradius, histogram_buckets, [10, 30, 50, 75, 100, 1000, 2000]), - prometheus_histogram:declare([{name, Name}, {labels, [server_ip, server_port, server_name, nas_ip, nas_id]}, - {duration_unit, milliseconds}, - {buckets, Buckets}, {help, Help}]), - observe(Name, Nas, Value, ServerName, Help) - end; - _ -> - ok - end. - -%% helper to be called from the aggregator to fetch this nodes values -%% @private -collect(Ref, Process) -> - lists:foreach(fun(Node) -> gen_server:cast({?MODULE, Node}, {collect, Ref, Process}) end, - eradius_node_mon:get_module_nodes(?MODULE)). - -%% @private --spec start_link() -> {ok, pid()} | {error, term()}. -start_link() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). - -%% ------------------------------------------------------------------------------------------ -%% -- gen_server Callbacks -%% @private -init([]) -> - ets:new(?MODULE, [ordered_set, protected, named_table, {keypos, #nas_counter.key}, {write_concurrency,true}]), - eradius:modules_ready([?MODULE]), - {ok, #state{reset = eradius_lib:timestamp()}}. - -%% @private -handle_call(pull, _From, State) -> - NassAndClients = read_stats(State), - Servers = server_stats(pull), - ets:delete_all_objects(?MODULE), - {reply, {Servers, NassAndClients}, State#state{reset = eradius_lib:timestamp()}}; -handle_call(read, _From, State) -> - NassAndClients = read_stats(State), - Servers = server_stats(read), - {reply, {Servers, NassAndClients}, State}; -handle_call(reset, _From, State) -> - server_stats(reset), - ets:delete_all_objects(?MODULE), - {reply, ok, State#state{reset = eradius_lib:timestamp()}}. - -%% @private -handle_cast({inc_counter, Counter, Key = {{_ClientName, _ClientIP, _ClientPort}, {_ServerName, _ServerIp, _ServerPort}}}, State) -> - ets:update_counter(?MODULE, Key, {counter_idx(Counter, client), 1}, init_counter(Key)), - {noreply, State}; - -handle_cast({inc_counter, Counter, Nas = #nas_prop{server_ip = ServerIP, server_port = ServerPort, nas_ip = NasIP, nas_id = NasId}}, State) -> - Key = {{ServerIP, ServerPort}, NasIP, NasId}, - {{ServerName, _, _}, _} = Nas#nas_prop.metrics_info, - ets:update_counter(?MODULE, Key, {counter_idx(Counter, nas), 1}, init_counter(Nas, ServerName)), - {noreply, State}; - -handle_cast({dec_counter, Counter, Key = {{_ClientName, _ClientIP, _ClientPort}, {_ServerName, _ServerIp, _ServerPort}}}, State) -> - ets:update_counter(?MODULE, Key, {counter_idx(Counter, client), -1}, init_counter(Key)), - {noreply, State}; - -handle_cast({dec_counter, Counter, Nas = #nas_prop{server_ip = ServerIP, server_port = ServerPort, nas_ip = NasIP, nas_id = NasId}}, State) -> - Key = {{ServerIP, ServerPort}, NasIP, NasId}, - {{ServerName, _, _}, _} = Nas#nas_prop.metrics_info, - ets:update_counter(?MODULE, Key, {counter_idx(Counter, nas), -1}, init_counter(Nas, ServerName)), - {noreply, State}; - -handle_cast({collect, Ref, Process}, State) -> - Process ! {collect, Ref, ets:tab2list(?MODULE)}, - ets:delete_all_objects(?MODULE), - {noreply, State#state{reset = eradius_lib:timestamp()}}; - -handle_cast(_Msg, State) -> - {noreply, State}. - -%% -- unused callbacks -%% @private -handle_info(_Info, State) -> {noreply, State}. -%% @private -code_change(_OldVsn, State, _Extra) -> {ok, State}. -%% @private -terminate(_Reason, _State) -> ok. - -%% ------------------------------------------------------------------------------------------ -%% -- helper functions -%% @private - -read_stats(State) -> - {State#state.reset, ets:tab2list(?MODULE)}. - -server_stats(Func) -> - lists:foldl(fun(S, Acc) -> [eradius_server:stats(S, Func)|Acc] end, [], eradius_server_sup:all()). - -%% @private -counter_idx(requests, nas) -> #nas_counter.requests; -counter_idx(replies, nas) -> #nas_counter.replies; -counter_idx(dupRequests, nas) -> #nas_counter.dupRequests; -counter_idx(malformedRequests, nas) -> #nas_counter.malformedRequests; -counter_idx(accessRequests, nas) -> #nas_counter.accessRequests; -counter_idx(accessAccepts, nas) -> #nas_counter.accessAccepts; -counter_idx(accessRejects, nas) -> #nas_counter.accessRejects; -counter_idx(accessChallenges, nas) -> #nas_counter.accessChallenges; -counter_idx(badAuthenticators, nas) -> #nas_counter.badAuthenticators; -counter_idx(packetsDropped, nas) -> #nas_counter.packetsDropped; -counter_idx(unknownTypes, nas) -> #nas_counter.unknownTypes; -counter_idx(handlerFailure, nas) -> #nas_counter.handlerFailure; -counter_idx(coaRequests, nas) -> #nas_counter.coaRequests; -counter_idx(coaAcks, nas) -> #nas_counter.coaAcks; -counter_idx(coaNaks, nas) -> #nas_counter.coaNaks; -counter_idx(discRequests, nas) -> #nas_counter.discRequests; -counter_idx(discAcks, nas) -> #nas_counter.discAcks; -counter_idx(discNaks, nas) -> #nas_counter.discNaks; -counter_idx(retransmissions, nas) -> #nas_counter.retransmissions; -counter_idx(pending, nas) -> #nas_counter.pending; -counter_idx(accountRequestsStart, nas) -> #nas_counter.accountRequestsStart; -counter_idx(accountRequestsStop, nas) -> #nas_counter.accountRequestsStop; -counter_idx(accountRequestsUpdate, nas) -> #nas_counter.accountRequestsUpdate; -counter_idx(accountResponsesStart, nas) -> #nas_counter.accountResponsesStart; -counter_idx(accountResponsesStop, nas) -> #nas_counter.accountResponsesStop; -counter_idx(accountResponsesUpdate, nas) -> #nas_counter.accountResponsesUpdate; - -counter_idx(requests, client) -> #client_counter.requests; -counter_idx(replies, client) -> #client_counter.replies; -counter_idx(accessRequests, client) -> #client_counter.accessRequests; -counter_idx(coaRequests, client) -> #client_counter.coaRequests; -counter_idx(discRequests, client) -> #client_counter.discRequests; -counter_idx(retransmissions, client) -> #client_counter.retransmissions; -counter_idx(accessAccepts, client) -> #client_counter.accessAccepts; -counter_idx(accessRejects, client) -> #client_counter.accessRejects; -counter_idx(accessChallenges, client) -> #client_counter.accessChallenges; -counter_idx(coaNaks, client) -> #client_counter.coaNaks; -counter_idx(coaAcks, client) -> #client_counter.coaAcks; -counter_idx(discNaks, client) -> #client_counter.discNaks; -counter_idx(discAcks, client) -> #client_counter.discAcks; -counter_idx(badAuthenticators, client) -> #client_counter.badAuthenticators; -counter_idx(packetsDropped, client) -> #client_counter.packetsDropped; -counter_idx(unknownTypes, client) -> #client_counter.unknownTypes; -counter_idx(pending, client) -> #client_counter.pending; -counter_idx(timeouts, client) -> #client_counter.timeouts; -counter_idx(accountRequestsStart, client) -> #client_counter.accountRequestsStart; -counter_idx(accountRequestsStop, client) -> #client_counter.accountRequestsStop; -counter_idx(accountRequestsUpdate, client) -> #client_counter.accountRequestsUpdate; -counter_idx(accountResponsesStart, client) -> #client_counter.accountResponsesStart; -counter_idx(accountResponsesStop, client) -> #client_counter.accountResponsesStop; -counter_idx(accountResponsesUpdate, client) -> #client_counter.accountResponsesUpdate. - -add_counter(Cnt1 = #nas_counter{}, Cnt2 = #nas_counter{}) -> - #nas_counter{ - key = Cnt1#nas_counter.key, - requests = Cnt1#nas_counter.requests + Cnt2#nas_counter.requests, - replies = Cnt1#nas_counter.replies + Cnt2#nas_counter.replies, - dupRequests = Cnt1#nas_counter.dupRequests + Cnt2#nas_counter.dupRequests, - malformedRequests = Cnt1#nas_counter.malformedRequests + Cnt2#nas_counter.malformedRequests, - accessRequests = Cnt1#nas_counter.accessRequests + Cnt2#nas_counter.accessRequests, - accessAccepts = Cnt1#nas_counter.accessAccepts + Cnt2#nas_counter.accessAccepts, - accessRejects = Cnt1#nas_counter.accessRejects + Cnt2#nas_counter.accessRejects, - accessChallenges = Cnt1#nas_counter.accessChallenges + Cnt2#nas_counter.accessChallenges, - accountRequestsStart = Cnt1#nas_counter.accountRequestsStart + Cnt2#nas_counter.accountRequestsStart, - accountRequestsStop = Cnt1#nas_counter.accountRequestsStop + Cnt2#nas_counter.accountRequestsStop, - accountRequestsUpdate = Cnt1#nas_counter.accountRequestsUpdate + Cnt2#nas_counter.accountRequestsUpdate, - accountResponsesStart = Cnt1#nas_counter.accountResponsesStart + Cnt2#nas_counter.accountResponsesStart, - accountResponsesStop = Cnt1#nas_counter.accountResponsesStop + Cnt2#nas_counter.accountResponsesStop, - accountResponsesUpdate = Cnt1#nas_counter.accountResponsesUpdate + Cnt2#nas_counter.accountResponsesUpdate, - noRecords = Cnt1#nas_counter.noRecords + Cnt2#nas_counter.noRecords, - badAuthenticators = Cnt1#nas_counter.badAuthenticators + Cnt2#nas_counter.badAuthenticators, - packetsDropped = Cnt1#nas_counter.packetsDropped + Cnt2#nas_counter.packetsDropped, - unknownTypes = Cnt1#nas_counter.unknownTypes + Cnt2#nas_counter.unknownTypes, - handlerFailure = Cnt1#nas_counter.handlerFailure + Cnt2#nas_counter.handlerFailure, - coaRequests = Cnt1#nas_counter.coaRequests + Cnt2#nas_counter.coaRequests, - coaAcks = Cnt1#nas_counter.coaAcks + Cnt2#nas_counter.coaAcks, - coaNaks = Cnt1#nas_counter.coaNaks + Cnt2#nas_counter.coaNaks, - discRequests = Cnt1#nas_counter.discRequests + Cnt2#nas_counter.discRequests, - discAcks = Cnt1#nas_counter.discAcks + Cnt2#nas_counter.discAcks, - discNaks = Cnt1#nas_counter.discNaks + Cnt2#nas_counter.discNaks, - retransmissions = Cnt1#nas_counter.retransmissions + Cnt2#nas_counter.retransmissions, - pending = Cnt1#nas_counter.pending + Cnt2#nas_counter.pending - }. diff --git a/src/eradius_counter_aggregator.erl b/src/eradius_counter_aggregator.erl deleted file mode 100644 index b137b370..00000000 --- a/src/eradius_counter_aggregator.erl +++ /dev/null @@ -1,162 +0,0 @@ --module(eradius_counter_aggregator). - --behaviour(gen_server). --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --export([start_link/0, reset/0, pull/0, read/0]). - --include("eradius_lib.hrl"). - --define(INIT_HB, 1000). --define(INTERVAL_HB, 5000). - --record(state, { - me :: reference(), - reset :: erlang:timestamp() - }). - -%% @doc reset all counters to zero -reset() -> - gen_server:call(?MODULE, reset). -%% @doc read counters and reset to zero --spec pull() -> eradius_counter:stats(). -pull() -> - gen_server:call(?MODULE, pull). -%% @doc read counters --spec read() -> eradius_counter:stats(). -read() -> - gen_server:call(?MODULE, read). - -%% @private --spec start_link() -> {ok, pid()} | {error, term()}. -start_link() -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). - -%% ------------------------------------------------------------------------------------------ -%% -- gen_server Callbacks -%% @private -init([]) -> - ets:new(?MODULE, [ordered_set, protected, named_table, {keypos, #nas_counter.key}, {write_concurrency,true}]), - eradius:modules_ready([?MODULE]), - EnableAggregator = application:get_env(eradius, counter_aggregator, false), - if EnableAggregator == true -> - erlang:send_after(?INIT_HB, self(), heartbeat); - true -> - ok - end, - {ok, #state{me = make_ref(), reset = eradius_lib:timestamp()}}. - -%% @private -handle_call(pull, _From, State) -> - Nass = read_stats(State), - Servers = server_stats(pull), - ets:delete_all_objects(?MODULE), - {reply, {Servers, Nass}, State#state{reset = eradius_lib:timestamp()}}; -handle_call(read, _From, State) -> - Nass = read_stats(State), - Servers = server_stats(read), - {reply, {Servers, Nass}, State}; -handle_call(reset, _From, State) -> - server_stats(reset), - ets:delete_all_objects(?MODULE), - {reply, ok, State#state{reset = eradius_lib:timestamp()}}. - -%% @private -handle_info(heartbeat, State) -> - eradius_counter:collect(State#state.me, self()), - erlang:send_after(?INTERVAL_HB, self(), heartbeat), - {noreply, State}; -handle_info({collect, Ref, Stats}, State = #state{me = Ref}) -> - lists:foreach(fun update_stats/1, Stats), - {noreply, State}; -handle_info({collect, Ref, Stats}, State) -> - io:format("invalid stats answer: ~p~n", [{collect, Ref, Stats}]), - {noreply, State}. - -%% -- unused callbacks -%% @private -handle_cast(_Msg, State) -> {noreply, State}. -%% @private -code_change(_OldVsn, State, _Extra) -> {ok, State}. -%% @private -terminate(_Reason, _State) -> ok. - -%% ------------------------------------------------------------------------------------------ -%% -- helper functions -%% @private - -read_stats(State) -> - {State#state.reset, ets:tab2list(?MODULE)}. - -server_stats(Func) -> - lists:foldl(fun(S, Acc) -> [eradius_server:stats(S, Func)|Acc] end, [], eradius_server_sup:all()). - -update_stats(Rec = #nas_counter{key = Key}) -> - Cnt0 = case ets:lookup(?MODULE, Key) of - [] -> #nas_counter{key = Key}; - [Cnt] -> Cnt - end, - ets:insert(?MODULE, add_counter(Cnt0, Rec)); -update_stats(Rec = #client_counter{key = Key}) -> - Cnt0 = case ets:lookup(?MODULE, Key) of - [] -> #client_counter{key = Key}; - [Cnt] -> Cnt - end, - ets:insert(?MODULE, add_counter(Cnt0, Rec)). - -add_counter(Cnt1 = #nas_counter{}, Cnt2 = #nas_counter{}) -> - #nas_counter{ - key = Cnt1#nas_counter.key, - requests = Cnt1#nas_counter.requests + Cnt2#nas_counter.requests, - replies = Cnt1#nas_counter.replies + Cnt2#nas_counter.replies, - dupRequests = Cnt1#nas_counter.dupRequests + Cnt2#nas_counter.dupRequests, - malformedRequests = Cnt1#nas_counter.malformedRequests + Cnt2#nas_counter.malformedRequests, - accessRequests = Cnt1#nas_counter.accessRequests + Cnt2#nas_counter.accessRequests, - accessAccepts = Cnt1#nas_counter.accessAccepts + Cnt2#nas_counter.accessAccepts, - accessRejects = Cnt1#nas_counter.accessRejects + Cnt2#nas_counter.accessRejects, - accessChallenges = Cnt1#nas_counter.accessChallenges + Cnt2#nas_counter.accessChallenges, - accountRequestsStart = Cnt1#nas_counter.accountRequestsStart + Cnt2#nas_counter.accountRequestsStart, - accountRequestsStop = Cnt1#nas_counter.accountRequestsStop + Cnt2#nas_counter.accountRequestsStop, - accountRequestsUpdate = Cnt1#nas_counter.accountRequestsUpdate + Cnt2#nas_counter.accountRequestsUpdate, - accountResponsesStart = Cnt1#nas_counter.accountResponsesStart + Cnt2#nas_counter.accountResponsesStart, - accountResponsesStop = Cnt1#nas_counter.accountResponsesStop + Cnt2#nas_counter.accountResponsesStop, - accountResponsesUpdate = Cnt1#nas_counter.accountResponsesUpdate + Cnt2#nas_counter.accountResponsesUpdate, - noRecords = Cnt1#nas_counter.noRecords + Cnt2#nas_counter.noRecords, - badAuthenticators = Cnt1#nas_counter.badAuthenticators + Cnt2#nas_counter.badAuthenticators, - packetsDropped = Cnt1#nas_counter.packetsDropped + Cnt2#nas_counter.packetsDropped, - unknownTypes = Cnt1#nas_counter.unknownTypes + Cnt2#nas_counter.unknownTypes, - handlerFailure = Cnt1#nas_counter.handlerFailure + Cnt2#nas_counter.handlerFailure, - coaRequests = Cnt1#nas_counter.coaRequests + Cnt2#nas_counter.coaRequests, - coaAcks = Cnt1#nas_counter.coaAcks + Cnt2#nas_counter.coaAcks, - coaNaks = Cnt1#nas_counter.coaNaks + Cnt2#nas_counter.coaNaks, - discRequests = Cnt1#nas_counter.discRequests + Cnt2#nas_counter.discRequests, - discAcks = Cnt1#nas_counter.discAcks + Cnt2#nas_counter.discAcks, - discNaks = Cnt1#nas_counter.discNaks + Cnt2#nas_counter.discNaks - }; -add_counter(Cnt1 = #client_counter{}, Cnt2 = #client_counter{}) -> - #client_counter{ - key = Cnt1#client_counter.key, - requests = Cnt1#client_counter.requests + Cnt2#client_counter.requests, - replies = Cnt1#client_counter.replies + Cnt2#client_counter.replies, - accessRequests = Cnt1#client_counter.accessRequests + Cnt2#client_counter.accessRequests, - accessAccepts = Cnt1#client_counter.accessAccepts + Cnt2#client_counter.accessAccepts, - accessRejects = Cnt1#client_counter.accessRejects + Cnt2#client_counter.accessRejects, - accessChallenges = Cnt1#client_counter.accessChallenges + Cnt2#client_counter.accessChallenges, - accountRequestsStart = Cnt1#client_counter.accountRequestsStart + Cnt2#client_counter.accountRequestsStart, - accountRequestsStop = Cnt1#client_counter.accountRequestsStop + Cnt2#client_counter.accountRequestsStop, - accountRequestsUpdate = Cnt1#client_counter.accountRequestsUpdate + Cnt2#client_counter.accountRequestsUpdate, - accountResponsesStart = Cnt1#client_counter.accountResponsesStart + Cnt2#client_counter.accountResponsesStart, - accountResponsesStop = Cnt1#client_counter.accountResponsesStop + Cnt2#client_counter.accountResponsesStop, - accountResponsesUpdate = Cnt1#client_counter.accountResponsesUpdate + Cnt2#client_counter.accountResponsesUpdate, - badAuthenticators = Cnt1#client_counter.badAuthenticators + Cnt2#client_counter.badAuthenticators, - packetsDropped = Cnt1#client_counter.packetsDropped + Cnt2#client_counter.packetsDropped, - unknownTypes = Cnt1#client_counter.unknownTypes + Cnt2#client_counter.unknownTypes, - coaRequests = Cnt1#client_counter.coaRequests + Cnt2#client_counter.coaRequests, - coaAcks = Cnt1#client_counter.coaAcks + Cnt2#client_counter.coaAcks, - coaNaks = Cnt1#client_counter.coaNaks + Cnt2#client_counter.coaNaks, - discRequests = Cnt1#client_counter.discRequests + Cnt2#client_counter.discRequests, - discAcks = Cnt1#client_counter.discAcks + Cnt2#client_counter.discAcks, - discNaks = Cnt1#client_counter.discNaks + Cnt2#client_counter.discNaks, - retransmissions = Cnt1#client_counter.retransmissions + Cnt2#client_counter.retransmissions, - timeouts = Cnt1#client_counter.timeouts + Cnt2#client_counter.timeouts, - pending = Cnt1#client_counter.pending + Cnt2#client_counter.pending - }. diff --git a/src/eradius_dict.erl b/src/eradius_dict.erl index 3fca6f30..00847dc4 100644 --- a/src/eradius_dict.erl +++ b/src/eradius_dict.erl @@ -1,11 +1,18 @@ -%% @private +%% Copyright (c) 2002-2007, Martin Björklund and Torbjörn Törnkvist +%% Copyright (c) 2011, Travelping GmbH <info@travelping.com> +%% +%% SPDX-License-Identifier: MIT +%% %% @doc Dictionary server -module(eradius_dict). --export([start_link/0, lookup/2, load_tables/1, load_tables/2, unload_tables/1, unload_tables/2]). --export_type([attribute/0, attr_value/0, table_name/0, attribute_id/0, attribute_type/0, - attribute_prim_type/0, attribute_encryption/0, vendor_id/0, value_id/0]). -behaviour(gen_server). + +%% API +-export([start_link/0, lookup/2, load_tables/1, load_tables/2, unload_tables/1, unload_tables/2]). +-ignore_xref([start_link/0, unload_tables/1, unload_tables/2]). + +%% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -include_lib("kernel/include/logger.hrl"). @@ -17,8 +24,9 @@ -type attribute_id() :: pos_integer() | {vendor_id(), pos_integer()}. -type attribute_encryption() :: 'no' | 'scramble' | 'salt_crypt' | 'ascend'. -type attribute_type() :: attribute_prim_type() | {tagged, attribute_prim_type()}. --type attribute_prim_type() :: 'string' | 'integer' | 'integer64' | 'ipaddr' | 'ipv6addr' - | 'ipv6prefix' | 'date' | 'abinary' | 'binary' | 'octets'. +-type attribute_prim_type() :: 'string' | 'integer' | 'integer24' | 'integer64' | + 'ipaddr' | 'ipv6addr' | 'ipv6prefix' | + 'date' | 'abinary' | 'binary' | 'octets'. -type value_id() :: {attribute_id(), pos_integer()}. -type vendor_id() :: pos_integer(). @@ -26,15 +34,23 @@ -type attribute() :: #attribute{} | attribute_id(). -type attr_value() :: term(). +-export_type([attribute/0, attr_value/0, table_name/0, attribute_id/0, attribute_type/0, + attribute_prim_type/0, attribute_encryption/0, vendor_id/0, value_id/0]). + + -record(state, {}). -%% ------------------------------------------------------------------------------------------ -%% -- API +%%%========================================================================= +%%% API +%%%========================================================================= + +%% @private -spec start_link() -> {ok, pid()} | {error, term()}. start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). --spec lookup(attribute | vendor | value, attribute_id() | value_id() | vendor_id()) -> false | #attribute{} | #value{} | #vendor{}. +-spec lookup(attribute | vendor | value, attribute_id() | value_id() | vendor_id()) -> + false | #attribute{} | #value{} | #vendor{}. lookup(Type, Id) -> dict_lookup(Type, Id). @@ -54,13 +70,17 @@ unload_tables(Tables) when is_list(Tables) -> unload_tables(Dir, Tables) when is_list(Tables) -> gen_server:call(?SERVER, {unload_tables, Dir, Tables}, infinity). -%% ------------------------------------------------------------------------------------------ -%% -- gen_server callbacks +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +%% @private init([]) -> {ok, InitialLoadTables} = application:get_env(eradius, tables), do_load_tables(code:priv_dir(eradius), InitialLoadTables), {ok, #state{}}. +%% @private handle_call({load_tables, Dir, Tables}, _From, State) -> {reply, do_load_tables(Dir, Tables), State}; @@ -68,13 +88,23 @@ handle_call({unload_tables, Dir, Tables}, _From, State) -> {reply, do_unload_tables(Dir, Tables), State}. %% unused callbacks + +%% @private handle_cast(_Msg, State) -> {noreply, State}. + +%% @private handle_info(_Info, State) -> {noreply, State}. + +%% @private terminate(_Reason, _State) -> ok. + +%% @private code_change(_OldVsn, _NewVsn, _State) -> {ok, state}. -%% ------------------------------------------------------------------------------------------ -%% -- gen_server callbacks +%%%========================================================================= +%%% internal functions +%%%========================================================================= + mapfile(A) when is_atom(A) -> mapfile(atom_to_list(A)); mapfile(A) when is_list(A) -> A ++ ".map". diff --git a/src/eradius_eap_packet.erl b/src/eradius_eap_packet.erl index 0f55cbd4..85176d61 100644 --- a/src/eradius_eap_packet.erl +++ b/src/eradius_eap_packet.erl @@ -8,32 +8,38 @@ %% -- gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +-ignore_xref([start/0, lookup_type/1, register_type/2, unregister_type/1]). +-ignore_xref([decode/1, encode/3, decode_eap_type/2, encode_eap_type/1]). + -behaviour(gen_server). --define(NAME, ?MODULE). +-define(SERVER, ?MODULE). %% ------------------------------------------------------------------------------------------ %% -- API start() -> - gen_server:start({local, ?NAME}, ?MODULE, []). + gen_server:start({local, ?SERVER}, ?MODULE, [], []). %% @doc lookup the handler module for an extended EAP type lookup_type(Type) -> - ets:lookup(?NAME, Type). + case ets:lookup(?SERVER, Type) of + [{Type, Module}] -> {ok, Module}; + _ -> false + end. %% @doc register the handler module for an extended EAP type register_type(Type, Module) -> - gen_server:call(?NAME, {register, Type, Module}). + gen_server:call(?SERVER, {register, Type, Module}). %% @doc unregister the handler module for an extended EAP type unregister_type(Type) -> - gen_server:call(?NAME, {unregister, Type}). + gen_server:call(?SERVER, {unregister, Type}). %% ------------------------------------------------------------------------------------------ %% -- gen_server callbacks %% @private init([]) -> - Table = ets:new(?NAME, [ordered_set, protected, named_table, {read_concurrency, true}]), + Table = ets:new(?SERVER, [ordered_set, protected, named_table, {read_concurrency, true}]), {ok, Table}. %% @private @@ -71,7 +77,7 @@ decode(<<Code:8, Id:8, Len:16, Rest/binary>>) -> encode(Code, Id, Msg) -> Data = encode_payload(Code, Msg), Len = size(Data) + 4, - <<Code:8, Id:8, Len:16, Data/binary>>. + <<(code(Code)):8, Id:8, Len:16, Data/binary>>. do_decode_payload(Code, Id, Data) -> try @@ -131,7 +137,7 @@ decode_eap_type({0, 3}, Data) -> decode_eap_type(Type, Data) -> case lookup_type(Type) of - [Module] -> + {ok, Module} -> Module:decode_eap_type(Type, Data); _ -> {Type, Data} end. @@ -173,7 +179,7 @@ encode_eap_type({otp, Data}) %% 6 Generic Token Card (GTC) encode_eap_type({gtc, Data}) when is_binary(Data) -> - <<6:8, Data>>; + <<6:8, Data/binary>>; %% 254 Expanded Types encode_eap_type({{Vendor, Type}, Data}) @@ -192,7 +198,7 @@ encode_eap_type({nak_ext, Data}) encode_eap_type(Msg) when is_tuple(Msg) -> - [Module] = lookup_type(element(1, Msg)), + {ok, Module} = lookup_type(element(1, Msg)), Module:encode_eap_type(Msg). code(1) -> request; @@ -206,4 +212,3 @@ code(success) -> 3; code(failure) -> 4; code(_) -> error. - diff --git a/src/eradius_lib.erl b/src/eradius_lib.erl index 650cbde3..9840b341 100644 --- a/src/eradius_lib.erl +++ b/src/eradius_lib.erl @@ -1,501 +1,24 @@ +%% Copyright (c) 2002-2007, Martin Björklund and Torbjörn Törnkvist +%% Copyright (c) 2011, Travelping GmbH <info@travelping.com> +%% +%% SPDX-License-Identifier: MIT +%% +%% @private -module(eradius_lib). --export([del_attr/2, get_attr/2, encode_request/1, encode_reply/1, decode_request/2, decode_request/3, decode_request_id/1]). --export([random_authenticator/0, zero_authenticator/0, pad_to/2, set_attr/3, get_attributes/1, set_attributes/2]). --export([timestamp/0, timestamp/1, printable_peer/2, make_addr_info/1]). --export_type([command/0, secret/0, authenticator/0, attribute_list/0]). + +-export([pad_to/2]). +-export([printable_peer/1, printable_peer/2]). %% -compile(bin_opt_info). --ifdef(TEST). --export([encode_value/2, decode_value/2, scramble/3, ascend/3]). --export([salt_encrypt/4, salt_decrypt/3, encode_attribute/3, decode_attribute/5]). --endif. -include("eradius_lib.hrl"). -include("eradius_dict.hrl"). --type command() :: 'request' | 'accept' | 'challenge' | 'reject' | 'accreq' | 'accresp' | 'coareq' | 'coaack' | 'coanak' | 'discreq' | 'discack' | 'discnak'. --type secret() :: binary(). --type authenticator() :: <<_:128>>. --type salt() :: binary(). --type attribute_list() :: list({eradius_dict:attribute(), term()}). - --define(IS_ATTR(Key, Attr), ?IS_KEY(Key, element(1, Attr))). -define(IS_KEY(Key, Attr), ((is_record(Attr, attribute) andalso (element(2, Attr) == Key)) orelse (Attr == Key)) ). %% ------------------------------------------------------------------------------------------ %% -- Request Accessors --spec random_authenticator() -> authenticator(). -random_authenticator() -> crypto:strong_rand_bytes(16). - --spec zero_authenticator() -> authenticator(). -zero_authenticator() -> <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>. - --spec set_attributes(#radius_request{}, attribute_list()) -> #radius_request{}. -set_attributes(Req = #radius_request{attrs = Attrs}, NewAttrs) -> - Req#radius_request{attrs = NewAttrs ++ Attrs}. - --spec get_attributes(#radius_request{}) -> attribute_list(). -get_attributes(#radius_request{attrs = Attrs}) -> - Attrs. - --spec set_attr(#radius_request{}, eradius_dict:attribute_id(), eradius_dict:attr_value()) -> #radius_request{}. -set_attr(Req = #radius_request{attrs = Attrs}, Id, Val) -> - Req#radius_request{attrs = [{Id, Val} | Attrs]}. - --spec get_attr(#radius_request{}, eradius_dict:attribute_id()) -> eradius_dict:attr_value() | undefined. -get_attr(#radius_request{attrs = Attrs}, Id) -> - get_attr_loop(Id, Attrs). - -del_attr(Req = #radius_request{attrs = Attrs}, Id) -> - Req#radius_request{attrs = lists:reverse(lists:foldl(fun(Attr, Acc) when ?IS_ATTR(Id, Attr) -> Acc; - (Attr, Acc) -> [Attr | Acc] - end, [], Attrs))}. - -get_attr_loop(Key, [{Id, Val}|_T]) when ?IS_KEY(Key, Id) -> Val; -get_attr_loop(Key, [_|T]) -> get_attr_loop(Key, T); -get_attr_loop(_, []) -> undefined. - -%% ------------------------------------------------------------------------------------------ -%% -- Wire Encoding - -%% @doc Convert a RADIUS request to the wire format. -%% The Message-Authenticator MUST be used in Access-Request that include an EAP-Message attribute [RFC 3579]. --spec encode_request(#radius_request{}) -> {binary(), binary()}. -encode_request(Req = #radius_request{reqid = ReqID, cmd = Command, attrs = Attributes}) when (Command == request) -> - Authenticator = random_authenticator(), - Req1 = Req#radius_request{authenticator = Authenticator}, - EncReq1 = encode_attributes(Req1, Attributes), - EncReq2 = encode_eap_message(Req1, EncReq1), - {Body, BodySize} = encode_message_authenticator(Req1, EncReq2), - {Authenticator, <<(encode_command(Command)):8, ReqID:8, (BodySize + 20):16, Authenticator:16/binary, Body/binary>>}; -encode_request(Req = #radius_request{reqid = ReqID, cmd = Command, attrs = Attributes}) -> - {Body, BodySize} = encode_attributes(Req, Attributes), - Head = <<(encode_command(Command)):8, ReqID:8, (BodySize + 20):16>>, - Authenticator = crypto:hash(md5, [Head, zero_authenticator(), Body, Req#radius_request.secret]), - {Authenticator, <<Head/binary, Authenticator:16/binary, Body/binary>>}. - -%% @doc Convert a RADIUS reply to the wire format. -%% This function performs the same task as {@link encode_request/2}, -%% except that it includes the authenticator substitution required for replies. -%% The Message-Authenticator MUST be used in Access-Accept, Access-Reject or Access-Chalange -%% replies that includes an EAP-Message attribute [RFC 3579]. --spec encode_reply(#radius_request{}) -> binary(). -encode_reply(Req = #radius_request{reqid = ReqID, cmd = Command, authenticator = RequestAuthenticator, attrs = Attributes}) -> - EncReq1 = encode_attributes(Req, Attributes), - EncReq2 = encode_eap_message(Req, EncReq1), - {Body, BodySize} = encode_message_authenticator(Req, EncReq2), - Head = <<(encode_command(Command)):8, ReqID:8, (BodySize + 20):16>>, - ReplyAuthenticator = crypto:hash(md5, [Head, <<RequestAuthenticator:16/binary>>, Body, Req#radius_request.secret]), - <<Head/binary, ReplyAuthenticator:16/binary, Body/binary>>. - --spec encode_command(command()) -> byte(). -encode_command(request) -> ?RAccess_Request; -encode_command(accept) -> ?RAccess_Accept; -encode_command(challenge) -> ?RAccess_Challenge; -encode_command(reject) -> ?RAccess_Reject; -encode_command(accreq) -> ?RAccounting_Request; -encode_command(accresp) -> ?RAccounting_Response; -encode_command(coareq) -> ?RCoa_Request; -encode_command(coaack) -> ?RCoa_Ack; -encode_command(coanak) -> ?RCoa_Nak; -encode_command(discreq) -> ?RDisconnect_Request; -encode_command(discack) -> ?RDisconnect_Ack; -encode_command(discnak) -> ?RDisconnect_Nak. - --spec encode_message_authenticator(#radius_request{}, {binary(), non_neg_integer()}) -> {binary(), non_neg_integer()}. -encode_message_authenticator(_Req = #radius_request{msg_hmac = false}, Request) -> - Request; -encode_message_authenticator(Req = #radius_request{reqid = ReqID, cmd = Command, authenticator = Authenticator, msg_hmac = true}, {Body, BodySize}) -> - Head = <<(encode_command(Command)):8, ReqID:8, (BodySize + 20 + 2 +16):16>>, - ReqAuth = <<Authenticator:16/binary>>, - HMAC = message_authenticator(Req#radius_request.secret, [Head, ReqAuth, Body, <<?RMessage_Authenticator,18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>]), - {<<Body/binary, ?RMessage_Authenticator, 18, HMAC/binary>>, BodySize + 2 + 16}. - -chunk(Bin, Length) -> - case Bin of - <<First:Length/bytes, Rest/binary>> -> {First, Rest}; - _ -> {Bin, <<>>} - end. - -encode_eap_attribute({<<>>, _}, EncReq) -> - EncReq; -encode_eap_attribute({Value, Rest}, {Body, BodySize}) -> - EncAttr = <<?REAP_Message, (byte_size(Value) + 2):8, Value/binary>>, - EncReq = {<<Body/binary, EncAttr/binary>>, BodySize + byte_size(EncAttr)}, - encode_eap_attribute(chunk(Rest, 253), EncReq). - --spec encode_eap_message(#radius_request{}, {binary(), non_neg_integer()}) -> {binary(), non_neg_integer()}. -encode_eap_message(#radius_request{eap_msg = EAP}, EncReq) - when is_binary(EAP); size(EAP) > 0 -> - encode_eap_attribute(chunk(EAP, 253), EncReq); -encode_eap_message(#radius_request{eap_msg = <<>>}, EncReq) -> - EncReq. - --spec encode_attributes(#radius_request{}, attribute_list()) -> {binary(), non_neg_integer()}. -encode_attributes(Req, Attributes) -> - F = fun ({A = #attribute{}, Val}, {Body, BodySize}) -> - EncAttr = encode_attribute(Req, A, Val), - {<<Body/binary, EncAttr/binary>>, BodySize + byte_size(EncAttr)}; - ({ID, Val}, {Body, BodySize}) -> - case eradius_dict:lookup(attribute, ID) of - AttrRec = #attribute{} -> - EncAttr = encode_attribute(Req, AttrRec, Val), - {<<Body/binary, EncAttr/binary>>, BodySize + byte_size(EncAttr)}; - _ -> - {Body, BodySize} - end - end, - lists:foldl(F, {<<>>, 0}, Attributes). - --spec encode_attribute(#radius_request{}, #attribute{}, term()) -> binary(). -encode_attribute(_Req, _Attr = #attribute{id = ?RMessage_Authenticator}, _) -> - %% message authenticator is handled through the msg_hmac flag - <<>>; -encode_attribute(_Req, _Attr = #attribute{id = ?REAP_Message}, _) -> - %% EAP-Message attributes are handled through the eap_msg field - <<>>; -encode_attribute(Req, Attr = #attribute{id = {Vendor, ID}}, Value) -> - EncValue = encode_attribute(Req, Attr#attribute{id = ID}, Value), - if byte_size(EncValue) + 6 > 255 -> - error(badarg, [{Vendor, ID}, Value]); - true -> ok - end, - <<?RVendor_Specific:8, (byte_size(EncValue) + 6):8, Vendor:32, EncValue/binary>>; -encode_attribute(Req, #attribute{type = {tagged, Type}, id = ID, enc = Enc}, Value) -> - case Value of - {Tag, UntaggedValue} when Tag >= 1, Tag =< 16#1F -> ok; - UntaggedValue -> Tag = 0 - end, - EncValue = encrypt_value(Req, encode_value(Type, UntaggedValue), Enc), - if byte_size(EncValue) + 3 > 255 -> - error(badarg, [ID, Value]); - true -> ok - end, - <<ID, (byte_size(EncValue) + 3):8, Tag:8, EncValue/binary>>; -encode_attribute(Req, #attribute{type = Type, id = ID, enc = Enc}, Value)-> - EncValue = encrypt_value(Req, encode_value(Type, Value), Enc), - if byte_size(EncValue) + 2 > 255 -> - error(badarg, [ID, Value]); - true -> ok - end, - <<ID, (byte_size(EncValue) + 2):8, EncValue/binary>>. - --spec encrypt_value(#radius_request{}, binary(), eradius_dict:attribute_encryption()) -> binary(). -encrypt_value(Req, Val, scramble) -> scramble(Req#radius_request.secret, Req#radius_request.authenticator, Val); -encrypt_value(Req, Val, salt_crypt) -> salt_encrypt(generate_salt(), Req#radius_request.secret, Req#radius_request.authenticator, Val); -encrypt_value(Req, Val, ascend) -> ascend(Req#radius_request.secret, Req#radius_request.authenticator, Val); -encrypt_value(_Req, Val, no) -> Val. - --spec encode_value(eradius_dict:attribute_prim_type(), term()) -> binary(). -encode_value(_, V) when is_binary(V) -> - V; -encode_value(binary, V) -> - V; -encode_value(integer, V) -> - <<V:32>>; -encode_value(integer24, V) -> - <<V:24>>; -encode_value(integer64, V) -> - <<V:64>>; -encode_value(ipaddr, {A,B,C,D}) -> - <<A:8, B:8, C:8, D:8>>; -encode_value(ipv6addr, {A,B,C,D,E,F,G,H}) -> - <<A:16, B:16, C:16, D:16, E:16, F:16, G:16, H:16>>; -encode_value(ipv6prefix, {{A,B,C,D,E,F,G,H}, PLen}) -> - L = (PLen + 7) div 8, - <<IP:L/bytes, _R/binary>> = <<A:16, B:16, C:16, D:16, E:16, F:16, G:16, H:16>>, - <<0, PLen, IP/binary>>; -encode_value(string, V) when is_list(V) -> - unicode:characters_to_binary(V); -encode_value(octets, V) when is_list(V) -> - iolist_to_binary(V); -encode_value(octets, V) when is_integer(V) -> - <<V:32>>; -encode_value(date, V) when is_list(V) -> - unicode:characters_to_binary(V); -encode_value(date, Date = {{_,_,_},{_,_,_}}) -> - EpochSecs = calendar:datetime_to_gregorian_seconds(Date) - calendar:datetime_to_gregorian_seconds({{1970,1,1},{0,0,0}}), - <<EpochSecs:32>>. - -%% ------------------------------------------------------------------------------------------ -%% -- Wire Decoding - --spec decode_request_id(binary()) -> {0..255, binary()} | {bad_pdu, list()}. -decode_request_id(Req = <<_Cmd:8, ReqId:8, _Rest/binary>>) -> {ReqId, Req}; -decode_request_id(_Req) -> {bad_pdu, "invalid request id"}. - --spec decode_request(binary(), secret()) -> #radius_request{} | {bad_pdu, list()}. -decode_request(Packet, Secret) -> - decode_request(Packet, Secret, undefined). - --spec decode_request(binary(), secret(), authenticator()) -> #radius_request{} | {bad_pdu, list()}. -decode_request(Packet, Secret, Authenticator) -> - case (catch decode_request0(Packet, Secret, Authenticator)) of - {'EXIT', _} -> {bad_pdu, "decode packet error"}; - Else -> Else - end. - --spec decode_request0(binary(), secret(), authenticator() | 'undefined') -> #radius_request{}. -decode_request0(<<Cmd:8, ReqId:8, Len:16, PacketAuthenticator:16/binary, Body0/binary>>, Secret, RequestAuthenticator) -> - ActualBodySize = byte_size(Body0), - GivenBodySize = Len - 20, - Body = if - ActualBodySize > GivenBodySize -> - throw({bad_pdu, "false packet size"}); - ActualBodySize == GivenBodySize -> - Body0; - true -> - binary:part(Body0, 0, GivenBodySize) - end, - Command = decode_command(Cmd), - PartialRequest = #radius_request{cmd = Command, reqid = ReqId, authenticator = PacketAuthenticator, secret = Secret, msg_hmac = false}, - DecodedState = decode_attributes(PartialRequest, RequestAuthenticator, Body), - Request = PartialRequest#radius_request{attrs = lists:reverse(DecodedState#decoder_state.attrs), - eap_msg = list_to_binary(lists:reverse(DecodedState#decoder_state.eap_msg))}, - validate_authenticator(Command, <<Cmd:8, ReqId:8, Len:16>>, RequestAuthenticator, PacketAuthenticator, Body, Secret), - if - is_integer(DecodedState#decoder_state.hmac_pos) -> - validate_packet_authenticator(Cmd, ReqId, Len, Body, DecodedState#decoder_state.hmac_pos, Secret, PacketAuthenticator, RequestAuthenticator), - Request#radius_request{msg_hmac = true}; - true -> Request - end. - --spec validate_packet_authenticator(non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), binary(), binary(), authenticator(), authenticator() | 'undefined') -> ok. -validate_packet_authenticator(Cmd, ReqId, Len, Body, Pos, Secret, PacketAuthenticator, undefined) -> - validate_packet_authenticator(Cmd, ReqId, Len, PacketAuthenticator, Body, Pos, Secret); -validate_packet_authenticator(Cmd, ReqId, Len, Body, Pos, Secret, _PacketAuthenticator, RequestAuthenticator) -> - validate_packet_authenticator(Cmd, ReqId, Len, RequestAuthenticator, Body, Pos, Secret). - --spec validate_packet_authenticator(non_neg_integer(), non_neg_integer(), non_neg_integer(), authenticator(), non_neg_integer(), binary(), binary()) -> ok. -validate_packet_authenticator(Cmd, ReqId, Len, Auth, Body, Pos, Secret) -> - case Body of - <<Before:Pos/bytes, Value:16/bytes, After/binary>> -> - case message_authenticator(Secret, [<<Cmd:8, ReqId:8, Len:16>>, Auth, Before, zero_authenticator(), After]) of - Value -> - ok; - _ -> - throw({bad_pdu, "Message-Authenticator Attribute is invalid"}) - end; - _ -> - throw({bad_pdu, "Message-Authenticator Attribute is malformed"}) - end. - -validate_authenticator(accreq, Head, _RequestAuthenticator, PacketAuthenticator, Body, Secret) -> - compare_authenticator(crypto:hash(md5, [Head, zero_authenticator(), Body, Secret]), PacketAuthenticator); -validate_authenticator(Cmd, Head, RequestAuthenticator, PacketAuthenticator, Body, Secret) - when - (Cmd =:= accept) orelse - (Cmd =:= reject) orelse - (Cmd =:= accresp) orelse - (Cmd =:= coaack) orelse - (Cmd =:= coanak) orelse - (Cmd =:= discack) orelse - (Cmd =:= discnak) orelse - (Cmd =:= challenge) -> - compare_authenticator(crypto:hash(md5, [Head, RequestAuthenticator, Body, Secret]), PacketAuthenticator); -validate_authenticator(_Cmd, _Head, _RequestAuthenticator, _PacketAuthenticator, - _Body, _Secret) -> - true. - -compare_authenticator(Authenticator, Authenticator) -> - true; -compare_authenticator(_RequestAuthenticator, _PacketAuthenticator) -> - throw({bad_pdu, "Authenticator Attribute is invalid"}). - --spec decode_command(byte()) -> command(). -decode_command(?RAccess_Request) -> request; -decode_command(?RAccess_Accept) -> accept; -decode_command(?RAccess_Reject) -> reject; -decode_command(?RAccess_Challenge) -> challenge; -decode_command(?RAccounting_Request) -> accreq; -decode_command(?RAccounting_Response) -> accresp; -decode_command(?RCoa_Request) -> coareq; -decode_command(?RCoa_Ack) -> coaack; -decode_command(?RCoa_Nak) -> coanak; -decode_command(?RDisconnect_Request) -> discreq; -decode_command(?RDisconnect_Ack) -> discack; -decode_command(?RDisconnect_Nak) -> discnak; -decode_command(_) -> error({bad_pdu, "unknown request type"}). - -append_attr(Attr, State) -> - State#decoder_state{attrs = [Attr | State#decoder_state.attrs]}. - --spec decode_attributes(#radius_request{}, binary(), binary()) -> #decoder_state{}. -decode_attributes(Req, RequestAuthenticator, As) -> - decode_attributes(Req, As, 0, #decoder_state{request_authenticator = RequestAuthenticator}). - --spec decode_attributes(#radius_request{}, binary(), non_neg_integer(), #decoder_state{}) -> #decoder_state{}. -decode_attributes(_Req, <<>>, _Pos, State) -> - State; -decode_attributes(Req, <<Type:8, ChunkLength:8, ChunkRest/binary>>, Pos, State) -> - ValueLength = ChunkLength - 2, - <<Value:ValueLength/binary, PacketRest/binary>> = ChunkRest, - NewState = case eradius_dict:lookup(attribute, Type) of - AttrRec = #attribute{} -> - decode_attribute(Value, Req, AttrRec, Pos + 2, State); - _ -> - append_attr({Type, Value}, State) - end, - decode_attributes(Req, PacketRest, Pos + ChunkLength, NewState). - -%% gotcha: the function returns a LIST of attribute-value pairs because -%% a vendor-specific attribute blob might contain more than one attribute. --spec decode_attribute(binary(), #radius_request{}, #attribute{}, non_neg_integer(), #decoder_state{}) -> #decoder_state{}. -decode_attribute(<<VendorID:32/integer, ValueBin/binary>>, Req, #attribute{id = ?RVendor_Specific}, Pos, State) -> - decode_vendor_specific_attribute(Req, VendorID, ValueBin, Pos + 4, State); -decode_attribute(<<Value/binary>>, _Req, Attr = #attribute{id = ?REAP_Message}, _Pos, State) -> - NewState = State#decoder_state{eap_msg = [Value | State#decoder_state.eap_msg]}, - append_attr({Attr, Value}, NewState); -decode_attribute(<<EncValue/binary>>, Req, Attr = #attribute{id = ?RMessage_Authenticator, type = Type, enc = Encryption}, Pos, State) -> - append_attr({Attr, decode_value(decrypt_value(Req, State, EncValue, Encryption), Type)}, State#decoder_state{hmac_pos = Pos}); -decode_attribute(<<EncValue/binary>>, Req, Attr = #attribute{type = Type, enc = Encryption}, _Pos, State) when is_atom(Type) -> - append_attr({Attr, decode_value(decrypt_value(Req, State, EncValue, Encryption), Type)}, State); -decode_attribute(WholeBin = <<Tag:8, Bin/binary>>, Req, Attr = #attribute{type = {tagged, Type}}, _Pos, State) -> - case {decode_tag_value(Tag), Attr#attribute.enc} of - {0, no} -> - %% decode including tag byte if tag is out of range - append_attr({Attr, {0, decode_value(WholeBin, Type)}}, State); - {TagV, no} -> - append_attr({Attr, {TagV, decode_value(Bin, Type)}}, State); - {TagV, Encryption} -> - %% for encrypted attributes, tag byte is never part of the value - append_attr({Attr, {TagV, decode_value(decrypt_value(Req, State, Bin, Encryption), Type)}}, State) - end. - --compile({inline, decode_tag_value/1}). -decode_tag_value(Tag) when (Tag >= 1) and (Tag =< 16#1F) -> Tag; -decode_tag_value(_OtherTag) -> 0. - --spec decode_value(binary(), eradius_dict:attribute_prim_type()) -> term(). -decode_value(<<Bin/binary>>, Type) -> - case Type of - octets -> - Bin; - binary -> - Bin; - abinary -> - Bin; - string -> - Bin; - integer -> - decode_integer(Bin); - integer24 -> - decode_integer(Bin); - integer64 -> - decode_integer(Bin); - date -> - case decode_integer(Bin) of - Int when is_integer(Int) -> - calendar:now_to_universal_time({Int div 1000000, Int rem 1000000, 0}); - _ -> - Bin - end; - ipaddr -> - <<B,C,D,E>> = Bin, - {B,C,D,E}; - ipv6addr -> - <<B:16,C:16,D:16,E:16,F:16,G:16,H:16,I:16>> = Bin, - {B,C,D,E,F,G,H,I}; - ipv6prefix -> - <<0,PLen,P/binary>> = Bin, - <<B:16,C:16,D:16,E:16,F:16,G:16,H:16,I:16>> = pad_to(16, P), - {{B,C,D,E,F,G,H,I}, PLen} - end. - --compile({inline, decode_integer/1}). -decode_integer(Bin) -> - ISize = bit_size(Bin), - case Bin of - <<Int:ISize/integer>> -> Int; - _ -> Bin - end. - --spec decrypt_value(#radius_request{}, #decoder_state{}, binary(), - eradius_dict:attribute_encryption()) -> eradius_dict:attr_value(). -decrypt_value(#radius_request{secret = Secret, authenticator = Authenticator}, - _, <<Val/binary>>, scramble) -> - scramble(Secret, Authenticator, Val); -decrypt_value(#radius_request{secret = Secret}, - #decoder_state{request_authenticator = RequestAuthenticator}, - <<Val/binary>>, salt_crypt) - when is_binary(RequestAuthenticator) -> - salt_decrypt(Secret, RequestAuthenticator, Val); -decrypt_value(#radius_request{secret = Secret, authenticator = Authenticator}, - _, <<Val/binary>>, ascend) -> - ascend(Secret, Authenticator, Val); -decrypt_value(_Req, _State, <<Val/binary>>, _Type) -> - Val. - --spec decode_vendor_specific_attribute(#radius_request{}, non_neg_integer(), binary(), non_neg_integer(), #decoder_state{}) -> #decoder_state{}. -decode_vendor_specific_attribute(_Req, _VendorID, <<>>, _Pos, State) -> - State; -decode_vendor_specific_attribute(Req, VendorID, <<Type:8, ChunkLength:8, ChunkRest/binary>>, Pos, State) -> - ValueLength = ChunkLength - 2, - <<Value:ValueLength/binary, PacketRest/binary>> = ChunkRest, - VendorAttrKey = {VendorID, Type}, - NewState = case eradius_dict:lookup(attribute, VendorAttrKey) of - Attr = #attribute{} -> - decode_attribute(Value, Req, Attr, Pos + 2, State); - _ -> - append_attr({VendorAttrKey, Value}, State) - end, - decode_vendor_specific_attribute(Req, VendorID, PacketRest, Pos + ChunkLength, NewState). - -%% ------------------------------------------------------------------------------------------ -%% -- Attribute Encryption --spec scramble(secret(), authenticator(), binary()) -> binary(). -scramble(SharedSecret, RequestAuthenticator, <<PlainText/binary>>) -> - B = crypto:hash(md5, [SharedSecret, RequestAuthenticator]), - do_scramble(SharedSecret, B, pad_to(16, PlainText), << >>). - -do_scramble(SharedSecret, B, <<PlainText:16/binary, Remaining/binary>>, CipherText) -> - NewCipherText = crypto:exor(PlainText, B), - Bnext = crypto:hash(md5, [SharedSecret, NewCipherText]), - do_scramble(SharedSecret, Bnext, Remaining, <<CipherText/binary, NewCipherText/binary>>); - -do_scramble(_SharedSecret, _B, << >>, CipherText) -> - CipherText. - --spec generate_salt() -> salt(). -generate_salt() -> - <<Salt1, Salt2>> = crypto:strong_rand_bytes(2), - <<(Salt1 bor 16#80), Salt2>>. - --spec salt_encrypt(salt(), secret(), authenticator(), binary()) -> binary(). -salt_encrypt(Salt, SharedSecret, RequestAuthenticator, PlainText) -> - CipherText = do_salt_crypt(encrypt, Salt, SharedSecret, RequestAuthenticator, (pad_to(16, << (byte_size(PlainText)):8, PlainText/binary >>))), - <<Salt/binary, CipherText/binary>>. - --spec salt_decrypt(secret(), authenticator(), binary()) -> binary(). -salt_decrypt(SharedSecret, RequestAuthenticator, <<Salt:2/binary, CipherText/binary>>) -> - << Length:8/integer, PlainText/binary >> = do_salt_crypt(decrypt, Salt, SharedSecret, RequestAuthenticator, CipherText), - if - Length < byte_size(PlainText) -> - binary:part(PlainText, 0, Length); - true -> - PlainText - end. - -do_salt_crypt(Op, Salt, SharedSecret, RequestAuthenticator, <<CipherText/binary>>) -> - B = crypto:hash(md5, [SharedSecret, RequestAuthenticator, Salt]), - salt_crypt(Op, SharedSecret, B, CipherText, << >>). - -salt_crypt(Op, SharedSecret, B, <<PlainText:16/binary, Remaining/binary>>, CipherText) -> - NewCipherText = crypto:exor(PlainText, B), - Bnext = case Op of - decrypt -> crypto:hash(md5, [SharedSecret, PlainText]); - encrypt -> crypto:hash(md5, [SharedSecret, NewCipherText]) - end, - salt_crypt(Op, SharedSecret, Bnext, Remaining, <<CipherText/binary, NewCipherText/binary>>); - -salt_crypt(_Op, _SharedSecret, _B, << >>, CipherText) -> - CipherText. - --spec ascend(secret(), authenticator(), binary()) -> binary(). -ascend(SharedSecret, RequestAuthenticator, <<PlainText/binary>>) -> - Digest = crypto:hash(md5, [RequestAuthenticator, SharedSecret]), - crypto:exor(Digest, pad_to(16, PlainText)). %% @doc pad binary to specific length %% See <a href="http://www.erlang.org/pipermail/erlang-questions/2008-December/040709.html"> @@ -508,50 +31,18 @@ pad_to(Width, Binary) -> N -> <<Binary/binary, 0:(N*8)>> end. --spec timestamp() -> erlang:timestamp(). -timestamp() -> - erlang:system_time(milli_seconds). - -timestamp(Units) -> - erlang:system_time(Units). - --spec make_addr_info({term(), {inet:ip_address(), integer()}}) -> atom_address(). -make_addr_info({undefined, {IP, Port}}) -> - {socket_to_atom(IP, Port), ip_to_atom(IP), port_to_atom(Port)}; -make_addr_info({Name, {IP, Port}}) -> - {to_atom(Name), ip_to_atom(IP), port_to_atom(Port)}. - -to_atom(Value) when is_atom(Value) -> Value; -to_atom(Value) when is_binary(Value) -> binary_to_atom(Value, latin1); -to_atom(Value) when is_list(Value) -> list_to_atom(Value). - -socket_to_atom(IP, undefined) -> - ip_to_atom(IP); -socket_to_atom(IP, Port) when is_tuple(IP) -> - list_to_atom(inet:ntoa(IP) ++ ":" ++ integer_to_list(Port)); -socket_to_atom(IP, Port) when is_binary(IP) -> - binary_to_atom(erlang:iolist_to_binary([IP, <<":">>, Port]), latin1); -socket_to_atom(IP, Port) when is_atom(IP) -> - binary_to_atom(erlang:iolist_to_binary([atom_to_binary(IP, latin1), <<":">>, Port]), latin1). - -ip_to_atom(IP) when is_atom(IP) -> IP; -ip_to_atom(IP) -> list_to_atom(inet:ntoa(IP)). - -port_to_atom(undefined) -> undefined; -port_to_atom(Port) when is_atom(Port) -> Port; -port_to_atom(Port) -> list_to_atom(integer_to_list(Port)). - --spec printable_peer(inet:ip4_address(),eradius_server:port_number()) -> io_lib:chars(). -printable_peer({IA,IB,IC,ID}, Port) -> - io_lib:format("~b.~b.~b.~b:~b",[IA,IB,IC,ID,Port]). - -%% @doc calculate the MD5 message authenticator --if(?OTP_RELEASE >= 23). -%% crypto API changes in OTP >= 23 -message_authenticator(Secret, Msg) -> - crypto:mac(hmac, md5, Secret, Msg). --else. -message_authenticator(Secret, Msg) -> - crypto:hmac(md5, Secret, Msg). - --endif. +-spec printable_peer(server_name() | {inet:ip_address() | any, inet:port_number()}) -> io_lib:chars(). +printable_peer(Atom) when is_atom(Atom) -> + [atom_to_list(Atom)]; +printable_peer(Binary) when is_binary(Binary) -> + [Binary]; +printable_peer({IP, Port}) -> + printable_peer(IP, Port). + +-spec printable_peer(inet:ip_address() | any, inet:port_number()) -> io_lib:chars(). +printable_peer(any, Port) -> + ["any:", integer_to_list(Port)]; +printable_peer({_, _, _, _} = IP, Port) -> + [inet:ntoa(IP), $:, integer_to_list(Port)]; +printable_peer({_, _, _, _, _, _, _, _} = IP, Port) -> + [$[, inet:ntoa(IP), $], $:, integer_to_list(Port)]. diff --git a/src/eradius_log.erl b/src/eradius_log.erl index f2974fc4..21b19c11 100644 --- a/src/eradius_log.erl +++ b/src/eradius_log.erl @@ -1,149 +1,99 @@ -%% Copyright (c) 2010-2011 by Travelping GmbH <info@travelping.com> - -%% Permission is hereby granted, free of charge, to any person obtaining a -%% copy of this software and associated documentation files (the "Software"), -%% to deal in the Software without restriction, including without limitation -%% the rights to use, copy, modify, merge, publish, distribute, sublicense, -%% and/or sell copies of the Software, and to permit persons to whom the -%% Software is furnished to do so, subject to the following conditions: - -%% The above copyright notice and this permission notice shall be included in -%% all copies or substantial portions of the Software. - -%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -%% DEALINGS IN THE SOFTWARE. - -%% @private +%% Copyright (c) 2010, 2011, Travelping GmbH <info@travelping.com> +%% +%% SPDX-License-Identifier: MIT +%% -module(eradius_log). --behaviour(gen_server). - %% API --export([start_link/0, write_request/2, collect_meta/2, collect_message/2, reconfigure/0]). +-export([update_logger_process_metadata/1, line/1]). +-export([collect_meta/1, format_req/1]). -export([bin_to_hexstr/1, format_cmd/1]). -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +-ignore_xref([?MODULE]). -include_lib("kernel/include/logger.hrl"). -include("eradius_lib.hrl"). -include("eradius_dict.hrl"). -include("dictionary.hrl"). --type sender() :: {inet:ip_address(), eradius_server:port_number(), eradius_server:req_id()}. - --define(SERVER, ?MODULE). - %%%=================================================================== %%% API %%%=================================================================== --spec start_link() -> {ok, pid()} | {error, Reason :: term}. -start_link() -> - gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). - --spec write_request(sender(), #radius_request{}) -> ok. -write_request(Sender, Request = #radius_request{}) -> - case application:get_env(eradius, logging) of - {ok, true} -> - Time = calendar:universal_time(), - gen_server:cast(?SERVER, {write_request, Time, Sender, Request}); - _ -> - ok - end. - --spec collect_meta(sender(),#radius_request{}) -> [{term(),term()}]. -collect_meta({_NASIP, _NASPort, ReqID}, Request) -> - Request_Type = binary_to_list(format_cmd(Request#radius_request.cmd)), - Request_ID = integer_to_list(ReqID), - Attrs = Request#radius_request.attrs, - [{request_type, Request_Type},{request_id, Request_ID}|[collect_attr(Key, Val) || {Key, Val} <- Attrs]]. - --spec collect_message(sender(),#radius_request{}) -> iolist(). -collect_message({NASIP, NASPort, ReqID}, Request) -> - StatusType = format_acct_status_type(Request), - io_lib:format("~s:~p [~p]: ~s ~s",[inet:ntoa(NASIP), NASPort, ReqID, format_cmd(Request#radius_request.cmd), StatusType]). - --spec reconfigure() -> ok. -reconfigure() -> - gen_server:call(?SERVER, reconfigure). - -%%%=================================================================== -%%% gen_server callbacks -%%%=================================================================== -init(_) -> {ok, init_logger()}. - -handle_call(reconfigure, _From, State) -> - file:close(State), - {reply, ok, init_logger()}; - -%% for tests -handle_call(get_state, _From, State) -> - {reply, State, State}; - -handle_call(_Request, _From, State) -> - {reply, ok, State}. - -handle_cast({write_request, _Time, _Sender, _Request}, logger_disabled = State) -> - {noreply, State}; - -handle_cast({write_request, Time, Sender, Request}, State) -> - try - Msg = format_message(Time, Sender, Request), - ok = io:put_chars(State, Msg), - {noreply, State} - catch - _:Error -> - ?LOG(error, "Failed to log RADIUS request: error: ~p, request: ~p, sender: ~p, " - "logging will be disabled", [Error, Request, Sender]), - {noreply, logger_disabled} - end. -handle_info(_Info, State) -> - {noreply, State}. - -terminate(_Reason, Fd) -> - file:close(Fd), - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. +%% @doc copy RADIUS AVPs into `m:logger' metadata. +%% +%% Helper function for use in a RADIUS server handler that will +%% copy all RADIUS attributes into `m:logger' metadata. +%% +%% <blockquote><h4 class="warning">WARNING</h4> +%% The function will format all attribute values to strings, +%% the resulting processing load can be significant. +%% </blockquote> +-spec update_logger_process_metadata(eradius_req:req()) -> ok. +update_logger_process_metadata(Req) -> + Metadata = maps:from_list(collect_meta(Req)), + logger:update_process_metadata(Metadata). + +%% @doc Serialize `t:eradius_req:req/0' object into a proplist. +%% +%% Helper function for use in a RADIUS server handler that will +%% serialize a RADIUS `t:eradius_req:req/0' object into a Key/Value lists. +%% +%% All values will be converted to human readable strings. +-spec collect_meta(eradius_req:req()) -> [{term(), term()}]. +collect_meta(#{cmd := Cmd, req_id := ReqId, attrs := Attrs}) -> + RequestType = binary_to_list(format_cmd(Cmd)), + RequestId = integer_to_list(ReqId), + [{request_type, RequestType}, + {request_id, RequestId}| + [collect_attr(Key, Val) || {Key, Val} <- Attrs]]. + +%% @doc Format `t:eradius_req:req/0' object into a RADIUS short log entry +%% +%% The short log format is not part of any RADIUS RFC, but has been +%% used by many RADIUS server implementations. +%% +%% The format is: `<Client-IP>:<Client-Port> [<Request-Id>]: <Command> [AcctStatusType]' +-spec line(eradius_req:req()) -> iolist(). +line(#{cmd := Cmd, req_id := ReqId, server_addr := {IP, Port}} = Req) -> + StatusType = format_acct_status_type(Req), + io_lib:format("~s:~p [~p]: ~s ~s", [inet:ntoa(IP), Port, ReqId, format_cmd(Cmd), StatusType]). + +%% @doc Format `t:eradius_req:req/0' object into a RADIUS log entry +%% +%% The long log format is not part of any RADIUS RFC, but has been +%% used by many RADIUS server implementations in the past. +%% +%% The format is: +%% ``` +%% <TimeStamp> <Client-IP>:<Client-Port> [<Request-Id>] <Command> +%% [<Key> = <Value>]+ +%% ''' +-spec format_req(eradius_req:req()) -> binary(). +format_req(Req) -> + Time = + case Req of + #{arrival_time := ATime} -> ATime + erlang:time_offset(); + _ -> erlang:system_time() + end, + format_message(Time, Req). %%%=================================================================== %%% Internal functions %%%=================================================================== -%% -- init -init_logger() -> - case application:get_env(eradius, logging) of - {ok, true} -> init_logfile(); - _ -> logger_disabled - end. - -init_logfile() -> - {ok, LogFile} = application:get_env(eradius, logfile), - ok = filelib:ensure_dir(LogFile), - case file:open(LogFile, [append]) of - {ok, Fd} -> Fd; - Error -> - ?LOG(error, "Failed to open file ~p (~p)", [LogFile, Error]), - logger_disabled - end. %% -- formatting -format_message(Time, Sender, Request) -> +format_message(Time, #{cmd := Cmd} = Req) -> BinTStamp = radius_date(Time), - BinSender = format_sender(Sender), - BinCommand = format_cmd(Request#radius_request.cmd), - BinPacket = format_packet(Request), + BinSender = format_sender(Req), + BinCommand = format_cmd(Cmd), + BinPacket = format_packet(Req), <<BinTStamp/binary, " ", BinSender/binary, " ", BinCommand/binary, "\n", BinPacket/binary, "\n">>. -format_sender({NASIP, NASPort, ReqID}) -> - <<(format_ip(NASIP))/binary, $:, (i2b(NASPort))/binary, " [", (i2b(ReqID))/binary, $]>>. +format_sender(#{req_id := ReqId, server_addr := {IP, Port}}) -> + <<(format_ip(IP))/binary, $:, (i2b(Port))/binary, " [", (i2b(ReqId))/binary, $]>>. +%% @private format_cmd(request) -> <<"Access-Request">>; format_cmd(accept) -> <<"Access-Accept">>; format_cmd(reject) -> <<"Access-Reject">>; @@ -160,8 +110,7 @@ format_cmd(discnak) -> <<"Disconnect-Nak">>. format_ip(IP) -> list_to_binary(inet_parse:ntoa(IP)). -format_packet(Request) -> - Attrs = Request#radius_request.attrs, +format_packet(#{attrs := Attrs} = _Req) -> << <<(print_attr(Key, Val))/binary>> || {Key, Val} <- Attrs >>. print_attr(Key = #attribute{name = Attr, type = Type}, InVal) -> @@ -242,8 +191,10 @@ collectable_attr_value(_Attr, <<Val/binary>>) -> collectable_attr_value(_Attr, Val) -> io_lib:format("~p", [Val]). -radius_date({{YYYY,MM,DD},{Hour,Min,Sec}}) -> - DayNumber = calendar:day_of_the_week(YYYY, MM, DD), +radius_date(Time) -> + {{YYYY, MM, DD} = Date, {Hour, Min, Sec}} = + calendar:system_time_to_universal_time(Time, native), + DayNumber = calendar:day_of_the_week(Date), list_to_binary( io_lib:format("~s ~3.s ~2.2.0w ~2.2.0w:~2.2.0w:~2.2.0w ~4.4.0w", [day(DayNumber), month(MM), DD, Hour, Min, Sec, YYYY])). @@ -292,12 +243,13 @@ hexchar(X) when X >= 0, X < 10 -> hexchar(X) when X >= 10, X < 16 -> X + ($A - 10). +%% @private -compile({inline, bin_to_hexstr/1}). bin_to_hexstr(Bin) -> << << (hexchar(X)) >> || <<X:4>> <= Bin >>. -format_acct_status_type(Request) -> - StatusType = eradius_lib:get_attr(Request, ?Acct_Status_Type), +format_acct_status_type(Req) -> + StatusType = eradius_req:attr(?Acct_Status_Type, Req), case StatusType of undefined -> ""; diff --git a/src/eradius_metrics_prometheus.erl b/src/eradius_metrics_prometheus.erl new file mode 100644 index 00000000..a1dee4a7 --- /dev/null +++ b/src/eradius_metrics_prometheus.erl @@ -0,0 +1,575 @@ +%% Copyright (c) 2024, Travelping GmbH <info@travelping.com> +%% +%% SPDX-License-Identifier: MIT +%% +%% @doc Provides metrics callbacks for recording metrics with prometheus.erl +-module(eradius_metrics_prometheus). + +-export([init/1, reset/0]). +-export([client_metrics_callback/3, server_metrics_callback/3]). + +-ignore_xref([init/1, reset/0]). +-ignore_xref([client_metrics_callback/3, server_metrics_callback/3]). + +-include_lib("kernel/include/logger.hrl"). +-include("dictionary.hrl"). +-include("eradius_lib.hrl"). +-include("eradius_dict.hrl"). + +-define(DEFAULT_BUCKETS, [10, 30, 50, 75, 100, 1000, 2000]). +-define(CONFIG, #{histogram_buckets => ?DEFAULT_BUCKETS, + client_metrics => true, + server_metrics => true}). +-define(TS_CLIENT_KEY, '_prometheus_metrics_client_ts'). +-define(TS_SERVER_KEY, '_prometheus_metrics_server_ts'). + +%%%========================================================================= +%%% Setup +%%%========================================================================= + +%% @doc Initialize the prometheus metrics +-spec init(#{histogram_buckets => [pos_integer()], + client_metrics => boolean(), + server_metrics => boolean()}) -> ok. +init(Opts) -> + Config = maps:merge(?CONFIG, Opts), + + init_client_metrics(Config), + init_server_metrics(Config), + ok. + +reset() -> + ok. + +init_client_metrics(#{histogram_buckets := Buckets, client_metrics := true}) -> + %% + %% Client Side Metrics + %% + + %% Server Status + prometheus_boolean:declare( + [{name, eradius_server_status}, + {labels, [server_ip, server_port]}, + {help, "Status of an upstream RADIUS Server"}]), + + ClientLabels = [server_ip, server_port, server_name, client_ip, client_name], + prometheus_counter:declare( + [{name, eradius_client_requests_total}, + {labels, ClientLabels}, + {help, "Amount of requests sent by a client"}]), + prometheus_counter:declare( + [{name, eradius_client_replies_total}, + {labels, ClientLabels}, + {help, "Amount of replies received by a client"}]), + prometheus_counter:declare( + [{name, eradius_client_access_requests_total}, + {labels, ClientLabels}, + {help, "Amount of Access requests sent by a client"}]), + prometheus_counter:declare( + [{name, eradius_client_accounting_requests_total}, + {labels, ClientLabels ++ [acct_type]}, + {help, "Amount of Accounting requests sent by a client"}]), + prometheus_counter:declare( + [{name, eradius_client_coa_requests_total}, + {labels, ClientLabels}, + {help, "Amount of CoA requests sent by a client"}]), + prometheus_counter:declare( + [{name, eradius_client_disconnect_requests_total}, + {labels, ClientLabels}, + {help, "Amount of Disconnect requests sent by client"}]), + prometheus_counter:declare( + [{name, eradius_client_retransmissions_total}, + {labels, ClientLabels}, + {help, "Amount of retransmissions done by a cliet"}]), + prometheus_counter:declare( + [{name, eradius_client_timeouts_total}, + {labels, ClientLabels}, + {help, "Amount of timeout errors triggered on a client"}]), + prometheus_counter:declare( + [{name, eradius_client_accept_responses_total}, + {labels, ClientLabels}, + {help, "Amount of Accept responses received by a client"}]), + prometheus_counter:declare( + [{name, eradius_client_reject_responses_total}, + {labels, ClientLabels}, + {help, "Amount of Reject responses received by a client"}]), + prometheus_counter:declare( + [{name, eradius_client_access_challenge_total}, + {labels, ClientLabels}, + {help, "Amount of Access-Challenge responses"}]), + prometheus_counter:declare( + [{name, eradius_client_accounting_responses_total}, + {labels, ClientLabels ++ [acct_type]}, + {help, "Amount of Accounting responses received by a client"}]), + prometheus_counter:declare( + [{name, eradius_client_coa_nacks_total}, + {labels, ClientLabels}, + {help, "Amount of CoA Nack received by a client"}]), + prometheus_counter:declare( + [{name, eradius_client_coa_acks_total}, + {labels, ClientLabels}, + {help, "Amount of CoA Ack received by a client"}]), + prometheus_counter:declare( + [{name, eradius_client_disconnect_acks_total}, + {labels, ClientLabels}, + {help, "Amount of Disconnect Acks received by a client"}]), + prometheus_counter:declare( + [{name, eradius_client_disconnect_nacks_total}, + {labels, ClientLabels}, + {help, "Amount of Disconnect Nacks received by a client"}]), + prometheus_counter:declare( + [{name, eradius_client_packets_dropped_total}, + {labels, ClientLabels}, + {help, "Amount of dropped packets"}]), + prometheus_counter:declare( + [{name, eradius_client_unknown_type_request_total}, + {labels, ClientLabels}, + {help, "Amount of RADIUS requests with unknown type"}]), + prometheus_counter:declare( + [{name, eradius_client_bad_authenticator_request_total}, + {labels, ClientLabels}, + {help, "Amount of RADIUS requests with bad authenticator"}]), + prometheus_gauge:declare( + [{name, eradius_client_pending_requests_total}, + {labels, ClientLabels}, + {help, "Amount of pending requests on client side"}]), + + %% Histograms + prometheus_histogram:declare( + [{name, eradius_client_request_duration_milliseconds}, + {labels, ClientLabels}, + {duration_unit, milliseconds}, + {buckets, Buckets}, + {help, "Execution time of a RADIUS request"}]), + prometheus_histogram:declare( + [{name, eradius_client_access_request_duration_milliseconds}, + {labels, ClientLabels}, + {duration_unit, milliseconds}, + {buckets, Buckets}, + {help, "Access-Request execution time"}]), + prometheus_histogram:declare( + [{name, eradius_client_accounting_request_duration_milliseconds}, + {labels, ClientLabels ++ [acct_type]}, + {duration_unit, milliseconds}, + {buckets, Buckets}, + {help, "Accounting-Request execution time"}]), + prometheus_histogram:declare( + [{name, eradius_client_coa_request_duration_milliseconds}, + {labels, ClientLabels}, + {duration_unit, milliseconds}, + {buckets, Buckets}, + {help, "CoA request execution time"}]), + prometheus_histogram:declare( + [{name, eradius_client_disconnect_request_duration_milliseconds}, + {labels, ClientLabels}, + {duration_unit, milliseconds}, + {buckets, Buckets}, + {help, "Disconnect execution time"}]), + ok; +init_client_metrics(_Config) -> + ok. + +init_server_metrics(#{histogram_buckets := Buckets, server_metrics := true}) -> + %% + %% Server Side Metrics + %% + + %% this need a collector... + %% {uptime_milliseconds, gauge, "RADIUS server uptime"}, + %% {since_last_reset_milliseconds, gauge, "RADIUS last server reset time"}, + + ServerLabels = [server_ip, server_port, server_name, nas_ip, nas_id], + prometheus_counter:declare( + [{name, eradius_requests_total}, + {labels, ServerLabels}, + {help, "Amount of requests received by the RADIUS server"}]), + prometheus_counter:declare( + [{name, eradius_replies_total}, + {labels, ServerLabels}, + {help, "Amount of responses"}]), + prometheus_counter:declare( + [{name, eradius_access_requests_total}, + {labels, ServerLabels}, + {help, "Amount of Access requests received by the RADIUS server"}]), + prometheus_counter:declare( + [{name, eradius_accounting_requests_total}, + {labels, ServerLabels ++ [acct_type]}, + {help, "Amount of Accounting requests received by RADIUS server"}]), + prometheus_counter:declare( + [{name, eradius_coa_requests_total}, + {labels, ServerLabels}, + {help, "Amount of CoA requests received by the RADIUS server"}]), + prometheus_counter:declare( + [{name, eradius_disconnect_requests_total}, + {labels, ServerLabels}, + {help, "Amount of Disconnect requests received by the RADIUS server"}]), + prometheus_counter:declare( + [{name, eradius_accept_responses_total}, + {labels, ServerLabels}, + {help, "Amount of Access-Accept responses"}]), + prometheus_counter:declare( + [{name, eradius_reject_responses_total}, + {labels, ServerLabels}, + {help, "Amount of Access-Reject responses"}]), + prometheus_counter:declare( + [{name, eradius_access_challenge_total}, + {labels, ServerLabels}, + {help, "Amount of Access-Challenge responses"}]), + prometheus_counter:declare( + [{name, eradius_accounting_responses_total}, + {labels, ServerLabels ++ [acct_type]}, + {help, "Amount of Accounting responses"}]), + prometheus_counter:declare( + [{name, eradius_coa_acks_total}, + {labels, ServerLabels}, + {help, "Amount of CoA ACK responses"}]), + prometheus_counter:declare( + [{name, eradius_coa_nacks_total}, + {labels, ServerLabels}, + {help, "Amount of CoA Nack responses"}]), + prometheus_counter:declare( + [{name, eradius_disconnect_acks_total}, + {labels, ServerLabels}, + {help, "Amount of Disconnect-Ack responses"}]), + prometheus_counter:declare( + [{name, eradius_disconnect_nacks_total}, + {labels, ServerLabels}, + {help, "Amount of Disconnect-Nack responses"}]), + prometheus_counter:declare( + [{name, eradius_malformed_requests_total}, + {labels, ServerLabels}, + {help, "Amount of malformed requests on RADIUS server"}]), + prometheus_counter:declare( + [{name, eradius_invalid_requests_total}, + {labels, ServerLabels}, + {help, "Amount of invalid requests on RADIUS server"}]), + prometheus_counter:declare( + [{name, eradius_retransmissions_total}, + {labels, ServerLabels}, + {help, "Amount of retrasmissions done by NAS"}]), + prometheus_counter:declare( + [{name, eradius_duplicated_requests_total}, + {labels, ServerLabels}, + {help, "Amount of duplicated requests"}]), + prometheus_gauge:declare( + [{name, eradius_pending_requests_total}, + {labels, ServerLabels}, + {help, "Amount of pending requests"}]), + prometheus_counter:declare( + [{name, eradius_packets_dropped_total}, + {labels, ServerLabels}, + {help, "Amount of dropped packets"}]), + prometheus_counter:declare( + [{name, eradius_unknown_type_request_total}, + {labels, ServerLabels}, + {help, "Amount of RADIUS requests with unknown type"}]), + prometheus_counter:declare( + [{name, eradius_bad_authenticator_request_total}, + {labels, ServerLabels}, + {help, "Amount of RADIUS requests with bad authenticator"}]), + + %% Histograms + prometheus_histogram:declare( + [{name, eradius_request_duration_milliseconds}, + {labels, ServerLabels}, + {duration_unit, milliseconds}, + {buckets, Buckets}, + {help, "RADIUS request execution time"}]), + prometheus_histogram:declare( + [{name, eradius_access_request_duration_milliseconds}, + {labels, ServerLabels}, + {duration_unit, milliseconds}, + {buckets, Buckets}, + {help, "Access-Request execution time"}]), + prometheus_histogram:declare( + [{name, eradius_accounting_request_duration_milliseconds}, + {labels, ServerLabels ++ [acct_type]}, + {duration_unit, milliseconds}, + {buckets, Buckets}, + {help, "Accounting-Request execution time"}]), + prometheus_histogram:declare( + [{name, eradius_coa_request_duration_milliseconds}, + {labels, ServerLabels}, + {duration_unit, milliseconds}, + {buckets, Buckets}, + {help, "Coa-Request execution time"}]), + prometheus_histogram:declare( + [{name, eradius_disconnect_request_duration_milliseconds}, + {labels, ServerLabels}, + {duration_unit, milliseconds}, + {buckets, Buckets}, + {help, "Disconnect-Request execution time"}]), + ok; +init_server_metrics(_Config) -> + ok. + +%%%========================================================================= +%%% Metrics Handler +%%%========================================================================= + +%% @doc Function for use as `t:eradius_req:metrics_callback/0' for a `t:eradius_req:req/0' +%% object in a RADIUS client to record prometheus metrics +-spec client_metrics_callback(Event :: eradius_req:metrics_event(), + MetaData :: term(), + Req :: eradius_req:req()) -> eradius_req:req(). +client_metrics_callback(Event, MetaData, + #{server := Server, server_addr := {ServerIP, ServerPort}, + client := Client, client_addr := ClientIP + } = Req) -> + ?LOG(debug, "Client-Metric:~nEvent: ~p~nMetaData: ~p~nReq: ~p~n", + [Event, MetaData, Req]), + + Labels = [ServerIP, ServerPort, Server, ClientIP, Client], + case Event of + request -> + client_request_metrics(MetaData, Labels, Req); + retransmission -> + prometheus_counter:inc(eradius_client_retransmissions_total, Labels, 1), + Req; + reply -> + client_reply_metrics(MetaData, Labels, Req); + _ -> + Req + end; +client_metrics_callback(Event, MetaData, Req) -> + ?LOG(error, "BROKEN Client-Metric:~nEvent: ~p~nMetaData: ~p~nReq: ~p~n", + [Event, MetaData, Req]), + Req. + +client_request_metrics(_MetaData, Labels, #{cmd := Cmd} = Req) -> + prometheus_counter:inc(eradius_client_requests_total, Labels, 1), + case Cmd of + request -> + prometheus_counter:inc(eradius_client_access_requests_total, Labels, 1); + accreq -> + AcctStatusType = acct_status_type(Req), + prometheus_counter:inc( + eradius_client_accounting_requests_total, Labels ++ [AcctStatusType], 1); + coareq -> + prometheus_counter:inc(eradius_client_coa_requests_total, Labels, 1); + discreq -> + prometheus_counter:inc(eradius_client_disconnect_requests_total, Labels, 1); + _ -> + %% WTF? how can a client generate a request with an unknown type? + %% should probably be _response_, keep it for compatibility + prometheus_counter:inc(eradius_client_unknown_type_request_total, Labels, 1) + end, + Req#{?TS_CLIENT_KEY => erlang:monotonic_time()}. + +client_request_duration(#{request := #{cmd := ReqCmd}}, Labels, + #{?TS_CLIENT_KEY := TS} = Req) -> + Duration = erlang:monotonic_time() - TS, + prometheus_histogram:observe( + eradius_request_duration_milliseconds, Labels, Duration), + + case ReqCmd of + request -> + prometheus_histogram:observe( + eradius_client_access_request_duration_milliseconds, Labels, Duration); + accreq -> + AcctStatusType = acct_status_type(Req), + prometheus_histogram:observe( + eradius_client_accounting_request_duration_milliseconds, + Labels ++ [AcctStatusType], Duration); + coareq -> + prometheus_histogram:observe( + eradius_client_coa_request_duration_milliseconds, Labels, Duration); + discreq -> + prometheus_histogram:observe( + eradius_client_disconnect_request_duration_milliseconds, Labels, Duration); + _ -> + ok + end, + Req. + +client_reply_metrics(MetaData, Labels, + #{cmd := Cmd, server_addr := {ServerIP, ServerPort}} = Req) -> + prometheus_boolean:set(eradius_server_status, [ServerIP, ServerPort], true), + case Cmd of + accept -> + prometheus_counter:inc(eradius_client_accept_responses_total, Labels, 1); + reject -> + prometheus_counter:inc(eradius_client_reject_responses_total, Labels, 1); + challenge -> + prometheus_counter:inc(eradius_client_access_challenge_total, Labels, 1); + accresp -> + AcctStatusType = acct_status_type(Req), + prometheus_counter:inc( + eradius_client_accounting_responses_total, Labels ++ [AcctStatusType], 1); + coaack -> + prometheus_counter:inc(eradius_client_coa_acks_total, Labels, 1); + coanak -> + prometheus_counter:inc(eradius_client_coa_nacks_total, Labels, 1); + discack -> + prometheus_counter:inc(eradius_client_disconnect_acks_total, Labels, 1); + discnak -> + prometheus_counter:inc(eradius_client_disconnect_nacks_total, Labels, 1); + _ -> + %% should probably be _response_, keep it for compatibility + prometheus_counter:inc(eradius_client_unknown_type_request_total, Labels, 1) + end, + client_request_duration(MetaData, Labels, Req). + +%% @doc Function for use as `t:eradius_req:metrics_callback/0' for a `t:eradius_req:req/0' +%% object in a RADIUS server to record prometheus metrics +-spec server_metrics_callback(Event :: eradius_req:metrics_event(), + MetaData :: term(), + Req :: eradius_req:req()) -> eradius_req:req(). +server_metrics_callback(Event, MetaData, + #{server := Server, server_addr := {ServerIP, ServerPort}, + client := Client, client_addr := ClientIP + } = Req) -> + ?LOG(debug, "Server-Metric:~nEvent: ~p~nMetaData: ~p~nReq: ~p~n", + [Event, MetaData, Req]), + + Labels = [ServerIP, ServerPort, Server, ClientIP, Client], + case Event of + request -> + server_request_metrics(MetaData, Labels, Req); + retransmission -> + prometheus_counter:inc(eradius_requests_total, Labels, 1), + prometheus_counter:inc(eradius_retransmissions_total, Labels, 1), + Req; + discard -> + server_discard_metrics(MetaData, Labels, Req); + reply -> + server_reply_metrics(MetaData, Labels, Req); + _ -> + ?LOG(error, "Unexpected Server-Metric:~nEvent: ~p~nMetaData: ~p~nReq: ~p~n", + [Event, MetaData, Req]), + Req + end; +server_metrics_callback(invalid_request, #{server := Server} = _MetaData, _) -> + prometheus_counter:inc(eradius_invalid_requests_total, [Server], 1), + ok; +server_metrics_callback(Event, MetaData, Req) -> + ?LOG(error, "Unexpected Server-Metric:~nEvent: ~p~nMetaData: ~p~nReq: ~p~n", + [Event, MetaData, Req]), + Req. + +server_request_metrics(_MetaData, Labels, #{cmd := Cmd} = Req) -> + prometheus_counter:inc(eradius_requests_total, Labels, 1), + prometheus_gauge:inc(eradius_pending_requests_total, Labels, 1), + case Cmd of + request -> + prometheus_counter:inc(eradius_access_requests_total, Labels, 1); + accreq -> + AcctStatusType = acct_status_type(Req), + prometheus_counter:inc( + eradius_accounting_requests_total, Labels ++ [AcctStatusType], 1); + coareq -> + prometheus_counter:inc(eradius_coa_requests_total, Labels, 1); + discreq -> + prometheus_counter:inc(eradius_disconnect_requests_total, Labels, 1); + _ -> + prometheus_counter:inc(eradius_unknown_type_request_total, Labels, 1) + end, + Req#{?TS_SERVER_KEY => erlang:monotonic_time()}. + +server_discard_metrics(MetaData, Labels, Req) -> + prometheus_gauge:inc(eradius_pending_requests_total, Labels, -1), + prometheus_counter:inc(eradius_requests_total, Labels, 1), + prometheus_counter:inc(eradius_packets_dropped_total, Labels, 1), + case MetaData of + #{reason := duplicate} -> + prometheus_counter:inc(eradius_duplicated_requests_total, Labels, 1); + #{reason := bad_authenticator} -> + prometheus_counter:inc(eradius_bad_authenticator_request_total, Labels, 1); + #{reason := unknown_req_type} -> + prometheus_counter:inc(eradius_unknown_type_request_total, Labels, 1); + #{reason := malformed} -> + prometheus_counter:inc(eradius_malformed_requests_total, Labels, 1); + _ -> + ?LOG(error, "Unexpected Server-Metric:~nEvent: ~p~nMetaData: ~p~nReq: ~p~n", + [discard, MetaData, Req]), + ok + end, + Req. + +server_request_duration(#{request := #{cmd := ReqCmd}}, Labels, + #{?TS_SERVER_KEY := TS} = Req) -> + Duration = erlang:monotonic_time() - TS, + prometheus_histogram:observe( + eradius_request_duration_milliseconds, Labels, Duration), + + case ReqCmd of + request -> + prometheus_histogram:observe( + eradius_access_request_duration_milliseconds, Labels, Duration); + accreq -> + AcctStatusType = acct_status_type(Req), + prometheus_histogram:observe( + eradius_accounting_request_duration_milliseconds, + Labels ++ [AcctStatusType], Duration); + coareq -> + prometheus_histogram:observe( + eradius_coa_request_duration_milliseconds, Labels, Duration); + discreq -> + prometheus_histogram:observe( + eradius_disconnect_request_duration_milliseconds, Labels, Duration); + _ -> + ok + end, + Req. + +server_reply_metrics(MetaData, Labels, #{cmd := Cmd} = Req) -> + prometheus_counter:inc(eradius_replies_total, Labels, 1), + prometheus_gauge:inc(eradius_pending_requests_total, Labels, -1), + case Cmd of + accept -> + prometheus_counter:inc(eradius_accept_responses_total, Labels, 1); + reject -> + prometheus_counter:inc(eradius_reject_responses_total, Labels, 1); + challenge -> + prometheus_counter:inc(eradius_access_challenge_total, Labels, 1); + accresp -> + AcctStatusType = acct_status_type(Req), + prometheus_counter:inc( + eradius_accounting_responses_total, Labels ++ [AcctStatusType], 1); + coaack -> + prometheus_counter:inc(eradius_coa_acks_total, Labels, 1); + coanak -> + prometheus_counter:inc(eradius_coa_nacks_total, Labels, 1); + discack -> + prometheus_counter:inc(eradius_disconnect_acks_total, Labels, 1); + discnak -> + prometheus_counter:inc(eradius_disconnect_nacks_total, Labels, 1); + _ -> + ok + end, + server_request_duration(MetaData, Labels, Req). + +acct_status_type(#{attrs := Attrs}) when is_list(Attrs) -> + acct_status_type_list(Attrs); +acct_status_type(#{body := Body}) when is_binary(Body) -> + acct_status_type_scan(Body); +acct_status_type(_) -> + invalid. + +acct_status_type_list([]) -> + invalid; +acct_status_type_list([{?Acct_Status_Type, Type}|_]) -> + acct_status_type_label(Type); +acct_status_type_list([{#attribute{id = ?Acct_Status_Type}, Type}|_]) -> + acct_status_type_label(Type); +acct_status_type_list([_|Next]) -> + acct_status_type_list(Next). + +acct_status_type_scan(<<?Acct_Status_Type, 6, Type:32, _/binary>>) -> + acct_status_type_label(Type); +acct_status_type_scan(<<_, Len, Rest/binary>>) -> + case Rest of + <<_:(Len-2)/bytes, Next/binary>> -> + acct_status_type_scan(Next); + _ -> + invalid + end; +acct_status_type_scan(_) -> + invalid. + +acct_status_type_label(?RStatus_Type_Start) -> start; +acct_status_type_label(?RStatus_Type_Stop) -> stop; +acct_status_type_label(?RStatus_Type_Update) -> update; +acct_status_type_label(?RStatus_Type_On) -> on; +acct_status_type_label(?RStatus_Type_Off) -> off; +acct_status_type_label(Type) -> integer_to_list(Type). diff --git a/src/eradius_node_mon.erl b/src/eradius_node_mon.erl deleted file mode 100644 index f352ab40..00000000 --- a/src/eradius_node_mon.erl +++ /dev/null @@ -1,186 +0,0 @@ -%% @private -%% @doc A server that keeps track of handler nodes. -%% Handler nodes should call {@link eradius:modules_ready/2} from their application master -%% as soon as they are ready, which makes them available for request processing. -%% The node_mon server monitors the application master and removes it from -%% request processing when it goes down. --module(eradius_node_mon). --export([start_link/0, modules_ready/2, set_nodes/1, get_module_nodes/1, get_remote_version/1]). - --behaviour(gen_server). --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). - --include_lib("kernel/include/logger.hrl"). - --define(NODE_TAB, eradius_node_mon). --define(NODE_INFO_TAB, eradius_node_info). --define(PING_INTERVAL, 3000). % 3 sec --define(PING_TIMEOUT, 300). % 0.3 sec --define(SERVER, ?MODULE). - -%% ------------------------------------------------------------------------------------------ -%% -- API -start_link() -> - gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). - --spec modules_ready(pid(), list(module())) -> ok. -modules_ready(ApplicationMaster, Modules) when is_pid(ApplicationMaster), is_list(Modules) -> - gen_server:cast(?SERVER, {modules_ready, ApplicationMaster, Modules}). - --spec set_nodes(list(node())) -> ok. -set_nodes(Nodes) -> - gen_server:call(?SERVER, {set_nodes, Nodes}). - --spec get_module_nodes(module()) -> [node()]. -get_module_nodes(Module) -> - try - ets:lookup_element(?NODE_TAB, Module, 2) - catch - error:badarg -> - [] - end. - --spec get_remote_version(node()) -> {integer(), integer()} | undefined. -get_remote_version(Node) -> - try - ets:lookup_element(?NODE_INFO_TAB, Node, 2) - catch - error:badarg -> - undefined - end. - -%% ------------------------------------------------------------------------------------------ -%% -- gen_server callbacks --record(state, { - live_registrar_nodes = sets:new() :: sets:set(), - dead_registrar_nodes = sets:new() :: sets:set(), - app_masters = maps:new() :: map(), - ping_timer :: reference() - }). - -init([]) -> - ets:new(?NODE_TAB, [bag, named_table, protected, {read_concurrency, true}]), - ets:new(?NODE_INFO_TAB, [set, named_table, protected, {read_concurrency, true}]), - PingTimer = erlang:send_after(?PING_INTERVAL, self(), ping_dead_nodes), - {ok, #state{ping_timer = PingTimer}}. - -handle_call(remote_get_regs_v1, From, State) -> - check_eradius_version(From), - Registrations = maps:to_list(State#state.app_masters), - {reply, {ok, Registrations}, State}; -handle_call({set_nodes, Nodes}, _From, State) -> - NewState = State#state{live_registrar_nodes = sets:new(), - dead_registrar_nodes = sets:from_list(Nodes)}, - self() ! ping_dead_nodes, - {reply, ok, NewState}. - -handle_cast({remote_modules_ready_v1, ApplicationMaster, Modules}, State) -> - NewState = State#state{app_masters = register_locally({ApplicationMaster, Modules}, State#state.app_masters)}, - {noreply, NewState}; -handle_cast({modules_ready, ApplicationMaster, Modules}, State) -> - NewState = State#state{app_masters = register_locally({ApplicationMaster, Modules}, State#state.app_masters)}, - lists:foreach(fun (Node) -> - check_eradius_version(Node), - gen_server:cast({?SERVER, Node}, {remote_modules_ready_v1, ApplicationMaster, Modules}) - end, nodes()), - {noreply, NewState}. - -handle_info({'DOWN', _MRef, process, {?SERVER, Node}, _Reason}, State = #state{live_registrar_nodes = LiveRegistrars}) -> - case sets:is_element(Node, LiveRegistrars) of - false -> - %% ignore the 'DOWN', it's from a node we don't really want to monitor anymore - %% and that shouldn't get into dead_registrar_nodes - {noreply, State}; - true -> - {noreply, State#state{live_registrar_nodes = sets:del_element(Node, LiveRegistrars), - dead_registrar_nodes = sets:add_element(Node, State#state.dead_registrar_nodes)}} - end; -handle_info({'DOWN', _MRef, process, Pid, _Reason}, State = #state{app_masters = AppMasters}) when is_pid(Pid) -> - case maps:find(Pid, AppMasters) of - error -> - {noreply, State}; - {ok, Modules} -> - ServerNode = node(Pid), - lists:foreach(fun (Mod) -> ets:delete_object(?NODE_TAB, {Mod, ServerNode}) end, Modules), - NewState = State#state{app_masters = maps:remove(Pid, AppMasters)}, - {noreply, NewState} - end; -handle_info(ping_dead_nodes, State = #state{app_masters = AppMasters, live_registrar_nodes = LiveRegistrars}) -> - erlang:cancel_timer(State#state.ping_timer), - {NewLive, NewDead, NewAppMasters} = - sets:fold(fun (Node, {Live, Dead, AppMastersAcc}) -> - case (catch gen_server:call({?SERVER, Node}, remote_get_regs_v1, ?PING_TIMEOUT)) of - {ok, Registrations} -> - NewAppMastersAcc = lists:foldl(fun register_locally/2, AppMastersAcc, Registrations), - erlang:monitor(process, {?SERVER, Node}), - {sets:add_element(Node, Live), Dead, NewAppMastersAcc}; - {'EXIT', _Reason} -> - {Live, sets:add_element(Node, Dead), AppMastersAcc} - end - end, {LiveRegistrars, sets:new(), AppMasters}, State#state.dead_registrar_nodes), - NewPingTimer = erlang:send_after(?PING_INTERVAL, self(), ping_dead_nodes), - NewState = State#state{live_registrar_nodes = NewLive, - dead_registrar_nodes = NewDead, - app_masters = NewAppMasters, - ping_timer = NewPingTimer}, - {noreply, NewState}; -handle_info(_Info, State) -> - {noreply, State}. - -terminate(_Reason, _State) -> ok. -code_change(_OldVsn, State, _Extra) -> {ok, State}. - -%% ------------------------------------------------------------------------------------------ -%% -- helpers --spec dict_prepend(term(), list(term()), map()) -> map(). -dict_prepend(Key, List, Map) -> - update_with(Key, fun (Old) -> List ++ Old end, List, Map). - -%% NOTE: -%% copy-pasted from maps.erl to have backward compatability with OTP 18 -%% it can be rewmoved if minimal version of OTP will be set to 19. -update_with(Key,Fun,Init,Map) when is_function(Fun,1), is_map(Map) -> - case maps:find(Key,Map) of - {ok,Val} -> maps:update(Key,Fun(Val),Map); - error -> maps:put(Key,Init,Map) - end; -update_with(Key,Fun,Init,Map) -> - erlang:error(error_type(Map),[Key,Fun,Init,Map]). - --define(IS_ITERATOR(I), is_tuple(I) andalso tuple_size(I) == 3; I == none; is_integer(hd(I)) andalso is_map(tl(I))). -error_type(M) when is_map(M); ?IS_ITERATOR(M) -> badarg; -error_type(V) -> {badmap, V}. - -register_locally({ApplicationMaster, Modules}, AppMasters) -> - case maps:is_key(ApplicationMaster, AppMasters) of - true -> - ok; %% already monitored - false -> - monitor(process, ApplicationMaster) - end, - ServerNode = node(ApplicationMaster), - ets:insert(?NODE_TAB, [{Mod, ServerNode} || Mod <- Modules]), - dict_prepend(ApplicationMaster, Modules, AppMasters). - -check_eradius_version({Pid, _}) when is_pid(Pid) -> - check_eradius_version(Pid); -check_eradius_version(Pid) when is_pid(Pid) -> - check_eradius_version(node(Pid)); -check_eradius_version(Node) -> - case rpc:call(Node, application, get_key, [eradius, vsn]) of - {ok, Vsn} -> - try interpret_vsn(Vsn) of - Version -> - ets:insert(?NODE_INFO_TAB, {Node, Version}) - catch - _:_ -> - ?LOG(warning, "unknown eradius version format ~p on node ~p", [Vsn, Node]) - end; - _ -> - ?LOG(warning, "eradius version do not known on node ~p", [Node]) - end. - -interpret_vsn(Vsn) -> - BinVsn = list_to_binary(Vsn), - [MajorVsn, MinorVsn | _] = binary:split(BinVsn, <<".">>, [global]), - {binary_to_integer(MajorVsn), binary_to_integer(MinorVsn)}. diff --git a/src/eradius_proxy.erl b/src/eradius_proxy.erl deleted file mode 100644 index 5961b13b..00000000 --- a/src/eradius_proxy.erl +++ /dev/null @@ -1,334 +0,0 @@ -%% @doc -%% This module implements a RADIUS proxy. -%% -%% It accepts following configuration: -%% -%% ``` -%% [{default_route, {{127, 0, 0, 1}, 1813, <<"secret">>}, [{pool, pool_name}, {retries, 5}, {timeout, 5000}], -%% {options, [{type, realm}, {strip, true}, {separator, "@"}]}, -%% {routes, [{"^test-[0-9].", {{127, 0, 0, 1}, 1815, <<"secret1">>}, [{pool, pool_name}, {retries, 5}, {timeout, 5000}]}]}] -%% ''' -%% -%% Or for backward compatibility: -%% -%% ``` -%% [{default_route, {{127, 0, 0, 1}, 1813, <<"secret">>}, pool_name}, -%% {options, [{type, realm}, {strip, true}, {separator, "@"}]}, -%% {routes, [{"^test-[0-9].", {{127, 0, 0, 1}, 1815, <<"secret1">>}, [{pool, pool_name}, {retries, 5}, {timeout, 5000}]}]}] -%% ''' -%% -%% Where the `pool_name` is the name of the pool that must be specified -%% in the `servers_pool` configuration and will be used as a pointer to -%% the list of secondary RADIUS servers for fail-over scenarios. -%% -%% ``` -%% {servers_pool, [{pool_name, [ -%% {{127, 0, 0, 1}, 1815, <<"secret">>, [{retries, 3}]}, -%% {{127, 0, 0, 1}, 1816, <<"secret">>}]}]} -%% ''' -%% -%% == WARNING == -%% -%% Define `routes' carefully. The `test' here in example above, is -%% a regular expression that may cause to problemts with performance. --module(eradius_proxy). - --behaviour(eradius_server). --export([radius_request/3, validate_arguments/1, get_routes_info/1, - put_default_route_to_pool/2, put_routes_to_pool/2]). - --ifdef(TEST). --export([resolve_routes/4, validate_options/1, new_request/3, - get_key/4, strip/4]). --endif. - --include_lib("kernel/include/logger.hrl"). --include("eradius_lib.hrl"). --include("dictionary.hrl"). - --define(DEFAULT_TYPE, realm). --define(DEFAULT_STRIP, false). --define(DEFAULT_SEPARATOR, "@"). --define(DEFAULT_TIMEOUT, 5000). --define(DEFAULT_RETRIES, 1). --define(DEFAULT_CLIENT_RETRIES, 3). - --define(DEFAULT_OPTIONS, [{type, ?DEFAULT_TYPE}, - {strip, ?DEFAULT_STRIP}, - {separator, ?DEFAULT_SEPARATOR}, - {timeout, ?DEFAULT_TIMEOUT}, - {retries, ?DEFAULT_RETRIES}]). - --type pool_name() :: atom(). --type route() :: eradius_client:nas_address() | - {eradius_client:nas_address(), RouteOptions :: [tuple()] | pool_name()}. --type routes() :: [{Name :: string(), eradius_client:nas_address()}] | - [{Name :: string(), eradius_client:nas_address(), PoolName :: atom()}]. --type undefined_route() :: {undefined, 0, []}. - -radius_request(Request, _NasProp, Args) -> - DefaultRoute = get_proxy_opt(default_route, Args, {undefined, 0, []}), - Routes = get_proxy_opt(routes, Args, []), - Options = proplists:get_value(options, Args, ?DEFAULT_OPTIONS), - Username = eradius_lib:get_attr(Request, ?User_Name), - {NewUsername, Route} = resolve_routes(Username, DefaultRoute, Routes, Options), - SendOpts = get_send_options(Route, Options), - send_to_server(new_request(Request, Username, NewUsername), Route, SendOpts). - -validate_arguments(Args) -> - DefaultRoute = get_proxy_opt(default_route, Args, {undefined, 0, []}), - Options = proplists:get_value(options, Args, ?DEFAULT_OPTIONS), - Routes = get_proxy_opt(routes, Args, undefined), - case {validate_route(DefaultRoute), validate_options(Options), compile_routes(Routes)} of - {false, _, _} -> default_route; - {_, false, _} -> options; - {_, _, false} -> routes; - {_, _, NewRoutes} -> - {true, [{default_route, DefaultRoute}, {options, Options}, {routes, NewRoutes}]} - end. - -compile_routes(undefined) -> []; -compile_routes(Routes) -> - RoutesOpts = lists:map(fun (Route) -> - {Name, Relay, RouteOptions} = route(Route), - case re:compile(Name) of - {ok, R} -> - case validate_route({Relay, RouteOptions}) of - false -> - false; - _ -> {R, Relay, RouteOptions} - end; - {error, {Error, Position}} -> - throw("Error during regexp compilation - " ++ Error ++ " at position " ++ integer_to_list(Position)) - end - end, Routes), - RelaysRegexps = lists:any(fun(Route) -> Route == false end, RoutesOpts), - if RelaysRegexps == false -> - RoutesOpts; - true -> - false - end. - - % @private --spec send_to_server(Request :: #radius_request{}, - Route :: undefined_route() | route(), - Options :: eradius_client:options()) -> - {reply, Reply :: #radius_request{}} | term(). -send_to_server(_Request, {undefined, 0, []}, _) -> - {error, no_route}; - -send_to_server(#radius_request{reqid = ReqID} = Request, {{Server, Port, Secret}, RelayOpts}, Options) -> - UpstreamServers = get_failover_servers(RelayOpts), - case eradius_client:send_request({Server, Port, Secret}, Request, [{failover, UpstreamServers} | Options]) of - {ok, Result, Auth} -> - decode_request(Result, ReqID, Secret, Auth); - no_active_servers -> - % If all RADIUS servers are marked as inactive for now just use - % just skip fail-over mechanism and use default given Peer - send_to_server(Request, {Server, Port, Secret}, Options); - Error -> - ?LOG(error, "~p: error during send_request (~p)", [?MODULE, Error]), - Error - end; -send_to_server(#radius_request{reqid = ReqID} = Request, {Server, Port, Secret}, Options) -> - case eradius_client:send_request({Server, Port, Secret}, Request, Options) of - {ok, Result, Auth} -> decode_request(Result, ReqID, Secret, Auth); - Error -> - ?LOG(error, "~p: error during send_request (~p)", [?MODULE, Error]), - Error - end. - - % @private -decode_request(Result, ReqID, Secret, Auth) -> - case eradius_lib:decode_request(Result, Secret, Auth) of - Reply = #radius_request{} -> - {reply, Reply#radius_request{reqid = ReqID}}; - Error -> - ?LOG(error, "~p: request is incorrect (~p)", [?MODULE, Error]), - Error - end. - - % @private --spec validate_route(Route :: route()) -> boolean(). -validate_route({{Host, Port, Secret}, RouteOpts}) -> - validate_route_options(RouteOpts) and validate_route({Host, Port, Secret}); -validate_route({_Host, Port, _Secret}) when not is_integer(Port); Port =< 0; Port > 65535 -> false; -validate_route({_Host, _Port, Secret}) when not is_list(Secret), not is_binary(Secret) -> false; -validate_route({Host, _Port, _Secret}) when is_list(Host) -> true; -validate_route({Host, Port, Secret}) when is_tuple(Host) -> - case inet_parse:ntoa(Host) of - {error, _} -> false; - Address -> validate_route({Address, Port, Secret}) - end; -validate_route({Host, _Port, _Secret}) when is_binary(Host) -> true; -validate_route(_) -> false. - - % @private --spec validate_route_options(Options :: [proplists:property()] | pool_name()) -> boolean(). -validate_route_options(PoolName) when is_atom(PoolName) -> - true; -validate_route_options([]) -> - true; -validate_route_options(Options) -> - Keys = proplists:get_keys(Options), - lists:all(fun(Key) -> validate_route_option(Key, proplists:get_value(Key, Options)) end, Keys). - - % @private --spec validate_route_option(Key :: atom(), Value :: term()) -> boolean(). -validate_route_option(timeout, Value) when is_integer(Value) -> - true; -validate_route_option(retries, Value) when is_integer(Value) -> - true; -validate_route_option(pool, Value) when is_atom(Value) -> - true; -validate_route_option(_, _) -> - false. - - % @private --spec validate_options(Options :: [proplists:property()]) -> boolean(). -validate_options(Options) -> - Keys = proplists:get_keys(Options), - lists:all(fun(Key) -> validate_option(Key, proplists:get_value(Key, Options)) end, Keys). - - % @private --spec validate_option(Key :: atom(), Value :: term()) -> boolean(). -validate_option(type, Value) when Value =:= realm; Value =:= prefix -> true; -validate_option(type, _Value) -> false; -validate_option(strip, Value) when is_boolean(Value) -> true; -validate_option(strip, _Value) -> false; -validate_option(separator, Value) when is_list(Value) -> true; -validate_option(timeout, Value) when is_integer(Value) -> true; -validate_option(retries, Value) when is_integer(Value) -> true; -validate_option(_, _) -> false. - - - % @private --spec new_request(Request :: #radius_request{}, - Username :: undefined | binary(), - NewUsername :: string()) -> - NewRequest :: #radius_request{}. -new_request(Request, Username, Username) -> Request; -new_request(Request, _Username, NewUsername) -> - eradius_lib:set_attr(eradius_lib:del_attr(Request, ?User_Name), - ?User_Name, NewUsername). - - % @private --spec resolve_routes(Username :: undefined | binary(), - DefaultRoute :: undefined_route() | route(), - Routes :: routes(), Options :: [proplists:property()]) -> - {NewUsername :: string(), Route :: route()}. -resolve_routes( undefined, DefaultRoute, _Routes, _Options) -> - {undefined, DefaultRoute}; -resolve_routes(Username, DefaultRoute, Routes, Options) -> - Type = proplists:get_value(type, Options, ?DEFAULT_TYPE), - Strip = proplists:get_value(strip, Options, ?DEFAULT_STRIP), - Separator = proplists:get_value(separator, Options, ?DEFAULT_SEPARATOR), - case get_key(Username, Type, Strip, Separator) of - {not_found, NewUsername} -> - {NewUsername, DefaultRoute}; - {Key, NewUsername} -> - {NewUsername, find_suitable_relay(Key, Routes, DefaultRoute)} - end. - -find_suitable_relay(_Key, [], DefaultRoute) -> DefaultRoute; -find_suitable_relay(Key, [{Regexp, Relay} | Routes], DefaultRoute) -> - case re:run(Key, Regexp, [{capture, none}]) of - nomatch -> find_suitable_relay(Key, Routes, DefaultRoute); - _ -> Relay - end; -find_suitable_relay(Key, [{Regexp, Relay, RelayOpts} | Routes], DefaultRoute) -> - case re:run(Key, Regexp, [{capture, none}]) of - nomatch -> find_suitable_relay(Key, Routes, DefaultRoute); - _ -> {Relay, RelayOpts} - end. - - % @private --spec get_key(Username :: binary() | string() | [], Type :: atom(), Strip :: boolean(), Separator :: list()) -> - {Key :: not_found | string(), NewUsername :: string()}. -get_key([], _, _, _) -> {not_found, []}; -get_key(Username, Type, Strip, Separator) when is_binary(Username) -> - get_key(binary_to_list(Username), Type, Strip, Separator); -get_key(Username, realm, Strip, Separator) -> - Realm = lists:last(string:tokens(Username, Separator)), - {Realm, strip(Username, realm, Strip, Separator)}; -get_key(Username, prefix, Strip, Separator) -> - Prefix = hd(string:tokens(Username, Separator)), - {Prefix, strip(Username, prefix, Strip, Separator)}; -get_key(Username, _, _, _) -> {not_found, Username}. - - % @private --spec strip(Username :: string(), Type :: atom(), Strip :: boolean(), Separator :: list()) -> - NewUsername :: string(). -strip(Username, _, false, _) -> Username; -strip(Username, realm, true, Separator) -> - case string:tokens(Username, Separator) of - [Username] -> Username; - [_ | _] = List -> - [_ | Tail] = lists:reverse(List), - string:join(lists:reverse(Tail), Separator) - end; -strip(Username, prefix, true, Separator) -> - case string:tokens(Username, Separator) of - [Username] -> Username; - [_ | Tail] -> string:join(Tail, Separator) - end. - -route({RouteName, RouteRelay}) -> {RouteName, RouteRelay, []}; -route({_RouteName, _RouteRelay, _RoutOptions} = Route) -> Route. - -get_routes_info(HandlerOpts) -> - DefaultRoute = lists:keyfind(default_route, 1, HandlerOpts), - Routes = lists:keyfind(routes, 1, HandlerOpts), - Options = lists:keyfind(options, 1, HandlerOpts), - Retries = case Options of - false -> - ?DEFAULT_CLIENT_RETRIES; - {options, Opts} -> - proplists:get_value(retries, Opts, ?DEFAULT_CLIENT_RETRIES) - end, - {DefaultRoute, Routes, Retries}. - -put_default_route_to_pool(false, _) -> ok; -put_default_route_to_pool({default_route, {Host, Port, _Secret}}, Retries) -> - eradius_client_mngr:store_radius_server_from_pool(Host, Port, Retries); -put_default_route_to_pool({default_route, {Host, Port, _Secret}, _PoolName}, Retries) -> - eradius_client_mngr:store_radius_server_from_pool(Host, Port, Retries); -put_default_route_to_pool(_, _) -> ok. - -put_routes_to_pool(false, _Retries) -> ok; -put_routes_to_pool({routes, Routes}, Retries) -> - lists:foreach(fun (Route) -> - case Route of - {_RouteName, {Host, Port, _Secret}} -> - eradius_client_mngr:store_radius_server_from_pool(Host, Port, Retries); - {_RouteName, {Host, Port, _Secret}, _Pool} -> - eradius_client_mngr:store_radius_server_from_pool(Host, Port, Retries); - {Host, Port, _Secret, _Opts} -> - eradius_client_mngr:store_radius_server_from_pool(Host, Port, Retries); - _ -> ok - end - end, Routes). - -get_proxy_opt(_, [], Default) -> Default; -get_proxy_opt(OptName, [{OptName, AddrOrRoutes} | _], _) -> AddrOrRoutes; -get_proxy_opt(OptName, [{OptName, Addr, Opts} | _], _) -> {Addr, Opts}; -get_proxy_opt(OptName, [_ | Args], Default) -> get_proxy_opt(OptName, Args, Default). - -get_send_options({_Relay, RelayOpts}, Options) when is_list(RelayOpts) -> - Retries = proplists:get_value(retries, Options, ?DEFAULT_RETRIES), - Timeout = proplists:get_value(timeout, Options, ?DEFAULT_TIMEOUT), - RelayTimeout = proplists:get_value(timeout, RelayOpts, Timeout), - RelayRetries = proplists:get_value(retries, RelayOpts, Retries), - [{retries, RelayRetries}, {timeout, RelayTimeout}]; -get_send_options(_Route, Options) -> - Retries = proplists:get_value(retries, Options, ?DEFAULT_RETRIES), - Timeout = proplists:get_value(timeout, Options, ?DEFAULT_TIMEOUT), - [{retries, Retries}, {timeout, Timeout}]. - -get_failover_servers(RelayOpts) when is_list(RelayOpts) -> - Pools = application:get_env(eradius, servers_pool, []), - Pool = proplists:get_value(pool, RelayOpts, undefined), - proplists:get_value(Pool, Pools, []); -get_failover_servers(Pool) -> - Pools = application:get_env(eradius, servers_pool, []), - proplists:get_value(Pool, Pools, []). diff --git a/src/eradius_req.erl b/src/eradius_req.erl new file mode 100644 index 00000000..94cfb1ef --- /dev/null +++ b/src/eradius_req.erl @@ -0,0 +1,732 @@ +%% Copyright (c) 2024, Travelping GmbH <info@travelping.com> +%% +%% SPDX-License-Identifier: MIT + +-module(eradius_req). + +-export([is_valid/1, + req_id/1, + cmd/1, + authenticator/1, + request_authenticator/1, + msg_hmac/1, + eap_msg/1, + packet/1, + attrs/1, + attr/2]). +-export([new/1, new/2, + request/4, + response/3, + set_secret/2, + set_body/2, + set_attrs/2, + add_attr/3, + set_attr/3, + set_msg_hmac/2, + set_eap_msg/2, + set_metrics_callback/2]). +-export([record_metric/3, metrics_callback/3]). + +-ignore_xref([is_valid/1, + req_id/1, + cmd/1, + authenticator/1, + request_authenticator/1, + msg_hmac/1, + eap_msg/1, + packet/1, + attrs/1, + attr/2]). +-ignore_xref([new/1, new/2, + request/4, + response/3, + set_secret/2, + set_body/2, + set_attrs/2, + add_attr/3, + set_attr/3, + set_msg_hmac/2, + set_eap_msg/2, + set_metrics_callback/2]). + +-ifdef(TEST). +-export([encode_value/2, decode_value/2, scramble/3, ascend/3]). +-export([salt_encrypt/4, salt_decrypt/3, encode_attribute/3, decode_attribute/4]). +-ignore_xref([encode_value/2, decode_value/2, scramble/3, ascend/3]). +-ignore_xref([salt_encrypt/4, salt_decrypt/3, encode_attribute/3, decode_attribute/4]). +-endif. + +-include("eradius_lib.hrl"). +-include("eradius_dict.hrl"). + +-type command() :: 'request' | 'accept' | 'challenge' | 'reject' | 'accreq' | 'accresp' | + 'coareq' | 'coaack' | 'coanak' | 'discreq' | 'discack' | 'discnak'. +-type secret() :: binary(). +-type authenticator() :: <<_:128>>. +-type salt() :: binary(). +-type attribute_list() :: [{eradius_dict:attribute(), term()}]. +-export_type([command/0, secret/0, authenticator/0, salt/0, attribute_list/0]). + +-type metrics_event() :: 'request' | 'reply' | + 'retransmission' | 'discard' | + 'invalid_request'. +-type metrics_callback() :: + fun((Event :: metrics_event(), MetaData :: term(), Req :: req()) -> req()). +-export_type([metrics_event/0, metrics_callback/0]). + +-type req() :: + #{ + %% public fields + is_valid := undefined | boolean(), + cmd := command(), + secret => secret(), + req_id => byte(), + authenticator => authenticator(), + request_authenticator => authenticator(), + + %% public, server only + client => binary(), + client_addr => {IP :: inet:ip_address(), inet:port_number()}, + + server => eradius_server:server_name(), + server_addr => {IP :: inet:ip_address(), inet:port_number()}, + + %% private fields + arrival_time => integer(), + socket => gen_udp:socket(), + + head => binary(), + body := undefined | binary(), + attrs := undefined | attribute_list(), + eap_msg := undefined | binary(), + msg_hmac := undefined | boolean() | integer(), + + metrics_callback := undefined | metrics_callback(), + + _ => _ + }. + +-export_type([req/0]). + +%%%========================================================================= +%%% API +%%%========================================================================= + +%% @doc Return validation state of the request. +%% +%% - `true' for a requests if has been encoded to binary form, +%% +%% - `true' for a response if has been decoded from binary form +%% and the authenticator has been validate, +%% +%% - `true' for a response if has been decoded from binary form +%% and the authenticator failed to validate, +%% +%% - `undefined' otherwise +%% @end +-spec is_valid(req()) -> true | false | undefined. +is_valid(#{is_valid := IsValid}) -> IsValid. + +-spec req_id(req()) -> byte() | undefined. +req_id(#{req_id := ReqId}) -> ReqId; +req_id(_) -> undefined. + +-spec cmd(req()) -> command(). +cmd(#{cmd := Cmd}) -> Cmd. + +-spec authenticator(req()) -> authenticator() | undefined. +authenticator(#{authenticator := Authenticator}) -> Authenticator; +authenticator(_) -> undefined. + +-spec request_authenticator(req()) -> authenticator() | undefined. +request_authenticator(#{authenticator := Authenticator}) -> Authenticator; +request_authenticator(_) -> undefined. + +-spec msg_hmac(req()) -> boolean() | undefined. +msg_hmac(#{msg_hmac := MsgHMAC}) -> MsgHMAC; +msg_hmac(_) -> undefined. + +-spec eap_msg(req()) -> binary() | undefined. +eap_msg(#{eap_msg := EAPmsg}) -> EAPmsg; +eap_msg(_) -> undefined. + +%% @doc Convert a RADIUS request to the wire format. +-spec packet(req()) -> {binary(), req()} | no_return(). +packet(#{req_id := _, cmd := _, authenticator := _, body := Body, secret := _} = Req) + when is_binary(Body) -> + %% body must be fully prepared + encode_body(Req, Body); +packet(#{req_id := _, cmd := _, secret := _, attrs := Attrs, eap_msg := EAPmsg} = Req) + when is_list(Attrs) -> + Body0 = encode_attributes(Req, Attrs, <<>>), + Body1 = encode_eap_message(EAPmsg, Body0), + Body = encode_message_authenticator(Req, Body1), + encode_body(Req, Body); +packet(Req) -> + erlang:error(badarg, [Req]). + +-spec attrs(req()) -> {attribute_list(), req()} | no_return(). +attrs(#{attrs := Attrs, is_valid := IsValid} = Req) + when is_list(Attrs), IsValid =/= false -> + {Attrs, Req}; +attrs(#{body := Body, secret := _} = Req0) + when is_binary(Body) -> + try + #{attrs := Attrs} = Req = decode_body(Body, Req0), + {Attrs, Req} + catch + exit:_ -> + throw({bad_pdu, decoder_error}) + end; +attrs(Req) -> + erlang:error(badarg, [Req]). + +attr(Id, #{attrs := Attrs, is_valid := IsValid}) + when is_list(Attrs), IsValid =/= false -> + get_attr(Id, Attrs); +attr(_, _) -> + undefined. + +get_attr(_Id, []) -> + undefined; +get_attr(Id, [Head|Tail]) -> + case Head of + {#attribute{id = Id}, Value} -> Value; + {Id, Value} -> Value; + _ -> get_attr(Id, Tail) + end. + + +-spec new(command()) -> req(). +new(Command) -> + new(Command, undefined). + +-spec new(command(), 'undefined' | metrics_callback()) -> req(). +new(Command, MetricsCallback) + when MetricsCallback =:= undefined; is_function(MetricsCallback, 3) -> + #{is_valid => undefined, + cmd => Command, + + body => undefined, + attrs => [], + eap_msg => undefined, + msg_hmac => undefined, + + metrics_callback => MetricsCallback + }. + +-spec request(binary(), binary(), eradius_server:client(), 'undefined' | metrics_callback()) -> + req() | no_return(). +request(<<Cmd, ReqId, Len:16, Authenticator:16/bytes>> = Header, Body, + #{secret := Secret, client := ClientId}, MetricsCallback) -> + Command = decode_command(Cmd), + Req = new(Command, MetricsCallback), + mk_req(Command, ReqId, Len, Authenticator, Header, Body, + Req#{req_id => ReqId, request_authenticator => Authenticator, + client => ClientId, secret => Secret}). + +-spec response(binary(), binary(), req()) -> req() | no_return(); + (command(), undefined | attribute_list(), req()) -> req(). +response(<<Cmd, ReqId, Len:16, Authenticator:16/bytes>> = Header, Body, + #{req_id := ReqId, secret := _} = Req) -> + Command = decode_command(Cmd), + mk_req(Command, ReqId, Len, Authenticator, Header, Body, Req); + +response(Response, Attrs, Req) when is_atom(Response) -> + Req#{cmd := Response, body := undefined, attrs := Attrs, is_valid := undefined}. + +-spec set_secret(req(), secret()) -> req(). +set_secret(Req, Secret) -> + Req#{secret => Secret, is_valid := undefined}. + +-spec set_body(req(), binary()) -> req(). +set_body(Req, Body) when is_binary(Body) -> + Req#{body := Body, attrs := undefined, is_valid := undefined}. + +-spec set_attrs(attribute_list(), req()) -> req(). +set_attrs(Attrs, Req) when is_list(Attrs) -> + Req#{body := undefined, attrs := Attrs, is_valid := undefined}. + +add_attr(Id, Value, #{attrs := Attrs} = Req) + when is_list(Attrs) -> + Req#{attrs := [{Id, Value} | Attrs], is_valid := undefined}. + +set_attr(Id, Value, #{attrs := Attrs} = Req) + when is_list(Attrs) -> + Req#{attrs := lists:keystore(Id, 1, Attrs, {Id, Value}), is_valid := undefined}. + +-spec set_msg_hmac(boolean(), req()) -> req(). +set_msg_hmac(MsgHMAC, Req) + when is_boolean(MsgHMAC) -> + Req#{msg_hmac => MsgHMAC}. + +-spec set_eap_msg(binary(), req()) -> req(). +set_eap_msg(EAPmsg, Req) + when is_binary(EAPmsg) -> + Req#{body := undefined, eap_msg := EAPmsg}. + +-spec set_metrics_callback(undefined | metrics_callback(), req()) -> req(). +set_metrics_callback(MetricsCallback, Req) -> + Req#{metrics_callback => MetricsCallback}. + +-spec metrics_callback(Cb :: undefined | eradius_req:metrics_callback(), Event :: metrics_event(), MetaData :: term()) -> any(). +metrics_callback(Cb, Event, MetaData) + when is_function(Cb, 3) -> + Cb(Event, MetaData, undefined); +metrics_callback(_, _, _) -> + undefined. + +-spec record_metric(Event :: metrics_event(), MetaData :: term(), Req :: req()) -> req(). +record_metric(Event, MetaData, #{metrics_callback := Cb} = Req) + when is_function(Cb, 3) -> + Cb(Event, MetaData, Req); +record_metric(_, _, Req) -> + Req. + +%%%=================================================================== +%%% binary format handling +%%%=================================================================== + +%% ------------------------------------------------------------------------------------------ +%% -- Request Accessors +-spec random_authenticator() -> authenticator(). +random_authenticator() -> crypto:strong_rand_bytes(16). + +-spec zero_authenticator() -> authenticator(). +zero_authenticator() -> <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>. + +mk_req(Cmd, ReqId, Len, Authenticator, Header, Body0, #{req_id := ReqId} = Req) + when byte_size(Body0) >= (Len - 20) -> + <<Body:(Len - 20)/bytes, _/binary>> = Body0, + <<Head:4/bytes, _/binary>> = Header, + + validate_authenticator(Cmd, Head, Authenticator, Body, Req), + Req#{cmd := Cmd, + authenticator => Authenticator, + is_valid := true, + msg_hmac := undefined, + eap_msg := undefined, + head => Head, + body := Body, + attrs := undefined}; +mk_req(_, ResponseReqId, _, _, _, _, #{req_id := ReqId}) + when ResponseReqId =/= ReqId -> + throw({bad_pdu, invalid_req_id}); +mk_req(_, _, Len, _, _, Body, _) + when byte_size(Body) =< (Len - 20) -> + throw({bad_pdu, invalid_packet_size}). + +%% ------------------------------------------------------------------------------------------ +%% -- Wire Encoding + +encode_body(#{req_id := ReqId, cmd := Cmd} = Req, Body) + when Cmd =:= request -> + Authenticator = random_authenticator(), + Packet = <<(encode_command(Cmd)):8, ReqId:8, (byte_size(Body) + 20):16, + Authenticator:16/binary, Body/binary>>, + {Packet, Req#{is_valid := true, request_authenticator => Authenticator}}; + +encode_body(#{req_id := ReqId, cmd := Cmd, secret := Secret} = Req, Body) + when Cmd =:= accreq; Cmd =:= coareq; Cmd =:= discreq -> + Head = <<(encode_command(Cmd)):8, ReqId:8, (byte_size(Body) + 20):16>>, + Authenticator = crypto:hash(md5, [Head, zero_authenticator(), Body, Secret]), + Packet = <<Head/binary, Authenticator:16/binary, Body/binary>>, + {Packet, Req#{is_valid := true, request_authenticator => Authenticator}}; + +encode_body(#{req_id := ReqId, cmd := Cmd, + request_authenticator := Authenticator, + secret := Secret} = Req, Body) -> + Head = <<(encode_command(Cmd)):8, ReqId:8, (byte_size(Body) + 20):16>>, + + ReplyAuthenticator = crypto:hash(md5, [Head, <<Authenticator:16/binary>>, Body, Secret]), + Packet = <<Head/binary, ReplyAuthenticator:16/binary, Body/binary>>, + {Packet, Req#{is_valid := true}}. + +-spec encode_command(command()) -> byte(). +encode_command(request) -> ?RAccess_Request; +encode_command(accept) -> ?RAccess_Accept; +encode_command(challenge) -> ?RAccess_Challenge; +encode_command(reject) -> ?RAccess_Reject; +encode_command(accreq) -> ?RAccounting_Request; +encode_command(accresp) -> ?RAccounting_Response; +encode_command(coareq) -> ?RCoa_Request; +encode_command(coaack) -> ?RCoa_Ack; +encode_command(coanak) -> ?RCoa_Nak; +encode_command(discreq) -> ?RDisconnect_Request; +encode_command(discack) -> ?RDisconnect_Ack; +encode_command(discnak) -> ?RDisconnect_Nak. + +-spec encode_message_authenticator(req(), binary()) -> binary(). +encode_message_authenticator(#{reqid := ReqId, cmd := Cmd, + authenticator := Authenticator, + secret := Secret, + msg_hmac := true}, Body) -> + Head = <<(encode_command(Cmd)):8, ReqId:8, (byte_size(Body) + 20 + 2 + 16):16>>, + HMAC = message_authenticator( + Secret, [Head, Authenticator, Body, + <<?RMessage_Authenticator,18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>]), + <<Body/binary, ?RMessage_Authenticator, 18, HMAC/binary>>; +encode_message_authenticator(_Req, Body) -> + Body. + +chunk(Bin, Length) -> + case Bin of + <<First:Length/bytes, Rest/binary>> -> {First, Rest}; + _ -> {Bin, <<>>} + end. + +encode_eap_attribute({<<>>, _}, EncReq) -> + EncReq; +encode_eap_attribute({Value, Rest}, Body) -> + EncAttr = <<?REAP_Message, (byte_size(Value) + 2):8, Value/binary>>, + encode_eap_attribute(chunk(Rest, 253), <<Body/binary, EncAttr/binary>>). + +-spec encode_eap_message(binary(), binary()) -> binary(). +encode_eap_message(EAP, EncReq) + when EAP =:= <<>>; EAP =:= undefined -> + EncReq; +encode_eap_message(EAP, EncReq) when is_binary(EAP) -> + encode_eap_attribute(chunk(EAP, 253), EncReq). + +-spec encode_attributes(req(), attribute_list(), binary()) -> binary(). +encode_attributes(Req, Attributes, Init) -> + lists:foldl( + fun ({A = #attribute{}, Val}, Body) -> + EncAttr = encode_attribute(Req, A, Val), + <<Body/binary, EncAttr/binary>>; + ({Id, Val}, Body) -> + case eradius_dict:lookup(attribute, Id) of + AttrRec = #attribute{} -> + EncAttr = encode_attribute(Req, AttrRec, Val), + <<Body/binary, EncAttr/binary>>; + _ -> + Body + end + end, Init, Attributes). + +-spec encode_attribute(eradius_req:req(), #attribute{}, term()) -> binary(). +encode_attribute(_Req, _Attr = #attribute{id = ?RMessage_Authenticator}, _) -> + %% message authenticator is handled through the msg_hmac flag + <<>>; +encode_attribute(_Req, _Attr = #attribute{id = ?REAP_Message}, _) -> + %% EAP-Message attributes are handled through the eap_msg field + <<>>; +encode_attribute(Req, Attr = #attribute{id = {Vendor, Id}}, Value) -> + EncValue = encode_attribute(Req, Attr#attribute{id = Id}, Value), + if byte_size(EncValue) + 6 > 255 -> + error(badarg, [{Vendor, Id}, Value]); + true -> ok + end, + <<?RVendor_Specific:8, (byte_size(EncValue) + 6):8, Vendor:32, EncValue/binary>>; +encode_attribute(Req, #attribute{type = {tagged, Type}, id = Id, enc = Enc}, Value) -> + case Value of + {Tag, UntaggedValue} when Tag >= 1, Tag =< 16#1F -> ok; + UntaggedValue -> Tag = 0 + end, + EncValue = encrypt_value(Req, encode_value(Type, UntaggedValue), Enc), + if byte_size(EncValue) + 3 > 255 -> + error(badarg, [Id, Value]); + true -> ok + end, + <<Id, (byte_size(EncValue) + 3):8, Tag:8, EncValue/binary>>; +encode_attribute(Req, #attribute{type = Type, id = Id, enc = Enc}, Value)-> + EncValue = encrypt_value(Req, encode_value(Type, Value), Enc), + if byte_size(EncValue) + 2 > 255 -> + error(badarg, [Id, Value]); + true -> ok + end, + <<Id, (byte_size(EncValue) + 2):8, EncValue/binary>>. + +-spec encrypt_value(req(), binary(), eradius_dict:attribute_encryption()) -> binary(). +encrypt_value(#{authenticator := Authenticator, secret := Secret}, Val, scramble) -> + scramble(Secret, Authenticator, Val); +encrypt_value(#{authenticator := Authenticator, secret := Secret}, Val, salt_crypt) -> + salt_encrypt(generate_salt(), Secret, Authenticator, Val); +encrypt_value(#{authenticator := Authenticator, secret := Secret}, Val, ascend) -> + ascend(Secret, Authenticator, Val); +encrypt_value(_Req, Val, no) -> + Val. + +-spec encode_value(eradius_dict:attribute_prim_type(), term()) -> binary(). +encode_value(_, V) when is_binary(V) -> + V; +encode_value(binary, V) -> + V; +encode_value(integer, V) -> + <<V:32>>; +encode_value(integer24, V) -> + <<V:24>>; +encode_value(integer64, V) -> + <<V:64>>; +encode_value(ipaddr, {A,B,C,D}) -> + <<A:8, B:8, C:8, D:8>>; +encode_value(ipv6addr, {A,B,C,D,E,F,G,H}) -> + <<A:16, B:16, C:16, D:16, E:16, F:16, G:16, H:16>>; +encode_value(ipv6prefix, {{A,B,C,D,E,F,G,H}, PLen}) -> + L = (PLen + 7) div 8, + <<IP:L/bytes, _R/binary>> = <<A:16, B:16, C:16, D:16, E:16, F:16, G:16, H:16>>, + <<0, PLen, IP/binary>>; +encode_value(string, V) when is_list(V) -> + unicode:characters_to_binary(V); +encode_value(octets, V) when is_list(V) -> + iolist_to_binary(V); +encode_value(octets, V) when is_integer(V) -> + <<V:32>>; +encode_value(date, V) when is_list(V) -> + unicode:characters_to_binary(V); +encode_value(date, Date = {{_,_,_},{_,_,_}}) -> + EpochSecs = calendar:datetime_to_gregorian_seconds(Date) - + calendar:datetime_to_gregorian_seconds({{1970,1,1},{0,0,0}}), + <<EpochSecs:32>>. + +%% ------------------------------------------------------------------------------------------ +%% -- Wire Decoding + +-spec decode_body(binary(), req()) -> req(). +decode_body(Body, #{is_valid := true, head := Head} = Req0) -> + Req1 = Req0#{msg_hmac => 0, attrs => [], eap_msg => <<>>}, + Req2 = decode_attributes(Body, 0, Req1), + + case Req2 of + #{msg_hmac := Pos} when Pos > 0 -> + validate_packet_authenticator(Head, Body, Pos, Req2), + Req2#{msg_hmac := true}; + _ -> + Req2#{msg_hmac := false} + end. + +validate_packet_authenticator(Head, Body, Pos, + #{request_authenticator := Authenticator, secret := Secret}) -> + validate_packet_authenticator(Head, Body, Pos, Authenticator, Secret); +validate_packet_authenticator(Head, Body, Pos, + #{authenticator := Authenticator, secret := Secret}) -> + validate_packet_authenticator(Head, Body, Pos, Authenticator, Secret). + +validate_packet_authenticator(Head, Body, Pos, Auth, Secret) -> + case Body of + <<Before:Pos/bytes, Value:16/bytes, After/binary>> -> + case message_authenticator(Secret, [Head, Auth, Before, zero_authenticator(), After]) of + Value -> + ok; + _ -> + throw({bad_pdu, "Message-Authenticator Attribute is invalid"}) + end; + _ -> + throw({bad_pdu, "Message-Authenticator Attribute is malformed"}) + end. + +validate_authenticator(Cmd, Head, PacketAuthenticator, Body, + #{authenticator := RequestAuthenticator, secret := Secret}) + when Cmd =:= accept; Cmd =:= reject; Cmd =:= accresp; Cmd =:= coaack; + Cmd =:= coanak; Cmd =:= discack; Cmd =:= discnak; Cmd =:= challenge -> + compare_authenticator(crypto:hash(md5, [Head, RequestAuthenticator, Body, Secret]), PacketAuthenticator); +validate_authenticator(accreq, Head, PacketAuthenticator, Body, + #{secret := Secret}) -> + compare_authenticator(crypto:hash(md5, [Head, zero_authenticator(), Body, Secret]), PacketAuthenticator); +validate_authenticator(_Cmd, _Head, _RequestAuthenticator, _Body, _Req) -> + true. + +compare_authenticator(Authenticator, Authenticator) -> + true; +compare_authenticator(_RequestAuthenticator, _PacketAuthenticator) -> + throw({bad_pdu, "Authenticator Attribute is invalid"}). + +-spec decode_command(byte()) -> command(). +decode_command(?RAccess_Request) -> request; +decode_command(?RAccess_Accept) -> accept; +decode_command(?RAccess_Reject) -> reject; +decode_command(?RAccess_Challenge) -> challenge; +decode_command(?RAccounting_Request) -> accreq; +decode_command(?RAccounting_Response) -> accresp; +decode_command(?RCoa_Request) -> coareq; +decode_command(?RCoa_Ack) -> coaack; +decode_command(?RCoa_Nak) -> coanak; +decode_command(?RDisconnect_Request) -> discreq; +decode_command(?RDisconnect_Ack) -> discack; +decode_command(?RDisconnect_Nak) -> discnak; +decode_command(_) -> error({bad_pdu, "unknown request type"}). + +append_attr(Attr, #{attrs := Attrs} = Req) -> + Req#{attrs := [Attr | Attrs]}. + +decode_attributes(<<>>, _Pos, #{attrs := Attrs} = Req) -> + Req#{attrs := lists:reverse(Attrs)}; +decode_attributes(<<Type:8, ChunkLength:8, ChunkRest/binary>>, Pos, Req0) -> + ValueLength = ChunkLength - 2, + <<Value:ValueLength/binary, PacketRest/binary>> = ChunkRest, + Req = case eradius_dict:lookup(attribute, Type) of + AttrRec = #attribute{} -> + decode_attribute(Value, AttrRec, Pos + 2, Req0); + _ -> + append_attr({Type, Value}, Req0) + end, + decode_attributes(PacketRest, Pos + ChunkLength, Req). + +%% gotcha: the function returns a LIST of attribute-value pairs because +%% a vendor-specific attribute blob might contain more than one attribute. +-spec decode_attribute(binary(), #attribute{}, non_neg_integer(), req()) -> req(). +decode_attribute(<<VendorId:32/integer, ValueBin/binary>>, + #attribute{id = ?RVendor_Specific}, Pos, Req) -> + decode_vendor_specific_attribute(ValueBin, VendorId, Pos + 4, Req); +decode_attribute(<<Value/binary>>, + #attribute{id = ?REAP_Message}, _Pos, #{eap_msg := EAP} = Req) -> + Req#{eap_msg := <<EAP/binary, Value/binary>>}; +decode_attribute(<<EncValue/binary>>, + Attr = #attribute{ + id = ?RMessage_Authenticator, type = Type, enc = Encryption}, + Pos, Req) -> + AVP = {Attr, decode_value(decrypt_value(Req, EncValue, Encryption), Type)}, + append_attr(AVP, Req#{msg_hmac := Pos}); + +decode_attribute(<<EncValue/binary>>, + Attr = #attribute{type = Type, enc = Encryption}, _Pos, Req) + when is_atom(Type) -> + append_attr({Attr, decode_value(decrypt_value(Req, EncValue, Encryption), Type)}, Req); +decode_attribute(WholeBin = <<Tag:8, Bin/binary>>, + Attr = #attribute{type = {tagged, Type}}, _Pos, Req) -> + case {decode_tag_value(Tag), Attr#attribute.enc} of + {0, no} -> + %% decode including tag byte if tag is out of range + append_attr({Attr, {0, decode_value(WholeBin, Type)}}, Req); + {TagV, no} -> + append_attr({Attr, {TagV, decode_value(Bin, Type)}}, Req); + {TagV, Encryption} -> + %% for encrypted attributes, tag byte is never part of the value + AVP = {Attr, {TagV, decode_value(decrypt_value(Req, Bin, Encryption), Type)}}, + append_attr(AVP, Req) + end. + +-compile({inline, decode_tag_value/1}). +decode_tag_value(Tag) when (Tag >= 1) and (Tag =< 16#1F) -> Tag; +decode_tag_value(_OtherTag) -> 0. + +-spec decode_value(binary(), eradius_dict:attribute_prim_type()) -> term(). +decode_value(Bin, octets) -> + Bin; +decode_value(Bin, binary) -> + Bin; +decode_value(Bin, abinary) -> + Bin; +decode_value(Bin, string) -> + Bin; +decode_value(Bin, integer) -> + binary:decode_unsigned(Bin); +decode_value(Bin, integer24) -> + binary:decode_unsigned(Bin); +decode_value(Bin, integer64) -> + binary:decode_unsigned(Bin); +decode_value(Bin, date) -> + Int = binary:decode_unsigned(Bin), + calendar:now_to_universal_time({Int div 1000000, Int rem 1000000, 0}); +decode_value(<<B,C,D,E>>, ipaddr) -> + {B,C,D,E}; +decode_value(<<B:16,C:16,D:16,E:16,F:16,G:16,H:16,I:16>>, ipv6addr) -> + {B,C,D,E,F,G,H,I}; +decode_value(<<_0, PLen, P/binary>>, ipv6prefix) -> + <<B:16,C:16,D:16,E:16,F:16,G:16,H:16,I:16>> = pad_to(16, P), + {{B,C,D,E,F,G,H,I}, PLen}. + +-spec decrypt_value(req(), binary(), eradius_dict:attribute_encryption()) -> + eradius_dict:attr_value(). +decrypt_value(#{secret := Secret, authenticator := Authenticator}, Val, scramble) -> + scramble(Secret, Authenticator, Val); +decrypt_value(#{secret := Secret, authenticator := Authenticator}, Val, salt_crypt) -> + salt_decrypt(Secret, Authenticator, Val); +decrypt_value(#{secret := Secret, authenticator := Authenticator}, Val, ascend) -> + ascend(Secret, Authenticator, Val); +decrypt_value(_Req, Val, _Type) -> + Val. + +-spec decode_vendor_specific_attribute(binary(), non_neg_integer(), pos_integer(), req()) -> + req(). +decode_vendor_specific_attribute(<<>>, _VendorId, _Pos, Req) -> + Req; +decode_vendor_specific_attribute(<<Type:8, ChunkLength:8, ChunkRest/binary>>, + VendorId, Pos, Req0) -> + ValueLength = ChunkLength - 2, + <<Value:ValueLength/binary, PacketRest/binary>> = ChunkRest, + VendorAttrKey = {VendorId, Type}, + Req = case eradius_dict:lookup(attribute, VendorAttrKey) of + Attr = #attribute{} -> + decode_attribute(Value, Attr, Pos + 2, Req0); + _ -> + append_attr({VendorAttrKey, Value}, Req0) + end, + decode_vendor_specific_attribute(PacketRest, VendorId, Pos + ChunkLength, Req). + +%% ------------------------------------------------------------------------------------------ +%% -- Attribute Encryption +-spec scramble(secret(), authenticator(), binary()) -> binary(). +scramble(SharedSecret, RequestAuthenticator, <<PlainText/binary>>) -> + B = crypto:hash(md5, [SharedSecret, RequestAuthenticator]), + do_scramble(SharedSecret, B, pad_to(16, PlainText), << >>). + +do_scramble(SharedSecret, B, <<PlainText:16/binary, Remaining/binary>>, CipherText) -> + NewCipherText = crypto:exor(PlainText, B), + Bnext = crypto:hash(md5, [SharedSecret, NewCipherText]), + do_scramble(SharedSecret, Bnext, Remaining, <<CipherText/binary, NewCipherText/binary>>); + +do_scramble(_SharedSecret, _B, << >>, CipherText) -> + CipherText. + +-spec generate_salt() -> salt(). +generate_salt() -> + <<Salt1, Salt2>> = crypto:strong_rand_bytes(2), + <<(Salt1 bor 16#80), Salt2>>. + +-spec salt_encrypt(salt(), secret(), authenticator(), binary()) -> binary(). +salt_encrypt(Salt, SharedSecret, RequestAuthenticator, PlainText) -> + CipherText = do_salt_crypt(encrypt, Salt, SharedSecret, RequestAuthenticator, (pad_to(16, << (byte_size(PlainText)):8, PlainText/binary >>))), + <<Salt/binary, CipherText/binary>>. + +-spec salt_decrypt(secret(), authenticator(), binary()) -> binary(). +salt_decrypt(SharedSecret, RequestAuthenticator, <<Salt:2/binary, CipherText/binary>>) -> + << Length:8/integer, PlainText/binary >> = do_salt_crypt(decrypt, Salt, SharedSecret, RequestAuthenticator, CipherText), + if + Length < byte_size(PlainText) -> + binary:part(PlainText, 0, Length); + true -> + PlainText + end. + +do_salt_crypt(Op, Salt, SharedSecret, RequestAuthenticator, <<CipherText/binary>>) -> + B = crypto:hash(md5, [SharedSecret, RequestAuthenticator, Salt]), + salt_crypt(Op, SharedSecret, B, CipherText, << >>). + +salt_crypt(Op, SharedSecret, B, <<PlainText:16/binary, Remaining/binary>>, CipherText) -> + NewCipherText = crypto:exor(PlainText, B), + Bnext = case Op of + decrypt -> crypto:hash(md5, [SharedSecret, PlainText]); + encrypt -> crypto:hash(md5, [SharedSecret, NewCipherText]) + end, + salt_crypt(Op, SharedSecret, Bnext, Remaining, <<CipherText/binary, NewCipherText/binary>>); + +salt_crypt(_Op, _SharedSecret, _B, << >>, CipherText) -> + CipherText. + +-spec ascend(secret(), authenticator(), binary()) -> binary(). +ascend(SharedSecret, RequestAuthenticator, <<PlainText/binary>>) -> + Digest = crypto:hash(md5, [RequestAuthenticator, SharedSecret]), + crypto:exor(Digest, pad_to(16, PlainText)). + +%% @doc pad binary to specific length +%% See <a href="http://www.erlang.org/pipermail/erlang-questions/2008-December/040709.html"> +%% http://www.erlang.org/pipermail/erlang-questions/2008-December/040709.html +%% </a> +-compile({inline, pad_to/2}). +pad_to(Width, Binary) -> + case (Width - byte_size(Binary) rem Width) rem Width of + 0 -> Binary; + N -> <<Binary/binary, 0:(N*8)>> + end. + +%% @doc calculate the MD5 message authenticator +-if(?OTP_RELEASE >= 23). +%% crypto API changes in OTP >= 23 +message_authenticator(Secret, Msg) -> + crypto:mac(hmac, md5, Secret, Msg). +-else. +message_authenticator(Secret, Msg) -> + crypto:hmac(md5, Secret, Msg). + +-endif. diff --git a/src/eradius_server.erl b/src/eradius_server.erl index 87e358e7..6adfc3ac 100644 --- a/src/eradius_server.erl +++ b/src/eradius_server.erl @@ -1,491 +1,427 @@ -%% @doc -%% This module implements a generic RADIUS server. A handler callback module -%% is used to process requests. The handler module is selected based on the NAS that -%% sent the request. Requests from unknown NASs are discarded. +%% Copyright (c) 2002-2007, Martin Björklund and Torbjörn Törnkvist +%% Copyright (c) 2011, Travelping GmbH <info@travelping.com> %% -%% It is also possible to run request handlers on remote nodes. If configured, -%% the server process will balance load among connected nodes. -%% Please see the Overview page for a detailed description of the server configuration. +%% SPDX-License-Identifier: MIT %% -%% == Callback Description == -%% -%% There are two callbacks at the moment. -%% -%% === validate_arguments(Args :: list()) -> boolean() | {true, NewArgs :: list()} | Error :: term(). === -%% -%% This is optional callback and can be absent. During application configuration processing `eradius_config` -%% calls this for the handler to validate and transform handler arguments. -%% -%% === radius_request(#radius_request{}, #nas_prop{}, HandlerData :: term()) -> {reply, #radius_request{}} | noreply === -%% -%% This function is called for every RADIUS request that is received by the server. -%% Its first argument is a request record which contains the request type and AVPs. -%% The second argument is a NAS descriptor. The third argument is an opaque term from the -%% server configuration. -%% -%% Both records are defined in 'eradius_lib.hrl', but their definition is reproduced here for easy reference. -%% -%% ``` -%% -record(radius_request, { -%% reqid :: byte(), -%% cmd :: 'request' | 'accept' | 'challenge' | 'reject' | -%% 'accreq' | 'accresp' | 'coareq' | 'coaack' | 'coanak' | -%% 'discreq' | 'discack' | 'discnak' -%% attrs :: eradius_lib:attribute_list(), -%% secret :: eradius_lib:secret(), -%% authenticator :: eradius_lib:authenticator(), -%% msg_hmac :: boolean(), -%% eap_msg :: binary() -%% }). -%% -%% -record(nas_prop, { -%% server_ip :: inet:ip_address(), -%% server_port :: eradius_server:port_number(), -%% nas_ip :: inet:ip_address(), -%% nas_port :: eradius_server:port_number(), -%% nas_id :: term(), -%% metrics_info :: {atom_address(), atom_address()}, -%% secret :: eradius_lib:secret(), -%% trace :: boolean(), -%% handler_nodes :: 'local' | list(atom()) -%% }). -%% ''' -module(eradius_server). --export([start_link/3, start_link/4]). --export_type([port_number/0, req_id/0]). +-feature(maybe_expr, enable). -%% internal --export([do_radius/7, handle_request/3, handle_remote_request/5, stats/2]). +-behaviour(gen_server). --import(eradius_lib, [printable_peer/2]). +%% API +-export([start_instance/3, start_instance/4, stop_instance/1]). +-export([start_link/3, start_link/4]). +-export_type([req_id/0]). --behaviour(gen_server). +%% internal API +-export([do_radius/4]). +-ignore_xref([do_radius/4]). + +%% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). +-ignore_xref([start_link/3, start_link/4]). +-ignore_xref([start_instance/3, start_instance/4, stop_instance/1]). + -include_lib("stdlib/include/ms_transform.hrl"). -include_lib("kernel/include/logger.hrl"). -include("eradius_lib.hrl"). -include("dictionary.hrl"). -include("eradius_dict.hrl"). +-import(eradius_lib, [printable_peer/1, printable_peer/2]). + -define(RESEND_TIMEOUT, 5000). % how long the binary response is kept after sending it on the socket -define(RESEND_RETRIES, 3). % how often a reply may be resent --define(HANDLER_REPLY_TIMEOUT, 15000). % how long to wait before a remote handler is considered dead --define(DEFAULT_RADIUS_SERVER_OPTS(IP), [{active, once}, {ip, IP}, binary]). --type port_number() :: 1..65535. +-export_type([handler/0]). + -type req_id() :: byte(). --type udp_socket() :: port(). --type udp_packet() :: {udp, udp_socket(), inet:ip_address(), port_number(), binary()}. +%% RADIUS request id + +-type socket_opts() :: #{family => inet | inet6, + ifaddr => inet:ip_address() | any, + port => inet:port_number(), + active_n => 'once' | non_neg_integer(), + ipv6_v6only => boolean, + inet_backend => inet | socket, + recbuf => non_neg_integer(), + sndbuf => non_neg_integer() + }. +%% Options to configure the RADIUS server UDP socket. + +-type socket_config() :: #{family := inet | inet6, + ifaddr := inet:ip_address() | any, + port := inet:port_number(), + active_n := 'once' | non_neg_integer(), + ipv6_v6only => boolean, + inet_backend => inet | socket, + recbuf => non_neg_integer(), + sndbuf => non_neg_integer() + }. +%% Options to configure the RADIUS server UDP socket. +%% Conceptually the same as `t:socket_opts/0', except that may fields are mandatory. + +-type server_opts() :: #{server_name => term(), + socket_opts => socket_opts(), + handler := {module(), term()}, + metrics_callback => eradius_req:metrics_callback(), + clients := map()}. +%% Options to configure the RADIUS server. + +-type server_config() :: #{server_name := term(), + socket_opts := socket_config(), + handler := {module(), term()}, + metrics_callback := undefined | eradius_req:metrics_callback(), + clients := map()}. +%% Options to configure the RADIUS server. +%% Conceptually the same as `t:server_opts/0', except that may fields are mandatory. + +-type client() :: #{client := binary(), + secret := eradius_req:secret()}. +%% RADIUS client settings + +-export_type([server_name/0, client/0]). -record(state, { - socket :: udp_socket(), % Socket Reference of opened UDP port - ip = {0,0,0,0} :: inet:ip_address(), % IP to which this socket is bound - port = 0 :: port_number(), % Port number we are listening on + name :: atom(), % server name + family :: inet:address_family(), + socket :: gen_udp:socket(), % Socket Reference of opened UDP port + server :: {inet:ip_address() | any, inet:port_number()}, % IP and port to which this socket is bound + active_n :: 'once' | non_neg_integer(), transacts :: ets:tid(), % ETS table containing current transactions - counter :: #server_counter{}, % statistics counter, - name :: atom() % server name + handler :: handler(), + metrics_callback :: eradius_req:metrics_callback(), + clients :: #{inet:ip_address() => client()} }). --optional_callbacks([validate_arguments/1]). - --callback validate_arguments(Args :: list()) -> - boolean() | {true, NewArgs :: list()}. - --callback radius_request(#radius_request{}, #nas_prop{}, HandlerData :: term()) -> - {reply, #radius_request{}} | noreply | {error, timeout}. - --spec start_link(atom(), inet:ip4_address(), port_number()) -> {ok, pid()} | {error, term()}. -start_link(ServerName, IP, Port) -> - start_link(ServerName, IP, Port, []). +-callback radius_request(eradius_req:req(), HandlerData :: term()) -> + {reply, eradius_req:req()} | noreply | {error, timeout}. + +%%%========================================================================= +%%% API +%%%========================================================================= + +-spec start_instance(IP :: 'any' | inet:ip_address(), Port :: inet:port_number(), + Opts :: server_opts()) -> gen_server:start_ret(). +start_instance(IP, Port, Opts) + when (IP =:= any orelse is_tuple(IP)) andalso + is_integer(Port) andalso Port >= 0 andalso Port < 65536 -> + eradius_server_sup:start_instance([IP, Port, Opts]). + +-spec start_instance(ServerName :: gen_server:server_name(), + IP :: 'any' | inet:ip_address(), Port :: inet:port_number(), + Opts :: server_opts()) -> gen_server:start_ret(). +start_instance(ServerName, IP, Port, Opts) + when (IP =:= any orelse is_tuple(IP)) andalso + is_integer(Port) andalso Port >= 0 andalso Port < 65536 -> + eradius_server_sup:start_instance([ServerName, IP, Port, Opts]). + +-spec stop_instance(Pid :: pid()) -> ok. +stop_instance(Pid) -> + try gen_server:call(Pid, stop) + catch exit:_ -> ok end. + +-spec start_link(IP :: 'any' | inet:ip_address(), Port :: inet:port_number(), + Opts :: server_opts()) -> gen_server:start_ret(). +start_link(IP, Port, #{handler := {_, _}, clients := #{}} = Opts) + when (IP =:= any orelse is_tuple(IP)) andalso + is_integer(Port) andalso Port >= 0 andalso Port < 65536 -> + maybe + {ok, Config} ?= config(IP, Port, Opts), + gen_server:start_link(?MODULE, [Config], []) + end. --spec start_link(atom(), inet:ip4_address(), port_number(), [inet:socket_setopt()]) -> {ok, pid()} | {error, term()}. -start_link(ServerName, IP = {A,B,C,D}, Port, Opts) -> - Name = list_to_atom(lists:flatten(io_lib:format("eradius_server_~b.~b.~b.~b:~b", [A,B,C,D,Port]))), - gen_server:start_link({local, Name}, ?MODULE, {ServerName, IP, Port, Opts}, []). +-spec start_link(ServerName :: gen_server:server_name(), + IP :: 'any' | inet:ip_address(), Port :: inet:port_number(), + Opts :: server_opts()) -> gen_server:start_ret(). +start_link(ServerName, IP, Port, #{handler := {_, _}, clients := #{}} = Opts) + when (IP =:= any orelse is_tuple(IP)) andalso + is_integer(Port) andalso Port >= 0 andalso Port < 65536 -> + maybe + {ok, Config} ?= config(IP, Port, Opts), + gen_server:start_link(ServerName, ?MODULE, [Config], []) + end. -stats(Server, Function) -> - gen_server:call(Server, {stats, Function}). +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== -%% ------------------------------------------------------------------------------------------ -%% -- gen_server Callbacks %% @private -init({ServerName, IP, Port, Opts}) -> +init([#{server_name := ServerName, + socket_opts := #{family := Family, active_n := ActiveN, + ifaddr := IP, port := Port} = SocketOpts, + handler := Handler, metrics_callback := MetricsCallback, + clients := Clients} = _Config]) -> process_flag(trap_exit, true), - SockOpts0 = proplists:get_value(socket_opts, Opts, []), - SockOpts1 = add_sock_opt(recbuf, 8192, SockOpts0), - SockOpts = add_sock_opt(sndbuf, 131072, SockOpts1), - SockOptsDef = ?DEFAULT_RADIUS_SERVER_OPTS(IP) ++ SockOpts, - case gen_udp:open(Port, SockOptsDef) of + + InetOpts = inet_opts(SocketOpts, [{active, ActiveN}, binary, Family]), + Server = {IP, Port}, + ?LOG(debug, "Starting RADIUS server on ~s with socket options ~0p", + [printable_peer(Server), InetOpts]), + + case gen_udp:open(Port, InetOpts) of {ok, Socket} -> - {ok, #state{socket = Socket, - ip = IP, port = Port, name = ServerName, - transacts = ets:new(transacts, []), - counter = eradius_counter:init_counter({IP, Port, ServerName})}}; + State = + #state{ + name = ServerName, + family = Family, + socket = Socket, + server = {IP, Port}, + active_n = ActiveN, + handler = Handler, + clients = Clients, + transacts = ets:new(transacts, []), + metrics_callback = MetricsCallback + }, + {ok, State}; {error, Reason} -> + ?LOG(debug, "Starting RADIUS server on ~s failed with ~0p", + [printable_peer(Server), Reason]), {stop, Reason} end. %% @private -handle_info(ReqUDP = {udp, Socket, FromIP, FromPortNo, Packet}, - State = #state{name = ServerName, transacts = Transacts, ip = _IP, port = _Port}) -> - TS1 = erlang:monotonic_time(), - case lookup_nas(State, FromIP, Packet) of - {ok, ReqID, Handler, NasProp} -> - #nas_prop{server_ip = ServerIP, server_port = Port} = NasProp, - ReqKey = {FromIP, FromPortNo, ReqID}, - NNasProp = NasProp#nas_prop{nas_port = FromPortNo}, - eradius_counter:inc_counter(requests, NasProp), - case ets:lookup(Transacts, ReqKey) of - [] -> - HandlerPid = proc_lib:spawn_link(?MODULE, do_radius, [self(), ServerName, ReqKey, Handler, NNasProp, ReqUDP, TS1]), - ets:insert(Transacts, {ReqKey, {handling, HandlerPid}}), - ets:insert(Transacts, {HandlerPid, ReqKey}), - eradius_counter:inc_counter(pending, NasProp); - [{_ReqKey, {handling, HandlerPid}}] -> - %% handler process is still working on the request - ?LOG(debug, "~s From: ~s INF: Handler process ~p is still working on the request. duplicate request (being handled) ~p", - [printable_peer(ServerIP, Port), printable_peer(FromIP, FromPortNo), HandlerPid, ReqKey]), - eradius_counter:inc_counter(dupRequests, NasProp); - [{_ReqKey, {replied, HandlerPid}}] -> - %% handler process waiting for resend message - HandlerPid ! {self(), resend, Socket}, - ?LOG(debug, "~s From: ~s INF: Handler ~p waiting for resent message. duplicate request (resent) ~p", - [printable_peer(ServerIP, Port), printable_peer(FromIP, FromPortNo), HandlerPid, ReqKey]), - eradius_counter:inc_counter(dupRequests, NasProp), - eradius_counter:inc_counter(retransmissions, NasProp) - end, - NewState = State; - {discard, Reason} when Reason == no_nodes_local, Reason == no_nodes -> - NewState = State#state{counter = eradius_counter:inc_counter(discardNoHandler, State#state.counter)}; - {discard, _Reason} -> - NewState = State#state{counter = eradius_counter:inc_counter(invalidRequests, State#state.counter)} +handle_call(stop, _From, State) -> + {stop, normal, ok, State}; +handle_call(_Call, _From, State) -> + {reply, ok, State}. + +%% @private +handle_cast(_Msg, State) -> + {noreply, State}. + +%% @private +handle_info({udp_passive, _Socket}, #state{socket = Socket, active_n = ActiveN} = State) -> + inet:setopts(Socket, [{active, ActiveN}]), + {noreply, State}; + +handle_info({udp, Socket, FromIP, FromPortNo, <<Header:20/bytes, Body/binary>>}, + #state{name = ServerName, server = Server, transacts = Transacts, + handler = Handler, clients = Clients, + metrics_callback = MetricsCallback} = State) + when is_map_key(FromIP, Clients) -> + NAS = maps:get(FromIP, Clients), + <<_, ReqId:8, _/binary>> = Header, + Req0 = eradius_req:request(Header, Body, NAS, MetricsCallback), + Req1 = Req0#{socket => Socket, + server => ServerName, + server_addr => Server, + client_addr => {FromIP, FromPortNo}}, + ReqKey = {FromIP, FromPortNo, ReqId}, + + case ets:lookup(Transacts, ReqKey) of + [] -> + Req = eradius_req:record_metric(request, #{}, Req1), + HandlerPid = + proc_lib:spawn_link(?MODULE, do_radius, [self(), Handler, ReqKey, Req]), + ets:insert(Transacts, {ReqKey, {handling, HandlerPid}}), + ets:insert(Transacts, {HandlerPid, ReqKey}); + + [{_ReqKey, {handling, HandlerPid}}] -> + %% handler process is still working on the request + ?LOG(debug, "~s From: ~s INF: Handler process ~p is still working on the request." + " duplicate request (being handled) ~p", + [printable_peer(Server), + printable_peer(FromIP, FromPortNo), HandlerPid, ReqKey]), + eradius_req:record_metric(discard, #{reason => duplicate}, Req1); + [{_ReqKey, {replied, HandlerPid}}] -> + %% handler process waiting for resend message + HandlerPid ! {self(), resend}, + ?LOG(debug, "~s From: ~s INF: Handler ~p waiting for resent message. " + "duplicate request (resent) ~p", + [printable_peer(Server), + printable_peer(FromIP, FromPortNo), HandlerPid, ReqKey]), + eradius_req:record_metric(retransmission, #{reason => duplicate}, Req1) end, - inet:setopts(Socket, [{active, once}]), - {noreply, NewState}; + flow_control(State), + {noreply, State}; + +handle_info({udp, _Socket, _FromIP, _FromPortNo, _Packet}, + #state{name = ServerName, metrics_callback = MetricsCallback} = State) -> + %% TBD: this should go into a malformed counter + eradius_req:metrics_callback(MetricsCallback, invalid_request, #{server => ServerName}), + flow_control(State), + {noreply, State}; + handle_info({replied, ReqKey, HandlerPid}, State = #state{transacts = Transacts}) -> ets:insert(Transacts, {ReqKey, {replied, HandlerPid}}), {noreply, State}; + handle_info({'EXIT', HandlerPid, _Reason}, State = #state{transacts = Transacts}) -> [ets:delete(Transacts, ReqKey) || {_, ReqKey} <- ets:take(Transacts, HandlerPid)], {noreply, State}; + handle_info(_Info, State) -> {noreply, State}. -%% @private --spec add_sock_opt(recbuf | sndbuf, pos_integer(), proplists:proplist()) -> proplists:proplist(). -add_sock_opt(OptName, Default, Opts) -> - case proplists:get_value(OptName, Opts) of - undefined -> - Buf = application:get_env(eradius, OptName, Default), - [{OptName, Buf} | Opts]; - _Val -> - Opts - end. - %% @private terminate(_Reason, State) -> gen_udp:close(State#state.socket), ok. -%% @private -handle_call({stats, pull}, _From, State = #state{counter = Counter}) -> - {reply, Counter, State#state{counter = eradius_counter:reset_counter(Counter)}}; -handle_call({stats, read}, _From, State = #state{counter = Counter}) -> - {reply, Counter, State}; -handle_call({stats, reset}, _From, State = #state{counter = Counter}) -> - {reply, ok, State#state{counter = eradius_counter:reset_counter(Counter)}}. - -%% -- unused callbacks -%% @private -handle_cast(_Msg, State) -> {noreply, State}. %% @private code_change(_OldVsn, State, _Extra) -> {ok, State}. --spec lookup_nas(#state{}, inet:ip_address(), binary()) -> {ok, req_id(), eradius_server_mon:handler(), #nas_prop{}} | {discard, invalid | malformed}. -lookup_nas(#state{ip = IP, port = Port}, NasIP, <<_Code, ReqID, _/binary>>) -> - case eradius_server_mon:lookup_handler(IP, Port, NasIP) of - {ok, Handler, NasProp} -> - {ok, ReqID, Handler, NasProp}; - {error, not_found} -> - {discard, invalid} - end; -lookup_nas(_State, _NasIP, _Packet) -> - {discard, malformed}. - -%% ------------------------------------------------------------------------------------------ -%% -- Request Handler +%%%========================================================================= +%%% handler functions +%%%========================================================================= + %% @private --spec do_radius(pid(), string(), term(), eradius_server_mon:handler(), #nas_prop{}, udp_packet(), integer()) -> any(). -do_radius(ServerPid, ServerName, ReqKey, Handler = {HandlerMod, _}, NasProp, {udp, Socket, FromIP, FromPort, EncRequest}, TS1) -> - #nas_prop{server_ip = ServerIP, server_port = Port} = NasProp, - Nodes = eradius_node_mon:get_module_nodes(HandlerMod), - case run_handler(Nodes, NasProp, Handler, EncRequest) of - {reply, EncReply, {ReqCmd, RespCmd}, Request} -> - ?LOG(debug, "~s From: ~s INF: Sending response for request ~p", - [printable_peer(ServerIP, Port), printable_peer(FromIP, FromPort), ReqKey]), - TS2 = erlang:monotonic_time(), - inc_counter({ReqCmd, RespCmd}, ServerName, NasProp, TS2 - TS1, Request), - gen_udp:send(Socket, FromIP, FromPort, EncReply), +-spec do_radius(pid(), handler(), term(), eradius_req:req()) -> any(). +do_radius(ServerPid, {HandlerMod, HandlerArg}, ReqKey, + #{server := Server, client_addr := Client} = Req0) -> + case apply_handler_mod(HandlerMod, HandlerArg, Req0) of + {reply, Packet, Resp0, Req} -> + ?LOG(debug, "~s From: ~s INF: Sending response for request ~0p", + [printable_peer(Server), printable_peer(Client), ReqKey]), + + Resp = eradius_req:record_metric(reply, #{request => Req}, Resp0), + send_response(Resp, Packet), case application:get_env(eradius, resend_timeout, 2000) of ResendTimeout when ResendTimeout > 0, is_integer(ResendTimeout) -> ServerPid ! {replied, ReqKey, self()}, - wait_resend_init(ServerPid, ReqKey, FromIP, FromPort, EncReply, ResendTimeout, ?RESEND_RETRIES); + wait_resend_init(ServerPid, ReqKey, Resp, Packet, ResendTimeout, ?RESEND_RETRIES); _ -> ok end; {discard, Reason} -> ?LOG(debug, "~s From: ~s INF: Handler discarded the request ~p for reason ~1000.p", - [printable_peer(ServerIP, Port), printable_peer(FromIP, FromPort), Reason, ReqKey]), - inc_discard_counter(Reason, NasProp); + [printable_peer(Server), printable_peer(Client), Reason, ReqKey]), + eradius_req:record_metric(discard, #{reason => Reason}, Req0); {exit, Reason} -> ?LOG(debug, "~s From: ~s INF: Handler exited for reason ~p, discarding request ~p", - [printable_peer(ServerIP, Port), printable_peer(FromIP, FromPort), Reason, ReqKey]), - inc_discard_counter(packetsDropped, NasProp) - end, - eradius_counter:dec_counter(pending, NasProp). + [printable_peer(Server), printable_peer(Client), Reason, ReqKey]), + eradius_req:record_metric(discard, #{reason => dropped}, Req0) + end. -wait_resend_init(ServerPid, ReqKey, FromIP, FromPort, EncReply, ResendTimeout, Retries) -> +wait_resend_init(ServerPid, ReqKey, Resp, Packet, ResendTimeout, Retries) -> erlang:send_after(ResendTimeout, self(), timeout), - wait_resend(ServerPid, ReqKey, FromIP, FromPort, EncReply, Retries). + wait_resend(ServerPid, ReqKey, Resp, Packet, Retries). -wait_resend(_ServerPid, _ReqKey, _FromIP, _FromPort, _EncReply, 0) -> ok; -wait_resend(ServerPid, ReqKey, FromIP, FromPort, EncReply, Retries) -> +wait_resend(_ServerPid, _ReqKey, _Resp, _Packet, 0) -> + ok; +wait_resend(ServerPid, ReqKey, Resp, Packet, Retries) -> receive - {ServerPid, resend, Socket} -> - gen_udp:send(Socket, FromIP, FromPort, EncReply), - wait_resend(ServerPid, ReqKey, FromIP, FromPort, EncReply, Retries - 1); + {ServerPid, resend} -> + send_response(Resp, Packet), + wait_resend(ServerPid, ReqKey, Resp, Packet, Retries - 1); timeout -> ok end. -run_handler([], _NasProp, _Handler, _EncRequest) -> - {discard, no_nodes}; -run_handler(NodesAvailable, NasProp = #nas_prop{handler_nodes = local}, Handler, EncRequest) -> - case lists:member(node(), NodesAvailable) of - true -> - handle_request(Handler, NasProp, EncRequest); - false -> - {discard, no_nodes_local} - end; -run_handler(NodesAvailable, NasProp, Handler, EncRequest) -> - case ordsets:intersection(lists:usort(NodesAvailable), lists:usort(NasProp#nas_prop.handler_nodes)) of - [LocalNode] when LocalNode == node() -> - handle_request(Handler, NasProp, EncRequest); - [RemoteNode] -> - run_remote_handler(RemoteNode, Handler, NasProp, EncRequest); - Nodes -> - %% humble testing at the erlang shell indicated that phash2 distributes N - %% very well even for small lenghts. - N = erlang:phash2(make_ref(), length(Nodes)) + 1, - case lists:nth(N, Nodes) of - LocalNode when LocalNode == node() -> - handle_request(Handler, NasProp, EncRequest); - RemoteNode -> - run_remote_handler(RemoteNode, Handler, NasProp, EncRequest) - end - end. - -run_remote_handler(Node, {HandlerMod, HandlerArgs}, NasProp, EncRequest) -> - RemoteArgs = [self(), HandlerMod, HandlerArgs, NasProp, EncRequest], - HandlerPid = spawn_link(Node, ?MODULE, handle_remote_request, RemoteArgs), - receive - {HandlerPid, ReturnValue} -> - ReturnValue - after - ?HANDLER_REPLY_TIMEOUT -> - %% this happens if the remote handler doesn't terminate - unlink(HandlerPid), - {discard, {remote_handler_reply_timeout, Node}} - end. - -%% @private --spec handle_request(eradius_server_mon:handler(), #nas_prop{}, binary()) -> any(). -handle_request({HandlerMod, HandlerArg}, NasProp = #nas_prop{secret = Secret, nas_ip = ServerIP, nas_port = Port}, EncRequest) -> - case eradius_lib:decode_request(EncRequest, Secret) of - Request = #radius_request{} -> - Sender = {ServerIP, Port, Request#radius_request.reqid}, - ?LOG(info, "~s", [eradius_log:collect_message(Sender, Request)], - maps:from_list(eradius_log:collect_meta(Sender, Request))), - eradius_log:write_request(Sender, Request), - apply_handler_mod(HandlerMod, HandlerArg, Request, NasProp); - {bad_pdu, "Message-Authenticator Attribute is invalid"} -> - ?LOG(error, "~s INF: Message-Authenticator Attribute is invalid", - [printable_peer(ServerIP, Port)]), - {discard, bad_authenticator}; - {bad_pdu, "Authenticator Attribute is invalid"} -> - ?LOG(error, "~s INF: Authenticator Attribute is invalid", - [printable_peer(ServerIP, Port)]), - {discard, bad_authenticator}; - {bad_pdu, "unknown request type"} -> - ?LOG(error, "~s INF: unknown request type", - [printable_peer(ServerIP, Port)]), - {discard, unknown_req_type}; - {bad_pdu, Reason} -> - ?LOG(error, "~s INF: Could not decode the request, reason: ~s", - [printable_peer(ServerIP, Port), Reason]), - {discard, malformed} - end. - -%% @private -%% @doc this function is spawned on a remote node to handle a radius request. -%% remote handlers need to be upgraded if the signature of this function changes. -%% error reports go to the logger of the node that executes the request. -handle_remote_request(ReplyPid, HandlerMod, HandlerArg, NasProp, EncRequest) -> - Result = handle_request({HandlerMod, HandlerArg}, NasProp, EncRequest), - ReplyPid ! {self(), Result}. - --spec apply_handler_mod(module(), term(), #radius_request{}, #nas_prop{}) -> {discard, term()} | {exit, term()} | {reply, binary()}. -apply_handler_mod(HandlerMod, HandlerArg, Request, NasProp) -> - #nas_prop{server_ip = ServerIP, server_port = Port} = NasProp, - try HandlerMod:radius_request(Request, NasProp, HandlerArg) of - {reply, Reply = #radius_request{cmd = ReplyCmd, attrs = ReplyAttrs, msg_hmac = MsgHMAC, eap_msg = EAPmsg}} -> - Sender = {NasProp#nas_prop.nas_ip, NasProp#nas_prop.nas_port, Request#radius_request.reqid}, - EncReply = eradius_lib:encode_reply(Request#radius_request{cmd = ReplyCmd, attrs = ReplyAttrs, - msg_hmac = Request#radius_request.msg_hmac or MsgHMAC or (size(EAPmsg) > 0), - eap_msg = EAPmsg}), - ?LOG(info, "~s", [eradius_log:collect_message(Sender, Reply)], - maps:from_list(eradius_log:collect_meta(Sender, Reply))), - eradius_log:write_request(Sender, Reply), - {reply, EncReply, {Request#radius_request.cmd, ReplyCmd}, Request}; +send_response(#{socket := Socket, client_addr := {ClientIP, ClientPort}}, Packet) -> + gen_udp:send(Socket, ClientIP, ClientPort, Packet). + +-spec apply_handler_mod(module(), term(), eradius_req:req()) -> + {discard, term()} | + {exit, term()} | + {reply, binary(), eradius_req:req(), eradius_req:req()}. +apply_handler_mod(HandlerMod, HandlerArg, + #{cmd := Cmd, req_id := ReqId, server := Server, client_addr := {ClientIP, _}} = Req) -> + try HandlerMod:radius_request(Req, HandlerArg) of + {reply, Resp0} -> + {Packet, Resp} = eradius_req:packet(Resp0), + {reply, Packet, Resp, Req}; noreply -> - ?LOG(error, "~s INF: Noreply for request ~p from handler ~p: returned value: ~p", - [printable_peer(ServerIP, Port), Request, HandlerArg, noreply]), + ?LOG(error, "~ts INF: Noreply for request ~tp from handler ~tp: returned value: ~tp", + [printable_peer(Server), ReqId, HandlerArg, noreply]), {discard, handler_returned_noreply}; {error, timeout} -> - ReqType = eradius_log:format_cmd(Request#radius_request.cmd), - ReqId = integer_to_list(Request#radius_request.reqid), - S = {NasProp#nas_prop.nas_ip, NasProp#nas_prop.nas_port, Request#radius_request.reqid}, - NAS = eradius_lib:get_attr(Request, ?NAS_Identifier), - NAS_IP = inet_parse:ntoa(NasProp#nas_prop.nas_ip), - ?LOG(error, "~s INF: Timeout after waiting for response to ~s(~s) from RADIUS NAS: ~s NAS_IP:~s", - [printable_peer(ServerIP, Port), ReqType, ReqId, NAS, NAS_IP], - maps:from_list(eradius_log:collect_meta(S, Request))), + ReqType = eradius_log:format_cmd(Cmd), + ?LOG(error, "~ts INF: Timeout after waiting for response to ~ts(~w) from RADIUS Client: ~s", + [printable_peer(Server), ReqType, ReqId, inet:ntoa(ClientIP)]), {discard, {bad_return, {error, timeout}}}; OtherReturn -> - ?LOG(error, "~s INF: Unexpected return for request ~p from handler ~p: returned value: ~p", - [printable_peer(ServerIP, Port), Request, HandlerArg, OtherReturn]), + ?LOG(error, "~ts INF: Unexpected return for request ~0tp from handler ~tp: returned value: ~tp", + [printable_peer(Server), ReqId, HandlerArg, OtherReturn]), {discard, {bad_return, OtherReturn}} catch Class:Reason:S -> - ?LOG(error, "~s INF: Handler crashed after request ~p, radius handler class: ~p, reason of crash: ~p, stacktrace: ~p", - [printable_peer(ServerIP, Port), Request, Class, Reason, S]), + ?LOG(error, "~ts INF: Handler crashed after request ~tp, radius handler class: ~tp, reason of crash: ~tp, stacktrace: ~tp", + [printable_peer(Server), ReqId, Class, Reason, S]), {exit, {Class, Reason}} end. -inc_counter({ReqCmd, RespCmd}, ServerName, NasProp, Ms, Request) -> - inc_request_counter(ReqCmd, ServerName, NasProp, Ms, Request), - inc_reply_counter(RespCmd, NasProp, Request). - -inc_request_counter(request, ServerName, NasProp, Ms, _) -> - eradius_counter:observe(eradius_request_duration_milliseconds, - NasProp, Ms, ServerName, "RADIUS request exeuction time"), - eradius_counter:observe(eradius_access_request_duration_milliseconds, - NasProp, Ms, ServerName, "Access-Request execution time"), - eradius_counter:inc_request_counter(accessRequests, NasProp); -inc_request_counter(accreq, ServerName, NasProp, Ms, Request) -> - eradius_counter:observe(eradius_request_duration_milliseconds, - NasProp, Ms, ServerName, "RADIUS request exeuction time"), - eradius_counter:observe(eradius_accounting_request_duration_milliseconds, - NasProp, Ms, ServerName, "Accounting-Request execution time"), - inc_request_counter_accounting(NasProp, Request); -inc_request_counter(coareq, ServerName, NasProp, Ms, _) -> - eradius_counter:observe(eradius_request_duration_milliseconds, - NasProp, Ms, ServerName, "RADIUS request exeuction time"), - eradius_counter:observe(eradius_coa_request_duration_milliseconds, - NasProp, Ms, ServerName, "Coa-Request execution time"), - eradius_counter:inc_request_counter(coaRequests, NasProp); -inc_request_counter(discreq, ServerName, NasProp, Ms, _) -> - eradius_counter:observe(eradius_request_duration_milliseconds, - NasProp, Ms, ServerName, "RADIUS request exeuction time"), - eradius_counter:observe(eradius_disconnect_request_duration_milliseconds, - NasProp, Ms, ServerName, "Disconnect-Request execution time"), - eradius_counter:inc_request_counter(discRequests, NasProp); -inc_request_counter(_Cmd, _ServerName, _NasProp, _Ms, _Request) -> - ok. - -inc_reply_counter(accept, NasProp, _) -> - eradius_counter:inc_counter(replies, NasProp), - eradius_counter:inc_reply_counter(accessAccepts, NasProp); -inc_reply_counter(reject, NasProp, _) -> - eradius_counter:inc_counter(replies, NasProp), - eradius_counter:inc_reply_counter(accessRejects, NasProp); -inc_reply_counter(challenge, NasProp, _) -> - eradius_counter:inc_counter(replies, NasProp), - eradius_counter:inc_reply_counter(accessChallenges, NasProp); -inc_reply_counter(accresp, NasProp, Request) -> - eradius_counter:inc_counter(replies, NasProp), - inc_response_counter_accounting(NasProp, Request); -inc_reply_counter(coaack, NasProp, _) -> - eradius_counter:inc_counter(replies, NasProp), - eradius_counter:inc_reply_counter(coaAcks, NasProp); -inc_reply_counter(coanak, NasProp, _) -> - eradius_counter:inc_counter(replies, NasProp), - eradius_counter:inc_reply_counter(coaNaks, NasProp); -inc_reply_counter(discack, NasProp, _) -> - eradius_counter:inc_counter(replies, NasProp), - eradius_counter:inc_reply_counter(discAcks, NasProp); -inc_reply_counter(discnak, NasProp, _) -> - eradius_counter:inc_counter(replies, NasProp), - eradius_counter:inc_reply_counter(discNaks, NasProp); -inc_reply_counter(_Cmd, _NasProp, _Request) -> +%%%========================================================================= +%%% internal functions +%%%========================================================================= + +-spec config(IP :: inet:ip_address() | any, inet:port_number(), + server_opts()) -> {ok, server_config()}. +config(IP, Port, #{handler := {_, _}, clients := Clients} = Opts0) + when (IP =:= any orelse is_tuple(IP)) andalso + is_map(Clients) andalso + is_integer(Port) andalso Port >= 0 andalso Port < 65536 -> + SocketOpts0 = maps:get(socket_opts, Opts0, #{}), + SocketOpts = #{family := Family, ifaddr := IfAddr} = + maps:merge(default_socket_opts(IP, Port), to_map(SocketOpts0)), + + Opts = + Opts0#{server_name => server_name(IP, Port, Opts0), + socket_opts => SocketOpts#{ifaddr := socket_ip(Family, IfAddr)}, + metrics_callback => maps:get(metrics_callback, Opts0, undefined), + clients => + maps:fold(fun(K, V, M) -> M#{socket_ip(Family, K) => V} end, #{}, Clients) + }, + {ok, Opts}. + +flow_control(#state{socket = Socket, active_n = once}) -> + inet:setopts(Socket, [{active, once}]); +flow_control(_) -> ok. -inc_request_counter_accounting(NasProp, #radius_request{attrs = Attrs}) -> - Requests = ets:match_spec_run(Attrs, server_request_counter_account_match_spec_compile()), - [eradius_counter:inc_request_counter(Type, NasProp) || Type <- Requests], - ok; -inc_request_counter_accounting(_, _) -> - ok. +server_name(_, _, #{server_name := ServerName}) -> + ServerName; +server_name(IP, Port, _) -> + iolist_to_binary(server_name(IP, Port)). -inc_response_counter_accounting(NasProp, #radius_request{attrs = Attrs}) -> - Responses = ets:match_spec_run(Attrs, server_response_counter_account_match_spec_compile()), - [eradius_counter:inc_reply_counter(Type, NasProp) || Type <- Responses], - ok; -inc_response_counter_accounting(_, _) -> - ok. +server_name(IP, Port) -> + [inet:ntoa(IP), $:, integer_to_list(Port)]. -inc_discard_counter(bad_authenticator, NasProp) -> - eradius_counter:inc_counter(badAuthenticators, NasProp); -inc_discard_counter(unknown_req_type, NasProp) -> - eradius_counter:inc_counter(unknownTypes, NasProp); -inc_discard_counter(malformed, NasProp) -> - eradius_counter:inc_counter(malformedRequests, NasProp); -inc_discard_counter(_Reason, NasProp) -> - eradius_counter:inc_counter(packetsDropped, NasProp). - -server_request_counter_account_match_spec_compile() -> - case persistent_term:get({?MODULE, ?FUNCTION_NAME}, undefined) of - undefined -> - MatchSpecCompile = - ets:match_spec_compile( - ets:fun2ms( - fun ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Start}) -> - accountRequestsStart; - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Stop}) -> - accountRequestsStop; - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Update}) -> - accountRequestsUpdate - end)), - persistent_term:put({?MODULE, ?FUNCTION_NAME}, MatchSpecCompile), - MatchSpecCompile; - MatchSpecCompile -> - MatchSpecCompile - end. +to_map(Opts) when is_list(Opts) -> + maps:from_list(Opts); +to_map(Opts) when is_map(Opts) -> + Opts. -server_response_counter_account_match_spec_compile() -> - case persistent_term:get({?MODULE, ?FUNCTION_NAME}, undefined) of - undefined -> - MatchSpecCompile = - ets:match_spec_compile( - ets:fun2ms( - fun ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Start}) -> - accountResponsesStart; - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Stop}) -> - accountResponsesStop; - ({#attribute{id = ?RStatus_Type}, ?RStatus_Type_Update}) -> - accountResponsesUpdate - end)), - persistent_term:put({?MODULE, ?FUNCTION_NAME}, MatchSpecCompile), - MatchSpecCompile; - MatchSpecCompile -> - MatchSpecCompile +%% @private +socket_ip(_, any) -> + any; +socket_ip(inet, {_, _, _, _} = IP) -> + IP; +socket_ip(inet6, {_, _, _, _} = IP) -> + inet:ipv4_mapped_ipv6_address(IP); +socket_ip(inet6, {_, _, _, _,_, _, _, _} = IP) -> + IP. + +default_socket_opts(Port) -> + #{port => Port, + active_n => 100, + recbuf => application:get_env(eradius, recbuf, 8192), + sndbuf => application:get_env(eradius, sndbuf, 131072) + }. + +default_socket_opts(any, Port) -> + Opts = default_socket_opts(Port), + Opts#{family => inet6, + ifaddr => any, + ipv6_v6only => false}; +default_socket_opts({_, _, _, _} = IP, Port) -> + Opts = default_socket_opts(Port), + Opts#{family => inet, + ifaddr => IP}; +default_socket_opts({_, _, _, _, _, _, _, _} = IP, Port) -> + Opts = default_socket_opts(Port), + Opts#{family => inet6, + ifaddr => IP, + ipv6_v6only => false}. + +inet_opts(Config, Opts0) -> + Opts = + maps:to_list( + maps:with([recbuf, sndbuf, ifaddr, + ipv6_v6only, netns, bind_to_device, read_packets], Config)) ++ Opts0, + case Config of + #{inet_backend := Backend} when Backend =:= inet; Backend =:= socket -> + [{inet_backend, Backend} | Opts]; + _ -> + Opts end. diff --git a/src/eradius_server_mon.erl b/src/eradius_server_mon.erl deleted file mode 100644 index dabaa386..00000000 --- a/src/eradius_server_mon.erl +++ /dev/null @@ -1,169 +0,0 @@ -%% @private -%% @doc Manager for RADIUS server processes. -%% This module manages the RADIUS server registry and -%% validates and applies the server configuration from the application environment. -%% It starts all servers that are configured as part of its initialization, -%% then sends ping requests to all nodes that are part of the configuration in order -%% to keep them connected. --module(eradius_server_mon). --export([start_link/0, reconfigure/0, lookup_handler/3, lookup_pid/2, all_nas_keys/0]). --export_type([handler/0]). - --behaviour(gen_server). --export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). - --include_lib("kernel/include/logger.hrl"). --include("eradius_lib.hrl"). - --define(SERVER, ?MODULE). --define(NAS_TAB, eradius_nas_tab). --export_type([server/0]). - --import(eradius_lib, [printable_peer/2]). - --record(nas, { - key :: {server(), inet:ip_address()}, - server_name :: server_name(), - handler :: handler(), - prop :: #nas_prop{} - }). - -%% ------------------------------------------------------------------------------------------ -%% -- API -start_link() -> - gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). - -%% @doc Apply NAS config from the application environment. -%% Walks the list of configured servers and NASs, -%% starting and stopping servers as necessary. -%% If the configuration is invalid, no servers are modified. -%% --spec reconfigure() -> ok | {error, invalid_config}. -reconfigure() -> - gen_server:call(?SERVER, reconfigure). - -%% @doc Fetch the RADIUS secret, handler and trace flag for a given server/NAS combination. -%% This is a very fast operation that is called for every -%% request by the RADIUS server process. --spec lookup_handler(inet:ip_address(), eradius_server:port_number(), inet:ip_address()) -> {ok, handler(), #nas_prop{}} | {error, not_found}. -lookup_handler(IP, Port, NasIP) -> - case ets:lookup(?NAS_TAB, {{IP, Port}, NasIP}) of - [] when NasIP == {0, 0, 0, 0} -> - {error, not_found}; - [] -> - lookup_handler(IP, Port, {0, 0, 0, 0}); - [Rec] -> - Prop = (Rec#nas.prop)#nas_prop{server_ip = IP, server_port = Port}, - {ok, Rec#nas.handler, Prop} - end. - -%% @doc Fetches the pid of RADIUS server at IP:Port, if there is one. --spec lookup_pid(inet:ip_address(), eradius_server:port_number()) -> {ok, pid()} | {error, not_found}. -lookup_pid(ServerIP, ServerPort) -> - gen_server:call(?SERVER, {lookup_pid, {ServerIP, ServerPort}}). - -%% @doc returns the list of all currently configured NASs --spec all_nas_keys() -> [term()]. -all_nas_keys() -> - ets:select(?NAS_TAB, [{#nas{key = '$1', _ = '_'}, [], ['$1']}]). - -%% ------------------------------------------------------------------------------------------ -%% -- gen_server callbacks --record(state, {running}). - -init([]) -> - ?NAS_TAB = ets:new(?NAS_TAB, [named_table, protected, {keypos, #nas.key}]), - case configure(#state{running = []}) of - {error, invalid_config} -> {stop, invalid_config}; - Else -> Else - end. - -handle_call({lookup_pid, Server}, _From, State) -> - case proplists:get_value(Server, State#state.running) of - undefined -> - {reply, {error, not_found}, State}; - Pid -> - {reply, {ok, Pid}, State} - end; -handle_call(reconfigure, _From, State) -> - case configure(State) of - {error, invalid_config} -> {reply, {error, invalid_config}, State}; - {ok, NState} -> {reply, ok, NState} - end; -handle_call(_Request, _From, State) -> - {noreply, State}. - -handle_info(_Info, State) -> - {noreply, State}. - -%% unused callbacks -handle_cast(_Msg, State) -> {noreply, State}. -terminate(_Reason, _State) -> ok. -code_change(_OldVsn, State, _Extra) -> {ok, State}. - -%% ------------------------------------------------------------------------------------------ -%% -- helpers - -configure(#state{running = Running}) -> - {ok, ConfServList} = application:get_env(servers), - case eradius_config:validate_config(ConfServList) of - {invalid, Message} -> - ?LOG(error, "Invalid server config, ~s", [Message]), - {error, invalid_config}; - ServList -> %% list of {ServerName, ServerAddr, NasHandler} tuples - NasList = lists:flatmap(fun(Server) -> server_naslist(Server) end, ServList), - Tab = ets:tab2list(?NAS_TAB), - ToDelete = Tab -- NasList, - ToInsert = NasList -- Tab, - update_nases(ToDelete, ToInsert), - NewServAddrs = [{ServerName, ServerAddr} || {ServerName, ServerAddr, _} <- ServList], - OldServAddrs =[{ServerName, ServerAddr} || {ServerName, ServerAddr, _} <- Running], - ToStop = OldServAddrs -- NewServAddrs, - ToStart = NewServAddrs -- OldServAddrs, - NewRunning = update_server(Running, ToStop, ToStart), - NasHandler = [ NasInfo || {_ServerName, _Addr, NasInfo} <- ServList], - eradius_node_mon:set_nodes(config_nodes(NasHandler)), - {ok, #state{running = NewRunning}} - end. - -server_naslist({ServerName, {IP, Port, _Opts}, HandlerList}) -> - server_naslist({ServerName, {IP, Port}, HandlerList}); -server_naslist({ServerName, {IP, Port}, HandlerList}) -> - lists:map(fun({NasId, NasIP, Secret, HandlerNodes, HandlerMod, HandlerArgs}) -> - ServerInfo = eradius_lib:make_addr_info({ServerName, {IP, Port}}), - NasInfo = eradius_lib:make_addr_info({NasId, {NasIP, undefined}}), - #nas{key = {{IP, Port}, NasIP}, server_name = ServerName, handler = {HandlerMod, HandlerArgs}, - prop = #nas_prop{handler_nodes = HandlerNodes, nas_id = NasId, nas_ip = NasIP, secret = Secret, - metrics_info = {ServerInfo, NasInfo}}} - end, HandlerList). - -config_nodes(NasHandler) -> - ordsets:from_list(lists:concat([N || {_, _, N, _, _} <- NasHandler, N /= local, N/= node()])). - -update_server(Running, ToStop, ToStart) -> - Stopped = lists:map(fun(ServerAddr = {_ServerName, Addr}) -> - StoppedServer = {_, _, Pid} = lists:keyfind(Addr, 2, Running), - eradius_server_sup:stop_instance(ServerAddr, Pid), - StoppedServer - end, ToStop), - StartFn = fun({ServerName, Addr = {IP, Port, _Opts}}=ServerAddr) -> - case eradius_server_sup:start_instance(ServerAddr) of - {ok, Pid} -> - {ServerName, Addr, Pid}; - {error, Error} -> - ?LOG(error, "Could not start listener on host: ~s, occurring error: ~p", - [printable_peer(IP, Port), Error]) - end - end, - NewStarted = lists:map(fun - ({ServerName, {IP, Port}}) -> - StartFn({ServerName, {IP, Port, []}}); - (ServerAddr) -> - StartFn(ServerAddr) - end, - ToStart), - (Running -- Stopped) ++ NewStarted. - -update_nases(ToDelete, ToInsert) -> - lists:foreach(fun(Nas) -> ets:delete_object(?NAS_TAB, Nas) end, ToDelete), - lists:foreach(fun(Nas) -> ets:insert(?NAS_TAB, Nas) end, ToInsert). diff --git a/src/eradius_server_sup.erl b/src/eradius_server_sup.erl index 2387baf9..624e1f77 100644 --- a/src/eradius_server_sup.erl +++ b/src/eradius_server_sup.erl @@ -1,12 +1,15 @@ %% @private %% @doc Supervisor for RADIUS server processes. -module(eradius_server_sup). --export([start_link/0, start_instance/1, stop_instance/2, all/0]). - -behaviour(supervisor). + +-export([start_link/0, start_instance/1, all/0]). + -export([init/1]). -import(eradius_lib, [printable_peer/2]). +-ignore_xref([start_link/0, all/0]). + -include_lib("kernel/include/logger.hrl"). -define(SERVER, ?MODULE). @@ -16,20 +19,8 @@ start_link() -> supervisor:start_link({local, ?SERVER}, ?MODULE, []). -start_instance(_ServerAddr = {ServerName, {IP, Port}}) -> - ?LOG(info, "Starting RADIUS Listener at ~s", [printable_peer(IP, Port)]), - supervisor:start_child(?SERVER, [ServerName, IP, Port]); - -start_instance(_ServerAddr = {ServerName, {IP, Port, Opts}}) -> - ?LOG(info, "Starting RADIUS Listener at ~s", [printable_peer(IP, Port)]), - supervisor:start_child(?SERVER, [ServerName, IP, Port, Opts]). - -stop_instance(_ServerAddr = {_ServerName, {IP, Port}}, Pid) -> - ?LOG(info, "Stopping RADIUS Listener at ~s", [printable_peer(IP, Port)]), - supervisor:terminate_child(?SERVER, Pid); - -stop_instance(ServerAddr = {_ServerName, {_IP, _Port, _Opts}}, Pid) -> - stop_instance(ServerAddr, Pid). +start_instance(Opts) -> + supervisor:start_child(?SERVER, Opts). all() -> lists:map(fun({_, Child, _, _}) -> Child end, supervisor:which_children(?SERVER)). diff --git a/src/eradius_server_top_sup.erl b/src/eradius_server_top_sup.erl deleted file mode 100644 index b5e8c57a..00000000 --- a/src/eradius_server_top_sup.erl +++ /dev/null @@ -1,27 +0,0 @@ -%% @private -%% @doc Supervisor for RADIUS server supervisor tree. -%% This is a one_for_all supervisor because the server_mon must always die when the server_sup goes down, and vice-versa. --module(eradius_server_top_sup). --behaviour(supervisor). - --export([start_link/0]). --export([init/1]). - --define(SERVER, ?MODULE). - -start_link() -> - supervisor:start_link({local, ?SERVER}, ?MODULE, []). - -%% ------------------------------------------------------------------------------------------ -%% -- supervisor callbacks -init([]) -> - RestartStrategy = one_for_all, - MaxRestarts = 10, - MaxSecondsBetweenRestarts = 1, - - SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts}, - - ServerSup = {sup, {eradius_server_sup, start_link, []}, permanent, infinity, supervisor, [eradius_server_sup]}, - ServerMon = {mon, {eradius_server_mon, start_link, []}, permanent, brutal_kill, worker, [eradius_server_mon]}, - - {ok, {SupFlags, [ServerSup, ServerMon]}}. diff --git a/src/eradius_sup.erl b/src/eradius_sup.erl index 86093a41..74ec2089 100644 --- a/src/eradius_sup.erl +++ b/src/eradius_sup.erl @@ -20,25 +20,19 @@ init([]) -> SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts}, DictServer = {dict, {eradius_dict, start_link, []}, permanent, brutal_kill, worker, [eradius_dict]}, - StatsServer = {counter, {eradius_counter, start_link, []}, permanent, brutal_kill, worker, [eradius_counter]}, - StatsCollect = {aggregator, {eradius_counter_aggregator, start_link, []}, permanent, brutal_kill, worker, [eradius_counter_aggregator]}, - NodeMon = {node_mon, {eradius_node_mon, start_link, []}, permanent, brutal_kill, worker, [eradius_node_mon]}, - RadiusLog = {radius_log, {eradius_log, start_link, []}, permanent, brutal_kill, worker, [eradius_log]}, - ServerTopSup = {server_top_sup, {eradius_server_top_sup, start_link, []}, permanent, infinity, supervisor, [eradius_server_top_sup]}, - ClientMngr = - #{id => client_mngr, - start => {eradius_client_mngr, start_link, []}, + ServerSup = + #{id => server_sup, + start => {eradius_server_sup, start_link, []}, restart => permanent, - shutdown => 500, - type => worker, - modules => [eradius_client_mngr]}, - ClientSocketSup = - #{id => eradius_client_socket_sup, - start => {eradius_client_socket_sup, start_link, []}, - restart => permanent, - shutdown => 5000, - type => supervisor, - modules => [eradius_client_socket_sup]}, + shutdown => infinity, + type => supervisor, + modules => [eradius_server_sup]}, + ClientTopSup = + #{id => client_top_sup, + start => {eradius_client_top_sup, start_link, []}, + restart => permanent, + shutdown => infinity, + type => supervisor, + modules => [eradius_client_top_sup]}, - {ok, {SupFlags, [DictServer, NodeMon, StatsServer, StatsCollect, RadiusLog, - ServerTopSup, ClientSocketSup, ClientMngr]}}. + {ok, {SupFlags, [DictServer, ServerSup, ClientTopSup]}}. diff --git a/test/eradius_client_SUITE.erl b/test/eradius_client_SUITE.erl index a7a34386..3214783b 100644 --- a/test/eradius_client_SUITE.erl +++ b/test/eradius_client_SUITE.erl @@ -14,31 +14,24 @@ %%% Defines %%%=================================================================== +-define(SERVER, eradius_test_handler). -define(HUT_SOCKET, eradius_client_socket). --define(BAD_SERVER_IP(Family), - {eradius_test_lib:localhost(Family, ip), 1820, "secret"}). -define(BAD_SERVER_INITIAL_RETRIES, 3). -define(BAD_SERVER_TUPLE_INITIAL(Family), - {{eradius_test_lib:localhost(Family, tuple), 1820}, - ?BAD_SERVER_INITIAL_RETRIES, - ?BAD_SERVER_INITIAL_RETRIES}). + {{eradius_test_lib:localhost(Family, mapped), 1920}, + ?BAD_SERVER_INITIAL_RETRIES, 0}). -define(BAD_SERVER_TUPLE(Family), - {{eradius_test_lib:localhost(Family, tuple), 1820}, - ?BAD_SERVER_INITIAL_RETRIES - 1, - ?BAD_SERVER_INITIAL_RETRIES}). --define(BAD_SERVER_IP_ETS_KEY(Family), - {eradius_test_lib:localhost(Family, tuple), 1820}). + {{eradius_test_lib:localhost(Family, mapped), 1920}, + ?BAD_SERVER_INITIAL_RETRIES, 1}). -define(GOOD_SERVER_INITIAL_RETRIES, 3). -define(GOOD_SERVER_TUPLE(Family), - {{eradius_test_lib:localhost(Family, tuple), 1812}, - ?GOOD_SERVER_INITIAL_RETRIES, - ?GOOD_SERVER_INITIAL_RETRIES}). + {{eradius_test_lib:localhost(Family, mapped), 1812}, + ?GOOD_SERVER_INITIAL_RETRIES, 0}). -define(GOOD_SERVER_2_TUPLE(Family), - {{eradius_test_lib:badhost(Family), 1813}, - ?GOOD_SERVER_INITIAL_RETRIES, - ?GOOD_SERVER_INITIAL_RETRIES}). + {{eradius_test_lib:localhost(Family, mapped), 1813}, + ?GOOD_SERVER_INITIAL_RETRIES, 0}). -define(RADIUS_SERVERS(Family), [?GOOD_SERVER_TUPLE(Family), @@ -91,13 +84,13 @@ init_per_group(inet, Config) -> init_per_group(socket, Config) -> [{inet_backend, socket} | Config]; init_per_group(ipv6 = Group, Config) -> - {skip, "no IPv6 server support (yet)"}; - %% case eradius_test_lib:has_ipv6_test_config() of - %% true -> - %% [{family, Group} | Config]; - %% _ -> - %% {skip, "IPv6 test IPs not configured"} - %% end; + %% {skip, "no IPv6 server support (yet)"}; + case eradius_test_lib:has_ipv6_test_config() of + true -> + [{family, Group} | Config]; + _ -> + {skip, "IPv6 test IPs not configured"} + end; init_per_group(ipv4_mapped_ipv6 = Group, Config) -> case eradius_test_lib:has_ipv6_test_config() of true -> @@ -117,18 +110,23 @@ start_handler(Config) -> Family = proplists:get_value(family, Config), eradius_test_handler:start(Backend, Family). +start_client(Config) -> + Backend = proplists:get_value(inet_backend, Config, inet), + Family = proplists:get_value(family, Config), + eradius_test_handler:start_client(Backend, Family). + init_per_testcase(send_request, Config) -> - application:stop(eradius), start_handler(Config), Config; init_per_testcase(send_request_failover, Config) -> - application:stop(eradius), start_handler(Config), Config; init_per_testcase(check_upstream_servers, Config) -> - application:stop(eradius), start_handler(Config), Config; +init_per_testcase(wanna_send, Config) -> + start_client(Config), + Config; init_per_testcase(_Test, Config) -> Config. @@ -147,8 +145,7 @@ end_per_testcase(_Test, Config) -> %% STUFF getSocketCount() -> - Counts = supervisor:count_children(eradius_client_socket_sup), - proplists:get_value(active, Counts). + eradius_client_mngr:get_socket_count(?SERVER). testSocket(undefined) -> true; @@ -185,7 +182,7 @@ parse_ip(Address) when is_list(Address) -> inet_parse:address(Address); parse_ip(T = {_, _, _, _}) -> {ok, T}; -parse_ip(T = {_, _, _, _, _, _}) -> +parse_ip(T = {_, _, _, _, _, _, _, _}) -> {ok, T}. %% CHECK @@ -221,66 +218,73 @@ check(#{sockets := OS, no_ports := _OP, idcounters := _OC, socket_id := {_, OA}} %% TESTS -send_request(Config) -> - Family = proplists:get_value(family, Config), - ?equal(accept, - eradius_test_handler:send_request(eradius_test_lib:localhost(Family, tuple))), - ?equal(accept, - eradius_test_handler:send_request(eradius_test_lib:localhost(Family, ip))), - ?equal(accept, - eradius_test_handler:send_request(eradius_test_lib:localhost(Family, string))), - ?equal(accept, - eradius_test_handler:send_request(eradius_test_lib:localhost(Family, binary))), +send_request(_Config) -> + ?equal(accept, eradius_test_handler:send_request(one)), ok. send(FUN, Ports, Address) -> meckStart(), - OldState = eradius_client_mngr:get_state(), + OldState = eradius_client_mngr:get_state(?SERVER), FUN(), - NewState = eradius_client_mngr:get_state(), + NewState = eradius_client_mngr:get_state(?SERVER), true = check(OldState, NewState, Ports, Address), meckStop(). wanna_send(_Config) -> - lists:map(fun(_) -> - IP = {rand:uniform(100), rand:uniform(100), rand:uniform(100), rand:uniform(100)}, - Port = rand:uniform(100), - FUN = fun() -> eradius_client_mngr:wanna_send({undefined, {IP, Port}}) end, + lists:map(fun(X) -> + Server = binary_to_atom(<<(X+$A)>>), + FUN = fun() -> eradius_client_mngr:wanna_send(?SERVER, [Server], []) end, send(FUN, null, null) - end, lists:seq(1, 10)). - -%% socket shutdown is done asynchronous, the tests need to wait a bit for it to finish. -reconf_address(_Config) -> + end, lists:seq(0, 9)). + +reconf_address(Config) -> + IP = case proplists:get_value(family, Config) of + ipv4 -> + {7, 13, 23, 42}; + ipv4_mapped_ipv6 -> + inet:ipv4_mapped_ipv6_address({7, 13, 23, 42}); + ipv6 -> + {16#fd96, 16#dcd2, 16#efdb, 16#41c3, 0, 0, 16#100, 1} + end, FUN = fun() -> - eradius_client_mngr:reconfigure(#{ip => "7.13.23.42"}), + eradius_client_mngr:reconfigure(?SERVER, #{ip => IP}), + %% socket shutdown is done asynchronous, + %% the tests need to wait a bit for it to finish. timer:sleep(100) end, - send(FUN, null, "7.13.23.42"). + send(FUN, null, inet:ntoa(IP)). reconf_ports_30(_Config) -> FUN = fun() -> - eradius_client_mngr:reconfigure(#{no_ports => 30}), + eradius_client_mngr:reconfigure(?SERVER, #{no_ports => 30}), + %% socket shutdown is done asynchronous, + %% the tests need to wait a bit for it to finish. timer:sleep(100) end, send(FUN, 30, null). reconf_ports_10(_Config) -> FUN = fun() -> - eradius_client_mngr:reconfigure(#{no_ports => 10}), + eradius_client_mngr:reconfigure(?SERVER, #{no_ports => 10}), + %% socket shutdown is done asynchronous, + %% the tests need to wait a bit for it to finish. timer:sleep(100) end, send(FUN, 10, null). send_request_failover(Config) -> Family = proplists:get_value(family, Config), - ?equal(accept, eradius_test_handler:send_request_failover(?BAD_SERVER_IP(Family))), + ?equal(accept, eradius_test_handler:send_request_failover(bad)), {ok, Timeout} = application:get_env(eradius, unreachable_timeout), timer:sleep(Timeout * 1000), - ?equal([?BAD_SERVER_TUPLE(Family)], - eradius_client_mngr:servers(?BAD_SERVER_IP_ETS_KEY(Family))), + ?equal(?BAD_SERVER_TUPLE(Family), eradius_client_mngr:server(?SERVER, bad)), ok. check_upstream_servers(Config) -> Family = proplists:get_value(family, Config), - ?equal(lists:keysort(1, ?RADIUS_SERVERS(Family)), eradius_client_mngr:servers()), + Servers = eradius_client_mngr:servers(?SERVER), + ct:pal("Servers: ~p~nExpected: ~p", [Servers, ?RADIUS_SERVERS(Family)]), + ?equal(true, + sets:is_subset(sets:from_list(?RADIUS_SERVERS(Family)), + sets:from_list(Servers))), ok. diff --git a/test/eradius_config_SUITE.erl b/test/eradius_config_SUITE.erl deleted file mode 100644 index 07dd2cba..00000000 --- a/test/eradius_config_SUITE.erl +++ /dev/null @@ -1,257 +0,0 @@ -%% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> - -%% Permission is hereby granted, free of charge, to any person obtaining a -%% copy of this software and associated documentation files (the "Software"), -%% to deal in the Software without restriction, including without limitation -%% the rights to use, copy, modify, merge, publish, distribute, sublicense, -%% and/or sell copies of the Software, and to permit persons to whom the -%% Software is furnished to do so, subject to the following conditions: - -%% The above copyright notice and this permission notice shall be included in -%% all copies or substantial portions of the Software. - -%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -%% DEALINGS IN THE SOFTWARE. - --module(eradius_config_SUITE). --compile(export_all). --include("../include/eradius_lib.hrl"). --include("eradius_test.hrl"). - -all() -> [config_1, config_2, config_options, - config_socket_options, config_nas_removing, - config_with_ranges, log_test, generate_ip_list_test, - test_validate_server]. - -init_per_suite(Config) -> - {ok, _} = application:ensure_all_started(eradius), - Config. - -end_per_suite(_Config) -> - application:stop(eradius), - ok. - -config_1(_Config) -> - Conf = [{session_nodes, ['node1@host1', 'node2@host2']}, - {radius_callback, ?MODULE}, - {servers, [ - {root, {eradius_test_lib:localhost(ip), [1812, 1813]}} - ]}, - {root, [ - { {"NAS1", [arg1, arg2]}, - [{"10.18.14.2/30", <<"secret1">>}]}, - { {"NAS2", [arg1, arg2]}, - [{{10, 18, 14, 3}, <<"secret2">>, [{nas_id, <<"name">>}]}]} - ]}], - ok = apply_conf(Conf), - LocalHost = eradius_test_lib:localhost(tuple), - ?match({ok, {?MODULE,[arg1,arg2]}, - #nas_prop{ - server_ip = LocalHost, - server_port = 1813, - nas_id = <<"name">>, - nas_ip = {10,18,14,3}, - handler_nodes = ['node1@host1', 'node2@host2'] - }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,3})), - ?match({ok, {?MODULE,[arg1,arg2]}, - #nas_prop{ - server_ip = LocalHost, - server_port = 1812, - nas_id = <<"name">>, - nas_ip = {10,18,14,3}, - handler_nodes = ['node1@host1', 'node2@host2'] - }}, eradius_server_mon:lookup_handler(LocalHost, 1812, {10,18,14,3})), - ?match({ok, {?MODULE,[arg1,arg2]}, - #nas_prop{ - server_ip = LocalHost, - server_port = 1813, - nas_id = <<"NAS1_10.18.14.2">>, - nas_ip = {10,18,14,2}, - handler_nodes = ['node1@host1', 'node2@host2'] - }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,2})), - ok. - -config_2(_Config) -> - Conf = [{session_nodes, [ - {"NodeGroup1", ['node1@host1', 'node2@host2']}, - {"NodeGroup2", ['node3@host3', 'node4@host4']} - ] - }, - {servers, [ - {root, {eradius_test_lib:localhost(ip), [1812, 1813]}} - ]}, - {root, [ - { {handler1, "NAS1", [arg1, arg2]}, - [ {"10.18.14.3", <<"secret1">>, [{group, "NodeGroup1"}]}, - {"10.18.14.4", <<"secret1">>, [{group, "NodeGroup1"}]} ] }, - { {handler2, "NAS2", [arg3, arg4]}, - [ {"10.18.14.2", <<"secret2">>, [{group, "NodeGroup2"}]} ] } - ]}], - ok = apply_conf(Conf), - LocalHost = eradius_test_lib:localhost(tuple), - ?match({ok, {handler1,[arg1,arg2]}, - #nas_prop{ - server_ip = LocalHost, - server_port = 1813, - nas_id = <<"NAS1_10.18.14.3">>, - nas_ip = {10,18,14,3}, - handler_nodes = ['node1@host1', 'node2@host2'] - }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,3})), - ?match({ok, {handler1,[arg1,arg2]}, - #nas_prop{ - server_ip = LocalHost, - server_port = 1813, - nas_id = <<"NAS1_10.18.14.4">>, - nas_ip = {10,18,14,4}, - handler_nodes = ['node1@host1', 'node2@host2'] - }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,4})), - ?match({ok, {handler2,[arg3,arg4]}, - #nas_prop{ - server_ip = LocalHost, - server_port = 1813, - nas_id = <<"NAS2_10.18.14.2">>, - nas_ip = {10,18,14,2}, - handler_nodes = ['node3@host3', 'node4@host4'] - }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,2})), - ok. - -config_socket_options(_Config) -> - Opts = [{netns, "/var/run/netns/net1"}], - ?match(Opts, eradius_config:validate_socket_options(Opts)), - Invalid = [{buffer, 512}, {active, false}], - ?match({invalid, _}, eradius_config:validate_socket_options(Invalid)), - Invalid2 = [{buffer, 512}, {ip, {127, 0, 0, 10}}], - ?match({invalid, _}, eradius_config:validate_socket_options(Invalid2)), - ok. - -config_options(_Config) -> - Opts = [{socket_opts, [{recbuf, 8192}, - {sndbuf, 131072}, - {netns, "/var/run/netns/net1"}]}], - ?match(Opts, eradius_config:validate_options(Opts)), - ok. -test_validate_server(_Config) -> - SocketOpts = [{socket_opts, [{recbuf, 8192}, {sndbuf, 131072}, {netns, "/var/run/netns/net1"}]}], - Opts = {{127, 0, 0, 1}, 1812, SocketOpts}, - ?match(Opts, eradius_config:validate_server(Opts)), - Opts2 = {{127, 0, 0, 1}, "1812", SocketOpts}, - ?match({{127, 0, 0, 1}, 1812, SocketOpts}, eradius_config:validate_server(Opts2)), - Opts3 = {{127, 0, 0, 1}, 1812}, - ?match(Opts3, eradius_config:validate_server(Opts3)), - ok. - -config_nas_removing(_Config) -> - Conf = [{servers, [ {root, {eradius_test_lib:localhost(ip), [1812, 1813]}} ]}, - {root, [ ]}], - ok = apply_conf(Conf), - ?match([], ets:tab2list(eradius_nas_tab)), - ok. - -config_with_ranges(_Config) -> - Nodes = ['node1@host1', 'node2@host2'], - Conf = [{session_nodes, [ - {"NodeGroup", Nodes} - ] - }, - {servers, [ - {root, {eradius_test_lib:localhost(ip), [1812, 1813]}} - ]}, - {root, [ - { {handler, "NAS", []}, - [ {"10.18.14.2/30", <<"secret2">>, [{group, "NodeGroup"}]} ] } - ]}], - ok = apply_conf(Conf), - LocalHost = eradius_test_lib:localhost(tuple), - ?match({ok, {handler,[]}, - #nas_prop{ - server_ip = LocalHost, - server_port = 1812, - nas_id = <<"NAS_10.18.14.2">>, - nas_ip = {10,18,14,2}, - handler_nodes = Nodes - }}, eradius_server_mon:lookup_handler(LocalHost, 1812, {10,18,14,2})), - ?match({ok, {handler,[]}, - #nas_prop{ - server_ip = LocalHost, - server_port = 1812, - nas_id = <<"NAS_10.18.14.3">>, - nas_ip = {10,18,14,3}, - handler_nodes = Nodes - }}, eradius_server_mon:lookup_handler(LocalHost, 1812, {10,18,14,3})), - ?match({ok, {handler,[]}, - #nas_prop{ - server_ip = LocalHost, - server_port = 1813, - nas_id = <<"NAS_10.18.14.1">>, - nas_ip = {10,18,14,1}, - handler_nodes = Nodes - }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,1})), - ?match({ok, {handler,[]}, - #nas_prop{ - server_ip = LocalHost, - server_port = 1813, - nas_id = <<"NAS_10.18.14.2">>, - nas_ip = {10,18,14,2}, - handler_nodes = Nodes - }}, eradius_server_mon:lookup_handler(LocalHost, 1813, {10,18,14,2})), - ok. - -log_test(_Config) -> - LogFile0 = "./radius.log", - LogFile1 = "./radius1.log", - LogOn0 = [{logging, true}, {logfile, LogFile0}], - LogOn1 = [{logging, true}, {logfile, LogFile1}], - LogOff = [{logging, false}], - - %% via eradius_log:reconfigure/0 - set_env(LogOn0), - ok = eradius_log:reconfigure(), - ?match(true, logger_disabled /= gen_server:call(eradius_log, get_state)), - ?match(true, filelib:is_file(LogFile0)), - - set_env(LogOff), - ok = eradius_log:reconfigure(), - logger_disabled = gen_server:call(eradius_log, get_state), - - set_env(LogOn1), - ?match(false, filelib:is_file(LogFile1)), - ok = eradius_log:reconfigure(), - ?match(true, logger_disabled /= gen_server:call(eradius_log, get_state)), - ?match(true, filelib:is_file(LogFile1)), - - %% via eradius:config_change/3 - set_env(LogOff), - eradius:config_change([], LogOff, []), - logger_disabled = gen_server:call(eradius_log, get_state), - - set_env(LogOn0), - eradius:config_change([], LogOn1, []), - ?match(true, logger_disabled /= gen_server:call(eradius_log, get_state)), - - %% check default value for logging - application:unset_env(eradius, logging), - eradius:config_change([], [], [logging]), - logger_disabled = gen_server:call(eradius_log, get_state), - - ok. - -set_env(Config) -> - [application:set_env(eradius, Env, Value) || {Env, Value} <- Config]. - -apply_conf(Config) -> - set_env(Config), - eradius_server_mon:reconfigure(). - -generate_ip_list_test(_) -> - ?equal([{192, 168, 11, 148}, {192, 168, 11, 149}, {192, 168, 11, 150}, {192, 168, 11, 151}], - eradius_config:generate_ip_list({192, 168, 11, 150}, "30")), - eradius_config:generate_ip_list({192, 168, 11, 150}, 24), - ?equal(256, length(eradius_config:generate_ip_list({192, 168, 11, 150}, 24))), - ?equal(2048, length(eradius_config:generate_ip_list({192, 168, 11, 10}, 21))), - ?match({invalid, _}, eradius_config:generate_ip_list({192, 168, 11, 150}, "34")), - ok. diff --git a/test/eradius_lib_SUITE.erl b/test/eradius_lib_SUITE.erl index 8dc884c9..1b158973 100644 --- a/test/eradius_lib_SUITE.erl +++ b/test/eradius_lib_SUITE.erl @@ -19,9 +19,11 @@ %% DEALINGS IN THE SOFTWARE. -module(eradius_lib_SUITE). +-include_lib("stdlib/include/assert.hrl"). -include("eradius_lib.hrl"). -include("eradius_dict.hrl"). -include("eradius_test.hrl"). + -compile(export_all). -define(SALT, <<171,213>>). @@ -33,8 +35,14 @@ -define(CIPHER_TEXT, <<171,213,166,95,152,126,124,120,86,10,78,216,190,216,26,87,55,15>>). -define(ENC_PASSWORD, 186,128,194,207,68,25,190,19,23,226,48,206,244,143,56,238). -define(ENC_PASSWORD_ASCEND, 222,170,194,83,115,231,228,55,75,17,20,6,198,33,112,197). --define(PDU, #radius_request{ reqid = 1, secret = ?SECRET, authenticator = ?REQUEST_AUTHENTICATOR }). - +-define(ENC_REQ, #{reqid => 1, + secret => ?SECRET, + authenticator => ?REQUEST_AUTHENTICATOR, + attrs => []}). +-define(DEC_REQ, #{reqid => 1, + secret => ?SECRET, + request_authenticator => ?REQUEST_AUTHENTICATOR, + attrs => []}). %% test callbacks all() -> [ipv6prefix, @@ -62,10 +70,11 @@ all() -> [ipv6prefix, init_per_suite(Config) -> Config. end_per_suite(_Config) -> ok. -init_per_testcase(Test, Config) when Test == dec_vendor_integer_t - orelse Test == dec_vendor_string_t - orelse Test == dec_vendor_ipv4_t - orelse Test == vendor_attribute_id_conflict_test -> +init_per_testcase(Test, Config) when + Test == dec_vendor_integer_t orelse + Test == dec_vendor_string_t orelse + Test == dec_vendor_ipv4_t orelse + Test == vendor_attribute_id_conflict_test -> application:set_env(eradius, tables, [dictionary]), eradius_dict:start_link(), eradius_dict:unload_tables([dictionary_3gpp]), @@ -85,80 +94,80 @@ ipv6prefix(_Config) -> ok. ipv6prefix_enc_dec(Prefix) -> - Bin = eradius_lib:encode_value(ipv6prefix, Prefix), - eradius_lib:decode_value(Bin, ipv6prefix). + Bin = eradius_req:encode_value(ipv6prefix, Prefix), + eradius_req:decode_value(Bin, ipv6prefix). salt_encrypt_test(_) -> - ?equal(?CIPHER_TEXT, eradius_lib:salt_encrypt(?SALT, ?SECRET, ?REQUEST_AUTHENTICATOR, << ?PLAIN_TEXT >>)). + ?equal(?CIPHER_TEXT, eradius_req:salt_encrypt(?SALT, ?SECRET, ?REQUEST_AUTHENTICATOR, << ?PLAIN_TEXT >>)). salt_decrypt_test(_) -> - ?equal(<< ?PLAIN_TEXT >>, eradius_lib:salt_decrypt(?SECRET, ?REQUEST_AUTHENTICATOR, ?CIPHER_TEXT)). + ?equal(<< ?PLAIN_TEXT >>, eradius_req:salt_decrypt(?SECRET, ?REQUEST_AUTHENTICATOR, ?CIPHER_TEXT)). scramble_enc_test(_) -> - ?equal(<< ?ENC_PASSWORD >>, eradius_lib:scramble(?SECRET, ?REQUEST_AUTHENTICATOR, << ?PLAIN_TEXT >>)). + ?equal(<< ?ENC_PASSWORD >>, eradius_req:scramble(?SECRET, ?REQUEST_AUTHENTICATOR, << ?PLAIN_TEXT >>)). scramble_dec_test(_) -> - ?equal(?PLAIN_TEXT_PADDED, eradius_lib:scramble(?SECRET, ?REQUEST_AUTHENTICATOR, << ?ENC_PASSWORD >>)). + ?equal(?PLAIN_TEXT_PADDED, eradius_req:scramble(?SECRET, ?REQUEST_AUTHENTICATOR, << ?ENC_PASSWORD >>)). ascend_enc_test(_) -> - ?equal(<< ?ENC_PASSWORD_ASCEND >>, eradius_lib:ascend(?SECRET, ?REQUEST_AUTHENTICATOR, << ?PLAIN_TEXT >>)). + ?equal(<< ?ENC_PASSWORD_ASCEND >>, eradius_req:ascend(?SECRET, ?REQUEST_AUTHENTICATOR, << ?PLAIN_TEXT >>)). ascend_dec_test(_) -> - ?equal(?PLAIN_TEXT_PADDED, eradius_lib:ascend(?SECRET, ?REQUEST_AUTHENTICATOR, << ?ENC_PASSWORD_ASCEND >>)). + ?equal(?PLAIN_TEXT_PADDED, eradius_req:ascend(?SECRET, ?REQUEST_AUTHENTICATOR, << ?ENC_PASSWORD_ASCEND >>)). enc_simple_test(_) -> L = length(?USER) + 2, - ?equal(<< ?RUser_Name, L:8, ?USER >>, eradius_lib:encode_attribute(?PDU, #attribute{id = ?RUser_Name, type = string, enc = no}, << ?USER >>)). + ?equal(<< ?RUser_Name, L:8, ?USER >>, eradius_req:encode_attribute(?ENC_REQ, #attribute{id = ?RUser_Name, type = string, enc = no}, << ?USER >>)). enc_scramble_test(_) -> L = 16 + 2, - ?equal(<< ?RUser_Passwd, L:8, ?ENC_PASSWORD >>, eradius_lib:encode_attribute(?PDU, #attribute{id = ?RUser_Passwd, type = string, enc = scramble}, << ?PLAIN_TEXT >>)). + ?equal(<< ?RUser_Passwd, L:8, ?ENC_PASSWORD >>, eradius_req:encode_attribute(?ENC_REQ, #attribute{id = ?RUser_Passwd, type = string, enc = scramble}, << ?PLAIN_TEXT >>)). enc_salt_test(_) -> L = 16 + 4, - << ?RUser_Passwd, L:8, Enc/binary >> = eradius_lib:encode_attribute(?PDU, #attribute{id = ?RUser_Passwd, type = string, enc = salt_crypt}, << ?PLAIN_TEXT >>), + << ?RUser_Passwd, L:8, Enc/binary >> = eradius_req:encode_attribute(?ENC_REQ, #attribute{id = ?RUser_Passwd, type = string, enc = salt_crypt}, << ?PLAIN_TEXT >>), %% need to decrypt to verfiy due to salt - ?equal(<< ?PLAIN_TEXT >>, eradius_lib:salt_decrypt(?SECRET, ?REQUEST_AUTHENTICATOR, Enc)). + ?equal(<< ?PLAIN_TEXT >>, eradius_req:salt_decrypt(?SECRET, ?REQUEST_AUTHENTICATOR, Enc)). enc_ascend_test(_) -> L = 16 + 2, - ?equal(<< ?RUser_Passwd, L:8, ?ENC_PASSWORD_ASCEND >>, eradius_lib:encode_attribute(?PDU, #attribute{id = ?RUser_Passwd, type = string, enc = ascend}, << ?PLAIN_TEXT >>)). + ?equal(<< ?RUser_Passwd, L:8, ?ENC_PASSWORD_ASCEND >>, eradius_req:encode_attribute(?ENC_REQ, #attribute{id = ?RUser_Passwd, type = string, enc = ascend}, << ?PLAIN_TEXT >>)). enc_vendor_test(_) -> L = length(?USER), E = << ?RVendor_Specific, (L+8):8, 18681:32, 1:8, (L+2):8, ?USER >>, - ?equal(E, eradius_lib:encode_attribute(?PDU, #attribute{id = {18681,1}, type = string, enc = no}, << ?USER >>)). + ?equal(E, eradius_req:encode_attribute(?ENC_REQ, #attribute{id = {18681,1}, type = string, enc = no}, << ?USER >>)). enc_vendor_octet_test(_) -> E = << ?RVendor_Specific, (4+8):8, 311:32, 7:8, (4+2):8, 7:32 >>, - ?equal(E, eradius_lib:encode_attribute(?PDU, #attribute{id = {311,7}, type = octets, enc = no}, 7)). + ?equal(E, eradius_req:encode_attribute(?ENC_REQ, #attribute{id = {311,7}, type = octets, enc = no}, 7)). -decode_attribute(A, B, C) -> - eradius_lib:decode_attribute(A, B, C, 0, #decoder_state{}). +decode_attribute(TLV, Req, Attr) -> + eradius_req:decode_attribute(TLV, Attr, 0, Req). dec_simple_integer_test(_) -> - State = decode_attribute(<<0,0,0,1>>, ?PDU, #attribute{id = 40, type = integer, enc = no}), - [{_, 1}] = State#decoder_state.attrs. + State = decode_attribute(<<0,0,0,1>>, ?DEC_REQ, #attribute{id = 40, type = integer, enc = no}), + ?assertMatch(#{attrs := [{_, 1}]}, State). dec_simple_string_test(_) -> - State = decode_attribute(<<"29113">>, ?PDU, #attribute{id = 44, type = string, enc = no}), - [{_, <<"29113">>}] = State#decoder_state.attrs. + State = decode_attribute(<<"29113">>, ?DEC_REQ, #attribute{id = 44, type = string, enc = no}), + ?assertMatch(#{attrs := [{_, <<"29113">>}]}, State). dec_simple_ipv4_test(_) -> - State = decode_attribute(<<10,33,0,1>>, ?PDU, #attribute{id = 4, type = ipaddr, enc = no}), - [{_, {10,33,0,1}}] = State#decoder_state.attrs. + State = decode_attribute(<<10,33,0,1>>, ?DEC_REQ, #attribute{id = 4, type = ipaddr, enc = no}), + ?assertMatch(#{attrs := [{_, {10,33,0,1}}]}, State). dec_vendor_integer_t(_) -> - State = decode_attribute(<<0,0,40,175,3,6,0,0,0,0>>, ?PDU, #attribute{id = ?RVendor_Specific, type = octets, enc = no}), - [{_, <<0, 0, 0, 0>>}] = State#decoder_state.attrs. + State = decode_attribute(<<0,0,40,175,3,6,0,0,0,0>>, ?DEC_REQ, #attribute{id = ?RVendor_Specific, type = octets, enc = no}), + ?assertMatch(#{attrs := [{_, <<0, 0, 0, 0>>}]}, State). dec_vendor_string_t(_) -> - State = decode_attribute(<<0,0,40,175,8,7,"23415">>, ?PDU, #attribute{id = ?RVendor_Specific, type = octets, enc = no}), - [{_, <<"23415">>}] = State#decoder_state.attrs. + State = decode_attribute(<<0,0,40,175,8,7,"23415">>, ?DEC_REQ, #attribute{id = ?RVendor_Specific, type = octets, enc = no}), + ?assertMatch(#{attrs := [{_, <<"23415">>}]}, State). dec_vendor_ipv4_t(_) -> - State = decode_attribute(<<0,0,40,175,6,6,212,183,144,246>>, ?PDU, #attribute{id = ?RVendor_Specific, type = octets, enc = no}), - [{_, <<212,183,144,246>>}] = State#decoder_state.attrs. + State = decode_attribute(<<0,0,40,175,6,6,212,183,144,246>>, ?DEC_REQ, #attribute{id = ?RVendor_Specific, type = octets, enc = no}), + ?assertMatch(#{attrs := [{_, <<212,183,144,246>>}]}, State). vendor_attribute_id_conflict_test(_) -> #attribute{} = eradius_dict:lookup(attribute, 52), diff --git a/test/eradius_logtest.erl b/test/eradius_logtest.erl deleted file mode 100644 index 125422ab..00000000 --- a/test/eradius_logtest.erl +++ /dev/null @@ -1,134 +0,0 @@ -%% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> - -%% Permission is hereby granted, free of charge, to any person obtaining a -%% copy of this software and associated documentation files (the "Software"), -%% to deal in the Software without restriction, including without limitation -%% the rights to use, copy, modify, merge, publish, distribute, sublicense, -%% and/or sell copies of the Software, and to permit persons to whom the -%% Software is furnished to do so, subject to the following conditions: - -%% The above copyright notice and this permission notice shall be included in -%% all copies or substantial portions of the Software. - -%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -%% DEALINGS IN THE SOFTWARE. - --module(eradius_logtest). - --export([start/0, test/0, radius_request/3, validate_arguments/1, test_client/0, test_client/1, test_proxy/0, test_proxy/1]). --import(eradius_lib, [get_attr/2]). - --include_lib("eradius/include/eradius_lib.hrl"). --include_lib("eradius/include/eradius_dict.hrl"). --include_lib("eradius/include/dictionary.hrl"). --include_lib("eradius/include/dictionary_3gpp.hrl"). - --define(ALLOWD_USERS, [undefined, <<"user">>, <<"user@domain">>, <<"proxy_test">>]). --define(SECRET, <<"secret">>). --define(SECRET2, <<"proxy_secret">>). --define(SECRET3, <<"test_secret">>). - --define(CLIENT_REQUESTS_COUNT, 1). --define(CLIENT_PROXY_REQUESTS_COUNT, 4). - --define(NAS1_ACCESS_REQS, 1). --define(NAS2_ACCESS_REQS, 4). - -start() -> - application:load(eradius), - ProxyConfig = [{default_route, {eradius_test_lib:localhost(tuple), 1813, ?SECRET}}, - {options, [{type, realm}, {strip, true}, {separator, "@"}]}, - {routes, [{"test", {eradius_test_lib:localhost(tuple), 1815, ?SECRET3}} - ]} - ], - Config = [{radius_callback, eradius_logtest}, - {servers, [{root, {eradius_test_lib:localhost(ip), [1812, 1813]}}, - {test, {eradius_test_lib:localhost(ip), [1815]}}, - {proxy, {eradius_test_lib:localhost(ip), [11812, 11813]}} - ]}, - {session_nodes, [node()]}, - {root, [ - { {eradius_logtest, "root", [] }, [{"127.0.0.1/24", ?SECRET, [{nas_id, <<"Test_Nas_Id">>}]}] } - ]}, - {test, [ - { {eradius_logtest, "test", [] }, [{eradius_test_lib:localhost(ip), ?SECRET3, [{nas_id, <<"Test_Nas_Id_test">>}]}] } - ]}, - {proxy, [ - { {eradius_proxy, "proxy", ProxyConfig }, [{eradius_test_lib:localhost(ip), ?SECRET2, [{nas_id, <<"Test_Nas_proxy">>}]}] } - ]} - ], - [application:set_env(eradius, Key, Value) || {Key, Value} <- Config], - {ok, _} = application:ensure_all_started(eradius), - spawn(fun() -> - eradius:modules_ready([?MODULE, eradius_proxy]), - timer:sleep(infinity) - end), - ok. - -test() -> - %% application:set_env(lager, handlers, [{lager_journald_backend, []}]), - eradius_logtest:start(), - eradius_logtest:test_client(), - eradius_logtest:test_proxy(), - ok. - -radius_request(#radius_request{cmd = request} = Request, _NasProp, _) -> - UserName = get_attr(Request, ?User_Name), - case lists:member(UserName, ?ALLOWD_USERS) of - true -> - {reply, #radius_request{cmd = accept}}; - false -> - {reply, #radius_request{cmd = reject}} - end; - -radius_request(#radius_request{cmd = accreq}, _NasProp, _) -> - {reply, #radius_request{cmd = accresp}}. - -validate_arguments(_Args) -> true. - -test_client() -> - test_client(request). - -test_client(Command) -> - eradius_dict:load_tables([dictionary, dictionary_3gpp]), - Request = eradius_lib:set_attributes(#radius_request{cmd = Command, msg_hmac = true}, attrs("user")), - send_request(eradius_test_lib:localhost(tuple), 1813, ?SECRET, Request). - -test_proxy() -> - test_proxy(request). - -test_proxy(Command) -> - eradius_dict:load_tables([dictionary, dictionary_3gpp]), - send_request(eradius_test_lib:localhost(tuple), 11813, ?SECRET2, #radius_request{cmd = Command}), - Request = eradius_lib:set_attributes(#radius_request{cmd = Command, msg_hmac = true}, attrs("proxy_test")), - send_request(eradius_test_lib:localhost(tuple), 11813, ?SECRET2, Request), - Request2 = eradius_lib:set_attributes(#radius_request{cmd = Command, msg_hmac = true}, attrs("user@test")), - send_request(eradius_test_lib:localhost(tuple), 11813, ?SECRET2, Request2), - Request3 = eradius_lib:set_attributes(#radius_request{cmd = Command, msg_hmac = true}, attrs("user@domain@test")), - send_request(eradius_test_lib:localhost(tuple), 11813, ?SECRET2, Request3). - -send_request(Ip, Port, Secret, Request) -> - case eradius_client:send_request({Ip, Port, Secret}, Request) of - {ok, Result, Auth} -> - eradius_lib:decode_request(Result, Secret, Auth); - Error -> - Error - end. - -attrs(User) -> - [{?NAS_Port, 8888}, - {?User_Name, User}, - {?NAS_IP_Address, {88,88,88,88}}, - {?Calling_Station_Id, "0123456789"}, - {?Service_Type, 2}, - {?Framed_Protocol, 7}, - {30,"some.id.com"}, %Called-Station-Id - {61,18}, %NAS_PORT_TYPE - {{10415,1}, "1337"}, %X_3GPP-IMSI - {{127,42},18} %Unbekannte ID - ]. diff --git a/test/eradius_metrics_SUITE.erl b/test/eradius_metrics_SUITE.erl index a94dff65..834b771e 100644 --- a/test/eradius_metrics_SUITE.erl +++ b/test/eradius_metrics_SUITE.erl @@ -21,59 +21,35 @@ -module(eradius_metrics_SUITE). -compile(export_all). +-include_lib("stdlib/include/assert.hrl"). -include_lib("eradius/include/eradius_lib.hrl"). -include_lib("eradius/include/eradius_dict.hrl"). -include_lib("eradius/include/dictionary.hrl"). -include("eradius_test.hrl"). +-define(SERVER, ?MODULE). -define(SECRET, <<"secret">>). -define(ATTRS_GOOD, [{?NAS_Identifier, "good"}, {?RStatus_Type, ?RStatus_Type_Start}]). -define(ATTRS_BAD, [{?NAS_Identifier, "bad"}]). -define(ATTRS_ERROR, [{?NAS_Identifier, "error"}]). -define(ATTRS_AS_RECORD, [{#attribute{id = ?RStatus_Type}, ?RStatus_Type_Start}]). --define(LOCALHOST, eradius_test_lib:localhost(atom)). + +%%%=================================================================== +%%% Setup +%%%=================================================================== %% test callbacks all() -> [good_requests, bad_requests, error_requests, request_with_attrs_as_record]. init_per_suite(Config) -> + logger:set_primary_config(level, debug), application:load(eradius), - EradiusConfig = [{radius_callback, ?MODULE}, - {servers, [{good, {eradius_test_lib:localhost(ip), [1812]}}, %% for 'positive' responses, e.g. access accepts - {bad, {eradius_test_lib:localhost(ip), [1813]}}, %% for 'negative' responses, e.g. coa naks - {error, {eradius_test_lib:localhost(ip), [1814]}} %% here things go wrong, e.g. duplicate requests - ]}, - {session_nodes, [node()]}, - {servers_pool, - [{test_pool, [{eradius_test_lib:localhost(tuple), 1814, ?SECRET}, - {eradius_test_lib:localhost(tuple), 1813, ?SECRET}, - {eradius_test_lib:localhost(tuple), 1812, ?SECRET}]}] - }, - {good, [ - { {"good", [] }, [{"127.0.0.2", ?SECRET, [{nas_id, <<"good_nas">>}]}] } - ]}, - {bad, [ - { {"bad", [] }, [{"127.0.0.2", ?SECRET, [{nas_id, <<"bad_nas">>}]}] } - ]}, - {error, [ - { {"error", [] }, [{"127.0.0.2", ?SECRET, [{nas_id, <<"error_nas">>}]}] } - ]}, - {tables, [dictionary]}, - {client_ip, {127,0,0,2}}, - {client_ports, 20}, - {counter_aggregator, false}, - {server_status_metrics_enabled, true} - ], - [application:set_env(eradius, Key, Value) || {Key, Value} <- EradiusConfig], - application:set_env(prometheus, collectors, [eradius_prometheus_collector]), - %% prometheus is not included directly to eradius but prometheus_eradius_collector - %% should include it - application:ensure_all_started(prometheus), + application:set_env(eradius, unreachable_timeout, 2), {ok, _} = application:ensure_all_started(eradius), - spawn(fun() -> - eradius:modules_ready([?MODULE]), - timer:sleep(infinity) - end), + + ok = start_client(Config), + ok = start_servers(Config), + Config. end_per_suite(_Config) -> @@ -82,10 +58,65 @@ end_per_suite(_Config) -> ok. init_per_testcase(_, Config) -> - eradius_client_mngr:init_server_status_metrics(), + application:stop(prometheus), + {ok, _} = application:ensure_all_started(prometheus), + eradius_metrics_prometheus:init(#{}), Config. -%% tests +%%%=================================================================== +%%% Helper +%%%=================================================================== + +start_client(_Config) -> + Backend = inet, Family = ipv4, + ClientConfig = + #{inet_backend => Backend, + family => eradius_test_lib:inet_family(Family), + ip => eradius_test_lib:localhost(Family, native), + servers => #{good => #{ip => eradius_test_lib:localhost(Family, native), + port => 1812, + secret => ?SECRET, + retries => 3}, + bad => #{ip => eradius_test_lib:localhost(Family, native), + port => 1813, + secret => ?SECRET, + retries => 3}, + error => #{ip => eradius_test_lib:localhost(Family, native), + port => 1814, + secret => ?SECRET, + retries => 3} + }, + metrics_callback => fun eradius_metrics_prometheus:client_metrics_callback/3 + }, + case eradius_client_mngr:start_client({local, ?SERVER}, ClientConfig) of + {ok, _} -> ok; + {error, {already_started, _}} -> ok + end. + +start_servers(_Config) -> + Family = ipv4, + + SrvOpts = + fun(Name, NasId) -> + #{handler => {?MODULE, []}, + server_name => Name, + metrics_callback => fun eradius_metrics_prometheus:server_metrics_callback/3, + clients => #{eradius_test_lib:localhost(Family, native) => + #{secret => ?SECRET, client => NasId}} + } + end, + eradius:start_server( + eradius_test_lib:localhost(Family, native), 1812, SrvOpts(good, <<"good_nas">>)), + eradius:start_server( + eradius_test_lib:localhost(Family, native), 1813, SrvOpts(bad, <<"bad_nas">>)), + eradius:start_server( + eradius_test_lib:localhost(Family, native), 1814, SrvOpts(error, <<"error_nas">>)), + ok. + +%%%=================================================================== +%%% tests +%%%=================================================================== + good_requests(_Config) -> Requests = [{request, access, access_accept}, {accreq, accounting, accounting}, @@ -107,66 +138,81 @@ error_requests(_Config) -> check_single_request(error, request, access, access_accept). request_with_attrs_as_record(_Config) -> - ok = send_request(accreq, eradius_test_lib:localhost(tuple), 1812, ?ATTRS_AS_RECORD, [{server_name, good}, {client_name, test_records}]), - ok = check_metric(accreq, client_accounting_requests_total, [{server_name, good}, {client_name, test_records}, {acct_type, start}], 1). + ok = send_request(good, accreq, ?ATTRS_AS_RECORD, + #{server_name => good, client_name => test_records}), + check_metric(accreq, eradius_client_accounting_requests_total, [{"server_name", good}, {"acct_type", start}], 1). %% helpers check_single_request(good, EradiusRequestType, _RequestType, _ResponseType) -> - ok = send_request(EradiusRequestType, eradius_test_lib:localhost(tuple), 1812, ?ATTRS_GOOD, [{server_name, good}, {client_name, test}]), - ok = check_metric(client_access_requests_total, [{server_name, good}], 1), - ok = check_metric_multi(EradiusRequestType, client_accounting_requests_total, [{server_name, good}], 1), - ok = check_metric_multi({bad_type, EradiusRequestType}, client_accounting_requests_total, [{server_name, good}, {acct_type, bad_type}], 0), - ok = check_metric(EradiusRequestType, client_accounting_requests_total, [{server_name, good}, {acct_type, start}], 1), - ok = check_metric(EradiusRequestType, client_accounting_requests_total, [{server_name, good}, {acct_type, stop}], 0), - ok = check_metric(EradiusRequestType, client_accounting_requests_total, [{server_name, good}, {acct_type, update}], 0), - ok = check_metric(client_accept_responses_total, [{server_name, good}], 1), - ok = check_metric(accept_responses_total, [{server_name, good}], 1), - ok = check_metric(access_requests_total, [{server_name, good}], 1), - ok = check_metric(server_status, true, [eradius_test_lib:localhost(tuple), 1812]); + ok = send_request(good, EradiusRequestType, ?ATTRS_GOOD, + #{server_name => good, client_name => test}), + + Metrics = prometheus_text_format:format(default), + ERadM = re:run(Metrics, "^eradius.*", [multiline, global, {capture, all, binary}]), + ct:pal("Metrics:~n~p~n", [ERadM]), + + check_metric(eradius_client_access_requests_total, [{"server_name", good}], 1), + check_metric_multi(EradiusRequestType, eradius_client_accounting_requests_total, [{"server_name", good}], 1), + check_metric_multi({bad_type, EradiusRequestType}, eradius_client_accounting_requests_total, [{"server_name", good}, {"acct_type", bad_type}], 0), + check_metric(EradiusRequestType, eradius_client_accounting_requests_total, [{"server_name", good}, {"acct_type", start}], 1), + check_metric(EradiusRequestType, eradius_client_accounting_requests_total, [{"server_name", good}, {"acct_type", stop}], 0), + check_metric(EradiusRequestType, eradius_client_accounting_requests_total, [{"server_name", good}, {"acct_type", update}], 0), + check_metric(eradius_client_accept_responses_total, [{"server_name", good}], 1), + check_metric(eradius_accept_responses_total, [{"server_name", good}], 1), + check_metric(eradius_access_requests_total, [{"server_name", good}], 1), + check_metric(eradius_server_status, true, [eradius_test_lib:localhost(ipv4, native), 1812]); check_single_request(bad, EradiusRequestType, _RequestType, _ResponseType) -> - ok = send_request(EradiusRequestType, eradius_test_lib:localhost(tuple), 1813, ?ATTRS_BAD, [{server_name, bad}, {client_name, test}]), - ok = check_metric(client_access_requests_total, [{server_name, bad}], 1), - ok = check_metric(client_reject_responses_total, [{server_name, bad}], 1), - ok = check_metric(access_requests_total, [{server_name, bad}], 1), - ok = check_metric(reject_responses_total, [{server_name, bad}], 1), - ok = check_metric(server_status, true, [eradius_test_lib:localhost(tuple), 1813]); + ok = send_request(bad, EradiusRequestType, ?ATTRS_BAD, + #{server_name => bad, client_name => test}), + check_metric(eradius_client_access_requests_total, [{"server_name", bad}], 1), + check_metric(eradius_client_reject_responses_total, [{"server_name", bad}], 1), + check_metric(eradius_access_requests_total, [{"server_name", bad}], 1), + check_metric(eradius_reject_responses_total, [{"server_name", bad}], 1), + check_metric(eradius_server_status, true, [eradius_test_lib:localhost(ipv4, native), 1813]); check_single_request(error, EradiusRequestType, _RequestType, _ResponseType) -> - ok = send_request(EradiusRequestType, eradius_test_lib:localhost(tuple), 1814, ?ATTRS_ERROR, - [{server_name, error}, {client_name, test}, {timeout, 1000}, - {failover, [{eradius_test_lib:localhost(tuple), 1812, ?SECRET}]}]), - ok = check_metric(client_access_requests_total, [{server_name, error}], 1), - ok = check_metric(client_retransmissions_total, [{server_name, error}], 1), - ok = check_metric(access_requests_total, [{server_name, error}], 1), - ok = check_metric(accept_responses_total, [{server_name, error}], 1), - ok = check_metric(duplicated_requests_total, [{server_name, error}], 1), - ok = check_metric(client_requests_total, [{server_name, error}], 1), - ok = check_metric(requests_total, [{server_name, error}], 2), - ok = check_metric(server_status, false, [eradius_test_lib:localhost(tuple), 1812]), - ok = check_metric(server_status, false, [eradius_test_lib:localhost(tuple), 1813]), - ok = check_metric(server_status, true, [eradius_test_lib:localhost(tuple), 1814]), - ok = check_metric(server_status, undefined, [eradius_test_lib:localhost(tuple), 1815]). - + ok = send_request(error, EradiusRequestType, ?ATTRS_ERROR, + #{server_name => error, client_name => test, timeout => 100, + failover => []}), + check_metric(eradius_client_access_requests_total, [{"server_name", error}], 1), + check_metric(eradius_client_retransmissions_total, [{"server_name", error}], 1), + check_metric(eradius_access_requests_total, [{"server_name", error}], 1), + check_metric(eradius_accept_responses_total, [{"server_name", error}], 1), + check_metric(eradius_duplicated_requests_total, [{"server_name", error}], 1), + check_metric(eradius_client_requests_total, [{"server_name", error}], 1), + check_metric(eradius_requests_total, [{"server_name", error}], 2), + check_metric(eradius_server_status, undefined, [eradius_test_lib:localhost(ipv4, native), 1812]), + check_metric(eradius_server_status, undefined, [eradius_test_lib:localhost(ipv4, native), 1813]), + check_metric(eradius_server_status, true, [eradius_test_lib:localhost(ipv4, native), 1814]), + ok. check_total_requests(good, N) -> - ok = check_metric(requests_total, [{server_name, good}], N), - ok = check_metric(replies_total, [{server_name, good}], N); + check_metric(eradius_requests_total, [{"server_name", good}], N), + check_metric(eradius_replies_total, [{"server_name", good}], N); check_total_requests(bad, N) -> - ok = check_metric(requests_total, [{server_name, bad}], N), - ok = check_metric(replies_total, [{server_name, bad}], N). + check_metric(eradius_requests_total, [{"server_name", bad}], N), + check_metric(eradius_replies_total, [{"server_name", bad}], N). -check_metric_multi({bad_type, accreq}, Id, Labels, _) -> - case eradius_prometheus_collector:fetch_counter(Id, Labels) of - [] -> - ok; - _ -> - {error, Id, Labels} - end; +check_metric_multi({bad_type, accreq}, Id, Labels, _Count) -> + Values = prometheus_counter:values(default, Id), + Filtered = + lists:filter( + fun({ValueLabels, _}) -> Labels -- ValueLabels =:= [] end, + Values), + ct:pal("check_metric-accreg-bad: ~p, ~p~nFetch: ~p~nFilteredL ~p~n", + [Id, Labels, Values, Filtered]), + ?assertEqual([], Filtered); check_metric_multi(accreq, Id, Labels, Count) -> - case eradius_prometheus_collector:fetch_counter(Id, Labels) of - [{Count, _} | _] -> - ok; - _ -> - {error, Id, Count} + Values = prometheus_counter:values(default, Id), + Filtered = + lists:filter( + fun({ValueLabels, _}) -> Labels -- ValueLabels =:= [] end, + Values), + ct:pal("check_metric-accreg-#1: ~p, ~p~nFetch: ~p~nFilteredL ~p~n", + [Id, Labels, Values, Filtered]), + case Filtered of + [{_, Count}|_] -> ok; + [] when Count =:= 0 -> ok; + _ -> ?assertMatch([{_, Count}|_], Filtered) end; check_metric_multi(_, _, _, _) -> ok. @@ -176,50 +222,56 @@ check_metric(accreq, Id, Labels, Count) -> check_metric(_, _, _, _) -> ok. -check_metric(server_status, Value, Labels) -> - ?equal(Value, prometheus_boolean:value(server_status, Labels)), - ok; +check_metric(eradius_server_status = Id, Value, LabelValues) -> + ct:pal("check_metric-#0 ~p: ~p, ~p", [Id, LabelValues, Value]), + ?assertEqual(Value, prometheus_boolean:value(Id, LabelValues)); check_metric(Id, Labels, Count) -> - case eradius_prometheus_collector:fetch_counter(Id, Labels) of - [{Count, _}] -> - ok; - _ -> - {error, Id, Count} + Values = prometheus_counter:values(default, Id), + Filtered = + lists:filter( + fun({ValueLabels, _}) -> Labels -- ValueLabels =:= [] end, + Values), + ct:pal("check_metric-#1: ~p, ~p, ~p~nFetch: ~p~nFilteredL ~p~n", + [Id, Labels, Count, Values, Filtered]), + case Filtered of + [{_, Count}|_] -> ok; + [] when Count =:= 0 -> ok; + _ -> ?assertMatch([{_, Count}|_], Filtered) end. -send_request(Command, IP, Port, Attrs, Opts) -> +send_request(ServerName, Command, Attrs, Opts) -> ok = eradius_dict:load_tables([dictionary]), - Request = eradius_lib:set_attributes(#radius_request{cmd = Command}, Attrs), - send_radius_request(IP, Port, ?SECRET, Request, Opts). + Req0 = eradius_req:new(Command), + Req = eradius_req:set_attrs(Attrs, Req0), + send_radius_request(ServerName, Req, Opts). -send_radius_request(Ip, Port, Secret, Request, Opts) -> - case eradius_client:send_request({Ip, Port, Secret}, Request, Opts) of - {ok, _Result, _Auth} -> +send_radius_request(ServerName, Req, Opts) -> + case eradius_client:send_request(?SERVER, ServerName, Req, Opts) of + {{ok, _Result}, _ReqN} -> ok; Error -> Error end. %% RADIUS NAS callbacks for 'good' requests -radius_request(#radius_request{cmd = request}, #nas_prop{nas_id = <<"good_nas">>}, _) -> - {reply, #radius_request{cmd = accept}}; -radius_request(#radius_request{cmd = accreq}, #nas_prop{nas_id = <<"good_nas">>}, _) -> - {reply, #radius_request{cmd = accresp}}; -radius_request(#radius_request{cmd = coareq}, #nas_prop{nas_id = <<"good_nas">>}, _) -> - {reply, #radius_request{cmd = coaack}}; -radius_request(#radius_request{cmd = discreq}, #nas_prop{nas_id = <<"good_nas">>}, _) -> - {reply, #radius_request{cmd = discack}}; +radius_request(#{cmd := request, client := <<"good_nas">>} = Req, _) -> + {reply, Req#{cmd := accept}}; +radius_request(#{cmd := accreq, client := <<"good_nas">>} = Req, _) -> + {reply, Req#{cmd := accresp}}; +radius_request(#{cmd := coareq, client := <<"good_nas">>} = Req, _) -> + {reply, Req#{cmd := coaack}}; +radius_request(#{cmd := discreq, client := <<"good_nas">>} = Req, _) -> + {reply, Req#{cmd := discack}}; %% RADIUS NAS callbacks for 'bad' requests -radius_request(#radius_request{cmd = request}, #nas_prop{nas_id = <<"bad_nas">>}, _) -> - {reply, #radius_request{cmd = reject}}; -radius_request(#radius_request{cmd = coareq}, #nas_prop{nas_id = <<"bad_nas">>}, _) -> - {reply, #radius_request{cmd = coanak}}; -radius_request(#radius_request{cmd = discreq}, #nas_prop{nas_id = <<"bad_nas">>}, _) -> - {reply, #radius_request{cmd = discnak}}; +radius_request(#{cmd := request, client := <<"bad_nas">>} = Req, _) -> + {reply, Req#{cmd := reject}}; +radius_request(#{cmd := coareq, client := <<"bad_nas">>} = Req, _) -> + {reply, Req#{cmd := coanak}}; +radius_request(#{cmd := discreq, client := <<"bad_nas">>} = Req, _) -> + {reply, Req#{cmd := discnak}}; %% RADIUS NAS callbacks for 'error' requests -radius_request(#radius_request{cmd = request}, #nas_prop{nas_id = <<"error_nas">>}, _) -> - timer:sleep(1500), %% this will by default trigger one resend - {reply, #radius_request{cmd = accept}}. - +radius_request(#{cmd := request, client := <<"error_nas">>} = Req, _) -> + timer:sleep(150), %% this will by default trigger one resend + {reply, Req#{cmd := accept}}. diff --git a/test/eradius_proxy_SUITE.erl b/test/eradius_proxy_SUITE.erl deleted file mode 100644 index 85c6a2e2..00000000 --- a/test/eradius_proxy_SUITE.erl +++ /dev/null @@ -1,166 +0,0 @@ -%% Copyright (c) 2010-2017 by Travelping GmbH <info@travelping.com> - -%% Permission is hereby granted, free of charge, to any person obtaining a -%% copy of this software and associated documentation files (the "Software"), -%% to deal in the Software without restriction, including without limitation -%% the rights to use, copy, modify, merge, publish, distribute, sublicense, -%% and/or sell copies of the Software, and to permit persons to whom the -%% Software is furnished to do so, subject to the following conditions: - -%% The above copyright notice and this permission notice shall be included in -%% all copies or substantial portions of the Software. - -%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -%% DEALINGS IN THE SOFTWARE. - --module(eradius_proxy_SUITE). --compile(export_all). --include("eradius_lib.hrl"). --include("dictionary.hrl"). --include("eradius_test.hrl"). - -all() -> [ - resolve_routes_test, - validate_arguments_test, - validate_options_test, - new_request_test, - get_key_test, - strip_test - ]. - -resolve_routes_test(_) -> - DefaultRoute = {eradius_test_lib:localhost(tuple), 1813, <<"secret">>}, - Prod = {eradius_test_lib:localhost(tuple), 1812, <<"prod">>}, - Test = {eradius_test_lib:localhost(tuple), 11813, <<"test">>}, - Dev = {eradius_test_lib:localhost(tuple), 11814, <<"dev">>}, - {ok, R1} = re:compile("prod"), - {ok, R2} = re:compile("test"), - {ok, R3} = re:compile("^dev_.*"), - Routes = [{R1, Prod}, {R2, Test, [{pool, test_pool}]}, {R3, Dev}], - %% default - ?equal({undefined, DefaultRoute}, eradius_proxy:resolve_routes(undefined, DefaultRoute, Routes,[])), - ?equal({"user", DefaultRoute}, eradius_proxy:resolve_routes(<<"user">>, DefaultRoute, Routes, [])), - ?equal({"user@prod", Prod}, eradius_proxy:resolve_routes(<<"user@prod">>, DefaultRoute, Routes,[])), - ?equal({"user@test", {Test, [{pool, test_pool}]}}, eradius_proxy:resolve_routes(<<"user@test">>, DefaultRoute, Routes,[])), - %% strip - Opts = [{strip, true}], - ?equal({"user", DefaultRoute}, eradius_proxy:resolve_routes(<<"user">>, DefaultRoute, Routes, Opts)), - ?equal({"user", Prod}, eradius_proxy:resolve_routes(<<"user@prod">>, DefaultRoute, Routes, Opts)), - ?equal({"user", {Test, [{pool, test_pool}]}}, eradius_proxy:resolve_routes(<<"user@test">>, DefaultRoute, Routes, Opts)), - ?equal({"user", Dev}, eradius_proxy:resolve_routes(<<"user@dev_server">>, DefaultRoute, Routes, Opts)), - ?equal({"user", DefaultRoute}, eradius_proxy:resolve_routes(<<"user@dev-server">>, DefaultRoute, Routes, Opts)), - - %% prefix - Opts1 = [{type, prefix}, {separator, "/"}], - ?equal({"user/example", DefaultRoute}, eradius_proxy:resolve_routes(<<"user/example">>, DefaultRoute, Routes, Opts1)), - ?equal({"test/user", {Test, [{pool, test_pool}]}}, eradius_proxy:resolve_routes(<<"test/user">>, DefaultRoute, Routes, Opts1)), - %% prefix and strip - Opts2 = Opts ++ Opts1, - ?equal({"example", DefaultRoute}, eradius_proxy:resolve_routes(<<"user/example">>, DefaultRoute, Routes, Opts2)), - ?equal({"user", {Test, [{pool, test_pool}]}}, eradius_proxy:resolve_routes(<<"test/user">>, DefaultRoute, Routes, Opts2)), - ok. - -validate_arguments_test(_) -> - GoodConfig = [{default_route, {eradius_test_lib:localhost(tuple), 1813, <<"secret">>}}, - {options, [{type, realm}, {strip, true}, {separator, "@"}]}, - {routes, [{"test_1", {eradius_test_lib:localhost(tuple), 1815, <<"secret1">>}, [{pool, test_pool}]}, - {"test_2", {<<"localhost">>, 1816, <<"secret2">>}} - ]} - ], - GoodOldConfig = [{default_route, {eradius_test_lib:localhost(tuple), 1813, <<"secret">>}, test_pool}, - {options, [{type, realm}, {strip, true}, {separator, "@"}]}, - {routes, [{"test_1", {eradius_test_lib:localhost(tuple), 1815, <<"secret1">>}, [{pool, test_pool}]}, - {"test_2", {<<"localhost">>, 1816, <<"secret2">>}} - ]} - ], - - BadConfig = [{default_route, {eradius_test_lib:localhost(tuple), 1813, <<"secret">>}}, - {options, [{type, abc}]} - ], - BadConfig1 = [{default_route, {eradius_test_lib:localhost(tuple), 0, <<"secret">>}}], - BadConfig2 = [{default_route, {abc, 123, <<"secret">>}}], - BadConfig3 = [{default_route, {eradius_test_lib:localhost(tuple), 1813, <<"secret">>}}, - {options, [{type, realm}, {strip, true}, {separator, "@"}]}, - {routes, [{"test_1", {wrong_ip, 1815, <<"secret1">>}}, - {"test_2", {<<"localhost">>, 1816, <<"secret2">>}} - ]}], - BadConfig4 = [{default_route, {eradius_test_lib:localhost(tuple), 1813, <<"secret">>}}, - {options, [{type, realm}, {strip, true}, {separator, "@"}, {timeout, "wrong"}]}, - {routes, [{"test", {wrong_ip, 1815, <<"secret1">>}}, - {"test_2", {<<"localhost">>, 1816, <<"secret2">>}} - ]}], - BadConfig5 = [{default_route, {eradius_test_lib:localhost(tuple), 1813, <<"secret">>}}, - {options, [{type, realm}, {strip, true}, {separator, "@"}, {retries, "wrong"}]}, - {routes, [{"test", {wrong_ip, 1815, <<"secret1">>}}, - {"test_2", {"localhost", 1816, <<"secret2">>}} - ]}], - BadConfig6 = [{default_route, {eradius_test_lib:localhost(tuple), 1813, <<"secret">>, [{pool, "wrong_pool"}]}}], - BadConfig7 = [{default_route, {eradius_test_lib:localhost(tuple), 1813, <<"secret">>}}, - {routes, [{"test", {wrong_ip, 1815, <<"secret1">>}, [{pool, "wrong_pool"}]}]}], - {Result, ConfigData} = eradius_proxy:validate_arguments(GoodConfig), - ?equal(true, Result), - {Valid, _} = eradius_proxy:validate_arguments(GoodOldConfig), - ?equal(true, Valid), - {routes, Routes} = lists:keyfind(routes, 1, ConfigData), - [{{CompiledRegexp_1, _, _, _, _}, _, _}, {{CompiledRegexp_2, _, _, _, _}, _, _}] = Routes, - ?equal(re_pattern, CompiledRegexp_1), - ?equal(re_pattern, CompiledRegexp_2), - ?equal(default_route, eradius_proxy:validate_arguments([])), - ?equal(options, eradius_proxy:validate_arguments(BadConfig)), - ?equal(default_route, eradius_proxy:validate_arguments(BadConfig1)), - ?equal(default_route, eradius_proxy:validate_arguments(BadConfig2)), - ?equal(routes, eradius_proxy:validate_arguments(BadConfig3)), - ?equal(options, eradius_proxy:validate_arguments(BadConfig4)), - ?equal(options, eradius_proxy:validate_arguments(BadConfig5)), - ?equal(default_route, eradius_proxy:validate_arguments(BadConfig6)), - ?equal(routes, eradius_proxy:validate_arguments(BadConfig7)), - ok. - -validate_options_test(_) -> - DefaultOptions = [{type, realm}, {strip, false}, {separator, "@"}], - ?equal(true, eradius_proxy:validate_options(DefaultOptions)), - ?equal(true, eradius_proxy:validate_options([{type, prefix}, {separator, "/"}, {strip, true}])), - ?equal(true, eradius_proxy:validate_options(DefaultOptions ++ [{timeout, 5000}])), - ?equal(true, eradius_proxy:validate_options(DefaultOptions ++ [{retries, 5}])), - ?equal(true, eradius_proxy:validate_options(DefaultOptions ++ [{timeout, 5000}, {retries, 5}])), - ?equal(false, eradius_proxy:validate_options([{type, unknow}])), - ?equal(false, eradius_proxy:validate_options([strip, abc])), - ?equal(false, eradius_proxy:validate_options([abc, abc])), - ?equal(false, eradius_proxy:validate_options(DefaultOptions ++ [{timeout, "5000"}])), - ?equal(false, eradius_proxy:validate_options(DefaultOptions ++ [{retries, "5"}])), - ok. - -new_request_test(_) -> - Req0 = #radius_request{}, - Req1 = eradius_lib:set_attr(Req0, ?User_Name, "user1"), - ?equal(Req0, eradius_proxy:new_request(Req0, "user", "user")), - ?equal(Req1, eradius_proxy:new_request(Req0, "user", "user1")), - ?equal(Req0, eradius_proxy:new_request(Req0, undefined, undefined)), - ok. - -get_key_test(_) -> - ?equal({"example", "user@example"}, eradius_proxy:get_key("user@example", realm, false, "@")), - ?equal({"user", "user/domain@example"}, eradius_proxy:get_key("user/domain@example", prefix, false, "/")), - ?equal({"example", "user"}, eradius_proxy:get_key("user@example", realm, true, "@")), - ?equal({"example", "user@domain"}, eradius_proxy:get_key("user@domain@example", realm, true, "@")), - ?equal({"user", "domain@example"}, eradius_proxy:get_key("user/domain@example", prefix, true, "/")), - ?equal({"user", "domain/domain2@example"}, eradius_proxy:get_key("user/domain/domain2@example", prefix, true, "/")), - ?equal({not_found, []}, eradius_proxy:get_key([], realm, false, "@")), - ok. - - -strip_test(_) -> - ?equal("user", eradius_proxy:strip("user", realm, false, "@")), - ?equal("user", eradius_proxy:strip("user", prefix, false, "@")), - ?equal("user", eradius_proxy:strip("user", realm, true, "@")), - ?equal("user", eradius_proxy:strip("user", prefix, true, "@")), - ?equal("user", eradius_proxy:strip("user@example", realm, true, "@")), - ?equal("user2@example", eradius_proxy:strip("user/user2@example", prefix, true, "/")), - ?equal("user/user2@example", eradius_proxy:strip("user/user2@example@roaming", realm, true, "@")), - ?equal("user/user2", eradius_proxy:strip("user/user2@example", realm, true, "@")), - ok. diff --git a/test/eradius_test_handler.erl b/test/eradius_test_handler.erl index 7427ac86..700cb22f 100644 --- a/test/eradius_test_handler.erl +++ b/test/eradius_test_handler.erl @@ -1,67 +1,81 @@ -module(eradius_test_handler). +-compile([export_all, nowarn_export_all]). -behaviour(eradius_server). --export([start/0, start/2, stop/0, send_request/1, send_request_failover/1, radius_request/3]). - -include("include/eradius_lib.hrl"). +-define(SERVER, ?MODULE). + start() -> start(inet, ipv4). start(Backend, Family) -> + application:stop(eradius), + application:load(eradius), - application:set_env(eradius, radius_callback, ?MODULE), - %% application:set_env(eradius, client_ip, eradius_test_lib:localhost(tuple)), - application:set_env(eradius, session_nodes, local), - application:set_env(eradius, one, - [{{"ONE", []}, [{eradius_test_lib:localhost(ip), "secret"}]}]), - application:set_env(eradius, two, - [{{"TWO", [{default_route, {{127, 0, 0, 2}, 1813, <<"secret">>}}]}, - [{eradius_test_lib:localhost(ip), "secret"}]}]), - application:set_env(eradius, servers, - [{one, {eradius_test_lib:localhost(ip), [1812]}}, - {two, {eradius_test_lib:localhost(ip), [1813]}}]), application:set_env(eradius, unreachable_timeout, 2), - application:set_env(eradius, servers_pool, - [{test_pool, - [{eradius_test_lib:localhost(tuple), 1812, "secret"}, - %% fake upstream server for fail-over - {eradius_test_lib:localhost(string), 1820, "secret"}]}]), application:ensure_all_started(eradius), + ok = start_client(Backend, Family), + + SrvOpts = #{handler => {?MODULE, []}, + clients => #{eradius_test_lib:localhost(Family, native) => + #{secret => "secret", client => <<"ONE">>}}}, + {ok, _} = eradius:start_server( + eradius_test_lib:localhost(Family, native), 1812, SrvOpts#{server_name => one}), + {ok, _} = eradius:start_server( + eradius_test_lib:localhost(Family, native), 1813, SrvOpts#{server_name => two}), + ok. + +stop() -> + application:stop(eradius), + application:unload(eradius). + +start_client(Backend, Family) -> + application:ensure_all_started(eradius), + + Clients = + maps:from_list( + [{binary_to_atom(<<(X+$A)>>), #{ip => eradius_test_lib:localhost(Family, native), + port => 1820 + X, secret => "secret"}} + || X <- lists:seq(0, 9)]), ClientConfig = #{inet_backend => Backend, family => eradius_test_lib:inet_family(Family), - ip => eradius_test_lib:localhost(Family, tuple), - servers => [{one, {eradius_test_lib:localhost(Family, ip), [1812]}}, - {two, {eradius_test_lib:localhost(Family, ip), [1813]}}], - servers_pool => - [{test_pool, [{eradius_test_lib:localhost(Family, tuple), 1812, "secret"}, - %% fake upstream server for fail-over - {eradius_test_lib:localhost(Family, string), 1820, "secret"}]}] + ip => eradius_test_lib:localhost(Family, native), + servers => Clients#{one => #{ip => eradius_test_lib:localhost(Family, native), + port => 1812, + secret => "secret", + retries => 3}, + two => #{ip => eradius_test_lib:localhost(Family, native), + port => 1813, + secret => "secret", + retries => 3}, + bad => #{ip => eradius_test_lib:localhost(Family, native), + port => 1920, + secret => "secret", + retries => 3}, + test_pool => [one, two]} }, - eradius_client_mngr:reconfigure(ClientConfig), - eradius:modules_ready([?MODULE]). - -stop() -> - application:stop(eradius), - application:unload(eradius), - application:start(eradius). + case eradius_client_mngr:start_client({local, ?SERVER}, ClientConfig) of + {ok, _} -> ok; + {error, {already_started, _}} -> ok + end. -send_request(IP) -> - {ok, R, A} = eradius_client:send_request({IP, 1812, "secret"}, #radius_request{cmd = request}, []), - #radius_request{cmd = Cmd} = eradius_lib:decode_request(R, <<"secret">>, A), +send_request(ServerName) -> + ct:pal("about to send"), + {{ok, Resp}, _Req} = + eradius_client:send_request(?SERVER, ServerName, eradius_req:new(request), #{}), + {_, #{cmd := Cmd}} = eradius_req:attrs(Resp), Cmd. send_request_failover(Server) -> - {ok, Pools} = application:get_env(eradius, servers_pool), - SecondaryServers = proplists:get_value(test_pool, Pools), - {ok, R, A} = eradius_client:send_request(Server, #radius_request{cmd = request}, [{retries, 1}, - {timeout, 2000}, - {failover, SecondaryServers}]), - #radius_request{cmd = Cmd} = eradius_lib:decode_request(R, <<"secret">>, A), + Opts = #{retries => 1, timeout => 2000, failover => [test_pool]}, + {{ok, Resp}, _Req} = + eradius_client:send_request(?SERVER, Server, eradius_req:new(request), Opts), + {_, #{cmd := Cmd}} = eradius_req:attrs(Resp), Cmd. -radius_request(#radius_request{cmd = request}, _Nasprop, _Args) -> - {reply, #radius_request{cmd = accept}}. +radius_request(Req = #{cmd := request}, _Args) -> + {reply, eradius_req:set_attrs([], Req#{cmd := accept})}. diff --git a/test/eradius_test_lib.erl b/test/eradius_test_lib.erl index bde63471..fbd70740 100644 --- a/test/eradius_test_lib.erl +++ b/test/eradius_test_lib.erl @@ -6,6 +6,9 @@ -compile([export_all, nowarn_export_all]). +-define(IP4_LOOPBACK, {127, 0, 0, 1}). +-define(IP6_LOOPBACK, {0, 0, 0, 0, 0, 0, 0, 1}). + %%%=================================================================== %%% Helper functions %%%=================================================================== @@ -26,28 +29,11 @@ inet_family(ipv4) -> inet; inet_family(ipv6) -> inet6; inet_family(ipv4_mapped_ipv6) -> inet6. -badhost(Family) - when Family =:= ipv4; Family =:= ipv4_mapped_ipv6 -> - {127, 0, 0, 2}; -badhost(ipv6) -> - {0, 0, 0, 0, 0, 0, 0, 2}. - -localhost(Type) -> - localhost(ipv4, Type). - -localhost(Family, string) when Family =:= ipv4; Family =:= ipv4_mapped_ipv6 -> - "ip4-loopback"; -localhost(ipv6, string) -> - "ip6-loopback"; -localhost(Family, binary) -> - list_to_binary(localhost(Family, string)); -localhost(Family, tuple) when Family =:= ipv4; Family =:= ipv4_mapped_ipv6 -> - {ok, IP} = inet:getaddr(localhost(ipv4, string), inet), - IP; -localhost(ipv6, tuple) -> - {ok, IP} = inet:getaddr(localhost(ipv6, string), inet6), - IP; -localhost(Family, ip) -> - inet:ntoa(localhost(Family, tuple)); -localhost(Family, atom) -> - list_to_atom(localhost(Family, ip)). +localhost(ipv4, _) -> + ?IP4_LOOPBACK; +localhost(ipv6, _) -> + ?IP6_LOOPBACK; +localhost(ipv4_mapped_ipv6, native) -> + ?IP4_LOOPBACK; +localhost(ipv4_mapped_ipv6, mapped) -> + inet:ipv4_mapped_ipv6_address(?IP4_LOOPBACK).