Instrument Neutral Distributed Interface INDI  1.9.5
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);
76  IUFillSwitchVector(&LANSearchSP, LANSearchS, 2, dev->getDeviceName(), INDI::SP::DEVICE_LAN_SEARCH, "LAN Search",
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 
213 bool TCP::Connect()
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 rc = true;
223  std::string hostname = AddressT[0].text;
224  std::string port = AddressT[1].text;
225 
226  if (m_Device->isSimulation() == false)
227  {
228  rc = false;
229  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]?)$");
230  const auto isIPv4 = regex_match(hostname, ipv4);
231  if (establishConnection(hostname, port) == false)
232  {
233  // Auto search is disabled.
235  return false;
236  // LAN search is enabled.
237  else if (isIPv4)
238  {
239  size_t found = hostname.find_last_of(".");
240 
241  if (found != std::string::npos)
242  {
243  // Get the source subnet
244  const auto sourceSubnet = hostname.substr(0, found);
245  std::deque<std::string> subnets;
246 
247  // Get all interface IPv4 addresses. From there we extract subnets
248  auto addrs_ipv4 = gmlc::netif::getInterfaceAddressesV4();
249  for (auto &oneInterfaceAddress : addrs_ipv4)
250  {
251  // Skip local IPs
252  if (oneInterfaceAddress.rfind("127", 0) == 0)
253  continue;
254 
255  size_t found = oneInterfaceAddress.find_last_of(".");
256  if (found != std::string::npos)
257  {
258  // Extract target subnect
259  const auto targetSubnet = oneInterfaceAddress.substr(0, found);
260  // Prefer subnets matching source subnet
261  if (targetSubnet == sourceSubnet)
262  subnets.push_front(targetSubnet);
263  else
264  subnets.push_back(targetSubnet);
265 
266  }
267  }
268 
269  for (auto &oneSubnet : subnets)
270  {
271  LOGF_INFO("Searching %s subnet, this operation will take a few minutes to complete. Stand by...", oneSubnet.c_str());
272  // Brute force search through all subnet
273  // N.B. This operation cannot be interrupted.
274  // TODO Must add a method to abort the search.
275  for (int i = 1; i < 255; i++)
276  {
277  const auto newAddress = oneSubnet + "." + std::to_string(i);
278  if (newAddress == hostname)
279  continue;
280 
281  if (establishConnection(newAddress, port, 1))
282  {
283  PortFD = m_SockFD;
284  LOGF_DEBUG("Connection to %s@%s is successful, attempting handshake...", hostname.c_str(), port.c_str());
285  rc = Handshake();
286  if (rc)
287  {
288  hostname = newAddress;
289  break;
290  }
291  }
292  }
293 
294  if (rc)
295  break;
296  }
297  }
298  }
299  }
300  else
301  {
302  PortFD = m_SockFD;
303  LOGF_DEBUG("Connection to %s@%s is successful, attempting handshake...", hostname.c_str(), port.c_str());
304  rc = Handshake();
305  }
306  }
307 
308  if (rc)
309  {
310  LOGF_INFO("%s is online.", getDeviceName());
311  IUSaveText(&AddressT[0], hostname.c_str());
312 
313  if (m_ConfigHost != std::string(AddressT[0].text) || m_ConfigPort != std::string(AddressT[1].text))
314  m_Device->saveConfig(true, "DEVICE_ADDRESS");
316  m_Device->saveConfig(true, "CONNECTION_TYPE");
318  {
322  }
323  }
324  else
325  LOG_DEBUG("Handshake failed.");
326 
327  return rc;
328 }
329 
333 bool TCP::Disconnect()
334 {
335  if (m_SockFD > 0)
336  {
337  close(m_SockFD);
338  m_SockFD = PortFD = -1;
339  }
340 
341  return true;
342 }
343 
347 void TCP::Activated()
348 {
352 }
353 
357 void TCP::Deactivated()
358 {
362 }
363 
367 bool TCP::saveConfigItems(FILE *fp)
368 {
372 
373  return true;
374 }
375 
379 void TCP::setDefaultHost(const char *addressHost)
380 {
381  if (m_ConfigHost.empty())
382  IUSaveText(&AddressT[0], addressHost);
384  IDSetText(&AddressTP, nullptr);
385 }
386 
390 void TCP::setDefaultPort(uint32_t addressPort)
391 {
392  if (m_ConfigPort.empty())
393  {
394  char portStr[8];
395  snprintf(portStr, 8, "%d", addressPort);
396  IUSaveText(&AddressT[1], portStr);
397  }
399  IDSetText(&AddressTP, nullptr);
400 }
401 
406 {
407  if (m_ConfigConnectionType < 0)
408  {
410  TcpUdpS[type].s = ISS_ON;
411  }
413  IDSetSwitch(&TcpUdpSP, nullptr);
414 }
415 
419 void TCP::setLANSearchEnabled(bool enabled)
420 {
424  IDSetSwitch(&LANSearchSP, nullptr);
425 }
426 }
indistandardproperty.h
Connection::Interface::Handshake
std::function< bool()> Handshake
Definition: connectioninterface.h:129
Connection::TCP::AddressTP
ITextVectorProperty AddressTP
Properties.
Definition: connectiontcp.h:144
Connection::TCP::establishConnection
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,...
Definition: connectiontcp.cpp:161
LOGF_ERROR
#define LOGF_ERROR(fmt,...)
Definition: indilogger.h:80
IPS_OK
@ IPS_OK
Definition: indiapi.h:161
Connection::TCP::Disconnect
virtual bool Disconnect() override
Disconnect Disconnect from device.
Definition: connectiontcp.cpp:349
INDI::SP::DEVICE_ADDRESS
const char * DEVICE_ADDRESS
Device hostname and port. It is part of Connection::TCPInterface to manage connections to devices ove...
Definition: indistandardproperty.cpp:68
ISS_OFF
@ ISS_OFF
Definition: indiapi.h:150
Connection::TCP::TcpUdpS
ISwitch TcpUdpS[2]
Definition: connectiontcp.h:147
connectiontcp.h
IDSetText
void IDSetText(const ITextVectorProperty *t, const char *msg,...) ATTRIBUTE_FORMAT_PRINTF(2
Tell client to update an existing text vector property.
MAXINDINAME
#define MAXINDINAME
Definition: indiapi.h:190
INDI::DefaultDevice::isSimulation
bool isSimulation() const
Definition: defaultdevice.cpp:734
INDI::DefaultDevice::defineProperty
void defineProperty(INumberVectorProperty *property)
Definition: defaultdevice.cpp:997
Connection::TCP::m_ConfigPort
std::string m_ConfigPort
Definition: connectiontcp.h:155
IUFillTextVector
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: indidriver.c:477
Connection::TCP::LANSearchSP
ISwitchVectorProperty LANSearchSP
Definition: connectiontcp.h:151
INDI::DefaultDevice::isInitializationComplete
bool isInitializationComplete() const
isInitializationComplete Check if driver initialization is complete.
Definition: defaultdevice.cpp:1263
INDI::BaseDevice::getDeviceName
const char * getDeviceName() const
Definition: basedevice.cpp:799
IUFillText
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: indidriver.c:369
Connection::TCP::PortFD
int PortFD
Definition: connectiontcp.h:158
IUUpdateText
int IUUpdateText(ITextVectorProperty *tvp, char *texts[], char *names[], int n)
Update all text members in a text vector property.
Definition: indidriver.c:259
Connection::TCP::saveConfigItems
virtual bool saveConfigItems(FILE *fp) override
Definition: connectiontcp.cpp:383
LOG_INFO
#define LOG_INFO(txt)
Definition: indilogger.h:74
IUResetSwitch
void IUResetSwitch(ISwitchVectorProperty *svp)
Reset all switches in a switch vector property to OFF.
Definition: indicom.c:1442
Connection::Interface::getDeviceName
const char * getDeviceName()
Definition: connectioninterface.cpp:53
Connection::Serial::m_ConfigPort
std::string m_ConfigPort
Definition: connectionserial.h:230
indilogger.h
Connection::TCP::Connect
virtual bool Connect() override
Connect Connect to device via the implemented communication medium. Do not perform any handshakes.
Definition: connectiontcp.cpp:229
LOGF_DEBUG
#define LOGF_DEBUG(fmt,...)
Definition: indilogger.h:83
type
__le16 type
Definition: pwc-ioctl.h:2
IUGetConfigText
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:1448
IUSaveConfigText
void IUSaveConfigText(FILE *fp, const ITextVectorProperty *tvp)
Add a text vector property value to the configuration file.
Definition: indicom.c:1460
Connection::TCP::LANSearchS
ISwitch LANSearchS[2]
Definition: connectiontcp.h:150
Connection::TCP::setConnectionType
void setConnectionType(int type)
TODO should be renamed to setDefaultConnectionType.
Definition: connectiontcp.cpp:421
Connection::Interface::type
virtual Type type()
type Return connection type
Definition: connectioninterface.h:105
IUFillSwitchVector
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: indidriver.c:412
INDI::BaseDevice::INDI_ENABLED
@ INDI_ENABLED
Definition: basedevice.h:64
ISR_1OFMANY
@ ISR_1OFMANY
Definition: indiapi.h:172
Connection::Interface::m_Device
INDI::DefaultDevice * m_Device
Definition: connectioninterface.h:130
IPS_IDLE
@ IPS_IDLE
Definition: indiapi.h:160
Connection::CONNECTION_TAB
const char * CONNECTION_TAB
CONNECTION_TAB Where all device connection settings (serial, usb, ethernet) are defined and controlle...
Definition: connectioninterface.cpp:41
Connection::TCP::ISNewText
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Definition: connectiontcp.cpp:100
IUUpdateSwitch
int IUUpdateSwitch(ISwitchVectorProperty *svp, ISState *states, char *names[], int n)
Update all switches in a switch vector property.
Definition: indidriver.c:171
_ITextVectorProperty::name
char name[MAXINDINAME]
Definition: indiapi.h:249
Connection::TCP::TCP
TCP(INDI::DefaultDevice *dev)
Definition: connectiontcp.cpp:60
LOG_DEBUG
#define LOG_DEBUG(txt)
Definition: indilogger.h:75
Connection::TCP::Activated
virtual void Activated() override
Activated Function called by the framework when the plugin is activated (i.e. selected by the user)....
Definition: connectiontcp.cpp:363
LOGF_INFO
#define LOGF_INFO(fmt,...)
Definition: indilogger.h:82
Connection::TCP::SOCKET_TIMEOUT
static constexpr uint8_t SOCKET_TIMEOUT
Definition: connectiontcp.h:159
INDI::SP::DEVICE_LAN_SEARCH
const char * DEVICE_LAN_SEARCH
Toggle device LAN search. If the initial handshake with the specified hostname and port number fails,...
Definition: indistandardproperty.cpp:66
LOG_ERROR
#define LOG_ERROR(txt)
Shorter logging macros. In order to use these macros, the function (or method) "getDeviceName()" must...
Definition: indilogger.h:72
Connection::TCP::TcpUdpSP
ISwitchVectorProperty TcpUdpSP
Definition: connectiontcp.h:148
Connection
Combines all INDI Connection Plugins. Each INDI connection plugin is responsible of managing communic...
Definition: arduino_st4.h:33
IUSaveText
void IUSaveText(IText *tp, const char *newtext)
Function to reliably save new text in a IText.
Definition: indicom.c:1449
name
const char * name
Definition: indiserver.c:116
Connection::TCP::port
virtual uint32_t port() const
Definition: connectiontcp.h:107
_ISwitchVectorProperty::s
IPState s
Definition: indiapi.h:382
Connection::TCP::m_SockFD
int m_SockFD
Definition: connectiontcp.h:157
Connection::TCP::setDefaultHost
void setDefaultHost(const char *addressHost)
Definition: connectiontcp.cpp:395
_ITextVectorProperty::s
IPState s
Definition: indiapi.h:259
INDI::DefaultDevice::saveConfig
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
Definition: defaultdevice.cpp:221
Connection::TCP::setDefaultPort
void setDefaultPort(uint32_t addressPort)
Definition: connectiontcp.cpp:406
Connection::TCP::name
virtual std::string name() override
Definition: connectiontcp.h:93
Connection::TCP::m_ConfigHost
std::string m_ConfigHost
Definition: connectiontcp.h:154
Connection::TCP::Deactivated
virtual void Deactivated() override
Deactivated Function called by the framework when the plugin is deactivated. It is usually used to de...
Definition: connectiontcp.cpp:373
Connection::TCP::setLANSearchEnabled
void setLANSearchEnabled(bool enabled)
Definition: connectiontcp.cpp:435
gmlc::netif::getInterfaceAddressesV4
std::vector< std::string > getInterfaceAddressesV4()
Definition: NetIF.hpp:269
IP_RW
@ IP_RW
Definition: indiapi.h:185
NetIF.hpp
ISState
ISState
Switch state.
Definition: indiapi.h:148
INDI::SP::DEVICE_AUTO_SEARCH
const char * DEVICE_AUTO_SEARCH
Toggle device auto search. If enabled and on connection failure with the default port,...
Definition: indistandardproperty.cpp:65
IUGetConfigOnSwitchIndex
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:1270
IUFindOnSwitchIndex
int IUFindOnSwitchIndex(const ISwitchVectorProperty *sp)
Returns the index of first ON switch it finds in the vector switch property.
Definition: indicom.c:1424
Connection::TCP::m_ConfigConnectionType
int m_ConfigConnectionType
Definition: connectiontcp.h:156
IUSaveConfigSwitch
void IUSaveConfigSwitch(FILE *fp, const ISwitchVectorProperty *svp)
Add a switch vector property value to the configuration file.
Definition: indicom.c:1465
INDI::DefaultDevice
Class to provide extended functionality for devices in addition to the functionality provided by INDI...
Definition: defaultdevice.h:118
Connection::TCP::AddressT
IText AddressT[2]
Definition: connectiontcp.h:145
INDI::DefaultDevice::deleteProperty
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
Definition: defaultdevice.cpp:965
IDSetSwitch
void void void void void IDSetSwitch(const ISwitchVectorProperty *s, const char *msg,...) ATTRIBUTE_FORMAT_PRINTF(2
Tell client to update an existing switch vector property.
errno
int errno
IUFillSwitch
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: indidriver.c:320
Connection::TCP::ISNewSwitch
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Definition: connectiontcp.cpp:120
INDI::BaseDevice::INDI_DISABLED
@ INDI_DISABLED
Definition: basedevice.h:65
_ISwitchVectorProperty::name
char name[MAXINDINAME]
Definition: indiapi.h:370
ISS_ON
@ ISS_ON
Definition: indiapi.h:151