//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2013, 2015 John Langner, WB2OSZ
//
// 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, see .
//
/*------------------------------------------------------------------
*
* Module: aclients.c
*
* Purpose: Multiple concurrent APRS clients for comparing
* TNC demodulator performance.
*
* Description: Establish connection with multiple servers and
* compare results side by side.
*
* Usage: aclients port1=name1 port2=name2 ...
*
* Example: aclients 8000=AGWPE 192.168.1.64:8002=DireWolf COM1=D710A
*
* This will connect to multiple physical or virtual
* TNCs, read packets from them, and display results.
*
* Each port can have the following forms:
*
* * host-name:tcp-port
* * ip-addr:tcp-port
* * tcp-port
* * serial port name (e.g. COM1, /dev/ttyS0)
*
*---------------------------------------------------------------*/
/*
* Native Windows: Use the Winsock interface.
* Linux: Use the BSD socket interface.
*/
#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h
#if __WIN32__
#include
#include // _WIN32_WINNT must be set to 0x0501 before including this
#else
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#endif
#include
#include
#include
#include
#include
#include
#include "ax25_pad.h"
#include "textcolor.h"
#include "version.h"
struct agwpe_s {
short portx; /* 0 for first, 1 for second, etc. */
short port_hi_reserved;
short kind_lo; /* message type */
short kind_hi;
char call_from[10];
char call_to[10];
int data_len; /* Number of data bytes following. */
int user_reserved;
};
#if __WIN32__
static unsigned __stdcall client_thread_net (void *arg);
static unsigned __stdcall client_thread_serial (void *arg);
#else
static void * client_thread_net (void *arg);
static void * client_thread_serial (void *arg);
#endif
/*
* Convert Internet address to text.
* Can't use InetNtop because it is supported only on Windows Vista and later.
*/
static char * ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t StringBufSize)
{
struct sockaddr_in *sa4;
struct sockaddr_in6 *sa6;
switch (Family) {
case AF_INET:
sa4 = (struct sockaddr_in *)pAddr;
#if __WIN32__
snprintf (pStringBuf, StringBufSize, "%d.%d.%d.%d", sa4->sin_addr.S_un.S_un_b.s_b1,
sa4->sin_addr.S_un.S_un_b.s_b2,
sa4->sin_addr.S_un.S_un_b.s_b3,
sa4->sin_addr.S_un.S_un_b.s_b4);
#else
inet_ntop (AF_INET, &(sa4->sin_addr), pStringBuf, StringBufSize);
#endif
break;
case AF_INET6:
sa6 = (struct sockaddr_in6 *)pAddr;
#if __WIN32__
snprintf (pStringBuf, StringBufSize, "%x:%x:%x:%x:%x:%x:%x:%x",
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[0]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[1]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[2]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[3]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[4]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[5]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[6]),
ntohs(((unsigned short *)(&(sa6->sin6_addr)))[7]));
#else
inet_ntop (AF_INET6, &(sa6->sin6_addr), pStringBuf, StringBufSize);
#endif
break;
default:
snprintf (pStringBuf, StringBufSize, "Invalid address family!");
}
assert (strlen(pStringBuf) < StringBufSize);
return pStringBuf;
}
/*------------------------------------------------------------------
*
* Name: main
*
* Purpose: Start up multiple client threads listening to different
* TNCs. Print packets. Tally up statistics.
*
* Usage: Described above.
*
*---------------------------------------------------------------*/
#define MAX_CLIENTS 6
/* Obtained from the command line. */
static int num_clients;
static char hostname[MAX_CLIENTS][50]; /* DNS host name or IPv4 address. */
/* Some of the code is there for IPv6 but */
/* needs more work. */
/* Defaults to "localhost" if not specified. */
static char port[MAX_CLIENTS][30]; /* If it begins with a digit, it is considered */
/* a TCP port number at the hostname. */
/* Otherwise, we treat it as a serial port name. */
static char description[MAX_CLIENTS][50]; /* Name used in the output. */
#if __WIN32__
static HANDLE client_th[MAX_CLIENTS];
#else
static pthread_t client_tid[MAX_CLIENTS];
#endif
#define LINE_WIDTH 120
static int column_width;
static char packets[LINE_WIDTH+4];
static int packet_count[MAX_CLIENTS];
//#define PRINT_MINUTES 2
#define PRINT_MINUTES 30
int main (int argc, char *argv[])
{
int j;
time_t start_time, now, next_print_time;
#if __WIN32__
#else
int e;
setlinebuf (stdout);
#endif
/*
* Extract command line args.
*/
num_clients = argc - 1;
if (num_clients < 1 || num_clients > MAX_CLIENTS) {
printf ("Specify up to %d TNCs on the command line.\n", MAX_CLIENTS);
exit (1);
}
column_width = LINE_WIDTH / num_clients;
for (j=0; j= next_print_time) {
next_print_time = now + (PRINT_MINUTES) * 60;
printf ("\nTotals after %d minutes", (int)((now - start_time) / 60));
for (j=0; jai_next) {
#if DEBUG_DNS
ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str));
printf (" %s\n", ipaddr_str);
#endif
hosts[num_hosts] = ai;
if (num_hosts < MAX_HOSTS) num_hosts++;
}
#if DEBUG_DNS
printf ("addresses for hostname:\n");
for (n=0; nai_family, hosts[n]->ai_addr, ipaddr_str, sizeof(ipaddr_str));
printf (" %s\n", ipaddr_str);
}
#endif
// Try each address until we find one that is successful.
for (n=0; nai_family, ai->ai_addr, ipaddr_str, sizeof(ipaddr_str));
is = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
#if __WIN32__
if (is == INVALID_SOCKET) {
printf ("Socket creation failed, err=%d", WSAGetLastError());
WSACleanup();
is = -1;
continue;
}
#else
if (err != 0) {
printf ("Socket creation failed, err=%s", gai_strerror(err));
(void) close (is);
is = -1;
continue;
}
#endif
#ifndef DEBUG_DNS
err = connect(is, ai->ai_addr, (int)ai->ai_addrlen);
#if __WIN32__
if (err == SOCKET_ERROR) {
#if DEBUGx
printf("Connect to %s on %s (%s), port %s failed.\n",
description[my_index], hostname[my_index], ipaddr_str, port[my_index]);
#endif
closesocket (is);
is = -1;
continue;
}
#else
if (err != 0) {
#if DEBUGx
printf("Connect to %s on %s (%s), port %s failed.\n",
description[my_index], hostname[my_index], ipaddr_str, port[my_index]);
#endif
(void) close (is);
is = -1;
continue;
}
int flag = 1;
err = setsockopt (is, IPPROTO_TCP, TCP_NODELAY, (void*)(long)(&flag), (socklen_t)sizeof(flag));
if (err < 0) {
printf("setsockopt TCP_NODELAY failed.\n");
}
#endif
/* Success. */
printf("Client %d now connected to %s on %s (%s), port %s\n",
my_index, description[my_index], hostname[my_index], ipaddr_str, port[my_index] );
server_sock = is;
#endif
break;
}
freeaddrinfo(ai_head);
if (server_sock == -1) {
printf("Client %d unable to connect to %s on %s (%s), port %s\n",
my_index, description[my_index], hostname[my_index], ipaddr_str, port[my_index] );
exit (1);
}
/*
* Send command to toggle reception of frames in raw format.
*
* Note: Monitor format is only for UI frames.
* It also discards the via path.
*/
memset (&mon_cmd, 0, sizeof(mon_cmd));
mon_cmd.kind_lo = 'k';
#if __WIN32__
send (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd), 0);
#else
err = write (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
#endif
/*
* Print all of the monitored packets.
*/
while (1) {
int n;
#if __WIN32__
n = recv (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd), 0);
#else
n = read (server_sock, (char*)(&mon_cmd), sizeof(mon_cmd));
#endif
if (n != sizeof(mon_cmd)) {
printf ("Read error, client %d received %d command bytes.\n", my_index, n);
exit (1);
}
#if DEBUGx
printf ("client %d received '%c' data, data_len = %d\n",
my_index, mon_cmd.kind_lo, mon_cmd.data_len);
#endif
assert (mon_cmd.data_len >= 0 && mon_cmd.data_len < (int)(sizeof(data)));
if (mon_cmd.data_len > 0) {
#if __WIN32__
n = recv (server_sock, data, mon_cmd.data_len, 0);
#else
n = read (server_sock, data, mon_cmd.data_len);
#endif
if (n != mon_cmd.data_len) {
printf ("Read error, client %d received %d data bytes.\n", my_index, n);
exit (1);
}
}
/*
* Print it and add to counter.
* The AGWPE score was coming out double the proper value because
* we were getting the same thing from ports 2 and 3.
* 'use_chan' is the first channel we hear from.
* Listen only to that one.
*/
if (mon_cmd.kind_lo == 'K' && (use_chan == -1 || use_chan == mon_cmd.portx)) {
packet_t pp;
char *pinfo;
int info_len;
char result[400];
char *p;
int col, len;
alevel_t alevel;
//printf ("server %d, portx = %d\n", my_index, mon_cmd.portx);
use_chan = mon_cmd.portx;
memset (&alevel, 0xff, sizeof(alevel));
pp = ax25_from_frame ((unsigned char *)(data+1), mon_cmd.data_len-1, alevel);
assert (pp != NULL);
ax25_format_addrs (pp, result);
info_len = ax25_get_info (pp, (unsigned char **)(&pinfo));
pinfo[info_len] = '\0';
strlcat (result, pinfo, sizeof(result));
for (p=result; *p!='\0'; p++) {
if (! isprint(*p)) *p = ' ';
}
#if DEBUGx
printf ("[%d] %s\n", my_index, result);
#endif
col = column_width * my_index;
len = strlen(result);
#define MARGIN 3
if (len > column_width - 3) {
len = column_width - 3;
}
if (packets[col] == ' ') {
memcpy (packets+col, result, (size_t)len);
}
else {
memcpy (packets+col, "OVERRUN! ", (size_t)10);
}
ax25_delete (pp);
packet_count[my_index]++;
}
}
} /* end client_thread_net */
/*-------------------------------------------------------------------
*
* Name: client_thread_serial
*
* Purpose: Establish connection with a TNC via serial port.
*
* Inputs: arg - My instance index, 0 thru MAX_CLIENTS-1.
*
* Outputs: packets - Received packets are put in the corresponding column.
*
*--------------------------------------------------------------------*/
#if __WIN32__
typedef HANDLE MYFDTYPE;
#define MYFDERROR INVALID_HANDLE_VALUE
#else
typedef int MYFDTYPE;
#define MYFDERROR (-1)
#endif
#if __WIN32__
static unsigned __stdcall client_thread_serial (void *arg)
#else
static void * client_thread_serial (void *arg)
#endif
{
int my_index = (int)(long)arg;
#if __WIN32__
MYFDTYPE fd;
DCB dcb;
int ok;
// Bug: Won't work for ports above COM9.
// http://support.microsoft.com/kb/115831
fd = CreateFile(port[my_index], GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, 0, NULL);
if (fd == MYFDERROR) {
printf("Client %d unable to connect to %s on %s.\n",
my_index, description[my_index], port[my_index] );
exit (1);
}
/* Reference: http://msdn.microsoft.com/en-us/library/windows/desktop/aa363201(v=vs.85).aspx */
memset (&dcb, 0, sizeof(dcb));
dcb.DCBlength = sizeof(DCB);
ok = GetCommState (fd, &dcb);
if (! ok) {
printf ("GetCommState failed.\n");
}
/* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214(v=vs.85).aspx */
dcb.DCBlength = sizeof(DCB);
dcb.BaudRate = 9600;
dcb.fBinary = 1;
dcb.fParity = 0;
dcb.fOutxCtsFlow = 0;
dcb.fOutxDsrFlow = 0;
dcb.fDtrControl = 0;
dcb.fDsrSensitivity = 0;
dcb.fOutX = 0;
dcb.fInX = 0;
dcb.fErrorChar = 0;
dcb.fNull = 0; /* Don't drop nul characters! */
dcb.fRtsControl = 0;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
ok = SetCommState (fd, &dcb);
if (! ok) {
printf ("SetCommState failed.\n");
}
#else
/* Linux version. */
int fd;
struct termios ts;
int e;
fd = open (port[my_index], O_RDWR);
if (fd == MYFDERROR) {
printf("Client %d unable to connect to %s on %s.\n",
my_index, description[my_index], port[my_index] );
exit (1);
}
e = tcgetattr (fd, &ts);
if (e != 0) { perror ("nm tcgetattr"); }
cfmakeraw (&ts);
// TODO: speed?
ts.c_cc[VMIN] = 1; /* wait for at least one character */
ts.c_cc[VTIME] = 0; /* no fancy timing. */
e = tcsetattr (fd, TCSANOW, &ts);
if (e != 0) { perror ("nm tcsetattr"); }
#endif
/* Success. */
printf("Client %d now connected to %s on %s\n",
my_index, description[my_index], port[my_index] );
/*
* Assume we are already in monitor mode.
*/
/*
* Print all of the monitored packets.
*/
while (1) {
unsigned char ch;
char result[500];
int col, len;
int done;
char *p;
len = 0;
done = 0;
while ( ! done) {
#if __WIN32__
DWORD n;
if (! ReadFile (fd, &ch, 1, &n, NULL)) {
printf ("Read error on %s.\n", description[my_index]);
CloseHandle (fd);
exit (1);
}
#else
int n;
if ( ( n = read(fd, & ch, 1)) < 0) {
printf ("Read error on %s.\n", description[my_index]);
close (fd);
exit (1);
}
#endif
if (n == 1) {
/*
* Try to build one line for each packet.
* The KPC3+ breaks a packet into two lines like this:
*
* KB1ZXL-1>T2QY5P,W1MHL*,WIDE2-1: <>:
* `c0+!h4>/]"4a}146.520MHz Listening, V-Alert & WLNK-1=
*
* N8VIM>BEACON,W1XM,WB2OSZ-1,WIDE2*: :
* !4240.85N/07133.99W_PHG72604/ Pepperell, MA. WX. 442.9+ PL100
*
* Don't know why some are <> and some .
*
* Anyhow, ignore the return character if preceded by >:
*/
if (ch == '\r') {
if (len >= 10 && result[len-2] == '>' && result[len-1] == ':') {
continue;
}
done = 1;
continue;
}
if (ch == '\n') continue;
result[len++] = ch;
}
}
result[len] = '\0';
/*
* Print it and add to counter.
*/
if (len > 0) {
/* Blank any unprintable characters. */
for (p=result; *p!='\0'; p++) {
if (! isprint(*p)) *p = ' ';
}
#if DEBUGx
printf ("[%d] %s\n", my_index, result);
#endif
col = column_width * my_index;
if (len > column_width - 3) {
len = column_width - 3;
}
if (packets[col] == ' ') {
memcpy (packets+col, result, (size_t)len);
}
else {
memcpy (packets+col, "OVERRUN! ", (size_t)10);
}
packet_count[my_index]++;
}
}
} /* end client_thread_serial */
/* end aclients.c */