PageRenderTime 60ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/src/eradius_proxy.erl

http://github.com/travelping/eradius
Erlang | 288 lines | 225 code | 26 blank | 37 comment | 3 complexity | 3407db427b5f0f27d5f48b34256ce3e9 MD5 | raw file
  1. %% @doc
  2. %% This module implements a RADIUS proxy.
  3. %%
  4. %% It accepts following configuration:
  5. %%
  6. %% ```
  7. %% [{default_route, {{127, 0, 0, 1}, 1813, <<"secret">>}, pool_name},
  8. %% {options, [{type, realm}, {strip, true}, {separator, "@"}]},
  9. %% {routes, [{"^test-[0-9].", {{127, 0, 0, 1}, 1815, <<"secret1">>}, pool_name}]}]
  10. %% '''
  11. %%
  12. %% Where the pool_name is optional field that contains list of
  13. %% RADIUS servers pool name that will be used for fail-over.
  14. %%
  15. %% Pools of RADIUS servers are defined in eradius configuration:
  16. %%
  17. %% ```
  18. %% {servers_pool, [{pool_name, [
  19. %% {{127, 0, 0, 1}, 1815, <<"secret">>, [{retries, 3}]},
  20. %% {{127, 0, 0, 1}, 1816, <<"secret">>}]}]}
  21. %% '''
  22. %%
  23. %% == WARNING ==
  24. %%
  25. %% Define `routes' carefully. The `test' here in example above, is
  26. %% a regular expression that may cause to problemts with performance.
  27. -module(eradius_proxy).
  28. -behaviour(eradius_server).
  29. -export([radius_request/3, validate_arguments/1, get_routes_info/1,
  30. put_default_route_to_pool/2, put_routes_to_pool/2]).
  31. -ifdef(TEST).
  32. -export([resolve_routes/4, validate_options/1, new_request/3,
  33. get_key/4, strip/4]).
  34. -endif.
  35. -include_lib("kernel/include/logger.hrl").
  36. -include("eradius_lib.hrl").
  37. -include("dictionary.hrl").
  38. -define(DEFAULT_TYPE, realm).
  39. -define(DEFAULT_STRIP, false).
  40. -define(DEFAULT_SEPARATOR, "@").
  41. -define(DEFAULT_TIMEOUT, 5000).
  42. -define(DEFAULT_RETRIES, 1).
  43. -define(DEFAULT_CLIENT_RETRIES, 3).
  44. -define(DEFAULT_OPTIONS, [{type, ?DEFAULT_TYPE},
  45. {strip, ?DEFAULT_STRIP},
  46. {separator, ?DEFAULT_SEPARATOR},
  47. {timeout, ?DEFAULT_TIMEOUT},
  48. {retries, ?DEFAULT_RETRIES}]).
  49. -type route() :: eradius_client:nas_address() |
  50. {eradius_client:nas_address(), PoolName :: atom()}.
  51. -type routes() :: [{Name :: string(), eradius_client:nas_address()}] |
  52. [{Name :: string(), eradius_client:nas_address(), PoolName :: atom()}].
  53. -type undefined_route() :: {undefined, 0, []}.
  54. radius_request(Request, _NasProp, Args) ->
  55. DefaultRoute = get_proxy_opt(default_route, Args, {undefined, 0, []}),
  56. Routes = get_proxy_opt(routes, Args, []),
  57. Options = proplists:get_value(options, Args, ?DEFAULT_OPTIONS),
  58. Username = eradius_lib:get_attr(Request, ?User_Name),
  59. {NewUsername, Route} = resolve_routes(Username, DefaultRoute, Routes, Options),
  60. Retries = proplists:get_value(retries, Options, ?DEFAULT_RETRIES),
  61. Timeout = proplists:get_value(timeout, Options, ?DEFAULT_TIMEOUT),
  62. SendOpts = [{retries, Retries}, {timeout, Timeout}],
  63. send_to_server(new_request(Request, Username, NewUsername), Route, SendOpts).
  64. validate_arguments(Args) ->
  65. DefaultRoute = get_proxy_opt(default_route, Args, {undefined, 0, []}),
  66. Options = proplists:get_value(options, Args, ?DEFAULT_OPTIONS),
  67. Routes = get_proxy_opt(routes, Args, undefined),
  68. case {validate_route(DefaultRoute), validate_options(Options), compile_routes(Routes)} of
  69. {false, _, _} -> default_route;
  70. {_, false, _} -> options;
  71. {_, _, false} -> routes;
  72. {_, _, NewRoutes} ->
  73. {true, [{default_route, DefaultRoute}, {options, Options}, {routes, NewRoutes}]}
  74. end.
  75. compile_routes(undefined) -> [];
  76. compile_routes(Routes) ->
  77. RoutesOpts = lists:map(fun (Route) ->
  78. {Name, Relay, Pool} = route(Route),
  79. case re:compile(Name) of
  80. {ok, R} ->
  81. case validate_route({Relay, Pool}) of
  82. false -> false;
  83. _ -> {R, Relay, Pool}
  84. end;
  85. {error, {Error, Position}} ->
  86. throw("Error during regexp compilation - " ++ Error ++ " at position " ++ integer_to_list(Position))
  87. end
  88. end, Routes),
  89. RelaysRegexps = lists:any(fun(Route) -> Route == false end, RoutesOpts),
  90. if RelaysRegexps == false ->
  91. RoutesOpts;
  92. true ->
  93. false
  94. end.
  95. % @private
  96. -spec send_to_server(Request :: #radius_request{},
  97. Route :: undefined_route() | route(),
  98. Options :: eradius_client:options()) ->
  99. {reply, Reply :: #radius_request{}} | term().
  100. send_to_server(_Request, {undefined, 0, []}, _) ->
  101. {error, no_route};
  102. send_to_server(#radius_request{reqid = ReqID} = Request, {{Server, Port, Secret}, Pool}, Options) ->
  103. Pools = application:get_env(eradius, servers_pool, []),
  104. UpstreamServers = proplists:get_value(Pool, Pools, []),
  105. case eradius_client:send_request({Server, Port, Secret}, Request, [{failover, UpstreamServers} | Options]) of
  106. {ok, Result, Auth} ->
  107. decode_request(Result, ReqID, Secret, Auth);
  108. no_active_servers ->
  109. % If all RADIUS servers are marked as inactive for now just use
  110. % just skip fail-over mechanism and use default given Peer
  111. send_to_server(Request, {Server, Port, Secret}, Options);
  112. Error ->
  113. ?LOG(error, "~p: error during send_request (~p)", [?MODULE, Error]),
  114. Error
  115. end;
  116. send_to_server(#radius_request{reqid = ReqID} = Request, {Server, Port, Secret}, Options) ->
  117. case eradius_client:send_request({Server, Port, Secret}, Request, Options) of
  118. {ok, Result, Auth} -> decode_request(Result, ReqID, Secret, Auth);
  119. Error ->
  120. ?LOG(error, "~p: error during send_request (~p)", [?MODULE, Error]),
  121. Error
  122. end.
  123. % @private
  124. decode_request(Result, ReqID, Secret, Auth) ->
  125. case eradius_lib:decode_request(Result, Secret, Auth) of
  126. Reply = #radius_request{} ->
  127. {reply, Reply#radius_request{reqid = ReqID}};
  128. Error ->
  129. ?LOG(error, "~p: request is incorrect (~p)", [?MODULE, Error]),
  130. Error
  131. end.
  132. % @private
  133. -spec validate_route(Route :: route()) -> boolean().
  134. validate_route({{Host, Port, Secret}, PoolName}) when is_atom(PoolName) ->
  135. validate_route({Host, Port, Secret});
  136. validate_route({_Host, Port, _Secret}) when not is_integer(Port); Port =< 0; Port > 65535 -> false;
  137. validate_route({_Host, _Port, Secret}) when not is_list(Secret), not is_binary(Secret) -> false;
  138. validate_route({Host, _Port, _Secret}) when is_list(Host) -> true;
  139. validate_route({Host, Port, Secret}) when is_tuple(Host) ->
  140. case inet_parse:ntoa(Host) of
  141. {error, _} -> false;
  142. Address -> validate_route({Address, Port, Secret})
  143. end;
  144. validate_route({Host, _Port, _Secret}) when is_binary(Host) -> true;
  145. validate_route(_) -> false.
  146. % @private
  147. -spec validate_options(Options :: [proplists:property()]) -> boolean().
  148. validate_options(Options) ->
  149. Keys = proplists:get_keys(Options),
  150. lists:all(fun(Key) -> validate_option(Key, proplists:get_value(Key, Options)) end, Keys).
  151. % @private
  152. -spec validate_option(Key :: atom(), Value :: term()) -> boolean().
  153. validate_option(type, Value) when Value =:= realm; Value =:= prefix -> true;
  154. validate_option(type, _Value) -> false;
  155. validate_option(strip, Value) when is_boolean(Value) -> true;
  156. validate_option(strip, _Value) -> false;
  157. validate_option(separator, Value) when is_list(Value) -> true;
  158. validate_option(timeout, Value) when is_integer(Value) -> true;
  159. validate_option(retries, Value) when is_integer(Value) -> true;
  160. validate_option(_, _) -> false.
  161. % @private
  162. -spec new_request(Request :: #radius_request{},
  163. Username :: undefined | binary(),
  164. NewUsername :: string()) ->
  165. NewRequest :: #radius_request{}.
  166. new_request(Request, Username, Username) -> Request;
  167. new_request(Request, _Username, NewUsername) ->
  168. eradius_lib:set_attr(eradius_lib:del_attr(Request, ?User_Name),
  169. ?User_Name, NewUsername).
  170. % @private
  171. -spec resolve_routes(Username :: undefined | binary(),
  172. DefaultRoute :: undefined_route() | route(),
  173. Routes :: routes(), Options :: [proplists:property()]) ->
  174. {NewUsername :: string(), Route :: route()}.
  175. resolve_routes( undefined, DefaultRoute, _Routes, _Options) ->
  176. {undefined, DefaultRoute};
  177. resolve_routes(Username, DefaultRoute, Routes, Options) ->
  178. Type = proplists:get_value(type, Options, ?DEFAULT_TYPE),
  179. Strip = proplists:get_value(strip, Options, ?DEFAULT_STRIP),
  180. Separator = proplists:get_value(separator, Options, ?DEFAULT_SEPARATOR),
  181. case get_key(Username, Type, Strip, Separator) of
  182. {not_found, NewUsername} ->
  183. {NewUsername, DefaultRoute};
  184. {Key, NewUsername} ->
  185. {NewUsername, find_suitable_relay(Key, Routes, DefaultRoute)}
  186. end.
  187. find_suitable_relay(_Key, [], DefaultRoute) -> DefaultRoute;
  188. find_suitable_relay(Key, [{Regexp, Relay} | Routes], DefaultRoute) ->
  189. case re:run(Key, Regexp, [{capture, none}]) of
  190. nomatch -> find_suitable_relay(Key, Routes, DefaultRoute);
  191. _ -> Relay
  192. end;
  193. find_suitable_relay(Key, [{Regexp, Relay, PoolName} | Routes], DefaultRoute) ->
  194. case re:run(Key, Regexp, [{capture, none}]) of
  195. nomatch -> find_suitable_relay(Key, Routes, DefaultRoute);
  196. _ -> {Relay, PoolName}
  197. end.
  198. % @private
  199. -spec get_key(Username :: binary() | string() | [], Type :: atom(), Strip :: boolean(), Separator :: list()) ->
  200. {Key :: not_found | string(), NewUsername :: string()}.
  201. get_key([], _, _, _) -> {not_found, []};
  202. get_key(Username, Type, Strip, Separator) when is_binary(Username) ->
  203. get_key(binary_to_list(Username), Type, Strip, Separator);
  204. get_key(Username, realm, Strip, Separator) ->
  205. Realm = lists:last(string:tokens(Username, Separator)),
  206. {Realm, strip(Username, realm, Strip, Separator)};
  207. get_key(Username, prefix, Strip, Separator) ->
  208. Prefix = hd(string:tokens(Username, Separator)),
  209. {Prefix, strip(Username, prefix, Strip, Separator)};
  210. get_key(Username, _, _, _) -> {not_found, Username}.
  211. % @private
  212. -spec strip(Username :: string(), Type :: atom(), Strip :: boolean(), Separator :: list()) ->
  213. NewUsername :: string().
  214. strip(Username, _, false, _) -> Username;
  215. strip(Username, realm, true, Separator) ->
  216. case string:tokens(Username, Separator) of
  217. [Username] -> Username;
  218. [_ | _] = List ->
  219. [_ | Tail] = lists:reverse(List),
  220. string:join(lists:reverse(Tail), Separator)
  221. end;
  222. strip(Username, prefix, true, Separator) ->
  223. case string:tokens(Username, Separator) of
  224. [Username] -> Username;
  225. [_ | Tail] -> string:join(Tail, Separator)
  226. end.
  227. route({RouteName, RouteRelay}) -> {RouteName, RouteRelay, undefined};
  228. route({_RouteName, _RouteRelay, _Pool} = Route) -> Route.
  229. get_routes_info(HandlerOpts) ->
  230. DefaultRoute = lists:keyfind(default_route, 1, HandlerOpts),
  231. Routes = lists:keyfind(routes, 1, HandlerOpts),
  232. Options = lists:keyfind(options, 1, HandlerOpts),
  233. Retries = case Options of
  234. false ->
  235. ?DEFAULT_CLIENT_RETRIES;
  236. {options, Opts} ->
  237. proplists:get_value(retries, Opts, ?DEFAULT_CLIENT_RETRIES)
  238. end,
  239. {DefaultRoute, Routes, Retries}.
  240. put_default_route_to_pool(false, _) -> ok;
  241. put_default_route_to_pool({default_route, {Host, Port, _Secret}}, Retries) ->
  242. eradius_client:store_radius_server_from_pool(Host, Port, Retries);
  243. put_default_route_to_pool({default_route, {Host, Port, _Secret}, _PoolName}, Retries) ->
  244. eradius_client:store_radius_server_from_pool(Host, Port, Retries);
  245. put_default_route_to_pool(_, _) -> ok.
  246. put_routes_to_pool(false, _Retries) -> ok;
  247. put_routes_to_pool({routes, Routes}, Retries) ->
  248. lists:foreach(fun (Route) ->
  249. case Route of
  250. {_RouteName, {Host, Port, _Secret}} ->
  251. eradius_client:store_radius_server_from_pool(Host, Port, Retries);
  252. {_RouteName, {Host, Port, _Secret}, _Pool} ->
  253. eradius_client:store_radius_server_from_pool(Host, Port, Retries);
  254. {Host, Port, _Secret, _Opts} ->
  255. eradius_client:store_radius_server_from_pool(Host, Port, Retries);
  256. _ -> ok
  257. end
  258. end, Routes).
  259. get_proxy_opt(_, [], Default) -> Default;
  260. get_proxy_opt(OptName, [{OptName, AddrOrRoutes} | _], _) -> AddrOrRoutes;
  261. get_proxy_opt(OptName, [{OptName, Addr, Pool} | _], _) -> {Addr, Pool};
  262. get_proxy_opt(OptName, [_ | Args], Default) -> get_proxy_opt(OptName, Args, Default).