首页 > 解决方案 > 如果服务器正在侦听 v6 套接字且 IPV6_V6 选项设置为 false,如何识别接收到的连接是 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, buff, sizeof(buff));
        // if msg contains "Exit" then server exit and chat ended.
        if (strncmp("exit", buff, 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, &flag, 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", str_addr, ntohs(cli.sin_port));
            } else if ( AF_INET6 ) {
                char str_addr[INET6_ADDRSTRLEN] = {0};
                inet_ntop(AF_INET6, &(cli.sin6_addr), str_addr, sizeof(str_addr));
                printf("New connection from: %s:%d ...\n", str_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);
    }
}

标签: csocketsclient-serveripv6ipv4

解决方案


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

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

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

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


推荐阅读