/* * The MySensors Arduino library handles the wireless radio link and protocol * between your home built sensors/actuators and HA controller of choice. * The sensors forms a self healing radio network with optional repeaters. Each * repeater and gateway builds a routing tables in EEPROM which keeps track of the * network topology allowing messages to be routed to nodes. * * Created by Henrik Ekblad * Copyright (C) 2013-2018 Sensnology AB * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors * * Documentation: http://www.mysensors.org * Support Forum: http://forum.mysensors.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * Based on Arduino ethernet library, Copyright (c) 2010 Arduino LLC. All right reserved. */ #include "EthernetClient.h" #include #include #include #include #include #include #include #include #include #include #include #include "log.h" EthernetClient::EthernetClient() : _sock(-1) { } EthernetClient::EthernetClient(int sock) : _sock(sock) { } int EthernetClient::connect(const char* host, uint16_t port) { struct addrinfo hints, *servinfo, *localinfo, *p; int rv; char s[INET6_ADDRSTRLEN]; char port_str[6]; bool use_bind = (_srcip != 0); close(); memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; sprintf(port_str, "%hu", port); if ((rv = getaddrinfo(host, port_str, &hints, &servinfo)) != 0) { logError("getaddrinfo: %s\n", gai_strerror(rv)); return -1; } if (use_bind) { if ((rv = getaddrinfo(_srcip.toString().c_str(), port_str, &hints, &localinfo)) != 0) { logError("getaddrinfo: %s\n", gai_strerror(rv)); return -1; } } // loop through all the results and connect to the first we can for (p = servinfo; p != NULL; p = p->ai_next) { if ((_sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { logError("socket: %s\n", strerror(errno)); continue; } if (use_bind) { if (::bind(_sock, localinfo->ai_addr, localinfo->ai_addrlen) == -1) { close(); logError("bind: %s\n", strerror(errno)); return -1; } } if (::connect(_sock, p->ai_addr, p->ai_addrlen) == -1) { close(); logError("connect: %s\n", strerror(errno)); continue; } break; } if (p == NULL) { logError("failed to connect\n"); return -1; } void *addr = &(((struct sockaddr_in*)p->ai_addr)->sin_addr); inet_ntop(p->ai_family, addr, s, sizeof s); logDebug("connected to %s\n", s); freeaddrinfo(servinfo); // all done with this structure if (use_bind) { freeaddrinfo(localinfo); // all done with this structure } return 1; } int EthernetClient::connect(IPAddress ip, uint16_t port) { return connect(ip.toString().c_str(), port); } size_t EthernetClient::write(uint8_t b) { return write(&b, 1); } size_t EthernetClient::write(const uint8_t *buf, size_t size) { int bytes = 0; if (_sock == -1) { return 0; } while (size > 0) { int rc = send(_sock, buf + bytes, size, MSG_NOSIGNAL | MSG_DONTWAIT); if (rc == -1) { logError("send: %s\n", strerror(errno)); close(); break; } bytes += rc; size -= rc; } return bytes; } size_t EthernetClient::write(const char *str) { if (str == NULL) { return 0; } return write((const uint8_t *)str, strlen(str)); } size_t EthernetClient::write(const char *buffer, size_t size) { return write((const uint8_t *)buffer, size); } int EthernetClient::available() { int count = 0; if (_sock != -1) { ioctl(_sock, SIOCINQ, &count); } return count; } int EthernetClient::read() { uint8_t b; if ( recv(_sock, &b, 1, MSG_DONTWAIT) > 0 ) { // recv worked return b; } else { // No data available return -1; } } int EthernetClient::read(uint8_t *buf, size_t bytes) { return recv(_sock, buf, bytes, MSG_DONTWAIT); } int EthernetClient::peek() { uint8_t b; if (recv(_sock, &b, 1, MSG_PEEK | MSG_DONTWAIT) > 0) { return b; } else { return -1; } } void EthernetClient::flush() { int count = 0; if (_sock != -1) { while (true) { ioctl(_sock, SIOCOUTQ, &count); if (count == 0) { return; } usleep(1000); } } } void EthernetClient::stop() { if (_sock == -1) { return; } // attempt to close the connection gracefully (send a FIN to other side) shutdown(_sock, SHUT_RDWR); timeval startTime, curTime; gettimeofday(&startTime, NULL); // wait up to a second for the connection to close do { uint8_t s = status(); if (s == ETHERNETCLIENT_W5100_CLOSED) { break; // exit the loop } usleep(1000); gettimeofday(&curTime, NULL); } while (((curTime.tv_sec - startTime.tv_sec) * 1000000) + (curTime.tv_usec - startTime.tv_usec) < 1000000); // free up the socket descriptor ::close(_sock); _sock = -1; } uint8_t EthernetClient::status() { if (_sock == -1) { return ETHERNETCLIENT_W5100_CLOSED; } struct tcp_info tcp_info; int tcp_info_length = sizeof(tcp_info); if ( getsockopt( _sock, SOL_TCP, TCP_INFO, (void *)&tcp_info, (socklen_t *)&tcp_info_length ) == 0 ) { switch (tcp_info.tcpi_state) { case TCP_ESTABLISHED: return ETHERNETCLIENT_W5100_ESTABLISHED; case TCP_SYN_SENT: return ETHERNETCLIENT_W5100_SYNSENT; case TCP_SYN_RECV: return ETHERNETCLIENT_W5100_SYNRECV; case TCP_FIN_WAIT1: case TCP_FIN_WAIT2: return ETHERNETCLIENT_W5100_FIN_WAIT; case TCP_TIME_WAIT: return TCP_TIME_WAIT; case TCP_CLOSE: return ETHERNETCLIENT_W5100_CLOSED; case TCP_CLOSE_WAIT: return ETHERNETCLIENT_W5100_CLOSING; case TCP_LAST_ACK: return ETHERNETCLIENT_W5100_LAST_ACK; case TCP_LISTEN: return ETHERNETCLIENT_W5100_LISTEN; case TCP_CLOSING: return ETHERNETCLIENT_W5100_CLOSING; } } return ETHERNETCLIENT_W5100_CLOSED; } uint8_t EthernetClient::connected() { return status() == ETHERNETCLIENT_W5100_ESTABLISHED || available(); } void EthernetClient::close() { if (_sock != -1) { ::close(_sock); _sock = -1; } } void EthernetClient::bind(IPAddress ip) { _srcip = ip; } int EthernetClient::getSocketNumber() { return _sock; } // the next function allows us to use the client returned by // EthernetServer::available() as the condition in an if-statement. EthernetClient::operator bool() { return _sock != -1; } bool EthernetClient::operator==(const EthernetClient& rhs) { return _sock == rhs._sock && _sock != -1 && rhs._sock != -1; }