├── .vscode ├── .conan_tools │ └── conanInfo.dot ├── launch.json └── settings.json ├── .clang-format ├── cmake └── Config.cmake.in ├── include └── mdns_cpp │ ├── macros.hpp │ ├── defs.hpp │ ├── utils.hpp │ ├── logger.hpp │ └── mdns.hpp ├── test_package ├── example.cpp ├── CMakeLists.txt ├── conanfile.py └── .gitignore ├── .gitignore ├── src ├── logger.cpp ├── utils.cpp ├── mdns.cpp └── mdns.h ├── .github └── workflows │ └── main.yml ├── example ├── discovery.cpp ├── service.cpp └── query.cpp ├── LICENSE ├── conanfile.py ├── CMakeLists.txt └── README.md /.vscode/.conan_tools/conanInfo.dot: -------------------------------------------------------------------------------- 1 | digraph { 2 | } 3 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | BasedOnStyle: Google 3 | Standard: Cpp11 4 | ColumnLimit: 120 5 | -------------------------------------------------------------------------------- /cmake/Config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") 4 | check_required_components("@PROJECT_NAME@") 5 | -------------------------------------------------------------------------------- /include/mdns_cpp/macros.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mdns_cpp/logger.hpp" 4 | 5 | // #define MDNS_LOG (LogMessage(__FILE__, __LINE__)) 6 | #define MDNS_LOG (LogMessage()) 7 | -------------------------------------------------------------------------------- /test_package/example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "mdns_cpp/mdns.hpp" 5 | 6 | using namespace mdns_cpp; 7 | 8 | int main() { mdns_cpp::mDNS mdns; } -------------------------------------------------------------------------------- /include/mdns_cpp/defs.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace mdns_cpp { 6 | 7 | class ServiceRecord { 8 | public: 9 | const char *service; 10 | const char *hostname; 11 | uint32_t address_ipv4; 12 | uint8_t *address_ipv6; 13 | uint16_t port; 14 | }; 15 | 16 | } // namespace mdns_cpp 17 | -------------------------------------------------------------------------------- /test_package/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1.2) 2 | 3 | project(mdns_cpp_app CXX) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | 7 | include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) 8 | conan_basic_setup(TARGETS) 9 | 10 | find_package(mdns_cpp REQUIRED) 11 | 12 | add_executable(mdns_cpp_app example.cpp) 13 | target_link_libraries(mdns_cpp_app mdns_cpp::mdns_cpp) 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | CMakeLists.txt.user 35 | build/ 36 | 37 | debug.log 38 | .vs/* -------------------------------------------------------------------------------- /include/mdns_cpp/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct sockaddr; 6 | struct sockaddr_in; 7 | struct sockaddr_in6; 8 | 9 | namespace mdns_cpp { 10 | 11 | std::string getHostName(); 12 | 13 | std::string ipv4AddressToString(char *buffer, size_t capacity, const struct sockaddr_in *addr, size_t addrlen); 14 | 15 | std::string ipv6AddressToString(char *buffer, size_t capacity, const struct sockaddr_in6 *addr, size_t addrlen); 16 | 17 | std::string ipAddressToString(char *buffer, size_t capacity, const struct sockaddr *addr, size_t addrlen); 18 | 19 | } // namespace mdns_cpp 20 | -------------------------------------------------------------------------------- /test_package/conanfile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os.path 4 | from conans import ConanFile, CMake, tools 5 | 6 | 7 | class MdnsCppCppTestConan(ConanFile): 8 | settings = "os", "compiler", "build_type", "arch" 9 | generators = ["cmake_find_package", "cmake", "cmake_paths"] 10 | 11 | def build(self): 12 | cmake = CMake(self) 13 | cmake.configure() 14 | cmake.build() 15 | 16 | def test(self): 17 | if not tools.cross_building(self.settings): 18 | bin_path = os.path.join("bin", "mdns_cpp_app") 19 | self.run(bin_path, run_environment=True) 20 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "(Windows) Launch", 9 | "type": "cppvsdbg", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/build/bin/Debug/mdns_cpp_service_with_logger_example.exe", 12 | "args": [], 13 | "stopAtEntry": false, 14 | "cwd": "${workspaceFolder}", 15 | "environment": [], 16 | "externalConsole": false 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /include/mdns_cpp/logger.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace mdns_cpp { 9 | 10 | class Logger { 11 | public: 12 | static void LogIt(const std::string& s); 13 | static void setLoggerSink(std::function callback); 14 | static void useDefaultSink(); 15 | 16 | private: 17 | static bool logger_registered; 18 | static std::function logging_callback_function; 19 | }; 20 | 21 | class LogMessage { 22 | public: 23 | LogMessage(const char* file, int line); 24 | LogMessage(); 25 | 26 | ~LogMessage(); 27 | 28 | template 29 | LogMessage& operator<<(const T& t) { 30 | os << t; 31 | return *this; 32 | } 33 | 34 | private: 35 | std::ostringstream os; 36 | }; 37 | 38 | } // namespace mdns_cpp 39 | -------------------------------------------------------------------------------- /src/logger.cpp: -------------------------------------------------------------------------------- 1 | #include "mdns_cpp/logger.hpp" 2 | 3 | namespace mdns_cpp { 4 | 5 | bool Logger::logger_registered = false; 6 | 7 | std::function Logger::logging_callback_function; 8 | 9 | void Logger::LogIt(const std::string &s) { 10 | if (logger_registered) { 11 | logging_callback_function(s); 12 | } else { 13 | std::cout << s << "\n"; 14 | } 15 | } 16 | 17 | void Logger::setLoggerSink(std::function callback) { 18 | logger_registered = true; 19 | logging_callback_function = callback; 20 | } 21 | 22 | void Logger::useDefaultSink() { logger_registered = false; } 23 | 24 | LogMessage::LogMessage(const char *file, int line) { os << "[" << file << ":" << line << "] "; } 25 | 26 | LogMessage::LogMessage() { os << ""; } 27 | 28 | LogMessage::~LogMessage() { Logger::LogIt(os.str()); } 29 | 30 | } // namespace mdns_cpp 31 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - '*' 10 | 11 | env: 12 | BUILD_TYPE: RelWithDebInfo 13 | 14 | jobs: 15 | build: 16 | 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | matrix: 20 | os: [windows-latest, ubuntu-latest, macOS-latest] 21 | 22 | steps: 23 | - uses: actions/checkout@v1 24 | 25 | - name: Create Build Environment 26 | run: cmake -E make_directory ${{runner.workspace}}/build 27 | 28 | - name: Configure CMake 29 | shell: bash 30 | working-directory: ${{runner.workspace}}/build 31 | run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE 32 | 33 | - name: Build 34 | working-directory: ${{runner.workspace}}/build 35 | # Execute the build. You can specify a specific target with "--target " 36 | run: cmake --build . --config $BUILD_TYPE -------------------------------------------------------------------------------- /example/discovery.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #ifdef _WIN32 9 | #include 10 | #endif 11 | 12 | #include "mdns_cpp/logger.hpp" 13 | #include "mdns_cpp/mdns.hpp" 14 | 15 | void onInterruptHandler(int s) { 16 | std::cout << "Caught signal: " << s << std::endl; 17 | exit(0); 18 | } 19 | 20 | int main() { 21 | signal(SIGINT, onInterruptHandler); 22 | 23 | #ifdef _WIN32 24 | WSADATA wsaData; 25 | // Initialize Winsock 26 | const int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); 27 | 28 | if (iResult != 0) { 29 | std::cout << "WSAStartup failed: " << iResult << "\n"; 30 | return 1; 31 | } 32 | #endif 33 | 34 | mdns_cpp::Logger::setLoggerSink([](const std::string& log_msg) { 35 | std::cout << "MDNS_LIBRARY: " << log_msg; 36 | std::flush(std::cout); 37 | }); 38 | 39 | mdns_cpp::mDNS mdns; 40 | 41 | mdns.executeDiscovery(); 42 | 43 | while (true) { 44 | std::this_thread::sleep_for(std::chrono::seconds(1)); 45 | } 46 | 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /example/service.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #ifdef _WIN32 9 | #include 10 | #endif 11 | 12 | #include "mdns_cpp/logger.hpp" 13 | #include "mdns_cpp/mdns.hpp" 14 | 15 | void onInterruptHandler(int s) { 16 | std::cout << "Caught signal: " << s << std::endl; 17 | exit(0); 18 | } 19 | 20 | int main() { 21 | signal(SIGINT, onInterruptHandler); 22 | 23 | #ifdef _WIN32 24 | WSADATA wsaData; 25 | // Initialize Winsock 26 | int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); 27 | 28 | if (iResult != 0) { 29 | std::cout << "WSAStartup failed: " << iResult << "\n"; 30 | return 1; 31 | } 32 | #endif 33 | 34 | mdns_cpp::Logger::setLoggerSink([](const std::string& log_msg) { 35 | std::cout << "MDNS_LIBRARY: " << log_msg; 36 | std::flush(std::cout); 37 | }); 38 | 39 | mdns_cpp::mDNS mdns; 40 | 41 | mdns.setServiceHostname("AirForce1"); 42 | 43 | mdns.startService(); 44 | 45 | while (true) { 46 | std::this_thread::sleep_for(std::chrono::seconds(1)); 47 | } 48 | 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Carlos Gomes Martinho 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/query.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #ifdef _WIN32 9 | #include 10 | #endif 11 | 12 | #include "mdns_cpp/logger.hpp" 13 | #include "mdns_cpp/mdns.hpp" 14 | 15 | void onInterruptHandler(int s) { 16 | std::cout << "Caught signal: " << s << std::endl; 17 | exit(0); 18 | } 19 | 20 | int main() { 21 | signal(SIGINT, onInterruptHandler); 22 | 23 | #ifdef _WIN32 24 | WSADATA wsaData; 25 | // Initialize Winsock 26 | const int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); 27 | 28 | if (iResult != 0) { 29 | std::cout << "WSAStartup failed: " << iResult << "\n"; 30 | return 1; 31 | } 32 | #endif 33 | 34 | mdns_cpp::Logger::setLoggerSink([](const std::string& log_msg) { 35 | std::cout << "MDNS_LIBRARY: " << log_msg; 36 | std::flush(std::cout); 37 | }); 38 | 39 | mdns_cpp::mDNS mdns; 40 | const std::string service = "_http._tcp.local."; 41 | 42 | mdns.executeQuery(service); 43 | 44 | while (true) { 45 | std::this_thread::sleep_for(std::chrono::seconds(1)); 46 | } 47 | 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /test_package/.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | 41 | # xemacs temporary files 42 | *.flc 43 | 44 | # Vim temporary files 45 | .*.swp 46 | 47 | # Visual Studio generated files 48 | *.ib_pdb_index 49 | *.idb 50 | *.ilk 51 | *.pdb 52 | *.sln 53 | *.suo 54 | *.vcproj 55 | *vcproj.*.*.user 56 | *.ncb 57 | *.sdf 58 | *.opensdf 59 | *.vcxproj 60 | *vcxproj.* 61 | 62 | # MinGW generated files 63 | *.Debug 64 | *.Release 65 | 66 | # Python byte code 67 | *.pyc 68 | 69 | # Binaries 70 | # -------- 71 | *.dll 72 | *.exe 73 | 74 | /_build/* 75 | /build/* 76 | CMakeLists.txt.user 77 | 78 | /node_modules 79 | -------------------------------------------------------------------------------- /include/mdns_cpp/mdns.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "mdns_cpp/defs.hpp" 8 | 9 | struct sockaddr; 10 | 11 | namespace mdns_cpp { 12 | 13 | class mDNS { 14 | public: 15 | ~mDNS(); 16 | 17 | void startService(); 18 | void stopService(); 19 | bool isServiceRunning(); 20 | 21 | void setServiceHostname(const std::string &hostname); 22 | void setServicePort(std::uint16_t port); 23 | void setServiceName(const std::string &name); 24 | void setServiceTxtRecord(const std::string &text_record); 25 | 26 | void executeQuery(const std::string &service); 27 | void executeDiscovery(); 28 | 29 | private: 30 | void runMainLoop(); 31 | int openClientSockets(int *sockets, int max_sockets, int port); 32 | int openServiceSockets(int *sockets, int max_sockets); 33 | 34 | std::string hostname_{"dummy-host"}; 35 | std::string name_{"_http._tcp.local."}; 36 | std::uint16_t port_{42424}; 37 | std::string txt_record_{}; 38 | 39 | bool running_{false}; 40 | 41 | bool has_ipv4_{false}; 42 | bool has_ipv6_{false}; 43 | 44 | uint32_t service_address_ipv4_{0}; 45 | uint8_t service_address_ipv6_[16]{0}; 46 | 47 | std::thread worker_thread_; 48 | }; 49 | 50 | } // namespace mdns_cpp 51 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env python 3 | 4 | from conans import ConanFile, CMake, tools 5 | import subprocess 6 | 7 | 8 | class MdnsCpp(ConanFile): 9 | name = "mdns_cpp" 10 | version = "0.1.0" 11 | license = "MIT" 12 | url = "https://github.com/gocarlos/mdns_cpp" 13 | description = "Provides a cross-platform mDNS and DNS-DS library in C++" 14 | settings = "os", "compiler", "build_type", "arch" 15 | options = {"shared": [True, False], "fPIC": [True, False]} 16 | default_options = {"shared": False, "fPIC": True} 17 | generators = ["cmake", "cmake_find_package", "cmake_paths"] 18 | exports_sources = "CMakeLists.txt", "cmake/*", "src/*", "include/*", "conanfile.txt" 19 | 20 | def build(self): 21 | cmake = CMake(self) 22 | cmake.definitions["MDNS_CPP_BUILD_EXAMPLE"] = False 23 | cmake.configure(source_folder=".") 24 | cmake.build() 25 | 26 | def package(self): 27 | self.copy("*.hpp", src=".") 28 | self.copy("*mdns_cpp.lib", dst="lib", keep_path=False) 29 | self.copy("*.dll", dst="bin", keep_path=False) 30 | self.copy("*.so", dst="lib", keep_path=False) 31 | self.copy("*.dylib", dst="lib", keep_path=False) 32 | self.copy("*.a", dst="lib", keep_path=False) 33 | 34 | def package_info(self): 35 | self.cpp_info.libs = tools.collect_libs(self) 36 | if self.settings.os == "Linux": 37 | self.cpp_info.system_libs = ["m", "pthread"] 38 | if self.settings.os == "Windows": 39 | self.cpp_info.system_libs = ["iphlpapi", "ws2_32"] 40 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Jansson", 4 | "Mattias", 5 | "Unlicense", 6 | "chrono", 7 | "cmake", 8 | "mdns", 9 | "mdnscpp", 10 | "mkdir", 11 | "rclass" 12 | ], 13 | "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", 14 | "files.associations": { 15 | "iostream": "cpp", 16 | "thread": "cpp", 17 | "type_traits": "cpp", 18 | "atomic": "cpp", 19 | "cctype": "cpp", 20 | "chrono": "cpp", 21 | "cmath": "cpp", 22 | "compare": "cpp", 23 | "concepts": "cpp", 24 | "cstddef": "cpp", 25 | "cstdint": "cpp", 26 | "cstdio": "cpp", 27 | "cstdlib": "cpp", 28 | "cstring": "cpp", 29 | "cwchar": "cpp", 30 | "exception": "cpp", 31 | "functional": "cpp", 32 | "initializer_list": "cpp", 33 | "ios": "cpp", 34 | "iosfwd": "cpp", 35 | "istream": "cpp", 36 | "iterator": "cpp", 37 | "limits": "cpp", 38 | "list": "cpp", 39 | "memory": "cpp", 40 | "new": "cpp", 41 | "ostream": "cpp", 42 | "ratio": "cpp", 43 | "sstream": "cpp", 44 | "stdexcept": "cpp", 45 | "streambuf": "cpp", 46 | "string": "cpp", 47 | "system_error": "cpp", 48 | "tuple": "cpp", 49 | "typeinfo": "cpp", 50 | "unordered_map": "cpp", 51 | "utility": "cpp", 52 | "vector": "cpp", 53 | "xfacet": "cpp", 54 | "xhash": "cpp", 55 | "xiosbase": "cpp", 56 | "xlocale": "cpp", 57 | "xlocinfo": "cpp", 58 | "xlocnum": "cpp", 59 | "xmemory": "cpp", 60 | "xstddef": "cpp", 61 | "xstring": "cpp", 62 | "xtr1common": "cpp", 63 | "xutility": "cpp" 64 | } 65 | } -------------------------------------------------------------------------------- /src/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "mdns_cpp/utils.hpp" 2 | 3 | #ifdef _WIN32 4 | #define _CRT_SECURE_NO_WARNINGS 1 5 | #endif 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #ifdef _WIN32 14 | #include 15 | #include 16 | #include 17 | #include 18 | #else 19 | #include 20 | #include 21 | #include 22 | #include 23 | #endif 24 | 25 | #include "mdns_cpp/macros.hpp" 26 | 27 | namespace mdns_cpp { 28 | 29 | std::string getHostName() { 30 | const char *hostname = "dummy-host"; 31 | 32 | #ifdef _WIN32 33 | WORD versionWanted = MAKEWORD(1, 1); 34 | WSADATA wsaData; 35 | if (WSAStartup(versionWanted, &wsaData)) { 36 | const auto msg = "Error: Failed to initialize WinSock"; 37 | MDNS_LOG << msg << "\n"; 38 | throw std::runtime_error(msg); 39 | } 40 | 41 | char hostname_buffer[256]; 42 | DWORD hostname_size = (DWORD)sizeof(hostname_buffer); 43 | if (GetComputerNameA(hostname_buffer, &hostname_size)) { 44 | hostname = hostname_buffer; 45 | } 46 | 47 | #else 48 | 49 | char hostname_buffer[256]; 50 | const size_t hostname_size = sizeof(hostname_buffer); 51 | if (gethostname(hostname_buffer, hostname_size) == 0) { 52 | hostname = hostname_buffer; 53 | } 54 | 55 | #endif 56 | 57 | return hostname; 58 | } 59 | 60 | std::string ipv4AddressToString(char *buffer, size_t capacity, const sockaddr_in *addr, size_t addrlen) { 61 | char host[NI_MAXHOST] = {0}; 62 | char service[NI_MAXSERV] = {0}; 63 | const int ret = getnameinfo((const struct sockaddr *)addr, (socklen_t)addrlen, host, NI_MAXHOST, service, NI_MAXSERV, 64 | NI_NUMERICSERV | NI_NUMERICHOST); 65 | int len = 0; 66 | if (ret == 0) { 67 | if (addr->sin_port != 0) { 68 | len = snprintf(buffer, capacity, "%s:%s", host, service); 69 | } else { 70 | len = snprintf(buffer, capacity, "%s", host); 71 | } 72 | } 73 | if (len >= (int)capacity) { 74 | len = (int)capacity - 1; 75 | } 76 | 77 | return std::string(buffer, len); 78 | } 79 | 80 | std::string ipv6AddressToString(char *buffer, size_t capacity, const sockaddr_in6 *addr, size_t addrlen) { 81 | char host[NI_MAXHOST] = {0}; 82 | char service[NI_MAXSERV] = {0}; 83 | const int ret = getnameinfo((const struct sockaddr *)addr, (socklen_t)addrlen, host, NI_MAXHOST, service, NI_MAXSERV, 84 | NI_NUMERICSERV | NI_NUMERICHOST); 85 | int len = 0; 86 | if (ret == 0) { 87 | if (addr->sin6_port != 0) { 88 | { 89 | len = snprintf(buffer, capacity, "[%s]:%s", host, service); 90 | } 91 | } else { 92 | len = snprintf(buffer, capacity, "%s", host); 93 | } 94 | } 95 | if (len >= (int)capacity) { 96 | len = (int)capacity - 1; 97 | } 98 | 99 | return std::string(buffer, len); 100 | } 101 | 102 | std::string ipAddressToString(char *buffer, size_t capacity, const sockaddr *addr, size_t addrlen) { 103 | if (addr->sa_family == AF_INET6) { 104 | return ipv6AddressToString(buffer, capacity, (const struct sockaddr_in6 *)addr, addrlen); 105 | } 106 | return ipv4AddressToString(buffer, capacity, (const struct sockaddr_in *)addr, addrlen); 107 | } 108 | 109 | } // namespace mdns_cpp 110 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | project( 3 | mdns_cpp 4 | VERSION 0.1.0 5 | LANGUAGES CXX) 6 | 7 | option(MDNS_CPP_BUILD_EXAMPLE "Build example executables" ON) 8 | 9 | # Set the output of the libraries and executables. 10 | set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) 11 | set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) 12 | 13 | if(NOT CMAKE_CXX_STANDARD) 14 | set(CMAKE_CXX_STANDARD 20) 15 | set(CMAKE_CXX_EXTENSIONS OFF) 16 | endif() 17 | 18 | # ############################################################################## 19 | # library 20 | # ############################################################################## 21 | 22 | add_library(${PROJECT_NAME}) 23 | target_include_directories( 24 | ${PROJECT_NAME} PUBLIC $ 25 | $) 26 | target_sources( 27 | ${PROJECT_NAME} 28 | PRIVATE include/mdns_cpp/macros.hpp 29 | include/mdns_cpp/defs.hpp 30 | include/mdns_cpp/logger.hpp 31 | src/logger.cpp 32 | src/mdns.h 33 | src/mdns.cpp 34 | include/mdns_cpp/mdns.hpp 35 | src/utils.cpp 36 | include/mdns_cpp/utils.hpp) 37 | 38 | if(MSVC) 39 | target_compile_options(${PROJECT_NAME} PRIVATE /W4) 40 | else() 41 | target_compile_options( 42 | ${PROJECT_NAME} 43 | PRIVATE -Wall -Wextra -pedantic 44 | # mdns.h uses static functions in the header file 45 | -Wno-unused-function) 46 | endif() 47 | 48 | if(WIN32) 49 | target_link_libraries(${PROJECT_NAME} INTERFACE iphlpapi ws2_32) 50 | endif() 51 | add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) 52 | 53 | # ############################################################################## 54 | # example 55 | # ############################################################################## 56 | 57 | find_package(Threads REQUIRED) 58 | 59 | if(MDNS_CPP_BUILD_EXAMPLE) 60 | add_executable(${PROJECT_NAME}_service_example 61 | ${CMAKE_CURRENT_LIST_DIR}/example/service.cpp) 62 | target_link_libraries(${PROJECT_NAME}_service_example ${PROJECT_NAME} 63 | Threads::Threads) 64 | add_executable(${PROJECT_NAME}_discovery_example 65 | ${CMAKE_CURRENT_LIST_DIR}/example/discovery.cpp) 66 | target_link_libraries(${PROJECT_NAME}_discovery_example ${PROJECT_NAME} 67 | Threads::Threads) 68 | add_executable(${PROJECT_NAME}_query_example 69 | ${CMAKE_CURRENT_LIST_DIR}/example/query.cpp) 70 | target_link_libraries(${PROJECT_NAME}_query_example ${PROJECT_NAME} 71 | Threads::Threads) 72 | endif() 73 | 74 | # ############################################################################## 75 | # install 76 | # ############################################################################## 77 | 78 | include(GNUInstallDirs) 79 | include(CMakePackageConfigHelpers) 80 | 81 | install( 82 | TARGETS ${PROJECT_NAME} 83 | EXPORT ${PROJECT_NAME}_Targets 84 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 85 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 86 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 87 | 88 | write_basic_package_version_file( 89 | "${PROJECT_NAME}ConfigVersion.cmake" 90 | VERSION ${PROJECT_VERSION} 91 | COMPATIBILITY SameMajorVersion) 92 | 93 | configure_package_config_file( 94 | "${PROJECT_SOURCE_DIR}/cmake/Config.cmake.in" 95 | "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" 96 | INSTALL_DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake) 97 | 98 | install( 99 | EXPORT ${PROJECT_NAME}_Targets 100 | FILE ${PROJECT_NAME}Targets.cmake 101 | NAMESPACE ${PROJECT_NAME}:: 102 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake 103 | COMPONENT dev) 104 | 105 | install( 106 | FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" 107 | "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" 108 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake 109 | COMPONENT dev) 110 | install( 111 | DIRECTORY include/ ${CMAKE_CURRENT_BINARY_DIR}/include/ 112 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 113 | COMPONENT dev 114 | FILES_MATCHING 115 | PATTERN "*.hpp") 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mDNS-Cpp 2 | 3 | This library provides a cross-platform mDNS and DNS-DS library in C++. 4 | 5 | Currently this library is only a C++ wrapper around Mattias Jansson ([@maniccoder](https://twitter.com/maniccoder)) [mdns.h](https://github.com/mjansson/mdns) file -> all feature credits go to him. His work is licensed with the [The Unlicense](https://github.com/mjansson/mdns/blob/master/LICENSE) license. 6 | 7 | why does this exist: 8 | 9 | * better integration in C++ projects 10 | * better usage of modern C++ features 11 | * higher level API 12 | * pass logger function to library to use users logger 13 | 14 | ## Features 15 | 16 | The library does DNS-SD discovery and service as well as one-shot single record mDNS query and response. 17 | 18 | ## Build 19 | 20 | ```bash 21 | mkdir build 22 | cd build 23 | cmake .. 24 | make -j 25 | make install 26 | ``` 27 | 28 | ## Usage 29 | 30 | you can either install the library, include the library as subdirectory or use conan: `mdns_cpp/0.1.0@gocarlos/testing` 31 | 32 | ```cmake 33 | 34 | # add subdirectory 35 | add_subdirectory(vendor/mdns_cpp) 36 | # or install / use conan 37 | find_package(mdns_cpp) 38 | 39 | add_executable(main main.cpp) 40 | target_link_libraries(main PRIVATE mdns_cpp::mdns_cpp) 41 | ``` 42 | 43 | See the example folder for the different examples: 44 | 45 | ### Service 46 | 47 | ```c++ 48 | #include 49 | #include "mdns_cpp/mdns.hpp" 50 | 51 | int main() { 52 | mdns_cpp::mDNS mdns; 53 | mdns.setServiceHostname("AirForce1"); 54 | mdns.startService(); 55 | while (true) { 56 | std::this_thread::sleep_for(std::chrono::seconds(1)); 57 | } 58 | 59 | return 0; 60 | } 61 | ``` 62 | 63 | To listen for incoming DNS-SD requests and mDNS queries the socket can be opened/setup on the default interface by passing 0 as socket address in the call to the socket open/setup functions (the socket will receive data from all network interfaces). Then call `mdns_socket_listen` either on notification of incoming data, or by setting blocking mode and calling `mdns_socket_listen` to block until data is available and parsed. 64 | 65 | The entry type passed to the callback will be `MDNS_ENTRYTYPE_QUESTION` and record type `MDNS_RECORDTYPE_PTR`. Use the `mdns_record_parse_ptr` function to get the name string of the service record that was asked for. 66 | 67 | If service record name is `_services._dns-sd._udp.local.` you should use `mdns_discovery_answer` to send the records of the services you provide (DNS-SD). 68 | 69 | If the service record name is a service you provide, use `mdns_query_answer` to send the service details back in response to the query. 70 | 71 | See the test executable implementation for more details on how to handle the parameters to the given functions. 72 | 73 | ### Discovery 74 | 75 | ```c++ 76 | #include 77 | #include "mdns_cpp/mdns.hpp" 78 | 79 | int main() { 80 | mdns_cpp::mDNS mdns; 81 | mdns.executeDiscovery(); 82 | while (true) { 83 | std::this_thread::sleep_for(std::chrono::seconds(1)); 84 | } 85 | return 0; 86 | } 87 | ``` 88 | 89 | To send a DNS-SD service discovery request use `mdns_discovery_send`. This will send a single multicast packet (single PTR question record for `_services._dns-sd._udp.local.`) requesting a unicast response. 90 | 91 | To read discovery responses use `mdns_discovery_recv`. All records received since last call will be piped to the callback supplied in the function call. The entry type will be one of `MDNS_ENTRYTYPE_ANSWER`, `MDNS_ENTRYTYPE_AUTHORITY` and `MDNS_ENTRYTYPE_ADDITIONAL`. 92 | 93 | ### Query 94 | 95 | ```c++ 96 | #include 97 | #include "mdns_cpp/mdns.hpp" 98 | 99 | int main() { 100 | mdns_cpp::mDNS mdns; 101 | const std::string service = "_http._tcp.local."; 102 | mdns.executeQuery(service); 103 | while (true) { 104 | std::this_thread::sleep_for(std::chrono::seconds(1)); 105 | } 106 | return 0; 107 | } 108 | ``` 109 | 110 | To send a one-shot mDNS query for a single record use `mdns_query_send`. This will send a single multicast packet for the given record (single PTR question record, for example `_http._tcp.local.`). You can optionally pass in a query ID for the query for later filtering of responses (even though this is discouraged by the RFC), or pass 0 to be fully compliant. The function returns the query ID associated with this query, which if non-zero can be used to filter responses in `mdns_query_recv`. If the socket is bound to port 5353 a multicast response is requested, otherwise a unicast response. 111 | 112 | To read query responses use `mdns_query_recv`. All records received since last call will be piped to the callback supplied in the function call. If `query_id` parameter is non-zero the function will filter out any response with a query ID that does not match the given query ID. The entry type will be one of `MDNS_ENTRYTYPE_ANSWER`, `MDNS_ENTRYTYPE_AUTHORITY` and `MDNS_ENTRYTYPE_ADDITIONAL`. 113 | -------------------------------------------------------------------------------- /src/mdns.cpp: -------------------------------------------------------------------------------- 1 | #include "mdns_cpp/mdns.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "mdns.h" 8 | #include "mdns_cpp/logger.hpp" 9 | #include "mdns_cpp/macros.hpp" 10 | #include "mdns_cpp/utils.hpp" 11 | 12 | #ifdef _WIN32 13 | #include 14 | #else 15 | #include 16 | #include 17 | #include 18 | #endif 19 | #include 20 | 21 | namespace mdns_cpp { 22 | 23 | static mdns_record_txt_t txtbuffer[128]; 24 | 25 | int mDNS::openServiceSockets(int *sockets, int max_sockets) { 26 | // When receiving, each socket can receive data from all network interfaces 27 | // Thus we only need to open one socket for each address family 28 | int num_sockets = 0; 29 | 30 | // Call the client socket function to enumerate and get local addresses, 31 | // but not open the actual sockets 32 | openClientSockets(0, 0, 0); 33 | 34 | if (num_sockets < max_sockets) { 35 | sockaddr_in sock_addr{}; 36 | sock_addr.sin_family = AF_INET; 37 | #ifdef _WIN32 38 | sock_addr.sin_addr = in4addr_any; 39 | #else 40 | sock_addr.sin_addr.s_addr = INADDR_ANY; 41 | #endif 42 | sock_addr.sin_port = htons(MDNS_PORT); 43 | #ifdef __APPLE__ 44 | sock_addr.sin_len = sizeof(struct sockaddr_in); 45 | #endif 46 | const int sock = mdns_socket_open_ipv4(&sock_addr); 47 | if (sock >= 0) { 48 | sockets[num_sockets++] = sock; 49 | } 50 | } 51 | 52 | if (num_sockets < max_sockets) { 53 | sockaddr_in6 sock_addr{}; 54 | sock_addr.sin6_family = AF_INET6; 55 | sock_addr.sin6_addr = in6addr_any; 56 | sock_addr.sin6_port = htons(MDNS_PORT); 57 | #ifdef __APPLE__ 58 | sock_addr.sin6_len = sizeof(struct sockaddr_in6); 59 | #endif 60 | int sock = mdns_socket_open_ipv6(&sock_addr); 61 | if (sock >= 0) sockets[num_sockets++] = sock; 62 | } 63 | 64 | return num_sockets; 65 | } 66 | 67 | int mDNS::openClientSockets(int *sockets, int max_sockets, int port) { 68 | // When sending, each socket can only send to one network interface 69 | // Thus we need to open one socket for each interface and address family 70 | int num_sockets = 0; 71 | 72 | #ifdef _WIN32 73 | 74 | IP_ADAPTER_ADDRESSES *adapter_address = nullptr; 75 | ULONG address_size = 8000; 76 | unsigned int ret{}; 77 | unsigned int num_retries = 4; 78 | do { 79 | adapter_address = (IP_ADAPTER_ADDRESSES *)malloc(address_size); 80 | ret = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_ANYCAST, 0, adapter_address, 81 | &address_size); 82 | if (ret == ERROR_BUFFER_OVERFLOW) { 83 | free(adapter_address); 84 | adapter_address = 0; 85 | } else { 86 | break; 87 | } 88 | } while (num_retries-- > 0); 89 | 90 | if (!adapter_address || (ret != NO_ERROR)) { 91 | free(adapter_address); 92 | LogMessage() << "Failed to get network adapter addresses\n"; 93 | return num_sockets; 94 | } 95 | 96 | int first_ipv4 = 1; 97 | int first_ipv6 = 1; 98 | for (PIP_ADAPTER_ADDRESSES adapter = adapter_address; adapter; adapter = adapter->Next) { 99 | if (adapter->TunnelType == TUNNEL_TYPE_TEREDO) { 100 | continue; 101 | } 102 | if (adapter->OperStatus != IfOperStatusUp) { 103 | continue; 104 | } 105 | 106 | for (IP_ADAPTER_UNICAST_ADDRESS *unicast = adapter->FirstUnicastAddress; unicast; unicast = unicast->Next) { 107 | if (unicast->Address.lpSockaddr->sa_family == AF_INET) { 108 | struct sockaddr_in *saddr = (struct sockaddr_in *)unicast->Address.lpSockaddr; 109 | if ((saddr->sin_addr.S_un.S_un_b.s_b1 != 127) || (saddr->sin_addr.S_un.S_un_b.s_b2 != 0) || 110 | (saddr->sin_addr.S_un.S_un_b.s_b3 != 0) || (saddr->sin_addr.S_un.S_un_b.s_b4 != 1)) { 111 | int log_addr = 0; 112 | if (first_ipv4) { 113 | service_address_ipv4_ = saddr->sin_addr.S_un.S_addr; 114 | first_ipv4 = 0; 115 | log_addr = 1; 116 | } 117 | has_ipv4_ = 1; 118 | if (num_sockets < max_sockets) { 119 | saddr->sin_port = htons((unsigned short)port); 120 | int sock = mdns_socket_open_ipv4(saddr); 121 | if (sock >= 0) { 122 | sockets[num_sockets++] = sock; 123 | log_addr = 1; 124 | } else { 125 | log_addr = 0; 126 | } 127 | } 128 | if (log_addr) { 129 | char buffer[128]; 130 | const auto addr = ipv4AddressToString(buffer, sizeof(buffer), saddr, sizeof(struct sockaddr_in)); 131 | MDNS_LOG << "Local IPv4 address: " << addr << "\n"; 132 | } 133 | } 134 | } else if (unicast->Address.lpSockaddr->sa_family == AF_INET6) { 135 | struct sockaddr_in6 *saddr = (struct sockaddr_in6 *)unicast->Address.lpSockaddr; 136 | static constexpr unsigned char localhost[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; 137 | static constexpr unsigned char localhost_mapped[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0x7f, 0, 0, 1}; 138 | if ((unicast->DadState == NldsPreferred) && memcmp(saddr->sin6_addr.s6_addr, localhost, 16) && 139 | memcmp(saddr->sin6_addr.s6_addr, localhost_mapped, 16)) { 140 | int log_addr = 0; 141 | if (first_ipv6) { 142 | memcpy(service_address_ipv6_, &saddr->sin6_addr, 16); 143 | first_ipv6 = 0; 144 | log_addr = 1; 145 | } 146 | has_ipv6_ = 1; 147 | if (num_sockets < max_sockets) { 148 | saddr->sin6_port = htons((unsigned short)port); 149 | int sock = mdns_socket_open_ipv6(saddr); 150 | if (sock >= 0) { 151 | sockets[num_sockets++] = sock; 152 | log_addr = 1; 153 | } else { 154 | log_addr = 0; 155 | } 156 | } 157 | if (log_addr) { 158 | char buffer[128]; 159 | const auto addr = ipv6AddressToString(buffer, sizeof(buffer), saddr, sizeof(struct sockaddr_in6)); 160 | MDNS_LOG << "Local IPv6 address: " << addr << "\n"; 161 | } 162 | } 163 | } 164 | } 165 | } 166 | 167 | free(adapter_address); 168 | 169 | #else 170 | 171 | struct ifaddrs *ifaddr = nullptr; 172 | struct ifaddrs *ifa = nullptr; 173 | 174 | if (getifaddrs(&ifaddr) < 0) { 175 | MDNS_LOG << "Unable to get interface addresses\n"; 176 | } 177 | 178 | int first_ipv4 = 1; 179 | int first_ipv6 = 1; 180 | for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { 181 | if (!ifa->ifa_addr) { 182 | continue; 183 | } 184 | 185 | if (ifa->ifa_addr->sa_family == AF_INET) { 186 | struct sockaddr_in *saddr = (struct sockaddr_in *)ifa->ifa_addr; 187 | if (saddr->sin_addr.s_addr != htonl(INADDR_LOOPBACK)) { 188 | int log_addr = 0; 189 | if (first_ipv4) { 190 | service_address_ipv4_ = saddr->sin_addr.s_addr; 191 | first_ipv4 = 0; 192 | log_addr = 1; 193 | } 194 | has_ipv4_ = 1; 195 | if (num_sockets < max_sockets) { 196 | saddr->sin_port = htons(port); 197 | int sock = mdns_socket_open_ipv4(saddr); 198 | if (sock >= 0) { 199 | sockets[num_sockets++] = sock; 200 | log_addr = 1; 201 | } else { 202 | log_addr = 0; 203 | } 204 | } 205 | if (log_addr) { 206 | char buffer[128]; 207 | const auto addr = ipv4AddressToString(buffer, sizeof(buffer), saddr, sizeof(struct sockaddr_in)); 208 | MDNS_LOG << "Local IPv4 address: " << addr << "\n"; 209 | } 210 | } 211 | } else if (ifa->ifa_addr->sa_family == AF_INET6) { 212 | struct sockaddr_in6 *saddr = (struct sockaddr_in6 *)ifa->ifa_addr; 213 | static constexpr unsigned char localhost[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; 214 | static constexpr unsigned char localhost_mapped[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0x7f, 0, 0, 1}; 215 | if (memcmp(saddr->sin6_addr.s6_addr, localhost, 16) && memcmp(saddr->sin6_addr.s6_addr, localhost_mapped, 16)) { 216 | int log_addr = 0; 217 | if (first_ipv6) { 218 | memcpy(service_address_ipv6_, &saddr->sin6_addr, 16); 219 | first_ipv6 = 0; 220 | log_addr = 1; 221 | } 222 | has_ipv6_ = 1; 223 | if (num_sockets < max_sockets) { 224 | saddr->sin6_port = htons(port); 225 | int sock = mdns_socket_open_ipv6(saddr); 226 | if (sock >= 0) { 227 | sockets[num_sockets++] = sock; 228 | log_addr = 1; 229 | } else { 230 | log_addr = 0; 231 | } 232 | } 233 | if (log_addr) { 234 | char buffer[128] = {}; 235 | const auto addr = ipv6AddressToString(buffer, sizeof(buffer), saddr, sizeof(struct sockaddr_in6)); 236 | MDNS_LOG << "Local IPv6 address: " << addr << "\n"; 237 | } 238 | } 239 | } 240 | } 241 | 242 | freeifaddrs(ifaddr); 243 | 244 | #endif 245 | 246 | return num_sockets; 247 | } 248 | 249 | static int query_callback(int sock, const struct sockaddr *from, size_t addrlen, mdns_entry_type_t entry, 250 | uint16_t query_id, uint16_t rtype, uint16_t rclass, uint32_t ttl, const void *data, 251 | size_t size, size_t name_offset, size_t name_length, size_t record_offset, 252 | size_t record_length, void *user_data) { 253 | (void)sizeof(sock); 254 | (void)sizeof(query_id); 255 | (void)sizeof(name_length); 256 | (void)sizeof(user_data); 257 | 258 | static char addrbuffer[64]{}; 259 | static char namebuffer[256]{}; 260 | static char entrybuffer[256]{}; 261 | 262 | const auto fromaddrstr = ipAddressToString(addrbuffer, sizeof(addrbuffer), from, addrlen); 263 | const char *entrytype = 264 | (entry == MDNS_ENTRYTYPE_ANSWER) ? "answer" : ((entry == MDNS_ENTRYTYPE_AUTHORITY) ? "authority" : "additional"); 265 | mdns_string_t entrystr = mdns_string_extract(data, size, &name_offset, entrybuffer, sizeof(entrybuffer)); 266 | 267 | const int str_capacity = 1000; 268 | char str_buffer[str_capacity]={}; 269 | 270 | if (rtype == MDNS_RECORDTYPE_PTR) { 271 | mdns_string_t namestr = 272 | mdns_record_parse_ptr(data, size, record_offset, record_length, namebuffer, sizeof(namebuffer)); 273 | 274 | snprintf(str_buffer, str_capacity, "%s : %s %.*s PTR %.*s rclass 0x%x ttl %u length %d\n", fromaddrstr.data(), 275 | entrytype, MDNS_STRING_FORMAT(entrystr), MDNS_STRING_FORMAT(namestr), rclass, ttl, (int)record_length); 276 | } else if (rtype == MDNS_RECORDTYPE_SRV) { 277 | mdns_record_srv_t srv = 278 | mdns_record_parse_srv(data, size, record_offset, record_length, namebuffer, sizeof(namebuffer)); 279 | snprintf(str_buffer, str_capacity,"%s : %s %.*s SRV %.*s priority %d weight %d port %d\n", fromaddrstr.data(), entrytype, 280 | MDNS_STRING_FORMAT(entrystr), MDNS_STRING_FORMAT(srv.name), srv.priority, srv.weight, srv.port); 281 | } else if (rtype == MDNS_RECORDTYPE_A) { 282 | struct sockaddr_in addr; 283 | mdns_record_parse_a(data, size, record_offset, record_length, &addr); 284 | const auto addrstr = ipv4AddressToString(namebuffer, sizeof(namebuffer), &addr, sizeof(addr)); 285 | snprintf(str_buffer, str_capacity,"%s : %s %.*s A %s\n", fromaddrstr.data(), entrytype, MDNS_STRING_FORMAT(entrystr), addrstr.data()); 286 | } else if (rtype == MDNS_RECORDTYPE_AAAA) { 287 | struct sockaddr_in6 addr; 288 | mdns_record_parse_aaaa(data, size, record_offset, record_length, &addr); 289 | const auto addrstr = ipv6AddressToString(namebuffer, sizeof(namebuffer), &addr, sizeof(addr)); 290 | snprintf(str_buffer, str_capacity,"%s : %s %.*s AAAA %s\n", fromaddrstr.data(), entrytype, MDNS_STRING_FORMAT(entrystr), addrstr.data()); 291 | } else if (rtype == MDNS_RECORDTYPE_TXT) { 292 | size_t parsed = mdns_record_parse_txt(data, size, record_offset, record_length, txtbuffer, 293 | sizeof(txtbuffer) / sizeof(mdns_record_txt_t)); 294 | for (size_t itxt = 0; itxt < parsed; ++itxt) { 295 | if (txtbuffer[itxt].value.length) { 296 | snprintf(str_buffer, str_capacity,"%s : %s %.*s TXT %.*s = %.*s\n", fromaddrstr.data(), entrytype, MDNS_STRING_FORMAT(entrystr), 297 | MDNS_STRING_FORMAT(txtbuffer[itxt].key), MDNS_STRING_FORMAT(txtbuffer[itxt].value)); 298 | } else { 299 | snprintf(str_buffer, str_capacity,"%s : %s %.*s TXT %.*s\n", fromaddrstr.data(), entrytype, MDNS_STRING_FORMAT(entrystr), 300 | MDNS_STRING_FORMAT(txtbuffer[itxt].key)); 301 | } 302 | } 303 | } else { 304 | snprintf(str_buffer, str_capacity,"%s : %s %.*s type %u rclass 0x%x ttl %u length %d\n", fromaddrstr.data(), entrytype, 305 | MDNS_STRING_FORMAT(entrystr), rtype, rclass, ttl, (int)record_length); 306 | } 307 | MDNS_LOG << std::string(str_buffer); 308 | 309 | return 0; 310 | } 311 | 312 | int service_callback(int sock, const struct sockaddr *from, size_t addrlen, mdns_entry_type entry, uint16_t query_id, 313 | uint16_t rtype, uint16_t rclass, uint32_t ttl, const void *data, size_t size, size_t name_offset, 314 | size_t name_length, size_t record_offset, size_t record_length, void *user_data) { 315 | (void)sizeof(name_offset); 316 | (void)sizeof(name_length); 317 | (void)sizeof(ttl); 318 | 319 | if (static_cast(entry) != MDNS_ENTRYTYPE_QUESTION) { 320 | return 0; 321 | } 322 | 323 | char addrbuffer[64] = {0}; 324 | char namebuffer[256] = {0}; 325 | 326 | const auto fromaddrstr = ipAddressToString(addrbuffer, sizeof(addrbuffer), from, addrlen); 327 | if (rtype == static_cast(mdns_record_type::MDNS_RECORDTYPE_PTR)) { 328 | const mdns_string_t service = 329 | mdns_record_parse_ptr(data, size, record_offset, record_length, namebuffer, sizeof(namebuffer)); 330 | MDNS_LOG << fromaddrstr << " : question PTR " << std::string(service.str, service.length) << "\n"; 331 | 332 | const char dns_sd[] = "_services._dns-sd._udp.local."; 333 | const ServiceRecord *service_record = (const ServiceRecord *)user_data; 334 | const size_t service_length = strlen(service_record->service); 335 | char sendbuffer[256] = {0}; 336 | 337 | if ((service.length == (sizeof(dns_sd) - 1)) && (strncmp(service.str, dns_sd, sizeof(dns_sd) - 1) == 0)) { 338 | MDNS_LOG << " --> answer " << service_record->service << " \n"; 339 | mdns_discovery_answer(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), service_record->service, 340 | service_length); 341 | } else if ((service.length == service_length) && 342 | (strncmp(service.str, service_record->service, service_length) == 0)) { 343 | uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE); 344 | MDNS_LOG << " --> answer " << service_record->hostname << "." << service_record->service << " port " 345 | << service_record->port << " (" << (unicast ? "unicast" : "multicast") << ")\n"; 346 | if (!unicast) addrlen = 0; 347 | char txt_record[] = "asdf=1"; 348 | mdns_query_answer(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id, service_record->service, 349 | service_length, service_record->hostname, strlen(service_record->hostname), 350 | service_record->address_ipv4, service_record->address_ipv6, (uint16_t)service_record->port, 351 | txt_record, sizeof(txt_record)); 352 | } 353 | } else if (rtype == static_cast(mdns_record_type::MDNS_RECORDTYPE_SRV)) { 354 | mdns_record_srv_t service = 355 | mdns_record_parse_srv(data, size, record_offset, record_length, namebuffer, sizeof(namebuffer)); 356 | MDNS_LOG << fromaddrstr << " : question SRV " << std::string(service.name.str, service.name.length) << "\n"; 357 | #if 0 358 | if ((service.length == service_length) && 359 | (strncmp(service.str, service_record->service, service_length) == 0)) { 360 | uint16_t unicast = (rclass & MDNS_UNICAST_RESPONSE); 361 | printf(" --> answer %s.%s port %d (%s)\n", service_record->hostname, 362 | service_record->service, service_record->port, 363 | (unicast ? "unicast" : "multicast")); 364 | if (!unicast) 365 | addrlen = 0; 366 | char txt_record[] = "test=1"; 367 | mdns_query_answer(sock, from, addrlen, sendbuffer, sizeof(sendbuffer), query_id, 368 | service_record->service, service_length, service_record->hostname, 369 | strlen(service_record->hostname), service_record->address_ipv4, 370 | service_record->address_ipv6, (uint16_t)service_record->port, 371 | txt_record, sizeof(txt_record)); 372 | } 373 | #endif 374 | } 375 | return 0; 376 | } 377 | 378 | mDNS::~mDNS() { stopService(); } 379 | 380 | void mDNS::startService() { 381 | if (running_) { 382 | stopService(); 383 | } 384 | 385 | running_ = true; 386 | worker_thread_ = std::thread([this]() { this->runMainLoop(); }); 387 | } 388 | 389 | void mDNS::stopService() { 390 | running_ = false; 391 | if (worker_thread_.joinable()) { 392 | worker_thread_.join(); 393 | } 394 | } 395 | 396 | bool mDNS::isServiceRunning() { return running_; } 397 | 398 | void mDNS::setServiceHostname(const std::string &hostname) { hostname_ = hostname; } 399 | 400 | void mDNS::setServicePort(std::uint16_t port) { port_ = port; } 401 | 402 | void mDNS::setServiceName(const std::string &name) { name_ = name; } 403 | 404 | void mDNS::setServiceTxtRecord(const std::string &txt_record) { txt_record_ = txt_record; } 405 | 406 | void mDNS::runMainLoop() { 407 | constexpr size_t number_of_sockets = 32; 408 | int sockets[number_of_sockets]; 409 | const int num_sockets = openServiceSockets(sockets, sizeof(sockets) / sizeof(sockets[0])); 410 | if (num_sockets <= 0) { 411 | const auto msg = "Error: Failed to open any client sockets"; 412 | MDNS_LOG << msg << "\n"; 413 | throw std::runtime_error(msg); 414 | } 415 | 416 | MDNS_LOG << "Opened " << std::to_string(num_sockets) << " socket" << (num_sockets ? "s" : "") 417 | << " for mDNS service\n"; 418 | MDNS_LOG << "Service mDNS: " << name_ << ":" << port_ << "\n"; 419 | MDNS_LOG << "Hostname: " << hostname_.data() << "\n"; 420 | 421 | constexpr size_t capacity = 2048u; 422 | std::shared_ptr buffer(malloc(capacity), free); 423 | ServiceRecord service_record{}; 424 | service_record.service = name_.data(); 425 | service_record.hostname = hostname_.data(); 426 | service_record.address_ipv4 = has_ipv4_ ? service_address_ipv4_ : 0; 427 | service_record.address_ipv6 = has_ipv6_ ? service_address_ipv6_ : 0; 428 | service_record.port = port_; 429 | 430 | // This is a crude implementation that checks for incoming queries 431 | while (running_) { 432 | int nfds = 0; 433 | fd_set readfs{}; 434 | FD_ZERO(&readfs); 435 | for (int isock = 0; isock < num_sockets; ++isock) { 436 | if (sockets[isock] >= nfds) nfds = sockets[isock] + 1; 437 | FD_SET(sockets[isock], &readfs); 438 | } 439 | 440 | if (select(nfds, &readfs, 0, 0, 0) >= 0) { 441 | for (int isock = 0; isock < num_sockets; ++isock) { 442 | if (FD_ISSET(sockets[isock], &readfs)) { 443 | mdns_socket_listen(sockets[isock], buffer.get(), capacity, service_callback, &service_record); 444 | } 445 | FD_SET(sockets[isock], &readfs); 446 | } 447 | } else { 448 | break; 449 | } 450 | } 451 | 452 | for (int isock = 0; isock < num_sockets; ++isock) { 453 | mdns_socket_close(sockets[isock]); 454 | } 455 | MDNS_LOG << "Closed socket " << (num_sockets ? "s" : "") << "\n"; 456 | } 457 | 458 | void mDNS::executeQuery(const std::string &service) { 459 | int sockets[32]; 460 | int query_id[32]; 461 | int num_sockets = openClientSockets(sockets, sizeof(sockets) / sizeof(sockets[0]), 0); 462 | 463 | if (num_sockets <= 0) { 464 | const auto msg = "Failed to open any client sockets"; 465 | MDNS_LOG << msg << "\n"; 466 | throw std::runtime_error(msg); 467 | } 468 | MDNS_LOG << "Opened " << num_sockets << " socket" << (num_sockets ? "s" : "") << " for mDNS query\n"; 469 | 470 | size_t capacity = 2048; 471 | void *buffer = malloc(capacity); 472 | void *user_data = 0; 473 | size_t records; 474 | 475 | MDNS_LOG << "Sending mDNS query: " << service << "\n"; 476 | for (int isock = 0; isock < num_sockets; ++isock) { 477 | query_id[isock] = mdns_query_send(sockets[isock], MDNS_RECORDTYPE_PTR, service.data(), strlen(service.data()), 478 | buffer, capacity, 0); 479 | if (query_id[isock] < 0) { 480 | MDNS_LOG << "Failed to send mDNS query: " << strerror(errno) << "\n"; 481 | } 482 | } 483 | 484 | // This is a simple implementation that loops for 5 seconds or as long as we 485 | // get replies 486 | int res{}; 487 | MDNS_LOG << "Reading mDNS query replies\n"; 488 | do { 489 | struct timeval timeout; 490 | timeout.tv_sec = 5; 491 | timeout.tv_usec = 0; 492 | 493 | int nfds = 0; 494 | fd_set readfs; 495 | FD_ZERO(&readfs); 496 | for (int isock = 0; isock < num_sockets; ++isock) { 497 | if (sockets[isock] >= nfds) nfds = sockets[isock] + 1; 498 | FD_SET(sockets[isock], &readfs); 499 | } 500 | 501 | records = 0; 502 | res = select(nfds, &readfs, 0, 0, &timeout); 503 | if (res > 0) { 504 | for (int isock = 0; isock < num_sockets; ++isock) { 505 | if (FD_ISSET(sockets[isock], &readfs)) { 506 | records += mdns_query_recv(sockets[isock], buffer, capacity, query_callback, user_data, query_id[isock]); 507 | } 508 | FD_SET(sockets[isock], &readfs); 509 | } 510 | } 511 | } while (res > 0); 512 | 513 | free(buffer); 514 | 515 | for (int isock = 0; isock < num_sockets; ++isock) { 516 | mdns_socket_close(sockets[isock]); 517 | } 518 | MDNS_LOG << "Closed socket" << (num_sockets ? "s" : "") << "\n"; 519 | } 520 | 521 | void mDNS::executeDiscovery() { 522 | int sockets[32]; 523 | int num_sockets = openClientSockets(sockets, sizeof(sockets) / sizeof(sockets[0]), 0); 524 | if (num_sockets <= 0) { 525 | const auto msg = "Failed to open any client sockets"; 526 | MDNS_LOG << msg << "\n"; 527 | throw std::runtime_error(msg); 528 | } 529 | 530 | MDNS_LOG << "Opened " << num_sockets << " socket" << (num_sockets ? "s" : "") << " for DNS-SD\n"; 531 | MDNS_LOG << "Sending DNS-SD discovery\n"; 532 | for (int isock = 0; isock < num_sockets; ++isock) { 533 | if (mdns_discovery_send(sockets[isock])) { 534 | MDNS_LOG << "Failed to send DNS-DS discovery: " << strerror(errno) << " \n"; 535 | } 536 | } 537 | 538 | size_t capacity = 2048; 539 | void *buffer = malloc(capacity); 540 | void *user_data = 0; 541 | size_t records; 542 | 543 | // This is a simple implementation that loops for 5 seconds or as long as we 544 | // get replies 545 | int res; 546 | MDNS_LOG << "Reading DNS-SD replies\n"; 547 | do { 548 | struct timeval timeout; 549 | timeout.tv_sec = 5; 550 | timeout.tv_usec = 0; 551 | 552 | int nfds = 0; 553 | fd_set readfs; 554 | FD_ZERO(&readfs); 555 | for (int isock = 0; isock < num_sockets; ++isock) { 556 | if (sockets[isock] >= nfds) nfds = sockets[isock] + 1; 557 | FD_SET(sockets[isock], &readfs); 558 | } 559 | 560 | records = 0; 561 | res = select(nfds, &readfs, 0, 0, &timeout); 562 | if (res > 0) { 563 | for (int isock = 0; isock < num_sockets; ++isock) { 564 | if (FD_ISSET(sockets[isock], &readfs)) { 565 | records += mdns_discovery_recv(sockets[isock], buffer, capacity, query_callback, user_data); 566 | } 567 | } 568 | } 569 | } while (res > 0); 570 | 571 | free(buffer); 572 | 573 | for (int isock = 0; isock < num_sockets; ++isock) { 574 | mdns_socket_close(sockets[isock]); 575 | } 576 | MDNS_LOG << "Closed socket" << (num_sockets ? "s" : "") << "\n"; 577 | } 578 | 579 | } // namespace mdns_cpp 580 | -------------------------------------------------------------------------------- /src/mdns.h: -------------------------------------------------------------------------------- 1 | /* mdns.h - mDNS/DNS-SD library - Public Domain - 2017 Mattias Jansson 2 | * 3 | * This library provides a cross-platform mDNS and DNS-SD library in C. 4 | * The implementation is based on RFC 6762 and RFC 6763. 5 | * 6 | * The latest source code is always available at 7 | * 8 | * https://github.com/mjansson/mdns 9 | * 10 | * This library is put in the public domain; you can redistribute it and/or modify it without any 11 | * restrictions. 12 | * 13 | */ 14 | 15 | // clang-format off 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #ifdef _WIN32 26 | #include 27 | #include 28 | #define strncasecmp _strnicmp 29 | #else 30 | #include 31 | #include 32 | #include 33 | #endif 34 | 35 | #ifdef __cplusplus 36 | extern "C" { 37 | #endif 38 | 39 | #define MDNS_INVALID_POS ((size_t)-1) 40 | 41 | #define MDNS_STRING_CONST(s) (s), (sizeof((s)) - 1) 42 | #define MDNS_STRING_FORMAT(s) (int)((s).length), s.str 43 | 44 | #define MDNS_POINTER_OFFSET(p, ofs) ((void*)((char*)(p) + (ptrdiff_t)(ofs))) 45 | #define MDNS_POINTER_OFFSET_CONST(p, ofs) ((const void*)((const char*)(p) + (ptrdiff_t)(ofs))) 46 | #define MDNS_POINTER_DIFF(a, b) ((size_t)((const char*)(a) - (const char*)(b))) 47 | 48 | #define MDNS_PORT 5353 49 | #define MDNS_UNICAST_RESPONSE 0x8000U 50 | #define MDNS_CACHE_FLUSH 0x8000U 51 | 52 | enum mdns_record_type { 53 | MDNS_RECORDTYPE_IGNORE = 0, 54 | // Address 55 | MDNS_RECORDTYPE_A = 1, 56 | // Domain Name pointer 57 | MDNS_RECORDTYPE_PTR = 12, 58 | // Arbitrary text string 59 | MDNS_RECORDTYPE_TXT = 16, 60 | // IP6 Address [Thomson] 61 | MDNS_RECORDTYPE_AAAA = 28, 62 | // Server Selection [RFC2782] 63 | MDNS_RECORDTYPE_SRV = 33 64 | }; 65 | 66 | enum mdns_entry_type { 67 | MDNS_ENTRYTYPE_QUESTION = 0, 68 | MDNS_ENTRYTYPE_ANSWER = 1, 69 | MDNS_ENTRYTYPE_AUTHORITY = 2, 70 | MDNS_ENTRYTYPE_ADDITIONAL = 3 71 | }; 72 | 73 | enum mdns_class { MDNS_CLASS_IN = 1 }; 74 | 75 | typedef enum mdns_record_type mdns_record_type_t; 76 | typedef enum mdns_entry_type mdns_entry_type_t; 77 | typedef enum mdns_class mdns_class_t; 78 | 79 | typedef int (*mdns_record_callback_fn)(int sock, const struct sockaddr* from, size_t addrlen, 80 | mdns_entry_type_t entry, uint16_t query_id, uint16_t rtype, 81 | uint16_t rclass, uint32_t ttl, const void* data, size_t size, 82 | size_t name_offset, size_t name_length, size_t record_offset, 83 | size_t record_length, void* user_data); 84 | 85 | typedef struct mdns_string_t mdns_string_t; 86 | typedef struct mdns_string_pair_t mdns_string_pair_t; 87 | typedef struct mdns_record_srv_t mdns_record_srv_t; 88 | typedef struct mdns_record_txt_t mdns_record_txt_t; 89 | 90 | #ifdef _WIN32 91 | typedef int mdns_size_t; 92 | #else 93 | typedef size_t mdns_size_t; 94 | #endif 95 | 96 | struct mdns_string_t { 97 | const char* str; 98 | size_t length; 99 | }; 100 | 101 | struct mdns_string_pair_t { 102 | size_t offset; 103 | size_t length; 104 | int ref; 105 | }; 106 | 107 | struct mdns_record_srv_t { 108 | uint16_t priority; 109 | uint16_t weight; 110 | uint16_t port; 111 | mdns_string_t name; 112 | }; 113 | 114 | struct mdns_record_txt_t { 115 | mdns_string_t key; 116 | mdns_string_t value; 117 | }; 118 | 119 | struct mdns_header_t { 120 | uint16_t query_id; 121 | uint16_t flags; 122 | uint16_t questions; 123 | uint16_t answer_rrs; 124 | uint16_t authority_rrs; 125 | uint16_t additional_rrs; 126 | }; 127 | 128 | // mDNS/DNS-SD public API 129 | 130 | //! Open and setup a IPv4 socket for mDNS/DNS-SD. To bind the socket to a specific interface, 131 | // pass in the appropriate socket address in saddr, otherwise pass a null pointer for INADDR_ANY. 132 | // To send one-shot discovery requests and queries pass a null pointer or set 0 as port to assign 133 | // a random user level ephemeral port. To run discovery service listening for incoming 134 | // discoveries and queries, you must set MDNS_PORT as port. 135 | static int 136 | mdns_socket_open_ipv4(struct sockaddr_in* saddr); 137 | 138 | //! Setup an already opened IPv4 socket for mDNS/DNS-SD. To bind the socket to a specific interface, 139 | // pass in the appropriate socket address in saddr, otherwise pass a null pointer for INADDR_ANY. 140 | // To send one-shot discovery requests and queries pass a null pointer or set 0 as port to assign 141 | // a random user level ephemeral port. To run discovery service listening for incoming 142 | // discoveries and queries, you must set MDNS_PORT as port. 143 | static int 144 | mdns_socket_setup_ipv4(int sock, struct sockaddr_in* saddr); 145 | 146 | //! Open and setup a IPv6 socket for mDNS/DNS-SD. To bind the socket to a specific interface, 147 | // pass in the appropriate socket address in saddr, otherwise pass a null pointer for in6addr_any. 148 | // To send one-shot discovery requests and queries pass a null pointer or set 0 as port to assign 149 | // a random user level ephemeral port. To run discovery service listening for incoming 150 | // discoveries and queries, you must set MDNS_PORT as port. 151 | static int 152 | mdns_socket_open_ipv6(struct sockaddr_in6* saddr); 153 | 154 | //! Setup an already opened IPv6 socket for mDNS/DNS-SD. To bind the socket to a specific interface, 155 | // pass in the appropriate socket address in saddr, otherwise pass a null pointer for in6addr_any. 156 | // To send one-shot discovery requests and queries pass a null pointer or set 0 as port to assign 157 | // a random user level ephemeral port. To run discovery service listening for incoming 158 | // discoveries and queries, you must set MDNS_PORT as port. 159 | static int 160 | mdns_socket_setup_ipv6(int sock, struct sockaddr_in6* saddr); 161 | 162 | //! Close a socket opened with mdns_socket_open_ipv4 and mdns_socket_open_ipv6. 163 | static void 164 | mdns_socket_close(int sock); 165 | 166 | //! Listen for incoming multicast DNS-SD and mDNS query requests. The socket should have been 167 | // opened on port MDNS_PORT using one of the mdns open or setup socket functions. Returns the 168 | // number of queries parsed. 169 | static size_t 170 | mdns_socket_listen(int sock, void* buffer, size_t capacity, mdns_record_callback_fn callback, 171 | void* user_data); 172 | 173 | //! Send a multicast DNS-SD reqeuest on the given socket to discover available services. Returns 174 | // 0 on success, or <0 if error. 175 | static int 176 | mdns_discovery_send(int sock); 177 | 178 | //! Recieve unicast responses to a DNS-SD sent with mdns_discovery_send. Any data will be piped to 179 | // the given callback for parsing. Returns the number of responses parsed. 180 | static size_t 181 | mdns_discovery_recv(int sock, void* buffer, size_t capacity, mdns_record_callback_fn callback, 182 | void* user_data); 183 | 184 | //! Send a unicast DNS-SD answer with a single record to the given address. Returns 0 if success, 185 | // or <0 if error. 186 | static int 187 | mdns_discovery_answer(int sock, const void* address, size_t address_size, void* buffer, 188 | size_t capacity, const char* record, size_t length); 189 | 190 | //! Send a multicast mDNS query on the given socket for the given service name. The supplied buffer 191 | // will be used to build the query packet. The query ID can be set to non-zero to filter responses, 192 | // however the RFC states that the query ID SHOULD be set to 0 for multicast queries. The query 193 | // will request a unicast response if the socket is bound to an ephemeral port, or a multicast 194 | // response if the socket is bound to mDNS port 5353. 195 | // Returns the used query ID, or <0 if error. 196 | static int 197 | mdns_query_send(int sock, mdns_record_type_t type, const char* name, size_t length, void* buffer, 198 | size_t capacity, uint16_t query_id); 199 | 200 | //! Receive unicast responses to a mDNS query sent with mdns_discovery_recv, optionally filtering 201 | // out any responses not matching the given query ID. Set the query ID to 0 to parse 202 | // all responses, even if it is not matching the query ID set in a specific query. Any data will 203 | // be piped to the given callback for parsing. Returns the number of responses parsed. 204 | static size_t 205 | mdns_query_recv(int sock, void* buffer, size_t capacity, mdns_record_callback_fn callback, 206 | void* user_data, int query_id); 207 | 208 | //! Send a unicast or multicast mDNS query answer with a single record to the given address. The 209 | // answer will be sent multicast if address size is 0, otherwise it will be sent unicast to the 210 | // given address. Use the top bit of the query class field (MDNS_UNICAST_RESPONSE) to determine 211 | // if the answer should be sent unicast (bit set) or multicast (bit not set). 212 | // Returns 0 if success, or <0 if error. 213 | static int 214 | mdns_query_answer(int sock, const void* address, size_t address_size, void* buffer, size_t capacity, 215 | uint16_t query_id, const char* service, size_t service_length, 216 | const char* hostname, size_t hostname_length, uint32_t ipv4, const uint8_t* ipv6, 217 | uint16_t port, const char* txt, size_t txt_length); 218 | 219 | // Internal functions 220 | 221 | static mdns_string_t 222 | mdns_string_extract(const void* buffer, size_t size, size_t* offset, char* str, size_t capacity); 223 | 224 | static int 225 | mdns_string_skip(const void* buffer, size_t size, size_t* offset); 226 | 227 | static int 228 | mdns_string_equal(const void* buffer_lhs, size_t size_lhs, size_t* ofs_lhs, const void* buffer_rhs, 229 | size_t size_rhs, size_t* ofs_rhs); 230 | 231 | static void* 232 | mdns_string_make(void* data, size_t capacity, const char* name, size_t length); 233 | 234 | static void* 235 | mdns_string_make_ref(void* data, size_t capacity, size_t ref_offset); 236 | 237 | static void* 238 | mdns_string_make_with_ref(void* data, size_t capacity, const char* name, size_t length, 239 | size_t ref_offset); 240 | 241 | static mdns_string_t 242 | mdns_record_parse_ptr(const void* buffer, size_t size, size_t offset, size_t length, 243 | char* strbuffer, size_t capacity); 244 | 245 | static mdns_record_srv_t 246 | mdns_record_parse_srv(const void* buffer, size_t size, size_t offset, size_t length, 247 | char* strbuffer, size_t capacity); 248 | 249 | static struct sockaddr_in* 250 | mdns_record_parse_a(const void* buffer, size_t size, size_t offset, size_t length, 251 | struct sockaddr_in* addr); 252 | 253 | static struct sockaddr_in6* 254 | mdns_record_parse_aaaa(const void* buffer, size_t size, size_t offset, size_t length, 255 | struct sockaddr_in6* addr); 256 | 257 | static size_t 258 | mdns_record_parse_txt(const void* buffer, size_t size, size_t offset, size_t length, 259 | mdns_record_txt_t* records, size_t capacity); 260 | 261 | // Implementations 262 | 263 | static int 264 | mdns_socket_open_ipv4(struct sockaddr_in* saddr) { 265 | int sock = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 266 | if (sock < 0) 267 | return -1; 268 | if (mdns_socket_setup_ipv4(sock, saddr)) { 269 | mdns_socket_close(sock); 270 | return -1; 271 | } 272 | return sock; 273 | } 274 | 275 | static int 276 | mdns_socket_setup_ipv4(int sock, struct sockaddr_in* saddr) { 277 | unsigned char ttl = 1; 278 | unsigned char loopback = 1; 279 | unsigned int reuseaddr = 1; 280 | struct ip_mreq req; 281 | 282 | setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuseaddr, sizeof(reuseaddr)); 283 | #ifdef SO_REUSEPORT 284 | setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuseaddr, sizeof(reuseaddr)); 285 | #endif 286 | setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (const char*)&ttl, sizeof(ttl)); 287 | setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, (const char*)&loopback, sizeof(loopback)); 288 | 289 | memset(&req, 0, sizeof(req)); 290 | req.imr_multiaddr.s_addr = htonl((((uint32_t)224U) << 24U) | ((uint32_t)251U)); 291 | if (saddr) 292 | req.imr_interface = saddr->sin_addr; 293 | if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&req, sizeof(req))) 294 | return -1; 295 | 296 | struct sockaddr_in sock_addr; 297 | if (!saddr) { 298 | saddr = &sock_addr; 299 | memset(saddr, 0, sizeof(struct sockaddr_in)); 300 | saddr->sin_family = AF_INET; 301 | saddr->sin_addr.s_addr = INADDR_ANY; 302 | #ifdef __APPLE__ 303 | saddr->sin_len = sizeof(struct sockaddr_in); 304 | #endif 305 | } else { 306 | setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (const char*)&saddr->sin_addr, 307 | sizeof(saddr->sin_addr)); 308 | #ifndef _WIN32 309 | saddr->sin_addr.s_addr = INADDR_ANY; 310 | #endif 311 | } 312 | 313 | if (bind(sock, (struct sockaddr*)saddr, sizeof(struct sockaddr_in))) 314 | return -1; 315 | 316 | #ifdef _WIN32 317 | unsigned long param = 1; 318 | ioctlsocket(sock, FIONBIO, ¶m); 319 | #else 320 | const int flags = fcntl(sock, F_GETFL, 0); 321 | fcntl(sock, F_SETFL, flags | O_NONBLOCK); 322 | #endif 323 | 324 | return 0; 325 | } 326 | 327 | static int 328 | mdns_socket_open_ipv6(struct sockaddr_in6* saddr) { 329 | int sock = (int)socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); 330 | if (sock < 0) 331 | return -1; 332 | if (mdns_socket_setup_ipv6(sock, saddr)) { 333 | mdns_socket_close(sock); 334 | return -1; 335 | } 336 | return sock; 337 | } 338 | 339 | static int 340 | mdns_socket_setup_ipv6(int sock, struct sockaddr_in6* saddr) { 341 | int hops = 1; 342 | unsigned int loopback = 1; 343 | unsigned int reuseaddr = 1; 344 | struct ipv6_mreq req; 345 | 346 | setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuseaddr, sizeof(reuseaddr)); 347 | #ifdef SO_REUSEPORT 348 | setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuseaddr, sizeof(reuseaddr)); 349 | #endif 350 | setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (const char*)&hops, sizeof(hops)); 351 | setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (const char*)&loopback, sizeof(loopback)); 352 | 353 | memset(&req, 0, sizeof(req)); 354 | req.ipv6mr_multiaddr.s6_addr[0] = 0xFF; 355 | req.ipv6mr_multiaddr.s6_addr[1] = 0x02; 356 | req.ipv6mr_multiaddr.s6_addr[15] = 0xFB; 357 | if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char*)&req, sizeof(req))) 358 | return -1; 359 | 360 | struct sockaddr_in6 sock_addr; 361 | if (!saddr) { 362 | saddr = &sock_addr; 363 | memset(saddr, 0, sizeof(struct sockaddr_in6)); 364 | saddr->sin6_family = AF_INET6; 365 | saddr->sin6_addr = in6addr_any; 366 | #ifdef __APPLE__ 367 | saddr->sin6_len = sizeof(struct sockaddr_in6); 368 | #endif 369 | } else { 370 | unsigned int ifindex = 0; 371 | setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, (const char*)&ifindex, sizeof(ifindex)); 372 | #ifndef _WIN32 373 | saddr->sin6_addr = in6addr_any; 374 | #endif 375 | } 376 | 377 | if (bind(sock, (struct sockaddr*)saddr, sizeof(struct sockaddr_in6))) 378 | return -1; 379 | 380 | #ifdef _WIN32 381 | unsigned long param = 1; 382 | ioctlsocket(sock, FIONBIO, ¶m); 383 | #else 384 | const int flags = fcntl(sock, F_GETFL, 0); 385 | fcntl(sock, F_SETFL, flags | O_NONBLOCK); 386 | #endif 387 | 388 | return 0; 389 | } 390 | 391 | static void 392 | mdns_socket_close(int sock) { 393 | #ifdef _WIN32 394 | closesocket(sock); 395 | #else 396 | close(sock); 397 | #endif 398 | } 399 | 400 | static int 401 | mdns_is_string_ref(uint8_t val) { 402 | return (0xC0 == (val & 0xC0)); 403 | } 404 | 405 | static mdns_string_pair_t 406 | mdns_get_next_substring(const void* rawdata, size_t size, size_t offset) { 407 | const uint8_t* buffer = (const uint8_t*)rawdata; 408 | mdns_string_pair_t pair = {MDNS_INVALID_POS, 0, 0}; 409 | if (!buffer[offset]) { 410 | pair.offset = offset; 411 | return pair; 412 | } 413 | if (mdns_is_string_ref(buffer[offset])) { 414 | if (size < offset + 2) 415 | return pair; 416 | 417 | offset = 0x3fff & ntohs(*(uint16_t*)MDNS_POINTER_OFFSET(buffer, offset)); 418 | if (offset >= size) 419 | return pair; 420 | 421 | pair.ref = 1; 422 | } 423 | 424 | size_t length = (size_t)buffer[offset++]; 425 | if (size < offset + length) 426 | return pair; 427 | 428 | pair.offset = offset; 429 | pair.length = length; 430 | 431 | return pair; 432 | } 433 | 434 | static int 435 | mdns_string_skip(const void* buffer, size_t size, size_t* offset) { 436 | size_t cur = *offset; 437 | mdns_string_pair_t substr; 438 | do { 439 | substr = mdns_get_next_substring(buffer, size, cur); 440 | if (substr.offset == MDNS_INVALID_POS) 441 | return 0; 442 | if (substr.ref) { 443 | *offset = cur + 2; 444 | return 1; 445 | } 446 | cur = substr.offset + substr.length; 447 | } while (substr.length); 448 | 449 | *offset = cur + 1; 450 | return 1; 451 | } 452 | 453 | static int 454 | mdns_string_equal(const void* buffer_lhs, size_t size_lhs, size_t* ofs_lhs, const void* buffer_rhs, 455 | size_t size_rhs, size_t* ofs_rhs) { 456 | size_t lhs_cur = *ofs_lhs; 457 | size_t rhs_cur = *ofs_rhs; 458 | size_t lhs_end = MDNS_INVALID_POS; 459 | size_t rhs_end = MDNS_INVALID_POS; 460 | mdns_string_pair_t lhs_substr; 461 | mdns_string_pair_t rhs_substr; 462 | do { 463 | lhs_substr = mdns_get_next_substring(buffer_lhs, size_lhs, lhs_cur); 464 | rhs_substr = mdns_get_next_substring(buffer_rhs, size_rhs, rhs_cur); 465 | if ((lhs_substr.offset == MDNS_INVALID_POS) || (rhs_substr.offset == MDNS_INVALID_POS)) 466 | return 0; 467 | if (lhs_substr.length != rhs_substr.length) 468 | return 0; 469 | if (strncasecmp((const char*)buffer_rhs + rhs_substr.offset, 470 | (const char*)buffer_lhs + lhs_substr.offset, rhs_substr.length)) 471 | return 0; 472 | if (lhs_substr.ref && (lhs_end == MDNS_INVALID_POS)) 473 | lhs_end = lhs_cur + 2; 474 | if (rhs_substr.ref && (rhs_end == MDNS_INVALID_POS)) 475 | rhs_end = rhs_cur + 2; 476 | lhs_cur = lhs_substr.offset + lhs_substr.length; 477 | rhs_cur = rhs_substr.offset + rhs_substr.length; 478 | } while (lhs_substr.length); 479 | 480 | if (lhs_end == MDNS_INVALID_POS) 481 | lhs_end = lhs_cur + 1; 482 | *ofs_lhs = lhs_end; 483 | 484 | if (rhs_end == MDNS_INVALID_POS) 485 | rhs_end = rhs_cur + 1; 486 | *ofs_rhs = rhs_end; 487 | 488 | return 1; 489 | } 490 | 491 | static mdns_string_t 492 | mdns_string_extract(const void* buffer, size_t size, size_t* offset, char* str, size_t capacity) { 493 | size_t cur = *offset; 494 | size_t end = MDNS_INVALID_POS; 495 | mdns_string_pair_t substr; 496 | mdns_string_t result; 497 | result.str = str; 498 | result.length = 0; 499 | char* dst = str; 500 | size_t remain = capacity; 501 | do { 502 | substr = mdns_get_next_substring(buffer, size, cur); 503 | if (substr.offset == MDNS_INVALID_POS) 504 | return result; 505 | if (substr.ref && (end == MDNS_INVALID_POS)) 506 | end = cur + 2; 507 | if (substr.length) { 508 | size_t to_copy = (substr.length < remain) ? substr.length : remain; 509 | memcpy(dst, (const char*)buffer + substr.offset, to_copy); 510 | dst += to_copy; 511 | remain -= to_copy; 512 | if (remain) { 513 | *dst++ = '.'; 514 | --remain; 515 | } 516 | } 517 | cur = substr.offset + substr.length; 518 | } while (substr.length); 519 | 520 | if (end == MDNS_INVALID_POS) 521 | end = cur + 1; 522 | *offset = end; 523 | 524 | result.length = capacity - remain; 525 | return result; 526 | } 527 | 528 | static size_t 529 | mdns_string_find(const char* str, size_t length, char c, size_t offset) { 530 | const void* found; 531 | if (offset >= length) 532 | return MDNS_INVALID_POS; 533 | found = memchr(str + offset, c, length - offset); 534 | if (found) 535 | return (size_t)((const char*)found - str); 536 | return MDNS_INVALID_POS; 537 | } 538 | 539 | static void* 540 | mdns_string_make(void* data, size_t capacity, const char* name, size_t length) { 541 | size_t pos = 0; 542 | size_t last_pos = 0; 543 | size_t remain = capacity; 544 | unsigned char* dest = (unsigned char*)data; 545 | while ((last_pos < length) && 546 | ((pos = mdns_string_find(name, length, '.', last_pos)) != MDNS_INVALID_POS)) { 547 | size_t sublength = pos - last_pos; 548 | if (sublength < remain) { 549 | *dest = (unsigned char)sublength; 550 | memcpy(dest + 1, name + last_pos, sublength); 551 | dest += sublength + 1; 552 | remain -= sublength + 1; 553 | } else { 554 | return 0; 555 | } 556 | last_pos = pos + 1; 557 | } 558 | if (last_pos < length) { 559 | size_t sublength = length - last_pos; 560 | if (sublength < remain) { 561 | *dest = (unsigned char)sublength; 562 | memcpy(dest + 1, name + last_pos, sublength); 563 | dest += sublength + 1; 564 | remain -= sublength + 1; 565 | } else { 566 | return 0; 567 | } 568 | } 569 | if (!remain) 570 | return 0; 571 | *dest++ = 0; 572 | return dest; 573 | } 574 | 575 | static void* 576 | mdns_string_make_ref(void* data, size_t capacity, size_t ref_offset) { 577 | if (capacity < 2) 578 | return 0; 579 | uint16_t* udata = (uint16_t*)data; 580 | *udata++ = htons(0xC000 | (uint16_t)ref_offset); 581 | return udata; 582 | } 583 | 584 | static void* 585 | mdns_string_make_with_ref(void* data, size_t capacity, const char* name, size_t length, 586 | size_t ref_offset) { 587 | void* remaindata = mdns_string_make(data, capacity, name, length); 588 | capacity -= MDNS_POINTER_DIFF(remaindata, data); 589 | if (!data || !capacity) 590 | return 0; 591 | return mdns_string_make_ref(MDNS_POINTER_OFFSET(remaindata, -1), capacity + 1, ref_offset); 592 | } 593 | 594 | static size_t 595 | mdns_records_parse(int sock, const struct sockaddr* from, size_t addrlen, const void* buffer, 596 | size_t size, size_t* offset, mdns_entry_type_t type, uint16_t query_id, 597 | size_t records, mdns_record_callback_fn callback, void* user_data) { 598 | size_t parsed = 0; 599 | int do_callback = (callback ? 1 : 0); 600 | for (size_t i = 0; i < records; ++i) { 601 | size_t name_offset = *offset; 602 | mdns_string_skip(buffer, size, offset); 603 | size_t name_length = (*offset) - name_offset; 604 | const uint16_t* data = (const uint16_t*)((const char*)buffer + (*offset)); 605 | 606 | uint16_t rtype = ntohs(*data++); 607 | uint16_t rclass = ntohs(*data++); 608 | uint32_t ttl = ntohl(*(const uint32_t*)(const void*)data); 609 | data += 2; 610 | uint16_t length = ntohs(*data++); 611 | 612 | *offset += 10; 613 | 614 | if (do_callback) { 615 | ++parsed; 616 | if (callback(sock, from, addrlen, type, query_id, rtype, rclass, ttl, buffer, size, 617 | name_offset, name_length, *offset, length, user_data)) 618 | do_callback = 0; 619 | } 620 | 621 | *offset += length; 622 | } 623 | return parsed; 624 | } 625 | 626 | static int 627 | mdns_unicast_send(int sock, const void* address, size_t address_size, const void* buffer, 628 | size_t size) { 629 | if (sendto(sock, (const char*)buffer, (mdns_size_t)size, 0, (const struct sockaddr*)address, 630 | (socklen_t)address_size) < 0) 631 | return -1; 632 | return 0; 633 | } 634 | 635 | static int 636 | mdns_multicast_send(int sock, const void* buffer, size_t size) { 637 | struct sockaddr_storage addr_storage; 638 | struct sockaddr_in addr; 639 | struct sockaddr_in6 addr6; 640 | struct sockaddr* saddr = (struct sockaddr*)&addr_storage; 641 | socklen_t saddrlen = sizeof(struct sockaddr_storage); 642 | if (getsockname(sock, saddr, &saddrlen)) 643 | return -1; 644 | if (saddr->sa_family == AF_INET6) { 645 | memset(&addr6, 0, sizeof(addr6)); 646 | addr6.sin6_family = AF_INET6; 647 | #ifdef __APPLE__ 648 | addr6.sin6_len = sizeof(addr6); 649 | #endif 650 | addr6.sin6_addr.s6_addr[0] = 0xFF; 651 | addr6.sin6_addr.s6_addr[1] = 0x02; 652 | addr6.sin6_addr.s6_addr[15] = 0xFB; 653 | addr6.sin6_port = htons((unsigned short)MDNS_PORT); 654 | saddr = (struct sockaddr*)&addr6; 655 | saddrlen = sizeof(addr6); 656 | } else { 657 | memset(&addr, 0, sizeof(addr)); 658 | addr.sin_family = AF_INET; 659 | #ifdef __APPLE__ 660 | addr.sin_len = sizeof(addr); 661 | #endif 662 | addr.sin_addr.s_addr = htonl((((uint32_t)224U) << 24U) | ((uint32_t)251U)); 663 | addr.sin_port = htons((unsigned short)MDNS_PORT); 664 | saddr = (struct sockaddr*)&addr; 665 | saddrlen = sizeof(addr); 666 | } 667 | 668 | if (sendto(sock, (const char*)buffer, (mdns_size_t)size, 0, saddr, saddrlen) < 0) 669 | return -1; 670 | return 0; 671 | } 672 | 673 | static const uint8_t mdns_services_query[] = { 674 | // Query ID 675 | 0x00, 0x00, 676 | // Flags 677 | 0x00, 0x00, 678 | // 1 question 679 | 0x00, 0x01, 680 | // No answer, authority or additional RRs 681 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 682 | // _services._dns-sd._udp.local. 683 | 0x09, '_', 's', 'e', 'r', 'v', 'i', 'c', 'e', 's', 0x07, '_', 'd', 'n', 's', '-', 's', 'd', 684 | 0x04, '_', 'u', 'd', 'p', 0x05, 'l', 'o', 'c', 'a', 'l', 0x00, 685 | // PTR record 686 | 0x00, MDNS_RECORDTYPE_PTR, 687 | // QU (unicast response) and class IN 688 | 0x80, MDNS_CLASS_IN}; 689 | 690 | static int 691 | mdns_discovery_send(int sock) { 692 | return mdns_multicast_send(sock, mdns_services_query, sizeof(mdns_services_query)); 693 | } 694 | 695 | static size_t 696 | mdns_discovery_recv(int sock, void* buffer, size_t capacity, mdns_record_callback_fn callback, 697 | void* user_data) { 698 | struct sockaddr_in6 addr; 699 | struct sockaddr* saddr = (struct sockaddr*)&addr; 700 | socklen_t addrlen = sizeof(addr); 701 | memset(&addr, 0, sizeof(addr)); 702 | #ifdef __APPLE__ 703 | saddr->sa_len = sizeof(addr); 704 | #endif 705 | int ret = recvfrom(sock, (char*)buffer, (mdns_size_t)capacity, 0, saddr, &addrlen); 706 | if (ret <= 0) 707 | return 0; 708 | 709 | size_t data_size = (size_t)ret; 710 | size_t records = 0; 711 | uint16_t* data = (uint16_t*)buffer; 712 | 713 | uint16_t query_id = ntohs(*data++); 714 | uint16_t flags = ntohs(*data++); 715 | uint16_t questions = ntohs(*data++); 716 | uint16_t answer_rrs = ntohs(*data++); 717 | uint16_t authority_rrs = ntohs(*data++); 718 | uint16_t additional_rrs = ntohs(*data++); 719 | 720 | // According to RFC 6762 the query ID MUST match the sent query ID (which is 0 in our case) 721 | if (query_id || (flags != 0x8400)) 722 | return 0; // Not a reply to our question 723 | 724 | // It seems some implementations do not fill the correct questions field, 725 | // so ignore this check for now and only validate answer string 726 | /* 727 | if (questions != 1) 728 | return 0; 729 | */ 730 | 731 | int i; 732 | for (i = 0; i < questions; ++i) { 733 | size_t ofs = (size_t)((char*)data - (char*)buffer); 734 | size_t verify_ofs = 12; 735 | // Verify it's our question, _services._dns-sd._udp.local. 736 | if (!mdns_string_equal(buffer, data_size, &ofs, mdns_services_query, 737 | sizeof(mdns_services_query), &verify_ofs)) 738 | return 0; 739 | data = (uint16_t*)((char*)buffer + ofs); 740 | 741 | uint16_t rtype = ntohs(*data++); 742 | uint16_t rclass = ntohs(*data++); 743 | 744 | // Make sure we get a reply based on our PTR question for class IN 745 | if ((rtype != MDNS_RECORDTYPE_PTR) || ((rclass & 0x7FFF) != MDNS_CLASS_IN)) 746 | return 0; 747 | } 748 | 749 | int do_callback = 1; 750 | for (i = 0; i < answer_rrs; ++i) { 751 | size_t ofs = (size_t)((char*)data - (char*)buffer); 752 | size_t verify_ofs = 12; 753 | // Verify it's an answer to our question, _services._dns-sd._udp.local. 754 | size_t name_offset = ofs; 755 | int is_answer = mdns_string_equal(buffer, data_size, &ofs, mdns_services_query, 756 | sizeof(mdns_services_query), &verify_ofs); 757 | size_t name_length = ofs - name_offset; 758 | data = (uint16_t*)((char*)buffer + ofs); 759 | 760 | uint16_t rtype = ntohs(*data++); 761 | uint16_t rclass = ntohs(*data++); 762 | uint32_t ttl = ntohl(*(uint32_t*)(void*)data); 763 | data += 2; 764 | uint16_t length = ntohs(*data++); 765 | if (length >= (data_size - ofs)) 766 | return 0; 767 | 768 | if (is_answer && do_callback) { 769 | ++records; 770 | ofs = (size_t)((char*)data - (char*)buffer); 771 | if (callback(sock, saddr, addrlen, MDNS_ENTRYTYPE_ANSWER, query_id, rtype, rclass, ttl, 772 | buffer, data_size, name_offset, name_length, ofs, length, user_data)) 773 | do_callback = 0; 774 | } 775 | data = (uint16_t*)((char*)data + length); 776 | } 777 | 778 | size_t offset = (size_t)((char*)data - (char*)buffer); 779 | records += 780 | mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, 781 | MDNS_ENTRYTYPE_AUTHORITY, query_id, authority_rrs, callback, user_data); 782 | records += mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, 783 | MDNS_ENTRYTYPE_ADDITIONAL, query_id, additional_rrs, callback, 784 | user_data); 785 | 786 | return records; 787 | } 788 | 789 | static size_t 790 | mdns_socket_listen(int sock, void* buffer, size_t capacity, mdns_record_callback_fn callback, 791 | void* user_data) { 792 | struct sockaddr_in6 addr; 793 | struct sockaddr* saddr = (struct sockaddr*)&addr; 794 | socklen_t addrlen = sizeof(addr); 795 | memset(&addr, 0, sizeof(addr)); 796 | #ifdef __APPLE__ 797 | saddr->sa_len = sizeof(addr); 798 | #endif 799 | int ret = recvfrom(sock, (char*)buffer, (mdns_size_t)capacity, 0, saddr, &addrlen); 800 | if (ret <= 0) 801 | return 0; 802 | 803 | size_t data_size = (size_t)ret; 804 | uint16_t* data = (uint16_t*)buffer; 805 | 806 | uint16_t query_id = ntohs(*data++); 807 | uint16_t flags = ntohs(*data++); 808 | uint16_t questions = ntohs(*data++); 809 | /* 810 | This data is unused at the moment, skip 811 | uint16_t answer_rrs = ntohs(*data++); 812 | uint16_t authority_rrs = ntohs(*data++); 813 | uint16_t additional_rrs = ntohs(*data++); 814 | */ 815 | data += 3; 816 | 817 | size_t parsed = 0; 818 | for (int iquestion = 0; iquestion < questions; ++iquestion) { 819 | size_t question_offset = (size_t)((char*)data - (char*)buffer); 820 | size_t offset = question_offset; 821 | size_t verify_ofs = 12; 822 | if (mdns_string_equal(buffer, data_size, &offset, mdns_services_query, 823 | sizeof(mdns_services_query), &verify_ofs)) { 824 | if (flags || (questions != 1)) 825 | return 0; 826 | } else { 827 | offset = question_offset; 828 | if (!mdns_string_skip(buffer, data_size, &offset)) 829 | break; 830 | } 831 | size_t length = offset - question_offset; 832 | data = (uint16_t*)((char*)buffer + offset); 833 | 834 | uint16_t rtype = ntohs(*data++); 835 | uint16_t rclass = ntohs(*data++); 836 | 837 | // Make sure we get a question of class IN 838 | if ((rclass & 0x7FFF) != MDNS_CLASS_IN) 839 | return 0; 840 | 841 | if (callback) 842 | callback(sock, saddr, addrlen, MDNS_ENTRYTYPE_QUESTION, query_id, rtype, rclass, 0, 843 | buffer, data_size, question_offset, length, question_offset, length, 844 | user_data); 845 | 846 | ++parsed; 847 | } 848 | 849 | return parsed; 850 | } 851 | 852 | static int 853 | mdns_discovery_answer(int sock, const void* address, size_t address_size, void* buffer, 854 | size_t capacity, const char* record, size_t length) { 855 | if (capacity < (sizeof(mdns_services_query) + 32 + length)) 856 | return -1; 857 | 858 | uint16_t* data = (uint16_t*)buffer; 859 | // Basic reply structure 860 | memcpy(data, mdns_services_query, sizeof(mdns_services_query)); 861 | // Flags 862 | uint16_t* flags = data + 1; 863 | *flags = htons(0x8400U); 864 | // One answer 865 | uint16_t* answers = data + 3; 866 | *answers = htons(1); 867 | 868 | // Fill in answer PTR record 869 | data = (uint16_t*)((char*)buffer + sizeof(mdns_services_query)); 870 | // Reference _services._dns-sd._udp.local. string in question 871 | *data++ = htons(0xC000U | 12U); 872 | // Type 873 | *data++ = htons(MDNS_RECORDTYPE_PTR); 874 | // Rclass 875 | *data++ = htons(MDNS_CLASS_IN); 876 | // TTL 877 | *(uint32_t*)data = htonl(10); 878 | data += 2; 879 | // Record string length 880 | uint16_t* record_length = data++; 881 | uint8_t* record_data = (uint8_t*)data; 882 | size_t remain = capacity - (sizeof(mdns_services_query) + 10); 883 | record_data = (uint8_t*)mdns_string_make(record_data, remain, record, length); 884 | *record_length = htons((uint16_t)(record_data - (uint8_t*)data)); 885 | *record_data++ = 0; 886 | 887 | ptrdiff_t tosend = (char*)record_data - (char*)buffer; 888 | return mdns_unicast_send(sock, address, address_size, buffer, (size_t)tosend); 889 | } 890 | 891 | static int 892 | mdns_query_send(int sock, mdns_record_type_t type, const char* name, size_t length, void* buffer, 893 | size_t capacity, uint16_t query_id) { 894 | if (capacity < (17 + length)) 895 | return -1; 896 | 897 | uint16_t rclass = MDNS_CLASS_IN | MDNS_UNICAST_RESPONSE; 898 | 899 | struct sockaddr_storage addr_storage; 900 | struct sockaddr* saddr = (struct sockaddr*)&addr_storage; 901 | socklen_t saddrlen = sizeof(addr_storage); 902 | if (getsockname(sock, saddr, &saddrlen) == 0) { 903 | if ((saddr->sa_family == AF_INET) && 904 | (ntohs(((struct sockaddr_in*)saddr)->sin_port) == MDNS_PORT)) 905 | rclass &= ~MDNS_UNICAST_RESPONSE; 906 | else if ((saddr->sa_family == AF_INET6) && 907 | (ntohs(((struct sockaddr_in6*)saddr)->sin6_port) == MDNS_PORT)) 908 | rclass &= ~MDNS_UNICAST_RESPONSE; 909 | } 910 | 911 | uint16_t* data = (uint16_t*)buffer; 912 | // Query ID 913 | *data++ = htons(query_id); 914 | // Flags 915 | *data++ = 0; 916 | // Questions 917 | *data++ = htons(1); 918 | // No answer, authority or additional RRs 919 | *data++ = 0; 920 | *data++ = 0; 921 | *data++ = 0; 922 | // Fill in question 923 | // Name string 924 | data = (uint16_t*)mdns_string_make(data, capacity - 17, name, length); 925 | if (!data) 926 | return -1; 927 | // Record type 928 | *data++ = htons(type); 929 | //! Optional unicast response based on local port, class IN 930 | *data++ = htons(rclass); 931 | 932 | ptrdiff_t tosend = (char*)data - (char*)buffer; 933 | if (mdns_multicast_send(sock, buffer, (size_t)tosend)) 934 | return -1; 935 | return query_id; 936 | } 937 | 938 | static size_t 939 | mdns_query_recv(int sock, void* buffer, size_t capacity, mdns_record_callback_fn callback, 940 | void* user_data, int only_query_id) { 941 | struct sockaddr_in6 addr; 942 | struct sockaddr* saddr = (struct sockaddr*)&addr; 943 | socklen_t addrlen = sizeof(addr); 944 | memset(&addr, 0, sizeof(addr)); 945 | #ifdef __APPLE__ 946 | saddr->sa_len = sizeof(addr); 947 | #endif 948 | int ret = recvfrom(sock, (char*)buffer, (mdns_size_t)capacity, 0, saddr, &addrlen); 949 | if (ret <= 0) 950 | return 0; 951 | 952 | size_t data_size = (size_t)ret; 953 | uint16_t* data = (uint16_t*)buffer; 954 | 955 | uint16_t query_id = ntohs(*data++); 956 | uint16_t flags = ntohs(*data++); 957 | uint16_t questions = ntohs(*data++); 958 | uint16_t answer_rrs = ntohs(*data++); 959 | uint16_t authority_rrs = ntohs(*data++); 960 | uint16_t additional_rrs = ntohs(*data++); 961 | (void)sizeof(flags); 962 | 963 | if ((only_query_id > 0) && (query_id != only_query_id)) 964 | return 0; // Not a reply to the wanted one-shot query 965 | 966 | if (questions > 1) 967 | return 0; 968 | 969 | // Skip questions part 970 | int i; 971 | for (i = 0; i < questions; ++i) { 972 | size_t ofs = (size_t)((char*)data - (char*)buffer); 973 | if (!mdns_string_skip(buffer, data_size, &ofs)) 974 | return 0; 975 | data = (uint16_t*)((char*)buffer + ofs); 976 | uint16_t rtype = ntohs(*data++); 977 | uint16_t rclass = ntohs(*data++); 978 | (void)sizeof(rtype); 979 | (void)sizeof(rclass); 980 | } 981 | 982 | size_t records = 0; 983 | size_t offset = MDNS_POINTER_DIFF(data, buffer); 984 | records += mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, 985 | MDNS_ENTRYTYPE_ANSWER, query_id, answer_rrs, callback, user_data); 986 | records += 987 | mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, 988 | MDNS_ENTRYTYPE_AUTHORITY, query_id, authority_rrs, callback, user_data); 989 | records += mdns_records_parse(sock, saddr, addrlen, buffer, data_size, &offset, 990 | MDNS_ENTRYTYPE_ADDITIONAL, query_id, additional_rrs, callback, 991 | user_data); 992 | return records; 993 | } 994 | 995 | static int 996 | mdns_query_answer(int sock, const void* address, size_t address_size, void* buffer, size_t capacity, 997 | uint16_t query_id, const char* service, size_t service_length, 998 | const char* hostname, size_t hostname_length, uint32_t ipv4, const uint8_t* ipv6, 999 | uint16_t port, const char* txt, size_t txt_length) { 1000 | if (capacity < (sizeof(struct mdns_header_t) + 32 + service_length + hostname_length)) 1001 | return -1; 1002 | 1003 | int unicast = (address_size ? 1 : 0); 1004 | int use_ipv4 = (ipv4 != 0); 1005 | int use_ipv6 = (ipv6 != 0); 1006 | int use_txt = (txt && txt_length && (txt_length <= 255)); 1007 | 1008 | uint16_t question_rclass = (unicast ? MDNS_UNICAST_RESPONSE : 0) | MDNS_CLASS_IN; 1009 | uint16_t rclass = (unicast ? MDNS_CACHE_FLUSH : 0) | MDNS_CLASS_IN; 1010 | uint32_t ttl = (unicast ? 10 : 60); 1011 | uint32_t a_ttl = ttl; 1012 | 1013 | // Basic answer structure 1014 | struct mdns_header_t* header = (struct mdns_header_t*)buffer; 1015 | header->query_id = (address_size ? htons(query_id) : 0); 1016 | header->flags = htons(0x8400); 1017 | header->questions = htons(unicast ? 1 : 0); 1018 | header->answer_rrs = htons(1); 1019 | header->authority_rrs = 0; 1020 | header->additional_rrs = htons((unsigned short)(1 + use_ipv4 + use_ipv6 + use_txt)); 1021 | 1022 | void* data = MDNS_POINTER_OFFSET(buffer, sizeof(struct mdns_header_t)); 1023 | uint16_t* udata; 1024 | size_t remain, service_offset = 0, local_offset = 0, full_offset, host_offset; 1025 | 1026 | // Fill in question if unicast 1027 | if (unicast) { 1028 | service_offset = MDNS_POINTER_DIFF(data, buffer); 1029 | remain = capacity - service_offset; 1030 | data = mdns_string_make(data, remain, service, service_length); 1031 | local_offset = MDNS_POINTER_DIFF(data, buffer) - 7; 1032 | remain = capacity - MDNS_POINTER_DIFF(data, buffer); 1033 | if (!data || (remain <= 4)) 1034 | return -1; 1035 | 1036 | udata = (uint16_t*)data; 1037 | *udata++ = htons(MDNS_RECORDTYPE_PTR); 1038 | *udata++ = htons(question_rclass); 1039 | data = udata; 1040 | } 1041 | remain = capacity - MDNS_POINTER_DIFF(data, buffer); 1042 | 1043 | // Fill in answers 1044 | // PTR record for service 1045 | if (unicast) { 1046 | data = mdns_string_make_ref(data, remain, service_offset); 1047 | } else { 1048 | service_offset = MDNS_POINTER_DIFF(data, buffer); 1049 | remain = capacity - service_offset; 1050 | data = mdns_string_make(data, remain, service, service_length); 1051 | local_offset = MDNS_POINTER_DIFF(data, buffer) - 7; 1052 | } 1053 | remain = capacity - MDNS_POINTER_DIFF(data, buffer); 1054 | if (!data || (remain <= 10)) 1055 | return -1; 1056 | udata = (uint16_t*)data; 1057 | *udata++ = htons(MDNS_RECORDTYPE_PTR); 1058 | *udata++ = htons(rclass); 1059 | *(uint32_t*)udata = htonl(ttl); 1060 | udata += 2; 1061 | uint16_t* record_length = udata++; // length 1062 | data = udata; 1063 | // Make a string ..local. 1064 | full_offset = MDNS_POINTER_DIFF(data, buffer); 1065 | remain = capacity - full_offset; 1066 | data = mdns_string_make_with_ref(data, remain, hostname, hostname_length, service_offset); 1067 | remain = capacity - MDNS_POINTER_DIFF(data, buffer); 1068 | if (!data || (remain <= 10)) 1069 | return -1; 1070 | *record_length = htons((uint16_t)MDNS_POINTER_DIFF(data, record_length + 1)); 1071 | 1072 | // Fill in additional records 1073 | // SRV record for ..local. 1074 | data = mdns_string_make_ref(data, remain, full_offset); 1075 | remain = capacity - MDNS_POINTER_DIFF(data, buffer); 1076 | if (!data || (remain <= 10)) 1077 | return -1; 1078 | udata = (uint16_t*)data; 1079 | *udata++ = htons(MDNS_RECORDTYPE_SRV); 1080 | *udata++ = htons(rclass); 1081 | *(uint32_t*)udata = htonl(ttl); 1082 | udata += 2; 1083 | record_length = udata++; // length 1084 | *udata++ = htons(0); // priority 1085 | *udata++ = htons(0); // weight 1086 | *udata++ = htons(port); // port 1087 | // Make a string .local. 1088 | data = udata; 1089 | host_offset = MDNS_POINTER_DIFF(data, buffer); 1090 | remain = capacity - host_offset; 1091 | data = mdns_string_make_with_ref(data, remain, hostname, hostname_length, local_offset); 1092 | remain = capacity - MDNS_POINTER_DIFF(data, buffer); 1093 | if (!data || (remain <= 10)) 1094 | return -1; 1095 | *record_length = htons((uint16_t)MDNS_POINTER_DIFF(data, record_length + 1)); 1096 | 1097 | // A record for .local. 1098 | if (use_ipv4) { 1099 | data = mdns_string_make_ref(data, remain, host_offset); 1100 | remain = capacity - MDNS_POINTER_DIFF(data, buffer); 1101 | if (!data || (remain <= 14)) 1102 | return -1; 1103 | udata = (uint16_t*)data; 1104 | *udata++ = htons(MDNS_RECORDTYPE_A); 1105 | *udata++ = htons(rclass); 1106 | *(uint32_t*)udata = htonl(a_ttl); 1107 | udata += 2; 1108 | *udata++ = htons(4); // length 1109 | *(uint32_t*)udata = ipv4; // ipv4 address 1110 | udata += 2; 1111 | data = udata; 1112 | remain = capacity - MDNS_POINTER_DIFF(data, buffer); 1113 | } 1114 | 1115 | // AAAA record for .local. 1116 | if (use_ipv6) { 1117 | data = mdns_string_make_ref(data, remain, host_offset); 1118 | remain = capacity - MDNS_POINTER_DIFF(data, buffer); 1119 | if (!data || (remain <= 26)) 1120 | return -1; 1121 | udata = (uint16_t*)data; 1122 | *udata++ = htons(MDNS_RECORDTYPE_AAAA); 1123 | *udata++ = htons(rclass); 1124 | *(uint32_t*)udata = htonl(a_ttl); 1125 | udata += 2; 1126 | *udata++ = htons(16); // length 1127 | memcpy(udata, ipv6, 16); // ipv6 address 1128 | data = MDNS_POINTER_OFFSET(udata, 16); 1129 | remain = capacity - MDNS_POINTER_DIFF(data, buffer); 1130 | } 1131 | 1132 | // TXT record for ..local. 1133 | if (use_txt) { 1134 | data = mdns_string_make_ref(data, remain, full_offset); 1135 | remain = capacity - MDNS_POINTER_DIFF(data, buffer); 1136 | if (!data || (remain <= (11 + txt_length))) 1137 | return -1; 1138 | udata = (uint16_t*)data; 1139 | *udata++ = htons(MDNS_RECORDTYPE_TXT); 1140 | *udata++ = htons(rclass); 1141 | *(uint32_t*)udata = htonl(ttl); 1142 | udata += 2; 1143 | *udata++ = htons((unsigned short)(txt_length + 1)); // length 1144 | char* txt_record = (char*)udata; 1145 | *txt_record++ = (char)txt_length; 1146 | memcpy(txt_record, txt, txt_length); // txt record 1147 | data = MDNS_POINTER_OFFSET(txt_record, txt_length); 1148 | // Unused until multiple txt records are supported 1149 | // remain = capacity - MDNS_POINTER_DIFF(data, buffer); 1150 | } 1151 | 1152 | size_t tosend = MDNS_POINTER_DIFF(data, buffer); 1153 | if (address_size) 1154 | return mdns_unicast_send(sock, address, address_size, buffer, tosend); 1155 | return mdns_multicast_send(sock, buffer, tosend); 1156 | } 1157 | 1158 | static mdns_string_t 1159 | mdns_record_parse_ptr(const void* buffer, size_t size, size_t offset, size_t length, 1160 | char* strbuffer, size_t capacity) { 1161 | // PTR record is just a string 1162 | if ((size >= offset + length) && (length >= 2)) 1163 | return mdns_string_extract(buffer, size, &offset, strbuffer, capacity); 1164 | mdns_string_t empty = {0, 0}; 1165 | return empty; 1166 | } 1167 | 1168 | static mdns_record_srv_t 1169 | mdns_record_parse_srv(const void* buffer, size_t size, size_t offset, size_t length, 1170 | char* strbuffer, size_t capacity) { 1171 | mdns_record_srv_t srv; 1172 | memset(&srv, 0, sizeof(mdns_record_srv_t)); 1173 | // Read the priority, weight, port number and the discovery name 1174 | // SRV record format (http://www.ietf.org/rfc/rfc2782.txt): 1175 | // 2 bytes network-order unsigned priority 1176 | // 2 bytes network-order unsigned weight 1177 | // 2 bytes network-order unsigned port 1178 | // string: discovery (domain) name, minimum 2 bytes when compressed 1179 | if ((size >= offset + length) && (length >= 8)) { 1180 | const uint16_t* recorddata = (const uint16_t*)((const char*)buffer + offset); 1181 | srv.priority = ntohs(*recorddata++); 1182 | srv.weight = ntohs(*recorddata++); 1183 | srv.port = ntohs(*recorddata++); 1184 | offset += 6; 1185 | srv.name = mdns_string_extract(buffer, size, &offset, strbuffer, capacity); 1186 | } 1187 | return srv; 1188 | } 1189 | 1190 | static struct sockaddr_in* 1191 | mdns_record_parse_a(const void* buffer, size_t size, size_t offset, size_t length, 1192 | struct sockaddr_in* addr) { 1193 | memset(addr, 0, sizeof(struct sockaddr_in)); 1194 | addr->sin_family = AF_INET; 1195 | #ifdef __APPLE__ 1196 | addr->sin_len = sizeof(struct sockaddr_in); 1197 | #endif 1198 | if ((size >= offset + length) && (length == 4)) 1199 | addr->sin_addr.s_addr = *(const uint32_t*)((const char*)buffer + offset); 1200 | return addr; 1201 | } 1202 | 1203 | static struct sockaddr_in6* 1204 | mdns_record_parse_aaaa(const void* buffer, size_t size, size_t offset, size_t length, 1205 | struct sockaddr_in6* addr) { 1206 | memset(addr, 0, sizeof(struct sockaddr_in6)); 1207 | addr->sin6_family = AF_INET6; 1208 | #ifdef __APPLE__ 1209 | addr->sin6_len = sizeof(struct sockaddr_in6); 1210 | #endif 1211 | if ((size >= offset + length) && (length == 16)) 1212 | addr->sin6_addr = *(const struct in6_addr*)((const char*)buffer + offset); 1213 | return addr; 1214 | } 1215 | 1216 | static size_t 1217 | mdns_record_parse_txt(const void* buffer, size_t size, size_t offset, size_t length, 1218 | mdns_record_txt_t* records, size_t capacity) { 1219 | size_t parsed = 0; 1220 | const char* strdata; 1221 | size_t separator, sublength; 1222 | size_t end = offset + length; 1223 | 1224 | if (size < end) 1225 | end = size; 1226 | 1227 | while ((offset < end) && (parsed < capacity)) { 1228 | strdata = (const char*)buffer + offset; 1229 | sublength = *(const unsigned char*)strdata; 1230 | 1231 | ++strdata; 1232 | offset += sublength + 1; 1233 | 1234 | separator = 0; 1235 | for (size_t c = 0; c < sublength; ++c) { 1236 | // DNS-SD TXT record keys MUST be printable US-ASCII, [0x20, 0x7E] 1237 | if ((strdata[c] < 0x20) || (strdata[c] > 0x7E)) 1238 | break; 1239 | if (strdata[c] == '=') { 1240 | separator = c; 1241 | break; 1242 | } 1243 | } 1244 | 1245 | if (!separator) 1246 | continue; 1247 | 1248 | if (separator < sublength) { 1249 | records[parsed].key.str = strdata; 1250 | records[parsed].key.length = separator; 1251 | records[parsed].value.str = strdata + separator + 1; 1252 | records[parsed].value.length = sublength - (separator + 1); 1253 | } else { 1254 | records[parsed].key.str = strdata; 1255 | records[parsed].key.length = sublength; 1256 | } 1257 | 1258 | ++parsed; 1259 | } 1260 | 1261 | return parsed; 1262 | } 1263 | 1264 | #ifdef _WIN32 1265 | #undef strncasecmp 1266 | #endif 1267 | 1268 | #ifdef __cplusplus 1269 | } 1270 | #endif --------------------------------------------------------------------------------