othermark ([info]othermark) wrote,
@ 2006-08-15 14:38:00
Previous Entry  Add to memories!  Tell a Friend!  Next Entry
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,"usage:\n");
        fprintf(stderr,"\t %s <interface name>\n",argv[0]);
        exit(1);
    }

    if (getifaddrs(&ifaphead) != 0)
    {
        perror("get_if_name: getifaddrs() failed");
        exit(1);
    }

    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]);
        if(ifaphead)
            freeifaddrs(ifaphead);
        exit(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] );

    if(ifaphead)
        freeifaddrs(ifaphead);
        
    exit(0);
}


(Post a new comment)

dude!
[info]node
2006-08-16 06:23 am UTC (link)
I wrote an app to do this (and more) several years ago, and gave it to Seth recently. Here.

(Reply to this)(Thread)

Re: dude!
[info]node
2006-08-16 06:25 am UTC (link)
Oh, and my older code is here.

(Reply to this)(Parent)

more examples.
[info]othermark
2006-08-16 02:16 pm UTC (link)
from here.

static void
getNodeID(uint8_t node[6])
{
    struct ifaddrs *ifap;

    if (getifaddrs(&ifap) == 0) {
        struct ifaddrs *p;
        for (p = ifap; p; p = p->ifa_next) {
            if (p->ifa_addr->sa_family == AF_LINK) {
                struct sockaddr_dl* sdp = (struct sockaddr_dl*) p->ifa_addr;
                memcpy(node, sdp->sdl_data + sdp->sdl_nlen, 6);
                freeifaddrs(ifap);
                return;
            }
        }
        freeifaddrs(ifap);
    }
}


(Reply to this)

more examples.
[info]othermark
2006-08-16 02:27 pm UTC (link)
and another example (using sysctl()) from here.
/*
 *      getmac.c
 *      
 *      Simple Demo:    Get MAC address of a specified NIC on FreeBSD
 *
 *      To compile: gcc getmac.c -o getmac
 *
 *      Tested on FreeBSD-4.6 RELEASE & FreeBSD-5.2-current
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
        int                     mib[6], len;
        char                    *buf;
        unsigned char           *ptr;
        struct if_msghdr        *ifm;
        struct sockaddr_dl      *sdl;

        if (argc != 2) {
                fprintf(stderr, "Usage: getmac <interface>\n");
                return 1;
        }

        mib[0] = CTL_NET;
        mib[1] = AF_ROUTE;
        mib[2] = 0;
        mib[3] = AF_LINK;
        mib[4] = NET_RT_IFLIST;
        if ((mib[5] = if_nametoindex(argv[1])) == 0) {
                perror("if_nametoindex error");
                exit(2);
        }

        if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) {
                perror("sysctl 1 error");
                exit(3);
        }

        if ((buf = malloc(len)) == NULL) {
                perror("malloc error");
                exit(4);
        }

        if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) {
                perror("sysctl 2 error");
                exit(5);
        }

        ifm = (struct if_msghdr *)buf;
        sdl = (struct sockaddr_dl *)(ifm + 1);
        ptr = (unsigned char *)LLADDR(sdl);
        printf("%02x:%02x:%02x:%02x:%02x:%02x\n", *ptr, *(ptr+1), *(ptr+2),
                        *(ptr+3), *(ptr+4), *(ptr+5));
        
        return 0;
}

(Reply to this)

Cross-platform solution? *BSD, Linux & Solaris?
(Anonymous)
2006-12-29 02:28 pm UTC (link)
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.

(Reply to this)


Create an Account
Forgot your login?
Login w/ OpenID
English • Español • Deutsch • Русский…