Tuesday, August 15, 2006

amazingly difficult to find 'getting a MAC address from a interface?' answer.

I was trying to convert a piece of network software to FreeBSD. Unfortunately, even though the author quotes
"should compile on any POSIX like OS," it doesn't (POSIX != LINUX). It should be obvious, when they drop things like:

if (ioctl (sock, SIOCGIFADDR, &ifr) < 0)

throughout the code.

So the above is from a section of code trying to obtain the MAC address on a interface. Doing this task
on FreeBSD can be done through ioctl(), but is discouraged by the developers. They encourage you to use getifaddrs().

This strange and wonderful function grabs all sorts of information from the interface list, however, a couple of things
are not obvious about it's use. If you really want to play arround with IF_LINK type addresses on the interfaces returned
you need to be using sockaddr_dl not just sockaddr. Overloading sockaddr with magic casting is the new pink these days and
thats what's required. It confused me at first since the manpage has:

struct ifaddrs *ifa_next; /* Pointer to next struct */
char *ifa_name; /* Interface name */
u_int ifa_flags; /* Interface flags */
struct sockaddr *ifa_addr; /* Interface address */
struct sockaddr *ifa_netmask; /* Interface netmask */
struct sockaddr *ifa_broadaddr; /* Interface broadcast address */
struct sockaddr *ifa_dstaddr; /* P2P interface destination */
void *ifa_data; /* Address specific data */

for the structure.. same with /usr/include/ifaddrs.h. It turns out copying sa_data of sa_len from your friendly
struct sockaddr does not give your the ethernet address from ifa_addr. :( :( :( :(.

Too make a long story short:

/* getmac.c -- retrieve the mac address from a interface name */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if_dl.h>
#include <ifaddrs.h>

int main(int argc, char ** argv)
struct ifaddrs *ifaphead;
unsigned char * if_mac;
int found = 0;
struct ifaddrs *ifap;
struct sockaddr_dl *sdl = NULL;

if (argc < 2)
fprintf(stderr,"\t %s <interface name>\n",argv[0]);

if (getifaddrs(&ifaphead) != 0)
perror("get_if_name: getifaddrs() failed");

for (ifap = ifaphead; ifap && !found; ifap = ifap->ifa_next)
if ((ifap->ifa_addr->sa_family == AF_LINK))
if (strlen(ifap->ifa_name) == strlen(argv[1]))
if (strcmp(ifap->ifa_name,argv[1]) == 0)
found = 1;
sdl = (struct sockaddr_dl *)ifap->ifa_addr;
if (sdl)
/* I was returning this from a function before converting
* this snippet, which is why I make a copy here on the heap */
if_mac = malloc(sdl->sdl_alen);
memcpy(if_mac, LLADDR(sdl), sdl->sdl_alen);
if (!found)
fprintf (stderr,"Can't find interface %s.\n",argv[1]);

fprintf (stdout, "%02X%02X%02X%02X%02X%02X\n",
if_mac[0] , if_mac[1] , if_mac[2] ,
if_mac[3] , if_mac[4] , if_mac[5] );




  1. I wrote an app to do this (and more) several years ago, and gave it to Seth recently. Here.

  2. Oh, and my older code is here.

  3. I've been looking for this all day! Brilliant! Should work for FreeBSD, OpenBSD, and NetBSD. What about Linux and Solaris? I need to find out how to do it on these other platforms too, since I'm betting getifaddrs() is BSD specific.