initial
This commit is contained in:
commit
ab9a0bd4e2
183 changed files with 20701 additions and 0 deletions
327
src/framework/socketconnection.h
Normal file
327
src/framework/socketconnection.h
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
// Copyright (c) 2018 The Swedish Internet Foundation
|
||||
// Written by Göran Andersson <initgoran@gmail.com>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include "socket.h"
|
||||
|
||||
#ifdef USE_GNUTLS
|
||||
#include <gnutls/gnutls.h>
|
||||
#endif
|
||||
|
||||
/// \brief
|
||||
/// This class implements low-level socket connection operations.
|
||||
/// Inherit from it to implement protocols like HTTP.
|
||||
///
|
||||
/// A SocketConnection object represents a socket connection. Inherit from this
|
||||
/// class to implement a "protocol" for the connection, i.e. when/what to
|
||||
/// read/write through the connection.
|
||||
///
|
||||
/// Each SocketConnection is owned by a Task object. SocketConnection objects
|
||||
/// will be added to the network Engine (a part of the EventLoop) through the
|
||||
/// task's addConnection method.
|
||||
///
|
||||
/// The subclass will be notified through callback functions when
|
||||
/// anything happens on the socket, i.e. if the socket has been closed, if data
|
||||
/// has arrived, or if the socket is writable.
|
||||
/// You define the callback functions by overloading the below virtual functions.
|
||||
/// All operations will be performed asynchronously (non-blocking) except for
|
||||
/// dns lookups, which are performed synchronously (blocking).
|
||||
///
|
||||
/// Note! You _must_ create the SocketConnection objects with new. The ownership
|
||||
/// will then be passed to the network engine.
|
||||
/// You are not allowed to delete a SocketConnection object.
|
||||
/// We will delete it when the connection has been closed, which will be
|
||||
///
|
||||
/// 1) If you order us to close it by returning CLOSE, KEEPALIVE or KILL from
|
||||
/// a callback, or
|
||||
///
|
||||
/// 2) After the closedByPeer callback, if the connection has been
|
||||
/// closed by peer.
|
||||
///
|
||||
/// 3) After the connectionFailed callback, if the connection couldn't
|
||||
/// be established in the first place.
|
||||
///
|
||||
/// The owner task will be notified before the object is deleted.
|
||||
class SocketConnection : public Socket {
|
||||
friend class Engine;
|
||||
public:
|
||||
/// Create a SocketConnection owned by the given Task. The network Engine
|
||||
/// will connect to the given hostname/port, and notify through the below
|
||||
/// method when the connection is ready.
|
||||
/// If iptype is 4, prefer ipv4. If iptype is 6, prefer ipv6.
|
||||
SocketConnection(const std::string &label, Task *owner,
|
||||
const std::string &hostname, uint16_t port,
|
||||
uint16_t iptype = 0, struct addrinfo *local_addr=nullptr);
|
||||
|
||||
#ifdef USE_GNUTLS
|
||||
virtual ~SocketConnection() override {
|
||||
if (tlsInitialized()) {
|
||||
gnutls_deinit(session);
|
||||
}
|
||||
}
|
||||
|
||||
/// Notify that the connection will be encrypted (SSL).
|
||||
void enableTLS() {
|
||||
use_tls = true;
|
||||
}
|
||||
|
||||
/// Return true if the connection will be encrypted (SSL).
|
||||
bool is_tls() const {
|
||||
return use_tls;
|
||||
}
|
||||
|
||||
/// Store SSL session in cache.
|
||||
gnutls_session_t cache_session() {
|
||||
session_initialized = false;
|
||||
return session;
|
||||
}
|
||||
|
||||
/// Reuse cached SSL session.
|
||||
void insert_cached_session(gnutls_session_t &old_session) {
|
||||
session_initialized = true;
|
||||
use_tls = true;
|
||||
session = old_session;
|
||||
gnutls_transport_set_ptr(session,
|
||||
static_cast<gnutls_transport_ptr_t>(this));
|
||||
}
|
||||
#endif
|
||||
|
||||
/// \brief
|
||||
/// Will be called when the connection is established.
|
||||
///
|
||||
/// Override it to start sending data when the connection is ready.
|
||||
///
|
||||
/// If a connection couldn't be established,
|
||||
/// SocketConnection::connectionFailed will be called instead.
|
||||
/// You must not return PollState::CONNECTING.
|
||||
virtual PollState connected() {
|
||||
return PollState::CLOSE;
|
||||
}
|
||||
|
||||
/// Will be called if the connection couldn't be established.
|
||||
virtual void connectionFailed(const std::string &err_msg) {
|
||||
log() << "connection failed: " << err_msg;
|
||||
}
|
||||
|
||||
/// Called when the socket has been closed by peer:
|
||||
/// May be called in states READ, WRITE, READ_WRITE
|
||||
virtual void closedByPeer();
|
||||
|
||||
/// \brief
|
||||
/// Callback, called when data has arrived; len > 0.
|
||||
///
|
||||
/// May be called in states READ, READ_WRITE. Override this method to
|
||||
/// handle the data. Return value should be the new state.
|
||||
///
|
||||
/// The buffer is owned by the implementation. However, you are allowed
|
||||
/// to modify the contents of the buffer and you may also use
|
||||
/// it in the asyncSendData call.
|
||||
virtual PollState readData(char *, size_t ) {
|
||||
return PollState::CLOSE;
|
||||
}
|
||||
|
||||
/// \brief
|
||||
/// Peer has sent data when it wasn't supposed to.
|
||||
///
|
||||
/// If peer sends data when we're not in state READ/READ_WRITE, this
|
||||
/// function will be called. Default is for the socket to be closed.
|
||||
/// If you return READ, any remaining async_send data will be discarded.
|
||||
virtual PollState unexpectedData(char *buf, size_t len);
|
||||
|
||||
/// \brief
|
||||
/// Callback, called when socket is writable.
|
||||
///
|
||||
/// May be called in states PollState::WRITE and PollState::READ_WRITE.
|
||||
/// Override it to write data.
|
||||
/// Return value should be the new state. Do not return PollState::WRITE or
|
||||
/// PollState::READ_WRITE except after a write operation where
|
||||
/// all data couldn't be sent immediately.
|
||||
virtual PollState writeData() {
|
||||
return PollState::CLOSE;
|
||||
}
|
||||
|
||||
/// \brief
|
||||
/// Try to send len bytes from the given buffer. Return the amount sent.
|
||||
///
|
||||
/// More accurately, this method will return the number of bytes that
|
||||
/// could be copied into the socket's send buffer.
|
||||
///
|
||||
/// Please understand that the return value might be < len.
|
||||
/// To safely get all data sent, you should use the
|
||||
/// SocketConnection::asyncSendData
|
||||
/// function instead. However, if you need to send large
|
||||
/// amounts of data ("large" as in "no upper limit") as fast
|
||||
/// as possible, this is the function to use.
|
||||
size_t sendData(const char *buf, size_t len);
|
||||
|
||||
#ifdef USE_WEBROOT
|
||||
size_t sendFileData(int fd, size_t len);
|
||||
#endif
|
||||
|
||||
/// \brief
|
||||
/// Send data to peer as soon as possible.
|
||||
///
|
||||
/// Helper function which you may call only during the
|
||||
/// execution of the callbacks SocketConnection::connected,
|
||||
/// SocketConnection::readData, and SocketConnection::writeData.
|
||||
///
|
||||
/// Send len bytes from the given buffer.
|
||||
/// The callback SocketConnection::closedByPeer
|
||||
/// might be executed before all data was sent.
|
||||
///
|
||||
/// If you need to send "unlimited" amounts of data, you cant use
|
||||
/// this method; instead you must use SocketConnection::sendData.
|
||||
void asyncSendData(const char *buf, size_t len);
|
||||
|
||||
/// \brief
|
||||
/// Send data to peer as soon as possible.
|
||||
///
|
||||
/// Helper function which you may call only during the
|
||||
/// execution of the callbacks SocketConnection::connected,
|
||||
/// SocketConnection::readData, and SocketConnection::writeData.
|
||||
///
|
||||
/// Send len bytes from the given buffer.
|
||||
/// The callback SocketConnection::closedByPeer
|
||||
/// might be executed before all data was sent.
|
||||
///
|
||||
/// If you need to send "unlimited" amounts of data, you cant use
|
||||
/// this method; instead you must use SocketConnection::sendData.
|
||||
void asyncSendData(const std::string data) {
|
||||
asyncSendData(data.c_str(), data.size());
|
||||
}
|
||||
|
||||
/// \brief
|
||||
/// Return number of bytes left to send after calling
|
||||
/// SocketConnection::asyncSendData.
|
||||
size_t asyncBufferSize() const {
|
||||
return to_send.size();
|
||||
}
|
||||
|
||||
/// Number of bytes sent by current thread
|
||||
static uint64_t totBytesSent() {
|
||||
return tot_bytes_sent;
|
||||
}
|
||||
|
||||
/// Number of bytes recieved by current thread
|
||||
static uint64_t totBytesReceived() {
|
||||
return tot_bytes_received;
|
||||
}
|
||||
|
||||
/// \brief
|
||||
/// Reset counter for SocketConnection::totBytesSent
|
||||
/// and SocketConnection::totBytesReceived.
|
||||
static void resetByteCounter() {
|
||||
tot_bytes_sent = 0;
|
||||
tot_bytes_received = 0;
|
||||
}
|
||||
|
||||
/// Return peer's IP address.
|
||||
const std::string &peerIp() const {
|
||||
return peer_ip;
|
||||
}
|
||||
|
||||
/// Return peer's port number.
|
||||
uint16_t peerPort() const {
|
||||
return peer_port;
|
||||
}
|
||||
|
||||
/// Enable debug output of data sent and received.
|
||||
void dbgOn(bool b = true) {
|
||||
debugging = b;
|
||||
}
|
||||
|
||||
/// Return true if socket debugging is enabled.
|
||||
bool dbgIsOn() {
|
||||
return debugging;
|
||||
}
|
||||
protected:
|
||||
|
||||
/// If fd is the socket descriptor of an already established connection,
|
||||
/// you may let us manage the connection by calling this constructor.
|
||||
/// fd will probably be a client socket connected through a ServerSocket.
|
||||
SocketConnection(const std::string &label, Task *owner, int fd,
|
||||
const char *ip, uint16_t port);
|
||||
|
||||
/// Send a "message" to owner task. It will be executed as
|
||||
/// Task::msgFromConnection(this, msg) in the owner task.
|
||||
/// This is useful if you want to create SocketConnection
|
||||
/// subclasseses that work with any Task.
|
||||
PollState tellOwner(const std::string &msg);
|
||||
|
||||
private:
|
||||
// Create a connection to the given host. Will be executed asynchronously,
|
||||
// then one of the callbacks connected / connectionFailed will be called.
|
||||
SocketConnection(Task *owner, const std::string &hostname,
|
||||
unsigned int port);
|
||||
|
||||
SocketConnection(const SocketConnection &);
|
||||
|
||||
// Will return false if connection fails immediately, e.g. if DNS
|
||||
// lookup fails. Otherwise callback "connected" or "connectionFailed"
|
||||
// will be called - perhaps even before this call returns:
|
||||
bool asyncConnect();
|
||||
|
||||
PollState doRead(int fd);
|
||||
|
||||
// In states READ and READ_BLOCKED, this will be called to check if
|
||||
// we the connection should be checked for writability too.
|
||||
// If you override this, make sure to return true if asyncBufferSize() > 0.
|
||||
bool wantToSend() override {
|
||||
return !to_send.empty();
|
||||
}
|
||||
|
||||
PollState doWrite() {
|
||||
if (to_send.empty())
|
||||
return this->writeData();
|
||||
if (size_t written = sendData(to_send.c_str(), to_send.size()))
|
||||
to_send.erase(0, written);
|
||||
return state();
|
||||
}
|
||||
|
||||
std::string to_send;
|
||||
char socket_buffer[100000];
|
||||
|
||||
std::string peer_ip;
|
||||
struct addrinfo *local_ip;
|
||||
uint16_t peer_port;
|
||||
uint16_t prefer_ip_type;
|
||||
|
||||
#ifdef USE_THREADS
|
||||
thread_local
|
||||
#endif
|
||||
// Per thread byte counters.
|
||||
static uint64_t tot_bytes_sent, tot_bytes_received;
|
||||
bool debugging = false;
|
||||
#ifdef USE_GNUTLS
|
||||
bool use_tls = false;
|
||||
bool session_initialized = false;
|
||||
bool tls_send_pending = false;
|
||||
void setSessionInitialized() {
|
||||
session_initialized = true;
|
||||
}
|
||||
bool tlsInitialized() {
|
||||
return session_initialized;
|
||||
}
|
||||
bool init_tls_server(gnutls_certificate_credentials_t &x509_cred,
|
||||
gnutls_priority_t &priority_cache);
|
||||
bool init_tls_client(gnutls_certificate_credentials_t &x509_cred,
|
||||
bool verify_cert);
|
||||
int try_tls_handshake() {
|
||||
dbg_log() << "TLS handshake socket " << socket();
|
||||
return gnutls_handshake(session);
|
||||
}
|
||||
static ssize_t tls_pull_static(gnutls_transport_ptr_t self,
|
||||
void *buf, size_t len) {
|
||||
return static_cast<SocketConnection *>(self)->tls_pull(buf, len);
|
||||
}
|
||||
static ssize_t tls_push_static(gnutls_transport_ptr_t self,
|
||||
const void *buf, size_t len) {
|
||||
return static_cast<SocketConnection *>(self)->tls_push(buf, len);
|
||||
}
|
||||
ssize_t tls_pull(void *buf, size_t len);
|
||||
ssize_t tls_push(const void *buf, size_t len);
|
||||
gnutls_session_t session;
|
||||
#endif
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue