From dbb7fd7ff47ac5ecf7e2e23fc762a8fc6446499d Mon Sep 17 00:00:00 2001 From: Chris Tallon Date: Tue, 15 Oct 2019 13:58:44 +0100 Subject: [PATCH] IPv6 support for discovery protocol --- Makefile | 4 +- dsock6.c | 229 ++++++++++++++++++++++++++++++++++++++++++++++++++ dsock6.h | 61 ++++++++++++++ mvpserver.c | 16 +++- mvpserver.h | 2 + udp6replier.c | 127 ++++++++++++++++++++++++++++ udp6replier.h | 44 ++++++++++ udpreplier.c | 14 +-- 8 files changed, 486 insertions(+), 11 deletions(-) create mode 100644 dsock6.c create mode 100644 dsock6.h create mode 100644 udp6replier.c create mode 100644 udp6replier.h diff --git a/Makefile b/Makefile index b6a0d99..26b8170 100644 --- a/Makefile +++ b/Makefile @@ -64,8 +64,8 @@ DEFINES += -DVOMPSERVER OBJS = $(PLUGIN).o # VOMP-INSERT -OBJS += dsock.o mvpserver.o udpreplier.o bootpd.o tftpd.o i18n.o vompclient.o tcp.o \ - ringbuffer.o mvprelay.o vompclientrrproc.o \ +OBJS += dsock.o dsock6.o mvpserver.o udpreplier.o udp6replier.o bootpd.o tftpd.o i18n.o \ + vompclient.o tcp.o ringbuffer.o mvprelay.o vompclientrrproc.o \ config.o log.o thread.o tftpclient.o \ media.o responsepacket.o \ mediafile.o mediaplayer.o servermediafile.o serialize.o medialauncher.o \ diff --git a/dsock6.c b/dsock6.c new file mode 100644 index 0000000..d7a3692 --- /dev/null +++ b/dsock6.c @@ -0,0 +1,229 @@ +/* + Copyright 2019 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dsock6.h" + +DatagramSocket6::DatagramSocket6() +{ + addrlen = sizeof(struct sockaddr_in6); + log = Log::getInstance(); + initted = 0; +} + +DatagramSocket6::~DatagramSocket6() +{ + shutdown(); +} + +bool DatagramSocket6::init(USHORT port) +{ + if ((socketnum = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) == -1) + { + log->log("UDP6", Log::CRIT, "Socket error"); + return false; + } + + char* one = (char*)1; + if (setsockopt(socketnum, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) + { + log->log("UDP6", Log::CRIT, "Reuse addr fail"); + return false; + } + + struct sockaddr_in6 saddr; + memset(&saddr, 0, sizeof(saddr)); + saddr.sin6_family = AF_INET6; + saddr.sin6_port = htons(port); + saddr.sin6_addr = in6addr_any; + + if (bind(socketnum, (struct sockaddr *)&saddr, addrlen) == -1) + { + log->log("UDP6", Log::CRIT, "Bind error %u", port); + close(socketnum); + return false; + } + + struct ifaddrs* ifas; + if(getifaddrs(&ifas)) + { + log->log("UDP6", Log::CRIT, "getifaddrs error"); + close(socketnum); + return false; + } + + struct if_nameindex* ifs = if_nameindex(); + std::vector mcastIndexes; + + struct ifaddrs* ifaNext = ifas; + for(int i = 0; ifaNext; i++) + { + if ((ifaNext->ifa_flags & IFF_MULTICAST) && (ifaNext->ifa_addr->sa_family == AF_INET6)) + { + for(int i = 0; ifs[i].if_index > 0; i++) + { + if (!strcmp(ifaNext->ifa_name, ifs[i].if_name)) + { + mcastIndexes.push_back(ifs[i].if_index); + break; + } + } + } + ifaNext = ifaNext->ifa_next; + } + + freeifaddrs(ifas); + if_freenameindex(ifs); + + + std::sort(mcastIndexes.begin(), mcastIndexes.end()); + auto last = std::unique(mcastIndexes.begin(), mcastIndexes.end()); + mcastIndexes.erase(last, mcastIndexes.end()); + + + for(auto mif : mcastIndexes) + { + //log->log("UDP6", Log::DEBUG, "To listen on index %u", mif); + + struct ipv6_mreq mGroup; + inet_pton(AF_INET6, "ff15:766f:6d70:2064:6973:636f:7665:7279", &mGroup.ipv6mr_multiaddr); + mGroup.ipv6mr_interface = mif; + + if (!setsockopt(socketnum, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mGroup, sizeof(mGroup))) + log->log("UDP6", Log::DEBUG, "Listening on IF %u", mif); + else + log->log("UDP6", Log::ERR, "Cannot listen on IF %u", mif); + } + + + FD_ZERO(&readfds); + FD_SET(socketnum, &readfds); + tv.tv_sec = 0; + tv.tv_usec = 0; + + initted = 1; + return true; +} + +void DatagramSocket6::shutdown() +{ + if (initted) close(socketnum); + initted = 0; +} + +unsigned char DatagramSocket6::waitforMessage(unsigned char how) +{ + if (!initted) return 0; + + /* how = 0 - block + how = 1 - start new wait + how = 2 - continue wait + */ + + struct timeval* passToSelect = NULL; + + + if (how == 0) + { + passToSelect = NULL; + } + else if (how == 1) + { + tv.tv_sec = 1; + tv.tv_usec = 100000; + passToSelect = &tv; + } + else if (how == 2) + { + if ((tv.tv_sec == 0) && (tv.tv_usec == 0)) // protection in case timer = 0 + { + tv.tv_sec = 1; + tv.tv_usec = 100000; + } + passToSelect = &tv; + } + FD_ZERO(&readfds); + FD_SET(socketnum, &readfds); + + if (select(socketnum + 1, &readfds, NULL, NULL, passToSelect) <= 0) return 1; + + struct sockaddr_in6 theirAddr; + if ((mlength = recvfrom(socketnum, buf, MAXBUFLEN, 0, (struct sockaddr *)&theirAddr, &addrlen)) == -1) + { + log->log("UDP6", Log::DEBUG, "recvfrom error"); + return 0; + } + else + { + memset(&buf[mlength], 0, MAXBUFLEN - mlength); + inet_ntop(AF_INET6, &theirAddr.sin6_addr, fromIPA, 40); + fromPort = ntohs(theirAddr.sin6_port); + log->log("UDP", Log::DEBUG, "%s:%i received length %i", fromIPA, fromPort, mlength); + return 2; + } + + /* Return 0, failure + Return 1, nothing happened, timer expired + Return 2, packet arrived (timer not expired) + */ +} + +void DatagramSocket6::send(const char *ipa, USHORT port, char *message, int length) +{ + int sentLength = 0; + + struct sockaddr_in6 theirAddr; + memset(&theirAddr, 0, sizeof(struct sockaddr_in6)); + + theirAddr.sin6_family = AF_INET6; + theirAddr.sin6_port = htons(port); + inet_pton(AF_INET6, ipa, &theirAddr.sin6_addr); + + sentLength = sendto(socketnum, message, length, 0, (struct sockaddr *)&theirAddr, addrlen); + if (sentLength == length) + { + log->log("UDP", Log::DEBUG, "%s:%u sent length %i", ipa, port, length); + return; + } + + log->log("UDP", Log::DEBUG, "%s:%u send failed %i", ipa, port, length); + + sentLength = sendto(socketnum, message, length, 0, (struct sockaddr *)&theirAddr, addrlen); + if (sentLength == length) + { + log->log("UDP", Log::DEBUG, "%s:%u sent length %i 2nd try", ipa, port, length); + return; + } + + log->log("UDP", Log::DEBUG, "%s:%u send failed %i 2nd try", ipa, port, length); +} diff --git a/dsock6.h b/dsock6.h new file mode 100644 index 0000000..190b5f6 --- /dev/null +++ b/dsock6.h @@ -0,0 +1,61 @@ +/* + Copyright 2019 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 DSOCK6_H +#define DSOCK6_H + +#include +#include + +#include "defines.h" +#include "log.h" + +#define MAXBUFLEN 2000 + +class DatagramSocket6 +{ + public: + DatagramSocket6(); + ~DatagramSocket6(); + bool init(USHORT port); + void shutdown(); + unsigned char waitforMessage(unsigned char); // int =0-block =1-new wait =2-continue wait + void send(const char *, USHORT, char *, int); // send wants: IP Address ddn style, port, + // data, length of data + + char* getData() { return buf; } // returns a pointer to the data + char* getFromIPA() { return fromIPA; } // returns a pointer to from IP address + USHORT getFromPort() const { return fromPort; } // returns the sender port number + int getDataLength() const { return mlength; } // returns data length + + private: + Log* log; + int initted; + int socketnum; // Socket descriptor + + socklen_t addrlen; // length of sockaddr struct + char buf[MAXBUFLEN]; // main data buffer + char fromIPA[40]; // from string (ip address) + USHORT fromPort; // which port user sent on + int mlength; // length of message + struct timeval tv; + fd_set readfds; +}; + +#endif diff --git a/mvpserver.c b/mvpserver.c index 15890fb..4ca1b99 100644 --- a/mvpserver.c +++ b/mvpserver.c @@ -1,5 +1,5 @@ /* - Copyright 2004-2005 Chris Tallon + Copyright 2004-2019 Chris Tallon This file is part of VOMP. @@ -14,8 +14,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with VOMP; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + along with VOMP. If not, see . */ #include "mvpserver.h" @@ -25,6 +24,8 @@ extern pthread_mutex_t threadClientMutex; +#define UDP6PORT 51056 + MVPServer::MVPServer() { // MH in case anbody has a better position :-) @@ -51,6 +52,7 @@ int MVPServer::stop() close(listeningSocket); udpr.shutdown(); + udpr6.shutdown(); bootpd.shutdown(); tftpd.shutdown(); mvprelay.shutdown(); @@ -177,6 +179,7 @@ int MVPServer::run(char* tconfigDir) if (fail) tcpServerPort = 3024; int udpSuccess = udpr.run(udpport, serverName, tcpServerPort); + int udp6Success = udpr6.run(UDP6PORT, serverName, tcpServerPort); delete[] serverName; @@ -187,6 +190,13 @@ int MVPServer::run(char* tconfigDir) return 0; } + if (!udp6Success) + { + log.log("Main", Log::CRIT, "Could not start UDP6 replier"); + stop(); + return 0; + } + // Read config and start bootp and tftp as appropriate char* configString; diff --git a/mvpserver.h b/mvpserver.h index ed29c65..d19a684 100644 --- a/mvpserver.h +++ b/mvpserver.h @@ -27,6 +27,7 @@ #include "defines.h" #include "udpreplier.h" +#include "udp6replier.h" #include "mvprelay.h" #include "bootpd.h" #include "tftpd.h" @@ -49,6 +50,7 @@ class MVPServer : public Thread Log log; Config config; UDPReplier udpr; + UDP6Replier udpr6; Bootpd bootpd; Tftpd tftpd; MVPRelay mvprelay; diff --git a/udp6replier.c b/udp6replier.c new file mode 100644 index 0000000..b20c427 --- /dev/null +++ b/udp6replier.c @@ -0,0 +1,127 @@ +/* + Copyright 2019 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 +#include +#include + +#include "vompclientrrproc.h" + +#include "udp6replier.h" + +UDP6Replier::UDP6Replier() +{ + message = NULL; + messageLen = 0; +} + +UDP6Replier::~UDP6Replier() +{ + shutdown(); +} + +int UDP6Replier::shutdown() +{ + if (threadIsActive()) threadCancel(); + + if (message) delete[] message; + message = NULL; + return 1; +} + +int UDP6Replier::run(USHORT port, char* serverName, USHORT serverPort) +{ + if (threadIsActive()) return 1; + + /* + VOMP Discovery Protocol V1 + + Client transmits: "VDP-0001\0<6-byte MAC>"... + ... for IPv4: broadcasts on ports 51051-51055 + ... for IPv6: multicasts to ff15:766f:6d70:2064:6973:636f:7665:7279 port 51056 + + Server responds: + + Field 1 p0: 9 bytes "VDP-0002\0" + + Field 2 p9, 1 byte: + + 0 = no IP specified + 4 = first 4 bytes of field 3 are IPv4 address of server + 6 = field 3 16 bytes are IPv6 address of server + + Field 3, p10, 16 bytes: + As described above. If field 2 is 0, this should be all zeros. If this is an IPv4 address, remaining bytes should be zeros. + + Field 4 p26, 2 bytes: + Port number of server + + Field 5 p28, 4 bytes: + VOMP protocol version (defined in vdr.cc) + + Field 6 p32, variable length + String of server name, null terminated + */ + + messageLen = strlen(serverName) + 33; + message = new char[messageLen]; + memset(message, 0, messageLen); + // by zeroing the packet, this sets no ip address return information + + strcpy(message, "VDP-0002"); + + USHORT temp = htons(serverPort); + memcpy(&message[26], &temp, 2); + + ULONG temp2 = htonl(VompClientRRProc::getProtocolVersionMin()); + memcpy(&message[28], &temp2, 4); + + strcpy(&message[32], serverName); + // Fix Me add also the maximum version somewhere + if (!ds.init(port)) + { + shutdown(); + return 0; + } + + if (!threadStart()) + { + shutdown(); + return 0; + } + + Log::getInstance()->log("UDP6Replier", Log::DEBUG, "UDP replier started"); + return 1; +} + +void UDP6Replier::threadMethod() +{ + int retval; + while(1) + { + retval = ds.waitforMessage(0); + if (retval == 1) continue; + + if (!strncmp(ds.getData(), "VDP-0001", 8)) + { + Log::getInstance()->log("UDP6Replier", Log::DEBUG, "UDP6 request from %s", ds.getFromIPA()); + ds.send(ds.getFromIPA(), ds.getFromPort(), message, messageLen); + } + } +} diff --git a/udp6replier.h b/udp6replier.h new file mode 100644 index 0000000..09da833 --- /dev/null +++ b/udp6replier.h @@ -0,0 +1,44 @@ +/* + Copyright 2019 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 UDP6REPLIER_H +#define UDP6REPLIER_H + +#include "log.h" +#include "dsock6.h" +#include "thread.h" + +class UDP6Replier : public Thread +{ + public: + UDP6Replier(); + virtual ~UDP6Replier(); + + int run(USHORT udpPort, char* serverName, USHORT serverPort); + int shutdown(); + + private: + void threadMethod(); + + DatagramSocket6 ds; + char* message; + int messageLen; +}; + +#endif diff --git a/udpreplier.c b/udpreplier.c index 88f83e4..b5c404c 100644 --- a/udpreplier.c +++ b/udpreplier.c @@ -49,27 +49,29 @@ int UDPReplier::run(USHORT port, char* serverName, USHORT serverPort) /* VOMP Discovery Protocol V1 - Client transmits: "VDP-0001\0<6-byte MAC>" on ports 51051-51055 + Client transmits: "VDP-0001\0<6-byte MAC>"... + ... for IPv4: broadcasts on ports 51051-51055 + ... for IPv6: multicasts to ff15:766f:6d70:2064:6973:636f:7665:7279 port 51051 Server responds: Field 1 p0: 9 bytes "VDP-0002\0" - + Field 2 p9, 1 byte: 0 = no IP specified - 4 = first 4 bytes of field 2 are IPv4 address of server - 6 = field 2 16 bytes are IPv6 address of server + 4 = first 4 bytes of field 3 are IPv4 address of server + 6 = field 3 16 bytes are IPv6 address of server Field 3, p10, 16 bytes: - As described above. If field 1 is 0, this should be all zeros. If this is an IPv4 address, remaining bytes should be zeros. + As described above. If field 2 is 0, this should be all zeros. If this is an IPv4 address, remaining bytes should be zeros. Field 4 p26, 2 bytes: Port number of server Field 5 p28, 4 bytes: VOMP protocol version (defined in vdr.cc) - + Field 6 p32, variable length String of server name, null terminated */ -- 2.39.5