#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); if(res < 0) goto err; 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; err: printf("error picking up call: %s\n", strerror(errno)); tel_write_err(stream, strerror(errno)); } 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; }