Getting Interface Lists- ifaddrlist

Hey *myself*,
I slept at 7 am today, and woke up at 4 pm. really long, and wanted to get up by 12 actually. Oh well, things don’t always work out the way we planned. Kinda like my internet as well. It aint working. I’m writing this on wordpad (I don’t like microsoft office for these things).
Today, I thought we could discuss about libnet_ifaddrlist today. But that requires some real hardware structuring using linux. It requires quite some background. While I ain’t going to provide a complete i/o communications manual, I’ll try to explain you what happens in some things-

Introduction to the pre-requisites-
Approach: We can’t directly pinpoint the hardware, right? Unless we somehow could, we don’t (I dunno how to anyways). So, here’s what we do: The socket function sets up a connection using underlying devices, for a nice little connection. This connection has different parameters. We hence, back-track: getting the socket, then reading its attributes to get the lower level devices.
socket() function: It accepts 3 arguments: The first is the family. Its usually set to AF_INET for internet sockets (which we use for even ethernet networks) although there are abt 10 other standards as well. The 2nd is the type of connection: Using streams or datagrams. The 3rd is the type: We use IPPROTO_RAW (0) for setting a raw IP packet (note: IP headers already included, TCP headers arent). Others are IPPROTO_TCP and IPPROTO_UDP.
The most important thing about this however, is that it returns a file descriptor to the connection. This file is a special file (since its a network connection).
ioctl() function: The ioctl function manipulates or reports the underlying device parameters of special files. For our purpose, we have the protocol (its actually variable argumnets in general tho): int ioctl(int [FILE DESCRIPTOR],int [REQUEST],[SOME WAY TO GET BACK or PROVIDE INFORMATION]); The last parameter depends on the [REQUEST].
To get a nice little list of all possible [REQUEST] (for network interfaces), read: /include/bits/ioctls.h . I’ll describe only what we will use now though (since thats what I know)-
SIOCGIFADDR: This gets the network interface list. It accepts an argument of struct ifconf (described later). the ifc_len of the ifconf structure object should be set to the maximum size of the return. If its 0, then when ioctl is called, ifc_len would contain the length. That could be used to set an appropriate buffer. If its not 0, then the ifc_buf (and ifcu_req array, since they are part of union) is filled with the interface list: It is an array of ifreq structures, cast into a character array (or ifcu_req, in which case, its not cast :P ).
SIOCHIFFLAGS: Used to get the flags, or, the status of the device. It returns the short int ifr_flags. It accepts a ifreq structure pointer (yes, not ifconf), and returns the result in it. Note that the ifreq structure passed SHOULD contain the name of the device to be queried for. It could be written over, so its better to copy the device name, and especially the other parameters (like the address) before the ioctl call.
SIOCGIFADDR: Used to get the “PA” address. Not sure whats PA address, probably the logical IP address. It gets the result in the ifr_addr sockaddr. It accepts a ifreq structure pointer (yes, not ifconf), and returns the result in it. Note that the ifreq structure passed SHOULD contain the name of the device to be queried for. It could be written over, so its better to copy the device name, and especially the other parameters (like the address) before the ioctl call.

IFF_ flags: Don’t ask me what they are called, I think they are called if flags as well. See, ifreq structure contains an int for flags. These flags are used to test that integer (and hence the device status). IFF flags are employed by using the AND bitwise (if that bit in integer is set) and concatenated with OR (if multiple bits are set.). You might guess that flags probably contain only 1 true bit, and the location of that 1, determines the flag. Thats my guess. For a list of flags, see /linux/if.h (which I found after quite some searching)
About SA_LEN: Some o/s’ , I think openBSD old versions are 1 of them, include a sa_len parameter in sockaddr definition. My version of OpenSuSE doesnt, but this is a short explaination to what I understood- Sometimes, on these systems, the sockaddr size could run OVER the expected size of sa_data (which is 14 bytes). In this case, the sa_len holds the length of the sockaddr array. However, if sa_len is smaller than sizeof(sockaddr), then nothing happens, cause sizeof(sockaddr) is like a minimum size of sockaddr. sa_len is kinda like the size, if sa_len > sizeof(sockaddr). Atleast, thats what I understood. Perhaps, to keep this code compatibility, the ifc_buf is used, instead of directly reading the req.

Pre-requisite’ed code-
The following is some code which I consider reading pre-requisite, before jumping into the function.
1. structure libnet_ifaddrlist
struct libnet_ifaddr_list
{
u_long addr; //The address of device (probably not physical, may be logical. Physical would need 6 bytes)
char *device; //The device name, which is usually referenced.
};

Defined in libnet directory->includes/libnet/libnet-structures.h

2. structure ifreq (careful, big)-
struct ifreq{
#define IFHWADDRLEN 6
//hwaddrlen must probably be the number of bytes for MAC address. 6 bytes is the no. of
//bytes taken by ethernet mac address.
#define IFNAMESIZ IF_NAMESIZE
union{
char ifrn_name[IFNAMESIZ];
} ifr_ifrn;
union{
struct sockaddr ifru_addr;
struct sockaddr ifru_dstaddr
struct sockaddr ifru_broadaddr
struct sockaddr ifru_netmask;
struct sockaddr ifru_hwaddr;
short int ifru_flags;
int ifru_ivalue;
int ifru_mtu;
struct ifmap ifru_map;
char ifru_slave[IFNAMSIZ];
char ifru_newname[IFNAMESIZ];
__caddr_t ifru_data
} ifr_ifru
};
#define ifr_name ifr_ifrn.ifrn_name /*Interface name (device name)*/
#define ifr_hwaddr ifr_ifru_hwaddr /*Mac address*/
#define ifr_addr ifr_ifru.ifru_addr /*address??? , perhaps the higher address. */
#define ifr_dstaddr ifr_ifru.ifru_dstaddr /*Other end of the p-p link (would be null, in out case, right?)*/
#define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */
#define ifr_netmask ifr_ifru.ifru_netmask /*Interface net mask*/
#define ifr_flags ifr_ifru_flags /*Flags*/

… Some other stuff. not important in our current discussion atleast.
The ifreq structure is defined in /net/if.h as well as /linux/if.h , altho in /linux/ there is an additional param in ifr_ifru union: if_settings structure object ifru_settings . The ifreq is used to get the information of a particular interface.

3. definition of structure ifconf
struct ifconf{
int ifc_len;
union{
char __user *ifcu_buf
struct ifreq __user *ifcu_req;
} ifc_ifcu;
};
#define ifc_buf ifc_ifcu.ifcu_buf /*Buffer address*/
#define ifc_req ifc_ifcu.ifcu_req /*array of structures*/
This structure is defined in /linux/if.h (yup, da same). The ifconf is used to get the interface list.

4. IFF flags: God damn difficult to trace where they were. But anyways, here is a list (of commonly used ones). (I’ve described using them earlier)-
IFF_UP – If the interface is up
IFF_LOOPBACK – If the interface is a loopback net
IFF_POINTOPOINT – If device has point-point link. Probably used when the device is already on a virtual circuit/streaming a connection
IFF_RUNNING – The device works, as in, it can successfully send and recieve packets.

Code Dissection:
Read the original code of libnet_ifaddrlist from libnet_if_addr list found in libnet directory->/src/ subdirectory. I’ll explain the function part by part. Keep up (btw, this function is skipped during compilation on solaris, since there, the default network device is le0. Its valid only for BSD and linux)-
int
libnet_ifaddrlist(register struct libnet_ifaddr_list **ipaddrp,
register char *errbuf)
{
The definition: The function takes the libnet_ifaddr_list array pointer, and modifies it with the return. errbuf char string is used in case of erros. It returns the number of network interfaces found.

register int fd, nipaddr;
#ifdef HAVE_SOCKADDR_SA_LEN
register int n;
#endif
register struct ifreq *ifrp, *ifend, *ifnext, *mp;
register struct sockaddr_in *sin;
register struct libnet_ifaddr_list *al;

struct ifconf ifc;
struct ifreq ibuf[MAX_IPADDR], ifr;
char device[sizeof(ifr.ifr_name) + 1];
static struct libnet_ifaddr_list ifaddrlist[MAX_IPADDR];
The fd is file descriptor for return value of socket(). nipaddr is to store number of interfaces found. ifrp, ifend,ifnext are used for traversing the interface list. ifrp is the current interface in question, ifrp is next stores the next address for the loop, and ifend is the end of the array. mp, well, I dont know why its there, cause I don’t find it used anywhere. The sockaddr_in *sin is that internet socket structure used for getting the logical address of device (the IP address as opposed to the raw logical address). The al is where all the interfaces will be sorted into at the end. The argument **ipaddrp is actually like a dummy. ipaddrp (the double pointer) is assigned to *al (array) (which coincidently also holds ifaddrlist). The ifc structure is used to get the interface listing (raw listing) and is passed via the ioctl. The ibuf is kinda like an alias to the buffer @ ifc. The device character string is used to store the logical device name. ifaddrlist, as mentioned, is used in conjunction to al, cause ifaddrlist is @ the beginning of the array, al keeps traversing the array (I know nothing made sense now, but reading this description over again later on will help). MAX_IPADDR is prob some macro for some arbit max size (didnt bother finding out where)

fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
{
sprintf(errbuf, "socket: %s", strerror(errno));
return (-1);
}
ifc.ifc_len = sizeof(ibuf);
ifc.ifc_buf = (caddr_t)ibuf;

The first line, gets the file descriptor. A simple socket() function performs a great deal, including setting up a nice little connection point, and a file descriptor to it. The next few lines are error check if socket couldnt be initialised. ifc.ifc_len is set to the maximum length of the interface listing that should be returned. Thats required as a part of the SIOCGIFCONF request. The last line, well, it sets the ifc_buf pointer to the same memory as ibuf. So ibuf could access the same memory location as that buffer returned from ioctl call. Kinda like an alias.

if (ioctl(fd,SIOCGIFCONF,(char *)&ifc) < 0 || ifc.ifc_len < sizeof(struct ifreq))
{
sprintf(errbuf, "SIOCGIFCONF: %s", strerror(errno));
close(fd);
return (-1);
}
ifrp = ibuf;
ifend = (struct ifreq *)((char *)ibuf + ifc.ifc_len);

ioctl function call is used to get the devices acting below the special file descriptor. The fd is the FD passed to the ioctl function. The fd is over the network device, and hence becomes ideal for this. SIOCGIFCONF is used to get the list of interfaces. It takes a pointer to the ifconf object. In our case, thats ifc. If ioctl turns out 0 (or not -1), then success. If it returns -1, then you got problems. Maybe the method is not supported on that hardware, or something. The ioctl hence, comes in the if statement. The other part, it part after or, is confirmation that what is returned is atleast not junk. If ifc_len is less than even 1 ifreq structure, that means 1/2 a device is returned? lol, ofcourse not, its prob junk. So again, some error happened.
Now the next part (after if error checking)- ifrp is the current ifreq structure under interrogation, which is set to the beginning of the list. (remember, ibuf and ifc.ifc_buf refer to the same location, but call it by different types: 1 by char, other by ifreq struct, wherein the ifreq struct is the right interpretation). The ifend refers to the memory location immediately AFTER the interfaces list. ifc_len is the length of bytes of list, and ibuf is the start. Ofcourse, what could be easier could be-: ifend=ibuf+(ifc.ifc_len)/sizeof(struct ifreq); or ifend=(struct ifreq*)(ifc.ifc_buf+ifc.ifc_len);

al = ifaddrlist;
mp = NULL;
nipaddr = 0;

Just initialising. al is set to the beginning of ifaddrlist array (dunno why u need a pointer for tranversing the array, when we could use the index, but hell). mp, god knows why its there. nipaddr is the number of interfaces.

for (; ifrp < ifend; ifrp = ifnext)
{
#ifdef HAVE_SOCKADDR_SA_LEN
n = ifrp->ifr_addr.sa_len + sizeof(ifrp->ifr_name);
if (n < sizeof(*ifrp))
{
ifnext = ifrp + 1;
}
else
{
ifnext = (struct ifreq *)((char *)ifrp + n);
}
if (ifrp->ifr_addr.sa_family != AF_INET) continue;

Here, the loop starts for traversing each address. ifrp already refers to the beginning. ifnext is known in the loop, and ifrp is set to it, kinda like an increment. So, this just goes through each of the network interfaces.
The rest of it is if sa_len member is present in the sockaddr structure, which normally isnt (isnt for me). Skip. The n will get the the number of bytes supposed to be the sizeof the memory of the interface. Since ifr_addr is part of the union, and is the biggest of the structures (sockaddr). There are only 2 memory location units in ifreq: The union (whose max size is sizeof sockaddr) and the name buffer. Both are accounted for. But the sa_len extends the memory of the sockaddr. If sa_len>sizeof(sockaddr), then ifnext would be set to the next valid location, which goes over the expected size. Else, ifnext is set to the next immediate location.

#else
ifnext = ifrp + 1;
#endif

dude, you must know this. Its the regular increment :P . Thats what normally happens. Infact, if sa_len was not declared, the loop would be easier, much easier.

/*
* Need a template to preserve address info that is
* used below to locate the next entry. (Otherwise,
* SIOCGIFFLAGS stomps over it because the requests
* are returned in a union.)
*/
strncpy(ifr.ifr_name, ifrp->ifr_name, sizeof(ifr.ifr_name));
if (ioctl(fd, SIOCGIFFLAGS, (char *)&ifr) < 0)
{
if (errno == ENXIO) continue;
sprintf(errbuf,
"SIOCGIFFLAGS: %.*s: %s",
(int)sizeof(ifr.ifr_name),
ifr.ifr_name,
strerror(errno));
close(fd);
return (-1);
}

Read the comment if u want. I didnt understand a shit out of it. The strncpy, um…. strange as it is, cause ifr and ifrp are part of the same ifreq struct, and its like copying the entire string. Nyways, strncpy is always a better practice (buffer overflow prevention). As for why the copying, perhaps to prevent the ifr_name from getting overwritten. ioctl could become notorious for losing data. It takes in data and outputs it in the same argument. So its always better to copy all the stuff iin ifreq before passing it to ioctl. (or in this case, just copy the device name to another struct instead of passing the original)
The next call is to get the status of the particular device. For that, again ioctl, but this time, with the SIOCGIFFLAGS request, and the corresponding ifr pointer. If its not successful, an error message is set, using global errno (as in normal socket operations). If errno== ENXIO , which means, that the device is offline/inaccessible, then its ok to continue the loop, cause multiple devices may be offline. But if its some other error, then it must be reported and function failed (like, microprocessor starts acting funny by passing interrupt to keyboard or sumthing instead :P )

/* Must be up and not the loopback */
if ((ifr.ifr_flags & IFF_UP) == 0 || LIBNET_ISLOOPBACK(&ifr))
{
continue;
}

This checks up the correct flags to make sure that the device is valid. Though online, the device might not be up. This is possible if the device is powered and connected to comp, but not to the network. The first part of the if expression checks if the device is up (common sense tells us how flags work, esp. bit flags. So, if they return 0, the flag was not set, and hence, comparison failed). The second is actually a macro, in ->/include/libnet/libnet-macro.h .
The first expression checks if device is NOT up. The second checks if device IS loopback. LIBNET_ISLOOKBACK is equivalent to: (ifr.ifr_flags & IFF_LOOPBACK) != 0 . If the device is not up or loopback proceed with next device, with next iteration.

strncpy(device, ifr.ifr_name, sizeof(ifr.ifr_name));
device[sizeof(device) - 1] = '';
if (ioctl(fd, SIOCGIFADDR, (char *)&ifr) < 0)
{
sprintf(errbuf, "SIOCGIFADDR: %s: %s", device, strerror(errno));
close(fd);
return (-1);
}

This resembles the earlier ioctl call alot. The last 1 got the flags, this 1 gets the the address :) . This 1 calls up the SIOCCGIFADDR request with ioctl. But first, just saves the device name before the ioctl call, in device string (lol, atlast, a normal character string). Then, we query ioctl, and do the bogus error checking the same way. Now, the ifr.ifr_addr is filled with the “PA” address of the device, probably the joint IP address+port+family or sumthing. Casting it to sockaddr_in , we should be able to get the ip address!

sin = (struct sockaddr_in *)&ifr.ifr_addr;
al->addr = sin->sin_addr.s_addr;
/*
* Replaced savestr() with strdup(). -- MDS
*/
al->device = strdup(device);
++al;
++nipaddr;
}
close(fd);

And as I said earlier, it was cast to sockaddr_in for internet sockets. sin_addr is a part of in_addr, which is a structure containing only 1 element: s_addr, which is a 32 bit int (4 bytes) ) (numeric ip address. To get the presentation Ip address, use inet_ntop(AF_INET,&(sin->sin_addr.s_addr),dest,sizeof(dest)) where dest is a character string of 12+1 characters.
Then the rest is simple. Just add that device to the ifaddrlist array (through the al pointer). (strdup just copies the string and returns a pointer to the copied) Also, update the number of records, by nipaddr increment.

*ipaddrp = ifaddrlist;
return (nipaddr);
}
#endif /* !__solaris__ */

That just repoints the passed argument to the interface list, which now contains a nice little list of device name and associated ip address. Also, return the number of records, from nipaddr. Finally, endif the solaris, cause this full function shouldnt be compiled under solaris.

Usage:
Lets use this function we learnt to output the interface device names.
#include "libnethead.h"
#define MAX_IPADDR 15
//Let the max number of interfaces be 15. Put something arbit big.
struct libnet_ifaddr_list{
long addr;
char *device;
};
int main(){
struct libnet_ifaddr_list *address_list;
char errbuf[100];
int i;
int no;
no=libnet_ifaddrlist(&address_list,errbuf);
if(no==-1){ printf("Error: %s",errbuf); exit(1); }
for(i=0;i<no;i++){
printf("Device: %s ,\t Address: %d\n",(address_list[i]).device,(address_list[i]).addr);
}
return 0;
}
//Below is more or less copy-paste, with a bit of editing to make it independent of libnet constants
int libnet_ifaddrlist(register struct libnet_ifaddr_list **ipaddrp,register char *errbuf){
register int fd, nipaddr;
register struct ifreq *ifrp, *ifend, *ifnext, *mp;
register struct sockaddr_in *sin;
register struct libnet_ifaddr_list *al;
struct ifconf ifc;
struct ifreq ibuf[MAX_IPADDR], ifr;
char device[sizeof(ifr.ifr_name) + 1];
static struct libnet_ifaddr_list ifaddrlist[MAX_IPADDR];
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
{
sprintf(errbuf, "socket: %s", strerror(errno));
return (-1);
}
ifc.ifc_len = sizeof(ibuf);
ifc.ifc_buf = (caddr_t)ibuf;
if (ioctl(fd,
SIOCGIFCONF,
(char *)&ifc) < 0 || ifc.ifc_len < sizeof(struct ifreq))
{
sprintf(errbuf, "SIOCGIFCONF: %s", strerror(errno));
close(fd);
return (-1);
}
ifrp = ibuf;
ifend = (struct ifreq *)((char *)ibuf + ifc.ifc_len);
al = ifaddrlist;
mp = NULL;
nipaddr = 0;
for (; ifrp < ifend; ifrp = ifnext)
{
ifnext = ifrp + 1;
strncpy(ifr.ifr_name, ifrp->ifr_name, sizeof(ifr.ifr_name));
if (ioctl(fd, SIOCGIFFLAGS, (char *)&ifr) < 0)
{
if (errno == ENXIO) continue;
sprintf(errbuf,
"SIOCGIFFLAGS: %.*s: %s",
(int)sizeof(ifr.ifr_name),
ifr.ifr_name,
strerror(errno));
close(fd);
return (-1);
}
/* Must be up and not the loopback */
if (((ifr.ifr_flags & IFF_UP) == 0) || ((ifr.ifr_flags & IFF_LOOPBACK)==1))
{
continue;
}
strncpy(device, ifr.ifr_name, sizeof(ifr.ifr_name));
device[sizeof(device) - 1] = '';
if (ioctl(fd, SIOCGIFADDR, (char *)&ifr) < 0)
{
sprintf(errbuf, "SIOCGIFADDR: %s: %s", device, strerror(errno));
close(fd);
return (-1);
}
sin = (struct sockaddr_in *)&ifr.ifr_addr;
al->addr = sin->sin_addr.s_addr;
al->device = strdup(device);
++al;
++nipaddr;
}
close(fd);
*ipaddrp = ifaddrlist;
return (nipaddr);
}
//EOF//
You would ofcourse need the file libnethead.h . Dont worry, it aint something mysterious, its just a holy bunch of includes we have. I know that most of them arent necessary for this program, but having a common template helps (also, I didnt bother figuring out which were needed, which werent :P ). Here is libnethead.h (just put it in the same directory as the file above).

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <ctype.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <linux/igmp.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <netdb.h>
#include <errno.h>
#include <stdarg.h>

Note that no additional stuff is required, other than this. Also, note that this file is not universal. It works with my build of OpenSuSE 10.2, on a IA32 platform. It may be a bit different on others. Modify/clip ur version of libnet.h (in libnet folder) to suite ur suite.
Compile the code, and voila! If you get nothing, don’t worry, you prob dont have any up (or nonloopback) network devices connected to ur computer at the moment. To check for loopback devices as well, remove that IFF_LOOPBACK thing.

Congratulations: You’ve communicated with “super” low level network jumble and got what you needed! Take a break. I know I need 1 :P .

Whats Next:
Perhaps a movie (:D) . Seriously, if your anything like me, u’d wanna watch 1 now, cause this stuff was heavy but damn important. I’m glad its out of the way, because these sort of things are perhaps the most undocumented things available. Once we are able to setup and transmit a ethernet line, we’re made, cause then is left all that packet creation crap, stuff for which the rfc’s are golden source’s of info.
As for whats next on the blog (whenever I post this, after that): Probably the function: libnet_get_hwaddr for getting the ethernet address (MAC address) . I expect it to be real small though, resembling ioctl to get the device hwaddr in that ifreq structure. What will be interesting is actually getting that and parsing it to make it human readable.

Update on internet (rofl, personal): Well, talked to you telecom ppl. Shitty stuff, they blocked the internet for sum ugly overusgae or sumthing. No warning, and way before due date. I couldnt hear them properly on phone, so heck, didnt bother listening to pukeface talking on the other end of the line anyways. Lets see, I’ll try to upload this asap, perhaps from my fathers’ reliance connection.
Update: Well, its almost perfectly 24 hours since I wrote this article, and now I get internet and time to post it. Next is getting the interface’s mac address, damm simple.


About this entry