diff --git a/Makefile.linux b/Makefile.linux index 599b6a5..ca02ffa 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -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. diff --git a/Makefile.win b/Makefile.win index 1a49554..4e71d8f 100644 --- a/Makefile.win +++ b/Makefile.win @@ -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 diff --git a/kiss_frame.c b/kiss_frame.c index b179ccd..6129687 100644 --- a/kiss_frame.c +++ b/kiss_frame.c @@ -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) @@ -58,6 +61,9 @@ * _5 FullDuplex Ignored. * * _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; inoise[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; @@ -456,11 +494,22 @@ void kiss_rec_byte (kiss_frame_t *kf, unsigned char ch, int debug, int client, v * * kiss_len - Number of bytes including the command. * - * debug - Debug option is selected. + * 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 */ diff --git a/kiss_frame.h b/kiss_frame.h index 3ef0b3e..0bddf3e 100644 --- a/kiss_frame.h +++ b/kiss_frame.h @@ -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 */ \ No newline at end of file +/* end kiss_frame.h */ diff --git a/kissutil.c b/kissutil.c new file mode 100644 index 0000000..f91f263 --- /dev/null +++ b/kissutil.c @@ -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 . +// + + +/*------------------------------------------------------------------ + * + * 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 +#include // _WIN32_WINNT must be set to 0x0501 before including this + +#else + +#include +#include + +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +//#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. +// + + +/*------------------------------------------------------------------ + * + * 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 +#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 "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; nai_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=0 && ksin_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 */ diff --git a/sock.h b/sock.h new file mode 100644 index 0000000..400ea8a --- /dev/null +++ b/sock.h @@ -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 \ No newline at end of file