diff options
Diffstat (limited to 'louloulibs/network/tcp_socket_handler.hpp')
m--------- | louloulibs | 0 | ||||
-rw-r--r-- | louloulibs/network/tcp_socket_handler.hpp | 293 |
2 files changed, 293 insertions, 0 deletions
diff --git a/louloulibs b/louloulibs deleted file mode 160000 -Subproject 0f3c1183e2bf0941ae2bffd3f31577bce4f3001 diff --git a/louloulibs/network/tcp_socket_handler.hpp b/louloulibs/network/tcp_socket_handler.hpp new file mode 100644 index 0000000..2b5cd49 --- /dev/null +++ b/louloulibs/network/tcp_socket_handler.hpp @@ -0,0 +1,293 @@ +#ifndef SOCKET_HANDLER_INCLUDED +# define SOCKET_HANDLER_INCLUDED + +#include <network/socket_handler.hpp> + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#include <memory> +#include <string> +#include <list> + +#include "louloulibs.h" + +#ifdef CARES_FOUND +# include <ares.h> +#endif + +#ifdef BOTAN_FOUND +# include <botan/botan.h> +# include <botan/tls_client.h> + +/** + * A very simple credential manager that accepts any certificate. + */ +class Permissive_Credentials_Manager: public Botan::Credentials_Manager +{ +public: + void verify_certificate_chain(const std::string& type, + const std::string& purported_hostname, + const std::vector<Botan::X509_Certificate>&); +}; +#endif // BOTAN_FOUND + +/** + * An interface, with a series of callbacks that should be implemented in + * subclasses that deal with a socket. These callbacks are called on various events + * (read/write/timeout, etc) when they are notified to a poller + * (select/poll/epoll etc) + */ +class TCPSocketHandler: public SocketHandler +{ +protected: + ~TCPSocketHandler(); + +public: + explicit TCPSocketHandler(std::shared_ptr<Poller> poller); + /** + * Connect to the remote server, and call on_connected() if this + * succeeds. If tls is true, we set use_tls to true and will also call + * start_tls() when the connection succeeds. + */ + void connect(const std::string& address, const std::string& port, const bool tls); + void connect() override final; + /** + * Reads raw data from the socket. And pass it to parse_in_buffer() + * If we are using TLS on this connection, we call tls_recv() + */ + void on_recv() override final; + /** + * Write as much data from out_buf as possible, in the socket. + */ + void on_send() override final; + /** + * Add the given data to out_buf and tell our poller that we want to be + * notified when a send event is ready. + * + * This can be overriden if we want to modify the data before sending + * it. For example if we want to encrypt it. + */ + void send_data(std::string&& data); + /** + * Watch the socket for send events, if our out buffer is not empty. + */ + void send_pending_data(); + /** + * Close the connection, remove us from the poller + */ + void close(); + /** + * Called by a TimedEvent, when the connection did not succeed or fail + * after a given time. + */ + void on_connection_timeout(); + /** + * Called when the connection is successful. + */ + virtual void on_connected() = 0; + /** + * Called when the connection fails. Not when it is closed later, just at + * the connect() call. + */ + virtual void on_connection_failed(const std::string& reason) = 0; + /** + * Called when we detect a disconnection from the remote host. + */ + virtual void on_connection_close(const std::string& error) = 0; + /** + * Handle/consume (some of) the data received so far. The data to handle + * may be in the in_buf buffer, or somewhere else, depending on what + * get_receive_buffer() returned. If some data is used from in_buf, it + * should be truncated, only the unused data should be left untouched. + * + * The size argument is the size of the last chunk of data that was added to the buffer. + */ + virtual void parse_in_buffer(const size_t size) = 0; + bool is_connected() const override final; + bool is_connecting() const; + +#ifdef CARES_FOUND + void on_hostname4_resolved(int status, struct hostent* hostent); + void on_hostname6_resolved(int status, struct hostent* hostent); + + void free_cares_addrinfo(); + + void fill_ares_addrinfo4(const struct hostent* hostent); + void fill_ares_addrinfo6(const struct hostent* hostent); +#endif + +private: + /** + * Initialize the socket with the parameters contained in the given + * addrinfo structure. + */ + void init_socket(const struct addrinfo* rp); + /** + * Reads from the socket into the provided buffer. If an error occurs + * (read returns <= 0), the handling of the error is done here (close the + * connection, log a message, etc). + * + * Returns the value returned by ::recv(), so the buffer should not be + * used if it’s not positive. + */ + ssize_t do_recv(void* recv_buf, const size_t buf_size); + /** + * Reads data from the socket and calls parse_in_buffer with it. + */ + void plain_recv(); + /** + * Mark the given data as ready to be sent, as-is, on the socket, as soon + * as we can. + */ + void raw_send(std::string&& data); + +#ifdef BOTAN_FOUND + /** + * Create the TLS::Client object, with all the callbacks etc. This must be + * called only when we know we are able to send TLS-encrypted data over + * the socket. + */ + void start_tls(); + /** + * An additional step to pass the data into our tls object to decrypt it + * before passing it to parse_in_buffer. + */ + void tls_recv(); + /** + * Pass the data to the tls object in order to encrypt it. The tls object + * will then call raw_send as a callback whenever data as been encrypted + * and can be sent on the socket. + */ + void tls_send(std::string&& data); + /** + * Called by the tls object that some data has been decrypt. We call + * parse_in_buffer() to handle that unencrypted data. + */ + void tls_data_cb(const Botan::byte* data, size_t size); + /** + * Called by the tls object to indicate that some data has been encrypted + * and is now ready to be sent on the socket as is. + */ + void tls_output_fn(const Botan::byte* data, size_t size); + /** + * Called by the tls object to indicate that a TLS alert has been + * received. We don’t use it, we just log some message, at the moment. + */ + void tls_alert_cb(Botan::TLS::Alert alert, const Botan::byte*, size_t); + /** + * Called by the tls object at the end of the TLS handshake. We don't do + * anything here appart from logging the TLS session information. + */ + bool tls_handshake_cb(const Botan::TLS::Session& session); + /** + * Called whenever the tls session goes from inactive to active. This + * means that the handshake has just been successfully done, and we can + * now proceed to send any available data into our tls object. + */ + void on_tls_activated(); +#endif // BOTAN_FOUND + /** + * Where data is added, when we want to send something to the client. + */ + std::list<std::string> out_buf; + /** + * Keep the details of the addrinfo that triggered a EINPROGRESS error when + * connect()ing to it, to reuse it directly when connect() is called + * again. + */ + struct addrinfo addrinfo; + struct sockaddr_in6 ai_addr; + socklen_t ai_addrlen; + +protected: + /** + * Where data read from the socket is added until we can extract a full + * and meaningful “message” from it. + * + * TODO: something more efficient than a string. + */ + std::string in_buf; + /** + * Whether we are using TLS on this connection or not. + */ + bool use_tls; + /** + * Provide a buffer in which data can be directly received. This can be + * used to avoid copying data into in_buf before using it. If no buffer + * needs to be provided, nullptr is returned (the default implementation + * does that), in that case our internal in_buf will be used to save the + * data until it can be used by parse_in_buffer(). + */ + virtual void* get_receive_buffer(const size_t size) const; + /** + * Hostname we are connected/connecting to + */ + std::string address; + /** + * Port we are connected/connecting to + */ + std::string port; + + bool connected; + bool connecting; + +#ifdef CARES_FOUND + bool resolving; + /** + * Whether or not the DNS resolution was successfully done + */ + bool resolved; + bool resolved4; + bool resolved6; + /** + * When using c-ares to resolve the host asynchronously, we need the + * c-ares callback to fill a structure (a struct addrinfo, for + * compatibility with getaddrinfo and the rest of the code that works when + * c-ares is not used) with all returned values (for example an IPv6 and + * an IPv4). The next call of connect() will then try all these values + * (exactly like we do with the result of getaddrinfo) and save the one + * that worked (or returned EINPROGRESS) in the other struct addrinfo (see + * the members addrinfo, ai_addrlen, and ai_addr). + */ + struct addrinfo* cares_addrinfo; + std::string cares_error; +#endif // CARES_FOUND + +private: + TCPSocketHandler(const TCPSocketHandler&) = delete; + TCPSocketHandler(TCPSocketHandler&&) = delete; + TCPSocketHandler& operator=(const TCPSocketHandler&) = delete; + TCPSocketHandler& operator=(TCPSocketHandler&&) = delete; + +#ifdef BOTAN_FOUND + /** + * Botan stuff to manipulate a TLS session. + */ + static Botan::AutoSeeded_RNG rng; + static Permissive_Credentials_Manager credential_manager; + static Botan::TLS::Policy policy; + static Botan::TLS::Session_Manager_In_Memory session_manager; + /** + * We use a unique_ptr because we may not want to create the object at + * all. The Botan::TLS::Client object generates a handshake message as + * soon and calls the output_fn callback with it as soon as it is + * created. Therefore, we do not want to create it if do not intend to do + * send any TLS-encrypted message. We create the object only when needed + * (for example after we have negociated a TLS session using a STARTTLS + * message, or stuf like that). + * + * See start_tls for the method where this object is created. + */ + std::unique_ptr<Botan::TLS::Client> tls; + /** + * An additional buffer to keep data that the user wants to send, but + * cannot because the handshake is not done. + */ + std::string pre_buf; +#endif // BOTAN_FOUND +}; + +#endif // SOCKET_HANDLER_INCLUDED |