如何使用C程序获取您计算机的MAC地址?

92
我正在使用Ubuntu系统。如何在C程序中获取我的计算机或某个接口(如eth0)的MAC地址?

请参阅相关问题:https://dev59.com/em445IYBdhLWcg3wRoLH - Keeely
12个回答

155

相较于所有的套接字或shell繁琐,更好的方法是使用sysfs来完成此操作:

位于/sys/class/net/eth0/address的文件作为一个简单字符串包含着您的MAC地址,只需使用fopen()/fscanf()/fclose()即可读取。这再简单不过了。

如果您想要支持除eth0以外的其他网络接口(您可能需要这么做),那么只需要对/sys/class/net/使用opendir()/readdir()/closedir()即可。


6
不错的答案,但并非适用于所有情况。例如嵌入式系统(特别是旧版本的busybox等较老的系统),这些系统没有sysfs或无法支持它,因为系统本身可能太老。 - Alex Marshall
3
问题明确要求使用C语言解决方案。 - Charles Salvia
13
@CharlesSalvia打开和读取系统文件仍然像是C语言的解决方案...只要你知道你的目标系统提供了什么!当然,你的程序将会绑定到那种类型的系统上。但是编译一个程序也会将其绑定到系统的架构上。我认为这个答案在许多情况下(但不是所有情况)都会有帮助。 - moodboom
(使用基于Debian的Bunsen在WiFi上)请参见:/sys/class/net/wlp2s0/address - AAAfarmclub
介绍fscanf用于使用可移植的C-API(例如getifaddrs)完成的事情,而不是真正的疯狂。这个解决方案既不可移植,也不安全,也不具备未来性。 https://docs.kernel.org/admin-guide/sysfs-rules.html - The19thFighter

73

您需要遍历计算机上所有可用的接口,并使用带有SIOCGIFHWADDR标志的ioctl来获取MAC地址。 MAC地址将作为一个6字节的二进制数组获得。 您还需要跳过回环接口。

#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);
}

1
为什么ifreq和ifconf前面不需要struct?这是因为在更近期的内核中是否删除了这些结构体的typedef,而这些结构体在2009年编写时是存在的? - Karl P
我认为需要注意的是,OpenVZ容器没有MAC地址,因此,这些解决方案都不会起作用。MAC地址被假定为“00:00:00:00:00:00”。 - SameOldNick
2
只有在eth0上有链接时才能正常工作。如果网络电缆未插入,则无法工作(在Ubuntu上)。 - Florin Petriuc
8
也许是 close(sock)? - Art Swri
@ArtSwri 我想你是对的,可能错误处理跳转到某个标签并关闭(sock)。 - J.Doe
显示剩余2条评论

28

您想查看getifaddrs(3)手册页。该页中有一个C语言的示例代码可以使用。您需要获取类型为AF_LINK的地址。


2
在 getifaddrs 的 manpage 中,未描述类型 AF_LINK。也许这已被 AF_PACKET 取代? - mpromonet
3
根据 https://dev59.com/E2w15IYBdhLWcg3wMpAY#26038501 的说法,AF_LINK 是 macOS/BSD 特有的,而 AF_PACKET 则是 Linux 系统的对应项。 - Manfred Urban

26
#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;
}

2
不要忘记对socket()进行错误检查,并在成功打开socket时关闭它。 - Anonymouse

20
使用getifaddrs函数,您可以从家族 AF_PACKET 中获取MAC地址。为了显示每个接口的MAC地址,您可以按如下方式进行:
#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


12

我刚刚在VirtualBox上安装了Gentoo并测试了一个程序。

// 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;
}

6
假设 C++ 代码(C++11)也可以,并且接口已知。
#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));
}

0
  1. 在Linux上,可以通过DBus使用“Network Manager”服务。

  2. 还有一个古老的shell程序,可以调用并获取结果(在C语言中使用exec函数):

$ /sbin/ifconfig | grep HWaddr


0
一种非常便携的方法是解析此命令的输出。
ifconfig | awk '$0 ~ /HWaddr/ { print $5 }'

如果当前用户有权限运行ifconfig(通常是可以的),并且已安装awk(通常也是这样),则可以提供此操作。这将为您提供机器的MAC地址。


4
它的可移植性非常差。在Mac OS X上没有输出结果。ifconfig命令的输出中不包含“HWaddr”文本。 - dreamlax
今年早些时候我不得不在Solaris、Linux和HP-UX上完成这个任务。 - hookenz
1
嗯,这个概念是可移植的。文本可能需要调整以适用于其他平台。 - hookenz
2
使用Shell脚本,即使像你这样微小的脚本也远非可移植的。有一个笑话说,一个Shell比一个Shell脚本更容易移植 :p - sigjuice
8
据我所知,这段代码在任何我所了解的C编译器中都无法通过编译。 - BobbyShaftoe
Debian Stretch 上没有 ifconfig 命令,完全不可移植。 - totten

0

在 @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://dev59.com/ll_Va4cB1Zd3GeqPWsIx
  //  ... 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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接