乐趣区

关于linux:C语言获取服务器mac地址

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

退出移动版