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 option to inject start-up commands
|
||||||
* add verbose raw data option to "read" and "write" 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 allow write direction in "read" command when inline defining a new message
|
||||||
|
* add option to discover device via mDNS
|
||||||
|
|
||||||
## Breaking Changes
|
## Breaking Changes
|
||||||
* change default config path to https://ebus.github.io/ serving files generated from new TypeSpec message definition sources
|
* 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)
|
# 23.3 (2023-12-26)
|
||||||
|
|||||||
@@ -24,9 +24,10 @@ The main features of the daemon are:
|
|||||||
* TCP
|
* TCP
|
||||||
* UDP
|
* UDP
|
||||||
* enhanced ebusd protocol allowing arbitration to be done directly by the hardware, e.g. for recent
|
* 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
|
* [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/)
|
* [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
|
* actively send messages to and receive answers from the eBUS
|
||||||
* passively listen to messages sent on the eBUS
|
* passively listen to messages sent on the eBUS
|
||||||
* answer to messages received from the eBUS
|
* answer to messages received from the eBUS
|
||||||
|
|||||||
+45
-2
@@ -23,6 +23,7 @@
|
|||||||
#include "ebusd/main.h"
|
#include "ebusd/main.h"
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
#include "ebusd/network.h"
|
#include "ebusd/network.h"
|
||||||
#include "lib/utils/log.h"
|
#include "lib/utils/log.h"
|
||||||
#include "lib/utils/httpclient.h"
|
#include "lib/utils/httpclient.h"
|
||||||
|
#include "lib/utils/tcpsocket.h"
|
||||||
#include "ebusd/scan.h"
|
#include "ebusd/scan.h"
|
||||||
|
|
||||||
namespace ebusd {
|
namespace ebusd {
|
||||||
@@ -411,11 +413,52 @@ int main(int argc, char* argv[], char* envp[]) {
|
|||||||
return overallResult == RESULT_OK ? EXIT_SUCCESS : EXIT_FAILURE;
|
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);
|
s_busHandler = new BusHandler(s_messageMap, s_scanHelper, s_opt.pollInterval);
|
||||||
|
|
||||||
// create the protocol and open the device
|
// create the protocol and open the device
|
||||||
ebus_protocol_config_t config = {
|
ebus_protocol_config_t config = {
|
||||||
.device = s_opt.device,
|
.device = device,
|
||||||
.noDeviceCheck = s_opt.noDeviceCheck,
|
.noDeviceCheck = s_opt.noDeviceCheck,
|
||||||
.readOnly = s_opt.readOnly,
|
.readOnly = s_opt.readOnly,
|
||||||
.extraLatency = s_opt.extraLatency,
|
.extraLatency = s_opt.extraLatency,
|
||||||
@@ -431,7 +474,7 @@ int main(int argc, char* argv[], char* envp[]) {
|
|||||||
};
|
};
|
||||||
s_protocol = ProtocolHandler::create(config, s_busHandler);
|
s_protocol = ProtocolHandler::create(config, s_busHandler);
|
||||||
if (s_protocol == nullptr) {
|
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();
|
cleanup();
|
||||||
return EINVAL;
|
return EINVAL;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -40,7 +40,7 @@ namespace ebusd {
|
|||||||
|
|
||||||
/** A structure holding all program options. */
|
/** A structure holding all program options. */
|
||||||
typedef struct 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 noDeviceCheck; //!< skip serial eBUS device test
|
||||||
bool readOnly; //!< read-only access to the device
|
bool readOnly; //!< read-only access to the device
|
||||||
bool initialSend; //!< send an initial escape symbol after connecting device
|
bool initialSend; //!< send an initial escape symbol after connecting device
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ namespace ebusd {
|
|||||||
|
|
||||||
/** the default program options. */
|
/** the default program options. */
|
||||||
static const options_t s_default_opt = {
|
static const options_t s_default_opt = {
|
||||||
.device = "/dev/ttyUSB0",
|
.device = "mdns:",
|
||||||
.noDeviceCheck = false,
|
.noDeviceCheck = false,
|
||||||
.readOnly = false,
|
.readOnly = false,
|
||||||
.initialSend = false,
|
.initialSend = false,
|
||||||
@@ -134,11 +134,14 @@ static string s_configPath = CONFIG_PATH;
|
|||||||
static const argDef argDefs[] = {
|
static const argDef argDefs[] = {
|
||||||
{nullptr, 0, nullptr, 0, "Device options:"},
|
{nullptr, 0, nullptr, 0, "Device options:"},
|
||||||
{"device", 'd', "DEV", 0, "Use DEV as eBUS device ("
|
{"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 "
|
"prefix \"ens:\" for enhanced high speed device or "
|
||||||
"\"enh:\" for enhanced device, with "
|
"\"enh:\" for enhanced device, with "
|
||||||
"\"IP[:PORT]\" for network device or "
|
"\"IP[:PORT]\" for network device or "
|
||||||
"\"DEVICE\" for serial device"
|
"\"DEVICE\" for serial device"
|
||||||
") [/dev/ttyUSB0]"},
|
") [mdns:]"},
|
||||||
{"nodevicecheck", 'n', nullptr, 0, "Skip serial eBUS device test"},
|
{"nodevicecheck", 'n', nullptr, 0, "Skip serial eBUS device test"},
|
||||||
{"readonly", 'r', nullptr, 0, "Only read from device, never write to it"},
|
{"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"},
|
{"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) {
|
switch (key) {
|
||||||
// Device options:
|
// Device options:
|
||||||
case 'd': // --device=/dev/ttyUSB0
|
case 'd': // --device=mdns:
|
||||||
if (arg == nullptr || arg[0] == 0) {
|
if (arg == nullptr || arg[0] == 0) {
|
||||||
argParseError(parseOpt, "invalid device");
|
argParseError(parseOpt, "invalid device");
|
||||||
return EINVAL;
|
return EINVAL;
|
||||||
|
|||||||
@@ -325,7 +325,7 @@ void SerialTransport::checkDevice() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result_t NetworkTransport::openInternal() {
|
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) {
|
if (m_fd < 0) {
|
||||||
return RESULT_ERR_GENERIC_IO;
|
return RESULT_ERR_GENERIC_IO;
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-93
@@ -40,6 +40,7 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include "lib/knx/knx.h"
|
#include "lib/knx/knx.h"
|
||||||
|
#include "lib/utils/tcpsocket.h"
|
||||||
|
|
||||||
namespace ebusd {
|
namespace ebusd {
|
||||||
|
|
||||||
@@ -228,7 +229,7 @@ typedef struct __attribute__ ((packed)) {
|
|||||||
#define SYSTEM_MULTICAST_PORT 3671
|
#define SYSTEM_MULTICAST_PORT 3671
|
||||||
|
|
||||||
// the default system multicast address 224.0.23.12
|
// 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
|
#define LAST_FRAME_TIMEOUT 2
|
||||||
|
|
||||||
@@ -400,96 +401,17 @@ class KnxNetConnection : public KnxConnection {
|
|||||||
// @copydoc
|
// @copydoc
|
||||||
const char* open() override {
|
const char* open() override {
|
||||||
close();
|
close();
|
||||||
int ret;
|
int fd = socketConnect(m_url && m_url[0] ? m_url : SYSTEM_MULTICAST_IP_STR,
|
||||||
struct in_addr mcast = {};
|
SYSTEM_MULTICAST_PORT, IPPROTO_UDP, nullptr, 0x02);
|
||||||
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);
|
|
||||||
if (fd < 0) {
|
if (fd < 0) {
|
||||||
return "create socket";
|
return "create socket";
|
||||||
}
|
}
|
||||||
|
|
||||||
// set non-blocking
|
// set non-blocking
|
||||||
ret = fcntl(fd, F_SETFL, O_NONBLOCK);
|
if (fcntl(fd, F_SETFL, O_NONBLOCK) != 0) {
|
||||||
if (ret != 0) {
|
|
||||||
::close(fd);
|
::close(fd);
|
||||||
return "non-blocking";
|
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;
|
m_sock = fd;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -674,7 +596,7 @@ class KnxNetConnection : public KnxConnection {
|
|||||||
}
|
}
|
||||||
d[0] = tpci;
|
d[0] = tpci;
|
||||||
logTelegram(true, c, l, d);
|
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) {
|
if (sent < 0) {
|
||||||
return "send error";
|
return "send error";
|
||||||
}
|
}
|
||||||
@@ -711,15 +633,6 @@ class KnxNetConnection : public KnxConnection {
|
|||||||
/** the URL to connect to. */
|
/** the URL to connect to. */
|
||||||
const char* m_url;
|
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. */
|
/** the socket if connected, or 0. */
|
||||||
int m_sock;
|
int m_sock;
|
||||||
|
|
||||||
|
|||||||
+433
-35
@@ -46,38 +46,91 @@ bool TCPSocket::isValid() {
|
|||||||
return fcntl(m_sfd, F_GETFL) != -1;
|
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 socketConnect(const char* server, uint16_t port, int udpProto, socketaddress* storeAddress,
|
||||||
int tcpKeepAliveInterval) {
|
int tcpConnToUdpOptions, int tcpKeepAliveInterval, struct in_addr* storeIntf) {
|
||||||
socketaddress localAddress;
|
socketaddress localAddress;
|
||||||
socketaddress* address = storeAddress ? storeAddress : &localAddress;
|
socketaddress* address = storeAddress ? storeAddress : &localAddress;
|
||||||
memset(reinterpret_cast<char*>(address), 0, sizeof(*address));
|
memset(reinterpret_cast<char*>(address), 0, sizeof(*address));
|
||||||
|
|
||||||
if (inet_aton(server, &address->sin_addr) == 0) {
|
// parse "address[@intf]"
|
||||||
struct hostent* he = gethostbyname(server);
|
const char* pos = strchr(server, '@');
|
||||||
if (he == nullptr) {
|
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;
|
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_family = AF_INET;
|
||||||
address->sin_port = (in_port_t)htons(port);
|
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) {
|
if (sfd < 0) {
|
||||||
return -1;
|
return -2;
|
||||||
}
|
}
|
||||||
int ret;
|
int ret = 0;
|
||||||
if (udp) {
|
if (udpProto) {
|
||||||
|
#define RET(chk, next) if (ret >= 0) { ret = chk; if (ret < 0) ret = next;}
|
||||||
struct sockaddr_in bindAddress = *address;
|
struct sockaddr_in bindAddress = *address;
|
||||||
bindAddress.sin_addr.s_addr = INADDR_ANY;
|
// allow multiple processes using the same port for multicast on the same host
|
||||||
ret = bind(sfd, (struct sockaddr*)&bindAddress, sizeof(bindAddress));
|
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) {
|
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) {
|
if (ret < 0) {
|
||||||
close(sfd);
|
close(sfd);
|
||||||
return -1;
|
return ret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return sfd;
|
return sfd;
|
||||||
}
|
}
|
||||||
@@ -85,7 +138,7 @@ int tcpKeepAliveInterval) {
|
|||||||
ret = setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<void*>(&value), sizeof(value));
|
ret = setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<void*>(&value), sizeof(value));
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
close(sfd);
|
close(sfd);
|
||||||
return -1;
|
return -3;
|
||||||
}
|
}
|
||||||
if (tcpKeepAliveInterval > 0) {
|
if (tcpKeepAliveInterval > 0) {
|
||||||
value = 1;
|
value = 1;
|
||||||
@@ -124,24 +177,40 @@ int tcpKeepAliveInterval) {
|
|||||||
}
|
}
|
||||||
#endif
|
#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);
|
close(sfd);
|
||||||
return -1;
|
return -4;
|
||||||
}
|
}
|
||||||
ret = ::connect(sfd, (struct sockaddr*)address, sizeof(*address));
|
ret = ::connect(sfd, (struct sockaddr*)address, sizeof(*address));
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
if (ret < 0 && (tcpConnectTimeout <= 0 || errno != EINPROGRESS)) {
|
if (ret < 0 && (tcpConnToUdpOptions <= 0 || errno != EINPROGRESS)) {
|
||||||
close(sfd);
|
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)
|
#if defined(HAVE_PPOLL) || defined(HAVE_PSELECT)
|
||||||
struct timespec tdiff;
|
struct timespec tdiff;
|
||||||
tdiff.tv_sec = tcpConnectTimeout;
|
tdiff.tv_sec = timeoutSeconds;
|
||||||
tdiff.tv_nsec = 0;
|
tdiff.tv_nsec = 0;
|
||||||
#else
|
#else
|
||||||
struct timeval tdiff;
|
struct timeval tdiff;
|
||||||
tdiff.tv_sec = tcpConnectTimeout;
|
tdiff.tv_sec = timeoutSeconds;
|
||||||
tdiff.tv_usec = 0;
|
tdiff.tv_usec = 0;
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_PPOLL
|
#ifdef HAVE_PPOLL
|
||||||
@@ -149,18 +218,24 @@ int tcpKeepAliveInterval) {
|
|||||||
struct pollfd fds[nfds];
|
struct pollfd fds[nfds];
|
||||||
memset(fds, 0, sizeof(fds));
|
memset(fds, 0, sizeof(fds));
|
||||||
fds[0].fd = sfd;
|
fds[0].fd = sfd;
|
||||||
fds[0].events = POLLIN|POLLOUT;
|
fds[0].events = which;
|
||||||
ret = ppoll(fds, nfds, &tdiff, nullptr);
|
ret = ppoll(fds, nfds, &tdiff, nullptr);
|
||||||
if (ret == 1 && fds[0].revents & POLLERR) {
|
if (ret >= 1 && fds[0].revents & POLLERR) {
|
||||||
ret = -1;
|
ret = -1;
|
||||||
|
} else if (ret >= 1) {
|
||||||
|
ret = fds[0].revents;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
fd_set readfds, writefds, exceptfds;
|
fd_set readfds, writefds, exceptfds;
|
||||||
FD_ZERO(&readfds);
|
FD_ZERO(&readfds);
|
||||||
FD_ZERO(&writefds);
|
FD_ZERO(&writefds);
|
||||||
FD_ZERO(&exceptfds);
|
FD_ZERO(&exceptfds);
|
||||||
|
if (which & POLLIN) {
|
||||||
FD_SET(sfd, &readfds);
|
FD_SET(sfd, &readfds);
|
||||||
|
}
|
||||||
|
if (which & POLLOUT) {
|
||||||
FD_SET(sfd, &writefds);
|
FD_SET(sfd, &writefds);
|
||||||
|
}
|
||||||
FD_SET(sfd, &exceptfds);
|
FD_SET(sfd, &exceptfds);
|
||||||
#ifdef HAVE_PSELECT
|
#ifdef HAVE_PSELECT
|
||||||
ret = pselect(sfd + 1, &readfds, &writefds, &exceptfds, &tdiff, nullptr);
|
ret = pselect(sfd + 1, &readfds, &writefds, &exceptfds, &tdiff, nullptr);
|
||||||
@@ -169,19 +244,11 @@ int tcpKeepAliveInterval) {
|
|||||||
#endif
|
#endif
|
||||||
if (ret >= 1 && FD_ISSET(sfd, &exceptfds)) {
|
if (ret >= 1 && FD_ISSET(sfd, &exceptfds)) {
|
||||||
ret = -1;
|
ret = -1;
|
||||||
|
} else if (ret >= 1) {
|
||||||
|
ret = (FD_ISSET(sfd, &readfds) ? POLLIN : 0) | (FD_ISSET(sfd, &writefds) ? POLLOUT : 0);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (ret == -1 || ret == 0) {
|
return ret;
|
||||||
close(sfd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (fcntl(sfd, F_SETFL, 0) < 0) { // set blocking again
|
|
||||||
close(sfd);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sfd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -243,4 +310,335 @@ TCPSocket* TCPServer::newSocket() {
|
|||||||
return new TCPSocket(sfd, &address);
|
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
|
} // namespace ebusd
|
||||||
|
|||||||
@@ -25,6 +25,11 @@
|
|||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#ifdef __FreeBSD__
|
||||||
|
#include <machine/endian.h>
|
||||||
|
#else
|
||||||
|
#include <endian.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
/** typedef for referencing @a sockaddr_in within namespace. */
|
/** typedef for referencing @a sockaddr_in within namespace. */
|
||||||
typedef struct sockaddr_in socketaddress;
|
typedef struct sockaddr_in socketaddress;
|
||||||
@@ -43,16 +48,29 @@ using std::string;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect a TCP or UDP socket.
|
* 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 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 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 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.
|
* @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 socketConnect(const char* server, uint16_t port, int udpProto, socketaddress* storeAddress = nullptr,
|
||||||
int tcpConnectTimeout = 0, int tcpKeepAliveInterval = 0);
|
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;
|
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
|
} // namespace ebusd
|
||||||
|
|
||||||
#endif // LIB_UTILS_TCPSOCKET_H_
|
#endif // LIB_UTILS_TCPSOCKET_H_
|
||||||
|
|||||||
@@ -807,7 +807,7 @@ int openSerial(std::string port) {
|
|||||||
|
|
||||||
int openNet(std::string host, uint16_t port) {
|
int openNet(std::string host, uint16_t port) {
|
||||||
// open network 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) {
|
if (fd < 0) {
|
||||||
std::cerr << "unable to open " << host << std::endl;
|
std::cerr << "unable to open " << host << std::endl;
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
Reference in New Issue
Block a user