THRIFT-2113 Erlang SSL Socket Support
Client: Erlang
Patch: David Robakowski


Project: http://git-wip-us.apache.org/repos/asf/thrift/repo
Commit: http://git-wip-us.apache.org/repos/asf/thrift/commit/a7d6a970
Tree: http://git-wip-us.apache.org/repos/asf/thrift/tree/a7d6a970
Diff: http://git-wip-us.apache.org/repos/asf/thrift/diff/a7d6a970

Branch: refs/heads/master
Commit: a7d6a970339ff11ed60dbb8b73e59b1ed6482acb
Parents: 7ab56e8
Author: David Robakowski <[email protected]>
Authored: Wed Aug 7 05:51:00 2013 +0200
Committer: Nobuaki Sukegawa <[email protected]>
Committed: Sun Nov 1 18:30:46 2015 +0900

----------------------------------------------------------------------
 lib/erl/src/thrift_client_util.erl         |  13 ++-
 lib/erl/src/thrift_socket_server.erl       |  35 ++++--
 lib/erl/src/thrift_sslsocket_transport.erl | 147 ++++++++++++++++++++++++
 test/erl/src/test_client.erl               |   8 ++
 test/erl/src/test_thrift_server.erl        |   8 ++
 5 files changed, 196 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/thrift/blob/a7d6a970/lib/erl/src/thrift_client_util.erl
----------------------------------------------------------------------
diff --git a/lib/erl/src/thrift_client_util.erl 
b/lib/erl/src/thrift_client_util.erl
index 7a11f3a..b51a0b4 100644
--- a/lib/erl/src/thrift_client_util.erl
+++ b/lib/erl/src/thrift_client_util.erl
@@ -41,7 +41,9 @@ split_options([Opt = {OptKey, _} | Rest], ProtoIn, TransIn)
   when OptKey =:= framed;
        OptKey =:= connect_timeout;
        OptKey =:= recv_timeout;
-       OptKey =:= sockopts ->
+       OptKey =:= sockopts;
+       OptKey =:= ssltransport;
+       OptKey =:= ssloptions->
     split_options(Rest, ProtoIn, [Opt | TransIn]).
 
 
@@ -49,10 +51,15 @@ split_options([Opt = {OptKey, _} | Rest], ProtoIn, TransIn)
 %% with the binary protocol
 new(Host, Port, Service, Options)
   when is_integer(Port), is_atom(Service), is_list(Options) ->
-    {ProtoOpts, TransOpts} = split_options(Options),
+    {ProtoOpts, TransOpts0} = split_options(Options),
+
+    {TransportModule, TransOpts2} = case lists:keytake(ssltransport, 1, 
TransOpts0) of
+                                        {value, {_, true}, TransOpts1} -> 
{thrift_sslsocket_transport, TransOpts1};
+                                        false -> {thrift_socket_transport, 
TransOpts0}
+                                    end,
 
     {ok, TransportFactory} =
-        thrift_socket_transport:new_transport_factory(Host, Port, TransOpts),
+        TransportModule:new_transport_factory(Host, Port, TransOpts2),
 
     {ok, ProtocolFactory} = thrift_binary_protocol:new_protocol_factory(
                               TransportFactory, ProtoOpts),

http://git-wip-us.apache.org/repos/asf/thrift/blob/a7d6a970/lib/erl/src/thrift_socket_server.erl
----------------------------------------------------------------------
diff --git a/lib/erl/src/thrift_socket_server.erl 
b/lib/erl/src/thrift_socket_server.erl
index f7c7a02..233b992 100644
--- a/lib/erl/src/thrift_socket_server.erl
+++ b/lib/erl/src/thrift_socket_server.erl
@@ -38,7 +38,9 @@
          listen=null,
          acceptor=null,
          socket_opts=[{recv_timeout, 500}],
-         framed=false
+         framed=false,
+         ssltransport=false,
+         ssloptions=[]
         }).
 
 start(State=#thrift_socket_server{}) ->
@@ -103,8 +105,14 @@ parse_options([{max, Max} | Rest], State) ->
                      Max
              end,
     parse_options(Rest, State#thrift_socket_server{max=MaxInt});
+
 parse_options([{framed, Framed} | Rest], State) when is_boolean(Framed) ->
-    parse_options(Rest, State#thrift_socket_server{framed=Framed}).
+    parse_options(Rest, State#thrift_socket_server{framed=Framed});
+
+parse_options([{ssltransport, SSLTransport} | Rest], State) when 
is_boolean(SSLTransport) ->
+    parse_options(Rest, State#thrift_socket_server{ssltransport=SSLTransport});
+parse_options([{ssloptions, SSLOptions} | Rest], State) when 
is_list(SSLOptions) ->
+    parse_options(Rest, State#thrift_socket_server{ssloptions=SSLOptions}).
 
 start_server(State=#thrift_socket_server{name=Name}) ->
     case Name of
@@ -168,25 +176,28 @@ new_acceptor(State=#thrift_socket_server{max=0}) ->
     State#thrift_socket_server{acceptor=null};
 new_acceptor(State=#thrift_socket_server{listen=Listen,
                                          service=Service, handler=Handler,
-                                         socket_opts=Opts, framed=Framed
+                                         socket_opts=Opts, framed=Framed,
+                                         ssltransport=SslTransport, 
ssloptions=SslOptions
                                         }) ->
     Pid = proc_lib:spawn_link(?MODULE, acceptor_loop,
-                              [{self(), Listen, Service, Handler, Opts, 
Framed}]),
+                              [{self(), Listen, Service, Handler, Opts, 
Framed, SslTransport, SslOptions}]),
     State#thrift_socket_server{acceptor=Pid}.
 
-acceptor_loop({Server, Listen, Service, Handler, SocketOpts, Framed})
+acceptor_loop({Server, Listen, Service, Handler, SocketOpts, Framed, 
SslTransport, SslOptions})
   when is_pid(Server), is_list(SocketOpts) ->
     case catch gen_tcp:accept(Listen) of % infinite timeout
         {ok, Socket} ->
             gen_server:cast(Server, {accepted, self()}),
             ProtoGen = fun() ->
-                               {ok, SocketTransport}   = 
thrift_socket_transport:new(Socket, SocketOpts),
-                               {ok, Transport}         =
-                                   case Framed of
-                                       true  -> 
thrift_framed_transport:new(SocketTransport);
-                                       false -> 
thrift_buffered_transport:new(SocketTransport)
-                                   end,
-                               {ok, Protocol}          = 
thrift_binary_protocol:new(Transport),
+                               {ok, SocketTransport} = case SslTransport of
+                                                           true  -> 
thrift_sslsocket_transport:new(Socket, SocketOpts, SslOptions);
+                                                           false -> 
thrift_socket_transport:new(Socket, SocketOpts)
+                                                       end,
+                               {ok, Transport}       = case Framed of
+                                                           true  -> 
thrift_framed_transport:new(SocketTransport);
+                                                           false -> 
thrift_buffered_transport:new(SocketTransport)
+                                                       end,
+                               {ok, Protocol}        = 
thrift_binary_protocol:new(Transport),
                                {ok, Protocol}
                        end,
             thrift_processor:init({Server, ProtoGen, Service, Handler});

http://git-wip-us.apache.org/repos/asf/thrift/blob/a7d6a970/lib/erl/src/thrift_sslsocket_transport.erl
----------------------------------------------------------------------
diff --git a/lib/erl/src/thrift_sslsocket_transport.erl 
b/lib/erl/src/thrift_sslsocket_transport.erl
new file mode 100644
index 0000000..211153f
--- /dev/null
+++ b/lib/erl/src/thrift_sslsocket_transport.erl
@@ -0,0 +1,147 @@
+%%
+%% Licensed to the Apache Software Foundation (ASF) under one
+%% or more contributor license agreements. See the NOTICE file
+%% distributed with this work for additional information
+%% regarding copyright ownership. The ASF licenses this file
+%% to you under the Apache License, Version 2.0 (the
+%% "License"); you may not use this file except in compliance
+%% with the License. You may obtain a copy of the License at
+%%
+%%   http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+%%
+-module(thrift_sslsocket_transport).
+
+-include("thrift_transport_behaviour.hrl").
+
+-behaviour(thrift_transport).
+
+-export([new/3,
+         write/2, read/2, flush/1, close/1,
+
+         new_transport_factory/3]).
+
+%% Export only for the transport factory
+-export([new/2]).
+
+-record(data, {socket,
+               recv_timeout=infinity}).
+-type state() :: #data{}.
+
+%% The following "local" record is filled in by parse_factory_options/2
+%% below. These options can be passed to new_protocol_factory/3 in a
+%% proplists-style option list. They're parsed like this so it is an O(n)
+%% operation instead of O(n^2)
+-record(factory_opts, {connect_timeout = infinity,
+                       sockopts = [],
+                       framed = false,
+                       ssloptions = []}).
+
+parse_factory_options([], Opts) ->
+    Opts;
+parse_factory_options([{framed, Bool} | Rest], Opts) when is_boolean(Bool) ->
+    parse_factory_options(Rest, Opts#factory_opts{framed=Bool});
+parse_factory_options([{sockopts, OptList} | Rest], Opts) when 
is_list(OptList) ->
+    parse_factory_options(Rest, Opts#factory_opts{sockopts=OptList});
+parse_factory_options([{connect_timeout, TO} | Rest], Opts) when TO =:= 
infinity; is_integer(TO) ->
+    parse_factory_options(Rest, Opts#factory_opts{connect_timeout=TO});
+parse_factory_options([{ssloptions, SslOptions} | Rest], Opts) when 
is_list(SslOptions) ->
+    parse_factory_options(Rest, Opts#factory_opts{ssloptions=SslOptions}).
+
+new(Socket, SockOpts, SslOptions) when is_list(SockOpts), is_list(SslOptions) 
->
+    inet:setopts(Socket, [{active, false}]), %% => prevent the ssl handshake 
messages get lost
+
+    %% upgrade to an ssl socket
+    case catch ssl:ssl_accept(Socket, SslOptions) of % infinite timeout
+        {ok, SslSocket} ->
+            new(SslSocket, SockOpts);
+        {error, Reason} ->
+            exit({error, Reason});
+        Other ->
+            error_logger:error_report(
+              [{application, thrift},
+               "SSL accept failed error",
+               lists:flatten(io_lib:format("~p", [Other]))]),
+            exit({error, ssl_accept_failed})
+    end.
+
+new(SslSocket, SockOpts) ->
+    State =
+        case lists:keysearch(recv_timeout, 1, SockOpts) of
+            {value, {recv_timeout, Timeout}}
+              when is_integer(Timeout), Timeout > 0 ->
+                #data{socket=SslSocket, recv_timeout=Timeout};
+            _ ->
+                #data{socket=SslSocket}
+        end,
+    thrift_transport:new(?MODULE, State).
+
+%% Data :: iolist()
+write(This = #data{socket = Socket}, Data) ->
+    {This, ssl:send(Socket, Data)}.
+
+read(This = #data{socket=Socket, recv_timeout=Timeout}, Len)
+  when is_integer(Len), Len >= 0 ->
+    case ssl:recv(Socket, Len, Timeout) of
+        Err = {error, timeout} ->
+            error_logger:info_msg("read timeout: peer conn ~p", 
[inet:peername(Socket)]),
+            ssl:close(Socket),
+            {This, Err};
+        Data ->
+            {This, Data}
+    end.
+
+%% We can't really flush - everything is flushed when we write
+flush(This) ->
+    {This, ok}.
+
+close(This = #data{socket = Socket}) ->
+    {This, ssl:close(Socket)}.
+
+%%%% FACTORY GENERATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%
+%% Generates a "transport factory" function - a fun which returns a 
thrift_transport()
+%% instance.
+%% This can be passed into a protocol factory to generate a connection to a
+%% thrift server over a socket.
+%%
+new_transport_factory(Host, Port, Options) ->
+    ParsedOpts = parse_factory_options(Options, #factory_opts{}),
+
+    F = fun() ->
+                SockOpts = [binary,
+                            {packet, 0},
+                            {active, false},
+                            {nodelay, true} |
+                            ParsedOpts#factory_opts.sockopts],
+                case catch gen_tcp:connect(Host, Port, SockOpts,
+                                           
ParsedOpts#factory_opts.connect_timeout) of
+                    {ok, Sock} ->
+                        SslSock = case catch ssl:connect(Sock, 
ParsedOpts#factory_opts.ssloptions,
+                                                         
ParsedOpts#factory_opts.connect_timeout) of
+                                      {ok, SslSocket} ->
+                                          SslSocket;
+                                      Other ->
+                                          error_logger:info_msg("error while 
connecting over ssl - reason: ~p~n", [Other]),
+                                          catch gen_tcp:close(Sock),
+                                          exit(error)
+                                  end,
+                        {ok, Transport} = 
thrift_sslsocket_transport:new(SslSock, SockOpts),
+                        {ok, BufTransport} =
+                            case ParsedOpts#factory_opts.framed of
+                                true  -> 
thrift_framed_transport:new(Transport);
+                                false -> 
thrift_buffered_transport:new(Transport)
+                            end,
+                        {ok, BufTransport};
+                    Error  ->
+                        Error
+                end
+        end,
+    {ok, F}.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/thrift/blob/a7d6a970/test/erl/src/test_client.erl
----------------------------------------------------------------------
diff --git a/test/erl/src/test_client.erl b/test/erl/src/test_client.erl
index 8cfeb8b..7b9efd6 100644
--- a/test/erl/src/test_client.erl
+++ b/test/erl/src/test_client.erl
@@ -47,6 +47,14 @@ parse_args([Head | Rest], Opts) ->
                     _Else ->
                         Opts
                 end;
+            "--ssl" ->
+                ssl:start(),
+                SslOptions =
+                    {ssloptions, [
+                        {certfile, "../keys/client.crt"}
+                        ,{keyfile, "../keys/server.key"}
+                    ]},
+                Opts#options{client_opts = [{ssltransport, true} | [SslOptions 
| Opts#options.client_opts]]};
             "--protocol=binary" ->
                 % TODO: Enable JSON protocol
                 Opts;

http://git-wip-us.apache.org/repos/asf/thrift/blob/a7d6a970/test/erl/src/test_thrift_server.erl
----------------------------------------------------------------------
diff --git a/test/erl/src/test_thrift_server.erl 
b/test/erl/src/test_thrift_server.erl
index 51457f5..884eb9e 100644
--- a/test/erl/src/test_thrift_server.erl
+++ b/test/erl/src/test_thrift_server.erl
@@ -47,6 +47,14 @@ parse_args([Head | Rest], Opts) ->
                     _Else ->
                         Opts
                 end;
+            "--ssl" ->
+                ssl:start(),
+                SslOptions =
+                    {ssloptions, [
+                         {certfile, "../keys/server.crt"}
+                        ,{keyfile,  "../keys/server.key"}
+                    ]},
+                Opts#options{server_opts = [{ssltransport, true} | [SslOptions 
| Opts#options.server_opts]]};
             "--protocol=" ++ _ -> Opts;
             _Else ->
                 erlang:error({bad_arg, Head})

Reply via email to