Skip to content

Commit

Permalink
Merge 9798c60 into d0b62e8
Browse files Browse the repository at this point in the history
  • Loading branch information
priestjim authored Dec 21, 2018
2 parents d0b62e8 + 9798c60 commit d96742d
Show file tree
Hide file tree
Showing 31 changed files with 673 additions and 152 deletions.
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit d96742d

Please sign in to comment.