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