LinuxQuestions.org
Download your favorite Linux distribution at LQ ISO.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 05-11-2009, 09:09 PM   #1
MrUmunhum
Member
 
Registered: May 2006
Location: Mt Umunhum, CA, USA, Earth
Distribution: Debian/ Fedora/ Ubuntu/ Raspbian
Posts: 549

Rep: Reputation: 40
finding mac address for default route?


Hi group,
I would like, from a C program, find the hardware MAC address of the default route path. With BASH I can do a 'route' find 'default and then an ifconfig and grep for 'HWaddr'. Are there C calls to do the same??
 
Old 05-11-2009, 09:20 PM   #2
MrUmunhum
Member
 
Registered: May 2006
Location: Mt Umunhum, CA, USA, Earth
Distribution: Debian/ Fedora/ Ubuntu/ Raspbian
Posts: 549

Original Poster
Rep: Reputation: 40
OK, I can get the default from /proc/net/route.
Now how do I get the MAC address??
 
Old 05-11-2009, 10:30 PM   #3
jim80net
LQ Newbie
 
Registered: May 2009
Location: San Antonio, TX
Distribution: Debian
Posts: 15

Rep: Reputation: 1
You could arping it...
 
Old 05-12-2009, 07:30 AM   #4
wje_lq
Member
 
Registered: Sep 2007
Location: Mariposa
Distribution: FreeBSD,Debian wheezy
Posts: 811

Rep: Reputation: 179Reputation: 179
jim80net, he wanted C calls, remember?

MrUmunhum, try running the following bash script.
Code:
cat > 1.c <<EOD; gcc -Wall -Werror 1.c -o 1; ./1
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
  int          address_index;
  int          ioctl_result;
  int          local_socket;

  struct ifreq mac_data;

  local_socket=socket(AF_INET,
                      SOCK_DGRAM,
                      0
                     );

  if(local_socket==-1)
  {
    perror("socket()");

    exit(1);
  }

  strcpy(mac_data.ifr_name,
         "eth0"
        );

  ioctl_result=ioctl(local_socket,
                     SIOCGIFHWADDR,
                     (char *)&mac_data
                    );

  if(ioctl_result==-1)
  {
    perror("ioctl(SIOCGIFHWADDR)");

    exit(1);
  }

  for(address_index=0;
      /* no     address_index<sizeof(mac_data.ifr_hwaddr.sa_data); */
      /* yes */ address_index<6;
      address_index++
     )
  {
    if(address_index>0)
    {
      printf(":");
    }

    printf("%02x",
           (unsigned int)((mac_data.ifr_hwaddr.sa_data[address_index])&0xFF)
          );
  }

  printf("\n");

  return 0;
}
EOD
 
Old 05-12-2009, 11:29 AM   #5
MrUmunhum
Member
 
Registered: May 2006
Location: Mt Umunhum, CA, USA, Earth
Distribution: Debian/ Fedora/ Ubuntu/ Raspbian
Posts: 549

Original Poster
Rep: Reputation: 40
looks good

That is perfect!
Thanks.
 
Old 05-12-2009, 04:06 PM   #6
osor
HCL Maintainer
 
Registered: Jan 2006
Distribution: (H)LFS, Gentoo
Posts: 2,450

Rep: Reputation: 78
If you don’t want to use the special network interface socket ioctls, all the information you want is already available in the /proc or /sys virtual filesystem. You can read /proc/net/route to get the interface for the default route, and later read /sys/class/net/%s/address, where you put in the name of the interface you found earlier.
 
Old 05-12-2009, 04:32 PM   #7
wje_lq
Member
 
Registered: Sep 2007
Location: Mariposa
Distribution: FreeBSD,Debian wheezy
Posts: 811

Rep: Reputation: 179Reputation: 179
Quote:
Originally Posted by osor View Post
If you don’t want to use the special network interface socket ioctls, all the information you want is already available in the /proc or /sys virtual filesystem. You can read /proc/net/route to get the interface for the default route, and later read /sys/class/net/%s/address, where you put in the name of the interface you found earlier.
What you buy with the C code is portability.
 
Old 05-12-2009, 10:39 PM   #8
osor
HCL Maintainer
 
Registered: Jan 2006
Distribution: (H)LFS, Gentoo
Posts: 2,450

Rep: Reputation: 78
Quote:
Originally Posted by wje_lq View Post
What you buy with the C code is portability.
There really isn’t any portable way to do MAC address enumeration. In fact, ioctl() is not “pure C” but rather POSIX. Moreover the various socket ioctls are not standardized in their name or arguments (interface naming itself is not portable). In various unices, you might use SIOCGIFHWADDR, SIOCGIFMAC, SIOCRPHYSADDR, or something else entirely like the function getifaddrs().

I believe in linux, the preferred method is not to use the ioctl() at all (this has been somewhat deprecated), but to use a netlink socket.

I just presented my solution (which, btw happens to be implementable in “pure C” with fopen() and fread()) because it was in line with the OP’s original idea of getting the default route interface from a procfile.

Any real world code to find a MAC address portably will be riddled with #ifdefs.
 
Old 05-13-2009, 06:20 AM   #9
wje_lq
Member
 
Registered: Sep 2007
Location: Mariposa
Distribution: FreeBSD,Debian wheezy
Posts: 811

Rep: Reputation: 179Reputation: 179
My error on the portability issue, of course.

This raises a question, though.

I have a program which contains this statement:
Code:
  ioctl_result=ioctl(local_socket,
                     SIOCSIFHWADDR,
                     (char *)&mac_data
                    );
The program serves me well, but does anyone here suggest a better alternative to ioctl() with SIOCSIFHWADDR?
 
Old 05-15-2009, 05:00 PM   #10
osor
HCL Maintainer
 
Registered: Jan 2006
Distribution: (H)LFS, Gentoo
Posts: 2,450

Rep: Reputation: 78
Quote:
Originally Posted by wje_lq View Post
The program serves me well, but does anyone here suggest a better alternative to ioctl() with SIOCSIFHWADDR?
IMO ioctl() is fine for such uses (when you know the interface name, and only want to find its MAC address).

In other, more complicated cases, and alternative would be to use netlink sockets with the NETLINK_ROUTE protocol. Using netlink to get the MAC address when you already know the interface name is like killing a fly with a sledgehammer.

In other cases (perhaps one relevant to the OP), you are most likely doing more than one MAC address lookup if you have some network device code. Here, using netlink makes more sense, because instead of using disparate methods to get your information (e.g., reading from /proc/net/route and then using SIOCSIFHWADDR), you have a single, more robust method.

If I have time later I will post an example where it is better (or at least more elegant) to use netlink rather than socket ioctls.
 
Old 05-15-2009, 08:04 PM   #11
wje_lq
Member
 
Registered: Sep 2007
Location: Mariposa
Distribution: FreeBSD,Debian wheezy
Posts: 811

Rep: Reputation: 179Reputation: 179
Quote:
Originally Posted by osor View Post
Quote:
Originally Posted by wje_lq View Post
The program serves me well, but does anyone here suggest a better alternative to ioctl() with SIOCSIFHWADDR?
IMO ioctl() is fine for such uses (when you know the interface name, and only want to find its MAC address).
Well, um, no, that's not the thrust of my question. SIOCSIFHWADDR is used to set the MAC address, not to find it. :)
 
Old 05-18-2009, 05:21 PM   #12
osor
HCL Maintainer
 
Registered: Jan 2006
Distribution: (H)LFS, Gentoo
Posts: 2,450

Rep: Reputation: 78
Quote:
Originally Posted by wje_lq View Post
Well, um, no, that's not the thrust of my question. SIOCSIFHWADDR is used to set the MAC address, not to find it. :)
I misread the S as a G :D. In any case, the statement still stands. There are (basically) two methods of changing the MAC address: using the socket ioctl and using rtnetlink. In most situations, the second case is like using a sledgehammer to kill a fly (it works but is way more than needed).

So I would recommend to stick with SIOCSIFHWADDR, unless your program makes extensive use of low-level network maniuplation, which can be encompassed in rtnetlink calls.

Code to follow…
 
Old 05-18-2009, 06:01 PM   #13
osor
HCL Maintainer
 
Registered: Jan 2006
Distribution: (H)LFS, Gentoo
Posts: 2,450

Rep: Reputation: 78
Okay, so here is a task very similar to the OP: find the interface for the default route and assign to it a MAC address of your choice.

This is the classical (socket ioctl) way of doing it:
Code:
#define _GNU_SOURCE

#include <inttypes.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#define ARPHRD_ETHER 1

#define ROUTE "/proc/net/route"
#define BUF_SIZE 200
#define NEWMAC "\x00\x11\x22\x33\x44\x55"

int main()
{
	FILE *route;
	char buf[BUF_SIZE];
	uint32_t dest = -1;
	int sd;

	struct ifreq mac_data = {
		.ifr_hwaddr = {
			.sa_family = ARPHRD_ETHER,
			.sa_data = NEWMAC
		}
	};

	if(!(route = fopen(ROUTE, "r"))) {
		perror("fopen");
		exit(EXIT_FAILURE);
	}

	while(dest && fgets(buf, BUF_SIZE, route))
		sscanf(buf, "%s %8" SCNx32, mac_data.ifr_name, &dest);

	if(dest) {
		fprintf(stderr, "No default route.\n");
		exit(EXIT_FAILURE);
	}

	fclose(route);

	if((sd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
		perror("socket");
		exit(EXIT_FAILURE);
	}

	if(ioctl(sd, SIOCSIFHWADDR, &mac_data) == -1) {
		perror("ioctl");
		exit(EXIT_FAILURE);
	}

	close(sd);

	return 0;
}
This would be the netlink way of doing it:
Code:
#include <asm/types.h>
#include <inttypes.h>
#include <sys/user.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/if.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include <errno.h>

#define NEWMAC "\x00\x11\x22\x33\x44\x55"

int request_rtmsg(int sd, uint32_t pid)
{
	struct {struct nlmsghdr nlh; struct rtmsg rtm; } msg =
	{	.nlh = {
			.nlmsg_len = NLMSG_LENGTH(sizeof(msg.rtm)),
			.nlmsg_type = RTM_GETROUTE,
			.nlmsg_flags = NLM_F_REQUEST|NLM_F_MATCH,
			.nlmsg_pid = pid
		}, .rtm = { .rtm_family = AF_INET } 
	};

	return send(sd, &msg, msg.nlh.nlmsg_len, 0);
}

int get_oif(int sd)
{
	int len, oif = -2;
	ssize_t bytes;
	struct nlmsghdr *nlh;
	struct nlmsgerr *err;
	struct rtattr *rta;
	struct rtmsg *rtm;
	char buf[PAGE_SIZE];
	bool multi = true;

	while(multi && (bytes = recv(sd, buf, sizeof(buf), 0))) {
		if(bytes == -1) 
			return -1;

		nlh = (struct nlmsghdr *)buf;
		multi = nlh->nlmsg_flags & NLM_F_MULTI;
		
		if(nlh->nlmsg_type == NLMSG_DONE)
			break;

		if(nlh->nlmsg_type == NLMSG_ERROR) {
			err = NLMSG_DATA(nlh);
			errno = -err->error;
			return -1;
		}

		for(; NLMSG_OK(nlh, bytes); nlh = NLMSG_NEXT(nlh, bytes)) {
			rtm = NLMSG_DATA(nlh);

			if(rtm->rtm_family  != AF_INET ||
			   rtm->rtm_table   != RT_TABLE_MAIN ||
			   rtm->rtm_dst_len != 0 ||
			   rtm->rtm_src_len != 0 )
				continue;

			for(rta = RTM_RTA(rtm), len = RTM_PAYLOAD(nlh);
			    RTA_OK(rta,len); rta = RTA_NEXT(rta,len))
				if(rta->rta_type == RTA_OIF)
					oif = *(int *)RTA_DATA(rta);
		}
	}

	return oif;
}

int set_mac(int sd, uint32_t pid, int index, const char *restrict mac)
{
	char buf[PAGE_SIZE];
	struct nlmsghdr *nlh;
	struct nlmsgerr *err;
	struct {
		struct nlmsghdr nlh;
		struct ifinfomsg ifi;
		struct rtattr rta;
		char mac[IFHWADDRLEN];
	} msg = {	.nlh = {
			.nlmsg_len = NLMSG_LENGTH(sizeof(msg)),
			.nlmsg_type = RTM_SETLINK,
			.nlmsg_flags = NLM_F_REQUEST|NLM_F_ACK,
			.nlmsg_pid = pid,
			.nlmsg_seq = 1
		}, .ifi = {
			.ifi_index = index,
			.ifi_change = -1
		}, .rta = {
			.rta_type = IFLA_ADDRESS,
			.rta_len = RTA_LENGTH(sizeof(msg.mac))
		}
	};
	
	memcpy(msg.mac, mac, sizeof(msg.mac));
	
	if(send(sd, &msg, msg.nlh.nlmsg_len, 0) == -1)
		return -1;
	if(recv(sd, buf, sizeof(buf), 0)== -1)
		return -1;

	nlh = (struct nlmsghdr *)buf;

	if(nlh->nlmsg_type != NLMSG_ERROR) {
		errno = ENOMSG;
		return -1;
	}

	err = NLMSG_DATA(nlh);

	if(errno=-err->error)
		return -1;
	return 0;
}

int main()
{
	int sd, oif;
	struct sockaddr_nl nl = {.nl_family = AF_NETLINK, .nl_pid = getpid()};
	
	if((sd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) == -1) {
		perror("socket");
		exit(EXIT_FAILURE);
	}

	if(bind(sd, (struct sockaddr*)&nl, sizeof(nl)) == -1) {
		perror("bind");
		exit(EXIT_FAILURE);
	}
	
	if(request_rtmsg(sd, nl.nl_pid) == -1) {
		perror("request_rtmsg");
		exit(EXIT_FAILURE);
	}

	if((oif = get_oif(sd)) == -1) {
		perror("get_oif");
		exit(EXIT_FAILURE);
	}

	if(oif == -2) {
		fprintf(stderr, "No default route.\n");
		exit(EXIT_FAILURE);
	}

	if(set_mac(sd, nl.nl_pid, oif, NEWMAC) == -1) {
		perror("set_mac");
		exit(EXIT_FAILURE);
	}
	
	close(sd);
	
	return 0;
}
As you can see, using the socket ioctl is very short and clean. However, you must use an entirely separate type of operation to find the name of the default route interface (namely the reading and parsing the procfile).

The rtnetlink method is long and bulky (requiring separate functions to simplify the logic). It could possibly be refactored (to reduce some of the code duplication) but not by much. However, if your code makes more extensive use of the routing table or other rtnetlink interfaces, it might end up being more elegant than an equivalent reading/parsing or the procfile.

Bottom line, most of the time socket ioctls are the best choice, but it all depends on what else your program is supposed to do.

Last edited by osor; 05-19-2009 at 04:01 PM. Reason: removed 3 lines of unneccesary code
 
  


Reply

Tags
ioctl, rtnetlink



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
Finding out BMC's MAC address prakashpms Linux - Hardware 1 04-06-2011 08:11 AM
Finding Ip address with Mac address rupeshdwivedi Linux - Networking 6 09-01-2005 07:44 AM
Finding a MAC Address bigrig2004 Linux - Networking 1 01-10-2005 07:21 PM
finding my mac address sethgeekx86 Linux - General 5 07-14-2004 11:17 AM
finding people MAC address over localnet gregram Linux - Networking 1 08-18-2003 08:38 AM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 10:02 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration