commit 19ae6e9dcb3a166e88c6e9951807ae1f60541eb0 Author: trimill Date: Thu Nov 2 19:43:50 2023 -0400 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c50ccfb --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +tel +teld +tel.o +teld.o +libtel.o diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..31eb527 --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +CFLAGS += -Wall -Wextra -pedantic +# CFLAGS += -O0 -g +CFLAGS += -O3 + +.phony: all clean + +all: tel teld + +clean: + rm -f tel teld tel.o teld.o libtel.o + +libtel.o: libtel.c + $(CC) $(CFLAGS) -c libtel.c + +tel.o: tel.c libtel.h + $(CC) $(CFLAGS) -c tel.c -ledit -lncurses + +tel: tel.o libtel.o + $(CC) $(CFLAGS) tel.o libtel.o -o tel + +teld.o: teld.c libtel.h + $(CC) $(CFLAGS) -c teld.c -ledit -lncurses + +teld: teld.o libtel.o + $(CC) $(CFLAGS) teld.o libtel.o -o teld diff --git a/libtel.c b/libtel.c new file mode 100644 index 0000000..29e3779 --- /dev/null +++ b/libtel.c @@ -0,0 +1,329 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "libtel.h" + +int tel_sa_any_eq(union tel_sa_any *a, union tel_sa_any *b) { + struct sockaddr *as = (struct sockaddr *)a; + struct sockaddr *bs = (struct sockaddr *)b; + if(as->sa_family != bs->sa_family) { + return 0; + } + if(as->sa_family == AF_INET) { + struct sockaddr_in *a4 = &a->v4; + struct sockaddr_in *b4 = &b->v4; + if(a4->sin_port != b4->sin_port) { + return 0; + } + if(a4->sin_addr.s_addr != b4->sin_addr.s_addr) { + return 0; + } + return 1; + } else if(as->sa_family == AF_INET6) { + struct sockaddr_in6 *a6 = &a->v6; + struct sockaddr_in6 *b6 = &b->v6; + if(a6->sin6_port != b6->sin6_port) { + return 0; + } + if(memcmp(a6->sin6_addr.s6_addr, b6->sin6_addr.s6_addr, 16 * sizeof(uint8_t)) != 0) { + return 0; + } + return 1; + } else { + return 0; + } +} + + +int tel_get_addr(char *ip, char *port, struct sockaddr **addr, socklen_t *addr_len) { + if(port == NULL) port = "4111"; + + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG; + + struct addrinfo *info; + int res = getaddrinfo(ip, port, &hints, &info); + if(res != 0) return res; + + *addr = info->ai_addr; + *addr_len = info->ai_addrlen; + + freeaddrinfo(info); + return 0; +} + +int tel_straddr(const struct sockaddr *restrict addr, char *restrict buf) { + uint16_t port = 0; + + if(addr->sa_family == AF_INET) { + struct sockaddr_in *addr4 = (struct sockaddr_in *)addr; + + port = addr4->sin_port; + + const char *res = inet_ntop(AF_INET, &addr4->sin_addr, buf, INET6_ADDRSTRLEN); + if(res == NULL) { + return -1; + } + } else if(addr->sa_family == AF_INET6) { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr; + + port = addr6->sin6_port; + + const char *res = inet_ntop(AF_INET6, &addr6->sin6_addr, buf, INET6_ADDRSTRLEN); + if(res == NULL) { + return -1; + } + } else { + errno = 0; + return -1; + } + + size_t len = strlen(buf); + sprintf(buf + len, " %u", ntohs(port)); + return 0; +} + + +ssize_t tel_write_all(int fd, const void *buf, size_t count) { + const char *cbuf = (char *)buf; + ssize_t wc; + ssize_t total = 0; + + while(count > 0) { + wc = write(fd, cbuf, count); + if(wc <= 0) { + return wc; + } + cbuf += wc; + count -= wc; + total += wc; + } + + return total; +} + +ssize_t tel_read_all(int fd, void *buf, size_t count) { + char *cbuf = (char *)buf; + ssize_t rc; + ssize_t total = 0; + + while(count > 0) { + rc = read(fd, cbuf, count); + if(rc <= 0) { + return rc; + } + cbuf += rc; + count -= rc; + total += rc; + } + + return total; +} + +ssize_t tel_write_u32(int fd, uint32_t n) { + uint32_t nn = htonl(n); + char bytes[4]; + memcpy(bytes, &nn, 4); + return tel_write_all(fd, bytes, 4); +} + +ssize_t tel_read_u32(int fd, uint32_t *n) { + char bytes[4]; + ssize_t res = tel_read_all(fd, bytes, 4); + if(res < 4) return res; + uint32_t nn; + memcpy(&nn, bytes, 4); + *n = ntohl(nn); + return res; +} + +ssize_t tel_write_u16(int fd, uint16_t n) { + uint16_t nn = htons(n); + char bytes[2]; + memcpy(bytes, &nn, 2); + return tel_write_all(fd, bytes, 2); +} + +ssize_t tel_read_u16(int fd, uint16_t *n) { + char bytes[2]; + ssize_t res = tel_read_all(fd, bytes, 2); + if(res < 2) return res; + uint16_t nn; + memcpy(&nn, bytes, 2); + *n = ntohs(nn); + return res; +} + +ssize_t tel_write_fd(int stream, int fd) { + struct msghdr msg; + struct iovec iov[1]; + struct cmsghdr *cmsg = NULL; + char ctrl_buf[CMSG_SPACE(sizeof(int))]; + char data[1]; + + memset(&msg, 0, sizeof(struct msghdr)); + memset(ctrl_buf, 0, CMSG_SPACE(sizeof(int))); + + data[0] = '#'; + iov[0].iov_base = data; + iov[0].iov_len = sizeof(data); + + msg.msg_name = NULL; + msg.msg_namelen = 0; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + msg.msg_controllen = CMSG_SPACE(sizeof(int)); + msg.msg_control = ctrl_buf; + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + + *((int *) CMSG_DATA(cmsg)) = fd; + + return sendmsg(stream, &msg, 0); +} + +ssize_t tel_read_fd(int sock, int *fd) { + int len; + char buf[1]; + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cmsg; + char cms[CMSG_SPACE(sizeof(int))]; + + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + msg.msg_name = 0; + msg.msg_namelen = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + msg.msg_control = (caddr_t) cms; + msg.msg_controllen = sizeof cms; + + len = recvmsg(sock, &msg, 0); + + if(len <= 0) { + return len; + } + + cmsg = CMSG_FIRSTHDR(&msg); + memmove(fd, CMSG_DATA(cmsg), sizeof(int)); + + return len; +} + +ssize_t tel_write_sockaddr(int fd, struct sockaddr *addr) { + ssize_t res; + ssize_t total = 0; + + if(addr->sa_family == AF_INET) { + struct sockaddr_in *addr4 = (struct sockaddr_in *)addr; + + res = tel_write_u32(fd, 4); + if(res < 0) return res; + total += res; + + res = tel_write_u32(fd, addr4->sin_addr.s_addr); + if(res < 0) return res; + total += res; + + res = tel_write_u16(fd, addr4->sin_port); + if(res < 0) return res; + total += res; + + return total; + } else if(addr->sa_family == AF_INET6) { + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr; + + res = tel_write_u32(fd, 6); + if(res < 0) return res; + total += res; + + res = tel_write_all(fd, addr6->sin6_addr.s6_addr, 16); + if(res < 0) return res; + total += res; + + res = tel_write_u16(fd, addr6->sin6_port); + if(res < 0) return res; + total += res; + } else { + errno = ESOCKTNOSUPPORT; + return -1; + } + return total; +} + +ssize_t tel_read_sockaddr(int fd, union tel_sa_any *addr, socklen_t *addr_len) { + ssize_t res; + ssize_t total = 0; + + uint32_t ty = 0; + res = tel_read_u32(fd, &ty); + if(res < 0) return res; + total += res; + + if(ty == 4) { + if(*addr_len < sizeof(struct sockaddr_in)) { + printf("addr_len %u too small for ipv4\n", *addr_len); + errno = 0; + return -1; + } + struct sockaddr_in *addr4 = &addr->v4; + *addr_len = sizeof(struct sockaddr_in); + addr4->sin_family = AF_INET; + + res = tel_read_u32(fd, &addr4->sin_addr.s_addr); + if(res < 0) return res; + total += res; + + res = tel_read_u16(fd, &addr4->sin_port); + if(res < 0) return res; + total += res; + } else if(ty == 6) { + if(*addr_len < sizeof(struct sockaddr_in6)) { + printf("addr_len %u to small for ipv6\n", *addr_len); + errno = 0; + return -1; + } + struct sockaddr_in6 *addr6 = &addr->v6; + *addr_len = sizeof(struct sockaddr_in6); + addr6->sin6_family = AF_INET6; + + res = tel_read_all(fd, addr6->sin6_addr.s6_addr, 16); + if(res < 0) return res; + total += res; + + res = tel_read_u16(fd, &addr6->sin6_port); + if(res < 0) return res; + total += res; + } else { + printf("ty was %u\n", ty); + errno = 0; + return -1; + } + return total; +} + +ssize_t tel_write_err(int fd, const char *msg) { + size_t msglen = strlen(msg); + ssize_t res1 = tel_write_u32(fd, MSG_ERROR); + if(res1 < 0) return res1; + ssize_t res2 = tel_write_u32(fd, msglen); + if(res2 < 0) return res2; + ssize_t res3 = tel_write_all(fd, msg, msglen); + if(res3 < 0) return res3; + return res1 + res2 + res3; +} diff --git a/libtel.h b/libtel.h new file mode 100644 index 0000000..e5c3b41 --- /dev/null +++ b/libtel.h @@ -0,0 +1,41 @@ +#pragma once +#include + +#define TEL_STRADDRLEN (INET6_ADDRSTRLEN + 1 + 5) + +union tel_sa_any { + struct sockaddr_in v4; + struct sockaddr_in6 v6; +}; + +int tel_sa_any_eq(union tel_sa_any *a, union tel_sa_any *b); + +enum tel_message { + MSG_PICKUP, + MSG_CALL, + MSG_LIST, + MSG_SOCKET, + MSG_OK, + MSG_ERROR, + MSG_NONE, +}; + +int tel_get_addr(char *ip, char *port, struct sockaddr **addr, socklen_t *addr_len); +int tel_straddr(const struct sockaddr *restrict addr, char *restrict buf); + +ssize_t tel_write_all(int fd, const void *buf, size_t count); +ssize_t tel_read_all(int fd, void *buf, size_t count); + +ssize_t tel_write_u32(int fd, uint32_t n); +ssize_t tel_read_u32(int fd, uint32_t *n); + +ssize_t tel_write_u16(int fd, uint16_t n); +ssize_t tel_read_u16(int fd, uint16_t *n); + +ssize_t tel_write_fd(int stream, int fd); +ssize_t tel_read_fd(int sock, int *fd); + +ssize_t tel_write_sockaddr(int fd, struct sockaddr *addr); +ssize_t tel_read_sockaddr(int fd, union tel_sa_any *addr, socklen_t *addr_len); + +ssize_t tel_write_err(int fd, const char *msg); diff --git a/tel.c b/tel.c new file mode 100644 index 0000000..de3dd86 --- /dev/null +++ b/tel.c @@ -0,0 +1,254 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "libtel.h" + +void handle_call(int sock) { + fcntl(0, F_SETFL, O_NONBLOCK); + fcntl(sock, F_SETFL, O_NONBLOCK); + struct pollfd fds[2]; + fds[0].fd = 0; + fds[0].events = POLLIN | POLLERR | POLLHUP; + fds[1].fd = sock; + fds[1].events = POLLIN | POLLERR | POLLHUP; + + while(1) { + char buf[1024]; + int res = poll(fds, 2, -1); + if(res < 0) { + perror("error polling"); + goto end_err; + } + + if(fds[0].revents & POLLIN) { + while((res = read(0, buf, 1024)) > 0) { + res = tel_write_all(sock, buf, res); + if(res < 0) { + perror("failed to send message"); + goto end_err; + } + } + if(res < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { + perror("failed to read input"); + goto end_err; + } + } + if(fds[1].revents & POLLIN) { + int readcount = 0; + while((res = read(sock, buf, 1024)) > 0) { + printf("%.*s", res, buf); + readcount++; + } + if(res < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { + perror("failed to receive message"); + goto end_err; + } + if(readcount == 0) { + printf("peer terminated call\n"); + goto end; + } + } + } +end: + close(sock); + exit(0); +end_err: + close(sock); + exit(1); +} + +int connect_local_sock(char *path) { + int res; + int sock; + struct sockaddr_un addr; + + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if(sock < 0) { + perror("error opening local socket"); + exit(1); + } + + res = connect(sock, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)); + if(res < 0) { + perror("error connecting to local socket"); + exit(1); + } + + return sock; +} + +void pickup(struct sockaddr *addr) { + int res; + int sock = connect_local_sock("/tmp/tel.sock"); + + res = tel_write_u32(sock, MSG_PICKUP); + if(res < 0) goto write_err; + + res = tel_write_sockaddr(sock, addr); + if(res < 0) goto write_err; + + uint32_t rsp; + res = tel_read_u32(sock, &rsp); + if(res < 0) goto read_err; + + switch(rsp) { + case MSG_NONE: + printf("no incoming calls from specified address\n"); + close(sock); + return; + case MSG_ERROR: { + uint32_t len; + res = tel_read_u32(sock, &len); + if(res < 0) goto read_err; + char buf[len + 1]; + res = tel_read_all(sock, buf, len); + if(res < 0) goto read_err; + buf[len] = '\0'; + fprintf(stderr, "error in daemon: %s\n", buf); + close(sock); + return; + } + case MSG_SOCKET: { + int fd; + int res = tel_read_fd(sock, &fd); + if(res < 0) { + perror("failed to receive fd"); + return; + } else if(res == 0) { + fprintf(stderr, "failed to receive fd: no data\n"); + return; + } + close(sock); + printf("picked up call\n"); + handle_call(fd); + return; + } + default: + close(sock); + return; + } + +read_err: + perror("error reading socket"); + goto end; +write_err: + perror("error writing socket"); + goto end; +end: + close(sock); +} + +void call(struct sockaddr *addr) { + int res; + int sock = connect_local_sock("/tmp/tel.sock"); + + res = tel_write_u32(sock, MSG_CALL); + if(res < 0) goto write_err; + + res = tel_write_sockaddr(sock, addr); + if(res < 0) goto write_err; + + uint32_t rsp; + res = tel_read_u32(sock, &rsp); + if(res < 0) goto read_err; + + switch(rsp) { + case MSG_NONE: + printf("none\n"); + close(sock); + return; + case MSG_ERROR: { + uint32_t len; + res = tel_read_u32(sock, &len); + if(res < 0) goto read_err; + char buf[len + 1]; + res = tel_read_all(sock, buf, len); + if(res < 0) goto read_err; + buf[len] = '\0'; + fprintf(stderr, "error in daemon: %s\n", buf); + close(sock); + return; + } + case MSG_SOCKET: { + int fd; + int res = tel_read_fd(sock, &fd); + if(res < 0) { + perror("failed to receive fd"); + return; + } else if(res == 0) { + fprintf(stderr, "failed to receive fd: no data\n"); + return; + } + close(sock); + printf("calling...\n"); + handle_call(fd); + return; + } + default: + close(sock); + return; + } + +read_err: + perror("error reading socket"); + goto end; +write_err: + perror("error writing socket"); + goto end; +end: + close(sock); +} + +void usage_exit(char *name) { + fprintf(stderr, "usage: %s call [ip] OR %s pkup [ip]", name, name); + exit(1); +} + +int main(int argc, char **argv) { + if(argc < 1) exit(1); + if(argc < 2) usage_exit(argv[0]); + + if(strcmp(argv[1], "call") == 0) { + if(argc < 3 || argc > 4) usage_exit(argv[0]); + + struct sockaddr *addr; + socklen_t addr_len; + int res = tel_get_addr(argv[2], (argc < 4) ? NULL : argv[3], &addr, &addr_len); + if(res != 0) { + fprintf(stderr, "error: %s\n", gai_strerror(res)); + exit(1); + } + + call(addr); + + } else if(strcmp(argv[1], "pkup") == 0) { + if(argc < 3 || argc > 4) usage_exit(argv[0]); + + struct sockaddr *addr; + socklen_t addr_len; + int res = tel_get_addr(argv[2], (argc < 4) ? NULL : argv[3], &addr, &addr_len); + if(res != 0) { + fprintf(stderr, "error: %s\n", gai_strerror(res)); + exit(1); + } + + pickup(addr); + } else { + usage_exit(argv[0]); + } +} diff --git a/teld.c b/teld.c new file mode 100644 index 0000000..5bef42a --- /dev/null +++ b/teld.c @@ -0,0 +1,394 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "libtel.h" + +#define CALL_BUF_SIZE 16 + +// config + +struct config { + struct sockaddr *addr; + socklen_t addr_len; + int connect_timeout; +}; + +struct in_call { + int fd; + union tel_sa_any addr; +}; + +// +// Call buffer operations +// + +struct call_buf { + struct in_call buf[CALL_BUF_SIZE]; + size_t len; +}; + +void call_buf_init(struct call_buf *buf) { + buf->len = 0; +} + +void call_buf_add(struct call_buf *buf, struct in_call call) { + // if buffer is full, remove and close tail element + if(buf->len == CALL_BUF_SIZE) { + struct in_call last = buf->buf[0]; + close(last.fd); + memmove(buf->buf, buf->buf + 1, CALL_BUF_SIZE - 1); + } + buf->buf[buf->len++] = call; +} + +struct in_call call_buf_remove(struct call_buf *buf, int n) { + struct in_call call = buf->buf[n]; + buf->len--; + buf->buf[n] = buf->buf[buf->len]; + return call; +} + +int call_buf_search(struct call_buf *buf, union tel_sa_any *callinfo) { + for(size_t i = 0; i < buf->len; i++) { + if(tel_sa_any_eq(&buf->buf[i].addr, callinfo)) { + return i; + } + } + return -1; +} + +// +// Socket initialization +// + +int init_client_sock(char *path) { + int res; + int sock; + struct sockaddr_un addr; + + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if(sock < 0) { + perror("error opening client socket"); + exit(1); + } + + unlink(path); + + res = bind(sock, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)); + if(res < 0) { + perror("error binding to client socket"); + exit(1); + } + + res = listen(sock, 16); + if(res < 0) { + perror("error listening on client socket"); + exit(1); + } + + printf("client socket listening at %s\n", path); + + return sock; +} + +int init_net_sock(struct sockaddr *addr, socklen_t addr_len) { + int res; + int sock; + + sock = socket(addr->sa_family, SOCK_STREAM, 0); + if(sock < 0) { + perror("error opening network socket"); + exit(1); + } + + int enable = 1; + + res = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + if(res < 0) { + perror("failed to set socket option SO_REUSEADDR"); + exit(1); + } + + res = setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)); + if(res < 0) { + perror("failed to set socket option SO_REUSEPORT"); + exit(1); + } + + res = bind(sock, addr, addr_len); + if(res < 0) { + perror("error binding to network socket"); + exit(1); + } + + res = listen(sock, 16); + if(res < 0) { + perror("error listening on network socket"); + exit(1); + } + + char buf[TEL_STRADDRLEN]; + res = tel_straddr(addr, buf); + if(res >= 0) { + printf("network socket listening at %s\n", buf); + } + + return sock; +} + +void client_pickup(struct call_buf *call_buf, int stream) { + int res; + + union tel_sa_any remote_addr; + socklen_t remote_addr_len = sizeof(union tel_sa_any); + res = tel_read_sockaddr(stream, &remote_addr, &remote_addr_len); + + int idx = call_buf_search(call_buf, &remote_addr); + if(idx < 0) { + tel_write_u32(stream, MSG_NONE); + return; + } + + struct in_call call = call_buf_remove(call_buf, idx); + int fd = call.fd; + + tel_write_u32(stream, MSG_SOCKET); + + res = tel_write_fd(stream, fd); + if(res < 0) { + tel_write_err(stream, strerror(errno)); + }; + close(fd); + return; +} + +void client_call(int stream, struct config cfg) { + int res; + + union tel_sa_any remote_addr; + socklen_t remote_addr_len = sizeof(union tel_sa_any); + res = tel_read_sockaddr(stream, &remote_addr, &remote_addr_len); + if(res < 0) goto err_noclose; + + char buf[TEL_STRADDRLEN]; + res = tel_straddr((struct sockaddr *)&remote_addr, buf); + if(res >= 0) { + printf("client calling %s...\n", buf); + } + + int sock = socket(remote_addr.v4.sin_family, SOCK_STREAM, 0); + if(res < 0) goto err_noclose; + + int enable = 1; + + res = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + if(res < 0) goto err; + + res = setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int)); + if(res < 0) goto err; + + res = bind(sock, cfg.addr, cfg.addr_len); + if(res < 0) goto err; + + int sock_flags = fcntl(sock, F_GETFL); + fcntl(sock, F_SETFL, sock_flags | O_NONBLOCK); + + struct pollfd pollfds[1]; + pollfds[0].fd = sock; + pollfds[0].events = POLLOUT; + + res = connect(sock, (struct sockaddr *)&remote_addr, remote_addr_len); + if(res < 0 && errno != EINPROGRESS) goto err; + + res = poll(pollfds, 1, cfg.connect_timeout); + if(res < 0) goto err; + if(res == 0) { + printf("error making call: timed out\n"); + tel_write_err(stream, "timed out"); + close(sock); + return; + } + if(pollfds[0].revents & POLLERR) { + printf("error making call: timed out\n"); + tel_write_err(stream, "unknown error connecting"); + close(sock); + return; + } + + fcntl(sock, F_SETFL, sock_flags); + + res = tel_write_u32(stream, MSG_SOCKET); + if(res < 0) goto err; + + res = tel_write_fd(stream, sock); + if(res < 0) goto err; + + printf("call made successfully\n"); + close(sock); + return; + +err_noclose: + printf("error making call: %s\n", strerror(errno)); + tel_write_err(stream, strerror(errno)); + return; + +err: + printf("error making call: %s\n", strerror(errno)); + tel_write_err(stream, strerror(errno)); + close(sock); + return; +} + +void accept_client(struct call_buf *call_buf, int fd, struct config cfg) { + int stream = accept(fd, NULL, 0); + if(stream < 0) { + perror("error accepting client connection"); + exit(1); + } + + printf("accepted client connection\n"); + + enum tel_message msg; + int rc = tel_read_u32(stream, &msg); + if(rc < 0) { + tel_write_err(stream, strerror(errno)); + return; + } + + switch(msg) { + case MSG_PICKUP: + client_pickup(call_buf, stream); + break; + case MSG_CALL: + client_call(stream, cfg); + break; + default: + tel_write_err(stream, "invalid command"); + break; + } + + close(stream); +} + +// +// Processing network connections +// + +void accept_net(struct call_buf *call_buf, int fd) { + union tel_sa_any cli_addr; + socklen_t addr_len = sizeof(union tel_sa_any); + + int stream = accept(fd, (struct sockaddr *)&cli_addr, &addr_len); + if(stream < 0) { + perror("error accepting network connection"); + exit(1); + } + + char buf[TEL_STRADDRLEN]; + int res = tel_straddr((struct sockaddr *)&cli_addr, buf); + if(res >= 0) { + printf("accepted connection: %s\n", buf); + } + + + struct in_call call; + call.fd = stream; + call.addr = cli_addr; + call_buf_add(call_buf, call); +} + +// +// Main +// + +void run(struct config cfg) { + char trash[4096]; + + int client_sock = init_client_sock("/tmp/tel.sock"); + int net_sock = init_net_sock(cfg.addr, cfg.addr_len); + + struct call_buf call_buf; + call_buf_init(&call_buf); + + struct pollfd fds[2 + CALL_BUF_SIZE]; + fds[0].fd = client_sock; + fds[0].events = POLLIN; + fds[1].fd = net_sock; + fds[1].events = POLLIN; + + while(1) { + int fd_count = 2 + call_buf.len; + for(size_t i = 0; i < call_buf.len; i++) { + fds[i + 2].fd = call_buf.buf[i].fd; + fds[i + 2].events = POLLIN | POLLERR | POLLHUP; + } + int res = poll(fds, fd_count, -1); + if(res < 0) { + perror("error polling"); + exit(1); + } + + if(fds[0].revents & POLLIN) { + accept_client(&call_buf, fds[0].fd, cfg); + } + if(fds[1].revents & POLLIN) { + accept_net(&call_buf, fds[1].fd); + } + for(int i = 2; i < fd_count; i++) { + if(fds[i].revents & (POLLHUP | POLLHUP)) { + call_buf_remove(&call_buf, i-2); + close(fds[i].fd); + } + if(fds[i].revents & POLLIN) { + read(fds[i].fd, trash, 4096); + } + } + } +} + +int main(int argc, char **argv) { + if(argc < 1) return 1; + if(argc < 3) { + fprintf(stderr, "usage: %s \n", argv[0]); + exit(1); + } + + struct config cfg; + cfg.connect_timeout = 1000; + + // disable SIGPIPE handler + struct sigaction sa; + sa.sa_handler = SIG_IGN; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGPIPE, &sa, NULL); + + // get address from ip/port + char *ip = argv[1]; + char *port = argv[2]; + + int res = tel_get_addr(ip, port, &cfg.addr, &cfg.addr_len); + if(res != 0) { + fprintf(stderr, "error: %s\n", gai_strerror(res)); + exit(1); + } + + // run application + run(cfg); + return 0; +}