1 /*!
2 * @file
3 * @brief The implementation of target-connector.
4 */
5
6 #include <arataga/acl_handler/handlers/http/basics.hpp>
7 #include <arataga/acl_handler/handlers/http/factories.hpp>
8 #include <arataga/acl_handler/handlers/http/helpers.hpp>
9 #include <arataga/acl_handler/handlers/http/responses.hpp>
10
11 namespace arataga::acl_handler
12 {
13
14 namespace handlers::http
15 {
16
17 //
18 // target_connector_handler_t
19 //
20 /*!
21 * @brief Connection-handler that makes a connection to the target host.
22 */
23 class target_connector_handler_t final : public handler_with_out_connection_t
24 {
25 //! HTTP-request parsing status.
26 http_handling_state_unique_ptr_t m_request_state;
27
28 //! Additional info for the request.
29 request_info_t m_request_info;
30
31 //! Address of the target host.
32 asio::ip::tcp::endpoint m_target_endpoint;
33
34 //! Traffic-limiter for the user.
35 traffic_limiter_unique_ptr_t m_traffic_limiter;
36
37 //! Timepoint at that connection attempt was started.
38 std::chrono::steady_clock::time_point m_created_at;
39
40 public:
target_connector_handler_t(handler_context_holder_t ctx,handler_context_t::connection_id_t id,asio::ip::tcp::socket connection,http_handling_state_unique_ptr_t request_state,request_info_t request_info,asio::ip::tcp::endpoint target_endpoint,traffic_limiter_unique_ptr_t traffic_limiter)41 target_connector_handler_t(
42 handler_context_holder_t ctx,
43 handler_context_t::connection_id_t id,
44 asio::ip::tcp::socket connection,
45 http_handling_state_unique_ptr_t request_state,
46 request_info_t request_info,
47 asio::ip::tcp::endpoint target_endpoint,
48 traffic_limiter_unique_ptr_t traffic_limiter )
49 : handler_with_out_connection_t{
50 std::move(ctx), id, std::move(connection)
51 }
52 , m_request_state{ std::move(request_state) }
53 , m_request_info{ std::move(request_info) }
54 , m_target_endpoint{ target_endpoint }
55 , m_traffic_limiter{ std::move(traffic_limiter) }
56 , m_created_at{ std::chrono::steady_clock::now() }
57 {}
58
59 protected:
60 void
on_start_impl(delete_protector_t delete_protector)61 on_start_impl( delete_protector_t delete_protector ) override
62 {
63 wrap_action_and_handle_exceptions(
64 delete_protector,
65 [this]( delete_protector_t delete_protector, can_throw_t can_throw )
66 {
67 initiate_connect( delete_protector, can_throw );
68 } );
69 }
70
71 void
on_timer_impl(delete_protector_t delete_protector)72 on_timer_impl( delete_protector_t delete_protector ) override
73 {
74 if( std::chrono::steady_clock::now() >= m_created_at +
75 context().config().connect_target_timeout() )
76 {
77 wrap_action_and_handle_exceptions(
78 delete_protector,
79 [this]( delete_protector_t delete_protector, can_throw_t can_throw )
80 {
81 send_negative_response_then_close_connection(
82 delete_protector,
83 can_throw,
84 remove_reason_t::current_operation_timed_out,
85 response_bad_gateway_connect_timeout );
86 } );
87 }
88 }
89
90 public:
91 arataga::utils::string_literal_t
name() const92 name() const noexcept override
93 {
94 using namespace arataga::utils::string_literals;
95 return "http-target-connect-handler"_static_str;
96 }
97
98 private:
99 void
initiate_connect(delete_protector_t delete_protector,can_throw_t can_throw)100 initiate_connect(
101 delete_protector_t delete_protector,
102 can_throw_t can_throw )
103 {
104 try
105 {
106 asio::error_code ec;
107
108 // Helper local function to avoid data duplication.
109 const auto finish_on_failure =
110 [this, &delete_protector, &can_throw](
111 std::string_view message ) -> void
112 {
113 log_problem_then_send_negative_response(
114 delete_protector,
115 can_throw,
116 remove_reason_t::io_error,
117 spdlog::level::err,
118 message,
119 response_internal_server_error );
120 };
121
122 m_out_connection.open( m_target_endpoint.protocol(), ec );
123 if( ec )
124 {
125 return finish_on_failure( fmt::format(
126 "unable open outgoing socket: {}",
127 ec.message() ) );
128 }
129
130 // New socket should work in non-blocking mode.
131 m_out_connection.non_blocking( true, ec );
132 if( ec )
133 {
134 return finish_on_failure( fmt::format(
135 "unable switch outgoing socket to non-blocking mode: {}",
136 ec.message() ) );
137 }
138
139 // We have to bind new socket to ACL's external address.
140 m_out_connection.bind(
141 // Use 0 as port number, the OS will assign actual number.
142 asio::ip::tcp::endpoint{ context().config().out_addr(), 0u },
143 ec );
144 if( ec )
145 {
146 return finish_on_failure( fmt::format(
147 "unable to bind outgoing socket to address {}: {}",
148 context().config().out_addr(),
149 ec.message() ) );
150 }
151
152 ::arataga::logging::wrap_logging(
153 proxy_logging_mode,
154 spdlog::level::trace,
155 [this, can_throw]( auto level )
156 {
157 log_message_for_connection(
158 can_throw,
159 level,
160 fmt::format( "trying to connect {} from {}",
161 m_target_endpoint,
162 m_out_connection.local_endpoint() ) );
163 } );
164
165 // Now we can initiate the connection.
166 m_out_connection.async_connect(
167 m_target_endpoint,
168 with<const asio::error_code &>().make_handler(
169 [this](
170 delete_protector_t delete_protector,
171 can_throw_t can_throw,
172 const asio::error_code & ec )
173 {
174 on_async_connect_result(
175 delete_protector, can_throw, ec );
176 } )
177 );
178 }
179 catch( const std::exception & x )
180 {
181 //FIXME: what is fmt::format throws?
182 log_problem_then_send_negative_response(
183 delete_protector,
184 can_throw,
185 remove_reason_t::unhandled_exception,
186 spdlog::level::err,
187 fmt::format( "an exception during the creation of "
188 "outgoing connection from {} to {}: {}",
189 context().config().out_addr(),
190 m_target_endpoint,
191 x.what() ),
192 response_internal_server_error );
193 }
194 }
195
196 void
log_problem_then_send_negative_response(delete_protector_t delete_protector,can_throw_t can_throw,remove_reason_t remove_reason,spdlog::level::level_enum log_level,std::string_view log_message,arataga::utils::string_literal_t negative_response)197 log_problem_then_send_negative_response(
198 delete_protector_t delete_protector,
199 can_throw_t can_throw,
200 remove_reason_t remove_reason,
201 spdlog::level::level_enum log_level,
202 std::string_view log_message,
203 arataga::utils::string_literal_t negative_response )
204 {
205 ::arataga::logging::wrap_logging(
206 proxy_logging_mode,
207 log_level,
208 [this, can_throw, log_message]( auto level )
209 {
210 log_message_for_connection(
211 can_throw,
212 level,
213 log_message );
214 } );
215
216 send_negative_response_then_close_connection(
217 delete_protector,
218 can_throw,
219 remove_reason,
220 negative_response );
221 }
222
223 void
on_async_connect_result(delete_protector_t delete_protector,can_throw_t can_throw,const asio::error_code & ec)224 on_async_connect_result(
225 delete_protector_t delete_protector,
226 can_throw_t can_throw,
227 const asio::error_code & ec )
228 {
229 if( ec )
230 {
231 if( asio::error::operation_aborted != ec )
232 {
233 log_problem_then_send_negative_response(
234 delete_protector,
235 can_throw,
236 remove_reason_t::io_error,
237 spdlog::level::warn,
238 fmt::format( "can't connect to target host {}: {}",
239 m_target_endpoint,
240 ec.message() ),
241 response_bad_gateway_connect_failure );
242 }
243 }
244 else
245 {
246 ::arataga::logging::wrap_logging(
247 proxy_logging_mode,
248 spdlog::level::debug,
249 [this, can_throw]( auto level )
250 {
251 log_message_for_connection(
252 can_throw,
253 level,
254 fmt::format(
255 "outgoing connection to {} from {} established",
256 m_target_endpoint,
257 m_out_connection.local_endpoint() ) );
258 } );
259
260 // New connection-handler depends on HTTP-method from the request.
261 // At the moment only CONNECT method requires a special handler.
262 const auto factory = (HTTP_CONNECT == m_request_info.m_method ?
263 &make_connect_method_handler :
264 &make_ordinary_method_handler);
265
266 replace_handler(
267 delete_protector,
268 can_throw,
269 [this, &factory]( can_throw_t )
270 {
271 return (*factory)(
272 std::move(m_ctx),
273 m_id,
274 std::move(m_connection),
275 std::move(m_request_state),
276 std::move(m_request_info),
277 std::move(m_traffic_limiter),
278 std::move(m_out_connection) );
279 } );
280 }
281 }
282 };
283
284 //
285 // make_target_connector_handler
286 //
287 [[nodiscard]]
288 connection_handler_shptr_t
make_target_connector_handler(handler_context_holder_t ctx,handler_context_t::connection_id_t id,asio::ip::tcp::socket connection,http_handling_state_unique_ptr_t request_state,request_info_t request_info,asio::ip::tcp::endpoint target_endpoint,traffic_limiter_unique_ptr_t traffic_limiter)289 make_target_connector_handler(
290 handler_context_holder_t ctx,
291 handler_context_t::connection_id_t id,
292 asio::ip::tcp::socket connection,
293 http_handling_state_unique_ptr_t request_state,
294 request_info_t request_info,
295 asio::ip::tcp::endpoint target_endpoint,
296 traffic_limiter_unique_ptr_t traffic_limiter )
297 {
298 return std::make_shared< target_connector_handler_t >(
299 std::move(ctx),
300 id,
301 std::move(connection),
302 std::move(request_state),
303 std::move(request_info),
304 target_endpoint,
305 std::move(traffic_limiter) );
306 }
307
308 } /* namespace arataga::acl_handler */
309
310 } /* namespace handlers::http */
311
312