Add support for IPv4 and IPv6 network sockets

Add support for IPv4 and IPv6 network sockets via socket syntax
"inet:<port>" and "inet6:<port>" respectively.

For example, to listen and redirect serial device I/O to a host bound
IPv4 socket simply do:

 $ tio /dev/ttyUSB0 --socket inet:4444

To connect do e.g.:

 $ nc 127.0.0.1 4444

Likewise, for IPv6 do:

 $ tio /dev/ttyUSB0 --socket inet6:4444

To connect do e.g.:

 $ nc ::1 4444

If port is 0 or no port is provided default port 3333 is used.
This commit is contained in:
Martin Lund 2022-06-11 13:59:59 +02:00
parent ba22191800
commit 17e96d70bc
2 changed files with 136 additions and 19 deletions

View file

@ -140,13 +140,21 @@ sequences are not recognized), and any input from the serial port is multiplexed
Sockets remain open while the serial port is disconnected, and writes will block. Sockets remain open while the serial port is disconnected, and writes will block.
Two socket types are supported using different prefixes in the socket field: The following socket types are supported using different prefixes in the socket field:
unix:<filename> - Unix Domain Socket (file)
inet:<IP>:<port> - Internet Socket (network) (NOT YET SUPPORTED)
.RS
.TP 20n
.IP "\fBunix:<filename>"
Unix Domain Socket (file)
.IP "\fBinet:<port>"
Internet Socket (network)
.IP "\fBinet6:<port>"
Internet IPv6 Socket (network)
.P
If port is 0 or no port is provided default port 3333 is used.
.P
At present there is a hardcoded limit of 16 clients connected at one time. At present there is a hardcoded limit of 16 clients connected at one time.
.RE
.TP .TP
.BR \-v ", " \-\-version .BR \-v ", " \-\-version

View file

@ -27,14 +27,19 @@
#include <errno.h> #include <errno.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/un.h> #include <sys/un.h>
#include <netinet/in.h>
#include <unistd.h> #include <unistd.h>
#define MAX_SOCKET_CLIENTS 16 #define MAX_SOCKET_CLIENTS 16
#define SOCKET_PORT_DEFAULT 3333
static int sockfd; static int sockfd;
static int clientfds[MAX_SOCKET_CLIENTS]; static int clientfds[MAX_SOCKET_CLIENTS];
static int socket_family = AF_UNSPEC;
static int port_number = SOCKET_PORT_DEFAULT;
static const char *socket_filename(void) static const char *socket_filename(void)
{ {
@ -42,57 +47,161 @@ static const char *socket_filename(void)
return option.socket + 5; return option.socket + 5;
} }
static int socket_inet_port(void)
{
/* skip 'inet:' */
int port_number = atoi(option.socket + 5);
if (port_number == 0)
{
port_number = SOCKET_PORT_DEFAULT;
}
return port_number;
}
static int socket_inet6_port(void)
{
/* skip 'inet6:' */
int port_number = atoi(option.socket + 6);
if (port_number == 0)
{
port_number = SOCKET_PORT_DEFAULT;
}
return port_number;
}
static void socket_exit(void) static void socket_exit(void)
{ {
unlink(socket_filename()); if (socket_family == AF_UNIX)
{
unlink(socket_filename());
}
} }
void socket_configure(void) void socket_configure(void)
{ {
struct sockaddr_un sockaddr_unix = {};
struct sockaddr_in sockaddr_inet = {};
struct sockaddr_in6 sockaddr_inet6 = {};
struct sockaddr *sockaddr_p;
socklen_t socklen;
if (!option.socket) if (!option.socket)
{ {
return; return;
} }
if (strncmp(option.socket, "unix:", 5) != 0) /* Parse socket string */
if (strncmp(option.socket, "unix:", 5) == 0)
{ {
error_printf("%s: Invalid socket scheme, must be 'unix:'", option.socket); socket_family = AF_UNIX;
exit(EXIT_FAILURE);
if (strlen(socket_filename()) == 0)
{
error_printf("Missing socket filename");
exit(EXIT_FAILURE);
}
if (strlen(socket_filename()) > sizeof(sockaddr_unix.sun_path) - 1)
{
error_printf("Socket file path %s too long", option.socket);
exit(EXIT_FAILURE);
}
} }
struct sockaddr_un sockaddr = {}; if (strncmp(option.socket, "inet:", 5) == 0)
if (strlen(socket_filename()) > sizeof(sockaddr.sun_path) - 1)
{ {
error_printf("Socket file path %s too long", option.socket); socket_family = AF_INET;
port_number = socket_inet_port();
if (port_number < 0)
{
error_printf("Invalid port number: %d", port_number);
exit(EXIT_FAILURE);
}
}
if (strncmp(option.socket, "inet6:", 6) == 0)
{
socket_family = AF_INET6;
port_number = socket_inet6_port();
if (port_number < 0)
{
error_printf("Invalid port number: %d", port_number);
exit(EXIT_FAILURE);
}
}
if (socket_family == AF_UNSPEC)
{
error_printf("%s: Invalid socket scheme, must be prefixed with 'unix:', 'inet:', or 'inet6:'", option.socket);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
sockaddr.sun_family = AF_UNIX; /* Configure socket */
strncpy(sockaddr.sun_path, socket_filename(), sizeof(sockaddr.sun_path) - 1);
sockfd = socket(AF_UNIX, SOCK_STREAM, 0); if (socket_family == AF_UNIX)
{
sockaddr_unix.sun_family = AF_UNIX;
strncpy(sockaddr_unix.sun_path, socket_filename(), sizeof(sockaddr_unix.sun_path) - 1);
sockaddr_p = (struct sockaddr *) &sockaddr_unix;
socklen = sizeof(sockaddr_unix);
}
if (socket_family == AF_INET)
{
sockaddr_inet.sin_family = AF_INET;
sockaddr_inet.sin_addr.s_addr = INADDR_ANY;
sockaddr_inet.sin_port = htons(port_number);
sockaddr_p = (struct sockaddr *) &sockaddr_inet;
socklen = sizeof(sockaddr_inet);
}
if (socket_family == AF_INET6)
{
sockaddr_inet6.sin6_family = AF_INET6;
inet_pton(AF_INET6, "::1", &sockaddr_inet6.sin6_addr);
sockaddr_inet6.sin6_port = htons(port_number);
sockaddr_p = (struct sockaddr *) &sockaddr_inet6;
socklen = sizeof(sockaddr_inet6);
}
/* Create socket */
sockfd = socket(socket_family, SOCK_STREAM, 0);
if (sockfd < 0) if (sockfd < 0)
{ {
error_printf("Failed to create socket: %s", strerror(errno)); error_printf("Failed to create socket: %s", strerror(errno));
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
if (bind(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0) /* Bind */
if (bind(sockfd, sockaddr_p, socklen) < 0)
{ {
error_printf("Failed to bind to socket %s: %s", socket_filename(), strerror(errno)); error_printf("Failed to bind to socket %s", strerror(errno));
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
/* Listen */
if (listen(sockfd, MAX_SOCKET_CLIENTS) < 0) if (listen(sockfd, MAX_SOCKET_CLIENTS) < 0)
{ {
error_printf("Failed to listen on socket %s: %s", socket_filename(), strerror(errno)); error_printf("Failed to listen on socket %s", strerror(errno));
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
memset(clientfds, -1, sizeof(clientfds)); memset(clientfds, -1, sizeof(clientfds));
atexit(socket_exit); atexit(socket_exit);
tio_printf("Listening on socket %s", socket_filename()); if (socket_family == AF_UNIX)
{
tio_printf("Listening on socket %s", socket_filename());
}
else
{
tio_printf("Listening on socket port %d", port_number);
}
} }
void socket_write(char input_char) void socket_write(char input_char)