From b9204190a432927d6c71d463797e7a44fc76afc7 Mon Sep 17 00:00:00 2001
From: Doralitze <doralitze@chaotikum.org>
Date: Mon, 4 Jan 2021 22:51:59 +0100
Subject: [PATCH] add: ctors for tcp_client to connecto to remote server

---
 src/net/tcp_client.cpp | 93 ++++++++++++++++++++++++++++++++++++++++--
 src/net/tcp_client.hpp |  8 ++--
 2 files changed, 95 insertions(+), 6 deletions(-)

diff --git a/src/net/tcp_client.cpp b/src/net/tcp_client.cpp
index aaf02cf..6566890 100644
--- a/src/net/tcp_client.cpp
+++ b/src/net/tcp_client.cpp
@@ -8,27 +8,114 @@
 #include "net/tcp_client.hpp"
 
 #include <ev++.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <sys/types.h>
 #include <netinet/in.h>
 #include <sys/socket.h>
 #include <utility>
+#include <deque>
 
 #include "net/netio_exception.hpp"
+#include "net/socketaddress.hpp"
 
 namespace rmrf::net {
 
 tcp_client::tcp_client(const destructor_cb_type destructor_cb_, auto_fd&& socket_fd, std::string peer_address_, uint16_t port_) :
 		connection_client{},
 		destructor_cb(destructor_cb_),
-		socket(std::forward<auto_fd>(socket_fd)),
 		peer_address(peer_address_), port(port_),
+		net_socket(std::forward<auto_fd>(socket_fd)),
 		io{}, write_queue{} {
 	io.set<tcp_client, &tcp_client::cb_ev>(this);
-	io.start(this->socket.get(), ::ev::READ);
+	io.start(this->net_socket.get(), ::ev::READ);
 	// TODO log created client
 }
 
+tcp_client::tcp_client(const std::string peer_address_, std::string service_or_port, int ip_addr_family) :
+		connection_client{},
+		destructor_cb(nullptr),
+		peer_address(peer_address_),
+		port(0),
+		net_socket(nullfd),
+		io{},
+		write_queue{} {
+	this->net_socket = auto_fd(socket(AF_INET, SOCK_STREAM, 0));
+	if(!this->net_socket.valid()) {
+		// TODO implement proper error handling
+		throw netio_exception("Failed to request socket fd from kernel.");
+	}
+
+	if(!(ip_addr_family == AF_UNSPEC || ip_addr_family == AF_INET || ip_addr_family == AF_INET6)) {
+		throw netio_exception("Invalid IP address family.");
+	}
+
+	// TODO build another nice HL structure wrapper for outbound connections
+	int status;
+	struct addrinfo hints;
+	struct addrinfo* servinfo;
+	memset(&hints, 0, sizeof hints);
+	hints.ai_family = ip_addr_family;
+	hints.ai_socktype = SOCK_STREAM;
+
+	if ((status = getaddrinfo(peer_address_.c_str(), service_or_port.c_str(), &hints, &servinfo)) != 0) {
+	    throw netio_exception("Failed to resolve address '" + peer_address_ + "' with service '" + service_or_port + "': " + gai_strerror(status));
+	}
+
+	std::deque<socketaddr> connection_candidates;
+	for(auto p = servinfo; p != NULL; p = p->ai_next) {
+		if (p->ai_family == AF_INET) {
+			struct sockaddr_in* ipv4 = (struct sockaddr_in *)p->ai_addr;
+			socketaddr sa{ipv4};
+			connection_candidates.push_back(sa);
+		} else if (p->ai_family == AF_INET6) {
+			struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
+			socketaddr sa{ipv6};
+			connection_candidates.push_front(sa);
+		}
+	}
+
+	freeaddrinfo(servinfo);
+	status = 1;
+	do {
+		if(connection_candidates.empty()) {
+			throw netio_exception("Unable to find suitable connection candidate.");
+		}
+		socketaddr socket_identifier = connection_candidates.front();
+		connection_candidates.pop_front();
+		auto_fd socket_candidate{socket(socket_identifier.family(), SOCK_STREAM, 0)};
+		if (socket_candidate.valid()) {
+			if(connect(socket_candidate.get(), socket_identifier.ptr(), socket_identifier.size()) == 0) {
+				status = 0;
+				this->net_socket = std::forward<auto_fd>(socket_candidate);
+				fcntl(this->net_socket.get(), F_SETFL, fcntl(this->net_socket.get(), F_GETFL, 0) | O_NONBLOCK);
+				// Hier bin ich mir nicht sicher, wie ich das am besten mache. Auch mit socketaddr und type cast ist das irgendwie doof.
+				// Das Problem besteht darin, dass erst nach erfolgreichem connect der Port auf dieser Seite bekannt ist.
+				/*
+				struct sockaddr_in sin;
+				if(getsockname(this->net_socket.get(), (struct sockaddr *) &sin, (socklen_t*) &((int) sizeof(sin)))) {
+					this->port = ntohs(sin.sin_port);
+				}*/
+			}
+		}
+		// We don't need to worry about closing broken fd as auto_fd handles this for us
+	} while (status == 1);
+
+
+	io.set<tcp_client, &tcp_client::cb_ev>(this);
+	io.start(this->net_socket.get(), ::ev::READ);
+	//TODO log connected client
+}
+
+tcp_client::tcp_client(const std::string peer_address_, std::string service_or_port) :
+		tcp_client(peer_address_, service_or_port, AF_UNSPEC) { }
+
+tcp_client::tcp_client(const std::string peer_address_, const uint16_t port_) :
+		tcp_client(peer_address_, std::to_string(port_)) {}
+
 tcp_client::~tcp_client() {
-	destructor_cb(EXIT_STATUS_NO_ERROR);
+	if(destructor_cb != nullptr)
+		destructor_cb(EXIT_STATUS_NO_ERROR);
 	io.stop();
 }
 
diff --git a/src/net/tcp_client.hpp b/src/net/tcp_client.hpp
index a6c73d8..d7690ae 100644
--- a/src/net/tcp_client.hpp
+++ b/src/net/tcp_client.hpp
@@ -45,15 +45,17 @@ public:
 	typedef std::function<void(exit_status)> destructor_cb_type;
 private:
 	const destructor_cb_type destructor_cb;
-	const auto_fd socket;
 	const std::string peer_address;
-	const uint16_t port;
 
+	uint16_t port;
+	auto_fd net_socket;
 	::ev::io io;
 	std::list<std::shared_ptr<impl::NICBuffer>> write_queue;
 public:
 	tcp_client(const destructor_cb_type destructor_cb_, auto_fd&& socket_fd, std::string peer_address_, uint16_t port_);
-	tcp_client(const std::string);
+	tcp_client(const std::string peer_address_, const uint16_t port_);
+	tcp_client(const std::string peer_address_, std::string service_or_port);
+	tcp_client(const std::string peer_address_, std::string service_or_port, int ip_addr_family);
 	virtual ~tcp_client();
 	virtual void write_data(std::string data);
 	std::string get_peer_address();
-- 
GitLab