Skip to content

travelping/eradius

Repository files navigation

eradius

Hex.pm Version Hex.pm Downloads Build Status Erlang Versions

This fork of eradius is a radical deviation from the original Jungerl code. It contains a generic RADIUS client, support for several authentication mechanisms and dynamic configuration (it implements the config_change/3 application callback).

Contents

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 for tested versions.

Building eradius

$ rebar3 compile

Using eradius

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.

Run sample server

$ cd sample
$ rebar3 shell --config example.config --apps eradius_server_sample

Then run a simple benchmark:

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

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 and RFC 2621 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.

See more in METRICS.md.

RADIUS server configuration

⚠️ Notes ⚠️

  • 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.

[{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.

[{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.

[{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:

[{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.

[{eradius, [
    %%% ...
    {default_route, {{127, 0, 0, 1}, 1812, <<"secret">>}, [{pool, pool_name}]}
    %%% ...
]}]

All pools are configured via:

[{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:

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:

[{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 the priv directory of the eradius application.

Example:

    [dictionary, dictionary_cisco, dictionary_travelping]