一、MAC 地址基础
1. 什么是 MAC 地址?
MAC 地址(媒体访问控制地址)是网络设备网卡的物理地址,用于在局域网内唯一标识一台设备,核心特性如下:
- 格式:由 6 字节(48 位)十六进制数组成,分为前 3 字节(厂商标识 OUI)和后 3 字节(设备序列号),格式如
00:11:22:33:44:55(冒号分隔,也可用连字符-); - 固化性:出厂时写入网卡 ROM,理论上全球唯一(可通过软件修改,非物理固化);
- 作用范围:仅局限于局域网(同一广播域),跨网段通信时会被网关替换,无法传递到外网。
2. MAC 地址的核心分类
根据传输范围和用途,MAC 地址分为三类,对应不同通信场景:
- 单播 MAC 地址:最常用,目标为单一设备,前 3 字节的最低位为 0(如
00:11:22:33:44:55),用于点对点通信(如两台电脑局域网传文件); - 广播 MAC 地址:全局广播,地址为
FF:FF:FF:FF:FF:FF,发送方发送后,局域网内所有设备都会接收(如 ARP 请求、DHCP Discover 报文); - 多播 MAC 地址:目标为一组设备,前 3 字节的最低位为 1(如
01:00:5E:00:00:01),用于一对多通信(如组播视频流、路由协议报文)。
3. MAC 与 IP 的核心区别
MAC 地址和 IP 地址共同完成网络通信,二者定位不同,核心区别可通过表格清晰区分:'
|------|------------------|----------------------|
| 对比维度 | MAC 地址(物理地址) | IP 地址(逻辑地址) |
| 所属层级 | 数据链路层 | 网络层 |
| 作用范围 | 局域网(广播域内) | 跨网段(互联网全局) |
| 分配方式 | 厂商预分配,理论唯一 | 手动配置或 DHCP 自动分配(可修改) |
| 核心用途 | 局域网内数据帧投递 | 跨网段设备定位 |
| 变更特性 | 设备不变则地址不变(可软件修改) | 更换网络(如切换 WiFi)时可能变更 |
4. MAC 地址在通信中的作用
结合之前讲过的 ARP 协议,MAC 地址在局域网通信中的流程可串联为:
- 应用层发起请求(如访问同网段设备),仅知晓目标 IP 地址;
- 通过 ARP 协议查询目标 IP 对应的 MAC 地址,获取后封装到以太网帧头;
- 以太网帧以 "目标 MAC 地址" 为标识,在局域网内传输,交换机根据 MAC 地址表转发帧到目标设备;
- 目标设备接收帧后,核对帧头的目标 MAC 地址,匹配则接收解析,不匹配则丢弃。
二、MAC 地址高频考点
1. MAC 地址的长度、格式及组成?
48 位(6 字节)十六进制数,格式为 XX:XX:XX:XX:XX:XX;前 3 字节为厂商标识(OUI),后 3 字节为设备序列号,全球唯一。
2. MAC 地址能跨网段传输吗?为什么?
不能。跨网段通信需经过网关(路由器),路由器会剥离原以太网帧头(含源 / 目标 MAC),重新封装网关的 MAC 地址转发,原设备 MAC 无法传递到其他网段。
3. 交换机是如何通过 MAC 地址转发数据的?
交换机维护 MAC 地址表(MAC→端口映射),接收帧后读取目标 MAC,若表中存在对应端口则单播转发,不存在则广播转发;同时记录源 MAC 与接入端口,更新地址表。
4. MAC 地址可以修改吗?如何修改?
可以(软件层面)。① Linux:通过 ifconfig 或 ip link 命令临时修改,重启失效;② Windows:在网卡属性→高级→网络地址中手动设置;注意:修改后仅局域网内生效,且需避免与其他设备冲突。
5. 为什么需要 ARP 协议?与 MAC 地址的关联是什么?
用层仅知晓目标 IP,而局域网传输依赖 MAC 地址,ARP 协议的核心作用就是 "IP 地址到 MAC 地址的映射",为以太网帧封装提供目标 MAC,衔接网络层与数据链路层。
6. 广播 MAC 地址的应用场景有哪些?
① ARP 请求报文(查询目标 IP 对应的 MAC);② DHCP 自动获取 IP(客户端发送广播请求 IP);③ 局域网内无目标 MAC 映射时,交换机广播转发帧。
7. 跨网段通信时,目标 MAC 是最终设备的 MAC?
跨网段时,目标 MAC 为网关的 MAC,原设备 MAC 仅在本地局域网有效;
8. MAC 地址绝对唯一且不可修改?
理论上唯一,可通过软件临时修改(非物理层面),修改后需注意局域网内冲突;
9. MAC 地址和网卡一一对应?
一台设备可能有多个网卡(有线、无线),每个网卡对应一个独立 MAC 地址。
三、MAC 地址获取与解析
1. 环境与依赖
- Linux 环境:无需额外库,通过系统调用读取网卡信息(如
/sys/class/net目录或ioctl函数); - Windows 环境:调用系统 API(如
IP_ADAPTER_INFO结构体),无需第三方库; - 核心思路:分别实现 Linux/Windows 下的本地 MAC 获取、以太网帧中 MAC 解析,代码加详细注释,确保小白可复现。
2. Linux 下获取本地网卡 MAC 地址
实现功能:遍历本地网卡(如 eth0、wlan0),获取并输出对应网卡的 MAC 地址,适配小白操作。
cpp
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/ioctl.h>
<netinet/in.h<net/if.h>
#include <arpa/inet.h>
// 获取指定网卡的MAC地址
std::string get_linux_mac(const std::string& ifname) {
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
< "创建套接字失败:" << strerror(errno) << std::endl;
return "";
}
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, ifname.c_str(), IFNAMSIZ - 1); // 传入网卡名称
// 通过ioctl获取网卡MAC地址
if (ioctl(sockfd, SIOCGIFHWADDR, &ifr) < 0) {
< "获取< "MAC地址失败:" << strerror(errno) << std::endl;
close(sockfd);
return "";
}
close(sockfd);
// 格式化MAC地址(将二进制转为十六进制,加冒号分隔)
unsigned char* mac = (unsigned char*)ifr.ifr_hwaddr.sa_data;
char mac_str[20];
snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return mac_str;
}
int main() {
std::string ifname;
std::cout << "请输入要查询的网卡名称(如eth0、wlan0):";
std::cin >> ifname;
std::string mac = get_linux_mac(ifname);
if (!mac.empty()) {
< ifname< " 的MAC地址:< std::endl;
}
return 0;
}
编译与运行(Linux)
- 编译命令:
g++ linux_mac_get.cpp -o linux_mac_get; - 运行:
./linux_mac_get,输入网卡名称(可通过ifconfig查看网卡列表),即可输出 MAC 地址。
3. Windows 下获取本地网卡 MAC 地址
Windows 下通过 API 读取网卡适配器信息,代码简洁,小白可直接用 VS 编译运行:
cpp
<windows.h>
<iphlpapi<icmpapi.h>
// 链接IP辅助库
#pragma comment(lib, "iphlpapi.lib")
// 获取所有网卡的MAC地址及对应IP
void get_windows_mac() {
DWORD buf_len = 0;
// 先获取所需缓冲区大小
GetAdaptersInfo(nullptr, &buf_len);
IP_ADAPTER_INFO* adapter_info = (IP_ADAPTER_INFO*)malloc(buf_len);
if (adapter_info == nullptr) {
std< "内存分配< std::endl;
return;
}
// 读取网卡信息
if (GetAdaptersInfo(adapter_info, &buf_len) != ERROR_SUCCESS) {
std::cerr << "获取网卡信息失败"< std::endl;
free(adapter_info);
return;
}
IP_ADAPTER_INFO* adapter = adapter_info;
int index = 1;
while (adapter != nullptr) {
// 过滤掉环路地址、无效网卡
if (adapter->Type != MIB_IF_TYPE_ETHERNET || strcmp(adapter->IpAddressList.IpAddress.String, "") == 0) {
adapter = adapter->Next;
continue;
}
// 格式化MAC地址
char mac_str[20];
unsigned char* mac = adapter->Address;
snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
// 输出网卡信息
std< "网卡" << index++ << ":" << std::endl;
< " < adapter< std::endl;
std::< " IP地址< adapter->IpAddressList.IpAddress.String< std::endl;
< " MAC地址:" << std::endl< std::endl;
adapter = adapter->Next;
}
free(adapter_info);
}
int main() {
std::cout << "本地网卡信息(MAC+IP):" << std::endl;
get_windows_mac();
return 0;
}
编译与运行(Windows)
- 环境:Visual Studio 或 MinGW;
- 编译:创建控制台项目,粘贴代码编译(自动链接
iphlpapi.lib); - 运行:双击 exe 文件,直接输出所有以太网网卡的 MAC 地址和对应 IP,无需手动输入网卡名称。
4. 解析以太网帧中的 MAC 地址(结合 libpcap)
基于 libpcap 抓包,解析以太网帧头中的源 / 目标 MAC 地址,衔接之前 ARP、ICMP 的抓包逻辑,深化实战能力:
cpp
#include <pcap.h>
#include <netinet/if_ether.h>
#include <cstring>
// 回调函数:解析捕获到的以太网帧
void packet_handler(u_char* user_data, const struct pcap_pkthdr* pkthdr, const u_char* packet) {
// 以太网帧头长度为14字节(目标MAC6字节+源MAC6字节+帧类型2字节)
if (pkth< 14) return;
// 解析以太网帧头
const struct ether_header* eth_header = (const struct ether_header*)packet;
unsigned char* dst_mac = eth_header->ether_dhost; // 目标MAC
unsigned char* src_mac = eth_header->ether_shost; // 源MAC
uint16_t eth_type = ntohs(eth_header->ether_type); // 帧类型(如ARP=0x0806,IP=0x0800)
// 格式化MAC地址
char dst_mac_str[20], src_mac_str[20];
snprintf(dst_mac_str, sizeof(dst_mac_str), "%02x:%02x:%02x:%02x:%02x:%02x",
dst_mac[0], dst_mac[1], dst_mac[2], dst_mac[3], dst_mac[4], dst_mac[5]);
snprintf(src_mac_str, sizeof(src_mac_str), "%02x:%02x:%02x:%02x:%02x:%02x",
src_mac[0], src_mac[1], src_mac[2], src_mac[3], src_mac[4], src_mac[5]);
// 输出解析结果
< "捕获< std::endl;
std< " < dst< std::endl;
std::< " 源< src_m< std::endl;
std::cout< " 帧类型< std::hex << eth_type << std::< std::endl;
}
int main() {
char err_buf[PCAP_ERRBUF_SIZE];
pcap_if_t* alldevs, *dev;
// 获取本地网卡设备
if (pcap_findalldevs(&alldevs, err_buf) == -1) {
std::cerr << "获取网卡设备失败:< std::endl;
return -1;
}
dev = alldevs;
std< "使用网卡< dev->name< std::endl;
// 打开网卡,混杂模式抓包
pcap_t* handle = pcap_open_live(dev->name, BUFSIZ, 1, 1000, err_buf);
if (handle == nullptr) {
< "打开< err< std::endl;
pcap_freealldevs(alldevs);
return -1;
}
pcap_freealldevs(alldevs);
// 开始抓包(循环捕获10个帧后退出)
std::< "开始抓包,共捕获10个< std::endl;
pcap_loop(handle, 10, packet_handler, nullptr);
pcap_close(handle);
return 0;
}
编译与运行(Linux)
- 依赖:安装 libpcap 库,命令
sudo apt-get install libpcap-dev; - 编译:
g++ mac_parse.cpp -o mac_parse -lpcap; - 运行(需 root 权限):
sudo ./mac_parse,捕获 10 个以太网帧并解析源 / 目标 MAC。
5. 扩展
- 局域网 MAC 扫描:结合 ARP 协议,发送 ARP 广播请求,收集响应设备的 IP-MAC 映射,实现设备扫描工具;
- MAC 地址过滤:抓包时过滤指定 MAC 地址的报文,仅保留目标设备的通信数据;
- 跨平台封装:用条件编译整合 Linux/Windows 代码,实现一份代码获取双系统网卡 MAC;
- MAC 绑定检测:对比本地存储的合法 MAC 列表,检测局域网内是否有非法设备接入。