csocketsgetsockopt

Why getsockopt optlen is zero?


I use nonblocking sockets and event library with it. I just noticed when I call connect rapidly to my local ip's ports randomly getsockopt(fd, SOL_SOCKET, SO_ERROR, &val, &optlen);'s optlen becomes 0.

I tried to make smaller code that's able to show problem. I used epoll in this code but same problem happens in other event libraries too.

#define nconnect_d 500
#define ebuffer_d 64

#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>

struct sockaddr_in initaddr(uint32_t ip, uint16_t port){
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = htonl(ip);
    memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
    return addr;
}

int getconnectfd(uint32_t sip, uint16_t sport, uint32_t dip, uint16_t dport){
    int fd;
    if((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
        return -1;
    if(setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (int[]){1}, sizeof(int)) == -1)
        return -1;
    if(fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) == -1)
        return -1;
    struct sockaddr_in saddr = initaddr(sip, sport);
    if(bind(fd, (const struct sockaddr *)&saddr, (socklen_t)sizeof(struct sockaddr_in)) == -1)
        return -1;
    struct sockaddr_in daddr = initaddr(dip, dport);
    if(connect(fd, (const struct sockaddr*)&daddr, (socklen_t)sizeof(struct sockaddr_in)) == -1 && errno != EINPROGRESS)
        return -1;
    return fd;
}

int epolltouch(int efd, int sfd, uint32_t flag){
    struct epoll_event event;
    event.data.fd = sfd;
    event.events = flag;
    if(epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &event) == -1)
        return -1;
    return 0;
}

int epollrm(int efd, int sfd){
    if(epoll_ctl(efd, EPOLL_CTL_DEL, sfd, NULL) == -1)
        return -1;
    return 0;
}

int main(){
    int efd = epoll_create1(0);
    assert(efd != -1);
    for(uint32_t i = 0; i < nconnect_d; i++){
        int sfd = getconnectfd(INADDR_ANY, 12420, 0x7f000001, 2048 + i);
        assert(sfd != -1);
        epolltouch(efd, sfd, EPOLLOUT | EPOLLET);
    }
    struct epoll_event events[ebuffer_d];
    uint32_t iconnect = nconnect_d;
    while(iconnect){
        int n = epoll_wait(efd, events, ebuffer_d, -1);
        assert(n != -1);
        for(uint32_t i = 0; i < n; i++){
            int evfd = events[i].data.fd;
            int opt, optlen;
            assert(getsockopt(evfd, SOL_SOCKET, SO_ERROR, &opt, &optlen) != -1);
            assert(optlen == sizeof(int));
            switch(opt){
                case 0:{
                    /* connection has been established */
                    break;
                }
                case ECONNREFUSED:
                case EHOSTUNREACH:
                case ENETUNREACH:
                /* can be more valid case isn't it? */
                {
                    /* connection has failed */
                    break;
                }
                default:{
                    assert(0);
                }
            }
            iconnect--;
            epollrm(efd, evfd);
            close(evfd);
        }
    }
    return 0;
}

When I run;

$ ./a.out
a.out: temp.c:72: main: Assertion `optlen == sizeof(int)' failed.
Aborted (core dumped)

Is optlen 0 means socket is closed with some reason? Do we need to close socket after it?


Solution

  • Calling getsockopt() requires that you provide the buffer to the system, and that means you have to set the optlen before the call:

    int opt;
    socklen_t optlen = sizeof opt;
    
    ... getsockopt(evfd, SOL_SOCKET, SO_ERROR, &opt, &optlen);
    

    This way, getsockopt() knows how much it's allowed to write into the "buffer", which in this case is just an integer, and it updates optlen with the number of bytes it actually wrote.

    The man page notes this that optlen is a value/result, which means you set it going in and expect to get another value back, but opt is a result only so doesn't care what the value going in is.

    EDIT fixed the type of optlen to socklen_t, with h/t to prog-fh