如何使用 C 程序获取机器的 MAC 地址?

我正在开发 Ubuntu。如何使用 C 程序得到我的机器或接口的 MAC 地址,比如 eth0。

167558 次浏览
  1. On Linux, use the service of "Network Manager" over the DBus.

  2. There is also good'ol shell program which can be invoke and the result grabbed (use an exec function under C):

$ /sbin/ifconfig | grep HWaddr

You want to take a look at the getifaddrs(3) manual page. There is an example in C in the manpage itself that you can use. You want to get the address with the type AF_LINK.

You need to iterate over all the available interfaces on your machine, and use ioctl with SIOCGIFHWADDR flag to get the mac address. The mac address will be obtained as a 6-octet binary array. You also want to skip the loopback interface.

#include <sys/ioctl.h>
#include <net/if.h>
#include <unistd.h>
#include <netinet/in.h>
#include <string.h>


int main()
{
struct ifreq ifr;
struct ifconf ifc;
char buf[1024];
int success = 0;


int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (sock == -1) { /* handle error*/ };


ifc.ifc_len = sizeof(buf);
ifc.ifc_buf = buf;
if (ioctl(sock, SIOCGIFCONF, &ifc) == -1) { /* handle error */ }


struct ifreq* it = ifc.ifc_req;
const struct ifreq* const end = it + (ifc.ifc_len / sizeof(struct ifreq));


for (; it != end; ++it) {
strcpy(ifr.ifr_name, it->ifr_name);
if (ioctl(sock, SIOCGIFFLAGS, &ifr) == 0) {
if (! (ifr.ifr_flags & IFF_LOOPBACK)) { // don't count loopback
if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) {
success = 1;
break;
}
}
}
else { /* handle error */ }
}


unsigned char mac_address[6];


if (success) memcpy(mac_address, ifr.ifr_hwaddr.sa_data, 6);
}

A very portable way is to parse the output of this command.

ifconfig | awk '$0 ~ /HWaddr/ { print $5 }'

Provided ifconfig can be run as the current user (usually can) and awk is installed (it often is). This will give you the mac address of the machine.

#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/if.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>


int main()
{
struct ifreq s;
int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);


strcpy(s.ifr_name, "eth0");
if (0 == ioctl(fd, SIOCGIFHWADDR, &s)) {
int i;
for (i = 0; i < 6; ++i)
printf(" %02x", (unsigned char) s.ifr_addr.sa_data[i]);
puts("\n");
return 0;
}
return 1;
}

Much nicer than all this socket or shell madness is simply using sysfs for this:

the file /sys/class/net/eth0/address carries your mac adress as simple string you can read with fopen()/fscanf()/fclose(). Nothing easier than that.

And if you want to support other network interfaces than eth0 (and you probably want), then simply use opendir()/readdir()/closedir() on /sys/class/net/.

I have just write one and test it on gentoo in virtualbox.

// get_mac.c
#include <stdio.h>    //printf
#include <string.h>   //strncpy
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>   //ifreq
#include <unistd.h>   //close


int main()
{
int fd;
struct ifreq ifr;
char *iface = "enp0s3";
unsigned char *mac = NULL;


memset(&ifr, 0, sizeof(ifr));


fd = socket(AF_INET, SOCK_DGRAM, 0);


ifr.ifr_addr.sa_family = AF_INET;
strncpy(ifr.ifr_name , iface , IFNAMSIZ-1);


if (0 == ioctl(fd, SIOCGIFHWADDR, &ifr)) {
mac = (unsigned char *)ifr.ifr_hwaddr.sa_data;


//display mac address
printf("Mac : %.2X:%.2X:%.2X:%.2X:%.2X:%.2X\n" , mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}


close(fd);


return 0;
}

Using getifaddrs you can get MAC address from the family AF_PACKET.

In order to display the MAC address to each interface, you can proceed like this:

#include <stdio.h>
#include <ifaddrs.h>
#include <netpacket/packet.h>


int main (int argc, const char * argv[])
{
struct ifaddrs *ifaddr=NULL;
struct ifaddrs *ifa = NULL;
int i = 0;


if (getifaddrs(&ifaddr) == -1)
{
perror("getifaddrs");
}
else
{
for ( ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next)
{
if ( (ifa->ifa_addr) && (ifa->ifa_addr->sa_family == AF_PACKET) )
{
struct sockaddr_ll *s = (struct sockaddr_ll*)ifa->ifa_addr;
printf("%-8s ", ifa->ifa_name);
for (i=0; i <s->sll_halen; i++)
{
printf("%02x%c", (s->sll_addr[i]), (i+1!=s->sll_halen)?':':'\n');
}
}
}
freeifaddrs(ifaddr);
}
return 0;
}

Ideone

Expanding on the answer given by @user175104 ...

std::vector<std::string> GetAllFiles(const std::string& folder, bool recursive = false)
{
// uses opendir, readdir, and struct dirent.
// left as an exercise to the reader, as it isn't the point of this OP and answer.
}


bool ReadFileContents(const std::string& folder, const std::string& fname, std::string& contents)
{
// uses ifstream to read entire contents
// left as an exercise to the reader, as it isn't the point of this OP and answer.
}


std::vector<std::string> GetAllMacAddresses()
{
std::vector<std::string> macs;
std::string address;


// from: https://stackoverflow.com/questions/9034575/c-c-linux-mac-address-of-all-interfaces
//  ... just read /sys/class/net/eth0/address


// NOTE: there may be more than one: /sys/class/net/*/address
//  (1) so walk /sys/class/net/* to find the names to read the address of.


std::vector<std::string> nets = GetAllFiles("/sys/class/net/", false);
for (auto it = nets.begin(); it != nets.end(); ++it)
{
// we don't care about the local loopback interface
if (0 == strcmp((*it).substr(-3).c_str(), "/lo"))
continue;
address.clear();
if (ReadFileContents(*it, "address", address))
{
if (!address.empty())
{
macs.push_back(address);
}
}
}
return macs;
}

Assuming that c++ code (c++11) is okay as well and the interface is known.

#include <cstdint>
#include <fstream>
#include <streambuf>
#include <regex>


using namespace std;


uint64_t getIFMAC(const string &ifname) {
ifstream iface("/sys/class/net/" + ifname + "/address");
string str((istreambuf_iterator<char>(iface)), istreambuf_iterator<char>());
if (str.length() > 0) {
string hex = regex_replace(str, std::regex(":"), "");
return stoull(hex, 0, 16);
} else {
return 0;
}
}
int main()
{
string iface = "eth0";
printf("%s: mac=%016llX\n", iface.c_str(), getIFMAC(iface));
}

This is a Bash line that prints all available mac addresses, except the loopback:

for x in `ls /sys/class/net |grep -v lo`; do cat /sys/class/net/$x/address; done

Can be executed from a C program.

netlink socket is possible

man netlink(7) netlink(3) rtnetlink(7) rtnetlink(3)

#include <assert.h>
#include <stdio.h>
#include <linux/if.h>
#include <linux/rtnetlink.h>
#include <unistd.h>


#define SZ 8192


int main(){


// Send
typedef struct {
struct nlmsghdr nh;
struct ifinfomsg ifi;
} Req_getlink;
assert(NLMSG_LENGTH(sizeof(struct ifinfomsg))==sizeof(Req_getlink));
int fd=-1;
fd=socket(AF_NETLINK,SOCK_RAW,NETLINK_ROUTE);
assert(0==bind(fd,(struct sockaddr*)(&(struct sockaddr_nl){
.nl_family=AF_NETLINK,
.nl_pad=0,
.nl_pid=getpid(),
.nl_groups=0
}),sizeof(struct sockaddr_nl)));
assert(sizeof(Req_getlink)==send(fd,&(Req_getlink){
.nh={
.nlmsg_len=NLMSG_LENGTH(sizeof(struct ifinfomsg)),
.nlmsg_type=RTM_GETLINK,
.nlmsg_flags=NLM_F_REQUEST|NLM_F_ROOT,
.nlmsg_seq=0,
.nlmsg_pid=0
},
.ifi={
.ifi_family=AF_UNSPEC,
// .ifi_family=AF_INET,
.ifi_type=0,
.ifi_index=0,
.ifi_flags=0,
.ifi_change=0,
}
},sizeof(Req_getlink),0));


// Receive
char recvbuf[SZ]={};
int len=0;
for(char *p=recvbuf;;){
const int seglen=recv(fd,p,sizeof(recvbuf)-len,0);
assert(seglen>=1);
len += seglen;
if(((struct nlmsghdr*)p)->nlmsg_type==NLMSG_DONE||((struct nlmsghdr*)p)->nlmsg_type==NLMSG_ERROR)
break;
p += seglen;
}


struct nlmsghdr *nh=(struct nlmsghdr*)recvbuf;
for(;NLMSG_OK(nh,len);nh=NLMSG_NEXT(nh,len)){
if(nh->nlmsg_type==NLMSG_DONE)
break;


struct ifinfomsg *ifm=(struct ifinfomsg*)NLMSG_DATA(nh);


printf("#%d ",ifm->ifi_index);
#ifdef _NET_IF_H
#pragma GCC error "include <linux/if.h> instead of <net/if.h>"
#endif


// Part 3 rtattr
struct rtattr *rta=IFLA_RTA(ifm); // /usr/include/linux/if_link.h
int rtl=RTM_PAYLOAD(nh);


for(;RTA_OK(rta,rtl);rta=RTA_NEXT(rta,rtl))switch(rta->rta_type){
case IFLA_IFNAME:printf("%s ",(const char*)RTA_DATA(rta));break;
case IFLA_ADDRESS:
printf("hwaddr ");
for(int i=0;i<5;++i)
printf("%02X:",*((unsigned char*)RTA_DATA(rta)+i));
printf("%02X ",*((unsigned char*)RTA_DATA(rta)+5));
break;
case IFLA_BROADCAST:
printf("bcast ");
for(int i=0;i<5;++i)
printf("%02X:",*((unsigned char*)RTA_DATA(rta)+i));
printf("%02X ",*((unsigned char*)RTA_DATA(rta)+5));
break;
case IFLA_PERM_ADDRESS:
printf("perm ");
for(int i=0;i<5;++i)
printf("%02X:",*((unsigned char*)RTA_DATA(rta)+i));
printf("%02X ",*((unsigned char*)RTA_DATA(rta)+5));
break;
}


printf("\n");


}


close(fd);
fd=-1;
return 0;


}


Example

#1 lo hwaddr 00:00:00:00:00:00 bcast 00:00:00:00:00:00
#2 eth0 hwaddr 57:da:52:45:5b:1a bcast ff:ff:ff:ff:ff:ff perm 57:da:52:45:5b:1a
#3 wlan0 hwaddr 3c:7f:46:47:58:c2 bcast ff:ff:ff:ff:ff:ff perm 3c:7f:46:47:58:c2