Skip to content
Erlang/Elixir Generic firewall process.
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
examples
include
src
test
.travis.yml
LICENSE
README.md
rebar.config
rebar3

README.md

gen_fw travis test status

gen_fw

Generic firewall process for Erlang/Elixir. It's an Erlang process between client processes and server process/port and prevents receiving some unwanted Erlang messages from server process/port.

When to use gen_fw?

When writing new server process, It's better to receive all messages and handle unwanted messages in that process instead of using gen_fw. But when you are using some generic behaviour or someone else's library and you don't want your client processes send everything (even wrongly) to server process/port, gen_fw is your friend. In below example i will show you how to prevent client process call supervisor:terminate/2 successfully for your supervisor.

How to use?

To starting main process you have to give its MFA to gen_fw and gen_fw will start itself and main process in order. Note that if you want to register main process, you have to give its register name to gen_fw and gen_fw will register itself.
API functions for starting linked gen_fw process:

gen_fw:start_link({Mod, Func, Args}=_MFA, Rules).
gen_fw:start_link({local, Name}, {Mod, Func, Args}=_MFA, Rules).

API functions for starting stand-alone gen_fw process:

gen_fw:start({Mod, Func, Args}=_MFA, Rules).
gen_fw:start({local, Name}, {Mod, Func, Args}=_MFA, Rules).

Rules

-type rules() :: [] | [rule()].
-type  rule() :: {'cast', action()}
               | {'call', action()}
               | {'message', action()}
               | {'system', action()}
               | {'all', action()}.
-type   action() :: 'allow' | 'deny' | 'dynamic'.

For example:

start_link_fw() ->
        gen_fw:start_link({?MODULE, start_link, []}
                         ,[{cast, deny}, {message, allow}, {system, allow}, {call, dynamic}]).

start_link() ->
	gen_server:start_link(?MODULE, init_arg, []).

Call to start_link_fw/0 means start my process using ?MODULE:start_link() which start a gen_server and:

  • Prevent receiving cast messages (Means that ?MODULE:handle_cast/2 never runs)
  • Allow receiving messages (Means that ?MODULE:handle_info/2 may run)
  • Allow receiving system message (Means you can use sys:get_state/1, sys:get_status/1, ...)
  • Use callback function ?MODULE:fw_handle_message/2 and this function will tell you that i want to receive calls or not (Means that gen_server may receive some calls and ?MODULE:handle_call/3 may run)

If function ?MODULE:fw_handle_message/2 is not exported or if it did not yield allow or deny or if it crashed, gen_fw denies message silently.

Note that instead of writing:

gen_fw:start_link({?MODULE, start_link, []}, [{cast, deny}, {message, deny}, {system, deny}, {call, allow}]).

you can just write:

gen_fw:start_link({?MODULE, start_link, []}, [{all, deny}, {call, allow}]).

Example

I wrote a childless supervisor in module my_sup. Let's start supervisor directly first:

1> {ok, Sup} = supervisor:start_link(my_sup, init_arg).
{ok,<0.60.0>}

2> Sup ! hello.
hello
 
=ERROR REPORT==== 2-Jun-2018::13:49:28 ===
Supervisor received unexpected message: hello

3> supervisor:which_children(Sup).
[]

4> supervisor:terminate_child(Sup, foo).
{error,not_found}

%% Crash :-S
5> gen_server:cast(Sup, oops).

=ERROR REPORT==== 2-Jun-2018::13:50:20 ===
** Generic server <0.60.0> terminating 
** Last message in was {'$gen_cast',oops}
** When Server state == {state,{<0.60.0>,my_sup},
                               one_for_one,[],undefined,1,3,[],0,my_sup,
                               init_arg}
** Reason for termination == 
** {function_clause,
       [{supervisor,handle_cast,
            [oops,
             {state,
                 {<0.60.0>,my_sup},
                 one_for_one,[],undefined,1,3,[],0,my_sup,init_arg}],
            [{file,"supervisor.erl"},{line,585}]},
        {gen_server,try_dispatch,4,[{file,"gen_server.erl"},{line,601}]},
        {gen_server,handle_msg,5,[{file,"gen_server.erl"},{line,667}]},
        {proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,247}]}]}
ok
** exception error: no function clause matching supervisor:handle_cast(oops,
                                                                       {state,{<0.60.0>,my_sup},
                                                                              one_for_one,[],undefined,1,3,[],0,my_sup,init_arg}) (supervisor.erl, line 585)
     in function  gen_server:try_dispatch/4 (gen_server.erl, line 601)
     in call from gen_server:handle_msg/5 (gen_server.erl, line 667)
     in call from proc_lib:init_p_do_apply/3 (proc_lib.erl, line 247)

Now i want to use gen_fw. I don't want to receive casts and messages. Also i just want allow supervisor:count_children/1 call and want to reply an error message to callers of other API functions directly from gen_fw. First read the my_sup code:

1> {ok, Sup} = my_sup:start_link([{cast, deny}, {message, deny}, {call, dynamic}]).
{ok,<0.60.0>}

%% supervisor process does not receive this message and does not call error_logger
2> Sup ! hello.
hello

3> supervisor:which_children(Sup).
{error,permission_denied}

4> supervisor:terminate_child(Sup, foo).
{error,permission_denied}

5> supervisor:count_children(Sup). 
[{specs,0},{active,0},{supervisors,0},{workers,0}]

%% supervisor process does not receive this message, then there is no crash
6> gen_server:cast(Sup, oops).
ok

License

BSD 3-Clause

Author

pouriya.jahanbakhsh@gmail.com

Hex

18.6.2

You can’t perform that action at this time.