Instrument Neutral Distributed Interface INDI  2.0.2
connectiontcp.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  Copyright(c) 2017 Jasem Mutlaq. All rights reserved.
3 
4  This library is free software; you can redistribute it and/or
5  modify it under the terms of the GNU Library General Public
6  License version 2 as published by the Free Software Foundation.
7 
8  This library is distributed in the hope that it will be useful,
9  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11  Library General Public License for more details.
12 
13  You should have received a copy of the GNU Library General Public License
14  along with this library; see the file COPYING.LIB. If not, write to
15  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16  Boston, MA 02110-1301, USA.
17 *******************************************************************************/
18 
19 #include "connectiontcp.h"
20 
21 #include "NetIF.hpp"
22 #include "indilogger.h"
23 #include "indistandardproperty.h"
24 
25 #include <cerrno>
26 #include <netdb.h>
27 #include <cstring>
28 #include <unistd.h>
29 #include <regex>
30 
31 #ifdef __FreeBSD__
32 #include <arpa/inet.h>
33 #include <netinet/in.h>
34 #include <sys/socket.h>
35 #endif
36 
37 namespace Connection
38 {
39 extern const char *CONNECTION_TAB;
40 
44 TCP::TCP(INDI::DefaultDevice *dev) : Interface(dev, CONNECTION_TCP)
45 {
46  char defaultHostname[MAXINDINAME] = {0};
47  char defaultPort[MAXINDINAME] = {0};
48 
49  // Try to load the port from the config file. If that fails, use default port.
50  if (IUGetConfigText(dev->getDeviceName(), INDI::SP::DEVICE_ADDRESS, "ADDRESS", defaultHostname, MAXINDINAME) == 0)
51  m_ConfigHost = defaultHostname;
52  if (IUGetConfigText(dev->getDeviceName(), INDI::SP::DEVICE_ADDRESS, "PORT", defaultPort, MAXINDINAME) == 0)
53  m_ConfigPort = defaultPort;
54 
55  // Address/Port
56  IUFillText(&AddressT[0], "ADDRESS", "Address", defaultHostname);
57  IUFillText(&AddressT[1], "PORT", "Port", defaultPort);
58  IUFillTextVector(&AddressTP, AddressT, 2, getDeviceName(), "DEVICE_ADDRESS", "Server", CONNECTION_TAB,
59  IP_RW, 60, IPS_IDLE);
60 
61  int connectionTypeIndex = 0;
62  if (IUGetConfigOnSwitchIndex(dev->getDeviceName(), "CONNECTION_TYPE", &connectionTypeIndex) == 0)
63  m_ConfigConnectionType = connectionTypeIndex;
64  IUFillSwitch(&TcpUdpS[TYPE_TCP], "TCP", "TCP", connectionTypeIndex == TYPE_TCP ? ISS_ON : ISS_OFF);
65  IUFillSwitch(&TcpUdpS[TYPE_UDP], "UDP", "UDP", connectionTypeIndex == TYPE_UDP ? ISS_ON : ISS_OFF);
66  IUFillSwitchVector(&TcpUdpSP, TcpUdpS, 2, getDeviceName(), "CONNECTION_TYPE", "Connection Type",
68 
69  int autoSearchIndex = 1;
70  // Try to load the port from the config file. If that fails, use default port.
72  IUFillSwitch(&LANSearchS[INDI::DefaultDevice::INDI_ENABLED], "INDI_ENABLED", "Enabled",
73  autoSearchIndex == 0 ? ISS_ON : ISS_OFF);
74  IUFillSwitch(&LANSearchS[INDI::DefaultDevice::INDI_DISABLED], "INDI_DISABLED", "Disabled",
75  autoSearchIndex == 0 ? ISS_OFF : ISS_ON);
78 
79 }
80 
84 bool TCP::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
85 {
86  if (!strcmp(dev, m_Device->getDeviceName()))
87  {
88  // TCP Server settings
89  if (!strcmp(name, AddressTP.name))
90  {
91  IUUpdateText(&AddressTP, texts, names, n);
92  AddressTP.s = IPS_OK;
93  IDSetText(&AddressTP, nullptr);
94  return true;
95  }
96  }
97 
98  return false;
99 }
100 
104 bool TCP::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
105 {
106  if (!strcmp(dev, m_Device->getDeviceName()))
107  {
108  if (!strcmp(name, TcpUdpSP.name))
109  {
110  IUUpdateSwitch(&TcpUdpSP, states, names, n);
111  TcpUdpSP.s = IPS_OK;
112 
113  IDSetSwitch(&TcpUdpSP, nullptr);
114 
115  return true;
116  }
117 
118  // Auto Search Devices on connection failure
119  if (!strcmp(name, LANSearchSP.name))
120  {
121  bool wasEnabled = (LANSearchS[0].s == ISS_ON);
122 
123  IUUpdateSwitch(&LANSearchSP, states, names, n);
124  LANSearchSP.s = IPS_OK;
125 
126  // Only display message if there is an actual change
127  if (wasEnabled == false && LANSearchS[0].s == ISS_ON)
128  LOG_INFO("LAN search is enabled. When connecting, the driver shall attempt to "
129  "communicate with all devices on the local network until a connection is "
130  "established.");
131  else if (wasEnabled && LANSearchS[1].s == ISS_ON)
132  LOG_INFO("Auto search is disabled.");
133  IDSetSwitch(&LANSearchSP, nullptr);
134 
135  return true;
136  }
137  }
138 
139  return false;
140 }
141 
145 bool TCP::establishConnection(const std::string &hostname, const std::string &port, int timeout)
146 {
147  struct sockaddr_in serv_addr;
148  struct hostent *hp = nullptr;
149 
150  struct timeval ts;
151  ts.tv_sec = timeout <= 0 ? SOCKET_TIMEOUT : timeout;
152  ts.tv_usec = 0;
153 
154  if (m_SockFD != -1)
155  close(m_SockFD);
156 
158  LOGF_INFO("Connecting to %s@%s ...", hostname.c_str(), port.c_str());
159  else
160  LOGF_DEBUG("Connecting to %s@%s ...", hostname.c_str(), port.c_str());
161 
162 
163  // Lookup host name or IPv4 address
164  hp = gethostbyname(hostname.c_str());
165  if (!hp)
166  {
168  LOG_ERROR("Failed to lookup IP Address or hostname.");
169  return false;
170  }
171 
172  memset(&serv_addr, 0, sizeof(serv_addr));
173  serv_addr.sin_family = AF_INET;
174  serv_addr.sin_addr.s_addr = ((struct in_addr *)(hp->h_addr_list[0]))->s_addr;
175  serv_addr.sin_port = htons(atoi(port.c_str()));
176 
177  int socketType = 0;
178  if (TcpUdpS[0].s == ISS_ON)
179  {
180  socketType = SOCK_STREAM;
181  }
182  else
183  {
184  socketType = SOCK_DGRAM;
185  }
186 
187  if ((m_SockFD = socket(AF_INET, socketType, 0)) < 0)
188  {
189  LOG_ERROR("Failed to create socket.");
190  return false;
191  }
192 
193  // Set the socket receiving and sending timeouts
194  setsockopt(m_SockFD, SOL_SOCKET, SO_RCVTIMEO, &ts, sizeof(struct timeval));
195  setsockopt(m_SockFD, SOL_SOCKET, SO_SNDTIMEO, &ts, sizeof(struct timeval));
196 
197  // Connect to the device
198  if (::connect(m_SockFD, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
199  {
201  LOGF_ERROR("Failed to connect to %s@%s: %s.", hostname.c_str(), port.c_str(), strerror(errno));
202  close(m_SockFD);
203  m_SockFD = -1;
204  return false;
205  }
206 
207  return true;
208 }
209 
214 {
215  if (AddressT[0].text == nullptr || AddressT[0].text[0] == '\0' || AddressT[1].text == nullptr ||
216  AddressT[1].text[0] == '\0')
217  {
218  LOG_ERROR("Error! Server address is missing or invalid.");
219  return false;
220  }
221 
222  bool handshakeResult = true;
223  std::string hostname = AddressT[0].text;
224  std::string port = AddressT[1].text;
225 
226  // Should just call handshake on simulation
227  if (m_Device->isSimulation())
228  handshakeResult = Handshake();
229 
230  else
231  {
232  handshakeResult = false;
233  std::regex ipv4("^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$");
234  const auto isIPv4 = regex_match(hostname, ipv4);
235 
236  // Establish connection to host:port
237  if (establishConnection(hostname, port))
238  {
239  PortFD = m_SockFD;
240  LOGF_DEBUG("Connection to %s@%s is successful, attempting handshake...", hostname.c_str(), port.c_str());
241  handshakeResult = Handshake();
242 
243  // Auto search is disabled.
244  if (handshakeResult == false && LANSearchS[INDI::DefaultDevice::INDI_ENABLED].s == ISS_OFF)
245  {
246  LOG_DEBUG("Handshake failed.");
247  return false;
248  }
249  }
250 
251  // If connection failed; or
252  // handshake failed; then we can search LAN if the IP address is v4
253  // LAN search is enabled.
254  if (handshakeResult == false &&
256  isIPv4)
257  {
258  size_t found = hostname.find_last_of(".");
259 
260  if (found != std::string::npos)
261  {
262  // Get the source subnet
263  const auto sourceSubnet = hostname.substr(0, found);
264  std::deque<std::string> subnets;
265 
266  // Get all interface IPv4 addresses. From there we extract subnets
267  auto addrs_ipv4 = gmlc::netif::getInterfaceAddressesV4();
268  for (auto &oneInterfaceAddress : addrs_ipv4)
269  {
270  // Skip local IPs
271  if (oneInterfaceAddress.rfind("127", 0) == 0)
272  continue;
273 
274  size_t found = oneInterfaceAddress.find_last_of(".");
275  if (found != std::string::npos)
276  {
277  // Extract target subnect
278  const auto targetSubnet = oneInterfaceAddress.substr(0, found);
279  // Prefer subnets matching source subnet
280  if (targetSubnet == sourceSubnet)
281  subnets.push_front(targetSubnet);
282  else
283  subnets.push_back(targetSubnet);
284 
285  }
286  }
287 
288  for (auto &oneSubnet : subnets)
289  {
290  LOGF_INFO("Searching %s subnet, this operation will take a few minutes to complete. Stand by...", oneSubnet.c_str());
291  // Brute force search through all subnet
292  // N.B. This operation cannot be interrupted.
293  // TODO Must add a method to abort the search.
294  for (int i = 1; i < 255; i++)
295  {
296  const auto newAddress = oneSubnet + "." + std::to_string(i);
297  if (newAddress == hostname)
298  continue;
299 
300  if (establishConnection(newAddress, port, 1))
301  {
302  PortFD = m_SockFD;
303  LOGF_DEBUG("Connection to %s@%s is successful, attempting handshake...", hostname.c_str(), port.c_str());
304  handshakeResult = Handshake();
305  if (handshakeResult)
306  {
307  hostname = newAddress;
308  break;
309  }
310  }
311  }
312 
313  if (handshakeResult)
314  break;
315  }
316  }
317  }
318  }
319 
320  if (handshakeResult)
321  {
322  LOGF_INFO("%s is online.", getDeviceName());
323  IUSaveText(&AddressT[0], hostname.c_str());
324 
325  if (m_ConfigHost != std::string(AddressT[0].text) || m_ConfigPort != std::string(AddressT[1].text))
326  m_Device->saveConfig(true, "DEVICE_ADDRESS");
328  m_Device->saveConfig(true, "CONNECTION_TYPE");
330  {
334  }
335  }
336  else
337  LOG_DEBUG("Handshake failed.");
338 
339  return handshakeResult;
340 }
341 
346 {
347  if (m_SockFD > 0)
348  {
349  close(m_SockFD);
350  m_SockFD = PortFD = -1;
351  }
352 
353  return true;
354 }
355 
360 {
364 }
365 
370 {
374 }
375 
379 bool TCP::saveConfigItems(FILE * fp)
380 {
384 
385  return true;
386 }
387 
391 void TCP::setDefaultHost(const char *addressHost)
392 {
393  if (m_ConfigHost.empty())
394  IUSaveText(&AddressT[0], addressHost);
396  IDSetText(&AddressTP, nullptr);
397 }
398 
402 void TCP::setDefaultPort(uint32_t addressPort)
403 {
404  if (m_ConfigPort.empty())
405  {
406  char portStr[8];
407  snprintf(portStr, 8, "%d", addressPort);
408  IUSaveText(&AddressT[1], portStr);
409  }
411  IDSetText(&AddressTP, nullptr);
412 }
413 
418 {
419  if (m_ConfigConnectionType < 0)
420  {
422  TcpUdpS[type].s = ISS_ON;
423  }
425  IDSetSwitch(&TcpUdpSP, nullptr);
426 }
427 
431 void TCP::setLANSearchEnabled(bool enabled)
432 {
436  IDSetSwitch(&LANSearchSP, nullptr);
437 }
438 }
The Interface class is the base class for all INDI connection plugins.
virtual Type type()
type Return connection type
std::function< bool()> Handshake
INDI::DefaultDevice * m_Device
void setDefaultHost(const char *addressHost)
ISwitchVectorProperty TcpUdpSP
ISwitch LANSearchS[2]
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
static constexpr uint8_t SOCKET_TIMEOUT
void setLANSearchEnabled(bool enabled)
virtual std::string name() override
Definition: connectiontcp.h:57
virtual bool Disconnect() override
Disconnect Disconnect from device.
void setConnectionType(int type)
TODO should be renamed to setDefaultConnectionType.
virtual void Activated() override
Activated Function called by the framework when the plugin is activated (i.e. selected by the user)....
void setDefaultPort(uint32_t addressPort)
virtual bool Connect() override
Connect Connect to device via the implemented communication medium. Do not perform any handshakes.
ISwitch TcpUdpS[2]
std::string m_ConfigHost
std::string m_ConfigPort
virtual uint32_t port() const
Definition: connectiontcp.h:71
virtual bool saveConfigItems(FILE *fp) override
ISwitchVectorProperty LANSearchSP
bool establishConnection(const std::string &hostname, const std::string &port, int timeout=-1)
establishConnection Create a socket connection to the host and port. If successful,...
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
ITextVectorProperty AddressTP
Properties.
virtual void Deactivated() override
Deactivated Function called by the framework when the plugin is deactivated. It is usually used to de...
TCP(INDI::DefaultDevice *dev)
const char * getDeviceName() const
Definition: basedevice.cpp:821
Class to provide extended functionality for devices in addition to the functionality provided by INDI...
bool isInitializationComplete() const
isInitializationComplete Check if driver initialization is complete.
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
bool isSimulation() const
int errno
ISState
Switch state.
Definition: indiapi.h:150
@ ISS_OFF
Definition: indiapi.h:151
@ ISS_ON
Definition: indiapi.h:152
@ IP_RW
Definition: indiapi.h:186
@ IPS_IDLE
Definition: indiapi.h:161
@ IPS_OK
Definition: indiapi.h:162
@ ISR_1OFMANY
Definition: indiapi.h:173
#define MAXINDINAME
Definition: indiapi.h:191
void IUSaveConfigSwitch(FILE *fp, const ISwitchVectorProperty *svp)
Add a switch vector property value to the configuration file.
Definition: indidevapi.c:25
int IUFindOnSwitchIndex(const ISwitchVectorProperty *svp)
Returns the index of first ON switch it finds in the vector switch property.
Definition: indidevapi.c:128
void IUResetSwitch(ISwitchVectorProperty *svp)
Reset all switches in a switch vector property to OFF.
Definition: indidevapi.c:148
void IUFillTextVector(ITextVectorProperty *tvp, IText *tp, int ntp, const char *dev, const char *name, const char *label, const char *group, IPerm p, double timeout, IPState s)
Assign attributes for a text vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:291
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition: indidevapi.c:36
void IUFillSwitch(ISwitch *sp, const char *name, const char *label, ISState s)
Assign attributes for a switch property. The switch's auxiliary elements will be set to NULL.
Definition: indidevapi.c:158
void IUFillText(IText *tp, const char *name, const char *label, const char *initialText)
Assign attributes for a text property. The text's auxiliary elements will be set to NULL.
Definition: indidevapi.c:198
void IUSaveConfigText(FILE *fp, const ITextVectorProperty *tvp)
Add a text vector property value to the configuration file.
Definition: indidevapi.c:20
void IUFillSwitchVector(ISwitchVectorProperty *svp, ISwitch *sp, int nsp, const char *dev, const char *name, const char *label, const char *group, IPerm p, ISRule r, double timeout, IPState s)
Assign attributes for a switch vector property. The vector's auxiliary elements will be set to NULL.
Definition: indidevapi.c:235
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
Definition: indidriver.c:1308
void IDSetSwitch(const ISwitchVectorProperty *svp, const char *fmt,...)
Definition: indidriver.c:1231
int IUUpdateText(ITextVectorProperty *tvp, char *texts[], char *names[], int n)
Update all text members in a text vector property.
Definition: indidriver.c:1396
int IUGetConfigText(const char *dev, const char *property, const char *member, char *value, int len)
IUGetConfigText Opens configuration file and reads single text property.
Definition: indidriver.c:889
int IUGetConfigOnSwitchIndex(const char *dev, const char *property, int *index)
IUGetConfigOnSwitchIndex Opens configuration file and reads single switch property to find ON switch ...
Definition: indidriver.c:711
void IDSetText(const ITextVectorProperty *tvp, const char *fmt,...)
Definition: indidriver.c:1191
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
#define LOG_DEBUG(txt)
Definition: indilogger.h:75
#define LOGF_DEBUG(fmt,...)
Definition: indilogger.h:83
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition: indilogger.h:72
#define LOGF_ERROR(fmt,...)
Definition: indilogger.h:80
#define LOG_INFO(txt)
Definition: indilogger.h:74
Combines all INDI Connection Plugins. Each INDI connection plugin is responsible of managing communic...
Definition: arduino_st4.h:34
const char * CONNECTION_TAB
const char * DEVICE_AUTO_SEARCH
Toggle device auto search. If enabled and on connection failure with the default port,...
const char * DEVICE_ADDRESS
Device hostname and port. It is part of Connection::TCPInterface to manage connections to devices ove...
const char * DEVICE_LAN_SEARCH
Toggle device LAN search. If the initial handshake with the specified hostname and port number fails,...
std::vector< std::string > getInterfaceAddressesV4()
Definition: NetIF.hpp:269
NLOHMANN_BASIC_JSON_TPL_DECLARATION std::string to_string(const NLOHMANN_BASIC_JSON_TPL &j)
user-defined to_string function for JSON values
Definition: json.h:23613
__le16 type
Definition: pwc-ioctl.h:0
char name[MAXINDINAME]
Definition: indiapi.h:371
char name[MAXINDINAME]
Definition: indiapi.h:250