Learning C in 2020 – Absolute Beginner – Listing Network Adapters on Windows

Book: Hands-On Network Programming with C: Learn socket programming in C and write secure and optimized network code

Overview reference

This implementation is provided in the book introduced above. I am simply going line by line to further understand.


Problem

Goal

List all network adapters on windows machine.

Pseudo-code

  1. Get Network Adapters
  2. Print out basic socket details

Theoretical Background

Bit

The smallest unit of data in computing is called binary digit and its acronym “bit”. It has two possible values namely, 0 and 1.

Byte

An eight bit cluster is termed a byte. Seemingly it is the smallest addressable unit of memory in computing. I suppose that means data types such as strings, integers, characters etc, when translated in bits, cannot have a bit length lower than 8.

Do correct me if I understood incorrectly.

Bitwise Operation

If you are already acquainted with programming languages such as Java, JavaScript etc, you should know how to “ADD” a string to another string or “SUBTRACT” an integer from another integer. Likewise, such operations can be performed on the byte-level and are called bitwise operations. The bitwise operators are as follows:

Symbol Bitwise Operator Action
& AND This operator results in a 1 only if both bits are a 1,else a 0 is expected
| OR If any of the two bits is a 1, the result is a 1
^ XOR The operation results in a 1 only if both bits are different
<< Left shift ??
>> Right shift ??
~ NOT Inverts all the bits

C Syntax

#ifndef

This checks whether a token is defined or included in the file.

#ifndef <token>

#define

Use this to assign a value (token) to an identifier in a file. For instance

#define identifier <token>
#define WIDTH 80

Every occurence of WIDTH in the file will be translated to 80.

int main()

The c program’s execution starts with this function. The int indicates the return value type. This implies a number has to be returned at the end of the program. 0 denotes that the program ran successfully.

C Libraries

<stdio.h>

Which stands for Standard Input and Output library. It provides functions needed to open, delete, rename a given file and other methods to manipulate a data stream.

<winsock2.h>

The windows library for socket programming

<iphlpapi.h>

Applications in need of IP helper functions use this library.

<ws2tcpip.h>

A library also required for socket programming.

<stdlib.h>

This library provides functions with which you can manipulate memory, exit a program and even sort an array.

Internet Protocol

This protocol is the instruction manual for datagram encapsulation and routing over internet network communication. There are two major versions of the protocol, version 4 ( IPv4) and version 6 (IPv6).

Each IP packet, datagram, consists of a header and a payload. The header carries information about the source IP address, destination IP address and more details on the structure of the packet. Whereas one can find the data to be transmitted in the payload.

Network Adapters

Also referred to as a Network Interface Controller and is a physical component that connects the computer to a network. The NIC is both the physical and data link layer in the OSI model.

I never knew this, but now I know.

Solution

Include required libraries

The first three lines ensure the identifier _WIN32_WINNT is defined before anything happens. Apparently this variable defines the version of window header files to include. Since the identifier is assigned the hexadecimal 0x0600, as further explained here , the windows version may be Windows Vista or Windows Server 2008. See more here.

#ifndef _WIN32_WINNT             
#define _WIN32_WINNT 0x0600        
#endif                         
#include <stdio.h>      
#include <winsock2.h>             
#include <iphlpapi.h>              
#include <ws2tcpip.h>             
#include <stdlib.h>               
#pragma comment(lib, "ws2_32.lib")

Startup

WSADATA

A variable of type WSADATA, (WSA: Windows Sockets API) which contains data structure information of the socket implementation (honestly do not know what that means yet,) is declared.

int main()
{
   
    WSADATA d;
    if (WSAStartup(MAKEWORD(2, 2), &d))
    {
        printf("Failed to initialize.\n");
        return -1;
    }

WSAStartup

The WSAStartup function takes in a 16 bit word as the first argument, and the memory address of the declared d variable.

Although I read through the documentation several times, I honestly still do not understand what it does. They keep saying socket implementation; like what does that even mean??! Explanations are very welcomed.

The function call return values are listed here.

How much memory do you need ?

Next we declare a variable called asize of type unsigned long int and assign the value of 20,000. This integer type can hold a value ranging from 0 to 18446744073709551615. Additionally a variable declared as adapters will be used to store a linked list of IP_ADAPTER_ADDRESSES structures.

Memory allocation

In the do block, the malloc function allocates memory of size 20000 byte size and returns a pointer to the allocated memory addresses. If there was an error during the memory allocation, the program is exited. Else, the program continues.

Following the memory allocation, as already defined here, the getadaperaddresses function gets all the addresses of all the adapters on the machine.

AF_UNSPEC

The first argument indicates the address family to be retrieved by the function. Since the argument is AF_UNSPEC, the function will retrieve both IPv4 and IPv6 addresses.

GAA_FLAG_INCLUDE_PREFIX

The second argument suggests that the addresses to be retrieved are either IPv4 or IPv6.

Adapters

The fourth address is a pointer to the memory allocated for the linked list IP_ADAPTER_ADDRESSES structures ( “adapters”).

Byte size of linked list adapter addresses

The last variable points to the memory address of the variable holding the size of the adapters variable.

Return value

If the function successfully retrieves the adapter addresses, the value returned is “ERROR_SUCCESS” ( I personally find the naming a bit confusing, like why error and success ?), also known as NO_ERROR. Else the following are possible return values :

  • ERROR_ADDRESS_NOT_ASSOCIATED
  • ERROR_BUFFER_OVERFLOW
  • ERROR_INVALID_PARAMETER,ERROR_NOT_ENOUGH_MEMORY
  • ERROR_NO_DATA
  • others

Log Adapter addresses

The next lines of code free up the allocated memory for the adapter addresses and exit the program when the function does not return NO_ERROR. Otherwise we loop through the linked list and print out each address information.

Linked List detailed blog post

Adapter information

FriendlyName

Type: PWCHAR | WCHAR ( 16-bit Unicode character)

A human readable form for the adapter

FirstUnicastAddress

Type: PIP_ADAPTER_UNICAST_ADDRESS | IP_ADAPTER_UNICAST_ADDRESS_LH

Points to the first element in the linked list containing all IP unicast addresses of a given adapter.

Address

Type : Socket Address

Each unicast address of a given adapter holds an Address property which provides protocol specific information about the address. Namely:

lpSockaddr

Holds the memory address of a socket address

iSockaddrLength

Stores the bytes-length of the socket address

Other accessible properties

Description, PhysicalAddress, PhysicalAddressLength

Read here for more.

ULONG asize = 20000;
    PIP_ADAPTER_ADDRESSES adapters;
    do
    {
        adapters = (PIP_ADAPTER_ADDRESSES)malloc(asize);

        if (!adapters)
        {
            printf("Couldn't allocate %ld bytes for adapters.\n", asize);
            WSACleanup();
            return -1;
        }

        int r = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, NULL, adapters, &asize);
        if (r == ERROR_BUFFER_OVERFLOW)
        {
            printf("\nGet Adapters Addresses wants %ld bytes.\n", asize);
            free(adapters);
        }
        else if (r == NO_ERROR)
        {
            PIP_ADAPTER_ADDRESSES adapter = adapters;
            while (adapter)
            {
                printf("\nAdapter name: %S\n", adapter->FriendlyName);
                PIP_ADAPTER_UNICAST_ADDRESS address = adapter->FirstUnicastAddress;
                while (address)
                {
                    printf("\t%s", address->Address.lpSockaddr->sa_family == AF_INET ? "IPv4" : "IPv6");
                    char ap[100];
                    getnameinfo(address->Address.lpSockaddr, address->Address.iSockaddrLength, ap, sizeof(ap), 0, 0, NI_NUMERICHOST);
                    printf("\t%s\n", ap);
                    address = address->Next;
                }
                adapter = adapter->Next;
            }
            free(adapters);
            WSACleanup();
            return 0;
        }
        else
        {
            printf("\nError from  GetAdaptersAddresses: %d. \n", r);
            free(adapters);
            WSACleanup();
            return -1;
        }
    } while (!adapters);

Result

Command to run:

Author Notes

This is most likely not the cleanest implementation, just a heads up.

Links

  1. Bit
  2. Byte
  3. Operators in C
  4. More on bitwise operators
  5. Bits and Bytes broken-down
  6. #ifndef
  7. Pragma directives
  8. The difference between int main() and int main(void)
  9. Stdio.h
  10. Socket programming with winsock
  11. IP Helper Application
  12. Stdlib.h
  13. Internet Protocol
  14. Network Adapter
  15. What is _WIN32_WINNT and how does it work?
  16. What is the meaning of & in c language?
  17. What is MAKEWORD used for?
  18. C data types