From c33a1efd820a9184385c93fd04802bb94a0187e7 Mon Sep 17 00:00:00 2001 From: Chris Tallon Date: Thu, 2 Apr 2020 17:09:02 +0100 Subject: [PATCH] Replace TCP. Use new IP API only, getMAC works with if!=eth0 --- objects.mk | 2 +- tcp.cc | 291 +++++++++++++++++++++++++++++++++++++++++++++++++++++ tcp.h | 56 +++++++++++ vdr.cc | 48 ++++----- vdr.h | 7 +- 5 files changed, 372 insertions(+), 32 deletions(-) create mode 100644 tcp.cc create mode 100644 tcp.h diff --git a/objects.mk b/objects.mk index 35be1e2..ee66ea4 100644 --- a/objects.mk +++ b/objects.mk @@ -1,4 +1,4 @@ -OBJ_COMMON = command.o thread.o timers.o i18n.o tcpold.o udp4.o udp6.o vdpc.o \ +OBJ_COMMON = command.o thread.o timers.o i18n.o udp4.o udp6.o vdpc.o tcp.o \ message.o messagequeue.o wol.o audio.o video.o log.o \ vdr.o recman.o recording.o recinfo.o channel.o rectimer.o event.o \ directory.o mark.o option.o vfeed.o afeed.o \ diff --git a/tcp.cc b/tcp.cc new file mode 100644 index 0000000..7ab755b --- /dev/null +++ b/tcp.cc @@ -0,0 +1,291 @@ +/* + Copyright 2020 Chris Tallon + + This file is part of VOMP. + + VOMP 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. + + VOMP 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 VOMP. If not, see . +*/ + +#include // memcmp +#include // pipe2 +#include // pipe2-O_NONBLOCK fcntl +#include // socket connect getaddrinfo +#include // socket connect getaddrinfo +#include // memset +#include // getaddrinfo +#include // select +#include // errno var +#include // getifaddrs +#include // for getMAC +#include // for getMAC + +#include "log.h" + +#include "tcp.h" + +TCP::~TCP() +{ + shutdown(); + + if (abortPipe[0] != -1) + { + close(abortPipe[0]); + close(abortPipe[1]); + abortPipe[0] = -1; + abortPipe[1] = -1; + } +} + +bool TCP::init() +{ + logger = Log::getInstance(); + + if (pipe2(abortPipe, O_NONBLOCK) == -1) + { + logger->log("TCP", Log::CRIT, "pipe2 error"); + return false; + } + + return true; +} + +bool TCP::connect(const std::string& ip, USHORT port) +{ + if (connected) return false; + + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; + + struct addrinfo* aip; + char portString[10]; + std::snprintf(portString, 10, "%u", port); + int gaiResult = getaddrinfo(ip.c_str(), portString, &hints, &aip); + + if ((gaiResult != 0) || !aip) + { + logger->log("TCP", Log::CRIT, "getaddrinfo error"); + return false; + } + + sockfd = socket(aip->ai_family, SOCK_STREAM, 0); + if (sockfd == -1) { logger->log("TCP", Log::CRIT, "socket error"); return false; } + + fcntl(sockfd, F_SETFL, O_NONBLOCK); + + errno = 0; + // There should only be one aip result.. + int connectResult = ::connect(sockfd, aip->ai_addr, aip->ai_addrlen); + + if (connectResult == 0) // success + { + freeaddrinfo(aip); + connected = true; + return true; + } + + if (errno != EINPROGRESS) + { + close(sockfd); + sockfd = -1; + logger->log("TCP", Log::CRIT, "connect error"); + return false; + } + + // Wait for connect to complete + fd_set writefds; + struct timeval tv; + FD_ZERO(&writefds); + FD_SET(abortPipe[0], &writefds); + FD_SET(sockfd, &writefds); + + tv.tv_sec = 5; // Allow 5s for a connect + tv.tv_usec = 0; + + int selectResult = select((sockfd > abortPipe[0] ? sockfd : abortPipe[0]) + 1, NULL, &writefds, NULL, &tv); + + if ((selectResult == 1) || FD_ISSET(sockfd, &writefds)) + { + freeaddrinfo(aip); + logger->log("TCP", Log::INFO, "connected"); + connected = true; + return true; + } + else + { + close(sockfd); + sockfd = -1; + logger->log("TCP", Log::CRIT, "connect/select error"); + return false; + } +} + +void TCP::shutdown() +{ + if (!connected) return; + connected = false; + close(sockfd); + sockfd = -1; +} + +bool TCP::status() +{ + return connected; +} + +bool TCP::write(void* src, ULONG numBytes) +{ + if (!connected) return false; + + std::lock_guard lg(writeMutex); + int result = send(sockfd, src, numBytes, 0); // FIXME does send return < numBytes? Might need loop + if (result < 0) return false; + if (static_cast(result) != numBytes) return false; + + return true; +} + +bool TCP::read(void* dst, ULONG numBytes) +{ + if (!connected) return false; + + fd_set readfds; + struct timeval tv; + + ULONG totalReceived = 0; + int abortCount = 0; + UCHAR* pointer = static_cast(dst); + + do + { + if (abortCount == 5) + { + logger->log("TCP", Log::ERR, "abortCount = 5"); + return false; + } + + FD_ZERO(&readfds); + FD_SET(abortPipe[0], &readfds); + FD_SET(sockfd, &readfds); + + tv.tv_sec = 2; + tv.tv_usec = 0; + + int selectResult = select((sockfd > abortPipe[0] ? sockfd : abortPipe[0]) + 1, &readfds, NULL, NULL, &tv); + + if (selectResult == -1) { shutdown(); return false; } + if (selectResult == 0) return false; + if (FD_ISSET(abortPipe[0], &readfds)) return false; + + int recvResult = recv(sockfd, pointer, numBytes - totalReceived, 0); + if (recvResult == -1) { shutdown(); return false; } + totalReceived += recvResult; + pointer += recvResult; + + } while (totalReceived < numBytes); + + return true; +} + +MACAddress TCP::getMAC() +{ + MACAddress macerror{ 00, 00, 00, 00, 00, 00 }; + + if (!connected) return macerror; + + /* + struct sockaddr - man 2 bind + struct sockaddr_in - man 7 ip + struct sockaddr_in6 - man 7 ipv6 + struct sockaddr_ll - man 7 packet + */ + + // Measured sizeof(struct sockaddr_in) = 16, and in6 = 28 + UINT buflen = 50; + unsigned char gsnBuffer[buflen]; + struct sockaddr* sap = reinterpret_cast(gsnBuffer); + if (getsockname(sockfd, sap, &buflen) == -1) return macerror; + + uint32_t my4AddrU32; + unsigned char my6Addr[16]; + + if (sap->sa_family == AF_INET) + { + struct sockaddr_in* sap4 = reinterpret_cast(gsnBuffer); + my4AddrU32 = sap4->sin_addr.s_addr; + } + else if (sap->sa_family == AF_INET6) + { + struct sockaddr_in6* sap6 = reinterpret_cast(gsnBuffer); + memcpy(my6Addr, sap6->sin6_addr.s6_addr, 16); + } + else + { + return macerror; + } + + struct ifaddrs* ifAddrs; + if (getifaddrs(&ifAddrs) == -1) return macerror; + + char* ifName = NULL; + + for(struct ifaddrs* ifa = ifAddrs; ifa != NULL; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr->sa_family != sap->sa_family) continue; + + if (sap->sa_family == AF_INET) + { + struct sockaddr_in* test = reinterpret_cast(ifa->ifa_addr); + if (test->sin_addr.s_addr == my4AddrU32) + { + ifName = ifa->ifa_name; + break; + } + } + else if (sap->sa_family == AF_INET6) + { + struct sockaddr_in6* test = reinterpret_cast(ifa->ifa_addr); + if (!memcmp(my6Addr, test->sin6_addr.s6_addr, 16)) + { + ifName = ifa->ifa_name; + break; + } + } + } + + if (!ifName) + { + freeifaddrs(ifAddrs); + return macerror; + } + + // Walk the list again to get the MAC for ifName using family == AF_PACKET + MACAddress toReturn = macerror; + + for(struct ifaddrs* ifa = ifAddrs; ifa != NULL; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr->sa_family != AF_PACKET) continue; + if (strcmp(ifName, ifa->ifa_name)) continue; + struct sockaddr_ll* sall = reinterpret_cast(ifa->ifa_addr); + if (sall->sll_halen != 6) continue; + + memcpy(&toReturn, sall->sll_addr, 6); + break; + } + + freeifaddrs(ifAddrs); + return toReturn; +} diff --git a/tcp.h b/tcp.h new file mode 100644 index 0000000..8a26df5 --- /dev/null +++ b/tcp.h @@ -0,0 +1,56 @@ +/* + Copyright 2020 Chris Tallon + + This file is part of VOMP. + + VOMP 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. + + VOMP 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 VOMP. If not, see . +*/ + +#ifndef TCP_H +#define TCP_H + +#include + +#include "defines.h" + +struct MACAddress +{ + UCHAR mac[6]; +}; + +class Log; + +class TCP +{ + public: + ~TCP(); + bool init(); // Must call this first, once on any new TCP object + void shutdown(); // Optional. Closes connection. Can call connect() next + + bool connect(const std::string& ip, USHORT port); + bool read(void* dest, ULONG numBytes); + bool write(void* src, ULONG numBytes); + bool status(); + + MACAddress getMAC(); + + private: + Log* logger; + int sockfd{-1}; + int abortPipe[2]{-1, -1}; + bool connected{}; + std::mutex writeMutex; +}; + +#endif diff --git a/vdr.cc b/vdr.cc index d091482..fe9969b 100644 --- a/vdr.cc +++ b/vdr.cc @@ -1,5 +1,5 @@ /* - Copyright 2004-2019 Chris Tallon + Copyright 2004-2020 Chris Tallon This file is part of VOMP. @@ -20,8 +20,6 @@ #include "vdr.h" #include "recman.h" -#include "tcpold.h" -#include "log.h" #include "recinfo.h" #include "channel.h" #include "event.h" @@ -140,6 +138,7 @@ int VDR::init() if (initted) return 0; initted = 1; logger = Log::getInstance(); + tcp.init(); return 1; } @@ -166,9 +165,8 @@ int VDR::connect() maxChannelNumber = 0; channelNumberWidth = 1; - if (tcpold) delete tcpold; - tcpold = new TCPOld(); - if (tcpold->connectTo(serverIP, serverPort)) + tcp.shutdown(); + if (tcp.connect(serverIP, serverPort)) { connected = true; threadStart(); @@ -183,15 +181,13 @@ int VDR::connect() void VDR::disconnect() { threadCancel(); - if (tcpold) delete tcpold; - tcpold = NULL; connected = false; logger->log("VDR", Log::DEBUG, "Disconnect"); } void VDR::setReceiveWindow(size_t size) { - if (connected && size) tcpold->setReceiveWindow(size); + //if (connected && size) tcpold->setReceiveWindow(size); } /////////////////////////////////////////////////////// @@ -216,18 +212,18 @@ void VDR::threadMethod() ULONG timeNow = 0; ULONG lastKAsent = 0; ULONG lastKArecv = time(NULL); - int readSuccess; + bool readSuccess; while(1) { timeNow = time(NULL); - readSuccess = tcpold->readData(&channelID, sizeof(ULONG)); // 2s timeout atm + readSuccess = tcp.read(&channelID, sizeof(ULONG)); // 2s timeout atm if (!readSuccess) { //logger->log("VDR", Log::DEBUG, "Net read timeout"); - if (!tcpold->isConnected()) { connectionDied(); return; } // return to stop this thread + if (!tcp.status()) { connectionDied(); return; } // return to stop this thread } // Error or timeout. @@ -264,9 +260,9 @@ void VDR::threadMethod() if (channelID == CHANNEL_REQUEST_RESPONSE) { - if (!tcpold->readData(&requestID, sizeof(ULONG))) break; + if (!tcp.read(&requestID, sizeof(ULONG))) break; requestID = ntohl(requestID); - if (!tcpold->readData(&userDataLength, sizeof(ULONG))) break; + if (!tcp.read(&userDataLength, sizeof(ULONG))) break; userDataLength = ntohl(userDataLength); if (userDataLength > 5000000) break; // how big can these packets get? userData = NULL; @@ -274,7 +270,7 @@ void VDR::threadMethod() { userData = malloc(userDataLength); if (!userData) break; - if (!tcpold->readData(userData, userDataLength)) break; + if (!tcp.read(userData, userDataLength)) break; } vresp = new VDR_ResponsePacket(); @@ -290,20 +286,20 @@ void VDR::threadMethod() } else if (channelID == CHANNEL_STREAM || channelID == CHANNEL_TVMEDIA) { - if (!tcpold->readData(&streamID, sizeof(ULONG))) break; + if (!tcp.read(&streamID, sizeof(ULONG))) break; streamID = ntohl(streamID); - if (!tcpold->readData(&flag, sizeof(ULONG))) break; + if (!tcp.read(&flag, sizeof(ULONG))) break; flag = ntohl(flag); - if (!tcpold->readData(&userDataLength, sizeof(ULONG))) break; + if (!tcp.read(&userDataLength, sizeof(ULONG))) break; userDataLength = ntohl(userDataLength); userData = NULL; if (userDataLength > 0) { userData = malloc(userDataLength); if (!userData) break; - if (!tcpold->readData(userData, userDataLength)) break; + if (!tcp.read(userData, userDataLength)) break; } vresp = new VDR_ResponsePacket(); @@ -320,7 +316,7 @@ void VDR::threadMethod() else if (channelID == CHANNEL_KEEPALIVE) { ULONG KAreply = 0; - if (!tcpold->readData(&KAreply, sizeof(ULONG))) break; + if (!tcp.read(&KAreply, sizeof(ULONG))) break; KAreply = ntohl(KAreply); if (KAreply == lastKAsent) // successful KA response { @@ -460,7 +456,7 @@ VDR_ResponsePacket* VDR::RequestResponse(VDR_RequestPacket* vrp) edMutex.lock(); - if (static_cast(tcpold->sendData(vrp->getPtr(), vrp->getLen())) != vrp->getLen()) + if (!tcp.write(vrp->getPtr(), vrp->getLen())) { edMutex.unlock(); edUnregister(&vdrpr); @@ -495,8 +491,7 @@ bool VDR::sendKA(ULONG timeStamp) buffer[pos++]=(ul>>16)&0xff; buffer[pos++]=(ul>>8)&0xff; buffer[pos++]=ul &0xff; - if (static_cast(tcpold->sendData(buffer, 8)) != 8) return false; - return true; + return tcp.write(buffer, 8); } ///////////////////////////////////////////////////////////////////////////// @@ -557,9 +552,8 @@ int VDR::doLogin(unsigned int* v_server_min, unsigned int* v_server_max, unsigne VDR_RequestPacket vrp; if (!vrp.init(VDR_LOGIN, true, 6)) return 0; - UCHAR mactemp[6]; - tcpold->getMAC(mactemp); - if (!vrp.copyin(mactemp, 6)) return 0; + MACAddress myMAC = tcp.getMAC(); + if (!vrp.copyin(reinterpret_cast(&myMAC), 6)) return 0; VDR_ResponsePacket* vresp = RequestResponse(&vrp); if (vresp->noResponse()) { delete vresp; return 0; } @@ -661,7 +655,7 @@ bool VDR::LogExtern(const char* logString) strcpy(&buffer[8], logString); - if (tcpold->sendData(buffer, packetLength) != packetLength) + if (!tcp.write(buffer, packetLength)) { connected = false; // stop the rest of the connection delete [] buffer; diff --git a/vdr.h b/vdr.h index af2e254..c00ea11 100644 --- a/vdr.h +++ b/vdr.h @@ -1,5 +1,5 @@ /* - Copyright 2004-2019 Chris Tallon + Copyright 2004-2020 Chris Tallon This file is part of VOMP. @@ -42,9 +42,8 @@ #include "i18n.h" #include "log.h" #include "command.h" +#include "tcp.h" -class TCPOld; -class Log; class RecInfo; class Event; class Channel; @@ -233,7 +232,7 @@ public ExternLogger Log* logger; int initted{}; int findingServer{}; - TCPOld* tcpold{}; + TCP tcp; char serverIP[40]; USHORT serverPort; bool connected{}; -- 2.39.5