esockd_access.erl 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. %%%-----------------------------------------------------------------------------
  2. %%% @Copyright (C) 2014-2015, Feng Lee <feng@emqtt.io>
  3. %%%
  4. %%% Permission is hereby granted, free of charge, to any person obtaining a copy
  5. %%% of this software and associated documentation files (the "Software"), to deal
  6. %%% in the Software without restriction, including without limitation the rights
  7. %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. %%% copies of the Software, and to permit persons to whom the Software is
  9. %%% furnished to do so, subject to the following conditions:
  10. %%%
  11. %%% The above copyright notice and this permission notice shall be included in all
  12. %%% copies or substantial portions of the Software.
  13. %%%
  14. %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. %%% SOFTWARE.
  21. %%%-----------------------------------------------------------------------------
  22. %%% @doc
  23. %%% esockd access control.
  24. %%%
  25. %%% CIDR: Classless Inter-Domain Routing
  26. %%%
  27. %%% @end
  28. %%%-----------------------------------------------------------------------------
  29. -module(esockd_access).
  30. -type cidr() :: string().
  31. -type rule() :: {allow, all} |
  32. {allow, cidr()} |
  33. {deny, all} |
  34. {deny, cidr()}.
  35. -type range() :: {cidr(), pos_integer(), pos_integer()}.
  36. -export_type([cidr/0, range/0, rule/0]).
  37. -type range_rule() :: {allow, all} |
  38. {allow, range()} |
  39. {deny, all} |
  40. {deny, range()}.
  41. -export([rule/1, match/2, range/1, mask/1, atoi/1, itoa/1]).
  42. %%------------------------------------------------------------------------------
  43. %% @doc
  44. %% Build CIDR, Make rule.
  45. %%
  46. %% @end
  47. %%------------------------------------------------------------------------------
  48. -spec rule(rule()) -> range_rule().
  49. rule({allow, all}) ->
  50. {allow, all};
  51. rule({allow, CIDR}) when is_list(CIDR) ->
  52. rule(allow, CIDR);
  53. rule({deny, CIDR}) when is_list(CIDR) ->
  54. rule(deny, CIDR);
  55. rule({deny, all}) ->
  56. {deny, all}.
  57. rule(Type, CIDR) when is_list(CIDR) ->
  58. {Start, End} = range(CIDR), {Type, {CIDR, Start, End}}.
  59. %%------------------------------------------------------------------------------
  60. %% @doc
  61. %% Match Addr with Access Rules.
  62. %%
  63. %% @end
  64. %%------------------------------------------------------------------------------
  65. -spec match(inet:ip_address(), [range_rule()]) -> {matched, allow} | {matched, deny} | nomatch.
  66. match(Addr, Rules) when is_tuple(Addr) ->
  67. match2(atoi(Addr), Rules).
  68. match2(_I, []) ->
  69. nomatch;
  70. match2(_I, [{allow, all}|_]) ->
  71. {matched, allow};
  72. match2(I, [{allow, {_, Start, End}}|_]) when I >= Start, I =< End ->
  73. {matched, allow};
  74. match2(I, [{allow, {_, _Start, _End}}|Rules]) ->
  75. match2(I, Rules);
  76. match2(I, [{deny, {_, Start, End}}|_]) when I >= Start, I =< End ->
  77. {matched, deny};
  78. match2(I, [{deny, {_, _Start, _End}}|Rules]) ->
  79. match2(I, Rules);
  80. match2(_I, [{deny, all}|_]) ->
  81. {matched, deny}.
  82. %%------------------------------------------------------------------------------
  83. %% @doc
  84. %% CIDR range.
  85. %%
  86. %% @end
  87. %%------------------------------------------------------------------------------
  88. -spec range(cidr()) -> {pos_integer(), pos_integer()}.
  89. range(CIDR) ->
  90. case string:tokens(CIDR, "/") of
  91. [Addr] ->
  92. {ok, IP} = inet:getaddr(Addr, inet),
  93. {atoi(IP), atoi(IP)};
  94. [Addr, Mask] ->
  95. {ok, IP} = inet:getaddr(Addr, inet),
  96. {Start, End} = subnet(IP, mask(list_to_integer(Mask))),
  97. {Start, End}
  98. end.
  99. subnet(IP, Mask) ->
  100. Start = atoi(IP) band Mask,
  101. End = Start bor (Mask bxor 16#FFFFFFFF),
  102. {Start, End}.
  103. %%------------------------------------------------------------------------------
  104. %% @doc
  105. %% Mask Int
  106. %%
  107. %% @end
  108. %%------------------------------------------------------------------------------
  109. -spec mask(0..32) -> 0..16#FFFFFFFF.
  110. mask(0) ->
  111. 16#00000000;
  112. mask(32) ->
  113. 16#FFFFFFFF;
  114. mask(N) when N >= 1, N =< 31 ->
  115. lists:foldl(fun(I, Mask) -> (1 bsl I) bor Mask end, 0, lists:seq(32 - N, 31)).
  116. %%------------------------------------------------------------------------------
  117. %% @doc
  118. %% Addr to Integer.
  119. %%
  120. %% @end
  121. %%------------------------------------------------------------------------------
  122. atoi({A, B, C, D}) ->
  123. (A bsl 24) + (B bsl 16) + (C bsl 8) + D.
  124. %%------------------------------------------------------------------------------
  125. %% @doc
  126. %% Integer to Addr.
  127. %%
  128. %% @end
  129. %%------------------------------------------------------------------------------
  130. itoa(I) ->
  131. A = (I bsr 24) band 16#FF,
  132. B = (I bsr 16) band 16#FF,
  133. C = (I bsr 8) band 16#FF,
  134. D = I band 16#FF,
  135. {A, B, C, D}.