mirror of
https://github.com/n5ac/smartsdr-dsp.git
synced 2025-12-06 03:01:59 +01:00
357 lines
9.9 KiB
C
357 lines
9.9 KiB
C
/* *****************************************************************************
|
|
* discovery_client.c
|
|
*
|
|
* \brief Discovery Client - Receives and parses discovery packets
|
|
*
|
|
*
|
|
* \author Eric Wachsmann, KE5DTO
|
|
* \date 2014-09-01
|
|
*
|
|
*******************************************************************************
|
|
*
|
|
* Copyright (C) 2014 FlexRadio Systems.
|
|
*
|
|
* 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 3 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/>.
|
|
*
|
|
* Contact Information:
|
|
* email: gpl<at>flexradiosystems.com
|
|
* Mail: FlexRadio Systems, Suite 1-150, 4616 W. Howard LN, Austin, TX 78728
|
|
*
|
|
* ***************************************************************************/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h> // for malloc
|
|
#include <string.h> // for memset
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h> // for htonl, htons, IPPROTO_UDP
|
|
#include <arpa/inet.h>
|
|
#include <pthread.h>
|
|
#include <errno.h> // for errno
|
|
#include <unistd.h>
|
|
|
|
#include "common.h"
|
|
#include "discovery_client.h"
|
|
#include "cmd.h" // for tokenize
|
|
#include "io_utils.h"
|
|
|
|
|
|
static int _dc_sock;
|
|
static pthread_t _dc_thread;
|
|
static BOOL _dc_abort = FALSE;
|
|
static char * _restrict_ip = NULL;
|
|
|
|
|
|
void printRadio(Radio radio)
|
|
{
|
|
output("discovery_protocol_version=%s model=%s serial=%s version=%s nickname=%s callsign=%s ip=%s port=%s status=%s\n",
|
|
radio->discovery_protocol_version,
|
|
radio->model,
|
|
radio->serial,
|
|
radio->version,
|
|
radio->nickname,
|
|
radio->callsign,
|
|
radio->ip,
|
|
radio->port,
|
|
radio->status);
|
|
}
|
|
|
|
char* protocol_version;
|
|
char* model;
|
|
char* serial;
|
|
char* version;
|
|
char* nickname;
|
|
char* callsign;
|
|
char* ip;
|
|
char* port;
|
|
char* status;
|
|
|
|
static void _dc_RadioFound(Radio radio)
|
|
{
|
|
|
|
BOOL radio_found = FALSE;
|
|
|
|
if ( _restrict_ip != NULL ) {
|
|
if ( getIP(radio->ip) == getIP(_restrict_ip)) {
|
|
/* We have found the radio that we are restricted to */
|
|
output("We found a radio that maches our _restrict_ip - '%s'\n", _restrict_ip);
|
|
radio_found = TRUE;
|
|
}
|
|
} else if ( FALSE ) { /* Are we running within a radio? */
|
|
|
|
if(getIP(radio->ip) == ntohl(net_get_ip())) {
|
|
radio_found = TRUE;
|
|
output("We found a radio that is running on the same box as us - '%s'\n", radio->ip);
|
|
}
|
|
|
|
} else { /* Simply connect to first radio we find */
|
|
radio_found = TRUE;
|
|
output("We are attaching to the first radio we see");
|
|
}
|
|
|
|
if ( radio_found ) {
|
|
output("Radio found\n");
|
|
// yes -- connect and stop looking for more radios
|
|
// TODO: connect
|
|
// start a keepalive to keep the channel open and know when it dies
|
|
tc_Init(radio->ip, radio->port);
|
|
|
|
usleep(250000);
|
|
hal_Listener_Init();
|
|
|
|
dc_Exit();
|
|
}
|
|
|
|
// print the content of the object
|
|
//printRadio(radio);
|
|
|
|
// because the radio object is malloc'ed, we need to recover the memory
|
|
safe_free(radio);
|
|
radio = 0;
|
|
}
|
|
|
|
static void _dc_ListenerParsePacket(uint8* packet, int32 length, struct sockaddr_in* sender)
|
|
{
|
|
//output("_dc_ListenerParsePacket\n");
|
|
|
|
// is this packet long enough to inspect for VITA header info?
|
|
if(length < 16)
|
|
{
|
|
// no -- discard the packet
|
|
//output("_dc_ListenerParsePacket: packet too short\n");
|
|
return;
|
|
}
|
|
|
|
// cast the incoming packet as a VITA packet
|
|
VitaIFData p = (VitaIFData)packet;
|
|
|
|
// does this packet have our OUI?
|
|
if(ntohl(p->class_id_h) != 0x00001C2D)
|
|
{
|
|
// no -- discard this packet
|
|
output("_dc_ListenerParsePacket: wrong OUI (0x%08X)\n", htonl(p->class_id_h));
|
|
return;
|
|
}
|
|
|
|
// is this packet an extended data packet?
|
|
if((ntohl(p->header) & VITA_HEADER_PACKET_TYPE_MASK) != VITA_PACKET_TYPE_EXT_DATA_WITH_STREAM_ID)
|
|
{
|
|
// no -- discard this packet
|
|
output("_dc_ListenerParsePacket: wrong packet type (0x%08X)\n", p->header & VITA_HEADER_PACKET_TYPE_MASK);
|
|
return;
|
|
}
|
|
|
|
// is this packet marked as a SL_VITA_DISCOVERY_CLASS?
|
|
if((ntohl(p->class_id_l) & VITA_CLASS_ID_PACKET_CLASS_MASK) != 0xFFFF)
|
|
{
|
|
// no -- discard this packet
|
|
output("_dc_ListenerParsePacket: wrong packet class (0x%04X)\n", p->class_id_l & VITA_CLASS_ID_PACKET_CLASS_MASK);
|
|
return;
|
|
}
|
|
|
|
// if we made it this far, then we can safely assume this is a
|
|
// discovery packet and we will attempt to split the payload up
|
|
// similar to a command on spaces and parse the data
|
|
|
|
int argc, i;
|
|
char *argv[MAX_ARGC + 1]; //Add one extra so we can null terminate the array
|
|
|
|
// split the payload string up
|
|
tokenize((char*)p->payload, &argc, argv, MAX_ARGC);
|
|
|
|
//output("_dc_ListenerParsePacket: payload: %s\n", p->payload);
|
|
//output("_dc_ListenerParsePacket: tokenize argc=%u\n", argc);
|
|
|
|
Radio radio = (Radio)safe_malloc(sizeof(radioType));
|
|
if(!radio)
|
|
{
|
|
output("_dc_ListenerParsePacket: Out of memory!\n");
|
|
return;
|
|
}
|
|
|
|
// clear the newly allocated memory
|
|
memset(radio, 0, sizeof(radioType));
|
|
|
|
// for each token, process the string
|
|
for(i=0; i<argc; i++)
|
|
{
|
|
if(strncmp(argv[i], "discovery_protocol_version", strlen("discovery_protocol_version")) == 0)
|
|
radio->discovery_protocol_version = argv[i]+strlen("discovery_protocol_version=");
|
|
else if(strncmp(argv[i], "model", strlen("model")) == 0)
|
|
radio->model = argv[i]+strlen("model=");
|
|
else if(strncmp(argv[i], "serial", strlen("serial")) == 0)
|
|
radio->serial = argv[i]+strlen("serial=");
|
|
else if(strncmp(argv[i], "version", strlen("version")) == 0)
|
|
radio->version = argv[i]+strlen("version=");
|
|
else if(strncmp(argv[i], "nickname", strlen("nickname")) == 0)
|
|
radio->nickname = argv[i]+strlen("nickname=");
|
|
else if(strncmp(argv[i], "callsign", strlen("callsign")) == 0)
|
|
radio->callsign = argv[i]+strlen("callsign=");
|
|
else if(strncmp(argv[i], "ip", strlen("ip")) == 0)
|
|
radio->ip = argv[i]+strlen("ip=");
|
|
else if(strncmp(argv[i], "port", strlen("port")) == 0)
|
|
radio->port = argv[i]+strlen("port=");
|
|
else if(strncmp(argv[i], "status", strlen("status")) == 0)
|
|
radio->status = argv[i]+strlen("status=");
|
|
}
|
|
|
|
// did we get at least an IP, port, and version?
|
|
if(radio->ip != 0 && radio->port != 0 && radio->version != 0) {
|
|
// yes -- report the radio as found
|
|
_dc_RadioFound(radio);
|
|
} else {
|
|
safe_free(radio);
|
|
}
|
|
}
|
|
|
|
static struct timeval timeout;
|
|
//! Allocates a buffer and receives one packet.
|
|
//! /param buffer Buffer to be allocated and populated with packet data
|
|
//! /returns Number of bytes read if successful, otherwise an error (recvfrom)
|
|
static BOOL _dc_ListenerRecv(uint8* buffer, int32* len, struct sockaddr_in* sender_addr)
|
|
{
|
|
//output("_dc_ListenerRecv\n");
|
|
|
|
uint32 addr_len = sizeof(struct sockaddr_in);
|
|
|
|
// we will wait up to 1 second for data in case someone is trying to abort us
|
|
|
|
timeout.tv_sec = 1;
|
|
timeout.tv_usec = 0;
|
|
fd_set socks;
|
|
FD_ZERO(&socks);
|
|
FD_SET(_dc_sock, &socks);
|
|
|
|
// see if there is data in the socket (but timeout if none)
|
|
select(_dc_sock + 1, &socks, NULL, NULL, &timeout);
|
|
if (FD_ISSET(_dc_sock, &socks))
|
|
{
|
|
// yes there is data -- get it
|
|
*len = recvfrom(_dc_sock, buffer, ETH_FRAME_LEN, 0, (struct sockaddr*)sender_addr, &addr_len);
|
|
//precisionTimerLap("HAL Listener Recv");
|
|
if(*len < 0)
|
|
output("_dc_ListenerRecv: recvfrom returned -1 errno=%08X\n", errno);
|
|
//else
|
|
//output("_hal_ListenerRecv: Error len=%d sender=%s:%u\n", len, inet_ntoa(sender_addr.sin_addr), htons(sender_addr.sin_port));
|
|
|
|
// TODO: May need to filter here to handle security (packet injection)
|
|
return TRUE;
|
|
}
|
|
|
|
*len = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
static void* _dc_ListenerLoop(void* param)
|
|
{
|
|
//printf("_dc_ListenerLoop\n");
|
|
|
|
struct sockaddr_in sender;
|
|
uint8 buf[ETH_FRAME_LEN];
|
|
|
|
while(!_dc_abort)
|
|
{
|
|
// get some data
|
|
int32 length = 0;
|
|
BOOL success = FALSE;
|
|
|
|
while (!success && !_dc_abort)
|
|
{
|
|
memset(&sender,0,sizeof(struct sockaddr_in));
|
|
memset(&buf,0,ETH_FRAME_LEN);
|
|
success = _dc_ListenerRecv(buf, &length, &sender);
|
|
}
|
|
|
|
if (!_dc_abort)
|
|
{
|
|
if(length == 0) // socket has been closed
|
|
{
|
|
output("_dc_ListenerLoop error: socket closed\n");
|
|
break;
|
|
}
|
|
|
|
if(length < 0)
|
|
{
|
|
output("_dc_ListenerLoop error: loop stopped\n");
|
|
break;
|
|
}
|
|
|
|
// length was reasonable -- lets try to parse the packet
|
|
//precisionTimerLap("HAL Listener Parse Packet");
|
|
_dc_ListenerParsePacket(buf, length, &sender);
|
|
}
|
|
}
|
|
|
|
if ( _restrict_ip ) {
|
|
safe_free(_restrict_ip);
|
|
_restrict_ip = NULL;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void dc_Init(const char * radio_ip)
|
|
{
|
|
output("Discovery Client Init: Opening socket");
|
|
int true = TRUE;
|
|
if((_dc_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
|
|
{
|
|
output("...failed! (socket call returned -1)\n");
|
|
return;
|
|
}
|
|
|
|
// set up destination address
|
|
struct sockaddr_in addr;
|
|
|
|
memset(&addr,0,sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = htonl(INADDR_ANY); // this is where we could limit to one IP
|
|
addr.sin_port=htons(DISCOVERY_PORT);
|
|
|
|
/* If you're running on the same box as the smartsdr firmware this is necessary so
|
|
* that both processes can bind to the same VITA port
|
|
*/
|
|
errno = 0;
|
|
setsockopt(_dc_sock, SOL_SOCKET, SO_REUSEADDR, &true, sizeof(true));
|
|
if (errno)
|
|
{
|
|
output("error with reuse option: errno=%d",errno);
|
|
}
|
|
|
|
// bind the socket to the port and/or IP
|
|
output("...binding");
|
|
errno = 0;
|
|
if(bind(_dc_sock, (struct sockaddr *)&addr, sizeof(addr)) == -1)
|
|
{
|
|
output("...failed! (bind call returned -1: errno:%d)\n", errno);
|
|
return;
|
|
}
|
|
output("\n");
|
|
|
|
if ( _restrict_ip ) {
|
|
safe_free(_restrict_ip );
|
|
_restrict_ip = NULL;
|
|
}
|
|
|
|
if ( radio_ip != NULL ) {
|
|
_restrict_ip = safe_malloc(strlen(radio_ip) + 1 );
|
|
strncpy(_restrict_ip, radio_ip, strlen(radio_ip) + 1);
|
|
}
|
|
|
|
// start the listener thread
|
|
pthread_create(&_dc_thread, NULL, &_dc_ListenerLoop, NULL);
|
|
}
|
|
|
|
void dc_Exit(void)
|
|
{
|
|
_dc_abort = TRUE;
|
|
}
|