Linux网络编程- struct ifreq & ioctl() 系统调用

struct ifreq

struct ifreq 是一个数据结构,用于各种与接口相关的输入/输出控制 (ioctl) 调用。它的主要用途是在网络编程中获取和设置网络接口的属性。这个结构体在 头文件中定义。

以下是 struct ifreq 的一些主要字段和它们的用途:

  1. ifr_name: 一个字符数组,表示接口的名称,如 “eth0”, “wlan0” 等。

  2. ifr_addr: 一个 struct sockaddr 类型的结构,表示接口的地址。

  3. ifr_netmask: 同样是一个 struct sockaddr 类型的结构,表示接口的网络掩码。

  4. ifr_broadaddr: 表示接口的广播地址。

  5. ifr_flags: 表示接口的标志,如 IFF_UP (接口正在运行)、IFF_BROADCAST (接口支持广播)、IFF_PROMISC (接口在混杂模式下)等。

  6. ifr_hwaddr: 表示接口的硬件(通常是 MAC)地址。

  7. ifr_mtu: 表示接口的最大传输单元 (MTU)。

还有其他一些字段,但上面列出的是最常用的。

当我们想查询或设置接口的特定属性时,会填充 struct ifreq 的适当字段,并使用 ioctl() 系统调用。例如,要设置接口为混杂模式,我们会填充 ifr_name 字段并设置 IFF_PROMISC 标志,然后调用 ioctl()

例如:

struct ifreq ifr;
int sockfd;

// 创建一个套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
    perror("socket");
    exit(EXIT_FAILURE);
}

// 设置ifr_name为我们想查询或修改的接口名
strncpy(ifr.ifr_name, "eth0", IFNAMSIZ);

// 获取接口标志
if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) {
    perror("ioctl");
    close(sockfd);
    exit(EXIT_FAILURE);
}

// 打印是否处于混杂模式
if (ifr.ifr_flags & IFF_PROMISC) {
    printf("eth0 is in promiscuous mode\n");
} else {
    printf("eth0 is not in promiscuous mode\n");
}

close(sockfd);

这是一个简单的示例,演示了如何检查网络接口(在这种情况下是 “eth0”)是否处于混杂模式。


ioctl() 系统调用

ioctl() 系统调用提供了一种通用的设备驱动程序或接口操作机制,可以用于各种设备特定的操作和控制。它通常用于那些不适合用常规系统调用(如 read(), write(), open() 等)表示的设备特定的操作。

函数原型:

int ioctl(int fd, unsigned long request, ...);

参数:

  1. fd: 是一个已经打开的文件或套接字的文件描述符。

  2. request: 是设备特定的请求码。这个请求码定义了我们想要执行的特定操作或命令。

  3. : 这是一个可变参数,取决于特定的 request。它可能是一个指向数据的指针,也可能是一个直接的值。

返回值:

  • ioctl() 的返回值取决于特定的操作。通常,成功返回非负值,失败返回 -1,并设置 errno

用途:

  1. 网络编程: ioctl() 经常用于查询和设置网络接口的参数。例如,获取或设置接口的 IP 地址,查询或设置接口的标志等。

  2. 设备控制: 对于各种设备(如终端、声音卡、图形卡),ioctl() 可以用于执行特定的操作或查询信息。

  3. 文件系统: 在某些情况下,ioctl() 也可用于文件系统操作。

一些例子:

  1. 获取网络接口的标志:

    struct ifreq ifr;
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    strncpy(ifr.ifr_name, "eth0", IFNAMSIZ);
    ioctl(fd, SIOCGIFFLAGS, &ifr);
    
  2. 设置非阻塞套接字:

    int flags = fcntl(fd, F_GETFL, 0);
    ioctl(fd, FIONBIO, &flags);
    
  3. 查询音频设备属性:

    int volume;
    ioctl(fd, SOUND_MIXER_READ_VOLUME, &volume);
    

注意事项:

  1. 不是所有的设备都支持所有的 ioctl 操作。要知道设备支持哪些操作,需要查阅特定设备的文档或头文件。

  2. 错误处理:与其他系统调用一样,使用 ioctl() 时也应进行适当的错误处理。它可能会失败,并设置 errno,所以应该检查它的返回值并相应地处理错误。

  3. 可移植性ioctl() 是一个非常通用且强大的函数,但它经常与特定的设备或平台相关。这意味着在不同的操作系统或硬件平台上,可能需要不同的 ioctl 请求代码或参数。

总之,ioctl() 提供了一种与设备进行交互的方法,允许开发者执行不同的设备特定操作。然而,正确地使用它需要对特定设备的工作方式有深入的了解。


示例

下面让我们用一个简单的例子来展示如何使用 ioctl() 来获取和设置网络接口(例如 eth0)的 IP 地址。

1. 获取 IP 地址

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main() {
    int sockfd;
    struct ifreq ifr;

    // 创建一个 socket
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket");
        exit(1);
    }

    // 指定要查询的接口名
    strncpy(ifr.ifr_name, "enp5s0", IFNAMSIZ);

    // 获取接口的 IP 地址
    if (ioctl(sockfd, SIOCGIFADDR, &ifr) < 0) {
        perror("ioctl - SIOCGIFADDR ");
        exit(1);
    }

    struct sockaddr_in *ipaddr = (struct sockaddr_in *)&ifr.ifr_addr;
    printf("IP address of enp5s0: %s\n", inet_ntoa(ipaddr->sin_addr));

    close(sockfd);
    return 0;
}

程序运行结果为:

majn@tiger:~/C_Project/network_project$ ./ioctl_setip 
IP address of enp5s0: 192.168.31.6

2. 设置 IP 地址

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main() {
    int sockfd;
    struct ifreq ifr;
    struct sockaddr_in *sin = (struct sockaddr_in *)&ifr.ifr_addr;

    // 创建一个 socket
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket");
        exit(1);
    }

    // 指定要设置的接口名
    strncpy(ifr.ifr_name, "eth0", IFNAMSIZ);

    // 设置新的 IP 地址
    sin->sin_family = AF_INET;
    inet_aton("192.168.1.100", &sin->sin_addr);

    if (ioctl(sockfd, SIOCSIFADDR, &ifr) < 0) {
        perror("ioctl - SIOCSIFADDR ");
        exit(1);
    }

    printf("IP address of eth0 set to: %s\n", inet_ntoa(sin->sin_addr));

    close(sockfd);
    return 0;
}

注意: 设置 IP 地址通常需要 root 权限,所以运行上述代码时可能需要使用 sudo

上述示例中,ioctl() 被用来查询或设置网络接口的 IP 地址。具体的操作由 SIOCGIFADDR(获取地址)和 SIOCSIFADDR(设置地址)指定。


inet_aton()

inet_aton() 函数用于将点分十进制格式的字符串(如 “192.168.1.1”)转换为一个网络字节序的 IPv4 地址。它在 头文件中定义。

函数原型:

int inet_aton(const char *cp, struct in_addr *inp);

参数:

  • cp: 指向点分十进制格式的 IPv4 地址字符串的指针。
  • inp: 指向一个 struct in_addr 结构体的指针,该结构体用于存储转换后的地址。

返回值:

  • 成功时返回 1,失败时返回 0。

inet_addr() 相比,inet_aton() 函数提供了更清晰的错误检查机制,因为它明确返回成功或失败的状态,而不是使用特殊的地址值来表示错误。

示例:

#include 
#include 

int main() {
    const char *ip_string = "192.168.1.1";
    struct in_addr ip_addr;

    if (inet_aton(ip_string, &ip_addr)) {
        printf("Conversion successful. Network byte order value: 0x%x\n", ip_addr.s_addr);
    } else {
        printf("Conversion failed.\n");
    }

    return 0;
}

上述程序的运行结果为:

majn@tiger:~/C_Project/network_project$ ./inet_aton_demo 
Conversion successful. Network byte order value: 0x101a8c0

在此示例中,我们将点分十进制的 IP 地址字符串 “192.168.1.1” 使用 inet_aton() 转换为 struct in_addr 格式,并输出转换的结果。

注意:inet_aton()inet_ntoa() 是一对,一个用于将字符串转换为二进制格式,另一个用于进行相反的操作。如果您正在寻找一个线程安全的方法或需要处理 IPv6 地址,建议使用 inet_pton()inet_ntop() 函数代替。


inet_ntoa()

inet_ntoa() 是一个网络工具函数,用于将一个网络字节序的 IPv4 地址转换为点分十进制字符串格式。它在 头文件中定义。

函数原型:

char *inet_ntoa(struct in_addr in);

参数:

  • in: 是一个 struct in_addr 结构体,包含一个 IPv4 地址(在网络字节序中)。

返回值:

  • 返回一个指向一个静态分配的字符串的指针,该字符串表示 IPv4 地址的点分十进制表示形式。因为返回的是一个静态字符串,所以之后的 inet_ntoa() 调用可能会覆盖之前的内容。

注意事项:

  1. inet_ntoa() 返回一个指向静态存储区的指针,这意味着每次调用都会覆盖前一次的结果。如果需要保留转换的地址,应该自行复制返回的字符串。
  2. 考虑到线程安全性,inet_ntoa() 在多线程环境中可能会导致问题。为了避免这种情况,可以考虑使用更现代、线程安全的函数,例如 inet_ntop()

示例:

#include 
#include 

int main() {
    struct in_addr ip_addr;
    ip_addr.s_addr = htonl(0xC0A80101);  // 0xC0A80101 = 192.168.1.1 in hexadecimal

    char *ip_str = inet_ntoa(ip_addr);
    printf("IP address in dotted-decimal format: %s\n", ip_str);

    return 0;
}

在此示例中,我们创建了一个 struct in_addr 结构体,设置了 IP 地址(以网络字节序),然后使用 inet_ntoa() 将它转换为字符串。

上述程序的运行结果为:

majn@tiger:~/C_Project/network_project$ ./inet_ntoa_demo 
IP address in dotted-decimal format: 192.168.1.1

你可能感兴趣的:(工程化C,Linux,linux,网络)