IPv4/IPv6 Socket Client Guide

I now qualify myself as being good enough to make a tutorial on tcp sockets in C. This will cover both IPv4 and IPv6, but only the client part for now. I assume you know that sockets are endpoints for software to communicate with each other over a network, and that this communication can be founded on a software port when using TCP or UDP. Now the main difference between IPv4 and IPv6 is the IP addressing. In IPv4 an IP address looks something like “192.168.1.1”, while an IPv6 address looks like “1234:5678:9abc:def0:dead:cafe:beef:feed”, it’s a necessary step to switch to IPv6 since the IPv4 address space will run out (which should be anytime in 2011).

This will add the appropriate includes depending on whether you’re using Linux is Windows:

#if defined(_WIN32)
#pragma message("Warning: Windows detected.")
#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#else if defined(linux)
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#else
#error gtfo macfag.
#endif

Also, as I show code examples, assume that the fail function is defined as follows:

#include <stdarg.h>
void fail(const char* format, ...) {
	va_list v;
	va_start(v, format);
	vprintf(format, v);
	exit(1);
}

If you use Windows, you must issue a call to WSAStartup before using any socket related functions. Like so (#include <stdlib.h>):

struct WSAData* wd = (struct WSAData*)malloc(sizeof(struct WSAData));
if (WSAStartup(MAKEWORD(2, 0), wd)) fail("Winsock API startup failed.\n");
free(wd);

WSAStartup prepares the winsock api to be used by a program. First argument is the required winsock api version (in this case 2.0), and the second argument is a reference to an over 500 byte struct that gets filled with useless garbage that the winsock api pukes out. All you need to know about it’s return value is that 0 means success and anything else means failure.

[u]IPv4 Socks[/u]

In Linux, a socket is actually just a file descriptor of type int. In Windows, it’s defined as SOCKET; but in the end it’s actually the same thing as an int. Depending on which OS, define sock as either int or SOCKET. Then you create the socket and assign it to sock:

sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

The arguments are: domain, socket type, and protocol. AF_INET means Internet Protocol version 4 domain, SOCK_STREAM means stream-like connection-oriented socket, and IPPROTO_TCP is the macro value for the TCP protocol. That’s the type of socket you make if you want to do something like make a connection to an http or irc server. Alternatively you could say SOCK_DGRAM and IPPROTO_UDP if you want to just send datagrams to a server without making a connection. Or even SOCK_RAW and IPPROTO_RAW for crafting packets and sending raw data. But in this tutorial, we will be focusing on TCP sockets.

So now we created our socket. If the call failed, the return value would be -1. So here’s an error-checking way of making a socket:

if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) fail("Failed to create IPv4 TCP stream socket.\n");

Now you’re probably assuming that we can now connect our socket. Right? …in your wet dreams. We have to set up a struct that specifies the IP address and port to connect to. For beginners, it could be complicated. Don’t worry, I’ll walk you through it. Let’s first declare this structure:

struct sockaddr_in sa;

That wasn’t so hard was it? Now sockaddr_in has a number of members:
[list]
[*] sin_addr holds another structure called in_addr that [s]represents[/s] is the IP address of the [s]victim[/s] host.
[*] sin_port holds the port number to connect to (in network byte order)
[*] sin_family holds the addressing family (use AF_INET)
[*] sin_zero is an 8 byte reserved block that must be set to zero[/list]
Ok, let’s fill up these members. Let’s start with these:

sa.sin_family = AF_INET;       //IPv4
sa.sin_port = htons(666);      //Port number 666 converted to network-byte order
memset(&(sa.sin_zero), 0, 8);  //Set reserved block to 0

htons means Host to Network Short. You see, the way shorts or ints are represented in memory have different byte ordering depending on your computer architecture. Similar functions for converting between your computer’s byte ordering and network byte ordering include:
[list]
[*] htonl – Host to Network Long (unsigned int)
[*] ntohl – Network to Host Long (unsigned int)
[*] ntohs – Network to Host Short (unsigned short)[/list]
Now for the sin_addr member, if you are already given an IP address in a string format representation you can just something like this:

char ip[] = "94.142.241.111";
sa.sin_addr.s_addr = inet_addr(ip);

If the above doesn’t work on windows, try this:

sa.sin_addr.S_un.S_addr = inet_addr(ip);

inet_addr will convert string representing an IP address to an unsigned long IP address value to be assigned to sin_addr.
Now if you are given a hostname instead of an IP address, then it gets harder. I’m just going to give you the code for that first:

char hostname[] = "towel.blinkenlights.nl";
sa.sin_addr = *(struct in_addr*)gethostbyname(hostname)->h_addr;

Well that was a mouthful. gethostbyname return a reference to a structure that contains information about a host given it’s name. One of these members is h_addr, which is a pointer to the IP address of that host. Since it’s a pointer and treated as an unsigned int (i think), we cast it as an in_addr structure pointer and dereference it to assign to sin_addr.

Using gethostbyname, we could make a small program that looks up the IP address of a host. This will compile on both Windows and Linux:

#if defined(_WIN32)
#pragma message("Warning: Windows detected.")
#include <WinSock2.h>
#include <WS2tcpip.h>
#else
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

void fail(const char* format, ...) {
	va_list v;
	va_start(v, format);
	vprintf(format, v);
	exit(1);
}

int main(int argc, char** argv) {
	if (argc != 2)
		fail("IP address lookup\nArgument: HOSTNAME\n");

#if defined(_WIN32)
	struct WSAData* wd = (struct WSAData*)malloc(sizeof(struct WSAData));
	if (WSAStartup(MAKEWORD(2, 0), wd))
		fail("Winsock API startup failed.\n");
	free(wd);
#endif

	struct hostent* host;
	if (!(host = gethostbyname(argv[1])))
		fail("Failed to lookup %s\n", argv[1]);
	printf("%s\n", inet_ntoa(*(struct in_addr*)host->h_addr));

#if defined(_WIN32)
	WSACleanup();
#endif
	return 0;
}

Noticed I used inet_ntoa. inet_ Network to Array (i think) will convert an IP address to a string. Or more specifically, it will convert an IN_ADDR structure into a IPv4 dotted decimal notation character array. inet_aton will do the opposite, convert an IP address string to an IN_ADDR.

Sorry I got off track. Now that we have the socket created and this socket address structure filled out, we can finally issue a connection:

connect(sock, (struct sockaddr*)&sa, sizeof(struct sockaddr));

3 arguments: the socket, socket address structure pointer (treated as sockaddr instead of sockaddr_in), and length of structure (sockaddr and sockaddr_in are same length).
If all goes well, connect will return 0.
Once connected, you can then send or receive data with the host. The function for sending data is send:

char msg[] = "hello world!\r\n";
send(sock, msg, strlen(msg), 0);

Arguments for send: the socket, the buffer, the size of buffer, last argument specifies whether your socket is a transvestite. Just put 0 for no.
Receiving data from the socket uses the same arguments:

char buf[512];
recv(sock, buf, 512, 0);

Except the 3rd argument for recv specifies the maximum length of the receive buffer and not necessarily the amount of data that will be read.
Both send and recv return the number of bytes read or written to the socket or -1 on error.

Now here’s a working example of a socket program to use a HEAD http request to google (we’re not asking google for a BJ).
For Linux:

#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char** argv) {
	char buf[4096];

	int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	struct sockaddr_in sa;
	sa.sin_family = AF_INET;
	sa.sin_port = htons(80);
	sa.sin_addr = *(struct in_addr*)gethostbyname("www.google.com")->h_addr;
	memset(&(sa.sin_zero), 0, 8);

	connect(sock, (struct sockaddr*)&sa, sizeof(struct sockaddr));
	strcpy(buf, "HEAD / HTTP/1.1\r\n\r\n");
	send(sock, buf, strlen(buf), 0);
	recv(sock, buf, 4096, 0);
	puts(buf);
	
	return 0;
}

For Windows:

#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char** argv) {
	char buf[4096];

	struct WSAData* wd = (struct WSAData*)malloc(sizeof(struct WSAData));
	WSAStartup(MAKEWORD(2, 0), wd)
	free(wd);

	SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	struct sockaddr_in sa;
	sa.sin_family = AF_INET;
	sa.sin_port = htons(80);
	sa.sin_addr = *(struct in_addr*)gethostbyname("www.google.com")->h_addr;
	memset(&(sa.sin_zero), 0, 8);

	connect(sock, (struct sockaddr*)&sa, sizeof(struct sockaddr));
	strcpy(buf, "HEAD / HTTP/1.1\r\n\r\n");
	send(sock, buf, strlen(buf), 0);
	recv(sock, buf, 4096, 0);
	puts(buf);
	
	WSACleanup();
	return 0;
}

[u]IPv6 Socks[/u]
IPv6 is totally different. Now I believe the best and most widely used method for IPv6 is to use getaddrinfo. This function will look up a host given an a hostname/IP address and port, it will return a linked list of socket addresses that you can use for aiding in the creation of a socket and connect. First we need 2 things:

struct addrinfo* ai;
struct addrinfo hints;

ai will hold the pointer of the linked list of addrinfo structures returned by getaddrinfo, and hints will carry the fields to specify what kind of addrinfo structures should be returned.

memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;

First we clear hints. Then by setting these fields in hints, we specify that the returned addrinfo structures will have either an AF_INET or AF_INET6 address family (IPv4 or IPv6), support stream type sockets, and the TCP protocol. Yes, AF_UNSPEC becuase we don’t know if the host support IPv6 or only IPv4. Now we look up our host:

getaddrinfo("www.google.com", "80", &hints, &ai);

“80” is the port number, the reason why it’s a string is because it can also be the name of a service such as “www” or “ftp”. A service is just a name that maps to a port number and protocol type. To see the list of Internet services in Linux, see /etc/services. For Windows it’s, C:\Windows\system32\Drivers\etc\services.
Now we have a linked list of addrinfo structures. An addrinfo structure is this:

struct addrinfo {
	int ai_flags;              //Useless options
	int ai_family;             //Domain
	int ai_socktype;           //Socket type
	int ai_protocol;           //Protocol
	size_t ai_addrlen;         //Length of socket address
	struct sockaddr *ai_addr;  //Pointer to socket address
	char *ai_canonname;        //Name of host (if AI_CANONNAME set on ai_flags)
	struct addrinfo *ai_next;  //Link list next
};

Now we can create the socket and connect:

sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
connect(sock, ai->ai_addr, ai->addrlen);

See how useful getaddrinfo is? It contains everything we need to fill out the parameters for socket and connect functions. Not only is it everything we need, but it’s also within range of everything we want because we specified the criteria for addrinfo with hints.
After we’re done with the addrinfo linked list, we need to free it:

freeaddrinfo(ai);

From there, we can just use the send and recv functions normally. Here’s a Linux/Windows compatible IPv4/IPv6 compatible HTTP HEAD request with google:

#if defined(_WIN32)
#include <WinSock2.h>
#include <Ws2tcpip.h>
#pragma command(lib, ws2_32.lib)
#else
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char** argv) {
	char buf[4096];

#if defined(_WIN32)
	SOCKET sock;
	struct WSAData* wd = (struct WSAData*)malloc(sizeof(struct WSAData));
	WSAStartup(MAKEWORD(2, 0), wd);
	free(wd);
#else
	int sock;
#endif

	struct addrinfo* ai;
	struct addrinfo hints;
	memset(&hints, 0, sizeof(struct addrinfo));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	getaddrinfo("www.google.com", "80", &hints, &ai);
	sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
	connect(sock, ai->ai_addr, ai->ai_addrlen);
	freeaddrinfo(ai);
	strcpy(buf, "HEAD / HTTP/1.1\r\n\r\n");
	send(sock, buf, strlen(buf), 0);
	recv(sock, buf, 4096, 0);
	puts(buf);

#if defined(_WIN32)
	WSACleanup();
#endif
	return 0;
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: