New kissutil application.

This commit is contained in:
wb2osz 2017-09-11 21:53:38 -04:00
parent 0dbb376129
commit 678b09df3f
7 changed files with 1563 additions and 40 deletions

View File

@ -4,8 +4,7 @@
APPS := direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc
APPS := direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients atest log2gpx gen_packets ttcalc kissutil
all : $(APPS) direwolf.desktop direwolf.conf
@echo " "
@ -255,18 +254,18 @@ endif
# but the initial test doesn't look good. About 2.3 times as slow.
# If you want to use OSS (for FreeBSD, OpenBSD) instead of
# ALSA (for Linux), comment out (or remove) the two lines below.
# ALSA (for Linux), comment out (or remove) the line below.
# TODO: Can we automate this somehow?
CFLAGS += -DUSE_ALSA
LDFLAGS += -lasound
alsa = 1
ifeq ($(wildcard /usr/include/pthread.h),)
$(error /usr/include/pthread.h does not exist. Install it with "sudo apt-get install libc6-dev" or "sudo yum install libc6-dev" )
endif
ifneq ($(USE_ALSA),)
ifneq ($(alsa),)
CFLAGS += -DUSE_ALSA
LDFLAGS += -lasound
ifeq ($(wildcard /usr/include/alsa/asoundlib.h),)
$(error /usr/include/alsa/asoundlib.h does not exist. Install it with "sudo apt-get install libasound2-dev" or "sudo yum install libasound2-dev" )
endif
@ -377,9 +376,9 @@ tocalls-symbols :
# Separate application to decode raw data.
# First two use .c rather than .o because they depend on DECAMAIN definition.
# First three use .c rather than .o because they depend on DECAMAIN definition.
decode_aprs : decode_aprs.c kiss_frame.c dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o symbols.o ax25_pad.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o misc.a
decode_aprs : decode_aprs.c kiss_frame.c ax25_pad.c dwgpsnmea.o dwgps.o dwgpsd.o serial_port.o symbols.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o misc.a
$(CC) $(CFLAGS) -DDECAMAIN -o $@ $^ $(LDFLAGS)
@ -429,6 +428,13 @@ aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.o misc.a
$(CC) $(CFLAGS) -g -o $@ $^
# Talk to a KISS TNC.
# Note: kiss_frame.c has conditional compilation on KISSUTIL.
kissutil : kissutil.c kiss_frame.c ax25_pad.o fcs_calc.o textcolor.o serial_port.o dtime_now.o sock.o misc.a
$(CC) $(CFLAGS) -g -DKISSUTIL -o $@ $^
# Touch Tone to Speech sample application.
ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o misc.a
@ -562,6 +568,7 @@ install : $(APPS) direwolf.conf tocalls.txt symbols-new.txt symbolsX.txt dw-icon
$(INSTALL) gen_packets $(INSTALLDIR)/bin
$(INSTALL) atest $(INSTALLDIR)/bin
$(INSTALL) ttcalc $(INSTALLDIR)/bin
$(INSTALL) kissutil $(INSTALLDIR)/bin
$(INSTALL) dwespeak.sh $(INSTALLDIR)/bin
#
# Telemetry Toolkit executables. Other .conf and .txt files will go into doc directory.

View File

@ -16,7 +16,7 @@
#
all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets atest ttcalc tnctest
all : direwolf decode_aprs text2tt tt2text ll2utm utm2ll aclients log2gpx gen_packets atest ttcalc tnctest kissutil
# People say we need -mthreads option for threads to work properly.
@ -30,6 +30,8 @@ CFLAGS := -Ofast -march=pentium3 -msse -Iregex -Iutm -Igeotranz -mthreads -DUSE_
AR := ar
CFLAGS += -g
# TEMP EXPERIMENT - DO NOT RELEASE
#CFLAGS += -fsanitize=undefined
# For version 1.4, we upgrade from gcc 4.6.2 to 4.9.3.
@ -159,9 +161,9 @@ tocalls-symbols :
# Separate application to decode raw data.
# First two use .c rather than .o because they depend on DECAMAIN definition.
# First three use .c rather than .o because they depend on DECAMAIN definition.
decode_aprs : decode_aprs.c kiss_frame.c dwgpsnmea.o dwgps.o serial_port.o symbols.o ax25_pad.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o regex.a misc.a geotranz.a
decode_aprs : decode_aprs.c kiss_frame.c ax25_pad.c dwgpsnmea.o dwgps.o serial_port.o symbols.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o regex.a misc.a geotranz.a
$(CC) $(CFLAGS) -DDECAMAIN -o decode_aprs $^
@ -556,6 +558,14 @@ aclients : aclients.c ax25_pad.c fcs_calc.c textcolor.c misc.a regex.a
$(CC) $(CFLAGS) -o $@ $^ -lwinmm -lws2_32
# Talk to a KISS TNC.
# Note: kiss_frame.c has conditional compilation on KISSUTIL.
kissutil : kissutil.c kiss_frame.c ax25_pad.o fcs_calc.o textcolor.o serial_port.o sock.o dtime_now.o misc.a regex.a
$(CC) $(CFLAGS) -DKISSUTIL -o $@ $^ -lwinmm -lws2_32
# Touch Tone to Speech sample application.
ttcalc : ttcalc.o ax25_pad.o fcs_calc.o textcolor.o misc.a regex.a
@ -641,6 +651,7 @@ dist-win : direwolf.exe decode_aprs.exe text2tt.exe tt2text.exe ll2utm.exe utm2l
gen_packets.exe \
atest.exe \
ttcalc.exe \
kissutil.exe \
dwespeak.bat \
telemetry-toolkit/*
rm README-doc.md

View File

@ -27,6 +27,9 @@
*
* Description: The KISS TNS protocol is described in http://www.ka9q.net/papers/kiss.html
*
* ( An extended form, to handle multiple TNCs on a single serial port.
* Not applicable for our situation. http://he.fi/pub/oh7lzb/bpq/multi-kiss.pdf )
*
* Briefly, a frame is composed of
*
* * FEND (0xC0)
@ -59,6 +62,9 @@
*
* _6 SetHardware TNC specific.
*
* _C XKISS extension - not supported.
* _E XKISS extention - not supported.
*
* FF Return Exit KISS mode. Ignored.
*
*
@ -86,9 +92,32 @@
/* In server.c. Should probably move to some misc. function file. */
void hex_dump (unsigned char *p, int len);
#ifdef KISSUTIL
void hex_dump (unsigned char *p, int len)
{
int n, i, offset;
offset = 0;
while (len > 0) {
n = len < 16 ? len : 16;
printf (" %03x: ", offset);
for (i=0; i<n; i++) {
printf (" %02x", p[i]);
}
for (i=n; i<16; i++) {
printf (" ");
}
printf (" ");
for (i=0; i<n; i++) {
printf ("%c", isprint(p[i]) ? p[i] : '.');
}
printf ("\n");
p += 16;
offset += 16;
len -= 16;
}
}
#endif
#if KISSTEST
@ -102,11 +131,17 @@ void text_color_set (dw_color_t c)
#else
#ifndef DECAMAIN
static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug);
#ifndef KISSUTIL
static void kiss_set_hardware (int chan, char *command, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int));
#endif
#endif
#endif
#if KISSUTIL
#define text_color_set(x) ;
#define dw_printf printf
#endif
/*-------------------------------------------------------------------
@ -332,7 +367,7 @@ int kiss_unwrap (unsigned char *in, int ilen, unsigned char *out)
void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, void (*sendfun)(int,unsigned char*,int,int))
void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int))
{
//dw_printf ("kiss_frame ( %c %02x ) \n", ch, ch);
@ -370,14 +405,17 @@ void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, v
kf->noise[kf->noise_len] = '\0';
}
#ifndef KISSUTIL
/* Try to appease client app by sending something back. */
if (strcasecmp("restart\r", (char*)(kf->noise)) == 0 ||
strcasecmp("reset\r", (char*)(kf->noise)) == 0) {
(*sendfun) (0, (unsigned char *)"\xc0\xc0", -1, client);
// first 2 parameters don't matter when length is -1 indicating text.
(*sendfun) (0, 0, (unsigned char *)"\xc0\xc0", -1, client);
}
else {
(*sendfun) (0, (unsigned char *)"\r\ncmd:", -1, client);
(*sendfun) (0, 0, (unsigned char *)"\r\ncmd:", -1, client);
}
#endif
kf->noise_len = 0;
}
return;
@ -421,7 +459,7 @@ void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, v
hex_dump (unwrapped+1, ulen-1);
}
kiss_process_msg (unwrapped, ulen, debug);
kiss_process_msg (unwrapped, ulen, debug, client, sendfun);
kf->state = KS_SEARCHING;
return;
@ -458,9 +496,20 @@ void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, v
*
* debug - Debug option is selected.
*
* client - Client app number for TCP KISS.
* Ignored for pseudo termal and serial port.
*
* sendfun - Function to send something to the client application.
* "Set Hardware" can send a response.
*
*-----------------------------------------------------------------*/
static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
#ifndef KISSUTIL // All these ifdefs in here are a sign that this should be refactored.
// Should split this into multiple files.
// Some functions are only for the TNC end.
// Other functions are suitble for both TNC and client app.
void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int))
{
int port;
int cmd;
@ -472,10 +521,19 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
switch (cmd)
{
case 0: /* Data Frame */
case KISS_CMD_DATA_FRAME: /* 0 = Data Frame */
/* Special hack - Discard apparently bad data from Linux AX25. */
/* Note July 2017: There is a variant of of KISS, called SMACK, that assumes */
/* a TNC can never have more than 8 ports. http://symek.de/g/smack.html */
/* It uses the MSB to indicate that a checksum is added. I wonder if this */
/* is why we sometimes hear about a request to transmit on channel 8. */
/* Should we have a message that asks the user if SMACK is being used, */
/* and if so, turn it off in the application configuration? */
/* Our current default is a maximum of 6 channels but it is easily */
/* increased by changing one number and recompiling. */
if ((port == 2 || port == 8) &&
kiss_msg[1] == 'Q' << 1 &&
kiss_msg[2] == 'S' << 1 &&
@ -525,50 +583,69 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
}
break;
case 1: /* TXDELAY */
case KISS_CMD_TXDELAY: /* 1 = TXDELAY */
text_color_set(DW_COLOR_INFO);
dw_printf ("KISS protocol set TXDELAY = %d (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, port);
if (kiss_msg[1] < 4 || kiss_msg[1] > 100) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Are you sure you want such an extreme value for TXDELAY?\n");
dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n");
}
xmit_set_txdelay (port, kiss_msg[1]);
break;
case 2: /* Persistence */
case KISS_CMD_PERSISTENCE: /* 2 = Persistence */
text_color_set(DW_COLOR_INFO);
dw_printf ("KISS protocol set Persistence = %d, port %d\n", kiss_msg[1], port);
if (kiss_msg[1] < 5 || kiss_msg[1] > 250) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Are you sure you want such an extreme value for PERSIST?\n");
dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n");
}
xmit_set_persist (port, kiss_msg[1]);
break;
case 3: /* SlotTime */
case KISS_CMD_SLOTTIME: /* 3 = SlotTime */
text_color_set(DW_COLOR_INFO);
dw_printf ("KISS protocol set SlotTime = %d (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, port);
if (kiss_msg[1] < 2 || kiss_msg[1] > 50) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Are you sure you want such an extreme value for SLOTTIME?\n");
dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n");
}
xmit_set_slottime (port, kiss_msg[1]);
break;
case 4: /* TXtail */
case KISS_CMD_TXTAIL: /* 4 = TXtail */
text_color_set(DW_COLOR_INFO);
dw_printf ("KISS protocol set TXtail = %d (*10mS units = %d mS), port %d\n", kiss_msg[1], kiss_msg[1] * 10, port);
if (kiss_msg[1] < 2) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Setting TXTAIL so low is asking for trouble. You probably don't want to do this.\n");
dw_printf ("See \"Radio Channel - Transmit Timing\" section of User Guide for explanation.\n");
}
xmit_set_txtail (port, kiss_msg[1]);
break;
case 5: /* FullDuplex */
case KISS_CMD_FULLDUPLEX: /* 5 = FullDuplex */
text_color_set(DW_COLOR_INFO);
dw_printf ("KISS protocol set FullDuplex = %d, port %d - Ignored\n", kiss_msg[1], port);
break;
case 6: /* TNC specific */
case KISS_CMD_SET_HARDWARE: /* 6 = TNC specific */
kiss_msg[kiss_len] = '\0';
text_color_set(DW_COLOR_INFO);
dw_printf ("KISS protocol set hardware - Ignored.\n");
// TODO: kiss_set_hardware (...)
dw_printf ("KISS protocol set hardware \"%s\", port %d\n", (char*)(kiss_msg+1), port);
kiss_set_hardware (port, (char*)(kiss_msg+1), debug, client, sendfun);
break;
case 15: /* End KISS mode, port should be 15. */
case KISS_CMD_END_KISS: /* 15 = End KISS mode, port should be 15. */
/* Ignore it. */
text_color_set(DW_COLOR_INFO);
dw_printf ("KISS protocol end KISS mode - Ignored.\n");
@ -584,11 +661,19 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
dw_printf ("Use \"-d kn\" option on direwolf command line to observe\n");
dw_printf ("all communication with the client application.\n");
if (cmd == XKISS_CMD_DATA || cmd == XKISS_CMD_POLL) {
dw_printf ("\n");
dw_printf ("It looks like you are trying to use the \"XKISS\" protocol which is not supported.\n");
dw_printf ("Change your application settings to use standard \"KISS\" rather than some other variant.\n");
dw_printf ("\n");
}
break;
}
} /* end kiss_process_msg */
#endif // ifndef KISSUTIL
/*-------------------------------------------------------------------
*
@ -596,7 +681,25 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
*
* Purpose: Process the "set hardware" command.
*
* Inputs:
* Inputs: chan - channel, 0 - 15.
*
* command - All but the first byte. e.g. "TXBUF:99"
* Case sensitive.
* Will be modified so be sure caller doesn't care.
*
* debug - debug level.
*
* client - Client app number for TCP KISS.
* Ignored for pseudo terminal and serial port.
*
* sendfun - Function to send something to the client application.
*
* This is the tricky part. We can have any combination of
* serial port, pseudo terminal, and multiple TCP clients.
* We need to send the response to same place where query came
* from. The function is different for each class of device
* and we need a client number for the TCP case because we
* can have multiple TCP KISS clients at the same time.
*
*
* Description: This is new in version 1.5. "Set hardware" was previously ignored.
@ -635,10 +738,61 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
* completely different. It might even be possible to recognize both.
* This might allow leveraging of other existing applications.
*
* Let's start with the easy to understand human readable format.
*
*--------------------------------------------------------------------*/
// static void kiss_set_hardware (...)
#ifndef KISSUTIL
static void kiss_set_hardware (int chan, char *command, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int))
{
char *param;
char response[100];
param = strchr (command, ':');
if (param != NULL) {
*param = '\0';
param++;
if (strcmp(command, "TXBUF") == 0) { /* Number of frames in transmit queue. */
if (strlen(param) > 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("KISS Set Hardware TXBUF: did not expect a parameter.\n");
}
// See what we have in the transmit queue for specified channel.
// fldigi uses bytes but frames seems to make more sense in this situation.
// Do we add one if PTT is on? That information doesn't seem to be easily available.
// TODO: FIXME: not implemented yet.
// n = tq_count (chan, TQ_PRIO_0_HI) + tq_count (chan, TQ_PRIO_1_LO);
snprintf (response, sizeof(response), "TXBUF:whatever");
(*sendfun) (chan, KISS_CMD_SET_HARDWARE, (unsigned char *)response, strlen(response), client);
}
else if (strcmp(command, "TNC") == 0) { /* Identify software version. */
; // TODO...
}
else if (strcmp(command, "TRXS") == 0) {
; // TODO... BUSY
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("KISS Set Hardware invalid command.\n");
}
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("KISS Set Hardware expected the form COMMAND:[parameter[,parameter...]]\n");
}
return;
} /* end kiss_set_hardware */
#endif // ifndef KISSUTIL
/*-------------------------------------------------------------------
@ -657,6 +811,7 @@ static void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug)
void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len)
{
#ifndef KISSUTIL
const char *direction [2] = { "from", "to" };
const char *prefix [2] = { "<<<", ">>>" };
const char *function[16] = {
@ -664,11 +819,14 @@ void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int
"TXtail", "FullDuplex", "SetHardware", "Invalid 7",
"Invalid 8", "Invalid 9", "Invalid 10", "Invalid 11",
"Invalid 12", "Invalid 13", "Invalid 14", "Return" };
#endif
text_color_set(DW_COLOR_DEBUG);
dw_printf ("\n");
#ifdef KISSUTIL
dw_printf ("From KISS TNC:\n");
#else
dw_printf ("\n");
if (special == NULL) {
unsigned char *p; /* to skip over FEND if present. */
@ -684,6 +842,7 @@ void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int
prefix[(int)fromto], special, direction[(int)fromto],
msg_len);
}
#endif
hex_dump (pmsg, msg_len);
} /* end kiss_debug_print */

View File

@ -4,6 +4,25 @@
#include "audio.h" /* for struct audio_s */
/*
* The first byte of a KISS frame has:
* channel in upper nybble.
* command in lower nybble.
*/
#define KISS_CMD_DATA_FRAME 0
#define KISS_CMD_TXDELAY 1
#define KISS_CMD_PERSISTENCE 2
#define KISS_CMD_SLOTTIME 3
#define KISS_CMD_TXTAIL 4
#define KISS_CMD_FULLDUPLEX 5
#define KISS_CMD_SET_HARDWARE 6
#define XKISS_CMD_DATA 12 // Not supported. http://he.fi/pub/oh7lzb/bpq/multi-kiss.pdf
#define XKISS_CMD_POLL 14 // Not supported.
#define KISS_CMD_END_KISS 15
/*
* Special characters used by SLIP protocol.
*/
@ -14,6 +33,7 @@
#define TFESC 0xDD
enum kiss_state_e {
KS_SEARCHING = 0, /* Looking for FEND to start KISS frame. */
/* Must be 0 so we can simply zero whole structure to initialize. */
@ -41,17 +61,20 @@ typedef struct kiss_frame_s {
} kiss_frame_t;
#ifndef KISSUTIL
void kiss_frame_init (struct audio_s *pa);
#endif
int kiss_encapsulate (unsigned char *in, int ilen, unsigned char *out);
int kiss_unwrap (unsigned char *in, int ilen, unsigned char *out);
void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, void (*sendfun)(int,unsigned char*,int,int));
void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int));
typedef enum fromto_e { FROM_CLIENT=0, TO_CLIENT=1 } fromto_t;
void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int));
void kiss_debug_print (fromto_t fromto, char *special, unsigned char *pmsg, int msg_len);
/* end kiss_frame.h */

866
kissutil.c Normal file
View File

@ -0,0 +1,866 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
//
/*------------------------------------------------------------------
*
* Module: kissutil.c
*
* Purpose: Utility for talking to a KISS TNC.
*
* Description: Convert between KISS format and usual text representation.
* This might also serve as the starting point for an application
* that uses a KISS TNC.
* The TNC can be attached by TCP or a serial port.
*
* Usage: kissutil [ options ]
*
* Default is to connect to localhost:8001.
* See the "usage" functions at the bottom for details.
*
* FIXME: This is a rough prototype that needs more work.
* TODO: Prepare the "man" page.
* TODO: Add to User Guide.
*
*---------------------------------------------------------------*/
#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h
#if __WIN32__
#include <winsock2.h>
#include <ws2tcpip.h> // _WIN32_WINNT must be set to 0x0501 before including this
#else
#include <stdlib.h>
#include <sys/errno.h>
#endif
#include <unistd.h>
#include <stdio.h>
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <getopt.h>
#include <sys/types.h>
#include <dirent.h>
//#include "ax25_pad.h"
#include "textcolor.h"
#include "serial_port.h"
#include "kiss_frame.h"
#include "sock.h"
#include "dtime_now.h"
// TODO: define in one place, use everywhere.
#if __WIN32__
#define THREAD_F unsigned __stdcall
#else
#define THREAD_F void *
#endif
#if __WIN32__
#define DIR_CHAR "\\"
#else
#define DIR_CHAR "/"
#endif
static THREAD_F tnc_listen_net (void *arg);
static THREAD_F tnc_listen_serial (void *arg);
static void send_to_kiss_tnc (int chan, int cmd, char *data, int dlen);
static void hex_dump (unsigned char *p, int len);
// Why didn't I use send/recv for Linux?
#if __WIN32__
#define SOCK_SEND(s,data,size) send(s,data,size,0)
#define SOCK_RECV(s,data,size) recv(s,data,size,0)
#else
#define SOCK_SEND(s,data,size) write(s,data,size)
#define SOCK_RECV(s,data,size) read(s,data,size)
#endif
static void usage(void);
static void usage2(void);
/* Obtained from the command line. */
static char hostname[50] = "localhost"; /* -h option. */
/* DNS host name or IPv4 address. */
/* Some of the code is there for IPv6 but */
/* it needs more work. */
/* Defaults to "localhost" if not specified. */
static char port[30] = "8001"; /* -p option. */
/* 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 int using_tcp = 1; /* Are we using TCP or serial port for TNC? */
/* Use corresponding one of the next two. */
/* This is derived from the first character of port. */
static int server_sock = -1; /* File descriptor for socket interface. */
/* Set to -1 if not used. */
/* (Don't use SOCKET type because it is unsigned.) */
static MYFDTYPE serial_fd = (MYFDTYPE)(-1); /* Serial port handle. */
static int serial_speed = 9600; /* -s option. */
/* Serial port speed, bps. */
static int verbose = 0; /* -v option. */
/* Display the KISS protocol in hexadecimal for troubleshooting. */
static char transmit_queue[120] = ""; /* -t option */
/* When specified, files are read from this directory */
/* rather than using stdin. Each file is one or more */
/* lines in the standard monitoring format. */
static char receive_queue[120] = ""; /* -r option */
/* When specified, each received frame is stored as a file */
/* with a unique name here. */
/* Directory must already exist; we won't create it. */
#if __WIN32__
#define THREAD_F unsigned __stdcall
#else
#define THREAD_F void *
#endif
#if __WIN32__
static HANDLE tnc_th;
#else
static pthread_t tnc_tid;
#endif
static void process_input (char *stuff);
/* Trim any CR, LF from the end of line. */
static void trim (char *stuff)
{
char *p;
p = stuff + strlen(stuff) - 1;
while (strlen(stuff) > 0 && (*p == '\r' || *p == '\n')) {
*p = '\0';
p--;
}
} /* end trim */
/*------------------------------------------------------------------
*
* Name: main
*
* Purpose: Attach to KISS TNC and exchange information.
*
* Usage: See "usage" functions at end.
*
*---------------------------------------------------------------*/
int main (int argc, char *argv[])
{
text_color_init (0); // Turn off text color.
// It could interfere with trying to pipe stdout to some other application.
#if __WIN32__
#else
int e;
setlinebuf (stdout); // TODO: What is the Windows equivalent?
#endif
/*
* Extract command line args.
*/
while (1) {
int option_index = 0;
int c;
static struct option long_options[] = {
//{"future1", 1, 0, 0},
//{"future2", 0, 0, 0},
//{"future3", 1, 0, 'c'},
{0, 0, 0, 0}
};
/* ':' following option character means arg is required. */
c = getopt_long(argc, argv, "h:p:s:vt:r:",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'h': /* -h for hostname. */
strlcpy (hostname, optarg, sizeof(hostname));
break;
case 'p': /* -p for port, either TCP or serial device. */
strlcpy (port, optarg, sizeof(port));
break;
case 's': /* -s for serial port speed. */
serial_speed = atoi(optarg);
break;
case 'v': /* -v for verbose. */
verbose++;
break;
case 't': /* -t for transmit queue directory. */
strlcpy (transmit_queue, optarg, sizeof(transmit_queue));
break;
case 'r': /* -t for receive queue directory. */
strlcpy (receive_queue, optarg, sizeof(receive_queue));
break;
case '?':
/* Unknown option message was already printed. */
usage ();
break;
default:
/* Should not be here. */
text_color_set(DW_COLOR_DEBUG);
dw_printf("?? getopt returned character code 0%o ??\n", c);
usage ();
}
} /* end while(1) for options */
if (optind < argc) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Warning: Unused command line arguments are ignored.\n");
}
/*
* If receive queue directory was specified, make sure that it exists.
*/
if (strlen(receive_queue) > 0) {
}
/* If port begins with digit, consider it to be TCP. */
/* Otherwise, treat as serial port name. */
using_tcp = isdigit(port[0]);
#if __WIN32__
if (using_tcp) {
tnc_th = (HANDLE)_beginthreadex (NULL, 0, tnc_listen_net, (void *)99, 0, NULL);
}
else {
tnc_th = (HANDLE)_beginthreadex (NULL, 0, tnc_listen_serial, (void *)99, 0, NULL);
}
if (tnc_th == NULL) {
printf ("Internal error: Could not create TNC listen thread.\n");
exit (EXIT_FAILURE);
}
#else
if (using_tcp) {
e = pthread_create (&tnc_tid, NULL, tnc_listen_net, (void *)(long)99);
}
else {
e = pthread_create (&tnc_tid, NULL, tnc_listen_serial, (void *)(long)99);
}
if (e != 0) {
perror("Internal error: Could not create TNC listen thread.");
exit (EXIT_FAILURE);
}
#endif
/*
* Process keyboard or other input source.
*/
char stuff[1000];
if (strlen(transmit_queue) > 0) {
/*
* Process and delete all files in specified directory.
* When done, sleep for a second and try again.
* This doesn't take them in any particular order.
* A future enhancement might sort by name or timestamp.
*/
while (1) {
DIR *dp;
struct dirent *ep;
//text_color_set(DW_COLOR_DEBUG);
//dw_printf("Get directory listing...\n");
dp = opendir (transmit_queue);
if (dp != NULL) {
while ((ep = readdir(dp)) != NULL) {
char path [300];
FILE *fp;
if (ep->d_name[0] == '.')
continue;
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Processing %s for transmit...\n", ep->d_name);
strlcpy (path, transmit_queue, sizeof(path));
strlcat (path, DIR_CHAR, sizeof(path));
strlcat (path, ep->d_name, sizeof(path));
fp = fopen (path, "r");
if (fp != NULL) {
while (fgets(stuff, sizeof(stuff), fp) != NULL) {
trim (stuff);
text_color_set(DW_COLOR_DEBUG);
dw_printf ("%s\n", stuff);
// TODO: Don't delete file if errors encountered?
process_input (stuff);
}
fclose (fp);
unlink (path);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf("Can't open for read: %s\n", path);
}
}
closedir (dp);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf("Can't access transmit queue directory %s. Quitting.\n", transmit_queue);
exit (EXIT_FAILURE);
}
SLEEP_SEC (1);
}
}
else {
/*
* Using stdin.
*/
while (fgets(stuff, sizeof(stuff), stdin) != NULL) {
process_input (stuff);
}
}
} /* end main */
/*-------------------------------------------------------------------
*
* Name: process_input
*
* Purpose: Process frames/commands from user, either interactively or from files.
*
* Inputs: stuff - A frame is in usual format like SOURCE>DEST,DIGI:whatever.
* Commands begin with lower case letter.
* Note that it can be modified by this function.
*
* Later Enhancement: Return success/fail status. The transmit queue processing might want
* to preserve files that were not processed as expected.
*
*--------------------------------------------------------------------*/
static void process_input (char *stuff)
{
char *p;
int chan = 0;
/*
* Remove any end of line character(s).
*/
trim (stuff);
/*
* Optional prefix, like "[9]" to specify channel. TODO FIXME
*/
p = stuff;
/*
* If it starts with upper case letter or digit, assume it is an AX.25 frame in monitor format.
* Lower case is a command (e.g. Persistence or set Hardware).
* Anything else, print explanation of what is expected.
*/
if (isupper(*p) || isdigit(*p)) {
// Parse the "TNC2 monitor format" and convert to AX.25 frame.
unsigned char frame_data[AX25_MAX_PACKET_LEN];
packet_t pp = ax25_from_text (p, 1);
if (pp != NULL) {
int frame_len = ax25_pack (pp, frame_data);
send_to_kiss_tnc (chan, KISS_CMD_DATA_FRAME, (char*)frame_data, frame_len);
ax25_delete (pp);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR! Could not convert to AX.25 frame: %s\n", p);
}
}
else if (islower(*p)) {
char value;
switch (*p) {
case 'd': // txDelay, 10ms units
// TODO: should check for range 0 - 255.
value = atoi(p+1);
send_to_kiss_tnc (chan, KISS_CMD_TXDELAY, &value, 1);
break;
case 'p': // Persistence
value = atoi(p+1);
send_to_kiss_tnc (chan, KISS_CMD_PERSISTENCE, &value, 1);
break;
case 's': // Slot time, 10ms units
value = atoi(p+1);
send_to_kiss_tnc (chan, KISS_CMD_SLOTTIME, &value, 1);
break;
case 't': // txTelay, 10ms units
value = atoi(p+1);
send_to_kiss_tnc (chan, KISS_CMD_TXTAIL, &value, 1);
break;
case 'f': // Full duplex
value = atoi(p+1);
send_to_kiss_tnc (chan, KISS_CMD_FULLDUPLEX, &value, 1);
break;
case 'h': // set Hardware
p++;
while (*p != '\0' && isspace(*p)) { p++; }
send_to_kiss_tnc (chan, KISS_CMD_SET_HARDWARE, p, strlen(p));
break;
default:
text_color_set(DW_COLOR_ERROR);
dw_printf ("Invalid command. Must be one of d p s t f h.\n");
usage2 ();
break;
}
}
else {
usage2 ();
}
} /* end process_input */
/*-------------------------------------------------------------------
*
* Name: send_to_kiss_tnc
*
* Purpose: Encapsulate the data/command, into a KISS frame, and send to the TNC.
*
* Inputs: chan - channel number.
*
* cmd - KISS_CMD_DATA_FRAME, KISS_CMD_SET_HARDWARE, etc.
*
* data - Information for KISS frame.
*
* dlen - Number of bytes in data.
*
* Description: Encapsulate as KISS frame and send to TNC.
*
*--------------------------------------------------------------------*/
static void send_to_kiss_tnc (int chan, int cmd, char *data, int dlen)
{
unsigned char temp[1000];
unsigned char kissed[2000];
int klen;
if (chan < 0 || chan > 15) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - Invalid channel %d - must be in range 0 to 15.\n", chan);
chan = 0;
}
if (cmd < 0 || cmd > 15) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - Invalid command %d - must be in range 0 to 15.\n", cmd);
cmd = 0;
}
if (dlen < 0 || dlen > (int)(sizeof(temp)-1)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("ERROR - Invalid data length %d - must be in range 0 to %d.\n", dlen, sizeof(temp)-1);
dlen = sizeof(temp)-1;
}
temp[0] = (chan << 4) | cmd;
memcpy (temp+1, data, dlen);
klen = kiss_encapsulate(temp, dlen+1, kissed);
if (verbose) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("Sending to KISS TNC:\n");
hex_dump (kissed, klen);
}
// FIXME: Should check for non -1 server_sock or serial_fd.
// Might need to delay when not using interactive input.
if (using_tcp) {
SOCK_SEND(server_sock, (char*)kissed, klen);
}
else {
serial_port_write (serial_fd, (char*)kissed, klen);
}
} /* end send_to_kiss_tnc */
/*-------------------------------------------------------------------
*
* Name: tnc_listen_net
*
* Purpose: Connect to KISS TNC via TCP port.
* Print everything it sends to us.
*
* Inputs: arg - Currently not used.
*
* Global In: host
* port
*
* Global Out: server_sock - Needed to send to the TNC.
*
*--------------------------------------------------------------------*/
//#define MAX_HOSTS 30
static THREAD_F tnc_listen_net (void *arg)
{
int err;
char ipaddr_str[SOCK_IPADDR_LEN]; // Text form of IP address.
char data[4096];
int allow_ipv6 = 0; // Maybe someday.
int debug = 0;
int client = 0; // Not used in this situation.
kiss_frame_t kstate;
memset (&kstate, 0, sizeof(kstate));
err = sock_init ();
if (err < 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Network interface failure. Can't go on.\n");
exit (EXIT_FAILURE);
}
/*
* Connect to network KISS TNC.
*/
server_sock = sock_connect (hostname, port, "TCP KISS TNC", allow_ipv6, debug, ipaddr_str);
if (server_sock == -1) {
text_color_set(DW_COLOR_ERROR);
// Should have been a message already. What else is there to say?
exit (EXIT_FAILURE);
}
/*
* Print what we get from TNC.
*/
int len;
while ((len = SOCK_RECV (server_sock, (char*)(data), sizeof(data))) > 0) {
int j;
for (j = 0; j < len; j++) {
// Feed in one byte at a time.
// kiss_process_msg is called when a complete frame has been accumulated.
// When verbose is specified, we get debug output like this:
//
// <<< Data frame from KISS client application, port 0, total length = 46
// 000: c0 00 82 a0 88 ae 62 6a e0 ae 84 64 9e a6 b4 ff ......bj...d....
// ...
// It says "from KISS client application" because it was written
// on the assumption it was being used in only one direction.
// Not worried enough about it to do anything at this time.
kiss_rec_byte (&kstate, data[j], verbose, client, NULL);
}
}
text_color_set(DW_COLOR_ERROR);
dw_printf ("Read error from TCP KISS TNC. Terminating.\n");
exit (EXIT_FAILURE);
// For the IGate we would loop around and try to reconnect but
// it doesn't seem appropriate here. Just quit.
} /* end tnc_listen_net */
/*-------------------------------------------------------------------
*
* Name: tnc_listen_serial
*
* Purpose: Connect to KISS TNC via serial port.
* Print everything it sends to us.
*
* Inputs: arg - Currently not used.
*
* Global In: port
* serial_speed
*
* Global Out: serial_fd - Need for sending to the TNC.
*
*--------------------------------------------------------------------*/
static THREAD_F tnc_listen_serial (void *arg)
{
int client = 0;
kiss_frame_t kstate;
memset (&kstate, 0, sizeof(kstate));
serial_fd = serial_port_open (port, serial_speed);
if (serial_fd == MYFDERROR) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Unable to connect to KISS TNC serial port %s.\n", port);
exit (EXIT_FAILURE);
}
/*
* Read and print.
*/
while (1) {
int ch;
ch = serial_port_get1(serial_fd);
if (ch < 0) {
dw_printf("Read error from serial port KISS TNC.\n");
exit (EXIT_FAILURE);
}
// Feed in one byte at a time.
// kiss_process_msg is called when a complete frame has been accumulated.
kiss_rec_byte (&kstate, ch, verbose, client, NULL);
}
} /* end tnc_listen_serial */
/*-------------------------------------------------------------------
*
* Name: kiss_process_msg
*
* Purpose: Process a frame from the KISS TNC.
* This is called when a complete frame has been accumulated.
* In this case, we simply print it.
*
* Inputs: kiss_msg - Kiss frame with FEND and escapes removed.
* The first byte contains channel and command.
*
* kiss_len - Number of bytes including the command.
*
* debug - Debug option is selected.
*
* client - Not used in this case.
*
* sendfun - Not used in this case.
*
*-----------------------------------------------------------------*/
void kiss_process_msg (unsigned char *kiss_msg, int kiss_len, int debug, int client, void (*sendfun)(int,int,unsigned char*,int,int))
{
int chan;
int cmd;
packet_t pp;
alevel_t alevel;
chan = (kiss_msg[0] >> 4) & 0xf;
cmd = kiss_msg[0] & 0xf;
switch (cmd)
{
case KISS_CMD_DATA_FRAME: /* 0 = Data Frame */
memset (&alevel, 0, sizeof(alevel));
pp = ax25_from_frame (kiss_msg+1, kiss_len-1, alevel);
if (pp == NULL) {
text_color_set(DW_COLOR_ERROR);
printf ("ERROR - Invalid KISS data frame from TNC.\n");
}
else {
char addrs[500];
unsigned char *pinfo;
int info_len;
ax25_format_addrs (pp, addrs);
info_len = ax25_get_info (pp, &pinfo);
text_color_set(DW_COLOR_REC);
if (chan != 0) {
dw_printf ("[%d] ", chan);
}
dw_printf ("%s", addrs); /* stations followed by : */
// Safe print will replace any unprintable characters with
// hexadecimal representation.
ax25_safe_print ((char *)pinfo, info_len, 0);
dw_printf ("\n");
/*
* Add to receive queue directory if specified.
* File name will be based on time to 0.01 sec resolution. ??? TBD
*/
if (strlen(receive_queue) > 0) {
char fname [30];
char path [300];
FILE *fp;
snprintf (fname, sizeof(fname), "%.0f", dtime_now() * 100.);
strlcpy (path, receive_queue, sizeof(path));
strlcat (path, DIR_CHAR, sizeof(path));
strlcat (path, fname, sizeof(path));
text_color_set(DW_COLOR_DEBUG);
dw_printf ("save received frame to %s\n", path);
fp = fopen (path, "w");
if (fp != NULL) {
fprintf (fp, "%s%s\n", addrs, pinfo);
fclose (fp);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Unable to open for write: %s\n", path);
}
}
ax25_delete (pp);
}
break;
case KISS_CMD_SET_HARDWARE: /* 6 = TNC specific */
kiss_msg[kiss_len] = '\0';
text_color_set(DW_COLOR_REC);
// TODO: Just precede by "h " for in/out symmetry?
printf ("KISS protocol set hardware \"%s\", channel %d\n", (char*)(kiss_msg+1), chan);
break;
case KISS_CMD_TXDELAY: /* 1 = TXDELAY */
case KISS_CMD_PERSISTENCE: /* 2 = Persistence */
case KISS_CMD_SLOTTIME: /* 3 = SlotTime */
case KISS_CMD_TXTAIL: /* 4 = TXtail */
case KISS_CMD_FULLDUPLEX: /* 5 = FullDuplex */
case KISS_CMD_END_KISS: /* 15 = End KISS mode, port should be 15. */
default:
text_color_set(DW_COLOR_ERROR);
printf ("Unexpected KISS command %d, channel %d\n", cmd, chan);
break;
}
} /* end kiss_process_msg */
// TODO: We have multiple copies of this. Move to some misc file.
void hex_dump (unsigned char *p, int len)
{
int n, i, offset;
offset = 0;
while (len > 0) {
n = len < 16 ? len : 16;
printf (" %03x: ", offset);
for (i=0; i<n; i++) {
printf (" %02x", p[i]);
}
for (i=n; i<16; i++) {
printf (" ");
}
printf (" ");
for (i=0; i<n; i++) {
printf ("%c", isprint(p[i]) ? p[i] : '.');
}
printf ("\n");
p += 16;
offset += 16;
len -= 16;
}
}
static void usage(void)
{
text_color_set(DW_COLOR_INFO);
dw_printf ("\n");
dw_printf ("kissutil - Utility for testing a KISS TNC.\n");
dw_printf ("\n");
dw_printf ("Convert between KISS format and usual text representation.\n");
dw_printf ("The TNC can be attached by TCP or a serial port.\n");
dw_printf ("\n");
dw_printf ("Usage: kissutil [ options ]\n");
dw_printf ("\n");
dw_printf (" -h hostname of TCP KISS TNC, default localhost.\n");
dw_printf (" -p port, default 8001.\n");
dw_printf (" If it does not start with a digit, it is\n");
dw_printf (" a serial port. e.g. /dev/ttyAMA0 or COM3.\n");
dw_printf (" -s Serial port speed, default 9600.\n");
dw_printf (" -v Verbose. Show the KISS frame contents.\n");
dw_printf (" -t Transmit queue directory. Processs and delete files here.\n");
dw_printf (" -r Receive queue directory. Store received frames here.\n");
usage2();
exit (EXIT_SUCCESS);
}
static void usage2 (void)
{
text_color_set(DW_COLOR_INFO);
dw_printf ("\n");
dw_printf ("Input, starting with upper case letter or digit, is assumed\n");
dw_printf ("to be an AX.25 frame in the usual TNC2 monitoring format.\n");
dw_printf ("\n");
dw_printf ("Input, starting with a lower case letter is a commmand.\n");
dw_printf ("Whitespace, as shown in examples, is optional.\n");
dw_printf ("\n");
dw_printf (" letter meaning example\n");
dw_printf (" ------ ------- -------\n");
dw_printf (" d txDelay, 10ms units d 30\n");
dw_printf (" p Persistence p 63\n");
dw_printf (" s Slot time, 10ms units s 10\n");
dw_printf (" t txTail, 10ms units t 5\n");
dw_printf (" f Full duplex f 0\n");
dw_printf (" h set Hardware h T.B.D.\n");
dw_printf ("\n");
dw_printf (" Lines may be preceded by the form \"[9]\" to indicate a\n");
dw_printf (" channel other than the default 0.\n");
dw_printf ("\n");
}
/* end kissutil.c */

438
sock.c Normal file
View File

@ -0,0 +1,438 @@
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
//
/*------------------------------------------------------------------
*
* Module: sock.c
*
* Purpose: Functions for TCP sockets.
*
* Description: These are used for connecting between different applications,
* possibly on different hosts.
*
* New in version 1.5:
* Duplicate code already exists in multiple places and I was about
* to add another one. Instead, we will gather the common code here
* instead of having yet another copy.
*
*---------------------------------------------------------------*/
#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h
#if __WIN32__
#include <winsock2.h>
#include <ws2tcpip.h> // _WIN32_WINNT must be set to 0x0501 before including this
#else
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <fcntl.h>
//#include <termios.h>
#include <sys/errno.h>
#endif
#include <unistd.h>
#include <stdio.h>
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include "textcolor.h"
#include "sock.h"
static void shuffle (struct addrinfo *host[], int nhosts);
/*-------------------------------------------------------------------
*
* Name: sock_init
*
* Purpose: Preparation before using socket interface.
*
* Inputs: none
*
* Returns: 0 for success, -1 for error.
*
* Errors: Message is printed. I've never seen it fail.
*
* Description: Doesn't do anything for Linux.
*
* TODO: Use this instead of own copy in aclients.c
* TODO: Use this instead of own copy in appserver.c
* TODO: Use this instead of own copy in audio_win.c
* TODO: Use this instead of own copy in igate.c
* TODO: Use this instead of own copy in kissnet.c
* TODO: Use this instead of own copy in kissutil.c
* TODO: Use this instead of own copy in server.c
* TODO: Use this instead of own copy in tnctest.c
* TODO: Use this instead of own copy in ttcalc.c
*
*--------------------------------------------------------------------*/
int sock_init(void)
{
#if __WIN32__
WSADATA wsadata;
int err;
err = WSAStartup (MAKEWORD(2,2), &wsadata);
if (err != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf("WSAStartup failed, error: %d\n", err);
return (-1);
}
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Could not find a usable version of Winsock.dll\n");
WSACleanup();
return (-1);
}
#endif
return (0);
} /* end sock_init */
/*-------------------------------------------------------------------
*
* Name: sock_connect
*
* Purpose: Connect to given host / port.
*
* Inputs: hostname - Host name or IP address.
*
* port - TCP port as text string.
*
* description - Description of the remote server to be used in error message.
* e.g. "APRS-IS (Igate) Server" or "TCP KISS TNC".
*
* allow_ipv6 - True to allow IPv6. Otherwise only IPv4.
*
* debug - Print debugging information.
*
* Outputs: ipaddr_str - The IP address, in text form, is placed here in case
* the caller wants it. Should be SOCK_IPADDR_LEN bytes.
*
* Returns: Socket Handle / file descriptor or -1 for error.
*
* Errors: (1) Can't find address for given host name.
*
* Print error and return -1.
*
* (2) Can't connect to one of the address(es).
*
* Silently try the next one.
*
* (3) Can't connect to any of the address(es).
*
* Nothing is printed for success. The caller might do that
* to provide confirmation on what is happening.
*
*--------------------------------------------------------------------*/
int sock_connect (char *hostname, char *port, char *description, int allow_ipv6, int debug, char ipaddr_str[SOCK_IPADDR_LEN])
{
#define MAX_HOSTS 50
struct addrinfo hints;
struct addrinfo *ai_head = NULL;
struct addrinfo *ai;
struct addrinfo *hosts[MAX_HOSTS];
int num_hosts, n;
int err;
int server_sock = -1;
strlcpy (ipaddr_str, "???", SOCK_IPADDR_LEN);
memset (&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; /* Allow either IPv4 or IPv6. */
if ( ! allow_ipv6) {
hints.ai_family = AF_INET; /* IPv4 only. */
}
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
/*
* First, we need to look up the DNS name to get IP address.
* It is possible to have multiple addresses.
*/
ai_head = NULL;
err = getaddrinfo(hostname, port, &hints, &ai_head);
if (err != 0) {
text_color_set(DW_COLOR_ERROR);
#if __WIN32__
dw_printf ("Can't get address for %s, %s, err=%d\n",
description, hostname, WSAGetLastError());
#else
dw_printf ("Can't get address for %s, %s, %s\n",
description, hostname, gai_strerror(err));
#endif
freeaddrinfo(ai_head);
return (-1);
}
if (debug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("getaddrinfo returns:\n");
}
num_hosts = 0;
for (ai = ai_head; ai != NULL; ai = ai->ai_next) {
if (debug) {
text_color_set(DW_COLOR_DEBUG);
sock_ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, SOCK_IPADDR_LEN);
dw_printf (" %s\n", ipaddr_str);
}
hosts[num_hosts] = ai;
if (num_hosts < MAX_HOSTS) num_hosts++;
}
shuffle (hosts, num_hosts);
if (debug) {
text_color_set(DW_COLOR_DEBUG);
dw_printf ("addresses for hostname:\n");
for (n=0; n<num_hosts; n++) {
sock_ia_to_text (hosts[n]->ai_family, hosts[n]->ai_addr, ipaddr_str, SOCK_IPADDR_LEN);
dw_printf (" %s\n", ipaddr_str);
}
}
/*
* Try each address until we find one that is successful.
*/
for (n = 0; n < num_hosts; n++) {
#if __WIN32__
SOCKET is;
#else
int is;
#endif
ai = hosts[n];
sock_ia_to_text (ai->ai_family, ai->ai_addr, ipaddr_str, SOCK_IPADDR_LEN);
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, hostname, ipaddr_str, port);
#endif
closesocket (is);
is = -1;
continue;
}
#else
if (err != 0) {
#if DEBUGx
printf("Connect to %s on %s (%s), port %s failed.\n",
description, hostname, ipaddr_str, port);
#endif
(void) close (is);
is = -1;
continue;
}
/* IGate documentation says to use no delay. */
/* Does it really make a difference? */
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. */
server_sock = is;
#endif
break;
}
freeaddrinfo(ai_head);
// no, caller should handle this.
// function should be generally be silent unless debug option.
if (server_sock == -1) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Unable to connect to %s at %s (%s), port %s\n",
description, hostname, ipaddr_str, port );
}
return (server_sock);
} /* end sock_connect */
/*-------------------------------------------------------------------
*
* Name: sock_bind
*
* Purpose: We also have a bunch of duplicate code for the server side.
*
* Inputs:
*
* TODO: Use this instead of own copy in audio.c
* TODO: Use this instead of own copy in audio_portaudio.c
* TODO: Use this instead of own copy in audio_win.c
* TODO: Use this instead of own copy in kissnet.c
* TODO: Use this instead of own copy in server.c
*
*--------------------------------------------------------------------*/
// Not implemented yet.
/*
* Addresses don't get mixed up very well.
* IPv6 always shows up last so we'd probably never
* end up using any of them for APRS-IS server.
* Add our own shuffle.
*/
static void shuffle (struct addrinfo *host[], int nhosts)
{
int j, k;
assert (RAND_MAX >= nhosts); /* for % to work right */
if (nhosts < 2) return;
srand (time(NULL));
for (j=0; j<nhosts; j++) {
k = rand() % nhosts;
assert (k >=0 && k<nhosts);
if (j != k) {
struct addrinfo *temp;
temp = host[j]; host[j] = host[k]; host[k] = temp;
}
}
}
/*-------------------------------------------------------------------
*
* Name: sock_ia_to_text
*
* Purpose: Convert binary IP Address to text form.
*
* Inputs: Family - AF_INET or AF_INET6.
*
* pAddr - Pointer to the IP Address storage location.
*
* StringBufSize - Number of bytes in pStringBuf.
*
* Outputs: pStringBuf - Text result is placed here.
*
* Returns: pStringBuf
*
* Description: Can't use InetNtop because it is supported only on Windows Vista and later.
* At one time Dire Wolf worked on Win XP. Haven't tried it for years.
* Maybe some other dependency on a newer OS version has crept in.
*
* TODO: Use this instead of own copy in aclients.c
* TODO: Use this instead of own copy in appserver.c
* TODO: Use this instead of own copy in igate.c
* TODO: Use this instead of own copy in tnctest.c
* TODO: Use this instead of own copy in ttcalc.c
*
*--------------------------------------------------------------------*/
char *sock_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!");
}
return pStringBuf;
} /* end sock_ia_to_text */
/* end sock.c */

19
sock.h Normal file
View File

@ -0,0 +1,19 @@
/* sock.h - Socket helper functions. */
#ifndef SOCK_H
#define SOCK_H 1
#define SOCK_IPADDR_LEN 48 // Size of string to hold IPv4 or IPv6 address.
// I think 40 would be adequate but we'll make
// it a little larger just to be safe.
// Use INET6_ADDRSTRLEN (from netinet/in.h) instead?
int sock_init (void);
int sock_connect (char *hostname, char *port, char *description, int allow_ipv6, int debug, char *ipaddr_str);
/* ipaddr_str needs to be at least SOCK_IPADDR_LEN bytes */
char *sock_ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t StringBufSize);
#endif