17 Commits

Author SHA1 Message Date
Kim Woelders 1081fd9235 ttynvt version 0.17 2025-06-25 17:32:41 +02:00
Frank Rolsted Jensen 72e09527b9 Handling of pthread_mutex_init failure fixed
pthread_mutex_init() returns errno on failure
2025-06-04 12:02:39 +02:00
Frank Rolsted Jensen 10d5c9cf3b Handling of initial protocol error changed
The client accepts a long(er) response time when waiting for server to
enable RFC2217 (telnet option).

Bug in hangling of rx timeout fixed
2025-06-04 12:02:34 +02:00
Kim Woelders 4d3f7d384b ttynvt: Move optional delay during close
Move to after closing the fds.

As suggested in https://gitlab.com/lars-thrane-as/ttynvt/-/issues/11.
2025-02-25 20:03:05 +01:00
Kim Woelders 1f516288ab test: Initial testing framework
Configure with --enable-testtools.

BLD_ROOT is now the top build directory.

Current test requires ttynvt to be started (by root) like
 # BLD_ROOT/src/ttynvt -d -E -S localhost:1234 -n ttyNVT99
Device and server name parameters must be as shown above.

Run tests like
 $ make -C BLD_ROOT test 	# Builds the other programs too
 $ make -C BLD_ROOT/test	# Only running tests
 $ make -C BLD_ROOT/test V=1 RUN_OPTS="--gtest_filter=Test1.t1"
2024-11-10 15:01:12 +01:00
Kim Woelders 55c5d0d226 nvtsrv: Change some log messages to debug 2024-11-10 13:34:38 +01:00
Kim Woelders cd5de2fe5d nvttest: Return error if anything fails 2024-11-10 13:31:30 +01:00
Kim Woelders da027d4de1 nvttest: Rework thread handling 2024-11-10 13:31:30 +01:00
Kim Woelders 3bcbf9c8c1 nvttest: Enable specifying device
Also check that the device file exists.
2024-11-10 11:30:16 +01:00
Kim Woelders 611ab3cbf3 nvttest: Improve help
And do help stuff like in nvtsrv.
2024-11-10 11:29:22 +01:00
Kim Woelders 6e2e530215 nvtsrv: Fix inconsistent help 2024-11-10 11:20:52 +01:00
Kim Woelders 3d5848aff0 ttynvt: On TCSET* skip rfc2217 baud rate change if zero
https://gitlab.com/lars-thrane-as/ttynvt/-/merge_requests/15
2024-10-26 10:52:40 +02:00
Nebojsa Sabovic 4223a4b92a Support odd parity. 2024-10-24 19:02:08 +02:00
Kim Woelders 2c090f3721 Re-indent everything with indent-2.2.13 2024-03-06 17:21:48 +01:00
Kim Woelders 797ccf08c8 socket: Indent according to profile 2024-03-06 17:18:46 +01:00
s-wakaba@gitlab.com 9739087594 support TCP/IPv6 address and UNIX domain socket path as rfc2217 server 2024-03-06 17:13:28 +01:00
Kim Woelders f41067c3be nvttest: Add missing -lpthread 2024-03-05 11:20:09 +01:00
16 changed files with 607 additions and 101 deletions
+8
View File
@@ -9,3 +9,11 @@ if BUILD_TESTTOOLS
SUBDIRS += nvtsrv SUBDIRS += nvtsrv
SUBDIRS += nvttest SUBDIRS += nvttest
endif endif
if BUILD_TEST
SUBDIRS += test
endif
.PHONY: $(SUBDIRS) test
$(SUBDIRS) test:
$(MAKE) -C $@
test: $(SUBDIRS)
+5 -2
View File
@@ -1,4 +1,4 @@
AC_INIT([ttynvt],[0.16],[frj@thrane.eu]) AC_INIT([ttynvt],[0.17],[frj@thrane.eu])
AM_INIT_AUTOMAKE([foreign dist-xz]) AM_INIT_AUTOMAKE([foreign dist-xz])
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
@@ -6,10 +6,10 @@ m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
AC_USE_SYSTEM_EXTENSIONS AC_USE_SYSTEM_EXTENSIONS
AC_PROG_CC AC_PROG_CC
AC_PROG_CXX
AC_PROG_INSTALL AC_PROG_INSTALL
define([AC_LIBTOOL_LANG_CXX_CONFIG], [:])dnl
define([AC_LIBTOOL_LANG_F77_CONFIG], [:])dnl define([AC_LIBTOOL_LANG_F77_CONFIG], [:])dnl
define([AC_LIBTOOL_LANG_GCJ_CONFIG], [:])dnl define([AC_LIBTOOL_LANG_GCJ_CONFIG], [:])dnl
@@ -31,6 +31,8 @@ AC_ARG_ENABLE(testtools,
enable_testtools=no) enable_testtools=no)
AM_CONDITIONAL([BUILD_TESTTOOLS], [test "x$enable_testtools" = "xyes"]) AM_CONDITIONAL([BUILD_TESTTOOLS], [test "x$enable_testtools" = "xyes"])
AM_CONDITIONAL(BUILD_TEST, false)
CFLAGS_WARN="$CFLAGS_WARN -Wall -Wextra -Werror -Wno-unused-parameter" CFLAGS_WARN="$CFLAGS_WARN -Wall -Wextra -Werror -Wno-unused-parameter"
CFLAGS_WARN="$CFLAGS_WARN -Wmissing-prototypes -Wmissing-declarations -Wstrict-prototypes" CFLAGS_WARN="$CFLAGS_WARN -Wmissing-prototypes -Wmissing-declarations -Wstrict-prototypes"
CFLAGS_WARN="$CFLAGS_WARN -Waggregate-return -Wpointer-arith -Wshadow -Wwrite-strings" CFLAGS_WARN="$CFLAGS_WARN -Waggregate-return -Wpointer-arith -Wshadow -Wwrite-strings"
@@ -44,6 +46,7 @@ lib/Makefile
src/Makefile src/Makefile
nvtsrv/Makefile nvtsrv/Makefile
nvttest/Makefile nvttest/Makefile
test/Makefile
]) ])
AC_OUTPUT AC_OUTPUT
+83 -15
View File
@@ -7,6 +7,7 @@
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/un.h>
#include "nvt_log.h" #include "nvt_log.h"
#include "nvt_socket.h" #include "nvt_socket.h"
@@ -18,42 +19,109 @@ typedef struct {
union { union {
struct sockaddr gen; struct sockaddr gen;
struct sockaddr_in ipv4; struct sockaddr_in ipv4;
struct sockaddr_in6 ipv6;
struct sockaddr_un un;
}; };
} sa_t; } sa_t;
static int _sa_host_lookup(sa_t * sa, const char *host) static int _sa_host_lookup(sa_t *sa, const char *host, const char *port)
{ {
struct hostent *server; struct addrinfo hint, *adrinf = NULL, *p;
int ret = -1;
server = gethostbyname(host); memset(&hint, 0, sizeof(struct addrinfo));
if (server == NULL)
if (getaddrinfo(host, port, &hint, &adrinf) != 0)
{ {
nvt_log(LOG_ERR, "Cannot resolve host name: %m\n"); nvt_log(LOG_ERR, "Cannot resolve host name: %m\n");
return -1; return -1;
} }
// Priority is given to the use of IPv4
// This code must be modified if the preffered protocol is changed or varied.
for (p = adrinf; p; p = p->ai_next)
{
if (p->ai_family == AF_INET)
{
sa->slen = sizeof(sa->ipv4); sa->slen = sizeof(sa->ipv4);
memcpy(&sa->ipv4, p->ai_addr, sizeof(struct sockaddr_in));
sa->ipv4.sin_family = AF_INET; ret = 0;
memcpy(&sa->ipv4.sin_addr.s_addr, server->h_addr, server->h_length); goto exit_host_lookup;
}
return 0; }
for (p = adrinf; p; p = p->ai_next)
{
if (p->ai_family == AF_INET6)
{
sa->slen = sizeof(sa->ipv6);
memcpy(&sa->ipv6, p->ai_addr, sizeof(struct sockaddr_in6));
ret = 0;
goto exit_host_lookup;
}
}
exit_host_lookup:
freeaddrinfo(adrinf);
return ret;
} }
static int _sa_addr_parse(sa_t * sa, const char *addr) static int _sa_addr_parse(sa_t *sa, const char *addr)
{ {
char host[64], port[64]; ssize_t host_len;
char host[64];
const char *port;
memset(sa, 0, sizeof(sa_t)); memset(sa, 0, sizeof(sa_t));
sscanf(addr, "%63[^:]:%63s", host, port); if (strncmp(addr, "unix:", 5) == 0)
{ // case of unix domain sockets ...
addr += 5; // skip "unix:" prefix
if (strlen(addr) > sizeof(sa->un.sun_path) - 1)
{
nvt_log(LOG_ERR, "UNIX domain socket path is too long.\n");
return -1;
}
sa->slen = sizeof(sa->un);
sa->un.sun_family = AF_UNIX;
strcpy(sa->un.sun_path, addr);
sa->type = SOCK_STREAM;
return 0;
}
if (_sa_host_lookup(sa, host) != 0) if (!(port = strrchr(addr, ':')))
{
nvt_log(LOG_ERR, "bad server address format.\n");
return -1;
}
host_len = (port++) - addr;
// *port indicates the last ':', its next is 1st char of the port number
if (host_len >= 2 && addr[0] == '[' && addr[host_len - 1] == ']')
{
// IPv6 addresses are enclosed in pair of square brackets.
addr += 1;
host_len -= 2;
}
else
{
if (strchr(addr, ':') != port - 1)
{
nvt_log(LOG_ERR,
"addresses containing colons must be enclosed in square brackets.\n");
return -1;
}
}
if ((ssize_t) sizeof(host) <= host_len)
{
nvt_log(LOG_ERR, "host name is too long.\n");
return -1;
}
strncpy(host, addr, host_len);
host[host_len] = '\0'; // strncpy doesn't add null char
if (_sa_host_lookup(sa, host, port) != 0)
return -1; return -1;
sa->type = SOCK_STREAM; sa->type = SOCK_STREAM;
sa->ipv4.sin_port = htons(atoi(port));
return 0; return 0;
} }
@@ -103,7 +171,7 @@ int socket_create_server(const char *addr)
return -1; return -1;
} }
if (sa.gen.sa_family == AF_INET) if (sa.gen.sa_family == AF_INET || sa.gen.sa_family == AF_INET6)
{ {
/* Avoid TIME_WAIT state on socket close */ /* Avoid TIME_WAIT state on socket close */
opt = 1; opt = 1;
+17 -11
View File
@@ -14,9 +14,15 @@
#include "telnet.h" #include "telnet.h"
#define HELP \ /**INDENT-OFF**/
"Usage: nvtsrv -deq\n" static const char help_text[] =
"Usage: nvtsrv OPTIONS... HOST:PORT\n"
"OPTIONS:\n"
" -d : Enable debug\n"
" -e : Echo received data\n"
" -q : Quiet\n"
;
/**INDENT-ON**/
struct { struct {
unsigned char mline; unsigned char mline;
@@ -30,10 +36,10 @@ int nfds;
#define fd_cli pfds[2].fd #define fd_cli pfds[2].fd
static void _nvtsrv_usage(void) static void _usage(int rc)
{ {
printf("Usage:\n" " nvtsrv host:port\n"); printf(help_text);
exit(0); exit(rc);
} }
static void _tn_cli_close(void) static void _tn_cli_close(void)
@@ -68,7 +74,7 @@ static void _tn_tx(void *cctx, const void *buf, int len)
static void _tn_ss(void *cctx, int signal, int value) static void _tn_ss(void *cctx, int signal, int value)
{ {
nvt_log(LOG_INFO, "RFC2217 signal %d: %02x", signal, value); DBG("RFC2217 signal %d: %02x", signal, value);
} }
static void _nvtsrv_init(void) static void _nvtsrv_init(void)
@@ -80,7 +86,7 @@ static void _nvtsrv_init(void)
dd.tnct = telnet_ctx_init(NULL, _tn_tx, _tn_ss); dd.tnct = telnet_ctx_init(NULL, _tn_tx, _tn_ss);
} }
static void _nvtsrv_cmd(tn_ctx_t * tcc, const char *line, int len) static void _nvtsrv_cmd(tn_ctx_t *tcc, const char *line, int len)
{ {
int val; int val;
@@ -127,8 +133,8 @@ int main(int argc, char **argv)
switch (opt) switch (opt)
{ {
default: /* '?' */ default: /* '?' */
fprintf(stderr, HELP); _usage(0);
exit(EXIT_FAILURE); break;
case 'd': case 'd':
debug += 1; debug += 1;
break; break;
@@ -144,7 +150,7 @@ int main(int argc, char **argv)
argc -= optind; argc -= optind;
argv += optind; argv += optind;
if (argc <= 0) if (argc <= 0)
_nvtsrv_usage(); _usage(1);
nvt_log_level(quiet, debug); nvt_log_level(quiet, debug);
nvt_log_dest(NVT_LOG_STDOUT); nvt_log_dest(NVT_LOG_STDOUT);
+6 -6
View File
@@ -36,7 +36,7 @@ static const unsigned char tn_opt[256] = {
}; };
static void _telnet_handle_req(tn_ctx_t * tcc, int cmd, int opt) static void _telnet_handle_req(tn_ctx_t *tcc, int cmd, int opt)
{ {
char buf[3]; char buf[3];
@@ -63,7 +63,7 @@ static void _telnet_handle_req(tn_ctx_t * tcc, int cmd, int opt)
telnet_rfc2217_init(tcc, TNI_ON); telnet_rfc2217_init(tcc, TNI_ON);
} }
void telnet_reply_opt(tn_ctx_t * tcc, int opt, int prm, void telnet_reply_opt(tn_ctx_t *tcc, int opt, int prm,
const void *val, int len) const void *val, int len)
{ {
unsigned char buf[128], *pu = buf; unsigned char buf[128], *pu = buf;
@@ -94,7 +94,7 @@ void telnet_reply_opt(tn_ctx_t * tcc, int opt, int prm,
tcc->cli_tx(tcc->cctx, buf, len); tcc->cli_tx(tcc->cctx, buf, len);
} }
static void _telnet_handle_opt(tn_ctx_t * tcc, int opt, static void _telnet_handle_opt(tn_ctx_t *tcc, int opt,
unsigned char *pu, int nd) unsigned char *pu, int nd)
{ {
DBG2_BUF("opti", pu, nd); DBG2_BUF("opti", pu, nd);
@@ -111,7 +111,7 @@ static void _telnet_handle_opt(tn_ctx_t * tcc, int opt,
} }
} }
int telnet_rx(tn_ctx_t * tcc, char *buf, int *plen) int telnet_rx(tn_ctx_t *tcc, char *buf, int *plen)
{ {
char *s, *p; char *s, *p;
unsigned char *pu; unsigned char *pu;
@@ -230,7 +230,7 @@ int telnet_rx(tn_ctx_t * tcc, char *buf, int *plen)
return 0; return 0;
} }
int telnet_tx(tn_ctx_t * tcc, const char *buf, int len) int telnet_tx(tn_ctx_t *tcc, const char *buf, int len)
{ {
const char *s, *p; const char *s, *p;
int rem, nd; int rem, nd;
@@ -260,7 +260,7 @@ int telnet_tx(tn_ctx_t * tcc, const char *buf, int len)
return 0; return 0;
} }
tn_ctx_t *telnet_ctx_init(void *cctx, cli_tx_f * ftx, cli_ss_f * fss) tn_ctx_t *telnet_ctx_init(void *cctx, cli_tx_f *ftx, cli_ss_f *fss)
{ {
tn_ctx_t *tcc; tn_ctx_t *tcc;
+6 -8
View File
@@ -10,7 +10,7 @@
#include "telnet.h" #include "telnet.h"
#include "telnet_param.h" #include "telnet_param.h"
void telnet_rfc2217_init(tn_ctx_t * tcc, int when) void telnet_rfc2217_init(tn_ctx_t *tcc, int when)
{ {
switch (when) switch (when)
{ {
@@ -39,7 +39,7 @@ void telnet_rfc2217_init(tn_ctx_t * tcc, int when)
} }
void telnet_rfc2217_handle_opt(tn_ctx_t * tcc, int opt, void telnet_rfc2217_handle_opt(tn_ctx_t *tcc, int opt,
unsigned char *pu, int nd) unsigned char *pu, int nd)
{ {
unsigned int sub, vali, valo, val4; unsigned int sub, vali, valo, val4;
@@ -138,8 +138,7 @@ void telnet_rfc2217_handle_opt(tn_ctx_t * tcc, int opt,
case 9: /* Set DTR Signal State OFF */ case 9: /* Set DTR Signal State OFF */
valo = vali; valo = vali;
tcc->dtr_state = (valo == 8) ? 1 : 0; tcc->dtr_state = (valo == 8) ? 1 : 0;
nvt_log(LOG_INFO, "RFC2217: Set DTR %s", DBG("RFC2217: Set DTR %s", tcc->dtr_state ? "on" : "off");
tcc->dtr_state ? "on" : "off");
goto do_ctl_reply; goto do_ctl_reply;
case 10: /* Request RTS Signal State */ case 10: /* Request RTS Signal State */
@@ -149,8 +148,7 @@ void telnet_rfc2217_handle_opt(tn_ctx_t * tcc, int opt,
case 12: /* Set RTS Signal State OFF */ case 12: /* Set RTS Signal State OFF */
valo = vali; valo = vali;
tcc->rts_state = (valo == 11) ? 1 : 0; tcc->rts_state = (valo == 11) ? 1 : 0;
nvt_log(LOG_INFO, "RFC2217: Set RTS %s", DBG("RFC2217: Set RTS %s", tcc->rts_state ? "on" : "off");
tcc->rts_state ? "on" : "off");
goto do_ctl_reply; goto do_ctl_reply;
case 13: /* Request Com Port Flow Control Setting (inbound) */ case 13: /* Request Com Port Flow Control Setting (inbound) */
@@ -220,7 +218,7 @@ void telnet_rfc2217_handle_opt(tn_ctx_t * tcc, int opt,
} }
} }
void telnet_rfc2217_change_linestate(tn_ctx_t * tcc, int mask) void telnet_rfc2217_change_linestate(tn_ctx_t *tcc, int mask)
{ {
unsigned char buf[1]; unsigned char buf[1];
@@ -238,7 +236,7 @@ void telnet_rfc2217_change_linestate(tn_ctx_t * tcc, int mask)
tcc->linestate = mask; tcc->linestate = mask;
} }
void telnet_rfc2217_change_modemstate(tn_ctx_t * tcc, int mask) void telnet_rfc2217_change_modemstate(tn_ctx_t *tcc, int mask)
{ {
unsigned char buf[1]; unsigned char buf[1];
+1 -1
View File
@@ -6,4 +6,4 @@ nvttest_SOURCES = \
nvttest_CPPFLAGS = -I$(top_builddir) -I$(top_srcdir) $(CFLAGS_WARN) nvttest_CPPFLAGS = -I$(top_builddir) -I$(top_srcdir) $(CFLAGS_WARN)
nvttest_LDADD = $(top_builddir)/lib/libnvt.la nvttest_LDADD = $(top_builddir)/lib/libnvt.la -lpthread
+67 -22
View File
@@ -7,6 +7,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h> #include <fcntl.h>
@@ -16,10 +17,20 @@
#include "lib/nvt_log.h" #include "lib/nvt_log.h"
#define HELP \ /**INDENT-OFF**/
"Usage: nvttest -dqr [-n loops] [-t threads]\n" static const char help_text[] =
"Usage: nvttest OPTIONS...\n"
"OPTIONS:\n"
" -D name : Specify device name (/dev/name, default /dev/ttyNVT0)\n"
" -d : Enable debug\n"
" -n loops : Test loops per thread\n"
" -q : Quiet\n"
" -r : Insert random delay in loops\n"
" -t threads : N. threads\n"
;
/**INDENT-ON**/
static const char *dev = "/dev/ttyNVT0"; static char dev[64] = "/dev/ttyNVT0";
typedef struct { typedef struct {
int nthread; int nthread;
@@ -28,22 +39,37 @@ typedef struct {
bool opt_rand_delay; bool opt_rand_delay;
} topts_t; } topts_t;
typedef struct {
pthread_t thr;
topts_t opts;
int errors;
} thr_t;
static void _usage(int rc)
{
printf(help_text);
exit(rc);
}
static void *_worker(void *arg) static void *_worker(void *arg)
{ {
topts_t *topts = arg; thr_t *thr = arg;
int iloop; int iloop;
int fd, err, nw; int fd, err, nw;
struct termios ios; struct termios ios;
for (iloop = 0; iloop < topts->nloop; iloop++) for (iloop = 0; iloop < thr->opts.nloop; iloop++)
{ {
nvt_log(LOG_INFO, "Thread %d/%d run %d/%d\n", nvt_log(LOG_INFO, "Thread %d/%d run %d/%d\n",
topts->ithread, topts->nthread, iloop + 1, topts->nloop); thr->opts.ithread, thr->opts.nthread,
iloop + 1, thr->opts.nloop);
{ {
fd = open(dev, O_RDWR); fd = open(dev, O_RDWR);
if (fd < 0) if (fd < 0)
{ {
nvt_log(LOG_ERR, "Open '%s' failed: %m\n", dev); nvt_log(LOG_ERR, "Open '%s' failed: %m\n", dev);
thr->errors++;
break; break;
} }
@@ -52,12 +78,14 @@ static void *_worker(void *arg)
if (err != 0) if (err != 0)
{ {
nvt_log(LOG_ERR, "ioctl(TCGETS) failed: %m\n"); nvt_log(LOG_ERR, "ioctl(TCGETS) failed: %m\n");
thr->errors++;
break; break;
} }
err = ioctl(fd, TCSETS, &ios); err = ioctl(fd, TCSETS, &ios);
if (err != 0) if (err != 0)
{ {
nvt_log(LOG_ERR, "ioctl(TCSETS) failed: %m\n"); nvt_log(LOG_ERR, "ioctl(TCSETS) failed: %m\n");
thr->errors++;
break; break;
} }
close(fd); close(fd);
@@ -74,6 +102,7 @@ static void *_worker(void *arg)
if (fd < 0) if (fd < 0)
{ {
nvt_log(LOG_ERR, "Open '%s' failed: %m\n", dev); nvt_log(LOG_ERR, "Open '%s' failed: %m\n", dev);
thr->errors++;
break; break;
} }
@@ -84,18 +113,20 @@ static void *_worker(void *arg)
if (nw < 0) if (nw < 0)
{ {
nvt_log(LOG_ERR, "write failed: %m\n"); nvt_log(LOG_ERR, "write failed: %m\n");
thr->errors++;
break; break;
} }
if (nw < len) if (nw < len)
{ {
nvt_log(LOG_ERR, "write short: %u/%u\n", nw, len); nvt_log(LOG_ERR, "write short: %u/%u\n", nw, len);
thr->errors++;
break; break;
} }
} }
close(fd); close(fd);
} }
if (topts->opt_rand_delay) if (thr->opts.opt_rand_delay)
usleep(rand() & 0xfff); usleep(rand() & 0xfff);
} }
@@ -104,24 +135,28 @@ static void *_worker(void *arg)
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
int err; int err, errors;
int opt; int opt;
int ithr, nthr; int ithr, nthr;
topts_t topts = { }, *popts; thr_t *threads, *thr;
pthread_t *ptids; topts_t topts = { };
int debug, quiet; int debug, quiet;
struct stat st;
debug = quiet = 0; debug = quiet = 0;
nthr = 1; nthr = 1;
topts.nloop = 1; topts.nloop = 1;
while ((opt = getopt(argc, argv, "dn:qrt:")) != -1) while ((opt = getopt(argc, argv, "D:dn:qrt:")) != -1)
{ {
switch (opt) switch (opt)
{ {
default: /* '?' */ default: /* '?' */
fprintf(stderr, HELP); _usage(1);
exit(EXIT_FAILURE); break;
case 'D':
snprintf(dev, sizeof(dev), "/dev/%s", optarg);
break;
case 'd': case 'd':
debug += 1; debug += 1;
break; break;
@@ -140,22 +175,28 @@ int main(int argc, char **argv)
} }
} }
if (stat(dev, &st) || !S_ISCHR(st.st_mode))
{
fprintf(stderr, "Invalid device file: '%s'\n", dev);
exit(EXIT_FAILURE);
}
nvt_log_level(quiet, debug); nvt_log_level(quiet, debug);
nvt_log_dest(NVT_LOG_STDOUT); nvt_log_dest(NVT_LOG_STDOUT);
topts.nthread = nthr; topts.nthread = nthr;
ptids = malloc(nthr * sizeof(pthread_t)); threads = calloc(nthr, sizeof(thr_t));
for (ithr = 0; ithr < nthr; ithr++) for (ithr = 0; ithr < nthr; ithr++)
{ {
nvt_log(LOG_INFO, "Create worker thread %d/%d\n", ithr + 1, nthr); nvt_log(LOG_INFO, "Create worker thread %d/%d\n", ithr + 1, nthr);
popts = malloc(sizeof(topts_t)); thr = &threads[ithr];
*popts = topts; thr->opts = topts;
popts->ithread = ithr + 1; thr->opts.ithread = ithr + 1;
err = pthread_create(&ptids[ithr], NULL, _worker, popts); err = pthread_create(&thr->thr, NULL, _worker, thr);
if (err) if (err)
{ {
nvt_log(LOG_ERR, "Failed to create thread %d/%d\n", ithr, nthr); nvt_log(LOG_ERR, "Failed to create thread %d/%d\n", ithr, nthr);
@@ -163,13 +204,17 @@ int main(int argc, char **argv)
} }
} }
errors = 0;
for (ithr = 0; ithr < nthr; ithr++) for (ithr = 0; ithr < nthr; ithr++)
{ {
if (ptids[ithr] == 0) thr = &threads[ithr];
if (thr->thr == 0)
break; break;
pthread_join(ptids[ithr], NULL); pthread_join(thr->thr, NULL);
nvt_log(LOG_INFO, "Joined worker thread %d/%d\n", ithr + 1, nthr); errors += thr->errors;
nvt_log(LOG_INFO, "Joined worker thread %d/%d (err=%d)\n",
ithr + 1, nthr, thr->errors);
} }
return 0; return errors ? 1 : 0;
} }
+14 -13
View File
@@ -7,6 +7,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include <errno.h>
#include "telnet.h" #include "telnet.h"
#include "telnet_param.h" #include "telnet_param.h"
@@ -38,7 +39,7 @@ static const unsigned char tn_opt[256] = {
}; };
static void _telnet_handle_req(tn_ctx_t * tcc, int cmd, int opt) static void _telnet_handle_req(tn_ctx_t *tcc, int cmd, int opt)
{ {
char buf[3]; char buf[3];
@@ -67,7 +68,7 @@ static void _telnet_handle_req(tn_ctx_t * tcc, int cmd, int opt)
tcc->srv_tx(tcc->cctx, buf, 3); tcc->srv_tx(tcc->cctx, buf, 3);
} }
static void _telnet_handle_resp(tn_ctx_t * tcc, int cmd, int opt) static void _telnet_handle_resp(tn_ctx_t *tcc, int cmd, int opt)
{ {
if (opt == TNO_CPCO) if (opt == TNO_CPCO)
{ {
@@ -76,7 +77,7 @@ static void _telnet_handle_resp(tn_ctx_t * tcc, int cmd, int opt)
} }
} }
void telnet_set_opt(tn_ctx_t * tcc, int opt, int prm, const void *val, int len) void telnet_set_opt(tn_ctx_t *tcc, int opt, int prm, const void *val, int len)
{ {
unsigned char buf[128], *pu = buf; unsigned char buf[128], *pu = buf;
const unsigned char *pv; const unsigned char *pv;
@@ -106,7 +107,7 @@ void telnet_set_opt(tn_ctx_t * tcc, int opt, int prm, const void *val, int len)
tcc->srv_tx(tcc->cctx, buf, len); tcc->srv_tx(tcc->cctx, buf, len);
} }
static void _telnet_handle_opt(tn_ctx_t * tcc, int opt, static void _telnet_handle_opt(tn_ctx_t *tcc, int opt,
unsigned char *pu, int nd) unsigned char *pu, int nd)
{ {
switch (opt) switch (opt)
@@ -120,7 +121,7 @@ static void _telnet_handle_opt(tn_ctx_t * tcc, int opt,
} }
} }
int telnet_rx(tn_ctx_t * tcc, char *buf, int *plen) int telnet_rx(tn_ctx_t *tcc, char *buf, int *plen)
{ {
char *s, *p; char *s, *p;
unsigned char *pu; unsigned char *pu;
@@ -233,7 +234,7 @@ int telnet_rx(tn_ctx_t * tcc, char *buf, int *plen)
return 0; return 0;
} }
int telnet_tx(tn_ctx_t * tcc, const char *buf, int len) int telnet_tx(tn_ctx_t *tcc, const char *buf, int len)
{ {
const char *s, *p; const char *s, *p;
int rem, nd; int rem, nd;
@@ -263,7 +264,7 @@ int telnet_tx(tn_ctx_t * tcc, const char *buf, int len)
return 0; return 0;
} }
int telnet_open(tn_ctx_t * tcc, int mode) int telnet_open(tn_ctx_t *tcc, int mode)
{ {
time_t t = time(NULL);; time_t t = time(NULL);;
char buf[3]; char buf[3];
@@ -284,17 +285,17 @@ int telnet_open(tn_ctx_t * tcc, int mode)
while (1) while (1)
{ {
res = tcc->srv_rx(tcc->cctx, 1000); res = tcc->srv_rx(tcc->cctx, 1000);
if (res <= 0) if (res < 0)
return -1; return errno;
else if (tcc->mode_rfc2217) else if (tcc->mode_rfc2217)
return 0; return 0;
else if (t - time(NULL) > 2) else if (time(NULL) - t > 10)
return -1; return EPROTO;
} }
} }
tn_ctx_t *telnet_ctx_init(void *cctx, srv_tx_f * ftx, tn_ctx_t *telnet_ctx_init(void *cctx, srv_tx_f *ftx,
srv_rx_f * frx, srv_ms_f * fms) srv_rx_f *frx, srv_ms_f *fms)
{ {
tn_ctx_t *tcc; tn_ctx_t *tcc;
+3 -3
View File
@@ -7,7 +7,7 @@
#include "telnet.h" #include "telnet.h"
#include "telnet_param.h" #include "telnet_param.h"
void telnet_rfc2217_handle_opt(tn_ctx_t * tcc, int opt, void telnet_rfc2217_handle_opt(tn_ctx_t *tcc, int opt,
unsigned char *pu, int nd) unsigned char *pu, int nd)
{ {
unsigned int sub; unsigned int sub;
@@ -32,7 +32,7 @@ void telnet_rfc2217_handle_opt(tn_ctx_t * tcc, int opt,
} }
} }
void telnet_rfc2217_cfg(tn_ctx_t * tcc, int cmd, const void *val, int nd) void telnet_rfc2217_cfg(tn_ctx_t *tcc, int cmd, const void *val, int nd)
{ {
if (!tcc->mode_rfc2217) if (!tcc->mode_rfc2217)
return; return;
@@ -40,7 +40,7 @@ void telnet_rfc2217_cfg(tn_ctx_t * tcc, int cmd, const void *val, int nd)
telnet_set_opt(tcc, TNO_CPCO, cmd, val, nd); telnet_set_opt(tcc, TNO_CPCO, cmd, val, nd);
} }
void telnet_rfc2217_ctl(tn_ctx_t * tcc, unsigned int val) void telnet_rfc2217_ctl(tn_ctx_t *tcc, unsigned int val)
{ {
unsigned char byte = val; unsigned char byte = val;
+29 -17
View File
@@ -150,7 +150,7 @@ static void _fd_close(int fd)
close(fd); close(fd);
} }
static void _notify(ttynvt_t * tty) static void _notify(ttynvt_t *tty)
{ {
pthread_mutex_lock(&tty->poll_lock); pthread_mutex_lock(&tty->poll_lock);
if (tty->ph) if (tty->ph)
@@ -162,7 +162,7 @@ static void _notify(ttynvt_t * tty)
pthread_mutex_unlock(&tty->poll_lock); pthread_mutex_unlock(&tty->poll_lock);
} }
static void _update_notify(ttynvt_t * tty, struct fuse_pollhandle *ph) static void _update_notify(ttynvt_t *tty, struct fuse_pollhandle *ph)
{ {
pthread_mutex_lock(&tty->poll_lock); pthread_mutex_lock(&tty->poll_lock);
struct fuse_pollhandle *tmp_ph = tty->ph; struct fuse_pollhandle *tmp_ph = tty->ph;
@@ -173,7 +173,7 @@ static void _update_notify(ttynvt_t * tty, struct fuse_pollhandle *ph)
fuse_pollhandle_destroy(tmp_ph); fuse_pollhandle_destroy(tmp_ph);
} }
static int _net_read_check_exit(ttynvt_t * tty) static int _net_read_check_exit(ttynvt_t *tty)
{ {
if ((tty->tio.c_cflag & CLOCAL) == 0 && if ((tty->tio.c_cflag & CLOCAL) == 0 &&
(tty->smcr_last & TIOCM_CD) != 0 && (tty->smcr & TIOCM_CD) == 0) (tty->smcr_last & TIOCM_CD) != 0 && (tty->smcr & TIOCM_CD) == 0)
@@ -397,7 +397,7 @@ static ttynvt_t *_ttynvt_ctx_create(void)
return tty; return tty;
} }
static void _ttynvt_ctx_destroy(ttynvt_t * tty) static void _ttynvt_ctx_destroy(ttynvt_t *tty)
{ {
if (tty) if (tty)
free(tty->tn); free(tty->tn);
@@ -470,14 +470,14 @@ static void ttynvt_open(fuse_req_t req, struct fuse_file_info *info)
res = res =
telnet_open(tty->tn, ttynvt_param.raw ? TN_MODE_RAW : TN_MODE_TELNET); telnet_open(tty->tn, ttynvt_param.raw ? TN_MODE_RAW : TN_MODE_TELNET);
if (res < 0) if (res != 0)
{ {
errno = EPROTO; errno = res;
goto open_err; goto open_err;
} }
if ((res = pthread_mutex_init(&tty->tty_lock, NULL) < 0) || if ((res = pthread_mutex_init(&tty->tty_lock, NULL)) ||
(res = pthread_mutex_init(&tty->poll_lock, NULL) < 0) || (res = pthread_mutex_init(&tty->poll_lock, NULL)) ||
(res = pthread_create(&tty->ptid_poll, NULL, &_read_net, tty))) (res = pthread_create(&tty->ptid_poll, NULL, &_read_net, tty)))
{ {
errno = res; errno = res;
@@ -691,10 +691,12 @@ static unsigned int _tio_parity(const struct termios2 *tio)
{ {
unsigned int par; unsigned int par;
if ((tio->c_cflag & PARENB) && (tio->c_iflag & IGNPAR)) if (!(tio->c_cflag & PARENB))
{
par = 1; par = 1;
}
#ifdef CMSPAR #ifdef CMSPAR
else if ((tio->c_cflag & PARENB) && (tio->c_iflag & CMSPAR)) else if (tio->c_iflag & CMSPAR)
{ {
if (tio->c_cflag & PARODD) if (tio->c_cflag & PARODD)
par = 4; par = 4;
@@ -702,10 +704,14 @@ static unsigned int _tio_parity(const struct termios2 *tio)
par = 5; par = 5;
} }
#endif #endif
else if (tio->c_cflag & PARENB) else if (tio->c_cflag & PARODD)
par = 3; {
par = 2;
}
else else
par = 1; {
par = 3;
}
return par; return par;
} }
@@ -818,8 +824,12 @@ ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
goto do_tcset; goto do_tcset;
do_tcset: do_tcset:
/* BEWARE of termios/termios2 buf structure difference! */ /* BEWARE of termios/termios2 buf structure difference! */
if (baud != 0)
{
byte4 = htonl(baud); byte4 = htonl(baud);
telnet_rfc2217_cfg(tty->tn, TNS_SET_BAUDRATE, &byte4, 4); telnet_rfc2217_cfg(tty->tn, TNS_SET_BAUDRATE, &byte4, 4);
}
byte = _tio_csize(tio); byte = _tio_csize(tio);
telnet_rfc2217_cfg(tty->tn, TNS_SET_DATASIZE, &byte, 1); telnet_rfc2217_cfg(tty->tn, TNS_SET_DATASIZE, &byte, 1);
@@ -1108,13 +1118,13 @@ static void ttynvt_release(fuse_req_t req, struct fuse_file_info *info)
pthread_mutex_destroy(&tty->tty_lock); pthread_mutex_destroy(&tty->tty_lock);
pthread_mutex_destroy(&tty->poll_lock); pthread_mutex_destroy(&tty->poll_lock);
if (ttynvt_param.close_delay_us)
_ttynvt_sleep_us(ttynvt_param.close_delay_us);
_fd_close(tty->fds[FD_NET].fd); _fd_close(tty->fds[FD_NET].fd);
_fd_close(tty->fds[FD_MASTER].fd); _fd_close(tty->fds[FD_MASTER].fd);
_fd_close(tty->fds[FD_SLAVE].fd); _fd_close(tty->fds[FD_SLAVE].fd);
if (ttynvt_param.close_delay_us)
_ttynvt_sleep_us(ttynvt_param.close_delay_us);
if (tty->ph) if (tty->ph)
fuse_pollhandle_destroy(tty->ph); fuse_pollhandle_destroy(tty->ph);
@@ -1172,7 +1182,9 @@ static void print_help(void)
"\t-q\t\tBe quiet (suppress informational messages)\n" "\t-q\t\tBe quiet (suppress informational messages)\n"
"\t-r\t\tDisable telnet processing\n" "\t-r\t\tDisable telnet processing\n"
"\t-s delay \t\tDelay at close in ms (use u suffix for us)\n" "\t-s delay \t\tDelay at close in ms (use u suffix for us)\n"
"\t-S server, --server=host:port\n"); "\t-S server, --server=host:port\n"
"\t\t\t[ipv6addr]:port (connect via IPv6)\n"
"\t\t\tunix:/path/to.socket (connect via UNIX domain socket)\n");
} }
static int static int
+48
View File
@@ -0,0 +1,48 @@
# Unit test makefile
#
.NOTPARALLEL:
noinst_PROGRAMS = $(GTESTS)
GTEST_LIBS = -lgtest -lstdc++
GTESTS = test_misc test_nvttest
AM_CFLAGS = -Wall -Wextra -Werror -Wno-unused-parameter
AM_CFLAGS += $(CFLAGS_ASAN)
AM_CXXFLAGS = $(AM_CFLAGS)
AM_CPPFLAGS = -I $(top_builddir)
AM_CPPFLAGS += -D TOP_SRC_DIR='"$(top_srcdir)"' -D TOP_BLD_DIR='"$(top_builddir)"'
AM_CPPFLAGS += -D BUILD_TEST=1
LIBS += $(GTEST_LIBS)
TEST_COMMON = test.cpp test.h
test_misc_SOURCES = $(TEST_COMMON) test_misc.cpp
test_misc_LDADD = $(LIBS)
test_nvttest_SOURCES = $(TEST_COMMON) test_nvttest.cpp
test_nvttest_LDADD = $(LIBS)
TESTS_RUN = $(addprefix run-, $(GTESTS))
VG_PROG = valgrind --leak-check=full
all-local: run
.PHONY: run $(TESTS_RUN)
run: $(TESTS_RUN)
$(TESTS_RUN): run-%: %
# $(TEST_ENV) ./.libs/$* $(RUN_OPTS)
$(TEST_ENV) ./$* $(RUN_OPTS)
TESTS_RUN_VG = $(addprefix run-vg-, $(GTESTS))
.PHONY: run-vg $(TESTS_RUN_VG)
run-vg: $(TESTS_RUN_VG)
$(TESTS_RUN_VG): run-vg-%: %
# $(TEST_ENV) $(VG_PROG) ./.libs/$* $(RUN_OPTS)
$(TEST_ENV) $(VG_PROG) ./$* $(RUN_OPTS)
+70
View File
@@ -0,0 +1,70 @@
/*
* General test stuff
*
* - The main() function
* - Some printing functions
*/
#include "test.h"
int debug = 0;
static bool ttyout = false;
int main(int argc, char **argv)
{
const char *s;
ttyout = isatty(STDOUT_FILENO);
::testing::InitGoogleTest(&argc, argv);
for (argc--, argv++; argc > 0; argc--, argv++)
{
s = argv[0];
if (*s++ != '-')
break;
again:
switch (*s++)
{
case 'd':
debug++;
goto again;
}
}
return RUN_ALL_TESTS();
}
#include <stdarg.h>
void _pr_text(const char *col, const char *fmt, va_list args)
{
char fmtx[1024];
if (ttyout)
snprintf(fmtx, sizeof(fmtx), "%s[ ] - %s%s\n",
col, fmt, COL_RST);
else
snprintf(fmtx, sizeof(fmtx), "[ ] - %s\n", fmt);
fmt = fmtx;
vprintf(fmt, args);
}
void pr_text(const char *col, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
_pr_text(col, fmt, args);
va_end(args);
}
void pr_info(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
_pr_text(COL_YEL, fmt, args);
va_end(args);
}
+25
View File
@@ -0,0 +1,25 @@
#ifndef TEST_H
#define TEST_H 1
#include <gtest/gtest.h>
#include "config.h"
#define D(...) do{ if (debug) printf(__VA_ARGS__); }while(0)
#define D2(...) do{ if (debug > 1) printf(__VA_ARGS__); }while(0)
extern int debug;
#define COL_RST "\x1B[0m"
#define COL_RED "\x1B[31m"
#define COL_GRN "\x1B[32m"
#define COL_YEL "\x1B[33m"
#define COL_BLU "\x1B[34m"
#define COL_MAG "\x1B[35m"
#define COL_CYN "\x1B[36m"
#define COL_WHT "\x1B[37m"
void pr_text(const char *col, const char *fmt, ...);
void pr_info(const char *fmt, ...);
#endif /* TEST_H */
+6
View File
@@ -0,0 +1,6 @@
#include "test.h"
TEST(Misc, dummy)
{
EXPECT_EQ(1, 1);
}
+216
View File
@@ -0,0 +1,216 @@
#include "test.h"
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/wait.h>
#define NVT_NAME "ttyNVT99"
#define NVT_DEV "/dev/" NVT_NAME
#define NVT_SRV TOP_BLD_DIR "/nvtsrv/nvtsrv"
#define NVT_TEST TOP_BLD_DIR "/nvttest/nvttest"
#define NVT_OPTS "-D" NVT_NAME
#define LAUNCH_NVTTEST(...) proc_launch(NVT_TEST, NVT_OPTS, __VA_ARGS__)
pid_t proc_spawn(char **argv)
{
pid_t pid;
char buf[1024];
for (int i = 0, len = 0; argv[i]; i++)
len += snprintf(buf + len, sizeof(buf) - len, "%s ", argv[i]);
pr_info("Launching: %s", buf);
pid = fork();
if (pid > 0)
{
pr_info("... ok, pid = %d", pid);
return pid;
}
// Child
execv(argv[0], argv);
pr_info("Launch failed: %m");
EXPECT_EQ(1, 0);
exit(1);
}
void proc_waitfor(const char *name, pid_t pid)
{
int err, wstatus;
pr_info("Wait for %s (pid %d) to finish", name, pid);
wstatus = 0;
err = waitpid(pid, &wstatus, 0);
if (err > 0)
pr_info("... ok, status = %x", wstatus);
else
pr_info("... error: %m");
EXPECT_GT(err, 0);
EXPECT_EQ(WEXITSTATUS(wstatus), 0);
}
static pid_t proc_launch(const char *prog, ...)
{
char *argv[16], *arg;
int i;
pid_t pid;
va_list args;
va_start(args, prog);
arg = (char *)prog;
i = 0;
argv[i++] = arg;
for (; arg != NULL;)
{
arg = va_arg(args, char *);
argv[i++] = arg;
}
va_end(args);
pid = proc_spawn(argv);
return pid;
}
bool ttynvt_check()
{
struct stat st;
bool ok;
ok = stat(NVT_DEV, &st) == 0 && S_ISCHR(st.st_mode);
if (!ok)
{
fprintf(stderr, "\n%s"
"************************************************************\n"
"*** Invalid device file: '%s'\n"
"*** Ensure that ttynvt is launched correcly, e.g.:\n"
"# ttynvt -d -E -S localhost:1234 -n %s\n"
"*** ttynvt must run as root, and the device and server names\n"
"*** must be as shown above.\n"
"************************************************************\n"
"%s\n", COL_CYN, NVT_DEV, NVT_NAME, COL_RST);
}
return ok;
}
TEST(Test1, t1)
{
int err;
pid_t pid_srv, pid;
ASSERT_TRUE(ttynvt_check());
pid_srv = proc_launch(NVT_SRV, "-de", "localhost:1234", NULL);
ASSERT_GT(pid_srv, 0);
usleep(100000);
pid = LAUNCH_NVTTEST(NULL);
ASSERT_GT(pid, 0);
proc_waitfor("nvttest", pid);
pr_info("Terminate nvtsrv");
err = kill(pid_srv, SIGTERM);
EXPECT_EQ(err, 0);
proc_waitfor("nvtsrv", pid_srv);
}
/* Using fixture */
/**INDENT-OFF**/
class Test2:public::testing::Test {
pid_t pid_srv;
protected:
Test2() { }
virtual ~Test2() { }
virtual void SetUp() {
// Ensure ttynvt is running
ASSERT_TRUE(ttynvt_check());
// NB! Quiet nvtsrv
pid_srv = proc_launch(NVT_SRV, "-qe", "localhost:1234", NULL);
ASSERT_GT(pid_srv, 0);
usleep(100000);
}
virtual void TearDown() {
int err;
pr_info("Terminate nvtsrv");
err = kill(pid_srv, SIGTERM);
EXPECT_EQ(err, 0);
proc_waitfor("nvtsrv", pid_srv);
}
};
/**INDENT-ON**/
TEST_F(Test2, t1)
{
pid_t pid;
pid = LAUNCH_NVTTEST(NULL);
ASSERT_GT(pid, 0);
proc_waitfor("nvttest", pid);
}
// Multiple threads
TEST_F(Test2, t2)
{
pid_t pid;
// NB! Quiet nvttest
pid = LAUNCH_NVTTEST("-t5", "-q", NULL);
ASSERT_GT(pid, 0);
proc_waitfor("nvttest", pid);
}
// Multiple loops
TEST_F(Test2, t3)
{
pid_t pid;
// NB! Quiet nvttest
pid = LAUNCH_NVTTEST("-n5", "-q", NULL);
ASSERT_GT(pid, 0);
proc_waitfor("nvttest", pid);
}
// Multiple threads and loops
TEST_F(Test2, t4)
{
pid_t pid;
// NB! Quiet nvttest
pid = LAUNCH_NVTTEST("-n5", "-t5", "-q", NULL);
ASSERT_GT(pid, 0);
proc_waitfor("nvttest", pid);
}