diff --git a/src/eradius_config.erl b/src/eradius_config.erl index c2bf7556..c3c99165 100644 --- a/src/eradius_config.erl +++ b/src/eradius_config.erl @@ -71,9 +71,10 @@ validate_behavior({Module, _, _}) -> validate_behavior(Term) -> ?invalid("bad Term in Behavior specifification: ~p", [Term]). -validate_arguments({Module, _Nas, Args} = Value) -> +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. diff --git a/src/eradius_proxy.erl b/src/eradius_proxy.erl index 4871c2de..73b1c207 100644 --- a/src/eradius_proxy.erl +++ b/src/eradius_proxy.erl @@ -1,3 +1,18 @@ +%% @doc +%% This module implements a RADIUS proxy. +%% +%% It accepts following configuration: +%% +%% ``` +%% [{default_route, {{127, 0, 0, 1}, 1813, <<"secret">>}}, +%% {options, [{type, realm}, {strip, true}, {separator, "@"}]}, +%% {routes, [{"^test-[0-9].", {{127, 0, 0, 1}, 1815, <<"secret1">>}}]}], +%% ``` +%% +%% == 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). @@ -31,12 +46,34 @@ radius_request(Request, _NasProp, Args) -> send_to_server(new_request(Request, Username, NewUsername), Route). validate_arguments(Args) -> - Route = proplists:get_value(default_route, Args, {undefined, 0, []}), + DefaultRoute = proplists:get_value(default_route, Args, {undefined, 0, []}), Options = proplists:get_value(options, Args, ?DEFAULT_OPTIONS), - case {validate_route(Route), validate_options(Options)} of - {false, _} -> default_route; - {_, false} -> options; - _ -> true + Routes = proplists:get_value(routes, Args, []), + 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(Routes) -> + RoutesOpts = lists:map(fun ({Name, Relay}) -> + case re:compile(Name) of + {ok, R} -> + case validate_route(Relay) of + false -> false; + _ -> {R, Relay} + 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 @@ -115,13 +152,20 @@ resolve_routes(Username, {_, _, DefaultSecret} = DefaultRoute, Routes, Options) {not_found, NewUsername} -> {NewUsername, DefaultRoute}; {Key, NewUsername} -> - case lists:keyfind(Key, 1, Routes) of + case find_suitable_relay(Key, Routes) of {Key, {_IP, _Port, _Secret} = Route} -> {NewUsername, Route}; {Key, {IP, Port}} -> {NewUsername, {IP, Port, DefaultSecret}}; _ -> {NewUsername, DefaultRoute} end end. +find_suitable_relay(_Key, []) -> []; +find_suitable_relay(Key, [{Regexp, Relay} | Routes]) -> + case re:run(Key, Regexp, [{capture, none}]) of + nomatch -> find_suitable_relay(Key, Routes); + _ -> {Key, Relay} + end. + % @private -spec get_key(Username :: binary() | string() | [], Type :: atom(), Strip :: boolean(), Separator :: list()) -> {Key :: not_found | string(), NewUsername :: string()}. diff --git a/test/eradius_proxy_SUITE.erl b/test/eradius_proxy_SUITE.erl index 2cc3baa4..4bbf5f89 100644 --- a/test/eradius_proxy_SUITE.erl +++ b/test/eradius_proxy_SUITE.erl @@ -37,7 +37,11 @@ resolve_routes_test(_) -> DefaultRoute = {{127, 0, 0, 1}, 1813, <<"secret">>}, Prod = {{127, 0, 0, 1}, 1812, <<"prod">>}, Test = {{127, 0, 0, 1}, 11813, <<"test">>}, - Routes = [{"prod", Prod}, {"test", Test}], + Dev = {{127, 0, 0, 1}, 11814, <<"dev">>}, + {ok, R1} = re:compile("prod"), + {ok, R2} = re:compile("test"), + {ok, R3} = re:compile("^dev_.*"), + Routes = [{R1, Prod}, {R2, Test}, {R3, Dev}], % default ?equal({undefined, DefaultRoute}, eradius_proxy:resolve_routes(undefined, DefaultRoute, Routes,[])), ?equal({"user", DefaultRoute}, eradius_proxy:resolve_routes(<<"user">>, DefaultRoute, Routes, [])), @@ -48,6 +52,9 @@ resolve_routes_test(_) -> ?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}, 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)), @@ -69,11 +76,19 @@ validate_arguments_test(_) -> ], BadConfig1 = [{default_route, {{127, 0, 0, 1}, 0, <<"secret">>}}], BadConfig2 = [{default_route, {abc, 123, <<"secret">>}}], - ?equal(true, eradius_proxy:validate_arguments(GoodConfig)), + BadConfig3 = [{default_route, {{127, 0, 0, 1}, 1813, <<"secret">>}}, + {options, [{type, realm}, {strip, true}, {separator, "@"}]}, + {routes, [{"test", {wrong_ip, 1815, <<"secret1">>}}]}], + {Result, ConfigData} = eradius_proxy:validate_arguments(GoodConfig), + ?equal(true, Result), + {routes, Routes} = lists:keyfind(routes, 1, ConfigData), + [{{CompiledRegexp, _, _, _, _}, _}] = Routes, + ?equal(re_pattern, CompiledRegexp), ?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)), ok. validate_options_test(_) ->