| /* |
| * Copyright (c) 2016, Linaro Limited |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License Version 2 as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <sys/types.h> |
| #include <stdbool.h> |
| #include <arpa/inet.h> |
| #include <err.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <netdb.h> |
| #include <netinet/in.h> |
| #include <poll.h> |
| #include <sys/socket.h> |
| #include <unistd.h> |
| |
| #include "sock_server.h" |
| |
| struct server_state { |
| struct sock_state *socks; |
| struct pollfd *fds; |
| nfds_t nfds; |
| bool got_quit; |
| struct sock_io_cb *cb; |
| }; |
| |
| #define SOCK_BUF_SIZE 512 |
| |
| struct sock_state { |
| bool (*cb)(struct server_state *srvst, size_t idx); |
| struct sock_server_bind *serv; |
| }; |
| |
| static bool server_io_cb(struct server_state *srvst, size_t idx) |
| { |
| short revents = srvst->fds[idx].revents; |
| short *events = &srvst->fds[idx].events; |
| struct sock_io_cb *cb = srvst->cb; |
| int fd = 0; |
| |
| fd = srvst->fds[idx].fd; |
| if (revents & POLLIN) { |
| if (!cb->read) |
| *events &= ~POLLIN; |
| else if (!cb->read(cb->ptr, fd, events)) |
| goto close; |
| } |
| |
| if (revents & POLLOUT) { |
| if (!cb->write) |
| *events &= ~POLLOUT; |
| else if (!cb->write(cb->ptr, fd, events)) |
| goto close; |
| } |
| |
| if (!(revents & ~(POLLIN | POLLOUT))) |
| return true; |
| close: |
| if (close(fd)) { |
| warn("server_io_cb: close(%d)", fd); |
| return false; |
| } |
| srvst->fds[idx].fd = -1; |
| return true; |
| } |
| |
| static bool server_add_state(struct server_state *srvst, |
| bool (*cb)(struct server_state *srvst, size_t idx), |
| struct sock_server_bind *serv, int fd, |
| short poll_events) |
| { |
| void *p = NULL; |
| size_t n = 0; |
| |
| for (n = 0; n < srvst->nfds; n++) { |
| if (srvst->fds[n].fd == -1) { |
| srvst->socks[n].cb = cb; |
| srvst->socks[n].serv = serv; |
| srvst->fds[n].fd = fd; |
| srvst->fds[n].events = poll_events; |
| srvst->fds[n].revents = 0; |
| return true; |
| } |
| } |
| |
| p = realloc(srvst->socks, sizeof(*srvst->socks) * (srvst->nfds + 1)); |
| if (!p) |
| return false; |
| srvst->socks = p; |
| srvst->socks[srvst->nfds].cb = cb; |
| srvst->socks[srvst->nfds].serv = serv; |
| |
| p = realloc(srvst->fds, sizeof(*srvst->fds) * (srvst->nfds + 1)); |
| if (!p) |
| return false; |
| srvst->fds = p; |
| srvst->fds[srvst->nfds].fd = fd; |
| srvst->fds[srvst->nfds].events = poll_events; |
| srvst->fds[srvst->nfds].revents = 0; |
| |
| srvst->nfds++; |
| return true; |
| } |
| |
| static bool tcp_server_accept_cb(struct server_state *srvst, size_t idx) |
| { |
| short revents = srvst->fds[idx].revents; |
| struct sockaddr_storage sass = { }; |
| struct sockaddr *sa = (struct sockaddr *)&sass; |
| socklen_t len = sizeof(sass); |
| int fd = 0; |
| short io_events = POLLIN | POLLOUT; |
| |
| if (!(revents & POLLIN)) |
| return false; |
| |
| fd = accept(srvst->fds[idx].fd, sa, &len); |
| if (fd == -1) { |
| if (errno == EAGAIN || errno == EWOULDBLOCK || |
| errno == ECONNABORTED) |
| return true; |
| return false; |
| } |
| |
| if (srvst->cb->accept && |
| !srvst->cb->accept(srvst->cb->ptr, fd, &io_events)) { |
| if (close(fd)) |
| warn("server_accept_cb: close(%d)", fd); |
| return true; |
| } |
| |
| return server_add_state(srvst, server_io_cb, srvst->socks[idx].serv, |
| fd, io_events); |
| } |
| |
| static bool udp_server_cb(struct server_state *srvst, size_t idx) |
| { |
| short revents = srvst->fds[idx].revents; |
| |
| if (!(revents & POLLIN)) |
| return false; |
| |
| return srvst->cb->accept(srvst->cb->ptr, srvst->fds[idx].fd, NULL); |
| } |
| |
| static bool server_quit_cb(struct server_state *srvst, size_t idx) |
| { |
| (void)idx; |
| srvst->got_quit = true; |
| return true; |
| } |
| |
| static void sock_server(struct sock_server *ts, |
| bool (*cb)(struct server_state *srvst, size_t idx)) |
| { |
| struct server_state srvst = { .cb = ts->cb }; |
| int pres = 0; |
| size_t n = 0; |
| char b = 0; |
| |
| sock_server_lock(ts); |
| |
| for (n = 0; n < ts->num_binds; n++) { |
| if (!server_add_state(&srvst, cb, ts->bind + n, |
| ts->bind[n].fd, POLLIN)) |
| goto bad; |
| } |
| |
| if (!server_add_state(&srvst, server_quit_cb, NULL, |
| ts->quit_fd, POLLIN)) |
| goto bad; |
| |
| while (true) { |
| sock_server_unlock(ts); |
| /* |
| * First sleep 5 ms to make it easier to test send timeouts |
| * due to this rate limit. |
| */ |
| poll(NULL, 0, 5); |
| pres = poll(srvst.fds, srvst.nfds, -1); |
| sock_server_lock(ts); |
| if (pres < 0) |
| goto bad; |
| |
| for (n = 0; pres && n < srvst.nfds; n++) { |
| if (srvst.fds[n].revents) { |
| pres--; |
| if (!srvst.socks[n].cb(&srvst, n)) |
| goto bad; |
| } |
| } |
| |
| if (srvst.got_quit) |
| goto out; |
| } |
| |
| bad: |
| ts->error = true; |
| out: |
| for (n = 0; n < srvst.nfds; n++) { |
| /* Don't close accept and quit fds */ |
| if (srvst.fds[n].fd != -1 && srvst.socks[n].serv && |
| srvst.fds[n].fd != srvst.socks[n].serv->fd) { |
| if (close(srvst.fds[n].fd)) |
| warn("sock_server: close(%d)", srvst.fds[n].fd); |
| } |
| } |
| free(srvst.socks); |
| free(srvst.fds); |
| if (read(ts->quit_fd, &b, 1) != 1) |
| ts->error = true; |
| |
| sock_server_unlock(ts); |
| } |
| |
| static void *sock_server_stream(void *arg) |
| { |
| sock_server(arg, tcp_server_accept_cb); |
| return NULL; |
| } |
| |
| static void *sock_server_dgram(void *arg) |
| { |
| sock_server(arg, udp_server_cb); |
| return NULL; |
| } |
| |
| static void sock_server_add_fd(struct sock_server *ts, struct addrinfo *ai) |
| { |
| struct sock_server_bind serv = { }; |
| struct sockaddr_storage sass = { }; |
| struct sockaddr *sa = (struct sockaddr *)&sass; |
| struct sockaddr_in *sain = (struct sockaddr_in *)&sass; |
| struct sockaddr_in6 *sain6 = (struct sockaddr_in6 *)&sass; |
| void *src = NULL; |
| socklen_t len = sizeof(sass); |
| struct sock_server_bind *p = NULL; |
| |
| serv.fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); |
| if (serv.fd < 0) |
| return; |
| |
| if (bind(serv.fd, ai->ai_addr, ai->ai_addrlen)) |
| goto bad; |
| |
| if (ai->ai_socktype == SOCK_STREAM && listen(serv.fd, 5)) |
| goto bad; |
| |
| if (getsockname(serv.fd, sa, &len)) |
| goto bad; |
| |
| switch (sa->sa_family) { |
| case AF_INET: |
| src = &sain->sin_addr; |
| serv.port = ntohs(sain->sin_port); |
| break; |
| case AF_INET6: |
| src = &sain6->sin6_addr; |
| serv.port = ntohs(sain6->sin6_port); |
| default: |
| goto bad; |
| } |
| |
| if (!inet_ntop(sa->sa_family, src, serv.host, sizeof(serv.host))) |
| goto bad; |
| |
| p = realloc(ts->bind, sizeof(*p) * (ts->num_binds + 1)); |
| if (!p) |
| goto bad; |
| |
| ts->bind = p; |
| p[ts->num_binds] = serv; |
| ts->num_binds++; |
| return; |
| bad: |
| if (close(serv.fd)) |
| warn("sock_server_add_fd: close(%d)", serv.fd); |
| } |
| |
| void sock_server_uninit(struct sock_server *ts) |
| { |
| size_t n = 0; |
| int e = 0; |
| |
| if (ts->stop_fd != -1) { |
| if (close(ts->stop_fd)) |
| warn("sock_server_uninit: close(%d)", ts->stop_fd); |
| ts->stop_fd = -1; |
| e = pthread_join(ts->thr, NULL); |
| if (e) |
| warnx("sock_server_uninit: pthread_join: %s", |
| strerror(e)); |
| } |
| |
| e = pthread_mutex_destroy(&ts->mu); |
| if (e) |
| warnx("sock_server_uninit: pthread_mutex_destroy: %s", |
| strerror(e)); |
| |
| for (n = 0; n < ts->num_binds; n++) |
| if (close(ts->bind[n].fd)) |
| warn("sock_server_uninit: close(%d)", ts->bind[n].fd); |
| free(ts->bind); |
| if (ts->quit_fd != -1 && close(ts->quit_fd)) |
| warn("sock_server_uninit: close(%d)", ts->quit_fd); |
| memset(ts, 0, sizeof(*ts)); |
| ts->quit_fd = -1; |
| ts->stop_fd = -1; |
| } |
| |
| static bool sock_server_init(struct sock_server *ts, struct sock_io_cb *cb, |
| int socktype) |
| { |
| struct addrinfo hints = { }; |
| struct addrinfo *ai = NULL; |
| struct addrinfo *ai0 = NULL; |
| int fd_pair[2] = { }; |
| int e = 0; |
| |
| memset(ts, 0, sizeof(*ts)); |
| ts->quit_fd = -1; |
| ts->stop_fd = -1; |
| ts->cb = cb; |
| |
| e = pthread_mutex_init(&ts->mu, NULL); |
| if (e) { |
| warnx("sock_server_init: pthread_mutex_init: %s", strerror(e)); |
| return false; |
| } |
| |
| hints.ai_flags = AI_PASSIVE; |
| hints.ai_family = AF_UNSPEC; |
| hints.ai_socktype = socktype; |
| |
| if (getaddrinfo(NULL, "0", &hints, &ai0)) |
| return false; |
| |
| for (ai = ai0; ai; ai = ai->ai_next) |
| sock_server_add_fd(ts, ai); |
| |
| freeaddrinfo(ai0); |
| |
| if (!ts->num_binds) |
| return false; |
| |
| if (pipe(fd_pair)) { |
| sock_server_uninit(ts); |
| return false; |
| } |
| |
| ts->quit_fd = fd_pair[0]; |
| |
| if (socktype == SOCK_STREAM) |
| e = pthread_create(&ts->thr, NULL, sock_server_stream, ts); |
| else |
| e = pthread_create(&ts->thr, NULL, sock_server_dgram, ts); |
| if (e) { |
| warnx("sock_server_init: pthread_create: %s", strerror(e)); |
| if (close(fd_pair[1])) |
| warn("sock_server_init: close(%d)", fd_pair[1]); |
| sock_server_uninit(ts); |
| return false; |
| } |
| |
| ts->stop_fd = fd_pair[1]; |
| return true; |
| } |
| |
| bool sock_server_init_tcp(struct sock_server *sock_serv, struct sock_io_cb *cb) |
| { |
| return sock_server_init(sock_serv, cb, SOCK_STREAM); |
| } |
| |
| bool sock_server_init_udp(struct sock_server *sock_serv, struct sock_io_cb *cb) |
| { |
| return sock_server_init(sock_serv, cb, SOCK_DGRAM); |
| } |
| |
| void sock_server_lock(struct sock_server *ts) |
| { |
| int e = pthread_mutex_lock(&ts->mu); |
| |
| if (e) |
| errx(1, "sock_server_lock: pthread_mutex_lock: %s", strerror(e)); |
| } |
| |
| void sock_server_unlock(struct sock_server *ts) |
| { |
| int e = pthread_mutex_unlock(&ts->mu); |
| |
| if (e) |
| errx(1, "sock_server_unlock: pthread_mutex_unlock: %s", |
| strerror(e)); |
| } |