]> git.ipfire.org Git - fireperf.git/commitdiff
server: Add basic implementation that accepts connections
authorMichael Tremer <michael.tremer@ipfire.org>
Mon, 25 Jan 2021 16:53:46 +0000 (16:53 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Mon, 25 Jan 2021 16:53:46 +0000 (16:53 +0000)
This creates an asynchronous loop which listens for new connections
opening and which will close connections after the client has closed
them.

This will also read any data that is being received on the sockets and
discard it.

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/main.h
src/server.c

index 06ec91481646092ab1313c20e4a9e1322c37d2c4..fb67aeb2a44d07aea6a7902ae43ea8581e071a19 100644 (file)
@@ -30,6 +30,7 @@
 #define MAX_PARALLEL (1 << 20)
 
 struct fireperf_config {
+       int terminated;
        int loglevel;
        enum {
                FIREPERF_MODE_NONE = 0,
index 3fd9471dc7bd0a4952973158273f8ed9dc4f51d5..a936a058b1bf2368d92f8e55bba5974331003752 100644 (file)
 #                                                                             #
 #############################################################################*/
 
+#include <errno.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <unistd.h>
+
 #include "logging.h"
 #include "main.h"
 #include "server.h"
 
+// Set the size of the read buffer to 1 MiB
+#define BUFFER_SIZE      1048576
+
+#define SOCKET_BACKLOG   1024
+#define EPOLL_MAX_EVENTS 1024
+
+static int create_socket(struct fireperf_config* conf) {
+       // Open a new socket
+       int fd = socket(AF_INET6, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0);
+       if (fd < 0) {
+               ERROR(conf, "Could not open socket: %s\n", strerror(errno));
+               goto ERROR;
+       }
+
+       struct sockaddr_in6 addr = {
+               .sin6_family = AF_INET6,
+               .sin6_port = htons(conf->port),
+       };
+
+       // Bind it to the selected port
+       int r = bind(fd, &addr, sizeof(addr));
+       if (r) {
+               ERROR(conf, "Could not bind socket: %s\n", strerror(errno));
+               goto ERROR;
+       }
+
+       // Listen
+       r = listen(fd, SOCKET_BACKLOG);
+       if (r) {
+               ERROR(conf, "Could not listen on socket: %s\n", strerror(errno));
+               goto ERROR;
+       }
+
+       DEBUG(conf, "Created listening socket %d\n", fd);
+
+       return fd;
+
+ERROR:
+       close(fd);
+
+       return -1;
+}
+
+static int accept_connection(struct fireperf_config* conf, int sockfd) {
+       struct sockaddr_in6 addr;
+       socklen_t l = sizeof(addr);
+
+       int fd = -1;
+
+       // The listening socket is ready, there is a new connection waiting to be accepted
+       do {
+               fd = accept(sockfd, &addr, &l);
+       } while (fd < 0 && (errno == EAGAIN || errno == EWOULDBLOCK));
+
+       if (fd < 0) {
+               ERROR(conf, "Could not accept a new connection: %s\n", strerror(errno));
+               return -1;
+       }
+
+       DEBUG(conf, "New connection accepted on socket %d\n", fd);
+
+       return fd;
+}
+
+static int handle_io_on_connection(struct fireperf_config* conf, int fd) {
+       char buffer[BUFFER_SIZE];
+       ssize_t bytes_read;
+
+       // Try reading into buffer
+       do {
+               bytes_read = recv(fd, buffer, sizeof(buffer), 0);
+       } while (bytes_read < 0 && (errno == EAGAIN || errno == EWOULDBLOCK));
+
+       // Error?
+       if (bytes_read < 0) {
+               ERROR(conf, "Could not read from socket %d: %s\n", fd, strerror(errno));
+               return -1;
+
+       // Connection closed
+       } else if (bytes_read == 0) {
+               DEBUG(conf, "Connection %d has closed\n", fd);
+               return 1;
+       }
+
+       DEBUG(conf, "Read %zu bytes from socket %d\n", bytes_read, fd);
+
+       return 0;
+}
+
 int fireperf_server(struct fireperf_config* conf) {
        DEBUG(conf, "Launching " PACKAGE_NAME " in server mode\n");
 
-       return 0;
+       int sockfd = -1;
+
+       int epollfd = -1;
+       struct epoll_event events[EPOLL_MAX_EVENTS];
+
+       int r = 1;
+
+       // Create listening socket
+       sockfd = create_socket(conf);
+       if (sockfd < 0)
+               return 1;
+
+       // Initialize epoll()
+       epollfd = epoll_create1(0);
+       if (epollfd < 0) {
+               ERROR(conf, "Could not initialize epoll(): %s\n", strerror(errno));
+               return 1;
+       }
+
+       // Add listening socket
+       struct epoll_event ev = {
+               .events  = EPOLLIN,
+               .data.fd = sockfd,
+       };
+
+       if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &ev)) {
+               ERROR(conf, "Could not add socket file descriptor to epoll(): %s\n",
+                       strerror(errno));
+               goto ERROR;
+       }
+
+       DEBUG(conf, "Entering main loop...\n");
+
+       while (!conf->terminated) {
+               int fds = epoll_wait(epollfd, events, EPOLL_MAX_EVENTS, -1);
+               if (fds < 1) {
+                       ERROR(conf, "epoll_wait() failed: %s\n", strerror(errno));
+                       goto ERROR;
+               }
+
+               DEBUG(conf, "%d event(s) are ready\n", fds);
+
+               for (int i = 0; i < fds; i++) {
+                       int fd = events[i].data.fd;
+
+                       // The listening socket
+                       if (fd == sockfd) {
+                               int connfd = accept_connection(conf, sockfd);
+                               if (connfd < 0)
+                                       goto ERROR;
+
+                               // Add the new socket to epoll()
+                               ev.events  = EPOLLIN;
+                               ev.data.fd = connfd;
+
+                               if (epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &ev)) {
+                                       ERROR(conf, "Could not add socket file descriptor to epoll(): %s\n",
+                                               strerror(errno));
+                                       goto ERROR;
+                               }
+
+                       // One of the connections had IO
+                       } else {
+                               r = handle_io_on_connection(conf, fd);
+                               if (r < 0)
+                                       goto ERROR;
+
+                               // Connection closed?
+                               else if (r == 1) {
+                                       if (epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL)) {
+                                               ERROR(conf, "Could not remove socket file descriptfor from epoll(): %s\n",
+                                                       strerror(errno));
+                                       }
+                               }
+                       }
+               }
+       }
+
+ERROR:
+       if (sockfd > 0)
+               close(sockfd);
+
+       if (epollfd > 0)
+               close(epollfd);
+
+       return r;
 }