add mdns discovery and make it the default

This commit is contained in:
john30
2024-09-12 07:10:16 +02:00
parent c5668a0d59
commit f6402f386a
10 changed files with 573 additions and 170 deletions
+2
View File
@@ -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)
+2 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
+6 -3
View File
@@ -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;
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
+48 -5
View File
@@ -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_
+1 -1
View File
@@ -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;