Skip to main content

Command Palette

Search for a command to run...

Networking Programming: getaddrinfo

Updated
4 min read
Networking Programming: getaddrinfo
T

Software Developer | Love Low Level Eng. | Python, Javascript, C, Linux I'm learning backend development and system programming.

I was reading about network programming and sockets with Beej’s Guide to Networking Programming, 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

#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

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:

# [(<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::

int getaddrinfo(const char *node,
                const char *service,
                const struct addrinfo *hints,
                struct addrinfo **res);
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.

CPythonDescriptionExample
nodehostHostname or IP addresshashnode.com
serviceportThe service type (“http”,…) or Port number80
hints (structure)family, type, protoAddress family (IPv4/v6, …); Socket type (stream, datagram, …); Protocol (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; Canonical name: 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

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.