Why getsockopt optlen is zero?

353 views Asked by At

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?

2

There are 2 answers

0
Steve Friedl On BEST ANSWER

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

0
prog-fh On

When using getsockopt() the last parameter must be the address of an initialised socklen_t. Before the call you should initialise it with the size of the storage to obtain the resulting value (sizeof(opt) here). It is passed by address because if this storage is too large for the actual result, then the system call adjusts it to this exact size.