:inet.peername failure

troubleshooting
Tags: #<Tag:0x00007f8ea0e1f650>

#1

I am using the :inet.peername function. Most of the time it returns {:ok, {ip, port}} but sometimes it fails with {:error, :einval}.

Under which conditions do the failure happens? Can I do something to avoid them?

I have looked at the implementation of the function, it is C code. but while I have a good understanding of C the function is rather complex and not intelligible for me.

The code of Erlang is using a port:

    case INET_REQ_PEER:  {      /* get peername */
    char tbuf[sizeof(inet_address)];
    inet_address peer;
    inet_address* ptr;
    unsigned int sz;

    DEBUGF(("inet_ctl(%ld): PEER\r\n", (long)desc->port)); 

    if (!(desc->state & INET_F_ACTIVE))
        return ctl_error(ENOTCONN, rbuf, rsize);
    if ((ptr = desc->peer_ptr) != NULL) {
        sz = desc->peer_addr_len;
    }
    else {
        ptr = &peer;
            sz = sizeof(peer);
            sys_memzero((char *) &peer, sz);
        if (IS_SOCKET_ERROR
        (sock_peer
         (desc->s, (struct sockaddr*)ptr, &sz)))
        return ctl_error(sock_errno(), rbuf, rsize);
    }
    if (inet_get_address(tbuf, ptr, &sz) < 0)
        return ctl_error(EINVAL, rbuf, rsize);
    return ctl_reply(INET_REP_OK, tbuf, sz, rbuf, rsize);
    }
static int inet_get_address(char* dst, inet_address* src, unsigned int* len)
{
    /* Compare the code with inet_address_to_erlang() */
    int family;
    short port;

    family = src->sa.sa_family;
    if ((family == AF_INET) && (*len >= sizeof(struct sockaddr_in))) {
    dst[0] = INET_AF_INET;
    port = sock_ntohs(src->sai.sin_port);
    put_int16(port, dst+1);
    sys_memcpy(dst+3, (char*)&src->sai.sin_addr, sizeof(struct in_addr));
    *len = 3 + sizeof(struct in_addr);
    return 0;
    }
#if defined(HAVE_IN6) && defined(AF_INET6)
    else if ((family == AF_INET6) && (*len >= sizeof(struct sockaddr_in6))) {
    dst[0] = INET_AF_INET6;
    port = sock_ntohs(src->sai6.sin6_port);
    put_int16(port, dst+1);
    sys_memcpy(dst+3, (char*)&src->sai6.sin6_addr,sizeof(struct in6_addr));
    *len = 3 + sizeof(struct in6_addr);
    return 0;
    }
#endif
#ifdef HAVE_SYS_UN_H
    else if (family == AF_UNIX) {
        size_t n, m;
        if (*len < offsetof(struct sockaddr_un, sun_path)) return -1;
        n = *len - offsetof(struct sockaddr_un, sun_path);
        if (255 < n) return -1;
        m = my_strnlen(src->sal.sun_path, n);
#ifdef __linux__
    /* Assume that the address is a zero terminated string,
         * except when the first byte is \0 i.e the string length is 0,
         * then use the reported length instead.
     * This fix handles Linux's nonportable
         * abstract socket address extension.
     */
    if (m == 0)  m = n;
#endif
        dst[0] = INET_AF_LOCAL;
        dst[1] = (char) ((unsigned char) m);
        sys_memcpy(dst+2, src->sal.sun_path, m);
        *len = 1 + 1 + m;
        return 0;
      }
#endif
    else if (family == AF_UNSPEC) {
        dst[0] = INET_AF_UNSPEC;
    *len = 1;
    }
    else {
        dst[0] = INET_AF_UNDEFINED;
    *len = 1;
    }
    return -1;
}

#2

:einval is the posix equivalent of erlangs :badarg.

It probably simply means, that whatever you passed to :inet.peername is not what is expected.

But since you didn’t show anything of the caller, we can not tell for sure. I do not think that there is a bug in this year old, well riped NIF :wink:


#3

Ok so I am calling it like this:


  def init(ref, socket, transport, sender) do
    Logger.info("Starting handler")
    :ok = :ranch.accept_ack(ref)
    :ok = transport.setopts(socket, [{:active, :once}])
    :gen_server.enter_loop(__MODULE__, [], %State{
      socket: socket,
      buffer: <<>>,
    })
  end


  def handle_info(
        {:tcp, socket, data}, state = %State{buffer: buffer}
      ) do
    # Allow the socket to send us the next message
    :inet.setopts(state.socket, active: :once)

    case :inet.peername(socket) do

#4

I’m not sure where the message comes from, but I just assume it’s correct that you get a socket there.

Can you log and inspect the socket on the :einval case? Maybe that gives some hints?


#5

Logging the socket just returns something like #Port<0.11103>.


#6

I must say this occurs rarely but since I am processing a lot of connections with the server it happens several times a day.


#7

Messages come from :gen_tcp!


#8

Is the socket alive when you call peername on it? Either peername is being called too soon (before full setup), or too late (socket death/closed)?


#9

I would expect :gen_tcp to not call handle_info before the process is ready to work with the socket.

How can I check if the socket is alive or closed?


#10

Hmm, unsure, depends on the message you are receiving at that time, though it looks like the :tcp 3-tuple, so maybe it’s dying by the time you call it (remember the socket process is doing it’s own stuff too, it could die if the connection dies while you are processing messages that it sent out).

As for testing if it’s alive, well that’s questionable too as it could easily die between the time you test it and when you use it’s messages.

If you can find some way to make a minimal reproduceable test I’d love to run it locally to start poking in to it. :slight_smile:


#11

I am not sure how to reproduce it. I may give it a try when I have more time. I am using ranch and Elixir.

I think your hypothesis is true: the socket is dead. I could observe that because I had a log showing the IP could not resolved, followed by a log indicating the TCP handler was being closed. And this twice.

I cannot confirm that it was also the case when posting the first message of this topic, because of the various paths of the code, but I see the longs as a strong indication.