Linux系统

在Linux系统,可以通过系统调用函数ioctl很容易就获取到服务器的mac地址。

#include <net/if.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
 
 
int main()
{
        int sock, if_count, i;
        struct ifconf ifc;
        struct ifreq ifr[10];
        unsigned char mac[6];

        memset(&ifc, 0, sizeof(struct ifconf));

        sock = socket(AF_INET, SOCK_DGRAM, 0);

        ifc.ifc_len = 10 * sizeof(struct ifreq);
        ifc.ifc_buf = (char *)ifr;
        //获取所有网卡信息
        ioctl(sock, SIOCGIFCONF, (char *)&ifc);

        if_count = ifc.ifc_len / (sizeof(struct ifreq));
        for (i = 0; i < if_count; i++) {        
                if (ioctl(sock, SIOCGIFHWADDR, &ifr[i]) == 0) {  
                        memcpy(mac, ifr[i].ifr_hwaddr.sa_data, 6);
                        printf("eth: %s, mac: %02x:%02x:%02x:%02x:%02x:%02x\n", ifr[i].ifr_name, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
                } 
        }
        return 0;
}

核心逻辑主要分两个部分,第一个部分是获取网卡,主要通过下面的函数完成:

ioctl(sock, SIOCGIFCONF, (char *)&ifc);

它的信息保存在结构体struct ifconf中,有可能不止一个。获取到的信息保存在ifc_buf中。
第二个逻辑就是根据网卡的名字去获取mac地址,主要用下面的函数完成:

ioctl(sock, SIOCGIFHWADDR, &ifr[i]);

通过上面简单的两步,就能获取到Linux服务器上所有的网卡对应的mac地址。
当前操作系统信息:

[root@vm101108 src]# uname -a
Linux vm101108 3.10.0-1160.15.2.el7.x86_64 #1 SMP Wed Feb 3 15:06:38 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
[root@vm101108 src]# cat /etc/os-release 
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:7"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"

CENTOS_MANTISBT_PROJECT="CentOS-7"
CENTOS_MANTISBT_PROJECT_VERSION="7"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="7"

上面的程序运行结果:

[root@vm101108 src]# ./get_mac_addr
eth: lo, mac: 00:00:00:00:00:00
eth: em1, mac: b8:2a:72:dc:42:f2
eth: p5p2, mac: 90:e2:ba:89:46:bd
eth: docker0, mac: 02:42:1c:d3:f8:e8

查看本地网卡的mac地址:

[root@vm101108 src]# ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 fe80::42:1cff:fed3:f8e8  prefixlen 64  scopeid 0x20<link>
        ether 02:42:1c:d3:f8:e8  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 5  bytes 446 (446.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

em1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.102.108  netmask 255.255.255.0  broadcast 192.168.102.255
        inet6 fe80::9f88:a3a9:1748:56fc  prefixlen 64  scopeid 0x20<link>
        ether b8:2a:72:dc:42:f2  txqueuelen 1000  (Ethernet)
        RX packets 4420019  bytes 547543658 (522.1 MiB)
        RX errors 0  dropped 52  overruns 0  frame 0
        TX packets 6526650  bytes 6637157039 (6.1 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        device interrupt 55  

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 140886  bytes 12507428 (11.9 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 140886  bytes 12507428 (11.9 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        
p5p2: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.101.108  netmask 255.255.255.0  broadcast 192.168.101.255
        inet6 fe80::427c:e13c:50b6:d747  prefixlen 64  scopeid 0x20<link>
        ether 90:e2:ba:89:46:bd  txqueuelen 1000  (Ethernet)
        RX packets 57573979  bytes 50997944188 (47.4 GiB)
        RX errors 0  dropped 41  overruns 0  frame 0
        TX packets 1111442  bytes 673920374 (642.7 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

可见,取出来的mac地址是正确的。

AIX系统

AIX系统是power架构的,没有SIOCGIFHWADDR这个接口,因此,不能像Linux那样获取mac地址。
这里提供两种方法:

第一种方法

#include <stdio.h>
#include <stdint.h>
#include <errno.h>
#include <sys/ndd_var.h>
#include <sys/kinfo.h>
#include <net/if_dl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>             /* for ifconf */
#include <netinet/in.h>         /* for sockaddr_in */
#include <sys/ioctl.h>

static int aix_get_mac_addr(uint8_t mac[6])
{
        struct ifconf ifc;
        struct ifreq *ifr;
        char buf[640] = "";
        int sock = socket(AF_INET, SOCK_DGRAM, 0);
        ifc.ifc_len = 640;
        ifc.ifc_buf  = buf;
        if (sock < 0)
        {
                free(ifr);
                return -1;
        }
        ioctl(sock, CSIOCGIFCONF, &ifc);
        ifr = (struct ifreq *)buf;
        struct sockaddr_dl *sdl = (struct sockaddr_dl *)&ifr->ifr_addr;
        memcpy(mac, ((caddr_t)((sdl)->sdl_data + (sdl)->sdl_nlen)), 6);
        close(sock);
        free(ifr);
        return 0;
}

void print_mac(uint8_t mac[6]){
        printf("%02x:%02x:%02x:%02x:%02x:%02x\n", 
               mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}

int main(void)
{
        unsigned char mac[6];
        if (aix_get_mac_addr(mac) == -1){
                perror("aix_getmac");
                exit(2);
        }
        print_mac(mac);
}

这种方法的核心逻辑是通过ioctl(sock, SIOCGIFCONF, &ifc)取出网卡信息后,将其地址强转成struct sockaddr_dl类型。
当前操作系统:

-bash-4.3# uname -a
AIX localhost 1 6 00C553DC4C00
-bash-4.3# oslevel
6.1.0.0

以上代码在AIX系统下运行结果:

-bash-4.3# ./mac1
00:11:25:c5:97:cc

查看系统网卡:

-bash-4.3# netstat -in
Name  Mtu   Network     Address            Ipkts Ierrs    Opkts Oerrs  Coll
en0   1500  link#2      0.11.25.c5.97.cc 127705533     0  1124424     3     0
en0   1500  192.168.21  192.168.21.216   127705533     0  1124424     3     0
lo0   16896 link#1                        1787514     0  1709788     0     0
lo0   16896 127         127.0.0.1         1787514     0  1709788     0     0
lo0   16896 ::1%1                         1787514     0  1709788     0     0

可见,取出来的mac地址是正确的。

第二种方法

#include <stdio.h>
#include <stdint.h>
#include <errno.h>
#include <sys/ndd_var.h>
#include <sys/kinfo.h>
#include <net/if_dl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>             /* for ifconf */
#include <netinet/in.h>         /* for sockaddr_in */
#include <sys/ioctl.h>
/*
  get ethernet MAC address on AIX
 */
static int aix_get_mac_addr(uint8_t mac[6])
{
        struct ifconf ifc;
        struct ifreq *ifr;
        int sock = socket(AF_INET, SOCK_DGRAM, 0);
        ifc.ifc_len = sizeof(struct ifreq);
        ifc.ifc_buf  = (char *)ifr;
        if (sock < 0)
        {
                free(ifr);
                return -1;
        }
        ioctl(sock, SIOCGIFCONF, &ifc);
        size_t ksize;
        struct kinfo_ndd *ndd;
        int count, i;

        ksize = getkerninfo(KINFO_NDD, 0, 0, 0);
        if (ksize == 0) {
                errno = ENOSYS;
                return -1;
        }

        ndd = (struct kinfo_ndd *)malloc(ksize);
        if (ndd == NULL) {
                errno = ENOMEM;
                return -1;
        }

        if (getkerninfo(KINFO_NDD, ndd, &ksize, 0) == -1) {
                errno = ENOSYS;
                return -1;
        }

        count= ksize/sizeof(struct kinfo_ndd);
        for (i=0;i<count;i++) {
                if ((ndd[i].ndd_type == NDD_ETHER || 
                     ndd[i].ndd_type == NDD_ISO88023) &&
                    ndd[i].ndd_addrlen == 6 &&
                    (strcmp(ndd[i].ndd_alias, ifr->ifr_name) == 0 ||
                     strcmp(ndd[i].ndd_name, ifr->ifr_name) == 0)) {
                        memcpy(mac, ndd[i].ndd_addr, 6);
                        free(ndd);
                        return 0;
                }
        }
        free(ndd);
        errno = ENOENT;
        return -1;
}


void print_mac(uint8_t mac[6]){
        printf("%02x:%02x:%02x:%02x:%02x:%02x\n", 
               mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}

int main(void)
{
        uint8_t mac[6];
        int i, ret;
        ret = aix_get_mac_addr(mac);
        if (ret == -1) {
                perror("aix_getmac");
                exit(1);
        }
        print_mac(mac);
}

第二种方法是通过getkerninfo函数去获取相关硬件信息。
其运行结果和第一种方法得到的一样:

-bash-4.3# ./mac2
00:11:25:c5:97:cc

Windows系统

//TODO


禹鼎侯
176 声望466 粉丝

OLAP数据库开发。跨平台数据采集。