mirror of https://github.com/wb2osz/direwolf.git
995 lines
31 KiB
C
995 lines
31 KiB
C
//
|
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
|
// Copyright (C) 2011-2014, 2015, 2017, 2021 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: kissnet.c
|
|
*
|
|
* Purpose: Provide service to other applications via KISS protocol via TCP socket.
|
|
*
|
|
* Input:
|
|
*
|
|
* Outputs:
|
|
*
|
|
* Description: This provides a TCP socket for communication with a client application.
|
|
*
|
|
* It implements the KISS TNS protocol as described in:
|
|
* http://www.ka9q.net/papers/kiss.html
|
|
*
|
|
* Briefly, a frame is composed of
|
|
*
|
|
* * FEND (0xC0)
|
|
* * Contents - with special escape sequences so a 0xc0
|
|
* byte in the data is not taken as end of frame.
|
|
* as part of the data.
|
|
* * FEND
|
|
*
|
|
* The first byte of the frame contains:
|
|
*
|
|
* * port number in upper nybble.
|
|
* * command in lower nybble.
|
|
*
|
|
*
|
|
* Commands from application recognized:
|
|
*
|
|
* _0 Data Frame AX.25 frame in raw format.
|
|
*
|
|
* _1 TXDELAY See explanation in xmit.c.
|
|
*
|
|
* _2 Persistence " "
|
|
*
|
|
* _3 SlotTime " "
|
|
*
|
|
* _4 TXtail " "
|
|
* Spec says it is obsolete but Xastir
|
|
* sends it and we respect it.
|
|
*
|
|
* _5 FullDuplex Ignored.
|
|
*
|
|
* _6 SetHardware TNC specific.
|
|
*
|
|
* FF Return Exit KISS mode. Ignored.
|
|
*
|
|
*
|
|
* Messages sent to client application:
|
|
*
|
|
* _0 Data Frame Received AX.25 frame in raw format.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
* References: Getting Started with Winsock
|
|
* http://msdn.microsoft.com/en-us/library/windows/desktop/bb530742(v=vs.85).aspx
|
|
*
|
|
* Future: Originally we had:
|
|
* KISS over serial port.
|
|
* AGW over socket.
|
|
* This is the two of them munged together and we end up with duplicate code.
|
|
* It would have been better to separate out the transport and application layers.
|
|
* Maybe someday.
|
|
*
|
|
*---------------------------------------------------------------*/
|
|
|
|
/*
|
|
Separate TCP ports per radio:
|
|
|
|
An increasing number of people are using multiple radios.
|
|
direwolf is capable of handling many radio channels and
|
|
provides cross-band repeating, etc.
|
|
Maybe a single stereo audio interface is used for 2 radios.
|
|
|
|
+------------+ tcp 8001, all channels
|
|
Radio A -------- | | -------------------------- Application A
|
|
| direwolf |
|
|
Radio B -------- | | -------------------------- Application B
|
|
+------------+ tcp 8001, all channels
|
|
|
|
The KISS protocol has a 4 bit field for the TNC port (which I prefer to
|
|
call channel because port has too many different meanings).
|
|
direwolf handles this fine. However, most applications were written assuming
|
|
that a TNC could only talk to a single radio. On reception, they ignore the
|
|
channel in the KISS frame. For transmit, the channel is always set to 0.
|
|
|
|
Many people are using the work-around of two separate instances of direwolf.
|
|
|
|
+------------+ tcp 8001, KISS ch 0
|
|
Radio A -------- | direwolf | -------------------------- Application A
|
|
+------------+
|
|
|
|
+------------+ tcp 8002, KISS ch 0
|
|
Radio B -------- | direwolf | -------------------------- Application B
|
|
+------------+
|
|
|
|
|
|
Or they might be using a single application that knows how to talk to multiple
|
|
single port TNCs. But they don't know how to multiplex multiple channels
|
|
thru a single KISS stream.
|
|
|
|
+------------+ tcp 8001, KISS ch 0
|
|
Radio A -------- | direwolf | ------------------------
|
|
+------------+ \
|
|
-- Application
|
|
+------------+ tcp 8002, KISS ch 0 /
|
|
Radio B -------- | direwolf | ------------------------
|
|
+------------+
|
|
|
|
Using two different instances of direwolf means more complex configuration
|
|
and loss of cross-channel digipeating. It is possible to use a stereo
|
|
audio interface but some ALSA magic is required to make it look like two
|
|
independent virtual mono interfaces.
|
|
|
|
In version 1.7, we add the capability of multiple KISS TCP ports, each for
|
|
a single radio channel. e.g.
|
|
|
|
KISSPORT 8001 1
|
|
KISSPORT 8002 2
|
|
|
|
Now can use a single instance of direwolf.
|
|
|
|
|
|
+------------+ tcp 8001, KISS ch 0
|
|
Radio A -------- | | -------------------------- Application A
|
|
| direwolf |
|
|
Radio B -------- | | -------------------------- Application B
|
|
+------------+ tcp 8002, KISS ch 0
|
|
|
|
When receiving, the KISS channel is set to 0.
|
|
- only radio channel 1 would be sent over tcp port 8001.
|
|
- only radio channel 2 would be sent over tcp port 8001.
|
|
|
|
When transmitting, the KISS channel is ignored.
|
|
- frames from tcp port 8001 are transmitted on radio channel 1.
|
|
- frames from tcp port 8002 are transmitted on radio channel 2.
|
|
|
|
Of course, you could also use an application, capable of connecting to
|
|
multiple single radio TNCs. Separate TCP ports actually go to the
|
|
same direwolf instance.
|
|
|
|
*/
|
|
|
|
|
|
/*
|
|
* Native Windows: Use the Winsock interface.
|
|
* Linux: Use the BSD socket interface.
|
|
*/
|
|
|
|
|
|
#include "direwolf.h" // Sets _WIN32_WINNT for XP API level needed by ws2tcpip.h
|
|
|
|
|
|
#if __WIN32__
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h> // _WIN32_WINNT must be set to 0x0501 before including this
|
|
#else
|
|
#include <stdlib.h>
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <errno.h>
|
|
#endif
|
|
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
|
|
|
|
#include "tq.h"
|
|
#include "ax25_pad.h"
|
|
#include "textcolor.h"
|
|
#include "audio.h"
|
|
#include "kissnet.h"
|
|
#include "kiss_frame.h"
|
|
#include "xmit.h"
|
|
|
|
void hex_dump (unsigned char *p, int len); // This should be in a .h file.
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: define in one place, use everywhere.
|
|
#if __WIN32__
|
|
#define THREAD_F unsigned __stdcall
|
|
#else
|
|
#define THREAD_F void *
|
|
#endif
|
|
|
|
static THREAD_F connect_listen_thread (void *arg);
|
|
static THREAD_F kissnet_listen_thread (void *arg);
|
|
|
|
|
|
static struct misc_config_s *s_misc_config_p;
|
|
|
|
|
|
// Each TCP port has its own status block.
|
|
// There is a variable number so use a linked list.
|
|
|
|
static struct kissport_status_s *all_ports = NULL;
|
|
|
|
static int kiss_debug = 0; /* Print information flowing from and to client. */
|
|
|
|
void kiss_net_set_debug (int n)
|
|
{
|
|
kiss_debug = n;
|
|
}
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------
|
|
*
|
|
* Name: kissnet_init
|
|
*
|
|
* Purpose: Set up a server to listen for connection requests from
|
|
* an application such as Xastir or APRSIS32.
|
|
* This is called once from the main program.
|
|
*
|
|
* Inputs: mc->kiss_port - TCP port for server.
|
|
* 0 means disable. New in version 1.2.
|
|
*
|
|
* Outputs:
|
|
*
|
|
* Description: This starts two threads:
|
|
* * to listen for a connection from client app.
|
|
* * to listen for commands from client app.
|
|
* so the main application doesn't block while we wait for these.
|
|
*
|
|
*--------------------------------------------------------------------*/
|
|
|
|
static void kissnet_init_one (struct kissport_status_s *kps);
|
|
|
|
void kissnet_init (struct misc_config_s *mc)
|
|
{
|
|
s_misc_config_p = mc;
|
|
|
|
for (int i = 0; i < MAX_KISS_TCP_PORTS; i++) {
|
|
if (mc->kiss_port[i] != 0) {
|
|
struct kissport_status_s *kps = calloc(sizeof(struct kissport_status_s), 1);
|
|
if (kps == NULL) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("FATAL ERROR: Out of memory.\n");
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
kps->tcp_port = mc->kiss_port[i];
|
|
kps->chan = mc->kiss_chan[i];
|
|
kissnet_init_one (kps);
|
|
|
|
// Add to list.
|
|
kps->pnext = all_ports;
|
|
all_ports = kps;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void kissnet_init_one (struct kissport_status_s *kps)
|
|
{
|
|
int client;
|
|
|
|
#if __WIN32__
|
|
HANDLE connect_listen_th;
|
|
HANDLE cmd_listen_th[MAX_NET_CLIENTS];
|
|
#else
|
|
pthread_t connect_listen_tid;
|
|
pthread_t cmd_listen_tid[MAX_NET_CLIENTS];
|
|
int e;
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#if DEBUG
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("kissnet_init ( tcp port %d, radio chan = %d )\n", kps->tcp_port, kps->chan);
|
|
#endif
|
|
|
|
|
|
for (client=0; client<MAX_NET_CLIENTS; client++) {
|
|
kps->client_sock[client] = -1;
|
|
memset (&(kps->kf[client]), 0, sizeof(kps->kf[client]));
|
|
}
|
|
|
|
if (kps->tcp_port == 0) {
|
|
text_color_set(DW_COLOR_INFO);
|
|
dw_printf ("Disabled KISS network client port.\n");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* This waits for a client to connect and sets client_sock[n].
|
|
*/
|
|
#if __WIN32__
|
|
connect_listen_th = (HANDLE)_beginthreadex (NULL, 0, connect_listen_thread, (void *)kps, 0, NULL);
|
|
if (connect_listen_th == NULL) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Could not create KISS socket connect listening thread for tcp port %d, radio chan %d\n", kps->tcp_port, kps->chan);
|
|
return;
|
|
}
|
|
#else
|
|
e = pthread_create (&connect_listen_tid, NULL, connect_listen_thread, (void *)kps);
|
|
if (e != 0) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
perror("Could not create KISS socket connect listening thread");
|
|
dw_printf ("for tcp port %d, radio chan %d\n", kps->tcp_port, kps->chan);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* These read messages from client when client_sock[n] is valid.
|
|
* Currently we start up a separate thread for each potential connection.
|
|
* Possible later refinement. Start one now, others only as needed.
|
|
*/
|
|
for (client = 0; client < MAX_NET_CLIENTS; client++) {
|
|
|
|
kps->arg2 = client;
|
|
|
|
#if __WIN32__
|
|
cmd_listen_th[client] = (HANDLE)_beginthreadex (NULL, 0, kissnet_listen_thread, (void*)kps, 0, NULL);
|
|
if (cmd_listen_th[client] == NULL) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Could not create KISS command listening thread for client %d\n", client);
|
|
return;
|
|
}
|
|
#else
|
|
e = pthread_create (&(cmd_listen_tid[client]), NULL, kissnet_listen_thread, (void *)kps);
|
|
if (e != 0) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("Could not create KISS command listening thread for client %d\n", client);
|
|
// Replace add perror with better message handling.
|
|
perror("");
|
|
return;
|
|
}
|
|
#endif
|
|
// Wait for new thread to get content of arg2 before reusing it for the next thread create.
|
|
|
|
int timer = 0;
|
|
while (kps->arg2 >= 0) {
|
|
SLEEP_MS(10);
|
|
timer++;
|
|
if (timer > 100) { // 1 second - thread did not start
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("KISS data listening thread did not start for tcp port %d, client slot %d\n", kps->tcp_port, client);
|
|
kps->arg2 = -1; // Keep moving along.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------------------------
|
|
*
|
|
* Name: connect_listen_thread
|
|
*
|
|
* Purpose: Wait for a connection request from an application.
|
|
*
|
|
* Inputs: arg - KISS port status block.
|
|
*
|
|
* Outputs: client_sock - File descriptor for communicating with client app.
|
|
*
|
|
* Description: Wait for connection request from client and establish
|
|
* communication.
|
|
* Note that the client can go away and come back again and
|
|
* re-establish communication without restarting this application.
|
|
*
|
|
*--------------------------------------------------------------------*/
|
|
|
|
static THREAD_F connect_listen_thread (void *arg)
|
|
{
|
|
struct kissport_status_s *kps = arg;
|
|
|
|
#if __WIN32__
|
|
|
|
struct addrinfo hints;
|
|
struct addrinfo *ai = NULL;
|
|
int err;
|
|
char tcp_port_str[12];
|
|
|
|
SOCKET listen_sock;
|
|
WSADATA wsadata;
|
|
|
|
snprintf (tcp_port_str, sizeof(tcp_port_str), "%d", kps->tcp_port);
|
|
#if DEBUG
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("DEBUG: kissnet port = %d = '%s'\n", (int)(ptrdiff_t)arg, tcp_port_str);
|
|
#endif
|
|
err = WSAStartup (MAKEWORD(2,2), &wsadata);
|
|
if (err != 0) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf("WSAStartup failed: %d\n", err);
|
|
return (0);
|
|
}
|
|
|
|
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();
|
|
//sleep (1);
|
|
return (0);
|
|
}
|
|
|
|
memset (&hints, 0, sizeof(hints));
|
|
hints.ai_family = AF_INET;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_protocol = IPPROTO_TCP;
|
|
hints.ai_flags = AI_PASSIVE;
|
|
|
|
err = getaddrinfo(NULL, tcp_port_str, &hints, &ai);
|
|
if (err != 0) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf("getaddrinfo failed: %d\n", err);
|
|
//sleep (1);
|
|
WSACleanup();
|
|
return (0);
|
|
}
|
|
|
|
listen_sock= socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
|
|
if (listen_sock == INVALID_SOCKET) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("connect_listen_thread: Socket creation failed, err=%d", WSAGetLastError());
|
|
return (0);
|
|
}
|
|
|
|
#if DEBUG
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf("Binding to port %s ... \n", tcp_port_str);
|
|
#endif
|
|
|
|
err = bind( listen_sock, ai->ai_addr, (int)ai->ai_addrlen);
|
|
if (err == SOCKET_ERROR) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf("Bind failed with error: %d\n", WSAGetLastError()); // TODO: provide corresponding text.
|
|
dw_printf("Some other application is probably already using port %s.\n", tcp_port_str);
|
|
dw_printf("Try using a different port number with KISSPORT in the configuration file.\n");
|
|
freeaddrinfo(ai);
|
|
closesocket(listen_sock);
|
|
WSACleanup();
|
|
return (0);
|
|
}
|
|
|
|
freeaddrinfo(ai);
|
|
|
|
#if DEBUG
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf("opened KISS socket as fd (%d) on port (%s) for stream i/o\n", listen_sock, tcp_port_str );
|
|
#endif
|
|
|
|
while (1) {
|
|
|
|
int client;
|
|
int c;
|
|
|
|
client = -1;
|
|
for (c = 0; c < MAX_NET_CLIENTS && client < 0; c++) {
|
|
if (kps->client_sock[c] <= 0) {
|
|
client = c;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Listen for connection if we have not reached maximum.
|
|
*/
|
|
if (client >= 0) {
|
|
|
|
if(listen(listen_sock, MAX_NET_CLIENTS) == SOCKET_ERROR)
|
|
{
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf("Listen failed with error: %d\n", WSAGetLastError());
|
|
return (0);
|
|
}
|
|
|
|
text_color_set(DW_COLOR_INFO);
|
|
if (kps->chan == -1) {
|
|
dw_printf("Ready to accept KISS TCP client application %d on port %s ...\n", client, tcp_port_str);
|
|
}
|
|
else {
|
|
dw_printf("Ready to accept KISS TCP client application %d on port %s (radio channel %d) ...\n", client, tcp_port_str, kps->chan);
|
|
}
|
|
|
|
kps->client_sock[client] = accept(listen_sock, NULL, NULL);
|
|
|
|
if (kps->client_sock[client] == -1) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf("Accept failed with error: %d\n", WSAGetLastError());
|
|
closesocket(listen_sock);
|
|
WSACleanup();
|
|
return (0);
|
|
}
|
|
|
|
text_color_set(DW_COLOR_INFO);
|
|
if (kps->chan == -1) {
|
|
dw_printf("\nAttached to KISS TCP client application %d on port %s ...\n\n", client, tcp_port_str);
|
|
}
|
|
else {
|
|
dw_printf("\nAttached to KISS TCP client application %d on port %s (radio channel %d) ...\n\n", client, tcp_port_str, kps->chan);
|
|
}
|
|
|
|
// Reset the state and buffer.
|
|
memset (&(kps->kf[client]), 0, sizeof(kps->kf[client]));
|
|
}
|
|
else {
|
|
SLEEP_SEC(1); /* wait then check again if more clients allowed. */
|
|
}
|
|
}
|
|
|
|
|
|
#else /* End of Windows case, now Linux / Unix / Mac OSX. */
|
|
|
|
|
|
struct sockaddr_in sockaddr; /* Internet socket address struct */
|
|
socklen_t sockaddr_size = sizeof(struct sockaddr_in);
|
|
int listen_sock;
|
|
int bcopt = 1;
|
|
|
|
listen_sock= socket(AF_INET,SOCK_STREAM,0);
|
|
if (listen_sock == -1) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
perror ("connect_listen_thread: Socket creation failed");
|
|
return (NULL);
|
|
}
|
|
|
|
/* Version 1.3 - as suggested by G8BPQ. */
|
|
/* Without this, if you kill the application then try to run it */
|
|
/* again quickly the port number is unavailable for a while. */
|
|
/* Don't try doing the same thing On Windows; It has a different meaning. */
|
|
/* http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t */
|
|
|
|
setsockopt (listen_sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&bcopt, 4);
|
|
|
|
sockaddr.sin_addr.s_addr = INADDR_ANY;
|
|
sockaddr.sin_port = htons(kps->tcp_port);
|
|
sockaddr.sin_family = AF_INET;
|
|
|
|
#if DEBUG
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf("Binding to port %d ... \n", kps->tcp_port);
|
|
#endif
|
|
|
|
if (bind(listen_sock,(struct sockaddr*)&sockaddr,sizeof(sockaddr)) == -1) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf("Bind failed with error: %d\n", errno);
|
|
dw_printf("%s\n", strerror(errno));
|
|
dw_printf("Some other application is probably already using port %d.\n", kps->tcp_port);
|
|
dw_printf("Try using a different port number with KISSPORT in the configuration file.\n");
|
|
return (NULL);
|
|
}
|
|
|
|
getsockname( listen_sock, (struct sockaddr *)(&sockaddr), &sockaddr_size);
|
|
|
|
#if DEBUG
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf("opened KISS TCP socket as fd (%d) on port (%d) for stream i/o\n", listen_sock, ntohs(sockaddr.sin_port) );
|
|
#endif
|
|
|
|
while (1) {
|
|
|
|
int client;
|
|
int c;
|
|
|
|
client = -1;
|
|
for (c = 0; c < MAX_NET_CLIENTS && client < 0; c++) {
|
|
if (kps->client_sock[c] <= 0) {
|
|
client = c;
|
|
}
|
|
}
|
|
|
|
if (client >= 0) {
|
|
|
|
if(listen(listen_sock,MAX_NET_CLIENTS) == -1)
|
|
{
|
|
text_color_set(DW_COLOR_ERROR);
|
|
perror ("connect_listen_thread: Listen failed");
|
|
return (NULL);
|
|
}
|
|
|
|
text_color_set(DW_COLOR_INFO);
|
|
if (kps->chan == -1) {
|
|
dw_printf("Ready to accept KISS TCP client application %d on port %d ...\n", client, kps->tcp_port);
|
|
}
|
|
else {
|
|
dw_printf("Ready to accept KISS TCP client application %d on port %d (radio channel %d) ...\n", client, kps->tcp_port, kps->chan);
|
|
}
|
|
|
|
kps->client_sock[client] = accept(listen_sock, (struct sockaddr*)(&sockaddr),&sockaddr_size);
|
|
|
|
text_color_set(DW_COLOR_INFO);
|
|
if (kps->chan == -1) {
|
|
dw_printf("\nAttached to KISS TCP client application %d on port %d ...\n\n", client, kps->tcp_port);
|
|
}
|
|
else {
|
|
dw_printf("\nAttached to KISS TCP client application %d on port %d (radio channel %d) ...\n\n", client, kps->tcp_port, kps->chan);
|
|
}
|
|
|
|
// Reset the state and buffer.
|
|
memset (&(kps->kf[client]), 0, sizeof(kps->kf[client]));
|
|
}
|
|
else {
|
|
SLEEP_SEC(1); /* wait then check again if more clients allowed. */
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------
|
|
*
|
|
* Name: kissnet_send_rec_packet
|
|
*
|
|
* Purpose: Send a packet, received over the radio, to the client app.
|
|
*
|
|
* Inputs: chan - Channel number where packet was received.
|
|
* 0 = first, 1 = second if any.
|
|
*
|
|
// TODO: add kiss_cmd
|
|
*
|
|
* fbuf - Address of raw received frame buffer
|
|
* or a text string.
|
|
*
|
|
* kiss_cmd - Usually KISS_CMD_DATA_FRAME but we can also have
|
|
* KISS_CMD_SET_HARDWARE when responding to a query.
|
|
*
|
|
* flen - Number of bytes for AX.25 frame.
|
|
* When called from kiss_rec_byte, flen will be -1
|
|
* indicating a text string rather than frame content.
|
|
* This is used to fake out an application that thinks
|
|
* it is using a traditional TNC and tries to put it
|
|
* into KISS mode.
|
|
*
|
|
* onlykps - KISS TCP status block pointer or NULL.
|
|
*
|
|
* onlyclient - It is possible to have more than client attached
|
|
* at the same time with TCP KISS.
|
|
* Starting with version 1.7 we can have multiple TCP ports.
|
|
* When a frame is received from the radio we normally want it
|
|
* to go to all of the clients.
|
|
* In this case specify NULL for onlykps and -1 tcp client.
|
|
* When responding to a command from the client, we want
|
|
* to send only to that one client app. In this case
|
|
* a non NULL kps and onlyclient >= 0.
|
|
*
|
|
* Description: Send message to client(s) if connected.
|
|
* Disconnect from client, and notify user, if any error.
|
|
*
|
|
*--------------------------------------------------------------------*/
|
|
|
|
void kissnet_send_rec_packet (int chan, int kiss_cmd, unsigned char *fbuf, int flen,
|
|
struct kissport_status_s *onlykps, int onlyclient)
|
|
{
|
|
unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN];
|
|
int kiss_len;
|
|
int err;
|
|
|
|
// Something received over the radio would normally be sent to all attached clients.
|
|
// However, there are times we want to send a response only to a particular client.
|
|
// In the case of a serial port or pseudo terminal, there is only one potential client.
|
|
// so the response would be sent to only one place. A new parameter has been added for this.
|
|
|
|
for (struct kissport_status_s *kps = all_ports; kps != NULL; kps = kps->pnext) {
|
|
|
|
if (onlykps == NULL || kps == onlykps) {
|
|
|
|
for (int client = 0; client < MAX_NET_CLIENTS; client++) {
|
|
|
|
if (onlyclient == -1 || client == onlyclient) {
|
|
|
|
if (kps->client_sock[client] != -1) {
|
|
|
|
if (flen < 0) {
|
|
|
|
// A client app might think it is attached to a traditional TNC.
|
|
// It might try sending commands over and over again trying to get the TNC into KISS mode.
|
|
// We recognize this attempt and send it something to keep it happy.
|
|
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("KISS TCP: Something unexpected from client application.\n");
|
|
dw_printf ("Is client app treating this like an old TNC with command mode?\n");
|
|
dw_printf ("This can be caused by the application sending commands to put a\n");
|
|
dw_printf ("traditional TNC into KISS mode. It is usually a harmless warning.\n");
|
|
dw_printf ("For best results, configure for a KISS-only TNC to avoid this.\n");
|
|
dw_printf ("In the case of APRSISCE/32, use \"Simply(KISS)\" rather than \"KISS.\"\n");
|
|
|
|
flen = strlen((char*)fbuf);
|
|
if (kiss_debug) {
|
|
kiss_debug_print (TO_CLIENT, "Fake command prompt", fbuf, flen);
|
|
}
|
|
strlcpy ((char *)kiss_buff, (char *)fbuf, sizeof(kiss_buff));
|
|
kiss_len = strlen((char *)kiss_buff);
|
|
}
|
|
else {
|
|
unsigned char stemp[AX25_MAX_PACKET_LEN + 1];
|
|
|
|
assert (flen < (int)(sizeof(stemp)));
|
|
|
|
// New in 1.7.
|
|
// Previously all channels were sent to everyone.
|
|
// We now have tcp ports which carry only a single radio channel.
|
|
// The application will see KISS channel 0 regardless of the radio channel.
|
|
|
|
if (kps->chan == -1) {
|
|
// Normal case, all channels.
|
|
stemp[0] = (chan << 4) | kiss_cmd;
|
|
}
|
|
else if (kps->chan == chan) {
|
|
// Single radio channel for this port. Application sees 0.
|
|
stemp[0] = (0 << 4) | kiss_cmd;
|
|
}
|
|
else {
|
|
// Skip it.
|
|
continue;
|
|
}
|
|
|
|
memcpy (stemp+1, fbuf, flen);
|
|
|
|
if (kiss_debug >= 2) {
|
|
/* AX.25 frame with the CRC removed. */
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("\n");
|
|
dw_printf ("Packet content before adding KISS framing and any escapes:\n");
|
|
hex_dump (fbuf, flen);
|
|
}
|
|
|
|
kiss_len = kiss_encapsulate (stemp, flen+1, kiss_buff);
|
|
|
|
/* This has the escapes and the surrounding FENDs. */
|
|
|
|
if (kiss_debug) {
|
|
kiss_debug_print (TO_CLIENT, NULL, kiss_buff, kiss_len);
|
|
}
|
|
}
|
|
|
|
#if __WIN32__
|
|
err = SOCK_SEND(kps->client_sock[client], (char*)kiss_buff, kiss_len);
|
|
if (err == SOCKET_ERROR) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("\nError %d sending message to KISS client application %d on port %d. Closing connection.\n\n", WSAGetLastError(), client, kps->tcp_port);
|
|
closesocket (kps->client_sock[client]);
|
|
kps->client_sock[client] = -1;
|
|
WSACleanup();
|
|
}
|
|
#else
|
|
err = SOCK_SEND (kps->client_sock[client], kiss_buff, kiss_len);
|
|
if (err <= 0) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("\nError %d sending message to KISS client application %d on port %d. Closing connection.\n\n", err, client, kps->tcp_port);
|
|
close (kps->client_sock[client]);
|
|
kps->client_sock[client] = -1;
|
|
}
|
|
#endif
|
|
} // frame length >= 0
|
|
} // if all clients or the one specifie
|
|
} // for each client on the tcp port
|
|
} // if all ports or the one specified
|
|
} // for each tcp port
|
|
|
|
} /* end kissnet_send_rec_packet */
|
|
|
|
|
|
/*-------------------------------------------------------------------
|
|
*
|
|
* Name: kissnet_copy
|
|
*
|
|
* Purpose: Send data from one network KISS client to all others.
|
|
*
|
|
* Inputs: in_msg - KISS frame data without the framing or escapes.
|
|
* The first byte is channel and command (should be data).
|
|
* Caller no longer cares this byte. We will clobber it here.
|
|
*
|
|
* in_len - Number of bytes in above.
|
|
*
|
|
* chan - Channel. Use this instead of first byte of in_msg.
|
|
*
|
|
* cmd - KISS command nybble.
|
|
* Should be 0 because I'm expecting this only for data.
|
|
*
|
|
* from_client - Number of network (TCP) client instance.
|
|
* Should be 0, 1, 2, ...
|
|
*
|
|
*
|
|
* Global In: kiss_copy - From misc. configuration.
|
|
* This enables the feature.
|
|
*
|
|
*
|
|
* Description: Send message to any attached network KISS clients, other than the one where it came from.
|
|
* Enable this by putting KISSCOPY in the configuration file.
|
|
* Note that this applies only to network (TCP) KISS clients, not serial port, or pseudo terminal.
|
|
*
|
|
*
|
|
*--------------------------------------------------------------------*/
|
|
|
|
|
|
void kissnet_copy (unsigned char *in_msg, int in_len, int chan, int cmd, struct kissport_status_s *from_kps, int from_client)
|
|
{
|
|
unsigned char kiss_buff[2 * AX25_MAX_PACKET_LEN];
|
|
int err;
|
|
|
|
|
|
if (s_misc_config_p->kiss_copy) {
|
|
|
|
for (struct kissport_status_s *kps = all_ports; kps != NULL; kps = kps->pnext) {
|
|
|
|
for (int client = 0; client < MAX_NET_CLIENTS; client++) {
|
|
|
|
if ( ! ( kps == from_kps && client == from_client ) ) { // To all but origin.
|
|
|
|
if (kps->client_sock[client] != -1) {
|
|
|
|
if (kps-> chan == -1 || kps->chan == chan) {
|
|
|
|
// Two different cases here:
|
|
// - The TCP port allows all channels, or
|
|
// - The TCP port allows only one channel. In this case set KISS channel to 0.
|
|
|
|
if (kps->chan == -1) {
|
|
in_msg[0] = (chan << 4) | cmd;
|
|
}
|
|
else {
|
|
in_msg[0] = 0 | cmd; // set channel to zero.
|
|
}
|
|
|
|
int kiss_len = kiss_encapsulate (in_msg, in_len, kiss_buff);
|
|
|
|
/* This has the escapes and the surrounding FENDs. */
|
|
|
|
if (kiss_debug) {
|
|
kiss_debug_print (TO_CLIENT, NULL, kiss_buff, kiss_len);
|
|
}
|
|
|
|
#if __WIN32__
|
|
err = SOCK_SEND(kps->client_sock[client], (char*)kiss_buff, kiss_len);
|
|
if (err == SOCKET_ERROR) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("\nError %d copying message to KISS TCP port %d client %d application. Closing connection.\n\n", WSAGetLastError(), kps->tcp_port, client);
|
|
closesocket (kps->client_sock[client]);
|
|
kps->client_sock[client] = -1;
|
|
WSACleanup();
|
|
}
|
|
#else
|
|
err = SOCK_SEND (kps->client_sock[client], kiss_buff, kiss_len);
|
|
if (err <= 0) {
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("\nError copying message to KISS TCP port %d client %d application. Closing connection.\n\n", kps->tcp_port, client);
|
|
close (kps->client_sock[client]);
|
|
kps->client_sock[client] = -1;
|
|
}
|
|
#endif
|
|
} // Channel is allowed on this port.
|
|
} // socket is open
|
|
} // if origin and destination different.
|
|
} // loop over all KISS network clients for one port.
|
|
} // loop over all KISS TCP ports
|
|
} // Feature enabled.
|
|
|
|
} /* end kissnet_copy */
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------
|
|
*
|
|
* Name: kissnet_listen_thread
|
|
*
|
|
* Purpose: Wait for KISS messages from an application.
|
|
*
|
|
* Inputs: arg - client number, 0 .. MAX_NET_CLIENTS-1
|
|
*
|
|
* Outputs: client_sock[n] - File descriptor for communicating with client app.
|
|
*
|
|
* Description: Process messages from the client application.
|
|
* Note that the client can go away and come back again and
|
|
* re-establish communication without restarting this application.
|
|
*
|
|
*--------------------------------------------------------------------*/
|
|
|
|
|
|
/* Return one byte (value 0 - 255) */
|
|
|
|
|
|
static int kiss_get (struct kissport_status_s *kps, int client)
|
|
{
|
|
|
|
while (1) {
|
|
|
|
while (kps->client_sock[client] <= 0) {
|
|
SLEEP_SEC(1); /* Not connected. Try again later. */
|
|
}
|
|
|
|
/* Just get one byte at a time. */
|
|
|
|
unsigned char ch;
|
|
int n = SOCK_RECV (kps->client_sock[client], (char *)(&ch), 1);
|
|
|
|
if (n == 1) {
|
|
#if DEBUG9
|
|
dw_printf (log_fp, "%02x %c %c", ch,
|
|
isprint(ch) ? ch : '.' ,
|
|
(isupper(ch>>1) || isdigit(ch>>1) || (ch>>1) == ' ') ? (ch>>1) : '.');
|
|
if (ch == FEND) fprintf (log_fp, " FEND");
|
|
if (ch == FESC) fprintf (log_fp, " FESC");
|
|
if (ch == TFEND) fprintf (log_fp, " TFEND");
|
|
if (ch == TFESC) fprintf (log_fp, " TFESC");
|
|
if (ch == '\r') fprintf (log_fp, " CR");
|
|
if (ch == '\n') fprintf (log_fp, " LF");
|
|
fprintf (log_fp, "\n");
|
|
if (ch == FEND) fflush (log_fp);
|
|
#endif
|
|
return(ch);
|
|
}
|
|
|
|
text_color_set(DW_COLOR_ERROR);
|
|
dw_printf ("\nKISS client application %d on TCP port %d has gone away.\n\n", client, kps->tcp_port);
|
|
#if __WIN32__
|
|
closesocket (kps->client_sock[client]);
|
|
#else
|
|
close (kps->client_sock[client]);
|
|
#endif
|
|
kps->client_sock[client] = -1;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static THREAD_F kissnet_listen_thread (void *arg)
|
|
{
|
|
struct kissport_status_s *kps = arg;
|
|
|
|
int client = kps->arg2;
|
|
assert (client >= 0 && client < MAX_NET_CLIENTS);
|
|
|
|
kps->arg2 = -1; // Indicates thread is running so
|
|
// arg2 can be reused for the next one.
|
|
|
|
#if DEBUG
|
|
text_color_set(DW_COLOR_DEBUG);
|
|
dw_printf ("kissnet_listen_thread ( tcp_port = %d, client = %d, socket fd = %d )\n", kps->tcp_port, client, kps->client_sock[client]);
|
|
#endif
|
|
|
|
|
|
|
|
// So why is kissnet_send_rec_packet mentioned here for incoming from the client app?
|
|
// The logic exists for the serial port case where the client might think it is
|
|
// attached to a traditional TNC. It might try sending commands over and over again
|
|
// trying to get the TNC into KISS mode. To keep it happy, we recognize this attempt
|
|
// and send it something to keep it happy.
|
|
// In the case of a serial port or pseudo terminal, there is only one potential client
|
|
// so the response would be sent to only one place.
|
|
// Starting in version 1.5, this now can have multiple attached clients. We wouldn't
|
|
// want to send the response to all of them. Actually, we should be providing only
|
|
// "Simply KISS" as some call it.
|
|
|
|
|
|
while (1) {
|
|
unsigned char ch = kiss_get(kps, client);
|
|
kiss_rec_byte (&(kps->kf[client]), ch, kiss_debug, kps, client, kissnet_send_rec_packet);
|
|
}
|
|
|
|
#if __WIN32__
|
|
return(0);
|
|
#else
|
|
return (THREAD_F) 0; /* Unreachable but avoids compiler warning. */
|
|
#endif
|
|
|
|
} /* end kissnet_listen_thread */
|
|
|
|
/* end kissnet.c */
|