I'm trying to learn socket programming in C, I want to create a server that manages a chat room, to which clients can connect and send messages to everyone else
Here's the server code: (You can skip it to get to the snippet of the important part of the code)
/* Server that manages a chat room */
// include necessary libraries
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/select.h>
#include <netdb.h>
// definitions
// function declarations
void printaddrs(struct addrinfo *res);
void printusage();
int isdig(char c);
int isaddr(char addr[INET6_ADDRSTRLEN]);
int isport(char port[6]);
char *strin(FILE *stream);
int getaddrfamily(char addr[INET6_ADDRSTRLEN]);
int main(int argc, char *argv[]) {
// argv[1] -> address for the server to bind to, if it is 0, print a list of addrs with getaddrinfo()
// argv[2] -> port for the server to bind to
// ~~~~~~~~~~~~ Argument validity checking - Start~~~~~~~~~~~~
if (argc < 3) {
printf("Not enough arguments\n\n");
printusage();
return 1;
}
if (argc > 3) {
printf("Too many arguments\n\n");
printusage();
return 1;
}
if (strlen(argv[1]) > INET6_ADDRSTRLEN) {
printf("IP address too long\n");
return 1;
}
if (isaddr(argv[1]) == 0) { // is not a valid IPv4 or IPv6 address
printf("Invalid <ip addr> argument\n\n");
printusage();
return 1;
}
if (strlen(argv[2]) > 6) {
printf("Port number too long\n");
return 1;
}
if (isport(argv[2]) == 0) { //
printf("Invalid <port> argument\n\n");
printusage();
return 1;
}
if (strcmp(argv[2], "0") == 0) {
printf("Argument <port> cannot be 0\n");
return 1;
}
if (strtol(argv[2], NULL, 10) < 1024) {
printf("CAUTION!\nBe cautious when using reserved port numbers\n");
}
// ~~~~~~~~~~~~ Argument validity checking - End ~~~~~~~~~~~~
struct addrinfo hints, *res;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // Use both IPv4 and IPv6
hints.ai_socktype = SOCK_STREAM; // Use TCP/IP (not UDP)
char addrstr[INET6_ADDRSTRLEN] = "";
if (strcmp(argv[1], "0") == 0) { // User entered '0' as the first argument
int status = getaddrinfo(NULL, argv[2], &hints, &res);
if (status != 0) {
printf("getaddrinfo(): error: %s\n", gai_strerror(status));
return 1;
}
printaddrs(res);
printf("Select an address to bind to: ");
char *id = strin(stdin);
int count = 0;
for (; res != NULL; res = res->ai_next) {
if (strtol(id, NULL, 10) == count) {
if (res->ai_family == AF_INET) {
struct sockaddr_in *ipv4;
ipv4 = (struct sockaddr_in *)res->ai_addr;
inet_ntop(res->ai_family, &(ipv4->sin_addr), addrstr, sizeof(addrstr));
} else if (res->ai_family == AF_INET6) {
struct sockaddr_in6 *ipv6;
ipv6 = (struct sockaddr_in6 *)res->ai_addr;
inet_ntop(res->ai_family, &(ipv6->sin6_addr), addrstr, sizeof(addrstr));
} else {
printf("error: Choosing an address of an invalid address family is not allowed\n");
printf("Terminating...\n");
free(id);
freeaddrinfo(res);
return 1;
}
break;
}
count++;
}
free(id);
if (res == NULL) {
printf("error: Couldn't find entered id\n");
freeaddrinfo(res);
return 1;
}
} else { // User entered a specific IP address
strncpy(addrstr, argv[1], 4);
int addrfamily = getaddrfamily(addrstr);
if (addrfamily == AF_INET) {
struct sockaddr_in *ipv4;
inet_pton(AF_INET, addrstr, &(ipv4->sin_addr));
res->ai_addr = (struct sockaddr *)ipv4;
res->ai_family = AF_INET;
} else if (addrfamily == AF_INET6) {
struct sockaddr_in6 *ipv6;
printf("check1\n");
inet_pton(AF_INET6, addrstr, &(ipv6->sin6_addr));
printf("check2\n");
res->ai_addr = (struct sockaddr *)ipv6;
res->ai_family = AF_INET6;
} else {
printf("error: Invalid address\n");
freeaddrinfo(res);
return 1;
}
}
printf("Server address: %s\n", addrstr);
printf("Server port: %s\n", argv[2]);
printf("Success!\nTerminating...\n");
freeaddrinfo(res);
return 0;
}
// ~~~~~~~~~~~~ Function definitions ~~~~~~~~~~~~
// prints available addresses to possibly bind to
void printaddrs(struct addrinfo *res) {
int id = 0;
char addrstr[INET6_ADDRSTRLEN];
char ipver[30];
for (struct addrinfo *p = res; p != NULL; p = p->ai_next) {
if (p->ai_family == AF_INET) {
struct sockaddr_in *ipv4;
ipv4 = (struct sockaddr_in *)p->ai_addr;
inet_ntop(AF_INET, &(ipv4->sin_addr), addrstr, sizeof(addrstr));
strcpy(ipver, "ipv4");
} else if (p->ai_family == AF_INET6) {
struct sockaddr_in6 *ipv6;
ipv6 = (struct sockaddr_in6 *)p->ai_addr;
inet_ntop(AF_INET6, &(ipv6->sin6_addr), addrstr, sizeof(addrstr));
strcpy(ipver, "ipv6");
} else {
strcpy(addrstr, "error: Invalid address family");
strcpy(ipver, "error: Invalid address family");
}
printf("%d|%s\t%s\n", id, ipver, addrstr);
id++;
}
}
// prints the 'usage:' message
void printusage() {
printf("usage:\n./server <ip addr> <port>\n");
printf("<ip addr> - IPv4 or IPv6 address for the server to bind to (only dots or colons allowed)\n");
printf("<port> - Port number for the server to bind to\n");
}
// Returns 1 if character c is a digit (0-9)
int isdig(char c) {
switch(c) {
case '0':
return 1;
case '1':
return 1;
case '2':
return 1;
case '3':
return 1;
case '4':
return 1;
case '5':
return 1;
case '6':
return 1;
case '7':
return 1;
case '8':
return 1;
case '9':
return 1;
default:
return 0;
}
}
// Returns 1 if addr is a valid IPv4 or IPv6 address, otherwise returns 0
int isaddr(char addr[INET6_ADDRSTRLEN]) {
int isipv4 = 0, isipv6 = 0;
for (int i = 0; i < strlen(addr); i++) {
// If both dots and colons are present in addrm return 0
if (isipv4 && isipv6)
return 0;
if (addr[i] == '.')
isipv4 = 1;
else if (addr[i] == ':')
isipv6 = 1;
// if character is something other than a digit, dot or colon
if (isdig(addr[i]) == 0 && addr[i] != '.' && addr[i] != ':')
return 0;
}
return 1;
}
// Returns 1 if number(5 digits) is a valid port number, returns 0 if it's not
int isport(char port[6]) {
for (int i = 0; i < strlen(port); i++) {
if (isdig(port[i]) == 0)
return 0;
}
return 1;
}
// Takes string input from FILE *stream - !!! result must be free()'d !!!
char *strin(FILE *stream) {
char *str = NULL;
size_t size = 0;
int c;
int i = 0;
while (1) {
c = getc(stream);
if (stream != stdin) {
if(c == EOF)
break;
} else {
if(c == EOF || c =='\n')
break;
}
size++;
str = realloc(str, size * sizeof(char));
*(str + i) = c;
i++;
}
if (size == 0) {
return NULL;
}
str[size] = '\0';
return str;
}
// Returns the address family of a given valid IP address, or -1 if no dots or colons are present
int getaddrfamily(char addr[INET6_ADDRSTRLEN]) {
for (int i = 0; i < strlen(addr); i++) {
if (addr[i] == '.')
return AF_INET;
else if (addr[i] == ':')
return AF_INET6;
}
return -1;
}
But this is the important part:
else { // User entered a specific IP address
strcpy(addrstr, argv[1]);
int addrfamily = getaddrfamily(addrstr);
if (addrfamily == AF_INET) {
struct sockaddr_in *ipv4;
inet_pton(AF_INET, addrstr, &(ipv4->sin_addr));
res->ai_addr = (struct sockaddr *)ipv4;
res->ai_family = AF_INET;
} else if (addrfamily == AF_INET6) {
struct sockaddr_in6 *ipv6;
printf("check1\n");
inet_pton(AF_INET6, addrstr, &(ipv6->sin6_addr));
printf("check2\n");
res->ai_addr = (struct sockaddr *)ipv6;
res->ai_family = AF_INET6;
} else {
printf("error: Invalid address\n");
freeaddrinfo(res);
return 1;
}
}
If the user enters 0 as the first argument, a list of possible addresses gets printed out and the user can choose one, but I wanted it to be possible to specify an address as the argument, but when I try to copy it (argv[1]) into the addrstr string, it throws segmentation fault...
I tried using strncpy instead, got the same result, however I notices that when I only specify to copy 3 characters (in case of 127.0.0.1 only the "127" part) it works like charm, but it throws segmentation fault again when I want to copy 4 characters... Could the dot be the problem here?
TL;DR:
When I try to copy the argument argv[1]
into a string addrstr
previously initialized as char addrstr[INET6_ADDRSTRLEN] = "";
, it throws segmentation fault.
Answers
The segmentation fault you're encountering when copying argv[1]
into addrstr
is likely due to memory corruption or accessing uninitialized memory. Let's debug this issue and find a solution.
Based on your description, it seems that addrstr
is initialized properly, but the issue arises when trying to copy argv[1]
into it. Here are a few things to check:
-
Ensure
argv[1]
is valid: Before copyingargv[1]
, make sure it contains a valid IP address. -
Check the length of
argv[1]
: Ensure thatargv[1]
doesn't exceed the size ofaddrstr
to avoid buffer overflow. -
Debugging the copy operation: You mentioned that copying only a part of
argv[1]
(e.g., the first three characters) works fine. This indicates that the issue might be related to the length of the string or the presence of special characters like dots.
Let's modify the code snippet to include error-checking and debugging statements:
else { // User entered a specific IP address
// Debugging: Print the length of argv[1] and its content
printf("Length of argv[1]: %zu\n", strlen(argv[1]));
printf("Content of argv[1]: %s\n", argv[1]);
strncpy(addrstr, argv[1], INET6_ADDRSTRLEN - 1); // Ensure null-termination
addrstr[INET6_ADDRSTRLEN - 1] = '\0'; // Ensure null-termination
// Debugging: Print the copied string
printf("Copied address: %s\n", addrstr);
int addrfamily = getaddrfamily(addrstr);
if (addrfamily == AF_INET) {
struct sockaddr_in ipv4;
inet_pton(AF_INET, addrstr, &(ipv4.sin_addr));
res->ai_addr = (struct sockaddr *)&ipv4;
res->ai_family = AF_INET;
} else if (addrfamily == AF_INET6) {
struct sockaddr_in6 ipv6;
inet_pton(AF_INET6, addrstr, &(ipv6.sin6_addr));
res->ai_addr = (struct sockaddr *)&ipv6;
res->ai_family = AF_INET6;
} else {
printf("error: Invalid address\n");
freeaddrinfo(res);
return 1;
}
}
With these modifications, you'll be able to check the length and content of argv[1]
, as well as the copied string (addrstr
). This should help identify any issues with the string copying process and debug the segmentation fault.