Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Project Phoenix #80

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 2 additions & 3 deletions .gitignore
@@ -1,4 +1,3 @@
.rebar3
_*
.eunit
*.o
Expand All @@ -14,13 +13,13 @@ rebar.lock
ebin
log
erl_crash.dump
.rebar
_rel
_deps
_plugins
_tdeps
_plt
logs
_build
rebar3
rebar
*.crashdump
.exrc
6 changes: 2 additions & 4 deletions .travis.yml
@@ -1,12 +1,10 @@
sudo: false
language: erlang
otp_release:
- "19.1"
- "19.2"
- "21.0"
cache:
directories:
- $HOME/otp/19.1
- $HOME/otp/19.2
- $HOME/otp/21.0
- $HOME/.cache/rebar3
- _plt
install: "true"
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,15 @@

Below is a non-exhaustive list of changes between `gen_rpc` versions.

## 3.0.0

- Deprecate support for Erlang < 21.0
- Support monitoring nodes
- Support EC SSL certificates
- Support cookie per node configuration
- Support external cookie validation mechanism
- Support keepalive gen_server that actively keeps a client connection alive

## 2.1.0

- Support multiple connections per node using aribtrary keys.
Expand Down
6 changes: 5 additions & 1 deletion README.md
Expand Up @@ -13,7 +13,7 @@

To build this project you need to have the following:

* **Erlang/OTP** >= 19.1
* **Erlang/OTP** >= 21.0

* **git** >= 1.7

Expand Down Expand Up @@ -94,6 +94,8 @@ For more information on what the functions below do, run `erl -man rpc`.

- `eval_everywhere(Module, Function, Args)` and `eval_everywhere(NodesOrNodesWithKeys, Module, Function, Args)`: Multi-node version of the `cast` function.

- `monitor_node(Node, Flag)` and `monitor_node(Node, Flag, MessageType)`: Sends messages of node connects and disconnects to the subscribed process. Set `MessageType` to `gen_server` to send a `gen_server:cast` as a state change message, `gen_fsm` to send a `gen_fsm:send_all_state_event` as a state change message or `simple` to send a simple message upon node state change. Please note that in contrast to `erlang:monitor_node`, calling `gen_rpc:monitor_node` multiple times will result to only one registration per process.

### Per-Key Sharding

`gen_rpc` supports multiple outgoing connections per node using a key of arbitrary type to differentiate between connections.
Expand Down Expand Up @@ -321,3 +323,5 @@ Please see [CONTRIBUTING.md](CONTRIBUTING.md)
### Contributors:

- [Edward Tsang](https://github.com/linearregression)
- [getong](https://github.com/getong)
- [JianBo He](https://github.com/HJianBo)
2 changes: 1 addition & 1 deletion TODO.md
Expand Up @@ -2,4 +2,4 @@

This is a list of pending features or code technical debt for `gen_rpc`:

- Implement `net_kernel:monitor_nodes/2` functionality
- Alternative Distribution Driver that transparently uses gen_rpc
1 change: 1 addition & 0 deletions include/guards.hrl
Expand Up @@ -7,6 +7,7 @@
-define(is_null(A), A =:= undefined orelse A =:= null).
-define(is_true(A), A =:= true orelse A =:= "true" orelse A =:= <<"true">>).
-define(is_false(A), A =:= false orelse A =:= "false" orelse A =:= <<"false">>).
-define(is_boolean(A), (?is_true(A)) orelse (?is_false(A))).
-define(is_process(A), is_pid(A) orelse is_atom(A)).
-define(is_limit(A), (is_integer(A) andalso A >= 0) orelse A =:= infinity).
-define(is_timeout(A), (is_integer(A) andalso A >= 0) orelse A =:= infinity).
Expand Down
3 changes: 2 additions & 1 deletion include/ssl.hrl
Expand Up @@ -30,6 +30,8 @@
"ECDH-ECDSA-AES256-SHA","ECDH-RSA-AES256-SHA","AES256-SHA","ECDHE-ECDSA-AES128-SHA",
"ECDHE-RSA-AES128-SHA","DHE-DSS-AES128-SHA","ECDH-ECDSA-AES128-SHA","ECDH-RSA-AES128-SHA","AES128-SHA"]},
{secure_renegotiate,true},
{honor_ecc_order,true},
{honor_cipher_order,true},
{reuse_sessions,true},
{versions,['tlsv1.2','tlsv1.1']},
{verify,verify_peer},
Expand All @@ -38,7 +40,6 @@

-define(SSL_DEFAULT_SERVER_OPTS, [{fail_if_no_peer_cert,true},
{log_alert,false},
{honor_cipher_order,true},
{client_renegotiation,true}]).

-define(SSL_DEFAULT_CLIENT_OPTS, [{server_name_indication,disable},
Expand Down
2 changes: 1 addition & 1 deletion package.exs
Expand Up @@ -3,7 +3,7 @@ defmodule GenRPC.Mixfile do

def project do
[app: :gen_rpc,
version: "2.0.0",
version: "3.0.0",
description: "A scalable RPC library for Erlang-VM based languages",
package: package]
end
Expand Down
14 changes: 14 additions & 0 deletions priv/ec_ssl/ca.cert.pem
@@ -0,0 +1,14 @@
-----BEGIN CERTIFICATE-----
MIICOTCCAd6gAwIBAgICMwwwCgYIKoZIzj0EAwIwdDELMAkGA1UEBhMCVVMxEzAR
BgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEDAOBgNV
BAoMB2dlbl9ycGMxJjAkBgNVBAMMHWdlbl9ycGMgQ2VydGlmaWNhdGUgQXV0aG9y
aXR5MB4XDTE4MTIyMTIzMTUwNVoXDTM4MTIxNjIzMTUwNVowdDELMAkGA1UEBhMC
VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x
EDAOBgNVBAoMB2dlbl9ycGMxJjAkBgNVBAMMHWdlbl9ycGMgQ2VydGlmaWNhdGUg
QXV0aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpZT2IwQ8QcG0BMeY
9OyzZN6lcTsNFpXv3dhEAsug8zhY5uUz1GVRWDFAtjrlFQ7mOERQHUlFiFXqRoYm
2c/BTKNgMF4wDwYDVR0TAQH/BAUwAwEB/zALBgNVHQ8EBAMCAQYwHQYDVR0OBBYE
FAF6LCa2P8J2MNb33CWg82MCfAgNMB8GA1UdIwQYMBaAFAF6LCa2P8J2MNb33CWg
82MCfAgNMAoGCCqGSM49BAMCA0kAMEYCIQDGq0LtZ2O7ks0m+wc3a7qJN8WCyeAz
vK8tGQOKLzTlSQIhAIMJcudk09EZt7cqtQUt5Fc6U8P9OsEPXe8WGginL/FU
-----END CERTIFICATE-----
15 changes: 15 additions & 0 deletions priv/ec_ssl/gen_rpc_master@127.0.0.1.cert.pem
@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICTTCCAfOgAwIBAgIDAI7gMAoGCCqGSM49BAMCMHQxCzAJBgNVBAYTAlVTMRMw
EQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRAwDgYD
VQQKDAdnZW5fcnBjMSYwJAYDVQQDDB1nZW5fcnBjIENlcnRpZmljYXRlIEF1dGhv
cml0eTAeFw0xODEyMjEyMzE1MzNaFw0zODEyMTYyMzE1MzNaMG8xCzAJBgNVBAYT
AlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2Nv
MRAwDgYDVQQKDAdnZW5fcnBjMSEwHwYDVQQDDBhnZW5fcnBjX21hc3RlckAxMjcu
MC4wLjEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASXE5wPPspbIS4IHn7xs3JJ
hqqVtS710Ys9q2eNzKSkFJ9Av0mXKwhw8gsYbCyCZtEw89QV1Yzg2f8MARzfU6nG
o3kwdzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAdBgNVHSUEFjAUBggrBgEFBQcD
AQYIKwYBBQUHAwIwHQYDVR0OBBYEFB6s40nETDtvzmXuxeatcNdjTisVMB8GA1Ud
IwQYMBaAFAF6LCa2P8J2MNb33CWg82MCfAgNMAoGCCqGSM49BAMCA0gAMEUCIHHt
aCD+Ihp/ATDdapFL5fbYJPAI0Ah0bFdul5ip4ekQAiEA11BZwECTDQo1/I/58kQi
E8y9hyoxh8FD/SzimQ7qZHY=
-----END CERTIFICATE-----
8 changes: 8 additions & 0 deletions priv/ec_ssl/gen_rpc_master@127.0.0.1.key.pem
@@ -0,0 +1,8 @@
-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIKegvLV7VLEr7dOKdsilVH06/edAt9Mcc+k3raAOtIM1oAoGCCqGSM49
AwEHoUQDQgAElxOcDz7KWyEuCB5+8bNySYaqlbUu9dGLPatnjcykpBSfQL9JlysI
cPILGGwsgmbRMPPUFdWM4Nn/DAEc31Opxg==
-----END EC PRIVATE KEY-----
15 changes: 15 additions & 0 deletions priv/ec_ssl/gen_rpc_slave@127.0.0.1.cert.pem
@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICTDCCAfKgAwIBAgIDAMEGMAoGCCqGSM49BAMCMHQxCzAJBgNVBAYTAlVTMRMw
EQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRAwDgYD
VQQKDAdnZW5fcnBjMSYwJAYDVQQDDB1nZW5fcnBjIENlcnRpZmljYXRlIEF1dGhv
cml0eTAeFw0xODEyMjEyMzE1MjJaFw0zODEyMTYyMzE1MjJaMG4xCzAJBgNVBAYT
AlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2Nv
MRAwDgYDVQQKDAdnZW5fcnBjMSAwHgYDVQQDDBdnZW5fcnBjX3NsYXZlQDEyNy4w
LjAuMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBmLXBysYtqQjvxAVxfihlQ5
OTDCJNNADxa6uuaW4BElOIc4tWEzouN+yCCjxI4AMs3g/7RitHHwYt6bnAuY8Iyj
eTB3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMB0GA1UdJQQWMBQGCCsGAQUFBwMB
BggrBgEFBQcDAjAdBgNVHQ4EFgQUCt8m+xW2pR3O6vx1zqBWmTH+Z5AwHwYDVR0j
BBgwFoAUAXosJrY/wnYw1vfcJaDzYwJ8CA0wCgYIKoZIzj0EAwIDSAAwRQIgR1PV
dClqz5h5W1RGHO2yffAqlCinWDzzCg2VO/1eD24CIQC7l0vlTGsoBtjhciGlI+ej
S2Ravs4obm9OgS5YEIFPCw==
-----END CERTIFICATE-----
8 changes: 8 additions & 0 deletions priv/ec_ssl/gen_rpc_slave@127.0.0.1.key.pem
@@ -0,0 +1,8 @@
-----BEGIN EC PARAMETERS-----
BggqhkjOPQMBBw==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIGSRUX8RPDRYPL1RAn3LM2yIW56F2nuPH43PKo/JKKEJoAoGCCqGSM49
AwEHoUQDQgAEGYtcHKxi2pCO/EBXF+KGVDk5MMIk00APFrq65pbgESU4hzi1YTOi
437IIKPEjgAyzeD/tGK0cfBi3pucC5jwjA==
-----END EC PRIVATE KEY-----
14 changes: 7 additions & 7 deletions rebar.config
Expand Up @@ -2,8 +2,8 @@
%%% ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et:
%%%

%%% Require OTP 19.1 at a bare minimum
{minimum_otp_vsn, "19.1"}.
%%% Require OTP 21.0 at a bare minimum
{minimum_otp_vsn, "21.0"}.

%% Plugins
{plugins, [rebar3_hex]}.
Expand Down Expand Up @@ -32,8 +32,8 @@
]}.

{deps, [
{hut, "~> 1.2"},
{ssl_verify_fun, "~> 1.1"}
{hut, "~> 1.2.1"},
{ssl_verify_fun, "~> 1.1.4"}
]}.

{profiles, [
Expand All @@ -44,16 +44,16 @@
warnings_as_errors,
export_all,
no_inline_list_funcs]},
{deps, [{lager, "~> 3.0"},
{eunit_formatters, "~> 0.3"}
{deps, [{lager, "~> 3.6.7"},
{eunit_formatters, "~> 0.5"}
]}
]},
{dev, [
{erl_opts, [{d,'HUT_LAGER'},
{parse_transform, lager_transform},
warnings_as_errors,
no_inline_list_funcs]},
{deps, [{lager, "~> 3.0"},
{deps, [{lager, "~> 3.6.7"},
{sync, {git, "git://github.com/rustyio/sync.git", {branch, "master"}}}
]}
]}
Expand Down
Binary file added rebar3
Binary file not shown.
109 changes: 58 additions & 51 deletions src/driver/gen_rpc_driver_ssl.erl
Expand Up @@ -30,12 +30,13 @@
get_peer/1,
send/2,
activate_socket/1,
authenticate_server/1,
authenticate_to_server/2,
authenticate_client/3,
copy_sock_opts/2,
set_controlling_process/2,
set_send_timeout/2,
set_acceptor_opts/1]).
set_acceptor_opts/1,
getstat/2]).

%%% ===================================================
%%% Public API
Expand All @@ -62,14 +63,13 @@ listen(Port) when is_integer(Port) ->
SslOpts = merge_ssl_options(server, undefined),
ssl:listen(Port, SslOpts).

-spec accept(ssl:sslsocket()) -> ok | {error, term()}.
-spec accept(ssl:sslsocket()) -> {ok, ssl:sslsocket()} | {error, term()}.
accept(Socket) when is_tuple(Socket) ->
{ok, TSocket} = ssl:transport_accept(Socket, infinity),
case ssl:ssl_accept(TSocket) of
ok ->
{ok, TSocket};
Error ->
Error
case ssl:handshake(TSocket) of
{ok, SslSocket} ->
{ok, SslSocket};
Error -> Error
end.

-spec send(ssl:sslsocket(), binary()) -> ok | {error, term()}.
Expand All @@ -92,11 +92,10 @@ activate_socket(Socket) when is_tuple(Socket) ->
ok.

%% Authenticate to a server
-spec authenticate_server(ssl:sslsocket()) -> ok | {error, {badtcp | badrpc, term()}}.
authenticate_server(Socket) ->
Cookie = erlang:get_cookie(),
NodeStr = erlang:atom_to_list(node()),
Packet = erlang:term_to_binary({gen_rpc_authenticate_connection, NodeStr, Cookie}),
-spec authenticate_to_server(atom(), ssl:sslsocket()) -> ok | {error, {badtcp | badrpc, term()}}.
authenticate_to_server(Node, Socket) ->
Cookie = gen_rpc_helper:get_cookie_per_node(Node),
Packet = erlang:term_to_binary({gen_rpc_authenticate_connection, node(), Cookie}),
SendTO = gen_rpc_helper:get_send_timeout(undefined),
RecvTO = gen_rpc_helper:get_call_receive_timeout(undefined),
ok = set_send_timeout(Socket, SendTO),
Expand Down Expand Up @@ -135,45 +134,49 @@ authenticate_server(Socket) ->
%% Authenticate a connected client
-spec authenticate_client(ssl:sslsocket(), tuple(), binary()) -> ok | {error, {badtcp | badrpc, term()}}.
authenticate_client(Socket, Peer, Data) ->
Cookie = erlang:get_cookie(),
try erlang:binary_to_term(Data) of
{gen_rpc_authenticate_connection, Node, Cookie} ->
PeerCert = extract_peer_certificate(Socket),
{SocketResponse, AuthResult} = case ssl_verify_hostname:verify_cert_hostname(PeerCert, Node) of
{fail, AuthReason} ->
?log(error, "event=node_certificate_mismatch socket=\"~s\" peer=\"~s\" reason=\"~p\"",
[gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), AuthReason]),
{{gen_rpc_connection_rejected,node_certificate_mismatch}, {error,{badrpc,node_certificate_mismatch}}};
{valid, _Hostname} ->
?log(debug, "event=certificate_validated socket=\"~s\" peer=\"~s\"",
[gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer)]),
{gen_rpc_connection_authenticated, ok}
end,
Packet = erlang:term_to_binary(SocketResponse),
case send(Socket, Packet) of
{error, Reason} ->
?log(error, "event=transmission_failed socket=\"~s\" peer=\"~s\" reason=\"~p\"",
[gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Reason]),
{error, {badtcp,Reason}};
ok ->
?log(debug, "event=transmission_succeeded socket=\"~s\" peer=\"~s\"",
[gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer)]),
ok = activate_socket(Socket),
AuthResult
{gen_rpc_authenticate_connection, Node, Cookie} when is_atom(Node), is_atom(Cookie) ->
ValidCookie = gen_rpc_helper:get_cookie_per_node(Node),
if
ValidCookie == Cookie ->
PeerCert = extract_peer_certificate(Socket),
NodeStr = gen_rpc_helper:to_string(Node),
{SocketResponse, AuthResult} = case ssl_verify_hostname:verify_cert_hostname(PeerCert, NodeStr) of
{fail, AuthReason} ->
?log(error, "event=node_certificate_mismatch socket=\"~s\" peer=\"~s\" node=\"~s\" reason=\"~p\"",
[gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Node, AuthReason]),
{{gen_rpc_connection_rejected,node_certificate_mismatch}, {error,{badrpc,node_certificate_mismatch}}};
{valid, _Hostname} ->
?log(debug, "event=certificate_validated socket=\"~s\" peer=\"~s\" node=\"~s\"",
[gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Node]),
{gen_rpc_connection_authenticated, ok}
end,
Packet = erlang:term_to_binary(SocketResponse),
case send(Socket, Packet) of
{error, Reason} ->
?log(error, "event=transmission_failed socket=\"~s\" peer=\"~s\" node=\"~s\" reason=\"~p\"",
[gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Node, Reason]),
{error, {badtcp,Reason}};
ok ->
?log(debug, "event=transmission_succeeded socket=\"~s\" peer=\"~s\" node=\"~s\"",
[gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Node]),
ok = activate_socket(Socket),
AuthResult
end;
true ->
?log(error, "event=invalid_cookie_received socket=\"~s\" peer=\"~s\" node=\"~s\"",
[gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Node]),
Packet = erlang:term_to_binary({gen_rpc_connection_rejected, invalid_cookie}),
ok = case send(Socket, Packet) of
{error, Reason} ->
?log(error, "event=transmission_failed socket=\"~s\" peer=\"~s\" node=\"~s\" reason=\"~p\"",
[gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Node, Reason]);
ok ->
?log(debug, "event=transmission_succeeded socket=\"~s\" peer=\"~s\" node=\"~s\"",
[gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Node])
end,
{error, {badrpc,invalid_cookie}}
end;
{gen_rpc_authenticate_connection, _Node, _IncorrectCookie} ->
?log(error, "event=invalid_cookie_received socket=\"~s\" peer=\"~s\"",
[gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer)]),
Packet = erlang:term_to_binary({gen_rpc_connection_rejected, invalid_cookie}),
ok = case send(Socket, Packet) of
{error, Reason} ->
?log(error, "event=transmission_failed socket=\"~s\" peer=\"~s\" reason=\"~p\"",
[gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), Reason]);
ok ->
?log(debug, "event=transmission_succeeded socket=\"~s\" peer=\"~s\"",
[gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer)])
end,
{error, {badrpc,invalid_cookie}};
OtherData ->
?log(debug, "event=erroneous_data_received socket=\"~s\" peer=\"~s\" data=\"~p\"",
[gen_rpc_helper:socket_to_string(Socket), gen_rpc_helper:peer_to_string(Peer), OtherData]),
Expand Down Expand Up @@ -207,12 +210,16 @@ set_acceptor_opts(Socket) when is_tuple(Socket) ->
ok = ssl:setopts(Socket, [{send_timeout, gen_rpc_helper:get_send_timeout(undefined)}]),
ok.

-spec getstat(ssl:sslsocket(), list()) -> ok | {error, any()}.
getstat(Socket, OptNames) ->
ssl:getstat(Socket, OptNames).

%%% ===================================================
%%% Private functions
%%% ===================================================
merge_ssl_options(client, Node) ->
{ok, ExtraOpts} = application:get_env(?APP, ssl_client_options),
NodeStr = atom_to_list(Node),
NodeStr = gen_rpc_helper:to_string(Node),
DefaultOpts = lists:append(?SSL_DEFAULT_COMMON_OPTS, ?SSL_DEFAULT_CLIENT_OPTS),
VerifyOpts = [{verify_fun, {fun ssl_verify_hostname:verify_fun/3,[{check_hostname,NodeStr}]}}|DefaultOpts],
gen_rpc_helper:merge_sockopt_lists(ExtraOpts, VerifyOpts);
Expand Down