Search code examples
clinuxsocketstcpclient-server

Connection refused with getaddrinfo on client side


I'm running a client and server code on Linux.

The server uses TCP/IP with AF_INET and SOCK_STREAM protocol. The code is the following:

void hostname_to_ip(char *hostname, int *connection)
{ 
    int check_sfd;
    struct addrinfo hints, *p, *servinfo;
    //struct sockaddr_in* ret_value;
 
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
 
    int res = getaddrinfo(hostname, NULL, &hints, &servinfo) 
    if (res != 0) {
        fprintf(stderr, "Error: error in getaddrinfo on hostname: %s\n", gai_strerror(s));
        exit(EXIT_FAILURE);
    }

    // getaddrinfo returned a linked list of relevant addresses
    // loop through the addresses and return the first one available
    for (p = servinfo; p != NULL; p = p->ai_next) {
        check_sfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
        if (check_sfd == -1)
            continue;
        
        if (connect(check_sfd, p->ai_addr, p->ai_addrlen) == 0) { // connection successful
        *connection = check_sfd;
        break;
        }
       
        close(check_sfd);
    }
    
    if (p == NULL) {
        // print error
        exit(EXIT_FAILURE);
     }

    freeaddrinfo(&servinfo);
}

However, now I can't seem to connect to the server, as connect always returns -1 (p starts as non-NULL, so the connection to the socket just fails). The error is connection refused.

When I change the ai_socktype to SOCK_DGRAM the connection is successful but the client fails later when sending the data (makes sense since the server is using SOCK_STREAM), which is pretty weird.

Any ideas? The code of the client and server is pretty long so I didn't attached all of it, but I tried to add all the relevant information. If anything is missing, comment and I'll add it.

This is the strace info on the client:

execve("./pcc_client", ["./pcc_client", "localhost", "2001", "1000"], [/* 71 vars */]) = 0
brk(NULL)                               = 0x2446000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=88205, ...}) = 0
mmap(NULL, 88205, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f0a0f4fa000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0a0f4f9000
mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f0a0ef21000
mprotect(0x7f0a0f0e1000, 2097152, PROT_NONE) = 0
mmap(0x7f0a0f2e1000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7f0a0f2e1000
mmap(0x7f0a0f2e7000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f0a0f2e7000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0a0f4f8000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0a0f4f7000
arch_prctl(ARCH_SET_FS, 0x7f0a0f4f8700) = 0
mprotect(0x7f0a0f2e1000, 16384, PROT_READ) = 0
mprotect(0x601000, 4096, PROT_READ)     = 0
mprotect(0x7f0a0f510000, 4096, PROT_READ) = 0
munmap(0x7f0a0f4fa000, 88205)           = 0
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
getsockname(3, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("0.0.0.0")}, [16]) = 0
socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 4
connect(4, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
close(4)                                = 0
socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 4
connect(4, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
close(4)                                = 0
brk(NULL)                               = 0x2446000
brk(0x2467000)                          = 0x2467000
open("/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 4
fstat(4, {st_mode=S_IFREG|0644, st_size=529, ...}) = 0
read(4, "# /etc/nsswitch.conf\n#\n# Example"..., 4096) = 529
read(4, "", 4096)                       = 0
close(4)                                = 0
open("/etc/host.conf", O_RDONLY|O_CLOEXEC) = 4
fstat(4, {st_mode=S_IFREG|0644, st_size=92, ...}) = 0
read(4, "# The \"order\" line is only used "..., 4096) = 92
read(4, "", 4096)                       = 0
close(4)                                = 0
getpid()                                = 12014
open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 4
fstat(4, {st_mode=S_IFREG|0644, st_size=184, ...}) = 0
read(4, "# Dynamic resolv.conf(5) file fo"..., 4096) = 184
read(4, "", 4096)                       = 0
close(4)                                = 0
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 4
fstat(4, {st_mode=S_IFREG|0644, st_size=88205, ...}) = 0
mmap(NULL, 88205, PROT_READ, MAP_PRIVATE, 4, 0) = 0x7f0a0f4fa000
close(4)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libnss_files.so.2", O_RDONLY|O_CLOEXEC) = 4
read(4, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260!\0\0\0\0\0\0"..., 832) = 832
fstat(4, {st_mode=S_IFREG|0644, st_size=47600, ...}) = 0
mmap(NULL, 2168600, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 4, 0) = 0x7f0a0ed0f000
mprotect(0x7f0a0ed1a000, 2093056, PROT_NONE) = 0
mmap(0x7f0a0ef19000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 4, 0xa000) = 0x7f0a0ef19000
mmap(0x7f0a0ef1b000, 22296, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f0a0ef1b000
close(4)                                = 0
mprotect(0x7f0a0ef19000, 4096, PROT_READ) = 0
munmap(0x7f0a0f4fa000, 88205)           = 0
open("/etc/hosts", O_RDONLY|O_CLOEXEC)  = 4
fstat(4, {st_mode=S_IFREG|0644, st_size=233, ...}) = 0
read(4, "127.0.0.1\tlocalhost\n127.0.1.1\tmi"..., 4096) = 233
read(4, "", 4096)                       = 0
close(4)                                = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
write(1, "x\n", 2x
)                      = 2
socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 4
write(1, "4\n", 24
)                      = 2
write(1, "\n", 1
)                       = 1
connect(4, {sa_family=AF_INET, sin_port=htons(53511), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 ECONNREFUSED (Connection refused)
close(4)                                = 0
write(1, "Error: couldn't connect to hostn"..., 56Error: couldn't connect to hostname. Connection refused
) = 56
exit_group(1)                           = ?
+++ exited with 1 +++

Thanks!


Solution

  • Your fix for the problem will work as long as the compiler cooperates and you don't need to support IPv6. But a better fix is to pass the port number to getaddrinfo in the first place, as the "service" argument. You have to convert it to a string to do that, but you can go back to using AF_UNSPEC and not having to cast or manipulate the sockaddrs in getaddrinfo's return list at all:

    void hostname_to_ip(char *hostname, int *connection_socket,
                        unsigned short port_number)
    {
        int check_sfd;
        struct addrinfo hints, *p, *servinfo;
        char port_number_s[sizeof("65535")];
    
        memset(&hints, 0, sizeof(struct addrinfo));
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_flags = AI_NUMERICSERV;
        hints.ai_protocol = 0;
    
        snprintf(port_number_s, sizeof port_number_s, "%u", port_number);
    
        int res = getaddrinfo(hostname, port_number_s, &hints, &servinfo);
        if (res == EAI_SYSTEM) {
            fprintf(stderr, "Error looking up %s: %s\n",
                    hostname, strerror(errno));
            exit(1);
        } else if (res != 0) {
            fprintf(stderr, "Error looking up %s: %s\n",
                    hostname, gai_strerror(res));
            exit(1);
        } else if (servinfo == NULL) {
            fprintf(stderr, "Error looking up %s: No addresses found\n",
                    hostname);
            exit(1);
        }
    
        // getaddrinfo returned a linked list of relevant addresses
        // loop through the addresses and return the first one we can connect to
        for (p = servinfo; p != NULL; p = p->ai_next) {
            check_sfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
            if (check_sfd == -1)
                continue;
    
            if (connect(check_sfd, p->ai_addr, p->ai_addrlen)) {
                *connection_socket = check_sfd;
                freeaddrinfo(servinfo);
                return;
            }
            close(check_sfd);
        }
        // If we get here, we couldn't connect to any of the addresses.
        fprintf(stderr, "Couldn't connect to %s: %s\n", hostname, strerror(errno));
        exit(1);
    }
    

    I also fixed some other subtle bugs:

    • port_number should be unsigned short because that's what it is in TCP.
    • If getaddrinfo returns EAI_SYSTEM, you need to print strerror(errno) instead of gai_strerror(res).
    • On some systems, getaddrinfo can claim to have succeeded but return zero addresses.
    • Always print the hostname of the peer you're trying to talk to, in error messages about connecting to the network. Same principle as always printing the filename when a file operation fails.
    • If you use an early return inside the loop when connect succeeds, you don't need to check again for success after the loop. It's not necessary to free memory when you're about to print an error message and exit.

    Follow-up exercises for you, in ascending order of difficulty:

    • It would be better to return -1 on failure or the open socket on success, instead of writing to an out-parameter or exiting the program.
    • Use getnameinfo to print "Connecting to 128.52.0.2..." messages like telnet does.
    • Use nonblocking sockets to connect to all the addresses at once, then use select or poll (your choice) to get the first one that works and close all the others.