/* Copyright 2001-2022 John Wiseman G8BPQ This file is part of LinBPQ/BPQ32. LinBPQ/BPQ32 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. LinBPQ/BPQ32 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 LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses */ /* Netrom over TCP Support This is intended for operation over radio links with an IP interface, eg New Packet Radio or possibly microwave links To simplify interface to the rest of the oode dummy LINK and PORT records are created Packet Format is Length (2 byte little endian) Call (10 bytes ASCII) NETROM L3/4 Packet, starting 0xcf (to detect framing errors). A TCP message can contain multiple packets and/or partial packets It uses the Telnet Server, with port defined in NETROMPORT ROUTE definitions have an extra field, the TCP Port Number */ //#pragma data_seg("_BPQDATA") #define _CRT_SECURE_NO_DEPRECATE #include "time.h" #include "stdio.h" #include //#include "vmm.h" #include "cheaders.h" #include "asmstrucs.h" #include "telnetserver.h" #define NETROM_PID 0xCF void NETROMConnectionLost(struct ConnectionInfo * sockptr); int DataSocket_ReadNETROM(struct ConnectionInfo * sockptr, SOCKET sock, struct NRTCPSTRUCT * Info, int portNo); int NETROMTCPConnect(struct ROUTE * Route, struct ConnectionInfo * sockptr); void NETROMConnected(struct ConnectionInfo * sockptr, SOCKET sock, struct NRTCPSTRUCT * Info); VOID SendRTTMsg(struct ROUTE * Route); BOOL FindNeighbour(UCHAR * Call, int Port, struct ROUTE ** REQROUTE); VOID NETROMMSG(struct _LINKTABLE * LINK, L3MESSAGEBUFFER * L3MSG); int BPQTRACE(MESSAGE * Msg, BOOL TOAPRS); VOID L3LINKCLOSED(struct _LINKTABLE * LINK, int Reason); void NetromTCPTrace(struct _MESSAGE * Message, char * Dirn); void NETROMCloseTCP(struct ROUTE * Route); extern SOCKET NodeAPISocket; struct NRTCPMsg { short Length; char Call[10]; unsigned char PID; char Packet[1024]; }; struct NRTCPSTRUCT { struct ConnectionInfo * sockptr; struct _LINKTABLE * LINK; // Dummy Link Record for this ROUTE struct ROUTE * Route; // May need backlink char Call[10]; }; struct NRTCPSTRUCT * NRTCPInfo[256] = {0}; // Do we want to use normal TCP server connections, which are limited, or our own. Let's try our own for now struct ConnectionInfo * AllocateNRTCPRec() { struct ConnectionInfo * sockptr = 0; struct NRTCPSTRUCT * Info; int i; for (i = 0; i < 255; i++) { if (NRTCPInfo[i] == 0) { // only allocate as many as needed Info = NRTCPInfo[i] = (struct NRTCPSTRUCT *)zalloc(sizeof(struct NRTCPSTRUCT)); Info->sockptr = (struct ConnectionInfo *)zalloc(sizeof(struct ConnectionInfo)); Info->LINK = (struct _LINKTABLE *)zalloc(sizeof(struct _LINKTABLE)); } else Info = NRTCPInfo[i]; Info->sockptr->Number = i; sockptr = Info->sockptr; if (sockptr->SocketActive == FALSE) { sockptr->SocketActive = TRUE; sockptr->ConnectTime = sockptr->LastSendTime = time(NULL); Debugprintf("NRTCP Allocated %d", i); return sockptr; } } return 0; } void checkNRTCPSockets(int portNo) { SOCKET sock; int Active = 0; SOCKET maxsock; int retval; int i; time_t Now = time(NULL); struct timeval timeout; fd_set readfd, writefd, exceptfd; struct ConnectionInfo * sockptr; timeout.tv_sec = 0; timeout.tv_usec = 0; // poll maxsock = 0; FD_ZERO(&readfd); FD_ZERO(&writefd); FD_ZERO(&exceptfd); for (i = 0; i < 255; i++) { if (NRTCPInfo[i] == 0) break; // only as many as have been used sockptr = NRTCPInfo[i]->sockptr; if (sockptr->SocketActive == 0) continue; if (sockptr->Connected && (Now - sockptr->LastReceiveTime) > 600) { struct NRTCPSTRUCT * Info = NRTCPInfo[sockptr->Number]; if (Info) Debugprintf("NETROMTCP link to %s idle for too long, closing", Info->Call); else Debugprintf("NETROMTCP link idle for too long, closing"); NETROMConnectionLost(sockptr); continue; } if (sockptr->Connecting) { // look for complete or failed FD_SET(sockptr->socket, &writefd); FD_SET(sockptr->socket, &exceptfd); } else { FD_SET(sockptr->socket, &readfd); FD_SET(sockptr->socket, &exceptfd); } Active++; if (sockptr->socket > maxsock) maxsock = sockptr->socket; } if (Active) { retval = select((int)maxsock + 1, &readfd, &writefd, &exceptfd, &timeout); if (retval == -1) { perror("data select"); Debugprintf("NRTCP Select Error %d Active %d", WSAGetLastError(), Active); } else { if (retval) { // see who has data for (i = 0; i < 255; i++) { if (NRTCPInfo[i] == 0) break; sockptr = NRTCPInfo[i]->sockptr; if (sockptr->SocketActive == 0) continue; sock = sockptr->socket; if (FD_ISSET(sock, &writefd)) NETROMConnected(sockptr, sock, NRTCPInfo[i]); if (FD_ISSET(sock, &readfd)) DataSocket_ReadNETROM(sockptr, sock, NRTCPInfo[i], portNo); if (FD_ISSET(sock, &exceptfd)) NETROMConnectionLost(sockptr); } } } } } int NETROMOpenConnection(struct ROUTE * Route) { struct NRTCPSTRUCT * Info; struct ConnectionInfo * sockptr; char farCall[10]; farCall[ConvFromAX25(Route->NEIGHBOUR_CALL, farCall)] = 0; sockptr = AllocateNRTCPRec(); if (sockptr == NULL) return 0; Debugprintf("Opening NRTCP Connection to %s index %d", farCall, sockptr->Number); Info = Route->TCPSession = NRTCPInfo[sockptr->Number]; memcpy(Info->Call, farCall, 10); Route->NEIGHBOUR_LINK = Info->LINK; Info->Route = Route; Info->LINK->NEIGHBOUR = Route; Info->LINK->LINKPORT = GetPortTableEntryFromPortNum(Route->NEIGHBOUR_PORT); memcpy(Route->NEIGHBOUR_LINK->LINKCALL, Route->NEIGHBOUR_CALL, 7); Route->NEIGHBOUR_LINK = Route->TCPSession->LINK; // Just in case! return NETROMTCPConnect(Route, sockptr); } void NETROMTCPResolve() { struct ROUTE * Route = NEIGHBOURS; int n = MAXNEIGHBOURS; struct addrinfo hints, *res = 0; char PortString[20]; int err; while (n--) { if (Route->TCPAddress) { // try to resolve host sprintf(PortString, "%d", Route->TCPPort); memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever hints.ai_socktype = SOCK_STREAM; getaddrinfo(Route->TCPHost, PortString, &hints, &res); err = WSAGetLastError(); if (res) { Route->TCPAddress->ai_family = res->ai_family; Route->TCPAddress->ai_socktype = res->ai_socktype; Route->TCPAddress->ai_protocol = res->ai_protocol; Route->TCPAddress->ai_addrlen = res->ai_addrlen; memcpy(Route->TCPAddress->ai_addr, res->ai_addr, sizeof(struct sockaddr)); freeaddrinfo(res); } } Route++; } } int NETROMTCPConnect(struct ROUTE * Route, struct ConnectionInfo * sockptr) { int err; u_long param=1; BOOL bcopt=TRUE; SOCKET sock; struct sockaddr_in sinx; int addrlen=sizeof(sinx); char PortString[20]; struct addrinfo * res = Route->TCPAddress; int Port = Route->TCPPort; struct sockaddr_in my_addr; socklen_t len = sizeof(my_addr); struct NRTCPSTRUCT * Info = NRTCPInfo[sockptr->Number]; // Get my ip address and port memset(&my_addr, 0, sizeof(my_addr)); getsockname(sockptr->socket, (struct sockaddr *) &my_addr, &len); sprintf(PortString, "%d", Port); // get host info, make socket, and connect it if (res->ai_family == 0) { // err = WSAGetLastError(); // Debugprintf("Resolve HostName %s Failed - Error %d", Route->TCPHost, err); return FALSE; // Resolve failed } sock = sockptr->socket = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sock == INVALID_SOCKET) { Debugprintf, ("Netrom over TCP Create Socket Failed"); return FALSE; } ioctl(sock, FIONBIO, ¶m); setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char FAR *)&bcopt,4); if (connect(sock, res->ai_addr, (int)res->ai_addrlen) == 0) { // // Connected successful // sockptr->Connected = TRUE; sockptr->LastReceiveTime = time(NULL); return TRUE; } else { err=WSAGetLastError(); if (err == 10035 || err == 115 || err == 36) //EWOULDBLOCK { // Connect in Progress sockptr->Connecting = TRUE; // Get my ip address and port memset(&my_addr, 0, sizeof(my_addr)); getsockname(sockptr->socket, (struct sockaddr *) &my_addr, &len); Route->localport = htons(my_addr.sin_port); Debugprintf("NRTCP Connection in progress %s local port %d", Info->Call, Route->localport); return TRUE; } else { // Connect failed closesocket(sockptr->socket); return FALSE; } } return FALSE; } void NETROMConnectionAccepted(struct ConnectionInfo * sockptr) { // Not sure we can do much here until first message arrives with callsign struct sockaddr_in my_addr; socklen_t len = sizeof(my_addr); // Get my ip address and port memset(&my_addr, 0, sizeof(my_addr)); // getsockname(sockptr->socket, (struct sockaddr *) &my_addr, &len); Debugprintf("INP3 Accept() Local port %d", htons(sockptr->sin.sin_port)); sockptr->Connected = TRUE; sockptr->LastReceiveTime = time(NULL); // Debugprintf("NRTCP Connection Accepted"); } void NETROMConnected(struct ConnectionInfo * sockptr, SOCKET sock, struct NRTCPSTRUCT * Info) { // Connection Complete // Debugprintf("NRTCP Connected"); Debugprintf("NRTCP Connection Complete %s Local port %d", Info->Call, Info->Route->localport); sockptr->Connecting = FALSE; sockptr->Connected = TRUE; sockptr->LastReceiveTime = time(NULL); Info->LINK->L2STATE = 5; if (Info->Route->INP3Node) SendRTTMsg(Info->Route); } int DataSocket_ReadNETROM(struct ConnectionInfo * sockptr, SOCKET sock, struct NRTCPSTRUCT * Info, int portNo) { int len=0, maxlen; struct NRTCPMsg * Msg; struct _L3MESSAGEBUFFER * L3Msg; struct ROUTE * Route; UCHAR axCall[7]; PMESSAGE Buffer; ioctl(sock,FIONREAD,&len); maxlen = InputBufferLen - sockptr->InputLen; if (len > maxlen) len = maxlen; len = recv(sock, &sockptr->InputBuffer[sockptr->InputLen], len, 0); if (len == SOCKET_ERROR || len == 0) { // Failed or closed - clear connection NETROMConnectionLost(sockptr); return 0; } sockptr->LastReceiveTime = time(NULL); sockptr->InputLen += len; // Process data checkLen: // See if we have a whole packet Msg = (struct NRTCPMsg *)&sockptr->InputBuffer[0]; if (Msg->Length > sockptr->InputLen) // if not got whole frame wait return 0; if (Info->Call[0] == 0) { // first packet - do we need to do anything? // This must be an incoming connection as Call is set before calling so need to find route record and set things up. memcpy(Info->Call, Msg->Call, 10); ConvToAX25(Msg->Call, axCall); if (FindNeighbour(axCall, portNo, &Route)) { Info->Route = Route; Route->NEIGHBOUR_LINK = Info->LINK; Route->NEIGHBOUR_PORT = portNo; Info->LINK->NEIGHBOUR = Route; Info->LINK->LINKPORT = GetPortTableEntryFromPortNum(portNo); Route->TCPSession = Info; Info->LINK->L2STATE = 5; memcpy(Route->NEIGHBOUR_LINK->LINKCALL, axCall, 7); Route->localport = htons(sockptr->sin.sin_port); Debugprintf("New NRTCP Connection from %s port %d", Msg->Call, Route->localport); if (Info->Route->INP3Node) SendRTTMsg(Info->Route); } else { Debugprintf("Neighbour %s port %d not found - closing connection", Msg->Call, portNo); closesocket(sockptr->socket); sockptr->SocketActive = FALSE; memset(sockptr, 0, sizeof(struct ConnectionInfo)); Info->Call[0] = 0; return 0; } } if (memcmp(Info->Call, Msg->Call, 10) != 0) { Debugprintf("NRTCP Mismatch - closing connection"); closesocket(sockptr->socket); sockptr->SocketActive = FALSE; memset(sockptr, 0, sizeof(struct ConnectionInfo)); Info->Call[0] = 0; return 0; } // Format as if come from an ax.25 link L3Msg = GetBuff(); if (L3Msg == 0) goto seeifMore; L3Msg->LENGTH = (Msg->Length - 12) + MSGHDDRLEN; L3Msg->Next = 0; L3Msg->Port = portNo; L3Msg->L3PID = NETROM_PID; memcpy(&L3Msg->L3SRCE, Msg->Packet, Msg->Length - 13); // Create a dummy L2 message so we can trace it Buffer = GetBuff(); if (Buffer) { Buffer->CHAIN = 0; Buffer->CTL = 0; Buffer->PORT = portNo; ConvToAX25(Info->Call, Buffer->ORIGIN); ConvToAX25(MYNETROMCALL, Buffer->DEST); memcpy(Buffer->L2DATA, &L3Msg->L3SRCE[0], Msg->Length - 13); Buffer->ORIGIN[6] |= 1; // Set end of calls Buffer->PID = NETROM_PID; Buffer->LENGTH = Msg->Length + 10; time(&Buffer->Timestamp); BPQTRACE(Buffer, FALSE); if(NodeAPISocket) NetromTCPTrace(Buffer, "rcvd"); ReleaseBuffer(Buffer); } Info->Route->NEIGHBOUR_LINK = Info->LINK; // Just in case! NETROMMSG(Info->LINK, L3Msg); seeifMore: sockptr->InputLen -= Msg->Length; if (sockptr->InputLen > 0) { memmove(sockptr->InputBuffer, &sockptr->InputBuffer[Msg->Length], sockptr->InputLen); goto checkLen; } return 0; } VOID TCPNETROMSend(struct ROUTE * Route, struct _L3MESSAGEBUFFER * Frame) { struct NRTCPMsg Msg; unsigned char * Data = (unsigned char *)&Frame->L3SRCE[0]; int DataLen = Frame->LENGTH - (MSGHDDRLEN + 1); // Not including PID int Ret; PMESSAGE Buffer; struct sockaddr_in my_addr; socklen_t len = sizeof(my_addr); struct ConnectionInfo * sockptr; Msg.Length = DataLen + 13; // include PID memcpy(Msg.Call, MYNETROMCALL, 10); Msg.PID = NETROM_PID; memcpy(Msg.Packet, Data, DataLen); if (Route->TCPSession == 0) return; sockptr = Route->TCPSession->sockptr; // Get other port if (strcmp(Route->TCPHost, "0.0.0.0") == 0) { // incoming // Debugprintf("INP3 Remote port %d", htons(sockptr->sin.sin_port)); if (Route->localport != htons(sockptr->sin.sin_port)) { // Route is linked to wrong session. Close it // Route->TCPSession = 0; // Route->NEIGHBOUR_LINK = 0; return; } } else { // outgoing memset(&my_addr, 0, sizeof(my_addr)); getsockname(sockptr->socket, (struct sockaddr *) &my_addr, &len); // Debugprintf("INP3 Local port %d", htons(my_addr.sin_port)); if (Route->localport != htons(my_addr.sin_port)) { // Route is linked to wrong session. Close it // if (sockptr->Connecting == 0) // { // Route->TCPSession = 0; // Route->NEIGHBOUR_LINK = 0; // } return; } } Ret = send(Route->TCPSession->sockptr->socket, (char *)&Msg, DataLen + 13, 0); // Create a dummy L2 message so we can trace it Buffer = GetBuff(); if (Buffer) { Buffer->CHAIN = 0; Buffer->CTL = 0; Buffer->PORT = Route->NEIGHBOUR_PORT; memcpy(Buffer->DEST, Route->NEIGHBOUR_CALL, 7); ConvToAX25(MYNETROMCALL, Buffer->ORIGIN); memcpy(Buffer->L2DATA, &Frame->L3SRCE[0], DataLen); Buffer->ORIGIN[6] |= 1; // Set end of calls Buffer->PID = NETROM_PID; Buffer->LENGTH = DataLen + 15 + MSGHDDRLEN; time(&Buffer->Timestamp); if(NodeAPISocket) NetromTCPTrace(Buffer, "sent"); Buffer->PORT = Route->NEIGHBOUR_PORT | 128; // TX Flag BPQTRACE(Buffer, FALSE); ReleaseBuffer(Buffer); } } void NETROMConnectionLost(struct ConnectionInfo * sockptr) { struct NRTCPSTRUCT * Info = NRTCPInfo[sockptr->Number]; struct ROUTE * Route; closesocket(sockptr->socket); // If there is an attached route (there should be) clear all connections if (Info) { Route = Info->Route; if (Route) { Route->NEIGHBOUR_LINK = 0; Route->TCPSession = 0; Route->localport = 0; } Info->Call[0] = 0; Info->LINK->L2STATE = 0; } sockptr->SocketActive = FALSE; memset(sockptr, 0, sizeof(struct ConnectionInfo)); } void NETROMCloseTCP(struct ROUTE * Route) { if (Route->TCPSession) { struct ConnectionInfo * sockptr = Route->TCPSession->sockptr; NETROMConnectionLost(sockptr); } else { Route->NEIGHBOUR_LINK = 0; } }