]> git.vomp.tv Git - vompclient.git/blob - tcp.cc
Replace TCP. Use new IP API only, getMAC works with if!=eth0
[vompclient.git] / tcp.cc
1 /*
2     Copyright 2020 Chris Tallon
3
4     This file is part of VOMP.
5
6     VOMP is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     VOMP is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with VOMP.  If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #include <string.h> // memcmp
21 #include <unistd.h> // pipe2
22 #include <fcntl.h> // pipe2-O_NONBLOCK fcntl
23 #include <sys/types.h>  // socket connect getaddrinfo
24 #include <sys/socket.h> // socket connect getaddrinfo
25 #include <string.h> // memset
26 #include <netdb.h> // getaddrinfo
27 #include <sys/select.h> // select
28 #include <errno.h> // errno var
29 #include <ifaddrs.h> // getifaddrs
30 #include <linux/if_packet.h> // for getMAC
31 #include <net/ethernet.h> // for getMAC
32
33 #include "log.h"
34
35 #include "tcp.h"
36
37 TCP::~TCP()
38 {
39   shutdown();
40   
41   if (abortPipe[0] != -1)
42   {
43     close(abortPipe[0]);
44     close(abortPipe[1]);
45     abortPipe[0] = -1;
46     abortPipe[1] = -1;
47   }
48 }
49
50 bool TCP::init()
51 {
52   logger = Log::getInstance();
53   
54   if (pipe2(abortPipe, O_NONBLOCK) == -1)
55   {
56     logger->log("TCP", Log::CRIT, "pipe2 error");
57     return false;
58   }
59   
60   return true;
61 }
62
63 bool TCP::connect(const std::string& ip, USHORT port)
64 {
65   if (connected) return false;
66
67   struct addrinfo hints;
68   memset(&hints, 0, sizeof(struct addrinfo));
69   hints.ai_family = AF_UNSPEC;
70   hints.ai_socktype = SOCK_STREAM;
71   hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
72
73   struct addrinfo* aip;
74   char portString[10];
75   std::snprintf(portString, 10, "%u", port);
76   int gaiResult = getaddrinfo(ip.c_str(), portString, &hints, &aip);
77
78   if ((gaiResult != 0) || !aip)
79   {
80     logger->log("TCP", Log::CRIT, "getaddrinfo error");
81     return false;
82   }
83
84   sockfd = socket(aip->ai_family, SOCK_STREAM, 0);
85   if (sockfd == -1) { logger->log("TCP", Log::CRIT, "socket error"); return false; }
86
87   fcntl(sockfd, F_SETFL, O_NONBLOCK);
88
89   errno = 0;
90   // There should only be one aip result..
91   int connectResult = ::connect(sockfd, aip->ai_addr, aip->ai_addrlen);
92
93   if (connectResult == 0) // success
94   {
95     freeaddrinfo(aip);
96     connected = true;
97     return true;
98   }
99
100   if (errno != EINPROGRESS)
101   {
102     close(sockfd);
103     sockfd = -1;
104     logger->log("TCP", Log::CRIT, "connect error");
105     return false;
106   }
107
108   // Wait for connect to complete
109   fd_set writefds;
110   struct timeval tv;
111   FD_ZERO(&writefds);
112   FD_SET(abortPipe[0], &writefds);
113   FD_SET(sockfd, &writefds);
114
115   tv.tv_sec = 5; // Allow 5s for a connect
116   tv.tv_usec = 0;
117
118   int selectResult = select((sockfd > abortPipe[0] ? sockfd : abortPipe[0]) + 1, NULL, &writefds, NULL, &tv);
119
120   if ((selectResult == 1) || FD_ISSET(sockfd, &writefds))
121   {
122     freeaddrinfo(aip);
123     logger->log("TCP", Log::INFO, "connected");
124     connected = true;
125     return true;
126   }
127   else
128   {
129     close(sockfd);
130     sockfd = -1;
131     logger->log("TCP", Log::CRIT, "connect/select error");
132     return false;
133   }
134 }
135
136 void TCP::shutdown()
137 {
138   if (!connected) return;
139   connected = false;
140   close(sockfd);
141   sockfd = -1;
142 }
143
144 bool TCP::status()
145 {
146   return connected;
147 }
148
149 bool TCP::write(void* src, ULONG numBytes)
150 {
151   if (!connected) return false;
152
153   std::lock_guard<std::mutex> lg(writeMutex);
154   int result = send(sockfd, src, numBytes, 0);  // FIXME does send return < numBytes? Might need loop
155   if (result < 0) return false;
156   if (static_cast<ULONG>(result) != numBytes) return false;
157
158   return true;
159 }
160
161 bool TCP::read(void* dst, ULONG numBytes)
162 {
163   if (!connected) return false;
164
165   fd_set readfds;
166   struct timeval tv;
167
168   ULONG totalReceived = 0;
169   int abortCount = 0;
170   UCHAR* pointer = static_cast<UCHAR*>(dst);
171   
172   do
173   {
174     if (abortCount == 5)
175     {
176       logger->log("TCP", Log::ERR, "abortCount = 5");
177       return false;
178     }
179     
180     FD_ZERO(&readfds);
181     FD_SET(abortPipe[0], &readfds);
182     FD_SET(sockfd, &readfds);
183
184     tv.tv_sec = 2;
185     tv.tv_usec = 0;
186
187     int selectResult = select((sockfd > abortPipe[0] ? sockfd : abortPipe[0]) + 1, &readfds, NULL, NULL, &tv);
188
189     if (selectResult == -1) { shutdown(); return false; }
190     if (selectResult == 0) return false;
191     if (FD_ISSET(abortPipe[0], &readfds)) return false;
192
193     int recvResult = recv(sockfd, pointer, numBytes - totalReceived, 0);
194     if (recvResult == -1) { shutdown(); return false; }
195     totalReceived += recvResult;
196     pointer += recvResult;
197     
198   } while (totalReceived < numBytes);
199   
200   return true;
201 }
202
203 MACAddress TCP::getMAC()
204 {
205   MACAddress macerror{ 00, 00, 00, 00, 00, 00 };
206
207   if (!connected) return macerror;
208
209   /*
210   struct sockaddr      - man 2 bind
211   struct sockaddr_in   - man 7 ip
212   struct sockaddr_in6  - man 7 ipv6
213   struct sockaddr_ll   - man 7 packet
214   */
215
216   // Measured sizeof(struct sockaddr_in) = 16, and in6 = 28
217   UINT buflen = 50;
218   unsigned char gsnBuffer[buflen];
219   struct sockaddr* sap = reinterpret_cast<struct sockaddr*>(gsnBuffer);
220   if (getsockname(sockfd, sap, &buflen) == -1) return macerror;
221
222   uint32_t my4AddrU32;
223   unsigned char my6Addr[16];
224
225   if (sap->sa_family == AF_INET)
226   {
227     struct sockaddr_in* sap4 = reinterpret_cast<struct sockaddr_in*>(gsnBuffer);
228     my4AddrU32 = sap4->sin_addr.s_addr;
229   }
230   else if (sap->sa_family == AF_INET6)
231   {
232     struct sockaddr_in6* sap6 = reinterpret_cast<struct sockaddr_in6*>(gsnBuffer);
233     memcpy(my6Addr, sap6->sin6_addr.s6_addr, 16);
234   }
235   else
236   {
237     return macerror;
238   }
239
240   struct ifaddrs* ifAddrs;
241   if (getifaddrs(&ifAddrs) == -1) return macerror;
242
243   char* ifName = NULL;
244
245   for(struct ifaddrs* ifa = ifAddrs; ifa != NULL; ifa = ifa->ifa_next)
246   {
247     if (ifa->ifa_addr->sa_family != sap->sa_family) continue;
248
249     if (sap->sa_family == AF_INET)
250     {
251       struct sockaddr_in* test = reinterpret_cast<struct sockaddr_in*>(ifa->ifa_addr);
252       if (test->sin_addr.s_addr == my4AddrU32)
253       {
254         ifName = ifa->ifa_name;
255         break;
256       }
257     }
258     else if (sap->sa_family == AF_INET6)
259     {
260       struct sockaddr_in6* test = reinterpret_cast<struct sockaddr_in6*>(ifa->ifa_addr);
261       if (!memcmp(my6Addr, test->sin6_addr.s6_addr, 16))
262       {
263         ifName = ifa->ifa_name;
264         break;
265       }
266     }
267   }
268
269   if (!ifName)
270   {
271     freeifaddrs(ifAddrs);
272     return macerror;
273   }
274
275   // Walk the list again to get the MAC for ifName using family == AF_PACKET
276   MACAddress toReturn = macerror;
277
278   for(struct ifaddrs* ifa = ifAddrs; ifa != NULL; ifa = ifa->ifa_next)
279   {
280     if (ifa->ifa_addr->sa_family != AF_PACKET) continue;
281     if (strcmp(ifName, ifa->ifa_name)) continue;
282     struct sockaddr_ll* sall = reinterpret_cast<struct sockaddr_ll*>(ifa->ifa_addr);
283     if (sall->sll_halen != 6) continue;
284
285     memcpy(&toReturn, sall->sll_addr, 6);
286     break;
287   }
288
289   freeifaddrs(ifAddrs);
290   return toReturn;
291 }