2023-04-01 10:00:13 +08:00

402 lines
10 KiB
C

/*
* Copyright (C) 2018 Marco d'Itri
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/* for getaddrinfo... */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <ctype.h>
#include "network.h"
#include "utils.h"
#include "log.h"
char *print_addr_port(const struct sockaddr *addr, socklen_t addrlen)
{
static char buf[1100], address[1025], port[32];
int err;
err = getnameinfo(addr, addrlen, address, sizeof(address),
port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV);
if (err == EAI_SYSTEM)
err_sys("getnameinfo");
else if (err)
log_printf_exit(1, log_err, "getnameinfo: %s", gai_strerror(err));
if (addr->sa_family == AF_INET6)
snprintf(buf, sizeof(buf) - 1, "[%s]:%s", address, port);
else
snprintf(buf, sizeof(buf) - 1, "%s:%s", address, port);
return buf;
}
static char *ai_print_addr_port(struct addrinfo *ai)
{
return print_addr_port((struct sockaddr *) ai->ai_addr, ai->ai_addrlen);
}
/*
* Try to parse anything that looks like:
* - an IPv4 or IPv6 address or a domain, with an optional port number
* - a port number
*
* address and/or port will be NULL if not found in the input.
* If address and port are not NULL then they must be freed by the caller.
*/
static void parse_address_port(const char *input, char **address, char **port)
{
const char *p;
*address = NULL;
*port = NULL;
if (*input == '\0') {
return;
} else if (*input == '[' && (p = strchr(input, ']'))) { /* IPv6 */
char *s;
int len = p - input - 1;
*address = s = NOFAIL(malloc(len + 1));
memcpy(s, input + 1, len);
*(s + len) = '\0';
p = strchr(p, ':');
if (p && *(p + 1) != '\0')
*port = NOFAIL(strdup(p + 1)); /* IPv6 + port */
} else if ((p = strchr(input, ':')) && /* IPv6, no port */
strchr(p + 1, ':')) { /* and no brackets */
*address = NOFAIL(strdup(input));
} else if ((p = strchr(input, ':'))) { /* IPv4 + port */
char *s;
int len = p - input;
if (len) {
*address = s = NOFAIL(malloc(len + 1));
memcpy(s, input, len);
*(s + len) = '\0';
}
p++;
if (*p != '\0')
*port = NOFAIL(strdup(p));
} else {
for (p = input; *p; p++)
if (!isdigit(p[0]))
break;
if (*p)
*address = NOFAIL(strdup(input)); /* IPv4, no port */
else
*port = NOFAIL(strdup(input)); /* just the port */
}
}
int udp_listener(const char *s)
{
char *address, *port;
struct addrinfo hints, *res, *ai;
int err, fd;
parse_address_port(s, &address, &port);
if (!port)
log_printf_exit(2, log_err, "Missing port in '%s'!", s);
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE | AI_IDN;
err = getaddrinfo(address, port, &hints, &res);
if (err == EAI_SYSTEM)
err_sys("getaddrinfo(%s:%s)", address, port);
else if (err)
log_printf_exit(1, log_err, "Cannot resolve %s:%s: %s",
address, port, gai_strerror(err));
if (address)
free(address);
if (port)
free(port);
for (ai = res; ai; ai = ai->ai_next) {
if ((fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0)
continue; /* ignore */
if (bind(fd, (struct sockaddr *) ai->ai_addr, ai->ai_addrlen) == 0)
break; /* success */
close(fd);
}
if (!ai)
err_sys("Cannot bind to %s", s);
log_printf(log_info, "Listening for UDP connections on %s",
ai_print_addr_port(ai));
freeaddrinfo(res);
return fd;
}
int *tcp_listener(const char *s)
{
char *address, *port;
struct addrinfo hints, *res, *ai;
int err, fd, opt;
int fd_num = 0;
int *fd_list = NULL;
size_t allocated_fds = 0;
parse_address_port(s, &address, &port);
if (!port)
log_printf_exit(2, log_err, "Missing port in '%s'!", s);
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE | AI_IDN;
err = getaddrinfo(address, port, &hints, &res);
if (err == EAI_SYSTEM)
err_sys("getaddrinfo(%s:%s)", address, port);
else if (err)
log_printf_exit(1, log_err, "Cannot resolve %s:%s: %s",
address, port, gai_strerror(err));
if (address)
free(address);
if (port)
free(port);
/* add to fd_list all the sockets which match ai_flags */
for (ai = res; ai; ai = ai->ai_next) {
if ((fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0)
continue; /* ignore */
opt = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
err_sys("setsockopt(SOL_SOCKET, SO_REUSEADDR)");
if (bind(fd, (struct sockaddr *) ai->ai_addr, ai->ai_addrlen) < 0)
err_sys("Cannot bind to %s", s);
/* success */
if (listen(fd, 128) < 0)
err_sys("listen");
if (allocated_fds < fd_num + 1 + 1) {
allocated_fds += 8;
fd_list = realloc(fd_list, allocated_fds * sizeof(int));
}
fd_list[fd_num++] = fd;
log_printf(log_info, "Listening for TCP connections on %s",
ai_print_addr_port(ai));
}
/* and then add -1 as the list terminator */
if (allocated_fds < fd_num + 1 + 1)
fd_list = realloc(fd_list, ++allocated_fds * sizeof(int));
fd_list[fd_num] = -1;
if (!fd_list)
err_sys("socket");
freeaddrinfo(res);
return fd_list;
}
/*
* Accept new connections and return after forking for each one.
*/
int accept_connections(int listening_sockets[])
{
while (1) {
int max = 0;
int i, fd;
fd_set readfds;
pid_t pid;
FD_ZERO(&readfds);
for (i = 0; listening_sockets[i] != -1; i++) {
int flags;
if ((flags = fcntl(listening_sockets[i], F_GETFL, 0)) < 0)
err_sys("fcntl(F_GETFL)");
if (fcntl(listening_sockets[i], F_SETFL, flags | O_NONBLOCK) < 0)
err_sys("fcntl(F_SETFL, O_NONBLOCK)");
FD_SET(listening_sockets[i], &readfds);
SET_MAX(listening_sockets[i]);
}
if (select(max, &readfds, NULL, NULL, NULL) < 0) {
if (errno == EINTR || errno == EAGAIN)
continue;
err_sys("select");
}
for (i = 0; listening_sockets[i] != -1; i++) {
int listen_sock;
struct sockaddr_storage client_addr;
socklen_t addrlen = sizeof(client_addr);
if (!FD_ISSET(listening_sockets[i], &readfds))
continue;
listen_sock = listening_sockets[i];
fd = accept(listen_sock, (struct sockaddr *) &client_addr, &addrlen);
if (fd < 0) {
if (errno == EAGAIN)
continue;
err_sys("accept");
}
log_printf(log_notice, "Received a TCP connection from %s",
print_addr_port((struct sockaddr *) &client_addr, addrlen));
#if 0
/* do not fork, for testing */
pid = 0;
#else
pid = fork();
#endif
if (pid < 0)
err_sys("fork");
if (pid > 0) {
close(fd);
} else {
for (i = 0; listening_sockets[i] != -1; i++)
close(listening_sockets[i]);
return fd;
}
}
}
}
int udp_client(const char *s, struct sockaddr_storage *remote_udpaddr)
{
char *address, *port;
struct addrinfo hints, *res, *ai;
int err, fd;
parse_address_port(s, &address, &port);
if (!address || !port)
log_printf_exit(2, log_err, "Missing address or port in '%s'!", s);
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_ADDRCONFIG | AI_IDN;
err = getaddrinfo(address, port, &hints, &res);
if (err == EAI_SYSTEM)
err_sys("getaddrinfo(%s:%s)", address, port);
else if (err)
log_printf_exit(1, log_err, "Cannot resolve %s:%s: %s",
address, port, gai_strerror(err));
if (address)
free(address);
if (port)
free(port);
/* continue with the first socket which matches ai_flags */
for (ai = res; ai; ai = ai->ai_next) {
if ((fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0)
continue; /* ignore */
break; /* success */
}
if (!ai)
err_sys("socket");
log_printf(log_debug, "The UDP destination is %s", ai_print_addr_port(ai));
/*
* Return to the caller the resolved address, to be able to use it as the
* destination address of the next UDP packet.
*/
if (remote_udpaddr)
memcpy(remote_udpaddr, ai->ai_addr, ai->ai_addrlen);
freeaddrinfo(res);
return fd;
}
int tcp_client(const char *s)
{
char *address, *port;
struct addrinfo hints, *res, *ai;
int err, fd;
parse_address_port(s, &address, &port);
if (!address || !port)
log_printf_exit(2, log_err, "Missing address or port in '%s'!", s);
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG | AI_IDN;
err = getaddrinfo(address, port, &hints, &res);
if (err == EAI_SYSTEM)
err_sys("getaddrinfo(%s:%s)", address, port);
else if (err)
log_printf_exit(1, log_err, "Cannot resolve %s:%s: %s",
address, port, gai_strerror(err));
if (address)
free(address);
if (port)
free(port);
for (ai = res; ai; ai = ai->ai_next) {
if ((fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0)
continue; /* ignore */
if (connect(fd, (struct sockaddr *) ai->ai_addr, ai->ai_addrlen) == 0)
break; /* success */
close(fd);
}
if (!ai)
err_sys("Cannot connect to %s", s);
log_printf(log_info, "TCP connection opened to %s",
ai_print_addr_port(ai));
freeaddrinfo(res);
return fd;
}