如果服务器正在侦听 IPV6_V6 选项设置为 false 的 v6 套接字,如何识别接收到的连接是 v4 还是 v6

问题描述

我创建了一个 v6 套接字,套接字选项 ipv6_v6only 设置为 false。我现在可以接受来自 v4 客户端和 v6 客户端的连接。此外,我想向套接添加一些选项,例如 MD5 哈希,并将地址转换为可显示格式,因此我想知道是否有任何方法可以根据以下代码识别接收到的连接是 v4 还是 v6。

谢谢。

#include <stdio.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#define MAX 80
#define PORT 8085
#define SA struct sockaddr

// Function designed for chat between client and server.
void func(int sockfd)
{
    char buff[MAX];
    int n;
    // infinite loop for chat
    for (;;) {
        bzero(buff,MAX);

        // read the message from client and copy it in buffer
        read(sockfd,buff,sizeof(buff));
        // print buffer which contains the client contents
        printf("From client: %s\t To client : ",buff);
        bzero(buff,MAX);
        n = 0;
        // copy server message in the buffer
        while ((buff[n++] = getchar()) != '\n')
            ;

        // and send that buffer to client
        write(sockfd,sizeof(buff));
        // if msg contains "Exit" then server exit and chat ended.
        if (strncmp("exit",4) == 0) {
            printf("Server Exit...\n");
            break;
        }
    }
}

// Driver function
int main()
{
    int sockfd,connfd,len;
    struct sockaddr_in6 servaddr;
    struct sockaddr_in cli;
    int ret,flag;
    pid_t childpid;

    // socket create and verification
    sockfd = socket(AF_INET6,SOCK_STREAM,IPPROTO_TCP);
    if (sockfd == -1) {
        printf("socket creation Failed...\n");
        perror("socket()");
        return EXIT_FAILURE;
    }
    else
        printf("Socket successfully created..\n");

    /* Set socket to reuse address */
    flag = 1;
    ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));
    if(ret == -1) {
        perror("setsockopt(SO_REUSEADDR)");
        return EXIT_FAILURE;
    }

    /* Set socket to reuse address */
    flag = 0;
    ret = setsockopt(sockfd,IPPROTO_IPV6,IPV6_V6ONLY,sizeof(flag));
    if(ret != 0) {
        perror("setsockopt(IPV6_V6ONLY)");
        return EXIT_FAILURE;
    }

    bzero(&servaddr,sizeof(servaddr));

    // assign IP,PORT
    servaddr.sin6_family = AF_INET6;
    servaddr.sin6_addr = in6addr_any;
    servaddr.sin6_port = htons(PORT);

    // Binding newly created socket to given IP and verification
    ret = bind(sockfd,(SA*)&servaddr,sizeof(servaddr));
    if (ret == -1) {
        printf("socket bind Failed...\n");
        perror("listen()");
        close(sockfd);
        return EXIT_FAILURE;
    }
    else
        printf("Socket successfully binded..\n");

    // Now server is ready to listen and verification
    if ((listen(sockfd,5)) != 0) {
        printf("Listen Failed...\n");
        return EXIT_FAILURE;
    }
    else
        printf("Server listening..\n");

    for (;;) { //infinite loop
            len = sizeof(cli);
            // Accept the data packet from client and verification
            connfd = accept(sockfd,(SA*)&cli,&len);
            if (connfd < 0) {
                printf("server accept Failed...\n");
                perror("accept()");
                return EXIT_FAILURE;
            }
            printf("Connection accepted...\n");

            *** 
             * Here i want to differentiate if the received connection is v4 or v6 and
             * based on that i want to do some operation like converting the client
             * address to presentable format,also applying md5 hash to the session
             ***
            if ( AF_INET ) {
                char str_addr[INET_ADDRSTRLEN] = {0};
                inet_ntop(AF_INET,&(cli.sin_addr),str_addr,sizeof(str_addr));
                printf("New connection from: %s:%d ...\n",ntohs(cli.sin_port));
            } else if ( AF_INET6 ) {
                char str_addr[INET6_ADDRSTRLEN] = {0};
                inet_ntop(AF_INET6,&(cli.sin6_addr),ntohs(cli.sin_port));
            }
                    
            if ((childpid = fork()) == 0) { //creating a child process

                    close(sockfd);
                    //stop listening for new connections by the main process.
                   //the child will continue to listen.
                    //the main process Now handles the connected client.

                    // Function for chatting between client and server
                    func(connfd);

            }
            // After chatting close the socket
            close(connfd);
    }
}

解决方法

如果您使用的是 IPv6 套接字,则本地和远程地址将始终为 struct sockaddr_in6 类型,因此您对 sockaddr_in(以及同样的 sin_port 成员)的使用是错误的。在 v6 侦听套接字上接收 IPv4 连接发生在 v4-mapped addressesIN6_IS_ADDR_V4MAPPED 中的 netinet/in.h 宏可用于查询客户端地址的 sin6_addr(注意 6!)是否是来自 v4 网络连接的 v4 映射地址。

然而,正如肖恩在评论中指出的那样:

如果您使用 getnameinfo() 将 sockaddr 转换为人类可读的地址,它会透明地处理所有不同的协议,因此您不必担心它们,顺便说一句

你可能没有理由真正关心。如果您使用现代 API 在 sockaddr 对象和字符串之间进行转换,一切都会自动运行,您无需检查特定系列 sockaddr 结构的成员。