1 /*!
2 * @file
3 * @brief connection_handler for the detection of user protocol.
4 */
5
6 #include <arataga/acl_handler/connection_handler_ifaces.hpp>
7 #include <arataga/acl_handler/buffers.hpp>
8 #include <arataga/acl_handler/handler_factories.hpp>
9
10 #include <arataga/utils/overloaded.hpp>
11
12 namespace arataga::acl_handler
13 {
14
15 namespace handlers::protocol_detection
16 {
17
18 class handler_t : public connection_handler_t
19 {
20 //! A time when the connection was accepted.
21 std::chrono::steady_clock::time_point m_created_at;
22
23 //! The first chunk for the connection.
24 first_chunk_t m_first_chunk;
25 //! In-buffer for parsing incoming data.
26 in_external_buffer_t m_in_buffer;
27
28 public :
handler_t(handler_context_holder_t ctx,handler_context_t::connection_id_t id,asio::ip::tcp::socket connection)29 handler_t(
30 handler_context_holder_t ctx,
31 handler_context_t::connection_id_t id,
32 asio::ip::tcp::socket connection )
33 : connection_handler_t{ std::move(ctx), id, std::move(connection) }
34 , m_created_at{ std::chrono::steady_clock::now() }
35 , m_first_chunk{ context().config().io_chunk_size() }
36 , m_in_buffer{
37 m_first_chunk.buffer(),
38 // There is no incoming data at the moment.
39 m_first_chunk.capacity()
40 }
41 {}
42
43 protected:
44 void
on_start_impl(delete_protector_t delete_protector)45 on_start_impl( delete_protector_t delete_protector ) override
46 {
47 wrap_action_and_handle_exceptions(
48 delete_protector,
49 [this]( delete_protector_t, can_throw_t can_throw ) {
50 // A new connection has to be reflected in the stats.
51 context().stats_inc_connection_count( connection_type_t::generic );
52
53 // The first part of data has to be read and analyzed.
54 read_some(
55 can_throw,
56 m_connection,
57 m_in_buffer,
58 [this]( delete_protector_t delete_protector,
59 can_throw_t can_throw )
60 {
61 analyze_data_read( delete_protector, can_throw );
62 } );
63 } );
64 }
65
66 void
on_timer_impl(delete_protector_t delete_protector)67 on_timer_impl( delete_protector_t delete_protector ) override
68 {
69 if( std::chrono::steady_clock::now() >= m_created_at +
70 context().config().protocol_detection_timeout() )
71 {
72 wrap_action_and_handle_exceptions(
73 delete_protector,
74 [this](
75 delete_protector_t delete_protector,
76 can_throw_t can_throw )
77 {
78 log_and_remove_connection(
79 delete_protector,
80 can_throw,
81 remove_reason_t::current_operation_timed_out,
82 spdlog::level::warn,
83 "protocol-detection timed out" );
84 } );
85 }
86 }
87
88 public:
89 arataga::utils::string_literal_t
name() const90 name() const noexcept override
91 {
92 using namespace arataga::utils::string_literals;
93
94 return "protocol-detector"_static_str;
95 }
96
97 private:
98 struct unknown_protocol_t {
99 std::byte m_first_byte;
100 };
101
102 struct connection_accepted_t
103 {
104 connection_type_t m_connection_type;
105 connection_handler_shptr_t m_handler;
106 };
107
108 // Type to be used as the result on try_accept_*_connection methods.
109 using detection_result_t = std::variant<
110 unknown_protocol_t,
111 connection_accepted_t
112 >;
113
114 void
analyze_data_read(delete_protector_t delete_protector,can_throw_t can_throw)115 analyze_data_read(
116 delete_protector_t delete_protector,
117 can_throw_t can_throw )
118 {
119 detection_result_t detection_result{ unknown_protocol_t{} };
120
121 // Run only those try_accept_*_connection that enabled for the ACL.
122 const auto acl_protocol = context().config().acl_protocol();
123 if( acl_protocol_t::autodetect == acl_protocol )
124 {
125 detection_result = try_accept_socks_connection( can_throw );
126 if( std::holds_alternative< unknown_protocol_t >( detection_result ) )
127 {
128 detection_result = try_accept_http_connection( can_throw );
129 }
130 }
131 else if( acl_protocol_t::socks == acl_protocol )
132 {
133 detection_result = try_accept_socks_connection( can_throw );
134 }
135 else if( acl_protocol_t::http == acl_protocol )
136 {
137 detection_result = try_accept_http_connection( can_throw );
138 }
139
140 // Analyze the result of acception attempt.
141 std::visit( ::arataga::utils::overloaded{
142 [this, delete_protector, can_throw]
143 ( connection_accepted_t & accepted )
144 {
145 // Update the stats. It should be done now because
146 // in the case of HTTP keep-alive connection can be used.
147 // In the case of HTTP keep-alive the connection should be
148 // counted only once. If we'll update the stats in
149 // http::initial_http_handler then the stats will be updated
150 // for every incoming request (there could be many
151 // requests in a single keep-alive connection).
152 context().stats_inc_connection_count(
153 accepted.m_connection_type );
154
155 // The handler can be changed now.
156 replace_handler(
157 delete_protector,
158 can_throw,
159 [&]( can_throw_t ) {
160 return std::move(accepted.m_handler);
161 } );
162 },
163 [this, delete_protector, can_throw]
164 ( const unknown_protocol_t & info )
165 {
166 // We don't know the protocol, the connection has to be closed.
167 log_and_remove_connection(
168 delete_protector,
169 can_throw,
170 remove_reason_t::unsupported_protocol,
171 spdlog::level::warn,
172 fmt::format( "unsupported protocol in the connection "
173 "(first byte: {:#02x})",
174 std::to_integer<unsigned short>(info.m_first_byte) )
175 );
176 } },
177 detection_result );
178 }
179
180 detection_result_t
try_accept_socks_connection(can_throw_t)181 try_accept_socks_connection( can_throw_t /*can_throw*/ )
182 {
183 constexpr std::byte socks5_protocol_first_byte{ 5u };
184
185 const auto first_byte = m_in_buffer.read_byte();
186
187 if( socks5_protocol_first_byte == first_byte )
188 {
189 // Assume that is SOCKS5.
190 return {
191 connection_accepted_t{
192 connection_type_t::socks5,
193 make_socks5_auth_method_detection_handler(
194 m_ctx,
195 m_id,
196 std::move(m_connection),
197 // All data in m_first_chunk are going to
198 // the next handler.
199 make_first_chunk_for_next_handler(
200 std::move(m_first_chunk),
201 0u,
202 m_in_buffer.size() ),
203 m_created_at )
204 }
205 };
206 }
207
208 return { unknown_protocol_t{ first_byte } };
209 }
210
211 detection_result_t
try_accept_http_connection(can_throw_t can_throw)212 try_accept_http_connection( can_throw_t can_throw )
213 {
214 (void)can_throw;
215
216 // Assume that this is HTTP if the first byte is a capital
217 // latin letter (it is because methods in HTTP are identified
218 // by capital letters).
219 //
220 // Even if we've made a mistake the consequent parsing of HTTP
221 // will fail and the connection will be closed.
222 //
223 const auto first_byte = m_in_buffer.read_byte();
224 if( std::byte{'A'} <= first_byte && first_byte <= std::byte{'Z'} )
225 {
226 // Assume that it's HTTP protocol.
227 return {
228 connection_accepted_t{
229 connection_type_t::http,
230 make_http_handler(
231 m_ctx,
232 m_id,
233 std::move(m_connection),
234 // All data in m_first_chunk are going to
235 // the next handler.
236 make_first_chunk_for_next_handler(
237 std::move(m_first_chunk),
238 0u,
239 m_in_buffer.size() ),
240 m_created_at )
241 }
242 };
243 }
244
245 return { unknown_protocol_t{ first_byte } };
246 }
247 };
248
249 } /* namespace handlers::protocol_detection */
250
251 [[nodiscard]]
252 connection_handler_shptr_t
make_protocol_detection_handler(handler_context_holder_t ctx,handler_context_t::connection_id_t id,asio::ip::tcp::socket connection)253 make_protocol_detection_handler(
254 handler_context_holder_t ctx,
255 handler_context_t::connection_id_t id,
256 asio::ip::tcp::socket connection )
257 {
258 using namespace handlers::protocol_detection;
259
260 return std::make_shared< handler_t >(
261 std::move(ctx), id, std::move(connection) );
262 }
263
264 } /* namespace arataga::acl_handler */
265
266