[erlang-patches] Running mnesia across a firewall
Serge Aleynikov
saleyn@REDACTED
Wed Mar 5 04:58:04 CET 2008
While most of the times when using mnesia I had a fairly straight
forward network setup, recently I ran into a need to be able to use
remote mnesia's interface in presence of a firewall and would like to
share my experience along with a net_kernel patch that adds a useful
feature.
The setup was as follows: two nodes (NodeA & NodeB) are inside the
firewall and NodeC is outside the firewall. NodeA & NodeB can connect
to NodeC, but all inbound access from NodeC is blocked. NodeA & NodeB
are running mnesia with a few tables with disc_copies. NodeC uses
mnesia's remote interface to access data tables on nodes inside the
firewall.
The diagram below shows network topology with corresponding Erlang's
kernel options.
NodeA
^ {dist_auto_connect, once}
|
|
| INSIDE | OUTSIDE
+----------|firewall------> NodeC
| | {dist_auto_connect, never},
| {inet_dist_listen_min, X},
v {inet_dist_listen_max, Y},
NodeB {global_groups, [{outside, [NodeC]}]}
{dist_auto_connect, once}
Notes about NodeC kernel options:
- {dist_auto_connect, never} prevents the node from
attempting connections inside the firewall.
- min/max listen options converge the firewall rules
to a minimal reserved setup.
- global_groups prevent global from connecting/synching
with other nodes inside the firewall after a connection
is made to some node inside the firewall.
All three nodes subscribe to net_kernel:monitor_nodes/1, and when NodeA
disconnects from NodeB (or vice versa) they enable UDP heartbeat and
upon detecting that the peer node is responding, restart one of the
nodes, which re-synchs mnesia.
If network access between NodeA (or NodeB) and NodeC is lost, NodeC
shuts down mnesia application and waits for {nodeup, NodeA} event, after
which it starts up mnesia. Without using this approach (i.e. not
shutting down mnesia on NodeC during network outage) upon healing the
network and reconnecting NodeC to NodeA/B, mnesia would detect
partitioned network condition and stop replicating data between NodeA
and NodeB (which is quite odd because NodeC doesn't have any local
tables and it's undesirable to have its visibility impact nodes inside
the firewall).
The main problem with this setup is that when NodeC looses connection to
NodeA/NodeB, either one of these two nodes would need to periodically
attempt to reconnect to NodeC. However, because {dist_auto_connect,
once} option is used on NodeA/NodeB, net_kernel wouldn't let
re-establishing connection to NodeC unless *both* NodeA and NodeB are
bounced!
The main culprit is the net_kernel's dist_auto_connect option that is an
all or none setting that cannot vary depending on connecting attempt to
a given node. The attached patch (for R12B-1) solves this issue by
introducing an additional kernel option:
{dist_auto_connect, {callback, M, F}}
This option allows to register a callback function M:F/2 with signature:
(Action, Node) -> Mode
Action = connect | disconnect
Node = node()
Mode = once | never | true
that will be called when a Node tries to connect (or looses connection).
Modes once and never are documented in kernel(3), and 'true' means to
continue connection action.
This patch allows to define different connecting behavior for connecting
a@REDACTED and b@REDACTED from connecting behavior of node a@REDACTED (or
b@REDACTED) and c@REDACTED
If others find this option as useful as I do, perhaps we can pursue the
OTP team to merge this patch with the distribution.
Regards,
Serge.
P.S. Here's a sample implementation of this custom function:
nodeA&B.config:
===============
[
{kernel,
[
{dist_auto_connect,
{callback, net_kernel_connector, dist_auto_connect}}
]},
{mnesia,
[
{extra_db_nodes, [a@REDACTED, b@REDACTED]}
]}
].
nodeC.config:
=============
[
{kernel,
[
{dist_auto_connect, never},
{global_groups, [{outside, [c@REDACTED]}]},
{inet_dist_listen_min, 8111},
{inet_dist_listen_max, 8119}
]}
{mnesia,
[
{extra_db_nodes, [a@REDACTED, b@REDACTED]}
]}
].
-module(net_kernel_connector).
-export([dist_auto_connect/2]).
dist_auto_connect(Action, Node) ->
case application:get_env(mnesia, extra_db_nodes) of
{ok, Masters} ->
IamMaster = lists:member(node(), Masters),
NodeIsMaster = lists:member(Node, Masters),
case {IamMaster, NodeIsMaster} of
{true, true} ->
once;
{_, _} ->
has_access(node(), Node)
end;
_ ->
has_access(node(), Node)
end.
has_access(From, To) -> has_access2(host(From), host(To)).
has_access2('hostC', _) -> never;
has_access2(_, _) -> true.
host(Node) ->
L = atom_to_list(Node),
[_, H] = string:tokens(L, "@"),
list_to_atom(H).
-------------- next part --------------
A non-text attachment was scrubbed...
Name: R12-1.net_kernel.patch
Type: application/octet-stream
Size: 2722 bytes
Desc: not available
URL: <http://erlang.org/pipermail/erlang-patches/attachments/20080304/fd702785/attachment.obj>
More information about the erlang-patches
mailing list