Linux 通過 ioctl 系統調用與網絡設備驅動交互。獲取 IPv4 地址的標準流程:
AF_INET + SOCK_DGRAM Socket(僅作 ioctl 控制通道,不通信)struct ifreq,設置接口名(如 eth0)ioctl(fd, SIOCGIFADDR, &ifreq) 獲取地址ifreq.ifr_addr 提取 struct sockaddr_in,轉換為點分十進制
/**********************************************************************
* 獲取指定網卡 IPv4 地址
* 編譯: gcc -Wall -o getip getip.c
* 運行: ./getip
**********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <errno.h>
#define IFACE_NAME "eth0" // 目標網卡名,可改為參數
int main() {
int sockfd;
struct ifreq ifr;
struct sockaddr_in *sin;
char ip_str[INET_ADDRSTRLEN];
/* 1. 創建 Socket */
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket 失敗");
return 1;
}
/* 2. 設置接口名 */
strncpy(ifr.ifr_name, IFACE_NAME, IFNAMSIZ - 1);
ifr.ifr_name[IFNAMSIZ - 1] = '\0'; // 確保結束符
/* 3. ioctl 獲取地址 */
if (ioctl(sockfd, SIOCGIFADDR, &ifr) < 0) {
perror("ioctl SIOCGIFADDR 失敗");
close(sockfd);
return 1;
}
/* 4. 提取並轉換 IP */
sin = (struct sockaddr_in *)&ifr.ifr_addr;
if (inet_ntop(AF_INET, &sin->sin_addr, ip_str, sizeof(ip_str)) == NULL) {
perror("inet_ntop 失敗");
close(sockfd);
return 1;
}
printf("接口 %s: %s\n", IFACE_NAME, ip_str);
close(sockfd);
return 0;
}
// 簡化版定義 (實際在頭文件中)
struct ifreq {
char ifr_name[IFNAMSIZ]; // 接口名,如 "eth0"
union {
struct sockaddr ifr_addr; // 地址
struct sockaddr ifr_dstaddr; // 目標地址
struct sockaddr ifr_broadaddr;// 廣播地址
struct sockaddr ifr_netmask; // 子網掩碼
// ...
} ifr_ifru;
};
struct sockaddr_in {
sa_family_t sin_family; // AF_INET
in_port_t sin_port; // 端口 (此處不使用)
struct in_addr sin_addr; // IPv4 地址
};
struct in_addr {
in_addr_t s_addr; // 32位網絡序地址
};
getifaddrs() (BSD 擴展,Linux 支持) 可一次性獲取所有接口地址,無需逐個 ioctl:
#include <ifaddrs.h>
void print_all_ips() {
struct ifaddrs *ifaddr, *ifa;
if (getifaddrs(&ifaddr) == -1) { perror("getifaddrs"); return; }
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL) continue;
if (ifa->ifa_addr->sa_family == AF_INET) {
struct sockaddr_in *sa = (struct sockaddr_in *)ifa->ifa_addr;
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &sa->sin_addr, ip, sizeof(ip));
printf("%s: %s\n", ifa->ifa_name, ip);
}
}
freeifaddrs(ifaddr);
}
優勢:枚舉所有接口、支持 IPv6、代碼更簡潔。缺點:非 POSIX 標準(但主流系統均支持)。
| 現象 | 原因 | 解決 |
|---|---|---|
| ioctl 返回 -1, errno=ENODEV | 接口名錯誤或不存在 | 用 ip link 或 ifconfig -a 確認名稱 |
| 獲取到 127.0.0.1 | 查詢了 lo 回環接口 | 指定物理接口名 (eth0, ens33, enp3s0 等) |
| 多網卡環境 | 需遍歷所有接口 | 用 getifaddrs() 或解析 /proc/net/dev |
| 權限不足 | 非 root 無法創建原始 Socket | SOCK_DGRAM 通常無需 root,SOCK_RAW 需要 |
// 同一 socket 可複用,依次調用不同 ioctl 命令
ioctl(sockfd, SIOCGIFHWADDR, &ifr); // MAC 地址
ioctl(sockfd, SIOCGIFNETMASK, &ifr); // 子網掩碼
ioctl(sockfd, SIOCGIFBRDADDR, &ifr); // 廣播地址
MAC 地址在 ifr.ifr_hwaddr.sa_data (6 位元組),需手動格式化為 xx:xx:xx:xx:xx:xx。