Przeglądaj źródła

KBF-24 implement mDNS PTR / service discovery

Bence Balint 3 lat temu
rodzic
commit
9a3a326ca1
3 zmienionych plików z 202 dodań i 17 usunięć
  1. 58 2
      include/kbf/net.h
  2. 46 3
      src/net.cpp
  3. 98 12
      test/test_net.cpp

+ 58 - 2
include/kbf/net.h

@@ -2,6 +2,7 @@
 #define KBF_NET_H
 
 #include <string>
+#include <vector>
 
 #include <esp_netif.h>
 #include <mdns.h>
@@ -116,6 +117,22 @@ namespace kbf::net {
         void invalidate(const std::string &);
     };
 
+//    enum class Interface {
+//        STA,
+//        AP,
+//        ETH
+//    };
+//
+//    enum class IPVersion {
+//        V4,
+//        V6
+//    };
+
+    enum class Proto {
+        TCP,
+        UDP
+    };
+
     /**
      * @brief Multicast DNS server.
      */
@@ -146,7 +163,19 @@ namespace kbf::net {
          *
          * @param hostname
          */
-        void setHostname(const std::string& hostname);
+        void setHostname(const std::string &hostname);
+
+        /**
+         * @brief Starts advertising a service.
+         *
+         * @param serviceType service type, e.g. "_http"
+         * @param port
+         * @param instanceName service instance name, e.g. "My HTTP Server"
+         * @param proto
+         */
+        void addService(const std::string &serviceType, int port, const std::string &instanceName = "",
+                        const Proto &proto = Proto::TCP
+        );
 
         /**
          * @brief Queries A record for hostname.
@@ -155,7 +184,34 @@ namespace kbf::net {
          * @param timeoutMs timeout in milliseconds; default is 2000
          * @return IP or nullptr if hostname not found
          */
-        IP *query(const std::string& hostname, int timeoutMs = 2000);
+        IP *queryA(const std::string &hostname, int timeoutMs = 2000);
+
+        /**
+         * @brief Holds a result of a PTR query.
+         */
+        struct PTRResult {
+//            Interface       interface;
+//            IPVersion       ipVersion;
+            Proto           protocol;
+            std::vector<IP> addresses;
+            int             port;
+            std::string     instanceName;
+            std::string     hostName;
+        };
+
+        /**
+         * @brief Queries PTR records for a service type.
+         *
+         * @param serviceType service type, e.g. "_http"
+         * @param timeoutMs timeout in milliseconds
+         * @param proto
+         * @return vector of results
+         */
+        std::vector<PTRResult>
+        queryPTR(const std::string &serviceType, int timeoutMs = 10000, Proto proto = Proto::TCP);
+
+    private:
+        static const char *protoStr(Proto proto);
     };
 }
 

+ 46 - 3
src/net.cpp

@@ -81,7 +81,7 @@ void net::IP::invalidate(const string &address) {
     valid = false;
 }
 
-net::MDNS::MDNS(const std::string& hostname) {
+net::MDNS::MDNS(const std::string &hostname) {
     ESP_LOGI(TAG, "MDNS(\"%s\")", hostname.c_str());
     CHECK(mdns_init());
     CHECK(mdns_instance_name_set(INSTANCE_NAME));
@@ -100,8 +100,8 @@ void net::MDNS::setHostname(const std::string& hostname) {
     CHECK(mdns_hostname_set(hostname.c_str()));
 }
 
-net::IP *net::MDNS::query(const std::string& hostname, int timeoutMs) {
-    ESP_LOGI(TAG, "query(\"%s\")", hostname.c_str());
+net::IP *net::MDNS::queryA(const std::string &hostname, int timeoutMs) {
+    ESP_LOGI(TAG, "queryA(\"%s\")", hostname.c_str());
 
     struct esp_ip4_addr addr{};
     addr.addr = 0;
@@ -120,3 +120,46 @@ net::IP *net::MDNS::query(const std::string& hostname, int timeoutMs) {
     return nullptr;  // unreachable
 }
 
+void
+net::MDNS::addService(const string &serviceType, int port, const string &instanceName, const net::Proto &proto) {
+    ESP_LOGI(TAG, "addService(%s, %d, %s, %s)", serviceType.c_str(), port, instanceName.c_str(), protoStr(proto));
+    mdns_service_add(instanceName == "" ? nullptr : instanceName.c_str(), serviceType.c_str(), protoStr(proto), port,
+                     nullptr, 0);
+}
+
+std::vector<net::MDNS::PTRResult> net::MDNS::queryPTR(const string &serviceType, int timeoutMs, net::Proto proto) {
+    ESP_LOGI(TAG, "queryPTR(%s, %s, %d)", serviceType.c_str(), protoStr(proto), timeoutMs);
+    std::vector<PTRResult> ret;
+
+    mdns_result_t *result = nullptr;
+    CHECK(mdns_query_ptr(serviceType.c_str(), protoStr(proto), timeoutMs, 20, &result));
+
+    for (; result; result = result->next) {
+        PTRResult tmp{
+//                .interface = static_cast<Interface>(result->tcpip_if),
+//                .ipVersion = static_cast<IPVersion>(result->ip_protocol),
+                .protocol = proto,
+                .port = result->port,
+                .instanceName = result->instance_name,
+                .hostName = result->hostname,
+        };
+
+        auto addr = result->addr;
+        for (; addr; addr = addr->next) {
+            if (addr->addr.type == IPADDR_TYPE_V6) {
+                ESP_LOGW(TAG, "IPv6 support not implemented");
+                continue;
+            }
+            tmp.addresses.push_back(IP(addr->addr.u_addr.ip4));
+        }
+        ESP_LOGI(TAG, "  result: %s on %s", tmp.instanceName.c_str(), tmp.hostName.c_str());
+        ret.push_back(tmp);
+    }
+
+    return ret;
+}
+
+const char *net::MDNS::protoStr(net::Proto proto) {
+    return proto == Proto::TCP ? "_tcp" : "_udp";
+}
+

+ 98 - 12
test/test_net.cpp

@@ -5,11 +5,23 @@
 
 #include "kbf/net.h"
 
-#define KBF_TEST_MDNS_SSID   "kbf_test_wifi_ap"
-#define KBF_TEST_MDNS_PASS   "Pas$w0Rd1337"
-#define KBF_TEST_MDNS_MASTER "master"
-#define KBF_TEST_MDNS_SLAVE  "slave"
-#define KBF_TEST_MDNS_SIGNAL "ready"
+#define KBF_TEST_MDNS_SSID         "kbf_test_wifi_ap"
+#define KBF_TEST_MDNS_PASS         "Pas$w0Rd1337"
+#define KBF_TEST_MDNS_MASTER       "master"
+#define KBF_TEST_MDNS_SLAVE        "slave"
+#define KBF_TEST_MDNS_SIGNAL       "ready"
+
+#define KBF_TEST_SERVICE1_TYPE "_hax"
+#define KBF_TEST_SERVICE1_PORT 1337
+#define KBF_TEST_SERVICE1_INST "Test TCP service"
+
+#define KBF_TEST_SERVICE2_TYPE "_http"
+#define KBF_TEST_SERVICE2_PORT 80
+#define KBF_TEST_SERVICE2_INST "Test TCP/HTTP service"
+
+#define KBF_TEST_SERVICE3_TYPE "_hax"
+#define KBF_TEST_SERVICE3_PORT 31337
+#define KBF_TEST_SERVICE3_INST "Test UDP service"
 
 using namespace kbf;
 
@@ -61,7 +73,6 @@ TEST_CASE("IP address functions", "[kbf_net]") {
 using std::shared_ptr;
 
 static rtos::EventGroup eventGroup;
-static net::MDNS        *mdns;
 
 static shared_ptr<kbf::UART> setupUart() {
     auto uart1 = new kbf::UART(1);
@@ -71,12 +82,12 @@ static shared_ptr<kbf::UART> setupUart() {
 
 void mdnsMaster() {
     wifi::start(wifi::AP::create(KBF_TEST_MDNS_SSID, KBF_TEST_MDNS_PASS));
-    mdns = new net::MDNS();
+    auto mdns = new net::MDNS();
     mdns->setHostname(KBF_TEST_MDNS_MASTER);
 
     setupUart()->waitFor(KBF_TEST_MDNS_SIGNAL);
 
-    auto ip = mdns->query(KBF_TEST_MDNS_SLAVE);
+    auto ip = mdns->queryA(KBF_TEST_MDNS_SLAVE);
     TEST_ASSERT_NOT_NULL(ip)
     TEST_ASSERT_EQUAL_STRING("192.168.4.2", ip->str().c_str());
 
@@ -90,13 +101,15 @@ void mdnsSlave() {
     eventGroup.clear();
 
     sta->onIp = {[]() {
-        mdns = new net::MDNS(KBF_TEST_MDNS_SLAVE);
+        auto mdns = new net::MDNS(KBF_TEST_MDNS_SLAVE);
 
-        TEST_ASSERT_NULL(mdns->query("nonexistent"))
+        TEST_ASSERT_NULL(mdns->queryA("nonexistent"))
 
-        auto ip = mdns->query(KBF_TEST_MDNS_MASTER);
+        auto ip = mdns->queryA(KBF_TEST_MDNS_MASTER);
         TEST_ASSERT_NOT_NULL(ip)
         TEST_ASSERT_EQUAL_STRING("192.168.4.1", ip->str().c_str());
+
+        delete mdns;
         setupUart()->write(KBF_TEST_MDNS_SIGNAL);
     }};
 
@@ -107,8 +120,81 @@ void mdnsSlave() {
     sta->connect(KBF_TEST_MDNS_SSID, KBF_TEST_MDNS_PASS);
 
     eventGroup.waitForBit(0);
-    delete mdns;
     wifi::stop();
 }
 
 TEST_CASE_MULTIPLE_DEVICES("mDNS", "[kbf_net]", mdnsMaster, mdnsSlave)
+
+void mdnsServiceMaster() {
+    eventGroup.clear();
+    wifi::start(wifi::AP::create(KBF_TEST_MDNS_SSID, KBF_TEST_MDNS_PASS));
+    wifi::getAP()->onDisconnect = {[](wifi::AP::STA &) {
+        eventGroup.setBit(0);
+    }};
+
+    auto mdns = new net::MDNS();
+    mdns->setHostname(KBF_TEST_MDNS_MASTER);
+    mdns->addService(KBF_TEST_SERVICE1_TYPE, KBF_TEST_SERVICE1_PORT, KBF_TEST_SERVICE1_INST);
+    mdns->addService(KBF_TEST_SERVICE2_TYPE, KBF_TEST_SERVICE2_PORT, KBF_TEST_SERVICE2_INST);
+    mdns->addService(KBF_TEST_SERVICE3_TYPE, KBF_TEST_SERVICE3_PORT, KBF_TEST_SERVICE3_INST, net::Proto::UDP);
+
+    TEST_ASSERT_TRUE(eventGroup.waitForBit(0, 20000));
+    delete mdns;
+    wifi::stop();
+}
+
+void mdnsServiceSlave() {
+    eventGroup.clear();
+    wifi::start();
+    wifi::getSTA()->connect(KBF_TEST_MDNS_SSID, KBF_TEST_MDNS_PASS);
+    wifi::getSTA()->onIp         = {[]() {
+        eventGroup.setBit(0);
+    }};
+    wifi::getSTA()->onDisconnect = {[]() {
+        TEST_ASSERT_EQUAL_STRING("", "connection error");
+    }};
+
+    eventGroup.waitForBit(0);
+    auto mdns = new net::MDNS();
+
+    auto results = mdns->queryPTR("_nonexistent", 2000);
+    TEST_ASSERT_EQUAL(0, results.size());
+
+    results = mdns->queryPTR(KBF_TEST_SERVICE1_TYPE, 2000);
+    TEST_ASSERT_EQUAL(1, results.size());
+//    TEST_ASSERT_EQUAL(net::Interface::STA, results[0].interface);
+    TEST_ASSERT_EQUAL_STRING(KBF_TEST_MDNS_MASTER, results[0].hostName.c_str());
+    TEST_ASSERT_EQUAL(net::Proto::TCP, results[0].protocol);
+//    TEST_ASSERT_EQUAL(net::IPVersion::V4, results[0].ipVersion);
+    TEST_ASSERT_EQUAL(KBF_TEST_SERVICE1_PORT, results[0].port);
+    TEST_ASSERT_EQUAL(1, results[0].addresses.size());
+    TEST_ASSERT_EQUAL_STRING("192.168.4.1", results[0].addresses[0].str().c_str());
+    TEST_ASSERT_EQUAL_STRING(KBF_TEST_SERVICE1_INST, results[0].instanceName.c_str());
+
+    results = mdns->queryPTR(KBF_TEST_SERVICE2_TYPE, 2000);
+    TEST_ASSERT_EQUAL(1, results.size());
+//    TEST_ASSERT_EQUAL(net::Interface::STA, results[1].interface);
+    TEST_ASSERT_EQUAL_STRING(KBF_TEST_MDNS_MASTER, results[0].hostName.c_str());
+    TEST_ASSERT_EQUAL(net::Proto::TCP, results[0].protocol);
+//    TEST_ASSERT_EQUAL(net::IPVersion::V4, results[1].ipVersion);
+    TEST_ASSERT_EQUAL(KBF_TEST_SERVICE2_PORT, results[0].port);
+    TEST_ASSERT_EQUAL(1, results[0].addresses.size());
+    TEST_ASSERT_EQUAL_STRING("192.168.4.1", results[0].addresses[0].str().c_str());
+    TEST_ASSERT_EQUAL_STRING(KBF_TEST_SERVICE2_INST, results[0].instanceName.c_str());
+
+    results = mdns->queryPTR(KBF_TEST_SERVICE3_TYPE, 2000, net::Proto::UDP);
+    TEST_ASSERT_EQUAL(1, results.size());
+//    TEST_ASSERT_EQUAL(net::Interface::STA, results[0].interface);
+    TEST_ASSERT_EQUAL_STRING(KBF_TEST_MDNS_MASTER, results[0].hostName.c_str());
+    TEST_ASSERT_EQUAL(net::Proto::UDP, results[0].protocol);
+//    TEST_ASSERT_EQUAL(net::IPVersion::V4, results[0].ipVersion);
+    TEST_ASSERT_EQUAL(KBF_TEST_SERVICE3_PORT, results[0].port);
+    TEST_ASSERT_EQUAL(1, results[0].addresses.size());
+    TEST_ASSERT_EQUAL_STRING("192.168.4.1", results[0].addresses[0].str().c_str());
+    TEST_ASSERT_EQUAL_STRING(KBF_TEST_SERVICE3_INST, results[0].instanceName.c_str());
+
+    delete mdns;
+    wifi::stop();
+}
+
+TEST_CASE_MULTIPLE_DEVICES("mDNS", "[kbf_net]", mdnsServiceMaster, mdnsServiceSlave)