#+BEGIN_COMMENT .. title: OpenSSL Sockets in C++ (part 1) .. slug: openssl-sockets-in-c++-part-1 .. date: 2014-12-22 16:52:09 UTC-08:00 .. tags: c++,sockets .. link: .. description: .. type: text #+END_COMMENT The goal of this tutorial series is to walk through using posix sockets, from the ground up. Projects merely wishing to add networking would probably be best advised to look at already well established abstraction layers like [[http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio.html][Boost Asio]]. To start off, we're going to create a basic http request. To keep things simple, for the first iteration, we're going to use a plain TCP blocking socket. First create a cpp file (mine is named sockets\_part1.cpp). First some constants and includes: #+NAME: sockets_part1.cpp #+BEGIN_SRC cpp #include #include #include #include #include namespace { const char HOST[] = "fizz.buzz"; const size_t BUFFER_SIZE = 1024; } int main(int argc, char** argv) { return 0; } #+END_SRC The first step to most network connections is doing a DNS request to convert a hostname like "fizz.buzz" to an ip address like "208.113.196.82". For illustration purposes you could manually do a DNS request from the shell with the following command: #+BEGIN_SRC sh $ dig +short fizz.buzz 208.113.196.82 #+END_SRC To make a DNS request we will be using [[https://www.freebsd.org/cgi/man.cgi?query=getaddrinfo&sektion=3][getaddrinfo(3)]] which will set the address of an addrinfo pointer passed into it. #+BEGIN_SRC cpp struct addrinfo* address_info; int error = getaddrinfo(HOST, "http", nullptr, &address_info); if (error != 0) { throw std::string("Error getting address info: ") + std::string(gai_strerror(error)); } #+END_SRC The second parameter to getaddrinfo defines the port. This can be a string for a protocol like "http" or "https", or it can be a numeric string like "80" and "443". The third parameter to getaddrinfo is a set of "hints" indicating what type of connection we're looking to open. The hints param is optional and could just be a nullptr in this case without issue. In our hints we're setting =ai_family= to =PF_UNSPEC= to indicate that we are fine with any protocol. We're also setting =ai_socktype= to =SOCK_STREAM= to indicate that we wish to open a TCP byte stream. The =addrinfo= struct that =address_info= now points to looks like this: #+BEGIN_SRC text addrinfo: ai_flags 0 ai_family 2 # AF_INET ai_socktype 1 # SOCK_STREAM ai_protocol 6 # IPPROTO_TCP ai_addrlen 16 # Length in bytes for the next field (ai_addr) ai_addr sockaddr_in sin_family 2 # AF_INET (ipv4) sin_port 80 # default http port sin_addr 208.113.196.82 # ipv4 address to fizz.buzz ai_canonname ai_next nullptr # Forms a linked list #+END_SRC As you can see we have all the details set for a TCP socket on port 80 to the ip address of fizz.buzz. Next we need to open a connection to the server. In the =addrinfo= struct we just generated there is an =ai_next= field that forms a singly-linked list, allowing =getaddrinfo= to return multiple values (for instance in the case where ipv4 and ipv6 would be supported. To handle that we will have to loop over the list trying to connect to each entry until we have a successful connection. #+BEGIN_SRC cpp int connection = -1; std::string error_string = ""; for (struct addrinfo* current_address_info = address_info; current_address_info != nullptr; current_address_info = current_address_info->ai_next) { connection = socket(current_address_info->ai_family, current_address_info->ai_socktype, current_address_info->ai_protocol); if (connection < 0) { error_string = "Unable to open socket"; continue; } if (connect(connection, current_address_info->ai_addr, current_address_info->ai_addrlen) < 0) { error_string = "Unable to connect"; close(connection); // Cleanup connection = -1; continue; } break; // Success } if (connection < 0) // If we failed to connect { throw error_string; } #+END_SRC This loop is walking down the singly linked list trying each entry for a connection. First it attempts to open up a socket and checks to ensure that was successful. The opening of the socket is a local operation that doesn't involve any calls out to fizz.buzz. Next it tries to actually open the connection which is where the fizz.buzz server comes into play for the first time. Now we're ready to make an HTTP request. We're going to make a very basic request for the home page with no special fields like cookies and user agents. The request string will look like this: #+BEGIN_SRC text GET / HTTP/1.1 Host: fizz.buzz #+END_SRC #+BEGIN_SRC cpp std::string http_query = "GET / HTTP/1.1\r\n" \ "Host: " + std::string(HOST) + "\r\n\r\n"; send(connection, http_query.c_str(), http_query.size(), 0); #+END_SRC Finally we will need to read the result from the socket. Since we are using blocking sockets, the [[https://www.freebsd.org/cgi/man.cgi?query=recv&apropos=0&sektion=3&manpath=SuSE+Linux%2Fi386+11.3&arch=default&format=html][recv(3)]] call will wait until either there is data available or the connection has been closed before returning, which keeps this block of code simple. #+BEGIN_SRC cpp char buffer[BUFFER_SIZE]; for (ssize_t read_size = recv(connection, buffer, BUFFER_SIZE, 0); read_size > 0; read_size = recv(connection, buffer, BUFFER_SIZE, 0)) { std::cout << std::string(buffer, read_size); } #+END_SRC Now all we have left to do is cleanup after ourselves #+BEGIN_SRC cpp close(connection); freeaddrinfo(address_info); #+END_SRC Awesome! Lets compile and run the program #+BEGIN_SRC sh $ clang++ -std=c++11 -o sockets_part1 files/sockets_part1.cpp $ ./sockets_part1 #+END_SRC Lets also check for memory leaks and run some static analysis #+BEGIN_SRC sh $ valgrind --leak-check=full ./sockets_part1 $ scan-build clang++ -std=c++11 -o sockets_part1 files/sockets_part1.cpp #+END_SRC Looks good! In [[http://fizz.buzz/posts/openssl-sockets-in-c++-part-2.html][part 2]] we will port this code over to non-blocking sockets. The source code for this post is available [[http://fizz.buzz/post_files/sockets_part_1/sockets_part1.cpp][here]] under the ISC license.