Reputation: 21
I'm trying to run a simple C program which uses the bind() function to bind a IPv4/IPv6 address to a socket.
Below is the code:
int main(int argc, char *argv[])
{
int socket_fd = -1, addrlen = 0, af;
struct sockaddr_storage addr = {0};
unsigned connections = 0;
pthread_t workers[WORKER_NUM] = { 0 };
int client_sock_fds[WORKER_NUM] = { 0 };
char ip_string[64];
if (argc > 1 && strcmp(argv[1], "inet6") == 0) {
af = AF_INET6;
init_sockaddr_inet6((struct sockaddr_in6 *)&addr);
}
else {
af = AF_INET;
init_sockaddr_inet((struct sockaddr_in *)&addr);
}
printf("[Server] Create socket\n");
socket_fd = socket(af, SOCK_STREAM, 0);
if (socket_fd < 0) {
perror("Create socket failed");
goto fail;
}
printf("[Server] Bind socket\n");
addrlen = sizeof(addr);
if (bind(socket_fd, (struct sockaddr *)&addr, addrlen) < 0) {
perror("Bind failed");
goto fail;
}
printf("[Server] Listening on socket\n");
if (listen(socket_fd, 3) < 0) {
perror("Listen failed");
goto fail;
}
printf("[Server] Wait for clients to connect ..\n");
while (connections < WORKER_NUM) {
client_sock_fds[connections] =
accept(socket_fd, (struct sockaddr *)&addr, (socklen_t *)&addrlen);
if (client_sock_fds[connections] < 0) {
perror("Accept failed");
break;
}
if (sockaddr_to_string((struct sockaddr *)&addr, ip_string,
sizeof(ip_string) / sizeof(ip_string[0]))
!= 0) {
printf("[Server] failed to parse client address\n");
goto fail;
}
printf("[Server] Client connected (%s)\n", ip_string);
if (pthread_create(&workers[connections], NULL, run,
&client_sock_fds[connections])) {
perror("Create a worker thread failed");
shutdown(client_sock_fds[connections], SHUT_RDWR);
break;
}
connections++;
}
if (connections == WORKER_NUM) {
printf("[Server] Achieve maximum amount of connections\n");
}
for (int i = 0; i < WORKER_NUM; i++) {
pthread_join(workers[i], NULL);
}
printf("[Server] Shuting down ..\n");
shutdown(socket_fd, SHUT_RDWR);
sleep(3);
printf("[Server] BYE \n");
return EXIT_SUCCESS;
fail:
printf("[Server] Shuting down ..\n");
if (socket_fd >= 0)
close(socket_fd);
sleep(3);
return EXIT_FAILURE;
}
static void
init_sockaddr_inet(struct sockaddr_in *addr)
{
/* 0.0.0.0:1234 */
addr->sin_family = AF_INET;
addr->sin_port = htons(1234);
addr->sin_addr.s_addr = htonl(INADDR_ANY);
}
static void
init_sockaddr_inet6(struct sockaddr_in6 *addr)
{
/* [::]:1234 */
addr->sin6_family = AF_INET6;
addr->sin6_port = htons(1234);
addr->sin6_addr = in6addr_any;
}
When this program is compiled and run on Linux it works without any errors. But when the same program is compiled and run on MacOS, the bind() function returns an "Invalid Argument" error.
I checked the man page for the bind() function to see what the possible reasons for this error could be. The three possible reasons were:
addrlen
value passed to the function is incorrectaddr
is not a valid address for this socket's domainI was able to verify that it wasn't due to the first and third reason.
My question would be as to why does the addrlen
value passed to the bind() function throw an "Incorrect argument" error when ran on a Unix system but works perfectly fine when ran on a Linux system?
Upvotes: 2
Views: 1151
Reputation: 596948
The addrlen
value you pass to bind()
must exactly match the address family you specify in socket()
. That means addrlen
must be set to sizeof(sockaddr_in)
for AF_INET
, and sizeof(sockaddr_in6)
for AF_INET6
. Using sizeof(sockaddr_storage)
is the wrong value, as sockaddr_storage
is designed to be large enough to hold all possible sockaddr_...
types, so its size may be larger than sockaddr_in6
.
On the other hand, when calling accept()
, you need to set addrlen
to the full size of addr
beforehand, so it knows how much memory it has to work with when writing the client's address to addr
. addrlen
will be adjusted to the actual size written. However, you can't simply type-cast an int*
into a socklen_t*
, so addrlen
needs to be an actual socklen_t
type. Besides, bind()
is expecting a socklen_t
anyway, not an int
.
Try something more like this instead:
int main(int argc, char *argv[])
{
int socket_fd = -1, af;
socklen_t addrlen; // <-- add this!
struct sockaddr_storage addr = {0};
unsigned connections = 0;
pthread_t workers[WORKER_NUM] = { 0 };
int client_sock_fds[WORKER_NUM] = { 0 };
char ip_string[64];
if (argc > 1 && strcmp(argv[1], "inet6") == 0) {
af = AF_INET6;
addrlen = sizeof(struct sockaddr_in6); // <-- add this!
init_sockaddr_inet6((struct sockaddr_in6 *)&addr);
}
else {
af = AF_INET;
addrlen = sizeof(struct sockaddr_in); // <-- add this!
init_sockaddr_inet((struct sockaddr_in *)&addr);
}
printf("[Server] Create socket\n");
socket_fd = socket(af, SOCK_STREAM, 0);
if (socket_fd < 0) {
perror("Create socket failed");
goto fail;
}
printf("[Server] Bind socket\n");
if (bind(socket_fd, (struct sockaddr *)&addr, addrlen) < 0) {
perror("Bind failed");
goto fail;
}
printf("[Server] Listening on socket\n");
if (listen(socket_fd, 3) < 0) {
perror("Listen failed");
goto fail;
}
printf("[Server] Wait for clients to connect ..\n");
while (connections < WORKER_NUM) {
addrlen = sizeof(addr); // <-- add this!
client_sock_fds[connections] =
accept(socket_fd, (struct sockaddr *)&addr, &addrlen);
if (client_sock_fds[connections] < 0) {
perror("Accept failed");
break;
}
if (sockaddr_to_string((struct sockaddr *)&addr, ip_string,
sizeof(ip_string) / sizeof(ip_string[0])) != 0) {
printf("[Server] failed to parse client address\n");
goto fail;
}
printf("[Server] Client connected (%s)\n", ip_string);
if (pthread_create(&workers[connections], NULL, run,
&client_sock_fds[connections]) != 0) {
perror("Create a worker thread failed");
shutdown(client_sock_fds[connections], SHUT_RDWR);
break;
}
connections++;
}
if (connections == WORKER_NUM) {
printf("[Server] Achieve maximum amount of connections\n");
}
for (int i = 0; i < connections; i++) { // <-- needs to be the actual thread count, not WORKER_NUM!
pthread_join(workers[i], NULL);
}
printf("[Server] Shuting down ..\n");
shutdown(socket_fd, SHUT_RDWR);
sleep(3);
printf("[Server] BYE \n");
return EXIT_SUCCESS;
fail:
printf("[Server] Shuting down ..\n");
if (socket_fd >= 0)
close(socket_fd);
sleep(3);
return EXIT_FAILURE;
}
That being said, you should use getaddrinfo()
instead to initialize the sockaddr_...
that you pass to bind()
. You should not be initializing it manually at all.
...
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int main(int argc, char *argv[])
{
int socket_fd = -1, res;
...
struct addrinfo hints = { 0 };
struct addrinfo *addrs = NULL;
if (argc > 1 && strcmp(argv[1], "inet6") == 0)
hints.ai_family = AF_INET6;
else
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
printf("[Server] Initializing socket address\n");
res = getaddrinfo(NULL, "1234", &hints, &addrs);
if (res != 0) {
fprintf(stderr, "getaddrinfo failed: %s\n", gai_strerror(res));
goto fail;
}
printf("[Server] Create socket\n");
socket_fd = socket(addrs->ai_family, addrs->ai_socktype, addrs->ai_protocol);
if (socket_fd < 0) {
perror("Create socket failed");
freeaddrinfo(addrs);
goto fail;
}
printf("[Server] Bind socket\n");
if (bind(socket_fd, addrs->ai_addr, addrs->ai_addrlen) < 0) {
perror("Bind failed");
freeaddrinfo(addrs);
goto fail;
}
freeaddrinfo(addrs);
...
}
Upvotes: 2