# Networking Programming: getaddrinfo

I was reading about network programming and sockets with [Beej’s Guide to Networking Programming](https://beej.us/guide/bgnet/html/split/), and we ended up writing a simplified version of the `nslookup` program in C. This program takes a domain name and finds the IPs associated with it by querying the DNS. *nslookup does more than that*, but for us, it's impressive to create something similar. After that, I tried to build the same thing in Python and laughed at the result: just 3 lines of code.

So, I'll show you the C code first, followed by the Python code.

`showip.c`

```c
#define _POSIX_C_SOURCE 200112L

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

int main(int argc, char const *argv[])
{
	int status;
	struct addrinfo hints, *res, *p;
	char ipstr[INET6_ADDRSTRLEN];

	if (argc != 2)
	{
		fprintf(stderr, "usage: showip hostname\n");
		return 1;
	}

	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;

	if ((status = getaddrinfo(argv[1], "80", &hints, &res)) != 0)
	{
		fprintf(stderr, "gai error: %s\n", gai_strerror(status));
		return 2;
	}

	printf("IP addressess for %s: \n\n", argv[1]);

	for (p = res; p != NULL; p = p->ai_next)
	{
		void *addr;
		char *ipver;
		struct sockaddr_in *ipv4;
		struct sockaddr_in6 *ipv6;

		if (p->ai_family == AF_INET)
		{
			ipv4 = (struct sockaddr_in *)p->ai_addr;
			addr = &(ipv4->sin_addr);
			ipver = "IPv4";
		}
		else
		{
			ipv6 = (struct sockaddr_in6 *)p->ai_addr;
			addr = &(ipv6->sin6_addr);
			ipver = "IPv6";
		}

		inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
		printf(" %s: %s\n", ipver, ipstr);
	}

	freeaddrinfo(res);
	return 0;
}
```

And now the `showip.py`

```python
import socket
import sys
from socket import AddressFamily

FAMILY_TYPE = {"AF_INET": "IPv4", "AF_INET6": "IPv6"}
PORT = '80'

def main():
    host = sys.argv[1]
    try:
        host_info = socket.getaddrinfo(host,  # hostname
                                       PORT,  # hostname port
                                       socket.AF_UNSPEC,  # IPv4/IPv6
                                       socket.SOCK_STREAM # TCP,
                                       flags=socket.AI_CANONNAME
                                       )
        for info in host_info:
            family = AddressFamily(info[0]).name
            text = f"{FAMILY_TYPE[family]}: {info[4][0]} --> {info[3]}"
            print(text)
    except socket.gaierror:
        print(f"Hostname {host} could not be resolved.", file=sys.stderr)
        return

if __name__ == "__main__":
    if len(sys.argv) != 2:
        raise ValueError("usage: showip hostname")
    main()
```

When we run the Python code, we get this:

```bash
# [(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, 'hashnode.com', ('66.33.60.194', 8081)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('76.76.21.22', 8081))]
$ IPv4: 66.33.60.194 -->  hashnode.com
  IPv4: 76.76.21.22 -->
```

We can see that even though the Python code is shorter, it uses a similar API to the C code. This is because Python simply wraps the `socket` API provided in C, which helps reduce some of the developer's workload. The key part is using `getaddrinfo` (get address info), which is defined like this::

```c
int getaddrinfo(const char *node,
                const char *service,
                const struct addrinfo *hints,
                struct addrinfo **res);
```

```python
socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0)

# and returned value
# list if of tuples (family, type, proto, canonname, sockaddr)
```

You can already see the similarity.

| **C** | **Python** | **Description** | **Example** |
| --- | --- | --- | --- |
| `node` | `host` | <mark>Hostname</mark> or IP address | `hashnode.com` |
| `service` | `port` | The <mark>service type</mark> (“http”,…) or <mark>Port number</mark> | `80` |
| `hints` (structure) | `family`, `type`, `proto` | <mark>Address family</mark> (IPv4/v6, …); <mark>Socket type</mark> (stream, datagram, …); <mark>Protocol</mark> (TCP, UDP, …) | family: `AF_INET` (IPv4), `AF_INET6` (IPv6), `AF_UNSPEC`(unspecified) type: `SOCK_STREAM` proto: `0` (any), if set to 0 and type to SOCK\_STREAM the system will pick TCP |
| `res` (Linked List) | list of (`family`, `type`, `proto`, `canonname`, `sockaddr`) | family, type, proto are the same as decrbed above; <mark>Canonical name</mark>: the true host name by the DNS (will read about it) sockaddr is a tuple of IP and Port |  |

In the C code, the result is stored in a linked list, and the head's address is passed to `getaddrinfo`. This is why we iterate over `res` and `res->ai_next`. In Python, the library takes care of this iteration, so we just go through the list of tuples until the end.

Why a list of IPs? Because a hostname can have multiple IPs (both IPv4 and IPv6) associated with it.

Good, right? Yeah, but I wondered how Python's `getaddrinfo` is implemented behind the scenes, so I asked ChatGPT, and it gave me this: [https://chatgpt.com/s/t\_68eba17bdb4481919241ce01a7890c5b](https://chatgpt.com/s/t_68eba17bdb4481919241ce01a7890c5b)

You can read it to get some ideas (I didn't understand everything).

**Done for now, see you next time!**

It's great to see the work Python has already done for developers and how things operate behind the scenes with C. I'm currently learning about network programming, so there might be a future article on sockets or similar topics.
