From a0531533851764c5bb0c59cd6f0c92ab2da25287 Mon Sep 17 00:00:00 2001
From: Benny Baumann <BenBE@geshi.org>
Date: Tue, 5 Jan 2021 01:54:53 +0100
Subject: [PATCH] chg: Resolve connection target and local port

---
 src/net/tcp_client.cpp | 82 ++++++++++++++++++++++++++----------------
 1 file changed, 51 insertions(+), 31 deletions(-)

diff --git a/src/net/tcp_client.cpp b/src/net/tcp_client.cpp
index 6566890..aaf1928 100644
--- a/src/net/tcp_client.cpp
+++ b/src/net/tcp_client.cpp
@@ -40,42 +40,47 @@ tcp_client::tcp_client(const std::string peer_address_, std::string service_or_p
 		net_socket(nullfd),
 		io{},
 		write_queue{} {
-	this->net_socket = auto_fd(socket(AF_INET, SOCK_STREAM, 0));
+	if (!(ip_addr_family == AF_INET || ip_addr_family == AF_INET6)) {
+		throw netio_exception("Invalid IP address family.");
+	}
+
+	this->net_socket = auto_fd(socket(ip_addr_family, 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 Extract DNS/service resolution into separate library
 	// TODO build another nice HL structure wrapper for outbound connections
+	std::deque<socketaddr> connection_candidates;
 	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));
-	}
+	{
+		//Reduce scope of locally declared variables
+		//May be candidate for extraction into own method
+		addrinfo hints;
+		addrinfo* servinfo = nullptr;
+		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);
+		// TODO: Prefer IPv6 over IPv4
+		for(auto p = servinfo; p != NULL; p = p->ai_next) {
+			if (p->ai_family == AF_INET) {
+				socketaddr sa{(sockaddr_in *)p->ai_addr};
+				connection_candidates.push_back(sa);
+			} else if (p->ai_family == AF_INET6) {
+				socketaddr sa{(sockaddr_in6 *)p->ai_addr};
+				connection_candidates.push_front(sa);
+			}
 		}
+
+		freeaddrinfo(servinfo);
 	}
 
-	freeaddrinfo(servinfo);
 	status = 1;
 	do {
 		if(connection_candidates.empty()) {
@@ -89,19 +94,34 @@ tcp_client::tcp_client(const std::string peer_address_, std::string service_or_p
 				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);
-				}*/
+				socketaddr sa_local;
+				socklen_t sa_local_len = sizeof(sockaddr_storage);
+				if (getsockname(this->net_socket.get(), sa_local.ptr(), &sa_local_len))
+				{
+					// Update length field after the internal structure was modified
+					// TODO: Maybe make this an internal method in socketaddr to update the size
+					sa_local = sa_local.ptr();
+					//The pointer casts are safe due to operator overloading in socketaddr ...
+					switch(sa_local.family()) {
+						case AF_INET:
+							this->port = ntohs(((sockaddr_in*)sa_local)->sin_port);
+							break;
+						case AF_INET6:
+							this->port = ntohs(((sockaddr_in6*)sa_local)->sin6_port);
+							break;
+						default:
+							throw netio_exception("Invalid/unexpected local socket address type");
+					}
+				}
 			}
 		}
+
 		// 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
-- 
GitLab