05 October 2010

The magic of sockets

What I wanted to know, is how to use linux sockets to get what I want (either the whole IP packet, or just the payload, and so on) from what packets I want (like only packets with a specific protocol, and so on).
socket(domain, family, protocol);
This is the important function to use. There are two domains which interest me.

PF_PACKET
This family of sockets is used to retrieve the packets directly from the ethernet interface. Two types are defined:
- SOCK_RAW: retrieves the whole ethernet frame (including the ethernet header).
- SOCK_DGRAM: retrieves only the IP/IPv6 packet (without ethernet header).
The third parameter is the protocol.
- ETH_P_IP/ETH_P_IPV6: only IPv4/IPv6 packets are retrieved.
- ETH_P_ALL: all packets are retrieved. However, when using ETH_P_ALL, the outgoing packets are retrieved as well.

PF_INET
This family of sockets is used to retrieve IP packets from the kernel. Three types are defined here:
- SOCK_DGRAM: this is used to listen for UDP packets. Only UDP packets cand be received/send using this type of socket
- SOCK_STREAM: this is used to listen for TCP connections. Only TCP packets can be received/sent using this type of socket
- SOCK_RAW: This is (in my opinion) the most interesting type. This is used to retrieve whichever protocol you like. If you use this option, you must always specify the protocol (like IPPROTO_GRE for GRE tunnels). When sending packets using this type of socket, the socket will send the packet by adding an IP header over the payload, which has the protocol with which the socket was opened. If you use SOCK_RAW, you can actually create your own IP header and send the whole IP packet from the socket. In order to do this, you must use the setsockopt function to add the option IP_HDRINCL to the socket.

Another important domain is the PF_UNIX domain, but since I haven't had much work to do with it until now, I won't discuss it here.

Once you opened the socket, simply bind it to an interface. For PF_PACKET sockets, as far as I know, you must bind it to an interface, while the AF_INET sockets can be bound to IP addresses on the interfaces.

11 September 2010

Getting the Link-Local Address from an interface

Recently I had to bind a socket to a link-local address and one of the issues was getting the IP of that interface. Since ioctl calls don't really work with IPv6 addresses, the solution I found was the getifaddrs() function. This function returns an array of 'ifaddrs' structs with all the IPs on all interfaces. After the list is returned, I only had to look in the list for my IP. I posted the code to my function. Feel free to ask questions or bring suggestions.
The code itself is a method to return the link-local address of an interface, however with simple modifications it can be used to retrieve any address needed.

So I defined a function which receives 3 parameters, the interface name, as a string of chars, the interface name length, which will be helpful later on when comparing the interface names with those we found, and the ip address structure where we want the IP address returned. Like Brandon mentioned, sys/types.h and ifaddrs.h are needed :).
#include "sys/types.h"
#include "ifaddrs.h"
int get_link_local_addr(char* if_name, int if_name_length, struct sockaddr_in6 *ip)
{
Two variables will be declared. The first one will be the return value for the getifaddrs function, consisting in the list of addresses returned. The second one will be used as an iterator to go through the list, once it is returned. An additional int will be declared, to store the return value.
    struct ifaddrs *ifaddr, *ifa;
    int ret = -2;
Call the getifaddrs function to populate the address list.
    if (getifaddrs(&ifaddr) == -1) {
        perror("getifaddrs");
        ret = -1;
        goto end;
    }
Now we have to walk through the list to find the IP we are looking for. In this example, I am looking for the link-local address, so I am looking for the ipv6 addres for my local interface which has the net address FE80::/10. This means, the first byte will be FE and the second one will be between 80 and BF.
    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next)
    {
Since I am looking for IPv6 only, everything else is discarded
        if (ifa->ifa_addr->sa_family != AF_INET6) continue;
For each IPv6 address a check is made to see if the address is set on the right interface or not.
        if (strncmp(ifa->ifa_name, if_name, if_name_length)) continue;
        struct sockaddr_in6 *current_addr = (struct sockaddr_in6 *) ifa->ifa_addr;
If the address is set on the right interface, it is checked to see if it is a link-local address or not. If yes, it is returned.
        memcpy(ip, current_addr, sizeof(*current_addr));
        ret = 0;
        goto end;
    }
end:
    freeifaddrs(ifaddr);
    return ret;
}
This is the code needed to retrieve the link-local address on an interface.

My first post!

Well, this is my first post here. This is supposed to be a blog with helpful hints I discover during my work in C/C++. Whenever I find something interesting, or something I figured out after a long search, I will post here, so it will be easy to find and (hopefully) well explained.