Reputation: 117
Currently, the following program only connects using an IPv4 address. I want it to be modified to connect to a server(compatible of accepting IPv4 and IPv6 clients) using any of IPv6 or IPv4 address of the server.
#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;
}
Upvotes: 4
Views: 2160
Reputation: 9978
In addition to fluter's answer you need to change the structure of your code a bit.
Instead of creating a socket and then looking up the address you need to call getaddrinfo
to get all the addresses for the given hostname. Hostnames are often dual stacked so you'll get multiple IPv4 and/or IPv6 addresses back. They are usually sorted in the order you should try them. So loop over all the addresses one by one until the connection succeeds.
Because some of the addresses will be IPv4 and some will be IPv6 you can't create a socket up front. For each address you try you should create a new socket belonging to the address family of the address you are trying to connect to. Fluter has already shown you how.
Once your connect succeeds you break the loop and use the established connection.
Upvotes: 2
Reputation: 13846
You will need to use functions that supports both IPV6 and IPV4, for example, do not use inet_pton
, use getaddrinfo
instead, it can parse addresses of both protocol versions, and tell your the right family to use.
In all subsequent network calls, you should use the family returned by getaddrinfo
, rather than hardcode it to AF_INET
, e.g. sock = socket(addr->ai_family, ...)
.
Additionally, read this IPV6 intro, it's a very good start.
https://www.akkadia.org/drepper/userapi-ipv6.html
For specific changes in your code, I think you need change these 3 places for a start:
// 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);
Upvotes: 1