Network System 0.1.1
High-performance modular networking library for scalable client-server applications
Loading...
Searching...
No Matches
secure_messaging_udp_server.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2024, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
8
9#include <string_view>
10
12{
13
15 : server_id_(server_id)
16{
17}
18
20{
21 try
22 {
23 // Ignore the return value in destructor to avoid throwing
24 (void)stop_server();
25 }
26 catch (...)
27 {
28 // Destructor must not throw - swallow all exceptions
29 }
30}
31
33 const std::string& file_path) -> VoidResult
34{
35 cert_file_ = file_path;
36 return ok();
37}
38
40 const std::string& file_path) -> VoidResult
41{
42 key_file_ = file_path;
43 return ok();
44}
45
47{
48 // Create DTLS server context
49 ssl_ctx_ = SSL_CTX_new(DTLS_server_method());
50 if (!ssl_ctx_)
51 {
53 "Failed to create DTLS context",
54 "secure_messaging_udp_server::init_ssl_context");
55 }
56
57 // Set DTLS options
58 SSL_CTX_set_options(ssl_ctx_, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
59
60 // Load certificate
61 if (!cert_file_.empty())
62 {
63 if (SSL_CTX_use_certificate_chain_file(ssl_ctx_, cert_file_.c_str()) != 1)
64 {
65 SSL_CTX_free(ssl_ctx_);
66 ssl_ctx_ = nullptr;
68 "Failed to load certificate: " + cert_file_,
69 "secure_messaging_udp_server::init_ssl_context");
70 }
71 }
72
73 // Load private key
74 if (!key_file_.empty())
75 {
76 if (SSL_CTX_use_PrivateKey_file(ssl_ctx_, key_file_.c_str(), SSL_FILETYPE_PEM) != 1)
77 {
78 SSL_CTX_free(ssl_ctx_);
79 ssl_ctx_ = nullptr;
81 "Failed to load private key: " + key_file_,
82 "secure_messaging_udp_server::init_ssl_context");
83 }
84
85 // Verify private key matches certificate
86 if (SSL_CTX_check_private_key(ssl_ctx_) != 1)
87 {
88 SSL_CTX_free(ssl_ctx_);
89 ssl_ctx_ = nullptr;
91 "Private key does not match certificate",
92 "secure_messaging_udp_server::init_ssl_context");
93 }
94 }
95
96 // Set verification mode (server doesn't verify client by default)
97 SSL_CTX_set_verify(ssl_ctx_, SSL_VERIFY_NONE, nullptr);
98
99 return ok();
100}
101
103{
104 if (is_running_.load(std::memory_order_acquire))
105 {
107 "Server already running",
108 "secure_messaging_udp_server::start_server");
109 }
110
111 // Check if certificates are configured
112 if (cert_file_.empty() || key_file_.empty())
113 {
115 "Certificate and private key files must be set",
116 "secure_messaging_udp_server::start_server");
117 }
118
119 // Initialize SSL context
120 auto ssl_result = init_ssl_context();
121 if (!ssl_result.is_ok())
122 {
123 return ssl_result;
124 }
125
126 // Create io_context
127 io_context_ = std::make_unique<asio::io_context>();
128
129 // Create and bind UDP socket
130 try
131 {
132 socket_ = std::make_unique<asio::ip::udp::socket>(
133 *io_context_,
134 asio::ip::udp::endpoint(asio::ip::udp::v4(), port));
135 }
136 catch (const std::exception& e)
137 {
138 SSL_CTX_free(ssl_ctx_);
139 ssl_ctx_ = nullptr;
141 std::string("Failed to bind port: ") + e.what(),
142 "secure_messaging_udp_server::start_server");
143 }
144
145 // Get thread pool from network context
147 if (!thread_pool_) {
148 // Fallback: create a temporary thread pool if network_context is not initialized
149 thread_pool_ = std::make_shared<integration::basic_thread_pool>(std::thread::hardware_concurrency());
150 }
151
152 // Prepare promise/future for wait_for_stop()
153 stop_promise_.emplace();
154 stop_future_ = stop_promise_->get_future();
155
156 // Start io_context in background
157 io_context_future_ = thread_pool_->submit(
158 [this]()
159 {
160 auto work_guard = asio::make_work_guard(*io_context_);
161 io_context_->run();
162 });
163
164 is_running_.store(true, std::memory_order_release);
165
166 // Start receiving
167 do_receive();
168
169 return ok();
170}
171
173{
174 if (!is_running_.exchange(false, std::memory_order_acq_rel))
175 {
176 // Already stopped
177 if (ssl_ctx_)
178 {
179 SSL_CTX_free(ssl_ctx_);
180 ssl_ctx_ = nullptr;
181 }
182 return ok();
183 }
184
185 // Clear sessions
186 {
187 std::lock_guard<std::mutex> lock(sessions_mutex_);
188 for (auto& [endpoint, session] : sessions_)
189 {
190 if (session && session->socket)
191 {
192 session->socket->stop_receive();
193 }
194 }
195 sessions_.clear();
196 }
197
198 // Close socket
199 if (socket_)
200 {
201 std::error_code ec;
202 socket_->close(ec);
203 socket_.reset();
204 }
205
206 // Stop io_context
207 if (io_context_)
208 {
209 io_context_->stop();
210 }
211
212 // Wait for io_context thread
213 if (io_context_future_.valid())
214 {
215 io_context_future_.wait();
216 }
217
218 // Cleanup SSL context
219 if (ssl_ctx_)
220 {
221 SSL_CTX_free(ssl_ctx_);
222 ssl_ctx_ = nullptr;
223 }
224
225 // Signal the promise for wait_for_stop()
226 if (stop_promise_.has_value())
227 {
228 try
229 {
230 stop_promise_->set_value();
231 }
232 catch (const std::future_error&)
233 {
234 // Promise already satisfied - this is OK during shutdown
235 }
236 stop_promise_.reset();
237 }
238
239 return ok();
240}
241
243{
244 if (stop_future_.valid())
245 {
246 stop_future_.wait();
247 }
248}
249
251{
252 if (!is_running_.load(std::memory_order_acquire) || !socket_)
253 {
254 return;
255 }
256
257 auto self = shared_from_this();
258 socket_->async_receive_from(
259 asio::buffer(read_buffer_),
260 sender_endpoint_,
261 [this, self](std::error_code ec, std::size_t length)
262 {
263 if (!is_running_.load(std::memory_order_acquire))
264 {
265 return;
266 }
267
268 if (ec)
269 {
270 if (ec != asio::error::operation_aborted)
271 {
272 std::function<void(std::error_code)> callback;
273 {
274 std::lock_guard<std::mutex> lock(callback_mutex_);
275 callback = error_callback_;
276 }
277 if (callback)
278 {
279 callback(ec);
280 }
281 }
282 return;
283 }
284
285 if (length > 0)
286 {
287 std::vector<uint8_t> data(read_buffer_.begin(),
288 read_buffer_.begin() + length);
289 process_session_data(data, sender_endpoint_);
290 }
291
292 // Continue receiving
293 if (is_running_.load(std::memory_order_acquire))
294 {
295 do_receive();
296 }
297 });
298}
299
301 [[maybe_unused]] const std::vector<uint8_t>& data, // TODO: Use when DTLS handling is implemented
302 const asio::ip::udp::endpoint& sender) -> void
303{
304 std::shared_ptr<dtls_session> session;
305
306 // Look up existing session
307 {
308 std::lock_guard<std::mutex> lock(sessions_mutex_);
309 auto it = sessions_.find(sender);
310 if (it != sessions_.end())
311 {
312 session = it->second;
313 }
314 }
315
316 // Create new session if needed
317 if (!session)
318 {
319 session = create_session(sender);
320 if (!session)
321 {
322 return;
323 }
324 }
325
326 // For a proper DTLS server, we would need to:
327 // 1. Parse the DTLS record to determine if it's a ClientHello
328 // 2. Handle cookie exchange for DoS protection
329 // 3. Create per-client SSL objects
330
331 // For this implementation, we rely on the dtls_socket to handle
332 // the DTLS protocol, but in a real server, each client needs its own
333 // SSL object and BIO pair.
334
335 // Note: A complete DTLS server implementation would require
336 // more sophisticated session management. This is a simplified version.
337}
338
340 const asio::ip::udp::endpoint& client_endpoint) -> std::shared_ptr<dtls_session>
341{
342 // Create a new UDP socket for this client session
343 // Note: In a real implementation, you might want to use a single socket
344 // and demultiplex based on endpoint, but that requires more complex
345 // BIO handling with OpenSSL's DTLS.
346
347 try
348 {
349 asio::ip::udp::socket client_socket(*io_context_, asio::ip::udp::v4());
350
351 auto session = std::make_shared<dtls_session>();
352 session->socket = std::make_shared<internal::dtls_socket>(
353 std::move(client_socket), ssl_ctx_);
354 session->socket->set_peer_endpoint(client_endpoint);
355
356 // Set receive callback
357 session->socket->set_receive_callback(
358 [this, client_endpoint](const std::vector<uint8_t>& data,
359 const asio::ip::udp::endpoint& /*sender*/)
360 {
361 std::function<void(const std::vector<uint8_t>&, const asio::ip::udp::endpoint&)> callback;
362 {
363 std::lock_guard<std::mutex> lock(callback_mutex_);
364 callback = receive_callback_;
365 }
366 if (callback)
367 {
368 callback(data, client_endpoint);
369 }
370 });
371
372 // Perform handshake
373 session->socket->async_handshake(
375 [this, session, client_endpoint](std::error_code ec)
376 {
377 if (ec)
378 {
379 // Handshake failed, remove session
380 {
381 std::lock_guard<std::mutex> lock(sessions_mutex_);
382 sessions_.erase(client_endpoint);
383 }
384 return;
385 }
386
387 session->handshake_complete = true;
388
389 // Notify client connected
390 std::function<void(const asio::ip::udp::endpoint&)> callback;
391 {
392 std::lock_guard<std::mutex> lock(callback_mutex_);
393 callback = client_connected_callback_;
394 }
395 if (callback)
396 {
397 callback(client_endpoint);
398 }
399 });
400
401 // Store session
402 {
403 std::lock_guard<std::mutex> lock(sessions_mutex_);
404 sessions_[client_endpoint] = session;
405 }
406
407 return session;
408 }
409 catch (const std::exception& /*e*/)
410 {
411 return nullptr;
412 }
413}
414
416 std::vector<uint8_t>&& data,
417 const asio::ip::udp::endpoint& endpoint,
418 std::function<void(std::error_code, std::size_t)> handler) -> void
419{
420 std::shared_ptr<dtls_session> session;
421
422 {
423 std::lock_guard<std::mutex> lock(sessions_mutex_);
424 auto it = sessions_.find(endpoint);
425 if (it != sessions_.end())
426 {
427 session = it->second;
428 }
429 }
430
431 if (!session || !session->socket || !session->handshake_complete)
432 {
433 if (handler)
434 {
435 handler(std::make_error_code(std::errc::not_connected), 0);
436 }
437 return;
438 }
439
440 session->socket->async_send_to(std::move(data), endpoint, std::move(handler));
441}
442
444 std::function<void(const std::vector<uint8_t>&,
445 const asio::ip::udp::endpoint&)> callback) -> void
446{
447 std::lock_guard<std::mutex> lock(callback_mutex_);
448 receive_callback_ = std::move(callback);
449}
450
452 std::function<void(std::error_code)> callback) -> void
453{
454 std::lock_guard<std::mutex> lock(callback_mutex_);
455 error_callback_ = std::move(callback);
456}
457
459 std::function<void(const asio::ip::udp::endpoint&)> callback) -> void
460{
461 std::lock_guard<std::mutex> lock(callback_mutex_);
462 client_connected_callback_ = std::move(callback);
463}
464
466 std::function<void(const asio::ip::udp::endpoint&)> callback) -> void
467{
468 std::lock_guard<std::mutex> lock(callback_mutex_);
469 client_disconnected_callback_ = std::move(callback);
470}
471
472} // namespace kcenon::network::core
std::shared_ptr< kcenon::network::integration::thread_pool_interface > get_thread_pool()
Get current thread pool.
static network_context & instance()
Get the singleton instance.
auto async_send_to(std::vector< uint8_t > &&data, const asio::ip::udp::endpoint &endpoint, std::function< void(std::error_code, std::size_t)> handler=nullptr) -> void
Sends an encrypted datagram to a specific client.
auto set_receive_callback(udp_receive_callback_t callback) -> void
Sets a UDP-specific callback to handle received decrypted datagrams.
auto wait_for_stop() -> void
Blocks the calling thread until the server is stopped.
auto create_session(const asio::ip::udp::endpoint &client_endpoint) -> std::shared_ptr< dtls_session >
Creates a new DTLS session for a client.
auto start_server(uint16_t port) -> VoidResult
Starts the server and begins listening for DTLS connections.
auto stop_server() -> VoidResult
Stops the server and releases all resources.
auto set_private_key_file(const std::string &file_path) -> VoidResult
Sets the private key file for TLS.
auto init_ssl_context() -> VoidResult
Initializes the SSL context for DTLS server.
auto set_client_disconnected_callback(udp_client_callback_t callback) -> void
Sets a UDP-specific callback for client disconnection.
auto set_error_callback(std::function< void(std::error_code)> callback) -> void
Sets a callback to handle errors.
auto set_certificate_chain_file(const std::string &file_path) -> VoidResult
Sets the certificate chain file for TLS.
secure_messaging_udp_server(std::string_view server_id)
Constructs a secure_messaging_udp_server with an identifier.
auto set_client_connected_callback(udp_client_callback_t callback) -> void
Sets a UDP-specific callback for new client connection.
~secure_messaging_udp_server() noexcept
Destructor. Automatically calls stop_server() if still running.
auto do_receive() -> void
Handles incoming datagrams and routes them to appropriate sessions.
auto process_session_data(const std::vector< uint8_t > &data, const asio::ip::udp::endpoint &sender) -> void
Processes received data for an existing session.
virtual std::future< void > submit(std::function< void()> task)=0
Submit a task to the thread pool.
VoidResult error_void(int code, const std::string &message, const std::string &source="network_system", const std::string &details="")
VoidResult ok()
Global context for shared network system resources.
DTLS server class for encrypted UDP communication.