add mdns discovery and make it the default
This commit is contained in:
@@ -14,9 +14,11 @@
|
||||
* add option to inject start-up commands
|
||||
* add verbose raw data option to "read" and "write" commands
|
||||
* add option to allow write direction in "read" command when inline defining a new message
|
||||
* add option to discover device via mDNS
|
||||
|
||||
## Breaking Changes
|
||||
* change default config path to https://ebus.github.io/ serving files generated from new TypeSpec message definition sources
|
||||
* change default device connection to be resolved automatically via mDNS
|
||||
|
||||
|
||||
# 23.3 (2023-12-26)
|
||||
|
||||
@@ -24,9 +24,10 @@ The main features of the daemon are:
|
||||
* TCP
|
||||
* UDP
|
||||
* enhanced ebusd protocol allowing arbitration to be done directly by the hardware, e.g. for recent
|
||||
* [eBUS Adapter Shield](https://adapter.ebusd.eu/v5/),
|
||||
* [eBUS Adapter Shields C6](https://adapter.ebusd.eu/v5-c6/) and [v5](https://adapter.ebusd.eu/v5/),
|
||||
* [adapter v3.1](https://adapter.ebusd.eu/v31)/[v3.0](https://adapter.ebusd.eu/v3), or
|
||||
* [ebusd-esp firmware](https://github.com/john30/ebusd-esp/)
|
||||
* auto-discover device connection via mDNS
|
||||
* actively send messages to and receive answers from the eBUS
|
||||
* passively listen to messages sent on the eBUS
|
||||
* answer to messages received from the eBUS
|
||||
|
||||
+45
-2
@@ -23,6 +23,7 @@
|
||||
#include "ebusd/main.h"
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <csignal>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
@@ -34,6 +35,7 @@
|
||||
#include "ebusd/network.h"
|
||||
#include "lib/utils/log.h"
|
||||
#include "lib/utils/httpclient.h"
|
||||
#include "lib/utils/tcpsocket.h"
|
||||
#include "ebusd/scan.h"
|
||||
|
||||
namespace ebusd {
|
||||
@@ -411,11 +413,52 @@ int main(int argc, char* argv[], char* envp[]) {
|
||||
return overallResult == RESULT_OK ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
const char* device = s_opt.device;
|
||||
if (!s_opt.checkConfig && strncmp(device, "mdns:", 5) == 0) {
|
||||
// auto discovery
|
||||
mdns_oneshot_t address;
|
||||
#define MAX_ADDRESSES 10
|
||||
mdns_oneshot_t addresses[MAX_ADDRESSES];
|
||||
size_t otherCount = MAX_ADDRESSES;
|
||||
logWrite(lf_main, ll_notice, "discovering device from \"%s\"", device);
|
||||
int ret = 0;
|
||||
for (int i=0; i < 3 && ret == 0; i++) { // 3*up to 5 seconds = 15 seconds max
|
||||
ret = resolveMdnsOneShot(device+5, &address, addresses, &otherCount);
|
||||
}
|
||||
if (ret < 0) {
|
||||
logWrite(lf_main, ll_error, "unable to discover device \"%s\", error %d", device, ret);
|
||||
cleanup();
|
||||
return EINVAL;
|
||||
}
|
||||
const char *ip;
|
||||
for (size_t pos=0; pos < otherCount; pos++) {
|
||||
mdns_oneshot_t *addr = addresses+pos;
|
||||
ip = inet_ntoa(addr->address);
|
||||
logWrite(lf_main, ll_info, "discovered another device with ID %s and device string %s:%s",
|
||||
addr->id, addr->proto, ip);
|
||||
}
|
||||
if (ret == 0) {
|
||||
logWrite(lf_main, ll_error, "unable to discover device \"%s\", %s found", device, otherCount ? "ID not" : "none");
|
||||
cleanup();
|
||||
return EINVAL;
|
||||
}
|
||||
if (ret > 1) {
|
||||
logWrite(lf_main, ll_notice,
|
||||
"found several devices from \"%s\", better limit to the desired one using e.g. \"mdns:%s\"", device,
|
||||
address.id);
|
||||
}
|
||||
ip = inet_ntoa(address.address);
|
||||
char *mdnsDevice = reinterpret_cast<char*>(malloc(4*4+3+1)); // ens:xxx.xxx.xxx.xxx
|
||||
snprintf(mdnsDevice, 4*4+3+1, "%s:%s", address.proto, ip);
|
||||
device = mdnsDevice;
|
||||
logWrite(lf_main, ll_notice, "using discovered device with ID %s and device string %s", address.id, device);
|
||||
}
|
||||
|
||||
s_busHandler = new BusHandler(s_messageMap, s_scanHelper, s_opt.pollInterval);
|
||||
|
||||
// create the protocol and open the device
|
||||
ebus_protocol_config_t config = {
|
||||
.device = s_opt.device,
|
||||
.device = device,
|
||||
.noDeviceCheck = s_opt.noDeviceCheck,
|
||||
.readOnly = s_opt.readOnly,
|
||||
.extraLatency = s_opt.extraLatency,
|
||||
@@ -431,7 +474,7 @@ int main(int argc, char* argv[], char* envp[]) {
|
||||
};
|
||||
s_protocol = ProtocolHandler::create(config, s_busHandler);
|
||||
if (s_protocol == nullptr) {
|
||||
logWrite(lf_main, ll_error, "unable to create protocol/device %s", s_opt.device); // force logging on exit
|
||||
logWrite(lf_main, ll_error, "unable to create protocol/device %s", config.device); // force logging on exit
|
||||
cleanup();
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ namespace ebusd {
|
||||
|
||||
/** A structure holding all program options. */
|
||||
typedef struct options {
|
||||
const char* device; //!< eBUS device (serial device or [udp:]ip[:port]) [/dev/ttyUSB0]
|
||||
const char* device; //!< eBUS device (serial device or mdns:[id] or [udp:]ip[:port]) [mdns:]
|
||||
bool noDeviceCheck; //!< skip serial eBUS device test
|
||||
bool readOnly; //!< read-only access to the device
|
||||
bool initialSend; //!< send an initial escape symbol after connecting device
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace ebusd {
|
||||
|
||||
/** the default program options. */
|
||||
static const options_t s_default_opt = {
|
||||
.device = "/dev/ttyUSB0",
|
||||
.device = "mdns:",
|
||||
.noDeviceCheck = false,
|
||||
.readOnly = false,
|
||||
.initialSend = false,
|
||||
@@ -134,11 +134,14 @@ static string s_configPath = CONFIG_PATH;
|
||||
static const argDef argDefs[] = {
|
||||
{nullptr, 0, nullptr, 0, "Device options:"},
|
||||
{"device", 'd', "DEV", 0, "Use DEV as eBUS device ("
|
||||
"\"mdns:\" for auto discovery via mDNS ("
|
||||
"optional suffix with specific HW ID as well as specific IP interface after '@', "
|
||||
"otherwise: "
|
||||
"prefix \"ens:\" for enhanced high speed device or "
|
||||
"\"enh:\" for enhanced device, with "
|
||||
"\"IP[:PORT]\" for network device or "
|
||||
"\"DEVICE\" for serial device"
|
||||
") [/dev/ttyUSB0]"},
|
||||
") [mdns:]"},
|
||||
{"nodevicecheck", 'n', nullptr, 0, "Skip serial eBUS device test"},
|
||||
{"readonly", 'r', nullptr, 0, "Only read from device, never write to it"},
|
||||
{"initsend", O_INISND, nullptr, 0, "Send an initial escape symbol after connecting device"},
|
||||
@@ -232,7 +235,7 @@ static int parse_opt(int key, char *arg, const argParseOpt *parseOpt, struct opt
|
||||
|
||||
switch (key) {
|
||||
// Device options:
|
||||
case 'd': // --device=/dev/ttyUSB0
|
||||
case 'd': // --device=mdns:
|
||||
if (arg == nullptr || arg[0] == 0) {
|
||||
argParseError(parseOpt, "invalid device");
|
||||
return EINVAL;
|
||||
|
||||
@@ -325,7 +325,7 @@ void SerialTransport::checkDevice() {
|
||||
}
|
||||
|
||||
result_t NetworkTransport::openInternal() {
|
||||
m_fd = socketConnect(m_hostOrIp, m_port, m_udp, nullptr, 5, 2); // wait up to 5 seconds for established connection
|
||||
m_fd = socketConnect(m_hostOrIp, m_port, m_udp ? IPPROTO_UDP : 0, nullptr, 5, 2); // wait up to 5 seconds for established connection
|
||||
if (m_fd < 0) {
|
||||
return RESULT_ERR_GENERIC_IO;
|
||||
}
|
||||
|
||||
+6
-93
@@ -40,6 +40,7 @@
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include "lib/knx/knx.h"
|
||||
#include "lib/utils/tcpsocket.h"
|
||||
|
||||
namespace ebusd {
|
||||
|
||||
@@ -228,7 +229,7 @@ typedef struct __attribute__ ((packed)) {
|
||||
#define SYSTEM_MULTICAST_PORT 3671
|
||||
|
||||
// the default system multicast address 224.0.23.12
|
||||
#define SYSTEM_MULTICAST_IP 0xe000170c
|
||||
#define SYSTEM_MULTICAST_IP_STR "224.0.23.12"
|
||||
|
||||
#define LAST_FRAME_TIMEOUT 2
|
||||
|
||||
@@ -400,96 +401,17 @@ class KnxNetConnection : public KnxConnection {
|
||||
// @copydoc
|
||||
const char* open() override {
|
||||
close();
|
||||
int ret;
|
||||
struct in_addr mcast = {};
|
||||
mcast.s_addr = htonl(SYSTEM_MULTICAST_IP);
|
||||
m_interface.s_addr = INADDR_ANY;
|
||||
m_port = SYSTEM_MULTICAST_PORT;
|
||||
if (m_url && m_url[0]) { // non-empty
|
||||
string urlStr = m_url; // "[mcast][@intf]" for non-default 224.0.23.12:3671)
|
||||
if (!urlStr.empty()) {
|
||||
auto pos = urlStr.find('@');
|
||||
if (pos != string::npos) {
|
||||
string intfStr = urlStr.substr(pos+1);
|
||||
const char* intfCstr = intfStr.c_str();
|
||||
ret = inet_aton(intfCstr, &m_interface);
|
||||
if (ret == 0) {
|
||||
return "intf addr";
|
||||
}
|
||||
urlStr = urlStr.substr(0, pos);
|
||||
}
|
||||
}
|
||||
if (!urlStr.empty()) {
|
||||
const char *mcastStr = urlStr.c_str();
|
||||
ret = inet_aton(mcastStr, &mcast);
|
||||
if (ret == 0) {
|
||||
return "multicast addr";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sockaddr_in address = {};
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_port = htons(m_port);
|
||||
address.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
int fd = socketConnect(m_url && m_url[0] ? m_url : SYSTEM_MULTICAST_IP_STR,
|
||||
SYSTEM_MULTICAST_PORT, IPPROTO_UDP, nullptr, 0x02);
|
||||
if (fd < 0) {
|
||||
return "create socket";
|
||||
}
|
||||
|
||||
// set non-blocking
|
||||
ret = fcntl(fd, F_SETFL, O_NONBLOCK);
|
||||
if (ret != 0) {
|
||||
if (fcntl(fd, F_SETFL, O_NONBLOCK) != 0) {
|
||||
::close(fd);
|
||||
return "non-blocking";
|
||||
}
|
||||
|
||||
// set reuse address option
|
||||
int optint = 1;
|
||||
ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optint, sizeof(optint));
|
||||
if (ret != 0) {
|
||||
::close(fd);
|
||||
return "reuse";
|
||||
}
|
||||
|
||||
// allow multiple processes using the same port for multicast on the same host
|
||||
unsigned char optchar = 1;
|
||||
ret = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &optchar, sizeof(optchar));
|
||||
if (ret != 0) {
|
||||
::close(fd);
|
||||
return "mcast loop";
|
||||
}
|
||||
|
||||
if (m_interface.s_addr != INADDR_ANY) {
|
||||
// set outgoing interface to other than default (determined by routing table)
|
||||
ret = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &m_interface, sizeof(m_interface));
|
||||
if (ret != 0) {
|
||||
::close(fd);
|
||||
return "mcast intf";
|
||||
}
|
||||
}
|
||||
|
||||
// bind for incoming multicast
|
||||
ret = bind(fd, (struct sockaddr*) &address, sizeof(address));
|
||||
if (ret != 0) {
|
||||
::close(fd);
|
||||
return "bind socket";
|
||||
}
|
||||
|
||||
// set the target address for later use by sendto()
|
||||
m_multicast = address;
|
||||
m_multicast.sin_addr = mcast;
|
||||
|
||||
// join the multicast inbound
|
||||
ip_mreq req = {};
|
||||
req.imr_multiaddr = mcast;
|
||||
req.imr_interface = m_interface;
|
||||
if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &req, sizeof(req)) < 0) {
|
||||
::close(fd);
|
||||
return "join multicast";
|
||||
}
|
||||
|
||||
m_sock = fd;
|
||||
return nullptr;
|
||||
}
|
||||
@@ -674,7 +596,7 @@ class KnxNetConnection : public KnxConnection {
|
||||
}
|
||||
d[0] = tpci;
|
||||
logTelegram(true, c, l, d);
|
||||
ssize_t sent = sendto(m_sock, buf, totalLen, MSG_NOSIGNAL, (sockaddr*)&m_multicast, sizeof(m_multicast));
|
||||
ssize_t sent = ::send(m_sock, buf, totalLen, MSG_NOSIGNAL);
|
||||
if (sent < 0) {
|
||||
return "send error";
|
||||
}
|
||||
@@ -711,15 +633,6 @@ class KnxNetConnection : public KnxConnection {
|
||||
/** the URL to connect to. */
|
||||
const char* m_url;
|
||||
|
||||
/** the multicast address to join. */
|
||||
struct sockaddr_in m_multicast;
|
||||
|
||||
/** the port to listen to. */
|
||||
in_port_t m_port;
|
||||
|
||||
/** the optional interface address to bind to. */
|
||||
struct in_addr m_interface;
|
||||
|
||||
/** the socket if connected, or 0. */
|
||||
int m_sock;
|
||||
|
||||
|
||||
+433
-35
@@ -46,38 +46,91 @@ bool TCPSocket::isValid() {
|
||||
return fcntl(m_sfd, F_GETFL) != -1;
|
||||
}
|
||||
|
||||
bool parseIp(const char* server, struct in_addr *sin_addr) {
|
||||
if (inet_aton(server, sin_addr) == 1) {
|
||||
return true;
|
||||
}
|
||||
struct hostent* he = gethostbyname(server);
|
||||
if (he == nullptr) {
|
||||
return false;
|
||||
}
|
||||
memcpy(sin_addr, he->h_addr_list[0], he->h_length);
|
||||
return true;
|
||||
}
|
||||
|
||||
int socketConnect(const char* server, uint16_t port, bool udp, socketaddress* storeAddress, int tcpConnectTimeout,
|
||||
int tcpKeepAliveInterval) {
|
||||
int socketConnect(const char* server, uint16_t port, int udpProto, socketaddress* storeAddress,
|
||||
int tcpConnToUdpOptions, int tcpKeepAliveInterval, struct in_addr* storeIntf) {
|
||||
socketaddress localAddress;
|
||||
socketaddress* address = storeAddress ? storeAddress : &localAddress;
|
||||
memset(reinterpret_cast<char*>(address), 0, sizeof(*address));
|
||||
|
||||
if (inet_aton(server, &address->sin_addr) == 0) {
|
||||
struct hostent* he = gethostbyname(server);
|
||||
if (he == nullptr) {
|
||||
// parse "address[@intf]"
|
||||
const char* pos = strchr(server, '@');
|
||||
struct in_addr intf;
|
||||
intf.s_addr = INADDR_ANY;
|
||||
if (pos) {
|
||||
char* str = strdupa(server);
|
||||
char* ifa = strchr(str, '@');
|
||||
ifa[0] = 0;
|
||||
ifa++;
|
||||
if (!str[0] || !parseIp(str, &address->sin_addr)) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(&address->sin_addr, he->h_addr_list[0], he->h_length);
|
||||
if (!parseIp(ifa, &intf)) {
|
||||
return -1;
|
||||
}
|
||||
} else if (!parseIp(server, &address->sin_addr)) {
|
||||
return -1;
|
||||
}
|
||||
if (storeIntf) {
|
||||
*storeIntf = intf;
|
||||
}
|
||||
address->sin_family = AF_INET;
|
||||
address->sin_port = (in_port_t)htons(port);
|
||||
|
||||
int sfd = socket(AF_INET, udp ? SOCK_DGRAM : SOCK_STREAM, 0);
|
||||
int sfd = socket(AF_INET, udpProto ? SOCK_DGRAM : SOCK_STREAM, udpProto);
|
||||
if (sfd < 0) {
|
||||
return -1;
|
||||
return -2;
|
||||
}
|
||||
int ret;
|
||||
if (udp) {
|
||||
int ret = 0;
|
||||
if (udpProto) {
|
||||
#define RET(chk, next) if (ret >= 0) { ret = chk; if (ret < 0) ret = next;}
|
||||
struct sockaddr_in bindAddress = *address;
|
||||
bindAddress.sin_addr.s_addr = INADDR_ANY;
|
||||
ret = bind(sfd, (struct sockaddr*)&bindAddress, sizeof(bindAddress));
|
||||
// allow multiple processes using the same port for multicast on the same host
|
||||
int optint = 1;
|
||||
RET(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &optint, sizeof(optint)), -3);
|
||||
#ifdef SO_REUSEPORT
|
||||
RET(setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &optint, sizeof(optint)), -3);
|
||||
#endif
|
||||
bool isMcast = IN_MULTICAST(ntohl(address->sin_addr.s_addr));
|
||||
if (isMcast) {
|
||||
// loop-back sent multicast packets
|
||||
unsigned char optchar = 1;
|
||||
RET(setsockopt(sfd, IPPROTO_IP, IP_MULTICAST_LOOP, &optchar, sizeof(optchar)), -3);
|
||||
if (ret >= 0) {
|
||||
ret = ::connect(sfd, (struct sockaddr*)address, sizeof(*address));
|
||||
// join the multicast inbound
|
||||
ip_mreq req = {};
|
||||
req.imr_multiaddr = address->sin_addr;
|
||||
req.imr_interface = intf;
|
||||
RET(setsockopt(sfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &req, sizeof(req)), -7);
|
||||
}
|
||||
if (ret >= 0 && intf.s_addr != INADDR_ANY) {
|
||||
// set outgoing interface to other than default (determined by routing table)
|
||||
RET(setsockopt(sfd, IPPROTO_IP, IP_MULTICAST_IF, &intf, sizeof(intf)), -3);
|
||||
}
|
||||
}
|
||||
bindAddress.sin_addr = intf;
|
||||
if (!(tcpConnToUdpOptions&0x01)) {
|
||||
bindAddress.sin_port = 0; // do not bind to same source port for outgoing packets
|
||||
}
|
||||
RET(bind(sfd, (struct sockaddr*)&bindAddress, sizeof(bindAddress)), -4);
|
||||
if (tcpConnToUdpOptions&0x02) {
|
||||
// set the default target address for later use by send()
|
||||
RET(::connect(sfd, (struct sockaddr*)address, sizeof(*address)), -5);
|
||||
if (ret < 0) {
|
||||
close(sfd);
|
||||
return -1;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return sfd;
|
||||
}
|
||||
@@ -85,7 +138,7 @@ int tcpKeepAliveInterval) {
|
||||
ret = setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<void*>(&value), sizeof(value));
|
||||
if (ret < 0) {
|
||||
close(sfd);
|
||||
return -1;
|
||||
return -3;
|
||||
}
|
||||
if (tcpKeepAliveInterval > 0) {
|
||||
value = 1;
|
||||
@@ -124,24 +177,40 @@ int tcpKeepAliveInterval) {
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (tcpConnectTimeout > 0 && fcntl(sfd, F_SETFL, O_NONBLOCK) < 0) { // set non-blocking
|
||||
if (tcpConnToUdpOptions > 0 && fcntl(sfd, F_SETFL, O_NONBLOCK) < 0) { // set non-blocking
|
||||
close(sfd);
|
||||
return -1;
|
||||
return -4;
|
||||
}
|
||||
ret = ::connect(sfd, (struct sockaddr*)address, sizeof(*address));
|
||||
if (ret != 0) {
|
||||
if (ret < 0 && (tcpConnectTimeout <= 0 || errno != EINPROGRESS)) {
|
||||
if (ret < 0 && (tcpConnToUdpOptions <= 0 || errno != EINPROGRESS)) {
|
||||
close(sfd);
|
||||
return -1;
|
||||
return -5;
|
||||
}
|
||||
if (tcpConnectTimeout > 0) {
|
||||
if (tcpConnToUdpOptions > 0) {
|
||||
ret = socketPoll(sfd, POLLIN|POLLOUT, tcpConnToUdpOptions);
|
||||
if (ret <= 0) {
|
||||
close(sfd);
|
||||
return -6;
|
||||
}
|
||||
if (fcntl(sfd, F_SETFL, 0) < 0) { // set blocking again
|
||||
close(sfd);
|
||||
return -4;
|
||||
}
|
||||
}
|
||||
}
|
||||
return sfd;
|
||||
}
|
||||
|
||||
int socketPoll(int sfd, int which, int timeoutSeconds) {
|
||||
int ret;
|
||||
#if defined(HAVE_PPOLL) || defined(HAVE_PSELECT)
|
||||
struct timespec tdiff;
|
||||
tdiff.tv_sec = tcpConnectTimeout;
|
||||
tdiff.tv_sec = timeoutSeconds;
|
||||
tdiff.tv_nsec = 0;
|
||||
#else
|
||||
struct timeval tdiff;
|
||||
tdiff.tv_sec = tcpConnectTimeout;
|
||||
tdiff.tv_sec = timeoutSeconds;
|
||||
tdiff.tv_usec = 0;
|
||||
#endif
|
||||
#ifdef HAVE_PPOLL
|
||||
@@ -149,18 +218,24 @@ int tcpKeepAliveInterval) {
|
||||
struct pollfd fds[nfds];
|
||||
memset(fds, 0, sizeof(fds));
|
||||
fds[0].fd = sfd;
|
||||
fds[0].events = POLLIN|POLLOUT;
|
||||
fds[0].events = which;
|
||||
ret = ppoll(fds, nfds, &tdiff, nullptr);
|
||||
if (ret == 1 && fds[0].revents & POLLERR) {
|
||||
if (ret >= 1 && fds[0].revents & POLLERR) {
|
||||
ret = -1;
|
||||
} else if (ret >= 1) {
|
||||
ret = fds[0].revents;
|
||||
}
|
||||
#else
|
||||
fd_set readfds, writefds, exceptfds;
|
||||
FD_ZERO(&readfds);
|
||||
FD_ZERO(&writefds);
|
||||
FD_ZERO(&exceptfds);
|
||||
if (which & POLLIN) {
|
||||
FD_SET(sfd, &readfds);
|
||||
}
|
||||
if (which & POLLOUT) {
|
||||
FD_SET(sfd, &writefds);
|
||||
}
|
||||
FD_SET(sfd, &exceptfds);
|
||||
#ifdef HAVE_PSELECT
|
||||
ret = pselect(sfd + 1, &readfds, &writefds, &exceptfds, &tdiff, nullptr);
|
||||
@@ -169,19 +244,11 @@ int tcpKeepAliveInterval) {
|
||||
#endif
|
||||
if (ret >= 1 && FD_ISSET(sfd, &exceptfds)) {
|
||||
ret = -1;
|
||||
} else if (ret >= 1) {
|
||||
ret = (FD_ISSET(sfd, &readfds) ? POLLIN : 0) | (FD_ISSET(sfd, &writefds) ? POLLOUT : 0);
|
||||
}
|
||||
#endif
|
||||
if (ret == -1 || ret == 0) {
|
||||
close(sfd);
|
||||
return -1;
|
||||
}
|
||||
if (fcntl(sfd, F_SETFL, 0) < 0) { // set blocking again
|
||||
close(sfd);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return sfd;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -243,4 +310,335 @@ TCPSocket* TCPServer::newSocket() {
|
||||
return new TCPSocket(sfd, &address);
|
||||
}
|
||||
|
||||
size_t readNameRecursive(uint8_t *data, size_t len, size_t pos, size_t maxPos, int maxDepth, char* str, size_t slen,
|
||||
size_t* spos) {
|
||||
size_t nlen = data[pos++];
|
||||
if ((nlen&0xc0) == 0xc0) {
|
||||
// pointer
|
||||
size_t p = ((nlen&0x3f) << 8) | data[pos];
|
||||
if (p >= len || maxDepth < 1) {
|
||||
return 0;
|
||||
}
|
||||
readNameRecursive(data, len, p, len, maxDepth-1, str, slen, spos);
|
||||
return 2;
|
||||
}
|
||||
if (!nlen) {
|
||||
return 1;
|
||||
}
|
||||
if (pos+nlen > maxPos || *spos+1+nlen > slen) {
|
||||
return 0;
|
||||
}
|
||||
if (*spos > 0) {
|
||||
str[*spos] = '.';
|
||||
*spos += 1;
|
||||
}
|
||||
memcpy(str+*spos, data+pos, nlen);
|
||||
*spos += nlen;
|
||||
pos += nlen;
|
||||
size_t add;
|
||||
if (pos >= maxPos || maxDepth < 1) {
|
||||
add = 0;
|
||||
} else {
|
||||
add = readNameRecursive(data, len, pos, maxPos, maxDepth-1, str, slen, spos);
|
||||
if (add == 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1+nlen+add;
|
||||
}
|
||||
|
||||
size_t readName(uint8_t *data, size_t len, size_t pos, size_t maxPos, char* str, size_t slen, size_t* spos) {
|
||||
return readNameRecursive(data, len, pos, maxPos, 4, str, slen, spos);
|
||||
}
|
||||
|
||||
typedef struct __attribute__ ((packed)) {
|
||||
uint16_t id;
|
||||
struct {
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
bool qr: 1; // 0=query, 1=answer
|
||||
uint8_t opcode: 4; // 0=standard query, 1=inverse query, 2=status request
|
||||
bool aa: 1; // authoritive answer
|
||||
bool tc: 1; // truncation
|
||||
bool rd: 1; // recursion desired
|
||||
#else
|
||||
bool rd: 1; // recursion desired
|
||||
bool tc: 1; // truncation
|
||||
bool aa: 1; // authoritive answer
|
||||
uint8_t opcode: 4; // 0=standard query, 1=inverse query, 2=status request
|
||||
bool qr: 1; // 0=query, 1=answer
|
||||
#endif
|
||||
};
|
||||
struct {
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
bool ra: 1; // recursion available
|
||||
uint8_t z: 3; // zero
|
||||
uint8_t rcode: 4; // response code: 0=OK
|
||||
#else
|
||||
uint8_t rcode: 4; // response code: 0=OK
|
||||
uint8_t z: 3; // zero
|
||||
bool ra: 1; // recursion available
|
||||
#endif
|
||||
};
|
||||
uint16_t qdCount; // question section entry count
|
||||
uint16_t anCount; // answer section entry count
|
||||
uint16_t nsCount; // name server section entry count
|
||||
uint16_t arCount; // additional records section entry count
|
||||
} dns_query_t;
|
||||
|
||||
typedef struct __attribute__ ((packed)) {
|
||||
uint8_t len;
|
||||
// unsigned char *name;
|
||||
} dns_qname_t;
|
||||
|
||||
typedef struct __attribute__ ((packed)) {
|
||||
dns_qname_t qname;
|
||||
uint16_t qtype;
|
||||
uint16_t qclass; // top bit used for unicast-response
|
||||
} dns_question_t;
|
||||
|
||||
#define DNS_TYPE_A 0x01
|
||||
#define DNS_TYPE_PTR 0x0c
|
||||
#define DNS_TYPE_TXT 0x10
|
||||
#define DNS_TYPE_SRV 0x21
|
||||
#define DNS_CLASS_AA 0x01
|
||||
|
||||
typedef struct __attribute__ ((packed)) {
|
||||
dns_qname_t aname;
|
||||
uint16_t atype;
|
||||
uint16_t aclass;
|
||||
uint32_t ttl;
|
||||
uint16_t rdLength;
|
||||
// uint8_t *rData;
|
||||
} dns_answer_t;
|
||||
|
||||
typedef struct __attribute__ ((packed)) {
|
||||
uint16_t priority;
|
||||
uint16_t weight;
|
||||
uint16_t port;
|
||||
dns_qname_t target;
|
||||
} dns_rr_srv_t;
|
||||
|
||||
int resolveMdnsOneShot(const char* url, mdns_oneshot_t *result, mdns_oneshot_t *moreResults, size_t *moreCount) {
|
||||
memset(result, 0, sizeof(mdns_oneshot_t));
|
||||
socketaddress address;
|
||||
const char* pos = strchr(url, '@');
|
||||
string limitId = string(url);
|
||||
string device = "224.0.0.251";
|
||||
if (pos) {
|
||||
limitId = limitId.substr(0, pos-url);
|
||||
device += string(pos);
|
||||
}
|
||||
int sock = socketConnect(device.c_str(), 5353, IPPROTO_UDP, &address);
|
||||
if (sock < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t record[1500];
|
||||
memset(record, 0, sizeof(record));
|
||||
dns_query_t *dnsr = reinterpret_cast<dns_query_t*>(record);
|
||||
dnsr->qdCount = htons(1);
|
||||
size_t len = sizeof(dns_query_t);
|
||||
dns_question_t *q = reinterpret_cast<dns_question_t*>(reinterpret_cast<uint8_t*>(dnsr)+len);
|
||||
const uint8_t serviceName[] = {
|
||||
0x06, 0x5f, 0x65, 0x62, 0x75, 0x73, 0x64, // _ebusd
|
||||
0x04, 0x5f, 0x74, 0x63, 0x70, // _tcp
|
||||
0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, // local
|
||||
0x00
|
||||
};
|
||||
memcpy(&q->qname.len, serviceName, sizeof(serviceName));
|
||||
len += sizeof(serviceName)-1; // -1 for final empty qname
|
||||
q = reinterpret_cast<dns_question_t*>(reinterpret_cast<uint8_t*>(dnsr)+len);
|
||||
q->qtype = htons(DNS_TYPE_PTR);
|
||||
q->qclass = htons(
|
||||
0x8000 | // unicast response bit
|
||||
DNS_CLASS_AA);
|
||||
len += sizeof(dns_question_t);
|
||||
ssize_t done = sendto(sock, record, len, 0, reinterpret_cast<sockaddr*>(&address), sizeof(address));
|
||||
#ifdef DEBUG_MDNS
|
||||
printf("mdns: sent %ld, err %d\n", done, errno);
|
||||
#endif
|
||||
fcntl(sock, F_SETFL, O_NONBLOCK);
|
||||
bool found = false, foundMore = false;
|
||||
size_t moreRemain = moreResults && moreCount && *moreCount > 0 ? *moreCount : 0;
|
||||
if (moreRemain > 0) {
|
||||
*moreCount = 0;
|
||||
}
|
||||
#ifdef DEBUG_MDNS
|
||||
socketaddress aaddr;
|
||||
socklen_t aaddrlen = 0;
|
||||
#endif
|
||||
for (int i=0; i < (found ? 3 : 5); i++) { // up to 5 seconds, at least 3 seconds
|
||||
int ret = socketPoll(sock, POLLIN, 1);
|
||||
done = 0;
|
||||
if (ret > 0 && (ret&POLLIN)) {
|
||||
#ifdef DEBUG_MDNS
|
||||
aaddrlen = sizeof(aaddr);
|
||||
done = recvfrom(sock, record, sizeof(record), 0, reinterpret_cast<sockaddr*>(&aaddr), &aaddrlen);
|
||||
#else
|
||||
done = recv(sock, record, sizeof(record), 0);
|
||||
#endif
|
||||
}
|
||||
if (done == 0 || (done < 0 && errno == EAGAIN) || done < sizeof(dns_query_t)) {
|
||||
continue;
|
||||
}
|
||||
dnsr = reinterpret_cast<dns_query_t*>(record);
|
||||
// todo length check
|
||||
#ifdef DEBUG_MDNS
|
||||
printf("mdns: got %d from %2.2x:%d, q=%d, an=%d, ns=%d, ar=%d\n", done, aaddr.sin_addr.s_addr,
|
||||
ntohs(aaddr.sin_port), ntohs(dnsr->qdCount), ntohs(dnsr->anCount), ntohs(dnsr->nsCount),
|
||||
ntohs(dnsr->arCount));
|
||||
#endif
|
||||
if (dnsr->qdCount || done < sizeof(dns_query_t)+sizeof(serviceName)+4*sizeof(dns_answer_t)+(26+2)+4+1+1+
|
||||
sizeof(dns_rr_srv_t)+(2+1+sizeof(mdns_oneshot_t::id)-1+1+5+1+sizeof(mdns_oneshot_t::proto)-1)+4
|
||||
// "eBUS Adapter Shield xxxxxx", "id=xxxxxxxxxxxx.proto=ens"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
int anCnt = ntohs(dnsr->anCount);
|
||||
int arCnt = ntohs(dnsr->arCount);
|
||||
if (anCnt < 1 || dnsr->nsCount || arCnt < 1) {
|
||||
continue;
|
||||
}
|
||||
len = sizeof(dns_query_t);
|
||||
char name[256];
|
||||
bool validPort = false;
|
||||
struct in_addr validAddress;
|
||||
validAddress.s_addr = INADDR_ANY;
|
||||
char id[sizeof(mdns_oneshot_t::id)] = {0};
|
||||
char proto[sizeof(mdns_oneshot_t::proto)] = {0};
|
||||
for (int i=0; i < anCnt+arCnt && len < done; i++) {
|
||||
dns_answer_t *a = reinterpret_cast<dns_answer_t*>(reinterpret_cast<uint8_t*>(dnsr)+len);
|
||||
if (i == 0) {
|
||||
if (memcmp(&a->aname.len, serviceName, sizeof(serviceName)) != 0) {
|
||||
#ifdef DEBUG_MDNS
|
||||
printf("mdns: an 0 mismatch\n");
|
||||
#endif
|
||||
anCnt = 0;
|
||||
break; // skip this one
|
||||
}
|
||||
#ifdef DEBUG_MDNS
|
||||
printf("mdns: an 0 match\n");
|
||||
#endif
|
||||
len += sizeof(serviceName)-1; // -1 for final empty qname
|
||||
} else {
|
||||
// read name
|
||||
size_t pos = 0;
|
||||
size_t nlen = readName(record, done, len, done, name, sizeof(name), &pos);
|
||||
if (nlen == 0) {
|
||||
anCnt = 0;
|
||||
break; // skip this one
|
||||
}
|
||||
len += nlen-1; // -1 for final empty qname / right pointer for below
|
||||
name[pos] = 0;
|
||||
#ifdef DEBUG_MDNS
|
||||
printf("mdns: a%c %d name=%s\n", i >= anCnt ? 'r' : 'n', i >= anCnt ? i-anCnt : i, name);
|
||||
#endif
|
||||
}
|
||||
a = reinterpret_cast<dns_answer_t*>(reinterpret_cast<uint8_t*>(dnsr)+len);
|
||||
int atype = ntohs(a->atype);
|
||||
int aclass = ntohs(a->aclass);
|
||||
#ifdef DEBUG_MDNS
|
||||
printf(" atype %d, aclass %d\n", atype, aclass);
|
||||
#endif
|
||||
if (i == 0 && (atype != DNS_TYPE_PTR
|
||||
|| aclass != DNS_CLASS_AA)) {
|
||||
anCnt = 0;
|
||||
break; // skip this one
|
||||
}
|
||||
len += sizeof(dns_answer_t);
|
||||
int rdLen = ntohs(a->rdLength);
|
||||
#ifdef DEBUG_MDNS
|
||||
printf(" rd %d @%2.2x = ", rdLen, len);
|
||||
for (int i=0; i < rdLen && len+i < done; i++) {
|
||||
printf("%2.2x ", reinterpret_cast<uint8_t*>(dnsr)[len+i]);
|
||||
}
|
||||
printf("\n");
|
||||
#endif
|
||||
if (atype == DNS_TYPE_PTR || atype == DNS_TYPE_TXT) {
|
||||
size_t pos = 0;
|
||||
if (readName(record, done, len, len+rdLen, name, sizeof(name), &pos) == 0) {
|
||||
anCnt = 0;
|
||||
break; // skip this one
|
||||
}
|
||||
name[pos] = 0;
|
||||
#ifdef DEBUG_MDNS
|
||||
printf(" %s=%s\n", (atype == DNS_TYPE_TXT) ? "txt" : "ptr", name);
|
||||
#endif
|
||||
if (atype == DNS_TYPE_TXT && name[0]) {
|
||||
char* sep = strchr(name, '=');
|
||||
char* sep2;
|
||||
if (sep && strncmp(name, "id", sep-name) == 0) {
|
||||
sep2 = strchr(name, '.');
|
||||
if (sep2-sep-1 == sizeof(mdns_oneshot_t::id)-1) {
|
||||
memcpy(id, sep+1, sizeof(mdns_oneshot_t::id)-1);
|
||||
} else {
|
||||
sep = nullptr;
|
||||
}
|
||||
sep = sep ? strchr(sep2+1, '=') : nullptr;
|
||||
}
|
||||
if (sep && strncmp(sep2+1, "proto", sep-sep2-1) == 0 && (
|
||||
pos == sep+1+sizeof(mdns_oneshot_t::proto)-1-name
|
||||
|| strchr(sep+1, '.') == sep+1+sizeof(mdns_oneshot_t::proto)-1)) {
|
||||
memcpy(proto, sep+1, sizeof(mdns_oneshot_t::proto)-1);
|
||||
}
|
||||
}
|
||||
} else if (atype == DNS_TYPE_SRV && rdLen >= sizeof(dns_rr_srv_t)) {
|
||||
dns_rr_srv_t *srv = reinterpret_cast<dns_rr_srv_t*>(record+len);
|
||||
size_t pos = 0;
|
||||
if (readName(record, done, len+sizeof(dns_rr_srv_t)-1, len+rdLen, name, sizeof(name), &pos) == 0) {
|
||||
anCnt = 0;
|
||||
break; // skip this one
|
||||
}
|
||||
name[pos] = 0;
|
||||
validPort = ntohs(srv->port) == 9999;
|
||||
#ifdef DEBUG_MDNS
|
||||
printf(" srv port %d target %s\n", ntohs(srv->port), name);
|
||||
#endif
|
||||
} else if (atype == DNS_TYPE_A) {
|
||||
// ipv4 address
|
||||
#ifdef DEBUG_MDNS
|
||||
printf(" address %d.%d.%d.%d\n", record[len], record[len+1], record[len+2], record[len+3]);
|
||||
#endif
|
||||
memcpy(reinterpret_cast<uint8_t*>(&validAddress.s_addr), record+len, 4);
|
||||
}
|
||||
len += rdLen;
|
||||
}
|
||||
if (!anCnt) {
|
||||
continue;
|
||||
}
|
||||
if (validPort && validAddress.s_addr != INADDR_ANY && validAddress.s_addr != INADDR_NONE && proto[0]) {
|
||||
mdns_oneshot_t *storeTo;
|
||||
if (!found && (!limitId.length() || limitId.compare(id) == 0)) {
|
||||
storeTo = result;
|
||||
found = true;
|
||||
if (limitId.length()) {
|
||||
break; // found the desired one, no need to wait for another
|
||||
}
|
||||
} else if (found && strcmp(id, result->id) == 0) {
|
||||
// skip duplicate answer
|
||||
continue;
|
||||
} else {
|
||||
foundMore = !limitId.length();
|
||||
if (moreRemain > 0) {
|
||||
storeTo = moreResults++;
|
||||
moreRemain--;
|
||||
(*moreCount)++;
|
||||
} else if (!found) {
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
storeTo->address = validAddress;
|
||||
strncpy(storeTo->id, id, sizeof(mdns_oneshot_t::id));
|
||||
strncpy(storeTo->proto, proto, sizeof(mdns_oneshot_t::proto));
|
||||
if (found && moreRemain == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
close(sock);
|
||||
return found ? foundMore ? 2 : 1 : 0;
|
||||
}
|
||||
|
||||
} // namespace ebusd
|
||||
|
||||
@@ -25,6 +25,11 @@
|
||||
#include <sys/time.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#ifdef __FreeBSD__
|
||||
#include <machine/endian.h>
|
||||
#else
|
||||
#include <endian.h>
|
||||
#endif
|
||||
|
||||
/** typedef for referencing @a sockaddr_in within namespace. */
|
||||
typedef struct sockaddr_in socketaddress;
|
||||
@@ -43,16 +48,29 @@ using std::string;
|
||||
|
||||
/**
|
||||
* Connect a TCP or UDP socket.
|
||||
* @param server the server name or ip address to connect to.
|
||||
* @param server the server name or ip address to connect to, optionally
|
||||
* followed by "@intf" to bind to a certain interface address.
|
||||
* @param port the port number.
|
||||
* @param udp true for UDP, false for TCP.
|
||||
* @param udpProto the protocol to use for UDP (e.g. IPPROTO_UDP), or 0 for TCP.
|
||||
* @param storeAddress optional pointer to where the socket address will be stored.
|
||||
* @param tcpConnectTimeout the TCP connect timeout in seconds, or 0.
|
||||
* @param tcpConnectTimeoutUdpOptions the connect timeout in seconds for TCP (or 0),
|
||||
* or a bit set of options for UDP (0x01 for binding to the same source port,
|
||||
* 0x02 for connecting to the target address).
|
||||
* @param tcpKeepAliveInterval optional interval in seconds for sending TCP keepalive.
|
||||
* @param storeIntf optional pointer to where the interface address will be stored.
|
||||
* @return the connected socket file descriptor on success, or -1 on error.
|
||||
*/
|
||||
int socketConnect(const char* server, uint16_t port, bool udp, socketaddress* storeAddress = nullptr,
|
||||
int tcpConnectTimeout = 0, int tcpKeepAliveInterval = 0);
|
||||
int socketConnect(const char* server, uint16_t port, int udpProto, socketaddress* storeAddress = nullptr,
|
||||
int tcpConnectTimeoutUdpOptions = 0, int tcpKeepAliveInterval = 0, struct in_addr* storeIntf = nullptr);
|
||||
|
||||
/**
|
||||
* Poll a socket.
|
||||
* @param sfd the socket file descriptor.
|
||||
* @param which the set of bits of the event(s) to wait for (e.g. POLLIN and/or POLLOUT).
|
||||
* @param timeoutSeconds the poll timeout in seconds.
|
||||
* @return a set of bits indicating the received event (e.g. POLLIN and/or POLLOUT), or -1 on error.
|
||||
*/
|
||||
int socketPoll(int sfd, int which, int timeoutSeconds);
|
||||
|
||||
|
||||
/**
|
||||
@@ -198,6 +216,31 @@ class TCPServer {
|
||||
bool m_listening;
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure for resolving device address via mDNS one-shot query.
|
||||
*/
|
||||
typedef struct __attribute__ ((packed)) {
|
||||
/** the device IP address. */
|
||||
struct in_addr address;
|
||||
/** the device ID. */
|
||||
char id[6*2+1];
|
||||
/** the announced ebusd protocol. */
|
||||
char proto[3+1];
|
||||
} mdns_oneshot_t;
|
||||
|
||||
/**
|
||||
* Use an mDNS one-shot query to resolve an eBUS device.
|
||||
* @param url the desired ID (or empty) followed by an optional host interface IP to use after an '@' sign.
|
||||
* @param result pointer to an mdns_oneshot_t structure to store the result in.
|
||||
* @param moreRequests optional pointer to further results not matching the desired ID.
|
||||
* @param moreCount optional pointer to the size of the moreRequests argument that will be updated with the number of
|
||||
* further results found upon success.
|
||||
* @return 1 on success, 2 when another device was found, 0 when no device was found or no found device matched the
|
||||
* desired ID, or less than 0 on error.
|
||||
*/
|
||||
int resolveMdnsOneShot(const char* url, mdns_oneshot_t *result,
|
||||
mdns_oneshot_t *moreResults = nullptr, size_t *moreCount = nullptr);
|
||||
|
||||
} // namespace ebusd
|
||||
|
||||
#endif // LIB_UTILS_TCPSOCKET_H_
|
||||
|
||||
@@ -807,7 +807,7 @@ int openSerial(std::string port) {
|
||||
|
||||
int openNet(std::string host, uint16_t port) {
|
||||
// open network port
|
||||
int fd = socketConnect(host.c_str(), port, false, nullptr, 5);
|
||||
int fd = socketConnect(host.c_str(), port, 0, nullptr, 5);
|
||||
if (fd < 0) {
|
||||
std::cerr << "unable to open " << host << std::endl;
|
||||
return -1;
|
||||
|
||||
Reference in New Issue
Block a user