I'm trying to code an agnostic echo server, that could accept both IPv4 and IPv6 connection. I'm working with addrinfo structure, set with getaddrinfo.
Ipv4 connect has no problem while I can't get a working ipV6 connection.
I think my problem could be due to a wrong getaddrinfo parameter, but I'm not able to see where I'm going wrong.
Here's my code
client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int simpleSocket = 0, simplePort = 0,returnStatus = 0, n;
char buffer[1024] = "";
struct hostent *hostinfo;
struct addrinfo simpleServer, *res;
if (3 != argc) {
fprintf(stderr, "Usage: %s <server> <port>\n", argv[0]);
exit(1);
}
simplePort = atoi(argv[2]);
memset(&simpleServer, 0, sizeof simpleServer);
simpleServer.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever
simpleServer.ai_socktype = SOCK_STREAM;
simpleServer.ai_flags = AI_PASSIVE; // fill in my IP for me
returnStatus = getaddrinfo(argv[1], argv[2], &simpleServer, &res);
simpleSocket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
char *s = NULL;
switch(res->ai_addr->sa_family) {
case AF_INET: {
struct sockaddr_in *addr_in = (struct sockaddr_in *)res;
s = malloc(INET_ADDRSTRLEN);
inet_ntop(AF_INET, &(addr_in->sin_addr), s, INET_ADDRSTRLEN);
returnStatus = connect(simpleSocket, res->ai_addr, res->ai_addrlen);
break;
}
case AF_INET6: {
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)res;
s = malloc(INET6_ADDRSTRLEN);
inet_ntop(AF_INET6, &(addr_in6->sin6_addr), s, INET6_ADDRSTRLEN);
returnStatus = connect(simpleSocket, res->ai_addr, res->ai_addrlen);
break;
}
default:
break;
}
fprintf(stdout, "IP address: %s\n", s);
returnStatus = connect(simpleSocket, res->ai_addr, res->ai_addrlen);
fprintf(stdout, "Type a message \n");
memset(buffer, '\0', strlen(buffer));
fgets(buffer, sizeof(buffer), stdin);
returnStatus = write(simpleSocket, buffer, sizeof(buffer));
memset(&buffer, '\0', sizeof(buffer));
fprintf(stdout, "Waiting server..\n");
returnStatus = read(simpleSocket, buffer, sizeof(buffer));
fprintf(stdout, "Message: %s\n", buffer);
close(simpleSocket);
return 0;
}
server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, char *argv[])
{
int simpleSocket = 0, simplePort = 0, returnStatus = 0, check = 1, n;
char buffer[1024];
struct addrinfo simpleServer, *res;
if (2 != argc) {
fprintf(stderr, "Usage: %s <port>\n", argv[0]);
exit(1);
}
simplePort = atoi(argv[1]);
memset(&simpleServer, 0, sizeof simpleServer);
simpleServer.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever
simpleServer.ai_socktype = SOCK_STREAM;
simpleServer.ai_flags = AI_PASSIVE; // fill in my IP for me
getaddrinfo(NULL, argv[1], &simpleServer, &res);
simpleSocket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
returnStatus =bind(simpleSocket, res->ai_addr, res->ai_addrlen);
returnStatus = listen(simpleSocket, 5);
struct addrinfo clientName = { 0 };
int clientNameLength = sizeof(clientName);
int simpleChildSocket = 0;
while (1) {
while (1) {
simpleChildSocket = accept(simpleSocket,(struct sockaddr *)&clientName, &clientNameLength);
fprintf(stdout,"Waiting..\n");
memset(&buffer, '\0', sizeof(buffer));
returnStatus = read(simpleChildSocket, buffer, sizeof(buffer));
fprintf(stdout, "Message: %s\n", buffer);
write(simpleChildSocket, buffer, sizeof(buffer));
}
}
close(simpleChildSocket);
close(simpleSocket);
return 0;
}
IPv4 provides an addressing capability of approximately 4.3 billion addresses. The Internet Protocol version 6 (IPv6) is more advanced and has better features compared to IPv4. It has the capability to provide an infinite number of addresses.
Before jumping into details, there are a few key features IPv6 incorporates: IPv6 uses 128-bit (2128) addresses, allowing 3.4 x 1038 unique IP addresses. This is equal to 340 trillion trillion trillion IP addresses.
Will IPv6 addresses run out eventually? In practical terms, no. There are 2^128 or 340 trillion, trillion, trillion IPv6 addresses, which is more than 100 times the number of atoms on the surface of the Earth. This will be more than sufficient to support trillions of Internet devices for the forseeable future.
The primary function of IPv6 is to allow for more unique TCP/IP address identifiers to be created, now that we've run out of the 4.3 billion created with IPv4. This is one of the main reasons why IPv6 is such an important innovation for the Internet of Things (IoT).
As @JoachimPileborg mentioned in comments, the problem is that your server code is not opening listening sockets for every address that getaddrinfo()
gives you. You are specifying AF_UNSPEC
in your simpleServer
struct, so getaddrinfo()
will give you a list of both IPv4 and IPv6 addresses. You are creating a listening socket for only the first entry in that list, which happens to be an IPv4 address. That is why your IPv4 client succeeds and your IPv6 client fails. You need to loop through the list creating a separate listening socket for each entry.
You are also making other mistakes, like not resetting your clientNameLength
variable each time you call accept()
, using addrinfo
as your clientName
buffer when you should be using sockaddr_storage
instead, and not paying attention to the return value of read()
.
Try something more like this:
include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <poll.h>
#define MAX_SERVERS 10
#define MAX_CLIENTS 50
#define MAX_SOCKETS (MAX_SERVERS + MAX_CLIENTS)
int main(int argc, char *argv[])
{
int simpleSocket, simplePort, returnStatus, n, m;
char buffer[1024];
pollfd simpleSockets[MAX_SOCKETS];
int numSockets = 0, numServers = 0;
struct addrinfo simpleServer, *res, *addr;
if (2 != argc)
{
fprintf(stderr, "Usage: %s <port>\n", argv[0]);
exit(1);
}
simplePort = atoi(argv[1]);
memset(&simpleServer, 0, sizeof simpleServer);
simpleServer.ai_family = AF_UNSPEC; // use IPv4 and/or IPv6, whatever is available
simpleServer.ai_socktype = SOCK_STREAM;
simpleServer.ai_flags = AI_PASSIVE; // fill in my IP for me
if (0 != getaddrinfo(NULL, argv[1], &simpleServer, &res))
{
fprintf(stderr, "getaddrinfo failed\n");
exit(1);
}
addr = res;
while (NULL != addr)
{
simpleSocket = socket(res->ai_family, addr->ai_socktype, addr->ai_protocol);
if (-1 == simpleSocket)
{
fprintf(stderr, "socket failed\n");
}
else
{
returnStatus = bind(simpleSocket, addr->ai_addr, addr->ai_addrlen);
if (0 == returnStatus)
returnStatus = listen(simpleSocket, 5);
if (0 == returnStatus)
{
simpleSockets[numSockets].fd = simpleSocket;
simpleSockets[numSockets].events = POLLIN;
simpleSockets[numSockets].revents = 0;
++numSockets;
++numServers;
if (MAX_SERVERS == numServers)
break;
}
else
{
fprintf(stderr, "bind/listen failed\n");
close(simpleSocket);
}
}
addr = addr->next;
}
freeaddrinfo(res);
if (0 == numServers)
{
fprintf(stderr, "no servers are listening\n");
exit(1);
}
struct sockaddr_storage clientName;
int clientNameLength;
while (1)
{
returnStatus = poll(simpleSockets, numSockets, -1);
if (-1 == returnStatus)
{
fprintf(stderr, "poll failed\n");
exit(1);
}
if (0 == returnStatus)
continue;
for (n = 0; n < numSockets; ++n)
{
if (simpleSockets[n].revents & POLLIN)
{
if (n < numServers)
{
clientNameLength = sizeof(clientName);
simpleSocket = accept(simpleSockets[n].fd, (struct sockaddr *)&clientName, &clientNameLength);
if (-1 == simpleSocket)
{
fprintf(stderr, "accept failed\n");
continue;
}
for (m = numServers; m < numSockets; ++m)
{
if (-1 == simpleSockets[m].fd)
{
simpleSockets[m].fd = simpleSocket;
simpleSockets[m].events = POLLIN;
simpleSockets[m].revents = 0;
simpleSocket = -1;
break;
}
}
if ((-1 != simpleSocket) && (MAX_SOCKETS > numSockets))
{
simpleSockets[numSockets].fd = simpleSocket;
simpleSockets[numSockets].events = POLLIN;
simpleSockets[numSockets].revents = 0;
++numSockets;
simpleSocket = -1;
}
if (-1 != simpleSocket)
{
fprintf(stderr, "Too many clients connected\n");
close(simpleSocket);
}
else
fprintf(stdout, "Client connected\n");
}
else
{
returnStatus = read(simpleSockets[n].fd, buffer, sizeof(buffer));
if (0 >= returnStatus)
{
if (0 > returnStatus)
fprintf(stdout, "Client error, disconnecting\n");
else
fprintf(stdout, "Client disconnected\n");
close(simpleSockets[n].fd);
simpleSockets[n].fd = -1;
simpleSockets[n].events = 0;
simpleSockets[n].revents = 0;
continue;
}
fprintf(stdout, "Message: %.*s\n", returnStatus, buffer);
write(simpleSockets[n].fd, buffer, returnStatus);
}
}
if (simpleSockets[n].revents & (POLLERR|POLLHUP|POLLNVAL))
{
if (simpleSockets[n].revents & POLLHUP)
fprintf(stdout, "Client disconnected\n");
else if (n >= numServers)
fprintf(stdout, "Client error, disconnecting\n");
else
fprintf(stdout, "Server error, closing\n");
close(simpleSockets[n].fd);
simpleSockets[n].fd = -1;
simpleSockets[n].events = 0;
simpleSockets[n].revents = 0;
}
}
}
for (n = 0; n < numSockets; ++n)
{
if (-1 != simpleSockets[n].fd)
close(simpleSockets[n].fd);
}
return 0;
}
With that said, if you are running on a platform that supports dual-stack sockets, your server does not have to create any IPv4 listening sockets at all. It can create IPv6 sockets only, and then disable their IPV6_V6ONLY
option. This will allow them to accept both IPv4 and IPv6 clients. The client address returned by accept()
will tell you whether an IPv4 or IPv6 client has connected.
memset(&simpleServer, 0, sizeof simpleServer);
simpleServer.ai_family = AF_INET6;
simpleServer.ai_socktype = SOCK_STREAM;
simpleServer.ai_flags = AI_PASSIVE;
if (0 != getaddrinfo(NULL, argv[1], &simpleServer, &res))
{
fprintf(stderr, "getaddrinfo failed\n");
exit(1);
}
addr = res;
while (NULL != addr)
{
simpleSocket = socket(res->ai_family, addr->ai_socktype, addr->ai_protocol);
if (-1 == simpleSocket)
{
fprintf(stderr, "socket failed\n");
}
else
{
n = 0;
returnStatus = setsockopt(simpleSocket, IPPROTO_IPV6, IPV6_V6ONLY, &n, sizeof(n));
...
}
addr = addr->next;
}
...
the client.c code is flawed, it's storing the complete addrinfo in sockaddr_in structure.
res is of type addrinfo
(http://man7.org/linux/man-pages/man3/getaddrinfo.3.html)
struct addrinfo {
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
socklen_t ai_addrlen;
struct sockaddr *ai_addr;
char *ai_canonname;
struct addrinfo *ai_next;
};
so instead ofstruct sockaddr_in *addr_in = (struct sockaddr_in *)res;
it should be
struct sockaddr_in *addr_in = (struct sockaddr_in *)res->ai_addr;
like
case AF_INET: {
struct sockaddr_in *addr_in = (struct sockaddr_in *)res->ai_addr;
s = malloc(INET_ADDRSTRLEN);
inet_ntop(AF_INET, &(addr_in->sin_addr), s, INET_ADDRSTRLEN);
returnStatus = connect(simpleSocket, res->ai_addr, res->ai_addrlen);
break;
}
case AF_INET6: {
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)res->ai_addr;
s = malloc(INET6_ADDRSTRLEN);
inet_ntop(AF_INET6, &(addr_in6->sin6_addr), s, INET6_ADDRSTRLEN);
returnStatus = connect(simpleSocket, res->ai_addr, res->ai_addrlen);
break;
}
after this modification the code works as expected.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With