254 lines
7.0 KiB
C++
254 lines
7.0 KiB
C++
|
|
#include "../Common.h"
|
|
#include "../Lockable.h"
|
|
|
|
#include "TCPListener.h"
|
|
#include "IListenerInterface.h"
|
|
#include "TCPClient.h"
|
|
#include "NetworkManager.h"
|
|
#include "NetworkMessage.h"
|
|
#include "PacketReader.h"
|
|
#include "logging/Logger.h"
|
|
|
|
#include <event2/event.h>
|
|
#include <event2/listener.h>
|
|
#include <event2/buffer.h>
|
|
#include <event2/bufferevent.h>
|
|
|
|
#include <mutex>
|
|
#include <cstring>
|
|
#include <cinttypes>
|
|
|
|
namespace Feather::Network
|
|
{
|
|
SocketAddress::SocketAddress(sockaddr* addr)
|
|
{
|
|
if (addr->sa_family == AF_INET)
|
|
{
|
|
auto sin = reinterpret_cast<const sockaddr_in*>(addr);
|
|
evutil_inet_ntop(sin->sin_family, &sin->sin_addr,
|
|
ip, sizeof(ip));
|
|
|
|
port = sin->sin_port;
|
|
}
|
|
else
|
|
{
|
|
auto sin = reinterpret_cast<const sockaddr_in6*>(addr);
|
|
evutil_inet_ntop(sin->sin6_family, &sin->sin6_addr,
|
|
ip, sizeof(ip));
|
|
|
|
port = sin->sin6_port;
|
|
}
|
|
}
|
|
|
|
class TCPSocket
|
|
{
|
|
public:
|
|
TCPSocket()
|
|
{
|
|
m_socket = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
|
|
|
|
// Can't create IPv6 socket? Create an IPv4 one.
|
|
if (!(m_ipv6 = IsValid()))
|
|
{
|
|
Log_Info("Failed to create IPv6 socket, falling back to IPv4.");
|
|
m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
}
|
|
}
|
|
|
|
~TCPSocket()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
bool IsIPV6() const { return m_ipv6; }
|
|
|
|
bool MarkReusable()
|
|
{
|
|
return evutil_make_listen_socket_reuseable(m_socket) == 0;
|
|
}
|
|
|
|
bool MarkDualBind()
|
|
{
|
|
constexpr uint32_t zero = 0;
|
|
return setsockopt(m_socket, IPPROTO_IPV6, IPV6_V6ONLY,
|
|
reinterpret_cast<const char*>(&zero),
|
|
sizeof(zero)) == 0;
|
|
}
|
|
|
|
bool Bind(uint16_t port)
|
|
{
|
|
sockaddr_in6 name_ipv6 = {};
|
|
name_ipv6.sin6_family = AF_INET6;
|
|
name_ipv6.sin6_port = ntohs(port);
|
|
|
|
sockaddr_in name_ipv4 = {};
|
|
name_ipv4.sin_family = AF_INET;
|
|
name_ipv4.sin_port = ntohs(port);
|
|
|
|
return m_ipv6
|
|
? BindGeneric(name_ipv6)
|
|
: BindGeneric(name_ipv4);
|
|
}
|
|
|
|
bool MarkNonBlocking()
|
|
{
|
|
return evutil_make_socket_nonblocking(m_socket) == 0;
|
|
}
|
|
|
|
bool Close()
|
|
{
|
|
if (!IsValid())
|
|
return true;
|
|
|
|
return evutil_closesocket(m_socket) == 0;
|
|
}
|
|
|
|
template <typename T>
|
|
evconnlistener* Listen(evconnlistener_cb callback, T* self)
|
|
{
|
|
if (listen(m_socket, SOMAXCONN) != 0)
|
|
return nullptr;
|
|
|
|
return evconnlistener_new(
|
|
NetworkManager::Instance().GetEventBase(),
|
|
callback, reinterpret_cast<void*>(self),
|
|
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
|
|
0,
|
|
m_socket);
|
|
}
|
|
|
|
bool IsValid() const { return m_socket != EVUTIL_INVALID_SOCKET; }
|
|
|
|
private:
|
|
|
|
template <typename T>
|
|
bool BindGeneric(const T& name)
|
|
{
|
|
return bind(m_socket, reinterpret_cast<const sockaddr*>(&name), sizeof(name)) == 0;
|
|
}
|
|
|
|
SocketHandle m_socket;
|
|
bool m_ipv6;
|
|
};
|
|
|
|
TCPClient::TCPClient(TCPListener* parent, SocketHandle socket, SocketAddress&& addr)
|
|
: m_parent(parent)
|
|
, m_bufferEvent(bufferevent_socket_new(NetworkManager::Instance().GetEventBase(), socket, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE | BEV_OPT_DEFER_CALLBACKS | BEV_OPT_UNLOCK_CALLBACKS))
|
|
, m_address(std::move(addr))
|
|
{
|
|
bufferevent_setcb(m_bufferEvent,
|
|
[](bufferevent* be, void* self) { static_cast<TCPClient*>(self)->ReadCallback(); },
|
|
[](bufferevent* be, void* self) { static_cast<TCPClient*>(self)->WriteCallback(); },
|
|
[](bufferevent* be, int16_t event, void* self) { static_cast<TCPClient*>(self)->EventCallback(event); },
|
|
this);
|
|
bufferevent_enable(m_bufferEvent, EV_READ | EV_WRITE);
|
|
}
|
|
|
|
TCPClient::~TCPClient()
|
|
{
|
|
bufferevent_free(m_bufferEvent);
|
|
}
|
|
|
|
void TCPClient::ReadCallback()
|
|
{
|
|
evbuffer* buffer = bufferevent_get_input(m_bufferEvent);
|
|
const size_t size = evbuffer_get_length(buffer);
|
|
auto [data, lock] = m_data.borrow();
|
|
const size_t offset = data.size();
|
|
data.resize(offset + size);
|
|
if (evbuffer_remove(buffer, &data[offset], size) != size)
|
|
{
|
|
Log_Error("Failed to remove data from buffer.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
void TCPClient::WriteCallback()
|
|
{
|
|
}
|
|
|
|
void TCPClient::EventCallback(int16_t event)
|
|
{
|
|
if (event & BEV_EVENT_ERROR)
|
|
{
|
|
const char* errorString = evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR());
|
|
Log_Error("TCPClient: %s", errorString);
|
|
}
|
|
|
|
if (event & BEV_EVENT_EOF)
|
|
m_parent->OnClientDisconnected(this);
|
|
}
|
|
|
|
void TCPClient::Write(const uint8_t* data, size_t size)
|
|
{
|
|
if (bufferevent_write(m_bufferEvent, data, size) != 0)
|
|
Log_Error("Failed to write to socket, size: " PRIuPTR ".", size);
|
|
}
|
|
|
|
TCPListener::TCPListener(uint16_t port, IListenerInterface* callbacks)
|
|
: m_callbacks(callbacks)
|
|
{
|
|
// Setup the listen socket.
|
|
auto socket = std::make_unique<TCPSocket>();
|
|
|
|
if (!socket->IsValid())
|
|
{
|
|
Log_Error("Socket failed to be created.");
|
|
return;
|
|
}
|
|
|
|
if (!socket->MarkReusable())
|
|
{
|
|
Log_Error("Failed to mark socket as resuable.");
|
|
return;
|
|
}
|
|
|
|
if (socket->IsIPV6() && !socket->MarkDualBind())
|
|
{
|
|
Log_Error("Failed to mark IPv6 socket as dual-bind.");
|
|
return;
|
|
}
|
|
|
|
if (!socket->Bind(port))
|
|
{
|
|
Log_Error("Failed to bind socket to port %hu.", port);
|
|
return;
|
|
}
|
|
|
|
if (!socket->MarkNonBlocking())
|
|
{
|
|
Log_Error("Failed to mark socket as non-blocking.");
|
|
return;
|
|
}
|
|
|
|
auto ListenerCallback = [](evconnlistener* evListener, SocketHandle socket, sockaddr* addr, int len, void* self)
|
|
{
|
|
TCPListener* listener = static_cast<TCPListener*>(self);
|
|
listener->OnClientConnect(std::make_unique<TCPClient>(listener, socket, addr));
|
|
};
|
|
|
|
if (!socket->Listen(ListenerCallback, this))
|
|
{
|
|
Log_Error("Failed to start listening on socket.");
|
|
return;
|
|
}
|
|
|
|
m_socket = std::move(socket);
|
|
}
|
|
|
|
TCPListener::~TCPListener()
|
|
{
|
|
}
|
|
|
|
void TCPListener::OnClientConnect(TCPClientHandle&& client)
|
|
{
|
|
m_callbacks->OnClientConnect(std::move(client));
|
|
}
|
|
|
|
void TCPListener::OnClientDisconnected(const TCPClient* client)
|
|
{
|
|
m_callbacks->OnClientDisconnect(client);
|
|
}
|
|
}
|