Instrument Neutral Distributed Interface INDI 2.2.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"
24
25#include <cerrno>
26#include <netdb.h>
27#include <cstring>
28#include <unistd.h>
29#include <regex>
30
31#if defined(__FreeBSD__) || defined(__OpenBSD__)
32#include <arpa/inet.h>
33#include <netinet/in.h>
34#include <sys/socket.h>
35#endif
36
37namespace Connection
38{
39extern const char *CONNECTION_TAB;
40
44TCP::TCP(INDI::DefaultDevice *dev, IPerm permission) : Interface(dev, CONNECTION_TCP), m_Permission(permission)
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 AddressTP[0].fill("ADDRESS", "Address", defaultHostname);
57 AddressTP[1].fill("PORT", "Port", defaultPort);
59
60 int connectionTypeIndex = 0;
61 if (IUGetConfigOnSwitchIndex(dev->getDeviceName(), "CONNECTION_TYPE", &connectionTypeIndex) == 0)
62 m_ConfigConnectionType = connectionTypeIndex;
63 TcpUdpSP[TYPE_TCP].fill("TCP", "TCP", connectionTypeIndex == TYPE_TCP ? ISS_ON : ISS_OFF);
64 TcpUdpSP[TYPE_UDP].fill("UDP", "UDP", connectionTypeIndex == TYPE_UDP ? ISS_ON : ISS_OFF);
65 TcpUdpSP.fill(getDeviceName(), "CONNECTION_TYPE", "Connection Type", CONNECTION_TAB, IP_RW, ISR_1OFMANY, 60, IPS_IDLE);
66
67 int autoSearchIndex = 1;
68 // Try to load the port from the config file. If that fails, use default port.
70 LANSearchSP[INDI::DefaultDevice::INDI_ENABLED].fill("INDI_ENABLED", "Enabled", autoSearchIndex == 0 ? ISS_ON : ISS_OFF);
71 LANSearchSP[INDI::DefaultDevice::INDI_DISABLED].fill("INDI_DISABLED", "Disabled", autoSearchIndex == 0 ? ISS_OFF : ISS_ON);
73 IPS_IDLE);
74}
75
79bool TCP::ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
80{
81 if (!strcmp(dev, m_Device->getDeviceName()))
82 {
83 // TCP Server settings
85 {
86 AddressTP.update(texts, names, n);
89 return true;
90 }
91 }
92
93 return false;
94}
95
99bool TCP::ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
100{
101 if (!strcmp(dev, m_Device->getDeviceName()))
102 {
104 {
105 TcpUdpSP.update(states, names, n);
107
108 TcpUdpSP.apply();
109
110 return true;
111 }
112
113 // Auto Search Devices on connection failure
115 {
117
118 LANSearchSP.update(states, names, n);
120
121 // Only display message if there is an actual change
122 if (wasEnabled == false && LANSearchSP[INDI::DefaultDevice::INDI_ENABLED].getState() == ISS_ON)
123 LOG_INFO("LAN search is enabled. When connecting, the driver shall attempt to "
124 "communicate with all devices on the local network until a connection is "
125 "established.");
126 else if (wasEnabled && LANSearchSP[INDI::DefaultDevice::INDI_DISABLED].getState() == ISS_ON)
127 LOG_INFO("Auto search is disabled.");
129
130 return true;
131 }
132 }
133
134 return false;
135}
136
140bool TCP::establishConnection(const std::string &hostname, const std::string &port, int timeout)
141{
142 struct sockaddr_in serv_addr;
143 struct hostent *hp = nullptr;
144
145 struct timeval ts;
146 ts.tv_sec = timeout <= 0 ? SOCKET_TIMEOUT : timeout;
147 ts.tv_usec = 0;
148
149 if (m_SockFD != -1)
150 close(m_SockFD);
151
153 LOGF_INFO("Connecting to %s@%s ...", hostname.c_str(), port.c_str());
154 else
155 LOGF_DEBUG("Connecting to %s@%s ...", hostname.c_str(), port.c_str());
156
157
158 // Lookup host name or IPv4 address
159 hp = gethostbyname(hostname.c_str());
160 if (!hp)
161 {
163 LOG_ERROR("Failed to lookup IP Address or hostname.");
164 return false;
165 }
166
167 memset(&serv_addr, 0, sizeof(serv_addr));
168 serv_addr.sin_family = AF_INET;
169 serv_addr.sin_addr.s_addr = ((struct in_addr *)(hp->h_addr_list[0]))->s_addr;
170 serv_addr.sin_port = htons(atoi(port.c_str()));
171
172 int socketType = 0;
173 if (TcpUdpSP[TYPE_TCP].getState() == ISS_ON)
174 {
175 socketType = SOCK_STREAM;
176 }
177 else
178 {
179 socketType = SOCK_DGRAM;
180 }
181
182 if ((m_SockFD = socket(AF_INET, socketType, 0)) < 0)
183 {
184 LOG_ERROR("Failed to create socket.");
185 return false;
186 }
187
188 // Set the socket receiving and sending timeouts
189 setsockopt(m_SockFD, SOL_SOCKET, SO_RCVTIMEO, &ts, sizeof(struct timeval));
190 setsockopt(m_SockFD, SOL_SOCKET, SO_SNDTIMEO, &ts, sizeof(struct timeval));
191
192 // Connect to the device
193 if (::connect(m_SockFD, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
194 {
196 LOGF_ERROR("Failed to connect to %s@%s: %s.", hostname.c_str(), port.c_str(), strerror(errno));
197 close(m_SockFD);
198 m_SockFD = -1;
199 return false;
200 }
201
202 return true;
203}
204
209{
210 if (AddressTP[0].isEmpty() || AddressTP[1].isEmpty())
211 {
212 LOG_ERROR("Error! Server address is missing or invalid.");
213 return false;
214 }
215
216 bool handshakeResult = true;
217 std::string hostname = AddressTP[0].getText();
218 std::string port = AddressTP[1].getText();
219
220 // Should just call handshake on simulation
221 if (m_Device->isSimulation())
222 handshakeResult = Handshake();
223
224 else
225 {
226 handshakeResult = false;
227 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]?)$");
228 const auto isIPv4 = regex_match(hostname, ipv4);
229
230 // Establish connection to host:port
231 if (establishConnection(hostname, port))
232 {
234 LOGF_DEBUG("Connection to %s@%s is successful, attempting handshake...", hostname.c_str(), port.c_str());
235 handshakeResult = Handshake();
236
237 // Auto search is disabled.
238 if (handshakeResult == false && LANSearchSP[INDI::DefaultDevice::INDI_ENABLED].getState() == ISS_OFF)
239 {
240 LOG_DEBUG("Handshake failed.");
241 return false;
242 }
243 }
244
245 // If connection failed; or
246 // handshake failed; then we can search LAN if the IP address is v4
247 // LAN search is enabled.
248 if (handshakeResult == false &&
250 isIPv4)
251 {
252 size_t found = hostname.find_last_of(".");
253
254 if (found != std::string::npos)
255 {
256 // Get the source subnet
257 const auto sourceSubnet = hostname.substr(0, found);
258 std::deque<std::string> subnets;
259
260 // Get all interface IPv4 addresses. From there we extract subnets
261 auto addrs_ipv4 = gmlc::netif::getInterfaceAddressesV4();
262 for (auto &oneInterfaceAddress : addrs_ipv4)
263 {
264 // Skip local IPs
265 if (oneInterfaceAddress.rfind("127", 0) == 0)
266 continue;
267
268 size_t found = oneInterfaceAddress.find_last_of(".");
269 if (found != std::string::npos)
270 {
271 // Extract target subnect
272 const auto targetSubnet = oneInterfaceAddress.substr(0, found);
273 // Prefer subnets matching source subnet
274 if (targetSubnet == sourceSubnet)
275 subnets.push_front(targetSubnet);
276 else
277 subnets.push_back(targetSubnet);
278
279 }
280 }
281
282 for (auto &oneSubnet : subnets)
283 {
284 LOGF_INFO("Searching %s subnet, this operation will take a few minutes to complete. Stand by...", oneSubnet.c_str());
285 // Brute force search through all subnet
286 // N.B. This operation cannot be interrupted.
287 // TODO Must add a method to abort the search.
288 for (int i = 1; i < 255; i++)
289 {
290 const auto newAddress = oneSubnet + "." + std::to_string(i);
291 if (newAddress == hostname)
292 continue;
293
294 if (establishConnection(newAddress, port, 1))
295 {
297 LOGF_DEBUG("Connection to %s@%s is successful, attempting handshake...", hostname.c_str(), port.c_str());
298 handshakeResult = Handshake();
299 if (handshakeResult)
300 {
301 hostname = newAddress;
302 break;
303 }
304 }
305 }
306
307 if (handshakeResult)
308 break;
309 }
310 }
311 }
312 }
313
314 if (handshakeResult)
315 {
316 LOGF_INFO("%s is online.", getDeviceName());
317 AddressTP[0].setText(hostname);
318
319 if (m_ConfigHost != AddressTP[0].getText() || m_ConfigPort != AddressTP[1].getText())
324 {
328 }
329 }
330 else
331 LOG_DEBUG("Handshake failed.");
332
333 return handshakeResult;
334}
335
340{
341 if (m_SockFD > 0)
342 {
343 close(m_SockFD);
344 m_SockFD = PortFD = -1;
345 }
346
347 return true;
348}
349
362
375
379bool TCP::saveConfigItems(FILE * fp)
380{
381 if (m_Permission != IP_RO)
382 {
383 AddressTP.save(fp);
384 TcpUdpSP.save(fp);
385 LANSearchSP.save(fp);
386 }
387
388 return true;
389}
390
394void TCP::setDefaultHost(const char *addressHost)
395{
396 if (m_ConfigHost.empty())
397 AddressTP[0].setText(addressHost);
400}
401
405void TCP::setDefaultPort(uint32_t addressPort)
406{
407 if (m_ConfigPort.empty())
408 AddressTP[1].setText(std::to_string(addressPort));
411}
412
417{
419 {
420 TcpUdpSP.reset();
422 }
424 TcpUdpSP.apply();
425}
426
437}
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)
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
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)
INDI::PropertyText AddressTP
Properties.
virtual bool Connect() override
Connect Connect to device via the implemented communication medium. Do not perform any handshakes.
INDI::PropertySwitch LANSearchSP
TCP(INDI::DefaultDevice *dev, IPerm permission=IP_RW)
INDI::PropertySwitch TcpUdpSP
std::string m_ConfigHost
std::string m_ConfigPort
virtual uint32_t port() const
virtual bool saveConfigItems(FILE *fp) override
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
virtual void Deactivated() override
Deactivated Function called by the framework when the plugin is deactivated. It is usually used to de...
const char * getDeviceName() const
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
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
void save(FILE *f) const
save Save property to configuration file.
const char * getName() const
bool isNameMatch(const char *otherName) const
bool update(const ISState states[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
bool update(const char *const texts[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
INDI::PropertyViewText * getText() const
int errno
ISState
Switch state.
Definition indiapi.h:148
@ ISS_OFF
Definition indiapi.h:149
@ ISS_ON
Definition indiapi.h:150
IPerm
Permission hint, with respect to client.
Definition indiapi.h:181
@ IP_RW
Definition indiapi.h:184
@ IP_RO
Definition indiapi.h:182
@ IPS_IDLE
Definition indiapi.h:159
@ IPS_OK
Definition indiapi.h:160
@ ISR_1OFMANY
Definition indiapi.h:171
#define MAXINDINAME
Definition indiapi.h:189
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:908
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:719
#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
uint16_t type
Definition pwc-ioctl.h:0