diff --git a/.gitignore b/.gitignore index ad00755..8690511 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.o *.d +qnitap qndvap qndvrptr qnlink diff --git a/Makefile b/Makefile index 6b046e8..e9f26bd 100644 --- a/Makefile +++ b/Makefile @@ -37,10 +37,11 @@ SRCS = $(wildcard *.cpp) $(wildcard $(IRC)/*.cpp) OBJS = $(SRCS:.cpp=.o) DEPS = $(SRCS:.cpp=.d) -ALL_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay qndvap qndvrptr +ALL_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay qndvap qndvrptr qnitap MDV_PROGRAMS=qngateway qnlink qnremote qnvoice qnrelay DVP_PROGRAMS=qngateway qnlink qnremote qnvoice qndvap DVR_PROGRAMS=qngateway qnlink qnremote qnvoice qndvrptr +TAP_PROGRAMS=qngateway qnlink qnremote qnvoice qnitap ICM_PROGRAMS=qngateway qnlink qnremote qnvoice all : $(ALL_PROGRAMS) @@ -48,6 +49,7 @@ mmdvm : $(MDV_PROGRAMS) dvap : $(DVP_PROGRAMS) dvrptr : $(DVR_PROGRAMS) icom : $(ICM_PROGRAMS) +itap : $(TAP_PROGRAMS) qngateway : $(IRCOBJS) QnetGateway.o aprs.o g++ $(CPPFLAGS) -o qngateway QnetGateway.o aprs.o $(IRCOBJS) $(LDFLAGS) -pthread @@ -58,6 +60,9 @@ qnlink : QnetLink.o Random.o qnrelay : QnetRelay.o g++ $(CPPFLAGS) -o qnrelay QnetRelay.o $(LDFLAGS) +qnitap : QnetITAP.o Random.o + g++ $(CPPFLAGS) -o qnitap QnetITAP.o Random.o $(LDFLAGS) + qndvap : QnetDVAP.o DVAPDongle.o Random.o $(DSTROBJS) g++ $(CPPFLAGS) -o qndvap QnetDVAP.o DVAPDongle.o Random.o $(DSTROBJS) $(LDFLAGS) -pthread @@ -105,6 +110,31 @@ install : $(MDV_PROGRAMS) gwys.txt qn.cfg systemctl daemon-reload systemctl start qnrelay.service +installitap : $(TAP_PROGRAMS) gwys.txt qn.cfg + ######### QnetGateway ######### + /bin/cp -f qngateway $(BINDIR) + /bin/cp -f qnremote qnvoice $(BINDIR) + /bin/ln -s $(shell pwd)/qn.cfg $(CFGDIR) + /bin/cp -f system/qngateway.service $(SYSDIR) + systemctl enable qngateway.service + systemctl daemon-reload + systemctl start qngateway.service + ######### QnetLink ######### + /bin/cp -f qnlink $(BINDIR) + /bin/cp -f announce/*.dat $(CFGDIR) + /bin/ln -s $(shell pwd)/gwys.txt $(CFGDIR) + /bin/cp -f exec_?.sh $(CFGDIR) + /bin/cp -f system/qnlink.service $(SYSDIR) + systemctl enable qnlink.service + systemctl daemon-reload + systemctl start qnlink.service + ######### QnetITAP ######### + /bin/cp -f qnitap $(BINDIR) + /bin/cp -f system/qnitap.service $(SYSDIR) + systemctl enable qnitap.service + systemctl daemon-reload + systemctl start qnitap.service + installicom : $(ICM_PROGRAMS) gwys.txt qn.cfg ######### QnetGateway ######### /bin/cp -f qngateway $(BINDIR) @@ -229,6 +259,36 @@ uninstall : /bin/rm -f $(BINDIR)/qnrelay systemctl daemon-reload +uninstallitap : + ######### QnetGateway ######### + systemctl stop qngateway.service + systemctl disable qngateway.service + /bin/rm -f $(SYSDIR)/qngateway.service + /bin/rm -f $(BINDIR)/qngateway + /bin/rm -f $(BINDIR)/qnremote + /bin/rm -f $(BINDIR)/qnvoice + /bin/rm -f $(CFGDIR)/qn.cfg + ######### QnetLink ######### + systemctl stop qnlink.service + systemctl disable qnlink.service + /bin/rm -f $(SYSDIR)/qnlink.service + /bin/rm -f $(BINDIR)/qnlink + /bin/rm -f $(CFGDIR)/already_linked.dat + /bin/rm -f $(CFGDIR)/already_unlinked.dat + /bin/rm -f $(CFGDIR)/failed_linked.dat + /bin/rm -f $(CFGDIR)/id.dat + /bin/rm -f $(CFGDIR)/linked.dat + /bin/rm -f $(CFGDIR)/unlinked.dat + /bin/rm -f $(CFGDIR)/RPT_STATUS.txt + /bin/rm -f $(CFGDIR)/gwys.txt + /bin/rm -f $(CFGDIR)/exec_?.sh + ######### QnetITAP ######### + systemctl stop qnitap.service + systemctl disable qnitap.service + /bin/rm -f $(SYSDIR)/qnitap.service + /bin/rm -f $(BINDIR)/qnitap + systemctl daemon-reload + uninstallicom : ######### QnetGateway ######### systemctl stop qngateway.service diff --git a/QnetITAP.cpp b/QnetITAP.cpp new file mode 100644 index 0000000..648e26e --- /dev/null +++ b/QnetITAP.cpp @@ -0,0 +1,701 @@ +/* + * Copyright (C) 2018 by Thomas A. Early N7TAE + * Copyright (C) 2011-2015,2018 by Jonathan Naylor G4KLX + * + * 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "versions.h" +#include "QnetITAP.h" +#include "QnetTypeDefs.h" + +std::atomic CQnetITAP::keep_running(true); + +CQnetITAP::CQnetITAP() : +COUNTER(0) +{ +} + +CQnetITAP::~CQnetITAP() +{ +} + +bool CQnetITAP::Initialize(const char *cfgfile) +{ + if (ReadConfig(cfgfile)) + return true; + + struct sigaction act; + act.sa_handler = &CQnetITAP::SignalCatch; + sigemptyset(&act.sa_mask); + if (sigaction(SIGTERM, &act, 0) != 0) { + printf("sigaction-TERM failed, error=%d\n", errno); + return true; + } + if (sigaction(SIGHUP, &act, 0) != 0) { + printf("sigaction-HUP failed, error=%d\n", errno); + return true; + } + if (sigaction(SIGINT, &act, 0) != 0) { + printf("sigaction-INT failed, error=%d\n", errno); + return true; + } + + return false; +} + +int CQnetITAP::OpenITAP() +{ + int fd = open(ITAP_DEVICE.c_str(), O_RDWR | O_NOCTTY | O_NDELAY, 0); + if (fd < 0) { + printf("Failed to open device [%s], error=%d, message=%s\n", ITAP_DEVICE.c_str(), errno, strerror(errno)); + return -1; + } + + if (isatty(fd) == 0) { + printf("Device %s is not a tty device\n", ITAP_DEVICE.c_str()); + close(fd); + return -1; + } + + static termios t; + if (tcgetattr(fd, &t) < 0) { + printf("tcgetattr failed for %s, error=%d, message-%s\n", ITAP_DEVICE.c_str(), errno, strerror(errno)); + close(fd); + return -1; + } + + t.c_lflag &= ~(ECHO | ECHOE | ICANON | IEXTEN | ISIG); + t.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON | IXOFF | IXANY); + t.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS); + t.c_cflag |= CS8; + t.c_oflag &= ~(OPOST); + t.c_cc[VMIN] = 0; + t.c_cc[VTIME] = 10; + + cfsetospeed(&t, B38400); + cfsetispeed(&t, B38400); + + if (tcsetattr(fd, TCSANOW, &t) < 0) { + printf("tcsetattr failed for %s, error=%dm message=%s\n", ITAP_DEVICE.c_str(), errno, strerror(errno)); + close(fd); + return -1; + } + + return fd; +} + +int CQnetITAP::OpenSocket(const std::string &address, const unsigned short port) +{ + if (! port) { + printf("ERROR: OpenSocket: non-zero port must be specified.\n"); + return -1; + } + + int fd = ::socket(PF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + printf("Cannot create the UDP socket, err: %d, %s\n", errno, strerror(errno)); + return -1; + } + + sockaddr_in addr; + ::memset(&addr, 0, sizeof(sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_ANY); + + if (! address.empty()) { + addr.sin_addr.s_addr = ::inet_addr(address.c_str()); + if (addr.sin_addr.s_addr == INADDR_NONE) { + printf("The local address is invalid - %s\n", address.c_str()); + close(fd); + return -1; + } + } + + int reuse = 1; + if (::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) { + printf("Cannot set the UDP socket %s:%u option, err: %d, %s\n", address.c_str(), port, errno, strerror(errno)); + close(fd); + return -1; + } + + if (::bind(fd, (sockaddr*)&addr, sizeof(sockaddr_in)) == -1) { + printf("Cannot bind the UDP socket %s:%u address, err: %d, %s\n", address.c_str(), port, errno, strerror(errno)); + close(fd); + return -1; + } + + return fd; +} + +REPLY_TYPE CQnetITAP::GetITAPData(unsigned char *buf) +{ + // Shamelessly adapted from Jonathan G4KLX's CIcomController::GetResponse() + // Get the start of the frame or nothing at all + int ret = ::read(serfd, buf, 1U); + if (ret < 0) { + printf("Error when reading first byte from the Icom radio %d: %s", errno, strerror(errno)); + return RT_ERROR; + } + + if (ret == 0) + return RT_TIMEOUT; + + if (buf[0U] == 0xFFU) + return RT_TIMEOUT; + + unsigned int length = buf[0U]; + + if (length >= 100U) { + printf("Invalid data received from the Icom radio"); + return RT_ERROR; + } + + unsigned int offset = 1U; + + while (offset < length) { + ret = ::read(serfd, buf + offset, length - offset); + if (ret < 0) { + printf("Error when reading buffer from the Icom radio %d: %s", errno, strerror(errno)); + return RT_ERROR; + } + + if (ret > 0) + offset += ret; + + if (ret == 0) + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + + switch (buf[1U]) { + case 0x03U: + return RT_PONG; + case 0x10U: + return RT_HEADER; + case 0x12U: + if ((buf[3U] & 0x40U) == 0x40U) + return RT_EOT; + else + return RT_DATA; + case 0x21U: + return RT_HEADER_ACK; + case 0x23U: + return RT_DATA_ACK; + default: + return RT_UNKNOWN; + } +} + +void CQnetITAP::Run(const char *cfgfile) +{ + if (Initialize(cfgfile)) + return; + + serfd = OpenITAP(); + if (serfd < 0) + return; + + gsock = OpenSocket(G2_INTERNAL_IP, G2_OUT_PORT); + if (gsock < 0) { + ::close(serfd); + return; + } + + vsock = OpenSocket(std::string("0.0.0.0"), MMDVM_OUT_PORT); + if (vsock < 0) { + ::close(serfd); + ::close(gsock); + return; + } + + printf("vsock=%d, gsock=%d serfd=%d\n", vsock, gsock, serfd); + + keep_running = true; + + while (keep_running) { + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(serfd, &readfds); + FD_SET(gsock, &readfds); + int maxfs = (serfd > gsock) ? serfd : gsock; + + // don't care about writefds and exceptfds: + // and we'll wait as long as needed + int ret = ::select(maxfs+1, &readfds, NULL, NULL, NULL); + if (ret < 0) { + printf("ERROR: Run: select returned err=%d, %s\n", errno, strerror(errno)); + break; + } + if (ret == 0) + continue; + + // there is something to read! + unsigned char buf[100]; + sockaddr_in addr; + memset(&addr, 0, sizeof(sockaddr_in)); + socklen_t size = sizeof(sockaddr); + ssize_t len; + REPLY_TYPE rt = RT_NOTHING; + + if (FD_ISSET(serfd, &readfds)) { + rt = GetITAPData(buf); + + if (rt == RT_ERROR) { + printf("ERROR: Run: recvfrom(USB) return error %d, %s\n", errno, strerror(errno)); + break; + } + + if (rt == RT_TIMEOUT) + continue; + + } + + if (FD_ISSET(gsock, &readfds)) { + len = ::recvfrom(gsock, buf, 100, 0, (sockaddr *)&addr, &size); + + if (len < 0) { + printf("ERROR: Run: recvfrom(gsock) returned error %d, %s\n", errno, strerror(errno)); + break; + } + + if (ntohs(addr.sin_port) != G2_IN_PORT) + printf("DEBUG: Run: read from gsock but the port was %u, expected %u\n", ntohs(addr.sin_port), G2_IN_PORT); + + } + + if (len == 0) { + printf("DEBUG: Run: read zero bytes from %u\n", ntohs(addr.sin_port)); + continue; + } + + if (rt != RT_NOTHING) { + //printf("read %d bytes from ITAP\n", (int)buf[0]); + if (RT_DATA==rt || RT_HEADER==rt || RT_EOT==rt) { + if (ProcessITAP(buf)) + break; + } else { + switch (rt) { + case RT_HEADER_ACK: + printf("DEBUG: Run: got header acknowledgement\n"); + break; + case RT_DATA_ACK: + printf("DEBUG: Run: got data acknowledgement\n"); + break; + case RT_PONG: + printf("DEBUG: Run: got pong\n"); + break; + case RT_TIMEOUT: + printf("DEBUG: Run: got timeout\n"); + break; + default: + break; + } + } + } else if (0 == ::memcmp(buf, "DSTR", 4)) { + //printf("read %d bytes from QnetGateway\n", (int)len); + if (ProcessGateway(len, buf)) + break; + } else { + char title[5]; + for (int i=0; i<4; i++) + title[i] = (buf[i]>=0x20u && buf[i]<0x7fu) ? buf[i] : '.'; + title[4] = '\0'; + printf("DEBUG: Run: received unknow packet '%s' len=%d\n", title, (int)len); + } + } + + ::close(serfd); + ::close(gsock); + ::close(vsock); +} + +int CQnetITAP::SendTo(const unsigned char *buf) +{ + unsigned int ptr = 0; + unsigned int len = buf[0]; + + while (ptr < len) { + ssize_t n = ::write(serfd, buf + ptr, len - ptr); + if (n < 0) { + printf("Error %d writing to dvap, message=%s\n", errno, strerror(errno)); + return -1; + } + + if (n > 0) + ptr += n; + } + + return len; +} + +int CQnetITAP::SendTo(const int fd, const unsigned char *buf, const int size, const std::string &address, const unsigned short port) +{ + sockaddr_in addr; + ::memset(&addr, 0, sizeof(sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = ::inet_addr(address.c_str()); + addr.sin_port = htons(port); + + int len = ::sendto(fd, buf, size, 0, (sockaddr *)&addr, sizeof(sockaddr_in)); + if (len < 0) + printf("ERROR: SendTo: fd=%d failed sendto %s:%u err: %d, %s\n", fd, address.c_str(), port, errno, strerror(errno)); + else if (len != size) + printf("ERROR: SendTo: fd=%d tried to sendto %s:%u %d bytes, actually sent %d.\n", fd, address.c_str(), port, size, len); + return len; +} + +bool CQnetITAP::ProcessGateway(const int len, const unsigned char *raw) +{ + static unsigned int counter = 0; + if (29==len || 58==len) { //here is dstar data + SDSTR dstr; + ::memcpy(dstr.pkt_id, raw, len); // transfer raw data to SDSTR struct + + SITAP itap; // destination + if (58 == len) { // write a Header packet + counter = 0; + itap.length = 41U; + itap.type = 0x20; + memcpy(itap.header.flag, dstr.vpkt.hdr.flag, 3); + memcpy(itap.header.r1, dstr.vpkt.hdr.r1, 8); + memcpy(itap.header.r2, dstr.vpkt.hdr.r2, 8); + memcpy(itap.header.ur, dstr.vpkt.hdr.ur, 8); + memcpy(itap.header.my, dstr.vpkt.hdr.my, 8); + memcpy(itap.header.nm, dstr.vpkt.hdr.nm, 4); + int ret = SendTo(&itap.length); + if (ret != 49) { + printf("41: ProcessGateway: Could not write Header ITAP packet\n"); + return true; + } + if (log_qso) + printf("Sent ITAP to %s ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", ITAP_DEVICE.c_str(), + itap.header.ur, itap.header.r1, itap.header.r2, itap.header.my, itap.header.nm); + } else { // write an AMBE packet + itap.length = 17U; + itap.type = 0x22U; + itap.voice.counter = counter++; + itap.voice.sequence = dstr.vpkt.ctrl; + if (log_qso && dstr.vpkt.ctrl&0x40) + printf("Sent ITAP end of stream\n"); + else if (dstr.vpkt.ctrl > 20) + printf("DEBUG: ProcessGateway: unexpected voice sequence number %d\n", itap.voice.sequence); + memcpy(itap.voice.ambe, dstr.vpkt.vasd.voice, 12); + itap.voice.end = 0xFFU; + int ret = SendTo(&itap.length); + if (ret != 17) { + printf("ERROR: ProcessGateway: Could not write AMBE ITAP packet\n"); + return true; + } + } + + } else + printf("DEBUG: ProcessGateway: unusual packet size read len=%d\n", len); + return false; +} + +bool CQnetITAP::ProcessITAP(const unsigned char *buf) +{ + static short stream_id = 0U; + SITAP itap; + unsigned int len = buf[0]; + if (len < 42) + ::memcpy(&itap.length, buf, len); // transfer raw data to SDSRP struct + + if (41==len || 16==len) { + // create a stream id if this is a header + if (41 == len) + stream_id = random.NewStreamID(); + + SDSTR dstr; // destination + // sets most of the params + ::memcpy(dstr.pkt_id, "DSTR", 4); + dstr.counter = htons(COUNTER++); + dstr.flag[0] = 0x73; + dstr.flag[1] = 0x12; + dstr.flag[2] = 0x0; + dstr.vpkt.icm_id = 0x20; + dstr.vpkt.dst_rptr_id = 0x0; + dstr.vpkt.snd_rptr_id = 0x1; + dstr.vpkt.snd_term_id = ('B'==RPTR_MOD) ? 0x1 : (('C'==RPTR_MOD) ? 0x2 : 0x3); + dstr.vpkt.streamid = stream_id; + + if (41 == len) { // header + dstr.remaining = 0x30; + dstr.vpkt.ctrl = 0x80; + //memcpy(dstr.vpkt.hdr.flag, dsrp.header.flag, 41); + memcpy(dstr.vpkt.hdr.flag, itap.header.flag, 3); + memcpy(dstr.vpkt.hdr.r1, itap.header.r1, 8); + memcpy(dstr.vpkt.hdr.r2, itap.header.r2, 8); + memcpy(dstr.vpkt.hdr.ur, itap.header.ur, 8); + memcpy(dstr.vpkt.hdr.my, itap.header.my, 8); + memcpy(dstr.vpkt.hdr.nm, itap.header.nm, 4); + calcPFCS(dstr.vpkt.hdr.flag, dstr.vpkt.hdr.pfcs); + int ret = SendTo(vsock, dstr.pkt_id, 58, G2_INTERNAL_IP, G2_IN_PORT); + if (ret != 58) { + printf("ERROR: ProcessITAP: Could not write gateway header packet\n"); + return true; + } + if (log_qso) + printf("Sent DSTR to %u, streamid=%04x ur=%.8s r1=%.8s r2=%.8s my=%.8s/%.4s\n", G2_IN_PORT, ntohs(dstr.vpkt.streamid), + dstr.vpkt.hdr.ur, dstr.vpkt.hdr.r1, dstr.vpkt.hdr.r2, dstr.vpkt.hdr.my, dstr.vpkt.hdr.nm); + } else if (16 == len) { // ambe + dstr.remaining = 0x16; + dstr.vpkt.ctrl = itap.voice.sequence; + memcpy(dstr.vpkt.vasd.voice, itap.voice.ambe, 12); + int ret = SendTo(vsock, dstr.pkt_id, 29, G2_INTERNAL_IP, G2_IN_PORT); + if (log_qso && dstr.vpkt.ctrl&0x40) + printf("Sent dstr end of streamid=%04x\n", ntohs(dstr.vpkt.streamid)); + + if (ret != 29) { + printf("ERROR: ProcessMMDVM: Could not write gateway voice packet\n"); + return true; + } + } +// } else if (len < 65 && dsrp.tag == 0xAU) { +// printf("MMDVM Poll: '%s'\n", (char *)mpkt.poll_msg); + } else + printf("DEBUG: ProcessMMDVM: unusual packet len=%d\n", (int)buf[0]); + return false; +} + +bool CQnetITAP::GetValue(const Config &cfg, const char *path, int &value, const int min, const int max, const int default_value) +{ + if (cfg.lookupValue(path, value)) { + if (value < min || value > max) + value = default_value; + } else + value = default_value; + printf("%s = [%d]\n", path, value); + return true; +} + +bool CQnetITAP::GetValue(const Config &cfg, const char *path, double &value, const double min, const double max, const double default_value) +{ + if (cfg.lookupValue(path, value)) { + if (value < min || value > max) + value = default_value; + } else + value = default_value; + printf("%s = [%lg]\n", path, value); + return true; +} + +bool CQnetITAP::GetValue(const Config &cfg, const char *path, bool &value, const bool default_value) +{ + if (! cfg.lookupValue(path, value)) + value = default_value; + printf("%s = [%s]\n", path, value ? "true" : "false"); + return true; +} + +bool CQnetITAP::GetValue(const Config &cfg, const char *path, std::string &value, int min, int max, const char *default_value) +{ + if (cfg.lookupValue(path, value)) { + int l = value.length(); + if (lmax) { + printf("%s value '%s' is wrong size\n", path, value.c_str()); + return false; + } + } else + value = default_value; + printf("%s = [%s]\n", path, value.c_str()); + return true; +} + +// process configuration file and return true if there was a problem +bool CQnetITAP::ReadConfig(const char *cfgFile) +{ + Config cfg; + + printf("Reading file %s\n", cfgFile); + // Read the file. If there is an error, report it and exit. + try { + cfg.readFile(cfgFile); + } + catch(const FileIOException &fioex) { + printf("Can't read %s\n", cfgFile); + return true; + } + catch(const ParseException &pex) { + printf("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError()); + return true; + } + + std::string itap_path, value; + int i; + for (i=0; i<3; i++) { + itap_path = "module."; + itap_path += ('a' + i); + if (cfg.lookupValue(itap_path + ".type", value)) { + if (0 == strcasecmp(value.c_str(), "itap")) + break; + } + } + if (i >= 3) { + printf("itap not defined in any module!\n"); + return true; + } + RPTR_MOD = 'A' + i; + int repeater_module = i; + MMDVM_OUT_PORT = (unsigned short int)(i + 19998); + + if (cfg.lookupValue(std::string(itap_path+".callsign").c_str(), value) || cfg.lookupValue("ircddb.login", value)) { + int l = value.length(); + if (l<3 || l>CALL_SIZE-2) { + printf("Call '%s' is invalid length!\n", value.c_str()); + return true; + } else { + for (i=0; iCALL_SIZE-2) { + printf("Call '%s' is invalid length!\n", value.c_str()); + return true; + } else { + for (i=0; i> 8) ^ crc_tabccitt[tmp]; + } + crc_dstar_ffff = ~crc_dstar_ffff; + tmp = crc_dstar_ffff; + + pfcs[0] = (unsigned char)(crc_dstar_ffff & 0xff); + pfcs[1] = (unsigned char)((tmp >> 8) & 0xff); + + return; +} + +int main(int argc, const char **argv) +{ + setbuf(stdout, NULL); + if (2 != argc) { + printf("usage: %s path_to_config_file\n", argv[0]); + printf(" %s --version\n", argv[0]); + return 1; + } + + if ('-' == argv[1][0]) { + printf("\nQnetITAP Version #%s Copyright (C) 2018 by Thomas A. Early N7TAE\n", ITAP_VERSION); + printf("QnetITAP comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"); + printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n"); + return 0; + } + + CQnetITAP qnitap; + + qnitap.Run(argv[1]); + + printf("%s is closing.\n", argv[0]); + + return 0; +} diff --git a/QnetITAP.h b/QnetITAP.h new file mode 100644 index 0000000..6083d2f --- /dev/null +++ b/QnetITAP.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2018 by Thomas A. Early N7TAE + * + * 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + +#include +#include +#include + +#include +#include "Random.h" // for streamid generation + +using namespace libconfig; + +#define CALL_SIZE 8 +#define IP_SIZE 15 + +enum REPLY_TYPE { + RT_TIMEOUT, + RT_ERROR, + RT_UNKNOWN, + RT_HEADER, + RT_DATA, + RT_EOT, + RT_HEADER_ACK, + RT_DATA_ACK, + RT_PONG, + RT_NOTHING +}; + +// Icom Terminal and Access Point Mode data structure +#pragma pack(push, 1) +typedef struct itap_tag { + unsigned char length; + // 41 for header (42 for writing) + // 16 for voice (17 for writing) + unsigned char type; + // 0x03U pong + // 0x10U header from icom + // 0x12U data from icom (it's EOT if voice.sequence bit 0x40 is set) + // 0x20U header to icom + // 0x21U header acknowledgement + // 0x22U data to icom + // 0x23U data acknowledgement + union { + struct { + unsigned char flag[3]; + unsigned char r2[8]; + unsigned char r1[8]; + unsigned char ur[8]; + unsigned char my[8]; + unsigned char nm[4]; + unsigned char end; // 0xFFU for writing + } header; + struct { + unsigned char counter; // ordinal counter is reset with each header + unsigned char sequence; // is modulo 21 + unsigned char ambe[9]; + unsigned char text[3]; + unsigned char end; // 0xFFU for writing + } voice; + }; +} SITAP; +#pragma pack(pop) + +class CQnetITAP +{ +public: + // functions + CQnetITAP(); + ~CQnetITAP(); + void Run(const char *cfgfile); + + // data + static std::atomic keep_running; + +private: + // functions + bool Initialize(const char *cfgfile); + static void SignalCatch(const int signum); + bool ProcessGateway(const int len, const unsigned char *raw); + bool ProcessITAP(const unsigned char *raw); + int OpenSocket(const std::string &address, const unsigned short port); + int OpenITAP(); + int SendTo(const int fd, const unsigned char *buf, const int size, const std::string &address, const unsigned short port); + int SendTo(const unsigned char *buf); + REPLY_TYPE GetITAPData(unsigned char *buf); + void calcPFCS(const unsigned char *packet, unsigned char *pfcs); + + // read configuration file + bool ReadConfig(const char *); + bool GetValue(const Config &cfg, const char *path, int &value, const int min, const int max, const int default_value); + bool GetValue(const Config &cfg, const char *path, double &value, const double min, const double max, const double default_value); + bool GetValue(const Config &cfg, const char *path, bool &value, const bool default_value); + bool GetValue(const Config &cfg, const char *path, std::string &value, const int min, const int max, const char *default_value); + + // config data + char RPTR_MOD; + char RPTR[CALL_SIZE + 1]; + char OWNER[CALL_SIZE + 1]; + std::string ITAP_DEVICE, G2_INTERNAL_IP; + unsigned short MMDVM_IN_PORT, MMDVM_OUT_PORT, G2_IN_PORT, G2_OUT_PORT; + bool log_qso; + + // parameters + int serfd, gsock, vsock; + unsigned char tapcounter; + unsigned short COUNTER; + + // helpers + CRandom random; +}; diff --git a/system/qnitap.service b/system/qnitap.service new file mode 100644 index 0000000..aa52f76 --- /dev/null +++ b/system/qnitap.service @@ -0,0 +1,10 @@ +[Unit] +Description=QnetITAP +After=systemd-user-session.service + +[Service] +Type=simple +ExecStart=/usr/local/bin/qnitap /usr/local/etc/qn.cfg + +[Install] +WantedBy=multi-user.target diff --git a/versions.h b/versions.h index 2c7bcf5..8fb5e86 100644 --- a/versions.h +++ b/versions.h @@ -3,6 +3,7 @@ #define LINK_VERSION "QnetLink-6.0.0" #define DVAP_VERSION "QnetDVAP-5.1.1" #define RELAY_VERSION "QnetRelay-0.2.0" +#define ITAP_VERSION "QnetITAP-0.0.0" #define DVRPTR_VERSION "QnetDVRPTR-5.1.0" #define MMDVM_VERSION "QnetGateway-MMDVM-0.1.0" #define ICOM_VERSION IRCDDB_VERSION