Skip to main content

rust_thrift_tls/
tls_threaded.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use log;
19use std::net::TcpListener;
20use std::sync::{Arc, Mutex};
21use threadpool::ThreadPool;
22
23use thrift::protocol::{
24    TInputProtocol, TInputProtocolFactory, TOutputProtocol, TOutputProtocolFactory,
25};
26use thrift::server::TProcessor;
27use thrift::transport::{TIoChannel, TReadTransportFactory, TWriteTransportFactory};
28use thrift::{ApplicationError, ApplicationErrorKind};
29
30use rustls::ServerSession as RusTLSServerSession;
31use rustls::StreamOwned as RusTLSStream;
32use rustls::{RootCertStore, ServerConfig, ServerSession};
33
34use super::{TLSStream, TLSTTcpChannel, X509Credentials};
35
36type ConnectionHook = fn(TLSStream<ServerSession>);
37
38/// Fixed-size thread-pool blocking Thrift server.
39///
40/// A `TLSTServer` listens on a given address and submits accepted connections
41/// to an **unbounded** queue. Connections from this queue are serviced by
42/// the first available worker thread from a **fixed-size** thread pool. Each
43/// accepted connection is handled by that worker thread, and communication
44/// over this thread occurs sequentially and synchronously (i.e. calls block).
45/// Accepted connections have an input half and an output half, each of which
46/// uses a `TTransport` and `TInputProtocol`/`TOutputProtocol` to translate
47/// messages to and from byes. Any combination of `TInputProtocol`, `TOutputProtocol`
48/// and `TTransport` may be used.
49pub struct TLSTServer<PRC, RTF, IPF, WTF, OPF>
50where
51    PRC: TProcessor + Send + Sync + 'static,
52    RTF: TReadTransportFactory + 'static,
53    IPF: TInputProtocolFactory + 'static,
54    WTF: TWriteTransportFactory + 'static,
55    OPF: TOutputProtocolFactory + 'static,
56{
57    r_trans_factory: RTF,
58    i_proto_factory: IPF,
59    w_trans_factory: WTF,
60    o_proto_factory: OPF,
61    processor: Arc<PRC>,
62    worker_pool: ThreadPool,
63    tls_config: Arc<ServerConfig>,
64    connection_hook: Option<ConnectionHook>,
65}
66
67impl<PRC, RTF, IPF, WTF, OPF> TLSTServer<PRC, RTF, IPF, WTF, OPF>
68where
69    PRC: TProcessor + Send + Sync + 'static,
70    RTF: TReadTransportFactory + 'static,
71    IPF: TInputProtocolFactory + 'static,
72    WTF: TWriteTransportFactory + 'static,
73    OPF: TOutputProtocolFactory + 'static,
74{
75    /// Create a `TLSTServer`.
76    ///
77    /// Each accepted connection has an input and output half, each of which
78    /// requires a `TTransport` and `TProtocol`. `TLSTServer` uses
79    /// `read_transport_factory` and `input_protocol_factory` to create
80    /// implementations for the input, and `write_transport_factory` and
81    /// `output_protocol_factory` to create implementations for the output.
82    ///     
83    /// `root_cert_store` contains the trust anchors. If `None`, the default
84    /// (embedded) will be used
85    /// `require_client_auth` is true if client authentication is enforced.
86    /// `connection_hook` is an optional callback function that is executed
87    /// right after a new connection is established and typically before
88    /// TLS handshake is performed.
89    pub fn new(
90        read_transport_factory: RTF,
91        input_protocol_factory: IPF,
92        write_transport_factory: WTF,
93        output_protocol_factory: OPF,
94        processor: PRC,
95        num_workers: usize,
96        key_pair: X509Credentials,
97        root_cert_store: Option<RootCertStore>,
98        require_client_auth: bool,
99        connection_hook: Option<ConnectionHook>,
100    ) -> TLSTServer<PRC, RTF, IPF, WTF, OPF> {
101        TLSTServer {
102            r_trans_factory: read_transport_factory,
103            i_proto_factory: input_protocol_factory,
104            w_trans_factory: write_transport_factory,
105            o_proto_factory: output_protocol_factory,
106            processor: Arc::new(processor),
107            worker_pool: ThreadPool::with_name("Thrift service processor".to_owned(), num_workers),
108            tls_config: super::make_tls_server_config(
109                key_pair,
110                root_cert_store,
111                require_client_auth,
112            ),
113            connection_hook: connection_hook,
114        }
115    }
116
117    /// Listen for incoming connections on `listen_address`.
118    ///
119    /// `listen_address` should be in the form `host:port`,
120    /// for example: `127.0.0.1:8080`.
121    ///
122    /// Return `()` if successful.
123    ///
124    /// Return `Err` when the server cannot bind to `listen_address` or there
125    /// is an unrecoverable error.
126    pub fn listen(&mut self, listen_address: &str) -> thrift::Result<()> {
127        let listener = TcpListener::bind(listen_address)?;
128        for stream in listener.incoming() {
129            match stream {
130                Ok(s) => {
131                    let tls_session = RusTLSServerSession::new(&self.tls_config);
132                    let so = RusTLSStream::new(tls_session, s);
133                    let ts = Arc::new(Mutex::new(so));
134                    let (i_prot, o_prot) = self.new_protocols_for_connection(ts.clone())?;
135                    let processor = self.processor.clone();
136                    let ch = self.connection_hook;
137                    self.worker_pool.execute(move || {
138                        if ch.is_some() {
139                            ch.unwrap()(ts)
140                        }
141                        handle_incoming_connection(processor, i_prot, o_prot)
142                    });
143                }
144                Err(e) => {
145                    log::warn!("failed to accept remote connection with error {:?}", e);
146                }
147            }
148        }
149
150        Err(thrift::Error::Application(ApplicationError {
151            kind: ApplicationErrorKind::Unknown,
152            message: "aborted listen loop".into(),
153        }))
154    }
155
156    fn new_protocols_for_connection(
157        &mut self,
158        stream: TLSStream<RusTLSServerSession>,
159    ) -> thrift::Result<(
160        Box<dyn TInputProtocol + Send>,
161        Box<dyn TOutputProtocol + Send>,
162    )> {
163        // create the shared tcp stream
164        let channel = TLSTTcpChannel::with_stream(stream);
165
166        // split it into two - one to be owned by the
167        // input tran/proto and the other by the output
168        let (r_chan, w_chan) = channel.split()?;
169
170        // input protocol and transport
171        let r_tran = self.r_trans_factory.create(Box::new(r_chan));
172        let i_prot = self.i_proto_factory.create(r_tran);
173
174        // output protocol and transport
175        let w_tran = self.w_trans_factory.create(Box::new(w_chan));
176        let o_prot = self.o_proto_factory.create(w_tran);
177
178        Ok((i_prot, o_prot))
179    }
180}
181
182fn handle_incoming_connection<PRC>(
183    processor: Arc<PRC>,
184    i_prot: Box<dyn TInputProtocol>,
185    o_prot: Box<dyn TOutputProtocol>,
186) where
187    PRC: TProcessor,
188{
189    let mut i_prot = i_prot;
190    let mut o_prot = o_prot;
191    loop {
192        let r = processor.process(&mut *i_prot, &mut *o_prot);
193        if let Err(e) = r {
194            log::debug!("processor completed with error: {:?}", e);
195            break;
196        }
197    }
198}