esp-idf icon indicating copy to clipboard operation
esp-idf copied to clipboard

socketpair support for posix compliance (IDFGH-12794)

Open sramrajkar opened this issue 9 months ago • 4 comments

Answers checklist.

  • [X] I have read the documentation ESP-IDF Programming Guide and the issue is not addressed there.
  • [X] I have updated my IDF branch (master or release) to the latest version and checked that the issue is present there.
  • [X] I have searched the issue tracker for a similar issue and not found a similar issue.

General issue report

I am trying to compile the following library for ESP32S3 using 5.x.x branches https://github.com/libplctag/libplctag and need some implementation for int socketpair(int domain, int type, int protocol, int sv[2]) Looks like ESP IDF is missing this posix API.

sramrajkar avatar May 10 '24 04:05 sramrajkar

Thanks for the request, we do indeed not support his. It is something we can consider adding support for in the future.

From looking at their github I could see some discussion about porting the library though, and it seems that even if we had socketpair() you might still run into problems trying to run this.

ESP-Marius avatar May 13 '24 07:05 ESP-Marius

we most likely will not support socketpari() in any near future (we could maybe implement some simplified version using localhost sockets or other signalling mechanism, but not fully fledged implementation, at least not until it's supported in LWIP)

as for porting high-leverl libraries, we typically provide alternative implementation if pipe(), signals, and interprocess communication come into consideration and it's usually sufficient. you can check our asio port for your reference:

https://github.com/espressif/esp-protocols/tree/master/components/asio

Regarding the libplctag library, I think it's just easier to supply IDF specific implementation of sock_create_event_wakeup_channel(), maybe something similar to the windows platform signalling:

https://github.com/libplctag/libplctag/blob/4793d92893906e80fcfc67d4c4ed87fd37d81df8/src/platform/windows/platform.c#L2169-L2176

david-cermak avatar May 28 '24 14:05 david-cermak

@david-cermak @ESP-Marius thank you getting back on this issue. @david-cermak and idf specific function implementation would be a great start for me if you guys can help out. I tried a method looking at some literature online and it does not really work. The code gets blocked in that function trying to send even the first packet out the ethernet netif. Here is what I have tried

static int socketpair(int domain, int type, int protocol, int sv[2]) {
    int listener = socket(domain, type, protocol);
    if (listener < 0) return -1;

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    addr.sin_port = 0;  // Let the OS choose the port

    if (bind(listener, (struct sockaddr *)&addr, sizeof(addr)) < 0) return -1;

    socklen_t len = sizeof(addr);
    if (getsockname(listener, (struct sockaddr *)&addr, &len) < 0) return -1;

    if (listen(listener, 1) < 0) return -1;

    sv[0] = socket(domain, type, protocol);
    if (sv[0] < 0) return -1;

    if (connect(sv[0], (struct sockaddr *)&addr, len) < 0) return -1;

    sv[1] = accept(listener, NULL, NULL);
    if (sv[1] < 0) return -1;

    close(listener);  // No longer needed
    return 0;
}

@ESP-Marius I have some concerns about the RAM usage as well and I had started looking into one issue at a time. Initially the logging code using 1.5k buffer on the stack which was blowing up the stack very quickly. I think I can handle some RAM related issues if the networking is set up and there are packets on the wire. We also have 2MB PSRAM and can increase it on the design if needed just for this stack.

sramrajkar avatar May 29 '24 13:05 sramrajkar

@sramrajkar I think you're just missing the non-blocking mode, everything else looks good. here's the updated code, I've only briefly tested:

#include <netdb.h>
#include <sys/socket.h>
#include "esp_check.h"

#define INVALID_SOCKET (-1)

static const char *TAG = "socket_helpers";

static int set_nonblocking(int sock)
{
    int opt;
    opt = fcntl(sock, F_GETFL, 0);
    if (opt == -1){
        return -1;
    }
    if (fcntl(sock, F_SETFL, opt | O_NONBLOCK) == -1) {
        return -1;
    }
    return 0;
}

int socketpair(int domain, int type, int protocol, int sv[2])
{
    struct sockaddr_storage ss;
    struct sockaddr_in *sa = (struct sockaddr_in *)&ss;
    socklen_t ss_len = sizeof(struct sockaddr_in);
    int fd1 = INVALID_SOCKET;
    int fd2 = INVALID_SOCKET;
    int listenfd = INVALID_SOCKET;
    int ret = 0; // Success

    sa->sin_family = AF_INET;
    sa->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    sa->sin_port = 0;
    listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    ESP_GOTO_ON_FALSE(listenfd != INVALID_SOCKET, -1, err, TAG, "Cannot create listening socket");
    ESP_GOTO_ON_FALSE(listen(listenfd, 1) == 0, -1, err, TAG, "Failed to listen");

    memset(&ss, 0, sizeof(ss));
    ss_len = sizeof(ss);
    ESP_GOTO_ON_FALSE(getsockname(listenfd, (struct sockaddr *)&ss, &ss_len) >= 0, -1, err, TAG, "getsockname failed");

    sa->sin_family = AF_INET;
    sa->sin_addr.s_addr = htonl(INADDR_LOOPBACK);

    fd1 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    ESP_GOTO_ON_FALSE(fd1 != INVALID_SOCKET, -1, err, TAG, "Cannot create read side socket");
    ESP_GOTO_ON_FALSE(set_nonblocking(fd1) == 0, -1, err, TAG, "Failed to set socket to nonblocking mode");
    if (connect(fd1, (struct sockaddr *)&ss, ss_len) < 0) {
        ESP_GOTO_ON_FALSE(errno == EINPROGRESS || errno == EWOULDBLOCK, -1, err, TAG, "Failed to connect fd1");
    }
    fd2 = accept(listenfd, NULL, 0);
    if (fd2 == -1) {
        ESP_GOTO_ON_FALSE(errno == EINPROGRESS || errno == EWOULDBLOCK, -1, err, TAG, "Failed to accept fd2");
    }
    ESP_GOTO_ON_FALSE(set_nonblocking(fd2) == 0, -1, err, TAG, "Failed to set socket to nonblocking mode");

    close(listenfd);
    sv[0] = fd1;
    sv[1] = fd2;
    return ret;

err:
    if (listenfd != INVALID_SOCKET) {
        close(listenfd);
    }
    if (fd1 != INVALID_SOCKET) {
        close(fd1);
    }
    if (fd2 != INVALID_SOCKET) {
        close(fd2);
    }
    return ret;
}

david-cermak avatar Sep 20 '24 12:09 david-cermak

@sramrajkar We will soon publish socket-helper component containing the above implementation of socketpair and some other utilities: https://github.com/espressif/esp-protocols/pull/671 You can check if you're able to compile (and run) some libplctag project with this.

david-cermak avatar Oct 30 '24 15:10 david-cermak

@david-cermak yes I can try this over the weekend with the new module. Thank you for looking into this.

sramrajkar avatar Nov 01 '24 00:11 sramrajkar