Хочу, чтобы моя клиентская программа подключалась через IPv4 или IPv6

В настоящее время следующая программа подключается только с использованием адреса IPv4. Я хочу, чтобы он был изменен для подключения к серверу (совместимого с приемом клиентов IPv4 и IPv6) с использованием любого адреса IPv6 или IPv4 сервера.

            #include <stdio.h>
            #include <stdlib.h>
            #include <string.h>
            #include <sys/types.h>
            #include <sys/socket.h>
            #include <netinet/in.h>
            #include <arpa/inet.h>

            #include "common.h"
            #include "client.h"



            int
            CreateClientTCP(const char *svrHost,   
                            unsigned short svrPort,
                            char *svrName, 
                            int svrNameLen)
            {
                int sock;
                struct sockaddr_in svrAddr;

                sock = socket(AF_INET, SOCK_STREAM, 0);
                if (sock < 0) {
                    perror("Failed to allocate the client socket");
                    exit(EXIT_FAILURE);
                }

                memset(&svrAddr, 0, sizeof(svrAddr));
                svrAddr.sin_family = AF_INET;
                svrAddr.sin_port   = htons(svrPort);

                if (inet_pton(AF_INET, svrHost, &svrAddr.sin_addr.s_addr) <= 0) {
                    perror("Failed to convert IP address\n");
                    exit(EXIT_FAILURE);
                }

                SocketAddrToString(&svrAddr, svrName, svrNameLen);
                Log("Attempting %s\n", svrName);

                if (connect(sock, (struct sockaddr *)&svrAddr, sizeof(svrAddr)) < 0) {
                    perror("Failed to connect to the server");
                    exit(EXIT_FAILURE);
                }

                return sock;
            }



            int
            main(int argc, char *argv[])
            {
                int sock;
                ClientArgs cliArgs;
                char svrName[INET_ADDRSTRLEN + PORT_STRLEN];

                ParseArgs(argc, argv, &cliArgs);

                sock = CreateClientTCP(cliArgs.svrHost, cliArgs.svrPort,
                                       svrName, sizeof svrName);

                Log("Connected to server at %s\n", svrName);

                Client(sock, &cliArgs);

                close(sock);
                Log("\nDisconnected from server at %s\n", svrName);
                return 0;
            }

person ramnarayanan    schedule 24.04.2016    source источник


Ответы (2)


В дополнение к ответу флутера вам нужно немного изменить структуру вашего кода.

Вместо того, чтобы создавать сокет и затем искать адрес, вам нужно вызвать getaddrinfo, чтобы получить все адреса для данного имени хоста. Имена хостов часто имеют двойной стек, поэтому вы получите обратно несколько адресов IPv4 и / или IPv6. Обычно они сортируются в том порядке, в котором вы должны их попробовать. Поэтому перебирайте все адреса один за другим, пока соединение не будет установлено.

Поскольку некоторые адреса будут IPv4, а некоторые будут IPv6, вы не можете создать сокет заранее. Для каждого адреса, который вы пробуете, вы должны создать новый сокет, принадлежащий к семейству адресов адреса, к которому вы пытаетесь подключиться. Fluter уже показал вам, как это сделать.

После успешного подключения вы разрываете петлю и используете установленное соединение.

person Sander Steffann    schedule 24.04.2016
comment
Некоторые платформы позволяют использовать сокет IPv6 для связи по обоим протоколам. Но я не уверен, что код, делающий это таким образом, можно сделать переносимым. Однако есть и другие преимущества создания отдельного сокета для каждого адреса, поскольку это позволяет параллельно использовать несколько сокетов. - person kasperd; 24.04.2016

Вам нужно будет использовать функции, которые поддерживают как IPV6, так и IPV4, например, не используйте inet_pton, вместо этого используйте getaddrinfo, он может анализировать адреса обеих версий протокола и указывать правильное семейство для использования.

Во всех последующих сетевых вызовах вы должны использовать семейство, возвращенное getaddrinfo, а не жестко запрограммировать его на AF_INET, например. sock = socket(addr->ai_family, ...).

Кроме того, прочитайте это введение IPV6, это очень хорошее начало.

https://www.akkadia.org/drepper/userapi-ipv6.html

Для конкретных изменений в вашем коде, я думаю, вам нужно изменить эти 3 места для начала:

// struct sockaddr_in svrAddr;  <-- sockaddr_in is for ipv4 address
struct sockaddr_storage svrAddr;

// inet_pton(AF_INET, svrHost, &svrAddr.sin_addr.s_addr) <-- IPV4 only
struct addrinfo *res;
getaddrinfo(svrHost, NULL, &hint, &res);

// sock = socket(AF_INET, SOCK_STREAM, 0); <-- IPV4 only
sock = socket(result->ai_family, SOCK_STREAM, 0);
person fluter    schedule 24.04.2016
comment
Какие изменения потребуются в рамках программы? Должен ли я кодировать отдельный клиент? - person ramnarayanan; 24.04.2016
comment
@ramnarayanan нет, цель состоит в том, чтобы использовать один клиент для поддержки обеих версий. - person fluter; 24.04.2016
comment
Есть поток ошибок, которыми я хотел бы поделиться. @флютер - person ramnarayanan; 24.04.2016