395 lines
8 KiB
C
395 lines
8 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);
|
||
|
|
||
|
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 <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;
|
||
|
}
|