tel/teld.c

400 lines
8.1 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <sys/poll.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <signal.h>
#include <netdb.h>
#include <fcntl.h>
#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 <addr> <port>\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;
}