From b9f654d3c939073b300deb40e802384899c325cf Mon Sep 17 00:00:00 2001 From: wb2osz Date: Fri, 19 Jul 2024 00:41:12 +0100 Subject: [PATCH] New NCHANNEL feature. --- src/nettnc.c | 490 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/nettnc.h | 7 + 2 files changed, 497 insertions(+) create mode 100644 src/nettnc.c create mode 100644 src/nettnc.h diff --git a/src/nettnc.c b/src/nettnc.c new file mode 100644 index 0000000..9b95ab1 --- /dev/null +++ b/src/nettnc.c @@ -0,0 +1,490 @@ + +// +// This file is part of Dire Wolf, an amateur radio packet TNC. +// +// Copyright (C) 2024 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: nettnc.c + * + * Purpose: Attach to Network KISS TNC(s) for NCHANNEL config file item(s). + * + * Description: Called once at application start up. + * + *---------------------------------------------------------------*/ + + +#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 +#endif + +#include +#include +#include +#include +#include +#include + +#include "textcolor.h" +#include "audio.h" // configuration. +#include "kiss.h" +#include "dwsock.h" // socket helper functions. +#include "ax25_pad.h" // for AX25_MAX_PACKET_LEN +#include "dlq.h" // received packet queue + +#include "nettnc.h" + + + +void hex_dump (unsigned char *p, int len); + + +// TODO: define macros in common locaation to hide platform specifics. + +#if __WIN32__ +#define THREAD_F unsigned __stdcall +#else +#define THREAD_F void * +#endif + +#if __WIN32__ +static HANDLE nettnc_listen_th[MAX_TOTAL_CHANS]; +static THREAD_F nettnc_listen_thread (void *arg); +#else +static pthread_t nettnc_listen_tid[MAX_TOTAL_CHANS]; +static THREAD_F nettnc_listen_thread (void *arg); +#endif + +static void my_kiss_rec_byte (kiss_frame_t *kf, unsigned char b, int debug, int channel_override); + +int s_kiss_debug = 0; + + +/*------------------------------------------------------------------- + * + * Name: nettnc_init + * + * Purpose: Attach to Network KISS TNC(s) for NCHANNEL config file item(s). + * + * Inputs: pa - Address of structure of type audio_s. + * + * debug ? TBD + * + * + * Returns: 0 for success, -1 for failure. + * + * Description: Called once at direwolf application start up time. + * Calls nettnc_attach for each NCHANNEL configuration item. + * + *--------------------------------------------------------------------*/ + +void nettnc_init (struct audio_s *pa) +{ + for (int i = 0; i < MAX_TOTAL_CHANS; i++) { + + if (pa->chan_medium[i] == MEDIUM_NETTNC) { + text_color_set(DW_COLOR_DEBUG); + dw_printf ("Channel %d: Network TNC %s %d\n", i, pa->nettnc_addr[i], pa->nettnc_port[i]); + int e = nettnc_attach (i, pa->nettnc_addr[i], pa->nettnc_port[i]); + if (e < 0) { + exit (1); + } + } + } + +} // end nettnc_init + + + +/*------------------------------------------------------------------- + * + * Name: nettnc_attach + * + * Purpose: Attach to one Network KISS TNC. + * + * Inputs: chan - channel number from NCHANNEL configuration. + * + * host - Host name or IP address. Often "localhost". + * + * port - TCP port number. Typically 8001. + * + * init_func - Call this function after establishing communication // + * with the TNC. We put it here, so that it can be done// + * again automatically if the TNC disappears and we// + * reattach to it.// + * It must return 0 for success.// + * Can be NULL if not needed.// + * + * Returns: 0 for success, -1 for failure. + * + * Description: This starts up a thread, for each socket, which listens to the socket and + * dispatches the messages to the corresponding callback functions. + * It will also attempt to re-establish communication with the + * TNC if it goes away. + * + *--------------------------------------------------------------------*/ + +static char s_tnc_host[MAX_TOTAL_CHANS][80]; +static char s_tnc_port[MAX_TOTAL_CHANS][20]; +static volatile int s_tnc_sock[MAX_TOTAL_CHANS]; // Socket handle or file descriptor. -1 for invalid. + + +int nettnc_attach (int chan, char *host, int port) +{ + assert (chan >= 0 && chan < MAX_TOTAL_CHANS); + + char tncaddr[DWSOCK_IPADDR_LEN]; + + char sport[20]; // need port as text string later. + snprintf (sport, sizeof(sport), "%d", port); + + strlcpy (s_tnc_host[chan], host, sizeof(s_tnc_host[chan])); + strlcpy (s_tnc_port[chan], sport, sizeof(s_tnc_port[chan])); + s_tnc_sock[chan] = -1; + + dwsock_init(); + + s_tnc_sock[chan] = dwsock_connect (s_tnc_host[chan], s_tnc_port[chan], "Network TNC", 0, 0, tncaddr); + + if (s_tnc_sock[chan] == -1) { + return (-1); + } + + +/* + * Read frames from the network TNC. + * If the TNC disappears, try to reestablish communication. + */ + + +#if __WIN32__ + nettnc_listen_th[chan] = (HANDLE)_beginthreadex (NULL, 0, nettnc_listen_thread, (void *)(ptrdiff_t)chan, 0, NULL); + if (nettnc_listen_th[chan] == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Internal error: Could not create remore TNC listening thread\n"); + return (-1); + } +#else + int e = pthread_create (&nettnc_listen_tid[chan], NULL, nettnc_listen_thread, (void *)(ptrdiff_t)chan); + if (e != 0) { + text_color_set(DW_COLOR_ERROR); + perror("Internal error: Could not create network TNC listening thread"); + return (-1); + } +#endif + +// TNC initialization if specified. + +// if (s_tnc_init_func != NULL) { +// e = (*s_tnc_init_func)(); +// return (e); +// } + + return (0); + +} // end nettnc_attach + + + +/*------------------------------------------------------------------- + * + * Name: nettnc_listen_thread + * + * Purpose: Listen for anything from TNC and process it. + * Reconnect if something goes wrong and we got disconnected. + * + * Inputs: arg - Channel number. + * s_tnc_host[chan] - Host & port for re-connection. + * s_tnc_port[chan] + * + * Outputs: s_tnc_sock[chan] - File descriptor for communicating with TNC. + * Will be -1 if not connected. + * + *--------------------------------------------------------------------*/ + +#if __WIN32__ +static unsigned __stdcall nettnc_listen_thread (void *arg) +#else +static void * nettnc_listen_thread (void *arg) +#endif +{ + int chan = (int)(ptrdiff_t)arg; + assert (chan >= 0 && chan < MAX_TOTAL_CHANS); + + kiss_frame_t kstate; // State machine to gather a KISS frame. + memset (&kstate, 0, sizeof(kstate)); + + char tncaddr[DWSOCK_IPADDR_LEN]; // IP address used by dwsock_connect. + // Useful when rotate addresses used. + +// Set up buffer for collecting a KISS frame.$CC exttnc.c + + while (1) { +/* + * Re-attach to TNC if not currently attached. + */ + if (s_tnc_sock[chan] == -1) { + + text_color_set(DW_COLOR_ERROR); + // I'm using the term "attach" here, in an attempt to + // avoid confusion with the AX.25 connect. + dw_printf ("Attempting to reattach to network TNC...\n"); + + s_tnc_sock[chan] = dwsock_connect (s_tnc_host[chan], s_tnc_port[chan], "Network TNC", 0, 0, tncaddr); + + if (s_tnc_sock[chan] != -1) { + dw_printf ("Successfully reattached to network TNC.\n"); + } + } + else { +#define NETTNCBUFSIZ 2048 + unsigned char buf[NETTNCBUFSIZ]; + int n = SOCK_RECV (s_tnc_sock[chan], (char *)buf, sizeof(buf)); + + if (n == -1) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Lost communication with network TNC. Will try to reattach.\n"); + dwsock_close (s_tnc_sock[chan]); + s_tnc_sock[chan] = -1; + SLEEP_SEC(5); + continue; + } + +#if 0 + text_color_set(DW_COLOR_DEBUG); + dw_printf ("TEMP DEBUG: %d bytes received from channel %d network TNC.\n", n, chan); +#endif + for (int j = 0; j < n; j++) { + // Separate the byte stream into KISS frame(s) and make it + // look like this came from a radio channel. + my_kiss_rec_byte (&kstate, buf[j], s_kiss_debug, chan); + } + } // s_tnc_sock != -1 + } // while (1) + + return (0); // unreachable but shutup warning. + +} // end nettnc_listen_thread + + + +/*------------------------------------------------------------------- + * + * Name: my_kiss_rec_byte + * + * Purpose: Process one byte from a KISS network TNC. + * + * Inputs: kf - Current state of building a frame. + * b - A byte from the input stream. + * debug - Activates debug output. + * channel_overide - Set incoming channel number to the NCHANNEL + * number rather than the channel in the KISS frame. + * + * Outputs: kf - Current state is updated. + * + * Returns: none. + * + * Description: This is a simplified version of kiss_rec_byte used + * for talking to KISS client applications. It already has + * too many special cases and I don't want to make it worse. + * This also needs to make the packet look like it came from + * a radio channel, not from a client app. + * + *-----------------------------------------------------------------*/ + +static void my_kiss_rec_byte (kiss_frame_t *kf, unsigned char b, int debug, int channel_override) +{ + + //dw_printf ("my_kiss_rec_byte ( %c %02x ) \n", b, b); + + switch (kf->state) { + + case KS_SEARCHING: /* Searching for starting FEND. */ + default: + + if (b == FEND) { + + /* Start of frame. */ + + kf->kiss_len = 0; + kf->kiss_msg[kf->kiss_len++] = b; + kf->state = KS_COLLECTING; + return; + } + return; + break; + + case KS_COLLECTING: /* Frame collection in progress. */ + + + if (b == FEND) { + + unsigned char unwrapped[AX25_MAX_PACKET_LEN]; + int ulen; + + /* End of frame. */ + + if (kf->kiss_len == 0) { + /* Empty frame. Starting a new one. */ + kf->kiss_msg[kf->kiss_len++] = b; + return; + } + if (kf->kiss_len == 1 && kf->kiss_msg[0] == FEND) { + /* Empty frame. Just go on collecting. */ + return; + } + + kf->kiss_msg[kf->kiss_len++] = b; + if (debug) { + /* As received over the wire from network TNC. */ + // May include escapted characters. What about FEND? +// FIXME: make it say Network TNC. + kiss_debug_print (FROM_CLIENT, NULL, kf->kiss_msg, kf->kiss_len); + } + + ulen = kiss_unwrap (kf->kiss_msg, kf->kiss_len, unwrapped); + + if (debug >= 2) { + /* Append CRC to this and it goes out over the radio. */ + text_color_set(DW_COLOR_DEBUG); + dw_printf ("\n"); + dw_printf ("Frame content after removing KISS framing and any escapes:\n"); + /* Don't include the "type" indicator. */ + /* It contains the radio channel and type should always be 0 here. */ + hex_dump (unwrapped+1, ulen-1); + } + + // Convert to packet object and send to received packet queue. + // Note that we use channel associated with the network TNC, not channel in KISS frame. + + int subchan = -3; + int slice = 0; + alevel_t alevel; + memset(&alevel, 0, sizeof(alevel)); + packet_t pp = ax25_from_frame (unwrapped+1, ulen-1, alevel); + if (pp != NULL) { + fec_type_t fec_type = fec_type_none; + retry_t retries; + memset (&retries, 0, sizeof(retries)); + char spectrum[] = "Network TNC"; + dlq_rec_frame (channel_override, subchan, slice, pp, alevel, fec_type, retries, spectrum); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Failed to create packet object for KISS frame from channel %d network TNC.\n", channel_override); + } + + kf->state = KS_SEARCHING; + return; + } + + if (kf->kiss_len < MAX_KISS_LEN) { + kf->kiss_msg[kf->kiss_len++] = b; + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("KISS frame from network TNC exceeded maximum length.\n"); + } + return; + break; + } + + return; /* unreachable but suppress compiler warning. */ + +} /* end my_kiss_rec_byte */ + + + + + + + + + +/*------------------------------------------------------------------- + * + * Name: nettnc_send_packet + * + * Purpose: Send packet to a KISS network TNC. + * + * Inputs: chan - Channel number from NCHANNEL configuration. + * pp - Packet object. + * b - A byte from the input stream. + * + * Outputs: Packet is converted to KISS and send to network TNC. + * + * Returns: none. + * + * Description: This does not free the packet object; caller is responsible. + * + *-----------------------------------------------------------------*/ + +void nettnc_send_packet (int chan, packet_t pp) +{ + +// First, get the on-air frame format from packet object. +// Prepend 0 byte for KISS command and channel. + + unsigned char frame_buff[AX25_MAX_PACKET_LEN + 2]; // One byte for channel/command, + // followed by the AX.25 on-air format frame. + frame_buff[0] = 0; // For now, set channel to 0. + + unsigned char *fbuf = ax25_get_frame_data_ptr (pp); + int flen = ax25_get_frame_len (pp); + + memcpy (frame_buff+1, fbuf, flen); + +// Next, encapsulate into KISS frame with surrounding FENDs and any escapes. + + unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN]; + int kiss_len = kiss_encapsulate (frame_buff, flen+1, kiss_buff); + +#if __WIN32__ + int err = SOCK_SEND(s_tnc_sock[chan], (char*)kiss_buff, kiss_len); + if (err == SOCKET_ERROR) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\nError %d sending packet to KISS Network TNC for channel %d. Closing connection.\n\n", WSAGetLastError(), chan); + closesocket (s_tnc_sock[chan]); + s_tnc_sock[chan] = -1; + } +#else + int err = SOCK_SEND (kps->client_sock[chan], kiss_buff, kiss_len); + if (err <= 0) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("\nError %d sending packet to KISS Network TNC for channel %d. Closing connection.\n\n", err, chan); + close (s_tnc_sock[chan]); + s_tnc_sock[chan] = -1; + } +#endif + + // Do not free packet object; caller will take care of it. + +} /* end nettnc_send_packet */ + diff --git a/src/nettnc.h b/src/nettnc.h new file mode 100644 index 0000000..d8a10f4 --- /dev/null +++ b/src/nettnc.h @@ -0,0 +1,7 @@ + + +void nettnc_init (struct audio_s *pa); + +int nettnc_attach (int chan, char *host, int port); + +void nettnc_send_packet (int chan, packet_t pp); \ No newline at end of file